2012-08-26 9 views
6

Así que todo lo que necesito es una forma sencilla de saber si dos matrices son idénticas en Perl. El orden no importa, por lo que estoy buscando algo como esto:¿Cómo puedo verificar si dos matrices contienen los mismos elementos en Perl?

my @a = (1, 2, 3);

my @b = (2, 3, 1);

my @c = (1, 2, 4);

&identical(@a, @b) devuelve 1

&identical(@a, @c) devuelve 0

Gracias!

+3

Es posible que desee elegir un nombre mejor. Ninguna de estas matrices son idénticas por definición (lo que incluye tener los mismos elementos en el mismo orden). Si no le importa el orden, el nombre debe reflejar eso. – cHao

+2

¿Pueden aparecer los elementos en una matriz más de una vez? – Zaid

Respuesta

5

Si está utilizando Perl 5.10 o mayor (y si no es así, que realmente debería actualizar), se puede utilizar el smart match operator:

use strict; 
use warnings; 

my @a = (1, 2, 3); 
my @b = (2, 3, 1); 
my @c = (1, 2, 4); 

#sort each of them (numerically) 
@a = sort { $a <=> $b } @a; 
@b = sort { $a <=> $b } @b; 
@c = sort { $a <=> $b } @c; 

if (@a ~~ @b) { 

    print "\@a and \@b are the same! (after sorting)\n"; 
} 
else { 

    print "nope\n"; 
} 

if (@a ~~ @c) { 

    print "\@a and \@c are the same! (after sorting)\n"; 
} 
else { 

    print "nope\n"; 
} 

También puede rodar su propia función:

use strict; 
use warnings; 

my @a = (1, 2, 3); 
my @b = (2, 3, 1); 
my @c = (1, 2, 4); 

print same_elements(\@a, \@b) . "\n"; 
print same_elements(\@a, \@c) . "\n"; 

#arguments are two array references 
sub same_elements { 

    my $array_ref_1 = shift; 
    my $array_ref_2 = shift; 
    my @arr1 = @$array_ref_1; 
    my @arr2 = @$array_ref_2; 

    #If they are not the same length, we are done. 
    if(scalar(@arr1) != scalar(@arr2)) { 

     return 0; 
    } 

    #sort them! 
    @arr1 = sort { $a <=> $b } @arr1; 
    @arr2 = sort { $a <=> $b } @arr2; 

    foreach my $i(0 .. $#arr1) { 

     if ($arr1[$i] != $arr2[$i]) { 
      return 0; 
     } 
    } 
    return 1; 
} 
+0

Downvoter: ¿me importa explicarlo? –

6

Puede contar los conteos de los elementos en un hash. Tener un (elemento => contar) hash, y aumentar el conteo cada vez que el primer conjunto tiene ese elemento, y hacia abajo cada vez que el otro lo tiene (o viceversa). Si las dos matrices tienen todos los mismos elementos, todos los valores en el hash será 0.

sub have_same_elements { 
    my ($arr1, $arr2) = @_; 
    my %counts =(); 
    $counts{$_} += 1 foreach (@$arr1); 
    $counts{$_} -= 1 foreach (@$arr2); 
    return !(grep { $_ != 0 } values %counts); 
} 


$a_and_b_same = have_same_elements(\@a, \@b); # will be true 
$a_and_c_same = have_same_elements(\@a, \@c); # will be false 

(Tenga en cuenta, esto podría o no podría funcionar con objetos que hacen su propia stringification. Claves hash no pueden ser referencias , por lo que Perl codifica las referencias a medida que las utiliza. Su secuenciador por defecto convierte las referencias en algo como ARRAY(0x12345678), lo que hace que las referencias sean distintas a menos que estén en la misma cosa, pero si un objeto hace su propia cadena y no devuelve cadenas distintas para diferentes referencias, esto probablemente se romperá. Para que lo sepas)

3

En primer lugar, tendrás que reconsiderar tu función.

identical(@a, @b); 

No pasa dos arrays a la función, pero pasa una única matriz con todos los elementos en ambas matrices. Es como si dicho:

identical(1, 2, 3, 2, 3, 1); 

Para que su función sea operativa, que tendrá que pasar references a sus matrices:

identical(\@a, \@b); 

yo diría que a prototype su subrutina, pero eso es probablemente va a causarte more problems que resolverá.

Si el orden no es importante, ordene las matrices antes de compararlas. Usted podría incluso ser capaz de hacer trampa ...

sub identical { 
    my $array_ref_1 = shift; 
    my $array_fef_2 = shift; 

    use Digest::SHA qw(sha1_hex); 

    if (ref($array_ref_1) ne "ARRAY") or (ref($array_ref_2) ne "ARRAY") { 
     return; #Error, need two array references 
    } 

    # Dereference Arrays 
    my @array_1 = @{$array_ref_1}; 
    my @array_2 = @{$array_ref_2}; 

    # Setup Arrays to be one big scalar 
    my $scalar_1 = join "\n", sort @array_1; 
    my $scalar_2 = join "\n", sort @array_2; 

    my $checksum_1 = sha1_hex $scalar_1; 
    my $checksum_2 = sha1_hex $scalar_2; 

    if ($checksum_1 eq $checksum_2) { 
    return 1; 
    } 
    else { 
    return 0_but_true; 

Unas pocas notas:

  • pudiera tener desreferencias, unidas, que genera la suma de comprobación, e hizo la comparación en un único estado. Los hice por separado para aclarar lo que estaba haciendo. Programáticamente, probablemente no haga ninguna diferencia.Perl optimizará todo de todos modos. Siempre voy por la claridad.
  • 0_but_true devuelve un 0, pero al mismo tiempo devuelve un valor verdadero. De esta forma, puede hacer algo como if (identical(\@A, \@B)) { para asegurarse de que la función funcionó. Entonces, puedes probar cero o uno.
  • Asegúrese de probar sus parámetros. Usé la función ref para hacer esto.
  • I engañado. Primero convertí las dos matrices ordenadas en escalares. Luego, utilicé la suma de comprobación sha1 para verificar que son iguales. Una suma de comprobación que usa la función sha1 debería ser bastante buena. Es muy poco probable que falle.

El problema real es lo que si tuviera matrices multi-revestido como este:

@a = ("this", "that", "the\nother"); 
@b = ("this", "that\nthe", "other"); 

Utilizando el join la manera que lo hice haría que los escalares resultantes sean iguales.

0

Creo que se puede escribir así en una forma que tenga el menor número hipótesis sobre el tipo de entrada que se está tratando con (sólo tiene que pasar la sub comparación apropiada):

use List::Util; 
sub identical { 
    my @this = @{ +shift }; 
    my @that = @{ +shift }; 
    my $cmp = shift // sub { shift eq shift }; 
    return '' unless @this == @that; 
    for my $idx (List::Util::shuffle keys @this) { 
    return '' unless $cmp->($this[$idx], $that[$idx]); 
    } 
    return 1; 
} 

que se comporta de esta manera:

0> identical([0..100], [0..100]) 
$res[0] = 1 

1> identical([0..100], ['0.0', 1..100]) 
$res[1] = '' 

2> identical([0..100], ['0.0', 1..100], sub {shift == shift}) 
$res[2] = 1 

3> identical(['0.66666666666', 0..10], [2/3, 0..10], sub {shift == shift}) 
$res[3] = '' 

4> identical(['0.66666666666', 0..10], [2/3, 0..10], sub {shift() - shift() < 1e-5}) 
$res[4] = 1 

# if you need this to be true check out https://stackoverflow.com/a/12127428/13992 
5> identical([0..100], [List::Util::shuffle(0..100)]) 
$res[5] = '' 
Cuestiones relacionadas