How can I parse String to (int * int) tuple in SML?

做~自己de王妃 提交于 2019-12-10 19:43:17

问题


I have a string something like this "3,4\r\n", and I want to convert them into a tuple i.e (3,4).

How can we achieve this in SML ?

The reason why I'm getting a string value is because I'm reading a file which returns strings like that.


回答1:


You need a simple parser to achieve that. An appropriate function to parse integers is already available in the library as Int.scan (along with friends for other types), but you have to write the rest yourself. For example:

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

And then, to parse all lines:

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

To use it, for example:

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

As you can see, the code is a bit repetitive. To get rid of all the matching of option types you could write a few basic parser combinators:

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

There are a lot more cool abstractions you can build along these lines, especially when defining your own infix operators. But I'll leave it at that.

You might also want to handle white space between tokens. The StringCvt.skipWS reader is readily available in the lib for that, just insert it in the right places.




回答2:


The following is a crude example of how this can be done

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

However note that mapPartial discards any thing that can't be converted to an integer (when Int.fromString returns NONE), and that it is assumed that the string always contains \r\n, as the last two characters is removed by taking the substring.

Update

Obviously the answer by Rossberg is the correct way of doing it. However depending on the task at hand this may still serve as an example of a quick and stupid way of doing it.




回答3:


Here is a straightforward way to extract all of the unsigned integers from a string and return them in a list (converting the list to a tuple is left as an exercise for the reader).

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:


Following should achieve this.

 exception MyError

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

PS - did not have access to an SML interpreter at work. So may need slight changes.



来源:https://stackoverflow.com/questions/14750444/how-can-i-parse-string-to-int-int-tuple-in-sml

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!