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

I've got an object method which digs into a hash of arrays of arrays and pulls out the last element of each stored array. Here's my code:
sub whatever { my $self = shift; # ... do stuff to set ar_data... # Slice out the last element of each stored array into a new array my @last_elements; push @last_elements, $_->[$#_] for @{$ar_data}; return \@last_elements; }
After grabbing some water, I walked back and realized that @last_elements is (uh-oh) a transitory variable. (and wouldn'tcha know it, I try to look up on the web why transitory variables are bad, and I can't find anything. Dang; I'm sure dominus wrote about this somewhere. Or maybe this is hard water I'm drinking.) So, with a vague idea that some variables are "good" and others are "bad", I wanted to re-write my method to just return the list of @last_elements without actually storing into a list. (Sidenote: or is this foolish? Am I just inviting bogarts into my code?)

After a bit of toying, I realized that there are other ways to iterate over a list than by using for. Enter sort, map, and grep.

#!/usr/bin/perl use strict; use warnings; # fill the 2-d array my @data; for ( 0..2 ) { push @data, [qw 'a bc def' ]; } # try extracting last element from each stored array my @for_data = slice_with_for ( @data ); my @map_data = slice_with_map ( @data ); my @sort_data = slice_with_sort ( @data ); my @grep_data = slice_with_grep ( @data ); # show the y-axis slices print "for : @for_data\n"; print "map : @map_data\n"; print "sort: @sort_data\n"; print "grep: @grep_data\n"; exit; sub slice_with_for { my @slice_me = @_; my @last_fields; push @last_fields, $_->[2] for @slice_me; @last_fields; } sub slice_with_map { my @slice_me = @_; return map { $_->[2] ? $_->[2] : () } @slice_me; } sub slice_with_sort { my @slice_me = @_; sort { $a->[2] cmp $b->[2] } @slice_me; } sub slice_with_grep { my @slice_me = @data; grep { $_->[2] } @slice_me; } exit;
for : def def def
map : def def def
sort: ARRAY(0x80fad5c) ARRAY(0x810c844) ARRAY(0x810c7c0)
grep: ARRAY(0x80fad5c) ARRAY(0x810c844) ARRAY(0x810c7c0)

My questions:
1. What other ways are there to iterate over the list besides for, sort, map, and grep?
2. Why don't my slice_with_grep and slice_with_sort functions return what I'm expecting?
3. Am I drunk, or is the water? That is, have I invented this whole notion of a "transitory variable", or is it really something that exists (albeit momentarily) and should be avoided?

setenv EXINIT 'set noai ts=2'

Replies are listed 'Best First'.
Re: seeking different ways to slice a 2-d array
by stephen (Priest) on Apr 25, 2002 at 03:34 UTC

    A transitory variable is a variable that isn't used for anything other than to schlep data from one line to another. (I'm not sure the term is right, but I think it's what you mean. I call 'em "useless variables", myself.) In other words, if I'm going to say:

    my $foo = get_foo(); do_something($foo);
    Then $foo is a transitory variable. It would be just as simple, and less error-prone, to say:
    do_something( get_foo() );

    Why? Because variables can and should vary. It doesn't seem like such a big deal when the assignment and the use of the variable are right next to each other, but what if they get separated?

    my $foo = get_foo(); if ($snargle = 3 && $foo = 2) { whatever($snargle, $foo); # lots of other stuff here... } do_something($foo);

    Oops, on line 2 up there we changed the value of $foo. Also, it makes refactoring harder-- what if you wanted to pull out the stuff inside the if statement into its own subroutine? It'd be simpler if you didn't have $foo there at all, and just called get_foo() when you needed a foo-value.

    In your code, @for_data, @map_data, @sort_data, and @grep_data are all transitory variables. Ironically, @last_elements is not... you're using it to collect elements! Nothing wrong with that.

    As for why slce_with_sort() and slice_with_grep() aren't doing what you expect, it's because sort and grep don't do what you apparently think they do. :) The block argument to grep is a test block, not a filter block. So the statement

    grep { $_->[2] } @slice_me;
    means, "If the item at index 2 of @$_ evaluates to true, return $_." The last bit is important-- it doesn't return the item at index 2, it returns the whole thing. A similar thing is happening with sort. You're sorting each row based on the value of the last column, and returning a list of the rows. Since the values are identical, it doesn't even change their order. :)

    So where did you get that water? Sounds like powerful stuff. :)


Re: seeking different ways to slice a 2-d array
by Fletch (Bishop) on Apr 25, 2002 at 02:18 UTC

    ITYM `lexically scoped variable', not transitory. And you're probably thinking of mjd's Coping with Scoping.

      As I learn more about Perl and programming in general, I find myself re-reading articles and saying, "Ohhh.... so that's what it means...". Case in point: Coping with Scoping. Thanks for the pointer.

      Dropping off to sleep last night I realized that the article I was thinking of was a Red Flags article on The actual term mjd used was synthetic variables although in a different Red Flags article he talks about synthetic code

      setenv EXINIT 'set noai ts=2'

      I'd like to read that again when it's updated to describe 'our'. I use 'our' all the time but the cross-over between the previously totally unrelated realms of lexical and package variables is still a bit vague in my mind.


        I am very curious to know why you use our so much. I have never had a reason to use it myself because i tend to use OO a lot.

        I recently had a big revelation at (jeffa) 3Re: Whether to use local(), hopefully this post will remove the vagueness from your mind as well.

        Oh $#!^ - you are Sam Tregar. Scratch that last sentance! :O


        (the triplet paradiddle with high-hat)
(jeffa) Re: seeking different ways to slice a 2-d array
by jeffa (Bishop) on Apr 25, 2002 at 14:19 UTC
    I always found splice to be a good way to slice an array. Here is an example that you can play with. I am assuming that you always want the last element from each inner array.
    use strict; use Data::Dumper; my @arry = ( [qw(one two three)], [qw(foo bar baz)], ); my @new; push @new, splice(@$_,$#$_,1) for @arry; print Dumper \@new;
    The third argument of '1' for splice is not necessary in this case, but i recommend keeping it in case you decide to remove only one element from any other position. ;)

    Hope this works for you.


    (the triplet paradiddle with high-hat)
      I am assuming that you always want the last element from each inner array.

      Here is an example that you can play with....Hope this works for you.
      Yes, it does - thanks for the new technique

      setenv EXINIT 'set noai ts=2'