我有一个std::collections::HashSet
,我想采样并删除一个均匀随机的元素。
当前,我正在做的是使用rand.gen_range
随机采样索引,然后将HashSet
遍历到该索引以获取元素。然后,我删除选定的元素。这可行,但效率不高。有没有一种有效的方法可以对元素进行随机采样?
这是我的代码的简化版本:
use std::collections::HashSet;
extern crate rand;
use rand::thread_rng;
use rand::Rng;
let mut hash_set = HashSet::new();
// ... Fill up hash_set ...
let index = thread_rng().gen_range(0, hash_set.len());
let element = hash_set.iter().nth(index).unwrap().clone();
hash_set.remove(&element);
// ... Use element ...
答案 0 :(得分:3)
唯一允许在恒定时间内进行均匀采样的数据结构是具有恒定时间索引访问的数据结构。 HashSet
不提供索引,因此您无法在恒定时间内生成随机样本。
我建议先将您的哈希集转换为Vec
,然后再从向量中采样。要删除一个元素,只需将最后一个元素移到其位置–向量中元素的顺序无论如何都不重要。
如果要以随机顺序使用集合中的所有元素,还可以将向量随机洗一次,然后对其进行迭代。
以下是在恒定时间内从Vec
中删除随机元素的示例实现:
use rand::{thread_rng, Rng};
pub trait RemoveRandom {
type Item;
fn remove_random<R: Rng>(&mut self, rng: &mut R) -> Option<Self::Item>;
}
impl<T> RemoveRandom for Vec<T> {
type Item = T;
fn remove_random<R: Rng>(&mut self, rng: &mut R) -> Option<Self::Item> {
if self.len() == 0 {
None
} else {
let index = rng.gen_range(0, self.len());
Some(self.swap_remove(index))
}
}
}
答案 1 :(得分:1)
考虑到Sven Marnach的答案,我想使用向量,但是我也需要固定时间插入而不重复。然后,我意识到可以同时维护向量和集合,并确保它们始终具有相同的元素。这样既可以进行固定时间重复数据删除,也可以进行固定时间随机删除。
这是我最终得到的实现:
struct VecSet<T> {
set: HashSet<T>,
vec: Vec<T>,
}
impl<T> VecSet<T>
where
T: Clone + Eq + std::hash::Hash,
{
fn new() -> Self {
Self {
set: HashSet::new(),
vec: Vec::new(),
}
}
fn insert(&mut self, elem: T) {
assert_eq!(self.set.len(), self.vec.len());
let was_new = self.set.insert(elem.clone());
if was_new {
self.vec.push(elem);
}
}
fn remove_random(&mut self) -> T {
assert_eq!(self.set.len(), self.vec.len());
let index = thread_rng().gen_range(0, self.vec.len());
let elem = self.vec.swap_remove(index);
let was_present = self.set.remove(&elem);
assert!(was_present);
elem
}
fn is_empty(&self) -> bool {
assert_eq!(self.set.len(), self.vec.len());
self.vec.is_empty()
}
}