【问题标题】:JavaScript; n-dimensional array creationJavaScript; n维数组创建
【发布时间】:2012-09-17 07:19:20
【问题描述】:

在为一种简单语言构建 JavaScript 解释器的过程中,我遇到了以下问题;

解析后,我们得到一个索引数组,它指定要修改的 n 维数组中的元素。例如,解析后:

a[1, 1, 1]

我们得到一个数组[1, 1, 1]。我正在使用的语言没有变量定义,因此变量在第一次使用时会被初始化。我的目标是能够创建这个 n 维数组,以便我可以将它放在变量表中(在上面的示例中,我们需要创建一个 3 维数组)。

简短的问题:

有没有办法在 JavaScript 中不使用使用eval() 创建一个 n 维数组?

【问题讨论】:

    标签: javascript arrays


    【解决方案1】:

    没有内置任何东西,但创建一个可以完成这项工作的函数非常容易:

    var genArray = function () {
        var arr, len, i;
        if(arguments.length > 0) {
            len = [].slice.call(arguments, 0, 1)[0];
            arr = new Array(len);
            for(i = 0; i < len; i++) {
                arr[i] = genArray.apply(null, [].slice.call(arguments, 1));
            }
        } else {
            return null; //or whatever you want to initialize values to.
        }
        return arr;
    };
    
    var a = genArray(3, 2); //is [[null, null],[null, null],[null, null]]
    var b = genArray(3, 1, 1); //is [[[null]],[[null]],[[null]]]
    
    a[0][1]; //is null
    b[1][0][0]; //is null
    b[1][0][0] = 3;
    b[1][0][0]; //is 3;
    b; //is [[[null]],[[3]],[[null]]]
    

    也许这会有所帮助?

    PS --

    我知道这似乎比必要的努力更多。但不幸的是,JavaScript 数组并不是真正的“数组”(如果“数组”是指一个连续的、索引的、不可变的内存块)。它们更像是大多数语言中的“地图”。因此,创建它们需要付出一定的努力。大多数语言创建多维数组没有问题,因为它们只是在做一些简单的乘法运算,然后是malloc()。但是对于 JavaScript,如果你想预先构造它们,你真的必须递归地生成你的数组。这很痛苦,但它确实证明了口译员需要付出的努力。

    去看看。

    【讨论】:

    • 聪明;谢谢。不过,还有一件事,您将如何访问已创建数组中的特定元素?
    • 我的意思是,因为我们不知道实际要处理多少个索引。
    • 嗯,不准确。假设我们已经创建了数组,然后我们将面临另一个索引数组[a, b, c]。您将如何继续查找array[a][b][c]?主要问题是您不知道会有多少索引。我知道可以使用eval() 来完成,但我希望有一个不那么丑陋的解决方案。
    • @Abody97 检查 Barmar 对其答案的更新。它也适用于我的代码生成的数组。
    • @Barmar 在他的编辑中得到了它。我想我必须接受他的回答,即使你是第一个。对此感到抱歉。
    【解决方案2】:

    在 Chrome 中测试:

    function createNDimArray(dimensions) {
        if (dimensions.length > 0) {
            var dim = dimensions[0];
            var rest = dimensions.slice(1);
            var newArray = new Array();
            for (var i = 0; i < dim; i++) {
                newArray[i] = createNDimArray(rest);
            }
            return newArray;
         } else {
            return undefined;
         }
     }
    

    然后createNDimArray([3, 2, 5]) 返回一个 3x2x5 数组。

    您可以使用类似的递归过程来访问索引在数组中的元素:

    function getElement(array, indices) {
        if (indices.length == 0) {
            return array;
        } else {
            return getElement(array[indices[0]], indices.slice(1));
        }
     }
    

    设置元素是类似的,留给读者作为练习。

    【讨论】:

    • 我讨厌在两个答案上发表相同的评论,但是,就像我对@Pete 说的那样:how would you access a specific element in the already-created array?
    • 一个建议:我认为null 是比undefined 更好的默认值。这样就很容易测试你是在数组的范围之外还是只是得到一个空值。但是 OP 没有指定,所以undefined 可能是他/她想要的。
    • 查看我的编辑以访问元素。将默认元素作为创建函数的参数将是微不足道的。
    • @Barmar 是的,可以的。谢谢!
    • getElement-函数似乎不正确,slice is not defined
    【解决方案3】:

    用于创建 n 维数组:

    function createNDimArray(dimensions) {
     var ret = undefined;
     if(dimensions.length==1){
        ret = new Array(dimensions[0]);
        for (var i = 0; i < dimensions[0]; i++)
            ret[i]=null; //or another value
        return ret;     
     }
     else{
        //recursion
        var rest = dimensions.slice(1);
        ret = new Array(dimensions[0]);
        for (var i = 0; i < dimensions[0]; i++)
            ret[i]=createNDimArray(rest);       
        return ret;
     }
    }
    

    【讨论】:

    • 尽管这个答案缺乏指导性帮助。这是列出的三个递归解决方案中最快的一个。这是一个很好的例子,说明为什么“硬编码”最后一次递归迭代会大大加快速度。与上面的其他createNDimArray(或最慢的genArray)相比,您获得的改进的速度因子随着尺寸越来越大而增加。稍微重构一下,这个解决方案可以用四行代码完成,而且速度可能更快。
    【解决方案4】:

    编辑:由于任何递归解决方案都会限制您可以创建的数组的大小......我在 PJs @ GitHub 库中制作了另一个解决方案。这个以伪即时速度运行,可以创建和管理任何大小、任何结构、在任何分支上具有任何维度的多维数组。它还可以模拟预填充和/或使用定制设计的节点对象。在这里查看:https://github.com/PimpTrizkit/PJs/wiki/14.-Complex-Multidimensional-Object--(pCMO.js)


    使用jfabrizio的修改版解决方案:

    function createNDimArray(dimensions) {
        var t, i = 0, s = dimensions[0], arr = new Array(s);
        if ( dimensions.length < 3 ) for ( t = dimensions[1] ; i < s ; ) arr[i++] = new Array(t);
        else for ( t = dimensions.slice(1) ; i < s ; ) arr[i++] = createNDimArray(t);
        return arr;
    }
    

    用法:

    var arr = createNDimArray([3, 2, 3]); 
    //  arr = [[[,,],[,,]],[[,,],[,,]],[[,,],[,,]]]
    console.log(arr[2][1]); // in FF: Array [ <3 empty slots> ]
    console.log("Falsy = " + (arr[2][1][0]?true:false) ); // Falsy = false
    

    我发现这要快得多。我可能会说,这可能是在 Javascript 中生成 N 维数组的最快方法。上面的这种重构有一些很好的速度提升。但是,最好的速度提升当然来自不预填充。此版本不预填充数组。它只返回一个完全创建的 Ns 长度的 N 维数组,其中最后一级只是一个空数组。如果您真的需要 null 值,我希望 arr[x][y][z]?arr[x][y][z]:null 就足够了。它是供我使用的。 :)

    如果您需要预填充,请使用他的原始版本。

    而且,如果你真的不在乎我做了什么;然后停止阅读。

    想要更多极客谈话吗?对于那些在那里学习的人来说,关于递归的一些事情。好了,攻略就到这里。在进行深度递归时,请记住最终级别。它是完成大部分工作的地方。在这种情况下,从字面上看,它是第 N 维。这是你的“有效载荷”,剩下的就是物流。在 jfab 的函数中,当dimensions.length 到达1 时,它的最后一维,它在第 N 维并执行有效载荷。这是创建空数组,或者在我的情况下,创建一个空数组。由于递归变得如此之深,每个维度都是最后一个维度的一个因素。当你到达第 N 维时,你将有很多函数调用,并且后勤对计算机来说变得很麻烦。在第 N 维,您将调用基本递归函数(在我们的例子中为createNDimArray)的有效负载次数比调用物流的次数要多。现在,就像在 jfab 的原始解决方案中一样,将有效负载的执行作为您在递归中做的第一件事(如果可能的话)通常是一件好事,特别是如果它很简单。在这里,通过将有效负载构建为最终的二维数组(而不仅仅是一个一维数组,只需返回一个new Array())。那么现在不必在这个级别上发生过多的函数调用。现在,当然,如果你想预填充数组,那么这个快捷方式并不总是有帮助。但更重要的是,预填充数组将是适当的有效负载。通过访问第 N 维上的每个项目,我们有效地删除了它。这样就少了一层函数调用,基本上第 N 维的有效负载实际上是在第 N-1 维上完成的。而且我们再也不会为了传递new Array() 而再次调用递归函数。不幸的是,对new Array(x)(通常)的调用并没有这样看。它的执行时间确实会随着x 的增大而增加。这实际上仍在访问第 N 维中的每个项目,但现在我们只使用本机代码执行一次,并包裹在一个紧凑而轻巧的循环中。现在我们要求createNDimArray 只能在N > 1 时调用,即从不用于创建一维数组。从理论上讲,您可能需要更大的 N,并在最后展开更多维度。基本上,带有if ( dimensions.length &lt; 3 ) 的行将读取类似&lt; 4&lt; 5 的内容,并且您必须将更多的for 循环环绕在那里,并且它们都需要自己的一组@987654340 @s ---所以我不确定这一切的效率有多高,因为您正在用类似的想法交易过多的函数调用和堆栈空间/操作,但在嵌入式 for 循环中 --- 但我想它可以加速如果您知道 N 始终高于某个水平,或者它仅适用于最终尺寸,则可以提升一些环境。就像这里一样,我在最后两个维度上做了。但是如果你展开太多,那么你的有效载荷本身就是一只熊。只有测试才能证明这是否值得。似乎堆栈空间是有限的,我想我记得我能够通过更多的展开来制作更大的数组。你可以制作一个数组的大小是有限制的。如果我这样做的话,在第 N 级为每个项目调用自己的递归解决方案具有最低限制.. 记得.. 正确.. 低得多。

    修改他的解决方案的下一部分只是物流,它只是一个简单的重构,以摆脱过多的块和代码。加入所有var 一起工作,就是这样。由于您需要一个arr 来返回,一旦循环结束,不妨先在一行上完成所有var 工作,幸运的是,四个vars 中的三个具有相同的初始化。请记住,如果可能,Javascript 可以在加入 , 时优化代码。这也使得代码更小。

    PT

    【讨论】:

      【解决方案5】:

      使用mapapplybind 函数的createNDimArray 的另一个版本:

      function createNDimArray(dims) {
          return dims.length === 1
              ? new Array(dims[0])
              : Array.apply(null, Array(dims[0])).map(createNDimensionalArray.bind(null, dims.slice(1)));
      }
      createNDimArray([3, 2, 5]); // returns 3x2x5 array
      

      【讨论】:

        【解决方案6】:

        创建 ND 阵列需要克隆嵌套的 ND 阵列。因此,您将需要一个适当的Array.prototype.clone() 方法,其余的很容易。据我所知,以下是 JS 中最简单、最有效的方法。

        Array.prototype.clone = function(){
          return this.reduce((p,c,i) => (p[i] = Array.isArray(c) ? c.clone() : c, p),[])
        }
        
        function arrayND(...n){
          return n.reduceRight((p,c) => c = (new Array(c)).fill().map(e => Array.isArray(p) ? p.clone() : p ));
        }
        
        var NDarr = arrayND(4,4,4,4,"."); // size of each dimension and the init value at the end
        console.log(JSON.stringify(NDarr))
        NDarr[0][1][2][3] = "kitty"; //access any location and change.
        console.log(JSON.stringify(NDarr))

        【讨论】:

        • 单独进入这个类似的线性解决方案stackoverflow.com/a/48013225[1, 3, 1, 4, 1].reduceRight((x, y) =&gt; new Array(y).fill().map(() =&gt; JSON.parse(JSON.stringify(x))), 0);,它不需要单独的克隆定义,但性能可能较低。
        【解决方案7】:

        Anwser 的原因

        这里有很好的答案,但由于 JavaScript 在这里发生了变化,这是一种通过 JavaScript 中的一些更新特性来解决这个问题的额外方法。

        function nArray (dem, size=dem, fill=null, currDepth=0) {
            const arr = new Array(size).fill(fill);
            return (currDepth+1 === dem) ? arr : arr.map(i => nArray(dem, size, fill, currDepth+1));
        };
        

        注意事项

        dem 是数组的维度。

        size是每个维度的大小,默认是dem的值。

        fill 是默认填充值。

        currDepth 不能用于函数的递归性质。

        【讨论】:

          【解决方案8】:

          使用默认值创建 n 维矩阵数组

          function arr (arg, def = 0){
                if (arg.length > 2){
                  return Array(arg[0]).fill().map(()=>arr(arg.slice(1)));
                } else {
                  return Array(arg[0]).fill().map(()=>Array(arg[1]).fill(def));
                }
              }
          
          //simple usage -> fills with 0
          var s = arr([3,5,8,4])  // 4 dimensions
          var t = arr([5,7])  // 2 dimensions
          
          //fill with null
          var k = arr([4,7,9] , null)  // 3 dimensions 
          

          【讨论】:

            【解决方案9】:

            如果您需要在每个集群中创建索引从 0 到 4 的 4d 数组,只需执行以下代码:

            function createNDimArray(dimensions) {
                if (dimensions.length > 0) {
                    var dim = dimensions[0];
                    var rest = dimensions.slice(1);
                    var newArray = new Array();
                    for (var i = 0; i < dim; i++) {
                        newArray[i] = createNDimArray(rest);
                    }
                    return newArray;
                 } else {
                    return undefined;
                 }
             }
            var MyArray=createNDimArray([5, 5, 5, 5]);
            //returns a 5x5x5x5 array with index from 0 to 4;
            MyArray[4][4][4][4]="MyArray 4d MyValue";
            alert(MyArray[4][4][4][4]);
            
            
            
            //For 5-demension array with this param.: 5x4x3x2x2 -> do this:
            var MyArray_5d=createNDimArray([5, 4, 3, 2, 2]);
            MyArray_5d[4][3][2][1][1]="MyArray 5d MyValue";
            alert(MyArray_5d[4][3][2][1][1]);

            【讨论】:

            • 这似乎是我答案的精确副本。我错过了什么吗?
            • 我附加了第二部分:"MyArray=createNDimArray([5, 5, 5, 5]);" ........我的回答是由你的回答组成的,这是正确的
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-11-15
            • 2018-06-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多