@ 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
、数値とインデックスのペアとして保持することです。キューは、最小値が「一番上」に来るように並べられ、小さい値は標準の pair<> コンパレータに依存します。
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);
}