How to print a list more nicely?
Question:
This is similar to How to print a list in Python “nicely”, but I would like to print the list even more nicely — without the brackets and apostrophes and commas, and even better in columns.
foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
evenNicerPrint(foolist)
Desired result:
exiv2-devel msvcrt
mingw-libs gdal-grass
tcltk-demos iconv
fcgi qgis-devel
netcdf qgis1.1
pdcurses-devel php_mapscript
thanks!
Answers:
Simple:
l = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
if len(l) % 2 != 0:
l.append(" ")
split = len(l)/2
l1 = l[0:split]
l2 = l[split:]
for key, value in zip(l1,l2):
print '%-20s %s' % (key, value) #python <2.6
print "{0:<20s} {1}".format(key, value) #python 2.6+
If the data is in the format you have provided, it is a little more work
>>> d = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
... 'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
... 'qgis1.1', 'php_mapscript']
>>> print "n".join("%-20s %s"%(d[i],d[i+len(d)/2]) for i in range(len(d)/2))
exiv2-devel msvcrt
mingw-libs gdal-grass
tcltk-demos iconv
fcgi qgis-devel
netcdf qgis1.1
pdcurses-devel php_mapscript
See formatting-a-list-of-text-into-columns,
A general solution, handles any number of columns and odd lists.
Tab characters separate columns, using generator expressions to save space.
def fmtcols(mylist, cols):
lines = ("t".join(mylist[i:i+cols]) for i in xrange(0,len(mylist),cols))
return 'n'.join(lines)
The way Aaron has done it can work with more than two colums
>>> l = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
... 'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
... 'qgis1.1', 'php_mapscript']
>>> cols = 4
>>> split=[l[i:i+len(l)/cols] for i in range(0,len(l),len(l)/cols)]
>>> for row in zip(*split):
... print "".join(str.ljust(i,20) for i in row)
...
exiv2-devel fcgi msvcrt qgis-devel
mingw-libs netcdf gdal-grass qgis1.1
tcltk-demos pdcurses-devel iconv php_mapscript
from itertools import izip_longest, islice
L = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
def columnize(sequence, columns=2):
size, remainder = divmod(len(sequence), columns)
if remainder:
size += 1
slices = [islice(sequence, pos, pos + size)
for pos in xrange(0, len(sequence), size)]
return izip_longest(fillvalue='', *slices)
for values in columnize(L):
print ' '.join(value.ljust(20) for value in values)
This answer uses the same method in the answer by @Aaron Digulla, with slightly more pythonic syntax. It might make some of the above answers easier to understand.
>>> for a,b,c in zip(foolist[::3],foolist[1::3],foolist[2::3]):
>>> print '{:<30}{:<30}{:<}'.format(a,b,c)
exiv2-devel mingw-libs tcltk-demos
fcgi netcdf pdcurses-devel
msvcrt gdal-grass iconv
qgis-devel qgis1.1 php_mapscript
This can be easily adapt to any number of columns or variable columns, which would lead to something like the answer by @gnibbler. The spacing can be adjusted for screen width.
Update: Explanation as requested.
Indexing
foolist[::3]
selects every third element of foolist
. foolist[1::3]
selects every third element, starting at the second element (‘1’ because python uses zero-indexing).
In [2]: bar = [1,2,3,4,5,6,7,8,9]
In [3]: bar[::3]
Out[3]: [1, 4, 7]
zip
Zipping lists (or other iterables) generates tuples of the elements of the the lists. For example:
In [5]: zip([1,2,3],['a','b','c'],['x','y','z'])
Out[5]: [(1, 'a', 'x'), (2, 'b', 'y'), (3, 'c', 'z')]
together
Putting these ideas together we get our solution:
for a,b,c in zip(foolist[::3],foolist[1::3],foolist[2::3]):
Here we first generate three “slices” of foolist
, each indexed by every-third-element and offset by one. Individually they each contain only a third of the list. Now when we zip these slices and iterate, each iteration gives us three elements of foolist
.
Which is what we wanted:
In [11]: for a,b,c in zip(foolist[::3],foolist[1::3],foolist[2::3]):
....: print a,b,c
Out[11]: exiv2-devel mingw-libs tcltk-demos
fcgi netcdf pdcurses-devel
[etc]
Instead of:
In [12]: for a in foolist:
....: print a
Out[12]: exiv2-devel
mingw-libs
[etc]
Found this question as a met almost the same task. And I’ve created function to print list in multi columns with the number of columns as parameter. Maybe not so elegant as one-liner solutions, but it could be useful for someone.
However, it handles incomplete lists, ex.: it can print list of 11 in 3 rows.
Function splitted for better readability:
def is_printable(my_list):
return len(my_list) > 0
def create_empty_list(columns):
result = []
for num in range(0, columns):
result.append([])
return result
def fill_empty_list(empty_list, my_list, columns):
column_depth = len(my_list) / columns if len(my_list) % columns == 0 else len(my_list) / columns + 1
item_index = 0
for column in range(0, columns):
while len(empty_list[column]) < column_depth:
if item_index < len(my_list):
empty_list[column].append(my_list[item_index])
else:
empty_list[column].append(" ") # last column could be incomplete, fill it with space
item_index += 1
def print_list_in_columns(my_list, columns=1):
if not is_printable(my_list):
print 'Nothing to print, sorry...'
return
column_width = 25 #(in symbols) Also can be calculated automatically
list_to_print = create_empty_list(columns)
fill_empty_list(list_to_print, my_list, columns)
iterators = ["it" + str(i) for i in range(0, columns)]
for iterators in zip(*list_to_print):
print ("".join(str.ljust(i, column_width) for i in iterators))
and the call part:
foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
print_list_in_columns(foolist, 2)
Here’s my solution. (Copy in GitHub gist)
It takes terminal width as input and displays only as many columns that can be fit in it.
def col_print(lines, term_width=80, indent=0, pad=2):
n_lines = len(lines)
if n_lines == 0:
return
col_width = max(len(line) for line in lines)
n_cols = int((term_width + pad - indent)/(col_width + pad))
n_cols = min(n_lines, max(1, n_cols))
col_len = int(n_lines/n_cols) + (0 if n_lines % n_cols == 0 else 1)
if (n_cols - 1) * col_len >= n_lines:
n_cols -= 1
cols = [lines[i*col_len : i*col_len + col_len] for i in range(n_cols)]
rows = list(zip(*cols))
rows_missed = zip(*[col[len(rows):] for col in cols[:-1]])
rows.extend(rows_missed)
for row in rows:
print(" "*indent + (" "*pad).join(line.ljust(col_width) for line in row))
Here’s a solution in python 3.4 that automatically detects terminal width and takes it into account. Tested on Linux and Mac.
def column_print(list_to_print, column_width=40):
import os
term_height, term_width = os.popen('stty size', 'r').read().split()
total_columns = int(term_width) // column_width
total_rows = len(list_to_print) // total_columns
# ceil
total_rows = total_rows + 1 if len(list_to_print) % total_columns != 0 else total_rows
format_string = "".join(["{%d:<%ds}" % (c, column_width)
for c in range(total_columns)])
for row in range(total_rows):
column_items = []
for column in range(total_columns):
# top-down order
list_index = row + column*total_rows
# left-right order
#list_index = row*total_columns + column
if list_index < len(list_to_print):
column_items.append(list_to_print[list_index])
else:
column_items.append("")
print(format_string.format(*column_items))
Inspired by gimel’s answer, above.
import math
def list_columns(obj, cols=4, columnwise=True, gap=4):
"""
Print the given list in evenly-spaced columns.
Parameters
----------
obj : list
The list to be printed.
cols : int
The number of columns in which the list should be printed.
columnwise : bool, default=True
If True, the items in the list will be printed column-wise.
If False the items in the list will be printed row-wise.
gap : int
The number of spaces that should separate the longest column
item/s from the next column. This is the effective spacing
between columns based on the maximum len() of the list items.
"""
sobj = [str(item) for item in obj]
if cols > len(sobj): cols = len(sobj)
max_len = max([len(item) for item in sobj])
if columnwise: cols = int(math.ceil(float(len(sobj)) / float(cols)))
plist = [sobj[i: i+cols] for i in range(0, len(sobj), cols)]
if columnwise:
if not len(plist[-1]) == cols:
plist[-1].extend(['']*(len(sobj) - len(plist[-1])))
plist = zip(*plist)
printer = 'n'.join([
''.join([c.ljust(max_len + gap) for c in p])
for p in plist])
print printer
Results (the second one satisfies your request):
>>> list_columns(foolist)
exiv2-devel fcgi msvcrt qgis-devel
mingw-libs netcdf gdal-grass qgis1.1
tcltk-demos pdcurses-devel iconv php_mapscript
>>> list_columns(foolist, cols=2)
exiv2-devel msvcrt
mingw-libs gdal-grass
tcltk-demos iconv
fcgi qgis-devel
netcdf qgis1.1
pdcurses-devel php_mapscript
>>> list_columns(foolist, columnwise=False)
exiv2-devel mingw-libs tcltk-demos fcgi
netcdf pdcurses-devel msvcrt gdal-grass
iconv qgis-devel qgis1.1 php_mapscript
>>> list_columns(foolist, gap=1)
exiv2-devel fcgi msvcrt qgis-devel
mingw-libs netcdf gdal-grass qgis1.1
tcltk-demos pdcurses-devel iconv php_mapscript
I extend an n
column solution to @Aman‘s answer
def printMultiCol(l, n_cols, buffer_len=5):
"""formats a list of strings, l, into n_cols with a separation of buffer_len"""
if not l: return [] # return if not iterable!
max_l = max(map(len, l))
formatter = '{{:<{max_l}}}'.format(max_l=max_l+buffer_len)*n_cols
zip_me_up = [l[i::n_cols] for i in xrange(n_cols)]
max_zip_l = max(map(len, zip_me_up))
zip_me_up = map(lambda x: x + ['']*(max_zip_l-len(x)), zip_me_up)
return [formatter.format(*undress_me) for undress_me in zip(*zip_me_up)]
Testing
Set up the test with random string lengths
import random
list_length = 16
random_strings = [
''.join(random.choice('spameggsbaconbeanssausage')
for x in range(random.randint(1,10)))
for i in xrange(list_length)
]
print 'for 4 columns (equal length cols) ...n{}'.format(
'n'.join(printMultiCol(random_strings, 4))
)
print 'for 7 columns (odd length cols) ...n{}'.format(
'n'.join(printMultiCol(random_strings, 5))
)
which returns
## -- End pasted text --
for 4 columns (equal length cols) ...
sgsebpasgm assgaesse ossmeagan ebesnagec
mees eeges m gcb
sm pbe bbgaa ganopabnn
bmou asbegu a psoge
for 7 columns (odd length cols) ...
sgsebpasgm assgaesse ossmeagan ebesnagec mees
eeges m gcb sm pbe
bbgaa ganopabnn bmou asbegu a
psoge
It’s useful to allow for uneven columns, without having to know in advance how many columns you can fit:
>>> words = [string.ascii_lowercase] + list(string.ascii_lowercase)
>>> print format_list(words)
abcdefghijklmnopqrstuvwxyz b d f h j l n p r t v x z
a c e g i k m o q s u w y
For your example:
>>> foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi',
... 'netcdf', 'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv',
... 'qgis-devel', 'qgis1.1', 'php_mapscript']
>>> print format_list(foolist, spacing=4, width=31)
exiv2-devel msvcrt
mingw-libs gdal-grass
tcltk-demos iconv
fcgi qgis-devel
netcdf qgis1.1
pdcurses-devel php_mapscript
Here is the code. Note that it also handles words with ANSI color codes (such as from the colorama package) – they won’t mess up the column widths.
ansi_pattern = re.compile(r'x1b[d{1,2}m')
def get_nchars(string):
"""Return number of characters, omitting ANSI codes."""
return len(ansi_pattern.sub('', string))
def format_list(items, indent=0, spacing=2, width=79):
"""Return string listing items along columns.
items : sequence
List of items to display that must be directly convertible into
unicode strings. ANSI color codes may be present, and are taken
into account in determining column widths
indent : int
Number of spaces in left margin.
spacing : int
Number of spaces between columns.
width : int
Maximum number of characters per line, including indentation.
"""
if not items:
return u''
# Ensure all items are strings
items = [unicode(item) for item in items]
# Estimate number of columns based on shortest and longest items
minlen = min(get_nchars(item) for item in items)
maxlen = max(get_nchars(item) for item in items)
# Assume one column with longest width, remaining with shortest.
# Use negative numbers for ceiling division.
ncols = 1 - (-(width - indent - maxlen) // (spacing + min(1, minlen)))
ncols = max(1, min(len(items), ncols))
# Reduce number of columns until items fit (or only one column)
while ncols >= 1:
# Determine number of rows by ceiling division
nrows = -(-len(items) // ncols)
# Readjust to avoid empty last column
ncols = -(-len(items) // nrows)
# Split items into columns, and test width
columns = [items[i*nrows:(i+1)*nrows] for i in range(ncols)]
totalwidth = indent - spacing + sum(
spacing + max(get_nchars(item) for item in column)
for column in columns
)
# Stop if columns fit. Otherwise, reduce number of columns and
# try again.
if totalwidth <= width:
break
else:
ncols -= 1
# Pad all items to column width
for i, column in enumerate(columns):
colwidth = max(get_nchars(item) for item in column)
columns[i] = [
item + ' ' * (colwidth - get_nchars(item))
for item in column
]
# Transpose into rows, and return joined rows
rows = list(itertools.izip_longest(*columns, fillvalue=''))
return 'n'.join(
' ' * indent + (u' ' * spacing).join(row).rstrip()
for row in rows
)
How about something like this?
def strlistToColumns( strl, maxWidth, spacing=4 ):
longest = max([len(s) for s in strl])
width = longest+spacing
# compute numCols s.t. (numCols-1)*(longest+spacing)+longest < maxWidth
numCols = 1 + (maxWidth-longest)//width
C = range(numCols)
# If len(strl) does not have a multiple of numCols, pad it with empty strings
strl += [""]*(len(strl) % numCols)
numRows = len(strl)/numCols
colString = ''
for r in range(numRows):
colString += "".join(["{"+str(c)+":"+str(width)+"}"
for c in C]+["n"]).format(*(strl[numCols*r+c]
for c in C))
return colString
if __name__ == '__main__':
fruits = ['apple', 'banana', 'cantaloupe', 'durian', 'elderberry',
'fig', 'grapefruit', 'honeydew', 'indonesian lime', 'jackfruit',
'kiwi', 'lychee', 'mango', 'orange', 'pomegranate', 'quince',
'raspberry', 'tangerine', 'ugli fruit', 'watermelon', 'xigua',
'yangmei', 'zinfandel grape']
cols = strlistToColumns( fruits, 80 )
print(cols)
Output
apple banana cantaloupe durian
elderberry fig grapefruit honeydew
indonesian lime jackfruit kiwi lychee
mango orange pomegranate quince
raspberry tangerine ugli fruit watermelon
xigua yangmei zinfandel grape
[print('{:20}'.format(key), end='t') if (idx + 1) % 5 else print(key, end='n') for idx, key in enumerate(list_variable)]
or
for idx, key in enumerate(list_variable):
if (idx + 1) % 5:
print('{:20}'.format(key), end='t')
else:
print(key, end='n')
There are tons of answers already, but I will share my solution, which in addition to printing the list into multiple columns, it also chooses the amount of columns dynamically, from the terminal width and the longest string on the list.
import os
cols = os.popen('stty size', 'r').read().split()[1]
def print_multicol(my_list):
max_len = len(max(my_list,key=len)) + 2
ncols = (int(cols) -4 ) / max_len
while my_list:
n = 0
while n < ncols:
if len(my_list) > 0 :
fstring = "{:<"+str(max_len)+"}"
print fstring.format(my_list.pop(0)),
n += 1
print
a_list = "a ab abc abcd abcde b bc bcde bcdef c cde cdef cdfg d de defg"
a_list += "defgh e ef efg efghi efghij f fg fgh fghij fghijk"
print_multicol(a_list.split())
As an expansion of @Aman below is a function which takes a list of strings and outputs them in columns based on the terminal size.
import os
def column_display(input_list):
'''
Used to create a structured column display based on the users terminal size
input_list : A list of string items which is desired to be displayed
'''
rows, columns = os.popen('stty size', 'r').read().split()
terminal_space_eighth = int(columns)/8
terminal_space_seventh = int(columns)/7
terminal_space_sixth = int(columns)/6
terminal_space_fifth = int(columns)/5
terminal_space_quarter = int(columns)/4
terminal_space_third = int(columns)/3
terminal_space_half = int(columns)/2
longest_string = max(input_list, key=len)
longest_length = len(longest_string) + 1
list_size = len(input_list)
if longest_length > terminal_space_half:
for string in input_list:
print(string)
elif terminal_space_eighth >= longest_length and list_size >= 8:
for a,b,c,d,e,f,g,h in zip(input_list[::8],input_list[1::8],input_list[2::8], input_list[3::8], input_list[4::8], input_list[5::8], input_list[6::8], input_list[7::8]):
column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length, longest_length, longest_length, longest_length )
output = column_space.format(a,b,c,d,e,f,g,h)
print(output)
elif terminal_space_seventh >= longest_length and list_size >= 7:
for a,b,c,d,e,f,g in zip(input_list[::7],input_list[1::7],input_list[2::7], input_list[3::7], input_list[4::7], input_list[5::7], input_list[6::7]):
column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length, longest_length, longest_length)
output = column_space.format(a,b,c,d,e,f,g)
print(output)
elif terminal_space_sixth >= longest_length and list_size >= 6:
for a,b,c,d,e,f in zip(input_list[::6],input_list[1::6],input_list[2::6], input_list[3::6], input_list[4::6], input_list[5::6]):
column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length, longest_length)
output = column_space.format(a,b,c,d,e,f)
print(output)
elif terminal_space_fifth >= longest_length and list_size >= 5:
for a,b,c,d,e in zip(input_list[::5],input_list[1::5],input_list[2::5], input_list[3::5], input_list[4::5]):
column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length)
output = column_space.format(a,b,c,d,e)
print(output)
elif terminal_space_quarter >= longest_length and list_size >= 4:
for a,b,c,d in zip(input_list[::4],input_list[1::4],input_list[2::4], input_list[3::4]):
column_space = '{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length)
output = column_space.format(a,b,c,d)
print(output)
elif terminal_space_third >= longest_length and list_size >= 3:
for a,b,c in zip(input_list[::3],input_list[1::3],input_list[2::3]):
column_space = '{:<%s}{:<%s}{:<}' % (longest_length, longest_length)
output = column_space.format(a,b,c)
print(output)
elif terminal_space_half >= longest_length and list_size >= 2:
for a,b in zip(input_list[::2],input_list[1::2]):
column_space = '{:<%s}{:<}' % longest_length
output = column_space.format(a,b)
print(output)
As an explanation this does a few different things.
First it gets the number of columns for the current user’s terminal using os.popen.
Second it takes the number of columns and divides in half, increasing to eighth. This will be used to compare the longest string in the list to determine the number of columns best suited for this.
Third is the longest string of the list pulled using the build in python function max().
Forth the length of the longest string is taken and then has one added to it for padding. The length of the list is taken as well so that if the list is less than 8 items it will only list the number of items that exist.
Fifth the longest string length is compared to each of the terminal spaces from one column to eight. If the column is greater than or equal to the length then it can be used. For example is the longest string is 10 and the columns divided by eight(terminal_space_eighth) is 8 but columns divided by seven(terminal_space_seventh) is 12, there will be seven columns. There will be seven because the longest string can fit in 12 characters but not in 8 characters.
It’s also worth noting the length of the list is taken into consideration to prevent creating more columns than list items.
Sixth is an expansion of the explanation by @Aman : https://stackoverflow.com/a/1524132/11002603
Indexing
Lets let i represent the number determined by terminal size for the sake of this example.
input_list[::i]
This selects element at i. Adding a number at the front such as input_list[1::i]
offsets the starting point(remember python considers 0 a valid number which is why it’s not used initially.)
Zipping
Zip is used to create a tuple with elements of a list. For example The output list will look similar to below
zip([string1,string2,string3], [string4,string5, string6], [string7,string8,string9])
output : [(string1,string4,string7), (string2,string5, string8), (string3,string6,string9)]
Using together
Depending on the number of columns, the letters are just used to represent a split. So for example if only 5 columns fit in the terminal, the following will be used
for a,b,c,d,e in zip(input_list[::5],input_list[1::5],input_list[2::5], input_list[3::5], input_list[4::5]):
This will take of the tuples created from zipping and store then as a,b,c,d and e variables so we can call them within the loop.
The column space is then used for format each of a,b,c,d and e into respective columns and is where the length of each column is determined. The length is based on the string length determined above.
I needed to adjust each of the columns. I have implemented this code
def print_sorted_list(data, columns):
if data:
gap = 2
ljusts = {}
for count, item in enumerate(sorted(data), 1):
column = count % columns
ljusts[column] = len(item) if (column not in ljusts) else max(ljusts[column], len(item))
for count, item in enumerate(sorted(data), 1):
print item.ljust(ljusts[count % columns] + gap),
if (count % columns == 0) or (count == len(data)):
print
Example:
foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript', 'blablablablablablabla', 'fafafafafafa']
print_sorted_list(foolist, 4)
Output:
blablablablablablabla exiv2-devel fafafafafafa fcgi
gdal-grass iconv mingw-libs msvcrt
netcdf pdcurses-devel php_mapscript qgis-devel
qgis1.1 tcltk-demos
Here is a straightforward way. See the inline comments for explanation:
import shutil
import itertools
from functools import reduce
def split_list(lst, ncols):
"""Split list into rows"""
return itertools.zip_longest(
*[lst[i::ncols] for i in range(ncols)], fillvalue=""
)
# -- Alternatively --
# import numpy as np
# array = np.array(lst)
# nrows = array.size / ncols + 1
# return np.array_split(array, int(nrows))
def print_in_columns(lst):
"""Print a list in columns."""
# Find maximum length of a string in colors_list
colsize = reduce(lambda x, y: max(x, len(y)), lst, 0)
# Terminal width
maxcols = shutil.get_terminal_size()[0]
ncols = maxcols / (colsize + 1)
rows = split_list(lst, int(ncols))
print(
# Join rows
"n".join(
(
# Fill items left justified
" ".join(item.ljust(colsize) for item in row)
for row in rows
)
)
)
Although not designed for it, the standard-library module in Python 3 cmd
has a utility for printing a list of strings in multiple columns
import cmd
cli = cmd.Cmd()
cli.columnize(foolist, displaywidth=80)
You even then have the option of specifying the output location, with cmd.Cmd(stdout=my_stream)
this one prints list in separate columns (order is preserved)
from itertools import zip_longest
def ls(items, n_cols=2, pad=30):
if len(items) == 0:
return
total = len(items)
chunk_size = total // n_cols
if chunk_size * n_cols < total:
chunk_size += 1
start = range(0, total, chunk_size)
end = range(chunk_size, total + chunk_size, chunk_size)
groups = (items[s:e] for s, e in zip(start, end))
for group in zip_longest(*groups, fillvalue=''):
template = (' ').join(['%%-%ds' % pad] * len(group))
print(template % group)
usage:
ls([1, 2, 3, 4, 5, 6, 7], n_cols=3, pad=10)
output:
1 4 7
2 5
3 6
note that there may be missing columns if there is not enough number of items, because columns are filled first.
ls([1, 2, 3, 4, 5], n_cols=4)
output:
1 3 5
2 4
For Python >=3.6, slight update to @JoshuaZastrow‘s answer using f-strings
and adding clear method to adjust columns
cols = 5
[print(f'{key:20}', end='t') if (idx + 1) % cols else print(f'{key}') for idx, key in enumerate(list_variable)]
or
cols = 5
for idx, key in enumerate(list_variable):
if (idx + 1) % cols:
print(f'{key:20}', end='t')
else:
print(f'{key}')
Here is the short and simple method:
def printList1(list, col, STR_FMT='{}', gap=1):
list = [STR_FMT.format(x).lstrip() for x in list]
FMT2 = '%%%ds%%s' % (max(len(x) for x in list)+gap)
print(''.join([FMT2 % (v, "" if (i+1) % col else "n") for i, v in enumerate(list)]))
Then here is the better method which can turn off the constant width across the entire list instead optimizing it across the columns, restrict the number of columns to fit the max characters per line or find the optimal number of columns to fit the max characters per line, then force left or right justify, and can still can take the optional format string, and gap width between each column.
def printList2(list, col=None, gap=2, uniform=True, ljust=True, STR_FMT="{}", MAX_CHARS=120, end='n'):
list = [STR_FMT.format(x).strip() for x in list]
Lmax, valid, valid_prev, cp, c = [MAX_CHARS+1], None, None, 1, max(col,1) if col else 1
LoL_prev, Lmax_prev = [], []
while True:
LoL = [list[i::c] for i in range(c)]
Lmax = [max(len(x)+gap for x in L) for L in LoL] # Find max width of each column with gap width
if uniform: # Set each max column width to max across entire set.
Lmax = [max(Lmax) for m in Lmax]
valid_prev, valid = valid, sum(Lmax) <= MAX_CHARS
if (col and (valid or (c == 1))) or not MAX_CHARS: # If column and valid strlen or MAX_CHARS is empty
break
elif valid_prev and not valid_prev == valid: # If valid_prev exist
c = cp if valid_prev and not valid else c
LoL, Lmax = (LoL_prev, Lmax_prev) if valid_prev else (LoL, Lmax)
break
LoL_prev, Lmax_prev = LoL, Lmax
cp, c = c, (c + (+1 if valid else -1))
ljust = '-' if ljust else ''
FMT = ["%%%s%ds%s" % (ljust, max(Lmax) if uniform else m, end if i+1 == c else '') for i, m in enumerate(Lmax)]
outStr = ''.join([''.join([f % v for v, f in zip(L, FMT)]) for L in zip(*LoL)])
remStr = ''.join([f % v for v, f in zip(list[c * (len(list) // c):], FMT)])
print(outStr+(remStr+end if remStr else remStr), end='')
Testing w/ outputs:
>>> foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
>>> printList2(foolist)
exiv2-devel mingw-libs tcltk-demos fcgi netcdf pdcurses-devel msvcrt
gdal-grass iconv qgis-devel qgis1.1 php_mapscript
>>> printList2(foolist, MAX_CHARS=48, uniform=False, gap=3)
exiv2-devel mingw-libs tcltk-demos
fcgi netcdf pdcurses-devel
msvcrt gdal-grass iconv
qgis-devel qgis1.1 php_mapscript
>>> printList2(foolist, col=2, MAX_CHARS=48, uniform=False, gap=3)
exiv2-devel mingw-libs
tcltk-demos fcgi
netcdf pdcurses-devel
msvcrt gdal-grass
iconv qgis-devel
qgis1.1 php_mapscript
>>> printList2(foolist, col=2, MAX_CHARS=48, uniform=False, ljust=False, gap=2)
exiv2-devel mingw-libs
tcltk-demos fcgi
netcdf pdcurses-devel
msvcrt gdal-grass
iconv qgis-devel
qgis1.1 php_mapscript
>>> printList2(foolist, col=10, MAX_CHARS=48, uniform=True, ljust=False, gap=2)
exiv2-devel mingw-libs tcltk-demos
fcgi netcdf pdcurses-devel
msvcrt gdal-grass iconv
qgis-devel qgis1.1 php_mapscript
>>> from math import pi
>>> FloatList = [pi**(i+1) for i in range(32)]
>>> printList2(FloatList, STR_FMT="{:.5g},", col=7, ljust=False)
3.1416, 9.8696, 31.006, 97.409, 306.02, 961.39, 3020.3,
9488.5, 29809, 93648, 2.942e+05, 9.2427e+05, 2.9037e+06, 9.1222e+06,
2.8658e+07, 9.0032e+07, 2.8284e+08, 8.8858e+08, 2.7916e+09, 8.77e+09, 2.7552e+10,
8.6556e+10, 2.7192e+11, 8.5427e+11, 2.6838e+12, 8.4313e+12, 2.6488e+13, 8.3214e+13,
2.6142e+14, 8.2129e+14, 2.5802e+15, 8.1058e+15,
I use IPython internal columnize function
import IPython
foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
foolist_columnized = IPython.utils.text.columnize(foolist)
print(foolist_columnized)
output will look like:
exiv2-devel tcltk-demos netcdf msvcrt iconv qgis1.1
mingw-libs fcgi pdcurses-devel gdal-grass qgis-devel php_mapscript
For Python3, I used python – How do you split a list into evenly sized chunks? – Stack Overflow to create
def chunkSectionList(listToPrint, columns):
""" separate a list into chunks of n-items """
for i in range(0, len(listToPrint), columns):
yield listToPrint[i:i + columns]
def printSections(listToPrint, columns):
remainder = len(listToPrint) % columns
listToPrint += (columns - remainder) *
(" ") if remainder != 0 else listToPrint
for sectionsLine in chunkSectionList(listToPrint, columns):
formatStr = columns * '{:<30}'
print(formatStr.format(* sectionsLine))
A succint variation that adapts the column width to the longest item in that column:
from itertools import zip_longest
def print_columns(obj_to_print, col_length, gutter):
columns = []
for i in range(1 + len(obj_to_print) // col_length):
col_items = [str(item) for item in obj_to_print[i*col_length:(i+1)*col_length]]
col_width = len(max(col_items, key=len))
columns.append([f"{item:{col_width}}" for item in col_items])
for row in zip_longest(*columns, fillvalue=""):
print(f"{' ' * gutter}".join(row))
This is similar to How to print a list in Python “nicely”, but I would like to print the list even more nicely — without the brackets and apostrophes and commas, and even better in columns.
foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
evenNicerPrint(foolist)
Desired result:
exiv2-devel msvcrt
mingw-libs gdal-grass
tcltk-demos iconv
fcgi qgis-devel
netcdf qgis1.1
pdcurses-devel php_mapscript
thanks!
Simple:
l = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
if len(l) % 2 != 0:
l.append(" ")
split = len(l)/2
l1 = l[0:split]
l2 = l[split:]
for key, value in zip(l1,l2):
print '%-20s %s' % (key, value) #python <2.6
print "{0:<20s} {1}".format(key, value) #python 2.6+
If the data is in the format you have provided, it is a little more work
>>> d = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
... 'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
... 'qgis1.1', 'php_mapscript']
>>> print "n".join("%-20s %s"%(d[i],d[i+len(d)/2]) for i in range(len(d)/2))
exiv2-devel msvcrt
mingw-libs gdal-grass
tcltk-demos iconv
fcgi qgis-devel
netcdf qgis1.1
pdcurses-devel php_mapscript
See formatting-a-list-of-text-into-columns,
A general solution, handles any number of columns and odd lists.
Tab characters separate columns, using generator expressions to save space.
def fmtcols(mylist, cols):
lines = ("t".join(mylist[i:i+cols]) for i in xrange(0,len(mylist),cols))
return 'n'.join(lines)
The way Aaron has done it can work with more than two colums
>>> l = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
... 'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
... 'qgis1.1', 'php_mapscript']
>>> cols = 4
>>> split=[l[i:i+len(l)/cols] for i in range(0,len(l),len(l)/cols)]
>>> for row in zip(*split):
... print "".join(str.ljust(i,20) for i in row)
...
exiv2-devel fcgi msvcrt qgis-devel
mingw-libs netcdf gdal-grass qgis1.1
tcltk-demos pdcurses-devel iconv php_mapscript
from itertools import izip_longest, islice
L = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
def columnize(sequence, columns=2):
size, remainder = divmod(len(sequence), columns)
if remainder:
size += 1
slices = [islice(sequence, pos, pos + size)
for pos in xrange(0, len(sequence), size)]
return izip_longest(fillvalue='', *slices)
for values in columnize(L):
print ' '.join(value.ljust(20) for value in values)
This answer uses the same method in the answer by @Aaron Digulla, with slightly more pythonic syntax. It might make some of the above answers easier to understand.
>>> for a,b,c in zip(foolist[::3],foolist[1::3],foolist[2::3]):
>>> print '{:<30}{:<30}{:<}'.format(a,b,c)
exiv2-devel mingw-libs tcltk-demos
fcgi netcdf pdcurses-devel
msvcrt gdal-grass iconv
qgis-devel qgis1.1 php_mapscript
This can be easily adapt to any number of columns or variable columns, which would lead to something like the answer by @gnibbler. The spacing can be adjusted for screen width.
Update: Explanation as requested.
Indexing
foolist[::3]
selects every third element of foolist
. foolist[1::3]
selects every third element, starting at the second element (‘1’ because python uses zero-indexing).
In [2]: bar = [1,2,3,4,5,6,7,8,9]
In [3]: bar[::3]
Out[3]: [1, 4, 7]
zip
Zipping lists (or other iterables) generates tuples of the elements of the the lists. For example:
In [5]: zip([1,2,3],['a','b','c'],['x','y','z'])
Out[5]: [(1, 'a', 'x'), (2, 'b', 'y'), (3, 'c', 'z')]
together
Putting these ideas together we get our solution:
for a,b,c in zip(foolist[::3],foolist[1::3],foolist[2::3]):
Here we first generate three “slices” of foolist
, each indexed by every-third-element and offset by one. Individually they each contain only a third of the list. Now when we zip these slices and iterate, each iteration gives us three elements of foolist
.
Which is what we wanted:
In [11]: for a,b,c in zip(foolist[::3],foolist[1::3],foolist[2::3]):
....: print a,b,c
Out[11]: exiv2-devel mingw-libs tcltk-demos
fcgi netcdf pdcurses-devel
[etc]
Instead of:
In [12]: for a in foolist:
....: print a
Out[12]: exiv2-devel
mingw-libs
[etc]
Found this question as a met almost the same task. And I’ve created function to print list in multi columns with the number of columns as parameter. Maybe not so elegant as one-liner solutions, but it could be useful for someone.
However, it handles incomplete lists, ex.: it can print list of 11 in 3 rows.
Function splitted for better readability:
def is_printable(my_list):
return len(my_list) > 0
def create_empty_list(columns):
result = []
for num in range(0, columns):
result.append([])
return result
def fill_empty_list(empty_list, my_list, columns):
column_depth = len(my_list) / columns if len(my_list) % columns == 0 else len(my_list) / columns + 1
item_index = 0
for column in range(0, columns):
while len(empty_list[column]) < column_depth:
if item_index < len(my_list):
empty_list[column].append(my_list[item_index])
else:
empty_list[column].append(" ") # last column could be incomplete, fill it with space
item_index += 1
def print_list_in_columns(my_list, columns=1):
if not is_printable(my_list):
print 'Nothing to print, sorry...'
return
column_width = 25 #(in symbols) Also can be calculated automatically
list_to_print = create_empty_list(columns)
fill_empty_list(list_to_print, my_list, columns)
iterators = ["it" + str(i) for i in range(0, columns)]
for iterators in zip(*list_to_print):
print ("".join(str.ljust(i, column_width) for i in iterators))
and the call part:
foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
print_list_in_columns(foolist, 2)
Here’s my solution. (Copy in GitHub gist)
It takes terminal width as input and displays only as many columns that can be fit in it.
def col_print(lines, term_width=80, indent=0, pad=2):
n_lines = len(lines)
if n_lines == 0:
return
col_width = max(len(line) for line in lines)
n_cols = int((term_width + pad - indent)/(col_width + pad))
n_cols = min(n_lines, max(1, n_cols))
col_len = int(n_lines/n_cols) + (0 if n_lines % n_cols == 0 else 1)
if (n_cols - 1) * col_len >= n_lines:
n_cols -= 1
cols = [lines[i*col_len : i*col_len + col_len] for i in range(n_cols)]
rows = list(zip(*cols))
rows_missed = zip(*[col[len(rows):] for col in cols[:-1]])
rows.extend(rows_missed)
for row in rows:
print(" "*indent + (" "*pad).join(line.ljust(col_width) for line in row))
Here’s a solution in python 3.4 that automatically detects terminal width and takes it into account. Tested on Linux and Mac.
def column_print(list_to_print, column_width=40):
import os
term_height, term_width = os.popen('stty size', 'r').read().split()
total_columns = int(term_width) // column_width
total_rows = len(list_to_print) // total_columns
# ceil
total_rows = total_rows + 1 if len(list_to_print) % total_columns != 0 else total_rows
format_string = "".join(["{%d:<%ds}" % (c, column_width)
for c in range(total_columns)])
for row in range(total_rows):
column_items = []
for column in range(total_columns):
# top-down order
list_index = row + column*total_rows
# left-right order
#list_index = row*total_columns + column
if list_index < len(list_to_print):
column_items.append(list_to_print[list_index])
else:
column_items.append("")
print(format_string.format(*column_items))
Inspired by gimel’s answer, above.
import math
def list_columns(obj, cols=4, columnwise=True, gap=4):
"""
Print the given list in evenly-spaced columns.
Parameters
----------
obj : list
The list to be printed.
cols : int
The number of columns in which the list should be printed.
columnwise : bool, default=True
If True, the items in the list will be printed column-wise.
If False the items in the list will be printed row-wise.
gap : int
The number of spaces that should separate the longest column
item/s from the next column. This is the effective spacing
between columns based on the maximum len() of the list items.
"""
sobj = [str(item) for item in obj]
if cols > len(sobj): cols = len(sobj)
max_len = max([len(item) for item in sobj])
if columnwise: cols = int(math.ceil(float(len(sobj)) / float(cols)))
plist = [sobj[i: i+cols] for i in range(0, len(sobj), cols)]
if columnwise:
if not len(plist[-1]) == cols:
plist[-1].extend(['']*(len(sobj) - len(plist[-1])))
plist = zip(*plist)
printer = 'n'.join([
''.join([c.ljust(max_len + gap) for c in p])
for p in plist])
print printer
Results (the second one satisfies your request):
>>> list_columns(foolist)
exiv2-devel fcgi msvcrt qgis-devel
mingw-libs netcdf gdal-grass qgis1.1
tcltk-demos pdcurses-devel iconv php_mapscript
>>> list_columns(foolist, cols=2)
exiv2-devel msvcrt
mingw-libs gdal-grass
tcltk-demos iconv
fcgi qgis-devel
netcdf qgis1.1
pdcurses-devel php_mapscript
>>> list_columns(foolist, columnwise=False)
exiv2-devel mingw-libs tcltk-demos fcgi
netcdf pdcurses-devel msvcrt gdal-grass
iconv qgis-devel qgis1.1 php_mapscript
>>> list_columns(foolist, gap=1)
exiv2-devel fcgi msvcrt qgis-devel
mingw-libs netcdf gdal-grass qgis1.1
tcltk-demos pdcurses-devel iconv php_mapscript
I extend an n
column solution to @Aman‘s answer
def printMultiCol(l, n_cols, buffer_len=5):
"""formats a list of strings, l, into n_cols with a separation of buffer_len"""
if not l: return [] # return if not iterable!
max_l = max(map(len, l))
formatter = '{{:<{max_l}}}'.format(max_l=max_l+buffer_len)*n_cols
zip_me_up = [l[i::n_cols] for i in xrange(n_cols)]
max_zip_l = max(map(len, zip_me_up))
zip_me_up = map(lambda x: x + ['']*(max_zip_l-len(x)), zip_me_up)
return [formatter.format(*undress_me) for undress_me in zip(*zip_me_up)]
Testing
Set up the test with random string lengths
import random
list_length = 16
random_strings = [
''.join(random.choice('spameggsbaconbeanssausage')
for x in range(random.randint(1,10)))
for i in xrange(list_length)
]
print 'for 4 columns (equal length cols) ...n{}'.format(
'n'.join(printMultiCol(random_strings, 4))
)
print 'for 7 columns (odd length cols) ...n{}'.format(
'n'.join(printMultiCol(random_strings, 5))
)
which returns
## -- End pasted text --
for 4 columns (equal length cols) ...
sgsebpasgm assgaesse ossmeagan ebesnagec
mees eeges m gcb
sm pbe bbgaa ganopabnn
bmou asbegu a psoge
for 7 columns (odd length cols) ...
sgsebpasgm assgaesse ossmeagan ebesnagec mees
eeges m gcb sm pbe
bbgaa ganopabnn bmou asbegu a
psoge
It’s useful to allow for uneven columns, without having to know in advance how many columns you can fit:
>>> words = [string.ascii_lowercase] + list(string.ascii_lowercase)
>>> print format_list(words)
abcdefghijklmnopqrstuvwxyz b d f h j l n p r t v x z
a c e g i k m o q s u w y
For your example:
>>> foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi',
... 'netcdf', 'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv',
... 'qgis-devel', 'qgis1.1', 'php_mapscript']
>>> print format_list(foolist, spacing=4, width=31)
exiv2-devel msvcrt
mingw-libs gdal-grass
tcltk-demos iconv
fcgi qgis-devel
netcdf qgis1.1
pdcurses-devel php_mapscript
Here is the code. Note that it also handles words with ANSI color codes (such as from the colorama package) – they won’t mess up the column widths.
ansi_pattern = re.compile(r'x1b[d{1,2}m')
def get_nchars(string):
"""Return number of characters, omitting ANSI codes."""
return len(ansi_pattern.sub('', string))
def format_list(items, indent=0, spacing=2, width=79):
"""Return string listing items along columns.
items : sequence
List of items to display that must be directly convertible into
unicode strings. ANSI color codes may be present, and are taken
into account in determining column widths
indent : int
Number of spaces in left margin.
spacing : int
Number of spaces between columns.
width : int
Maximum number of characters per line, including indentation.
"""
if not items:
return u''
# Ensure all items are strings
items = [unicode(item) for item in items]
# Estimate number of columns based on shortest and longest items
minlen = min(get_nchars(item) for item in items)
maxlen = max(get_nchars(item) for item in items)
# Assume one column with longest width, remaining with shortest.
# Use negative numbers for ceiling division.
ncols = 1 - (-(width - indent - maxlen) // (spacing + min(1, minlen)))
ncols = max(1, min(len(items), ncols))
# Reduce number of columns until items fit (or only one column)
while ncols >= 1:
# Determine number of rows by ceiling division
nrows = -(-len(items) // ncols)
# Readjust to avoid empty last column
ncols = -(-len(items) // nrows)
# Split items into columns, and test width
columns = [items[i*nrows:(i+1)*nrows] for i in range(ncols)]
totalwidth = indent - spacing + sum(
spacing + max(get_nchars(item) for item in column)
for column in columns
)
# Stop if columns fit. Otherwise, reduce number of columns and
# try again.
if totalwidth <= width:
break
else:
ncols -= 1
# Pad all items to column width
for i, column in enumerate(columns):
colwidth = max(get_nchars(item) for item in column)
columns[i] = [
item + ' ' * (colwidth - get_nchars(item))
for item in column
]
# Transpose into rows, and return joined rows
rows = list(itertools.izip_longest(*columns, fillvalue=''))
return 'n'.join(
' ' * indent + (u' ' * spacing).join(row).rstrip()
for row in rows
)
How about something like this?
def strlistToColumns( strl, maxWidth, spacing=4 ):
longest = max([len(s) for s in strl])
width = longest+spacing
# compute numCols s.t. (numCols-1)*(longest+spacing)+longest < maxWidth
numCols = 1 + (maxWidth-longest)//width
C = range(numCols)
# If len(strl) does not have a multiple of numCols, pad it with empty strings
strl += [""]*(len(strl) % numCols)
numRows = len(strl)/numCols
colString = ''
for r in range(numRows):
colString += "".join(["{"+str(c)+":"+str(width)+"}"
for c in C]+["n"]).format(*(strl[numCols*r+c]
for c in C))
return colString
if __name__ == '__main__':
fruits = ['apple', 'banana', 'cantaloupe', 'durian', 'elderberry',
'fig', 'grapefruit', 'honeydew', 'indonesian lime', 'jackfruit',
'kiwi', 'lychee', 'mango', 'orange', 'pomegranate', 'quince',
'raspberry', 'tangerine', 'ugli fruit', 'watermelon', 'xigua',
'yangmei', 'zinfandel grape']
cols = strlistToColumns( fruits, 80 )
print(cols)
Output
apple banana cantaloupe durian
elderberry fig grapefruit honeydew
indonesian lime jackfruit kiwi lychee
mango orange pomegranate quince
raspberry tangerine ugli fruit watermelon
xigua yangmei zinfandel grape
[print('{:20}'.format(key), end='t') if (idx + 1) % 5 else print(key, end='n') for idx, key in enumerate(list_variable)]
or
for idx, key in enumerate(list_variable):
if (idx + 1) % 5:
print('{:20}'.format(key), end='t')
else:
print(key, end='n')
There are tons of answers already, but I will share my solution, which in addition to printing the list into multiple columns, it also chooses the amount of columns dynamically, from the terminal width and the longest string on the list.
import os
cols = os.popen('stty size', 'r').read().split()[1]
def print_multicol(my_list):
max_len = len(max(my_list,key=len)) + 2
ncols = (int(cols) -4 ) / max_len
while my_list:
n = 0
while n < ncols:
if len(my_list) > 0 :
fstring = "{:<"+str(max_len)+"}"
print fstring.format(my_list.pop(0)),
n += 1
print
a_list = "a ab abc abcd abcde b bc bcde bcdef c cde cdef cdfg d de defg"
a_list += "defgh e ef efg efghi efghij f fg fgh fghij fghijk"
print_multicol(a_list.split())
As an expansion of @Aman below is a function which takes a list of strings and outputs them in columns based on the terminal size.
import os
def column_display(input_list):
'''
Used to create a structured column display based on the users terminal size
input_list : A list of string items which is desired to be displayed
'''
rows, columns = os.popen('stty size', 'r').read().split()
terminal_space_eighth = int(columns)/8
terminal_space_seventh = int(columns)/7
terminal_space_sixth = int(columns)/6
terminal_space_fifth = int(columns)/5
terminal_space_quarter = int(columns)/4
terminal_space_third = int(columns)/3
terminal_space_half = int(columns)/2
longest_string = max(input_list, key=len)
longest_length = len(longest_string) + 1
list_size = len(input_list)
if longest_length > terminal_space_half:
for string in input_list:
print(string)
elif terminal_space_eighth >= longest_length and list_size >= 8:
for a,b,c,d,e,f,g,h in zip(input_list[::8],input_list[1::8],input_list[2::8], input_list[3::8], input_list[4::8], input_list[5::8], input_list[6::8], input_list[7::8]):
column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length, longest_length, longest_length, longest_length )
output = column_space.format(a,b,c,d,e,f,g,h)
print(output)
elif terminal_space_seventh >= longest_length and list_size >= 7:
for a,b,c,d,e,f,g in zip(input_list[::7],input_list[1::7],input_list[2::7], input_list[3::7], input_list[4::7], input_list[5::7], input_list[6::7]):
column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length, longest_length, longest_length)
output = column_space.format(a,b,c,d,e,f,g)
print(output)
elif terminal_space_sixth >= longest_length and list_size >= 6:
for a,b,c,d,e,f in zip(input_list[::6],input_list[1::6],input_list[2::6], input_list[3::6], input_list[4::6], input_list[5::6]):
column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length, longest_length)
output = column_space.format(a,b,c,d,e,f)
print(output)
elif terminal_space_fifth >= longest_length and list_size >= 5:
for a,b,c,d,e in zip(input_list[::5],input_list[1::5],input_list[2::5], input_list[3::5], input_list[4::5]):
column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length)
output = column_space.format(a,b,c,d,e)
print(output)
elif terminal_space_quarter >= longest_length and list_size >= 4:
for a,b,c,d in zip(input_list[::4],input_list[1::4],input_list[2::4], input_list[3::4]):
column_space = '{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length)
output = column_space.format(a,b,c,d)
print(output)
elif terminal_space_third >= longest_length and list_size >= 3:
for a,b,c in zip(input_list[::3],input_list[1::3],input_list[2::3]):
column_space = '{:<%s}{:<%s}{:<}' % (longest_length, longest_length)
output = column_space.format(a,b,c)
print(output)
elif terminal_space_half >= longest_length and list_size >= 2:
for a,b in zip(input_list[::2],input_list[1::2]):
column_space = '{:<%s}{:<}' % longest_length
output = column_space.format(a,b)
print(output)
As an explanation this does a few different things.
First it gets the number of columns for the current user’s terminal using os.popen.
Second it takes the number of columns and divides in half, increasing to eighth. This will be used to compare the longest string in the list to determine the number of columns best suited for this.
Third is the longest string of the list pulled using the build in python function max().
Forth the length of the longest string is taken and then has one added to it for padding. The length of the list is taken as well so that if the list is less than 8 items it will only list the number of items that exist.
Fifth the longest string length is compared to each of the terminal spaces from one column to eight. If the column is greater than or equal to the length then it can be used. For example is the longest string is 10 and the columns divided by eight(terminal_space_eighth) is 8 but columns divided by seven(terminal_space_seventh) is 12, there will be seven columns. There will be seven because the longest string can fit in 12 characters but not in 8 characters.
It’s also worth noting the length of the list is taken into consideration to prevent creating more columns than list items.
Sixth is an expansion of the explanation by @Aman : https://stackoverflow.com/a/1524132/11002603
Indexing
Lets let i represent the number determined by terminal size for the sake of this example.
input_list[::i]
This selects element at i. Adding a number at the front such as input_list[1::i]
offsets the starting point(remember python considers 0 a valid number which is why it’s not used initially.)
Zipping
Zip is used to create a tuple with elements of a list. For example The output list will look similar to below
zip([string1,string2,string3], [string4,string5, string6], [string7,string8,string9])
output : [(string1,string4,string7), (string2,string5, string8), (string3,string6,string9)]
Using together
Depending on the number of columns, the letters are just used to represent a split. So for example if only 5 columns fit in the terminal, the following will be used
for a,b,c,d,e in zip(input_list[::5],input_list[1::5],input_list[2::5], input_list[3::5], input_list[4::5]):
This will take of the tuples created from zipping and store then as a,b,c,d and e variables so we can call them within the loop.
The column space is then used for format each of a,b,c,d and e into respective columns and is where the length of each column is determined. The length is based on the string length determined above.
I needed to adjust each of the columns. I have implemented this code
def print_sorted_list(data, columns):
if data:
gap = 2
ljusts = {}
for count, item in enumerate(sorted(data), 1):
column = count % columns
ljusts[column] = len(item) if (column not in ljusts) else max(ljusts[column], len(item))
for count, item in enumerate(sorted(data), 1):
print item.ljust(ljusts[count % columns] + gap),
if (count % columns == 0) or (count == len(data)):
print
Example:
foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript', 'blablablablablablabla', 'fafafafafafa']
print_sorted_list(foolist, 4)
Output:
blablablablablablabla exiv2-devel fafafafafafa fcgi
gdal-grass iconv mingw-libs msvcrt
netcdf pdcurses-devel php_mapscript qgis-devel
qgis1.1 tcltk-demos
Here is a straightforward way. See the inline comments for explanation:
import shutil
import itertools
from functools import reduce
def split_list(lst, ncols):
"""Split list into rows"""
return itertools.zip_longest(
*[lst[i::ncols] for i in range(ncols)], fillvalue=""
)
# -- Alternatively --
# import numpy as np
# array = np.array(lst)
# nrows = array.size / ncols + 1
# return np.array_split(array, int(nrows))
def print_in_columns(lst):
"""Print a list in columns."""
# Find maximum length of a string in colors_list
colsize = reduce(lambda x, y: max(x, len(y)), lst, 0)
# Terminal width
maxcols = shutil.get_terminal_size()[0]
ncols = maxcols / (colsize + 1)
rows = split_list(lst, int(ncols))
print(
# Join rows
"n".join(
(
# Fill items left justified
" ".join(item.ljust(colsize) for item in row)
for row in rows
)
)
)
Although not designed for it, the standard-library module in Python 3 cmd
has a utility for printing a list of strings in multiple columns
import cmd
cli = cmd.Cmd()
cli.columnize(foolist, displaywidth=80)
You even then have the option of specifying the output location, with cmd.Cmd(stdout=my_stream)
this one prints list in separate columns (order is preserved)
from itertools import zip_longest
def ls(items, n_cols=2, pad=30):
if len(items) == 0:
return
total = len(items)
chunk_size = total // n_cols
if chunk_size * n_cols < total:
chunk_size += 1
start = range(0, total, chunk_size)
end = range(chunk_size, total + chunk_size, chunk_size)
groups = (items[s:e] for s, e in zip(start, end))
for group in zip_longest(*groups, fillvalue=''):
template = (' ').join(['%%-%ds' % pad] * len(group))
print(template % group)
usage:
ls([1, 2, 3, 4, 5, 6, 7], n_cols=3, pad=10)
output:
1 4 7
2 5
3 6
note that there may be missing columns if there is not enough number of items, because columns are filled first.
ls([1, 2, 3, 4, 5], n_cols=4)
output:
1 3 5
2 4
For Python >=3.6, slight update to @JoshuaZastrow‘s answer using f-strings
and adding clear method to adjust columns
cols = 5
[print(f'{key:20}', end='t') if (idx + 1) % cols else print(f'{key}') for idx, key in enumerate(list_variable)]
or
cols = 5
for idx, key in enumerate(list_variable):
if (idx + 1) % cols:
print(f'{key:20}', end='t')
else:
print(f'{key}')
Here is the short and simple method:
def printList1(list, col, STR_FMT='{}', gap=1):
list = [STR_FMT.format(x).lstrip() for x in list]
FMT2 = '%%%ds%%s' % (max(len(x) for x in list)+gap)
print(''.join([FMT2 % (v, "" if (i+1) % col else "n") for i, v in enumerate(list)]))
Then here is the better method which can turn off the constant width across the entire list instead optimizing it across the columns, restrict the number of columns to fit the max characters per line or find the optimal number of columns to fit the max characters per line, then force left or right justify, and can still can take the optional format string, and gap width between each column.
def printList2(list, col=None, gap=2, uniform=True, ljust=True, STR_FMT="{}", MAX_CHARS=120, end='n'):
list = [STR_FMT.format(x).strip() for x in list]
Lmax, valid, valid_prev, cp, c = [MAX_CHARS+1], None, None, 1, max(col,1) if col else 1
LoL_prev, Lmax_prev = [], []
while True:
LoL = [list[i::c] for i in range(c)]
Lmax = [max(len(x)+gap for x in L) for L in LoL] # Find max width of each column with gap width
if uniform: # Set each max column width to max across entire set.
Lmax = [max(Lmax) for m in Lmax]
valid_prev, valid = valid, sum(Lmax) <= MAX_CHARS
if (col and (valid or (c == 1))) or not MAX_CHARS: # If column and valid strlen or MAX_CHARS is empty
break
elif valid_prev and not valid_prev == valid: # If valid_prev exist
c = cp if valid_prev and not valid else c
LoL, Lmax = (LoL_prev, Lmax_prev) if valid_prev else (LoL, Lmax)
break
LoL_prev, Lmax_prev = LoL, Lmax
cp, c = c, (c + (+1 if valid else -1))
ljust = '-' if ljust else ''
FMT = ["%%%s%ds%s" % (ljust, max(Lmax) if uniform else m, end if i+1 == c else '') for i, m in enumerate(Lmax)]
outStr = ''.join([''.join([f % v for v, f in zip(L, FMT)]) for L in zip(*LoL)])
remStr = ''.join([f % v for v, f in zip(list[c * (len(list) // c):], FMT)])
print(outStr+(remStr+end if remStr else remStr), end='')
Testing w/ outputs:
>>> foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
>>> printList2(foolist)
exiv2-devel mingw-libs tcltk-demos fcgi netcdf pdcurses-devel msvcrt
gdal-grass iconv qgis-devel qgis1.1 php_mapscript
>>> printList2(foolist, MAX_CHARS=48, uniform=False, gap=3)
exiv2-devel mingw-libs tcltk-demos
fcgi netcdf pdcurses-devel
msvcrt gdal-grass iconv
qgis-devel qgis1.1 php_mapscript
>>> printList2(foolist, col=2, MAX_CHARS=48, uniform=False, gap=3)
exiv2-devel mingw-libs
tcltk-demos fcgi
netcdf pdcurses-devel
msvcrt gdal-grass
iconv qgis-devel
qgis1.1 php_mapscript
>>> printList2(foolist, col=2, MAX_CHARS=48, uniform=False, ljust=False, gap=2)
exiv2-devel mingw-libs
tcltk-demos fcgi
netcdf pdcurses-devel
msvcrt gdal-grass
iconv qgis-devel
qgis1.1 php_mapscript
>>> printList2(foolist, col=10, MAX_CHARS=48, uniform=True, ljust=False, gap=2)
exiv2-devel mingw-libs tcltk-demos
fcgi netcdf pdcurses-devel
msvcrt gdal-grass iconv
qgis-devel qgis1.1 php_mapscript
>>> from math import pi
>>> FloatList = [pi**(i+1) for i in range(32)]
>>> printList2(FloatList, STR_FMT="{:.5g},", col=7, ljust=False)
3.1416, 9.8696, 31.006, 97.409, 306.02, 961.39, 3020.3,
9488.5, 29809, 93648, 2.942e+05, 9.2427e+05, 2.9037e+06, 9.1222e+06,
2.8658e+07, 9.0032e+07, 2.8284e+08, 8.8858e+08, 2.7916e+09, 8.77e+09, 2.7552e+10,
8.6556e+10, 2.7192e+11, 8.5427e+11, 2.6838e+12, 8.4313e+12, 2.6488e+13, 8.3214e+13,
2.6142e+14, 8.2129e+14, 2.5802e+15, 8.1058e+15,
I use IPython internal columnize function
import IPython
foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
'qgis1.1', 'php_mapscript']
foolist_columnized = IPython.utils.text.columnize(foolist)
print(foolist_columnized)
output will look like:
exiv2-devel tcltk-demos netcdf msvcrt iconv qgis1.1
mingw-libs fcgi pdcurses-devel gdal-grass qgis-devel php_mapscript
For Python3, I used python – How do you split a list into evenly sized chunks? – Stack Overflow to create
def chunkSectionList(listToPrint, columns):
""" separate a list into chunks of n-items """
for i in range(0, len(listToPrint), columns):
yield listToPrint[i:i + columns]
def printSections(listToPrint, columns):
remainder = len(listToPrint) % columns
listToPrint += (columns - remainder) *
(" ") if remainder != 0 else listToPrint
for sectionsLine in chunkSectionList(listToPrint, columns):
formatStr = columns * '{:<30}'
print(formatStr.format(* sectionsLine))
A succint variation that adapts the column width to the longest item in that column:
from itertools import zip_longest
def print_columns(obj_to_print, col_length, gutter):
columns = []
for i in range(1 + len(obj_to_print) // col_length):
col_items = [str(item) for item in obj_to_print[i*col_length:(i+1)*col_length]]
col_width = len(max(col_items, key=len))
columns.append([f"{item:{col_width}}" for item in col_items])
for row in zip_longest(*columns, fillvalue=""):
print(f"{' ' * gutter}".join(row))