Est-il possible d'utiliser un seul appel à collect
pour créer 2 nouvelles listes? Sinon, comment puis-je faire cela en utilisant partition
?
collect
(défini sur TraversableLike et disponible dans toutes les sous-classes) fonctionne avec une collection et un PartialFunction
. Il se trouve également qu'un groupe de clauses de casse définies entre accolades est une fonction partielle (voir la section 8.5 des Spécification du langage Scala [avertissement - PDF] )
Comme dans la gestion des exceptions:
try {
... do something risky ...
} catch {
//The contents of this catch block are a partial function
case e: IOException => ...
case e: OtherException => ...
}
C'est un moyen pratique de définir une fonction qui n'acceptera que certaines valeurs d'un type donné.
Pensez à l'utiliser sur une liste de valeurs mixtes:
val mixedList = List("a", 1, 2, "b", 19, 42.0) //this is a List[Any]
val results = mixedList collect {
case s: String => "String:" + s
case i: Int => "Int:" + i.toString
}
L'argument de la méthode to collect
est un PartialFunction[Any,String]
. PartialFunction
car il n'est pas défini pour toutes les entrées possibles de type Any
(c'est le type du List
) et String
car c'est ce que toutes les clauses renvoient .
Si vous essayez d'utiliser map
au lieu de collect
, la valeur double à la fin de mixedList
provoquera un MatchError
. L'utilisation de collect
supprime simplement cela, ainsi que toute autre valeur pour laquelle la fonction partielle n'est pas définie.
Une utilisation possible serait d'appliquer une logique différente aux éléments de la liste:
var strings = List.empty[String]
var ints = List.empty[Int]
mixedList collect {
case s: String => strings :+= s
case i: Int => ints :+= i
}
Bien que ce ne soit qu'un exemple, l'utilisation de variables mutables comme celle-ci est considérée par beaucoup comme un crime de guerre - Alors, ne le faites pas!
Une meilleure meilleure solution consiste à utiliser collecter deux fois:
val strings = mixedList collect { case s: String => s }
val ints = mixedList collect { case i: Int => i }
Ou si vous savez avec certitude que la liste ne contient que deux types de valeurs, vous pouvez utiliser partition
, qui divise les collections en valeurs selon qu'elles correspondent ou non à un prédicat:
//if the list only contains Strings and Ints:
val (strings, ints) = mixedList partition { case s: String => true; case _ => false }
Le hic ici est que strings
et ints
sont de type List[Any]
, bien que vous puissiez facilement les contraindre à quelque chose de plus sûr (peut-être en utilisant collect
...)
Si vous avez déjà une collection de type sécurisé et que vous souhaitez partager une autre propriété des éléments, les choses sont un peu plus faciles pour vous:
val intList = List(2,7,9,1,6,5,8,2,4,6,2,9,8)
val (big,small) = intList partition (_ > 5)
//big and small are both now List[Int]s
J'espère que cela résume comment les deux méthodes peuvent vous aider ici!
Je ne sais pas comment le faire avec collect
sans utiliser de listes mutables, mais partition
peut également utiliser la correspondance de modèle (juste un peu plus verbeux)
List("a", 1, 2, "b", 19).partition {
case s:String => true
case _ => false
}
La signature du collect
normalement utilisé sur, disons, Seq
, est
collect[B](pf: PartialFunction[A,B]): Seq[B]
ce qui est vraiment un cas particulier de
collect[B, That](pf: PartialFunction[A,B])(
implicit bf: CanBuildFrom[Seq[A], B, That]
): That
Donc, si vous l'utilisez en mode par défaut, la réponse est non, certainement pas: vous en tirez exactement une séquence. Si vous suivez CanBuildFrom
à Builder
, vous voyez qu'il serait possible de faire de That
en fait deux séquences, mais il n'y aurait aucun moyen de savoir quelle séquence un élément devrait entrer, car la fonction partielle ne peut dire que "oui, j'appartiens" ou "non, je n'appartiens pas".
Alors, que faites-vous si vous souhaitez que plusieurs conditions entraînent la division de votre liste en plusieurs morceaux différents? Une façon consiste à créer une fonction d'indicateur A => Int
, où votre A
est mappé dans une classe numérotée, puis utilisez groupBy
. Par exemple:
def optionClass(a: Any) = a match {
case None => 0
case Some(x) => 1
case _ => 2
}
scala> List(None,3,Some(2),5,None).groupBy(optionClass)
res11: scala.collection.immutable.Map[Int,List[Any]] =
Map((2,List(3, 5)), (1,List(Some(2))), (0,List(None, None)))
Vous pouvez maintenant rechercher vos sous-listes par classe (0, 1 et 2 dans ce cas). Malheureusement, si vous voulez ignorer certaines entrées, vous devez toujours les mettre dans une classe (par exemple, vous ne vous souciez probablement pas des multiples copies de None
dans ce cas).
J'utilise ça. Une bonne chose à ce sujet est qu'il combine le partitionnement et le mappage en une seule itération. Un inconvénient est qu'il alloue un tas d'objets temporaires (le Either.Left
et Either.Right
instances)
/**
* Splits the input list into a list of B's and a list of C's, depending on which type of value the mapper function returns.
*/
def mapSplit[A,B,C](in: List[A])(mapper: (A) => Either[B,C]): (List[B], List[C]) = {
@tailrec
def mapSplit0(in: List[A], bs: List[B], cs: List[C]): (List[B], List[C]) = {
in match {
case a :: as =>
mapper(a) match {
case Left(b) => mapSplit0(as, b :: bs, cs )
case Right(c) => mapSplit0(as, bs, c :: cs)
}
case Nil =>
(bs.reverse, cs.reverse)
}
}
mapSplit0(in, Nil, Nil)
}
val got = mapSplit(List(1,2,3,4,5)) {
case x if x % 2 == 0 => Left(x)
case y => Right(y.toString * y)
}
assertEquals((List(2,4),List("1","333","55555")), got)
Démarrage Scala 2.13
, la plupart des collections sont désormais fournies avec une méthode partitionMap
qui partitionne les éléments en fonction d'une fonction qui renvoie Right
ou Left
.
Cela nous permet de faire correspondre les modèles en fonction du type (qui, en tant que collect
, permet d'avoir des types spécifiques dans les listes partitionnées) ou de tout autre modèle:
val (strings, ints) =
List("a", 1, 2, "b", 19).partitionMap {
case s: String => Left(s)
case x: Int => Right(x)
}
// strings: List[String] = List("a", "b")
// ints: List[Int] = List(1, 2, 19)
Je n'ai pas pu trouver de solution satisfaisante à ce problème fondamental ici. Je n'ai pas besoin d'une conférence sur collect
et je me fiche que ce soit les devoirs de quelqu'un. De plus, je ne veux pas quelque chose qui ne fonctionne que pour List
.
Voici donc mon coup de couteau. Efficace et compatible avec toutes les chaînes de TraversableOnce
, même:
implicit class TraversableOnceHelper[A,Repr](private val repr: Repr)(implicit isTrav: Repr => TraversableOnce[A]) {
def collectPartition[B,Left](pf: PartialFunction[A, B])
(implicit bfLeft: CanBuildFrom[Repr, B, Left], bfRight: CanBuildFrom[Repr, A, Repr]): (Left, Repr) = {
val left = bfLeft(repr)
val right = bfRight(repr)
val it = repr.toIterator
while (it.hasNext) {
val next = it.next
if (!pf.runWith(left += _)(next)) right += next
}
left.result -> right.result
}
def mapSplit[B,C,Left,Right](f: A => Either[B,C])
(implicit bfLeft: CanBuildFrom[Repr, B, Left], bfRight: CanBuildFrom[Repr, C, Right]): (Left, Right) = {
val left = bfLeft(repr)
val right = bfRight(repr)
val it = repr.toIterator
while (it.hasNext) {
f(it.next) match {
case Left(next) => left += next
case Right(next) => right += next
}
}
left.result -> right.result
}
}
Exemples d'utilisations:
val (syms, ints) =
Seq(Left('ok), Right(42), Right(666), Left('ko), Right(-1)) mapSplit identity
val ctx = Map('a -> 1, 'b -> 2) map {case(n,v) => n->(n,v)}
val (bound, unbound) = Vector('a, 'a, 'c, 'b) collectPartition ctx
println(bound: Vector[(Symbol, Int)], unbound: Vector[Symbol])
Quelque chose comme ça pourrait aider
def partitionMap[IN, A, B](seq: Seq[IN])(function: IN => Either[A, B]): (Seq[A], Seq[B]) = {
val (eitherLeft, eitherRight) = seq.map(function).partition(_.isLeft)
eitherLeft.map(_.left.get) -> eitherRight.map(_.right.get)
}
Pour l'appeler
val seq: Seq[Any] = Seq(1, "A", 2, "B")
val (ints, strings) = CollectionUtils.partitionMap(seq) {
case int: Int => Left(int)
case str: String => Right(str)
}
ints shouldBe Seq(1, 2)
strings shouldBe Seq("A", "B")
Advantage est une API simple, similaire à celle de Scala 2.12
Désavantage; la collection est exécutée deux fois et il manque la prise en charge de CanBuildFrom