当我在Prolog中得到关于我的数据结构的批评时,我在这里向专家们询问了替代解决方案。
例如,我有一个XML格式的配方描述数据集
<recipeml fileversion="13.8.2014">
<recipe>
<head>
<title>Green Soup</title>
</head>
<ing-div type="titled">
<title>soup</title>
<ingredients>
<ing>
<amt><qty>500</qty><unit>gramm</unit></amt>
<item>pea</item>
</ing>
<ing>
<amt><qty>200</qty><unit>ml</unit></amt>
<item>cream</item>
</ing>
...
</ingredients>
</ing-div>
<directions>
<step>Do something, cooking ....</step>
<step>Next do again something...</step>
...
</directions>
</recipe>
<recipe>
...
</recipe>
...
</recipeml>
我选择使用列表将它作为迭代元素树存储在Prolog中:
database([element('recipeml',[version=0.5],
[element('recipe',[],
[element('head',[],
[element('title',[],['Green Soup']
)]
),
element('ing-div',[type=titled],
[element('title',[],['soup']),
element('ingredients',[],
[element(ing,[],
[ element(amt,[],
[ element(qty,[],['500']), element(unit,[],[gramm]),]),
element(item,[],['pea'])
]),
element(ing,[],
[ element(amt,[],
[ element(qty,[],['200']), element(unit,[],[ml]),]),
element(item,[],['pea'])
])
]
)]
)]
),
element('recipe',[],...
)]
)]).
我想要做的是根据用户输入轻松查看食谱。 用户可以将配方名称的一部分或一部分作为输入。
实际上我按照
运行了这些元素ask_element(Name, Child, Parent) :-
(
member( element(Name,_,Child),Parent)
;
member( element(_,_,NewParent),Parent),
[_|_] = NewParent,
ask_element(Name, Child, NewParent)
).
我通过
获得了所有含有特殊成分的食谱 findall(RTitle,
(
ask_element('recipe',RKnot,Knot),
ask_element('item',TmpIng,RKnot),
contains(TmpIng,Ingredient),
[Ing|_] = TmpIng, % avoid brackets [Egg]
define_xml_knot(['head','title'],_,RKnot,TmpRTitle),
[RTitle|_] = TmpRTitle % avoid brackets [Soup]
,Bag),
我的结果是食谱标题列表。如果输入了成分列表,我需要 第二个分析步骤,以获得具有最匹配成分的配方。也许这是 不是真的Prolog风格?
在保罗·莫拉(谢谢)的评论之后,有一个想法是将数据安排为
recipe(IDnumber,'Green Soup',ingredients(item(500,gramm,'pea'),item(200,ml,'cream')),steps('Do something','Next step do again something')).
我不确定这是否真的有帮助。寻找含有某种成分的配方,如果我所寻找的成分(或单词的一部分)被包含,我必须在每个食谱中逐步查看每个食谱。如果我想添加一个新的描述符,例如&#34; level(easy)
&#34;我必须更改所有数据调用,因为recipe()
中的元素数量发生了变化。使用element(element...)
构造,我不必更改调用。
但是响应会更好,只返回IDnumber,然后我将整个食谱放在一个&#34; call&#34; (recipe(123,X,Y,Z)
)进一步处理。其实我
作为响应返回&#34;列表中的字符串文本&#34;正如你在&#34; Bag&#34;以上......
这是我在Prolog中的第一个应用程序,因此我不太熟悉适当的数据存储。我会很感激每一个提示。
答案 0 :(得分:2)
SWI-Prolog提供库(xpath),允许引用节点和属性'Prolog样式'。 在回溯时,实例化将返回给调用者。所以你可以在findall等中使用,因为你认为最合适。
?- database(Db), xpath(Db, //recipe, Recipe).
将枚举所有食谱。该库功能强大,但不易学。看看你在那里看到的(微薄)例子......
您还可以查看here,我回答建议library(xpath)
来处理GCC XML。我用它来构建我的SWI / OpenGL界面......
答案 1 :(得分:1)
如果您想从XML文件中表示的Prolog信息访问,Carlo的解决方案是一个很好的解决方案。
但是,让我们假设你想要Prolog中代表的所有食谱。正如您所描述的,一种解决方案是每个配方使用一个事实,其结构最适合您应用程序中最常见的数据访问模式。你还注意到,寻找例如使用特定成分或需要特定测试的食谱效率不高,因为您必须从食谱事实到成分列表(或步骤),然后在该列表上进行线性搜索(您可以使用二叉搜索树而不是列表但我怀疑可能的低数量的物品会在计算上证明它是合理的)。此外,在您的问题中添加level/1
等新描述符需要将更改传播到访问配方数据的所有代码。考虑到这些问题,可能值得研究使用模块或对象表示来进行配方。我们的想法是每个配方都由一个模块或对象表示,每个属性有一个谓词。使用此表示,访问配料的计算成本将与访问配方名称或其中一个步骤的成本相同。例如,搜索具有特定成分的配方,枚举所有配方模块或对象的必要步骤是一种廉价的操作。使用对象表示添加新描述符很容易,也可以使用模块表示进行黑客攻击(实质上,您只需修改配方界面,可能为新描述符添加默认值)。它也可能具有混合表示,并且存在这种解决方案合理的情况。如果您分享有关要应用于配方数据库的访问或推理的更多详细信息,建议会更容易。
更新:一个例子,基于Prolog的Logtalk面向对象扩展(可以与大多数Prolog实现一起使用,包括GNU Prolog和SWI-Prolog)。有几种变化是可能的。对于黑客使用替代模块的接口/协议的概念,参见例如这post。
:- protocol(recipep).
:- public([
name/1, ingredient/3, step/1 % descriptors
]).
:- end_protocol.
:- object(proto_recipe, implements(recipep)).
:- public([
ingredient/1, ingredients/1, steps/1 % utility predicates
]).
ingredient(Ingredient) :-
::ingredient(Ingredient,_,_).
ingredients(Ingredients) :-
findall(Ingredient, ::ingredient(Ingredient,_,_), Ingredients).
steps(Steps) :-
findall(Step, ::step(Step), Steps).
:- end_object.
:- object(green_soup, extends(proto_recipe)).
name('Green Soup').
ingredient(pea, 500, gr).
ingredient(cream, 200, ml).
step(...).
...
:- end_object.
:- object(mashed_peas, extends(proto_recipe)).
name('Mashed Peas').
ingredient(pea, 700, gr).
ingredient(salt, 20, gr).
...
:- end_object.
示例查询:
?- green_soup::ingredients(Ingredients).
Ingredients = [pea, cream].
?- conforms_to_protocol(Recipe, recipep), Recipe::ingredient(pea).
Recipe = green_soup ;
Recipe = mashed_peas ;
false.
现在假设您稍后要为所有食谱添加level/1
描述符。只是为了好玩,让我们使用热补丁:
:- category(add_recipe_level_descriptor, complements(proto_recipe)).
:- public(level/1).
:- dynamic(level/1).
:- end_category.
您现在可以添加烹饪体验了。例如。你在制作绿汤时经常遇到麻烦:
?- green_soup::assertz(level(hard)).
true.
但大多数食谱都很简单,所以让我们为所有食谱添加一个默认值:
:- category(recipe_level_default_value, complements(proto_recipe)).
level(easy).
:- end_category.
现在你可以问:
?- mashed_peas::level(Level).
Level = easy.
我省略了一些细节(例如设置和编译/加载步骤),但希望这可以让您了解可能的内容(但是完整运行的示例here)。