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 @@ +