DJules
DJules

Reputation: 27

Use an array of keys to filter another array, keeping the "order"

I have made the following using standard PHP coding, involving loops and things. Though, I am sure I can accomplish the same algorithm using a combination of all PHP array functions: array_intersect*, array_fill*, and co...

I am hoping someone already encountered this and found a way, a fast way.

Ok, here it is:

I have an indexed array, let's call it data, it contains loads of informations about an entity, order doesn't matter obviously as it is an indexed array after all:

$data = [
    'title' => 'loops and things',
    'id' => '28fr2',
    'link' => 'https://blah',
    'itemid' => '28fr3',
    'quantity' => 3,
    //...
];

Now, I have a set of other arrays which describe a subset of my data array, for instance:

$product = [
    'id',
    'title',
    'link'
];

and

$inventory = [
    'itemid',
    'quantity',
];

what I am after is something like:

array_intersect_key_while_we_keep_the_order($data, $product);

would give

[
    'id' => '28fr2',
    'title' => 'loops and things',
    'link' => 'https://blah'
]

and

array_intersect_key_while_we_keep_the_order($data, $inventory);

would give

[
    'itemid' => '28fr2',
    'quantity' => 3
]

The problem I have with array_intersect_key is that the "order" is from the source array, not the one defining the list of keys. This is obviously straight forward using foreaches, but I am sure it can be done faster. I just can't see it.

Upvotes: 0

Views: 2413

Answers (2)

ArtisticPhoenix
ArtisticPhoenix

Reputation: 21671

Pretty simple

$data = [
    'title' => 'loops and things',
    'id' => '28fr2',
    'link' => 'https://blah',
    'itemid' => '28fr3',
    'quantity' => 3,
    //...
];
$product = [
    'id',
    'title',
    'link'
];

$res = array_merge(array_fill_keys($product, null),array_intersect_key($data, array_flip($product)));
print_r($res);

Output

Array
(
    [id] => 28fr2
    [title] => loops and things
    [link] => https://blah
)

Sandbox

How it works, The main trick here is array fill keys, and array merge. By using these we can create a "template" array. Because merging 2 arrays with the same keys merge in the order of the first array. So we can setup that first array to be the order of $product and then when we merge into that the "fake" values are replaced with the real values and the order is preserved.

If you wanted to you could also use the result of the array flip (or array fill keys), but then it would be 2 lines of code... :)

And of course we have to use something like array flip or array fill keys to change the values of $product into the keys we can use for array intersect key.

I used both array fill keys and array flip for variety, in this situation they are somewhat interchangeable. For example this is a bit more efficient:

$map = array_fill_keys($product, null);
$res = array_merge($map,array_intersect_key($data, $map));
print_r($res);

Sandbox

But then it takes 2 lines... I should mention array fill keys is preferred to array flip. It doesn't really matter because the array intersect key filters anything out not in map. But it's "in general" better because then the values of the map array are the same, whereas when using flip they are the numeric keys which can be a bit harder to work with for some situations (but not this one).

I almost for got you can functionize it like this

function ordered_array_intersect_key(array $data, array $map){
    return array_merge(($map = array_fill_keys($map, null)),array_intersect_key($data, $map));
}

I took the liberty of naming it something sensible and by using assignment inline in array_merge you can have your cake and eat it too (reuse array_fill_keys and have 1 line). Something I just happened to think of when making a function out of it.

Sanbox

Enjoy!

UPDATE

Thanks to mosh442, for the idea. You can also use array_walk to build a new array like this:

$data = [
    'title' => 'loops and things',
    'id' => '28fr2',
    'link' => 'https://blah',
    'itemid' => '28fr3',
    'quantity' => 3,
    //...
];
$product = [
    'id',
    'title',
    'link'
];

$new = [];

array_walk($product, function($v,$k,$data) use (&$new){$new[$v] = $data[$v];},$data);

print_r($new);

Output

Array
(
    [id] => 28fr2
    [title] => loops and things
    [link] => https://blah
)

Sandox

I love questions like this, so much fun....

Upvotes: 2

mosh442
mosh442

Reputation: 738

You can also use array_filter

array_filter(
            $data,
            function ($key) use ($inventory) {return in_array($key,$inventory);} ,
            ARRAY_FILTER_USE_KEY
            );

As noted in the comments the ARRAY_FILTER_USE_KEY flag was added in PHP 5.6.0

Upvotes: 1

Related Questions