I am trying trying to sort a list into columns with uksort.
The array already alpha sorted, so it is like array(\'A\',\'B\',\'C\',\'D\',\'E\',\'F\',\'G\',\'H\
@hakre has the correct code answer. The why:
The underlying sort function, Zend_qsort, does not actually reorder the elements and keys. Instead, it reorders the internal array buckets the zend engine uses. If you ksort a numerically indexed array, then iterate over with $q = count($array);for($i=0; $i<$q); $i++)
it will return the values exactly as before; if you iterate with for($key in $array)
you will get they new key ordering.
Setting up the following:
$array = range('A', 'M');
$columns = 4;
$length = count($array);
print_matrix($array, $columns);
Which outputs each member and it's key by index (row and colum) and as well the elements order on top:
One row - A B C D E F G H I J K L M
A[ 0] B[ 1] C[ 2] D[ 3]
E[ 4] F[ 5] G[ 6] H[ 7]
I[ 8] J[ 9] K[10] L[11]
M[12]
The javascript code linked could be easily converted to PHP. However, if you look closely to that question/answer, it becomes clear that it only work with full rows, like with my previous attempt:
function callback_sort($array, $columns)
{
$sort = function($columns)
{
return function($a, $b) use ($columns)
{
$bycol = ($a % $columns) - ($b % $columns);
return $bycol ? : $a - $b;
};
};
uksort($array, $sort(4));
return $array;
}
Output:
One row - A E I M B F J C G K D H L
A[ 0] E[ 4] I[ 8] M[12]
B[ 1] F[ 5] J[ 9] C[ 2]
G[ 6] K[10] D[ 3] H[ 7]
L[11]
So it's just that the function provided in the other question does not work.
But as the array is already sorted, you don't need to sort it again but just to change the order or elements. But which order? If the matrix is not complete e.g. n x n
fully filled, per each column, a different new index needs to be calculated. Taken the example with 13 elements (A-M
) gives you the following distribution of rows per column:
column: 1 2 3 4
rows: 4 3 3 3
So per each column, the value differs. For example at index 12, the 13th element is in the 4th row. On the way coming to that position, it has been passed 4 times through column 1 and 3 times in the other columns 2-4. So to get the virtual index of the iterated index, you need so sum how often you've been in each column to find out how many numbers in the original index you were going forward. If you go over the maximum number of members, you continue over at 0.
So this could be iteratively solved by stepping forward per each index to distribute the calculation over the indexes:
Index 0:
No column: 0
Index 1:
1x in column is which has 4 rows: 4
Index 2:
1x in column 1 (4 rows) and 1x in other columns (3 rows): 4 + 3
... and so on. If the virtual index goes over 12, it will start at 0, for example for the 5th Element (index 4) the virtual index would calculate 13:
Index 4:
1x 4 rows and 3x 3 rows = 13 (4 + 9)
13 > 12 => 1 (13 - 12)
Now filling a new array by starting with the virtual index 0
and giving the appropriate offset each time (look in which column you are, add the number of rows of that column, wrap around if necessary) will give the desired output:
One row - A E H K B F I L C G J M D
A[ 0] E[ 4] H[ 7] K[10]
B[ 1] F[ 5] I[ 8] L[11]
C[ 2] G[ 6] J[ 9] M[12]
D[ 3]
Written in code, that's a simple foreach
over the original indexes. By maintaining an index of keys as well, this works with any array, even those with string keys:
$floor = floor($length/$columns);
$modulo = $length % $columns;
$max = $length-1;
$virtual = 0;
$keys = array_keys($array);
$build = array();
foreach($keys as $index => $key)
{
$vkey = $keys[$virtual];
$build[$vkey] = $array[$vkey];
$virtual += $floor + ($index % $columns < $modulo);
($virtual>$max) && $virtual %= $max;
}
print_matrix($build, $columns);
And that's it: Demo, Gist.