Get keys of a deeply nested dictionary by the nesting level - python

I have a multilevel dictionary, imagine something like:
raw_dict = {'a':{'b':{'c':[1,2,3]}},
'd':{'e':{'f':{'g':[4,5,6]}}}}
Is it possible to access the keys of a specified nesting level?
That is, is there a way to do something like:
level = 1
keys_level_1 = list([a way to call the dictionary at specified nesting level])
print(keys_level_1)
which will return
['b', 'e']
this is similar, but not exactly what I want.

Use a recursive function
def get_keys(dct, level):
def get_(d, current=0):
# check if we're at the level we want, yield if so
if current==level:
yield d
# if not, move down one more level
else:
for v in d.values():
# only move down if the value is a dict
if isinstance(v, dict):
yield from get_(v, current+1)
return [x for s_l in get_(dct) for x in s_l]
raw_dict = {'a':{'b':{'c':[1,2,3]}},
'd':{'e':{'f':{'g':[4,5,6]}}}}
get_keys(raw_dict, 0)
['a', 'd']
get_keys(raw_dict, 1)
['b', 'e']
get_keys(raw_dict, 2)
['c', 'f']
get_keys(raw_dict, 3)
['g']

One possibility using a recursive function:
raw_dict = {'a':{'b':{'c':[1,2,3]}},
'd':{'e':{'f':{'g':[4,5,6]}}}}
def get_level(d, level):
from itertools import chain
if level == 0:
return list(d)
else:
return list(chain.from_iterable(get_level(d[item], level=level-1)
for item in d if isinstance(d[item], dict)))
get_level(raw_dict, level=1)
output: ['b', 'e']
other examples:
get_level(raw_dict, level=0)
['a', 'd']
get_level(raw_dict, level=1)
['b', 'e']
get_level(raw_dict, level=2)
['c', 'f']
get_level(raw_dict, level=3)
['g']
get_level(raw_dict, level=4)
[]

Related

How to return a shuffled list considering mutually exclusive items?

Say I have a list of options and I want to pick a certain number randomly.
In my case, say the options are in a list ['a', 'b', 'c', 'd', 'e'] and I want my script to return 3 elements.
However, there is also the case of two options that cannot appear at the same time. That is, if option 'a' is picked randomly, then option 'b' cannot be picked. And the same applies the other way round.
So valid outputs are: ['a', 'c', 'd'] or ['c', 'd', 'b'], while things like ['a', 'b', 'c'] would not because they contain both 'a' and 'b'.
To fulfil these requirements, I am fetching 3 options plus another one to compensate a possible discard. Then, I keep a set() with the mutually exclusive condition and keep removing from it and check if both elements have been picked or not:
import random
mutually_exclusive = set({'a', 'b'})
options = ['a', 'b', 'c', 'd', 'e']
num_options_to_return = 3
shuffled_options = random.sample(options, num_options_to_return + 1)
elements_returned = 0
for item in shuffled_options:
if elements_returned >= num_options_to_return:
break
if item in mutually_exclusive:
mutually_exclusive.remove(item)
if not mutually_exclusive:
# if both elements have appeared, then the set is empty so we cannot return the current value
continue
print(item)
elements_returned += 1
However, I may be overcoding and Python may have better ways to handle these requirements. Going through random's documentation I couldn't find ways to do this out of the box. Is there a better solution than my current one?
One way to do this is use itertools.combinations to create all of the possible results, filter out the invalid ones and make a random.choice from that:
>>> from itertools import combinations
>>> from random import choice
>>> def is_valid(t):
... return 'a' not in t or 'b' not in t
...
>>> choice([
... t
... for t in combinations('abcde', 3)
... if is_valid(t)
... ])
...
('c', 'd', 'e')
Maybe a bit naive, but you could generate samples until your condition is met:
import random
options = ['a', 'b', 'c', 'd', 'e']
num_options_to_return = 3
mutually_exclusive = set({'a', 'b'})
while True:
shuffled_options = random.sample(options, num_options_to_return)
if all (item not in mutually_exclusive for item in shuffled_options):
break
print(shuffled_options)
You can restructure your options.
import random
options = [('a', 'b'), 'c', 'd', 'e']
n_options = 3
selected_option = random.sample(options, n_options)
result = [item if not isinstance(item, tuple) else random.choice(item)
for item in selected_option]
print(result)
I would implement it with sets:
import random
mutually_exclusive = {'a', 'b'}
options = ['a', 'b', 'c', 'd', 'e']
num_options_to_return = 3
while True:
s = random.sample(options, num_options_to_return)
print('Sample is', s)
if not mutually_exclusive.issubset(s):
break
print('Discard!')
print('Final sample:', s)
Prints (for example):
Sample is ['a', 'b', 'd']
Discard!
Sample is ['b', 'a', 'd']
Discard!
Sample is ['e', 'a', 'c']
Final sample: ['e', 'a', 'c']
I created the below function and I think it's worth sharing it too ;-)
def random_picker(options, n, mutually_exclusives=None):
if mutually_exclusives is None:
return random.sample(options, n)
elif any(len(pair) != 2 for pair in mutually_exclusives):
raise ValueError('Lenght of pairs of mutually_exclusives iterable, must be 2')
res = []
while len(res) < n:
item_index = random.randint(0, len(options) - 1)
item = options[item_index]
if any(item == exc and pair[-(i - 1)] in res for pair in mutually_exclusives
for i, exc in enumerate(pair)):
continue
res.append(options.pop(item_index))
return res
Where:
options is the list of available options to pick from.
n is the number of items you want to be picked from options
mutually_exclusives is an iterable containing tuples pairs of mutually exclusive items
You can use it as follows:
>>> random_picker(['a', 'b', 'c', 'd', 'e'], 3)
['c', 'e', 'a']
>>> random_picker(['a', 'b', 'c', 'd', 'e'], 3, [('a', 'b')])
['d', 'b', 'e']
>>> random_picker(['a', 'b', 'c', 'd', 'e'], 3, [('a', 'b'), ('a', 'c')])
['e', 'd', 'a']
import random
l = [['a','b'], ['c'], ['d'], ['e']]
x = [random.choice(i) for i in random.sample(l,3)]
here l is a two-dimensional list, where the fist level reflects an and relation and the second level an or relation.

Looping through list and nested lists in python

Im trying to add a prefix to a list of strings in Python. The list of strings may contain multiple levels of nested lists.
Is there a way to loop through this list (and its nested lists), while keeping the structure?
nested for-loops became unreadable very quick, and did not seem to be the right approach..
list = ['a', 'b', ['C', 'C'], 'd', ['E', ['Ee', 'Ee']]]
for i in list:
if isinstance(i, list):
for a in i:
a = prefix + a
#add more layers of for loops
else:
i = prefix + i
desired outcome:
prefix = "#"
newlist = ['#a', '#b', ['#C', '#C'], '#d', ['#E', ['#Ee', '#Ee']]]
Thanks in advance!
You could write a simple recursive function
def apply_prefix(l, prefix):
# Base Case
if isinstance(l, str):
return prefix + l
# Recursive Case
else:
return [apply_prefix(i, prefix) for i in l]
l = ['a', 'b', ['C', 'C'], 'd', ['E', ['Ee', 'Ee',]]]
print(apply_prefix(l, "#"))
# ['#a', '#b', ['#C', '#C'], '#d', ['#E', ['#Ee', '#Ee']]]
This will use recursion:
a = ['a', 'b', ['C', 'C'], 'd', ['E', ['Ee', 'Ee',]]]
def insert_symbol(structure, symbol='#'):
if isinstance(structure, list):
return [insert_symbol(sub_structure) for sub_structure in structure]
else:
return symbol + structure
print(insert_symbol(a))
>>> ['#a', '#b', ['#C', '#C'], '#d', ['#E', ['#Ee', '#Ee']]]
You can use a recursive code like this!, Try it, and if you have questions you can ask me
def add_prefix(input_list):
changed_list = []
for elem in input_list:
if isinstance(elem, list):
elem = add_prefix(elem)
changed_list.append(elem)
else:
elem = "#" + elem
changed_list.append(elem)
return changed_list
Maybe you can use a function to do it recursively.
list_example = ['a', 'b', ['C', 'C'], 'd', ['E', ['Ee', 'Ee']]]
def add_prefix(p_list, prefix):
for idx in range(len(p_list)):
if isinstance(p_list[idx], list):
p_list[idx] = add_prefix(p_list[idx], prefix)
else:
p_list[idx] = prefix + p_list[idx]
return p_list
add_prefix(list_example, '#')
edit: I see now someone has posted the almost same thing.
btw. it is considered bad practice to name a list list, since it is also a typename in python. Might result in unwanted behaviour

Removing duplicates (not by using set)

My data look like this:
let = ['a', 'b', 'a', 'c', 'a']
How do I remove the duplicates? I want my output to be something like this:
['b', 'c']
When I use the set function, I get:
set(['a', 'c', 'b'])
This is not what I want.
One option would be (as derived from Ritesh Kumar's answer here)
let = ['a', 'b', 'a', 'c', 'a']
onlySingles = [x for x in let if let.count(x) < 2]
which gives
>>> onlySingles
['b', 'c']
Try this,
>>> let
['a', 'b', 'a', 'c', 'a']
>>> dict.fromkeys(let).keys()
['a', 'c', 'b']
>>>
Sort the input, then removing duplicates becomes trivial:
data = ['a', 'b', 'a', 'c', 'a']
def uniq(data):
last = None
result = []
for item in data:
if item != last:
result.append(item)
last = item
return result
print uniq(sorted(data))
# prints ['a', 'b', 'c']
This is basically the shell's cat data | sort | uniq idiom.
The cost is O(N * log N), same as with a tree-based set.
Instead of sorting, or linearly scanning and re-counting the main list for its occurrences each time.
Count the number of occurrences and then filter on items that appear once...
>>> from collections import Counter
>>> let = ['a', 'b', 'a', 'c', 'a']
>>> [k for k, v in Counter(let).items() if v == 1]
['c', 'b']
You have to look at the sequence at least once regardless - although it makes sense to limit the amount of times you do so.
If you really want to avoid any type or set or otherwise hashed container (because you perhaps can't use them?), then yes, you can sort it, then use:
>>> from itertools import groupby, islice
>>> [k for k,v in groupby(sorted(let)) if len(list(islice(v, 2))) == 1]
['b', 'c']

A program to mutate Nested Lists

Could someone help me do two things:
Review the code and see if it could be written in a better way.
Finish this program. I got stuck in trying to put the list back the way it is. i.e. a nested list of lists.
Here we go:
t = ['a', 'b', ['c', 'd'], ['e'], 'f']
def capitalize_list(t):
L = []
N = []
for i in range(len(t)):
if type(t[i]) == str:
L.append(t[i].capitalize())
if type(t[i]) == list:
L.extend(t[i])
for s in L:
N.append(s.capitalize())
print N
capitalize_list(t)
This code prints:
['A', 'B', 'C', 'D', 'E', 'F']
I need it to print:
['A', 'B', ['C', 'D'], ['E'], 'F']
You can use recursion:
def capitalize_list(t):
N = []
for i in range(len(t)):
if type(t[i]) == str:
N.append(t[i].capitalize())
if type(t[i]) == list:
N.append(capitalize_list(t[i]))
return N
Output:
['A', 'B', ['C', 'D'], ['E'], 'F']
An alternative way of doing this recursively:
def capitalize(item):
if type(item) is str:
return item.capitalize()
if type(item) is list:
return map(capitalize, item)
You could even do
def capitalize(item):
try:
return item.capitalize()
except AttributeError:
return map(capitalize, item)
-- which would use the capitalize method for any object that has it (such as a normal or unicode string) and attempt to iterate through any object that does not.

Obtain all subtrees in value

Given "a.b.c.d.e" I want to obtain all subtrees, efficiently, e.g. "b.c.d.e" and "c.d.e", but not "a.d.e" or "b.c.d".
Real world situation:
I have foo.bar.baz.example.com and I want all possible subdomain trees.
listed = "a.b.c.d.e".split('.')
subtrees = ['.'.join(listed[idx:]) for idx in xrange(len(listed))]
Given your sample data, subtrees equals ['a.b.c.d.e', 'b.c.d.e', 'c.d.e', 'd.e', 'e'].
items = data.split('.')
['.'.join(items[i:]) for i in range(0, len(items))]
def parts( s, sep ):
while True:
yield s
try:
# cut the string after the next sep
s = s[s.index(sep)+1:]
except ValueError:
# no `sep` left
break
print list(parts("a.b.c.d.e", '.'))
# ['a.b.c.d.e', 'b.c.d.e', 'c.d.e', 'd.e', 'e']
Not sure, if this is what you want.
But slicing of the list with varying sizes yields that.
>>> x = "a.b.c.d.e"
>>> k = x.split('.')
>>> k
['a', 'b', 'c', 'd', 'e']
>>> l = []
>>> for el in range(len(k)): l.append(k[el+1:])
...
>>> l
[['b', 'c', 'd', 'e'], ['c', 'd', 'e'], ['d', 'e'], ['e'], []]
>>> [".".join(l1) for l1 in l if l1]
['b.c.d.e', 'c.d.e', 'd.e', 'e']
>>>
Of course, the above was to illustrate the process. You could combine them into one liner.
[Edit: I thought the answer is same as any here and explains it well]

Categories