in reply to I hate nested loops

To me, for idiomatic clarity I find the original nesting of for/foreach loops to be superior. The Algorithm::Loops method was initially less familiar to me, but once I put it to use it became very legible, and I imagine it would be even to someone who hadn't read the Algorithm::Loops documentation.

The slice method is a little less legible to me. I use slices all the time, but there's just something about how it plays out that makes it less than obvious at first glance what it going on. Reading through it a little more carefully, it's easy enough to understand, but it's not a segment of code that, to me at least, screams "This is what I do." However, it has the advantage of being the most computationally efficient solution, which I'll demonstrate in a moment.

For good measure I played with a nested map option too. I find it fairly easy to read; seeing the keyword map twice, and one buried inside of brackets tells me they're nested.

I was kind of hesitant to post this as it might motivate people jump to computational efficiency as a means of driving coding decisions, when maintainability and clarity probably ought to take the driver's seat unless there is a known issue. So look at the following from the perspective of already having determined that you've got a bottleneck.

use warnings; use strict; use Benchmark qw/cmpthese/; use Algorithm::Loops qw/NestedLoops/; use Test::More 'no_plan'; my @outer = ( 'aa' .. 'jj' ); my @inner = ( 'AA' .. 'JJ' ); my $count = 100; my $control = \&sliced; foreach my $compare ( \&for_each, \&nested_loops, \&mapped ) { is_deeply( $compare->( \@outer, \@inner ), $control->( \@outer, \@inner ), "Control matched." ); } cmpthese( $count, { foreach => sub{ my $res = for_each( \@outer, \@inner ) }, NestedLoops => sub{ my $res = nested_loops( \@outer, \@inner ) }, mapped => sub{ my $res = mapped( \@outer, \@inner ) }, sliced => sub{ my $res = sliced( \@outer, \@inner ) }, } ); sub for_each { my %hash; foreach my $out ( @{ $_[0] } ) { foreach my $in ( @{ $_[1] } ) { $hash{$out}{$in} = 1; } } return \%hash; } sub nested_loops { my %hash; NestedLoops( [ $_[0], $_[1] ], sub { $hash{ $_[0] }{ $_[1] } = 1; } ); return \%hash; } sub mapped { my %hash = map{ $_ => { map { $_ => 1 } @{ $_[1] } } } @{ $_[0] }; return \%hash; } sub sliced { my %hash; @{ $hash{$_} }{ @{ $_[1] } } = (1) x @{ $_[1] } for @{ $_[0] }; return \%hash; }

That produced the following results:

ok 1 - Control matched. ok 2 - Control matched. ok 3 - Control matched. Rate NestedLoops mapped foreach sliced NestedLoops 5.17/s -- -89% -91% -95% mapped 49.0/s 848% -- -19% -49% foreach 60.2/s 1065% 23% -- -37% sliced 96.2/s 1760% 96% 60% -- 1..3

Dave

Replies are listed 'Best First'.
Re^2: I hate nested loops
by BrowserUk (Patriarch) on Aug 11, 2011 at 18:38 UTC
    I was kind of hesitant to post this as it might motivate people jump to computational efficiency as a means of driving coding decisions,

    Once you put the code into a subroutine, all you need to do is give it a good name, say init2Dhash() or whatever makes sense in the context of the code, and what idiom is used inside the sub becomes irrelevant.

    At that point, you might as well go for the most efficient solution.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.