我正在构建一个单词解扰器(php/mysql),它接受用户输入的2到8个字母,并返回可以由这些字母组成的2到八个字母的单词,不一定使用所有字母,但绝对不包括比提供的更多的字母。
用户将输入类似MSIKE或MSIKEI(两个i)的内容,或字母的任何组合或字母的多次出现。
下面的查询将查找所有出现的包含M、S、I、K或E的单词。
但是,下面的查询也会返回多次出现未请求的字母的单词。例如,单词"温顺"会被返回,即使它有两个e,用户没有输入两个e;或者单词"亲吻",即使用户没有输入s两次。
SELECT word
FROM words
WHERE word REGEXP '[msike]'
AND has_a=0
AND has_b=0
AND has_c=0
AND has_d=0
(we skip e) or we could add has_e=1
AND has_f=0
...and so on...skipping letters m, s, i, k, and e
AND has_w=0
AND has_x=0
AND has_y=0
AND has_z=0
请注意,如果字母出现在单词中,那么列has_a、has_b等要么为1,要么为0。
我对表架构的任何更改都持开放态度。
此网站:http://grecni.com/texttwist.php是我试图效仿的一个很好的例子。
问题是如何修改查询,使其不返回多次出现字母的单词,除非用户多次明确输入字母。按单词长度分组将是一个额外的奖励。
非常感谢。
编辑:我根据@awei的建议修改了数据库,has_{letter}现在是count_{letter},并存储相应字母在相应单词中的出现总数。当用户多次输入一封信时,这可能很有用。示例:用户输入MSIKES(两个)。
此外,我已经放弃了REGEXP方法,如原始SQL语句中所示。大部分工作都是在PHP方面完成的,但仍有许多障碍。
编辑:包括表的前10行
id word alpha otcwl ospd csw sowpods dictionary enable vowels consonants start_with end_with end_with_ing end_with_ly end_with_xy count_a count_b count_c count_d count_e count_f count_g count_h count_i count_j count_k count_l count_m count_n count_o count_p count_q count_r count_s count_t count_u count_v count_w count_x count_y count_z q_no_u letter_count scrabble_points wwf_points status date_added
1 aa aa 1 0 0 1 1 1 aa a a 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 2 1 2015-11-12 05:39:45
2 aah aah 1 0 0 1 0 1 aa h a h 0 0 0 2 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 6 5 1 2015-11-12 05:39:45
3 aahed aadeh 1 0 0 1 0 1 aae hd a d 0 0 0 2 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 9 8 1 2015-11-12 05:39:45
4 aahing aaghin 1 0 0 1 0 1 aai hng a g 1 0 0 2 0 0 0 0 0 1 1 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 6 10 11 1 2015-11-12 05:39:45
5 aahs aahs 1 0 0 1 0 1 aa hs a s 0 0 0 2 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 4 7 6 1 2015-11-12 05:39:45
6 aal aal 1 0 0 1 0 1 aa l a l 0 0 0 2 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 4 1 2015-11-12 05:39:45
7 aalii aaiil 1 0 0 1 1 1 aaii l a i 0 0 0 2 0 0 0 0 0 0 0 2 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 5 6 1 2015-11-12 05:39:45
8 aaliis aaiils 1 0 0 1 0 1 aaii ls a s 0 0 0 2 0 0 0 0 0 0 0 2 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 6 6 7 1 2015-11-12 05:39:45
9 aals aals 1 0 0 1 0 1 aa ls a s 0 0 0 2 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 4 4 5 1 2015-11-12 05:39:45
10 aardvark aaadkrrv 1 0 0 1 1 1 aaa rdvrk a k 0 0 0 3 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 2 0 0 0 1 0 0 0 0 0 8 16 17 1 2015-11-12 05:39:45
认为您已经对修改后的模式进行了艰苦的工作。现在所需要做的就是修改查询以查找<=
,即用户指定的每个字母的计数。
例如,如果用户输入"ALIAS":
SELECT word
FROM words
WHERE count_a <= 2
AND count_b <= 0
AND count_c <= 0
AND count_d <= 0
AND count_e <= 0
AND count_f <= 0
AND count_g <= 0
AND count_h <= 0
AND count_i <= 1
AND count_j <= 0
AND count_k <= 0
AND count_l <= 1
AND count_m <= 0
AND count_n <= 0
AND count_o <= 0
AND count_p <= 0
AND count_q <= 0
AND count_r <= 0
AND count_s <= 1
AND count_t <= 0
AND count_u <= 0
AND count_v <= 0
AND count_w <= 0
AND count_x <= 0
AND count_y <= 0
AND count_z <= 0
ORDER BY CHAR_LENGTH(word), word;
注意:根据要求,这是按单词长度排序,然后按字母顺序排列。甚至对<= 0
使用了<=
,只是为了更容易手动修改其他字母。
这将返回"aa"、"aal"answers"aals"(但不是"aalii"或"aaliis",因为它们都有两个"i")。
请参阅SQL Fiddle演示。
由于您有两种不同的需求,我建议同时实现两种不同解决方案。
若您不关心dup字母,那个么就构建一个包含26个字母的SET
数据类型。根据单词的内容填充位。这会忽略重复的字母。这也便于查找具有字母子集的单词:(the_set & ~the_letters) = 0
。
如果你真的关心dups,请将单词中的字母排序,并将其作为关键字存储。"msike"变成了"eikms"。
构建一个包含3列的表:
eikms -- non unique index on this
msike -- the real word - probably good to have this as the PRIMARY KEY
SET('m','s','i',','k','e') -- for the other situation.
msikei和meck将被列入
eikms
msikei
SET('m','s','i',','k','e') -- (or, if more convenient: SET('m','i','s','i',','k','e')
ekm
meek
SET('e','k','m')
REGEXP
对于您的任务来说是不实用的。
编辑1
我想你还需要一个列来指示单词中是否有双字母。这样,您可以区分msikes
允许使用kiss
,但msike
允许使用。
编辑2
SET
或INT UNSIGNED
可以为26个字母中的每一个保持1位——0表示不存在,1表示存在。
msikes
和msike
都将进入正好5位开启的集合。对于msikes
,INSERT
的值将是'm,s,i,k,e,s'
。由于其余部分需要涉及布尔运算,因此使用INT UNSIGNED
可能会更好。所以…
a is 1 (1 << 0)
b is 2 (1 << 1)
c is 4 (1 << 2)
d is 8 (1 << 3)
...
z is (1 << 25)
对于INSERT
,使用|
运算符。bad
变为
(1 << 1) | (1 << 0) | (1 << 3)
注意比特是如何排列的,底部有"a":
SELECT BIN((1 << 1) | (1 << 0) | (1 << 3)); ==> 1011
类似地,"ad"是1001。那么,"ad"answers"bad"匹配吗?答案来自
SELECT b'1001' & ~b'1011' = 0; ==> 1 (meaning 'true')
这意味着"ad"(1001)中的所有字母都在"bad"(1011)中找到。让我们介绍一下"床",它是11010。
SELECT b'11010' & ~b'1011' = 0; ==> FALSE because of 'e' (10000)
但"爸爸"(1001)会很好用:
SELECT b'1001' & ~b'1011' = 0; ==> TRUE
因此,现在出现了"dup"标志。由于"dad"有dup字母,但"bad"没有,所以你的规则说这不匹配。但这是一个"骗局"才做出的决定。
如果你还没有上过布尔算术的课程,那么,我刚刚介绍了前几章。如果我写得太快,找一本关于这方面的数学书,然后跳进去。"这不是火箭科学。"
因此,回到需要什么代码来决定my_word是否有字母子集以及是否允许它有重复的字母:
SELECT $my_mask & ~tbl.mask = 0, dup FROM tbl;
然后在两者之间进行适当的"与/或"运算以完成逻辑运算。
由于MySQL对Regex的支持有限,我能做的最好的事情就是用PHP脚本生成查询,假设它只包括英文字母。似乎制作一个排除无效单词的表达式比包含它们的表达式更容易。
<?php
$inputword = str_split('msikes');
$counter = array();
for ($l = 'a'; $l < 'z'; $l++) {
$counter[$l] = 0;
}
foreach ($inputword as $l) {
$counter[$l]++;
}
$nots = '';
foreach ($counter as $l => $c) {
if (!$c) {
$nots .= $l;
unset($counter[$l]);
}
}
$conditions = array();
if(!empty($nots)) {
// exclude words that have letters not given
$conditions[] = "[" . $nots . "]'";
}
foreach ($counter as $l => $c) {
$letters = array();
for ($i = 0; $i <= $c; $i++) {
$letters[] = $l;
}
// exclude words that have the current letter more times than given
$conditions[] = implode('.*', $letters);
}
$sql = "SELECT word FROM words WHERE word NOT RLIKE '" . implode('|', $conditions) . "'";
echo $sql;
类似的东西可能对您有用:
// Input Word
$WORD = strtolower('msikes');
// Alpha Array
$Alpha = range('a', 'z');
// Turn it into letters.
$Splited = str_split($WORD);
$Letters = array();
// Count occurrence of each letter, use letter as key to make it unique
foreach( $Splited as $Letter ) {
$Letters[$Letter] = array_key_exists($Letter, $Letters) ? $Letters[$Letter] + 1 : 1;
}
// Build a list of letters that shouldn't be present in the word
$ShouldNotExists = array_filter($Alpha, function ($Letter) use ($Letters) {
return ! array_key_exists($Letter, $Letters);
});
#### Building SQL Statement
// Letters to skip
$SkipLetters = array();
foreach( $ShouldNotExists as $SkipLetter ) {
$SkipLetters[] = "`has_{$SkipLetter}` = 0";
}
// count condition (for multiple occurrences)
$CountLetters = array();
foreach( $Letters as $K => $V ) {
$CountLetters[] = "`count_{$K}` <= {$V}";
}
$SQL = 'SELECT `word` FROM `words` WHERE '.PHP_EOL;
$SQL .= '('.implode(' AND ', $SkipLetters).')'.PHP_EOL;
$SQL .= ' AND ('.implode(' AND ', $CountLetters).')'.PHP_EOL;
$SQL .= ' ORDER BY LENGTH(`word`), `word`'.PHP_EOL;
echo $SQL;