I need to sort some data that is not coming from a database, but is structured like a sql result set.
In MySQL, I would write a query as follows to sort the data by two columns:
SELECT product, qty FROM stock ORDER BY qty DESC, LENGTH(product) DESC
However, in this case, I need to perform this sorting logic with php. Specifically, the rows are sorted first by descending qty
, and then by the length of name
descending.
Unsorted Input:
[
['name' => 'foo bar', 'qty' => 6],
['name' => 'foo bar bar foo', 'qty' => 10],
['name' => 'b', 'qty' => 5],
['name' => 'foo', 'qty' => 10],
['name' => 'bar', 'qty' => 6],
['name' => 'foo bar bar bar foo', 'qty' => 6],
]
After sorting, I need to restructure the data with the name
values as keys and the qty
values as values of a flat, associative array The finished array would look something like this:
Desired Output:
[
'foo bar bar foo' => 10,
'foo' => 10,
'foo bar bar bar foo' => 6,
'foo bar' => 6,
'bar' => 6,
'b' => 5
]
take a look at php's usort
and uasort
.
You should be able to define a function that can sort it like that
Not sure if it would work easily with that current array but this one it would
$array = array(
array('name' => 'foo bar bar foo', 'qty' => 10 ),
array('name' => 'foo', 'qty' => 6),
array('name' => 'foo bar bar foo', 'qty' => 6 ),
array('name' => 'foo bar', 'qty' => 6 )
);
uasort($array, 'arraySort');
function arraySort($a, $b)
{
if($a['qty'] > $b['qty'])
return 1;
elseif($a['qty'] < $b['qty'])
return -1;
else
if(strlen($a['name']) >= strlen($b['name']))
return 1;
else
return -1;
}
Looking at the answers to this question: PHP array multiple sort - by value then by key? , it seems array_multisort
is the way to go. (I'm not really sure how array_multisort
works, I just kinda hacked this up, and it seems to work).
Try this:
$arr = array(
'foo bar' => 6,
'foo' => 10,
'bar' => 6,
'b' => 5,
'foo bar bar bar foo' => 6,
'foo bar bar foo' => 10
);
array_multisort(array_values($arr), SORT_DESC,
array_map(create_function('$v', 'return strlen($v);'), array_keys($arr)),
SORT_DESC, $arr);
Demo: http://codepad.org/mAttNIV7
UPDATE: Added array_map
to make it sort by the length of the string, before it was just doing:
$str1 > $str2
instead of strlen($str1) > strlen($str2)
.
UPDATE 2: In PHP >= 5.3, you can replace create_function
with a real anonymous function.
array_map(function($v){return strlen($v);}, array_keys($arr))
One limitation while sorting the keys on the basis of length is that: equal length keys are not re-ordered. Say we need to order the keys by length in descending
order.
$arr = array(
"foo 0" => "apple",
"foo 1" => "ball",
"foo 2 foo 0 foo 0" => "cat",
"foo 2 foo 0 foo 1 foo 0" => "dog",
"foo 2 foo 0 foo 1 foo 1" => "elephant",
"foo 2 foo 1 foo 0" => "fish",
"foo 2 foo 1 foo 1" => "giraffe"
);
debug($arr, "before sort");
$arrBad = $arr;
sortKeysDescBAD($arrBad);
debug($arrBad, "after BAD sort");
sortKeysDescGOOD($arr);
debug($arr, "after GOOD sort 2");
function sortKeysDescBAD(&$arrNew) {
$arrKeysLength = array_map('strlen', array_keys($arrNew));
array_multisort($arrKeysLength, SORT_DESC, $arrNew);
//return max($arrKeysLength);
}
function sortKeysDescGOOD(&$arrNew) {
uksort($arrNew, function($a, $b) {
$lenA = strlen($a); $lenB = strlen($b);
if($lenA == $lenB) {
// If equal length, sort again by descending
$arrOrig = array($a, $b);
$arrSort = $arrOrig;
rsort($arrSort);
if($arrOrig[0] !== $arrSort[0]) return 1;
} else {
// If not equal length, simple
return $lenB - $lenA;
}
});
}
function debug($arr, $title = "") {
if($title !== "") echo "<br/><strong>{$title}</strong><br/>";
echo "<pre>"; print_r($arr); echo "</pre><hr/>";
}
Output will be:
before sort
Array
(
[foo 0] => apple
[foo 1] => ball
[foo 2 foo 0 foo 0] => cat
[foo 2 foo 0 foo 1 foo 0] => dog
[foo 2 foo 0 foo 1 foo 1] => elephant
[foo 2 foo 1 foo 0] => fish
[foo 2 foo 1 foo 1] => giraffe
)
after BAD sort
Array
(
[foo 2 foo 0 foo 1 foo 0] => dog
[foo 2 foo 0 foo 1 foo 1] => elephant
[foo 2 foo 0 foo 0] => cat
[foo 2 foo 1 foo 0] => fish
[foo 2 foo 1 foo 1] => giraffe
[foo 0] => apple
[foo 1] => ball
)
after GOOD sort
Array
(
[foo 2 foo 0 foo 1 foo 1] => elephant
[foo 2 foo 0 foo 1 foo 0] => dog
[foo 2 foo 1 foo 1] => giraffe
[foo 2 foo 1 foo 0] => fish
[foo 2 foo 0 foo 0] => cat
[foo 1] => ball
[foo 0] => apple
)
Notice the order of elephant
and dog
for example (or others) in two sorting methods. The second method looks better. There may be easier ways to solve this but hope this helps someone...
Starting from an input array that resembles a sql result set, you can cleanly use usort()
containing a 3-way comparison then a conditional secondary 3-way comparison. When done, you can isolate the qty
column of data as the values and (assuming all of the name
values are unique) use the name
column as the assigned keys using array_column()
. When comparing, write the $b
data on the left of the operator and the $a
on the right to achieve a descending sort order.
Code: ( Demo )
$array = [
['name' => 'foo bar bar foo', 'qty' => 6],
['name' => 'bah', 'qty' => 5],
['name' => 'foo foo bar foo', 'qty' => 10],
['name' => 'foo', 'qty' => 6],
['name' => 'foo bar', 'qty' => 6],
['name' => 'bar', 'qty' => 11],
];
usort($array, function($a, $b) {
return $b['qty'] <=> $a['qty'] ?: strlen($b['name']) <=> strlen($a['name']);
});
var_export(array_column($array, 'qty', 'name'));
Output:
array (
'bar' => 11,
'foo foo bar foo' => 10,
'foo bar bar foo' => 6,
'foo bar' => 6,
'foo' => 6,
'bah' => 5,
)
The advantage of using comparison1 ?: comparison2
is that the function calls in comparison2
are not executed unless a tiebreak is necessary -- this improves efficiency. Alternatively, using array_multisort()
will unconditionally call strlen()
on all qty
values -- even if they are not needed for sorting.
It is perfectly valid to execute the sort with a single spaceship operator, but the following technique will make two function calls every time a comparison is made. This will be less efficient than my above snippet, so I do not recommend the following snippet .
usort($array, function($a, $b) {
return [$b['qty'], strlen($b['name']] <=> [$a['qty'], strlen($a['name'])];
});
ps Of course this can be done with array_multisort()
as well, I just find the syntax to be less concise.
array_multisort(
array_column($array, 'qty'),
SORT_DESC,
array_map(
function($row) {
return strlen($row['name']);
},
$array
),
SORT_DESC,
$array
);
var_export(array_column($array, 'qty', 'name'));
use the sort function in php. make sure you use the TRUE as the second parameter to preserve the keys.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.