2011-01-24 9 views

Respuesta

75

collect (definida en TraversableLike y está disponible en todas las subclases) funciona con una colección y PartialFunction. También sucede que un montón de cláusulas de casos definidos dentro de llaves son una función parcial (Véase la sección 8.5 del Scala Language Specification[advertencia - PDF])

Al igual que en el manejo de excepciones:

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

que es una forma práctica de definir una función que solo aceptará algunos valores de un tipo dado.

Considere el uso en una lista de valores mixtos:

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 
} 

El argumento de que es un método collectPartialFunction[Any,String]. PartialFunction porque no está definido para todas las entradas posibles de tipo Any (que es el tipo de List) y String porque eso es lo que devuelven todas las cláusulas.

Si ha intentado utilizar map en lugar de collect, el valor doble al final de mixedList causaría un MatchError. El uso de collect simplemente descarta esto, así como cualquier otro valor para el cual PartialFunction no está definido.

Un posible uso sería aplicar lógica diferente a los elementos de la lista:

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

Aunque esto es sólo un ejemplo, el uso de variables mutables como esto es considerado por muchos como un crimen de guerra - Así que por favor no lo hagas!

Una gran solución mejor es utilizar recoger dos veces:

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

O si usted sabe con certeza que la lista sólo contiene dos tipos de valores, puede utilizar partition, que divide a las colecciones en valores dependiendo de si son o no coinciden con algún predicado:

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

El truco aquí es que tanto strings y ints son de tipo.210, aunque se puede coaccionar fácilmente de vuelta a algo más typesafe (tal vez mediante el uso de collect ...)

Si ya tiene una colección typesafe y quieren dividir en alguna otra propiedad de los elementos, entonces las cosas son un poco más fácil para usted:

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 

¡Espero que resuma cómo los dos métodos pueden ayudarle aquí!

+3

Muy buena explicación, pero lo que creo que OP quiere es una combinación de' collect' y 'partition' que devuelve una tupla de una lista de los valores recopilados y una lista de todo el resto. 'def collectAndPartition [A, B] (pf: PartialFunction [A, B]): (Lista [B], Lista [A])'. Esto probablemente se lograría de forma más elegante con una función de biblioteca nativa, es decir, en la fuente de 'collect' en TraversableLike tenemos' for (x <- this) if (pf.isDefinedAt (x)) b + = pf (x) ' , uno podría tachar simplemente un 'else a + = x' al final de eso, donde' a' sería un constructor para la lista de todos los demás. –

+3

Sé lo que necesita el OP, y también soy consciente de que esta es una pregunta para la tarea (se ha mencionado mucho en el desbordamiento de pila recientemente), así que con mucho gusto daré un montón de teoría sin resolverla en realidad. En cuanto a collectAndPartition, ya escribí eso, aunque nombré el método 'collate'.Si alguien está enseñando scala al nivel en el que se espera que los estudiantes trabajen con CanBuildFrom, entonces me sorprenderá mucho, está más allá de la mayoría de las personas que actualmente usan scala en producción. –

+0

Eso fue muy útil. Pero todavía estoy pensando ... ¿es posible separar, por ejemplo, los valores positivos y negativos, sin hacer un "crimen de guerra" como escribiste antes? Solo soy cómico porque ya hice la tarea con el uso de la partición. Ohhh ... Y, por cierto, ¡gracias por la ayuda! –

6

No está seguro de cómo hacerlo con collect sin usar listas mutables, pero partition puede utilizar coincidencia de patrones, así (sólo un poco más detallado)

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

@coubeatczech - Debido partición devuelve una '(Lista [A], Lista [A])'. Eso es todo lo que puede hacer dado que la entrada es una 'Lista [A]' y una función indicadora 'A => Booleana'. No tiene forma de saber que la función del indicador podría ser específica del tipo. –

+1

@Rex Definí mi propio método de 'cotejo' para proxenetismo en colecciones que resuelven justamente este problema. En una 'Lista [A]' la firma del caso de uso es 'intercalar [B] (fn: Función parcial [A, B]): (Lista (B), Lista (A))', obviamente la firma * real * es un poco más peludo que eso ya que también estoy usando 'CanBuildFrom' –

5

La firma del collect utilizado normalmente en, por ejemplo, Seq, es

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

que en realidad es un caso particular de

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

Así que si lo usa en el modo por defecto, el la respuesta es no, ciertamente no: obtienes exactamente una secuencia de ella. Si sigue CanBuildFrom hasta Builder, verá que sería posible hacer que That sean en realidad dos secuencias, pero no tendría forma de saber a qué secuencia debe ir un elemento, ya que la función parcial solo puede decir "sí, yo" pertenecer "o" no, no pertenezco ".

Entonces, ¿qué haces si quieres tener varias condiciones que hacen que tu lista se divida en varias piezas diferentes? Una forma es crear una función de indicador A => Int, donde su A esté mapeado en una clase numerada, y luego use groupBy. Por ejemplo:

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

Ahora usted puede buscar sus sublistas por clase (0, 1 y 2 en este caso). Desafortunadamente, si quiere ignorar algunas entradas, igual debe ponerlas en una clase (por ejemplo, probablemente no le importen las copias múltiples de None en este caso).

3

Yo uso esto. Una cosa buena de esto es que combina partición y mapeo en una iteración. Una desventaja es que asignar un montón de objetos temporales (los Either.Left y Either.Right casos)

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

no pude encontrar una solución satisfactoria a este problema básico aquí. No necesito una conferencia en collect y no me importa si esta es la tarea de alguien. Además, no quiero algo que funcione solo para List.

Así que aquí está mi puñalada. Eficientes y compatible con cualquier TraversableOnce, incluso cadenas:

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

Ejemplo Usos:

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