如何在javascript中快速缩放二维数组?

Posted

技术标签:

【中文标题】如何在javascript中快速缩放二维数组?【英文标题】:How to scale a two dimensional array in javascript fast? 【发布时间】:2018-09-12 04:47:37 【问题描述】:

给定二维数组a:

let a = [
    [0, 0, 1, 0], 
    [0, 1, 1, 1], 
    [0, 0, 1, 0], 
    [0, 0, 1, 1] 
]

如何按给定因子对其进行缩放?例如,数组 b 是数组 a 缩放 4:

let b =[ 
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
    [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]
]

这是我为执行此操作而编写的代码,但在处理大型数组 (200 x 200) 时速度很慢(客户端浏览器:Chrome),并且缩放比例为 16。

// scale an array by a factor of 'scale'

const scaledMatrixArray = (arr, scale) => 
        let newArr = [];
        arr.forEach((el) => 
            let newArrRow = [];
            el.forEach((el) => 
                for (let j = 0; j < scale; j++) 
                    newArrRow.push(el);
                
            );
            for(let i = 0; i < scale ; i++) 
                newArr.push(newArrRow);
            
        );
        return newArr;
    ;

我知道我的实现是 O(n^2) 的一些变体并且效率非常低。我正在寻找一种更好的方法来做到这一点,或者寻找一个做得更好更快的库。我的最终结果是,我的 N X N 数组具有超过 N > 200 可以以最有效、最快且内存占用最少的方式扩展到 800 x 800 的数组。

【问题讨论】:

请注意,任何采用 N*N 数组并将其缩放某个因子 M 的实现都会产生总共 N*N*M 值,因此将是 O(mn^2 ) @Hamms 所以没有办法让它更快? 取决于你到底想做什么,可能有比制作一个巨大的数组更快/更好的方法 如果你真的很在意速度,你可以在任何地方使用for循环而不是forEach,它在执行大量重复操作时性能提升很小。 似乎您可以在 50x50 矩阵上找到路径并将生成的路径点乘以 16。meta.stackexchange.com/questions/66377/what-is-the-xy-problem 【参考方案1】:

这是一种非常简化的方式,使用Array().fill,至少在我的浏览器中它的运行速度比其他答案快。

我添加了两个版本,一个使用扩展运算符,另一个使用.apply。我使用apply 获得更快的结果。

function scaleSpread(array, factor) 
	const scaled = [];

	for(const row of array) 
		let x = [];

		for(const item of row)
			x.push(...Array(factor).fill(item));

		scaled.push(...Array(factor).fill(x));
	

	return scaled;


function scaleApply(array, factor) 
	const scaled = [];

	for(const row of array) 
		let x = [];

		for(const item of row)
			x.push.apply(x, Array(factor).fill(item));

		scaled.push.apply(scaled, Array(factor).fill(x));
	

	return scaled;


function scaleConcat(array, factor) 
	let scaled = [];

	for(const row of array) 
		let x = [];

		for(const item of row)
			x = x.concat(Array(factor).fill(item));

		scaled = scaled.concat(Array(factor).fill(x));
	

	return scaled;


var a = [ [0, 0, 1, 0], [0, 1, 1, 1],  [0, 0, 1, 0],  [0, 0, 1, 1] ]

console.time('spread');
scaleSpread(a, 10000);
console.timeEnd('spread');

console.time('apply');
scaleApply(a, 10000);
console.timeEnd('apply');

console.time('concat');
scaleConcat(a, 10000);
console.timeEnd('concat');

编辑:添加了一个使用 .concat 的版本,因为 apply 和 spread 导致 Maximum call stack size exceeded 具有非常大的数组。

【讨论】:

@EmmanuelNK 添加了另一个版本,使用 apply 更快,至少在我的浏览器中是这样。 是的,这在我的浏览器中也明显更快。 我昨天没有发帖,因为在 node 中它比 Emmanuel 的慢。你得到什么号码? spread: 7.900ms apply: 1.200ms 我怀疑apply 比直接调用push 更快。 Anyhoos,传播时间约为 10 毫秒,适用于我的时间约为 7.6 毫秒 好的哇,这绝对是更快。我在浏览器中得到 2.8 毫秒的 concat【参考方案2】:

此方法使用for loop,为确定的n 次迭代n 维数组。

这使用Array.splice 方法,通过获取源值并将其插入到数组中的某个索引处。

PS:源数组(a)在这里发生了变异。但是,您始终可以克隆原始数组并根据需要为结果创建 b

var a = [
    [0, 0, 1, 0],
    [0, 1, 1, 1], 
    [0, 0, 1, 0], 
    [0, 0, 1, 1] 
  ],
  scale = 4,
  scaleTheArray = function (arrayToScale, nTimes) 
    for (var idx = 0, i = 0, len = arrayToScale.length * nTimes; i < len; i++) 
      var elem = arrayToScale[idx];

      /* Insert the element into (idx + 1) */
      arrayToScale.splice(idx + 1, 0, elem);

      /* Add idx for the next elements */
      if ((i + 1) % nTimes === 0) 
        idx += nTimes + 1;
      
    
  ;

console.time('testScale');

/* 1. Expand each of the a[n] length */
for (var i = 0, len = a.length; i < len; i++) 
  var arr = a[i];

  scaleTheArray(arr, scale - 1);


/* 2. Expand each of the a length */
scaleTheArray(a, scale - 1);

console.timeEnd('testScale');

【讨论】:

天哪,这速度很快。 10000 scale = 1328.18995ms @Zze 对你来说太糟糕了,对我来说10000 scale = 302.428955078125ms 8) @choz 今天晚些时候继续关注我的个人电脑报告! 这速度非常快...我收到了10000 scale = 367.779052734375ms @EmmanuelNK 会发布一个答案,我的方法至少在我的浏览器中比 choz' 快。 Choz 和 EmmanuelNK 愿意告诉我我发布后你得到了什么?【参考方案3】:

一般来说,更少的函数调用 = 更少的开销:

function scale1D(arr, n) 

  for (var i = arr.length *= n; i; ) 
    arr[--i] = arr[i / n | 0]


function scale2D(arr, n) 

  for (var i = arr.length; i; )
    scale1D(arr[--i], n)

  scale1D(arr, n)


var a = [ [0, 0, 1, 0], [0, 1, 1, 1],  [0, 0, 1, 0],  [0, 0, 1, 1] ]
console.time( 1e5 )
scale2D(a, 1e5)
console.timeEnd( 1e5 )

var b = [ [0, 0, 1, 0], [0, 1, 1, 1],  [0, 0, 1, 0],  [0, 0, 1, 1] ]
scale2D(b, 4)
console.log( JSON.stringify( b ).replace(/],/g, '],\n ') )

主要的优化是在调整每一行的大小后,它们会重复,而不是创建所有的 # rows * scale 行。因此,不是处理 n * scale 数组,而是只处理 n 个数组。另一个可能的优化可能是在某些浏览器上,arr.length *= n 可能会一次分配所有需要的连续内存。


为了比较,上面的函数式方法慢了大约 2 倍:

const scale1D = (arr, n) => [...Array(arr.length * n)].map((_, i) => arr[i / n | 0])

const scale2D = (arr, n) => scale1D( arr.map((row, i) => scale1D(row, n)), n )

console.time( 1e5 )
let a = scale2D([ [0, 0, 1, 0], [0, 1, 1, 1],  [0, 0, 1, 0],  [0, 0, 1, 1] ], 1e5)
console.timeEnd( 1e5 )

let b = scale2D([ [0, 0, 1, 0], [0, 1, 1, 1],  [0, 0, 1, 0],  [0, 0, 1, 1] ], 4)
console.log( JSON.stringify( b ).replace(/],/g, '],\n ') )

【讨论】:

这要快得多。我应该考虑将其向后插入以使not using splice 成为可能。不错的方法。【参考方案4】:

有点有趣,如果你没有访问很多值,你可以懒惰地做。没有对这段代码进行太多测试,但应该可以工作

        var a = [
            [0, 0, 1, 0],
            [0, 1, 1, 1], 
            [0, 0, 1, 0], 
            [0, 0, 1, 42] 
          ],
          scale = 4;
    
        for (var idx = 0; idx < a.length; idx++) 
            a[idx] = new Proxy(a[idx], 
              get: function(target, i) 
            return target[Math.floor(i/scale)];
          
        );
        
        a = new Proxy(a, 
              get: function(target, i) 
            return target[Math.floor(i/scale)];
          
        );
    
        console.log(a[16-1][16-1])

        for (var ii = 0; ii < 16;ii++) 
          for(var j=0;j<16;j++)
            console.log(a[ii][j])
          
        

【讨论】:

以上是关于如何在javascript中快速缩放二维数组?的主要内容,如果未能解决你的问题,请参考以下文章

如何按特定单元格快速排序二维数组?

JavaScript 如何定义一个二维数组

JavaScript 如何定义一个二维数组

Javascript 二维数组 indexOf

javascript 定义二维数组,两列,未知行数

如何创建二维数组,大小在objective-c中作为变量给出