题
在书“编程在Scala中”,第23章,作者得到类似的示例:
case class Book(title: String, authors: String*)
val books: List[Book] = // list of books, omitted here
// find all authors who have published at least two books
for (b1 <- books; b2 <- books if b1 != b2;
a1 <- b1.authors; a2 <- b2.authors if a1 == a2)
yield a1
作者说,这将转化为:
books flatMap (b1 =>
books filter (b2 => b1 != b2) flatMap (b2 =>
b1.authors flatMap (a1 =>
b2.authors filter (a2 => a1 == a2) map (a2 =>
a1))))
但如果你看地图,然后flatmap方法定义( TraversableLike.scala 的),你可能会发现,他们被定义为循环:
def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(repr)
b.sizeHint(this)
for (x <- this) b += f(x)
b.result
}
def flatMap[B, That](f: A => Traversable[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(repr)
for (x <- this) b ++= f(x)
b.result
}
好了,我想这对将继续被翻译成的foreach,然后翻译成while语句这是一个构建物中不表达,Scala没有一个用于构建,因为它希望为总是产生的东西。
所以,我想和你讨论的是,为什么斯卡拉做到这一点“对于翻译”?
笔者的例子中使用4个发电机组,这将被翻译成嵌套在端部线圈4的水平,我认为这将有很可怕的表现时,books
大。
斯卡拉鼓励人们使用这种“语法糖”,你总能看到代码,在很大程度上利用过滤器,地图和flatmap,这似乎是程序员忘记了自己真正要做的是在另一个内部嵌套一个循环,什么实现只是使代码看起来有点短。什么是你的主意吗?
解决方案
有关内涵是为一元变换语法糖,并且,这样,在各种各样的地方是有用的。在那个,它们更详细Scala中比同等的Haskell构建体(当然,Haskell是不严格的默认状态下,所以人们不能谈论像Scala中构建体的性能)。
同样重要的是,这个结构保持正在做什么,清楚,避免快速升级压痕或不必要的私有方法的嵌套。
至于最后考虑,是否隐藏了复杂性或没有,我会断定这样:
for {
b1 <- books
b2 <- books
if b1 != b2
a1 <- b1.authors
a2 <- b2.authors
if a1 == a2
} yield a1
这是很容易看到正在做什么,和复杂性是明确的:乙^ 2 * A ^ 2(过滤器不会改变的复杂性),对书和作者的数字。现在,用Java编写相同的代码,无论是深的压痕或私有方法,并设法确定,在快速浏览一下,该代码的复杂性是什么。
那么,恕我直言,这不能掩盖的复杂性,但是,相反,清楚。
对于map
/ flatMap
/ filter
定义你提到,他们不属于List
或者任何其它类,所以他们不会被应用。基本上,
for(x <- List(1, 2, 3)) yield x * 2
被翻译成
List(1, 2, 3) map (x => x * 2)
和是不一样的东西
map(List(1, 2, 3), ((x: Int) => x * 2)))
这是如何传递的定义将被调用。对于记录,上map
List
的实际实现是:
def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
val b = bf(repr)
b.sizeHint(this)
for (x <- this) b += f(x)
b.result
}
其他提示
我写的代码,这样很容易理解和维护。那么我的个人资料。如果有一个瓶颈,那就是我投入了我的注意。如果是在像你描述的,我会以不同的方式攻击问题。在此之前,我爱的“糖”。它节省了我写的东西出来或认真思考它的麻烦。
有实际上6环。每个滤波器/ flatMap /地图一个环
在过滤器 - >地图对可在一个循环中通过使用集合懒惰观点完成(迭代法)
在一般情况下,TT运行2个嵌套循环书籍找到所有的书对,然后两个嵌套循环以查找是否一种书的作者是在其他作者的名单。
使用简单的数据结构,你会明确地编码时一样。
当然,这里的实例是显示了复杂的“for”循环,不写的最有效的代码。例如,代替作者的序列,可以使用一组,然后找到若交点是一个非空:
for (b1 <- books; b2 <- books; a <- (b1.authors & b2.authors)) yield a
注意,在2.8中,呼叫filter
改变为withFilter
这是懒惰,并能避免构建的中间结构。请参见指南,从过滤器移动到withFilter?。
相信for
被翻译为map
,flatMap
和withFilter
(以及值定义如果存在的话)的原因是为了使利用单子的更容易。
在一般我认为,如果你正在做的计算涉及循环4次,这是一个使用for
循环罚款。如果计算可以更有效地完成和性能是非常重要的,那么你应该使用更高效的算法。
一个后续@ IttayD对算法的效率答案。值得一提的是,在原来的职位(并在书中)的算法是嵌套循环连接即可。在实践中,这是不适合大型数据集的高效算法,和大多数数据库会使用散聚合这里来代替。在Scala中,哈希聚集看起来是这样的:
(for (book <- books;
author <- book.authors) yield (book, author)
).groupBy(_._2).filter(_._2.size > 1).keys