Verificando si los elementos de una matriz están en otra matriz en PHP

130

Tengo dos matrices en PHP de la siguiente manera:

Personas:

Array
(
    [0] => 3
    [1] => 20
)

Criminales buscados:

Array
(
    [0] => 2
    [1] => 4
    [2] => 8
    [3] => 11
    [4] => 12
    [5] => 13
    [6] => 14
    [7] => 15
    [8] => 16
    [9] => 17
    [10] => 18
    [11] => 19
    [12] => 20
)

¿Cómo verifico si alguno de los elementos Personas está en la matriz de Wanted Criminals ?

En este ejemplo, debería regresar trueporque 20está en Wanted Criminals .

Philip Morton
fuente

Respuestas:

204

Puedes usar array_intersect().

$result = !empty(array_intersect($people, $criminals));
Greg
fuente
8
No se puede usar empty () con otra cosa que no sea una variable.
grantwparks
@grantwparks, entonces, ¿por qué en los documentos PHP acerca de esta función dicen "Devuelve FALSE si var existe y tiene un valor no vacío, no cero. De lo contrario, devuelve TRUE. Las siguientes cosas se consideran vacías: array () (una matriz vacía ) "? Fuente: php.net/manual/en/function.empty.php
Pere
55
Desde la página a la que se vinculó: "Antes de PHP 5.5, empty () solo admite variables; cualquier otra cosa dará como resultado un error de análisis. En otras palabras, lo siguiente no funcionará: empty (trim ($ name)). En cambio, use trim ($ name) == false ".
grantwparks
9
Como se mencionó en los comentarios, descubrí que !empty no funciona como se esperaba . En cambio, usé count():!count(array_intersect($people, $criminals));
Mattios550
3
¿Por qué está marcado como la respuesta con 65 votos arriba cuando arroja Error fatal: no se puede usar el valor de retorno de la función en el contexto de escritura?
Dave Heq
31

No hay nada de malo en usar array_intersect () y count () (en lugar de vacío).

Por ejemplo:

$bFound = (count(array_intersect($criminals, $people))) ? true : false;
papsia
fuente
2
No tiene nada de malo, pero count()no se considera eficiente (si te importa la micro optimización, eso es)
Jake A. Smith
23

Si 'vacío' no es la mejor opción, ¿qué pasa con esto?

if (array_intersect($people, $criminals)) {...} //when found

o

if (!array_intersect($people, $criminals)) {...} //when not found
ihtus
fuente
22

Ese código no es válido ya que solo puede pasar variables a construcciones de lenguaje. empty()Es una construcción del lenguaje.

Tienes que hacer esto en dos líneas:

$result = array_intersect($people, $criminals);
$result = !empty($result);
Paul Dragoonis
fuente
El problema no es que es una construcción de lenguaje. El problema es que espera una referencia y Greg está pasando un valor.
Artefacto
1
@Artefacto, de php.net "Nota: Debido a que esta es una construcción de lenguaje y no una función, no se puede llamar usando funciones variables". Es exactamente como dijo Paul.
grantwparks
17

Prueba de rendimiento para in_array vs array_intersect:

$a1 = array(2,4,8,11,12,13,14,15,16,17,18,19,20);

$a2 = array(3,20);

$intersect_times = array();
$in_array_times = array();
for($j = 0; $j < 10; $j++)
{
    /***** TEST ONE array_intersect *******/
    $t = microtime(true);
    for($i = 0; $i < 100000; $i++)
    {
        $x = array_intersect($a1,$a2);
        $x = empty($x);
    }
    $intersect_times[] = microtime(true) - $t;


    /***** TEST TWO in_array *******/
    $t2 = microtime(true);
    for($i = 0; $i < 100000; $i++)
    {
        $x = false;
        foreach($a2 as $v){
            if(in_array($v,$a1))
            {
                $x = true;
                break;
            }
        }
    }
    $in_array_times[] = microtime(true) - $t2;
}

echo '<hr><br>'.implode('<br>',$intersect_times).'<br>array_intersect avg: '.(array_sum($intersect_times) / count($intersect_times));
echo '<hr><br>'.implode('<br>',$in_array_times).'<br>in_array avg: '.(array_sum($in_array_times) / count($in_array_times));
exit;

Aquí están los resultados:

0.26520013809204
0.15600109100342
0.15599989891052
0.15599989891052
0.1560001373291
0.1560001373291
0.15599989891052
0.15599989891052
0.15599989891052
0.1560001373291
array_intersect avg: 0.16692011356354

0.015599966049194
0.031199932098389
0.031200170516968
0.031199932098389
0.031200885772705
0.031199932098389
0.031200170516968
0.031201124191284
0.031199932098389
0.031199932098389
in_array avg: 0.029640197753906

in_array es al menos 5 veces más rápido. Tenga en cuenta que nos "rompemos" tan pronto como se encuentre un resultado.

Frank Forte
fuente
Gracias por el punto de referencia. Entonces, si sabe que está manejando matrices pequeñas, es mejor quedarse array_intersect().
Tokeeen.com
issetEs aún más rápido. Y podría usar bool val para habilitar o deshabilitar. Además, los valores de búsqueda como clave se aseguran de no tener duplicados. ´array_intersect avg: 0.52077736854553; en_array promedio: 0.015597295761108; promedio de isset: 0.0077081203460693´
cottton
1

También puede usar in_array de la siguiente manera:

<?php
$found = null;
$people = array(3,20,2);
$criminals = array( 2, 4, 8, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);
foreach($people as $num) {
    if (in_array($num,$criminals)) {
        $found[$num] = true;
    } 
}
var_dump($found);
// array(2) { [20]=> bool(true)   [2]=> bool(true) }

Si bien array_intersect es ciertamente más conveniente de usar, resulta que no es realmente superior en términos de rendimiento. También creé este script:

<?php
$found = null;
$people = array(3,20,2);
$criminals = array( 2, 4, 8, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20);
$fastfind = array_intersect($people,$criminals);
var_dump($fastfind);
// array(2) { [1]=> int(20)   [2]=> int(2) }

Luego, ejecuté ambos fragmentos respectivamente en: http://3v4l.org/WGhO7/perf#tabs y http://3v4l.org/g1Hnu/perf#tabs y verifiqué el rendimiento de cada uno. Lo interesante es que el tiempo total de la CPU, es decir, el tiempo del usuario + el tiempo del sistema es el mismo para PHP5.6 y la memoria también es la misma. El tiempo total de CPU en PHP5.4 es menor para in_array que array_intersect, aunque marginalmente.

slevy1
fuente
Los resultados son engañosos. Ejecutarlo solo una vez es demasiado rápido para medir cualquier diferencia. Si tiene cientos o miles de solicitudes por segundo, esas fracciones de segundo se suman rápidamente, por lo que si cree que su aplicación necesita escalar, me quedaría con la in_arrayimplementación.
Frank Forte
1

He aquí una forma en que lo hago después de investigarlo por un tiempo. Quería hacer un punto final de API Laravel que verifique si un campo está "en uso", por lo que la información importante es: 1) ¿qué tabla de DB? 2) ¿Qué columna de DB? y 3) ¿hay un valor en esa columna que coincida con los términos de búsqueda?

Sabiendo esto, podemos construir nuestra matriz asociativa:

$SEARCHABLE_TABLE_COLUMNS = [
    'users' => [ 'email' ],
];

Luego, podemos establecer nuestros valores que verificaremos:

$table = 'users';
$column = 'email';
$value = '[email protected]';

Luego, podemos usar array_key_exists()y in_array()entre sí para ejecutar un combo de uno, dos pasos y luego actuar según la truthycondición:

// step 1: check if 'users' exists as a key in `$SEARCHABLE_TABLE_COLUMNS`
if (array_key_exists($table, $SEARCHABLE_TABLE_COLUMNS)) {

    // step 2: check if 'email' is in the array: $SEARCHABLE_TABLE_COLUMNS[$table]
    if (in_array($column, $SEARCHABLE_TABLE_COLUMNS[$table])) {

        // if table and column are allowed, return Boolean if value already exists
        // this will either return the first matching record or null
        $exists = DB::table($table)->where($column, '=', $value)->first();

        if ($exists) return response()->json([ 'in_use' => true ], 200);
        return response()->json([ 'in_use' => false ], 200);
    }

    // if $column isn't in $SEARCHABLE_TABLE_COLUMNS[$table],
    // then we need to tell the user we can't proceed with their request
    return response()->json([ 'error' => 'Illegal column name: '.$column ], 400);
}

// if $table isn't a key in $SEARCHABLE_TABLE_COLUMNS,
// then we need to tell the user we can't proceed with their request
return response()->json([ 'error' => 'Illegal table name: '.$table ], 400);

Pido disculpas por el código PHP específico de Laravel, pero lo dejaré porque creo que puede leerlo como pseudocódigo. La parte importante son las dos ifdeclaraciones que se ejecutan sincrónicamente.

array_key_exists()y in_array()son funciones PHP.

fuente:

Lo bueno del algoritmo que he mostrado antes es que se puede hacer un extremo REST como GET /in-use/{table}/{column}/{value}(donde table, columny valueson variables).

Podrías tener:

$SEARCHABLE_TABLE_COLUMNS = [
    'accounts' => [ 'account_name', 'phone', 'business_email' ],
    'users' => [ 'email' ],
];

y luego puede realizar solicitudes GET como:

GET /in-use/accounts/account_name/Bob's Drywall (es posible que deba codificar uri en la última parte, pero generalmente no)

GET /in-use/accounts/phone/888-555-1337

GET /in-use/users/email/[email protected]

Tenga en cuenta también que nadie puede hacer:

GET /in-use/users/password/dogmeat1337porque passwordno aparece en su lista de columnas permitidas para user.

Buena suerte en tu viaje.

agm1984
fuente
No tengo idea de qué tiene que ver esto con la pregunta, pero eché un vistazo y: ¡realmente espero que NUNCA uses datos dinámicos $SEARCHABLE_TABLE_COLUMNS! Esto exige una inyección, no importa si hay un "generador de consultas de marco ultra seguro" que intente enmascarar y filtrar cadenas de tablas y columnas. Al final, las cadenas de tabla y columna no se pueden agregar a través de un marcador de posición (declaraciones preparadas) y se deben insertar directamente como SELECT ... FROM {$table} WHERE {$column} = :placeholder ..... Ofc depende de los adaptadores (mysql, mongo, ...) ¡PERO ese no es un argumento para salvar! Pls estática o sin lista =)
cottton