views:

504

answers:

4

Currently I have an array that contains x and y coordinates of various positions.

ex. $location[0]['x'] = 1; $location[0]['y'] = 1

This indicates id 0 has a position of (1,1).

Sometimes I want to sort this array by x, and other times by y.

Currently I am using array-multisort() to sort my data, but I feel this method is inefficient since every time before I sort, I must make a linear pass through the $location array just to build the index (on the x or y key) before I can invoke the array-multisort() command.

Does anyone know a better way to do this? Perhaps it is a bad idea even to store the data like this? Any suggestions would be great.

+2  A: 

You could use usort() which lets you choose how your array elements are compared.

// sort by 'y'
usort($location, 'cmp_location_y');

// or sort by 'x'
usort($location, 'cmp_location_x');

// here are the comparison functions
function cmp_location_x($a, $b) {
    return cmp_location($a, $b, 'x');
}

function cmp_location_y($a, $b) {
    return cmp_location($a, $b, 'y');
}

function cmp_location($a, $b, $key) {
    if ($a[$key] == $b[$key]) {
     return 0;
    } else if ($a[$key] < $b[$key]) {
     return -1;
    } else {
     return 1;
    }
}
yjerem
A: 

keeping the arrays and the multisort you have, changing the structure to something like the following would eliminate the need for a previous pass:

$locations = array( 
    'x' => $x_coordinates,
    'y' => $y_coordinates,
    'data' => $data_array
);

then just use the array_multisort() on all columns.

jcinacio
If you do this how do you preserve the relation between x,y, and the data?
array_multisort preserves the relation, that is the x in position 1 has a y in position 1 and data in position 1.
OIS
yes, array_multisort() takes care of preserving the relation between items in the different arrays.
jcinacio
A: 

Something like what jcinacio said. With this class you can store and sort all sorts of data really, not just locations in different dimensions. You can implement other methods like remove etc as needed.

class Locations {
    public $locations = array();
    public $data = array();
    public $dimensions = 2;

    public function __construct($dimensions = null)
    {
     if (is_int($dimensions))
      $this->dimensions = $dimensions;
    }

    public function addLocation()
    {
     $t = func_num_args();

     if ($t !== $this->dimensions)
      throw new Exception("This Locations object has {$this->dimensions} dimensions");

     $args = func_get_args();

     for ($i = 0; $i < $t; $i++)
      $this->locations[$i][] = $args[$i];

     return $this;
    }

    public function sortByDimension($dimension = 1) 
    {
     if ($dimension > $this->dimensions)
      throw new Exception("Wrong number of dimensions");

     --$dimension;

     $params[] = &$this->locations[$dimension];

     for ($i = 0, $t = $this->dimensions; $i < $t; $i++) {
      if ($i === $dimension)
       continue;

      $params[] = &$this->locations[$i];
     }

     call_user_func_array('array_multisort', $params);

     return $this;
    }
}

test data:

$loc = new Locations(3);

$loc
    ->addLocation(1, 1, 'A')
    ->addLocation(2, 3, 'B')
    ->addLocation(4, 2, 'C')
    ->addLocation(3, 2, 'D')
;
$loc->sortByDimension(1);
var_dump($loc->locations);
$loc->sortByDimension(2);
var_dump($loc->locations);
OIS
the problem i see with this is that you still can't avoid that for loop in your sortByDimensions. every time you want to sort it you'll still need to build an index before you can invoke multisort.
Its a loop running for the number of dimensions you have, and its constant for every number of dimensions no matter how many locations. Unlike your index loop which is slower and slower the more locations you add.
OIS
Just to specify, the extra cost of making the sort index is negligible compared to the benefit of a general purpose class.
OIS
Careful, over engineering is worse than code duplication
gradbot
There's hardly more code here for "unlimited" number of dimensions compared to a 2 dimension only location, with the added benefit of storing data etc as a 'unused dimension' if wanted. I removed almost everything related to $data when I realised that, but forgot one line. A-D is data in example.
OIS
And you seem to be a stiffer for speed gradbot. This is faster. The more coordinates the more noticable it is. For just 10-100 coords any thing is fast enough though. But Id say an object is better then random functions or code.
OIS
+2  A: 

You want to keep using multisort.

I made a quick benchmark of usort and array_multisort. Even at a count of only 10 multisort with building an index is faster than usort. At 100 elements it's about 5 times faster. At around 1000 elements improvement levels off right at a magnitude faster. User function calls are just too slow. I'm running 5.2.6

$count = 100;

for ($i = 0; $i < $count; $i++)
{
  $temp = array('x' => rand(), 'y' => rand());
  $data[] = $temp; 
  $data2[] = $temp; 
}

function sortByX($a, $b) { return ($a['x'] > $b['x']); }

$start = microtime(true);
usort($data, "sortByX");
echo (microtime(true) - $start) * 1000000, "<br/>\n";

$start = microtime(true);
foreach ($data2 as $temp)
  $s[] = $temp['x'];
array_multisort($s, SORT_NUMERIC, $data2);
echo (microtime(true) - $start) * 1000000, "<br/>\n";

PHP currently doesn't have an array_pluck function like ruby. Once it does you can replace this code

foreach ($data2 as $temp)
    $s[] = $temp['x'];`

with

$s = array_pluck('x', $data2);
gradbot