153

I have two arrays in PHP as follows:

People:

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

Wanted Criminals:

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
)

How do I check if any of the People elements are in the Wanted Criminals array?

In this example, it should return true because 20 is in Wanted Criminals.

Philip Morton
  • 125,759
  • 37
  • 85
  • 97

7 Answers7

244

You can use array_intersect().

$result = !empty(array_intersect($people, $criminals));
Amal Murali
  • 73,160
  • 18
  • 123
  • 143
Greg
  • 307,243
  • 53
  • 363
  • 328
  • 8
    Can't use empty() with anything other than a variable. – grantwparks Sep 25 '09 at 19:21
  • @grantwparks then why in PHP docs about this function they say "Returns FALSE if var exists and has a non-empty, non-zero value. Otherwise returns TRUE. The following things are considered to be empty: array() (an empty array)"? Source: http://php.net/manual/en/function.empty.php – Pere Jul 30 '13 at 09:34
  • 5
    From the page you linked to: "Prior to PHP 5.5, empty() only supports variables; anything else will result in a parse error. In other words, the following will not work: empty(trim($name)). Instead, use trim($name) == false." – grantwparks Sep 19 '13 at 22:31
  • 11
    As mentioned in the comments I found that `!empty` **does not work as expected**. Instead, I used `count()`: `!count(array_intersect($people, $criminals));` – Mattios550 Nov 30 '16 at 21:12
  • 3
    Why is this marked as the answer with 65 votes up when it throws Fatal error: Can't use function return value in write context? – Dave Heq Jan 27 '17 at 18:02
  • Would it be wrong to assign the response to a value within the empty() construct? What I was thinking: `$result = !empty($isTrue = array_intersect($people, $criminals));` – Dan Feb 26 '19 at 18:54
  • Using `!empty()` is unnecessary. Just cast the returned array to a bool data type. `$result = (bool) array_intersect(...);` No reason to call `empty()` or `count()`. An empty array becomes `false` a non-empty array becomes `true`. – mickmackusa Feb 21 '22 at 02:16
31

There's little wrong with using array_intersect() and count() (instead of empty).

For example:

$bFound = (count(array_intersect($criminals, $people))) ? true : false;
Peter O.
  • 30,765
  • 14
  • 76
  • 91
papsy
  • 423
  • 4
  • 5
28

if 'empty' is not the best choice, what about this:

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

or

if (!array_intersect($people, $criminals)) {...} //when not found
ihtus
  • 2,435
  • 12
  • 38
  • 53
23

That code is invalid as you can only pass variables into language constructs. empty() is a language construct.

You have to do this in two lines:

$result = array_intersect($people, $criminals);
$result = !empty($result);
Tokeeen.com
  • 708
  • 7
  • 19
Paul Dragoonis
  • 2,265
  • 1
  • 15
  • 22
  • The problem is not it is a language construct. The problem is it expects a reference and Greg's passing a value. – Artefacto Jul 28 '10 at 15:41
  • 1
    @Artefacto, from the php.net "Note: Because this is a language construct and not a function, it cannot be called using variable functions." It's exactly like Paul said. – grantwparks Sep 19 '13 at 22:35
20

Performance test for 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;

Here are the results:

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 is at least 5 times faster. Note that we "break" as soon as a result is found.

Frank Forte
  • 1,797
  • 15
  • 18
  • Thanks for the benchmark. So if you know you are handling small arrays it's better to stay with `array_intersect()`. – Tokeeen.com Nov 02 '17 at 15:01
  • `isset` is even faster. And you could use bool val to enable or disable. Also the search values as key make sure to have no duplicates. ´array_intersect avg: 0.52077736854553; in_array avg: 0.015597295761108; isset avg: 0.0077081203460693´ – cottton Jun 18 '19 at 12:03
  • Its even faster if you use in_array with the strict boolean set. Test averages at 3x faster than without the strict boolean set. – omarjebari Apr 24 '21 at 10:39
2

You could also use in_array as follows:

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

While array_intersect is certainly more convenient to use, it turns out that its not really superior in terms of performance. I created this script too:

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

Then, I ran both snippets respectively at: http://3v4l.org/WGhO7/perf#tabs and http://3v4l.org/g1Hnu/perf#tabs and checked the performance of each. The interesting thing is that the total CPU time, i.e. user time + system time is the same for PHP5.6 and the memory also is the same. The total CPU time under PHP5.4 is less for in_array than array_intersect, albeit marginally so.

slevy1
  • 3,689
  • 2
  • 24
  • 32
  • The results are deceiving. Running it just one time is too fast to measure any difference. If you have hundreds or thousands of requests per second, those fractions of a second add up quickly, so if you think your application needs to scale, I would stick with the `in_array` implementation. – Frank Forte Dec 07 '17 at 04:06
1

Here's a way I am doing it after researching it for a while. I wanted to make a Laravel API endpoint that checks if a field is "in use", so the important information is: 1) which DB table? 2) what DB column? and 3) is there a value in that column that matches the search terms?

Knowing this, we can construct our associative array:

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

Then, we can set our values that we will check:

$table = 'users';
$column = 'email';
$value = 'alice@bob.com';

Then, we can use array_key_exists() and in_array() with eachother to execute a one, two step combo and then act upon the truthy condition:

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

I apologize for the Laravel-specific PHP code, but I will leave it because I think you can read it as pseudo-code. The important part is the two if statements that are executed synchronously.

array_key_exists() and in_array() are PHP functions.

source:

The nice thing about the algorithm that I showed above is that you can make a REST endpoint such as GET /in-use/{table}/{column}/{value} (where table, column, and value are variables).

You could have:

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

and then you could make GET requests such as:

GET /in-use/accounts/account_name/Bob's Drywall (you may need to uri encode the last part, but usually not)

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

GET /in-use/users/email/alice@bob.com

Notice also that no one can do:

GET /in-use/users/password/dogmeat1337 because password is not listed in your list of allowed columns for user.

Good luck on your journey.

agm1984
  • 12,436
  • 6
  • 68
  • 94
  • I have no idea what this has to do with the question but i took a look and: i really hope you NEVER use dynamic data in `$SEARCHABLE_TABLE_COLUMNS`! This screams for a injection - no matter if there is a "ultra secure framework query builder" between that tries to mask and filter table and column strings! At the end table and column strings cannot be added via placeholder (prepared statements) and must be inserted directly like `SELECT ... FROM {$table} WHERE {$column} = :placeholder ....`. Ofc depends on adapters (mysql, mongo, ...) BUT that is no argument to be save! Pls static or no list =) – cottton Jun 18 '19 at 11:43