获取Scala中的所有子对象

时间:2014-12-24 08:52:22

标签: scala playframework-2.0

我有这样的模特:

case class Product(id:String, nextVersion:Option[Product] = None)

是否可以获得所有版本的产品?我可以使用while:

def getAllVersions(product: Product) = {

  var allVersions = List[Product]()
  var actualProduct = product
  while (actualProduct != null) {
    allVersions :+= actualProduct
    actualProduct = actualProduct.nextVersion.orNull
  }

  allVersions
}

但也许有更好的方法可以做到这一点,更实用?

例如:

Product("1", Some(Product("2", Some(Product("3", None)))))

我想获得三种产品的清单" 1"," 2"," 3"。

1 个答案:

答案 0 :(得分:4)

我猜你的意思是你想要从产品链获得一系列ID,如下所示:

scala> case class Product(id:String, nextVersion:Option[Product] = None)
defined class Product

scala> val p = Product("1", Some(Product("2", Some(Product("3", None)))))
p: Product = Product(1,Some(Product(2,Some(Product(3,None)))))

scala> def toList(p:Product): List[String] = p match {
         case Product(id, None)       => List(id)
         case Product(id, Some(next)) => id :: toList(next)
       }
toList: (p: Product)List[String]

scala> toList(p)
res2: List[String] = List(1, 2, 3)

尾递归版本更有效并且不受堆栈溢出的影响:

scala> def toList(p:Product) = {
         @tailrec def loop(p: Product, soFar: List[String]): List[String] =
           p match {
            case Product(id, None)       =>            id :: soFar
            case Product(id, Some(next)) => loop(next, id :: soFar)
           }
         loop(p, Nil).reverse
       }
toList: (p: Product)List[String]

scala> toList(p)
res4: List[String] = List(1, 2, 3)

虽然我们可能最好使用Vector来创建它:创建的对象更少,最后不需要反向,如果调用者想要非顺序访问结果,则效率更高。但是让我们返回Seq,以便我们可以在以后更改类型:

scala> def toList(p:Product): Seq[String] = {
         @tailrec def loop(p: Product, soFar: Vector[String]): Vector[String] =
           p match {
             case Product(id, None)       =>            soFar :+ id
             case Product(id, Some(next)) => loop(next, soFar :+ id)
           }
         loop(p, Vector())
       }
toList: (p: Product)Seq[String]

scala> toList(p)
res5: Seq[String] = Vector(1, 2, 3)

以下版本较短,而且显式递归 - 回复toList的回复是在Option#map内完成的。但是,这当前意味着不会对递归执行尾调用优化(Scala编译器不能执行此操作,而JVM当前不执行此操作),因此这可能会导致堆栈如果产品版本链足够长,则会溢出。它的效率将低于上述代码。

scala> def toList(p:Product): List[String] =
         p.id :: (p.nextVersion map toList getOrElse Nil)
toList: (p: Product)List[String]

scala> toList(p)
res1: List[String] = List(1, 2, 3)

您可以定义并使用Iterator[Product],如下所示:

scala> implicit def productIterator(firstP: Product) = new Iterator[Product] {
         private var optP = Option(firstP)
         def hasNext = optP.isDefined
         def next =
           if (hasNext) {
             val p = optP.get
             optP = p.nextVersion
             p
           } else {
             sys error "read past end of Iterator[Product]"
           }
       }
productIterator: (firstP: Product)Iterator[Product]

scala> (p map (_.id)).toList
res22: List[String] = List(1, 2, 3)

但由于您的数据结构是递归的,因此遍历它的代码必须是递归的(显式或隐式)或使用可变变量(如Iterator所做的那样)。