Skip to content

Feature/random #146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,48 @@ for (final i in 10.rangeTo(2).step(2)) {
}
```

## Random

### .nextIntRange()

Gets the next random `int` from the random number generator in the specified range.

```dart
final rand = Random();
final randomNumber = rand.nextIntRange((-10).rangeTo(10).step(2)));
// print a number from [-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10]
```

### .nextDoubleRange()

Gets the next random `double` from the random number generator in the specified range.

```dart
final rand = Random();
final randomNumber = rand.nextDoubleRange(10, 11);
// a random number in range [10.0..11)
```

### .choice()

Returns a random element from this list

```dart
final rand = Random();
final randomNumber = rand.choice([1, 2, 3, 4, 5, 6, 7, 8, 9]);
// choice a number from [1, 2, 3, 4, 5, 6, 7, 8, 9]
```

### .choices()

Returns a list with the randomly selected element from the specified sequence

```dart
final rand = Random();
final randomList = rand.choices([1, 2, 3, 4, 5], weights: [100, 1, 1, 1, 100], length: 4);
// create a list like that [1, 5, 5, 1]
```

## Function

### .invoke() - DEPRECATED
Expand Down
2 changes: 2 additions & 0 deletions lib/dartx.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ part 'src/iterable_num.dart';
part 'src/list.dart';
part 'src/map.dart';
part 'src/num.dart';
part 'src/preconditions.dart';
part 'src/random.dart';
part 'src/range.dart';
part 'src/sorted_list.dart';
part 'src/string.dart';
22 changes: 22 additions & 0 deletions lib/src/preconditions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
part of dartx;

/// Throws an [Exception] with the result of calling [message] if the [value] is false.
// ignore: avoid_positional_boolean_parameters
void require(bool value, [dynamic message = 'Failed requirement.']) {
if (!value) {
throw Exception(message);
}
}

/// Throws an [Exception] with the result of calling [message] if the [value] is null. Otherwise
/// returns the not null value.
T requireNotNull<T>(T? value, [dynamic message = 'Required value was null.']) {
if (value == null) {
throw Exception(message);
} else {
return value;
}
}

/// Throws an [Exception] with the given [message].
void error(dynamic message) => throw Exception(message);
207 changes: 207 additions & 0 deletions lib/src/random.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
part of dartx;

final _rand = Random();

extension RandomNextIntRange on Random {
/// Gets the next random `int` from the random number generator in the specified [range].
///
/// Generates an `int` random value uniformly distributed in the specified [range] :
/// from `range.first` inclusive to `range.endInclusive` inclusive with `range.stepSize`.
///
/// * [throws] IllegalArgumentException if [range] is empty.
///
/// #### Example :
///
/// ```dart
/// final rand = Random();
/// final randomNumber = rand.nextIntRange((-10).rangeTo(10).step(2)));
/// // print a number from [-10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10]
/// ```
int nextIntRange(IntProgression range) {
final start = range.start;
final end = range.endInclusive;
final step = range.stepSize;

_checkRangeBounds(start, end);
return nextInt((end + step - start) ~/ step) * step + start;
}
}

extension RandomNextDoubleRange on Random {
/// Gets the next random `double` from the random number generator in the specified range.
///
/// Generates a `double` random value uniformly distributed between the specified [start] (inclusive) and [end] (exclusive) bounds.
///
/// [start] and [end] must be finite otherwise the behavior is unspecified.
///
/// * [throws] IllegalArgumentException if [start] is greater than or equal to [end].
///
/// #### Example :
///
/// ```dart
/// final rand = Random();
/// final randomNumber = rand.nextDoubleRange(10, 11);
/// // a random number in range [10.0..11)
/// ```
double nextDoubleRange(double start, double end) {
_checkRangeBounds(start, end);

final size = end - start;
final double r;
if (size.isInfinite && start.isFinite && end.isFinite) {
final r1 = nextDouble() * (end / 2 - start / 2);
r = start + r1 + r1;
} else {
r = start + nextDouble() * size;
}
return r >= end ? end.roundToDouble() : r;
}
}

void _checkRangeBounds(num start, num end) =>
require(end > start, 'Cannot get random in empty range: $start..$end');

extension RandomChoice on Random {
/// Returns a random element from this list
///
/// #### Example :
///
/// ```dart
/// final rand = Random();
/// final randomNumber = rand.choice([1, 2, 3, 4, 5, 6, 7, 8, 9]);
/// // choice a number from [1, 2, 3, 4, 5, 6, 7, 8, 9]
/// ```
T choice<T>(List<T> elements) => elements[nextInt(elements.length)];
}

extension RandomChoices on Random {
/// Returns a list with the randomly selected element from the specified sequence
///
/// [weights] Optional. a list were you can weigh the possibility for each value. default null
///
/// [length] Optional. an integer defining the length of the returned list
///
/// #### Example :
///
/// ```dart
/// final rand = Random();
/// final randomList = rand.choices([1, 2, 3, 4, 5], weights: [100, 1, 1, 1, 100], length: 4);
/// // create a list like that [1, 5, 5, 1]
/// ```
List<T> choices<T>(List<T> elements, {List<int>? weights, int length = 1}) {
if (weights != null) {
require(
weights.length == elements.length,
'Weights size must be equals to numbers size',
);
}
final randoms = <T>[];

if (weights != null) {
final random = _RandomWeight(weights);
for (var i = 0; i < length; i++) {
final index = random.pickIndex();
randoms.add(elements[index]);
}
} else {
for (var i = 0; i < length; i++) {
randoms.add(elements.choice());
}
}
return randoms;
}
}

extension IterableChoice<T> on Iterable<T> {
/// Returns a random element from this [Iterable]
///
/// #### Example :
///
/// ```dart
/// final randomNumber = [1, 2, 3, 4, 5, 6, 7, 8, 9].choice();
/// // choice a number from [1, 2, 3, 4, 5, 6, 7, 8, 9]
/// ```
T choice([Random? random]) => (random ?? _rand).choice(toList());
}

extension IterableChoices<T> on Iterable<T> {
/// Returns a list with the randomly selected element from the specified sequence
///
/// [weights] Optional. a list were you can weigh the possibility for each value. default null
///
/// [length] Optional. an integer defining the length of the returned list
///
/// #### Example :
///
/// ```dart
/// final randomList = [1, 2, 3, 4, 5].choices(weights: [100, 1, 1, 1, 100], length: 4);
/// // create a list like that [1, 5, 5, 1]
/// ```
List<T> choices({Random? random, List<int>? weights, int length = 1}) =>
(random ?? _rand).choices(toList(), weights: weights, length: length);
}

extension MapChoice<K, V> on Map<K, V> {
/// Returns a random element from this [Map]
///
/// #### Example :
///
/// ```dart
/// final randomNumber = <int, String>{1: 'a', 2: 'b', 3: 'c'}.choice();
/// // choice a number from {1: 'a', 2: 'b', 3: 'c'}
/// ```
Pair<K, V> choice([Random? random]) {
final choice = (random ?? _rand).choice(keys.toList());
return Pair(choice, this[choice] as V);
}
}

class _RandomWeight {
_RandomWeight(List<int> weights) {
_summedWt = List.filled(weights.length, 0);
_summedWt[0] = weights[0];

for (final index in weights.indices) {
_upBound += weights[index];
if (index >= 1) {
_summedWt[index] = weights[index] + _summedWt[index - 1];
}
}
}

var _summedWt = <int>[];
var _upBound = 0;

int pickIndex() {
// Generate a random index between 1 and upBound (both inclusive)
final key = _rand.nextInt(_upBound) + 1;

// Search for the key in the sorted summedWt array
return _binSearch(key);
}

int _binSearch(int key) {
var lo = 0;
var hi = _summedWt.length - 1;
var mid = (lo + (hi - lo) / 2).toInt();

while (lo < hi) {
// If the key belongs to the current bucket
if (_summedWt[mid] == key ||
_summedWt[mid] > key && mid - 1 >= 0 && _summedWt[mid - 1] < key) {
break;
} else if (_summedWt[mid] < key && _summedWt[mid + 1] >= key) {
mid += 1;
break;
} else if (_summedWt[mid] < key) {
lo = mid + 1;
} else {
hi = mid - 1;
}

// Compute the new mid
mid = (lo + (hi - lo) / 2).toInt();
}
return mid;
}
}
26 changes: 26 additions & 0 deletions test/preconditions_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:dartx/dartx.dart';
import 'package:test/test.dart';

void main() {
group('Preconditions', () {
test('require() requirements not met', () {
expect(() => require(false), throwsException);
});

test('require() requirements met', () {
expect(() => require(true), returnsNormally);
});

test('requireNotNull() value is null and requirements not met', () {
expect(() => requireNotNull(null), throwsException);
});

test('requireNotNull() value is not null and requirements met', () {
expect(requireNotNull('value'), 'value');
});

test('error()', () {
expect(() => error('error massage'), throwsException);
});
});
}
Loading