Related
I created a simple Bingo card generator that generates 4 numbers between n and n+15. However, I noticed that numbers are sometimes generated twice despite an if statement I wrote that was supposed to find duplicates and rerun the random generation for that cell. My code is below:
import random
ballCount = 75 # Max value of cell
gridWidth = 4
gridHeight = 5
numPerCard = 20 # Numbers generated per card
minimum = 1
maximum = 15
def generate_number(stored, small_num, big_num):
random.seed(a=None, version=2)
random_number = str(random.randint(small_num, big_num))
if "| "+random_number+" |" in stored:
generate_number(stored, small_num, big_num)
return random_number
for row in range(gridHeight):
string = ""
for Number in range(gridWidth):
number = generate_number(string, minimum, maximum)
string += " | " + number
string += " |"
print(string)
maximum += 15
minimum += 15
And here is an example of output with duplicates (23 appears 2 times):
| 2 | 5 | 9 | 12 |
| 25 | 23 | 19 | 23 |
| 37 | 42 | 30 | 37 |
| 60 | 49 | 50 | 55 |
| 73 | 71 | 69 | 67 |
if "| "+random_number+" |" in stored:
generate_number(stored, small_num, big_num)
return random_number
If you already have the number, you call generate_number again, but you still return the original number (the duplicate).
You may be interested in random.sample as an alternative way of getting a random selection of unique numbers within a range. E.g.:
>>> [random.sample(range(n, n+15), 4) for n in range(15, 75, 15)]
[[20, 26, 15, 23], [37, 44, 32, 40], [48, 45, 47, 50], [63, 62, 68, 71]]
or, with pretty-printing:
>>> for row in [random.sample(range(n, n+15), 4) for n in range(15, 75, 15)]:
... print(f"| {' | '.join(map(str, row))} |")
...
| 17 | 15 | 27 | 22 |
| 34 | 33 | 42 | 36 |
| 54 | 51 | 55 | 48 |
| 68 | 73 | 61 | 72 |
Inside the if statement you need to return the result from generate_number. Right now it will do the if statement and then discard that result and then return the original number.
Problems found in your code:
1) Your 'stored' variable in generate_number is missing the vertical bar "|" in the end, so number doesn't match.
2) The return of your recursive function must be assigned to "random_number".
Follow the corrected code:
import random
ballCount = 75 # Max value of cell
gridWidth = 4
gridHeight = 5
numPerCard = 20 # Numbers generated per card
cards = 6 # Number of cards
minimum = 1
maximum = 15
def generate_number(stored, small_num, big_num):
random.seed(a=None, version=2)
random_number = str(random.randint(small_num, big_num))
if "| " + random_number + " |" in stored +" |":
random_number = generate_number(stored, small_num, big_num)
return random_number
for row in range(gridHeight):
string = ""
for Number in range(gridWidth):
number = generate_number(string, minimum, maximum)
string += " | " + number
string += " |"
print(string)
maximum += 15
minimum += 15
Hope this helps
There are two problems I can see here. The first problem is, when your program retries the random number generation, it doesn't do anything with the result. You should add a return before generate_number(stored, small_num, big_num), making it return generate_number(stored, small_num, big_num). Secondly, you should remove the " |" in the "| "+random_number+" |". It can be possible that the repetition is at the last cell you generated, and therefore does not have a " |" after it. Hope this answers your question.
Let's say I have a list of x,y coordinates like this:
coordinate_list = [(4,6),(2,5),(0,4),(-2,-2),(0,2),(0,0),(8,8),(8,11),(8,14)]
I want to find the average y-value associated with each x-value. So for instance, there's only one "2" x-value in the dataset, so the average y-value would be "5". However, there are three 8's and the average y-value would be 11 [ (8+11+14) / 3 ].
What would be the most efficient way to do this?
y_values_by_x = {}
for x, y in coordinate_list:
y_values_by_x.setdefault(x, []).append(y)
average_y_by_x = {k: sum(v)/len(v) for k, v in y_values_by_x.items()}
You can use pandas
coordinate_list = [(4,6),(2,5),(0,4),(-2,-2),(0,2),(0,0),(8,8),(8,11),(8,14)]
import pandas as pd
df = pd.DataFrame(coordinate_list)
df
df.groupby([0]).mean()
| 0 | | 1 |
| --- | --- |
| -2 | -2 |
| 0 | 2 |
| 2 | 5 |
| 4 | 6 |
| 8 | 11 |
Try the mean() function from statistics module with list comprehension
from statistics import mean
x0_filter_value = 0 # can be any value of your choice for finding average
result = mean([x[1] for x in coordinate_list if x[0] == x0_filter_value])
print(result)
And to print means for all X[0] values:
for i in set([x[0] for x in coordinate_list]):
print (i,mean([x[1] for x in coordinate_list if x[0] == i]))
I have a MySQL database using Python(mysql.connector). This is my table:
+----+-------+-------+--------+------------+
| id | num1 | num2 | gen | filename |
+----+-------+-------+--------+------------+
| 1 | 45 | 55 | woman | vid1.mp4 |
| 2 | 25 | 35 | man | vid2.mp4 |
| 3 | 45 | 55 | man | vid3.mp4 |
| 4 | 5 | 15 | woman | vid4.mp4 |
I then get the list below by querying the database:
[(1, 45, 55, 'woman', 'vid1.mp4'), (2, 25, 35, 'man', 'vid2.mp4'),(3, 45, 55, 'man', 'vid3.mp4'),(4, 5, 15, 'woman', 'vid4.mp4')]
I get the number and gender in my code and compare it with the num1, num2 and gender entered in the database.
For example:
if 25<num<35 and gender =='man':
filename = 'vid2.mp4'
How could I implement this?
When I have a list of data like you have specified here, it is useful to add column names to it.
data = [(1, 45, 55, 'woman', 'vid1.mp4') ... ] # This comes from DB
col_names = ['id','num1','num2','gen','filename']
Next, iterate over your data in a loop
for row in data:
# row now contains a single value, for instance (1, 45, 55, 'woman', 'vid1.mp4')
row_dict = dict(zip(col_names, row))
# row_dict is now a dictionary of {'id':1, 'num1':45 ... }
if row_dict['num1'] == 25:
print("%s has an num1 of 25!" % row_dict)
I hope that this gives you an idea how to iterate over your data and detect certain values are as you expect.
Another options is to use tuple unpacking in the loop
for id, num1, num2, gen, filename in data:
if num1 == 25:
print ("wow!")
Based on your edit:
for id, num1, num2, gen, db_filename in data:
if 25<num1<35 and gen == 'man':
filename = db_filename
else:
print("Oh no, file not found")
filename = None
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 4 years ago.
The community reviewed whether to reopen this question 4 months ago and left it closed:
Original close reason(s) were not resolved
Improve this question
I'm looking for a way to pretty-print tables like this:
=======================
| column 1 | column 2 |
=======================
| value1 | value2 |
| value3 | value4 |
=======================
I've found the asciitable library but it doesn't do the borders, etc. I don't need any complex formatting of data items, they're just strings. I do need it to auto-size columns.
Do other libraries or methods exist, or do I need to spend a few minutes writing my own?
I've read this question long time ago, and finished writing my own pretty-printer for tables: tabulate.
My use case is:
I want a one-liner most of the time
which is smart enough to figure the best formatting for me
and can output different plain-text formats
Given your example, grid is probably the most similar output format:
from tabulate import tabulate
print tabulate([["value1", "value2"], ["value3", "value4"]], ["column 1", "column 2"], tablefmt="grid")
+------------+------------+
| column 1 | column 2 |
+============+============+
| value1 | value2 |
+------------+------------+
| value3 | value4 |
+------------+------------+
Other supported formats are plain (no lines), simple (Pandoc simple tables), pipe (like tables in PHP Markdown Extra), orgtbl (like tables in Emacs' org-mode), rst (like simple tables in reStructuredText). grid and orgtbl are easily editable in Emacs.
Performance-wise, tabulate is slightly slower than asciitable, but much faster than PrettyTable and texttable.
P.S. I'm also a big fan of aligning numbers by a decimal column. So this is the default alignment for numbers if there are any (overridable).
Here's a quick and dirty little function I wrote for displaying the results from SQL queries I can only make over a SOAP API. It expects an input of a sequence of one or more namedtuples as table rows. If there's only one record, it prints it out differently.
It is handy for me and could be a starting point for you:
def pprinttable(rows):
if len(rows) > 1:
headers = rows[0]._fields
lens = []
for i in range(len(rows[0])):
lens.append(len(max([x[i] for x in rows] + [headers[i]],key=lambda x:len(str(x)))))
formats = []
hformats = []
for i in range(len(rows[0])):
if isinstance(rows[0][i], int):
formats.append("%%%dd" % lens[i])
else:
formats.append("%%-%ds" % lens[i])
hformats.append("%%-%ds" % lens[i])
pattern = " | ".join(formats)
hpattern = " | ".join(hformats)
separator = "-+-".join(['-' * n for n in lens])
print hpattern % tuple(headers)
print separator
_u = lambda t: t.decode('UTF-8', 'replace') if isinstance(t, str) else t
for line in rows:
print pattern % tuple(_u(t) for t in line)
elif len(rows) == 1:
row = rows[0]
hwidth = len(max(row._fields,key=lambda x: len(x)))
for i in range(len(row)):
print "%*s = %s" % (hwidth,row._fields[i],row[i])
Sample output:
pkid | fkn | npi
-------------------------------------+--------------------------------------+----
405fd665-0a2f-4f69-7320-be01201752ec | 8c9949b9-552e-e448-64e2-74292834c73e | 0
5b517507-2a42-ad2e-98dc-8c9ac6152afa | f972bee7-f5a4-8532-c4e5-2e82897b10f6 | 0
2f960dfc-b67a-26be-d1b3-9b105535e0a8 | ec3e1058-8840-c9f2-3b25-2488f8b3a8af | 1
c71b28a3-5299-7f4d-f27a-7ad8aeadafe0 | 72d25703-4735-310b-2e06-ff76af1e45ed | 0
3b0a5021-a52b-9ba0-1439-d5aafcf348e7 | d81bb78a-d984-e957-034d-87434acb4e97 | 1
96c36bb7-c4f4-2787-ada8-4aadc17d1123 | c171fe85-33e2-6481-0791-2922267e8777 | 1
95d0f85f-71da-bb9a-2d80-fe27f7c02fe2 | 226f964c-028d-d6de-bf6c-688d2908c5ae | 1
132aa774-42e5-3d3f-498b-50b44a89d401 | 44e31f89-d089-8afc-f4b1-ada051c01474 | 1
ff91641a-5802-be02-bece-79bca993fdbc | 33d8294a-053d-6ab4-94d4-890b47fcf70d | 1
f3196e15-5b61-e92d-e717-f00ed93fe8ae | 62fa4566-5ca2-4a36-f872-4d00f7abadcf | 1
Example
>>> from collections import namedtuple
>>> Row = namedtuple('Row',['first','second','third'])
>>> data = Row(1,2,3)
>>> data
Row(first=1, second=2, third=3)
>>> pprinttable([data])
first = 1
second = 2
third = 3
>>> pprinttable([data,data])
first | second | third
------+--------+------
1 | 2 | 3
1 | 2 | 3
For some reason when I included 'docutils' in my google searches I stumbled across texttable, which seems to be what I'm looking for.
I too wrote my own solution to this. I tried to keep it simple.
https://github.com/Robpol86/terminaltables
from terminaltables import AsciiTable
table_data = [
['Heading1', 'Heading2'],
['row1 column1', 'row1 column2'],
['row2 column1', 'row2 column2']
]
table = AsciiTable(table_data)
print table.table
+--------------+--------------+
| Heading1 | Heading2 |
+--------------+--------------+
| row1 column1 | row1 column2 |
| row2 column1 | row2 column2 |
+--------------+--------------+
table.inner_heading_row_border = False
print table.table
+--------------+--------------+
| Heading1 | Heading2 |
| row1 column1 | row1 column2 |
| row2 column1 | row2 column2 |
+--------------+--------------+
table.inner_row_border = True
table.justify_columns[1] = 'right'
table.table_data[1][1] += '\nnewline'
print table.table
+--------------+--------------+
| Heading1 | Heading2 |
+--------------+--------------+
| row1 column1 | row1 column2 |
| | newline |
+--------------+--------------+
| row2 column1 | row2 column2 |
+--------------+--------------+
I just released termtables for this purpose. For example, this
import termtables as tt
tt.print(
[[1, 2, 3], [613.23236243236, 613.23236243236, 613.23236243236]],
header=["a", "bb", "ccc"],
style=tt.styles.ascii_thin_double,
padding=(0, 1),
alignment="lcr"
)
gets you
+-----------------+-----------------+-----------------+
| a | bb | ccc |
+=================+=================+=================+
| 1 | 2 | 3 |
+-----------------+-----------------+-----------------+
| 613.23236243236 | 613.23236243236 | 613.23236243236 |
+-----------------+-----------------+-----------------+
By default, the table is rendered with Unicode box-drawing characters,
┌─────────────────┬─────────────────┬─────────────────┐
│ a │ bb │ ccc │
╞═════════════════╪═════════════════╪═════════════════╡
│ 1 │ 2 │ 3 │
├─────────────────┼─────────────────┼─────────────────┤
│ 613.23236243236 │ 613.23236243236 │ 613.23236243236 │
└─────────────────┴─────────────────┴─────────────────┘
termtables are very configurable; check out the tests for more examples.
If you want a table with column and row spans, then try my library dashtable
from dashtable import data2rst
table = [
["Header 1", "Header 2", "Header3", "Header 4"],
["row 1", "column 2", "column 3", "column 4"],
["row 2", "Cells span columns.", "", ""],
["row 3", "Cells\nspan rows.", "- Cells\n- contain\n- blocks", ""],
["row 4", "", "", ""]
]
# [Row, Column] pairs of merged cells
span0 = ([2, 1], [2, 2], [2, 3])
span1 = ([3, 1], [4, 1])
span2 = ([3, 3], [3, 2], [4, 2], [4, 3])
my_spans = [span0, span1, span2]
print(data2rst(table, spans=my_spans, use_headers=True))
Which outputs:
+----------+------------+----------+----------+
| Header 1 | Header 2 | Header3 | Header 4 |
+==========+============+==========+==========+
| row 1 | column 2 | column 3 | column 4 |
+----------+------------+----------+----------+
| row 2 | Cells span columns. |
+----------+----------------------------------+
| row 3 | Cells | - Cells |
+----------+ span rows. | - contain |
| row 4 | | - blocks |
+----------+------------+---------------------+
You can try BeautifulTable. It does what you want to do. Here's an example from it's documentation
>>> from beautifultable import BeautifulTable
>>> table = BeautifulTable()
>>> table.columns.header = ["name", "rank", "gender"]
>>> table.rows.append(["Jacob", 1, "boy"])
>>> table.rows.append(["Isabella", 1, "girl"])
>>> table.rows.append(["Ethan", 2, "boy"])
>>> table.rows.append(["Sophia", 2, "girl"])
>>> table.rows.append(["Michael", 3, "boy"])
>>> print(table)
+----------+------+--------+
| name | rank | gender |
+----------+------+--------+
| Jacob | 1 | boy |
+----------+------+--------+
| Isabella | 1 | girl |
+----------+------+--------+
| Ethan | 2 | boy |
+----------+------+--------+
| Sophia | 2 | girl |
+----------+------+--------+
| Michael | 3 | boy |
+----------+------+--------+
Version using w3m designed to handle the types MattH's version accepts:
import subprocess
import tempfile
import html
def pprinttable(rows):
esc = lambda x: html.escape(str(x))
sour = "<table border=1>"
if len(rows) == 1:
for i in range(len(rows[0]._fields)):
sour += "<tr><th>%s<td>%s" % (esc(rows[0]._fields[i]), esc(rows[0][i]))
else:
sour += "<tr>" + "".join(["<th>%s" % esc(x) for x in rows[0]._fields])
sour += "".join(["<tr>%s" % "".join(["<td>%s" % esc(y) for y in x]) for x in rows])
with tempfile.NamedTemporaryFile(suffix=".html") as f:
f.write(sour.encode("utf-8"))
f.flush()
print(
subprocess
.Popen(["w3m","-dump",f.name], stdout=subprocess.PIPE)
.communicate()[0].decode("utf-8").strip()
)
from collections import namedtuple
Row = namedtuple('Row',['first','second','third'])
data1 = Row(1,2,3)
data2 = Row(4,5,6)
pprinttable([data1])
pprinttable([data1,data2])
results in:
┌───────┬─┐
│ first │1│
├───────┼─┤
│second │2│
├───────┼─┤
│ third │3│
└───────┴─┘
┌─────┬───────┬─────┐
│first│second │third│
├─────┼───────┼─────┤
│1 │2 │3 │
├─────┼───────┼─────┤
│4 │5 │6 │
└─────┴───────┴─────┘
I know it the question is a bit old but here's my attempt at this:
https://gist.github.com/lonetwin/4721748
It is a bit more readable IMHO (although it doesn't differentiate between single / multiple rows like #MattH's solutions does, nor does it use NamedTuples).
I use this small utility function.
def get_pretty_table(iterable, header):
max_len = [len(x) for x in header]
for row in iterable:
row = [row] if type(row) not in (list, tuple) else row
for index, col in enumerate(row):
if max_len[index] < len(str(col)):
max_len[index] = len(str(col))
output = '-' * (sum(max_len) + 1) + '\n'
output += '|' + ''.join([h + ' ' * (l - len(h)) + '|' for h, l in zip(header, max_len)]) + '\n'
output += '-' * (sum(max_len) + 1) + '\n'
for row in iterable:
row = [row] if type(row) not in (list, tuple) else row
output += '|' + ''.join([str(c) + ' ' * (l - len(str(c))) + '|' for c, l in zip(row, max_len)]) + '\n'
output += '-' * (sum(max_len) + 1) + '\n'
return output
print get_pretty_table([[1, 2], [3, 4]], ['header 1', 'header 2'])
output
-----------------
|header 1|header 2|
-----------------
|1 |2 |
|3 |4 |
-----------------
from sys import stderr, stdout
def create_table(table: dict, full_row: bool = False) -> None:
min_len = len(min((v for v in table.values()), key=lambda q: len(q)))
max_len = len(max((v for v in table.values()), key=lambda q: len(q)))
if min_len < max_len:
stderr.write("Table is out of shape, please make sure all columns have the same length.")
stderr.flush()
return
additional_spacing = 1
heading_separator = '| '
horizontal_split = '| '
rc_separator = ''
key_list = list(table.keys())
rc_len_values = []
for key in key_list:
rc_len = len(max((v for v in table[key]), key=lambda q: len(str(q))))
rc_len_values += ([rc_len, [key]] for n in range(len(table[key])))
heading_line = (key + (" " * (rc_len + (additional_spacing + 1)))) + heading_separator
stdout.write(heading_line)
rc_separator += ("-" * (len(key) + (rc_len + (additional_spacing + 1)))) + '+-'
if key is key_list[-1]:
stdout.flush()
stdout.write('\n' + rc_separator + '\n')
value_list = [v for vl in table.values() for v in vl]
aligned_data_offset = max_len
row_count = len(key_list)
next_idx = 0
newline_indicator = 0
iterations = 0
for n in range(len(value_list)):
key = rc_len_values[next_idx][1][0]
rc_len = rc_len_values[next_idx][0]
line = ('{:{}} ' + " " * len(key)).format(value_list[next_idx], str(rc_len + additional_spacing)) + horizontal_split
if next_idx >= (len(value_list) - aligned_data_offset):
next_idx = iterations + 1
iterations += 1
else:
next_idx += aligned_data_offset
if newline_indicator >= row_count:
if full_row:
stdout.flush()
stdout.write('\n' + rc_separator + '\n')
else:
stdout.flush()
stdout.write('\n')
newline_indicator = 0
stdout.write(line)
newline_indicator += 1
stdout.write('\n' + rc_separator + '\n')
stdout.flush()
Example:
table = {
"uid": ["0", "1", "2", "3"],
"name": ["Jon", "Doe", "Lemma", "Hemma"]
}
create_table(table)
Output:
uid | name |
------+------------+-
0 | Jon |
1 | Doe |
2 | Lemma |
3 | Hemma |
------+------------+-
Here's my solution:
def make_table(columns, data):
"""Create an ASCII table and return it as a string.
Pass a list of strings to use as columns in the table and a list of
dicts. The strings in 'columns' will be used as the keys to the dicts in
'data.'
Not all column values have to be present in each data dict.
>>> print(make_table(["a", "b"], [{"a": "1", "b": "test"}]))
| a | b |
|----------|
| 1 | test |
"""
# Calculate how wide each cell needs to be
cell_widths = {}
for c in columns:
values = [str(d.get(c, "")) for d in data]
cell_widths[c] = len(max(values + [c]))
# Used for formatting rows of data
row_template = "|" + " {} |" * len(columns)
# CONSTRUCT THE TABLE
# The top row with the column titles
justified_column_heads = [c.ljust(cell_widths[c]) for c in columns]
header = row_template.format(*justified_column_heads)
# The second row contains separators
sep = "|" + "-" * (len(header) - 2) + "|"
# Rows of data
rows = []
for d in data:
fields = [str(d.get(c, "")).ljust(cell_widths[c]) for c in columns]
row = row_template.format(*fields)
rows.append(row)
return "\n".join([header, sep] + rows)
This can be done with only builtin modules fairly compactly using list and string comprehensions. Accepts a list of dictionaries all of the same format...
def tableit(dictlist):
lengths = [ max(map(lambda x:len(x.get(k)), dictlist) + [len(k)]) for k in dictlist[0].keys() ]
lenstr = " | ".join("{:<%s}" % m for m in lengths)
lenstr += "\n"
outmsg = lenstr.format(*dictlist[0].keys())
outmsg += "-" * (sum(lengths) + 3*len(lengths))
outmsg += "\n"
outmsg += "".join(
lenstr.format(*v) for v in [ item.values() for item in dictlist ]
)
return outmsg
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 4 years ago.
The community reviewed whether to reopen this question 4 months ago and left it closed:
Original close reason(s) were not resolved
Improve this question
I'm looking for a way to pretty-print tables like this:
=======================
| column 1 | column 2 |
=======================
| value1 | value2 |
| value3 | value4 |
=======================
I've found the asciitable library but it doesn't do the borders, etc. I don't need any complex formatting of data items, they're just strings. I do need it to auto-size columns.
Do other libraries or methods exist, or do I need to spend a few minutes writing my own?
I've read this question long time ago, and finished writing my own pretty-printer for tables: tabulate.
My use case is:
I want a one-liner most of the time
which is smart enough to figure the best formatting for me
and can output different plain-text formats
Given your example, grid is probably the most similar output format:
from tabulate import tabulate
print tabulate([["value1", "value2"], ["value3", "value4"]], ["column 1", "column 2"], tablefmt="grid")
+------------+------------+
| column 1 | column 2 |
+============+============+
| value1 | value2 |
+------------+------------+
| value3 | value4 |
+------------+------------+
Other supported formats are plain (no lines), simple (Pandoc simple tables), pipe (like tables in PHP Markdown Extra), orgtbl (like tables in Emacs' org-mode), rst (like simple tables in reStructuredText). grid and orgtbl are easily editable in Emacs.
Performance-wise, tabulate is slightly slower than asciitable, but much faster than PrettyTable and texttable.
P.S. I'm also a big fan of aligning numbers by a decimal column. So this is the default alignment for numbers if there are any (overridable).
Here's a quick and dirty little function I wrote for displaying the results from SQL queries I can only make over a SOAP API. It expects an input of a sequence of one or more namedtuples as table rows. If there's only one record, it prints it out differently.
It is handy for me and could be a starting point for you:
def pprinttable(rows):
if len(rows) > 1:
headers = rows[0]._fields
lens = []
for i in range(len(rows[0])):
lens.append(len(max([x[i] for x in rows] + [headers[i]],key=lambda x:len(str(x)))))
formats = []
hformats = []
for i in range(len(rows[0])):
if isinstance(rows[0][i], int):
formats.append("%%%dd" % lens[i])
else:
formats.append("%%-%ds" % lens[i])
hformats.append("%%-%ds" % lens[i])
pattern = " | ".join(formats)
hpattern = " | ".join(hformats)
separator = "-+-".join(['-' * n for n in lens])
print hpattern % tuple(headers)
print separator
_u = lambda t: t.decode('UTF-8', 'replace') if isinstance(t, str) else t
for line in rows:
print pattern % tuple(_u(t) for t in line)
elif len(rows) == 1:
row = rows[0]
hwidth = len(max(row._fields,key=lambda x: len(x)))
for i in range(len(row)):
print "%*s = %s" % (hwidth,row._fields[i],row[i])
Sample output:
pkid | fkn | npi
-------------------------------------+--------------------------------------+----
405fd665-0a2f-4f69-7320-be01201752ec | 8c9949b9-552e-e448-64e2-74292834c73e | 0
5b517507-2a42-ad2e-98dc-8c9ac6152afa | f972bee7-f5a4-8532-c4e5-2e82897b10f6 | 0
2f960dfc-b67a-26be-d1b3-9b105535e0a8 | ec3e1058-8840-c9f2-3b25-2488f8b3a8af | 1
c71b28a3-5299-7f4d-f27a-7ad8aeadafe0 | 72d25703-4735-310b-2e06-ff76af1e45ed | 0
3b0a5021-a52b-9ba0-1439-d5aafcf348e7 | d81bb78a-d984-e957-034d-87434acb4e97 | 1
96c36bb7-c4f4-2787-ada8-4aadc17d1123 | c171fe85-33e2-6481-0791-2922267e8777 | 1
95d0f85f-71da-bb9a-2d80-fe27f7c02fe2 | 226f964c-028d-d6de-bf6c-688d2908c5ae | 1
132aa774-42e5-3d3f-498b-50b44a89d401 | 44e31f89-d089-8afc-f4b1-ada051c01474 | 1
ff91641a-5802-be02-bece-79bca993fdbc | 33d8294a-053d-6ab4-94d4-890b47fcf70d | 1
f3196e15-5b61-e92d-e717-f00ed93fe8ae | 62fa4566-5ca2-4a36-f872-4d00f7abadcf | 1
Example
>>> from collections import namedtuple
>>> Row = namedtuple('Row',['first','second','third'])
>>> data = Row(1,2,3)
>>> data
Row(first=1, second=2, third=3)
>>> pprinttable([data])
first = 1
second = 2
third = 3
>>> pprinttable([data,data])
first | second | third
------+--------+------
1 | 2 | 3
1 | 2 | 3
For some reason when I included 'docutils' in my google searches I stumbled across texttable, which seems to be what I'm looking for.
I too wrote my own solution to this. I tried to keep it simple.
https://github.com/Robpol86/terminaltables
from terminaltables import AsciiTable
table_data = [
['Heading1', 'Heading2'],
['row1 column1', 'row1 column2'],
['row2 column1', 'row2 column2']
]
table = AsciiTable(table_data)
print table.table
+--------------+--------------+
| Heading1 | Heading2 |
+--------------+--------------+
| row1 column1 | row1 column2 |
| row2 column1 | row2 column2 |
+--------------+--------------+
table.inner_heading_row_border = False
print table.table
+--------------+--------------+
| Heading1 | Heading2 |
| row1 column1 | row1 column2 |
| row2 column1 | row2 column2 |
+--------------+--------------+
table.inner_row_border = True
table.justify_columns[1] = 'right'
table.table_data[1][1] += '\nnewline'
print table.table
+--------------+--------------+
| Heading1 | Heading2 |
+--------------+--------------+
| row1 column1 | row1 column2 |
| | newline |
+--------------+--------------+
| row2 column1 | row2 column2 |
+--------------+--------------+
I just released termtables for this purpose. For example, this
import termtables as tt
tt.print(
[[1, 2, 3], [613.23236243236, 613.23236243236, 613.23236243236]],
header=["a", "bb", "ccc"],
style=tt.styles.ascii_thin_double,
padding=(0, 1),
alignment="lcr"
)
gets you
+-----------------+-----------------+-----------------+
| a | bb | ccc |
+=================+=================+=================+
| 1 | 2 | 3 |
+-----------------+-----------------+-----------------+
| 613.23236243236 | 613.23236243236 | 613.23236243236 |
+-----------------+-----------------+-----------------+
By default, the table is rendered with Unicode box-drawing characters,
┌─────────────────┬─────────────────┬─────────────────┐
│ a │ bb │ ccc │
╞═════════════════╪═════════════════╪═════════════════╡
│ 1 │ 2 │ 3 │
├─────────────────┼─────────────────┼─────────────────┤
│ 613.23236243236 │ 613.23236243236 │ 613.23236243236 │
└─────────────────┴─────────────────┴─────────────────┘
termtables are very configurable; check out the tests for more examples.
If you want a table with column and row spans, then try my library dashtable
from dashtable import data2rst
table = [
["Header 1", "Header 2", "Header3", "Header 4"],
["row 1", "column 2", "column 3", "column 4"],
["row 2", "Cells span columns.", "", ""],
["row 3", "Cells\nspan rows.", "- Cells\n- contain\n- blocks", ""],
["row 4", "", "", ""]
]
# [Row, Column] pairs of merged cells
span0 = ([2, 1], [2, 2], [2, 3])
span1 = ([3, 1], [4, 1])
span2 = ([3, 3], [3, 2], [4, 2], [4, 3])
my_spans = [span0, span1, span2]
print(data2rst(table, spans=my_spans, use_headers=True))
Which outputs:
+----------+------------+----------+----------+
| Header 1 | Header 2 | Header3 | Header 4 |
+==========+============+==========+==========+
| row 1 | column 2 | column 3 | column 4 |
+----------+------------+----------+----------+
| row 2 | Cells span columns. |
+----------+----------------------------------+
| row 3 | Cells | - Cells |
+----------+ span rows. | - contain |
| row 4 | | - blocks |
+----------+------------+---------------------+
You can try BeautifulTable. It does what you want to do. Here's an example from it's documentation
>>> from beautifultable import BeautifulTable
>>> table = BeautifulTable()
>>> table.columns.header = ["name", "rank", "gender"]
>>> table.rows.append(["Jacob", 1, "boy"])
>>> table.rows.append(["Isabella", 1, "girl"])
>>> table.rows.append(["Ethan", 2, "boy"])
>>> table.rows.append(["Sophia", 2, "girl"])
>>> table.rows.append(["Michael", 3, "boy"])
>>> print(table)
+----------+------+--------+
| name | rank | gender |
+----------+------+--------+
| Jacob | 1 | boy |
+----------+------+--------+
| Isabella | 1 | girl |
+----------+------+--------+
| Ethan | 2 | boy |
+----------+------+--------+
| Sophia | 2 | girl |
+----------+------+--------+
| Michael | 3 | boy |
+----------+------+--------+
Version using w3m designed to handle the types MattH's version accepts:
import subprocess
import tempfile
import html
def pprinttable(rows):
esc = lambda x: html.escape(str(x))
sour = "<table border=1>"
if len(rows) == 1:
for i in range(len(rows[0]._fields)):
sour += "<tr><th>%s<td>%s" % (esc(rows[0]._fields[i]), esc(rows[0][i]))
else:
sour += "<tr>" + "".join(["<th>%s" % esc(x) for x in rows[0]._fields])
sour += "".join(["<tr>%s" % "".join(["<td>%s" % esc(y) for y in x]) for x in rows])
with tempfile.NamedTemporaryFile(suffix=".html") as f:
f.write(sour.encode("utf-8"))
f.flush()
print(
subprocess
.Popen(["w3m","-dump",f.name], stdout=subprocess.PIPE)
.communicate()[0].decode("utf-8").strip()
)
from collections import namedtuple
Row = namedtuple('Row',['first','second','third'])
data1 = Row(1,2,3)
data2 = Row(4,5,6)
pprinttable([data1])
pprinttable([data1,data2])
results in:
┌───────┬─┐
│ first │1│
├───────┼─┤
│second │2│
├───────┼─┤
│ third │3│
└───────┴─┘
┌─────┬───────┬─────┐
│first│second │third│
├─────┼───────┼─────┤
│1 │2 │3 │
├─────┼───────┼─────┤
│4 │5 │6 │
└─────┴───────┴─────┘
I know it the question is a bit old but here's my attempt at this:
https://gist.github.com/lonetwin/4721748
It is a bit more readable IMHO (although it doesn't differentiate between single / multiple rows like #MattH's solutions does, nor does it use NamedTuples).
I use this small utility function.
def get_pretty_table(iterable, header):
max_len = [len(x) for x in header]
for row in iterable:
row = [row] if type(row) not in (list, tuple) else row
for index, col in enumerate(row):
if max_len[index] < len(str(col)):
max_len[index] = len(str(col))
output = '-' * (sum(max_len) + 1) + '\n'
output += '|' + ''.join([h + ' ' * (l - len(h)) + '|' for h, l in zip(header, max_len)]) + '\n'
output += '-' * (sum(max_len) + 1) + '\n'
for row in iterable:
row = [row] if type(row) not in (list, tuple) else row
output += '|' + ''.join([str(c) + ' ' * (l - len(str(c))) + '|' for c, l in zip(row, max_len)]) + '\n'
output += '-' * (sum(max_len) + 1) + '\n'
return output
print get_pretty_table([[1, 2], [3, 4]], ['header 1', 'header 2'])
output
-----------------
|header 1|header 2|
-----------------
|1 |2 |
|3 |4 |
-----------------
from sys import stderr, stdout
def create_table(table: dict, full_row: bool = False) -> None:
min_len = len(min((v for v in table.values()), key=lambda q: len(q)))
max_len = len(max((v for v in table.values()), key=lambda q: len(q)))
if min_len < max_len:
stderr.write("Table is out of shape, please make sure all columns have the same length.")
stderr.flush()
return
additional_spacing = 1
heading_separator = '| '
horizontal_split = '| '
rc_separator = ''
key_list = list(table.keys())
rc_len_values = []
for key in key_list:
rc_len = len(max((v for v in table[key]), key=lambda q: len(str(q))))
rc_len_values += ([rc_len, [key]] for n in range(len(table[key])))
heading_line = (key + (" " * (rc_len + (additional_spacing + 1)))) + heading_separator
stdout.write(heading_line)
rc_separator += ("-" * (len(key) + (rc_len + (additional_spacing + 1)))) + '+-'
if key is key_list[-1]:
stdout.flush()
stdout.write('\n' + rc_separator + '\n')
value_list = [v for vl in table.values() for v in vl]
aligned_data_offset = max_len
row_count = len(key_list)
next_idx = 0
newline_indicator = 0
iterations = 0
for n in range(len(value_list)):
key = rc_len_values[next_idx][1][0]
rc_len = rc_len_values[next_idx][0]
line = ('{:{}} ' + " " * len(key)).format(value_list[next_idx], str(rc_len + additional_spacing)) + horizontal_split
if next_idx >= (len(value_list) - aligned_data_offset):
next_idx = iterations + 1
iterations += 1
else:
next_idx += aligned_data_offset
if newline_indicator >= row_count:
if full_row:
stdout.flush()
stdout.write('\n' + rc_separator + '\n')
else:
stdout.flush()
stdout.write('\n')
newline_indicator = 0
stdout.write(line)
newline_indicator += 1
stdout.write('\n' + rc_separator + '\n')
stdout.flush()
Example:
table = {
"uid": ["0", "1", "2", "3"],
"name": ["Jon", "Doe", "Lemma", "Hemma"]
}
create_table(table)
Output:
uid | name |
------+------------+-
0 | Jon |
1 | Doe |
2 | Lemma |
3 | Hemma |
------+------------+-
Here's my solution:
def make_table(columns, data):
"""Create an ASCII table and return it as a string.
Pass a list of strings to use as columns in the table and a list of
dicts. The strings in 'columns' will be used as the keys to the dicts in
'data.'
Not all column values have to be present in each data dict.
>>> print(make_table(["a", "b"], [{"a": "1", "b": "test"}]))
| a | b |
|----------|
| 1 | test |
"""
# Calculate how wide each cell needs to be
cell_widths = {}
for c in columns:
values = [str(d.get(c, "")) for d in data]
cell_widths[c] = len(max(values + [c]))
# Used for formatting rows of data
row_template = "|" + " {} |" * len(columns)
# CONSTRUCT THE TABLE
# The top row with the column titles
justified_column_heads = [c.ljust(cell_widths[c]) for c in columns]
header = row_template.format(*justified_column_heads)
# The second row contains separators
sep = "|" + "-" * (len(header) - 2) + "|"
# Rows of data
rows = []
for d in data:
fields = [str(d.get(c, "")).ljust(cell_widths[c]) for c in columns]
row = row_template.format(*fields)
rows.append(row)
return "\n".join([header, sep] + rows)
This can be done with only builtin modules fairly compactly using list and string comprehensions. Accepts a list of dictionaries all of the same format...
def tableit(dictlist):
lengths = [ max(map(lambda x:len(x.get(k)), dictlist) + [len(k)]) for k in dictlist[0].keys() ]
lenstr = " | ".join("{:<%s}" % m for m in lengths)
lenstr += "\n"
outmsg = lenstr.format(*dictlist[0].keys())
outmsg += "-" * (sum(lengths) + 3*len(lengths))
outmsg += "\n"
outmsg += "".join(
lenstr.format(*v) for v in [ item.values() for item in dictlist ]
)
return outmsg