重复数据删除大量数组的有效方法

时间:2019-09-17 20:18:36

标签: javascript arrays

我有一个非常大的数组数组(大约960,799个条目,或者可能更大)。我需要将其处理成一个新的数组,例如:

  1. 每个子数组均不包含重复项。
  2. 主数组不包含重复的子数组。

问题在于“重复子数组”必须包含具有相同值且顺序不同的数组。换句话说,如果我有这些子数组:

[[1,2,3], [1,2,3], [3,1,2]]

它们都将被视为重复项,并且只会保留一个(它们中的任何一个都没有关系;我一直只保留第一个;如果选定的子数组的顺序不重复也可以) t实际上是匹配的,即子数组中元素的顺序在处理过程中发生了变化。

我尝试的解决方案是基于对子数组进行重复数据删除,排序并将其与定界符连接,从而将所有子数组映射为字符串。然后,我对最后一个数组进行重复数据删除,然后通过拆分将它们映射回数组。它可以工作,但是过程非常缓慢。单遍需要30秒钟,而且由于我最终处理的数组会成倍增长,所以这是不可接受的。我需要一种更高效的算法。

这是我现在正在使用的代码,它很慢(ret是输入数组):

const stringList = ret.map(list => {
    return [...new Set(list)].sort().join('|');
});
const hashSet = new Set(stringList);
const output = [...hashSet].map(str => str.split('|'));

有人可以帮助我更有效地获得相同的结果吗?谢谢。

编辑

为了详细说明,我通过计算本质上是某些字符串输入的幂集的方法来获得这些庞大的输入数组。这是代码;如果可以一开始就阻止它产生重复的条目,那也很好,我想:

// Calculate the Cartesian product of set s
function cart(s) {
    return s.reduce((acc, val) => {
        return acc.map((x, i) => {
            return val.map(y => {
                return x.concat([y]);
            });
        }).flat();
    }, [[]]);
}

// Use the Cartesian product to calculate the power set of set s
function pset(s) {
    let ret = [];
    for (let i = 0; i < s.length; ++i) {
        const temp = [];
        for (let j = 0; j <= i; ++j) {
            temp.push([].concat(s));
        }
        ret = ret.concat(cart(temp));
    }
    return ret;
}

3 个答案:

答案 0 :(得分:0)

您可以生成无重复的功率集。

function pset(array) {
    function iter(index, temp) {
        if (index >= array.length) {
            temp.length && result.push(temp);
            return;
        }
        iter(index + 1, temp.concat(array[index]));
        iter(index + 1, temp);
    }
    var result = [];
    iter(0, []);
    return result;
}

console.log(pset(['a', 'b', 'c']));
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 1 :(得分:0)

鉴于我无法使用真实数据执行基准测试,因此无法验证这种方法对您的用例有多快,而是通过使用基本的for循环并尽可能避免使用功能代码为了方便起见,我提出了以下建议:

const ret = [[1, 2, 3], [1, 2, 3], [3, 1, 2], [1, 4, 5], [4, 1, 5]];

function ascending (a, b) {
  // works for strings and numbers
  return -(a < b) || +(a > b);
}

function ascending2d (a, b) {
  const aLength = a.length;
  const bLength = b.length;
  const length = Math.min(aLength, bLength);

  for (let i = 0; i < length; ++i) {
    const difference = ascending(a[i], b[i]);
    if (difference !== 0) return difference;
  }

  return aLength - bLength;
}

for (let i = 0; i < ret.length; ++i) {
  ret[i].sort(ascending);
}

ret.sort(ascending2d);

const output = [ret[0]];

for (let i = 1; i < ret.length; ++i) {
  const value = ret[i];
  if (ascending2d(ret[i - 1], value) !== 0) output.push(value);
}

console.log(output);

让我知道这是否是您当前方法的改进。您随时可以通过profiling your code并寻找可以重写的瓶颈来进一步提高性能。

性能基准

我已经在示例here中使用测试数据发布了基准测试,比较了您的原始解决方案,我的解决方案和Andrew的解决方案。我无法加入Nina进行比较,因为Nina不会在ret上执行重复数据删除,而是修改了ret的生成。

答案 2 :(得分:0)

编辑:没关系,我的实现没有基准。慢一点由于JSON.parseJSON.stringify的基础实现以及Array#sort的默认算法。

由于您正在寻找最前沿的性能,因此很难找到一个优雅的解决方案。如果使用Object.create(null)实例化对象,则可以最大程度地减少O(1)插入的开销。它创建没有原型的POJO。您也不需要在for in的循环中检查Object.hasOwnProperty,因为没有要搜索的原型。

const ret = [[], [1, 2, 3], [3, 1, 2], [1, 4, 5], [4, 1, 5]];

const hashMap = Object.create(null)
function createUniqArraysOfPrimitiveArrays(ret) {
  for (let i = 0; i < ret.length; i++) {
    const currEl = ret[i]
    if (currEl.length === 0) {
      hashMap['[]'] = null
    } else if (currEl.length === 1) {
      hashMap[`[${currEl[0]}]`] = null
    } else {
      hashMap[JSON.stringify(currEl.sort())] = null
    }
  }
  const outputArray = []
  for (const array in hashMap) {
    outputArray.push(JSON.parse(array))
  }
  return outputArray
}

console.log(createUniqArraysOfPrimitiveArrays(ret))