diff --git a/src/Fable.Transforms/Beam/Replacements.fs b/src/Fable.Transforms/Beam/Replacements.fs index 4b3df7f340..f4ba6fe23c 100644 --- a/src/Fable.Transforms/Beam/Replacements.fs +++ b/src/Fable.Transforms/Beam/Replacements.fs @@ -2144,6 +2144,26 @@ let private stringModule let private makeComparer (com: ICompiler) ctx typArg = objExpr [ "Compare", makeComparerFunction com ctx typArg ] +/// BinarySearch of an array or ResizeArray `list`, where a range search goes to `rangeFuncName`. +/// A missing comparer, or a literal null one as .NET allows, means the default comparer. +let private binarySearch (com: ICompiler) ctx r t rangeFuncName (list: Expr) (args: Expr list) = + let call funcName searchArgs (value: Expr) comparer = + let comparer = + match comparer with + | None + | Some NullConst -> makeComparer com ctx value.Type + | Some comparer -> comparer + + Helper.LibCall(com, "fable_resize_array", funcName, t, list :: searchArgs @ [ comparer ], ?loc = r) + |> Some + + match args with + | [ value ] -> call "binary_search" [ value ] value None + | [ value; comparer ] -> call "binary_search" [ value ] value (Some comparer) + | [ index; count; value ] -> call rangeFuncName [ index; count; value ] value None + | [ index; count; value; comparer ] -> call rangeFuncName [ index; count; value ] value (Some comparer) + | _ -> None + let private makeEqualityFunction (com: ICompiler) ctx typArg = let x = makeUniqueIdent com ctx typArg "x" let y = makeUniqueIdent com ctx typArg "y" @@ -2390,6 +2410,9 @@ let private arrays | "IndexOf", None, [ arr; value ] -> let arr = derefArr r arr Helper.LibCall(com, "fable_list", "index_of_value", t, [ value; arr ]) |> Some + // System.Array.BinarySearch, with or without a range and a comparer + | "BinarySearch", None, (ExprType(Array _) as arr) :: args -> + binarySearch com _ctx r t "binary_search_range" (derefArr r arr) args // Iterating an array as a non-generic IEnumerable (e.g. `for v in Enum.GetValues t do`). // Deref via derefArr so byte arrays (atomics, not process-dict refs) are handled too; // the non-generic System.Array of Enum.GetValues stays a ref, which get_enumerator accepts. @@ -3700,6 +3723,9 @@ let private resizeArrays ?loc = r ) |> Some + // BinarySearch + | "BinarySearch", Some callee, args -> + binarySearch com _ctx r t "binary_search_range_count" (emitExpr r (List Any) [ callee ] "get($0)") args // Remove | "Remove", Some callee, [ arg ] -> let listExpr = emitExpr r (List Any) [ callee ] "get($0)" diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs index 65a3c7311e..ef69270fef 100644 --- a/src/Fable.Transforms/Dart/Replacements.fs +++ b/src/Fable.Transforms/Dart/Replacements.fs @@ -1696,6 +1696,25 @@ let seqModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg ) |> Some +/// BinarySearch without a comparer, or with a null one, uses the default comparer, like ResizeArray.Sort() +let binarySearch (com: ICompiler) ctx r t (i: CallInfo) rangeMeth (ar: Expr) args = + let call meth args comparer = + let comparer = + match comparer with + | None + | Some NullConst -> makeComparer com ctx (getElementType ar.Type) + | Some comparer -> comparer + + Helper.LibCall(com, "Array", meth, t, ar :: args @ [ comparer ], genArgs = i.GenericArgs, ?loc = r) + |> Some + + match args with + | [ value ] -> call "binarySearch" [ value ] None + | [ value; comparer ] -> call "binarySearch" [ value ] (Some comparer) + | [ index; length; value ] -> call rangeMeth [ index; length; value ] None + | [ index; length; value; comparer ] -> call rangeMeth [ index; length; value ] (Some comparer) + | _ -> None + let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg, args with | ".ctor", _, [] -> makeResizeArray (getElementType t) [] |> Some @@ -1741,6 +1760,7 @@ let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (this Helper.LibCall(com, "Array", "getSubArray", t, [ ar; idx; cnt ], genArgs = i.GenericArgs, ?loc = r) |> Some | "IndexOf", Some ar, args -> Helper.InstanceCall(ar, "indexOf", t, args, ?loc = r) |> Some + | "BinarySearch", Some ar, args -> binarySearch com ctx r t i "binarySearchRangeCount" ar args | "Insert", Some ar, [ idx; arg ] -> Helper.InstanceCall(ar, "insert", t, [ idx; arg ], ?loc = r) |> Some | "InsertRange", Some ar, [ idx; arg ] -> Helper.InstanceCall(ar, "insertAll", t, [ idx; arg ], ?loc = r) |> Some | "RemoveRange", Some ar, [ startIdx; count ] -> @@ -1823,6 +1843,7 @@ let arrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: E | "IndexOf", None, args -> Helper.LibCall(com, "Array", "indexOf", t, args, i.SignatureArgTypes, genArgs = i.GenericArgs, ?loc = r) |> Some + | "BinarySearch", None, ar :: args -> binarySearch com ctx r t i "binarySearchRange" ar args | "GetEnumerator", Some arg, _ -> getEnumerator com r t arg |> Some | _ -> None diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 248e3266ef..4fff6e121e 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -1850,6 +1850,26 @@ let injectIndexOfArgs com ctx r genArgs args = injectArg com ctx r "Array" "indexOf" genArgs args +/// resize_array's binary search indexes arrays and ResizeArrays alike. +/// As in .NET, an omitted or null comparer means the default one. +let binarySearch (com: ICompiler) ctx r t rangeMeth (ar: Expr) args = + let call meth args comparer = + let comparer = + match comparer with + | None + | Some NullConst -> makeComparer com ctx (getElementType ar.Type) + | Some comparer -> comparer + + Helper.LibCall(com, "resize_array", meth, t, ar :: args @ [ comparer ], ?loc = r) + |> Some + + match args with + | [ value ] -> call "binary_search" [ value ] None + | [ value; comparer ] -> call "binary_search" [ value ] (Some comparer) + | [ index; length; value ] -> call rangeMeth [ index; length; value ] None + | [ index; length; value; comparer ] -> call rangeMeth [ index; length; value ] (Some comparer) + | _ -> None + let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg, args with | ".ctor", _, [] -> makeResizeArray (getElementType t) [] |> Some @@ -1928,6 +1948,7 @@ let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (this Helper.LibCall(com, "resize_array", "index_of", t, args @ [ ar ], ?loc = r) |> Some + | "BinarySearch", Some ar, args -> binarySearch com ctx r t "binary_search_range_count" ar args | "Insert", Some ar, [ idx; arg ] -> Helper.InstanceCall(ar, "insert", t, [ idx; arg ], ?loc = r) |> Some | "InsertRange", Some ar, [ idx; arg ] -> Helper.LibCall(com, "resize_array", "insert_range_in_place", t, [ idx; arg; ar ], ?loc = r) @@ -2034,6 +2055,7 @@ let arrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: E Helper.LibCall(com, "array", "index_of", t, args, i.SignatureArgTypes, ?loc = r) |> Some + | "BinarySearch", None, ar :: args -> binarySearch com ctx r t "binary_search_range" ar args | "GetEnumerator", Some arg, _ -> getEnumerator com r t arg |> Some | "Resize", None, args -> let args = diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 8109a1ae2a..e519e3f749 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -2155,6 +2155,28 @@ let copyToArray (com: ICompiler) r t (i: CallInfo) args = Helper.LibCall(com, "Util", "copyToArray", t, args, i.SignatureArgTypes, genArgs = i.GenericArgs, ?loc = r) |> Some +let binarySearch (com: ICompiler) ctx r t rangeMeth (ar: Expr) args = + let call meth args comparer = + let comparer = + match comparer with + // .NET uses the default comparer when the comparer is null. The null literal + // gets here as Util.defaultOf() unless its type is nullable (see defaultof) + | None + | Some NullConst + | Some(MaybeCasted(Call(Import({ Selector = "defaultOf" }, _, _), _, _, _))) -> + makeComparer com ctx (getElementType ar.Type) + | Some comparer -> comparer + + Helper.LibCall(com, "Array", meth, t, ar :: args @ [ comparer ], ?loc = r) + |> Some + + match args with + | [ value ] -> call "binarySearch" [ value ] None + | [ value; comparer ] -> call "binarySearch" [ value ] (Some comparer) + | [ index; length; value ] -> call rangeMeth [ index; length; value ] None + | [ index; length; value; comparer ] -> call rangeMeth [ index; length; value ] (Some comparer) + | _ -> None + let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg, args with | ".ctor", _, [] -> makeResizeArray (getElementType t) [] |> Some @@ -2233,6 +2255,7 @@ let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (this | "IndexOf", Some ar, args -> let args = injectIndexOfArgs com ctx r i.GenericArgs (ar :: args) Helper.LibCall(com, "Array", "indexOf", t, args, ?loc = r) |> Some + | "BinarySearch", Some ar, args -> binarySearch com ctx r t "binarySearchRangeCount" ar args | "Insert", Some ar, [ idx; arg ] -> Helper.InstanceCall(ar, "splice", t, [ idx; makeIntConst 0; arg ], ?loc = r) |> Some @@ -2329,6 +2352,7 @@ let arrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: E Helper.LibCall(com, "Array", "indexOf", t, args, i.SignatureArgTypes, genArgs = i.GenericArgs, ?loc = r) |> Some + | "BinarySearch", None, ar :: args -> binarySearch com ctx r t "binarySearchRange" ar args | "GetEnumerator", Some arg, _ -> getEnumerator com r t arg |> Some | "Resize", None, args -> let args = diff --git a/src/fable-library-beam/fable_resize_array.erl b/src/fable-library-beam/fable_resize_array.erl index ec594f894c..c24a6df568 100644 --- a/src/fable-library-beam/fable_resize_array.erl +++ b/src/fable-library-beam/fable_resize_array.erl @@ -9,6 +9,9 @@ insert_range/3, get_range/3, index_of/2, index_of/3, + binary_search/3, + binary_search_range/5, + binary_search_range_count/5, find/3, find_last/2, find_all/2, @@ -32,6 +35,9 @@ -spec get_range(list(), non_neg_integer(), non_neg_integer()) -> list(). -spec index_of(list(), term()) -> integer(). -spec index_of(list(), term(), non_neg_integer()) -> integer(). +-spec binary_search(list(), term(), map() | reference()) -> integer(). +-spec binary_search_range(list(), integer(), integer(), term(), map() | reference()) -> integer(). +-spec binary_search_range_count(list(), integer(), integer(), term(), map() | reference()) -> integer(). -spec find(fun(), list(), term()) -> term(). -spec find_last(list(), fun()) -> term(). -spec find_all(list(), fun()) -> list(). @@ -117,6 +123,54 @@ index_of_impl([], _Item, _Idx) -> -1; index_of_impl([Item | _], Item, Idx) -> Idx; index_of_impl([_ | Rest], Item, Idx) -> index_of_impl(Rest, Item, Idx + 1). +%% BinarySearch with an IComparer, probing in .NET's order so duplicates and comparers get .NET's +%% answers; a miss returns the complement of the index of the first larger element. +binary_search(List, Value, Comparer) -> + Compare = fable_utils:iface_get(compare, Comparer), + binary_search_impl(list_to_tuple(List), 0, length(List) - 1, Value, Compare). + +%% System.Array.BinarySearch(array, index, length, value, comparer) +binary_search_range(List, Index, Length, Value, Comparer) -> + binary_search_range(List, Index, Length, Value, Comparer, <<"length">>). + +%% List.BinarySearch(index, count, item, comparer), whose count .NET calls "count" +binary_search_range_count(List, Index, Count, Item, Comparer) -> + binary_search_range(List, Index, Count, Item, Comparer, <<"count">>). + +%% .NET checks the range in this order before searching it +binary_search_range(_List, Index, _Count, _Value, _Comparer, _CountName) when Index < 0 -> + non_negative_number_required(<<"index">>); +binary_search_range(_List, _Index, Count, _Value, _Comparer, CountName) when Count < 0 -> + non_negative_number_required(CountName); +binary_search_range(List, Index, Count, _Value, _Comparer, _CountName) + when length(List) - Index < Count -> + erlang:error(#{ + exn_type => argument_exception, + message => + <<"Offset and length were out of bounds for the array or count is greater than the " + "number of elements from index to the end of the source collection.">> + }); +binary_search_range(List, Index, Count, Value, Comparer, _CountName) -> + Compare = fable_utils:iface_get(compare, Comparer), + binary_search_impl(list_to_tuple(List), Index, Index + Count - 1, Value, Compare). + +binary_search_impl(Tuple, Lo, Hi, Value, Compare) when Lo =< Hi -> + I = Lo + ((Hi - Lo) bsr 1), + case Compare(element(I + 1, Tuple), Value) of + 0 -> I; + C when C < 0 -> binary_search_impl(Tuple, I + 1, Hi, Value, Compare); + _ -> binary_search_impl(Tuple, Lo, I - 1, Value, Compare) + end; +binary_search_impl(_Tuple, Lo, _Hi, _Value, _Compare) -> + bnot Lo. + +non_negative_number_required(ParamName) -> + erlang:error(#{ + exn_type => argument_out_of_range_exception, + message => + fable_utils:argument_exception_message(<<"Non-negative number required.">>, ParamName) + }). + %% Find first matching predicate, return Default when not found find(_Pred, [], Default) -> Default; diff --git a/src/fable-library-dart/Array.fs b/src/fable-library-dart/Array.fs index 447fd3cdb6..e5b61d9c63 100644 --- a/src/fable-library-dart/Array.fs +++ b/src/fable-library-dart/Array.fs @@ -411,6 +411,50 @@ let indexOf (array: 'T[]) (item: 'T) (start: int option) (count: int option) : i | Some count when i >= start + count -> -1 | _ -> i +// Follows .NET's probe order, so duplicates and comparers with side effects give the same results +let private binarySearchFrom (array: 'T[]) (index: int) (length: int) (value: 'T) (comparer: IComparer<'T>) : int = + let rec loop lo hi = + if lo > hi then + ~~~lo + else + let i = lo + ((hi - lo) >>> 1) + let c = comparer.Compare(array[i], value) + + if c = 0 then + i + elif c < 0 then + loop (i + 1) hi + else + loop lo (i - 1) + + loop index (index + length - 1) + +let private checkBinarySearchRange (lengthName: string) (array: 'T[]) (index: int) (length: int) : unit = + if index < 0 then + raise (System.ArgumentOutOfRangeException("index", "Non-negative number required.")) + + if length < 0 then + raise (System.ArgumentOutOfRangeException(lengthName, "Non-negative number required.")) + + if array.Length - index < length then + raise ( + System.ArgumentException( + "Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection." + ) + ) + +let binarySearch (array: 'T[]) (value: 'T) (comparer: IComparer<'T>) : int = + binarySearchFrom array 0 array.Length value comparer + +let binarySearchRange (array: 'T[]) (index: int) (length: int) (value: 'T) (comparer: IComparer<'T>) : int = + checkBinarySearchRange "length" array index length + binarySearchFrom array index length value comparer + +// ResizeArray's range overload, which names its length "count" +let binarySearchRangeCount (array: 'T[]) (index: int) (count: int) (value: 'T) (comparer: IComparer<'T>) : int = + checkBinarySearchRange "count" array index count + binarySearchFrom array index count value comparer + let partition (f: 'T -> bool) (source: 'T[]) = let res1 = ResizeArray() let res2 = ResizeArray() diff --git a/src/fable-library-py/fable_library/resize_array.py b/src/fable-library-py/fable_library/resize_array.py index e31c625c5f..d2b405f733 100644 --- a/src/fable-library-py/fable_library/resize_array.py +++ b/src/fable-library-py/fable_library/resize_array.py @@ -1,11 +1,12 @@ from __future__ import annotations -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Sequence from typing import Any, cast from .core import int32 from .option import Option, some -from .protocols import IEnumerable_1 +from .protocols import IComparer_1, IEnumerable_1 +from .system import ArgumentException__ctor_Z721C83C5, ArgumentOutOfRangeException__ctor_Z384F8060 from .util import to_iterable @@ -100,6 +101,52 @@ def index_of[T](value: T, start: int, count: int | None, xs: list[T]) -> int: return -1 +def _check_range(size: int, index: int, count: int, count_name: str) -> None: + """Raise like .NET when index and count do not describe a range of a collection of the given size.""" + if index < 0: + raise ArgumentOutOfRangeException__ctor_Z384F8060("index", "Non-negative number required.") + if count < 0: + raise ArgumentOutOfRangeException__ctor_Z384F8060(count_name, "Non-negative number required.") + if size - index < count: + raise ArgumentException__ctor_Z721C83C5( + "Offset and length were out of bounds for the array or count is greater than " + + "the number of elements from index to the end of the source collection." + ) + + +def _binary_search[T](array: Sequence[T], index: int, length: int, value: T, comparer: IComparer_1[T]) -> int: + """Search array[index:index + length] in .NET's probe order. Returns ~insertion point if not found.""" + lo = index + hi = index + length - 1 + while lo <= hi: + i = lo + ((hi - lo) >> 1) + c = comparer.Compare(array[i], value) + if c == 0: + return int32(i) + if c < 0: + lo = i + 1 + else: + hi = i - 1 + return int32(~lo) + + +def binary_search[T](array: Sequence[T], value: T, comparer: IComparer_1[T]) -> int: + """Binary search a sorted array or ResizeArray (System.Array.BinarySearch).""" + return _binary_search(array, 0, len(array), value, comparer) + + +def binary_search_range[T](array: Sequence[T], index: int, length: int, value: T, comparer: IComparer_1[T]) -> int: + """Binary search a sorted range of an array (System.Array.BinarySearch with index and length).""" + _check_range(len(array), index, length, "length") + return _binary_search(array, index, length, value, comparer) + + +def binary_search_range_count[T](xs: list[T], index: int, count: int, value: T, comparer: IComparer_1[T]) -> int: + """Binary search a sorted range of a ResizeArray (ResizeArray.BinarySearch with index and count).""" + _check_range(len(xs), index, count, "count") + return _binary_search(xs, index, count, value, comparer) + + def insert_range_in_place[T](index: int, items: Iterable[T] | IEnumerable_1[T], xs: list[T]) -> None: """Insert a range of items into xs at the given index.""" xs[index:index] = to_iterable(items) @@ -141,6 +188,9 @@ def contains[T](value: T, xs: list[T], cons: Any | None = None) -> bool: "add_in_place", "add_range", "add_range_in_place", + "binary_search", + "binary_search_range", + "binary_search_range_count", "contains", "exists", "filter", diff --git a/src/fable-library-ts/Array.fs b/src/fable-library-ts/Array.fs index 9632b11662..8a95276aa3 100644 --- a/src/fable-library-ts/Array.fs +++ b/src/fable-library-ts/Array.fs @@ -242,6 +242,50 @@ let indexOf<'T> let contains<'T> (value: 'T) (array: 'T[]) ([] eq: IEqualityComparer<'T>) = indexOf array value None None eq >= 0 +// Follows .NET's probe order, so duplicates and comparers with side effects give the same results +let private binarySearchFrom (array: 'T[]) (index: int) (length: int) (value: 'T) (comparer: IComparer<'T>) = + let rec loop lo hi = + if lo > hi then + ~~~lo + else + let i = lo + ((hi - lo) >>> 1) + let c = comparer.Compare(array.[i], value) + + if c = 0 then + i + elif c < 0 then + loop (i + 1) hi + else + loop lo (i - 1) + + loop index (index + length - 1) + +let private checkBinarySearchRange (lengthName: string) (array: 'T[]) (index: int) (length: int) = + if index < 0 then + raise (System.ArgumentOutOfRangeException("index", "Non-negative number required.")) + + if length < 0 then + raise (System.ArgumentOutOfRangeException(lengthName, "Non-negative number required.")) + + if array.Length - index < length then + raise ( + System.ArgumentException( + "Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection." + ) + ) + +let binarySearch (array: 'T[]) (value: 'T) (comparer: IComparer<'T>) : int = + binarySearchFrom array 0 array.Length value comparer + +let binarySearchRange (array: 'T[]) (index: int) (length: int) (value: 'T) (comparer: IComparer<'T>) : int = + checkBinarySearchRange "length" array index length + binarySearchFrom array index length value comparer + +// ResizeArray's range overload, which names its length "count" +let binarySearchRangeCount (array: 'T[]) (index: int) (count: int) (value: 'T) (comparer: IComparer<'T>) : int = + checkBinarySearchRange "count" array index count + binarySearchFrom array index count value comparer + let empty cons = allocateArrayFromCons cons 0 let singleton value ([] cons: Cons<'T>) = diff --git a/tests/Beam/ArrayTests.fs b/tests/Beam/ArrayTests.fs index 9b4544b7d5..3f9329abff 100644 --- a/tests/Beam/ArrayTests.fs +++ b/tests/Beam/ArrayTests.fs @@ -981,6 +981,140 @@ let ``test System.Array.IndexOf works with non-primitive types`` () = System.Array.IndexOf(myArray, Dog 5) |> equal -1 System.Array.IndexOf(myArray, Duck 5) |> equal 0 +[] +let ``test System.Array.BinarySearch works`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 0) |> equal -1 + System.Array.BinarySearch(xs, 1) |> equal 0 + System.Array.BinarySearch(xs, 2) |> equal -2 + System.Array.BinarySearch(xs, 7) |> equal 3 + System.Array.BinarySearch(xs, 8) |> equal -5 + System.Array.BinarySearch(xs, 10) |> equal -6 + System.Array.BinarySearch(([||]: int[]), 3) |> equal -1 + +[] +let ``test System.Array.BinarySearch works with duplicates`` () = + System.Array.BinarySearch([|2; 2; 2; 2; 2; 2; 2; 2|], 2) |> equal 3 + System.Array.BinarySearch([|1; 2; 2; 2; 2; 3; 4; 5; 6|], 2) |> equal 4 + +[] +let ``test System.Array.BinarySearch works with a range`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2) |> equal -2 + System.Array.BinarySearch(xs, 1, 3, 9) |> equal -5 + System.Array.BinarySearch(xs, 1, 2, 9) |> equal -4 + System.Array.BinarySearch(xs, 2, 0, 5) |> equal -3 + System.Array.BinarySearch(xs, 5, 0, 5) |> equal -6 + +[] +let ``test System.Array.BinarySearch works with a comparer`` () = + let desc = { new System.Collections.Generic.IComparer with member _.Compare(a, b) = compare b a } + let ys = [|9; 7; 5; 3; 1|] + System.Array.BinarySearch(ys, 3, desc) |> equal 3 + System.Array.BinarySearch(ys, 4, desc) |> equal -4 + System.Array.BinarySearch(ys, 1, 3, 3, desc) |> equal 3 + System.Array.BinarySearch([|1; 3; 5; 7; 9|], 7, (null: System.Collections.Generic.IComparer)) |> equal 3 + +[] +let ``test System.Array.BinarySearch passes the array element to the comparer first`` () = + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 9, logging) |> equal 4 + List.ofSeq calls |> equal [(5, 9); (7, 9); (9, 9)] + calls.Clear() + System.Array.BinarySearch(xs, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (1, 4); (3, 4)] + +type BinarySearchRecord = { A: int; B: string } + +[] +let ``test System.Array.BinarySearch works with other element types`` () = + let strings = [|"apple"; "banana"; "cherry"|] + System.Array.BinarySearch(strings, "banana") |> equal 1 + System.Array.BinarySearch(strings, "blue") |> equal -3 + let floats = [|1.5; 2.5; 3.5|] + System.Array.BinarySearch(floats, 2.5) |> equal 1 + System.Array.BinarySearch(floats, 3.0) |> equal -3 + let int64s = [|1L; 5L; 10L|] + System.Array.BinarySearch(int64s, 10L) |> equal 2 + System.Array.BinarySearch(int64s, 6L) |> equal -3 + let records = [|{ A = 1; B = "x" }; { A = 2; B = "a" }; { A = 2; B = "b" }|] + System.Array.BinarySearch(records, { A = 2; B = "b" }) |> equal 2 + System.Array.BinarySearch(records, { A = 1; B = "z" }) |> equal -2 + let unions = [|Duck 1; Duck 4; Dog 0|] + System.Array.BinarySearch(unions, Dog 0) |> equal 2 + System.Array.BinarySearch(unions, Duck 2) |> equal -2 + +[] +let ``test System.Array.BinarySearch works with a range and a null comparer`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7, null) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2, null) |> equal -2 + +[] +let ``test System.Array.BinarySearch works with a single element and a large array`` () = + System.Array.BinarySearch([|5|], 5) |> equal 0 + System.Array.BinarySearch([|5|], 3) |> equal -1 + System.Array.BinarySearch([|5|], 7) |> equal -2 + let big = Array.init 1000 (fun i -> i * 2) + System.Array.BinarySearch(big, 998) |> equal 499 + System.Array.BinarySearch(big, 999) |> equal -501 + +[] +let ``test System.Array.BinarySearch works with more element types`` () = + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 3uy) |> equal 1 + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 4uy) |> equal -3 + System.Array.BinarySearch([|-5s; 0s; 5s|], 0s) |> equal 1 + System.Array.BinarySearch([|-5s; 0s; 5s|], -6s) |> equal -1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'c') |> equal 1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'd') |> equal -3 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "b")) |> equal 1 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "c")) |> equal -3 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 2.5M) |> equal 1 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 3.0M) |> equal -3 + let dates = [|System.DateTime(2020, 1, 1); System.DateTime(2021, 1, 1)|] + System.Array.BinarySearch(dates, System.DateTime(2021, 1, 1)) |> equal 1 + System.Array.BinarySearch(dates, System.DateTime(2020, 6, 1)) |> equal -2 + System.Array.BinarySearch([|1I; 5I; 10I|], 5I) |> equal 1 + System.Array.BinarySearch([|1I; 5I; 10I|], 11I) |> equal -4 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 1) |> equal 1 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 2) |> equal -3 + +[] +let ``test System.Array.BinarySearch with a range passes the array element to the comparer first`` () = + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 1, 3, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (3, 4)] + +[] +let ``test System.Array.BinarySearch with a range checks its arguments`` () = + let xs = [|1; 3; 5; 7; 9|] + let raised index length = + try + System.Array.BinarySearch(xs, index, length, 5) |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised -1 2 |> equal "ArgumentOutOfRangeException" + raised -1 -1 |> equal "ArgumentOutOfRangeException" + raised 1 -1 |> equal "ArgumentOutOfRangeException" + raised 3 3 |> equal "ArgumentException" + raised 6 0 |> equal "ArgumentException" + // --- Additional tests ported from JS --- [] diff --git a/tests/Beam/ResizeArrayTests.fs b/tests/Beam/ResizeArrayTests.fs index 77391ca97f..e8d8c3e7d8 100644 --- a/tests/Beam/ResizeArrayTests.fs +++ b/tests/Beam/ResizeArrayTests.fs @@ -65,6 +65,72 @@ let ``test ResizeArray.IndexOf works`` () = ra.IndexOf(20) |> equal 1 ra.IndexOf(99) |> equal -1 +// --- BinarySearch --- + +[] +let ``test ResizeArray.BinarySearch works`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let desc = { new IComparer with member _.Compare(a, b) = compare b a } + // Beam has no Comparer.Default, so this stands in for it + let asc = { new IComparer with member _.Compare(a, b) = compare a b } + ra.BinarySearch(7) |> equal 3 + ra.BinarySearch(8) |> equal -5 + (ResizeArray [9; 7; 5; 3; 1]).BinarySearch(3, desc) |> equal 3 + ra.BinarySearch(1, 3, 7, asc) |> equal 3 + ra.BinarySearch(1, 2, 8, asc) |> equal -4 + ra.BinarySearch(1, 2, 3, null) |> equal 1 + +[] +let ``test ResizeArray.BinarySearch works with an empty list, duplicates and a null range comparer`` () = + ResizeArray().BinarySearch(1) |> equal -1 + (ResizeArray [2; 2; 2; 2]).BinarySearch(2) |> equal 1 + (ResizeArray [1; 2; 2; 2; 2; 3]).BinarySearch(2) |> equal 2 + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(0, 5, 2, null) |> equal -2 + ra.BinarySearch(2, 3, 9, null) |> equal 4 + +[] +let ``test ResizeArray.BinarySearch with a range passes the element to the comparer first`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + ra.BinarySearch(1, 3, 7, logging) |> equal 3 + List.ofSeq calls |> equal [(5, 7); (7, 7)] + +[] +let ``test ResizeArray.BinarySearch with a range checks index and count together`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(6, 0, 3, null)) |> equal "ArgumentException" + raised (fun () -> ra.BinarySearch(0, 6, 3, null)) |> equal "ArgumentException" + +[] +let ``test ResizeArray.BinarySearch with a range checks its arguments`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised index count = + try + ra.BinarySearch(index, count, 3, null) |> ignore + "none" + with + | :? ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? ArgumentException -> "ArgumentException" + | _ -> "other" + raised -1 2 |> equal "ArgumentOutOfRangeException" + raised 1 -1 |> equal "ArgumentOutOfRangeException" + raised 3 3 |> equal "ArgumentException" + // --- Remove --- [] diff --git a/tests/Dart/src/ArrayTests.fs b/tests/Dart/src/ArrayTests.fs index 54aaa76232..132b255618 100644 --- a/tests/Dart/src/ArrayTests.fs +++ b/tests/Dart/src/ArrayTests.fs @@ -36,6 +36,16 @@ type Things = type Animal = Duck of int | Dog of int +type DescendingComparer() = + interface System.Collections.Generic.IComparer with + member _.Compare(x, y) = compare y x + +type LoggingComparer(calls: ResizeArray) = + interface System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y + let tests () = testCase "Pattern matching with arrays works" <| fun () -> match [||] with [||] -> true | _ -> false @@ -440,6 +450,118 @@ let tests () = // System.Array.IndexOf(myArray, Dog 3, 0, 1) |> equal -1 // System.Array.IndexOf(myArray, Duck 5, 1) |> equal -1 + testCase "System.Array.BinarySearch works" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 0) |> equal -1 + System.Array.BinarySearch(xs, 1) |> equal 0 + System.Array.BinarySearch(xs, 2) |> equal -2 + System.Array.BinarySearch(xs, 7) |> equal 3 + System.Array.BinarySearch(xs, 8) |> equal -5 + System.Array.BinarySearch(xs, 10) |> equal -6 + System.Array.BinarySearch(([||]: int[]), 3) |> equal -1 + + testCase "System.Array.BinarySearch works with duplicates" <| fun () -> + System.Array.BinarySearch([|2; 2; 2; 2; 2; 2; 2; 2|], 2) |> equal 3 + System.Array.BinarySearch([|1; 2; 2; 2; 2; 3; 4; 5; 6|], 2) |> equal 4 + + testCase "System.Array.BinarySearch works with a range" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2) |> equal -2 + System.Array.BinarySearch(xs, 1, 3, 9) |> equal -5 + System.Array.BinarySearch(xs, 1, 2, 9) |> equal -4 + System.Array.BinarySearch(xs, 2, 0, 5) |> equal -3 + System.Array.BinarySearch(xs, 5, 0, 5) |> equal -6 + + testCase "System.Array.BinarySearch works with a comparer" <| fun () -> + let ys = [|9; 7; 5; 3; 1|] + System.Array.BinarySearch(ys, 3, DescendingComparer()) |> equal 3 + System.Array.BinarySearch(ys, 4, DescendingComparer()) |> equal -4 + System.Array.BinarySearch(ys, 1, 3, 3, DescendingComparer()) |> equal 3 + System.Array.BinarySearch([|1; 3; 5; 7; 9|], 7, (null: System.Collections.Generic.IComparer)) |> equal 3 + + testCase "System.Array.BinarySearch passes the array element to the comparer first" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + System.Array.BinarySearch(xs, 9, LoggingComparer(calls)) |> equal 4 + List.ofSeq calls |> equal [(5, 9); (7, 9); (9, 9)] + calls.Clear() + System.Array.BinarySearch(xs, 4, LoggingComparer(calls)) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (1, 4); (3, 4)] + + testCase "System.Array.BinarySearch works with other element types" <| fun () -> + let strings = [|"apple"; "banana"; "cherry"|] + System.Array.BinarySearch(strings, "banana") |> equal 1 + System.Array.BinarySearch(strings, "blue") |> equal -3 + let floats = [|1.5; 2.5; 3.5|] + System.Array.BinarySearch(floats, 2.5) |> equal 1 + System.Array.BinarySearch(floats, 3.0) |> equal -3 + let int64s = [|1L; 5L; 10L|] + System.Array.BinarySearch(int64s, 10L) |> equal 2 + System.Array.BinarySearch(int64s, 6L) |> equal -3 + let things = [|{ MainThing = 1; OtherThing = "x" }; { MainThing = 2; OtherThing = "a" }; { MainThing = 2; OtherThing = "b" }|] + System.Array.BinarySearch(things, { MainThing = 2; OtherThing = "b" }) |> equal 2 + System.Array.BinarySearch(things, { MainThing = 1; OtherThing = "z" }) |> equal -2 + let animals = [|Duck 1; Duck 4; Dog 0|] + System.Array.BinarySearch(animals, Dog 0) |> equal 2 + System.Array.BinarySearch(animals, Duck 2) |> equal -2 + + testCase "System.Array.BinarySearch works with a range and a null comparer" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7, null) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2, null) |> equal -2 + + testCase "System.Array.BinarySearch works with a single element and a large array" <| fun () -> + System.Array.BinarySearch([|5|], 5) |> equal 0 + System.Array.BinarySearch([|5|], 3) |> equal -1 + System.Array.BinarySearch([|5|], 7) |> equal -2 + let big = Array.init 1000 (fun i -> i * 2) + System.Array.BinarySearch(big, 998) |> equal 499 + System.Array.BinarySearch(big, 999) |> equal -501 + + testCase "System.Array.BinarySearch works with more element types" <| fun () -> + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 3uy) |> equal 1 + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 4uy) |> equal -3 + System.Array.BinarySearch([|-5s; 0s; 5s|], 0s) |> equal 1 + System.Array.BinarySearch([|-5s; 0s; 5s|], -6s) |> equal -1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'c') |> equal 1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'd') |> equal -3 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "b")) |> equal 1 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "c")) |> equal -3 + let dates = [|System.DateTime(2020, 1, 1); System.DateTime(2021, 1, 1)|] + System.Array.BinarySearch(dates, System.DateTime(2021, 1, 1)) |> equal 1 + System.Array.BinarySearch(dates, System.DateTime(2020, 6, 1)) |> equal -2 + + testCase "System.Array.BinarySearch with a range passes the array element to the comparer first" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + LoggingComparer(calls) + System.Array.BinarySearch(xs, 1, 3, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (3, 4)] + + testCase "System.Array.BinarySearch non-generic overloads work" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs :> System.Array, box 7) |> equal 3 + System.Array.BinarySearch(xs :> System.Array, box 4) |> equal -3 + System.Array.BinarySearch(xs :> System.Array, 1, 3, box 5) |> equal 2 + + testCase "System.Array.BinarySearch with a range checks its arguments" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> System.Array.BinarySearch(xs, -1, 2, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, -1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 3, 3, 5)) |> equal "ArgumentException" + raised (fun () -> System.Array.BinarySearch(xs, 6, 0, 5)) |> equal "ArgumentException" + testCase "Array.findIndex works" <| fun () -> let xs = [|1.f; 2.f; 3.f; 4.f|] xs |> Array.findIndex ((=) 2.f) diff --git a/tests/Dart/src/ResizeArrayTests.fs b/tests/Dart/src/ResizeArrayTests.fs index 706e5f788e..61788d980c 100644 --- a/tests/Dart/src/ResizeArrayTests.fs +++ b/tests/Dart/src/ResizeArrayTests.fs @@ -2,6 +2,20 @@ open Util +type AscendingComparer() = + interface System.Collections.Generic.IComparer with + member _.Compare(x, y) = compare x y + +type DescendingComparer() = + interface System.Collections.Generic.IComparer with + member _.Compare(x, y) = compare y x + +type LoggingComparer(calls: ResizeArray) = + interface System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y + let tests() = testCase "ResizeArray zero creation works" <| fun () -> let li = ResizeArray() @@ -171,6 +185,60 @@ let tests() = li.IndexOf("ab") |> equal 1 li.IndexOf("cd") |> equal -1 + testCase "ResizeArray.BinarySearch works" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(7) |> equal 3 + ra.BinarySearch(8) |> equal -5 + (ResizeArray [9; 7; 5; 3; 1]).BinarySearch(3, DescendingComparer()) |> equal 3 + // Comparer.Default isn't supported in Dart + ra.BinarySearch(1, 3, 7, AscendingComparer()) |> equal 3 + ra.BinarySearch(1, 2, 8, AscendingComparer()) |> equal -4 + ra.BinarySearch(1, 2, 3, null) |> equal 1 + + testCase "ResizeArray.BinarySearch works with an empty list, duplicates and a null range comparer" <| fun () -> + ResizeArray().BinarySearch(1) |> equal -1 + (ResizeArray [2; 2; 2; 2]).BinarySearch(2) |> equal 1 + (ResizeArray [1; 2; 2; 2; 2; 3]).BinarySearch(2) |> equal 2 + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(0, 5, 2, null) |> equal -2 + ra.BinarySearch(2, 3, 9, null) |> equal 4 + + testCase "ResizeArray.BinarySearch with a range passes the element to the comparer first" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let calls = ResizeArray() + let logging = + LoggingComparer(calls) + ra.BinarySearch(1, 3, 7, logging) |> equal 3 + List.ofSeq calls |> equal [(5, 7); (7, 7)] + + testCase "ResizeArray.BinarySearch with a range checks index and count together" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(6, 0, 3, null)) |> equal "ArgumentException" + raised (fun () -> ra.BinarySearch(0, 6, 3, null)) |> equal "ArgumentException" + + testCase "ResizeArray.BinarySearch with a range checks its arguments" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, 2, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(3, 3, 3, null)) |> equal "ArgumentException" + testCase "ResizeArray.Remove works" <| fun () -> let li = ResizeArray<_>() li.Add("ab") diff --git a/tests/Js/Main/ArrayTests.fs b/tests/Js/Main/ArrayTests.fs index b0b90a14a6..4347b44434 100644 --- a/tests/Js/Main/ArrayTests.fs +++ b/tests/Js/Main/ArrayTests.fs @@ -563,6 +563,133 @@ let tests = System.Array.IndexOf(myArray, Dog 3, 0, 1) |> equal -1 System.Array.IndexOf(myArray, Duck 5, 1) |> equal -1 + testCase "System.Array.BinarySearch works" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 0) |> equal -1 + System.Array.BinarySearch(xs, 1) |> equal 0 + System.Array.BinarySearch(xs, 2) |> equal -2 + System.Array.BinarySearch(xs, 7) |> equal 3 + System.Array.BinarySearch(xs, 8) |> equal -5 + System.Array.BinarySearch(xs, 10) |> equal -6 + System.Array.BinarySearch(([||]: int[]), 3) |> equal -1 + + testCase "System.Array.BinarySearch works with duplicates" <| fun () -> + System.Array.BinarySearch([|2; 2; 2; 2; 2; 2; 2; 2|], 2) |> equal 3 + System.Array.BinarySearch([|1; 2; 2; 2; 2; 3; 4; 5; 6|], 2) |> equal 4 + + testCase "System.Array.BinarySearch works with a range" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2) |> equal -2 + System.Array.BinarySearch(xs, 1, 3, 9) |> equal -5 + System.Array.BinarySearch(xs, 1, 2, 9) |> equal -4 + System.Array.BinarySearch(xs, 2, 0, 5) |> equal -3 + System.Array.BinarySearch(xs, 5, 0, 5) |> equal -6 + + testCase "System.Array.BinarySearch works with a comparer" <| fun () -> + let desc = ComparisonIdentity.FromFunction(fun (a: int) b -> compare b a) + let ys = [|9; 7; 5; 3; 1|] + System.Array.BinarySearch(ys, 3, desc) |> equal 3 + System.Array.BinarySearch(ys, 4, desc) |> equal -4 + System.Array.BinarySearch(ys, 1, 3, 3, desc) |> equal 3 + System.Array.BinarySearch([|1; 3; 5; 7; 9|], 7, (null: IComparer)) |> equal 3 + + testCase "System.Array.BinarySearch passes the array element to the comparer first" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 9, logging) |> equal 4 + List.ofSeq calls |> equal [(5, 9); (7, 9); (9, 9)] + calls.Clear() + System.Array.BinarySearch(xs, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (1, 4); (3, 4)] + + testCase "System.Array.BinarySearch works with other element types" <| fun () -> + let strings = [|"apple"; "banana"; "cherry"|] + System.Array.BinarySearch(strings, "banana") |> equal 1 + System.Array.BinarySearch(strings, "blue") |> equal -3 + let floats = [|1.5; 2.5; 3.5|] + System.Array.BinarySearch(floats, 2.5) |> equal 1 + System.Array.BinarySearch(floats, 3.0) |> equal -3 + let longs = [|1L; 5L; 10L|] + System.Array.BinarySearch(longs, 10L) |> equal 2 + System.Array.BinarySearch(longs, 6L) |> equal -3 + let things = [|{ MainThing = 1; OtherThing = "x" }; { MainThing = 2; OtherThing = "a" }; { MainThing = 2; OtherThing = "b" }|] + System.Array.BinarySearch(things, { MainThing = 2; OtherThing = "b" }) |> equal 2 + System.Array.BinarySearch(things, { MainThing = 1; OtherThing = "z" }) |> equal -2 + let animals = [|Duck 1; Duck 4; Dog 0|] + System.Array.BinarySearch(animals, Dog 0) |> equal 2 + System.Array.BinarySearch(animals, Duck 2) |> equal -2 + + testCase "System.Array.BinarySearch works with a range and a null comparer" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7, null) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2, null) |> equal -2 + + testCase "System.Array.BinarySearch works with a single element and a large array" <| fun () -> + System.Array.BinarySearch([|5|], 5) |> equal 0 + System.Array.BinarySearch([|5|], 3) |> equal -1 + System.Array.BinarySearch([|5|], 7) |> equal -2 + let big = Array.init 1000 (fun i -> i * 2) + System.Array.BinarySearch(big, 998) |> equal 499 + System.Array.BinarySearch(big, 999) |> equal -501 + + testCase "System.Array.BinarySearch works with more element types" <| fun () -> + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 3uy) |> equal 1 + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 4uy) |> equal -3 + System.Array.BinarySearch([|-5s; 0s; 5s|], 0s) |> equal 1 + System.Array.BinarySearch([|-5s; 0s; 5s|], -6s) |> equal -1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'c') |> equal 1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'd') |> equal -3 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "b")) |> equal 1 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "c")) |> equal -3 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 2.5M) |> equal 1 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 3.0M) |> equal -3 + let dates = [|System.DateTime(2020, 1, 1); System.DateTime(2021, 1, 1)|] + System.Array.BinarySearch(dates, System.DateTime(2021, 1, 1)) |> equal 1 + System.Array.BinarySearch(dates, System.DateTime(2020, 6, 1)) |> equal -2 + System.Array.BinarySearch([|1I; 5I; 10I|], 5I) |> equal 1 + System.Array.BinarySearch([|1I; 5I; 10I|], 11I) |> equal -4 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 1) |> equal 1 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 2) |> equal -3 + + testCase "System.Array.BinarySearch with a range passes the array element to the comparer first" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 1, 3, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (3, 4)] + + testCase "System.Array.BinarySearch non-generic overloads work" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs :> System.Array, box 7) |> equal 3 + System.Array.BinarySearch(xs :> System.Array, box 4) |> equal -3 + System.Array.BinarySearch(xs :> System.Array, 1, 3, box 5) |> equal 2 + + testCase "System.Array.BinarySearch with a range checks its arguments" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> System.Array.BinarySearch(xs, -1, 2, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, -1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 3, 3, 5)) |> equal "ArgumentException" + raised (fun () -> System.Array.BinarySearch(xs, 6, 0, 5)) |> equal "ArgumentException" + testCase "Array.findIndex works" <| fun () -> let xs = [|1.f; 2.f; 3.f; 4.f|] xs |> Array.findIndex ((=) 2.f) diff --git a/tests/Js/Main/ResizeArrayTests.fs b/tests/Js/Main/ResizeArrayTests.fs index fe0b8c22d6..408699ff97 100644 --- a/tests/Js/Main/ResizeArrayTests.fs +++ b/tests/Js/Main/ResizeArrayTests.fs @@ -283,6 +283,63 @@ let tests = ns.Sort(comparer) Seq.toList ns |> equal [1;2;3] + testCase "ResizeArray.BinarySearch works" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(7) |> equal 3 + ra.BinarySearch(8) |> equal -5 + let desc = ComparisonIdentity.FromFunction(fun (a: int) b -> compare b a) + (ResizeArray [9; 7; 5; 3; 1]).BinarySearch(3, desc) |> equal 3 + ra.BinarySearch(1, 3, 7, Comparer.Default) |> equal 3 + ra.BinarySearch(1, 2, 8, Comparer.Default) |> equal -4 + ra.BinarySearch(1, 2, 3, null) |> equal 1 + + testCase "ResizeArray.BinarySearch works with an empty list, duplicates and a null range comparer" <| fun () -> + ResizeArray().BinarySearch(1) |> equal -1 + (ResizeArray [2; 2; 2; 2]).BinarySearch(2) |> equal 1 + (ResizeArray [1; 2; 2; 2; 2; 3]).BinarySearch(2) |> equal 2 + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(0, 5, 2, null) |> equal -2 + ra.BinarySearch(2, 3, 9, null) |> equal 4 + + testCase "ResizeArray.BinarySearch with a range passes the element to the comparer first" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + ra.BinarySearch(1, 3, 7, logging) |> equal 3 + List.ofSeq calls |> equal [(5, 7); (7, 7)] + + testCase "ResizeArray.BinarySearch with a range checks index and count together" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(6, 0, 3, null)) |> equal "ArgumentException" + raised (fun () -> ra.BinarySearch(0, 6, 3, null)) |> equal "ArgumentException" + + testCase "ResizeArray.BinarySearch with a range checks its arguments" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, 2, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(3, 3, 3, null)) |> equal "ArgumentException" + testCase "ResizeArray.ToArray works" <| fun () -> let li = ResizeArray<_>() li.Add(3.); li.Add(6.); li.Add(5.); li.Add(4.); li.Add(8.) diff --git a/tests/Python/TestArray.fs b/tests/Python/TestArray.fs index 260343908c..c1f68a3c12 100644 --- a/tests/Python/TestArray.fs +++ b/tests/Python/TestArray.fs @@ -516,6 +516,147 @@ let ``test System.Array.IndexOf works with non-primitive types`` () = System.Array.IndexOf(myArray, Dog 3, 0, 1) |> equal -1 System.Array.IndexOf(myArray, Duck 5, 1) |> equal -1 +[] +let ``test System.Array.BinarySearch works`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 0) |> equal -1 + System.Array.BinarySearch(xs, 1) |> equal 0 + System.Array.BinarySearch(xs, 2) |> equal -2 + System.Array.BinarySearch(xs, 7) |> equal 3 + System.Array.BinarySearch(xs, 8) |> equal -5 + System.Array.BinarySearch(xs, 10) |> equal -6 + System.Array.BinarySearch(([||]: int[]), 3) |> equal -1 + +[] +let ``test System.Array.BinarySearch works with duplicates`` () = + System.Array.BinarySearch([|2; 2; 2; 2; 2; 2; 2; 2|], 2) |> equal 3 + System.Array.BinarySearch([|1; 2; 2; 2; 2; 3; 4; 5; 6|], 2) |> equal 4 + +[] +let ``test System.Array.BinarySearch works with a range`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2) |> equal -2 + System.Array.BinarySearch(xs, 1, 3, 9) |> equal -5 + System.Array.BinarySearch(xs, 1, 2, 9) |> equal -4 + System.Array.BinarySearch(xs, 2, 0, 5) |> equal -3 + System.Array.BinarySearch(xs, 5, 0, 5) |> equal -6 + +[] +let ``test System.Array.BinarySearch works with a comparer`` () = + let desc = ComparisonIdentity.FromFunction(fun (a: int) b -> compare b a) + let ys = [|9; 7; 5; 3; 1|] + System.Array.BinarySearch(ys, 3, desc) |> equal 3 + System.Array.BinarySearch(ys, 4, desc) |> equal -4 + System.Array.BinarySearch(ys, 1, 3, 3, desc) |> equal 3 + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 7, (null: System.Collections.Generic.IComparer)) |> equal 3 + +[] +let ``test System.Array.BinarySearch passes the array element to the comparer first`` () = + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 9, logging) |> equal 4 + List.ofSeq calls |> equal [(5, 9); (7, 9); (9, 9)] + calls.Clear() + System.Array.BinarySearch(xs, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (1, 4); (3, 4)] + +[] +let ``test System.Array.BinarySearch works with other element types`` () = + let strings = [|"apple"; "banana"; "cherry"|] + System.Array.BinarySearch(strings, "banana") |> equal 1 + System.Array.BinarySearch(strings, "blue") |> equal -3 + let floats = [|1.5; 2.5; 3.5|] + System.Array.BinarySearch(floats, 2.5) |> equal 1 + System.Array.BinarySearch(floats, 3.0) |> equal -3 + let longs = [|1L; 5L; 10L|] + System.Array.BinarySearch(longs, 10L) |> equal 2 + System.Array.BinarySearch(longs, 6L) |> equal -3 + let records = + [|{ MainThing = 1; OtherThing = "x" }; { MainThing = 2; OtherThing = "a" }; { MainThing = 2; OtherThing = "b" }|] + System.Array.BinarySearch(records, { MainThing = 2; OtherThing = "b" }) |> equal 2 + System.Array.BinarySearch(records, { MainThing = 1; OtherThing = "z" }) |> equal -2 + let unions = [|Duck 1; Duck 4; Dog 0|] + System.Array.BinarySearch(unions, Dog 0) |> equal 2 + System.Array.BinarySearch(unions, Duck 2) |> equal -2 + +[] +let ``test System.Array.BinarySearch works with a range and a null comparer`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7, null) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2, null) |> equal -2 + +[] +let ``test System.Array.BinarySearch works with a single element and a large array`` () = + System.Array.BinarySearch([|5|], 5) |> equal 0 + System.Array.BinarySearch([|5|], 3) |> equal -1 + System.Array.BinarySearch([|5|], 7) |> equal -2 + let big = Array.init 1000 (fun i -> i * 2) + System.Array.BinarySearch(big, 998) |> equal 499 + System.Array.BinarySearch(big, 999) |> equal -501 + +[] +let ``test System.Array.BinarySearch works with more element types`` () = + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 3uy) |> equal 1 + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 4uy) |> equal -3 + System.Array.BinarySearch([|-5s; 0s; 5s|], 0s) |> equal 1 + System.Array.BinarySearch([|-5s; 0s; 5s|], -6s) |> equal -1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'c') |> equal 1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'd') |> equal -3 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "b")) |> equal 1 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "c")) |> equal -3 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 2.5M) |> equal 1 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 3.0M) |> equal -3 + let dates = [|System.DateTime(2020, 1, 1); System.DateTime(2021, 1, 1)|] + System.Array.BinarySearch(dates, System.DateTime(2021, 1, 1)) |> equal 1 + System.Array.BinarySearch(dates, System.DateTime(2020, 6, 1)) |> equal -2 + System.Array.BinarySearch([|1I; 5I; 10I|], 5I) |> equal 1 + System.Array.BinarySearch([|1I; 5I; 10I|], 11I) |> equal -4 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 1) |> equal 1 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 2) |> equal -3 + +[] +let ``test System.Array.BinarySearch with a range passes the array element to the comparer first`` () = + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 1, 3, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (3, 4)] + +[] +let ``test System.Array.BinarySearch non-generic overloads work`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs :> System.Array, box 7) |> equal 3 + System.Array.BinarySearch(xs :> System.Array, box 4) |> equal -3 + System.Array.BinarySearch(xs :> System.Array, 1, 3, box 5) |> equal 2 + +[] +let ``test System.Array.BinarySearch with a range checks its arguments`` () = + let xs = [|1; 3; 5; 7; 9|] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> System.Array.BinarySearch(xs, -1, 2, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, -1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 3, 3, 5)) |> equal "ArgumentException" + raised (fun () -> System.Array.BinarySearch(xs, 6, 0, 5)) |> equal "ArgumentException" + [] let ``test Array.findIndex works`` () = let xs = [|1.f; 2.f; 3.f; 4.f|] diff --git a/tests/Python/TestResizeArray.fs b/tests/Python/TestResizeArray.fs index dd98f6bf9d..fe07fc5cb2 100644 --- a/tests/Python/TestResizeArray.fs +++ b/tests/Python/TestResizeArray.fs @@ -193,6 +193,68 @@ let ``test ResizeArray.IndexOf works`` () = li.IndexOf("ab") |> equal 1 li.IndexOf("cd") |> equal -1 +[] +let ``test ResizeArray.BinarySearch works`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(7) |> equal 3 + ra.BinarySearch(8) |> equal -5 + let desc = ComparisonIdentity.FromFunction(fun (a: int) b -> compare b a) + (ResizeArray [9; 7; 5; 3; 1]).BinarySearch(3, desc) |> equal 3 + ra.BinarySearch(1, 3, 7, System.Collections.Generic.Comparer.Default) |> equal 3 + ra.BinarySearch(1, 2, 8, System.Collections.Generic.Comparer.Default) |> equal -4 + ra.BinarySearch(1, 2, 3, null) |> equal 1 + +[] +let ``test ResizeArray.BinarySearch works with an empty list, duplicates and a null range comparer`` () = + ResizeArray().BinarySearch(1) |> equal -1 + (ResizeArray [2; 2; 2; 2]).BinarySearch(2) |> equal 1 + (ResizeArray [1; 2; 2; 2; 2; 3]).BinarySearch(2) |> equal 2 + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(0, 5, 2, null) |> equal -2 + ra.BinarySearch(2, 3, 9, null) |> equal 4 + +[] +let ``test ResizeArray.BinarySearch with a range passes the element to the comparer first`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + ra.BinarySearch(1, 3, 7, logging) |> equal 3 + List.ofSeq calls |> equal [(5, 7); (7, 7)] + +[] +let ``test ResizeArray.BinarySearch with a range checks index and count together`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(6, 0, 3, null)) |> equal "ArgumentException" + raised (fun () -> ra.BinarySearch(0, 6, 3, null)) |> equal "ArgumentException" + +[] +let ``test ResizeArray.BinarySearch with a range checks its arguments`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, 2, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(3, 3, 3, null)) |> equal "ArgumentException" + [] let ``test ResizeArray.Remove works`` () = let li = ResizeArray<_>()