2010-02-08 6 views
17

Estoy tratando de analizar argumentos de línea de comandos en una aplicación F #. Estoy usando la coincidencia de patrones en la lista de parámetros para lograrlo. Algo así como:Coincidencia de patrón insensible a mayúsculas y minúsculas en listas de cadenas

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

El problema es que quiero hacer caso "/out" partido insensibles preservando al mismo tiempo el caso de otras cosas. Eso significa que no puedo alterar la entrada y hacer coincidir la versión en minúscula de la entrada en su contra (esto perderá la información del caso fileName).

he pensado en varias soluciones:

  • Resort a when cláusulas que es menos que ideal.
  • Haga coincidir una tupla cada vez, la primera sería el parámetro real (que guardaré para su posterior procesamiento y la coincidencia con el comodín) y la segunda sería la versión en minúscula utilizada en dichas coincidencias. Esto se ve peor que el primero.
  • Usa patrones activos pero parece demasiado detallado. Tendré que repetir cosas como ToLower "/out" antes de cada artículo.

¿Hay una mejor opción/patrón para hacer este tipo de cosas? Creo que este es un problema común y que debería haber una buena forma de manejarlo.

Respuesta

27

me gusta bastante su idea de utilizar patrones activos F # para solucionar esto. Es un poco más detallado que usar preprocesamiento, pero creo que es bastante elegante. Además, según some BCL guidelines, no debe usar ToLower al comparar cadenas (ignorando la carcasa). El enfoque correcto es usar el indicador OrdinalIgnoreCase. Todavía se puede definir un patrón activo agradable de hacer esto para usted:

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

Tienes razón que es más detallado, pero muy bien oculta la lógica y le da la energía suficiente para usar el estilo de codificación recomendada (I No estoy seguro de cómo se puede hacer esto con el procesamiento previo).

+1

Me parece que el nombre 'InvariantEqual' es engañoso ya que está utilizando' OrdinalIgnoreCase' no 'InvariantCultureIgnoreCase' – Maslow

2

que podría hacer algunos pre-procesamiento para permitir ya sea "-" o "/" al principio de las palabras clave, y para normalizar el caso:

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

Es quizás no es ideal, pero no es como cualquier usuario tendrá la suficiente paciencia para escribir tantos parámetros de línea de comandos que recorrerlos dos veces es notablemente lento.

2

Puede utilizar guardias para que coincida con su reparto:

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

Me encontré con esto en busca de una solución a un problema similar, y aunque la solución de Tomas funciona para cadenas individuales, no ayuda con el problema original de la coincidencia de patrones con las listas de cadenas. Una versión modificada de su patrón activa permite que las listas de juego:

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" 

que no he sido capaz de averiguar cómo hacer que coincida con marcadores de posición adecuada para hacer | InvariantEqual "/out" :: fileName :: rest -> ... todavía, pero si usted sabe todo el contenido de la lista es una mejora

Cuestiones relacionadas