Join the Stack Overflow Community
Stack Overflow is a community of 6.6 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

I have this set of code:

 use strict; 
 use warnings; 
 my %hash = ( 5328 => 'Adorable', 
              26191 => '"Giraffe"', 
              57491 => 'Is Very', 
               4915 => 'Cute',);
 foreach (sort { ($hash{$a} cmp $hash{$b}) || ($a cmp $b) } keys %hash) 
 { print "$hash{$_}\n"; }

this will result as:

"Giraffe"

Adorable

Cute

Is Very

I need it to be ordered alphabetically, and disregard the special character before the AlphaNumeric character like in this sample:

Adorable

Cute

"Giraffe"

Is Very

Any suggestions?

share|improve this question
1  
build a hash with unquoted strings as keys and sort the keys. You can also trim each value in the comparison. – Casimir et Hippolyte Jan 9 at 22:01
    
If the keys are numeric, you probably want to use <=> rather than cmp for the second-level sort. – Matt Jacob Jan 9 at 22:15
    
Your data set is wrong. You need to remove the quotes before they get anywhere near your hash. Are you dealing with CSV data perhaps? – Borodin Jan 9 at 22:28
    
Please clarify: is every alternate line in your data really blank? – Borodin Jan 9 at 22:29
    
What is the source of the data that has built your hash %hash (and please don't call things @array or %hash or $scalar). It should be rewritten. – Borodin Jan 9 at 22:44
up vote 7 down vote accepted

You can simply do that (note that I changed the second cmp to the shuttle operator <=> for numeric values):

foreach (sort { ($hash{$a}=~s/^\W+//r cmp $hash{$b}=~s/^\W+//r) || ($a <=> $b) } keys %hash) {
    print "$hash{$_}\n";
}

But if you have a lot of data, it's better to transform your data (once and for all): For example using a schwartzian transform:

my @result = map { $_->[2] }
             sort { ($a->[0] cmp $b->[0]) || ($a->[1] <=> $b->[1]) }
             map { [ $hash{$_}=~s/^"|"$//gr, $_, $hash{$_}] } keys %hash; 

print join "\n", @result;
share|improve this answer
4  
You might want to mention that you need Perl >= 5.14 to use /r. – Matt Jacob Jan 9 at 22:16
    
works like a charm, thanks! – Xavia Jan 10 at 14:06

Create a function which truncates special characters (truncate_special_chars in my case). Then, use it under your sort routine.

use strict;
use warnings;

my %hash = (
    5328 => 'Adorable',
    26191 => '"Giraffe"',
    57491 => 'Is Very',
    4915 => 'Cute',
    );


print join "\n", 
    map { $hash{$_ -> [0]} }
    sort { $a -> [1] cmp $b -> [1] || $a -> [0] <=> $b -> [0] }
    map { [ $_, truncate_special_chars($hash{$_}) ] }
    keys %hash;

sub truncate_special_chars {
    my $str = shift;
    $str =~ s/^\W//;
    # may be use lc if you want case insensitive sort
    return $str;
}

Otherwise you can use /r if you are using Perl >= 5.14.

share|improve this answer
    
You missed the case where $hash{$a} == $hash{$b}, it must be ordered by key. – Toto Jan 10 at 10:10
    
@Toto Edited. I guess, it would be trivial, If I carry key instead of value :). – Arunesh Singh Jan 10 at 10:49
    
I think you should do numeric comparison for the hash keys: sort { $a -> [1] cmp $b -> [1] || $a -> [0] <=> $b -> [0] }. – jreisinger Jan 10 at 11:32
    
@jreisinger Done. – Arunesh Singh Jan 10 at 11:34

Use a custom sort function that strips out the characters you want to ignore.

foreach (sort {
            my ($aa,$bb) = ($hash{$a},$hash{$b});
            s/["]//g for $aa,$bb; # ignore " char. Add whatever else you want
            $aa cmp $bb           # or lc($aa) cmp lc($bb) case-insensitive search
                || $a cmp $b
       } keys %hash) { ... }
share|improve this answer
    
You could include a Schwartzian transform to make it faster. Stripping out stuff all the time is expensive. – simbabque Jan 9 at 23:02
1  
Yes, a Schwartzian transform is worthwhile when the input is large and the transformations to the input before comparing are expensive. – mob Jan 9 at 23:09

Try as follows

use strict; 
use warnings; 
my %hash = ( 5328 => 'Adorable', 
      26191 => '"Giraffe"', 
      57491 => 'Is Very', 
       4915 => 'Cute',);

foreach (sort{my ($one)=$hash{$a}=~m/([\w\s]+)/; my ($two)=$hash{$b}=~m/([\w\s]+)/; $one cmp $two} keys %hash) 
{ 
    print "$hash{$_}\n"; 
}

Group the elements and store into $one and $two then compare it.

But it is very easy with @Casimir et Hippolyte answer. He used non-destructive modifier(r). but it will support above the 5.14 version

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.