Sorting Arrays of objects by predefined map of val

2019-07-28 20:49发布

问题:

I have the following Arrays:

$inputArray = Array(
    [0] => stdClass Object (
        [id] => 8
    )

    [1] => stdClass Object (
        [id] => 7
    )

    [2] => stdClass Object (
        [id] => 5
    )
)

$sortingArray = [5,8,1]

I'm looking an efficient way to sort the input array by the id "map of values".. expected output should be $inputArray reordered so that 3rd item would be first, first item would be 2nd etc.

thanks!

回答1:

I've used a few things here, the main thing is that I use the sorting array as a target for an array_replace() and I use array_column() to index the objects by (this needs PHP 7.0+ to work with objects)...

$input = array_column($inputArray, null, "id");   
$sort = array_fill_keys($sortingArray, null);
$output = array_filter(array_replace($sort, $input));

The array_filter() will remove any elements which aren't in the input array but in the sorting array. The array_fill_keys() is used instead of array_flip() so that I can set a null value, which allows the filter to work.



回答2:

You can use array_flip to make a temporary array. This will flip the array means the values will be the key.

Use usort to sort the array.

$tempArr = array_flip( $sortingArray  );
usort($inputArray, function($a, $b) use ( $tempArr ) {
    $tempA = isset( $tempArr[ $a->id ] ) ? $tempArr[ $a->id ] : 999999; //If id does not exist on $sortingArray. Use 999999 as an index
    $tempB = isset( $tempArr[ $b->id ] ) ? $tempArr[ $b->id ] : 999999;
    return $tempA - $tempB;
});

echo "<pre>";
print_r( $inputArray );
echo "</pre>";

This will result to:

Array
(
    [0] => stdClass Object
        (
            [id] => 5
        )

    [1] => stdClass Object
        (
            [id] => 8
        )

    [2] => stdClass Object
        (
            [id] => 7
        )

)


回答3:

First and foremost, if your input array is the result set of a MySQL query, you should be sorting the data via an ORDER BY clause. FIELD() is an excellent tool for the job, with slight weirdness that you need to reverse your sorting logic and write DESC after the function.

Resource: https://www.electrictoolbox.com/mysql-order-specific-field-values/ (scroll down to "gotcha")

SQL Fiddle Demo: http://sqlfiddle.com/#!9/6b996f/1


Beyond that, I'll offer two refined versions of Nigel's and Eddie's solutions leveraging some php7+ goodness. Which one you choose to implement will have to do with:

  1. Whether you want to allow elements with duplicate id values to "survive" the process
  2. Which one performs more efficiently for your actual project data (the size of your input array and your custom order array)
  3. Personal preference (both snippets can be sensibly written in 3 to 5 lines, so brevity isn't much of a criteria)

Solution #1: usort() and null coalescing operator

PHP Demo

$array = [
    (object)['id' => 8],
    (object)['id' => 7],
    (object)['id' => 5]
];

$order = [5, 8, 1];  // custom sort order

$order = array_flip($order);  // restructure for easy lookup with isset()
$order[''] = max(array_column($array, 'id')) + 1;  // append value higher than max for outlying ids

usort($array, function($a, $b) use ($order) {
    return ($order[$a->id] ?? $order['']) <=> ($order[$b->id] ?? $order['']);
});
var_export($array);

*note that I could have passed $outlier into the custom function scope as a second use argument and replaced all of the $order[''] variables with $outlier, but I opted to append the outlier data into the lookup array.

*I used parentheses in my custom function not only to improve readability, but to guarantee that the evaluation is performed as intended -- I didn't actually bother to check if the evaluation is the same without the parenthetical grouping.

Solution #2: array_replace() a flipped & filtered sort array with a keyed array

(PHP Demo)

$array = [
    (object)['id' => 8],
    (object)['id' => 7],
    (object)['id' => 5]
];

$order = [5, 8, 1];

$order = array_flip($order);                            // flip for future key comparisons
$keyed = array_column($array, null, 'id');              // declare id values as new keys
$filtered_order = array_intersect_key($order, $keyed);  // remove unwanted order keys
$replaced = array_replace($filtered_order, $keyed);     // apply objects to output array
var_export(array_values($replaced));                    // re-index the output (if desired)

*note this won't generate an oversized array of null values and it will not remove values that are "falsey".

*I will again state that the use of array_column() will damage the data if there are duplicate id values in the input array.