Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses

Scalar joining, concatenation, involving varying text

by mhearse (Chaplain)
on Jun 27, 2005 at 20:06 UTC ( #470370=perlquestion: print w/replies, xml ) Need Help??
mhearse has asked for the wisdom of the Perl Monks concerning the following question:

I'm looking for a way to condense this:
my $combined; if ($a) { $combined .= "a=$a "; } if ($b) { $combined .= "b=$b "; } if ($c) { $combined .= "c=$c "; } ... ... ...
I'm just not sure how to approach it. I thought I could use map, but I'm not sure how:
my $combined = map { ??? } qw($a $b $c ...);
I would appreciate any ideas. Thanks.

Replies are listed 'Best First'.
Re: Scalar joining, concatenation, involving varying text
by tlm (Prior) on Jun 27, 2005 at 20:16 UTC
    use strict; use warnings; my $combined = ''; my ( $a, $b, $c ) = ( 1, 0, 1 ); eval qq(if ( \$$_ ) { \$combined .= "$_=\$$_ " }) for qw( a b c ); print "$combined\n"; __END__ a=1 c=1

    Update: Quotation bug fixed. (In the original version of my solution the RHS of the concat/assignment in the eval was the single-quoted '$_=\$$_ ', resulting in $combined ending up with the value 'a=$a c=$c' instead of 'a=1 c=1').

    Also, to clarify what the eval is doing, at each iteration, only the $_ get interpolated; the remaining $'s are quoted verbatim. Hence, in the first iteration, the argument to eval is the string

    'if ( $a ) { $combined .= "a=$a " }'
    All the a's in this string come from interpolating $_, whereas the $'s come from the \$'s in the original double-quoted expression.

    The fuss with double quotes and backslashes is a way to selectively interpolate certain values (i.e. $_'s); without all the backslashing, perl would try to interpolate $$, for example.

    Perhaps this is a clearer alternative:

    my $template = 'if ( $%s ) { $combined .= "%s=$%s " }'; eval sprintf $template, $_, $_, $_ for qw( a b c );
    Of course, in the last snippet, $template can alternatively be set to
    '$combined .= "%s=$%s " if $%s'
    '$%s and $combined .= "%s=$%s "'

    the lowliest monk

      Thanks for the reply. Could you elaborate on the logic here. Does \ prevent interpolation until later evaluation? I just want to make sure I understand, \$$_ allow us to substitute a variable name as the name for an implied variable within a loop.
        \ in string literals causes the next character as ordinary, so yes, it will will prevent interpolation:
        $cow = 'moo!'; $_ = 'cow'; print("$cow"); # Prints moo! print("\$cow"); # Prints $cow print("\\\$cow"); # Prints \$cow print("\$$_"); # Prints $cow print(eval "$cow"); # Error executing "moo!". print(eval "\$cow"); # Prints moo! print(eval "\$$_"); # Prints moo!

        qq{...} is the same thing as "..."

Re: Scalar joining, concatenation, involving varying text
by ikegami (Pope) on Jun 27, 2005 at 20:17 UTC

    If everything was in a hash, it would be easier:

    my %data; $data{a} = 1; $data{b} = 2; $data{c} = 3; my $combined = join ' ', map { "$_=$data{$_}" } grep { $data{$_} } sort keys %data;

    There are two alternatives to using hashes.

    1) Mucking around with the symbol table. This won't work if $a, $b, etc are lexical (my) variables.

    our $a = 1; our $b = 2; our $c = 3; my $combined = join ' ', map { "$_->[0]=$_->[1]" } grep { $_->[1] } map { [ $_, do { no strict 'refs'; ${$_} } ] } qw(a b c);

    2) Using eval. This is slow and risky.

    my $a = 1; my $b = 2; my $c = 3; my $combined = join ' ', map { "$_->[0]=$_->[1]" } grep { $_->[1] } map { [ $_, eval('$'.$_) ] } qw(a b c);

    Update: Added grep to every snippet to fix the bug crenz pointed out.

    Here's the above without using map+grep:

      Uh, no. You're loosing the if-condition here, so your approach has different semantics than what the original poster wanted.

Re: Scalar joining, concatenation, involving varying text
by crenz (Priest) on Jun 27, 2005 at 20:40 UTC

    I'd try something like this:

    my %values = ( a => 1, b => 0, c => 1); my $combined = ''; foreach (sort keys %values) { $combined .= "$_=$values{$_} " if $values{$_}; }

    Once you understand the above solution, you can try to replace it with using map and grep. Even though I'm very familiar with both, I'd just leave it like that, though.

Re: Scalar joining, concatenation, involving varying text
by GrandFather (Sage) on Jun 27, 2005 at 20:40 UTC

    Modifiers may well be what you want:

    $combined .= "a=$a " if $a; $combined .= "b=$b " if $b; $combined .= "c=$c " if $c;

    Concise and clear.

    Perl is Huffman encoded by design.
Re: Scalar joining, concatenation, involving varying text
by davidrw (Prior) on Jun 27, 2005 at 20:18 UTC
    well, can use eval:
    my $combined = join ' ', grep $_, map { my $value = eval "\$$_"; $valu +e ? sprintf("%s=%s", $_, $value) : undef } ( 'a' .. 'z' );
    But comes with all the caveats of eval'ing arbitrary variables. I hope someone posted a way of doing this, which would make the map a lot simpler:
    my $x = 3; print f($x); # 'x'
    Also note that it is bad to use $a and $b becaues it can/will cause confusion with the special variables used by sort.
Re: Scalar joining, concatenation, involving varying text
by Animator (Hermit) on Jun 27, 2005 at 21:38 UTC

    As I see it you have two options: one that requires 'no strict qw/refs/' and one that works fine under use strict. Both of these use a simple foreach loop. (both of these examples are similar to the one ikegami posted in the read-more tags.)

    Or do you really want a solution that uses map?

    It is also my opinion that using eval for this is simply a bad idea... why use a complex, slow and dangerous (if used incorrectly) function for something soo simple?

    First one: uses soft-reference, and does not work with lexical variables

    no strict qw/refs/; foreach my $e (qw/a c b/) { if ($$e) { $combined .= $e . "=" . $$e; } }

    Second one:

    foreach my $e ( ["a", $a], ["c", $c], ["b", $b]/) { if ($e->[1]) { $combined .= $e->[0] . "=" . $e->[1]; } }
    (You could write this one using one long list aswell, but then you would need to use a C-style for loop.)

Re: Scalar joining, concatenation, involving varying text
by graff (Chancellor) on Jun 28, 2005 at 01:22 UTC
    If you wanted to do this with map, here is one such approach. Note that I'm using "$i,$j,$k" -- I think it's a bad idea to use "$a" and "$b" as names for application data variables, because in Perl these names are used by the "sort" function.
    my ($i, $j, $k) = (2, 0, 4); # make up some data values my $ltr = "i"; my $combined = join "", map { $_ &&= "$ltr=$_ "; $ltr++; ($_)? $_:"" } + ( $i, $j, $k ); print $combined, "\n";
    But frankly, I would prefer to use a solution that puts the values into a hash and loops over the sorted hash keys, as suggested by some of the replies above. (That way, you can safely start at "a" if you want.)
Re: Scalar joining, concatenation, involving varying text
by tcf03 (Deacon) on Jun 27, 2005 at 20:44 UTC
    would something like this work?
    my $a = "testA"; my $b = "testB"; my $c = "testC"; my %combined = ( $a => "a=$a", $b => "b=$b", $c => "c=$c" ); my $combined = join ' ', $combined{$a}, $combined{$b}, $combined{$c};

    "That which we persist in doing becomes easier, not that the task itself has become easier, but that our ability to perform it has improved."
      --Ralph Waldo Emerson

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://470370]
Approved by davidrw
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (3)
As of 2018-01-21 23:45 GMT
Find Nodes?
    Voting Booth?
    How did you see in the new year?

    Results (230 votes). Check out past polls.