Search code examples
perldictionary

Advice on Perl map function


I'm having difficulty understanding the behaviour of the map function in the Perl script below

my @list = qw/A C T G/;
my @curr = (0, undef, undef, 1);
my @res = map { $list[ $_ ] } @curr;
print @res; #prints AAAC

The value of @res is AAAC. I had expected it be just AC as the 2 middle values of @curr are undef.

My understanding of map is that each item from the list becomes the value of $_ in turn. So I can understand how $list[0] returns A and how $list[1] returns C. I can't understand why/how the undef values have returned A? To my mind $list[undef] would not be a value?

I'm missing something obvious here probably, but would be really grateful for some help

The script that I take this code from is below. I'm stepping through it in the debugger, and I can see this behaviour in the last line returned by sub gen_permutate

my @DNA = qw/A C T G/;
my $seq = gen_permutate(14, @DNA);

while ( my $strand = $seq->() ) {
print "$strand\n";

}

sub gen_permutate {
    my ($max, @list) = @_;
    my @curr;
    
    return sub {
        if ( (join '', map { $list[ $_ ] } @curr) eq $list[ -1 ] x @curr ) {
            @curr = (0) x (@curr + 1);
        else {
            my $pos = @curr; 
            while ( --$pos > -1 ) {
                ++$curr[ $pos ], last if $curr[ $pos ] < $#list; 
                $curr[ $pos ] = 0;
            }
        }
        return undef if @curr > $max;
        return join '', map { $list[ $_ ] } @curr; 
    };
}

J


Solution

  • This is a part of "DWIM" -- an array index need be a number, so what is passed in is converted to one, as best as the interpreter can do/guess. In case of undef at least it's clear -- becomes a 0.

    But it's really a programming error, or at least sloppiness.

    To do what you expect:

    my @res = map { $list[ $_ ] } grep { defined } @curr;
    

    or, in one iteration over the list

    my @res = map { defined $_ ? $list[$_] : () } @curr;
    

    Here the empty list, indicated by (), gets flattened into the return list, thus disappearing; so we filter inside map as well. But the point of that ternary is to be able to put something meaningful instead, and if some input need be just removed then having an explicit grep first is clearer.

    NOTE   With use warnings; in place you'd hear all about it

    Use of uninitialized value $_ in array element at ...

    (and it would convert it to a number)

    Each script really must begin with use warnings; (and use strict;). Sometimes the biggest part in inheriting older scripts is to stick use warnings; on top right away (and work through the screenfulls of messages :(.