We are storing configurations. Let’s assume we are working on a store and we want to save prices for different products. But there are a set of configurations we have to consider1:
- We are working over multiple countries.
- Every country has got some locations.
- Every location has a set of articles.
- For an article, I might have multiple sizes.
- Finally, for every size I have got prices.
Now let’s say that we want to store all of that in a Python dictionary. Luckily, the set of options is not too large, so that we can easily store it in a nested dictionary, which looks something like that:
| |
Now this all works out fine. All my prices are pushed to a Kafka topic so that multiple downstream consumers can poll all the prices and have the live prices available at all time.
Then there is a deletion request - say this shoe in size 42 was the last article in Graz and this now also does not get sold any more. Ideally, I want to drop the full AT object from my prices, as there is essentially nothing more to store for Austria.
The challenge
Now the simple way is the following: Remove the size, then find out that the shoe is empty and remove the shoe. Next, find out that Graz now is empty and remove Graz. Finally, remove Austria. These are just some nested if statements.
But I did not study maths and development of algorithms to just write a bunch of nested if stamements! So let’s over-engineer the problem and find a proper soltion that can work with as many layers as needed.
My solution
So let’s take another approach. This definitely is a recursive problem. I did choose a semi-recursive concept, though.
| |
Note that as given in my case, the relevant key attributes are passed to me in an ordered list. This is important for the rest to work out.
I put the whole concept into a function. That way, I can easily stop at any time if required by just leaving the function. Now what happens:
- I setup a
childrenlist which for now just holds the whole prices object. - Now I iterate over each key, except for the last one, in order. For every
keyelement, I add another entry to thechildrenarray which just contains the dictionary for this respective key.- If my key does not exist then this means that something went wrong. For example, assume that I wanted to delete the socks in size 43 from the Graz store. Since there are no socks in Graz, there is nothing to delete. So in the
forloop whenkey == "socks", the checkkey not in children[-1]isTrueand we will stop.
- If my key does not exist then this means that something went wrong. For example, assume that I wanted to delete the socks in size 43 from the Graz store. Since there are no socks in Graz, there is nothing to delete. So in the
- Now the relevant state obviously is the
childrenlist. After the firstforloop, it will look as follows:Note that every element of1 2 3 4 5 6[ {'AT': {'Graz': {'shoe': {'size-42': 99.99}}}}, # This is `prices` {'Graz': {'shoe': {'size-42': 99.99}}}, # prices["AT"] {'shoe': {'size-42': 99.99}}, # prices["AT"]["Graz"] {'size-42': 99.99} # prices["AT"]["Graz"]["shoe"] ]childrenis a reference to an already existing dictionary, and Dictionaries in Python are passed by reference (unless explicitly copied). This has two relevant advantages:- When editing an element of
childrenthen also thepricesdictionary gets updated accordingly. childrendoes not really take a lot of storage space. Not that we should take about efficiency in Python.
- When editing an element of
- Now in the last element of
children, it is simple to remove thesize-42attribute. This is what happens in the next line. - Finally, there is another
forloop, which does the cleanup. We will now traverse the same keys as before, but in reverse order. For every key, we pop the last element ofchildren. This directly returns the dictionary to the currently considered key.
With this dictionary we just popped, we only do one thing: We check if it still has elements. The pop and check happens combined in line 20, and we then react on it accordingly:- If the dictionary is not empty (the
elsepart) then we stop here, as nothing else has to be done. - If the dictionary is empty, then we want to remove this key from it’s parent dictionary. Since we popped the value of the current key,
children[-1]already is the parent dictionary of the key, and a pop here is easy!
We don’t even need a.pop(..., None)here, as we know that the key we want to pop definitely exists.
- If the dictionary is not empty (the
Conclusion
This is a totally overpowered version of cleaning up a nested dictionary. But i like it, as I can use the exact same code, no matter how deeply the dictionary is nested!
Please note that price data should be stored in another format, a database for example. But I did have to work with such a concept, altough it was not article-price-related. ↩︎