将元组直接解压缩到scala中的类

时间:2016-03-18 06:12:47

标签: scala iterable-unpacking

Scala能够在执行各种操作时将元组解压缩为多个局部变量,例如,如果我有一些数据

val infos = Array(("Matt", "Awesome"), ("Matt's Brother", "Just OK"))
然后,而不是做像

这样丑陋的事情
infos.map{ person_info => person_info._1 + " is " + person_info._2 }

我可以选择更优雅的

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

我经常想知道的一件事是如何直接将元组解压缩到比如在类构造函数中使用的参数。我想象这样的事情:

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

甚至更好:

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

但当然这不起作用。如果已经有人问过道歉 - 我还没有找到直接答案 - 有没有办法做到这一点?

5 个答案:

答案 0 :(得分:5)

我相信你至少可以在Scala 2.11.x中找到方法,如果你还没有听说过,你应该结帐The Neophyte's Guide to Scala Part 1: Extractors

整个16部分系列很棒,但第1部分涉及案例类,模式匹配和提取器,这是我认为你所追求的。

另外,我在IntelliJ中也得到java.lang.String投诉,由于我不完全清楚的原因,我默认这一点,我能够通过在典型的“postfix”中明确设置类型来解决这个问题。风格“即_: String。必须有一些解决方法。

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

}

更新:

所以在看到其他一些答案之后,我很好奇他们之间是否有任何性能差异。所以我设置了,我认为可能是一个合理的测试,使用一个包含1,000,000个随机字符串元组的数组,每个实现运行100次:

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

假设我的测试是合理的,似乎在伴侣对象中的不适用比模式匹配快2倍,并且模式匹配另一个比两个地图快1.5倍。每个实现可能都有其用例/限制。

我很感激,如果有人在我的测试策略中看到任何明显愚蠢的东西让我知道它(并抱歉)。谢谢!

答案 1 :(得分:3)

case类的提取器接受case类的实例并返回其字段的元组。你可以编写一个反面的提取器:

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

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

答案 2 :(得分:2)

您可以将tuppled用于案例类

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

然后您可以使用PersonInfo您需要的方式

答案 3 :(得分:0)

你的意思是这样的(scala 2.11.8):

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

case class PersonInfo(p: String)

Seq(PersonInfo("foo")) map {
    case p@ PersonInfo(info) => s"info=$info / ${p.p}"
}

// Exiting paste mode, now interpreting.

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

顺便说一下,方法是不可能的。

答案 4 :(得分:0)

可以将几个答案结合起来,形成最终的统一方法:

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 }

当然,在这个小案例中它有点矫枉过正,但对于更复杂的用例,这是基本的骨架。