对于另一个更大的数据阵列,我有一个大的,严格增加的数组(1000万个整数)的偏移量。 data
中的任何元素都不大于50.例如,
unsigned char data[70*1000*1000] = {0,2,1,1,0,2,1,4,2, ...};
unsigned int offsets[10*1000*1000] = {0,1,2,4,6,7,8, ...};
然后我想找到一系列范围内每个元素的计数,这些范围在运行时才知道,包括只有其偏移量包含在offsets
数组中的元素。每个范围的端点是指数据数组的索引,而不是偏移量。例如,范围[1,4]的数据将是:
1 zero
1 one
1 two
结果只包含一个“一个”,因为虽然data[3]
和data[2]
都等于一,但offsets
中不包含3个。
我需要计算几百个范围的这些分箱计数,其中一些范围跨越整个阵列。我考虑迭代数据数组来存储每个bin和元素的累积和,但是内存要求会过高。这是我的实现的简单版本:
for(int i=0; i<range_count; i++){
unsigned int j=0;
while(j<range_starts[i]) pi++;
while(j < 10000000 and data[j]<=range_ends[i]) bins[i][data[offsets[j++]]]++;
}
有没有更有效的方法来计算这些计数?
答案 0 :(得分:2)
虽然鲁本的回答确实将计算时间缩短了一半,但对我的申请来说仍然太慢了。为了好奇,我在这里提供了我的解决方案。
首先,我通过将未data
索引的offsets
数组中的元素设置为未使用的值(例如51)来进行优化。这消除了跟踪偏移的需要,因为我可以在报告结果时忽略第51个分档的内容。
虽然我在答案中提到存储每个bin和element的累积计数需要太多内存,但我能够以线性时间存储每个bin和range端点的累积计数。然后,对于每个范围,我通过从右端点处的计数中减去该范围的左端点处的该元素的累积计数来计算每个元素的出现次数。这是我使用的:
struct range{
unsigned int lowerbound;
unsigned int upperbound;
unsigned int bins[52];
};
struct endpoint{
int n;
unsigned int counts[50];
};
range ranges[N_RANGES];
endpoint endpoints[N_RANGES*2];
cumulative_counts[52];
// ... < data manipulation > ...
endpoint* first_ep = &endpoints[0];
endpoint* last_ep = &endpoints[N_RANGES*2-1];
endpoint* next_ep;
for(next_ep=&endpoints[0];next_ep<last_ep;next_ep++){
unsigned char* i = &data[next_ep->n];
unsigned char* i_end = &data[(next_ep+1)->n];
for(int j=0;j<51;j++) next_ep->counts[j] = cumulative_counts[j];
while(i<i_end) cumulative_counts[*(i++)]++;
}
for(int i=0;i<51;i++) last_ep->sums[i] = cumulative_counts[i];
for(int i=0;i<N_RANGES;i++){
while(first_ep->n != ranges[i].lowerbound) first_ep++;
last_ep = first_ep+1;
while(last_ep->n != ranges[i].upperbound) last_ep++;
for(int j=0;j<51;j++) tests[i].bins[j] = end_ep->counts[j]-start_ep->counts[j];
ranges[i].bins[data[last_ep->n]]++;
}
答案 1 :(得分:1)
当你说你的偏移被限制在50时,听起来你已经得到了答案 - 而且它们似乎是正整数。
如何为每个数据值索引矢量矢量,从0到50,然后进行其他计算会更便宜。这将是一种从数据到数据库条目的反向索引。
所以,你会:
data[50][...] = {offsets related to the given data value}
将对每个数组执行计算,检查初始元素,并从数组跳到数组,保持验证最后一个元素的位置。
这对整个数组的元素数量是线性的,是搜索范围的倍数,乘以数组“数据”(0到50)中元素的数量,考虑到你需要这么多次,这不是最好的方法。
另一种方法是,对于每个数据条目,使用0到50,二叉树 - 甚至哈希结构 - ,这样您现在可以查看数据库条目标识符是否属于该集合当前数据元素的ID(从0到50)。对于每次迭代,在最佳情况下,这将是搜索范围的线性。
我在分析中考虑了50作为常数,因此仅在第一个数据数组中搜索,或者在数据“数据”的所有50个条目中搜索将是相同的。我不确定这是否是一个有效的假设,因此复杂性为:O(nr),n等于您的数据最大范围(0到50),r等于您的搜索范围(in你的数据库)。这对每次计算都有效,因此,考虑到i作为计算次数,复杂度将以O(nri)给出。
答案 2 :(得分:1)
这可行吗?
(演示 http://ideone.com/6rAj7k )
#include <algorithm>
#include <iostream>
unsigned char data[/*70*1000*1000*/] = {0,2,1,1,0,2,1,4,2};
unsigned int offsets[/*10*1000*1000*/] = {0,1,2,4,6,7,8};
using namespace std;
void do_something_for_data_index(unsigned int data_index)
{
std::cout << "visited: " << (int) data[data_index] << " (at index " << data_index << ")\n";
}
void foo(size_t first_data_index, size_t high_data_index)
{
const auto low = lower_bound(begin(offsets), end(offsets), first_data_index);
const auto high = upper_bound(low , end(offsets), high_data_index);
for(auto offset_it = low; offset_it != high; ++offset_it)
{
do_something_for_data_index(*offset_it);
}
}
int main()
{
foo(1,4);
}
输出:
visited: 2 (at index 1)
visited: 1 (at index 2)
visited: 0 (at index 4)