Array Map? Walk? Something Else? - php

Thanks for all the inputs on the last two questions I asked. Slowly but surely, gettin there in terms of learning more. Now this next question has to do with array mapping/walking. I am not sure how to get this to work, but this is what's going on:
I have three arrays, each made up of keys with their own arrays. Each array looks like:
$arr1 = array(
'service3'=>array('price'=>'2.00','transit'=>'3'),
'service2'=>array('price'=>'4.00', 'transit'=>'1')
);
$arr2 = array(
'service1'=>array('price'=>'6.00','transit'=>'2'),
'service2'=>array('price'=>'8.00', 'transit'=>'1')
);
arr3 = array(
'service3'=>array('price'=>'2.00','transit'=>'3'),
'service2'=>array('price'=>'4.00', 'transit'=>'1')
);
and so on.
What I want to do is add the services from each of the three arrays with the matching keys. The caveats are that each array may be of a different length, and each may not have the same service (see above array 3 for what I mean).
I was looking for a cleaner way to add the three arrays together without running a bunch of foreach loops or creating temp arrays. It seems like array_map with a callback function would be a good way to go, or perhaps even array_walk. Just not sure how to proceed, because of the fact that the arrays may not be of the same length or even have the same service keys.

Here is a quick function, you can use (untested):
<?php
function group_by_service() {
$return = array();
$arrays = func_get_args();
foreach($arrays as $arr) {
foreach($arr as $service => $props) {
if (!array_key_exists($service, $return)) {
$return[$service] = $props;
} else {
$return[$service]['price'] += $props['price'];
$return[$service]['transit'] += $props['transit'];
}
}
}
return $return;
}
You can, then, use this function like this:
<?php
$arr1 = array(
'service3'=>array('price'=>'2.00','transit'=>'3'),
'service2'=>array('price'=>'4.00', 'transit'=>'1')
);
$arr2 = array(
'service1'=>array('price'=>'6.00','transit'=>'2'),
'service2'=>array('price'=>'8.00', 'transit'=>'1')
);
arr3 = array(
'service3'=>array('price'=>'2.00','transit'=>'3'),
'service2'=>array('price'=>'4.00', 'transit'=>'1')
);
$grouped = group_by_service($arr1, $arr2, $arr3);
print_r($grouped);

Related

When should I define a new array in php?

I want to know which one is more optimized?
Defining several separated array
Using nested array keys
Here is an example:
function arr(){
$arr1 = array();
$arr2 = array();
$arr3 = array();
$arr1['key1'] = 'val1';
$arr2['key1'] = 'val1';
$arr3['key1'] = 'val1';
return array($arr1, $arr2, arr3);
}
OR
function arr(){
$arr = array();
$arr['arr1']['key1'] = 'val1';
$arr['arr2']['key1'] = 'val1';
$arr['arr3']['key1'] = 'val1';
return $arr1;
}
Actually I have not any problem, I just want to know, which approach is faster in php? nested key or separated array?!
If you have a fixed set of keys, then create the whole array in a single statement, as below. It will be both the fastest and the most readable way.
function arr(){
return array(
array('key1' => 'val1'),
array('key1' => 'val2'),
array('key1' => 'val3'),
);
}
Regarding the performance difference between the two ways — you may benchmark the two functions to find out, as Pamblam did. But the difference will be so minimal that it will barely ever matter.

Individually sorting subarrays of an array in PHP

I have an array of numeric subarrays. I want to sort all the subarrays, and then sort the whole array, and remove duplicates. Using sort($val) doesn't work, so I found the following workaround with $derp, which I find insanely stupid:
$arr = array( array(5,6), array(1,2), array(6,5) );
foreach ($arr as $key => $val) {
$derp = $val;
sort($derp);
$arr[$key] = $derp;
}
sort($arr);
$arr = array_map("unserialize", array_unique(array_map("serialize", $arr)));
The result is array( array(1,2), array(5,6) ). Is that is the correct way to do this in PHP, or is there a better and shorter way?
I created a pastebin as a response to the first answer: pastebin.com/Y5vNvKKL
This question is not anymore just about a less goofy way to write this: Now the question is:Why does sort() in array_work() not give the same result as sort() in foreach?
By the way: This is about finding the partitions of multisets.
I'd approach it like this:
array_walk($arr, 'sort');
$deduped = array();
foreach ($arr as $val) {
$deduped[serialize($val)] = $val;
}
$arr = array_values($deduped);

Shuffle an array of Q&A pairs

What is the best way you think to shuffle a multi-dimensional array in the following structure, so that question-answer pairs are separated?
$myArray = array(
array('question' => 'q1', 'answer' => 'a1'),
array('question' => 'q2', 'answer' => 'a2'),
array('question' => 'q3', 'answer' => 'a3')
//...
//...
);
What I need is to turn this:
q1-a1, q2-a2, q3-a3...
into this:
q3-a2, q4-a3, q1-a9...
I get this array from a questions database. I want to display question-answer pairs but shuffled obviously. I have a few solutions in my mind, just curious for clever ways to do it ;)
Well you could just simply get the questions and answers, shuffle them, then reapply:
$q = $a = array();
foreach ($myArray as $value) {
$q[] = $value['question'];
$a[] = $value['answer'];
}
shuffle($q);
shuffle($a);
foreach($myArray as $key => &$value) {
$value['question'] = $q[$key];
$value['answer'] = $a[$key];
}
echo '<pre>';
print_r($myArray);
You could also use array_collumn() if available (PHP 5 >= 5.5.0):
$q = array_column($myArray, 'question');
$a = array_column($myArray, 'answer');
PHP has a plethora amount of built-in array functions. Using a combination of these functions, you could create a custom shuffle function that uses array_keys, array_values, shuffle and array_combine internally. Try the following:
// Create a copy of the original array to key
// for processing later...
$originalArray = $myArray;
function shuffleAll($myArray) {
// Pull the keys into an array and
// pull the values into another
// array...
$keys = array_keys($myArray);
$values = array_values($myArray);
// Shuffle the arrays independently...
shuffle($keys);
shuffle($values);
// Combine the arrays into one...
return array_combine($keys, $values);
}
$myArray = shuffleAll($myArray);
$myArray should now have a custom assortment of your now non-matching key-value pairs. The original values have been preserved in $originalArray for latter processing and matching.
References:
array_combine(...)
array_keys(...)
array_values(...)
shuffle(...)

Filter array by its key using the values of another array and order results by second array [duplicate]

This question already has answers here:
How to filter an associative array comparing keys with values in an indexed array?
(12 answers)
Closed 4 months ago.
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
$result=array_intersect_key($array ,array_flip($fields) );
// little trick required here...
$fields = array('random' => 0, 'xoxo' => 0, 'temp' => 0);
$result = array_intersect_key($array,$fields);
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.
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
)
);
I believe this works as you require.
$result = array_splice(array_merge(array_flip($fields) , $array) , 0 , count($fields));
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.
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);
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
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.
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];
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.
try:
$result=array();
reset($fields);
while(list($key,$value)=each($fields))
{
if(isset($array[$value])) $result[$value]=$array[$value];
}
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".
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.

How to parse key/value chain into associative array in PHP?

I have a string that looks like: key1/value1/key2/value2/ and so on
I did an explode('/',trim($mystring,'/')) on that
Now I want to haven an associative array like:
array(
'key1' => 'value1',
'key2' => 'value2',
...
);
of course i can do it with an for loop where i always read two entries and push them into a target array, but isnt there something more efficient and elegant? a one liner with some some php core function similar to ·list()· or something?
A method using array_walk() with a callback function:
function buildNewArray($value, $key, &$newArray) {
(($key % 2) == 0) ? $newArray[$value] = '' : $newArray[end(array_keys($newArray))] = $value;
}
$myString = 'key1/value1/key2/value2/';
$myArray = explode('/',trim($myString,'/'));
$newArray = array();
array_walk($myArray, 'buildNewArray', &$newArray);
var_dump($newArray);
If the format is not changin (i mean it is allways Key1/value1/key2/value2) it should be easy.
After the explode you have this:
$arr = Array('key1','value1','key2','value2')
so you now can do:
$new_arr = array();
$i = 0;
for($i=0;$i<count($arr);$i+=2)
{
$new_arr[$arr[i]] = $arr[$i+1];
}
at the end of the loop you will have:
$new_arr = array("key1"=>"value1","key2"=>"value2")
KISS :)
HTH!
One can always make yourself a one liner.
This is called User-defined functions
No need to devise something ugly-but-one-liner-at-any-cost.
Make your own function and you will get much shorter, cleaner and way more reliable result.
A function, which will not only do elementary string operations but also do something more intelligent, like parameter validation.

Categories