【问题标题】:Strange results when using Scala collections使用 Scala 集合时的奇怪结果
【发布时间】:2013-03-10 05:40:09
【问题描述】:

我有一些测试结果我无法完全解释。

第一个测试对包含 4 个元素的列表进行过滤、映射和归约:

{
    val counter = new AtomicInteger(0)
    val l = List(1, 2, 3, 4)
    val filtered = l.filter{ i =>
        counter.incrementAndGet()
        true
    }
    val mapped = filtered.map{ i =>
        counter.incrementAndGet()
        i*2
    }
    val reduced = mapped.reduce{ (a, b) =>
        counter.incrementAndGet()
        a+b
    }
    println("counted " + counter.get + " and result is " + reduced)
    assert(20 == reduced)
    assert(11 == counter.get)
}

计数器按我的预期增加了 11 倍:过滤期间每个元素一次,映射期间每个元素一次,以及 3 次以将 4 个元素相加。

使用通配符改变结果:

{
    val counter = new AtomicInteger(0)
    val l = List(1, 2, 3, 4)
    val filtered = l.filter{
        counter.incrementAndGet()
        _ > 0
    }
    val mapped = filtered.map{
        counter.incrementAndGet()
        _*2
    }
    val reduced = mapped.reduce{ (a, b) =>
        counter.incrementAndGet()
        a+b
    }
    println("counted " + counter.get + " and result is " + reduced)
    assert(20 == reduced)
    assert(5 == counter.get)
}

我不知道如何在 reduce 中使用通配符(代码无法编译),但现在,计数器只增加了 5 次!!

那么,问题 1:为什么通配符会改变调用计数器的次数以及它是如何工作的?

然后是我的第二个相关问题。我对视图的理解是它们会懒惰地执行传递给一元方法的函数,但是下面的代码没有显示出来。

{
    val counter = new AtomicInteger(0)
    val l = Seq(1, 2, 3, 4).view
    val filtered = l.filter{
        counter.incrementAndGet()
        _ > 0
    }
println("after filter: " + counter.get)
    val mapped = filtered.map{
        counter.incrementAndGet()
        _*2
    }
println("after map: " + counter.get)
    val reduced = mapped.reduce{ (a, b) =>
        counter.incrementAndGet()
        a+b
    }
println("after reduce: " + counter.get)
    println("counted " + counter.get + " and result is " + reduced)
    assert(20 == reduced)
    assert(5 == counter.get)
}

输出是:

after filter: 1
after map: 2
after reduce: 5
counted 5 and result is 20

问题 #2:为什么函数会立即执行?

我使用的是 Scala 2.10

【问题讨论】:

  • 你真的应该接受 Rex Kerr 的回答(而不仅仅是赞美)!
  • 我会的,但经验表明值得等待其他答案,这可能会更好!

标签: scala scala-collections


【解决方案1】:

你可能这么想

filter {
  println
  _ > 0
}

意思

filter{ i =>
  println
  i > 0
}

但 Scala 有其他想法。原因是

{ println; _ > 0 }

是一个语句,它首先打印一些东西,然后返回> 0 函数。因此,它将您正在做的事情解释为指定函数的一种有趣方式,相当于:

val p = { println; (i: Int) => i > 0 }
filter(p)

相当于

println
val temp = (i: Int) => i > 0   // Temporary name, forget we did this!
val p = temp
filter(p)

您可以想象它并不能完全按照您想要的方式进行 - 您只在开始时打印(或在您的情况下进行增量)一次。您的两个问题都源于此。

如果您使用下划线表示“填写参数”,请确保您只有一个表达式!如果您使用多个语句,最好坚持使用显式命名的参数。

【讨论】:

  • 有没有办法使用视图/流使 Scala 只对元素进行一次迭代,但将所有函数应用于元素,以获得相同的结果 (20)?还是您必须以不同的方式编写代码?
  • @JohnSmith - 过滤器、映射和归约具有足够不同的语义,不,您不能以任何简单的方式组合它们。您当然可以迭代一次,然后手动进行过滤、映射和归约。
  • @JohnSmith: .view 应该懒惰地过滤和映射元素。因此调用.reduce 应该只遍历列表一次。
  • 其他人告诉我,但测试没有同意,如果你查看源代码,我不确定它会不会。当然,它很懒,但它似乎仍然为每个单子函数迭代。
  • @JohnSmith - 是的,你是对的——view 不是这样工作的。在需要结果之前它不会进行评估,但如果您需要三个不同的结果,它不会将它们合并到一个通道中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-05
  • 1970-01-01
  • 1970-01-01
  • 2010-09-14
  • 2011-12-19
相关资源
最近更新 更多