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

Since I'm having a complete mental block, I thought I could use the help of my fellow monks.

I have two arrays of variable length, I need to traverse both and search non matching values. ie.

Array 1
[1, 2, 4, 5, 6, 8, 15];

Array 2
[1, 3, 4, 5, 6, 7, 9];


What needs to happen in my case is that a loop needs to traverse array 1 and test against each value in array 2, if it doesn't match, then push the value I'm testing against into a different array, for clarity we'll call it @add. If a value matches, then nothing has to happen, just skip to the next iteration. So to complete the thought, the first set of results would contain the following (2,8,15) these results would get pushed into the @add array.

On the other hand, I need to step through array 2 and find all non-matching values, then push them into an array called @delete.

My immediate solution follows, though I find it significantly ugly: (untested)

my @delete = (); my @add = (); my $found = ''; for my $form_id (@tax_ids) { $found = ''; for my $db_id (@db_state) { if ($db_id != $form_id) { next; } else { $found++; } } if (!$found) { push @add, $form_id; } }

And do the exact loop again for the @db_state array, find all non-matching characters and push them into the @delete array.

What's a better solution? Quicker, smoother, less ugly? :) Thanks!

Replies are listed 'Best First'.
Re: Supreme Laziness
by merlyn (Sage) on Sep 25, 2001 at 20:57 UTC
    { my %t; $t{$_} .= "1" for @tax_ids; $t{$_} .= "2" for @db_state; @add = grep $t{$_} eq "1", keys %t; @delete = grep $t{$_} eq "2", keys %t; }
    I might have the add/delete pairs backwards there. You jumped from "array 1" to "@tax_ids" in your example. {grin}

    -- Randal L. Schwartz, Perl hacker

      Oh man... That's beautiful!
Re: Supreme Laziness
by suaveant (Parson) on Sep 25, 2001 at 20:51 UTC
    Easy way...
    my %states; @states{@db_state} = (1) x @db_state; @add = grep { !exists $states{$_} } @tax_ids;
    should work... but untested. Put all states known in a hash, then grep tax_ids dropping any states you already have, by looking for them in the hash and keeping those that don't exist

                    - Ant
                    - Some of my best work - Fish Dinner

Re: Supreme Laziness
by dragonchild (Archbishop) on Sep 25, 2001 at 20:54 UTC
    my $max = $#arr1; $max = $#arr2 if $#arr2 < $max; foreach my $i (0 .. $max) { push @add, $arr1[$i] unless $arr1[$i] == $arr2[$i]; } if ($max < $#arr1) { push @add, $arr1[$_] for ($max .. $#arr1); } elsif ($max < $#arr2) { push @add, $arr2[$_] for ($max .. $#arr2); }

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

Re: Supreme Laziness
by tomhukins (Curate) on Sep 25, 2001 at 20:58 UTC

    How about something like this?

    my %array2 = map(($_ => undef), @array2); my @delete; # Traverse array1 foreach (@array1) { exists $array2{$_} && push(@delete, $_); } my %array1 = map(($_ => undef), @array1); my @add; # Traverse array2 foreach (@array2) { exists $array1{$_} && push(@add, $_); }
(tye)Re: Supreme Laziness
by tye (Sage) on Sep 25, 2001 at 23:12 UTC

    If the lists are actually sorted (as in your examples), especially if the lists could be very large, a merge might be a good choice. Anyone have a more idiomatically Perl way of writing a merge that doesn't copy nor modify the original lists?

    my @old= ( 1, 2, 4, 5, 6, 8, 15 ); my @new= ( 1, 3, 4, 5, 6, 7, 9 ); my( $i, $j )= ( 0+@old, 0+@new ); my( @add, @del ); while( $i || $j ) { if( ! $j || $i && $new[$j-1] < $old[$i-1] ) { push @del, $old[--$i]; } elsif( ! $i || $j && $old[$i-1] < $new[$j-1] ) { push @add, $new[--$j]; } else { $i--; $j--; } } print "del: @del\n"; print "add: @add\n"; __END__ del: 15 8 2 add: 9 7 3

            - tye (but my friends call me "Tye")