I'm trying to sort a multi-dimensional array by another array, but have so far come up short.
array_multisort seems be working only for real sorting.
Suppose I have these 2 arrays:
$order = array(2,3,1);
$data = array(
array('id' => 1, 'title' => 'whatever'),
array('id' => 2, 'title' => 'whatever'),
array('id' => 3, 'title' => 'whatever')
);
Now I would like to sort my $data array according to the order in my $order array.
This is what I would like the result to be:
$data = array(
array('id' => 2, 'title' => 'whatever'),
array('id' => 3, 'title' => 'whatever')
array('id' => 1, 'title' => 'whatever'),
);
I can accomplish this easily by running a nested loop, but that would not scale well (my array is pretty big, and the arrays have many more fields).
In your example the ids in the $data array are are numbered consecutively and starting at 1. The code I give below assumes this is always the case. If this is not the case, the code does not work.
$result = array();
$index = 0;
foreach ($order as $position) {
$result[$index] = $data[$position - 1];
$index++;
}
At http://codepad.org/YC8w0yHh you can see that it works for your example data.
EDIT
If the assumption mentioned above does not hold, the following code will achieve the same result:
<?php
$data = array(
array('id' => 1, 'title' => 'whatever'),
array('id' => 2, 'title' => 'whatever'),
array('id' => 3, 'title' => 'whatever')
);
$order = array(2,3,1);
$order = array_flip($order);
function cmp($a, $b)
{
global $order;
$posA = $order[$a['id']];
$posB = $order[$b['id']];
if ($posA == $posB) {
return 0;
}
return ($posA < $posB) ? -1 : 1;
}
usort($data, 'cmp');
var_dump($data);
See http://codepad.org/Q7EcTSfs for proof.
By calling array_flip() on the $order array it can be used for position lookup. This is like a hashtable lookup, which is linear in time, or O(n). You cannot do better.
There is no built-in function for this in PHP and i am unable to think of any custom function, which would do this using usort. But array_map is simple enough, imo, so why not use it instead?
$sorted = array_map(function($v) use ($data) {
return $data[$v - 1];
}, $order);
For those of you who want to sort data based on an array with actual IDs, rather than based on an array with indexes like in the accepted answer - you can use the following simple comparison function for the usort:
usort($data, function($a, $b) use ($order) {
$posA = array_search($a['id'], $order);
$posB = array_search($b['id'], $order);
return $posA - $posB;
});
So the following example will work fine and you won't get the Undefined offset notices and an array with null values:
$order = [20, 30, 10];
$data = [
['id' => 10, 'title' => 'Title 1'],
['id' => 20, 'title' => 'Title 2'],
['id' => 30, 'title' => 'Title 3']
];
usort($data, function($a, $b) use ($order) {
$posA = array_search($a['id'], $order);
$posB = array_search($b['id'], $order);
return $posA - $posB;
});
echo '<pre>', var_dump($data), '</pre>';
Output:
array(3) {
[0]=>
array(2) {
["id"]=>
int(20)
["title"]=>
string(7) "Title 2"
}
[1]=>
array(2) {
["id"]=>
int(30)
["title"]=>
string(7) "Title 3"
}
[2]=>
array(2) {
["id"]=>
int(10)
["title"]=>
string(7) "Title 1"
}
}
This would be how I would do. I would use a custom usort function (arr_sort) in conjunction with the $data array.
<?php
$order = array(2,3,1);
$data = array(
array('id' => 1, 'title' => 'whatever'),
array('id' => 2, 'title' => 'whatever'),
array('id' => 3, 'title' => 'whatever')
);
function arr_sort($a,$b){
global $order;
foreach ($order as $key => $value) {
if ($value==$a['id']) {
return 0;
break;
}
if ($value==$b['id']) {
return 1;
break;
}
}
}
usort($data,'arr_sort');
echo "<pre>";
print_r($data);
echo "<pre>";
You could try using a custom sort with usort(). This way you can use the first array to determine the order of the second array.
Related
Is there a PHP function I've missed that will change the keys of the parent array when given the key name of its child(associative array) or is there at least an alternative to a foreach loop which i am using at the moment to change the keys.
Example array
$arr = array(
array(
'id' => 1,
'name' => 'one',
),
array(
'id' => 2,
'name' => 'two',
),
array(
'id' => 3,
'name' => 'three',
)
);
I would like it to work like so..
$arr_name = array_change_key($arr,'name');
print_r($arr_name);
$arr_name => array(
'one', => array(
'id' => 1,
'name' => 'one',
),
'two' => array(
'id' => 2,
'name' => 'two',
),
'three' => array(
'id' => 3,
'name' => 'three',
)
);
//$arr is unchanged
This is just an added extra (not sure if possible)
array_change_key($arr,'name');
print_r($arr);
//$arr has changed because it doesn't have a variable to set
$arr => array(
'one', => array(
'id' => 1,
'name' => 'one',
),
'two' => array(
'id' => 2,
'name' => 'two',
),
'three' => array(
'id' => 3,
'name' => 'three',
)
);
print_r($arr[0]); //undefined index
If I understand the question correctly, something like:
$arr = array_combine(
array_column($arr, 'name'),
$arr
);
will use the name value from each record as the parent key, and give
array(3) {
["one"]=>
array(2) {
["id"]=>
int(1)
["name"]=>
string(3) "one"
}
["two"]=>
array(2) {
["id"]=>
int(2)
["name"]=>
string(3) "two"
}
["three"]=>
array(2) {
["id"]=>
int(3)
["name"]=>
string(5) "three"
}
}
You would have to tell the function whether or not to "pass by reference" it has no way of knowing whether you are trying to set the returned result to a variable;
function array_change_key(array &$array, $key, $pass_by_reference = false){
if($pass_by_reference){
// check is_scalar($key)
if(!is_scalar($key)) return FALSE;
// we already know isset($array), is_array($array) and isset(key) are true because $pass_by_reference is true;
$array = markBakersAnswer($array,$key);
return TRUE;
// pass-by-reference functions usually return true or false
}
return markBakersAnswer($array,$key);
}
MarkBakersAnswer +1
$new_array = array_change_key($arr, 'name'); // $arr unchanged and $new_array == array with new keys
$new_array = array_change_key($arr, 'name', false); // $arr unchanged and $new_array == array with new keys
$new_array = array_change_key($arr, 'name', true); // $arr changed (new keys), $new_array = TRUE;
$new_array = array_change_key($arr, array(), true); // $arr changed (new keys), $new_array = FALSE;
I want to sort a mulitdimensional array according to a field in the inner array, like this:
$result = array(
array("first" => 1, "second" => 5),
array("first" => 3, "second" => 8),
array("first" => 6, "second" => 7),
array("first" => 6, "second" => 1)
);
sort($result,"second");
/*
$result = array(
array("first" => 6, "second" => 1),
array("first" => 1, "second" => 5),
array("first" => 6, "second" => 7),
array("first" => 3, "second" => 8)
);
*/
Is there something like the intended sort function here in PHP or do I have to reimplement that?
This one is very much simple function for sorting array.
function sort_by_key($a, $subkey) {
foreach($a as $k=>$v) {
$b[$k] = strtolower($v[$subkey]);
}
asort($b);
foreach($b as $key=>$val) {
$c[] = $a[$key];
}
return $c;
}
You can call it like this in your case:
sort_by_key($result , 'second');
use usort for this
This function will sort an array by its values using a user-supplied comparison function. If the array you wish to sort needs to be sorted by some non-trivial criteria, you should use this function.
function cmp($a, $b) {
if ($a['second'] == $b['second']) {
return 0;
}
return ($a['second'] < $b['second']) ? -1 : 1;
}
usort($array, 'cmp');
You can even sort of 'second' first and 'first' second :) (sort on 'first' if 'second' is the same)
function cmp($a, $b) {
if ($a['second'] == $b['second']) {
if ($a['first'] == $b['first']) {
return 0;
}
return ($a['first'] < $b['first']) ? -1 : 1;
}
return ($a['second'] < $b['second']) ? -1 : 1;
}
usort($array, "cmp");
I have the following two associative arrays:
$arr1 = array(
'id' => 1,
'text' => 'Some text is here',
'timestamp' => 130458750
)
$arr2 = array(
'post_id' => 12,
'content' => 'Some content is here too',
'created_at' => 1402154823
)
I want to sort these two arrays based on timestamp and created_at keys, i.e. the larger integer is first and lesser second and so on. Is that possible using PHP's built-in functions? If not, how may I approach the problem?
EDIT
The desired result is: Here, $arr1's timestamp is less and $arr2's timestamp (i.e. created_at) is larger. So, I want to get a combination of $arr1 and $arr2 where $arr2 is first and $arr1 is second. Something like:
$sorted_arr = array($arr2, $arr1);
First let me say that one of your array contains timestamp and second contains created_at. I assumed both of them should be created_at.
In case you want to "sort" just two entries like you said in the comments, the task is straightforward:
<?php
$arr1 = array(
'id' => 1,
'text' => 'Some text is here',
'created_at' => 130458750 #corrected from "timestamp"
);
$arr2 = array(
'post_id' => 12,
'content' => 'Some content is here too',
'created_at' => 1402154823
);
$posts = $arr2['created_at'] > $arr1['created_at']
? [$arr2, $arr1]
: [$arr1, $arr2];
But apparently what you're after is a way to sort the posts if they're in array of unknown length. In that case you should use uasort built-in PHP function, which allows to sort by user-defined function and maintains indexes in associative arrays (as opposed to plain usort). Example code would then look like this:
$posts = [$arr1, $arr2];
uasort($posts, function($a, $b)
{
return $b['created_at'] - $a['created_at'];
});
var_dump($posts);
which outputs:
array(2) {
[1]=>
array(3) {
["post_id"]=>
int(12)
["content"]=>
string(24) "Some content is here too"
["created_at"]=>
int(1402154823)
}
[0]=>
array(3) {
["id"]=>
int(1)
["text"]=>
string(17) "Some text is here"
["created_at"]=>
int(130458750)
}
}
To get reverse order you may just reverse arguments in custom sort function, i.e. swap $a with $b.
Combining rr-'s solution, I came up with the following:
$arr1 = array(
'id' => 1,
'text' => 'Some text is here',
'timestamp' => 130458750
);
$arr2 = array(
'post_id' => 12,
'content' => 'Some content is here too',
'created_at' => 1402154823
);
$arr3 = array(
'post_id' => 21,
'content' => 'Some content is here too',
'created_at' => 1258475
);
$arr = [];
$arr[] = $arr1;
$arr[] = $arr2;
$arr[] = $arr3;
uasort($arr, function($a, $b)
{
$t1 = isset($a['timestamp']) ? $a['timestamp'] : $a['created_at'];
$t2 = isset($b['timestamp']) ? $b['timestamp'] : $b['created_at'];
return $t2 - $t1
});
var_dump($arr);
It sorts the arrays even when the keys are different.
I have an array of the following structure:
Array ( [0] => Array ( [event] => event1 [Weight] => 2 )
And I am trying to sort by 'Weight'. I have tried this:
function cmp($a, $b) {
if ($a['Weight'] > $b['Weight'] ){
return -1;
} else {
return 1;
}
}
But it isnt sorting by weight. It seems to be how i refer to weight but I am unsure how to do this correctly.
You can sort it like this:
uasort($array, function ($a, $b) {
$c = $a['Weight'];
$d = $b['Weight'];
if ($c == $d){
return 0;
}
else if($c > $d){
return 1;
}
else{
return -1;
}
});
<?php
// Obtain a list of columns
//$data = Your Array
foreach ($data as $key => $row) {
$weight[$key] = $row['Weight'];
}
// Sort the data with volume descending, edition ascending
// Add $data as the last parameter, to sort by the common key
array_multisort($weight, SORT_ASC, $data);
?>
I think your problem must be the function you use to do the actual sorting, Here is a complete example of how to sort in ether ascending or descending order.
$array = array(
array( 'event'=> 'something', 'Weight' => 2),
array( 'event'=> 'something', 'Weight' => 1),
array( 'event'=> 'something', 'Weight' => 10),
array( 'event'=> 'something', 'Weight' => 10),
array( 'event'=> 'something', 'Weight' => 0),
array( 'event'=> 'something', 'Weight' => 1),
array( 'event'=> 'something', 'Weight' => -10),
);
function weightCmp($isAscending = true) {
return function($a, $b) use ($isAscending) {
$diff = $a['Weight'] - $b['Weight'];
return $isAscending ? $diff : $diff * -1;
};
}
usort($array, weightCmp());
var_dump($array);
usort($array, weightCmp(false));
var_dump($array);
Basically I need to take two arrays, merge them with unique values and sum one of columns. It makes more sense when written out below:
$a = [
['ID' => 1, 'Count' => 2],
];
$b = [
['ID' => 1, 'Count' => 4],
['ID' => 2, 'Count' => 3]
];
and I need the final product to be:
$a_plus_b = [
['ID' => 1, 'Count' => 6],
['ID' => 2, 'Count' => 3]
];
I have been playing with different variations of array_merge() and array_unique(), but I can't find an efficient way to do what I need. I know I can always do nested loops, but I was hoping for something easier. Any ideas?
This should do the trick
Note: This solution requires PHP >= 5.3. There is a PHP < 5.3 solution below.
$input = array($a, $b);
// add as many result arrays to $input as you want; e.g.,
// $input = array($a, $b, $c, $d);
$output = array_count_values(
call_user_func_array(
'array_merge',
array_map(
function($arr) {
return array_fill(0, $arr['Count'], $arr['ID']);
},
call_user_func_array(
'array_merge',
$input
)
)
)
);
print_r($output);
Output
Array
(
[1] => 6
[2] => 3
)
Note the array keys above are ID values. The array values are Count values.
If you're running PHP < 5.2 you won't be able to use the inline closure with array_fill. You have to define it as a separate function.
$input = array($a, $b);
function _fill($arr) {
return array_fill(0, $arr['Count'], $arr['ID']);
}
$output = array_count_values(
call_user_func_array(
'array_merge',
array_map(
'_fill',
call_user_func_array(
'array_merge',
$input
)
)
)
);
print_r($output);
From here, converting the output to your desired format is a trivial task.
Please don't over-engineer such a basic task. Iterate both array with a single loop and assign temporary keys using ID values. If encountering a respective ID key more than once, just add the new Count value to the stored value.
Code: (Demo)
$result = [];
foreach (array_merge($a, $b) as $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
}
var_export(array_values($result));
Output:
array (
0 =>
array (
'ID' => 1,
'Count' => 6,
),
1 =>
array (
'ID' => 2,
'Count' => 3,
),
)
Functional programming can be used as well to achieve the same result -- array_reduce() is ideal since the number of elements in the output will be equal to or less than the number of elements in the input data.
Code: (Demo)
var_export(
array_values(
array_reduce(
array_merge($a, $b),
function ($result, $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
return $result;
},
[]
)
)
);
If the ID values in the first array are guaranteed to be unique, you can avoid the array_merge() call by porting the $a array to the result array and assigning temporary keys using the ID values. (Demo)
$result = array_column($a, null, 'ID');
foreach ($b as $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
}
var_export(array_values($result));