Идиоматический способ "слить" несколько списков одной длины в F #?
У меня есть несколько списков, каждый из которых содержит 9 чисел с плавающей запятой. То, что мне действительно нужно сделать, это создать один новый список, который берет первый элемент из каждого из моих списков и добавляет их вместе в качестве моего первого элемента, а затем добавляет второй элемент из каждого списка в качестве моего второго элемента и т.д.
Так эффективно, если мои данные выглядят примерно так:
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]
Затем мне нужно создать новый список Listx таким образом, чтобы
Listx = [a1 + a2 + ... + an; b1 + b2 + ... + bn; ... ]
Количество списков, которые я буду слиянием, будет меняться (иногда у меня может быть только один список из 9 чисел, а иногда и более 100 списков, всегда 9 элементов), поэтому мне было интересно, есть ли у кого-нибудь совет по хороший идиоматический способ сделать это?
Я просмотрел этот вопрос и этот, но оба, похоже, выступают за промежуточный шаг сначала индексировать мои элементы, а затем использовать groupby. Это меня задевает, потому что я чувствую, что может быть более элегантное решение для моего конкретного случая, и б) производительность может быть проблемой позже - я не хочу оптимизировать преждевременно, но я также не хочу снимать себя в ноге.
Ответы
Ответ 1
Вот решение, которое работает в списке списков с одинаковой длиной:
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
Ответ 2
Я бы сделал что-то проще, чем матрица:
let merge xss =
let m = matrix xss
List.map (m.Column >> Seq.sum) [0..m.NumCols-1]
Ответ 3
Здесь один подход:
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
Некоторые примечания:
-
List.reduce (+)
используется вместо List.sum
или List.sumBy
, потому что последний работает только для числовых типов, тогда как (+)
может работать, например. string
.
-
merge
выводится как тип int list list -> int list
, а не является общим из-за тонкостей работы оператора +
. Если вам нужна только эта работа для одного типа, и этот тип не является int
(например, float
), добавление аннотации типа к merge
будет достаточным:
let merge (lists:float list list) =
-
merge
может быть помечен inline
, а затем будет работать для любого типа, поддерживающего оператор +
, но это приведет к большому раздутию в вашем IL, если есть более одного или двух вызовов, места. Если у вас есть несколько типов, которые должны работать с merge
, и все они известны заранее, тогда хорошим решением будет сделать merge
inline
(и, возможно, private
), а затем определить различные специфичные для типа функции, которые реализуется с точки зрения общего 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
Если вы тогда вызываете только тип merge
s, то набегание IL должно быть незначительным.
- Наконец, если производительность действительно вызывает беспокойство, используйте массивы, а не списки.
Ответ 4
// 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]
Ответ 5
Здесь альтернативный один вкладыш со значениями по умолчанию, когда список списков пуст:
let sumVertically length = List.fold (List.map2 (+)) (List.replicate length 0)
//usage
//stolen from pad answer
let listOfLists = List.init 1000 (fun i -> [i+1..i+9])
sumVertically 9 listOfLists