2010-09-20 4 views
5

que desea extraer la parte media de una cadena usando FSharp si es citado, similar como esto:¿Cómo puedo extraer la parte media de una cadena en FSharp?

let middle = 
    match original with 
    | "\"" + mid + "\"" -> mid 
    | all -> all 

Pero esto no funciona debido a que el operador infijo + en la expresión del modelo. ¿Cómo puedo extraer esto?

+0

Véase también http://stackoverflow.com/questions/3722591/pattern-matching-on-the-beginning- of-a-string-in-f – Brian

Respuesta

10

No creo que haya ningún soporte directo para esto, pero ciertamente puede escribir un patrón activo. Los patrones activos le permiten implementar su propio código que se ejecutará como parte de la coincidencia de patrones y puede extraer & para devolver alguna parte del valor.

El siguiente es un patrón que toma dos parámetros (prefijo y cadena postfija) y tiene éxito si la entrada dada comienza/termina con las cadenas especificadas. El patrón no está completa (puede fallar), por lo que vamos a utilizar la sintaxis |Name|_| y tendrá que volver valor de la opción:

let (|Middle|_|) prefix postfix (input:string) = 
    // Check if the string starts with 'prefix', ends with 'postfix' and 
    // is longer than the two (meaning that it contains some middle part) 
    if input.StartsWith(prefix) && input.EndsWith(postfix) && 
    input.Length >= (prefix.Length + postfix.Length) then 
    // Strip the prefix/postfix and return 'Some' to indicate success 
    let len = input.Length - prefix.Length - postfix.Length 
    Some(input.Substring(prefix.Length, len)) 
    else None // Return 'None' - string doesn't match the pattern 

Ahora podemos usar Middle de coincidencia de patrones (por ejemplo, cuando se utiliza match):

match "[aaa]" with 
| Middle "[" "]" mid -> mid 
| all -> all 
+0

Su estrategia de implementación es mejor que la mía (solo una llamada a 'Substring'), ¡pero me alegro de ver que teníamos la misma idea! :) – Brian

+0

Gracias Tomas. Tu respuesta es tan clara y útil. Gracias a brian y kvb también. Desafortunadamente, soy nuevo en StackOverflow y no tengo suficientes créditos para hacer tus respuestas. – newwave

+0

@Brian: Sí, parece que publicamos casi la misma respuesta. También consideré tu estrategia (pero pensé que verificar la duración es más fácil) y también consideré pasar los dos argumentos usando una tupla :-) –

2

Los patrones tienen una gramática limitada: no se puede usar cualquier expresión. En este caso, yo sólo tiene que utilizar un if/then/else:

let middle (s:string) = 
    if s.[0] = '"' && s.[s.Length - 1] = '"' && s.Length >= 2 then 
    s.Substring(1,s.Length - 2) 
    else s 

Si agarrar el medio de una cadena con inicios y finales estáticamente conocidos es algo que se va a hacer mucho, entonces siempre se puede usa un patrón activo como sugiere Tomás.

+0

Gracias kvb. Prefiero la respuesta de Tomás solo porque no quiero usarla de manera imperativa. – newwave

3

Parametrizado active patterns al rescate!

let (|HasPrefixSuffix|_|) (pre:string, suf:string) (s:string) = 
    if s.StartsWith(pre) then 
     let rest = s.Substring(pre.Length) 
     if rest.EndsWith(suf) then 
      Some(rest.Substring(0, rest.Length - suf.Length)) 
     else 
      None 
    else 
     None 

let Test s = 
    match s with 
    | HasPrefixSuffix("\"","\"") inside -> 
     printfn "quoted, inside is: %s" inside 
    | _ -> printfn "not quoted: %s" s 

Test "\"Wow!\"" 
Test "boring" 
+1

Los patrones activos parametrizados son increíblemente poderosos. – gradbot

+0

¡Seriamente ingenioso! :-) –

2

... o simplemente utilizar el viejo y simple expresión regular

let Middle input = 
    let capture = Regex.Match(input, "\"([^\"]+)\"") 
    match capture.Groups.Count with 
    | 2 -> capture.Groups.[1].Value 
    | _ -> input 
+0

¿Será mejor devolver un tipo de opción? –

+0

El autor quiere devolver la cadena completa si no está citada, ya que entendí la pregunta. –

0

No está seguro de qué tan eficiente esto es:

let GetQuote (s:String) (q:char) = 
     s 
     |> Seq.skip ((s |> Seq.findIndex (fun c -> c = q))+1) 
     |> Seq.takeWhile (fun c-> c <> q) 
     |> Seq.fold(fun acc c -> String.Format("{0}{1}", acc, c)) "" 

O no es esto con la subcadena en el lugar del pliegue:

let GetQuote2 (s:String) (q:char) = 
    let isQuote = (fun c -> c = q) 
    let a = (s |> Seq.findIndex isQuote)+1 
    let b = ((s |> Seq.take(a) |> Seq.findIndex isQuote)-1) 
    s.Substring(a,b); 

Estos obtendrán la primera instancia del texto citado en cualquier lugar de la cadena, por ejemplo, "Hola [Mundo]" -> "Mundo"

Cuestiones relacionadas