Scala List.filter 有两个条件,只应用一次

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12183651/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:28:55  来源:igfitidea点击:

Scala List.filter with two conditions, applied only once

scala

提问by Maurício Szabo

Don't know if this is possible, but I have some code like this:

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

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

But I don't want to iterate over all elements twice, so is there a way that I can create a "generic pick-all-the-evens numbers on this collection" and apply some other function, so that it would only iterate once?

但是我不想对所有元素迭代两次,所以有没有办法可以在这个集合上创建一个“通用选择所有偶数”并应用其他一些函数,这样它只会迭代一次?

回答by dhg

If you turn listinto a lazy collection, such as an Iterator, then you can apply all the filter operations (or other things like mapetc) in one pass:

如果你变成list了一个惰性集合,比如Iterator,那么你可以map一次性应用所有过滤操作(或其他类似的东西):

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

When you convert the collection to an Iterator with .iterator, Scala will keep track of the operations to be performed (here, two filters), but will wait to perform them until the result is actually accessed (here, via the call to .toList).

当您使用 将集合转换为 Iterator 时.iterator,Scala 将跟踪要执行的操作(此处为两个filters),但会等到实际访问结果(此处通过调用.toList)时才执行它们。

So I might rewrite your code like this:

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

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

Depending on exactly what you want to do, you might want an Iterator, a Stream, or a View. They are all lazily computed (so the one-pass aspect will apply), but they differ on things like whether they can be iterated over multiple times (Streamand View) or whether they keep the computed value around for later access (Stream).

根据您想要做什么,您可能需要 an Iterator、 aStream或 a View。它们都是惰性计算的(因此将应用单遍方面),但它们在诸如是否可以多次迭代(StreamView)或是否保留计算值以供以后访问(Stream)等方面有所不同。

To really see these different lazy behaviors, try running this bit of code and set <OPERATION>to either toList, iterator, view, or toStream:

要真正看到这些不同的懒惰行为,尝试运行的代码,并设置该位<OPERATION>要么toListiteratorview,或toStream

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

Here's the behavior you will see:

这是您将看到的行为:

  • List(or any other non-lazy collection): Each filteris requires a separate iteration through the collection. The resulting filtered collection is stored in memory so that each foreachcan just display it.
  • Iterator: Both filters and the first foreachare done in a single iteration. The second foreachdoes nothing since the Iteratorhas been consumed. Results are not stored in memory.
  • View: Both foreachcalls result in their own single-pass iteration over the collection to perform the filters. Results are not stored in memory.
  • Stream: Both filters and the first foreachare done in a single iteration. The resulting filtered collection is stored in memory so that each foreachcan just display it.
  • List(或任何其他非惰性集合):每个filter都需要通过集合进行单独的迭代。生成的过滤集合存储在内存中,以便每个人都foreach可以显示它。
  • Iterator: filters 和 firstforeach都是在一次迭代中完成的。foreach由于Iterator已被消耗,第二个什么也不做。结果不存储在内存中。
  • View:这两个foreach调用都会导致它们自己对集合进行单遍迭代以执行filters. 结果不存储在内存中。
  • Stream: filters 和 firstforeach都是在一次迭代中完成的。生成的过滤集合存储在内存中,以便每个人都foreach可以显示它。

回答by Luigi Plinge

You could use function composition. someConditionhere is only called once, when deciding which function to compose with:

您可以使用函数组合。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)
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)

(This doesn't do what you want - it still traverses the list twice)

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

Just do this:

只需这样做:

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

or maybe better:

或者更好:

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

回答by Dan Simon

Wouldn't this work:

这行不通:

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

also FYI e % 2 == 0is going to give you all the even numbers, unless you're naming the val oddsfor another reason.

FYIe % 2 == 0也会为您提供所有偶数,除非您odds出于其他原因命名 val 。

回答by defhlt

You just write two conditions in the filter:

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

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 }