2010-08-26 7 views
13

Tôi đang học Scala vì nó phù hợp với nhu cầu của tôi tốt nhưng tôi thấy khó để cấu trúc mã thanh lịch. Tôi đang ở trong tình huống mà tôi có một số Listx và muốn tạo hai số List s: một chứa tất cả các thành phần của SomeClass và một chứa tất cả các phần tử không phải là SomeClass.Scala: Lọc dựa trên loại

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

Ngay bây giờ mã của tôi trông giống như vậy. Tuy nhiên, nó không phải là rất hiệu quả vì nó lặp lại x hai lần và mã bằng cách nào đó có vẻ hơi hack. Có cách nào tốt hơn (thanh lịch hơn) để làm việc không?

Có thể giả định rằng SomeClass không có lớp con.

Trả lời

8

EDITED

Trong khi sử dụng đồng bằng partition là có thể, nó sẽ mất những thông tin kiểu giữ lại bởi collect trong câu hỏi.

Người ta có thể định nghĩa một biến thể của partition phương pháp mà chấp nhận một hàm trả về một giá trị của một trong hai loại sử dụng 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) 
} 

Sau đó, các loại được giữ lại:

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

Dĩ nhiên giải pháp có thể được cải thiện bằng cách sử dụng các nhà xây dựng và tất cả các công cụ thu thập được cải tiến :).

Đối với đầy đủ câu trả lời cũ của tôi sử dụng đơn giản partition:

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

Ví dụ:

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

Quá xấu 'a' thuộc loại' Danh sách [Bất kỳ] 'so với' Danh sách [Int] '... – huynhjl

+0

Chỉ vì' x' là. Xem câu trả lời của @ abhin4v. –

+1

Tôi hiểu tại sao nó là 'List [Any]', nó chỉ là 'collect' như được sử dụng trong câu hỏi sẽ trả về một' List [SomeClass] 'trong khi phân vùng mất thông tin này. – huynhjl

4

Sử dụng 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

Đối với phân vùng theo loại, sử dụng phương pháp này:

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

Cách sử dụng:

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

Trong khi phân vùng là mát mẻ và tôi cũng đã chọn phương pháp này, nó không hoạt động tốt trong tình huống được mô tả trong câu hỏi, vì nó không cung cấp 'a' kiểu tĩnh' List [SomeClass] '. Vì vậy, khi bạn sử dụng 'a' sau trong chương trình, bạn sẽ phải kiểm tra kiểu thời gian chạy một lần nữa hoặc bỏ vô điều kiện [rùng mình]. – mkneissl

2

Nếu danh sách chỉ chứa các lớp con của AnyRef, becaus của phương pháp getClass. Bạn có thể làm điều này:

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

Chỉ muốn mở rộng về câu trả lời mkneissl với một phiên bản "chung chung hơn" nên hoạt động trên nhiều bộ sưu tập khác nhau trong thư viện:

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

Chỉ cần một lưu ý: Các phân vùng phương pháp tương tự, nhưng chúng tôi cần phải nắm bắt một vài loại:

X -> Loại gốc cho các mục trong bộ sưu tập.

A -> Các loại mặt hàng trong phân vùng trái

B -> Các loại mặt hàng trong phân vùng ngay

CC -> Các "cụ thể" loại của bộ sưu tập (Vector, Danh sách, Seq, v.v.) này phải được xếp loại cao hơn. Có lẽ chúng ta có thể làm việc xung quanh một số vấn đề kiểu suy luận (xem phản ứng của Adrian ở đây: http://suereth.blogspot.com/2010/06/preserving-types-and-differing-subclass.html), nhưng tôi đã cảm thấy lười biếng;)

Để -> Các loại toàn bộ bộ sưu tập ở phía bên tay trái

To2 -> Loại hoàn chỉnh của bộ sưu tập ở phía bên tay phải

Cuối cùng, các tham số ngầm "CanBuildFrom" hài hước là thứ cho phép chúng tôi tạo các loại cụ thể, như Danh sách hoặc Vector, một cách tổng quát. Chúng được tích hợp vào tất cả các bộ sưu tập thư viện cốt lõi.

Trớ trêu thay, toàn bộ lý do cho phép thuật CanBuildFrom là xử lý BitSets đúng cách. Vì tôi yêu cầu CC phải cao cấp hơn, chúng tôi nhận được thông báo lỗi thú vị này khi sử dụng phân vùng:

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

Tôi để người này giải quyết nếu cần! Tôi sẽ xem nếu tôi có thể cung cấp cho bạn một giải pháp làm việc với BitSet sau khi chơi thêm.

+0

Tuyệt vời, cảm ơn. Tôi đã rất gần với giải pháp của bạn nhưng không nghĩ đến việc làm cho thông số loại bộ sưu tập cao hơn. Do đó, inferencer loại suy ra 'Nothing' ... – mkneissl