首先:我知道这个系统会有缺陷!
注意:我添加了其他几种语言,因为我没有发现特定于php的问题。JavaScript 或 jquery 解决方案可以工作...我可以更改语言...这是我追求的方法!
什么:我正在尝试解析一个字符串以确定用户想要什么。
这个想法是字符串是从语音生成的
示例 1:打开厨房的灯,关掉卧室和客厅的灯。
示例 2:打开厨房的灯,打开卧室的灯,关掉客厅的灯。
例3:关掉我的厨房、卧室和客厅的灯。
这是一个过于简化的示例,但请注意,我想扩展到这三个房间之外,以及只控制灯光示例:外部吊扇打开...
如何:我目前正在使用一些while循环来迭代数组并检查数组中是否有某些字符串。
更多方法:我的想法是首先在"and"上的字符串上拆分。然后,我检查每个数组是否打开或关闭。如果它没有开或关,我将数组与下一个连接。
帮助:我很想清理这个概念,看看别人的想法......我愿意做任何事情..
谢谢JT
法典:
$input = 'kitchen lights on and bed and living lights off';
$output = preg_split( "/ (and) /", $input );
$num = (int)count($output);
$i=0;
while($i<$num){
if ((strpos($output[$i],'on') !== false)||(strpos($output[$i],'off') !== false)) {}
elseif(((strpos($output[$i+1],'on') !== false)||(strpos($output[$i+1],'off') !== false))){
$output[$i+1] .= ' + '.$output[$i];
unset($output[$i]);
}
$i++;
}
$output = array_values($output);
$i=0;
$num = (int)count($output);
echo '<br>';
while($i<$num){
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'on') !== false)&&(strpos($output[$i],'kitchen') !== false)){
echo'kitchen lights on<br>';
}
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'off') !== false)&&(strpos($output[$i],'kitchen') !== false)){
echo'kitchen lights off<br>';
}
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'on') !== false)&&(strpos($output[$i],'living') !== false)){
echo'living lights on<br>';
}
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'off') !== false)&&(strpos($output[$i],'living') !== false)){
echo'living lights off<br>';
}
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'on') !== false)&&(strpos($output[$i],'bed') !== false)){
echo'bed lights on<br>';
}
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'off') !== false)&&(strpos($output[$i],'bed') !== false)){
echo'bed lights off<br>';
}
$i++;
}
代码试用 2: 注意:这处理了上述所有示例!
<?php
//works list
$inp[]='turn the lights in the bedroom on';
$inp[]='Turn on the bedroom light';
$inp[]='turn on the lights in the bedroom';
$inp[]='Turn my kitchen and my bedroom and living room lights off.';
$inp[]='Turn the light in the kitchen on and the fan in the bedroom off';
$inp[]='Turn my kitchen lights on and my bedroom and living room lights off';
$inp[]='Turn my kitchen fan and my bedroom lights on and living room lights off.';
$inp[]='Turn my kitchen lights on and my bedroom lights on and living room lights off';
$inp[] = 'kitchen lights on and bath and living lights off';
$inp[] = 'flip on the lights in the living room';
$inp[] = 'turn on all lights';
//does not work list
//$inp[] = 'turn on all lights but living';
foreach ($inp as $input){
$input = trim($input);
$input = rtrim($input, '.');
$input = trim($input);
$input = rtrim($input, '.');
$words = explode(" ", $input);
$state = array('and','but','on','off','all','living','bed','bedroom','bath','kitchen','dining','light','lights','fan','tv');
$result = array_intersect($words, $state);
$result = implode(" ", $result);
$result = trim($result);
//$result = preg_split('/(and|but)/',$input,-1, PREG_SPLIT_DELIM_CAPTURE);
$result = preg_split( "/ (and|but) /", $result );
//$result = explode("and", $result);
$sep=array();
foreach($result as $string){
$word = explode(" ", $string);
$sep[]=$word;
}
$test=array();
$num = (int)count($sep);
$i=0;
while($i<($num)){
$result = (int)count(array_intersect($sep[$i], $state));
$j=$i;
while($result<=3)
{
$imp = implode(" ", $sep[$j]);
if(isset($test[$i])){$test[$i]=$imp.' '.$test[$i];}
else{$test[$i]=$imp;}
if ($result>=3){$j++;break;}
$result = (int)count(array_intersect($sep[++$j], $state));
}
$i=$j;
}
print_r($test);
echo '<br>';
}
?>
解析自然语言并非易事,如果你想要一个真正的自然语言解析器,我建议你尝试使用现有的项目或库。这是一个基于Web的解析器,基于斯坦福解析器。或者维基百科是一个很好的起点。
话虽如此,如果您愿意限制所涉及的语法和关键字,您也许可以简化它。首先,你需要知道什么是重要的——你在"地方"(卧室、厨房)中有"东西"(灯、风扇)需要进入特定的状态("开"、"关")。
我会将字符串放入单词数组中,要么使用 str_tok,要么在' '
上爆炸。
现在你有一组单词从末尾开始,然后倒退寻找"状态"——开或关。然后沿着它向后寻找一个"东西",最后是一个"地方"。如果您达到另一个状态,则可以重新开始。
让我尝试在伪代码中执行此操作:
// array of words is inArray
currentPlace = null;
currentThing = null;
currentState = null;
for (i = (inArray.length - 1); i >= 0; i--) {
word = inArray[i];
if (isState(word)) {
currentState = word;
currentPlace = null;
currentThing = null;
} else if (currentState) {
if (isThing(word)) {
currentThing = word;
currentPlace = null;
} else if (currentThing) {
if (isPlace(word)) {
currentPlace = word
// Apply currentState to currentThing in currentPlace
}
// skip non-place, thing or state word.
}
// Skip when we don't have a thing to go with our state
}
// Skip when we don't have a current state and we haven't found a state
}
而且,写完之后,很明显它应该使用状态机和 switch 语句——这表明我应该首先在纸上设计它。如果你变得更复杂,你想使用状态机来实现逻辑 - 状态将是"lookingForState","lookingForThing"等
。此外,您实际上并不需要currentPlace
作为变量,但我会保留它,因为它使逻辑更清晰。
编辑
如果你想支持"打开卧室的灯",你需要调整逻辑(如果你没有东西,你需要保存"地方")。如果你还想支持"打开卧室的灯",你需要走得更远。
想来想去,我想知道你能不能做到:
have a currentState variable and arrays for currentPlace and currentThing
for each word
if it's a state:
store it in currentState
if it's a thing, or place:
add it to the approriate array
if currentState is set and there is content in currentPlaces and currentThings:
apply currentState to all currentThings in all currentPlaces
这还不完全存在,但其中一个实现可能会给你一个起点。
编辑 2
好的,我测试了一下,由于英语的结构方式,存在一些问题。问题是如果你想支持"打开..."。和"转...on"然后你需要使用我的第二个伪代码,但由于句子中的"and",这并不容易工作。例如:
打开厨房的灯,关掉卧室和客厅的灯。
第一个和连接两个语句,第二个和连接到位置。正确的方法是绘制句子图,以找出适用于什么的内容。
有两个快速选项,首先您可以坚持使用不同的单词或短语来连接两个命令:
打开厨房的灯,然后关掉卧室和客厅的灯。 打开我的厨房灯,也关掉我的卧室和客厅的灯。
或者,这可能更容易,您可以坚持只使用"转...关/开'。这适用于我上面的第一个伪代码。
JavaScript 第一个伪代码的示例。
请注意,如果有任何标点符号等机会,您可能需要对字符串进行大量预处理。您可能还想考虑将"客厅"(以及类似的两个单词短语)替换为"客厅",而不是像我一样只匹配一个单词并希望得到最好的结果。此外,代码可以简化一点,但我想让它接近伪代码示例。
编辑 3
新的 JavaScript 示例
这处理了一些额外的句子,并且清理得更好一些,它仍然依赖于每个子句末尾的"状态",因为这是它用作应用动作的触发器(这个版本可能会向前读取而不是向后读取)。此外,它不会处理类似以下内容:
Turn my kitchen fan and my bedroom lights on and living room lights off.
你必须做一些更复杂的事情来理解"厨房"和"风扇"以及"卧室"和"灯"之间的关系。
这些技术的一些组合可能足以做一些相当令人印象深刻的事情,只要输入/说出命令的人遵循一些基本规则。
这当然不是最有效的解决方案,但这里有一个。你绝对可以改进它,比如缓存正则表达式,但你明白了。每个子数组中的最后一项是操作。
演示
var s = 'Turn my kitchen lights on and my bedroom lights on and living room lights off and my test and another test off',
r = s.replace(/^Turn|'s*my/g, '').match(/.+? (on|off)/g).map(function(item) {
var items = item.trim().replace(/^and's*/, '').split(/'s*and's*/),
last = items.pop().split(' '),
op = last.pop();
return items.concat([last.join(' '), op]);
});
console.log(r);
介意解释你使用的逻辑...我的意思是我正在阅读代码,但我 只是好奇你能不能说得更好
逻辑其实很简单,也许太简单了:
var s = 'Turn my kitchen lights on and my bedroom lights on and living room lights off and my test and another test off',
r = s
.replace(/^Turn|'s*my/g, '') //remove noisy words
.match(/.+? (on|off)/g) //capture all groups of [some things][on|off]
//for each of those groups, generate a new array from the returned results
.map(function(item) {
var items = item.trim()
.replace(/^and's*/, '') //remove and[space] at the beginning of string
//split on and to get all things, for instance if we have
//test and another test off, we want ['test', 'another test off']
.split(/'s*and's*/),
//split the last item on spaces, with previous example we would get
//['another', 'test', 'off']
last = items.pop().split(' '),
op = last.pop(); //on/off will always be the last item in the array, pop it
//items now contains ['test'], concatenate with the array passed as argument
return items.concat(
[
//last is ['another', 'test'], rejoin it together to give 'another test'
last.join(' '),
op //this is the operation
]
);
});
编辑:在我发布答案时,我还没有意识到您需要它是多么复杂和灵活。我提供的解决方案仅适用于结构如我的示例,具有可识别的嘈杂单词和特定命令顺序的句子。对于更复杂的事情,您别无选择,只能像@SpaceDog建议的那样创建一个解析器。只要我有足够的时间,我会尝试想出一些东西。
我一直在解析菜单和食谱(未完成),这是我的方法:
- 找到句子分隔符(我使用 AND 以及其他)
- 解析每个句子以找到您需要的
key
单词(灯/灯泡/等,开/关) - 如果你有一组有限的地方(厨房,浴室等...
- 搜索这些关键字,删除其他关键字
- 还
- 删除某些人可能使用的
extra words
(明亮,彩色等...
- 将其存储到数组中,可能如下所示:
- 什么
- 哪里
- 如果您没有其中一个字段,请将其留空
- 对于每个结果,请检查您拥有的内容,如果您有空白字段,请用之前的解析填充它
即:打开卧室和厨房的灯
- 1:
- 打开卧室的灯
- 内容:开灯
- 地点: 卧室
- 阿拉伯数字:
- 在厨房里
- 什么:
- 地点: 厨房
what_2
为空,则what_2
为lights on
请记住,有时需要用下一个结果填充数组(取决于句子的结构,但这种情况很少见),我在其中添加"+"或"-",以便我知道在解析时是否必须前进或后退以找到缺失的部分