Brethren--

I'm lazy. So whenever I need to sort a hash, I don't want to have to think about the mechanics of sorting it...I just want it done.

I've been able to get away with just needing to sort one column/field in my hashes but recently needed to be able to sort on multiple columns so I modified my hash sorting subroutine to the below code after pilfering the idea (using "or" to string together multiple sort criterias) presented by lhoward in this node and after getting some great help at this node.

Hope this can help some other lazy monks.

mdog

$hash{'Me'}->{"name"} = "Me"; $hash{'Me'}->{"number"} = "7.7"; $hash{'Chuck'}->{"name"} = "Chuck"; $hash{'Chuck'}->{"number"} = "7.7"; $hash{'Zed'}->{"name"} = "Zed"; $hash{'Zed'}->{"number"} = "7.7"; $hash{'Wife'}->{"name"} = "Wife"; $hash{'Wife'}->{"number"} = "7.6"; $hash{'Dad'}->{"name"} = "Dad"; $hash{'Dad'}->{"number"} = "53"; $hash{'Michael'}->{"name"} = "Michael"; $hash{'Michael'}->{"number"} = "24"; my @sortedKeys = SortHashByMultipleColumns(\%hash,["number:asc","name: +asc"]); foreach my $sortedKey(@sortedKeys){ print $hash{$sortedKey}->{'number'} . " " . $hash{$sortedKey}->{'n +ame'} . "\n"; } sub SortHashByMultipleColumns{ my($hashRef,$sortInfoAR) = @_; my $sortCriteria; foreach my $sortInfo(@$sortInfoAR){ my($sortColumn,$sortDirection) = split(/\:/,$sortInfo); my $sortType; # VERIFY THAT THIS IS A VALID COLUMN if(! defined $hashRef->{((keys %$hashRef))[0]}->{$sortColumn}) +{ print "SortHashByMultipleColumns Error: $sortColumn is not + a column in this hash.\n"; exit(0); # VALID COLUMN, FIGURE OUT IF IT IS A NUMERIC COLUMN OR ALPHA } else { if($hashRef->{((keys %$hashRef))[0]}->{$sortColumn} =~ /^\ +d+/){ $sortType = "numeric"; } else { $sortType = "alpha"; } } # want to sort a number if($sortType eq "numeric"){ # sort it asc if($sortDirection =~ /asc/i){ # add an or if we already have something in the sort c +riteria if($sortCriteria){ $sortCriteria .= qq| or |; } $sortCriteria .= '$hashRef->{$a}->{\'' . $sortColumn . + '\'} <=> $hashRef->{$b}->{\'' . $sortColumn . '\'}'; # sort it desc } else { # add an or if we already have something in the sort c +riteria if($sortCriteria){ $sortCriteria .= qq| or |; } $sortCriteria .= '$hashRef->{$b}->{\'' . $sortColumn . + '\'} <=> $hashRef->{$a}->{\'' . $sortColumn . '\'}'; } # want to sort it by alpha } else { # sort it asc if($sortDirection =~ /asc/i){ # add an or if we already have something in the sort c +riteria if($sortCriteria){ $sortCriteria .= qq| or |; } $sortCriteria .= '$hashRef->{$a}->{\'' . $sortColumn . + '\'} cmp $hashRef->{$b}->{\'' . $sortColumn . '\'}'; # sort it desc } else { # add an or if we already have something in the sort c +riteria if($sortCriteria){ $sortCriteria .= qq| or |; } $sortCriteria .= '$hashRef->{$b}->{\'' . $sortColumn . + '\'} cmp $hashRef->{$a}->{\'' . $sortColumn . '\'}'; } } } my $sortfunc = eval "sub { $sortCriteria }"; my @sortedKeys = sort $sortfunc keys %$hashRef; return @sortedKeys; }

Replies are listed 'Best First'.
•Re: Sort Multidimensional Hash By Multiple Columns
by merlyn (Sage) on Aug 06, 2004 at 19:18 UTC
      merlyn,
      I am likely wrong, but I don't see mdog's code being a reinvention of Sort::Fields. The code is designed to arbitrarily sort the keys of a multi-dimensional hash based off user supplied key names. It also sorts ascending or descending based off user input and auto-magically detects numerical versus ASCIIBetical sorting.

      What mdog has provided is not very flexible. It assumes a specific structure. I was going to try and make it more generic, but gave up after only duplicating the fuctionality. Why you might ask - besides getting bored, look at the atrocity:

      #!/usr/bin/perl use strict; use Tie::Hash::Sorted; use Scalar::Util 'looks_like_number'; my @keys = qw(name number); tie my %hash, 'Tie::Hash::Sorted'; @{ $hash{$_} }{ @keys } = ($_, '7.7') for qw(Me Chuck Zed); @{ $hash{Wife} }{ @keys } = qw(Wife 7.6); @{ $hash{Dad} }{ @keys } = qw(Dad 53); @{ $hash{Michael} }{ @keys } = qw(Michael 24); sub Build_Sort { my $hash = shift; my (@col , @ord); $_ % 2 ? (push @ord , $_[$_]) : (push @col , $_[$_]) for 0 .. $#_; my $sort_it = sub { for ( 0 .. $#col ) { if ( $ord[$_] eq 'dsc' ) { if ( looks_like_number( $hash->{$a}{$col[$_]} ) && loo +ks_like_number( $hash->{$b}{$col[$_]} ) ) { next if ($hash->{$b}{$col[$_]} <=> $hash->{$a}{$co +l[$_]}) == 0; return $hash->{$b}{$col[$_]} <=> $hash->{$a}{$co +l[$_]}; } else { next if ($hash->{$b}{$col[$_]} cmp $hash->{$a}{$co +l[$_]}) == 0; return $hash->{$b}{$col[$_]} cmp $hash->{$a}{$co +l[$_]}; } } else { if ( looks_like_number( $hash->{$a}{$col[$_]} ) && loo +ks_like_number( $hash->{$b}{$col[$_]} ) ) { next if ($hash->{$a}{$col[$_]} <=> $hash->{$b}{$co +l[$_]}) == 0; return $hash->{$a}{$col[$_]} <=> $hash->{$b}{$co +l[$_]}; } else { next if ($hash->{$a}{$col[$_]} cmp $hash->{$b}{$co +l[$_]}) == 0; return $hash->{$a}{$col[$_]} cmp $hash->{$b}{$co +l[$_]}; } } } }; return sub { my $h = shift; [ sort $sort_it keys %$h ]; }; } tied( %hash )->Sort_Routine( Build_Sort(\%hash, number => 'dsc', name +=> 'asc') ); print "$hash{$_}{name}\t$hash{$_}{number}\n" for keys %hash;

      Cheers - L~R

        Yeah, it's not super portable but for my intents/purposes it works great and I don't have to install any modules to get it to do what I want.

        If anyone gets any value out of seeing the code (the "or" bit was new to me), then I'm happy. Just trying to give back a little to this great site!