-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, try_weighted/1, map2/3, pair/2, uniform/2, try_uniform/1, choose/2, map3/4, map4/5, map5/6, fixed_size_string/1, string/0, bit_array/0, set/1, fixed_size_set/2, dict/2, fixed_size_dict/3]). -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" " pair\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(DQP) :: {generator, fun((seed()) -> {DQP, seed()})}. -type seed() :: any(). -file("src/prng/random.gleam", 123). -spec new_seed(integer()) -> seed(). new_seed(Int) -> prng_ffi:new_seed(Int). -file("src/prng/random.gleam", 152). ?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(DQQ), seed()) -> {DQQ, seed()}. step(Generator, Seed) -> (erlang:element(2, Generator))(Seed). -file("src/prng/random.gleam", 188). -spec sort_ascending(DQT, DQT, fun((DQT, DQT) -> gleam@order:order())) -> {DQT, DQT}. sort_ascending(One, Other, Compare) -> case Compare(One, Other) of lt -> {One, Other}; eq -> {One, Other}; gt -> {Other, One} end. -file("src/prng/random.gleam", 182). ?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) -> {generator, fun(Seed) -> {Low, High} = sort_ascending(From, To, fun gleam@int:compare/2), prng_ffi:random_int(Seed, Low, High) end}. -file("src/prng/random.gleam", 207). ?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) -> {generator, fun(Seed) -> {Low, High} = sort_ascending(From, To, fun gleam@float:compare/2), prng_ffi:random_float(Seed, Low, High) end}. -file("src/prng/random.gleam", 229). ?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(DQV) -> generator(DQV). constant(Value) -> {generator, fun(Seed) -> {Value, Seed} end}. -file("src/prng/random.gleam", 389). -spec get_by_weight({float(), DRN}, list({float(), DRN}), float()) -> DRN. get_by_weight(First, Others, Countdown) -> {Weight, Value} = First, case Others of [] -> Value; [Second | Rest] -> Positive_weight = gleam@float:absolute_value(Weight), case gleam@float:compare(Countdown, Positive_weight) of lt -> Value; eq -> Value; gt -> get_by_weight(Second, Rest, Countdown - Positive_weight) end end. -file("src/prng/random.gleam", 476). -spec do_fixed_size_list(list(DSA), seed(), generator(DSA), integer()) -> {list(DSA), 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", 468). ?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(DRW), integer()) -> generator(list(DRW)). fixed_size_list(Generator, Length) -> {generator, fun(Seed) -> do_fixed_size_list([], Seed, Generator, Length) end}. -file("src/prng/random.gleam", 670). ?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(DTP), fun((DTP) -> generator(DTR))) -> generator(DTR). then(Generator, Generator_from) -> {generator, fun(Seed) -> {Value, Seed@1} = step(Generator, Seed), _pipe = Generator_from(Value), step(_pipe, Seed@1) 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(DSE)) -> generator(list(DSE)). list(Generator) -> then(int(0, 32), fun(Size) -> fixed_size_list(Generator, Size) end). -file("src/prng/random.gleam", 697). ?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(DTU), fun((DTU) -> DTW)) -> generator(DTW). map(Generator, Fun) -> {generator, fun(Seed) -> {Value, Seed@1} = step(Generator, Seed), {Fun(Value), Seed@1} end}. -file("src/prng/random.gleam", 344). ?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(), DRF}, list({float(), DRF})) -> generator(DRF). weighted(First, Others) -> Normalise = fun(Pair) -> gleam@float:absolute_value(gleam@pair:first(Pair)) end, Total = Normalise(First) + gleam@float:sum( gleam@list:map(Others, Normalise) ), map( float(+0.0, Total), fun(_capture) -> get_by_weight(First, Others, _capture) end ). -file("src/prng/random.gleam", 382). ?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(), DRI})) -> {ok, generator(DRI)} | {error, nil}. try_weighted(Options) -> case Options of [First | Rest] -> {ok, weighted(First, Rest)}; [] -> {error, nil} end. -file("src/prng/random.gleam", 741). ?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(DTY), generator(DUA), fun((DTY, DUA) -> DUC)) -> generator(DUC). 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", 449). ?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(DRR), generator(DRT)) -> generator({DRR, DRT}). pair(One, Other) -> map2(One, Other, fun gleam@pair:new/2). -file("src/prng/random.gleam", 266). ?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(DQX, list(DQX)) -> generator(DQX). uniform(First, Others) -> weighted( {1.0, First}, gleam@list:map( Others, fun(_capture) -> gleam@pair:new(1.0, _capture) end ) ). -file("src/prng/random.gleam", 302). ?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(DRA)) -> {ok, generator(DRA)} | {error, nil}. try_uniform(Options) -> case Options of [First | Rest] -> {ok, uniform(First, Rest)}; [] -> {error, nil} end. -file("src/prng/random.gleam", 429). ?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(DRP, DRP) -> generator(DRP). choose(One, Other) -> uniform(One, [Other]). -file("src/prng/random.gleam", 785). ?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(DUE), generator(DUG), generator(DUI), fun((DUE, DUG, DUI) -> DUK) ) -> generator(DUK). 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", 802). ?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(DUM), generator(DUO), generator(DUQ), generator(DUS), fun((DUM, DUO, DUQ, DUS) -> DUU) ) -> generator(DUU). 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", 824). ?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(DUW), generator(DUY), generator(DVA), generator(DVC), generator(DVE), fun((DUW, DUY, DVA, DVC, DVE) -> DVG) ) -> generator(DVG). 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", 861). ?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", 872). ?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", 849). ?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", 630). ?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", 623). ?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(DTK)) -> generator(gleam@set:set(DTK)). set(Generator) -> then(int(0, 32), fun(Size) -> fixed_size_set(Generator, Size) end). -file("src/prng/random.gleam", 575). ?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(DTB), integer()) -> generator(gleam@set:set(DTB)). fixed_size_set(Generator, Size) -> _pipe = gleam@int:max(Size, 0), do_fixed_size_set(Generator, _pipe, 0, 0, gleam@set:new()). -file("src/prng/random.gleam", 583). -spec do_fixed_size_set( generator(DTF), integer(), integer(), integer(), gleam@set:set(DTF) ) -> generator(gleam@set:set(DTF)). 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 -> _pipe = (Consecutive_attempts + 1), do_fixed_size_set( Generator, Size, Unique_items, _pipe, Acc ); false -> _pipe@1 = gleam@set:insert(Acc, Item), do_fixed_size_set( Generator, Size, Unique_items + 1, 0, _pipe@1 ) end end ) end ) end ). -file("src/prng/random.gleam", 563). ?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", 512). ?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(DSI), generator(DSK), integer()) -> generator(gleam@dict:dict(DSI, DSK)). 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", 521). -spec do_fixed_size_dict( generator(DSN), generator(DSP), integer(), integer(), integer(), gleam@dict:dict(DSN, DSP) ) -> generator(gleam@dict:dict(DSN, DSP)). 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 -> _pipe = (Consecutive_attempts + 1), do_fixed_size_dict( Keys, Values, Size, Unique_keys, _pipe, Acc ); false -> then( Values, fun(Value) -> _pipe@1 = gleam@dict:insert( Acc, Key, Value ), do_fixed_size_dict( Keys, Values, Size, Unique_keys + 1, 0, _pipe@1 ) end ) end end) end ) end ).