Функциональное программирование, Scala отображение и сложение влево

Какими хорошими учебниками на сгибе осталось?

Оригинальный вопрос, восстановленный из удаления, чтобы предоставить контекст для других ответов:

Я пытаюсь реализовать метод для поиска окна будильника прямоугольника, круга, местоположения и группы, которая все расширяет форму. Группа в основном представляет собой массив фигур

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  

Я получил ограничительную рамку, рассчитанную для всех трех, кроме первой. Итак, теперь для метода ограничивающей рамки я знаю, что я должен использовать карту и складывать слева для группы, но я просто не могу найти точный синтаксис ее создания.

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

Групповая ограничивающая рамка - это в основном самый маленький ограничивающий прямоугольник со всеми включенными формами.

Ответ 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) )

Есть все основания, почему это может быть полезно. Возможно, у вас есть куча строк, и вы хотите знать, как долго каждый из них, или вы хотите сделать их все в верхнем регистре, или вы хотите их назад. Если у вас есть функция, которая делает то, что вы хотите для одного элемента, карта будет делать это со всеми элементами:

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.

Но что, если мы хотим собрать наши результаты? То, где складывается складка (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 и строку, и возвращает 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

Хорошо, хорошо, но что, если мы хотим знать, кто самый длинный? Один из способов (возможно, не самый лучший, но хорошо иллюстрирующий полезный шаблон) заключается в том, чтобы переносить как длину (целое число), так и ведущий соперник (строка). Пусть дают a go:

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)=>String. Как мы применим этот?

Ну, в этом случае есть "самый короткий" случай по умолчанию, поэтому мы можем сбросить функцию string-max, начиная с "". Но лучше использовать сокращение. Как и в случае с складками, есть две версии, одна из которых работает слева, а другая - справа. Он не принимает начального значения и требует функции f:(A,A)=>A. То есть, он принимает две вещи и возвращает один и тот же тип. Вот пример с функцией 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)

Во-вторых, если вы ссылаетесь только на переменную один раз, вы можете использовать _ вместо имени переменной и опустить часть анонимной функции 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

На этом этапе вы сможете создавать функции и отображать, складывать и уменьшать их с помощью Scala. Таким образом, если вы знаете, как ваш алгоритм должен работать, он должен быть достаточно простым для его реализации.

Ответ 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)
}

Теперь вам нужно написать contains и boxBounding, что является проблемой с чистыми алгоритмами, более чем языковой проблемой.

Если бы все формы имели одинаковый центр, реализация 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, который принимает две формы и объединяет их, он обычно будет прямоугольником, но может быть кругом при определенных событиях. Во всяком случае, это довольно прямолинейно, как только вы определили алгоритм.

Ответ 3

Ограничивающий прямоугольник обычно представляет собой прямоугольник. Я не думаю, что круг, расположенный в (-r, -r), является ограничивающим прямоугольником окружности радиуса r....

В любом случае предположим, что у вас есть ограничивающий прямоугольник b1 и другой b2 и функция combineBoxes, которая вычисляет ограничивающий прямоугольник b1 и b2.

Затем, если у вас есть непустой набор фигур в вашей группе, вы можете использовать reduceLeft, чтобы вычислить всю ограничительную рамку списка ограничивающих прямоугольников, объединив их по два за один раз, пока не останется только один гигантский ящик. (Такую же идею можно использовать для сокращения числа чисел до суммы чисел, добавив их в пары. И он называется reduceLeft, потому что он работает слева направо по списку.)

Предположим, что blist - список ограничивающих прямоугольников каждой формы. (Подсказка: здесь находится map.) Затем

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

Однако вам нужно поймать пустой групповой случай отдельно. (Поскольку у него нет четко определенного ограничивающего прямоугольника, вы не хотите использовать сгибы, складки хороши, когда есть пустой пустой случай, который имеет смысл. Или вам нужно сбросить с помощью Option, но тогда ваше объединение функция должна понимать, как объединить None с Some(box), что, вероятно, не стоит в этом случае, но очень хорошо может быть, если вы пишете производственный код, который должен элегантно обрабатывать различные виды ситуаций с пустым списком.)