-2

I've looked for various sorting function but quite couldn't find what I am looking for.

Here's my current array result.

  0 =>
  array(
    'member_reference_identifier' => '001',
    'user_status' => 'Hold',
    'is_primary' => 'true',
  ),
  1 =>
  array(
    'member_reference_identifier' => '002',
    'user_status' => 'Banned',
    'is_primary' => 'true',
  ),
  2 =>
  array(
    'member_reference_identifier' => '003',
    'user' => 'Banned',
    'is_primary' => 'true',
  ),
  3 =>
  array(
    'member_reference_identifier' => '004',
    'user_status' => 'Active',
    'is_primary' => 'false',
  ),
  4 =>
  array(
    'member_reference_identifier' => '005',
    'user_status' => 'Active',
    'is_primary' => 'true',
  ),
  5 =>
  array(
    'member_reference_identifier' => '006',
    'user_status' => 'Hold',
    'is_primary' => 'true',
  ),
  6 =>
  array(
    'member_reference_identifier' => '007',
    'user_status' => 'Banned',
    'is_primary' => 'true',
  ),
)

I am looking to show the primary results first and in that, I want to have sorted order: Active > Hold > Banned.

Here's what I am expecting:

  4 =>
  array(
    'member_reference_identifier' => '005',
    'user_status' => 'Active',
    'is_primary' => 'true',
  ),

  0 =>
  array(
    'member_reference_identifier' => '001',
    'user_status' => 'Hold',
    'is_primary' => 'true',
  ),
  5 =>
  array(
    'member_reference_identifier' => '006',
    'user_status' => 'Hold',
    'is_primary' => 'true',
  ),
  1 =>
  array(
    'member_reference_identifier' => '002',
    'user_status' => 'Banned',
    'is_primary' => 'true',
  ),
  2 =>
  array(
    'member_reference_identifier' => '003',
    'user_status' => 'Banned',
    'is_primary' => 'true',
  ),

  6 =>
  array(
    'member_reference_identifier' => '007',
    'user_status' => 'Banned',
    'is_primary' => 'true',
  ),
  3 =>
  array(
    'member_reference_identifier' => '004',
    'user_status' => 'Active',
    'is_primary' => 'false',
  ),

)

Even though the last object is active, it's at last because is_primary is false.

Adding more details to make the question bit more focused: I was able to determine the super brute force solution splitting my main array into multiple arrays. Sorting them individually and joining them again. But, that was such an expensive operation given the large dataset I was dealing with, probably millions of objects in an array. I am mainly looking for something optimal.

  • Does this answer your question? [How to Sort Multi-dimensional Array by Value?](https://stackoverflow.com/questions/2699086/how-to-sort-multi-dimensional-array-by-value) – biesior May 16 '21 at 16:23
  • @biesior saw that question before posting. That user (in 2010) was looking for just ascending order sorting. I want customized order. – creamCheeseCoder May 16 '21 at 16:32
  • So what did you try? You should tell us that so we don't need to guess, If `usort()`doesn't satisfy you, you can also iterate input array, put elements to two new arrays like `if ($item['is_primary']===true){$primares[] =$item;} else {$nonPrimaries[]=$item;}` and finally merge these arrays to get final one `$finalArray = array_merge($primaries, $nonPrimaries);` (sorry, you should create 3 arrays for your states and merge them, my bad, but that's just POC) – biesior May 16 '21 at 16:36
  • That's a great idea to be honest. But then I will have to sort both those arrays individually for user_status before merging. Don't you think that would be too expensive? – creamCheeseCoder May 16 '21 at 16:45
  • You can sort input array first, then do the split+re-merge thing, so you'll have it sorted by one index and grouped by another. That depends on the size of the input array actually, however for such short datasets I wouldn't care about the performance. I used in in one of my project where there are max 100 elements to group and didn't notice any performance drop at this approach. – biesior May 16 '21 at 16:49
  • For this particular thing, we are talking about at least a million results. "streaming" industry you know! – creamCheeseCoder May 16 '21 at 16:51
  • That can be a performance-killer, anyway in such a big dataset I would check at least several approaches, to find the most optimal one. simple time measurement in PHP will give you a clear answer. – biesior May 16 '21 at 16:56
  • @creamCheeseCoder You can't estimate the expense of an operation by counting lines and the number of statements. Some times one line of code using a built-in function can be more expensive than 100 lines of conditional statements because you dont know what is going on behind the scenes in the internal implications. You should get your code running correctly following best practicesand avoiding known culprits and then improve it later – USER249 May 16 '21 at 21:36

3 Answers3

1

try uasort - it'll sort an array with a user-defined comparison function and maintain index association:

<?php
        $x = [
        0 =>[
                'member_reference_identifier' => '001',
                'user_status' => 'Hold',
                'is_primary' => 'true',
                ],
        1 => [
                'member_reference_identifier' => '002',
                'user_status' => 'Banned',
                'is_primary' => 'true',
                ],
        2 => [
                'member_reference_identifier' => '003',
                'user_status' => 'Banned',
                'is_primary' => 'true',
                ],
        3 =>[
                'member_reference_identifier' => '004',
                'user_status' => 'Active',
                'is_primary' => 'false',
                ],
        4 => [
                'member_reference_identifier' => '005',
                'user_status' => 'Active',
                'is_primary' => 'true',
                ],
        5 => [
                'member_reference_identifier' => '006',
                'user_status' => 'Hold',
                'is_primary' => 'true',
        ],
        6 => [
                'member_reference_identifier' => '007',
                'user_status' => 'Banned',
                'is_primary' => 'true',
                ],
        ];


        function sorter($a,$b){
                $states = ['Active'=>0,'Hold'=>1,'Banned'=>2];
                if ($a['is_primary'] == 'true' && $b['is_primary']=='false') return -1;
                if ($a['is_primary'] == 'false' && $b['is_primary']=='true') return 1;
                return $states[$a['user_status']]<=>$states[$b['user_status']];
        }
        uasort($x,'sorter');
        print_r($x);

PS your array was inconsistent - i've unified keys user. account_status to user_status.

pQd
  • 116
  • 4
  • 20
  • Thank you. Can the logic for is_primary incorporated in this? Or should I sort the sorted by user_status array for is_primary key? – creamCheeseCoder May 16 '21 at 16:49
  • Agreed! This answer is great and basically what I was looking for. If @pQd can modify it to incorporate the is_primary flag too, I would accept it as complete answer. – creamCheeseCoder May 16 '21 at 19:02
  • @creamCheeseCoder i've updated my answer to include is_primary as a primary ranking factor – pQd May 17 '21 at 17:40
  • This is great! Thanks. I can accept this as an answer. – creamCheeseCoder May 18 '21 at 03:36
  • But this way the non-primary members will not be ordered at the end of the array. Its better to make the states 1,2,3 and then multiply by 1 or - 1 before the <=> comparison for the boolean is_primary condition so that non-primary members get ordered as well at the end of the array and with a simpler and faster code at the same time. Check my answer. – USER249 May 18 '21 at 17:55
0

Using a slightly modified version of pQd's sorter function will give you what you want. First we need to incorporate the is_primary field value by giving true a value of 1 and false -1 and then multiply it with both values from the states array before returning the result to the "uasort" function. We also need to modify the states array shifting it one to the right to eliminate the zero because it will always give zero when multiplied with either a negative or a positive number.
I didn't test this but it should work if there are no typos.

    function sorter($a,$b){
       $is_primary = ['true' =>-1, 'false' =>1]   // using true =1, false=-1 will reverse is_primary order 
       $states = ['Active'=>1,'Hold'=>2,'Banned'=>3];
       $c=$is_primary[$a['is_primary']];
       $d=$is_primary[$b['is_primary']];
       $e=$states[$a['user_status']];
       $f=$states[$b['user_status']];
      return (e*$c)<=>(f*$d);
    }
    uasort($the_array,'sorter');
    print_r($the_array);     

The above is the same as the below. I edited it breaking it down for better readability and understanding:

    function sorter($a,$b){

      $is_primary = ['true' =>-1, 'false' =>1]       

      $states = ['Active'=>1,'Hold'=>2,'Banned'=>3];

      return $states[$a['user_status']]*$is_primary[$a['is_primary']] <=>$states[$b['user_status']]*$is_primary[$b['is_primary']];

    }

    uasort($the_array,'sorter');

    print_r($the_array);     
     

Note that those with is_primary = false will be reversed in term of the status criteria order but that should be easy to fix inside the sorter function by for example initalizing the array iniside an IF statement reversing the values whenever both is_primary fields of $a and $b are false 1,2,3 to 3,2,1 but I'm not sure, you need to inspect the underlying sorting algorithm of the uasort function.

USER249
  • 1,080
  • 7
  • 14
  • What's $a and $b passed to sorter? – creamCheeseCoder May 16 '21 at 19:25
  • You should have asked what is sorter first! sorter is the callback function of the uasort function and $a is an element from the array being compared to another element $b and its called number of times debending on the sorting algorithms used internaly by the uasort function. you should at least take a look in the documentation every time you use a built-in function for the first time and you should study the subject of sorting algorithms before starting any serious coding – USER249 May 16 '21 at 19:46
  • Refer to this https://en.wikipedia.org/wiki/Merge_sort and this https://www.php.net/manual/en/function.uasort.php and this https://www.php.net/manual/en/function.usort.php and refer to the docs about the <=> operator and the rest is elementary mathematics on what happens when we multiply numbers with same or different signs positive & negative and zero – USER249 May 16 '21 at 19:53
0

You could use collection's sortBy method.

$array = array(
  0 =>
  array(
    'member_reference_identifier' => '001',
    'user_status' => 'Hold',
    'is_primary' => 'true',
  ),
  1 =>
  array(
    'member_reference_identifier' => '002',
    'user_status' => 'Banned',
    'is_primary' => 'true',
  ),
  2 =>
  array(
    'member_reference_identifier' => '003',
    'user' => 'Banned',
    'is_primary' => 'true',
  ),
  3 =>
  array(
    'member_reference_identifier' => '004',
    'user_status' => 'Active',
    'is_primary' => 'false',
  ),
  4 =>
  array(
    'member_reference_identifier' => '005',
    'user_status' => 'Active',
    'is_primary' => 'true',
  ),
  5 =>
  array(
    'member_reference_identifier' => '006',
    'user_status' => 'Hold',
    'is_primary' => 'true',
  ),
  6 =>
  array(
    'member_reference_identifier' => '007',
    'user_status' => 'Banned',
    'is_primary' => 'true',
  ),
);
collect($array)
    ->sortBy(function ($item) {
        if (!isset($item['user_status'] || !isset($item['is_primary'])) {
            return 7;
        } elseif ($item['user_status'] == 'Active' && $item['is_primary'] == 'true') {
            return 1;
        } elseif ($item['user_status'] == 'Hold' && $item['is_primary'] == 'true') {
            return 2;
        } elseif ($item['user_status'] == 'Banned' && $item['is_primary'] == 'true') {
            return 3;
        } elseif ($item['user_status'] == 'Active' && $item['is_primary'] == 'false') {
            return 4;
        } elseif ($item['user_status'] == 'Hold' && $item['is_primary'] == 'false') {
            return 5;
        } elseif ($item['user_status'] == 'Banned' && $item['is_primary'] == 'false') {
            return 6;
        } else {
            return 7;
        }
    })
    ->values()
    ->all();

What I'm doing in the Closure is assigning a number to each possibility. This number is what the sortBy method will use to sort the data.


You could also use LazyCollections instead of Collections. The logic stays the same for the most part.

use Illuminate\Support\LazyCollection;

$sorted = LazyCollection::make($array)->sortBy(function ($item) {
        ...
    })
    ->values()
    ->all();
IGP
  • 14,160
  • 4
  • 26
  • 43
  • Thanks for the answer but I am looking for something less expensive solution as I can't have if_else on 1 million plus object array/collection. – creamCheeseCoder May 16 '21 at 19:05
  • You could use LazyCollections in that case. They use little memory. I'll add a section on it at the end of my answer. – IGP May 16 '21 at 19:16