4

I have a dynamic number of items in which I'll need to divide into columns. Let's say I'm given this:

array("one", "two", "three", "four", "five", "six", "seven", "eight")

I need to generate this:

<ul>
  <li>one</li>
  <li>two</li>
  <li>three</li>
  <li>four</li>
</ul>
<ul>
  <li>five</li>
  <li>six</li>
  <li>seven</li>
  <li>eight</li>
</ul>

Here are some rules:

  • if there are no items, I don't want anything to be spat out
  • if there are 16 or under 16 items, id like 4 items per <ul>
  • if there are more than 16 items, i'd like it to be spread out evenly
  • i'll have to alphabetically reorder items. if there are 17 items, the 17th item will need to go to the first column but everything needs to be reordered.

What I have so far:

function divide( $by, $array ) {
 14     $total = count( $array );
 15     $return = array();
 16     $index=0;
 17     $remainder = $total % $by !== 0;
 18     $perRow = $remainder ?
 19         $total / $by + 1:
 20         $total / $by
 21         ;
 22 
 23     for ( $j = 0; $j<$by; $j++ ) {
 24         //$return[] = array();
 25 
 26         if ( $index == 0 ) {
 27             $slice = array_slice( $array, 0, $perRow );
 28             $index = $perRow;
 29             $return[$j] = $slice;
 30         } else {
 31             $slice = array_slice( $array, $index, $perRow );
 32             $index = $index+$perRow;
 33             $return[$j] = $slice;
 34         }
 35     }
}

I feed a number, like divide( 4, $arrRef ), the number dictates the # of columns, but I need to refactor so it determines the number of columns

Jonah
  • 9,991
  • 5
  • 45
  • 79
Reznor
  • 1,235
  • 5
  • 14
  • 23
  • 1
    Interesting question. I'd recommend you do not accept an answer right away as answering this question with a really good answer might take some time. – Tatu Ulmanen Dec 08 '10 at 18:05
  • 1
    "if there are more than 16 items, i'd like it to be spread out evenly"? Define "spread out evenly". – Karl Knechtel Dec 08 '10 at 18:06
  • 1
    Your conditions aren't self-consistent re: sorting. Do you want successive items to be in row-order (implied by the example) or column order (implied by "if there are 17 items, the 17th item will need to go to the first column") – Tyler Eaves Dec 08 '10 at 18:07
  • 1
    As Karl suggests, I think more rules are in order. For instance, if there are 100 items, how many columns should there be? 4, 5, 10, etc...? – webbiedave Dec 08 '10 at 18:08
  • 1
    This is a very common problem and is effectively pagination. – Orbling Dec 08 '10 at 18:14
  • 1
    @Karl I'm fairly sure he means even counts per column. The number of columns is passed to the function. The only missing info is whether it is to be by row or column order, as Tyler asks, the existing code is column order. – Orbling Dec 08 '10 at 18:15
  • By "spread out evenly", do you mean it needs to calculate how many columns it needs to have full rows? It sounds like your main question is how to change it so that it " *determines the number of columns* ". What does that mean? – Jonah Dec 08 '10 at 18:30

4 Answers4

5

I used this code inside my view template..

<?php
$col = 3;
$projects = array_chunk($projects, ceil(count($projects) / $col));

foreach ($projects as $i => $project_chunk)
{
    echo "<ul class='pcol{$i+1}'>";
    foreach ($project_chunk as $project)
        {
        echo "<li>{$project->name}</li>";
    };
    echo "</ul>";
}; ?>
cwouter
  • 719
  • 6
  • 5
3

Your requirements are pretty weird/vague, but I think this does what you're asking

function columnize( array $set, $maxCols = 4 )
{
  if ( count( $set ) <= 16 )
  {
    return array_chunk( $set, 4 );
  }

  return array_chunk( $set, ceil( count( $set ) / $maxCols ) );
}
Peter Bailey
  • 105,256
  • 31
  • 182
  • 206
  • While I think this ought to be done outside the function, for the OPs purpose it may be a good idea to do the array sorting within the function as well. – simshaun Dec 08 '10 at 20:10
3

If I understood you correctly, you are looking to divide your items into four columns "intelligently" so that the lists can be read logically when placed side by side. If that is the case, this should do the trick:

function columnize($items, $columns = 4, $min_per_column = 4) {

    if(empty($items)) return array();

    $result     = array();
    $count      = count($items);
    $min_count  = $min_per_column * $columns;

    if($count <= $min_count) {
        $columns = ceil($count / $min_per_column);
    } else {    
        $per_column = floor($count / $columns);
        $overflow   = count($items) % $columns;
    }

    for($column = 0; $column < $columns; $column++) {
        if($count <= $min_count) {
            $item_count = $min_per_column;
        } else {
            $item_count = $per_column;
            if($overflow > 0) {
                $item_count++;
                $overflow--;
            }
        }
        $result[$column] = array_slice($items, 0, $item_count);
        $items = array_slice($items, $item_count);
    }

    return $result;
}

You can test it here:

http://www.ulmanen.fi/stuff/columnize.php

Tatu Ulmanen
  • 123,288
  • 34
  • 187
  • 185
0

Untested:

function divide($by, $list, $order = 'c') {
   if (empty($list)) {
       return array();
   }

   if (count($list) <= 16) {
       $by = 4;
   }

   $columnLists = array();
   for ($cIndex = 0; $cIndex < $by; $cIndex++) {
       $columnLists[$cColumn] = array();
   }

   switch ($order) {
       case 'r':
           foreach ($list as $cEntry) {
               $columnLists[$cColumn][] = $cEntry;
               $cColumn = ($cColumn + 1) % $by;
           }

           break;
       case 'c':
       default:
           $maxColumnHeight = intval(count($list) / $by) + (count($list) % $by > 0 ? 1 : 0);
           for ($cIndex = 0; $cIndex < $by; $cIndex++) {
               $columnLists[$cColumn] = array_slice($list, $cIndex * $maxColumnHeight, $maxColumnHeight);
           }

           break;
   }

   return $columnLists;
}
Orbling
  • 20,413
  • 3
  • 53
  • 64