将文件中的字符串与 Scala 中的大小写 class 匹配的最佳方法是什么?
What's the best way to match strings in a file to case class in Scala?
我们有一个文件,其中包含我们要与案例匹配的数据 class。我知道足以暴力破解它,但在 Scala 中寻找一种惯用的方式。
给定文件:
#record
name:John Doe
age: 34
#record
name: Smith Holy
age: 33
# some comment
#record
# another comment
name: Martin Fowler
age: 99
(两行的字段值无效,例如姓名:John\n Smith 应该会出错)
并例class
case class Record(name:String, age:Int)
我想要return一个Seq类型,例如Stream:
val records: Stream records
我正在研究但到目前为止尚未实施的几个想法是:
删除所有新行并将整个文件视为一个长字符串。然后 grep 匹配字符串 "((?!name).)+((?!age).)+age:([\s\d]+)" 并创建我的案例的新对象 class 对于每场比赛,但到目前为止我的正则表达式 foo 很低并且无法匹配评论。
递归思路:遍历每一行找到第一行匹配record,然后递归调用函数匹配name,再匹配age。在 name
之后击中下一个 record
时递归 return Some(new Record(cumulativeMap.get(name), cumulativeMap.get(age))
或 None
(即从未遇到 age
)
??更好的主意?
感谢阅读!该文件比上面更复杂,但所有规则都是相同的。对于好奇:我正在尝试解析自定义 M3U 播放列表文件格式。
我在 Scala 方面经验不多,但是这些正则表达式可以工作吗:
您可以使用 (?<=name:).*
匹配姓名值,并使用 (?<=age:).*
匹配年龄值。如果您使用它,请删除找到的匹配项中的 spaces,否则 name: bob
将匹配 bob
之前的 space,您可能不希望这样。
如果 name:
或任何其他标签在注释中,或者注释在值之后,则会匹配某些内容。如果您想避免这种情况,请发表评论。
你可以试试这个:
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)
}
你可以使用 Parser Combinators.
如果您有 BNF 格式的文件格式规范或可以编写一个,那么 Scala 可以根据这些规则为您创建一个解析器。这可能比手工制作的基于正则表达式的解析器更健壮。肯定更多 "Scala".
我会使用 kantan.regex 作为一个相当简单的基于正则表达式的解决方案。
不用花哨的无形推导,你可以这样写:
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
并得到完全相同的结果。
免责声明:我是图书馆的作者。
我们有一个文件,其中包含我们要与案例匹配的数据 class。我知道足以暴力破解它,但在 Scala 中寻找一种惯用的方式。
给定文件:
#record
name:John Doe
age: 34
#record
name: Smith Holy
age: 33
# some comment
#record
# another comment
name: Martin Fowler
age: 99
(两行的字段值无效,例如姓名:John\n Smith 应该会出错)
并例class
case class Record(name:String, age:Int)
我想要return一个Seq类型,例如Stream:
val records: Stream records
我正在研究但到目前为止尚未实施的几个想法是:
删除所有新行并将整个文件视为一个长字符串。然后 grep 匹配字符串 "((?!name).)+((?!age).)+age:([\s\d]+)" 并创建我的案例的新对象 class 对于每场比赛,但到目前为止我的正则表达式 foo 很低并且无法匹配评论。
递归思路:遍历每一行找到第一行匹配record,然后递归调用函数匹配name,再匹配age。在
name
之后击中下一个record
时递归 returnSome(new Record(cumulativeMap.get(name), cumulativeMap.get(age))
或None
(即从未遇到age
)??更好的主意?
感谢阅读!该文件比上面更复杂,但所有规则都是相同的。对于好奇:我正在尝试解析自定义 M3U 播放列表文件格式。
我在 Scala 方面经验不多,但是这些正则表达式可以工作吗:
您可以使用 (?<=name:).*
匹配姓名值,并使用 (?<=age:).*
匹配年龄值。如果您使用它,请删除找到的匹配项中的 spaces,否则 name: bob
将匹配 bob
之前的 space,您可能不希望这样。
如果 name:
或任何其他标签在注释中,或者注释在值之后,则会匹配某些内容。如果您想避免这种情况,请发表评论。
你可以试试这个:
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)
}
你可以使用 Parser Combinators.
如果您有 BNF 格式的文件格式规范或可以编写一个,那么 Scala 可以根据这些规则为您创建一个解析器。这可能比手工制作的基于正则表达式的解析器更健壮。肯定更多 "Scala".
我会使用 kantan.regex 作为一个相当简单的基于正则表达式的解决方案。
不用花哨的无形推导,你可以这样写:
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
并得到完全相同的结果。
免责声明:我是图书馆的作者。