【问题标题】:Find Duplicate Array within Array在数组中查找重复数组
【发布时间】:2016-08-15 13:08:30
【问题描述】:

给定一个数组,识别重复项的有效方法是什么?

var array = [
  [
    11.31866455078125,
    44.53836644772605
  ],
  [                     // <-- Here's the duplicate
    11.31866455078125,
    44.53836644772605
  ],
  [
    11.371536254882812,
    44.53836644772605
  ],
  [
    11.371536254882812,
    44.50140292110874
  ]
]

我一直在使用 lodash 作为可接受的依赖项来处理这个问题,我知道如何使用 _.uniqWith_.isEqual 返回“唯一”列表:

_.uniqWith(array,_.isEqual)

With 会给出列表的“唯一”版本:

[ 
    [ 11.31866455078125,  44.53836644772605 ],
    [ 11.371536254882812, 44.53836644772605 ],
    [ 11.371536254882812, 44.50140292110874 ]
]

但我不仅需要报告唯一元素,还需要重复的元素,理想情况下是第一次出现的索引。

lodash 库中是否通过我缺少的一些方法组合实际涵盖了这一点?还是我只能忍受编写循环来比较元素。

可能只是对此感到过度疲劳,因此欢迎以新的眼光看待这个问题。

如果有适合的库方法,尽量不要重写函数,所以我基本上坚持:

  1. 仅返回重复项或至少返回与“唯一列表”的比较差异。

  2. 基本上识别数组中数组的“索引”。虽然我认为一旦识别出重复项目,这可能是使用_.isEqual 减少过滤器。

还尝试避免创建对象 Hash/Map 并在此处计算键的出现次数,或者至少不作为单独的对象,并且作为可以在功能上“内联”完成的事情。

【问题讨论】:

    标签: javascript arrays underscore.js lodash


    【解决方案1】:

    你可以使用纯 javascript 来做到这一点,这并不难,这是我的实现

    for (let i = 0; i < array.length; i++) {
      for (let j = i + 1; j < array.length; j++) {
      
         // quick elimination by comparing sub-array lengths
         if (array[i].length !== array[j].length) {
            continue;
         }
         // look for dupes
         var dupe = true;
         for (var k = 0; k < array[i].length; k++) {
           if (array[i][k] !== array[j][k]) {
             dupe = false;
             break;
           }
         }
         // if a dupe then print
         if (dupe) {
             console.debug("%d is a dupe", j); 
         }
       }
     }
    

    这个实现的好处是它会多次打印出索引处的数组是多个欺骗的欺骗,你可以使用这个事实来计算每个索引中的欺骗!

    这实际上是一种非常有效的方法,因为内部for 循环(j)总是从外部循环的下一个位置(i)运行。所以你的支票数量减半。

    这是plunk

    【讨论】:

      【解决方案2】:

      我相信构建 LUT 是进行比较时最有效的方法之一。以下方法利用Array.prototype.reduce() 构造了一个LUT,并最终通过删除不仅一个而且所有重复元素来改变原始数组,无论有多少重复元素。

      var arr = [
        [
          11.31866455078125,
          44.53836644772605
        ],
        [
          11.31866455078125,
          44.53836644772605
        ],
        [
          11.371536254882812,
          44.53836644772605
        ],
        [
          11.371536254882812,
          44.50140292110874
        ]
      ];
      arr.reduce((p,c,i)=> { var prop = c[0]+"" + c[1]+"";
                             p[prop] === void 0 ? p[prop] = i : p.dups.push(i);
                             return p;
                           },{dups:[]}).dups.reverse().forEach( i => arr.splice(i,1))
      
      document.write('<pre>' + JSON.stringify(arr, 0, 2) + '</pre>');

      但是,如果您想通过保留原始数组来获得一个新数组,那么显然这会更快。

      【讨论】:

        【解决方案3】:

        这是一种使用uniqWith()difference()的方法:

        _.indexOf(array, _.head(_.difference(array, _.uniqWith(array, _.isEqual))));
        

        基本思路是:

        1. 使用uniqWith() 删除array 中的重复项。
        2. 使用difference()array 与无重复版本进行比较。这为我们提供了一个重复的数组。
        3. 使用head() 获取数组的第一项。这是我们感兴趣的副本。
        4. 使用indexOf() 查找重复项的索引,在本例中为1

        但是,如果你需要原件的索引,而不是重复,我们就得做一些调整:

        var duplicate = _.head(_.difference(array, _.uniqWith(array, _.isEqual)));
        _.findIndex(array, _.unary(_.partial(_.isEqual, duplicate)));
        

        我们仍在使用uniqWith()difference() 来查找duplicate。但是现在,我们使用findIndex() 来获取索引。原因是我们需要使用isEqual() 来查找副本的first 位置,而不是second。我们使用partial()unary() 构造谓词。这次的结果是0

        【讨论】:

        • 我发誓这正是我尝试的第一件事,因为它合乎逻辑。但我认为我的大脑开始使用_.differenceWith() 和同样的_.isEqual,其中只需要一个普通的_.difference()。过度思考它可以被拒绝。索引匹配的好方法。
        【解决方案4】:

        Lodash 提供了很多有用的函数来实现查找第一个重复索引。
        使用_.findIndex()_.isEqual() 以下代码将找到第一个重复索引:

        var duplicateIndex = _.findIndex(array, function(value, index, collection) {
          var equal = _.isEqual.bind(undefined, value);
          return _.findIndex(collection.slice(0, index), equal) !== -1;
        });
        

        或者更快一点但更详细:

        var duplicateIndex = _.findIndex(array, function(value, index, collection) {
          var equal = _.isEqual.bind(undefined, value);
          return _.findIndex(collection, function(val, ind) {
             return ind < index && equal(val);
          }) !== -1;
        });
        

        请注意,如果不存在重复项,则将返回 -1
        简而言之,该算法遍历数组,如果当前元素不存在,则回溯。如果是,则返回当前迭代索引。
        请检查工作中的demo

        【讨论】:

        • 进一步看,我发现了我的错字,并仔细查看了代码并了解您在这里做什么。不能说我对使用.slice() 来不断增加列表非常满意,但它确实比索引循环更干净。仔细考虑。
        • @NeilLunn _.findIndex(collection.slice(0, index), equal) !== -1; 可以简化为手动 findIndex 以仅迭代一次。但目前的方法是紧凑。
        • 我在想什么。不管怎样,你得到了我的选票。我仍然只是清醒头脑并考虑选择。就像我说的,这是一种比其他方法更简洁的编码方法。
        【解决方案5】:

        除了自己编写算法外,我不知道该怎么做。这个答案和其他发布的答案都不是很有效,但应该没问题:

        function findIndex(array, startingIndex, value) {
          var predicate = _.partial(_.isEqual, value);
          var arraySubset = array.slice(startingIndex+1);
          var index = arraySubset.findIndex(predicate);
          return index === -1 ? index : index+startingIndex+1;
        }
        
        function findDuplicates(array) {
          return array.map((value, index) => {
            return {
              value,
              index: findIndex(array, index, value)
            };
          }).filter(info => info.index !== -1);
        }
        
        findDuplicates([1, 2, 3, 4, 1, [ 3 ], [ 4 ], [ 3 ] ]);
        
        // [ { value: 1, index: 4 }, { value: [ 3 ], index: 7 } ]    // [ { value: 1, index: 4 }, { value: [ 3 ], index: 7 } ]
        

        这基本上创建了一个数组的映射,对数组的其余部分调用 .findIndex(),记下任何重复项的索引,返回有关具有重复项的每个项目的信息以及重复项的索引是什么。

        这方面的一个好处是它适用于三次或任意数量的值出现。

        【讨论】:

          猜你喜欢
          • 2022-01-21
          • 2018-05-16
          • 2015-04-13
          • 2016-01-17
          相关资源
          最近更新 更多