53 votes

Programmation fonctionnelle, Scala map et fold à gauche

Quels sont les bons tutoriels sur le pliage à gauche ?

Question originale, restaurée après avoir été supprimée, afin de fournir un contexte aux autres réponses :

J'essaie d'implémenter une méthode pour trouver la boîte de boudin d'un rectangle, d'un cercle, d'un emplacement et d'un groupe, qui tous étendent Shape. 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 le calcul de la boîte englobante pour les trois, sauf pour le groupe. 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 pour la créer.

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

Le rectangle de délimitation du groupe est en fait le plus petit rectangle de délimitation dans lequel se trouvent toutes les formes.

263voto

Rex Kerr Points 94401

Maintenant que vous avez modifié votre question pour en poser une presque totalement différente, je vais y répondre différemment. Plutôt que de pointer vers un tutoriel sur les cartes et les plis, je vais simplement en donner un.

En Scala, vous devez d'abord savoir comment créer une fonction anonyme. Cela se passe comme suit, 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, le map de 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

puis

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. Vous avez peut-être un tas de chaînes de caractères et vous voulez connaître la longueur de chacune d'entre elles, ou vous voulez qu'elles soient toutes en majuscules, ou vous voulez qu'elles soient inversées. Si vous avez une fonction qui fait ce que vous voulez un la carte le fera pour 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 map en général, et en Scala.

Mais que faire si nous voulons collecter nos résultats ? C'est là que le pli intervient ( foldLeft étant la version qui commence à gauche et travaille à droite).

Supposons que nous ayons une fonction f:(B,A) => B Nous pourrions commencer avec un B, puis y introduire notre liste de A un par un, et à la fin, nous aurions un B. C'est exactement ce que fait le pli. foldLeft le fait en commençant par l'extrémité gauche de la liste ; foldRight commence par la droite. C'est-à-dire,

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

produit

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

b0 est, bien entendu, votre valeur initiale.

Ainsi, nous disposons peut-être d'une fonction qui prend un int et une chaîne de caractères et renvoie l'int ou la longueur de la chaîne de caractères, selon la valeur la plus élevée. Si nous plions notre liste en utilisant cette fonction, elle nous indiquerait la chaîne de caractères la plus longue (en supposant que nous commencions par 0). Ou nous pourrions ajouter la longueur à l'int, en accumulant les valeurs au fur et à mesure.

Faisons un essai.

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

Ok, très bien, mais si nous voulons savoir qui est le plus long ? Une façon de procéder (qui n'est peut-être pas la meilleure, mais qui illustre bien un modèle utile) consiste à transporter à la fois la longueur (un nombre entier) et le premier prétendant (une corde). Faisons un essai :

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

Aquí, i est maintenant un tuple de type (Int,String) y 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 de caractères, la fonction la plus naturelle serait une fonction du type max:(String,String)=>String . Comment appliquer celui-là ?

Dans ce cas, il y a un cas par défaut "le plus court", donc nous pourrions plier la fonction string-max en commençant par "". Mais une meilleure solution consiste à utiliser réduire le site . Comme pour le pli, il existe deux versions, l'une qui fonctionne à gauche, l'autre qui fonctionne à droite. Elle ne prend pas de valeur initiale, et nécessite une fonction f:(A,A)=>A . C'est-à-dire qu'elle prend deux choses et en retourne une 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 y a juste deux autres astuces. D'abord, les deux suivantes signifient la même chose :

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

Remarquez que la seconde est plus courte et vous donne l'impression que vous êtes en train de prendre b0 et faire quelque chose à la liste avec (ce que vous faites). ( :\ est la même chose que foldRight mais vous l'utilisez comme ça : (list :\ b0) (f)

Deuxièmement, si vous ne faites référence à une variable qu'une seule fois, vous pouvez utiliser _ à la place du nom de la variable et omettre le x => de la déclaration de la 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 être en mesure de créer des fonctions et de les mapper, les plier et les réduire à l'aide de Scala. Ainsi, si vous savez comment votre algorithme doit fonctionner, il devrait être raisonnablement simple de l'implémenter.

4voto

Daniel C. Sobral Points 159554

L'algorithme de base serait le suivant :

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

Maintenant, vous devez écrire contains y boxBounding ce qui est un problème d'algorithme pur plus qu'un problème de langage.

Si les formes ont toutes le même centre, la mise en œuvre contains serait plus facile. Ça se passerait comme ça :

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 Il s'agit d'une combinaison de deux formes. Il s'agit généralement d'un rectangle, mais il peut s'agir d'un cercle dans certaines circonstances. Quoi qu'il en soit, c'est assez simple, une fois que vous avez compris l'algorithme.

2voto

Rex Kerr Points 94401

Une boîte de délimitation est généralement un rectangle. Je ne pense pas qu'un cercle situé à (-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 de délimitation b1 et une autre b2 et une fonction combineBoxes qui calcule la boîte englobante de b1 et b2.

Alors si vous avez un non vide ensemble de formes dans votre groupe, vous pouvez utiliser reduceLeft pour calculer la boîte englobante entière d'une liste de boîtes englobantes en les combinant deux par deux jusqu'à ce qu'il ne reste qu'une seule 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 additionnant par paires. C'est ce qu'on appelle reduceLeft car il fonctionne de gauche à droite dans la liste).

Supposons que blist est une liste de boîtes englobantes pour chaque forme. (Indice : c'est ici que map entre.) Puis

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

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X