2012-02-01 11 views
5

Ho un numero di elenchi - ogni istanza di cui contiene 9 numeri in virgola mobile. Quello che ho effettivamente bisogno di fare è produrre una nuova lista che prende il primo elemento da ciascuna delle mie liste e li aggiunge come il mio primo elemento, quindi aggiungi il secondo elemento da ogni elenco come il mio secondo elemento, ecc.Modo idiomatico di "unire" più elenchi della stessa lunghezza in F #?

Quindi in modo efficace, se il mio dati simile a questa:

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] 

poi ho bisogno di produrre un nuovo elenco Listx tale che

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

Il numero di liste sarò la fusione può variare (a volte ho solo può avere una sola lista di 9 numeri, e talvolta più di 100 liste, sempre lunghe 9 elementi), quindi mi chiedevo se qualcuno ha qualche consiglio su un bel modo idiomatico di farlo?

Ho dato un'occhiata a this question e this one ma entrambi sembrano sostenere un passaggio intermedio di indicizzazione prima i miei elementi e quindi utilizzando groupby. Questo mi infastidisce perché a) ho la sensazione che ci potrebbe essere una soluzione più elegante per il mio caso particolare eb) le prestazioni potrebbero essere un problema più tardi - non voglio ottimizzarle prematuramente, ma non voglio nemmeno spararmi nel piede.

+2

" * Le prestazioni potrebbero essere un problema più tardi, non voglio ottimizzarle prematuramente, ma non voglio nemmeno spararmi ai piedi. * "Sono d'accordo con questo sentimento, ma vale la pena notare che se incapsuli correttamente la funzionalità quindi modificare l'implementazione in un secondo momento se le prestazioni sono un problema dovrebbe essere indolore e non influenzare il resto del codice. – ildjarn

risposta

7

Ecco una soluzione che funziona su una lista di liste con la stessa lunghezza:

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

È necessario contrassegnare 'sumAll'' inline' perché funzioni con 'float' e altri tipi numerici. – Daniel

+0

Grazie per aver segnalato, risolto. – pad

+0

Perfetto! Grazie –

1

Ecco uno approccio:

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 

Alcune note:

  • List.reduce (+) viene utilizzato invece di List.sum o List.sumBy perché quest'ultimo funziona solo con tipi numerici mentre (+) può funzionare per es. string.
  • merge viene considerato di tipo int list list -> int list anziché generico a causa di sottigliezze del modo in cui l'operatore + funziona. Se avete solo bisogno questo a lavorare per un unico tipo, e che tipo è nonint (ad es float), quindi l'aggiunta di un tipo di annotazione per merge sarà sufficiente:

    let merge (lists:float list list) = 
    
  • merge può essere contrassegnato inline e quindi funzionerà per qualsiasi tipo che supporta l'operatore +, ma questo porterà a un sacco di ingigantire il tuo IL se ci sono più di uno o due siti di chiamata. Se hai più tipi che devono lavorare con merge e tutti sono noti in anticipo, allora una buona soluzione è fare mergeinline (e possibilmente private) quindi definire diverse funzioni specifiche del tipo che sono implementate in termini di merge generico:

    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 
    

    Se quindi si chiama solo le specifiche di tipo merge s, la pesantezza iL dovrebbe essere trascurabile.

  • Infine, se la prestazione è veramente una preoccupazione, quindi utilizzare le matrici anziché le liste.
2

mi piacerebbe andare via qualcosa di più facile come una matrice:

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

Ecco un uno di linea alternativa con valori di default quando l'elenco delle liste è vuota:

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 
Problemi correlati