C++如何进行字符串的SimHash计算?(海量文本去重基础)

6次阅读

simhash是一种语义敏感的指纹算法,通过分词、加权、向量合并、降维生成64位整数,使相似文本汉明距离小;而std::hash输入敏感,无法用于去重。

C++如何进行字符串的SimHash计算?(海量文本去重基础)

SimHash 是什么,为什么不能直接用 std::hash

SimHash 不是普通哈希,它生成的是「语义敏感」的指纹:相似文本产出的 64 位整数汉明距离很小。而 std::hash<:string></:string> 对输入极其敏感,”abc” 和 “abd” 哈希值完全无关,没法用于去重。SimHash 的核心在于分词 → 加权 → 向量合并 → 降维,c++ 标准库不提供现成实现。

手动实现 SimHash 的关键四步(C++17 可用)

不需要第三方库,但得自己写清楚每一步的取舍。重点不是“跑通”,而是控制精度和性能平衡:

  • 分词建议用空格 + 标点切割,别上 std::Regex——太慢;简单用 std::stringstreamstd::string_view::find_first_of 切更稳
  • 每个词用 std::hash<:string_view></:string_view> 得到 64 位哈希值,再按词频或 TF-IDF 权重影响向量维度(初版可全设为 1)
  • 维护一个长度为 64 的整型数组 weight[64],对每个词哈希的每一位(bit 0 到 bit 63),若该位为 1 就 += weight,否则 -= weight
  • 最后遍历 weight 数组:正数则对应位设为 1,负数/零设为 0,组合成 uint64_t 返回

示例片段(省略分词):

uint64_t simhash(const std::vector<std::string_view>& words) {     int64_t weight[64] = {};     for (const auto& w : words) {         uint64_t h = std::hash<std::string_view>{}(w);         for (int i = 0; i < 64; ++i) {             bool bit = (h >> i) & 1;             weight[i] += bit ? 1 : -1;         }     }     uint64_t res = 0;     for (int i = 0; i < 64; ++i)         if (weight[i] > 0) res |= (1ULL << i);     return res; }

汉明距离计算必须手写,别调 __builtin_popcountll 就完事

__builtin_popcountll(a ^ b) 确实快,但要注意:GCC/Clang 支持,MSVC 要换 std::popcount(C++20)或查表法。更重要的是——别在循环里反复算距离。实际去重时,应先用 std::unordered_set<uint64_t></uint64_t> 快速过滤汉明距离 > 3 的候选,再对小集合逐个计算精确距离。否则 O(n²) 一上来就崩。

立即学习C++免费学习笔记(深入)”;

常见翻车点:分词太粗、权重没归一、64 位移位写错

最容易被忽略的是位操作细节:

  • 1 计算掩码?i ≥ 31 就溢出 —— 必须写 <code>1ULL
  • 分词没去停用词、没转小写,导致 “The” 和 “the” 被当不同词,simhash 差距拉大
  • 权重没做归一化(比如词频直接当权重),长文本天然压倒短文本,结果偏向长度而非语义
  • 没考虑 Unicode:UTF-8 字符串直接切可能截断多字节,简单场景可先转 std::wstring 或用 ICU,但多数日志/标题场景用 ASCII 分词已够用

SimHash 看似只是个哈希,但每个环节都在悄悄吃掉召回率。真正上线前,拿几十对人工标好的相似/不相似文本跑一遍,看汉明距离分布是否符合预期——这才是最不可跳过的验证。

text=ZqhQzanResources