I've read at least twenty web tutorials on usort(). Unfortunately, they all seem to use exactly the same example (pulled from PHP.NET's online manual), and I don't understand the user sort function.

I'm building an array that will have one or more of the following elements in random order:

Greybox
Browsie
Feelies
Manual
Disks
Folio
Mastertronic

I want to be able to sort the array in the above order, but I don't understand how to write the user sort function. Any tips would be greatly appreciated.

--Nathanael

    Let me give you an example solution, and then try to explain what I did:

    <?php
    
    $correct_order = array(
        'Greybox',
        'Browsie',
        'Feelies',
        'Manual',
        'Disks',
        'Folio',
        'Mastertronic',
    );
    
    function cmp($a, $b) {
        global $correct_order;
        $a_index = array_search($a, $correct_order);
        $b_index = array_search($b, $correct_order);
        if($a_index < $b_index) return -1;
        if($a_index > $b_index) return 1;
        return 0;
    }
    
    $mixed_order = array(
        'Disks',
        'Mastertronic',    
    'Manual', 'Greybox', 'Feelies', ); usort($mixed_order, 'cmp'); foreach($mixed_order as $v) echo "$v<br />\n"; ?>

    The usort function takes two arguments, the array to be sorted, and the name of the function that does the comparison. The comparison function ('cmp' in the example) is called repeatedly during the sort process. It needs to be able to compare any 2 elements of the array being sorted and determine a relationship between them. It must then return a value to reflect this relationship. If you're comparing two variables $a and $b, returning a -1 means that $a should come before $b. Returning 1 means $b should come before $a. If they're the same, return a 0.

    In your case, you need a way to determine the intended order of the possible values. What I did was created a list in the correct order. This list is referenced as a global value in the cmp() function. For any compared element being passed in to the cmp function, it uses the array_search function to find the index of the element in the correct order. For example, 'Feelies' has an index of 2 in the correct order. 'Browsie' has an index of 1. If you did a cmp('Feelies', 'Browsie') it would return 1, since 'Browsie should come before 'Feelies'.

    Hopefully that helps you. Let me know if I can clarify anything.

      usort() requires that you have a "callback" function defined which takes two arguments, compares them however you want, and returns a negative integer if the first argument should sort before the second, a positive integer if the second arg should be first, or 0 if they are considered equal in sorting order. So the main work in this case is figuring out how to compare any two values from the array and return the proper result. Since the desired sort order appears to be arbitrary, all I can think of is something like using a hard-coded array to indicate the ordering:

      <?php
      // sorting callback function
      function mySort($a, $b)
      {
         $order = array(
            'Greybox',
            'Browsie',
            'Feelies',
            'Manual',
            'Disks',
            'Folio',
            'Mastertronic'
         );
         return((array_search($a, $order) < array_search($b, $order)) ? -1 : 1);
      }
      // test:
      $test = array(
         'Folio',
         'Browsie',
         'Manual',
         'Greybox'
      );
      usort($test, 'mySort');
      echo "<pre>".print_r($test,1)."</pre>";
      

      Output:

      Array
      (
          [0] => Greybox
          [1] => Browsie
          [2] => Manual
          [3] => Folio
      )
      

        Just as an follow-up, I thought of an optimization for this. If you're doing a significant amount of sorting, you can save some time by creating a hash of the correct order. By 'hash' I mean an associative array indexed by string value that maps to the index of the order. For example, the index 'Feelies' would contain the value 2. Do that mapping up front, and you can avoid the repeated array_search calls. In comparing the two versions, I found using the hash to be about 8 times faster on my laptop, YMMV.

        Here's the revised code:

        <?php
        
        $correct_order = array(
            'Greybox',
            'Browsie',
            'Feelies',
            'Manual',
            'Disks',
            'Folio',
            'Mastertronic',
        );
        
        $correct_order_hash = array();
        foreach($correct_order as $i => $c) {
            $correct_order_hash[$c] = $i;
        }
        
        function cmp($a, $b) {
            global $correct_order_hash;
            $a_index = $correct_order_hash[$a];
            $b_index = $correct_order_hash[$b];
            if($a_index < $b_index) return -1;
            if($a_index > $b_index) return 1;
            return 0;
        }
        
        $mixed_order = array(
            'Disks',
            'Mastertronic',    
        'Manual', 'Greybox', 'Feelies', ); usort($mixed_order, 'cmp'); foreach($mixed_order as $v) echo "$v<br />\n"; ?>
          Tonsil;10952082 wrote:
          $correct_order_hash = array();
          foreach($correct_order as $i => $c) {
              $correct_order_hash[$c] = $i;
          }
          

          Just FYI, you could replace that with:

          $correct_order_hash = array_flip($correct_order);
          

            ...

            NogDog;10952083 wrote:

            Just FYI, you could replace that with:

            $correct_order_hash = array_flip($correct_order);
            

            Nice, thanks! I tend to forget some of the vast library functions PHP has to offer.

              usort() is great, but consider: $correct_order is an array with the elements in correct order. $mixed_order is an array with the elements that you want, but not necessarily in correct order. You want the elements in correct order, but only if they are the ones you want. Therefore, you might as well use [man]array_intersect/man, e.g.,

              $mixed_order = array_intersect($correct_order, $mixed_order);
                laserlight;10952091 wrote:

                usort() is great, but consider: $correct_order is an array with the elements in correct order. $mixed_order is an array with the elements that you want, but not necessarily in correct order. You want the elements in correct order, but only if they are the ones you want. Therefore, you might as well use [man]array_intersect/man, e.g.,

                $mixed_order = array_intersect($correct_order, $mixed_order);

                I guess the one question here is whether or not array_intersect() is guaranteed to maintain the order of the first array? (It's hard for me to imagine why it would be implemented otherwise, but, if it's mission critical, then Murphy's Law would probably say that some future upgrade would not. :rolleyes: )

                  NogDog wrote:

                  I guess the one question here is whether or not array_intersect() is guaranteed to maintain the order of the first array?

                  The PHP manual states that keys are preserved. If the order is not maintained, then that cannot be so for an initially numerically indexed array.

                    Thanks to everyone for your help. The compare function in the other examples I'd looked at looked like black voodo to me -- lots of mumbo jumbo, then the correct result magically pops out. I had no clue how to write my own. Tonsil's example is much clearer. I still feel like I'm swimming over my head -- associative arrays and hash indices I'm still lost on -- but at least I can see bottom now.

                    And Laserlight, array_intersect looks like it might do what I need. I'll play around with all this tonight and tomorrow.

                    --Nathanael

                      Write a Reply...