Вывод списка в несколько колонок

28.07.2011
@LEXXX_NF

Чтобы было понятно, над чем я тупил целых 3 часа, формализуем задачу. Дан массив из N элементов. Его надо разделить на C массивов наиболее равномерным образом. Например, то самое меню, с которым я бился: нужно распределить массив ссылок на 3 колонки. Причём, принципиально выводить элементы по столбцам, а не по строкам!

Проиллюстрирую, что значит равномерно.

1 5 8
2 6 9
3 7 10
4

Это равномерно

1 5 9
2 6 10
3 7
4 8

А это неравномерно (последнюю колонку явно обделили)

С наскоку мне удалось получить результат, изображенный на второй картинке, хотя на первый взгляд, задача тривиальна. Видимо, сказалась привычка делить на только на 2 колонки, что значительно проще.

Объясню откуда алгоритм получения неверного результата:

  1. Делим количество элементов на количество колонок и округляем вверх. Получаем количество элементов в одной колонке. Обозначим его n.
  2. Выводим по n элементов в каждой колонке, кроме последней. В последнюю выводим то, что останется.

Этот простой алгоритм замечательно работает, если колонок всего 2, поэтому в подавляющем большинстве случаем им можно пользоваться.

Но если нужно более универсальное решение, то придётся пораскинуть мозгами. На словах универсальный алгоритм выглядит так.

  1. Вычисляем минимальное количество элементов в колонке, поделив общее количество элементов на число колонок и округлив вниз. В примере — $min.
  2. Вычисляем количество «лишних» элементов — это остаток от деления общего количества элементов на число колонок. В примере — $extra.
  3. Теперь ключевой момент. Создаём массив, каждый элемент которого — это количество элементов в соответствующей колонке. Все элементы будут равны числу, высчитанному на первом шаге, а к нескольким первым мы добавим по лишней единичке. Число «счастливчиков» равно числу лишних элементов, полученных на шаге 2. Чтобы дальше было проще, можно запоминать не количество элементов в колонках, а индексы «пограничных» элементов — элементов на которых будет заканчиваться один массив и начинаться другой. В примере — $colsIndex.
  4. Дальше всё просто. Пробегаемся циклом по исходному массиву. Если индекс текущего элемента содержится в массиве, полученном на предыдущем этапе, то начинаем выводить новую колонку.

Надеюсь идея ясна. А теперь код примера:

//  исходные данные
$elems = 10;  //  число элементов
$cols = 3;    //  число количество колонок


//  заполняем массив тестовыми элементами
$array = array();
for($q = 0; $q < $elems; $q++)
  $array[] = $q;


//  а теперь сам код
$total = sizeof($array);         //  сколько у нас элементов
$min   = floor($total / $cols);  //  минимальное количество элементов в столбце
$extra = $total - $min * $cols;  //  "лишние" элементы

$colsIndex = array();  //  массив с "пограничными" индексами для колонок
$prevCol = 0;          //  количество элементов, которое уже распределено по колонкам
for($q = 0; $q < $cols; $q++){

  //  если еще есть лишние элементы, то добавляем один из них к текущей колонке
  $colNum = $extra-- > 0 ? $min + 1 : $min;

  $prevCol += $colNum;
  $colsIndex[] = $prevCol;

}

$curCol = 0;  //  какую колонку выводим
foreach($array as $c => $val){

  if($c == $colsIndex[$curCol]){

    ++$curCol;
    echo "\n";

  }

  echo $val."\n";

}

Кстати мой товарищ, занимающийся программированием для телефонов в очень приличной конторе, решил эту задачку за 10 минут :)

Буду издеваться над кандидатами на должность программиста :)

#1
Kola
01.08.2011 09:50
Все вроде понятно, но IMHO проще сформулировать решение следующим образом.

# Выводим (или записываем в соответствующий массив) последовательно в каждую колонку по $min * $cols элементнов;

# Записав последний элемент, смотрим, если номер текущей колонки меньше либо равен $extra (строго меньше, если нумерация с нуля), то добавляем еще один, иначе сразу переходим к следующей колонке.
#2
@LEXXX_NF
01.08.2011 11:10
А что, хорошая оптимизация! Видимо я еще не прекратил тупить :)
Надо будет включить в статью.
#3
ProfBiss
07.11.2011 16:53
$a = array(1,2,3,4,5,6,7,8,9,10);
foreach($a as $i=>$v)$ar[$i%3][$i]=$v;
#4
ProfBiss
07.11.2011 16:55
2 минуты на написание и проверку)

$ar[$i%3][$i]=$v; - тут 3 это колличество колонок
#5
ProfBiss
07.11.2011 17:04
Ещё вариант:

$a = array(1,2,3,4,5,6,7,8,9,10);
echo '<ul>';
foreach($a as $i=>$v) echo '<li '.(!($i%3)?'style="clear:both"':'').'>'.$v.'</li>';
echo '</ul>';
echo '<style>ul > li {float:left;width:100px;list-style: none outside none;}</style>';
#6
@LEXXX_NF
07.11.2011 21:16
Всем эти варианты хороши и, пожалуй, даже идеальны, кроме одного - они решают другую задачу. Надо вывести массив по столбцам, а не по строкам. Предложенные тобой варианты дают построчный вывод.
#7
ProfBiss
21.11.2011 13:25
Ещё вариант в туже степь
$a = array(1,2,3,4,5,6,7,8,9,10);
$c=count($a);$t=$c/3;
foreach($a as $i=>$v)$y[$v%$t][$i]=$v;
#8
Серый
27.02.2012 01:04
Тю, так оно в строку все выводит, где тут обещанные колонки?
#9
@LEXXX_NF
27.02.2012 02:29
Дело то в общем и не в строке и не в колонках. В широком смысле суть задачи в том, чтобы разделить один большой массив на несколько маленьких, равномерно заполненных. А уж какую разметку добавить, чтоб эти массивы вывести — дело десятое.

Мой скрипит выводит элементы массива по одному на строке и еще одну пустую строку между колонками. Вы, вероятно, этого не увидили, потому что смотрели результат работы скрипа в браузере. Откройте исходный код вашей страницы — там всё будет видно правильно.
#10
Евгений
28.02.2012 11:51
Более интересная задача, деление сплошного текста последовательно на колонки (как в типографской верстке).
#11
@LEXXX_NF
28.02.2012 12:14
В CSS3 это уже есть: http://www.w3.org/TR/css3-multicol/.
А на JavaScript'е такое писать ИМХО извращение. Потому что будет медленно и глюковато.
#12
Евгений
28.02.2012 12:58
>В CSS3 это уже есть: http://www.w3.org/TR/css3-multicol/.
Осталось дождаться поддержки большинством браузеров и отмирания динозавров
#13
Евгений
28.02.2012 13:10
Не пробовал, но есть плагин для JQuery http://archive.plugins.jquery.com/project/Columnizer
#14
@LEXXX_NF
28.02.2012 13:22
Прикольный плагин.
Ему бы только побороть проблему висячих строк: http://prntscr.com/6js2n
Выровнять колонки по ширине и добавить расстановку переносов: http://code.google.com/p/hyphenator/
И останется только одна проблема - получить заказ на разработку сайта от www.thetimes.co.uk :)
#15
Timures
13.04.2015 17:08
Добрый день,
А array_chunk, не проще ли использовать, указал на сколько делить, и вперед.
#16
@LEXXX_NF
13.04.2015 17:45
array_chunk даёт то, что изображено на правой иллюстрации. Не этого нам надо.

Писáть здесь