How to count the odd number of occurrences in Perl?

Viewed 202

I have a program in Perl that is supposed to count the number of times an element appears in an array, and prints out the value of the element if the number of times it appears is odd.

Here is my code.

#!/usr/bin/perl
use strict;
use warnings;

sub FindOddCount($)
{
    my @arraynumber = @_; 
    my $Even = 0;
    my $i = 0;
    my $j = 0;
    my $array_length = scalar(@_);
    for ($i = 0; $i <= $array_length; $i++)
    {   
        my $IntCount = 0;
        for ($j = 0; $j <= $array_length; $j++)
        {
            if ($arraynumber[$i] == $arraynumber[$j])
            {
                $IntCount++;
                print($j);
            }
        }
        $Even = $IntCount % 2;
        if ($Even != 0)
        {
            return $arraynumber[$i];
        }
    }   
    if ($Even == 0)
    {   
        return "none";
    }   
}

my @array1 = (1,1,2,2,3,3,4,4,5,5,6,7,7,7,7);
my @array2 = (10,10,7,7,6,6,2,2,3,3,4,4,5,5,6,7,7,7,7,10,10);
my @array3 = (6,6,10,7,7,6,6,2,2,3,3,4,4,5,5,6,7,7,7,7,10.10);
my @array4 = (10,10,7,7,2,2,3,3,4,4,5,5,7,7,7,7,10,10,6);
my @array5 = (6,6);
my @array6 = (1);

my $return_value1 = FindOddCount(@array1);
my $return_value2 = FindOddCount(@array2);
my $return_value3 = FindOddCount(@array3);
my $return_value4 = FindOddCount(@array4);
my $return_value5 = FindOddCount(@array5);
my $return_value6 = FindOddCount(@array6);

print "The Odd value for the first array is $return_value1\n";
print "The Odd value for the 2nd array is $return_value2\n ";
print "The Odd value for the 3rd array is $return_value3\n ";
print "The Odd value for the 4th array is $return_value4\n ";
print "The Odd value for the 5th array is $return_value5\n ";
print "The Odd value for the sixth array is $return_value6\n ";

Here are my results.

The Odd value for the first array is 15
The Odd value for the first array is 21
The Odd value for the first array is 21
The Odd value for the first array is 19
The Odd value for the first array is 2
The Odd value for the first array is 1

If you can't tell. It is printing the count of all of the elements of the array instead of returning the element that occurs an odd number of times. In addition I get this error.

Use of uninitialized value in numeric eq (==) at OddCount.pl line 17.

Line 17 is where the 1st array and the 2nd array are compared. Yet the values are clearly instantiated and they work when I print them out. What is the issue?

2 Answers

Build a frequency hash for an array then go through it to see which elements have odd counts

use warnings;
use strict;
use feature 'say';

my @ary = qw(7 o1 7 o2 o1 z z o1);  # o1,o2  appear odd number of times

my %freq;
++$freq{$_} for @ary;

foreach my $key (sort keys %freq) { 
    say "$key => $freq{$key}" if $freq{$key} & 1;
}

This is far simpler than the code in the question -- but which is easily fixed, too. See below.

Some notes

  • ++$freq{$_} increments the value for the key $_ in the hash %freq by 1, or it adds the key to the hash if it doesn't exist (by autovivification) and sets its value to one. So when an array is iterated over with this code in the end the hash %freq contains for keys the array elements and for their values the elements' counts

  • Test $n & 1 uses the bitwise AND -- it is true if $n has the lowest bit set, so if it is odd

  • That ++$freq{$_} for @ary; is a Statement Modifier, running the statement for each element of @ary where the current element is aliased by $_ variable

This prints

o1 => 3
o2 => 1

This printing of odd-frequency elements (if any) is sorted alphabetically in elements, just so. Please change to any particular order that may be needed, or let me know.


Comments on the code in the question, which is correct with two simple fixes.

  • It uses prototypes in a wrong way for the purpose, in sub FindOddCount($). I suspect that this isn't needed so let's not dwell on it -- just drop that and make it sub FindOddCount

  • The index in loops includes the length of the array (<=) so in the last iteration they attempt to index into the array past its last element. Off-by-one error. That can be fixed by changing the condition into < $array_length (instead of <=), but read on

  • There is no reason to use C-style loops, not even to iterate over the index. (Needed here since the position in the array is used.) Scripting languages provide for cleaner ways†

    foreach my $i1 (0 .. $#arraynumber) { 
        my $IntCount = 0;      
        foreach my $i2 (0 .. $#arraynumber) { 
            if ( $arraynumber[$i1] == $arraynumber[$i2] ) { 
                ...
    

    That 0..N is the range operator, which creates the list of numbers within that range. The syntax $#array_name is the index of the last element in the array @array_name. Exactly what's needed. So there is no need for the array length

  • Multiple (six) arrays, used to check the code, can be manipulated in far better and easier ways by using references; see the tutorial for complex data structures perldsc, and in particular the page perllol, for array-of-arrays

In short: when you remove the prototype and fix off-by-one error your code seems to be correct.


† And not only scripting ones -- for example, C++11 introduced the range-based for loop

for (auto var: container) ...  // really const auto&, or auto&, or auto&&

and the link (a standard reference) says

Used as a more readable equivalent to the traditional for loop [...]

Count the number of occurrences in a for loop using a hash. Then print the desired elements using grep, like so:

#!/usr/bin/env perl

use warnings;
use strict;
use feature qw( say );

my @array = (10,10,7,7,6,6,2,2,3,3,4,4,5,5,6,7,7,7,7,10,10);
my %cnt;

# Count each element of the array:
$cnt{$_}++ for @array;

# Print only the array elements that occurred an odd number of times,
# separated by ", ":
say join q{, }, grep { $cnt{$_} % 2 } @array;
# 6, 6, 6
Related