http://qs321.pair.com?node_id=216152

Well, it got to be that for a web interface, I needed to generate tons of tables, where the user could re-sort lists based on values they click on, and that the values stack up (but other configuration options could cause them to change the order, reverse, etc.). Rather than deal with static (or poorly dynamic) sort routines, here's to constructing expressions for sort() on-the-fly.

The data retrieved from the DB is stored in a 2-d hash with each top-level key being the record #, and having keys that represent 'columns' of data below each of those. So, with that in mind -- here's the (simple) solution to doing dynamic sorts on column-based data. A sub that accepts the columns to sort by, in order, as an arrayref, and then constructs the expression for sort, then executes it for you.

sub is_num_col { # do something to look up whether or not # a given column is numeric or # alphabetic. I use a hash for lookup, # e.g.: return($hash{"$_[0]"}); # # should return 1 if the column is # numeric, 0 otherwise. } sub sort_hash { # this sub creates a sort expression on-the-fly # given an array reference containing the keys # (column names) to sort a 2-d hash by. The 2-d # hash should be provided by reference as the second # argument to the sub. # # The 2-d hash model is used in that the first # dimension referrs to a record (or row), and the second # dimension to the columns within that record. # # An array reference is returned, where each element # is a key from hashref, in the order returned from the sort # # If you wish to reverse the sort order (descending) for # a column/key, simply add 'r-' to the column name. # # example: # # my $aRef = sort_hash(['id','r-name','type'],\%hash); # # Where it will first sort the hash by the id key, then # (descending) by the name key, and then by the type key. my $aRef = shift; my $hRef = shift; die("[sort_hash] Not enough Arguments!\n") if(!defined($aRef) || !def +ined($hRef)); die("[sort_hash] Incorrect Arguments!\n") if(ref($aRef) ne 'ARRAY' || + ref($hRef) ne 'HASH'); my @tests; foreach my $column (@{ $aRef }) { next if(!defined($column)); my $rev = 0; if($column =~ /^r-(.*)$/) { $rev = 1; $column = $1; } if( is_num_col($column) ) { # if this is a numeric column... if($rev == 1) { # descending push(@tests,"\$hRef->{\"\$b\"}{'$column'} <=> \$hRef->{\"\ +$a\"}{'$column'}"); } else { #ascending push(@tests,"\$hRef->{\"\$a\"}{'$column'} <=> \$hRef-> +{\"\$b\"}{'$column'}"); } } else { # if this is an alphabetic column... if($rev == 1) { # descending push(@tests,"\$hRef->{\"\$b\"}{'$column'} cmp \$hRef-> +{\"\$a\"}{'$column'}"); } else { #ascending push(@tests,"\$hRef->{\"\$a\"}{'$column'} cmp \$hR +ef->{\"\$b\"}{'$column'}"); } } } #end foreach # now, create our actual expression via join... my $sort_expr = join(' || ',@tests); # we create an anonymous subref, as doing an eval inside of # the sort's BLOCK can occaisonally cause problems, especially # nested way down in some module. my $SortSub = sub { return(eval $sort_expr); }; my @sorted = sort { &$SortSub } keys(%{ $hRef }); return(\@sorted); }