From a2575ecc40a767549b1330e7f5f5b89b115a9d27 Mon Sep 17 00:00:00 2001 From: mrdimosthenis Date: Fri, 10 Nov 2023 12:41:55 +0200 Subject: [PATCH] Version 0.1, simulation friendly, written in modern Gleam --- .github/workflows/test.yml | 12 ++- .gitignore | 23 +--- README.md | 6 +- gleam.toml | 21 ++-- manifest.toml | 11 -- src/minigen.gleam | 133 ++++++++++-------------- src/minigen/interop.gleam | 20 ++-- src/native_rand_mimigen.erl | 2 +- test/minigen/minigen/interop_test.gleam | 6 +- test/minigen/minigen_test.gleam | 100 +++++++++--------- 10 files changed, 136 insertions(+), 198 deletions(-) delete mode 100644 manifest.toml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a24335e..cf2096e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,11 +11,13 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.0.0 - - uses: erlef/setup-beam@v1.9.0 + - uses: actions/checkout@v3 + - uses: erlef/setup-beam@v1 with: - otp-version: "23.2" - gleam-version: "0.21.0" - - run: gleam format --check src test + otp-version: "26.0.2" + gleam-version: "0.32.4" + rebar3-version: "3" + # elixir-version: "1.15.4" - run: gleam deps download - run: gleam test + - run: gleam format --check src test diff --git a/.gitignore b/.gitignore index 11bdb43..170cca9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,4 @@ *.beam -*.iml -*.o -*.plt -*.swo -*.swp -*~ -.erlang.cookie -.eunit -.idea -.rebar -.rebar3 -_* -_build -docs -ebin -erl_crash.dump -gen -log -logs -rebar3.crashdump -/rebar.lock +*.ez build +erl_crash.dump diff --git a/README.md b/README.md index 79f6fda..83d4dd3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # minigen -A library for generating random data in the Erlang ecosystem, written in Gleam. +Pure random data generation library, appropriate for realistic simulations in the Erlang ecosystem. ## Installation @@ -8,7 +8,7 @@ A library for generating random data in the Erlang ecosystem, written in Gleam. ```erlang {deps, [ - {minigen, "0.0.3"} + {minigen, "0.1.0"} ]}. ``` @@ -17,7 +17,7 @@ A library for generating random data in the Erlang ecosystem, written in Gleam. ```elixir defp deps do [ - {:minigen, "~> 0.0.3"} + {:minigen, "~> 0.1.0", manager: :rebar3} ] end ``` diff --git a/gleam.toml b/gleam.toml index 394cc45..cae4ef6 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,20 +1,13 @@ name = "minigen" -version = "0.0.3" -licences = ["Apache-2.0"] -description = "A library for generating data in the Erlang ecosystem" - -[docs] -links = [ - { title = 'GitHub', href = 'https://github.com/mrdimosthenis/minigen' } -] +version = "0.1.0" -[repository] -type = "github" -user = "mrdimosthenis" -repo = "minigen" +description = "Pure random data generation, appropriate for realistic simulations" +licences = ["Apache-2.0"] +repository = { type = "github", user = "mrdimosthenis", repo = "minigen" } +links = [{ title = 'GitHub', href = 'https://github.com/mrdimosthenis/minigen' }] [dependencies] -gleam_stdlib = "~> 0.21" +gleam_stdlib = "~> 0.32" [dev-dependencies] -gleeunit = "~> 0.6" +gleeunit = "~> 1.0" diff --git a/manifest.toml b/manifest.toml deleted file mode 100644 index 65bd3e1..0000000 --- a/manifest.toml +++ /dev/null @@ -1,11 +0,0 @@ -# This file was generated by Gleam -# You typically do not need to edit this file - -packages = [ - { name = "gleam_stdlib", version = "0.21.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "FB03CD50B477867DA65B1318252ABA02F76175754762D15BF6C792CF5B85BCC4" }, - { name = "gleeunit", version = "0.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "5BF486C3E135B7F5ED8C054925FC48E5B2C79016A39F416FD8CF2E860520EE55" }, -] - -[requirements] -gleam_stdlib = "~> 0.21" -gleeunit = "~> 0.6" diff --git a/src/minigen.gleam b/src/minigen.gleam index b78bdff..83deff1 100644 --- a/src/minigen.gleam +++ b/src/minigen.gleam @@ -5,9 +5,8 @@ import gleam/float import gleam/int import gleam/list -import gleam/string import gleam/pair -import gleam/result +import gleam/string import minigen/interop type Seed = @@ -205,7 +204,7 @@ pub fn always(x: a) -> Generator(a) { Generator(f) } -/// Creates a generatoe for float values. +/// Creates a generator for float values. /// By running it, we get a random float uniformly distributed between 0.0 (included) and 1.0 (excluded). /// /// #### Erlang example @@ -268,12 +267,10 @@ pub fn float() -> Generator(Float) { /// ``` /// pub fn integer(n: Int) -> Generator(Int) { - float() - |> map(fn(x) { - x *. int.to_float(n) - |> float.floor - |> float.round - }) + use x <- map(float()) + x *. int.to_float(n) + |> float.floor + |> float.round } /// Creates a generator for boolean values. @@ -304,8 +301,8 @@ pub fn integer(n: Int) -> Generator(Int) { /// ``` /// pub fn boolean() -> Generator(Bool) { - float() - |> map(fn(x) { x <. 0.5 }) + use x <- map(float()) + x <. 0.5 } /// Creates a generator that randomly selects an element from a list. @@ -356,7 +353,7 @@ pub fn element_of_list(ls: List(a)) -> Generator(Result(a, Nil)) { ls |> list.length |> integer - |> map(fn(n) { list.at(ls, n) }) + |> map(list.at(ls, _)) } /// Creates a generator that changes the order of the elements in a list. @@ -386,49 +383,36 @@ pub fn element_of_list(ls: List(a)) -> Generator(Result(a, Nil)) { /// ``` /// pub fn shuffled_list(ls: List(a)) -> Generator(List(a)) { - let move_to_edge = fn(acc_ls) { - case acc_ls { - [] -> always([]) - [x] -> always([x]) - _ -> - list.length(acc_ls) - 1 - |> integer - |> map2( - integer(6), - fn(i, cs) { - let #(before, rest) = list.split(acc_ls, i + 1) - let #(elem, after) = list.split(rest, 1) - case cs { - 0 -> list.flatten([elem, before, after]) - 1 -> list.flatten([elem, after, before]) - 2 -> list.flatten([before, elem, after]) - 3 -> list.flatten([before, after, elem]) - 4 -> list.flatten([after, elem, before]) - 5 -> list.flatten([after, before, elem]) - } - }, - ) + case ls { + [] -> always([]) + _ -> { + let gen = + ls + |> list.length + |> list.range(1) + |> list.map(integer) + |> sequence + { + use indexes <- map(gen) + use acc, i <- list.fold(indexes, #([], ls)) + let #(selected_elems, rest_elems) = acc + let #(before, [chosen, ..after]) = list.split(rest_elems, i) + let next_selected_elems = list.prepend(selected_elems, chosen) + let next_rest_elems = list.append(before, after) + #(next_selected_elems, next_rest_elems) + } + |> map(pair.first) } } - - list.fold(ls, always(ls), fn(acc, _) { then(acc, move_to_edge) }) } -fn number_graphemes() -> List(String) { - ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] -} - -fn lower_graphemes() -> List(String) { +fn graphemes() -> List(String) { [ - "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", - "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", - ] -} - -fn upper_graphemes() -> List(String) { - [ - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", - "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", + "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", + "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", ] } @@ -460,56 +444,45 @@ fn upper_graphemes() -> List(String) { /// ``` /// pub fn string(n: Int) -> Generator(String) { - 3 - |> integer - |> then(fn(cs) { - case cs { - 0 -> number_graphemes() - 1 -> lower_graphemes() - 2 -> upper_graphemes() - } - |> element_of_list + graphemes() + |> element_of_list + |> map(fn(r) { + let assert Ok(c) = r + c }) - |> map(result.unwrap(_, "")) |> list(n) |> map(list.reverse) - |> map(list.fold(_, "", string.append)) + |> map(string.join(_, "")) } -fn list_help( +fn list_go( gen: Generator(a), seed: Seed, ls: List(a), n: Int, ) -> #(Seed, List(a)) { - case n < 1 { - True -> #(seed, ls) - False -> { + case n { + 0 -> #(seed, ls) + _ -> { let Generator(f) = gen let #(next_seed, value) = f(seed) - let next_ls = list.append([value], ls) - list_help(gen, next_seed, next_ls, n - 1) + let next_ls = list.prepend(ls, value) + list_go(gen, next_seed, next_ls, n - 1) } } } pub fn list(gen: Generator(a), n: Int) -> Generator(List(a)) { - let f = fn(seed) { list_help(gen, seed, [], n) } + let f = fn(seed) { list_go(gen, seed, [], n) } Generator(f) } pub fn sequence(gens: List(Generator(a))) -> Generator(List(a)) { - list.fold( - gens, - always([]), - fn(acc_gen, next_gen) { - then( - acc_gen, - fn(acc_ls) { - map(next_gen, fn(next_head) { list.append([next_head], acc_ls) }) - }, - ) - }, - ) + { + use acc_gen, next_gen <- list.fold(gens, always([])) + use acc_ls <- then(acc_gen) + use next_head <- map(next_gen) + list.prepend(acc_ls, next_head) + } |> map(list.reverse) } diff --git a/src/minigen/interop.gleam b/src/minigen/interop.gleam index f6f65e6..1039e32 100644 --- a/src/minigen/interop.gleam +++ b/src/minigen/interop.gleam @@ -1,15 +1,15 @@ -pub external type Algorithm +pub type Algorithm -pub external type State +pub type State -pub external fn default_algorithm() -> Algorithm = - "native_rand_mimigen" "default_algorithm" +@external(erlang, "native_rand_mimigen", "default_algorithm") +pub fn default_algorithm() -> Algorithm -pub external fn seed_s(Algorithm) -> State = - "rand" "seed_s" +@external(erlang, "rand", "seed_s") +pub fn seed_s(algorithm: Algorithm) -> State -pub external fn seed(Algorithm, Int) -> State = - "rand" "seed" +@external(erlang, "rand", "seed") +pub fn seed(algorithm: Algorithm, int: Int) -> State -pub external fn uniform_s(State) -> #(Float, State) = - "rand" "uniform_s" +@external(erlang, "rand", "uniform_s") +pub fn uniform_s(state: State) -> #(Float, State) diff --git a/src/native_rand_mimigen.erl b/src/native_rand_mimigen.erl index bcbdc90..e84262c 100644 --- a/src/native_rand_mimigen.erl +++ b/src/native_rand_mimigen.erl @@ -3,4 +3,4 @@ -export([default_algorithm/0]). default_algorithm() -> - exsss. + exs1024s. diff --git a/test/minigen/minigen/interop_test.gleam b/test/minigen/minigen/interop_test.gleam index f95ef76..3c0d835 100644 --- a/test/minigen/minigen/interop_test.gleam +++ b/test/minigen/minigen/interop_test.gleam @@ -6,9 +6,9 @@ pub fn uniform_s_test() { interop.default_algorithm() |> interop.seed(1000) let #(x, new_state) = interop.uniform_s(init_state) - should.equal(x, 0.7109364198110805) + should.equal(x, 0.27586903946041397) let #(y, _) = interop.uniform_s(init_state) - should.equal(y, 0.7109364198110805) + should.equal(y, 0.27586903946041397) let #(z, _) = interop.uniform_s(new_state) - should.equal(z, 0.47372875562526207) + should.equal(z, 0.1952355138836377) } diff --git a/test/minigen/minigen_test.gleam b/test/minigen/minigen_test.gleam index 4857215..3bec49d 100644 --- a/test/minigen/minigen_test.gleam +++ b/test/minigen/minigen_test.gleam @@ -19,11 +19,11 @@ pub fn always_test() { pub fn float_test() { minigen.float() |> minigen.run_with_seed(1000) - |> should.equal(0.7109364198110805) + |> should.equal(0.27586903946041397) minigen.float() |> minigen.run_with_seed(999) - |> should.equal(0.4944539429884903) + |> should.equal(0.14229440857120912) let random_float = minigen.float() @@ -37,11 +37,11 @@ pub fn float_test() { pub fn integer_test() { minigen.integer(10) |> minigen.run_with_seed(1000) - |> should.equal(7) + |> should.equal(2) minigen.integer(10) |> minigen.run_with_seed(999) - |> should.equal(4) + |> should.equal(1) let random_integer = minigen.integer(5) @@ -55,33 +55,33 @@ pub fn integer_test() { pub fn boolean_test() { minigen.boolean() |> minigen.run_with_seed(1000) - |> should.equal(False) + |> should.equal(True) minigen.boolean() - |> minigen.run_with_seed(999) - |> should.equal(True) + |> minigen.run_with_seed(1001) + |> should.equal(False) } pub fn element_of_list_test() { [False, False, True, False] |> minigen.element_of_list |> minigen.run_with_seed(1000) - |> should.equal(Ok(True)) + |> should.equal(Ok(False)) - [0.5348931595479329, 0.47372875562526207, 0.7109364198110805] + [0.5348931595479329, 0.1952355138836377, 0.27586903946041397] |> minigen.element_of_list |> minigen.run_with_seed(1000) - |> should.equal(Ok(0.7109364198110805)) + |> should.equal(Ok(0.5348931595479329)) [2, 9, 5, 4, 7] |> minigen.element_of_list |> minigen.run_with_seed(1000) - |> should.equal(Ok(4)) + |> should.equal(Ok(9)) [2, 9, 5, 4, 7] |> minigen.element_of_list |> minigen.run_with_seed(999) - |> should.equal(Ok(5)) + |> should.equal(Ok(2)) [6] |> minigen.element_of_list @@ -108,63 +108,63 @@ pub fn shuffled_list_test() { [False, False, True, False] |> minigen.shuffled_list |> minigen.run_with_seed(1000) - |> should.equal([False, False, False, True]) + |> should.equal([True, False, False, False]) - [0.5348931595479329, 0.47372875562526207, 0.7109364198110805] + [0.5348931595479329, 0.1952355138836377, 0.27586903946041397] |> minigen.shuffled_list |> minigen.run_with_seed(1000) - |> should.equal([0.47372875562526207, 0.5348931595479329, 0.7109364198110805]) + |> should.equal([0.27586903946041397, 0.1952355138836377, 0.5348931595479329]) [2, 9, 5, 4, 7] |> minigen.shuffled_list |> minigen.run_with_seed(1000) - |> should.equal([7, 2, 9, 5, 4]) + |> should.equal([4, 5, 7, 2, 9]) [2, 9, 5, 4, 7] |> minigen.shuffled_list |> minigen.run_with_seed(999) - |> should.equal([2, 4, 7, 9, 5]) + |> should.equal([5, 7, 4, 9, 2]) [2, 9, 5, 4, 7] |> minigen.shuffled_list |> minigen.run_with_seed(998) - |> should.equal([4, 7, 5, 9, 2]) + |> should.equal([4, 9, 2, 7, 5]) [1, 2, 3] |> minigen.shuffled_list |> minigen.list(30) |> minigen.run_with_seed(1000) |> should.equal([ + [2, 3, 1], + [2, 1, 3], + [2, 1, 3], + [2, 1, 3], [1, 2, 3], + [1, 3, 2], + [2, 1, 3], + [3, 2, 1], [1, 2, 3], - [3, 1, 2], - [2, 3, 1], [1, 3, 2], [3, 1, 2], [3, 1, 2], + [2, 1, 3], [3, 1, 2], [3, 2, 1], + [1, 3, 2], + [2, 3, 1], [3, 2, 1], [1, 2, 3], - [2, 1, 3], - [3, 1, 2], - [2, 1, 3], - [2, 3, 1], - [2, 3, 1], + [3, 2, 1], [1, 2, 3], - [1, 3, 2], - [2, 3, 1], [1, 2, 3], + [3, 1, 2], [3, 2, 1], [1, 3, 2], [1, 3, 2], - [1, 3, 2], - [1, 3, 2], + [2, 1, 3], [1, 2, 3], [3, 2, 1], - [2, 1, 3], [3, 2, 1], - [2, 1, 3], ]) [False, True] @@ -172,26 +172,26 @@ pub fn shuffled_list_test() { |> minigen.list(20) |> minigen.run_with_seed(1000) |> should.equal([ - [False, True], [False, True], [True, False], [False, True], [False, True], - [False, True], [True, False], [False, True], [False, True], [False, True], - [False, True], [True, False], [False, True], [True, False], - [True, False], + [False, True], + [False, True], [True, False], [True, False], [True, False], [False, True], + [True, False], [False, True], + [True, False], ]) [6] @@ -219,22 +219,22 @@ pub fn string_test() { 4 |> minigen.string |> minigen.run_with_seed(1000) - |> should.equal("Mx01") + |> should.equal("rmPg") 5 |> minigen.string |> minigen.run_with_seed(1000) - |> should.equal("Mx01l") + |> should.equal("rmPgD") 5 |> minigen.string |> minigen.run_with_seed(999) - |> should.equal("euyO3") + |> should.equal("ikD7Q") 1 |> minigen.string |> minigen.run_with_seed(1000) - |> should.equal("M") + |> should.equal("r") 0 |> minigen.string @@ -246,22 +246,22 @@ pub fn list_test() { minigen.boolean() |> minigen.list(4) |> minigen.run_with_seed(1000) - |> should.equal([False, False, True, False]) + |> should.equal([True, False, True, True]) minigen.float() |> minigen.list(3) |> minigen.run_with_seed(1000) - |> should.equal([0.5348931595479329, 0.47372875562526207, 0.7109364198110805]) + |> should.equal([0.6724156678264623, 0.1952355138836377, 0.27586903946041397]) minigen.integer(10) |> minigen.list(5) |> minigen.run_with_seed(1000) - |> should.equal([2, 9, 5, 4, 7]) + |> should.equal([4, 1, 6, 1, 2]) minigen.integer(10) |> minigen.list(5) |> minigen.run_with_seed(999) - |> should.equal([5, 7, 3, 1, 4]) + |> should.equal([6, 9, 4, 1, 1]) minigen.integer(5) |> minigen.list(0) @@ -271,7 +271,7 @@ pub fn list_test() { minigen.integer(5) |> minigen.list(1) |> minigen.run_with_seed(1000) - |> should.equal([3]) + |> should.equal([1]) } pub fn sequence_test() { @@ -291,31 +291,31 @@ pub fn sequence_test() { ] |> minigen.sequence |> minigen.run_with_seed(1000) - |> should.equal([0, 0, 1, 3, 1, 0, 0, 0, 4, 4, 5, 9]) + |> should.equal([0, 0, 2, 0, 2, 2, 6, 5, 2, 8, 4, 0]) minigen.boolean() |> list.repeat(4) |> minigen.sequence |> minigen.run_with_seed(1000) - |> should.equal([False, True, False, False]) + |> should.equal([True, True, False, True]) minigen.float() |> list.repeat(3) |> minigen.sequence |> minigen.run_with_seed(1000) - |> should.equal([0.7109364198110805, 0.47372875562526207, 0.5348931595479329]) + |> should.equal([0.27586903946041397, 0.1952355138836377, 0.6724156678264623]) minigen.integer(10) |> list.repeat(5) |> minigen.sequence |> minigen.run_with_seed(1000) - |> should.equal([7, 4, 5, 9, 2]) + |> should.equal([2, 1, 6, 1, 4]) minigen.integer(10) |> list.repeat(5) |> minigen.sequence |> minigen.run_with_seed(999) - |> should.equal([4, 1, 3, 7, 5]) + |> should.equal([1, 1, 4, 9, 6]) [] |> minigen.sequence @@ -325,5 +325,5 @@ pub fn sequence_test() { [minigen.integer(5)] |> minigen.sequence |> minigen.run_with_seed(1000) - |> should.equal([3]) + |> should.equal([1]) }