我试图编写一个百分位函数,它将2个向量作为输入,1个向量作为输出。输入向量之一(Distr)将是随机数的分布。另一个输入向量(测试)将是我想要从Distr计算百分位数的值向量。输出将是一个向量(与测试大小相同),它返回测试中每个值的百分位数。
以下是我想要的例子:
Input Distr = {3, 5, 8, 12}
Input Tests = {4, 9}
Output Percentile = {0.375, 0.8125}
以下是我在C ++中的实现:
vector<double> Percentile(vector<double> Distr, vector<double> Tests)
{
double prevValue, nextValue;
vector<double> result;
unsigned distrSize = Distr.size();
std::sort(Distr.begin(), Distr.end());
for (vector<double>::iterator test = Tests.begin(); test != Tests.end(); test++)
{
if (*test <= Distr.front())
{
result.push_back((double) 1 / distrSize); // min percentile returned (not important)
}
else if (Distr.back() <= *test)
{
result.push_back(1); // max percentile returned (not important)
}
else
{
prevValue = Distr[0];
for (unsigned sortedDistrIdx = 1; sortedDistrIdx < distrSize; sortedDistrIdx++)
{
nextValue = Distr[sortedDistrIdx];
if (nextValue <= *test)
{
prevValue = nextValue;
}
else
{
// linear interpolation
result.push_back(((*test - prevValue) / (nextValue - prevValue) + sortedDistrIdx) / distrSize);
break;
}
}
}
}
return result;
}
Distr和Tests的大小可以从2,000到30,000。
是否有任何现有的库可以计算百分位数如上所示(或类似)?如果不能,我怎样才能更快地完成上述代码?
答案 0 :(得分:0)
对于每个测试元素,Distr的线性搜索将是主要的时间量,如果这两个元素都很大。
当Distr大得多时,进行二分搜索而不是线性搜索要快得多。 std中有一个二进制搜索算法。你不需要写一个。
当测试几乎和Distr一样大或更大时,执行索引排序测试然后按顺序排列两个排序列表一起存储结果,然后在下一次传递中输出存储的结果更快。
编辑:我看到Csaba Balint的回答更详细地说明了我的意思&#34;将两个排序列表排在一起&#34;。
编辑:讨论的基本方法是:
1)对两个列表进行排序,然后线性地一起处理,时间NlogN + MlogM
2)只对一个列表和二分搜索进行排序,时间(N + M)logM
3)只排序其他列表和分区,时间我还没弄清楚,但是在N和M类似的情况下,它必须比方法1或2更大,并且在N足够小的情况下必须小于方法1或2。
答案 1 :(得分:0)
您的问题有一个线性算法(两种尺寸的线性时间对数)。您需要对两个向量进行排序,然后使用两个迭代器(itDistr
,itTest
)。有三种可能性:
1。 * itDistr&lt; * itTest
除了增加itDistr
之外,你没有任何其他内容。
2。 * itDistr&gt; = * itTest
当您找到* itTest
是区间[ *(itDistr-1), *itDistr )
的元素的测试用例时,会出现这种情况。所以你必须进行你使用的插值(线性),然后增加itTest
。
第三种可能性是其中任何一个到达其容器向量的末尾。您还必须定义在开头和结尾处发生的事情,这取决于您从数字系列中定义分布的方式。
是否存在可以计算百分位数的现有库,如上所示(或类似)?
可能,但很容易实现它,你可以很好地控制插值技术。
答案 2 :(得分:0)
此答案与input
最初是随机的(未排序)且test.size()
小于input.size()
的情况相关,这是最常见的情况。
假设只有一个测试值。然后,您只需要根据此值对input
进行分区,并获取较低(较高)分区的上限(下限)以计算相应的百分位数。这比输入的完全排序要快得多(快速排序实现为分区的递归)。
如果test.size()>1
,那么您首先排序test
(理想情况下,test
已经排序,您可以跳过此步骤),然后每次按递增顺序继续测试元素仅将上部分区与前一个分区分开。由于我们还跟踪上部分区的下限(以及下部分区的上限),我们可以检测连续测试元素之间是否没有输入数据,并避免分区。
这个算法应该接近最优,因为没有生成不必要的信息(就像完整的input
那样)。
如果后续分区将输入大致分成两半,则算法将是最佳的。这可以通过不按test
的递增顺序进行近似,而是通过随后的test
减半,即从中值测试元素开始,然后是第一个&amp;第三等四等。
答案 3 :(得分:0)
我会做像
这样的事情vector<double> Percentile(vector<double> Distr, vector<double> Tests)
{
double prevValue, nextValue;
vector<double> result;
unsigned distrSize = Distr.size();
std::sort(Distr.begin(), Distr.end());
for (vector<double>::iterator test = Tests.begin(); test != Tests.end(); test++)
{
if (*test <= Distr.front())
{
result.push_back((double) 1 / distrSize); // min percentile returned (not important)
}
else if (Distr.back() <= *test)
{
result.push_back(1); // max percentile returned (not important)
}
else
{
auto it = lower_bound(Distr.begin(), Distr.end(), *test);
prevValue = *(it - 1);
nextValue = *(it + 1);
// linear interpolation
result.push_back(((*test - prevValue) / (nextValue - prevValue) + (it - Distr.begin())) / distrSize);
}
}
return result;
}
请注意,我不是对每个测试的 Distr 进行线性搜索,而是利用 Distr 进行排序并进行二进制搜索的事实(使用 LOWER_BOUND )。