我喜欢@JoshO'Brien 的回答;部分排序的使用很棒!这是一个 Rcpp 解决方案(我不是一个强大的 C++ 程序员,所以可能会出现愚蠢的错误;欢迎更正......我将如何在 Rcpp 中对其进行模板化,以处理不同类型的输入向量?)
为了方便起见,我首先包含适当的标头并使用命名空间
#include <Rcpp.h>
#include <queue>
using namespace Rcpp;
using namespace std;
然后安排将我的 C++ 函数暴露给 R
// [[Rcpp::export]]
IntegerVector top_i_pq(NumericVector v, int n)
并定义一些变量,最重要的priority_queue
是 a 将数值和索引保持为一对。队列是有序的,因此最小值位于“顶部”,较小的值依赖于标准对<>比较器。
typedef pair<double, int> Elt;
priority_queue< Elt, vector<Elt>, greater<Elt> > pq;
vector<int> result;
现在我将遍历输入数据,如果 (a) 我还没有足够的值或 (b) 当前值大于队列中的最小值,则将其添加到队列中。在后一种情况下,我弹出最小值,并插入它的替换。这样,优先级队列总是包含 n_max 个最大的元素。
for (int i = 0; i != v.size(); ++i) {
if (pq.size() < n)
pq.push(Elt(v[i], i));
else {
Elt elt = Elt(v[i], i);
if (pq.top() < elt) {
pq.pop();
pq.push(elt);
}
}
}
最后,我将优先级队列中的索引弹出到返回向量中,记住转换为基于 1 的 R 坐标。
result.reserve(pq.size());
while (!pq.empty()) {
result.push_back(pq.top().second + 1);
pq.pop();
}
并将结果返回给 R
return wrap(result);
这有很好的内存使用(优先级队列和返回向量相对于原始数据都很小)并且速度很快
> library(Rcpp); sourceCpp("top_i_pq.cpp"); z <- runif(12000 * 12000)
> system.time(top_i_pq(z, 10000))
user system elapsed
0.992 0.000 0.998
此代码的问题包括:
默认比较器的greater<Elt>
工作原理是,在跨越第 _n_th 元素值的平局的情况下,保留最后一个而不是第一个重复项。
NA 值(和非有限值?)可能无法正确处理;我不确定这是否属实。
该函数仅适用于NumericVector
输入,但该逻辑适用于定义了适当排序关系的任何 R 数据类型。
问题 1 和 2 可以通过编写适当的比较器来解决;也许对于 2 这已经在 Rcpp 中实现了?我不知道如何利用 C++ 语言特性和 Rcpp 设计来避免为我想要支持的每种数据类型重新实现函数。
这是完整的代码:
#include <Rcpp.h>
#include <queue>
using namespace Rcpp;
using namespace std;
// [[Rcpp::export]]
IntegerVector top_i_pq(NumericVector v, int n)
{
typedef pair<double, int> Elt;
priority_queue< Elt, vector<Elt>, greater<Elt> > pq;
vector<int> result;
for (int i = 0; i != v.size(); ++i) {
if (pq.size() < n)
pq.push(Elt(v[i], i));
else {
Elt elt = Elt(v[i], i);
if (pq.top() < elt) {
pq.pop();
pq.push(elt);
}
}
}
result.reserve(pq.size());
while (!pq.empty()) {
result.push_back(pq.top().second + 1);
pq.pop();
}
return wrap(result);
}