我们有一个文件,其中包含我们希望与case类匹配的数据。我知道足够的蛮力,但在scala中寻找一种惯用的方法。
给定文件:
#record
name:John Doe
age: 34
#record
name: Smith Holy
age: 33
# some comment
#record
# another comment
name: Martin Fowler
age: 99
(两行字段值无效,例如name:Johnn Smith should error)
和case类
case class Record(name:String, age:Int)
我想返回一个Seq类型,如Stream:
val records: Stream records
我正在使用但到目前为止还没有实现的几个想法是:
删除所有新行并将整个文件视为一个长字符串。然后grep匹配字符串"((?!name).)+((?!age).)+age:([sd]+)",并为每个匹配创建我的case类的新对象,但到目前为止,我的正则表达式foo很低,无法匹配周围的注释。
递归思想:遍历每行找到与record匹配的第一行,然后递归调用函数匹配name,然后是age。当在
name
之后遇到下一个record
(即从未遇到age
)时,尾部递归返回Some(new Record(cumulativeMap.get(name), cumulativeMap.get(age))
或None
? ?更好的主意吗?
感谢阅读!文件比上面更复杂,但所有规则都是相同的。对于好奇:我试图解析自定义M3U播放列表文件格式。
我会用粤语。一个相当简单的基于正则表达式的解决方案。
如果没有花哨的非形状派生,可以这样写:
import kantan.regex._
import kantan.regex.implicits._
case class Record(name:String, age:Int)
implicit val decoder = MatchDecoder.ordered(Record.apply _)
input.evalRegex[Record](rx"(?:name:s*([^n]+))n(?:age:s*([0-9]+))").toList
这个收益率:
List(Success(Record(John Doe,34)), Success(Record(Smith Holy,33)), Success(Record(Martin Fowler,99)))
注意,这个解决方案需要您手工编写decoder
,但它通常可以自动派生。如果你不介意没有形状的依赖,你可以简单地写:
import kantan.regex._
import kantan.regex.implicits._
import kantan.regex.generic._
case class Record(name:String, age:Int)
input.evalRegex[Record](rx"(?:name:s*([^n]+))n(?:age:s*([0-9]+))").toList
得到完全相同的结果
免责声明:我是这个库的作者。
您可以使用解析器组合子。
如果你有BNF的文件格式规范或者可以编写一个,那么Scala可以根据这些规则为你创建一个解析器。这可能比手工制作的基于正则表达式的解析器更健壮。
我没有太多Scala的经验,但是这些正则表达式可以工作吗?
您可以使用(?<=name:).*
来匹配名称值,(?<=age:).*
来匹配年龄值。如果您使用此方法,请删除找到的匹配中的空格,否则name: bob
将在bob
之前使用空格进行匹配,您可能不希望这样做。
如果name:
或任何其他标记在comment中,或者comment在value之后,则将匹配某些内容。如果你想避免这种情况,请留下评论
你可以试试:
Path file = Paths.get("file.txt");
val lines = Files.readAllLines(file, Charset.defaultCharset());
val records = lines.filter(s => s.startsWith("age:") || s.startsWith("name:"))
.grouped(2).toList.map {
case List(a, b) => Record(a.replaceAll("name:", "").trim,
b.replaceAll("age:", "").trim.toInt)
}