微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

可以更优雅地重写这段代码吗,在 F# 中

如何解决可以更优雅地重写这段代码吗,在 F# 中

像这样的数组:

let a = [| 1.; 2.; nan; 4.; 5. |]

我知道最后一个不是一个 NaN(列表来自一个生成前导 NaN 的操作)我想从最后扫描列表,如果值是一个 nan,替换它与以前的值。

在这种情况下,输出将是:

[| 1.; 2.; 4.; 4.; 5. |]

首先,我做了这个怪物:

Array.append a [|a.[^0]|] |> Array.pairwise |> Array.rev |> Array.map (fun (a,b) -> if Double.IsNaN(a) then b else a) |> Array.rev

然后是一个更干净的循环:

seq {
    for i = a.Length - 1 downto 0 do
        yield if Double.IsNaN(a.[i]) then a.[i+1] else a.[i]
} |> Seq.rev |> Seq.toArray

我在想也许 foldBack 会起作用:

Array.foldBack (fun x acc -> Array.append acc (seq {yield if Double.IsNaN(x) then acc.[^0] else x} |> Seq.toArray)) a Array.empty

但这也太丑了..

我不禁想到必须有一种简单而优雅的方式来做到这一点……有人有想法吗?

解决方法

首先,通过意识到 foldBackseq { yield foo } |> Seq.toArray 相同,您可以使您的 [|foo|] 解决方案更加简洁:

Array.foldBack (fun x acc -> Array.append acc [|if Double.IsNaN(x) then acc.[^0] else x|]) a Array.empty

但更好的方法是scan。这是一个不错的小函数,有点像 map,但每次迭代也可以访问前一次的结果。除了,当然,因为你要从后到前,你必须使用 scanBack:

Array.scanBack (fun x prev -> if Double.IsNaN x then prev else x) a nan

唯一的问题是扫描的“种子”(这是最后一个参数 - 在我的示例中为 nan)将保留在结果数组中,成为其最后一个元素。因此,您之后必须将其过滤掉,但我认为这不会成为问题,因为您似乎可以在 foldBack 方法中重新分配多个数组:

Array.scanBack (fun x prev -> if Double.IsNaN x then prev else x) a nan
|> Array.takeWhile (not << Double.IsNaN)

或者,您可以记住数组是可变的,只需改变原始数组即可:

for i in 0..(Array.length a - 1) do
    if Double.IsNaN a.[i] then a.[i] <- a.[i+1]
,

您可以使用单个 Array.mapi 来完成此操作。这类似于 Array.map,但它提供了每个项目的索引。

let fillNans xs =
    xs
    |> Array.mapi (fun i x -> if Double.IsNaN x then xs.[i+1] else x)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。