-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, pair/2, list/1, map/2, weighted/2, uniform/2, try_uniform/1, try_weighted/1, choose/2, map2/3, map3/4, map4/5, map5/6, shuffle/1, fixed_size_string/1, string/0, bit_array/0, set/1, fixed_size_set/2, dict/2, fixed_size_dict/3, 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(DQM) :: {generator, fun((seed()) -> {DQM, 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", 149). ?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 [`to_yielder`](#to_yielder),\n" " [`to_random_yielder`](#to_random_yielder), or [`sample`](#sample) instead.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let initial_seed = seed.new(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(DQN), seed()) -> {DQN, seed()}. step(Generator, Seed) -> (erlang:element(2, Generator))(Seed). -file("src/prng/random.gleam", 179). ?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", 198). ?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", 221). ?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(DQR) -> generator(DQR). constant(Value) -> {generator, fun(Seed) -> {Value, Seed} end}. -file("src/prng/random.gleam", 340). -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", 387). -spec get_by_weight({float(), DRL}, list({float(), DRL}), float()) -> DRL. 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", 476). -spec do_fixed_size_list(list(DRY), seed(), generator(DRY), integer()) -> {list(DRY), 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", 469). ?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(DRU), integer()) -> generator(list(DRU)). fixed_size_list(Generator, Length) -> {generator, fun(_capture) -> do_fixed_size_list([], _capture, Generator, Length) end}. -file("src/prng/random.gleam", 800). ?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(DUU), fun((DUU) -> generator(DUW))) -> generator(DUW). 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", 448). ?DOC( " Generates pairs of values obtained by combining the values produced by the\n" " given generators.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let one_to_five = random.int(1, 5)\n" " let probability = random.float(0.0, 1.0)\n" " let ints_and_floats = random.pair(one_to_five, probability)\n" "\n" " random.random_sample(ints_and_floats)\n" " // -> #(3, 0.22)\n" " ```\n" ). -spec pair(generator(DRP), generator(DRR)) -> generator({DRP, DRR}). pair(One, Other) -> then(One, fun(N) -> then(Other, fun(M) -> constant({N, M}) end) end). -file("src/prng/random.gleam", 497). ?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(DSC)) -> generator(list(DSC)). list(Generator) -> then(int(0, 32), fun(_capture) -> fixed_size_list(Generator, _capture) end). -file("src/prng/random.gleam", 827). ?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(DUZ), fun((DUZ) -> DVB)) -> generator(DVB). map(Generator, Fun) -> {generator, fun(Seed) -> {Value, Seed@1} = step(Generator, Seed), {Fun(Value), Seed@1} end}. -file("src/prng/random.gleam", 335). ?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(), DRB}, list({float(), DRB})) -> generator(DRB). 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", 257). ?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(DQT, list(DQT)) -> generator(DQT). uniform(First, Others) -> weighted( {1.0, First}, gleam@list:map(Others, fun(Value) -> {1.0, Value} end) ). -file("src/prng/random.gleam", 293). ?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(DQW)) -> {ok, generator(DQW)} | {error, nil}. try_uniform(Options) -> case Options of [First | Rest] -> {ok, uniform(First, Rest)}; [] -> {error, nil} end. -file("src/prng/random.gleam", 380). ?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(), DRG})) -> {ok, generator(DRG)} | {error, nil}. try_weighted(Options) -> case Options of [First | Rest] -> {ok, weighted(First, Rest)}; [] -> {error, nil} end. -file("src/prng/random.gleam", 427). ?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(DRN, DRN) -> generator(DRN). choose(One, Other) -> uniform(One, [Other]). -file("src/prng/random.gleam", 873). ?DOC( " Combines two generators into a single one. The resulting generator produces\n" " values obtained by applying `fun` to the values generated by the given\n" " generators.\n" "\n" " ## Examples\n" "\n" " Imagine you need to generate random points in a 2D space:\n" "\n" " ```gleam\n" " pub type Point {\n" " Point(x: Float, y: Float)\n" " }\n" " ```\n" "\n" " You can compose two basic generators into a `Point` generator using `map2`:\n" "\n" " ```gleam\n" " let x_generator = random.float(-1.0, 1.0)\n" " let y_generator = random.float(-1.0, 1.0)\n" " let point_generator = map2(x_generator, y_generator, Point)\n" " ```\n" "\n" " > Notice how you could get the same result using `then`:\n" " >\n" " > ```gleam\n" " > pub fn point_generator() -> Generator(Point) {\n" " > use x <- random.then(random.float(-1.0, 1.0))\n" " > use y <- random.then(random.float(-1.0, 1.0))\n" " > random.constant(Point(x, y))\n" " > }\n" " > ```\n" " >\n" " > the `use` syntax paired with `then` may be confusing for other people\n" " > reading your code, especially Gleam newcomers.\n" " >\n" " > Usually `map2`/`map3`/... will be more than enough if you just need to\n" " > combine simple generators into more complex ones.\n" ). -spec map2(generator(DVD), generator(DVF), fun((DVD, DVF) -> DVH)) -> generator(DVH). map2(One, Other, Fun) -> {generator, fun(Seed) -> {A, Seed@1} = step(One, Seed), {B, Seed@2} = step(Other, Seed@1), {Fun(A, B), Seed@2} end}. -file("src/prng/random.gleam", 919). ?DOC( " Combines three generators into a single one. The resulting generator\n" " produces values obtained by applying `fun` to the values generated by the\n" " given generators.\n" "\n" " ## Examples\n" "\n" " Imagine you're writing a generator for random enemies in a game you're\n" " making:\n" "\n" " ```gleam\n" " pub type Enemy {\n" " Enemy(health: Int, attack: Int, defense: Int)\n" " }\n" " ```\n" "\n" " Each enemy starts with a random health (that can go from 50 to 100) and\n" " random values for the `attack` and `defense` stats (each can be in a range\n" " from 1 to 5):\n" "\n" " ```gleam\n" " let health_generator = random.int(50, 100)\n" " let attack_generator = random.int(1, 5)\n" " let defense_generator = random.int(1, 5)\n" "\n" " let enemy_generator =\n" " random.map3(\n" " health_generator,\n" " attack_generator,\n" " defense_generator,\n" " Enemy,\n" " )\n" " ```\n" ). -spec map3( generator(DVJ), generator(DVL), generator(DVN), fun((DVJ, DVL, DVN) -> DVP) ) -> generator(DVP). map3(One, Two, Three, Fun) -> {generator, fun(Seed) -> {A, Seed@1} = step(One, Seed), {B, Seed@2} = step(Two, Seed@1), {C, Seed@3} = step(Three, Seed@2), {Fun(A, B, C), Seed@3} end}. -file("src/prng/random.gleam", 938). ?DOC( " Combines four generators into a single one. The resulting generator\n" " produces values obtained by applying `fun` to the values generated by the\n" " given generators.\n" ). -spec map4( generator(DVR), generator(DVT), generator(DVV), generator(DVX), fun((DVR, DVT, DVV, DVX) -> DVZ) ) -> generator(DVZ). map4(One, Two, Three, Four, Fun) -> {generator, fun(Seed) -> {A, Seed@1} = step(One, Seed), {B, Seed@2} = step(Two, Seed@1), {C, Seed@3} = step(Three, Seed@2), {D, Seed@4} = step(Four, Seed@3), {Fun(A, B, C, D), Seed@4} end}. -file("src/prng/random.gleam", 962). ?DOC( " Combines five generators into a single one. The resulting generator\n" " produces values obtained by applying `fun` to the values generated by the\n" " given generators.\n" "\n" " > There's no `map6`, `map7`, and so on. If you feel like you need to compose\n" " > together even more generators, you can use the `random.then` function.\n" ). -spec map5( generator(DWB), generator(DWD), generator(DWF), generator(DWH), generator(DWJ), fun((DWB, DWD, DWF, DWH, DWJ) -> DWL) ) -> generator(DWL). map5(One, Two, Three, Four, Five, Fun) -> {generator, fun(Seed) -> {A, Seed@1} = step(One, Seed), {B, Seed@2} = step(Two, Seed@1), {C, Seed@3} = step(Three, Seed@2), {D, Seed@4} = step(Four, Seed@3), {E, Seed@5} = step(Five, Seed@4), {Fun(A, B, C, D, E), Seed@5} end}. -file("src/prng/random.gleam", 654). -spec do_shuffle(list(DTR), list({float(), DTR})) -> generator(list(DTR)). 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", 650). ?DOC( " Generates lists with the same element of the given one, but in a random\n" " order.\n" ). -spec shuffle(list(DTN)) -> generator(list(DTN)). shuffle(List) -> do_shuffle(List, []). -file("src/prng/random.gleam", 724). -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 => 727, value => _assert_fail, start => 21492, 'end' => 21554, pattern_start => 21503, pattern_end => 21513}) end, constant(Random@1) end ). -file("src/prng/random.gleam", 1000). ?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", 1011). ?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", 988). ?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", 643). ?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", 636). ?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(DTI)) -> generator(gleam@set:set(DTI)). set(Generator) -> then(int(0, 32), fun(Size) -> fixed_size_set(Generator, Size) end). -file("src/prng/random.gleam", 582). ?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(DSZ), integer()) -> generator(gleam@set:set(DSZ)). 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", 589). -spec do_fixed_size_set( generator(DTD), integer(), integer(), integer(), gleam@set:set(DTD) ) -> generator(gleam@set:set(DTD)). 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", 570). ?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", 511). ?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(DSG), generator(DSI), integer()) -> generator(gleam@dict:dict(DSG, DSI)). 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", 520). -spec do_fixed_size_dict( generator(DSL), generator(DSN), integer(), integer(), integer(), gleam@dict:dict(DSL, DSN) ) -> generator(gleam@dict:dict(DSL, DSN)). 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", 700). -spec sample_loop( list(DUA), gleam@dict:dict(integer(), DUA), integer(), float() ) -> generator(gleam@dict:dict(integer(), DUA)). 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 => 706, value => _assert_fail, start => 20804, 'end' => 20850, pattern_start => 20815, pattern_end => 20822}) 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", 742). -spec build_reservoir_loop( list(DUN), integer(), gleam@dict:dict(integer(), DUN) ) -> {gleam@dict:dict(integer(), DUN), list(DUN)}. 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", 738). ?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(DUI), integer()) -> {gleam@dict:dict(integer(), DUI), list(DUI)}. build_reservoir(List, N) -> build_reservoir_loop(List, N, maps:new()). -file("src/prng/random.gleam", 682). ?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(DTW), integer()) -> generator(list(DTW)). 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.