Sorting Erlang records in a list?

2019-03-19 05:02发布

问题:

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

回答1:

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.



回答2:

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]).


回答3:

% 3723064

-module(t).
-export([record_sort/0, price_cmp/2, qty_cmp/2]).

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

price_cmp(A, B) ->
    A#item.price < B#item.price.

qty_cmp(A, B) ->
    A#item.quantity < B#item.quantity.

record_sort() -> 
    Items = [ 
        #item{id=1, price=10, quantity=5},
        #item{id=2, price=50, quantity=0},
        #item{id=3, price=30, quantity=3},
        #item{id=4, price=60, quantity=9}
    ],
    io:format("Unsorted Items: ~p~n", [Items]),
    io:format("By Price: ~p~n", [lists:sort({t, price_cmp}, Items)]),
    io:format("By Quantity: ~p~n", [lists:sort({t, qty_cmp}, Items)]).

    % Alternatively use anonymous functions:

    % io:format("By Price: ~p~n", [lists:sort(
    %   fun(A, B) -> A#item.price < B#item.price end, Items)]),
    % 
    % io:format("By Quantity: ~p~n", [lists:sort(
    %   fun(A, B) -> A#item.quantity < B#item.quantity end, Items)]).

This will yield (assuming example file t.erl):

1> c(t).           
{ok,t}
2> t:record_sort().
Unsorted Items: [{item,1,10,5},{item,2,50,0},{item,3,30,3},{item,4,60,9}]
By Price: [{item,1,10,5},{item,3,30,3},{item,2,50,0},{item,4,60,9}]
By Quantity: [{item,2,50,0},{item,3,30,3},{item,1,10,5},{item,4,60,9}]
ok