Finding cycles in a dictionary - python

I have a dictionary which has values as:
m = {1: 2, 7: 3, 2: 1, 4: 4, 5: 3, 6: 9}
The required output should be cyclic values like 1:2 -> 2:1 = cycle, which both are present in dictionary. 4:4 is also a cycle.
My output should be
[(1, 2), [4]]
CODE
m = {1: 2, 7: 3, 2: 1, 4: 4, 5: 3, 6: 9}
k = list(m.items())
print(k)
p = [t[::-1] for t in k]
print(p)
my_list = [(a, b) for (a, b) in p for (c, d) in k if ((a ==c) and (b == d))]
for i in k:
if i in my_list:
print("cycles : ", i)
OUTPUT:
The output I am getting is
cycles : (1, 2)
cycles : (2, 1)
cycles : (4, 4)
Can someone help me out with this?

Let's split this into two steps. First we'll find the cycles. Then we'll format the output however you want.
Finding cycles is easy, given that you have a dictionary: values become keys, making lookup fast. We can store the cycles in sorted order in a set to avoid duplicates:
cycles = set()
for k, v in m.items():
if m.get(v) == k:
cycles.add(tuple(sorted((k, v))))
Not that I'd recommend it, but the above can be written as an illegible one-liner:
cycles = set(tuple(sorted(item)) for item in m.items() if m.get(item[1]) == item[0])
Now to format the data. You want a list output, and entries with duplicates formatted as lists:
output = [[k] if k == v else (k, v) for k, v in cycles]
If you don't like clean code, you can imagine how to turn the whole operation into a one-liner :)
Update
It's worth considering the case where cycles are longer than one or two entries. You seem to want to store only one element per cycle, so let's do that. We can follow the chain from each element in the dictionary. If any part of the chain makes a cycle, we can find the minimum element of the cycle to report, and remove all the visited elements, since they are all no longer under consideration:
def find_cycles(m):
n = m.copy() # don't mutilate the original
cycles = []
while n:
visited = {}
count = 0
k, v = n.popitem()
while v is not None:
visited[k] = (count, v)
count += 1
k = v
v = n.pop(k, None)
if k in visited:
cycle_start = visited[k][0]
item = min((k, v) for k, (c, v) in visited.items() if c >= cycle_start)
cycles.append(item)
return [[k] if k == v else (k, v) for k, v in cycles]
For example:
>>> find_cycles({1:2, 2:3, 3:4, 4:5, 5:1, 6:1, 7:1, 8:8})
[(1, 2), [8]]
A better generalization might be to return a tuple with the entire cycle in it, starting with the smallest key. Mostly the statements under if k in visited: need to change to do that:
visited[k] = count
...
if k in visited:
if len(visited) == 1:
cycle = list(visited.keys())
else:
cycle_start = visited[k]
cycle = sorted((c, k) for k, c in visited.items() if c >= cycle_start)
cycle = tuple(k for c, k in cycle)
k = min(range(len(cycle)), key=lambda x: cycle[x])
cycle = cycle[k:] + cycle[:k]
cycles.append(cycle)
return cycles
This version is more informative:
>>> find_cycles({1:2, 2:3, 3:4, 4:5, 5:1, 6:1, 7:1, 8:8})
[(1, 2, 3, 4, 5), [8]]
Here is my IDEOne scratchspace in case you're interested: https://ideone.com/6kpRrW

Edited
Hey you have to just replace the pair with any other symbol in the list once it is found. Then it gives you the desired results.
map = {1:2, 7:3, 2:1, 4:4, 5:3, 6:9}
k = list(map.items())
print(k)
result = []
for i in k:
if i != -1 and i[::-1] in k:
result.append((set(i)))
k[k.index(i[::-1])] = -1
print(result)
#if you want tuples as output
[print(tuple(x),end = " ") for x in result]

Related

concatenating list of tuples python

I have a list of tuples as seen below :
l = [(1,'Nick'),(4,'George'),(4,'George'),(3,'Nick')]
what i would like to do is merge the tuples with the same Name and add the corresponding numbers.
Result here should be :
l_res = [(4,'Nick'),(8,'George')]
How could this be done using python?
You can use a combination of set and list comprehensions like this:
>>> l = [(1,'Nick'),(4,'George'),(4,'George'),(3,'Nick')]
>>> unique = set(e[1] for e in l)
>>> [(sum([x[0] for x in l if x[1] == n]), n) for n in unique]
[(8, 'George'), (4, 'Nick')]
You can use an intermediate set as suggested in a previous answer or with an intermediate dictionary like this:
l = [(1,'Nick'),(4,'George'),(4,'George'),(3,'Nick')]
d = dict()
for v, t in l:
d[t] = d.get(t, 0) + v
result = [(v, k) for k, v in d.items()]
print(result)
Output:
[(4, 'Nick'), (8, 'George')]
Probably the cleanest way is by using a Counter dictionary.
from collections import Counter
l = [(1,'Nick'),(4,'George'),(4,'George'),(3,'Nick')]
sum_dict = Counter()
for el in l:
sum_dict[el[1]] += el[0]
print(sum_dict) # Output: Counter({'George': 8, 'Nick': 4})
If you really want a list of tuples (which doesn't seem like the best datatype for this data), you can easily convert it with list comprehension over dict.items()
sum_list = [(v,k) for k,v in sum_dict.items()]
print(sum_list) # Output: [('Nick', 4), ('George', 8)]
You want to turn l_res into a dict by name, adding the numbers:
dic = {}
for tup in l:
num, name = tup
if name in dic:
dic[name] += num
else:
dic[name] = num
to turn it back into a list:
l_res = [(dic[k], k) for k in dic]

Find minimum non zero value in dictionary (Python)

I have a dictionary and I would like to get the key whose value is the minimum nonzero.
E.g. given the input:
{1:0, 2:1, 3:2}
It would return 2.
You can do it on one iteration.
d = {1:0, 2:1, 3:2}
# Save the minimum value and the key that belongs to it as we go
min_val = None
result = None
for k, v in d.items():
if v and (min_val is None or v < min_val):
min_val = v
result = k
print(result)
Some assumptions:
Negative values will be considered
It will return the first key that found
If it helps, min_val will hold the minimum value
You can use the fact 0 is considered False to filter out 0 values. Then use next with a generator expression:
d = {1:0, 2:1, 3:2}
val = min(filter(None, d.values()))
res = next(k for k, v in d.items() if v == val) # 2
This will only return one key in the case of duplicate keys with 1 as value. For multiple matches, you can use a list comprehension:
res = [k for k, v in d.items() if v == val]
Note your literal ask for "minimum non-zero" will include negative values.
Performance note
The above solution is 2-pass but has time complexity O(n), it's not possible to have lower complexity than this. A 1-pass O(n) solution is possible as shown by #Maor, but this isn't necessarily more efficient:
# Python 3.6.0
%timeit jpp(d) # 43.9 ms per loop
%timeit mao(d) # 98.8 ms per loop
%timeit jon(d) # 183 ms per loop
%timeit reu(d) # 303 ms per loop
Code used for benchmarking:
from random import randint
n = 10**6
d = {i: randint(0, 9) for i in range(n)}
def jpp(d):
val = min(filter(None, d.values()))
return next(k for k, v in d.items() if v == val)
def mao(d):
min_val = None
result = None
for k, v in d.items():
if v and (min_val is None or v < min_val):
min_val = v
result = k
return result
def jon(d):
return min({i for i in d if d[i] != 0})
def reu(d):
no_zeros = {k: v for k, v in d.items() if v != 0}
key, val = min(no_zeros.items(), key=itemgetter(1))
return key
Assuming the dict is named a:
from operator import itemgetter
a = {1:0, 2:1, 3:2}
# remove zeros
no_zeros = {k: v for k, v in a.items() if v != 0} # can use `if v`
# find minimal key and value (by value)
key, val = min(no_zeros.items(), key=itemgetter(1))
# key = 2, val = 1
print(min(i for i in dictionary if dictionary[i] != 0))
this makes a set with no zeros and return the minimum value in that set. Though it is worth pointing out this makes 2 iterations and is thus slower than Maor Refaeli's solution.
Solution
some_dict = {1:0, 2:1, 3:2}
compare = []
for k, v in some_dict.items():
if k != 0:
compare.append(k)
x = min(compare)
print(x)
I just appended all the non-zero keys to a list (compare) and then applied min(compare)
We can plug x back in and check that it is pointing to the key 1 which is the smallest non-zero key and that returns it's value which is 0
>>> print(some_dict[x])
>>> 0

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]

Python count elements in dictionary comprehension when count is above a threshold

I want to populate a dictionary with the counts of various items in a list, but only when the count exceeds a certain number. (This is in Python 2.7)
For example:
x = [2,3,4,2,3,5,6] if I only want numbers that appear twice or more, I would want only
d = {2: 2, 3: 2} as an output.
I wanted to do this with a dictionary comprehension, for example
{(num if x.count(num) >= 2): x.count(num) for num in x}
But this throws an "invalid syntax" error, and it seems I need to set some default key, which means some key I don't want being added to the dictionary which I then have to remove.
What I'm doing now is in two lines:
d = {(num if x.count(num) >= 2 else None): x.count(num) for num in x}
d.pop(None, None)
But is there a way to do it in one, or to do the dictionary comprehension with an if statement without actually adding any default key for the else statement?
Use Counter to count each items in x, the use a dictionary comprehension to pull those values where the count is greater than or equal to your threshold (e.g. 2).
from collections import Counter
x = [2, 3, 4, 2, 3, 5, 6]
threshold = 2
c = Counter(x)
d = {k: v for k, v in c.iteritems() if v >= threshold}
>>> d
{2: 2, 3: 2}
That works:
{ i: x.count(i) for i in x if x.count(i) >= 2}
The if part must be after the for, not before, that's why you get the syntax error.
To avoid counting elements twice, and without any extra import, you could also use two nested comprehensions (actually the inner one is a generator to avoid iterating the full list twice) :
>>> { j: n for j, n in ((i, x.count(i)) for i in x) if n >= 2}
{2: 2, 3: 2}
The test in your expression: (num if x.count(num) >= 2 else None) comes too late: you already instructed the dict comp to issue a value. You have to filter it out beforehand.
just move the condition from ternary to the filter part of the comprehension:
x = [2,3,4,2,3,5,6]
d = {num: x.count(num) for num in x if x.count(num) >= 2}
that said, this method isn't very effective, because it counts elements twice.
Filter a Counter instead:
import collections
d = {num:count for num,count in collections.Counter(x).items() if count>=2}
This should work:
a = [1,2,2,2,3,4,4,5,6,2,2,2]
{n: a.count(n) for n in set(a) if a.count(n) >= 2}
{2: 6, 4: 2}
This should work:
Input:
a = [2,2,2,2,1,1,1,3,3,4]
Code:
x = { i : a.count(i) for i in a }
print(x)
Output:
>>> {2: 4, 1: 3, 3: 2, 4: 1}

Python: get key with the least value from a dictionary BUT multiple minimum values

I'm trying to do the same as
Get the key corresponding to the minimum value within a dictionary, where we want to get the key corresponding to the minimum value in a dictionary.
The best way appears to be:
min(d, key=d.get)
BUT I want to apply this on a dictionary with multiple minimum values:
d = {'a' : 1, 'b' : 2, 'c' : 1}
Note that the answer from the above would be:
>>> min(d, key=d.get)
'a'
However, I need both the two keys that have a minimum value, namely a and c.
What would be the best approach?
(Ultimately I want to pick one of the two at random, but I don't think this is relevant).
One simple option is to first determine the minimum value, and then select all keys mapping to that minimum:
min_value = min(d.itervalues())
min_keys = [k for k in d if d[k] == min_value]
For Python 3 use d.values() instead of d.itervalues().
This needs two passes through the dictionary, but should be one of the fastest options to do this anyway.
Using reservoir sampling, you can implement a single pass approach that selects one of the items at random:
it = d.iteritems()
min_key, min_value = next(it)
num_mins = 1
for k, v in it:
if v < min_value:
num_mins = 1
min_key, min_value = k, v
elif v == min_value:
num_mins += 1
if random.randrange(num_mins) == 0:
min_key = k
After writing down this code, I think this option is of rather theoretical interest… :)
EDITED: Now using setdefault as suggested :)
I don't know if that helps you but you could build a reverse dictionary with the values as key and the keys (in a list as values).
d = {'a' : 1, 'b' : 2, 'c' : 1}
d2 = {}
for k, v in d.iteritems():
d2.setdefault(v, []).append(k)
print d2[min(d2)]
It will print this:
['a', 'c']
However, I think the other solutions are more compact and probably more elegant...
min_keys = [k for k in d if all(d[m] >= d[k] for m in d)]
or, slightly optimized
min_keys = [k for k, x in d.items() if not any(y < x for y in d.values())]
It's not as efficient as other solutions, but demonstrates the beauty of python (well, to me at least).
def get_rand_min(d):
min_val = min(d.values())
min_keys = filter(lambda k: d[k] == min_val, d)
return random.choice(min_keys)
You can use heapq.nsmallest to get the N smallest members of the dict, then filter out all that are not equal to the lowest one. That's provided you know the maximal number of smallest members you can have, let's assume it's N here. something like:
from heapq import nsmallest
from operator import itemgetter
#get the N smallest members
smallestN = nsmallest(N, myDict.iteritems(), itemgetter(1)))
#leave in only the ones with a score equal to the smallest one
smallest = [x for x in smallestN if x[1] == smallestN[0][1]]
minValue,minKey = min((v,k) for k,v in d.items())
Due to your semantics you need to go through the entire dictionary at least once. This will retrieve exactly 1 minimum element.
If you want all the minimum items in O(log(N)) query time, you can insert your elements into a priority queue as you generate them (if you can). The priority queue must have O(1) insertion time and O(log(N)) extract-min time. (This will be as bad as sorting if all your elements have the same value, but otherwise may work quite well.)
One pass solution would be:
>>> result = [100000, []]
>>> for key, val in d.items():
... if val < result[0]:
... result[1] = [key]; result[0]=val;
... elif val == result[0]:
... result[1].append(key)
...
>>> result
[1, ['a', 'c']]
Here's another way to do it in one pass:
d = {'foo': 2, 'a' : 1, 'b' : 2, 'c' : 1, 'z': 99, 'x': 1}
current_min = d[d.keys()[0]]
min_keys = []
for k, v in d.iteritems():
if v < current_min:
current_min = v
min_keys = [k]
elif v == current_min:
min_keys.append(k)
print min_keys
['a', 'x', 'c']
This works:
d = {'a' :1, 'b' : 2, 'c' : 1}
min_value = min(d.values())
result = [x[0] for x in d.items() if x[1] == k]
Hmpf. After fixing up the code to work, I ended up with #Sven Marnach's answer, so, disregard this ;)

Categories