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?
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.
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.
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)
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 List
s, 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_._
.
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)
}