Re: One out of three ain't bad (order)
by tye (Sage) on Oct 22, 2005 at 05:49 UTC
|
if( 1 == grep $_, $x, $y, $z )
Or, using a more versatile trick:
if( 1 == !!$x + !!$y + !!$z )
But note that some will kvetch that the fact of !0 == 1 is "not defined" in Perl. But that ship has already sailed; practicality actually prevents Perl from changing the value of "true" to something other than 1, even if this property was intentionally left undefined.
| [reply] [d/l] [select] |
|
|
I like this !! based solution the best. It's the most logical way to solve the problem - convert the arguments to boolean truth values, then sum them. You could do it for an arbitrarily long list of variables with:
use List::Util qw( sum );
if( 1 == sum( map { !!$_ } @inputs) ) {
...
}
| [reply] [d/l] |
|
|
if (1 == sum( map { $_ ? 1 : 0 } @inputs ))
While tye may be right that, in practice, Perl can't change the value of !0, I just don't like relying on obscure implementation details when clear, well-defined implementation details are available to me to accomplish the same thing in a readable, understandable, and maintainable manner. | [reply] [d/l] |
|
|
|
|
|
|
|
| [reply] [d/l] |
|
|
Which seems to work right now, since you've seen the progression of what came before. But in 2 months, I can imagine you thinking, "Now why in the world is there a two in there... I must want two of them to be true" on your first look while skimming your code.
-Bryan
| [reply] |
|
|
|
|
|
Re: One out of three ain't bad
by davido (Cardinal) on Oct 22, 2005 at 05:52 UTC
|
I'm not convinced that you need an elegant solution to finding if one and only one of three elements evaluates to truth. But it's kind of a fun problem anyway, and if the number of elements grows, a sleek solution becomes more relevant. So here goes...
Try cpan's List::MoreUtils:
use strict;
use warnings;
use List::MoreUtils qw/ true /;
my @lists = ( [ 1, 0, 1 ],
[ 1, 0, 0 ],
[ 1, 1, 1 ],
[ 0, 0, 0 ] );
foreach my $aref ( @lists ) {
print "Testing @{$aref}\n";
print "Total list Exclusive OR ",
( 1 == true { $_ } @{ $aref } ) ?
'' : 'not ',
"satisfied.\n\n";
}
Here we're just using the true() function from List::MoreUtils. The function returns the number of true elements. Like one of your proposed solutions, we simply check to ensure that the number of true elements is exactly one. This one works almost identically to your grep solution, except that it uses an explicit function name (true()) instead of a generic one such as grep.
My example snippet actually tests a list of lists to see if each sub-list individually satisfies your requirement of a single element of truth. The engine is this:
1 == true { $_ } @array
If that test evaluates to truth, you've got a list with a single element of truth.
| [reply] [d/l] [select] |
Re: One out of three ain't bad
by ikegami (Patriarch) on Oct 22, 2005 at 05:53 UTC
|
You could hide the guts in a function to make it more readable:
sub single_true {
my $count = 0;
$_ && $count++ foreach @_;
return $count == 1;
}
if (single_true($x, $y, $z)) {
...
}
| [reply] [d/l] |
Re: One out of three ain't bad
by strat (Canon) on Oct 22, 2005 at 05:53 UTC
|
well, you can write it a little bit shorter...
my $count = 0;
$_ and $count++ for ($x,$y,$z);
if (1 == $count) {
print "Exactly one variable set\n";
}
or
or even pack the $count and ++ into a do:
if (1 == do {my $cnt=0; $_ and $cnt++ for ($x,$y,$z); $cnt } {
print "Exactly one variable set\n";
}
But I doubt that this is more readable
update: I'd prefer a grep solution as posted by tye
if (1 == grep {$_} ($x,$y,$z)) {
print "Exactly one variable set\n";
}
Best regards,
perl -e "s>>*F>e=>y)\*martinF)stronat)=>print,print v8.8.8.32.11.32"
| [reply] [d/l] [select] |
Re: One out of three ain't bad
by ambrus (Abbot) on Oct 22, 2005 at 20:10 UTC
|
Whenever you feel there's no intuitive way to do it,
write a subroutine.
In this case, let's define, say,
sub exactly_one { 1 == grep $_, @_; }
Then you can do exactly_one($x, $y, $z) which is now cleaner than any other solution.
| [reply] [d/l] [select] |
Re: One out of three ain't bad
by Util (Priest) on Oct 22, 2005 at 19:55 UTC
|
Your grep solution would be intuitive to many monks, but it becomes more maintainable when paired with well-named variables or subroutines. My (current, and PBP-influenced) preferences:
-
If you check only once in your program, then be descriptive with a variable.
my $number_of_true_variables = grep { $_ } $x, $y, $z;
if ( $number_of_true_variables == 1 ) {
print "Exactly one variable set\n";
}
-
If you check more than once, sometimes comparing the count to values other than one, then use a sub named for the count it returns.
sub number_of_true_variables {
return scalar grep { $_ } @_;
}
if ( number_of_true_variables( $x, $y, $z ) == 1 ) {
print "Exactly one variable set\n";
}
-
If you check more than once, always comparing to one, then use a sub named for that comparison.
sub exactly_one_is_set {
my $number_of_true_variables = grep { $_ } @_;
return ( $number_of_true_variables == 1 );
}
if ( exactly_one_is_set( $x, $y, $z ) ) {
print "Exactly one variable set\n";
}
| [reply] [d/l] [select] |
Re: One out of three ain't bad
by gloryhack (Deacon) on Oct 22, 2005 at 07:26 UTC
|
"Intuitive" is subjective... to those who grok perl, your "more elegant" solution should make perfect sense, even if it's not the ultimate reality solution. If it satisfies all test cases (3! of them) then it works... if it's also readable, then let it be. Don't sweat the irrelevant. | [reply] |
Re: One out of three ain't bad
by JamesNC (Chaplain) on Oct 22, 2005 at 14:38 UTC
|
Here is a simple example using the shift operator.
my $x = -2;
my $y = 0;
my $z = 1;
my $t = 1;
for( $x,$y,$z){ $t = $t<<1 if $_; }
print "none set" if $t == 1;
print "only one set" if $t == 2;
print "more than 1 set" if $t > 2;
JamesNC | [reply] [d/l] |
Re: One out of three ain't bad (benchmarks)
by snowhare (Friar) on Oct 23, 2005 at 15:37 UTC
|
In the interest of "if you are going to do it, overdo it", I took the various solutions presented (along with two additional variants; snowhare5 and snowhare6 I thought of) and ran precision benchmarks on them to see how they stack up performance wise in addition to 'elegance wise'. And to verify that they all actually produce correct results. The benchmarks were run with 500,000 loops to get good precision and I 'tare' compensated the scripts with a 'null script' that factored out the testing framework overhead time.
The good news is all the solutions produce correct results. (Yay everyone!).
The bad news is that they vary by about 400% performance wise from the fastest to the slowest.
The ranked results were as follows:
snowhare6 : 3.76 secs 100% (0 errors)
snowhare2 : 3.90 secs 104% (0 errors)
tye2 : 4.06 secs 108% (0 errors)
snowhare3 : 4.09 secs 109% (0 errors)
snowhare5 : 4.13 secs 110% (0 errors)
snowhare1 : 4.73 secs 126% (0 errors)
saintmike1 : 5.51 secs 147% (0 errors)
tye1 : 5.62 secs 149% (0 errors)
snowhare4 : 5.69 secs 151% (0 errors)
saintmike2 : 6.37 secs 169% (0 errors)
strat1 : 10.60 secs 282% (0 errors)
ikegami1 : 11.14 secs 296% (0 errors)
tanktalus1 : 11.68 secs 311% (0 errors)
strat2 : 11.81 secs 314% (0 errors)
jamesnc1 : 12.07 secs 321% (0 errors)
ph713_1 : 15.60 secs 415% (0 errors)
davido1 : 16.18 secs 430% (0 errors)
snowhare5 and snowhare6 were algorithmically the same as the previously presented snowhare1 and snowhare2 respectively, except I added 'use integer;' to them.
Among the 'trivially generalized to handle N values' scripts, tye1 is the fastest (all it would take is replacing the explict variable callouts with @_).
| [reply] [d/l] [select] |
Re: One out of three ain't bad
by EvanCarroll (Chaplain) on Oct 22, 2005 at 06:12 UTC
|
I'm not sure what solution to offer. Don't shoot for concise, short for maintainablity. Short circuiting should be avoided in perl, it has better faculties.
## Instead of...
$x && $count++;
## Consider...
$count++ if $x;
I would probably go with the slightly modified grep suggested in the post above. But see 'perldoc -q contain in'
Evan Carroll www.EvanCarroll.com
| [reply] [d/l] [select] |
|
|
open FH, '>', 'filename' or die $!;
...and who could argue that use of short circuiting is inferior to this:
die $! unless open FH, '>', 'filename';
This is further discussed in the following quote from perlstyle:
Here are some other more substantive style issues to think about:
Just because you CAN do something a particular way doesn't mean that you SHOULD do it that way. Perl is designed to give you several ways to do anything, so consider picking the most readable one. For instance
open(FOO,$foo) || die "Can't open $foo: $!";
is better than
die "Can't open $foo: $!" unless open(FOO,$foo);
because the second way hides the main point of the statement in a modifier.
The point here is that you cannot make a sweeping statement such as "short circuiting should be avoided in Perl". Avoided why? There are several equally efficient ways to do things. The docs are right: "consider picking the most readable one." You might be right that short circuiting hides the intent in this case, but certanly it's going too far to say it should be avoided altogether.
| [reply] [d/l] [select] |
Re: One out of three ain't bad
by snowhare (Friar) on Oct 23, 2005 at 03:54 UTC
|
Ok. The last one is best.
my $only_one = 1 == ($x ? 1 : 0) + ($y ? 1 : 0) + ($z ? 1 :0);
+
my $only_one = 2 == (! $x) + (! $y) + (! $z);
my $only_one = ($x || $y || $z) && (! ($x && $y)) && (! ($y && $z)) &&
+ (! ($z && $x));
my $only_one = (! ($x && $y && $z)) && ($x ^ $y ^ $z);
| [reply] [d/l] |
|
|
| [reply] [d/l] [select] |
|
|
I'll disagree here an say a form like...
if( !$x && !$y && $z or
!$x && $y && !$z or
$x && !$y && !$z )
{
print "Exactly one...";
}
...is easier to maintain because it is more explicit as to what is required for a truth value. Anyone versed in boolean algebra will easily recognize the sum-of-product form here and instantly grasp the intent.
| [reply] [d/l] |
|
|
|
|
You probably want logical exclusive or (xor) not bitwise exclusive or (^).
| [reply] |
|
|
Good catch. My test framework had a couple of bugs that let me miss that. I've corrected the framework errors, corrected my logical vs bitwise xor error and added the two contributions by anon monks to the tests. It changed the rankings - snowhare4 moved from about 9th to 2nd behind anonmonk1 after fixing. However, anonmonk1 got 3 test case failures, so snowhare4 is now the fastest of the correct solutions. It's nice that the solution that is to me the most elegant is also the fastest correct solution.
anonmonk1 : 3.26 secs 100% (3 errors)
snowhare4 : 4.50 secs 138% (0 errors)
snowhare6 : 4.66 secs 143% (0 errors)
snowhare2 : 4.99 secs 153% (0 errors)
tye2 : 5.13 secs 157% (0 errors)
snowhare5 : 5.17 secs 159% (0 errors)
snowhare3 : 5.17 secs 159% (0 errors)
snowhare1 : 5.30 secs 163% (0 errors)
saintmike1 : 6.36 secs 195% (0 errors)
tye1 : 7.21 secs 221% (0 errors)
saintmike2 : 7.54 secs 231% (0 errors)
knom1 : 13.41 secs 411% (0 errors)
strat1 : 13.41 secs 411% (0 errors)
ikegami1 : 13.50 secs 414% (0 errors)
jamesnc1 : 14.86 secs 456% (0 errors)
strat2 : 15.05 secs 462% (0 errors)
tanktalus1 : 15.49 secs 475% (0 errors)
ph713_1 : 20.21 secs 620% (0 errors)
davido1 : 21.96 secs 674% (0 errors)
| [reply] [d/l] [select] |
Re: One out of three ain't bad
by Anonymous Monk on Oct 23, 2005 at 19:00 UTC
|
if($x && !$y && !$z or !$x && ($y xor $z))
{
print "Exactly one variable set\n";
}
| [reply] [d/l] |
Re: One out of three ain't bad
by Anonymous Monk on Oct 24, 2005 at 09:21 UTC
|
undef $count;
foreach(($x, $y, $z)) {
$count++ if($_);
}
print "Exactly one variable set\n" if($count == 1);
--Knom | [reply] |