Идиоматический способ «слить» несколько списков одинаковой длины в F #? - PullRequest
5 голосов
/ 01 февраля 2012

У меня есть несколько списков - каждый экземпляр содержит 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 элементов), поэтому мне было интересно, есть ли у кого-нибудь какие-либо советы по поводу хорошего идиоматического способа сделать это?

Я взглянул на этот вопрос и на этот , но оба, похоже, выступают за промежуточный этап индексации моих элементов сначала, а затем с помощью групповой работы.Это меня беспокоит, потому что а) у меня такое ощущение, что может быть более элегантное решение для моего конкретного случая, и б) производительность может быть проблемой позже - я не хочу преждевременно оптимизировать, но я также не хочу стрелять в себяв ногу.

Ответы [ 5 ]

7 голосов
/ 02 февраля 2012

Вот решение, которое работает со списком списков одинаковой длины:

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 голосов
/ 03 февраля 2012

Я бы прошел через что-то более простое, например матрицу:

let merge xss =
  let m = matrix xss
  List.map (m.Column >> Seq.sum) [0..m.NumCols-1]
1 голос
/ 05 февраля 2012
// 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]
1 голос
/ 02 февраля 2012

Вот один из подходов:

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 должно быть незначительным.

  • Наконец, если производительность составляет действительно , то используйте массивы, а не списки.
0 голосов
/ 12 января 2017

Вот альтернативный вкладыш со значениями по умолчанию, когда список списков пуст:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...