9

Here is data

$array = array(
    'random' => 1,
    'pewpew' => 2,
    'temp' => 5,
    'xoxo' => 3,
    'qweqweqe' => 4,
);

$fields = array('random', 'xoxo', 'temp');

I need to get in result:

$result = array(
    'random' => 1,
    'xoxo' => 3,
    'temp' => 5,
);

I mean keys presence/order from $fields apply to $array.

The question is: Can I perform this transformation using only array_ functions? (I don't wanna use iteations) If yes: can you link me function that I need?

(sorry for spelling mistakes)

upd.

PHP 5.2

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
Miraage
  • 3,334
  • 3
  • 26
  • 43

14 Answers14

5
$result=array_intersect_key($array ,array_flip($fields) );
Waygood
  • 2,657
  • 2
  • 15
  • 16
  • I need to preserve keys order (from $result). Anyway thanks for an answer. – Miraage Jul 02 '12 at 07:31
  • 1
    See my last comment: "it should remain in the same order. php.net/manual/en/function.array-intersect-key.php notice blue comes before green in array1 and result, but is the other way around in array2" – Waygood Jul 02 '12 at 10:05
  • 2
    It does not order it correctly (based on the $fields order, not $array order); – Sarke Jul 08 '12 at 09:18
  • Ah! the $fields is the order too. Well you will also need to $result=array_merge(array_intersect_key(array_flip($fields), $array), $result); which will take a flipped $fields that have matched data (no mismatches) and replace the values with my previous answer. – Waygood Jul 12 '12 at 11:07
3
// little trick required here...
$fields = array('random' => 0, 'xoxo' => 0, 'temp' => 0);
$result = array_intersect_key($array,$fields);
LeleDumbo
  • 9,192
  • 4
  • 24
  • 38
  • Wanted to suggest this, except his $fields array isn't in the right format. – somedev Jun 29 '12 at 16:10
  • it is if you array_flip the keys and the values – Waygood Jun 29 '12 at 16:12
  • awesome! the only issue i see with that is the ordering will not be dictated by the $fields var. – Sabeen Malik Jun 29 '12 at 16:17
  • 1
    it isn't, it should remain in the same order. http://php.net/manual/en/function.array-intersect-key.php notice blue comes before green in array1 and result, but is the other way around in array2 – Waygood Jun 29 '12 at 16:21
  • 1
    I was about to suggest the same solution but the result for me didn't work as he wanted, it comes out as `Array ( [random] => 1 [temp] => 5 [xoxo] => 3 )` – Sabeen Malik Jun 29 '12 at 16:24
2

I am always interested in these types of question, where it's about efficient code (both in code-usage and speed). That said, I tried and benchmarked several different methods, and nothing was as efficient and as a simple foreach!

I tried all the solutions posted, and my own array_walk and basic foreach. I ran several test, both with the arrays and fields posted by Miraage, and some with much larger arrays. I also noted anything odd with the results, such as additional values if $fields had values not in $array.

I've ordered it by speed.

FOREACH: 0.01245 sec

$result = array();
foreach ($fields as $k)
{
    if (isset($array[$k]))
        $result[$k] = $array[$k];
}

ARRAY_DIFF_KEY: 0.01471 sec (unexpected results: additional values)

$result = array_diff_key($fields, $array);

FOREACH (function): 0.02449 sec

function array_filter_by_key($array, $fields)
{
    $result = array();
    foreach ($fields as $k)
    {
        if (isset($array[$k]))
            $result[$k] = $array[$k];
    }

    return $result;
}

ARRAY_WALK (by reference): 0.09123 sec

function array_walk_filter_by_key($item, $key, $vars)
{
    if (isset($vars[1][$item]))
        $vars[0][$item] = $vars[1][$item];
}

$result = array();
array_walk($fields, 'array_walk_filter_by_key', array(&$result, &$array));

LIST/EACH: 0.12456 sec

$result = array();
reset($fields);
while (list($key, $value) = each($fields))
{
    if (isset($array[$value]))
        $result[$value] = $array[$value];
}

ARRAY_INTERSECT_KEY: 0.27264 sec (incorrect order)

$result = array_intersect_key($array, array_flip($fields));

ARRAY_REPLACE (array_intersect_key second): 0.29409 sec (unexpected results: additional values)

$result = array_replace(
    array_fill_keys($fields, false),
    array_intersect_key($array, array_flip($fields))
);

ARRAY_REPLACE (two array_intersect_key): 0.33311 sec

$flip = array_flip($fields);
$result = array_replace(
    array_intersect_key($flip, $array),
    array_intersect_key($array, $flip)
);

ARRAY_WALK (set null): 3.35929 sec (unexpected results: additional values)

function array_walk_filter_by_key_null(&$item, $key, $array)
{
    if (isset($array[$key]))
        $item = $array[$key];
    else
        $item = null;
}

$result = array_flip($fields);
array_walk($result, 'array_walk_filter_by_key_null', $array);

ARRAY_REPLACE (array_intersect_key first): 11.11044 sec

$flip = array_flip($fields);
$result = array_intersect_key(
    array_replace($flip, $array),
    array_intersect_key($flip, $array)
);

ARRAY_MERGE: 14.11296 sec (unexpected results: additional values)

$result = array_splice(
    array_merge(array_flip($fields), $array),
    0,
    count($fields)
);

So there it is. Can't beat a DIY. Sometimes the perception is that the built-in functions are faster, but it's not always the case. Compilers are pretty good these days.

Sarke
  • 2,805
  • 2
  • 18
  • 28
  • a factor of more than 1000 between the fastest and the slowest solution is funny, to say the least. Did you feed the same data to all solutions? What size of $fields and $array? – Walter Tross Jul 08 '12 at 17:25
  • Same data of course. 10,000 in $array and 100 in $fields, looped a few thousand times. The results were proportional though, didn't seem to matter how many loops or how big or small the arrays. – Sarke Jul 08 '12 at 17:30
  • since the first and the third solution in your list differ only for a function call, this means that a function call costs as much as looping 100 times accessing the $array and assigning to the $result - interesting. – Walter Tross Jul 08 '12 at 18:14
  • Yes, I think at this point we're just getting down to memory operations (I think this is why the use of multiple built-in functions is slower). I ran the test again with much larger arrays and only one loop, and the results were still proportional to the times posted. – Sarke Jul 08 '12 at 23:28
  • On PHP 5.2.6 I get "only" a factor of 100 (ca.) between the best and the worst time (10000 in $array, 100 in $fields, no cache, no optimizer). What configuration are you using? – Walter Tross Jul 09 '12 at 18:02
  • On PHP 5.3.9 I get a factor of 160 (but on a "weak" home PC) (no cache, no optimizer). Still very far from your factor of more than 1000... – Walter Tross Jul 09 '12 at 19:52
  • and BTW, if $array and $fields are 1000 each, I get a factor of less than 2. The factor of 100, which is the ratio of the sizes of $array and $fields, goes away. This confirms what I wrote in my answer, i.e., that the **real** problem is that no solution built of `array_` functions can avoid looping on `$array` instead of `$fields`. – Walter Tross Jul 09 '12 at 21:09
  • PHP 5.3.3 with APC, Debian 6, 2.6.32-5-xen-amd64, VPS. When I run it with 100,000 in each of $array and $fields and only one loop, I get these results: FOREACH: 0.02622 and ARRAY_MERGE: 0.26482. A factor of ten, which is still faster. Not surprising considering you need three `array_` functions. – Sarke Jul 10 '12 at 02:20
1

I believe this works as you require.

$result = array_splice(array_merge(array_flip($fields) , $array) , 0 , count($fields));
Sabeen Malik
  • 10,816
  • 4
  • 33
  • 50
  • +1, but a problem arises if one value of `$fields` is not present in `$array` – Walter Tross Jul 06 '12 at 15:41
  • Thank you @WalterTross . I just assumed based on the question that those would be in the $array and if not they will become 0. I guess if they wanted to they could weed out the 0 values by using `array_filter` – Sabeen Malik Jul 06 '12 at 16:00
  • This was by far the slowest implementation I tested, see my post. – Sarke Jul 08 '12 at 12:58
1

Just to solve the puzzle:

$result = array_replace(
  array_intersect_key(array_flip($fields), $array),
  array_intersect_key($array, array_flip($fields))
);

The first array_intersect creates the list of fields in good order, the other one overcomes array_replace functionality to create the the keys that do not exist in the first array.

Meets your requirements. But I wouldn't use it in any production code, as this may be pretty heavy (I didn't benchmark though, so it's just a gut feeling). An array_walk solution seems lighter.

wdev
  • 2,190
  • 20
  • 26
  • +1 for elegance - although in order not to loose too much efficiency, the `array_flip()` should be factored out, of course (just like Sarke has done in his/her benchmark) – Walter Tross Jul 10 '12 at 17:47
1

This code preserves order and works in PHP 5.2 as required

One line:

$result = array_merge( array_flip($fields),
                       array_intersect_key(
                                          $array,
                                          array_flip( $fields )
                                          )
                       );

For performance:

$flip = array_flip($fields);
$result = array_merge( $flip
                       array_intersect_key(
                                          $array,
                                          $flip
                                          )
                       );
jornare
  • 2,903
  • 19
  • 27
0

If you want to keep key order from $fields, you could try this: (if key not exists in $array, then the value for that key will be null.)

$result = array_flip($fields);
array_walk($result, function(&$item, $key, $array) {
  $item = isset($array[$key]) ? $array[$key] : null;
}, $array);

var_dump($result);
xdazz
  • 158,678
  • 38
  • 247
  • 274
0

I will consider that you can't change the input (neither $array or $fields).

This can be achieved if you have an array that uses as keys the values from $fields. After that you can merge the two (with $fields being the first parameter) and remove the extra elements.

Considering that you can't change $fields, I will create it:

$tmp = array_combine($fields, range(1, count($fields)));
$result = array_merge($tmp, $array);
$result = array_splice($result, 0, count($fields));

The full working sample (with some comments) can be found here: http://codepad.org/H0CDN7ok

mishu
  • 5,347
  • 1
  • 21
  • 39
0

My attempt:

    array_replace(
            array_fill_keys($fields, false),
            array_intersect_key($array,             # Keys in array, without order
                    array_flip($fields))));

It was easy to get the keys in the same order as $array. Then to get them in the proper order, I built an array with keys equal to $fields. Array_replace did the rest.

The solution is "stable" in that missing keys in $array will be replaced with FALSE and can thus be filtered out if need be.

array_flip walks the field array of size N once, array_intersect walks M time a N sized array, array_fill_keys costs N and the final array_replace is, I believe, N^2.

So total cost is M*N^5.

Walking the smallest array and picking the values from the large one is O(M^2*N^2), so for large values of N I suspect that the PHP solution might prove faster. This doesn't enter keys which are not in the data array.

   $answer = array();
   foreach($fields as $fld)     // N-sized cycle
       if (isset($array[$fld])) // Cost M
           $answer[$fld] =      // Assignment is N*1/2
               $array[$fld];    // Getting value is another M

(some time and much puzzlement later)

I ran a check and I think I must be making some silly mistake, for the times I'm getting are totally nonsensical. Admittedly I'm using a very short $fields array, so I'd expect skewed results, but not this skewed. Unless $answer[$fld] is calculated with some REALLY clever hash trick, whereby the true cost of the interpreted solution is not O(M^2*N^2) but O(K*N^2) with K small.

If anyone wants to play with times or tell me what stupid mistake I might have made, here's the benchmark.

I was of two minds about posting this, because the other obvious explanation is that I made a ridiculous, silly mistake somewhere and I'm going to end up with egg on my face, but, oh, what the hell.

    $array = array(
        'random' => 1,
        'pewpew' => 2,
        'temp' => 5,
        'xoxo' => 3,
        'qweqweqe' => 4,
    );
    $fields = array('random', 'xoxo', 'temp');
    // Let's not print anything just yet, maybe that's what screwing the timer?
    $results = '';
    $duh = 0;

    for ($cycle = 0; $cycle < 10; $cycle++)
    {
            // Add some more elements to $array.
            for ($i = 0; $i < 10000; $i++)
            {
                    $k = uniqid();
                    $array[$k] = 42;
            }
            $start  = explode(' ', microtime());
            // WTF? Do more cycles to average the timing.
            for ($j = 0; $j < 10; $j++)
            {
                    // 0 or 1 to switch
                    if (1)
                    {
                    // INTERPRETED ANSWER
                    $answer = array();
                    foreach($fields as $fld)     // N-sized cycle
                       if (isset($array[$fld])) // Cost M
                           $answer[$fld] =      // Assignment is N*1/2
                               $array[$fld];    // Getting value is another M
                    } else {
                    // FUNCTION ANSWER
                    $answer = array_replace(
                            array_fill_keys($fields, false),
                            // array_combine($fields, $fields),
                            array_intersect_key($array,             # Keys in array, without order
                                    array_flip($fields)));
                    }
                    // USE $answer so to avoid premature optimization?
                    // You can't be that clever.
                    $duh += strlen(serialize($answer));
            }
            $stop   = explode(' ', microtime());
            // An error in timing? Check via a stupid roundabout.
            $int    = $stop[1]-$start[1]+1;
            $int    += ($stop[0]-$start[0]);
            $int    -= 1;
            $elapsed = number_format($int * 1000000, 2);
            $results        .= "".(5000*$cycle)." = $elapsed us.\n";
    }

    // I need to get in result:
    $wanted = array(
        'random' => 1,
        'xoxo' => 3,
        'temp' => 5,
    );
    // DID we get the right answer?
    print "Wanted:\n"; print_r($wanted);
    print "Gotten:\n"; print_r($answer);
    print "Results: $results\n$duh -- count of array is " . count($array);

    // And yet I have always the same realtime, name of a dog, how can that be?
    // I must be doing something REALLY REALLY wrong somewhere.
LSerni
  • 55,617
  • 10
  • 65
  • 107
0

This is a solution that also handles the case when some $fields are not present as keys in $array:

$flip = array_flip($fields);
$result = array_intersect_key(array_replace($flip, $array), array_intersect_key($flip, $array));

If all $fields are known to be present as keys in $array there is this simpler solution:

$flip = array_flip($fields);
$result = array_intersect_key(array_replace($flip, $array), $flip);

which can be written as a one-liner like this:

$result = array_intersect_key(array_replace($flip=array_flip($fields), $array), $flip);

If some $fields are not keys of $array, but $array contains counts, so that it makes sense to return a 0 count for missing keys, we can replace flip() with array_fill_keys($fields, 0):

$result = array_intersect_key(array_replace($fill=array_fill_keys($fields, 0), $array), $fill);

to which we can apply an array_filter() to filter out the 0s again, if needed. By replacing 0 with false or null we can flag and handle the absence of a key in $array when the values are not counts.

The sad thing is that these solutions, like all others on this page, have to work through all keys of the $array, while any explicit loop would be on $fields. For the time being, it seems that when count($array) is much bigger than count($fields) there is no array-function-based solution as fast as an explicit loop (since they would explicitly construct the result in callback functions, I consider array_walk() and array_reduce() to be explicit loops here).

The problem is that none of the available array_ functions breaks the association between keys and values, and since we would like to loop on $fields, or rather a flipped array thereof, also to conserve its sort order, while keeping the values of $array, we are out of luck.

Walter Tross
  • 12,237
  • 2
  • 40
  • 64
0

Easy Way:

$array = array(
   'random' => 1,
        'pewpew' => 2,
        'temp' => 5,
        'xoxo' => 3,
        'qweqweqe' => 4,
);

$fields = array('random', 'xoxo', 'temp');

$output = array();

foreach ($fields as $value) 
    if(isset($array[$value]))
         $output[$value]=$array[$value];            
Yehuda
  • 457
  • 2
  • 6
  • 16
-1

try:

$result=array();
reset($fields);
while(list($key,$value)=each($fields))
{
    if(isset($array[$value])) $result[$value]=$array[$value];
}
Waygood
  • 2,657
  • 2
  • 15
  • 16
-1

This will work and preserve order for you:

$fields = array_flip($fields);
array_merge($fields,array_intersect_key($array, $fields));
$fields = array_keys($fields);

note, you could just call array_flip twice, but the above seemed a bit "cleaner".

nathan
  • 5,402
  • 1
  • 22
  • 18
-1

The PHP function is called array_diff_key.

Sample code:

$array = array(
    'random' => 1,
    'pewpew' => 2,
    'temp' => 5,
    'xoxo' => 3,
    'qweqweqe' => 4
);

$fields = array('random', 'xoxo', 'temp');
$result = array_diff_key($fields, $array);

This will produce the desired result.

Demo: http://shaquin.tk/experiments/array1.php

EDIT: If $fields can contain a value that is not a key in $array, then use this code:

$result = array_diff_key($fields, $array);
$result = array_intersect_key(array_flip($fields), $array);
$result = array_flip(array_diff(array_keys($result), $array));
$result = array_replace($result, $array);
$result = array_flip(array_intersect(array_flip($result), $fields));

It may be possible to optimize this a bit, but it works!

Note: I cannot link to an example, since my (hosted) site doesn't have >= PHP 5.3, however, I can link to a similar one: http://shaquin.tk/experiments/array2.php.

uınbɐɥs
  • 7,236
  • 5
  • 26
  • 42
  • This produces unexpected results (additional entries) if $fields has values not present in the $array keys. – Sarke Jul 08 '12 at 12:42
  • Oops, I forgot it needed to be PHP 5.2 - there is a (recursive) function in a comment at http://php.net/manual/en/function.array-replace.php – uınbɐɥs Jul 12 '12 at 02:37