2013-07-01 29 views
5

Tôi có một mã như:scala, lọc một bộ sưu tập dựa trên một số điều kiện

val strs = List("hello", "andorra", "trab", "world") 
def f1(s: String) = !s.startsWith("a") 
def f2(s: String) = !s.endsWith("b") 

val result = strs.filter(f1).filter(f2) 

bây giờ, f1 và f2 nên được áp dụng dựa trên một điều kiện, chẳng hạn như:

val tmp1 = if (cond1) strs.filter(f1) else strs 
val out = if (cond2) tmp1.filter(f2) else tmp1 

là có cách nào tốt hơn để làm điều này, mà không sử dụng biến tạm thời tmp1?

một cách sẽ để lọc dựa trên một danh sách các chức năng, chẳng hạn như:

val fs = List(f1 _,f2 _) 
fs.foldLeft(strs)((fn, list) => list.filter(fn)) 

nhưng sau đó tôi sẽ cần phải xây dựng một danh sách các chức năng dựa trên các điều kiện (và do đó, tôi sẽ chuyển vấn đề sử dụng biến danh sách chuỗi tạm thời, để sử dụng biến danh sách hàm tạm thời (hoặc tôi cần phải sử dụng danh sách có thể thay đổi)).

Tôi đang tìm kiếm một cái gì đó như thế này (tất nhiên điều này không biên dịch, nếu không tôi sẽ đã có câu trả lời cho các câu hỏi):

val result = 
    strs 
    .if(cond1, filter(f1)) 
    .if(cond2, filter(f2)) 
+0

Có vẻ như bạn muốn danh sách các bộ dữ liệu có (condition, filterPredicate). Sau đó, bạn có thể lọc danh sách này dựa trên điều kiện có hay không (ví dụ: _._ 1) giữ. Bây giờ bạn có một danh sách các hàm bạn muốn áp dụng. Sau đó, bạn có thể ánh xạ chuỗi này bằng chuỗi và giảm bằng && (logic và). Xin lỗi nếu nó quá lượn sóng. – Felix

+0

cảm ơn, đó cũng là một ý tưởng hay, nhưng tôi đang tìm kiếm một cái gì đó giống như câu trả lời của noah. –

Trả lời

8

Bạn có thể dễ dàng sử dụng một lớp ngầm để cung cấp cho bạn cú pháp sau:

val strs = List("hello", "andorra", "trab", "world") 

    def f1(s: String) = !s.startsWith("a") 

    def f2(s: String) = !s.endsWith("b") 

    val cond1 = true 
    val cond2 = true 

    implicit class FilterHelper[A](l: List[A]) { 
    def ifFilter(cond: Boolean, f: A => Boolean) = { 
     if (cond) l.filter(f) else l 
    } 
    } 

    strs 
    .ifFilter(cond1, f1) 
    .ifFilter(cond2, f2) 

res1: List[String] = List(hello, world) 

Tôi đã sử dụng if làm tên phương thức nhưng đó là từ dành riêng.

+0

đó là những gì tôi đang tìm kiếm; sau đó tôi có thể xử lý nó trong nhiều bước như trong strs.filter (...). map (...). ifFilter (...). map (...). ifFilter (...). Tôi sẽ chỉ thay đổi "l.filter (a =>! Cond || filter (a))" by "if (cond) l.filter (bộ lọc) khác l" để tránh sử dụng bộ lọc với điều kiện luôn luôn đúng. –

+0

Tôi đã cập nhật mã để phản ánh thay đổi, không cần phải đi qua khi bạn không cần. – Noah

3

Bạn có thể thực hiện việc này bằng cách cộng các hàm vị ngữ của bạn.

Quan sát rằng một vị bộ lọc, A => Boolean, có một hoạt động append:

def append[A](p1: A => Boolean, p2: A => Boolean): A => Boolean = 
    a => p1(a) && p2(a) 

Và một giá trị bản sắc:

def id[A]: A => Boolean = 
    _ => true 

mà thỏa mãn điều kiện là đối với bất kỳ vị p: A => Boolean, append(p, id) === p.

Điều này giúp đơn giản hóa vấn đề bao gồm/loại trừ vị từ dựa trên điều kiện: nếu điều kiện là sai, chỉ cần bao gồm biến vị ngữ id. Nó không ảnh hưởng đến bộ lọc vì bộ lọc luôn trả về true.

Tổng hợp các vị từ:

def sum[A](ps: List[A => Boolean]): A => Boolean = 
    ps.foldLeft[A => Boolean](id)(append) 

Lưu ý rằng chúng tôi gấp lên id, vì vậy nếu ps là trống rỗng, chúng tôi nhận được vị tính, ví dụ: một bộ lọc mà không làm gì, như bạn mong muốn.

Đưa tất cả điều này với nhau:

val predicates = List(cond1 -> f1 _, cond2 -> f2 _) 

strs.filter(sum(predicates.collect { case (cond, p) if cond => p })) 
// List(hello, world) 

Lưu ý rằng danh sách strs chỉ đi qua một lần.


Bây giờ, đối với phiên bản Scalaz những điều trên:

val predicates = List(cond1 -> f1 _, cond2 -> f2 _) 

strs filter predicates.foldMap { 
    case (cond, p) => cond ?? (p andThen (_.conjunction)) 
} 
// List("hello", "world") 
+0

vấn đề là nếu cond1 = false, bạn đang áp dụng một cái gì đó như "strs.filter (true)", mà là chậm hơn so với chỉ trở về danh sách chính nó. –

+1

David: nhưng sau đó, nếu cả hai điều kiện là đúng, chúng tôi vẫn chỉ áp dụng 'bộ lọc' một lần. Đối với các vị từ 'n', giải pháp được chấp nhận của bạn sẽ chạy' filter' tối đa 'n' lần! –

+0

Bạn có nói rằng các hoạt động/chức năng 'idend' và nhận dạng' id' tồn tại, hay chỉ là chúng có thể được định nghĩa? Tôi không thể tìm thấy chúng trong Scala 2.10. –

3

@ Noah của câu trả lời là tốt, và bạn có thể mang nó và khái quát hóa nó hơn nữa nếu bạn muốn để có thể thực hiện bất kỳ loại hành động trên một danh sách sau đó trả về một danh sách mới được đưa ra một điều kiện nếu bạn thực hiện các thay đổi sau:

implicit class FilterHelper[A](l: List[A]) { 
    def ifthen[B](cond: Boolean, f:(List[A]) => List[B]) = { 
    if (cond) f(l) else l 
    } 
} 

sau đó sử dụng nó như thế này:

val list = List("1", "2")  
val l2 = list.ifthen(someCondition, _.filter(f1) 
val l3 = list.ifthen(someOtherCondition, _.map(_.size)) 
2

Nó sẽ là khá đơn giản để chỉ bao gồm các điều kiện trong khối của bạn cho bộ lọc, như vậy:

val result = strs filter (x => !cond1 || f1(x)) filter (x => !cond2 || f2(x)) 

theo đó kết quả sẽ áp dụng một bộ lọc nếu điều kiện được đáp ứng, hoặc chỉ đơn giản là trả lại cùng danh sách.

+0

Có, nhưng tôi nghĩ rằng điểm là lý do tại sao thậm chí áp dụng các chức năng nếu nếu sẽ không làm bất cứ điều gì. Tại sao lặp lại thông qua danh sách (nói nó rất lớn) nếu bạn không phải làm thế? – cmbaxter

+0

Có thể viết 'strs filter {x => (! Cond1 || f1 (x)) && (! Cond2 || f2 (x))}' để bạn chỉ lướt qua một lần –

Các vấn đề liên quan