基于多个属性,从对象数组中过滤重复项,包括原始值。

3

data是一个对象列表。我们需要基于多个对象属性来筛选所有重复项,包括原始值。

我的代码可以根据多个对象属性来筛选出重复项,但是怎样调整代码才能同时筛选掉原始值呢?

我们的目标是得到这些重复项的列表。

const data = [{
  name: 'x',
  latitude: '45.9',
  longitude: '50.2'
}, {
  name: 'y',
  latitude: '45.9',
  longitude: '50.2'
}, {
  name: 'z',
  latitude: '40.5',
  longitude: '85.7'
}];

const duplicates = data
  .filter((obj, index, array) =>
    array.findIndex(o =>
      o.latitude === obj.latitude &&
      o.longitude === obj.longitude
    ) != index
  );

console.log(duplicates);

输出:

[{
  name: 'y',
  latitude: '45.9',
  longitude: '50.2'
}]

期望的输出:

[{
  name: 'x',
  latitude: '45.9',
  longitude: '50.2'
}, {
  name: 'y',
  latitude: '45.9',
  longitude: '50.2'
}]

1
是的,已经更新了问题以澄清 - 我想保留仅为重复的元素。 - brienna
3个回答

2

不使用findIndex,您可以运行一个for循环并加上额外条件,即索引不能是您正在检查的相同索引

基于此,您可以直接从循环内部返回。

var data = [
    { name: 'x',
        latitude: '45.9',
      longitude: '50.2'},
    { name: 'y',
        latitude: '45.9',
      longitude: '50.2'},
    { name: 'z',
        latitude: '40.5',
      longitude: '85.7'},
];

var duplicates = data.filter((obj, index, array) => {
  for(let i = 0 ; i < array.length;i++){
  if(i!=index && array[i].latitude == obj.latitude 
    && array[i].longitude == obj.longitude ){
      return true;
    }
  }
  return false;    
});
    
    

console.log(duplicates);


@brienna... 首先,不需要嵌套循环;其次,在for循环中不能在中途使用return - Peter Seliger
@PeterSeliger 它能正常工作吗?如果我将 return falsereturn true 互换,它会导致唯一性。 - brienna
@brienna...虽然它能工作,但这并不意味着一个糟糕实现的解决方案是正确的。 - Peter Seliger

1
这种基于reduce的方法,通过地理坐标签名检测重复项,该签名是一个字符串键,由每个项目的latitudelongitude属性值连接而成。此键用于对坐标项进行分组,组类型的值告诉我们签名是单个项还是相同的坐标项(重复项)。一旦找到至少两个重复项,这些项也会被内部累加器list对象收集。因此,此方法只迭代一次并在单个reduce周期结束时提供最终结果...

function collectDuplicates(collector, item) {
  const { index, list } = collector;
  const { latitude, longitude } = item;

  const key = [
    parseFloat(latitude),
    parseFloat(longitude),
  ].join('/');

  const grouped = index[key];

  if (Array.isArray(grouped)) {
    // already more than 2 duplicates detected.

    grouped.push(item);
    list.push(item);

  } else if (grouped) {
    // first time duplicate detection (2 same items).

    index[key] = [grouped, item];
    list.push(grouped, item);

  } else {
    // register first item of its kind.
    index[key] = item;
  }
  return collector;
}

const data = [{
  name: 'x',
  latitude: '45.9',
  longitude: '50.2'
}, {
  name: 'y',
  latitude: '45.9',
  longitude: '50.2'
}, {
  name: 'z',
  latitude: '40.5',
  longitude: '85.7'
}];

const duplicates =
  data.reduce(collectDuplicates, { index: {}, list: [] }).list;

console.log({ duplicates });

console.log(
  'data.reduce(collectDuplicates, { index: {}, list: [] }) ...',
  data.reduce(collectDuplicates, { index: {}, list: [] })
)
.as-console-wrapper { min-height: 100%!important; top: 0; }


有趣的方法。请看我的回答,比较一下你的答案和其他人在这里的输出结构和性能特征。 - Scott Sauyet

1

一个简单的修复您代码以完成此操作的方法可能如下所示:

const duplicates = (data) => data
  .filter((obj, index, array) =>
    array.find((o, i) =>
      o.latitude === obj.latitude &&
      o.longitude === obj.longitude &&
      i != index
    ) 
  )

我们只需要在find回调函数内测试不匹配的索引即可。
但我认为将过滤/重复检查逻辑与测试两个元素是否相等的代码分离出来会有很多好处。这样做更加合理,并且我们可以从中获得一个可重用的函数。

So I might write it like this:

const keepDupsBy = (eq) => (xs) => xs .filter (
  (x, i) => xs .find ((y, j) => i !== j && eq (x, y))
)

const dupLocations = keepDupsBy ((a, b) => 
  a .latitude == b.latitude && 
  a .longitude == b .longitude
) 

const data = [{name: 'x', latitude: '45.9', longitude: '50.2'}, {name: 'y', latitude: '45.9', longitude: '50.2'}, {name: 'z', latitude: '40.5', longitude: '85.7'}];

console .log (dupLocations (data))
.as-console-wrapper {max-height: 100% !important; top: 0}

这将保留原始数组中所有在其他地方有重复的元素,并按它们在原始数组中的相对顺序返回。 这与上面的结果相同,但与Peter Seliger答案中有趣的方法不同,后者将所有匹配值分组在一起,并按每个组的第一个元素的相对顺序返回。
请注意,如果您希望在大型列表上使用此功能,则性能差异很大。 您的原始列表和除Peter外的所有答案都在O(n ^ 2)时间内运行。 Peter的操作在O(n)时间内完成。 对于较大的列表,差异可能很大。 当涉及到内存资源时,权衡是不同的,因为Peter的操作需要额外的O(n)内存,而所有其他操作都在常量内存中运行- O(1)。 除非您处理数万个元素或以上,否则这些都不太可能有影响,但通常值得考虑。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接