Skip to content
Merged
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
26 changes: 26 additions & 0 deletions src/Fable.Transforms/Beam/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)"
Expand Down
21 changes: 21 additions & 0 deletions src/Fable.Transforms/Dart/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ] ->
Expand Down Expand Up @@ -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

Expand Down
22 changes: 22 additions & 0 deletions src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 =
Expand Down
24 changes: 24 additions & 0 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down
54 changes: 54 additions & 0 deletions src/fable-library-beam/fable_resize_array.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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().
Expand Down Expand Up @@ -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<T>.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;
Expand Down
44 changes: 44 additions & 0 deletions src/fable-library-dart/Array.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
54 changes: 52 additions & 2 deletions src/fable-library-py/fable_library/resize_array.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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",
Expand Down
Loading
Loading