我正在尝试用f#编写二进制搜索,但偶然发现了一个问题:
let find(words:string[]) (value:string) =
let mutable mid = 0
let mutable fpos = 0
let mutable lpos = words.Length - 1
while fpos < lpos do
mid <- (fpos + lpos) / 2
if value < words.[mid] then
lpos <- mid
else if value > words.[mid] then
fpos <- mid
else if value = words.[mid] then
true
false
在true
说明预期类型为unit()
的表达式而不是bool
的行中给出了错误。编写此函数的正确方法是什么?
修改
我暂时写了如下:
let find(words:string[]) (value:string) =
let mutable mid = 0
let mutable fpos = 0
let mutable lpos = words.Length - 1
let ret = false
while fpos < lpos && ret = false do
mid <- (fpos + lpos) / 2
if value < words.[mid] then
lpos <- mid
else if value > words.[mid] then
fpos <- mid
else if value = words.[mid] then
ret <- true
ret
但执行明智我认为我在这里做了很多操作而不是预期......
答案 0 :(得分:7)
使用递归函数:
let find(words:string[]) (value:string) =
let rec findRec fpos lpos =
if fpos > lpos then
false
else
let mid = (fpos + lpos) / 2
if value < words.[mid] then
findRec fpos (mid-1)
else if value > words.[mid] then
findRec (mid+1) lpos
else
true
findRec 0 (words.Length-1)
非递归版本(改编自Gene的回答):
let find (words: string[]) (value:string) =
let mutable mid = 0
let mutable fpos = 0
let mutable lpos = words.Length - 1
let mutable cont = true
while fpos <= lpos && cont do
mid <- (fpos + lpos) / 2
match sign(value.CompareTo(words.[mid])) with
| -1 -> lpos <- mid-1
| 1 -> fpos <- mid+1
| _ -> cont <- false
not cont
但我认为递归版本更可取:更具惯用性,与迭代版本一样高效,因为它使用尾调用。
答案 1 :(得分:2)
首先,您的算法不会终止value
,而不是最右边的words
元素(简单的测试用例为find [|"a";"b";"c";"d"|] "e"
)。
这个问题正在纠正并且只进行了一些小的优化,最终的交互式实现可能不会比下面的
<德尔> let find (words: string[]) (value:string) =
德尔>
let mutable lpos = words.Length - 1
if value.CompareTo(words.[lpos]) > 0 then
false
else
let mutable mid = 0
let mutable fpos = 0
let mutable cont = true
while fpos < lpos && cont do
mid <- (fpos + lpos) / 2
match sign(value.CompareTo(words.[mid])) with
| -1 -> lpos <- mid
| 1 -> fpos <- mid
| _ -> cont <- false
not cont
更新:这就是在急于回答问题时所发生的事情:(。上面的内容并不值得骄傲。正如MiMo所做的那样。 already took care上面的代码片段中的所有问题我将尝试不同的方式来证明自己,即尝试演示在尾调用递归消除之后MiMo的递归实现几乎完全转变为他的非递归实现。
我们将分两步执行此操作:首先使用带有标签和gotos的伪代码来说明编译器为消除这种形式的尾递归所做的操作,然后将伪代码转换回F#以获取命令式版本。
// Step 1 - pseudo-code with tail recursion substituted by goto
let find(words:string[]) (value:string) =
let mutable fpos = 0
let mutable lpos = words.Length - 1
findRec:
match fpos - lpos > 0 with
| true -> return false
| _ -> let mid = (fpos + lpos) / 2
match sign(value.CompareTo(words.[mid])) with
| -1 -> lpos <- mid - 1
goto findRec
| 1 -> fpos <- mid + 1
goto findRec
| _ -> return true
现在,在没有goto
的情况下,我们应该在保持F#构造的合法集合的同时提出一个等效的构造。最简单的方法是使用while...do
构造与可变state
变量一起使用,同时发出信号while
何时停止并携带返回值。为此目的,两个布尔的元组就足够了:
// Step 2 - conversion of pseudo-code back to F#
let find(words:string[]) (value:string) =
let mutable fpos = 0
let mutable lpos = words.Length - 1
let mutable state = (true,false)
while (fst state) do
match fpos - lpos > 0 with
| true -> state <- (false,false)
| _ -> let mid = (fpos + lpos) / 2
match sign(value.CompareTo(words.[mid])) with
| -1 -> lpos <- mid - 1
| 1 -> fpos <- mid + 1
| _ -> state <- (false,true)
snd state
总结一下,“a-la编译器优化的”递归版本和精心挑选的命令式版本之间的区别是微不足道的,实际上,在我看来,这应该表明正确安排的递归版本性能明智等同于命令式版本,但是,如果由编译器执行转换,则不会为有状态编码的错误留下空间。
答案 2 :(得分:2)
我建议像这样的递归解决方案:
let find (xs: _ []) x =
let rec loop i0 i2 =
match i2-i0 with
| 0 -> false
| 1 -> xs.[i0]=x
| di ->
let i1 = i0 + di/2
let c = compare x xs.[i1]
if c<0 then loop i0 i1
else c=0 || loop i1 i2
loop 0 xs.Length
F#将尾调用转换为gotos,当然:
internal static bool loop@4<a>(a[] xs, a x, int i0, int i2)
{
a a;
while (true)
{
int num = i2 - i0;
switch (num)
{
case 0:
return false;
case 1:
goto IL_50;
default:
{
int i3 = i0 + num / 2;
a = xs[i3];
int c = LanguagePrimitives.HashCompare.GenericComparisonIntrinsic<a>(x, a);
if (c < 0)
{
a[] arg_37_0 = xs;
a arg_35_0 = x;
int arg_33_0 = i0;
i2 = i3;
i0 = arg_33_0;
x = arg_35_0;
xs = arg_37_0;
}
else
{
if (c == 0)
{
return true;
}
a[] arg_4A_0 = xs;
a arg_48_0 = x;
int arg_46_0 = i3;
i2 = i2;
i0 = arg_46_0;
x = arg_48_0;
xs = arg_4A_0;
}
break;
}
}
}
return true;
IL_50:
a = xs[i0];
return LanguagePrimitives.HashCompare.GenericEqualityIntrinsic<a>(a, x);
}
public static bool find<a>(a[] xs, a x)
{
return File1.loop@4<a>(xs, x, 0, xs.Length);
}