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(...)
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);
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.
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.
I have an array:
$arr = array('foo', 'bar', 'bash', 'monkey', 'badger');
I want to have the elements in that array appear as the variables in my list():
list($foo, $bar, $bash, $monkey, $badger) = $data;
Without actually specifying the variables, I tried;
list(implode(",$", $arr)) = $data; and
list(extract($arr)) = $data;
But they don't work, I get:
Fatal error: Can't use function return value in write context
Does anyone have any idea whether this is possible?
UPDATE: more context:
I am getting a CSV of data from an API, the first row is column names, each subsequent row is data. I want to build an associative array that looks like this:
$data[0]['colname1'] = 'col1data';
$data[0]['colname2'] = 'col2data';
$data[0]['colname3'] = 'col3data';
$data[1]['colname1'] = 'col1data';
$data[1]['colname2'] = 'col2data';
$data[1]['colname3'] = 'col3data';
Before I do that, however, I want to make sure I have all the columns I need. So, I build an array with the column names I require, run some checks to ensure the CSV does contain all the columns I need. Once thats done, the code looks somewhat like this (which is executed on a foreach() for each row of data in the CSV):
//$data is an array of data WITHOUT string indexes
list( $col1,
$col2,
$col3,
...
$col14
) = $data;
foreach($colNames AS $name)
{
$newData[$i][$name] = $$name;
}
// Increemnt
$i++;
As I already HAVE an array of column name, I though it would save some time to use THAT in the list function, instead of explicitly putting in each variable name.
The data is cleaned and sanitised elsewhere.
Cheers,
Mike
I want to have the elements in that array appear as the variables in my list():
i think there is your problem in understanding. list() does not create a new list structure or variable, it can only be used to assign many variables at once:
$arr = array(1, 2, 3);
list($first, $second, $third) = $arr;
// $first = 1, $second = 2, $third = 3
see http://php.net/list for more information.
you are probably looking for an associative array. you can create it with the following code:
$arr = array('first' => 1, 'second' => 2, 'third' => 3);
// $arr['first'] = 1, …
If some rows in your input file are missing columns, you can't really know which one is missing. Counting the number of values and aborting or jumping to next row when less than expected should be enough.
... unless you set the rule that last columns are optional. I'll elaborate on this.
Your code sample is far for complete but it seems that your problem is that you are using arrays everywhere except when matching column names to cell values. Use arrays as well: you don't need individual variables and they only make it harder. This is one of the possible approaches, not necessarily the best one but (I hope) clear enough:
<?php
$required_columns = array('name', 'age', 'height');
$input_data = array(
array('John', 33, 169),
array('Bill', 40, 180),
array('Ashley', 21, 155),
array('Vincent', 13), // Incomplete record
array('Michael', 55, 182),
);
$output = array();
foreach($input_data as $row_index => $row){
foreach($required_columns as $col_index => $column_name){
if( isset($row[$col_index]) ){
$output[$row_index][$column_name] = $row[$col_index];
}
}
}
print_r($output);
?>
I've used $row_index and $col_index for simplicity.
Original answer, for historical purposes only ;-)
I can't really understand your specs but PHP features variable variables:
<?php
$arr = array('foo', 'bar', 'bash', 'monkey', 'badger');
foreach($arr as $i){
$$i = $i;
}
?>
Now your script has these variables available: $foo, $bar... It's quite useless and potentially dangerous but it does what you seem to need.
You are trying to use a language construct in a manner in which it's not meant to be used. Use variable variables as Alvaro mentioned.
$arr = array('foo', 'bar', 'bash', 'monkey', 'badger');
foreach ($arr as $index => $key) {
$$key = $data[$index];
}
or
$arr = array('foo', 'bar', 'bash', 'monkey', 'badger');
$result = array();
foreach ($arr as $index => $key) {
$result[$key] = $data[$index];
}
extract($result);
In short, do not use "list", use arrays and associated arrays. Write helper functions to make your code clearer.
why not immediatly call the vars like
$arr['foo']
or
$arr[0]
If you want to extract the elements of $data into the current context, you can do that with extract. You might find it useful to call array_intersect_key first to pare down $data to the elements that you want to extract.
May be try like this :
$arr = array('foo', 'bar', 'bash', 'monkey', 'badger');
$data = "$".implode(",", $arr);
echo str_replace(",", ",$", $data);