Remove elements from one array found in another

Discussion in 'Perl Misc' started by Bryan, May 6, 2004.

  1. Bryan

    Bryan Guest

    Hi, I need a function that will remove any elements of the second array
    from the first. I found a function that does mostly what I want but its
    not behaving the way I want in one case.

    For example, if I have two arrays;

    @a1 = (1, 2, 3, 4, 5);
    @a2 = (2, 4);

    @a1 = sub removeArrayElements(\@a1, \@a2);

    This works just fine, and @a1 = (1, 3, 5);

    But if @a1 is empty;
    @a1 = ();
    @a2 = (2, 4);

    The function returns @a1 = (4, 2), when I would like it to return ().

    I believe the basic functionality for this function is from the perl
    cookbook, so maybe Im using the wrong thing (don't have the cookbook
    handy). Or is this the right function and it just needs to be tweaked?

    Thanks,
    B

    sub removeArrayElements {
    my ($a, $b) = @_;
    my @a = @$a;
    my @b = @$b;
    my (@diff,%count);
    my (%HASH_A,%HASH_B);



    # build hashes to remove redundant elements
    @HASH_A{@a}=(); # hash slice, sets @a as keys of %HASH_A
    @HASH_B{@b}=();



    # set $count{$e} to 2 if the element exists in both hashes
    # $count{$e} will be 1 if the element is unique
    foreach my $e (keys %HASH_A,keys %HASH_B) { $count{$e}++ }



    # writes the hash key (the element) to @diff if it is unique
    foreach my $e (keys %count) {
    push @diff, $e if ($count{$e} == 1);
    }



    return @diff;
    }
    Bryan, May 6, 2004
    #1
    1. Advertising

  2. Bryan

    Chief S. Guest

    Bryan wrote:
    > Hi, I need a function that will remove any elements of the second array
    > from the first. I found a function that does mostly what I want but its
    > not behaving the way I want in one case.
    >
    > For example, if I have two arrays;
    >
    > @a1 = (1, 2, 3, 4, 5);
    > @a2 = (2, 4);


    Won't this do the trick?

    $seen{$_} = 1 for @a2;
    @a1 = grep ! $seen{$_}, @a1;

    --
    Chief S.
    Chief S., May 6, 2004
    #2
    1. Advertising

  3. Bryan

    Tore Aursand Guest

    On Thu, 06 May 2004 22:15:26 +0000, Bryan wrote:
    > sub removeArrayElements {
    > my ($a, $b) = @_;
    > my @a = @$a;
    > my @b = @$b;
    > my (@diff,%count);
    > my (%HASH_A,%HASH_B);
    >
    > # build hashes to remove redundant elements
    > @HASH_A{@a}=(); # hash slice, sets @a as keys of %HASH_A
    > @HASH_B{@b}=();
    >
    > # set $count{$e} to 2 if the element exists in both hashes
    > # $count{$e} will be 1 if the element is unique
    > foreach my $e (keys %HASH_A,keys %HASH_B) { $count{$e}++ }
    >
    > # writes the hash key (the element) to @diff if it is unique
    > foreach my $e (keys %count) {
    > push @diff, $e if ($count{$e} == 1);
    > }
    >
    > return @diff;
    > }


    Don't be so hard to yourself. As far as I can tell from your question,
    this function should do (untested);

    sub removeArrayElements {
    my $a1 = shift;
    my $a2 = shift;

    my %hash;
    $hash{$_}++ for ( @$a2 );

    my @new = grep { not $hash{$_} } @$a1;
    return \@new;
    }


    --
    Tore Aursand <>
    "War is too serious a matter to entrust to military men." (Georges
    Clemenceau)
    Tore Aursand, May 6, 2004
    #3
  4. Bryan wrote:
    >
    > Hi, I need a function that will remove any elements of the second array
    > from the first. I found a function that does mostly what I want but its
    > not behaving the way I want in one case.
    >
    > For example, if I have two arrays;
    >
    > @a1 = (1, 2, 3, 4, 5);
    > @a2 = (2, 4);
    >
    > @a1 = sub removeArrayElements(\@a1, \@a2);

    ^^^
    That is a syntax error.


    > This works just fine, and @a1 = (1, 3, 5);
    >
    > But if @a1 is empty;
    > @a1 = ();
    > @a2 = (2, 4);
    >
    > The function returns @a1 = (4, 2), when I would like it to return ().
    >
    > I believe the basic functionality for this function is from the perl
    > cookbook, so maybe Im using the wrong thing (don't have the cookbook
    > handy). Or is this the right function and it just needs to be tweaked?
    >
    >
    > sub removeArrayElements {
    >
    > [snip]


    This will do what you want:

    sub removeArrayElements {
    my ( $orig, $remove ) = @_;
    my %seen = map { $_ => 1 } @$remove;
    @$orig = grep !$seen{ $_ }, @$orig;
    }




    John
    --
    use Perl;
    program
    fulfillment
    John W. Krahn, May 7, 2004
    #4
  5. Bryan <> wrote:
    > Hi, I need a function that will remove any elements of the second array
    > from the first. I found a function that does mostly what I want but its
    > not behaving the way I want in one case.


    [...]
    sub removeArrayElements($$) {
    my %tmp;
    my @a = @{shift()};
    my @b = @{shift()};
    @tmp{@a} = (1) x @a;
    delete @tmp{@b};
    return keys %tmp;
    }



    --
    Glenn Jackman
    NCF Sysadmin
    Glenn Jackman, May 7, 2004
    #5
  6. Glenn Jackman <> writes:

    > Bryan <> wrote:
    > > Hi, I need a function that will remove any elements of the second array
    > > from the first. I found a function that does mostly what I want but its
    > > not behaving the way I want in one case.

    >
    > [...]
    > sub removeArrayElements($$) {


    What is the point of that prototype?

    I can see why you could want a (\@\@) prototype to save the caller
    putting \ in the call but I really can't see any use for ($$)

    > my %tmp;
    > my @a = @{shift()};
    > my @b = @{shift()};


    Why are you making copies of the arrays?

    > @tmp{@a} = (1) x @a;
    > delete @tmp{@b};
    > return keys %tmp;
    > }


    This is not order preseving. The OP said they wanted to remove
    elements from an array. Since arrays are ordered then the
    null-hypothesis should be that the OP wanted to preserve order.

    --
    \\ ( )
    . _\\__[oo
    .__/ \\ /\@
    . l___\\
    # ll l\\
    ###LL LL\\
    Brian McCauley, May 7, 2004
    #6
  7. "Chief S." <> writes:

    > Bryan wrote:
    > > Hi, I need a function that will remove any elements of the second
    > > array from the first. I found a function that does mostly what I
    > > want but its not behaving the way I want in one case.
    > > For example, if I have two arrays;
    > > @a1 = (1, 2, 3, 4, 5);
    > > @a2 = (2, 4);

    >
    > Won't this do the trick?
    >
    > $seen{$_} = 1 for @a2;
    > @a1 = grep ! $seen{$_}, @a1;


    That does not remove elements from the array @a1.

    That replaces the content of @a1 with a list which is the old content
    of @a1 less those elements that also appeared in @a2.

    Usually this distinction is not relevant but occasionally it may be.

    To answer the OP's question more pedantically

    my %seen;
    undef $seen{@a2}; = 1 for @a2;
    for my $i ( reverse 0 .. $#a1 ) {
    if ( exists $seen{$a1[$i]} ) {
    splice @a1,$i,1;
    }
    }

    Deja vu anyone?

    http://groups.google.com/groups?threadm=
    http://groups.google.com/groups?threadm=
    http://groups.google.com/groups?threadm=

    --
    \\ ( )
    . _\\__[oo
    .__/ \\ /\@
    . l___\\
    # ll l\\
    ###LL LL\\
    Brian McCauley, May 7, 2004
    #7
  8. Bryan

    Chief S. Guest

    > "Chief S." <> writes:
    >>$seen{$_} = 1 for @a2;
    >>@a1 = grep ! $seen{$_}, @a1;



    Brian McCauley wrote:
    > That does not remove elements from the array @a1.
    >
    > That replaces the content of @a1 with a list which is the old content
    > of @a1 less those elements that also appeared in @a2.
    >
    > Usually this distinction is not relevant but occasionally it may be.


    Is the relevance of the distinction related to the outcome -- that is,
    sometimes the resulting @a1 will be incorrect using the grep() method?

    Or is the relevance of the distinction related to process -- that is,
    you need to actually remove the items from the list, rather than
    building a new filtered list, because your larger programming logic is
    structured a certain way?

    Thanks for the clarification.

    --
    Chief S.
    Chief S., May 7, 2004
    #8
  9. "Chief S." <> writes:

    > > "Chief S." <> writes:
    > >>$seen{$_} = 1 for @a2;
    > >>@a1 = grep ! $seen{$_}, @a1;

    >
    >
    > Brian McCauley wrote:
    > > That does not remove elements from the array @a1.
    > > That replaces the content of @a1 with a list which is the old content
    > > of @a1 less those elements that also appeared in @a2.
    > > Usually this distinction is not relevant but occasionally it may be.

    >
    > Is the relevance of the distinction related to the outcome -- that is,
    > sometimes the resulting @a1 will be incorrect using the grep() method?
    >
    > Or is the relevance of the distinction related to process -- that is,
    > you need to actually remove the items from the list, rather than
    > building a new filtered list, because your larger programming logic is
    > structured a certain way?


    Er... I don't get the distinction you are drawing :)

    Basically it depends on if there are any references to elements of @a1
    floating around.

    my @a1 = qw/ cat dog rabbit /;
    my @a2 = qw/ dog /;
    my $r = \$a1[2]; # References 'rabbit'
    my %seen;
    $seen{$_} = 1 for @a2;
    @a1 = grep ! $seen{$_}, @a1;
    $$r = 'hare'; # Change 'rabbit' to 'hare'
    print "@a1\n"; # cat rabbit

    As I said above it's relatively rare that this is an issue.

    --
    \\ ( )
    . _\\__[oo
    .__/ \\ /\@
    . l___\\
    # ll l\\
    ###LL LL\\
    Brian McCauley, May 7, 2004
    #9
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. diffused
    Replies:
    9
    Views:
    710
    Oscar kind
    Aug 1, 2004
  2. Replies:
    3
    Views:
    530
  3. Replies:
    10
    Views:
    668
  4. Ari Brown
    Replies:
    4
    Views:
    111
    Elad Meidar
    Aug 30, 2007
  5. M.L.
    Replies:
    8
    Views:
    125
Loading...

Share This Page