# mruby-random

mruby-random is an mrbgem that provides pseudo-random number generation facilities for mruby.

## Features

- Provides `Kernel#rand` method for generating pseudo-random numbers.
- Supports generating random numbers within a specific range.
- Allows setting a seed for reproducible random number sequences using `Kernel#srand`.

## Global Random Number Generation

### Generating a random number

To generate a pseudo-random floating-point number between 0.0 (inclusive) and 1.0 (exclusive):

```ruby
r = rand
p r # => 0.31415926535
```

To generate a pseudo-random integer number between 0 (inclusive) and a given maximum integer (exclusive):

```ruby
r = rand(100)
p r # => 42
```

To generate a pseudo-random integer within a given `Range`:

```ruby
r = rand(10..20) # or rand(10...20)
p r # => 15 (e.g., between 10 and 20, or 10 and 19)
```

### Seeding the random number generator

To initialize the pseudo-random number generator with a specific seed:

```ruby
srand(12345)
p rand(100) # => 81
p rand(100) # => 81 (if you re-seed with srand(12345) again)

# Using the same seed will produce the same sequence of random numbers
srand(12345)
p rand(100) # => 81
srand(12345)
p rand(100) # => 81
```

## The `Random` Class

Besides the global `Kernel#rand` and `Kernel#srand` methods, `mruby-random` also provides a `Random` class for managing separate random number generators.

### Creating an Instance

You can create a new instance of the `Random` class with a system-generated seed:

```ruby
rng = Random.new
p rng.rand(100)
```

Or you can provide a specific seed:

```ruby
rng = Random.new(12345)
p rng.rand(100) # => 81
```

### Instance Methods

#### `rand`

The `rand` instance method behaves similarly to `Kernel#rand`, but operates on the specific `Random` instance.

- Called with no arguments, it returns a pseudo-random floating-point number between 0.0 (inclusive) and 1.0 (exclusive).

  ```ruby
  rng = Random.new
  p rng.rand # => 0.123456789
  ```

- Called with an integer `max` argument, it returns a pseudo-random integer between 0 (inclusive) and `max` (exclusive).

  ```ruby
  rng = Random.new
  p rng.rand(50) # => 23
  ```

- Called with a `Range` argument (`min..max` or `min...max`), it returns a pseudo-random integer within that range (inclusive of `min`, and inclusive or exclusive of `max` depending on the range type).

  ```ruby
  rng = Random.new
  p rng.rand(10..20) # => 15 (between 10 and 20, inclusive)
  p rng.rand(10...20) # => 12 (between 10 and 19, inclusive)
  ```

#### `srand`

The `srand` instance method is used to seed the specific `Random` instance. It allows you to re-initialize the random number generator for that instance with a specific seed, making its sequence of generated numbers predictable.

```ruby
rng = Random.new(111)
p rng.rand(1000) # => 100
p rng.rand(1000) # => 283

rng.srand(111) # Re-seed the same instance
p rng.rand(1000) # => 100 (sequence repeats for this instance)

rng2 = Random.new(111) # A different instance with the same seed
p rng2.rand(1000) # => 100
```

It is important to note that `Random#srand` is an alias for `Random#initialize`. Re-seeding an existing `Random` object will reset its internal state.

#### `bytes(n)`

The `bytes` method returns a string containing `n` pseudo-random bytes.

```ruby
rng = Random.new
p rng.bytes(5) # => "\xAB\xCD\xEF\x12\x34" (example output)
```

### Class Methods (Using the Default Generator)

The `Random` class also provides class methods that operate on a global, default random number generator. This is the same generator used by `Kernel#rand` and `Kernel#srand`.

#### `Random.rand`

This method is equivalent to `Kernel.rand` (or simply `rand`).

- With no arguments, returns a float between 0.0 and 1.0 (exclusive of 1.0):

  ```ruby
  p Random.rand # => 0.7654321
  ```

- With an integer `max` argument, returns an integer between 0 and `max` (exclusive of `max`):

  ```ruby
  p Random.rand(10) # => 7
  ```

- With a `Range` argument, returns an integer within the range:

  ```ruby
  p Random.rand(50..60) # => 53
  ```

#### `Random.srand(seed)`

This method is equivalent to `Kernel.srand(seed)` (or simply `srand(seed)`). It seeds the global default random number generator.

```ruby
Random.srand(123)
p Random.rand(100) # => 13
p rand(100)        # => 80 (uses the same seeded generator)

Random.srand(123)
p Random.rand(100) # => 13 (sequence repeats)
```

#### `Random.bytes(n)`

This method returns a string containing `n` pseudo-random bytes, generated by the global default random number generator.

```ruby
p Random.bytes(3) # => "\xDE\xAD\xBE" (example output)
```

## Array Methods

`mruby-random` extends the `Array` class with methods for shuffling elements and sampling random elements. These methods can optionally accept a `Random` instance to use a specific random number generator.

### `shuffle`

The `shuffle` method returns a _new_ array with the elements of the original array in a random order.

```ruby
a = [1, 2, 3, 4, 5]
p a.shuffle # => [3, 1, 5, 2, 4] (example output)
p a         # => [1, 2, 3, 4, 5] (original array is unchanged)
```

You can provide a specific `Random` instance using the `random:` keyword argument. This is useful for reproducible shuffling.

```ruby
rng = Random.new(123)
a = [1, 2, 3, 4, 5]
p a.shuffle(random: rng) # => [1, 5, 3, 2, 4] (example output, will be consistent with seed 123)

rng2 = Random.new(123) # Same seed
p a.shuffle(random: rng2) # => [1, 5, 3, 2, 4] (same shuffled order)
```

### `shuffle!`

The `shuffle!` method shuffles the elements of the array in-place. It modifies the original array.

```ruby
a = [1, 2, 3, 4, 5]
p a.shuffle! # => [4, 2, 1, 5, 3] (example output)
p a          # => [4, 2, 1, 5, 3] (original array is modified)
```

Similarly to `shuffle`, you can provide a specific `Random` instance using the `random:` keyword argument.

```ruby
rng = Random.new(456)
a = [:a, :b, :c, :d, :e]
p a.shuffle!(random: rng) # => [:c, :a, :e, :d, :b] (example output, consistent with seed 456)
p a                       # => [:c, :a, :e, :d, :b] (original array is modified)
```

### `sample`

The `sample` method chooses one or more random elements from the array.

- When called with no arguments, it returns a single random element from the array. If the array is empty, it returns `nil`.

  ```ruby
  a = ["apple", "banana", "cherry", "date"]
  p a.sample # => "cherry" (example output)

  empty_array = []
  p empty_array.sample # => nil
  ```

- When called with an integer `n` as an argument, it returns a new array containing `n` unique random elements from the original array. If the array does not have enough unique elements, it returns all elements in a shuffled order. If the array is empty, it returns an empty array.

  ```ruby
  a = ["apple", "banana", "cherry", "date", "elderberry"]
  p a.sample(3) # => ["date", "apple", "banana"] (example output)
  p a.sample(10) # => ["banana", "elderberry", "apple", "date", "cherry"] (all elements, shuffled)


  empty_array = []
  p empty_array.sample(3) # => []
  ```

- You can provide a specific `Random` instance using the `random:` keyword argument for both forms of `sample`.

  ```ruby
  rng = Random.new(789)
  a = [10, 20, 30, 40, 50]
  p a.sample(random: rng)    # => 30 (example output, consistent with seed 789)

  rng_b = Random.new(789) # Re-initialize with the same seed for predictable multi-sampling
  p a.sample(2, random: rng_b) # => [30, 50] (example output)
  ```

## Algorithm

The `mruby-random` mrbgem primarily utilizes the **xoshiro128++ 1.0** algorithm for pseudo-random number generation.

In certain 32-bit configurations (specifically when `MRB_INT32` is defined and `MRB_64BIT` is not), a different algorithm, **XORSHIFT96**, might be employed.

## License

The `mruby-random` mrbgem is released under the MIT License. See the [LICENSE](LICENSE) file for details.
