2012-01-16 50 views
10

Tengo una lista de listas que representan una cuadrícula de datos (pensar filas en una hoja de cálculo). Cada fila puede tener un número arbitrario de columnas, y los datos en cada celda son una cadena de longitud arbitraria.cómo normalizar la lista de cadenas de caracteres en python?

Quiero normalizar esto para, en efecto, hacer que cada fila tenga el mismo número de columnas y cada columna en los datos tenga el mismo ancho, relleno con espacios según sea necesario. Por ejemplo, dada la siguiente entrada:

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

Quiero que los datos para el siguiente aspecto:

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

Cuál es la solución Pythonic para Python 2.6 o mayor? Para ser claros: no estoy buscando imprimir bastante la lista per se, estoy buscando una solución que devuelva una nueva lista de listas (o tuplas de tuplas) con los valores rellenos.

+1

Solo para ser * perfectamente claro *: ¿Desea los datos contenidos en tuplas, o formateados como líneas de cadenas? – Makoto

+0

Quiero los datos en tuplas, como se indica en la última línea de la pregunta: "Estoy buscando una solución que arroje una nueva lista de listas (o tuplas de tuplas) con los valores rellenos". –

Respuesta

7

A partir de los datos de entrada:

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

la banda una vez para determinar el tamaño máximo de cada columna:

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

luego hacer una segunda pasada a la almohadilla de cada columna para la anchura requerida:

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

que da el resultado deseado:

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

Para mayor comodidad, los pasos se pueden combinar en una sola función:

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

Ver:

+1

Ese código tiene muchos errores. ¿Eres capaz de ejecutar eso en tu máquina? Faltan paréntesis de cierre, supongo que 'zip_longest' estaba destinado a' izip_longest', hay un problema de indentación después del ciclo for, e incluso cuando los soluciono sigo recibiendo un error. –

+0

Este es un modelo para basar su solución, y usa Python 3. –

0

sólo puede pensar en hacer esto de ir a través de él dos veces - pero no debería ser difícil:

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 
1

le sugeriría a utilizar en lugar de listtuple. tuple s son inmutables y difíciles de trabajar.

Primero, encuentre la longitud de la fila más larga.

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

Entonces almohadilla cada fila por número necesario de cadenas:

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

A continuación, puede intercambiar las filas y columnas es decir, las columnas deben ser filas y viceversa.Para eso puede escribir

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

Ahora, puede tomar una fila (una columna de la lista anterior) y rellenar las cadenas según sea necesario.

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

Ahora convertir la tabla de nuevo al formato original:

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

de poner juntos en una función:

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

Esta solución funciona para list s.

2

En primer lugar, definir una función padding:

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

entonces la almohadilla de cada entrada a la misma longitud por '':

a = # your list of list of string 

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

entonces, incorporar la presente lista de lista para que podamos trabajar columna por columna,

a_tr = zip(*a_padded) 

por cada fila, encontramos la longitud máxima de la str ings, y luego rellenarlo a la longitud especificada.

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

finalmente lo transponemos de nuevo y evaluamos el resultado.

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

Uso tuple(tuple(x) for ...) si quieres una tupla de tuplas en lugar de lista de la lista.

Demostración: http://ideone.com/4d0DE

+0

Esto es más o menos lo que estaba buscando, aunque esto parece requerir Python 3 (?). Al menos, el código de demostración no funciona en mi instalación 2.7: el argumento 'TypeError: zip() después de * debe ser una secuencia, no generador' –

+0

@BryanOakley: Debe haber algún problema con la instalación de Python porque acabo de intentado, y funciona en [Python 2.6.4 en ideone] (http://ideone.com/GBeit) y Python 2.7.2 en mi máquina. – kennytm

6

Esto es lo que ocurrió:

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

y decir que es así:

print tuple(pad_strings(x)) 

produce este resultado:

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

solo por diversión - o ne liner

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

este es un gran ejemplo de cuándo un trazador de líneas es ** inadecuado ** – jterrace

0

Estoy de acuerdo con todos los demás, que debe haber dos pases. El pase 1 calcula el ancho máximo para cada columna y pasa 2 almohadillas por cada celda al ancho de su columna.

El siguiente código se basa en las funciones integradas de Python map() y reduce(). El inconveniente es que las expresiones son posiblemente más crípticas. Intenté compensar eso con mucha sangría. El beneficio es que el código se beneficia de cualquier optimización de bucle que la implementación haya realizado en estas funciones.

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

Esto da salida (con saltos de línea adicionales para la legibilidad):

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

que he probado este código. Las expresiones ... if cell is None else cell son detalladas, pero necesarias para que las expresiones realmente funcionen.

Cuestiones relacionadas