Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preliminary Map.binarySearch function with tests #15107

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion src/FSharp.Core/map.fs
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,31 @@ module MapTree =
(m.Key, m.Value)
else
rightmost nd.Right


let rec binarySearch (comparer: IComparer<'Key>) k lower higher (m: MapTree<'Key, 'Value>) =
if isEmpty m then
lower, None, higher
else
let c = comparer.Compare(k, m.Key)
if m.Height = 1 then
if c = 0 then
lower, Some (m.Key, m.Value), higher
elif c < 0 then
lower, None, Some (m.Key, m.Value)
else
Some (m.Key, m.Value), None, higher
elif c = 0 then
let nd = asNode m
(if isEmpty nd.Left then None else Some (rightmost nd.Left)),
Some (m.Key, m.Value),
(if isEmpty nd.Right then None else Some (leftmost nd.Right))
elif c > 0 then
let nd = asNode m
binarySearch comparer k (Some (nd.Key, nd.Value)) higher nd.Right
else
let nd = asNode m
binarySearch comparer k lower (Some (nd.Key, nd.Value)) nd.Left

[<System.Diagnostics.DebuggerTypeProxy(typedefof<MapDebugView<_, _>>)>]
[<System.Diagnostics.DebuggerDisplay("Count = {Count}")>]
[<Sealed>]
Expand Down Expand Up @@ -843,6 +867,9 @@ type Map<[<EqualityConditionalOn>] 'Key, [<EqualityConditionalOn; ComparisonCond

member m.MinKeyValue = MapTree.leftmost tree
member m.MaxKeyValue = MapTree.rightmost tree

member m.BinarySearch key =
MapTree.binarySearch comparer key None None tree

static member ofList l : Map<'Key, 'Value> =
let comparer = LanguagePrimitives.FastGenericComparer<'Key>
Expand Down Expand Up @@ -1305,3 +1332,7 @@ module Map =
[<CompiledName("MaxKeyValue")>]
let maxKeyValue (table: Map<_, _>) =
table.MaxKeyValue

[<CompiledName("BinarySearch")>]
let binarySearch (key: 'Key) (table: Map<'Key, 'T>) =
table.BinarySearch key
17 changes: 17 additions & 0 deletions src/FSharp.Core/map.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -781,3 +781,20 @@ module Map =
/// </example>
[<CompiledName("MaxKeyValue")>]
val maxKeyValue: table: Map<'Key, 'T> -> 'Key * 'T

/// <summary>Returns a 3-tuple of the item with the closest key below the map, if any;
/// the matching item, if any; and the closest key above the map, if any.</summary>
///
/// <param name="key">The key to search.</param>
/// <param name="table">The input map.</param>
///
/// <example>
/// <code lang="fsharp">
/// let sample = Map [ (10, "a"); (12, "b"); (20, "c"); (22, "d"); (25, "e"); (28, "f")
/// (30, "g"); (36, "h"); (38, "i"); (40, "j"); (48, "k") ]
///
/// sample |> Map.binarySearch 20 // evaluates to Some (12, "b"), Some (20, "c"), Some (25, "e")
/// </code>
/// </example>
[<CompiledName("BinarySearch")>]
val binarySearch: key: 'Key -> table: Map<'Key, 'T> -> ('Key * 'T) option * ('Key * 'T) option * ('Key * 'T) option
Original file line number Diff line number Diff line change
Expand Up @@ -725,4 +725,64 @@ type MapModule() =
let eptMap = Map.empty
CheckThrowsKeyNotFoundException (fun () -> Map.maxKeyValue eptMap |> ignore)

()

[<Fact>]
member _.BinarySearch() =
let map = Map [ (10, "a"); (12, "b"); (20, "c"); (22, "d"); (25, "e"); (28, "f")
(30, "g"); (36, "h"); (38, "i"); (40, "j"); (48, "k") ]
// Produces the following tree at tolerance 2:
// 22
// |- 12
// |- 10
// |- 20
// |- 28
// |- 25
// |- 36
// |- 30
// |- 40
// |- 38
// |- 48

// Matches exact and either side at middle of tree
let result = Map.binarySearch 12 map
let expected = Some (10, "a"), Some (12, "b"), Some (20, "c")
Assert.Equal(expected, result)

// Matches exact and either side at bottom of tree
let result = Map.binarySearch 20 map
let expected = Some (12, "b"), Some (20, "c"), Some (22, "d")
Assert.Equal(expected, result)

// Matches exact and either side at top of tree
let result = Map.binarySearch 22 map
let expected = Some (20, "c"), Some (22, "d"), Some (25, "e")
Assert.Equal(expected, result)

// Matches on either side
let result = Map.binarySearch 11 map
let expected = Some (10, "a"), None, Some (12, "b")
Assert.Equal(expected, result)

// Only matches to left
let result = Map.binarySearch 50 map
let expected = Some (48, "k"), None, None
Assert.Equal(expected, result)

// Only matches to right
let result = Map.binarySearch 1 map
let expected = None, None, Some (10, "a")
Assert.Equal(expected, result)

// One-element Map
let map = Map [ (1, "a") ]
let result = Map.binarySearch 1 map
let expected = None, Some (1, "a"), None
Assert.Equal(expected, result)

// Empty Map
let result = Map.binarySearch 1 (Map [])
let expected = None, None, None
Assert.Equal(expected, result)

()