-3

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],
]
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
Aditya Pratama
  • 657
  • 1
  • 8
  • 21
  • Why not 16, 16, 16 and 17 elements? Seems more balanced and 17 is not higher than (2 * 9) - 1. Or 11, 11, 11, 11, 11 and 10 elements? Problem description is ambiguous. – Robby Cornelissen Mar 14 '23 at 07:51
  • Smaller number of items for each array is prioritied – Aditya Pratama Mar 14 '23 at 07:56
  • Did you delete your earlier question where I asked you to provide multiple sample arrays and multiple factors so that we could see the required behavior? I also referred to [Split an Array into N Arrays - PHP](https://stackoverflow.com/q/15579702/2943403) – mickmackusa Mar 14 '23 at 10:54

2 Answers2

1

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;
    }
Aditya Pratama
  • 657
  • 1
  • 8
  • 21
  • A more balanced result would be 5 arrays of 13 given that your array length can be between `$min` and `2*$min-1`. If you say "small amount of items for each array is priorited" then it's pointless to use any array length other than `$min` and your other condition is meaningless. – Nick Mar 14 '23 at 11:30
1

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.

mickmackusa
  • 43,625
  • 12
  • 83
  • 136