将一百万个字符串映射到 C++11 中的整数
map a million strings to ints in c++11
我有一百万个 ASCII 字符串,没有重复,每个字符串最多 7 个字节。我需要将每个字符串映射到一个正整数。这些整数中最大的应该不超过一百万。虽然初始化可能很慢,但查找应该很快:给定一个字符串,return对应的整数(或 - 1,如果没有找到)。如何在 C++11 中实现这一点?
一个解决方案:将字符串累加成一个std::unordered_map<string,int>
;然后迭代地图,从递增计数器分配整数。然后去查找,就unordered_map::find("foo")->second
。但它闻起来像其他一些容器会更快并且开销更少(内置索引,而不是手动编码)。也许 unordered_set
和指针算术??
范围限制似乎使完美哈希变得困难。
(int 的范围是有限的,因为它索引到传递给 svm_light 的特征向量。该软件不使用稀疏存储,因此具有数万亿(大部分为零)元素的向量使其成为 运行 内存不足。所以这种字符串到整数的预处理实现了一种稀疏数据结构。)
你所描述的看起来像 perfect hashing。
有实现完美哈希的 C++ 库,例如 Tiny perfect hash library for C, C++, and Lua。
如果您正好有数百万个字符串,每个字符串恰好有 7 个字节长,那么这是使用基数排序的完美先决条件;所以基本上首先你将所有 10^6 个字符串存储在大数组中(它只有 7MB/6.7MiB,所以很容易管理),然后使用基数排序算法排序 - 时间复杂度 O(wn), w = 7, n = 10^6 在你的情况下,可以实现 in situ .实现的细节对于保持线性复杂度的低常数很重要,但基数排序相当容易实现。
作为基数排序的替代方法,您可以简单地将字符串视为 uint64_t
并使用 std::sort
(它实现了优化良好的内部排序,对于您的约束,它的性能可能与基数一样好,尽管时间复杂度更高)。
一旦数组被排序,你就遍历它并将数组的索引放入普通的 std::unordered_map
中,以字符串作为键。所以最后你在基本线性时间内创建了完美的散列,并以平均 O(1).
的反向查找结束
[edit] 为了将字符串放入 unordered_map,您可能想要实现自己的哈希算法,我建议使用 djb2,它具有良好的统计特性并且是易于实施。
将您的字符串转换为 int64_t
,将它们存储在 unordered_set
中,并将迭代器用作唯一索引。
实际上,您将实现 O(1) 查找,加上 O(N) 计算迭代器偏移量。您还将保证最大索引不会超过数组的大小。
unordered_set<int> s;
s.insert(10);
s.insert(2000000);
s.insert(5000000);
int index = std::distance(s.find(10), s.end());
cout << index << endl;
index = std::distance(s.find(2000000), s.end());
cout << index << endl;
index = std::distance(s.find(5000000), s.end());
cout << index << endl;
输出:
1
2
3
既然你有一个独特的映射,使用unordered_map
来实现你的目标,并丢弃unordered_set
:
unordered_set<int> s;
unordered_map<int,int> m;
s.insert(10);
s.insert(2000000);
s.insert(5000000);
int index = std::distance(s.find(10), s.end());
m[10] = index;
cout << index << endl;
index = std::distance(s.find(2000000), s.end());
m[2000000] = index;
cout << index << endl;
index = std::distance(s.find(5000000), s.end());
m[5000000] = index;
cout << index << endl;
s.clear();
cout << m[10] << " " << m[2000000] << " " << m[5000000] << endl;
查找将是 O(1)。
我有一百万个 ASCII 字符串,没有重复,每个字符串最多 7 个字节。我需要将每个字符串映射到一个正整数。这些整数中最大的应该不超过一百万。虽然初始化可能很慢,但查找应该很快:给定一个字符串,return对应的整数(或 - 1,如果没有找到)。如何在 C++11 中实现这一点?
一个解决方案:将字符串累加成一个std::unordered_map<string,int>
;然后迭代地图,从递增计数器分配整数。然后去查找,就unordered_map::find("foo")->second
。但它闻起来像其他一些容器会更快并且开销更少(内置索引,而不是手动编码)。也许 unordered_set
和指针算术??
范围限制似乎使完美哈希变得困难。
(int 的范围是有限的,因为它索引到传递给 svm_light 的特征向量。该软件不使用稀疏存储,因此具有数万亿(大部分为零)元素的向量使其成为 运行 内存不足。所以这种字符串到整数的预处理实现了一种稀疏数据结构。)
你所描述的看起来像 perfect hashing。
有实现完美哈希的 C++ 库,例如 Tiny perfect hash library for C, C++, and Lua。
如果您正好有数百万个字符串,每个字符串恰好有 7 个字节长,那么这是使用基数排序的完美先决条件;所以基本上首先你将所有 10^6 个字符串存储在大数组中(它只有 7MB/6.7MiB,所以很容易管理),然后使用基数排序算法排序 - 时间复杂度 O(wn), w = 7, n = 10^6 在你的情况下,可以实现 in situ .实现的细节对于保持线性复杂度的低常数很重要,但基数排序相当容易实现。
作为基数排序的替代方法,您可以简单地将字符串视为 uint64_t
并使用 std::sort
(它实现了优化良好的内部排序,对于您的约束,它的性能可能与基数一样好,尽管时间复杂度更高)。
一旦数组被排序,你就遍历它并将数组的索引放入普通的 std::unordered_map
中,以字符串作为键。所以最后你在基本线性时间内创建了完美的散列,并以平均 O(1).
[edit] 为了将字符串放入 unordered_map,您可能想要实现自己的哈希算法,我建议使用 djb2,它具有良好的统计特性并且是易于实施。
将您的字符串转换为 int64_t
,将它们存储在 unordered_set
中,并将迭代器用作唯一索引。
实际上,您将实现 O(1) 查找,加上 O(N) 计算迭代器偏移量。您还将保证最大索引不会超过数组的大小。
unordered_set<int> s;
s.insert(10);
s.insert(2000000);
s.insert(5000000);
int index = std::distance(s.find(10), s.end());
cout << index << endl;
index = std::distance(s.find(2000000), s.end());
cout << index << endl;
index = std::distance(s.find(5000000), s.end());
cout << index << endl;
输出:
1
2
3
既然你有一个独特的映射,使用unordered_map
来实现你的目标,并丢弃unordered_set
:
unordered_set<int> s;
unordered_map<int,int> m;
s.insert(10);
s.insert(2000000);
s.insert(5000000);
int index = std::distance(s.find(10), s.end());
m[10] = index;
cout << index << endl;
index = std::distance(s.find(2000000), s.end());
m[2000000] = index;
cout << index << endl;
index = std::distance(s.find(5000000), s.end());
m[5000000] = index;
cout << index << endl;
s.clear();
cout << m[10] << " " << m[2000000] << " " << m[5000000] << endl;
查找将是 O(1)。