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);
}
-
Are you posting in the right place? Check out Where do I post X? to know for sure.
-
Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
<code> <a> <b> <big>
<blockquote> <br /> <dd>
<dl> <dt> <em> <font>
<h1> <h2> <h3> <h4>
<h5> <h6> <hr /> <i>
<li> <nbsp> <ol> <p>
<small> <strike> <strong>
<sub> <sup> <table>
<td> <th> <tr> <tt>
<u> <ul>
-
Snippets of code should be wrapped in
<code> tags not
<pre> tags. In fact, <pre>
tags should generally be avoided. If they must
be used, extreme care should be
taken to ensure that their contents do not
have long lines (<70 chars), in order to prevent
horizontal scrolling (and possible janitor
intervention).
-
Want more info? How to link
or How to display code and escape characters
are good places to start.