2016-03-18 1 views
3

Scala donne la possibilité de déballer un tuple en plusieurs variables locales lors de l'exécution de diverses opérations, par exemple si j'ai des donnéestuple Déballer directement dans la classe dans scala

val infos = Array(("Matt", "Awesome"), ("Matt's Brother", "Just OK")) 

alors au lieu de faire quelque chose de laid comme

infos.map{ person_info => person_info._1 + " is " + person_info._2 } 

Je peux choisir la plus élégante

infos.map{ case (person, status) => person + " is " + status } 

Une chose que je On s'est souvent demandé comment décompacter directement le tuple en, par exemple, les arguments à utiliser dans un constructeur de classe. J'imagine quelque chose comme ceci:

case class PersonInfo(person: String, status: String) 
infos.map{ case (p: PersonInfo) => p.person + " is " + p.status } 

ou encore mieux si PersonInfo a des méthodes:

infos.map{ case (p: PersonInfo) => p.verboseStatus() } 

Mais bien sûr, cela ne fonctionne pas. Excuses si cela a déjà été demandé - je n'ai pas été capable de trouver une réponse directe - y a-t-il un moyen de faire cela?

Répondre

5

Je crois que vous pouvez obtenir les méthodes au moins à Scala 2.11.x, aussi, si vous n'en avez pas entendu parler, vous devriez vérifier The Neophyte's Guide to Scala Part 1: Extractors.

Toute la série de 16 parties est fantastique, mais la partie 1 traite des classes de cas, de la correspondance de motifs et des extracteurs, ce que je pense que vous recherchez.

De plus, je reçois aussi cette plainte dans IntelliJ, par défaut, pour des raisons qui ne me sont _: String. Il doit y avoir un moyen de contourner cela.

object Demo { 

    case class Person(name: String, status: String) { 
     def verboseStatus() = s"The status of $name is $status" 
    } 

    val peeps = Array(("Matt", "Alive"), ("Soraya", "Dead")) 

    peeps.map { 
    case p @ (_ :String, _ :String) => Person.tupled(p).verboseStatus() 
    } 

} 

MISE À JOUR:

Donc après avoir vu quelques-unes des autres réponses, j'étais curieux de savoir s'il y avait des différences de performance entre eux. Donc, je mis en place, ce que je pense pourrait être un test raisonnable en utilisant un tableau de 1.000.000 tuples de chaînes aléatoires et chaque implémentation est exécuté 100 fois:

import scala.util.Random 

object Demo extends App { 

    //Utility Code 
    def randomTuple(): (String, String) = { 
    val random = new Random 
    (random.nextString(5), random.nextString(5)) 
    } 

    def timer[R](code: => R)(implicit runs: Int): Unit = { 
    var total = 0L 
    (1 to runs).foreach { i => 
     val t0 = System.currentTimeMillis() 
     code 
     val t1 = System.currentTimeMillis() 
     total += (t1 - t0) 
    } 
    println(s"Time to perform code block ${total/runs}ms\n") 
    } 

    //Setup 
    case class Person(name: String, status: String) { 
    def verboseStatus() = s"$name is $status" 
    } 

    object PersonInfoU { 
    def unapply(x: (String, String)) = Some(Person(x._1, x._2)) 
    } 

    val infos = Array.fill[(String, String)](1000000)(randomTuple) 

    //Timer 
    implicit val runs: Int = 100 

    println("Using two map operations") 
    timer { 
    infos.map(Person.tupled).map(_.verboseStatus) 
    } 

    println("Pattern matching and calling tupled") 
    timer { 
    infos.map { 
     case p @ (_: String, _: String) => Person.tupled(p).verboseStatus() 
    } 
    } 

    println("Another pattern matching without tupled") 
    timer { 
    infos.map { 
     case (name, status) => Person(name, status).verboseStatus() 
    } 
    } 

    println("Using unapply in a companion object that takes a tuple parameter") 
    timer { 
    infos.map { case PersonInfoU(p) => p.name + " is " + p.status } 
    } 
} 

/*Results 
    Using two map operations 
    Time to perform code block 208ms 

    Pattern matching and calling tupled 
    Time to perform code block 130ms 

    Another pattern matching without tupled 
    Time to perform code block 130ms 

    WINNER 
    Using unapply in a companion object that takes a tuple parameter 
    Time to perform code block 69ms 
*/ 

mon entrée test est le son, il semble que le unapply dans un companion- L'objet ish était ~ 2x plus rapide que l'appariement des motifs, et le motif correspondait à un autre ~ 1.5x plus vite que deux cartes. Chaque implémentation a probablement ses cas d'utilisation/limitations.

Je serais reconnaissant si quelqu'un voit quelque chose de manifestement stupide dans ma stratégie de test pour me le faire savoir (et désolé pour ce var). Merci!

+0

Totalement fonctionne - c'est à peu près exactement ce que je cherchais. Merci – ohruunuruus

+0

Vous n'avez pas besoin du 'unapply'. – Dima

+0

Aussi, je ne vois pas comment 'cas p @ (_: String, _: String) => Personne.tupled (p) .verboseStatus()' est mieux que 'case (nom, statut) => Personne (nom, statut) .verboseStatus' – Dima

0

Vous voulez dire comme ça (scala 2.11.8):

scala> :paste 
// Entering paste mode (ctrl-D to finish) 

case class PersonInfo(p: String) 

Seq(PersonInfo("foo")) map { 
    case [email protected] PersonInfo(info) => s"info=$info/${p.p}" 
} 

// Exiting paste mode, now interpreting. 

defined class PersonInfo 
res4: Seq[String] = List(info=foo/foo) 

méthodes ne sera pas possible par la voie.

+0

Hmmm cela semble prometteur mais cela ne fonctionne pas pour moi, je reçois: 'constructeur ne peut pas être instancié au type attendu; trouvé: PersonInfo requis: (java.lang.String, java.lang.String) ' J'ai la version 2.9.2 de scala, pourrait-il être un problème de version? Pouvez-vous me diriger vers des documents pertinents? – ohruunuruus

+0

Je ne sais pas à propos de 2.9. J'ai mis à jour mon exemple avec une copie exacte du repl. – rethab

+0

Je pense que je vois où vous alliez maintenant. Ahmad a tout compris mais merci beaucoup – ohruunuruus

2

Vous pouvez utiliser tuppled pour le cas classe

val infos = Array(("Matt", "Awesome"), ("Matt's Brother", "Just OK")) 

infos.map(PersonInfo.tupled) 

scala> infos: Array[(String, String)] = Array((Matt,Awesome), (Matt's Brother,Just OK)) 

scala> res1: Array[PersonInfo] = Array(PersonInfo(Matt,Awesome), PersonInfo(Matt's Brother,Just OK)) 

et vous pouvez utiliser PersonInfo la façon dont vous avez besoin

+0

La chose "tupled" est cruciale! On dirait qu'Ahmad a tout compris, mais c'était parfait. – ohruunuruus

3

L'extracteur d'une classe case prend une instance de la classe case et retourne un tuple de ses champs. Vous pouvez écrire un extracteur qui fait le contraire:

object PersonInfoU { 
    def unapply(x: (String, String)) = Some(PersonInfo(x._1, x._2)) 
} 

infos.map { case PersonInfoU(p) => p.person + " is " + p.status } 
+0

C'est la pièce manquante dans les autres réponses ... merci! – ohruunuruus

0

Plusieurs réponses peuvent être combinées pour produire une approche finale, unifiée:

val infos = Array(("Matt", "Awesome"), ("Matt's Brother", "Just OK")) 

object Person{ 

    case class Info(name: String, status: String){ 
    def verboseStatus() = name + " is " + status 
    } 

    def unapply(x: (String, String)) = Some(Info(x._1, x._2)) 

} 

infos.map{ case Person(p) => p.verboseStatus } 

Bien sûr, dans ce petit cas, il est surpuissant, mais plus complexe cas d'utilisation c'est le squelette de base.