【问题标题】:Easiest way to decide if List contains duplicates?确定 List 是否包含重复项的最简单方法?
【发布时间】:2018-06-07 19:15:27
【问题描述】:

一种方法是这样的

list.distinct.size != list.size

有没有更好的方法?如果有一个containsDuplicates 方法就好了

【问题讨论】:

  • 用例是什么?请记住,distinct 的成本很高,尤其是在经常调用时。搜索重复项不可避免地会导致排序。话虽这么说,也许您实际上需要SetMap(如果您想跟踪重复项)?当然你也可以使用隐式转换将containsDuplicates添加到List[T]
  • @TomaszNurkiewicz:我需要检查列表是否包含重复项。此检查仅在创建列表时进行一次。此后该列表永远不会被修改。该列表很小(20-50 个元素之间)。我也可以使用Set。我以前没有考虑过。

标签: scala


【解决方案1】:

假设“更好”意味着“更快”,请参阅以this question 为基准的替代方法,这似乎显示了一些更快的方法(尽管请注意 distinct 使用 HashSet 并且已经是 O(n))。当然,YMMV 取决于特定的测试用例、scala 版本等。可能对“distinct.size”方法的任何重大改进都来自于发现重复项后的早期退出,但加速的程度是多少实际获得的结果很大程度上取决于您的用例中实际重复的常见程度。

如果您的意思是“更好”,因为您想写 list.containsDuplicates 而不是 containsDuplicates(list),请使用隐式:

implicit def enhanceWithContainsDuplicates[T](s:List[T]) = new {
  def containsDuplicates = (s.distinct.size != s.size)
}

assert(List(1,2,2,3).containsDuplicates)
assert(!List("a","b","c").containsDuplicates)

【讨论】:

    【解决方案2】:

    你也可以写:

    list.toSet.size != list.size
    

    但结果将是相同的,因为distinct 已经是implemented with a Set。在这两种情况下,时间复杂度都应该是O(n):你必须遍历列表并且Set插入是O(1)

    【讨论】:

      【解决方案3】:

      我认为一旦找到重复项就会停止,并且可能比 distinct.size 更有效 - 因为我假设 distinct 也保留一个集合:

      @annotation.tailrec
      def containsDups[A](list: List[A], seen: Set[A] = Set[A]()): Boolean = 
        list match {
          case x :: xs => if (seen.contains(x)) true else containsDups(xs, seen + x)
          case _ => false
      }
      
      containsDups(List(1,1,2,3))
      // Boolean = true
      
      containsDups(List(1,2,3))
      // Boolean = false
      

      我知道你问的很简单,我现在不知道这个版本是,但查找重复项也是查找是否存在以前见过的元素:

      def containsDups[A](list: List[A]): Boolean =  {
        list.iterator.scanLeft(Set[A]())((set, a) => set + a) // incremental sets
          .zip(list.iterator)
          .exists{ case (set, a) => set contains a }
      }
      

      【讨论】:

      • 有趣的答案。但这不会需要更多内存吗?
      • @Jus12,我的第一个建议可能与 distinct 相同,只是库使用可变集,我使用不可变集。第二个应该只有一点内存,因为可能创建了一些迭代器(但这应该是恒定数量的迭代器)。
      【解决方案4】:
      @annotation.tailrec 
      def containsDuplicates [T] (s: Seq[T]) : Boolean = 
        if (s.size < 2) false else 
          s.tail.contains (s.head) || containsDuplicates (s.tail)
      

      这个我没测过,觉得和huynhjl的方案差不多,但是理解起来更简单一点。

      如果发现重复,它会提前返回,所以我查看了 Seq.contains 的来源,是否提前返回 - 确实如此。

      在SeqLike中,'contains (e)'被定义为'exists(_ == e)',而exists在TraversableLike中被定义:

      def exists (p: A => Boolean): Boolean = {
        var result = false
        breakable {
          for (x <- this)
            if (p (x)) { result = true; break }
        }
        result
      }
      

      我很好奇如何在多核上使用并行集合来加快速度,但我想这是提前返回的一般问题,而另一个线程将继续运行,因为它不知道解决方案是已经找到了。

      【讨论】:

        【解决方案5】:

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

        注意:这个答案是straight copy of the answer on a related question

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

        函数如下:

        def filterDupes[A](items: List[A]): (List[A], List[(A, Int)]) = {
          def recursive(remaining: List[A], index: Int, accumulator: (List[A], List[(A, Int)])): (List[A], List[(A, Int)]) =
            if (remaining.isEmpty)
              accumulator
            else
              recursive(
                  remaining.tail
                , index + 1
                , if (accumulator._1.contains(remaining.head))
                    (accumulator._1, (remaining.head, index) :: accumulator._2)
                  else
                    (remaining.head :: accumulator._1, accumulator._2)
              )
          val (distinct, dupes) = recursive(items, 0, (Nil, Nil))
          (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) =
          filterDupes(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 =
          filterDupes(withDupes)._2.map(_._1).distinct
        

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

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

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

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

        这是我的开源 Scala 库 ScalaOlio 中的 filterDupes 函数的直接副本。它位于org.scalaolio.collection.immutable.List_._

        【讨论】:

          【解决方案6】:

          如果您尝试在测试中检查重复项,那么 ScalaTest 会很有帮助。

          import org.scalatest.Inspectors._
          import org.scalatest.Matchers._
          forEvery(list.distinct) { item =>
            withClue(s"value $item, the number of occurences") {
              list.count(_ == item) shouldBe 1
            }
          }
          
          // example:
          scala> val list = List(1,2,3,4,3,2)
          list: List[Int] = List(1, 2, 3, 4, 3, 2)
          
          scala> forEvery(list) { item => withClue(s"value $item, the number of occurences") { list.count(_ == item) shouldBe 1 } }
          org.scalatest.exceptions.TestFailedException: forEvery failed, because:
            at index 1, value 2, the number of occurences 2 was not equal to 1 (<console>:19),
            at index 2, value 3, the number of occurences 2 was not equal to 1 (<console>:19)
          in List(1, 2, 3, 4)
          

          【讨论】:

            猜你喜欢
            • 2014-10-14
            • 2011-04-12
            • 2020-08-09
            • 2020-10-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-11-25
            • 2020-03-06
            相关资源
            最近更新 更多