元组从列表中重复删除

时间:2014-07-20 14:05:17

标签: scala duplicate-removal performance

考虑以下元组列表:

val input= List((A,B), 
                (C,B), 
                (B,A)) 

并假设元素(A,B)(B,A)是相同的,因此是重复的,从上面的列表中消除重复的有效方式(最好是在Scala中)是什么。这意味着所需的输出是另一个列表:

val deduplicated= List((A,B), 
                       (C,B)) 

提前致谢!

p.s:这不是家庭作业;)

更新:

感谢大家! " set" -solution似乎是最好的。

6 个答案:

答案 0 :(得分:2)

您可以尝试使用set,但是您需要声明自己的元组类才能使其正常工作。

case class MyTuple[A](t: (A, A)) {
  override def hashCode = t._1.hashCode + t._2.hashCode
  override def equals(other: Any) = other match {
    case MyTuple((a, b)) => a.equals(t._1) && b.equals(t._2) || a.equals(t._2) && b.equals(t._1)
    case _ => false
  }
}

val input= List(("A","B"), 
                ("C","B"), 
                ("B","A"))

val output = input.map(MyTuple.apply).toSet.toList.map((mt: MyTuple[String]) => mt.t)
println(output)

修改 特拉维斯的回答让我意识到有一个更好的方法来做到这一点。这是通过编写一个与sortBy类似的distinctBy方法。

implicit class extList[T](list: List[T]) {
  def distinctBy[U](f: T => U): List[T] = {
    var set = Set.empty[U]
    var result = List.empty[T]
    for(t <- list) {
      val u = f(t)
      if(!set(u)) {
        result ::= t
        set += u
      }
    }
    result.reverse
  }
}

println(input.distinctBy { case (a, b) => Set((a,b), (b,a)) })

答案 1 :(得分:2)

我们可以使用Set来跟踪我们已经看过的元素,同时使用filter来消除重复项:

def removeDuplicates[T](l: List[(T, T)]) = {
  val set = scala.collection.mutable.Set[(T, T)]()
  l.filter { case t@(x, y) =>
    if (set(t)) false else {
      set += t
      set += ((y, x))
      true
    }
  }
}

当我们找到一个我们以前没见过的元组时,我们把它和它的元素交换到集合中。

答案 2 :(得分:1)

与SpiderPig的回答相同,这里是一个不使用集合的解决方案(因为通过集合不会保留原始列表的顺序,这可能是一个烦恼)

case class MyPimpedTuple(t: Tuple2[String, String]) {
  override def hashCode = t._1.hashCode + t._2.hashCode
  override def equals(other: Any) = other match {
      case MyPimpedTuple((a, b)) => a.equals(t._1) && b.equals(t._2) || a.equals(t._2) && b.equals(t._1)
      case _ => false
  }
}

val input = List[MyPimpedTuple](("A","B"), ("C","B"),("B","A"))

input.map(MyPimpedTuple(_)).distinct.map(_.t)

实施例

val input = List(("A","B"), ("C","B"),("B","A"))
//> input: List[(String, String)] = List((A,B), (C,B), (B,A))

val distinctTuples = input.map(MyPimpedTuple(_)).distinct.map(_.t)
//> distinctTuples: List[(String, String)] = List((A,B), (C,B))

答案 3 :(得分:1)

为了完整起见,可以非常简单地以纯粹功能的方式进行折叠(手动定义平等让我感到紧张,而且我不确定可变性在这里买得多):

def distinctPairs[A](xs: List[(A, A)]) = xs.foldLeft(List.empty[(A, A)]) {
  case (acc, (a, b)) if acc.contains((a, b)) || acc.contains((b, a)) => acc
  case (acc, p) => acc :+ p
}

这不是非常有效,因为它为每个项目搜索列表两次(并附加到列表中),但这并不难解决:

def distinctPairs[A](xs: List[(A, A)]) = xs.foldLeft(
  (List.empty[(A, A)], Set.empty[(A, A)])
) {
  case (current @ (_, seen), p) if seen(p) => current
  case ((acc, seen), p @ (a, b)) => (p :: acc, seen ++ Set((a, b), (b, a)))
}._1.reverse

这两个实现都维持秩序。

答案 4 :(得分:1)

还要考虑依赖Map上的唯一键,其中键是双重元素的集合,

def uniq[A](a: List[(A,A)]) = a.map( t => Set(t._1,t._2) -> t ).toMap.values

效率最高,但不够简单;适用于小型馆藏。

答案 5 :(得分:0)

是的我还建议将一个集合作为目标数据结构,因为集合查找可能比两个for循环更有效。 (对不起,我是一个clojure家伙,当然这不是clojure中最短的版本......)

(def data `(("A" "B") ("B" "C") ("B" "A")))
;;(def data `(("A" "B") ("B" "C") ("B" "A") ("C" "D") ("C" "B") ("D" "F")))

(defn eliminator [source]
 (println "Crunching: " source)
  (loop [s source t '#{}]
    (if (empty? s) (reverse t) ;; end
      (if (contains? t (list (last (first s)) (first (first s)))) ;reverse is in set !
        (recur (rest s) t) ; next iteration
        (recur (rest s) (conj t (first s))))))) ;; add it