Sprawdzanie, czy elementy jednej tablicy znajdują się w innej tablicy w PHP

130

Mam dwie tablice w PHP w następujący sposób:

Ludzie:

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

Poszukiwani przestępcy:

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
)

Jak sprawdzić, czy którykolwiek z elementów People znajduje się w tablicy Wanted Criminals ?

W tym przykładzie powinien powrócić, trueponieważ 20znajduje się w Poszukiwani przestępcy .

Philip Morton
źródło

Odpowiedzi:

204

Możesz użyć array_intersect().

$result = !empty(array_intersect($people, $criminals));
Greg
źródło
8
Nie można używać pustego () z niczym innym niż zmienną.
grantwparks
@grantwparks to dlaczego w dokumentach PHP o tej funkcji mówią "Zwraca FAŁSZ, jeśli zmienna istnieje i ma niepustą, niezerową wartość. W przeciwnym razie zwraca PRAWDA. Następujące rzeczy są uważane za puste: tablica () (pusta tablica ) ”? Źródło: php.net/manual/en/function.empty.php
Pere
5
Ze strony, do której utworzyłeś link: "Przed PHP 5.5 funkcja empty () obsługuje tylko zmienne; cokolwiek innego spowoduje błąd analizy. Innymi słowy, nie będzie działać: empty (trim ($ name)). Zamiast tego, użyj trim ($ name) == false. "
grantwparks
9
Jak wspomniano w komentarzach, stwierdziłem, że !empty nie działa zgodnie z oczekiwaniami . Zamiast tego użyłem count():!count(array_intersect($people, $criminals));
Mattios550
3
Dlaczego jest to oznaczone jako odpowiedź z 65 głosami w górę, kiedy zgłasza błąd krytyczny: nie można użyć wartości zwracanej przez funkcję w kontekście zapisu?
Dave Heq
31

Nie ma nic złego w używaniu array_intersect () i count () (zamiast pustego).

Na przykład:

$bFound = (count(array_intersect($criminals, $people))) ? true : false;
papsy
źródło
2
Nie ma w tym nic złego, ale count()nie jest uważany za wydajny ( to znaczy, jeśli zależy ci na mikrooptymalizacji)
Jake A. Smith
23

jeśli „pusty” nie jest najlepszym wyborem, co z tym:

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

lub

if (!array_intersect($people, $criminals)) {...} //when not found
ihtus
źródło
22

Ten kod jest nieprawidłowy, ponieważ możesz przekazywać zmienne tylko do konstrukcji językowych. empty()jest konstrukcją językową.

Musisz to zrobić w dwóch wierszach:

$result = array_intersect($people, $criminals);
$result = !empty($result);
Paul Dragoonis
źródło
Problem w tym, że nie jest to konstrukcja językowa. Problem polega na tym, że oczekuje odniesienia, a Greg przekazuje wartość.
Artefacto
1
@Artefacto, z php.net "Uwaga: ponieważ jest to konstrukcja języka, a nie funkcja, nie można jej wywołać za pomocą funkcji zmiennych." Dokładnie tak, jak powiedział Paul.
grantwparks
17

Test wydajności dla 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;

Oto wyniki:

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 jest co najmniej 5 razy szybsze. Zwróć uwagę, że „przerywamy”, gdy tylko zostanie znaleziony wynik.

Frank Forte
źródło
Dzięki za test porównawczy. Więc jeśli wiesz, że obsługujesz małe tablice, lepiej pozostać przy array_intersect().
Tokeeen.com
issetjest jeszcze szybsza. I możesz użyć bool val, aby włączyć lub wyłączyć. Również wartości wyszukiwania jako klucz zapewniają, że nie ma duplikatów. ´array_intersect avg: 0,52077736854553; in_array avg: 0,015597295761108; isset Średnia: 0.0077081203460693'
cottton
1

Możesz również użyć in_array w następujący sposób:

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

Chociaż array_intersect jest z pewnością wygodniejszy w użyciu, okazuje się, że nie jest lepszy pod względem wydajności. Stworzyłem też ten skrypt:

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

Następnie uruchomiłem oba fragmenty odpowiednio pod adresem: http://3v4l.org/WGhO7/perf#tabs i http://3v4l.org/g1Hnu/perf#tabs i sprawdziłem wydajność każdego z nich. Ciekawostką jest to, że całkowity czas procesora, tj. Czas użytkownika + czas systemowy, jest taki sam dla PHP5.6, a pamięć też jest taka sama. Całkowity czas procesora w PHP5.4 jest mniejszy dla in_array niż array_intersect, choć marginalnie.

slevy1
źródło
Wyniki są mylące. Uruchomienie go tylko raz jest zbyt szybkie, aby zmierzyć jakąkolwiek różnicę. Jeśli masz setki lub tysiące żądań na sekundę, te ułamki sekundy szybko się sumują, więc jeśli uważasz, że Twoja aplikacja wymaga skalowania, trzymam się in_arrayimplementacji.
Frank Forte
1

Oto sposób, w jaki robię to po pewnym czasie badania. Chciałem stworzyć punkt końcowy API Laravel, który sprawdza, czy pole jest „używane”, więc ważne informacje to: 1) która tabela DB? 2) jaka kolumna DB? i 3) czy w tej kolumnie znajduje się wartość, która pasuje do wyszukiwanych haseł?

Wiedząc to, możemy skonstruować naszą tablicę asocjacyjną:

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

Następnie możemy ustawić nasze wartości, które sprawdzimy:

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

Następnie możemy użyć array_key_exists()i in_array()wspólnie wykonać jedno, dwuetapowe combo, a następnie działać pod truthywarunkiem:

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

Przepraszam za kod PHP specyficzny dla Laravel, ale zostawię go, ponieważ myślę, że można go odczytać jako pseudokod. Ważną częścią są dwie ifinstrukcje, które są wykonywane synchronicznie.

array_key_exists()i in_array()są funkcjami PHP.

źródło:

Zaletą algorytmu, który pokazałem powyżej jest to, że można zrobić końcowy REST takich jak GET /in-use/{table}/{column}/{value}(gdzie table, columni valuesą zmienne).

Możesz mieć:

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

a następnie możesz wykonać żądania GET, takie jak:

GET /in-use/accounts/account_name/Bob's Drywall (może być konieczne zakodowanie ostatniej części w formacie uri, ale zwykle nie)

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

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

Zauważ również, że nikt nie może zrobić:

GET /in-use/users/password/dogmeat1337ponieważ passwordnie ma go na liście dozwolonych kolumn dla user.

Powodzenia w podróży.

agm1984
źródło
Nie mam pojęcia, co to ma wspólnego z pytaniem, ale przyjrzałem się temu i: mam nadzieję, że NIGDY nie używasz danych dynamicznych $SEARCHABLE_TABLE_COLUMNS! To krzyczy o zastrzyk - bez względu na to, czy pomiędzy próbami maskowania i filtrowania ciągów tabel i kolumn jest „ultra bezpieczny framework do tworzenia zapytań”! Na końcu tabeli i kolumn nie można dodawać za pomocą symbolu zastępczego (przygotowanych instrukcji) i należy je wstawiać bezpośrednio tak, jak SELECT ... FROM {$table} WHERE {$column} = :placeholder ..... Ofc zależy od adapterów (mysql, mongo, ...) ALE to nie jest argument do oszczędzania! Pls statyczny lub brak listy =)
Cottton