1
\$\begingroup\$

Added the problem link below and my implementation of it. Took me a few hours to program. I have added comments to explain my thought process. Problem Link Goal - To implement a program that identifies to whom a sequence of DNA belongs.

  1. should require first command-line argument the name of a CSV file containing the STR counts for a list of individuals and sits second command-line argument the name of a text file containing the DNA sequence to identify.

  2. program should print an error message if incorrect no of arguments.

  3. Your program should open the CSV file and dna file and read its contents into memory.

  4. first row of the CSV file will be the column names. The first column will be the word name and the remaining columns will be the STR sequences themselves.

  5. Your program should open the DNA sequence and read its contents into memory.

  6. For each of the STRs (from the first line of the CSV file), your program should compute the longest run of consecutive repeats of the STR in the DNA sequence to identify.

  7. If the STR counts match exactly with any of the individuals in the CSV file, your program should print out the name of the matching individual and no match if there are no matches.

  8. You may assume that the STR counts will not match more than one individual.

import csv
import sys
import re


def main():
    # checking if its correct no of arguments
    if len(sys.argv) != 3:
        print("Please specify both csv and text files")
        sys.exit(1)

    data = []
    # opening the file into memory
    with open(sys.argv[1], "r") as f:
        reader = csv.DictReader(f)
        for i in reader:
            data.append(i)

    # opening text sequence file and storing as string in seq
    with open(sys.argv[2]) as f:
        seq = f.read()

    # getting count from sequence
    
    countDict = count(data, seq)
    name = ""
    # comparing with data from csv file
    name = check(countDict, data)
    
    # printing the name of the match
    if name:
        print(name)
    else:
        print("No match")


def count(data, seq):

    # getting STRs from database file so we know what STRs to search for
    keys = list(data[0].keys())
    keys.pop(0)

    countDict = {}
    # creating a dictionary to store the count for the keys with key names
    for i in keys:
        countDict[i] = 0
    
    strlist = []
    # looping through the STRs to see if any matches in the string and if there are adding to the count
    for key in countDict:
        counter = 0
        for i in range(len(seq)):
            end = len(key)      # to get the length of the key
            if key == seq[i:end+i]:
                counter += 1
                v = countDict[key]
                if not key == seq[i+end:end+i+end]:
                    countDict[key] = max(v, counter)
                    counter = 0  # resetting the counter to 0
                else:
                    countDict[key] = v
                i += len(key)
                
    return countDict


def check(countDict, data):
    # empty string to store match name
    name = ""
    # going through the csv data file with potential matches ine at a time
    for i in data:
        # removing names from the data so we can do direct comparison with the count from dna found
        dictExceptName = dict(list(i.items())[1:])      
        # converting string count to int count for comparison
        for key, value in dictExceptName.items():
            dictExceptName[key] = int(value)
        # comparing each person to dna found list    
        if dictExceptName == countDict:
            name = i["name"]    # getting name of the person if there is a match
    
    return name


main()

\$\endgroup\$
3
  • 2
    \$\begingroup\$ While the question is excellent (perfect match for the site)! We ask that every question is self contained, what happens if your link dies? Please include the problemstatement from your link in your question =) \$\endgroup\$ Nov 22 at 17:09
  • \$\begingroup\$ As a second note you would be able to read the problem statement in your code if you had added docstrings and proper documentation ;-) \$\endgroup\$ Nov 22 at 17:10
  • \$\begingroup\$ also included the problem statement, and i will look into how to add docstrings and do proper documentation thanks \$\endgroup\$ Nov 22 at 17:34
0
\$\begingroup\$

Your code seems to work properly on the whole test suite provided which is a very good start.

Also, splitting the logic in 2 parts, one preprocessing the counts and the other performing the comparison is a great way to proceeed.

However, there are still various way to improve the code.

Style

Python has a style guide called PEP 8 which is definitly worth reading and trying to apply.

Among other things, in your case, it would imply using snake_case instead of camel_case.

Also, tiny invisible detail: your code contains trailing whitespace which can be cleaned up.

Documentation

The functions defined have very generic name which do not bring much information about what is being done. This could be improved with a better name but also by adding documentation to your function, for instance as docstrings

More functions to count

The count function is quite complicated because it tries to apply some logic to various elements but also needs to worry about preprocessing and storing the results in a usable form.

Here is my take on this:

def count_consecutive_repetitions(key, sequence):
    """Count the number of consecutive repetitions of string 'key' are in string 'sequence'."""
    search_str = key
    counter = 0
    pos = 0
    while True:
        pos = sequence.find(search_str, pos)
        if pos == -1:
            return counter
        counter += 1
        search_str += key

def count(data, seq):
    """Get count for to be continued."""
    # Extract relevant key strings from header row and get rid of first item
    keys = list(data[0].keys())[1:]

    # Get consecutive counts and store in a dictionnary
    return { k: count_consecutive_repetitions(k, seq) for k in keys }

Note: I've:

  • rewritten the algorithm to search to avoid messing with string indices
  • used a dictionnary comprehension to create the dictionnary. This was not required but it is a nice tool to have in your toolkit

Also, smaller functions are also easier to test. In my case, I wrote:

assert count_consecutive_repetitions("ABC", "") == 0
assert count_consecutive_repetitions("ABC", "DEF") == 0
assert count_consecutive_repetitions("ABC", "ABDEFC") == 0
assert count_consecutive_repetitions("ABC", "ABCDEF") == 1
assert count_consecutive_repetitions("ABC", "DEFABC") == 1
assert count_consecutive_repetitions("ABC", "DEFABCABCABCABCGHI") == 4
assert count_consecutive_repetitions("ABC", "DEFABCDEFABCABCDEFABCABCABCGHI") == 3

In order to do things properly, these could have been written with a proper test framework but I was too lazy.

Improvements to check

  • Instead of giving name the initial value "", you could give the value None. It would be clearer in case of wrong data to make a difference between someone whose name was input as "" and the case where no relevant result was found. (You would need to update the main code accordingly with name is None).

  • Here again, we can use dictionnary comprehension to perform all the operations you are interested: filtering and converting.

Here is what I got:

def check(countDict, data):
    """Get elements in data matching the string count in `countDict`."""
    # Going through the csv data file with potential matches ine at a time
    for i in data:
        # Get count by converting data to integers (except for the name) 
        count = { k: int(v) for k, v in i.items() if k != 'name' }
        # Comparing each person to dna found list
        if count == countDict:
            return i["name"]
    return None

Now, to make the filtering (based on string rather than index) more consistent in the other function, we could update it to:

def count(data, seq):
    """Get count for to be continued."""
    # Get consecutive counts and store in a dictionnary
    return { k: count_consecutive_repetitions(k, seq) for k in data[0].keys() if k != 'name' }

Final code

Performing various cosmetic changes, here is my final version of the code:

import csv
import sys


def main():
    # Check number of arguments
    if len(sys.argv) != 3:
        print("Please specify both csv and text files")
        sys.exit(1)

    # Get list of individuals
    with open(sys.argv[1], "r") as f:
        data = list(csv.DictReader(f))

    # Get DNA sequence
    with open(sys.argv[2]) as f:
        seq = f.read()

    # Get count of repeatited keys
    count_dict = count_repeated_keys(data, seq)

    # Get name of person with same count
    name = get_name_with_same_count(data, count_dict)

    # Print result
    print("No match" if name is None else name)


def count_consecutive_repetitions(key, sequence):
    """Count the number of consecutive repetitions of string 'key' are in string 'sequence'."""
    search_str = key
    counter = 0
    pos = 0
    while True:
        pos = sequence.find(search_str, pos)
        if pos == -1:
            return counter
        counter += 1
        search_str += key


def count_repeated_keys(data, seq):
    """Get repeated count for each key from data."""
    # Get consecutive counts and store in a dictionnary
    return {
        k: count_consecutive_repetitions(k, seq) for k in data[0].keys() if k != "name"
    }


def get_name_with_same_count(data, count_dict):
    """Get element in data matching the string count in `count_dict`."""
    # Going through the csv data file with potential matches ine at a time
    for i in data:
        # Get count by converting data to integers (except for the name)
        count = {k: int(v) for k, v in i.items() if k != "name"}
        # Comparing each person to dna found list
        if count == count_dict:
            return i["name"]
    return None


main()
assert count_consecutive_repetitions("ABC", "") == 0
assert count_consecutive_repetitions("ABC", "DEF") == 0
assert count_consecutive_repetitions("ABC", "ABDEFC") == 0
assert count_consecutive_repetitions("ABC", "ABCDEF") == 1
assert count_consecutive_repetitions("ABC", "DEFABC") == 1
assert count_consecutive_repetitions("ABC", "DEFABCABCABCABCGHI") == 4
assert count_consecutive_repetitions("ABC", "DEFABCDEFABCABCDEFABCABCABCGHI") == 3

Edit

I just realised that count_consecutive_repetitions could be simplified as:

def count_consecutive_repetitions(key, sequence):
    """Count the number of consecutive repetitions of string 'key' are in string 'sequence'."""
    pos = 0
    for c in itertools.count(start=1):
        pos = sequence.find(key * c, pos)
        if pos == -1:
            return c - 1
\$\endgroup\$
1
  • \$\begingroup\$ thanks thats really really helpful, i am just starting out , even this took me a few hours to implement. I am gonna look at the style guide and the docstrings. Thank You \$\endgroup\$ yesterday

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.