F#BinarySearch返回位置

时间:2014-09-18 20:47:12

标签: f# functional-programming binary-search

我正在尝试在F#中实现一个简单的二进制搜索,作为学习函数式编程的一种方式。我已经在C#中以强制方式实现了它,这是我的主要语言,但我认为通过在F#中以递归的方式实现它来学习函数概念是一个很好的挑战。我已经足够远,它正确地找到了值,我的问题是我希望函数返回-1,如果值不在数组中,否则值的索引,但我无法想出跟踪指数的好方法。这是我的代码:

let rec chop i (nums:array<int>) =
match nums with
| [||] -> -1
| [|x|] -> if x = i then x else -1
| _ -> 
    let mid = nums.Length / 2 in
    if nums.[mid] < i then (chop i nums.[mid..])
    elif nums.[mid] > i then (chop i nums.[..mid])
    else mid

[<EntryPoint>]
let main argv = 
    printfn "%d" (chop 20 (Array.init 100 (fun index -> index * 2)))
    System.Console.ReadKey() |> ignore
    0 // return an integer exit code

由于我每次迭代都会拆分列表,所以当我向上移动到数组中时,我会得到不同的索引。在我的示例中,代码返回3而不是10。

修复此问题的推荐方法是什么?任何解决它的方法都值得赞赏,虽然我显然更喜欢在功能意义上做“正确”的方式,因为那就是我正在练习的东西。如果从性能的角度来看我的解决方案是否糟糕,我还想提出任何建议(例如,首先拆分数组是个坏主意吗?)

2 个答案:

答案 0 :(得分:5)

如果没有复制数组,我会更多地使用原始数据(并使用Option而不是使用幻数-1来表示失败):

let chop i (nums : int[]) =
    let rec find a b =
        if b < a then None else
        let mid = (a+b)/2
        let midValue = nums.[mid]
        if midValue = i then 
            Some mid 
        elif i < midValue then
            find a (mid-1)
        else
            find (mid+1) b
    find 0 (nums.Length-1)

也许你不喜欢所有这些if,但这仍然是算法的明显翻译。并且它不会改变或重新分配任何东西。

如果你想删除ifs,你可以这样做:

let chop i (nums : int[]) =
    let rec find a b =
        if b < a then None else
        let mid = (a+b)/2
        match (compare i nums.[mid]) with
        | 0  -> Some mid
        | -1 -> find a (mid-1)
        | 1  -> find (mid+1) b
        | _  -> failwith "should never happen"
    find 0 (nums.Length-1)

更具可读性(但有点样板):

[<Literal>] 
let Lt = -1
[<Literal>] 
let Gt = +1


let chop i (nums : int[]) =
    let rec find a b =
        if b < a then None else
        let mid = (a+b)/2
        match (compare i nums.[mid]) with
        | Lt -> find a (mid-1)
        | Gt -> find (mid+1) b
        | Eq -> Some mid
    find 0 (nums.Length-1)

评论请注意,最后一个案例实际上是匹配所有我应该写为| _ -> Some mid的情况但我想在这种情况下我可能会为可读性作弊缘故)

Performancewise唯一可以改进的就是删除递归(但如果启用优化则不应该是一个问题)

备注

正如@kvb正确地指出(a+b)/2有点风险(而且我犯了这个错误的规则) - 所以如果你有更大的数组并希望更安全,那么请更好地使用它:

let mid = a + (b - a) / 2

当然在数学上是相同的但是对于有限范围和溢出相当差异

答案 1 :(得分:4)

略有变化,并不需要&#34;不可能&#34;情况下:

let binarySearch value (array: 'T[]) =
    let rec loop lo hi =
        if lo <= hi then
            let mid = lo + ((hi - lo) >>> 1)
            match array.[mid] with
            | x when x = value -> Some mid
            | x when x < value -> loop (mid + 1) hi
            | _ -> loop lo (mid - 1)
        else None
    loop 0 (array.Length - 1)