2010-08-26 24 views
13

Estoy aprendiendo Scala, ya que se adapta bien a mis necesidades, pero me resulta difícil estructurar el código con elegancia. Estoy en una situación en la que tengo Listx y quiero crear dos List s: uno que contenga todos los elementos de SomeClass y uno que contenga todos los elementos que no sean de SomeClass.Scala: filtrado basado en el tipo

val a = x collect {case y:SomeClass => y} 
val b = x filterNot {_.isInstanceOf[SomeClass]} 

Ahora mi código se ve así. Sin embargo, no es muy eficiente ya que itera x dos veces y el código de alguna manera parece un poco hackish. ¿Hay una forma mejor (más elegante) de hacer las cosas?

Puede asumirse que SomeClass no tiene subclases.

Respuesta

8

editado

Durante el uso normal partition es posible, se pierde la información de tipo retenido por collect en la pregunta.

Se podría definir una variante del método partition que acepta una función que devuelve un valor de uno de dos tipos usando Either:

import collection.mutable.ListBuffer 

def partition[X,A,B](xs: List[X])(f: X=>Either[A,B]): (List[A],List[B]) = { 
    val as = new ListBuffer[A] 
    val bs = new ListBuffer[B] 
    for (x <- xs) { 
    f(x) match { 
     case Left(a) => as += a 
     case Right(b) => bs += b 
    } 
    } 
    (as.toList, bs.toList) 
} 

Entonces se retienen los tipos:

scala> partition(List(1,"two", 3)) { 
    case i: Int => Left(i) 
    case x => Right(x) 
} 

res5: (List[Int], List[Any]) = (List(1, 3),List(two)) 

Por supuesto la solución podría mejorarse usando constructores y todas las cosas mejoradas de la colección :).

Para completar mi vieja respuesta utilizando llanura partition:

val (a,b) = x partition { _.isInstanceOf[SomeClass] } 

Por ejemplo:

scala> val x = List(1,2, "three") 
x: List[Any] = List(1, 2, three) 

scala> val (a,b) = x partition { _.isInstanceOf[Int] } 
a: List[Any] = List(1, 2) 
b: List[Any] = List(three) 
+0

Lástima 'a' 'es de tipo Lista [Cualquier]' 'frente Lista [Int]' ... – huynhjl

+0

eso es sólo porque 'x' es. Ver la respuesta de @ abhin4v. –

+1

entiendo por qué es 'Lista [Cualquier] ', es sólo que' collect' tal como se utiliza en la pregunta devolverá una lista de '[SomeClass]' mientras partición pierde esta información. – huynhjl

4

Uso list.partition:

scala> val l = List(1, 2, 3) 
l: List[Int] = List(1, 2, 3) 

scala> val (even, odd) = l partition { _ % 2 == 0 } 
even: List[Int] = List(2) 
odd: List[Int] = List(1, 3) 

EDITAR

para dividir por tipo, utilice este método:

def partitionByType[X, A <: X](list: List[X], typ: Class[A]): 
    Pair[List[A], List[X]] = { 
    val as = new ListBuffer[A] 
    val notAs = new ListBuffer[X] 
    list foreach {x => 
     if (typ.isAssignableFrom(x.asInstanceOf[AnyRef].getClass)) { 
     as += typ cast x 
     } else { 
     notAs += x 
     } 
    } 
    (as.toList, notAs.toList) 
} 

Uso:

scala> val (a, b) = partitionByType(List(1, 2, "three"), classOf[java.lang.Integer]) 
a: List[java.lang.Integer] = List(1, 2) 
b: List[Any] = List(three) 
+0

Mientras partición es fresco y que originalmente también había elegido este enfoque, no funciona bien en la situación descrita en la pregunta, ya que no da 'a' 'tipo estático Lista [SomeClass] '. Entonces, cuando usó 'a' más adelante en el programa, debería verificar nuevamente el tipo de tiempo de ejecución o emitir incondicionalmente [escalofrío]. – mkneissl

2

Si la lista sólo contiene subclases de AnyRef, becaus del método getClass. Usted puede hacer esto:

scala> case class Person(name: String)               
defined class Person 

scala> case class Pet(name: String)                
defined class Pet 

scala> val l: List[AnyRef] = List(Person("Walt"), Pet("Donald"), Person("Disney"), Pet("Mickey")) 
l: List[AnyRef] = List(Person(Walt), Pet(Donald), Person(Disney), Pet(Mickey)) 

scala> val groupedByClass = l.groupBy(e => e.getClass) 
groupedByClass: scala.collection.immutable.Map[java.lang.Class[_],List[AnyRef]] = Map((class Person,List(Person(Walt), Person(Disney))), (class Pet,List(Pet(Donald), Pet(Mickey)))) 

scala> groupedByClass(classOf[Pet])(0).asInstanceOf[Pet] 
res19: Pet = Pet(Donald) 
5

Sólo quería ampliar la respuesta de mkneissl con una versión "más genérico" que debe funcionar en muchas colecciones diferentes en la biblioteca:

scala> import collection._ 
import collection._ 

scala> import generic.CanBuildFrom 
import generic.CanBuildFrom 

scala> def partition[X,A,B,CC[X] <: Traversable[X], To, To2](xs : CC[X])(f : X => Either[A,B])(
    | implicit cbf1 : CanBuildFrom[CC[X],A,To], cbf2 : CanBuildFrom[CC[X],B,To2]) : (To, To2) = { 
    | val left = cbf1() 
    | val right = cbf2() 
    | xs.foreach(f(_).fold(left +=, right +=)) 
    | (left.result(), right.result()) 
    | } 
partition: [X,A,B,CC[X] <: Traversable[X],To,To2](xs: CC[X])(f: (X) => Either[A,B])(implicit cbf1: scala.collection.generic.CanBuildFrom[CC[X],A,To],implicit cbf2: scala.collection.generic.CanBuildFrom[CC[X],B,To2])(To, To2) 

scala> partition(List(1,"two", 3)) {                 
    | case i: Int => Left(i)                  
    | case x => Right(x)                   
    | } 
res5: (List[Int], List[Any]) = (List(1, 3),List(two)) 

scala> partition(Vector(1,"two", 3)) { 
    | case i: Int => Left(i)  
    | case x => Right(x)   
    | } 
res6: (scala.collection.immutable.Vector[Int], scala.collection.immutable.Vector[Any]) = (Vector(1, 3),Vector(two)) 

Sólo una nota: La partición método es similar, pero tenemos que capturar algunos tipos:

X -> El tipo original para los elementos de la colección.

A -> El tipo de elementos de la partición izquierda

B -> El tipo de elementos en la partición correcta

CC -> El tipo "específico" de la colección (vector, Lista, Seq etc.) Este debe ser mayor-kinded. Probablemente podríamos evitar algunos problemas de tipo de inferencia (véase la respuesta de Adrian aquí: http://suereth.blogspot.com/2010/06/preserving-types-and-differing-subclass.html), pero me sentía perezoso;)

A -> El tipo completo de la colección en el lado izquierdo

A2 -> El tipo completo de la colección en el lado derecho

Por último, los divertidos parámetros "CanBuildFrom" son los que nos permiten construir tipos específicos, como List o Vector, genéricamente. Están integrados en todas las colecciones de la biblioteca principal.

Irónicamente, la única razón por la magia CanBuildFrom es manejar BitSets correctamente. Debido a que requiero CC para ser kinded más altos, se obtiene este mensaje de error cuando se utiliza la diversión partición:

scala> partition(BitSet(1,2, 3)) {  
    | case i if i % 2 == 0 => Left(i) 
    | case i if i % 2 == 1 => Right("ODD") 
    | } 
<console>:11: error: type mismatch; 
found : scala.collection.BitSet 
required: ?CC[ ?X ] 
Note that implicit conversions are not applicable because they are ambiguous: 
both method any2ArrowAssoc in object Predef of type [A](x: A)ArrowAssoc[A] 
and method any2Ensuring in object Predef of type [A](x: A)Ensuring[A] 
are possible conversion functions from scala.collection.BitSet to ?CC[ ?X ] 
     partition(BitSet(1,2, 3)) { 

Me marcho esta abierto para alguien para arreglar si es necesario! Veré si puedo darte una solución que funcione con BitSet después de jugar un poco más.

+0

Impresionante, gracias. Estuve muy cerca de su solución, pero no pensé en hacer que el parámetro de tipo de recopilación sea más útil. Por lo tanto, el tipo inferencer inferido 'Nothing' ... – mkneissl

Cuestiones relacionadas