Prolog中的知识表示 - 如何存储数据?

4

由于我的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风格?
一个想法是,根据Paulo Moura(感谢)的评论,将数据排列为
recipe(IDnumber,'Green Soup',ingredients(item(500,gramm,'pea'),item(200,ml,'cream')),steps('Do something','Next step do again something')).

我不确定这是否真的有用。如果我要寻找某个特定成分的食谱,我必须逐步查看每个食谱中的每个项目,以确定我正在寻找的成分(或部分单词)是否包含在其中。如果我想添加一个新的描述符,例如 "level(easy)",我必须更改所有数据调用,因为recipe()中元素的数量发生了变化。使用element(element...)结构,我不必更改调用。但是,通过仅返回ID号码作为响应,然后我可以在一个“调用”(recipe(123,X,Y,Z))中获取整个食谱以进行进一步处理。实际上,我将“字符串文本列表”作为响应返回,就像您在上面的“Bag”中看到的那样...
这是我的Prolog首个应用程序,因此我对适当的数据存储不太熟悉。我会感激每一个提示。

在我的建议中,成分和步骤将有一个单一的参数,分别是成分和步骤的列表。例如,使用您的示例,recipe(IDnumber,'Green Soup',ingredients([item(500,gramm,'pea'),item(200,ml,'cream')]),steps(['Do something','Next step do again something'])).。将成分和步骤作为具有与食谱相关的元数的复合术语只会使处理变得更加复杂,没有任何好处。这也更接近您呈现的XML结构(假设这是您的意图)。 - Paulo Moura
2个回答

2
SWI-Prolog 提供了库(xpath),允许以“Prolog 风格”引用节点和属性。在回溯时,实例化将返回给调用者。因此,您可以根据需要在 findall 等中使用。
?- database(Db), xpath(Db, //recipe, Recipe).

将以下文本翻译成中文:

将枚举所有的食谱。这个库非常强大,但不容易学习。看看你在那里看到的(可怜的)例子...

你也可以看看这里,我建议使用library(xpath)来处理GCC XML。我用它来构建我的SWI / OpenGL接口...


既然您已经使用了SWI使用的相同XML表示法,也许值得花费一些精力将您的访问谓词包装在兼容的接口中。如果您只保留基本功能,应该很容易做到...实际上,这归结为具有“惰性评估”的能力,就像member/2已经执行的那样,并且自定义模式匹配。不是太难...此外,它显示了一个重要的架构点,即如何使用xpath_chk(我认为映射到memberchk)高效地处理大型树形结构。 - CapelliC
相似之处在于我是从SWI Prolog开始迈出我的第一步。由于许可证的具体规定,这里决定使用GNU Prolog...然而,由于在Windows上运行GNU Prolog(或gplc)时遇到的问题,这可能不是最终决定... - kiw
你对许可证的评论很有意思。我曾经认为SWI-Prolog比GnuProlog更宽松,也许我错了。 - CapelliC
据我所理解,就我们在这里讨论的许可证而言,原则上LGPL对两者都适用,但是(1)对于GNU Prolog,明确说明了使用gplc编译的自己的开发项目可以从法律角度看作是独立的开发项目。而在SWI Prolog网站上(正如我们的IT专家所说),并没有如此明确地写明,只是说“SWI希望采用与gcc相同的许可证原则”(2) SWI Prolog包含其他许可证条件的软件包。 - kiw
1
感谢您“重新开放”编译器讨论 :) 我刚刚给JanW写了一封电子邮件,看来您是正确的,希望我很快可以切换回SWI :) - kiw
显示剩余2条评论

1
如果您想从Prolog中访问以XML文件表示的信息,Carlo的解决方案是一个不错的选择。
假设您希望将所有食谱表示为Prolog。一种解决方案是使用每个食谱一个事实的结构,该结构最适合应用程序中最常见的数据访问模式。正如您所注意到的,查找使用特定成分或需要特定测试的食谱将不会很有效,因为您必须从食谱事实转到成分(或步骤)列表,然后在该列表上进行线性搜索(您可以使用二叉搜索树而不是列表,但我怀疑可能的低数量的项目是否足以在计算上证明它)。此外,添加新的描述符,例如level/1,需要对访问食谱数据的所有代码进行潜在传播的更改。考虑到这些问题,值得研究使用模块或对象表示来处理食谱。每个食谱由一个具有每个属性的谓词的模块或对象表示。使用此表示,访问成分的计算成本与访问食谱名称或其步骤的成本相同。例如,在搜索具有特定成分的食谱时,枚举所有食谱模块或对象的必要步骤是一项廉价操作。使用对象表示很容易添加新的描述符,并且也可以使用模块表示进行修改(基本上,您只需修改食谱接口,可能为新描述符添加默认值)。还可以使用混合表示,有些情况下这种解决方案是合理的。如果您分享有关对食谱数据库的访问或推理的更多详细信息,我们可以提供建议。 更新: 一个例子,基于Logtalk面向对象扩展到Prolog(可以与大多数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.

我省略了一些细节(例如设置和编译/加载步骤),但希望这能让您了解可能的情况(完整运行示例在此处)。


谢谢您的再次输入 :) 说实话,在Prolog的上下文中,模块或对象表示对我来说是新的。也许这对我的问题来说有点过大了。我不需要最快速的数据库访问,只需要“不太傻”的一个。我的主要话题将是对话组件,接近于“语言理解过程”,以便用户可以输入“我还有一个鸡蛋和一杯奶油,我能做什么?” 因此,对数据库的搜索仅限于作为配料、食谱名称或其他食谱描述符的关键字。[...] - kiw
[...]我不需要保留原始的XML结构,我需要在启动时将数据加载到Prolog中进行表示。因此,我认为您的第一个建议使用基于列表的参数recipe(IDnumber,'Green Soup',ingredients([item(500,gramm,'pea'),item(200,ml,'cream')]),steps(['Do something','Next step do again something']))已经足够好了。[...] - kiw
希望这是我最后一个问题:如果我想扩展数据模型以将配料表分成不同的部分,是否将列表内容扩展为此是个好主意:recipe(IDnumber,'Green Soup',content([part('main dish', ingredients( [item(500,gramm,'pea'), item(200,ml,'cream')]), part('side dish', ingredients([item(200,gramm,'bread')]))]),steps(['Do something','Next step do again something'])) 或者我把它复杂化了吗??你必须知道,我是第一个在这里使用Prolog的人,所以我对这个平台和你的输入真的非常非常高兴!! - kiw
完成了。我已经用一个例子扩展了我的回答。 - Paulo Moura
哇!太棒了,非常感谢!你是对的,这是一个非常聪明的方法。我会仔细研究的。 :) - kiw
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接