Prolog, Sorting facts by parameter - sorting

I've been trying to find how I could do this, I'm trying to sort facts like:
package(X,Y,N), where N is a number. I want to create a list with the X values, ordered by the value of N (lowest to highest).
Tried using :
gera_caminho_tempo(,L):- findall(package(N,,S), package(,,S), Packages), msort(Packages, L).
But no results, any ideas?

For such tasks, check out the standard predicate keysort/2.
In your case, you can create pairs of the form N-package(X,Y,N), using for example:
?- findall(N-package(X,Y,N), package(X,Y,N), Pairs0).
Once you have obtained Pairs0, you can use keysort/2 to obtain the pairs sorted by key, where the first component of each pair acts as its key:
?- findall(N-package(X,Y,N), package(X,Y,N), Pairs0),
keysort(Pairs0, Pairs).
I leave relating such sorted pairs to only the sorted packages as an easy exercise.

Related

Using a list of pairs as an argument in Prolog

I am trying to find the value per key inputted in Prolog from an associative list. This function works but only for one pair. How can I get it to work for multiple pairs in a list?
assoc([pair(K,V)],Key,V) :-
K = Key.
assoc([pair(K,[])],Key,V):-
assoc([pair(K,[])],Key,V).
Your simple case, of just one pair in the list, could be written as follows:
assoc([pair(K,V)], K, V).
Your association is a list of such pairs. In Prolog, the simplest way to represent that is with a head-tail form: [H|T] where H is the first element and T is the tail or the rest of the list. In the case of a match, you shouldn't care whether there are any more elements in the list or not. So you should really write the above as:
assoc([pair(K,V)|_], K, V).
In other words, if the key of the first element matches, the value should match (and vice versa). I don't care what the rest of the association list looks like (thus, the _). If they don't, then this clause will fail and Prolog will test the next clause, which should check the rest of the list:
assoc([_|AssocRest], Key, Value) :-
assoc(AssocRest, Key, Value).
In this case, I don't care what the first pair is (I've taken care of that case already). So the head is _.
A more canonical way to represent a key value pair is with the term Key-Value. So the above would become:
assoc([K-V|_], K, V).
assoc([_|AssocRest], Key, Value) :-
assoc(AssocRest, Key, Value).

Write Prolog ordered predicate

I am trying to write a predicate that succeeds if and only if the numbers in the list are in non-decreasing order. I am having a hard time trying to figure this out. I know that if each element is less than or equal to the previous one then it should return false but I am lost on how to do it.
ordered(L) :-
Recursion should usually be your first thought for approaching any problem in Prolog. This means:
Defining a base case, where you can easily determine that the predicate is true or false
In other cases, splitting the problem into parts - one part you can resolve immediately, another you can resolve recursively. These parts of the problem generally correspond to portions of the list.
In the simplest cases, the recursive logic is simply to apply some test to the first element of the list; if it passes, recursively apply the predicate to the remainder of the list.
In your case I think it is a bit more complex, as there is no meaningful way you can test an individual element for orderedness (and maybe that gives you a hint what the base case is ...).
ordered(L) :- ordered2(L).
% empty list is ordered
ordered2([]) :- true.
% list with one element is ordered
ordered2([_]) :- true.
% list is ordered if two first elements are ordered
% and the rest of list is ordered
ordered2([A,B|T]) :- A=<B, ordered2([B|T]).

How to make a random list of a specific length without duplicates in prolog?

I'm trying to write a predicate randomnames/1 that generates a random list of three different names. The names are in a database and I already have a predicate for one random name:
name(1, Mary).
name(2, Pat).
name(3, James).
name(4, Bob).
name(5, Susan).
random_name(Name):-
random(0, 6, N),
name(N, Name).
To get this in a list, someone suggested I should do:
random_names([A,B,C]) :-
random_name(A),
random_name(B),
random_name(C).
The only problem with this is that it's possible to get duplicates in the list generated. I can't figure out how to fix that issue. I could write a new predicate for removing duplicates, but how would I substitute the duplicate with another variable then, so that the list still has three elements? And how would I even write the remove predicate when there isn't a clear head and tail in the random_names predicate?
When programming in Prolog, think in terms of conditions that your solutions must satisfy.
Currently, you have already figured out how to describe a list with three elements, where each element is a random name.
That's a good start, but not yet sufficient: In addition, you now want to describe the condition that the elements are pairwise distinct.
So, think about how to describe a list of three elements where all elements are pairwise distinct.
I give you a start, using dif/2 to express disequality of terms in a sound way (see prolog-dif):
three_distinct_elements([A,B,C]) :-
dif(A, B),
dif(A, C),
dif(B, C).
You may find a more general and more elegant way to describe this for lists with more than 3 elements. However, the above suffices to solve the task at hand.
So, it only remains to combine the predicates you already have, using for example:
three_distinct_random_names(Ls) :-
random_names(Ls),
three_distinct_elements(Ls).
This is simply the conjunction of conditions which you have already implemented. In total, solutions of this predicate will give you what you want: A list with three distinct random names.
However, the predicate may also fail (Exercise: Why?).
To try the predicate until it finds a solution, use for example repeat/0:
?- repeat, three_distinct_random_names(Ls).
There are also better ways to solve this. However, as a first approximation, I recommend to focus on good building-blocks, describing the conditions you want to satisfy.
I have a general comment on what you write:
I could write a new predicate for removing duplicates, but how would I substitute the duplicate with another variable then, so that the list still has three elements?
This is all worded very imperatively: You think here about "removing", "substituting" etc. To get the most out of Prolog, focus on describing the conditions that must hold for the solutions you want to find!
You want to find a list without duplicates? Describe what such a a list must look like. You want random names? Describe what such a name looks like, etc.
In case if you are using Swi-Prolog you can use very handy randseq/3 predicate which comes bundled with Swi. randseq/3 generates list of all distinct random numbers in range from 1 to N. After getting this list generated all that remains is mapping numbers to names:
name(1, 'Mary').
name(2, 'Pat').
name(3, 'James').
name(4, 'Bob').
name(5, 'Susan').
random_names(Names, Count) :-
% 5 is the amount of names available in database
randseq(Count, 5, L),
maplist(name, L, Names).
Usage examples:
?- random_names(Names, 3).
Names = ['Mary', 'James', 'Susan'].
?- random_names(Names, 5).
Names = ['Susan', 'Bob', 'James', 'Mary', 'Pat'].

how to compare a list's elements 3 at a time in prolog

Given a list (A) I want to be able to create a new list (B) that contains only the elements of A that are the smallest or the biggest compared to their next and previous element. My problem is that I don't know how to do the comparisons of each element with its previous one.
(This question may be silly but I'm new to prolog and any help would be appreciated.)
You could start with something like that:
compareElem([]).
compareElem([H,H1,H2|B]):-compareElem(B),
compare(?Order, H1,H2),
compare(?Order, H1, H).
where ?Order is the order of comparison (like '<' or '>'). See compare/3.
Some queries:
?- compareElem([1,2,3,4,5,6]).
true.
?- compareElem([1,2,3,4,5,3]).
false.
of course to apply this example you must ensure that the list has 3n elements, this is just a basic example. Together with this comparison you can generate the other list

How to change an order of list of lists

I have a list of substitutions between variables and values
they are presented in a list [[x,y],[1,2]] (meaning that the value of x equals 1, and the value of y equals 2).
I want to change the list to a list of pairs meaning [[x,1],[y,2]],
I tried to use append so that I will create a pair in each step of the recursion and append it to a new list but i have problem of doing so (mainly selecting the head and tails of each pair)
your example it's a bit contrived, but transpose it's a bit overkill, specially if you're going to implement yourself. I think this should solve your need
'change an order of list of lists'([X_Y_Z, A_B_C], XA_YB_ZC) :-
findall([K, V], (nth1(I, X_Y_Z, K), nth1(I, A_B_C, V)), XA_YB_ZC).

Resources