Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sorting Erlang records in a list?

Tags:

sorting

erlang

I have a record in erlang:

-record(myrec,
    { 
      id = 0,
      price = 0,
      quantity = 0
    }).

I then have a list of records that I want to sort by id and price, both in descending and ascending order, where price is the first key and if two records have the same price I want to sort those by id.

How can I define a fun for this?

I'm a newb at Erlang :)

thanks, nisbus

like image 648
nisbus Avatar asked Sep 16 '10 01:09

nisbus


People also ask

How do I split a list in Erlang?

You can use lists:split/2 for this: divide(L, N) -> divide(L, N, []).

How do I find the length of a list in Erlang?

You can use length() to find the length of a list, and can use list comprehensions to filter your list. num(L) -> length([X || X <- L, X < 1]).

How do you define an Erlang record?

Erlang has the extra facility to create records. These records consist of fields. For example, you can define a personal record which has 2 fields, one is the id and the other is the name field. In Erlang, you can then create various instances of this record to define multiple people with various names and id's.


2 Answers

This is a shorter solution than what has been suggested so far. First define your record:

1> rd(myrec, {id=0, price=0, quantity=0}).
myrec

Then let's invent 3 of them:

2> A = #myrec{id=1, price=10, quantity=2}, B = #myrec{id=2, price=4, quantity=3}, C = #myrec{id=3, price=10, quantity=1}.
#myrec{id = 3,price = 10,quantity = 1

Now we need a comparison function. This is where the solution is shorter. Erlang can compare terms of a tuple in the order they appear, so if we want to sort by price, then by id, we just have to compare two tuples of the form {PriceA, IdA} < {PriceB, IdB}:

3> F = fun(X, Y) -> {X#myrec.price, X#myrec.id} < {Y#myrec.price, Y#myrec.id} end.
#Fun<erl_eval.12.113037538>

And plug it in lists:sort/2:

4> lists:sort(F, [C,B,A]).
[#myrec{id = 2,price = 4,quantity = 3},
 #myrec{id = 1,price = 10,quantity = 2},
 #myrec{id = 3,price = 10,quantity = 1}]

The order is now [B, A, C] and your list is sorted.

Note that if you wanted to sort by descending id instead, You could trick it by reversing the ids in the tuples as follows:

5> G = fun(X, Y) -> {X#myrec.price, Y#myrec.id} < {Y#myrec.price, X#myrec.id} end.
#Fun<erl_eval.12.113037538>
6> lists:sort(G, [C,B,A]).                                                       
[#myrec{id = 2,price = 4,quantity = 3},
 #myrec{id = 3,price = 10,quantity = 1},
 #myrec{id = 1,price = 10,quantity = 2}]

Giving us [B, C, A]. This is not obvious to the reader, so you'd better document it or use Dustin's solution in this case. The advantage of the solution presented here is that there is no nesting required. By setting elements in either tuple in the comparison, you can pretty much compare as many of them as you want without making the code that much longer.

like image 60
I GIVE TERRIBLE ADVICE Avatar answered Sep 23 '22 08:09

I GIVE TERRIBLE ADVICE


First, you figure out how to compare your records:

-spec compare(#myrec{}, #myrec{}) -> boolean().
compare(A, B) ->
    case A#myrec.price == B#myrec.price of
        true ->
            A#myrec.id < B#myrec.id;
        _ ->
            B#myrec.price < A#myrec.price
    end.

Then, you just use the normal lists:sort function with your comparison function to get what you want (this is an eunit test of the above I ran to make sure I did something that made sense):

compare_test() ->
    R1 = #myrec{id=5, price=3, quantity=2},
    R2 = #myrec{id=6, price=5, quantity=1},
    R3 = #myrec{id=7, price=5, quantity=0},

    false = compare(R1, R2),
    true = compare(R2, R1),

    true = compare(R2, R3),
    false = compare(R3, R2),

    false = compare(R1, R3),
    true = compare(R3, R1),

    % Run a sort with the above comparator.
    [R2, R3, R1] = lists:sort(fun compare/2, [R1, R2, R3]).
like image 34
Dustin Avatar answered Sep 23 '22 08:09

Dustin