2012-02-01 16 views
5

Mam wiele list - każda instancja zawiera 9 liczb zmiennoprzecinkowych. To, co muszę zrobić, to stworzyć jedną nową listę, która pobiera pierwszy element z każdej z moich list i dodaje je jako mój pierwszy element, a następnie dodaje drugi element z każdej listy jako mój drugi element, itd.Idiomatyczny sposób na "scalenie" wielu list o tej samej długości w F #?

skutecznie, jeśli moje dane wygląda mniej więcej tak:

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] 

Potem trzeba produkować nową listę Listx takie, że

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

liczbę wykazów będę łączących się różnić (czasami może tylko mieć jedną listę 9 liczb, a czasem więcej niż 100 listy, zawsze o długości 9 elementów), więc zastanawiałem się, czy ktoś ma jakąś radę na temat idiomatycznego sposobu robienia tego?

Spojrzałem na this question i this one, ale obie wydają się popierać pośredni krok indeksowania moich elementów, a następnie korzystania z groupby. To budzi mnie, ponieważ a) mam wrażenie, że może być bardziej eleganckie rozwiązanie dla mojej konkretnej sprawy i b) wydajność może być problemem później - nie chcę optymalizować przedwcześnie, ale też nie chcę strzelać do siebie w stopę.

+2

" * Wydajność może być problemem później - nie chcę optymalizować przedwcześnie, ale nie chcę też strzelać sobie w stopę. * "Zgadzam się z tym sentymentem, ale warto zauważyć, że jeśli odpowiednio obudujesz funkcjonalność, która później zmienia implementację, jeśli wydajność jest problemem, powinna być bezbolesna i nie wpływać na resztę kodu. – ildjarn

Odpowiedz

7

Oto rozwiązanie, które działa na liście list o tej samej długości:

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

Musisz zaznaczyć 'sumAll'' inline', aby działał z 'float' i innymi typami liczbowymi. – Daniel

+0

Dzięki za wskazanie, naprawione. – pad

+0

Idealny! Dziękuję Ci –

1

Oto jeden sposób:

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 

Kilka uwag:

  • List.reduce (+) służy zamiast List.sum lub List.sumBy, ponieważ te ostatnie działają tylko dla typów liczbowych, natomiast (+) może pracować dla np. string.
  • merge został wydedukowany jako typ int list list -> int list, a nie jako ogólny ze względu na subtelności sposobu działania operatora +. Jeśli wystarczy to do pracy dla jednego typu, a typ jest nieint (np float), a następnie dodanie typu adnotacji do merge będzie wystarczająca:

    let merge (lists:float list list) = 
    
  • merge mogą być oznaczone inline i wtedy będzie działać dla każdego typu obsługującego operatora +, ale spowoduje to dużo rozrostu w twojej IL, jeśli jest więcej niż jedna lub dwie strony wywoławcze. Jeśli masz wiele typów, które muszą współpracować z merge, a wszystkie są znane wcześniej, dobrym rozwiązaniem jest ustawienie merge (i ewentualnie private), a następnie zdefiniowanie różnych funkcji specyficznych dla danego typu, które są zaimplementowane pod kątem ogólnego 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 
    

    Jeśli to tylko zadzwonić typu specyficzne merge s, bloat IL powinien być znikomy.

  • Na koniec, jeśli wydajność to naprawdę, należy użyć tablic zamiast list.
2

pójdę za coś łatwiejszego jak matrix:

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

Oto alternatywa jedna wkładka z wartościami domyślnymi, gdy lista list jest pusty:

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 
Powiązane problemy