问题描述
像这样的数组:
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
但这也太丑了..
我不禁想到必须有一种简单而优雅的方式来做到这一点……有人有想法吗?
解决方法
首先,通过意识到 foldBack
与 seq { 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)