【问题标题】:How to find duplicates in a list?如何在列表中查找重复项?
【发布时间】:2014-09-03 22:58:02
【问题描述】:

我有一个 未排序 整数列表,我想找到那些有重复的元素。

val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)

我可以通过 dup.distinct 找到集合的不同元素,所以我将答案写如下。

val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)
val distinct = dup.distinct
val elementsWithCounts = distinct.map( (a:Int) => (a, dup.count( (b:Int) => a == b )) )
val duplicatesRemoved = elementsWithCounts.filter( (pair: Pair[Int,Int]) => { pair._2 <= 1 } )
val withDuplicates = elementsWithCounts.filter( (pair: Pair[Int,Int]) => { pair._2 > 1 } )

有没有更简单的方法来解决这个问题?

【问题讨论】:

    标签: list scala scala-collections


    【解决方案1】:

    试试这个:

    val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)
    dup.groupBy(identity).collect { case (x, List(_,_,_*)) => x }
    

    groupBy 将每个不同的整数与其出现的列表相关联。 collect 基本上是 map,其中不匹配的元素被忽略。 case 之后的匹配模式将匹配与匹配模式 List(_,_,_*) 的列表相关联的整数 x,该列表具有至少两个元素,每个元素由下划线表示,因为我们实际上不需要存储这些元素值(这两个元素后面可以跟零个或多个元素:_*)。

    你也可以这样做:

    dup.groupBy(identity).collect { case (x,ys) if ys.lengthCompare(1) > 0 => x }
    

    它比您提供的方法快得多,因为它不必重复传递数据。

    【讨论】:

    • List(_,_,_*) 可以替换为 _::_::_。这是否更清楚取决于。此外,ys.size &gt; 1 可以替换为 ys.lengthCompare(1) &gt; 0,以避免遍历整个重复列表来查找大小。
    • 真的需要解释 - 谢谢!有了它,它实际上相当简单。学会了 '_*' 把戏 - 很好!
    • 注意:由于某种原因,此代码未能检测到我的重复项(来自文件名列表),但@LuigiPlinge 的回答有效。
    • 我想提取重复项和重复次数,以下内容运行良好并且对我来说是可读的:dup.groupBy(identity).map(t =&gt; (t._1, t._2.size))。结果:Map(101 -&gt; 2, 5 -&gt; 2, 1 -&gt; 3, 6 -&gt; 1, 102 -&gt; 1, 2 -&gt; 1, 3 -&gt; 1, 4 -&gt; 1, 100 -&gt; 1)
    • @akauppi 也许你遇到了和我一样的问题。部分函数case (x, List(_,_,_*)) 不会匹配任何东西,因为我的输入Seq 是一个缓冲区,因此我不得不使用case (x, Buffer(_,_,_*)) 来匹配......真的很烦追踪。所以我不太喜欢这种方法,它太依赖于实现了。
    【解决方案2】:

    聚会有点晚了,但这是另一种方法:

    dup.diff(dup.distinct).distinct
    

    diff 为您提供参数 (dup.distinct) 中的所有额外项目,它们是重复项。

    【讨论】:

    • 取决于 dup 的类型,这可能是 O(N^2)。 @dhg 的答案是 O(N)。
    • @pathikrit 对于哪些类型的算法将是 O(N^2)?
    • 我不相信 groupBy 方法是 O(n) 或 distinct 不是 O(n) fwiw。
    【解决方案3】:

    另一种方法是使用foldLeft 并努力做到这一点。

    我们从两个空集开始。一种是我们至少见过一次的元素。另一个是我们至少见过两次(又名重复)的元素。

    我们遍历列表。当当前元素已被看到 (seen(cur)) 时,它是重复的,因此添加到 duplicates。否则我们将其添加到seen。 结果现在是包含重复项的第二组。

    我们也可以把它写成一个泛型方法。

    def dups[T](list: List[T]) = list.foldLeft((Set.empty[T], Set.empty[T])){ case ((seen, duplicates), cur) => 
      if(seen(cur)) (seen, duplicates + cur) else (seen + cur, duplicates)      
    }._2
    
    val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)
    
    dups(dup) //Set(1,5,101)
    

    【讨论】:

      【解决方案4】:

      总结: 我编写了一个非常高效的函数,它返回 List.distinctList ,其中包含多次出现的每个元素以及出现重复元素的索引。

      详情: 如果您需要更多关于重复项本身的信息,就像我所做的那样,我已经编写了一个更通用的函数,它只迭代一次 List(因为排序很重要)并返回一个由原始 @987654327 组成的 Tuple2 @deduped(删除第一个之后的所有重复项;即与调用distinct 相同)和第二个List 显示每个重复项以及它在原始List 中出现的Int 索引。

      我已经基于general performance characteristics of the Scala collections实现了两次功能; filterDupesL(L 代表线性)和filterDupesEc(Ec 代表有效常数)。

      这里是“线性”函数:

      def filterDupesL[A](items: List[A]): (List[A], List[(A, Int)]) = {
        def recursive(
            remaining: List[A]
          , index: Int =
              0
          , accumulator: (List[A], List[(A, Int)]) =
              (Nil, Nil)): (List[A], List[(A, Int)]
        ) =
          if (remaining.isEmpty)
            accumulator
          else
            recursive(
                remaining.tail
              , index + 1
              , if (accumulator._1.contains(remaining.head)) //contains is linear
                (accumulator._1, (remaining.head, index) :: accumulator._2)
              else
                (remaining.head :: accumulator._1, accumulator._2)
            )
        val (distinct, dupes) = recursive(items)
        (distinct.reverse, dupes.reverse)
      }
      

      下面是一个示例,它可能会使其更直观。鉴于此字符串值列表:

      val withDupes =
        List("a.b", "a.c", "b.a", "b.b", "a.c", "c.a", "a.c", "d.b", "a.b")
      

      ...然后执行以下操作:

      val (deduped, dupeAndIndexs) =
        filterDupesL(withDupes)
      

      ...结果是:

      deduped: List[String] = List(a.b, a.c, b.a, b.b, c.a, d.b)
      dupeAndIndexs: List[(String, Int)] = List((a.c,4), (a.c,6), (a.b,8))
      

      如果您只想要重复项,您只需 map 穿过 dupeAndIndexes 并调用 distinct

      val dupesOnly =
        dupeAndIndexs.map(_._1).distinct
      

      ...或全部在一次调用中:

      val dupesOnly =
        filterDupesL(withDupes)._2.map(_._1).distinct
      

      ...或者如果首选Set,请跳过distinct 并调用toSet...

      val dupesOnly2 =
        dupeAndIndexs.map(_._1).toSet
      

      ...或全部在一次调用中:

      val dupesOnly2 =
        filterDupesL(withDupes)._2.map(_._1).toSet
      

      对于非常大的Lists,考虑使用这个更高效的版本(它使用额外的Set 来更改contains 签入有效的恒定时间):

      这是“有效恒定”函数:

      def filterDupesEc[A](items: List[A]): (List[A], List[(A, Int)]) = {
        def recursive(
            remaining: List[A]
          , index: Int =
              0
          , seenAs: Set[A] =
              Set()
          , accumulator: (List[A], List[(A, Int)]) =
              (Nil, Nil)): (List[A], List[(A, Int)]
        ) =
          if (remaining.isEmpty)
            accumulator
          else {
            val (isInSeenAs, seenAsNext) = {
              val isInSeenA =
                seenAs.contains(remaining.head) //contains is effectively constant
              (
                  isInSeenA
                , if (!isInSeenA)
                    seenAs + remaining.head
                  else
                    seenAs
              )
            }
            recursive(
                remaining.tail
              , index + 1
              , seenAsNext
              , if (isInSeenAs)
                (accumulator._1, (remaining.head, index) :: accumulator._2)
              else
                (remaining.head :: accumulator._1, accumulator._2)
            )
          }
        val (distinct, dupes) = recursive(items)
        (distinct.reverse, dupes.reverse)
      }
      

      以上两个函数都是对我的开源 Scala 库 ScalaOlio 中的 filterDupes 函数的改编。它位于org.scalaolio.collection.immutable.List_._

      【讨论】:

        【解决方案5】:
        def findDuplicates[T](seq: Seq[T]): Seq[T] = {
          seq.groupMapReduce(identity)(_ => false)((_, _) => true).filter(_._2).keys.toSeq
        }
        

        我们首先将每个元素与false(映射阶段)相关联,一旦找到重复项,我们将其与true(减少阶段)相关联。我们利用这样一个事实,即对于每个元素,减少阶段仅在该元素是重复的情况下完成。然后我们过滤只保留与true 关联的元素(过滤阶段)。

        这适用于特征Seq所有实现。

        时间和空间复杂度均为O(n)

        与其他答案比较:

        1. @dhg 答案不适用于所有类型的序列。例如。它适用于List,但会为Array 产生不正确的结果(因为List 模式匹配不适用于Array)。此外,虽然它具有相同的时间和空间复杂度,但它为每个包含相应元素的所有重复项的元素创建一个 List,只是为了检查列表是否包含多个元素。这意味着内存占用和运行时间会更高。
        2. @Luigi Plinge 的回答非常优雅。但是,它会创建 3 次中间的类似 hashmap 的数据结构(diff 1 次,distinct 2 次),因此运行时间可能会更高。

        【讨论】:

          【解决方案6】:

          我最喜欢的是

          def hasDuplicates(in: List[Int]): Boolean = {
            val sorted = in.sortWith((i, j) => i < j)
            val zipped = sorted.tail.zip(sorted)
            zipped.exists(p => p._1 == p._2)
          }
          

          【讨论】:

          • 欢迎来到 SO!我们通常建议向提出问题的人添加对代码的解释,以便更好地理解您的答案。尽量避免只回答代码!
          • 这不符合OP的要求,即查找重复项,而不仅仅是测试有一些
          猜你喜欢
          • 1970-01-01
          • 2013-03-29
          • 2013-02-21
          • 2013-11-17
          • 2018-11-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多