Reputation: 495
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
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
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
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
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 undef
ined 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 undef
s.
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
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
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