Как нормализовать список списков строк в 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)