nested for loop not counting correctly - python

I have two lists:
common_nodes_list = ['A', 'A', 'B', 'C', 'C', 'C']
uniquePatterns = ['A', 'B', 'C']
I am trying to create a dict with the counts of each unique pattern. Like this:
A: 2
B: 1
C: 3
I have a for loop inside of another for loop:
patternRank = {}
for i in common_nodes_list:
score = 0
for pattern in uniquePatterns:
if pattern == i:
score += 1
patternRank[pattern]=score
patternRank
but It's only returning:
'C': 1

An alternative one line:
patternRank = {i: common_nodes_list.count(i) for i in uniquePatterns}
# {'A': 2, 'B': 1, 'C': 3}

You should do it the other way: for each pattern in the unique patterns, count how many there are in the common_nodes_list:
common_nodes_list = ['A', 'A', 'B', 'C', 'C', 'C']
unique_patterns = ['A', 'B', 'C']
pattern_rank = {}
for pattern in unique_patterns:
score = 0
for node in common_nodes_list:
if node == pattern:
score += 1
pattern_rank[pattern] = score
print(pattern_rank)
>> {'A': 2, 'B': 1, 'C': 3}
And maybe, try to be consistant with the way you name the variables: snake_case or CapWords.

Maybe it would be better:
pattern_count = {x: 0 for x in uniquePatterns}
for t in common_nodes_list:
pattern_count[t] += 1
Your solution will work if you do this modifications:
patternRank = {}
for i in common_nodes_list:
for pattern in uniquePatterns:
score = 0 if pattern not in patternRank.keys() else patternRank[pattern] # <-
if pattern == i:
score += 1
patternRank[pattern]=score # <- Tab
patternRank

we can use a for loop that iterates on the unique_patterns list. then using dict.update({key: value}).
common_nodes_list = ['A', 'A', 'B', 'C', 'C', 'C']
unique_patterns = ['A', 'B', 'C']
char_hist = {}
for char in unique_patterns:
char_hist.update({char: common_nodes_list.count(char)})
print(char_hist)

Related

How to find a single value in a nested list (and its position) with minimal iterations?

Say I have the following nested list:
my_list = [
['a', 'b', 'c', 'd'],
['a', 'c', 'd'],
['b', 'c', 'd', 'e'],
['a', 'c'],
]
I want to find any values that appear only once in the entire list, and also their position in the parent list. For example, 'e' appears only once, and it's in my_list[2].
The only ways I can think of doing this involve at least three iterations. For example:
for letter in ['a', 'b', 'c', 'd', 'e']: # Iteration 1
count = 0
for child_list in my_list: # Iteration 2
if letter in child_list:
count += 1
if count == 1:
print(letter)
for i in range(len(my_list)): # Iteration 3
if letter in my_list[i]:
print(i)
or:
counter = {}
for i in range(len(my_list)): # Iteration 1
for letter in my_list[i]: # Iteration 2
if letter not in counter:
# Store the count and (first) position of each letter
counter[letter] = [1, i]
else:
counter[letter][0] += 1
for letter in counter: # Iteration 3
if counter[letter][0] == 1:
print(letter)
print(counter[letter][1])
Is there a way of doing this that doesn't require so many iterations?
Here's a way to do what your question asks using a python set and dict:
my_list = [
['a', 'b', 'c', 'd'],
['a', 'c', 'd'],
['b', 'c', 'd', 'e'],
['a', 'c'],
]
visited = set()
singletons = {}
for i, L in enumerate(my_list):
for v in L:
if v not in visited:
visited.add(v)
singletons[v] = i
else:
singletons.pop(v, None)
print(singletons)
Output:
{'e': 2}
Here is a simple way with a single iteration (i.e. each element is visited only once) that creates a dictionary of {letter: [count, first position]}:
d = {}
for i, lst in enumerate(my_list):
for item in lst:
if item in d:
d[item][0] +=1
else:
d[item] = [1, i]
output:
{'a': [3, 0], 'b': [2, 0], 'c': [4, 0], 'd': [3, 0], 'e': [1, 2]}
Interesting problem, not sure if this is what you are after but here it is:
x = 0
y = 0
output = {}
duplicates = []
while x < len(my_list):
if y < len(my_list[x]):
value = my_list[x][y]
y = y + 1
if value in output:
del output[value]
duplicates.append(value)
elif not value in output and not value in duplicates:
output[value] = x
else:
y = 0
x = x + 1
What you get is:
{'e': 2}
It is a single loop but work involved is probably comparable to running two nested loops.

Insert frequency of elements in list without using temporary list

Input : ['a', 'a', 'b', 'b', 'b', 'b', 'c', 'c', 'd', 'a']
Output: ['a', 'a', 2, 'b', 'b', 'b', 'b', 4, 'c', 'c', 2, 'd', 1, 'a',1]
What can be the best way to get the output as above without using temporary list in python? I am trying using while loop but the frequency of last element is missed out as the loop gets terminated.
l = ['a', 'a', 'b', 'b', 'b', 'b', 'c', 'c', 'd', 'a']
i = 0
idx = 0
length = len(l)
while i < length:
if l[i - 1] != l[i]:
l.insert(i, (i - idx))
length += 1
idx = i + 1
i += 2
else:
i += 1
print(l)
The original code has a crucial bug: since i=0 in the first iteration, the condition is checking if the first and last items of the list match, which I don't think is what's intended.
Here's the corrected code.
l = ['a', 'a', 'b', 'b', 'b', 'b', 'c', 'c', 'd', 'a']
i = 0 # dynamic index
n = 1 # length of sequence
length = len(l)
while i < length:
if i == length-1 or l[i] != l[i+1]:
l.insert(i+1, n)
length += 1
n = 1
i += 2
else:
n += 1
i += 1
print(l)
Instead, I'm comparing to the subsequent item. This of course would be a problem for the last item, but this condition is caught first by the check i == length-1.
Just add the last frequency at the end, so after the loop:
l.append(i - idx)
But make sure your input list isn't empty!
itertools is your friend:
import itertools
l = ['a', 'a', 'b', 'b', 'b', 'b', 'c', 'c', 'd', 'a']
list(itertools.chain(*[(k, len(list(g))) for k,g in itertools.groupby(l)]))
['a', 2, 'b', 4, 'c', 2, 'd', 1, 'a', 1]
.. but fixing your own code is more satisfying ;)
Edit: So the problem definition changed again? Python 3.9+ needed:
list(itertools.chain(*[(*(gg:=list(g)), len(gg)) for k,g in itertools.groupby(l)]))
['a', 'a', 2, 'b', 'b', 'b', 'b', 4, 'c', 'c', 2, 'd', 1, 'a', 1]
Another approach could be:
from functools import reduce
breaks = [0] + [i for i in range(1, len(l)) if l[i] != l[i-1]] + [len(l)]
reduce(lambda x, y: x + y, [l[breaks[i-1]:breaks[i]] + [breaks[i] - breaks[i-1]] for i in range(1, len(breaks))])
OUTPUT
['a', 'a', 2, 'b', 'b', 'b', 'b', 4, 'c', 'c', 2, 'd', 1, 'a', 1]
Alternatively, if you would like to do everything inside the loop, this could be another approach
l = ['a', 'a', 'b', 'b', 'b', 'b', 'c', 'c', 'd', 'a']
l = list(reversed(l))
e = l.pop()
res = []
res.append(e)
i = 1
while len(l) > 0:
new_e = l.pop()
if new_e == e:
i += 1
else:
res.append(i)
i = 1
e = new_e
res.append(new_e)
res.append(i)
print(res)
OUTPUT
['a', 'a', 2, 'b', 'b', 'b', 'b', 4, 'c', 'c', 2, 'd', 1, 'a', 1]
You can insert the repetition counts as you go if you are careful with resetting the counter so that the insertions in the list don't skew your end of streak detection:
L = ['a', 'a','b', 'b', 'b', 'b', 'c', 'c', 'd', 'a']
count = 0 # current number of reperitions
for i,c in enumerate(L): # go through list's indexes and items
if count and L[i-1] != c: # end of non-empty streak
L.insert(i,count) # insert count
count = 0 # and reset it
else:
count += 1 # count repetitions
L.append(count) # count for last streak
print(L)
['a', 'a', 2, 'b', 'b', 'b', 'b', 4, 'c', 'c', 2, 'd', 1, 'a', 1]
When the count is inserted, the next iteration will be on the same item (c) that caused the end of streak. Because we reset the count to zero after insertion, that item will go to the else: part and be counted as 1 (0+1) thus properly starting a new streak.
Another way to approach this is to track the index of the first item of a streak and insert the difference between indexes when detecting a change. Here too, you need to be careful with the effect of inserting into the list that you are iterating over. The index of items that cause a break in the streak will be 1 more than their original index after inserting the count:
L = ['a', 'a','b', 'b', 'b', 'b', 'c', 'c', 'd', 'a']
s = 0 # index of start of streak
for i,c in enumerate(L): # go through list's indexes and items
if L[s] != c: # end of streak
L.insert(i,i-s) # insert count
s = i+1 # track start of next streak
L.append(len(L)-s) # count for last streak
print(L)
['a', 'a', 2, 'b', 'b', 'b', 'b', 4, 'c', 'c', 2, 'd', 1, 'a', 1]

how to match value of 1st list with the indexes of 2nd list in python

I am trying to match the value of 1st list with the indexes of 2nd list and if value and indexes match then in the result it will produce matches of the index value.
A = [0,2,0,5,2,1,1] # value of list 1
B = ['A', 'B', 'C', 'D'] # value of 2nd list
the result should be,
res = ['A', 'C', 'A', 'C', 'B', 'B']
Try the code below,
A = [0, 2, 0, 5, 2, 1, 1] # values of list 1
B = ['A', 'B', 'C', 'D'] # values of list 2
res = [B[i] for i in A if i<len(B)]
res
Output
['A', 'C', 'A', 'C', 'B', 'B']
A = [0,2,0,5,2,1,1] # value of list 1
B = ["A", "B", "C", "D"] # value of 2nd list
new_list = []
for val in A:
if val < len(B) - 1:
new_list.append(B[val])
print(new_list)
Results:
['A', 'C', 'A', 'C', 'B', 'B']
A = [0,2,0,5,2,1,1] # value of list 1
B = ['A', 'B', 'C', 'D']
size_minus_1 = len(B) - 1
result = []
for i in A:
if i < size_minus_1:
result.append(B[i])
print(result)

Python: Find the list element that is contained least times in another list

So I have a list of possible elments:
elems = ['a', 'b', 'c', 'd']
Then I have another list of random items chosen from the first list.
Something like (for example):
items = ['a', 'a', 'c', 'a', 'c', 'd']
What is the Pythonic way to find out which element in elems is contained least often in items?
In this example it is 'b', because that's not contained in items at all.
Short
print(min(elems, key=items.count)) # b
Relatively short and efficient (only use it if you're sure there're no duplicates in elems)
from collections import Counter
c = Counter(items + elems)
print(c.most_common()[-1][0]) # b
Just efficient
d = {x: 0 for x in elems}
for x in items:
d[x] += 1
print(min(d, key=d.get)) # b
Another "just efficient"
from collections import defaultdict
d = defaultdict(int)
for x in items:
d[x] += 1
print(min(elems, key=d.__getitem__))
# or print(min(elems, key=lambda x: d[x])) - gives same result
>>> from collections import Counter
>>> elems = ['a', 'b', 'c', 'd']
>>> items = ['a', 'a', 'c', 'a', 'c', 'd']
>>> c = Counter(dict.fromkeys(elems, 0))
>>> c.update(Counter(items))
>>> c
Counter({'a': 3, 'c': 2, 'd': 1, 'b': 0})
>>> min(c, key=c.get)
'b'
The fastest way is to make a dictionary with counts in it:
aDict = { k:0 for k in elems }
for x in items:
if x not in aDict: aDict[x] = 0
aDict[x] += 1
print min(aDict, key=aDict.get)

Python dictionary comprehension example

I am trying to learn Python dictionary comprehension, and I think it is possible to do in one line what the following functions do. I wasn't able to make the n+1 as in the first or avoid using range() as in the second.
Is it possible to use a counter that automatically increments during the comprehension, as in test1()?
def test1():
l = ['a', 'b', 'c', 'd']
d = {}
n = 1
for i in l:
d[i] = n
n = n + 1
return d
def test2():
l = ['a', 'b', 'c', 'd']
d = {}
for n in range(len(l)):
d[l[n]] = n + 1
return d
It's quite simple using the enumerate function:
>>> L = ['a', 'b', 'c', 'd']
>>> {letter: i for i,letter in enumerate(L, start=1)}
{'a': 1, 'c': 3, 'b': 2, 'd': 4}
Note that, if you wanted the inverse mapping, i.e. mapping 1 to a, 2 to b etc, you could simply do:
>>> dict(enumerate(L, start=1))
{1: 'a', 2: 'b', 3: 'c', 4: 'd'}
This works
>>> l = ['a', 'b', 'c', 'd']
>>> { x:(y+1) for (x,y) in zip(l, range(len(l))) }
{'a': 1, 'c': 3, 'b': 2, 'd': 4}

Categories

Resources