このような文字列があり、"3,4\r\n"
それらをタプルに変換したい(3,4)
。
SML でこれをどのように達成できますか?
文字列値を取得している理由は、そのような文字列を返すファイルを読んでいるためです。
それを実現するには、単純なパーサーが必要です。整数を解析するための適切な関数は、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
リーダーは lib ですぐに利用できます。適切な場所に挿入するだけです。
以下は、これを行う方法の大まかな例です
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
返す場合) を破棄し、最後の 2 文字は部分文字列を取得することで削除されるため、NONE
文字列には常に が含まれていると見なされることに注意してください。\r\n
アップデート
明らかに、Rossberg の答えが正しい方法です。ただし、目前のタスクによっては、これは依然として、迅速かつ愚かな方法の例として役立つ場合があります。
文字列からすべての符号なし整数を抽出し、それらをリストで返す簡単な方法を次に示します (リストをタプルに変換する方法は、読者の課題として残されています)。
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 *)
以下はこれを達成するはずです。
exception MyError
fun convert(s) =
case String.explode(s) of
x::','::y::_ => (x,y)
| _ => raise MyError
PS-職場でSMLインタープリターにアクセスできませんでした。したがって、わずかな変更が必要になる場合があります。