Jak w Scali usunąć duplikaty z listy?

95

Przypuśćmy, że tak

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

Czy istnieje operacja na liście, która zwraca „a”, „b”, „c”

deltanovember
źródło

Odpowiedzi:

177

Zajrzyj do ScalaDoc dla Seq ,

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

Aktualizuj . Inni sugerowali użycie Setzamiast List. To dobrze, ale pamiętaj, że domyślnie Setinterfejs nie zachowuje kolejności elementów. Możesz użyć zestawu realizację jawnie nie zachowania porządku, takie jak collection.mutable.LinkedHashSet .

Kipton Barros
źródło
2
A co, jeśli masz listę plików i chcesz porównać coś w rodzaju części nazwy pliku?
ozon
4
@ozone Ciekawe pytanie. Być może najłatwiejszym sposobem jest utworzenie nowej mapy typu Map[String, File], w której klucze są częścią nazwy pliku, która Cię interesuje. Po skonstruowaniu mapy możesz wywołać valuesmetodę w celu uzyskania Iterablewartości - wszystkie klucze będą się różnić konstrukcją.
Kipton Barros
@KiptonBarros i myślę, że możesz to zrobić, używając groupByczłonka scala.collection.Iterable[A].
Louis-Jacob Lebel
18

scala.collection.immutable.Listteraz ma .distinctmetodę.

Więc dzwonienie dirty.distinctjest teraz możliwe bez konwertowania na Setlub Seq.

crockpotveggies
źródło
1
.distinctnie jest zdefiniowany dla scala.collection.Iterable[A]. Więc w takim przypadku, aby to zadziałało , musisz użyć uaktualnienia dirtydo a Seqlub a Set(tj. Używając albo .toList, .toSeqalbo .toSetczłonków).
Louis-Jacob Lebel
15

Przed użyciem rozwiązania Kitpon pomyśl o użyciu a, Seta nie a List, zapewnia to, że każdy element jest wyjątkowy.

Jak większość operacji list ( foreach, map, filter, ...) są takie same dla zbiorów i list, zmieniając kolekcji może być bardzo łatwy w kodzie.

paradygmatyczny
źródło
7

Korzystanie z Set w pierwszej kolejności jest oczywiście właściwą drogą, ale:

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

Pracuje. Lub po prostu toSetobsługujeSeq Traversable berło.

zentrope
źródło
1
Zredagowałem twoją odpowiedź, ponieważ Setnarzędzia Traversablenie Seq. Różnica polega na tym, że Seqgwarantuje porządek dla elementów, a Traversablenie.
Kipton Barros
0

Listy już posortowane

Jeśli chcesz, aby poszczególne elementy listy, o których wiesz, że są już posortowane , jak często potrzebowałem, następujące czynności działają około dwa razy szybciej niż .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

Wyniki wydajności na liście 100 000 000 losowych Intów od 0 do 99:

distinct        : 0.6655373s
distinctOnSorted: 0.2848134s

Wydajność z MutableList lub ListBuffer

Chociaż mogłoby się wydawać, że bardziej zmienne / niefunkcjonalne podejście do programowania może być szybsze niż poprzedzanie niezmiennej listy, praktyka pokazuje inaczej. Niezmienna implementacja konsekwentnie działa lepiej. Domyślam się, że powodem jest to, że scala koncentruje swoje optymalizacje kompilatora na niezmiennych kolekcjach i dobrze sobie z tym radzi. (Zapraszam innych do przesyłania lepszych wdrożeń.)

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

Wdrożenia:

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
  }
}

Test:

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")
  }
}
voxoid
źródło
Jest to dość wydajne, ponieważ istnieje tylko 100 unikalnych wartości, ale wpadłbyś w kłopoty, gdyby było ich znacznie więcej, ponieważ używasz niezmiennej struktury. Aby działać jeszcze szybciej, możesz zaimplementować to za pomocą zmiennej struktury.
Nick
@Nick Początkowo myślałem, że tak też będzie, jednak zobacz powyższe zmiany.
voxoid
Wypróbowałem powyższe osobiście, ponieważ nie rozumiem, dlaczego niezmienne byłoby lepsze w tym przypadku, ale nadal tak jest, nawet jeśli znacznie zwiększysz liczbę różnych wartości. Wypróbowałem także kilka zmiennych struktur, w których poprzedzanie jest bardziej wydajne, ale nawet bez odwracania wyniku na końcu jest wolniejsze.
Nick
0

Możesz także użyć rekursji i dopasowania wzorców:

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)
}

codeepic
źródło
1
removeDuplicates(tail.filter(_ != head))
jwvh
-3

inArr.distinct foreach println _

Sumit Pal
źródło
to drukuje żądane wyjście, czy OP nie prosił o jego zwrot (prawdopodobnie jako Listę)?
RobP
-5

Algorytmiczny sposób ...

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 " "
}
Farquad
źródło
1
Ma listę, a nie ciąg. To nie odpowiada na pytanie.
Tim Gautier