2012-01-16 14 views
10

Ho una lista di liste che rappresentano una griglia di dati (si pensi a righe in un foglio di calcolo). Ogni riga può avere un numero arbitrario di colonne e i dati in ogni cella sono una stringa di lunghezza arbitraria.come normalizzare la lista di liste di stringhe in python?

Voglio normalizzarlo, in effetti, rendere ogni riga ha lo stesso numero di colonne e ogni colonna nei dati ha la stessa larghezza, riempire con spazi, se necessario. Ad esempio, dato il seguente testo:

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

Voglio i dati a guardare come questo:

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

Qual è la soluzione divinatorio per Python 2.6 o superiore? Giusto per essere chiari: non sto cercando di stampare la lista di per sé, sto cercando una soluzione che restituisca un nuovo elenco di liste (o tuple di tuple) con i valori inseriti.

+1

Giusto per essere * perfettamente chiaro *: vuoi i dati contenuti in tuple o formattati come linee di stringhe? – Makoto

+0

Voglio i dati in tuple, come indicato nell'ultima riga della domanda: "Sto cercando una soluzione che restituisca un nuovo elenco di liste (o tuple di tuple) con i valori inseriti." –

risposta

7

A partire con i dati di input:

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

fare un passaggio per determinare la dimensione massima di ogni colonna:

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

poi fare un secondo passaggio per riempire ogni colonna alla larghezza desiderata:

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

che dà il risultato desiderato:

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

Per comodità, i passaggi possono essere combinati in un'unica funzione:

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

See:

+1

Quel codice ha molti errori in esso. Sei in grado di eseguirlo sul tuo computer? Mancano le parentesi di chiusura, presumo che 'zip_longest' fosse inteso come' izip_longest', c'è un problema di indentazione dopo il ciclo for, e anche quando risolvo quelli ho ancora un errore. –

+0

Questo è un modello su cui basare la tua soluzione e utilizza Python 3. –

0

posso solo pensare di fare questo passando attraverso due volte - ma non dovrebbe essere difficile:

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

io suggerirei di utilizzare list invece di tuple. tuple s sono immutabili e difficili da lavorare.

Innanzitutto, trova la lunghezza della riga più lunga.

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

Poi pad ciascuna riga facendo necessario numero di stringhe:

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

Quindi è possibile intercambiare le righe e le colonne cioè colonne devono essere righe e viceversa.Per quello puoi scrivere

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

Ora puoi prendere una riga (una colonna della vecchia lista) e tamponare le stringhe come richiesto.

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

Ora convertire la tabella indietro al formato originale:

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

per mettere insieme in una funzione:

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

Questa soluzione funziona per list s.

2

Prima di tutto, definire una funzione 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)) 

poi pad ogni voce per la stessa lunghezza dalla '':

a = # your list of list of string 

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

poi, attuare la presente lista di lista in modo che possiamo lavorare colonna per colonna,

a_tr = zip(*a_padded) 

per ogni riga, troviamo la lunghezza massima dello str ing, quindi incollarlo alla lunghezza specificata.

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

finalmente lo trasponiamo nuovamente e valutiamo il risultato.

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

Usa tuple(tuple(x) for ...) se si desidera una tupla di tuple invece di lista di lista.

Demo: http://ideone.com/4d0DE

+0

Questo è più o meno quello che stavo cercando, anche se questo sembra richiedere python 3 (?). Almeno, il codice demo non funziona sulla mia installazione 2.7: 'TypeError: zip() argomento dopo * deve essere una sequenza, non generatore' –

+0

@BryanOakley: Ci deve essere qualche problema con l'installazione di Python perché ho solo provato, e funziona su [Python 2.6.4 su ideone] (http://ideone.com/GBeit) e Python 2.7.2 sulla mia macchina. – kennytm

6

Ecco cosa mi è venuta:

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

e chiamando in questo modo:

print tuple(pad_strings(x)) 

cede questo risultato:

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

solo per divertimento - 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

questo è un ottimo esempio di quando un liner è ** non ** appropriato – jterrace

0

Sono d'accordo con tutti gli altri, che ci dovrebbero essere due passaggi. Il passaggio 1 calcola la larghezza massima per ogni colonna e passa 2 pastiglie ogni cella alla larghezza della sua colonna.

Il seguente codice si basa sulle funzioni incorporate di Python map() e reduce(). Lo svantaggio è che le espressioni sono probabilmente più criptiche. Ho provato a compensare questo con un sacco di indentazione. Il vantaggio è che il codice beneficia di tutte le ottimizzazioni del ciclo eseguite dall'implementazione in queste funzioni.

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

Questo dà in uscita (con le interruzioni di riga aggiunte per la leggibilità):

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

che ho provato questo codice. Le espressioni ... if cell is None else cell sono dettagliate, ma necessarie per far funzionare effettivamente le espressioni.

Problemi correlati