【问题标题】:Scala List.filter with two conditions, applied only onceScala List.filter 有两个条件,只应用一次
【发布时间】:2012-10-05 11:38:04
【问题描述】:

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

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

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

【问题讨论】:

  • 在您的示例中,只会对初始列表进行一次迭代,其他过滤器将应用于新的过滤(较小)集合,您能否说明您想要实现的目标

标签: scala


【解决方案1】:

如果您将list 转换为惰性集合,例如Iterator,那么您可以一次性应用所有过滤操作(或其他类似map 等):

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

当您使用.iterator 将集合转换为迭代器时,Scala 将跟踪要执行的操作(这里是两个filters),但会等待执行它们直到实际访问结果(这里,通过调用.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 都可以显示它。
  • Iteratorfilters 和第一个 foreach 都是在一次迭代中完成的。第二个foreach 什么都不做,因为Iterator 已被消耗。结果不存储在内存中。
  • View:两个foreach 调用都会导致它们自己对集合进行单遍迭代以执行filters。结果不存储在内存中。
  • Streamfilters 和第一个 foreach 都是在一次迭代中完成的。结果过滤后的集合存储在内存中,以便每个foreach 都可以显示它。

【讨论】:

  • 在我的测试中,只有“视图”迭代一次。当我在可遍历集合上调用 toIterator 时,它会在我调用 toList 之前遍历集合。仅使用“视图”我已经看到它仅在“toList”调用之后迭代。但是
  • +1 很好的答案,谢谢。 JVM 性能通常非常好,以至于您不会注意到低效的 List 操作...直到您这样做 ;-) someList.filter(x=> otherList.contains(x.id)) 的迭代器版本至少快 2 倍.
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    这行不通:

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

    FYI e % 2 == 0 也会给你所有的偶数,除非你出于其他原因将 val odds 命名。

    【讨论】:

    • 不会someCondition 被检查list.size 次吗?
    • 当然,我假设它只是一个值。如果是函数调用或复杂操作,则结果可以在过滤操作之前设置为 val。
    • 只有当语句的第一部分e % 2 == 0为真时才会检查。
    • 我会将条件提取为val n = if (condition) 3 else 5; list.filter(e =&gt; e%2==0 &amp;&amp; e%n==0) 之类的东西(当然,只有在条件稳定的情况下才会起作用)
    【解决方案4】:

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

    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 }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-30
      • 1970-01-01
      • 2017-01-11
      • 1970-01-01
      • 2018-04-04
      • 1970-01-01
      相关资源
      最近更新 更多