-1

I want to convert a simple dict like so: d = {0:0, 1:1, 2:2, 3:3} to a list of dict with the same amount of elements in each of them (or so) as this one: [{0:0, 2:2}, {1:1, 3:3}]. I've tried with simple indexing like I use with lists but it throws me a TypeError: unhashable type: 'slice'. Here what i have right now:

def dico_chunks(dico, n):
    if len(dico) < n:
        n = len(dico)
    return [dico[i::n] for i in range(n)]

Keep in mind that i don't need the list to be order in anyway. I just need to have my main dict splited in a list of n sub-dicts.

Some Guy
  • 70
  • 3
  • 10
  • You need to decide on a rule for which elements to put in the same dictionary inside the list. – Tatsuya Yokota Nov 17 '17 at 15:48
  • Dictionaries are unordered collections, the items don't have a natural index number associated with them. You could split the dict's keys list, but bear in mind that that list may not be in the order that you expect. – PM 2Ring Nov 17 '17 at 15:50
  • Can you make the rule for splitting more clearly? – Chivorn Kouch Nov 17 '17 at 15:58
  • I need each value in the main dict to appear only once in any sub-dict. I just need to split my main dict in a number of smaller dict (`n`) with the same number of value in each sub-dict. – Some Guy Nov 17 '17 at 16:04
  • Does the accepted answer in the linked question do what you want? – PM 2Ring Nov 17 '17 at 16:06
  • No it doesn't since the last arg is not the number of chunks/dicts to return but the size of the chunks. @PM2Ring – Some Guy Nov 17 '17 at 16:17
  • Well, that's only a minor issue, and schwobaseggl has shown how to perform the necessary calculation. Of course, if the chunk size isn't a divisor of the list length then some chunks will be smaller than the specified size. I was more concerned that the code in the accepted answer of the linked question splits the original dict into contiguous blocks, rather than doing what your `dico[i::n] for i in range(n)` attempts to do. – PM 2Ring Nov 17 '17 at 16:24
  • Yup just accepted the answer! Had a hard time figuring out schwobaseggl codes but everthings fine! I'm sorry I've waste your time, gentlemen. – Some Guy Nov 17 '17 at 16:30

2 Answers2

6

Generator based approach:

def chunk_dict(d, chunk_size):
    r = {}
    for k, v in d.items():
        if len(r) == chunk_size:
            yield r
            r = {}
        r[k] = v
    if r:
        yield r

d = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}
list(chunk_dict(d, 2))
# [{1: 1, 2: 2}, {3: 3, 4: 4}, {5: 5, 6: 6}]

A shorter and more performant version of the same functionality (using itertools.islice) can be found in this answer.

For a given number of as evenly as possible distributed chunks (e.g. sizes 4, 4, 3, 3 instead of 4, 4, 4, 2), you can do:

chunks = [{} for _ in range(num_chunks)]
for i, k in enumerate(d):
    chunks[i % num_chunks][k] = d[v]
user2390182
  • 72,016
  • 6
  • 67
  • 89
0

You can do this using the pairwise function defined in Iterate through pairs of items in a Python list, passing it the items() of the dict:

from itertools import tee

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

d = {0:0, 1:1, 2:2, 3:3}

list(map(dict,pairwise(d.items())))
# [{0: 0, 1: 1}, {1: 1, 2: 2}, {2: 2, 3: 3}]

If you want non-repeating pairs, then you can use zip:

items = list(d.items())

list(map(dict, zip(items[::2], items[1::2])))
# [{0: 0, 1: 1}, {2: 2, 3: 3}]

Dictionaries are fundamentally unordered, so if you want a particular order you need to specify that using some other logic.

jakevdp
  • 77,104
  • 11
  • 125
  • 160