2
\$\begingroup\$

I have $fruits_arr:

Array
(
    [0] => Array
        (
            [id] => 213
            [fruit] => banana
        )

    [1] => Array
        (
            [id] => 438
            [fruit] => apple
        )

    [2] => Array
        (
            [id] => 154
            [fruit] => peach
        )
)

And $ids_arr:

Array ( 
  [0] => 213
  [1] => 154 
)

I want to recreate $fruits_arr to have only array items where id is equal to a value from $ids_arr. I also want to maintain the index/array order of $fruits_arr.

I'm using the following:

$selected_fruits = array();

foreach( $fruits_arr as $fruit ) :
    if ( in_array( $fruit['id'], $ids_arr ) ) :
        $selected_fruits[] = $fruit;
    endif;
endforeach;

print_r( $selected_fruits );

It seems to work but I am wondering if there is a shorter, better way to accomplish this in the latest PHP version.

\$\endgroup\$
  • \$\begingroup\$ To give you the best advice, tell us where this array is being generated / how you are generating it. If it is necessary to filter this data, there is a better array structure to pursue. Is this a query's result set? If so, are you using mysqli? pdo? Are your ids unique within the $fruits_arr array? in_array() is not an efficient means to filter your data. \$\endgroup\$ – mickmackusa Jul 15 at 7:55
2
\$\begingroup\$

Only thing you can use besides the foreach loop, it is array_filter:

$selected_fruits = array_filter($fruits_arr, function($fruit) use($ids_arr) {
    return in_array( $fruit['id'], $ids_arr );
});

looks shorter and uses native functions

\$\endgroup\$
  • \$\begingroup\$ ARRAY_FILTER_USE_BOTH is entirely unnecessary. \$\endgroup\$ – mickmackusa Jul 15 at 8:06
  • \$\begingroup\$ yeap u are right \$\endgroup\$ – n1creator Jul 15 at 16:42
1
\$\begingroup\$

The best performing techniques for filtering arrays employ key-based comparisons.

To prepare your posted data structures for such a technique will require both arrays to have there ids copied to the first level keys.

Once both arrays have id values for keys, you can filter by comparison.

Code: (Demo)

$fruits_arr = [
    ['id' => 213, 'fruit' => 'banana'],
    ['id' => 438, 'fruit' => 'apple'],
    ['id' => 154, 'fruit' => 'peach']
];
$ids_arr = [213, 154];

var_export(array_intersect_key(array_column($fruits_arr, null, "id"), array_flip($ids_arr)));

This is theoretic efficiency, because more effort is required simply to prepare. It would be better if you could declare these data structures in the necessary structure.

All of the above assumes your ids are unique, if not your data will lose values with duplicated ids.

\$\endgroup\$
1
\$\begingroup\$

There's nothing wrong with your foreach loop, but you can use a slightly different syntax:

$selected_fruits = [];

foreach ($fruits as $fruit) {
    if (in_array($fruit["id"], $selected_ids)) {
        $selected_fruits[] = $fruit;
    }
}

print_r($selected_fruits);

I've changed $fruits_arr to $fruits, and $ids_arr to $selected_ids. A variable name should describe the meaning or purpose of the data or resource a variable contains. And you should be consistent about your choices. For instance, why don't the arrays $selected_fruits and $fruit have the _arr post-fix in your code? They are arrays as well.

There's no difference in the way the code above works, when compared with your code, but it is actually shorter than when array_filter() is used. Shorter code is, however, never a hallmark of better code. I think readability, and simply code that makes sense, are far more important. Using a rather complex function like array_filter() here is, in my eyes, overkill.

As far as I can tell the syntax, as used in this answer, is used more often than the syntax in your question. For the [] array declaration you need PHP 5.4 or higher.

You also wrote:

I also want to maintain the index/array order of $fruits_arr.

It is not completely clear what this means. If you actually want to maintain the same indexes you have to do a bit more. The code would then look like this:

$selected_fruits = [];

foreach ($fruits as $index => $fruit) {
    if (in_array($fruit["id"], $selected_ids)) {
        $selected_fruits[$index] = $fruit;
    }
}

print_r($selected_fruits);

This would make the indexes of $selected_fruits equal to those of $fruits_arr.

It is clear that both foreach loops, used in the code segments above, will have to walk the whole $fruits array to find all selected fruits. If the $fruits array becomes very long, compared to the number of selected fruits, this will not be very efficient. One of the reasons for this is the way that you've defined the $fruits_arr. The keys in this array don't do much. If I assume that the fruit ids in this array are unique, then it would be very helpful if the $fruits_arr keys actually were those fruit ids. I can redefine your $fruits_arr to achieve this, with this code:

$fruits_arr = array_combine(array_column($fruits_arr, "id"), $fruits_arr);

Now the $fruits_arr looks like this:

Array
(
    [213] => Array
        (
            [id] => 213
            [fruit] => banana
        )

    [438] => Array
        (
            [id] => 438
            [fruit] => apple
        )

    [154] => Array
        (
            [id] => 154
            [fruit] => peach
        )
)

It would, of course, be better to have done this right from the start, so you don't have to manipulate $fruits_arr to get those useful keys. Given this new array you can now do:

$selected_fruits = [];

foreach ($selected_ids as $fruit_id) {
    $selected_fruits[] = $fruits[$fruit_id];
}

print_r($selected_fruits);

This code only walks over the shorter $selected_ids array. It also doesn't contain the in_array() function anymore, because I assumed that all selected fruits are present in the fruits array. It wouldn't be a selection otherwise.

Finally, the code above can be shortened to:

$selected_fruits = array_intersect_key($fruits, array_flip($selected_ids));

print_r($selected_fruits);

This is the shortest and most efficient version I can think of.

\$\endgroup\$
  • \$\begingroup\$ Don't use combine(), see my snippet. \$\endgroup\$ – mickmackusa Jul 15 at 8:21
  • \$\begingroup\$ And flip the selected ids \$\endgroup\$ – mickmackusa Jul 15 at 8:33
  • \$\begingroup\$ I agree that combine() should not be used, it's better to create the array in the correct form from the start. And you're right about the array_intersect_key(), I'll correct that. \$\endgroup\$ – KIKO Software Jul 15 at 9:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.