在表中查找类似的数字模式

时间:2010-08-08 22:14:15

标签: php mysql

好的,我们假设我们有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方法的情况下给我一些想法。那个答案会得到赏金。谢谢。 (赏金将在我能够做到的时候公布)

9 个答案:

答案 0 :(得分:12)

将您的数字序列转换为位掩码并使用BIT_COUNT(列^搜索)作为相似度函数,范围从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%。

答案 1 :(得分:8)

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字段存储数据作为位(并用整数表示),我们可以轻松完成所有这些操作!使用1-2-1-1-1代替0-1-0-0-0,但不使用破折号。

以下是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的decbinbindec进来的地方。

Learn more about the binary numeral system.

希望这有帮助!

答案 2 :(得分:3)

执行此操作的一种方法是计算搜索字符串与每个成员的about_member字段之间的Levenshtein distanceHere'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%。

答案 3 :(得分:3)

显而易见的解决方案是查看levenstein距离(没有内置到mysql中的实现,但是有其他实现可以访问,例如pl / sql中的this one和一些扩展),但是像往常一样,右边解决问题的方法是首先正确地对数据进行规范化。

答案 4 :(得分:2)

阅读了原始问题的澄清评论后, Levenshtein距离不是您正在寻找的答案

尝试计算最少的编辑数以将一个字符串更改为另一个字符串。

试图将一组数字与另一组数字进行比较。您要查找的是两组数字之间差异的最小(加权)总和。

将每个答案放在一个单独的栏目中(Ans1,Ans2,Ans3,Ans4,....)

假设您正在寻找1-2-1-2的相似之处。

SELECT UserName,Abs(Ans1 - 1)+ Abs(Ans2 - 2)+ Abs(Ans3 - 1)+ Abs(Ans4 - 2)as Difference ORDER BY Difference ASC

假设所有问题均匀加权,将按照与答案1-2-1-2的相似性列出用户。

如果您想让某些答案更重要,只需将每个字词乘以加权因子即可。

如果问题总是为是/否且答案数量足够小以致所有答案都可以拟合成一个整数并且所有答案都具有相同的权重,那么您可以在一列中编码所有答案,建议使用BIT_COUNT。这将是一个更快,更节省空间的实现。

答案 5 :(得分:1)

我会使用内置的similar_text() PHP。它似乎正是你想要的:

$percent = 0;
similar_text($string1, $string2, $percent);

echo $percent;

它可以解决问题。

答案 6 :(得分:0)

我会使用Levenshtein distance方法,您可以在MySQLPHP内使用它。

答案 7 :(得分:0)

如果您将答案模式表示为位序列,则可以使用公式(100 * (bit_length - similarity) / bit_length)。

按照上面提到的例子,当我们将“1”转换为bit off并且“2”s转换为“1-1-2-2-1”上的位时变为6(作为基数-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%匹配。

答案 8 :(得分:0)

如果没有太多字段,可以在about_member的整数表示上创建索引。然后你可以在about_member字段上找到100%的完全匹配,然后通过改变1位找到80%的匹配,通过改变2位找到60%的匹配,依此类推。