f#中的迭代二进制搜索实现

时间:2013-11-29 17:57:32

标签: f# binary-search

我正在尝试用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

但执行明智我认为我在这里做了很多操作而不是预期......

3 个答案:

答案 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);
}