Использование setof/3 с рекурсивным вызовом в GOAL неправильно, почему?

У меня проблемы с использованием setof/3, отсутствуют некоторые результаты.

Контекст:

Я загружаю xml-файл, используя SWI-Prolog load_xml(), чтобы получить элемент рекурсивного списка (см. testelement в примере). Затем я хочу найти определенные элементы в этом списке (в дереве xml).
Использование findall/3 в сочетании с sort/2 работает нормально. Но если я использую setof/3, я пропускаю один результат. Я предполагаю, что у setof/3 есть проблемы из-за рекурсивного вызова в askElement/3 для получения/сохранения элементов? Кто-нибудь знает другое решение для получения элементов из рекурсивного списка?

Мой тестовый код:

testElement([element('recipeml',[version=0.5], 
    [element('recipe',[],
        [element('head',[],
            [element('title',[],['Spaghetti Bolognese']
            )]
        ),
        element('ing-div',[type=titled], 
            [element('title',[],['sauce']),
             element('ingredients',[],
                [element('ing',[],
                    [element('item',[],['hackfleisch']),
                     element('item',[],['fleischtomaten']),
                     element('item',[],['zwiebeln']),
                     element('item',[],['sellerie']
                    )]
                )]
            )]
        )]
    ),
    element('recipe',[],
        [element('head',[],
            [element('title',[],['Erbsensuppe']
            )]
        ),
        element('ing-div',[type=titled], 
            [element('title',[],['elementar']),
             element('ingredients',[],
                [element('ing',[],
                    [element('item',[],['sahne']),
                     element('item',[],['erbsen']),
                     element('item',[],['gemüsebrühe']
                    )]
                )]
            )]
        )]
    )] 
)]).

askElement(Name, Child, Parent) :-
    (
        member( element(Name,_,Child),Parent)
    ;
        member( element(_,_,NewParent),Parent),
        [_|_] = NewParent,
        askElement(Name, Child, NewParent)
    ).

allRecipes_findall(RecipeName) :-
    testElement(Knot),
    findall(TmpR,(askElement('head',HKnot,Knot),askElement('title',TmpR,HKnot)),Bag),
    sort(Bag, RecipeName).

allRecipes_setof(RecipeName) :-
    testElement(Knot),
    setof(TmpR,(askElement('head',HKnot,Knot),askElement('title',TmpR,HKnot)),RecipeName).

Мой вывод:

3 ?- allRecipes_findall(X).
X = [['Erbsensuppe'], ['Spaghetti Bolognese']].

4 ?- allRecipes_setof(X).
X = [['Erbsensuppe']] 

Я ожидал, что в обоих случаях я получу

X = [['Erbsensuppe'], ['Spaghetti Bolognese']].

Что не так?

Спасибо заранее!

PS: каждый комментарий/обзор моего (первой попытки) кода Prolog очень приветствуется :}


person kiw    schedule 08.08.2014    source источник


Ответы (2)


Стандартный предикат setof/3 дает вам решение для каждого отдельного экземпляра свободных переменных в цели. Использование вашего кода как есть дает:

?- allRecipes_findall(X).
X = [['Erbsensuppe'], ['Spaghetti Bolognese']].

?- allRecipes_setof(X).
X = [['Erbsensuppe']] ;
X = [['Spaghetti Bolognese']].

Это ожидаемый результат. Однако вы можете заставить setof/3 игнорировать свободные переменные, экзистенциально определяя их количество с помощью оператора ^/2:

allRecipes_setof(RecipeName) :-
    testElement(Knot),
    setof(TmpR,HKnot^(askElement('head',HKnot,Knot),askElement('title',TmpR,HKnot)),RecipeName).

С этим изменением вы получите тот же результат, что и с предикатом findall/3:

?- allRecipes_setof(X).
X = [['Erbsensuppe'], ['Spaghetti Bolognese']].

Что касается комментариев к вашему стилю программирования, используйте символы подчеркивания вместо CamelCase в атомах для удобочитаемости кода. Например. ask_element вместо askElement. С другой стороны, для переменных часто используется CamelCase.

person Paulo Moura    schedule 08.08.2014
comment
Отлично :-) В моей большой программе я сделал разрез (!), поэтому я не знал, что у Пролога будет более одного решения. И в моем простом примере я просто забыл ввести ;. Отлично, спасибо, это помогает. И оператор ^/2, если честно, для меня новый... как и весь пролог... То, как я запускаю testElement, более или менее нормально/эффективно? - person kiw; 08.08.2014
comment
Я предлагаю вам использовать другое представление в третьем аргументе термина element/3, чтобы легко различать дочерние и не дочерние элементы вместо использования списка в обоих случаях. Это позволит вам улучшить определение предиката askElement/3. Обратите внимание, что [_|_] также унифицируется, например, с ['Spaghetti Bolognese'] так что вы не делаете эти два случая исключительными. - person Paulo Moura; 08.08.2014
comment
Еще раз спасибо :-) Что касается вашего первого предложения, я не уверен, что вы имеете в виду. Должен ли я дать элементу «заголовок» в пути «recipe-head-title» уникальное имя, чтобы отличить его от «recipe-ingdiv-title», чтобы мне не нужно было дважды вызывать askElement? Я не могу этого сделать, так как хочу использовать заданный xml-формат. Или вы имеете в виду что-то другое? Спасибо за замечание по поводу '[|]', я изменю его на 'is_list'. - person kiw; 08.08.2014
comment
Я имею в виду использование такого представления, как element(..., ..., child(Child)) для листовых узлов и element(..., ..., descendants([Descendant1, ...])) или просто element(..., ..., [Descendant1, ...]) для узлов с потомками. т.е. заставить третий аргумент терма element/3 использовать другие функторы. - person Paulo Moura; 08.08.2014
comment
о, упс. Теперь я понимаю. Хорошая идея :-) Тогда мне нужно искать только потомка при поиске элемента. Я подумаю об этом. Поскольку я получил testElement из load_xml в SWI-Prolog, я, вероятно, один раз преобразую его в предложенное вами представление перед дальнейшим использованием. Спасибо еще раз :) - person kiw; 08.08.2014

Пауло уже дал много советов по поводу вашего текущего кода. Я здесь только для того, чтобы предложить воспользоваться библиотекой (xpath), когда вам нужно обрабатывать XML. Это требует немного упражнений, но тогда вы будете вознаграждены большим количеством функций... для вашего примера:

?- [library(xpath)].
true.

?- testElement(E), xpath(E, //head//title(text), T).
...
T = 'Spaghetti Bolognese' ;
...
T = 'Erbsensuppe' ;
false.
person CapelliC    schedule 08.08.2014
comment
Ух ты! (текст-заполнитель из-за безмолвия) - person mat; 08.08.2014
comment
Спасибо еще раз. Работает в обеих версиях: :- use_module(library(xpath)). или :- [library(xpath)]. - person kiw; 11.08.2014