根据已知字符串列表检查字符串中常见的错误识别字符

时间:2014-08-13 03:00:32

标签: php php-5.5

背景

我的(MySQL)数据库中包含六(6)个字符的代码列表。它们由随机选择的数字和字母组成。它们被视为不区分大小写,但它们在数据库中以大写形式存储。它们可能包含数字0但不包含字母O。我使用这些代码作为用户的一次性身份验证。

问题

代码已经在卡片上手写,不幸的是,某些字母和数字可能与某些人相似。这就是为什么我最初没有使用字母O,因为它与手写的0密切相关。

到目前为止我做了什么

我能够针对用户输入检查代码(不区分大小写)并确定它是否完全匹配。如果不是,我会默默地用O替换任何0,然后重试。

问题

我的问题是,我如何才能为其他字母和数字执行此操作,例如我在下面列出的字母和数字,并且仍然相对自信我没有将用户身份验证为不是他人?在这种情况下,两个字符都可以存在于代码中。我已经看过PHP中的Levenshtein函数(http://php.net/manual/en/function.levenshtein.php)以及similar_text()http://php.net/manual/en/function.similar-text.php),但两者都不是我想要的,所以我想我可能要滚动我自己(可能使用它们)来实现这一点。

类似字符:

S <=> 5
G <=> 6
I <=> 1

3 个答案:

答案 0 :(得分:4)

您所描述的问题实际上是哈希冲突。您有多个可能的输入值,并且希望它们分解为单个明确的键。我在这里有几个想法。

正如@bishop建议的那样,你真正需要确定的是,任何给定的输入是否明确无误。我的方法虽然略有不同:

对于任何给定的输入,我将生成所有可能匹配键的列表,并在数据库中查询整个列表。如果只返回一个结果,则没有问题,您可以根据该单个记录继续。在这种情况下,如果用户输入ABCDE5ABCDES,则无关紧要,因为数据库中只有一个可能匹配任何一个。

如果返回多个结果,则无法确定用户的输入是否准确或是否输入错误。

(事后看来,设计密钥最好不要使任何模糊的字符对都成为可能。只允许&#34; S&#34;禁止&#34; 5&#34 ;,例如,允许您保证任何给定输入只会有一个匹配,无论用户输入&#34; S&#34; &#34; 5&#34;,因为你可以总是安全地将您在输入中看到的任何5个转换为知道它们是输入错误的S。事实上,根据确切的值,您可以追溯修改许多或者数据库中的所有键都遵循此规则并使查找不那么麻烦。)

无论如何,在那个模棱两可的案例中,我不认为你没有别的选择,只能向用户推回并要求他们重新检查他们的意见,希望能解释他们可能存在的问题。屏幕上的消息。

编辑:

以下是根据实际提供的单个输入生成用户意图输入的可能值的示例:

<?php

$inputs = [
        'ABCDEF', // No ambiguity, DB should return 0 or 1 match.
        'AAAAA1', // One ambiguous char, user could have meant `AAAAAI`
                  // instead so search DB for both.
        '156ISG', // Worst case. If the DB values overlap a lot, there
                  // wouldn't be much hope of "guessing" what the user
                  // actually meant.
];

foreach ($inputs as $input) {
    print_r(generatePossibleMatches($input));
}

//----------------------------------------
function generatePossibleMatches($input) {
    $input = strtoupper($input);
    $ambiguous = [
        'I' => '1',
        'G' => '6',
        'S' => '5',
    ];
    $possibles = [$input];
    foreach ($ambiguous as $letter => $number) {
        foreach ($possibles as $possible) {
            foreach (str_split($possible) as $pos => $char) {
                $addNumber = substr_replace($possible, $number, $pos, 1);
                $addLetter = substr_replace($possible, $letter, $pos, 1);
                if ($char === $letter && !in_array($addNumber, $possibles)) {
                    $possibles[] = $addNumber;
                }
                if ($char === $number && !in_array($addLetter, $possibles)) {
                    $possibles[] = $addLetter;
                }
            }
        }
    }
    return $possibles;
}

答案 1 :(得分:2)

一种解决方案:转换&#34;令人困惑&#34;将字符转换为匹配可能的替换项的正则表达式,然后将扩展的正则表达式与输入匹配。示例:如果输入是&#34; AIX&#34;,正则表达式扩展将是&#34; A [I1] X&#34;。

代码:

$input = 'S1G6AB'; // given this
$store = '5I6GAB'; // need to match this

// convert each confusing character to a regular expression character class
$regex = implode('', array_map(function ($c) {
    $map = ['S'=>'[S5]','5'=>'[S5]','1'=>'[1I]','I'=>'[1I]','G'=>'[6G]','6'=>'[6G]'];
    return (array_key_exists($c, $map) ? $map[$c] : $c);
}, str_split($input)));

// match regex representing the input against the stored value    
echo (0 < preg_match("/$regex/", $store) ? 'Match' : 'No match');

Fiddle here

显然,这假设任何给定输入的排列永远不会出现在多个记录中。如果用户X有&#34; ABCDE1&#34;用户Y有&#34; ABCDEI&#34;,这不会起作用。


@beporter answer

上修改建筑物

如果您的数据库支持正则表达式(like MySQL),您可以询问是否存在冲突:

SELECT COUNT(*) FROM Table WHERE token REGEXP '$regex'

如果是2或更多,则发生碰撞,您可以要求用户检查字母并重试。或者也许让他们输入他们信息的其他部分,比如姓氏?这对take it to the UX people来说是一个很好的问题。

答案 2 :(得分:1)

你看过Hamming Distance了吗?

虽然您有字母和数字,但您可以将所有内容转换为二进制(ASCII值)并使用汉明距离进行比较。如果距离大于某个阈值,则拒绝它。 否则,您实际上是在寻找一个字符串指标,以满足您识别错误识别&#34;字符。你是对的 - 你可能必须自己建造一个。