Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Sorting strings

by rbi (Monk)
on Aug 30, 2001 at 15:55 UTC ( [id://109049]=perlquestion: print w/replies, xml ) Need Help??

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

Hi,
I have to sort arrays of time differences from some zero, like this one:
['-6h0m','+11h39m',+1h7m','-12h8m']
into
['+11h39m','+1h7m','-6h0m','-12h8m']
I wrote this routine. Is there a cleaner way to sort the array?
Thanks in advance
ciao, Roberto
############### sub sort_deltas { ############### my @deltas = @{ shift() }; my $delta; my %delta_tmp; for $delta (@deltas) { my $hm = substr($delta,1); my @tmp = split /h/, $hm; my $h = $tmp[0]; @tmp = split /m/, $tmp[1]; my $m = $tmp[0]; my $minutes = $h * 60. + $m; my $signum = substr($delta,0,1); if ($signum eq '-') { $minutes = - $minutes; } $delta_tmp{$minutes} = $delta; } my $tmp; my @tmp_abs; my @back; push @tmp_abs, sort numerically keys %delta_tmp; for $tmp (@tmp_abs) { push @back, $delta_tmp{$tmp}; } return reverse @back; }

Replies are listed 'Best First'.
Re: Sorting strings
by Hofmator (Curate) on Aug 30, 2001 at 16:24 UTC

    Well, the cleanest way to go is to use a Schwartzian transform, i.e. a map sort map trick as follows:

    my @unsorted = ('-6h0m','+11h39m','+1h7m','-12h8m'); my @sorted = map {$_->[0]} sort {$b->[1] <=> $a->[1]} map { /^([+-])(\d+)h(\d+)m$/; [$_, sprintf("%s%d",$1, $2*60+$3) ] + } @unsorted; print join ', ', @sorted;
    As you see, the extraction of hours and minutes can be greatly simplified with a regex.

    -- Hofmator

      Hofmator, could you explain (for this novice) what the map portions do in there? I would have to agree with dmmiller2k, very elegant, short, and to the point. Too bad for me I don't get the point though.
      I have one more request, take it easy on me, I write my perl like I wrote my Basic programs 14 years ago.
      TIA.
      -spartan

      Very funny Scotty... Now PLEASE beam down my PANTS!

        I think the for someone who is not very familiar with map, the Schwartzian Transform is a complete mystery. It took me seeing map in action to appreciate its usefulness. So, in the interest of opening a novice's eyes...

        The map function is one of Perl's more wonderous keywords. It's in my list of Perl Milestones of Enlightenment - mysterious at first, powerful yet simple in its action.

        map takes two arguments: an operation of some sort (no pun intended) like a single keyword or a code block (as is used in this case). And a list to operate on. The example given in the pod offers:@chars = map(chr, @nums);. Which takes the list of @nums and performs the chr function on them and assigns the new array to @chars. Or, rewritten with a code block: @chars = map {chr}, @nums; Look right to left, bottom to top, to see the progression. On to the routine...

        So what happens in Hofmator's implementation is every element in the list @unsorted goes through the code block of:

        /^([+-])(\d+)h(\d+)m$/; [$_, sprintf("%s%d",$1,$2*60+$3)]

        The regex splits up the string into +-, hours, and minutes, and the second half creates an anonymous array out of it: first element being the original string, $_, the second being the total minutes of the string.

        Sort does its magic, sorting on the second element (that's 1). No calculation of minutes needed - already done by the map.

        Then the map closest to the @sorted assignment takes the annonymous array, taking just the first element (the original string).

        There's your sorted list of time strings with the minute calculation performed only once per array element.

        HTH

        -Ducky

        Ok, you have read Schwartzian Transform, haven't you? And then I point you to the excellent FMTEYEWTK on sort at CPAN.

        I don't think you will need to - but feel free to get back to me if anything is still unclear after reading that.

        -- Hofmator

      Nice work. Very clean, elegant.

      As I was reading through the node and its replies, I was formulating just such an approach for my own reply.

      dmm

      Just call me the Anti-Gates ...
      
Re: Sorting strings
by ozone (Friar) on Aug 30, 2001 at 16:16 UTC
    you can use the clever form of sort:
    my @normalTimes = ('+1h6m', '0h13m', '-4h10m', '-6h5m'); my @sortedTimes = sort { getMinutes($b) <=> getMinutes($a) } @normalTi +mes; sub getMinutes { my($hour,$minute) = split(/[hm]/, shift); return( ($hour * 60) + (($hour > 0) ? $minute : -$minute)); }
    UPDATE: fixed typos & sub error (thanks Hofmator)

      Some things go wrong with your code

      • typos: substitute the braces after getMinutes with normal parentheses
      • getMinutes doesn't calculate correctly:
        -1h10m => -50 # correct -70 -1h20m => -40 # correct -80
        so the sorting is not correct.
      • You are calculating the getMinutes values more than once per entry. See my solution elsewhere in this thread for a way to cache these results.

      -- Hofmator

        I'd be hesitant to say that running that calculation more than once per value is something "wrong" with the code--as was pointed out to me by jeroenes, for sorting a small number of elements, the Schwartzian doesn't buy you anything significant (especially for a cheap calculation like this one). Of course, if N gets larger, then the benefits of using it increase, so ++ for posting it: I just don't think it's necessarily called for here.



        If God had meant us to fly, he would *never* have given us the railroads.
            --Michael Flanders

Re: Sorting strings
by spartan (Pilgrim) on Aug 31, 2001 at 00:41 UTC
    ++hofmator,++ducky... I now understand how map works. My next problem is this: map  {$_->[0]}. I know that map is operating on $_[0] (I think) but what is this: ->[0] ???. I read the FMTEYEWTK, and while Mr. Christiansen does a good job explaining, I must admit that I would rather have read a more exhaustive dissertation with more examples of what @alist, $a, @blist, $b, etc.. were set to. I found that I could understand some of the things Tom was explaining, and on others I just got lost in the code without knowing what the values of the variables he was operting in were. I guess I'm thinking very rigidly, because I can understand this:
    @single = 1..10; @treble = map { $_ * 3 } @single; foreach (@treble) { print $_." "; } print "\n";
    <rant>
    I can follow EXACTLY what's happening in that code snippet, because I know what @single is equal to each time the map function iterates over one of the values. The output is as follows:
    3 6 9 12 15 18 21 24 27 30
    However, when I tried following the code examples in FMTEYEWTK, I had a horrible time understanding what he was trying to convey. I know I won't know everything the moment I read it, and I intend to read FMTEYEWTK a few more times, but I have to say I'm a bit frustrated. I do know this: I'm not giving up. I might not understand this today, or tomorrow, but I will at some point.
    </rant>
    If anybody would like to take a shot at helping me understand, I would appreciate it GREATLY. Hopefully I can pass this (or other) knowledge along to someone else someday.
    TIA
    -spartan

    Very funny Scotty... Now PLEASE beam down my PANTS!
      My next problem is this: map {$_->[0]}. I know that map is operating on $_[0] (I think) but what is this: ->[0] ???.
      I think you need to get the idea of refrences to arrays and arrays. Consider
      my $array=[]; #$array is a refrence to an anonymous array my @array;
      Now if I need to get an element of @array directly I use this syntax:
      $array[1];
      but if I want to get it through the refrence i have to do
      $array->[1];
      The -> tells perl to treat the '$array' as the SCALAR with the name 'array' and not as another type with the same name, the [] tells perl that the scalar refers to an array and not a hash. The same thing applies to the variables $_ @_ %_

      So in the ST you wrap you data and your keys, one at a time, in anonymous arays (Notice the square brackets in the rightmost map body). Then SORT goes through each of the elements, which are refrences to arrays, and has to dereference them to get at the keys, finally the left most map takes of the anonymous array 'wrapper' and throws it away, returning the sorted list.
      BTW Maybe you know this, but for map/sort/grep/join its often a good idea to read them from right to left. (Or my preference from bottom to top)

      my @sorted=map {$_->[0]} sort{$a->[1] <=> $b->[1]} map {[$_,keyfunction($_)]} @list; my $nasty=join(",", map {$_->[0]} sort{$a->[1] <=> $b->[1]} map {[$_,keyfunction($_)]} grep {/^\w/} @list );

      HTH
      Yves
        but if I want to get it through the reference I have to do

        That should be:

        but if I want to use the array that $array refrences I have to do

        Sorry, I changed the example slightly and didnt change that line.

        Anyway, the point to keep in mind that

        $_=[]; # $_ now is a refrence to empty array (anonymous array) $_[0]=[]; # the zeroth element of the array @_ $_->[0]=[];# the zeroth element of the anonymous array $_{0}=[]; # the value associated with 0 in %_ $_{0}->[0]; # element zero in ^^^^^^^^ $_[0]->[0]; #zeroth element in array refrenced by the zeroth elemtn + of @_ $_->[0][0]={}; #dont need a second -> $_->{0}[0]={}; #this works too $_->[0]->[0]->{0}="but we can use -> if we want"; $_->{0}[0]{0}="This is getting confusing";
        I dont think %_ is used that much, but I could be wrong.

        Yves
        ++demrphq
        Egads...
        I need to do some homework. I still do not understand. Thank you so much for your explanation though, I just don't get it. :(
        -spartan

        Very funny Scotty... Now PLEASE beam down my PANTS!
(tye)Re: Sorting strings ("natural sort" w/ negatives)
by tye (Sage) on Aug 31, 2001 at 21:02 UTC

    Since I haven't done a version of my standard "natural sort" trick that handles negative numbers recently, here goes:

    my @deltas= qw( -6h0m +11h39m 0h2m +1h7m -12h8m ); @deltas= @deltas[ do { s#([-+]?\d+)# "\x80" ^ pack"N",$1 #ge for my @sort= @deltas; sort { $sort[$a] cmp $sort[$b] } 0..$#sort; } ]; print "@deltas\n";

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

      mmhhh, breaks on this my @deltas= qw( -1h0m -1h10m -0h50m );

      -- Hofmator

        Yes. Sorry, I should have realized that a "natural sort" doesn't handle that (the negative sign distributes across to the minutes field but the "natural sort" is not aware of this).

        Though you can still use a natural sort, you just have to process the leading sign separately. For example:

        my @deltas= qw( -6h0m -0h42m +0h18m -12h50m +11h39m 0h2m +1h7m -12h8m -0h25m 0h0m ); @deltas= do { my( @sort, %sort )= map { local($_)= $_; my $sign= s#^([-+]?)## && $1; s#(\d+)# "\x80" ^ pack"N",$sign.$1 #ge; $_; } @deltas; @sort{@sort}= @deltas; @sort{ sort @sort }; }; print "@deltas\n";

        Though just for fun I switched from an index sort to a key sort so this version drops identical values. (:

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

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://109049]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others learning in the Monastery: (3)
As of 2024-04-20 01:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found