Verificando se os elementos de uma matriz estão em outra matriz no PHP

130

Eu tenho duas matrizes em PHP da seguinte maneira:

Pessoas:

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

Criminosos procurados:

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
)

Como verifico se algum dos elementos People está na matriz Wanted Criminals ?

Neste exemplo, ele deve retornar trueporque 20está em criminosos procurados .

Philip Morton
fonte

Respostas:

204

Você pode usar array_intersect().

$result = !empty(array_intersect($people, $criminals));
Greg
fonte
8
Não é possível usar empty () com nada além de uma variável.
Grantwparks
@grantwparks, então por que, nos documentos do PHP sobre essa função, eles dizem "Retorna FALSE se var existe e tem um valor não-vazio e não-zero. Caso contrário, retorna TRUE. As seguintes coisas são consideradas vazias: array () (uma matriz vazia) ) "? Fonte: php.net/manual/pt/function.empty.php
Pere
5
Na página à qual você vinculou: "Antes do PHP 5.5, empty () suporta apenas variáveis; qualquer outra coisa resultará em um erro de análise. Em outras palavras, o seguinte não funcionará: empty (trim ($ name)). Em vez disso, use trim ($ name) == false. "
Grantwparks
9
Como mencionado nos comentários, descobri que !empty não funciona conforme o esperado . Em vez disso, eu usei count():!count(array_intersect($people, $criminals));
Mattios550
3
Por que isso é marcado como a resposta com 65 votos para cima quando gera erro fatal: Não é possível usar o valor de retorno da função no contexto de gravação?
Dave Heq
31

Há pouco erro em usar array_intersect () e count () (em vez de vazio).

Por exemplo:

$bFound = (count(array_intersect($criminals, $people))) ? true : false;
papoula
fonte
2
Não há nada de errado com isso, mas count()não é considerado alto desempenho (se você se preocupa com micro otimização, que é)
Jake A. Smith
23

se 'vazio' não for a melhor opção, o que dizer disso:

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

ou

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

Esse código é inválido, pois você só pode passar variáveis ​​para construções de linguagem. empty()é uma construção de linguagem.

Você precisa fazer isso em duas linhas:

$result = array_intersect($people, $criminals);
$result = !empty($result);
Paul Dragoonis
fonte
O problema não é que é uma construção de linguagem. O problema é que ele espera uma referência e Greg passa um valor.
Artefacto
1
@Artefacto, do php.net "Nota: Como esta é uma construção de linguagem e não uma função, não pode ser chamada usando funções variáveis." É exatamente como Paul disse.
Grantwparks
17

Teste de desempenho 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;

Aqui estão os 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 é pelo menos 5 vezes mais rápido. Observe que "quebramos" assim que o resultado é encontrado.

Frank Forte
fonte
Obrigado pela referência. Portanto, se você souber que está lidando com pequenas matrizes, é melhor ficar com ela array_intersect().
Tokeeen.com
isseté ainda mais rápido. E você pode usar bool val para ativar ou desativar. Além disso, os valores da pesquisa como chave garantem que não há duplicatas. ´array_intersect avg: 0.52077736854553; matriz média do arquivo: 0,015597295761108; isset avg: 0.0077081203460693´
cottton
1

Você também pode usar in_array da seguinte maneira:

<?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) }

Embora array_intersect seja certamente mais conveniente de usar, verifica-se que não é realmente superior em termos de desempenho. Também criei 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) }

Em seguida, executei os dois trechos respectivamente em: http://3v4l.org/WGhO7/perf#tabs e http://3v4l.org/g1Hnu/perf#tabs e verifiquei o desempenho de cada um. O interessante é que o tempo total da CPU, ou seja, o tempo do usuário + o tempo do sistema é o mesmo para o PHP5.6 e a memória também é a mesma. O tempo total da CPU no PHP5.4 é menor para in_array do que array_intersect, embora marginalmente.

slevy1
fonte
Os resultados são enganosos. Executá-lo apenas uma vez é muito rápido para medir qualquer diferença. Se você tem centenas ou milhares de solicitações por segundo, essas frações de segundo se somam rapidamente; portanto, se você acha que seu aplicativo precisa ser dimensionado, eu continuaria com a in_arrayimplementação.
Frank Forte
1

Aqui está uma maneira de fazer isso depois de pesquisar por um tempo. Eu queria criar um ponto de extremidade da API do Laravel que verifique se um campo está "em uso"; portanto, as informações importantes são: 1) qual tabela de banco de dados? 2) qual coluna DB? e 3) existe um valor nessa coluna que corresponda aos termos da pesquisa?

Sabendo disso, podemos construir nossa matriz associativa:

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

Então, podemos definir nossos valores que iremos verificar:

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

Então, podemos usar array_key_exists()e in_array()uns com os outros para executar um, dois etapa de combinação e, em seguida, agir sobre a truthycondição:

// 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);

Peço desculpas pelo código PHP específico do Laravel, mas deixarei porque acho que você pode lê-lo como pseudo-código. A parte importante são as duas ifinstruções que são executadas de forma síncrona.

array_key_exists()e in_array()são funções PHP.

fonte:

A coisa agradável sobre o algoritmo que eu mostrei acima é que você pode fazer um terminal REST, como GET /in-use/{table}/{column}/{value}(onde table, columne valuesão variáveis).

Você pode ter:

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

e você pode fazer solicitações GET, como:

GET /in-use/accounts/account_name/Bob's Drywall (pode ser necessário codificar uri a última parte, mas geralmente não)

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

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

Observe também que ninguém pode fazer:

GET /in-use/users/password/dogmeat1337porque passwordnão está listado na sua lista de colunas permitidas para user.

Boa sorte em sua jornada.

agm1984
fonte
Não tenho idéia do que isso tem a ver com a pergunta, mas dei uma olhada e: realmente espero que você NUNCA use dados dinâmicos $SEARCHABLE_TABLE_COLUMNS! Isso grita por uma injeção - não importa se existe um "construtor de consultas de estrutura ultra-seguro" que tenta mascarar e filtrar as seqüências de tabela e coluna! Na tabela final e nas cadeias de colunas, não podem ser adicionadas via espaço reservado (instruções preparadas) e devem ser inseridas diretamente como SELECT ... FROM {$table} WHERE {$column} = :placeholder ..... Ofc depende de adaptadores (mysql, mongo, ...) MAS isso não é um argumento a ser salvo! Lista estática ou nenhuma lista =)
cottton 18/06/19