2011-01-24 11 views

risposta

75

collect (definito su TraversableLike e disponibile in tutte le sottoclassi) funziona con una collezione e uno PartialFunction. Anche il caso che un gruppo di clausole case definite all'interno delle parentesi sono una funzione parziale (vedere la sezione 8.5 della Scala Language Specification[avvertimento - PDF])

Come nella gestione delle eccezioni:

try { 
    ... do something risky ... 
} catch { 
    //The contents of this catch block are a partial function 
    case e: IOException => ... 
    case e: OtherException => ... 
} 

esso è un modo pratico per definire una funzione che accetta solo alcuni valori di un determinato tipo.

Considerare l'utilizzo su un elenco di valori misti:

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'argomento di per collect metodo è una PartialFunction[Any,String]. PartialFunction perché non è definito per tutti i possibili input di tipo Any (che è il tipo di List) e String perché è quello che restituiscono tutte le clausole.

Se si è tentato di utilizzare map invece di collect, il doppio valore alla fine del mixedList causerebbe un MatchError. L'utilizzo di collect elimina questo, così come qualsiasi altro valore per il quale PartialFunction non è definito.

Un uso possibile sarebbe quella di applicare la logica diversa da elementi della lista:

var strings = List.empty[String] 
var ints = List.empty[Int] 
mixedList collect { 
    case s: String => strings :+= s 
    case i: Int => ints :+= i 
} 

Anche se questo è solo un esempio, utilizzando le variabili mutabili come questo è considerato da molti come un crimine di guerra - Quindi, per favore non farlo!

Un molto soluzione migliore è quella di utilizzare raccogliere due volte:

val strings = mixedList collect { case s: String => s } 
val ints = mixedList collect { case i: Int => i } 

Oppure, se si sa per certo che l'elenco contiene solo due tipi di valori, è possibile utilizzare partition, che si divide una collezione in valori a seconda se o non corrispondono alcuni predicati:

//if the list only contains Strings and Ints: 
val (strings, ints) = mixedList partition { case s: String => true; case _ => false } 

Il fermo qui è che sia strings e ints sono di tipo List[Any], anche se puoi facilmente costringerli a qualcosa di più tipografico (magari usando collect ...)

Se hai già una raccolta sicura dal tipo e vuoi dividere su qualche altra proprietà degli elementi, allora le cose sono un po 'più facile per te:

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 

Spero che questo riassuma come i due metodi possono aiutarti qui!

+3

Molto bella spiegazione, ma quello che penso OP vuole è una combinazione di' collect' e 'partition' che restituisce una tupla di una lista dei valori raccolti e una lista di tutto il resto 'def collectAndPartition [A, B] (pf: PartialFunction [A, B]): (List [B], List [A])'. Questo probabilmente sarebbe il risultato più elegante con una funzione di libreria nativa, cioè nel sorgente di 'collect' in TraversableLike abbiamo' for (x <- this) if (pf.isDefinedAt (x)) b + = pf (x) ' , si potrebbe semplicemente virare un 'else a + = x' alla fine di quello, dove' a' sarebbe un costruttore per l'elenco di tutto il resto. –

+3

So di cosa ha bisogno l'OP, e sono anche consapevole del fatto che si tratta di una domanda sui compiti (è stata menzionata molto recentemente nello stack overflow), quindi darò volentieri un sacco di teoria senza effettivamente risolverlo. Per quanto riguarda collectAndPartition, l'ho già scritto, anche se ho chiamato il metodo 'collate'.Se qualcuno sta insegnando scala al livello in cui ci si aspetta che gli studenti lavorino con CanBuildFrom, sarei molto sorpreso, è al di là della maggior parte delle persone che attualmente utilizzano scala in produzione. –

+0

Questo è stato molto utile. Ma sto ancora pensando ... è possibile separare per esempio valori positivi e negativi, senza fare un "crimine di guerra" come hai scritto erlier? Sono solo coraggioso perché ho già fatto i compiti con l'uso della partizione. Ohhh ... E comunque, grazie per l'aiuto! –

6

Non sai come farlo con collect senza l'utilizzo di liste mutabili, ma partition può usare il pattern matching e (solo un po 'più prolisso)

List("a", 1, 2, "b", 19).partition { 
    case s:String => true 
    case _ => false 
} 
+0

@coubeatczech - A causa delle partizioni restituisce un '(Lista [A], List [A])'. Questo è tutto ciò che può fare poiché l'input è un 'List [A]' e una funzione di indicatore 'A => Boolean'. Non ha modo di sapere che la funzione dell'indicatore potrebbe essere specifica per tipo. –

+1

@Rex Ho definito il mio metodo 'collate' per sfruttare le raccolte che risolvono proprio questo problema. In una 'Lista [A]' la firma del caso d'uso è 'collate [B] (fn: PartialFunction [A, B]): (List (B), List (A))', ovviamente la * actual * signature è un po 'più peloso di quello che sto usando anche 'CanBuildFrom' –

5

La firma del normalmente utilizzato collect, diciamo, Seq, è

collect[B](pf: PartialFunction[A,B]): Seq[B] 

che è davvero un caso particolare di

collect[B, That](pf: PartialFunction[A,B])(
    implicit bf: CanBuildFrom[Seq[A], B, That] 
): That 

Quindi, se lo si utilizza in modalità predefinita, il la risposta è no, sicuramente no: ottieni esattamente una sequenza da essa. Se si segue CanBuildFrom tramite Builder, si vede che sarebbe possibile creare That in realtà in due sequenze, ma non avrebbe modo di sapere in quale sequenza deve essere inserito un elemento, poiché la funzione parziale può solo dire "sì, io appartengono "o" no, non appartengo ".

Quindi cosa si fa se si desidera avere più condizioni che comportano la suddivisione in un gruppo di pezzi diversi? Un modo è creare una funzione indicatore A => Int, in cui il tuo A è mappato in una classe numerata e quindi utilizzare groupBy. Per esempio:

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

Ora è possibile cercare i tuoi sotto-liste per classe (0, 1, e 2 in questo caso). Sfortunatamente, se vuoi ignorare alcuni input, devi comunque inserirli in una classe (ad esempio probabilmente non ti interessa più copie di None in questo caso).

3

Io uso questo. Una cosa carina è che combina il partizionamento e la mappatura in un'unica iterazione. Uno svantaggio è che assegnare un mucchio di oggetti temporanei (i Either.Left e Either.Right istanze)

/** 
* 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

non riuscivo a trovare una soluzione soddisfacente a questo problema di fondo qui. Non ho bisogno di una lezione su collect e non mi interessa se questo è compito di qualcuno. Inoltre, non voglio qualcosa che funzioni solo per List.

Quindi ecco la mia pugnalata. Efficienti e compatibili con qualsiasi TraversableOnce, anche le stringhe:

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

Esempio Usi:

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