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

Hi,

Is there any way within Perl to use scalar names to access specific elements within an array.

Please note that I want this scalar to be updated as the array is updated.

For example, in FORTRAN, the code would look like

REAL*4 MYARRAY(80) REAL*4 FIFTHELE EQUIVALENCE (FIFTHELE,MYARRAY(5)) C SET 5TH ELEMENT TO 61 MYARRAY(5) = 61 C USE THE 5TH ELEMENT IN SOME EQUATION X = FIFTHELE-30 C X NOW EQUALS 31, AND MYARRAY(5) ALSO EQUALS 31.
PS. I'm am also trying to stick to using  use strict "vars" although I might give that up (maybe).

Thanks

Sandy

Quick thought

Would

$myvar = \$array[0]; $myothervar = $$myvar;
work??

Replies are listed 'Best First'.
Re: use simple scalar names to point to specific array elements
by BrowserUk (Patriarch) on Dec 05, 2003 at 20:45 UTC

    You probably should look at Lexical::Alias.

    The module appears to complain if you try to use it with a version of perl prior to 5.8.0, but there is a comment in the pod saying that thanks to a bug fix (by tye), it should now work with versions greater than 5.005.

    Presumable it would be a case of modifying the test if you are using an earlier version.

    Update: An example of use.

    P:\test>perl -MLexical::Alias my( @a, $x, $y, $z ); @a = 'A' .. 'Z'; alias( $a[23], $x ); alias( $a[24], $y ); alias( $a[25], $z ); print "$x : $y : $z\n"; ^Z X : Y : Z

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Hooray!
    Wanted!

      Sweet! Yet another module to make my code harder to understand :) One day I need to just sit down and start reading through the CPAN to find all of those odd little modules that I don't know about.

      Cheers,
      Ovid

      New address of my CGI Course.

      Looks like it might be exactly what I want. Thankyou.

      Sandy

Re: use simple scalar names to point to specific array elements
by Ovid (Cardinal) on Dec 05, 2003 at 20:42 UTC

    If you want named elements, use a hash instead.

    my %hash = ( myvar => 3, name => 'Ovid', ); print $hash{name};

    If the order of elements is important, you could use Tie::Hash::Indexed or something similar.

    In any event, I would be careful about wanting to alias one variable to another. This can result in "action at a distance" problems and those can be hard to debug.

    Finally, if you always know which index you wanted to access by name, you could try a constant:

    use constant NAME => 2; print $array[NAME];

    However, if you really, really want to be evil and alias a variable to an array element, then you can do the following. How this works is an exercise left to the reader :)

    #!/usr/bin/perl use warnings; use strict; my @array = qw( foo bar baz ); my $var = alias_to(\@array, 1); print $var; sub alias_to { $_[0]->[$_[1]] }

    Cheers,
    Ovid

    New address of my CGI Course.

      How this works is an exercise left to the reader :)

      Well, it doesn't actually work at all.

      $ perl -le 'sub alias_to { $_[0]->[$_[1]] } my @x = (1,2,3); my $z=ali +as_to(\@x,1); print $z; $z=42; print $x[1]' 2 2
      All it does is return the value of the array referenced in $_[0] at the index $_[1]. The aliasing of sub args isn't helping like you think it is.

      -sauoq
      "My two cents aren't worth a dime.";
      

        Silly me. Typing something from memory when I know how bad my memory is. Here's a correct version (which was cheerfully misremembered from Damian Conway):

        #!/usr/bin/perl use warnings; use strict; use Data::Dumper; use vars '@var'; my @array = qw( foo bar baz ); *var = alias_to(@array[1,2]); print Dumper \@var; $var[1] = 'silly me'; print Dumper \@array; sub alias_to { \@_ }

        Cheers,
        Ovid

        New address of my CGI Course.

Re: use simple scalar names to point to specific array elements
by sauoq (Abbot) on Dec 05, 2003 at 20:44 UTC
    $ perl -Mstrict -wle 'our(@x,$z); $x[3] = "foo"; *z=\$x[3]; print $z; +$z="bar"; print $x[3]' foo bar

    Note: This will work even if @x is a lexical, but the alias, $z, must be global as lexicals don't have symbol table entries. If you want lexical aliases, use Lexical::Alias as BrowserUk mentioned.

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: use simple scalar names to point to specific array elements
by diotalevi (Canon) on Dec 05, 2003 at 20:43 UTC
    use constant FOOBAR => 5; $array[ FOOBAR ] = 'something'; # OR... my $foobar = \ $array[ 5 ]; $$foobar = 'something';
Re: use simple scalar names to point to specific array elements
by bart (Canon) on Dec 05, 2003 at 22:48 UTC
    I don't think anybody has mentioned this here, so I'll tell you now that it's possible to create an alias to a scalar using a for/foreach loop. Of course, it will ever loop only once, so it's more or less how one could write a with statement in Perl.

    This will do what you ask for:

    for my $fifthele($myarray[5]) { # inside this loop body, $fifthele is an alias to $myarray[5] $fifthele = 123; }

    I wish Perl had a more appropriate keyword, then people would feel more at ease using this idiom, but let me assure you: it works fine, and it's 100% reliable.

      Foreach is a good solution as long as you don't assign the array as a whole like @myarray= map {$_, $_+1} @myarray;.

      I don't exactly know what the consequences of that are, but it's illegal, and perl can't catch it. update: Removed comma after map{whatever}

Re: use simple scalar names to point to specific array elements
by Zed_Lopez (Chaplain) on Dec 05, 2003 at 20:46 UTC

    You're on the right track. You can make a reference to a specific array element, and changes you make through the reference will be to the array element, but you have to do it by de-referencing the reference. In your example above, $myothervar just ends up with an independent copy of what's in $array[0].

    my @a = qw(a b c); my $var = \$a[1]; my $othervar = $$var; $othervar = 'x'; $$var = 7; print join ' ', $othervar, $$var, @a, "\n"; $a[1] = 'q'; print join ' ', $othervar, $$var, @a, "\n"

    prints

    x 7 a 7 c x q a q c

    And all this is fine under strict. It would also be possible with symbolic references, but that would fail under strict refs.

    But why do you want to do such a thing? It only seems to be begging for confusion and code that's hard to read and maintain.

    Updated: Heh. Rereading the original post and the replies, now I see why you wanted to do such a thing. ++Ovid for seeing beyond your literal question and giving an answer relevant to what you really wanted.

      I have to read in a whole host of columns from a data base. I want the variable names to be the same as the column names (for readability and maintainability).

      I was trying to create only one list within the code with all these column names defined. Then, if someone needs to enter a new column, appending to the list could then be used to create the proper 'SELECT' statement, and also modify the way the result was read.

      i.e. What I don't want:

      my $query = "SELECT abc def ghi jkl ....."; ... set up database stuff ... ($abc, $def, $ghi, $jkl ...) = @array;
      because then the next guy to maintain my code might goof up when they have to add (or move around) columns.

      So I set up an array with all the column names, and create the query (which is complex) using the column names.

      So far so good, I got that far. But now when I want to be able to create a list (or array) that can read in the result, and create new variables based on the list of columns.

      So, I decided to make things difficult for myself, and was fartin' around trying to construct some code that could use the pre-existing list of column names to construct the array assignment.

      Something like (I haven't tried this, i'm just trying to brain storm...)

      map {$_='$'.$_} (@var_list=@col_list) join (",",@var_list); (eval "$var_list") = @array;
      I'm guessing that "use strict vars" is going to be upset when I try to access $abc;

      However, before I started with that, I just sorta fell back into some of the joys of FORTRAN (yes fortran can be fun too).

      In my previous fortran code, one could read a line from a text file, using a single string or array. Then, use the individual fields as required. If the format of the input line changed, the only thing that needs to be changed is the equivalence statement.

      Any thoughts?

      I could always just hard code the variable and column names and be done with it, but I don't wanna. I can be very stubborn at times.

        Oh ick. Use a hash and read "Why it's stupid to `use a variable as a variable name" Part 1, 2, 3. You're in the same ballpark as symbolic references (except worse because you are using eval).
Re: use simple scalar names to point to specific array elements
by Sandy (Curate) on Dec 05, 2003 at 22:02 UTC
    To all,

    Thank you for your input. It is good to know that an 'alias' is possible, although I don't think I need it for my current problem.

    I know it was the question I asked because I had hoped it would solve my problem. Nonetheless, I am happy to learn all new things.

    My Problem:

    I wanted an easy way to ensure that the order of a list of columns read from database query could be modified, without any mistakes.

    I was worried that if someone changed the select statement, they might forget to change the statement that actually reads the data.

    My hopeful solution... create a single list of columns that would be used to construct the query statement, and be used to parse the query result.

    Condition to this hopeful solution... I still want to use 'use strict' because it will also prevent some muck-ups.

    I think I have got it!. See below

    #!/usr/bin/perl -w # ------------------------------------------ # PURPOSE: # - Test if I can make a program that reads # a db table. # Maintainability issue: Want to be able to easily # manipulate the order in which the columns # are read. # # To change order of which columns are selected/read # from db only requires changing @parm_cols. # # Robustness? If a column is added to @parm_cols # without predefining the variable, use strict will # complain if one tries to use that variable.? # # ------------------------------------------ use strict; # ------------------ # define my variables # ------------------ my ( $abc, $def, $ghi, $jkl ); my @parm_cols = (); my $map_to_array; # ----------------- # define columns and order to read them # Note order of columns not same as the # my definition above. # ----------------- @parm_cols = qw( abc ghi jkl def ); # --------------------- # define select statement # --------------------- my $select = 'SELECT '.join(",",@parm_cols); # --------------------- # define how to map array which # is result of query statement to db # to individual variables # --------------------- map {$_='$'.$_} (my @tmp = @parm_cols); $map_to_array = join (",",@tmp); # --------------------- # read db (for testing purpose, just set array) # --------------------- my @array = (4,3,2,1); # --------------------- # populate my already declared # variables # --------------------- eval "($map_to_array) = ".'@array'; # --------------------- # test to see if it works # --------------------- print "$abc $def $ghi $jkl\n"; exit;

    Thanks again,

    Sandy

      Why did you opt not to do something like
      my %db_results; my @parm_cols = qw( abc ghi jkl def ); my $select = 'SELECT '.join(',', @parm_cols); # Read db @db_results{@parm_cols} = (4,3,2,1); print join(' ', @db_results{@parm_cols}), "\n"; # Or index individual columns at will
      ?

      Your columns are specified once, so any changing happens in one place.


      The PerlMonk tr/// Advocate
        As my son would say

        s'cool!

        Thanks.

Re: use simple scalar names to point to specific array elements
by Anonymous Monk on Dec 05, 2003 at 20:49 UTC

    Grab the Lexical::Alias module. Example:

    #!/usr/bin/perl -w use strict; use Lexical::Alias; my @array = (1,2,3,4); my $alias; alias($array[2], $alias); $alias = 42; print "@array : $alias\n"; $array[2] *= 10; print "@array : $alias\n"; __END__ # output: 1 2 42 4 : 42 1 2 420 4 : 420