【问题标题】:Efficiently transferring elements between arrays有效地在数组之间传输元素
【发布时间】:2015-05-28 07:41:09
【问题描述】:

我有几个 JavaScript 数组,每个数组都包含一个指向对象的指针列表。当一个对象满足某个条件时,它的指针必须从其当前包含的数组中移除并放入不同的数组中。

我当前的(天真的)解决方案是 splice 退出现有的数组元素,然后 concatenate 将它们放到他们正在输入的数组中。这是一种缓慢的方法,并且随着时间的推移似乎会导致内存碎片化。

任何人都可以就更好的方法提供建议(一般的或特定于 JS 的)吗?

演示代码:

// Definitions
TestObject = function() {
    this.shouldSwitch = function() {
        return(Math.random() > 0.9);
    }
}

A = [];
B = [];

while(A.length < 500) {
    A.push(new TestObject());
}

// Transfer loop
doTransfers = function() {
    var A_pending = [];
    var B_pending = [];
    for(var i = 0; i < A.length; i++) {
        if(A[i].shouldSwitch()) {
            B_pending.push(A[i]);
            A.splice(i,1);
            i--;
        }
    }
    for(var i = 0; i < B.length; i++) {
        if(B[i].shouldSwitch()) {
            A_pending.push(B[i]);
            B.splice(i,1);
            i--;
        }
    }

    A = A.concat(A_pending);
    B = B.concat(B_pending);
}

setInterval(doTransfers,10);

谢谢!

【问题讨论】:

  • 是的,你绝对不应该在循环中使用splice。但是,由于您无论如何都在创建新数组,因此您不能直接将其删除,而是立即推送到新的*_pending

标签: javascript arrays optimization memory-management data-structures


【解决方案1】:

对于此问题的一种与语言无关的解决方案,当您将元素从一个连续序列(数组)传输到另一个时,它不会将元素附加到新数组的后面,这将成为瓶颈(恒定时间复杂性),这将是从现有容器的中间移除元素(线性时间复杂度)。

因此,您可以获得的最大好处是用一个仍然使用缓存友好的连续数组表示的恒定时间操作替换从数组中间删除的线性时间操作。

执行此操作的最简单方法之一是简单地创建两个新数组而不是一个:一个新数组来附加您要保留的元素,一个新数组来附加您要传输的元素。完成后,您可以将要保留(不转移)的新元素数组换成您拥有的旧数组。

在这种情况下,我们将从容器中间的线性时间删除与摊销的恒定时间插入交换到新容器的后面。虽然插入到容器末尾的重新分配仍然具有 O(N) 的最坏情况复杂度,但它发生的频率很低,并且通常仍然比每次支付平均复杂度为 O(N) 的操作要好得多您通过不断地从中间移除来转移单个元素。

解决这个问题的另一种更有效的方法是:

...当您传输一个元素时,首先将它的副本(可能只是一个浅副本)附加到新容器中。然后用旧容器后面的元素覆盖旧容器中该索引处的元素。现在只需弹出旧容器背面的元素。所以我们有一个 push、一个 assignment 和一个 pop。

在这种情况下,我们将容器中间的线性时间删除与单个赋值(存储/移动指令)和容器背面的恒定时间弹出(通常是基本算术)交换。如果旧数组中元素的顺序可以稍微打乱,这可以非常有效,并且通常是一个被忽视的解决方案,用于将数组中间的线性时间删除变为具有恒定时间复杂度的数组数组的背面。

【讨论】:

  • 我会试试这两个。感谢您的彻底回应!
【解决方案2】:

splice 对循环中的性能非常有害。但无论如何,您似乎不需要对输入数组进行突变 - 您正在创建新数组并覆盖以前的值。

做事

function doTransfers() {
    var A_pending = [];
    var B2_pending = [];
    for (var i = 0; i < A.length; i++) {
        if (A[i].shouldSwitch())
            B_pending.push(A[i]);
        else
            A_pending.push(A[i]);
    }
    var B1_pending = [];
    for (var i = 0; i < B.length; i++) {
        if (B[i].shouldSwitch())
            A_pending.push(B[i]);
        else
            B1_pending.push(B[i]);
    }

    A = A_pending;
    B = B1_pending.concat(B2_pending);
}

【讨论】:

  • 我明白了!应该已经意识到我已经在concat 步骤中重新制作了AB。通常是重复使用push,还是根据传入条目的计数预先分配A_pendingB*_pending
  • 你可以试试,虽然不是所有的 JS 引擎都会在设置 .length 属性时进行预分配;并且数组增长非常快。
猜你喜欢
  • 2011-02-14
  • 1970-01-01
  • 2018-05-12
  • 1970-01-01
  • 2023-04-02
  • 2016-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多