How to create a nested dictionary from existing dictionary with set and list of tuples

461 Views Asked by At

I have parsed a midi file, and I've successfully gotten a dictionary of notes broken up by instrument. An abbreviated example of this is note_dict below, truncated for the purposes of this question.

My end goal is to have a nested dictionary that provides me with the track name, then each possible note as a key, then a list of all possible "next" notes as values. The intent is to use this in as a Markov chain in Foxdot, a python interface for music generation.

It should look something like:

{'track1': {note: [note1, note2, note3], note2: [note1, note2, note3]}, 'track2': {note: [note1, note2, note3], note2: [note1, note2, note3]}

Here is an example of what I have:

import itertools 

def pairwise(iterable):
    a, b = itertools.tee(iterable)
    next(b, None)
    return list(zip(a, b))

note_dict = {'Vocal': [-2, -2, -1, -2], 'Guitar': [1, 1, 4, 1, -2, 1]}

note_dict_updated = { track: [{ n for n in notes }, pairwise(notes), notes] for track, notes in note_dict.items() }
print(note_dict_updated)

This gives me the following, where the first set is all distinct notes, the list of tuples is a pairing of (note, next note), and the last list is just a raw list of notes in order.

{'Vocal': [{-2, -1}, [(-2, -2), (-2, -1), (-1, -2)], [-2, -2, -1, -2]], 'Guitar': [{1, 4, -2}, [(1, 1), (1, 4), (4, 1), (1, -2), (-2, 1)], [1, 1, 4, 1, -2, 1]]}

I'd like the elements of the sets to act as keys, and when the first element of the tuple matches an element of the set, it is added to a list of values associated with the key.

My desired end result, based on note_dict above is:

{'Vocal': {-2: [-2, -1], -1: [-2]}, 'Guitar': {1: [1, 4, -2], 4: [1], -2: [1]}}

All that said, I am not locked into the method where I need to work with note_dict_updated. If there is a smarter way to get from note_dict to my desired end result, I'd love to hear.

edit: I've updated my question a bit. The first answer worked for my initial example, but I believe there are issues when the list of notes in each value overlap. Hopefully, my updated desired end result will be more helpful.

1

There are 1 best solutions below

2
On

The first loop creates an intermediary dictionary of dictionary with inner keys and the same unique sets. This is then cleaned with a second a for-loop as shown here:

Input:

{'Vocal': [-2, -2, -1, -2], 'Guitar': [1, 1, 4, 1]}

Output:

{'Guitar': {1: [1, 4], 4: [1]}, 'Vocal': {-2: [-1, -2], -1: [-2]}}

Code:

#create a new dictionary of dictionary with inner keys and same unique sets

note_dict_updated={}
for key, value in note_dict.iteritems():
    note_dict_updated[key]={}
    for element in set(note_dict[key]):
        note_dict_updated[key][element]=list(set(note_dict[key]))

# remove the values (of not interest) from list values of inner keys 
for key, value in note_dict_updated.iteritems():
    comb=[]
    for lkey, lvalue in note_dict_updated[key].iteritems():
        for val in lvalue:
            if (val,lkey) in comb:
                try:
                    note_dict_updated[key][lkey].remove(lkey)
                except ValueError as e:
                    print ('Issue in key {} for subkey {}'.format(key,lkey))
        for val in lvalue:
            comb.append((lkey,val))