【问题标题】:How do you split a string into lists unless it is inside quotation marks ("") in Ocaml?除非它在 ​​Ocaml 中的引号 ("") 内,否则如何将字符串拆分为列表?
【发布时间】:2019-10-28 01:33:04
【问题描述】:

我正在读取一个多行的输入文件。每一行的格式如下:

Greeting "hello"
Greeting " Good morning"
Sit
Smile
Question "How are you?"

我的当前可以将每一行读入一个字符串列表。然后我使用这个函数处理它,这个函数应该把它分解成一个字符串列表:

let rec process (l : string list) (acc : string list list) : string list list = 
  match l with
  | [] -> acc
  | hd :: tl -> String.split_on_char ' ' hd :: (process tl acc)

不幸的是,这不起作用,因为它还会在引号内分割空格。任何人都想到了这样做的正确方法,可能使用 map 或 fold_left 等?这将是我的预期输出:

[["Greeting"; "/"hello/""];[Greeting; "/" Good morning"];["Sit"]]

等等。谢谢!

【问题讨论】:

  • 您要求进行真正的(尽管非常基本的)词法分析,而不仅仅是简单的拆分操作。这是为了学校作业吗?如果是这样,您可能应该尝试编写自己的小型扫描仪。 OCaml 有词法分析工具,但对于这个简单的问题,它们可能有点矫枉过正。 (或者它们可能正是您应该使用的。)
  • 不,这不是学校作业。我简化了我的问题以使其更通用。 “扫描仪”是什么意思?我考虑过获取第一次出现的 ' ' 然后拆分,从 - 到它的索引,但我认为某处必须有更简单的方法。

标签: ocaml


【解决方案1】:

您想要一个真实的(但非常简单的)词法分析。恕我直言,这超出了简单的字符串拆分所能做到的范围。

扫描器获取一个字符流并返回它看到的下一个标记。您可以通过具有遍历字符串的索引将字符串变成流。

这是您想要的大致扫描仪:

let rec scan s offset =
    let slen = String.length s in
    if offset >= slen then
        None
    else if s.[offset] = ' ' then
        scan s (offset + 1)
    else if s.[offset] = '"' then
        let rec qlook loff =
            if loff >= slen then
                (* Unterminated quotation *)
                let tok = String.sub s offset (slen - offset) in
                Some (tok, slen)
            else if s.[loff] = '"' then
                let tok = String.sub s offset (loff - offset + 1) in
                Some (tok, loff + 1)
            else qlook (loff + 1)
        in
        qlook (offset + 1)
    else
        let rec wlook loff =
            if loff >= slen then
                let tok = String.sub s offset (slen - offset) in
                Some (tok, slen)
            else if s.[loff] = ' ' || s.[loff] = '"' then
                let tok = String.sub s offset (loff - offset) in
                Some (tok, loff)
            else
                wlook (loff + 1)
        in
        wlook (offset + 1)

它处理了一些您没有指定的情况:如果有一个未闭合的引号该怎么办。如何处理 abc"def ghi" 之类的内容。

扫描器在字符串末尾返回None,或Some (token, offset),即下一个标记和继续扫描的偏移量。

分解字符串的递归函数如下所示:

let split s =
    let rec isplit accum offset =
        match scan s offset with
        | None -> List.rev accum
        | Some (tok, offset') -> isplit (tok :: accum) offset'
    in
    isplit [] 0

【讨论】:

    【解决方案2】:

    这可以通过状态机进行可视化。你有两个主要状态:寻找''和寻找'"'。处理字符串是丑陋的,你不能模式匹配它。所以我做的第一件事就是把字符串变成一个字符列表。实现这两种状态然后变成简单:

    let split s =
      let rec split_space acc word = function
      | [] -> List.rev (List.rev word::acc)
      | ' '::xs -> split_space (List.rev word::acc) [] xs
      | '"'::xs -> find_quote acc ('"'::word) xs
      | x::xs -> split_space acc (x::word) xs
      and find_quote acc word = function
      | [] -> List.rev (List.rev word::acc)
      | '"'::xs -> split_space acc ('"'::word) xs
      | x::xs -> find_quote acc (x::word) xs
      in
      split_space [] [] s
    ;;
    
    # split ['a';'b';' ';'"';'c';' ';'d';'"';' ';'e'];;
    - : char list list = [['a'; 'b']; ['"'; 'c'; ' '; 'd'; '"']; ['e']]
    

    现在,如果您想使用留给您的字符串来执行此操作。想法是一样的。或者你可以在最后将字符列表列表变成一个字符串列表。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-24
      • 1970-01-01
      • 2020-11-06
      • 2014-10-10
      • 2011-01-28
      • 1970-01-01
      • 1970-01-01
      • 2016-06-24
      相关资源
      最近更新 更多