Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Case Insensitive Pattern Matching over String Lists

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.

like image 433
mmx Avatar asked Feb 08 '10 22:02

mmx


People also ask

Are patterns for SQL string matching case-sensitive?

SQL pattern matching enables you to use _ to match any single character and % to match an arbitrary number of characters (including zero characters). In MySQL, SQL patterns are case-insensitive by default.

How do you perform a case-insensitive comparison of two strings?

The most basic way to do case insensitive string comparison in JavaScript is using either the toLowerCase() or toUpperCase() method to make sure both strings are either all lowercase or all uppercase.

How do you perform a case-insensitive string comparison in Python?

Using the casefold() method is the strongest and the most aggressive approach to string comparison in Python. It's similar to lower() , but it removes all case distinctions in strings. This is a more efficient way to make case-insensitive comparisons in Python.

How do you ignore case-sensitive in python?

Class names and constants are written in uppercase. We can ignore case in Python using the . upper() and . lower() methods.


3 Answers

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).

like image 113
Tomas Petricek Avatar answered Oct 12 '22 01:10

Tomas Petricek


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.

like image 37
Joel Mueller Avatar answered Oct 12 '22 00:10

Joel Mueller


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 }
like image 21
ssp Avatar answered Oct 12 '22 00:10

ssp