2015-04-08 2 views
3

У меня есть список строкЕсть ли комбинатор для этого?

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

я тогда хочу «карта», что, например, 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)) 

Ie. Я хочу взять переменные числовые строки, объединить их с Лицом и «передать» оставшиеся строки. 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) 
} 

... но !, рекурсия дорого, если вы не сделаете его хвостовой рекурсией ..:

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 в конце, чтобы получить заказ. Комбинатор бы неплохо здесь

Так поясню, у меня есть список, я хочу «потреблять» & объединить переменное число его элементов, и вернуть останки. Есть ли способ сделать это, не прибегая к рекурсии?

+0

Разделителя между строками, которые должны быть использованы для одного человека является пустой строкой '«»' в списке (или конец списка)? Я не думаю, что для этого есть готовая функция. – Jesper

+0

Если вы попытаетесь об этом подумать ... почти каждый потребитель - 'look -> study -> Consume -> снова начать с рекурсии отдыха. –

+0

Да! Это настолько распространено, что для этого есть * комбинатор? – kornfridge

ответ

4

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

Вы можете сделать это со складкой-это, возможно, не совсем, как конкретно это применимо, как хотелось бы, и это немного шумно, но это работает:

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], который содержит человека, у которого в настоящее время добавлены данные (если есть).

Обычно я предлагаю использовать комбинаторы, такие как сгиб и избегающий явной рекурсии, но в этом случае я думаю, что это меньше из-за помех - рекурсивная версия (явно) намного понятнее.