2011-01-24 20 views

Odpowiedz

75

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!

+3

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. –

+3

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. –

+0

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! –

6

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 
} 
+0

@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. –

+1

@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' –

5

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).

3

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) 
1

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])