How to Merge Two Dictionaries or Arrays in GDScript (Godot Engine)? - godot

Say I have two Dictionaries (or could be Arrays), each having sub-dictionaries (or sub-arrays):
var dict_A = {'a': 1, 'sub_dict': {'hello': 'world', 'quick': 'fox'}}
var dict_B = {'b': 2, 'sub_dict': {'hello': 'godot'}}
Is there a builtin method in GDScript to merge them, including the sub-dictionaries or sub-arrays?
Expected result would be:
merge_dict(dict_A, dict_B)
# {'a': 1, 'b': 2, 'sub_dict': {'hello': 'godot', 'quick': 'fox'}}

There's no builtin method in GDScript for that, but you can use the following functions (find usage examples, unit tests, and more on this Github gist):
Observation: when using deep_merge=true with merge_array, all sub-dictionaries and sub-arrays must be JSON serializable.
func merge_array(array_1: Array, array_2: Array, deep_merge: bool = false) -> Array:
var new_array = array_1.duplicate(true)
var compare_array = new_array
var item_exists
if deep_merge:
compare_array = []
for item in new_array:
if item is Dictionary or item is Array:
compare_array.append(JSON.print(item))
else:
compare_array.append(item)
for item in array_2:
item_exists = item
if item is Dictionary or item is Array:
item = item.duplicate(true)
if deep_merge:
item_exists = JSON.print(item)
if not item_exists in compare_array:
new_array.append(item)
return new_array
func merge_dict(dict_1: Dictionary, dict_2: Dictionary, deep_merge: bool = false) -> Dictionary:
var new_dict = dict_1.duplicate(true)
for key in dict_2:
if key in new_dict:
if deep_merge and dict_1[key] is Dictionary and dict_2[key] is Dictionary:
new_dict[key] = merge_dict(dict_1[key], dict_2[key])
elif deep_merge and dict_1[key] is Array and dict_2[key] is Array:
new_dict[key] = merge_array(dict_1[key], dict_2[key])
else:
new_dict[key] = dict_2[key]
else:
new_dict[key] = dict_2[key]
return new_dict
This code is licensed under BSD 3-Clause License | Copyright 2022 Hackverse.org

Related

How do I use python to turn two lists into a dictionary, and the value is the maximum value

I have two lists here
L = ["a","b","b","c","c","c"]
L_02 = [1,3,2,4,6,""]
I want to turn two lists into a dictionary, and the value is the maximum value
dic = {"a":1,"b":3,"c":6}
how can I do this?
We can first get the indices of each element in the list, get the corresponding values in the second list, and find the maximums and make a dictionary.
dic = {}
for element in set(L):
indices = [i for i, x in enumerate(L) if x == element]
corresponding = []
for i in indices:
if type(L_02[i]) == int:
corresponding.append(L_02[i])
value = max(corresponding)
dic[element] = value
L = ["a","b","b","c","c","c"]
L_02 = [1,3,2,4,6,""]
return_dict = {}
for item1, item2 in list(zip(L,L_02)):
if item1 not in return_dict:
return_dict[item1] = item2
elif isinstance(item2,int) and return_dict[item1] < item2:
return_dict[item1] = item2
print(return_dict)
Output:
{'a': 1, 'b': 3, 'c': 6}

The problem of using {}.fromkey(['k1','k2'],[]) and {'k1':[],'k2':[]}

list1 = [99,55]
dict1 = {'k1':[],'k2':[]}
for num in list1:
if num > 77:
dict1['k1'].append(num)
else:
dict1['k2'].append(num)
print(dict1)
{'k1':[99],'k2':[55]}
But when I replaced dict1 = {'k1':[],'k2':[]} to {}.fromkeys(['k1','k2'],[]) , the result became {'k1': [99, 55], 'k2': [99, 55]}
why this happens? I really have no idea.
This happens because you are passing the same list object to both keys. This is the same situation as when you create an alias for a variable:
a = []
b = a
a.append(55)
b.append(99)
print(b)
prints [55, 99] because it is the same list instance.
If you want to make it more concise from a list of keys to initialize with empty list, you can do this:
dict1 = {k: [] for k in ('k1', 'k2')}
This will create a new list instance for every key.
Alternatively, you can use defaultdict
from collections import defaultdict
list1 = [99,55]
dict1 = defaultdict(list)
for num in list1:
if num > 77:
dict1['k1'].append(num)
else:
dict1['k2'].append(num)
print(dict1)
Also works.
The fromKeys() can also be supplied with a mutable object as the default value.
if we append value in the original list, the append takes place in all the values of keys.
example:
list1 = ['a', 'b', 'c', 'd']
list2 = ['SALIO']
dict1 = dict.fromkeys(list1, list2)
print(dict1)
output:
{'a': ['SALIO'], 'b': ['SALIO'], 'c': ['SALIO'], 'd': ['SALIO']}
then you can use this:
list1 = ['k1', 'k2']
dict1 = {'k1':[],'k2':[]}
list2 =[99,55]
for num in list2:
if num > 77:
a = ['k1']
dict1 = dict.fromkeys(a, [num])
else:
b = ['k2']
dict2 = dict.fromkeys(b,[num] )
res = {**dict1, **dict2}
print(res)
output:
{'k1': [99], 'k2': [55]}
You can also use the python code to merge dict code:
this function:
def Merge(dict1, dict2):
return(dict2.update(dict1))
then:
print(Merge(dict1, dict2)) #This return None
print(dict2) # changes made in dict2

Adding keys and value pair to a dictionary within a dictionary with duplicate keys

results is a list within a list with the data as shown in the results section. I am hoping to achieve a dictionary within a dictionary as shown in the results portion.
input:
results = [['abc','12'3,'1123','qwe', 'asd'],['abc','123,'1123','qwe', '123'],['abc','123','1123','ewq','zxc'], ['bcd','123','1123','ewq','zxc'], ['bcd','123','1123','ewq','zxc]]
Code:
report_dict = dict()
axis_list = []
results = self.report_data(conn)
for row in results:
try:
report_dict[row[0]] = {}
report_dict[row[0]][row[3]] = row[1]
except IndexError:
None
print(report_dict)
Result:
report_dict = { 'abc': {'qwe':['asd','123'], 'ewq':['zxc']}, 'bcd' : {'qwe':['asd'], 'ewq':['zxc']} …..}
Please note there are duplicate keys in the dataset.
you could do:
d = {}
for i in results:
if not d.get(i[0],0):
d[i[0]] = {}
if not d[i[0]].get(i[3],0):
d[i[0]][i[3]] = []
d[i[0]][i[3]].append(i[4])
d
{'abc': {'qwe': ['asd', '123'], 'ewq': ['zxc']}, 'bcd': {'ewq': ['zxc', 'zxc']}}
The following is a solution to your immediate question with tuples in the list:
from collections import defaultdict
report_dict = defaultdict(list)
# results = self.report_data(conn)
results = [["abc",123,1123,"qwe", "asd"],["abc",123,1123,"ewq","zxc"], ["bcd",123,1123,"ewq","zxc"], ["bcd",123,1123,"ewq","zxc"]]
for row in results:
try:
report_dict[row[0]].append((row[3], row[1]))
except IndexError:
None
print(report_dict)
Result: defaultdict(<class 'list'>, {'abc': [('qwe', 123), ('ewq', 123)], 'bcd': [('ewq', 123), ('ewq', 123)]})
you can also change it to a dictionaries in the list with the following line
report_dict[row[0]].append({row[3]: row[1]})
Result: defaultdict(<class 'list'>, {'abc': [{'qwe': 123}, {'ewq': 123}], 'bcd': [{'ewq': 123}, {'ewq': 123}]})

return dictionary of file names as keys and word lists with words unique to file as values

I am trying to write a function to extract only words unique to each key and list them in a dictionary output like {"key1": "unique words", "key2": "unique words", ... }. I start out with a dictionary. To test with I created a simple dictionary:
d = {1:["one", "two", "three"], 2:["two", "four",
"five"], 3:["one","four", "six"]}
My output should be:
{1:"three",
2:"five",
3:"six"}
I am thinking maybe split in to separate lists
def return_unique(dct):
Klist = list(dct.keys())
Vlist = list(dct.values())
aList = []
for i in range(len(Vlist)):
for j in Vlist[i]:
if
What I'm stuck on is how do I tell Python to do this: if Vlist[i][j] is not in the rest of Vlist then aList.append(Vlist[i][j]).
Thank you.
You can try something like this:
def return_unique(data):
all_values = []
for i in data.values(): # Get all values
all_values = all_values + i
unique_values = set([x for x in all_values if all_values.count(x) == 1]) # Values which are not duplicated
for key, value in data.items(): # For Python 3.x ( For Python 2.x -> data.iteritems())
for item in value: # Comparing values of two lists
for item1 in unique_values:
if item == item1:
data[key] = item
return data
d = {1:["one", "two", "three"], 2:["two", "four", "five"], 3:["one","four", "six"]}
print (return_unique(d))
result >> {1: 'three', 2: 'five', 3: 'six'}
Since a key may have more than one unique word associated with it, it makes sense for the values in the new dictionary to be a container type object to hold the unique words.
The set difference operator returns the difference between 2 sets:
>>> a = set([1, 2, 3])
>>> b = set([2, 4, 6])
>>> a - b
{1, 3}
We can use this to get the values unique to each key. Packaging these into a simple function yields:
def unique_words_dict(data):
res = {}
values = []
for k in data:
for g in data:
if g != k:
values += data[g]
res[k] = set(data[k]) - set(values)
values = []
return res
>>> d = {1:["one", "two", "three"],
2:["two", "four", "five"],
3:["one","four", "six"]}
>>> unique_words_dict(d)
{1: {'three'}, 2: {'five'}, 3: {'six'}}
If you only had to do this once, then you might be interested in the less efficeint but more consice dictionary comprehension:
>>> from functools import reduce
>>> {k: set(d[k]) - set(reduce(lambda a, b: a+b, [d[g] for g in d if g!=k], [])) for k in d}
{1: {'three'}, 2: {'five'}, 3: {'six'}}

Counter class extension

I am having a problem finding an elegant way to create a Counter() class that can:
Feed in arbitrary number of keys and return a nested dictionary based on this list of keys.
Increment for this nested dictionary is arbitrary as well.
For example:
counter = Counter()
for line in fin:
if a:
counter.incr(key1, 1)
else:
counter.incr(key2, key3, 2)
print counter
Ideally I am hoping to get the result looks like: {key1 : 20, {key2 : {key3 : 40}}}. But I am stuck in creating this arbitrary nested dictionary from list of keys. Any help is appreciated.
you can subclass dict and create your own nested structure.
here's my attempt at writing such class :
class Counter(dict):
def incr(self, *args):
if len(args) < 2:
raise TypeError, "incr() takes at least 2 arguments (%d given)" %len(args)
curr = self
keys, count = args[:-1], args[-1]
for depth, key in enumerate(keys, 1):
if depth == len(keys):
curr[key] = curr.setdefault(key, 0) + count
else:
curr = curr.setdefault(key, {})
counter = Counter()
counter.incr('key1', 1)
counter.incr('key2', 'key3', 2)
counter.incr('key1', 7)
print counter #{'key2': {'key3': 2}, 'key1': 8}
There are two possibilities.
First, you can always fake the nested-keys thing by using a flat Counter with a "key path" made of tuples:
counter = Counter()
for line in fin:
if a:
counter.incr((key1,), 1)
else:
counter.incr((key2, key3), 2)
But then you'll need to write a str-replacement—or, better, a wrapper class that implements __str__. And while you're at it, you can easily write an incr wrapper that lets you use exactly the API you wanted:
def incr(self, *args):
super().incr(args[:-1], args[-1])
Alternatively, you can build your own Counter-like class on top of a nested dict. The code for Counter is written in pure Python, and the source is pretty simple and readable.
From, your code, it looks like you don't have any need to access things like counter[key2][key3] anywhere, which means the first is probably going to be simpler and more appropriate.
The only type of value that can exist in a Counter object is an int, you will not be able to represent a nested dictionary with a Counter.
Here is one way to do this with a normal dictionary (counter = {}). First, to update increment the value for a single key:
counter[key1] = counter.setdefault(key1, 0) + 1
Or for an arbitrary list of keys to create the nested structure:
tmp = counter
for key in key_list[:-1]:
tmp = tmp.setdefault(key, {})
tmp[key_list[-1]] = tmp.setdefault(key_list[-1], 0) + 1
I would probably turn this into the following function:
def incr(counter, val, *keys):
tmp = counter
for key in keys[:-1]:
tmp = tmp.setdefault(key, {})
tmp[keys[-1]] = tmp.setdefault(keys[-1], 0) + val
Example:
>>> counter = {}
>>> incr(counter, 1, 'a')
>>> counter
{'a': 1}
>>> incr(counter, 2, 'a')
>>> counter
{'a': 3}
>>> incr(counter, 2, 'b', 'c', 'd')
>>> counter
{'a': 3, 'b': {'c': {'d': 2}}}
>>> incr(counter, 3, 'b', 'c', 'd')
>>> counter
{'a': 3, 'b': {'c': {'d': 5}}}

Resources