Scala 重写更实用的字符串修改
我在读文件中的行Scala 重写更实用的字符串修改,scala,Scala,我在读文件中的行 for (line <- Source.fromFile("test.txt").getLines) { .... } 我基本上想要一个列表[项目],其中Project看起来像 class Project (val User: String, val Name:String, val Desc: String) {} 描述是一大块文本,它不是以=开头,而是可以延伸到任意数量的行 我知道如何以迭代的方式完成这项工作。只需对关键字执行一系列检查,并填充一个类的实例,然
for (line <- Source.fromFile("test.txt").getLines) {
....
}
我基本上想要一个列表[项目],其中Project
看起来像
class Project (val User: String, val Name:String, val Desc: String) {}
描述是一大块文本,它不是以=
开头,而是可以延伸到任意数量的行
我知道如何以迭代的方式完成这项工作。只需对关键字执行一系列检查,并填充一个类的实例,然后将其添加到列表中,以便稍后返回
但我认为应该可以用适当的函数风格来实现这一点,可能是使用匹配大小写、yield
和递归,从而生成一个包含User
、Project
等字段的对象列表。使用的类和所有关键字都是已知的,并且文件格式也不是一成不变的。我主要是想学习更好的功能性风格
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(_))
将以项目列表结束。您显然正在解析某些内容,因此可能是时候使用。。。解析器 由于您的语言似乎将换行符视为有意义的,因此您需要参考来告诉解析器 除此之外,一个相当简单的实现是
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)
}
另一种可能的实现(因为这个解析器相当简单),使用递归:
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
}
以及测试:
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))
}
其中:
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))
要回答您的问题而不进行关键词解析,请折叠行并聚合行,除非它是空的,在这种情况下,您将开始一个新的空段落
lines.foldLeft(List("")) { (l, x) =>
if (x.isEmpty) "" :: l else (l.head + "\n" + x) :: l.tail
} reverse
您会注意到它在处理零行、多行和尾随空行的方式上有一些折痕。适应你的需要。此外,如果您对字符串连接非常熟悉,那么可以将它们收集到嵌套列表中,并最终展平(使用.map(uu.mkString)),这只是为了展示将序列折叠为新序列而不是标量的基本技术
这会以相反的顺序生成列表,因为在每个步骤中,列表前置(::)比追加到l更有效。显然您正在生成某些内容,因此您可能希望尝试。。。建筑工人 像尤尔根一样,我的第一个想法是折叠,你在那里积累结果 一个可变的.Builder以可变的方式进行累积,使用collection.generic.CanBuildFrom指示要用于从源集合生成目标集合的构建器。你让可变的东西保持足够长的时间以得到结果。这就是我对局部易变性的理解。以免假设从List[String]到List[Project]的路径是不可变的 对于其他的好答案(非负面评价),我想补充一点,功能风格意味着功能分解,通常是小功能 如果您没有使用正则表达式解析器,请不要忽略模式匹配中的正则表达式 尽量省去这些点。事实上,我相信明天是一个空闲的Dots日,对Dots敏感的人被建议呆在室内
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
}
火柴也可以这样捣碎:
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)
}
在折叠之前,先做段落似乎更简单。这是命令式思维吗
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
}
objecttest0使用示例扩展应用程序{
def-grafs(ss:Iterator[String]):List[List[String]={
val(g,rest)=ss dropWhile(u.isEmpty)span(u.nonEmpty)
val others=if(rest.nonEmpty)grafs(rest)else Nil
g、 托利斯特:其他
}
def-toProject(ss:List[String]):项目={
var p=项目(“,”,“)
对于(行p=p.copy(用户=u)
案例数组(“Project”,n)=>p=p.copy(name=n)
案例数组(k,)=>系统错误s“错误关键字$k”
大小写数组(text)=>p=p.copy(description=s“${p.description}$text”)
}
P
}
val ps=grafs(采样线)映射到项目
控制台打印
}
你能提供test.txt的示例和你想要的列表吗?我正确地将示例标记为示例:)以及结果列表的内容应该是什么?+1表示卑鄙的混蛋。他们可能行动迟缓,但到了早上他们已经造成了破坏。不完全如此,我添加了更精确的细节。我接受这一点是因为它很容易理解(不包括始终神秘的正则表达式),尽管提供的其他解决方案也相当简洁,我学到了很多。谢谢大家。
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)
}
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
}