Sorting Array of Std Class Objects by Custom Order (PHP)

341 Views Asked by At

I have an array of StdClass Objects that contain different Car Types (ex. Compact Car, Mid-Size Car, etc.). The array should be sorted in the following order:

  • Economy Car
  • Compact Car
  • Mid-Size Car
  • Standard-Size Car
  • Full-Size Car
  • Premium Car
  • Luxury Car
  • Standard-Size Convertible
  • Mid-Size SUV
  • Standard-Size SUV
  • Full-Size SUV
  • Minivan
  • The remaining cars in the array should then be sorted by lowest to highest price (Example: After Minivan, display Specialty Vehicle, Compact SUV, Full-Size Hybrid, Sports, and so on...).

I'm having a problem with sorting the remaining cars by price. I tried sorting by price first, and then the ordered list in the second usort but it's not working as expected. Any help would be appreciated. I posted a similar question earlier, but figured I'd repost a more organized version of the requirements.

Input Array:

array (
  0 => 
  stdClass::__set_state(array(
     'description' => 'Mid-Size SUV',
     'codes' => 
    array (
      0 => 'IFAR',
    ),
     'max_people' => '5',
     'min_people' => '5',
     'max_bags' => '4',
     'min_bags' => '1',
     'price' => '30.31',
  )),
  1 => 
  stdClass::__set_state(array(
     'description' => 'Standard-Size SUV',
     'codes' => 
    array (
      0 => 'SFAR',
      1 => 'RFAR',
    ),
     'max_people' => '7',
     'min_people' => '5',
     'max_bags' => '4',
     'min_bags' => '1',
     'price' => '35.53',
  )),
  2 => 
  stdClass::__set_state(array(
     'description' => 'Economy Car',
     'codes' => 
    array (
      0 => 'ECAR',
      1 => 'EDAR',
    ),
     'max_people' => '5',
     'min_people' => '4',
     'max_bags' => '2',
     'min_bags' => '1',
     'price' => '37.21',
  )),
  3 => 
  stdClass::__set_state(array(
     'description' => 'Specialty Vehicle',
     'codes' => 
    array (
      0 => 'XXAR',
    ),
     'max_people' => false,
     'min_people' => false,
     'max_bags' => '',
     'min_bags' => '',
     'price' => '36.72',
  )),
  4 => 
  stdClass::__set_state(array(
     'description' => 'Compact Car',
     'codes' => 
    array (
      0 => 'CCAR',
      1 => 'CDAR',
    ),
     'max_people' => '5',
     'min_people' => '4',
     'max_bags' => '3',
     'min_bags' => '1',
     'price' => '37.21',
  )),
  5 => 
  stdClass::__set_state(array(
     'description' => 'Mid-Size Car',
     'codes' => 
    array (
      0 => 'ICAR',
      1 => 'IDAR',
    ),
     'max_people' => '5',
     'min_people' => '5',
     'max_bags' => '4',
     'min_bags' => '1',
     'price' => '39.46',
  )),
  6 => 
  stdClass::__set_state(array(
     'description' => 'Standard-Size Car',
     'codes' => 
    array (
      0 => 'SCAR',
      1 => 'SDAR',
    ),
     'max_people' => '5',
     'min_people' => '5',
     'max_bags' => '4',
     'min_bags' => '1',
     'price' => '41.77',
  )),
  7 => 
  stdClass::__set_state(array(
     'description' => 'Minivan',
     'codes' => 
    array (
      0 => 'MVAR',
    ),
     'max_people' => '7',
     'min_people' => '7',
     'max_bags' => '5',
     'min_bags' => '2',
     'price' => '43.18',
  )),
  8 => 
  stdClass::__set_state(array(
     'description' => 'Full-Size Car',
     'codes' => 
    array (
      0 => 'FCAR',
      1 => 'FDAR',
    ),
     'max_people' => '5',
     'min_people' => '5',
     'max_bags' => '4',
     'min_bags' => '1',
     'price' => '43.50',
  )),
  9 => 
  stdClass::__set_state(array(
     'description' => 'Compact SUV',
     'codes' => 
    array (
      0 => 'CFAR',
    ),
     'max_people' => '5',
     'min_people' => '5',
     'max_bags' => '3',
     'min_bags' => '3',
     'price' => '46.42',
  )),
  10 => 
  stdClass::__set_state(array(
     'description' => 'Full-Size Hybrid',
     'codes' => 
    array (
      0 => 'FCAH',
    ),
     'max_people' => false,
     'min_people' => false,
     'max_bags' => '',
     'min_bags' => '',
     'price' => '48.00',
  )),
  11 => 
  stdClass::__set_state(array(
     'description' => 'Standard-Size Convertible',
     'codes' => 
    array (
      0 => 'STAR',
    ),
     'max_people' => '4',
     'min_people' => '4',
     'max_bags' => '4',
     'min_bags' => '1',
     'price' => '48.39',
  )),
  12 => 
  stdClass::__set_state(array(
     'description' => 'Premium Car',
     'codes' => 
    array (
      0 => 'PCAR',
    ),
     'max_people' => '5',
     'min_people' => '5',
     'max_bags' => '4',
     'min_bags' => '2',
     'price' => '48.72',
  )),
  13 => 
  stdClass::__set_state(array(
     'description' => 'Sports Car',
     'codes' => 
    array (
      0 => 'XSAR',
      1 => 'SSAR',
    ),
     'max_people' => '5',
     'min_people' => '4',
     'max_bags' => '3',
     'min_bags' => '1',
     'price' => '48.72',
  )),
  14 => 
  stdClass::__set_state(array(
     'description' => 'Luxury Car',
     'codes' => 
    array (
      0 => 'LCAR',
      1 => 'LDAR',
    ),
     'max_people' => '5',
     'min_people' => '5',
     'max_bags' => '5',
     'min_bags' => '1',
     'price' => '52.16',
  )),
  15 => 
  stdClass::__set_state(array(
     'description' => 'Full-Size SUV',
     'codes' => 
    array (
      0 => 'FFAR',
    ),
     'max_people' => '8',
     'min_people' => '7',
     'max_bags' => '4',
     'min_bags' => '2',
     'price' => '52.92',
  )),
  16 => 
  stdClass::__set_state(array(
     'description' => 'Compact Convertible',
     'codes' => 
    array (
      0 => 'CTAR',
    ),
     'max_people' => '4',
     'min_people' => '4',
     'max_bags' => '1',
     'min_bags' => '1',
     'price' => '91.17',
  )),
  17 => 
  stdClass::__set_state(array(
     'description' => 'Premium SUV',
     'codes' => 
    array (
      0 => 'PFAR',
      1 => 'UFAR',
    ),
     'max_people' => '7',
     'min_people' => '5',
     'max_bags' => '5',
     'min_bags' => '1',
     'price' => '92.64',
  )),
  18 => 
  stdClass::__set_state(array(
     'description' => 'Specialty Car',
     'codes' => 
    array (
      0 => 'XCAR',
    ),
     'max_people' => '5',
     'min_people' => '5',
     'max_bags' => '3',
     'min_bags' => '3',
     'price' => '94.42',
  )),
  19 => 
  stdClass::__set_state(array(
     'description' => 'Mid-Size Convertible',
     'codes' => 
    array (
      0 => 'ITAR',
    ),
     'max_people' => '4',
     'min_people' => '4',
     'max_bags' => '2',
     'min_bags' => '2',
     'price' => '97.98',
  )),
  20 => 
  stdClass::__set_state(array(
     'description' => 'Full-Size Van',
     'codes' => 
    array (
      0 => 'FVAR',
    ),
     'max_people' => '15',
     'min_people' => '12',
     'max_bags' => '5',
     'min_bags' => '1',
     'price' => '90.38',
  )),
  21 => 
  stdClass::__set_state(array(
     'description' => 'Luxury SUV',
     'codes' => 
    array (
      0 => 'LFAR',
    ),
     'max_people' => '5',
     'min_people' => '5',
     'max_bags' => '3',
     'min_bags' => '3',
     'price' => '113.66',
  )),
  22 => 
  stdClass::__set_state(array(
     'description' => 'Premium Convertible',
     'codes' => 
    array (
      0 => 'PTAR',
    ),
     'max_people' => '4',
     'min_people' => '4',
     'max_bags' => '1',
     'min_bags' => '1',
     'price' => '121.15',
  )),
  23 => 
  stdClass::__set_state(array(
     'description' => 'Luxury Convertible',
     'codes' => 
    array (
      0 => 'LTAR',
    ),
     'max_people' => '4',
     'min_people' => '4',
     'max_bags' => '1',
     'min_bags' => '1',
     'price' => '129.01',
  )),
  24 => 
  stdClass::__set_state(array(
     'description' => 'Premium Van',
     'codes' => 
    array (
      0 => 'PVAR',
    ),
     'max_people' => '15',
     'min_people' => '15',
     'max_bags' => '1',
     'min_bags' => '1',
     'price' => '159.49',
  )),
  25 => 
  stdClass::__set_state(array(
     'description' => 'Specialty Convertible',
     'codes' => 
    array (
      0 => 'XTAR',
    ),
     'max_people' => '4',
     'min_people' => '4',
     'max_bags' => '2',
     'min_bags' => '2',
     'price' => '307.64',
  )),
)

Code:

    $order = [
        'Economy Car' => 1,
        'Compact Car' => 2,
        'Mid-Size Car' => 3,
        'Standard-Size Car' => 4,
        'Full-Size Car' => 5,
        'Premium Car' => 6,
        'Luxury Car' => 7,
        'Standard-Size Convertible' => 8,
        'Mid-Size SUV' => 9,
        'Standard SUV' => 10,
        'Full-Size SUV' => 11,
        'Minivan' => 12
    ];

    usort($car_types,function($a, $b) {
        return $a->price == $b->price ? 0 : $a->price > $b->price;
    });

    usort($car_types, function($a, $b) use ($order) {
        $a_set = intval(isset($order[$a->description]));
        $b_set = intval(isset($order[$b->description]));

        return $a_set === $b_set && $a_set === 1
            ? $order[$a->description] - $order[$b->description]
            : $b_set - $a_set;
    });

New Output:

Array
(
[0] => stdClass Object
    (
        [description] => Economy Car
        [codes] => Array
            (
                [0] => ECAR
                [1] => EDAR
            )

        [max_people] => 5
        [min_people] => 4
        [max_bags] => 2
        [min_bags] => 1
        [price] => 37.21
    )

[1] => stdClass Object
    (
        [description] => Compact Car
        [codes] => Array
            (
                [0] => CCAR
                [1] => CDAR
            )

        [max_people] => 5
        [min_people] => 4
        [max_bags] => 3
        [min_bags] => 1
        [price] => 37.21
    )

[2] => stdClass Object
    (
        [description] => Mid-Size Car
        [codes] => Array
            (
                [0] => ICAR
                [1] => IDAR
            )

        [max_people] => 5
        [min_people] => 5
        [max_bags] => 4
        [min_bags] => 1
        [price] => 39.46
    )

[3] => stdClass Object
    (
        [description] => Standard-Size Car
        [codes] => Array
            (
                [0] => SCAR
                [1] => SDAR
            )

        [max_people] => 5
        [min_people] => 5
        [max_bags] => 4
        [min_bags] => 1
        [price] => 41.77
    )

[4] => stdClass Object
    (
        [description] => Full-Size Car
        [codes] => Array
            (
                [0] => FCAR
                [1] => FDAR
            )

        [max_people] => 5
        [min_people] => 5
        [max_bags] => 4
        [min_bags] => 1
        [price] => 43.50
    )

[5] => stdClass Object
    (
        [description] => Premium Car
        [codes] => Array
            (
                [0] => PCAR
            )

        [max_people] => 5
        [min_people] => 5
        [max_bags] => 4
        [min_bags] => 2
        [price] => 48.72
    )

[6] => stdClass Object
    (
        [description] => Luxury Car
        [codes] => Array
            (
                [0] => LCAR
                [1] => LDAR
            )

        [max_people] => 5
        [min_people] => 5
        [max_bags] => 5
        [min_bags] => 1
        [price] => 52.16
    )

[7] => stdClass Object
    (
        [description] => Standard-Size Convertible
        [codes] => Array
            (
                [0] => STAR
            )

        [max_people] => 4
        [min_people] => 4
        [max_bags] => 4
        [min_bags] => 1
        [price] => 48.39
    )

[8] => stdClass Object
    (
        [description] => Mid-Size SUV
        [codes] => Array
            (
                [0] => IFAR
            )

        [max_people] => 5
        [min_people] => 5
        [max_bags] => 4
        [min_bags] => 1
        [price] => 30.31
    )

[9] => stdClass Object
    (
        [description] => Standard-Size SUV
        [codes] => Array
            (
                [0] => SFAR
                [1] => RFAR
            )

        [max_people] => 7
        [min_people] => 5
        [max_bags] => 4
        [min_bags] => 1
        [price] => 35.53
    )

[10] => stdClass Object
    (
        [description] => Full-Size SUV
        [codes] => Array
            (
                [0] => FFAR
            )

        [max_people] => 8
        [min_people] => 7
        [max_bags] => 4
        [min_bags] => 2
        [price] => 52.92
    )

[11] => stdClass Object
    (
        [description] => Minivan
        [codes] => Array
            (
                [0] => MVAR
            )

        [max_people] => 7
        [min_people] => 7
        [max_bags] => 5
        [min_bags] => 2
        [price] => 43.18
    )

[12] => stdClass Object
    (
        [description] => Specialty Vehicle
        [codes] => Array
            (
                [0] => XXAR
            )

        [max_people] => 
        [min_people] => 
        [max_bags] => 
        [min_bags] => 
        [price] => 36.72
    )

[13] => stdClass Object
    (
        [description] => Compact SUV
        [codes] => Array
            (
                [0] => CFAR
            )

        [max_people] => 5
        [min_people] => 5
        [max_bags] => 3
        [min_bags] => 3
        [price] => 46.42
    )

[14] => stdClass Object
    (
        [description] => Sports Car
        [codes] => Array
            (
                [0] => XSAR
                [1] => SSAR
            )

        [max_people] => 5
        [min_people] => 4
        [max_bags] => 3
        [min_bags] => 1
        [price] => 48.72
    )

[15] => stdClass Object
    (
        [description] => Full-Size Hybrid
        [codes] => Array
            (
                [0] => FCAH
            )

        [max_people] => 
        [min_people] => 
        [max_bags] => 
        [min_bags] => 
        [price] => 48.00
    )

[16] => stdClass Object
    (
        [description] => Full-Size Van
        [codes] => Array
            (
                [0] => FVAR
            )

        [max_people] => 15
        [min_people] => 12
        [max_bags] => 5
        [min_bags] => 1
        [price] => 90.38
    )

[17] => stdClass Object
    (
        [description] => Compact Convertible
        [codes] => Array
            (
                [0] => CTAR
            )

        [max_people] => 4
        [min_people] => 4
        [max_bags] => 1
        [min_bags] => 1
        [price] => 91.17
    )

[18] => stdClass Object
    (
        [description] => Premium SUV
        [codes] => Array
            (
                [0] => PFAR
                [1] => UFAR
            )

        [max_people] => 7
        [min_people] => 5
        [max_bags] => 5
        [min_bags] => 1
        [price] => 92.64
    )

[19] => stdClass Object
    (
        [description] => Specialty Car
        [codes] => Array
            (
                [0] => XCAR
            )

        [max_people] => 5
        [min_people] => 5
        [max_bags] => 3
        [min_bags] => 3
        [price] => 94.42
    )

[20] => stdClass Object
    (
        [description] => Mid-Size Convertible
        [codes] => Array
            (
                [0] => ITAR
            )

        [max_people] => 4
        [min_people] => 4
        [max_bags] => 2
        [min_bags] => 2
        [price] => 97.98
    )

[21] => stdClass Object
    (
        [description] => Luxury SUV
        [codes] => Array
            (
                [0] => LFAR
            )

        [max_people] => 5
        [min_people] => 5
        [max_bags] => 3
        [min_bags] => 3
        [price] => 113.66
    )

[22] => stdClass Object
    (
        [description] => Premium Convertible
        [codes] => Array
            (
                [0] => PTAR
            )

        [max_people] => 4
        [min_people] => 4
        [max_bags] => 1
        [min_bags] => 1
        [price] => 121.15
    )

[23] => stdClass Object
    (
        [description] => Luxury Convertible
        [codes] => Array
            (
                [0] => LTAR
            )

        [max_people] => 4
        [min_people] => 4
        [max_bags] => 1
        [min_bags] => 1
        [price] => 129.01
    )

[24] => stdClass Object
    (
        [description] => Premium Van
        [codes] => Array
            (
                [0] => PVAR
            )

        [max_people] => 15
        [min_people] => 15
        [max_bags] => 1
        [min_bags] => 1
        [price] => 159.49
    )

[25] => stdClass Object
    (
        [description] => Specialty Convertible
        [codes] => Array
            (
                [0] => XTAR
            )

        [max_people] => 4
        [min_people] => 4
        [max_bags] => 2
        [min_bags] => 2
        [price] => 307.64
    )
)
2

There are 2 best solutions below

6
On

So just assign anything not in $order the maximum sorting weight, and then add a secondary price comparison if the cars have identical weights. This method is far more simple and only requires one sort operation.

usort($car_types, function($a, $b) use ($order) {
    $a_weight = key_exists($a->description, $order) ? $order[$a->description] : PHP_INT_MAX;
    $b_weight = key_exists($b->description, $order) ? $order[$b->description] : PHP_INT_MAX;

    if( $a_weight != $b_weight ) {
        return $a_weight - $b_weight;
    } else {
        return ( $a->price - $b->price ) * 100;
    }
});
6
On

You can do this with one call to usort.

First of all, you can also use array_flip to make it easier to manage the $order array. That way you don't have to type (or change) the number values:

$order = array_flip([
    'Economy Car',
    'Compact Car',
    'Mid-Size Car',
    'Standard-Size Car',
    'Full-Size Car',
    'Premium Car',
    'Luxury Car',
    'Standard-Size Convertible',
    'Mid-Size SUV',
    'Standard SUV',
    'Full-Size SUV',
    'Minivan'
]);

And the sorting itself:

usort($car_types, function($a, $b) use ($order) {
    $ret =  (isset($order[$a->description]) ? $order[$a->description] : count($order))
          - (isset($order[$b->description]) ? $order[$b->description] : count($order));
    return $ret ? $ret : ($a->price - $b->price) * 100;
});

The first assignment calculates the difference in order based on the description field. If no order is known, a default value of count($order) is taken instead (using the ternary operator): this way car types that are not in the $order array will be ordered after any car types that have an entry in $order. The count function works here assuming you have numbered the types starting with 0 (which is the case with array_flip), but you can use any great enough number instead.

The return statement then checks if the above value is different from zero, and if so returns it. If it is zero the difference in price (in cents) is returned. The value in cents is taken because the return value is expected to be an integer, not a float. As said in the documentation:

Caution: Returning non-integer values from the comparison function, such as float, will result in an internal cast to integer of the callback's return value. So values such as 0.99 and 0.1 will both be cast to an integer value of 0, which will compare such values as equal.