Skip to content

Commit

Permalink
Simplify random
Browse files Browse the repository at this point in the history
  • Loading branch information
lpil committed Dec 17, 2023
1 parent 86c2555 commit 78980c3
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 154 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- The `int.random` function now takes a single argument.
- The `float.random` function no longer takes any arguments.
- Changed `list.index_map` callback signature to `fn(a, Int) -> b` from
`fn(Int, a) -> b`, to be consistent with `list.index_fold`.
- Changed `iterator.index` to return `Iterator(#(a, Int))` instead of
Expand Down
19 changes: 7 additions & 12 deletions src/gleam/float.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -416,27 +416,22 @@ fn do_product(numbers: List(Float), initial: Float) -> Float {
}
}

/// Generates a random float between the given minimum and maximum values.
/// Generates a random float between the given zero (inclusive) and one
/// (exclusive).
///
/// On Erlang this updates the random state in the process dictionary.
/// See: <https://www.erlang.org/doc/man/rand.html#uniform-0>
///
/// ## Examples
///
/// ```gleam
/// > random(1.0, 5.0)
/// 2.646355926896028
/// > random()
/// 0.646355926896028
/// ```
///
pub fn random(min: Float, max: Float) -> Float {
do_random_uniform() *. { max -. min } +. min
}

/// Returns a random float uniformly distributed in the value range
/// 0.0 =< X < 1.0 and updates the state in the process dictionary.
/// See: <https://www.erlang.org/doc/man/rand.html#uniform-0>
///
@external(erlang, "rand", "uniform")
@external(javascript, "../gleam_stdlib.mjs", "random_uniform")
fn do_random_uniform() -> Float
pub fn random() -> Float

/// Returns division of the inputs as a `Result`.
///
Expand Down
22 changes: 17 additions & 5 deletions src/gleam/int.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -526,17 +526,29 @@ fn do_undigits(
}
}

/// Generates a random int between the given minimum and maximum values.
/// Generates a random int between zero and the given maximum.
///
/// The lower number is inclusive, the upper number is exclusive.
///
/// ## Examples
///
/// ```gleam
/// > random(1, 5)
/// 2
/// > random(10)
/// 4
/// ```
///
/// ```gleam
/// > random(1)
/// 0
/// ```
///
/// ```gleam
/// > random(-1)
/// -1
/// ```
///
pub fn random(min: Int, max: Int) -> Int {
float.random(to_float(min), to_float(max))
pub fn random(max: Int) -> Int {
{ float.random() *. to_float(max) }
|> float.floor()
|> float.round()
}
Expand Down
102 changes: 40 additions & 62 deletions src/gleam/list.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -428,15 +428,11 @@ pub fn map_fold(
from acc: acc,
with fun: fn(acc, a) -> #(acc, b),
) -> #(acc, List(b)) {
fold(
over: list,
from: #(acc, []),
with: fn(acc, item) {
let #(current_acc, items) = acc
let #(next_acc, next_item) = fun(current_acc, item)
#(next_acc, [next_item, ..items])
},
)
fold(over: list, from: #(acc, []), with: fn(acc, item) {
let #(current_acc, items) = acc
let #(next_acc, next_item) = fun(current_acc, item)
#(next_acc, [next_item, ..items])
})
|> pair.map_second(reverse)
}

Expand Down Expand Up @@ -1438,16 +1434,13 @@ pub fn key_find(
in keyword_list: List(#(k, v)),
find desired_key: k,
) -> Result(v, Nil) {
find_map(
keyword_list,
fn(keyword) {
let #(key, value) = keyword
case key == desired_key {
True -> Ok(value)
False -> Error(Nil)
}
},
)
find_map(keyword_list, fn(keyword) {
let #(key, value) = keyword
case key == desired_key {
True -> Ok(value)
False -> Error(Nil)
}
})
}

/// Given a list of 2-element tuples, finds all tuples that have a given
Expand All @@ -1472,16 +1465,13 @@ pub fn key_filter(
in keyword_list: List(#(k, v)),
find desired_key: k,
) -> List(v) {
filter_map(
keyword_list,
fn(keyword) {
let #(key, value) = keyword
case key == desired_key {
True -> Ok(value)
False -> Error(Nil)
}
},
)
filter_map(keyword_list, fn(keyword) {
let #(key, value) = keyword
case key == desired_key {
True -> Ok(value)
False -> Error(Nil)
}
})
}

fn do_pop(haystack, predicate, checked) {
Expand Down Expand Up @@ -1590,16 +1580,13 @@ pub fn key_pop(
haystack: List(#(k, v)),
key: k,
) -> Result(#(v, List(#(k, v))), Nil) {
pop_map(
haystack,
fn(entry) {
let #(k, v) = entry
case k {
k if k == key -> Ok(v)
_ -> Error(Nil)
}
},
)
pop_map(haystack, fn(entry) {
let #(k, v) = entry
case k {
k if k == key -> Ok(v)
_ -> Error(Nil)
}
})
}

/// Given a list of 2-element tuples, inserts a key and value into the list.
Expand Down Expand Up @@ -1720,15 +1707,12 @@ pub fn permutations(l: List(a)) -> List(List(a)) {
l
|> index_map(fn(i, i_idx) {
l
|> index_fold(
[],
fn(acc, j, j_idx) {
case i_idx == j_idx {
True -> acc
False -> [j, ..acc]
}
},
)
|> index_fold([], fn(acc, j, j_idx) {
case i_idx == j_idx {
True -> acc
False -> [j, ..acc]
}
})
|> reverse
|> permutations
|> map(fn(permutation) { [i, ..permutation] })
Expand Down Expand Up @@ -2030,11 +2014,9 @@ pub fn combinations(items: List(a), by n: Int) -> List(List(a)) {
let first_combinations =
map(combinations(xs, n - 1), with: fn(com) { [x, ..com] })
|> reverse
fold(
first_combinations,
combinations(xs, n),
fn(acc, c) { [c, ..acc] },
)
fold(first_combinations, combinations(xs, n), fn(acc, c) {
[c, ..acc]
})
}
}
}
Expand Down Expand Up @@ -2125,18 +2107,14 @@ fn do_shuffle_pair_unwrap(list: List(#(Float, a)), acc: List(a)) -> List(a) {
fn do_shuffle_by_pair_indexes(
list_of_pairs: List(#(Float, a)),
) -> List(#(Float, a)) {
sort(
list_of_pairs,
fn(a_pair: #(Float, a), b_pair: #(Float, a)) -> Order {
float.compare(a_pair.0, b_pair.0)
},
)
sort(list_of_pairs, fn(a_pair: #(Float, a), b_pair: #(Float, a)) -> Order {
float.compare(a_pair.0, b_pair.0)
})
}

/// Takes a list, randomly sorts all items and returns the shuffled list.
///
/// This function uses Erlang's `:rand` module or Javascript's
/// `Math.random()` to calculate the index shuffling.
/// This function uses `float.random` to decide the order of the elements.
///
/// ## Example
///
Expand All @@ -2148,7 +2126,7 @@ fn do_shuffle_by_pair_indexes(
///
pub fn shuffle(list: List(a)) -> List(a) {
list
|> fold(from: [], with: fn(acc, a) { [#(float.random(0.0, 1.0), a), ..acc] })
|> fold(from: [], with: fn(acc, a) { [#(float.random(), a), ..acc] })
|> do_shuffle_by_pair_indexes()
|> do_shuffle_pair_unwrap([])
}
66 changes: 19 additions & 47 deletions test/gleam/float_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -348,55 +348,27 @@ pub fn product_test() {
}

pub fn random_test() {
let test_boundaries = fn(_accumulator, _element) {
float.random(0.0, 0.0)
|> should.equal(0.0)

float.random(0.0, 10.0)
|> fn(x) { x >=. 0.0 && x <. 10.0 }
|> should.be_true

float.random(10.0, 0.0)
|> fn(x) { x >=. 0.0 && x <. 10.0 }
|> should.be_true

float.random(0.0, -10.0)
|> fn(x) { x >=. -10.0 && x <. 0.0 }
|> should.be_true

float.random(-10.0, 0.0)
|> fn(x) { x >=. -10.0 && x <. 0.0 }
|> should.be_true

float.random(-10.0, 10.0)
|> fn(x) { x >=. -10.0 && x <. 10.0 }
|> should.be_true

float.random(10.0, -10.0)
|> fn(x) { x >=. -10.0 && x <. 10.0 }
|> should.be_true
}
list.range(0, 100)
|> iterator.from_list()
|> iterator.fold(Nil, test_boundaries)

let test_mean = fn(iterations: Int, min: Float, max: Float, tolerance: Float) {
let expected_average = float.sum([min, max]) /. 2.0
let expected_average = 0.5
let iterations = 10_000
let sum =
list.range(0, iterations)
|> iterator.from_list()
|> iterator.fold(
from: 0.0,
with: fn(accumulator, _element) { accumulator +. float.random(min, max) },
)
|> fn(sum) { sum /. int.to_float(iterations) }
|> float.loosely_equals(expected_average, tolerance)
|> should.be_true
}
test_mean(100, 0.0, 0.0, 5.0)
test_mean(1000, 0.0, 100.0, 5.0)
test_mean(1000, -100.0, 100.0, 5.0)
test_mean(1000, -100.0, 0.0, 5.0)
test_mean(1000, 0.0, -100.0, 5.0)
|> iterator.fold(from: 0.0, with: fn(accumulator, _element) {
let i = float.random()

{ i <. 1.0 }
|> should.be_true
{ i >=. 0.0 }
|> should.be_true

accumulator +. i
})
let average = sum /. int.to_float(iterations)

{ average <. expected_average +. 0.1 }
|> should.be_true
{ average >. expected_average -. 0.1 }
|> should.be_true
}

pub fn divide_test() {
Expand Down
38 changes: 10 additions & 28 deletions test/gleam/int_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -394,44 +394,26 @@ pub fn undigits_test() {

pub fn random_test() {
let test_boundaries = fn(_accumulator, _element) {
int.random(0, 0)
int.random(0)
|> should.equal(0)

int.random(-1, 0)
|> list.contains([-1, 0], _)
|> should.be_true
int.random(1)
|> should.equal(0)

int.random(-1)
|> should.equal(-1)

int.random(-1, 1)
|> list.contains([-1, 0], _)
int.random(2)
|> list.contains([0, 1], _)
|> should.be_true

int.random(-1, 2)
|> list.contains([-1, 0, 1], _)
int.random(3)
|> list.contains([0, 1, 2], _)
|> should.be_true
}
list.range(0, 100)
|> iterator.from_list
|> iterator.fold(Nil, test_boundaries)

let test_average = fn(iterations: Int, min: Int, max: Int, tolerance: Int) {
let expected_average = int.sum([min, max]) / 2
list.range(0, iterations)
|> iterator.from_list
|> iterator.fold(
from: 0,
with: fn(accumulator, _element) { accumulator + int.random(min, max) },
)
|> fn(sum) { sum / iterations }
|> fn(average) {
average - tolerance <= expected_average || average + tolerance >= expected_average
}
|> should.be_true
}
test_average(100, 0, 0, 5)
test_average(1000, 0, 100, 5)
test_average(1000, -100, 100, 5)
test_average(1000, -100, 0, 5)
test_average(1000, 0, -100, 5)
}

pub fn divide_test() {
Expand Down

0 comments on commit 78980c3

Please sign in to comment.