En Perl, ¿hay una forma integrada de comparar dos matrices para la igualdad?


Tengo dos matrices de cadenas que me gustaría comparar para la igualdad:

my @array1 = ("part1", "part2", "part3", "part4");
my @array2 = ("part1", "PART2", "part3", "part4");

¿Hay una forma integrada de comparar matrices como la que hay para los escalares? Lo intenté:

if (@array1 == @array2) {...}

Pero solo evaluó cada matriz en contexto escalar, y así comparó la longitud de cada matriz.

Puedo rodar mi propia función para hacerlo, pero parece una operación de bajo nivel que debería haber una forma incorporada de hacerlo. ¿Lo hay?

Editar: lamentablemente, no tengo acceso a 5.10+ o componentes opcionales.

Author: G. Cito, 2009-10-22

13 answers

Existe el nuevo operador smart match :

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;

my @x = (1, 2, 3);
my @y = qw(1 2 3);

say "[@x] and [@y] match" if @x ~~ @y;

Con respecto a Array:: Compare :

Internamente el comparador compara los dos arrays usando join para convertir ambos arrays en strings y comparando las strings usando eq.

Supongo que es un método válido, pero mientras estemos usando comparaciones de cadenas, preferiría usar algo como:{[14]]}

#!/usr/bin/perl

use strict;
use warnings;

use List::AllUtils qw( each_arrayref );

my @x = qw(1 2 3);
my @y = (1, 2, 3);

print "[@x] and [@y] match\n" if elementwise_eq( \(@x, @y) );

sub elementwise_eq {
    my ($xref, $yref) = @_;
    return unless  @$xref == @$yref;

    my $it = each_arrayref($xref, $yref);
    while ( my ($x, $y) = $it->() ) {
        return unless $x eq $y;
    }
    return 1;
}

Si los arrays que está comparando son grandes, unirlos es va a hacer un montón de trabajo y consumir una gran cantidad de memoria que simplemente comparar cada elemento uno por uno.

Actualización: Por supuesto, uno debería probar tales afirmaciones. Puntos de referencia simples:

#!/usr/bin/perl

use strict;
use warnings;

use Array::Compare;
use Benchmark qw( cmpthese );
use List::AllUtils qw( each_arrayref );

my @x = 1 .. 1_000;
my @y = map { "$_" } 1 .. 1_000;

my $comp = Array::Compare->new;

cmpthese -5, {
    iterator => sub { my $r = elementwise_eq(\(@x, @y)) },
    array_comp => sub { my $r = $comp->compare(\(@x, @y)) },
};

Este es el peor escenario donde elementwise_eq tiene que pasar por todos y cada uno de los elementos en ambos arrays 1_000 veces y muestra:

             Rate   iterator array_comp
iterator    246/s         --       -75%
array_comp 1002/s       308%         --

Por otro lado, el mejor escenario es:

my @x = map { rand } 1 .. 1_000;
my @y = map { rand } 1 .. 1_000;
              Rate array_comp   iterator
array_comp   919/s         --       -98%
iterator   52600/s      5622%         --

iterator el rendimiento cae bastante rápido, sin embargo:

my @x = 1 .. 20, map { rand } 1 .. 1_000;
my @y = 1 .. 20, map { rand } 1 .. 1_000;
              Rate   iterator array_comp
iterator   10014/s         --       -23%
array_comp 13071/s        31%         --

No me fijé en la utilización de la memoria.

 55
Author: Sinan Ünür,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-29 22:32:12

Está la función is_deeply() de Test::More, que también mostrará exactamente dónde difieren las estructuras, o la función eq_deeply () de Test::Deep, que no requiere un arnés de prueba (y solo devuelve true o false).

 20
Author: Ether,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-22 20:15:27

No está incorporado, pero hay Array::Compare.

Esta es una de las operaciones que queda fuera del núcleo de Perl por lo que creo que son razones didácticas that es decir, si estás tratando de hacerlo, probablemente hay algo mal. El ejemplo más ilustrativo de esto, creo, es la ausencia de una función core read_entire_file; básicamente, proporcionar esa función en el núcleo llevaría a la gente a pensar que es una buena idea hacer eso, pero en cambio, Perl está diseñado de una manera que suavemente le empuja hacia el procesamiento de archivos línea a línea, que generalmente es mucho más eficiente y por lo demás una mejor idea, pero los programadores novatos rara vez se sienten cómodos con él y necesitan un poco de aliento para llegar allí.

Lo mismo se aplica aquí: probablemente hay una manera mucho mejor de hacer la determinación que está tratando de lograr comparando dos matrices. No necesariamente, pero probablemente. Así que Perl te está empujando a pensar en otras formas de lograr tu objetivo.

 14
Author: chaos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-22 19:40:40

Perl 5.10 le da el operador smart match.

use 5.010;

if( @array1 ~~ @array2 )
{
    say "The arrays are the same";
}

De lo contrario, como usted ha dicho, usted tendrá top roll su propio.

 8
Author: David Harris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-22 19:45:24

Mientras esté usando perl 5.10 o posterior, puede usar el operador smart match.

if (@array1 ~~ @array2) {...}
 7
Author: Quentin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-22 19:49:15

La solución más simple es más rápida:

#!/usr/bin/perl

use strict;
use warnings;

use Array::Compare;
use Benchmark qw( cmpthese );
use List::AllUtils qw( each_arrayref );

my @x = 1 .. 1_000;
my @y = map { "$_" } 1 .. 1_000;

my $comp = Array::Compare->new;

cmpthese -2, {
    iterator => sub { my $r = elementwise_eq(\(@x, @y)) },
    my_comp => sub { my $r = my_comp(\(@x, @y)) },
    array_comp => sub { my $r = $comp->compare(\(@x, @y)) },
};

@x = 1 .. 20, map { rand } 1 .. 1_000;
@y = 1 .. 20, map { rand } 1 .. 1_000;

cmpthese -2, {
    iterator => sub { my $r = elementwise_eq(\(@x, @y)) },
    my_comp => sub { my $r = my_comp(\(@x, @y)) },
    array_comp => sub { my $r = $comp->compare(\(@x, @y)) },
};

sub elementwise_eq {
    my ($xref, $yref) = @_;
    return unless  @$xref == @$yref;

    my $it = each_arrayref($xref, $yref);
    while ( my ($x, $y) = $it->() ) {
        return unless $x eq $y;
    }
    return 1;
}

sub my_comp {
    my ($xref, $yref) = @_;
    return unless  @$xref == @$yref;

    my $i;
    for my $e (@$xref) {
        return unless $e eq $yref->[$i++];
    }
    return 1;
}

Y resulta en perl 5, version 14, subversion 2 (v5.14.2) built for x86_64-linux-gnu-thread-multi:

             Rate   iterator array_comp    my_comp
iterator   1544/s         --       -67%       -80%
array_comp 4697/s       204%         --       -41%
my_comp    7914/s       413%        68%         --
               Rate   iterator array_comp    my_comp
iterator    63846/s         --        -1%       -75%
array_comp  64246/s         1%         --       -75%
my_comp    252629/s       296%       293%         --
 6
Author: Hynek -Pichi- Vychodil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-01 01:48:36

Para comprobar la igualdad de dos matrices pruebe esto. En el código dado, si %eq_or_not tiene algún valor entonces ambos arrays no son iguales de lo contrario son iguales.

my @array1 = ("part1", "part2", "part3", "part4");
my @array2 = ("part1", "PART2", "part3", "part4");

my %eq_or_not;

@eq_or_not{ @array1 } = undef;
delete @eq_or_not{ @array2 };
 2
Author: Pradeep Gupta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-01 10:33:11

Esta pregunta se ha convertido en un recurso muy útil. ++ for the benchmarks and discussion.

Como otros han señalado, la función smart match tuvo problemas y se está eliminando gradualmente en su forma actual. Hay alternativas que son "menos inteligentes" (y así evitar los problemas) y que son pequeños, bastante rápido y no tienen demasiadas dependencias no CENTRALES.

Puedes encontrar enlaces a algunas discusiones bastante buenas sobre la historia del futuro de ~~ mirando un par de entradas de blog por @brian d foy, y el archivo de correo p5p hilos de 2011 y 2012 de @rjbs.

Comparar matrices puede ser simple y divertido!

use v5.20;   
use match::smart; 
my @x = (1, 2, 3);       
my @y = qw(4 5 6);    
my @z = qw(4 5 6);   
say \@x |M| \@y ? "[\@x] and [\@y] match": "no match";  
say \@y |M| \@z ? "[\@y] and [\@z] match": "no match";

__END__                              
@y and @z match, @x and @y do not

... especialmente divertido si la matriz es simple. Pero una matriz puede ser algo complicado, y a veces quieren diferentes tipos de información de los resultados de la comparación. Para eso, Array::Compare puede hacer la comparación más fácil.

 2
Author: G. Cito,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-22 04:46:27

Si la carcasa es la única diferencia, simplemente puede usar:

if (lc "@array1" eq lc "@array2") {...}

Mientras que "@array1" devuelve lo mismo que join ( " ", @array1 )

 1
Author: tempire,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-25 18:58:18

Si el orden y los valores duplicados no importan, sino solo la igualdad de los valores (es decir, la comparación de conjuntos), podría usar Set::Scalar.

Sobrecarga operadores comunes como == o !=.

my @array1 = ("part1", "part2", "part3", "part4");
my @array2 = ("part1", "PART2", "part3", "part4");

if ( Set::Scalar->new(@array1) == Set::Scalar->new(@array2) ) {...}

Alternativamente, también hay Algorithm::Diff y List::Compare.

 1
Author: Archimedix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-17 13:21:22

Data::Cmp es otra opción reciente. La función cmp_data() funciona de manera similar al operador cmp (ver perlop para cmp uso).

Ejemplo:

use 5.10;
use Data::Cmp qw/cmp_data/;

my @array1 = ("part1", "part2", "part3", "part4");
my @array2 = ("part1", "PART2", "part3", "part4");
my @array3 = ("part1", "PART2", "part3", "part4");

# sample usage 
say "1 & 2 are different" if cmp_data(\@array1, \@array2) ;
sat "2 & 3 are the same" unless cmp_data(\@array2, \@array3) ;

También es posible comparar hashes y estructuras de datos anidadas más complicadas (dentro de lo razonable). Para un solo módulo sin dependencias no básicas, Data::Cmp es bastante "inteligente" ;-) ... errm quiero decir "útil".

 1
Author: G. Cito,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-16 19:20:32

Se podría usar la función grep en un contexto escalar ( http://perldoc.perl.org/functions/grep.html#grep-BLOCK-LIST )

(0 eq (grep {arra array1 [ne_] ne ne array2 [ _ _ ]} 0..$#array1)) if arra#array1 eq arra#array2;

HIH.

 0
Author: f_v,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-20 00:53:27

Si el único criterio es "son equivalentes o no?"y no la pregunta más compleja, "son equivalentes o no, y si son distintos, ¿cómo?"hay formas mucho más rápidas/feas de hacerlo. Por ejemplo, rompa la totalidad de cada matriz en dos escalares y compárelos.

Por ejemplo

my @array1 = ("part1", "part2", "part3", "part4");
my @array2 = ("part1", "PART2", "part3", "part4");

my $smash1 = join("", @array1);
my $smash2 = join("", @array2);

if ($smash1 eq $smash2)
{
  # equal
}
else
{
  #unequal
}

Sí, probablemente acabo de hacer llorar a Larry Wall.

 -2
Author: AlwaysLearning,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-30 22:51:43