【发布时间】:2009-11-24 06:33:57
【问题描述】:
我正在尝试使用 STL 来解决以下问题(如果不需要,我不想实现自己的数据结构)。我想出了一个可行的实现,但我希望有更快的方法......或者我有什么代码是最好的方法?
我有一个大型数据集,其中每个条目包含两个项目:一个键和一个大小。数据集中有多个条目具有相同的键。我需要知道的是:对于每个键,数据集中有多少个键,每个键的总大小是多少。例如,给定这个数据集(key, size):
(1, 3)
(3, 27)
(7, 7)
(3, 2)
(1, 1)
我想生成这个输出,按大小升序排序:
Key 1: Size 4, Count 2
Key 7: Size 7, Count 1
Key 3: Size 29, Count 2
由于数据集是完全未排序的,所以我首先需要对keys进行聚合统计并总结大小。然后我需要通过总大小来使用该数据结构来生成最终输出。这是我使用 std::map 和 std::vector 完成任务的代码:
struct Node
{
int Size;
int Count;
Node()
: Size(0), Count(0)
{
}
Node(int size)
: Size(size), Count(1)
{
}
};
void map_insert(std::map<int, Node> &map, int key, int size)
{
std::map<int, Node>::iterator itr = map.find(key);
if (itr != map.end())
{
itr->second.Count++;
itr->second.Size += size;
}
else
{
map[key] = Node(size);
}
}
bool compare(const std::pair<int, Node> &a1, const std::pair<int, Node> &a2)
{
return a1.second.Size < a2.second.Size;
}
int _tmain(int argc, _TCHAR* argv[])
{
std::map<int, Node> _map;
map_insert(_map, 1, 3);
map_insert(_map, 3, 27);
map_insert(_map, 7, 7);
map_insert(_map, 3, 2);
map_insert(_map, 1, 1);
std::vector<std::pair<int, Node>> v(_map.begin(), _map.end());
std::sort(v.begin(), v.end(), compare);
return 0;
}
减去输出代码,这会产生正确的排序。我讨厌使用两个单独的数据结构,但似乎没有一种方法可以根据不同的键“重新排序”树。我可以避免这里有什么严重的低效率吗?谁能想到更好的方法来做到这一点?
请注意,我假设使用 Node 实例(而不是 Node 指针)将比新建和删除此处使用的每个节点更快。这是一个合理的假设,还是您认为 new/delete 会比复制这些小型结构更快?
编辑:有趣的是,我从来不知道 multimap,但是使用下面提供的实现(感谢 Naveen),看起来 Multimap 的性能更差。 (注意我的意图是快速实现,内存不是问题,我应该指出这一点。)使用这个实现:
class Timer
{
public:
Timer()
: mStart(0)
{
}
void Start()
{
mStart = std::clock();
}
double Mark()
{
std::clock_t curr = std::clock();
double f = (curr - mStart)/((double)CLOCKS_PER_SEC);
mStart = curr;
return f;
}
private:
std::clock_t mStart;
};
struct Node
{
int Size;
int Count;
Node()
: Size(0), Count(0)
{
}
Node(int size)
: Size(size), Count(1)
{
}
};
void map_insert(std::map<int, Node> &map, int key, int size)
{
std::map<int, Node>::iterator itr = map.find(key);
if (itr != map.end())
{
itr->second.Count++;
itr->second.Size += size;
}
else
{
map[key] = Node(size);
}
}
bool compare(const std::pair<int, Node> &a1, const std::pair<int, Node> &a2)
{
return a1.second.Size < a2.second.Size;
}
int make_size(int i, int size_max)
{
return (7 * i) % size_max;
}
int make_key(int i, int key_max)
{
return (11 * i) % key_max;
}
void first_impl(int max, int size_max, int key_max)
{
std::cout << "first_impl:" << std::endl;
double total = 0;
double curr = 0;
Timer t;
t.Start();
{
std::map<int, Node> _map;
for (int i = 0; i < max; ++i)
map_insert(_map, make_key(i, key_max), make_size(i, size_max));
total += curr = t.Mark();
std::cout << "\tinsert: " << curr << std::endl;
std::vector<std::pair<int, Node>> v(_map.begin(), _map.end());
total += curr = t.Mark();
std::cout << "\tcreate: " << curr << std::endl;
std::sort(v.begin(), v.end(), compare);
total += curr = t.Mark();
std::cout << "\tsort: " << curr << std::endl;
}
total += curr = t.Mark();
std::cout << "\tcleanup: " << curr << std::endl;
std::cout << "\ttotal: " << total << std::endl;
}
void second_impl(int max, int size_max, int key_max)
{
std::cout << "second_impl:" << std::endl;
double total = 0;
double curr = 0;
Timer t;
t.Start();
{
std::map<int, Node> res;
typedef std::multimap<int, int> MultiMap;
MultiMap mMap;
for (int i = 0; i < max; ++i)
mMap.insert(std::make_pair(make_key(i, key_max), make_size(i, size_max)));
total += curr = t.Mark();
std::cout << "\tinsert: " << curr << std::endl;
std::multimap<int, int>::iterator iter = mMap.begin();
std::multimap<int, int>::iterator endIter = mMap.end();
for(; iter != endIter; ++iter)
{
int val = iter->first;
if(res.find(val) != res.end())
{
continue;
}
std::pair<MultiMap::iterator, MultiMap::iterator> iterPair = mMap.equal_range(val);
Node n;
n.Size = val;
n.Count = mMap.count(val);
int size = 0;
for(; iterPair.first != iterPair.second; ++iterPair.first)
{
size += iterPair.first->second;
}
res[size] = n;
}
total += curr = t.Mark();
std::cout << "\tsort: " << curr << std::endl;
}
total += curr = t.Mark();
std::cout << "\tcleanup: " << curr << std::endl;
std::cout << "\ttotal: " << total << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
const int size_max = 31;
const int key_max = 1019;
const int max = 1000000;
first_impl(max, size_max, key_max);
second_impl(max, size_max, key_max);
return 0;
}
结果如下所示:
first_impl:
insert: 0.094
create: 0
sort: 0
cleanup: 0
total: 0.094
second_impl:
insert: 1.653
sort: 46.894
cleanup: 66.081
total: 114.628
第二种实现显然更慢。看起来键的总数远低于项目总数(唯一键的总数约为 1000 代表我的数据集)使得 std::map 在这里获胜因为它很快就达到了不需要更多节点的稳定状态。在我进行二次调查之前,我完全忽略了这个事实。
看起来我的原始实现比 multimap 更好,并且由于我不愿意依赖 Boost,我想我有我的答案。谢谢大家!
【问题讨论】:
-
再看一下使用 multimap 的代码,我并不完全相信这个实现是正确的,但是事实上插入 multimap 的时间比完全计算我的时间要长用map/vector/sort寻找,这似乎仍然很清楚,原来的实现就是我要使用的。
-
为了更快的速度,你可以查看 hash_map(或 tr1:unordered_map)。
-
是的,迭代非常慢,所以 MultiMap 已经出来了,而且它在技术上是不正确的,因为您不需要保留大小和计数,只需将它们总结起来,就像您正确完成的那样。 STL 无序映射的哈希表大约是映射的 3 倍,但对于您的适度数据大小,不值得麻烦。一个有趣的想法是使用 1 个映射来汇总数据,并使用第 2 个映射对其进行排序 - 并不是说 qsort() 在汇总数据后就不能正常工作,但是第 2 个映射会在实时。