【问题标题】:How can I parse String to (int * int) tuple in SML?如何在 SML 中将字符串解析为 (int * int) 元组?
【发布时间】:2013-01-22 21:54:32
【问题描述】:

我有一个类似"3,4\r\n" 的字符串,我想将它们转换成一个元组,即(3,4)

我们如何在 SML 中实现这一点?

我得到一个字符串值的原因是因为我正在读取一个返回类似字符串的文件。

【问题讨论】:

    标签: sml smlnj ml


    【解决方案1】:

    您需要一个简单的解析器来实现这一点。 Int.scan 已经在库中提供了一个解析整数的适当函数(以及其他类型的朋友),但您必须自己编写其余的函数。例如:

    (* scanLine : (char, 's) StringCvt.reader -> (int * int, 's) StringCvt.reader *)
    fun scanLine getc stream =
        case Int.scan StringCvt.DEC getc stream
          of NONE => NONE
           | SOME (x1, stream') =>
        case getc stream'
          of NONE => NONE
           | SOME (c1, stream'') =>
        if c1 <> #"," then NONE else
        case Int.scan StringCvt.DEC getc stream''
          of NONE => NONE
           | SOME (x2, stream''') => 
        case getc stream'''
          of NONE => NONE
           | SOME (c2, stream'''') =>
        if c2 <> #"\n" then NONE else
        SOME ((x1, x2), stream'''')
    

    然后,解析所有行:

    (* scanList : ((char, 's) StringCvt.reader -> ('a, 's) StringCvt.reader) -> (char, 's)  StringCvt.reader -> ('a list, 's) StringCvt.reader *)
    fun scanList scanElem getc stream =
        case scanElem getc stream
          of NONE => SOME ([], stream)
           | SOME (x, stream') =>
        case scanList scanElem getc stream'
          of NONE => NONE
           | SOME (xs, stream'') => SOME (x::xs, stream'')
    

    要使用它,例如:

    val test = "4,5\n2,3\n"
    val result = StringCvt.scanString (scanList scanLine) test
    (* val result : (int * int) list = [(4, 5), (2, 3)] *)
    

    如您所见,代码有点重复。为了摆脱选项类型的所有匹配,您可以编写一些基本的解析器组合器

    (* scanCharExpect : char -> (char, 's) StringCvt.reader -> (char, 's) StringCvt.reader *)
    fun scanCharExpect expect getc stream =
        case getc stream
          of NONE => NONE
           | SOME (c, stream') =>
             if c = expect then SOME (c, stream') else NONE
    
    (* scanSeq : ((char, 's) StringCvt.reader -> ('a, 's) StringCvt.reader) * ((char, 's) StringCvt.reader -> ('b, 's) StringCvt.reader) -> (char, 's) StringCvt.reader -> ('a * 'b, 's) StringCvt.reader *)
    fun scanSeq (scan1, scan2) getc stream =
        case scan1 getc stream
          of NONE => NONE
           | SOME (x1, stream') =>
        case scan2 getc stream'
          of NONE => NONE
           | SOME (x2, stream'') => SOME ((x1, x2), stream'')
    
    fun scanSeqL (scan1, scan2) getc stream =
        Option.map (fn ((x, _), stream) => (x, stream)) (scanSeq (scan1, scan2) getc stream)
    fun scanSeqR (scan1, scan2) getc stream =
        Option.map (fn ((_, x), stream) => (x, stream)) (scanSeq (scan1, scan2) getc stream)
    
    (* scanLine : (char, 's) StringCvt.reader -> (int * int, 's) StringCvt.reader *)
    fun scanLine getc stream =
        scanSeq (
            scanSeqL (Int.scan StringCvt.DEC, scanCharExpect #","),
            scanSeqL (Int.scan StringCvt.DEC, scanCharExpect #"\n")
        ) getc stream
    

    您可以按照这些思路构建更多很酷的抽象,尤其是在定义您自己的中缀运算符时。但我会留在那里。

    您可能还想处理标记之间的空白。 StringCvt.skipWS 阅读器在库中很容易获得,只需将其插入正确的位置即可。

    【讨论】:

    • 使用高阶函数来操作资源(比如打开的文件)有点伤脑筋,因为你永远不知道什么时候会抛出异常,在这种情况下不清楚资源不会泄漏。有没有更一阶的替代方法?
    • @pyon,如果您遵循上面定义的所有函数的自然风格,以便计算只发生在完全应用的主体中,那么异常不应该比其他地方更成为问题。当然,最好有一个效果系统来检查...无论哪种方式,进行一阶解析的唯一方法是不具有多态解析函数。
    【解决方案2】:

    以下是如何做到这一点的粗略示例

    fun toPair s =
        let
          val s' = String.substring(s, 0, size s-2)
        in
          List.mapPartial Int.fromString (String.tokens (fn c => c = #",") s')
        end
    

    但是请注意,mapPartial 会丢弃任何无法转换为整数的内容(当Int.fromString 返回NONE 时),并且假定字符串始终包含\r\n,因为最后两个字符是通过获取子字符串删除。

    更新

    显然,Rossberg 的回答是正确的做法。然而,根据手头的任务,这仍然可以作为一个快速而愚蠢的做法的例子。

    【讨论】:

    • 不错的技巧。但它抛出了这个错误:Error: unbound variable or constructor: mapPartial. 但是如果我这样做 List.mapPartial 我得到 List 而不是 tuple。
    • 没错,我在测试其他东西时打开了列表模块,我的错。
    • 无论如何,我可以进行模式匹配并将它们转换为元组,这没什么大不了的。 :)
    【解决方案3】:

    这是从字符串中提取所有无符号整数并将它们返回到列表中的简单方法(将列表转换为元组留作读者练习)。

    fun ints_from_str str =
      List.mapPartial
        Int.fromString
        (String.tokens (not o Char.isDigit) str);
    
    ints_from_str " foo 1, bar:22? and 333___  ";
    
    (* val it = [1,22,333] : int list *)
    

    【讨论】:

      【解决方案4】:

      以下应该实现这一点。

       exception MyError
      
       fun convert(s) = 
         case String.explode(s) of
              x::','::y::_ => (x,y)
             | _ => raise MyError
      

      PS - 在工作中无法使用 SML 解释器。所以可能需要稍作改动。

      【讨论】:

      • 即使您这样做,返回的元组也将仅包含 x 和 y 作为字符。
      • 这只有在两个数字都恰好是一位数字时才有效。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-16
      • 1970-01-01
      相关资源
      最近更新 更多