2012-02-01 9 views
5

Tengo varias listas, cada una de las cuales contiene 9 números de coma flotante. Lo que efectivamente necesito hacer es producir una nueva lista que tome el primer elemento de cada una de mis listas y las agregue como mi primer elemento, luego agregue el segundo elemento de cada lista como mi segundo elemento, etc.¿Forma idiomática de "combinar" varias listas de la misma longitud en F #?

Entonces efectivamente, si mis datos se ve algo como esto:

List1 = [a1; b1; c1; d1; e1; f1; g1; h1; i1] 
List2 = [a2; b2; c2; d2; e2; f2; g2; h2; i2] 
... 
Listn = [an; bn; cn; dn; en; fn; gn; hn; in] 

entonces necesito para producir una nueva lista Listx tal que

Listx = [a1 + a2 + ... + an; b1 + b2 + ... + bn; ... ] 

El número de listas voy a la fusión variará (a veces sólo puede tener una lista única de 9 números, y a veces más de 100 listas, siempre 9 elementos de largo), así que me preguntaba si alguien tiene algún consejo sobre una buena manera idiomática de hacer esto?

Eché un vistazo a this question y this one, pero ambos parecen abogar por un paso intermedio de indexar mis elementos primero y luego usar groupby. Esto me molesta porque a) tengo la sensación de que podría haber una solución más elegante para mi caso particular yb) el rendimiento puede ser un problema más tarde. No quiero optimizarlo prematuramente, pero tampoco me quiero fotografiar. en el pie

+2

" * el rendimiento puede ser un problema más adelante; no quiero optimizarlo prematuramente, pero tampoco quiero dispararme en el pie. * "Estoy de acuerdo con este sentimiento, pero vale la pena señalar que si encapsulas adecuadamente la funcionalidad que luego cambia la implementación más adelante si el rendimiento es un problema debería ser sencillo y no afectar el resto de su código. – ildjarn

Respuesta

7

Aquí es una solución que funciona en una lista de listas con la misma longitud:

let mapN f = function 
    | [] -> [] 
    | xss -> List.reduce (List.map2 f) xss 

let inline merge xss = mapN (+) xss 

// Usage 
let yss = List.init 1000 (fun i -> [i+1..i+9]) 
let ys = merge yss 
+1

Debe marcar 'sumAll'' inline' porque funciona con 'float' y otros tipos numéricos. – Daniel

+0

Gracias por señalar, fijo. – pad

+0

¡Perfecto! Gracias –

1

Aquí es uno de los enfoques:

let merge lists = 
    let rec impl acc lists = 
    match List.head lists with 
    | [] -> List.rev acc 
    | _ -> let acc' = (lists |> List.map List.head |> List.reduce (+))::acc 
      let lists' = List.map List.tail lists 
      impl acc' lists' 
    impl [] lists 

Algunas notas:

  • List.reduce (+) se usa en lugar de List.sum o List.sumBy porque este último solo funciona para los tipos numéricos mientras que (+) puede funcionar, p. string.
  • merge se deduce que es del tipo int list list -> int list en lugar de ser genérico debido a las sutilezas de la forma en que opera el operador +. Si sólo necesita que esto funcione para un solo tipo, y que tipo es noint (por ejemplo float), a continuación, añadir una anotación de tipo de merge será suficiente:

    let merge (lists:float list list) = 
    
  • merge se puede marcar inline y luego funcionará para cualquier tipo que admita el operador +, pero esto generará muchas molestias en su IL si hay más de uno o dos call-sites. Si tiene varios tipos que necesitan trabajar con merge, y todos son conocidos de antemano, una buena solución aquí es hacer mergeinline (y posiblemente private) luego definir diferentes funciones específicas de tipo que se implementan en términos del genérico merge:

    let inline merge lists = 
        let rec impl acc lists = 
        match List.head lists with 
        | [] -> List.rev acc 
        | _ -> let acc' = (lists |> List.map List.head |> List.reduce (+))::acc 
          let lists' = List.map List.tail lists 
          impl acc' lists' 
        impl [] lists 
    
    let mergeInts (lists:int list list) = merge lists 
    let mergeFloats (lists:float list list) = merge lists 
    let mergeStrings (lists:string list list) = merge lists 
    

    Si a continuación, sólo se llama a los específicos del tipo merge s, la hinchazón IL debería ser insignificante.

  • Por último, si el rendimiento es realmente una preocupación, entonces use matrices en lugar de listas.
2

me gustaría ir a través de algo más sencillo como una matriz:

let merge xss = 
    let m = matrix xss 
    List.map (m.Column >> Seq.sum) [0..m.NumCols-1] 
1
// Shamelessly stolen from: 
// http://hackage.haskell.org/packages/archive/base/latest/doc/html/src/Data-List.html#transpose 
let rec transpose = function 
    | [] -> [] 
    | ([] :: xss) -> transpose xss 
    | ((x :: xs) :: xss) -> (x :: List.map List.head xss) :: transpose (xs :: List.map List.tail xss) 

let fuse = transpose >> List.map List.sum 

printfn "%A" (fuse [[3; 4]; [1; 90]; [34; 89]]) // prints [38; 183] 
0

He aquí un chiste alternativa con valores por defecto cuando la lista de listas está vacía:

let sumVertically length = List.fold (List.map2 (+)) (List.replicate length 0) 

//usage 
//stolen from pad's answer 
let listOfLists = List.init 1000 (fun i -> [i+1..i+9]) 
sumVertically 9 listOfLists 
Cuestiones relacionadas