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

1

7

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.

Papuan answered 19/7, 2020 at 23:26 Comment(0)
G
1

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))
Gasparo answered 20/7, 2020 at 4:17 Comment(2)
thanks! When running this for the actual dictionary (above 16k values), I get ValueError: list.remove(x): x not in list Your code works perfectly on small dictionaries, though. Do you have any recommendations to get around this concern with remove()?Papuan
I've added an exception handler that will print the offending key & subkey. It might help you to discern why the error is arising.Gasparo

© 2022 - 2024 — McMap. All rights reserved.