This question is part of a series solving the Rosalind challenges. For the previous question in this series, see Counting nucleobases in a nucleotide c. The repository with all my up-to-date solutions so far can be found here.
I've been wondering for a while how awful it would look if I created an OO version of the first Rosalind challenge: DNA.
Turns out it isn't totally awful, so why not put it up for review?
I've considered adding more input sanitation this time, but there's not much that can go wrong. The sequence is derived from the argument provided, so it can't be empty or ArgParse
will yell at you. I've forced it to capitalize every character if for some reason the user forgets to do so and I explicitly mention it's only supposed to work for DNA sequences (a version detecting whether a nucleobase is DNA/RNA or something different altogether would be a nice class for in a library, perhaps in a later version).
I've also considered (ab)using Biopython's Seq
for this solution, but it would be overkill at this moment. Again, might be added later.
As said, it looks better than I expected, but it still looks like the method behind writing it is too chaotic. It could be more logically designed and probably more idiomatic as well. Argument handling, docstrings and internal datastructures are primary concern. Would it make sense to put argument handling in it's own class?
Problem: DNA
A string is simply an ordered collection of symbols selected from some alphabet and formed into a word; the length of a string is the number of symbols that it contains. An example of a length 21 DNA string (whose alphabet contains the symbols 'A', 'C', 'G', and 'T') is "ATGCTTCAGAAAGGTCTTACG."
Given:
A DNA string
s
of length at most 1000 nt.Return:
Four integers (separated by spaces) counting the respective number of times that the symbols 'A', 'C', 'G', and 'T' occur in
s
.Sample Dataset:
AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC
Sample Output:
20 12 17 21
Actual Dataset:
GTAGTATATCCACATACGAATTTATGGACGCTATCGCGACGCTCTGAACGCGGATTCCGGCAAGCCATGCAGTGTCTACTTACACCCGACGTCCACTCCGCCGGATACATGAGACATATCCCGACAGCTACACGATACTCTATACCTGATCGTACTGTTCTAGCGTACAATACGCGTGTCGCCGGCGGTATCTAAGCTTATAAGTTTCCGCCCTGCGGTTGCGATATTCCGTGTGACGGGCGGCAAATACGGGCCGGGGCACTGTAAGACTCGGAGGGTAAACAGTGGTACAGTGTTGATTATCCGAAACCGCCGGGGAGAGGTGTCGATGACGCTGCAACCTGTACCGCAGTTAGGGGCCGGGATGATGTGGTCGTGCACGCCTTAATCTCCGACTAACTGCAGTTCATAGAGCTTTGATTACATTTCCCAGAGGAATGGATACCTAGTCTTAAAGACAGCACGCTACGCACCAAAATCAGCACCAAACGCAAGCCTCGTGTTCAGACAACTTATTCGCCGTGAAGCTAAGTTTAAGAGGACTCGGATACTATGGAAAATCCTTCTTACAGCAGTGCCATTCCAGTTCGTACTTGTCAGCGAGGCGTTCAGCAATTGGCACCGCAATCATGGGGCACCACGTCATCGTAACTTGTTGGGACACAGTCACGCGGGGTCGTAGTTCTACCGATGGTTAATGCCAGTACCTCAGACGGTCTACACCCTTCGCCCCGGAGAGCGGATCAATGCATCTAGGTAGGTATTCTGCATCCCCCAAGCCTGCGTAGGAACAGATGGATCAGTCCGCCCTACTATGCGTTTAGTGAAGGACGAGGCATTAATACTAGTCCCGTGCGGCCCTGATGACCTGTCTTACGCGATGTACGCCTGACGTCGTGCATTGAATGTTTCATCTTAGGGTCGTAT
Actual Output:
223 244 238 222
Of-course, plenty of other input works correctly as well.
gtagtatatccacatacgaatttatggacgctatcgcgacgctctgaacgcggattccggcaagccatgcagtgtctacttacacccgacgtccactccgccggatacatgagacatatcccgacagctacacgatactctatacctgatcgtactgttctagcgtacaatacgcgtgtcgccggcggtatctaagcttataagtttccgccctgcggttgcgatattccgtgtgacgggcggcaaatacgggccggggcactgtaagactcggagggtaaacagtggtacagtgttgattatccgaaaccgccggggagaggtgtcgatgacgctgcaacctgtaccgcagttaggggccgggatgatgtggtcgtgcacgccttaatctccgactaactgcagttcatagagctttgattacatttcccagaggaatggatacctagtcttaaagacagcacgctacgcaccaaaatcagcaccaaacgcaagcctcgtgttcagacaacttattcgccgtgaagctaagtttaagaggactcggatactatggaaaatccttcttacagcagtgccattccagttcgtacttgtcagcgaggcgttcagcaattggcaccgcaatcatggggcaccacgtcatcgtaacttgttgggacacagtcacgcggggtcgtagttctaccgatggttaatgccagtacctcagacggtctacacccttcgccccggagagcggatcaatgcatctaggtaggtattctgcatcccccaagcctgcgtaggaacagatggatcagtccgccctactatgcgtttagtgaaggacgaggcattaatactagtcccgtgcggccctgatgacctgtcttacgcgatgtacgcctgacgtcgtgcattgaatgtttcatcttagggtcgtat
The above provides the same output and the following (invalid) input will fail as expected:
AACTAIN
Invalid DNA nucleobase encountered. Aborting.
Usage: python DNA.py <sequence>
DNA.py:
from argparse import ArgumentParser
class DnaNucleobaseSequence:
"""
Contains a DNA nucleobase sequence.
Counts the occurence of the available bases.
"""
def __init__(self, sequence):
self.sequence = sequence.upper()
self.counted_nucleobases = self.count_nucleobases()
def count_nucleobases(self):
"""
Return the count of nucleobases in sequence.
Since this is explicitly a DNA sequence, only adenine, cytosine,
guanine and thymine are allowed to be present.
Exit on KeyError (invalid nucleobase(s)).
"""
count = {
"A": 0,
"C": 0,
"G": 0,
"T": 0,
}
try:
for nucleobase in self.sequence:
count[nucleobase] = count[nucleobase] + 1
return count
except KeyError:
print("Invalid DNA nucleobase encountered. Aborting.")
raise SystemExit
def space_separated_nucleobases(self):
"""
Return the count of A, C, G and T separated by a single space.
"""
return "{0} {1} {2} {3}".format(
self.counted_nucleobases["A"],
self.counted_nucleobases["C"],
self.counted_nucleobases["G"],
self.counted_nucleobases["T"]
)
def main():
parser = ArgumentParser(description='DNA nucleobase sequence')
parser.add_argument(
'sequence',
type=str,
help='Should contain a DNA nucleobase sequence'
)
args = parser.parse_args()
seq = DnaNucleobaseSequence(args.sequence)
print(seq.space_separated_nucleobases())
if __name__ == "__main__":
main()
collections.Counter
is out of the question? \$\endgroup\$try
/catch
hacky method. \$\endgroup\$