【问题标题】:Javascript split array into n subarrays, size of chunks don't matter [closed]Javascript将数组拆分为n个子数组,块的大小无关紧要[关闭]
【发布时间】:2020-10-28 05:12:51
【问题描述】:

我想将一个数组拆分为 n 个子数组。 我不在乎每个数组中有多少元素,但元素必须分布在所有可用的子数组中。

像这样,解决方案 A 和 B 是两种方法,但 我正在寻找解决方案 A

a = [1,2,3,4,5,6,7,8,9]

into_subarrays(a, 2);

  • 解决方案 A => [[1,3,5,7,9],[2,4,6,8]]
  • 解决方案 B => [[1,2,3,4,5],[6,7,8,9]]

into_subarrays(a, 4);

  • 解决方案 A => [[1,5,9],[2,6],[3,7],[4,8]]
  • 解决方案 B => [[1,2,3],[4,5],[6,7],[8,9]]

into_subarrays(a, 6);

  • 解决方案 A => [[1,7],[2,8],[3,9],[4],[5],[6]]
  • 解决方案 B => [[1,2],[3,4],[5,6],[7],[8],[9]]

into_subarrays(a, 12);

  • 解决方案 A => [[1],[2],[3],[4],[5],[6],[7],[8],[9],[],[], []]
  • 解决方案 B => [[1],[2],[3],[4],[5],[6],[7],[8],[9],[],[], []]

我有这个解决方案,我只是想确保它尽可能高效:

into_subarrays(myArray, chunks=2){
  var a = myArray.slice(); //Copy array so that the original is not modified
  var i = 0;
  var result = [];
          
  while(a.length){
   //Create array if needed
   if (typeof result[i] == 'undefined'){
     result[i] = [];
   }

   result[i].push(a.shift());
   i++;
   i = (i == chunks) ? 0 : i; //Wrap around chunk selector
  }

  return result;
}

谢谢。

【问题讨论】:

  • 这个问题可能更适合codereview.stackexchange.com
  • 谢谢,我不知道它存在。我只是将我的答案作为解决方案发布并转到 codereview.stackexchange

标签: javascript arrays slice


【解决方案1】:

我可能会建议一些更紧凑的东西,works 20% faster:

const src = [...'abcdefghijklmn'],

      chunkArr = (arr, qty) =>
        arr.reduce((r,e,i) => 
          (r[i%qty].push(e),r), 
          [...Array(qty)].map(_ => []))
        
console.log(chunkArr(src, 4))
.as-console-wrapper{min-height:100%;}

【讨论】:

  • 这确实是解决方案 B,我正在寻找解决方案 A。
  • @Nelo :最新更新实现了您所谓的“解决方案 A”,它的运行速度比您当前的代码快 20%
  • Array.from 和 shift 只是慢(相对而言),并且 OP 的 while 循环方法破坏了 v8 打包选项。 Array(qty).fill().map(_=>[]) 并删除 ||[] 检查,问题解决。或者 ....fill([],qty,arr.length) 如果可以接受指向相同引用的空数组。或者停止尝试将其单行并使用 for 循环来填充其余部分。
  • @3limin4t0r :已修复,如果不再相关,请随时删除您的评论
  • @user120242 : 我更喜欢[...Array(qty)] 而不是Array(qty).fill(), it seems 不要搞砸性能
【解决方案2】:

关于您当前的方法,有几点需要注意。

  1. 我个人不会使用if (typeof result[i] == 'undefined')。相反,您可以省略typeof 并执行if (result[i] === undefined)。但是,由于您知道该值是一个数组或根本不存在,因此检查 if (!result[i]) 就足够了。

  2. 如果您使用余数运算符%,则可以合并行i++i = (i == chunks) ? 0 : i。导致i = (i + 1) % chunks

  3. 您的解决方案仅在需要新数组时添加一个空数组。当您尝试使用当前解决方案执行 into_subarrays(a, 12) 时,这将导致无效结果。使用Array.from({length: chunks}, () => []) 之类的东西预先创建正确数量的数组可能会更好。这也将消除对 if 语句的需要。

应用上述结果将类似于:

function into_subarrays(array, chunks = 2) {
  array = array.slice(); // Copy array so that the original is not modified
  const result = Array.from({length: chunks}, () => []);

  for (let i = 0; array.length; i = (i + 1) % chunks) {
    result[i].push(array.shift());
  }

  return result;
}

const array = [1,2,3,4,5,6,7,8,9];
console.log(into_subarrays(array, 4));
console.log(into_subarrays(array, 12));

如果您没有 shift 数组中的元素,您可以省略 slice

function into_subarrays(array, chunks = 2) {
  const result = Array.from({length: chunks}, () => []);

  for (let i = 0; i < array.length; ++i) {
    const chunk = result[i % chunks];
    chunk.push(array[i]);
  }

  return result;
}

const array = [1,2,3,4,5,6,7,8,9];
console.log(into_subarrays(array, 4));
console.log(into_subarrays(array, 12));

我想指出,这里还有一个机会可以使用 generator function 循环遍历生成的数组。

function* cycle(iterable) {
  while (true) yield* iterable;
}

function into_subarrays(array, chunks = 2) {
  const result = Array.from({length: chunks}, () => []);
  const cycler = cycle(result);
  
  for (const item of array) {
    const chunk = cycler.next().value;
    chunk.push(item);
  }

  return result;
}

const array = [1,2,3,4,5,6,7,8,9];
console.log(into_subarrays(array, 4));
console.log(into_subarrays(array, 12));

【讨论】:

    【解决方案3】:

    大多数微基准测试的差异将归结为这些优化:
    https://v8.dev/blog/elements-kinds

    您的问题需要更多上下文才能相关。取决于引擎/环境特定的优化、块大小、数据集大小和数据类型(利用打包优化)。性能特征可能会有所不同,并且它只是一种猜测,而只是一种看法,而不是事实,而现实世界的情况可能更可能不是你的瓶颈。
    例如:在 React 中,不变性为库的用户提供了性能优势,但 React 开发人员在内部使用了上述链接优化。
    尽管如此,性能的相关性还是有争议的,应该用现实世界的数据来衡量。这些优化不太可能适用于您。

    例如,在不必遍历整个数组并将所有内容存储在内存中的情况下,生成器方法不必遍历整个列表并构建整个列表可能会显着提高性能。在运行多次的 microbench 中,面临的挑战是使用具有本地访问优化的同构数组构建整个列表,最终只会增加开销。

    到目前为止,关于其他答案和您的演示代码的性能的一些要点:

    • Array.from is slow(相对于 V8(Chrome))
    • 使用shift(+push) 很慢
    • 访问超出实际长度的数组索引会破坏快速路径
    • 生成器会延迟性能,并且在 microbench 中针对 OP 的给定问题只会增加数组访问的开销
    • 对于小型阵列,几乎看不到任何差异。几乎都是对象初始化
    • 更大的块也可以缩小差距

    此处为基准:https://jsben.ch/JrtTv

    注意:调整数组大小和块大小会极大地改变结果

    由于避免了开销、对象初始化和打包优化,For 循环实现将优于所有其他答案。还控制了内存使用:

    array = [1,2,3,4,5,6,7,8,9]
    const chunk = (a, n) => {
      const res = []
      for(let i = 0; i < n; i++) {
        const r = []
        res.push(r)
        for(let j = 0; j < (a.length/n|0)+(i<a.length%n); j++)
          r.push(a[j*n+i])
      }
      return res
    }
    console.log(chunk(array, 4));
    console.log(chunk(array, 12));

    由于预分配(并确保数组索引优化)而快一点(但比 for 循环慢):

    a = [1,2,3,4,5,6,7,8,9]
    
    const chunk = (a, n) =>
      Array(n).fill().map((_,i)=>
        Array((a.length/n|0)+(i<a.length%n)).fill().map((_,j)=>a[j*n+i]))
        
        
    console.log(chunk(a,4))
    console.log(chunk(a,12))

    【讨论】:

    • 您能否详细说明 "Array.from 很慢" 声明?您如何以及从什么答案得出结论?您是否有任何仅测试数组创建变体的基准?
    • 这里是简单的基准测试:jsben.ch/XSfHE 上面的 V8 链接解释了其中的一部分。由于作为对象进行迭代(和特殊检查)。找不到带有讨论和长椅的 SO 线程。 (相对而言,这是一个数量级的差异,而不是现实世界的相关性)
    • 我个人更喜欢可读性而不是速度,除非速度是一个实际问题。因为我发现Array.from 方法最具描述性,所以我最倾向于使用该方法。当您根据实际情况调整基准时,它的表现也不错。 jsben.ch/c06eS 但是我同意你的观点,那些微优化通常不值得麻烦。
    • 所以是的,我不是在谈论现实世界的相关性,我试图在我的帖子中强烈强调这一点,甚至举出具体的例子。这极不可能成为您的瓶颈。我也没有谈到可读性,这也是主观的,特别是在这种情况下,老实说,我并没有真正看到 Array.from({length: n}) 比 Array(n) (.fill 如果你是分裂头发)。在这种情况下,OP 谈到了效率,客观地说,从纯粹的基准性能的角度来看,Array.from 总是比替代方案慢一个数量级。
    • 注意,即便如此,它仍然慢了 3 倍。说“它很慢”绝对是正确的说法。当你对它进行基准测试时,当一切都只是对高尔夫进行基准测试并且你利用本机级别的数组访问时,Array.from 开始影响数字。我不会指责任何人使用 Array.from,因为他们觉得它看起来不错。但我个人是看不出来的。我真的不明白 Array.from({length:n},mapFn) 比 Array(n).fill().map(mapFn) 更具可读性。我可以看到 [...Array(n)] 的优点。
    【解决方案4】:
    into_subarrays(myArray, chunks=2){
      var a = myArray.slice(); //Copy array so that the original is not modified
      var i = 0;
      var result = [];
              
      while(a.length){
       //Create array if needed
       if (typeof result[i] == 'undefined'){
         result[i] = [];
       }
    
       result[i].push(a.shift());
       i++;
       i = (i == chunks) ? 0 : i; //Wrap around chunk selector
      }
    
      return result;
    }
    

    【讨论】:

      猜你喜欢
      • 2014-12-22
      • 2012-06-27
      • 2020-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-19
      相关资源
      最近更新 更多