skip to Main Content

I want to split an array into rows as evenly as possible, obeying a minimum count per row constraint so that row counts are as close as possible to the minimum and the difference between different row counts is never more than 1.

In other words, the number of elements in each row can’t be lower than $min or higher than (2 * $min – 1).

For example, I have a 65-element array and a minimum row size constraint of 9.

$x = range(1, 65);
$min = 9;

I want to split the array into rows with longer rows occurring before shorter rows.

[
    [ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10],
    [11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
    [21, 22, 23, 24, 25, 26, 27, 28, 29],
    [30, 31, 32, 33, 34, 35, 36, 37, 38],
    [39, 40, 41, 42, 43, 44, 45, 46, 47],
    [48, 49, 50, 51, 52, 53, 54, 55, 56],
    [57, 58, 59, 60, 61, 62, 63, 64, 65],
]

2

Answers


  1. Chosen as BEST ANSWER

    I found the answer after hours of experimenting :D

            // i.e. count($x) = 65, min = 9
            $total = (int)floor(count($x) / $min);     // 65 : 9 = 7 (rounded down)
            $sisa = count($x) % $min;                  // 65 mod 9 = 2 (leftover)
            
            $result = [];
            $endOffset = 0;
    
            for ($i = 0; $i < $total; $i++){
                if ($sisa > 0){                             // 2 > 0                // 1 > 0
                    $add = (int)floor($sisa / $total) + 1;  // (2 : 7) + 1 = 1      // (1 : 7) + 1 = 1
    
                    $length = $min + $add;                  // 9 + 1 = 10           // 9 + 1 = 10
                    $offset = $endOffset;                   // 0                    // 10
                    
                    $sisa = $sisa - $add;                   // 2 - 1 = 1            // 1 - 1 = 0
                } else {
                    $offset = $endOffset;                                                                   // 20                   // 29               etc.        // 56
                    $length = $min;                                                                         // 9                    // 9                etc.        // 9
                }
    
                $arr = array_slice(
                    $x, $offset, $length
                );                                          // [0-9]                // [10-19]              // [20-28]              // [29-37]          etc.        // [56-64]
                
                $endOffset = $offset + $length;             // 0 + 10 = 10          // 10 + 10 = 20         // 20 + 9 = 29          // 29 + 9 = 38      etc.        // 56 + 9 = 65
                $result[] = $arr;
            }
    
            return $result;
        }
    

  2. By performing the following calculations, an approach using array_splice() and array_chunk() can replace iterated processes.

    • total count of the input array ($count)
    • the total count divided by the number of times that the minimum number of elements as an integer ($maxRows)
    • the total count divided by $maxRows with the dividend rounded up ($maxColumns)
    • the remainder after dividing the total count by the $maxRows

    Code: (Demo)

    $count = count($array);                 // 65
    $maxRows = intdiv($count, $minColumns); // 7
    $maxColumns = ceil($count / $maxRows);  // 10
    $longRowsCount = $count % $maxRows;     // 2
    
    var_export([
        ...array_chunk(
            array_splice(
                $array,
                0,
                ($maxColumns * $longRowsCount) ?: $count
            ),
            $maxColumns
        ),
        ...array_chunk($array, $maxColumns - 1)
    ]);
    

    The two chunking attempts (either of which might produce an empty array) are merge together by unpacking their rows using spread operators (...) inside of an array. If preferred, array_merge() can be called instead of unpacking into an array.

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