tabla secuencias secuencia numeros incrementar generar ejemplo developer currval create crear consultar como alfanumerica f# sequences

numeros - F#: ¿Cómo puedo dividir una secuencia en una secuencia de secuencias?



secuencia alfanumerica oracle (8)

Fondo:

Tengo una secuencia de datos contiguos, con sello de tiempo. La secuencia de datos tiene lagunas en donde los datos no son contiguos. Quiero crear un método para dividir la secuencia en una secuencia de secuencias para que cada subsecuencia contenga datos contiguos (divida la secuencia de entrada en los espacios).

Restricciones:

  • El valor de retorno debe ser una secuencia de secuencias para garantizar que los elementos solo se producen según sea necesario (no se puede usar list / array / cacheing)
  • La solución NO debe ser O (n ^ 2), probablemente descartando un patrón Seq.take - Seq.skip (véase la publicación de Brian )
  • Puntos de bonificación para un enfoque funcionalmente idiomático (ya que quiero ser más competente en programación funcional), pero no es un requisito.

Firma del método

let groupContiguousDataPoints (timeBetweenContiguousDataPoints : TimeSpan) (dataPointsWithHoles : seq<DateTime * float>) : (seq<seq< DateTime * float >>)= ...

A primera vista, el problema me parecía trivial, pero incluso empleando Seq.pairwise, IEnumerator <_>, comprensión de secuencias y declaraciones de rendimiento, la solución se me escapa. Estoy seguro de que esto se debe a que todavía carezco de experiencia con la combinación de los valores F # -idioms, o posiblemente porque hay algunas construcciones lingüísticas a las que aún no he estado expuesto.

// Test data let numbers = {1.0..1000.0} let baseTime = DateTime.Now let contiguousTimeStamps = seq { for n in numbers ->baseTime.AddMinutes(n)} let dataWithOccationalHoles = Seq.zip contiguousTimeStamps numbers |> Seq.filter (fun (dateTime, num) -> num % 77.0 <> 0.0) // Has a gap in the data every 77 items let timeBetweenContiguousValues = (new TimeSpan(0,1,0)) dataWithOccationalHoles |> groupContiguousDataPoints timeBetweenContiguousValues |> Seq.iteri (fun i sequence -> printfn "Group %d has %d data-points: Head: %f" i (Seq.length sequence) (snd(Seq.hd sequence)))


Debajo hay un código que hace lo que creo que quieres. No es idiomático F #.

(Puede ser similar a la respuesta de Brian, aunque no puedo decirlo porque no estoy familiarizado con la semántica de LazyList).

Pero no coincide exactamente con la especificación de la prueba: Seq.length enumera toda su entrada. Su "código de prueba" llama a Seq.length y luego llama a Seq.hd Eso generará un enumerador dos veces, y como no hay memoria caché, las cosas se arruinan. No estoy seguro de si hay alguna forma limpia de permitir múltiples enumeradores sin almacenar en caché. Francamente, seq<seq<''a>> puede no ser la mejor estructura de datos para este problema.

De todos modos, aquí está el código:

type State<''a> = Unstarted | InnerOkay of ''a | NeedNewInner of ''a | Finished // f() = true means the neighbors should be kept together // f() = false means they should be split let split_up (f : ''a -> ''a -> bool) (input : seq<''a>) = // simple unfold that assumes f captured a mutable variable let iter f = Seq.unfold (fun _ -> match f() with | Some(x) -> Some(x,()) | None -> None) () seq { let state = ref (Unstarted) use ie = input.GetEnumerator() let innerMoveNext() = match !state with | Unstarted -> if ie.MoveNext() then let cur = ie.Current state := InnerOkay(cur); Some(cur) else state := Finished; None | InnerOkay(last) -> if ie.MoveNext() then let cur = ie.Current if f last cur then state := InnerOkay(cur); Some(cur) else state := NeedNewInner(cur); None else state := Finished; None | NeedNewInner(last) -> state := InnerOkay(last); Some(last) | Finished -> None let outerMoveNext() = match !state with | Unstarted | NeedNewInner(_) -> Some(iter innerMoveNext) | InnerOkay(_) -> failwith "Move to next inner seq when current is active: undefined behavior." | Finished -> None yield! iter outerMoveNext } open System let groupContigs (contigTime : TimeSpan) (holey : seq<DateTime * int>) = split_up (fun (t1,_) (t2,_) -> (t2 - t1) <= contigTime) holey // Test data let numbers = {1 .. 15} let contiguousTimeStamps = let baseTime = DateTime.Now seq { for n in numbers -> baseTime.AddMinutes(float n)} let holeyData = Seq.zip contiguousTimeStamps numbers |> Seq.filter (fun (dateTime, num) -> num % 7 <> 0) let grouped_data = groupContigs (new TimeSpan(0,1,0)) holeyData printfn "Consuming..." for group in grouped_data do printfn "about to do a group" for x in group do printfn " %A" x


Parece que quieres una función que tenga firma

(`a -> bool) -> seq<''a> -> seq<seq<''a>>

Es decir, una función y una secuencia, luego divida la secuencia de entrada en una secuencia de secuencias basada en el resultado de la función.

El almacenamiento en memoria caché de los valores en una colección que implementa IEnumerable probablemente sería el más simple (aunque no exactamente purista, pero evitando repetir la entrada varias veces. Perderá gran parte de la pereza de la entrada):

let groupBy (fun: ''a -> bool) (input: seq) = seq { let cache = ref (new System.Collections.Generic.List()) for e in input do (!cache).Add(e) if not (fun e) then yield !cache cache := new System.Collections.Generic.List() if cache.Length > 0 then yield !cache }

Una implementación alternativa podría pasar la colección de caché (como seq<''a> ) a la función para que pueda ver múltiples elementos para elegir los puntos de ruptura.


Traduje el Haskell de Alexey a F #, pero no es bonito en F #, y sigue siendo un elemento demasiado ansioso.

Espero que haya una mejor manera, pero tendré que volver a intentarlo más tarde.

let N = 20 let data = // produce some arbitrary data with holes seq { for x in 1..N do if x % 4 <> 0 && x % 7 <> 0 then printfn "producing %d" x yield x } let rec GroupBy comp (input:LazyList<''a>) : LazyList<LazyList<''a>> = LazyList.delayed (fun () -> match input with | LazyList.Nil -> LazyList.cons (LazyList.empty()) (LazyList.empty()) | LazyList.Cons(x,LazyList.Nil) -> LazyList.cons (LazyList.cons x (LazyList.empty())) (LazyList.empty()) | LazyList.Cons(x,(LazyList.Cons(y,_) as xs)) -> let groups = GroupBy comp xs if comp x y then LazyList.consf (LazyList.consf x (fun () -> let (LazyList.Cons(firstGroup,_)) = groups firstGroup)) (fun () -> let (LazyList.Cons(_,otherGroups)) = groups otherGroups) else LazyList.cons (LazyList.cons x (LazyList.empty())) groups) let result = data |> LazyList.of_seq |> GroupBy (fun x y -> y = x + 1) printfn "Consuming..." for group in result do printfn "about to do a group" for x in group do printfn " %d" x


Una solución Haskell, porque no sé bien la sintaxis F #, pero debería ser lo suficientemente fácil de traducir:

type TimeStamp = Integer -- ticks type TimeSpan = Integer -- difference between TimeStamps groupContiguousDataPoints :: TimeSpan -> [(TimeStamp, a)] -> [[(TimeStamp, a)]]

Hay una función groupBy :: (a -> a -> Bool) -> [a] -> [[a]] en el Preludio:

La función de grupo toma una lista y devuelve una lista de listas tal que la concatenación del resultado es igual al argumento. Además, cada sublista en el resultado contiene solo elementos iguales. Por ejemplo,

group "Mississippi" = ["M","i","ss","i","ss","i","pp","i"]

Es un caso especial de groupBy, que le permite al programador proporcionar su propia prueba de igualdad.

No es exactamente lo que queremos, porque compara cada elemento de la lista con el primer elemento del grupo actual, y tenemos que comparar elementos consecutivos. Si tuviéramos dicho grupo de funcionesBy1, podríamos escribir groupContiguousDataPoints fácilmente:

groupContiguousDataPoints maxTimeDiff list = groupBy1 (/(t1, _) (t2, _) -> t2 - t1 <= maxTimeDiff) list

¡Así que vamos a escribirlo!

groupBy1 :: (a -> a -> Bool) -> [a] -> [[a]] groupBy1 _ [] = [[]] groupBy1 _ [x] = [[x]] groupBy1 comp (x : xs@(y : _)) | comp x y = (x : firstGroup) : otherGroups | otherwise = [x] : groups where groups@(firstGroup : otherGroups) = groupBy1 comp xs

ACTUALIZACIÓN: parece que F # no permite que el patrón coincida con el seq , por lo que no es muy fácil de traducir después de todo. Sin embargo, este hilo en HubFS muestra una forma de emparejar secuencias de patrones convirtiéndolos a LazyList cuando sea necesario.

ACTUALIZACIÓN2: las listas de Haskell son flojas y se generan según sea necesario, por lo que corresponden a LazyList F # (no a la seq , porque los datos generados se almacenan en la LazyList caché (y se recogen, por supuesto, si ya no se hace referencia)).


(EDITAR: Esto tiene un problema similar al de la solución de Brian, ¡en eso iterar la secuencia externa sin iterar sobre cada secuencia interna ensuciará las cosas mal!)

Aquí hay una solución que anida las expresiones de secuencia. La naturaleza imperfecta de IEnumerable<T> de .NET es bastante evidente aquí, lo que hace que sea un poco más difícil escribir el código F # idiomático para este problema, pero espero que todavía esté claro lo que está sucediendo.

let groupBy cmp (sq:seq<_>) = let en = sq.GetEnumerator() let rec partitions (first:option<_>) = seq { match first with | Some first'' -> //'' (* The following value is always overwritten; it represents the first element of the next subsequence to output, if any *) let next = ref None (* This function generates a subsequence to output, setting next appropriately as it goes *) let rec iter item = seq { yield item if (en.MoveNext()) then let curr = en.Current if (cmp item curr) then yield! iter curr else // consumed one too many - pass it on as the start of the next sequence next := Some curr else next := None } yield iter first'' (* '' generate the first sequence *) yield! partitions !next (* recursively generate all remaining sequences *) | None -> () // return an empty sequence if there are no more values } let first = if en.MoveNext() then Some en.Current else None partitions first let groupContiguousDataPoints (time:TimeSpan) : (seq<DateTime*_> -> _) = groupBy (fun (t,_) (t'',_) -> t'' - t <= time)


De acuerdo, intentando de nuevo. Lograr la cantidad óptima de pereza resulta ser un poco difícil en F # ... Por el lado positivo, esto es algo más funcional que mi último intento, en el sentido de que no utiliza ninguna celda de ref.

let groupBy cmp (sq:seq<_>) = let en = sq.GetEnumerator() let next() = if en.MoveNext() then Some en.Current else None (* this function returns a pair containing the first sequence and a lazy option indicating the first element in the next sequence (if any) *) let rec seqStartingWith start = match next() with | Some y when cmp start y -> let rest_next = lazy seqStartingWith y // delay evaluation until forced - stores the rest of this sequence and the start of the next one as a pair seq { yield start; yield! fst (Lazy.force rest_next) }, lazy Lazy.force (snd (Lazy.force rest_next)) | next -> seq { yield start }, lazy next let rec iter start = seq { match (Lazy.force start) with | None -> () | Some start -> let (first,next) = seqStartingWith start yield first yield! iter next } Seq.cache (iter (lazy next()))


Ok, aquí hay una respuesta con la que no estoy contento.

(EDITAR: No estoy contento, está mal, pero no hay tiempo para intentar arreglarlo ahora).

Utiliza un poco de estado imperativo, pero no es demasiado difícil de seguir (siempre que recuerde que ''!'' Es el operador de desreferencia F #, y no ''no''). Es tan vago como sea posible, y toma un seq como entrada y devuelve un seq de seqs como salida.

let N = 20 let data = // produce some arbitrary data with holes seq { for x in 1..N do if x % 4 <> 0 && x % 7 <> 0 then printfn "producing %d" x yield x } let rec GroupBy comp (input:seq<_>) = seq { let doneWithThisGroup = ref false let areMore = ref true use e = input.GetEnumerator() let Next() = areMore := e.MoveNext(); !areMore // deal with length 0 or 1, seed ''prev'' if not(e.MoveNext()) then () else let prev = ref e.Current while !areMore do yield seq { while not(!doneWithThisGroup) do if Next() then let next = e.Current doneWithThisGroup := not(comp !prev next) yield !prev prev := next else // end of list, yield final value yield !prev doneWithThisGroup := true } doneWithThisGroup := false } let result = data |> GroupBy (fun x y -> y = x + 1) printfn "Consuming..." for group in result do printfn "about to do a group" for x in group do printfn " %d" x


Creo que esto hace lo que quieres

dataWithOccationalHoles |> Seq.pairwise |> Seq.map(fun ((time1,elem1),(time2,elem2)) -> if time2-time1 = timeBetweenContiguousValues then 0, ((time1,elem1),(time2,elem2)) else 1, ((time1,elem1),(time2,elem2)) ) |> Seq.scan(fun (indexres,(t1,e1),(t2,e2)) (index,((time1,elem1),(time2,elem2))) -> (index+indexres,(time1,elem1),(time2,elem2)) ) (0,(baseTime,-1.0),(baseTime,-1.0)) |> Seq.map( fun (index,(time1,elem1),(time2,elem2)) -> index,(time2,elem2) ) |> Seq.filter( fun (_,(_,elem)) -> elem <> -1.0) |> PSeq.groupBy(fst) |> Seq.map(snd>>Seq.map(snd))

Gracias por hacer esta buena pregunta