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
1 change: 1 addition & 0 deletions src/Fable.Transforms/Beam/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4603,6 +4603,7 @@ let private randoms
match args with
| [ seed ] -> Helper.LibCall(com, "fable_random", "new_seeded", t, [ seed ], ?loc = r) |> Some
| _ -> Helper.LibCall(com, "fable_random", "new", t, [], ?loc = r) |> Some
| "get_Shared", None -> Helper.LibCall(com, "fable_random", "shared", t, [], ?loc = r) |> Some
| "Next", Some thisArg ->
match args with
| [] -> Helper.LibCall(com, "fable_random", "next", t, [ thisArg ], ?loc = r) |> Some
Expand Down
1 change: 1 addition & 0 deletions src/Fable.Transforms/Dart/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3285,6 +3285,7 @@ let random (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opti
?loc = r
)
|> Some
| "get_Shared", None -> Helper.LibCall(com, "Random", "shared", t, [], ?loc = r) |> Some
// Not yet supported
| ("NextInt64" | "NextSingle"), _ -> None
| meth, Some thisArg ->
Expand Down
15 changes: 8 additions & 7 deletions src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3473,17 +3473,18 @@ let random (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opti
| ".ctor", _ ->
Helper.LibCall(com, "util", "create_random", t, args, i.SignatureArgTypes, ?loc = r)
|> Some
| "get_Shared", None -> Helper.LibCall(com, "util", "random_shared", t, [], ?loc = r) |> Some
| "Next", Some thisArg ->
let min, max =
let moduleName, meth, args =
match args with
| [] -> makeIntConst 0, makeIntConst System.Int32.MaxValue
| [ max ] -> makeIntConst 0, max
| [ min; max ] -> min, max
| [] -> "util", "random_int", [ thisArg; makeIntConst 0; makeIntConst System.Int32.MaxValue ]
// These check the arguments like .NET
| [ _ ] -> "random_", "next_max", thisArg :: args
| [ _; _ ] -> "random_", "next_range", thisArg :: args
| _ -> FableError "Unexpected arg count for Random.Next" |> raise

let args = [ thisArg; min; max ]
let argTypes = [ thisArg.Type; min.Type; max.Type ]
Helper.LibCall(com, "util", "random_int", t, args, argTypes, ?loc = r) |> Some
let argTypes = args |> List.map (fun a -> a.Type)
Helper.LibCall(com, moduleName, meth, t, args, argTypes, ?loc = r) |> Some
| "NextDouble", Some thisArg ->
let args = [ thisArg ]
let argTypes = [ thisArg.Type ]
Expand Down
1 change: 1 addition & 0 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4024,6 +4024,7 @@ let random (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr opti
| args ->
Helper.LibCall(com, "Random", "seeded", t, args, i.SignatureArgTypes, ?loc = r)
|> Some
| "get_Shared", None -> Helper.LibCall(com, "Random", "shared", t, [], ?loc = r) |> Some
// Not yet supported
| ("NextInt64" | "NextSingle"), _ -> None
| meth, Some thisArg ->
Expand Down
22 changes: 18 additions & 4 deletions src/fable-library-beam/Seq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1397,16 +1397,30 @@ let randomShuffleWith (random: System.Random) (xs: 'T seq) : 'T seq =

let randomShuffle (xs: 'T seq) : 'T seq = randomShuffleWith (System.Random()) xs

let private randomChoiceSource (count: int) (xs: 'T seq) : 'T[] =
if count < 0 then
invalidArg "count" (SR.inputMustBeNonNegative + "\ncount = " + string count)

let arr = toArray xs

if count > 0 && Array.isEmpty arr then
invalidArg "source" SR.inputSequenceEmpty

arr

let randomChoiceBy (randomizer: unit -> float) (xs: 'T seq) : 'T =
xs |> toArray |> Array.randomChoiceBy randomizer
randomChoiceSource 1 xs |> Array.randomChoiceBy randomizer

let randomChoiceWith (random: System.Random) (xs: 'T seq) : 'T =
xs |> toArray |> Array.randomChoiceWith random
randomChoiceSource 1 xs |> Array.randomChoiceWith random

let randomChoice (xs: 'T seq) : 'T = xs |> toArray |> Array.randomChoice
let randomChoice (xs: 'T seq) : 'T =
randomChoiceSource 1 xs |> Array.randomChoice

// Draws on enumeration, like FSharp.Core
let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T seq) : 'T seq =
xs |> toArray |> Array.randomChoicesBy randomizer count |> ofArray
let arr = randomChoiceSource count xs
initialize count (fun _ -> Array.randomChoiceBy randomizer arr)

let randomChoicesWith (random: System.Random) (count: int) (xs: 'T seq) : 'T seq =
randomChoicesBy (fun () -> random.NextDouble()) count xs
Expand Down
90 changes: 49 additions & 41 deletions src/fable-library-beam/fable_list.erl
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ take(Count, List) ->

-spec random_shuffle_by(fun((ok) -> float()), list()) -> list().
random_shuffle_by(Randomizer, Xs) ->
shuffle_loop(Randomizer, erlang:list_to_tuple(Xs), erlang:length(Xs) - 1).
shuffle_loop(Randomizer, erlang:list_to_tuple(Xs), erlang:length(Xs), 0).

-spec random_shuffle_with(reference(), list()) -> list().
random_shuffle_with(Random, Xs) ->
Expand All @@ -710,14 +710,7 @@ random_shuffle(Xs) ->
random_choice_by(_Randomizer, []) ->
invalid_arg(<<"source">>, <<"The input sequence was empty.">>);
random_choice_by(Randomizer, Xs) ->
Len = erlang:length(Xs),
R = Randomizer(ok),
if
R < 0.0; R >= 1.0 ->
randomizer_out_of_range(R);
true ->
lists:nth(erlang:trunc(R * Len) + 1, Xs)
end.
lists:nth(random_next(Randomizer, 0, erlang:length(Xs)) + 1, Xs).

-spec random_choice_with(reference(), list()) -> term().
random_choice_with(Random, Xs) ->
Expand All @@ -731,7 +724,7 @@ random_choice(Xs) ->
random_choices_by(Randomizer, Count, Xs) ->
if
Count < 0 ->
invalid_arg(<<"count">>, <<"The input must be non-negative.">>);
negative_count(Count);
Count > 0 andalso Xs =:= [] ->
invalid_arg(<<"source">>, <<"The input sequence was empty.">>);
true ->
Expand All @@ -744,14 +737,8 @@ random_choices_by(Randomizer, Count, Xs) ->
choices_loop(_Randomizer, _Arr, _Len, 0, Acc) ->
lists:reverse(Acc);
choices_loop(Randomizer, Arr, Len, N, Acc) ->
R = Randomizer(ok),
if
R < 0.0; R >= 1.0 ->
randomizer_out_of_range(R);
true ->
E = erlang:element(erlang:trunc(R * Len) + 1, Arr),
choices_loop(Randomizer, Arr, Len, N - 1, [E | Acc])
end.
E = erlang:element(random_next(Randomizer, 0, Len) + 1, Arr),
choices_loop(Randomizer, Arr, Len, N - 1, [E | Acc]).

-spec random_choices_with(reference(), non_neg_integer(), list()) -> list().
random_choices_with(Random, Count, Xs) ->
Expand All @@ -765,7 +752,7 @@ random_choices(Count, Xs) ->
random_sample_by(Randomizer, Count, Xs) ->
if
Count < 0 ->
invalid_arg(<<"count">>, <<"The input must be non-negative.">>);
negative_count(Count);
true ->
Len = erlang:length(Xs),
if
Expand All @@ -774,7 +761,11 @@ random_sample_by(Randomizer, Count, Xs) ->
Count > Len ->
invalid_arg(<<"count">>, <<"The input sequence has an insufficient number of elements.">>);
true ->
sample_loop(Randomizer, erlang:list_to_tuple(Xs), Len, Count, 0)
Arr = erlang:list_to_tuple(Xs),
case Len =< max_set_size(Count) of
true -> sample_pool(Randomizer, Arr, Len, Count, 0, []);
false -> sample_set(Randomizer, Arr, Len, Count, #{}, [])
end
end
end.

Expand All @@ -786,38 +777,55 @@ random_sample_with(Random, Count, Xs) ->
random_sample(Count, Xs) ->
random_sample_with(fable_random:new(), Count, Xs).

%% Partial Fisher-Yates: after Count swaps the first Count positions hold the sample.
sample_loop(_Randomizer, Arr, _Len, Count, I) when I >= Count ->
lists:sublist(erlang:tuple_to_list(Arr), Count);
sample_loop(Randomizer, Arr, Len, Count, I) ->
R = Randomizer(ok),
if
R < 0.0; R >= 1.0 ->
randomizer_out_of_range(R);
%% FSharp.Core's sampling: a shrinking pool for small inputs, otherwise redraw seen indices.
max_set_size(Count) when Count > 5 ->
21 + erlang:trunc(math:pow(4, math:ceil(math:log(Count * 3) / math:log(4))));
max_set_size(_Count) ->
21.

sample_pool(_Randomizer, _Pool, _Len, Count, I, Acc) when I >= Count ->
lists:reverse(Acc);
sample_pool(Randomizer, Pool, Len, Count, I, Acc) ->
J = random_next(Randomizer, 0, Len - I),
Item = erlang:element(J + 1, Pool),
Pool2 = erlang:setelement(J + 1, Pool, erlang:element(Len - I, Pool)),
sample_pool(Randomizer, Pool2, Len, Count, I + 1, [Item | Acc]).

sample_set(_Randomizer, _Arr, _Len, 0, _Selected, Acc) ->
lists:reverse(Acc);
sample_set(Randomizer, Arr, Len, N, Selected, Acc) ->
J = random_next(Randomizer, 0, Len),
case maps:is_key(J, Selected) of
true ->
J = I + erlang:trunc(R * (Len - I)),
EI = erlang:element(I + 1, Arr),
EJ = erlang:element(J + 1, Arr),
Arr2 = erlang:setelement(I + 1, erlang:setelement(J + 1, Arr, EI), EJ),
sample_loop(Randomizer, Arr2, Len, Count, I + 1)
sample_set(Randomizer, Arr, Len, N, Selected, Acc);
false ->
Item = erlang:element(J + 1, Arr),
sample_set(Randomizer, Arr, Len, N - 1, Selected#{J => true}, [Item | Acc])
end.

%% Full Fisher-Yates shuffle: builds result from index Len-1 downto 1.
shuffle_loop(_Randomizer, Arr, I) when I =< 0 ->
%% FSharp.Core's forward Fisher-Yates shuffle.
shuffle_loop(_Randomizer, Arr, Len, I) when I >= Len - 1 ->
erlang:tuple_to_list(Arr);
shuffle_loop(Randomizer, Arr, I) ->
shuffle_loop(Randomizer, Arr, Len, I) ->
J = random_next(Randomizer, I, Len),
EI = erlang:element(I + 1, Arr),
EJ = erlang:element(J + 1, Arr),
Arr2 = erlang:setelement(I + 1, erlang:setelement(J + 1, Arr, EI), EJ),
shuffle_loop(Randomizer, Arr2, Len, I + 1).

%% Like FSharp.Core's Random.next: an index in [Min, Max) from a randomizer result in [0.0, 1.0).
random_next(Randomizer, Min, Max) ->
R = Randomizer(ok),
if
R < 0.0; R >= 1.0 ->
randomizer_out_of_range(R);
true ->
J = erlang:trunc(R * (I + 1)),
EI = erlang:element(I + 1, Arr),
EJ = erlang:element(J + 1, Arr),
Arr2 = erlang:setelement(I + 1, erlang:setelement(J + 1, Arr, EI), EJ),
shuffle_loop(Randomizer, Arr2, I - 1)
erlang:trunc(R * (Max - Min)) + Min
end.

negative_count(Count) ->
invalid_arg(<<"count">>, <<"The input must be non-negative.\ncount = ", (erlang:integer_to_binary(Count))/binary>>).

randomizer_out_of_range(R) ->
erlang:error(#{
exn_type => argument_out_of_range_exception,
Expand Down
40 changes: 32 additions & 8 deletions src/fable-library-beam/fable_random.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
-export([
new/0,
new_seeded/1,
shared/0,
next/1,
next/2,
next/3,
Expand All @@ -27,6 +28,18 @@ new_seeded(Seed) ->
put(Ref, rand:seed_s(exsss, Seed)),
Ref.

%% Random.Shared: one unseeded instance per process.
-spec shared() -> reference().
shared() ->
case get(fable_random_shared) of
undefined ->
Ref = new(),
put(fable_random_shared, Ref),
Ref;
Ref ->
Ref
end.

%% Random.Next() — returns a non-negative integer less than Int32.MaxValue.
-spec next(reference()) -> non_neg_integer().
next(Ref) ->
Expand All @@ -36,24 +49,29 @@ next(Ref) ->

%% Random.Next(maxValue) — returns a non-negative integer less than maxValue.
-spec next(reference(), integer()) -> non_neg_integer().
next(_Ref, MaxValue) when MaxValue < 0 ->
out_of_range(
<<"maxValue">>,
<<"maxValue ('", (erlang:integer_to_binary(MaxValue))/binary, "') must be a non-negative value.">>
);
next(_Ref, 0) ->
0;
next(Ref, MaxValue) when MaxValue > 0 ->
next(Ref, MaxValue) ->
{Val, NewState} = rand:uniform_s(MaxValue, get(Ref)),
put(Ref, NewState),
Val - 1;
next(_, _) ->
erlang:error(badarg).
Val - 1.

%% Random.Next(minValue, maxValue) — returns an integer in [minValue, maxValue).
-spec next(reference(), integer(), integer()) -> integer().
next(Ref, MinValue, MaxValue) when MaxValue > MinValue ->
next(_Ref, MinValue, MaxValue) when MinValue > MaxValue ->
out_of_range(<<"minValue">>, <<"'minValue' cannot be greater than maxValue.">>);
next(_Ref, MinValue, MinValue) ->
MinValue;
next(Ref, MinValue, MaxValue) ->
Range = MaxValue - MinValue,
{Val, NewState} = rand:uniform_s(Range, get(Ref)),
put(Ref, NewState),
MinValue + Val - 1;
next(_, _, _) ->
erlang:error(badarg).
MinValue + Val - 1.

%% Random.NextDouble() — returns a float in [0.0, 1.0).
-spec next_double(reference()) -> float().
Expand All @@ -76,3 +94,9 @@ fill_random_bytes(Ref, AtomicsRef, Remaining, Idx) ->
put(Ref, NewState),
atomics:put(AtomicsRef, Idx, Val - 1),
fill_random_bytes(Ref, AtomicsRef, Remaining - 1, Idx + 1).

out_of_range(ParamName, Message) ->
erlang:error(#{
exn_type => argument_out_of_range_exception,
message => fable_utils:argument_exception_message(Message, ParamName)
}).
Loading
Loading