Printing tabular form lists with generators

Asked

Viewed 179 times

3

I have a list that represents 5 one-month periods with 31 days, and each period contains daily target values for a retail seller. Only for elucidation purposes would the empty structure be like this:

[[], [], [], [], []]

ITERATING OVER THE LIST (WITH VALUES):

for periodo in plan:
    print(periodo)

OUTPUT:

[2000.0, 2000.0, 2000.0, 2000.0, 2000.0, 2000.0]
[2000.0, 2000.0, 2000.0, 2000.0, 2000.0, 2000.0, 2000.0]
[2000.0, 2000.0, 1250.0, 1250.0, 1250.0, 1250.0, 1250.0]
[1250.0, 1250.0, 1250.0, 1250.0, 1250.0, 1250.0, 1250.0]
[1250.0, 1250.0, 1250.0]

The periods did not become equal because there is a cut on Sundays.

I want to plot these values in a modular way, with a horizontal header that discriminates each period like this:

print(f'\033[34m{"1º PERÍODO":<15}{"2º PERÍODO":<15}{"3º PERÍODO":<15}{"4º PERÍODO":<15}{"5º PERÍODO":<15}\033[m')

OUTPUT:

1º PERÍODO     2º PERÍODO     3º PERÍODO     4º PERÍODO     5º PERÍODO 

My difficulty is to have each element of the planning list (periods) organized horizontally according to the header. I tried a solution with geradores, among others, but it just doesn’t work.

My first attempt with generators was this:

gerador_periodo = (periodo for periodo in plan)
print(f"{next(gerador_periodo)}, {next(gerador_periodo)}, {next(gerador_periodo)}, {next(gerador_periodo)}, {next(gerador_periodo)}")

Obviously, it failed, because it printed the periods in a linear way, not respecting the organization of the header above.

My second attempt was using a list comprehension looped for to print breaking lines:

print(f"{[print(num) for num in next(gerador_periodo)]}, {[print(num) for num in next(gerador_periodo)]}, ...")

Here, I received on the screen all values below each other followed by the 5 periods with values None(Why? ). I tried using the parameter end in various ways also.

Is it possible to build this structure without using Pandas, Dataframe, etc? Some other idea?

1 answer

3


His second attempt generated several None because the comprehensilist on is using the return of print(num), and print always returns None.

Anyway, from what I understand, you want to have the first element of each of the periods in the first line, in the second line, the second element of each of the periods, etc.

That is, deep down you want to go through all the lists at the same time. It is usually used zip for that, but zip interrupts the loop when the smallest of the lists ends. Only in this case you want to go to the end of the largest of the lists, then you should use itertools.zip_longest:

plan = [
    [2000.0, 2000.0, 2000.0, 2000.0, 2000.0, 2000.0],
    [2000.0, 2000.0, 2000.0, 2000.0, 2000.0, 2000.0, 2000.0],
    [2000.0, 2000.0, 1250.0, 1250.0, 1250.0, 1250.0, 1250.0],
    [1250.0, 1250.0, 1250.0, 1250.0, 1250.0, 1250.0, 1250.0],
    [1250.0, 1250.0, 1250.0]
]

from itertools import zip_longest

size = 15
print(f'{"1º PERÍODO":<{size}}{"2º PERÍODO":<{size}}{"3º PERÍODO":<{size}}{"4º PERÍODO":<{size}}{"5º PERÍODO":<{size}}')

for metas in zip_longest(*plan, fillvalue=''):
    print(('{:<{size}}' * len(metas)).format(*metas, size=size))

The asterisk in *plan and *metas serves to make the unpacking. That is to say, zip_longest(*plan) is the same as doing zip_longest(plan[0], plan[1], etc...). But so I don’t need to know the exact amount of elements in plan.

At each iteration, the variable metas will be a tuple containing the elements of each of the lists contained in plan. In the first iteration, it has the first element of each, in the second iteration, the second element and so on.

The fillvalue indicates the value to be used when a list is smaller than the others and has no more elements to iterate. In this case, I used the empty string, but you can put whatever you want there.

To format, I generated a string that has several times {:<{size}} (the number of times is the number of elements in metas, ie, all values of it will be formatted equally). Then I step the values, also using unpacking, and indicate the size to be used in the alignment.

The exit is:

1º PERÍODO     2º PERÍODO     3º PERÍODO     4º PERÍODO     5º PERÍODO     
2000.0         2000.0         2000.0         1250.0         1250.0         
2000.0         2000.0         2000.0         1250.0         1250.0         
2000.0         2000.0         1250.0         1250.0         1250.0         
2000.0         2000.0         1250.0         1250.0                        
2000.0         2000.0         1250.0         1250.0                        
2000.0         2000.0         1250.0         1250.0                        
               2000.0         1250.0         1250.0                        

Another example, changing the fillvalue:

for metas in zip_longest(*plan, fillvalue='-'):
    print(('{:<{size}}' * len(metas)).format(*metas, size=size))

Exit:

1º PERÍODO     2º PERÍODO     3º PERÍODO     4º PERÍODO     5º PERÍODO     
2000.0         2000.0         2000.0         1250.0         1250.0         
2000.0         2000.0         2000.0         1250.0         1250.0         
2000.0         2000.0         1250.0         1250.0         1250.0         
2000.0         2000.0         1250.0         1250.0         -              
2000.0         2000.0         1250.0         1250.0         -              
2000.0         2000.0         1250.0         1250.0         -              
-              2000.0         1250.0         1250.0         -              

Note: for the header, you can also use:

print(('{:<{size}}' * len(plan)).format(*map(lambda x: f'{x + 1}º PERÍODO', range(len(plan))), size=size))

Or:

print(*map(lambda x: f'{str(x + 1) + "º PERÍODO":<{size}}', range(len(plan))), sep='')

So it also becomes dynamic, according to the amount of elements in plan.

  • @hksotsubo, helped a lot! The size having to be assigned to size is because of the interpolation within another interpolation, right?

  • Another interesting thing was that I couldn’t format the string with 2 floating houses (in case I needed to). I had to make this change directly into the other function that provides the planning for this modeling function that you created with me.

  • @dev.Uri O size is because inside the string used in format, it has to know what to put there (detail that would not need to have the same name, unlike an f-string, which has to be the same variable name). To always leave 2 decimal places, just use {:<{size}.2f}. But as in this case there are empty strings between the values, it will give error pq this format always expects numbers. So a solution would be to format outside the print. Example: https://ideone.com/Ud3LTW

  • Perfect. As I said, plan is a list generated by another function called planner that appends values in a for loop. I append an f-string formatted with . 2f to already receive the values this way in the tabular function. The problem was when calculating the sum of periods to put below each column to make it even more complete. I made a conversion to float in-place and calculated the sum of each period with sum().

  • I have 5 months of Python and the challenge is always to understand which is the best way, the most pythonic. Gradually this becomes clearer. In your opinion, the path I have set is a good one?

  • When you say format always expects numbers you mean what? Size in case is an integer. It has to be an even number?!

  • @dev.Curi Yes, the size there is the amount of spaces that will be used, so it only makes sense if it is an integer number. Look at this - and many more options - in the documentation: https://docs.python.org/3/library/string.html#formatspec - but what I meant is that the format .2f only works with numbers (because if it’s not a number it won’t have decimals and this format says to print only 2 houses), but as it also has the empty string in some cases (in the columns that are empty), so it doesn’t work, so I did the formatting in a separate function

Show 2 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.