-module(prng@random). -compile([no_auto_import, nowarn_unused_vars]). -export([step/2, to_iterator/2, int/2, sample/1, sample_once/1, float/2, constant/1, list/2, then/2, map/2, weighted/2, try_weighted/1, map2/3, pair/2, uniform/2, try_uniform/1, choose/2, dictionary/3, map3/4, map4/5, map5/6]). -export_type([generator/1]). -opaque generator(FDZ) :: {generator, fun((prng@seed:seed()) -> {FDZ, prng@seed:seed()})}. -spec step(generator(FEA), prng@seed:seed()) -> {FEA, prng@seed:seed()}. step(Generator, Seed) -> (erlang:element(2, Generator))(Seed). -spec to_iterator(generator(FEH), prng@seed:seed()) -> gleam@iterator:iterator(FEH). to_iterator(Generator, Seed) -> gleam@iterator:unfold( Seed, fun(Seed@1) -> {Value, New_seed} = step(Generator, Seed@1), {next, Value, New_seed} end ). -spec sort_ascending(FEL, FEL, fun((FEL, FEL) -> gleam@order:order())) -> {FEL, FEL}. sort_ascending(One, Other, Compare) -> case Compare(One, Other) of lt -> {One, Other}; eq -> {One, Other}; gt -> {Other, One} end. -spec int(integer(), integer()) -> generator(integer()). int(From, To) -> {generator, fun(Seed) -> {Low, High} = sort_ascending(From, To, fun gleam@int:compare/2), ffi:random_int(Seed, Low, High) end}. -spec sample(generator(FEE)) -> gleam@iterator:iterator(FEE). sample(Generator) -> to_iterator(Generator, ffi:new_seed(gleam@int:random(0, 4294967296))). -spec sample_once(generator(FEC)) -> FEC. sample_once(Generator) -> _assert_subject = gleam@iterator:first(sample(Generator)), {ok, Result} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"prng/random"/utf8>>, function => <<"sample_once"/utf8>>, line => 122}) end, Result. -spec float(float(), float()) -> generator(float()). float(From, To) -> {generator, fun(Seed) -> {Low, High} = sort_ascending(From, To, fun gleam@float:compare/2), ffi:random_float(Seed, Low, High) end}. -spec constant(FEN) -> generator(FEN). constant(Value) -> {generator, fun(Seed) -> {Value, Seed} end}. -spec get_by_weight({float(), FFF}, list({float(), FFF}), float()) -> FFF. 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. -spec do_list(list(FFS), prng@seed:seed(), generator(FFS), integer()) -> {list(FFS), prng@seed:seed()}. do_list(Acc, Seed, Generator, Length) -> case Length =< 0 of true -> {Acc, Seed}; false -> {Value, Seed@1} = step(Generator, Seed), do_list([Value | Acc], Seed@1, Generator, Length - 1) end. -spec list(generator(FFO), integer()) -> generator(list(FFO)). list(Generator, Length) -> {generator, fun(Seed) -> do_list([], Seed, Generator, Length) end}. -spec then(generator(FGD), fun((FGD) -> generator(FGF))) -> generator(FGF). then(Generator, Generator_from) -> {generator, fun(Seed) -> {Value, Seed@1} = step(Generator, Seed), _pipe = Generator_from(Value), step(_pipe, Seed@1) end}. -spec map(generator(FGI), fun((FGI) -> FGK)) -> generator(FGK). map(Generator, Fun) -> {generator, fun(Seed) -> {Value, Seed@1} = step(Generator, Seed), {Fun(Value), Seed@1} end}. -spec weighted({float(), FEX}, list({float(), FEX})) -> generator(FEX). 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 ). -spec try_weighted(list({float(), FFA})) -> {ok, generator(FFA)} | {error, nil}. try_weighted(Options) -> case Options of [First | Rest] -> {ok, weighted(First, Rest)}; [] -> {error, nil} end. -spec map2(generator(FGM), generator(FGO), fun((FGM, FGO) -> FGQ)) -> generator(FGQ). 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}. -spec pair(generator(FFJ), generator(FFL)) -> generator({FFJ, FFL}). pair(One, Other) -> map2(One, Other, fun gleam@pair:new/2). -spec uniform(FEP, list(FEP)) -> generator(FEP). uniform(First, Others) -> weighted( {1.0, First}, gleam@list:map( Others, fun(_capture) -> gleam@pair:new(1.0, _capture) end ) ). -spec try_uniform(list(FES)) -> {ok, generator(FES)} | {error, nil}. try_uniform(Options) -> case Options of [First | Rest] -> {ok, uniform(First, Rest)}; [] -> {error, nil} end. -spec choose(FFH, FFH) -> generator(FFH). choose(One, Other) -> uniform(One, [Other]). -spec dictionary(integer(), generator(FFW), generator(FFY)) -> generator(gleam@map:map_(FFW, FFY)). dictionary(Size, Keys, Values) -> _pipe = pair(Keys, Values), _pipe@1 = list(_pipe, Size), map(_pipe@1, fun gleam@map:from_list/1). -spec map3( generator(FGS), generator(FGU), generator(FGW), fun((FGS, FGU, FGW) -> FGY) ) -> generator(FGY). 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}. -spec map4( generator(FHA), generator(FHC), generator(FHE), generator(FHG), fun((FHA, FHC, FHE, FHG) -> FHI) ) -> generator(FHI). 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}. -spec map5( generator(FHK), generator(FHM), generator(FHO), generator(FHQ), generator(FHS), fun((FHK, FHM, FHO, FHQ, FHS) -> FHU) ) -> generator(FHU). 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}.