0

I need a method that sorts an array of items by priority.

Here is what I have working so far:

function arraySortPriority(array &$array, $offset, array $priorities)
{
    uasort($array, function ($a, $b) use ($offset, $priorities) {
        if (!isset($a[$offset])) {
            $a[$offset] = null;
        }
        if (!isset($b[$offset])) {
            $b[$offset] = null;
        }
        if ($a[$offset] == $b[$offset]) {
            return 0;
        }
        $aPriority = isset($priorities[$a[$offset]])
            ? $priorities[$a[$offset]]
            : null;
        $bPriority = isset($priorities[$b[$offset]])
            ? $priorities[$b[$offset]]
            : null;
        return $aPriority > $bPriority ? -1 : 1;
    });
}

// an array to sort
$array = [
    ['type' => 'A'],
    ['type' => 'A'],
    ['type' => 'B'],
    ['type' => 'B'],
    ['type' => 'C'],
    ['type' => 'C'],
    ['type' => 'D'],
    ['type' => 'D'],
    ['type' => 'E'],
    ['type' => 'E'],
    ['type' => 'F'],
    ['type' => 'F'],
    ['type' => 'G'],
    ['type' => 'G'],
    ['type' => 'H'],
    ['type' => 'H'],
    ['type' => 'Foo'],
    ['type' => 'Foo'],
    ['type' => 'Bar'],
    ['type' => 'Bar'],
    [0 => 'no type should be last'],
    [0 => 'no type should be last'],
];
// shuffle the array
shuffle($array);
// set priorities
$priorities = [
    'A' => 8,
    'B' => 7,
    'C' => 6,
    'D' => 5,
    'E' => 4,
    'F' => 3,
    'G' => 2,
    'H' => 1,
];
// call
arraySortPriority($array, 'type', $priorities);
// test output
foreach ($array as $item) {
    if (isset($item['type'])) {
        echo "{$item['type']}\r\n";
    } else {
        $values = array_values($item);
        echo reset($values) . PHP_EOL;
    }
}

Expected:

A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
Foo
Foo
Bar
Bar
no type should be last
no type should be last

Actual:

A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
no type should be last   <-- should be at bottom
no type should be last   <-- should be at bottom
Bar
Bar
Foo
Foo

The problem is that the items that have not been given an $offset should be always sorted to the bottom.

This means that no type should be last should always be sorted lower than Foo or Bar.

How can I do this?

0

4 Answers 4

2
<?php 

// an array to sort
$array = [
    ['type' => 'A'],
    ['type' => 'A'],
    ['type' => 'B'],
    ['type' => 'B'],
    ['type' => 'C'],
    ['type' => 'C'],
    ['type' => 'D'],
    ['type' => 'D'],
    ['type' => 'E'],
    ['type' => 'E'],
    ['type' => 'F'],
    ['type' => 'F'],
    ['type' => 'G'],
    ['type' => 'G'],
    ['type' => 'H'],
    ['type' => 'H'],
    ['type' => 'Foo'],
    ['type' => 'Foo'],
    ['type' => 'Bar'],
    ['type' => 'Bar'],
    [0 => 'no type should be last'],
    [0 => 'no type should be last'],
];
// shuffle the array
shuffle($array);
// set priorities
$priorities = [
    'A' => 8,
    'B' => 7,
    'C' => 6,
    'D' => 5,
    'E' => 4,
    'F' => 3,
    'G' => 2,
    'H' => 1,
];

uasort($array,function($a,$b) use ($priorities){
        if(!isset($a['type'])){
            if(!isset($b['type'])) return -1;
            return 1;
        }else if(!isset($b['type'])){
            return -1;
        }

        if(isset($priorities[$a['type']])){
            if(!isset($priorities[$b['type']])) return -1;

            if($priorities[$a['type']] > $priorities[$b['type']]) return -1;
            else if($priorities[$a['type']] < $priorities[$b['type']]) return 1;

        }else if(isset($priorities[$b['type']])){
            return 1;           
        }

        return 0;
});

echo "<pre>";
print_r($array);

OUTPUT

Array
(
    [21] => Array
        (
            [type] => A
        )

    [8] => Array
        (
            [type] => A
        )

    [18] => Array
        (
            [type] => B
        )

    [20] => Array
        (
            [type] => B
        )

    [6] => Array
        (
            [type] => C
        )

    [16] => Array
        (
            [type] => C
        )

    [11] => Array
        (
            [type] => D
        )

    [1] => Array
        (
            [type] => D
        )

    [7] => Array
        (
            [type] => E
        )

    [17] => Array
        (
            [type] => E
        )

    [13] => Array
        (
            [type] => F
        )

    [5] => Array
        (
            [type] => F
        )

    [15] => Array
        (
            [type] => G
        )

    [9] => Array
        (
            [type] => G
        )

    [4] => Array
        (
            [type] => H
        )

    [0] => Array
        (
            [type] => H
        )

    [19] => Array
        (
            [type] => Bar
        )

    [10] => Array
        (
            [type] => Foo
        )

    [14] => Array
        (
            [type] => Bar
        )

    [12] => Array
        (
            [type] => Foo
        )

    [2] => Array
        (
            [0] => no type should be last
        )

    [3] => Array
        (
            [0] => no type should be last
        )

)
Sign up to request clarification or add additional context in comments.

1 Comment

I decided to use your edited solution. See stackoverflow.com/a/50513717/3411766.
2

You have 3 logical rules for your sorting. The spaceship operators affords a very clear and concise syntax without the mess of condition blocks.

Just write corresponding elements on both sides of the 3-way comparison operator (<=>) and the rules will be honored from left to right. When $b is the first/top/left array, then DESC order is applied; conversely, $a in the first/top/left array equates to ASC sorting.

  1. sort by the existence of the targeted column DESC
  2. then to break #1 ties, sort by the priority value DESC
  3. then to break #2 ties, sort by the target column value ASC

Code: (Demo)

function arraySortPriority(array &$array, $column, array $priorities):void
{
    usort($array, function($a, $b) use ($column, $priorities) {
        $aFocus = $a[$column] ?? null;
        $bFocus = $b[$column] ?? null;
        return [array_key_exists($column, $b), $priorities[$bFocus] ?? 0, $aFocus]
               <=>
               [array_key_exists($column, $a), $priorities[$aFocus] ?? 0, $bFocus];
    });
}

arraySortPriority($array, 'type', $priorities);
var_export($array)

If your $array will never contain null values, then this snippet can be further refined to contain no function calls inside of the usort() scope.

Comments

1

I decided to use @vivek_23 (edited|fixed) solution.

I edited out unnecessary else and changed the return value to zero if f.e. both offsets not set.

If a and b are missing the offset or have no priority the function should return zero imo.

Working code:

uasort($array, function ($a, $b) use ($offset, $priorities) {
    if (!isset($a[$offset])) {
        return !isset($b[$offset])
            ? 0
            : 1; // down
    } elseif (!isset($b[$offset])) {
        return -1; // up
    }
    if (isset($priorities[$a[$offset]])) {
        if (!isset($priorities[$b[$offset]])) {
            return -1; // up
        }
        return $priorities[$a[$offset]] > $priorities[$b[$offset]]
            ? -1 // up
            : 1; // down
    }
    return isset($priorities[$b[$offset]])
        ? 1 // down
        : 0;
});

This allows me to use negative priority, float priority and i do not override priorities (see -99999999 @Eddie solution).

Tested

// expect A, A, B, B, C, C, ... "no type ..." at bottom
$priorities = [
    'A' => 8,
    'B' => 7,
    'C' => 6,
    'D' => 5,
    'E' => 4,
    'F' => 3,
    'G' => 2,
    'H' => 1,
];

// expect "no type ..." at bottom, ..., C, C, B, B, A, A
$priorities = [
    'A' => -8,
    'B' => -7,
    'C' => -6,
    'D' => -5,
    'E' => -4,
    'F' => -3,
    'G' => -2,
    'H' => -1,
];

// expect B, B, A, A, C, C, ... "no type ..." at bottom
$priorities = [
    'A' => 6.5,
    'B' => 7,
    'C' => 6,
    'D' => 5,
    'E' => 4,
    'F' => 3,
    'G' => 2,
    'H' => 1,
];

// expect "no type ..." at bottom, ..., C, C, A, A, B, B
$priorities = [
    'A' => -6.5,
    'B' => -7,
    'C' => -6,
    'D' => -5,
    'E' => -4,
    'F' => -3,
    'G' => -2,
    'H' => -1,
];

Thanks @vivek_23 :)

Comments

0

You are not handling the case where both priorities are the same.

Solution 1

Simply add the following to your function before the ternary:

if ($aPriority == $bPriority)
    return 0;

So it should look like this:

uasort($array, function ($a, $b) use ($offset, $priorities) {
    if (!isset($a[$offset])) {
        $a[$offset] = null;
    }
    if (!isset($b[$offset])) {
        $b[$offset] = null;
    }
    if ($a[$offset] == $b[$offset]) {
        return 0;
    }

    $aPriority = isset($priorities[$a[$offset]])
        ? $priorities[$a[$offset]]
        : null;
    $bPriority = isset($priorities[$b[$offset]])
        ? $priorities[$b[$offset]]
        : null;

    if ($aPriority == $bPriority)
        return 0;

    return $aPriority > $bPriority ? -1 : 1;
});

Otherwise, you simply assume that $aPriority is less than $bPriority if it's not greater but it can be equal.

Solution 2

Another way to achieve it without an extra if case is to set the priority to 0 instead of null if it's not set:

$aPriority = isset($priorities[$a[$offset]])
    ? $priorities[$a[$offset]]
    : 0;
$bPriority = isset($priorities[$b[$offset]])
    ? $priorities[$b[$offset]]
    : 0;

Then return the subtraction:

return $bPriority - $aPriority;

So it would look like this:

uasort($array, function ($a, $b) use ($offset, $priorities) {
    if (!isset($a[$offset])) {
        $a[$offset] = null;
    }
    if (!isset($b[$offset])) {
        $b[$offset] = null;
    }
    if ($a[$offset] == $b[$offset]) {
        return 0;
    }

    $aPriority = isset($priorities[$a[$offset]])
        ? $priorities[$a[$offset]]
        : 0;
    $bPriority = isset($priorities[$b[$offset]])
        ? $priorities[$b[$offset]]
        : 0;

    return $bPriority - $aPriority;
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.