0

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

OzZie
  • 523
  • 9
  • 21

3 Answers3

1

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"
  }
}
Yuval
  • 3,207
  • 32
  • 45
  • This is a heroic answer. I wonder why there's no upvotes. I gave a +1 just now. I looked at your profile and saw you committed to the Protein Modeling proposal on Area51. Sorry that it didn't succeed. I proposed a [Materials Modeling](https://area51.stackexchange.com/proposals/122958/materials-modeling?referrer=NzE1MWZiMjg1NmE1MDU4YWQ2ODkzMzZiNGY5OTRkNWRhN2JkNzdhZGUwODllOWUxNGNjZjRhZjBmYTE3M2M4ZBljSouiYXUzpNjPv6N_DNmpFSwSGHCb4w8-XBwHc-_E0) site which covers protein modeling, but also other materials like solar cells, photo-voltaics, energy storage materials, new developments in batteries, – Nike Feb 15 '20 at 02:54
  • and lighter or stronger materials for cars, airplanes, spaceships, etc. It would be very lovely to have you in the Private Beta! Please commit if you don't think it would be inconvenient! – Nike Feb 15 '20 at 02:55
0

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);
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
0

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,
  ),
)
Progrock
  • 7,373
  • 1
  • 19
  • 25