prolog - Recopile todas las soluciones "mínimas" de un predicado
backtracking aggregates (3)
Dados los siguientes hechos en una base de datos:
foo(a, 3).
foo(b, 2).
foo(c, 4).
foo(d, 3).
foo(e, 2).
foo(f, 6).
foo(g, 3).
foo(h, 2).
Quiero recopilar todos los primeros argumentos que tienen el segundo argumento más pequeño, más el valor del segundo argumento. Primer intento:
find_min_1(Min, As) :-
setof(B-A, foo(A, B), [Min-_|_]),
findall(A, foo(A, Min), As).
?- find_min_1(Min, As).
Min = 2,
As = [b, e, h].
En lugar de
setof/3
, podría usar
aggregate/3
:
find_min_2(Min, As) :-
aggregate(min(B), A^foo(A, B), Min),
findall(A, foo(A, Min), As).
?- find_min_2(Min, As).
Min = 2,
As = [b, e, h].
nótese bien
Esto solo da los mismos resultados si busco el mínimo de un
número
.
Si una expresión aritmética está involucrada, los resultados pueden ser diferentes.
Si se trata de un no número, el
aggregate(min(...), ...)
arrojará un error.
O, en cambio, puedo usar la lista completa ordenada por clave:
find_min_3(Min, As) :-
setof(B-A, foo(A, B), [Min-First|Rest]),
min_prefix([Min-First|Rest], Min, As).
min_prefix([Min-First|Rest], Min, [First|As]) :-
!,
min_prefix(Rest, Min, As).
min_prefix(_, _, []).
?- find_min_3(Min, As).
Min = 2,
As = [b, e, h].
Finalmente, a la (s) pregunta (s):
-
¿Puedo hacer esto directamente con la biblioteca (agregado)? Parece que debería ser posible ...
-
¿O hay un predicado como
std::partition_point
de la biblioteca estándar de C ++? -
¿O hay alguna manera más fácil de hacer esto?
EDITAR:
Para ser más descriptivo.
Digamos que hubo un predicado (biblioteca)
partition_point/4
:
partition_point(Pred_1, List, Before, After) :-
partition_point_1(List, Pred_1, Before, After).
partition_point_1([], _, [], []).
partition_point_1([H|T], Pred_1, Before, After) :-
( call(Pred_1, H)
-> Before = [H|B],
partition_point_1(T, Pred_1, B, After)
; Before = [],
After = [H|T]
).
(No me gusta el nombre pero podemos vivir con él por ahora)
Entonces:
find_min_4(Min, As) :-
setof(B-A, foo(A, B), [Min-X|Rest]),
partition_point(is_min(Min), [Min-X|Rest], Min_pairs, _),
pairs_values(Min_pairs, As).
is_min(Min, Min-_).
?- find_min_4(Min, As).
Min = 2,
As = [b, e, h].
¿Cuál es el enfoque idiomático para esta clase de problemas?
¿Hay alguna manera de simplificar el problema?
Muchas de las siguientes observaciones podrían agregarse a muchos programas aquí en SO.
Nombres imperativos
Cada vez que escribe un nombre imperativo para algo que es una relación, reducirá su comprensión de las relaciones.
No mucho, solo un poquito.
Muchos modismos comunes de Prolog como
append/3
no dan un buen ejemplo.
Piense en
append(As,As,AsAs)
.
El primer argumento de
find_min(Min, As)
es el mínimo.
Entonces
minimum_with_nodes/2
podría ser un mejor nombre.
findall/3
No use
findall/3
menos que los usos se verifiquen rigurosamente, esencialmente todo debe ser molido.
En tu caso sucede que funciona.
Pero una vez que generalices un poco
foo/2
, perderás.
Y eso es frecuentemente un problema: escribes un pequeño programa;
Y parece funcionar.
Una vez que te mueves a las más grandes, el mismo enfoque ya no funciona.
findall/3
es (en comparación con
setof/3
) como un toro en una tienda de porcelana rompiendo el fino tejido de variables compartidas y cuantificación.
Otro problema es que la falla accidental no conduce a la falla de
findall/3
que a menudo lleva a casos extraños y difíciles de imaginar.
Programa inestable, demasiado específico
Otro problema también está relacionado con
findall/3
.
Su programa es tan específico que es bastante improbable que lo pruebe alguna vez.
Y los cambios marginales invalidarán sus pruebas.
Así que pronto te rendirás para realizar las pruebas.
Veamos qué es específico: principalmente la relación
foo/2
.
Sí, solo un ejemplo.
Piense en cómo configurar una configuración de prueba donde
foo/2
puede cambiar.
Después de cada cambio (escribir un nuevo archivo) deberá volver a cargar el programa.
Esto es tan complejo, es probable que nunca lo hagas.
Supongo que no tienes un arnés de prueba para eso.
Plunit para uno, no cubre tales pruebas.
Como regla general: si no puede probar un predicado en el nivel superior, nunca lo hará.
Considere en su lugar
minimum_with(Rel_2, Min, Els)
Con tal relación, ahora puede tener un
xfoo/3
generalizado con un parámetro adicional, por ejemplo:
xfoo(o, A,B) :-
foo(A,B).
xfoo(n, A,B) :-
newfoo(A,B).
y, naturalmente, obtienes dos respuestas para
minimum_with(xfoo(X), Min, Els)
.
Si hubiera utilizado
findall/3
lugar de
setof/3
, ya tendría problemas serios.
O simplemente en general:
minmum_with(/A^B^member(AB, [x-10,y-20]), Min, Els)
.
Para que pueda jugar en el nivel superior y producir muchos casos de prueba interesantes.
Casos fronterizos no verificados
Su versión 3 es claramente mi enfoque preferido, sin embargo, todavía hay algunas partes que pueden mejorarse. En particular, si hay respuestas que contienen variables como mínimo. Estos deben ser revisados.
Y ciertamente, también
setof/3
tiene sus límites.
E idealmente los probarías.
Las respuestas no deben contener restricciones, en particular no en las variables relevantes.
Esto muestra cómo
setof/3
tiene ciertos límites.
Después de la fase pionera, SICStus produjo muchos errores para las restricciones en tales casos (mediados de la década de 1990), que luego cambió para ignorar las restricciones en los elementos integrados que no pueden manejarlos.
SWI, por otro lado, hace cosas completamente indefinidas aquí.
A veces las cosas se copian, a veces no.
Como ejemplo, tome:
setof(A, ( A in 1..3 ; A in 3..5 ), _)
y
setof(t, ( A in 1..3 ; A in 3.. 5 ), _)
.
Al envolver el objetivo, esto se puede evitar.
call_unconstrained(Goal_0) :-
call_residue_vars(Goal_0, Vs),
( Vs = [] -> true ; throw(error(representation_error(constraint),_)) ).
Sin embargo, tenga en cuenta que SWI tiene restricciones espurias:
?- call_residue_vars(all_different([]), Xs).
Xs = [_G1130].
No está claro si esta es una característica mientras tanto.
Ha estado allí desde la introducción de
call_residue_vars/2
aproximadamente 5 años.
No creo que la biblioteca (agregado) cubra su caso de uso. agregado (min) permite un testigo:
min (Expr, Witness) Un término min (Min, Witness), donde Min es la versión mínima de Expr sobre todas las soluciones, y Witness es cualquier otra plantilla aplicada a las soluciones que produjeron Min. Si varias soluciones proporcionan el mismo mínimo, Witness corresponde a la primera solución.
Hace algún tiempo, escribí una pequeña ''biblioteca'', lag.pl , con predicados para agregar con poca sobrecarga, de ahí el nombre (LAG = Agregado lineal). He agregado un fragmento, que maneja su caso de uso:
integrate(min_list_associated, Goal, Min-Ws) :-
State = term(_, [], _),
forall(call(Goal, V, W), % W stands for witness
( arg(1, State, C), % C is current min
arg(2, State, CW), % CW are current min witnesses
( ( var(C) ; V @< C )
-> U = V, Ws = [W]
; U = C,
( C == V
-> Ws = [W|CW]
; Ws = CW
)
),
nb_setarg(1, State, U),
nb_setarg(2, State, Ws)
)),
arg(1, State, Min), arg(2, State, Ws).
Es una extensión simple de integración (min) ... El método de comparación es seguramente cuestionable (utiliza un operador menos general para la igualdad), podría valer la pena adoptar una llamada convencional como la adoptada para predsort / 3. En cuanto a la eficiencia, aún mejor sería codificar el método de comparación como una opción en el ''selector de funciones'' (min_list_associated en este caso)
edite
gracias @false y @Boris por corregir el error relativo a la representación del estado.
Llamar a
nb_setarg(2, State, Ws)
realidad cambia el término ''forma, cuando se usó
State = (_,[],_)
.
Actualizará el repositorio de github en consecuencia ...
Usando la
library(pairs)
y [
sort/4
], esto se puede escribir simplemente como:
?- bagof(B-A, foo(A, B), Ps),
sort(1, @=<, Ps, Ss), % or keysort(Ps, Ss)
group_pairs_by_key(Ss, [Min-As|_]).
Min = 2,
As = [b, e, h].
Esta llamada a
sort/4
se puede reemplazar con
keysort/2
, pero con
sort/4
también se pueden encontrar, por ejemplo, los primeros argumentos asociados con el segundo argumento más grande: simplemente use
@>=
como segundo argumento.
Esta solución probablemente no sea tan eficiente en tiempo y espacio como las otras, pero puede ser más fácil de asimilar.
Pero hay otra forma de hacerlo por completo:
?- bagof(A, ( foo(A, Min), /+ ( foo(_, Y), Y @< Min ) ), As).
Min = 2,
As = [b, e, h].