按整理顺序将阵列数组减少为on

时间:2019-02-20 22:58:56

标签: javascript arrays functional-programming

我正在尝试使用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这样的变异循环。

10 个答案:

答案 0 :(得分:8)

也许只是一个简单的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)

简单但可预测且可读的


避免每次循环

如果您需要避免使用forEach,则可以采用类似的方法:获取max child array lengthbuild a range of integers,它们是由for循环创建的({{1} }),将每个值映射到枢轴数组flatten the multi-dimensional array,然后filter移出空单元格。

首先分步进行,然后作为一个衬纸:

[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);

答案 1 :(得分:7)

使用Array.from()创建一个长度最长的子数组的新数组。要获得最长子数组的长度,请使用Array.map()来获取长度的数组并取最大项。

Array.from()的回调中,使用Array.reduceRight()Array.reduce()(取决于您想要的顺序)从每个子数组中收集项目。如果子数组中存在当前索引,则取该项。用Array.flat()展平子数组。

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);

答案 2 :(得分:6)

这是一个满足您指定的优雅标准的递归解决方案:

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)

先前的解决方案使用big steps来计算答案。这是一个递归解决方案,它需要small steps来计算答案:

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])

希望有帮助。

答案 3 :(得分:5)

我使用递归方法来避免突变。

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 :(得分:4)

有趣的解决方案

  1. 将索引添加为内部数组的前缀
  2. 展平数组
  3. 对数组进行排序
  4. 删除前缀

/usr/local/go/bin

答案 5 :(得分:4)

我已经看到了名为round-robin的问题,但是也许interleave是一个更好的名字。当然,mapreducefilter是功能过程,但并非所有功能程序都需要依赖它们。当这些是我们唯一知道如何使用的功能时,由于通常更合适,因此有时导致程序笨拙。

  • map产生一对一的结果。如果我们有4个子数组,我们的结果将有4个元素。 interleave产生的结果应等于合并后的子数组的长度,因此map只能使我们在其中有所发展。需要其他步骤才能获得最终结果。

  • reduce一次遍历输入元素以产生最终结果。在第一个reduce中,我们将获得第一个子数组,但是没有直接的方法来处理整个子数组,然后再移到下一个子数组。我们可以强制我们的程序使用reduce,但是这样做可以使我们将整理过程视为一种欺骗性过程,而不是实际的整理过程。

现实是您不限于使用这些原始功能过程。您可以采用直接编码其意图的方式来编写interleave。我认为interleave具有漂亮的递归定义。我认为在这里使用深度解构分配很好,因为函数的签名显示了interleave期望的数据的 shape ;数组的数组。 Mathematical induction使我们能够自然地处理程序的分支-

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使我们摆脱了思想thinking密的束缚。我不再需要笨拙地组合在一起的变形碎片来思考我的问题–我不再考虑数组索引或sortforEach,也不会考虑使用{{1}来改变状态},或使用push>进行比较。我也不必考虑array-like之类的不正当事物-哇,我们确实确实对JavaScript有多少了解!

上面,应该感到欣慰的是没有 依赖项。想象一下一个初学者在学习该程序:他/她只需要学习1)如何定义一个函数,2)解构语法,3)三元表达式。拼凑在一起的程序与无数小的依赖关系将要求学习者在获得对程序的直觉之前先熟悉每个程序。

也就是说,用于解构值的JavaScript语法不是最漂亮的,有时为了方便起见而进行交易以提高可读性-

Math.max

此处演变的依赖性为const interleave = ([ v, ...vs ], acc = []) => v === undefined ? acc : isEmpty (v) ? interleave (vs, acc) : interleave ( [ ...vs, tail (v) ] , [ ...acc, head (v) ] ) isEmptytail-

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" ]interleavemap 开始考虑filter,那么它们很可能会成为其中的一部分最终的解决方案。如果这是您的方法,那么在此答案的两个程序中都找不到reducemapfilter,这会让您感到惊讶。这里的教训是您成为所知道的囚徒。有时您需要忘记reducemap才能观察到其他问题具有独特的性质,因此尽管可能有效,但通用的方法不一定是最合适的方法

答案 6 :(得分:3)

在这里,我提供了一个生成器函数,该函数将按所需顺序产生值。如果将yieldpush替换为要返回的结果数组,则可以轻松地将其转换为返回数组的常规函数​​。

该算法将所有数组作为参数,然后获取每个数组的迭代器。然后,它进入主循环,在其中将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);
  }
}

答案 7 :(得分:3)

那呢?

  • 排序数组以将最长的数组放在第一位。
  • 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)
   

答案 8 :(得分:2)

这是我想出的...虽然在看到其他答案之后,此解决方案似乎体积更大了...并且仍然使用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++;
  }
});

答案 9 :(得分:0)

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]]