如何在SML中解析String到(int * int)元组?

时间:2013-02-07 11:50:10

标签: sml smlnj ml

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

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

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

4 个答案:

答案 0 :(得分:7)

您需要一个简单的解析器来实现这一目标。解析整数的适当函数已在库中以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

您可以在这些方面构建更多很酷的抽象,特别是在定义自己的中缀运算符时。但我会留下它。

您可能还想处理令牌之间的空白区域。可以在lib中随时使用StringCvt.skipWS阅读器,只需将其插入正确的位置即可。

答案 1 :(得分:4)

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

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,因为最后两个字符是通过获取子字符串来删除的。

<强>更新

显然罗斯伯格的答案是正确的做法。然而,根据手头的任务,这仍然可以作为快速和愚蠢的方式的一个例子。

答案 2 :(得分:0)

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

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

答案 3 :(得分:-1)

以下应该实现这一点。

 exception MyError

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

PS - 在工作中无法访问SML解释器。所以可能需要稍作修改。