在 F# 中“合并”多个相同长度的列表的惯用方式?

Posted

技术标签:

【中文标题】在 F# 中“合并”多个相同长度的列表的惯用方式?【英文标题】:Idiomatic way to "merge" multiple lists of the same length in F#? 【发布时间】:2012-02-24 12:19:50 【问题描述】:

我有许多列表——每个列表都包含 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 个元素长),所以我想知道是否有人对这样做的好习惯方式?

我确实看过this question 和this one,但两者似乎都提倡先索引我的元素然后使用groupby 的中间步骤。这让我很烦恼,因为a)我觉得对于我的特定情况可能有一个更优雅的解决方案,b)性能可能是以后的问题——我不想过早地优化,但我也不想自己开枪在脚下。

【问题讨论】:

"以后性能可能会成为问题——我不想过早优化,但我也不想自欺欺人。" 我同意这种观点,但值得注意的是,如果您正确封装了功能,那么如果性能是一个问题,那么稍后更改实现应该是无痛的,并且不会影响您的其余代码。 【参考方案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

【讨论】:

您需要标记sumAll inline 以使其与float 和其他数字类型一起使用。【参考方案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.sumList.sumBy,因为后者仅适用于数字类型,而(+) 可以用于例如string

merge 被推断为 int list list -> int list 类型,而不是泛型,因为运算符 + 工作方式的微妙之处。如果您只需要它适用于单一类型,并且该类型不是 int(例如float),那么向merge 添加类型注释就足够了:

let merge (lists:float list list) =

merge 可以标记为inline,然后将适用于支持运算符+ 的任何类型,但是如果有超过一两个呼叫站点,这将导致您的IL 中的大量膨胀。如果您有多种类型需要使用merge,并且所有类型都是事先已知的,那么这里一个好的解决方法是使mergeinline(可能还有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

如果您随后只调用特定类型的 merges,则 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's answer
let listOfLists = List.init 1000 (fun i -> [i+1..i+9])
sumVertically 9 listOfLists

【讨论】:

以上是关于在 F# 中“合并”多个相同长度的列表的惯用方式?的主要内容,如果未能解决你的问题,请参考以下文章

在列表理解中拥有许多相同生成器的惯用方式

Scala:将地图列表与每个键的最大值合并的惯用方法?

派生类型的模式匹配对于 F# 来说是惯用的吗?

Python:如何将具有相同变量类型的多个列表合并到一个列表列表中?

合并两个相同长度的Scala列表,同一个索引的元素成为一个元素

是否有一种惯用的方式来操作 Ruby 中的 2 个数组?