Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Sorting - lower to upper

by joule (Acolyte)
on Jul 15, 2004 at 15:36 UTC ( [id://374690]=perlquestion: print w/replies, xml ) Need Help??

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

I have a hash, %hash, with the following keys, in the following order:

$hash{'John'}
$hash{'Bob'}
$hash{'xavier'}
$hash{'alice'}

I would like to sort these keys according to the 'case-ness' of the first letter of the keys, such that the hash is re-sorted like the following:

$hash{'alice'}
$hash{'xavier'}
$hash{'Bob'}
$hash{'John'}

I've tried using various sort techniques mentioned in perldoc perlfunc, but none have worked.

Any suggestions?

Thanks for the help.

Replies are listed 'Best First'.
Re: Sorting - lower to upper
by Joost (Canon) on Jul 15, 2004 at 15:45 UTC
      ... map { tr/a-zA-Z/A-Za-z/; $_ } ...
      I'm always a bit wary of code that looks like that, especially when it doesn't have the big warning sign to the right that says:
      Warning
      This map alters its incoming arguments as well as generating the desired list. While it is safe in this particular usage, use caution when copying this to another segment of code.

      In other words, I'd have written that as:

      ... map { (my $x = $_) =~ tr/a-zA-Z/A-Za-z/; $x } ...
      which ensures that I get the desired output value without seriously mangling my input value as a side-effect.

      -- Randal L. Schwartz, Perl hacker
      Be sure to read my standard disclaimer if this is a reply.

Re: Sorting - lower to upper
by pelagic (Priest) on Jul 15, 2004 at 15:51 UTC
    use strict; use Data::Dumper; my %h = ( 'John' => 'P9111', 'Bob' => 4711, 'xavier' => '20040610', 'alice' => '20040610', ); my %sk = map ({$_, ($_ =~ m/^[a-z]/) ? 0 : 1} (keys %h)); print Dumper %sk; my @xk = (sort {$sk{$a} <=> $sk{$b} || $a cmp $b} keys %sk); print Dumper @xk; ___OUTPUT___ $VAR1 = 'Bob'; $VAR2 = 1; $VAR3 = 'alice'; $VAR4 = 0; $VAR5 = 'John'; $VAR6 = 1; $VAR7 = 'xavier'; $VAR8 = 0; $VAR1 = 'alice'; $VAR2 = 'xavier'; $VAR3 = 'Bob'; $VAR4 = 'John';

    pelagic

      Instead of using Dumper as you have:

      print Dumper %sk; print Dumper @xk;

      You might consider using it like so:

      print Dumper \%sk; print Dumper \@xk;

      You should find the output a bit more sensible.

        Yeah, thanks, that was actually a typo. I always use the reference with Dumper.

        pelagic
Re: Sorting - lower to upper
by hardburn (Abbot) on Jul 15, 2004 at 15:42 UTC

    with the following keys, in the following order

    No, you don't. Hashes don't have any defined order. If you want order, use an array.

    ----
    send money to your kernel via the boot loader.. This and more wisdom available from Markov Hardburn.

Re: Sorting - lower to upper
by shemp (Deacon) on Jul 15, 2004 at 15:54 UTC
    Heres a not very tight implementation, but it should make clear whats going on:
    { my %hash = ( 'Ask' => 1, 'Bob' => 1, 'Cat' => 1, 'amy' => 1, 'ben' => 1, 'can' => 1, ); print join "\n", sort case_sorter keys %hash; } sub case_sorter { my $first_a = substr $a, 0, 1; my $first_b = substr $b, 0, 1; if ( ($first_a eq uc($first_a)) && ($first_b eq uc($first_b)) ) { return $a cmp $b; } elsif ( $first_a eq uc($first_a) ) { return 1; } elsif ( $first_b eq uc($first_b) ) { return -1; } else { return $a cmp $b; } }
Re: Sorting - lower to upper
by ccn (Vicar) on Jul 15, 2004 at 15:54 UTC

    You can not have a sorted hash, but you can sort it's keys before use

    foreach ( sort { ord($a) <=> ord($b) } keys %hash ) { print "$_ => $hash{$_}\n" }
      Your comparison is backwards, the OP wants the lowercase first so you need:
      foreach ( sort { ord($b) <=> ord($a) } keys %hash ) { print "$_ => $hash{$_}\n" }
      Kudos though, much more elegant than the solution i posted!
      This works after switching the $a and $b.

      Thank you all for your kind help.

Re: Sorting - lower to upper
by shemp (Deacon) on Jul 15, 2004 at 16:44 UTC
    This question has really intrigued me, so sorry for the flurry of posts. I was thinking about extending the sort concept to include the same case concerns for all characters in the strings.

    The original question would lead to odd results when comparing 'Amy' to 'AMy', because 'AMy' would come before 'Amy', since 'M' comes before 'm'. So i was thinking, if lowercase comes before upper case on the first letter, why not for subsequent letters, so heres a neat sorter to deal with it:

    (my $rev_a = $a) =~ tr/A-Za-z/a-zA-Z/; (my $rev_b = $b) =~ tr/A-Za-z/a-zA-Z/; return $rev_a cmp $rev_b;
    It just exploits that we want normal string cmp(), except that same letter different case sorting is reversed.
Re: Sorting - lower to upper
by mifflin (Curate) on Jul 15, 2004 at 15:58 UTC
    How about this...
    erickn@isfe:/home/erickn> cat t use warnings; $hash{John} = ''; $hash{Bob} = ''; $hash{xavier} = ''; $hash{alice} = ''; for my $key (sort {ucfirst($b) cmp lcfirst($a)} keys %hash) { print "$key\n"; } erickn@isfe:/home/erickn> perl t alice xavier Bob John
      Nope, wont work. Just consider $a = 'amy', $b = 'Ask'. after the case conversion, its comparing then as they were originally, and the capital comes before the lower case.
Re: Sorting - lower to upper
by Roy Johnson (Monsignor) on Jul 15, 2004 at 17:27 UTC
    @keys = ((sort grep(/^[a-z]/, keys %hash)), (sort grep(/^[A-Z]/, keys +%hash)));

    We're not really tightening our belts, it just feels that way because we're getting fatter.
      That'll make duplicate copies of everything that doesn't begin with a letter. Maybe you want to narrow those regexen down a bit.

      -- Randal L. Schwartz, Perl hacker
      Be sure to read my standard disclaimer if this is a reply.


      update: Oops, confused [^a-z] with ^[a-z]. So yes, everything without a letter will be absent, not duplicated. Doesn't change that there's something wrong with this solution.
        Seems to me that it should leave out anything that doesn't begin with a letter. Given the specs, that may or may not be desirable.

        We're not really tightening our belts, it just feels that way because we're getting fatter.
        It seems to me that rather than duplicate all non-first-lettered-words (nice hyphenate don't you think?), it actually discards all these words. I mean, the first list sorts all words which start with a small-case and the second list sorts all the first-upper-cased-words.

        I would appreciate explanation.

        Update: It seems that my pertinent and correct remark was voted down at least two times. I wonder what that means.
Re: Sorting - lower to upper
by orderthruchaos (Scribe) on Jul 15, 2004 at 17:02 UTC
    Don't forget about the Schwartzian Transform:
    my %hash = qw/John 1 Bob 1 xavier 1 alice 1/; my @keys = map { tr/a-zA-Z/A-Za-z/; $_ } sort map { tr/a-zA-Z/A-Za-z/; $_ } keys %hash; print "@keys\n";
    Will give you
    alice xavier Bob John
        Taking merlyn's advice into account, I'll change my answer;-)

        --from above--

        Don't forget about the Schwartzian Transform:
        my %hash = qw/John 1 Bob 1 xavier 1 alice 1/; my @keys = map { (my $x = $_) =~ tr/a-zA-Z/A-Za-z/; $x } sort map { (my $x = $_) =~ tr/a-zA-Z/A-Za-z/; $x } keys %hash; print "@keys\n";
        Will give you
        alice xavier Bob John
        Sorry about that... this thread is growing too fast for me to keep up!

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (1)
As of 2024-04-25 05:51 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found