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

I have a 3d matrix and I need to check if several elements equals a value (target value could be different for each element in the future).
Is there any way to simplify the if conditional at the end? I am hoping there would be some way to setup a 'conditional' matrix and compare them while ignoring elements that don't matter in the condition.

currently my code is:

use warnings; use strict; #fancy matrix my @matrix; #set to a blank 3d test matrix for my $i (0 .. 6) { for my $j (0 .. 6) { for my $k (0 .. 2) { $matrix[$i][$j][$k] = 0; } } } #set some values for matrix for my $j (0..2, 4..6) { my $i = 4; for my $k (0 .. 2) { $matrix[$i][$j][$k] = 1; } } for my $i (0..2, 4..6) { my $j = 4; for my $k (0 .. 2) { $matrix[$i][$j][$k] = 1; } } #the conditional problem child if ($matrix[0][4][0] == 1 and $matrix[0][4][1] == 1 and $matrix[0][4][ +2] == 1 and $matrix[1][4][0] == 1 and $matrix[1][4][1] == 1 and $matr +ix[1][4][2] == 1 and $matrix[2][4][0] == 1 and $matrix[2][4][1] == 1 +and $matrix[2][4][2] == 1 and $matrix[4][4][0] == 1 and $matrix[4][4] +[1] == 1 and $matrix[4][4][2] == 1 and $matrix[5][4][0] == 1 and $mat +rix[5][4][1] == 1 and $matrix[5][4][2] == 1 and $matrix[6][4][0] == 1 + and $matrix[6][4][1] == 1 and $matrix[6][4][2] == 1 and $matrix[4][0 +][0] == 1 and $matrix[4][0][1] == 1 and $matrix[4][0][2] == 1 and $ma +trix[4][1][0] == 1 and $matrix[4][1][1] == 1 and $matrix[4][1][2] == +1 and $matrix[4][2][0] == 1 and $matrix[4][2][1] == 1 and $matrix[4][ +2][2] == 1 and $matrix[4][4][0] == 1 and $matrix[4][4][1] == 1 and $m +atrix[4][4][2] == 1 and $matrix[4][5][0] == 1 and $matrix[4][5][1] == + 1 and $matrix[4][5][2] == 1 and $matrix[4][6][0] == 1 and $matrix[4] +[6][1] == 1 and $matrix[4][6][2] == 1) { print "it worked"; } <stdin>;


ps: yes this is a shameless crosspost from stackoverflow

Replies are listed 'Best First'.
Re: 36 Conditions in If Statement [sendhelp]
by Athanasius (Archbishop) on Feb 04, 2017 at 13:03 UTC

    Hello UpTide,

    Here’s an approach which, although less efficient, is easier to read and therefore to debug:

    use strict; use warnings; use List::Util qw( all ); ... # create and populate my @matrix as before ... my @indices = ( [0, 4, 0], [0, 4, 1], [0, 4, 2], [1, 4, 0], [1, 4, 1], [1, 4, 2], [2, 4, 0], [2, 4, 1], [2, 4, 2], [4, 4, 0], [4, 4, 1], [4, 4, 2], # A [5, 4, 0], [5, 4, 1], [5, 4, 2], [6, 4, 0], [6, 4, 1], [6, 4, 2], [4, 0, 0], [4, 0, 1], [4, 0, 2], [4, 1, 0], [4, 1, 1], [4, 1, 2], [4, 2, 0], [4, 2, 1], [4, 2, 2], [4, 4, 0], [4, 4, 1], [4, 4, 2], # B [4, 5, 0], [4, 5, 1], [4, 5, 2], [4, 6, 0], [4, 6, 1], [4, 6, 2], ); my @to_test; push @to_test, $matrix[ $_->[0] ][ $_->[1] ][ $_->[2] ] for @indices; print "It worked\n" if all { $_ == 1 } @to_test;

    By making it easier to see which array elements are being tested, this approach reveals that three of the tests are in fact duplicated (see the lines labelled A and B).

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      The approach you suggest is very ++nice. It can easily be adapted to other cases; e.g., a case in which the comparison may vary with each tuple of indices.

      use strict; use warnings; use List::Util qw(all); # create and populate my @matrix as before ... my @tuples = ( [1, 0, 4, 0], [2, 0, 4, 1], [3, 0, 4, 2], [4, 1, 4, 0], [5, 1, 4, 1], [6, 1, 4, 2], ... [9, 4, 6, 0], [8, 4, 6, 1], [7, 4, 6, 2], ); do_something() if all { $_->[0] == $matrix[$_->[1]][$_->[2]][$_->[3]] +} @tuples;
      At a slight performance cost, the subroutine block of  all could be made a bit more readable:
          { my ($n, $i, $j, $k) = @$_;  $n == $matrix[$i][$j][$k]] }


      Give a man a fish:  <%-{-{-{-<

Re: 36 Conditions in If Statement [sendhelp]]
by kcott (Archbishop) on Feb 04, 2017 at 15:27 UTC

    G'day UpTide,

    Writing a condition, on one line, that is almost a thousand characters long, and contains over a hundred unexplained, numeric constants, is utterly ridiculous! It's a maintenance nightmare; extremely error-prone; and a disaster just waiting to happen.

    For a start, when you have complex conditions, spread them over multiple lines, and line up the conditions such that they are easy to read, e.g.

    if ( $matrix[0][4][0] == 1 and $matrix[0][4][1] == 1 and $matrix[0][4][2] == 1 ... ) { # action if TRUE }

    Next, although those numeric constants might be meaningful to you now, while you're writing the code and it's fresh in your mind, they won't be when you return to this code later; they most certainly will not be for anyone looking at this code for the first time. There are many ways to give these constants meaningful names: the builtin pragma, constant, is one option; the CPAN module, enum, is another. The SEE ALSO sections of both of those have other suggestions; a "CPAN search for 'constant'" produces hundreds of results.

    Using meaningful names should make your code self-documenting; however, you may want to add comments (e.g. to explain why you're testing for exact equality with "1", as opposed to, say, testing for any TRUE value).

    Instead of putting that much code in the middle of your script, use a subroutine. I think you'll agree this is much clearer:

    print "it worked" if matrix_ok(\@matrix);

    In this instance, instead of dozens of equality tests, you can use loops similar to what you've already written. For efficiency, I wouldn't repeatedly redeclare and redefine the fixed indices. Here's an untested example:

    sub matrix_ok { my ($matrix) = @_; { my $i = 4; for my $j (0 .. 2, 4 .. 6) { for my $k (0 .. 2) { return 0 unless $matrix->[$i][$j][$k] == 1; } } } { my $j = 4; for my $i (0 .. 2, 5, 6) { for my $k (0 .. 2) { return 0 unless $matrix->[$i][$j][$k] == 1; } } } return 1; }

    Note that I didn't use meaningful names, because I have no idea what all those numbers mean. If I did know what they meant, I might write code like this:

    ... for my $k (MIN_DEPTH .. MAX_DEPTH) { ...

    See also (functions such as any() and all() in): the builtin module List::Util; and the CPAN modules, Perl6::Junction and Quantum::Superpositions.

    — Ken

      To Whom It May Concern: Note that the functions  any() all() et al were added to version number (mumble) of module List::Util; prior to that version, they appeared (and still do appear) in the List::MoreUtils module.


      Give a man a fish:  <%-{-{-{-<

Re: 36 Conditions in If Statement [sendhelp]]
by vr (Curate) on Feb 05, 2017 at 12:43 UTC

    Using PDL http://pdl.perl.org for this particular task may (or may not) be a good idea, but, at least, worth mentioning in context of "matrices", maybe UpTide will find it beneficial to invest his time studying it. Especially because

    target value could be different for each element in the future

    E.g., look how concise the original program becomes (same comments in place, but code replaced):

    use warnings; use strict; use PDL; use PDL::NiceSlice; #fancy matrix #set to a blank 3d test matrix my $matrix = zeroes 7,7,3; #set some values for matrix $matrix( pdl( 0..2, 4..6 ), 4, 0:2 ) .= 1; $matrix( 4, pdl( 0..2, 4..6 ), 0:2 ) .= 1; #the conditional problem child my $ind = $matrix-> whichND( $matrix == 1 ); if ( all $matrix-> indexND( $ind ) == 1 ) { print "it worked\n"; }

    Well, it looks like some cheating, condition is obvious to be true.

    The problem, maybe, can be seen as "there are known places in a matrix of predefined dimensions, check if they hold 'good (but different) values'". Values can be extracted from these 'known places', as shown above, and compared with pre-built vector holding 'good values'.

    Of course, e.g. Athanasius's solution can be easily modified to extract values, pack them into string, compare with 'good' pre-packed string (all that without any PDL).

    But it's also possible to read the question strictly verbatim:

    I am hoping there would be some way to setup a 'conditional' matrix and compare them while ignoring elements that don't matter in the condition.

    and write a test program:

    use warnings; use strict; use PDL; use PDL::NiceSlice; use Test::PDL 'eq_pdl', -tolerance => 0.42; my $mask = zeroes 7,7,3; $mask( pdl( 0..2, 4..6 ), 4, 0:2 ) .= 1; $mask( 4, pdl( 0..2, 4..6 ), 0:2 ) .= 1; my $good_matrix = random 7,7,3; my $some_matrix; my $count; while () { $some_matrix = random 7,7,3; last if eq_pdl $good_matrix * $mask, $some_matrix * $mask; $count ++ } $PDL::doubleformat = '%.1f'; print "it worked only after $count attempts,\n", "mask was: $mask", "good matrix was: $good_matrix", "and it was not very different from: $some_matrix";

    Same dimensions, same 'known places'. A mask to 'ignore elements that don't matter'. Arbitrarily chosen 'good values'. Actually, checking, how much time it takes to generate 33 random numbers in 0..1 range which are 'close enough' to 33 original numbers. Well, it looks like tolerance better than 0.4 takes forever, but only a few seconds otherwise:

    it worked only after 144532 attempts, mask was: [ [ [0 0 0 0 1 0 0] [0 0 0 0 1 0 0] [0 0 0 0 1 0 0] [0 0 0 0 0 0 0] [1 1 1 0 1 1 1] [0 0 0 0 1 0 0] [0 0 0 0 1 0 0] ] [ [0 0 0 0 1 0 0] [0 0 0 0 1 0 0] [0 0 0 0 1 0 0] [0 0 0 0 0 0 0] [1 1 1 0 1 1 1] [0 0 0 0 1 0 0] [0 0 0 0 1 0 0] ] [ [0 0 0 0 1 0 0] [0 0 0 0 1 0 0] [0 0 0 0 1 0 0] [0 0 0 0 0 0 0] [1 1 1 0 1 1 1] [0 0 0 0 1 0 0] [0 0 0 0 1 0 0] ] ] good matrix was: [ [ [0.4 0.3 0.3 0.9 0.6 0.3 0.2] [0.8 0.8 0.2 0.8 0.6 0.9 0.2] [0.9 0.0 1.0 0.3 0.8 1.0 0.3] [0.5 0.3 0.8 0.5 0.3 0.6 1.0] [0.8 0.7 0.3 0.4 0.3 0.2 0.4] [1.0 0.4 0.8 0.9 0.4 0.2 0.9] [0.0 0.8 0.7 0.9 0.0 0.1 0.7] ] [ [0.1 0.8 0.5 1.0 0.5 0.1 0.8] [0.0 0.4 0.7 0.1 0.3 0.1 0.5] [0.6 0.0 0.4 0.7 0.5 0.1 0.5] [0.7 0.2 0.1 0.1 0.3 0.2 0.5] [0.6 0.5 0.2 0.5 0.5 1.0 0.6] [0.9 0.2 0.5 0.6 0.5 0.0 0.2] [0.9 0.9 0.2 0.6 0.2 0.1 0.2] ] [ [0.8 0.3 0.9 0.5 0.5 0.3 0.2] [0.2 0.5 0.5 0.3 0.8 0.0 0.3] [0.3 1.0 0.3 0.8 0.3 0.8 0.8] [0.7 0.9 0.7 0.1 0.6 0.9 0.7] [0.4 0.3 0.8 0.7 0.5 0.8 0.3] [0.8 0.9 0.5 0.2 0.1 0.4 0.2] [0.4 0.7 0.5 0.2 0.7 0.1 0.3] ] ] and it was not very different from: [ [ [0.5 0.2 1.0 0.4 0.9 0.2 0.4] [0.7 0.2 0.1 0.6 0.6 0.2 0.4] [0.8 0.3 0.6 0.7 0.4 0.3 0.9] [0.7 0.1 0.5 0.3 0.5 0.7 0.2] [0.5 0.6 0.7 1.0 0.4 0.1 0.5] [0.4 0.8 1.0 0.6 0.4 0.3 0.7] [0.8 1.0 0.7 0.6 0.3 0.7 0.8] ] [ [0.4 0.6 0.1 0.2 0.1 0.2 0.7] [0.7 0.9 0.1 0.2 0.7 0.7 0.2] [0.9 0.6 0.0 1.0 0.6 0.7 0.8] [0.3 1.0 0.3 0.4 0.5 0.3 0.3] [0.8 0.7 0.3 0.1 0.3 0.7 0.5] [0.2 0.4 0.5 0.2 0.3 0.3 0.9] [0.9 0.7 0.4 0.1 0.2 0.9 0.8] ] [ [0.1 0.0 0.7 0.3 0.8 0.2 0.1] [0.4 0.4 0.4 0.3 0.8 0.7 0.1] [0.3 0.3 0.0 0.0 0.1 0.2 1.0] [0.8 0.1 0.7 0.7 0.4 0.3 0.3] [0.7 0.1 0.9 0.6 0.4 0.9 0.3] [0.1 0.2 0.4 0.4 0.5 0.6 0.3] [0.6 0.5 0.5 0.9 0.7 0.5 0.6] ] ]
Re: 36 Conditions in If Statement [sendhelp]]
by Laurent_R (Canon) on Feb 04, 2017 at 10:46 UTC
    I first thought you could do the checks in a nested loop, but it turns out that you would essentially have to rewrite the loops that you used to populate the matrix, so that you wouldn't really check very much.

    Just a quick comment on your loops. Using Perl-style loops instead of C-style loops might be slightly easier, for example:

    for my $i (0..6) { for my $j (0..6) { $matrix[$i][$j][$_] = 0 for (0..2); } }

      I have changed!
      using $_ for (0 .. 2) scares me though

        using $_ for (0 .. 2) scares me though

        Be not afraid! This is a bedrock Perl idiom. The  $_ variable is completely localized (update: and aliased! — it can be used to assign to the for-loop list item if that item is an lvalue) (I believe the term is "topicalized") within the for statement or statement block. Do you have any specific concern about this usage?


        Give a man a fish:  <%-{-{-{-<

        You don't need to use $_, I just wanted to show a possible alternative syntactical construct (statement modifier) for the most inner loop.
Re: 36 Conditions in If Statement [sendhelp]]
by FreeBeerReekingMonk (Deacon) on Feb 04, 2017 at 19:44 UTC
    I looked for you in the Math::Matrix module, but the lack of splice()-like functions (only selecting columns is possible) makes it impossible to use that.

    So here is my attempt at testing a range of values:

    so you have a range of numbers:

    range => "0..2,4..6;4;0..2",

    Which means X=0,1,2,4,5,6 and Y=4 and Z=0,1,2

    Then you can set the *value*. The value passes through an eval block (very slow) but it allows "math":

    value => '$x+$y'

    in the example, we only use the value "1":

    use warnings; use strict; #fancy matrix my @matrix; #set to a blank 3d test matrix 7x7x3 for my $i (0 .. 6) { for my $j (0 .. 6) { for my $k (0 .. 2) { $matrix[$i][$j][$k] = 0; } } } #set some values for matrix for my $j (0..2, 4..6) { my $i = 4; for my $k (0 .. 2) { $matrix[$i][$j][$k] = 1; } } for my $i (0..2, 4..6) { my $j = 4; for my $k (0 .. 2) { $matrix[$i][$j][$k] = 1; } } my $test1 = testrange( { range => "0..2,4..6;4;0..2", value => 1, matrix=>\@matrix } ); my $test2 = testrange( { range => "4;0..2,4..6;0..2", value => 1, matrix=>\@matrix } ); if($test1 && $test2 ){ print "it worked"; }else{ print "it did not work"; } sub testrange{ my($d)=@_; my ($XX,$YY,$ZZ) = split(/\s*;\s*/, $d->{range}); my @X = eval($XX); my @Y = eval($YY); my @Z = eval($ZZ); my $M = $d->{matrix}; my $success = 1; for my $x (@X){ for my $y (@Y){ for my $z (@Z){ my $value = eval($d->{value}); #print "DEBUG test:[$x,$y,$z]=$value == $M->[$x][$y][$ +z]\n"; unless($value eq $M->[$x][$y][$z]){ print "FAIL test:[$x,$y,$z]=$value != $M->[$x][$y] +[$z]\n"; $success = 0; #return 0; # fail immediately } } } } return $success; # sucess }
Re: 36 Conditions in If Statement [sendhelp]]
by Anonymous Monk on Feb 04, 2017 at 11:05 UTC