如何在Python中规范化字符串列表的列表?

11

我有一个列表的列表,表示数据网格(类似电子表格中的行)。每行可以有任意数量的列,每个单元格中的数据都是长度任意的字符串。

我想要将其规范化,实际上是让每一行具有相同数量的列,每个列中的数据具有相同的宽度,并在必要时填充空格。例如,给定以下输入:

(
 ("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 2.6或更高版本,有什么Pythonic的解决方案吗?只是为了明确:我不是要漂亮地打印列表 per se ,我正在寻找一个解决方案,它返回一个新的列表或元组的元组,其中值已填充。


1
只是为了完全清楚:您想要元组中包含的数据,还是格式化为字符串行? - Makoto
我希望数据以元组的形式呈现,正如问题的最后一行所述:“我正在寻找一种解决方案,返回一个新的列表(或元组的元组),其中值被填充。” - Bryan Oakley
8个回答

7

从您的输入数据开始:

>>> 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

7
我想到的是这个:

以下是我想到的:

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'))

2

首先,定义一个填充函数:

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

这大致是我在寻找的,但似乎需要使用Python 3(?)。至少,在我的2.7安装中,演示代码无法运行:TypeError: zip() argument after * must be a sequence, not generator - Bryan Oakley
@BryanOakley:你的Python安装肯定有问题,因为我刚试了一下,在ideone上的Python 2.6.4和我机器上的Python 2.7.2都可以正常运行。 - kennytm

1
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))

请参见:


1
那段代码有很多错误。你能在你的机器上运行它吗?缺少闭合括号,我猜想 zip_longest 应该是 izip_longest,在 for 循环后面有一个缩进问题,即使修复了这些问题,我仍然会得到一个错误。 - Bryan Oakley
这是一个基于Python 3的模型,可以用来构建解决方案。 - Matt Joiner

1
我建议您使用list而不是tupletuple是不可变的,难以处理。
首先,找到最长行的长度。
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


0

我只能想到通过两次遍历来完成这个任务 - 但应该不难:

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

0

我同意其他人的观点,应该有两个步骤。第一步计算每列的最大宽度,第二步将每个单元格填充到其列宽。

下面的代码依赖于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表达式有些啰嗦,但是为了让表达式真正起作用,它们是必要的。

-1

只是为了好玩 - 一行代码

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)

1
这是一个很好的例子,说明一行代码并不合适。 - jterrace

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接