skip to Main Content

I have the following array in PHP which contains the lengths of 3 sides of a triangle:

$edge_lengths = array(
  [0]=>
  float(420)
  [1]=>
  float(593.9696961967)
  [2]=>
  float(420)
);

I now need to sort this array so that the values are sorted in reverse order (highest-to-lowest). If any values match then the array must further be sorted by key in reverse order (highest-to-lowest) to get the following result:

$edge_sorted = array(
  [1]=>
  float(593.9696961967)
  [2]=>
  float(420)
  [0]=>
  float(420)
);

In Python I can do this with one line of code like this:

edge_sorted = np.argsort(edge_lengths)[::-1]

Which will then return this:

[1 2 0]

Note that this just returns a list of the indexes; I don’t need the points as such.

My equivalent code in PHP to try and do the same thing is this:

asort($edge_lengths, SORT_NUMERIC); // sort values in ascending order
$edge_lengths = array_reverse($edge_lengths, true); // reverse array and preserve keys
$edge_sorted = array();
foreach ($edge_sorted as $key => $value) {
   $edge_sorted[] = $key;
}

This code nearly works, but on some occasions where the values are the same the keys are still ordered lowest-to-highest rather than highest-to-lowest. I have considered trying to write a comparison function using usort or uksort but I don’t know where to start and would appreciate some help.

2

Answers


  1. There is not a PHP function that can sort by key and value simultaneously, you need to convert the input into an array of pairs, sort that, and then translate it back into a keyed array.

    // you cannot directly compare floats and exect a sane answer
    function float_compare($a, $b, $margin) {
        return abs($a - $b) <= $margin;
    }
    
    function edgesort($edges, $margin=0.00000000001) {
        $e = [];
        foreach($edges as $k => $v) {
            $e[] = [$k, $v];
        }
        
        usort($e, function($b, $a)use($margin){
            if( ! float_compare($a[1], $b[1], $margin) ) {
                return $a[1] <=> $b[1];
            }
            return $a[0] <=> $b[0];
        });
        
        $r = [];
        foreach($e as $i) {
            $r[$i[0]] = $i[1];
        }
        return $r;
    }
    
    $edge_lengths = [420, 593.9696961967, 420];
    
    var_dump(edgesort($edge_lengths));
    

    Output:

    array(3) {
      [1]=>
      float(593.9696961967)
      [2]=>
      int(420)
      [0]=>
      int(420)
    }
    

    That said, it would be simpler if you just stored the values as [edge_id, edge_length] pairs in the first place.

    Login or Signup to reply.
  2. You could use a custom sort function with uksort, sorting the keys based initially on the value at that location, and then (in case of equal values) on the key itself. You can then use array_keys to get the same result as argsort.

    $edge_lengths = [420, 593.9696961967, 420];
    
    uksort($edge_lengths, function($k1, $k2) use ($edge_lengths) {
        $res = $edge_lengths[$k2] <=> $edge_lengths[$k1];
        if ($res) return $res;
        return $k2 <=> $k1;
    });
    
    print_r(array_keys($edge_lengths));
    

    Output:

    Array
    (
        [0] => 1
        [1] => 2
        [2] => 0
    )
    

    Demo on 3v4l.org

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search