Как нормализовать список списков строк в python?

У меня есть список списков, которые представляют собой сетку данных (считайте строки в электронной таблице). Каждая строка может иметь произвольное количество столбцов, а данные в каждой ячейке - это строка произвольной длины.

Я хочу нормализовать это, фактически, чтобы каждая строка имела одинаковое количество столбцов, и каждый столбец в данных имеет одинаковую ширину, заполняя пробелы по мере необходимости. Например, учитывая следующий ввод:

(
 ("row a", "a1","a2","a3"),
 ("another row", "b1"),
 ("c", "x", "y", "a long string")
)

Я хочу, чтобы данные выглядели следующим образом:

(
 ("row a      ", "a1", "a2", "a3           "),
 ("another row", "b1", "  ", "             "),
 ("c          ", "x ", "y ", "a long string")
)

Что такое python для python 2.6 или выше? Просто чтобы быть ясным: я не хочу красиво печатать список как таковой, я ищу решение, которое возвращает новый список списков (или кортежей кортежей) со значениями, выпадающими.

Ответ 1

Начиная с ваших входных данных:

>>> d = (
 ("row a", "a1","a2","a3"),
 ("another row", "b1"),
 ("c", "x", "y", "a long string")
)

Сделайте один проход, чтобы определить максимальный размер каждого столбца:

>>> col_size = {}
>>> for row in d:
        for i, col in enumerate(row):
            col_size[i] = max(col_size.get(i, 0), len(col))

>>> ncols = len(col_size)

Затем сделайте второй проход, чтобы заполнить каждый столбец необходимой шириной:

>>> result = []
>>> for row in d:
        row = list(row) + [''] * (ncols - len(row))
        for i, col in enumerate(row):
            row[i] = col.ljust(col_size[i])
        result.append(row)

Это дает желаемый результат:

>>> from pprint import pprint
>>> pprint(result)
[['row a      ', 'a1', 'a2', 'a3           '],
 ['another row', 'b1', '  ', '             '],
 ['c          ', 'x ', 'y ', 'a long string']]

Для удобства этапы могут быть объединены в одну функцию:

def align(array):
    col_size = {}
    for row in array:
        for i, col in enumerate(row):
            col_size[i] = max(col_size.get(i, 0), len(col))
    ncols = len(col_size)
    result = []
    for row in array:
        row = list(row) + [''] * (ncols - len(row))
        for i, col in enumerate(row):
            row[i] = col.ljust(col_size[i])
        result.append(row)
    return result

Ответ 2

Вот что я придумал:

import itertools

def pad_rows(strs):
   for col in itertools.izip_longest(*strs, fillvalue=""):
      longest = max(map(len, col))
      yield map(lambda x: x.ljust(longest), col)

def pad_strings(strs):
   return itertools.izip(*pad_rows(strs))

И называя его следующим образом:

print tuple(pad_strings(x))

дает этот результат:

(('row a      ', 'a1', 'a2', 'a3           '),
 ('another row', 'b1', '  ', '             '),
 ('c          ', 'x ', 'y ', 'a long string'))

Ответ 3

Прежде всего, определите функцию заполнения:

def padder(lst, pad_by):
  lengths = [len(x) for x in lst]
  max_len = max(lengths)
  return (x + pad_by * (max_len - length) for x, length in zip(lst, lengths))

затем поместите каждую запись на ту же длину на '':

a = # your list of list of string

a_padded = padder(a, ('',))

тогда переставьте этот список списка, чтобы мы могли работать столбцом по столбцу,

a_tr = zip(*a_padded)

для каждой строки, мы найдем максимальную длину строк, а затем наложим ее на указанную длину.

a_tr_strpadded = (padder(x, ' ') for x in a_tr)

наконец, мы снова транспонируем его и оцениваем результат.

a_strpadded = zip(*a_tr_strpadded)
return [list(x) for x in a_strpadded]

Используйте tuple(tuple(x) for ...), если вы хотите кортеж кортежа вместо списка списка.

Демо: http://ideone.com/4d0DE

Ответ 4

import itertools

def fix_grid(grid):
    # records the number of cols, and their respective widths
    cols = []
    for row in grid:
        # extend cols with widths of 0 if necessary
        cols.extend(itertools.repeat(0, max(0, len(row) - len(cols)))
        for index, value in enumerate(row):
            # increase any widths in cols if this row has larger entries
            cols[index] = max(cols[index], len(value)
    # generate new rows with values widened, and fill in values that are missing 
    for row in grid:           
        yield tuple(value.ljust(width)
                    for value, width in itertools.zip_longest(row, cols, ''))
# create a tuple of fixed rows from the old grid
grid = tuple(fix_grid(grid))

См:

Ответ 5

Я предлагаю вам использовать list вместо tuple. tuple являются неизменяемыми и трудными для работы.

Сначала найдите длину самой длинной строки.

maxlen = max([len(row) for row in yourlist])

Затем проложите каждую строку необходимым количеством строк:

for row in yourlist:
    row += ['' for i in range(maxlen - len(row))]

Затем вы можете обменивать строки и столбцы, т.е. столбцы должны быть строками и наоборот. Для этого вы можете написать

newlist = [[row[i] for row in yourlist] for i in range(len(row))]

Теперь вы можете взять строку (столбец старого списка) и поместить строки по мере необходимости.

for row in newlist:
    maxlen = max([len(s) for s in row])
    for i in range(len(row)):
        row[i] += ' ' * (maxlen - len(row[i]))

Теперь верните таблицу в исходный формат:

table = [[row[i] for row in newlist] for i in range(len(row))]

Объединить его в функцию:

def f(table):
    maxlen = max([len(row) for row in table])
    for row in table:
        row += ['' for i in range(maxlen - len(row))]
    newtable = [[row[i] for row in table] for i in range(len(row))]
    for row in newtable:
        maxlen = max([len(s) for s in row])
        for i in range(len(row)):
            row[i] += ' ' * (maxlen - len(row[i]))
    return [[row[i] for row in newtable] for i in range(len(row))]

Это решение работает для list s.

Ответ 6

Я могу только подумать об этом, пройдя его дважды - но не должно быть сложно:

def pad_2d_matrix(data):
    widths = {}
    for line in data:
        for index, string in enumerate(line):
            widths[index] = max(widths.get(index, 0), len(string))
    result = []
    max_strings = max(widths.keys())
    for line in data:
        result.append([])
        for index, string in enumerate(line):
            result[-1].append(string + " " * (widths[index] - len(string)   ))
        for index_2 in range(index, max_strings):
            result[-1].append(" " * widths[index_2])
    return result

Ответ 7

Я согласен со всеми, что должно быть два прохода. Pass 1 вычисляет максимальную ширину для каждого столбца и пропускает 2 ячейки каждой ячейки до ее ширины столбца.

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

g = (
 ("row a", "a1","a2","a3"),
 ("another row", "b1"),
 (),     # null row added as a test case
 ("c", "x", "y", "a long string")
)

widths = reduce(
        lambda sofar, row: 
            map(
                lambda longest, cell: 
                    max(longest, 0 if cell is None else len(cell)
                ), 
            sofar, 
            row
        ),
        g, 
        []
) #reduce()

print 'widths:', widths

print 'normalised:', tuple([ 
    tuple(map(
        lambda cell, width: ('' if cell is None else cell).ljust(width), 
        row, 
        widths
    )) #tuple(map(
    for row in g 
]) #tuple([

Это дает результат (с разрывами строк, добавленными для удобочитаемости):

widths: [11, 2, 2, 13]
normalised: (
    ('row a      ', 'a1', 'a2', 'a3           '), 
    ('another row', 'b1', '  ', '             '), 
    ('           ', '  ', '  ', '             '), 
    ('c          ', 'x ', 'y ', 'a long string')
)

Я тестировал этот код. Выражения ... if cell is None else cell являются подробными, но необходимыми для того, чтобы заставить выражения работать.

Ответ 8

только для удовольствия - один лайнер

from itertools import izip_longest as zl


t=(
 ("row a", "a1","a2","a3"),
 ("another row", "b1"),
 ("c", "x", "y", "a long string")
);


b=tuple(tuple(("{: <"+str(map(max, ( map(lambda x: len(x) if x else 0,i) for i in zl(*t) ))[i])+"}").format(j) for i,j in enumerate(list(k)+[""]*(max(map(len,t))-len(k)))) for k in t)
print(b)