I have a list of dicts. Each dict can be nested. I want to remove the key id from each one of the dics, recursively. Fopr example (Note that I don't know if the amount of levels):
"files" : [
{
'id': 'ada21321',
'd': 'asdasdas',
'data': {
'd': 'asdasdas'
}
},
{
'id': 'ada23112341321',
'd': 'asdasdas',
'data': {
'd': 'asdasdas',
'id': 'asdasd21asda'
}
}
],
I don't know how nested the dics are, and where id is located. I need to remove id from all of the dics from all levels. Output:
"files" : [
{
'd': 'asdasdas',
'data': {
'd': 'asdasdas'
}
},
{
'd': 'asdasdas',
'data': {
'd': 'asdasdas'
}
}
],
I know how to remove in one level:
for current_file in data["files"]:
current_file.pop('id', None)
Is there an elegant way to achieve it?
This should do it for you:
def remove_key(container, key):
if type(container) is dict:
if key in container:
del container[key]
for v in container.values():
remove_key(v, key)
if type(container) is list:
for v in container:
remove_key(v, key)
remove_key(data['files'], 'id')
Output:
{'files': [{'d': 'asdasdas', 'data': {'d': 'asdasdas'}}, {'d': 'asdasdas', 'data': {'d': 'asdasdas'}}]}
You can use recursion:
data = {'files': [{'id': 'ada21321', 'd': 'asdasdas', 'data': {'d': 'asdasdas'}}, {'id': 'ada23112341321', 'd': 'asdasdas', 'data': {'d': 'asdasdas', 'id': 'asdasd21asda'}}]}
def d_rem(d):
if not isinstance(d, dict):
return d if not isinstance(d, list) else list(map(d_rem, d))
return {a:d_rem(b) for a, b in d.items() if a != 'id'}
new_d = d_rem(data)
Output:
{'files': [{'d': 'asdasdas', 'data': {'d': 'asdasdas'}}, {'d': 'asdasdas', 'data': {'d': 'asdasdas'}}]}
This should do the trick (note that this will remove any id keys regardless of whether the associated value to that id key is a str or dict):
def remove_id(file):
for k in list(file.keys()):
if isinstance(file[k], dict):
remove_id(file[k])
if k=='id':
del file[k]
for file in files:
remove_id(file)
Yields:
[{'d': 'asdasdas', 'data': {'d': 'asdasdas'}}, {'d': 'asdasdas', 'data': {'d': 'asdasdas'}}]
Related
I have a dict like:
source_dict = {
"/a": {"foo": "bar", "randomstuff": 3},
"/b/a": {"some": "thing", "abc": {"bx": 1}},
"/b/g/h/g": {"any": "value"}
}
The paths could be infinite deep and they never end with /(> not empty 'folders' stored)
For hours I tried to get source_dict to a nested dict like:
final_dict = {"/": {
"a": {"foo": "bar", "randomstuff": 3},
"b": {"a": {"some": "thing", "abc": {"bx": 1}}, "g": {"h": {"g": {"any": "value"}}}}
}}
The value not changes.
Any ideas?
You can use itertools.groupby with recursion:
from itertools import groupby as gb
def group(d):
new_d = [(a, list(b)) for a, b in gb(sorted(d, key=lambda x:x[0][0]), key=lambda x:x[0][0])]
return {a:b[-1][-1] if not b[0][0][1:] else group([(c, k) for [_, *c], k in b]) for a, b in new_d}
source_dict = {"/a": {"foo": "bar", "randomstuff": 3}, "/b/a": {"some":"thing", "else":{"bx": 1}}, "/b/g/h/g": {"any": "value"}}
r = {'/':group([(list(filter(None, a.split('/'))), b) for a, b in source_dict.items()])}
Output:
{'/': {'a': {'foo': 'bar', 'randomstuff': 3}, 'b': {'a': {'some': 'thing', 'else': {'bx': 1}}, 'g': {'h': {'g': {'any': 'value'}}}}}}
I'm trying to manipulate nested dictionaries to combine backwards the keys of any nested dictionaries with only a single key.
I've tried to do this recursively, but I'm having a hard time being able to remove keys from the dictionary and replace them with the concatenated keys.
For example:
{'adv':
{'e':
{'r':
{
's':
{'e':
{'_end_': '_end_'}
},
't':
{'_end_': '_end_',
'i':
{'s':
{'e':
{'r':
{'_end_': '_end_'}
}
}
}
}
}
},
'i': {'c': {'e': {'_end_': '_end_'}
}
}
}
}
would become
{'adv':
{'er':
{
'se':
{'_end_': '_end_'},
't':
{'_end_': '_end_',
'iser':
{'_end_': '_end_'}
}
},
'ice':
{'_end_': '_end_'}
}
}
This was an interesting problem - there is probably a more elegant solution, but I did the following:
import pprint
t={'adv': {'e': {'r': {'s': {'e': {'_end_': '_end_'}},
't': {'_end_': '_end_',
'i': {'s': {'e': {'r': {'_end_': '_end_'}}}}}}},
'i': {'c': {'e': {'_end_': '_end_'}}}}}
def concat_dict(d):
if d == '_end_':
return '_end_'
rv = {}
for k, v in d.items():
if '_end_' in v:
rv[k] = concat_dict(v)
elif len(list(x for x in v.keys() if x != '_end_')) == 1:
top_str = k
next_str = list(v.keys())[0]
rv[top_str + next_str] = concat_dict(v[next_str])
else:
rv[k] = concat_dict(v)
return rv
def format_dict(d):
while concat_dict(d) != d:
d = concat_dict(d)
return d
pprint.pprint(format_dict(t))
Output:
{'adv': {'er': {'se': {'_end_': '_end_'},
't': {'_end_': '_end_', 'iser': {'_end_': '_end_'}}},
'ice': {'_end_': '_end_'}}}
I'm using cerberus to validate data. One of my fields is optional - it doesn't need to be present for every item. However, the key must be populated at least once across the entire data array.
As an example, say I want to validate the key 'c' occurs in at least one dictionary in my data list:
from cerberus import Validator
has_c = {'data': [{'a': 1, 'b': 2}, {'b': 2}, {'c': 3}]}
no_c = {'data': [{'a': 1, 'b': 2}, {'a': 1}]}
schema = {'data':
{'type': 'list',
'schema': {
'type': 'dict',
'schema': {
'a': {'required': True},
'b': {'required': True},
'c': {'required': False, 'at_least_one': True}
}
}
}
}
class MyValidator(Validator) # Some fancy code...
....
v = MyValidator()
v.validate(has_c, schema) # Passes
v.validate(no_c, schema) # Fails
This seems doable outside of cerberus, but I'd like to keep the method in my validator if possible.
If you want the method to be in the Validator subclass, then you will want to create a custom rule just like you were thinking.
from cerberus import Validator
test_with_c = {'data': [{'a': 1, 'b': 2}, {'b': 2}, {'c': 3}]}
test_with_no_c = {'data': [{'a': 1, 'b': 2}, {'a': 1}]}
class MyValidator(Validator):
def _validate_has_c(self, has_c, field, value):
seen_c = False
for v in value:
if "c" in v:
seen_c = True
if has_c and not seen_c:
self._error(field, "Must contain a 'c' key")
schema = {
"data": {
"type": "list",
"has_c": True
}
}
v = MyValidator(schema)
print(v(test_with_c), v.errors)
print(v(test_with_no_c), v.errors)
Running this will yield the results you want with respect to looking for a c key in one of the elements. Running that code yields
True {}
False {'data': ["Must contain a 'c' key"]}
I'm trying to write a simple function that given a letter, you are returned the value of the scrabble tile. Here is what I have:
def letterPoint(letter):
letter = letter.upper()
lettersWorthOne =(['A','E','I','N','O','R','S','T'])
lettersWorthTwo = (['D','G'])
lettersWorthThree = (['B','C','M','P'])
lettersWorthFour = (['F','H','U','V','W','Y'])
lettersWorthFive = (['K'])
lettersWorthEight = (['J','X'])
lettersWorthTen = (['Q','Z'])
if letterWorthOne:
print '1'
if letterWorthTwo:
print '2'
if letterWorthThree:
print '3'
if letterWorthFour:
print '4'
if letterWorthFive:
print '5'
if letterWorthEight:
print '8'
if letterWorthTen:
print '10'
Use a dictionary. Rather than
lettersWorthTwo = (['D','G']), etc.
You would have a data structure along the lines of:
letterValues = {'D':2, 'G':2, ... }
Then a lookup for value is simply:
letterValues['D'] # returns 2 for the value of the tile
To point out why your code doesn't work, because you're not comparing your letter to the list.
#Change from this:
if letterWorthOne:
print '1'
#to this, should work
if letter in letterWorthOne:
print '1'
.....
Use a python dictionary is the way to go.
Further to the solution someone has already posted. You can also construct a more content dictionary like this:
Letters = {
'a': { 'quantity' : 9, 'value': 1},
'b': { 'quantity' : 2, 'value': 3},
'c': { 'quantity' : 2, 'value': 3},
'd': { 'quantity' : 4, 'value': 2},
'e': { 'quantity' : 12, 'value': 1},
'f': { 'quantity' : 2, 'value': 4},
'g': { 'quantity' : 3, 'value': 2},
'h': { 'quantity' : 2, 'value': 4},
'i': { 'quantity' : 9, 'value': 1},
'j': { 'quantity' : 1, 'value': 8},
'k': { 'quantity' : 1, 'value': 5},
'l': { 'quantity' : 4, 'value': 1},
'm': { 'quantity' : 2, 'value': 3},
'n': { 'quantity' : 6, 'value': 1},
'o': { 'quantity' : 8, 'value': 1},
'p': { 'quantity' : 2, 'value': 3},
'q': { 'quantity' : 1, 'value': 10},
'r': { 'quantity' : 6, 'value': 1},
's': { 'quantity' : 4, 'value': 1},
't': { 'quantity' : 6, 'value': 1},
'u': { 'quantity' : 4, 'value': 1},
'v': { 'quantity' : 2, 'value': 4},
'w': { 'quantity' : 2, 'value': 4},
'x': { 'quantity' : 1, 'value': 8},
'y': { 'quantity' : 2, 'value': 4},
'z': { 'quantity' : 1, 'value': 10},
'*': { 'quantity' : 2, 'value': 0}
}
# to get to it's "content", like this:
Letters['a']
{'quantity': 9, 'value': 1}
# you can then get its 'value' or 'quantity' in a tile bag
Letters['a']['value']
1
# if you MUST use a function, do this with above dictionary, although it's quite pointless
def letter_point(letter):
return Letters[letter.upper()]['value']
In letterPoint(), letterWorthOne and lettersWorthOne are separate variables. Each lettersWorth* variable holds a list, and you appear to want letterWorthOne to contain a boolean value (True or False) specifying whether or not letter is in the lettersWorthOne list. To determine whether a value is in a collection, use operator in.
def letterPoint(letter):
letter = letter.upper()
lettersWorthOne =(['A','E','I','N','O','R','S','T'])
lettersWorthTwo = (['D','G'])
letterWorthOne = letter in lettersWorthOne
if letterWorthOne:
print '1'
letterWorthTwo = letter in lettersWorthTwo
if letterWorthTwo:
print '2'
# rest of values omitted for brevity
print 'E worth'
letterPoint('E')
print 'D worth'
letterPoint('D')
This program produces the following output:
E worth
1
D worth
2
This explains why your existing function doesn't work. But in the long run, I'd recommend using a dictionary to hold the value and quantity of each letter, and store the values as numbers instead of strings so that you can add the values of all letters in a word.
To use the code you began with, you could consider changing the lines to:
if letter in lettersWorthOne print 1
...
This is because the data structure you're using is a list (it is surrounded by [] brackets). The way to use these lists in your function is to see if they contain the letter in the list using the code:
if <variable> in <list> print <value>
The () brackets aren't doing anything, as mentioned in another answer so you can get rid of them.
This is just to explain to you why you don't see any result in the function you've written. The other answers which suggest using a dict (dictionary) are a better approach in practice.
Given a nested dictionary:
nested = {
'A': {
'B': {
'C': 'C val',
'G': 'G val'
},
'D': {
'E': {
'F': 'F val'
}
}
}
}
I want to recursively concatenate the keys of the dictionary, except for the "final" key-value pairs, and put the concatenated keys in a new dictionary, like so:
expected = {
'A:B': {'C': 'C val', 'G': 'G val'},
'A:D:E': {'F': 'F val'}
}
How can I make such a function, without knowing the structure of the nested dict beforehand?
A recursive solution is the simplest. This code does as you ask.
def flatten(dictionary, prefix=[], result={}):
for k, v in dictionary.iteritems():
type_v = type(v)
if type_v == dict:
flatten(v, prefix+[k], result)
elif type_v == str:
prefix_str = ':'.join(prefix)
if not prefix_str in result:
result[prefix_str] = {}
result[prefix_str][k] = v
else:
raise TypeError('%s not permissible in data structure' % type_v)
return result
nested = {
'A': {
'B': {
'C': 'C val',
'G': 'G val',
},
'D': {
'E': {
'F': 'F val',
}
}
}
}
expected = flatten(nested)
print(expected)
output
{'A:B': {'C': 'C val', 'G': 'G val'}, 'A:D:E': {'F': 'F val'}}