Recently, I was refactoring a CGI script at work. It contained a subroutine used to determine the default value for a dropdown list:
sub DefaultHashValue { my %h = @_; my %r = reverse %h; my @k = sort values %h; return $r{ $k[0] } }

Neat and short, I thought. But wait, what exactly does it do? We pick up the asciibetically first value and find the corresponding key. It took me some time to understand it (yes, I'm tough). Could this code be written in a more speaking way?

I'd probably write it differently:

sub sort_keys { my %h = @_; my @s = sort { $h{$a} cmp $h{$b} } keys %h; return $s[0] }

Our dropdowns vary in size from 2 elements to several hundreds. For pure curiosity (there were no speed problems), I benchmarked the solutions (see below). Interestingly, for lists over 50 elements, the original solution was faster.

It wasn't so hard to come with a winner. It's still readable, too:

sub min { my %h = @_; my $min = (keys %h)[0]; $h{$_} lt $h{$min} and $min = $_ for keys %h; return $min }

Which solution would you use and why? Or, would you use something else? Why? (I stayed with the original).

For the interested, the full testing and benchmarking code:

#!/usr/bin/perl use warnings; use strict; use Test::More; use Benchmark qw{ cmpthese }; sub orig { my %h = @_; my %r = reverse %h; return $r{ (sort values %h)[0] } } sub Sort { my %h = @_; return (sort { $h{$a} cmp $h{$b} } keys %h)[0] } sub min { my %h = @_; my $min = (keys %h)[0]; $h{$_} lt $h{$min} and $min = $_ for keys %h; return $min } # TEST for my $t ( { a => 'A', b => 'B', c => 'C' }, { abc => 'ABC', def => 'ABC' , xyz => 'XYZ' }, { 1 .. 1_000}, { map int rand 100, 1 .. 1_000 }, ) { my $o = orig(%$t); is Sort(%$t), $o, "onleline-$o"; is min(%$t), $o, "min-$o"; } done_testing(); # BENCHMARK for my $size (10, 100, 1_000, 5E4, 1E6) { my %h = map int rand 100, 1 .. $size; cmpthese(0, { orig => sub { orig(%h) }, sort => sub { Sort(%h) }, min => sub { min(%h) }, }); }
لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

Replies are listed 'Best First'.
Re: Default Dropdown Value
by Corion (Patriarch) on Oct 17, 2014 at 08:30 UTC

    From an UI perspective, I'm never happy with drop down lists with 30+ items. I prefer an autocomplete text entry with a dropdown list for manually choosing/inspecting the values (a "ComboBox" in Windows parlance).

    But implementing something like that involves Javascript, and I'm unaware of a nice library that takes a listbox / <select> and turns it into a ComboBox.

Re: Default Dropdown Value
by LanX (Saint) on Oct 17, 2014 at 09:20 UTC
    The first, but with clear self documenting variable names.

     return $inverted{ $values_asc[0] } # default key

    Part of the readability problem is that reverse stands for two different functions including an idiomatic trick to invert hashes.

    Highlighting that hash inversion was meant can't be wrong...

    Cheers Rolf

    (addicted to the Perl Programming Language and ☆☆☆☆ :)

    update

    Talking about performance, the first line is superfluous copying you could already reverse the arguments. :)

      you could already reverse the arguments
      Where would I get values %h then?
      لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
        @values_asc = sort keys %inverted

        :)

        Cheers Rolf

        (addicted to the Perl Programming Language and ☆☆☆☆ :)

Re: Default Dropdown Value
by Anonymous Monk on Oct 17, 2014 at 08:27 UTC

    Which solution would you use and why? Or, would you use something else? Why? (I stayed with the original).

    Well ... I can't tell what solution I would use to write the original, but once I wrote it and it did what it was supposed to, I wouldn't refactor for readability, I would add documentation describing the algorithm