2010-08-26 11 views
13

Ich lerne Scala, wie es meine Bedürfnisse gut passt, aber ich finde es schwer, Code elegant zu strukturieren. Ich bin in einer Situation, in der ich ein Listx habe und zwei List s erstellen möchte: eins, das alle Elemente von SomeClass enthält und eins, das alle Elemente enthält, die nicht von SomeClass sind.Scala: Filterung basierend auf Typ

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

Im Moment sieht mein Code so aus. Allerdings ist es nicht sehr effizient, da es zweimal x iteriert und der Code scheint irgendwie ein bisschen hackish. Gibt es eine bessere (elegantere) Art, Dinge zu tun?

Es kann angenommen werden, dass SomeClass keine Unterklassen hat.

Antwort

8

EDITED

Bei der Verwendung von Klar partition möglich ist, verliert er die Typinformationen durch collect in der Frage erhalten.

Man könnte eine Variante des partition Verfahren definieren, die eine Funktion mit einem Wert von einem von zwei Typen akzeptiert Either unter Verwendung zurückkehr:

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

Dann werden die Arten erhalten werden:

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

Natürlich Die Lösung könnte verbessert werden, indem man Builder und all die verbesserten Sammlungen verwendet :).

Der Vollständigkeit meiner alte Antwort Ebene mit partition:

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

Zum Beispiel:

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

Schade, dass 'a' vom Typ' List [Any] 'ist gegenüber' List [Int] '... – huynhjl

+0

Das liegt nur daran, dass' x' ist. Siehe @ abhin4vs Antwort. –

+1

Ich verstehe, warum es 'List [Any]' ist, es ist nur, dass 'collect', wie in der Frage verwendet, eine' List [SomeClass] 'zurückgibt, während die Partition diese Information verliert. – huynhjl

4

Verwenden 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

zum Abteilen nach Typ, mit dieser Methode:

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

Usage:

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

Während Partition ist cool und ich hatte ursprünglich auch diesen Ansatz gewählt, es funktioniert nicht gut in der Situation in der Frage beschrieben, weil es nicht gibt den statischen Typ "List [SomeClass]". Wenn Sie 'a' später im Programm verwenden, müssen Sie den Runtime-Typ erneut überprüfen oder unbedingt [shudder] umwandeln. – mkneissl

2

Wenn die Liste nur Subklassen von AnyRef enthält, becaus des Verfahrens getClass. Sie können dies tun:

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

Ich wollte nur mit einem „allgemeineren“ -Version auf mkneissl Antwort erweitern, die auf vielen verschiedenen Sammlungen in der Bibliothek arbeiten sollten:

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

Nur eine Anmerkung: die Trennwand Methode ist ähnlich, aber wir müssen ein paar Arten erfassen:

X -> Der ursprüngliche Typ für Elemente in der Sammlung.

A -> Die Art der Elemente in der linken Trennwand

B -> Die Art der Elemente in der richtigen Partition

CC -> Die "spezifische" Art der Sammlung (Vector, List, Seq etc.) Diese muss höher-kinded sein. Wir könnten wahrscheinlich einige Typ-Inferenz-Probleme umgehen (siehe Adrian Antwort hier: http://suereth.blogspot.com/2010/06/preserving-types-and-differing-subclass.html), aber ich fühlte mich faul;)

To -> Die komplette Art der Sammlung auf der linken Seite

To2 -> Der vollständige Typ der Sammlung auf der rechten Seite

Schließlich sind die lustigen "CanBuildFrom" impliziten Parameter, die uns erlauben, bestimmte Typen, wie List oder Vector, generisch zu konstruieren. Sie sind in alle zentralen Bibliothekssammlungen integriert.

Ironischerweise ist der Grund für die CanBuildFrom-Magie, BitSets korrekt zu behandeln. Weil ich CC erfordern höhere kinded sein, bekommen wir diese Meldung Spaß Fehler, wenn Partition mit:

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

Ich gehe für jemanden offen, dies zu beheben, wenn nötig! Ich werde sehen, ob ich Ihnen eine Lösung geben kann, die nach einiger Zeit mit BitSet funktioniert.

+0

Super, danke. Ich war Ihrer Lösung sehr nahe, habe aber nicht daran gedacht, den Parameter für den Sammlertyp höher zu setzen. So folgerte der Typinterferenzist "Nichts" ... – mkneissl