2010-02-21 23 views
10

Tengo una matriz @test. ¿Cuál es la mejor manera de verificar si cada elemento de la matriz es la misma cadena?¿Cómo puedo verificar si todos los elementos de una matriz son idénticos en Perl?

Sé que puedo hacerlo con un lazo foreach, pero ¿hay una mejor manera de hacerlo? Revisé la función de mapa, pero no estoy seguro de si eso es lo que necesito.

+0

La calidad de su solución depende realmente de lo que está haciendo en ese bucle foreach. Sí, hay muchas formas de hacerlo, pero ¿por qué siente que le falta su solución actual? –

+1

¿Por qué no codifica y publica esa solución "foreach", para que podamos comentar? – lexu

+2

a todos los carteles preocupados por undef, no asumiría que "comprueba si cada elemento de la matriz es la misma cadena" incluye la posibilidad de undefs; se define una "cadena" por definición :). (aunque sin duda es algo que debe tener en cuenta) – ysth

Respuesta

11

Si se conoce la secuencia, puede utilizar grep en contexto escalar:

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

De lo contrario, utilice un hash:

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

o una versión más corta:

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

NOTA: El valor interno undef se comporta como la cadena vacía ("") cuando se usa como una cadena en Perl. Por lo tanto, las comprobaciones serán verdaderas si la matriz contiene solo cadenas vacías y undef s.

Aquí es una solución que tenga esto en cuenta:

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; 
} 
+3

Malo. Siempre tendrá que atravesar toda la matriz, incluso si no coinciden en el primer elemento. – codeholic

+1

Lo que hice antes de ver esta respuesta fue ordenar la matriz y verificar si el primer y el último elemento son iguales. Gracias por el código con el hash. Todavía estoy aprendiendo a usar el mapa correctamente. – somebody

+1

@Quick Joe Smith: No es que falte mi solución actual. Solo buscaba diferentes formas de hacer lo mismo, y básicamente uso el código más corto para que se vea bien: P – somebody

2

Uso List::Util::first para todos los propósitos similares.

# 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; 

que utilizan map \$_, @test aquí para evitar problemas con los valores que se evalúan como falso.

Nota. Como cjm notó bastante, usar map derrota la ventaja del primer cortocircuito. Así que le puse mi sombrero a Sinan con su solución first_index.

+0

Pero, ¿y si '@ test' contiene la cadena vacía (o 0, o' undef')? Su prueba establecerá '$ ok' en verdadero cuando sea falso. – cjm

+0

Ok. Voy a corregir – codeholic

+0

Pero no es la prueba lo que está mal. Es el hecho de que 'first' devuelve el elemento de' @ test' que pasó la prueba. No hay forma de distinguir entre un 'undef' en' @ test' y la imposibilidad de encontrar una coincidencia. – cjm

10

ambos métodos en el puesto aceptado le dan la respuesta equivocada si @test = (undef, ''). Es decir, declaran que un valor indefinido es igual a la cadena vacía.

Eso podría ser aceptable. Además, el uso de grep pasa por todos los elementos de la matriz, incluso si se encuentra una falta de coincidencia desde el principio y el uso del hash más que duplica la memoria utilizada por los elementos de la matriz. Ninguno de estos sería un problema si tiene matrices pequeñas. Y, es probable que grep sea lo suficientemente rápido para un tamaño de lista razonable.

Sin embargo, aquí es una alternativa que 1) devuelve false para (undef, '') y (undef, 0), 2) no aumenta el consumo de memoria de su programa y 3) cortocircuitos tan pronto como una falta de coincidencia se encuentra:

#!/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; 
} 

Sin embargo, el uso de List::MoreUtils::first_index es probable que sea más rápido:

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; 
} 
+0

+1 Interesante respuesta. Publiqué una variante de tu primer método (pero avísame si pasé por alto algo). – FMc

+0

Um, realmente deberías probar tu código antes de publicarlo. Quieres 'my $ ref = \ @_;' en vour sub 'my ($ ref) = @_;' está poniendo el primer elemento o el conjunto pasado en $ ref. – htaccess

+0

@htaccess Exactamente ... se supone que debes llamar a 'all_the_same' con una referencia a la matriz que estás verificando como el único argumento. –

4

TIMTOWTDI, y he estado leyendo un montón de Mark Jason Dominus últimamente.

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"; 
} 
+0

Lo usé, funciona perfectamente. Sin embargo, tiene un pequeño defecto. devolución a menos que $ igual -> ($ v); debería ser return 0 a menos que $ equal -> ($ v); – user2050516

+0

@ user2050516 En el caso que nos ocupa, no importa si devolvemos 'undef' o' 0' (ambos son falsos). – FMc

+0

hasta ayer tuve la misma opinión, pero exactamente este error no regresó undef o 0 jodido. Vea a continuación esas son dos cosas, undef es un valor, nada es nada. return undef; devolución; ¿Dónde juega un papel? Si haces esto: @ array = (all_the_same ([0,1]), all_the_same ([1,1])); print @array, "\ n"; Gran sorpresa, tendrá un elemento en la matriz. – user2050516

3

Puede comprobar el número de veces que el elemento de la matriz (@test) se repite contando en un hash (% visto). Puede verificar cuántas teclas ($ size) están presentes en el hash (% visto).Si hay más de 1 clave presente, sabrá que los elementos en la matriz no son idénticos.

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; 
    } 
} 
+0

¡Esto funciona para mí! Gracias Hemanth! – Vibhuti

2

pienso, podemos utilizar la Lista :: MoreUtils qw (uniq)

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