treyBake
treyBake

Reputation: 6558

Sort/group array by key

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 upto 1002 as a key. 125 with 12 goes upto 12511 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

Answers (4)

treyBake
treyBake

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

Royal Wares
Royal Wares

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

KhorneHoly
KhorneHoly

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

Nigel Ren
Nigel Ren

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

Related Questions