Hullo all。
我是一名C#程序员,在我的空闲时间探索F#。我在2D中编写了以下用于图像卷积的小程序。
open System
let convolve y x =
y |> List.map (fun ye -> x |> List.map ((*) ye))
|> List.mapi (fun i l -> [for q in 1..i -> 0] @ l @ [for q in 1..(l.Length - i - 1) -> 0])
|> List.reduce (fun r c -> List.zip r c |> List.map (fun (a, b) -> a + b))
let y = [2; 3; 1; 4]
let x = [4; 1; 2; 3]
printfn "%A" (convolve y x)
我的问题是:上面的代码是否是惯用的F#?可以更简洁吗? (例如,是否有一些更短的方法来生成0的填充列表(为此我在代码中使用了列表理解))。任何可以改善其性能的变化?
非常感谢任何帮助。感谢。
修改
谢谢Brian。我没有得到你的第一个建议。以下是我的代码在应用您的第二个建议后的样子。 (我还抽象出了列表填充操作。)
open System
let listFill howMany withWhat = [for i in 1..howMany -> withWhat]
let convolve y x =
y |> List.map (fun ye -> x |> List.map ((*) ye))
|> List.mapi (fun i l -> (listFill i 0) @ l @ (listFill (l.Length - i - 1) 0))
|> List.reduce (List.map2 (+))
let y = [2; 3; 1; 4]
let x = [4; 1; 2; 3]
printfn "%A" (convolve y x)
还有什么可以改进吗?等待更多建议......
答案 0 :(得分:5)
正如Brian所提到的,@
的使用通常是有问题的,因为无法为(简单的)功能列表有效地实现运算符 - 它需要复制整个第一个列表。
我认为Brians的建议是编写一个可以立即生成列表的序列生成器,但这有点复杂。您必须将列表转换为数组,然后编写如下内容:
let convolve y x =
y |> List.map (fun ye -> x |> List.map ((*) ye) |> Array.ofList)
|> List.mapi (fun i l -> Array.init (2 * l.Length - 1) (fun n ->
if n < i || n - i >= l.Length then 0 else l.[n - i]))
|> List.reduce (Array.map2 (+))
通常,如果性能是一个重要的问题,那么您可能仍然需要使用数组(因为通过索引访问元素可以最好地解决这种问题)。使用数组有点困难(你需要正确编制索引),但在F#中使用完美的方法。
无论如何,如果你想用列表写这个,那么这里有一些选项。你可以在任何地方使用序列表达式,如下所示:
let convolve y (x:_ list) =
[ for i, v1 in x |> List.zip [ 0 .. x.Length - 1] ->
[ yield! listFill i 0
for v2 in y do yield v1 * v2
yield! listFill (x.Length - i - 1) 0 ] ]
|> List.reduce (List.map2 (+))
...或者您也可以组合这两个选项并使用嵌套序列表达式(使用yield!
生成零和列表)在您传递给List.mapi
的lambda函数中:< / p>
let convolve y x =
y |> List.map (fun ye -> x |> List.map ((*) ye))
|> List.mapi (fun i l ->
[ for _ in 1 .. i do yield 0
yield! l
for _ in 1 .. (l.Length - i - 1) do yield 0 ])
|> List.reduce (List.map2 (+))
答案 1 :(得分:3)
惯用解决方案是使用数组和循环,就像在C中一样。但是,您可能会对使用模式匹配的以下替代解决方案感兴趣:
let dot xs ys =
Seq.map2 (*) xs ys
|> Seq.sum
let convolve xs ys =
let rec loop vs xs ys zs =
match xs, ys with
| x::xs, ys -> loop (dot ys (x::zs) :: vs) xs ys (x::zs)
| [], _::(_::_ as ys) -> loop (dot ys zs :: vs) [] ys zs
| _ -> List.rev vs
loop [] xs ys []
convolve [2; 3; 1; 4] [4; 1; 2; 3]
答案 2 :(得分:2)
关于零,例如
[for q in 0..l.Length-1 -> if q=i then l else 0]
(我没有经过测试验证这是完全正确的,但希望这个想法很明确。)一般来说,任何使用@
都是代码气味。
关于整体表现,对于小名单,这可能很好;对于较大的,您可以考虑使用Seq
而不是List
来进行某些中间计算,以避免在此过程中分配尽可能多的临时列表。
看起来最终的zip-then-map可能只需要调用map2就可以取代,例如
... fun r c -> (r,c) ||> List.map2 (+)
或甚至可能只是
... List.map2 (+)
但是我离开了编译器,所以没有仔细检查它。
答案 3 :(得分:1)
(fun ye -> x |> List.map ((*) ye))
真的吗?
我会承认|&gt;很漂亮,但你可以写道:
(fun ye -> List.map ((*) ye) x)
答案 4 :(得分:1)
你可以做的另一件事是融合前两张地图。 l |> List.map f |> List.mapi g
= l |> List.mapi (fun i x -> g i (f x))
,因此结合Tomas和Brian的建议,您可以得到类似的内容:
let convolve y x =
let N = List.length x
y
|> List.mapi (fun i ye ->
[for _ in 1..i -> 0
yield! List.map ((*) ye) x
for _ in 1..(N-i-1) -> 0])
|> List.reduce (List.map2 (+))