如何调整数组大小

17

有没有一个数组方法可以替换下面的函数(类似于 a.splice(some, tricky, arguments) 这样的方法)?

function resize(arr, newSize, defaultValue) {
  if (newSize > arr.length)
    while(newSize > arr.length)
      arr.push(defaultValue);
  else
    arr.length = newSize;
}

如果没有的话,是否有更好/更漂亮/更短的实现方法?该函数应在数组需要增长时添加默认值,在收缩时删除值。


3
arr.length = newSize; 这行代码将数组的长度调整为 newSize,如果新长度比旧长度大,则新增的元素将会被填充为 undefined - crush
2
如果你追求效率,就不要在每次 push/pop 时调整数组大小。最好拥有一个足够大的数组,在必要时缩小它或者只有在意识到数组不够大时才增加它的大小。 - blurfus
3
好的,有 Array.prototype.fill 这个方法,但它目前还没有得到广泛支持。 - crush
3
你能解释一下为什么你需要这种行为吗? - crush
1
如果您坚决要使用这种方法,那么您可以考虑在页面加载时进行特征检测,并根据Array.prototype.fill是否存在本地提供不同的方法。(不要费心使用polyfill,它比您已经拥有的东西更慢)。您唯一能做的另一件事就是创建一个巨大的预定义数组查找表,其中包含默认值和Array.prototype.concat。同样,所有这些都取决于您对“优雅”和“高效”的理解。您是指处理能力、内存、代码行数等方面的高效吗? - crush
显示剩余10条评论
8个回答

23

就优雅度而言,我认为你可以将原始解决方案简化为:

function resize(arr, newSize, defaultValue) {
    while(newSize > arr.length)
        arr.push(defaultValue);
    arr.length = newSize;
}

或者使用原型:

Array.prototype.resize = function(newSize, defaultValue) {
    while(newSize > this.length)
        this.push(defaultValue);
    this.length = newSize;
}

编辑:ES2016方法:

function resize(arr, newSize, defaultValue) {
    return [ ...arr, ...Array(Math.max(newSize - arr.length, 0)).fill(defaultValue)];
}

这样可以消除比较,但确保了赋值。因此,这是否更有效取决于比较是否比赋值更昂贵。 - crush
1
注意:如果defaultValue是一个对象,则此答案可能导致不良行为。请查看我的答案 - Reza
2
我不同意这个答案。每次向数组推送元素都会重新调整数组大小。因此,您应该先设置长度,然后通过迭代剩余的项来设置默认值。 - siOnzee
2
在第一个示例中已经更改了长度,重置arr.length的意义是什么?这是多余的。 - BigDreamz

7
function resize(arr, size, defval) {
    while (arr.length > size) { arr.pop(); }
    while (arr.length < size) { arr.push(defval); }
}

我认为以下方法更加高效:

我认为这样做会更有效率:

function resize(arr, size, defval) {
    var delta = arr.length - size;

    while (delta-- > 0) { arr.pop(); }
    while (delta++ < 0) { arr.push(defval); }
}

虽然没有那么优雅,但这可能是最有效的方法:

function resize(arr, size, defval) {
    var delta = arr.length - size;

    if (delta > 0) {
        arr.length = size;
    }
    else {
        while (delta++ < 0) { arr.push(defval); }
    }
}

这两种方法几乎是等效的。它们做相同的事情。arr.length = size 比手动弹出每个元素直到达到所需大小更快。对于添加新元素,你在两个函数中的代码与 OP 所写的代码是等效的。你只是用不同的方式表达了它。 - crush
@crush 这段代码在添加元素时更加高效,因为它不会在每次迭代中重新计算 arr.length - Thriggle
右边的 arr.length = 更好,添加了另一个... 谢谢 - wayofthefuture
@Thriggle 这不是真的。它们在性能上几乎是等价的(http://jsperf.com/property-access-vs-cached-value-access)。也许在一些旧的浏览器中,这个说法是正确的。当你去掉那个错误的声明后,这个答案的第三种方法正好和提问者提供的一样。 - crush
@Dude2TheN 这是一个很好的问题。乍一看,我看不出为什么它会有任何区别。它的目的显然是确保两个测试在每个方面都是相等的。可能与JSPerfs基准测试工具有关。有人知道为什么拆卸代码会产生如此大的差异吗?这只是在每个测试之前将初始值设置为相同。 - crush
显示剩余13条评论

3

对于那些喜爱一行代码的人

如果你和我一样是个一行代码爱好者,你想知道的可能是我的resize_array_right函数是用来干什么的。


const resize_array_left = (array, length, fill_with) => (new Array(length)).fill(fill_with).concat(array).slice(-length);
// Pads left when expanding an array.
// Put left elements first to be removed when shrinking an array.

const resize_array_right = (array, length, fill_with) => array.concat((new Array(length)).fill(fill_with)).slice(0, length);
// Pads right when expanding an array.
// Put right elements first to be removed when shrinking an array.

您可以在 NPM 上找到它,叫做 resize-array


浏览器兼容性

  • Chrome 浏览器:45 及以上版本。
  • Firefox 浏览器:31 及以上版本。
  • Internet Explorer 浏览器:✘。
  • Edge 浏览器:✔。

2
你可以使用以下代码来调整数组大小:

最初的回答:

// resizing function for arrays
Array.prototype.resize = function( newSize, defaultValue )
{
  while( newSize > this.length )
  {
    typeof( defaultValue ) === "object" ? this.push( Object.create( defaultValue ) ) : this.push( defaultValue );
  }
  this.length = newSize;
}

我检查了defaultValue的类型,因为如果它是一个对象并且您只是将其推到新元素中,那么您最终将得到一个数组,其中新元素指向相同的对象。这意味着,如果您在数组的一个元素中更改该对象的属性,则所有其他元素也会更改。但是,如果您的defaultValue是一个基本类型,您可以安全地将其推送到新元素中。"最初的回答"

2

由于Array.prototype.fill(请参见链接页面底部的浏览器兼容性),此解决方案仅适用于新版浏览器(除IE外)或使用polyfill。

function resize(arr, newSize, defaultValue) {
    var originLength = arr.length; // cache original length

    arr.length = newSize; // resize array to newSize

    (newSize > originLength) && arr.fill(defaultValue, originLength); // Use Array.prototype.fill to insert defaultValue from originLength to the new length
}

它是 ES6 格式,因此在未来几年内不会与所有浏览器兼容。 - wayofthefuture
它适用于Firefox和Safari(兼容性-无法检查),并将适用于下一个版本的Chrome。也许是Edge。然而,由于它现在不能在所有浏览器上运行,我已经在答案中加入了免责声明。 - Ori Drori

1
深入探讨James的解决方案:

Array.prototype.resize = function(newSize, defaultValue) {
    while(newSize > this.length)
        this.push(defaultValue);
    this.length = newSize;
}

如果您想要更高效,可以对Array.prototype.fill进行浏览器检测,然后使用它来代替while循环。

if (Array.prototype.fill) {
    Array.prototype.resize = function (size, defaultValue) {
        var len = this.length;

        this.length = size;

        if (this.length - len > 0)
            this.fill(defaultValue, len);
    };
} else {
    Array.prototype.resize = function (size, defaultValue) {
        while (size > this.length)
            this.push(defaultValue);

        this.length = size;
    };
}

如果有人为Array.prototype.fill编写了polyfill,那么您希望他们使用您的非填充版本。使用polyfill会导致填充方法比非填充版本慢。
这个StackOverflow Q&A解决如何检测函数是否原生实现。您可以将其纳入初始条件,但这只会增加额外的速度损失。
我可能只会在确保不存在Array.prototype.fill时使用此解决方案。

0

调整数组大小一次比进行大量的.push操作更有效率。这可以通过Array.prototype.lengthsetter特性实现:

function resize(arr, newLength, defaultValue) {
  const oldLength = arr.length;
  arr.length = newLength;
  if (newLength > oldLength && typeof(defaultValue) !== 'undefined') {
    for (let i = oldLength; i < newLength; i++) {
      arr[i] = defaultValue;
      // Note: this will create many references to the same object, which
      // may not be what you want. See other answers to this question for 
      // ways to prevent this.
    }
  }
}

0

建议:

let expectedLength = 500
myArray = myArray.filter((element, index)=>{
     if(index<expectedLength){
         return element
     }
}) 

1
你的答案可以通过添加额外的支持信息来改善。请[编辑]以添加进一步的细节,如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写好答案的更多信息。 - mufazmi

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