web-dev-qa-db-fra.com

Comment trouver des doublons dans une liste?

J'ai une liste de non triés nombres entiers et je veux trouver les éléments qui ont des doublons.

val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)

Je peux trouver les éléments distincts de l'ensemble avec dup.distinct, alors j'ai écrit ma réponse comme suit.

val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)
val distinct = dup.distinct
val elementsWithCounts = distinct.map( (a:Int) => (a, dup.count( (b:Int) => a == b )) )
val duplicatesRemoved = elementsWithCounts.filter( (pair: Pair[Int,Int]) => { pair._2 <= 1 } )
val withDuplicates = elementsWithCounts.filter( (pair: Pair[Int,Int]) => { pair._2 > 1 } )

Y a-t-il un moyen plus simple de résoudre ce problème?

25
Phil

Essaye ça:

val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)
dup.groupBy(identity).collect { case (x, List(_,_,_*)) => x }

La groupBy associe chaque entier distinct à une liste de ses occurrences. La collect est fondamentalement map où les éléments non correspondants sont ignorés. Le modèle de correspondance suivant case correspond aux entiers x associés à une liste qui correspond au modèle List(_,_,_*), une liste comportant au moins deux éléments, chacun représenté par un trait de soulignement, car nous n'avons pas réellement besoin de stocker ces valeurs (et ces deux éléments). peut être suivi de zéro ou plusieurs éléments: _*).

Vous pouvez aussi faire:

dup.groupBy(identity).collect { case (x,ys) if ys.lengthCompare(1) > 0 => x }

C'est beaucoup plus rapide que l'approche que vous avez fournie puisqu'il n'est pas nécessaire de transmettre les données à plusieurs reprises.

47
dhg

Un peu tard pour la fête, mais voici une autre approche:

dup.diff(dup.distinct).distinct

diff vous donne tous les éléments supplémentaires au-dessus de ceux de l'argument (dup.distinct), qui sont les doublons.

25
Luigi Plinge

Une autre approche consiste à utiliser foldLeft et à le faire avec difficulté.

Nous commençons avec deux ensembles vides. L'une concerne les éléments que nous avons vus au moins une fois. L'autre concerne les éléments que nous avons vus au moins deux fois (ou doublons). 

Nous parcourons la liste. Lorsque l'élément actuel a déjà été vu (seen(cur)), il s'agit d'un doublon et est donc ajouté à duplicates. Sinon, nous l'ajoutons à seen. Le résultat est maintenant le deuxième ensemble contenant les doublons.

Nous pouvons aussi écrire cela comme une méthode générique.

def dups[T](list: List[T]) = list.foldLeft((Set.empty[T], Set.empty[T])){ case ((seen, duplicates), cur) => 
  if(seen(cur)) (seen, duplicates + cur) else (seen + cur, duplicates)      
}._2

val dup = List(1,1,1,2,3,4,5,5,6,100,101,101,102)

dups(dup) //Set(1,5,101)
2
Kigyo

Résumé: J'ai écrit une fonction très efficace qui renvoie List.distinct et un List composé de chaque élément apparaissant plusieurs fois et de l'index auquel le doublon est apparu.

Détails: Si vous avez besoin d’un peu plus d’informations sur les doublons eux-mêmes, j’ai écrit une fonction plus générale qui itère sur un List (la commande étant importante) une seule fois et renvoie un Tuple2 consistant en de List original déduplé (tous les doublons après le premier sont supprimés; c’est-à-dire qu’ils invoquent distinct) et un second List affichant chaque duplicata et un index Int auquel il s’est produit dans le List original.

J'ai implémenté la fonction deux fois en fonction des caractéristiques de performance générales des collections Scala ; filterDupesL (où le L est pour linéaire) et filterDupesEc (où le ec est effectivement constant).

Voici la fonction "linéaire":

def filterDupesL[A](items: List[A]): (List[A], List[(A, Int)]) = {
  def recursive(
      remaining: List[A]
    , index: Int =
        0
    , accumulator: (List[A], List[(A, Int)]) =
        (Nil, Nil)): (List[A], List[(A, Int)]
  ) =
    if (remaining.isEmpty)
      accumulator
    else
      recursive(
          remaining.tail
        , index + 1
        , if (accumulator._1.contains(remaining.head)) //contains is linear
          (accumulator._1, (remaining.head, index) :: accumulator._2)
        else
          (remaining.head :: accumulator._1, accumulator._2)
      )
  val (distinct, dupes) = recursive(items)
  (distinct.reverse, dupes.reverse)
}

Vous trouverez ci-dessous un exemple qui pourrait le rendre plus intuitif. Étant donné cette liste de valeurs de chaîne:

val withDupes =
  List("a.b", "a.c", "b.a", "b.b", "a.c", "c.a", "a.c", "d.b", "a.b")

... puis effectuez les opérations suivantes:

val (deduped, dupeAndIndexs) =
  filterDupesL(withDupes)

... les résultats sont:

deduped: List[String] = List(a.b, a.c, b.a, b.b, c.a, d.b)
dupeAndIndexs: List[(String, Int)] = List((a.c,4), (a.c,6), (a.b,8))

Et si vous ne voulez que les doublons, vous devez simplement map sur dupeAndIndexes et invoquer distinct:

val dupesOnly =
  dupeAndIndexs.map(_._1).distinct

... ou tout en un seul appel:

val dupesOnly =
  filterDupesL(withDupes)._2.map(_._1).distinct

... ou si vous préférez Set, ignorez distinct et appelez toSet...

val dupesOnly2 =
  dupeAndIndexs.map(_._1).toSet

... ou tout en un seul appel:

val dupesOnly2 =
  filterDupesL(withDupes)._2.map(_._1).toSet

Pour les très grands Lists, envisagez d'utiliser cette version plus efficace (qui utilise un Set supplémentaire pour modifier le contrôle contains dans un temps constant constant):

Voici la fonction "Effectivement constante":

def filterDupesEc[A](items: List[A]): (List[A], List[(A, Int)]) = {
  def recursive(
      remaining: List[A]
    , index: Int =
        0
    , seenAs: Set[A] =
        Set()
    , accumulator: (List[A], List[(A, Int)]) =
        (Nil, Nil)): (List[A], List[(A, Int)]
  ) =
    if (remaining.isEmpty)
      accumulator
    else {
      val (isInSeenAs, seenAsNext) = {
        val isInSeenA =
          seenAs.contains(remaining.head) //contains is effectively constant
        (
            isInSeenA
          , if (!isInSeenA)
              seenAs + remaining.head
            else
              seenAs
        )
      }
      recursive(
          remaining.tail
        , index + 1
        , seenAsNext
        , if (isInSeenAs)
          (accumulator._1, (remaining.head, index) :: accumulator._2)
        else
          (remaining.head :: accumulator._1, accumulator._2)
      )
    }
  val (distinct, dupes) = recursive(items)
  (distinct.reverse, dupes.reverse)
}

Les deux fonctions ci-dessus sont des adaptations de la fonction filterDupes dans ma bibliothèque Scala open source, ScalaOlio . Il est situé à org.scalaolio.collection.immutable.List_._.

2
chaotic3quilibrium

Mon favori est 

def hasDuplicates(in: List[Int]): Boolean = {
  val sorted = in.sortWith((i, j) => i < j)
  val zipped = sorted.tail.Zip(sorted)
  zipped.exists(p => p._1 == p._2)
}
0
user8221989