in reply to Mini-Tutorial: Working with Odd/Even Elements

One of my favorite subs:

sub map_pairs(&@) { my $f = shift; my @res; no strict 'refs'; no warnings 'once'; my $caller = caller; local(*{$caller."::a"}) = \my $a; local(*{$caller."::b"}) = \my $b; push @res, $f->($a,$b) while ($a, $b) = splice @_, 0, 2; return @res; }

gives:

map_pairs { $a } qw/a b c d/; # ac map_pairs { $b } qw/a b c d/; # bd map_pairs { uc($a),lc($b) } qw/a b c d/; # AbCd

But, yes, this very much crosses the line out of "one-liner" land

Good Day,
    Dean

Replies are listed 'Best First'.
Re^2: Mini-Tutorial: Working with Odd/Even Elements
by ikegami (Pope) on Jul 09, 2009 at 23:26 UTC

    I was surprised by the lack of such a function in List::MoreUtils recently. There is natatime, but it returns an iterator rather than using a callback.

    I wonder how much speed you'd gain by replacing $f->($a,$b) with &$f. Another advanced feature for the collection!

    It would be a nice bonus if you $a and $b were aliases to the args like with map.

    sub map_pairs(&@) { my $cb = shift; my $caller = caller(); my $ap = do { no strict 'refs'; \*{$caller.'::a'} }; my $bp = do { no strict 'refs'; \*{$caller.'::b'} }; local *$ap; local *$bp; my @res; while (@_) { *$ap = \shift; *$bp = \shift; push @res, &$cb; } return @res; }
      Too golfy or arcane? (fixed now)
      sub map_pairs(&@) { my $fn = shift; my $pkg = $main::{caller().'::'}; map { @{$pkg}{qw(a b)} = \(@_[0,1]); $fn->(shift, shift); } (0..$#_/2); } package Smarter; our($a, $b) = qw(orig value); my @arr = qw(a b c d); print main::map_pairs {$_[0] = uc($a); print "[$a $_[1]]\n"; $a} @arr; print "\n"; print "Now @arr\n"; print "\n$a $b\n";
      I like being able to avoid sym refs and all the globbage. The interesting thing to note is that I seem to get a magical localization of my variables.

      Caution: Contents may have been coded under pressure.

        Too golfy or arcane?

        All of these map_pairs implementations are in the red zone of the arcanometer.

        The interesting thing to note is that I seem to get a magical localization of my variables.

        Array elements aren't lexical variables, if that's what you're talking about.

        As for using local on array elements, perlsub says

        Some types of lvalues can be localized as well : hash and array elements and slices, conditionals (provided that their result is always localizable), and symbolic references. As for simple variables, this creates new, dynamically scoped values.

        That means

        my @a; local $a[0]; # Not a problem my %a; local $a{k}; # Not a problem
        The interesting thing to note is that I seem to get a magical localization of my variables.
        that's the output I get
        [A b] [C d] AC Now A b C d C d
        "orig" and "value" are lost, so no "magical localization". but adding local
            local @{$pkg}{qw(a b)} = \(@_[0,1]);
        produces
        [A b] [C d] AC Now A b C d orig value

        Cheers Rolf

        UPDATE:This was perl, v5.10.0 built for i486-linux-gnu-thread-multi

        By the way, your fixed is still broken. It only works if the calling package is one level away from the root. If the caller is Foo, it works. If the caller is Foo::Bar, it doesn't.

        Using a symbolic ref is simpler than using %::.

        my $pkg = do { no strict 'refs'; *{caller().'::'} };

      Actually, I like passing the arguments so that I can do things like this: (sum doesn't know to look at $a and $b)

      use List::Util qw/sum/; say for map_pairs \&sum, 1..10;

      I do like the aliasing bonus, but it seems to not work on hash keys:

      use YAML; my @array = ( foo_name => " Bob Smiley ", foo_age => " 32" ); map_pairs { $a =~ s/foo_//; s/^\s+//, s/\s+$// for $b; } @array; print Dump \@array; my %hash = ( foo_name => " Bob Smiley ", foo_age => " 32" ); map_pairs { $a =~ s/foo_//; s/^\s+//, s/\s+$// for $b; } %hash; print Dump \%hash;

      outputs

      --- - name - Bob Smiley - age - 32 --- foo_age: 32 foo_name: Bob Smiley

      Good Day,
          Dean

        It's not a problem with map_pairs. You'll notice the same with map and for.

        Hash keys aren't Perl variables (aren't an SV), so %hash can't possibly return an alias to them. It returns a copy.

        It could return something magical that would result in the keys being "changed", but it doesn't. One could make a tie implementation if one needed such a feature.

Re^2: Mini-Tutorial: Working with Odd/Even Elements
by LanX (Cardinal) on Jul 10, 2009 at 10:12 UTC
    local(*{$caller."::a"}) = \my $a; local(*{$caller."::b"}) = \my $b;
    Does this technique depend on the default "automatic declaration" of $a and $b as package-vars? (it's meant for sort {...})

    In other words: using $c for map_triples wouldn't be as easy..(?)

    Cheers Rolf

      It will be a problem if you are running under strict. However, if the caller sets up $c as a package variable it will work.

      sub map_triples(&@) { my $f = shift; my @res; no strict 'refs'; my $caller = caller; local(*{$caller."::a"}) = \my $a; local(*{$caller."::b"}) = \my $b; local(*{$caller."::c"}) = \my $c; push @res, $f->($a,$b,$c) while ($a, $b, $c) = splice @_, 0, 3; return @res; } use 5.010; use warnings; use strict; our $c; say for map_triples { $a + $b + $c } 1..12;

      Good Day,
          Dean

        hmm .. maybe an approach using the @_-Array (i.e. $_[0] instead $a and so on) is more scalable...

        Cheers Rolf

Re^2: Mini-Tutorial: Working with Odd/Even Elements
by Roy Johnson (Monsignor) on Jul 10, 2009 at 18:45 UTC
    Update: figured it out (simulthanks to Ikegami). Gotta reference the right package. Corrected solution:
    sub map_pairs(&@) { my $fn = shift; my $pkg = caller; map { my $idx = $_ * 2; no strict 'refs'; my ($a, $b) = local (${$pkg.'::a'}, ${$pkg.'::b'}) = (@_[$idx,$idx+1]); $fn->($a, $b); } (0..$#_/2); }
    Previous, erroneous solution follows.

    I think you made this harder than it needs to be. Isn't this equivalent?

    sub map_pairs(&@) { my $fn = shift; map { my $idx = $_ * 2; local ($a, $b) = @_[$idx,$idx+1]; $fn->($a, $b); } (0..$#_/2); }

    Caution: Contents may have been coded under pressure.

      That won't work if the callback was compiled into a different package than map_pairs. The parent's code fixes this (assuming the callback is always compiled into the same package as the caller).