web-dev-qa-db-ja.com

関数型プログラミング、Scala map and fold left

左折の良いチュートリアルは何ですか?

他の回答のコンテキストを提供するために削除から復元された元の質問:

四角形、円、場所、およびすべてがShapeを拡張するグループのバウンディングボックスを見つけるためのメソッドを実装しようとしています。グループは基本的に図形の配列です

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を除く3つすべてについて境界ボックスを計算しました。したがって、バウンディングボックスメソッドでは、マップを使用してグループ用に左にフォールドする必要があることはわかっていますが、作成するための正確な構文を見つけることができません。

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

グループ境界ボックスは、基本的にすべての形状が囲まれた最小の境界ボックスです。

53
jon

ほぼ完全に異なる質問をするように編集したので、別の回答をします。地図と折り畳みに関するチュートリアルを指すのではなく、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) )
_

これが役に立つかもしれない理由はいろいろあります。たくさんの文字列があり、それぞれの長さを知りたい場合や、すべて大文字にしたい場合、逆方向にしたい場合があります。 one要素にしたいことをする関数がある場合、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のリストをそれに入れることができます一度に1つずつ、最後に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
_

さて、結構です、でもwhoが一番長いと知りたい場合はどうでしょうか? 1つの方法(おそらく最善ではありませんが、有用なパターンをよく示しています)は、長さ(整数)および主要な候補(文字列)の両方を運ぶことです。それをやってみましょう:

_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)の最初の部分になります。

しかし、このような場合には、フォールドを使用することは本当に望んでいません。 2つの文字列のうち長い方が必要な場合、最も自然な関数はmax:(String,String)=>Stringのような関数になります。どのように適用しますか?

この場合、デフォルトの「最短」ケースがあるため、「」で始まるstring-max関数を折りたたむことができます。しかし、より良い方法はreduceを使用することです。フォールドと同様に、2つのバージョンがあり、1つは左から機能し、もう1つは右から機能します。初期値は不要で、関数f:(A,A)=>Aが必要です。つまり、2つのことを受け取り、同じタイプの1つを返します。 string-max関数を使用した例を次に示します。

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

現在、さらに2つのトリックがあります。まず、次の2つは同じことを意味します。

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

2番目の方が短いことに注意してください。これは、_b0_を取り、それを使ってリストに対して何かをしているという印象を与えます。 (_:\_はfoldRightと同じですが、次のように使用します:_(list :\ b0) (f)_

次に、変数を一度だけ参照する場合は、変数名の代わりに___を使用し、匿名関数宣言の_x =>_部分を省略できます。以下に2つの例を示します。

_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を使用して関数を作成し、それらをマッピング、折りたたみ、および縮小できるはずです。したがって、アルゴリズムがどのように機能するかを知っている場合、それを実装するのは合理的で簡単なはずです。

262
Rex Kerr

基本的なアルゴリズムは次のようになります。

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

2つの形状を取り、それらを結合するboxBoundingに関しては、通常は長方形ですが、特定の状況では円にすることができます。とにかく、アルゴリズムがわかれば、それはかなり簡単です。

4

通常、境界ボックスは長方形です。 (-r、-r)にある円は半径rの円のバウンディングボックスだとは思いません。

とにかく、境界ボックスb1と別のb2と、b1とb2の境界ボックスを計算する関数combineBoxesがあるとします。

次に、グループ内にnon-empty形状のセットがある場合、reduceLeftを使用して、一度に2つを組み合わせて、境界ボックスのリストの境界ボックス全体を計算できます。巨大な箱が1つだけ残るまで。 (同じ考え方を使用して、数字のリストをペアで追加することで数字の合計に減らすことができます。また、リスト全体で左から右に機能するため、reduceLeftと呼ばれます。)

blistが各形状の境界ボックスのリストであるとします。 (ヒント:これはmapの出番です。)

_val bigBox = blist reduceLeft( (box1,box2) => combineBoxes(box1,box2) )
_

ただし、空のグループケースを個別にキャッチする必要があります。 (境界ボックスが明確に定義されていないため、折り畳みを使用したくありません。折り畳みは、デフォルトの空のケースが理にかなっている場合に適しています。または、Optionで折り畳む必要がありますが、結合関数はNoneSome(box)と結合する方法を理解する必要がありますが、この場合はおそらく価値がありませんが、さまざまな種類の空のリストの状況をエレガントに処理します。)

2
Rex Kerr