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

发布于 2024-08-20 16:18:31 字数 724 浏览 13 评论 0原文

我正在尝试解析 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" 之类的内容。

做这些事情有更好的选择/模式吗?我认为这是一个普遍的问题,应该有一个好的方法来处理它。

I'm trying to parse command line arguments in an F# application. I'm using pattern matching over parameters list to accomplish it. Something like:

let rec parseCmdLnArgs = 
  function
  | [] -> { OutputFile = None ; OtherParam = None }
  | "/out" :: fileName :: rest -> let parsedRest = parseCmdLnArgs rest
                                  { OutputFile = Some(fileName) with parsedRest }

The problem is I want to make "/out" match case insensitive while preserving the case of other stuff. That means I can't alter the input and match the lowercase version of the input against it (this will lose the fileName case information).

I have thought about several solutions:

  • Resort to when clauses which is less than ideal.
  • Match a tuple each time, the first would be the actual parameter (which I'll just save for further processing and will wildcard match it) and the second would be the lowercased version used in such matchings. This looks worse than the first.
  • Use active patterns but that looks too verbose. I'll have to repeat things like ToLower "/out" before every item.

Is there a better option/pattern for doing these kind of stuff? I think this is a common problem and there should be a good way to handle it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

星光不落少年眉 2024-08-27 16:18:31

我非常喜欢您使用 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!"    

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

I quite like your idea of using F# active patterns to solve this. It is a bit more verbose than using pre-processing, but I think it's quite elegant. Also, according to some BCL guidelines, you shouldn't be using ToLower when comparing strings (ignoring the case). The right approach is to use OrdinalIgnoreCase flag. You can still define a nice active pattern to do this for you:

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!"    

You're right that it's more verbose, but it nicely hides the logic and it gives you enough power to use the recommended coding style (I'm not sure how this could be done using pre-processing).

暖阳 2024-08-27 16:18:31

我可能会做一些预处理,以允许在关键字开头使用“-”或“/”,并规范化情况:

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

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

I might do some pre-processing to allow for either "-" or "/" at the beginning of keywords, and to normalize the case:

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

It's perhaps not ideal, but it's not like any user is going to have enough patience to type so many command-line parameters that looping through them twice is noticeably slow.

离去的眼神 2024-08-27 16:18:31

您可以使用守卫来匹配您的交易:

let rec parseCmdLnArgs = 
  function
  | [] -> { OutputFile = None ; OtherParam = None }
  | root :: fileName :: rest when root.ToUpper() = "/OUT" -> let parsedRest = parseCmdLnArgs rest
                                  { OutputFile = Some(fileName) with parsedRest }

You can use guards to match your deal:

let rec parseCmdLnArgs = 
  function
  | [] -> { OutputFile = None ; OtherParam = None }
  | root :: fileName :: rest when root.ToUpper() = "/OUT" -> let parsedRest = parseCmdLnArgs rest
                                  { OutputFile = Some(fileName) with parsedRest }
国粹 2024-08-27 16:18:31

遇到这个问题是为了寻找类似问题的解决方案,虽然托马斯的解决方案适用于单个字符串,但它对解决字符串列表的模式匹配的原始问题没有帮助。他的活动模式的修改版本允许匹配列表:

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" :: 文件名 :: 休息 -> ... 还没有,但如果您知道列表的全部内容,那就是一个进步。

Ran into this looking for a solution to a similar issue, and while Tomas' solution works for individual strings, it doesn't help with the original issue of pattern matching against lists of strings. A modified version of his active pattern allows matching lists:

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"

I haven't been able to figure out how to make it match with placeholders properly to do | InvariantEqual "/out" :: fileName :: rest -> ... yet, but if you know the entire contents of the list, it's an improvement.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文