Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?

Sorting an array of strings by number

by Anonymous Monk
on Oct 01, 2001 at 15:48 UTC ( #115828=perlquestion: print w/replies, xml ) Need Help??
Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I have an array containing lines of colon separated values. e.g.


i am sorting the array before i work on it, so that when i use my display method, they are automatically in ascending price order (first value is price). However, it works fine for prices less than 100, but 100+ takes precidence over the other prices, due obviously to the 1.

so instead of 39,45,120,139 I get 120,139,39,45

Is there any way I can get this to work right?

Many Thanks.

Replies are listed 'Best First'.
Re: Sorting an array of strings by number
by C-Keen (Monk) on Oct 01, 2001 at 16:00 UTC
    Maybe a good idea would be using the sort SUBNAME LIST subroutine. With SUBNAME you specify a subroutine that will take care of comparing two elements and it will return depends on the outcome of the comparison.
    The Camel Book states the following sub for sorting numerically:

    sub numeric {$a <=> $b}

    The <=> operator will take care of everything! Of course you will need to change the above sub to fit exactly your needs, but that should be a piece of cake. :-) If not dare to ask again :-)


Re: Sorting an array of strings by number
by tachyon (Chancellor) on Oct 01, 2001 at 16:03 UTC

    You need to use a numeric sort (the default is alphabetic). Provided the numbers are at the start of the line you can just do this:

    my @data = <DATA>; my @alpha_sort = sort { $a cmp $b } @data; my @numeric_sort = sort { $a <=> $b } @data; print "Alpha sort\n@alpha_sort\n"; print "Numeric sort\n@numeric_sort\n"; __DATA__ 123.45Hello 32.2World 1.5Just 288.2Another 288.3Perl 288.4Hacker

    If you have more complex requirements then a Schwartzian transform will be in order.




Re: Sorting an array of strings by number
by jeroenes (Priest) on Oct 01, 2001 at 16:05 UTC
    There is more than one path to take here:

    1. Zero-pad your numbers, so generate them with
      my $padded=sprintf("%08.3f",39.452);
      You can do this while reading your data:
      while (<>){ split /:/; $_[0]=sprintf("%08.3f", $_[0]); push @data, join ":", @_; }
    2. Or you can sort numerically. This requires you to extract the numbers and sort on them with <=> (see perlop), like
      sort{ $a <=> $b} @numbers;
      . You can write a sub for the extraction:
      sort{extract( $a ) <=> extract( $b )} @lines; sub extract{ $a = shift; split /:/, $a; $_[0]; }
    3. It's better to extract your numbers, and use them as keys in a hash, and work with a list of sorted keys:
      my %hash; while(<>){ my ($key, $data) = split( /:/, $_, 2 ); $hash{ $key } = $data; } print "Sorted!:\n\n", join "|", sort {$a <=> $b} keys %hash;
    4. .... many more paths to come up with


    "We are not alone"(FZ)
    (code untested, you'll get the idea)

    Albannach is right of course. Still, if you have quite some data, a hash-lookup is more efficient. And more scalable, as you can tie your hash onto a database, e.g. To prevent that double-count problem, add a check in for the key, if a double, add a counter or something.

    $key .= ':001' if defined $hash{$key}; $key++ while defined $hash{$key};
      Note that in your option 3 the unique keys of the hash will be likely to clobber some data as the anonymonk specified that the first value is price in his problem. Hashes are great for many reasons but this example is easily and simply addressed by a simple numerical sort so unless there is reason to need to search the data by price, and a certainty of unique pricing, I'll go with one of your first two options.

      I'd like to be able to assign to an luser

Re: Sorting an array of strings by number
by MZSanford (Curate) on Oct 01, 2001 at 16:01 UTC
    I would guess a custom sort black ... like the following test :

    @a = qw(39 45 120 139); @b = sort(@a); @c = sort { $a <=> $b } @a; print "B : ",join(',',@b),"\n"; # B : 120,139,39,45 print "C : ",join(',',@c),"\n"; # C : 39,45,120,139

    "They shall not overcome. Whoever told them that the truth shall set them free was obviously and grossly unfamiliar with federal law."
        -- John Ashcroft
Re: Sorting an array of strings by number
by Chady (Priest) on Oct 01, 2001 at 16:02 UTC

    force comparing when you sort :

    foreach $line (sort { (split /:/, $a)[0] <=> (split /:/, $b)[0] } @lis +t){ ...

    He who asks will be a fool for five minutes, but he who doesn't ask will remain a fool for life.

    Chady |
Re: Sorting an array of strings by number
by suaveant (Parson) on Oct 01, 2001 at 17:28 UTC
    Actually, you do not have to do the extra work of extracting the numbers from your strings if you do not want to. The way perl handles strings (IIRC) in a numeric context is it will take whatever it recognizes as a number off the front of the string, like in this example
    print('1.01:dfsfda'+'1.34:kjlkljkj',"\n"); # prints 2.35\n
    So, all you should need is the simple
    @sorted = sort { $a <=> $b } @data;

                    - Ant
                    - Some of my best work - Fish Dinner

      True. But I don't think anyone has mentioned that this will generate warnings. But you can turn them off around that part of the code:

      my @sorted= do { local( $^W )= 0; sort { $a <=> $b } @data; };

              - tye (but my friends call me "Tye")

        Or if you're using a 5.6 or newer perl you can turn off just warnings about the numericness (see perllexwarn for more information).

        my @sorted = do { no warnings 'numeric'; sort { $a <=> $b } @data ; };
Re: Sorting an array of strings by number
by broquaint (Abbot) on Oct 01, 2001 at 16:53 UTC
    How about this -
    @res = sort { int($a) <=> int($b) } <DATA>; print foreach(@res); __END__ 34:dsfalk:as4 2:asd:3425 67:dfa:srkwej9 98:erwvrklj:sek output - 2:asd:3425 34:dsfalk:as4 67:dfa:srkwej9 98:erwvrklj:sek
    This sorts the data according to the number in the first field, so pretty much what you're looking for right?


Schwartzian Transform, anyone?
by Fletch (Chancellor) on Oct 02, 2001 at 19:21 UTC
    @lines = ( q{23.40:hello:this:is:a:line}, q{123.38:this:is:another:line}, q{7.32895:yet:another:line}, ); @sorted = map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [ (split(/:/,$_))[0], $_ ] }@lines;

    Of course, if you're going to split things anyhow, it might make just as much sense to go ahead and split the whole line and just use that.

    @split = sort { $a->[0] <=> $b->[0] } map { [ split /:/ ] } @lines;

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://115828]
Approved by root
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (2)
As of 2018-01-20 23:40 GMT
Find Nodes?
    Voting Booth?
    How did you see in the new year?

    Results (227 votes). Check out past polls.