167

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.

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
Philip Morton
  • 129,733
  • 38
  • 88
  • 97

8 Answers8

273

You can use array_intersect().

$peopleContainsCriminal = !empty(array_intersect($people, $criminals));
goulashsoup
  • 2,639
  • 2
  • 34
  • 60
Greg
  • 316,276
  • 54
  • 369
  • 333
  • 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
  • 12
    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
  • 5
    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
  • 1
    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
  • This works well for flat arrays, but it doesn't work if either of the arrays is multidimensional. – kloddant Oct 12 '22 at 15:13
32

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.
  • 32,158
  • 14
  • 82
  • 96
papsy
  • 433
  • 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,673
  • 13
  • 40
  • 58
28

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
  • 718
  • 7
  • 19
Paul Dragoonis
  • 2,315
  • 1
  • 16
  • 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
  • 3
    @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
22

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 = (bool) array_intersect($a1,$a2);
        // $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 updated results, with "empty" removed (thanks @mickmackusa) using php 8.1.6 on an old android phone:

0.48460602760315
0.48387813568115
0.48307418823242
0.48364686965942
0.48398804664612
0.48381400108337
0.48366618156433
0.48358988761902
0.48365116119385
0.48321080207825
array_intersect avg: 0.48371253013611

0.023025989532471
0.022964954376221
0.023096084594727
0.022902965545654
0.022971868515015
0.022997856140137
0.02297306060791
0.022901058197021
0.022931814193726
0.023001909255981
in_array avg: 0.022976756095886

in_array was at least 5 times faster, ironically it looks 20 times faster with php 8.1, despite removing the empty() function from the array_intersect loop. Note that we "break" as soon as a result is found, but based on test values in $a1 and $a2, it is the very last loop anyways.

Frank Forte
  • 2,031
  • 20
  • 19
  • 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
  • 1
    It is unfair to make the benchmark comparison for `array_intersect()` also call `empty()`. `empty()` is not necessary at all. `!array_intersect()` is enough to do the job because there is no need to check if `$x` is declared. – mickmackusa Jan 10 '23 at 21:27
3

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,797
  • 2
  • 27
  • 33
  • 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
2

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
  • 15,500
  • 6
  • 89
  • 113
  • 1
    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
-1

I've created a clean Helper Function for you to use.

if (!function_exists('array_has_one')) {

/**
 * array_has_one
 * 
 * Uses the search array to match at least one of the haystack to return TRUE
 * 
 * @param {array} $search
 * @param {array} $haystack
 * @return {boolean}
 */
function array_has_one(array $search, array $haystack){
    if(!count(array_intersect($search, $haystack)) === FALSE){
        return TRUE;
    }else{
        return FALSE;
    }

}
}

you would use this like

if(array_has_one([1,2,3,4,5], [5,6,7,8,9])){
  echo "FOUND 5";
}
David Clews
  • 795
  • 6
  • 14
  • I guess you realised that `!count()` returns a boolean, so why do you check for false instead of another simple negation? And why not just return the expression result directly?? `if(somethingIsTrue)return true else false` is highly redundant. In short, that code can be simplified to a one liner: `return !!count(array_intersect($search, $haystack));` or even `return (bool)count(array_intersect($search, $haystack));` – Christian Dec 04 '22 at 18:09