34
35:- module(top_k,
36 [ top_k/3, 37 group_by_sorted/4
38 ]). 39
40:- use_module(library(heaps)). 41:- use_module(library(assoc)). 42:- use_module(library(option)). 43:- use_module(library(solution_sequences)). 44:- use_module(library(local_dynamic)). 45
76
78
81
83
87
92
93:- meta_predicate top_k(+, 0, -). 94
95top_k(Options1, Goal, Result) :-
96 select_option(return(Return), Options1, Options, backtrack),
97 top_k(Return, Options, Goal, Result).
98
99top_k(Return, Options, Goal, Result) :-
100 ( Options = [_, _|_]
101 -> run_optimized(Return, Options, Goal, Result)
102 ; dispatch_singles(Return, Options, Goal, Result)
103 ).
104
105dispatch_singles(backtrack, Opts, Goal, Goal) :- dispatch_singles(Opts, Goal, _).
106dispatch_singles(list(Term), Opts, Goal, List) :-
107 option(group_by(Group), Opts, ungrouped),
108 ( group_by(GK, Term, dispatch_singles(Opts, Goal, GK), List)
109 *-> Group = GK 110 ; ground(Group),
111 List = [] 112 ).
113
114dispatch_singles([], Goal, ungrouped) :- call(Goal).
115dispatch_singles([Opt], Goal, GK) :- dispatch_single(Opt, Goal, GK).
116
117ordered_term_variables(Term, Vars) :-
118 term_variables(Term, UVars),
119 sort(UVars, Vars).
120
121dispatch_single(order_by(Spec), Goal, ungrouped) :- order_by([Spec], Goal).
122dispatch_single(limit(K), Goal, ungrouped) :- limit(K, Goal).
123dispatch_single(distinct(W), Goal, ungrouped) :- distinct(W, Goal).
124dispatch_single(group_by(Group), Goal, Group) :- dispatch_group_by(Goal, Group).
125
126dispatch_group_by(Goal, Group) :-
127 ordered_term_variables(Goal, GVars),
128 ordered_term_variables(Group, KVars),
129 ord_subtract(GVars, KVars, TVars),
130 Term =.. [v|TVars],
131 bagof(Term, Goal, List),
132 member(Term, List).
133
134run_optimized(Return, Opts, Goal, Result) :-
135 option(limit(Count), Opts, inf),
136 option(order_by(OrderSpec), Opts, asc(unordered)),
137 option(group_by(Group), Opts, ungrouped),
138 ( option(distinct(Witness), Opts)
139 -> Distinct = true
140 ; Distinct = false
141 ),
142 priority_for(OrderSpec, Pri, Key),
143 run_optimized(Goal, Count, Pri, Key, Distinct, Witness, Group, Return, Result).
144
146
158
159priority_for(asc(Key), @=<, Key).
160priority_for(desc(Key), @>=, Key).
161
164better_than(@>=, P1, P2) :- P1 @> P2.
165better_than(@=<, P1, P2) :- P1 @< P2.
166
167setup_state(false, none).
168setup_state(true, state(DictHolder)) :-
169 empty_assoc(D0),
170 DictHolder = holder(D0).
171
172seen_hash(state(holder(D)), Hash, Key) :-
173 get_assoc(Hash, D, Key).
174
175mark_hash(state(DictHolder), Hash, Key) :-
176 DictHolder = holder(D0),
177 put_assoc(Hash, D0, Key, D1),
178 nb_setarg(1, DictHolder, D1),
179 true.
180
181update_topk(Count, Pri, Key, Entry, HHolder) :-
182 HHolder = holder(N0, H0),
183 ( N0 < Count
184 -> add_to_heap(H0, Key, Entry, H1),
185 N1 is N0 + 1,
186 nb_setarg(1, HHolder, N1),
187 nb_setarg(2, HHolder, H1)
188 ; 189 replace_topk(_WorstKey, Pri, Key, Entry, HHolder)
190 ).
191
192revdel_from_heap(Q0,Px,X,Q) :-
193 get_from_heap(Q0,Py,Y,Q1),
194 revdel_from_heap(Q1,Px,X,Q2),
195 add_to_heap(Q2,Py,Y,Q),
196 !.
197revdel_from_heap(Q0,P,X,Q) :-
198 get_from_heap(Q0,P,X,Q).
199
200pri_del_from_heap(Pri, H0, Key, Entry, HRest) :-
201 ( var(Key),
202 Pri == (@=<)
203 -> revdel_from_heap(H0, Key, Entry, HRest)
204 ; delete_from_heap(H0, Key, Entry, HRest)
205 ).
206
207replace_topk(Key1, Pri, Key, Entry, HHolder) :-
208 HHolder = holder(_, H0),
209 pri_del_from_heap(Pri, H0, Key1, _, HRest),
210 ( better_than(Pri, Key, Key1)
211 -> add_to_heap(HRest, Key, Entry, H1),
212 nb_setarg(2, HHolder, H1)
213 ; fail
214 ).
215
216heap_to_list(holder(_N, H), Pri, SortedKeyVars) :-
217 heap_to_list(H, KV0),
218 ( Pri == (@=<)
219 -> 220 221 reverse(KV0, KV1)
222 ; KV1 = KV0
223 ),
224 sort(1, Pri, KV1, SortedKeyVars).
225
227
228run_optimized(Goal, Count, Pri, Key, Distinct, Witness, Group, Return, Result) :-
229 term_variables(Goal, Vars),
230 setup_state(Distinct, State),
231 empty_assoc(G0),
232 GHolder = holder(G0), 233 term_variables(Witness, WVars),
234 WTerm =.. [w|WVars],
235 ( ground(Group)
236 ->create_bucket(GHolder, Group, _)
237 ; true
238 ),
239 forall(Goal,
240 ignore(consider_solution(Count, Pri, Key, Distinct, WTerm, Group, Vars, State, GHolder))),
241 finalize(Group, Return, Pri, GHolder, Vars, Goal, Result).
242
243consider_solution(Count, Pri, Key, Distinct, WTerm, Group, Vars, State, GHolder) :-
244 Entry = Vars,
245 ( Distinct == true
246 ->variant_sha1(WTerm, Hash),
247 ( seen_hash(State, Hash, Key1)
248 ->get_or_create_bucket(GHolder, Group, Bucket),
249 replace_topk(Key1, Pri, Key, Entry, Bucket),
250 GHolder = holder(G0),
251 put_assoc(Group, G0, Bucket, G1),
252 nb_setarg(1, GHolder, G1),
253 mark_hash(State, Hash, Key),
254 fail
255 ; true
256 )
257 ; true
258 ),
259 get_or_create_bucket(GHolder, Group, Bucket),
260 update_topk(Count, Pri, Key, Entry, Bucket),
261 GHolder = holder(G0),
262 put_assoc(Group, G0, Bucket, G1),
263 nb_setarg(1, GHolder, G1),
264 ( Distinct == true
265 ->mark_hash(State, Hash, Key)
266 ; true
267 ).
268
269get_or_create_bucket(GHolder, Group, Bucket) :-
270 GHolder = holder(G0),
271 ( get_assoc(Group, G0, Bucket)
272 -> true
273 ; create_bucket(GHolder, Group, Bucket)
274 ).
275
276create_bucket(GHolder, Group, Bucket) :-
277 GHolder = holder(G0),
278 empty_heap(H0),
279 Bucket = holder(0, H0),
280 put_assoc(Group, G0, Bucket, G1),
281 nb_setarg(1, GHolder, G1).
282
283finalize(Group, Return, Pri, holder(G), Vars, Goal, Result) :-
284 gen_assoc(Group, G, Bucket),
285 (Group == ungrouped -> ! ; true), 286 heap_to_list(Bucket, Pri, List),
287 emit_result(Return, Vars, List, Goal, Result).
288
289emit_result(list(Term), Vars, List, _, Result) :- findall(Term, member(_Key-Vars, List), Result).
290emit_result(backtrack, Vars, List, Goal, Goal) :- member(_Key-Vars, List).
291
292:- meta_predicate group_by_sorted(+, +, 0, -).
299group_by_sorted(Key, Value, Goal, Bag) :-
300 with_local_dynamic([key_value/2], H, group_by_sorted(H, Key, Value, Goal, Bag)).
301
302group_by_sorted(H, Key, Value, Goal, Bag) :-
303 ( copy_term(Goal-Key-Value, Goal1-Key1-Value1),
304 Goal1,
305 ld_assertz(H, key_value(Key1, Value1)),
306 once(ld_call(H, key_value(Key, _))),
307 Key \= Key1
308 ; true
309 ),
310 group_by(Key, Value, ld_retract(H, key_value(Key, Value)), Bag)