Skip to content

Commit

Permalink
re-write columnize, with intermediate step.
Browse files Browse the repository at this point in the history
fix test that where wrong, add some others.

fix ipython#1860
  • Loading branch information
Carreau committed Jun 10, 2012
1 parent 57e39ba commit a1ddb86
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 57 deletions.
31 changes: 28 additions & 3 deletions IPython/utils/tests/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import os
import math
import random

import nose.tools as nt

Expand All @@ -32,13 +33,37 @@ def test_columnize():
items = [l*size for l in 'abc']
out = text.columnize(items, displaywidth=80)
nt.assert_equals(out, 'aaaaa bbbbb ccccc\n')
out = text.columnize(items, displaywidth=10)
out = text.columnize(items, displaywidth=12)
nt.assert_equals(out, 'aaaaa ccccc\nbbbbb\n')

out = text.columnize(items, displaywidth=10)
nt.assert_equals(out, 'aaaaa\nbbbbb\nccccc\n')

def test_columnize_random():
"""Test with random input to hopfully catch edge case """
for nitems in [random.randint(2,70) for i in range(2,20)]:
displaywidth = random.randint(20,200)
rand_len = [random.randint(2,displaywidth) for i in range(nitems)]
items = ['x'*l for l in rand_len]
out = text.columnize(items, displaywidth=displaywidth)
longer_line = max([len(x) for x in out.split('\n')])
longer_element = max(rand_len)
if longer_line > displaywidth:
print "Columnize displayed something lager than displaywidth : %s " % longer_line
print "longer element : %s " % longer_element
print "displaywidth : %s " % displaywidth
print "number of element : %s " % nitems
print "size of each element :\n %s" % rand_len
assert False

def test_columnize_medium():
"""Test with inputs than shouldn't be wider tahn 80 """
size = 40
items = [l*size for l in 'abc']
out = text.columnize(items, displaywidth=80)
nt.assert_equals(out, '\n'.join(items+['']))

def test_columnize_long():
"""Test columnize with inputs longer than the display window"""
text.columnize(['a'*81, 'b'*81], displaywidth=80)
size = 11
items = [l*size for l in 'abc']
out = text.columnize(items, displaywidth=size-1)
Expand Down
100 changes: 46 additions & 54 deletions IPython/utils/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,45 @@ def parse(self, fmt_string):
# Re-yield the {foo} style pattern
yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)

#-----------------------------------------------------------------------------
# Utils to columnize a list of string
#-----------------------------------------------------------------------------
def _chunks(l, n):
"""Yield successive n-sized chunks from l."""
for i in xrange(0, len(l), n):
yield l[i:i+n]

def _find_optimal(rlist , sepsize=2 , displaywidth=80):
"""Calculate optimal info to columnize a list of string"""
for nrow in range(1, len(rlist)+1) :
chk = [max(l) for l in _chunks(rlist, nrow) ]
sumlength = sum(chk)
ncols = len(chk)
if sumlength+sepsize*(ncols-1) <= displaywidth :
break;
return {'columns_numbers' : ncols,
'optimal_separator_width':(displaywidth - sumlength)/(ncols-1) if (ncols -1) else 0,
'rows_numbers' : nrow,
'columns_width' : chk
}

def _get_or_default(mylist, i, default=None):
"""return list item number, or default if don't exist"""
if i >= len(mylist):
return default
else :
return mylist[i]

def compute_item_matrix(items, *args, **kwargs) :
""" Transform a list of strings into a nested list to columnize
Returns a tuple of (strings_matrix, dict_info)
innermost lists are rows, see columnize for options info
"""
info = _find_optimal(map(len, items), *args, **kwargs)
nrow, ncol = info['rows_numbers'], info['columns_numbers']
return ([[ _get_or_default(items, c*nrow+i) for c in range(ncol) ] for i in range(nrow) ], info)

def columnize(items, separator=' ', displaywidth=80):
""" Transform a list of strings into a single string with columns.
Expand All @@ -679,58 +718,11 @@ def columnize(items, separator=' ', displaywidth=80):
-------
The formatted string.
"""
# Note: this code is adapted from columnize 0.3.2.
# See http://code.google.com/p/pycolumnize/

# Some degenerate cases.
size = len(items)
if size == 0:
if not items :
return '\n'
elif size == 1:
return '%s\n' % items[0]

# Special case: if any item is longer than the maximum width, there's no
# point in triggering the logic below...
item_len = map(len, items) # save these, we can reuse them below
longest = max(item_len)
if longest >= displaywidth:
return '\n'.join(items+[''])

# Try every row count from 1 upwards
array_index = lambda nrows, row, col: nrows*col + row
for nrows in range(1, size):
ncols = (size + nrows - 1) // nrows
colwidths = []
totwidth = -len(separator)
for col in range(ncols):
# Get max column width for this column
colwidth = 0
for row in range(nrows):
i = array_index(nrows, row, col)
if i >= size: break
x, len_x = items[i], item_len[i]
colwidth = max(colwidth, len_x)
colwidths.append(colwidth)
totwidth += colwidth + len(separator)
if totwidth > displaywidth:
break
if totwidth <= displaywidth:
break

# The smallest number of rows computed and the max widths for each
# column has been obtained. Now we just have to format each of the rows.
string = ''
for row in range(nrows):
texts = []
for col in range(ncols):
i = row + nrows*col
if i >= size:
texts.append('')
else:
texts.append(items[i])
while texts and not texts[-1]:
del texts[-1]
for col in range(len(texts)):
texts[col] = texts[col].ljust(colwidths[col])
string += '%s\n' % separator.join(texts)
return string
matrix, info = compute_item_matrix(items, sepsize=len(separator), displaywidth=displaywidth)
#sep = ' '*min(info['optimal_separator_width'], 9)
fmatrix = matrix
fmatrix = [filter(None, x) for x in matrix]
sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['columns_width'])])
return '\n'.join(map(sjoin, fmatrix))+'\n'

0 comments on commit a1ddb86

Please sign in to comment.