【问题标题】:In Scala how do I remove duplicates from a list?在 Scala 中,如何从列表中删除重复项?
【发布时间】:2011-08-21 00:41:20
【问题描述】:

假设我有

val dirty = List("a", "b", "a", "c")

是否有返回“a”、“b”、“c”的列表操作

【问题讨论】:

    标签: scala


    【解决方案1】:

    查看 Seq 的 ScalaDoc,

    scala> dirty.distinct
    res0: List[java.lang.String] = List(a, b, c)
    

    更新。其他人建议使用Set 而不是List。这很好,但请注意,默认情况下,Set 接口不保留元素顺序。您可能希望使用明确确实保留顺序的 Set 实现,例如 collection.mutable.LinkedHashSet

    【讨论】:

    • 如果您有一个文件列表并且需要比较文件名的一部分,该怎么办?
    • @ozone 有趣的问题。也许最简单的方法是创建一个Map[String, File] 类型的新map,其中的键是感兴趣的文件名的一部分。构建映射后,您可以调用values 方法来获取值的Iterable——键都将通过构造不同。
    • @KiptonBarros 我认为您可以使用scala.collection.Iterable[A]groupBy 成员来做到这一点。
    【解决方案2】:

    scala.collection.immutable.List 现在有一个.distinct 方法。

    所以现在可以调用dirty.distinct,而无需转换为SetSeq

    【讨论】:

    • .distinct 没有为scala.collection.Iterable[A] 定义。所以在这种情况下,你必须使用升级dirtySeqSet 无论如何(即通过使用.toList.toSeq.toSet 成员)才能工作。
    【解决方案3】:

    在使用 Kitpon 的解决方案之前,请考虑使用 Set 而不是 List,它可以确保每个元素都是唯一的。

    由于大多数列表操作(foreachmapfilter、...)对于集合和列表都是相同的,因此在代码中更改集合非常容易。

    【讨论】:

      【解决方案4】:

      当然,首先使用 Set 是正确的方法,但是:

      scala> List("a", "b", "a", "c").toSet.toList
      res1: List[java.lang.String] = List(a, b, c)
      

      有效。或者只是 toSet,因为它支持 Seq Traversable 接口。

      【讨论】:

      • 我编辑了您的答案,因为Set 实现了Traversable,而不是Seq。区别在于Seq 保证元素的顺序,而Traversable 不保证。
      【解决方案5】:

      对于已经排序的列表

      如果你碰巧想要一个列表中你知道已经排序的不同项目,正如我经常需要的那样,下面的执行速度大约是.distinct 的两倍:

        def distinctOnSorted[V](seq: List[V]): List[V] =
          seq.foldLeft(List[V]())((result, v) =>
            if (result.isEmpty || v != result.head) v :: result else result)
          .reverse
      

      在 0-99 的 100,000,000 个随机 Int 列表上的性能结果:

      distinct        : 0.6655373s
      distinctOnSorted: 0.2848134s
      

      MutableList 或 ListBuffer 的性能

      虽然看起来更可变/非函数式编程方法可能比预先添加到不可变列表更快,但实践表明并非如此。不可变的实现始终表现得更好。我的猜测是 scala 将其编译器优化集中在不可变集合上,并且做得很好。 (欢迎其他人提交更好的实现。)

      List size 1e7, random 0 to 1e6
      ------------------------------
      distinct            : 4562.2277ms
      distinctOnSorted    : 201.9462ms
      distinctOnSortedMut1: 4399.7055ms
      distinctOnSortedMut2: 246.099ms
      distinctOnSortedMut3: 344.0758ms
      distinctOnSortedMut4: 247.0685ms
      
      List size 1e7, random 0 to 100
      ------------------------------
      distinct            : 88.9158ms
      distinctOnSorted    : 41.0373ms
      distinctOnSortedMut1: 3283.8945ms
      distinctOnSortedMut2: 54.4496ms
      distinctOnSortedMut3: 58.6073ms
      distinctOnSortedMut4: 51.4153ms
      

      实现:

      object ListUtil {
        def distinctOnSorted[V](seq: List[V]): List[V] =
          seq.foldLeft(List[V]())((result, v) =>
            if (result.isEmpty || v != result.head) v :: result else result)
          .reverse
      
        def distinctOnSortedMut1[V](seq: List[V]): Seq[V] = {
          if (seq.isEmpty) Nil
          else {
            val result = mutable.MutableList[V](seq.head)
            seq.zip(seq.tail).foreach { case (prev, next) =>
              if (prev != next) result += next
            }
            result //.toList
          }
        }
      
        def distinctOnSortedMut2[V](seq: List[V]): Seq[V] = {
          val result = mutable.MutableList[V]()
          if (seq.isEmpty) return Nil
          result += seq.head
          var prev = seq.head
          for (v <- seq.tail) {
            if (v != prev) result += v
            prev = v
          }
          result //.toList
        }
      
        def distinctOnSortedMut3[V](seq: List[V]): List[V] = {
          val result = mutable.MutableList[V]()
          if (seq.isEmpty) return Nil
          result += seq.head
          var prev = seq.head
          for (v <- seq.tail) {
            if (v != prev) v +=: result
            prev = v
          }
          result.reverse.toList
        }
      
        def distinctOnSortedMut4[V](seq: List[V]): Seq[V] = {
          val result = ListBuffer[V]()
          if (seq.isEmpty) return Nil
          result += seq.head
          var prev = seq.head
          for (v <- seq.tail) {
            if (v != prev) result += v
            prev = v
          }
          result //.toList
        }
      }
      

      测试:

      import scala.util.Random
      
      class ListUtilTest extends UnitSpec {
        "distinctOnSorted" should "return only the distinct elements in a sorted list" in {
          val bigList = List.fill(1e7.toInt)(Random.nextInt(100)).sorted
      
          val t1 = System.nanoTime()
          val expected = bigList.distinct
          val t2 = System.nanoTime()
          val actual = ListUtil.distinctOnSorted[Int](bigList)
          val t3 = System.nanoTime()
          val actual2 = ListUtil.distinctOnSortedMut1(bigList)
          val t4 = System.nanoTime()
          val actual3 = ListUtil.distinctOnSortedMut2(bigList)
          val t5 = System.nanoTime()
          val actual4 = ListUtil.distinctOnSortedMut3(bigList)
          val t6 = System.nanoTime()
          val actual5 = ListUtil.distinctOnSortedMut4(bigList)
          val t7 = System.nanoTime()
      
          actual should be (expected)
          actual2 should be (expected)
          actual3 should be (expected)
          actual4 should be (expected)
          actual5 should be (expected)
      
          val distinctDur = t2 - t1
          val ourDur = t3 - t2
      
          ourDur should be < (distinctDur)
      
          print(s"distinct            : ${distinctDur / 1e6}ms\n")
          print(s"distinctOnSorted    : ${ourDur / 1e6}ms\n")
          print(s"distinctOnSortedMut1: ${(t4 - t3) / 1e6}ms\n")
          print(s"distinctOnSortedMut2: ${(t5 - t4) / 1e6}ms\n")
          print(s"distinctOnSortedMut3: ${(t6 - t5) / 1e6}ms\n")
          print(s"distinctOnSortedMut4: ${(t7 - t6) / 1e6}ms\n")
        }
      }
      

      【讨论】:

      • 这非常有效,因为只有 100 个唯一值,但如果使用不可变结构,如果有更多,你会遇到麻烦。为了更快,您可以使用可变结构来实现它。
      • @Nick 我原本以为也是这样,但是请参阅上面的编辑。
      • 我自己尝试了上述方法,因为我不明白为什么 immutable 会更好,但即使您大大增加了不同值的数量,它仍然如此。我还尝试了一些可变结构,其中 prepend 更有效,但即使最后没有反转结果,它也会变慢。
      【解决方案6】:

      您还可以使用递归和模式匹配:

      def removeDuplicates[T](xs: List[T]): List[T] = xs match {
        case Nil => xs
        case head :: tail => head :: removeDuplicates(for (x <- tail if x != head) yield x)
      }
      
      

      【讨论】:

      • removeDuplicates(tail.filter(_ != head))
      【解决方案7】:

      inArr.distinct foreach println _

      【讨论】:

      • 这会打印所需的输出,不是 OP 要求返回它(大概是一个列表)?
      【解决方案8】:

      算法方式...

      def dedupe(str: String): String = {
        val words = { str split " " }.toList
      
        val unique = words.foldLeft[List[String]] (Nil) {
          (l, s) => {
            val test = l find { _.toLowerCase == s.toLowerCase } 
            if (test == None) s :: l else l
          }
        }.reverse
      
        unique mkString " "
      }
      

      【讨论】:

      • 他有一个列表,而不是一个字符串。这没有回答问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-20
      • 1970-01-01
      • 2019-12-07
      • 2011-04-24
      • 2023-03-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多