Convert List to List of Tuples In Mercury - mercury

I am just a total beginner in mercury and finding it hard to solve this problem. I want to convert a list to a list of tupples sorted from smaller to higher frequenties. Eg:
string.to_char_list("this is a test") becomes
[{'a', 1}, {'e', 1}, {'h', 1}, {'i', 2}, {' ', 3}, {'s', 3}, {'t', 3}]
OR
[3,2,1,2,1,1,2] becomes
[{3, 1}, {1, 3}, {2, 3}]
You can see that all the list of tuples are sorted from smaller to higher frequenties.
I am asking if someone can help me to slove it or a pointer to a tutorial where i can find more tips to do it.
Thanks for your reply.

The standard library has for example the bag datatype that nicely has all the tools ready. You basically just convert your list to a bag and then convert the bag back to a list with the frequencies. Then use the sort for lists to have it sorted like you want. Or you can do the same by hand and fold over the list with a map as the accumulator where you store the encountered elements with their occurrence count.
An example with the bag:
:- module freq.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
:- import_module string.
:- import_module list.
:- import_module assoc_list.
:- import_module bag.
main(!IO) :-
List = string.to_char_list("this is a test"),
bag.from_list(List, Bag),
bag.to_assoc_list(Bag, ElemSortedAssocList),
list.sort(assoc_list.reverse_members(ElemSortedAssocList), CountSortedAssocList),
assoc_list.reverse_members(CountSortedAssocList, Result),
io.write(Result, !IO),
io.nl(!IO).

Related

Prolog - dividing list into n-elements sections

I have a predict which gets first N elements:
nfirst(N, _, Lnew) :- N =< 0, Lnew = [].
nfirst(_, [], []).
nfirst(N, [X|Y], [X|Y1]) :- N1 is N - 1, nfirst(N1, Y, Y1).
It works:
% nfirst(3,[1,2,3,4,5,6],X).
% X = [1, 2, 3]
I need a predict for divide list like below:
% divide([a,b,c,d,e,f,g,h],[3,2,1,2],X).
% X = [[a,b,c],[d,e],[f],[g,h]]
The best way is using nfirst.
Very similar question to the one I answered here. Again, the trick is to use append/3 plus length/2 to "bite off" a chunk of list, per my comment above:
split_at(N, List, [H|[T]]) :- append(H, T, List), length(H, N).
If you run that, you'll see this:
?- split_at(4, [1,2,3,4,5,6,7,8], X).
X = [[1, 2, 3, 4], [5, 6, 7, 8]] ;
So this is the backbone of your program, and now you just need the usual recursive stuff around it. First, the base case, which says, if I'm out of list, I should be out of split locations, and thus out of result:
divide([], [], []).
Note that explicit base cases like this make your program more correct than something like divide([], _, _) because they will cause you to fail if you get too many split locations for your list size.
Now the recursive case is not difficult, but because split_at/3 puts two things together in a list (probably a bad choice, you could make split_at/4 as an improvement) you have to take them out, and it clouds the logic a bit here while making (IMO) a nicer API on its own.
divide(List, [Split|Splits], [Chunk|Rest]) :-
split_at(Split, List, [Chunk, Remainder]),
divide(Remainder, Splits, Rest).
This should be fairly straightforward: we're just taking a Split location, using it to chop up the List, and repeating the processing on what's left over. It seems to work as you expect:
?- divide([a,b,c,d,e,f,g,h],[3,2,1,2],X).
X = [[a, b, c], [d, e], [f], [g, h]] ;
false.
Hope this helps! Compare to the other answer, it may illuminate things.

Prolog - Arithmetic mean and merge

I have this query in Prolog:
?- order([[person1, [7,8,8,9]], [person2, [8,9,8,9]], [person3,
[6,7,5,4]]],X).
I need the arithmetic mean of each person and then use mergesort, something like this:
X=[[person2,[8,9,8,9],8.5],[person1,[7,8,8,9],8],[person3,[6,7,5,4],5.5]].
I know how to get the arithmetic mean for one list, but in this case I will need something recursive, I think.
Someone can help me?
Applying a predicate to each member of a list to obtain a new list is what maplist is for. For sorting, you can use the built-in keysort/2, or, if you have the latest SWI-Prolog version, sort/4. However, it would be better if you used pairs (for example) and not just another item in a list (you know the number of "things", after all):
person_vals_mean([P, Vs], Mean-[P, Vs]) :-
numlist_mean(Vs, Mean). % assuming you have defined it elsewhere
order(PVs, Ordered) :-
maplist(person_vals_mean, PVs, PVMs),
keysort(PVMs, Ordered_rev),
reverse(Ordered_rev, Ordered).
It might be wise to also get rid of the lists altogether: why [person, [1,2,3]] and not person_vals(person, [1,2,3])? With this representation, you can rewrite person_vals_mean/2 as:
person_vals_mean(person_vals(P, Vs), Mean-person_vals(P, Vs)) :- ...
Or, if you can use sort/4, even:
person_vals_mean(person_vals(P, Vs), person_vals_mean(P, Vs, M)) :-
numlist_mean(Vs, M).
order_by_means(PVs, Ordered) :-
maplist(person_vals_mean, PVs, PVMs),
sort(3, #>=, PVMs, Ordered).
And then:
?- order_by_means([person_vals(person1, [7,8,8,9]),
person_vals(person2, [8,9,8,9]),
person_vals(person3, [6,7,5,4])],
Ordered).
Ordered = [person_vals_mean(person2, [8, 9, 8, 9], 8.5),
person_vals_mean(person1, [7, 8, 8, 9], 8),
person_vals_mean(person3, [6, 7, 5, 4], 5.5)].
As a matter of fact, all sorting built-ins in SWI-Prolog use a merge sort algorithm, but this is implemented in C and really quite irrelevant.

How do I append 3 lists efficiently in Prolog?

I know how to do it for 2 lists:
append([],L,L).
append([H|T],L,[H|R]):-append(T,L,R).
but how to do it for 3? Without using the append for 2 lists twice.
To append lists efficiently, consider using difference lists. A difference list is a list expressed using a term with two lists. The most common representation uses (-)/2 as the functor for the term. For example, the list [1,2,3] can be expressed as:
[1,2,3| Tail]-Tail.
By keeping track of the list tail, i.e. of its open end, you can do several operations efficiently. For example, you can append an element to end of the list in O(1) by instantiating the tail:
add_to_end_of_list(List-Tail, Element, List-Tail2) :-
Tail = [Element| Tail2].
Or simply:
add_to_end_of_list(List-[Element| Tail2], Element, List-Tail2).
Let's try it:
?- add_to_end_of_list([1,2,3| Tail]-Tail, 4, Result).
Tail = [4|_G1006],
Result = [1, 2, 3, 4|_G1006]-_G1006.
Now, appending two lists is similar and also O(1). Instead of appending an element, we want to append a list of elements:
dappend(List1-Tail1, Tail1-Tail2, List1-Tail2).
For example:
?- dappend([1,2,3 | Tail1]-Tail1, [4,5,6| Tail2]-Tail2, Result).
Tail1 = [4, 5, 6|Tail2],
Result = [1, 2, 3, 4, 5, 6|Tail2]-Tail2.
I leave to you as an exercise to answer your own question using difference lists. Note that going from a difference list to a closed list, is simply a question of instantiating the open end to the empty list. For example:
?- dappend([1,2,3 | Tail1]-Tail1, [4,5,6| Tail2]-Tail2, Result-[]).
Tail1 = [4, 5, 6],
Tail2 = [],
Result = [1, 2, 3, 4, 5, 6].
However, going from a closed list to a difference list does requires you to traverse the list, which is O(n):
as_difflist([], Back-Back).
as_difflist([Head| Tail], [Head| Tail2]-Back) :-
as_difflist(Tail, Tail2-Back).
The cost of constructing the difference lists may or may not be an issue, of course, depending on how you get the initial lists and how often you will be appending lists in your application.
Hope I understood the question (and I don't think the following is more efficient than the other solutions here), but did you mean something like this?
append([],[],L,L).
append([],[H|T],L,[H|R]) :- append([],T,L,R).
append([H|T],L0,L1,[H|R]) :- append(T,L0,L1,R).
append3(Xs, Ys, Zs, XsYsZs) :-
append(Xs, YsZs, XsYsZs),
append(Ys, Zs, YsZs).
Is as efficient, as it can get. Cost is about |Xs|+|Ys| inferences. However, you might have attempted to define it like the following with about 2|Xs|+|Ys| inferences.
append3bad(Xs, Ys, Zs, XsYsZs) :-
append(Xs, Ys, XsYs),
append(XsYs, Zs, XsYsZs).
Also, termination is much better in the first case:
append3(Xs, Ys, Zs, XsYsZs)
terminates_if b(Xs),b(Ys);b(XsYsZs)
meaning that either Xs and Ys or XsYsZs needs to be known to make append3/4 terminate
... versus
append3bad(Xs, Ys, Zs, XsYsZs)
terminates_if b(Xs),b(Ys);b(Xs),b(XsYsZs)
^^^^^
for append3bad/4, where XsYsZs is not sufficient, but additionally also Xs has to be known.

Working with lists of lists in Prolog

New to prolog, trying to get my head around dealing with lists of lists recursively.
Say I have a list:
prices([20, 20, 40]).
And another list (of lists):
items([ [12, 14, 23],[8, 16, 22],[18, 12, 14] ]).
I want to append an element from prices to each list in items:
items([ [12, 14, 23, 20],[8, 16, 22, 20],[18, 12, 14, 40] ]).
I'm having trouble with working through both a single list and a list of lists at the same time.
Any pointers would be appreciated.
Thanks!
It's not too difficult if you sort it out in its appropriate pieces. In prolog, a list is a list until you need to do something with an element.
item_prices(ItemPrices) :-
prices(Prices),
items(ItemLists),
item_prices(ItemLists, Prices, [], ItemPrices), !.
item_prices([ItemList|ItemLists], [Price|Prices], SoFar, ItemPrices) :-
append(ItemList, [Price], ItemPrice), % Assumes the items are lists, price is not
item_prices(ItemLists, Prices, [ItemPrice|SoFar], ItemPrices).
item_prices(_, _, ItemPrices, ItemPrices).
Then you'd just call:
item_prices(ItemPrices).
To give:
ItemPrices = [[18,12,14,40],[8,16,22,20],[12,14,23,20]]
any time you have difficulty in dealing with too much stuff at the same time, see if you can separate your concerns and deal with each separately, through an auxiliary predicate.
For instance here you could define add_to_end( Element, List, Newlist) and use it in your main predicate.
add_to_end( E, [], R):- R = ... .
add_to_end( E, [A|B], R):- R=[A|X], add_to_end( E, B, ...).
When you've mastered the basics of the language, do see what's there in the library; chances are there's already a predicate that lets you append two lists, which you could use somehow instead of writing your own specialized version of it. After all, add_to_end( E, L, R) == append_two_lists(L, [E], ...).
this is a variation on mbratch answer
item_prices(ItemPrices) :-
prices(Prices),
items(ItemLists),
item_prices(ItemLists, Prices, ItemPrices), !.
item_prices([ItemList|ItemLists], [Price|Prices], [ItemPrice|ItemPrices]) :-
append(ItemList, [Price], ItemPrice), % Assumes the items are lists, price is not
item_prices(ItemLists, Prices, ItemPrices).
item_prices([], [], []).
without an accumulator, we avoid to reverse the order.
Here is a solution, using maplist/3, handy when you must perform the same operation among matched lists elements:
items_updated(Us) :-
items(Is),
prices(Ps),
maplist(append_, Is, Ps, Us).
append_(Is, P, R) :- append(Is, [P], R).

prolog: sorting w.r.t. some attribute

My database is similar to this:
% happy(Person,Happiness)
happy(1,10).
happy(2,5).
happy(3,8).
happy(4,1).
I want to sort people w.r.t. their happiness.
I coded the following and it does what I want. However it looked cumbersome to me. Any improvements?
? - sortPeople(Ts).
Ts = [1, 3, 2, 4].
My solution:
getFirst([],R,R).
getFirst([[H1,_]|T],F,R) :-
append([H1],F,R1),
getFirst(T,R1,R).
compareHappiness(X, [_,S1], [_,S2]) :- compare(X, S1, S2).
sortPeople(Ts) :-
findall([X,Y], happy(X,Y), List),
predsort(compareHappiness, List, SortedList),
getFirst(SortedList,[],Ts).
Consider using more descriptive and declarative predicate names, for example:
person_happiness(1, 10).
person_happiness(2, 5).
person_happiness(3, 8).
person_happiness(4, 1).
To sort people by happiness, consider using the built-in keysort/2, which is more efficient than predsort/3. You only need to build key-value pairs, for which by convention the functor -/2 is used, and instead of your auxiliary predicate, consider using the SWI-Prolog built-ins pairs_values/2 and reverse/2:
descending_happiness(Ps) :-
findall(H-P, person_happiness(P, H), HPs),
keysort(HPs, HPs1),
pairs_values(HPs1, Ps1),
reverse(Ps1, Ps).
Example query:
?- descending_happiness(Ps).
Ps = [1, 3, 2, 4].
-Here is what I got :
sort(Rez) :- findall([Happiness,PId],happy(PId,Happiness),List),
msort(List,LSorted),
findall(PersonID,member([_,PersonID],LSorted),Sorted),
reverse(Sorted,Rez).

Resources