I have a problem with object comparison in PHP. What seems like a straightforward code actually runs way too slow for my liking and as I am not that advanced in the language I would like some feedback and suggestions regarding the following code:
class TestTokenGroup {
private $tokens;
...
public static function create($tokens) {
$instance = new static();
$instance->tokens = $tokens;
...
return $instance;
}
public function getTokens() {
return $this->tokens;
}
public static function compare($tokenGroup1, $tokenGroup2) {
$i = 0;
$minLength = min(array(count($tokenGroup1->getTokens()), count($tokenGroup2->getTokens())));
$equalLengths = (count($tokenGroup1->getTokens()) == count($tokenGroup2->getTokens()));
$comparison = strcmp($tokenGroup1->getTokens()[$i], $tokenGroup2->getTokens()[$i]);
while ($comparison == 0) {
$i++;
if (($i == $minLength) && ($equalLengths == true)) {
return 0;
}
$comparison = strcmp($tokenGroup1->getTokens()[$i], $tokenGroup2->getTokens()[$i]);
}
$result = $comparison;
if ($result < 0)
return -1;
elseif ($result > 0)
return 1;
else
return 0;
}
...
}
In the code above $tokens is just a simple array of strings.
Using the method above through usort() for an array of TestTokenGroup consisting of around 40k objects takes ~2secs.
Is there a sensible way to speed that up? Where is the bottleneck here?
EDIT: Added the getTokens() method I initially forgot to include.
You know that objects are "pass by reference", and arrays are "pass by value"?
If getTokens() returns $this->tokens, the array is copied every time you invoke that method.
Try accessing $tokens directly via $tokenGroup1->tokens. You could also use references (&) although returning a reference doesn't work in all PHP versions.
Alternatively, make one copy only:
$tokens1 = $tokenGroup1->getTokens();
$tokens2 = $tokenGroup2->getTokens();
Even if each token group is relatively small, it will save at least 40000 * ( 6 + $average_token_group_length * 2) array copies.
UPDATE
I've benchmarked OP's code (removing the ... lines) using:
function gentokens() {
$ret = [];
for ( $i=0; $i< 3; $i++)
{
$str = "";
for ( $x = rand(0,3); $x < 10; $x ++ )
$str .= chr( rand(0,25) + ord('a') );
$ret[] = $str;
}
return $ret;
}
$start = microtime(true);
$array = []; // this will hold the TestTokenGroup instances
$dummy = ""; // this will hold the tokens, space-separated and newline-separated
$dummy2= []; // this will hold the space-concatenated strings
for ( $i=0; $i < 40000; $i++)
{
$array[] = TestTokenGroup::create( $t = gentokens() );
$dummy .= implode(' ', $t ) . "\n";
$dummy2[] = implode(' ', $t );
}
// write a test file to benchmark GNU sort:
file_put_contents("sort-data.txt", $dummy);
$inited = microtime(true);
printf("init: %f s\n", ($inited-$start));
usort( $array, [ 'TestTokenGroup', 'compare'] );
$sorted = microtime(true);
printf("sort: %f s\n", ($sorted-$inited));
usort( $dummy2, 'strcmp' );
$sorted2 = microtime(true);
printf("sort: %f s\n", ($sorted2-$sorted));
With the following results:
init: 0.359329 s // for generating 40000 * 3 random strings and setup
sort: 1.012096 s // for the TestTokenGroup::compare
sort: 0.120583 s // for the 'strcmp' compare
And, running time sort sort-data.txt > /dev/null yields
.052 u (user-time, in seconds).
optimisation 1: remove array copies
replacing ->getTokens() with ->tokens yields (I'll only list the TestTokenGroup::compare results):
sort: 0.832794 s
Optimisation 2: remove redundant array() in min
Changing the $minlength line to:
$minLength = min(count($tokenGroup1->tokens), count($tokenGroup2->tokens));
gives
sort: 0.779134 s
Optimisation 3: Only call count once for each tokenGroup
$count1 = count($tokenGroup1->tokens);
$count2 = count($tokenGroup2->tokens);
$minLength = min($count1, $count2);
$equalLengths = ($count1 == $count2);
gives
sort: 0.679649 s
Alternative approach
The fastest sort so far is strcmp( $stringarray, 'strcmp' ): 0.12s - still twice as slow as GNU sort, but the latter only does one thing, and does it well.
So, to sort the TokenGroups efficiently we need to construct sort key consisting of a simple string. We can use \0 as a delimiter for the tokens, and we don't have to worry about them being equal length, because as soon as one character is different, the compare aborts.
Here's the implementation:
$arr2 = [];
foreach ( $array as $o )
$arr2[ implode("\0", $o->getTokens() ) ] = $o;
$init2 = microtime(true);
printf("init2: %f s\n", ($init2-$sorted2));
uksort( $arr2, 'strcmp' );
$sorted3 = microtime(true);
printf("sort: %f s\n", ($sorted3-$init2));
and here the results:
init2: 0.125939 s
sort: 0.104717 s
Related
I need to convert Excel coordinates (for example "AD45") into X=30 and Y=45 positions in integers.
I have this snippet of PHP code:
/**
* #param String $coordinates
*
* #return array
*/
public function getCoordinatesPositions($coordinates) {
$letters = preg_replace('/[^a-zA-Z]/', '', $coordinates);
$numbers = preg_replace('/[^0-9]/', '', $coordinates);
$letters = strtoupper($letters);
$columnCoordinate = 0;
$alphabetIterate = 0;
$alphabetRange = range('A', 'Z');
$alphabetCount = count($alphabetRange);
$splittedLetters = str_split($letters);
$lettersCount = count($splittedLetters);
$i = 1;
if ($lettersCount === 1) {
$columnCoordinate = array_search($splittedLetters[0], $alphabetRange) + 1;
} else {
foreach ($splittedLetters as $letter) {
if ($i !== $lettersCount) {
$position = (array_search($letter, $alphabetRange) + 1) * $alphabetCount;
} else {
$position = (array_search($letter, $alphabetRange) + 1);
}
$columnCoordinate += $position;
$i++;
}
}
return array('column' => $columnCoordinate, 'row' => $numbers);
}
My problem is, that this function is not returning correct column value if you pass coordinates with 3 or more letters ("ABC45"). And my colleague said, that this algorithm is also poor performance.
Do you have any ideas for simpler and better performance algorithm? Thank you.
In principle the algorithm is fine. You can simplify it and make it more general this way:
function getCoordinatesPositions($coordinates) {
$letters = preg_replace('/[^a-zA-Z]/', '', $coordinates);
$numbers = preg_replace('/[^0-9]/', '', $coordinates);
$letters = strtoupper($letters);
$alphabetRange = range('A', 'Z');
$alphabetCount = count($alphabetRange);
$splittedLetters = str_split($letters);
$lettersCount = count($splittedLetters);
$columnCoordinate = 0;
$i = 1;
foreach ($splittedLetters as $letter) {
$columnCoordinate += (array_search($letter, $alphabetRange) + 1) * pow($alphabetCount, $lettersCount - $i);
$i++;
}
return array('column' => $columnCoordinate, 'row' => intval($numbers));
}
var_dump(getCoordinatesPositions("ABC456"));
For PHPExcel see PHPExcel how to get column index from cell.
The #Axel Richter's answer is a good solution and works fine, but it may be improved to:
Secure against wrong coordinates.
Reduce code.
And probably increase performance.
Here is the proposed version:
function getCoordinatesPositions($coordinates) {
if (preg_match('/^([a-z]+)(\d+)$/i', $coordinates, $matches)) {
$level = strlen($matches[1]);
$matches[1] = array_reduce(
str_split(strtoupper($matches[1])),
function($result, $letter) use (&$level) {
return $result + (ord($letter) - 64) * pow(26, --$level);
}
);
return array_splice($matches, 1);
}
// (returns NULL when wrong $coordinates)
}
Using the initial preg_match() ensures to avoid working with wrong coordinates, and directly extracts the column part into $matches['1'].
Now the main improvement is to use ord($letter) to compute the letter's individual value: it avoids creating a temporary array of range('A', 'Z'), and simplifies the evaluation.
Then array_reduce() allows more compact processing of the column part, which is modified in situ, so the final return is also simplified as a simple part of the intermediary $matches.
I would like to make a function that is able to generate a list of letters and optional numbers using a-z,0-9.
$output = array();
foreach(range('a','z') as $i) {
foreach(range('a','z') as $j) {
foreach(range('a','z') as $k) {
$output[] =$i.$j.$k;
}
}
}
Thanks
example:
myfunction($include, $length)
usage something like this:
myfunction('a..z,0..9', 3);
output:
000
001
...
aaa
aab
...
zzz
The output would have every possible combination of the letters, and numbers.
Setting the stage
First, a function that expands strings like "0..9" to "0123456789" using range:
function expand_pattern($pattern) {
$bias = 0;
$flags = PREG_SET_ORDER | PREG_OFFSET_CAPTURE;
preg_match_all('/(.)\.\.(.)/', $pattern, $matches, $flags);
foreach ($matches as $match) {
$range = implode('', range($match[1][0], $match[2][0]));
$pattern = substr_replace(
$pattern,
$range,
$bias + $match[1][1],
$match[2][1] - $match[1][1] + 1);
$bias += strlen($range) - 4; // 4 == length of "X..Y"
}
return $pattern;
}
It handles any number of expandable patterns and takes care to preserve their position inside your source string, so for example
expand_pattern('abc0..4def5..9')
will return "abc01234def56789".
Calculating the result all at once
Now that we can do this expansion easily, here's a function that calculates cartesian products given a string of allowed characters and a length:
function cartesian($pattern, $length) {
$choices = strlen($pattern);
$indexes = array_fill(0, $length, 0);
$results = array();
$resets = 0;
while ($resets != $length) {
$result = '';
for ($i = 0; $i < $length; ++$i) {
$result .= $pattern[$indexes[$i]];
}
$results[] = $result;
$resets = 0;
for ($i = $length - 1; $i >= 0 && ++$indexes[$i] == $choices; --$i) {
$indexes[$i] = 0;
++$resets;
}
}
return $results;
}
So for example, to get the output described in the question you would do
$options = cartesian(expand_pattern('a..z0..9'), 3);
See it in action (I limited the expansion length to 2 so that the output doesn't explode).
Generating the result on the fly
Since the result set can be extremely large (it grows exponentially with $length), producing it all at once can turn out to be prohibitive. In that case it is possible to rewrite the code so that it returns each value in turn (iterator-style), which has become super easy with PHP 5.5 because of generators:
function cartesian($pattern, $length) {
$choices = strlen($pattern);
$indexes = array_fill(0, $length, 0);
$resets = 0;
while ($resets != $length) {
$result = '';
for ($i = 0; $i < $length; ++$i) {
$result .= $pattern[$indexes[$i]];
}
yield $result;
$resets = 0;
for ($i = $length - 1; $i >= 0 && ++$indexes[$i] == $choices; --$i) {
$indexes[$i] = 0;
++$resets;
}
}
}
See it in action.
See this answer for a code that produces all possible combinations:
https://stackoverflow.com/a/8567199/1800369
You just need to add the $length parameter to limit the combinations size.
You can use a recursive function
assuming you mean it can be any number of levels deep, you can use a recursive function to generate an array of the permutations e.g.:
/**
* take the range of characters, and generate an array of all permutations
*
* #param array $range range of characters to itterate over
* #param array $array input array - operated on by reference
* #param int $depth how many chars to put in the resultant array should be
* #param int $currentDepth internal variable to track how nested the current call is
* #param string $prefix internal variable to know what to prefix the current string with
* #return array permutations
*/
function foo($range, &$array, $depth = 1, $currentDepth = 0, $prefix = "") {
$start = !$currentDepth;
$currentDepth++;
if ($currentDepth > $depth) {
return;
}
foreach($range as $char) {
if ($currentDepth === $depth) {
$array[] = $prefix . $char;
continue;
}
foo($range, $array, $depth, $currentDepth, $prefix . $char);
}
if ($start) {
return $array;
}
With the above function, initialize the return variable and call it:
$return = array();
echo implode(foo(range('a', 'z'), $return, 3), "\n");
And you're output will be all three char combinations from aaa, to zzz:
aaa
aab
...
zzy
zzz
The numeric parameter determins how recursive the function is:
$return = array();
echo implode(foo(range('a', 'z'), $return, 1), "\n");
a
b
c
...
Here's a live example.
$number= range(0, 9);
$letters = range('a', 'z');
$array= array_merge($number, $letters);
//print_r($array);
for($a=0;$a<count($array);$a++){
for($b=0;$b<count($array);$b++){
for($c=0;$c<count($array);$c++){
echo $array[$a].$array[$b].$array[$c]."<br>";
}
}
}
tested and working :)
I have collections of numbers (arbitrary order) to store.
psuedocode:
id_a:[3,5,7,11]
id_x:[3,5,10,21]
id_b:[12,24,25,26]
etc.
I need to be able to search through all the collections and return the group_IDs.
For example, if I look up 5, I should get back ['id_a','id_x']. I want to do this efficiently with some sort of mapping, not by looping through all numbers of all collections. I also want to be able to map directly to each key and get back the collection (e.g., 'id_x' returns [3,5,10,21]) ; again I prefer this be done efficiently without looping through the keys.
edit:
I could use the numbers as the keys and efficiently get back 'id_'. Or, I could go the other way and use 'id_' as keys and efficiently get back the array of numbers. However, I want to be able to go efficiently in both directions. I guess I could maintain two arrays, but that seems messy.
Your examples all show the array values in sorted order. If they are always in sorted order, then you can use a binary search to find known values. This code:
function binarySearch($needle, array $haystack) {
$high = count($haystack) - 1;
$low = 0;
$mid = false;
while ($high >= $low) {
$mid = ($high + $low) >> 1;
$t = $needle - $haystack[$mid];
if ($t < 0) {
$high = $mid - 1;
} elseif ($t > 0) {
$low = $mid + 1;
} else {
return $mid;
}
}
return $mid;
}
function searchArrays($needle) {
static $id_a = array(3,5,7,11);
static $id_x = array(3,5,10,21);
static $id_b = array(12,24,25,26);
static $arrayNames = array('id_a', 'id_x', 'id_b');
$rv = array();
foreach ($arrayNames as $arrayName) {
$array = $$arrayName;
$index = binarySearch($needle, $array);
if ($array[$index] == $needle) {
$rv[] = $arrayName;
}
}
return $rv;
}
$needles = range(3,8);
foreach ($needles as $needle) {
$result = searchArrays($needle);
printf("searchArrays(%s)=%s\n", $needle, join(', ', $result));
}
will output the following:
searchArrays(3)=id_a, id_x
searchArrays(4)=
searchArrays(5)=id_a, id_x
searchArrays(6)=
searchArrays(7)=id_a
searchArrays(8)=
I've recently send my CV to one company that was hiring PHP developers. They send me back a task to solve, to mesure if I'm experienced enough.
The task goes like that:
You have an array with 10k unique elements, sorted descendant. Write function that generates this array and next write three different functions which inserts new element into array, in the way that after insert array still will be sorted descendant. Write some code to measure speed of those functions. You can't use PHP sorting functions.
So I've wrote function to generate array and four functions to insert new element to array.
/********** Generating array (because use of range() was to simple :)): *************/
function generateSortedArray($start = 300000, $elementsNum = 10000, $dev = 30){
$arr = array();
for($i = 1; $i <= $elementsNum; $i++){
$rand = mt_rand(1, $dev);
$start -= $rand;
$arr[] = $start;
}
return $arr;
}
/********************** Four insert functions: **************************/
// for loop, and array copying
function insert1(&$arr, $elem){
if(empty($arr)){
$arr[] = $elem;
return true;
}
$c = count($arr);
$lastIndex = $c - 1;
$tmp = array();
$inserted = false;
for($i = 0; $i < $c; $i++){
if(!$inserted && $arr[$i] <= $elem){
$tmp[] = $elem;
$inserted = true;
}
$tmp[] = $arr[$i];
if($lastIndex == $i && !$inserted) $tmp[] = $elem;
}
$arr = $tmp;
return true;
}
// new element inserted at the end of array
// and moved up until correct place
function insert2(&$arr, $elem){
$c = count($arr);
array_push($arr, $elem);
for($i = $c; $i > 0; $i--){
if($arr[$i - 1] >= $arr[$i]) break;
$tmp = $arr[$i - 1];
$arr[$i - 1] = $arr[$i];
$arr[$i] = $tmp;
}
return true;
}
// binary search for correct place + array_splice() to insert element
function insert3(&$arr, $elem){
$startIndex = 0;
$stopIndex = count($arr) - 1;
$middle = 0;
while($startIndex < $stopIndex){
$middle = ceil(($stopIndex + $startIndex) / 2);
if($elem > $arr[$middle]){
$stopIndex = $middle - 1;
}else if($elem <= $arr[$middle]){
$startIndex = $middle;
}
}
$offset = $elem >= $arr[$startIndex] ? $startIndex : $startIndex + 1;
array_splice($arr, $offset, 0, array($elem));
}
// for loop to find correct place + array_splice() to insert
function insert4(&$arr, $elem){
$c = count($arr);
$inserted = false;
for($i = 0; $i < $c; $i++){
if($elem >= $arr[$i]){
array_splice($arr, $i, 0, array($elem));
$inserted = true;
break;
}
}
if(!$inserted) $arr[] = $elem;
return true;
}
/*********************** Speed tests: *************************/
// check if array is sorted descending
function checkIfArrayCorrect($arr, $expectedCount = null){
$c = count($arr);
if(isset($expectedCount) && $c != $expectedCount) return false;
$correct = true;
for($i = 0; $i < $c - 1; $i++){
if(!isset($arr[$i + 1]) || $arr[$i] < $arr[$i + 1]){
$correct = false;
break;
}
}
return $correct;
}
// claculates microtimetime diff
function timeDiff($startTime){
$diff = microtime(true) - $startTime;
return $diff;
}
// prints formatted execution time info
function showTime($func, $time){
printf("Execution time of %s(): %01.7f s\n", $func, $time);
}
// generated elements num
$elementsNum = 10000;
// generate starting point
$start = 300000;
// generated elements random range 1 - $dev
$dev = 50;
echo "Generating array with descending order, $elementsNum elements, begining from $start\n";
$startTime = microtime(true);
$arr = generateSortedArray($start, $elementsNum, $dev);
showTime('generateSortedArray', timeDiff($startTime));
$step = 2;
echo "Generating second array using range range(), $elementsNum elements, begining from $start, step $step\n";
$startTime = microtime(true);
$arr2 = range($start, $start - $elementsNum * $step, $step);
showTime('range', timeDiff($startTime));
echo "Checking if array is correct\n";
$startTime = microtime(true);
$sorted = checkIfArrayCorrect($arr, $elementsNum);
showTime('checkIfArrayCorrect', timeDiff($startTime));
if(!$sorted) die("Array is not in descending order!\n");
echo "Array OK\n";
$toInsert = array();
// number of elements to insert from every range
$randElementNum = 20;
// some ranges of elements to insert near begining, middle and end of generated array
// start value => end value
$ranges = array(
300000 => 280000,
160000 => 140000,
30000 => 0,
);
foreach($ranges as $from => $to){
$values = array();
echo "Generating $randElementNum random elements from range [$from - $to] to insert\n";
while(count($values) < $randElementNum){
$values[mt_rand($from, $to)] = 1;
}
$toInsert = array_merge($toInsert, array_keys($values));
}
// some elements to insert on begining and end of array
array_push($toInsert, 310000);
array_push($toInsert, -1000);
echo "Generated elements: \n";
for($i = 0; $i < count($toInsert); $i++){
if($i > 0 && $i % 5 == 0) echo "\n";
printf("%8d, ", $toInsert[$i]);
if($i == count($toInsert) - 1) echo "\n";
}
// functions to test
$toTest = array('insert1' => null, 'insert2' => null, 'insert3' => null, 'insert4' => null);
foreach($toTest as $func => &$time){
echo "\n\n================== Testing speed of $func() ======================\n\n";
$tmpArr = $arr;
$startTime = microtime(true);
for($i = 0; $i < count($toInsert); $i++){
$func($tmpArr, $toInsert[$i]);
}
$time = timeDiff($startTime, 'checkIfArraySorted');
showTime($func, $time);
echo "Checking if after using $func() array is still correct: \n";
if(!checkIfArrayCorrect($tmpArr, count($arr) + count($toInsert))){
echo "Array INCORRECT!\n\n";
}else{
echo "Array OK!\n\n";
}
echo "Few elements from begining of array:\n";
print_r(array_slice($tmpArr, 0, 5));
echo "Few elements from end of array:\n";
print_r(array_slice($tmpArr, -5));
//echo "\n================== Finished testing $func() ======================\n\n";
}
echo "\n\n================== Functions time summary ======================\n\n";
print_r($toTest);
Results can be found here: http://ideone.com/1xQ3T
Unfortunately I was rated only 13 points out of 30 for this task (don't know how it was calculated or what exactly was taken in account). I can only assume that's because there are better ways to insert new element into sorted array in PHP. I'm searching this topic for some time now but couldn't find anything good. Maby you know of better approach or some articles about that topic?
Btw on my localhost (PHP 5.3.6-13ubuntu3.6 with Suhosin-Patch, AMD Athlon(tm) II X4 620) insert2() is fastest, but on ideone (PHP 5.2.11) insert3() is fastest.
Any ideas why? I suppose that array_splice() is tuned up somehow :).
//EDIT
Yesterday I thought about it again, and figured out the better way to do inserts. If you only need sorted structure and a way to iterate over it and your primary concern is the speed of insert operation, than the best choise would be using SplMaxHeap class. In SplMaxHeap class inserts are damn fast :) I've modified my script to show how fast inserts are. Code is here: http://ideone.com/vfX98 (ideone has php 5.2 so there won't be SplMaxHeap class)
On my localhost I get results like that:
================== Functions time summary ======================
insert1() => 0.5983521938
insert2() => 0.2605950832
insert3() => 0.3288729191
insert4() => 0.3288729191
SplMaxHeap::insert() => 0.0000801086
It may just be me, but maybe they were looking for readability and maintainability as well?
I mean, you're naming your variables $arr, and $c and $middle, without even bothering to place proper documentation.
Example:
/**
* generateSortedArray() Function to generate a descending sorted array
*
* #param int $start Beginning with this number
* #param int $elementsNum Number of elements in array
* #param int $dev Maximum difference between elements
* #return array Sorted descending array.
*/
function generateSortedArray($start = 300000, $elementsNum = 10000, $dev = 30) {
$arr = array(); #Variable definition
for ($i = 1; $i <= $elementsNum; $i++) {
$rand = mt_rand(1, $dev); #Generate a random number
$start -= $rand; #Substract from initial value
$arr[] = $start; #Push to array
}
return $arr;
}
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.
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;
}
}
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);
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);
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.