简体   繁体   中英

Sort multidimensional array on column values DESC, then by length of another column DESC

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

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

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM