Skip to content

Commit 5706126

Browse files
authored
Add choose_multple function for choosing several values (#55)
1 parent f939572 commit 5706126

File tree

6 files changed

+84
-3
lines changed

6 files changed

+84
-3
lines changed

.github/workflows/ci.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ jobs:
5252
- name: Build with no default features
5353
# Use no-std target to ensure we don't link to std.
5454
run: cargo build --no-default-features --target thumbv7m-none-eabi
55+
- name: Build with no default features and alloc
56+
# Use no-std target to ensure we don't link to std.
57+
run: cargo build --no-default-features --features alloc --target thumbv7m-none-eabi
5558
- name: Test wasm
5659
run: wasm-pack test --headless --chrome
5760
if: startsWith(matrix.os, 'ubuntu')
@@ -75,7 +78,7 @@ jobs:
7578
matrix:
7679
# When updating this, the reminder to update the minimum supported
7780
# Rust version in Cargo.toml.
78-
rust: ['1.34']
81+
rust: ['1.36']
7982
steps:
8083
- uses: actions/checkout@v3
8184
- name: Install Rust

Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ name = "fastrand"
66
version = "1.9.0"
77
authors = ["Stjepan Glavina <stjepang@gmail.com>"]
88
edition = "2018"
9-
rust-version = "1.34"
9+
rust-version = "1.36"
1010
description = "A simple and fast random number generator"
1111
license = "Apache-2.0 OR MIT"
1212
repository = "https://github.com/smol-rs/fastrand"
@@ -16,7 +16,8 @@ exclude = ["/.*"]
1616

1717
[features]
1818
default = ["std"]
19-
std = ["instant"]
19+
alloc = []
20+
std = ["alloc", "instant"]
2021

2122
[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies]
2223
instant = { version = "0.1", optional = true }

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ let i = fastrand::usize(..v.len());
4040
let elem = v[i];
4141
```
4242

43+
Sample values from an array with `O(n)` complexity (`n` is the length of array):
44+
45+
```rust
46+
fastrand::choose_multiple(vec![1, 4, 5].iter(), 2);
47+
fastrand::choose_multiple(0..20, 12);
48+
```
49+
4350
Shuffle an array:
4451

4552
```rust

src/global_rng.rs

+5
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,8 @@ pub fn f32() -> f32 {
185185
pub fn f64() -> f64 {
186186
with_rng(|r| r.f64())
187187
}
188+
189+
/// Collects `amount` values at random from the iterator into a vector.
190+
pub fn choose_multiple<T: Iterator>(source: T, amount: usize) -> Vec<T::Item> {
191+
with_rng(|rng| rng.choose_multiple(source, amount))
192+
}

src/lib.rs

+52
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@
2929
//! let elem = v[i];
3030
//! ```
3131
//!
32+
//! Sample values from an array with `O(n)` complexity (`n` is the length of array):
33+
//!
34+
//! ```
35+
//! fastrand::choose_multiple(vec![1, 4, 5].iter(), 2);
36+
//! fastrand::choose_multiple(0..20, 12);
37+
//! ```
38+
//!
39+
//!
3240
//! Shuffle an array:
3341
//!
3442
//! ```
@@ -76,9 +84,15 @@
7684
#![forbid(unsafe_code)]
7785
#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
7886

87+
#[cfg(feature = "alloc")]
88+
extern crate alloc;
89+
7990
use core::convert::{TryFrom, TryInto};
8091
use core::ops::{Bound, RangeBounds};
8192

93+
#[cfg(feature = "alloc")]
94+
use alloc::vec::Vec;
95+
8296
#[cfg(feature = "std")]
8397
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
8498
mod global_rng;
@@ -332,6 +346,44 @@ impl Rng {
332346
f64::from_bits((1 << (b - 2)) - (1 << f) + (self.u64(..) >> (b - f))) - 1.0
333347
}
334348

349+
/// Collects `amount` values at random from the iterator into a vector.
350+
///
351+
/// The length of the returned vector equals `amount` unless the iterator
352+
/// contains insufficient elements, in which case it equals the number of
353+
/// elements available.
354+
///
355+
/// Complexity is `O(n)` where `n` is the length of the iterator.
356+
#[cfg(feature = "alloc")]
357+
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
358+
pub fn choose_multiple<T: Iterator>(&mut self, mut source: T, amount: usize) -> Vec<T::Item> {
359+
// Adapted from: https://docs.rs/rand/latest/rand/seq/trait.IteratorRandom.html#method.choose_multiple
360+
let mut reservoir = Vec::with_capacity(amount);
361+
362+
reservoir.extend(source.by_ref().take(amount));
363+
364+
// Continue unless the iterator was exhausted
365+
//
366+
// note: this prevents iterators that "restart" from causing problems.
367+
// If the iterator stops once, then so do we.
368+
if reservoir.len() == amount {
369+
for (i, elem) in source.enumerate() {
370+
let end = i + 1 + amount;
371+
let k = self.usize(0..end);
372+
if let Some(slot) = reservoir.get_mut(k) {
373+
*slot = elem;
374+
}
375+
}
376+
} else {
377+
// If less than one third of the `Vec` was used, reallocate
378+
// so that the unused space is not wasted. There is a corner
379+
// case where `amount` was much less than `self.len()`.
380+
if reservoir.capacity() > 3 * reservoir.len() {
381+
reservoir.shrink_to_fit();
382+
}
383+
}
384+
reservoir
385+
}
386+
335387
rng_integer!(
336388
i8,
337389
u8,

tests/smoke.rs

+13
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,19 @@ fn with_seed() {
119119
assert_eq!(a.u64(..), b.u64(..));
120120
}
121121

122+
#[test]
123+
fn choose_multiple() {
124+
let mut a = fastrand::Rng::new();
125+
let mut elements = (0..20).collect::<Vec<_>>();
126+
127+
while !elements.is_empty() {
128+
let chosen = a.choose_multiple(0..20, 5);
129+
for &x in &chosen {
130+
elements.retain(|&y| y != x);
131+
}
132+
}
133+
}
134+
122135
#[test]
123136
fn choice() {
124137
let items = [1, 4, 9, 5, 2, 3, 6, 7, 8, 0];

0 commit comments

Comments
 (0)