somebody
somebody

Reputation: 495

How can I check if all elements of an array are identical in Perl?

I have an array @test. What's the best way to check if each element of the array is the same string?

I know I can do it with a foreach loop but is there a better way to do this? I checked out the map function but I'm not sure if that's what I need.

Upvotes: 12

Views: 12986

Answers (6)

Vibhuti
Vibhuti

Reputation: 1634

I think, we can use List::MoreUtils qw(uniq)

my @uniq_array = uniq @array;
my $array_length = @uniq_array;
$array_length == 1 ? return 1 : return 0;

Upvotes: 3

Hemanth K
Hemanth K

Reputation: 51

You can check how many times the element in the array (@test) is repeated by counting it in a hash (%seen). You can check how many keys ($size) are present in the hash (%seen). If more than 1 key is present, you know that the elements in the array are not identical.

sub all_the_same {
    my @test = @_;
    my %seen;
    foreach my $item (@test){
      $seen{$item}++
    }
    my $size = keys %seen;
    if ($size == 1){
        return 1;
    }
    else{
        return 0;
    }
}

Upvotes: 3

FMc
FMc

Reputation: 42411

TIMTOWTDI, and I've been reading a lot of Mark Jason Dominus lately.

use strict;
use warnings;

sub all_the_same {
    my $ref = shift;
    return 1 unless @$ref;
    my $cmp = $ref->[0];
    my $equal = defined $cmp ?
        sub { defined($_[0]) and $_[0] eq $cmp } :
        sub { not defined $_[0] };
    for my $v (@$ref){
        return 0 unless $equal->($v);
    }
    return 1;
}

my @tests = (
    [ qw(foo foo foo) ],
    [ '', '', ''],
    [ undef, undef, undef ],
    [ qw(foo foo bar) ],
    [ '', undef ],
    [ undef, '' ]
);

for my $i (0 .. $#tests){
    print "$i. ", all_the_same($tests[$i]) ? 'equal' : '', "\n";
}

Upvotes: 4

Eugene Yarmash
Eugene Yarmash

Reputation: 149796

If the string is known, you can use grep in scalar context:

if (@test == grep { $_ eq $string } @test) {
 # all equal
}

Otherwise, use a hash:

my %string = map { $_, 1 } @test;
if (keys %string == 1) {
 # all equal
}

or a shorter version:

if (keys %{{ map {$_, 1} @test }} == 1) {
 # all equal
}

NOTE: The undefined value behaves like the empty string ("") when used as a string in Perl. Therefore, the checks will return true if the array contains only empty strings and undefs.

Here's a solution that takes this into account:

my $is_equal = 0;
my $string   = $test[0]; # the first element

for my $i (0..$#test) {
    last unless defined $string == defined $test[$i];
    last if defined $test[$i] && $test[$i] ne $string;
    $is_equal = 1 if $i == $#test;
}

Upvotes: 15

codeholic
codeholic

Reputation: 5848

I use List::Util::first for all similar purposes.

# try #0: $ok = !first { $_ ne $string } @test;
# try #1: $ok = !first { (defined $_ != defined $string) || !/\A\Q$string\E\z/ } @test;

# final solution
use List::Util 'first';
my $str = shift @test;
my $ok = !first { defined $$_ != defined $str || defined $str && $$_ ne $str } map \$_, @test;

I used map \$_, @test here to avoid problems with values that evaluate to false.

Note. As cjm noted fairly, using map defeats the advantage of first short-circuiting. So I tip my hat to Sinan with his first_index solution.

Upvotes: 2

Sinan Ünür
Sinan Ünür

Reputation: 118128

Both methods in the accepted post give you the wrong answer if @test = (undef, ''). That is, they declare an undefined value to be equal to the empty string.

That might be acceptable. In addition, using grep goes through all elements of the array even if a mismatch is found early on and using the hash more than doubles the memory used by elements of array. Neither of these would be a problem if you have small arrays. And, grep is likely to be fast enough for reasonable list sizes.

However, here is an alternative that 1) returns false for (undef, '') and (undef, 0), 2) does not increase the memory footprint of your program and 3) short-circuits as soon as a mismatch is found:

#!/usr/bin/perl

use strict; use warnings;

# Returns true for an empty array as there exist
# no elements of an empty set that are different
# than each other (see
# http://en.wikipedia.org/wiki/Vacuous_truth)

sub all_the_same {
    my ($ref) = @_;
    return 1 unless @$ref;
    my $cmpv = \ $ref->[-1];
    for my $i (0 .. $#$ref - 1)  {
        my $this = \ $ref->[$i];
        return unless defined $$cmpv == defined $$this;
        return if defined $$this
            and ( $$cmpv ne $$this );
    }
    return 1;
}

However, using List::MoreUtils::first_index is likely to be faster:

use List::MoreUtils qw( first_index );

sub all_the_same {
    my ($ref) = @_;
    my $first = \ $ref->[0];
    return -1 == first_index {
        (defined $$first != defined)
            or (defined and $_ ne $$first)
    } @$ref;
}

Upvotes: 10

Related Questions