Skip to content

Commit 664bb6c

Browse files
committed
[All] Add missing Array, List, and Seq random choice/shuffle/sample members and tests
1 parent 830bbb4 commit 664bb6c

32 files changed

Lines changed: 2412 additions & 114 deletions

File tree

src/Fable.Cli/CHANGELOG.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
* [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)`
13-
* [Dart] Implement `System.Random` runtime support using Dart's built-in `Random` and re-enable Dart tests
14-
* [Rust] Add missing `Array` members and tests: `randomShuffleInPlaceBy`, `randomShuffleInPlaceWith`, `randomShuffleInPlace`
15-
* [Rust] Add missing `System.Random` implementation and tests (by @ncave)
16-
* [Rust] Add missing `Array`, `List` and `Seq` module members and tests: `randomChoice`, `randomChoiceBy`, `randomChoiceWith`, `randomChoices`, `randomChoicesBy`, `randomChoicesWith`, `randomSample`, `randomSampleBy`, `randomSampleWith`, `randomShuffle`, `randomShuffleBy`, `randomShuffleWith` (by @ncave)
13+
* [All] Add missing `Array`, `List`, and `Seq` random choice/shuffle/sample members and tests (by @ncave)
14+
* [Dart/Rust] Add missing `System.Random` implementations and tests (by @ncave)
1715
* [Beam] Implement missing DateTimeOffset members, add DateOnly and TimeOnly support
1816

1917
### Fixed

src/Fable.Compiler/CHANGELOG.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
* [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)`
13-
* [Dart] Implement `System.Random` runtime support using Dart's built-in `Random` and re-enable Dart tests
14-
* [Rust] Add missing `Array` members and tests: `randomShuffleInPlaceBy`, `randomShuffleInPlaceWith`, `randomShuffleInPlace`
15-
* [Rust] Add missing `System.Random` implementation and tests (by @ncave)
16-
* [Rust] Add missing `Array`, `List` and `Seq` module members and tests: `randomChoice`, `randomChoiceBy`, `randomChoiceWith`, `randomChoices`, `randomChoicesBy`, `randomChoicesWith`, `randomSample`, `randomSampleBy`, `randomSampleWith`, `randomShuffle`, `randomShuffleBy`, `randomShuffleWith` (by @ncave)
13+
* [All] Add missing `Array`, `List`, and `Seq` random choice/shuffle/sample members and tests (by @ncave)
14+
* [Dart/Rust] Add missing `System.Random` implementations and tests (by @ncave)
1715
* [Beam] Implement missing DateTimeOffset members, add DateOnly and TimeOnly support
1816

1917
### Fixed

src/Fable.Transforms/Beam/Replacements.fs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,6 +1587,35 @@ let private listModule
15871587
| "OfArray", [ arr ] -> derefArr r arr |> Some
15881588
| "OfSeq", [ seq ] -> emitExpr r t [ seq ] "fable_utils:to_list($0)" |> Some
15891589
| "ToSeq", [ list ] -> Some(List.head args)
1590+
| "RandomShuffleBy", [ randomizer; list ] ->
1591+
Helper.LibCall(com, "fable_list", "random_shuffle_by", t, [ randomizer; list ])
1592+
|> Some
1593+
| "RandomShuffleWith", [ random; list ] ->
1594+
Helper.LibCall(com, "fable_list", "random_shuffle_with", t, [ random; list ])
1595+
|> Some
1596+
| "RandomShuffle", [ list ] -> Helper.LibCall(com, "fable_list", "random_shuffle", t, [ list ]) |> Some
1597+
| "RandomChoiceBy", [ randomizer; list ] ->
1598+
Helper.LibCall(com, "fable_list", "random_choice_by", t, [ randomizer; list ])
1599+
|> Some
1600+
| "RandomChoiceWith", [ random; list ] ->
1601+
Helper.LibCall(com, "fable_list", "random_choice_with", t, [ random; list ])
1602+
|> Some
1603+
| "RandomChoice", [ list ] -> Helper.LibCall(com, "fable_list", "random_choice", t, [ list ]) |> Some
1604+
| "RandomChoicesBy", [ randomizer; count; list ] ->
1605+
Helper.LibCall(com, "fable_list", "random_choices_by", t, [ randomizer; count; list ])
1606+
|> Some
1607+
| "RandomChoicesWith", [ random; count; list ] ->
1608+
Helper.LibCall(com, "fable_list", "random_choices_with", t, [ random; count; list ])
1609+
|> Some
1610+
| "RandomChoices", [ count; list ] ->
1611+
Helper.LibCall(com, "fable_list", "random_choices", t, [ count; list ]) |> Some
1612+
| "RandomSampleBy", [ randomizer; count; list ] ->
1613+
Helper.LibCall(com, "fable_list", "random_sample_by", t, [ randomizer; count; list ])
1614+
|> Some
1615+
| "RandomSampleWith", [ random; count; list ] ->
1616+
Helper.LibCall(com, "fable_list", "random_sample_with", t, [ random; count; list ])
1617+
|> Some
1618+
| "RandomSample", [ count; list ] -> Helper.LibCall(com, "fable_list", "random_sample", t, [ count; list ]) |> Some
15901619
| _ -> None
15911620

15921621
/// Beam-specific FSharpList instance method replacements.
@@ -2119,6 +2148,91 @@ let private arrayModule
21192148
let a1 = derefArr r a1
21202149
let a2 = derefArr r a2
21212150
Helper.LibCall(com, "fable_list", "compare_with", t, [ fn; a1; a2 ]) |> Some
2151+
| "RandomShuffleBy", [ randomizer; arr ] ->
2152+
let lst = derefArr r arr
2153+
2154+
Helper.LibCall(com, "fable_list", "random_shuffle_by", t, [ randomizer; lst ])
2155+
|> wrapArr com r t
2156+
|> Some
2157+
| "RandomShuffleWith", [ random; arr ] ->
2158+
let lst = derefArr r arr
2159+
2160+
Helper.LibCall(com, "fable_list", "random_shuffle_with", t, [ random; lst ])
2161+
|> wrapArr com r t
2162+
|> Some
2163+
| "RandomShuffle", [ arr ] ->
2164+
let lst = derefArr r arr
2165+
2166+
Helper.LibCall(com, "fable_list", "random_shuffle", t, [ lst ])
2167+
|> wrapArr com r t
2168+
|> Some
2169+
| "RandomShuffleInPlaceBy", [ randomizer; arr ] ->
2170+
let lst = derefArr r arr
2171+
2172+
let shuffled =
2173+
Helper.LibCall(com, "fable_list", "random_shuffle_by", Any, [ randomizer; lst ])
2174+
2175+
emitExpr r t [ arr; shuffled ] "begin erlang:put($0, $1), ok end" |> Some
2176+
| "RandomShuffleInPlaceWith", [ random; arr ] ->
2177+
let lst = derefArr r arr
2178+
2179+
let shuffled =
2180+
Helper.LibCall(com, "fable_list", "random_shuffle_with", Any, [ random; lst ])
2181+
2182+
emitExpr r t [ arr; shuffled ] "begin erlang:put($0, $1), ok end" |> Some
2183+
| "RandomShuffleInPlace", [ arr ] ->
2184+
let lst = derefArr r arr
2185+
let shuffled = Helper.LibCall(com, "fable_list", "random_shuffle", Any, [ lst ])
2186+
emitExpr r t [ arr; shuffled ] "begin erlang:put($0, $1), ok end" |> Some
2187+
| "RandomChoiceBy", [ randomizer; arr ] ->
2188+
let lst = derefArr r arr
2189+
2190+
Helper.LibCall(com, "fable_list", "random_choice_by", t, [ randomizer; lst ])
2191+
|> Some
2192+
| "RandomChoiceWith", [ random; arr ] ->
2193+
let lst = derefArr r arr
2194+
2195+
Helper.LibCall(com, "fable_list", "random_choice_with", t, [ random; lst ])
2196+
|> Some
2197+
| "RandomChoice", [ arr ] ->
2198+
let lst = derefArr r arr
2199+
Helper.LibCall(com, "fable_list", "random_choice", t, [ lst ]) |> Some
2200+
| "RandomChoicesBy", [ randomizer; count; arr ] ->
2201+
let lst = derefArr r arr
2202+
2203+
Helper.LibCall(com, "fable_list", "random_choices_by", t, [ randomizer; count; lst ])
2204+
|> wrapArr com r t
2205+
|> Some
2206+
| "RandomChoicesWith", [ random; count; arr ] ->
2207+
let lst = derefArr r arr
2208+
2209+
Helper.LibCall(com, "fable_list", "random_choices_with", t, [ random; count; lst ])
2210+
|> wrapArr com r t
2211+
|> Some
2212+
| "RandomChoices", [ count; arr ] ->
2213+
let lst = derefArr r arr
2214+
2215+
Helper.LibCall(com, "fable_list", "random_choices", t, [ count; lst ])
2216+
|> wrapArr com r t
2217+
|> Some
2218+
| "RandomSampleBy", [ randomizer; count; arr ] ->
2219+
let lst = derefArr r arr
2220+
2221+
Helper.LibCall(com, "fable_list", "random_sample_by", t, [ randomizer; count; lst ])
2222+
|> wrapArr com r t
2223+
|> Some
2224+
| "RandomSampleWith", [ random; count; arr ] ->
2225+
let lst = derefArr r arr
2226+
2227+
Helper.LibCall(com, "fable_list", "random_sample_with", t, [ random; count; lst ])
2228+
|> wrapArr com r t
2229+
|> Some
2230+
| "RandomSample", [ count; arr ] ->
2231+
let lst = derefArr r arr
2232+
2233+
Helper.LibCall(com, "fable_list", "random_sample", t, [ count; lst ])
2234+
|> wrapArr com r t
2235+
|> Some
21222236
// === Transform ops: deref input(s) AND wrap result ===
21232237
| "Map", [ fn; arr ] ->
21242238
let arr = derefArr r arr

src/Fable.Transforms/Dart/Replacements.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1816,6 +1816,7 @@ let arrayModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Ex
18161816
let t = genArg com ctx r 0 i.GenericArgs
18171817
makeArrayWithRange r t [] |> Some
18181818
| "IsEmpty", [ ar ] -> getFieldWith r t ar "isEmpty" |> Some
1819+
| "Contains", [ value; ar ] -> Helper.LibCall(com, "Array", "contains", t, [ value; ar ], ?loc = r) |> Some
18191820
| "CopyTo", args -> copyToArray com r t i args
18201821
| ("Distinct" | "DistinctBy" | "Except" | "GroupBy" | "CountBy" as meth), args ->
18211822
let meth = Naming.lowerFirst meth

src/fable-library-beam/fable_list.erl

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,19 @@
7373
remove_many_at/3,
7474
index_of_value/2,
7575
get_slice/3,
76-
take/2
76+
take/2,
77+
random_shuffle_by/2,
78+
random_shuffle_with/2,
79+
random_shuffle/1,
80+
random_choice_by/2,
81+
random_choice_with/2,
82+
random_choice/1,
83+
random_choices_by/3,
84+
random_choices_with/3,
85+
random_choices/2,
86+
random_sample_by/3,
87+
random_sample_with/3,
88+
random_sample/2
7789
]).
7890

7991
-spec fold(fun(), term(), list() | reference() | map()) -> term().
@@ -599,3 +611,134 @@ take(Count, List) ->
599611
true -> lists:sublist(List, Count);
600612
false -> erlang:error(<<"The input sequence has an insufficient number of elements.">>)
601613
end.
614+
615+
%% ---- Random functions --------------------------------------------------------
616+
%% These implement Array/List random* operations directly on plain Erlang lists,
617+
%% avoiding any delegation through seq.erl (which would cause circular recursion).
618+
%%
619+
%% Calling convention: Randomizer is fun(ok) -> float() (F# unit -> float curried).
620+
%% Random (System.Random) is the atom ok; all randomness goes through fable_random.
621+
622+
-spec random_shuffle_by(fun((ok) -> float()), list()) -> list().
623+
random_shuffle_by(Randomizer, Xs) ->
624+
shuffle_loop(Randomizer, erlang:list_to_tuple(Xs), erlang:length(Xs) - 1).
625+
626+
-spec random_shuffle_with(ok, list()) -> list().
627+
random_shuffle_with(_Random, Xs) ->
628+
random_shuffle_by(fun(_) -> fable_random:next_double() end, Xs).
629+
630+
-spec random_shuffle(list()) -> list().
631+
random_shuffle(Xs) ->
632+
random_shuffle_with(ok, Xs).
633+
634+
-spec random_choice_by(fun((ok) -> float()), list()) -> term().
635+
random_choice_by(_Randomizer, []) ->
636+
erlang:error(<<"The input sequence was empty.">>);
637+
random_choice_by(Randomizer, Xs) ->
638+
Len = erlang:length(Xs),
639+
R = Randomizer(ok),
640+
if
641+
R < 0.0; R >= 1.0 ->
642+
erlang:error(<<"The randomizer function should return a float in [0, 1).">>);
643+
true ->
644+
lists:nth(erlang:trunc(R * Len) + 1, Xs)
645+
end.
646+
647+
-spec random_choice_with(ok, list()) -> term().
648+
random_choice_with(_Random, Xs) ->
649+
random_choice_by(fun(_) -> fable_random:next_double() end, Xs).
650+
651+
-spec random_choice(list()) -> term().
652+
random_choice(Xs) ->
653+
random_choice_with(ok, Xs).
654+
655+
-spec random_choices_by(fun((ok) -> float()), non_neg_integer(), list()) -> list().
656+
random_choices_by(Randomizer, Count, Xs) ->
657+
if
658+
Count < 0 ->
659+
erlang:error(<<"The input must be non-negative.">>);
660+
true ->
661+
case {Count > 0, Xs} of
662+
{true, []} ->
663+
erlang:error(<<"The input sequence was empty.">>);
664+
_ ->
665+
Len = erlang:length(Xs),
666+
lists:map(
667+
fun(_) ->
668+
R = Randomizer(ok),
669+
if
670+
R < 0.0; R >= 1.0 ->
671+
erlang:error(<<"The randomizer function should return a float in [0, 1).">>);
672+
true ->
673+
lists:nth(erlang:trunc(R * Len) + 1, Xs)
674+
end
675+
end,
676+
lists:seq(1, Count)
677+
)
678+
end
679+
end.
680+
681+
-spec random_choices_with(ok, non_neg_integer(), list()) -> list().
682+
random_choices_with(_Random, Count, Xs) ->
683+
random_choices_by(fun(_) -> fable_random:next_double() end, Count, Xs).
684+
685+
-spec random_choices(non_neg_integer(), list()) -> list().
686+
random_choices(Count, Xs) ->
687+
random_choices_with(ok, Count, Xs).
688+
689+
-spec random_sample_by(fun((ok) -> float()), non_neg_integer(), list()) -> list().
690+
random_sample_by(Randomizer, Count, Xs) ->
691+
if
692+
Count < 0 ->
693+
erlang:error(<<"The input must be non-negative.">>);
694+
true ->
695+
Len = erlang:length(Xs),
696+
if
697+
Len =:= 0, Count > 0 ->
698+
erlang:error(<<"The input sequence was empty.">>);
699+
Count > Len ->
700+
erlang:error(<<"The input sequence has an insufficient number of elements.">>);
701+
true ->
702+
sample_loop(Randomizer, erlang:list_to_tuple(Xs), Len, Count, 0)
703+
end
704+
end.
705+
706+
-spec random_sample_with(ok, non_neg_integer(), list()) -> list().
707+
random_sample_with(_Random, Count, Xs) ->
708+
random_sample_by(fun(_) -> fable_random:next_double() end, Count, Xs).
709+
710+
-spec random_sample(non_neg_integer(), list()) -> list().
711+
random_sample(Count, Xs) ->
712+
random_sample_with(ok, Count, Xs).
713+
714+
%% Partial Fisher-Yates: after Count swaps the first Count positions hold the sample.
715+
sample_loop(_Randomizer, Arr, _Len, Count, I) when I >= Count ->
716+
lists:sublist(erlang:tuple_to_list(Arr), Count);
717+
sample_loop(Randomizer, Arr, Len, Count, I) ->
718+
R = Randomizer(ok),
719+
if
720+
R < 0.0; R >= 1.0 ->
721+
erlang:error(<<"The randomizer function should return a float in [0, 1).">>);
722+
true ->
723+
J = I + erlang:trunc(R * (Len - I)),
724+
EI = erlang:element(I + 1, Arr),
725+
EJ = erlang:element(J + 1, Arr),
726+
Arr2 = erlang:setelement(I + 1, erlang:setelement(J + 1, Arr, EI), EJ),
727+
sample_loop(Randomizer, Arr2, Len, Count, I + 1)
728+
end.
729+
730+
%% Full Fisher-Yates shuffle: builds result from index Len-1 downto 1.
731+
shuffle_loop(_Randomizer, Arr, I) when I =< 0 ->
732+
erlang:tuple_to_list(Arr);
733+
shuffle_loop(Randomizer, Arr, I) ->
734+
R = Randomizer(ok),
735+
if
736+
R < 0.0; R >= 1.0 ->
737+
erlang:error(<<"The randomizer function should return a float in [0, 1).">>);
738+
true ->
739+
J = erlang:trunc(R * (I + 1)),
740+
EI = erlang:element(I + 1, Arr),
741+
EJ = erlang:element(J + 1, Arr),
742+
Arr2 = erlang:setelement(I + 1, erlang:setelement(J + 1, Arr, EI), EJ),
743+
shuffle_loop(Randomizer, Arr2, I - 1)
744+
end.

0 commit comments

Comments
 (0)