字符串列表中的不区分大小写匹配

时间:2010-02-08 22:25:24

标签: string f# pattern-matching match case-insensitive

我正在尝试解析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 个答案:

答案 0 :(得分:28)

我非常喜欢使用F#活动模式来解决这个问题。它比使用预处理更冗长,但我认为它非常优雅。此外,根据some BCL guidelines,您在比较字符串时不应使用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!"    

你是对的,它更加冗长,但它很好地隐藏了逻辑,并且它为你提供了足够的能力来使用推荐的编码风格(我不确定如何使用预处理来完成)。

答案 1 :(得分:2)

我可能会做一些预处理,在关键字的开头允许“ - ”或“/”,并规范化案例:

let normalize (arg:string) =
    if arg.[0] = '/' || arg.[0] = '-' then 
        ("-" + arg.[1..].ToLower())
    else arg
let normalized = args |> List.map normalize

它可能并不理想,但它并不像任何用户都有足够的耐心来输入这么多的命令行参数,循环它们两次显然很慢。

答案 2 :(得分: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 }

答案 3 :(得分: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 -> ...,但如果你知道列表的全部内容,那就是一种改进。