Выбрать главу

Программа будет общаться с объектами ячеек через хорошо определённый интерфейс. Типы ячеек не будут заданы жёстко. Мы сможем добавлять новые стили ячеек – к примеру, подчёркнутые ячейки у заголовка. И если они будут поддерживать наш интерфейс, они просто заработают, без изменений в программе. Интерфейс:

• minHeight() возвращает число, показывающее минимальную высоту, которую требует ячейка (выраженную в строчках)

• minWidth() возвращает число, показывающее минимальную ширину, которую требует ячейка (выраженную в символах)

• draw(width, height) возвращает массив длины height, содержащий наборы строк, каждая из которых шириной в width символов. Это содержимое ячейки.

Я буду использовать функции высшего порядка, поскольку они здесь очень уместны.

Первая часть программы вычисляет массивы минимальных ширин колонок и высот строк для матрицы ячеек. Переменная rows будет содержать массив массивов, где каждый внутренний массив – это строка ячеек.

function rowHeights(rows) {

  return rows.map(function(row) {

    return row.reduce(function(max, cell) {

      return Math.max(max, cell.minHeight());

    }, 0);

  });

}

function colWidths(rows) {

  return rows[0].map(function(_, i) {

    return rows.reduce(function(max, row) {

      return Math.max(max, row[i].minWidth());

    }, 0);

  });

}

Используя переменную, у которой имя начинается с (или полностью состоит из) подчёркивания (_), мы показываем тому, кто будет читать код, что этот аргумент не будет использоваться.

Функция rowHeights не должна вызвать затруднений. Она использует reduce для подсчёта максимальной высоты массива ячеек, и заворачивает это в map, чтобы пройти все строки в массиве rows.

Ситуация с colWidths посложнее, потому что внешний массив – это массив строк, а не столбцов. Я забыл упомянуть, что map (как и forEach, filter и похожие методы массивов) передаёт в заданную функцию второй аргумент – индекс текущего элемента. Проходя при помощи map элементы первой строки и используя только второй аргумент функции, colWidths строит массив с одним элементом для каждого индекса столбца. Вызов reduce проходит по внешнему массиву rows для каждого индекса, и выбирает ширину широчайшей ячейки в этом индексе.

Код для вывода таблицы:

function drawTable(rows) {

  var heights = rowHeights(rows);

  var widths = colWidths(rows);

  function drawLine(blocks, lineNo) {

    return blocks.map(function(block) {

      return block[lineNo];

    }).join(" ");

  }

  function drawRow(row, rowNum) {

    var blocks = row.map(function(cell, colNum) {

      return cell.draw(widths[colNum], heights[rowNum]);

    });

    return blocks[0].map(function(_, lineNo) {

      return drawLine(blocks, lineNo);

    }).join("\n");

  }

  return rows.map(drawRow).join("\n");

}

Функция drawTable использует внутреннюю функцию drawRow для рисования всех строк, соединяя их через символы новой строки.

Функция drawRow сперва превращает объекты ячеек строки в блоки, которые являются массивами строк, представляющими содержимое ячеек, разделённые линиями. Одна ячейка, содержащая число 3776, может быть представлена массивом из одного элемента ["3776"], а подчёркнутая ячейка может занять две строки и выглядеть как массив ["name", "----"].

Блоки для строки, у которых одинаковая высота, должны выводиться рядом друг с другом. Второй вызов map в drawRow строит этот результат построчно, начиная с ячеек самого левого блока, и для каждой из них дополняя строку до полной ширины таблицы. Полученные строки затем соединяются через символ новой строки, создавая целый ряд, который и возвращает drawRow.

Функция drawLine выцепляет строки, которые должны располагаться рядом друг с другом, из массива блоков и соединяет их через пробел, чтобы создать промежуток в один символ между столбцами таблицы.

Давайте напишем конструктор для ячеек, содержащих текст, который предоставляет интерфейс для ячеек. Он разбивает строчку в массив строк при помощи метода split, который режет строчку каждый раз, когда в ней встречается его аргумент, и возвращает массив полученных кусочков. Метод minWidth находит максимальную ширину строки в массиве.

function repeat(string, times) {

  var result = "";