Custom array_chunk to prioritize filling columns as much as possible
Asked Answered
S

19

14

I have an array which can have several items in it, e.g:

Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
etc

I need the quickest way to restructure this array so that it has at most X items. So if I say that X is 3, the resulting array must be:

Item 1 , Item 2
Item 3, Item 4
Item 5, Item 6
etc

or if it has 7 items, it would be:

Item 1 , Item 2, Item 3,
Item 4, Item 5, 
Item 6, Item 7

What's the easiest way to do this?

I started with this, but it seems like there really must be an easier way:

foreach ($addressParts as $part)
{
    if (empty($part)) continue;
    if (empty($addressLines[$count]))  $addressLines[$count] = '';
    $addressLines[$count] .= $part;
    $count++;
    if ($count > 2) $count = 0;
}

Also, this won't work, because you will end up with this:

item 1, item 4, item 7
item 2, item 5
item 3, item 6

... which is wrong. Any thoughts?

UPDATE

If I start with:

Array
(
    [0] => item 1
    [1] => item 2
    [2] => item 3
    [3] => item 4
    [4] => item 5
    [5] => item 6
    [6] => item 7
)

I want to end with:

Array
(
    [0] => item 1, item 2, item 3
    [1] => item 4, item 5
    [2] => item 6, item 7
)

Make sense?

Sharanshard answered 30/10, 2013 at 13:53 Comment(8)
array_chunk() perhaps? You just need to work out the chunk sizeAutocade
Quickest as in fastest executing, or least lines/complexity?Chromolithography
Least lines, easiest to read.Sharanshard
I'm sorry, this does not make any sense to me. I don't see the difference between the examples. X is a variable unsigned integer the array passed is of unknown length, and it has to be broken up sequentially following what rules exactly? if X=3 break into three subarrays containing an equal amount of elements, if X=7 break into... I don't get it. Either 3 and 7 are the only options available, and those 2 examples are the only result posible, or my brain is going to burn in derpCafard
@Cafard the two examples (wrong and good) differ in direction, the wrong one fills down first, the good one fills to the right, then down. The interesting part here is to know the length of each line befor starting to fill wich is kind of related to word wrapping for given line numbers instead of given line length.Blevins
I seem to be getting it... so if I have an array of length N, it should be broken into (how many sub arrays?) that have almost the same amount (preferably the same amount). So in your example I see 7 elements, these have to be broken into 3 subarrays but they shouldn't differ in as much as 1 element in length. Is this correct?Cafard
The output should be an array of 3 strings that each contain some items separated by ", " (as shown on your last output sample), or a two-dimensional array of individual items?Jackstraws
@coderama I can't believe I found 13 incorrect answers on this page! This page needs a spring clean.Entellus
B
5

This function combines elements to a new array as per your example. It handles any number of input elements.

function ReduceArray($input, $length) {
        $frac = $length / count($input);
        $frac = $frac + 0.0001;    // Offset for float calculations
        $index = 0.0;

        // Copy the elements, rolling over on $index
        $temp = array();
        foreach ($input as $part) {
                $i= floor($index);
                if (!isset($temp[$i])) {
                        $temp[$i] = array($part);
                } else {
                        $temp[$i][] = $part;
                }
                $index += $frac;
        }

        // Combine the sub arrays
        $output = array();
        foreach ($temp as $line) {
            $output[] = implode(', ', $line);
        }
        return $output;
}

$input = array('Item 1',  'Item 2',  'Item 3',  'Item 4',  'Item 5',  'Item 6', 'Item 7');
$output = ReduceArray($input, 3);
print_r($output);

Output

Array
(
    [0] => Item 1, Item 2, Item 3
    [1] => Item 4, Item 5
    [2] => Item 6, Item 7
)

Edit "fixed" the output as per the given output.

Edit See Comment for nine elements, tested for up to 12 elements. Thanks sectus

Blevins answered 30/10, 2013 at 14:13 Comment(9)
cant we use only else part? the output remains same when we use only else part.Vereeniging
@vishal Yes, you are then initialising the array on the fly. I like to avoid this in my code, even when this disqualifies me for 'least lines' ; )Blevins
Wrong result for 9 itemsMoria
D'uh thanks for commenting,, don't know how I missed that. They failed me again, my trusty floats.Blevins
Unfortunately it depends on the offset, because the additions will eventually overflow. The $frac += 0.0001; version will fail for 1000 items. I guess we could compute the offset dynamically, something like $frac += 1 / (count($input) *count($input));. But even that will fail for 100000 items. Floats are funny that way.Jackstraws
Yes, that offset is tied to the expected number of elements, therefore I mentioned my test range. There are a couple of non-float answers here, they may be better for 1000s of elements but your dynamic offset looks good too.Blevins
Just made one that uses array_slice and dynamic offset + lengths, but ended up pretty much like the one from @ThomasWeinert.Blevins
It's wierd solution to use floats with this taskMoria
I'm not sure I follow why floats are useful in this answer, but this answer does provide the desired output.Entellus
G
4

For each group, calculate to offset of the first element and the group length, copy that slice from the input array.

function array_group_elements($array, $groups) {
  $result = array();
  $count = count($array);
  // minimum in each group
  $limit = floor($count / $groups);
  // balance, modulo
  $overhead = $count % $groups;
  // for each group
  for ($i = 0; $i < $groups; ++$i) {
    // group offset, add 1 for each group that got a balance element
    $offset = ($i * $limit) + ($i < $overhead ? $i : $overhead);
    // length, add 1 if it is a group with balance element
    $length = $limit + ($i < $overhead ? 1 : 0);
    // copy slice from original array
    $result[] = array_slice($array, $offset, $length);
  }
  return $result;
}

$input = array('Item 1',  'Item 2',  'Item 3',  'Item 4',  'Item 5',  'Item 6', 'Item 7');
$grouped = array_group_elements($input, 3);
var_dump(
  array_map(
    function($group) {
      return implode(', ', $group);
    },
    $grouped
  )
);

Output:

array(3) {
  [0]=>
  string(22) "Item 1, Item 2, Item 3"
  [1]=>
  string(14) "Item 4, Item 5"
  [2]=>
  string(14) "Item 6, Item 7"
}

The function array_group_elements() loops over the $groups (3 times), not the $array (7 times).

Grasso answered 28/11, 2013 at 15:32 Comment(1)
This answer, unlike many on this page, does work as required.Entellus
M
3

I have two functions with different approaches.

function restructureArray1($array, $x)
    {
    $size = sizeof($array);
    if ($size < $x)
        return array_chunk($array, 1) + array_fill(0, $x, array());
        // chunk by 1 element and add missing empty elements

    $big_row_length = (int) ($size / $x) + 1;
    $big_rows_chunk = array_splice($array, 0, $size % $x * $big_row_length);
    // $big_rows_chunk contains items with big rows
    // $array now contains items with small rows
    return array_merge(array_chunk($big_rows_chunk, $big_row_length), array_chunk($array, $big_row_length - 1));
    // chunk and merge
    }

function restructureArray2($array, $x)
    {
    $size = sizeof($array);
    $small_row_length = (int) ($size / $x);
    $big_row_count = $size % $x;
    for ($i = 0; $i < $x; ++$i)
        {
        $length = $small_row_length + (int) ($i < $big_row_count); // type juggling
        $return [] = array_splice($array, 0, $length);
        // $return[] contains one row
        // $array now contains rest of array
        }
    return $return;
    }

To get result with array of strings you need just array_map it.

$x = 3;
$size = 7;
$array = range(1, $size);
$result1 = restructureArray1($array, $x);
$result2 = restructureArray2($array, $x);

var_dump(array_map(function($array)
            { return implode(', ', $array); }, $result2));

Demo

Relative links to documentation: array_splice, array_chunk, array_merge

P.S. The shortest solution


function restructureArray3($array, $x)
    {
    while($x)
        $return [] = array_splice($array, 0, ceil(sizeof($array) / $x--));
    return $return;
    }

Demo

Moria answered 4/12, 2013 at 0:52 Comment(4)
+2. This works perfect. But adds empty items when length is less than $xPsoas
@GeorgeGarchagudashvili, all function could be changed easy for this behaviour.Moria
sure, no prob. Just noted :))Psoas
I think the call to implode() should be part of the function, like so: $return [] = implode(', ', array_splice($array, 0, ceil(sizeof($array) / $x--)));. Then it really is the shortest solution. Genious! You should remove the first two solutions, coz it just clutters your answer and hides the best solution!Jackstraws
D
3

I've come up with the following algorithm that keeps fitting the right number of items in each row; in each iteration, we round up the division of the number of remaining lines vs remaining items:

$a = range(1,7);

$len = count($a);
$res = array();
$max = 3;

$i = 0;
while ($i < $len) {
        // divide the remaining number of items by the maximum rows allowed
        // this gives the width for the current row
        $width = ceil(($len - $i) / $max);

        $res[] = join(', ', array_slice($a, $i, $width));

        // adjust pointer and reduce the maximum
        $i += $width;
        --$max;
}

print_r($res);

Demo

Diathesis answered 5/12, 2013 at 8:21 Comment(1)
This answer DOES provide the expected output.Entellus
K
2
<?php
$source_array = array(
  'item 1',
  'item 2',
  'item 3',
  'item 4',
  'item 5',
  'item 6',
  'item 7',
  'item 8',
  'item 9',
  'item 10',
);


$k = 4;

// allocating cells
$allocated_cells_sizes = array();
$ik = 0;
foreach ($source_array as $value){
  if (! isset($allocated_cells_sizes[$ik])) $allocated_cells_sizes[$ik] = 0;
  $allocated_cells_sizes[$ik] ++;
  if (++$ik >= $k) $ik = 0;
}

// filling result array
$result = array();
foreach ($allocated_cells_sizes as $cells_sizes){
  $result[] = implode(', ', array_slice($source_array, 0, $cells_sizes));
  $source_array = array_slice($source_array, $cells_sizes, null);
}

print_r($result);

/**
 * Output
 * Array
    (
    [0] => item 1, item 2, item 3
    [1] => item 4, item 5, item 6
    [2] => item 7, item 8
    [3] => item 9, item 10
    )

 */
Knut answered 28/11, 2013 at 15:35 Comment(1)
+1. It works. But for small array count of result would smaller than $kMoria
J
2

For each group you need to calculate the maximum number of items that will fit in each segment, so I used the ceil(); function to always round up.

Input:

Array
(
    [0] => item1
    [1] => item2
    [2] => item3
    [3] => item4
    [4] => item5
    [5] => item6
    [6] => item7
)

Function:

function segment_array($array, $segment = '3'){

    // Count the items
    $count = count($array);

    // Create an array item for each segment
    for($i=0;$i<$segment;$i++){
        $offset += ($count - ($count - $chunk));
        $chunk = ceil(($count - $offset)/($segment - $i));
        $set[$i] = array_slice($array, $offset, $chunk);
        $new_array[] = implode(', ', $set[$i]);
    }

    return($new_array);
}

$segmented = segment_array($array, '3');

Output:

Array
(
    [0] => item1, item2, item3
    [1] => item4, item5
    [2] => item6, item7
)
Janes answered 30/11, 2013 at 18:44 Comment(4)
I just realized that the new array items need to be evenly balanced where possible. Working on a revision.Janes
Edited: Reformatted the for loop to appropriately distribute the array elements evenlyJanes
+1. Good version. But Notices. And too many calculations inside loop, imhoMoria
+1 very short version. $offset += ($count - ($count - $chunk)); is equivalent to $offset += chunk; and a better place for this line would be as the last line of the for cycle.Jackstraws
J
2

The code:

$input = array('Item 1',  'Item 2',  'Item 3',  'Item 4',  'Item 5',  'Item 6',  'Item 7');
$height = 3;
$commonWidth = floor(count($input) / $height);
$remaining = count($input) % $height;
$i = 0;
for ($j = 0; $j < $height; $j++) {
    $width = $commonWidth + (0 < $remaining--);
    $output[] = implode(', ', array_slice($input, $i, $width));
    $i += $width;
}
print_r($output);

The output:

Array
(
    [0] => Item 1, Item 2, Item 3
    [1] => Item 4, Item 5
    [2] => Item 6, Item 7
)

I wonder if the problem description is incorrect and the required result is in fact a multi-dimensional array, not an array of concatenated strings. In that case just remove the call to implode:

$input = array('Item 1',  'Item 2',  'Item 3',  'Item 4',  'Item 5',  'Item 6',  'Item 7');
$height = 3;
$commonWidth = floor(count($input) / $height);
$remaining = count($input) % $height;
$i = 0;
for ($j = 0; $j < $height; $j++) {
    $width = $commonWidth + (0 < $remaining--);
    $output[] = array_slice($input, $i, $width);
    $i += $width;
}
print_r($output);

And the output is:

Array
(
    [0] => Array
        (
            [0] => Item 1
            [1] => Item 2
            [2] => Item 3
        )

    [1] => Array
        (
            [0] => Item 4
            [1] => Item 5
        )

    [2] => Array
        (
            [0] => Item 6
            [1] => Item 7
        )
)
Jackstraws answered 4/12, 2013 at 11:32 Comment(1)
This both of the approaches in this answer work. 3v4l.org/luM53 and 3v4l.org/BDCAfEntellus
A
1

working for any number of items. and change in any number of columns, just change $x value.

<?php
    $arr = array('Item 1','Item 2','Item 3','Item 4','Item 5','Item 6', 'Item 7');
    $x = 3;
    $newArr = array();
    $j = 0;
    foreach($arr as $key=>$val){
        if($j == $x){
            $j = 0;
        }
        $newArr[$j][] = $val;
        $j++;
    }
    foreach($newArr as $key=>$val){
        $tempVal = implode(',', $val);
        $newArr[$key] = $tempVal;
    }
    print_r($newArr);
    ?>
Ascospore answered 3/12, 2013 at 11:45 Comment(2)
The output is wrong, the first element contains items 1,4,7 instead of 1,2,3.Jackstraws
This answer does not provide the output that the OP expects. Proof: 3v4l.org/niL2UEntellus
T
1

Another algorithm, because as I understand the question is to restructure the array in place, so the best efficiency can be achieved when no temporary arrays are created.

<?php
function group_array(&$array, $parts = 3)
{
    $size = count($array);
    $length = floor($size / $parts);
    $remains = $size % $parts;
    $done = 0;
    for($i = 0; $i < $parts; ++$i)
    {
        $real_length = $length + ($i < $remains ? 1 : 0);
        $array[$i] = $array[$done];
        for($j = $done + 1; $j < min($done + $real_length, $size); ++$j)
            $array[$i] .= ', ' . $array[$j];
        $done += $real_length;
    }
    for($i = $size; $i >= $parts ; --$i)
        unset($array[$i]);
}

?>

Test case #1:

<?php

$array = array("item 1", "item 2", "item 3", "item 4", "item 5", "item 6", "item 7");
group_array($array);

echo '<pre>' . print_r($array, true) . '</pre>';

?>

Output:

Array
(
    [0] => item 1, item 2, item 3
    [1] => item 4, item 5
    [2] => item 6, item 7
)

Test case #2:

<?php

$array = array("item 1", "item 2", "item 3", "item 4", "item 5", "item 6",
               "item 7", "item 8", "item 9", "item 10");
group_array($array);

echo '<pre>' . print_r($array, true) . '</pre>';

?>

Output:

Array
(
    [0] => item 1, item 2, item 3, item 4
    [1] => item 5, item 6, item 7
    [2] => item 8, item 9, item 10
)
Tanto answered 4/12, 2013 at 14:2 Comment(0)
K
1

If you're satisfied with a two-dimensional array, and you're happy if the last row can be much smaller than all the rest, here's a one-liner:

function split_array1($orig_array, $parts = 1) {
    return array_chunk($orig_array,ceil(count($orig_array)/$parts));    
}

Same thing, merging each piece into strings:

function split_array2($orig_array, $parts = 1) {
    $split = array_chunk($orig_array,ceil(count($orig_array)/$parts));
    foreach ($split as &$row) $row = join($row, ', ');
    return $split;
}

Output of three tests, with 7, 8, and 9 items:

Array ( [0] => Item 1, Item 2, Item 3 [1] => Item 4, Item 5, Item 6 [2] => Item 7 ) 
Array ( [0] => Item 1, Item 2, Item 3 [1] => Item 4, Item 5, Item 6 [2] => Item 7, Item 8 ) 
Array ( [0] => Item 1, Item 2, Item 3 [1] => Item 4, Item 5, Item 6 [2] => Item 7, Item 8, Item 9 ) 

Test:

$input = array('Item 1',  'Item 2',  'Item 3',  'Item 4',  'Item 5',  'Item 6', 'Item 7', 'Item 8',  'Item 9');
for ($size=7; $size<=count($input); $size++){
    $output = split_array2(array_slice($input, 0, $size), 3);
    print_r($output);
    echo '<br>';
}

This will match your examples exactly:

function split_array3($orig_array, $parts = 1) {
    $count = count($orig_array);
    for ($i=0, $index=0; $i<$parts; $i++) {
        $size_of_sub = ceil(($count - $index) / ($parts-$i));
        $split[$i] = join(array_slice($orig_array, $index, $size_of_sub), ', ');
        $index += $size_of_sub;
    }
    return $split;
}

Result:

Array ( [0] => Item 1, Item 2, Item 3 [1] => Item 4, Item 5 [2] => Item 6, Item 7 ) 
Array ( [0] => Item 1, Item 2, Item 3 [1] => Item 4, Item 5, Item 6 [2] => Item 7, Item 8 ) 
Array ( [0] => Item 1, Item 2, Item 3 [1] => Item 4, Item 5, Item 6 [2] => Item 7, Item 8, Item 9 ) 

Just for fun, here's a solution that uses recursion:

function recursive_split_array($orig_array, $num_sub_arrays = 1) {
    $size_of_sub = ceil(count($orig_array) / $num_sub_arrays);
    $split[0] = join(array_slice($orig_array, 0, $size_of_sub),', ');
    $split = array_merge( $split,
                          split_array(array_slice($orig_array, $size_of_sub,
                                                  count($orig_array)-$size_of_sub),$num_sub_arrays - 1));
    return $split;
}
Keeley answered 4/12, 2013 at 15:47 Comment(2)
split_array1() does not provide the OP's desired output structure. Proof: 3v4l.org/o2CBv split_array2() does not generate the correct result either. Proof: 3v4l.org/HpnDJ After I changed split_array() to recursive_split_array() your "just for fun" snippet breaks more times than I can count. Proof: 3v4l.org/FRGuB array_split3() DOES work! Please either remove or correct all of the other functions so that your answer is 100% valuable to others.Entellus
(I am taking it upon myself to test all of the answers on this page because there is an astounding number of incorrect answers here.)Entellus
H
0

Sorry it should be a comment but I can't do comments maybe because of my reputation...

When you have an array with 7 elements, do you know that you'll divide the array by 3 or you have to find the divisor ?

EDIT 1 Something like this ? :

$formatedAddress = array();
$divisor = 3;
$key = 0;
$counter =1;
foreach ($addressParts as $part)
{
    if(!empty($part){
        $formattedAddress[$key] = $part;
        if($counter != $divisor){
           $counter++;
        }else{
            $counter = 1;
            $key++;
        }
    }

}

EDIT 2 :

I found some errors :

$formatedAddress = array();
$divisor = 3;
$key = 0;
$counter =1;
foreach ($addressParts as $part)
{
    if(!empty($part)){

    $formatedAddress[$key][] = $part;

    if($counter != $divisor){
       $counter++;
    }else{
        $counter = 1;
        $key++;
    }
    }
}
Hamm answered 30/10, 2013 at 14:10 Comment(2)
For now, you can assume it will always be 3. To make it vary will be easy.Sharanshard
The most recent edit (which I assume is the best one) does not provide the output array that the OP requires. Not only is the general structure incorrect, the OP wants to implode the values into comma-separated values. Proof: 3v4l.org/GRva8Entellus
Y
0
$source_array = array(
  'item 1',
  'item 2',
  'item 3',
  'item 4',
  'item 5',
  'item 6',
  'item 7',
  'item 8',
  'item 9',
  'item 10',
);

function reduce_array($input, $first_count = 3, $count = 2)
{
    $array_slice = array(array_slice($input, 0, $first_count));

    $array_chunk = array_chunk(array_slice($input, $first_count), $count);

    return array_merge($array_slice, $array_chunk);
}

$reduce_array = reduce_array($source_array); 

var_dump($reduce_array);

foreach($reduce_array as $array)
{
    var_dump(implode(',', $array));
}


Output:


array(5) {
  [0]=>
  array(3) {
    [0]=>
    string(6) "item 1"
    [1]=>
    string(6) "item 2"
    [2]=>
    string(6) "item 3"
  }
  [1]=>
  array(2) {
    [0]=>
    string(6) "item 4"
    [1]=>
    string(6) "item 5"
  }
  [2]=>
  array(2) {
    [0]=>
    string(6) "item 6"
    [1]=>
    string(6) "item 7"
  }
  [3]=>
  array(2) {
    [0]=>
    string(6) "item 8"
    [1]=>
    string(6) "item 9"
  }
  [4]=>
  array(1) {
    [0]=>
    string(7) "item 10"
  }
}
string(20) "item 1,item 2,item 3"
string(13) "item 4,item 5"
string(13) "item 6,item 7"
string(13) "item 8,item 9"
string(7) "item 10"
Yesima answered 28/11, 2013 at 18:10 Comment(1)
This answer does not provide the OP's desired output. Proof: 3v4l.org/fVH29Entellus
C
0
<?php

    $sample_array = array('item 1','item 2', 'itme 3','item 4', 'item 5','item 6','item 7', 'item 8','item 9','item 10',
        'item 11','item 12','item 13','item 14','item 15','item 16');

    restructure($sample_array,3);        

          function restructure($array ,$size)
           {
            $i=0;       
            while($i<= count($array))
             {
                $j=$i;
                $count = 0;
                while ($count<$size)
                {
                    if($j<count($array))
                    {
                     echo $array[$j]. " ";
                     $j++;
                    }
                         $count++;
                 }
                 echo '<br>';
                 $i=$i+$size;

             }

           }

    ?>

    <?php
Claribel answered 2/12, 2013 at 5:39 Comment(4)
There is no JS tag in questionMoria
array.length() does not work. i and j should be $i and $j. The result is very bad, for 7 items it only returns 3. Did you try this code before posting it?Jackstraws
The first code i posted was just to give an idea but this one should work for you because i already tested it.Claribel
This answer does not provide the OP's desired output. This answer limits the number of comma-separated values per group to 3 and that's what is asked for. Proof: 3v4l.org/QqvuaEntellus
F
0

Try this out:

function array_group($array, $X){
    $extra = count($array)%$X;
    $pre = array_splice($array, 0, $extra);
    $post = array_chunk($array, count($array)/$X);

    $post[0] = array_merge($pre, $post[0]);

    foreach ($post as &$key) {
        $key = implode(', ', $key);
    }
    return $post;
}
Frisby answered 2/12, 2013 at 12:46 Comment(2)
Wrong value with 8 items.Moria
This answer does not provide the desired output when the array contains 7 elements and the maximum rows is 4. Proof: 3v4l.org/iQPrm It also doesn't work when the number of elements is 8 and the "row" limit is 3. Proof: 3v4l.org/vOkefEntellus
W
0

Is that ok?

$items = array('item1', 'item2', 'item3', 'item4', 'item5', 'item6', 'item7');
array_unshift($items, '');
$chunkitems = array_chunk($items, 2);
$newitems = array_map(function($v){return implode(', ', $v);}, $chunkitems);
$first = ltrim(implode(', ', array(array_shift($newitems), array_shift($newitems))),', ');
array_unshift($newitems, $first);
print_r($newitems);
Wham answered 3/12, 2013 at 14:13 Comment(2)
No, count of array result must be equal 3. Try your code with another array.Moria
This answer does not limit the number of "rows" as required by the OP. Proof: 3v4l.org/Ku9XZ (I also find the logic difficult to follow and I have no idea if it is built in a way that is customizable in case the row limit needs to be changed.)Entellus
B
0

This seems pretty succinct:

function ReduceArray($input,$length)
{
    $count = count($input);

    // fill new array with new number of empty elements
    $newArray = array_fill(0,ceil($count/$length),"");

    for( $i = $count; $i > 0; $i--)
    {   
        // calculate index in new array to insert item
        $index = ceil($i / $length)-1;

        // we need a comma separator in this position if the array is empty
        $sep = ($newArray[$index] != "" ? "," : "");

        // insert into new array
        $newArray[$index] = array_pop($input) . $sep . $newArray[$index] ;
    }

    return $newArray;
}
Bresnahan answered 4/12, 2013 at 22:29 Comment(1)
This answer breaks when there are 6 elements in the array and the number of output elements is set to 4. Proof: 3v4l.org/kf06WEntellus
E
0

Here's an adaptation from an answer of mine on another very, very similar question (near duplicate).

Code: (Demo)

function custom_chunk($array, $maxrows) {
    $result = [];
    $size = sizeof($array);
    $columns = ceil($size / $maxrows);
    $fullrows = $size - ($columns - 1) * $maxrows;

    for ($i = 0; $i < $maxrows; ++$i) {
        $result[] = implode(', ', array_splice($array, 0, ($i < $fullrows ? $columns : $columns - 1)));
    }
    return $result;
}

$data = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6', 'Item 7'];

var_export(custom_chunk($data, 3));

Output:

array (
  0 => 'Item 1, Item 2, Item 3',
  1 => 'Item 4, Item 5',
  2 => 'Item 6, Item 7',
)
Entellus answered 3/11, 2018 at 12:54 Comment(0)
E
-1

Another one cent, maybe. :-)

<?php

function restructure($x,$slots)
{
  $result=array();
  $count=count($x);

  $least=(int)($count/$slots);
  $excess=$count-$least*$slots;

  for($i=0;$i<($least+1)*$excess;$i+=$least+1)
    array_push($result,implode(", ",array_slice($x,$i,$least+1)));
  for(;$i<$count;$i+=$least)
    array_push($result,implode(", ",array_slice($x,$i,$least)));
  return $result;
}

if (PHP_SAPI==='cli') {

  $x=array(
  "item 1",
  "item 2",
  "item 3",
  "item 4",
  "item 5",
  "item 6",
  "item 7",
  );

  print_r(restructure($x,3));

}

Which makes:

Array
(
    [0] => item 1, item 2, item 3
    [1] => item 4, item 5
    [2] => item 6, item 7
)

Edit: fixed the bug @mickmackusa pointed out. See https://3v4l.org/hIVfb

Elveraelves answered 5/12, 2013 at 8:56 Comment(1)
This answer breaks when there are 7 elements in the array and the row count is set to 5. Proof: 3v4l.org/Nt8VLEntellus
S
-1

Great Question +1 for it. I have created solution more dynamically as.

  1. As the user input can be any number from 1.....n
  2. The Minimum Grouping of Members to be described or changed
  3. The Maximum Grouping of Members to be described or can be changed any time

This would be an edge upon other to change things accordingly:

<?

//can n number of array items
$items = array('item1', 'item2', 'item3', 'item4', 'item5', 'item6', 'item7');

//counting of total number of items
$total_items = count($items);

//item to find in array
$find_item = "item7";

//first item found key number
$key = array_search($find_item, $items);

//Splitting into two

//array with last found as item
$temp_output1 = array_slice($items, 0, $key+1); 

//items left
$temp_output2 = array_slice($items, $key+1, $total_items); 

//minimum number items to group
$minimum_group=2;

//Maximum Number of Grouped items
$maximum_group=4;

if ( $temp_output1 ) {
    //sending to group accordingly
    $output1 = do_slicing($temp_output1, $minimum_group, $maximum_group);
    print_r($output1);
}

if ( $temp_output2 ) {
    //sending to group accordingly
    $output2 = do_slicing($temp_output2, $minimum_group, $maximum_group);
    print_r($output2);
}


function do_slicing($temp_output = array(), $minimum_group, $maximum_group){
    $count = count($temp_output);

    //array is equal to min grouping
    if ( $count == $minimum_group){
        //Group them and return
        return by_grouping($temp_output, $minimum_group);
    }elseif ($count == $maximum_group){
        //if items equal to maximum then group them and return
        return by_grouping($temp_output, $maximum_group);
    }elseif ( ($count > $minimum_group) and ($count < $maximum_group)){
        return by_grouping($temp_output, count($temp_output));
    }elseif ($count == 1) {
        //if item is 1 print error or return from here
        return $temp_output;
    }else{
        //calculate the total number of groups available
        $item_slice_count = intval($count/$maximum_group);

        //Split them as grouped members
        $temp_slice = array_slice($temp_output, 0, 
                    ($item_slice_count*$maximum_group));

        //non group members
        $temp_slice2 = array_slice($temp_output, 
                    ($item_slice_count*$maximum_group), $count);

        //if there is no non group members
        if ( !$temp_slice2 ) {
            //combine them and return according to maximum grouping
            return by_grouping($temp_slice, $maximum_group);
        }else{
            if ( 
                (count($temp_slice2) === $minimum_group) or 
                (
                  (count($temp_slice2) > $minimum_group) and  
                  (count($temp_slice2) < 
                   $maximum_group)
                 ) 
             ){
                //if count of slice2 equal to minimum group then
                $a=by_grouping($temp_slice, $maximum_group);
                if ( 
                  (count($temp_slice2) > $minimum_group) and  
                  (count($temp_slice2) < 
                   $maximum_group)
                 ){
                    $b=by_grouping($temp_slice2, count($temp_slice2));
                }else{
                    $b=by_grouping($temp_slice2, $minimum_group);
                }
                return array_merge($a, $b);
            }elseif( count($temp_slice2) < $minimum_group ) {
                //if less then minimum group then
                //if total count is divisible with minimum group
                if ( ($count % $minimum_group ) == 0 ){
                    //return after clubbing according minimum group
                    return by_grouping($temp_output, $minimum_group);
                }else{
                    //Where last group is not equal to minimum group

                    //Minimum more needed to complete last slice
                    $minimum_needed = $minimum_group - count($temp_slice2);

                    //slice1 become
                    $slice1 = array_slice($temp_output, 0, (
                               ($item_slice_count-1)*$maximum_group));

                    //slice2 would be
                    $slice2 = array_slice($temp_output, (
                                      ($item_slice_count-1)*$maximum_group),
                                      ($maximum_group-$minimum_needed));

                    //slice 3 then
                    $slice3 = array_slice($temp_output, ( 
                               (($item_slice_count-1)*$maximum_group) + 
                              ($maximum_group-$minimum_needed)),
                               $count);

                    //if slice2 is greater or equal to minimum group then
                    if ( $slice2>=$minimum_group) {
                        $a=by_grouping($slice1, $maximum_group);
                        $b=array_merge($a, by_grouping($slice2,
                                      count($slice2)));
                        $c=by_grouping($slice3, $minimum_group);
                        return array_merge($b, $c);
                    }else{
                        die("This Situation doesn't reached any time");
                    }
                }
            }
        }
    }
}

//Grouping of members according to slices provided
function by_grouping($temp_slice, $group){
    $return = array();
    $temp = array_chunk($temp_slice, $group);
    foreach($temp as $a){
        $return[] = implode(', ', $a);
    }
    return $return;
}

?>

DEMO CODE

Salley answered 5/12, 2013 at 9:42 Comment(1)
This is the right answer to the wrong question. At no point did the OP ask to search for a specific value. Because this page is already overloaded with answers, it would make this page more valuable if the only answers on page did the one thing that the OP asked for.Entellus

© 2022 - 2024 — McMap. All rights reserved.