当repl变量设置为false时,为什么以下函数返回不正确长度的序列?
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)
match m > 0 with
| true ->
let newIdx = DiscreteUniform.Samples(0, n-1) |> Seq.take m
let idx = (Seq.append idx newIdx) |> Seq.distinct
generateIndex idx
| false ->
idx
let sample =
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)
sample
运行功能......
let requested = 1000
let dat = Normal.Samples(0., 1.) |> Seq.take 10000
let resultlen = sample dat requested false |> Seq.length
printfn "requested -> %A\nreturned -> %A" requested resultlen
结果长度错误。
>
requested -> 1000
returned -> 998
>
requested -> 1000
returned -> 1001
>
requested -> 1000
returned -> 997
知道我在做什么错误吗?
答案 0 :(得分:9)
首先,我想对编码风格做出评论。然后,我将解释为什么你的序列会以不同的长度返回。
在评论中,我提到使用简单的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#允许您重复使用这样的名称,但函数内部的名称将会&#34; shadow&#34;在函数外部的名称,重用名称与原始名称具有完全不同的类型通常是个坏主意。换句话说,这可能是一个好主意:
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
。但是他们会对#34;同样的&#34;类型 - 也就是说,#34之间的心理差异非常小;调用者可能已经指定了一个浮点值,或者他们可能没有&#34;和#34;现在我肯定有一个浮点值&#34;。但是&#34之间存在非常大的心理差距。名称sample
是一个带有三个参数的函数&#34;和&#34;名称sample
是一系列花车&#34;。我强烈建议您使用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
任何时候我发现自己写作&#34;让结果=(某事);导致&#34;在我的函数结束时,我通常只想用(something)
替换整个代码块。即,上面的代码段可能只是:
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代码中,并添加了一些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,它将会只打印出四个值,然后如果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; ...]
了解数字如何变化?这是我的第一个线索,有些事情发生了。请注意,seq
是 懒惰 。在他们不得不这样做之前,他们不会评估他们的内容。您不应该将seq
视为数字列表。相反,将其视为生成器,当被要求提供数字时,会根据某些规则生成它们。在您的情况下,规则为&#34;选择0和n
- 1之间的随机整数,然后取m
这些数字&#34;。关于seq
s的另一个问题是它们不会缓存它们的内容(尽管有Seq.cache
函数可用于缓存它们的内容)。因此,如果你有一个基于随机数生成器的seq
,它的结果每次都会有所不同,正如你在输出中看到的那样。当我打印出newIdx
时,打印出[74; 76; 97; 78; 31],但是当我将它附加到空seq时,结果打印为[68; 28; 65; 58; 82]。
为什么会出现这种差异?因为Seq.append
不强制评估。它只是创建一个新的seq
,其规则是&#34;从第一个seq获取所有项目,然后当那个耗尽时,从第二个seq中获取所有项目。当那个人耗尽时,结束。&#34;并且Seq.distinct
也不会强制进行评估;它只是创建一个新的seq
,其规则是&#34;将seq
中的项目交给您,并在被问到时开始将它们移出。但是当你去的时候记住它们,如果你以前把它们中的一个交出去,那就不要再把它拿出去了。&#34;所以你在调用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,也只有不到10%的可能性,返回的seq
的长度最终为4 - 因为它选择了不同的随机数集,而不是您检查的随机数。
为了证明这一点,我再次运行该功能,这次只挑选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]
请注意我何时打印&#34;完成,返回(idx
的值)&#34;,它只有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
值并且错误将会消失远。您只需使用简单的generateIdx
替换整个DiscreteUniform.Samples(0, n-1) |> Seq.distinct |> Seq.take size
函数即可。 (并使用数组进行数据查找,以便您的函数运行得更快)。换句话说,这里是 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])
那就是它!简单,易于理解,并且(据我所知)无错误。
编辑 ...但不是完全干,因为在那&#34;最终&#34;中仍然有一些重复的代码版。 (感谢CaringDev在下面的评论中指出它)。在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])