Reputation: 6558
TL;DR
Sort/group array by key without adding another level to the array (data parsed by jQuery plugin)?
Details
I am building an array to return to some <select>
DOM element.
It takes in CC (engine size stuff) as a parameter and uses that as a key, the problem lies with sorting the array after.
Let's say, user selects this range of CC's:
50, 100, 125
50 has 32 options available
100 has 3 options available
125 has 12 options available
My current code loops through the CC's, executes the SQL to get the options and using a loop counter creates the key like this:
$options[$cc. $id] = $someValue;
This works as you'd expect, however my output is showing results not in exactly the order I need (CC ASC
- so all 50s should show first, together).
The problem is that
50 with 32 goes upto
5031
as a key. 100 with 3 goes upto1002
as a key. 125 with 12 goes upto12511
as a key.
By now hopefully you can clearly see the issue. 5031 is greater than 1002. So options for 50cc with a loop counter passed 9 is greater than 100cc options.
(just for clarity, example output is):
50cc Option 1
50cc Option 2
50cc Option 3
50cc Option 4
50cc Option 5
100cc Option 1
100cc Option 2
100cc Option 3
50cc Option 6
50cc Option 7
Maybe the initial problem is how I'm creating the keys, but I've tried to use ksort
with a few different flags to try and achieve my goal but none of the flags seem to target what I'm after:
SORT_REGULAR - compare items normally (don't change types) SORT_NUMERIC - compare items numerically SORT_STRING - compare items as strings SORT_LOCALE_STRING - compare items as strings, based on the current locale. It uses the locale, which can be changed using setlocale() SORT_NATURAL - compare items as strings using "natural ordering" like natsort() SORT_FLAG_CASE - can be combined (bitwise OR) with SORT_STRING or SORT_NATURAL to sort strings case-insensitively
How do I sort/group my keys without adding another level to my array (the data is parsed by a jQuery plugin that needs the data in a certain format)?
EDIT: Full Script
<?php
if (strpos(PHP_OS, 'Linux') > -1) {
require_once $_SERVER['DOCUMENT_ROOT']. '/app/connect.php';
} else {
require_once getcwd(). '\\..\\..\\..\\..\\app\\connect.php';
}
$make = $_POST['make'];
$cc = $_POST['cc'];
$sql = 'SELECT * FROM `table`
WHERE `UKM_CCM` = :cc
AND `UKM_Make` = :make
ORDER BY `UKM_Model`, `UKM_StreetName`, `Year` ASC;';
$options = array();
foreach ($cc as $k => $value)
{
$res = $handler->prepare($sql);
$res->execute(array(':cc' => $value, ':make' => $make));
$data = $res->fetchAll(PDO::FETCH_ASSOC);
$i = 0;
if (count($data) > 0) {
foreach ($data as $result)
{
$arrayKey = sprintf('%03d%02d', $cc, $i);
$epid = $result['ePID'];
$make = $result['UKM_Make'];
$model = $result['UKM_Model'];
$cc = $result['UKM_CCM'];
$year = $result['Year'];
$sub = $result['UKM_Submodel'];
$street = $result['UKM_StreetName'];
$options[$arrayKey]['name'] = $make. ' ' .$model. ' ' .$cc. ' ' .$year. ' ' .$sub. ' ' .$street;
$options[$arrayKey]['value'] = $epid;
$options[$arrayKey]['checked'] = false;
$options[$arrayKey]['attributes']['data-epid'] = $epid;
$options[$arrayKey]['attributes']['data-make'] = $make;
$options[$arrayKey]['attributes']['data-model'] = $model;
$options[$arrayKey]['attributes']['data-cc'] = $cc;
$options[$arrayKey]['attributes']['data-year'] = $year;
$options[$arrayKey]['attributes']['data-sub'] = $sub;
$options[$arrayKey]['attributes']['data-street'] = $street;
$i++;
}
}
}
ksort($options, SORT_STRING);
echo json_encode($options);
Upvotes: 4
Views: 256
Reputation: 6558
I'm not sure this could be an exact answer to my own question as it handles the issue but in a different way. Essentially, I've changed my SQL to this:
$sql = 'SELECT * FROM `ebay_mml`
WHERE `UKM_CCM` IN ('. $where .')
AND `UKM_Make` = :make
ORDER BY CAST(SUBSTR(`UKM_CCM`, INSTR(`UKM_CCM`, " ") + 1) AS UNSIGNED),
`UKM_Model`,
`UKM_StreetName`,
`Year`
ASC;';
$where
is a variable generated from a foreach loop:
foreach ($cc as $k => $v)
{
$where .= ':'. $k .($k != end(array_keys($cc)) ? ', ' : '');
$whereData[':'. $k] = $v;
}
This returns all my data at once, so all I need to do now is loop through the results and count the iterations as I go to build the key:
$i = 0;
foreach ($data as $result)
{
# my sexy code
$i++;
}
Now my results are as I want them.
Disclaimer: As this does resolve the issue at hand, it kinda veers away from the original question posed as it's more of a MySQL solution as opposed to sorting/grouping the array by it's key value. Let me know if this answer is ok (if so, will remove the disclaimer segment) or not.
Thanks for your help all :)
Upvotes: 0
Reputation: 1272
Make your 'faux delimiter' an uncommon pattern like "929292" in this example. Then you can use uksort to go through just your keys. replace "929292" with "." so you end up with something like "100.3", "125.12" and "150.32".
Now you're no longer limited to trying to work numerically and with patterns, you can use a good old explode and compare manually.
This solution doesn't care what's nested in your arrays, it's only concerned with the keys for sorting.
$options = [
'1009292923' => '100cc',
'12592929212' => '150cc',
'5092929232' => '50cc'
];
$sorted = uksort($options, function($a, $b){
$parts = explode('.',str_replace('929292', '.', $a));
$acc = $parts[0];
$parts = explode('.',str_replace('929292', '.', $b));
$bcc = $parts[0];
if($acc == $bcc) { return 0; }
if($acc > $bcc) { return 1; }
if($acc < $bcc) { return -1; }
});
var_dump($options);
Edit: str_replace is kind of pointless here, you could just run explode directly on the key using "929292" as your delimeter.
Upvotes: 0
Reputation: 4766
If you can add the key additional into your array, you could create a usort()
that will sort your array as you need it:
$arrSort = [50, 100, 125];
$arrData = [
501 => [
'foo',
'bar',
'arrayKey' => 501
],
504 => [
'foo',
'bar',
'arrayKey' => 504
],
1002 => [
'foo',
'bar',
'arrayKey' => 1002
],
10045 => [
'foo',
'bar',
'arrayKey' => 10045
],
1251 => [
'foo',
'bar',
'arrayKey' => 1251
],
5045 => [
'foo',
'bar',
'arrayKey' => 5045
]
];
usort($arrData, function($a, $b) use ($arrSort)
{
$posA = array_search(substr($a['arrayKey'], 0, 2), $arrSort);
if ($posA === false) {
$posA = array_search(substr($a['arrayKey'], 0, 3), $arrSort);
}
$posB = array_search(substr($b['arrayKey'], 0, 2), $arrSort);
if ($posB === false) {
$posB = array_search(substr($b['arrayKey'], 0, 3), $arrSort);
}
return $posA - $posB;
});
The return of this function in this example would be:
array:6 [▼ 0 => array:3 [▼ 0 => "foo" 1 => "bar" "arrayKey" => 501 ] 1 => array:3 [▼ 0 => "foo" 1 => "bar" "arrayKey" => 504 ] 2 => array:3 [▼ 0 => "foo" 1 => "bar" "arrayKey" => 5045 ] 3 => array:3 [▼ 0 => "foo" 1 => "bar" "arrayKey" => 1002 ] 4 => array:3 [▼ 0 => "foo" 1 => "bar" "arrayKey" => 10045 ] 5 => array:3 [▼ 0 => "foo" 1 => "bar" "arrayKey" => 1251 ] ]
Upvotes: 0
Reputation: 57131
You could format the key to have 3 digits for the cc and 2 for the option...
$options[sprintf('%03d%02d', $cc, $id)] = $someValue;
which should give you keys 05031
and 10002
.
Then use SORT_STRING
to force it to sort them as strings (although they would sort as numbers as well)
Upvotes: 2