问题描述
我正在尝试使用reduce()
以“整理”顺序组合一组数组,以便具有相似索引的项目在一起。
例如:
input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["first","second","third"]]
output = [ 'first','1','uno','one','second','2','dos','two','third','3','three','4' ]
只要具有相似索引的项目在一起,它们的顺序无关紧要,因此'one','uno','1'...
的结果与上面的一样好。
如果可能的话,我想只使用不可变变量。
我有一个有效的方法:
const output = input.reduce((accumulator, currentArray, arrayIndex)=>{
currentArray.forEach((item,itemIndex)=>{
const newIndex = itemIndex*(arrayIndex+1);
accumulator.splice(newIndex<accumulator.length?newIndex:accumulator.length,0,item);
})
return accumulator;
})
但它不是很漂亮,我不喜欢它,特别是因为它在 forEach 方法中改变累加器的方式。 我觉得一定有更优雅的方法。
我不敢相信以前没有人问过这个问题,但我尝试了一堆不同的查询,但找不到它,所以请告诉我它是否在那里但我错过了它。 有没有更好的办法?
为了澄清评论中的每个问题,我希望能够在不改变任何变量或数组的情况下做到这一点,就像我对accumulator.splice
所做的那样,并且只使用诸如.map
或.reduce
类的函数方法而不是变异循环就像一个.forEach
。
1楼
KyleMit
8
2019-02-20 23:22:24
也许只是一个简单的for... i
循环,检查每个数组中是否有i
位置的项目
var input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["1st","2nd","3rd"]] var output = [] var maxLen = Math.max(...input.map(arr => arr.length)); for (i=0; i < maxLen; i++) { input.forEach(arr => { if (arr[i] !== undefined) output.push(arr[i]) }) } console.log(output)
简单,但可预测和可读
避免 For Each 循环
如果您需要避免forEach
,这里有一个类似的方法,您可以:获取,由 for 循环( [1,2,3,4]
)创建,映射每个值旋转数组,,然后掉空单元格。
首先在离散步骤中,然后作为单行:
var input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["1st","2nd","3rd"]];
多个步骤:
var maxLen = Math.max(...input.map(arr => arr.length));
var indexes = Array(maxLen).fill().map((_,i) => i);
var pivoted = indexes.map(i => input.map(arr => arr[i] ));
var flattened = pivoted.flat().filter(el => el !== undefined);
一个班轮:
var output = Array(Math.max(...input.map(arr => arr.length))).fill().map((_,i) => i)
.map(i => input.map(arr => arr[i] ))
.flat().filter(el => el !== undefined)
2楼
Ori Drori
7
2019-02-20 23:06:18
使用创建一个具有最长子数组长度的新数组。 要获取最长子数组的长度,请使用获取长度数组并取最大项。
在Array.from()
的回调中,使用或 (取决于您想要的顺序)从每个子数组中收集项目。
如果当前索引存在于子数组中,则取该项目。
使用平子数组。
const input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["first","second","third"]] const result = Array.from( { length: Math.max(...input.map(o => o.length)) }, (_, i) => input.reduceRight((r, o) => i < o.length ? [...r, o[i]] : r , []) ) .flat(); console.log(result);
3楼
bird
6
已采纳
2019-02-21 04:02:25
我用递归方法来避免突变。
let input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["first","second","third"]] function recursion(input, idx = 0) { let tmp = input.map(elm => elm[idx]) .filter(e => e !== undefined) return tmp[0] ? tmp.concat(recursion(input, idx + 1)) : [] } console.log(recursion(input))
4楼
Aadit M Shah
6
2019-02-21 05:05:14
这是一个满足您指定的优雅标准的递归解决方案:
const head = xs => xs[0]; const tail = xs => xs.slice(1); const notNull = xs => xs.length > 0; console.log(collate([ ["one", "two", "three"] , ["uno", "dos"] , ["1", "2", "3", "4"] , ["first", "second", "third"] ])); function collate(xss) { if (xss.length === 0) return []; const yss = xss.filter(notNull); return yss.map(head).concat(collate(yss.map(tail))); }
可以直接翻译成Haskell代码:
collate :: [[a]] -> [a]
collate [] = []
collate xss = let yss = filter (not . null) xss
in map head yss ++ collate (map tail yss)
之前的解决方案在计算答案方面采取了。 这是一个递归解决方案,它需要计算答案:
console.log(collate([ ["one", "two", "three"] , ["uno", "dos"] , ["1", "2", "3", "4"] , ["first", "second", "third"] ])); function collate(xss_) { if (xss_.length === 0) return []; const [xs_, ...xss] = xss_; if (xs_.length === 0) return collate(xss); const [x, ...xs] = xs_; return [x, ...collate(xss.concat([xs]))]; }
这是等效的 Haskell 代码:
collate :: [[a]] -> [a]
collate [] = []
collate ([]:xss) = collate xss
collate ((x:xs):xss) = x : collate (xss ++ [xs])
希望有帮助。
5楼
sumit
4
2019-02-20 23:25:07
有趣的解决方案
- 在内部数组上添加索引作为前缀
- 展平阵列
- 对数组进行排序
- 去掉前缀
let input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["first","second","third"]] let ranked=input.map(i=>i.map((j,k)=>k+'---'+j)).slice() console.log(ranked.flat().sort().map(i=>i.split('---')[1]));
6楼
Mulan
4
2019-02-21 06:11:55
我已经看到了一个叫做的问题,但也许interleave
是一个更好的名字。
当然, map
、 reduce
和filter
是函数式程序,但并不是所有的函数式程序都需要依赖它们。
当这些是我们唯一知道如何使用的函数时,生成的程序有时会很尴尬,因为通常有更合适的。
map
产生一对一的结果。 如果我们有 4 个子数组,我们的结果将有 4 个元素。interleave
应该产生一个等于组合子数组长度的结果,所以map
可能只能让我们到达那里。 需要额外的步骤才能获得最终结果。reduce
一次一个地遍历输入元素以产生最终结果。 在第一个 reduce 中,我们将获得第一个子数组,但是在移动到下一个子数组之前没有直接的方法来处理整个子数组。 我们可以强制我们的程序使用reduce
,但这样做会使我们将整理程序视为一个重新整理程序,而不是它实际的样子。
现实情况是,您并不仅限于使用这些原始功能过程。
您可以以直接编码其意图的方式编写interleave
。
我认为interleave
有一个漂亮的递归定义。
我认为在这里使用深度解构赋值很好,因为函数的签名显示了interleave
期望的数据的形状;
数组数组。
让我们可以自然地处理程序的分支——
const None = Symbol ('None') const interleave = ( [ [ v = None, ...vs ] = [] // first subarray , ...rest // rest of subarrays ] ) => v === None ? rest.length === 0 ? vs // base: no `v`, no `rest` : interleave (rest) // inductive: some `rest` : [ v, ...interleave ([ ...rest, vs ]) ] // inductive: some `v`, some `rest` const input = [ [ "one", "two", "three" ] , [ "uno", "dos" ] , [ "1", "2", "3", "4" ] , [ "first", "second", "third" ] ] console.log (interleave (input)) // [ "one", "uno", "1", "first", "two", "dos", "2", "second", "three", "3", "third", "4" ]
interleave
使我们摆脱了思想封闭的束缚。
我不再需要根据笨拙地组合在一起的畸形片段来考虑我的问题——我不是在考虑数组索引、 sort
、 forEach
或使用push
改变状态,或者使用>
或Math.max
进行比较。
我也不必考虑诸如反常事情——哇,我们真的认为我们对 JavaScript 的了解程度是理所当然的!
上面,应该感觉清爽,没有依赖。 想象一下接近这个程序的初学者:他/她只需要学习 1) 如何定义函数,2) 解构语法,3) 三元表达式。 由无数小依赖拼凑在一起的程序需要学习者在获得对程序的直觉之前熟悉每个程序。
也就是说,用于解构值的 JavaScript 语法并不是最漂亮的,有时为了提高可读性而进行交易——
const interleave = ([ v, ...vs ], acc = []) =>
v === undefined
? acc
: isEmpty (v)
? interleave (vs, acc)
: interleave
( [ ...vs, tail (v) ]
, [ ...acc, head (v) ]
)
这里演化出的依赖项是isEmpty
、 tail
和head
-
const isEmpty = xs =>
xs.length === 0
const head = ([ x, ...xs ]) =>
x
const tail = ([ x, ...xs ]) =>
xs
功能是一样的——
const input =
[ [ "one", "two", "three" ]
, [ "uno", "dos" ]
, [ "1", "2", "3", "4" ]
, [ "first", "second", "third" ]
]
console.log (interleave (input))
// [ "one", "uno", "1", "first", "two", "dos", "2", "second", "three", "3", "third", "4" ]
在您自己的浏览器中验证以下结果 -
const isEmpty = xs => xs.length === 0 const head = ([ x , ...xs ]) => x const tail = ([ x , ...xs ]) => xs const interleave = ([ v, ...vs ], acc = []) => v === undefined ? acc : isEmpty (v) ? interleave (vs, acc) : interleave ( [ ...vs, tail (v) ] , [ ...acc, head (v) ] ) const input = [ [ "one", "two", "three" ] , [ "uno", "dos" ] , [ "1", "2", "3", "4" ] , [ "first", "second", "third" ] ] console.log (interleave (input)) // [ "one", "uno", "1", "first", "two", "dos", "2", "second", "three", "3", "third", "4" ]
如果您开始考虑使用map
、 filter
和reduce
interleave
,那么它们很可能会成为最终解决方案的一部分。
如果这是您的方法,那么您应该惊讶地发现map
、 filter
和reduce
在此答案的两个程序中无处可见。
这里的教训是你成为你所知道的的囚徒。
有时您需要忘记map
和reduce
以观察其他问题具有独特的性质,因此通用方法虽然可能有效,但不一定是最合适的。
7楼
kamoroso94
3
2019-02-21 00:49:51
在这里,我提供了一个生成器函数,它将以所需的顺序生成值。
如果将yield
替换为push
送到要返回的结果数组,则可以轻松地将其转换为返回数组的常规函数??。
该算法将所有数组作为参数,然后获取每个数组的迭代器。
然后它进入主循环,将iters
数组视为队列,将迭代器放在前面,产生下一个生成的值,然后将其放回队列的末尾,除非它为空。
如果您将数组转换为一个链表,其中向前后添加需要恒定时间,则效率会提高,而数组上的shift
是将所有内容向下移动一个位置的线性时间。
function* collate(...arrays) {
const iters = arrays.map(a => a.values());
while(iters.length > 0) {
const iter = iters.shift();
const {done, value} = iter.next();
if(done) continue;
yield value;
iters.push(iter);
}
}
8楼
Matías Fidemraizer
3
2019-02-21 14:31:28
那这个呢?
- 对数组进行排序以将最长的放在第一位。
-
flatMap
它们,因此最长数组中每个项目的索引获取xs
中任何其他数组的每个索引。 -
过滤掉平面数组中
undefined
项目(通过获取超出每个可能数组范围的索引产生的项目)
const input = [ ["one", "two", "three"], ["uno", "dos"], ["1", "2", "3", "4"], ["first", "second", "third"] ] const arrayLengthComparer = (a, b) => b.length - a.length const collate = xs => { const [xs_, ...ys] = xs.sort (arrayLengthComparer) return xs_.flatMap ((x, i) => [x, ...ys.map (y => y[i])]) .filter (x => x) } const output = collate (input) console.log (output)
9楼
Nick Young
2
2019-02-21 16:19:24
这就是我想出的……虽然现在看到其他答案后,这个解决方案似乎更笨重……而且它仍然使用 forEach。 我很想知道不使用 forEach 的好处。
var input = [["1a", "2a", "3a"], ["1b"], ["1c", "2c", "3c", "4c", "5c", "6c", "7c"],["one","two","three","four","five","six","seven"],["uno","dos","tres"],["1","2","3","4","5","6","7","8","9","10","11"],["first","second","third","fourth"]]; // sort input array by length input = input.sort((a, b) => { return b.length - a.length; }); let output = input[0]; let multiplier = 2; document.writeln(output + "<br>"); input.forEach((arr, i1) => { if (i1 > 0) { let index = -1; arr.forEach((item) => { index = index + multiplier; output.splice(index, 0, item); }); document.writeln(output + "<br>"); multiplier++; } });
10楼
Christian Bongiorno
0
2021-04-26 18:44:13
Array.prototype.coallate = function (size) {
return [...Array(Math.ceil(this.length / size)).keys()].map(i => this.slice(i * size, size * (i + 1)));
};
const result = [0,1,2,3,4,5,6,7,8,9].coallate(3)
console.log(JSON.stringify(result));
结果: [[0,1,2],[3,4,5],[6,7,8],[9]]