【问题标题】:How to remove repeating entries in a massive array (javascript)如何删除大量数组中的重复条目(javascript)
【发布时间】:2019-11-15 14:02:32
【问题描述】:

我正在尝试使用 Kendo UI 绘制一个庞大的数据集(大约 160 万个点)。这个数字太大了,但我发现其中许多点都在重复。数据当前以这种格式存储: [ [x,y], [x,y], [x,y]...] 每个 x 和 y 都是一个数字,因此每个子数组都是一个点。 我想到的方法是创建第二个空数组,然后遍历非常长的原始数组,如果还没有找到新的点,则只将每个点推到新的数组中。

我尝试使用 jQuery.inArray(),但它似乎不适用于我这里的 2D 数组。

我目前正在尝试这个:

    var datMinified = [];
    for( z = 2; z < dat1.length; z++) //I start at 2 because the first 2 elements are strings, disregard this
     {

       if( !(testContains(datMinified, dat1[z])) )
       {

         datMinified.push(dat1[z])

       }
      }

辅助函数定义为:

    function testContains(arr, val)
      {
        for(i=0;i<arr.length;i++)
        {
          if( arraysEqual( arr[i], val) )
          {
            return true;
          }
        }
        return false;
      }

和:

    function arraysEqual(arr1, arr2)
    {
      if(! (arr1.length == arr2.length))
      {
        return false;
      }
      for( i = 0; i < arr1.length; i++ )
      {
        if( !(arr1[i] == arr2[i]))
        {
          return false;
        }
      }
      return true;
    }

当我运行这个脚本时,即使是长度为 6000 的较小数组,它仍然会卡住。 也许 jQuery 是一个很好的解决方案?

编辑:我也在想可能有某种方法可以告诉浏览器不要超时,而只是坐下来处理数据?

【问题讨论】:

  • 在这种情况下,您应该真正对数据进行预处理(以对您的点元组进行重复数据删除),然后将那个存储并提供给您的用户界面。无论您采用何种重复数据删除解决方案,在运行时处理客户端上的 160 万个条目都会“缓慢”,尤其是因为您只保留了 0.3% 的数据集!
  • @msanford 所以,我试图让这个应用程序(数据绘图仪)成为离线类型的东西;目的是拥有一个可以在任何操作系统上运行的 html 文件。我可以让服务器端数据处理在同一个客户端上运行吗?加载时间不是问题,事实上我完全可以让它加载几分钟

标签: javascript asynchronous bigdata


【解决方案1】:

你有一个不平凡的问题,但我会立即解决,所以如果我在某个地方失去你,请提出问题。此解决方案将坐标转换为字符串或使用JSON.stringify等其他技术将其序列化 -

从创建坐标的方法开始 -

const Coord = (x, y) =>
  [ x, y ]

为了演示解决方案,我需要构造许多随机坐标-

const rand = x =>
  Math.floor(Math.random() * x)

const randCoord = x => 
  Coord(rand(x), rand(x))

console.log(randCoord(1e3))
// [ 655, 89 ]

现在我们创建一个包含 100 万个随机坐标的数组 -

const million =
  Array.from(Array(1e6), _ => randCoord(1e3))

现在我们使用DeepMap创建一个函数来过滤所有唯一值,这是我在this answer中开发的一个小模块。

const uniq = (coords = []) =>
{ const m = new Map
  const r = []
  for (const c of coords)
    if (!DeepMap.has(m, c))
      { DeepMap.set(m, c, true)
        r.push(c)
      }
  return r
}

由于forDeepMap具有出色的性能,uniq可以在不到一秒内识别所有唯一值 -

console.time("uniq")
const result = uniq(million)
console.timeEnd("uniq")

console.log("uniq length:", result.length)
console.log("sample:", result.slice(0,10))

// uniq: 535 ms
// uniq length: 631970
// sample: 
// [ [ 908, 719 ]
// , [ 532, 967 ]
// , [ 228, 689 ]
// , [ 942, 546 ]
// , [ 716, 180 ]
// , [ 456, 427 ]
// , [ 714, 79 ]
// , [ 315, 480 ]
// , [ 985, 499 ]
// , [ 212, 407 ]
// ]

展开下面的sn-p,在自己的浏览器中验证结果-

const DeepMap =
  { has: (map, [ k, ...ks ]) =>
      ks.length === 0
        ? map.has(k)
        : map.has(k)
          ? DeepMap.has(map.get(k), ks)
          : false

  , set: (map, [ k, ...ks ], value) =>
      ks.length === 0
        ? map.set(k, value)
        : map.has(k)
            ? (DeepMap.set(map.get(k), ks, value), map)
            : map.set(k, DeepMap.set(new Map, ks, value))
  }

const Coord = (x, y) =>
  [ x, y ]

const rand = x =>
  Math.floor(Math.random() * x)

const randCoord = x => 
  Coord(rand(x), rand(x))

const million =
  Array.from(Array(1e6), _ => randCoord(1e3))

const uniq = (coords = []) =>
{ const m = new Map
  const r = []
  for (const c of coords)
    if (!DeepMap.has(m, c))
      { DeepMap.set(m, c, true)
        r.push(c)
      }
  return r
}

console.time("uniq")
const result = uniq(million)
console.timeEnd("uniq")

console.log("uniq length:", result.length)
console.log("sample:", result.slice(0,10))

// uniq: 535 ms
// uniq length: 631970
// sample: 
// [ [ 908, 719 ]
// , [ 532, 967 ]
// , [ 228, 689 ]
// , [ 942, 546 ]
// , [ 716, 180 ]
// , [ 456, 427 ]
// , [ 714, 79 ]
// , [ 315, 480 ]
// , [ 985, 499 ]
// , [ 212, 407 ]
// ]

通过生成更小的随机坐标,我们可以验证uniq 正在生成正确的输出。下面我们生成高达[ 100, 100 ] 的坐标,最大可能有10,000 个唯一坐标。当您运行下面的程序时,由于坐标是随机生成的,result.length 可能会低于 10,000,但它永远不应超过它 - 在这种情况下,我们会知道一个无效的 (重复)坐标已添加 -

const million =
  Array.from(Array(1e6), _ => randCoord(1e2))

console.time("uniq")
const result = uniq(million)
console.timeEnd("uniq")

console.log("uniq length:", result.length)
console.log("sample:", result.slice(0,10))

// uniq: 173 ms
// uniq length: 10000
// sample: 
// [ [ 50, 60 ]
// , [ 18, 69 ]
// , [ 87, 10 ]
// , [ 8, 7 ]
// , [ 91, 41 ]
// , [ 48, 47 ]
// , [ 78, 28 ]
// , [ 39, 12 ]
// , [ 18, 84 ]
// , [ 0, 71 ]
// ]

展开下面的sn-p,在自己的浏览器中验证结果-

const DeepMap =
  { has: (map, [ k, ...ks ]) =>
      ks.length === 0
        ? map.has(k)
        : map.has(k)
          ? DeepMap.has(map.get(k), ks)
          : false

  , set: (map, [ k, ...ks ], value) =>
      ks.length === 0
        ? map.set(k, value)
        : map.has(k)
            ? (DeepMap.set(map.get(k), ks, value), map)
            : map.set(k, DeepMap.set(new Map, ks, value))
  }

const Coord = (x, y) =>
  [ x, y ]

const rand = x =>
  Math.floor(Math.random() * x)

const randCoord = x => 
  Coord(rand(x), rand(x))

const uniq = (coords = []) =>
{ const m = new Map
  const r = []
  for (const c of coords)
    if (!DeepMap.has(m, c))
      { DeepMap.set(m, c, true)
        r.push(c)
      }
  return r
}

const million =
  Array.from(Array(1e6), _ => randCoord(1e2))

console.time("uniq")
const result = uniq(million)
console.timeEnd("uniq")

console.log("uniq length:", result.length)
console.log("sample:", result.slice(0,10))

// uniq: 173 ms
// uniq length: 10000
// sample: 
// [ [ 50, 60 ]
// , [ 18, 69 ]
// , [ 87, 10 ]
// , [ 8, 7 ]
// , [ 91, 41 ]
// , [ 48, 47 ]
// , [ 78, 28 ]
// , [ 39, 12 ]
// , [ 18, 84 ]
// , [ 0, 71 ]
// ]

最后,我将包含这里使用的 DeepMap 模块 -

const DeepMap =
  { has: (map, [ k, ...ks ]) =>
      ks.length === 0
        ? map.has(k)
        : map.has(k)
          ? DeepMap.has(map.get(k), ks)
          : false

  , set: (map, [ k, ...ks ], value) =>
      ks.length === 0
        ? map.set(k, value)
        : map.has(k)
            ? (DeepMap.set(map.get(k), ks, value), map)
            : map.set(k, DeepMap.set(new Map, ks, value))

  , get: (map, [ k, ...ks ]) =>
    // ...

  , entries: function* (map, fields = [])
    // ...
  }

有关完整的实现,请参阅linked Q&A。 Fwiw,我认为您会发现该链接很有趣,因为它为这个问题的复杂性提供了更多背景信息。

【讨论】:

  • 不能选择转换为字符串时的好解决方案!上面的Set 方法在技术上是一个 3 班轮虽然 =D...所以对我来说有点像这里的“简单”值的东西很有吸引力!
  • 这个解决方案给我留下了深刻的印象,它运行良好。我发现它非常有趣,我仍在尝试了解它是如何工作的。你的实现真的很强大
【解决方案2】:

你可以试试这样的。做一些基准测试可能会有所帮助,或者考虑做服务器端。这是很多数据,您可能会看到大多数浏览器挂起:

points = ["test", "string", [1,1], [1,2],[1,3],[1,4],[1,2],[1,3],[1,4],[1,5],[1,6],[1,7],[1,8],[2,1],[2,1],[2,2],[1,1],[1,1],[1,1],[1,1],[1,1]];
t={};
unique = points.filter(e=>!(t[e]=e in t));
console.log(unique);

【讨论】:

  • 请注意,这会将点转换为字符串。在这种微不足道的情况下,该技术有效,但并不总是可以对复合数据进行字符串化。例如 String([1,2,3])String([1,"2,3"])String(["1,2",3])String(["1,2,3"]) 生成相同的字符串,最后三个将作为第一个的重复项被删除,即使每个输入都是完全唯一的。
  • 即,使用points = [[1,2,3], [1,"2,3"], ["1,2",3], ["1,2,3"]] 重新运行您的程序 - 结果是[[1,2,3]]
【解决方案3】:

更新

简而言之:您可以使用 Set 自动创建唯一值的集合(这是 SetMap 的区别),如果这些值采用合适的(例如可比较的)格式:

let collection = new Set(data.map((point) => point.toString()));
collection = [...collection].map((val) => val.split(','));

这两行足以在大约 1 秒内将 100 万 + 数组过滤为唯一值。更详细的解释见第三个例子 =)...


原答案

jQuery 主要用于 DOM 操作和帮助(旧的)浏览器怪癖,而不是处理大数据!所以,不,我不建议这样做,而且它会进一步减慢你的处理速度......问题是,你可以在你的应用程序中使用现代 JS(例如生成器函数)还是它也必须在旧浏览器中工作?

我不确定这对超过 1 万个条目的性能有何影响,但请告诉我这是如何工作的(data 当然是你的datMinified):

const data = [
    'string',
    'string',
    [1, 2],
    [1, 2],
    [2, 3],
    [3, 4],
    [3, 4],
    [4, 5],
];

data.splice(0, 2); // remove your 2 strings at the beginning

console.time('filtering with reduce');
let collection = data.reduce((acc, val) => {
    const pointstr = val.toString();
    if ( !acc.includes(pointstr) ) {
        acc.push(pointstr);
    }

    return acc;
}, []);
collection.map((point) => point.split(','));
console.timeEnd('filtering with reduce');
console.log(`filtered data has ${collection.length} entries!`);

生成器函数可以帮助您降低内存消耗(也许?)=),并且可以省去上面示例末尾的 .map() 部分:

console.time('filtering with generator');
function* filter(arr) {
    let filtered = [];
    for (var i = 0, l = arr.length; i < l; i++ ) {
        const pointstr = arr[i].toString();
        if ( !filtered.includes(pointstr) ) {
            filtered.push(pointstr);
            yield arr[i];
        }
    }
}
let collection = [];
for (let point of filter(data)) {
    collection.push(point);
}
console.timeEnd('filtering with generator');
console.log(`filtered data has ${collection.length} entries!`);

编辑

上述两种情况在性能方面都很糟糕,这里是您的用例的现实场景,具有 1'000'000 个数据点,并且基于 @user633183 的建议使用 Set 或 @ 进行了显着改进987654331@。我选择使用集合是因为​​它代表了一个唯一值的集合,这正是我们想要的,例如它会自动为我们处理过滤(如果数据的格式正确,当然可以识别重复项):

const randomBetween = (min,max) => Math.floor(Math.random()*(max-min+1)+min);

var data = Array(1000000);
for (var i = data.length; i; data[--i] = [randomBetween(1,1000), randomBetween(1, 1000)]);

console.log(`unfiltered data has ${data.length} entries!`);

console.time('filtering');

// create the Set with unique values by adding them as strings
// like that the Set will automatically filter duplicates
let collection = new Set(data.map((point) => point.toString()));

console.log(`filtered data has ${collection.size} entries!`);

// we still have to revert the toString() process here
// but we operate on the automatically filtered collection of points
// and this is fast!
collection = [...collection].map((val) => val.split(','));
console.log(`resulting data has ${collection.length} entries!`);
console.timeEnd('filtering');

再次感谢@user633183,今天学到了一些东西=)!

另一种选择是将生成器函数与Set 结合起来,如下所示:

console.time('filtering with generator and Set');
function* filterSet(arr) {
    let filtered = new Set();
    for (var i = 0, l = arr.length; i < l; i++ ) {
        const pointstr = arr[i].toString();
        if ( !filtered.has(pointstr) ) {
            filtered.add(pointstr);
            yield arr[i];
        }
    }
}
let collection = [];
for (let point of filterSet(data)) {
    collection.push(point);
}
console.timeEnd('filtering with generator and Set');
console.log(`filtered data has ${collection.length} entries!`);

这再次使您不必反转.toString(),并且比“直接”new Set() 方法略快。

为了完成这一点,这里在我的机器上使用 100'000 个数据点进行完全主观的基准测试:

unfiltered data has 100000 entries!
filtering with reduce: 31946.634ms
filtered data has 95232 entries!
filtering with generator: 39533.802ms
filtered data has 95232 entries!
filtering with generator and Set: 107.893ms
filtered data has 95232 entries!
filtering with Set: 159.894ms
filtered data has 95232 entries!

【讨论】:

  • 我不认为我需要支持旧浏览器,生成器函数如何帮助解决这个问题?
  • @msanford 不是真的,我打算在澄清上述内容后提供答案
  • @msanford 谢谢!感谢您的反馈,在 SO 上仍然没有太多经验。
  • @exside 查看 MapSet 以获得闪电般的快速查找时间
  • @user633183 绝对同意,该解决方案仅适用于这种场景中使用的简单数据,您的方法更加通用,我肯定会为它添加书签,以防我偶然发现更复杂的类似问题数据!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-06
  • 1970-01-01
  • 1970-01-01
  • 2013-12-05
  • 2019-12-14
  • 2010-12-17
相关资源
最近更新 更多