【问题标题】:Creating multidimensional arrays & matrices in Javascript在 Javascript 中创建多维数组和矩阵
【发布时间】:2016-08-26 10:24:53
【问题描述】:

试图创建一个函数mCreate(),给定一个数字集合返回一个多维数组(矩阵):

mCreate(2, 2, 2)    
//   [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]

当这个函数只处理 2 个级别的深度时,即:mCreate(2, 2) //[[0, 0], [0, 0]] 我知道要做 2 个级别,你可以使用 2 个嵌套的 for loops 但我遇到的问题是如何处理第 n 个参数.

是否可以通过递归更好地解决这个问题,否则我如何动态确定在给定参数数量的情况下我需要的嵌套for loops 的数量?

ps:最高效的方式会很棒但不是必需的

RE-EDIT - 使用 Benchmark.js 检查 perf 后,结果如下:

BenLesh x 82,043 ops/sec ±2.56% (83 runs sampled)
Phil-P x 205,852 ops/sec ±2.01% (81 runs sampled)
Brian x 252,508 ops/sec ±1.17% (89 runs sampled)
Rick-H x 287,988 ops/sec ±1.25% (82 runs sampled)
Rodney-R x 97,930 ops/sec ±1.67% (81 runs sampled)
Fastest is Rick-H

@briancavalier 也想出了一个很好的解决方案JSbin

const mCreate = (...sizes) => (initialValue) => _mCreate(sizes, initialValue, sizes.length-1, 0)

const _mCreate = (sizes, initialValue, len, index) =>
    Array.from({ length: sizes[index] }, () => 
        index === len ? initialValue : _mCreate(sizes, initialValue, len, index+1))
mCreate(2, 2, 2)(0)

【问题讨论】:

  • 你能解释一下你到底想达到什么目标吗?
  • function mCreate(...arg){}
  • @PranavCBalan 这只会返回[2, 2, 2] 而不是我所追求的:(
  • @cmdv : 你可以得到 n 个参数,然后使用一些方法实现你想要的
  • @RickHitchcock 它将包含 1 个元素

标签: javascript matrix multidimensional-array


【解决方案1】:

按照@Pranav 的建议,您应该使用arguments 对象。

递归+参数对象

function mCreate() {
  var args = arguments;
  var result = [];
  if (args.length > 1) {
    for (var i = 1; i < args.length; i++) {
      var new_args = Array.prototype.slice.call(args, 1);
      result.push(mCreate.apply(this, new_args));
    }
  } else {
    for (var i = 0; i < args[0]; i++) {
      result.push(0)
    }
  }
  return result;
}

function print(obj) {
  document.write("<pre>" + JSON.stringify(obj, 0, 4) + "</pre>");
}
print(mCreate(2, 2, 2, 2))

【讨论】:

  • 感谢唯一吸引我的是使用apply,因为这是一个相当缓慢的过程,但我喜欢递归:)
【解决方案2】:

这是一个非递归的解决方案:

function mCreate() {
  var result = 0, i;

  for(i = arguments.length - 1; i >= 0 ; i--) {
    result = new Array(arguments[i]).fill(result);
  }

  return JSON.parse(JSON.stringify(result));
}

JSON 函数用于模拟深度克隆,但这会导致函数无法执行。

function mCreate() {
  var result = 0, i;
  
  for(i = arguments.length - 1; i >= 0 ; i--) {
    result = new Array(arguments[i]).fill(result);
  }

  return JSON.parse(JSON.stringify(result));
}


console.log(JSON.stringify(mCreate(2, 2, 2)));
console.log(JSON.stringify(mCreate(1, 2, 3, 4)));
console.log(JSON.stringify(mCreate(5)));
console.log(JSON.stringify(mCreate(1, 5)));
console.log(JSON.stringify(mCreate(5, 1)));

var m = mCreate(1, 2, 3, 4);
m[0][1][1][3] = 4;
console.log(JSON.stringify(m));

【讨论】:

  • 这看起来很棒,你认为它会比递归选项更高效吗?
  • 是的,因为递归解决方案会将执行上下文添加到每个维度的堆栈中。
  • 哇,看起来真的很干净它也提高了性能!没想到会这样,虽然我正在运行的测试非常糟糕并且在 jsfiddle 上!!
  • 一旦您开始修改生成的数组的值,此解决方案就会出现小问题。对于var x = mCreate(2, 1);,以下是正确的:x[0] === x[1]。所以如果你做了x[0][0] = 1;你也会有x[1][0] === 1。在“克隆”数组时使用 .slice() 可以解决这个问题。
  • @rodneyrehm,好点子!不幸的是,.slice() 做了一个窄拷贝,而我们需要一个深拷贝。我的解决方案需要更多工作。
【解决方案3】:

要点是将create 的结果作为create 的第二个参数传递,除了最后一个(或第一个,取决于你如何看待它)实例:

function create(n, v) {
  let arr = Array(n || 0);
  if (v !== undefined) arr.fill(v);
  return arr;
}

create(2, create(2, 0)); // [[0,0],[0,0]]
create(2, create(2, create(2, 0))); // [[[0,0],[0,0]],[[0,0],[0,0]]]

DEMO

使用循环我们可以建立数组维度:

function loop(d, l) {
  var out = create(d, 0);
  for (var i = 0; i < l - 1; i++) {
    out = create(d, out);
  }
  return out;
}

loop(2,2) // [[0,0],[0,0]]
loop(2,3) // [[[0,0],[0,0]],[[0,0],[0,0]]]
loop(1,3) // [[[0]]]

DEMO

【讨论】:

    【解决方案4】:

    递归算法可能更容易推理,但通常它们不是必需的。在这种特殊情况下,迭代方法很简单。

    您的问题由两部分组成:

    1. 创建具有可变数量0-value 元素的数组
    2. 为先前创建的数组创建可变数量的数组

    这是我认为您正在尝试创建的实现:

    function nested() {
      // handle the deepest level first, because we need to generate the zeros
      var result = [];
      for (var zeros = arguments[arguments.length - 1]; zeros > 0; zeros--) {
        result.push(0);
      }
    
      // for every argument, walking backwards, we clone the
      // previous result as often as requested by that argument
      for (var i = arguments.length - 2; i >= 0; i--) {
        var _clone = [];
        for (var clones = arguments[i]; clones > 0; clones--) {
          // result.slice() returns a shallow copy
          _clone.push(result.slice(0));
        }
    
        result = _clone;
      }
    
      if (arguments.length > 2) {
        // the shallowly copying the array works fine for 2 dimensions,
        // but for higher dimensions, we need to compensate
        return JSON.parse(JSON.stringify(result));
      }
    
      return result;
    }
    

    由于编写算法只是解决方案的一半,因此这里进行测试以验证我们的函数是否确实按照我们想要的方式执行。我们通常会使用其中一个庞大的测试运行器(例如 mochaAVA)。但由于我不知道您的设置(如果有的话),我们将手动执行此操作:

    var tests = [
      {
        // the arguments we want to pass to the function.
        // translates to nested(2, 2)
        input: [2, 2],
        // the result we expect the function to return for
        // the given input
        output: [
          [0, 0],
          [0, 0]
        ]
      },
      {
        input: [2, 3],
        output: [
          [0, 0, 0],
          [0, 0, 0]
        ]
      },
      {
        input: [3, 2],
        output: [
          [0, 0],
          [0, 0],
          [0, 0]
        ]
      },
      {
        input: [3, 2, 1],
        output: [
          [
            [0], [0]
          ],
          [
            [0], [0]
          ],
          [
            [0], [0]
          ]
        ]
      },
    ];
    
    tests.forEach(function(test) {
      // execute the function with the input array as arguments
      var result = nested.apply(null, test.input);
      // verify the result is correct
      var matches = JSON.stringify(result) === JSON.stringify(test.output);
      if (!matches) {
        console.error('failed input', test.input);
        console.log('got', result, 'but expected', rest.output);
      } else {
        console.info('passed', test.input);
      }
    });
    

    由您来定义和处理边缘情况,例如 nested(3, 0)nested(0, 4)nested(3, -1)nested(-1, 2)

    【讨论】:

    • 感谢您的精彩解释,我只是要快速设置适当的性能测试,但使用来自 Phil-plückthun 的快速 jsfiddle,看起来像 @rick-hitchcock 它出来了 :-) jsfiddle.net/2v4zj76t
    • Rick 的解决方案不是克隆数组,因此产生的结果无法在没有副作用的情况下发生突变。如果这是一个问题并且您解决了这个问题,那么两个函数将做同样的事情并且性能应该相同。
    • 我更希望它克隆数组,因为我正在尝试将此函数用于我一直在尝试编写的 FP 库github.com/Cmdv/linearJs
    • @cmdv 如果 Math.js 已经具有您需要的功能,为什么不简单地将其作为依赖项加载并完成它呢?我的意思是,该库是 APL,已记录并经过测试。它有> 3k颗星。从我的立场来看,你是在浪费时间重新实现这个已经解决的问题……你甚至只能加载 zeros 模块:github.com/josdejong/mathjs/blob/master/lib/function/matrix/… - 为什么还要这样做?
    • 您的解决方案还需要一个深拷贝。见jsfiddle.net/8k5w0gvh。应该只有一个“2”
    【解决方案5】:

    一个简单的递归答案是这样的(在 ES2015 中):

    const mCreate = (...sizes) => 
        Array.from({ length: sizes[0] }, () => 
            sizes.length === 1 ? 0 : mCreate(...sizes.slice(1)));
    

    JS Bin here

    编辑:我想我会添加具有更高阶函数的初始化程序:

    const mCreate = (...sizes) => (initialValue) => 
        Array.from({ length: sizes[0] }, () => 
            sizes.length === 1 ? initialValue : mCreate(...sizes.slice(1))(initialValue));
    

    可以这样使用:

    mCreate(2, 2, 2)('hi'); 
    // [[["hi", "hi"], ["hi", "hi"]], [["hi", "hi"], ["hi", "hi"]]]
    

    JSBin of that

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      • 2011-02-11
      • 2019-01-04
      • 2020-12-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多