在表中查找相似的数字模式
Posted
技术标签:
【中文标题】在表中查找相似的数字模式【英文标题】:Finding similar number patterns in table 【发布时间】:2011-03-27 01:31:12 【问题描述】:好的,假设我们有members
表。有一个名为about_member
的字段。每个人都会有一个像1-1-2-1-2
这样的字符串。让我们假设 member_1 有这个字符串 1-1-2-2-1
并且他搜索具有相似字符串或尽可能相似的人。例如,如果 member_2 有字符串 1-1-2-2-1
,它将是 100% 匹配,但如果 member_3 有这样的字符串 2-1-1-2-1
,它将是 60% 匹配。它必须按匹配百分比排序。使用 mysql 和 php 的最佳方法是什么?真的很难解释我的意思,但也许你明白了,如果没有,问我。谢谢。
编辑:请给我没有 Levenshtein 方法的想法。这个答案将得到赏金。谢谢。 (赏金将在我能够做到时公布)
【问题讨论】:
不是真的...你呢? 是否总是位掩码(1 或 2)? 重命名:Finding similar number patterns in table
怎么样?
我不确定关系数据库是否是解决您所说的问题的最佳方法。但是,如果将每个数字放在不同的列中,而不是所有数字的单个字符串,您可能会有更好的解决方案。
我们总是有相同数量的元素吗?
【参考方案1】:
如果您将答案模式表示为位序列,则可以使用公式 (100 * (bit_length - similarity) / bit_length
)。
按照上面提到的例子,当我们将“1”s 转换为 bit off 并将“2”s 转换为 bit on “1-1-2-2-1”时,“1-1-2-2-1”变成了 6(作为 base-10,二进制中的 00110)和"2-1-1-2-1" 变成 18 (10010b) 等等。
另外,我认为您应该将答案的位存储到最低有效位,但只要您保持不同成员的答案一致,这并不重要。
这是一个针对 MySQL 运行的示例脚本。
DROP TABLE IF EXISTS `test`;
CREATE TABLE `members` (
`id` VARCHAR(16) NOT NULL ,
`about_member` INT NOT NULL
) ENGINE = InnoDB;
INSERT INTO `members`
(`id`, `about_member`)
VALUES
('member_1', '6'),
('member_2', '18');
SELECT 100 * ( 5 - BIT_COUNT( about_member ^ (
SELECT about_member
FROM members
WHERE id = 'member_1' ) ) ) / 5
FROM members;
脚本中神奇的 5 是答案的数量(上面公式中的 bit_length)。无论实际使用的数据类型有多少位,您都应该根据自己的情况进行更改,因为 BIT_COUNT 不知道您正在使用多少字节。
BIT_COUNT 返回设置的位数,在MySQL manual 中有说明。 ^
是 MySQL 中的 binary XOR operator。
这里将member_1
的答案与每个人的答案进行比较,包括他们自己的答案 - 自然是 100% 匹配。
【讨论】:
你是怎么得到"1-1-2-2-1" becomes 6
的?
BIT_COUNT(N ^ N)
是什么意思?
当你用 bit off (0) 替换 "1"s 和 bit on (1) 替换 "2"s 时,"1-1-2-2-1" -> 00110 (base- 2) -> 6 (base-10)。
已更新。如需进一步说明,请参阅en.wikipedia.org/wiki/Bitwise_operation#XOR【参考方案2】:
阅读了关于原始问题的说明后,Levenshtein 距离不是您要寻找的答案。
您并没有尝试计算最少的编辑次数来将一个字符串更改为另一个字符串。
您正在尝试将一组数字与另一组数字进行比较。您正在寻找的是两组数字之间差异的最小(加权)和。
将每个答案放在单独的列中(Ans1、Ans2、Ans3、Ans4、....)
假设您正在搜索与 1-2-1-2 的相似之处。
选择用户名,Abs( Ans1 - 1 ) + Abs( Ans2 - 2 ) + Abs( Ans3 - 1 ) + Abs( Ans4 - 2) 作为差异排序,按差异 ASC 排序
将按与答案 1-2-1-2 的相似度列出用户,假设所有问题的权重均等。
如果您想让某些答案更重要,只需将每个术语乘以一个权重因子。
如果问题总是是/否,并且答案的数量足够小,以至于所有答案都可以放入一个整数并且所有答案的权重相同,那么您可以将所有答案编码在一个列中,然后按照建议使用 BIT_COUNT。这将是一种更快、更节省空间的实现方式。
【讨论】:
【参考方案3】:Jawa 最初发布了这个想法;这是我的尝试。
^
是 XOR 函数。它逐位比较2个二进制数,如果两个位相同则返回0,否则返回1。
0 1 0 0 0 1 0 1 0 1 1 1 (number 1)
^ 0 1 1 1 0 1 0 1 1 0 1 1 (number 2)
= 0 0 1 1 0 0 0 0 1 1 0 0 (result)
这如何适用于您的问题:
// In binary...
1111 ^ 0111 = 1000 // (1 bit out of 4 didn't match: 75% match)
1111 ^ 0000 = 1111 // (4 bits out of 4 didn't match: 0% match)
// The same examples, except now in decimal...
15 ^ 7 = 8 (1000 in binary) // (1 bit out of 4 didn't match: 75% match)
15 ^ 0 = 15 (1111 in binary) // (4 bits out of 4 didn't match: 0% match)
我们如何在 MySQL 中计算这些位:
BIT_COUNT(b'0111') = 3 // Bit count of binary '0111'
BIT_COUNT(7) = 3 // Bit count of decimal 7 (= 0111 in binary)
BIT_COUNT(b'1111' ^ b'0111') = 1 // (1 bit out of 4 didn't match: 75% match)
所以要获得相似度...
// First we focus on calculating mismatch.
(BIT_COUNT(b'1111' ^ b'0111') / YOUR_TOTAL_BITS) = 0.25 (25% mismatch)
(BIT_COUNT(b'1111' ^ b'1111') / YOUR_TOTAL_BITS) = 0 (0% mismatch; 100% match)
// Now, getting the proportion of matched bits is easy
1 - (BIT_COUNT(b'1111' ^ b'0111') / YOUR_TOTAL_BITS) = 0.75 (75% match)
1 - (BIT_COUNT(b'1111' ^ b'1111') / YOUR_TOTAL_BITS) = 1.00 (100% match)
如果我们可以让您的about_member
字段将数据存储为位(并用整数表示),我们可以轻松完成所有这些!使用0-1-0-0-0
代替1-2-1-1-1
,但不要使用破折号。
以下是 PHP 可以帮助我们的方式:
bindec('01000') == 8;
bindec('00001') == 1;
decbin(8) == '01000';
decbin(1) == '00001';
最后,这是实现:
// Setting a member's about_member property...
$about_member = '01100101';
$about_member_int = bindec($about_member);
$query = "INSERT INTO members (name,about_member) VALUES ($name,$about_member_int)";
// Getting matches...
$total_bits = 8; // The maximum length the member_about field can be (8 in this example)
$my_member_about = '00101100';
$my_member_about_int = bindec($my_member_about_int);
$query = "
SELECT
*,
(1 - (BIT_COUNT(member_about ^ $my_member_about_int) / $total_bits)) match
FROM members
ORDER BY match DESC
LIMIT 10";
最后一个查询将选择与我最相似的 10 个成员!
现在,用外行的话说,回顾一下,
我们使用二进制是因为它使事情变得更容易;二进制数就像一长串电灯开关。我们想保存我们的“电灯开关配置”,并找到具有最相似配置的成员。
^
运算符,给定 2 个灯开关配置,为我们做了一个比较。结果又是一系列开关;如果 2 个原始开关处于不同位置,则开关将是 ON
,如果它们处于相同位置,则为 OFF
。
BIT_COUNT
告诉我们有多少个开关是ON
——让我们计算有多少个开关是不同的。 YOUR_TOTAL_BITS
是交换机的总数。
但是二进制数仍然只是数字......所以一串 1 和 0 真的只是代表一个像 133 或 94 这样的数字。但是如果我们使用十进制数字,我们的“电灯开关配置”就更难可视化了。这就是 PHP 的 decbin
和 bindec
发挥作用的地方。
Learn more about the binary numeral system.
希望这会有所帮助!
【讨论】:
【参考方案4】:我会使用内置的similar_text()
PHP。这似乎正是您想要的:
$percent = 0;
similar_text($string1, $string2, $percent);
echo $percent;
它按问题预期工作。
【讨论】:
【参考方案5】:如果你没有太多的字段,你可以在 about_member 的整数表示上创建一个索引。然后您可以通过 about_member 字段上的完全匹配找到 100%,然后通过更改 1 位找到 80% 匹配,通过更改 2 位找到 60% 匹配,依此类推。
【讨论】:
【参考方案6】:我会使用Levenshtein distance 方法,您可以在MySQL 或PHP 中使用它。
【讨论】:
【参考方案7】:一种方法是计算您的搜索字符串和每个成员的 about_member 字段之间的Levenshtein distance。 Here's an implementation的函数作为MySQL存储函数。
你可以这样做:
SELECT name, LEVENSHTEIN(about_member, '1-1-2-1-2') AS diff
FROM members
ORDER BY diff ASC
相似度百分比与diff
有关;如果diff=0
则为 100%,如果 diff 是字符串的大小(减去短划线的数量),则为 0%。
【讨论】:
哇,很有趣。那么性能呢?查询慢吗?它会“吃掉”很多托管资源吗? @hey 老实说,我不知道。您必须尝试找出答案。 @hey - 如果您在 Order By 或 Where 子句中使用函数结果,它将强制执行表扫描。在一张大桌子上,这可能表现不佳。如果您告诉我们更多关于您为什么使用这种模式的信息,我们或许可以帮助您找到性能更好的基于集合的解决方案。 @hey:最好仔细看看 Levenshtein 距离是如何工作的。我不知何故怀疑这就是你的想法 - 虽然可能是错误的...... 假设会员有问题,他们可以回答问题是或否,如果他们回答是,那么它会设置值1,如果不是 - 值2。我真的不知道如何解释更好...【参考方案8】:将您的数字序列转换为位掩码并使用BIT_COUNT(column ^ search) 作为相似度函数,范围从 0(= 100% 匹配,字符串相等)到 [位长度](=0%,字符串完全不同的)。要将此相似度函数转换为百分比值,请使用
100 * (bit_length - similarity) / bit_length
例如“1-1-2-2-1”变成“00110”(假设你只有两个状态),2-1-1-2-1就是“10010”,bit_count(00110 ^ 10010) = 2,位长 = 5,100 * (5 - 2) / 5 = 60%。
【讨论】:
你能不能试着用疑问解释一下,赏金是你的,好吗?谢谢。 我想他的意思是这样的:$sql = "SELECT BIT_COUNT(about_member ^ '$search') AS similarity FROM members";
注意 about_member 中的数据应该保存为 00101 而不是 1-1-2-1-2。【参考方案9】:
显而易见的解决方案是查看 levenstein 距离(mysql 中没有内置实现,但有其他实现可以访问,例如 pl/sql 中的this one 和一些扩展),但是像往常一样,正确的方法是解决问题的方法是首先正确地规范化数据。
【讨论】:
以上是关于在表中查找相似的数字模式的主要内容,如果未能解决你的问题,请参考以下文章