字符串按相似度分组

相关代码已经上传GitHub : https://github.com/EricLi404/go-utils

0x00 概述

在做在做反垃圾账号业务的时候,检测发现有几十名用户都在某个IP上有过行为打点。

# 原始数据
꧁❥?➦SAMI?MMS➣?❥꧂
꧁❥?➦Ⓜ️AHI?MMS➣?❥꧂..
jgcch7b?V?O?I?
2kc7fd7?V?O?I?
꧁❥?➦VIDYA?MMS➣?❥꧂
RcR?Raden??F
RcR?Raden?B
꧁❥?➦RESMA?MMS➣?❥꧂
Sanny8_star197
꧁❥?PARI?MMS➣?❥꧂
❁════❃MAHAKAL❃══7══❁
꧁❥?BIPASHA?MMS➣?❥꧂
.꧁❥?ISRAT?MMS?꧂
ek8660?V?O?I? 
a8h9163?V?O?I?
꧁❥?➦NIRA?➣?❥꧂
꧁❥?PARINEETI?MMS?꧂
RcR?Raden?J
dem3ba2?V?O?I?
ggk5b27?V?O?I?
?RcR?Raden??

可以很明显的感知到其中部分账号存在一定的相似性,现需要将这几十名用户按相似性分为若干组,因此提出了一种基莱文斯坦距离和并查集结构的算法,来实现将一组字符串按相似度提取出若干不重复组的功能。

0x01 理论基础

莱文斯坦距离

Wikipedia:
莱文斯坦距离,又称Levenshtein距离,是编辑距离的一种。指两个字串之间,由一个转成另一个所需的最少编辑操作次数。允许的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。

例如将kitten一字转成sitting:

  1. sitten (k→s)
  2. sittin (e→i)
  3. sitting (→g)

则两者之间莱文斯坦距离为3.
俄罗斯科学家弗拉基米尔·莱文斯坦在1965年提出这个概念。

其用途主要有 DNA分析、拼写检查、语音辨识、抄袭侦测 。

并查集

Wikipedia:
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

关于并查集的应用,有个经典的朋友圈问题

0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 10个人,其中 [0,1], [0, 4], [1, 2], [1, 3], [5, 6], [6, 7], [7, 5], [8, 9] 互为朋友,求该10个人中有几个朋友圈(二度以内朋友)。

计算方式如下:

  1. 首先列出所有的用户,然后给每个用户都分为单独的一组
0123456789
ABCDEFGHIJ
  1. 然后遍历一度朋友关系[0,1], [0, 4], [1, 2], [1, 3], [5, 6], [6, 7], [7, 5], [8, 9] ,将每组一度关系更新为同一组(将一度关系后者的组更新为前者的组):

[0,1]

0123456789
AACDEFGHIJ

[0, 4]

0123456789
AACDAFGHIJ

[1, 2]

0123456789
AAADAFGHIJ

[1, 3]

0123456789
AAAAAFGHIJ

[5, 6]

0123456789
AAAAAFFHIJ

[6, 7]

0123456789
AAAAAFFFIJ

[7, 5]

0123456789
AAAAAFFFIJ

[8, 9]

0123456789
AAAAAFFFII
  1. 至此,朋友圈已分出,也即
    • [0,1,2,3,4] 为一个朋友圈
    • [5,6,7] 为一个朋友圈
    • [8,9] 为一个朋友圈

0x02 main

  1. 基于莱文斯坦距离计算两个字符串之间的相似度
公式为: 
Similarity(str1,str2) = Levenshtein(str1,str2)*2 / len(str1) + len(str2)

  1. 将莱文斯坦距离小于阈值的两个字符串记为连通(类似于朋友圈问题中的一度朋友), 得到如下列表:
str1str2Similarity
꧁❥?➦SAMI?MMS➣?❥꧂꧁❥?➦Ⓜ️AHI?MMS➣?❥꧂…0.123457
kcfd?V?O?I?ah?V?O?I?0.181818
RcR?Raden?BRcR?Raden?J0.176471
RcR?Raden?B?RcR?Raden??0.146341
ah?V?O?I?demba?V?O?I?0.222222
꧁❥?➦NIRA?➣?❥꧂꧁❥?PARINEETI?MMS?꧂0.358209
RcR?Raden?J?RcR?Raden??0.195122
demba?V?O?I?ggkb?V?O?I?0.170213
  1. 利用并查集将字符串分组,得到以下分组情况
꧁❥?BIPASHA?MMS➣?❥꧂
꧁❥?➦SAMI?MMS➣?❥꧂
.꧁❥?ISRAT?MMS?꧂
꧁❥?➦Ⓜ️AHI?MMS➣?❥꧂..
꧁❥?➦VIDYA?MMS➣?❥꧂
꧁❥?➦RESMA?MMS➣?❥꧂
꧁❥?PARI?MMS➣?❥꧂
꧁❥?➦NIRA?➣?❥꧂
꧁❥?PARINEETI?MMS?꧂

RcR?Raden??F
RcR?Raden?B
RcR?Raden?J
?RcR?Raden??

jgcchb?V?O?I?
kcfd?V?O?I?
ek?V?O?I? 
ah?V?O?I?
demba?V?O?I?
ggkb?V?O?I?

Sanny8_star197

❁════❃MAHAKAL❃══7══❁

0x03 GitHub

相关代码已经上传Github : https://github.com/EricLi404/go-utils

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
字符串相似度分组是一种常见的任务,可以通过计算字符串之间的相似度来将它们分成若干个组,以方便后续的处理。在 Python 中,可以使用 Levenshtein 距离或 Jaccard 相似度等算法来计算字符串之间的相似度,然后根据相似度来进行分组。 以下是一个简单的示例代码,使用 Jaccard 相似度来对字符串进行分组: ``` python from typing import List from collections import defaultdict def jaccard_sim(a: str, b: str) -> float: """计算 Jaccard 相似度""" a, b = set(a), set(b) return len(a & b) / len(a | b) def group_strings(strings: List[str], threshold: float) -> List[List[str]]: """将相似度大于等于阈值的字符串分到同一组中""" groups = defaultdict(list) for s in strings: added = False for g in groups: sim = jaccard_sim(s, g) if sim >= threshold: groups[g].append(s) added = True break if not added: groups[s].append(s) return list(groups.values()) ``` 在上述代码中,`jaccard_sim` 函数计算 Jaccard 相似度,`group_strings` 函数将相似度大于等于阈值的字符串分到同一组中。这里使用了 defaultdict 来创建组,如果某个字符串与已有的组相似度大于等于阈值,则将其添加到该组中,否则将其作为新的组添加到 defaultdict 中。 示例用法: ``` python strings = ["abc", "abd", "abf", "xyz", "pqr"] groups = group_strings(strings, 0.5) print(groups) ``` 输出: ``` python [['abc', 'abd', 'abf'], ['xyz'], ['pqr']] ``` 在上述示例中,阈值为 0.5,因此将 "abc", "abd", "abf" 分到同一组中,将 "xyz" 作为新的一组,将 "pqr" 作为新的一组。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值