OzZie
OzZie

Reputation: 523

Sort a multidimensional array by two given keys

I've got an array that looks like this:

[
{
    "id": "1",
    "country_id": "1",
    "spec_id": "1",
    "spec_children_name": "SUPER REDUCES RATE",
    "spec_children_first_col": "",
    "spec_children_second_col": "",
    "spec_children_third_col": ""
},
{
    "id": "2",
    "country_id": "1",
    "spec_id": "1",
    "spec_children_name": "REDUCED RATE",
    "spec_children_first_col": "10% and 13%",
    "spec_children_second_col": "food, passenger transport, accommodotion, newspaper, pharmaceutical products,\u2026.(10%); plants, antiques, firewood, cinema, theatre,\u2026(13%)",
    "spec_children_third_col": ""
},
{
    "id": "3",
    "country_id": "1",
    "spec_id": "1",
    "spec_children_name": "MEDIUM RATE",
    "spec_children_first_col": "",
    "spec_children_second_col": "",
    "spec_children_third_col": ""
},
{
    "id": "4",
    "country_id": "1",
    "spec_id": "1",
    "spec_children_name": "STANDARD RATE",
    "spec_children_first_col": "20%",
    "spec_children_second_col": "other",
    "spec_children_third_col": ""
},
{
    "id": "5",
    "country_id": "1",
    "spec_id": "1",
    "spec_children_name": "ZERO RATE",
    "spec_children_first_col": "",
    "spec_children_second_col": "",
    "spec_children_third_col": ""
},
    {
        "id": "104",
        "country_id": "2",
        "spec_id": "1",
        "spec_children_name": "REDUCED RATE",
        "spec_children_first_col": "TEXT 547",
        "spec_children_second_col": "TEXT 1000",
        "spec_children_third_col": ""
    }
]

What i want: I'd like to sort this array by 2 object key compare, if spec_children_name and spec_id. Finally, it should look like:

    [
        [
    {
        "id": "1",
        "country_id": "1",
        "spec_id": "1",
        "spec_children_name": "SUPER REDUCES RATE",
        "spec_children_first_col": "",
        "spec_children_second_col": "",
        "spec_children_third_col": ""
    },
    {
        "id": "2",
        "country_id": "1",
        "spec_id": "1",
        "spec_children_name": "REDUCED RATE",
        "spec_children_first_col": "10% and 13%",
        "spec_children_second_col": "food, passenger transport, accommodotion, newspaper, pharmaceutical products,\u2026.(10%); plants, antiques, firewood, cinema, theatre,\u2026(13%)",
        "spec_children_third_col": ""
    },
,
    {
        "id": "104",
        "country_id": "2",
        "spec_id": "1",
        "spec_children_name": "REDUCED RATE",
        "spec_children_first_col": "TEXT 547",
        "spec_children_second_col": "TEXT 1000",
        "spec_children_third_col": ""
    }
    {
        "id": "3",
        "country_id": "1",
        "spec_id": "1",
        "spec_children_name": "MEDIUM RATE",
        "spec_children_first_col": "",
        "spec_children_second_col": "",
        "spec_children_third_col": ""
    },
    {
        "id": "4",
        "country_id": "1",
        "spec_id": "1",
        "spec_children_name": "STANDARD RATE",
        "spec_children_first_col": "20%",
        "spec_children_second_col": "other",
        "spec_children_third_col": ""
    },
    {
        "id": "5",
        "country_id": "1",
        "spec_id": "1",
        "spec_children_name": "ZERO RATE",
        "spec_children_first_col": "",
        "spec_children_second_col": "",
        "spec_children_third_col": ""
    }
    ]

*Note the two objects with same keys (spec_id and spec_children_name) one after another.

What i've tried so far:

function array_sort($array, $on, $order=SORT_ASC){
    $new_array = array();
    $sortable_array = array();
    if (count($array) > 0) {
        foreach ($array as $k => $v) {
            if (is_array($v)) {
                foreach ($v as $k2 => $v2) {
                    if ($k2 == $on) {
                        $sortable_array[$k] = $v2;
                    }
                }
            } else {
                $sortable_array[$k] = $v;
            }
        }
        switch ($order) {
            case SORT_ASC:
                asort($sortable_array);
                break;
            case SORT_DESC:
                arsort($sortable_array);
                break;
        }
        foreach ($sortable_array as $k => $v) {
            $new_array[$k] = $array[$k];
        }
    }
    return $new_array;
}

Fn call: array_sort($array, 'spec_children_name', SORT_ASC);

Dynamic solution please, with key parameters to sort by

Another approach:

usort($country_specs_meta_results, function($a, $b){
                            $c .= $b['spec_id'] - $a['spec_id'];
                            $c .= strcmp($a['spec_children_name'], $b['spec_children_name']);
                            return $c;
                        });

EDIT: I've updated the arrays as solution above still wreck the output

EDIT 2: This is the output with the function array_sort modified by @Jeto:

{
    "4": {
        "id": "5",
        "country_id": "1",
        "spec_id": "1",
        "spec_children_name": "ZERO RATE",
        "spec_children_first_col": "",
        "spec_children_second_col": "",
        "spec_children_third_col": ""
    },
    "0": {
        "id": "1",
        "country_id": "1",
        "spec_id": "1",
        "spec_children_name": "SUPER REDUCES RATE",
        "spec_children_first_col": "",
        "spec_children_second_col": "",
        "spec_children_third_col": ""
    },
    "3": {
        "id": "4",
        "country_id": "1",
        "spec_id": "1",
        "spec_children_name": "STANDARD RATE",
        "spec_children_first_col": "20%",
        "spec_children_second_col": "other",
        "spec_children_third_col": ""
    },
    "1": {
        "id": "2",
        "country_id": "1",
        "spec_id": "1",
        "spec_children_name": "REDUCED RATE",
        "spec_children_first_col": "10% and 13%",
        "spec_children_second_col": "food, passenger transport, accommodotion, newspaper, pharmaceutical products,\u2026.(10%); plants, antiques, firewood, cinema, theatre,\u2026(13%)",
        "spec_children_third_col": ""
    },
    "76": {
        "id": "104",
        "country_id": "2",
        "spec_id": "1",
        "spec_children_name": "REDUCED RATE",
        "spec_children_first_col": "10% and 13% BG",
        "spec_children_second_col": "TEXT FOR BG",
        "spec_children_third_col": ""
    },
    "2": {
        "id": "3",
        "country_id": "1",
        "spec_id": "1",
        "spec_children_name": "MEDIUM RATE",
        "spec_children_first_col": "",
        "spec_children_second_col": "",
        "spec_children_third_col": ""
    }
}

So the order changed but not for the "spec_children_name": "REDUCED RATE" only

Upvotes: 0

Views: 84

Answers (3)

Yuval
Yuval

Reputation: 3433

Your question is essentially how to sort an array according to two keys. In many programming languages, PHP included, the answer would be to create a comparison function (sometimes dubbed a comparator), that returns values (commonly -1, 0, 1) depending on the required relative ordering of its arguments. If the two arguments differ on the first of the two keys, return -1 or 1 depending on the first key's ordering. The they are equal on the first key, check the second key - and return -1, 0, 1 according to the ordering of the second key. This logic can naturally be extended to an arbitrary amount of keys. The PHP Spaceship operator <=> implements this logic for many built-in types.

Below is code that demonstrates this for your scenario using PHP. It is adapted from this question. Here, I assumed that you wanted to sort by the rate (zero < super reduced < reduced < medium < standard) and then the spec_id (numerically). I also corrected SUPER REDUCES RATE to SUPER REDUCED RATE.

$RATE_ORDERING = array(
  'ZERO RATE' => 1,
  'SUPER REDUCED RATE' => 2, 
  'REDUCED RATE' => 3,
  'MEDIUM RATE' => 4,
  'STANDARD RATE' => 5
  );
$arr = array(
    array('spec_id' => 2, 'spec_children_name' => 'STANDARD RATE'),
    array('spec_id' => 1, 'spec_children_name' => 'STANDARD RATE'),
    array('spec_id' => 2, 'spec_children_name' => 'ZERO RATE'),
    array('spec_id' => 1, 'spec_children_name' => 'MEDIUM RATE'),
    array('spec_id' => 2, 'spec_children_name' => 'REDUCED RATE'),
    array('spec_id' => 2, 'spec_children_name' => 'MEDIUM RATE'),
    array('spec_id' => 1, 'spec_children_name' => 'SUPER REDUCED RATE'),
    array('spec_id' => 2, 'spec_children_name' => 'SUPER REDUCED RATE'),
    array('spec_id' => 1, 'spec_children_name' => 'REDUCED RATE'),
    array('spec_id' => 1, 'spec_children_name' => 'ZERO RATE')
);

usort($arr, function ($a, $b) use ($RATE_ORDERING) {
  $result = $RATE_ORDERING[$a['spec_children_name']] - $RATE_ORDERING[$b['spec_children_name']];
  if ($result != 0) {
    return $result;
  }
  return $a['spec_id'] - $b['spec_id'];
});

var_dump($arr);

Output:

array(10) {
  [0]=>
  array(2) {
    ["spec_id"]=>
    int(1)
    ["spec_children_name"]=>
    string(9) "ZERO RATE"
  }
  [1]=>
  array(2) {
    ["spec_id"]=>
    int(2)
    ["spec_children_name"]=>
    string(9) "ZERO RATE"
  }
  [2]=>
  array(2) {
    ["spec_id"]=>
    int(1)
    ["spec_children_name"]=>
    string(18) "SUPER REDUCED RATE"
  }
  [3]=>
  array(2) {
    ["spec_id"]=>
    int(2)
    ["spec_children_name"]=>                                                                                                   [17/133]
    string(18) "SUPER REDUCED RATE"
  }
  [4]=>
  array(2) {
    ["spec_id"]=>
    int(1)
    ["spec_children_name"]=>
    string(12) "REDUCED RATE"
  }
  [5]=>
  array(2) {
    ["spec_id"]=>
    int(2)
    ["spec_children_name"]=>
    string(12) "REDUCED RATE"
  }
  [6]=>
  array(2) {
    ["spec_id"]=>
    int(1)
    ["spec_children_name"]=>
    string(11) "MEDIUM RATE"
  }
  [7]=>
  array(2) {
    ["spec_id"]=>
    int(2)
    ["spec_children_name"]=>
    string(11) "MEDIUM RATE"
  }
  [8]=>
  array(2) {
    ["spec_id"]=>
    int(1)
    ["spec_children_name"]=>
    string(13) "STANDARD RATE"
  }
  [9]=>
  array(2) {
    ["spec_id"]=>
    int(2)
    ["spec_children_name"]=>
    string(13) "STANDARD RATE"
  }
}

Upvotes: 1

mickmackusa
mickmackusa

Reputation: 47864

The spaceship operator will take care of all data types automatically. Pass the dynamic column name and the sorting direction "factor" via use() into the custom function scope.

Code: (Demo)

$objects = json_decode($json);

$column = 'spec_children_name';
$direction = 'asc';
$reverser = $direction === 'asc' ? 1 : -1;

uasort($objects, function($a, $b) use ($column, $reverser) {
    return $reverser * ($a->$column <=> $b->$column);
});

var_export($objects);

Or perhaps... (Demo)

$objects = json_decode($json);

$rules = ['spec_id' => 'ASC', 'spec_children_name' => 'DESC'];

uasort($objects, function($a, $b) use ($rules) {
    foreach ($rules as $column => $order) {
        $left[] = $order === 'ASC' ? $a->$column : $b->$column;
        $right[] = $order === 'ASC' ? $b->$column : $a->$column;
    }
    return $left <=> $right;
});

var_export($objects);

Upvotes: 0

Progrock
Progrock

Reputation: 7485

There's hardly a reason to reinvent a function as array_multisort and array_column will do.

$data =
[
    [
    'name' => 'John',
    'age'  => 34
    ],
    [
    'name'  => 'Jack',
    'age'   => 55,
    ],
    [
    'name' => 'Adam',
    'age'  => 42
    ],
    [
    'name' => 'Jack',
    'age'  => 78
    ],
    [
    'name' => 'Adam',
    'age'  => 80
    ]
];

array_multisort(array_column($data, 'name'), SORT_ASC, array_column($data, 'age'), SORT_DESC, $data);
var_export($data);

Output:

array (
  0 => 
  array (
    'name' => 'Adam',
    'age' => 80,
  ),
  1 => 
  array (
    'name' => 'Adam',
    'age' => 42,
  ),
  2 => 
  array (
    'name' => 'Jack',
    'age' => 78,
  ),
  3 => 
  array (
    'name' => 'Jack',
    'age' => 55,
  ),
  4 => 
  array (
    'name' => 'John',
    'age' => 34,
  ),
)

Upvotes: 0

Related Questions