anechkayf
anechkayf

Reputation: 567

Building a table with the data from scratch Python

I need to build a table with the data like this:

     ┌────────┬───────────┬────────┐
     │ ID     │ Name      │ Age    │
     ├────────┼───────────┼────────┤
     │ 1      │ Jonh      │ 35     │
     ├────────┼───────────┼────────┤
     │ 2      │ Joseph    │ 40     │
     └────────┴───────────┴────────┘

I am not allowed use any Python libraries for that. It has to be done from scratch. I found that there are some box drawing unicode characters that I could use to draw the table(https://en.wikipedia.org/wiki/Box-drawing_character). Ex:

print(u'\u250C') -> will print ─

I am lost on how I should approach this problem. Should I print the data and then draw the table around or should I print the complete boxed row by row. Any help is appreciated.

My work so far:

length_list = [len(element) for row in data for element in row]
column_width = max(length_list)
for row in data:
   print(u'\u250C' + (u'\u2500'*column_width*len(data[0])) + u'\u2510')
   row = "".join(element.ljust(column_width + 2) for element in row)
   print(row)
   print(u'\u2514' + (u'\u2500'*column_width*len(data[0])) + u'\u2518')

Gives me this:

┌──────────────────┐
ID      Name    Age     
└──────────────────┘
┌──────────────────┐
1       John    35      
└──────────────────┘
┌──────────────────┐
2       Joseph  40      
└──────────────────┘

Upvotes: 7

Views: 976

Answers (5)

pho
pho

Reputation: 25500

You're nearly there. You could divide the task into smaller portions and use join() to make your life easier.

Let's first define some constant characters to improve code readability

char_line = u'\u2500'
char_lcorner_top = u'\u250C'
char_rcorner_top = u'\u2510'
char_lside = u'\u251C'
char_rside = u'\u2524'
char_top = u'\u252C'
char_bot = u'\u2534'
char_cross = u'\u253C'
char_lcorner_bot = u'\u2514'
char_rcorner_bot = u'\u2518'

Now let's write functions to create the lines between rows:

def top_rule(width, ncols):
    return char_lcorner_top + char_top.join([char_line * width for i in range(ncols)]) + char_rcorner_top

Explanation:

  • char_line * width multiplies the - character width times. Let's say width = 4. This would give four dashes like so: ----
  • [char_line * width for i in range(ncols)] creates a list with ncols items, each of which is ----.
  • char_top.join(...) joins the elements of the list with the character
  • Before returning, we add before and after the string we just created.

So top_rule(4, 3) gives "┌────┬────┬────┐"

Similarly, we can define more functions:

def bot_rule(width, ncols):
    return char_lcorner_bot + char_bot.join([char_line * width for i in range(ncols)]) + char_rcorner_bot

def mid_rule(width, ncols):
    return char_lside + char_cross.join([char_line * width for i in range(ncols)]) + char_rside

In each row, we have multiple cells to format. Let's write a function to format each row individually.

def fmt_row(row, width, loffset):
    return "|" + "|".join([cell.ljust(width - loffset).rjust(width) for cell in row]) + "|"

For each cell in the row, we left-justify the cell text to a length of (width - loffset) and right-justify it to width. Then, join with a pipe (|) and add pipes before and after

Now all we need to do is call the functions we made. Remember we only need to print a single mid_rule after all rows except the last. After the last row, we need to print a bot_rule.

num_cols = len(data[0])
length_list = [len(element) for row in data for element in row]
column_width = max(length_list) + 2

print(top_rule(column_width, num_cols))
for row in data[:-1]:
    print(fmt_row(row, column_width, 1))
    print(mid_rule(column_width, num_cols))

print(fmt_row(data[-1], column_width, 1))
print(bot_rule(column_width, num_cols))

With your data, you should get this:

┌────────┬────────┬────────┐
| ID     | Name   | Age    |
├────────┼────────┼────────┤
| 1      | John   | 35     |
├────────┼────────┼────────┤
| 2      | Joseph | 40     |
└────────┴────────┴────────┘

Upvotes: 5

AlpacaJones
AlpacaJones

Reputation: 134

This might just be a small challenge or something, but you really should try to clean up your code to make it more understandable to other people, but also help you understand what's going on, and importantly what's going wrong.

For example, we could write some functions for creating table_row, the table_top, and table_bottom. Then we simply print the table_top, print table_row (looping through each row), then print the table_bottom.

To keep things clean (like you already did) we would also need to factor in the size of each column.

def table_row(column_sizes: list, row: list):
    output = "│"
    for idx, value in enumerate(row): # assuming length of row is same as column_sizes
        val = " " + str(value)
        while len(val) < column_sizes[idx]:
            val += " "
        output += val + "│"
    return output

def table_top(column_sizes: list):
    output = "┌" 
    for idx, column_size in enumerate(column_sizes):
        output += "─" * column_size
        if idx != len(column_sizes) - 1:
            output += "┬"
    output += "┐"
    return output

def table_bottom(column_sizes: list):

    output = "└" 
    for idx, column_size in enumerate(column_sizes):
        output += "─" * column_size
        if idx != len(column_sizes) - 1:
            output += "┴"
    output += "┘"
    return output


# Find the size for each column. This could be implemented
# different ways depending on your data format.

data = [
    ["ID", "Name", "Age"],
    [1, "John", 35],
    [2, "Joseph", 40]
] 
column_sizes = [0 for i in range(len(data[0]))] # eg [0, 0, 0]
print(column_sizes)
for row in data:
    for idx,col in enumerate(row):
        if len(str(col)) > column_sizes[idx]: 
            column_sizes[idx] = len(str(col)) + 4 # spaces on either side
print(column_sizes) # [6, 8, 7]
      
print(table_top(column_sizes)) # ┌──────┬────────┬───────┐
for row in data:
    print(table_row(column_sizes, row))
    # │ ID   │ Name   │ Age   │
    # │ 1    │ John   │ 35    │
    # │ 2    │ Joseph │ 40    │

print(table_bottom(column_sizes)) # └──────┴────────┴───────┘

Upvotes: 1

hostingutilities.com
hostingutilities.com

Reputation: 9549

Instead of printing out boxes, you only want to print a single line of table characters between rows. Make sure you check if you're iterating over the first row, and if you're not then use the character.

And then you need to add a | to the beginning and end of each row you print out.

Also since the width of the table rows are always the same you can calculate it before your for loop, and it'll make the print statements look a little bit cleaner.

The following works as long as each row has the same number of items in it.

data = [list('abcde'), ['ab', 'cd', 'ef', 'gh', 'aa'], list('abcde')]

length_list = [len(element) for row in data for element in row]
min_gutter_space = 2 # minimum space between columns
column_width = max(length_list) + min_gutter_space
row_width = (len(data[0])+1) * column_width - 1
for i, row in enumerate(data):
    row = ['|', *row, '|']
    if i == 0:
        print('┌' + '─'*row_width + '┐')
    else:
        print('├' + '─'*row_width + '┤')
    print( "".join(element.ljust(column_width) for element in row) )
print('└' + '─'*row_width + '┘')

Upvotes: 1

Code-Apprentice
Code-Apprentice

Reputation: 83597

Should I print the data and then draw the table around or should I print the complete boxed row by row.

Printing to the terminal is done row by row. With the basic print() function in Python, there is no way to go back to a previous row. This means that you have to print the data and the borders as you go.

Upvotes: 1

Cz_
Cz_

Reputation: 371

It seems like you've basically got it. The only two minor changes you need to make are

  1. Print the rows formatted correctly in terms of spacing (See here) and put pipes between the the entries.
  2. Print the divider rows once per loop rather than twice. You only need to use the corner characters for the first and last iteration (Move one of the prints to above outside of the loop, keep track of how many rows you printed (perhaps using enumerate) and only print the corner characters instead of pipes if it's the last iteration)

Upvotes: 1

Related Questions