2011-01-24 30 views

Trả lời

75

collect (xác định trên TraversableLike và có sẵn trong tất cả các lớp con) làm việc với bộ sưu tập và PartialFunction. Nó cũng chỉ cần như vậy sẽ xảy ra rằng một loạt các trường hợp điều khoản định nghĩa bên trong niềng răng là một hàm từng phần (Xem phần 8.5 của Scala Language Specification[cảnh báo - PDF])

Như trong xử lý ngoại lệ:

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

Đó là một cách tiện dụng để xác định một hàm sẽ chỉ chấp nhận một số giá trị của một kiểu nhất định.

Xem xét sử dụng nó trên một danh sách các giá trị hỗn hợp:

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 
} 

Đối số cho đến collect phương pháp là một PartialFunction[Any,String]. PartialFunction vì không được xác định cho tất cả các mục nhập có thể có của loại Any (đó là loại List) và String vì đó là tất cả các mệnh đề trả về.

Nếu bạn cố gắng sử dụng map thay vì collect, giá trị tăng gấp đôi ở cuối mixedList sẽ gây ra một số MatchError. Sử dụng collect chỉ loại bỏ điều này, cũng như bất kỳ giá trị nào khác mà PartialFunction không được xác định.

Một sử dụng có thể sẽ được áp dụng logic khác nhau đến các yếu tố của danh sách:

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

Mặc dù đây chỉ là một ví dụ, sử dụng các biến có thể thay đổi như thế này là được nhiều người xem là một tội ác chiến tranh - Vì vậy, xin vui lòng đừng làm thế!

Một giải pháp nhiều tốt hơn là sử dụng thu thập hai lần:

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

Hoặc nếu bạn biết chắc chắn rằng danh sách chỉ chứa hai loại giá trị, bạn có thể sử dụng partition, mà chia tách một bộ sưu tập vào giá trị tuỳ thuộc vào việc hay không phù hợp với một số vị:

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

Việc nắm bắt ở đây là cả hai stringsints là loại List[Any], mặc dù bạn có thể dễ dàng ép chúng trở lại một cái gì đó an toàn hơn (có thể bằng cách sử dụng collect ...)

Nếu bạn đã có bộ sưu tập an toàn loại và muốn phân chia một số tài sản khác của các phần tử, thì mọi thứ một chút dễ dàng hơn cho bạn:

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 

Mong rằng tổng hợp hai phương pháp có thể giúp bạn ra khỏi đây!

+3

Giải thích rất hay, nhưng những gì tôi nghĩ OP muốn là sự kết hợp của' collection' và 'partition' trả về một danh sách các giá trị được thu thập và một danh sách của tất cả các phần còn lại. 'def collectAndPartition [A, B] (pf: PartialFunction [A, B]): (Danh sách [B], Danh sách [A])'. Điều này có lẽ sẽ đạt được một cách tao nhã nhất với một hàm thư viện riêng, tức là trong nguồn 'collect' trong TraversableLike chúng ta có' for (x <- this) nếu (pf.isDefinedAt (x)) b + = pf (x) ' , người ta có thể chỉ đơn giản là tack một 'else a + = x' vào cuối của đó, nơi' a' sẽ là một người xây dựng cho danh sách của tất cả các phần còn lại. –

+3

Tôi biết OP cần gì, và tôi cũng biết rằng đây là một câu hỏi về bài tập về nhà (gần đây đã được đề cập trên chồng tràn), vì vậy tôi sẵn sàng cung cấp nhiều lý thuyết mà không thực sự giải quyết nó. Đối với collectAndPartition, tôi đã viết rằng, mặc dù tôi đã đặt tên cho phương thức 'collate'.Nếu bất cứ ai đang dạy scala đến mức mà sinh viên được dự kiến ​​sẽ làm việc với CanBuildFrom thì tôi sẽ rất ngạc nhiên, nó vượt ra ngoài hầu hết mọi người hiện đang sử dụng scala trong sản xuất. –

+0

Điều đó rất hữu ích. Nhưng tôi vẫn đang nghĩ ... liệu có thể tách biệt các giá trị tích cực và tiêu cực, mà không tạo ra "tội phạm chiến tranh" khi bạn viết sai lầm hơn? Tôi chỉ can đảm vì tôi đã làm bài tập về nhà với việc sử dụng phân vùng. Ohhh ... Và nhân tiện, Cảm ơn bạn đã giúp đỡ! –

6

Không chắc làm thế nào để làm điều đó với collect mà không cần sử dụng danh sách có thể thay đổi, nhưng partition có thể sử dụng mô hình kết hợp cũng như (chỉ là một chút dài dòng hơn)

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

@coubeatczech - Bởi vì phân vùng trả về một '(Danh sách [A], Danh sách [A])'. Đó là tất cả những gì nó có thể làm vì đầu vào là một 'List [A]' và một hàm chỉ thị 'A => Boolean'. Nó không có cách nào để biết rằng chức năng chỉ báo có thể là loại cụ thể. –

+1

@Rex Tôi đã định nghĩa phương thức 'collate' của riêng mình để pimp vào các bộ sưu tập giải quyết vấn đề này. Trên một 'List [A]' chữ ký sử dụng là 'collate [B] (fn: PartialFunction [A, B]): (Danh sách (B), List (A))', rõ ràng chữ ký * thực tế * là một chút hairier hơn vì tôi cũng đang sử dụng 'CanBuildFrom' –

5

Chữ ký của người bình thường được sử dụng collect trên, nói rằng, Seq, là

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

mà thực sự là một trường hợp đặc biệt của

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

Vì vậy, nếu bạn sử dụng nó trong chế độ mặc định, câu trả lời là không, chắc chắn không: bạn nhận được chính xác một chuỗi từ nó. Nếu bạn theo dõi CanBuildFrom qua Builder, bạn thấy rằng có thể thực hiện That thực sự là hai chuỗi, nhưng sẽ không có cách nào để được biết chuỗi nào một mục nên đi vào, vì chức năng một phần chỉ có thể nói "có, tôi thuộc về "hoặc" không, tôi không thuộc về ".

Vì vậy, bạn sẽ làm gì nếu bạn muốn có nhiều điều kiện dẫn đến danh sách của bạn được chia thành một loạt các phần khác nhau? Một cách là tạo hàm chỉ báo A => Int, trong đó A của bạn được ánh xạ vào một lớp được đánh số, sau đó sử dụng groupBy. Ví dụ:

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

Bây giờ bạn có thể tra cứu danh sách con theo lớp (0, 1 và 2 trong trường hợp này). Thật không may, nếu bạn muốn bỏ qua một số yếu tố đầu vào, bạn vẫn phải đặt chúng vào một lớp học (ví dụ: bạn có thể không quan tâm đến nhiều bản sao của None trong trường hợp này).

3

Tôi sử dụng tính năng này. Một điều tốt đẹp về nó là nó kết hợp phân vùng và ánh xạ trong một lần lặp. Một nhược điểm là nó phân bổ một loạt các đối tượng tạm thời (các Either.LeftEither.Right trường hợp)

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

tôi không thể tìm thấy một giải pháp đáp ứng cho vấn đề cơ bản ở đây. Tôi không cần một bài giảng trên collect và không quan tâm nếu đây là bài tập về nhà của ai đó. Ngoài ra, tôi không muốn một cái gì đó mà chỉ hoạt động cho List.

Vì vậy, đây là cú đâm của tôi vào đó. Hiệu quả và tương thích với bất kỳ TraversableOnce, thậm chí dây:

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

Ví dụ cách dùng:

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]) 
Các vấn đề liên quan