Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

How to create variables for each hash key that has a value.

by Perl300 (Friar)
on Jun 26, 2019 at 20:12 UTC ( [id://11101990]=perlquestion: print w/replies, xml ) Need Help??

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

Hello Monks,

I get values from front end in a hash. What I want to do is create variables (array or scalar) only for those hash keys that are not empty.

Also these array or scalar variables generated should NOT have scope limited to a loop. They should be available anywhere in program.

Is it possible to do it?

What I am doing right now is creating fixed number of variables for all the known keys. But the number of keys sent by front end might change and I won't be able to handle that. I want to be able to create variables by checking the keys and their values.

#!/opt/apps/perl/perl5261/bin/perl use strict; use warnings; my %params = { 'default-0' => ['lakja', 'haljl', 'alka'], 'default-1' => 'abc', 'default-2' => [1, 54, 83, 23], 'default-3' => [], 'default-4' => '', 'default-5' => '2019-06-26 00:00:10', }; my @arr_0 = @{ $passedParameters{'default-0'} } if defined($passedParameters{'default-0'}); my $scal_1 = $passedParameters{'default-1'} if defined($passedParameters{'default-1'}); my @arr_2 = @{ $passedParameters{'default-2'} } if defined($passedParameters{'default-2'}); my @arr_3 = @{ $passedParameters{'default-3'} } if defined($passedParameters{'default-3'}); my $scal_4 = $passedParameters{'default-4'} if defined($passedParameters{'default-4'}); my $date_5 = $passedParameters{'default-5'} if defined( $passedParameters{'default-5'});

Replies are listed 'Best First'.
Re: How to create variables for each hash key that has a value.
by shmem (Chancellor) on Jun 26, 2019 at 23:06 UTC
    What I am doing right now is creating fixed number of variables for all the known keys. But the number of keys sent by front end might change and I won't be able to handle that. I want to be able to create variables by checking the keys and their values.

    Hashes were created to avoid the creation of variables; instead of that a container is made which holds all "variables" as in a book.

    Perl variables - non-lexical variables, that only apply for a given scope - aren't much different in that respect, since they are identifiers stored in a symbol table which - suprise! - also is a hash. Each identifier is connected to a hash which has slots for each type: SCALAR, ARRAY, CODE, GLOB and so on.

    use vars qw($foo); $foo = "bar"; # assign the value 'bar' to variable $foo ( +package global) print "$foo\n"; # prints bar print "${*foo{SCALAR}}\n"; # also prints bar! same variable ${*foo{SCALAR}} = 'quux'; # assign a different value print "$foo\n"; # prints 'quux', value has beeen changed

    Instead of messing with variables created from keys and possible side effects, it is much more convenient to have them easily controlled in a container. Use e.g. $v{foo} instead of $foo. This is only slightly more typing, but enables you to control passed values via an array which holds the names of the variables you request to be able to create:

    my @known_params = qw(foo bar baz); # of these you know # this is a mockup of the passedParameters hash my %passedParameters = ( foo => 'oil', bar => 'lantern', baz => 'match', quux => 'half pig', ); my %v = validateParams(%passedParameters); print "valid parameters are:\n"; print "$_ => $v{$_}\n" for keys %v; sub validateParams { my %args = @_; my %valid; for my $key (@known_params) { $valid{$key} = delete $args{$key}; # remove from input } if (keys %args) { # any keys left which we don't know? for my $key (keys %args) { warn "unknown parameter '$key' passed, value = '$args{$key +}'\n"; } } return %valid; } __END__ unknown parameter 'quux' passed, value = 'half pig' valid parameters are: baz => match foo => oil bar => lantern

    The scope of the hash variable %v (in the example, go find a better name) is entirely up to you. You can make it into a lexical, a package variable at the main level, or a global (read perlvar).

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
Re: How to create variables for each hash key that has a value.
by AnomalousMonk (Archbishop) on Jun 27, 2019 at 02:55 UTC
Re: How to create variables for each hash key that has a value.
by GrandFather (Saint) on Jun 26, 2019 at 21:00 UTC

    Why? What is the bigger goal? Do you know that a hash value can be anything, including a reference to another (nested) hash or a reference to an array?

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: How to create variables for each hash key that has a value.
by GrandFather (Saint) on Jun 26, 2019 at 22:45 UTC

    You still haven't actually said what you are trying to do, but reading between the lines it seems you want to be able to run code to handle an entry in a hash passed to your code from some front end code dealing with fields (sounds a little like handling a web page to me). If that is the case then consider:

    use strict; use warnings; my %dispatch = ( 0 => \&HandleParam0, 1 => \&HandleParam1, 2 => \&HandleParam2, 3 => \&HandleParam3, 4 => \&HandleParam4, 5 => \&HandleParam5, ); my %params = ( 'default-0' => ['lakja', 'haljl', 'alka'], 'default-1' => 'abc', 'default-2' => [1, 54, 83, 23], 'default-5' => '2019-06-26 00:00:10', ); for my $key (sort keys %params) { next if $key !~ /^default-(\d+)/ || !exists $dispatch{$1}; $dispatch{$1}->($params{$key}); } sub HandleParam0 { my ($values) = @_; print "default-0: @$values\n"; } sub HandleParam1 { my ($str) = @_; print "default-1: $str\n"; } sub HandleParam2 { my ($values) = @_; print "default-2: @$values\n"; } sub HandleParam3 { my ($values) = @_; print "default-3: @$values\n"; } sub HandleParam4 { my ($str) = @_; print "default-4: $str\n"; } sub HandleParam5 { my ($date) = @_; print "default-5: $date\n"; }

    Prints:

    default-0: lakja haljl alka default-1: abc default-2: 1 54 83 23 default-5: 2019-06-26 00:00:10
    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

      An interesting twist is to dispense with the dispatch table:

      use strict; use warnings; my %params = ( 'default-0' => ['lakja', 'haljl', 'alka'], 'default-1' => 'abc', 'default-2' => [1, 54, 83, 23], 'default-5' => '2019-06-26 00:00:10', 'default-10' => 'Nothing to see here', ); for my $key (sort keys %params) { next if $key !~ /^default-(\d+)/; my $sub = main->can("HandleParam$1"); if ($sub) { $sub->($params{$key}); } else { warn "Don't know how to handle 'default-$1'"; } } ...

      which prints:

      default-0: lakja haljl alka Don't know how to handle 'default-10' at noname1.pl line 20. default-1: abc default-2: 1 54 83 23 default-5: 2019-06-26 00:00:10

      where the second line is a warning line. Using this technique means you only need to create correctly named handler subs to add support for new fields and you get told if there is a missing handler. Both features make maintenance much easier!

      Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: How to create variables for each hash key that has a value.
by dsheroh (Monsignor) on Jun 27, 2019 at 07:23 UTC
    These are fields that will/won't be sent from front end. My code for each of these fields is in separate if(@arr_0), if($scal_1) blocks. So if any of these variables don't exist, the related code will not execute.
    First rule of backend programming: Never trust the front end.

    You've already gotten the standard references to why symbolic references ("using a variable as a variable name") are bad ju-ju - error-prone, hard to maintain, inexplicable action-at-a-distance, and so on - but there's another piece which is very relevant to this sort of situation that hasn't been brought up yet: It allows the remote client to overwrite any (non-lexical) variable, not just the ones you've set up if blocks around.

    For example, I imagine your application does some kind of access control, since you probably need to prevent random anonymous users from changing things they shouldn't. Let's say, for the sake of discussion, that your code uses a global variable named $authenticated_user to keep track of what user is logged in. Now, what happens when someone connects to your application using software they control (i.e., not the front end program that you intended them to use) and sends your server a list of parameters that includes authenticated_user=administrator?

    I'll tell you what happens. Your hash-to-variables routine sees "there's a hash key called authenticated_user, so I'll set the value of $authenticated_user to the value of that hash key" and, boom, you've just handed admin access to some rando who probably doesn't even have a legitimate user account in the first place.

    Just say no to creating variables based on user input. Users can't be trusted with that kind of power.

Re: How to create variables for each hash key that has a value.
by LanX (Saint) on Jun 26, 2019 at 23:18 UTC
    your whole concept is dubious, but you may want to play around with this code
    use strict; use warnings; use Data::Dump qw/pp dd/; $\="\n"; my @var_list; use vars (@var_list = qw/$s1 $s2 $s3 @a1 @a2 @a3/); my %hash = ( s1 => 1, s2 => 2, a1 => [1,2,3], a2 => [], ); { no strict 'refs'; for ( @var_list ) { my ($sigil,$name) = m/^([\$@])(.*)$/; #warn $name; if ($sigil eq '@') { *{$name} = exists $hash{$name} ? $hash{$name} : []; } if ($sigil eq '$') { *{$name} = exists $hash{$name} ? \ $hash{$name} : \ undef } } } # test for my $var ( @var_list) { print "--- $var"; eval "print \"\$var = <$var> isn't false\" if $var"; }

    --- $s1 $s1 = <1> isn't false --- $s2 $s2 = <2> isn't false --- $s3 --- @a1 @a1 = <1 2 3> isn't false --- @a2 --- @a3

    please note that many things can be false in Perl, including the value 0.

    So you may rather test if (defined $scalar) and if (@array) or even better if(exists $hash{name})

    I don't think this will really help you, but you may learn from the code! =)

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: How to create variables for each hash key that has a value.
by bliako (Monsignor) on Jun 26, 2019 at 23:56 UTC

    I imagine you are in a refactoring scenario where you already have a big chunk of code which uses global variables like @arr_0 and you want to incorporate (i.e. copy-paste) it into a new script to work with data from front end but without converting that said chunk of code into a function for which all those global params should be function params.

    In my imagination, one way forward would be to convert your "chunk of code" to a function and pass its params either as one-after-the-other or as one single monolithic hash: the front-end hash you mentioned with all empty-items filtered out. It's trivial to filter out empty values :

    use strict; use warnings; my %h = ('a'=>'123','b'=>'xxx','c'=>''); my %newhash; for(keys %h){ $newhash{$_} = $h{$_} if $h{$_} ne '' }; print join(",", keys %newhash)."\n";

    After the intro. This declares the keys of the hash as variables and initialises them to whatever value they have in the hash and they are accessible in the outer scope:

    use strict; use warnings; my %h = ( 'a' => 'aaa', 'b' => '' ); for(keys %h){ next if $h{$_} eq ''; my $evalstr = "\$$_ = '$h{$_}';"; print "eval: $evalstr\n"; eval $evalstr; die "eval $@" if $@; } print "a=$a\n";

    HOWEVER, $a is a pre-declared variable in Perl and the above code compiles OK (though strict should have complained because we are using a variable at print "a=$a\n"; without declaring it at compile time but only through an eval() at runtime.

    There is an added complexity: in your scenario your real variables are completely unrelated to the front-end hash keys. So you will probably need a hash giving the correspondence between front-end variables and real variables.

    This can work for you if you are willing to not use use strict;

    #!/usr/bin/env perl #use strict; use warnings; my %params = ( 'default-0' => ['lakja', 'haljl', 'alka'], 'default-1' => 'abc', 'default-2' => [1, 54, 83, 23], 'default-3' => [], 'default-4' => '', 'default-5' => '2019-06-26 00:00:10', ); my %cor = ( 'default-0' => '@arr_0', 'default-1' => '$scal_1', 'default-2' => '@arr_2', 'default-3' => '@arr_3', 'default-4' => '$scal_4', 'default-5' => '$date_5', # etc ); my $evalstr; for(keys %params){ my $varname = $cor{$_}; die "var not found for $_" unless defined $varname; if( $varname =~ /^\$/ ){ next if $params{$_} eq ''; # quote value because it's a string: very basic rule of thumb. +.. $evalstr = "$varname = '$params{$_}';"; } elsif( $varname =~ /^@/ ){ next if @{$params{$_}}==0; $evalstr = "$varname = ".'@{["'.join('","',@{$params{$_}}).'"] +};'; } else { die "can't handle $varname = ".$params{$_} } print "eval: $evalstr\n"; eval $evalstr; die "eval $@" if $@; } print '@arr_0='."@arr_0\n".'$scal_1='."$scal_1\n";

    prints:

    Possible unintended interpolation of @arr_0 in string at b.pl line 45. Name "main::arr_0" used only once: possible typo at b.pl line 45. Name "main::scal_1" used only once: possible typo at b.pl line 45. eval: @arr_0 = @{["lakja","haljl","alka"]}; eval: $date_5 = '2019-06-26 00:00:10'; eval: @arr_2 = @{["1","54","83","23"]}; eval: $scal_1 = 'abc'; @arr_0=lakja haljl alka $scal_1=abc

    IMO: the whole idea though is flawed. It is roguelike to use the word of the day.

    bw, bliako

Re: How to create variables for each hash key that has a value.
by LanX (Saint) on Jun 26, 2019 at 20:19 UTC
    Yes it's possible with package variables but bad practice because if you can't control the keys how do you avoid naming conflicts?

    It would be better to choose a very short hash name.

    update
    > create variables (array or scalar) only for those hash keys that are not empty

    this is a very puzzling request, because how do you write code for variables which may or may not exist?

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      Thank you LanX and GrandFather for your response.

      this is a very puzzling request, because how do you write code for variables which may or may not exist?

      These are fields that will/won't be sent from front end. My code for each of these fields is in separate if(@arr_0), if($scal_1) blocks. So if any of these variables don't exist, the related code will not execute.

      Why? What is the bigger goal?

      The reasons I am trying to find if this is possible is:

      1) If this is possible, I'll create variables only for those fields that were selected by user. (And so will have non empty values)

      2) This will also prevent need for any immediate code changes at back end when fields are added or removed from the front end. If a field is removed from front end, the variable will not be created for it and related code block for that field will not execute. When fields is added at least the variable will be created for new field. I can make use of that. I'll still have to write code specific to that field.

      Do you know that a hash value can be anything, including a reference to another (nested) hash or a reference to an array?

      Thank you for suggesting this. I know about it but will be thinking in that direction now. Will try to see if I can make use of this somehow.

        > These are fields that will/won't be sent from front end. My code for each of these fields is in separate if(@arr_0), if($scal_1) blocks. So if any of these variables don't exist, the related code will not execute.

        This is a misconception, a variable must already exist to be tested the way you do (at least under use strict ) , but it can hold a false value if you wanna disable the block.

        It looks like all var-names are known beforehand, otherwise you wouldn't be able to write code testing it!

        hence you are free to write

        our @arr_0 = @{ $hash{arr_0} // [] } ; our $scal_1 = $hash{scal_1} ;

        both will be false if missing.

        BTW use exists not defined to test hash elements.

        still a very weird requirement, about how many variables are we talking?

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: How to create variables for each hash key that has a value.
by karlgoethebier (Abbot) on Jun 27, 2019 at 19:18 UTC

    Let me guess: Probably what you really want is to generate a class on-the-fly where the attributes of the class are the keys of your hash which have defined values. If i guessed right you may take a look at Re^2: Building Perl classes dynamically based on an input schema/template. I think the example ibidem can be relatively easy customized for your needs. If this technique is useful in your case or at all is another question. But it works like a charm. See also Class::Tiny. Best regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

Re: How to create variables for each hash key that has a value.
by Perl300 (Friar) on Jun 27, 2019 at 17:48 UTC
    Thank you again for all your inputs.

    After reading all the responses, I feel that it's not a good idea to try doing something like this. So I'll go with the way it's setup right now. Just to answer a few queries:

    LanX: still a very weird requirement, about how many variables are we talking? -> 12 variables at present

    I am running and trying to learn from all the suggestions here but will not be using it in prod. Also, I can't skip user strict as well.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11101990]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (8)
As of 2024-03-28 09:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found