1

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
]
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
Drahcir
  • 11,772
  • 24
  • 86
  • 128

5 Answers5

3

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;
}
Ascherer
  • 8,223
  • 3
  • 42
  • 60
3

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))

Demo 2: http://codepad.viper-7.com/6qrFwj

Community
  • 1
  • 1
gen_Eric
  • 223,194
  • 41
  • 299
  • 337
  • hmm, yeah that way works better than mine with the original array, i originally avoided this cause i didnt think it would sort the strings by length, but it looks like it is lol – Ascherer Dec 21 '11 at 21:24
  • @Ascherer: It was originally sorting the strings by doing `$a > $b`, it wasn't sorting by length, it just happened to be in that order. I added an `array_map` to make it sort by legnth. – gen_Eric Dec 21 '11 at 21:31
  • @Ascherer: `usort` can't sort on both the keys and the values, though. – gen_Eric Dec 22 '11 at 14:39
0

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...

sagunms
  • 8,030
  • 5
  • 41
  • 43
0

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'])];
});

p.s. 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'));
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
-2

use the sort function in php. make sure you use the TRUE as the second parameter to preserve the keys.

john smith
  • 733
  • 3
  • 8
  • 18
  • [`sort`](http://php.net/sort) doesn't have a "preserve keys" parameter. If you want to do that, you'd use [`asort`](http://php.net/asort). – gen_Eric Dec 21 '11 at 21:03