View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  1985-2020, University of Amsterdam,
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   38Copyright notes: findall/3, bagof/3 and setof/3 are part of the standard
   39folklore of Prolog. The core  is  findall/3   based  on  C code that was
   40written for SWI-Prolog. Older versions also used C-based implementations
   41of  bagof/3  and  setof/3.  As   these    proved   wrong,   the  current
   42implementation is modelled  after  an  older   version  of  Yap.  Ulrich
   43Neumerkel fixed the variable preservation of   bagof/3 and setof/3 using
   44an algorithm also found in  Yap  6.3,   where  it  is claimed: "uses the
   45SICStus algorithm to guarantee that variables will have the same names".
   46- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
   47
   48:- module('$bags',
   49          [ findall/3,                  % +Templ, :Goal, -List
   50            findall/4,                  % +Templ, :Goal, -List, +Tail
   51            findnsols/4,                % +Count, +Templ, :Goal, -List
   52            findnsols/5,                % +Count, +Templ, :Goal, -List, +Tail
   53            bagof/3,                    % +Templ, :Goal, -List
   54            setof/3                     % +Templ, :Goal, -List
   55          ]).   56
   57:- meta_predicate
   58    findall(?, 0, -),
   59    findall(?, 0, -, ?),
   60    findnsols(+, ?, 0, -),
   61    findnsols(+, ?, 0, -, ?),
   62    bagof(?, ^, -),
   63    setof(?, ^, -).   64
   65:- noprofile((
   66        findall/4,
   67        findall/3,
   68        findnsols/4,
   69        findnsols/5,
   70        bagof/3,
   71        setof/3,
   72        findall_loop/4)).   73
   74:- '$iso'((findall/3,
   75           bagof/3,
   76           setof/3)).   77
   78%!  findall(-Var, +Goal, -Bag) is det.
   79%!  findall(-Var, +Goal, -Bag, +Tail) is det.
   80%
   81%   Bag holds all alternatives for Var  in  Goal.   Bag  might  hold
   82%   duplicates.   Equivalent  to bagof, using the existence operator
   83%   (^) on all free variables of Goal.  Succeeds with Bag  =  []  if
   84%   Goal fails immediately.
   85%
   86%   The  findall/4  variation  is  a    difference-list  version  of
   87%   findall/3.
   88
   89findall(Templ, Goal, List) :-
   90    findall(Templ, Goal, List, []).
   91
   92findall(Templ, Goal, List, Tail) :-
   93    setup_call_cleanup(
   94        '$new_findall_bag',
   95        findall_loop(Templ, Goal, List, Tail),
   96        '$destroy_findall_bag').
   97
   98findall_loop(Templ, Goal, List, Tail) :-
   99    (   Goal,
  100        '$add_findall_bag'(Templ)   % fails
  101    ;   '$collect_findall_bag'(List, Tail)
  102    ).
  103
  104%!  findnsols(+Count, @Template, :Goal, -List) is nondet.
  105%!  findnsols(+Count, @Template, :Goal, -List, ?Tail) is nondet.
  106%
  107%   True when List is the next chunk of maximal Count instantiations
  108%   of Template that reprensents a solution of Goal.  For example:
  109%
  110%     ==
  111%     ?- findnsols(5, I, between(1, 12, I), L).
  112%     L = [1, 2, 3, 4, 5] ;
  113%     L = [6, 7, 8, 9, 10] ;
  114%     L = [11, 12].
  115%     ==
  116%
  117%   @compat Ciao, but the SWI-Prolog version is non-deterministic.
  118%   @error  domain_error(not_less_than_zero, Count) if Count is less
  119%           than 0.
  120%   @error  type_error(integer, Count) if Count is not an integer.
  121
  122findnsols(Count, Template, Goal, List) :-
  123    findnsols(Count, Template, Goal, List, []).
  124
  125findnsols(Count, Template, Goal, List, Tail) :-
  126    integer(Count),
  127    !,
  128    findnsols2(count(Count), Template, Goal, List, Tail).
  129findnsols(Count, Template, Goal, List, Tail) :-
  130    Count = count(Integer),
  131    integer(Integer),
  132    !,
  133    findnsols2(Count, Template, Goal, List, Tail).
  134findnsols(Count, _, _, _, _) :-
  135    '$type_error'(integer, Count).
  136
  137findnsols2(Count, Template, Goal, List, Tail) :-
  138    nsols_count(Count, N), N > 0,
  139    !,
  140    copy_term(Template+Goal, Templ+G),
  141    setup_call_cleanup(
  142        '$new_findall_bag',
  143        findnsols_loop(Count, Templ, G, List, Tail),
  144        '$destroy_findall_bag').
  145findnsols2(Count, _, _, List, Tail) :-
  146    nsols_count(Count, 0),
  147    !,
  148    Tail = List.
  149findnsols2(Count, _, _, _, _) :-
  150    nsols_count(Count, N),
  151    '$domain_error'(not_less_than_zero, N).
  152
  153findnsols_loop(Count, Templ, Goal, List, Tail) :-
  154    nsols_count(Count, FirstStop),
  155    State = state(FirstStop),
  156    (   call_cleanup(Goal, Det=true),
  157        '$add_findall_bag'(Templ, Found),
  158        Det \== true,
  159        arg(1, State, Found),
  160        '$collect_findall_bag'(List, Tail),
  161        (   '$suspend_findall_bag'
  162        ;   nsols_count(Count, Incr),
  163            NextStop is Found+Incr,
  164            nb_setarg(1, State, NextStop),
  165            fail
  166        )
  167    ;   '$collect_findall_bag'(List, Tail)
  168    ).
  169
  170nsols_count(count(N), N).
  171
  172%!  bagof(+Var, +Goal, -Bag) is semidet.
  173%
  174%   Implements Clocksin and  Melish's  bagof/3   predicate.  Bag  is
  175%   unified with the alternatives of Var  in Goal, Free variables of
  176%   Goal are bound,  unless  asked  not   to  with  the  existential
  177%   quantifier operator (^).
  178
  179bagof(Templ, Goal0, List) :-
  180    '$free_variable_set'(Templ^Goal0, Goal, Vars),
  181    (   Vars == v
  182    ->  findall(Templ, Goal, List),
  183        List \== []
  184    ;   alloc_bind_key_list(Vars, VDict),
  185        findall(Vars-Templ, Goal, Answers),
  186        bind_bagof_keys(Answers, VDict),
  187        keysort(Answers, Sorted),
  188        pick(Sorted, Vars, List)
  189    ).
  190
  191%!  alloc_bind_key_list(+Vars, -VDict) is det.
  192%
  193%   Pre-allocate the variable dictionary used   by bind_bagof_keys/2. By
  194%   pre-allocating this list all variables  bound become references from
  195%   the `Vars` of  each  answer  to  this   dictionary.  If  we  do  not
  196%   preallocate we create a huge reference chain from VDict through each
  197%   of the answers, causing serious slowdown in the subsequent keysort.
  198%
  199%   The slowdown was discovered by Jan Burse.
  200
  201alloc_bind_key_list(Vars, VDict) :-
  202    functor(Vars, _, Count),
  203    length(List, Count),
  204    '$append'(List, _, VDict).
  205
  206%!  bind_bagof_keys(+VarsTemplPairs, -SharedVars)
  207%
  208%   Establish a canonical binding  of   the  _vars_ structures. This
  209%   code   was   added    by    Ulrich     Neumerkel    in    commit
  210%   1bf9e87900b3bbd61308e80a784224c856854745.
  211
  212bind_bagof_keys([], _).
  213bind_bagof_keys([W-_|WTs], Vars) :-
  214    term_variables(W, Vars, _),
  215    bind_bagof_keys(WTs, Vars).
  216
  217pick(Bags, Vars1, Bag1) :-
  218    pick_first(Bags, Vars0, Bag0, RestBags),
  219    select_bag(RestBags, Vars0, Bag0, Vars1, Bag1).
  220
  221select_bag([], Vars0, Bag0, Vars1, Bag1) :-   % last one: deterministic
  222    !,
  223    Vars0 = Vars1,
  224    Bag0 = Bag1.
  225select_bag(_, Vars, Bag, Vars, Bag).
  226select_bag(RestBags, _, _, Vars1, Bag1) :-
  227    pick(RestBags, Vars1, Bag1).
  228
  229%!  pick_first(+Bags, +Vars, -Bag1, -RestBags) is semidet.
  230%
  231%   Pick the first result-bag from the   list  of Templ-Answer. Note
  232%   that we pick all elements that are  equal under =@=, but because
  233%   the variables in the witness are canonized this is the same as ==.
  234%
  235%   @param Bags     List of Templ-Answer
  236%   @param Vars     Initial Templ (for rebinding variables)
  237%   @param Bag1     First bag of results
  238%   @param RestBags Remaining Templ-Answer
  239
  240pick_first([Vars-Templ|T0], Vars, [Templ|T], RestBag) :-
  241    pick_same(T0, Vars, T, RestBag).
  242
  243
  244pick_same([V-H|T0], Vars, [H|T], Bag) :-
  245    V == Vars,
  246    !,
  247    pick_same(T0, Vars, T, Bag).
  248pick_same(Bag, _, [], Bag).
  249
  250
  251%!  setof(+Var, +Goal, -Set) is semidet.
  252%
  253%   Equivalent to bagof/3, but sorts the   resulting bag and removes
  254%   duplicate answers. We sort  immediately   after  the  findall/3,
  255%   removing duplicate Templ-Answer pairs early.
  256
  257setof(Templ, Goal0, List) :-
  258    '$free_variable_set'(Templ^Goal0, Goal, Vars),
  259    (   Vars == v
  260    ->  findall(Templ, Goal, Answers),
  261        Answers \== [],
  262        sort(Answers, List)
  263    ;   alloc_bind_key_list(Vars, VDict),
  264        findall(Vars-Templ, Goal, Answers),
  265        (   ground(Answers)
  266        ->  sort(Answers, Sorted),
  267            pick(Sorted, Vars, List)
  268        ;   bind_bagof_keys(Answers, VDict),
  269            sort(Answers, Sorted),
  270            pick(Sorted, Vars, Listu),
  271            sort(Listu, List) % Listu ordering may be nixed by Vars
  272        )
  273    )