2010-02-19 5 views
52

Quels sont les bons tutoriels sur plier à gauche?Programmation fonctionnelle, carte Scala et pli gauche

question originale restaurée de la suppression à fournir un contexte pour d'autres réponses:

Je suis en train de mettre en œuvre une méthode pour trouver la boîte de boudning du rectangle, cercle, l'emplacement et le groupe qui tous ne se forme. 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 cadre de délimitation pour les trois sauf le groupe un. Donc, maintenant, pour la méthode de la boîte englobante, je sais que je devrais utiliser map et fold à gauche pour Group, mais je ne peux 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. 
    } 
} 

boîte englobante groupe est essentiellement la zone de délimitation la plus petite de toutes les formes fermées.

+9

Vous êtes dans le s Ame classe comme Tom? Voir http://stackoverflow.com/questions/2274852/scala-how-to-perform-pattern-matching-with-vararg-case-classes. – huynhjl

+3

Ce n'est pas une question sur Scala et 'foldLeft'. C'est une question sur les algorithmes. Vous feriez mieux de demander _ "Comment puis-je calculer la plus petite boîte englobante à partir d'une liste de formes, en utilisant des structures de données immuables?" Marquer la question comme agnostique et algorithmes. Et peut-être la programmation fonctionnelle. Si vous rencontrez un problème lors de l'implémentation des algorithmes proposés dans Scala, vous ouvrez une question Scala à ce propos. La question actuelle est ciblée sur le mauvais groupe. –

Répondre

2

Une boîte englobante 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 un cadre englobant b1 et un autre b2 et une fonction qui calcule la boîte de délimitation de b1 et b2.

Ensuite, si vous avez un non vide de formes dans votre groupe, vous pouvez utiliser reduceLeft pour calculer l'ensemble boîte de délimitation d'une liste des boîtes englobantes en les combinant deux à la fois jusqu'à ce qu'un seul reste de 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.)

Supposons que blist est une liste de reduceLeft. boîtes de délimitation de chaque forme. (Indice: c'est là map entre en jeu.) Puis

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

Vous devez prendre toutefois le cas du groupe vide séparément,. (Comme il n'y a pas de boîte englobante bien définie, vous ne voulez pas utiliser de pliures, 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 a de comprendre comment combiner None avec Some(box), ce qui est probablement pas la peine dans ce cas -. mais pourrait très bien être si vous écriviez le code de production qui doit gérer avec élégance différentes sortes de situations de liste vide)

+0

Votre problème ne semble pas seulement être que vous ne connaissez pas la syntaxe Scala. D'abord, déterminez ce qui devrait arriver mathématiquement. Alors s'inquiéter de la façon de l'écrire dans la langue. Utiliser la syntaxe correcte pour faire la mauvaise chose n'est pas utile! Vous avez besoin d'une fonction ou d'une méthode qui peut prendre deux champs de saisie et une entrée et produire la boîte de délimitation des deux en sortie. 'A.x + R.x' ne va pas mettre le coin où vous le voulez. Si vous dessinez une image et calculez les maths, vous y êtes pour la plupart. –

4

La base algorithme irait comme ceci:

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 et boxBounding, qui est un pur algorithmes prob lem plus d'un problème de langue.

Si les formes avaient toutes le même centre, la mise en œuvre contains serait plus facile. Il va 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 combiner, il sera généralement un rectangle, mais peut être un cercle sous certaines circunstances. Quoi qu'il en soit, c'est assez simple, une fois que vous avez compris l'algorithme.

+0

La méthode 'contains' de la classe Group que vous avez ici n'est pas utile dans le calcul d'une boîte englobante (que vous insistiez que ce soit une boîte ou non). Un point x est contenu dans a1 U a2 U ... U aN ssi il existe un ai tel que x est dans aI. 'forall' requiert que x soit dans chacun d'entre eux (et bien sûr, vous l'exigez pour l'objet entier, pas pour tous les points). Vous pouvez au moins utiliser prudemment 'find' au lieu de calculer l'union. Mais, mis à part cela, je pense que c'est un exemple instructif de l'utilisation de Scala. –

+0

Oui, j'ai suivi cette partie.Je ne faisais qu'objecter à la 'forall' que vous avez corrigé - correctement avec un' exists' plutôt que moins utilement avec 'find' comme je l'ai suggéré. –

+0

@Rex ah, d'accord. Maintenant que j'ai relu votre commentaire, je me rends compte que vous parliez du 'contains' de' Group', au lieu de 'Group' sur les différents' contains'. :-) –

258

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 en donner un.

Dans Scala, vous devez d'abord savoir comment créer une fonction anonyme. Il va comme si, de plus général 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 autre) 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. Peut-être que vous avez un tas de chaînes et que vous voulez savoir combien de temps chacun est, ou vous voulez les faire tous en majuscules, ou vous voulez les revenir en arrière. Si vous avez une fonction qui fait ce que vous voulez un élément, carte 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 collecter nos résultats? C'est là que le pli entre (foldLeft étant la version qui commence à gauche et fonctionne bien). Supposons que nous ayons une fonction f:(B,A) => B, c'est-à-dire qu'elle prenne un B et un A, et les combine pour produire un B. Eh bien, nous pourrions commencer par un B, et ensuite nourrir notre liste de A dans un à une 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 à partir de 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, selon celle qui est la plus grande - si nous avons plié notre liste en utilisant cela, elle nous dirait la plus longue (En supposant que nous commençons avec 0). Ou nous pourrions ajouter la longueur à l'int, accumulant des valeurs comme nous allons.

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 

Bon, très bien, mais si nous voulons savoir qui est la plus longue? Un moyen (peut-être pas le meilleur, mais il illustre bien un modèle utile) est de porter à la fois la longueur (un entier) et le principal concurrent (une chaîne).Donnons qu'un coup:

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, l'utilisation d'un pli n'est pas vraiment ce que nous voulons. Si nous voulons plus de deux chaînes, la fonction la plus naturelle serait une comme max:(String,String)=>String. Comment appliquons-nous celui-là? Eh bien, dans ce cas, il y a un cas "le plus court" par défaut, donc nous pourrions replier la fonction string-max en commençant par "". Mais une meilleure façon est d'utiliser réduire. Comme avec fold, il existe deux versions, une qui fonctionne à partir de la gauche, l'autre qui fonctionne à partir de la droite. Il ne prend aucune valeur initiale et nécessite une fonction f:(A,A)=>A. C'est-à-dire qu'il faut 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 n'y a que deux trucs supplémentaires. Tout d'abord, les deux signifient la même chose suivante:

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

Remarquez que la seconde est plus courte, et ce genre de vous donne l'impression que vous prenez b0 et faire quelque chose à la liste avec elle (que vous êtes). (:\ est le même que foldRight, mais vous l'utiliser comme ceci: (list :\ b0) (f)

Deuxièmement, si vous faites référence seulement à une variable une fois, vous pouvez utiliser _ au lieu du nom de variable et omettre la x => partie 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 être en mesure de créer des fonctions et plan, plier, et de les réduire en utilisant Scala Ainsi, si vous savez comment votre algorithme devrait fonctionner, il doit être raisonnable facile à mettre en œuvre

+22

+1 Je voudrais pouvoir voter deux fois .. –

+1

Réponse parfaite, cela m'a énormément aidé – qui

+1

Oh. Ma. Dieu. +200. – slezica

Questions connexes