以排序顺序将数组数组减少到数组中

jim*_*web 21 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' ]
Run Code Online (Sandbox Code Playgroud)

只要它们在一起,具有相似索引的项目的顺序无关紧要,因此结果'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;
    })
Run Code Online (Sandbox Code Playgroud)

但它不是很漂亮而且我不喜欢它,特别是因为它在forEach方法中改变累加器的方式.我觉得必须有一个更优雅的方法.

我不敢相信之前没有人问过这个问题,但是我尝试了一堆不同的查询而无法找到它,所以请告诉我它是否在那里而我错过了它.有没有更好的办法?

为了澄清评论中的每个问题,我希望能够在不改变任何变量或数组的情况下执行此操作,因为我正在使用accumulator.splice和仅使用函数方法,例如.map,或者.reduce不使用类似的变异循环.forEach.

Kyl*_*Mit 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)
Run Code Online (Sandbox Code Playgroud)

简单,但可预测和可读


避免每个循环

如果你需要避免forEach,这里有一个类似的方法,你可以:获取最大子数组长度,构建一个由for循环([1,2,3,4])创建的整数范围,映射每个值以旋转数组,展平多个-dimensional数组,然后过滤掉空单元格.

首先是不连续的步骤,然后是一个衬里:

var input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["1st","2nd","3rd"]];
Run Code Online (Sandbox Code Playgroud)

多个步骤:

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);
Run Code Online (Sandbox Code Playgroud)

一个班轮:

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)
Run Code Online (Sandbox Code Playgroud)


Ori*_*ori 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);
Run Code Online (Sandbox Code Playgroud)


bir*_*ird 6

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

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))
Run Code Online (Sandbox Code Playgroud)


Aad*_*hah 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)));
}
Run Code Online (Sandbox Code Playgroud)

它可以直接翻译成Haskell代码:

collate :: [[a]] -> [a]
collate []  = []
collate xss = let yss = filter (not . null) xss
              in map head yss ++ collate (map tail yss)
Run Code Online (Sandbox Code Playgroud)

先前的解决方案采取了重大步骤来计算答案。这是一个递归解决方案,它需要一些小步骤来计算答案:

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]))];
}
Run Code Online (Sandbox Code Playgroud)

以下是等效的Haskell代码:

collate :: [[a]] -> [a]
collate []           = []
collate ([]:xss)     = collate xss
collate ((x:xs):xss) = x : collate (xss ++ [xs])
Run Code Online (Sandbox Code Playgroud)

希望能有所帮助。


Tha*_*you 5

我见过名为round-robin的问题,但也许interleave是一个更好的名字。当然,mapreduce、 和filter是函数式程序,但并非所有函数式程序都需要依赖它们。当这些是我们唯一知道如何使用的函数时,生成的程序有时会很尴尬,因为通常有更合适的函数。

  • map产生一对一的结果。如果我们有 4 个子数组,我们的结果将有 4 个元素。interleave应该产生一个等于组合子数组长度的结果,所以map只能让我们到达那里。需要额外的步骤才能获得最终结果。

  • reduce一次一个地迭代输入元素以产生最终结果。在第一次归约中,我们将获得第一个子数组,但在进入下一个子数组之前没有直接的方法来处理整个子数组。我们可以强制我们的程序使用reduce,但这样做会让我们将排序过程视为一个递归过程,而不是它实际上是什么。

事实上,您并不局限于使用这些原始函数过程。您可以interleave采用直接编码其意图的方式进行编写。我认为 ainterleave有一个漂亮的递归定义。我认为这里使用深度解构赋值很好,因为函数的签名显示了期望的数据的形状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" ]
Run Code Online (Sandbox Code Playgroud)

interleave让我们摆脱了狭隘思维的束缚。我不再需要根据那些笨拙地组合在一起的畸形部分来考虑我的问题 - 我不再考虑数组索引、 or sort、 or forEach、或使用 改变状态push,或使用>or进行比较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) ]
    )
Run Code Online (Sandbox Code Playgroud)

这里演变的依赖关系是isEmptytailhead-

const isEmpty = xs =>
  xs.length === 0

const head = ([ x, ...xs ]) =>
  x

const tail = ([ x, ...xs ]) =>
  xs
Run Code Online (Sandbox Code Playgroud)

功能是一样的——

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" ]
Run Code Online (Sandbox Code Playgroud)

在您自己的浏览器中验证以下结果 -

const interleave = ([ v, ...vs ], acc = []) =>
  v === undefined
    ? acc
: isEmpty (v)
    ? interleave (vs, acc)
: interleave
    ( [ ...vs, tail (v) ]
    , [ ...acc, head (v) ]
    )
Run Code Online (Sandbox Code Playgroud)

如果您开始考虑interleave使用mapfilterreduce,那么它们很可能会成为最终解决方案的一部分。如果这是您的方法,您应该会感到惊讶map,在这个答案的两个程序中看不到 、filter、 和。reduce这里的教训是你会成为你所知道的的囚徒。有时您需要忘记mapreduce观察其他问题具有独特的性质,因此通用方法虽然可能有效,但不一定是最合适的。