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

Dear monks,

I am a neophyte perl programmer and I request your wisdom on a problem that perplexs me.

My perl script parses /var/log/maillog and creates a hash using the message id as the key and the to/from as values thusly:

$mailarray{$msgid}{"from"}= $from if ($msgid, $from) = m/.*\[\d+\]: ([a-fA-F0-9]+): from=<(.*)>,/; $mailarray{$msgid}{"to"}= $mailarray{$msgid}{"to"} . " " . $to,/ if ($msgid, $to) = m/.*\[\d+\]: ([a-fA-F0-9]+): to=<(.*)>,/;

I can easily sort onthe $msgid key, but I now need to sort on the $from (or $to) value. The only wisdom I could find is on ordering by the $msgid key.

Can anyone enlighten this novice perl user?

Thank you.

Replies are listed 'Best First'.
Re: Sorting hashes on non-keys
by MarkM (Curate) on Feb 22, 2003 at 16:50 UTC

    The sort language construct accepts a code block argument for use as a custom comparison operator.

    Example:

    @list = sort {$b cmp $a} @list

    The above code segment sorts @list in reverse to what is normally expected ('z', 'y', 'x', ..., 'a'). This can be extended in many ways -- one of which is to sort $msgid based on a value derived from $msgid:

    @msgids = sort { $mailarray{$a}{'from'} cmp $mailarray{$b}{'from'} } keys %mailarray;

    You should read up on 'sort' using 'perldoc -f sort'. Good luck.

      Thanks for the example. I did read up on sort, but didn't realize that I could do it the way you mentioned.
Re: Sorting hashes on non-keys
by thelenm (Vicar) on Feb 22, 2003 at 16:59 UTC
    You want the msg ids sorted in order of their "from" or "to" values, right? If so, here's some example code to sort them based on their associated "from" values.
    my @sorted_ids = sort {$mailarray{$a}{"from"} cmp $mailarray{$b}{"from +"}} keys %mailarray;

    If your list is large, you may want to make this a bit faster with a Schwartzian Transform.

    Two minor things I noticed: I think you'll need to get rid of the ,/ after $to on the fourth line to get your code to run. Also, you're always adding a space to the "to" value even if the "to" value was previously empty. Instead, you may want to check to make sure there is already something there before adding a space separator, maybe like this (although it's kind of ugly and I'm not sure whether you need to worry about the value existing but being undefined, or being defined and empty):

    $mailarray{$msgid}{"to"} .= (exists $mailarray{$msgid}{"to"} and length $mailarray{$msgid}{"to +"} ? " " : ""). $to if ($msgid, $to) = ... # your regex here

    -- Mike

    --
    just,my${.02}

      > If your list is large, you may want to make this a bit faster with a Schwartzian Transform. <

      That went WAY over my head! :-)

      >Also, you're always adding a space to the "to" value even if the "to" value was previously empty.<

      Cool! Thanks! I was having problems with "to" being undefined (the compiler was giving me lots of errors). Hopefully this will let me clean up my code.

          > If your list is large, you may want to make this a bit faster with a Schwartzian Transform.

          That went WAY over my head! :-)

        Then one certain way of improving your Perl skills is to read up on it. :-) Figuring out the Schwartzian Transform probably shouldn't be your highest priority at the moment, but it'll help your understanding of sorting, arrays, and the infamous map.

        If you're interested, I strongly recommend japhy's excellent article Re: Helping Beginners (continued).

        --
        F o x t r o t U n i f o r m
        Found a typo in this node? /msg me
        The hell with paco, vote for Erudil!

Re: Sorting hashes on non-keys
by hv (Prior) on Feb 22, 2003 at 17:00 UTC

    sort can take a block of code as its first argument, which allows you to control what the sort order should be by providing a template of how you would compare two values $a and $b. The default comparison is { $a cmp $b } which compares two values as strings to sort them in ASCII (er, codepoint) order.

    So to get the right comparison block for your requirement, you need to construct code that assumes $a and $b have been provided with the items you are sorting (the hash keys in this case) and construct a block that compares the elements you need.

    Given a hash key in $a, the 'from' value will be in $mailarray{$a}{"from"}, likewise for $b, and this leads to the sort:

    @sortedkeys = sort { $mailarray{$a}{"from"} cmp $mailarray{$b}{"from"} } keys %mailarray;

    If you need greater speed, a complex sort comparator can benefit from the Schwartzian transform.

    Update: missed out "sort" :(

    Hugo
Re: Sorting hashes on non-keys
by Nkuvu (Priest) on Feb 22, 2003 at 17:02 UTC

    Whoosh. In the time it took me to type out what I meant and revise it, four other (better) solutions showed up. :)

    So I'll just keep my ugly code to myself, OK? :)