2009-04-04 16 views
6

Tengo problemas para hacer una secuencia. Básicamente, necesito cortar una secuencia en una secuencia de matrices. Seq.windowed casi lo hace, pero no quiero elementos duplicados.F # array_chunk para la secuencia

Puedo obtener lo que quiero leyendo todo en una matriz primero, pero prefiero usar una secuencia.

let array_chunk s (a:int[]) = 
    Array.init (a.Length/s) (fun i -> Array.sub a (i * s) s) 

someSequence |> Seq.to_array |> array_chunk 5 

Respuesta

5

Aquí hay un buen imperativo que funcionará con seq y generar matrices de cualquier tamaño. El último será más pequeño si la secuencia no está igualada por n.

let chunk n xs = seq { 
    let i = ref 0 
    let arr = ref <| Array.create n (Unchecked.defaultof<'a>) 
    for x in xs do 
     if !i = n then 
      yield !arr 
      arr := Array.create n (Unchecked.defaultof<'a>) 
      i := 0 
     (!arr).[!i] <- x 
     i := !i + 1 
    if !i <> 0 then 
     yield (!arr).[0..!i-1] } 
+0

Gran respuesta. Estaba cerca de esto con mi código pero no lo tenía. – gradbot

3

¿Qué tal:

let rec chunks n sq = 
    if not (Seq.is_empty sq) then 
    seq { 
     yield Seq.take n sq |> Seq.to_array 
     yield! chunks n (Seq.skip n sq) 
    } 
    else 
    Seq.empty 

Tenga en cuenta que esto requiere cuadrados a tener una serie de elementos que es divisible por n (porque Seq.take y Seq.skip, a diferencia de La Opinión de LINQ y Skip extensión métodos, requieren que la secuencia contenga al menos n elementos). Además, esto no es tan eficiente como sería el uso explícito del enumerador, pero es más elegante.

+0

Quería votar a favor del secuencial recursivo (una técnica que personalmente uso mucho), pero este código arroja una excepción cuando sq.Length no es divisible uniformemente por n. – Juliet

+0

Sí, lo mencioné después del código. Sería bueno si Seq.take y Seq.skip se implementaran más como las operaciones correspondientes de LINQ, de modo que no requirieran al menos tantos elementos como se están tomando u omitiendo. – kvb

+0

Ver mi respuesta here Benjol

5

Me encanta solución Seq.take & Seq.skip. Es hermoso, simple y fácil de leer, pero me gustaría utilizar algo como esto:

let chunks n (sequence: seq<_>) = 
    let fold_fce (i, s) value = 
     if i < n then (i+1, Seq.append s (Seq.singleton value)) 
       else ( 1, Seq.singleton value) 
    in sequence 
    |> Seq.scan (fold_fce) (0, Seq.empty) 
    |> Seq.filter (fun (i,_) -> i = n) 
    |> Seq.map (Seq.to_array << snd) 

No es imprescindible código y debe ser más eficiente que la solución que utiliza Seq.skip. Por otro lado, recorta la secuencia de entrada a la longitud divisible por n. Si este comportamiento es inaceptable que puede ser fijado por modificación simple:

let chunks n (sequence: seq<_>) = 
    let fold_fce (i, s) value = 
     if i < n then (i+1, Seq.append s (Seq.singleton value)) 
       else ( 1, Seq.singleton value) 
    in sequence 
    |> Seq.map (Some) 
    |> fun s -> Seq.init_finite (n-1) (fun _ -> None) |> Seq.append s 
    |> Seq.scan (fold_fce) (0, Seq.empty) 
    |> Seq.filter (fun (i,_) -> i = n) 
    |> Seq.map (Seq.to_array << (Seq.choose (id)) << snd) 
+0

Fui con esta respuesta, ya que aprender a entender este código me ha dado más información sobre F #. – gradbot

+0

Hice un banco rápido y su primera solución es aproximadamente un 50% más lenta que la solución imperativa MichaelGG y la segunda es aproximadamente un 100% más lenta. Terminé usando tu primera solución. Gracias :) – gradbot

+1

Para usar un estilo sin sentido, puede hacer "|> Seq.filter (fst >> (=) n)" "|> Seq.filter (fun (i, _) -> i = n)". – MichaelGG

1

Esto es agradable y sucinta:

let chunk size (arr : 'a array) = 
    [| for a in 0 .. size .. arr.Length - size -> arr.[a..a + size - 1] |] 

Sin embargo, este lops fuera el último (tamaño arr.Length%) elementos en el formación. Puedes solucionar este problema por el acaparamiento de los elementos que faltan y el uso de Array.append:

let chunk2 size (arr : 'a array) = 
    let first = [| for a in 0 .. size .. arr.Length - size -> arr.[a..a + size - 1] |] 
    let numberOfMissingElements = arr.Length - (first.Length * size) 
    if numberOfMissingElements > 0 then 
     let last = [| arr.[arr.Length - numberOfMissingElements..] |] 
     Array.append first last 
    else first 
0

Aquí hay otro enfoque con un poco de coincidencia de patrones - se parece más a * .iter, y tengo que escupir listas en lugar de matrices , ya que así es como generalmente me gustan mis datos.

let sequence_in_lists_of_length_n_with_acc (s: seq<'a>) n acc = seq { 
use e = s.GetEnumerator() 
let rec next_with_acc acc = 
    match e.MoveNext(), acc with 
    | true, a when List.length a + 1 = n -> 
    yield (List.rev (e.Current :: a)) 
    next_with_acc [] 
    | true, _ -> next_with_acc (e.Current :: acc) 
    | false, _ -> 
    f(List.rev acc) 
() 
next_with_acc [] 

}

0

me está gustando esta solución mejor. Genera una nueva secuencia a partir de la secuencia existente (lo que significa que no necesita atravesar toda la secuencia para obtener un resultado; eso es crítico si está haciendo algo como procesamiento de registro, donde no puede llamar cosas como Longitud).

Terminé escribiendo blog post con más detalles sobre cómo llegué aquí.

module Seq = 

vamos grouped_by_with_leftover_processing f (f2: 'una lista -> lista <' a> opcional) (s: ss < 'a>) = vamos grouped_by_with_acc rec (f:' a -> 'una lista - > 'una lista de opciones *' una lista) ACC (es decir: IEnumerator < 'a>) = {ss si ie.MoveNext() continuación dejó nextValue, sobras = f ie.Current acc si nextValue.IsSome entonces rendimiento nextValue.¡Valor rendimiento! sobras grouped_by_with_acc f, es decir demás dejar que rem = f2 acc si rems.IsSome luego dió rems.Value } {SEC rendimiento! grouped_by_with_acc f [] (s.GetEnumerator())}

vamos YieldReversedLeftovers (f: 'una lista) = si f.IsEmpty entonces ninguno otra cosa Algunos (List.rev f)

Let grouped_by fs = grouped_by_with_leftover_processing YieldReversedLeftovers Fs

dejar que group_by_length_n ns = dejó grouping_function nuevoValor acc = deja que newList = nuevoValor :: acc // Si tenemos la longitud correcta, volver // a Algunos como el primer valor. Eso va a // ser cedido por la secuencia. if List.length acc = n - 1 luego Some (List.rev newList), [] // Si no tenemos la longitud correcta, // use None (por lo que no se producirá nada) else Ninguno , newList

secuencias grandes grouped_by grouping_function s no son un problema:

ss {for i en 1..1000000000 - > i} | > Seq.group_by_length_n 3 ;; val it: seq < lista int > = seq [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]; [10; 11; 12]; ...] >
0

versión nueva de la princesa se ha corregido para obtener la cola y se convierte en la SEC

let array_chunk size (arr : 'a array) = 
    let maxl = arr.Length - 1 
    seq { for a in 0 .. size .. maxl -> arr.[a .. min (a + size - 1) maxl ] } 
3

versión corregida de toma/saltar respuesta, como función de extensión. Debería trabajar para longitudes desiguales. No hay garantías de rendimiento, aunque ...

module Seq = 
    let rec chunks n (s:#seq<_>) = 
     seq { 
       if Seq.length s <= n then 
        yield s 
       else 
        yield Seq.take n s 
        yield! chunks n (Seq.skip n s)   
      } 

(Código tomado de mi respuesta here)

+0

Si bien esto es simple y limpio, es O (n^2). Gracias por la respuesta. :) – gradbot

+1

No soy un experto, pero por lo que he visto, optimizar el rendimiento en F # a menudo termina con la mutabilidad, aunque no lo sé, siempre es así. – Benjol

+1

Es O (n^2) debido a Seq.length es O (n) - en general. Si s es una matriz, entonces Seq.length es O (1) y chunks() es solo O (n) – Ray

0

¿Qué tal este:

let grouped n = 
    Seq.unfold(fun s -> if not (Seq.isEmpty s) then 
          Some (Seq.take n s, Seq.skip n s) 
         else None) 

Es en el mismo sentido que la respuesta de kvb.

De alguna manera recuerdo (¿un vínculo?) Que una secuencia no recuerda la posición, por lo que las sucesivas tomas/omisiones no serán óptimas.

4

Esta respuesta probablemente obtendrá enterrado, pero aquí está mi opinión sobre el problema:

let chunk n xs = 
    xs 
    |> Seq.mapi(fun i x -> i/n, x) 
    |> Seq.groupBy fst 
    |> Seq.map (fun (_, g) -> Seq.map snd g) 

Pros:

  • utiliza sólo ss, no hay matrices
  • O (n) tiempo de ejecución. No O (n^2) como soluciones Seq.skip/take
  • Seq.la longitud no tiene que ser un múltiplo de n
  • pequeña y fácil de entender?

Contras:

  • probablemente no es tan eficiente como bucles imperativas/mutables
+0

Como nota aparte, algunas funciones en 'Seq' enumeran toda la colección que se les proporciona tan pronto como acceden a su primer elemento . 'Seq.groupBy' usa un diccionario para hacer la agrupación. https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/seq.fs#L1273 – gradbot

0

Aquí está la solución de @ kvb con el Seq.skip/toma limitaciones fijas. Es pequeño, elegante y O (n).

let eSkip n s = System.Linq.Enumerable.Skip(s, n) 

let rec seq_chunks n sq = 
    if (Seq.isEmpty sq) 
    then Seq.empty 
    else seq { 
     yield Seq.truncate n sq 
     yield! seq_chunks n (eSkip n sq) 
} 
+0

Esto no es O (n). Es O (n^2). Intente pasar una secuencia que imprime evaluaciones. 'seq {for i in 1 .. 15 do printf"% A "i; rendimiento i} ' – gradbot

+0

Santa mierda! tienes razón. eSkip & Seq.skip están haciendo cosas muy extrañas ... – Ray

0

Aquí está tomando mi versión de una matriz como la entrada y salida:

let chunk chunkNumber (array : _ array) = 
    let chunkSize = array.Length/chunkNumber 
    let mutable startIndex = 0 
    [| 
     let n1 = array.Length % chunkNumber 
     for i = 1 to n1 do 
      yield Array.sub array startIndex (chunkSize+1) 
      startIndex <- startIndex + chunkSize+1 

     let n2 = chunkNumber - n1 
     for i = 1 to n2 do 
      yield Array.sub array startIndex chunkSize 
      startIndex <- startIndex + chunkSize 
    |] 

La función intenta hacer trozos de tamaño similar (en vez de conseguir una muy pequeña último fragmento) y construye la salida de la Del mismo modo que construirías una secuencia (facilitando la reescritura para obtener una secuencia como salida)

Cuestiones relacionadas