Related
So I have a list that looks like this:
[
["p1", "p2", "100", "Storgatan"],
["p1", "p3", "200", "Lillgatan"],
["p2", "p4", "100", "Nygatan"],
["p3", "p4", "50", "Kungsgatan"],
["p4", "p5", "150", "Kungsgatan"]
]
The elements in each nested list represent (in order):
1st element = Start Point
2nd element = End Point
3rd element = Distance
4th element = Street Name.
I have to now write a predicate which figures out which street is the shortest and which street is the longest, along with their respective (summed up) distances.
For example the final output should look something like this:
Longest street: Kungsgatan, 200
Shortest street: Storgatan, 100
I don't really understand why the start and end points are relevant information here. My current idea is to collect all the unique street names, put them in a separate list along with a counter for each street that starts at zero and then use that list to accumulate all of the distances for each separate street.
Something like:
create_sum_list([
["p1", "p2", "100", "Storgatan"],
["p1", "p3", "200", "Lillgatan"],
["p2", "p4", "100", "Nygatan"],
["p3", "p4", "50", "Kungsgatan"],
["p4", "p5", "150", "Kungsgatan"]
], SL).
SL= [[Storgatan, 0], [Lillgatan, 0],
[Nygatan, 0], [Kungsgatan ,0]]
accumulate(SL, List).
List=[[Storgatan, 100], [Lillgatan, 200],
[Nygatan, 100], [Kungsgatan ,200]]
This is probably a stupid idea and there is probably a way better way to solve this. I have thought of many different ideas where I either reach a dead end or they are way too complex for such a "simple" task.
I can achieve this easily through "normal" imperative programming but I am new to logical programming and Prolog. I have no idea how to achieve this.
Help?
Thanks!
If you already have a list and you want to group by street name and sum the lengths, you must decide how you do the grouping. One way is to use library(pairs):
streets_lengths(S, L) :-
maplist(street_name_and_length, S, NL),
keysort(NL, NL_sorted),
group_pairs_by_key(NL_sorted, G),
maplist(total_lengths, G, GT),
transpose_pairs(GT, By_length), % sorts!
group_pairs_by_key(By_length, L).
street_name_and_length([_, _, N, L], L_atom-N_number) :-
number_string(N_number, N),
atom_string(L_atom, L).
total_lengths(S-Ls, S-T) :-
sum_list(Ls, T).
You can use it like this:
?- streets_lengths([
["p1", "p2", "100", "Storgatan"],
["p1", "p3", "200", "Lillgatan"],
["p2", "p4", "100", "Nygatan"],
["p3", "p4", "50", "Kungsgatan"],
["p4", "p5", "150", "Kungsgatan"]
], SL).
SL = [100-['Storgatan', 'Nygatan'], 200-['Lillgatan', 'Kungsgatan']].
Since there can be many streets with the same length, the results are returned grouped by length. You can get the "shortest" and "longest" by getting the first and last element of the list, like this:
L = [First|_], last(L, Last)
Since this is homework I won't give you the entire answer but the key part of the code.
As I noted in the comments, the format of the structure matters, e.g. list, terms, atoms, strings, etc.
test(Street_lengths,Shortest) :-
List =
[
street(p1, p2, 100, 'Storgatan'),
street(p1, p3, 200, 'Lillgatan'),
street(p2, p4, 100, 'Nygatan'),
street(p3, p4, 50, 'Kungsgatan'),
street(p4, p5, 150, 'Kungsgatan')
],
street_lengths(List,Street_lengths),
lengths1(Street_lengths,Lengths),
min_list(Lengths,Min),
convlist(value_shortest2(Min),Street_lengths,Shortest).
street_lengths([H|T],Street_lengths) :-
merge_streets(H,T,Street_lengths).
% 2 or more items in list
merge_streets(street(_,_,Length0,Name),[street(_,_,Length1,Name),street(_,_,Length2,Name2)|Streets0],[street(Length,Name)|Streets]) :-
Length is Length0 + Length1,
merge_streets(street(_,_,Length2,Name2),Streets0,Streets).
merge_streets(street(_,_,Length0,Name0),[street(_,_,Length1,Name1)|Streets0],[street(Length0,Name0)|Streets]) :-
Name0 \= Name1,
merge_streets(street(_,_,Length1,Name1),Streets0,Streets).
% 1 item in list
merge_streets(street(_,_,Length0,Name),[street(_,_,Length1,Name)],[street(Length,Name)]) :-
Length is Length0 + Length1.
merge_streets(street(_,_,Length0,Name0),[street(_,_,Length1,Name1)],[street(Length0,Name0)|Streets]) :-
Name0 \= Name1,
merge_streets(street(_,_,Length1,Name1),[],Streets).
% no item in list
merge_streets(street(_,_,Length,Name),[],[street(Length,Name)]).
lengths1(List,Lengths) :-
maplist(value_length1,List,Lengths).
value_length1(street(Length,_),Length).
value_shortest2(Min,street(Min,Name),street(Min,Name)).
Example run:
?- test(Street_lengths,Shortest).
Street_lengths = [street(100, 'Storgatan'), street(200, 'Lillgatan'), street(100, 'Nygatan'), street(200, 'Kungsgatan')],
Shortest = [street(100, 'Storgatan'), street(100, 'Nygatan')] ;
false.
I left the longest for you to do, but should be a cake walk.
To display the information as you noted in the question I would use format/2.
So now you either have to change how you get the data read into the format for this code, or change this code to work with how you structured the data. IMHO I would change the data to work with this structure.
If want to know how efficient your code is you can use time/1
?- time(test(Street_lengths,Shortest)).
% 44 inferences, 0.000 CPU in 0.000 seconds (?% CPU, Infinite Lips)
Street_lengths = [street(100, 'Storgatan'), street(200, 'Lillgatan'), street(100, 'Nygatan'), street(200, 'Kungsgatan')],
Shortest = [street(100, 'Storgatan'), street(100, 'Nygatan')] ;
% 17 inferences, 0.000 CPU in 0.000 seconds (?% CPU, Infinite Lips)
false.
Erlang Newbie here . Suppose I have two lists which are like this .
L1= [{'Lady in the Water',2.5},
{'Snakes on a Plane',3.5},
{'Just My Luck',3.0},
{'Superman Returns',3.5},
{'You, Me and Dupree',2.5},
{'The Night Listener',3.0}]
and
L2 = [{'Lady in the Water',3.0},
{'Snakes on a Plane',3.5},
{'Just My Luck',1.5},
{'Superman Returns',5.0},
{'You, Me and Dupree',3.5}]
I want the common ratings in a list of tuple like
[{2.5,3.0},{3.5,3.5},{3.0,1.5},{3.5,5.0},{2.5,3.5}]
My code is like this
common_rating(R1,R2)->common_rating(R1,R2,0,0).
common_rating(_X,[],M,N) ->{M,N};
common_rating(X,[Y|Y1],A,B)->
{M,R}=X,
{N,K }= Y,
case M=/=N of
true -> common_rating(X,Y1,A,B);
false -> common_rating(X,[],A+R,B+K)
end.
common_rating_final([],_R2,J) ->J;
common_rating_final([X|X1],R2,J)->
common_rating_final(X1,R2,J++[common_rating(X,R2)]).
To better understand the code
common_rating function expects a tuple of {movie,rating} and finds the same movie and rating from another list(B) and returns {rating,rating_B}
Now common_rating_final recursively goes over a list ,lets say A, and uses common_rating to find {rating_A,rating_B} for all movies that are common in A and B both
but when I run my code
my_module:common_rating_final(L1,L2,[]).
it returns me
[{2.5,3.0},{3.5,3.5},{3.0,1.5},{3.5,5.0},{2.5,3.5},{0,0}]
I can filter the {0,0} part but I think my logic is flawed but could not able to write a code which returns only the common ratings without the {0,0} part .
Please assist .
TL;DR
[{X2, Y2} || {X1, X2} <- L1, {Y1, Y2} <- L2, X1 =:= Y1].
Maybe the "cracks" here can find a better (more efficient, etc.) solution, but this one works. Basically it pattern matches ("deconstruct") the Ls and compares the first element of the tuples, returning the second element if the first ones happen to be equal.
The whole stuff/proof:
gorre#uplink:~$ erl
Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]
Eshell V8.3 (abort with ^G)
1> L1= [{"Lady in the Water",2.5}, {"Snakes on a Plane",3.5}, {"Just My Luck",3.0}, {"Superman Returns",3.5}, {"You, Me and Dupree",2.5}, {"The Night Listener",3.0}].
[{"Lady in the Water",2.5},
{"Snakes on a Plane",3.5},
{"Just My Luck",3.0},
{"Superman Returns",3.5},
{"You, Me and Dupree",2.5},
{"The Night Listener",3.0}]
2> L2 = [{"Lady in the Water",3.0}, {"Snakes on a Plane",3.5}, {"Just My Luck",1.5}, {"Superman Returns",5.0}, {"You, Me and Dupree",3.5}].
[{"Lady in the Water",3.0},
{"Snakes on a Plane",3.5},
{"Just My Luck",1.5},
{"Superman Returns",5.0},
{"You, Me and Dupree",3.5}]
3> [{X2, Y2} || {X1, X2} <- L1, {Y1, Y2} <- L2, X1 =:= Y1].
[{2.5,3.0},{3.5,3.5},{3.0,1.5},{3.5,5.0},{2.5,3.5}]
4>
Notice that I changed the atoms into strings (it works the same way though).
Let's assume I have something like:
% person(ID, Name, Age)
person(_, paul, Age).
person(_, Name, 20).
...
...and instead of the typical binding I would like to have a helper function get_person_list(ID, Name, Age) that calls person but returns me a list:
?- get_person_list(_, Name, 20).
[frank, jack, jane].
...to get a list for e.g. all persons with age 20. How can I achieve this?
Do not use findall/3 for this. This is the perfect example of how bagof/3 and setof/3 are different from findall/3.
Your example is a bit strange, so here is another database. I removed the first argument because you don't seem to be using it, and added more rows so that there can be more different ages. Note that it makes no sense to have free variables in a table of facts like person, so all rows have values at both positions.
person(abel, 20).
person(bill, 30).
person(clive, 20).
person(diana, 10).
person(evan, 200).
person(fia, 20).
people_age(Age, Ps) :-
bagof(P, person(P, Age), Ps).
With this definition of people_age/2, you can query, for example:
Who are the people aged 20?
?- people_age(20, Ps).
Ps = [abel, clive, fia].
Group people by their age.
?- people_age(Age, Ps).
Age = 10,
Ps = [diana] ;
Age = 20,
Ps = [abel, clive, fia] ;
Age = 30,
Ps = [bill] ;
Age = 200,
Ps = [evan].
Who is at least 30 years old?
?- people_age(Age, Ps), Age >= 30.
Age = 30,
Ps = [bill] ;
Age = 200,
Ps = [evan].
The last one could be done differently, if you don't want to group by age. Here is how:
?- bagof(P, Age^( person(P, Age), Age >= 30 ), Ps).
Ps = [bill, evan].
or maybe
?- bagof(Age-P, Age^( person(P, Age), Age >= 30 ), Ps).
Ps = [30-bill, 200-evan].
... if you don't want to throw away the age completely.
But you should just read up on "collecting all solutions in Prolog" and the intended use of all three: findall/3, bagof/3, setof/3.
You could simply write: findall(X,person(_,X,20),L).
This will return you a list L which is the list you asked...
Example:
person(_, name1, 20).
person(_, name2, 20).
person(_, name3, 20).
person(_, name4, 20).
person(_, name5, 20).
?- findall(X,person(_,X,20),L).
L = [name1, name2, name3, name4, name5].
In your code, you don't provide a parameter for the resulting list.
Just call:
findall(Id, person(Id, _, 20), L).
You can make a separate predicate for this query if you really need it often, of course.
get_person_list(Id, List) :-
findall(Id, person(Id, _, 20), List).
See also bagof/3, as explained in Boris's answer.
This is a homework assignment and my first experience with Prolog. My goal is to create a list of Assignments from a list of people and a list of tasks. If a person has the letter identifier which matches the tasks then that persons ID and the Tasks ID are matched up and placed in a list of Assignments. My function prints out a list but it does not look like it is comparing all the elements. A sample input: schedule([p1,p2,p3],[t1,t2],Result). A sample output would look like [[p1,t1],[p2,t2][p3,t1],[p3,t2]].
What I have so far:
%%
%% person(ID, TASK_CAPABILITIES, AVAILABLE_HOURS)
%%
%% How many hours each person has available and what classes of tasks they
%% are capable of performing.
%%
person(p1, [c,a], 20).
person(p2, [b], 10).
person(p3, [a,b], 15).
person(p4, [c], 30).
%%
%% task(ID, REQUIRED_HOURS, TASK_CLASS)
%%
%% How long each task requires and what class it falls under.
%%
task(t1, a, 5).
task(t2, b, 10).
task(t3, c, 15).
task(t4, c, 10).
task(t5, a, 15).
task(t6, b, 10).
%test arithmetic functions
add(X, Y, Z) :- Z is X + Y.
subtract(X,Y,Z) :- Z is X - Y.
schedule([],[],[]).
schedule(People,
[Task|OtherTasks],
[[PersonId, TaskId]|RestOfAssignments]):-
member(PersonId, People),
person(PersonId, PersonCapabilities,_),
member(TaskId, [Task|OtherTasks]),
task(TaskId, TaskType,_),
member(TaskType, PersonCapabilities),
schedule( _, OtherTasks, RestOfAssignments).
My reasoning behind what I wrote was that the list of People would be compared to each task, then that task would be replaced by the next task and the comparison would repeat. What I see in the trace of this function instead is that the tasks are being removed from the list but are only compared to the first two People. My question is how can I get the schedule function to check the full list of people for each task?
Your problem seems ill specified, and you are simplifying too much... the code should keep into account hours availability as well as memberships. Ignoring this problem, select/3 instead of member/2 could help to model a naive solution:
schedule([],_,[]).
% peek a suitable task for PersonId
schedule([PersonId|People], Tasks, [[PersonId, TaskId]|RestOfAssignments]):-
select(TaskId, Tasks, RestTasks),
person(PersonId, PersonCapabilities,_),
task(TaskId, TaskType,_),
memberchk(TaskType, PersonCapabilities),
schedule(People, RestTasks, RestOfAssignments).
% if no suitable task for PersonId
schedule([_PersonId|People], Tasks, Assignments):-
schedule(People, Tasks, Assignments).
yields these solutions
?- schedule([p1,p2,p3],[t1,t2],Result).
Result = [[p1, t1], [p2, t2]] ;
Result = [[p1, t1], [p3, t2]] ;
Result = [[p1, t1]] ;
Result = [[p2, t2], [p3, t1]] ;
Result = [[p2, t2]] ;
Result = [[p3, t1]] ;
Result = [[p3, t2]] ;
Result = [].
statement:
value(engine,2000).
value(frame,605).
vehicle(motorbike,[engine,frame]).
how to write prolog predicate total(X). X is your total sum for motorbike.
I was unable to relate value of engine=2000 plus value of frame=605 that should return answer 2605 if i consult total(motorbike).
aggregation it's your friend:
total(Kind, X) :-
vehicle(Kind, Parts),
aggregate(sum(Price), Part^(member(Part, Parts), value(Part, Price)), X).
Here's a version that does the summation explicitly if you don't have the aggregate predicate that CapelliC shows:
% This says total is sum of the parts
total( Item, Amt ) :-
vehicle( Item, PartList ),
sum_parts( PartList, 0, Amt ).
% This says the sum of the parts is the sum of current part in list plus the sum
% of the rest
sum_parts( [Part|PartList], Acc, Amt ) :-
value( Part, PartAmt ), % This statement will relate the part to its price for you
Acc1 is Acc + PartAmt, % This will accumulate the price to the running total
sum_parts( PartList, Acc1, Amt ).
sum_parts( [], Amt, Amt ).