abhishek ranjan
abhishek ranjan

Reputation: 652

A simple Erlang twister

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

Answers (5)

Pascal
Pascal

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

7stud
7stud

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

Derek Brown
Derek Brown

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

Egor Biriukov
Egor Biriukov

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

Asier Azkuenaga
Asier Azkuenaga

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

Related Questions