3

It should quite simple algorithm, but I just can't get around it.

I have some arrays in alphabetical order

[0] => Array
    (
        [0] => a
        [1] => b
        [2] => c
    )

and for example

[0] => Array
    (
        [0] => a
        [1] => b
        [2] => c
        [3] => d
    )

and I need to sort them into rows. For example:

I should receive a table with 3 columns and as many rows as it may get and it should be in alphabetical order.

Here is an example: First array should be converted into

[0] => Array
    (
        [0] => Array
            (
                [0] => a
                [1] => b
                [2] => c
            )

    )

But second one should be as

[1] => Array
    (
        [0] => Array
            (
                [0] => a
                [1] => c
                [2] => d
            )
        [1] => Array
            (
                [0] => b
            )
    )

I'm writing it in php, so if anyone can help I would be really appreciated.

UPD: Code example:

function sortAsOrdered( array $categories )
{
    foreach ( $categories as $groupId => $group )
    {
        $regroupMenuItems = array();
        $limit            = count( $group );
        $rows             = ceil( $limit / 3 );

        for ( $i = 0; $i < $rows; ++$i )
        {
            $jumper = 0;

            for ( $j = 0; $j < 3; $j++ )
            {
                if ( 0 == $jumper )
                {
                    $jumper = $i;
                }

                if ( isset( $group[ $jumper ] ) )
                {
                    $regroupMenuItems[ $i ][ $j ] = $group[ $jumper ];
                }

                $jumper = $jumper + $rows;
            }
        }

        $categories[ $groupId ] = $regroupMenuItems;
    }

    return $categories;
}

Guys I solved this one. Here you could see my algorithm http://pastebin.com/xe2yjhYW. But don't be sad your help will not go in vain. I probably will place bounty just for those who helped with this dificult algorithm for me.

Guys thanks one more time. Your thoughts inspired me to think differently.

Eugene
  • 4,352
  • 8
  • 55
  • 79
  • 1
    In you second result they are no longer sorted in alphabetical order, what's the criteria for breaking the sort order? – Marcus Oct 18 '11 at 08:59
  • it seems he wants columns to be sorted alphavetically – Your Common Sense Oct 18 '11 at 09:01
  • I think he's wanting this sorted in columns, then rows – Paul Dixon Oct 18 '11 at 09:02
  • @Marcus They are if you place them as columns on top of each other. – Eugene Oct 18 '11 at 09:06
  • @Col. Shrapnel, Paul Dixon Exactly. – Eugene Oct 18 '11 at 09:07
  • Is the criteria only to divide the array into rows and columns – andho Oct 18 '11 at 09:10
  • Possible duplicate of or at least very related to [How do I distribute values of an array in three columns?](http://stackoverflow.com/questions/6450810/how-do-i-distribute-values-of-an-array-in-three-columns) – deceze Oct 18 '11 at 09:10
  • Or do you want to sort the data first and then divide into rows and columns – andho Oct 18 '11 at 09:10
  • @andho I have already sorted data in array now I only need to divide it as shown above, but alphabetical order should remain in columns if there are any. – Eugene Oct 18 '11 at 09:14
  • @deceze It looks correct, but problem comes up same way as in my algorithm when you try with 4 elements. – Eugene Oct 18 '11 at 09:27
  • @Eugene what happens if you have, abcde, does d then end up below c, and e in the 3rd column? and then further what about abcdefgh, would the 3 columns be [abc][def][gh]? – Ben Oct 18 '11 at 09:31
  • @there is too much code here for the purpose – andho Oct 18 '11 at 09:37
  • So Omeid Herat got it right? right? – andho Oct 18 '11 at 14:22
  • @andho Not quite. I explained it in comment on his answer. You could view my solution to see what I meant. – Eugene Oct 18 '11 at 18:16
  • @Eugene, have you tried my solution? It seems to output exactly what you want with the testing I've done. – Ben Oct 18 '11 at 22:32
  • tried your solution with `sortAsOrdered(array('a', 'b', 'g', 'h'));` returns `array( [0] => array( [0] => array( [0] => 'a' ) ), [1] => array( [0] => array( [0] => 'b' ) ), ...` Is the usage right here? – andho Oct 19 '11 at 07:51
  • also tried with `sortAsOrdered(array(array('a', 'b', 'g', 'h')));` returns `array( [0] => array( [0] => array( [0]=>'a', [1]=>'g'), [1] => array( [0]=>'b', [1]=>'h' ) ) )` – andho Oct 19 '11 at 07:54
  • Related, but different requirements: https://stackoverflow.com/a/53130329/2943403 – mickmackusa Aug 28 '21 at 07:43

8 Answers8

1

array_chunk() wold have been the solution but as you want it to be specially sorted, that wouldn't help you much.

So here is my five cents:

function array_chunk_vertical($input, $size_max) {
    $chunks = array();
    $chunk_count = ceil(count($input) / $size_max);

    $chunk_index = 0;
    foreach ($input as $key => $value) {
        $chunks[$chunk_index][$key] = $value;

        if (++$chunk_index == $chunk_count) {
            $chunk_index = 0;
        }
    }

    return $chunks;
}


$array = array('a', 'b', 'c', 'd', 'e', 'f');
var_dump(array_chunk_vertical($array, 2));

Which will give you:

array
  0 => 
    array
      0 => string 'a' (length=1)
      3 => string 'd' (length=1)
  1 => 
    array
      1 => string 'b' (length=1)
      4 => string 'e' (length=1)
  2 => 
    array
      2 => string 'c' (length=1)
      5 => string 'f' (length=1)

The downside of this function is that you can only tell the max number of elements in a chunk, and then it equally divides the array to chunks. So for [4] and max_size 3 you will get [2,2] unlike the expected [3,1].

  • The output expected here should be [ab] in first column, [cd] 2nd, [ef] in the 3rd. It looks like your solution is doing [abc] in the first row, and [def] in the 2nd. – Ben Oct 18 '11 at 14:04
  • @Ben: well, he's question is a bit confusing, but if he wants the way you said, `array_chunk()` should work just fine. –  Oct 18 '11 at 14:10
  • Not quite, sorry. If you try your algorithm with such array `array( 'a', 'b', 'g', 'h' )`, you will get as the result `array( [0] => array( 'a', 'g', ), [1] => array( 'b', 'h', ), )`, but I would need it to be `array( [0] => array( 'a', 'g', 'h', ), [1] => array( 'b', ), )` – Eugene Oct 18 '11 at 17:36
  • Sorry, the algorithm is not smart-enough for sake of being simple and fast, I re-factored the code to reflect that. I will add a more smarter one later as soon as I get inspired for something smart. –  Oct 19 '11 at 01:11
  • His function accepts a 2nd parameter indicating how many columns there should be. If you call `array_chunk_vertical($array, 3));` @Eugene, your desired output comes. – andho Oct 19 '11 at 07:02
  • You can try solution, that I added in my question at the bottom. There is a link to pastebin. – Eugene Oct 19 '11 at 09:20
0

Let's see if this is nearer the mark

function splitVerticalArrayIntoColumns($aInput, $iNumberOfColumns) {

  //output array
  $aOutput = array();

  //the total length of the input array
  $iInputLength = count($aInput);

  //the number of rows will be ceil($iInputLength / $iNumberOfColumns)
  $iNumRows = ceil($iInputLength / $iNumberOfColumns);

  for($iInputIndex = 0; $iInputIndex < $iInputLength; $iInputIndex++) {
    $iCurrentRow = $iInputIndex % $iNumRows;
    $aOutput[$iCurrentRow][] = $aInput[$iInputIndex];
  }

  //return
  return $aOutput;
}

Which - when run thus:

$aList = array("a", "e", "d", "b", "c");
echo 'array("a", "e", "d", "b", "c")' . "\n\n";
print_r(splitVerticalArrayIntoColumns($aList, 3));

Gives:

array("a", "e", "d", "b", "c")

Array
(
    [0] => Array
        (
            [0] => a
            [1] => d
            [2] => c
        )

    [1] => Array
        (
            [0] => e
            [1] => b
        )

)

That's not sorting each row yet but is that the kind of thing you're after?



begin facepalm edit

... or of course, array_chunk($aList, 3) after you've sorted it O_o

http://uk3.php.net/manual/en/function.array-chunk.php

I'll leave everything below for reference or whatever - I'd completely forgotten about array_chunk()

end facepalm edit


I'd use a modulo in a loop where you're counting the array index (after sorting the array) - for instance if you're trying to split an array into 3 "columns" you could try something like:

if($iIndex % 3 == 0) {
  //... create a new array
}
else {
  //... add to an existing array
}

EDIT code example:

$aList = array("a", "e", "d", "b", "c");
sort($aList);

$iDesiredNumberOfColumns = 3;
$iListLength = count($aList);

$aListInColumns = array();
$iRowNumber = 0;

for($iIndex = 0; $iIndex < $iListLength; $iIndex++) {
  $iColumnNumber = $iIndex % 3;
  if($iIndex != 0 && $iColumnNumber == 0) {
    $iRowNumber++;
  }

  $aListInColumns[$iRowNumber][$iColumnNumber] = $aList[$iIndex];
}

Just ran it on my local server (and corrected the typo), and it outputs as:

Array
(
    [0] => Array
        (
            [0] => a
            [1] => b
            [2] => c
        )

    [1] => Array
        (
            [0] => d
            [1] => e
        )

)

There's probably a tidier way of doing it (that's a little procedural) but it should do the job.

CD001
  • 8,332
  • 3
  • 24
  • 28
  • I had such result long ago. I need it to look like http://pastebin.com/MYZWsevq. If you place each row as row, then you will see, that in columns they stay in alphabetical order. :) – Eugene Oct 18 '11 at 09:46
  • `array_chunk` would be great solution, but when i try it with `array( 'a', 'b', 'c', 'd', 'e' )`, then I can set only how many rows will there be. I need to distribute in another structure. Not as ('a', 'b'), ('c', 'd'), ('e'), but as ('a', 'c', 'e'), ('b', 'd'). I hope you see my point. – Eugene Oct 18 '11 at 09:58
  • Ahhh - I think I get it now, your array is `array(r0c0, r1c0, r0c1, r1c1, r0c2)` where r is the row index and c is the column index so it sort of goes vertically THEN horizontally... – CD001 Oct 18 '11 at 10:11
0
<?php

$five_el = array('a', 'b', 'c', 'd', 'e');

$two_el = array('a', 'b');

$three_el = array('a', 'b', 'c');

$six_el = array('a', 'b', 'c', 'd', 'e', 'f');

function multid($sorted_array) {
    $mulidarray = array();
    $row = 0;
    $column = 0;
    foreach ($sorted_array as $value) {
        if ($column == 3) {
            $row++;
        }
        $column++;
        if (!isset($mulidarray[$row])) {
            $mulidarray[$row] = array();
        }
        $multidarray[$row][] = $value;
    }

    return $multidarray;
}

var_dump(multid($five_el));
var_dump(multid($two_el));
var_dump(multid($three_el));
var_dump(multid($six_el));
andho
  • 1,166
  • 1
  • 15
  • 27
  • Try `array( 'a', 'b', 'c', 'd' )` and show the result if you can. I would need `array( [0] => array( 'a', 'c', 'd' ), [1] => array( 'b' ) )`. – Eugene Oct 18 '11 at 10:15
  • the answer comes as `array( [0] => array( 'a', 'b', 'c'), [1] => array( 'd' ) )` – andho Oct 18 '11 at 13:34
  • Oh hehe, I think I didn't see the question very well. Sorry! – andho Oct 18 '11 at 13:36
0

array_chunk is a natural first approach to the problem, but it won't do exactly what you need to. If the solution is provided that way, you need to either restructure the resulting array or restructure the input before processing it, as below:

$input = range('a', 'k');  // arbitrary
$columns = 3;  // configure this
$rows = ceil(count($input) / $columns);

// fugly, but this way it works without declaring a function
// and also in PHP < 5.3 (on 5.3 you'd use a lambda instead)
$order = create_function('$i',
                         '$row = (int)($i / '.$rows.');'.
                         '$col = $i % '.$rows.';'.
                         'return $col * ('.$columns.' + 1) + $row;');

// $order is designed to get the index of an item in the original array,
// and produce the index that item would have if the items appeared in
// column-major order instead of row-major as they appear now
$array = array_map($order, array_keys($input));

// replace the old keys with the new ones
$array = array_combine($array, $input);

// sort based on the new keys; this will effectively transpose the matrix,
// if it were already structured as a matrix instead of a single-dimensional array
ksort($array);

// done!
$array = array_chunk($array, $columns);
print_r($array);

See it in action.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • unfortunately this doesn't work for range('a','d') - he's expecting (a,c,d)(b) - your script gives (a,c,b)(d) :( – Ben Oct 18 '11 at 10:46
0

YAYAYAY!! I've got it. You could turn this into a function if you'll be doing it regularly.

# Here we setup our array and the number of columns we want.
$myArray = range('a','d');  
$numCols = 3;

# Here we break ourselves up into columns
for ($i = 0; $i < $numCols; $i++) {
    $numRows = ceil(count($myArray) / ($numCols - $i));
    $columns[$i] = array_slice($myArray,0,$numRows);
    $myArray = array_slice($myArray,$numRows);
}

# Here we transpose our array to be in rows instead of columns.
for ($i = 0; $i < $numCols; $i++) {
    for ($j = 0; $j < count($columns[$i]); $j++) {
        $rows[$j][$i] = $columns[$i][$j];
    }
}

# Our rows are now in $rows
var_dump($rows);

The output from this is:

array(2) {
  [0]=>
  array(3) {
    [0]=>
    string(1) "a"
    [1]=>
    string(1) "c"
    [2]=>
    string(1) "d"
  }
  [1]=>
  array(1) {
    [0]=>
    string(1) "b"
  }
}
Ben
  • 1,382
  • 10
  • 14
0

How about:

$arrs = array(
    array('a','b','c'),
    array('a','b','c','d'),
    array('a','b','c','d','e'),
    array('a','b','c','d','e','f'),
    array('a','b','c','d','e','f','g')
);
$nbcols = 3;
foreach ($arrs as $arr) {
    $arr_size = count($arr);
    $nblines = ceil($arr_size/$nbcols);
    $res = array();
    $l = 0;
    foreach ($arr as $el) {
        if ($l == $arr_size - 1 && count($res[0]) < $nbcols) $l=0;
        $res[$l%$nblines][] = $el;
        $l++;
    }
    print_r($res);
}

output:

Array
(
    [0] => Array
        (
            [0] => a
            [1] => b
            [2] => c
        )

)
Array
(
    [0] => Array
        (
            [0] => a
            [1] => c
            [2] => d
        )

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

)
Array
(
    [0] => Array
        (
            [0] => a
            [1] => c
            [2] => e
        )

    [1] => Array
        (
            [0] => b
            [1] => d
        )

)
Array
(
    [0] => Array
        (
            [0] => a
            [1] => c
            [2] => e
        )

    [1] => Array
        (
            [0] => b
            [1] => d
            [2] => f
        )

)
Array
(
    [0] => Array
        (
            [0] => a
            [1] => d
            [2] => g
        )

    [1] => Array
        (
            [0] => b
            [1] => e
        )

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

)
Toto
  • 89,455
  • 62
  • 89
  • 125
  • nice one. slightly different output to mine with a-g. Without further response from @Eugene it's hard to know how he wants it formatted with more than 6 items. – Ben Oct 18 '11 at 11:51
0

In order to do this, you need to do two operations:

First, split the array into 3 groups, as evenly as possible.

function array_grouped($arr, $group_count)
{
    if (!count($arr)) return array();
    $result = array();
    for ($i = $group_count; $i > 0; --$i)
    {
        # break off the next ceil(remaining count / remaining columns) elements
        # (avoiding FP math, cause that way lies madness)
        $result[] = array_splice($arr, 0, ((count($arr)-1) / $i) + 1);
    }
    return $result;
}

Then, "transpose" the array, so that rows and columns switch places.

function array_transposed($arr)
{
    $result = array();
    foreach ($arr as $x => $subarr)
    {
        foreach ($subarr as $y => $val)
        {
            if (!isset($result[$y])) $result[$y] = array();
            $result[$y][$x] = $val;
        }
    }
    return $result;
}

array_transposed(array_grouped($arr, 3)) gives you entries in the order you want them.

cHao
  • 84,970
  • 20
  • 145
  • 172
0

If to say it shortly, then here is a method for that algorithm.

/**
 * @param array $toTransform
 * @param int $columnsMax
 * @return array
 */
private function transformation( array $toTransform, $columnsMax = 3 )
{
    // First divide array as you need
    $listlen   = count( $toTransform );
    $partlen   = floor( $listlen / $columnsMax );
    $partrem   = $listlen % $columnsMax;
    $partition = array();
    $mark      = 0;

    for ( $px = 0; $px < $columnsMax; $px++ )
    {
        $incr             = ( $px < $partrem ) ? $partlen + 1 : $partlen;
        $partition[ $px ] = array_slice( $toTransform, $mark, $incr );
        $mark             += $incr;
    }

    // Secondly fill empty slots for easy template use
    $result = array();

    for ( $i = 0; $i < count( $partition[0] ); $i++ )
    {
        $tmp = array();

        foreach ( $partition as $column )
        {
            if ( isset( $column[ $i ] ) )
            {
                $tmp[] = $column[ $i ];
            }
            else
            {
                $tmp[] = '';
            }
        }

        $result[] = $tmp;
    }

    return $result;
}

Also I included PHPUnit test for that. You can find it at, that link.

Eugene
  • 4,352
  • 8
  • 55
  • 79