函数式编程,斯卡拉地图和折叠左

什么是一些好的教程折叠左?

原始问题,从删除恢复为其他答案提供上下文:

我正在尝试实现一种方法来查找矩形,圆形,位置和所有扩展形状的组的方法。 组基本上是一个形状的数组

abstract class Shape case class Rectangle(width: Int, height: Int) extends Shape case class Location(x: Int, y: Int, shape: Shape) extends Shape case class Circle(radius: Int) extends Shape case class Group(shape: Shape*) extends Shape 

我得到了除了组1之外的所有三个边界框。 所以,现在对于边界框方法,我知道我应该使用映射,并向左折叠组,但我无法find创build它的确切语法。

 object BoundingBox { def boundingBox(s: Shape): Location = s match { case Circle(c)=> new Location(-c,-c,s) case Rectangle(_, _) => new Location(0, 0, s) case Location(x, y, shape) => { val b = boundingBox(shape) Location(x + bx, y + by, b.shape) } case Group(shapes @ _*) => ( /: shapes) { } // i dont know how to proceed here. } } 

组边界框基本上是所有封闭形状的最小边界框。

现在你已经编辑了一个几乎完全不同的问题,我会给出一个不同的答案。 而不是指向地图和折叠教程,我只会给一个。

在斯卡拉,你首先需要知道如何创build一个匿名函数。 从最一般的情况来看,情况就是如此:

 (var1: Type1, var2: Type2, ..., varN: TypeN) => /* output */ (var1, var2, ..., varN) => /* output, if types can be inferred */ var1 => /* output, if type can be inferred and N=1 */ 

这里有些例子:

 (x: Double, y: Double, z: Double) => Math.sqrt(x*x + y*y + z*z) val f:(Double,Double)=>Double = (x,y) => x*y + Math.exp(-x*y) val neg:Double=>Double = x => -x 

现在,列表的map方法将对地图的每个元素应用一个函数(匿名或其他)。 那就是,如果你有的话

 List(a1,a2,...,aN) f:A => B 

然后

 List(a1,a2,...,aN) map (f) 

产生

 List( f(a1) , f(a2) , ..., f(aN) ) 

有各种各样的原因为什么这可能是有用的。 也许你有一串string,你想知道每一个是多长,或者你想使他们全部大写,或者你想他们倒退。 如果你有一个function,你想要一个元素,地图会做到所有元素:

 scala> List("How","long","are","we?") map (s => s.length) res0: List[Int] = List(3, 4, 3, 3) scala> List("How","capitalized","are","we?") map (s => s.toUpperCase) res1: List[java.lang.String] = List(HOW, CAPITALIZED, ARE, WE?) scala> List("How","backwards","are","we?") map (s => s.reverse) res2: List[scala.runtime.RichString] = List(woH, sdrawkcab, era, ?ew) 

所以,这是一般的地图,在斯卡拉。

但是如果我们想收集我们的结果呢? 这就是折叠进来的地方( foldLeft是从左边开始并且正确的版本)。

假设我们有一个函数f:(B,A) => B ,也就是说,它需要一个B和一个A,并且将它们结合起来产生一个B.那么我们可以从一个B开始,然后提供我们的列表A一次一个地进入,最后,我们会有一些B.这正是折叠所做的。 foldLeft从列表的左端开始; foldRight从右侧开始。 那是,

 List(a1,a2,...,aN) foldLeft(b0)(f) 

产生

 f( f( ... f( f(b0,a1) , a2 ) ... ), aN ) 

其中b0当然是你的初始值。

所以,也许我们有一个函数,它接受一个int和一个string,并返回int或string的长度,以较大者为准 – 如果我们使用它来折叠列表,它会告诉我们最长的string(假设我们从0开始)。 或者我们可以将长度添加到int中,随着我们的进行累积值。

试一试吧。

 scala> List("How","long","is","longest?").foldLeft(0)((i,s) => i max s.length) res3: Int = 8 scala> List("How","long","is","everyone?").foldLeft(0)((i,s) => i + s.length) res4: Int = 18 

好的,但是,如果我们想知道是最长的呢? 一种方式(可能不是最好的,但它很好地说明了一个有用的模式)是携带长度(一个整数) 领先竞争者(一个string)。 让我们一起去吧:

 scala> List("Who","is","longest?").foldLeft((0,""))((i,s) => | if (i._1 < s.length) (s.length,s) | else i | ) res5: (Int, java.lang.String) = (8,longest?) 

在这里, i现在是一个types为(Int,String)的元组,并且i._1是该元组的第一部分(一个Int)。

但在这种情况下,使用折叠并不是我们想要的。 如果我们想要两个string中较长的一个,最自然的函数将是一个像max:(String,String)=>String 。 我们如何应用那个?

那么,在这种情况下,就有一个默认的“最短”的情况,所以我们可以折叠以“”开始的string-max函数。 但更好的方法是使用reduce 。 与折叠一样,有两个版本,一个从左边开始工作,另一个从右边开始。 它不需要初始值,并且需要函数f:(A,A)=>A 也就是说,它需要两件事情,并返回一个相同的types。 这里是一个string-max函数的例子:

 scala> List("Who","is","longest?").reduceLeft((s1,s2) => | if (s2.length > s1.length) s2 | else s1 | ) res6: java.lang.String = longest? 

现在,只有两个技巧。 首先,以下两个意思是相同的事情:

 list.foldLeft(b0)(f) (b0 /: list)(f) 

注意第二个是如何缩短的,它给人的印象是你正在服用b0并用它来做一些事情(你是谁)。 ( :\foldRight是一样的,但是你可以像这样使用它: (list :\ b0) (f)

其次,如果只引用一个variables,则可以使用_而不是variables名,并省略匿名函数声明的x =>部分。 这里有两个例子:

 scala> List("How","long","are","we?") map (_.length) res7: List[Int] = List(3, 4, 3, 3) scala> (0 /: List("How","long","are","we","all?"))(_ + _.length) res8: Int = 16 

在这一点上,您应该能够创build函数并映射,折叠和使用Scala减less它们。 因此,如果你知道你的algorithm应该如何工作,那么实现它应该是相当简单的。

基本的algorithm会是这样的:

 shapes.tail.foldLeft(boundingBox(shapes.head)) { case (box, shape) if box contains shape => box case (box, shape) if shape contains box => shape case (box, shape) => boxBounding(box, shape) } 

现在你必须写containsboxBounding ,这是一个纯粹的algorithm问题,而不是一个语言问题。

如果形状都具有相同的中心,则执行contains将会更容易。 它会这样:

 abstract class Shape { def contains(s: Shape): Boolean } case class Rectangle(width: Int, height: Int) extends Shape { def contains(s: Shape): Boolean = s match { case Rectangle(w2, h2) => width >= w2 && height >= h2 case Location(x, y, s) => // not the same center case Circle(radius) => width >= radius && height >= radius case Group(shapes @ _*) => shapes.forall(this.contains(_)) } } case class Location(x: Int, y: Int, shape: Shape) extends Shape { def contains(s: Shape): Boolean = // not the same center } case class Circle(radius: Int) extends Shape { def contains(s: Shape): Boolean = s match { case Rectangle(width, height) => radius >= width && radius >= height case Location(x, y) => // not the same center case Circle(r2) => radius >= r2 case Group(shapes @ _*) => shapes.forall(this.contains(_)) } } case class Group(shapes: Shape*) extends Shape { def contains(s: Shape): Boolean = shapes.exists(_ contains s) } 

至于boxBounding ,它将两个形状结合起来,通常是一个矩形,但在某些情况下可以是一个圆形。 无论如何,一旦你算出algorithm,这是相当直接的。

边界框通常是一个矩形。 我不认为位于(-r,-r)的圆是半径为r的圆的边界框。

无论如何,假设您有一个边界框b1和另一个b2以及计算b1和b2的边界框的函数combineBoxes

然后,如果组中有一组非空的形状,则可以使用reduceLeft来计算边界框列表的整个边界框,方法是一次组合两个边界框,直到只剩下一个巨型框。 (同样的想法可以用来将一个数字列表减less到一个数字的总和,它们被称为reduceLeft因为它在列表中从左到右。

假设blist是每个形状的边界框的列表。 (提示:这是map进入的地方。)然后

 val bigBox = blist reduceLeft( (box1,box2) => combineBoxes(box1,box2) ) 

但是,您需要分别捕获空的组案例。 (因为它没有明确的边界框,所以你不想使用折叠;当有一个默认的空白的情况下,折叠是有用的,或者你必须折叠Option ,但是你的组合函数有了解如何将NoneSome(box)结合起来,这在这种情况下可能是不值得的 – 但是,如果您正在编写需要优雅地处理各种空列表情况的生产代码,那么这很好。