NodeJS - 从数组中减去数组,而不是删除所有重复项[重复]

Posted

技术标签:

【中文标题】NodeJS - 从数组中减去数组,而不是删除所有重复项[重复]【英文标题】:NodeJS - Subtract array from array, not removing all duplicates [duplicate] 【发布时间】:2018-02-11 12:10:57 【问题描述】:

标题可能没有多大意义,但你会怎么做:

a = [1, 2, 2, 3, 3, 3];
b = [1, 2, 3];
a.subtract(b);

我希望它返回 [2, 3, 3],而不是像类似问题的其他答案那样返回 [],它只保留根本不在另一个数组中的项目,而不是只删除多少在另一个数组中。

【问题讨论】:

所以只删除第一个出现的第二个数组项? 【参考方案1】:

您可以为Array 创建一个原型,并通过检查并消除找到的元素来过滤数组。

Array.prototype.subtract = function (array) 
    array = array.slice();
    return this.filter(function (a) 
       var p = array.indexOf(a);
       if (p === -1)  
           return true;
       
       array.splice(p, 1);
    );


var a = [1, 2, 2, 3, 3, 3],
    b = [1, 2, 3];

console.log(a.subtract(b));

带有哈希表的更快版本:

Array.prototype.subtract = function (array) 
    var hash = Object.create(null);
    array.forEach(function (a) 
        hash[a] = (hash[a] || 0) + 1;
    );
    return this.filter(function (a) 
       return !hash[a] || (hash[a]--, false);
    );


var a = [1, 2, 2, 3, 3, 3],
    b = [1, 2, 3];

console.log(a.subtract(b));

【讨论】:

哇,真快。谢谢!我使用了类似但有点不同的东西,那就是删除所有重复项,而不是在第二个数组中每次出现一次,例如 Array.prototype.diff = function(a) return this.filter(function(i) return a.indexOf(i) 我用你更快的版本更新了my testbench。它仍然比使用 Set但我的解决方案适用于 ES5。 @NinaScholz 无论如何都没关系,我之前的回答无效。我现在通过改进你的答案的性能进行了更新,当我更正我的答案并改用Map 时,它实际上比你的普通对象方法慢了 13%。【参考方案2】:

您可以使用filter()indexOf() 来检查元素是否存在于其他数组中,是否使用splice() 删除它

var a = [1, 2, 2, 3, 3, 3];
var b = [1, 2, 3];

var result = a.filter(function(e) 
  let i = b.indexOf(e)
  return i == -1 ? true : (b.splice(i, 1), false)
)

console.log(result)

【讨论】:

我使用了类似的东西,但没有使用 splice 将其删除,而是将其全部从数组中删除(这是不对的): Array.prototype.diff = function(a) return this.filter(function(i) return a.indexOf(i) 【参考方案3】:

您可以这样做:循环遍历数组b 并检查它是否存在于数组a 中。如果是这样,请在其索引处插入 undefined。返回过滤后的a 数组以删除所有undefined 元素。

编辑:实际上不需要过滤器使用splice() 其他人已经这样做了。

let a = [1, 2, 2, 3, 3, 3];
let b = [1, 2, 3];

let sub = function(a, b) 

  for (i in b) 
    let index = a.indexOf(b[i]);
    if (index != -1) 
      a.splice([index],1)
    
  
  
  return a



console.log(sub(a, b))

【讨论】:

【参考方案4】:

此处建议使用indexOf() 检查是否存在的答案是低效的 O(n^2) 运行时。更有效的方法是使用Map 并使用has() 检查是否存在,从而将运行时间降低到 O(n):

// see the bottom of the answer for a more efficient implementation than this
Object.defineProperty(Array.prototype, 'subtract', 
  configurable: true,
  value: function subtract (array) 
    return this.filter(
      function (element) 
        const count = this.get(element)

        if (count > 0) 
          this.set(element, count - 1)
        

        return count === 0
      , array.reduce(
        (map, element) => 
          if (map.has(element)) 
            map.set(element, map.get(element) + 1)
           else 
            map.set(element, 1)
          

          return map
        , new Map()
      )
    )
  ,
  writable: true
)

let a = [1, 2, 2, 3, 3, 3]
let b = [1, 2, 3]

console.log(a.subtract(b))

这是一个测试平台,与@Nina 的答案相比,显示了此解决方案的效率:

Array.prototype.ninaSubtract = function (array) 
    var hash = Object.create(null);
    array.forEach(function (a) 
        hash[a] = (hash[a] || 0) + 1;
    );
    return this.filter(function (a) 
       return !hash[a] || (hash[a]--, false);
    );


Object.defineProperty(Array.prototype, 'patrickSubtract', 
  configurable: true,
  value: function subtract (array) 
    return this.filter(
      function (element) 
        const count = this.get(element)

        if (count > 0) 
          this.set(element, count - 1)
        

        return count === 0
      , array.reduce(
        (map, element) => 
          if (map.has(element)) 
            map.set(element, map.get(element) + 1)
           else 
            map.set(element, 1)
          

          return map
        , new Map()
      )
    )
  ,
  writable: true
)

let a = [1, 2, 2, 3, 3, 3]
let b = [1, 2, 3]

let ninaStart = 0
let ninaStop = 0
let patrickStart = 0
let patrickStop = 0

ninaStart = performance.now()

for (let i = 100000; i > 0; i--) 
  a.ninaSubtract(b)


ninaStop = performance.now()

patrickStart = performance.now()

for (let i = 100000; i > 0; i--) 
  a.patrickSubtract(b)


patrickStop = performance.now()

console.log('Nina time: ', ninaStop - ninaStart, 'ms')
console.log('Patrick time: ', patrickStop - patrickStart, 'ms')

在我的笔记本电脑上使用 Chrome v60 运行几次表明 Nina 使用普通对象的更新答案比我的答案快大约 13%。

让我们尝试通过改进她的答案来解决这个问题:

Array.prototype.ninaSubtract = function (array) 
    var hash = Object.create(null);
    array.forEach(function (a) 
        hash[a] = (hash[a] || 0) + 1;
    );
    return this.filter(function (a) 
       return !hash[a] || (hash[a]--, false);
    );


Object.defineProperty(Array.prototype, 'patrickSubtract', 
  configurable: true,
  value: function subtract (array) 
    const lookup = Object.create(null)
    const output = []

    for (let index = 0; index < array.length; index++) 
      const element = array[index]
      lookup[element] = element in lookup ? lookup[element] + 1 : 1
    

    for (let index = 0; index < this.length; index++) 
      const element = this[index]

      if (!(element in lookup) || lookup[element]-- <= 0) 
        output.push(element)
      
    

    return output
  ,
  writable: true
)

let a = [1, 2, 2, 3, 3, 3]
let b = [1, 2, 3]

let ninaStart = 0
let ninaStop = 0
let patrickStart = 0
let patrickStop = 0

ninaStart = performance.now()

for (let i = 100000; i > 0; i--) 
  a.ninaSubtract(b)


ninaStop = performance.now()

patrickStart = performance.now()

for (let i = 100000; i > 0; i--) 
  a.patrickSubtract(b)


patrickStop = performance.now()

console.log('Nina time: ', ninaStop - ninaStart, 'ms')
console.log('Patrick time: ', patrickStop - patrickStart, 'ms')

虽然不那么规范,但它的性能要高得多,因为它避免使用 reduce()forEach()filter() 来减少函数调用的上下文切换。此解决方案的执行时间几乎是 Nina 更新答案的一半(大约快 43%):

Object.defineProperty(Array.prototype, 'subtract', 
  configurable: true,
  value: function subtract (array) 
    const lookup = Object.create(null)
    const output = []

    for (let index = 0; index < array.length; index++) 
      const element = array[index]
      lookup[element] = element in lookup ? lookup[element] + 1 : 1
    

    for (let index = 0; index < this.length; index++) 
      const element = this[index]

      if (!(element in lookup) || lookup[element]-- <= 0) 
        output.push(element)
      
    

    return output
  ,
  writable: true
)

let a = [1, 2, 2, 3, 3, 3]
let b = [1, 2, 3]

console.log(a.subtract(b))

更新

之前出现性能差异的原因是因为我之前使用Set的算法不正确。如果b 包含非唯一元素,则输出将不正确。

【讨论】:

以上是关于NodeJS - 从数组中减去数组,而不是删除所有重复项[重复]的主要内容,如果未能解决你的问题,请参考以下文章

NodeJS:如何从数组中删除重复项[重复]

.splice() 正在从数组中删除 2 个对象而不是 1 个

从 BigQuery 中的数组中减去项目

如何从数据数组中删除对象(核心数据)

NodeJs MongoDb Mongoose - 如何从多维数组中删除项目?

复选框数组返回nodejs中的最后一个检查值,而不是整个数组