5 ms·
> # turns out that the most difficult problem in computer science is aligning things > # this one function looks simple but it took so fucking long Heh. I can
by bheadmaster 2y ago
> # turns out that the most difficult problem in computer science is aligning things
> # this one function looks simple but it took so fucking long
Heh. I can't count how many times I've written a column-aligning functions in various programming languages, and each time it is a pain. And it sounds simple in my head - just get max-length of each column, and add spaces up to the next multiple of tab-size.
But even in Python with f-strings and all the fancy padding stuff it has, it is ends up a convoluted, unreadable mess:
# randomwordgenerator.com
table = [
['agony', 'kick', 'pump'],
['frown', 'lonely', 'mutation'],
['sail', 'tasty', 'want'],
]
tab_width = 4
n_rows, n_cols = len(table), len(table[0])
max_width = [
max(len(table[r][c]) for r in range(n_rows))
for c in range(n_cols)
]
for r in range(n_rows):
for c in range(n_cols):
item = table[r][c]
if c == n_cols - 1:
# do not print space after last item
print(item)
else:
# only print newline after last item
# EDIT: found a bug here after commenting...
# width = int(((max_width[c] + 1) / tab_width) * tab_width)
width = ((max_width[c] // tab_width) + 1) * tab_width
print(f'{item:<{width}}', end='')
Even while writing this code for the comment for the hundredth time, I've had to fix at least 5 bugs! Truly horrible.
- freedomben 2y agoOh man, seriously! It's way harder than it seems like it would. It can also be tempting to make use of tabs, but IME that usually makes it worse because you will hit edge cases that mess it up, so then you have to start tracking different behavior. It's a hell of trek.
- anthk 2y agoawk does that for free. Heck, awk can read /proc just fine. No bash needed.
- guipsp 2y ago> without ever forking
- Izkata 2y agoYou need one fork to start OP's bash script, and one fork to start awk. No further forking after that.
- izabera 2y agojust type it all in your current live shell manually :)
- fsckboy 2y agoyou can put it in a function, functions don't fork
- AshamedCaptain 2y agoTechnically you don't need to fork bash if you are already running it. ( . AKA source does not fork )
- btilly 2y agoIf you want to launch another process, might as well launch ps. The challenge here is how to get ps in an environment where the command won't run. And in that case, awk won't run either. As for why it might happen, well just save the following shell script and run it on a Linux system. #! /bin/sh $0 & perl -e 'push @big, 1 while 1` & $0 Now that your system is struggling, figure out how to rescue it. (True story. I once worked with a careless programmer who would make mistakes whose results looked like that fairly regularly. It was...an education.)
- liveoneggs 2y agohttps://perldoc.perl.org/perlform https://perldoc.perl.org/perlform
- shawn_w 2y agohttps://metacpan.org/pod/Perl6::Form https://metacpan.org/pod/Perl6::Form for a modern less idiosyncratic version. https://metacpan.org/pod/Term::Table https://metacpan.org/pod/Term::Table or https://metacpan.org/pod/Text::Table https://metacpan.org/pod/Text::Table for simple table formatting.
- Mister_Snuggles 2y agoI've literally added Pandas to some of my projects just so I could use a DataFrame to print a nicely formatted table instead of writing code like this. Surely there's a library out there to do this job, it seems like such a common use-case. I'm surprised it's not in the standard library to be honest!
- roelschroeven 2y agoThere is a library: I use tabulate ([1]) semi-regularly for this purpose. There are probably more. I agree it would be very nice to have this in the standard library. [1] https://pypi.org/project/tabulate/ https://pypi.org/project/tabulate/, https://github.com/astanin/python-tabulate https://github.com/astanin/python-tabulate
- Mister_Snuggles 2y agoThank you - this looks like exactly what I was hoping for!
- Liquix 2y agoPrettyTable is another option, actively maintained and much lighter than Pandas
- INTPenis 2y agoI recently used the rich library to print tables in terminal. https://github.com/Textualize/rich https://github.com/Textualize/rich
- dotancohen 2y agoI answered a question once with a rather simple O(n) solution: https://stackoverflow.com/questions/10865483/print-results-in-mysql-format-with-python/20383011#20383011 https://stackoverflow.com/questions/10865483/print-results-i... sql = "SELECT * FROM someTable" cursor.execute(sql) conn.commit() results = cursor.fetchall() widths = [] columns = [] tavnit = '|' separator = '+' for cd in cursor.description: widths.append(max(cd[2], len(cd[0]))) columns.append(cd[0]) for w in widths: tavnit += " %-"+"%ss |" % (w,) separator += '-'*w + '--+' print(separator) print(tavnit % tuple(columns)) print(separator) for row in results: print(tavnit % row) print(separator) Am I missing anything, are there any glaring bugs? I don't see this as a particularly difficult problem.
- larsnystrom 2y agoI’m at the other end of things: I often have to parse column-aligned data. Sounds easy, except that values can contain spaces, are padded with spaces, are sometimes misaligned and sometimes overflow their column. Maybe you and I can make a pact here and now to just not column-align data, but rather use some simpler human-readable format? Win-win?
- bheadmaster 2y ago> Maybe you and I can make a pact here and now to just not column-align data, but rather use some simpler human-readable format? Win-win? To be fair, most of scripts that print tabular data for human reading also contain "--json-out" flag. Maybe I can just use YAML as a compromise? :)
- sandermvanvliet 2y agoHave you ever used the Octopus Deploy command line tool? It says on every command “oh you can use -f json” except that almost none of its commands actually implement it and you get the human readable output which you then have to sed/awk/grep your way around in…
- bheadmaster 2y agoI mean to write "most of my scripts". My mistake. > Have you ever used the Octopus Deploy command line tool? Thankfully, no :) but I've had similar experience with other pieces of software. Some even provide ability to output JSON, but you need to find the right incantation to do so (looking at you, Docker CLI, and your --format="{{json .}}"!)
- deleted 2y ago[deleted]
- RodgerTheGreat 2y agoI had a go at doing this in Lil, with the simplification of using spaces for alignment instead of tabs, since tab-formatting can be very brittle: on format_tab x do w:max each r in x count @ r end f:(list "%%-%is ")format -1 drop w ("\n",""fuse f,"%s")format x end The basic idea is to assemble a format string which right-pads all but the last column, and then use that format string uniformly across all rows. And then in K for comparison (somewhat clumsily): ft:{,/"\n"/((1+|/#:''t)$/:t:-1_'x),'-1#'x} Much simpler still if we permit trailing whitespace padding: ft:{,/"\n"/(1+|/#:''x)$/:x}
- deleted 2y ago[deleted]
- deleted 2y ago[deleted]
- deno 2y agofrom itertools import zip_longest tab_width = 4 col_max_widths = [(v := max(map(len, a))) + tab_width - (v % tab_width) for a in zip_longest(*table, fillvalue='')] for row in table: print(''.join(c.ljust(cw) for cw, c in zip(col_max_widths, row)))
- bheadmaster 2y agoIsn't zip_longest only useful for zipping sequences of different lengths? What exactly is it doing here that couldn't be done with plain zip? Also that code (IMHO, of course) still gives me a headache when I try to mentally parse it.
- deno 2y agoRegular zip works, I just don’t like the worst case because if one row has fewer columns the entire column will be quietly dropped since builtin zip works as zip_shortest rather than raise an exception. I never use bultin zip on input data.
- otteromkram 2y agoHow about this? max_width = max(len(x) for y in table for x in y)) items = [] for row in table: rowstr = "".join( el + " " * (max_width - len(el)) for el in row ) items.append(rowstr) # or, to print inline #for el in row: # print(item, end = " " * (max_width - len(el))) #print() print("\n".join(items))
- robertlagrant 2y agoYou could probably do something a little simpler: widths = [len(max(col, key=len)) for col in zip(*table)] print("\n".join(" | ".join([f"{r:<{w}}" for r, w in zip(row, widths)]) for row in table)) # Gives: # agony | kick | pump # frown | lonely | mutation # sail | tasty | want
- robertlagrant 2y agoFractionally more compact: widths = [max(map(len, col)) for col in zip(*table)] print("\n".join(" | ".join(f"{r:<{w}}" for r, w in zip(row, widths)) for row in table))
- bheadmaster 2y agoHa, clever. It didn't occur to me that I could use zip for extracting columns from a table. I personally prefer readable to compact, so this is what I ended up with, inspired by your version: tab_width = 4 columns = zip(*table) column_widths = [ max(len(item) for item in column) for column in columns ] column_indents = [ tab_width * ((width // tab_width) + 1) for width in column_widths ] for row in table: items = [ f'{item:<{indent}}' for item, indent in zip(row, column_indents) ] print(''.join(items).rstrip())
- robertlagrant 2y agoHah nice - yes I had it on three lines, which breaks out the logic a little better, but I thought it'd be good to just have a couple of lines to paste in and it would work. Someone elsewhere said they import pandas just to print a table! Surely overkill.