ordering array in specific order [duplicate] - php

This question already has answers here:
Sort multidimensional array by multiple columns
(8 answers)
Closed 5 months ago.
I have following array
Array
(
[0] => Array
(
[label] => Germany
[conversions] => 1
)
[1] => Array
(
[label] => United States
[conversions] => 8
)
[2] => Array
(
[label] => France
[conversions] => 1
)
[3] => Array
(
[label] => China
[conversions] => 1
)
[4] => Array
(
[label] => Philippines
[conversions] => 1
)
[5] => Array
(
[label] => Turkey
[conversions] => 1
)
)
I want to order following array, first by conversions (desc), then by label (asc)
output will have following sequence:
United States
China
France
Germany
Philippines
Turkey

If using >= PHP 5.3...
usort($arr, function($a, $b) {
$diff = $b['conversions'] - $a['conversions'];
if ( ! $diff) {
return strcmp($a['label'], $b['label']);
}
return $diff;
});
It is relatively simple to make it work with < PHP 5.3.

You need to use PHP's usort() function. This allows you to write a function which determines the sort order, so you can sort things into any order you like.
The function will be called repeatedly by the usort() algorithm, and will give you two parameters, being the two elements of the array that it wants to sort at any given moment. Your function should return -1 if the first of those two elements is bigger, +1 if it the second is bigger, and zero if they are to be considered the same for the purposes of the sort.
See the PHP manual page for more info and examples: http://php.net/manual/en/function.usort.php

I preferred array_multisort PHP Manual in my answer below as you can specify the sort order with parameters.
Next to flexibility it should be faster than using usort which has the problem that it's not really parametrized for the sort order, so not re-inventing the wheel as well.
For more comfort, wrap it up into a function to specify the keys as strings (Demo):
$sorted = $multisortByKey($array, 'conversions', SORT_DESC, 'label', SORT_ASC);
as in:
$array = array(
0 => array(
'label' => 'Germany',
'conversions' => 1,
),
1 => array(
'label' => 'United States',
'conversions' => 8,
),
2 => array(
'label' => 'France',
'conversions' => 1,
),
3 => array(
'label' => 'China',
'conversions' => 1,
),
4 => array(
'label' => 'Philippines',
'conversions' => 1,
),
5 => array(
'label' => 'Turkey',
'conversions' => 1,
),
);
$multisortByKey = function(array $a) {
$args = func_get_args();
$a = array_shift($args);
$extract = function($k) use($a) {return array_map(function($v) use($k) { return $v[$k]; }, $a); };
# NOTE: The following check/for-loop is not entirely correct
# as multiple sort parameters per entry are allowed. I leave this
# for practice.
$c = count($args);
if(!$c%2) throw new InvalidArgumentException('Parameter count mismatch');
for($i=0;$i<$c;$i+=2)
$args[$i] = $extract($args[$i]);
$args[] = &$a;
call_user_func_array('array_multisort', $args);
return $a;
};
$sorted = $multisortByKey($array, 'conversions', SORT_DESC, 'label', SORT_ASC);
var_dump($sorted);

try this
$myArray="your array";
<?php
foreach($myArray as $c=>$key) {
$sort_conv[$c] = $key['conversions'];
$sort_lable[$c] = $key['label'];
}
array_multisort($sort_conv, SORT_ASC, $sort_lable, SORT_STRING, $myArray);
print_r($myArray);
?>

You can use usort to provide your own sorting function
usort($a, function($x, $y) {
return $y["conversions"] < $x["conversions"] ? 1 :
($x["conversions"] < $y["conversions"] ? -1 :
strcmp($x["label"], $y["label"]))
);
});

Try the following: (didn't test it, source: PHP Example #3)
foreach ($data as $key => $row) {
$label[$key] = $row['label'];
$conversion[$key] = $row['conversion'];
}
array_multisort($conversion, SORT_DESC, $label, SORT_ASC, $data);

Related

PHP: assign priority to array values and then sort by that priority

Is there a way to assign a "priority score" to an array in PHP? Then sort based on that priority. Given my input:
Array(
[princeton] => Princeton
[stanford] => Stanford
[yale] => Yale
[mit] => MIT
[harvard] => Harvard
)
I would like the output to always have Harvard and Yale at the top, the rest alphabetically sorted:
Array(
[harvard] => Harvard
[yale] => Yale
[mit] => MIT
[princeton] => Princeton
[stanford] => Stanford
)
I checked out asort() which does the alphabetical and uasort() which is custom-defined, but I'm stuck on how I can "assign a priority". Any help would be appreciated!
uasort will sort your array by your own comparison function. I've made the function place either Harvard or Yale first (the ordering of those two will be uncertain since you haven't specified) and sort the rest alphabetically.
function cmp($a, $b) {
if ($a == 'Yale' || $a == 'Harvard') {
return -1;
} else if ($b == 'Yale' || $b == 'Harvard') {
return 1;
} else return strcmp($a, $b);
}
uasort($array, 'cmp');
Perhaps you were overthinking this.
sort by key
replace your short priority array with the alphabetized array
Code: (Demo)
$ivyLeaguers = [
'princeton' => 'Princeton',
'cornell' => 'Cornell',
'stanford' => 'Stanford',
'yale' => 'Yale',
'mit' => 'MIT',
'harvard' => 'Harvard'
];
ksort($ivyLeaguers);
var_export(
array_replace(['harvard' => null, 'yale' => null], $ivyLeaguers)
);
Output:
array (
'harvard' => 'Harvard',
'yale' => 'Yale',
'cornell' => 'Cornell',
'mit' => 'MIT',
'princeton' => 'Princeton',
'stanford' => 'Stanford',
)
The null values are overwritten but their position is respected. This technique pulls the priority schools to the top of the list based on the matching keys.
Assuming you have something like
$priotities=array(
[harvard] => 1
[yale] => 2
[mit] => 3
[princeton] => 4
[stanford] => 5
)
you could
$final=$priorites;
sort(final);
foreach ($final as $k=>$v)
$final[$k]=$universities[$k];

Unwanted behavior of PHP usort

I have a problem with PHP usort(). Let's suppose I have an array like this (that's a simplification, I've not to work with names, and I have an array of objects, not arrays):
$data = array(
array('name' => 'Albert', 'last' => 'Einstein'),
array('name' => 'Lieserl', 'last' => 'Einstein'),
array('name' => 'Alan', 'last' => 'Turing' ),
array('name' => 'Mileva', 'last' => 'Einstein'),
array('name' => 'Hans Albert', 'last' => 'Einstein')
);
As you can see, the array is sorted arbitrarily.
Now, if want to sort it by last, I do:
function sort_some_people($a, $b) { return strcmp($a['last'], $b['last']); }
usort($data, 'sort_some_people');
And I have:
Array (
[0] => Array ( [name] => Mileva [last] => Einstein )
[3] => Array ( [name] => Albert [last] => Einstein )
[1] => Array ( [name] => Lieserl [last] => Einstein )
[2] => Array ( [name] => Hans Albert [last] => Einstein )
[4] => Array ( [name] => Alan [last] => Turing )
)
That is ok, now they are sorted by last. But, as you can see, I've just completely lost the previous sorting. What am I saying? I want to preserve array sorting as it was before, but as a secondary sorting. I hope I was clear. Practically I want to sort data using something as usort() (so, a completely custom sorting), but if sorting field is identical between two items, I want to keep their relative position as it was before. Considering the given example, I want Lieserl Einstein to appear before Mileva Einstein because it was like this at beginning.
The sorting algorithms used in PHP have this property where the order is undefined if the items match.
If you need to keep the order, then you have to roll your own code.
Fortunately some one already has:
http://www.php.net/manual/en/function.usort.php#38827
$data = array(
array('name' => 'Albert', 'last' => 'Einstein'),
array('name' => 'Lieserl', 'last' => 'Einstein'),
array('name' => 'Alan', 'last' => 'Turing' ),
array('name' => 'Mileva', 'last' => 'Einstein'),
array('name' => 'Hans Albert', 'last' => 'Einstein')
);
function sort_some_people($a, $b) {
return strcmp($a['last'], $b['last']);
}
function mergesort(&$array, $cmp_function = 'strcmp') {
// Arrays of size < 2 require no action.
if (count($array) < 2) return;
// Split the array in half
$halfway = count($array) / 2;
$array1 = array_slice($array, 0, $halfway);
$array2 = array_slice($array, $halfway);
// Recurse to sort the two halves
mergesort($array1, $cmp_function);
mergesort($array2, $cmp_function);
// If all of $array1 is <= all of $array2, just append them.
if (call_user_func($cmp_function, end($array1), $array2[0]) < 1) {
$array = array_merge($array1, $array2);
return;
}
// Merge the two sorted arrays into a single sorted array
$array = array();
$ptr1 = $ptr2 = 0;
while ($ptr1 < count($array1) && $ptr2 < count($array2)) {
if (call_user_func($cmp_function, $array1[$ptr1], $array2[$ptr2]) < 1) {
$array[] = $array1[$ptr1++];
}
else {
$array[] = $array2[$ptr2++];
}
}
// Merge the remainder
while ($ptr1 < count($array1)) $array[] = $array1[$ptr1++];
while ($ptr2 < count($array2)) $array[] = $array2[$ptr2++];
return;
}
mergesort($data, 'sort_some_people');
print_r($data);
Output:
Array
(
[0] => Array
(
[name] => Albert
[last] => Einstein
)
[1] => Array
(
[name] => Lieserl
[last] => Einstein
)
[2] => Array
(
[name] => Mileva
[last] => Einstein
)
[3] => Array
(
[name] => Hans Albert
[last] => Einstein
)
[4] => Array
(
[name] => Alan
[last] => Turing
)
)
Voila!
$compare = strcmp($a['last'], $b['last']);
if ($compare == 0)
$compare = strcmp($a['name'], $b['name']);
return $compare
You're asking for a stable sorting algorithm, which php doesn't offer. Beware that even if it may appear stable sometimes, there's no guarantee of it, and so likely to misbehave given certain inputs.
If you know the sorting criteria of the other columns, you can resort it all at once to get the desired behavior. array_multisort does this. The following is a bit more powerful way in that the comparison logic is completely user defined.
// should behave similar to sql "order by last, first"
$comparatorSequence = array(
function($a, $b) {
return strcmp($a['last'], $b['last']);
}
, function($a, $b) {
return strcmp($a['first'], $b['first']);
}
// more functions as needed
);
usort($theArray, function($a, $b) use ($comparatorSequence) {
foreach ($comparatorSequence as $cmpFn) {
$diff = call_user_func($cmpFn, $a, $b);
if ($diff !== 0) {
return $diff;
}
}
return 0;
});
If you really need a stable sort because the existing order of elements isnt well defined, try writing your own sort. bubble sort for example is very easy to write. This is a good solution so long as the number of elements in your lists are relatively small, otherwise look to implement one of the other stable sorting algorithms.
Try this:
function sort_some_people($a, $b)
{
$compareValue = 10 * strcmp($a['last'], $b['last']);
$compareValue += 1 * strcmp($a['name'], $b['name']);
return $compareValue;
}
Sample: http://codepad.org/zkHviVBM
This functions uses each digit of the decimal system for a sort criteria. The one that comes first has the highest digit. Might not be the smartest, but works for me.

Sorting on 2 columns in PHP without having to redo MySQL query

I have a $result variable in PHP from a MySQL query & I would like to sort it (with PHP) on 2 different columns.
I am doing this anytime a user clicks a different sort method, so I don't want to have to redo the MySQL query with ORDER BY ...., .... each time.
It would probably be more efficient to have MySQL do the ordering for you, but if you want to do it in PHP, check out usort which will let you sort an array using a callback function to decide the ordering.
If your $result is an array of associative database rows, here's a very simple example of using usort to sort on a string column 'foo' ...
//sort on string column
function sortStringFoo($a, $b)
{
return strcmp($a['foo'], $b['foo']);
}
usort($result, 'sortStringFoo');
Here's how you'd do a numeric sort...
//sort on numeric column
function sortNumericBar($a, $b)
{
if ($a['bar'] == $b['bar']) {
return 0;
}
return ($a['bar'] < $b['bar']) ? -1 : 1;
}
usort($result, 'sortNumericBar');
That illustrates the basics of how to use usort, but how might we sort on several columns at once? Here's how we could combine our comparison callbacks to illustrate this by sorting on 'foo' and then 'bar'...
function sortFooBar($a,$b)
{
$order=sortStringFoo($a,$b);
if ($order==0)
$order=sortNumericBar($a,$b);
return $order;
}
usort($result, 'sortFooBar');
You could unroll sortFooBar into a single function, but this illustrates the technique: compare first column, if same, compare second column, and so on.
You can achieve this by splitting all the columns of your table into separate arrays, then doing an array_multisort (notice example #3). Here is a test case:
<?php
$arr = array(
array(
'name' => 'Maria',
'surname' => 'White',
'age' => 24
),
array(
'name' => 'John',
'surname' => 'Brown',
'age' => 32
),
array(
'name' => 'John',
'surname' => 'Brown',
'age' => 33
)
);
$names = array();
$surnames = array();
$ages = array();
foreach($arr as $key => $row){
$names[$key] = $row['name'];
$surnames[$key] = $row['surnames'];
$ages[$key] = $row['age'];
}
// perform any sorting task you like
array_multisort($names, SORT_ASC, $surnames, SORT_ASC, $ages, SORT_DESC, $arr);
print_r($arr);
?>
The output will be:
Array
(
[0] => Array
(
[name] => John
[surname] => Brown
[age] => 33
)
[1] => Array
(
[name] => John
[surname] => Brown
[age] => 32
)
[2] => Array
(
[name] => Maria
[surname] => White
[age] => 24
)
)
If you split your columns into arrays, you have the power to sort by any column on any direction.

Count number of different strings?

I have an array that looks like
Array
(
[1] => Array
(
[0] => Date
[1] => Action
)
[2] => Array
(
[0] => 2011-01-22 11:23:19
[1] => SHARE_TWEET
)
[3] => Array
(
[0] => 2011-01-22 11:23:19
[1] => SHARE_FACEBOOK
)
and many other different values (about 10), what I want to do is I want to count the number of times a string is in the array. I was going to use array_count_values but it doesn't count multidimensional arrays.
Any other options?
This could be done by first flattening the array, and then using array_count_values() on it:
For flattening, here is the trick:
$array = call_user_func_array('array_merge', $arrays);
And then:
$counts = array_count_values($array);
Output:
array (
'Date' => 1,
'Action' => 1,
'2011-01-22 11:23:19' => 2,
'SHARE_TWEET' => 1,
'SHARE_FACEBOOK' => 1,
)
Full code:
$array = call_user_func_array('array_merge', $arrays);
var_export(array_count_values($array));
Any time you're dealing with arrays, especially with loops in PHP I can't string enough suggest you look at the array documentation, You'd be suprised how quickly you realise most of the loops in your code is unnecessary. PHP has a built in function to achieve what you're after called array_walk_recursive. And since you're using PHP5 you can use closures rather that create_function (which can be very troublesome, especially to debug, and can't be optimised by the PHP interpreter afik)
$strings = array();
array_walk_recursive($arr, function($value, $key) use (&$strings) {
$strings[$value] = isset($strings[$value]) ? $strings[$value]+1 : 1;
});
I know, unary statements aren't always clear, but this one is simple enough, but feel free to expand out the if statement.
The result of the above is:
print_r($strings);
Array
(
[Date] => 1,
[Action] => 1,
[2011-01-22 11:23:19] => 2,
[SHARE_TWEET] => 1,
[SHARE_FACEBOOK] => 1,
)
Pseudo Code
$inputArray = // your array as in the example above
foreach ($inputArray as $key => $value) {
$result[$value[1]] = $result[$value[1]] + 1;
}
var_dump($result);
Here is a way to do the job:
$arr = Array (
1 => Array (
0 => 'Date',
1 => 'Action'
),
2 => Array (
0 => '2011-01-22 11:23:19',
1 => 'SHARE_TWEET'
),
3 => Array (
0 => '2011-01-22 11:23:19',
1 => 'SHARE_FACEBOOK'
)
);
$result = array();
function count_array($arr) {
global $result;
foreach($arr as $k => $v) {
if (is_array($v)) {
count_array($v);
} else {
if (isset($result[$v])) {
$result[$v]++;
} else {
$result[$v] = 1;
}
}
}
}
count_array($arr);
print_r($result);
output:
Array
(
[Date] => 1
[Action] => 1
[2011-01-22 11:23:19] => 2
[SHARE_TWEET] => 1
[SHARE_FACEBOOK] => 1
)

How do I sort a PHP array by an element nested inside? [duplicate]

This question already has answers here:
PHP Sort a multidimensional array by element containing Y-m-d H:i:s date
(11 answers)
Closed 11 months ago.
I have an array like the following:
Array
(
[0] => Array
(
'name' => "Friday"
'weight' => 6
)
[1] => Array
(
'name' => "Monday"
'weight' => 2
)
)
I would like to grab the last values in that array (the 'weight'), and use that to sort the main array elements. So, in this array, I'd want to sort it so the 'Monday' element appears before the 'Friday' element.
You can use usort as:
function cmp($a, $b) {
return $a['weight'] - $b['weight'];
}
usort($arr,"cmp");
Can be done using an anonymous function.
Also if your 'weight' is a string use one of the other returns (see the commented out lines):
<?php
$arr = array(
0 => array (
'name' => 'Friday',
'weight' => 6,
),
1 => array (
'name' => 'Monday',
'weight' => 2,
),
);
// sort by 'weight'
usort($arr, function($a, $b) { // anonymous function
// compare numbers only
return $a['weight'] - $b['weight'];
// compare numbers or strings
//return strcmp($a['weight'], $b['weight']);
// compare numbers or strings non-case-sensitive
//return strcmp(strtoupper($a['weight']), strtoupper($b['weight']));
});
var_export($arr);
/*
array (
0 => array (
'name' => 'Monday',
'weight' => 2,
),
1 => array (
'name' => 'Friday',
'weight' => 6,
),
)
*/
You can also use an anonymous function.
usort($items, function($a, $b) {
return $a['name'] > $b['name'];
});
Agree with usort, I also sometimes use array_multisort (http://ca2.php.net/manual/en/function.usort.php) example 3, sorting database results.
You could do something like:
<?php
$days = array(
array('name' => 'Friday', 'weight' => 6),
array('name' => 'Monday', 'weight' => 2),
);
$weight = array();
foreach($days as $k => $d) {
$weight[$k] = $d['weight'];
}
print_r($days);
array_multisort($weight, SORT_ASC, $days);
print_r($days);
?>
Output:
Array
(
[0] => Array
(
[name] => Friday
[weight] => 6
)
[1] => Array
(
[name] => Monday
[weight] => 2
)
)
Array
(
[0] => Array
(
[name] => Monday
[weight] => 2
)
[1] => Array
(
[name] => Friday
[weight] => 6
)
)
If the filed you sort by is string like title name,
array_multisort + Flags for Natural Sorting and CaseInSensitivity are the way to go:
$sort_by_title = array();
foreach($items as $item) {
$sort_by_title [] = $item['title'];
}
array_multisort($sort_by_title , SORT_ASC, SORT_NATURAL | SORT_FLAG_CASE, $items );
NOTE, if the element you are sorting on is a float like .0534 and .0353 (like for a percentage), then you have to multiply both by 1000. not sure why frankly... it appears that usort seems to compare the integer values.
took me awhile to figure that one out...
and 2 tips that may not be immediately obvious:
if your arrays are objects, you can use return $a->weight - $b->weight of course
if you return $b->weight - $a->weight, it will sort desending.
Here's a cool function that might help:
function subval_sort($a,$subkey,$sort) {
foreach($a as $k=>$v) {
$b[$k] = strtolower($v[$subkey]);
}
if($b)
{
$sort($b);
foreach($b as $key=>$val) {
$c[] = $a[$key];
}
return $c;
}
}
Send in the array as $a the key as $subkey and 'asort' or 'sort' for the $sort variable

Categories