使用函数式编程将位置列表转换为2D位置数组(F#)

时间:2010-03-26 17:23:48

标签: algorithm f# functional-programming

如何以相同的速度使下面的代码正常运行?通常,作为输入,我有一个包含位置坐标和其他东西的对象列表,我需要创建一个包含这些对象的2D数组。

let m = Matrix.Generic.create 6 6 []
let pos = [(1.3,4.3); (5.6,5.4); (1.5,4.8)]  
pos |> List.iter (fun (pz,py) ->
  let z, y = int pz, int py
  m.[z,y] <- (pz,py) :: m.[z,y]
)

可能以这种方式完成:

let pos = [(1.3,4.3); (5.6,5.4); (1.5,4.8)]
Matrix.generic.init 6 6 (fun z y ->
  pos |> List.fold (fun state (pz,py) ->
    let iz, iy = int pz, int py
    if iz = z && iy = y then (pz,py) :: state else state
  ) []
)

但是我猜它会慢得多,因为它循环遍历整个矩阵乘以列表而不是前一个列表迭代......

PS:代码可能有误,因为我在这台计算机上没有F#来检查它。

3 个答案:

答案 0 :(得分:3)

这取决于“功能”的定义。我会说“功能”函数意味着它总是为相同的参数返回相同的结果,并且它不会修改任何全局状态(或者如果它们是可变的则参数的值)。我认为这是F#的合理定义,但它也意味着在本地使用变异没有任何“失效”。

在我看来,以下函数是“functional”,因为它创建并返回一个新矩阵而不是修改现有矩阵,但当然,该函数的实现使用了变异。

let performStep m =
  let res = Matrix.Generic.create 6 6 [] 
  let pos = [(1.3,4.3); (5.6,5.4); (1.5,4.8)]   
  for pz, py in pos do
    let z, y = int pz, int py 
    res.[z,y] <- (pz,py) :: m.[z,y] 
  res

无变异版本: 现在,如果你想让实现完全正常,那么我首先要在你想要将新列表元素添加到矩阵元素的地方创建一个包含Some(pz, py)的矩阵,并{{1}在所有其他地方。我想这可以通过初始化稀疏矩阵来完成。像这样:

None

然后,您应该能够将原始矩阵let sp = pos |> List.map (fun (pz, py) -> int pz, int py, (pz, py)) let elementsToAdd = Matrix.Generic.initSparse 6 6 sp 与新创建的m结合起来。这当然可以使用elementsToAdd完成(但是,init之类的内容可能更好一些):

map2

F#库函数(例如let res = Matrix.init 6 6 (fun i j -> match elementsToAdd.[i, j], m.[i, j] with | Some(n), res -> n::res | _, res -> res ) init)中仍然隐藏着一些突变,但至少它显示了一种使用更原始操作实现操作的方法。 p>

编辑:仅当您需要向每个矩阵单元添加最多单个元素时,此选项才有效。如果您想添加多个元素,则必须先将它们分组(例如使用initSparse

答案 1 :(得分:1)

您可以这样做:

[1.3, 4.3; 5.6, 5.4; 1.5, 4.8]
|> Seq.groupBy (fun (pz, py) -> int pz, int py)
|> Seq.map (fun ((pz, py), ps) -> pz, py, ps)
|> Matrix.Generic.initSparse 6 6

但在你的问题中,你说:

  

如何以相同的速度使下面的代码正常运行?

在后来的评论中你说:

  

好吧,我尽量避免可变性,以便将来代码很容易进行并列化

我担心这是希望胜过现实的胜利。功能代码通常具有较差的绝对性能,并且在并行化时会严重缩放。鉴于此代码正在进行大量分配,您根本不可能从并行性中获得任何性能提升。

答案 2 :(得分:0)

为什么要在功能上这样做? Matrix类型被设计为变异,所以你现在的样子对我来说很好。

如果你真的想在功能上做到这一点,那么这就是我要做的事情:

let pos = [(1.3,4.3); (5.6,5.4); (1.5,4.8)]  
let addValue m k v = 
  if Map.containsKey k m then
    Map.add k (v::m.[k]) m
  else
    Map.add k [v] m
let map = 
  pos 
  |> List.map (fun (x,y) -> (int x, int y),(x,y)) 
  |> List.fold (fun m (p,q) -> addValue m p q) Map.empty
let m = Matrix.Generic.init 6 6 (fun x y -> if (Map.containsKey (x,y) map) then map.[x,y] else [])

这会在列表中运行一次,从索引到点列表创建一个不可变的映射。然后,我们初始化矩阵中的每个条目,为每个条目执行单个映射查找。这应该花费总时间O(M + N log N),其中MN分别是矩阵和列表中的条目数。我相信使用变异的原始解决方案需要O(M+N)时间,而修改后的解决方案需要O(M*N)次。