I'm trying to do an academic project (I cannot use any of the libraries from python) to construct a graph data structure from teh data in a csv file.
First I'm reading the csv file and put the information into a dictionary, using:
def github_csv():
with open('Github1.csv', 'r') as csv_file:
data = csv.DictReader(csv_file)
next(data)
for row in data:
print(row)
The output is:
{'follower': '9236', 'followed': '1570'}
{'follower': '13256', 'followed': '9236'}
{'follower': '9236', 'followed': '13256'}
My first doubt is there any way to put it like this:
'9236': ['1570', '13256']
'13256': ['9236']
Then how can I assign the key value to a vertex and corresponding values for another vertex and then create the edges?
My graph class is:
class Graph:
def __init__(self, directed=False):
self._directed = directed
self._number = 0
self._vertices = {}
def insert_vertex(self, x):
v = Vertex(x)
self._vertices[v] = {}
self._number = len(self._vertices)
return v
def insert_edge(self, u, v, x=None):
e = Edge(u, v, x)
self._vertices[u][v] = e
self._vertices[v][u] = e
EDIT:
class Vertex:
__slots__ = "_element"
def __init__(self, x):
self._element = x
def vertice(self):
return self._element
def __eq__(self, other):
if isinstance(other, Vertex):
return self._element == other.vertice()
return False
def __repr__(self):
return '{0}'.format(self._element)
def __hash__(self):
return hash(id(self))
class Edge:
__slots__ = '_origin', '_destination', '_weight'
def __init__(self, u, v, p=None):
self._origin = u
self._destination = v
self._weight = p
def __hash__(self):
return hash((self._origin, self._destination))
def __repr__(self):
if self._weight is None:
return '({0}, {1})'.format(self._origin, self._destination)
return '({0}, {1}, {2})'.format(self._origin, self._destination, self._weight)
def endpoints(self):
return self._origin, self._destination
def opposite(self, v):
return self._origin if v is self._destination else self._origin
def cost(self):
return self._weight
def show_edge(self):
print('(', self._origin, ', ', self._destination, ') com peso', self._weight)
Regarding the first question:
lista = [{'follower': '9236', 'followed': '1570'},
{'follower': '13256', 'followed': '9236'},
{'follower': '9236', 'followed': '13256'}]
rel_dict = {}
for d in lista:
if d["follower"] in rel_dict.keys():
rel_dict[d["follower"]].append(d["followed"])
else:
rel_dict[d["follower"]] = [d["followed"]]
rel_dict:
{'9236': ['1570', '13256'], '13256': ['9236']}
EDIT2 (with Vertex and Edge definition):
To add these data to the graph:
graph = Graph()
for k,v in rel_dict.items():
k_vertex = graph.insert_vertex(k)
for v_item in v:
v_item_vertex = graph.insert_vertex(v_item)
graph.insert_edge(k_vertex, v_item_vertex)
Assuming you get row as the dictionaries you've mentioned,
edges_dict = {}
def github_csv():
with open('Github1.csv', 'r') as csv_file:
data = csv.DictReader(csv_file)
next(data)
for row in data:
edges_dict[ row['follower'] ].append(row['followed'])
This should give you an edges_dict dictionary of lists as required.
Is there a way to rename a dictionary key, without reassigning its value to a new name and removing the old name key; and without iterating through dict key/value?
In case of OrderedDict do the same, while keeping that key's position.
For a regular dict, you can use:
mydict[k_new] = mydict.pop(k_old)
This will move the item to the end of the dict, unless k_new was already existing in which case it will overwrite the value in-place.
For a Python 3.7+ dict where you additionally want to preserve the ordering, the simplest is to rebuild an entirely new instance. For example, renaming key 2 to 'two':
>>> d = {0:0, 1:1, 2:2, 3:3}
>>> {"two" if k == 2 else k:v for k,v in d.items()}
{0: 0, 1: 1, 'two': 2, 3: 3}
The same is true for an OrderedDict, where you can't use dict comprehension syntax, but you can use a generator expression:
OrderedDict((k_new if k == k_old else k, v) for k, v in od.items())
Modifying the key itself, as the question asks for, is impractical because keys are hashable which usually implies they're immutable and can't be modified.
Using a check for newkey!=oldkey, this way you can do:
if newkey!=oldkey:
dictionary[newkey] = dictionary[oldkey]
del dictionary[oldkey]
In case of renaming all dictionary keys:
target_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
new_keys = ['k4','k5','k6']
for key,n_key in zip(target_dict.keys(), new_keys):
target_dict[n_key] = target_dict.pop(key)
You can use this OrderedDict recipe written by Raymond Hettinger and modify it to add a rename method, but this is going to be a O(N) in complexity:
def rename(self,key,new_key):
ind = self._keys.index(key) #get the index of old key, O(N) operation
self._keys[ind] = new_key #replace old key with new key in self._keys
self[new_key] = self[key] #add the new key, this is added at the end of self._keys
self._keys.pop(-1) #pop the last item in self._keys
Example:
dic = OrderedDict((("a",1),("b",2),("c",3)))
print dic
dic.rename("a","foo")
dic.rename("b","bar")
dic["d"] = 5
dic.rename("d","spam")
for k,v in dic.items():
print k,v
output:
OrderedDict({'a': 1, 'b': 2, 'c': 3})
foo 1
bar 2
c 3
spam 5
A few people before me mentioned the .pop trick to delete and create a key in a one-liner.
I personally find the more explicit implementation more readable:
d = {'a': 1, 'b': 2}
v = d['b']
del d['b']
d['c'] = v
The code above returns {'a': 1, 'c': 2}
Suppose you want to rename key k3 to k4:
temp_dict = {'k1':'v1', 'k2':'v2', 'k3':'v3'}
temp_dict['k4']= temp_dict.pop('k3')
Other answers are pretty good.But in python3.6, regular dict also has order. So it's hard to keep key's position in normal case.
def rename(old_dict,old_name,new_name):
new_dict = {}
for key,value in zip(old_dict.keys(),old_dict.values()):
new_key = key if key != old_name else new_name
new_dict[new_key] = old_dict[key]
return new_dict
In Python 3.6 (onwards?) I would go for the following one-liner
test = {'a': 1, 'old': 2, 'c': 3}
old_k = 'old'
new_k = 'new'
new_v = 4 # optional
print(dict((new_k, new_v) if k == old_k else (k, v) for k, v in test.items()))
which produces
{'a': 1, 'new': 4, 'c': 3}
May be worth noting that without the print statement the ipython console/jupyter notebook present the dictionary in an order of their choosing...
I am using #wim 's answer above, with dict.pop() when renaming keys, but I found a gotcha. Cycling through the dict to change the keys, without separating the list of old keys completely from the dict instance, resulted in cycling new, changed keys into the loop, and missing some existing keys.
To start with, I did it this way:
for current_key in my_dict:
new_key = current_key.replace(':','_')
fixed_metadata[new_key] = fixed_metadata.pop(current_key)
I found that cycling through the dict in this way, the dictionary kept finding keys even when it shouldn't, i.e., the new keys, the ones I had changed! I needed to separate the instances completely from each other to (a) avoid finding my own changed keys in the for loop, and (b) find some keys that were not being found within the loop for some reason.
I am doing this now:
current_keys = list(my_dict.keys())
for current_key in current_keys:
and so on...
Converting the my_dict.keys() to a list was necessary to get free of the reference to the changing dict. Just using my_dict.keys() kept me tied to the original instance, with the strange side effects.
In case someone wants to rename all the keys at once providing a list with the new names:
def rename_keys(dict_, new_keys):
"""
new_keys: type List(), must match length of dict_
"""
# dict_ = {oldK: value}
# d1={oldK:newK,} maps old keys to the new ones:
d1 = dict( zip( list(dict_.keys()), new_keys) )
# d1{oldK} == new_key
return {d1[oldK]: value for oldK, value in dict_.items()}
I came up with this function which does not mutate the original dictionary. This function also supports list of dictionaries too.
import functools
from typing import Union, Dict, List
def rename_dict_keys(
data: Union[Dict, List[Dict]], old_key: str, new_key: str
):
"""
This function renames dictionary keys
:param data:
:param old_key:
:param new_key:
:return: Union[Dict, List[Dict]]
"""
if isinstance(data, dict):
res = {k: v for k, v in data.items() if k != old_key}
try:
res[new_key] = data[old_key]
except KeyError:
raise KeyError(
"cannot rename key as old key '%s' is not present in data"
% old_key
)
return res
elif isinstance(data, list):
return list(
map(
functools.partial(
rename_dict_keys, old_key=old_key, new_key=new_key
),
data,
)
)
raise ValueError("expected type List[Dict] or Dict got '%s' for data" % type(data))
#helloswift123 I like your function. Here is a modification to rename multiple keys in a single call:
def rename(d, keymap):
"""
:param d: old dict
:type d: dict
:param keymap: [{:keys from-keys :values to-keys} keymap]
:returns: new dict
:rtype: dict
"""
new_dict = {}
for key, value in zip(d.keys(), d.values()):
new_key = keymap.get(key, key)
new_dict[new_key] = d[key]
return new_dict
You can use below code:
OldDict={'a':'v1', 'b':'v2', 'c':'v3'}
OldKey=['a','b','c']
NewKey=['A','B','C']
def DictKeyChanger(dict,OldKey,NewKey):
ListAllKey=list(dict.keys())
for x in range(0,len(NewKey)):
dict[NewKey[x]]=dict[OldKey[x]] if OldKey[x] in ListAllKey else None
for x in ListAllKey:
dict.pop(x)
return dict
NewDict=DictKeyChanger(OldDict,OldKey,NewKey)
print(NewDict)#===>>{'A': 'v1', 'B': 'v2', 'C': 'v3'}
Notes:
The length of list OldKey and list NewKey must be equal.
The length of the list OldKey must be equal to the listNewKey, if the key does not exist in the OldKey, put 'noexis' instead as shown as.
Example:
OldDict={'a':'v1', 'b':'v2', 'c':'v3'}
OldKey=['a','b','c','noexis','noexis']
NewKey=['A','B','C','D','E']
NewDict=DictKeyChanger(OldDict,OldKey,NewKey)
print(NewDict)#===>>{'A': 'v1', 'B': 'v2', 'C': 'v3', 'D': None, 'E': None}
For the keeping of order case (the other one is trivial, remove old and add new one): I was not satisfied with the ordered-dictionary needing reconstruction (at least partially), obviously for efficiency reasons, so I've put together a class (OrderedDictX) that extends OrderedDict and allows you to do key changes efficiently, i.e. in O(1) complexity. The implementation can also be adjusted for the now-ordered built-in dict class.
It uses 2 extra dictionaries to remap the changed keys ("external" - i.e. as they appear externally to the user) to the ones in the underlying OrderedDict ("internal") - the dictionaries will only hold keys that were changed so as long as no key changing is done they will be empty.
Performance measurements:
import timeit
import random
# Efficiency tests
from collections import MutableMapping
class OrderedDictRaymond(dict, MutableMapping):
def __init__(self, *args, **kwds):
if len(args) > 1:
raise TypeError('expected at 1 argument, got %d', len(args))
if not hasattr(self, '_keys'):
self._keys = []
self.update(*args, **kwds)
def rename(self,key,new_key):
ind = self._keys.index(key) #get the index of old key, O(N) operation
self._keys[ind] = new_key #replace old key with new key in self._keys
self[new_key] = self[key] #add the new key, this is added at the end of self._keys
self._keys.pop(-1) #pop the last item in self._keys
dict.__delitem__(self, key)
def clear(self):
del self._keys[:]
dict.clear(self)
def __setitem__(self, key, value):
if key not in self:
self._keys.append(key)
dict.__setitem__(self, key, value)
def __delitem__(self, key):
dict.__delitem__(self, key)
self._keys.remove(key)
def __iter__(self):
return iter(self._keys)
def __reversed__(self):
return reversed(self._keys)
def popitem(self):
if not self:
raise KeyError
key = self._keys.pop()
value = dict.pop(self, key)
return key, value
def __reduce__(self):
items = [[k, self[k]] for k in self]
inst_dict = vars(self).copy()
inst_dict.pop('_keys', None)
return (self.__class__, (items,), inst_dict)
setdefault = MutableMapping.setdefault
update = MutableMapping.update
pop = MutableMapping.pop
keys = MutableMapping.keys
values = MutableMapping.values
items = MutableMapping.items
def __repr__(self):
pairs = ', '.join(map('%r: %r'.__mod__, self.items()))
return '%s({%s})' % (self.__class__.__name__, pairs)
def copy(self):
return self.__class__(self)
#classmethod
def fromkeys(cls, iterable, value=None):
d = cls()
for key in iterable:
d[key] = value
return d
class obj_container:
def __init__(self, obj) -> None:
self.obj = obj
def change_key_splice(container, k_old, k_new):
od = container.obj
container.obj = OrderedDict((k_new if k == k_old else k, v) for k, v in od.items())
def change_key_raymond(container, k_old, k_new):
od = container.obj
od.rename(k_old, k_new)
def change_key_odx(container, k_old, k_new):
odx = container.obj
odx.change_key(k_old, k_new)
NUM_ITEMS = 20000
od_splice = OrderedDict([(x, x) for x in range(NUM_ITEMS)])
od_raymond = OrderedDictRaymond(od_splice.items())
odx = OrderedDictX(od_splice.items())
od_splice, od_raymond, odx = [obj_container(d) for d in [od_splice, od_raymond, odx]]
assert odx.obj == od_splice.obj
assert odx.obj == od_raymond.obj
# Pick randomly half of the keys to change
keys_to_change = random.sample(range(NUM_ITEMS), NUM_ITEMS//2)
print(f'OrderedDictX: {timeit.timeit(lambda: [change_key_odx(odx, k, k+NUM_ITEMS) for k in keys_to_change], number=1)}')
print(f'OrderedDictRaymond: {timeit.timeit(lambda: [change_key_raymond(od_raymond, k, k+NUM_ITEMS) for k in keys_to_change], number=1)}')
print(f'Splice: {timeit.timeit(lambda: [change_key_splice(od_splice, k, k+NUM_ITEMS) for k in keys_to_change], number=1)}')
assert odx.obj == od_splice.obj
assert odx.obj == od_raymond.obj
And results:
OrderedDictX: 0.06587849999999995
OrderedDictRaymond: 1.1131364
Splice: 1165.2614647
As expected, the splicing method is extremely slow (didn't expect it to be that much slower either though) and uses a lot of memory, and the O(N) solution of #Ashwini Chaudhary (bug-fixed though, del also needed) is also slower, 17X times in this example.
Of course, this solution being O(1), compared to the O(N) OrderedDictRaymond the time difference becomes much more apparent as the dictionary size increases, e.g. for 5 times more elements (100000), the O(N) is now 100X slower:
NUM_ITEMS = 100000
OrderedDictX: 0.3636919999999999
OrderedDictRaymond: 36.3963971
Here's the code, please comment if you see issues or have improvements to propose as this might still be error-prone.
from collections import OrderedDict
class OrderedDictX(OrderedDict):
def __init__(self, *args, **kwargs):
# Mappings from new->old (ext2int), old->new (int2ext).
# Only the keys that are changed (internal key doesn't match what the user sees) are contained.
self._keys_ext2int = OrderedDict()
self._keys_int2ext = OrderedDict()
self.update(*args, **kwargs)
def change_key(self, k_old, k_new):
# Validate that the old key is part of the dict
if not self.__contains__(k_old):
raise Exception(f'Cannot rename key {k_old} to {k_new}: {k_old} not existing in dict')
# Return if no changing is actually to be done
if len(OrderedDict.fromkeys([k_old, k_new])) == 1:
return
# Validate that the new key would not conflict with another one
if self.__contains__(k_new):
raise Exception(f'Cannot rename key {k_old} to {k_new}: {k_new} already in dict')
# Change the key using internal dicts mechanism
if k_old in self._keys_ext2int:
# Revert change temporarily
k_old_int = self._keys_ext2int[k_old]
del self._keys_ext2int[k_old]
k_old = k_old_int
# Check if new key matches the internal key
if len(OrderedDict.fromkeys([k_old, k_new])) == 1:
del self._keys_int2ext[k_old]
return
# Finalize key change
self._keys_ext2int[k_new] = k_old
self._keys_int2ext[k_old] = k_new
def __contains__(self, k) -> bool:
if k in self._keys_ext2int:
return True
if not super().__contains__(k):
return False
return k not in self._keys_int2ext
def __getitem__(self, k):
if not self.__contains__(k):
# Intentionally raise KeyError in ext2int
return self._keys_ext2int[k]
return super().__getitem__(self._keys_ext2int.get(k, k))
def __setitem__(self, k, v):
if k in self._keys_ext2int:
return super().__setitem__(self._keys_ext2int[k], v)
# If the key exists in the internal state but was renamed to a k_ext,
# employ this trick: make it such that it appears as if k_ext has also been renamed to k
if k in self._keys_int2ext:
k_ext = self._keys_int2ext[k]
self._keys_ext2int[k] = k_ext
k = k_ext
return super().__setitem__(k, v)
def __delitem__(self, k):
if not self.__contains__(k):
# Intentionally raise KeyError in ext2int
del self._keys_ext2int[k]
if k in self._keys_ext2int:
k_int = self._keys_ext2int[k]
del self._keys_ext2int[k]
del self._keys_int2ext[k_int]
k = k_int
return super().__delitem__(k)
def __iter__(self):
yield from self.keys()
def __reversed__(self):
for k in reversed(super().keys()):
yield self._keys_int2ext.get(k, k)
def __eq__(self, other: object) -> bool:
if not isinstance(other, dict):
return False
if len(self) != len(other):
return False
for (k, v), (k_other, v_other) in zip(self.items(), other.items()):
if k != k_other or v != v_other:
return False
return True
def update(self, *args, **kwargs):
for k, v in OrderedDict(*args, **kwargs).items():
self.__setitem__(k, v)
def popitem(self, last=True) -> tuple:
if not last:
k = next(iter(self.keys()))
else:
k = next(iter(reversed(self.keys())))
v = self.__getitem__(k)
self.__delitem__(k)
return k, v
class OrderedDictXKeysView:
def __init__(self, odx: 'OrderedDictX', orig_keys):
self._odx = odx
self._orig_keys = orig_keys
def __iter__(self):
for k in self._orig_keys:
yield self._odx._keys_int2ext.get(k, k)
def __reversed__(self):
for k in reversed(self._orig_keys):
yield self._odx._keys_int2ext.get(k, k)
class OrderedDictXItemsView:
def __init__(self, odx: 'OrderedDictX', orig_items):
self._odx = odx
self._orig_items = orig_items
def __iter__(self):
for k, v in self._orig_items:
yield self._odx._keys_int2ext.get(k, k), v
def __reversed__(self):
for k, v in reversed(self._orig_items):
yield self._odx._keys_int2ext.get(k, k), v
def keys(self):
return self.OrderedDictXKeysView(self, super().keys())
def items(self):
return self.OrderedDictXItemsView(self, super().items())
def copy(self):
return OrderedDictX(self.items())
# FIXME: move this to pytest
if __name__ == '__main__':
MAX = 25
items = [(i+1, i+1) for i in range(MAX)]
keys = [i[0] for i in items]
d = OrderedDictX(items)
# keys() before change
print(list(d.items()))
assert list(d.keys()) == keys
# __contains__ before change
assert 1 in d
# __getitem__ before change
assert d[1] == 1
# __setitem__ before change
d[1] = 100
assert d[1] == 100
d[1] = 1
assert d[1] == 1
# __delitem__ before change
assert MAX in d
del d[MAX]
assert MAX not in d
d[MAX] = MAX
assert MAX in d
print('== Tests before key change finished ==')
# change_key and __contains__
assert MAX-1 in d
assert MAX*2 not in d
d.change_key(MAX-1, MAX*2)
assert MAX-1 not in d
assert MAX*2 in d
# items() and keys()
items[MAX-2] = (MAX*2, MAX-1)
keys[MAX-2] = MAX*2
assert list(d.items()) == items
assert list(d.keys()) == keys
print(list(d.items()))
# __getitem__
assert d[MAX*2] == MAX-1
# __setitem__
d[MAX*2] = MAX*3
items[MAX-2] = (MAX*2, MAX*3)
keys[MAX-2] = MAX*2
assert list(d.items()) == items
assert list(d.keys()) == keys
# __delitem__
del d[MAX]
items = items[:-1]
keys = keys[:-1]
assert list(d.items()) == items
assert list(d.keys()) == keys
d[MAX] = MAX
items.append((MAX, MAX))
keys.append(MAX)
# __iter__
assert list(d) == keys
# __reversed__
print(list(reversed(d.items())))
assert list(reversed(d)) == list(reversed(keys))
assert list(reversed(d.keys())) == list(reversed(keys))
assert list(reversed(d.items())) == list(reversed(items))
# pop_item()
assert d.popitem() == (MAX, MAX)
assert d.popitem() == (MAX*2, MAX*3)
items = items[:-2]
keys = keys[:-2]
assert list(d.items()) == items
assert list(d.keys()) == keys
# update()
d.update({1: 1000, MAX-2: MAX*4})
items[0] = (1, 1000)
items[MAX-3] = (MAX-2, MAX*4)
assert list(d.items()) == items
assert list(d.keys()) == keys
# move_to_end()
d.move_to_end(1)
items = items[1:] + [items[0]]
keys = keys[1:] + [keys[0]]
assert list(d.items()) == items
assert list(d.keys()) == keys
# __eq__
d.change_key(1, 2000)
other_d = OrderedDictX(d.items())
assert d == other_d
assert other_d == d
In my case, I had a function call returning a dict, which had a key I was hoping to rename in a single line, so none of these worked for me. Starting in python 3.8, you can use the walrus operator to keep it to one line if you are not looking for an inplace operation and the dict is not yet defined.
old_dict = get_dict()
# old_dict = {'a': 1, 'b': 2, 'c': 3}
new_dict = {'new1': (x := get_dict()).pop('b'), **x}
# new_dict = {'a': 1, 'new1': 2, 'c': 3}
I have combined some answers from the above thread and come up with the solution below. Although it is simple it can be used as a building block for making more complex key updates from a dictionary.
test_dict = {'a': 1, 'b': 2, 'c': 3}
print(test_dict)
# {'a': 1, 'b': 2, 'c': 3}
prefix = 'up'
def dict_key_update(json_file):
new_keys = []
old_keys = []
for i,(key,value) in enumerate(json_file.items()):
old_keys.append(key)
new_keys.append(str(prefix) + key) # i have updated by adding a prefix to the
# key
for old_key, new_key in zip(old_keys,new_keys):
print('old {}, new {}'.format(old_key, new_key))
if new_key!=old_key:
json_file[new_key] = json_file.pop(old_key)
return json_file
test_dict = dict_key_update(test_dict)
print(test_dict)
# {'upa': 1, 'upb': 2, 'upc': 3}
I am looking for a data structure that holds the same values under two different indexes, where I can access the data by either one.
Example:
x = mysticalDataStructure()
x.add(1,'karl', dog)
x.add(2,'lisa', cat)
$ x[1].age
2
$ x['karl'].age
2
$ x[1].age = 4
$ x['karl'].age
4
Is there anything prerolled, or what is the best approach to roll my own (I need access via an index (number going from 0 to n in increments of 1), and via a string).
collections.ordereddict does not seem to have fast random access via the position, as far as I see I can only walk it with the iterator until I reach element i (I can insert in the right order).
class MultiKeyDict(object):
def __init__(self, **kwargs):
self._keys = {}
self._data = {}
for k, v in kwargs.iteritems():
self[k] = v
def __getitem__(self, key):
try:
return self._data[key]
except KeyError:
return self._data[self._keys[key]]
def __setitem__(self, key, val):
try:
self._data[self._keys[key]] = val
except KeyError:
if isinstance(key, tuple):
if not key:
raise ValueError(u'Empty tuple cannot be used as a key')
key, other_keys = key[0], key[1:]
else:
other_keys = []
self._data[key] = val
for k in other_keys:
self._keys[k] = key
def add_keys(self, to_key, new_keys):
if to_key not in self._data:
to_key = self._keys[to_key]
for key in new_keys:
self._keys[key] = to_key
#classmethod
def from_dict(cls, dic):
result = cls()
for key, val in dic.items():
result[key] = val
return result
Usage:
>>> d = MultiKeyDict(a=1, b=2)
>>> d['c', 'd'] = 3 # two keys for one value
>>> print d['c'], d['d']
3 3
>>> d['c'] = 4
>>> print d['d']
4
>>> d.add_keys('d', ('e',))
>>> d['e']
4
>>> d2 = MultiKeyDict.from_dict({ ('a', 'b'): 1 })
>>> d2['a'] = 2
>>> d2['b']
2
Is there a particular reason you can't just use a dictionary:
x = {}
x[1] = x['karl'] = dog
x[2] = x['lisa'] = cat
Then you can access it by either.
If you really don't want to repeat your self you do this:
class MysticalDataStructure(dict):
def add(self, key1, key2, value):
return self[key1] = self[key2] = value
x = MysticalDataStructure()
x.add(1, 'karl', dog)
x.add(2, 'lisa', cat)
Just use three maps.
maps = [dict(), dict(), dict()]
def insert(rec):
maps[0][rec[0]] = rec
maps[1][rec[1]] = rec
maps[2][rec[2]] = rec
Changes to key attributes of the rec object will require reinsertion though. Just like any other map, when you change the key of an object.
The maps just map key -> object, after all. They don't actually store copies of the object (it just isn't garbage collected). So a map is an index, nothing more. If you want three indexes, use three maps. Write a couple of glue code functions to manage them.
As mentioned by Trevor, you can also use a shared dictionary:
index = dict()
def insert(rec):
index[rec[0]] = rec
index[rec[1]] = rec
index[rec[2]] = rec
then you can access it by either.
Beware of key collisions though!
Thanks to some great folks on SO, I discovered the possibilities offered by collections.defaultdict, notably in readability and speed. I have put them to use with success.
Now I would like to implement three levels of dictionaries, the two top ones being defaultdict and the lowest one being int. I don't find the appropriate way to do this. Here is my attempt:
from collections import defaultdict
d = defaultdict(defaultdict)
a = [("key1", {"a1":22, "a2":33}),
("key2", {"a1":32, "a2":55}),
("key3", {"a1":43, "a2":44})]
for i in a:
d[i[0]] = i[1]
Now this works, but the following, which is the desired behavior, doesn't:
d["key4"]["a1"] + 1
I suspect that I should have declared somewhere that the second level defaultdict is of type int, but I didn't find where or how to do so.
The reason I am using defaultdict in the first place is to avoid having to initialize the dictionary for each new key.
Any more elegant suggestion?
Thanks pythoneers!
Use:
from collections import defaultdict
d = defaultdict(lambda: defaultdict(int))
This will create a new defaultdict(int) whenever a new key is accessed in d.
Another way to make a pickleable, nested defaultdict is to use a partial object instead of a lambda:
from functools import partial
...
d = defaultdict(partial(defaultdict, int))
This will work because the defaultdict class is globally accessible at the module level:
"You can't pickle a partial object unless the function [or in this
case, class] it wraps is globally accessible ... under its __name__
(within its __module__)"
-- Pickling wrapped partial functions
Look at nosklo's answer here for a more general solution.
class AutoVivification(dict):
"""Implementation of perl's autovivification feature."""
def __getitem__(self, item):
try:
return dict.__getitem__(self, item)
except KeyError:
value = self[item] = type(self)()
return value
Testing:
a = AutoVivification()
a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6
print a
Output:
{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}
As per #rschwieb's request for D['key'] += 1, we can expand on previous by overriding addition by defining __add__ method, to make this behave more like a collections.Counter()
First __missing__ will be called to create a new empty value, which will be passed into __add__. We test the value, counting on empty values to be False.
See emulating numeric types for more information on overriding.
from numbers import Number
class autovivify(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
def __add__(self, x):
""" override addition for numeric types when self is empty """
if not self and isinstance(x, Number):
return x
raise ValueError
def __sub__(self, x):
if not self and isinstance(x, Number):
return -1 * x
raise ValueError
Examples:
>>> import autovivify
>>> a = autovivify.autovivify()
>>> a
{}
>>> a[2]
{}
>>> a
{2: {}}
>>> a[4] += 1
>>> a[5][3][2] -= 1
>>> a
{2: {}, 4: 1, 5: {3: {2: -1}}}
Rather than checking argument is a Number (very non-python, amirite!) we could just provide a default 0 value and then attempt the operation:
class av2(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
def __add__(self, x):
""" override addition when self is empty """
if not self:
return 0 + x
raise ValueError
def __sub__(self, x):
""" override subtraction when self is empty """
if not self:
return 0 - x
raise ValueError
Late to the party, but for arbitrary depth I just found myself doing something like this:
from collections import defaultdict
class DeepDict(defaultdict):
def __call__(self):
return DeepDict(self.default_factory)
The trick here is basically to make the DeepDict instance itself a valid factory for constructing missing values. Now we can do things like
dd = DeepDict(DeepDict(list))
dd[1][2].extend([3,4])
sum(dd[1][2]) # 7
ddd = DeepDict(DeepDict(DeepDict(list)))
ddd[1][2][3].extend([4,5])
sum(ddd[1][2][3]) # 9
def _sub_getitem(self, k):
try:
# sub.__class__.__bases__[0]
real_val = self.__class__.mro()[-2].__getitem__(self, k)
val = '' if real_val is None else real_val
except Exception:
val = ''
real_val = None
# isinstance(Avoid,dict)也是true,会一直递归死
if type(val) in (dict, list, str, tuple):
val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
# 重新赋值当前字典键为返回值,当对其赋值时可回溯
if all([real_val is not None, isinstance(self, (dict, list)), type(k) is not slice]):
self[k] = val
return val
def _sub_pop(self, k=-1):
try:
val = self.__class__.mro()[-2].pop(self, k)
val = '' if val is None else val
except Exception:
val = ''
if type(val) in (dict, list, str, tuple):
val = type('Avoid', (type(val),), {'__getitem__': _sub_getitem, 'pop': _sub_pop})(val)
return val
class DefaultDict(dict):
def __getitem__(self, k):
return _sub_getitem(self, k)
def pop(self, k):
return _sub_pop(self, k)
In[8]: d=DefaultDict()
In[9]: d['a']['b']['c']['d']
Out[9]: ''
In[10]: d['a']="ggggggg"
In[11]: d['a']
Out[11]: 'ggggggg'
In[12]: d['a']['pp']
Out[12]: ''
No errors again.
No matter how many levels nested.
pop no error also
dd=DefaultDict({"1":333333})