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

How can I extract just the unique elements of an array? is very helpful if that's what I want to do. But I need a list of all the elements that are *not* unique. And I need each element in this output list to be, itself, unique, even if it occurs more than twice in the input list. And to make matters worse, I need to do this in a for loop where I do other operations on the input list, so that shifting off one element after another is not an option. What I have at present is
my @in = qw/ test foo test bar baz foo test /; my %multiples; my %out; for (@in) { if ($multiples{$_} == 1) { $out{$_} = 1; } $multiples{$_} = 1; } print "$_\n" for keys %out;
This does what I want, but seems clunky. Is there a cute idiom for this operation?

§ George Sherston

Comment on Get All Duplicated Elements in an Array (Once, without shifting)
Select or Download Code
•Re: Get All Duplicated Elements in an Array (Once, without shifting)
by merlyn (Sage) on Oct 14, 2002 at 16:19 UTC
Re: Get All Duplicated Elements in an Array (Once, without shifting)
by John M. Dlugosz (Monsignor) on Oct 14, 2002 at 19:47 UTC
    You could refine that to have an out list rather than an out hash. Every value you store in it has the value 1!

    my %multiples; my @out; for (@in) { if ($multiples{$_} == 1) { push @out, $_; } $multiples{$_} = 1; } print "@out";
    Now since it contains only one statement, you could write the if in suffix form. You could use ++ instead of setting the count to 1 in the unconditional statement.

    That gets down to:

    my %multiples; my @out; for (@in) { push @out, $_ if ($multiples{$_}++ == 1) } print "@out";
    That's tight enough that you can see how merlyn's form works. Just use the built-in looping mechanism of grep instead of your own foreach loop/push. It's the same thing.

    He used pre-increment == 2, which you'll note is the same thing as post-increment == 1. The latter is more directly equivilent to what you had originally, but the pre-increment is arguably more efficient (though I don't know if measurably faster in Perl).

Re: Get All Duplicated Elements in an Array (Once, without shifting)
by ehdonhon (Curate) on Oct 14, 2002 at 22:31 UTC
    my @in = qw/ test foo test bar baz foo test /; my %multiples; for (@in) { $multiples{$_}++; } for keys( %multiples ) { print "$_\n" if (--$multiples{$_}); }
Re: Get All Duplicated Elements in an Array (Once, without shifting)
by Flexx (Pilgrim) on Oct 15, 2002 at 00:02 UTC

    Of course, ++merlyns post shows what an elegant solution is... So that's the cute idiom you where asking for!

    Assuming you /can|want/ not process the duplicates right away, you could save memory by omitting the extra array @out. When you go over the array for the fist time, do the counting, then do the processing separately:

    my @in = qw(test foo test bar baz foo test); my %keycount; foreach (@in) { $keycount{$_}++; # do whatever else you need to do } foreach (keys %keycount) { #print $_ if $keycount > 1; print if $keycount{$_} > 1; # Thx, ++Aristotle! # or do something else with $_ }

    So long,
    Flexx

      That is, of course, print if $keycount{$_} > 1;

      Makeshifts last the longest.

        Yikes!!! Caught me with my pants way down... (you caught me back, I guess ;)

        But, it works if there are no duplicates! Umm... 8)

        Cheers,
        Flexx

        Update: struck a potentially offensive joke, that wasn't meant to be such.

Re: Get All Duplicated Elements in an Array (Once, without shifting)
by thor (Priest) on Oct 15, 2002 at 00:44 UTC
    Maybe I'm missing something, but...
    for(@in){ $hash{$_}++; } foreach my $key (keys %hash){ print "$key is lonely\n" if ($hash{$key} == 1); print "$key has friends\n" if ($hash{$key} != 1); }

    thor

    Update: changed %hash{$_}++ to $hash{$_}++ to make it correct.

      Well, the one thing you're missing is that %hash{$_}++ is illegal syntax, unless you've already projected forward about three years and are using Perl 6.

      -- Randal L. Schwartz, Perl hacker

        Are you really that bored that you have to rip on people for a typo? How old are you again?

        thor

        While it is true that %hash{$_}++ is illegal syntax, it is interesting to note that %hash->{$_}++ is not. (But it only works on actual hashes and not hash references.) AFAIK its a bug, but one that has basically become a feature.
        use Data::Dumper; use strict; use warnings; my %hash; %hash->{$_}=$_ foreach 0..5; print Dumper(\%hash); __END__ $VAR1 = { '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5 };
        It embarrassed the crap out of me when I discovered this. I was reviewing some code of my colleagues, (relatively new to perl at the time) and identified these as compile time errors. He politely told me what I was full of :-) and then showed me it compiled (and worked) fine. Luckily he had a few other subtle bugs that I found so I managed to avoid looking like a complete moron. :-)

        --- demerphq
        my friends call me, usually because I'm late....

        How much of an estimate is 'about' and are you still confident with your prediction? :P

        -=( Graq )=-