I want to make a code which counts all triplets in a sequence. I've read a plenty of posts so far, but none of them could help me.
This is my code:
def cnt(seq):
mydict = {}
if len(seq) % 3 == 0:
a = [x for x in seq]
for i in range(len(seq)//3):
b = ''.join(a[(0+3*i):(3+3*i)])
for base1 in ['A', 'T', 'G', 'C']:
for base2 in ['A', 'T', 'G', 'C']:
for base3 in ['A', 'T', 'G', 'C']:
triplet = base1 + base2 + base3
if b == triplet:
mydict[b] = 1
for key in sorted(mydict):
print("%s: %s" % (key, mydict[key]))
else:
print("Error")
Does Biopython provide a function to solve this problem?
EDIT:
Note that, for instance, in the sequence 'ATGAAG', 'TGA' or 'GAA' are not "valid" triplets, only 'ATG' and 'AAG', because in biology and bioinformatics, we read it 'ATG' and 'AAG', thats the information we need to translate it or whatever else.
You can imagine it as a sequence of words, for example "Hello world". The way we read it is "Hello" and "world", not "Hello", "ello ", "llo w",...
It took me a while to understand that you do not want to count the number of codons but the frequency of each codon. Your title is a bit misleading in this respect. Anyway, you can employ collections.Counter for your task:
from collections import Counter
def cnt(seq):
if len(seq) % 3 == 0:
#split list into codons of three
codons = [seq[i:i+3] for i in range(0, len(seq), 3)]
#create Counter dictionary for it
codon_freq = Counter(codons)
#determine number of codons, should be len(seq) // 3
n = sum(codon_freq.values())
#print out all entries in an appealing form
for key in sorted(codon_freq):
print("{}: {} = {:5.2f}%".format(key, codon_freq[key], codon_freq[key] * 100 / n))
#or just the dictionary
#print(codon_freq)
else:
print("Error")
seq = "ATCGCAGAAATCCGCAGAATC"
cnt(seq)
Sample output:
AGA: 1 = 14.29%
ATC: 3 = 42.86%
CGC: 1 = 14.29%
GAA: 1 = 14.29%
GCA: 1 = 14.29%
You can use clever techniques, as suggested in the other answers, but I will build a solution starting from your code, which is almost working: Your problem is that every time you do mydict[b] = 1, you reset the count of b to 1.
A minimal fix
You could solve this by testing if the key is present, if not, create the entry in the dict, then increment the value, but there are more convenient tools in python.
A minimal change to your code would be to use a defaultdict(int) instead of a dict. Whenever a new key is encountered, it is assumed to have the associated default value for an int: 0. So you can increment the value instead of resetting:
from collections import defaultdict
def cnt(seq):
# instanciate a defaultdict that creates ints when necessary
mydict = defaultdict(int)
if len(seq) % 3 == 0:
a = [x for x in seq]
for i in range(len(seq)//3):
b = ''.join(a[(0+3*i):(3+3*i)])
for base1 in ['A', 'T', 'G', 'C']:
for base2 in ['A', 'T', 'G', 'C']:
for base3 in ['A', 'T', 'G', 'C']:
triplet = base1 + base2 + base3
if b == triplet:
# increment the existing count (or the default 0 value)
mydict[b] += 1
for key in sorted(mydict):
print("%s: %s" % (key, mydict[key]))
else:
print("Error")
It works as desired:
cnt("ACTGGCACT")
ACT: 2
GGC: 1
Some possible improvements
Now let's try to improve your code a bit.
First, as I wrote in the comments, let's avoid the un-necessary conversion of your sequence to a list, and use a better variable name for the currently counted codon:
from collections import defaultdict
def cnt(seq):
mydict = defaultdict(int)
if len(seq) % 3 == 0:
a = [x for x in seq]
for i in range(len(seq)//3):
codon = seq[(0+3*i):(3+3*i)]
for base1 in ['A', 'T', 'G', 'C']:
for base2 in ['A', 'T', 'G', 'C']:
for base3 in ['A', 'T', 'G', 'C']:
triplet = base1 + base2 + base3
if codon == triplet:
mydict[codon] += 1
for key in sorted(mydict):
print("%s: %s" % (key, mydict[key]))
else:
print("Error")
Now lets simplify the nested loop part, trying all possible codons, by generating in advance the set of possible codons:
from collections import defaultdict
from itertools import product
codons = {
"".join((base1, base2, base3))
for (base1, base2, base3) in product("ACGT", "ACGT", "ACGT")}
def cnt(seq):
mydict = defaultdict(int)
if len(seq) % 3 == 0:
a = [x for x in seq]
for i in range(len(seq)//3):
codon = seq[(0+3*i):(3+3*i)]
if codon in codons:
mydict[codon] += 1
for key in sorted(mydict):
print("%s: %s" % (key, mydict[key]))
else:
print("Error")
Now, your code simply ignores the triplets that are not valid codons. Maybe you should instead issue a warning:
from collections import defaultdict
from itertools import product
codons = {
"".join((base1, base2, base3))
for (base1, base2, base3) in product("ACGT", "ACGT", "ACGT")}
def cnt(seq):
mydict = defaultdict(int)
if len(seq) % 3 == 0:
a = [x for x in seq]
for i in range(len(seq)//3):
codon = seq[(0+3*i):(3+3*i)]
# We count even invalid triplets
mydict[codon] += 1
# We display counts only for valid triplets
for codon in sorted(codons):
print("%s: %s" % (codon, mydict[codon]))
# We compute the set of invalid triplets:
# the keys that are not codons.
invalid = mydict.keys() - codons
# An empty set has value False in a test.
# We issue a warning if the set is not empty.
if invalid:
print("Warning! There are invalid triplets:")
print(", ".join(sorted(invalid)))
else:
print("Error")
A more fancy solution
Now a more fancy solution, using cytoolz (probably needs to be installed because it is not part of usual python distributions: pip3 install cytoolz, if you are using pip):
from collections import Counter
from itertools import product, repeat
from cytoolz import groupby, keymap, partition
# To make strings out of lists of strings
CAT = "".join
# The star "extracts" the elements from the result of repeat,
# so that product has 3 arguments, and not a single one
codons = {CAT(bases) for bases in product(*repeat("ACGT", 3))}
def cnt(seq):
# keymap(CAT, ...) transforms the keys (that are tuples of letters)
# into strings
# if len(seq) is not a multiple of 3, pad="-" will append "-"
# to complete the last triplet (which will be an invalid one)
codon_counts = keymap(CAT, Counter(partition(3, seq, pad="-")))
# separate encountered codons into valids and invalids
codons_by_validity = groupby(codons.__contains__, codon_counts.keys())
# get allows to provide a default value,
# in case one of the categories is not present
valids = codons_by_validity.get(True, [])
invalids = codons_by_validity.get(False, [])
# We display counts only for valid triplets
for codon in sorted(valids):
print("%s: %s" % (codon, codon_counts[codon]))
# We issue a warning if there are invalid codons.
if invalids:
print("Warning! There are invalid triplets:")
print(", ".join(sorted(invalids)))
Hope this helps.
You could do something like this:
from itertools import product
seq = 'ATGATG'
all_triplets = [seq[i:i+3] for i in range(len(seq)) if i <= len(seq)-3]
# this gives ['ATG', 'TGA', 'GAT', 'ATG']
# add more valid_triplets here
valid_triplets = ['ATG']
len([(i, j) for i, j in product(valid_triplets, all_triplets) if i==j])
Output:
2
It is unclear what output is expected. Here we use one of many grouping functions from more_itertools to build adjacent triplets or "codons".
import more_itertools as mit
seq = "ATGATG"
codons = ["".join(w) for w in mit.grouper(3, seq)]
codons
# ['ATG', 'ATG']
Count the number of codons by calling len.
len(triplets)
# 2
For more detailed analysis, consider splitting the problem into smaller functions that (1) extract codons and (2) compute occurrences.
Code
import collections as ct
def split_codons(seq):
"Return codons from a sequence; raise for bad sequences."
for w in mit.windowed(seq, n=3, step=3, fillvalue=""):
part = "".join(w)
if len(part) < 3:
raise ValueError(f"Sequence not divisible by 3. Got extra '{part}'.")
yield part
def count_codons(codons):
"""Return dictionary of codon occurences."""
dd = ct.defaultdict(int)
for i, c in enumerate(codons, 1):
dd[c] += 1
return {k: (v, 100 * v/i) for k, v in dd.items()}
Demo
>>> seq = "ATCGCAGAAATCCGCAGAATC"
>>> bad_seq = "ATCGCAGAAATCCGCAGAATCA"
>>> list(split_codons(seq))
['ATC', 'GCA', 'GAA', 'ATC', 'CGC', 'AGA', 'ATC']
>>> list(split_codons(bad_seq))
ValueError: Sequence not divisible by 3. Got extra 'A'.
>>> count_codons(split_codons(seq))
{'ATC': (3, 42.857142857142854),
'GCA': (1, 14.285714285714286),
'GAA': (1, 14.285714285714286),
'CGC': (1, 14.285714285714286),
'AGA': (1, 14.285714285714286)}
Related
I have 2 strings:
s1 = "Are they here"
s2 = "yes, they are here"
I want to create a dictionary (e) that has as key the maximum number of times each shared element is present in the string that contains it the most and as value the element (i.e. the "y" is contained once in s1 and twice in s2. Therefore I want a dict that goes:
e = {2:y} # and so on
To describe my code, I thought of creating a list (c) with all the shared elements:
c = ['r', 'e', 't', 'h', 'e', 'y', 'h', 'e', 'r', 'e', 'y', 'e', 't', 'h', 'e', 'y', 'r', 'e', 'h', 'e', 'r', 'e']
then switch it to a set to eliminate duplicates and using them as iterators:
d = {'h', 'y', 'r', 't', 'e'}
Ultimately I thought of using a for loop to fill the dict (e) by iterating every element in d and reporting the maximum times it was present.
Here's my full code
please note that I don't want to use any library.
Also note that the code works with dict comprehension:
def mix(s1, s2):
c = [] # create a var to be filled with all shared chars
for i in s1:
if i != " ":
if i in s2:
c.append(i)
for i in s2:
if i != " ":
if i in s1:
c.append(i) # end of 1st process
d = set(c) # remove duplicates
e = {} # create a dict to align counting and relative char
for i in d:
a = s1.count(i)
b = s2.count(i)
m = max(a, b)
e[m] = i
# z = {i:max(s1.count(i), s2.count(i)) for i in d} this is what actually works
return e # z works instead
The issue I get is that the for loop stops after 3 iteration.
Edit: I see that Rakshith B S has made a better version of my comment, refer to thiers.
I'll start by saying I'm an amateur, and the following can absolutely be simplified.
First, decide about capitalization, A != a, use str.lower or str.upper.
Second, switching the dictionary to be {'letter':count} would make everything easier.
Then, it would most likely be easier to create two dictionaries to count the unique letters in each string.
d1 = {}
s1 = s1.lower()
for letter in s1:
if letter != " ":
if letter in d1:
d1[letter] += 1 # if in dict, add one to count
else:
d1[letter] = 1 #add new letter to dict
d2 = {}
s2 = s2.lower()
for letter in s2:
if letter != " ":
if letter in d2:
d2[letter] += 1 # if in dict, add one to count
else:
d2[letter] = 1 #add new letter to dict
That should make two dictionaries, for loop it to compare and append the max values (this part can be made more efficiently).
d3 = {}
for let in d1:
if let not in d2:
d3[let] = d1.get(let)
if let in d2:
if d1[let] >= d2[let]:
d3[let] = d1.get(let)
else:
d3[let] = d2.get(let)
for let in d2:
if let not in d1:
d3[let] = d2.get(let)
del d3[',']
This should at least get you on the right track.
I have just realized that sets can obviously have UNIQUE values as keys, so, of course my code will be display "partially".
When it gets the same key, it overwrites it.
So using the element as key will work and the for loop can be like so:
for i in d:
a = s1.count(i)
b = s2.count(i)
m = max(a, b)
e[i] = m
def mix(s1, s2):
dict1 = dict()
dict2 = dict()
for i in s1:
if i != " " and i != ",":
if i in dict1:
dict1[i] += 1
else:
dict1[i] = 1
for i in s2:
if i != " " and i != ",":
if i in dict2:
dict2[i] += 1
else:
dict2[i] = 1
# print(dict1)
# print(dict2)
for key, value in dict2.items():
if key in dict1:
# print(f' check {key}, {value}')
if value >= dict1[key]:
dict1[key] = value
else:
dict1[key] = value
#print(f' create {key}, {value}')
return {v: k for k, v in dict1.items()} #inverted
s1 = "eeeeaaabbbcccc"
s2 = "eeeeeaaa"
print(mix(s1, s2))
Why create a merged list and recheck against the counter set
Here I've compared values from dict1( which is s1) and dict2(again s2) and overwritten dict1 if the value is high else if its not found I've assigned it as the highest
OUTPUTS:
{'e': 5, 'a': 3, 'b': 3, 'c': 4}
{5: 'e', 3: 'b', 4: 'c'}
This might end up overwriting as 'a' is overwritten by 'b'
I would like to generate a list of combinations. I will try to simplify my problem to make it understandable.
We have 3 variables :
x : number of letters
k : number of groups
n : number of letters per group
I would like to generate using python a list of every possible combinations, without any duplicate knowing that : i don't care about the order of the groups and the order of the letters within a group.
As an example, with x = 4, k = 2, n = 2 :
# we start with 4 letters, we want to make 2 groups of 2 letters
letters = ['A','B','C','D']
# here would be a code that generate the list
# Here is the result that is very simple, only 3 combinations exist.
combos = [ ['AB', 'CD'], ['AC', 'BD'], ['AD', 'BC'] ]
Since I don't care about the order of or within the groups, and letters within a group, ['AB', 'CD'] and ['DC', 'BA'] is a duplicate.
This is a simplification of my real problem, which has those values : x = 12, k = 4, n = 3. I tried to use some functions from itertools, but with that many letters my computer freezes because it's too many combinations.
Another way of seeing the problem : you have 12 players, you want to make 4 teams of 3 players. What are all the possibilities ?
Could anyone help me to find an optimized solution to generate this list?
There will certainly be more sophisticated/efficient ways of doing this, but here's an approach that works in a reasonable amount of time for your example and should be easy enough to adapt for other cases.
It generates unique teams and unique combinations thereof, as per your specifications.
from itertools import combinations
# this assumes that team_size * team_num == len(players) is a given
team_size = 3
team_num = 4
players = list('ABCDEFGHIJKL')
unique_teams = [set(c) for c in combinations(players, team_size)]
def duplicate_player(combo):
"""Returns True if a player occurs in more than one team"""
return len(set.union(*combo)) < len(players)
result = (combo for combo in combinations(unique_teams, team_num) if not duplicate_player(combo))
result is a generator that can be iterated or turned into a list with list(result). On kaggle.com, it takes a minute or so to generate the whole list of all possible combinations (a total of 15400, in line with the computations by #beaker and #John Coleman in the comments). The teams are tuples of sets that look like this:
[({'A', 'B', 'C'}, {'D', 'E', 'F'}, {'G', 'H', 'I'}, {'J', 'K', 'L'}),
({'A', 'B', 'C'}, {'D', 'E', 'F'}, {'G', 'H', 'J'}, {'I', 'K', 'L'}),
({'A', 'B', 'C'}, {'D', 'E', 'F'}, {'G', 'H', 'K'}, {'I', 'J', 'L'}),
...
]
If you want, you can cast them into strings by calling ''.join() on each of them.
Another solution (players are numbered 0, 1, ...):
import itertools
def equipartitions(base_count: int, group_size: int):
if base_count % group_size != 0:
raise ValueError("group_count must divide base_count")
return set(_equipartitions(frozenset(range(base_count)), group_size))
def _equipartitions(base_set: frozenset, group_size: int):
if not base_set:
yield frozenset()
for combo in itertools.combinations(base_set, group_size):
for rest in _equipartitions(base_set.difference(frozenset(combo)), group_size):
yield frozenset({frozenset(combo), *rest})
all_combinations = [
[tuple(team) for team in combo]
for combo in equipartitions(12, 3)
]
print(all_combinations)
print(len(all_combinations))
And another:
import itertools
from typing import Iterable
def equipartitions(players: Iterable, team_size: int):
if len(players) % team_size != 0:
raise ValueError("group_count must divide base_count")
return _equipartitions(set(players), team_size)
def _equipartitions(players: set, team_size: int):
if not players:
yield []
return
first_player, *other_players = players
for other_team_members in itertools.combinations(other_players, team_size-1):
first_team = {first_player, *other_team_members}
for other_teams in _equipartitions(set(other_players) - set(first_team), team_size):
yield [first_team, *other_teams]
all_combinations = [
{''.join(sorted(team)) for team in combo} for combo in equipartitions(players='ABCDEFGHIJKL', team_size=3)
]
print(all_combinations)
print(len(all_combinations))
Firstly, you can use a list comprehension to give you all of the possible combinations (regardless of the duplicates):
comb = [(a,b) for a in letters for b in letters if a != b]
And, afterwards, you can use the sorted function to sort the tuples. After that, to remove the duplicates, you can convert all of the items to a set and then back to a list.
var = [tuple(sorted(sub)) for sub in comb]
var = list(set(var))
You could use the list comprehension approach, which has a time complexity of O(n*n-1), or you could use a more verbose way, but with a slightly better time complexity of O(n^2-n)/2:
comb = []
for first_letter_idx, _ in enumerate(letters):
for sec_letter_idx in range(first_letter_idx + 1, len(letters)):
comb.append(letters[first_letter_idx] + letters[sec_letter_idx])
print(comb)
comb2 = []
for first_letter_idx, _ in enumerate(comb):
for sec_letter_idx in range(first_letter_idx + 1, len(comb)):
if (comb[first_letter_idx][0] not in comb[sec_letter_idx]
and comb[first_letter_idx][1] not in comb[sec_letter_idx]):
comb2.append([comb[first_letter_idx], comb[sec_letter_idx]])
print(comb2)
This algorithm needs more work to handle dynamic inputs. Maybe with recursion.
Use combination from itertools
from itertools import combinations
x = list(combinations(['A','B','C','D'],2))
t = []
for i in (x):
t.append(i[0]+i[1]) # concatenating the strings and adding in a list
g = []
for i in range(0,len(t),2):
for j in range(i+1,len(t)):
g.append([t[i],t[j]])
break
print(g)
I am doing text recognition with pytesseract. Sometimes text is not properly extracted.
For example, "DDR4" might be interpreted as "ODR4"
Hence I have a dictionary which record all possible escape, and code to detect how many char needed to be replace and its index for example,
my_dictionary= {
'D': ['O', '0'],
'O': 'D',
'0': 'D'
}
user_input = "DDR4"
char_to_replace = 0
char_index = []
for index, val in enumerate(user_input):
if val in my_dictionary:
char_to_replace += 1
char_index.append(index)
In this case, how could I produce a list of all possible combination, for example
D0R4, DOR4, 00R4, 0OR4, OOR4, O0R4, 0DR4, ODR4
Appreciate for any inputs
This is what i have come up with, pretty ugly code and im sure it can be done much easier with itertools, but what the heck - i hope it helps:
my_dictionary = {
'D': ['O', '0'],
'O': 'D',
'0': 'D'
}
user_input = "DDR4"
def replace_char(variable=None, replace_index=None, replace_with=None):
"""Supplementary function"""
return variable[:replace_index] + replace_with + variable[replace_index+1:]
# create maximum required iterations (max len of list in dict)
number_of_iterations_required = max([len(f) for f in my_dictionary.values()])
# list for preliminary word combinations
baseword_combinations = [user_input]
for key, val in my_dictionary.items():
for idx, char in enumerate(user_input):
if char == key:
for v in val:
baseword_combinations.append(replace_char(variable=user_input, replace_index=idx, replace_with=v))
# list for final returns, again append initial input
possible_combinations = [user_input]
for word in baseword_combinations:
for idx, char in enumerate(word):
for key, val in my_dictionary.items():
for v in val:
if char == key:
possible_combinations.append(replace_char(variable=word, replace_index=idx, replace_with=v))
if char == val:
possible_combinations.append(replace_char(variable=word, replace_index=idx, replace_with=key))
# get rid of duplicates, print result
print(list(set(possible_combinations)))
Result:
['OOR4', '00R4', 'ODR4', 'D0R4', '0DR4', 'DDR4', '0OR4', 'O0R4', 'DOR4']
Edit
The part with the number_of_iterations_required was unused in my above code, also i reworked it a little to use list comprehension - which makes it much less understandable, but much shorter, so here you go:
my_dictionary = {
'D': ['O', '0'],
'O': 'D',
'0': 'D'
}
user_input = "DDR4"
def replace_char(variable=None, replace_index=None, replace_with=None):
"""Supplementary function"""
return variable[:replace_index] + replace_with + variable[replace_index+1:]
# list for preliminary word combinations
base = [user_input]
base.extend([replace_char(user_input, idx, v) for key, val in my_dictionary.items()
for idx, char in enumerate(user_input) for v in val if char == key])
# list for final results
final_results = [user_input]
final_results.extend([replace_char(word, idx, key) for word in base for idx, char in enumerate(word)
for key, val in my_dictionary.items() for v in val if char == val])
result = list(set(final_results))
print(result)
I'm try to solve this problem in this way,hope that can help you:
import itertools as it
my_list= ['D','O','0']
result = []
input = 'DDR4'
# 'DDR4' -> '**R4'
for i in range(len(my_list)):
if input[i] in my_list:
input = input.replace(input[i],'*')
for e in it.product('DO0',repeat=input.count('*')):
a = list(e)
input_copy = input
for i in a:
# print(i)
input_copy = input_copy.replace('*',i,1)
result.append(input_copy)
result:
['DDR4', 'DOR4', 'D0R4', 'ODR4', 'OOR4', 'O0R4', '0DR4', '0OR4', '00R4']
I need to display the letter and it's count if it has maximum count in a name. However, I have two letters (n:2, u:2) with equal count in a name, how to print both the letters with their count as they have maximum and equal count. I could only do for one letter.
name = 'Annuu'
name = name.lower()
names = set(name)
highest = 0
p = ''
for i in names:
if name.count(i) > highest:
highest = name.count(i)
p = i
print(f"{p} {highest}")
You can use Counter object to find the count.
Then find the maximum count to filter the letters.
from collections import Counter
name = "annuu"
count_dict = Counter(name)
max_count = max(count_dict.values())
for letter, count in count_dict.items():
if count == max_count:
print(letter, count)
This is without using any imports:
name = "Onnuu"
name = name.lower()
names = set(name)
print(names)
l = []
for i in names:
l.append((name.count(i),i))
l.sort(reverse = True)
for i in l:
if l[0][0] == i[0]:
print(i[1])
Store the values in dict and find the max_frequency
name = 'Annuu'
name = name.lower()
d={}
for i in name:
d[i]=d.get(i,0)+1
max_freq = max((d.values()))
for k,v in sorted(d.items(),key=lambda (x,y):(y,x), reverse=True):
if v == max_freq:
print(k,v)
else:
break
The following code works and produces the output:
The maximum characters and their respective count is as follows:
n 2
u 2
name = 'Annuu'
name = name.lower()
names = set(name)
name_count_dict = {} # Use dictionary because of easy mapping between character and respective max
for current_char in names:
# First save the counts in a dictionary
name_count_dict[current_char] = name.count(current_char)
# Use the max function to find the max (only one max at this point but will find the remaining in the lines below)
max_char = max(name_count_dict, key=name_count_dict.get)
max_value = name_count_dict[max_char]
# Find all other characters which match the max-value, i.e. all maximums
array_of_all_maxes = [k for k, v in name_count_dict.items() if v == max(name_count_dict.values())]
print("The maximum characters and their respective count is as follows:")
for max_chars in array_of_all_maxes:
print(f"{max_chars} {max_value}")
I think this would be a simple solution for the problem without using any external package like collections.
Here, I written 2 test cases and repeated the same lines of code. You haven't to do like that. What you have more than 2, 3 etc. So it's better to write any other function to test the code by passing different values to it.
def get_count_and_highest (name):
name = name.lower()
names = set(name)
highest = 0
d = {}
for ch in names:
count = name.count(ch)
if count >= highest:
highest = count
if highest in d:
d[highest].append(ch)
else:
d[highest] = [ch]
return highest, d
#Test case 1
highest, d = get_count_and_highest("Annuu")
l = d.get(highest, []) # in case if dictionary d is empty then l will be an empty list
output = {ch: highest for ch in l}
print(highest) # 2
print(d) # {1: ['a'], 2: ['n', 'u']}
print(l) # ['n', 'u']
print (output) # {'u': 2, 'n': 2}
# Test case 2
highest, d = get_count_and_highest("Babylon python new one")
l = d.get(highest, []) # in case if dictionary d is empty then l will be an empty list
output = {ch: highest for ch in l}
print(highest) # 4
print(d) # {3: ['o', 'p', 'l', 'b', 't', ' ', 'h', 'y', 'w'], 4: ['n', 'e', 'a']}
print(l) # ['n', 'e', 'a']
print (output) # {'n': 4, 'e': 4, 'a': 4}
An example with collections.Counter
from collections import Counter
name = 'Annuu'
c = Counter(name.lower())
mc = c.most_common()
max_count = mc[0][1]
for i, x in enumerate(mc):
if x[1] < max_count:
break
print(mc[:i+1]) # [('n', 2), ('u', 2)]
I'm looking for an elegant 'Pythonic' way to loop through input in groups of a certain size. But the size of the groups vary and you don't know the size of a particular group until you start parsing.
Actually I only care about groups of two sizes. I have a large sequence of input which come in groups of mostly size X but occasionally size Y. For this example lets say X=3 and Y=4 (my actual problem is X=7 and Y=8). I don't know until mid-way through the group of elements if it's going to be a group of size X or of size Y elements.
In my problem I'm dealing with lines of input but to simplify I'll illustrate with characters.
So if it's a group of a particular size I know I'll always get input in the same sequence. So for example if it's a size X group I'll be getting elements of type [a,a,b] but if it's a size Y group I'll be getting elements of type [a,a,c,b]. f it's something of type 'a' I'll want to process it in a certain way and 'b' another etc.
Obviously I have to test an element at some point to determine if it's of type one group or the other. As demonstrated above I cannot just check the type of every element because there may be two of the same in sequence. Also the groups may be the same pattern at the start and only differ near the end. In this example the earliest I can check if I'm in a size X or size Y group is by testing the 3rd element (to see if it's of type 'c' or type 'b').
I have a solution with a for loop with an exposed index and two extra variables, but I'm wondering if there is a more elegant solution.
Here is my code. I've put pass statements in place of where I would do the actual parsing depending on what type it is:
counter = 0
group = 3
for index, x in enumerate("aabaabaacbaabaacbaabaab"):
column = index - counter;
print(str(index) + ", " + x + ", " + str(column))
if column == 0:
pass
elif column == 1:
pass
elif column == 2:
if x == 'c':
pass
elif x == 'd':
group = 4
elif column == 3:
pass
if column + 1 == group:
counter += group
group = 3
In the code example the input stream is aabaabaacbaabaacbaabaab so that is groups of:
aab (3)
aab (3)
aacb (4)
aab (3)
aacb (4)
aab (3)
aab (3)
I would use a generator that collect these groups and determines the size for each, and then ultimately yields each group:
def getGroups (iterable):
group = []
for item in iterable:
group.append(item)
if len(group) == 3 and group[2] == 'c':
yield group
group = []
elif len(group) == 4 and group[2] == 'd':
yield group
group = []
for group in getGroups('abcabcabdcabcabdcabcabc'):
print(group)
['a', 'b', 'c']
['a', 'b', 'c']
['a', 'b', 'd', 'c']
['a', 'b', 'c']
['a', 'b', 'd', 'c']
['a', 'b', 'c']
['a', 'b', 'c']
Looks like you need a simple automata with backtracking, for example:
def parse(tokens, patterns):
backtrack = False
i = 0
while tokens:
head, tail = tokens[:i+1], tokens[i+1:]
candidates = [p for p in patterns if p.startswith(head)]
match = any(p == head for p in candidates)
if match and (backtrack or len(candidates) == 1 or not tail):
yield head
tokens = tail
backtrack = False
i = 0
elif not candidates:
if not i or backtrack:
raise SyntaxError, head
else:
backtrack = True
i -= 1
elif tail:
i += 1
else:
raise SyntaxError, head
tokens = 'aabaabcaabaabcxaabxxyzaabx'
patterns = ['aab', 'aabc', 'aabcx', 'x', 'xyz']
for p in parse(tokens, patterns):
print p
With your particular example, you could use a regex:
>>> s="aabaabaacbaabaacbaabaab"
>>> re.findall(r'aac?b', s)
['aab', 'aab', 'aacb', 'aab', 'aacb', 'aab', 'aab']
If you want a parser that does the same thing, you can do:
def parser(string, patterns):
patterns=sorted(patterns, key=len, reverse=True)
i=0
error=False
while i<len(string) and not error:
for pat in patterns:
j=len(pat)
if string[i:i+j]==pat:
i+=j
yield pat
break
else:
error=True
if error or i<len(string):
raise SyntaxWarning, "Did not match the entire string"
>>> list(parser(s, ['aab', 'aacb']))
['aab', 'aab', 'aacb', 'aab', 'aacb', 'aab', 'aab']