Mayúsculas y minúsculas coincidencia de patrón sobre listas de cadenas
-
19-09-2019 - |
Pregunta
Estoy tratando de analizar los argumentos de línea de comando en una aplicación C #. Estoy usando coincidencia de patrones sobre la lista de parámetros para lograrlo. Algo así como:
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 puede alterar la entrada y que coincida con la versión en minúsculas de la entrada en contra de ella (esto perderá la información del caso fileName
).
He pensado en varias soluciones:
- El recurso a cláusulas
when
que es menos que ideal. - coinciden con una tupla cada vez, el primero sería el parámetro real (que voy a guardar para su posterior procesamiento y tendrá que coincidir con Comodín) y la segunda sería la versión en minúsculas utilizado en este tipo de emparejamientos. Esto se ve peor que el primero.
- Uso de los patrones activos pero que se ve demasiado prolijo. Voy a tener que repetir cosas como
ToLower "/out"
antes de cada elemento.
¿Hay una mejor opción / patrón para hacer este tipo de cosas? Creo que este es un problema común y no debería ser una buena manera de manejar la situación.
Solución
Me gusta bastante su idea de utilizar patrones activos F # para solucionar esto. Es un poco más detallado que el uso de pre-procesamiento, pero creo que es bastante elegante. También, de acuerdo a algunas pautas BCL , no deberías t estar usando ToLower
al comparar cadenas (ignorando el caso). El enfoque correcto es utilizar la bandera 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 la que le da el poder suficiente para utilizar el estilo de codificación recomendada (no estoy seguro de cómo esto podría hacerse mediante pre-procesamiento).
Otros consejos
Yo podría hacer un poco de 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 va a tener bastante paciencia para escribir tantos parámetros de línea de comandos que bucle a través de ellos dos veces es notablemente más lento.
Puede utilizar los 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 }
encontramos con este en busca de una solución a un problema similar, y mientras que la solución de Tomás trabaja para cuerdas individuales, que no ayuda con la emisión original de coincidencia de patrones contra listas de cadenas. Una versión modificada de su patrón activo permite 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"
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, que es una mejora.