初始化
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
"""三层去重的指纹算法。
|
||||
|
||||
核心:
|
||||
- normalize_content: 把 content 折叠成纯净文本,用于跨源比对
|
||||
- content_hash: normalize 后 SHA1[:16]
|
||||
- simhash64: 字符 3-gram + md5 加权累加,产出 64 位无符号整数
|
||||
- hamming: 两个 SimHash 的汉明距离
|
||||
|
||||
设计取舍:
|
||||
SimHash 的"分词"用字符 3-gram 而非英文分词库。理由:
|
||||
1. 字符 3-gram 对英文和中文同样有效,无需外部 NLP 依赖
|
||||
2. 英文字符级 3-gram 天然捕获词根、前缀、后缀信息
|
||||
3. 跨语言场景(英文源可能引用中文/日文公司名)字符级更鲁棒
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import unicodedata
|
||||
|
||||
# 64 位 SimHash 位宽
|
||||
SIMHASH_BITS = 64
|
||||
SIMHASH_MASK = (1 << SIMHASH_BITS) - 1
|
||||
|
||||
# 默认 SimHash 汉明距离阈值(≤ 此值视为重复)
|
||||
DEFAULT_HAMMING_THRESHOLD = 3
|
||||
|
||||
# 字符 n-gram 长度
|
||||
NGRAM_SIZE = 3
|
||||
|
||||
|
||||
def normalize_content(text: str) -> str:
|
||||
"""把 content 折叠成"无空白无标点"形式,用于 L2 内容 hash 与 SimHash 输入。
|
||||
|
||||
使用 Unicode 类别判断:
|
||||
- P* Punctuation(所有中英文标点)
|
||||
- Z* Separator(空格 / 行 / 段分隔符)
|
||||
- C* Control(NUL / 换行控制等)
|
||||
保留 L*(字母)、N*(数字)、S*(符号,如 +/-、% 等),以及 CJK 字符。
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
return "".join(
|
||||
ch for ch in text if unicodedata.category(ch)[0] not in ("P", "Z", "C")
|
||||
)
|
||||
|
||||
|
||||
def content_hash(text: str) -> str:
|
||||
"""对 normalize_content(text) 做 SHA1,取前 16 hex 字符。"""
|
||||
norm = normalize_content(text)
|
||||
return hashlib.sha1(norm.encode("utf-8")).hexdigest()[:16]
|
||||
|
||||
|
||||
def _ngrams(text: str, n: int = NGRAM_SIZE) -> list[str]:
|
||||
"""字符级 n-gram。文本短于 n 时,直接整体作为单个 token。"""
|
||||
if len(text) < n:
|
||||
return [text] if text else []
|
||||
return [text[i : i + n] for i in range(len(text) - n + 1)]
|
||||
|
||||
|
||||
def simhash64(text: str) -> int:
|
||||
"""64 位 SimHash。返回无符号整数,空文本返回 0。"""
|
||||
norm = normalize_content(text)
|
||||
if not norm:
|
||||
return 0
|
||||
|
||||
grams = _ngrams(norm)
|
||||
if not grams:
|
||||
return 0
|
||||
|
||||
v = [0] * SIMHASH_BITS
|
||||
for gram in grams:
|
||||
h = int(hashlib.md5(gram.encode("utf-8"), usedforsecurity=False).hexdigest(), 16)
|
||||
# 取低 64 位
|
||||
h64 = h & SIMHASH_MASK
|
||||
for i in range(SIMHASH_BITS):
|
||||
if (h64 >> i) & 1:
|
||||
v[i] += 1
|
||||
else:
|
||||
v[i] -= 1
|
||||
|
||||
fp = 0
|
||||
for i in range(SIMHASH_BITS):
|
||||
if v[i] > 0:
|
||||
fp |= 1 << i
|
||||
return fp
|
||||
|
||||
|
||||
def hamming(a: int, b: int) -> int:
|
||||
"""两个 SimHash 的汉明距离。"""
|
||||
return bin((a ^ b) & SIMHASH_MASK).count("1")
|
||||
Reference in New Issue
Block a user