首先,我想对编码风格发表评论。然后我会解释为什么你的序列以不同的长度返回。
在 cmets 中,我提到将 match (bool) with true -> ... | false -> ... 替换为简单的 if ... then ... else 表达式,但您使用的另一种编码风格我认为可以改进。你写道:
let sample (various_parameters) = // This is a function
// Other code ...
let sample = some_calculation // This is a variable
sample // Return the variable
虽然 F# 允许您重用这样的名称,并且函数内的名称会“遮蔽”函数外的名称,但重用名称具有完全不同的类型通常不是一个好主意 比原来的名字。换句话说,这可能是个好主意:
let f (a : float option) =
let a = match a with
| None -> 0.0
| Some value -> value
// Now proceed, knowing that `a` has a real value even if had been None before
或者,因为以上正是 F# 为您提供 defaultArg 的原因:
let f (a : float option) =
let a = defaultArg a 0.0
// This does exactly the same thing as the previous snippet
在这里,我们在函数内部将名称 a 与名为 a 的参数引用不同的类型:参数是 float option,而函数内部的 a 是 float .但它们是“相同”的类型——也就是说,“调用者可能指定了一个浮点值,或者他们可能没有”和“现在我肯定有一个浮点值”之间的心理差异很小.但是在“名称sample 是一个接受三个参数的函数”和“名称sample 是一个浮点数序列”之间存在非常很大的心理差距。我强烈建议您使用 result 之类的名称作为要从函数返回的值,而不是重复使用函数名称。
此外,这似乎是不必要的冗长:
let result =
match repl with
| true ->
DiscreteUniform.Samples(0, n-1)
|> Seq.take size
|> Seq.map (fun index -> Seq.item index data)
| false ->
generateIndex (seq [])
|> Seq.map (fun index -> Seq.item index data)
result
每当我发现自己在函数末尾写“let result = (something) ; result”时,我通常只想用 (something) 替换整个代码块。也就是说,上面的 sn-p 可以变成:
match repl with
| true ->
DiscreteUniform.Samples(0, n-1)
|> Seq.take size
|> Seq.map (fun index -> Seq.item index data)
| false ->
generateIndex (seq [])
|> Seq.map (fun index -> Seq.item index data)
又可以用if...then...else 表达式替换:
if repl then
DiscreteUniform.Samples(0, n-1)
|> Seq.take size
|> Seq.map (fun index -> Seq.item index data)
else
generateIndex (seq [])
|> Seq.map (fun index -> Seq.item index data)
这是代码中的最后一个表达式。换句话说,我可能会重写你的函数如下(只改变样式,不改变逻辑):
open MathNet.Numerics.Distributions
open MathNet.Numerics.LinearAlgebra
let sample (data : seq<float>) (size : int) (repl : bool) =
let n = data |> Seq.length
// without replacement
let rec generateIndex idx =
let m = size - Seq.length(idx)
if m > 0 then
let newIdx = DiscreteUniform.Samples(0, n-1) |> Seq.take m
let idx = (Seq.append idx newIdx) |> Seq.distinct
generateIndex idx
else
idx
if repl then
DiscreteUniform.Samples(0, n-1)
|> Seq.take size
|> Seq.map (fun index -> Seq.item index data)
else
generateIndex (seq [])
|> Seq.map (fun index -> Seq.item index data)
如果我能找出您的序列长度错误的原因,我也会使用该信息更新此答案。
更新: 好的,我想我看到了您的 generateIndex 函数中发生的事情,这给您带来了意想不到的结果。有两件事会绊倒你:一是序列惰性,二是随机性。
我将您的 generateIndex 函数复制到 VS Code 中,并添加了一些 printfn 语句来查看发生了什么。先是我跑的代码,然后是结果:
let rec generateIndex n size idx =
let m = size - Seq.length(idx)
printfn "m = %d" m
match m > 0 with
| true ->
let newIdx = DiscreteUniform.Samples(0, n-1) |> Seq.take m
printfn "Generating newIdx as %A" (List.ofSeq newIdx)
let idx = (Seq.append idx newIdx) |> Seq.distinct
printfn "Now idx is %A" (List.ofSeq idx)
generateIndex n size idx
| false ->
printfn "Done, returning %A" (List.ofSeq idx)
idx
所有这些List.ofSeq idx 调用是为了让 F# Interactive 在我打印出来时会打印超过四个 seq 项(默认情况下,如果您尝试使用 %A 打印一个 seq,它只会打印出四个如果序列中有更多可用值,则打印省略号)。此外,我将n 和size 转换为参数(在调用之间我不会更改),以便我可以轻松测试它。然后我将其称为generateIndex 100 5 (seq []) 并得到以下结果:
m = 5
Generating newIdx as [74; 76; 97; 78; 31]
Now idx is [68; 28; 65; 58; 82]
m = 0
Done, returning [37; 58; 24; 48; 49]
val it : seq<int> = seq [12; 69; 97; 38; ...]
看看数字是如何不断变化的?这是我的第一个线索,事情发生了。看,seqs 懒惰。除非他们必须这样做,否则他们不会评估他们的内容。您不应该将seq 视为数字列表。相反,将其视为一个生成器,当被要求输入数字时,它会根据某些规则生成它们。在您的情况下,规则是“选择 0 和 n-1 之间的随机整数,然后取这些数字中的 m”。关于seqs 的另一件事是它们不缓存它们的内容(尽管有一个可用的Seq.cache 函数可以缓存它们的内容)。因此,如果你有一个基于随机数生成器的seq,它的结果每次都会不同,正如你在我的输出中看到的那样。当我打印出newIdx 时,它打印为 [74; 76; 97; 78; 31],但是当我将它附加到一个空序列时,结果打印为 [68; 28; 65; 58; 82]。
为什么会有这种差异?因为Seq.append 不强制评估。它只是创建一个新的seq,其规则是“从第一个 seq 中取出所有项目,然后当那个用完时,从第二个 seq 中取出所有项目。当那个用完时,结束。”并且Seq.distinct 也不强制评估;它只是创建了一个新的seq,其规则是“从seq 中取出交给你的物品,并在被要求时开始分发它们。但是在你去的时候记住它们,如果你之前已经分发过其中一个,不要再发了。”因此,您在调用generateIdx 之间传递的是一个对象,在评估时,它将选择一组介于 0 和 n-1 之间的随机数(在我的简单情况下,介于 0 和100),然后将该集合缩减为一组不同的数字。
现在,事情就是这样。每次评估 seq 时,它都会从头开始:首先调用 DiscreteUniform.Samples(0, n-1) 以生成无限的随机数流,然后从该流中选择 m 数字,然后丢弃任何重复的数字。 (我暂时忽略Seq.append,因为它会造成不必要的心理复杂性,而且它并不是真正的错误的一部分)。现在,在您的函数的每个循环开始时,您检查序列的长度,这确实会导致它被评估。这意味着它选择(在我的示例代码的情况下)0 到 99 之间的 5 个随机数,然后确保它们都是不同的。如果它们都是不同的,那么m = 0 并且函数将退出,返回...不是数字列表,而是seq 对象。当评估该seq 对象时,它将从头开始,选择一个不同 5 个随机数的集合,然后丢弃所有重复项。因此,仍然有可能这组 5 个数字中至少有一个最终会重复,因为经过测试的序列的长度(我们知道它不包含重复,否则 m 将大于 0)是不是返回的序列。返回的序列有 1.0 * 0.99 * 0.98 * 0.97 * 0.96 的机会不包含任何重复项,约为 0.9035。因此,即使您检查了 Seq.length 并且它是 5,但返回的 seq 的长度最终还是 4 的可能性略低于 10%——因为它选择了一个 不同的 em> 一组随机数,而不是你检查的那个。
为了证明这一点,我再次运行了该函数,这次只选择了 4 个数字,以便将结果完全显示在 F# Interactive 提示符处。我运行generateIndex 100 4 (seq []) 产生了以下输出:
m = 4
Generating newIdx as [36; 63; 97; 31]
Now idx is [39; 93; 53; 94]
m = 0
Done, returning [47; 94; 34]
val it : seq<int> = seq [48; 24; 14; 68]
注意当我打印“完成,返回(idx 的值)”时,它只有 3 个值?即使它最终返回了 4 个值(因为它为实际结果选择了不同的随机数选择,并且该选择没有重复),这证明了问题。
顺便说一句,您的函数还有一个其他问题,那就是它比它需要的要慢得多。函数Seq.item,在某些情况下,必须从头遍历序列,才能选择序列中的第n项。最好在函数开始时将数据存储在数组中 (let arrData = data |> Array.ofSeq),然后替换
|> Seq.map (fun index -> Seq.item index data)
与
|> Seq.map (fun index -> arrData.[index])
数组查找是在恒定时间内完成的,因此您的示例函数从 O(N^2) 降低到 O(N)。
TL;DR:使用Seq.distinct 在您从中获取m 值,错误就会消失。你可以用一个简单的DiscreteUniform.Samples(0, n-1) |> Seq.distinct |> Seq.take size 替换整个generateIdx 函数。 (并使用数组进行数据查找,以便您的函数运行得更快)。换句话说,这是我将如何重写您的代码的 final 几乎-最终版本:
let sample (data : seq<float>) (size : int) (repl : bool) =
let arrData = data |> Array.ofSeq
let n = arrData |> Array.length
if repl then
DiscreteUniform.Samples(0, n-1)
|> Seq.take size
|> Seq.map (fun index -> arrData.[index])
else
DiscreteUniform.Samples(0, n-1)
|> Seq.distinct
|> Seq.take size
|> Seq.map (fun index -> arrData.[index])
就是这样!简单、易于理解并且(据我所知)没有错误。
编辑: ...但不是完全干燥,因为在那个“最终”版本中仍然有一些重复的代码。 (感谢CaringDev 在下面的 cmets 中指出)。 Seq.take size |> Seq.map 在if 表达式的两个分支中重复出现,因此有一种方法可以简化该表达式。我们可以这样做:
let randomIndices =
if repl then
DiscreteUniform.Samples(0, n-1)
else
DiscreteUniform.Samples(0, n-1) |> Seq.distinct
randomIndices
|> Seq.take size
|> Seq.map (fun index -> arrData.[index])
所以这是我建议的真正最终版本:
let sample (data : seq<float>) (size : int) (repl : bool) =
let arrData = data |> Array.ofSeq
let n = arrData |> Array.length
let randomIndices =
if repl then
DiscreteUniform.Samples(0, n-1)
else
DiscreteUniform.Samples(0, n-1) |> Seq.distinct
randomIndices
|> Seq.take size
|> Seq.map (fun index -> arrData.[index])