1

I want to display a dictionary in two columns. I'm basically trying to do what this question was trying to achieve with a list, but I need to do it with a dictionary.

My script prints the directory listing for the current folder, with numbers.

Here is the script:

import os

files = os.listdir(os.curdir)       #get directory listing

final_dic = dict()                  #initialize the dictionary

x = 1

for item in files:                  #associate index numbers with the filenames
    final_dic[x] = item
    x+=1

print '''
       -------------
       | FILE LIST |
       -------------
       '''

for k, v in final_dic.iteritems():              #print out the enumerated listing
    print ('{0}: {1}'.format(k, v))

num = int(raw_input('\nEnter no. of file: '))   #have user select file by no.

print ('\n{0}\n'.format(final_dic[num]))        #print out corresp. filename

The output of the file listing is:

   -------------
   | FILE LIST |
   -------------

1: file1
2: file2
3: file3
etc.

The problem is that in folders with a lot of files, the listing scrolls off the top of the screen, so I would like to be able to display the listing like this:

1: file1     4: file4
2: file2     5: file5
3: file3     6: file6
etc.

From the question linked to above, it looks like zip takes two lists and creates a dictionary out of them, but I've already got a dictionary.

So, is there a way to do this?

Added comment: some of the comments mention that I should use a list, but unfortunately it has to be a dictionary. This is just one part of a larger script.

Community
  • 1
  • 1
Marc Adler
  • 514
  • 1
  • 4
  • 16
  • Yes, but there's no nice columnizer; you have to write the logic yourself to figure out how many columns you want, then loop through with total/cols as your stepping. Would you be just as happy with rows instead of columns? That will be easier. – Prune May 20 '16 at 23:08
  • Not to answer your question, but you don't need a dictionary, you can use a list since you want the keys to just be numbers – joel goldstick May 20 '16 at 23:11
  • 1
    I actually do need it to be a dictionary, because the index no. is also associated with a file ID which is what the script is actually designed to work with. I left all the rest off because it's irrelevant to the question. – Marc Adler May 20 '16 at 23:16
  • This is more difficult than you might think depending on how dynamic you want things to be since there could be almost any number of files (so the number of digits in the file number could vary) and some of the filename could have very long — all of which you want to fit in two columns of some within some (undefined) maximum printing width. Something robust enough to gracefully handle all those variable is going to be relatively long and complicated. Your best bet is to find an existing module that does some or all of what you need. – martineau May 21 '16 at 00:24
  • @martineau That's exactly what I thought, too. I wanted to link to some module, but couldn't find a fitting one. There's [tabulate](https://pypi.python.org/pypi/tabulate), which prints really nice tables, but it doesn't do the asked-for column breaking ... Any idea what to use? – NichtJens May 21 '16 at 02:10

3 Answers3

2

I can't say that I like the solution you are pointing at very much.

But you can recycle that code directly by producing a list l like it is used there:

l = []
for k, v in final_dic.iteritems():
    l.append('{0}: {1}'.format(k, v))

... at the place where you print in your code. Then you use the answer that you linked to.


I had linked to https://pypi.python.org/pypi/tabulate before, but seem to have remembered its capabilities wrong. While it does print nice tables with justification/padding, it does not break columns as asked for...

NichtJens
  • 1,132
  • 10
  • 24
2

The following uses only simple column formatting, but should at least give you an idea of how to approach the problem. Filenames are simply truncated to a maximum length to keep the columns lined-up. Note that the file number is one more than its index in the files list created, so that has to be dealt with when accepting the user's input.

from itertools import islice
import os

files = sorted(os.listdir(os.curdir))  # get directory listing

desired_cols = 2
whole_rows, partial_rows = divmod(len(files), desired_cols)
num_rows = whole_rows + (1 if partial_rows > 0 else 0)

iterators = islice(files, None), islice(files, num_rows, None)

for i, f1 in enumerate(iterators[0], start=1):
    j = i + num_rows
    try:
        f2 = next(iterators[1])
    except StopIteration:
        print('{:2d}: {:38}'.format(i, f1[:38]))  # second column empty
        break
    print('{:2d}: {:38}  {:2d}: {:38}'.format(i, f1[:38], j, f2[:38]))

num = int(raw_input('\nEnter no. of file: '))  # have user select file by no.
index = num - 1
print ('\n{0}\n'.format(files[index]))  # print out corresp. filename
martineau
  • 99,260
  • 22
  • 139
  • 249
1

Since dictionaries are arbitrarily-ordered and you're not sorting it before printing it, you can simply take pairs of items and print those:

it = final_dic.iteritems()
for item in it:
    try:
        n = next(it)
    except StopIteration:
        print '{}: {}'.format(*item)
    else:
        print '{}: {}\t'.format(*item), '{}: {}'.format(*n)

An alternative method, with itertools.izip_longest():

it = final_dic.iteritems()
for a,b in itertools.izip_longest(it, it):
    print '{}: {}'.format(*a), '\t{}: {}'.format(*b) if b else ''

Using a list instead of a dictionary so that the output is ordered and goes column by column instead of row by row:

final_list = list(enumerate(files))
half = len(final_list)//2+1
for (idx1,val1),(idx2,val2) in itertools.izip_longest(
                               final_list[:half], final_list[half:], fillvalue=(0,'')):
    print '{}: {}'.format(idx1+1, val1) if val1 else '', '\t{}: {}'.format(idx2+1, val2) if val2 else ''

Test result with final_list = list(enumerate('abcde')):

1: a    4: d
2: b    5: e
3: c
TigerhawkT3
  • 44,764
  • 6
  • 48
  • 82