字符串列表上的不区分大小写模式匹配。

19

我试图在一个F#应用程序中解析命令行参数。 我正在使用模式匹配来处理参数列表。 类似于:

let rec parseCmdLnArgs = 
  function
  | [] -> { OutputFile = None ; OtherParam = None }
  | "/out" :: fileName :: rest -> let parsedRest = parseCmdLnArgs rest
                                  { OutputFile = Some(fileName) with parsedRest }
问题是,我想让"/out"在匹配时不区分大小写,同时保留其他内容的大小写信息。这意味着我不能更改输入并将其与小写版本匹配(这将丢失fileName的大小写信息)。
我考虑了几种解决方案:
  • 使用when子句,但这不是理想的选择。
  • 每次匹配一个元组,第一个元素是实际参数(我会保存它以供进一步处理,并使用通配符进行匹配),第二个元素是用于此类匹配的小写版本。这看起来比第一个方案更糟糕。
  • 使用活动模式,但那看起来太冗长了。我必须在每个项目之前重复类似于ToLower "/out"的内容。
有没有更好的选项/模式来处理这些问题?我认为这是一个常见的问题,应该有一个好的方法来处理它。
4个回答

33

我非常喜欢你使用F#活动模式来解决这个问题的想法。虽然比预处理稍微繁琐一些,但我认为它非常优雅。此外,根据一些BCL指南,在比较字符串(忽略大小写)时不应使用ToLower。正确的方法是使用OrdinalIgnoreCase标志。你仍然可以定义一个很好的活动模式来为你完成这项工作:

open System

let (|InvariantEqual|_|) (str:string) arg = 
  if String.Compare(str, arg, StringComparison.OrdinalIgnoreCase) = 0
    then Some() else None

match "HellO" with
| InvariantEqual "hello" -> printfn "yep!"
| _ -> printfn "Nop!"    

你说的没错,这种方法比较冗长,但它很好地隐藏了逻辑,并且给你足够的能力使用推荐的编码风格(我不确定如何使用预处理来实现这一点)。


2
我认为InvariantEqual这个名称有误导性,因为您使用的是OrdinalIgnoreCase而不是InvariantCultureIgnoreCase - Maslow

2
我可能会进行一些预处理,以允许关键字以"-"或"/"开头,并对大小写进行规范化处理:
let normalize (arg:string) =
    if arg.[0] = '/' || arg.[0] = '-' then 
        ("-" + arg.[1..].ToLower())
    else arg
let normalized = args |> List.map normalize

也许不是最理想的,但用户不会有足够的耐心来输入很多命令行参数,所以两次循环遍历它们并不明显缓慢。


2
您可以使用守卫来匹配您的条件:
let rec parseCmdLnArgs = 
  function
  | [] -> { OutputFile = None ; OtherParam = None }
  | root :: fileName :: rest when root.ToUpper() = "/OUT" -> let parsedRest = parseCmdLnArgs rest
                                  { OutputFile = Some(fileName) with parsedRest }

1
我是一名有用的助手,可以为您翻译文本。

在寻找类似问题的解决方案时遇到了这个问题,虽然Tomas的解决方案适用于单个字符串,但它不能解决针对字符串列表进行模式匹配的原始问题。他的活动模式的修改版本允许匹配列表:

let (|InvariantEqual|_|) : string list -> string list -> unit option =
    fun x y ->
        let f : unit option -> string * string -> unit option =
            fun state (x, y) ->
                match state with
                | None -> None
                | Some() ->
                    if x.Equals(y, System.StringComparison.OrdinalIgnoreCase)
                    then Some()
                    else None
        if x.Length <> y.Length then None
        else List.zip x y |> List.fold f (Some())

match ["HeLlO wOrLd"] with
| InvariantEqual ["hello World";"Part Two!"] -> printfn "Bad input"
| InvariantEqual ["hello WORLD"] -> printfn "World says hello"
| _ -> printfn "No match found"

我还没有弄清楚如何正确地将其与占位符匹配,以执行| InvariantEqual "/out" :: fileName :: rest -> ...,但是如果您知道列表的全部内容,这是一种改进。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接