¿Cómo puedo verificar si una matriz Perl contiene un valor particular?

239

Estoy tratando de encontrar una manera de verificar la existencia de un valor en una matriz sin iterar a través de la matriz.

Estoy leyendo un archivo para un parámetro. Tengo una larga lista de parámetros con los que no quiero tratar. Coloqué estos parámetros no deseados en una matriz @badparams.

Quiero leer un nuevo parámetro y, si no existe @badparams, procesarlo. Si existe @badparams, vaya a la siguiente lectura.

Mel
fuente
3
Para el registro, la respuesta depende de su situación. Parece que desea realizar búsquedas repetidas, por lo que usar un hash como sugiere jkramer es bueno. Si solo quisiera hacer una búsqueda, también podría iterar. (¡Y en algunos casos es posible que desee realizar una búsqueda binaria en lugar de usar un hash!)
Cascabel
55
perldoc -f grep
Ether
66
Para el registro (y esto puede ser totalmente inaplicable a su situación), generalmente es una mejor idea identificar 'buenos valores' e ignorar el resto en lugar de tratar de eliminar los 'malos valores' conocidos. La pregunta que debe hacer es si es posible que haya algunos valores malos que aún no conozca.
Grant McLean

Respuestas:

187

Simplemente convierta la matriz en un hash:

my %params = map { $_ => 1 } @badparams;

if(exists($params{$someparam})) { ... }

También puede agregar más parámetros (únicos) a la lista:

$params{$newparam} = 1;

Y luego obtenga una lista de parámetros (únicos):

@badparams = keys %params;
jkramer
fuente
38
Para el registro, este código todavía itera a través de la matriz. La llamada map {} simplemente hace que esa iteración sea muy fácil de escribir.
Kenny Wyland
3
Solo haría esto si sus valores en @badparams son pseudoestáticos y planea hacer muchas comprobaciones en el mapa. No recomendaría esto para un solo cheque.
Aaron T Harris
¿No va a explotar una matriz con varios elementos con el mismo valor?
Rob Wells
3
@RobWells no, funcionará bien. La próxima vez que vea el mismo valor, simplemente sobrescribirá la entrada en el hash, que en este caso lo establece en1 nuevo.
andrewrjones
222

Mejor propósito general: especialmente matrices cortas (1000 elementos o menos) y codificadores que no están seguros de qué optimizaciones se adaptan mejor a sus necesidades.

# $value can be any regex. be safe
if ( grep( /^$value$/, @array ) ) {
  print "found it";
}

Se ha mencionado que grep pasa por todos los valores, incluso si el primer valor de la matriz coincide. Esto es cierto, sin embargo, grep sigue siendo extremadamente rápido en la mayoría de los casos . Si está hablando de matrices cortas (menos de 1000 elementos), la mayoría de los algoritmos serán bastante rápidos de todos modos. Si está hablando de matrices muy largas (1,000,000 artículos) grep es aceptablemente rápido independientemente de si el artículo es el primero o el medio o el último en la matriz.

Casos de optimización para matrices más largas:

Si su matriz está ordenada , use una "búsqueda binaria".

Si la misma matriz se busca repetidamente muchas veces, cópiela primero en un hash y luego verifique el hash. Si le preocupa la memoria, mueva cada elemento de la matriz al hash. Más memoria eficiente pero destruye la matriz original.

Si se buscan repetidamente los mismos valores dentro de la matriz, construya un caché de forma perezosa. (a medida que se busca cada elemento, primero verifique si el resultado de la búsqueda se almacenó en un hash persistente. Si el resultado de la búsqueda no se encuentra en el hash, luego busque la matriz y coloque el resultado en el hash persistente para que la próxima vez búscalo en el hash y salta la búsqueda).

Nota: estas optimizaciones solo serán más rápidas cuando se trate de matrices largas. No lo optimices en exceso.

Aaron T Harris
fuente
12
La doble tilde se introdujo en Perl 5.10
pausa hasta nuevo aviso.
15
@DennisWilliamson ... y en 5.18 se considera experimental .
Xaerxess
55
Evite smartmatch en el código de producción. Es inestable / experimental hasta nuevo aviso.
Vector Gorgoth
1
También me parece más legible, pero No usar dice que no es eficiente y comprueba cada elemento, incluso si es el primero.
giordano
77
No utilice if ("valor" ~~ @array). ~~ es una característica experimental llamada Smartmatch. El experimento parece considerarse un fracaso y se eliminará o modificará en una versión futura de Perl.
yahermann
120

Puede usar la función smartmatch en Perl 5.10 de la siguiente manera:

Para la búsqueda de valor literal, hacer a continuación hará el truco.

if ( "value" ~~ @array ) 

Para la búsqueda escalar, hacer lo siguiente funcionará como lo hizo anteriormente.

if ($val ~~ @array)

Para la matriz en línea que se muestra a continuación, funcionará como se indica arriba.

if ( $var ~~ ['bar', 'value', 'foo'] ) 

En Perl 5.18, smartmatch se marca como experimental, por lo tanto, debe desactivar las advertencias activando pragma experimental agregando a continuación a su script / módulo:

use experimental 'smartmatch';

Alternativamente, si desea evitar el uso de smartmatch, entonces, como Aaron dijo, use:

if ( grep( /^$value$/, @array ) ) {
  #TODO:
}
Mapa de bits
fuente
44
Esto es bueno, pero parece ser nuevo en Perl 5.10. Pasó algún tiempo antes de descubrir por qué recibo errores de sintaxis.
Igor Skochinsky
17
Advertencia: es posible que desee evitar este, ya que el operador tiene un comportamiento aparentemente diferente en diferentes versiones y, mientras tanto, se ha marcado como experimental . Entonces, a menos que tenga control total sobre su versión de Perl (y quién tiene eso), probablemente debería evitarlo.
Laberinto
1
Me gusta esta explicación sobre por qué use experimental 'smartmatch'se recomienda la configuración . Como tengo control de mi versión perl (sistema interno), uso la no warnings 'experimental::smartmatch';declaración.
lepe
43

Esta publicación de blog discute las mejores respuestas a esta pregunta.

Como breve resumen, si puede instalar módulos CPAN, las soluciones más legibles son:

any(@ingredients) eq 'flour';

o

@ingredients->contains('flour');

Sin embargo, un idioma más común es:

any { $_ eq 'flour' } @ingredients

¡Pero por favor no use la first()función! No expresa la intención de su código en absoluto. No utilice el ~~operador "Smart match": está roto. Y no use grep()ni la solución con un hash: iteran por toda la lista.

any() se detendrá tan pronto como encuentre su valor.

Echa un vistazo a la publicación del blog para más detalles.

mascota
fuente
8
las necesidadesuse List::Util qw(any);. List::Utilestá en los módulos principales .
Onlyjob
13

Método 1: grep (puede ser cuidadoso mientras se espera que el valor sea una expresión regular).

Intente evitar el uso grep, si observa los recursos.

if ( grep( /^$value$/, @badparams ) ) {
  print "found";
}

Método 2: búsqueda lineal

for (@badparams) {
    if ($_ eq $value) {
       print "found";
       last;
    }
}

Método 3: usa un hash

my %hash = map {$_ => 1} @badparams;
print "found" if (exists $hash{$value});

Método 4: smartmatch

(agregado en Perl 5.10, marcado es experimental en Perl 5.18).

use experimental 'smartmatch';  # for perl 5.18
print "found" if ($value ~~ @badparams);

Método 5: usar el módulo List::MoreUtils

use List::MoreUtils qw(any);
@badparams = (1,2,3);
$value = 1;
print "found" if any {$_ == $value} @badparams;
Kamal Nayan
fuente
12

El punto de referencia de @eakssjo está roto: mide la creación de hashes en bucle frente a la creación de expresiones regulares en bucle. Versión fija (además he agregado List::Util::firsty List::MoreUtils::any):

use List::Util qw(first);
use List::MoreUtils qw(any);
use Benchmark;

my @list = ( 1..10_000 );
my $hit = 5_000;
my $hit_regex = qr/^$hit$/; # precompute regex
my %params;
$params{$_} = 1 for @list;  # precompute hash
timethese(
    100_000, {
        'any' => sub {
            die unless ( any { $hit_regex } @list );
        },
        'first' => sub {
            die unless ( first { $hit_regex } @list );
        },
        'grep' => sub {
            die unless ( grep { $hit_regex } @list );
        },
        'hash' => sub {
            die unless ( $params{$hit} );
        },
    });

Y resultado (es para 100_000 iteraciones, diez veces más que en la respuesta de @eakssjo):

Benchmark: timing 100000 iterations of any, first, grep, hash...
       any:  0 wallclock secs ( 0.67 usr +  0.00 sys =  0.67 CPU) @ 149253.73/s (n=100000)
     first:  1 wallclock secs ( 0.63 usr +  0.01 sys =  0.64 CPU) @ 156250.00/s (n=100000)
      grep: 42 wallclock secs (41.95 usr +  0.08 sys = 42.03 CPU) @ 2379.25/s (n=100000)
      hash:  0 wallclock secs ( 0.01 usr +  0.00 sys =  0.01 CPU) @ 10000000.00/s (n=100000)
            (warning: too few iterations for a reliable count)
Xaerxess
fuente
66
Si desea probar varios elementos, crear el hash de antemano le ahorra tiempo. Pero si solo desea saber si contiene un solo elemento, entonces todavía no tiene el hash. Por lo tanto, crear el hash debería ser parte del tiempo de computación. Más aún para la expresión regular: necesita una nueva expresión regular para cada elemento que busca.
fishinear
1
@fishinear Es cierto, pero si solo está interesado en una verificación, no en varias, obviamente es una microoptimización incluso preguntarse qué método es más rápido porque esos microsegundos no importan. Si desea rehacer esta verificación, el hash es el camino a seguir, ya que el costo de crear hash una vez es lo suficientemente pequeño como para ignorarlo. Los puntos de referencia anteriores miden solo diferentes formas de prueba, sin incluir ninguna configuración. Sí, esto puede ser inválido en su caso de uso, pero de nuevo, si está haciendo una sola comprobación, debe usar lo que sea más legible para usted y sus compañeros.
Xaerxess
10

Aunque es conveniente de usar, parece que la solución de conversión a hash cuesta bastante rendimiento, lo cual fue un problema para mí.

#!/usr/bin/perl
use Benchmark;
my @list;
for (1..10_000) {
    push @list, $_;
}

timethese(10000, {
  'grep'    => sub {
            if ( grep(/^5000$/o, @list) ) {
                # code
            }
        },
  'hash'    => sub {
            my %params = map { $_ => 1 } @list;
            if ( exists($params{5000}) ) {
                # code
            }
        },
});

Salida de prueba de referencia:

Benchmark: timing 10000 iterations of grep, hash...
          grep:  8 wallclock secs ( 7.95 usr +  0.00 sys =  7.95 CPU) @ 1257.86/s (n=10000)
          hash: 50 wallclock secs (49.68 usr +  0.01 sys = 49.69 CPU) @ 201.25/s (n=10000)
aksel
fuente
55
El uso List::Util::firstes más rápido ya que deja de iterar cuando encuentra una coincidencia.
RobEarl
1
-1 Su punto de referencia tiene defectos, grepes significativamente más lento que crear hash y realizar búsquedas, ya que el primero es O (n) y el último O (1). Simplemente haga la creación de hash solo una vez (fuera del ciclo) y precalcule la expresión regular para medir solo los métodos ( vea mi respuesta ).
Xaerxess
44
@Xaerxess: en mi caso, quería hacer una búsqueda, así que creo que es justo contar tanto la creación de hash / regex como la búsqueda / grep. Si la tarea sería hacer muchas búsquedas, supongo que su solución es mejor.
aksel
3
Si desea hacer solo una iteración, la diferencia es indistinguible entre cualquiera de los métodos que elija, por lo que cualquier punto de referencia es incorrecto ya que es una microoptimización malvada en este caso.
Xaerxess
2
La expresión regular se compila solo una vez, ya que tiene la bandera 'o'.
Apoc
3

@files es una matriz existente

my @new_values =  grep(/^2[\d].[\d][A-za-z]?/,@files);

print join("\n", @new_values);

print "\n";

/^2[\dfont>.[\dfont>[A-za-zfont>?/ = valores a partir de 2 aquí puede poner cualquier expresión regular

Rohan
fuente
2

Ciertamente quieres un hash aquí. Coloque los parámetros incorrectos como claves en el hash, luego decida si existe un parámetro particular en el hash.

our %bad_params = map { $_ => 1 } qw(badparam1 badparam2 badparam3)

if ($bad_params{$new_param}) {
  print "That is a bad parameter\n";
}

Si está realmente interesado en hacerlo con una matriz, mire List::UtiloList::MoreUtils

David M
fuente
0

hay dos maneras de hacer esto. Puede usar arrojar los valores en un hash para una tabla de búsqueda, como lo sugieren las otras publicaciones. (Agregaré solo otro idioma).

my %bad_param_lookup;
@bad_param_lookup{ @bad_params } = ( 1 ) x @bad_params;

Pero si se trata de datos de caracteres principalmente de palabras y no demasiados meta, puede volcarlos en una alternancia de expresiones regulares:

use English qw<$LIST_SEPARATOR>;

my $regex_str = do { 
    local $LIST_SEPARATOR = '|';
    "(?:@bad_params)";
 };

 # $front_delim and $back_delim being any characters that come before and after. 
 my $regex = qr/$front_delim$regex_str$back_delim/;

Esta solución tendría que ajustarse para los tipos de "valores incorrectos" que está buscando. Y de nuevo, puede ser totalmente inapropiado para ciertos tipos de cadenas, por lo que se debe advertir .

Axeman
fuente
1
También puede escribir @bad_param_lookup{@bad_params} = (), pero necesitaría usar existspara probar la membresía.
Greg Bacon
-1
my @badparams = (1,2,5,7,'a','zzz');

my $badparams = join('|',@badparams);   # '|' or any other character not present in params

foreach my $par (4,5,6,7,'a','z','zzz')
{
    if ($badparams =~ /\b$par\b/)
    {
        print "$par is present\n";
    }
    else
    {
        print "$par is not present\n";
    }
}

Es posible que desee verificar la consistencia numérica de espacios iniciales

Sarga
fuente