【问题标题】:Mutating an array with the elements of another array while maintaining order and existing instances用另一个数组的元素改变一个数组,同时保持顺序和现有实例
【发布时间】:2025-12-10 22:30:01
【问题描述】:

需要一个算法的帮助,该算法采用两个数组并导致它们之间保持相同顺序的特定比较:

  • 如果 _ID 在现有但 _ID 不在覆盖器中,则不要在结果中包含元素
  • 如果 _ID 在覆盖中但 _ID 不存在,则将元素添加到 RESULT
  • 如果 _ID 存在于现有和覆盖器中,则将具有 _ID 版本/实例的元素从现有添加到结果中

大写字母代表引用_ID v# 表示版本/实例引用_ID

1.

现有:[Av1 Bv1 Cv1 Ev1 Fv1 Gv1]

覆盖:[Bv2 Dv1 Fv2 Hv1 Jv1]

结果:[ Bv1 Dv1 Fv1 Hv1 Jv1 ]

2.

现有:[Bv1 Cv1 Ev1 Fv1 Gv1]

覆盖:[Av1 Bv2 Dv1 Fv2 Hv1 Jv1]

结果:[Av1 Bv1 Dv1 Fv1 Hv1 Jv1]

3.

现有:[Av1 Bv1 Cv1 Ev1 Fv1 Gv1]

覆盖:[Av2 Bv2 Dv1 Fv2 Hv1 Jv1]

结果:[Av1 Bv1 Dv1 Fv1 Hv1 Jv1]

4.

现有:[Cv1 Dv1 Ev1]

覆盖:[Av1 Bv1 Dv2 Fv1 Hv1 Jv1]

结果:[Av1 Bv1 Dv1 Fv1 Hv1 Jv1]

5.

现有:[Av1 Bv1 Cv1 Ev1 Fv1 Gv1]

覆盖:[Dv1]

结果:[Dv1]

我正在寻找一个 log(n) 函数,它可以一次性执行此突变操作(可能具有现有和覆盖器的枢轴索引,但我不确定)。

不想使用 indexOf。

这是我使用缩尾技术的 log(n^2) 解决方案:

let eIndx = 0,
    existing = [ Av1, Bv1, Cv1, Ev1, Fv1, Gv1 ],
    overwriter = [ Bv2, Dv1, Fv2, Hv1, Jv1 ];

overwriter.map( element => {

    while( element._ID !== existing[ eIndx ]._ID && 
           eIndx       !== existing.length ) {

        delete existing[ eIndx ];

        ++eIndx;
    }

    return eIndx !== existing.length ? existing[ eIndx ] : element;
});

如果现有的 _ID 都没有在覆盖器中,则此解决方案最慢。

我不确定是否应该遍历现有数组或覆盖器数组。

在我转述这篇文章的扩展(更复杂)解决方案中,我遍历了覆盖器,并且我有一个哈希字典来引用将来是否已经存在 _ID/version 组合(后来的索引尚未要迭代)的数组。我不能再使用那个全局字典,我想弄清楚我是否需​​要为每个数组实例制作一个本地字典,或者是否有一种方法我不需要字典而只使用枢轴进行比较。我在 log(n) 解决方案中看到的一个问题是,它不知道覆盖器的第一个元素是否是新的 _ID,而无需遍历所有现有数组。

我主要是在寻找尽可能快的东西

非常感谢您能分享的帮助。

【问题讨论】:

    标签: javascript arrays masking mutation


    【解决方案1】:

    您可以使用 Map 并检查 id 是否存在。

    function merge(existing, overwriter) {
        const getId = s => s.split('v')[0]; // or whatever is suitable
    
        var versions = new Map;
    
        existing.forEach(s => versions.set(getId(s),  s));
        return overwriter.map(s => versions.get(getId(s)) || s);
    }
    
    console.log(merge([ 'Av1', 'Bv1','Cv1','Ev1','Fv1', 'Gv1' ],[ 'Bv2', 'Dv1','Fv2','Hv1','Jv1']));
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    【讨论】:

    • 这听起来有点像 OP 试图替换的代码。但我认为这实际上是唯一真正有用的方法。
    • 感谢 Scott 和 Nina。在覆盖数组到达的那一刻,在合并函数中建立现有数组的哈希索引会更有利吗?或者更好地在现有数组最初创建时构建哈希索引并仅存储它(与现有数组一起)直到覆盖数组到达?
    • 我认为这将基于您的应用程序。显然,重建需要时间成本。存储它需要空间成本(和复杂性)。如有必要,我会尝试重建和配置文件,看看它是否满足您的需求。那肯定会更容易维护。仅将其存储起来会导致性能问题。
    【解决方案2】:

    我发布此答案作为讨论的基础,尽管 based on previous discussion,我知道它不能完全满足您的需求。

    它是递归的,在 Javascript 中,它很可能仍然意味着它很慢或可能溢出堆栈。不过,它已经为尾调用优化做好了准备,所以最终它不应该太慢。但是由于是递归的,代码非常干净。 但是,这是基于我从您的示例中做出的列表已排序的假设。

    const original = [{"_ID": 2, "val": "a0"}, {"_ID": 3, "val": "a1"}, {"_ID": 5, "val": "a2"}, {"_ID": 7, "val": "a3"}, {"_ID": 11, "val": "a4"}, {"_ID": 13, "val": "a5"}, {"_ID": 17, "val": "a6"}, {"_ID": 19, "val": "a7"}]
    
    const overwriter = [{"_ID": 1, "val": "b0"}, {"_ID": 2, "val": "b1"}, {"_ID": 3, "val": "b2"}, {"_ID": 5, "val": "b3"}, {"_ID": 8, "val": "b4"}, {"_ID": 13, "val": "b5"}, {"_ID": 21, "val": "b6"}, {"_ID": 34, "val": "b7"}]
    
    const mergeLists = (a, b, combined = []) => a.length == 0
        ? combined.concat(b)
        : b.length == 0 
            ? combined
            : a[0]._ID < b[0]._ID 
                ? mergeLists(a.slice(1), b, combined)
                : a[0]._ID == b[0]._ID
                    ? mergeLists(a.slice(1), b.slice(1), combined.concat([a[0]]))
                    : mergeLists(a, b.slice(1), combined.concat([b[0]]))
             
    
    console.log(mergeLists(original, overwriter))

    既然我们发现它们没有排序,我认为没有什么比在本地重新创建该索引更好的了。它肯定会比每次都搜索列表更快(这将是O(m * n)。)

    使用对象作为索引的属性查找仅比O(1) 慢一点,因此,以额外空间为代价,您仍然应该能够在O(m + n) 中执行此操作。

    该代码应该非常简单:通过将现有数据减少到一个对象中创建一个索引,键入 _ID 属性,然后迭代覆盖,将索引中匹配的一个(如果存在)放入您的输出中,或以其他方式覆盖的。

    这仍然没有变异。您可以使用适当的splice 来实现这一点,但如果您这样做,请确保您反向迭代。但我建议在任何情况下都不要变异。

    无论如何,这段代码都适用于我上面使用的数据。如果您的 ID 不是字符串或数字,则应使用 Nina 的 Map 版本:

    const mergeLists = (original, overwriter) => {
      const index = original.reduce((idx, val) => (idx[val._ID] = val, idx), {})
      return overwriter.map(val => index[val._ID] || val)
    }
    

    【讨论】:

      【解决方案3】:

      你说的是 log(n)log(n2) 但显然是指 O(n)O(n2)

      如果您首先为现有值创建一个映射,并以它们的 id 值作为键,则可以实现 O(n) 时间复杂度。然后覆盖数组上的循环可以在每次取回的恒定时间内取回相应的元素。

      这是一个例子:

      const existing = [
              { _id: "A", version: 1 }, 
              { _id: "B", version: 1 },
              { _id: "C", version: 1 },
              { _id: "E", version: 1 }, 
              { _id: "F", version: 1 },
              { _id: "G", version: 1 },
          ],
          overwriter = [
              { _id: "B", version: 2 },
              { _id: "D", version: 1 },
              { _id: "F", version: 2 },
              { _id: "H", version: 1 },
              { _id: "J", version: 1 },
          ];
      
      const map = new Map(existing.map( o => [o._id, o] ));
      const result = overwriter.map( el => map.get(el._id) || el );
      
      console.log(result);
      .as-console-wrapper { max-height: 100% !important; top: 0; }

      【讨论】:

        最近更新 更多