Czy można użyć jednego połączenia z numerem collect
, aby utworzyć 2 nowe listy? Jeśli nie, w jaki sposób mogę to zrobić, używając partition
?Scala Partition/Collect Usage
Odpowiedz
collect
(określona na TraversableLike i dostępne we wszystkich podklas) działa z kolekcją i PartialFunction
. Również tak zdarza się, że kilka klauzul przypadku określonych wewnątrz szyn jest częściowe działanie (patrz punkt 8.5 do Scala Language Specification[ostrzegawczy - PDF])
jak w wyjątkowych:
try {
... do something risky ...
} catch {
//The contents of this catch block are a partial function
case e: IOException => ...
case e: OtherException => ...
}
To przydatny sposób zdefiniowania funkcji, która będzie akceptować tylko niektóre wartości danego typu.
Rozważ używanie go na liście 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 do collect
metoda 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 właśnie zwracają wszystkie klauzule.
Jeśli próbowałeś użyć map
zamiast collect
, podwójna wartość na końcu mixedList
spowodowałaby MatchError
. Użycie tej opcji powoduje odrzucenie tej wartości, jak również każdej innej wartości, dla której nie została zdefiniowana funkcja częściowa.
Jednym z możliwych zastosowań byłoby zastosować inną logikę 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, stosując zmienne zmienne jak to jest uważany przez wielu za zbrodnię wojenną - Więc proszę nie rób tego!
znacznie lepszym rozwiązaniem jest użycie zbierać dwa razy:
val strings = mixedList collect { case s: String => s }
val ints = mixedList collect { case i: Int => i }
Albo jeśli wiesz na pewno, że lista zawiera tylko dwa rodzaje wartości, można użyć partition
, która dzieli kolekcji na wartości w zależności od tego, czy są one zgodne jakiś predykat:
//if the list only contains Strings and Ints:
val (strings, ints) = mixedList partition { case s: String => true; case _ => false }
połów jest to, że zarówno strings
i ints
są typu List[Any]
, ale możesz łatwo przekonać ich do czegoś bardziej bezpiecznego (prawdopodobnie używając collect
...)
Jeśli masz już kolekcję bezpieczną dla typów i chcesz podzielić ją na inną właściwość elementów, to są rzeczy nieco łatwiej dla ciebie:
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 podsumowuję, jak dwie metody mogą ci pomóc!
Nie wiem, jak to zrobić z collect
bez użycia zmienny list, ale partition
mogą korzystać wzór pasujący jak również (tylko trochę bardziej gadatliwy)
List("a", 1, 2, "b", 19).partition {
case s:String => true
case _ => false
}
@coubeatczech - Ponieważ przegroda Zwraca '(Lista [A] Lista [A])'. To wszystko, co może zrobić, ponieważ dane wejściowe to 'List [A]' i funkcja wskaźnika 'A => Boolean'. Nie ma możliwości sprawdzenia, czy funkcja wskaźnika może być specyficzna dla typu. –
@Rex Zdefiniowałem własną metodę 'collate', która służy do tworzenia kolekcji, która rozwiązuje właśnie ten problem. Na "liście [A]" sygnaturą przypadku użycia jest 'collate [B] (fn: PartialFunction [A, B]): (lista (B), lista (A))', oczywiście * faktyczny * podpis jest trochę bardziej owłosione, ponieważ używam 'CanBuildFrom' –
Podpis normalnie stosowanego collect
na, powiedzmy, Seq
jest
collect[B](pf: PartialFunction[A,B]): Seq[B]
który jest naprawdę szczególny przypadek
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, na pewno nie: otrzymujesz od niej dokładnie jedną sekwencję. Jeśli podążysz za CanBuildFrom
do Builder
, zobaczysz, że byłoby możliwe, aby That
były w rzeczywistości dwiema sekwencjami, ale nie można by było powiedzieć, do której sekwencji dany element ma wejść, ponieważ funkcja częściowa może powiedzieć tylko "tak, ja należą "lub" nie, nie należę ".
Co więc zrobić, jeśli chcesz mieć wiele warunków, które sprawiają, że twoja lista jest podzielona na kilka różnych elementów? Jednym ze sposobów jest utworzenie funkcji wskaźnika A => Int
, w której twój A
jest odwzorowany na klasę o numerach, a następnie użyć 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żna sprawdzić swoje sub-list od klasy (0, 1 i 2, w tym przypadku). Niestety, jeśli chcesz zignorować niektóre dane wejściowe, nadal musisz umieścić je w klasie (np. Prawdopodobnie nie dbasz o wiele kopii None
w tym przypadku).
Używam tego. Jedną fajną rzeczą jest to, że łączy partycjonowanie i mapowanie w jednej iteracji. Jedną wadą jest to, że ma przeznaczyć kilka tymczasowych obiektów (na Either.Left
i Either.Right
wystąpienia)
/**
* 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)
nie mogłem znaleźć satysfakcjonujące rozwiązanie tego podstawowego problemu tutaj. Nie potrzebuję wykładu na temat collect
i nie obchodzi mnie, czy to jest czyjaś praca domowa. Ponadto nie chcę, aby coś, co działa tylko dla List
.
Oto moje ukłucie. Wydajne i zgodne z dowolnym TraversableOnce
nawet ciągi:
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łady użycia:
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])
Bardzo ładne wyjaśnienie, ale to co myślę, że OP chce, to kombinacja' collect' i 'partition' która zwraca krotkę listy zebranych wartości i listę z całej reszty. 'def collectAndPartition [A, B] (pf: PartialFunction [A, B]): (Lista [B], Lista [A])'. Najprawdopodobniej byłoby to najlepiej osiągnięte dzięki natywnej funkcji bibliotecznej, tj. W źródle 'collect' w TraversableLój mamy' for (x <- this) if (pf.isDefinedAt (x)) b + = pf (x) ' , można by po prostu użyć 'else a + = x' na końcu tego, gdzie' a' byłoby budowniczym dla listy wszystkich pozostałych. –
Wiem, czego potrzebuje OP, i jestem również świadomy, że jest to zadanie domowe (zostało to ostatnio powiedziane na stosie przepełnienia stosu), więc z przyjemnością przedstawię dużo teorii bez faktycznego jej rozwiązania. Co do collectAndPartition, już to napisałem, chociaż nazwałem metodę 'collate'.Jeśli ktokolwiek naucza scala do poziomu, na którym oczekuje się, że uczniowie będą pracować z CanBuildFrom, to będę bardzo zaskoczony, że większość ludzi używa obecnie scala w produkcji. –
To było bardzo pomocne. Ale wciąż myślę ... czy można oddzielić na przykład wartości pozytywne i negatywne, bez tworzenia "zbrodni wojennych", jak pisałeś wcześniej? Jestem po prostu kolektywny, ponieważ już odrabiłem pracę domową przy użyciu partycji. Ohhh ... A tak przy okazji, dzięki za pomoc! –