-module(prng@random). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/prng/random.gleam"). -export([new_seed/1, step/2, int/2, float/2, constant/1, fixed_size_list/2, then/2, list/1, map/2, weighted/2, uniform/2, try_uniform/1, try_weighted/1, choose/2, dict/2, fixed_size_dict/3, set/1, fixed_size_set/2, fixed_size_string/1, string/0, bit_array/0, shuffle/1, sample/2]). -export_type([generator/1, seed/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " This package provides many building blocks that can be used to define\n" " pure generators of pseudo-random values.\n" "\n" " This is based on the great\n" " [Elm implementation](https://package.elm-lang.org/packages/elm/random/1.0.0/)\n" " of [Permuted Congruential Generators](https://www.pcg-random.org).\n" "\n" " _It is not cryptographically secure!_\n" "\n" " You can use this cheatsheet to navigate the module documentation:\n" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "
Building generators\n" " int,\n" " float,\n" " string,\n" " fixed_size_string,\n" " bit_array,\n" " uniform,\n" " weighted,\n" " choose,\n" " constant\n" "
Transform and compose generators\n" " map,\n" " then\n" "
Generating common data structures\n" " fixed_size_list,\n" " list,\n" " fixed_size_dict,\n" " dict\n" " fixed_size_set,\n" " set\n" "
Getting values out of a generator\n" " step\n" "
\n" "\n" ). -opaque generator(DKT) :: {generator, fun((seed()) -> {DKT, seed()})}. -type seed() :: any(). -file("src/prng/random.gleam", 120). -spec new_seed(integer()) -> seed(). new_seed(Int) -> prng_ffi:new_seed(Int). -file("src/prng/random.gleam", 148). ?DOC( " Steps a `Generator(a)` producing a random value of type `a` using the given\n" " seed as the source of randomness.\n" "\n" " The stepping logic is completely deterministic. This means that, given a\n" " seed and a generator, you'll always get the same result.\n" "\n" " This is why this function also returns a new seed that can be used to make\n" " subsequent calls to `step` to get other random values.\n" "\n" " Stepping a generator by hand can be quite cumbersome, so I recommend you\n" " try [`sample`](#sample) instead.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let initial_seed = random.new_seed(11)\n" " let dice_roll = random.int(1, 6)\n" " let #(first_roll, new_seed) = random.step(dice_roll, initial_seed)\n" " let #(second_roll, _) = random.step(dice_roll, new_seed)\n" "\n" " #(first_roll, second_roll)\n" " // -> #(3, 2)\n" " ```\n" ). -spec step(generator(DKU), seed()) -> {DKU, seed()}. step(Generator, Seed) -> (erlang:element(2, Generator))(Seed). -file("src/prng/random.gleam", 178). ?DOC( " Generates integers in the given inclusive range.\n" "\n" " ## Examples\n" "\n" " Say you want to model the outcome of a dice, you could use `int` like this:\n" "\n" " ```gleam\n" " let dice_roll = random.int(1, 6)\n" " ```\n" ). -spec int(integer(), integer()) -> generator(integer()). int(From, To) -> case From =< To of true -> {generator, fun(_capture) -> prng_ffi:random_int(_capture, From, To) end}; false -> {generator, fun(_capture@1) -> prng_ffi:random_int(_capture@1, To, From) end} end. -file("src/prng/random.gleam", 197). ?DOC( " Generates floating point numbers in the given inclusive range.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let probability = random.float(0.0, 1.0)\n" " ```\n" ). -spec float(float(), float()) -> generator(float()). float(From, To) -> case From =< To of true -> {generator, fun(_capture) -> prng_ffi:random_float(_capture, From, To) end}; false -> {generator, fun(_capture@1) -> prng_ffi:random_float(_capture@1, To, From) end} end. -file("src/prng/random.gleam", 220). ?DOC( " Always generates the given value, no matter the seed used.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let always_eleven = random.constant(11)\n" " random.random_sample(always_eleven)\n" " // -> 11\n" " ```\n" ). -spec constant(DKY) -> generator(DKY). constant(Value) -> {generator, fun(Seed) -> {Value, Seed} end}. -file("src/prng/random.gleam", 454). -spec do_fixed_size_list(list(DMA), seed(), generator(DMA), integer()) -> {list(DMA), seed()}. do_fixed_size_list(Acc, Seed, Generator, Length) -> case Length =< 0 of true -> {Acc, Seed}; false -> {Value, Seed@1} = step(Generator, Seed), do_fixed_size_list([Value | Acc], Seed@1, Generator, Length - 1) end. -file("src/prng/random.gleam", 447). ?DOC( " Generates a lists of a fixed size; its values are generated using the\n" " given generator.\n" "\n" " ## Examples\n" "\n" " Imagine you're modelling a game of\n" " [Risk](https://en.wikipedia.org/wiki/Risk_(game)); when a player \"attacks\"\n" " they can roll three dice. You may model that outcome using `fixed_size_list`\n" " like this:\n" "\n" " ```gleam\n" " let dice_roll = random.int(1, 6)\n" " let attack_outcome = random.fixed_size_list(dice_roll, 3)\n" " ```\n" ). -spec fixed_size_list(generator(DLW), integer()) -> generator(list(DLW)). fixed_size_list(Generator, Length) -> {generator, fun(_capture) -> do_fixed_size_list([], _capture, Generator, Length) end}. -file("src/prng/random.gleam", 781). ?DOC( " Transforms a generator into another one based on its generated values.\n" "\n" " The random value generated by the given generator is fed into the `do`\n" " function and the returned generator is used as the new generator.\n" "\n" " ## Examples\n" "\n" " `then` is a really powerful function, almost all functions exposed by this\n" " library could be defined in term of it!\n" " Take as an example `map`, it can be implemented like this:\n" "\n" " ```gleam\n" " fn map(generator: Generator(a), with fun: fn(a) -> b) -> Generator(b) {\n" " random.then(generator, fn(value) {\n" " random.constant(fun(value))\n" " })\n" " }\n" " ```\n" "\n" " Notice how the `do` function needs to return a `Generator(b)`, you can\n" " achieve that by wrapping any constant value with the `random.constant`\n" " generator.\n" "\n" " > Code written with `then` can gain a lot in readability if you use the\n" " > `use` syntax, especially if it has some deep nesting. As an example, this\n" " > is how you can rewrite the previous example taking advantage of `use`:\n" " >\n" " > ```gleam\n" " > fn map(generator: Generator(a), with fun: fn(a) -> b) -> Generator(b) {\n" " > use value <- random.then(generator)\n" " > random.constant(fun(value))\n" " > }\n" " > ```\n" ). -spec then(generator(DOW), fun((DOW) -> generator(DOY))) -> generator(DOY). then(Generator, Generator_from) -> {generator, fun(Seed) -> {Value, Seed@1} = step(Generator, Seed), step(Generator_from(Value), Seed@1) end}. -file("src/prng/random.gleam", 475). ?DOC( " Generates a list with a random size with at most 32 items.\n" " Each item is generated using the given generator.\n" "\n" " This is similar to `fixed_size_list` with the difference that the size\n" " is chosen randomly.\n" ). -spec list(generator(DME)) -> generator(list(DME)). list(Generator) -> then(int(0, 32), fun(_capture) -> fixed_size_list(Generator, _capture) end). -file("src/prng/random.gleam", 386). -spec get_by_weight({float(), DLS}, list({float(), DLS}), float()) -> DLS. get_by_weight(First, Others, Countdown) -> {Weight, Value} = First, case Others of [] -> Value; [Second | Rest] -> Positive_weight = gleam@float:absolute_value(Weight), case Countdown > Positive_weight of false -> Value; true -> get_by_weight(Second, Rest, Countdown - Positive_weight) end end. -file("src/prng/random.gleam", 808). ?DOC( " Transforms the values produced by a generator using the given function.\n" "\n" " ## Examples\n" "\n" " Imagine you want to make a generator for boolean values that returns\n" " `True` and `False` with the same probability. You could do that using `map`\n" " like this:\n" "\n" " ```gleam\n" " let bool_generator = random.int(1, 2) |> random.map(fn(n) { n == 1 })\n" " ```\n" "\n" " Here `map` allows you to transform the values produced by the initial\n" " integer generator - either 1 or 2 - into boolean values: when the original\n" " generator produces a 1, `bool_generator` will produce `True`; when the\n" " original generator produces a 2, `bool_generator` will produce `False`.\n" ). -spec map(generator(DPB), fun((DPB) -> DPD)) -> generator(DPD). map(Generator, Fun) -> {generator, fun(Seed) -> {Value, Seed@1} = step(Generator, Seed), {Fun(Value), Seed@1} end}. -file("src/prng/random.gleam", 339). -spec sum_absolute_values(list({float(), any()}), float()) -> float(). sum_absolute_values(List, Acc) -> case List of [] -> Acc; [{Value, _} | Rest] -> sum_absolute_values(Rest, Acc + gleam@float:absolute_value(Value)) end. -file("src/prng/random.gleam", 334). ?DOC( " Generates values from the given ones with a weighted probability.\n" "\n" " This generator can guarantee to produce values since it always takes at\n" " least one item (as its first argument); if it were to accept just a list of\n" " options, it could be called like this:\n" "\n" " ```gleam\n" " weighted([])\n" " ```\n" "\n" " In which case it would be impossible to actually produce any value: none was\n" " provided!\n" "\n" " ## Examples\n" "\n" " Given the following type to model the outcome of a coin flip:\n" "\n" " ```gleam\n" " pub type CoinFlip {\n" " Heads\n" " Tails\n" " }\n" " ```\n" "\n" " You could write a generator for a loaded coin that lands on head 75% of the\n" " times like this:\n" "\n" " ```gleam\n" " let loaded_coin = random.weighted(#(0.75, Heads), [#(0.25, Tails)])\n" " ```\n" "\n" " In this example the weights add up to 1, but you could use any number: the\n" " weights get added up to a `total` and the probability of each option is its\n" " `weight` / `total`.\n" ). -spec weighted({float(), DLI}, list({float(), DLI})) -> generator(DLI). weighted(First, Others) -> Total = sum_absolute_values( Others, gleam@float:absolute_value(erlang:element(1, First)) ), map( float(+0.0, Total), fun(_capture) -> get_by_weight(First, Others, _capture) end ). -file("src/prng/random.gleam", 256). ?DOC( " Generates values from the given ones with an equal probability.\n" "\n" " This generator can guarantee to produce values since it always takes at\n" " least one item (as its first argument); if it were to accept just a list of\n" " options, it could be called like this:\n" "\n" " ```gleam\n" " uniform([])\n" " ```\n" "\n" " In which case it would be impossible to actually produce any value: none was\n" " provided!\n" "\n" " ## Examples\n" "\n" " Given the following type to model colors:\n" "\n" " ```gleam\n" " pub type Color {\n" " Red\n" " Green\n" " Blue\n" " }\n" " ```\n" "\n" " You could write a generator that returns each color with an equal\n" " probability (~33%) each color like this:\n" "\n" " ```gleam\n" " let color = random.uniform(Red, [Green, Blue])\n" " ```\n" ). -spec uniform(DLA, list(DLA)) -> generator(DLA). uniform(First, Others) -> weighted( {1.0, First}, gleam@list:map(Others, fun(Value) -> {1.0, Value} end) ). -file("src/prng/random.gleam", 292). ?DOC( " This function works exactly like `uniform` but will return an `Error(Nil)`\n" " if the provided argument is an empty list since the generator wouldn't be\n" " able to produce any value in that case.\n" "\n" " It generates values from the given list with equal probability.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " random.try_uniform([])\n" " // -> Error(Nil)\n" " ```\n" "\n" " For example if you consider the following type definition to model color:\n" "\n" " ```gleam\n" " type Color {\n" " Red\n" " Green\n" " Blue\n" " }\n" " ```\n" "\n" " This call of `try_uniform` will produce a generator wrapped in an `Ok`:\n" "\n" " ```gleam\n" " let assert Ok(color_1) = random.try_uniform([Red, Green, Blue])\n" " let color_2 = random.uniform(Red, [Green, Blue])\n" " ```\n" "\n" " The generators `color_1` and `color_2` will behave exactly the same.\n" ). -spec try_uniform(list(DLD)) -> {ok, generator(DLD)} | {error, nil}. try_uniform(Options) -> case Options of [First | Rest] -> {ok, uniform(First, Rest)}; [] -> {error, nil} end. -file("src/prng/random.gleam", 379). ?DOC( " This function works exactly like `weighted` but will return an `Error(Nil)`\n" " if the provided argument is an empty list since the generator wouldn't be\n" " able to produce any value in that case.\n" "\n" " It generates values from the given list with a weighted probability.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " random.try_weighted([])\n" " // -> Error(Nil)\n" " ```\n" "\n" " For example if you consider the following type definition to model color:\n" "\n" " ```gleam\n" " type CoinFlip {\n" " Heads\n" " Tails\n" " }\n" " ```\n" "\n" " This call of `try_weighted` will produce a generator wrapped in an `Ok`:\n" "\n" " ```gleam\n" " let assert Ok(coin_1) =\n" " random.try_weighted([#(0.75, Heads), #(0.25, Tails)])\n" " let coin_2 = random.uniform(#(0.75, Heads), [#(0.25, Tails)])\n" " ```\n" "\n" " The generators `coin_1` and `coin_2` will behave exactly the same.\n" ). -spec try_weighted(list({float(), DLN})) -> {ok, generator(DLN)} | {error, nil}. try_weighted(Options) -> case Options of [First | Rest] -> {ok, weighted(First, Rest)}; [] -> {error, nil} end. -file("src/prng/random.gleam", 426). ?DOC( " Generates two values with equal probability.\n" "\n" " This is a shorthand for `random.uniform(one, [other])`, but can read better\n" " when there's only two choices.\n" "\n" " ## Examples\n" "\n" " Given the following type to model the outcome of a coin flip:\n" "\n" " ```gleam\n" " pub type CoinFlip {\n" " Heads\n" " Tails\n" " }\n" " ```\n" "\n" " You can write a generator for coin flip outcomes like this:\n" "\n" " ```gleam\n" " let flip = random.choose(Heads, Tails)\n" " ```\n" ). -spec choose(DLU, DLU) -> generator(DLU). choose(One, Other) -> uniform(One, [Other]). -file("src/prng/random.gleam", 548). ?DOC( " Generates a `Map(k, v)` where each key value pair is generated using the\n" " provided generators.\n" "\n" " This is similar to `fixed_size_dict` with the difference that the map is\n" " going to have a random number of key-value pairs between 0 (inclusive) and\n" " 32 (inclusive).\n" ). -spec dict(generator(any()), generator(any())) -> generator(gleam@dict:dict(any(), any())). dict(Keys, Values) -> then(int(0, 32), fun(Size) -> fixed_size_dict(Keys, Values, Size) end). -file("src/prng/random.gleam", 498). -spec do_fixed_size_dict( generator(DMN), generator(DMP), integer(), integer(), integer(), gleam@dict:dict(DMN, DMP) ) -> generator(gleam@dict:dict(DMN, DMP)). do_fixed_size_dict(Keys, Values, Size, Unique_keys, Consecutive_attempts, Acc) -> Has_required_size = Unique_keys =:= Size, gleam@bool:guard( Has_required_size, constant(Acc), fun() -> Has_reached_maximum_attempts = Consecutive_attempts >= 10, gleam@bool:guard( Has_reached_maximum_attempts, constant(Acc), fun() -> then(Keys, fun(Key) -> case gleam@dict:has_key(Acc, Key) of true -> do_fixed_size_dict( Keys, Values, Size, Unique_keys, Consecutive_attempts + 1, Acc ); false -> then( Values, fun(Value) -> Unique_keys@1 = Unique_keys + 1, Acc@1 = gleam@dict:insert( Acc, Key, Value ), do_fixed_size_dict( Keys, Values, Size, Unique_keys@1, 0, Acc@1 ) end ) end end) end ) end ). -file("src/prng/random.gleam", 489). ?DOC( " Generates a `Dict(k, v)` where each key value pair is generated using the\n" " provided generators.\n" "\n" " > ⚠️ This function makes a best effort at generating a map with exactly the\n" " > specified number of keys, but beware that it may contain less items if\n" " > the keys generator cannot generate enough distinct keys.\n" ). -spec fixed_size_dict(generator(DMI), generator(DMK), integer()) -> generator(gleam@dict:dict(DMI, DMK)). fixed_size_dict(Keys, Values, Size) -> _pipe = gleam@int:max(Size, 0), do_fixed_size_dict(Keys, Values, _pipe, 0, 0, maps:new()). -file("src/prng/random.gleam", 614). ?DOC( " Generates a `Set(a)` where each item is generated using the provided\n" " generator.\n" "\n" " This is similar to `fixed_size_set` with the difference that the set is\n" " going to have a random size between 0 (inclusive) and 32 (inclusive).\n" ). -spec set(generator(DNK)) -> generator(gleam@set:set(DNK)). set(Generator) -> then(int(0, 32), fun(Size) -> fixed_size_set(Generator, Size) end). -file("src/prng/random.gleam", 567). -spec do_fixed_size_set( generator(DNF), integer(), integer(), integer(), gleam@set:set(DNF) ) -> generator(gleam@set:set(DNF)). do_fixed_size_set(Generator, Size, Unique_items, Consecutive_attempts, Acc) -> Has_required_size = Unique_items =:= Size, gleam@bool:guard( Has_required_size, constant(Acc), fun() -> Has_reached_maximum_attempts = Consecutive_attempts >= 10, gleam@bool:guard( Has_reached_maximum_attempts, constant(Acc), fun() -> then( Generator, fun(Item) -> case gleam@set:contains(Acc, Item) of true -> do_fixed_size_set( Generator, Size, Unique_items, Consecutive_attempts + 1, Acc ); false -> Unique_items@1 = Unique_items + 1, Acc@1 = gleam@set:insert(Acc, Item), do_fixed_size_set( Generator, Size, Unique_items@1, 0, Acc@1 ) end end ) end ) end ). -file("src/prng/random.gleam", 560). ?DOC( " Generates a `Set(a)` where each item is generated using the provided\n" " generator.\n" "\n" " > ⚠️ This function makes a best effort at generating a set with exactly the\n" " > specified number of items, but beware that it may contain less items if\n" " > the given generator cannot generate enough distinct values.\n" ). -spec fixed_size_set(generator(DNB), integer()) -> generator(gleam@set:set(DNB)). fixed_size_set(Generator, Size) -> do_fixed_size_set(Generator, gleam@int:max(Size, 0), 0, 0, gleam@set:new()). -file("src/prng/random.gleam", 846). ?DOC( " I'm not exposing this function because, if one is not careful with the range,\n" " it might lead to a nasty infinite loop.\n" " When I come up with a better alternative I might make a similar API public,\n" " for now, if someone wants to do something unsafe they will have to\n" " manually reimplement it.\n" ). -spec utf_codepoint_in_range(integer(), integer()) -> generator(integer()). utf_codepoint_in_range(Lower, Upper) -> then( int(Lower, Upper), fun(Raw_codepoint) -> case gleam@string:utf_codepoint(Raw_codepoint) of {ok, Codepoint} -> constant(Codepoint); {error, _} -> utf_codepoint_in_range(Lower, Upper) end end ). -file("src/prng/random.gleam", 835). ?DOC( " Generates Strings with the given number number of UTF code points.\n" "\n" " > ⚠️ The generated codepoints will be in the range from 0 (inclusive) to\n" " > 1023 (inclusive). If you feel like these strings are not enough for your\n" " > needs, please open an issue! I'd love to hear your use case and improve\n" " > the package.\n" ). -spec fixed_size_string(integer()) -> generator(binary()). fixed_size_string(Size) -> _pipe = fixed_size_list(utf_codepoint_in_range(0, 1023), Size), map(_pipe, fun gleam_stdlib:utf_codepoint_list_to_string/1). -file("src/prng/random.gleam", 823). ?DOC( " Generates Strings with a random number of UTF code points, between\n" " 0 (included) and 32 (included).\n" "\n" " This is similar to `fixed_size_string`, with the difference that the\n" " size is randomly generated as well.\n" ). -spec string() -> generator(binary()). string() -> then(int(0, 32), fun(Size) -> fixed_size_string(Size) end). -file("src/prng/random.gleam", 621). ?DOC(" Generates `BitArray`s with a random size.\n"). -spec bit_array() -> generator(bitstring()). bit_array() -> map(string(), fun gleam_stdlib:identity/1). -file("src/prng/random.gleam", 632). -spec do_shuffle(list(DNT), list({float(), DNT})) -> generator(list(DNT)). do_shuffle(List, Acc) -> Slightly_less_than_1 = 1.0 - 2.2250738585072014e-308, case List of [] -> _pipe = gleam@list:sort( Acc, fun(One, Other) -> gleam@float:compare( erlang:element(1, One), erlang:element(1, Other) ) end ), _pipe@1 = gleam@list:map( _pipe, fun(Pair) -> erlang:element(2, Pair) end ), constant(_pipe@1); [First | Rest] -> then( float(+0.0, Slightly_less_than_1), fun(Order) -> do_shuffle(Rest, [{Order, First} | Acc]) end ) end. -file("src/prng/random.gleam", 628). ?DOC( " Generates lists with the same element of the given one, but in a random\n" " order.\n" ). -spec shuffle(list(DNP)) -> generator(list(DNP)). shuffle(List) -> do_shuffle(List, []). -file("src/prng/random.gleam", 702). -spec log_random() -> generator(float()). log_random() -> Slightly_less_than_1 = 1.0 - 2.2250738585072014e-308, then( float(+0.0, Slightly_less_than_1), fun(Float) -> Random@1 = case gleam@float:logarithm( Float + 2.2250738585072014e-308 ) of {ok, Random} -> Random; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"prng/random"/utf8>>, function => <<"log_random"/utf8>>, line => 705, value => _assert_fail, start => 20857, 'end' => 20919, pattern_start => 20868, pattern_end => 20878}) end, constant(Random@1) end ). -file("src/prng/random.gleam", 678). -spec sample_loop( list(DOC), gleam@dict:dict(integer(), DOC), integer(), float() ) -> generator(gleam@dict:dict(integer(), DOC)). sample_loop(List, Reservoir, N, W) -> Log@1 = case gleam@float:logarithm(1.0 - W) of {ok, Log} -> Log; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"prng/random"/utf8>>, function => <<"sample_loop"/utf8>>, line => 684, value => _assert_fail, start => 20169, 'end' => 20215, pattern_start => 20180, pattern_end => 20187}) end, then( log_random(), fun(Log_randon) -> Skip = erlang:round(math:floor(case Log@1 of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> Log_randon / Gleam@denominator end)), case gleam@list:drop(List, Skip) of [] -> constant(Reservoir); [First | Rest] -> then( int(0, N - 1), fun(Position) -> then( log_random(), fun(Log_random) -> Reservoir@1 = gleam@dict:insert( Reservoir, Position, First ), W@1 = W * math:exp(case erlang:float(N) of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator@1 -> Log_random / Gleam@denominator@1 end), sample_loop(Rest, Reservoir@1, N, W@1) end ) end ) end end ). -file("src/prng/random.gleam", 723). -spec build_reservoir_loop( list(DOP), integer(), gleam@dict:dict(integer(), DOP) ) -> {gleam@dict:dict(integer(), DOP), list(DOP)}. build_reservoir_loop(List, Size, Reservoir) -> Reservoir_size = maps:size(Reservoir), case Reservoir_size >= Size of true -> {Reservoir, List}; false -> case List of [] -> {Reservoir, []}; [First | Rest] -> Reservoir@1 = gleam@dict:insert( Reservoir, Reservoir_size, First ), build_reservoir_loop(Rest, Size, Reservoir@1) end end. -file("src/prng/random.gleam", 716). ?DOC( " Builds the initial reservoir used by Algorithm L.\n" " This is a dictionary with keys ranging from `0` up to `n - 1` where each\n" " value is the corresponding element at that position in `list`.\n" "\n" " This also returns the remaining elements of `list` that didn't end up in\n" " the reservoir.\n" ). -spec build_reservoir(list(DOK), integer()) -> {gleam@dict:dict(integer(), DOK), list(DOK)}. build_reservoir(List, N) -> build_reservoir_loop(List, N, maps:new()). -file("src/prng/random.gleam", 660). ?DOC( " Generates random samples of up to n elements from a list using reservoir\n" " sampling via [Algorithm L](https://en.wikipedia.org/wiki/Reservoir_sampling#Optimal:_Algorithm_L).\n" " Returns an empty list if the sample size is less than or equal to 0.\n" "\n" " Order is not random, only selection is.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " sample([1, 2, 3, 4, 5], 3)\n" " // some samples could be: [2, 4, 5], [1, 4, 5], ...\n" " ```\n" ). -spec sample(list(DNY), integer()) -> generator(list(DNY)). sample(List, N) -> {Reservoir, Rest} = build_reservoir(List, N), case gleam@dict:is_empty(Reservoir) of true -> constant([]); false -> then( log_random(), fun(Log_random) -> W = math:exp(case erlang:float(N) of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> Log_random / Gleam@denominator end), _pipe = sample_loop(Rest, Reservoir, N, W), map(_pipe, fun maps:values/1) end ) end.