具有两个条件的Scala List.filter,仅应用一次

时间:2012-08-29 17:48:25

标签: scala

不知道这是否可行,但我有一些这样的代码:

val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
val evens = list.filter { e => e % 2 == 0 }

if(someCondition) {
  val result = evens.filter { e => e % 3 == 0 }
} else {
  val result = evens.filter { e => e % 5 == 0 }
}

但是我不想迭代所有元素两次,所以有没有办法可以在这个集合上创建一个“泛型选择全部数字”并应用其他一些函数,这样就可以了只迭代一次?

4 个答案:

答案 0 :(得分:26)

如果您将list变为惰性集合(例如Iterator),则可以在一次传递中应用所有过滤操作(或其他内容,如map等):< / p>

val list = (1 to 12).toList
val doubleFiltered: List[Int] =
  list.iterator
    .filter(_ % 2 == 0)
    .filter(_ % 3 == 0)
    .toList
println(doubleFiltered)

当您使用.iterator将集合转换为迭代器时,Scala将跟踪要执行的操作(此处为两个filter),但是等待执行它们,直到结果为止实际访问过(这里,通过调用.toList)。

所以我可能会像这样重写你的代码:

val list = (1 to 12).toList
val evens = list.iterator.filter(_ % 2 == 0)

val result = 
  if(someCondition)
    evens.filter(_ % 3 == 0)
  else
    evens.filter(_ % 5 == 0)

result foreach println

根据您要执行的操作,您可能需要IteratorStreamView。它们都是懒惰计算的(因此一次通过方面将适用),但它们在诸如是否可以多次迭代(StreamView)之类的事情上是不同的,或者它们是否保持计算值以后访问(Stream)。

要真正看到这些不同的懒惰行为,请尝试运行此代码并将<OPERATION>设置为toListiteratorviewtoStream

val result =
  (1 to 12).<OPERATION>
    .filter { e => println("filter 1: " + e); e % 2 == 0 }
    .filter { e => println("filter 2: " + e); e % 3 == 0 }
result foreach println
result foreach println

以下是您将看到的行为:

  • List(或任何其他非惰性集合):每个filter都需要在集合中单独迭代。生成的过滤集合存储在内存中,以便每个foreach只能显示它。
  • Iteratorfilter和第一个foreach都在一次迭代中完成。第二个foreach没有做任何事情,因为已经消耗了Iterator。结果不会存储在内存中。
  • View:两个foreach调用都会在集合上进行自己的单遍迭代,以执行filters。结果不会存储在内存中。
  • Streamfilter和第一个foreach都在一次迭代中完成。生成的过滤集合存储在内存中,以便每个foreach只能显示它。

答案 1 :(得分:8)

您可以使用功能组合。 someCondition在决定使用哪个函数时只调用一次:

def modN(n: Int)(xs: List[Int]) = xs filter (_ % n == 0)

val f = modN(2) _ andThen (if (someCondition) modN(3) else modN(5))

val result = f(list)

(这不是你想要的 - 它仍然遍历列表两次)

这样做:

val f: Int => Boolean = if (someCondition) { _ % 3 == 0 } else { _ % 5 == 0 }
val result = list filter (x => x % 2 == 0 && f(x))

或者更好:

val n = if (someCondition) 3 else 5
val result = list filter (x => x % 2 == 0 && x % n == 0)

答案 2 :(得分:2)

这不会起作用:

list.filter{e => e % 2 == 0 && (if (someCondition) e % 3 == 0 else e % 5 == 0)}

同样FYI e % 2 == 0将为您提供所有偶数,除非您出于其他原因命名val odds

答案 3 :(得分:1)

您只需在过滤器中写下两个条件:

val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)

var result = List(0)
val someCondition = true

result = if (someCondition) list.filter { e => e % 2 == 0 && e % 3 == 0 }
         else               list.filter { e => e % 2 == 0 && e % 5 == 0 }