这有组合器吗?

Is there a combinator for this?

我有一个字符串列表

val allLines = List("James Bond", "secret agent", "", "Martin Odersky")

然后我想要 "map" 例如a List[Person] 其中 case class Person(name: String, moreDetails: List[String] 一次使用 多个元素 .

val people = allLines.someCombinatorFunction { lines => 
   val name = lines.head
   val (moreDetails, remainingLines) = lines.span(_ != "")
   val person = Person(name, moreDetails)

   (person, remainingLines)
}

这应该给我:

List(Person("James Bond", List("secret agent")), Person("Martin Odersky", Nil))

即。我想采用可变数量的行,将它们组合成一个人,然后 "hand off" 剩余的行。 List[String] => List[Person]。这对于递归来说是微不足道的:

def linesToPeople(lines: List[String]): List[Person] = { lines => 
   val name = lines.head
   val (moreDetails, remainingLines) = lines.span(_ != "")
   val person = Person(name, moreDetails)

   person :: linesToPeople(remainingLines)
}

...但是!,递归是昂贵的,除非你使它尾递归..:[=​​19=]

def linesToPeople(lines: List[String], acc: List[Person] = Nil): List[Person] = { lines => 
   val name = lines.head
   val (moreDetails, remainingLines) = lines.span(_ != "")
   val person = Person(name, moreDetails)

   linesToPeople(remainingLines, person :: acc)
}

^ 这就是我觉得有点太麻烦的地方。您还需要在最后执行 .reverse 以获得正确的顺序。组合器在这里会很好

基本上,我有一个列表,我想 "consume" 并组合可变数量的元素,return 剩下的。有没有办法不用递归就可以做到这一点?

你可以通过折叠来做到这一点——它可能不像你想要的那样特别适用,而且有点吵,但它有效:

val (last, rest) =
  allLines.foldLeft((None: Option[Person], List.empty[Person])) {
    case ((None, people), line) => (Some(Person(line, Nil)), people)
    case ((Some(last), people), "") => (None, people :+ last)
    case ((Some(Person(name, details)), people), line) =>
      (Some(Person(name, details :+ line)), people)
  }

val people = rest ++ last

基本思想是您携带一个累加器,该累加器还指示处理状态。在这种情况下,我使用的是已完成人员的列表和包含当前已添加详细信息(如果有)的人员的 Option[Person]

我通常建议使用像 fold 这样的组合器并避免显式递归,但在这种情况下,我认为它不是灌篮——(显式)递归版本更清晰。

Scalaz 有一个函数 selectSplit

import scalaz._
import Scalaz._

def getPeople(lines: List[String]): List[Person] = 
  lines.selectSplit(_ != "").map(l => Person(l.head, l.tail))

然后:

scala> getPeople(List(
  "James Bond", "secret agent", "", 
  "Martin Odersky", "", 
  "Arnold Schwarzenegger", "Terminator", "governor"))
res8: List[Person] = List(Person(James Bond,List(secret agent)), Person(Martin Odersky,List()), Person(Arnold Schwarzenegger,List(Terminator, governor)))