Skip to content

Commit e97b5b6

Browse files
authored
Simpler and faster implementation of Floyd's F2 (#1277)
The previous implementation used either `Vec::insert` or a second F-Y shuffling phase to achieve fair random order. Instead, use the random numbers already drawn to achieve a fair shuffle.
1 parent b9e7d84 commit e97b5b6

File tree

3 files changed

+15
-23
lines changed

3 files changed

+15
-23
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md).
88

99
You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful.
1010

11+
## [Unreleased API changing release]
12+
13+
### Other
14+
- Simpler and faster implementation of Floyd's F2 (#1277). This
15+
changes some outputs from `rand::seq::index::sample` and
16+
`rand::seq::SliceRandom::choose_multiple`.
17+
1118
## [0.8.5] - 2021-08-20
1219
### Fixes
1320
- Fix build on non-32/64-bit architectures (#1144)

src/seq/index.rs

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -380,33 +380,18 @@ where
380380
/// This implementation uses `O(amount)` memory and `O(amount^2)` time.
381381
fn sample_floyd<R>(rng: &mut R, length: u32, amount: u32) -> IndexVec
382382
where R: Rng + ?Sized {
383-
// For small amount we use Floyd's fully-shuffled variant. For larger
384-
// amounts this is slow due to Vec::insert performance, so we shuffle
385-
// afterwards. Benchmarks show little overhead from extra logic.
386-
let floyd_shuffle = amount < 50;
387-
383+
// Note that the values returned by `rng.gen_range()` can be
384+
// inferred from the returned vector by working backwards from
385+
// the last entry. This bijection proves the algorithm fair.
388386
debug_assert!(amount <= length);
389387
let mut indices = Vec::with_capacity(amount as usize);
390388
for j in length - amount..length {
391389
let t = rng.gen_range(0..=j);
392-
if floyd_shuffle {
393-
if let Some(pos) = indices.iter().position(|&x| x == t) {
394-
indices.insert(pos, j);
395-
continue;
396-
}
397-
} else if indices.contains(&t) {
398-
indices.push(j);
399-
continue;
390+
if let Some(pos) = indices.iter().position(|&x| x == t) {
391+
indices[pos] = j;
400392
}
401393
indices.push(t);
402394
}
403-
if !floyd_shuffle {
404-
// Reimplement SliceRandom::shuffle with smaller indices
405-
for i in (1..amount).rev() {
406-
// invariant: elements with index > i have been locked in place.
407-
indices.swap(i as usize, rng.gen_range(0..=i) as usize);
408-
}
409-
}
410395
IndexVec::from(indices)
411396
}
412397

@@ -628,8 +613,8 @@ mod test {
628613
);
629614
};
630615

631-
do_test(10, 6, &[8, 0, 3, 5, 9, 6]); // floyd
632-
do_test(25, 10, &[18, 15, 14, 9, 0, 13, 5, 24]); // floyd
616+
do_test(10, 6, &[8, 3, 5, 9, 0, 6]); // floyd
617+
do_test(25, 10, &[18, 14, 9, 15, 0, 13, 5, 24]); // floyd
633618
do_test(300, 8, &[30, 283, 150, 1, 73, 13, 285, 35]); // floyd
634619
do_test(300, 80, &[31, 289, 248, 154, 5, 78, 19, 286]); // inplace
635620
do_test(300, 180, &[31, 289, 248, 154, 5, 78, 19, 286]); // inplace

src/seq/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ mod test {
725725
.choose_multiple(&mut r, 8)
726726
.cloned()
727727
.collect::<Vec<char>>(),
728-
&['d', 'm', 'b', 'n', 'c', 'k', 'h', 'e']
728+
&['d', 'm', 'n', 'k', 'h', 'e', 'b', 'c']
729729
);
730730

731731
#[cfg(feature = "alloc")]

0 commit comments

Comments
 (0)