Reputation: 652
I was playing with the lists in erlang. I have a randomly populated list of the following format:
List=[{10,"English",id1},{20,"Maths",id2},{30,"Geo",id3},{20,"English",id4}]
this is in the format [{Marks,Subject,Id}].
I wanted to make a list out of this list containing of only "English" as the subject which I did as follows
NewList=lists:filter(fun(A)->element(2,A)=="English",List)
which gives me
[{10,"English",id1},{20,"English",id4}]
which is fine but now I want to get the id of the tuple inside the NewList which has the greater value of the Marks for example, here,
out of id1
and id4
since id4 is greater, I need Id4.
The problem here is that List is the randomly populated list which means that in future all the 4 entries may be present whose subject is English only
Can anybody suggest a way out.Thanks in advance.
Upvotes: 1
Views: 77
Reputation: 14042
1> List = [{10,"maths", "Joe"},{12,"english","Mark"},{10,"maths","Rebecca"},{15,"english","Janice"}].
[{10,"maths","Joe"},
{12,"english","Mark"},
{10,"maths","Rebecca"},
{15,"english","Janice"}]
2> Best = fun(List,Subject) -> lists:foldl(fun({Mark,S,Id},{BestMark,_}) when Mark > BestMark, S == Subject -> {Mark,[Id]};
2> ({Mark,S,Id},{BestMark,Students}) when Mark == BestMark, S == Subject -> {BestMark,[Id|Students]};
2> (_,Acc) -> Acc
2> end,{0,[]},List) end.
#Fun<erl_eval.12.52032458>
3> Best(List,"maths").
{10,["Rebecca","Joe"]}
4> Best(List,"english").
{15,["Janice"]}
5> Best(List,"art").
{0,[]}
6>
Upvotes: 0
Reputation: 48629
I don't think Derek Brown's lists:foldl() solution will work correctly. lists:foldl()
allows you to step through a list while maintaining and manipulating a separate variable. After the last element has been processed, lists:foldl()
returns the separate variable. In this case, you can use the separate variable to update the Student with the highest mark.
You provide lists:foldl()
with a fun whose arguments are the current element in the list and the separate variable that you want to manipulate. The return value of the fun is the new value for the separate variable.
max_mark(Students, Subject) ->
lists:foldl(
fun({M,S,_Id}=Student, {Highest,_,_}) when S=:=Subject, M>Highest -> Student;
(_Student, BestStudent) -> BestStudent
end,
{0, Subject, none}, %Starting value for the separate variable
Students %The list you want to step through
).
In your case, the separate variable will hold the student with the highest mark so far.
In the shell:
50> c(my).
{ok,my}
51> Students = [{10,"English",id1},{20,"Maths",id2},{30,"Geo",id3},{30,"Maths",id1},{30,"English",id4},{20,"English",id3}].
[{10,"English",id1},
{20,"Maths",id2},
{30,"Geo",id3},
{30,"Maths",id1},
{30,"English",id4},
{20,"English",id3}]
52> my:max_mark(Students, "English").
{30,"English",id4}
53> my:max_mark(Students, "Maths").
{30,"Maths",id1}
54> my:max_mark(Students, "Geo").
{30,"Geo",id3}
Getting ties will take some more work.
The advantage of using lists:foldl()
is that you only have to traverse your list once to get the information you want, rather than traversing the list one time with filter()
and then a second time with max()
. You can imagine that if you had a list with millions of elements, it would behoove you to traverse the list as few times as possible.
Upvotes: 1
Reputation: 521
Note that this assumes that the input list contains only tuples for the Subject desired, as the OP has already filtered on that. Alternatively that filter could be added here to avoid that separate step.
To get the tuples with the max value of an arbitrary element of each one (Marks in this case, of which could be more than one with the same Marks), without concern for tuple ordering rules, could do something like this (which does use the first element for comparison, as in this question), as long as the list contains at least one element.
Uses a tuple containing the value of the Marks for the first list entry, and that entry, as the initial accumulator. Then, starting with the second tuple, add to the list in the accumulator if the input tuple in question has Marks equal to the current highest. If the current input tuple has marks higher than the current highest, replace the list in the accumulator with a new list containing only the current input tuple. Else, keep accumulator as-is. If the list only contains one entry, it will be returned, by virtue of being the initial accumulator in the foldl
call.
find_max([{FirstMarks, _, _} = First | T]) ->
lists:foldl(
fun
%% Current tuple from input list has marks higher than current highest in accumulator,
%% so reset the accumulator to this new value for marks and new list containing
%% just this input list tuple
({N, _, _} = This, {MaxN, _L}) when N > MaxN ->
{N, [This]};
%% Current tuple from input list has marks equal to current highest in accumulator,
%% so add this input list tuple to the accumulator's list
({N, _, _} = This, {N, L}) ->
{N, [This | L]};
%% Current tuple from input list has marks less than current highest in accumulator,
%% so don't change accumulator
(_, Acc) ->
Acc
end,
%% Accumulator is tuple containing initial high value for marks, and list containing
%% containing the first element of the input list
{FirstMarks, [First]}, T).
1> tst_so:find_max([{30, a, a}]).
{30,[{30,a,a}]}
2>
2> tst_so:find_max([{30, a, a}, {20, b, b}, {40, c, c}, {10, d, d}, {40, e, e}]).
{40,[{40,e,e},{40,c,c}]}
3>
Upvotes: 0
Reputation: 679
If I understood correctly, that would work:
NewList = lists:filter(fun(A)->element(2,A)=="English" end,List).
{_, _, MaxID} = lists:max(NewList).
Upvotes: 1
Reputation: 1189
After filtering the list, couldn't you use lists:max/1
to get the tuple with maximum marks?
lists:max(NewList)
Upvotes: 1