Adapt a dictionary depending on the values - python

I have the following problem: I create a dictionary in which the keys are IDs (0 to N) and the values are list of one or more numbers.
D = dict()
D[0] = [1]
D[1] = [2]
D[2] = [0]
OR:
D = dict()
D[0] = [1, 2]
D[1] = [1, 2]
D[2] = [0]
When the list stored in dictionary has more than one value, it always means that this list is present under 2 different keys. What I now want is to convert both dict into this:
D = dict()
D[0] = 1
D[1] = 2
D[2] = 0
For the first one, it's simple, the function will simply replace the values of the dict by the first value in the list:
def transform_dict(D):
for key, value in D.items():
D[key] = value[0]
return D
However, in the second case, the function must assign one of the key with one of the value, and the second with another. For instance, the key "0" can be assign the value "1" or "2"; and the key "1" will be assign the other one.
I am struggling with this simple problem, and I don't see a way to do this efficiently. Do you have any idea?
EDIT: Explanation n°2
The initial dict can have the following format:
D[key1] = [val1]
D[key2] = [val2]
D[key3] = [val3, val4]
D[key4] = [val3, val4]
If a list of values is composed of more than one element, it means that a second key exist within the dictionnary with the same list of values (key3 and key4).
The goal is to transform this dict into:
D[key1] = val1
D[key2] = val2
D[key3] = val3
D[key4] = val4
Where val3 and val4 are attributed to key3 and key4 in whatever way (I don't care which one goes with which key).
EDIT2: Examples:
# Input dict
D[0] = [7]
D[1] = [5]
D[2] = [4]
D[3] = [1, 2, 3]
D[4] = [6, 8]
D[5] = [1, 2, 3]
D[6] = [1, 2, 3]
D[7] = [6, 8]
#Output
D[0] = 7
D[1] = 5
D[2] = 4
D[3] = 1
D[4] = 6
D[5] = 2
D[6] = 3
D[7] = 8

You can also create a class which behaves likes a dictionary. That way you don't need any additional functions to "clean" the dictionary afterwards but rather solve it on the fly :)
How it works:
We extend collections.abc.Mapping and overwrite the standard dictionary functions __getitem__, __setitem__ and __iter__. We use self._storage to save the actual dictionary.
We use a second dictionary _unresolved to keep track of the keys which haven't been resolved yet. In the example above it for example has the entry (1, 2, 3): [4, 5].
We use a helper function _resolve() that checks if the len((1,2,3)) == len([4,5]). On the moment you assign D[6] this lengths are equal, and the items are assigned to self._storage.
Tried to add comments in the code.
from collections.abc import Mapping
from collections import defaultdict
class WeirdDict(Mapping):
def __init__(self, *args, **kw):
self._storage = dict() # the actual dictionary returned
self._unresolved = defaultdict(list) # a reversed mapping of the unresolved items
for key, value in dict(*args, **kw).items():
self._unresolved_vals[value].append(key)
self._resolve()
def __getitem__(self, key):
return self._storage[key]
def __setitem__(self, key, val):
""" Setter. """
if type(val) == int:
self._storage[key] = val
elif len(val) == 1:
self._storage[key] = val[0]
elif key not in self._storage:
self._unresolved[tuple(val)].append(key)
self._resolve()
def _resolve(self):
""" Helper function - checks if any keys can be resolved """
resolved = set()
for val, keys in self._unresolved.items(): # left to resolve
if len(val) == len(keys): # if we can resolve (count exhausted)
for i, k in enumerate(keys):
self._storage[k] = val[i]
resolved.add(val)
# Remove from todo list
for val in resolved:
del self._unresolved[val]
def __iter__(self):
return iter(self._storage)
def __len__(self):
return len(self._storage)
And then start with:
D = WeirdDict()
D[0] = [7]
D[1] = 5
D[2] = (4)
D[3] = (1, 2, 3)
D[4] = (6, 8)
D[5] = (1, 2, 3)
D[6] = (1, 2, 3)
D[7] = [6, 8]
# Try this for different output
D[7] # gives 8

I am not sure this is the most efficient, but it seems a way of doing it:
in_dict = dict()
in_dict[0] = [7]
in_dict[1] = [5]
in_dict[2] = [4]
in_dict[3] = [1, 2, 3]
in_dict[4] = [6, 8]
in_dict[5] = [1, 2, 3]
in_dict[6] = [1, 2, 3]
in_dict[7] = [6, 8]
out_dict = dict()
out_dict[0] = 7
out_dict[1] = 5
out_dict[2] = 4
out_dict[3] = 1
out_dict[4] = 6
out_dict[5] = 2
out_dict[6] = 3
out_dict[7] = 8
def weird_process(mapping):
result = dict()
for key, val in mapping.items():
if len(val) == 1:
result[key] = val[0]
elif key not in result: # was: `else:`
# find other keys having the same value
matching_keys = [k for k, v in mapping.items() if v == val]
for i, k in enumerate(matching_keys):
result[k] = val[i]
return result
weird_process(in_dict) == out_dict
# True
EDIT: I have simplified the code a little bit.
EDIT2: I have improved the efficiency by skipping elements that have been already processed
EDIT3
An even faster approach would be to use a temporary copy of the input keys to reduce the inner looping by consuming the input as soon as it gets used:
def weird_process(mapping):
unseen = set(mapping.keys())
result = dict()
for key, val in mapping.items():
if len(val) == 1:
result[key] = val[0]
elif key not in result:
# find other keys having the same value
matching_keys = [k for k in unseen if mapping[k] == val]
for i, k in enumerate(matching_keys):
result[k] = val[i]
unseen.remove(k)
return result

Related

find the key with longest path from the dictionary

find the key with longest path from the dictionary. The key value pair will be integers. consider the following dictionary d={2:1,3:2,4:5,1:4}
here the first key is 2 and its value is 1. so you need to find the value of key 1. this method has to follow until the value is not present in the dictionary as a key or the value become the key where we start to traverse the dictionary
i tried like this :
d = {2: 1, 3: 2, 4: 5, 1: 4}
k = 0
count = 0
def find(k):
m = d[k]
return m
for i in d.keys():
k = d[i]
find(k)
count = count + 1
print(count)
my aim pass the each to function and return
If I'm right, this is the functionality you require:
d = {2:1, 3:2, 4:5, 1:4}
so if
key = 2, value = 1;
key = 1, value = 4;
key = 4, value = 5;
key = 5 --> No value so stop here
Thus to find the key with the longest path:
d={2:1,3:2,4:5,1:4}
c1=0
ans_key = -1
for i in d.keys():
k=i
c=0
while(k in d.keys()):
k = d[k]
c+=1
if(c1<c):
c1=c
ans_key=i
print("The Key with the longest path is",ans_key)
This will return the output:
The Key with the longest path is 3
Hope this helps!

How to print only the unique items in a list, those occurring once?

def get_distinct(original_list):
distinct_list = []
for i in original_list:
if i not in distinct_list:
distinct_list.append(each)
return distinct_list
list_1 = [1,2,3,4,4,5,6,6]
print(get_distinct(list_1))
So I want it to print 1, 2, 3, 5 instead of 1,2,3,4,5,6
collections.Counter() is good way to count things, e.g.:
from collections import Counter
def get_distinct(original_list):
return [k for k, v in Counter(original_list).items() if v == 1]
In []:
list_1 = [1,2,3,4,4,5,6,6]
get_distinct(list_1)
Out[]:
[1, 2, 3, 5]
While in 3.6 this will be in the order expected Counter() doesn't make any order guarantees, if you need it in the same order as in original_list then you can create a set and use that to test uniqueness, e.g.:
def get_distinct(original_list):
uniqs = {k for k, v in Counter(original_list).items() if v == 1}
return [e for e in original_list if e in uniqs]
print([x for x in list_1 if list_1.count(x) == 1])
Although using collections.Counter() is the best approach here, another option you can use is counting with a collections.defaultdict():
from collections import defaultdict
def get_distinct(original_list):
counts = defaultdict(int)
for item in original_list:
counts[item] += 1
return [k for k, v in counts.items() if v == 1]
Which works as follows:
>>> get_distinct([1,2,3,4,4,5,6,6])
[1, 2, 3, 5]
If you want to guarantee order, you can use a collections.OrderedDict():
from collections import OrderedDict
def get_distinct(original_list):
counts = OrderedDict()
for item in original_list:
counts[item] = counts.get(item, 0) + 1
return [k for k, v in counts.items() if v == 1]

Function Return List as Dictionary?

My function below is taking a list of values and returning the counts of duplicates. I managed to make it count and print, but my task is to return it as a dictionary. I've been struggling to return in the correct format, any advice?
def counts(values):
d = {}
for val in values:
d.setdefault(val,0)
d[val] += 1
for val, count in d.items():
d = ("{} {}".format(val,count))
return d
counts([1,1,1,2,3,3,3,3,5]) # Should return → {1: 3, 2: 1, 3: 4, 5: 1}
Just return the created dictionary:
def counts(values):
d = {}
for val in values:
d.setdefault(val,0)
d[val] += 1
return d
Yields:
>>> counts([1,1,1,2,3,3,3,3,5])
{1: 3, 2: 1, 3: 4, 5: 1}
Of course, as Moses points out, a Counter is built for this so just use that instead:
from collections import Counter
def counts(values):
return dict(Counter(values))

Group list-items by order of appearance in unsorted list

Test cases:
group_ordered([1,3,2,3,6,3,1]) = [1,1,3,3,3,2,6]
group_ordered([1,2,3,4,5,6,1]) = [1,1,2,3,4,5,6]
I have some code already, but it's ugly and probably slow on large lists as well, since for each unique item I'm looking at the whole list. I came up with this algorithm, but I am wondering if there is a faster, cleaner, or more pythonic way I can do this:
def make_order_list(list_in):
order_list = []
for item in list_in:
if item not in order_list:
order_list.append(item)
return order_list
def group_ordered(list_in):
if list_in is None:
return None
order_list = make_order_list(list_in)
current = 0
for item in order_list:
search = current + 1
while True:
try:
if list_in[search] != item:
search += 1
else:
current += 1
list_in[current], list_in[search] = list_in[search], list_in[current]
search += 1
except IndexError:
break
return list_in
Use a collections.OrderedDict() instance to do the grouping:
from collections import OrderedDict
def group_ordered(list_in):
result = OrderedDict()
for value in list_in:
result.setdefault(value, []).append(value)
return [v for group in result.values() for v in group]
Because this specialised dictionary object tracks insertion order of the key, the output is ordered by first occurrence of a group value.
Demo:
>>> group_ordered([1,3,2,3,6,3,1])
[1, 1, 3, 3, 3, 2, 6]
>>> group_ordered([1,2,3,4,5,6,1])
[1, 1, 2, 3, 4, 5, 6]

Iterate over a dict or list in Python

Just wrote some nasty code that iterates over a dict or a list in Python. I have a feeling this was not the best way to go about it.
The problem is that in order to iterate over a dict, this is the convention:
for key in dict_object:
dict_object[key] = 1
But modifying the object properties by key does not work if the same thing is done on a list:
# Throws an error because the value of key is the property value, not
# the list index:
for key in list_object:
list_object[key] = 1
The way I solved this problem was to write this nasty code:
if isinstance(obj, dict):
for key in obj:
do_loop_contents(obj, key)
elif isinstance(obj, list):
for i in xrange(0, len(obj)):
do_loop_contents(obj, i)
def do_loop_contents(obj, key):
obj[key] = 1
Is there a better way to do this?
Thanks!
I've never needed to do this, ever. But if I did, I'd probably do something like this:
seq_iter = x if isinstance(x, dict) else xrange(len(x))
For example, in function form:
>>> def seq_iter(obj):
... return obj if isinstance(obj, dict) else xrange(len(obj))
...
>>> x = [1,2,3]
>>> for i in seq_iter(x):
... x[i] = 99
...
>>> x
[99, 99, 99]
>>>
>>> x = {1: 2, 2:3, 3:4}
>>> for i in seq_iter(x):
... x[i] = 99
...
>>> x
{1: 99, 2: 99, 3: 99}
This is the correct way, but if for some reason you need to treat these two objects the same way, you can create an iterable that will return indexes / keys no matter what:
def common_iterable(obj):
if isinstance(obj, dict):
return obj
else:
return (index for index, value in enumerate(obj))
Which will behave in the way you wanted:
>>> d = {'a': 10, 'b': 20}
>>> l = [1,2,3,4]
>>> for index in common_iterable(d):
d[index] = 0
>>> d
{'a': 0, 'b': 0}
>>> for index in common_iterable(l):
l[index] = 0
>>> l
[0, 0, 0, 0]
Or probably more efficiently, using a generator:
def common_iterable(obj):
if isinstance(obj, dict):
for key in obj:
yield key
else:
for index, value in enumerate(obj):
yield index
To be pythonic and ducktype-y, and also to follow "ask for forgiveness not permission", you could do something like:
try:
iterator = obj.iteritems()
except AttributeError:
iterator = enumerate(obj)
for reference, value in iterator:
do_loop_contents(obj, reference)
Though if all you need is the key/index:
try:
references = obj.keys()
except AttributeError:
references = range(len(obj))
for reference in references:
do_loop_contents(obj, reference)
Or as a function:
def reference_and_value_iterator(iterable):
try:
return iterable.iteritems()
except AttributeError:
return enumerate(iterable)
for reference, value in reference_and_value_iterator(obj):
do_loop_contents(obj, reference)
Or for just the references:
def references(iterable):
try:
return iterable.keys()
except AttributeError:
return range(len(iterable))
for reference in references(obj):
do_loop_contents(obj, reference)
test_list = [2, 3, 4]
for i, entry in enumerate(test_list):
test_list[i] = entry * 2
print(test_list) # Gives: [4, 6, 8]
But you probably want a list comprehension:
test_list = [2, 3, 4]
test_list = [entry * 2 for entry in test_list]
print(test_list) # Gives: [4, 6, 8]
You probably just want to have a different code depending on if the object you are trying to change is a dict or a list.
if type(object)==type([]):
for key in range(len(object)):
object[key]=1
elif type(object)==type({}): #use 'else' if you know that object will be a dict if not a list
for key in object:
object[key]=1
I stumbled upon this post while searching for a better one, here's how I did it.
for row in [dict_or_list] if not type(dict_or_list) is list else dict_or_list:
for i,v in row.items():
print(i,v)

Categories