简体   繁体   中英

What's the best way to conditionally include an element in a list?

Possible ways:

  1. Using push :

     my @list; push @list, 'foo' if $foo; push @list, 'bar' if $bar; 
  2. Using the conditional operator :

     my @list = ( $foo ? 'foo' : (), $bar ? 'bar' : (), ); 
  3. Using the x!! Boolean list squash operator :

     my @list = ( ('foo') x!! $foo, ('bar') x!! $bar, ); 

Which one is better and why?

Well, they all do different things.

However, all other factors being equal,

push @list, 'foo' if $foo;

is the statement that conveys its meaning most directly, so it should be preferred.

If you have to pause and think about statement that supposedly does something as simple as pushing an array element, you are doing something wrong.

my @list = (
    $foo ? 'foo' : (),
    $bar ? 'bar' : (),
);

could be OK if this is part of some colossal initialization that is being done elsewhere.

I think using

my @list = (
    ('foo') x!! $foo,
    ('bar') x!! $bar,
); 

indicates that the programmer has — how can I put this? issues .

Incidentally, there is no such thing called the x!! composite operator. The !! is double logical negation . It converts an arbitrary false value to a defined but false value (the empty string) which yields 0 when used where perl expects a number. A true value is converted to a 1 . Here, !! operates on $foo and $bar and writing it x!! $foo x!! $foo unnecessarily obfuscates the meaning of the code.

x is the repetition operator . So,

 ('foo') x !!$foo;

means repeat ('foo') once or not at all, depending on whether $foo is true or false, respectively .

PS: Of course, it turns out there was a PerlMonks article introducing the so-called boolean list squash operator . I read the article and find it unconvincing.

I'd like to suggest what I shall dub the "Readable" approach:

sub maybe ($$) { $_[1] ? $_[0] : () }

my @list = (
  maybe("foo", $foo),
  maybe("bar", $bar),
);

It has many of the benefits of this alleged x!! operator (though slightly longer), but with the added bonus that you can actually understand your code when you come back to it later.

EDIT: The prototype doesn't help Perl parse any better and doesn't let us ditch the parenthesis, it just prevents Perl from doing things we don't want. If we want to ditch the parens, we have to do some work. Here's an alternate version that works without parenthesis:

sub maybe {
  my($var, $cond, @rest) = @_;
  return @rest unless $cond;
  return $var, @rest;
}

my @list = (
  maybe "foo", $foo,
  maybe "bar", $bar,
);

No matter what we do with prototypes, Perl will try to parse it as maybe("foo", $foo, maybe("bar", $bar)) , so if we want to ditch the parenthesis, we just have to make that give the correct result.

Although the OP didn't call for it, this problem provided me with a good excuse to use Benchmark;

Here's the code:

use strict;
use warnings;
use Benchmark qw( cmpthese);

my $foo = 1;
my $bar = "true";

cmpthese( -10, {
  "push" => sub {
      my @list;
      push @list, 'foo' if $foo;
      push @list, 'bar' if $bar;
  },
  "?::" => sub {
      my @list = (
        $foo ? 'foo' : (),
        $bar ? 'bar' : ()
      );
  },
  "x!!" => sub {
      my @list = (
        ('foo') x!! $foo,
        ('bar') x!! $bar
      );
  }
});

And here are some typical results:

         Rate  x!!  ?:: push
x!!  646539/s   --  -8% -12%
?::  701429/s   8%   --  -5%
push 736035/s  14%   5%   --

Given brian d foy's 7% estimate for Benchmark 's uncertainty, it definitely seems like push is the fastest way to grow.

Summing up:

$need4speed ? do { push @like $this} : try { something('less' x!! $obfuscated) };
# DISCLAIMER: This is not valid code

Personally I use $foo ? 'foo' : () $foo ? 'foo' : () in such cases, since it is clear (comparing to ('foo') x!! $foo ), doesn't require particular effort to be understood (comparing to ('foo') x !!$foo ) and can be used in the list context (comparing to push @list, 'foo' if $foo; ).

But, of course, the answer depends on what criteria you choose to choose the best option. If you compare them by obfuscation, ('foo') x!! $foo ('foo') x!! $foo will surely win. If you compare them by clumsiness, push @list, 'foo' if $foo; will be the first. Or maybe you meant performance? I guess not :-)

But if you compare them by good style, my choice is $foo ? 'foo' : () $foo ? 'foo' : () .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM