There's more than one way to do things PerlMonks

### Scalar joining, concatenation, involving varying text

by mhearse (Chaplain)
 on Jun 27, 2005 at 20:06 UTC 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'
or
```'\$%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};

Ted
--
"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

Create A New User
Node Status?
node history
Node Type: perlquestion [id://470370]
Approved by davidrw
help
Chatterbox?
 Discipulus wonders if just numbers less than 18 are really floating..( chemical humor) [Lady_Aleena]: Hello. [Discipulus]: ciao Lady_Aleena

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (6)
As of 2017-06-26 21:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
How many monitors do you use while coding?

Results (594 votes). Check out past polls.