problem: create a recursive function that given an input key, would return the amount of basic components to build the given input key.
EX 1) input = "Engine"
output = Engine ==> metal: 3, rubber: 2
EX 2) input = "metal"
output = metal ==> metal: 1
EX 3) input = "piston"
output = piston ==> metal: 1, rubber: 1
car= {
"Engine" : ["pistons", "timing belt", "metal" ,"metal"],
"Pistons" : ["Metal", "rubber"],
"timing belt" : ["rubber"],
"metal" : [],
"rubber" : []
}
my code has different variable names and key name, but it's the same idea
parts = {
'A': ['B', 'B', 'C'],
'B': [],
'C': ['D','E','F'],
'D': [],
'E': ['B','D'],
'F': []
}
#above here its user input
counter_dictio = {
'A': [],
'B': [],
'C': [],
'D': [],
'E': [],
'F': []
}
def desamble(key, dictionary):
#check if array is empty
#ccounter +=1
if (len(dictionary[key])) == 0:
counter_dictio[key].append(key)
#if array is populated
#enter to this array
#desample(i, dictionary)
else:
for i in dictionary[key]:
desamble(i, dictionary)
key = "A"
desamble(key, parts)
One way to go is:
from collections import Counter
car= {
"engine": ["pistons", "timing belt", "metal", "metal"],
"pistons": ["metal", "rubber"],
"timing belt": ["rubber"],
"metal": [],
"rubber": []
}
def ingredients(key, dct):
if dct[key] == []:
yield key
else:
for sub_part in dct[key]:
yield from ingredients(sub_part, dct)
print(*ingredients('engine', car)) # metal rubber rubber metal metal
print(Counter(ingredients('engine', car))) # Counter({'metal': 3, 'rubber': 2})
ingredients makes a generator of ingredients, so you can use Counter to count them.
Here is code that will group by your components
from collections import defaultdict
parts = {
'A': ['B', 'B', 'C'],
'B': [],
'C': ['D', 'E', 'F'],
'D': [],
'E': ['B', 'D'],
'F': []
}
result = defaultdict(dict)
for k, v in parts.items():
row = result[k] # used to create empty dict on initial empty list
for item in v:
if row.get(item) is None:
row[item] = 1
else:
row[item] += 1
This will result in following dict
{'A': {'B': 2, 'C': 1}, 'B': {}, 'C': {'D': 1, 'E': 1, 'F': 1}, 'D': {}, 'E': {'B': 1, 'D': 1}, 'F': {}}
another solution without using recursively, and for a predetermined list would be:
#==== user input====
key = "E"
parts = {
'A': ['B', 'B', 'C'],
'B': [],
'C': ['D','E','F'],
'D': [],
'E': ['B','D'],
'F': []
}
#====end user input=====
#Function for the predetermined dictionary
def desamble(key, dictionary):
if key == "B" or key == "D" or key == "F":
print(key + "==> " + key + ": 1")
elif key == "E":
print(key + "==> " + "B: 1, D: 1" )
elif key == "C":
print(key + "==> " + "B: 1, D: 2, F: 1" )
elif key == "A":
print(key + "==> " + "B: 3, D: 2, F: 1" )
else:
print("Key " + key + " is not defined in dictionary")
#====end====
desamble(key, parts)
Another recursive way to solve this problem, adding a solution for the problem of circularity, meaning that in case that the parts call to eachother.
EX)
dictionary = {
"A": "B",
"B": "A"
}
from typing iport Counter
def func(key, dictionary, current=None):
if not current:
current = set() # could also be a list
if key in current:
raise ValueError # or whichever
if not dictionary.get(key):
return [key]
ret = []
for subkey in dictionary[key]:
ret.extend(func(subkey, dictionary, current.union({key})))
return ret
Print(Counter(func("A"), parts))
#by officerthegeeks
Related
all the contents must be inserted from the input
graph={
'A':{'B':3,'C':4},
'B':{'A':3,'C':5},
'C':{'B':5,'D':'1'},
'D':{'C':1},
}
Take the input in JSON format and then you can easily use json.loads to convert it to a dictionary.
>>> import json
>>> graph = json.loads(input("Enter graph as JSON: "))
Enter graph as JSON: {"A":{"B":3,"C":4},"B":{"A":3,"C":5},"C":{"B":5,"D":"1"},"D":{"C":1}}
>>> import pprint
>>> pprint.pprint(graph)
{'A': {'B': 3, 'C': 4},
'B': {'A': 3, 'C': 5},
'C': {'B': 5, 'D': '1'},
'D': {'C': 1}}
record_to_insert = int(input("num of record to insert :: "))
dic = {}
for i in range(record_to_insert):
print("")
key_name = input("parent key name :: ")
dic[key_name] = {}
print("Enter how many number of key,value pairs you want for key :: ",
key_name)
num_of_child_keyvalues_to_insert = int(input(""))
for k in range(num_of_child_keyvalues_to_insert):
key = input("child key name :: ")
value = input("Value name :: ")
dic[key_name][key] = value
print(dic)
def make_dict():
d = {}
while True:
key = input("Enter the key of dict or stay blank to finish adding key to this level of dict: ")
if key == "": # just stay blank to out from func
break
ch = input(f"Do you wanna to create nested dict for the key{key}? [y / <Press Enter for No>] ")
if ch == "y":
value = make_dict() # use recursion
else:
value = input(f"Enter the value for the key {key}: ")
d[key] = value
return d
print(make_dict())
link to screenshot
I am working on an API that returns JSON. I am logging my responses, and sometimes the JSON is just absurdly long and basically clogs my log files. Is there a neat way to reduce the length of a JSON, purely for visually logging the data? (not in effect in production)
The basic approach is to reduce arrays over a length of 5 to [first 2, "...", last 2], and dictionaries with more than 4 items to {first 4, "..." : "..."}
The code below is ugly. I am aware that it should be a recursive solution that reduces the items in the same way for a JSON of arbitrary depth - it currently only does so for depth 2.
def log_reducer(response_log):
original_response_log = response_log
try:
if type(response_log) == dict:
if len(response_log) >= 4: # {123456}
response_log = dict(list(response_log.items())[:4])
response_log.update({"...": "..."}) # {1234...}
for key, value in response_log.items():
if type(value) == list:
if len(value) >= 5: # {key:[123456]}
new_item = value[:2] + ['...'] + value[-2:] # {[12...56]}
response_log.update({key: new_item})
if type(value) == dict:
if len(value) >= 4: # {key:{123456}}
reduced_dict = dict(list(value.items())[:4])
reduced_dict.update({"...": "..."})
response_log.update({key: reduced_dict}) # {{1234...}}
elif type(response_log) == list:
if len(response_log) >= 5: # [123456]
response_log = response_log[:2] + ['...'] + response_log[-2:] # [12...56]
for inner_item in response_log:
if type(inner_item) == list:
if len(inner_item) >= 5: # [[123456]]
reduced_list = inner_item[:2] + ['...'] + inner_item[-2:] # [[12...56]]
response_log.remove(inner_item)
response_log.append(reduced_list)
if type(inner_item) == dict:
if len(inner_item) >= 4: # [{123456}]
reduced_dict = dict(list(inner_item.items())[:4])
reduced_dict.update({"...": "..."}) # [{1234...}]
response_log.remove(inner_item)
response_log.append(reduced_dict)
except Exception as e:
return original_response_log
return response_log
The returned response_log is then logged with logger.info(str(response_log))
As you can see, the fact that there can be either arrays or dictionaries at every level makes this task a little more complex, and I am struggling to find a library or code snipped of any kind which would simplify this. If anyone wants to give it a shot, I would appreciate it a lot.
you can use a test JSON like this to see it in effect:
test_json = {"works": [1, 2, 3, 4, 5, 6],
"not_affected": [{"1": "1", "2": "2", "3": "3", "4": "4", "5": "5"}],
"1": "1", "2": "2", "3": "3",
"removed": "removed"
}
print("original", test_json)
reduced_log = log_reducer(test_json)
print("reduced", reduced_log)
print("original", test_json)
reduced_log = log_reducer([test_json]) # <- increases nesting depth
print("reduced", reduced_log)
This answer uses #calceamenta's idea, but implements the actual cutting-down logic:
def recursive_reduce(obj):
if isinstance(obj, (float, str, int, bool, type(None))):
return obj
if isinstance(obj, dict):
keys = list(sorted(obj))
obj['...'] = '...'
if len(keys) > 5:
new_keys = keys[:2] + ["..."] + keys[-2:]
else:
new_keys = keys
new_dict = {x:obj[x] for x in new_keys}
for k, v in new_dict.items():
new_dict[k] = recursive_reduce(v)
return new_dict
if isinstance(obj, list):
if len(obj) > 5:
new_list = obj[:2] + ["..."] + obj[-2:]
else:
new_list = obj
for i, v in enumerate(new_list):
new_list[i] = recursive_reduce(v)
return new_list
return str(obj)
test_json = {"works": [1, 2, 3, 4, 5, 6],
"not_affected": [{"1": "1", "2": "2", "3": "3", "4": "4", "5": "5"}],
"1": "1", "2": "2", "3": "3",
"removed": "removed"
}
print("original", test_json)
reduced_log = recursive_reduce(test_json)
print("reduced", reduced_log)
Output:
original {'works': [1, 2, 3, 4, 5, 6], 'not_affected': [{'1': '1', '2': '2', '3': '3', '4': '4', '5': '5'}], '1': '1', '2': '2', '3': '3', 'removed': 'removed'}
reduced {'1': '1', '2': '2', '...': '...', 'removed': 'removed', 'works': [1, 2, '...', 5, 6]}
Hope this helps :)
You can overwrite the string representation of dicts and lists in python using the def __str__(): method. Using this just recursively call the print function on all elements. It can have a simple boilerplate like this:
def custom_print(obj):
log_str = ''
if type(obj) == list:
for item in obj:
log_str += custom_print(item)
elif type(obj) == dict:
for k, item in obj.items():
custom_print(item)
Use this custom log function to print into your log file as per your log file format.
I'm trying to make a function to find the highest score in a directed graph. I do have a start node and can't go throught the same node twice. I've tried to use recursive to get the sum of values until I hit one end node. Then I would call back my function to the start node and try other option ultil I hit another. And so on.
My problem is that when i return to a node with more than one path, the score value for this node is the sum of all paths it can take. And I only want the sum of one specific path.
Here is my code so far:
caminho = list()
def maxscore(start, parentals, score):
global caminho
parentals += start + '|'
if len(graph[start]) > 0:
for i in graph[start]:
if i not in parentals.split('|'):
value = graph[start][i]
if value:
score += value
func = maxscore(i, parentals, score)
else:
continue
if func[0] > score:
score = func[0]
caminho = parentals.split('|')
return score, caminho
else:
return score, start
graph = {
'a': {'b': 2, 'c': 4},
'b': {'d': 5},
'c': {'a': 1, 'e': 3},
'd': {'f': 4},
'e': {'b': 2, 'f': 3, 'g': 2},
'f': {},
'g': {}
}
print(maxscore('a', '', 0))
How could I make it work to return in the end only the best score with the path(caminho) it took.
Sorry if I couldn't make myself clear enough. Feel free to ask any questions.
You probably want to send the score variable by value, but you are sending it by reference, therefore all the possible paths' scores are added to it.
This is my approach:
def maxscore(start, parentals, score):
newParentals = parentals + start + '|'
print newParentals, score
scores = []
if graph[start]:
for nextNode in graph[start]:
if nextNode not in newParentals.split('|'):
scores.append(maxscore(nextNode, newParentals, score + graph[start][nextNode]))
return sorted(scores)[-1]
else:
return score
graph = {
'a': {'b': 2, 'c': 4},
'b': {'d': 5},
'c': {'a': 1, 'e': 3},
'd': {'f': 4},
'e': {'b': 2, 'f': 3, 'g': 2},
'f': {},
'g': {}
}
print(maxscore('a', '', 0))
And this is what gets printed:
a| 0
a|c| 4
a|c|e| 7
a|c|e|b| 9
a|c|e|b|d| 14
a|c|e|b|d|f| 18
a|c|e|g| 9
a|c|e|f| 10
a|b| 2
a|b|d| 7
a|b|d|f| 11
18
You can see how it checks all possible paths, and then picks the highest score :D
Here is an approach:
def maxscore(start, path, score):
best_score = -1
best_i = None
best_path = None
current_path = path + [start]
for i in graph[start]:
if not i in path:
score_i, path_i = maxscore(i, current_path, score + graph[start][i])
if score_i > best_score:
best_score = score_i
best_i = i
best_path = path_i
if best_i is None:
return score, current_path
else:
return best_score, best_path
graph = {
'a': {'b': 2, 'c': 4},
'b': {'d': 5},
'c': {'a': 1, 'e': 3},
'd': {'f': 4},
'e': {'b': 2, 'f': 3, 'g': 2},
'f': {},
'g': {}
}
print(maxscore('a', [], 0))
Output:
(18, ['a', 'c', 'e', 'b', 'd', 'f'])
Notes:
In the code of the question caminho and parentals served the same function. Just having a list is easier to work with than with a string.
In the for-loop, we test each of the edges. If the end-node of the edge is not yet in the path, we calculate the maximum score following that edge. We have to compare each of the possible edges and keep the one with the best score. We can only return from maxscore() after testing all possible edges.
If no free edge was found, we just return the current score and path
If free edges were found, we return the score and path of the best edge
PS: After revising the code, it can be shortened a bit. best_iis not really needed, and the test if best_i is None can be replaced by if best_path is None.
Also, if you need the path in the string form, you can print("|".join(best_path)).
I have a list of dictionaries and need to iterate through them and check for the keys that exist already. I have implemented a python code to manually calculate a score as below. In my code, I'm manually combining keys from previous dictionaries in each iteration. Iteration will start from dict11.
How can I change this code to automatically iterate through a dynamic number of dictionaries and in each iteration how can I combine the keys dynamically?
dict10 = {'A': 1, 'C': 2}
dict11 = {'B': 3, 'C': 4}
dict12 = {'A': 5, 'E': 6, 'F': 7}
dict13 = {'G': 8, 'E': 9}
exist_score = 0
for key in dict11.keys() & dict10.keys():
exist_score += dict11[key]
for key in dict12.keys() & set(dict11.keys()).union(set(dict10.keys())):
exist_score += dict12[key]
for key in dict13.keys() & set(dict12.keys()).union(set(dict11.keys()).union(set(dict10.keys()))):
exist_score += dict13[key]
print(exist_score)
First you need to turn this into something that can be put into a loop. The same thing has to happen with dict11, dict12 and dict13:
# same definition for dict10, dict11, dict12, dict13
exist_score = 0
seen_keys = set(dict10.keys())
for key in dict11.keys():
if key in seen_keys:
exist_score += dict11[key]
seen_keys.update(dict11.keys())
for key in dict12.keys():
if key in seen_keys:
exist_score += dict12[key]
seen_keys.update(dict12.keys())
for key in dict13.keys():
if key in seen_keys:
exist_score += dict13[key]
seen_keys.update(dict13.keys())
This should do the same thing as your script. Now you can put that into a loop …
# same definition for dict10, dict11, dict12, dict13
exist_score = 0
seen_keys = set(dict10.keys())
other_dicts = [dict11, dict12, dict13]
for d in other_dicts:
for key in d.keys():
if key in seen_keys:
exist_score += d[key]
seen_keys.update(d.keys())
It makes the most sense to keep dicts in a list themselves. Putting this logic into a function is also a no-brainer.
dicts = [
{'A': 1, 'C': 2},
{'B': 3, 'C': 4},
{'A': 5, 'E': 6, 'F': 7},
{'G': 8, 'E': 9}
]
def score_dicts(dicts):
score = 0
all_keys = set()
for d in dicts:
keys = d.keys()
for key in keys & all_keys:
score += d[key]
all_keys.update(keys)
return score
exist_score = score_dicts(dicts)
If you need to update the score periodically (one dict at a time), you can maintain the state in either a class or a closure.
Class:
class DictScorer():
def __init__(self):
self.exist_score = 0
self.all_keys = set()
def score(self, d):
keys = d.keys()
for key in keys & self.all_keys:
self.exist_score += d[key]
self.all_keys.update(keys)
return self.exist_score
dict10 = {'A': 1, 'C': 2}
dict11 = {'B': 3, 'C': 4}
dict12 = {'A': 5, 'E': 6, 'F': 7}
dict13 = {'G': 8, 'E': 9}
scorer = DictScorer()
exist_score = scorer.score(dict10)
print(exist_score)
exist_score = scorer.score(dict11)
print(exist_score)
exist_score = scorer.score(dict12)
print(exist_score)
exist_score = scorer.score(dict13)
print(exist_score)
Closure:
# returns a scorer which can
# be used incrementally
def create_dict_scorer():
score = 0
all_keys = set()
def dict_scorer(d):
nonlocal score
keys = d.keys()
for key in keys & all_keys:
score += d[key]
all_keys.update(keys)
return score
return dict_scorer
dict10 = {'A': 1, 'C': 2}
dict11 = {'B': 3, 'C': 4}
dict12 = {'A': 5, 'E': 6, 'F': 7}
dict13 = {'G': 8, 'E': 9}
scorer = create_dict_scorer()
exist_score = scorer(dict10)
print(exist_score)
exist_score = scorer(dict11)
print(exist_score)
exist_score = scorer(dict12)
print(exist_score)
exist_score = scorer(dict13)
print(exist_score)
Short magic with slicing and set operations:
dicts = [dict10, dict11, dict12, dict13]
exist_score = 0
for i, d in enumerate(dicts[:0:-1]):
offset = -(i - 2)
exist_score += sum(d[k] for k in d.keys() & set().union(*dicts[offset::-1]))
print(exist_score)
dicts[:0:-1] - slice of dictionaries in reversed order excluding the 1st one
-(i - 2) - negative offset to get consecutive "backward" slices for further set unions
The output (the same as in your initial approach):
18
I have the following attempt at a recursive function to create a shift table within a dictionary:
def createShiftTable(alphabet,T,shiftT):
if not T:
for char in alphabet:
shiftT[char] = len(T)+1
return shiftT
else:
return createShiftTable(alphabet,T[1:],shiftT)
shiftT[T[0]] = len(T)
al=['a','b','c','d']
T = "aaabbdddaaba"
print(createShiftTable(al,T,{}))
This is returning {'a': 1, 'c': 1, 'b': 1, 'd': 1}
I'd like it to return {'a': 1, 'c': 13, 'b': 2, 'd': 5}
Non-recursively the following works ok but how do I get the above recursive function working?
def createShiftTableX(alphabet,T):
shiftT={}
for char in al:
shiftT[char] = len(T)+1
for i in range(len(T)):
shiftT[T[i]] = len(T)-i
return shiftT
al=['a','b','c','d']
T = "aaabbdddaaba"
print(createShiftTableX(al,T))
How about this:
def createShiftTableX(alphabet, T):
l = len(T)
for c in alphabet:
i = T.find(c)
if i<0:
yield l
else:
yield i+1
al=['a','b','c','d']
T = "aaabbdddaaba"
print(list(createShiftTableX(al,T)))
You should run this in a debugger or put in some logging or print statements to see what it is actually doing.