Dans Scala, comment supprimer les doublons d'une liste?


94

Supposons que j'ai

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

Existe-t-il une opération de liste qui renvoie "a", "b", "c"

Réponses:


175

Jetez un œil au ScalaDoc pour Seq ,

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

Mettre à jour . D'autres ont suggéré d'utiliser Setplutôt que List. C'est bien, mais sachez que par défaut, l' Setinterface ne conserve pas l'ordre des éléments. Vous pouvez utiliser un ensemble implémentation qui explicitement ne préserver l' ordre, comme collection.mutable.LinkedHashSet .


2
Que faire si vous avez une liste de fichiers et avez besoin de comparer sur quelque chose comme une partie du nom de fichier?
ozone

4
@ozone Question intéressante. Le moyen le plus simple est peut-être de créer une nouvelle carte de type Map[String, File], où les clés font partie du nom de fichier qui vous intéresse. Une fois la carte construite, vous pouvez appeler la valuesméthode pour obtenir une Iterablevaleur de - les clés seront toutes distinctes par construction.
Kipton Barros

@KiptonBarros et je pense que vous pouvez le faire en utilisant le groupBymembre de scala.collection.Iterable[A].
Louis-Jacob Lebel

18

scala.collection.immutable.Lista maintenant une .distinctméthode.

Ainsi, l'appel dirty.distinctest désormais possible sans conversion en un Setou Seq.


1
.distinctn'est pas défini pour scala.collection.Iterable[A]. Donc, dans ce cas, vous devez utiliser la mise dirtyà niveau vers a Seqou a de Settoute façon (c'est-à-dire en utilisant l'un ou l' autre .toList, .toSeqou les .toSetmembres) pour que cela fonctionne.
Louis-Jacob Lebel

15

Avant d'utiliser la solution de Kitpon, pensez à utiliser a Setplutôt que a List, cela garantit que chaque élément est unique.

Comme la plupart des opérations de liste ( foreach, map, filter, ...) sont les mêmes pour les jeux et les listes, la collecte pourrait changer très facile dans le code.


7

Utiliser Set en premier lieu est la bonne façon de le faire, bien sûr, mais:

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

Travaux. Ou tout toSetcomme il prend en charge leSeq Traversable interface.


1
J'ai édité votre réponse parce que des Setoutils Traversable, non Seq. La différence est que Seqgarantit un ordre aux éléments, alors que ce Traversablen'est pas le cas.
Kipton Barros

0

Pour les listes déjà triées

Si vous souhaitez que les éléments distincts d'une liste dont vous savez qu'ils sont déjà triés , comme j'en ai souvent eu besoin, ce qui suit fonctionne environ deux fois plus vite que .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

Résultats de performance sur une liste de 100 000 000 d'Ints aléatoires de 0 à 99:

distinct        : 0.6655373s
distinctOnSorted: 0.2848134s

Performances avec MutableList ou ListBuffer

Alors qu'il semblerait qu'une approche de programmation plus modifiable / non fonctionnelle puisse être plus rapide que le préfixe à une liste immuable, la pratique montre le contraire. L'implémentation immuable fonctionne toujours mieux. Je suppose que la raison est que scala concentre ses optimisations de compilateur sur des collections immuables et fait du bon travail. (J'encourage les autres à soumettre de meilleures implémentations.)

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

Implémentations:

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

Tester:

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

C'est assez efficace car il n'y a que 100 valeurs uniques, mais vous auriez des ennuis s'il y en avait beaucoup plus car vous utilisez une structure immuable. Pour aller encore plus vite, vous pouvez l'implémenter avec une structure mutable.
Nick le

@Nick Je pensais à l'origine que ce serait aussi le cas, mais voyez les modifications ci-dessus.
voxoid le

J'ai essayé moi-même ce qui précède car je ne comprends pas pourquoi immuable serait mieux pour cela, mais cela continue de l'être même si vous augmentez considérablement le nombre de valeurs distinctes. J'ai également essayé quelques structures mutables où le préfixe est plus efficace mais même sans inverser le résultat à la fin, il est plus lent.
Nick il y a

-3

inArr.distinct foreach println _


cela imprime la sortie souhaitée, OP n'a-t-il pas demandé de le retourner (sous forme de liste, probablement)?
RobP

-5

La manière algorithmique ...

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

1
Il a une liste, pas une chaîne. Cela ne répond pas à la question.
Tim Gautier
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.