【问题标题】:Remove duplicates in List specifying equality function删除列表中的重复项指定相等函数
【发布时间】:2019-02-17 06:04:17
【问题描述】:

我有一个List[A],在给定相等函数(a:A, b:A) => Boolean 的情况下,删除重复项的惯用方法是什么?我一般不能覆盖equalsfor A

我现在能想到的方法是创建一个包装 class AExt 并覆盖 equals,然后

list.map(new AExt(_)).distinct

但我想知道是否有更清洁的方法。

【问题讨论】:

标签: scala


【解决方案1】:

有一种简单(更简单)的方法可以做到这一点:

list.groupBy(_.key).mapValues(_.head)

如果您愿意,可以通过将_.head 替换为如下功能块来立即使用生成的地图:

sameElements => { val observedItem = sameElements.head
                  new A (var1 = observedItem.firstAttr,
                         var2 = "SomethingElse") }

为每个不同的元素返回一个新的A

只有一个小问题。上面的代码 (list.groupBy(_.key).mapValues(_.head)) 没有很好地解释删除重复项的意图。出于这个原因,拥有像 distinctIn[A](attr: A => B)distinctBy[A](eq: (A, A) -> Boolean) 这样的函数会很棒。

【讨论】:

  • 这样,结果将是唯一键上的映射。要获得不同的值,应该 .map(_._2.head)
  • 这在大多数情况下都有效;在惰性评估的情况下应该小心,因为groupBy 往往暗示着急切的评估。
【解决方案2】:

使用 misingFaktor 的回答中的 FoocustomEquals

  case class Foo(a: Int, b: Int)
  val (a, b, c, d) = (Foo(3, 4), Foo(3, 1), Foo(2, 5), Foo(2, 5))
  def customEquals(x: Foo, y: Foo) = x.a == y.a

  (Seq(a, b, c, d).foldLeft(Seq[Foo]()) {
    (unique, curr) => {
      if (!unique.exists(customEquals(curr, _)))
        curr +: unique
      else
        unique
    }
  }).reverse

如果结果排序很重要,但要删除的重复项不重要,那么 foldRight 更可取

  Seq(a, b, c, d).foldRight(Seq[Foo]()) {
    (curr, unique) => {
      if (!unique.exists(customEquals(curr, _)))
        curr +: unique
      else
        unique
    }
  }

【讨论】:

    【解决方案3】:

    我必须说我想我会通过一个中间集合,它是一个 Set,如果你预计你的 Lists 可能会很长,因为 测试存在(通过 @987654323 @ 或 find) 在 Seq 上当然是 O(n)

    而不是写一个自定义的equals;决定元素相等的属性。所以而不是:

    def myCustomEqual(a1: A, a2: A) = a1.foo == a2.foo && a1.bar == a2.bar
    

    制作一个钥匙。像这样:

    type Key = (Foo, Bar)
    def key(a: A) = (a.foo, a.bar)
    

    然后您可以将密钥添加到Set 以查看您以前是否遇到过它们。

    var keys = Set.empty[Key]
    ((List.empty[A] /: as) { (l, a) => 
      val k = key(a)
      if (keys(k)) l else { keys += k; a +: l  }
    }).reverse
    

    当然,在非常短的列表的情况下,此解决方案具有更差的空间复杂性和可能更差的性能(因为您正在创建额外的对象 - 键)。如果您不喜欢折叠中的 var,您可能想看看如何使用 scalaz 7

    中的 StateTraverse 实现此目的

    【讨论】:

    • 最终实现了一些更简单的方法,但根据您的建议,通过识别键来实现。
    【解决方案4】:
    scala> case class Foo(a: Int, b: Int)
    defined class Foo
    
    scala> val (a, b, c, d) = (Foo(3, 4), Foo(3, 1), Foo(2, 5), Foo(2, 5))
    a: Foo = Foo(3,4)
    b: Foo = Foo(3,1)
    c: Foo = Foo(2,5)
    d: Foo = Foo(2,5)
    
    scala> def customEquals(x: Foo, y: Foo) = x.a == y.a
    customEquals: (x: Foo, y: Foo)Boolean
    
    scala> Seq(a, b, c, d) filter {
         |   var seq = Seq.empty[Foo]
         |   x => {
         |    if(seq.exists(customEquals(x, _))) {
         |      false 
         |    } else { 
         |      seq :+= x
         |      true 
         |    }
         | }
    res13: Seq[Foo] = List(Foo(3,4), Foo(2,5))
    

    【讨论】:

    • 这个答案的灵感来自distinct 的实现:lampsvn.epfl.ch/trac/scala/browser/scala/tags/R_2_9_1_final/src/… 所以查看源代码有时会有所帮助。
    • 不,这个答案不受该实施的启发。我写了一些我想到的东西。这实际上甚至不是一个好的解决方案。 @drstevens 的回答更好。
    • 我的错,我寻找它是如何在 scala 中实现的,因为我认为它是一个很好的灵感来源,然后发现它与您的实现形式相同。不过我同意你的观点,foldleft 更优雅。
    【解决方案5】:

    Scala 2.13 开始,我们可以使用新的distinctBy 方法,该方法在应用转换函数f 后返回忽略由== 确定的重复项的序列元素:

    def distinctBy[B](f: (A) => B): List[A]

    例如:

    // case class A(a: Int, b: String, c: Double)
    // val list = List(A(1, "hello", 3.14), A(2, "world", 3.14), A(1, "hello", 12.3))
    list.distinctBy(x => (x.a, x.b)) // List(A(1, "hello", 3.14), A(2, "world", 3.14))
    list.distinctBy(_.c)             // List(A(1, "hello", 3.14), A(1, "hello", 12.3))
    

    【讨论】:

      【解决方案6】:
      case class Foo (a: Int, b: Int)
      
      val x = List(Foo(3,4), Foo(3,1), Foo(2,5), Foo(2,5))
      def customEquals(x : Foo, y: Foo) = (x.a == y.a && x.b == y.b)
      
      x.foldLeft(Nil : List[Foo]) {(list, item) => 
         val exists = list.find(x => customEquals(item, x))
         if (exists.isEmpty) item :: list
         else list
       }.reverse
      

      res0: List[Foo] = List(Foo(3,4), Foo(3,1), Foo(2,5))

      【讨论】:

        猜你喜欢
        • 2019-01-05
        • 1970-01-01
        • 1970-01-01
        • 2018-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多