【问题标题】:Functional programming, Scala map and fold left [closed]函数式编程,Scala映射和左折叠[关闭]
【发布时间】:2011-01-18 14:42:07
【问题描述】:

有哪些关于左折叠的好教程?

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

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

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  

除了第一组之外,我计算了所有三个的边界框。所以现在对于边界框方法,我知道我应该为 Group 使用 map 和 fold left,但我就是找不到创建它的确切语法。

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 + b.x, y + b.y, b.shape)  
    }  
    case Group(shapes @ _*) =>  ( /: shapes) { } // i dont know how to proceed here.
  }
}

组包围盒基本上是所有形状都被包围的最小包围盒。

【问题讨论】:

  • 你和汤姆在同一个班?见stackoverflow.com/questions/2274852/…
  • 这不是关于 Scala 和foldLeft 的问题。这是一个关于算法的问题。您最好问“如何使用不可变数据结构从形状列表中计算最小边界框?”。将问题标记为与语言无关和算法。也许是函数式编程。如果你在实现 Scala 中建议的算法时遇到问题,那么你可以打开一个关于它的 Scala 问题。目前的问题针对的是错误的群体。

标签: scala map functional-programming fold


【解决方案1】:

既然您已经编辑了一个几乎完全不同的问题,我将给出一个不同的答案。与其指向关于地图和折叠的教程,我只提供一个。

在 Scala 中,您首先需要知道如何创建匿名函数。它是这样的,从最一般到更具体:

(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) )

这可能有用的原因有很多。也许你有一堆字符串,你想知道每个字符串有多长,或者你想让它们全部大写,或者你想让它们倒过来。如果您有一个函数可以对 一个 元素执行您想要的操作,则 map 将对所有元素执行此操作:

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)

所以,这是一般的地图,在 Scala 中也是如此。

但是如果我们想收集我们的结果呢?这就是 fold 的用武之地(foldLeft 是从左侧开始并向右工作的版本)。

假设我们有一个函数f:(B,A) => B,也就是说,它需要一个 B 和一个 A,并将它们组合起来产生一个 B。好吧,我们可以从一个 B 开始,然后将我们的 A 列表输入其中一个一次,最后,我们会得到一些 B。这正是 fold 所做的。 foldLeft 从列表的左端开始; foldRight 从右边开始。也就是说,

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

生产

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

b0 当然是你的初始值。

所以,也许我们有一个函数,它接受一个 int 和一个字符串,并返回 int 或字符串的长度,以较大者为准——如果我们使用它折叠我们的列表,它会告诉我们最长的字符串 (假设我们从 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

好的,很好,但是如果我们想知道最长呢?一种方法(也许不是最好的,但它很好地说明了一个有用的模式)是同时携带长度(整数)领先的竞争者(字符串)。让我们试一试:

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 现在是 (Int,String) 类型的元组,i._1 是该元组的第一部分(一个 Int)。

但在某些情况下,使用折叠并不是我们真正想要的。如果我们想要两个字符串中较长的一个,最自然的函数就是max:(String,String)=&gt;String。我们如何应用它?

好吧,在这种情况下,有一个默认的“最短”情况,所以我们可以折叠以“”开头的 string-max 函数。但更好的方法是使用reduce。与折叠一样,有两个版本,一个从左侧工作,另一个从右侧工作。它没有初始值,需要一个函数f:(A,A)=&gt;A。也就是说,它需要两个东西并返回一个相同类型的东西。下面是一个字符串最大值函数的例子:

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)

其次,如果你只引用一个变量一次,你可以使用_代替变量名并省略匿名函数声明中的x =&gt;部分。这里有两个例子:

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

此时,您应该能够使用 Scala 创建函数并映射、折叠和归约它们。因此,如果您知道您的算法应该如何工作,那么实现它应该相当简单。

【讨论】:

  • +1 我希望我能投两次票..
  • 完美答案,这对我帮助很大
  • 我发现使用花括号将函数括起来的语法更加清晰,有什么理由更喜欢其中一个吗?例如:list.foldLeft(0){ (i, s) =&gt; i max s.length }list.foldLeft(0)((i, s) =&gt; i max s.length )
  • @weezybizzle - 如果您使用花括号,则可以使用 case 语句和分号。否则,不,这只是一种风格偏好。我倾向于在需要时保存大括号,以及何时帮助我进行视觉解析(大括号对我来说意味着“案例或多语句或其他不太小的东西”)。
  • 我现在在 Coursera 上上 Odersky 的课程,并且多次使用 foldLeft 但总是很挣扎,但你的解释终于让我明白了,做得好
【解决方案2】:

基本算法是这样的:

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,这是一个纯粹的算法问题而不是语言问题。

如果所有形状都具有相同的中心,则实现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,它有两个形状并组合在一起,通常是一个矩形,但在某些情况下可以是一个圆形。无论如何,一旦你弄清楚了算法,它就很简单了。

【讨论】:

  • 您拥有的 Group 类的 contains 方法对计算边界框没有帮助(无论您是否坚持认为它是一个框)。一个点 x 包含在 a1 U a2 U ... U aN 中,当且仅当存在一个 aI 使得 x 在 aI 中。 forall 要求 x 出现在每个对象中(当然,您需要整个对象,而不是每个点)。您至少可以保守地使用 find 而不是实际计算联合。但是,除此之外,我认为这是一个关于如何使用 Scala 的具有启发性的示例。
  • 是的,我遵循了那部分。我只是反对您修复的forall——正确地使用exists,而不是像我建议的那样使用find 不太有用。
  • @Rex 啊,好的。现在我再次阅读您的评论,我意识到您在谈论Groupcontains,而不是各种contains 上的Group。 :-)
【解决方案3】:

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

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

然后,如果您的组中有一组 非空 形状,您可以使用 reduceLeft 通过一次组合两个边界框来计算边界框列表的整个边界框直到只剩下一个巨大的盒子。 (同样的想法可以通过成对相加来将一个数字列表减少为一个数字的总和。它被称为reduceLeft,因为它在列表中从左到右工作。)

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

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

但是,您需要单独捕获空组案例。 (因为它没有明确定义的边界框,所以您不想使用折叠;当存在有意义的默认空案例时,折叠非常有用。或者您必须使用 Option 折叠,但随后您的组合函数必须了解如何将NoneSome(box) 结合起来,这在这种情况下可能不值得——但如果您正在编写需要优雅地处理各种空列表情况的生产代码,则很有可能。)

【讨论】:

  • 您的问题似乎不仅在于您不了解 Scala 语法。首先,弄清楚数学上应该发生什么。然后担心如何用语言把它写下来。使用正确的语法做错误的事情是没有帮助的!您需要一个可以接受两个边界框并输入并产生两个边界框作为输出的函数或方法。 A.x + R.x 不会把角落放在你想要的地方。如果你画一张图并算出数学,你就大功告成了。
猜你喜欢
  • 2020-10-06
  • 2017-04-11
  • 2021-06-24
  • 2022-07-14
  • 2018-12-03
  • 2010-11-06
  • 1970-01-01
  • 2011-09-04
  • 2023-03-19
相关资源
最近更新 更多