0

I've tried to use pandas and PrettyTable but neither of them helped me in my case.

Here is my case:

left_headers = ['Numbers', 'Animals', 'Names', 'Flowers']
data = [
    [1, 2, 3, 4, 5, 6],
    ['dog', 'cat', 'rabbit', 'elephant', 'hyena', 'kangaroo'],
    ['short name', 'a very long name', '123', 'some text', 'different name', 'another name'],
    ['tulip', 'cactus', 'daffodil', 'hydrangea', 'geranium', 'rose']
]

Now I want it in this form:

Numbers 1           2                   3           4           5               6
Animals dog         cat                 rabbit      elephant    hyena           kangaroo
Names   short name  a very long name    123         some text   different name  another name
Flowers tulip       cactus              daffodil    hydrangea   geranium        rose

Data is separated by tabs not spaces. All beginning characters should be adjusted.

The main idea: headers are on left side. All data (and headers) are separated by some number of tabs. My problem is that I don't know how to predict how many tabs do I need to fit the data. I want to use as less tabs as possible to fit all data with minimal space but It should be at least one 'space' (like between "Numbers" and "1").

Edit: I did it with very ugly code. I added my answer.

Piotr Wasilewicz
  • 1,751
  • 2
  • 15
  • 26
  • Do you want to have tabs or spaces as horizontal spacer character? – Niko Föhr Jul 22 '20 at 17:32
  • 1
    Check solutions here https://stackoverflow.com/questions/63023871/how-can-i-align-every-word-from-this-list/63024106#63024106 . I am sure you can use a similar logic – vestronge Jul 22 '20 at 17:32
  • @np8 I added that information. – Piotr Wasilewicz Jul 22 '20 at 17:33
  • @vestronge maybe I can. It's not so hard with spaces I guess. tabs can have one, two, three or four "spaces" and it depends of position in the text (that's how I understand it). I checked the solution and I think it is not the answer. – Piotr Wasilewicz Jul 22 '20 at 17:36
  • 1
    @vestronge Am pretty sure this is all that the answer needs. – Albert Alberto Jul 22 '20 at 17:46
  • @AlbertAlberto I've tried with it and I don't think so. You can try. – Piotr Wasilewicz Jul 22 '20 at 17:48
  • 2
    You cannot line up the columns using only tabs without knowing *exactly* how tabs are going to be treated in the program that eventually displays your output. Commonly, tabs move to the next multiple of 8 spaces - but that isn't universal. If the output is shown in a GUI program using a non-monospaced font, where the space between tab stops is specified as a physical distance, there's no possible way to choose the right number of tabs to make the columns line up regardless of font or font size. – jasonharper Jul 22 '20 at 17:57
  • @jasonharper Where I need to use it tabs works like in windows notepad and windows notepad++ (and like here I think). It's "max 4 spaces". – Piotr Wasilewicz Jul 22 '20 at 18:02

3 Answers3

1

The answer depends on the required output format

1. With one tab (\t) separation

With tab (\t) separation it is very easy to print it:

for header, items in zip(left_headers, data):
    print(header, '\t', '\t'.join(map(str, items)))

Output:

Numbers          1      2       3       4       5       6
animals          dog    cat     rabbit  elephant        hyena   kangaroo
name     short name     a very long name        123     some text       different name  another name
flowers          tulip  cactus  daffodil        hydrangea       geranium        rose

Short explanation

  • map(str, items) turns a list of items into list of strings (one list was integers, so this is needed)
  • '\t'.join(lst) creates a new string from items in a list lst, and joins them with \t.
  • zip(lst1, lst2) is used to iterate two lists taking one element at time from each one.

2. With space separation (equal width columns)

This is one-liner with tabulate

from tabulate import tabulate
print(tabulate(data, showindex=left_headers, tablefmt='plain'))

Output

Numbers  1           2                 3         4          5               6
animals  dog         cat               rabbit    elephant   hyena           kangaroo
name     short name  a very long name  123       some text  different name  another name
flowers  tulip       cactus            daffodil  hydrangea  geranium        rose

3. With variable tab separation

This is the toughest one. One thing you have to do is to assume that how the tabulator is handled by the program that uses the output. Here it is assumed that "tab = 4 spaces".

import math 
import os 

SPACES_PER_TAB = 4

table = [[str(item) for item in items] for items in data]
for header, items in zip(left_headers, table):
    items.insert(0, header)

offset_table = [] # in tabs
for col in zip(*table):
    lengths = [len(x) for x in col]
    cell_length = math.ceil(max(lengths)/SPACES_PER_TAB)*SPACES_PER_TAB
    offsets_s = [cell_length - length for length in lengths] # in spaces
    additional_tabs = 1 if min(offsets_s) == 0 else 0
    offsets = [math.ceil(o/SPACES_PER_TAB) + additional_tabs for o in offsets_s]
    offset_table.append(offsets)


with open('table_out.txt', 'w') as f:
    for row, row_offsets in zip(table, zip(*offset_table)):
        for item, offset in zip(row, row_offsets):
            f.write(item)
            f.write('\t'*offset)
        f.write('\n')

The output looks like this (tabs copied here won't look good, so here's a printscreen from Notepad++)

enter image description here

Short explanation

  • First, we just create one table called table that contains the headers and the data as strings.
  • Then, we calculate the lengths of the cells (in spaces), assuming there is one additional space between cells. Then, one additional space is added if some cell would end up having no space before next cell.
  • Here the builtin zip() is really put to work, and it is used for example to transpose lists of lists by zip(*lst).
  • Finally, the results are written into an output file.
Niko Föhr
  • 28,336
  • 10
  • 93
  • 96
  • Your solution is exactly what I can do and what I don't want. Check that your columns are not adjusted like in my example. – Piotr Wasilewicz Jul 22 '20 at 17:40
  • You said that you want the data "separated by tabs not spaces". So you do want the separation with spaces, instead? – Niko Föhr Jul 22 '20 at 17:41
  • No. I want data nice-looking. Your is not. See my example below "Now I want it in this form:" – Piotr Wasilewicz Jul 22 '20 at 17:42
  • "short name" should start under dog (like tulip), "123" should start under "rabbit". Now "123" is... somewhere. – Piotr Wasilewicz Jul 22 '20 at 17:43
  • Nice-looking depends on where you want them to be inserted :) If you copy-paste to Excel, the tab separation will look perfect. If you want to use it as text, space-separation with equal width columns is what you need. – Niko Föhr Jul 22 '20 at 17:43
  • I know but I need to do it with tabs. Copy and paste it into simple text editor like "notepad". It should be "nice looking" here too. – Piotr Wasilewicz Jul 22 '20 at 17:45
  • Edited space separated table. – Niko Föhr Jul 22 '20 at 17:56
  • Now it's look like it should but still with spaces. Client user has a program (nearly 90s') that reads "tabulators separated data" and I can't use spaces. For now I am adding them on my own. Exactly the same result but with spaces won't work. – Piotr Wasilewicz Jul 22 '20 at 18:01
  • Sorry friend, you have to choose either spaces or tabs. Or do you want to replace each space character with a tabulator? In that case the table will ***not*** be visually aligned. – Niko Föhr Jul 22 '20 at 18:05
  • But I can allign the table on my own with tabs so I think (maybe I wrong) that it should be possible to do it automatically. And unfortunately I don't have choice. – Piotr Wasilewicz Jul 22 '20 at 18:08
  • And... You were wrong. I did it. One second I will answer my question. – Piotr Wasilewicz Jul 22 '20 at 18:19
  • 1
    Ok I see, so the goal is to replace sets of 4 spaces with tabs. Nice that you came up with a solution! – Niko Föhr Jul 22 '20 at 18:40
  • I think I have problems with expressing what is my goal but I see that you understood now. – Piotr Wasilewicz Jul 22 '20 at 18:44
  • 1
    No problem. Sometimes specifying the problem for another person is difficult :) I see it now! – Niko Föhr Jul 22 '20 at 18:46
1

You can use pandas to achieve this:

import pandas as pd

left_headers = ['Numbers', 'animals', 'name', 'flowers']
data = [
    [1, 2, 3, 4, 5, 6],
    ['dog', 'cat', 'rabbit', 'elephant', 'hyena', 'kangaroo'],
    ['short name', 'a very long name', '123', 'some text', 'different name', 'another name'],
    ['tulip', 'cactus', 'daffodil', 'hydrangea', 'geranium', 'rose']
]

df = pd.DataFrame(data, index=left_headers)
print(df.to_string(header=False))

The output is:

Numbers           1                 2         3          4               5             6
animals         dog               cat    rabbit   elephant           hyena      kangaroo
name     short name  a very long name       123  some text  different name  another name
flowers       tulip            cactus  daffodil  hydrangea        geranium          rose
Daniel Geffen
  • 1,777
  • 1
  • 11
  • 16
0

I did it! My code is not simple but does what I want:

left_headers = ['Numbers', 'Animals', 'Names', 'Flowers']
data = [
    [1, 2, 3, 4, 5, 6],
    ['dog', 'cat', 'rabbit', 'elephant', 'hyena', 'kangaroo'],
    ['short name', 'a very long name', '123', 'some text', 'different name', 'another name'],
    ['tulip', 'cactus', 'daffodil', 'hydrangea', 'geranium', 'rose']
]
for i in range(len(left_headers)):
    print(left_headers[i], end="\t")
    how_many_tabs_do_i_need = max([len(h) for h in left_headers]) // 4
    how_many_tabs_actual_word_has = len(left_headers[i]) // 4
    print("\t"*(how_many_tabs_do_i_need-how_many_tabs_actual_word_has), end="")
    for j in range(len(data[0])):
        how_many_tabs_do_i_need = max([len(str(data[k][j])) for k in range(len(left_headers))]) // 4
        how_many_tabs_actual_word_has = len(str(data[i][j])) // 4
        print(str(data[i][j]) +"\t"*(how_many_tabs_do_i_need - how_many_tabs_actual_word_has + 1), end="")
    print()

The output:

Numbers 1           2                   3           4           5               6               
Animals dog         cat                 rabbit      elephant    hyena           kangaroo        
Names   short name  a very long name    123         some text   different name  another name    
Flowers tulip       cactus              daffodil    hydrangea   geranium        rose

If one's can simplify the code - the problem is open.

Piotr Wasilewicz
  • 1,751
  • 2
  • 15
  • 26