http://www.perlmonks.org?node_id=970330

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

Dear Monks,

I am going mad with this so please help. I have two arrays and if an element in 1st array is in 2nd array I want to append "+" to array 3. If it's not I want to append '-' to array 3. It's simple with if and grep but I want to finally start making my code shorter. I tried different varation of;

@x = qw(6); @y = qw(1 2 3 4); foreach $x (@x) { any{$x == $_ } @y ? push(@z, "y") : push(@z, "n"); }

I relize there are probably tons of ways to make it simpler but I can someone tell me what to change in this to make it work? I really want to use the ? : ; scheme properly in such cases so I can use it in the future.

Perl Apprentice

Replies are listed 'Best First'.
Re: Failed array attemp
by tobyink (Canon) on May 13, 2012 at 21:37 UTC

    You mean like this?

    push @z, (grep { $_ ~~ \@y } @x) ? 'y' : 'n';

    Shifting some of that into a sub might make it a little more readable...

    sub arrays_intersect { grep { $_ ~~ $_[1] } @{$_[0]} } push @z, arrays_intersect(\@x, \@y) ? 'y' : 'n';

    Or you could use Set::Scalar...

    my $x_set = Set::Scalar->new(@x); my $y_set = Set::Scalar->new(@y); push @z, $x_set->intersection($y_set)->empty ? 'n' : 'y';
    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
      Looks like exactly what I want but does not work. I got rid of syntax error by substituting ~~ with == but the results is wrong.
      @x = qw(1 4); @y = qw(1 2 3); push @z, (grep { $_ == \@y } @x) ? 'y' : 'n'; print $z[0]; print $z[1];

      $z[0] gives me now and $z1 is uninitilised.

        The ~~ was intentional. It's the smart match operator. == won't cut it.

        Smart match was introduced in Perl 5.9.3, about 6 years ago.

        perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

        You have to be using perl v5.10 or higher to use the smartmatch operator (~~).

        I keep getting syntax error and can't change the version so it must be old. Sorry to bother but anyway of making this work without smart catching? (can change slightly by I will be using this scheme a lot so I like the one line clean code. Thanks again.
        push @z, (grep { $_ ~~ \@y } @x) ? 'y' : 'n';
Re: Failed array attemp
by stevieb (Canon) on May 13, 2012 at 21:27 UTC

    imho, the ternary (?:) operator isn't really meant to perform side effects. It was designed to select from two separate values:

    $x = (1 == 1) ? 'true' : 'false'

    'true' is put into $x if 1==1, and 'false' otherwise. Here's a way that does what you need, still using the ternary operator:

    #!/usr/bin/perl use warnings; use strict; use List::Util qw (first); my @a = qw( 1 2 3 4 5 ); my @b = qw( 9 8 2 1 5 ); my @z; for my $elem ( @a ){ my $result = ( first { $elem == $_ } @b ) ? '+' : '-'; push @z, $result; } print @z;

    Output:

    ++--+

    Edit: Changed from using alpha to using numeric as to not confuse the OP with 'eq'.

    Update: Please see this post by Anomalous Monk as to why my choice of using first() from List::Util should be replaced with any() from List::MoreUtils. It's a drop-in replacement in fact.

      Thanks for that.If I should not really do it this way what would be a best and shortest method?
        for my $elem ( @a ){ push @z, $_ = ( first { $elem eq $_ } @b ) ? '+' : '-'; }
Re: Failed array attemp
by AnomalousMonk (Archbishop) on May 13, 2012 at 22:57 UTC

    A number of replies use the grep built-in to scan an array for the presence of an element of another array. The problem with  grep is that it will always scan the entire array even though the OPer only seems interested in the first occurrence of the element in the scanned array. List::MoreUtils::any will stop scanning at the first occurrence. For some value of the product of the sizes of the two arrays (100,000? 1,000,000? ...? Benchmark to find out), this difference in behavior will result in a significant performance win for any. For sufficiently small arrays, the difference is trivial.

      this difference in behavior will result in a significant performance win for any.

      It'll only save a constant factor and so it'll still be O(XY) whereas using a temporary hash would be O(X).

      The cost of using a hash would be additional storage on the order of O(Y) but, as you are already storing the array, this increase is just a constant factor.

      Comparatively, stopping at the first found element is better the more duplication there is in the array. But the more duplication in the array, the less the storage cost of using a temporary hash instead (for the theoretically significant improvement.)

      If you want efficiency, and aren't so short on storage that you can't do it, using a temporary hash is almost certainly the way to go.

      -sauoq
      "My two cents aren't worth a dime.";
Re: Failed array attemp
by sauoq (Abbot) on May 13, 2012 at 23:07 UTC
    I have two arrays and if an element in 1st array is in 2nd array I want to append "+" to array 3. If it's not I want to append '-' to array 3.

    Just doing exactly what you say you want (and using numeric comparison because you did) would give:

    for my $x (@x) { push @z, (grep {$_ == $x} @y) ? '+' : '-'; }
    If your arrays stay short, this method might be fine, but you might want to avoid iterating over @y for each and every element of @z. A better way to do it would be:
    my %k = map {($_, 1)} @y; for my $x (@x) { push @z2, (exists $k{$x}) ? '+' : '-'; }
    And if this is something you are doing a lot, you should just wrap it up in a sub so you can call it with references to two arrays and get a reference to a newly constructed array in return.

    Don't worry about having short code. My second example is a little longer but still more efficient. You should focus on writing good and readable code and then reusing it properly.

    -sauoq
    "My two cents aren't worth a dime.";
Re: Failed array attemp
by kcott (Archbishop) on May 14, 2012 at 00:05 UTC
    "... I want to finally start making my code shorter."

    You could use push() just once in the line:

    any{$x == $_ } @y ? push(@z, "y") : push(@z, "n");

    by writing:

    push @z, any {$x == $_ } @y ? "y" : "n";

    That's really just intended as an example. stevieb has provided an excellent response with a complete script above.

    "I really want to use the ? : ; scheme properly ..."

    Take a look at: Short form (ternary) if else. It discusses many aspects of ternary operator usage.

    -- Ken