Rust—如何查找集合中第n个最频繁的元素

Rust—如何查找集合中第n个最频繁的元素,rust,Rust,我无法想象以前没有人问过这个问题,但我到处都找遍了,找不到答案 我有一个iterable,它包含重复的元素。我想计算每个元素在此iterable中出现的次数,并返回第n个最频繁的元素 我有一个工作代码可以做到这一点,但我真的怀疑这是实现这一点的最佳方式 use std::collections::{BinaryHeap, HashMap}; // returns n-th most frequent element in collection pub fn most_frequent<T

我无法想象以前没有人问过这个问题,但我到处都找遍了,找不到答案

我有一个iterable,它包含重复的元素。我想计算每个元素在此iterable中出现的次数,并返回第n个最频繁的元素

我有一个工作代码可以做到这一点,但我真的怀疑这是实现这一点的最佳方式

use std::collections::{BinaryHeap, HashMap};

// returns n-th most frequent element in collection
pub fn most_frequent<T: std::hash::Hash + std::cmp::Eq + std::cmp::Ord>(array: &[T], n: u32) -> &T {
    // intialize empty hashmap
    let mut map = HashMap::new();

    // count occurence of each element in iterable and save as (value,count) in hashmap
    for value in array {
        // taken from https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry
        // not exactly sure how this works
        let counter = map.entry(value).or_insert(0);
        *counter += 1;
    }

    // determine highest frequency of some element in the collection
    let mut heap: BinaryHeap<_> = map.values().collect();
    let mut max = heap.pop().unwrap();
    // get n-th largest value
    for _i in 1..n {
        max = heap.pop().unwrap();
    }

    // find that element (get key from value in hashmap)
    // taken from https://stackoverflow.com/questions/59401720/how-do-i-find-the-key-for-a-value-in-a-hashmap
    map.iter()
        .find_map(|(key, &val)| if val == *max { Some(key) } else { None })
        .unwrap()
}
使用std::collections::{BinaryHeap,HashMap};
//返回集合中第n个最频繁的元素
发布fn最频繁(数组:&[T],n:u32)->&T{
//初始化空哈希映射
让mut map=HashMap::new();
//计算iterable中每个元素的出现次数,并在hashmap中另存为(值,计数)
对于数组中的值{
//取自https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry
//不太清楚这是怎么回事
让计数器=映射项(值)或插入(0);
*计数器+=1;
}
//确定集合中某些元素的最高频率
让mut heap:BinaryHeap=map.values().collect();
让mut max=heap.pop().unwrap();
//获取第n个最大值
对于1..n中的i{
max=heap.pop().unwrap();
}
//查找该元素(从hashmap中的值获取键)
//取自https://stackoverflow.com/questions/59401720/how-do-i-find-the-key-for-a-value-in-a-hashmap
map.iter()
.find_map(|(key,&val)|如果val==max{Some(key)}否则{None})
.unwrap()
}

有没有更好的方法或更优化的
std
方法来实现我想要的?或者我可以使用一些社区制作的板条箱。

您的实现的时间复杂度为Ω(n logn),其中n是数组的长度。对于检索第k个最频繁元素,该问题的最优解决方案的复杂度为Ω(n logk)。这个最佳解决方案的通常实现确实涉及到二进制堆,但不是以您使用它的方式

下面是一个建议的通用算法实现:

use std::cmp::{Eq, Ord, Reverse};
use std::collections::{BinaryHeap, HashMap};
use std::hash::Hash;

pub fn most_frequent<T>(array: &[T], k: usize) -> Vec<(usize, &T)>
where
    T: Hash + Eq + Ord,
{
    let mut map = HashMap::new();
    for x in array {
        *map.entry(x).or_default() += 1;
    }

    let mut heap = BinaryHeap::with_capacity(k + 1);
    for (x, count) in map.into_iter() {
        heap.push(Reverse((count, x)));
        if heap.len() > k {
            heap.pop();
        }
    }
    heap.into_sorted_vec().into_iter().map(|r| r.0).collect()
}
使用std::cmp::{Eq,Ord,Reverse};
使用std::collections::{BinaryHeap,HashMap};
使用std::hash::hash;
发布fn最频繁(数组:&[T],k:usize)->Vec
哪里
T:散列+Eq+Ord,
{
让mut map=HashMap::new();
对于数组中的x{
*map.entry(x)或_default()+=1;
}
让mut heap=BinaryHeap::具有_容量(k+1);
对于map.into_iter()中的(x,count){
push(反向((计数,x));
如果heap.len()>k{
heap.pop();
}
}
heap.into_sorted_vec().into_iter().map(| r | r.0).collect()
}
()

我更改了函数的原型,以返回
k
最频繁元素的向量及其计数,因为这是您无论如何都需要跟踪的内容。如果您只需要
k
,则可以使用
[k-1][1]
对结果进行索引

算法本身首先构建元素计数的映射,就像代码一样——我只是用更简洁的形式编写了它

接下来,我们为最频繁的元素构建了一个
BinaryHeap
。在每次迭代之后,这个堆最多包含
k
个元素,这是迄今为止最常见的元素。如果堆中的元素超过了
k
,我们将丢弃最不频繁的元素。由于我们总是删除迄今为止最不频繁的元素,因此堆始终保留迄今为止最频繁的
k
元素。我们需要使用
Reverse
包装器来获得最小堆,如下所示

最后,我们将结果收集到一个向量中。
into_sorted_vec()
函数基本上为我们完成了这项工作,但我们仍然希望从其
反向
包装中打开项目–该包装是我们函数的一个实现细节,不应返回给调用方

(在Rust Nightly中,我们还可以使用,节省一个向量分配。)


此答案中的代码确保堆基本上限于
k
元素,因此对堆的插入具有Ω的复杂性(log
k
)。在代码中,一次将所有元素从数组推送到堆中,而不限制堆的大小,因此插入的复杂度为Ω(log
n
)。实际上,您使用二进制堆对计数列表进行排序。这是可行的,但这肯定不是实现这一目标的最简单和最快的方法,因此没有理由走这条路线。

我认为您已经非常接近最佳解决方案了。第一步是像您那样计算所有元素的出现次数。其次,您希望找到未排序列表中的第k个最小/最大元素。关于这点,我建议你看看它和它的参考资料。我一点也不知道生锈。我只是想知道是否有一种方法可以执行GroupBy并对您的收藏进行计数?按计数排序将允许您按索引引用项。@Ach113不知道它们是否更好,但
BinaryHeap::into_sorted_vec()
将避免重复弹出堆(这可能不太好),或者您可以将hashmap()收集到
(Count,item)
的vec中,然后按计数排序,然后直接获取第n个元素。使用二进制堆查找最频繁的元素是无用的查找最大值只取O(n),在这里,如果不是更多,则执行O(n*log(n)),因为您做了很多,但做的并不多。预先分配哈希映射。@Stargateur,如果我只需要max元素,我会这样做,有没有办法得到O(n)中第n个最大的元素?(不跟踪n个变量)如果n很大,这会更好(事实上peek mut应该更好)这里我们还要等一下。。。为什么排序
(usize,&T)
?我们只想比较一下。。。它在原始数组中按频率和位置排序,因此不需要
t:Ord
。(虽然if
T
必须是
Ord