【问题标题】:map a million strings to ints in c++11在c ++ 11中将一百万个字符串映射到整数
【发布时间】:2016-08-01 16:28:55
【问题描述】:

我有一百万个 ASCII 字符串,没有重复,每个最多 7 个字节长。我需要将每个字符串映射到一个正整数。这些 int 中最大的应该不超过一百万。虽然初始化可能很慢,但查找应该很快:给定一个字符串,返回相应的 int(或 -1,如果未找到)。如何在 C++11 中实现这一点?

一种解决方案:将字符串累积成一个std::unordered_map<string,int>;然后遍历地图,从递增计数器分配整数。然后查找,只需unordered_map::find("foo")->second。但它闻起来像其他容器会更快并且开销更少(内置索引,而不是手动编码)。也许unordered_set 和指针算法??

范围限制似乎使完美的哈希变得困难。

(int 的范围受到限制,因为它索引到传递给svm_light 的特征向量。该软件不使用稀疏存储,因此具有数万亿(大部分为零)元素的向量使其内存不足。所以这种从字符串到整数的预处理实现了一种稀疏数据结构。)

【问题讨论】:

  • 你能举一个这样的映射例子吗?
  • 我错过了什么? 10^6 字符串,每个不超过 7 个字节,并且没有重复?
  • 这不是一个解决方案,但我会将它们放在一个向量中,对其进行排序并使用二进制搜索。
  • 马克,O(n) 查找太慢了。 Fran,O(log n) 更好,但仍然比 unordered_map 的 O(1) 慢。

标签: string c++11 dictionary svm sparse-matrix


【解决方案1】:

你描述的内容看起来像perfect hashing

有实现完美哈希的 C++ 库,例如Tiny perfect hash library for C, C++, and Lua

【讨论】:

  • 是的,用这个术语来说,我需要一个几乎“最小完美的散列函数”。但是制作一个是difficult
  • @CamilleGoudeseune 你看过我提供的 CHD 基础库吗? GNU gperf 已经过时了
  • 哇,80% 的负载率太棒了!我主要是尽量避免低于 0.01% :)
  • 一个 7 字节的字符串适合 int64_t,而哈希是......本身!
  • @EmilioGaravaglia 嗯...The largest of these ints should be not much more than a million 呢?
【解决方案2】:

将您的字符串转换为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)。

【讨论】:

    【解决方案3】:

    如果您有一百万个字符串,每个字符串长度正好是 7 个字节,那么它是使用基数排序的完美前提;所以基本上首先你将所有 10^6 个字符串存储在一个大数组中(它只有 7MB/6.7MiB,所以非常易于管理),然后使用基数排序算法进行排序 - 时间复杂度 O(wn),w = 7, n = 10^6 在你的情况下,可以原地实现。实现细节对于保持线性复杂度的低常数很重要,但基数排序相当容易实现。

    作为基数排序的替代方案,您可以简单地将字符串视为uint64_t 并使用std::sort(它实现了优化的内部排序,尽管时间复杂度更高,但对于您的约束,它的性能可能与基数一样好)。

    一旦数组被排序,你迭代它并将数组的索引放入普通的std::unordered_map,以字符串为键。所以最后你在基本上线性的时间内创建了完美的散列,并最终以平均 O(1) 的速度进行反向查找。

    [edit] 对于将字符串放入unordered_map,你可能想实现自己的哈希算法,我建议使用djb2,它具有良好的统计特性并且易于实现。

    【讨论】:

    • 一些字符串短于 7 个字节。即便如此,将它们视为 64 位整数还是很聪明的!
    • 如果查找时使用的数据结构是unordered_map,为什么还要事先进行排序呢?这听起来并不比我最初提出的解决方案简单。
    • 您问题中的第二句话是:“这些整数中最大的应该不超过一百万”;)
    • 好的,我会详细说明 - 你原来的建议很好,但你是对的 - 这可能开销太大。我认为,在std::unordered_set 的迭代过程中,对连续内存区域实施排序将比在整个地方跳跃更快。因此,与您最初建议的相比,构建您的映射会快。
    • 我仍然看不到在填充 u_map 之前进行排序有什么帮助。 u_map 存储数据unpredictably,所以,嗖嗖嗖,再见参考位置。
    猜你喜欢
    • 2013-09-26
    • 2017-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多