2010-08-26 26 views
13

Sto imparando Scala in base alle mie esigenze, ma trovo difficile strutturare il codice in modo elegante. Sono in una situazione in cui ho una Listx e voglio creare due List s: uno contenente tutti gli elementi di SomeClass e uno contenente tutti gli elementi che non sono di SomeClass.Scala: filtro basato sul tipo

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

In questo momento il mio codice sembra così. Tuttavia, non è molto efficiente dato che itera x due volte e il codice in qualche modo sembra un po 'hackerato. Esiste un modo migliore (più elegante) di fare le cose?

Si può presumere che SomeClass non abbia sottoclassi.

risposta

8

a cura

Durante l'utilizzo normale partition è possibile, perde le informazioni sul tipo trattenuto dal collect nella questione.

Si potrebbe definire una variante del metodo partition che accetta una funzione che restituisce un valore di uno dei due tipi utilizzando 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) 
} 

Poi i tipi sono conservati:

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

Naturalmente la soluzione potrebbe essere migliorata usando i builder e tutte le migliori risorse di raccolta :).

Per completezza mia vecchio risposta utilizzando pianura partition:

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

Ad esempio:

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

Peccato che 'a' sia di tipo' Elenco [Qualsiasi] 'contro 'Elenco [Int]' ... – huynhjl

+0

Questo è solo perché 'x' è. Vedi la risposta di @ abhin4v. –

+1

Capisco perché è 'List [Any]', è solo che 'collect' come usato nella domanda restituirà un' List [SomeClass] 'mentre la partizione perde queste informazioni. – 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) 

EDIT

Per partizionamento per tipo, utilizzare questo metodo:

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

utilizzati:

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

Mentre la partizione è buona e inizialmente avevo scelto questo approccio, non funziona bene nella situazione descritta nella domanda, perché non fornisce 'a' il tipo statico' Elenco [SomeClass] '. Quindi, quando hai usato 'a' più avanti nel programma, dovresti controllare di nuovo il tipo di esecuzione o lanciare incondizionatamente [shudder]. – mkneissl

2

Se l'elenco contiene solo sottoclassi di AnyRef, becaus del metodo getClass. Si può fare questo:

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

Volevo solo per espandere sulla risposta mkneissl con una versione "più generico" che dovrebbe funzionare su molte collezioni differenti nella 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)) 

Solo una nota: La partizione il metodo è simile, ma è necessario acquisire alcuni tipi:

X -> Il tipo originale per gli elementi nella raccolta.

A -> Il tipo di oggetti nella partizione sinistra

B -> Il tipo di oggetti nella partizione destra

CC -> Il tipo "specifico" della collezione (Vector, Lista, Seq ecc.) Questo deve essere superiore a. Potremmo probabilmente risolvere alcuni problemi di tipo-inferenza (vedi la risposta di Adrian qui: http://suereth.blogspot.com/2010/06/preserving-types-and-differing-subclass.html), ma mi sentivo pigro;)

A -> Il tipo completo di raccolta sul lato sinistro

To2 -> Il tipo completo della raccolta sul lato destro

Infine, i simpatici parametri "CanBuildFrom" impliciti sono ciò che ci consente di costruire tipi specifici, come Elenco o Vettore, genericamente. Sono integrati in tutte le raccolte della libreria principale.

Ironia della sorte, l'intero motivo della magia CanBuildFrom è gestire correttamente i BitSet. Perché ho bisogno di essere CC kinded superiori, otteniamo questo messaggio di errore quando si utilizza divertente partizione:

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

sto lasciando questo aperto per qualcuno a risolvere, se necessario! Vedrò se posso darti una soluzione che funziona con BitSet dopo un po 'più di gioco.

+0

Fantastico, grazie. Sono stato molto vicino alla tua soluzione ma non ho pensato di rendere il parametro del tipo di raccolta più alto. Quindi il tipo di inferenza ha dedotto 'Nothing' ... – mkneissl