http://www.perlmonks.org?node_id=693179

k_grdn has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,

I'm having trouble re-ordering an array based on predefined values.

my @dports = qw(544 554 80 80 80 80 80 53 22 22 22 22 8080 554 443 80 80 25 110 143 143 110 21 22 111 110);

Desired output:

554 554 554 110 110 110 80 etc...
I have tried numerous solutions but have hit the wall could someone kindly throw some ideas my way.

The script is part of a smaller picture to re-arrange firewall rule precedence based on ports.

Regards,

k_grdn

Replies are listed 'Best First'.
Re: Predefined Sort
by kyle (Abbot) on Jun 20, 2008 at 16:15 UTC

    I don't understand what you're trying to do. What are the predefined values? Ordinarily for "ordering", I'd use sort, but from your example, I can't tell what order you're shooting for. It's not highest to lowest because 8080 doesn't appear at the top, and it doesn't look alphabetical either. Could you show us some of what you've tried already?

Re: Predefined Sort
by krusty (Hermit) on Jun 20, 2008 at 16:30 UTC
    I'm having a bit of trouble understanding what you want your sort criteria to be?

    Near as I can tell you either want to sort in some type of reverse numeric order perhaps ignoring certain values OR you want to sort all ports in a list based on an order that you predefine.

    For sorting a list based on predefined order where your predefined order is stored in an array @predefined:
    foreach $element (@dports) { $count_hash{$element}++; } foreach $key (@predefined) { print "$key\n" x $count_hash{$key} if $count_hash{$key}; }
    For sorting in numeric reverse order:
    my @sorted = sort {$b <=> $a} @dports;

    If you are filtering out certain groups we'd have to stack this command with some type of grep.

    Hope this helps,
    Kris

      For sorting in reverse order, this is easier to understand and executes just as quickly:

      my @sorted = reverse sort {$a <=> $b} @dports

      When you swap $a and $b, there's a much better chance that someone reading through later won't notice that (particularly if the expression gets bigger and more complicated). Sticking reverse in is obvious.

        Just as quickly? Looks like (surprisingly, to me at least) ~9-10% faster on average.

        use Benchmark qw( timethese cmpthese ); use List::Util qw( shuffle ); for my $max (qw( 100 200 500 1000 2000 5000 )) { print "results for $max elems\n"; my @big_list = shuffle 1 .. $max; cmpthese( -1, { plain_sort => sub { my @local = sort { $b <=> $a } @big_list; }, reversed => sub { my @local = reverse sort { $a <=> $b } @big_list; }, } ); print "\n"; } exit 0; __END__ results for 100 elems               Rate plain_sort   reversed plain_sort 46849/s         --        -8% reversed   50717/s         8%         -- results for 200 elems               Rate plain_sort   reversed plain_sort 20676/s         --       -12% reversed   23424/s        13%         -- results for 500 elems              Rate plain_sort   reversed plain_sort 7657/s         --        -9% reversed   8374/s         9%         -- results for 1000 elems              Rate plain_sort   reversed plain_sort 3490/s         --        -8% reversed   3794/s         9%         -- results for 2000 elems              Rate plain_sort   reversed plain_sort 1599/s         --        -9% reversed   1756/s        10%         -- results for 5000 elems             Rate plain_sort   reversed plain_sort 553/s         --       -10% reversed   613/s        11%         --

        This space reserved for the update when someone points out my obvious benchmark fau pas . . . :)

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

        Good point. I like reverse sort {$a <=> $b} @unsorted for readability/maintenance.
        I think I'd probably use it, but is it really true that it executes just as quickly?
        Doesn't reverse have to re-evaluate the list given to it before executing?
        This would mean are evaluating the contents of the unsorted list twice: once to perform an ascending sort and again to perform a reverse.

        I do a lot of large batch database processing, and I'm fond of anything which is more efficient. If I had to execute the sort a few million times in a loop or give it an extremely large list such as all rows in my database, I could see the single sort approach being more efficient. Please let me know if I'm missing some, but in the meantime I'm off to use Benchmark to try this out.
Re: Predefined Sort
by graff (Chancellor) on Jun 21, 2008 at 00:04 UTC
    If you want the port numbers to be listed in a particular order (which is different from a numeric or string sorted order), have you tried creating a hash with the port numbers as keys and the desired ordering as values? Something like this (if this is what you want):
    # seemingly arbitrary (but "meaningful") ordering sequence of port num +bers: my @port_seq = qw/554 110 80 8080 22 25 21 443 53/; # hash to deliver ordering value for each port value: my %port_order = map { $port_seq[$_] => $_ } 0 .. $#port_seq; # unordered array of data (port numbers): my @ports = qw(544 554 80 80 80 80 80 53 22 22 22 22 8080 554 443 80 8 +0 25 110 143 143 110 21 22 111 110); # print @ports in "sorted" order: print "$_\n" for ( sort {$port_order{$a} <=> $port_order{$b}} @ports ) +;
    (I'm just guessing what the order should be for port numbers that follow "554, 110, 80", since you didn't clarify that.)

    You might want the "port_seq" array to be in a config file, in case you change your mind about how the ports should be ordered (or how many/which ports are being listed).

Re: Predefined Sort
by pc88mxer (Vicar) on Jun 20, 2008 at 16:22 UTC
    Try:
    my @ordered = reverse sort { $a <=> $b } @dports; # or my @ordered = sort { $b <=> $a } @dports; print "@ordered";
    By default sort uses string comparisons. By using <=> you tell it to compare the values as numbers.
Re: Predefined Sort
by educated_foo (Vicar) on Jun 22, 2008 at 04:44 UTC
    You mean like
    setbuf(3)
    , but for Perl files? You can probably do something like that in XS (so long as Perl is using stdio), but I don't know of a Perl way off-hand.

    PS -- "How can you open 30,000 files?!" and "You can't open 30,000 files!" have to be two of the stupidest, least relevant, and most useless responses I've ever seen on this site.

    PPS: Wrong thread -- man, I need some sleep.

        Thanks for all your replies!

        I am trying to sort on predefined values not by order high to low etc..

        I will work my way through all provided code and update the post!

        Thanks again all you monks!