web-dev-qa-db-fra.com

Programmation fonctionnelle, Scala carte et pli gauche

Quels sont les bons tutoriels à gauche?

Question d'origine, restaurée à partir de la suppression pour fournir un contexte pour d'autres réponses:

J'essaie de mettre en œuvre une méthode pour trouver la boîte bouddhiste du rectangle, du cercle, de l'emplacement et du groupe qui étend tous la forme. Le groupe est essentiellement un tableau de formes

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  

J'ai obtenu la boîte englobante calculée pour les trois sauf le groupe. Alors maintenant, pour la méthode de la boîte englobante, je sais que je devrais utiliser map et fold left pour Group, mais je n'arrive pas à trouver la syntaxe exacte de sa création.

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

La boîte englobante de groupe est fondamentalement la plus petite boîte englobante avec toutes les formes incluses.

53
jon

Maintenant que vous avez modifié pour poser une question presque complètement différente, je vais donner une réponse différente. Plutôt que de pointer vers un tutoriel sur les cartes et les plis, je vais juste en donner un.

Dans Scala, vous devez d'abord savoir comment créer une fonction anonyme. Cela va comme ça, du plus général au plus spécifique:

(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 */

Voici quelques exemples:

(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

Maintenant, la méthode map des listes et autres appliquera une fonction (anonyme ou non) à chaque élément de la carte. Autrement dit, si vous avez

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

ensuite

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

produit

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

Il y a toutes sortes de raisons pour lesquelles cela pourrait être utile. Peut-être que vous avez un tas de chaînes et que vous voulez savoir combien de temps chacune est, ou vous voulez les mettre en majuscules, ou vous les voulez en arrière. Si vous avez une fonction qui fait ce que vous voulez n élément, map le fera à tous les éléments:

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)

Donc, c'est la carte en général, et à Scala.

Mais que se passe-t-il si nous voulons recueillir nos résultats? C'est là qu'intervient fold (foldLeft étant la version qui commence à gauche et fonctionne à droite).

Supposons que nous ayons une fonction f:(B,A) => B, c'est-à-dire qu'elle prend un B et un A, et les combine pour produire un B. Eh bien, nous pourrions commencer par un B, puis y alimenter notre liste de A un à la fois, et à la fin de tout cela, nous aurions du B. C'est exactement ce que fait le pli. foldLeft le fait à partir de l'extrémité gauche de la liste; foldRight commence par la droite. C'est,

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

produit

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

b0 est, bien sûr, votre valeur initiale.

Donc, peut-être que nous avons une fonction qui prend un int et une chaîne, et retourne l'int ou la longueur de la chaîne, la valeur la plus élevée étant retenue - si nous repliions notre liste en utilisant cela, cela nous indiquerait la chaîne la plus longue (en supposant que nous commencer par 0). Ou nous pourrions ajouter la longueur à l'int, accumulant des valeurs au fur et à mesure.

Essayons.

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

D'accord, d'accord, mais si nous voulons savoir qui est le plus long? Une façon (peut-être pas la meilleure, mais elle illustre bien un modèle utile) est de transporter à la fois la longueur (un entier) et le concurrent principal (une chaîne). Essayons:

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

Ici, i est maintenant un Tuple de type (Int,String), Et i._1 Est la première partie de ce Tuple (un Int).

Mais dans certains cas comme celui-ci, utiliser un pli n'est pas vraiment ce que nous voulons. Si nous voulons la plus longue de deux chaînes, la fonction la plus naturelle serait une comme max:(String,String)=>String. Comment l'appliquer?

Eh bien, dans ce cas, il existe un cas par défaut "le plus court", donc nous pourrions plier la fonction string-max en commençant par "". Mais une meilleure façon est d'utiliser réduire. Comme pour le pli, il existe deux versions, l'une qui fonctionne à gauche, l'autre à droite. Il ne prend aucune valeur initiale et nécessite une fonction f:(A,A)=>A. Autrement dit, il prend deux choses et retourne l'un du même type. Voici un exemple avec une fonction string-max:

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

Maintenant, il ne reste plus que deux astuces. Tout d'abord, les deux suivants signifient la même chose:

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

Remarquez comment la seconde est plus courte, et cela vous donne en quelque sorte l'impression que vous prenez b0 Et que vous faites quelque chose avec la liste (que vous êtes). (:\ Est identique à foldRight, mais vous l'utilisez comme ceci: (list :\ b0) (f)

Deuxièmement, si vous ne faites référence à une variable qu'une seule fois, vous pouvez utiliser _ Au lieu du nom de la variable et omettre la partie x => De la déclaration de fonction anonyme. Voici deux exemples:

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

À ce stade, vous devriez pouvoir créer des fonctions et les mapper, les plier et les réduire à l'aide de Scala. Ainsi, si vous savez comment votre algorithme devrait fonctionner, il devrait être relativement simple de le mettre en œuvre.

262
Rex Kerr

L'algorithme de base se présenterait comme suit:

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

Vous devez maintenant écrire contains et boxBounding, qui est un problème d'algorithmes pur plus qu'un problème de langue.

Si les formes avaient toutes le même centre, l'implémentation de contains serait plus facile. Cela se passerait comme ceci:

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

Quant à boxBounding, qui prend deux formes et les combine, ce sera généralement un rectangle, mais peut être un cercle dans certaines circonstances. Quoi qu'il en soit, c'est assez simple, une fois l'algorithme compris.

4
Daniel C. Sobral

Une boîte englobante est généralement un rectangle. Je ne pense pas qu'un cercle situé en (-r, -r) soit la boîte englobante d'un cercle de rayon r ....

Quoi qu'il en soit, supposons que vous ayez une boîte englobante b1 et une autre b2 et une fonction combineBoxes qui calcule la boîte englobante de b1 et b2.

Ensuite, si vous avez un non vide ensemble de formes dans votre groupe, vous pouvez utiliser reduceLeft pour calculer la zone de délimitation entière d'une liste de zones de délimitation en les combinant deux à la fois jusqu'à ce qu'il ne reste qu'une boîte géante. (La même idée peut être utilisée pour réduire une liste de nombres à une somme de nombres en les ajoutant par paires. Et cela s'appelle reduceLeft car il fonctionne de gauche à droite dans la liste.)

Supposons que blist est une liste de boîtes englobantes de chaque forme. (Astuce: c'est là que map entre en jeu.) Ensuite

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

Vous devrez cependant attraper le cas de groupe vide séparément. (Puisqu'il n'a pas de boîte de délimitation bien définie, vous ne voulez pas utiliser de plis; les plis sont bons quand il y a un cas vide par défaut qui a du sens. Ou vous devez plier avec Option, mais alors votre fonction de combinaison doit comprendre comment combiner None avec Some(box), ce qui ne vaut probablement pas la peine dans ce cas - mais cela pourrait très bien être le cas si vous écriviez du code de production qui doit gérer avec élégance diverses sortes de situations de liste vide.)

2
Rex Kerr