Scala Partition/Collect Usage
Czy można użyć jednego wywołania do collect
, aby utworzyć 2 nowe listy? Jeśli nie, jak Mogę to zrobić używając partition
?
5 answers
collect
(zdefiniowane na TraversableLike i dostępne we wszystkich podklasach) działa z kolekcją i PartialFunction
. Tak się też składa, że kilka klauzul przypadków zdefiniowanych wewnątrz klamer jest funkcją częściową (patrz sekcja 8.5 specyfikacji języka Scala [Ostrzeżenie-PDF])
Jak w obsłudze WYJĄTKÓW:
try {
... do something risky ...
} catch {
//The contents of this catch block are a partial function
case e: IOException => ...
case e: OtherException => ...
}
Jest to przydatny sposób definiowania funkcji, która będzie akceptować tylko niektóre wartości danego typu.
Rozważ użycie go na lista wartości mieszanych:
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
}
Argumentem metody to to collect
jest PartialFunction[Any,String]
. PartialFunction
ponieważ nie jest zdefiniowany dla wszystkich możliwych wejść typu Any
(który jest typem List
) i String
ponieważ to jest to, co zwracają wszystkie klauzule.
Jeśli spróbujesz użyć map
zamiast collect
, Podwójna wartość na końcu mixedList
spowodowałaby MatchError
. Użycie collect
po prostu odrzuca tę, jak również każdą inną wartość, dla której funkcja częściowa nie jest zdefiniowana.
Jeden możliwe byłoby zastosowanie innej logiki do elementów listy:
var strings = List.empty[String]
var ints = List.empty[Int]
mixedList collect {
case s: String => strings :+= s
case i: Int => ints :+= i
}
Chociaż jest to tylko przykład, używanie zmiennych zmiennych, takich jak ta, jest uważane przez wielu za zbrodnię wojenną - więc proszę, nie róbcie tego!
A dużo lepszym rozwiązaniem jest użycie collect dwa razy:
val strings = mixedList collect { case s: String => s }
val ints = mixedList collect { case i: Int => i }
Lub jeśli wiesz na pewno, że lista zawiera tylko dwa typy wartości, możesz użyć partition
, która dzieli zbiory na wartości w zależności od tego, czy pasują do niektórych predykat:
//if the list only contains Strings and Ints:
val (strings, ints) = mixedList partition { case s: String => true; case _ => false }
Haczyk polega na tym, że zarówno strings
, jak i ints
są typu List[Any]
, choć można je łatwo zmusić do czegoś więcej typów (być może za pomocą collect
...)
Jeśli posiadasz już kolekcję type-safe I chcesz podzielić się na inną własność elementów, wtedy jest ci trochę łatwiej:
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
Mam nadzieję, że to podsumuje, jak te dwie metody mogą Ci pomóc tutaj!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-24 16:56:17
Nie wiem, jak to zrobić z collect
bez użycia mutowalnych list, ale partition
może również używać dopasowania wzorców (tylko trochę bardziej wyrazistych)
List("a", 1, 2, "b", 19).partition {
case s:String => true
case _ => false
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-29 18:43:22
Podpis normalnie używanego collect
na, powiedzmy, Seq
, to
collect[B](pf: PartialFunction[A,B]): Seq[B]
Co jest naprawdę szczególnym przypadkiem
collect[B, That](pf: PartialFunction[A,B])(
implicit bf: CanBuildFrom[Seq[A], B, That]
): That
Więc jeśli używasz go w trybie domyślnym, odpowiedź brzmi nie, z pewnością nie: otrzymujesz z niego dokładnie jedną sekwencję. Jeśli zastosujesz CanBuildFrom
przez Builder
, zobaczysz, że możliwe byłoby, aby That
były w rzeczywistości dwiema sekwencjami, ale nie można powiedzieć, do której sekwencji powinien wejść element, ponieważ funkcja częściowa może tylko powiedzieć " tak, należę" albo "nie, nie należę".
Więc co zrobić, jeśli chcesz mieć wiele warunków, które skutkują podzieleniem listy na kilka różnych części? Jednym ze sposobów jest utworzenie funkcji wskaźnika A => Int
, gdzie twoja A
jest mapowana do numerowanej klasy, a następnie użyj groupBy
. Na przykład:
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)))
Teraz możesz przeglądać swoje listy podrzędne według klas (w tym przypadku 0, 1 i 2). Niestety, jeśli chcesz zignorować niektóre wejścia, nadal musisz umieścić je w klasie (np. prawdopodobnie nie obchodzi mnie wiele kopii None
w tym przypadku).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-24 17:03:20
Używam tego. Jedną miłą rzeczą jest to, że łączy partycjonowanie i mapowanie w jednej iteracji. Jedną z wad jest to, że przydziela kilka tymczasowych obiektów (instancje Either.Left
i Either.Right
)
/**
* 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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-25 18:15:31
Nie mogłem znaleźć satysfakcjonującego rozwiązania tego podstawowego problemu.
Nie potrzebuję wykładu o collect
i nie obchodzi mnie, czy to czyjeś zadanie domowe. Poza tym, nie chcę czegoś, co działa tylko dla List
.
TraversableOnce
, nawet ciągami:
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
}
}
Przykładowe zastosowania:
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])
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-26 19:52:50