【问题标题】:Knowledge representation in Prolog - how to store data?Prolog 中的知识表示 - 如何存储数据?
【发布时间】:2014-10-02 22:09:05
【问题描述】:

当我对我在 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 中的第一个应用程序,所以我对足够的数据存储不是很熟悉。我会很感激每一个提示。

【问题讨论】:

  • 在我的建议中,成分和步骤将有一个参数,分别是成分和步骤的列表。 IE。使用您的示例recipe(IDnumber,'Green Soup',ingredients([item(500,gramm,'pea'),item(200,ml,'cream')]),steps(['Do something','Next step do again something'])).。将成分和步骤作为具有取决于配方的数量的复合术语只会使处理复杂化而没有任何好处。这也将更接近地反映您呈现的 XML 结构(假设这是您的意图)。

标签: database-design prolog


【解决方案1】:

SWI-Prolog 提供库 (xpath),允许引用节点和属性“Prolog 样式”。 在回溯时,实例化被返回给调用者。所以你可以在 findall 等中使用你认为最合适的。

?- database(Db), xpath(Db, //recipe, Recipe).

将枚举所有配方。该库功能强大,但不容易学习。看看你在那里看到的(微不足道的)例子......

您也可以查看here,我回答建议library(xpath) 处理GCC XML。我用它来构建我的 SWI / OpenGL 接口...

【讨论】:

  • 因为您已经在使用与 SWI 相同的 XML 表示形式,也许值得努力将您的访问谓词包装在一个兼容的接口中。如果你保持基本,应该相当简单......真的,它归结为有一个“惰性评估”,正如 member/2 已经执行的那样,以及自定义模式匹配。不太难...此外,它显示了一个重要的架构点,即如何使用 xpath_chk(映射到 memberchk,我认为)有效地处理大树
  • ...相似之处在于我在 SWI Prolog 中开始了我的第一步。由于许可证的具体情况,这里决定使用 GNU Prolog……而在 Windows 上运行 GNU Prolog(或 gplc)也存在问题,这可能不是最后的决定……
  • 您对许可的评论很有趣。我曾经认为 SWI-Prolog 比 GnuProlog 更宽容,也许我错了。
  • 据我了解我们在这里的许可证讨论,原则上 LGPL 都是有效的,但 (1) 对于 GNU Prolog,清楚地描述了您自己使用 gplc 编译的开发可以被视为单独的从法律的角度发展。在 SWI Prolog 网站上(正如我们的 IT 专家所说)没有写得那么清楚,只是“SWI 希望使用与 gcc 相同的许可原则”(2)SWI Prolog 包含具有其他许可条件的软件包。
  • 感谢您对“重新打开”编译器的讨论 :) 我刚刚给 JanW 写了一封电子邮件,看来您是对的,希望我能尽快切换回 SWI :)
【解决方案2】:

如果您想从 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.

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

【讨论】:

  • 再次感谢您的输入 :) 老实说,在 Prolog 的上下文中,模块或对象表示对我来说是新的。也许它对我的问题来说太大了。我不想以最快的速度访问数据库,而只是“不要傻傻地工作”。我的主题将是一个对话组件,接近“语言理解过程”,这样用户就可以输入“我还有一个鸡蛋和一杯奶油,我可以做什么?”。因此,在数据库中的搜索仅限于作为成分、食谱名称或食谱的其他描述符的一部分的关键字。 [...]
  • [...]我不需要保留原始 XML 结构,我需要在启动时加载数据时在 Prolog 中表示。所以我想你的第一个建议是使用基于列表的recipe(IDnumber,'Green Soup',ingredients([item(500,gramm,'pea'),item(200,ml,'cream')]),steps(['Do something','Next step do again something'])) 作为参数就足够了。 [...]
  • [...]希望我的最后一个问题:如果我想扩展数据模型以将成分列表划分为不同部分,那么扩展列表内容是否是一个好主意: 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 的人,所以我对这个平台和你的意见感到非常非常高兴!
  • 模块/对象表示对于您的问题来说并不过分。这将有助于查询,例如这套食材能做什么菜。它还可以更灵活地更改配方描述,例如添加新的描述符。关键点是它将为您提供一个接口,您可以使用该接口将配方表示的详细信息抽象为将访问配方数据的客户端代码。当然,您也可以通过定义一组访问谓词来近似它,并确保始终通过这些谓词而不是直接访问食谱数据。
  • 如果你有时间,请给我一个小例子或一个链接到一个模块和这样一个界面的样子。这对我很有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-07
  • 2012-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多