2013-05-07 22 views
5

lese ich Zeilen aus einer DateiRewrite-String Modifikationen funktionelle

for (line <- Source.fromFile("test.txt").getLines) { 
    .... 
} 

ich im Grunde eine Liste der Absätze am Ende erhalten mag. Wenn eine Zeile leer ist, beginnt sie als neuer Absatz, und ich möchte vielleicht in Zukunft einige Schlüsselwort-Wert-Paare analysieren.

Die Textdatei enthält eine Liste von Einträgen wie dieser (oder so ähnlich, wie eine Ini-Datei)

User=Hans 
Project=Blow up the moon 
The slugs are going to eat the mustard. // multiline possible! 
They are sneaky bastards, those slugs. 

User=.... 

Und ich möchte im Grunde eine Liste haben [Projekt] wo Project etwas wie

sieht
class Project (val User: String, val Name:String, val Desc: String) {} 

Und die Beschreibung ist das große Stück Text, der nicht mit einem <keyword>= beginnt, aber über eine beliebige Anzahl von Zeilen erstrecken kann.

Ich weiß, wie dies in einem iterativen Stil zu tun. Führen Sie einfach eine Checkliste für die Schlüsselwörter aus, füllen Sie eine Instanz einer Klasse auf und fügen Sie sie einer Liste hinzu, um später zurückzukehren.

Aber ich denke, dass es möglich sein sollte, diese in der richtigen funktionalen Stil zu tun, möglicherweise mit match case, yield und Rekursion, in einer Liste von Objekten führt, die die Felder haben User, Project und so weiter. Die verwendete Klasse ist bekannt, ebenso wie alle Schlüsselwörter, und das Dateiformat ist ebenfalls nicht in Stein gemeißelt. Ich versuche hauptsächlich, einen besseren funktionalen Stil zu lernen.

+1

Können Sie ein Beispiel für test.txt und die Liste, die Sie erhalten möchten, angeben? – maks

+0

Ich habe das Beispiel korrekt als Beispiel bezeichnet. :) –

+1

Und was sollte der Inhalt der Ergebnisliste sein? – maks

Antwort

8

Sie analysieren offensichtlich etwas, also könnte es an der Zeit sein, ... einen Parser zu verwenden!

Da Ihre Sprache Linie zu behandeln scheint, als bedeutende bricht, müssen Sie so zu this question sagen, den Parser verweisen.

aus, dass abgesehen, wäre eine ziemlich einfache Implementierung

import scala.util.parsing.combinator.RegexParsers 

case class Project(user: String, name: String, description: String) 

object ProjectParser extends RegexParsers { 
    override val whiteSpace = """[ \t]+""".r 

    def eol : Parser[String] = """\r?\n""".r 

    def user: Parser[String] = "User=" ~> """[^\n]*""".r <~ eol 
    def name: Parser[String] = "Project=" ~> """[^\n]*""".r <~ eol 
    def description: Parser[String] = repsep("""[^\n]+""".r, eol) ^^ { case l => l.mkString("\n") } 
    def project: Parser[Project] = user ~ name ~ description ^^ { case a ~ b ~ c => Project(a, b, c) } 
    def projects: Parser[List[Project]] = repsep(project,eol ~ eol) 
} 

sein und wie man es benutzt:

val sample = """User=foo1 
Project=bar1 
desc1 
desc2 
desc3 

User=foo 
Project=bar 
desc4 desc5 desc6 
desc7 desc8 desc9""" 

import scala.util.parsing.input._ 
val reader = new CharSequenceReader(sample) 
val res = ProjectParser.parseAll(ProjectParser.projects, reader) 
if(res.successful) { 
    print("Found projects: " + res.get) 
} else { 
    print(res) 
} 
+0

Ich akzeptierte dies, weil es sehr einfach zu verstehen ist (ausgenommen Regex, die immer kryptisch sind), obwohl die anderen angebotenen Lösungen auch ziemlich ordentlich sind, und ich habe eine Menge gelernt. Danke euch allen. –

-1
class Project (val User: String, val Name:String, val Desc: String) {} 
object Project { 
    def apply(str: String): Project = { 
    val user = somehowFetchUserName(str) 
    val name = somehowFetchProjectName(str) 
    val desc = somehowFetchDescription(str) 
    new Project(user, name, desc) 
    } 
} 

val contents: Array[String] = Source.fromFile("test.txt").mkString.split("\\n\\n") 
val list = contents map(Project(_)) 

wird mit der Liste der Projekte enden.

+0

Nicht ganz, ich fügte genauere Details hinzu. –

+0

bearbeitet meine Antwort – maks

1

Eine weitere mögliche Implementierung (da dieser Parser ziemlich einfach ist), Rekursion:

import scala.io.Source 
case class Project(user: String, name: String, desc: String) 
@scala.annotation.tailrec 
def parse(source: Iterator[String], list: List[Project] = Nil): List[Project] = { 
    val emptyProject = Project("", "", "") 
    @scala.annotation.tailrec 
    def parseProject(project: Option[Project] = None): Option[Project] = { 
    if(source.hasNext) { 
     val line = source.next 
     if(!line.isEmpty) { 
     val splitted = line.span(_ != '=') 
     parseProject(splitted match { 
      case (h, t) if h == "User" => project.orElse(Some(emptyProject)).map(_.copy(user = t.drop(1))) 
      case (h, t) if h == "Project" => project.orElse(Some(emptyProject)).map(_.copy(name = t.drop(1))) 
      case _ => project.orElse(Some(emptyProject)).map(project => project.copy(desc = (if(project.desc.isEmpty) "" else project.desC++ "\n") ++ line)) 
     }) 
     } else project 
    } else project 
    } 

    if(source.hasNext) { 
    parse(source, parseProject().map(_ :: list).getOrElse(list)) 
    } else list.reverse 
} 

Und der Test:

object Test { 
    def source = Source.fromString("""User=Hans 
Project=Blow up the moon 
The slugs are going to eat the mustard. // multiline possible! 
They are sneaky bastards, those slugs. 

User=Plop 
Project=SO 
Some desc""") 

    def test = println(parse(source.getLines)) 
} 

Welche gibt:

List(Project(Hans,Blow up the moon,The slugs are going to eat the mustard. // multiline possible! 
They are sneaky bastards, those slugs.), Project(Plop,SO,Some desc)) 
1

Um Ihre Frage zu beantworten, ohne auch Keyword-Analyse der Bewältigung, falten Sie die Linien und Aggregat Linien über, wenn es eine leere ist, in dem Fall, dass Sie einen neuen leeren Absatz beginnen.

Sie werden feststellen, dass es einige Falten in der Handhabung von Nulllinien und mehreren und nachfolgenden leeren Linien gibt. Passen Sie sich Ihren Bedürfnissen an. Auch wenn Sie über String-Verkettungen sprechen, können Sie sie in einer verschachtelten Liste sammeln und am Ende abflachen (mit .map (_.mkString)), soll hier nur die grundlegende Technik zum Falten einer Sequenz dargestellt werden, die nicht zu einem Skalar, sondern zu einer neuen Sequenz führt.

Dadurch wird eine Liste in umgekehrter Reihenfolge erstellt, da list prepend (: :) effizienter ist als l in jedem Schritt anzuhängen.

1

Sie bauen offensichtlich etwas, also möchten Sie vielleicht versuchen ... einen Baumeister!

Wie Jürgen, war mein erster Gedanke zu folden, wo Sie ein Ergebnis ansammeln.

Ein mutable.Builder führt die Akkumulation mit einem collection.generic.CanBuildFrom durch, um den Builder anzugeben, der zum Erstellen einer Zielsammlung aus einer Quellensammlung verwendet werden soll. Du hältst das veränderbare Ding gerade lange genug, um ein Ergebnis zu erhalten. Das ist also mein Plug für lokalisierte Veränderlichkeit. Angenommen, der Pfad von List [String] zu List [Project] ist unveränderlich.

Zu den anderen feinen Antworten (die mit nicht negativen Wertschätzungen), würde ich hinzufügen, dass funktionale Stil bedeutet funktionale Dekomposition, und in der Regel kleine Funktionen.

Wenn Sie keine Regex-Parser verwenden, vernachlässigen Sie keine Regexes in Ihren Musterübereinstimmungen.

Und versuchen Sie, die Punkte zu verschonen. Tatsächlich glaube ich, dass morgen ein Spare-the-Dots-Tag ist, und Leuten, die auf Punkte empfindlich reagieren, wird geraten, im Haus zu bleiben.

case class Project(user: String, name: String, description: String) 

trait Sample { 
    val sample = """ 
    |User=Hans 
    |Project=Blow up the moon 
    |The slugs are going to eat the mustard. // multiline possible! 
    |They are sneaky bastards, those slugs. 
    | 
    |User=Bob 
    |I haven't thought up a project name yet. 
    | 
    |User=Greta 
    |Project=Burn the witch 
    |It's necessary to escape from the witch before 
    |we blow up the moon. I hope Hans sees it my way. 
    |Once we burn the bitch, I mean witch, we can 
    |wreak whatever havoc pleases us. 
    |""".stripMargin 
} 

object Test extends App with Sample { 
    val kv = "(.*?)=(.*)".r 
    def nonnully(s: String) = if (s == null) "" else s + " " 
    val empty = Project(null, null, null) 
    val (res, dummy) = ((List.empty[Project], empty) /: sample.lines) { (acc, line) => 
    val (sofar, cur) = acc 
    line match { 
     case kv("User", u) => (sofar, cur copy (user = u)) 
     case kv("Project", n) => (sofar, cur copy (name = n)) 
     case kv(k, _)   => sys error s"Bad keyword $k" 
     case x if x.nonEmpty => (sofar, cur copy (description = s"${nonnully(cur.description)}$x")) 
     case _ if cur != empty => (cur :: sofar, empty) 
     case _    => (sofar, empty) 
    } 
    } 
    val ps = if (dummy == empty) res.reverse else (dummy :: res).reverse 
    Console println ps 
} 

Das Spiel kann auf diese Weise gestampft werden, auch:

val (res, dummy) = ((List.empty[Project], empty) /: sample.lines) { 
    case ((sofar, cur), kv("User", u))  => (sofar, cur copy (user = u)) 
    case ((sofar, cur), kv("Project", n)) => (sofar, cur copy (name = n)) 
    case ((sofar, cur), kv(k, _))   => sys error s"Bad keyword $k" 
    case ((sofar, cur), x) if x.nonEmpty => (sofar, cur copy (description = s"${nonnully(cur.description)}$x")) 
    case ((sofar, cur), _) if cur != empty => (cur :: sofar, empty) 
    case ((sofar, cur), _)     => (sofar, empty) 
    } 

Vor der Falte, schien es einfache Absätze zuerst zu tun. Ist das Imperativ zu denken?

object Test0 extends App with Sample { 
    def grafs(ss: Iterator[String]): List[List[String]] = { 
    val (g, rest) = ss dropWhile (_.isEmpty) span (_.nonEmpty) 
    val others = if (rest.nonEmpty) grafs(rest) else Nil 
    g.toList :: others 
    } 
    def toProject(ss: List[String]): Project = { 
    var p = Project("", "", "") 
    for (line <- ss; parts = line split '=') parts match { 
     case Array("User", u) => p = p.copy(user = u) 
     case Array("Project", n) => p = p.copy(name = n) 
     case Array(k, _)   => sys error s"Bad keyword $k" 
     case Array(text)   => p = p.copy(description = s"${p.description} $text") 
    } 
    p 
    } 
    val ps = grafs(sample.lines) map toProject 
    Console println ps 
} 
Verwandte Themen