-
-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move random number generation to the Random type
This refactors std::random (now renamed to std::rand) to use a type (Random) for random number generation, instead of using module methods. Using a type makes it possible to customise the seed used for creating a random number generator. As part of this we also introduce the Shuffle type, used for randomly sorting arrays, and used by Array.shuffle. Moving this logic into a dedicated type means we can shuffle with fixed seeds, without having to add more methods to the Array type. This fixes https://gitlab.com/inko-lang/inko/-/issues/272. Changelog: added
- Loading branch information
1 parent
a0fe7a6
commit 6991a8d
Showing
12 changed files
with
299 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
# Cryptographically secure random number generation. | ||
import std::drop::Drop | ||
|
||
# A cryptographically secure pseudo random number generator (CSPRNG). | ||
# | ||
# The algorithm used is unspecified but guaranteed to be cryptographically | ||
# secure. | ||
class pub Random { | ||
# The internal/low-level random number generator. | ||
let @rng: Any | ||
|
||
# Returns a new `Random` using the given `Int` as its seed. | ||
# | ||
# `Random` instances created using this method **are not** suitable for | ||
# cryptography, as a single `Int` doesn't produce enough entropy. For | ||
# cryptography you _must_ use `Random.new` instead. | ||
# | ||
# # Examples | ||
# | ||
# import std::rand::Random | ||
# | ||
# Random.from_int(42) | ||
fn pub static from_int(seed: Int) -> Self { | ||
Self { @rng = _INKO.random_from_int(seed) } | ||
} | ||
|
||
# Returns a new `Random` seeded using a cryptographically secure seed. | ||
# | ||
# Seeding is performed by the runtime using a thread-local random number | ||
# generator suitable for cryptography. | ||
# | ||
# # Examples | ||
# | ||
# import std::rand::Random | ||
# | ||
# Random.new | ||
fn pub static new -> Self { | ||
Self { @rng = _INKO.random_new } | ||
} | ||
|
||
# Returns a randomly generated `Int`. | ||
# | ||
# # Examples | ||
# | ||
# import std::rand::Random | ||
# | ||
# Random.new.int | ||
fn pub mut int -> Int { | ||
_INKO.random_int(@rng) | ||
} | ||
|
||
# Returns a randomly generated `Float`. | ||
# | ||
# # Examples | ||
# | ||
# import std::rand::Random | ||
# | ||
# Random.new.float | ||
fn pub mut float -> Float { | ||
_INKO.random_float(@rng) | ||
} | ||
|
||
# Returns a randomly generated `Int` in the given range. | ||
# | ||
# The returned value is in the range `start <= value < stop`. If | ||
# `start >= stop` is true, this method returns `0`. | ||
fn pub int_between(min: Int, max: Int) -> Int { | ||
_INKO.random_int_range(@rng, min, max) | ||
} | ||
|
||
# Returns a randomly generated `Float` in the given range. | ||
# | ||
# The returned value is in the range `start <= value < stop`. If | ||
# `start >= stop` is true, this method returns `0.0`. | ||
fn pub float_between(min: Float, max: Float) -> Float { | ||
_INKO.random_float_range(@rng, min, max) | ||
} | ||
|
||
# Returns a `ByteArray` containing randomly generated bytes. | ||
# | ||
# The returned `ByteArray` will contain exactly `size` bytes. | ||
# | ||
# # Panics | ||
# | ||
# This method might panic if no random bytes could be generated. | ||
fn pub bytes(size: Int) -> ByteArray { | ||
_INKO.random_bytes(@rng, size) | ||
} | ||
} | ||
|
||
impl Drop for Random { | ||
fn mut drop { | ||
_INKO.random_drop(@rng) | ||
} | ||
} | ||
|
||
# A type for sorting arrays in a random order. | ||
class pub Shuffle { | ||
let @rng: Random | ||
|
||
# Returns a new `Shuffle` that sorts values in a random order. | ||
fn pub static new -> Self { | ||
Self { @rng = Random.new } | ||
} | ||
|
||
# Returns a new `Shuffle` that uses the given seed for sorting values. | ||
fn pub static from_int(seed: Int) -> Self { | ||
Self { @rng = Random.from_int(seed) } | ||
} | ||
|
||
# Sorts the values of the given `Array` in place such that they are in a | ||
# random order. | ||
# | ||
# The algorithm used by this method is Sattolo's algorithm. Some more details | ||
# on this are found here: | ||
# | ||
# - https://en.wikipedia.org/wiki/Fisher–Yates_shuffle#Sattolo's_algorithm | ||
# - https://danluu.com/sattolo/ | ||
# - https://rosettacode.org/wiki/Sattolo_cycle | ||
# | ||
# # Examples | ||
# | ||
# import std::rand::Shuffle | ||
# | ||
# let a = [10, 20] | ||
# | ||
# Shuffle.new.sort(a) | ||
# a # => [20, 10] | ||
fn pub mut sort[T](array: mut Array[T]) { | ||
# Note that the types produced by `array_get()` and `array_set()` are `Any`. | ||
# These values aren't dropped automatically, so there's no need to mark them | ||
# as moved to prevent them from being dropped after the swap. | ||
let mut swap = array.length - 1 | ||
|
||
while swap > 0 { | ||
let swap_with = @rng.int_between(min: 0, max: swap) | ||
let swap_val = _INKO.array_get(array, swap) | ||
|
||
_INKO.array_set(array, swap, _INKO.array_set(array, swap_with, swap_val)) | ||
|
||
swap -= 1 | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import helpers::(fmt, hash) | ||
import std::rand::(Random, Shuffle) | ||
import std::test::Tests | ||
|
||
fn pub tests(t: mut Tests) { | ||
t.test('Random.from_int') fn (t) { | ||
let rng = Random.from_int(42) | ||
|
||
# Since we have a fixed seed, we also have a fixed output. | ||
t.equal(rng.int, -8733474309719776094) | ||
t.equal(rng.float, 0.5427252099031439) | ||
t.equal(rng.bytes(3), ByteArray.from_array([209, 52, 81])) | ||
} | ||
|
||
t.test('Random.int') fn (t) { | ||
# This is just a smoke test to ensure the underlying code isn't outright | ||
# wrong. | ||
Random.new.int | ||
} | ||
|
||
t.test('Random.float') fn (t) { | ||
# This is just a smoke test to ensure the underlying code isn't outright | ||
# wrong. | ||
Random.new.float | ||
} | ||
|
||
t.test('Random.int_between') fn (t) { | ||
let rng = Random.new | ||
let range1 = rng.int_between(min: 1, max: 5) | ||
let range2 = rng.int_between(min: 1, max: 1) | ||
let range3 = rng.int_between(min: 10, max: 1) | ||
|
||
t.true(range1 >= 1 and range1 < 5) | ||
t.equal(range2, 0) | ||
t.equal(range3, 0) | ||
} | ||
|
||
t.test('Random.float_between') fn (t) { | ||
let rng = Random.new | ||
let range1 = rng.float_between(min: 1.0, max: 5.0) | ||
let range2 = rng.float_between(min: 1.0, max: 1.0) | ||
let range3 = rng.float_between(min: 10.0, max: 1.0) | ||
|
||
t.true(range1 >= 1.0 and range1 < 5.0) | ||
t.equal(range2, 0.0) | ||
t.equal(range3, 0.0) | ||
} | ||
|
||
t.test('Random.bytes') fn (t) { | ||
let rng = Random.new | ||
|
||
t.equal(rng.bytes(3).length, 3) | ||
} | ||
|
||
t.test('Shuffle.sort') fn (t) { | ||
let one = [10] | ||
let two = [10, 20] | ||
let three = [10, 20, 30] | ||
let shuffle = Shuffle.from_int(42) | ||
|
||
shuffle.sort(one) | ||
shuffle.sort(two) | ||
shuffle.sort(three) | ||
|
||
t.equal(one, [10]) | ||
t.equal(two, [20, 10]) | ||
t.equal(three, [20, 30, 10]) | ||
} | ||
} |
Oops, something went wrong.