【问题标题】:What's the best way to match strings in a file to case class in Scala?将文件中的字符串与Scala中的案例类匹配的最佳方法是什么?
【发布时间】:2016-11-11 01:16:37
【问题描述】:

我们有一个文件,其中包含我们想要与案例类匹配的数据。我知道足以强行使用它,但在 scala 中寻找一种惯用的方式。

给定文件:

#record
name:John Doe
age: 34

#record
name: Smith Holy
age: 33 

# some comment

#record
# another comment
name: Martin Fowler
age: 99 

(两行的字段值是无效的,例如 name:John\n Smith 应该出错)

还有案例类

case class Record(name:String, age:Int) 

我想返回一个 Seq 类型比如 Stream:

val records: Stream records

我正在使用但到目前为止尚未实施的几个想法是:

  1. 删除所有新行并将整个文件视为一个长字符串。然后 grep 匹配字符串 "((?!name).)+((?!age).)+age:([\s\d]+)" 并为每个匹配项创建我的案例类的新对象,但是到目前为止,我的正则表达式 foo 很低,无法与 cmets 匹配。

  2. 递归思路:遍历每一行,找到匹配记录的第一行,然后递归调用函数匹配name,然后age。在name 之后点击下一个record 时,尾部递归返回Some(new Record(cumulativeMap.get(name), cumulativeMap.get(age))None(即从未遇到过age

  3. ??更好的主意?

感谢阅读!该文件比上面更复杂,但所有规则都是平等的。对于好奇:我正在尝试解析自定义 M3U 播放列表文件格式。

【问题讨论】:

    标签: regex scala recursion case-class fileparsing


    【解决方案1】:

    我会使用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
    

    得到完全相同的结果。

    免责声明:我是图书馆的作者。

    【讨论】:

      【解决方案2】:

      你可以使用Parser Combinators

      如果您在 BNF 中有文件格式规范或可以编写一个,那么 Scala 可以根据这些规则为您创建解析器。这可能比手工制作的基于正则表达式的解析器更健壮。它肯定更像是“Scala”。

      【讨论】:

      • 我认为最好的 Scala 选项在这里。在我的实际工作中,我有太多的字段可以坐在这里并为每个字段匹配正则表达式。还有标题字段。我认为这将是要走的路。我会检查一下。
      【解决方案3】:

      我在 Scala 方面没有太多经验,但是这些正则表达式可以工作吗:

      您可以使用(?<=name:).* 匹配姓名值,使用(?<=age:).* 匹配年龄值。如果您使用它,请删除找到的匹配项中的空格,否则 name: bob 将匹配 bob 与之前的空格,您可能不希望这样。

      如果name: 或任何其他标记在注释中,或者注释在值之后,则会匹配某些内容。如果您想避免这种情况,请发表评论。

      【讨论】:

      • 不行。我可能会使用sed 删除任何以哈希开头但不是#record 的行。应该删除所有 cmets!谢谢!
      【解决方案4】:

      你可以试试这个:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-12-11
        • 2010-10-25
        • 1970-01-01
        • 2019-03-07
        • 1970-01-01
        • 2018-07-05
        • 2010-09-17
        • 2020-10-05
        相关资源
        最近更新 更多