2012-09-24 18 views
11

me gustaría escribir una función que filtra una secuencia utilizando un predicado pero el resultado debería incluir también el primer elemento para el que el predicado devuelve falso.cómo hacer Seq.takeWhile + un elemento en F #

La lógica sería algo como esto, si había una palabra clave rotura en F #

let myFilter predicate s = 
    seq { 
     for item in s do 
      yield item 
      if predicate item then 
       break 
    } 

Probé combinaciones de Seq.takeWhile y Seq.skipWhile, algo como esto:

Seq.append 
    (Seq.takeWhile predicate s) 
    (Seq.skipWhile predicate s |> Seq.take 1) 

... pero el problema es que el primer elemento que coincide con el predicado se pierde entre takeWhile y skipWhile

También tenga en cuenta que la secuencia de entrada es floja por lo que cualquier sol la consumición de la secuencia y la toma de decisiones posteriores no es viable.

¿Alguna idea?

Gracias!

EDITAR: ¡Muchas gracias por todas las respuestas! No esperaba tantas respuestas tan rápido. Echaré un vistazo a cada uno de ellos pronto. Ahora solo quiero dar un poco más de contexto. Considere el kata siguiente codificación que implementa un shell: "! Adiós"

let cmdProcessor state = function 
    | "q" -> "Good bye!" 
    | "h" -> "Help content" 
    | c -> sprintf "Bad command: '%s'" c 

let processUntilQuit = 
    Seq.takeWhile (fun cmd -> cmd <> "q") 

let processor = 
    processUntilQuit 
    >> Seq.scan cmdProcessor "Welcome!" 

module io = 
    let consoleLines = seq { while true do yield System.Console.ReadLine() } 

    let display : string seq -> unit = Seq.iter <| printfn "%s" 

io.consoleLines |> processor|> io.display 

printf "Press any key to continue..." 
System.Console.ReadKey()|> ignore 

Esta aplicación tiene el problema de que no se de impresión cuando se ingresa el comando q.

Lo que quiero hacer es poner en práctica la función processUntilQuit tal que procesa todos los comandos hasta que "q", incluyendo "q".

+1

¿Qué quiere decir con * el problema es que el primer elemento que coincide con el predicado se pierde entre takeWhile y skipWhile *? Su solución es correcta porque el primer elemento falso todavía está allí en 's'. – pad

+0

@pad: Así que si puedo implementar la processUntilQuit función como esta: vamos processUntilQuit cmds = vamos isNotFinished cmd cmd = <> "q" ss {rendirá! Seq.takeWhile isNotFinished cmds rendimiento! Seq.skipWhile isNotFinished cmds |> Seq.truncate 1} ... el resultado es el siguiente: ¡Bienvenido! q q ¡Adiós! Presione cualquier tecla para continuar ... Como puede ver, tuve que escribir q dos veces para salir. – vidi

Respuesta

13

La falta de apoyo a break en las expresiones de cálculo es un poco molesto. No encaja bien con el modelo utilizado por F # (razón por la cual no es compatible), pero sería realmente útil en este caso.

Si desea implementar esto utilizando sólo una sola iteración sobre la secuencia, entonces creo que la solución más limpia es usar la estructura subyacente de secuencias y escribir como un bucle recursivo utilizando IEnumerator<'T>

Esto es bastante corto (en comparación con otras soluciones aquí) y es bastante claro código también:

let myFilter predicate (s:seq<_>) = 
    /// Iterates over the enumerator, yielding elements and 
    /// stops after an element for which the predicate does not hold 
    let rec loop (en:IEnumerator<_>) = seq { 
    if en.MoveNext() then 
     // Always yield the current, stop if predicate does not hold 
     yield en.Current 
     if predicate en.Current then 
     yield! loop en } 

    // Get enumerator of the sequence and yield all results 
    // (making sure that the enumerator gets disposed) 
    seq { use en = s.GetEnumerator() 
     yield! loop en } 
+0

Esto no es realmente funcional, pero funciona y es lo suficientemente limpio. ¡Gracias! Todavía sería interesante ver cómo se puede escribir esto de una manera agradable y funcional. – vidi

+2

No tengo una "prueba" para este reclamo, pero creo que no se puede escribir de una "buena manera funcional" porque la estructura de datos subyacente - 'seq <'T>' no es realmente funcional. Podrías escribirlo en un estilo funcional si usaste la lista diferida, pero las secuencias son más idiomáticas en F # (incluso si a veces te obligan a usar el estilo imperativo para implementar alguna función declarativa de nivel superior). –

0

feo solución

let myfilter f s = 
    let failed = ref false 
    let newf = fun elem -> match !failed with 
          |true -> 
           failed := f elem 
           true 
          |false->false 
    Seq.takeWhile newf s 
+0

Con esta implementación, después de ingresar el comando q se lee un comando más y luego sale de – vidi

0

solución funcional feo no funcional :):

let rec myFilter predicate = 
     Seq.fold (fun acc s -> 
      match acc with 
       | (Some x, fs) -> 
        match predicate s with 
         | true -> (Some x, fs @ [s]) 
         | false -> (Some x, fs) 
       | (_, fs) -> 
        match predicate s with 
         | true -> (None, fs @ [s]) 
         | false -> (Some s, fs)) 
      (None, []) 

Se termina con tupla, de los cuales primero elemento contiene opción con primer elemento no coincidente de la lista fuente y el segundo elemento contiene una lista filtrada.

solución perezosa funcional feo (lo siento, no he leído tu post correctamente por primera vez):

let myFilterLazy predicate s = 
     let rec inner x = 
      seq { 
       match x with 
        | (true, ss) when ss |> Seq.isEmpty = false -> 
         let y = ss |> Seq.head 
         if predicate y = true then yield y 
         yield! inner (true, ss |> Seq.skip 1) 
        | (_, ss) when ss |> Seq.isEmpty = false -> 
         let y = ss |> Seq.head 
         if predicate y = true then 
          yield y 
          yield! inner (false, ss |> Seq.skip 1) 
         else 
          yield y 
          yield! inner (true, ss |> Seq.skip 1) 
        | _ -> 0.0 |> ignore 
      } 

     inner (false, s) 

No soy fluido suficiente en F # para hacer que termina en caso de coincidencia se ven bien, tal vez algunos de los gurús F # ayudarán.

Editar: # solución no tan feo, pura F inspirado en respuesta Tomas Petricek:

let myFilterLazy2 predicate s = 
     let rec inner ss = seq { 
      if Seq.isEmpty ss = false then 
       yield ss |> Seq.head 
       if ss |> Seq.head |> predicate then 
        yield! ss |> Seq.skip 1 |> inner 
     } 

     inner s 
+0

Merece +1 por ser la solución más fea :) Gracias, pero esperaba algo más limpio. – vidi

+0

Gracias por el aprecio :). Tercera versión, puro F # sin extrañas clases .net;). Inspirado por la respuesta de Tomas Petricek, que también es mi favorito. – rkrahl

+0

Acabo de probar su segunda solución y no funciona. Necesita los comandos ingresados ​​dos veces. – vidi

0
let duplicateHead xs = seq { yield Seq.head xs; yield! xs } 
let filter predicate xs = 
    xs 
    |> duplicateHead 
    |> Seq.pairwise 
    |> Seq.takeWhile (fst >> predicate) 
    |> Seq.map snd 

versión alternativa de duplicateHead, en caso de que si no te gusta la expresión de cálculo aquí:

let duplicateHead' xs = 
    Seq.append 
     (Seq.head xs) 
     xs 

Este enfoque se basa en construir tuplas del elemento actual y siguiente.El predicate se está aplicando al elemento actual, pero se devuelve el siguiente.

NOTA: Es no seguro para los casos en que predicate falla en el primer elemento. Para que funcione bien, debe volver a trabajar duplicateHead agregando un elemento que ciertamente pasaría el predicate.

+0

Casi funciona pero lee un comando más después de la qy, como ya dijiste, todavía hay un problema si el primer comando es q – vidi

2

Realmente no entiendo cuál es el problema con su solución.

Dos pequeñas correcciones:

(1) expresión Uso de secuencia para mejorar la legibilidad.

(2) Use Seq.truncate en lugar de Seq.take en caso de que la secuencia de entrada esté vacía.

let myFilter predicate s = 
    seq { yield! Seq.takeWhile predicate s 
      yield! s |> Seq.skipWhile predicate |> Seq.truncate 1 } 
+0

¿No enumerará s dos veces en el peor de los casos (todo el predicado de coincidencia de s)? – rkrahl

+0

Sí, lo hará. Pero no creo que sea la preocupación de OP en la pregunta. – pad

+0

No entiendo tu primer punto: creo que tanto las versiones de OP como las tuyas son igualmente flojas. Lo escribiría de esta manera también, pero eso es solo preferencia estilística ... (Buen punto sobre 'truncado') –

0

Un poco mejor. :)

let padWithTrue n xs = seq { for _ in 1..n do yield true; done; yield! xs } 
let filter predicate n xs = 
    let ys = xs |> Seq.map predicate |> padWithTrue n 
    Seq.zip xs ys 
    |> Seq.takeWhile snd 
    |> Seq.map fst 

Éste toma un parámetro adicional n que define cómo muchos elementos adicionales para añadir.

NOTA: cuidado con una sola línea de padWithTrue (done palabra clave)

+0

Éste no funciona. Necesita los comandos escritos dos veces. Esta es la implementación que escribí según su sugerencia: let processUntilQuit cmds = let padWithTrue n xs = seq {for _ in 1..n do true; hecho; ¡rendimiento! xs} let ys = cmds |> Seq.map (fun cmd -> cmd <> "q") |> padWithTrue 1 Seq.zip cmds ys |> Seq.takeWhile snd |> Seq.map fst – vidi

0

supongo que lo que quiere es takeUntil:

let takeUntil pred s = 
    let state = ref true 
    Seq.takeWhile (fun el -> 
    let ret= !state 
    state := not <| pred el 
    ret 
    ) s 
+1

Esto no es correcto, ya que evalúa un elemento adicional (que puede tener un efecto secundario no deseado). – brianberns

0

Esto es muy antiguo, pero pensé que contribuiría porque las otras soluciones no sugerían esto ...

¿Qué pasa con el uso de Seq.scan para establecer una pila de dos elementos de resultados de predicados y simplemente tomar la parte inferior de esa pila, que representa el resultado del predicado del elemento anterior, es true? (nota, no he probado este código)

Seq.scan (fun (a,b,v) e -> (pred e, a, Some e)) (true, true, None) 
>> Seq.takeWhile (fun (_,b,_) -> b) 
>> Seq.map (fun (_,_,c) -> c) 
0

Entiendo que esta es una pregunta anterior. Pero hay una solución más funcional.

Aunque, para ser honesto, para esta pregunta me gusta más el more imperative solution by Tomas Petricek.

let takeWhileAndNext predicate mySequence = 
    let folder pred state element = 
     match state with 
      | Some (_, false) -> 
       None 
      | _ -> 
       Some (Some element, pred element) 
    let initialState = Some (None, true) 
    Seq.scan (folder predicate) initialState mySequence |> Seq.takeWhile Option.isSome 
                 |> Seq.map Option.get 
                 |> Seq.map fst 
                 |> Seq.filter Option.isSome 
                 |> Seq.map Option.get 

en la penúltima línea, |> Seq.filter Option.isSome puede ser reemplazado por |> Seq.tail, como no hay estados distintos de initialState partido Some (None, _).

Cuestiones relacionadas