diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md
index 5f655e0d59..df6bfd45ee 100644
--- a/src/Fable.Cli/CHANGELOG.md
+++ b/src/Fable.Cli/CHANGELOG.md
@@ -9,14 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
-* [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)`
-* [Rust] Add missing `System.Random` implementation and tests (by @ncave)
-* [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)
-* [Beam] Implement missing DateTimeOffset members, add DateOnly and TimeOnly support
+* [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)` (by @OnurGumus)
+* [All] Add missing `Array`, `List`, and `Seq` random choice/shuffle/sample members and tests (by @ncave)
+* [Dart/Rust] Add missing `System.Random` implementations and tests (by @ncave)
+* [Beam] Implement missing DateTimeOffset members, add DateOnly and TimeOnly support (by @dbrattli)
### Fixed
* [All] Fix unnecessary object allocations during AST traversal when visiting `Import` expressions (by Repo Assist)
+* [Beam] Fix `System.Random.Next(0)` implementation (by @ncave)
+* [Python] Fix `System.Random` seeded implementation (by @ncave)
+* [Beam] Fix `System.Random` seeded implementation to use per-instance state (by @dbrattli)
* [Dart] Fix `Array.compareWith` comparing lengths before elements, producing wrong results for arrays with common prefixes (fixes #2961)
* [Python] Fix unsafe option unwrapping in `DateTimeOffset.get_Offset` and regex replacements (by @dbrattli)
* [All] Replace unsafe option `.Value` unwrapping with safe alternatives in Python/Replacements.fs and Rust/Fable2Rust.fs (code scanning alerts IONIDE-006)
diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md
index db8b462e7e..f7e809b562 100644
--- a/src/Fable.Compiler/CHANGELOG.md
+++ b/src/Fable.Compiler/CHANGELOG.md
@@ -9,14 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
-* [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)`
-* [Rust] Add missing `System.Random` implementation and tests (by @ncave)
-* [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)
-* [Beam] Implement missing DateTimeOffset members, add DateOnly and TimeOnly support
+* [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)` (by @OnurGumus)
+* [All] Add missing `Array`, `List`, and `Seq` random choice/shuffle/sample members and tests (by @ncave)
+* [Dart/Rust] Add missing `System.Random` implementations and tests (by @ncave)
+* [Beam] Implement missing DateTimeOffset members, add DateOnly and TimeOnly support (by @dbrattli)
### Fixed
* [All] Fix unnecessary object allocations during AST traversal when visiting `Import` expressions (by Repo Assist)
+* [Beam] Fix `System.Random.Next(0)` implementation (by @ncave)
+* [Python] Fix `System.Random` seeded implementation (by @ncave)
+* [Beam] Fix `System.Random` seeded implementation to use per-instance state (by @dbrattli)
* [Dart] Fix `Array.compareWith` comparing lengths before elements, producing wrong results for arrays with common prefixes (fixes #2961)
* [Python] Fix unsafe option unwrapping in `DateTimeOffset.get_Offset` and regex replacements (by @dbrattli)
* [All] Replace unsafe option `.Value` unwrapping with safe alternatives in Python/Replacements.fs and Rust/Fable2Rust.fs (code scanning alerts IONIDE-006)
diff --git a/src/Fable.Transforms/Beam/Replacements.fs b/src/Fable.Transforms/Beam/Replacements.fs
index 2da2630de4..93322138f0 100644
--- a/src/Fable.Transforms/Beam/Replacements.fs
+++ b/src/Fable.Transforms/Beam/Replacements.fs
@@ -1587,6 +1587,35 @@ let private listModule
| "OfArray", [ arr ] -> derefArr r arr |> Some
| "OfSeq", [ seq ] -> emitExpr r t [ seq ] "fable_utils:to_list($0)" |> Some
| "ToSeq", [ list ] -> Some(List.head args)
+ | "RandomShuffleBy", [ randomizer; list ] ->
+ Helper.LibCall(com, "fable_list", "random_shuffle_by", t, [ randomizer; list ])
+ |> Some
+ | "RandomShuffleWith", [ random; list ] ->
+ Helper.LibCall(com, "fable_list", "random_shuffle_with", t, [ random; list ])
+ |> Some
+ | "RandomShuffle", [ list ] -> Helper.LibCall(com, "fable_list", "random_shuffle", t, [ list ]) |> Some
+ | "RandomChoiceBy", [ randomizer; list ] ->
+ Helper.LibCall(com, "fable_list", "random_choice_by", t, [ randomizer; list ])
+ |> Some
+ | "RandomChoiceWith", [ random; list ] ->
+ Helper.LibCall(com, "fable_list", "random_choice_with", t, [ random; list ])
+ |> Some
+ | "RandomChoice", [ list ] -> Helper.LibCall(com, "fable_list", "random_choice", t, [ list ]) |> Some
+ | "RandomChoicesBy", [ randomizer; count; list ] ->
+ Helper.LibCall(com, "fable_list", "random_choices_by", t, [ randomizer; count; list ])
+ |> Some
+ | "RandomChoicesWith", [ random; count; list ] ->
+ Helper.LibCall(com, "fable_list", "random_choices_with", t, [ random; count; list ])
+ |> Some
+ | "RandomChoices", [ count; list ] ->
+ Helper.LibCall(com, "fable_list", "random_choices", t, [ count; list ]) |> Some
+ | "RandomSampleBy", [ randomizer; count; list ] ->
+ Helper.LibCall(com, "fable_list", "random_sample_by", t, [ randomizer; count; list ])
+ |> Some
+ | "RandomSampleWith", [ random; count; list ] ->
+ Helper.LibCall(com, "fable_list", "random_sample_with", t, [ random; count; list ])
+ |> Some
+ | "RandomSample", [ count; list ] -> Helper.LibCall(com, "fable_list", "random_sample", t, [ count; list ]) |> Some
| _ -> None
/// Beam-specific FSharpList instance method replacements.
@@ -2119,6 +2148,91 @@ let private arrayModule
let a1 = derefArr r a1
let a2 = derefArr r a2
Helper.LibCall(com, "fable_list", "compare_with", t, [ fn; a1; a2 ]) |> Some
+ | "RandomShuffleBy", [ randomizer; arr ] ->
+ let lst = derefArr r arr
+
+ Helper.LibCall(com, "fable_list", "random_shuffle_by", t, [ randomizer; lst ])
+ |> wrapArr com r t
+ |> Some
+ | "RandomShuffleWith", [ random; arr ] ->
+ let lst = derefArr r arr
+
+ Helper.LibCall(com, "fable_list", "random_shuffle_with", t, [ random; lst ])
+ |> wrapArr com r t
+ |> Some
+ | "RandomShuffle", [ arr ] ->
+ let lst = derefArr r arr
+
+ Helper.LibCall(com, "fable_list", "random_shuffle", t, [ lst ])
+ |> wrapArr com r t
+ |> Some
+ | "RandomShuffleInPlaceBy", [ randomizer; arr ] ->
+ let lst = derefArr r arr
+
+ let shuffled =
+ Helper.LibCall(com, "fable_list", "random_shuffle_by", Any, [ randomizer; lst ])
+
+ emitExpr r t [ arr; shuffled ] "begin erlang:put($0, $1), ok end" |> Some
+ | "RandomShuffleInPlaceWith", [ random; arr ] ->
+ let lst = derefArr r arr
+
+ let shuffled =
+ Helper.LibCall(com, "fable_list", "random_shuffle_with", Any, [ random; lst ])
+
+ emitExpr r t [ arr; shuffled ] "begin erlang:put($0, $1), ok end" |> Some
+ | "RandomShuffleInPlace", [ arr ] ->
+ let lst = derefArr r arr
+ let shuffled = Helper.LibCall(com, "fable_list", "random_shuffle", Any, [ lst ])
+ emitExpr r t [ arr; shuffled ] "begin erlang:put($0, $1), ok end" |> Some
+ | "RandomChoiceBy", [ randomizer; arr ] ->
+ let lst = derefArr r arr
+
+ Helper.LibCall(com, "fable_list", "random_choice_by", t, [ randomizer; lst ])
+ |> Some
+ | "RandomChoiceWith", [ random; arr ] ->
+ let lst = derefArr r arr
+
+ Helper.LibCall(com, "fable_list", "random_choice_with", t, [ random; lst ])
+ |> Some
+ | "RandomChoice", [ arr ] ->
+ let lst = derefArr r arr
+ Helper.LibCall(com, "fable_list", "random_choice", t, [ lst ]) |> Some
+ | "RandomChoicesBy", [ randomizer; count; arr ] ->
+ let lst = derefArr r arr
+
+ Helper.LibCall(com, "fable_list", "random_choices_by", t, [ randomizer; count; lst ])
+ |> wrapArr com r t
+ |> Some
+ | "RandomChoicesWith", [ random; count; arr ] ->
+ let lst = derefArr r arr
+
+ Helper.LibCall(com, "fable_list", "random_choices_with", t, [ random; count; lst ])
+ |> wrapArr com r t
+ |> Some
+ | "RandomChoices", [ count; arr ] ->
+ let lst = derefArr r arr
+
+ Helper.LibCall(com, "fable_list", "random_choices", t, [ count; lst ])
+ |> wrapArr com r t
+ |> Some
+ | "RandomSampleBy", [ randomizer; count; arr ] ->
+ let lst = derefArr r arr
+
+ Helper.LibCall(com, "fable_list", "random_sample_by", t, [ randomizer; count; lst ])
+ |> wrapArr com r t
+ |> Some
+ | "RandomSampleWith", [ random; count; arr ] ->
+ let lst = derefArr r arr
+
+ Helper.LibCall(com, "fable_list", "random_sample_with", t, [ random; count; lst ])
+ |> wrapArr com r t
+ |> Some
+ | "RandomSample", [ count; arr ] ->
+ let lst = derefArr r arr
+
+ Helper.LibCall(com, "fable_list", "random_sample", t, [ count; lst ])
+ |> wrapArr com r t
+ |> Some
// === Transform ops: deref input(s) AND wrap result ===
| "Map", [ fn; arr ] ->
let arr = derefArr r arr
@@ -3936,18 +4050,24 @@ 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
- | "Next", Some _ ->
+ | "Next", Some thisArg ->
match args with
- | [] -> Helper.LibCall(com, "fable_random", "next", t, [], ?loc = r) |> Some
- | [ maxVal ] -> Helper.LibCall(com, "fable_random", "next", t, [ maxVal ], ?loc = r) |> Some
+ | [] -> Helper.LibCall(com, "fable_random", "next", t, [ thisArg ], ?loc = r) |> Some
+ | [ maxVal ] ->
+ Helper.LibCall(com, "fable_random", "next", t, [ thisArg; maxVal ], ?loc = r)
+ |> Some
| [ minVal; maxVal ] ->
- Helper.LibCall(com, "fable_random", "next", t, [ minVal; maxVal ], ?loc = r)
+ Helper.LibCall(com, "fable_random", "next", t, [ thisArg; minVal; maxVal ], ?loc = r)
|> Some
| _ -> None
- | "NextDouble", Some _ -> Helper.LibCall(com, "fable_random", "next_double", t, [], ?loc = r) |> Some
- | "NextBytes", Some _ ->
+ | "NextDouble", Some thisArg ->
+ Helper.LibCall(com, "fable_random", "next_double", t, [ thisArg ], ?loc = r)
+ |> Some
+ | "NextBytes", Some thisArg ->
match args with
- | [ arr ] -> Helper.LibCall(com, "fable_random", "next_bytes", t, [ arr ], ?loc = r) |> Some
+ | [ arr ] ->
+ Helper.LibCall(com, "fable_random", "next_bytes", t, [ thisArg; arr ], ?loc = r)
+ |> Some
| _ -> None
| _ -> None
diff --git a/src/Fable.Transforms/Dart/Fable2Dart.fs b/src/Fable.Transforms/Dart/Fable2Dart.fs
index 3324c8120f..4b0dd7ac32 100644
--- a/src/Fable.Transforms/Dart/Fable2Dart.fs
+++ b/src/Fable.Transforms/Dart/Fable2Dart.fs
@@ -226,6 +226,7 @@ module Util =
| "Fable.Core.Dart.DartNullable`1", [ genArg ] -> Nullable genArg
| Types.regexGroup, _ -> Nullable String
| Types.regexMatch, _ -> makeTypeRefFromName "Match" []
+ | "System.Random", _ -> libTypeRef com ctx "Random" "Random" []
// We use `dynamic` for Exception because there is no single type that catches all errors in Dart
// (except when inherited as base class, then it's getExceptionType, see transformInheritedClass)
| Types.exception_, _ -> Dynamic // getExceptionType com ctx
diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs
index c19628489c..3bbb410eb6 100644
--- a/src/Fable.Transforms/Dart/Replacements.fs
+++ b/src/Fable.Transforms/Dart/Replacements.fs
@@ -1816,6 +1816,7 @@ let arrayModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Ex
let t = genArg com ctx r 0 i.GenericArgs
makeArrayWithRange r t [] |> Some
| "IsEmpty", [ ar ] -> getFieldWith r t ar "isEmpty" |> Some
+ | "Contains", [ value; ar ] -> Helper.LibCall(com, "Array", "contains", t, [ value; ar ], ?loc = r) |> Some
| "CopyTo", args -> copyToArray com r t i args
| ("Distinct" | "DistinctBy" | "Except" | "GroupBy" | "CountBy" as meth), args ->
let meth = Naming.lowerFirst meth
@@ -3076,11 +3077,18 @@ let globalization
let random (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
match i.CompiledName, thisArg with
| ".ctor", _ ->
- match args with
- | [] -> Helper.LibCall(com, "Random", "nonSeeded", t, [], [], ?loc = r) |> Some
- | args ->
- Helper.LibCall(com, "Random", "seeded", t, args, i.SignatureArgTypes, genArgs = i.GenericArgs, ?loc = r)
- |> Some
+ Helper.LibCall(
+ com,
+ "Random",
+ "Random",
+ t,
+ args,
+ i.SignatureArgTypes,
+ genArgs = i.GenericArgs,
+ isConstructor = true,
+ ?loc = r
+ )
+ |> Some
// Not yet supported
| ("NextInt64" | "NextSingle"), _ -> None
| meth, Some thisArg ->
diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs
index f61ac8be32..f440597c6f 100644
--- a/src/Fable.Transforms/Python/Replacements.fs
+++ b/src/Fable.Transforms/Python/Replacements.fs
@@ -3254,10 +3254,12 @@ let globalization
ObjectExpr([], t, None) |> Some
| _ -> None
-let random (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) =
- match i.CompiledName with
- | ".ctor" -> ObjectExpr([], t, None) |> Some
- | "Next" ->
+let random (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
+ match i.CompiledName, thisArg with
+ | ".ctor", _ ->
+ Helper.LibCall(com, "util", "create_random", t, args, i.SignatureArgTypes, ?loc = r)
+ |> Some
+ | "Next", Some thisArg ->
let min, max =
match args with
| [] -> makeIntConst 0, makeIntConst System.Int32.MaxValue
@@ -3265,19 +3267,19 @@ let random (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (a
| [ min; max ] -> min, max
| _ -> FableError "Unexpected arg count for Random.Next" |> raise
- Helper.LibCall(com, "util", "randint", t, [ min; max ], [ min.Type; max.Type ], ?loc = r)
- |> Some
- | "NextDouble" ->
- let ranExpr = Helper.ImportedCall("random", "random", t, [], [])
- Helper.LibCall(com, "core", "float64", t, [ ranExpr ], ?loc = r) |> Some
- | "NextBytes" ->
- let byteArray =
- match args with
- | [ b ] -> b
- | _ -> FableError "Unexpected arg count for Random.NextBytes" |> 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
+ | "NextDouble", Some thisArg ->
+ let args = [ thisArg ]
+ let argTypes = [ thisArg.Type ]
- Helper.LibCall(com, "util", "random_bytes", t, [ byteArray ], [ byteArray.Type ], ?loc = r)
+ Helper.LibCall(com, "util", "random_double", t, args, argTypes, ?loc = r)
|> Some
+ | "NextBytes", Some thisArg ->
+ let args = thisArg :: args
+ let argTypes = thisArg.Type :: i.SignatureArgTypes
+ Helper.LibCall(com, "util", "random_bytes", t, args, argTypes, ?loc = r) |> Some
| _ -> None
let cancels (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
diff --git a/src/fable-library-beam/fable_list.erl b/src/fable-library-beam/fable_list.erl
index f13941f7aa..948865dd99 100644
--- a/src/fable-library-beam/fable_list.erl
+++ b/src/fable-library-beam/fable_list.erl
@@ -73,7 +73,19 @@
remove_many_at/3,
index_of_value/2,
get_slice/3,
- take/2
+ take/2,
+ random_shuffle_by/2,
+ random_shuffle_with/2,
+ random_shuffle/1,
+ random_choice_by/2,
+ random_choice_with/2,
+ random_choice/1,
+ random_choices_by/3,
+ random_choices_with/3,
+ random_choices/2,
+ random_sample_by/3,
+ random_sample_with/3,
+ random_sample/2
]).
-spec fold(fun(), term(), list() | reference() | map()) -> term().
@@ -599,3 +611,134 @@ take(Count, List) ->
true -> lists:sublist(List, Count);
false -> erlang:error(<<"The input sequence has an insufficient number of elements.">>)
end.
+
+%% ---- Random functions --------------------------------------------------------
+%% These implement Array/List random* operations directly on plain Erlang lists,
+%% avoiding any delegation through seq.erl (which would cause circular recursion).
+%%
+%% Calling convention: Randomizer is fun(ok) -> float() (F# unit -> float curried).
+%% Random (System.Random) is a reference() holding per-instance PRNG state.
+
+-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).
+
+-spec random_shuffle_with(reference(), list()) -> list().
+random_shuffle_with(Random, Xs) ->
+ random_shuffle_by(fun(_) -> fable_random:next_double(Random) end, Xs).
+
+-spec random_shuffle(list()) -> list().
+random_shuffle(Xs) ->
+ random_shuffle_with(fable_random:new(), Xs).
+
+-spec random_choice_by(fun((ok) -> float()), list()) -> term().
+random_choice_by(_Randomizer, []) ->
+ erlang:error(<<"The input sequence was empty.">>);
+random_choice_by(Randomizer, Xs) ->
+ Len = erlang:length(Xs),
+ R = Randomizer(ok),
+ if
+ R < 0.0; R >= 1.0 ->
+ erlang:error(iolist_to_binary(io_lib:format("The index is outside the legal range.~nrandomizer returned ~w, should be in range [0.0, 1.0).", [R])));
+ true ->
+ lists:nth(erlang:trunc(R * Len) + 1, Xs)
+ end.
+
+-spec random_choice_with(reference(), list()) -> term().
+random_choice_with(Random, Xs) ->
+ random_choice_by(fun(_) -> fable_random:next_double(Random) end, Xs).
+
+-spec random_choice(list()) -> term().
+random_choice(Xs) ->
+ random_choice_with(fable_random:new(), Xs).
+
+-spec random_choices_by(fun((ok) -> float()), non_neg_integer(), list()) -> list().
+random_choices_by(Randomizer, Count, Xs) ->
+ if
+ Count < 0 ->
+ erlang:error(<<"The input must be non-negative.">>);
+ Count > 0 andalso Xs =:= [] ->
+ erlang:error(<<"The input sequence was empty.">>);
+ true ->
+ Arr = erlang:list_to_tuple(Xs),
+ Len = erlang:tuple_size(Arr),
+ choices_loop(Randomizer, Arr, Len, Count, [])
+ end.
+
+%% Tail-recursive helper for random_choices_by: builds result in reverse then flips.
+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 ->
+ erlang:error(iolist_to_binary(io_lib:format("The index is outside the legal range.~nrandomizer returned ~w, should be in range [0.0, 1.0).", [R])));
+ true ->
+ E = erlang:element(erlang:trunc(R * Len) + 1, Arr),
+ choices_loop(Randomizer, Arr, Len, N - 1, [E | Acc])
+ end.
+
+-spec random_choices_with(reference(), non_neg_integer(), list()) -> list().
+random_choices_with(Random, Count, Xs) ->
+ random_choices_by(fun(_) -> fable_random:next_double(Random) end, Count, Xs).
+
+-spec random_choices(non_neg_integer(), list()) -> list().
+random_choices(Count, Xs) ->
+ random_choices_with(fable_random:new(), Count, Xs).
+
+-spec random_sample_by(fun((ok) -> float()), non_neg_integer(), list()) -> list().
+random_sample_by(Randomizer, Count, Xs) ->
+ if
+ Count < 0 ->
+ erlang:error(<<"The input must be non-negative.">>);
+ true ->
+ Len = erlang:length(Xs),
+ if
+ Len =:= 0 andalso Count > 0 ->
+ erlang:error(<<"The input sequence was empty.">>);
+ Count > Len ->
+ erlang:error(<<"The input sequence has an insufficient number of elements.">>);
+ true ->
+ sample_loop(Randomizer, erlang:list_to_tuple(Xs), Len, Count, 0)
+ end
+ end.
+
+-spec random_sample_with(reference(), non_neg_integer(), list()) -> list().
+random_sample_with(Random, Count, Xs) ->
+ random_sample_by(fun(_) -> fable_random:next_double(Random) end, Count, Xs).
+
+-spec random_sample(non_neg_integer(), list()) -> list().
+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 ->
+ erlang:error(iolist_to_binary(io_lib:format("The index is outside the legal range.~nrandomizer returned ~w, should be in range [0.0, 1.0).", [R])));
+ 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)
+ end.
+
+%% Full Fisher-Yates shuffle: builds result from index Len-1 downto 1.
+shuffle_loop(_Randomizer, Arr, I) when I =< 0 ->
+ erlang:tuple_to_list(Arr);
+shuffle_loop(Randomizer, Arr, I) ->
+ R = Randomizer(ok),
+ if
+ R < 0.0; R >= 1.0 ->
+ erlang:error(iolist_to_binary(io_lib:format("The index is outside the legal range.~nrandomizer returned ~w, should be in range [0.0, 1.0).", [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)
+ end.
diff --git a/src/fable-library-beam/fable_random.erl b/src/fable-library-beam/fable_random.erl
index c73b2a21fd..dd6e1be1e0 100644
--- a/src/fable-library-beam/fable_random.erl
+++ b/src/fable-library-beam/fable_random.erl
@@ -3,60 +3,76 @@
-export([
new/0,
new_seeded/1,
- next/0,
next/1,
next/2,
- next_double/0,
- next_bytes/1
+ next/3,
+ next_double/1,
+ next_bytes/2
]).
--spec new() -> ok.
--spec new_seeded(integer()) -> ok.
--spec next() -> non_neg_integer().
--spec next(pos_integer()) -> non_neg_integer().
--spec next(integer(), integer()) -> integer().
--spec next_double() -> float().
--spec next_bytes(tuple() | reference()) -> ok.
+%% All functions take a Ref (process dictionary key) as the first argument,
+%% so each Random instance has its own independent PRNG state.
-%% System.Random() — no-op, uses default process-level PRNG state.
+%% System.Random() — create a new instance with a random seed.
+-spec new() -> reference().
new() ->
- ok.
+ Ref = make_ref(),
+ put(Ref, rand:seed_s(exsss)),
+ Ref.
-%% System.Random(seed) — seed the process-level PRNG for deterministic output.
+%% System.Random(seed) — create a new instance with a deterministic seed.
+-spec new_seeded(integer()) -> reference().
new_seeded(Seed) ->
- rand:seed(exsss, Seed),
- ok.
+ Ref = make_ref(),
+ put(Ref, rand:seed_s(exsss, Seed)),
+ Ref.
%% Random.Next() — returns a non-negative integer less than Int32.MaxValue.
-next() ->
- rand:uniform(2147483647) - 1.
+-spec next(reference()) -> non_neg_integer().
+next(Ref) ->
+ {Val, NewState} = rand:uniform_s(2147483647, get(Ref)),
+ put(Ref, NewState),
+ Val - 1.
%% Random.Next(maxValue) — returns a non-negative integer less than maxValue.
-next(MaxValue) when MaxValue > 0 ->
- rand:uniform(MaxValue) - 1;
-next(_) ->
+-spec next(reference(), integer()) -> non_neg_integer().
+next(_Ref, 0) ->
+ 0;
+next(Ref, MaxValue) when MaxValue > 0 ->
+ {Val, NewState} = rand:uniform_s(MaxValue, get(Ref)),
+ put(Ref, NewState),
+ Val - 1;
+next(_, _) ->
erlang:error(badarg).
%% Random.Next(minValue, maxValue) — returns an integer in [minValue, maxValue).
-next(MinValue, MaxValue) when MaxValue > MinValue ->
- MinValue + rand:uniform(MaxValue - MinValue) - 1;
-next(_, _) ->
+-spec next(reference(), integer(), integer()) -> integer().
+next(Ref, MinValue, MaxValue) when MaxValue > MinValue ->
+ Range = MaxValue - MinValue,
+ {Val, NewState} = rand:uniform_s(Range, get(Ref)),
+ put(Ref, NewState),
+ MinValue + Val - 1;
+next(_, _, _) ->
erlang:error(badarg).
%% Random.NextDouble() — returns a float in [0.0, 1.0).
-next_double() ->
- rand:uniform().
+-spec next_double(reference()) -> float().
+next_double(Ref) ->
+ {Val, NewState} = rand:uniform_s(get(Ref)),
+ put(Ref, NewState),
+ Val.
%% Random.NextBytes(byte[]) — fills a byte array with random bytes.
-%% Uses the seeded rand state so results are deterministic after seeding.
-%% Accepts both direct byte_array tuples and process dictionary references.
-next_bytes({byte_array, Size, AtomicsRef}) ->
- fill_random_bytes(AtomicsRef, Size, 1);
-next_bytes(PdRef) when is_reference(PdRef) ->
- next_bytes(get(PdRef)).
-
-fill_random_bytes(_AtomicsRef, 0, _Idx) ->
+-spec next_bytes(reference(), tuple() | reference()) -> ok.
+next_bytes(Ref, {byte_array, Size, AtomicsRef}) ->
+ fill_random_bytes(Ref, AtomicsRef, Size, 1);
+next_bytes(Ref, PdRef) when is_reference(PdRef) ->
+ next_bytes(Ref, get(PdRef)).
+
+fill_random_bytes(_Ref, _AtomicsRef, 0, _Idx) ->
ok;
-fill_random_bytes(AtomicsRef, Remaining, Idx) ->
- atomics:put(AtomicsRef, Idx, rand:uniform(256) - 1),
- fill_random_bytes(AtomicsRef, Remaining - 1, Idx + 1).
+fill_random_bytes(Ref, AtomicsRef, Remaining, Idx) ->
+ {Val, NewState} = rand:uniform_s(256, get(Ref)),
+ put(Ref, NewState),
+ atomics:put(AtomicsRef, Idx, Val - 1),
+ fill_random_bytes(Ref, AtomicsRef, Remaining - 1, Idx + 1).
diff --git a/src/fable-library-dart/Array.fs b/src/fable-library-dart/Array.fs
index 55425c3d54..447fd3cdb6 100644
--- a/src/fable-library-dart/Array.fs
+++ b/src/fable-library-dart/Array.fs
@@ -1130,3 +1130,108 @@ let updateAt (index: int) (y: 'T) (xs: 'T[]) : 'T[] =
else
xs[i]
)
+
+let randomShuffleInPlaceBy (randomizer: unit -> float) (xs: 'T[]) : unit =
+ let len = xs.Length
+
+ for i = len - 1 downto 1 do
+ let r = randomizer ()
+
+ if r < 0.0 || r >= 1.0 then
+ invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
+
+ let j = int (r * float (i + 1))
+ let tmp = xs[i]
+ xs[i] <- xs[j]
+ xs[j] <- tmp
+
+let randomShuffleInPlaceWith (random: System.Random) (xs: 'T[]) : unit =
+ randomShuffleInPlaceBy (fun () -> random.NextDouble()) xs
+
+let randomShuffleInPlace (xs: 'T[]) : unit =
+ randomShuffleInPlaceWith (System.Random()) xs
+
+let randomShuffleBy (randomizer: unit -> float) (xs: 'T[]) : 'T[] =
+ let arr = copy xs
+ randomShuffleInPlaceBy randomizer arr
+ arr
+
+let randomShuffleWith (random: System.Random) (xs: 'T[]) : 'T[] =
+ randomShuffleBy (fun () -> random.NextDouble()) xs
+
+let randomShuffle (xs: 'T[]) : 'T[] = randomShuffleWith (System.Random()) xs
+
+let randomChoiceBy (randomizer: unit -> float) (xs: 'T[]) : 'T =
+ if xs.Length = 0 then
+ invalidArg "source" SR.inputSequenceEmpty
+
+ let len = xs.Length
+ let r = randomizer ()
+
+ if r < 0.0 || r >= 1.0 then
+ invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
+
+ xs[int (r * float len)]
+
+let randomChoiceWith (random: System.Random) (xs: 'T[]) : 'T =
+ randomChoiceBy (fun () -> random.NextDouble()) xs
+
+let randomChoice (xs: 'T[]) : 'T = randomChoiceWith (System.Random()) xs
+
+let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T[]) : 'T[] =
+ if count < 0 then
+ invalidArg "count" SR.inputMustBeNonNegative
+
+ if count > 0 && xs.Length = 0 then
+ invalidArg "source" SR.inputSequenceEmpty
+
+ let len = xs.Length
+
+ initialize
+ count
+ (fun _ ->
+ let r = randomizer ()
+
+ if r < 0.0 || r >= 1.0 then
+ invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
+
+ xs[int (r * float len)]
+ )
+
+let randomChoicesWith (random: System.Random) (count: int) (xs: 'T[]) : 'T[] =
+ randomChoicesBy (fun () -> random.NextDouble()) count xs
+
+let randomChoices (count: int) (xs: 'T[]) : 'T[] =
+ randomChoicesWith (System.Random()) count xs
+
+let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T[]) : 'T[] =
+ if count < 0 then
+ invalidArg "count" SR.inputMustBeNonNegative
+
+ let arr = copy xs
+ let len = arr.Length
+
+ if len = 0 && count > 0 then
+ invalidArg "source" SR.inputSequenceEmpty
+
+ if count > len then
+ invalidArg "count" SR.notEnoughElements
+
+ for i = 0 to count - 1 do
+ let r = randomizer ()
+
+ if r < 0.0 || r >= 1.0 then
+ invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
+
+ let j = i + int (r * float (len - i))
+ let tmp = arr[i]
+ arr[i] <- arr[j]
+ arr[j] <- tmp
+
+ getSubArray arr 0 count
+
+let randomSampleWith (random: System.Random) (count: int) (xs: 'T[]) : 'T[] =
+ randomSampleBy (fun () -> random.NextDouble()) count xs
+
+let randomSample (count: int) (xs: 'T[]) : 'T[] =
+ randomSampleWith (System.Random()) count xs
diff --git a/src/fable-library-dart/Fable.Library.Dart.fsproj b/src/fable-library-dart/Fable.Library.Dart.fsproj
index 0e239923f4..9209274957 100644
--- a/src/fable-library-dart/Fable.Library.Dart.fsproj
+++ b/src/fable-library-dart/Fable.Library.Dart.fsproj
@@ -23,6 +23,7 @@
+
diff --git a/src/fable-library-dart/List.fs b/src/fable-library-dart/List.fs
index 06ec570d33..d0cd36bb3b 100644
--- a/src/fable-library-dart/List.fs
+++ b/src/fable-library-dart/List.fs
@@ -975,3 +975,39 @@ let updateAt (index: int) (y: 'T) (xs: 'T list) : 'T list =
invalidArg "index" SR.indexOutOfBounds
ys
+
+let randomShuffleBy (randomizer: unit -> float) (xs: 'T list) : 'T list =
+ let arr = toArray xs
+ Array.randomShuffleInPlaceBy randomizer arr
+ ofArray arr
+
+let randomShuffleWith (random: System.Random) (xs: 'T list) : 'T list =
+ randomShuffleBy (fun () -> random.NextDouble()) xs
+
+let randomShuffle (xs: 'T list) : 'T list = randomShuffleWith (System.Random()) xs
+
+let randomChoiceBy (randomizer: unit -> float) (xs: 'T list) : 'T =
+ toArray xs |> Array.randomChoiceBy randomizer
+
+let randomChoiceWith (random: System.Random) (xs: 'T list) : 'T =
+ randomChoiceBy (fun () -> random.NextDouble()) xs
+
+let randomChoice (xs: 'T list) : 'T = randomChoiceWith (System.Random()) xs
+
+let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T list) : 'T list =
+ toArray xs |> Array.randomChoicesBy randomizer count |> ofArray
+
+let randomChoicesWith (random: System.Random) (count: int) (xs: 'T list) : 'T list =
+ randomChoicesBy (fun () -> random.NextDouble()) count xs
+
+let randomChoices (count: int) (xs: 'T list) : 'T list =
+ randomChoicesWith (System.Random()) count xs
+
+let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T list) : 'T list =
+ toArray xs |> Array.randomSampleBy randomizer count |> ofArray
+
+let randomSampleWith (random: System.Random) (count: int) (xs: 'T list) : 'T list =
+ randomSampleBy (fun () -> random.NextDouble()) count xs
+
+let randomSample (count: int) (xs: 'T list) : 'T list =
+ randomSampleWith (System.Random()) count xs
diff --git a/src/fable-library-dart/Random.dart b/src/fable-library-dart/Random.dart
new file mode 100644
index 0000000000..d3b47305ad
--- /dev/null
+++ b/src/fable-library-dart/Random.dart
@@ -0,0 +1,48 @@
+// ignore_for_file: file_names
+import 'dart:math' as dart_math;
+
+Exception _outOfRange(String paramName) =>
+ Exception('Specified argument was out of the range of valid values. (Parameter $paramName)');
+
+Exception _nullArg(String paramName) =>
+ Exception('Value cannot be null. (Parameter $paramName)');
+
+class Random {
+ final dart_math.Random _random;
+
+ Random([int? seed]) : _random = dart_math.Random(seed);
+
+ int Next0() => _random.nextInt(0x7fffffff);
+
+ int Next1(int maxValue) {
+ if (maxValue < 0) {
+ throw _outOfRange('maxValue');
+ }
+ return Next2(0, maxValue);
+ }
+
+ int Next2(int minValue, int maxValue) {
+ if (maxValue < minValue) {
+ throw _outOfRange('minValue');
+ }
+
+ if (maxValue == minValue) {
+ return minValue;
+ }
+
+ final range = maxValue - minValue;
+ return _random.nextInt(range) + minValue;
+ }
+
+ double NextDouble() => _random.nextDouble();
+
+ void NextBytes(List? buffer) {
+ if (buffer == null) {
+ throw _nullArg('buffer');
+ }
+
+ for (var i = 0; i < buffer.length; i++) {
+ buffer[i] = _random.nextInt(256);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/fable-library-dart/Seq.fs b/src/fable-library-dart/Seq.fs
index b78e255524..3361f0b509 100644
--- a/src/fable-library-dart/Seq.fs
+++ b/src/fable-library-dart/Seq.fs
@@ -1229,3 +1229,39 @@ let updateAt (index: int) (y: 'T) (xs: seq<'T>) : seq<'T> =
None
)
(fun e -> e.Dispose())
+
+let randomShuffleBy (randomizer: unit -> float) (xs: 'T seq) : 'T seq =
+ let arr = toArray xs
+ Array.randomShuffleInPlaceBy randomizer arr
+ ofArray arr
+
+let randomShuffleWith (random: System.Random) (xs: 'T seq) : 'T seq =
+ randomShuffleBy (fun () -> random.NextDouble()) xs
+
+let randomShuffle (xs: 'T seq) : 'T seq = randomShuffleWith (System.Random()) xs
+
+let randomChoiceBy (randomizer: unit -> float) (xs: 'T seq) : 'T =
+ xs |> toArray |> Array.randomChoiceBy randomizer
+
+let randomChoiceWith (random: System.Random) (xs: 'T seq) : 'T =
+ xs |> toArray |> Array.randomChoiceWith random
+
+let randomChoice (xs: 'T seq) : 'T = xs |> toArray |> Array.randomChoice
+
+let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T seq) : 'T seq =
+ xs |> toArray |> Array.randomChoicesBy randomizer count |> ofArray
+
+let randomChoicesWith (random: System.Random) (count: int) (xs: 'T seq) : 'T seq =
+ randomChoicesBy (fun () -> random.NextDouble()) count xs
+
+let randomChoices (count: int) (xs: 'T seq) : 'T seq =
+ randomChoicesWith (System.Random()) count xs
+
+let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T seq) : 'T seq =
+ xs |> toArray |> Array.randomSampleBy randomizer count |> ofArray
+
+let randomSampleWith (random: System.Random) (count: int) (xs: 'T seq) : 'T seq =
+ randomSampleBy (fun () -> random.NextDouble()) count xs
+
+let randomSample (count: int) (xs: 'T seq) : 'T seq =
+ randomSampleWith (System.Random()) count xs
diff --git a/src/fable-library-py/fable_library/Array.fs b/src/fable-library-py/fable_library/Array.fs
deleted file mode 100644
index dc44b26f3b..0000000000
--- a/src/fable-library-py/fable_library/Array.fs
+++ /dev/null
@@ -1,1227 +0,0 @@
-module ArrayModule
-
-// Disables warn:1204 raised by use of LanguagePrimitives.ErrorStrings.*
-#nowarn "1204"
-
-open System.Collections.Generic
-open Fable.Core
-open Fable.Core.JsInterop
-
-open Native
-open Native.Helpers
-
-let private indexNotFound () =
- failwith "An index satisfying the predicate was not found in the collection."
-
-let private differentLengths () = failwith "Arrays had different lengths"
-
-// Pay attention when benchmarking to append and filter functions below
-// if implementing via native JS array .concat() and .filter() do not fall behind due to js-native transitions.
-
-// Don't use native JS Array.prototype.concat as it doesn't work with typed arrays
-let append (array1: 'T[]) (array2: 'T[]) ([] cons: Cons<'T>) : 'T[] =
- let len1 = array1.Length
- let len2 = array2.Length
- let newArray = allocateArrayFromCons cons (len1 + len2)
-
- for i = 0 to len1 - 1 do
- newArray.[i] <- array1.[i]
-
- for i = 0 to len2 - 1 do
- newArray.[i + len1] <- array2.[i]
-
- newArray
-
-let filter (predicate: 'T -> bool) (array: 'T[]) = filterImpl predicate array
-
-// intentionally returns target instead of unit
-let fill (target: 'T[]) (targetIndex: int) (count: int) (value: 'T) : 'T[] = fillImpl target value targetIndex count
-
-let getSubArray (array: 'T[]) (start: int) (count: int) : 'T[] = subArrayImpl array start count
-
-let last (array: 'T[]) =
- if array.Length = 0 then
- invalidArg "array" LanguagePrimitives.ErrorStrings.InputArrayEmptyString
-
- array.[array.Length - 1]
-
-let tryLast (array: 'T[]) =
- if array.Length = 0 then
- None
- else
- Some array.[array.Length - 1]
-
-let mapIndexed (f: int -> 'T -> 'U) (source: 'T[]) ([] cons: Cons<'U>) : 'U[] =
- let len = source.Length
- let target = allocateArrayFromCons cons len
-
- for i = 0 to (len - 1) do
- target.[i] <- f i source.[i]
-
- target
-
-let map (f: 'T -> 'U) (source: 'T[]) ([] cons: Cons<'U>) : 'U[] =
- let len = source.Length
- let target = allocateArrayFromCons cons len
-
- for i = 0 to (len - 1) do
- target.[i] <- f source.[i]
-
- target
-
-let mapIndexed2
- (f: int -> 'T1 -> 'T2 -> 'U)
- (source1: 'T1[])
- (source2: 'T2[])
- ([] cons: Cons<'U>)
- : 'U[]
- =
- if source1.Length <> source2.Length then
- failwith "Arrays had different lengths"
-
- let result = allocateArrayFromCons cons source1.Length
-
- for i = 0 to source1.Length - 1 do
- result.[i] <- f i source1.[i] source2.[i]
-
- result
-
-let map2 (f: 'T1 -> 'T2 -> 'U) (source1: 'T1[]) (source2: 'T2[]) ([] cons: Cons<'U>) : 'U[] =
- if source1.Length <> source2.Length then
- failwith "Arrays had different lengths"
-
- let result = allocateArrayFromCons cons source1.Length
-
- for i = 0 to source1.Length - 1 do
- result.[i] <- f source1.[i] source2.[i]
-
- result
-
-let mapIndexed3
- (f: int -> 'T1 -> 'T2 -> 'T3 -> 'U)
- (source1: 'T1[])
- (source2: 'T2[])
- (source3: 'T3[])
- ([] cons: Cons<'U>)
- : 'U[]
- =
- if source1.Length <> source2.Length || source2.Length <> source3.Length then
- failwith "Arrays had different lengths"
-
- let result = allocateArrayFromCons cons source1.Length
-
- for i = 0 to source1.Length - 1 do
- result.[i] <- f i source1.[i] source2.[i] source3.[i]
-
- result
-
-let map3
- (f: 'T1 -> 'T2 -> 'T3 -> 'U)
- (source1: 'T1[])
- (source2: 'T2[])
- (source3: 'T3[])
- ([] cons: Cons<'U>)
- : 'U[]
- =
- if source1.Length <> source2.Length || source2.Length <> source3.Length then
- failwith "Arrays had different lengths"
-
- let result = allocateArrayFromCons cons source1.Length
-
- for i = 0 to source1.Length - 1 do
- result.[i] <- f source1.[i] source2.[i] source3.[i]
-
- result
-
-let mapFold<'T, 'State, 'Result>
- (mapping: 'State -> 'T -> 'Result * 'State)
- state
- (array: 'T[])
- ([] cons: Cons<'Result>)
- =
- match array.Length with
- | 0 -> [||], state
- | len ->
- let mutable acc = state
- let res = allocateArrayFromCons cons len
-
- for i = 0 to array.Length - 1 do
- let h, s = mapping acc array.[i]
- res.[i] <- h
- acc <- s
-
- res, acc
-
-let mapFoldBack<'T, 'State, 'Result>
- (mapping: 'T -> 'State -> 'Result * 'State)
- (array: 'T[])
- state
- ([] cons: Cons<'Result>)
- =
- match array.Length with
- | 0 -> [||], state
- | len ->
- let mutable acc = state
- let res = allocateArrayFromCons cons len
-
- for i = array.Length - 1 downto 0 do
- let h, s = mapping array.[i] acc
- res.[i] <- h
- acc <- s
-
- res, acc
-
-let indexed (source: 'T[]) =
- let len = source.Length
- let target = allocateArray len
-
- for i = 0 to (len - 1) do
- target.[i] <- i, source.[i]
-
- target
-
-let truncate (count: int) (array: 'T[]) : 'T[] =
- let count = max 0 count
- subArrayImpl array 0 count
-
-let concat (arrays: 'T[] seq) ([] cons: Cons<'T>) : 'T[] =
- let arrays =
- if isDynamicArrayImpl arrays then
- arrays :?> 'T[][] // avoid extra copy
- else
- arrayFrom arrays
-
- match arrays.Length with
- | 0 -> allocateArrayFromCons cons 0
- | 1 -> arrays.[0]
- | _ ->
- let mutable totalIdx = 0
- let mutable totalLength = 0
-
- for arr in arrays do
- totalLength <- totalLength + arr.Length
-
- let result = allocateArrayFromCons cons totalLength
-
- for arr in arrays do
- for j = 0 to (arr.Length - 1) do
- result.[totalIdx] <- arr.[j]
- totalIdx <- totalIdx + 1
-
- result
-
-let collect (mapping: 'T -> 'U[]) (array: 'T[]) ([] cons: Cons<'U>) : 'U[] =
- let mapped = map mapping array Unchecked.defaultof<_>
- concat mapped cons
-// collectImpl mapping array // flatMap not widely available yet
-
-let where predicate (array: _[]) = filterImpl predicate array
-
-let indexOf<'T>
- (array: 'T[])
- (item: 'T)
- (start: int option)
- (count: int option)
- ([] eq: IEqualityComparer<'T>)
- =
- let start = defaultArg start 0
-
- let end' =
- count |> Option.map (fun c -> start + c) |> Option.defaultValue array.Length
-
- let rec loop i =
- if i >= end' then
- -1
- else if eq.Equals(item, array.[i]) then
- i
- else
- loop (i + 1)
-
- loop start
-
-let contains<'T> (value: 'T) (array: 'T[]) ([] eq: IEqualityComparer<'T>) =
- indexOf array value None None eq >= 0
-
-let empty cons = allocateArrayFromCons cons 0
-
-let singleton value ([] cons: Cons<'T>) =
- let ar = allocateArrayFromCons cons 1
- ar.[0] <- value
- ar
-
-let initialize count initializer ([] cons: Cons<'T>) =
- if count < 0 then
- invalidArg "count" LanguagePrimitives.ErrorStrings.InputMustBeNonNegativeString
-
- let result = allocateArrayFromCons cons count
-
- for i = 0 to count - 1 do
- result.[i] <- initializer i
-
- result
-
-let pairwise (array: 'T[]) =
- if array.Length < 2 then
- [||]
- else
- let count = array.Length - 1
- let result = allocateArray count
-
- for i = 0 to count - 1 do
- result.[i] <- array.[i], array.[i + 1]
-
- result
-
-let replicate count initial ([] cons: Cons<'T>) =
- // Shorthand version: = initialize count (fun _ -> initial)
- if count < 0 then
- invalidArg "count" LanguagePrimitives.ErrorStrings.InputMustBeNonNegativeString
-
- let result: 'T array = allocateArrayFromCons cons count
-
- for i = 0 to result.Length - 1 do
- result.[i] <- initial
-
- result
-
-let copy (array: 'T[]) =
- // if isTypedArrayImpl array then
- // let res = allocateArrayFrom array array.Length
- // for i = 0 to array.Length-1 do
- // res.[i] <- array.[i]
- // res
- // else
- copyImpl array
-
-let copyTo (source: 'T[]) (sourceIndex: int) (target: 'T[]) (targetIndex: int) (count: int) =
- // TODO: Check array lengths
- System.Array.Copy(source, sourceIndex, target, targetIndex, count)
-
-let reverse (array: 'T[]) =
- // if isTypedArrayImpl array then
- // let res = allocateArrayFrom array array.Length
- // let mutable j = array.Length-1
- // for i = 0 to array.Length-1 do
- // res.[j] <- array.[i]
- // j <- j - 1
- // res
- // else
- copyImpl array |> reverseImpl
-
-let scan<'T, 'State> folder (state: 'State) (array: 'T[]) ([] cons: Cons<'State>) =
- let res = allocateArrayFromCons cons (array.Length + 1)
- res.[0] <- state
-
- for i = 0 to array.Length - 1 do
- res.[i + 1] <- folder res.[i] array.[i]
-
- res
-
-let scanBack<'T, 'State> folder (array: 'T[]) (state: 'State) ([] cons: Cons<'State>) =
- let res = allocateArrayFromCons cons (array.Length + 1)
- res.[array.Length] <- state
-
- for i = array.Length - 1 downto 0 do
- res.[i] <- folder array.[i] res.[i + 1]
-
- res
-
-let skip count (array: 'T[]) ([] cons: Cons<'T>) =
- if count > array.Length then
- invalidArg "count" "count is greater than array length"
-
- if count = array.Length then
- allocateArrayFromCons cons 0
- else
- let count =
- if count < 0 then
- 0
- else
- count
-
- skipImpl array count
-
-let skipWhile predicate (array: 'T[]) ([] cons: Cons<'T>) =
- let mutable count = 0
-
- while count < array.Length && predicate array.[count] do
- count <- count + 1
-
- if count = array.Length then
- allocateArrayFromCons cons 0
- else
- skipImpl array count
-
-let take count (array: 'T[]) ([] cons: Cons<'T>) =
- if count < 0 then
- invalidArg "count" LanguagePrimitives.ErrorStrings.InputMustBeNonNegativeString
-
- if count > array.Length then
- invalidArg "count" "count is greater than array length"
-
- if count = 0 then
- allocateArrayFromCons cons 0
- else
- subArrayImpl array 0 count
-
-let takeWhile predicate (array: 'T[]) ([] cons: Cons<'T>) =
- let mutable count = 0
-
- while count < array.Length && predicate array.[count] do
- count <- count + 1
-
- if count = 0 then
- allocateArrayFromCons cons 0
- else
- subArrayImpl array 0 count
-
-let addInPlace (x: 'T) (array: 'T[]) =
- // if isTypedArrayImpl array then invalidArg "array" "Typed arrays not supported"
- pushImpl array x |> ignore
-
-let addRangeInPlace (range: seq<'T>) (array: 'T[]) =
- // if isTypedArrayImpl array then invalidArg "array" "Typed arrays not supported"
- for x in range do
- addInPlace x array
-
-let insertRangeInPlace index (range: seq<'T>) (array: 'T[]) =
- // if isTypedArrayImpl array then invalidArg "array" "Typed arrays not supported"
- let mutable i = index
-
- for x in range do
- insertImpl array i x |> ignore
- i <- i + 1
-
-let removeInPlace (item: 'T) (array: 'T[]) ([] eq: IEqualityComparer<'T>) =
- let i = indexOf array item None None eq
-
- if i > -1 then
- spliceImpl array i 1 |> ignore
- true
- else
- false
-
-let removeAllInPlace predicate (array: 'T[]) =
- let rec countRemoveAll count =
- let i = findIndexImpl predicate array
-
- if i > -1 then
- spliceImpl array i 1 |> ignore
- countRemoveAll count + 1
- else
- count
-
- countRemoveAll 0
-
-let partition (f: 'T -> bool) (source: 'T[]) ([] cons: Cons<'T>) =
- let len = source.Length
- let res1 = allocateArrayFromCons cons len
- let res2 = allocateArrayFromCons cons len
- let mutable iTrue = 0
- let mutable iFalse = 0
-
- for i = 0 to len - 1 do
- if f source.[i] then
- res1.[iTrue] <- source.[i]
- iTrue <- iTrue + 1
- else
- res2.[iFalse] <- source.[i]
- iFalse <- iFalse + 1
-
- res1 |> truncate iTrue, res2 |> truncate iFalse
-
-let find (predicate: 'T -> bool) (array: 'T[]) : 'T =
- match findImpl predicate array with
- | Some res -> res
- | None -> indexNotFound ()
-
-let tryFind (predicate: 'T -> bool) (array: 'T[]) : 'T option = findImpl predicate array
-
-let findIndex (predicate: 'T -> bool) (array: 'T[]) : int =
- match findIndexImpl predicate array with
- | index when index > -1 -> index
- | _ ->
- indexNotFound ()
- -1
-
-let tryFindIndex (predicate: 'T -> bool) (array: 'T[]) : int option =
- match findIndexImpl predicate array with
- | index when index > -1 -> Some index
- | _ -> None
-
-let pick chooser (array: _[]) =
- let rec loop i =
- if i >= array.Length then
- indexNotFound ()
- else
- match chooser array.[i] with
- | None -> loop (i + 1)
- | Some res -> res
-
- loop 0
-
-let tryPick chooser (array: _[]) =
- let rec loop i =
- if i >= array.Length then
- None
- else
- match chooser array.[i] with
- | None -> loop (i + 1)
- | res -> res
-
- loop 0
-
-let findBack predicate (array: _[]) =
- let rec loop i =
- if i < 0 then
- indexNotFound ()
- elif predicate array.[i] then
- array.[i]
- else
- loop (i - 1)
-
- loop (array.Length - 1)
-
-let tryFindBack predicate (array: _[]) =
- let rec loop i =
- if i < 0 then
- None
- elif predicate array.[i] then
- Some array.[i]
- else
- loop (i - 1)
-
- loop (array.Length - 1)
-
-let findLastIndex predicate (array: _[]) =
- let rec loop i =
- if i < 0 then
- -1
- elif predicate array.[i] then
- i
- else
- loop (i - 1)
-
- loop (array.Length - 1)
-
-let findIndexBack predicate (array: _[]) =
- let rec loop i =
- if i < 0 then
- indexNotFound ()
- -1
- elif predicate array.[i] then
- i
- else
- loop (i - 1)
-
- loop (array.Length - 1)
-
-let tryFindIndexBack predicate (array: _[]) =
- let rec loop i =
- if i < 0 then
- None
- elif predicate array.[i] then
- Some i
- else
- loop (i - 1)
-
- loop (array.Length - 1)
-
-let choose (chooser: 'T -> 'U option) (array: 'T[]) ([] cons: Cons<'U>) =
- let res: 'U[] = [||]
-
- for i = 0 to array.Length - 1 do
- match chooser array.[i] with
- | None -> ()
- | Some y -> pushImpl res y |> ignore
-
- match box cons with
- | null -> res // avoid extra copy
- | _ -> map id res cons
-
-let foldIndexed<'T, 'State> folder (state: 'State) (array: 'T[]) =
- // if isTypedArrayImpl array then
- // let mutable acc = state
- // for i = 0 to array.Length - 1 do
- // acc <- folder i acc array.[i]
- // acc
- // else
- foldIndexedImpl (fun acc x i -> folder i acc x) state array
-
-let fold<'T, 'State> folder (state: 'State) (array: 'T[]) =
- // if isTypedArrayImpl array then
- // let mutable acc = state
- // for i = 0 to array.Length - 1 do
- // acc <- folder acc array.[i]
- // acc
- // else
- foldImpl folder state array
-
-let iterate action (array: 'T[]) =
- for i = 0 to array.Length - 1 do
- action array.[i]
-
-let iterateIndexed action (array: 'T[]) =
- for i = 0 to array.Length - 1 do
- action i array.[i]
-
-let iterate2 action (array1: 'T1[]) (array2: 'T2[]) =
- if array1.Length <> array2.Length then
- differentLengths ()
-
- for i = 0 to array1.Length - 1 do
- action array1.[i] array2.[i]
-
-let iterateIndexed2 action (array1: 'T1[]) (array2: 'T2[]) =
- if array1.Length <> array2.Length then
- differentLengths ()
-
- for i = 0 to array1.Length - 1 do
- action i array1.[i] array2.[i]
-
-let isEmpty (array: 'T[]) = array.Length = 0
-
-let forAll predicate (array: 'T[]) =
- // if isTypedArrayImpl array then
- // let mutable i = 0
- // let mutable result = true
- // while i < array.Length && result do
- // result <- predicate array.[i]
- // i <- i + 1
- // result
- // else
- forAllImpl predicate array
-
-let permute f (array: 'T[]) =
- let size = array.Length
- let res = copyImpl array
- let checkFlags = allocateArray size
-
- iterateIndexed
- (fun i x ->
- let j = f i
-
- if j < 0 || j >= size then
- invalidOp "Not a valid permutation"
-
- res.[j] <- x
- checkFlags.[j] <- 1
- )
- array
-
- let isValid = checkFlags |> forAllImpl ((=) 1)
-
- if not isValid then
- invalidOp "Not a valid permutation"
-
- res
-
-let setSlice (target: 'T[]) (lower: int option) (upper: int option) (source: 'T[]) =
- let lower = defaultArg lower 0
- let upper = defaultArg upper -1
-
- let length =
- (if upper >= 0 then
- upper
- else
- target.Length - 1)
- - lower
- // can't cast to TypedArray, so can't use TypedArray-specific methods
- // if isTypedArrayImpl target && source.Length <= length then
- // typedArraySetImpl target source lower
- // else
- for i = 0 to length do
- target.[i + lower] <- source.[i]
-
-let sortInPlaceBy (projection: 'a -> 'b) (xs: 'a[]) ([] comparer: IComparer<'b>) : unit =
- sortInPlaceWithImpl (fun x y -> comparer.Compare(projection x, projection y)) xs
-
-let sortInPlace (xs: 'T[]) ([] comparer: IComparer<'T>) =
- sortInPlaceWithImpl (fun x y -> comparer.Compare(x, y)) xs
-
-let sortInPlaceWith (comparer: 'T -> 'T -> int) (xs: 'T[]) =
- sortInPlaceWithImpl comparer xs
- xs
-
-let sort (xs: 'T[]) ([] comparer: IComparer<'T>) : 'T[] =
- sortWithImpl (fun x y -> comparer.Compare(x, y)) (xs)
-
-let sortBy (projection: 'a -> 'b) (xs: 'a[]) ([] comparer: IComparer<'b>) : 'a[] =
- sortWithImpl (fun x y -> comparer.Compare(projection x, projection y)) xs
-
-let sortDescending (xs: 'T[]) ([] comparer: IComparer<'T>) : 'T[] =
- sortWithImpl (fun x y -> comparer.Compare(x, y) * -1) xs
-
-let sortByDescending (projection: 'a -> 'b) (xs: 'a[]) ([] comparer: IComparer<'b>) : 'a[] =
- sortWithImpl (fun x y -> comparer.Compare(projection x, projection y) * -1) xs
-
-let sortWith (comparer: 'T -> 'T -> int) (xs: 'T[]) : 'T[] = sortWithImpl comparer xs
-
-let allPairs (xs: 'T1[]) (ys: 'T2[]) : ('T1 * 'T2)[] =
- let len1 = xs.Length
- let len2 = ys.Length
- let res = allocateArray (len1 * len2)
-
- for i = 0 to xs.Length - 1 do
- for j = 0 to ys.Length - 1 do
- res.[i * len2 + j] <- (xs.[i], ys.[j])
-
- res
-
-let unfold<'T, 'State> (generator: 'State -> ('T * 'State) option) (state: 'State) : 'T[] =
- let res: 'T[] = [||]
-
- let rec loop state =
- match generator state with
- | None -> ()
- | Some(x, s) ->
- pushImpl res x |> ignore
- loop s
-
- loop state
- res
-
-// TODO: We should pass Cons<'T> here (and unzip3) but 'a and 'b may differ
-let unzip (array: _[]) =
- let len = array.Length
- let res1 = allocateArray len
- let res2 = allocateArray len
-
- iterateIndexed
- (fun i (item1, item2) ->
- res1.[i] <- item1
- res2.[i] <- item2
- )
- array
-
- res1, res2
-
-let unzip3 (array: _[]) =
- let len = array.Length
- let res1 = allocateArray len
- let res2 = allocateArray len
- let res3 = allocateArray len
-
- iterateIndexed
- (fun i (item1, item2, item3) ->
- res1.[i] <- item1
- res2.[i] <- item2
- res3.[i] <- item3
- )
- array
-
- res1, res2, res3
-
-let zip (array1: 'T[]) (array2: 'U[]) =
- // Shorthand version: map2 (fun x y -> x, y) array1 array2
- if array1.Length <> array2.Length then
- differentLengths ()
-
- let result = allocateArray array1.Length
-
- for i = 0 to array1.Length - 1 do
- result.[i] <- array1.[i], array2.[i]
-
- result
-
-let zip3 (array1: 'T[]) (array2: 'U[]) (array3: 'V[]) =
- // Shorthand version: map3 (fun x y z -> x, y, z) array1 array2 array3
- if array1.Length <> array2.Length || array2.Length <> array3.Length then
- differentLengths ()
-
- let result = allocateArray array1.Length
-
- for i = 0 to array1.Length - 1 do
- result.[i] <- array1.[i], array2.[i], array3.[i]
-
- result
-
-let chunkBySize (chunkSize: int) (array: 'T[]) : 'T[][] =
- if chunkSize < 1 then
- invalidArg "size" "The input must be positive."
-
- if array.Length = 0 then
- [| [||] |]
- else
- let result: 'T[][] = [||]
- // add each chunk to the result
- for x = 0 to int (System.Math.Ceiling(float (array.Length) / float (chunkSize))) - 1 do
- let start = x * chunkSize
- let slice = subArrayImpl array start chunkSize
- pushImpl result slice |> ignore
-
- result
-
-let splitAt (index: int) (array: 'T[]) : 'T[] * 'T[] =
- if index < 0 || index > array.Length then
- invalidArg "index" SR.indexOutOfBounds
-
- subArrayImpl array 0 index, skipImpl array index
-
-// Note that, though it's not consistent with `compare` operator,
-// Array.compareWith doesn't compare first the length, see #2961
-let compareWith (comparer: 'T -> 'T -> int) (source1: 'T[]) (source2: 'T[]) =
- if isNull source1 then
- if isNull source2 then
- 0
- else
- -1
- elif isNull source2 then
- 1
- else
- let len1 = source1.Length
- let len2 = source2.Length
-
- let len =
- if len1 < len2 then
- len1
- else
- len2
-
- let mutable i = 0
- let mutable res = 0
-
- while res = 0 && i < len do
- res <- comparer source1.[i] source2.[i]
- i <- i + 1
-
- if res <> 0 then
- res
- elif len1 > len2 then
- 1
- elif len1 < len2 then
- -1
- else
- 0
-
-let compareTo (comparer: 'T -> 'T -> int) (source1: 'T[]) (source2: 'T[]) =
- if isNull source1 then
- if isNull source2 then
- 0
- else
- -1
- elif isNull source2 then
- 1
- else
- let len1 = source1.Length
- let len2 = source2.Length
-
- if len1 > len2 then
- 1
- elif len1 < len2 then
- -1
- else
- let mutable i = 0
- let mutable res = 0
-
- while res = 0 && i < len1 do
- res <- comparer source1.[i] source2.[i]
- i <- i + 1
-
- res
-
-let equalsWith (equals: 'T -> 'T -> bool) (array1: 'T[]) (array2: 'T[]) =
- if isNull array1 then
- if isNull array2 then
- true
- else
- false
- elif isNull array2 then
- false
- else
- let mutable i = 0
- let mutable result = true
- let length1 = array1.Length
- let length2 = array2.Length
-
- if length1 > length2 then
- false
- elif length1 < length2 then
- false
- else
- while i < length1 && result do
- result <- equals array1.[i] array2.[i]
- i <- i + 1
-
- result
-
-let exactlyOne (array: 'T[]) =
- if array.Length = 1 then
- array.[0]
- elif array.Length = 0 then
- invalidArg "array" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString
- else
- invalidArg "array" "Input array too long"
-
-let tryExactlyOne (array: 'T[]) =
- if array.Length = 1 then
- Some(array.[0])
- else
- None
-
-let head (array: 'T[]) =
- if array.Length = 0 then
- invalidArg "array" LanguagePrimitives.ErrorStrings.InputArrayEmptyString
- else
- array.[0]
-
-let tryHead (array: 'T[]) =
- if array.Length = 0 then
- None
- else
- Some array.[0]
-
-let tail (array: 'T[]) =
- if array.Length = 0 then
- invalidArg "array" "Not enough elements"
-
- skipImpl array 1
-
-let item index (array: _[]) = array.[index]
-
-let tryItem index (array: 'T[]) =
- if index < 0 || index >= array.Length then
- None
- else
- Some array.[index]
-
-let foldBackIndexed<'T, 'State> folder (array: 'T[]) (state: 'State) =
- // if isTypedArrayImpl array then
- // let mutable acc = state
- // let size = array.Length
- // for i = 1 to size do
- // acc <- folder (i-1) array.[size - i] acc
- // acc
- // else
- foldBackIndexedImpl (fun acc x i -> folder i x acc) state array
-
-let foldBack<'T, 'State> folder (array: 'T[]) (state: 'State) =
- // if isTypedArrayImpl array then
- // foldBackIndexed (fun _ x acc -> folder x acc) array state
- // else
- foldBackImpl (fun acc x -> folder x acc) state array
-
-let foldIndexed2 folder state (array1: _[]) (array2: _[]) =
- let mutable acc = state
-
- if array1.Length <> array2.Length then
- failwith "Arrays have different lengths"
-
- for i = 0 to array1.Length - 1 do
- acc <- folder i acc array1.[i] array2.[i]
-
- acc
-
-let fold2<'T1, 'T2, 'State> folder (state: 'State) (array1: 'T1[]) (array2: 'T2[]) =
- foldIndexed2 (fun _ acc x y -> folder acc x y) state array1 array2
-
-let foldBackIndexed2<'T1, 'T2, 'State> folder (array1: 'T1[]) (array2: 'T2[]) (state: 'State) =
- let mutable acc = state
-
- if array1.Length <> array2.Length then
- differentLengths ()
-
- let size = array1.Length
-
- for i = 1 to size do
- acc <- folder (i - 1) array1.[size - i] array2.[size - i] acc
-
- acc
-
-let foldBack2<'T1, 'T2, 'State> f (array1: 'T1[]) (array2: 'T2[]) (state: 'State) =
- foldBackIndexed2 (fun _ x y acc -> f x y acc) array1 array2 state
-
-let reduce reduction (array: 'T[]) =
- if array.Length = 0 then
- invalidOp LanguagePrimitives.ErrorStrings.InputArrayEmptyString
- // if isTypedArrayImpl array then
- // foldIndexed (fun i acc x -> if i = 0 then x else reduction acc x) Unchecked.defaultof<_> array
- // else
- reduceImpl reduction array
-
-let reduceBack reduction (array: 'T[]) =
- if array.Length = 0 then
- invalidOp LanguagePrimitives.ErrorStrings.InputArrayEmptyString
- // if isTypedArrayImpl array then
- // foldBackIndexed (fun i x acc -> if i = 0 then x else reduction acc x) array Unchecked.defaultof<_>
- // else
- reduceBackImpl reduction array
-
-let forAll2 predicate array1 array2 =
- fold2 (fun acc x y -> acc && predicate x y) true array1 array2
-
-let rec existsOffset predicate (array: 'T[]) index =
- if index = array.Length then
- false
- else
- predicate array.[index] || existsOffset predicate array (index + 1)
-
-let exists predicate array = existsOffset predicate array 0
-
-let rec existsOffset2 predicate (array1: _[]) (array2: _[]) index =
- if index = array1.Length then
- false
- else
- predicate array1.[index] array2.[index]
- || existsOffset2 predicate array1 array2 (index + 1)
-
-let rec exists2 predicate (array1: _[]) (array2: _[]) =
- if array1.Length <> array2.Length then
- differentLengths ()
-
- existsOffset2 predicate array1 array2 0
-
-let sum (array: 'T[]) ([] adder: IGenericAdder<'T>) : 'T =
- let mutable acc = adder.GetZero()
-
- for i = 0 to array.Length - 1 do
- acc <- adder.Add(acc, array.[i])
-
- acc
-
-let sumBy (projection: 'T -> 'T2) (array: 'T[]) ([] adder: IGenericAdder<'T2>) : 'T2 =
- let mutable acc = adder.GetZero()
-
- for i = 0 to array.Length - 1 do
- acc <- adder.Add(acc, projection array.[i])
-
- acc
-
-let maxBy (projection: 'a -> 'b) (xs: 'a[]) ([] comparer: IComparer<'b>) : 'a =
- reduce
- (fun x y ->
- if comparer.Compare(projection y, projection x) > 0 then
- y
- else
- x
- )
- xs
-
-let max (xs: 'a[]) ([] comparer: IComparer<'a>) : 'a =
- reduce
- (fun x y ->
- if comparer.Compare(y, x) > 0 then
- y
- else
- x
- )
- xs
-
-let minBy (projection: 'a -> 'b) (xs: 'a[]) ([] comparer: IComparer<'b>) : 'a =
- reduce
- (fun x y ->
- if comparer.Compare(projection y, projection x) > 0 then
- x
- else
- y
- )
- xs
-
-let min (xs: 'a[]) ([] comparer: IComparer<'a>) : 'a =
- reduce
- (fun x y ->
- if comparer.Compare(y, x) > 0 then
- x
- else
- y
- )
- xs
-
-let average (array: 'T[]) ([] averager: IGenericAverager<'T>) : 'T =
- if array.Length = 0 then
- invalidArg "array" LanguagePrimitives.ErrorStrings.InputArrayEmptyString
-
- let mutable total = averager.GetZero()
-
- for i = 0 to array.Length - 1 do
- total <- averager.Add(total, array.[i])
-
- averager.DivideByInt(total, array.Length)
-
-let averageBy (projection: 'T -> 'T2) (array: 'T[]) ([] averager: IGenericAverager<'T2>) : 'T2 =
- if array.Length = 0 then
- invalidArg "array" LanguagePrimitives.ErrorStrings.InputArrayEmptyString
-
- let mutable total = averager.GetZero()
-
- for i = 0 to array.Length - 1 do
- total <- averager.Add(total, projection array.[i])
-
- averager.DivideByInt(total, array.Length)
-
-// let toList (source: 'T[]) = List.ofArray (see Replacements)
-
-let windowed (windowSize: int) (source: 'T[]) : 'T[][] =
- if windowSize <= 0 then
- failwith "windowSize must be positive"
-
- let res =
- FSharp.Core.Operators.max 0 (source.Length - windowSize + 1) |> allocateArray
-
- for i = windowSize to source.Length do
- res.[i - windowSize] <- source.[i - windowSize .. i - 1]
-
- res
-
-let splitInto (chunks: int) (array: 'T[]) : 'T[][] =
- if chunks < 1 then
- invalidArg "chunks" "The input must be positive."
-
- if array.Length = 0 then
- [| [||] |]
- else
- let result: 'T[][] = [||]
- let chunks = FSharp.Core.Operators.min chunks array.Length
- let minChunkSize = array.Length / chunks
- let chunksWithExtraItem = array.Length % chunks
-
- for i = 0 to chunks - 1 do
- let chunkSize =
- if i < chunksWithExtraItem then
- minChunkSize + 1
- else
- minChunkSize
-
- let start = i * minChunkSize + (FSharp.Core.Operators.min chunksWithExtraItem i)
-
- let slice = subArrayImpl array start chunkSize
- pushImpl result slice |> ignore
-
- result
-
-let transpose (arrays: 'T[] seq) ([] cons: Cons<'T>) : 'T[][] =
- let arrays =
- if isDynamicArrayImpl arrays then
- arrays :?> 'T[][] // avoid extra copy
- else
- arrayFrom arrays
-
- let len = arrays.Length
-
- match len with
- | 0 -> allocateArray 0
- | _ ->
- let firstArray = arrays.[0]
- let lenInner = firstArray.Length
-
- if arrays |> forAll (fun a -> a.Length = lenInner) |> not then
- differentLengths ()
-
- let result: 'T[][] = allocateArray lenInner
-
- for i in 0 .. lenInner - 1 do
- result.[i] <- allocateArrayFromCons cons len
-
- for j in 0 .. len - 1 do
- result.[i].[j] <- arrays.[j].[i]
-
- result
-
-let insertAt (index: int) (y: 'T) (xs: 'T[]) ([] cons: Cons<'T>) : 'T[] =
- let len = xs.Length
-
- if index < 0 || index > len then
- invalidArg "index" SR.indexOutOfBounds
-
- let target = allocateArrayFromCons cons (len + 1)
-
- for i = 0 to (index - 1) do
- target.[i] <- xs.[i]
-
- target.[index] <- y
-
- for i = index to (len - 1) do
- target.[i + 1] <- xs.[i]
-
- target
-
-let insertManyAt (index: int) (ys: seq<'T>) (xs: 'T[]) ([] cons: Cons<'T>) : 'T[] =
- let len = xs.Length
-
- if index < 0 || index > len then
- invalidArg "index" SR.indexOutOfBounds
-
- let ys = arrayFrom ys
- let len2 = ys.Length
- let target = allocateArrayFromCons cons (len + len2)
-
- for i = 0 to (index - 1) do
- target.[i] <- xs.[i]
-
- for i = 0 to (len2 - 1) do
- target.[index + i] <- ys.[i]
-
- for i = index to (len - 1) do
- target.[i + len2] <- xs.[i]
-
- target
-
-let removeAt (index: int) (xs: 'T[]) : 'T[] =
- if index < 0 || index >= xs.Length then
- invalidArg "index" SR.indexOutOfBounds
-
- let mutable i = -1
-
- xs
- |> filter (fun _ ->
- i <- i + 1
- i <> index
- )
-
-let removeManyAt (index: int) (count: int) (xs: 'T[]) : 'T[] =
- let mutable i = -1
- // incomplete -1, in-progress 0, complete 1
- let mutable status = -1
-
- let ys =
- xs
- |> filter (fun _ ->
- i <- i + 1
-
- if i = index then
- status <- 0
- false
- elif i > index then
- if i < index + count then
- false
- else
- status <- 1
- true
- else
- true
- )
-
- let status =
- if status = 0 && i + 1 = index + count then
- 1
- else
- status
-
- if status < 1 then
- // F# always says the wrong parameter is index but the problem may be count
- let arg =
- if status < 0 then
- "index"
- else
- "count"
-
- invalidArg arg SR.indexOutOfBounds
-
- ys
-
-let updateAt (index: int) (y: 'T) (xs: 'T[]) ([] cons: Cons<'T>) : 'T[] =
- let len = xs.Length
-
- if index < 0 || index >= len then
- invalidArg "index" SR.indexOutOfBounds
-
- let target = allocateArrayFromCons cons len
-
- for i = 0 to (len - 1) do
- target.[i] <-
- if i = index then
- y
- else
- xs.[i]
-
- target
diff --git a/src/fable-library-py/fable_library/List.fs b/src/fable-library-py/fable_library/List.fs
index 578f26197e..feebfcfa5c 100644
--- a/src/fable-library-py/fable_library/List.fs
+++ b/src/fable-library-py/fable_library/List.fs
@@ -1004,3 +1004,39 @@ let updateAt (index: int) (y: 'T) (xs: 'T list) : 'T list =
invalidArg "index" SR.indexOutOfBounds
ys
+
+let randomShuffleBy (randomizer: unit -> float) (xs: 'T list) : 'T list =
+ let arr = toArray xs
+ Array.randomShuffleInPlaceBy randomizer arr
+ ofArray arr
+
+let randomShuffleWith (random: System.Random) (xs: 'T list) : 'T list =
+ randomShuffleBy (fun () -> random.NextDouble()) xs
+
+let randomShuffle (xs: 'T list) : 'T list = randomShuffleWith (System.Random()) xs
+
+let randomChoiceBy (randomizer: unit -> float) (xs: 'T list) : 'T =
+ toArray xs |> Array.randomChoiceBy randomizer
+
+let randomChoiceWith (random: System.Random) (xs: 'T list) : 'T =
+ randomChoiceBy (fun () -> random.NextDouble()) xs
+
+let randomChoice (xs: 'T list) : 'T = randomChoiceWith (System.Random()) xs
+
+let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T list) : 'T list =
+ toArray xs |> Array.randomChoicesBy randomizer count |> ofArray
+
+let randomChoicesWith (random: System.Random) (count: int) (xs: 'T list) : 'T list =
+ randomChoicesBy (fun () -> random.NextDouble()) count xs
+
+let randomChoices (count: int) (xs: 'T list) : 'T list =
+ randomChoicesWith (System.Random()) count xs
+
+let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T list) : 'T list =
+ toArray xs |> Array.randomSampleBy randomizer count |> ofArray
+
+let randomSampleWith (random: System.Random) (count: int) (xs: 'T list) : 'T list =
+ randomSampleBy (fun () -> random.NextDouble()) count xs
+
+let randomSample (count: int) (xs: 'T list) : 'T list =
+ randomSampleWith (System.Random()) count xs
diff --git a/src/fable-library-py/fable_library/Seq.fs b/src/fable-library-py/fable_library/Seq.fs
index 1ff8335922..8700050184 100644
--- a/src/fable-library-py/fable_library/Seq.fs
+++ b/src/fable-library-py/fable_library/Seq.fs
@@ -1297,3 +1297,39 @@ let updateAt (index: int) (y: 'T) (xs: seq<'T>) : seq<'T> =
None
)
(fun e -> e.Dispose())
+
+let randomShuffleBy (randomizer: unit -> float) (xs: 'T seq) : 'T seq =
+ let arr = toArray xs
+ Array.randomShuffleInPlaceBy randomizer arr
+ ofArray arr
+
+let randomShuffleWith (random: System.Random) (xs: 'T seq) : 'T seq =
+ randomShuffleBy (fun () -> random.NextDouble()) xs
+
+let randomShuffle (xs: 'T seq) : 'T seq = randomShuffleWith (System.Random()) xs
+
+let randomChoiceBy (randomizer: unit -> float) (xs: 'T seq) : 'T =
+ xs |> toArray |> Array.randomChoiceBy randomizer
+
+let randomChoiceWith (random: System.Random) (xs: 'T seq) : 'T =
+ xs |> toArray |> Array.randomChoiceWith random
+
+let randomChoice (xs: 'T seq) : 'T = xs |> toArray |> Array.randomChoice
+
+let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T seq) : 'T seq =
+ xs |> toArray |> Array.randomChoicesBy randomizer count |> ofArray
+
+let randomChoicesWith (random: System.Random) (count: int) (xs: 'T seq) : 'T seq =
+ randomChoicesBy (fun () -> random.NextDouble()) count xs
+
+let randomChoices (count: int) (xs: 'T seq) : 'T seq =
+ randomChoicesWith (System.Random()) count xs
+
+let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T seq) : 'T seq =
+ xs |> toArray |> Array.randomSampleBy randomizer count |> ofArray
+
+let randomSampleWith (random: System.Random) (count: int) (xs: 'T seq) : 'T seq =
+ randomSampleBy (fun () -> random.NextDouble()) count xs
+
+let randomSample (count: int) (xs: 'T seq) : 'T seq =
+ randomSampleWith (System.Random()) count xs
diff --git a/src/fable-library-py/fable_library/array_.py b/src/fable-library-py/fable_library/array_.py
index fbca0e6ad1..9e4e58193b 100644
--- a/src/fable-library-py/fable_library/array_.py
+++ b/src/fable-library-py/fable_library/array_.py
@@ -137,6 +137,22 @@
compare_to = array.compare_to
sort_with = array.sort_with
choose = array.choose
+random_choice = array.random_choice
+random_choice_by = array.random_choice_by
+random_choice_with = array.random_choice_with
+random_choices = array.random_choices
+random_choices_by = array.random_choices_by
+random_choices_with = array.random_choices_with
+random_sample = array.random_sample
+random_sample_by = array.random_sample_by
+random_sample_with = array.random_sample_with
+random_shuffle = array.random_shuffle
+random_shuffle_by = array.random_shuffle_by
+random_shuffle_in_place = array.random_shuffle_in_place
+random_shuffle_in_place_by = array.random_shuffle_in_place_by
+random_shuffle_in_place_with = array.random_shuffle_in_place_with
+random_shuffle_with = array.random_shuffle_with
+
Array = array.FSharpArray
FSharpCons = array.FSharpCons
@@ -229,6 +245,21 @@
"pick",
"reduce",
"reduce_back",
+ "random_choice",
+ "random_choice_by",
+ "random_choice_with",
+ "random_choices",
+ "random_choices_by",
+ "random_choices_with",
+ "random_sample",
+ "random_sample_by",
+ "random_sample_with",
+ "random_shuffle",
+ "random_shuffle_by",
+ "random_shuffle_in_place",
+ "random_shuffle_in_place_by",
+ "random_shuffle_in_place_with",
+ "random_shuffle_with",
"remove_at",
"remove_in_place",
"remove_many_at",
diff --git a/src/fable-library-py/fable_library/core/array.pyi b/src/fable-library-py/fable_library/core/array.pyi
index f84e9027e0..342053d554 100644
--- a/src/fable-library-py/fable_library/core/array.pyi
+++ b/src/fable-library-py/fable_library/core/array.pyi
@@ -7,7 +7,7 @@ methods we have written in Rust. The file will never be used by Python at runtim
from __future__ import annotations
from collections.abc import Callable, Iterable, Iterator, MutableSequence
-from typing import Any, ClassVar, Literal, SupportsInt, overload
+from typing import Any, ClassVar, Literal, SupportsFloat, SupportsInt, overload
from fable_library.option import Option
from fable_library.protocols import (
@@ -175,6 +175,21 @@ class FSharpArray[T](MutableSequence[T]):
) -> tuple[FSharpArray[T], FSharpArray[T]]: ...
def permute(self, f: Callable[[Int32], SupportsInt], array: FSharpArray[T]) -> FSharpArray[T]: ...
def pick[U](self, chooser: Callable[[T], Option[U]]) -> U: ...
+ def random_choice(self) -> T: ...
+ def random_choice_by(self, randomizer: Callable[[], SupportsFloat]) -> T: ...
+ def random_choice_with(self, random: Any) -> T: ...
+ def random_choices(self, count: int) -> FSharpArray[T]: ...
+ def random_choices_by(self, randomizer: Callable[[], SupportsFloat], count: int) -> FSharpArray[T]: ...
+ def random_choices_with(self, random: Any, count: int) -> FSharpArray[T]: ...
+ def random_sample(self, count: int) -> FSharpArray[T]: ...
+ def random_sample_by(self, randomizer: Callable[[], SupportsFloat], count: int) -> FSharpArray[T]: ...
+ def random_sample_with(self, random: Any, count: int) -> FSharpArray[T]: ...
+ def random_shuffle(self) -> FSharpArray[T]: ...
+ def random_shuffle_by(self, randomizer: Callable[[], SupportsFloat]) -> FSharpArray[T]: ...
+ def random_shuffle_in_place(self) -> None: ...
+ def random_shuffle_in_place_by(self, randomizer: Callable[[], SupportsFloat]) -> None: ...
+ def random_shuffle_in_place_with(self, random: Any) -> None: ...
+ def random_shuffle_with(self, random: Any) -> FSharpArray[T]: ...
def reduce(self, folder: Callable[[Any, Any], Any], state: Any) -> Any: ...
def reduce_back(self, reduction: Callable[[Any, Any], Any], state: Any) -> Any: ...
def remove_all_in_place(self, predicate: Callable[[T], bool]) -> SupportsInt: ...
@@ -368,6 +383,21 @@ def partition[T](
) -> tuple[FSharpArray[T], FSharpArray[T]]: ...
def permute[T](f: Callable[[Int32], SupportsInt], array: Elements[T]) -> FSharpArray[T]: ...
def pick[T, U](chooser: Callable[[T], Option[U]], array: Elements[T]) -> U: ...
+def random_choice[T](xs: Elements[T]) -> T: ...
+def random_choice_by[T](randomizer: Callable[[], SupportsFloat], xs: Elements[T]) -> T: ...
+def random_choice_with[T](random: Any, xs: Elements[T]) -> T: ...
+def random_choices[T](count: int, xs: Elements[T]) -> FSharpArray[T]: ...
+def random_choices_by[T](randomizer: Callable[[], SupportsFloat], count: int, xs: Elements[T]) -> FSharpArray[T]: ...
+def random_choices_with[T](random: Any, count: int, xs: Elements[T]) -> FSharpArray[T]: ...
+def random_sample[T](count: int, xs: Elements[T]) -> FSharpArray[T]: ...
+def random_sample_by[T](randomizer: Callable[[], SupportsFloat], count: int, xs: Elements[T]) -> FSharpArray[T]: ...
+def random_sample_with[T](random: Any, count: int, xs: Elements[T]) -> FSharpArray[T]: ...
+def random_shuffle[T](xs: Elements[T]) -> FSharpArray[T]: ...
+def random_shuffle_by[T](randomizer: Callable[[], SupportsFloat], xs: Elements[T]) -> FSharpArray[T]: ...
+def random_shuffle_in_place[T](xs: FSharpArray[T]) -> None: ...
+def random_shuffle_in_place_by[T](randomizer: Callable[[], SupportsFloat], xs: FSharpArray[T]) -> None: ...
+def random_shuffle_in_place_with[T](random: Any, xs: FSharpArray[T]) -> None: ...
+def random_shuffle_with[T](random: Any, xs: Elements[T]) -> FSharpArray[T]: ...
def reduce[T](reduction: Callable[[T, T], T], array: Elements[T]) -> T: ...
def reduce_back[T](reduction: Callable[[T, T], T], array: Elements[T]) -> T: ...
def remove_all_in_place[T](array: FSharpArray[T], predicate: Callable[[T], bool]) -> SupportsInt: ...
diff --git a/src/fable-library-py/fable_library/util.py b/src/fable-library-py/fable_library/util.py
index 4d8fbb4968..66542668d2 100644
--- a/src/fable-library-py/fable_library/util.py
+++ b/src/fable-library-py/fable_library/util.py
@@ -41,7 +41,7 @@
SizedBase,
StringableBase,
)
-from .core import float32, float64, int32
+from .core import float32, float64, int32, uint8
# Re-export protocols for backward compatibility
from .exceptions import ObjectDisposedException
@@ -680,8 +680,24 @@ def round(value: float64 | float32, digits: int = 0) -> float64 | float32:
return value.round(digits)
-def randint(a: int32, b: int32) -> int32:
- return int32(random.randint(int(a), int(b) - 1))
+def create_random(seed: int32 | None = None) -> random.Random:
+ return random.Random(None if seed is None else int(seed))
+
+
+def random_int(rand: random.Random, a: int32, b: int32) -> int32:
+ if a == b:
+ return int32(a)
+
+ return int32(rand.randrange(int(a), int(b)))
+
+
+def random_double(rand: random.Random) -> float64:
+ return float64(rand.random())
+
+
+def random_bytes(rand: random.Random, buffer: Array[uint8]) -> None:
+ for i in builtins.range(len(buffer)):
+ buffer[i] = uint8(rand.getrandbits(8))
def unescape_data_string(s: str) -> str:
@@ -882,13 +898,16 @@ def range(start: int, stop: int, step: int = 1) -> Iterable[int32]:
"copy_to_array",
"escape_data_string",
"escape_uri_string",
+ "create_random",
"get_platform",
"identity_hash",
"ignore",
"nullable",
"number_hash",
"physical_hash",
- "randint",
+ "random_int",
+ "random_bytes",
+ "random_double",
"range",
"round",
"to_iterable",
diff --git a/src/fable-library-py/src/array.rs b/src/fable-library-py/src/array.rs
index 746af0af79..efd0d2d5d4 100644
--- a/src/fable-library-py/src/array.rs
+++ b/src/fable-library-py/src/array.rs
@@ -3249,6 +3249,243 @@ impl FSharpArray {
Ok(FSharpArray { storage: result })
}
+
+ // --- Internal swap helper ---
+
+ fn swap_items(&mut self, i: usize, j: usize) {
+ match &mut self.storage {
+ NativeArray::Int8(vec) => vec.swap(i, j),
+ NativeArray::UInt8(vec) => vec.swap(i, j),
+ NativeArray::Int16(vec) => vec.swap(i, j),
+ NativeArray::UInt16(vec) => vec.swap(i, j),
+ NativeArray::Int32(vec) => vec.swap(i, j),
+ NativeArray::UInt32(vec) => vec.swap(i, j),
+ NativeArray::Int64(vec) => vec.swap(i, j),
+ NativeArray::UInt64(vec) => vec.swap(i, j),
+ NativeArray::Float32(vec) => vec.swap(i, j),
+ NativeArray::Float64(vec) => vec.swap(i, j),
+ NativeArray::Bool(vec) => vec.swap(i, j),
+ NativeArray::PyObject(arc) => {
+ let mut vec = arc.lock().unwrap();
+ vec.swap(i, j);
+ }
+ }
+ }
+
+ // --- Random functions ---
+
+ pub fn random_shuffle_in_place_by(
+ &mut self,
+ _py: Python<'_>,
+ randomizer: &Bound<'_, PyAny>,
+ ) -> PyResult<()> {
+ let len = self.storage.len();
+ for i in (1..len).rev() {
+ let r: f64 = randomizer.call0()?.extract()?;
+ if r < 0.0 || r >= 1.0 {
+ return Err(PyErr::new::(format!(
+ "The index is outside the legal range.\nrandomizer returned {}, should be in range [0.0, 1.0).",
+ r
+ )));
+ }
+ let j = (r * (i + 1) as f64) as usize;
+ self.swap_items(i, j);
+ }
+ Ok(())
+ }
+
+ pub fn random_shuffle_in_place_with(
+ &mut self,
+ py: Python<'_>,
+ _random: &Bound<'_, PyAny>,
+ ) -> PyResult<()> {
+ let random_module = py.import("random")?;
+ let randomizer = random_module.getattr("random")?;
+ self.random_shuffle_in_place_by(py, &randomizer)
+ }
+
+ pub fn random_shuffle_in_place(&mut self, py: Python<'_>) -> PyResult<()> {
+ let random_module = py.import("random")?;
+ let randomizer = random_module.getattr("random")?;
+ self.random_shuffle_in_place_by(py, &randomizer)
+ }
+
+ pub fn random_shuffle_by(
+ &self,
+ py: Python<'_>,
+ randomizer: &Bound<'_, PyAny>,
+ ) -> PyResult {
+ let mut arr = self.clone();
+ arr.random_shuffle_in_place_by(py, randomizer)?;
+ Ok(arr)
+ }
+
+ pub fn random_shuffle_with(
+ &self,
+ py: Python<'_>,
+ _random: &Bound<'_, PyAny>,
+ ) -> PyResult {
+ let random_module = py.import("random")?;
+ let randomizer = random_module.getattr("random")?;
+ self.random_shuffle_by(py, &randomizer)
+ }
+
+ pub fn random_shuffle(&self, py: Python<'_>) -> PyResult {
+ let random_module = py.import("random")?;
+ let randomizer = random_module.getattr("random")?;
+ self.random_shuffle_by(py, &randomizer)
+ }
+
+ pub fn random_choice_by(
+ &self,
+ py: Python<'_>,
+ randomizer: &Bound<'_, PyAny>,
+ ) -> PyResult> {
+ let len = self.storage.len();
+ if len == 0 {
+ return Err(PyErr::new::(
+ "The input sequence was empty.",
+ ));
+ }
+ let r: f64 = randomizer.call0()?.extract()?;
+ if r < 0.0 || r >= 1.0 {
+ return Err(PyErr::new::(format!(
+ "The index is outside the legal range.\nrandomizer returned {}, should be in range [0.0, 1.0).",
+ r
+ )));
+ }
+ self.storage.get(py, (r * len as f64) as usize)
+ }
+
+ pub fn random_choice_with(
+ &self,
+ py: Python<'_>,
+ _random: &Bound<'_, PyAny>,
+ ) -> PyResult> {
+ let random_module = py.import("random")?;
+ let randomizer = random_module.getattr("random")?;
+ self.random_choice_by(py, &randomizer)
+ }
+
+ pub fn random_choice(&self, py: Python<'_>) -> PyResult> {
+ let random_module = py.import("random")?;
+ let randomizer = random_module.getattr("random")?;
+ self.random_choice_by(py, &randomizer)
+ }
+
+ pub fn random_choices_by(
+ &self,
+ py: Python<'_>,
+ randomizer: &Bound<'_, PyAny>,
+ count: isize,
+ ) -> PyResult {
+ if count < 0 {
+ return Err(PyErr::new::(
+ "The input must be non-negative.",
+ ));
+ }
+ let count = count as usize;
+ let xs_len = self.storage.len();
+ if count > 0 && xs_len == 0 {
+ return Err(PyErr::new::(
+ "The input sequence was empty.",
+ ));
+ }
+ if count == 0 {
+ if xs_len == 0 {
+ return FSharpArray::empty(py, None);
+ }
+ let first = self.storage.get(py, 0)?;
+ return FSharpArray::create(py, 0, first.bind(py));
+ }
+ let first = self.storage.get(py, 0)?;
+ let mut result = FSharpArray::create(py, count, first.bind(py))?;
+ for i in 0..count {
+ let r: f64 = randomizer.call0()?.extract()?;
+ if r < 0.0 || r >= 1.0 {
+ return Err(PyErr::new::(format!(
+ "The index is outside the legal range.\nrandomizer returned {}, should be in range [0.0, 1.0).",
+ r
+ )));
+ }
+ let src_idx = (r * xs_len as f64) as usize;
+ let val = self.storage.get(py, src_idx)?;
+ result.__setitem__(i as isize, val.bind(py), py)?;
+ }
+ Ok(result)
+ }
+
+ pub fn random_choices_with(
+ &self,
+ py: Python<'_>,
+ _random: &Bound<'_, PyAny>,
+ count: isize,
+ ) -> PyResult {
+ let random_module = py.import("random")?;
+ let randomizer = random_module.getattr("random")?;
+ self.random_choices_by(py, &randomizer, count)
+ }
+
+ pub fn random_choices(&self, py: Python<'_>, count: isize) -> PyResult {
+ let random_module = py.import("random")?;
+ let randomizer = random_module.getattr("random")?;
+ self.random_choices_by(py, &randomizer, count)
+ }
+
+ pub fn random_sample_by(
+ &self,
+ py: Python<'_>,
+ randomizer: &Bound<'_, PyAny>,
+ count: isize,
+ ) -> PyResult {
+ if count < 0 {
+ return Err(PyErr::new::(
+ "The input must be non-negative.",
+ ));
+ }
+ let count = count as usize;
+ let mut arr = self.clone();
+ let length = arr.storage.len();
+ if length == 0 && count > 0 {
+ return Err(PyErr::new::(
+ "The input sequence was empty.",
+ ));
+ }
+ if count > length {
+ return Err(PyErr::new::(
+ "The input sequence has an insufficient number of elements.",
+ ));
+ }
+ for i in 0..count {
+ let r: f64 = randomizer.call0()?.extract()?;
+ if r < 0.0 || r >= 1.0 {
+ return Err(PyErr::new::(format!(
+ "The index is outside the legal range.\nrandomizer returned {}, should be in range [0.0, 1.0).",
+ r
+ )));
+ }
+ let j = i + (r * (length - i) as f64) as usize;
+ arr.swap_items(i, j);
+ }
+ arr.get_sub_array(py, 0, count, None)
+ }
+
+ pub fn random_sample_with(
+ &self,
+ py: Python<'_>,
+ _random: &Bound<'_, PyAny>,
+ count: isize,
+ ) -> PyResult {
+ let random_module = py.import("random")?;
+ let randomizer = random_module.getattr("random")?;
+ self.random_sample_by(py, &randomizer, count)
+ }
+
+ pub fn random_sample(&self, py: Python<'_>, count: isize) -> PyResult {
+ let random_module = py.import("random")?;
+ let randomizer = random_module.getattr("random")?;
+ self.random_sample_by(py, &randomizer, count)
+ }
}
// Loose functions that delegate to member functions
@@ -4062,6 +4299,145 @@ pub fn copy(py: Python<'_>, array: &Bound<'_, PyAny>) -> PyResult {
array.copy(py)
}
+#[pyfunction]
+pub fn random_shuffle_in_place_by(
+ py: Python<'_>,
+ randomizer: &Bound<'_, PyAny>,
+ xs: &mut FSharpArray,
+) -> PyResult<()> {
+ xs.random_shuffle_in_place_by(py, randomizer)
+}
+
+#[pyfunction]
+pub fn random_shuffle_in_place_with(
+ py: Python<'_>,
+ random: &Bound<'_, PyAny>,
+ xs: &mut FSharpArray,
+) -> PyResult<()> {
+ xs.random_shuffle_in_place_with(py, random)
+}
+
+#[pyfunction]
+pub fn random_shuffle_in_place(py: Python<'_>, xs: &mut FSharpArray) -> PyResult<()> {
+ xs.random_shuffle_in_place(py)
+}
+
+#[pyfunction]
+pub fn random_shuffle_by(
+ py: Python<'_>,
+ randomizer: &Bound<'_, PyAny>,
+ xs: &Bound<'_, PyAny>,
+) -> PyResult {
+ let xs = ensure_array(py, xs)?;
+ xs.random_shuffle_by(py, randomizer)
+}
+
+#[pyfunction]
+pub fn random_shuffle_with(
+ py: Python<'_>,
+ random: &Bound<'_, PyAny>,
+ xs: &Bound<'_, PyAny>,
+) -> PyResult {
+ let xs = ensure_array(py, xs)?;
+ xs.random_shuffle_with(py, random)
+}
+
+#[pyfunction]
+pub fn random_shuffle(py: Python<'_>, xs: &Bound<'_, PyAny>) -> PyResult {
+ let xs = ensure_array(py, xs)?;
+ xs.random_shuffle(py)
+}
+
+#[pyfunction]
+pub fn random_choice_by(
+ py: Python<'_>,
+ randomizer: &Bound<'_, PyAny>,
+ xs: &Bound<'_, PyAny>,
+) -> PyResult> {
+ let xs = ensure_array(py, xs)?;
+ xs.random_choice_by(py, randomizer)
+}
+
+#[pyfunction]
+pub fn random_choice_with(
+ py: Python<'_>,
+ random: &Bound<'_, PyAny>,
+ xs: &Bound<'_, PyAny>,
+) -> PyResult> {
+ let xs = ensure_array(py, xs)?;
+ xs.random_choice_with(py, random)
+}
+
+#[pyfunction]
+pub fn random_choice(py: Python<'_>, xs: &Bound<'_, PyAny>) -> PyResult> {
+ let xs = ensure_array(py, xs)?;
+ xs.random_choice(py)
+}
+
+#[pyfunction]
+pub fn random_choices_by(
+ py: Python<'_>,
+ randomizer: &Bound<'_, PyAny>,
+ count: isize,
+ xs: &Bound<'_, PyAny>,
+) -> PyResult {
+ let xs = ensure_array(py, xs)?;
+ xs.random_choices_by(py, randomizer, count)
+}
+
+#[pyfunction]
+pub fn random_choices_with(
+ py: Python<'_>,
+ random: &Bound<'_, PyAny>,
+ count: isize,
+ xs: &Bound<'_, PyAny>,
+) -> PyResult {
+ let xs = ensure_array(py, xs)?;
+ xs.random_choices_with(py, random, count)
+}
+
+#[pyfunction]
+pub fn random_choices(
+ py: Python<'_>,
+ count: isize,
+ xs: &Bound<'_, PyAny>,
+) -> PyResult {
+ let xs = ensure_array(py, xs)?;
+ xs.random_choices(py, count)
+}
+
+#[pyfunction]
+pub fn random_sample_by(
+ py: Python<'_>,
+ randomizer: &Bound<'_, PyAny>,
+ count: isize,
+ xs: &Bound<'_, PyAny>,
+) -> PyResult {
+ let xs = ensure_array(py, xs)?;
+ xs.random_sample_by(py, randomizer, count)
+}
+
+#[pyfunction]
+pub fn random_sample_with(
+ py: Python<'_>,
+ random: &Bound<'_, PyAny>,
+ count: isize,
+ xs: &Bound<'_, PyAny>,
+) -> PyResult {
+ let xs = ensure_array(py, xs)?;
+ xs.random_sample_with(py, random, count)
+}
+
+#[pyfunction]
+pub fn random_sample(
+ py: Python<'_>,
+ count: isize,
+ xs: &Bound<'_, PyAny>,
+) -> PyResult {
+ let xs = ensure_array(py, xs)?;
+ xs.random_sample(py, count)
+}
+
#[pyfunction]
#[pyo3(signature = (array, comparer))]
pub fn max(
@@ -4938,6 +5314,21 @@ pub fn register_array_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()
m.add_function(wrap_pyfunction!(windowed, &m)?)?;
m.add_function(wrap_pyfunction!(zip, &m)?)?;
m.add_function(wrap_pyfunction!(compare_to, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_shuffle_in_place_by, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_shuffle_in_place_with, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_shuffle_in_place, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_shuffle_by, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_shuffle_with, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_shuffle, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_choice_by, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_choice_with, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_choice, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_choices_by, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_choices_with, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_choices, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_sample_by, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_sample_with, &m)?)?;
+ m.add_function(wrap_pyfunction!(random_sample, &m)?)?;
m.add_function(wrap_pyfunction!(allocate_array_from_cons, &m)?)?;
diff --git a/src/fable-library-rust/Cargo.toml b/src/fable-library-rust/Cargo.toml
index 0cb4ee6ead..7b53e40f18 100644
--- a/src/fable-library-rust/Cargo.toml
+++ b/src/fable-library-rust/Cargo.toml
@@ -13,7 +13,7 @@ enum_string = []
guid = ["dep:uuid"]
lrc_ptr = []
no_std = ["dep:hashbrown"]
-random = ["dep:rand"]
+random = ["dep:rand", "dep:chrono"]
regexp = ["dep:regex"]
static_do_bindings = ["dep:startup"]
threaded = ["atomic", "dep:futures", "dep:futures-timer"]
@@ -27,9 +27,9 @@ hashbrown = { version = "0.16", optional = true }
num-bigint = { version = "0.4", optional = true }
num-integer = { version = "0.1", optional = true }
num-traits = { version = "0.2", optional = true }
-rand = { version = "0.10", features = ["alloc", "thread_rng"], default-features = false, optional = true }
+rand = { version = "0.10", features = ["std_rng"], default-features = false, optional = true }
regex = { version = "1.12", optional = true }
-rust_decimal = { version = "1.40", features = ["maths"], default-features = false, optional = true }
+rust_decimal = { version = "1.41", features = ["maths"], default-features = false, optional = true }
startup = { version = "0.1", path = "vendored/startup", optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
diff --git a/src/fable-library-rust/src/Array.fs b/src/fable-library-rust/src/Array.fs
index 04c707e334..13c8f44d5b 100644
--- a/src/fable-library-rust/src/Array.fs
+++ b/src/fable-library-rust/src/Array.fs
@@ -985,9 +985,8 @@ let distinctBy<'T, 'Key when 'Key: equality and 'Key: not null> (projection: 'T
let hashSet = System.Collections.Generic.HashSet<'Key>()
xs |> filter (fun x -> hashSet.Add(projection x))
-let randomShuffleBy (randomizer: unit -> float) (xs: 'T[]) : 'T[] =
- let arr = copy xs
- let len = arr.Length
+let randomShuffleInPlaceBy (randomizer: unit -> float) (xs: 'T[]) : unit =
+ let len = xs.Length
for i = len - 1 downto 1 do
let r = randomizer ()
@@ -996,10 +995,19 @@ let randomShuffleBy (randomizer: unit -> float) (xs: 'T[]) : 'T[] =
invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
let j = int (r * float (i + 1))
- let tmp = arr[i]
- arr[i] <- arr[j]
- arr[j] <- tmp
+ let tmp = xs[i]
+ xs[i] <- xs[j]
+ xs[j] <- tmp
+let randomShuffleInPlaceWith (random: System.Random) (xs: 'T[]) : unit =
+ randomShuffleInPlaceBy (fun () -> random.NextDouble()) xs
+
+let randomShuffleInPlace (xs: 'T[]) : unit =
+ randomShuffleInPlaceWith (System.Random()) xs
+
+let randomShuffleBy (randomizer: unit -> float) (xs: 'T[]) : 'T[] =
+ let arr = copy xs
+ randomShuffleInPlaceBy randomizer arr
arr
let randomShuffleWith (random: System.Random) (xs: 'T[]) : 'T[] =
@@ -1029,7 +1037,7 @@ let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T[]) : 'T[] =
invalidArg "count" SR.inputMustBeNonNegative
if count > 0 && isEmpty xs then
- invalidArg "source" SR.notEnoughElements
+ invalidArg "source" SR.inputSequenceEmpty
let len = xs.Length
@@ -1057,6 +1065,9 @@ let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T[]) : 'T[] =
let arr = copy xs
let len = arr.Length
+ if len = 0 && count > 0 then
+ invalidArg "source" SR.inputSequenceEmpty
+
if count > len then
invalidArg "count" SR.notEnoughElements
diff --git a/src/fable-library-rust/src/List.fs b/src/fable-library-rust/src/List.fs
index ff11a64a8d..9c2399bc68 100644
--- a/src/fable-library-rust/src/List.fs
+++ b/src/fable-library-rust/src/List.fs
@@ -1005,19 +1005,7 @@ let updateAt (index: int) (y: 'T) (xs: 'T list) : 'T list =
let randomShuffleBy (randomizer: unit -> float) (xs: 'T list) : 'T list =
let arr = toArray xs
- let len = arr.Length
-
- for i = len - 1 downto 1 do
- let r = randomizer ()
-
- if r < 0.0 || r >= 1.0 then
- invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
-
- let j = int (r * float (i + 1))
- let tmp = arr[i]
- arr[i] <- arr[j]
- arr[j] <- tmp
-
+ Array.randomShuffleInPlaceBy randomizer arr
ofArray arr
let randomShuffleWith (random: System.Random) (xs: 'T list) : 'T list =
@@ -1026,17 +1014,7 @@ let randomShuffleWith (random: System.Random) (xs: 'T list) : 'T list =
let randomShuffle (xs: 'T list) : 'T list = randomShuffleWith (System.Random()) xs
let randomChoiceBy (randomizer: unit -> float) (xs: 'T list) : 'T =
- if isEmpty xs then
- invalidArg "source" SR.inputSequenceEmpty
-
- let arr = toArray xs
- let len = arr.Length
- let r = randomizer ()
-
- if r < 0.0 || r >= 1.0 then
- invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
-
- arr[int (r * float len)]
+ toArray xs |> Array.randomChoiceBy randomizer
let randomChoiceWith (random: System.Random) (xs: 'T list) : 'T =
randomChoiceBy (fun () -> random.NextDouble()) xs
@@ -1044,25 +1022,7 @@ let randomChoiceWith (random: System.Random) (xs: 'T list) : 'T =
let randomChoice (xs: 'T list) : 'T = randomChoiceWith (System.Random()) xs
let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T list) : 'T list =
- if count < 0 then
- invalidArg "count" SR.inputMustBeNonNegative
-
- if count > 0 && isEmpty xs then
- invalidArg "source" SR.notEnoughElements
-
- let arr = toArray xs
- let len = arr.Length
-
- initialize
- count
- (fun _ ->
- let r = randomizer ()
-
- if r < 0.0 || r >= 1.0 then
- invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
-
- arr[int (r * float len)]
- )
+ toArray xs |> Array.randomChoicesBy randomizer count |> ofArray
let randomChoicesWith (random: System.Random) (count: int) (xs: 'T list) : 'T list =
randomChoicesBy (fun () -> random.NextDouble()) count xs
@@ -1071,34 +1031,7 @@ let randomChoices (count: int) (xs: 'T list) : 'T list =
randomChoicesWith (System.Random()) count xs
let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T list) : 'T list =
- if count < 0 then
- invalidArg "count" SR.inputMustBeNonNegative
-
- let arr = toArray xs
- let len = arr.Length
-
- if count > len then
- invalidArg "count" SR.notEnoughElements
-
- // Partial Fisher-Yates: select count distinct elements
- for i = 0 to count - 1 do
- let r = randomizer ()
-
- if r < 0.0 || r >= 1.0 then
- invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
-
- let j = i + int (r * float (len - i))
- let tmp = arr[i]
- arr[i] <- arr[j]
- arr[j] <- tmp
-
- // Build result list from first count elements
- let mutable result = empty ()
-
- for i = count - 1 downto 0 do
- result <- cons arr[i] result
-
- result
+ toArray xs |> Array.randomSampleBy randomizer count |> ofArray
let randomSampleWith (random: System.Random) (count: int) (xs: 'T list) : 'T list =
randomSampleBy (fun () -> random.NextDouble()) count xs
diff --git a/src/fable-library-rust/src/Random.rs b/src/fable-library-rust/src/Random.rs
index ae61845504..8c5f39bb59 100644
--- a/src/fable-library-rust/src/Random.rs
+++ b/src/fable-library-rust/src/Random.rs
@@ -1,217 +1,126 @@
#[cfg(feature = "random")]
pub mod Random_ {
- use rand::{Rng, RngExt};
#[cfg(feature = "no_std")]
- use rand::rngs::SmallRng as NonSeededRng;
- use rand::rngs::SmallRng;
+ use rand::rngs::SmallRng as RngImpl;
#[cfg(not(feature = "no_std"))]
- use rand::rngs::ThreadRng as NonSeededRng;
- use rand::SeedableRng;
- use core::fmt;
- use crate::Native_::{Lrc, MutCell};
- use crate::NativeArray_::Array;
+ use rand::rngs::StdRng as RngImpl;
+ use rand::{Rng, RngExt, SeedableRng};
- /// Trait for random number generation to support both seeded and non-seeded variants
- pub trait IRandomGenerator: Send + Sync {
- fn next0(&self) -> i32;
- fn next1(&self, max_value: i32) -> i32;
- fn next2(&self, min_value: i32, max_value: i32) -> i32;
- fn next_double(&self) -> f64;
- fn next_bytes(&self, buffer: &mut [u8]);
- }
+ use crate::Native_::{LrcPtr, MutCell};
+ use crate::NativeArray_::Array;
- /// Non-seeded random number generator using thread-local RNG
- pub struct NonSeededRandom {
- rng: MutCell,
- }
+ #[repr(transparent)]
+ pub struct Random(MutCell);
- impl NonSeededRandom {
+ impl Random {
pub fn new() -> Self {
- #[cfg(feature = "no_std")]
- let rng = SmallRng::seed_from_u64(0x5EED_u64);
-
- #[cfg(not(feature = "no_std"))]
- let rng = rand::rng();
-
- NonSeededRandom {
- rng: MutCell::new(rng),
- }
+ let seed = chrono::Utc::now().timestamp_micros() as u64;
+ Random(MutCell::new(RngImpl::seed_from_u64(seed)))
}
- }
-
- impl Default for NonSeededRandom {
- fn default() -> Self {
- Self::new()
- }
- }
- impl fmt::Debug for NonSeededRandom {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("NonSeededRandom").finish()
+ pub fn new_seeded(seed: i32) -> Self {
+ Random(MutCell::new(RngImpl::seed_from_u64(seed as u64)))
}
- }
- impl IRandomGenerator for NonSeededRandom {
- fn next0(&self) -> i32 {
- self.rng.get_mut().random_range(0..i32::MAX)
+ pub fn next0(&self) -> i32 {
+ self.0.get_mut().random_range(0..i32::MAX)
}
- fn next1(&self, max_value: i32) -> i32 {
+ pub fn next1(&self, max_value: i32) -> i32 {
if max_value < 0 {
panic!("max_value must be non-negative");
}
if max_value == 0 {
return 0;
}
- self.rng.get_mut().random_range(0..max_value)
+ self.0.get_mut().random_range(0..max_value)
}
- fn next2(&self, min_value: i32, max_value: i32) -> i32 {
+ pub fn next2(&self, min_value: i32, max_value: i32) -> i32 {
if min_value > max_value {
panic!("min_value must be less than or equal to max_value");
}
if min_value == max_value {
- min_value
- } else {
- self.rng.get_mut().random_range(min_value..max_value)
+ return min_value;
}
+ self.0.get_mut().random_range(min_value..max_value)
}
- fn next_double(&self) -> f64 {
- self.rng.get_mut().random_range(0f64..1f64)
+ pub fn nextDouble(&self) -> f64 {
+ self.0.get_mut().random_range(0f64..1f64)
}
- fn next_bytes(&self, buffer: &mut [u8]) {
- if buffer.is_empty() {
- return;
- }
- self.rng.get_mut().fill_bytes(buffer);
+ pub fn nextBytes(&self, buffer: Array) {
+ self.0.get_mut().fill_bytes(buffer.get_mut().as_mut_slice())
}
}
- /// Seeded random number generator using deterministic SmallRng
- pub struct SeededRandom {
- rng: MutCell,
- }
+ // impl Default for Random {
+ // fn default() -> Self {
+ // Self::new()
+ // }
+ // }
- impl SeededRandom {
- pub fn new(seed: u64) -> Self {
- SeededRandom {
- rng: MutCell::new(SmallRng::seed_from_u64(seed)),
- }
+ impl core::fmt::Debug for Random {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("Random").finish()
}
}
- impl fmt::Debug for SeededRandom {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("SeededRandom").finish()
- }
+ pub fn new() -> LrcPtr {
+ LrcPtr::new(Random::new())
}
- impl IRandomGenerator for SeededRandom {
- fn next0(&self) -> i32 {
- self.rng.get_mut().random_range(0..i32::MAX)
- }
-
- fn next1(&self, max_value: i32) -> i32 {
- if max_value < 0 {
- panic!("max_value must be non-negative");
- }
- if max_value == 0 {
- return 0;
- }
- self.rng.get_mut().random_range(0..max_value)
- }
-
- fn next2(&self, min_value: i32, max_value: i32) -> i32 {
- if min_value > max_value {
- panic!("min_value must be less than or equal to max_value");
- }
- if min_value == max_value {
- min_value
- } else {
- self.rng.get_mut().random_range(min_value..max_value)
- }
- }
+ pub fn new_seeded(seed: i32) -> LrcPtr {
+ LrcPtr::new(Random::new_seeded(seed))
+ }
+}
- fn next_double(&self) -> f64 {
- self.rng.get_mut().random_range(0f64..1f64)
- }
+// stub implementation of System.Random for when the "random" feature is not enabled
+#[cfg(not(feature = "random"))]
+pub mod Random_ {
+ use crate::Native_::{LrcPtr, MutCell};
+ use crate::NativeArray_::Array;
- fn next_bytes(&self, buffer: &mut [u8]) {
- if buffer.is_empty() {
- return;
- }
- self.rng.get_mut().fill_bytes(buffer);
- }
- }
+ pub struct Random;
- /// Wrapper struct that holds a random generator using reference counting
- #[derive(Clone)]
- pub struct Random {
- rng: Lrc,
- }
+ const MSG: &str = "Using System.Random requires enabling the 'random' feature";
impl Random {
- /// Create a new Random instance without seeding
pub fn new() -> Self {
- Random {
- rng: Lrc::new(NonSeededRandom::new()) as Lrc,
- }
+ unimplemented!("{}", MSG)
}
- /// Create a new Random instance with a seed
pub fn new_seeded(seed: i32) -> Self {
- Random {
- rng: Lrc::new(SeededRandom::new(seed as u64)) as Lrc,
- }
+ unimplemented!("{}", MSG)
}
- /// Get the next random i32 from 0 to i32::MAX
pub fn next0(&self) -> i32 {
- self.rng.next0()
+ unimplemented!("{}", MSG)
}
- /// Get the next random i32 from 0 to maxValue (exclusive)
pub fn next1(&self, max_value: i32) -> i32 {
- self.rng.next1(max_value)
+ unimplemented!("{}", MSG)
}
- /// Get the next random i32 from minValue to maxValue (exclusive)
pub fn next2(&self, min_value: i32, max_value: i32) -> i32 {
- self.rng.next2(min_value, max_value)
+ unimplemented!("{}", MSG)
}
- /// Get the next random float from 0.0 to 1.0 (exclusive)
pub fn nextDouble(&self) -> f64 {
- self.rng.next_double()
+ unimplemented!("MSG")
}
- /// Fill a byte array with random bytes
pub fn nextBytes(&self, buffer: Array) {
- self.rng.next_bytes(buffer.get_mut().as_mut_slice());
- }
- }
-
- impl Default for Random {
- fn default() -> Self {
- Self::new()
- }
- }
-
- impl fmt::Debug for Random {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("Random").finish()
+ unimplemented!("{}", MSG)
}
}
- /// Create a new non-seeded Random instance (used by Fable-generated code)
- pub fn new() -> Lrc {
- Lrc::new(Random::new())
+ pub fn new() -> LrcPtr {
+ unimplemented!("{}", MSG)
}
- /// Create a new seeded Random instance (used by Fable-generated code)
- pub fn new_seeded(seed: i32) -> Lrc {
- Lrc::new(Random::new_seeded(seed))
+ pub fn new_seeded(seed: i32) -> LrcPtr {
+ unimplemented!("{}", MSG)
}
}
diff --git a/src/fable-library-rust/src/Seq.fs b/src/fable-library-rust/src/Seq.fs
index b33f6e0c96..b13941ce00 100644
--- a/src/fable-library-rust/src/Seq.fs
+++ b/src/fable-library-rust/src/Seq.fs
@@ -1263,13 +1263,14 @@ let distinctBy<'T, 'Key when 'Key: equality and 'Key: not null> (projection: 'T
)
let randomShuffleBy (randomizer: unit -> float) (xs: 'T seq) : 'T seq =
- xs |> toArray |> Array.randomShuffleBy randomizer |> ofArray
+ let arr = toArray xs
+ Array.randomShuffleInPlaceBy randomizer arr
+ ofArray arr
let randomShuffleWith (random: System.Random) (xs: 'T seq) : 'T seq =
- xs |> toArray |> Array.randomShuffleWith random |> ofArray
+ randomShuffleBy (fun () -> random.NextDouble()) xs
-let randomShuffle (xs: 'T seq) : 'T seq =
- xs |> toArray |> Array.randomShuffle |> ofArray
+let randomShuffle (xs: 'T seq) : 'T seq = randomShuffleWith (System.Random()) xs
let randomChoiceBy (randomizer: unit -> float) (xs: 'T seq) : 'T =
xs |> toArray |> Array.randomChoiceBy randomizer
@@ -1283,19 +1284,19 @@ let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T seq) : 'T s
xs |> toArray |> Array.randomChoicesBy randomizer count |> ofArray
let randomChoicesWith (random: System.Random) (count: int) (xs: 'T seq) : 'T seq =
- xs |> toArray |> Array.randomChoicesWith random count |> ofArray
+ randomChoicesBy (fun () -> random.NextDouble()) count xs
let randomChoices (count: int) (xs: 'T seq) : 'T seq =
- xs |> toArray |> Array.randomChoices count |> ofArray
+ randomChoicesWith (System.Random()) count xs
let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T seq) : 'T seq =
xs |> toArray |> Array.randomSampleBy randomizer count |> ofArray
let randomSampleWith (random: System.Random) (count: int) (xs: 'T seq) : 'T seq =
- xs |> toArray |> Array.randomSampleWith random count |> ofArray
+ randomSampleBy (fun () -> random.NextDouble()) count xs
let randomSample (count: int) (xs: 'T seq) : 'T seq =
- xs |> toArray |> Array.randomSample count |> ofArray
+ randomSampleWith (System.Random()) count xs
let except<'T when 'T: equality> (itemsToExclude: 'T seq) (xs: 'T seq) =
delay (fun () ->
diff --git a/src/fable-library-ts/Array.fs b/src/fable-library-ts/Array.fs
index 109b85fc6f..9632b11662 100644
--- a/src/fable-library-ts/Array.fs
+++ b/src/fable-library-ts/Array.fs
@@ -1171,6 +1171,124 @@ let insertManyAt (index: int) (ys: seq<'T>) (xs: 'T[]) ([ float) (xs: 'T[]) : unit =
+ let len = xs.Length
+
+ for i = len - 1 downto 1 do
+ let r = randomizer ()
+
+ if r < 0.0 || r >= 1.0 then
+ invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
+
+ let j = int (r * float (i + 1))
+ let tmp = xs[i]
+ xs[i] <- xs[j]
+ xs[j] <- tmp
+
+let randomShuffleInPlaceWith (random: System.Random) (xs: 'T[]) : unit =
+ randomShuffleInPlaceBy (fun () -> random.NextDouble()) xs
+
+let randomShuffleInPlace (xs: 'T[]) : unit =
+ randomShuffleInPlaceWith (System.Random()) xs
+
+let randomShuffleBy (randomizer: unit -> float) (xs: 'T[]) : 'T[] =
+ let arr = copy xs
+ randomShuffleInPlaceBy randomizer arr
+ arr
+
+let randomShuffleWith (random: System.Random) (xs: 'T[]) : 'T[] =
+ randomShuffleBy (fun () -> random.NextDouble()) xs
+
+let randomShuffle (xs: 'T[]) : 'T[] = randomShuffleWith (System.Random()) xs
+
+let randomChoiceBy (randomizer: unit -> float) (xs: 'T[]) : 'T =
+ if isEmpty xs then
+ invalidArg "source" SR.inputSequenceEmpty
+
+ let len = xs.Length
+ let r = randomizer ()
+
+ if r < 0.0 || r >= 1.0 then
+ invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
+
+ xs[int (r * float len)]
+
+let randomChoiceWith (random: System.Random) (xs: 'T[]) : 'T =
+ randomChoiceBy (fun () -> random.NextDouble()) xs
+
+let randomChoice (xs: 'T[]) : 'T = randomChoiceWith (System.Random()) xs
+
+let randomChoicesBy
+ (randomizer: unit -> float)
+ (count: int)
+ (xs: 'T[])
+ ([] cons: Cons<'T>)
+ : 'T[]
+ =
+ if count < 0 then
+ invalidArg "count" SR.inputMustBeNonNegative
+
+ if count > 0 && isEmpty xs then
+ invalidArg "source" SR.inputSequenceEmpty
+
+ let len = xs.Length
+
+ initialize
+ count
+ (fun _ ->
+ let r = randomizer ()
+
+ if r < 0.0 || r >= 1.0 then
+ invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
+
+ xs[int (r * float len)]
+ )
+ cons
+
+let randomChoicesWith
+ (random: System.Random)
+ (count: int)
+ (xs: 'T[])
+ ([] cons: Cons<'T>)
+ : 'T[]
+ =
+ randomChoicesBy (fun () -> random.NextDouble()) count xs cons
+
+let randomChoices (count: int) (xs: 'T[]) ([] cons: Cons<'T>) : 'T[] =
+ randomChoicesWith (System.Random()) count xs cons
+
+let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T[]) : 'T[] =
+ if count < 0 then
+ invalidArg "count" SR.inputMustBeNonNegative
+
+ let arr = copy xs
+ let len = arr.Length
+
+ if len = 0 && count > 0 then
+ invalidArg "source" SR.inputSequenceEmpty
+
+ if count > len then
+ invalidArg "count" SR.notEnoughElements
+
+ for i = 0 to count - 1 do
+ let r = randomizer ()
+
+ if r < 0.0 || r >= 1.0 then
+ invalidArg "randomizer" SR.Arg_ArgumentOutOfRangeException
+
+ let j = i + int (r * float (len - i))
+ let tmp = arr[i]
+ arr[i] <- arr[j]
+ arr[j] <- tmp
+
+ getSubArray arr 0 count
+
+let randomSampleWith (random: System.Random) (count: int) (xs: 'T[]) : 'T[] =
+ randomSampleBy (fun () -> random.NextDouble()) count xs
+
+let randomSample (count: int) (xs: 'T[]) : 'T[] =
+ randomSampleWith (System.Random()) count xs
+
let removeAt (index: int) (xs: 'T[]) : 'T[] =
if index < 0 || index >= xs.Length then
invalidArg "index" SR.indexOutOfBounds
diff --git a/src/fable-library-ts/List.fs b/src/fable-library-ts/List.fs
index 762ed98b62..38440743dd 100644
--- a/src/fable-library-ts/List.fs
+++ b/src/fable-library-ts/List.fs
@@ -1013,3 +1013,39 @@ let updateAt (index: int) (y: 'T) (xs: 'T list) : 'T list =
invalidArg "index" SR.indexOutOfBounds
ys
+
+let randomShuffleBy (randomizer: unit -> float) (xs: 'T list) : 'T list =
+ let arr = toArray xs
+ Array.randomShuffleInPlaceBy randomizer arr
+ ofArray arr
+
+let randomShuffleWith (random: System.Random) (xs: 'T list) : 'T list =
+ randomShuffleBy (fun () -> random.NextDouble()) xs
+
+let randomShuffle (xs: 'T list) : 'T list = randomShuffleWith (System.Random()) xs
+
+let randomChoiceBy (randomizer: unit -> float) (xs: 'T list) : 'T =
+ toArray xs |> Array.randomChoiceBy randomizer
+
+let randomChoiceWith (random: System.Random) (xs: 'T list) : 'T =
+ randomChoiceBy (fun () -> random.NextDouble()) xs
+
+let randomChoice (xs: 'T list) : 'T = randomChoiceWith (System.Random()) xs
+
+let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T list) : 'T list =
+ toArray xs |> Array.randomChoicesBy randomizer count |> ofArray
+
+let randomChoicesWith (random: System.Random) (count: int) (xs: 'T list) : 'T list =
+ randomChoicesBy (fun () -> random.NextDouble()) count xs
+
+let randomChoices (count: int) (xs: 'T list) : 'T list =
+ randomChoicesWith (System.Random()) count xs
+
+let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T list) : 'T list =
+ toArray xs |> Array.randomSampleBy randomizer count |> ofArray
+
+let randomSampleWith (random: System.Random) (count: int) (xs: 'T list) : 'T list =
+ randomSampleBy (fun () -> random.NextDouble()) count xs
+
+let randomSample (count: int) (xs: 'T list) : 'T list =
+ randomSampleWith (System.Random()) count xs
diff --git a/src/fable-library-ts/Seq.fs b/src/fable-library-ts/Seq.fs
index d0cef1a29c..59d9839fcc 100644
--- a/src/fable-library-ts/Seq.fs
+++ b/src/fable-library-ts/Seq.fs
@@ -1293,3 +1293,39 @@ let updateAt (index: int) (y: 'T) (xs: seq<'T>) : seq<'T> =
None
)
(fun e -> e.Dispose())
+
+let randomShuffleBy (randomizer: unit -> float) (xs: 'T seq) : 'T seq =
+ let arr = toArray xs
+ Array.randomShuffleInPlaceBy randomizer arr
+ ofArray arr
+
+let randomShuffleWith (random: System.Random) (xs: 'T seq) : 'T seq =
+ randomShuffleBy (fun () -> random.NextDouble()) xs
+
+let randomShuffle (xs: 'T seq) : 'T seq = randomShuffleWith (System.Random()) xs
+
+let randomChoiceBy (randomizer: unit -> float) (xs: 'T seq) : 'T =
+ xs |> toArray |> Array.randomChoiceBy randomizer
+
+let randomChoiceWith (random: System.Random) (xs: 'T seq) : 'T =
+ xs |> toArray |> Array.randomChoiceWith random
+
+let randomChoice (xs: 'T seq) : 'T = xs |> toArray |> Array.randomChoice
+
+let randomChoicesBy (randomizer: unit -> float) (count: int) (xs: 'T seq) : 'T seq =
+ xs |> toArray |> Array.randomChoicesBy randomizer count |> ofArray
+
+let randomChoicesWith (random: System.Random) (count: int) (xs: 'T seq) : 'T seq =
+ randomChoicesBy (fun () -> random.NextDouble()) count xs
+
+let randomChoices (count: int) (xs: 'T seq) : 'T seq =
+ randomChoicesWith (System.Random()) count xs
+
+let randomSampleBy (randomizer: unit -> float) (count: int) (xs: 'T seq) : 'T seq =
+ xs |> toArray |> Array.randomSampleBy randomizer count |> ofArray
+
+let randomSampleWith (random: System.Random) (count: int) (xs: 'T seq) : 'T seq =
+ randomSampleBy (fun () -> random.NextDouble()) count xs
+
+let randomSample (count: int) (xs: 'T seq) : 'T seq =
+ randomSampleWith (System.Random()) count xs
diff --git a/tests/Beam/ArithmeticTests.fs b/tests/Beam/ArithmeticTests.fs
index c80e263e62..055d9f2635 100644
--- a/tests/Beam/ArithmeticTests.fs
+++ b/tests/Beam/ArithmeticTests.fs
@@ -645,22 +645,6 @@ let ``test ref cell basic operations work`` () =
x := 30
equal 30 !x
-[]
-let ``test System.Random works`` () =
- let rnd = Random()
- let x = rnd.Next()
- x >= 0 |> equal true
- let x = rnd.Next(5)
- (x >= 0 && x < 5) |> equal true
- let x = rnd.Next(14, 20)
- (x >= 14 && x < 20) |> equal true
- let x = rnd.Next(-14, -10)
- (x >= -14 && x < -10) |> equal true
- let x = rnd.NextDouble()
- (x >= 0.0 && x < 1.0) |> equal true
- throwsAnyError <| fun () -> rnd.Next(-10)
- throwsAnyError <| fun () -> rnd.Next(14, 10)
-
// --- More tests ported from Python ---
[]
@@ -1005,48 +989,6 @@ let ``test Decimal floor works`` () =
let ``test Decimal pown works`` () =
pown 2.2M 3 |> equal 10.648M
-// --- Random seeded tests ---
-
-[]
-let ``test System.Random seeded is deterministic`` () =
- let rnd1 = Random(1234)
- let a1 = rnd1.Next()
- let a2 = rnd1.Next(100)
- let a3 = rnd1.Next(1000, 10000)
- let a4 = rnd1.NextDouble()
- let rnd2 = Random(1234)
- let b1 = rnd2.Next()
- let b2 = rnd2.Next(100)
- let b3 = rnd2.Next(1000, 10000)
- let b4 = rnd2.NextDouble()
- a1 |> equal b1
- a2 |> equal b2
- a3 |> equal b3
- a4 |> equal b4
-
-[]
-let ``test System.Random seeded validates arguments`` () =
- let rnd = Random(42)
- throwsAnyError <| fun () -> rnd.Next(-10)
- throwsAnyError <| fun () -> rnd.Next(14, 10)
-
-[]
-let ``test System.Random.NextBytes works`` () =
- let buffer = Array.create 16 0uy
- Random().NextBytes(buffer)
- buffer.Length |> equal 16
- buffer = Array.create 16 0uy |> equal false
-
-[]
-let ``test System.Random.NextBytes seeded is deterministic`` () =
- let buffer1 = Array.create 4 0uy
- let rnd1 = Random(5432)
- rnd1.NextBytes(buffer1)
- let buffer2 = Array.create 4 0uy
- let rnd2 = Random(5432)
- rnd2.NextBytes(buffer2)
- buffer1 |> equal buffer2
-
// TODO: Decimal constructors from GetBits require low/mid/high int32 representation
// which doesn't map to the fixed-scale integer approach used in Beam.
// []
diff --git a/tests/Beam/ArrayTests.fs b/tests/Beam/ArrayTests.fs
index 1cde0e2acc..8a364c45a7 100644
--- a/tests/Beam/ArrayTests.fs
+++ b/tests/Beam/ArrayTests.fs
@@ -723,6 +723,124 @@ let ``test Array.removeManyAt works`` () =
equal [|1; 2; 5|] (Array.removeManyAt 2 2 [|1..5|])
equal [|1; 2; 3|] (Array.removeManyAt 3 2 [|1..5|])
+[]
+let ``test Array.randomShuffle works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let shuffled = Array.randomShuffle xs
+ Array.sort shuffled |> equal xs
+ Array.length shuffled |> equal 5
+
+[]
+let ``test Array.randomShuffleWith works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ let shuffled = Array.randomShuffleWith rng xs
+ Array.sort shuffled |> equal xs
+ Array.length shuffled |> equal 5
+
+[]
+let ``test Array.randomShuffleBy works`` () =
+ let shuffled = Array.randomShuffleBy (fun () -> 0.0) [|1; 2; 3; 4; 5|]
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Array.randomShuffleBy (fun () -> 1.0) [|1; 2|] |> ignore)
+ throwsAnyError (fun () -> Array.randomShuffleBy (fun () -> -0.1) [|1; 2|] |> ignore)
+
+[]
+let ``test Array.randomShuffleInPlace works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ Array.randomShuffleInPlace xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+
+[]
+let ``test Array.randomShuffleInPlaceWith works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ Array.randomShuffleInPlaceWith rng xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+
+[]
+let ``test Array.randomShuffleInPlaceBy works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ Array.randomShuffleInPlaceBy (fun () -> 0.0) xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Array.randomShuffleInPlaceBy (fun () -> 1.0) [|1; 2|])
+ throwsAnyError (fun () -> Array.randomShuffleInPlaceBy (fun () -> -0.1) [|1; 2|])
+
+[]
+let ``test Array.randomChoice works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let x = Array.randomChoice xs
+ Array.contains x xs |> equal true
+ throwsAnyError (fun () -> Array.randomChoice [||] |> ignore)
+
+[]
+let ``test Array.randomChoiceWith works`` () =
+ let xs = [|10; 20; 30|]
+ let rng = System.Random(42)
+ let x = Array.randomChoiceWith rng xs
+ Array.contains x xs |> equal true
+
+[]
+let ``test Array.randomChoiceBy works`` () =
+ let x = Array.randomChoiceBy (fun () -> 0.0) [|42; 99; 7|]
+ x |> equal 42
+ throwsAnyError (fun () -> Array.randomChoiceBy (fun () -> 1.0) [|1|] |> ignore)
+ throwsAnyError (fun () -> Array.randomChoiceBy (fun () -> 0.5) [||] |> ignore)
+
+[]
+let ``test Array.randomChoices works`` () =
+ let xs = [|1; 2; 3|]
+ let choices = Array.randomChoices 5 xs
+ Array.length choices |> equal 5
+ choices |> Array.forall (fun x -> Array.contains x xs) |> equal true
+ Array.randomChoices 0 xs |> equal [||]
+ throwsAnyError (fun () -> Array.randomChoices -1 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomChoices 1 [||] |> ignore)
+
+[]
+let ``test Array.randomChoicesWith works`` () =
+ let xs = [|10; 20; 30|]
+ let rng = System.Random(42)
+ let choices = Array.randomChoicesWith rng 4 xs
+ Array.length choices |> equal 4
+ choices |> Array.forall (fun x -> Array.contains x xs) |> equal true
+
+[]
+let ``test Array.randomChoicesBy works`` () =
+ let choices = Array.randomChoicesBy (fun () -> 0.5) 3 [|10; 20; 30|]
+ Array.length choices |> equal 3
+ choices |> Array.forall (fun x -> x = 20) |> equal true
+
+[]
+let ``test Array.randomSample works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let sample = Array.randomSample 3 xs
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Array.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+ Array.randomSample 0 xs |> equal [||]
+ Array.randomSample 5 xs |> Array.sort |> equal xs
+ throwsAnyError (fun () -> Array.randomSample -1 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomSample 6 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomSample 1 [||] |> ignore)
+
+[]
+let ``test Array.randomSampleWith works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ let sample = Array.randomSampleWith rng 3 xs
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Array.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+
+[]
+let ``test Array.randomSampleBy works`` () =
+ let sample = Array.randomSampleBy (fun () -> 0.0) 3 [|1; 2; 3; 4; 5|]
+ Array.length sample |> equal 3
+ Array.distinct sample |> Array.length |> equal 3
+ throwsAnyError (fun () -> Array.randomSampleBy (fun () -> 1.0) 1 [|1; 2|] |> ignore)
+ throwsAnyError (fun () -> Array.randomSampleBy (fun () -> -0.1) 1 [|1; 2|] |> ignore)
+
// --- CompareWith ---
[]
diff --git a/tests/Beam/Fable.Tests.Beam.fsproj b/tests/Beam/Fable.Tests.Beam.fsproj
index 0f098565f7..5f6a5c1885 100644
--- a/tests/Beam/Fable.Tests.Beam.fsproj
+++ b/tests/Beam/Fable.Tests.Beam.fsproj
@@ -29,6 +29,7 @@
+
diff --git a/tests/Beam/ListTests.fs b/tests/Beam/ListTests.fs
index a0310c62bf..6010ff27c5 100644
--- a/tests/Beam/ListTests.fs
+++ b/tests/Beam/ListTests.fs
@@ -580,6 +580,103 @@ let ``test List.removeManyAt works`` () =
equal [1; 2; 5] (List.removeManyAt 2 2 [1..5])
equal [1; 2; 3] (List.removeManyAt 3 2 [1..5])
+[]
+let ``test List.randomShuffle works`` () =
+ let xs = [1; 2; 3; 4; 5]
+ let shuffled = List.randomShuffle xs
+ List.sort shuffled |> equal xs
+ List.length shuffled |> equal 5
+
+[]
+let ``test List.randomShuffleWith works`` () =
+ let xs = [1; 2; 3; 4; 5]
+ let rng = System.Random(42)
+ let shuffled = List.randomShuffleWith rng xs
+ List.sort shuffled |> equal xs
+ List.length shuffled |> equal 5
+
+[]
+let ``test List.randomShuffleBy works`` () =
+ let shuffled = List.randomShuffleBy (fun () -> 0.0) [1; 2; 3; 4; 5]
+ List.sort shuffled |> equal [1; 2; 3; 4; 5]
+ throwsAnyError (fun () -> List.randomShuffleBy (fun () -> 1.0) [1; 2] |> ignore)
+ throwsAnyError (fun () -> List.randomShuffleBy (fun () -> -0.1) [1; 2] |> ignore)
+
+[]
+let ``test List.randomChoice works`` () =
+ let xs = [1; 2; 3; 4; 5]
+ let x = List.randomChoice xs
+ List.contains x xs |> equal true
+ throwsAnyError (fun () -> List.randomChoice [] |> ignore)
+
+[]
+let ``test List.randomChoiceWith works`` () =
+ let xs = [10; 20; 30]
+ let rng = System.Random(42)
+ let x = List.randomChoiceWith rng xs
+ List.contains x xs |> equal true
+
+[]
+let ``test List.randomChoiceBy works`` () =
+ let x = List.randomChoiceBy (fun () -> 0.0) [42; 99; 7]
+ x |> equal 42
+ throwsAnyError (fun () -> List.randomChoiceBy (fun () -> 1.0) [1] |> ignore)
+ throwsAnyError (fun () -> List.randomChoiceBy (fun () -> 0.5) [] |> ignore)
+
+[]
+let ``test List.randomChoices works`` () =
+ let xs = [1; 2; 3]
+ let choices = List.randomChoices 5 xs
+ List.length choices |> equal 5
+ choices |> List.forall (fun x -> List.contains x xs) |> equal true
+ List.randomChoices 0 xs |> equal []
+ throwsAnyError (fun () -> List.randomChoices -1 xs |> ignore)
+ throwsAnyError (fun () -> List.randomChoices 1 [] |> ignore)
+
+[]
+let ``test List.randomChoicesWith works`` () =
+ let xs = [10; 20; 30]
+ let rng = System.Random(42)
+ let choices = List.randomChoicesWith rng 4 xs
+ List.length choices |> equal 4
+ choices |> List.forall (fun x -> List.contains x xs) |> equal true
+
+[]
+let ``test List.randomChoicesBy works`` () =
+ let choices = List.randomChoicesBy (fun () -> 0.5) 3 [10; 20; 30]
+ List.length choices |> equal 3
+ choices |> List.forall (fun x -> x = 20) |> equal true
+
+[]
+let ``test List.randomSample works`` () =
+ let xs = [1; 2; 3; 4; 5]
+ let sample = List.randomSample 3 xs
+ List.length sample |> equal 3
+ sample |> List.forall (fun x -> List.contains x xs) |> equal true
+ List.distinct sample |> List.length |> equal 3
+ List.randomSample 0 xs |> equal []
+ List.randomSample 5 xs |> List.sort |> equal xs
+ throwsAnyError (fun () -> List.randomSample -1 xs |> ignore)
+ throwsAnyError (fun () -> List.randomSample 6 xs |> ignore)
+ throwsAnyError (fun () -> List.randomSample 1 [] |> ignore)
+
+[]
+let ``test List.randomSampleWith works`` () =
+ let xs = [1; 2; 3; 4; 5]
+ let rng = System.Random(42)
+ let sample = List.randomSampleWith rng 3 xs
+ List.length sample |> equal 3
+ sample |> List.forall (fun x -> List.contains x xs) |> equal true
+ List.distinct sample |> List.length |> equal 3
+
+[]
+let ``test List.randomSampleBy works`` () =
+ let sample = List.randomSampleBy (fun () -> 0.0) 3 [1; 2; 3; 4; 5]
+ List.length sample |> equal 3
+ List.distinct sample |> List.length |> equal 3
+ throwsAnyError (fun () -> List.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> ignore)
+ throwsAnyError (fun () -> List.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> ignore)
+
[]
let ``test List.updateAt works`` () =
equal [0; 2; 3; 4; 5] (List.updateAt 0 0 [1..5])
diff --git a/tests/Beam/RandomTests.fs b/tests/Beam/RandomTests.fs
new file mode 100644
index 0000000000..06eeacfe95
--- /dev/null
+++ b/tests/Beam/RandomTests.fs
@@ -0,0 +1,114 @@
+module Fable.Tests.RandomTests
+
+open System
+open Fable.Tests.Util
+open Util.Testing
+
+let chiSquaredStatistic bucketCount sampleCount (nextBucket: unit -> int) =
+ let counts = Array.zeroCreate bucketCount
+
+ for _ in 1 .. sampleCount do
+ let bucket = nextBucket()
+ counts[bucket] <- counts[bucket] + 1
+
+ let expected = float sampleCount / float bucketCount
+
+ counts
+ |> Array.sumBy (fun count ->
+ let diff = float count - expected
+ diff * diff / expected)
+
+let chiSquaredUpperBound degreesOfFreedom sigma =
+ float degreesOfFreedom + sigma * Math.Sqrt(2.0 * float degreesOfFreedom)
+
+let runsZScore sampleCount (nextBit: unit -> bool) =
+ let mutable ones = 0
+ let mutable zeros = 0
+ let mutable runs = 0
+ let mutable hasPrevious = false
+ let mutable previous = false
+
+ for _ in 1 .. sampleCount do
+ let current = nextBit()
+
+ if current then
+ ones <- ones + 1
+ else
+ zeros <- zeros + 1
+
+ if not hasPrevious || current <> previous then
+ runs <- runs + 1
+ previous <- current
+ hasPrevious <- true
+
+ if ones = 0 || zeros = 0 then
+ Double.PositiveInfinity
+ else
+ let ones' = float ones
+ let zeros' = float zeros
+ let total = float sampleCount
+ let expectedRuns = 1.0 + 2.0 * ones' * zeros' / total
+
+ let variance =
+ (2.0 * ones' * zeros' * (2.0 * ones' * zeros' - ones' - zeros'))
+ / (total * total * (total - 1.0))
+
+ Math.Abs(float runs - expectedRuns) / Math.Sqrt(variance)
+
+[]
+let ``test System.Random works`` () =
+ let rnd = Random()
+ let x = rnd.Next()
+ (x >= 0 && x < 0x7fffffff) |> equal true
+ rnd.Next(0) |> equal 0
+ let x = rnd.Next(5)
+ (x >= 0 && x < 5) |> equal true
+ let x = rnd.Next(14, 20)
+ (x >= 14 && x < 20) |> equal true
+ let x = rnd.Next(-14, -10)
+ (x >= -14 && x < -10) |> equal true
+ let x = rnd.NextDouble()
+ (x >= 0.0 && x < 1.0) |> equal true
+
+[]
+let ``test System.Random seeded works`` () =
+ let rnd1 = Random(1234)
+ let rnd2 = Random(1234)
+ rnd1.Next() |> equal (rnd2.Next())
+ rnd1.Next(100) |> equal (rnd2.Next(100))
+ rnd1.Next(1000, 10000) |> equal (rnd2.Next(1000, 10000))
+ rnd1.NextDouble() |> equal (rnd2.NextDouble())
+
+[]
+let ``test System.Random seeded validates arguments`` () =
+ let rnd = Random(42)
+ throwsAnyError <| fun () -> rnd.Next(-10)
+ throwsAnyError <| fun () -> rnd.Next(14, 10)
+ // throwsAnyError <| fun () -> rnd.NextBytes(null)
+
+[]
+let ``test System.Random seeded passes chi-squared test`` () =
+ let rnd = Random(2026)
+ let statistic = chiSquaredStatistic 16 10000 (fun () -> rnd.Next(16))
+ statistic < chiSquaredUpperBound 15 4.0 |> equal true
+
+[]
+let ``test System.Random seeded passes Wald-Wolfowitz runs test`` () =
+ let rnd = Random(2027)
+ let zScore = runsZScore 10000 (fun () -> rnd.NextDouble() >= 0.5)
+ zScore < 4.0 |> equal true
+
+[]
+let ``test System.Random.NextBytes works`` () =
+ let buffer = Array.create 16 0uy
+ Random().NextBytes(buffer)
+ buffer.Length |> equal 16
+ buffer = Array.create 16 0uy |> equal false
+
+[]
+let ``test System.Random.NextBytes seeded works`` () =
+ let buffer1 = Array.create 4 0uy
+ let buffer2 = Array.create 4 0uy
+ Random(5432).NextBytes(buffer1)
+ Random(5432).NextBytes(buffer2)
+ buffer1 |> equal buffer2
diff --git a/tests/Beam/SeqTests.fs b/tests/Beam/SeqTests.fs
index ac5a08b4ad..8834b9f0e9 100644
--- a/tests/Beam/SeqTests.fs
+++ b/tests/Beam/SeqTests.fs
@@ -514,6 +514,103 @@ let ``test Seq.removeManyAt works`` () =
|> Seq.toList
|> equal [1; 4; 5]
+[]
+let ``test Seq.randomShuffle works`` () =
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let shuffled = Seq.randomShuffle xs |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ Array.length shuffled |> equal 5
+
+[]
+let ``test Seq.randomShuffleWith works`` () =
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let rng = System.Random(42)
+ let shuffled = Seq.randomShuffleWith rng xs |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ Array.length shuffled |> equal 5
+
+[]
+let ``test Seq.randomShuffleBy works`` () =
+ let shuffled = Seq.randomShuffleBy (fun () -> 0.0) [1; 2; 3; 4; 5] |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Seq.randomShuffleBy (fun () -> 1.0) [1; 2] |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomShuffleBy (fun () -> -0.1) [1; 2] |> Seq.toArray |> ignore)
+
+[]
+let ``test Seq.randomChoice works`` () =
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let x = Seq.randomChoice xs
+ Seq.contains x xs |> equal true
+ throwsAnyError (fun () -> Seq.randomChoice Seq.empty |> ignore)
+
+[]
+let ``test Seq.randomChoiceWith works`` () =
+ let xs = seq { 10; 20; 30 }
+ let rng = System.Random(42)
+ let x = Seq.randomChoiceWith rng xs
+ Seq.contains x xs |> equal true
+
+[]
+let ``test Seq.randomChoiceBy works`` () =
+ let x = Seq.randomChoiceBy (fun () -> 0.0) [42; 99; 7]
+ x |> equal 42
+ throwsAnyError (fun () -> Seq.randomChoiceBy (fun () -> 1.0) [1] |> ignore)
+ throwsAnyError (fun () -> Seq.randomChoiceBy (fun () -> 0.5) Seq.empty |> ignore)
+
+[]
+let ``test Seq.randomChoices works`` () =
+ let xs = seq { 1; 2; 3 }
+ let choices = Seq.randomChoices 5 xs |> Seq.toArray
+ Array.length choices |> equal 5
+ choices |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+ Seq.randomChoices 0 xs |> Seq.toArray |> equal [||]
+ throwsAnyError (fun () -> Seq.randomChoices -1 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomChoices 1 Seq.empty |> Seq.toArray |> ignore)
+
+[]
+let ``test Seq.randomChoicesWith works`` () =
+ let xs = seq { 10; 20; 30 }
+ let rng = System.Random(42)
+ let choices = Seq.randomChoicesWith rng 4 xs |> Seq.toArray
+ Array.length choices |> equal 4
+ choices |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+
+[]
+let ``test Seq.randomChoicesBy works`` () =
+ let choices = Seq.randomChoicesBy (fun () -> 0.5) 3 [10; 20; 30] |> Seq.toArray
+ Array.length choices |> equal 3
+ choices |> Array.forall (fun x -> x = 20) |> equal true
+
+[]
+let ``test Seq.randomSample works`` () =
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let sample = Seq.randomSample 3 xs |> Seq.toArray
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+ Seq.randomSample 0 xs |> Seq.toArray |> equal [||]
+ Seq.randomSample 5 xs |> Seq.toArray |> Array.sort |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Seq.randomSample -1 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSample 6 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSample 1 Seq.empty |> Seq.toArray |> ignore)
+
+[]
+let ``test Seq.randomSampleWith works`` () =
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let rng = System.Random(42)
+ let sample = Seq.randomSampleWith rng 3 xs |> Seq.toArray
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+
+[]
+let ``test Seq.randomSampleBy works`` () =
+ let sample = Seq.randomSampleBy (fun () -> 0.0) 3 [1; 2; 3; 4; 5] |> Seq.toArray
+ Array.length sample |> equal 3
+ Array.distinct sample |> Array.length |> equal 3
+ throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> Seq.toArray |> ignore)
+
[]
let ``test Seq.average works`` () =
[1.0; 2.0; 3.0] |> Seq.average |> equal 2.0
diff --git a/tests/Dart/main.dart b/tests/Dart/main.dart
index 78cb2f2744..46faa232c2 100644
--- a/tests/Dart/main.dart
+++ b/tests/Dart/main.dart
@@ -1,4 +1,5 @@
import './src/ArithmeticTests.dart' as arithmetic;
+import './src/RandomTests.dart' as random;
import './src/ArrayTests.dart' as array;
import './src/ComparisonTests.dart' as comparison;
import './src/CustomOperatorTests.dart' as custom_operator;
@@ -30,6 +31,7 @@ import './src/UnionTests.dart' as union;
void main() {
arithmetic.tests();
+ random.tests();
array.tests();
comparison.tests();
custom_operator.tests();
diff --git a/tests/Dart/src/ArithmeticTests.fs b/tests/Dart/src/ArithmeticTests.fs
index 7ea94e79f6..4aaaa5b65a 100644
--- a/tests/Dart/src/ArithmeticTests.fs
+++ b/tests/Dart/src/ArithmeticTests.fs
@@ -753,47 +753,6 @@ let tests () =
decr i
!i |> equal 4
- (*
- // Random coverage remains gated for Dart.
- testCase "System.Random works" <| fun () ->
- let rnd = Random()
- let x = rnd.Next()
- x >= 0 |> equal true
- let x = rnd.Next(5)
- (x >= 0 && x < 5) |> equal true
- let x = rnd.Next(14, 20)
- (x >= 14 && x < 20) |> equal true
- let x = rnd.Next(-14, -10)
- (x >= -14 && x < -10) |> equal true
- let x = rnd.NextDouble()
- (x >= 0.0 && x < 1.0) |> equal true
- throwsAnyError <| fun () -> rnd.Next(-10)
- throwsAnyError <| fun () -> rnd.Next(14, 10)
-
- // Note: Test could fail sometime during life of universe, if it picks all zeroes.
- testCase "System.Random.NextBytes works" <| fun () ->
- let buffer = Array.create 16 0uy // guid-sized buffer
- Random().NextBytes(buffer)
- buffer.Length |> equal 16
- buffer = Array.create 16 0uy |> equal false
- throwsAnyError <| fun () -> Random().NextBytes(null)
-
- testCase "System.Random seeded works" <| fun () ->
- let rnd = Random(1234)
- rnd.Next() |> equal 857019877
- rnd.Next(100) |> equal 89
- rnd.Next(1000, 10000) |> equal 3872
- rnd.NextDouble() |> equal 0.9467375338760845
- throwsAnyError <| fun () -> rnd.Next(-10)
- throwsAnyError <| fun () -> rnd.Next(14, 10)
-
- testCase "System.Random.NextBytes seeded works" <| fun () ->
- let buffer = Array.create 4 0uy // guid-sized buffer
- Random(5432).NextBytes(buffer)
- buffer |> equal [|152uy; 238uy; 227uy; 30uy|]
- throwsAnyError <| fun () -> Random().NextBytes(null)
- *)
-
testCase "Long integers equality works" <| fun () ->
let x = 5L
let y = 5L
diff --git a/tests/Dart/src/ArrayTests.fs b/tests/Dart/src/ArrayTests.fs
index 38407633de..8adf876651 100644
--- a/tests/Dart/src/ArrayTests.fs
+++ b/tests/Dart/src/ArrayTests.fs
@@ -1102,6 +1102,109 @@ let tests () =
throwsAnyError (fun () -> Array.removeManyAt -1 2 [|1|] |> ignore)
throwsAnyError (fun () -> Array.removeManyAt 2 2 [|1|] |> ignore)
+ testCase "Array.randomShuffle works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let shuffled = Array.randomShuffle xs
+ Array.sort shuffled |> equal xs
+ Array.length shuffled |> equal 5
+
+ testCase "Array.randomShuffleWith works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ let shuffled = Array.randomShuffleWith rng xs
+ Array.sort shuffled |> equal xs
+ Array.length shuffled |> equal 5
+
+ testCase "Array.randomShuffleBy works" <| fun () ->
+ let shuffled = Array.randomShuffleBy (fun () -> 0.0) [|1; 2; 3; 4; 5|]
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Array.randomShuffleBy (fun () -> 1.0) [|1; 2|] |> ignore)
+ throwsAnyError (fun () -> Array.randomShuffleBy (fun () -> -0.1) [|1; 2|] |> ignore)
+
+ testCase "Array.randomShuffleInPlace works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ Array.randomShuffleInPlace xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+
+ testCase "Array.randomShuffleInPlaceWith works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ Array.randomShuffleInPlaceWith rng xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+
+ testCase "Array.randomShuffleInPlaceBy works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ Array.randomShuffleInPlaceBy (fun () -> 0.0) xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Array.randomShuffleInPlaceBy (fun () -> 1.0) [|1; 2|])
+ throwsAnyError (fun () -> Array.randomShuffleInPlaceBy (fun () -> -0.1) [|1; 2|])
+
+ testCase "Array.randomChoice works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let x = Array.randomChoice xs
+ Array.exists ((=) x) xs |> equal true
+ throwsAnyError (fun () -> Array.randomChoice [||] |> ignore)
+
+ testCase "Array.randomChoiceWith works" <| fun () ->
+ let xs = [|10; 20; 30|]
+ let rng = System.Random(42)
+ let x = Array.randomChoiceWith rng xs
+ Array.exists ((=) x) xs |> equal true
+
+ testCase "Array.randomChoiceBy works" <| fun () ->
+ let x = Array.randomChoiceBy (fun () -> 0.0) [|42; 99; 7|]
+ x |> equal 42
+ throwsAnyError (fun () -> Array.randomChoiceBy (fun () -> 1.0) [|1|] |> ignore)
+ throwsAnyError (fun () -> Array.randomChoiceBy (fun () -> 0.5) [||] |> ignore)
+
+ testCase "Array.randomChoices works" <| fun () ->
+ let xs = [|1; 2; 3|]
+ let choices = Array.randomChoices 5 xs
+ Array.length choices |> equal 5
+ choices |> Array.forall (fun x -> Array.exists ((=) x) xs) |> equal true
+ Array.randomChoices 0 xs |> equal [||]
+ throwsAnyError (fun () -> Array.randomChoices -1 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomChoices 1 [||] |> ignore)
+
+ testCase "Array.randomChoicesWith works" <| fun () ->
+ let xs = [|10; 20; 30|]
+ let rng = System.Random(42)
+ let choices = Array.randomChoicesWith rng 4 xs
+ Array.length choices |> equal 4
+ choices |> Array.forall (fun x -> Array.exists ((=) x) xs) |> equal true
+
+ testCase "Array.randomChoicesBy works" <| fun () ->
+ let choices = Array.randomChoicesBy (fun () -> 0.5) 3 [|10; 20; 30|]
+ Array.length choices |> equal 3
+ choices |> Array.forall (fun x -> x = 20) |> equal true
+
+ testCase "Array.randomSample works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let sample = Array.randomSample 3 xs
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Array.exists ((=) x) xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+ Array.randomSample 0 xs |> equal [||]
+ Array.randomSample 5 xs |> Array.sort |> equal xs
+ throwsAnyError (fun () -> Array.randomSample -1 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomSample 6 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomSample 1 [||] |> ignore)
+
+ testCase "Array.randomSampleWith works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ let sample = Array.randomSampleWith rng 3 xs
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Array.exists ((=) x) xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+
+ testCase "Array.randomSampleBy works" <| fun () ->
+ let sample = Array.randomSampleBy (fun () -> 0.0) 3 [|1; 2; 3; 4; 5|]
+ Array.length sample |> equal 3
+ Array.distinct sample |> Array.length |> equal 3
+ throwsAnyError (fun () -> Array.randomSampleBy (fun () -> 1.0) 1 [|1; 2|] |> ignore)
+ throwsAnyError (fun () -> Array.randomSampleBy (fun () -> -0.1) 1 [|1; 2|] |> ignore)
+
testCase "Array.compareWith works" <| fun () -> // See #2961
let a = [|1;3|]
let b = [|1;2;3|]
diff --git a/tests/Dart/src/Fable.Tests.Dart.fsproj b/tests/Dart/src/Fable.Tests.Dart.fsproj
index dc723b460e..38de08823f 100644
--- a/tests/Dart/src/Fable.Tests.Dart.fsproj
+++ b/tests/Dart/src/Fable.Tests.Dart.fsproj
@@ -9,6 +9,7 @@
+
diff --git a/tests/Dart/src/ListTests.fs b/tests/Dart/src/ListTests.fs
index feffd85015..58cd87c5df 100644
--- a/tests/Dart/src/ListTests.fs
+++ b/tests/Dart/src/ListTests.fs
@@ -978,3 +978,88 @@ let tests() =
throwsAnyError (fun () -> List.removeManyAt 0 2 [] |> ignore)
throwsAnyError (fun () -> List.removeManyAt -1 2 [1] |> ignore)
throwsAnyError (fun () -> List.removeManyAt 2 2 [1] |> ignore)
+
+ testCase "List.randomShuffle works" <| fun () ->
+ let xs = [1; 2; 3; 4; 5]
+ let shuffled = List.randomShuffle xs
+ List.sort shuffled |> equal xs
+ List.length shuffled |> equal 5
+
+ testCase "List.randomShuffleWith works" <| fun () ->
+ let xs = [1; 2; 3; 4; 5]
+ let rng = System.Random(42)
+ let shuffled = List.randomShuffleWith rng xs
+ List.sort shuffled |> equal xs
+ List.length shuffled |> equal 5
+
+ testCase "List.randomShuffleBy works" <| fun () ->
+ let shuffled = List.randomShuffleBy (fun () -> 0.0) [1; 2; 3; 4; 5]
+ List.sort shuffled |> equal [1; 2; 3; 4; 5]
+ throwsAnyError (fun () -> List.randomShuffleBy (fun () -> 1.0) [1; 2] |> ignore)
+ throwsAnyError (fun () -> List.randomShuffleBy (fun () -> -0.1) [1; 2] |> ignore)
+
+ testCase "List.randomChoice works" <| fun () ->
+ let xs = [1; 2; 3; 4; 5]
+ let x = List.randomChoice xs
+ List.exists ((=) x) xs |> equal true
+ throwsAnyError (fun () -> List.randomChoice [] |> ignore)
+
+ testCase "List.randomChoiceWith works" <| fun () ->
+ let xs = [10; 20; 30]
+ let rng = System.Random(42)
+ let x = List.randomChoiceWith rng xs
+ List.exists ((=) x) xs |> equal true
+
+ testCase "List.randomChoiceBy works" <| fun () ->
+ let x = List.randomChoiceBy (fun () -> 0.0) [42; 99; 7]
+ x |> equal 42
+ throwsAnyError (fun () -> List.randomChoiceBy (fun () -> 1.0) [1] |> ignore)
+ throwsAnyError (fun () -> List.randomChoiceBy (fun () -> 0.5) [] |> ignore)
+
+ testCase "List.randomChoices works" <| fun () ->
+ let xs = [1; 2; 3]
+ let choices = List.randomChoices 5 xs
+ List.length choices |> equal 5
+ choices |> List.forall (fun x -> List.exists ((=) x) xs) |> equal true
+ List.randomChoices 0 xs |> equal []
+ throwsAnyError (fun () -> List.randomChoices -1 xs |> ignore)
+ throwsAnyError (fun () -> List.randomChoices 1 [] |> ignore)
+
+ testCase "List.randomChoicesWith works" <| fun () ->
+ let xs = [10; 20; 30]
+ let rng = System.Random(42)
+ let choices = List.randomChoicesWith rng 4 xs
+ List.length choices |> equal 4
+ choices |> List.forall (fun x -> List.exists ((=) x) xs) |> equal true
+
+ testCase "List.randomChoicesBy works" <| fun () ->
+ let choices = List.randomChoicesBy (fun () -> 0.5) 3 [10; 20; 30]
+ List.length choices |> equal 3
+ choices |> List.forall (fun x -> x = 20) |> equal true
+
+ testCase "List.randomSample works" <| fun () ->
+ let xs = [1; 2; 3; 4; 5]
+ let sample = List.randomSample 3 xs
+ List.length sample |> equal 3
+ sample |> List.forall (fun x -> List.exists ((=) x) xs) |> equal true
+ List.distinct sample |> List.length |> equal 3
+ List.randomSample 0 xs |> equal []
+ List.randomSample 5 xs |> List.sort |> equal xs
+ throwsAnyError (fun () -> List.randomSample -1 xs |> ignore)
+ throwsAnyError (fun () -> List.randomSample 6 xs |> ignore)
+ throwsAnyError (fun () -> List.randomSample 1 [] |> ignore)
+
+ testCase "List.randomSampleWith works" <| fun () ->
+ let xs = [1; 2; 3; 4; 5]
+ let rng = System.Random(42)
+ let sample = List.randomSampleWith rng 3 xs
+ List.length sample |> equal 3
+ sample |> List.forall (fun x -> List.exists ((=) x) xs) |> equal true
+ List.distinct sample |> List.length |> equal 3
+
+ testCase "List.randomSampleBy works" <| fun () ->
+ let sample = List.randomSampleBy (fun () -> 0.0) 3 [1; 2; 3; 4; 5]
+ List.length sample |> equal 3
+ List.distinct sample |> List.length |> equal 3
+ throwsAnyError (fun () -> List.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> ignore)
+ throwsAnyError (fun () -> List.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> ignore)
diff --git a/tests/Dart/src/RandomTests.fs b/tests/Dart/src/RandomTests.fs
new file mode 100644
index 0000000000..1f403d1ef3
--- /dev/null
+++ b/tests/Dart/src/RandomTests.fs
@@ -0,0 +1,108 @@
+module Fable.Tests.Dart.RandomTests
+
+open Util
+open System
+
+let chiSquaredStatistic bucketCount sampleCount (nextBucket: unit -> int) =
+ let counts = Array.zeroCreate bucketCount
+
+ for _ in 1 .. sampleCount do
+ let bucket = nextBucket()
+ counts[bucket] <- counts[bucket] + 1
+
+ let expected = float sampleCount / float bucketCount
+
+ counts
+ |> Array.sumBy (fun count ->
+ let diff = float count - expected
+ diff * diff / expected)
+
+let chiSquaredUpperBound degreesOfFreedom sigma =
+ float degreesOfFreedom + sigma * Math.Sqrt(2.0 * float degreesOfFreedom)
+
+let runsZScore sampleCount (nextBit: unit -> bool) =
+ let mutable ones = 0
+ let mutable zeros = 0
+ let mutable runs = 0
+ let mutable hasPrevious = false
+ let mutable previous = false
+
+ for _ in 1 .. sampleCount do
+ let current = nextBit()
+
+ if current then
+ ones <- ones + 1
+ else
+ zeros <- zeros + 1
+
+ if not hasPrevious || current <> previous then
+ runs <- runs + 1
+ previous <- current
+ hasPrevious <- true
+
+ if ones = 0 || zeros = 0 then
+ Double.PositiveInfinity
+ else
+ let ones' = float ones
+ let zeros' = float zeros
+ let total = float sampleCount
+ let expectedRuns = 1.0 + 2.0 * ones' * zeros' / total
+
+ let variance =
+ (2.0 * ones' * zeros' * (2.0 * ones' * zeros' - ones' - zeros'))
+ / (total * total * (total - 1.0))
+
+ Math.Abs(float runs - expectedRuns) / Math.Sqrt(variance)
+
+let tests () =
+
+ testCase "System.Random works" <| fun () ->
+ let rnd = Random()
+ let x = rnd.Next()
+ (x >= 0 && x < 0x7fffffff) |> equal true
+ rnd.Next(0) |> equal 0
+ let x = rnd.Next(5)
+ (x >= 0 && x < 5) |> equal true
+ let x = rnd.Next(14, 20)
+ (x >= 14 && x < 20) |> equal true
+ let x = rnd.Next(-14, -10)
+ (x >= -14 && x < -10) |> equal true
+ let x = rnd.NextDouble()
+ (x >= 0.0 && x < 1.0) |> equal true
+
+ testCase "System.Random seeded works" <| fun () ->
+ let rnd1 = Random(1234)
+ let rnd2 = Random(1234)
+ rnd1.Next() |> equal (rnd2.Next())
+ rnd1.Next(100) |> equal (rnd2.Next(100))
+ rnd1.Next(1000, 10000) |> equal (rnd2.Next(1000, 10000))
+ rnd1.NextDouble() |> equal (rnd2.NextDouble())
+
+ testCase "System.Random seeded validates arguments" <| fun () ->
+ let rnd = Random(42)
+ throwsAnyError <| fun () -> rnd.Next(-10)
+ throwsAnyError <| fun () -> rnd.Next(14, 10)
+ throwsAnyError <| fun () -> rnd.NextBytes(null)
+
+ testCase "System.Random seeded passes chi-squared test" <| fun () ->
+ let rnd = Random(2026)
+ let statistic = chiSquaredStatistic 16 10000 (fun () -> rnd.Next(16))
+ statistic < chiSquaredUpperBound 15 4.0 |> equal true
+
+ testCase "System.Random seeded passes Wald-Wolfowitz runs test" <| fun () ->
+ let rnd = Random(2027)
+ let zScore = runsZScore 10000 (fun () -> rnd.NextDouble() >= 0.5)
+ zScore < 4.0 |> equal true
+
+ testCase "System.Random.NextBytes works" <| fun () ->
+ let buffer = Array.create 16 0uy
+ Random().NextBytes(buffer)
+ buffer.Length |> equal 16
+ buffer = Array.create 16 0uy |> equal false
+
+ testCase "System.Random.NextBytes seeded works" <| fun () ->
+ let buffer1 = Array.create 4 0uy
+ let buffer2 = Array.create 4 0uy
+ Random(5432).NextBytes(buffer1)
+ Random(5432).NextBytes(buffer2)
+ buffer1 |> equal buffer2
diff --git a/tests/Dart/src/SeqTests.fs b/tests/Dart/src/SeqTests.fs
index e44ac2368e..0cc40e8075 100644
--- a/tests/Dart/src/SeqTests.fs
+++ b/tests/Dart/src/SeqTests.fs
@@ -1049,3 +1049,88 @@ let tests() =
throwsAnyError (fun () -> Seq.removeManyAt 0 2 [] |> Seq.toList |> ignore)
throwsAnyError (fun () -> Seq.removeManyAt -1 2 [1] |> Seq.toList |> ignore)
throwsAnyError (fun () -> Seq.removeManyAt 2 2 [1] |> Seq.toList |> ignore)
+
+ testCase "Seq.randomShuffle works" <| fun () ->
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let shuffled = Seq.randomShuffle xs |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ Array.length shuffled |> equal 5
+
+ testCase "Seq.randomShuffleWith works" <| fun () ->
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let rng = System.Random(42)
+ let shuffled = Seq.randomShuffleWith rng xs |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ Array.length shuffled |> equal 5
+
+ testCase "Seq.randomShuffleBy works" <| fun () ->
+ let shuffled = Seq.randomShuffleBy (fun () -> 0.0) [1; 2; 3; 4; 5] |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Seq.randomShuffleBy (fun () -> 1.0) [1; 2] |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomShuffleBy (fun () -> -0.1) [1; 2] |> Seq.toArray |> ignore)
+
+ testCase "Seq.randomChoice works" <| fun () ->
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let x = Seq.randomChoice xs
+ Seq.exists ((=) x) xs |> equal true
+ throwsAnyError (fun () -> Seq.randomChoice Seq.empty |> ignore)
+
+ testCase "Seq.randomChoiceWith works" <| fun () ->
+ let xs = seq { 10; 20; 30 }
+ let rng = System.Random(42)
+ let x = Seq.randomChoiceWith rng xs
+ Seq.exists ((=) x) xs |> equal true
+
+ testCase "Seq.randomChoiceBy works" <| fun () ->
+ let x = Seq.randomChoiceBy (fun () -> 0.0) [42; 99; 7]
+ x |> equal 42
+ throwsAnyError (fun () -> Seq.randomChoiceBy (fun () -> 1.0) [1] |> ignore)
+ throwsAnyError (fun () -> Seq.randomChoiceBy (fun () -> 0.5) Seq.empty |> ignore)
+
+ testCase "Seq.randomChoices works" <| fun () ->
+ let xs = seq { 1; 2; 3 }
+ let choices = Seq.randomChoices 5 xs |> Seq.toArray
+ Array.length choices |> equal 5
+ choices |> Array.forall (fun x -> Seq.exists ((=) x) xs) |> equal true
+ Seq.randomChoices 0 xs |> Seq.toArray |> equal [||]
+ throwsAnyError (fun () -> Seq.randomChoices -1 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomChoices 1 Seq.empty |> Seq.toArray |> ignore)
+
+ testCase "Seq.randomChoicesWith works" <| fun () ->
+ let xs = seq { 10; 20; 30 }
+ let rng = System.Random(42)
+ let choices = Seq.randomChoicesWith rng 4 xs |> Seq.toArray
+ Array.length choices |> equal 4
+ choices |> Array.forall (fun x -> Seq.exists ((=) x) xs) |> equal true
+
+ testCase "Seq.randomChoicesBy works" <| fun () ->
+ let choices = Seq.randomChoicesBy (fun () -> 0.5) 3 [10; 20; 30] |> Seq.toArray
+ Array.length choices |> equal 3
+ choices |> Array.forall (fun x -> x = 20) |> equal true
+
+ testCase "Seq.randomSample works" <| fun () ->
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let sample = Seq.randomSample 3 xs |> Seq.toArray
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Seq.exists ((=) x) xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+ Seq.randomSample 0 xs |> Seq.toArray |> equal [||]
+ Seq.randomSample 5 xs |> Seq.toArray |> Array.sort |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Seq.randomSample -1 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSample 6 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSample 1 Seq.empty |> Seq.toArray |> ignore)
+
+ testCase "Seq.randomSampleWith works" <| fun () ->
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let rng = System.Random(42)
+ let sample = Seq.randomSampleWith rng 3 xs |> Seq.toArray
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Seq.exists ((=) x) xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+
+ testCase "Seq.randomSampleBy works" <| fun () ->
+ let sample = Seq.randomSampleBy (fun () -> 0.0) 3 [1; 2; 3; 4; 5] |> Seq.toArray
+ Array.length sample |> equal 3
+ Array.distinct sample |> Array.length |> equal 3
+ throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> Seq.toArray |> ignore)
diff --git a/tests/Js/Main/ArithmeticTests.fs b/tests/Js/Main/ArithmeticTests.fs
index b57e5ae636..96cc8b6c68 100644
--- a/tests/Js/Main/ArithmeticTests.fs
+++ b/tests/Js/Main/ArithmeticTests.fs
@@ -743,44 +743,6 @@ let tests =
decr i
!i |> equal 4
- testCase "System.Random works" <| fun () ->
- let rnd = Random()
- let x = rnd.Next()
- x >= 0 |> equal true
- let x = rnd.Next(5)
- (x >= 0 && x < 5) |> equal true
- let x = rnd.Next(14, 20)
- (x >= 14 && x < 20) |> equal true
- let x = rnd.Next(-14, -10)
- (x >= -14 && x < -10) |> equal true
- let x = rnd.NextDouble()
- (x >= 0.0 && x < 1.0) |> equal true
- throwsAnyError <| fun () -> rnd.Next(-10)
- throwsAnyError <| fun () -> rnd.Next(14, 10)
-
- // Note: Test could fail sometime during life of universe, if it picks all zeroes.
- testCase "System.Random.NextBytes works" <| fun () ->
- let buffer = Array.create 16 0uy // guid-sized buffer
- Random().NextBytes(buffer)
- buffer.Length |> equal 16
- buffer = Array.create 16 0uy |> equal false
- throwsAnyError <| fun () -> Random().NextBytes(null)
-
- testCase "System.Random seeded works" <| fun () ->
- let rnd = Random(1234)
- rnd.Next() |> equal 857019877
- rnd.Next(100) |> equal 89
- rnd.Next(1000, 10000) |> equal 3872
- rnd.NextDouble() |> equal 0.9467375338760845
- throwsAnyError <| fun () -> rnd.Next(-10)
- throwsAnyError <| fun () -> rnd.Next(14, 10)
-
- testCase "System.Random.NextBytes seeded works" <| fun () ->
- let buffer = Array.create 4 0uy // guid-sized buffer
- Random(5432).NextBytes(buffer)
- buffer |> equal [|152uy; 238uy; 227uy; 30uy|]
- throwsAnyError <| fun () -> Random().NextBytes(null)
-
testCase "Long integers equality works" <| fun () ->
let x = 5L
let y = 5L
diff --git a/tests/Js/Main/ArrayTests.fs b/tests/Js/Main/ArrayTests.fs
index cd31d6d88a..a51b818ee4 100644
--- a/tests/Js/Main/ArrayTests.fs
+++ b/tests/Js/Main/ArrayTests.fs
@@ -1220,6 +1220,109 @@ let tests =
throwsAnyError (fun () -> Array.removeManyAt -1 2 [|1|] |> ignore)
throwsAnyError (fun () -> Array.removeManyAt 2 2 [|1|] |> ignore)
+ testCase "Array.randomShuffle works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let shuffled = Array.randomShuffle xs
+ Array.sort shuffled |> equal xs
+ Array.length shuffled |> equal 5
+
+ testCase "Array.randomShuffleWith works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ let shuffled = Array.randomShuffleWith rng xs
+ Array.sort shuffled |> equal xs
+ Array.length shuffled |> equal 5
+
+ testCase "Array.randomShuffleBy works" <| fun () ->
+ let shuffled = Array.randomShuffleBy (fun () -> 0.0) [|1; 2; 3; 4; 5|]
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Array.randomShuffleBy (fun () -> 1.0) [|1; 2|] |> ignore)
+ throwsAnyError (fun () -> Array.randomShuffleBy (fun () -> -0.1) [|1; 2|] |> ignore)
+
+ testCase "Array.randomShuffleInPlace works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ Array.randomShuffleInPlace xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+
+ testCase "Array.randomShuffleInPlaceWith works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ Array.randomShuffleInPlaceWith rng xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+
+ testCase "Array.randomShuffleInPlaceBy works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ Array.randomShuffleInPlaceBy (fun () -> 0.0) xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Array.randomShuffleInPlaceBy (fun () -> 1.0) [|1; 2|])
+ throwsAnyError (fun () -> Array.randomShuffleInPlaceBy (fun () -> -0.1) [|1; 2|])
+
+ testCase "Array.randomChoice works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let x = Array.randomChoice xs
+ Array.contains x xs |> equal true
+ throwsAnyError (fun () -> Array.randomChoice [||] |> ignore)
+
+ testCase "Array.randomChoiceWith works" <| fun () ->
+ let xs = [|10; 20; 30|]
+ let rng = System.Random(42)
+ let x = Array.randomChoiceWith rng xs
+ Array.contains x xs |> equal true
+
+ testCase "Array.randomChoiceBy works" <| fun () ->
+ let x = Array.randomChoiceBy (fun () -> 0.0) [|42; 99; 7|]
+ x |> equal 42
+ throwsAnyError (fun () -> Array.randomChoiceBy (fun () -> 1.0) [|1|] |> ignore)
+ throwsAnyError (fun () -> Array.randomChoiceBy (fun () -> 0.5) [||] |> ignore)
+
+ testCase "Array.randomChoices works" <| fun () ->
+ let xs = [|1; 2; 3|]
+ let choices = Array.randomChoices 5 xs
+ Array.length choices |> equal 5
+ choices |> Array.forall (fun x -> Array.contains x xs) |> equal true
+ Array.randomChoices 0 xs |> equal [||]
+ throwsAnyError (fun () -> Array.randomChoices -1 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomChoices 1 [||] |> ignore)
+
+ testCase "Array.randomChoicesWith works" <| fun () ->
+ let xs = [|10; 20; 30|]
+ let rng = System.Random(42)
+ let choices = Array.randomChoicesWith rng 4 xs
+ Array.length choices |> equal 4
+ choices |> Array.forall (fun x -> Array.contains x xs) |> equal true
+
+ testCase "Array.randomChoicesBy works" <| fun () ->
+ let choices = Array.randomChoicesBy (fun () -> 0.5) 3 [|10; 20; 30|]
+ Array.length choices |> equal 3
+ choices |> Array.forall (fun x -> x = 20) |> equal true
+
+ testCase "Array.randomSample works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let sample = Array.randomSample 3 xs
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Array.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+ Array.randomSample 0 xs |> equal [||]
+ Array.randomSample 5 xs |> Array.sort |> equal xs
+ throwsAnyError (fun () -> Array.randomSample -1 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomSample 6 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomSample 1 [||] |> ignore)
+
+ testCase "Array.randomSampleWith works" <| fun () ->
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ let sample = Array.randomSampleWith rng 3 xs
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Array.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+
+ testCase "Array.randomSampleBy works" <| fun () ->
+ let sample = Array.randomSampleBy (fun () -> 0.0) 3 [|1; 2; 3; 4; 5|]
+ Array.length sample |> equal 3
+ Array.distinct sample |> Array.length |> equal 3
+ throwsAnyError (fun () -> Array.randomSampleBy (fun () -> 1.0) 1 [|1; 2|] |> ignore)
+ throwsAnyError (fun () -> Array.randomSampleBy (fun () -> -0.1) 1 [|1; 2|] |> ignore)
+
testCase "Array.compareWith works" <| fun () -> // See #2961
let a = [|1;3|]
let b = [|1;2;3|]
diff --git a/tests/Js/Main/Fable.Tests.fsproj b/tests/Js/Main/Fable.Tests.fsproj
index 4959ece88c..8c84e1b755 100644
--- a/tests/Js/Main/Fable.Tests.fsproj
+++ b/tests/Js/Main/Fable.Tests.fsproj
@@ -39,6 +39,7 @@
+
diff --git a/tests/Js/Main/ListTests.fs b/tests/Js/Main/ListTests.fs
index e742c3880b..d5f9a68272 100644
--- a/tests/Js/Main/ListTests.fs
+++ b/tests/Js/Main/ListTests.fs
@@ -990,4 +990,89 @@ let tests =
throwsAnyError (fun () -> List.removeManyAt 0 2 [] |> ignore)
throwsAnyError (fun () -> List.removeManyAt -1 2 [1] |> ignore)
throwsAnyError (fun () -> List.removeManyAt 2 2 [1] |> ignore)
+
+ testCase "List.randomShuffle works" <| fun () ->
+ let xs = [1; 2; 3; 4; 5]
+ let shuffled = List.randomShuffle xs
+ List.sort shuffled |> equal xs
+ List.length shuffled |> equal 5
+
+ testCase "List.randomShuffleWith works" <| fun () ->
+ let xs = [1; 2; 3; 4; 5]
+ let rng = System.Random(42)
+ let shuffled = List.randomShuffleWith rng xs
+ List.sort shuffled |> equal xs
+ List.length shuffled |> equal 5
+
+ testCase "List.randomShuffleBy works" <| fun () ->
+ let shuffled = List.randomShuffleBy (fun () -> 0.0) [1; 2; 3; 4; 5]
+ List.sort shuffled |> equal [1; 2; 3; 4; 5]
+ throwsAnyError (fun () -> List.randomShuffleBy (fun () -> 1.0) [1; 2] |> ignore)
+ throwsAnyError (fun () -> List.randomShuffleBy (fun () -> -0.1) [1; 2] |> ignore)
+
+ testCase "List.randomChoice works" <| fun () ->
+ let xs = [1; 2; 3; 4; 5]
+ let x = List.randomChoice xs
+ List.contains x xs |> equal true
+ throwsAnyError (fun () -> List.randomChoice [] |> ignore)
+
+ testCase "List.randomChoiceWith works" <| fun () ->
+ let xs = [10; 20; 30]
+ let rng = System.Random(42)
+ let x = List.randomChoiceWith rng xs
+ List.contains x xs |> equal true
+
+ testCase "List.randomChoiceBy works" <| fun () ->
+ let x = List.randomChoiceBy (fun () -> 0.0) [42; 99; 7]
+ x |> equal 42
+ throwsAnyError (fun () -> List.randomChoiceBy (fun () -> 1.0) [1] |> ignore)
+ throwsAnyError (fun () -> List.randomChoiceBy (fun () -> 0.5) [] |> ignore)
+
+ testCase "List.randomChoices works" <| fun () ->
+ let xs = [1; 2; 3]
+ let choices = List.randomChoices 5 xs
+ List.length choices |> equal 5
+ choices |> List.forall (fun x -> List.contains x xs) |> equal true
+ List.randomChoices 0 xs |> equal []
+ throwsAnyError (fun () -> List.randomChoices -1 xs |> ignore)
+ throwsAnyError (fun () -> List.randomChoices 1 [] |> ignore)
+
+ testCase "List.randomChoicesWith works" <| fun () ->
+ let xs = [10; 20; 30]
+ let rng = System.Random(42)
+ let choices = List.randomChoicesWith rng 4 xs
+ List.length choices |> equal 4
+ choices |> List.forall (fun x -> List.contains x xs) |> equal true
+
+ testCase "List.randomChoicesBy works" <| fun () ->
+ let choices = List.randomChoicesBy (fun () -> 0.5) 3 [10; 20; 30]
+ List.length choices |> equal 3
+ choices |> List.forall (fun x -> x = 20) |> equal true
+
+ testCase "List.randomSample works" <| fun () ->
+ let xs = [1; 2; 3; 4; 5]
+ let sample = List.randomSample 3 xs
+ List.length sample |> equal 3
+ sample |> List.forall (fun x -> List.contains x xs) |> equal true
+ List.distinct sample |> List.length |> equal 3
+ List.randomSample 0 xs |> equal []
+ List.randomSample 5 xs |> List.sort |> equal xs
+ throwsAnyError (fun () -> List.randomSample -1 xs |> ignore)
+ throwsAnyError (fun () -> List.randomSample 6 xs |> ignore)
+ throwsAnyError (fun () -> List.randomSample 1 [] |> ignore)
+
+ testCase "List.randomSampleWith works" <| fun () ->
+ let xs = [1; 2; 3; 4; 5]
+ let rng = System.Random(42)
+ let sample = List.randomSampleWith rng 3 xs
+ List.length sample |> equal 3
+ sample |> List.forall (fun x -> List.contains x xs) |> equal true
+ List.distinct sample |> List.length |> equal 3
+
+ testCase "List.randomSampleBy works" <| fun () ->
+ let sample = List.randomSampleBy (fun () -> 0.0) 3 [1; 2; 3; 4; 5]
+ List.length sample |> equal 3
+ List.distinct sample |> List.length |> equal 3
+ throwsAnyError (fun () -> List.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> ignore)
+ throwsAnyError (fun () -> List.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> ignore)
]
diff --git a/tests/Js/Main/Main.fs b/tests/Js/Main/Main.fs
index ca2d7a0244..77683c39ff 100644
--- a/tests/Js/Main/Main.fs
+++ b/tests/Js/Main/Main.fs
@@ -6,6 +6,7 @@ let allTests =
UtilTests.tests
Applicative.tests
Arithmetic.tests
+ RandomTests.tests
Arrays.tests
Async.tests
Chars.tests
diff --git a/tests/Js/Main/RandomTests.fs b/tests/Js/Main/RandomTests.fs
new file mode 100644
index 0000000000..5a56c1a019
--- /dev/null
+++ b/tests/Js/Main/RandomTests.fs
@@ -0,0 +1,109 @@
+module Fable.Tests.RandomTests
+
+open System
+open Util.Testing
+open Fable.Tests.Util
+
+let chiSquaredStatistic bucketCount sampleCount (nextBucket: unit -> int) =
+ let counts = Array.zeroCreate bucketCount
+
+ for _ in 1 .. sampleCount do
+ let bucket = nextBucket()
+ counts[bucket] <- counts[bucket] + 1
+
+ let expected = float sampleCount / float bucketCount
+
+ counts
+ |> Array.sumBy (fun count ->
+ let diff = float count - expected
+ diff * diff / expected)
+
+let chiSquaredUpperBound degreesOfFreedom sigma =
+ float degreesOfFreedom + sigma * Math.Sqrt(2.0 * float degreesOfFreedom)
+
+let runsZScore sampleCount (nextBit: unit -> bool) =
+ let mutable ones = 0
+ let mutable zeros = 0
+ let mutable runs = 0
+ let mutable hasPrevious = false
+ let mutable previous = false
+
+ for _ in 1 .. sampleCount do
+ let current = nextBit()
+
+ if current then
+ ones <- ones + 1
+ else
+ zeros <- zeros + 1
+
+ if not hasPrevious || current <> previous then
+ runs <- runs + 1
+ previous <- current
+ hasPrevious <- true
+
+ if ones = 0 || zeros = 0 then
+ Double.PositiveInfinity
+ else
+ let ones' = float ones
+ let zeros' = float zeros
+ let total = float sampleCount
+ let expectedRuns = 1.0 + 2.0 * ones' * zeros' / total
+
+ let variance =
+ (2.0 * ones' * zeros' * (2.0 * ones' * zeros' - ones' - zeros'))
+ / (total * total * (total - 1.0))
+
+ Math.Abs(float runs - expectedRuns) / Math.Sqrt(variance)
+
+let tests =
+ testList "Random" [
+ testCase "System.Random works" <| fun () ->
+ let rnd = Random()
+ let x = rnd.Next()
+ (x >= 0 && x < 0x7fffffff) |> equal true
+ rnd.Next(0) |> equal 0
+ let x = rnd.Next(5)
+ (x >= 0 && x < 5) |> equal true
+ let x = rnd.Next(14, 20)
+ (x >= 14 && x < 20) |> equal true
+ let x = rnd.Next(-14, -10)
+ (x >= -14 && x < -10) |> equal true
+ let x = rnd.NextDouble()
+ (x >= 0.0 && x < 1.0) |> equal true
+
+ testCase "System.Random seeded works" <| fun () ->
+ let rnd = Random(1234)
+ rnd.Next() |> equal 857019877
+ rnd.Next(100) |> equal 89
+ rnd.Next(1000, 10000) |> equal 3872
+ rnd.NextDouble() |> equal 0.9467375338760845
+
+ testCase "System.Random seeded validates arguments" <| fun () ->
+ let rnd = Random(42)
+ throwsAnyError <| fun () -> rnd.Next(-10)
+ throwsAnyError <| fun () -> rnd.Next(14, 10)
+ throwsAnyError <| fun () -> rnd.NextBytes(null)
+
+ testCase "System.Random seeded passes chi-squared test" <| fun () ->
+ let rnd = Random(2026)
+ let statistic = chiSquaredStatistic 16 10000 (fun () -> rnd.Next(16))
+ statistic < chiSquaredUpperBound 15 4.0 |> equal true
+
+ testCase "System.Random seeded passes Wald-Wolfowitz runs test" <| fun () ->
+ let rnd = Random(2027)
+ let zScore = runsZScore 10000 (fun () -> rnd.NextDouble() >= 0.5)
+ zScore < 4.0 |> equal true
+
+ // Note: Test could fail sometime during life of universe, if it picks all zeroes.
+ testCase "System.Random.NextBytes works" <| fun () ->
+ let buffer = Array.create 16 0uy // guid-sized buffer
+ Random().NextBytes(buffer)
+ buffer.Length |> equal 16
+ buffer = Array.create 16 0uy |> equal false
+
+ testCase "System.Random.NextBytes seeded works" <| fun () ->
+ let buffer = Array.create 4 0uy
+ Random(5432).NextBytes(buffer)
+ buffer |> equal [|152uy; 238uy; 227uy; 30uy|]
+
+ ]
\ No newline at end of file
diff --git a/tests/Js/Main/SeqTests.fs b/tests/Js/Main/SeqTests.fs
index 296b134f03..aaecc2e754 100644
--- a/tests/Js/Main/SeqTests.fs
+++ b/tests/Js/Main/SeqTests.fs
@@ -886,6 +886,91 @@ let tests =
Seq.except [|49|] [|7; 49|] |> Seq.last|> equal 7
Seq.except [{ Bar= "test" }] [{ Bar = "test" }] |> Seq.isEmpty |> equal true
+ testCase "Seq.randomShuffle works" <| fun () ->
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let shuffled = Seq.randomShuffle xs |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ Array.length shuffled |> equal 5
+
+ testCase "Seq.randomShuffleWith works" <| fun () ->
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let rng = System.Random(42)
+ let shuffled = Seq.randomShuffleWith rng xs |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ Array.length shuffled |> equal 5
+
+ testCase "Seq.randomShuffleBy works" <| fun () ->
+ let shuffled = Seq.randomShuffleBy (fun () -> 0.0) [1; 2; 3; 4; 5] |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Seq.randomShuffleBy (fun () -> 1.0) [1; 2] |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomShuffleBy (fun () -> -0.1) [1; 2] |> Seq.toArray |> ignore)
+
+ testCase "Seq.randomChoice works" <| fun () ->
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let x = Seq.randomChoice xs
+ Seq.contains x xs |> equal true
+ throwsAnyError (fun () -> Seq.randomChoice Seq.empty |> ignore)
+
+ testCase "Seq.randomChoiceWith works" <| fun () ->
+ let xs = seq { 10; 20; 30 }
+ let rng = System.Random(42)
+ let x = Seq.randomChoiceWith rng xs
+ Seq.contains x xs |> equal true
+
+ testCase "Seq.randomChoiceBy works" <| fun () ->
+ let x = Seq.randomChoiceBy (fun () -> 0.0) [42; 99; 7]
+ x |> equal 42
+ throwsAnyError (fun () -> Seq.randomChoiceBy (fun () -> 1.0) [1] |> ignore)
+ throwsAnyError (fun () -> Seq.randomChoiceBy (fun () -> 0.5) Seq.empty |> ignore)
+
+ testCase "Seq.randomChoices works" <| fun () ->
+ let xs = seq { 1; 2; 3 }
+ let choices = Seq.randomChoices 5 xs |> Seq.toArray
+ Array.length choices |> equal 5
+ choices |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+ Seq.randomChoices 0 xs |> Seq.toArray |> equal [||]
+ throwsAnyError (fun () -> Seq.randomChoices -1 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomChoices 1 Seq.empty |> Seq.toArray |> ignore)
+
+ testCase "Seq.randomChoicesWith works" <| fun () ->
+ let xs = seq { 10; 20; 30 }
+ let rng = System.Random(42)
+ let choices = Seq.randomChoicesWith rng 4 xs |> Seq.toArray
+ Array.length choices |> equal 4
+ choices |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+
+ testCase "Seq.randomChoicesBy works" <| fun () ->
+ let choices = Seq.randomChoicesBy (fun () -> 0.5) 3 [10; 20; 30] |> Seq.toArray
+ Array.length choices |> equal 3
+ choices |> Array.forall (fun x -> x = 20) |> equal true
+
+ testCase "Seq.randomSample works" <| fun () ->
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let sample = Seq.randomSample 3 xs |> Seq.toArray
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+ Seq.randomSample 0 xs |> Seq.toArray |> equal [||]
+ Seq.randomSample 5 xs |> Seq.toArray |> Array.sort |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Seq.randomSample -1 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSample 6 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSample 1 Seq.empty |> Seq.toArray |> ignore)
+
+ testCase "Seq.randomSampleWith works" <| fun () ->
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let rng = System.Random(42)
+ let sample = Seq.randomSampleWith rng 3 xs |> Seq.toArray
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+
+ testCase "Seq.randomSampleBy works" <| fun () ->
+ let sample = Seq.randomSampleBy (fun () -> 0.0) 3 [1; 2; 3; 4; 5] |> Seq.toArray
+ Array.length sample |> equal 3
+ Array.distinct sample |> Array.length |> equal 3
+ throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> Seq.toArray |> ignore)
+
testCase "Seq.item throws exception when index is out of range" <| fun () ->
let xs = [0]
(try Seq.item 1 xs |> ignore; false with | _ -> true) |> equal true
diff --git a/tests/Python/Fable.Tests.Python.fsproj b/tests/Python/Fable.Tests.Python.fsproj
index b555e53a20..817fbaa04d 100644
--- a/tests/Python/Fable.Tests.Python.fsproj
+++ b/tests/Python/Fable.Tests.Python.fsproj
@@ -32,6 +32,7 @@
+
diff --git a/tests/Python/TestArithmetic.fs b/tests/Python/TestArithmetic.fs
index f1cc5ea063..2f46b16e49 100644
--- a/tests/Python/TestArithmetic.fs
+++ b/tests/Python/TestArithmetic.fs
@@ -917,48 +917,6 @@ let ``test decr works`` () =
i.Value <- i.Value - 1
i.Value |> equal 2
-[]
-let ``test System.Random works`` () =
- let rnd = Random()
- let x = rnd.Next()
- x >= 0 |> equal true
- let x = rnd.Next(5)
- (x >= 0 && x < 5) |> equal true
- let x = rnd.Next(14, 20)
- (x >= 14 && x < 20) |> equal true
- let x = rnd.Next(-14, -10)
- (x >= -14 && x < -10) |> equal true
- let x = rnd.NextDouble()
- (x >= 0.0 && x < 1.0) |> equal true
- throwsAnyError <| fun () -> rnd.Next(-10)
- throwsAnyError <| fun () -> rnd.Next(14, 10)
-
-// // Note: Test could fail sometime during life of universe, if it picks all zeroes.
-// []
-// let ``test System.Random.NextBytes works`` () =
-// let buffer = Array.create 16 0uy // guid-sized buffer
-// Random().NextBytes(buffer)
-// buffer.Length |> equal 16
-// buffer = Array.create 16 0uy |> equal false
-// throwsAnyError <| fun () -> Random().NextBytes(null)
-
-// []
-// let ``test System.Random seeded works`` () =
-// let rnd = Random(1234)
-// rnd.Next() |> equal 857019877
-// rnd.Next(100) |> equal 89
-// rnd.Next(1000, 10000) |> equal 3872
-// rnd.NextDouble() |> equal 0.9467375338760845
-// throwsAnyError <| fun () -> rnd.Next(-10)
-// throwsAnyError <| fun () -> rnd.Next(14, 10)
-
-// []
-// let ``test System.Random.NextBytes seeded works`` () =
-// let buffer = Array.create 4 0uy // guid-sized buffer
-// Random(5432).NextBytes(buffer)
-// buffer |> equal [|152uy; 238uy; 227uy; 30uy|]
-// throwsAnyError <| fun () -> Random().NextBytes(null)
-
[]
let ``test Long integer equality works`` () =
let x = 5L
diff --git a/tests/Python/TestArray.fs b/tests/Python/TestArray.fs
index 7eff7833dd..ce64ee29f1 100644
--- a/tests/Python/TestArray.fs
+++ b/tests/Python/TestArray.fs
@@ -1215,3 +1215,121 @@ let ``test System.Array.Resize works`` () =
xs <- null
System.Array.Resize(&xs, 0)
xs |> equal [||]
+
+[]
+let ``test Array.randomShuffle works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let shuffled = Array.randomShuffle xs
+ Array.sort shuffled |> equal xs
+ Array.length shuffled |> equal 5
+
+[]
+let ``test Array.randomShuffleWith works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ let shuffled = Array.randomShuffleWith rng xs
+ Array.sort shuffled |> equal xs
+ Array.length shuffled |> equal 5
+
+[]
+let ``test Array.randomShuffleBy works`` () =
+ let shuffled = Array.randomShuffleBy (fun () -> 0.0) [|1; 2; 3; 4; 5|]
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Array.randomShuffleBy (fun () -> 1.0) [|1; 2|] |> ignore)
+ throwsAnyError (fun () -> Array.randomShuffleBy (fun () -> -0.1) [|1; 2|] |> ignore)
+
+[]
+let ``test Array.randomShuffleInPlace works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ Array.randomShuffleInPlace xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+
+[]
+let ``test Array.randomShuffleInPlaceWith works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ Array.randomShuffleInPlaceWith rng xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+
+[]
+let ``test Array.randomShuffleInPlaceBy works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ Array.randomShuffleInPlaceBy (fun () -> 0.0) xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Array.randomShuffleInPlaceBy (fun () -> 1.0) [|1; 2|])
+ throwsAnyError (fun () -> Array.randomShuffleInPlaceBy (fun () -> -0.1) [|1; 2|])
+
+[]
+let ``test Array.randomChoice works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let x = Array.randomChoice xs
+ Array.contains x xs |> equal true
+ throwsAnyError (fun () -> Array.randomChoice [||] |> ignore)
+
+[]
+let ``test Array.randomChoiceWith works`` () =
+ let xs = [|10; 20; 30|]
+ let rng = System.Random(42)
+ let x = Array.randomChoiceWith rng xs
+ Array.contains x xs |> equal true
+
+[]
+let ``test Array.randomChoiceBy works`` () =
+ let x = Array.randomChoiceBy (fun () -> 0.0) [|42; 99; 7|]
+ x |> equal 42
+ throwsAnyError (fun () -> Array.randomChoiceBy (fun () -> 1.0) [|1|] |> ignore)
+ throwsAnyError (fun () -> Array.randomChoiceBy (fun () -> 0.5) [||] |> ignore)
+
+[]
+let ``test Array.randomChoices works`` () =
+ let xs = [|1; 2; 3|]
+ let choices = Array.randomChoices 5 xs
+ Array.length choices |> equal 5
+ choices |> Array.forall (fun x -> Array.contains x xs) |> equal true
+ Array.randomChoices 0 xs |> equal [||]
+ throwsAnyError (fun () -> Array.randomChoices -1 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomChoices 1 [||] |> ignore)
+
+[]
+let ``test Array.randomChoicesWith works`` () =
+ let xs = [|10; 20; 30|]
+ let rng = System.Random(42)
+ let choices = Array.randomChoicesWith rng 4 xs
+ Array.length choices |> equal 4
+ choices |> Array.forall (fun x -> Array.contains x xs) |> equal true
+
+[]
+let ``test Array.randomChoicesBy works`` () =
+ let choices = Array.randomChoicesBy (fun () -> 0.5) 3 [|10; 20; 30|]
+ Array.length choices |> equal 3
+ choices |> Array.forall (fun x -> x = 20) |> equal true
+
+[]
+let ``test Array.randomSample works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let sample = Array.randomSample 3 xs
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Array.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+ Array.randomSample 0 xs |> equal [||]
+ Array.randomSample 5 xs |> Array.sort |> equal xs
+ throwsAnyError (fun () -> Array.randomSample -1 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomSample 6 xs |> ignore)
+ throwsAnyError (fun () -> Array.randomSample 1 [||] |> ignore)
+
+[]
+let ``test Array.randomSampleWith works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ let sample = Array.randomSampleWith rng 3 xs
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Array.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+
+[]
+let ``test Array.randomSampleBy works`` () =
+ let sample = Array.randomSampleBy (fun () -> 0.0) 3 [|1; 2; 3; 4; 5|]
+ Array.length sample |> equal 3
+ Array.distinct sample |> Array.length |> equal 3
+ throwsAnyError (fun () -> Array.randomSampleBy (fun () -> 1.0) 1 [|1; 2|] |> ignore)
+ throwsAnyError (fun () -> Array.randomSampleBy (fun () -> -0.1) 1 [|1; 2|] |> ignore)
diff --git a/tests/Python/TestList.fs b/tests/Python/TestList.fs
index 2762e99c2f..05badb8fa9 100644
--- a/tests/Python/TestList.fs
+++ b/tests/Python/TestList.fs
@@ -1143,6 +1143,103 @@ let ``test Array.removeManyAt works`` () =
throwsAnyError (fun () -> Array.removeManyAt -1 2 [|1|] |> ignore)
throwsAnyError (fun () -> Array.removeManyAt 2 2 [|1|] |> ignore)
+[]
+let ``test List.randomShuffle works`` () =
+ let xs = [1; 2; 3; 4; 5]
+ let shuffled = List.randomShuffle xs
+ List.sort shuffled |> equal xs
+ List.length shuffled |> equal 5
+
+[]
+let ``test List.randomShuffleWith works`` () =
+ let xs = [1; 2; 3; 4; 5]
+ let rng = System.Random(42)
+ let shuffled = List.randomShuffleWith rng xs
+ List.sort shuffled |> equal xs
+ List.length shuffled |> equal 5
+
+[]
+let ``test List.randomShuffleBy works`` () =
+ let shuffled = List.randomShuffleBy (fun () -> 0.0) [1; 2; 3; 4; 5]
+ List.sort shuffled |> equal [1; 2; 3; 4; 5]
+ throwsAnyError (fun () -> List.randomShuffleBy (fun () -> 1.0) [1; 2] |> ignore)
+ throwsAnyError (fun () -> List.randomShuffleBy (fun () -> -0.1) [1; 2] |> ignore)
+
+[]
+let ``test List.randomChoice works`` () =
+ let xs = [1; 2; 3; 4; 5]
+ let x = List.randomChoice xs
+ List.contains x xs |> equal true
+ throwsAnyError (fun () -> List.randomChoice [] |> ignore)
+
+[]
+let ``test List.randomChoiceWith works`` () =
+ let xs = [10; 20; 30]
+ let rng = System.Random(42)
+ let x = List.randomChoiceWith rng xs
+ List.contains x xs |> equal true
+
+[]
+let ``test List.randomChoiceBy works`` () =
+ let x = List.randomChoiceBy (fun () -> 0.0) [42; 99; 7]
+ x |> equal 42
+ throwsAnyError (fun () -> List.randomChoiceBy (fun () -> 1.0) [1] |> ignore)
+ throwsAnyError (fun () -> List.randomChoiceBy (fun () -> 0.5) [] |> ignore)
+
+[]
+let ``test List.randomChoices works`` () =
+ let xs = [1; 2; 3]
+ let choices = List.randomChoices 5 xs
+ List.length choices |> equal 5
+ choices |> List.forall (fun x -> List.contains x xs) |> equal true
+ List.randomChoices 0 xs |> equal []
+ throwsAnyError (fun () -> List.randomChoices -1 xs |> ignore)
+ throwsAnyError (fun () -> List.randomChoices 1 [] |> ignore)
+
+[]
+let ``test List.randomChoicesWith works`` () =
+ let xs = [10; 20; 30]
+ let rng = System.Random(42)
+ let choices = List.randomChoicesWith rng 4 xs
+ List.length choices |> equal 4
+ choices |> List.forall (fun x -> List.contains x xs) |> equal true
+
+[]
+let ``test List.randomChoicesBy works`` () =
+ let choices = List.randomChoicesBy (fun () -> 0.5) 3 [10; 20; 30]
+ List.length choices |> equal 3
+ choices |> List.forall (fun x -> x = 20) |> equal true
+
+[]
+let ``test List.randomSample works`` () =
+ let xs = [1; 2; 3; 4; 5]
+ let sample = List.randomSample 3 xs
+ List.length sample |> equal 3
+ sample |> List.forall (fun x -> List.contains x xs) |> equal true
+ List.distinct sample |> List.length |> equal 3
+ List.randomSample 0 xs |> equal []
+ List.randomSample 5 xs |> List.sort |> equal xs
+ throwsAnyError (fun () -> List.randomSample -1 xs |> ignore)
+ throwsAnyError (fun () -> List.randomSample 6 xs |> ignore)
+ throwsAnyError (fun () -> List.randomSample 1 [] |> ignore)
+
+[]
+let ``test List.randomSampleWith works`` () =
+ let xs = [1; 2; 3; 4; 5]
+ let rng = System.Random(42)
+ let sample = List.randomSampleWith rng 3 xs
+ List.length sample |> equal 3
+ sample |> List.forall (fun x -> List.contains x xs) |> equal true
+ List.distinct sample |> List.length |> equal 3
+
+[]
+let ``test List.randomSampleBy works`` () =
+ let sample = List.randomSampleBy (fun () -> 0.0) 3 [1; 2; 3; 4; 5]
+ List.length sample |> equal 3
+ List.distinct sample |> List.length |> equal 3
+ throwsAnyError (fun () -> List.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> ignore)
+ throwsAnyError (fun () -> List.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> ignore)
+
[]
let ``test List.collect works II`` () =
let xs = ["a"; "fable"; "bar" ]
diff --git a/tests/Python/TestRandom.fs b/tests/Python/TestRandom.fs
new file mode 100644
index 0000000000..06eeacfe95
--- /dev/null
+++ b/tests/Python/TestRandom.fs
@@ -0,0 +1,114 @@
+module Fable.Tests.RandomTests
+
+open System
+open Fable.Tests.Util
+open Util.Testing
+
+let chiSquaredStatistic bucketCount sampleCount (nextBucket: unit -> int) =
+ let counts = Array.zeroCreate bucketCount
+
+ for _ in 1 .. sampleCount do
+ let bucket = nextBucket()
+ counts[bucket] <- counts[bucket] + 1
+
+ let expected = float sampleCount / float bucketCount
+
+ counts
+ |> Array.sumBy (fun count ->
+ let diff = float count - expected
+ diff * diff / expected)
+
+let chiSquaredUpperBound degreesOfFreedom sigma =
+ float degreesOfFreedom + sigma * Math.Sqrt(2.0 * float degreesOfFreedom)
+
+let runsZScore sampleCount (nextBit: unit -> bool) =
+ let mutable ones = 0
+ let mutable zeros = 0
+ let mutable runs = 0
+ let mutable hasPrevious = false
+ let mutable previous = false
+
+ for _ in 1 .. sampleCount do
+ let current = nextBit()
+
+ if current then
+ ones <- ones + 1
+ else
+ zeros <- zeros + 1
+
+ if not hasPrevious || current <> previous then
+ runs <- runs + 1
+ previous <- current
+ hasPrevious <- true
+
+ if ones = 0 || zeros = 0 then
+ Double.PositiveInfinity
+ else
+ let ones' = float ones
+ let zeros' = float zeros
+ let total = float sampleCount
+ let expectedRuns = 1.0 + 2.0 * ones' * zeros' / total
+
+ let variance =
+ (2.0 * ones' * zeros' * (2.0 * ones' * zeros' - ones' - zeros'))
+ / (total * total * (total - 1.0))
+
+ Math.Abs(float runs - expectedRuns) / Math.Sqrt(variance)
+
+[]
+let ``test System.Random works`` () =
+ let rnd = Random()
+ let x = rnd.Next()
+ (x >= 0 && x < 0x7fffffff) |> equal true
+ rnd.Next(0) |> equal 0
+ let x = rnd.Next(5)
+ (x >= 0 && x < 5) |> equal true
+ let x = rnd.Next(14, 20)
+ (x >= 14 && x < 20) |> equal true
+ let x = rnd.Next(-14, -10)
+ (x >= -14 && x < -10) |> equal true
+ let x = rnd.NextDouble()
+ (x >= 0.0 && x < 1.0) |> equal true
+
+[]
+let ``test System.Random seeded works`` () =
+ let rnd1 = Random(1234)
+ let rnd2 = Random(1234)
+ rnd1.Next() |> equal (rnd2.Next())
+ rnd1.Next(100) |> equal (rnd2.Next(100))
+ rnd1.Next(1000, 10000) |> equal (rnd2.Next(1000, 10000))
+ rnd1.NextDouble() |> equal (rnd2.NextDouble())
+
+[]
+let ``test System.Random seeded validates arguments`` () =
+ let rnd = Random(42)
+ throwsAnyError <| fun () -> rnd.Next(-10)
+ throwsAnyError <| fun () -> rnd.Next(14, 10)
+ // throwsAnyError <| fun () -> rnd.NextBytes(null)
+
+[]
+let ``test System.Random seeded passes chi-squared test`` () =
+ let rnd = Random(2026)
+ let statistic = chiSquaredStatistic 16 10000 (fun () -> rnd.Next(16))
+ statistic < chiSquaredUpperBound 15 4.0 |> equal true
+
+[]
+let ``test System.Random seeded passes Wald-Wolfowitz runs test`` () =
+ let rnd = Random(2027)
+ let zScore = runsZScore 10000 (fun () -> rnd.NextDouble() >= 0.5)
+ zScore < 4.0 |> equal true
+
+[]
+let ``test System.Random.NextBytes works`` () =
+ let buffer = Array.create 16 0uy
+ Random().NextBytes(buffer)
+ buffer.Length |> equal 16
+ buffer = Array.create 16 0uy |> equal false
+
+[]
+let ``test System.Random.NextBytes seeded works`` () =
+ let buffer1 = Array.create 4 0uy
+ let buffer2 = Array.create 4 0uy
+ Random(5432).NextBytes(buffer1)
+ Random(5432).NextBytes(buffer2)
+ buffer1 |> equal buffer2
diff --git a/tests/Python/TestSeq.fs b/tests/Python/TestSeq.fs
index 165a608238..ee206d5547 100644
--- a/tests/Python/TestSeq.fs
+++ b/tests/Python/TestSeq.fs
@@ -1159,3 +1159,100 @@ let ``test Seq.removeManyAt works`` () =
throwsAnyError (fun () -> Seq.removeManyAt 0 2 [] |> Seq.toList |> ignore)
throwsAnyError (fun () -> Seq.removeManyAt -1 2 [1] |> Seq.toList |> ignore)
throwsAnyError (fun () -> Seq.removeManyAt 2 2 [1] |> Seq.toList |> ignore)
+
+[]
+let ``test Seq.randomShuffle works`` () =
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let shuffled = Seq.randomShuffle xs |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ Array.length shuffled |> equal 5
+
+[]
+let ``test Seq.randomShuffleWith works`` () =
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let rng = System.Random(42)
+ let shuffled = Seq.randomShuffleWith rng xs |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ Array.length shuffled |> equal 5
+
+[]
+let ``test Seq.randomShuffleBy works`` () =
+ let shuffled = Seq.randomShuffleBy (fun () -> 0.0) [1; 2; 3; 4; 5] |> Seq.toArray
+ Array.sort shuffled |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Seq.randomShuffleBy (fun () -> 1.0) [1; 2] |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomShuffleBy (fun () -> -0.1) [1; 2] |> Seq.toArray |> ignore)
+
+[]
+let ``test Seq.randomChoice works`` () =
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let x = Seq.randomChoice xs
+ Seq.contains x xs |> equal true
+ throwsAnyError (fun () -> Seq.randomChoice Seq.empty |> ignore)
+
+[]
+let ``test Seq.randomChoiceWith works`` () =
+ let xs = seq { 10; 20; 30 }
+ let rng = System.Random(42)
+ let x = Seq.randomChoiceWith rng xs
+ Seq.contains x xs |> equal true
+
+[]
+let ``test Seq.randomChoiceBy works`` () =
+ let x = Seq.randomChoiceBy (fun () -> 0.0) [42; 99; 7]
+ x |> equal 42
+ throwsAnyError (fun () -> Seq.randomChoiceBy (fun () -> 1.0) [1] |> ignore)
+ throwsAnyError (fun () -> Seq.randomChoiceBy (fun () -> 0.5) Seq.empty |> ignore)
+
+[]
+let ``test Seq.randomChoices works`` () =
+ let xs = seq { 1; 2; 3 }
+ let choices = Seq.randomChoices 5 xs |> Seq.toArray
+ Array.length choices |> equal 5
+ choices |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+ Seq.randomChoices 0 xs |> Seq.toArray |> equal [||]
+ throwsAnyError (fun () -> Seq.randomChoices -1 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomChoices 1 Seq.empty |> Seq.toArray |> ignore)
+
+[]
+let ``test Seq.randomChoicesWith works`` () =
+ let xs = seq { 10; 20; 30 }
+ let rng = System.Random(42)
+ let choices = Seq.randomChoicesWith rng 4 xs |> Seq.toArray
+ Array.length choices |> equal 4
+ choices |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+
+[]
+let ``test Seq.randomChoicesBy works`` () =
+ let choices = Seq.randomChoicesBy (fun () -> 0.5) 3 [10; 20; 30] |> Seq.toArray
+ Array.length choices |> equal 3
+ choices |> Array.forall (fun x -> x = 20) |> equal true
+
+[]
+let ``test Seq.randomSample works`` () =
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let sample = Seq.randomSample 3 xs |> Seq.toArray
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+ Seq.randomSample 0 xs |> Seq.toArray |> equal [||]
+ Seq.randomSample 5 xs |> Seq.toArray |> Array.sort |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Seq.randomSample -1 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSample 6 xs |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSample 1 Seq.empty |> Seq.toArray |> ignore)
+
+[]
+let ``test Seq.randomSampleWith works`` () =
+ let xs = seq { 1; 2; 3; 4; 5 }
+ let rng = System.Random(42)
+ let sample = Seq.randomSampleWith rng 3 xs |> Seq.toArray
+ Array.length sample |> equal 3
+ sample |> Array.forall (fun x -> Seq.contains x xs) |> equal true
+ Array.distinct sample |> Array.length |> equal 3
+
+[]
+let ``test Seq.randomSampleBy works`` () =
+ let sample = Seq.randomSampleBy (fun () -> 0.0) 3 [1; 2; 3; 4; 5] |> Seq.toArray
+ Array.length sample |> equal 3
+ Array.distinct sample |> Array.length |> equal 3
+ throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> 1.0) 1 [1; 2] |> Seq.toArray |> ignore)
+ throwsAnyError (fun () -> Seq.randomSampleBy (fun () -> -0.1) 1 [1; 2] |> Seq.toArray |> ignore)
diff --git a/tests/Rust/Fable.Tests.Rust.fsproj b/tests/Rust/Fable.Tests.Rust.fsproj
index add37fe2d3..3f7e9a5258 100644
--- a/tests/Rust/Fable.Tests.Rust.fsproj
+++ b/tests/Rust/Fable.Tests.Rust.fsproj
@@ -26,6 +26,7 @@
+
diff --git a/tests/Rust/tests/src/ArithmeticTests.fs b/tests/Rust/tests/src/ArithmeticTests.fs
index 81d15f314d..1067e99a29 100644
--- a/tests/Rust/tests/src/ArithmeticTests.fs
+++ b/tests/Rust/tests/src/ArithmeticTests.fs
@@ -916,55 +916,6 @@ let ``decr works`` () =
i.Value <- i.Value - 1
i.Value |> equal 2
-[]
-let ``System.Random works`` () =
- let rnd = Random()
- let x = rnd.Next()
- x >= 0 |> equal true
- let x = rnd.Next(5)
- (x >= 0 && x < 5) |> equal true
- rnd.Next(0) |> equal 0
- let x = rnd.Next(14, 20)
- (x >= 14 && x < 20) |> equal true
- let x = rnd.Next(-14, -10)
- (x >= -14 && x < -10) |> equal true
- let x = rnd.NextDouble()
- (x >= 0.0 && x < 1.0) |> equal true
- throwsAnyError <| fun () -> rnd.Next(-10)
- throwsAnyError <| fun () -> rnd.Next(14, 10)
-
-// Note: Test could fail sometime during life of universe, if it picks all zeroes.
-[]
-let ``System.Random.NextBytes works`` () =
- let buffer = Array.create 16 0uy // guid-sized buffer
- Random().NextBytes(buffer)
- buffer.Length |> equal 16
- buffer = Array.create 16 0uy |> equal false
- // throwsAnyError <| fun () -> Random().NextBytes(null)
-
-[]
-let ``System.Random seeded works`` () =
- let rnd = Random(1234)
- let x = rnd.Next()
- x >= 0 |> equal true
- let x = rnd.Next(100)
- (x >= 0 && x < 100) |> equal true
- let x = rnd.Next(1000, 10000)
- (x >= 1000 && x < 10000) |> equal true
- let x = rnd.NextDouble()
- (x >= 0.0 && x < 1.0) |> equal true
- throwsAnyError <| fun () -> rnd.Next(-10)
- throwsAnyError <| fun () -> rnd.Next(14, 10)
-
-[]
-let ``System.Random.NextBytes seeded works`` () =
- let buffer = Array.create 4 0uy // guid-sized buffer
- Random(5432).NextBytes(buffer)
- buffer.Length |> equal 4
- // Just verify it's not all zeros
- buffer <> Array.create 4 0uy |> equal true
- // throwsAnyError <| fun () -> Random().NextBytes(null)
-
[]
let ``Long integer equality works`` () =
let x = 5L
diff --git a/tests/Rust/tests/src/ArrayTests.fs b/tests/Rust/tests/src/ArrayTests.fs
index e33230acb2..4bbf58ee26 100644
--- a/tests/Rust/tests/src/ArrayTests.fs
+++ b/tests/Rust/tests/src/ArrayTests.fs
@@ -1346,6 +1346,27 @@ let ``Array.randomShuffleBy works`` () =
throwsAnyError (fun () -> Array.randomShuffleBy (fun () -> 1.0) [|1; 2|] |> ignore)
throwsAnyError (fun () -> Array.randomShuffleBy (fun () -> -0.1) [|1; 2|] |> ignore)
+[]
+let ``Array.randomShuffleInPlace works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ Array.randomShuffleInPlace xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+
+[]
+let ``Array.randomShuffleInPlaceWith works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ let rng = System.Random(42)
+ Array.randomShuffleInPlaceWith rng xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+
+[]
+let ``Array.randomShuffleInPlaceBy works`` () =
+ let xs = [|1; 2; 3; 4; 5|]
+ Array.randomShuffleInPlaceBy (fun () -> 0.0) xs
+ Array.sort xs |> equal [|1; 2; 3; 4; 5|]
+ throwsAnyError (fun () -> Array.randomShuffleInPlaceBy (fun () -> 1.0) [|1; 2|])
+ throwsAnyError (fun () -> Array.randomShuffleInPlaceBy (fun () -> -0.1) [|1; 2|])
+
[]
let ``Array.randomChoice works`` () =
let xs = [|1; 2; 3; 4; 5|]
diff --git a/tests/Rust/tests/src/RandomTests.fs b/tests/Rust/tests/src/RandomTests.fs
new file mode 100644
index 0000000000..f767e44d57
--- /dev/null
+++ b/tests/Rust/tests/src/RandomTests.fs
@@ -0,0 +1,113 @@
+module Fable.Tests.RandomTests
+
+open System
+open Util.Testing
+
+let chiSquaredStatistic bucketCount sampleCount (nextBucket: unit -> int) =
+ let counts = Array.zeroCreate bucketCount
+
+ for _ in 1 .. sampleCount do
+ let bucket = nextBucket()
+ counts[bucket] <- counts[bucket] + 1
+
+ let expected = float sampleCount / float bucketCount
+
+ counts
+ |> Array.sumBy (fun count ->
+ let diff = float count - expected
+ diff * diff / expected)
+
+let chiSquaredUpperBound degreesOfFreedom sigma =
+ float degreesOfFreedom + sigma * Math.Sqrt(2.0 * float degreesOfFreedom)
+
+let runsZScore sampleCount (nextBit: unit -> bool) =
+ let mutable ones = 0
+ let mutable zeros = 0
+ let mutable runs = 0
+ let mutable hasPrevious = false
+ let mutable previous = false
+
+ for _ in 1 .. sampleCount do
+ let current = nextBit()
+
+ if current then
+ ones <- ones + 1
+ else
+ zeros <- zeros + 1
+
+ if not hasPrevious || current <> previous then
+ runs <- runs + 1
+ previous <- current
+ hasPrevious <- true
+
+ if ones = 0 || zeros = 0 then
+ Double.PositiveInfinity
+ else
+ let ones' = float ones
+ let zeros' = float zeros
+ let total = float sampleCount
+ let expectedRuns = 1.0 + 2.0 * ones' * zeros' / total
+
+ let variance =
+ (2.0 * ones' * zeros' * (2.0 * ones' * zeros' - ones' - zeros'))
+ / (total * total * (total - 1.0))
+
+ Math.Abs(float runs - expectedRuns) / Math.Sqrt(variance)
+
+[]
+let ``System.Random works`` () =
+ let rnd = Random()
+ let x = rnd.Next()
+ (x >= 0 && x < 0x7fffffff) |> equal true
+ rnd.Next(0) |> equal 0
+ let x = rnd.Next(5)
+ (x >= 0 && x < 5) |> equal true
+ let x = rnd.Next(14, 20)
+ (x >= 14 && x < 20) |> equal true
+ let x = rnd.Next(-14, -10)
+ (x >= -14 && x < -10) |> equal true
+ let x = rnd.NextDouble()
+ (x >= 0.0 && x < 1.0) |> equal true
+
+[]
+let ``System.Random seeded works`` () =
+ let rnd1 = Random(1234)
+ let rnd2 = Random(1234)
+ rnd1.Next() |> equal (rnd2.Next())
+ rnd1.Next(100) |> equal (rnd2.Next(100))
+ rnd1.Next(1000, 10000) |> equal (rnd2.Next(1000, 10000))
+ rnd1.NextDouble() |> equal (rnd2.NextDouble())
+
+[]
+let ``System.Random seeded validates arguments`` () =
+ let rnd = Random(42)
+ throwsAnyError <| fun () -> rnd.Next(-10)
+ throwsAnyError <| fun () -> rnd.Next(14, 10)
+ // throwsAnyError <| fun () -> rnd.NextBytes(null)
+
+[]
+let ``System.Random seeded passes chi-squared test`` () =
+ let rnd = Random(2026)
+ let statistic = chiSquaredStatistic 16 10000 (fun () -> rnd.Next(16))
+ statistic < chiSquaredUpperBound 15 4.0 |> equal true
+
+[]
+let ``System.Random seeded passes Wald-Wolfowitz runs test`` () =
+ let rnd = Random(2027)
+ let zScore = runsZScore 10000 (fun () -> rnd.NextDouble() >= 0.5)
+ zScore < 4.0 |> equal true
+
+[]
+let ``System.Random.NextBytes works`` () =
+ let buffer = Array.create 16 0uy
+ Random().NextBytes(buffer)
+ buffer.Length |> equal 16
+ buffer = Array.create 16 0uy |> equal false
+
+[]
+let ``System.Random.NextBytes seeded works`` () =
+ let buffer1 = Array.create 4 0uy
+ let buffer2 = Array.create 4 0uy
+ Random(5432).NextBytes(buffer1)
+ Random(5432).NextBytes(buffer2)
+ buffer1 |> equal buffer2
diff --git a/tests/TypeScript/Fable.Tests.TypeScript.fsproj b/tests/TypeScript/Fable.Tests.TypeScript.fsproj
index 1eea218d5f..5bd1587c65 100644
--- a/tests/TypeScript/Fable.Tests.TypeScript.fsproj
+++ b/tests/TypeScript/Fable.Tests.TypeScript.fsproj
@@ -30,6 +30,7 @@
+