JavaScript; n维数组创建

12

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

解析之后,我们获得了一个索引数组,用于指定要修改的 n 维数组中的元素。例如,在解析以下内容之后:

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

简短的问题:

是否有一种方法在JavaScript中创建一个n维数组而不使用eval()
10个回答

12

已在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));
    }
 }

设置一个元素类似,留给读者练习。


你可以用 a[2][3][5] 来代替 getElement(a, [2, 3, 5]) - sk29910
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Barmar
我不确定我理解你的观点——如果我需要动态下标,我是否可以只使用a[index1][index2][index3] - sk29910
维度的数量也是动态的。它可以是 a[index1]a[index][index2] - Barmar
@sebastian_k 他正在为另一种语言实现解释器。因此,他需要解析其数组索引语法,并使用JS实现它。 - Barmar
显示剩余8条评论

5

虽然没有内置功能,但是创建一个函数来完成这个任务非常容易:

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,如果您想要预先构建它们,您真的必须递归生成数组。这很麻烦,但确实展示了解释器所需的努力。
莫名其妙。

聪明;谢谢。不过,还有一件事,你如何访问已创建数组中的特定元素? - Chris
我的意思是,由于我们不知道实际上会处理多少个索引。 - Chris
嗯,不完全是。假设我们已经创建了数组,然后面临另一个索引数组 [a, b, c]。你如何实际查找 array[a][b][c]?主要问题是你不知道会有多少个索引。我知道可以使用 eval() 来完成,但我希望有一种更简洁的解决方案。 - Chris
@Abody97,请查看Barmar对他答案的更新。这将适用于由我的代码生成的数组。 - Pete
@Barmar 在他的编辑中解决了这个问题。我想我必须接受他的答案,尽管你先回答了。对此很抱歉。 - Chris

1

编辑:由于任何递归解决方案都将有一个可以创建的数组大小限制...我在我的 GitHub上的 PJs 库中提供了另一种解决方案。这个解决方案运行速度几乎瞬间,可以创建和管理任何大小、任何结构、任何维度的多维数组,并且可以模拟预填充和/或使用自定义设计的节点对象。请在这里查看: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维数组可能的最快的方式。上面的重构有一些很好的速度提升。但是,当然,最好的速度提升来自于不预填充。这个版本不预填数组。它只返回一个完全创建的N维数组,其中每个长度为N的最后一层都是一个空数组。我希望如果你真的需要null值,arr[x] [y] [z]? arr[x] [y] [z]:null就足够了。对我的用途来说是这样的。 :)

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

而且,如果您不在乎我所做的事情,那么请停止阅读。

想要更多的极客话题吗?下面是关于递归的一些小技巧,供那些正在学习的人参考。当进行深度递归时,请记住最后一层。它是大部分工作完成的地方。在这种情况下,它就是第N维度,字面意思上就是你的“有效载荷”,其余部分则是后勤。在jfab的函数中,当dimensions.length变成1时,它是最后一维,也是第N维,并执行有效载荷。这就是创建空数组的任务,或者在我的例子中,是一个空数组。由于递归的深度很深,每个维度都是上一个维度的因素。当你到达第N维度时,你将有许多函数调用,而后勤对计算机来说变得繁琐。在第N维度,你将为有效载荷调用基本递归函数(在我们的例子中是createNDimArray)比为后勤调用的次数多。现在,就像jfab的原始解决方案一样,如果可能的话,将有效载荷的执行放在递归的最前面通常是一件好事,特别是如果它很简单。在这里,通过将有效载荷设置为最终2D数组的构建(而不仅仅是通过返回new Array()来创建1D数组),现在不必在此级别上进行过多的函数调用。当然,如果你想预填充数组,则这种快捷方式并不总是有效。但更重要的是,预填充数组将成为适当的有效载荷。通过访问第N维度上的每个项目,我们实际上已经删除了它。这样就少了一层函数调用,基本上第N维度的有效载荷实际上是在第N-1个维度上完成的。而且我们永远不会再次调用递归函数,只是为了提供new Array()。不幸的是,对于new Array(x)的调用(通常情况下)并不是这样。它的执行时间随着更大的x而增加。这实际上仍然相当于访问第N维度上的每个项目,但现在我们只需要一次,并使用本地代码和包装在紧凑而轻巧的循环中。现在,我们要求createNDimArray只能被调用N > 1,即永远不要用来创建1D数组。理论上,你可以要求更大的N,并在最后展开更多的维度。基本上,具有if ( dimensions.length < 3 )的行将读取类似于<4<5的内容,然后你需要在已经存在的循环周围包装那么多额外的for循环,它们都需要自己的一组var --- 所以我不确定它是否非常高效,因为你正在用过多的函数调用和堆栈空间/操作来交换一个类似的想法,但是嵌入式的for循环。但我想如果你知道N始终在某个水平以上或者仅适用于最终维度,那么它可能会加速某些环境
下一步就是修改他的解决方案,只是物流方面的问题,只需简单重构以消除过多的块和代码,将所有的var工作合并在一起即可。由于需要返回一个arr,所以一旦循环结束,最好先在一行上完成所有的var工作,幸运的是,四个var中有三个具有相同的初始化。请记住,如果可能的话,Javascript可以通过逗号优化代码的连接。这也使得代码更加紧凑。

1

创建一个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)相比,您获得的改进速度因子随着维度更高和更大而增加。通过一些重构,这个解决方案可以缩减到四行,并且可能更快。 - Pimp Trizkit

0

使用 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

0

多维数组可以被视为嵌套数组。 看看以下内容是否有帮助。

<script type="text/javascript">"use strict";
   const arr = [
                  ["D1","D2","D3"],
                  [
                     ["T11","T12","T13"],
                     ["T21","T22","T23"]
                  ]
               ];

   for(let k=0;k<arr[0].length;k++)console.log(arr[0][k]);
      // D1
      // D2
      // D3

   for(let k=0;k<arr[1].length;k++)console.log(arr[1][k]);
      // Array(3) [ "T11", "T12", "T13" ]
      // Array(3) [ "T21", "T22", "T23" ]

   for(let k=0;k<arr[1].length;k++)console.log(arr[1][0][k]);
      // T11
      // T12

   for(let k=0;k<arr[1].length;k++)console.log(arr[1][1][k]);
      // T21
      // T22

   for(let k=0;k<arr[1][0].length;k++)console.log(arr[1][0][k]);
      // T11
      // T12
      // T13

   for(let k=0;k<arr[1][1].length;k++)console.log(arr[1][1][k]);
      // T21
      // T22
      // T23
</script>

// // // // // // // // // // // // // // // // // // // //

从同样的角度来看,这是一个多维对象!

<script type="text/javascript">"use strict";
   const o = {
               un:{u1:"U1",u2:"U2",u3:"U3"},
               deux:{
                  trois : {d11:"D11",d12:"D12",d13:"D13"},
                  quatre: {t21:"T21",t22:"T22",t23:"T23"}
               }
             };

   let ref = Object.keys(o);
   for(let k=0;k<ref.length;k++)
      console.log(ref[k] , ":" ,
                  Object.values(o)[k]);
      // un   : Object { u1: "U1", u2: "U2", u3: "U3" }
      // deux : Object { trois: {…}, quatre: {…} }
         // quatre: Object { t21: "T21", t22: "T22", t23: "T23" }
         // trois : Object { d11: "D11", d12: "D12", d13: "D13" }

   ref = Object.keys(o["un"]);
   for(let k=0;k<ref.length;k++)
      console.log(ref[k] , ":" ,
                  Object.values(o["un"])[k]);
      // u1 : U1
      // u2 : U2
      // u3 : U3

   ref = Object.keys(o["deux"]);
   for(let k=0;k<ref.length;k++)
      console.log(ref[k] , ":" ,
                  Object.values(o["deux"])[k]);
      // trois  : Object { d11: "D11", d12: "D12", d13: "D13" }
      // quatre : Object { t21: "T21", t22: "T22", t23: "T23" }

   ref = Object.keys(o["deux"]["trois"]);
   for(let k=0;k<ref.length;k++)
      console.log(ref[k] , ":" ,
                  Object.values(o["deux"]["trois"])[k]);
      // d11 : D11
      // d12 : D12
      // d13 : D13

   ref = Object.keys(o["deux"]["quatre"]);
   for(let k=0;k<Object.keys(ref).length;k++)
      console.log(ref[k] , ":" ,
                  Object.values(o["deux"]["quatre"])[k]);
      // t21 : T21
      // t22 : T22
      // t23 : T23

   ref = Object.keys(o["deux"]["trois"]);
   console.log(ref[0] , ":" ,
               Object.values(o["deux"]["trois"])[0]);
      // d11 : D11

   ref = Object.values(o["deux"]["quatre"]);
   console.log(Object.keys(o["deux"]["quatre"])[ref.length-1] ,
               ":" , ref[ref.length-1] );
      // t23 : T23
</script>

0

回答原因

这里有一些好的答案,但由于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 不应使用,它是函数递归性质的一部分。


0
创建一个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))


这个与之类似的线性解决方案是单独实现的:https://dev59.com/sGsz5IYBdhLWcg3w9ssQ#48013225 [1, 3, 1, 4, 1].reduceRight((x, y) => new Array(y).fill().map(() => JSON.parse(JSON.stringify(x))), 0);,它不需要单独的克隆定义,但可能性能较差。 - Ebrahim Byagowi

0

If you need to create 4d Array with index from 0 to 4 in the each cluster just do this code:

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


这似乎是我的答案的完全复制。我有什么遗漏吗? - Barmar
我添加了第二部分: "MyArray=createNDimArray([5, 5, 5, 5]);" .................. 我的答案是基于你的答案,这是正确的。 - Arthur Alunts

0

使用默认值创建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 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接