{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercise solutions: structuring code\n", "\n", "## Exercise 2.1 \n", "Write a function that takes as argument a number, and returns it divided by 2 if it is even, and unchanged if it is not." ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 -> 0\n", "1 -> 1\n", "2 -> 1\n", "3 -> 3\n", "4 -> 2\n", "5 -> 5\n", "6 -> 3\n", "7 -> 7\n", "8 -> 4\n", "9 -> 9\n" ] } ], "source": [ "def divide_by_two_even(n):\n", " '''Takes a number and divide it by 2 if it is even'''\n", " if n % 2 == 0: # if the number is even, then the remainder of its division by 2 is 0\n", " n = n / 2 # then divide it by 2\n", " return int(n)\n", "\n", "# Now let's test our function for all integer below ten:\n", "for n in range(10):\n", " print(n , '->', divide_by_two_even(n))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Exercise 2.2\n", "Consider the following string :\n", "* `\"Brave Sir Robin ran away. Bravely ran away away. When danger reared it's ugly head, he bravely turned his tail and fled. Brave Sir Robin turned about and gallantly he chickened out...\"`\n", "\n", "Build a dictionnary whose keys are characters, and whose values correspond to the number of time the character is present in the string." ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'B' is present 3 times\n", "'r' is present 13 times\n", "'a' is present 21 times\n", "'v' is present 4 times\n", "'e' is present 16 times\n", "' ' is present 31 times\n", "'S' is present 2 times\n", "'i' is present 8 times\n", "'R' is present 2 times\n", "'o' is present 4 times\n", "'b' is present 4 times\n", "'n' is present 12 times\n", "'w' is present 3 times\n", "'y' is present 7 times\n", "'.' is present 6 times\n", "'l' is present 8 times\n", "'W' is present 1 times\n", "'h' is present 6 times\n", "'d' is present 9 times\n", "'g' is present 3 times\n", "'t' is present 7 times\n", "''' is present 1 times\n", "'s' is present 2 times\n", "'u' is present 5 times\n", "',' is present 1 times\n", "'f' is present 1 times\n", "'c' is present 2 times\n", "'k' is present 1 times\n", "'B' is present 3 times\n", "'r' is present 13 times\n", "'a' is present 21 times\n", "'v' is present 4 times\n", "'e' is present 16 times\n", "' ' is present 31 times\n", "'S' is present 2 times\n", "'i' is present 8 times\n", "'R' is present 2 times\n", "'o' is present 4 times\n", "'b' is present 4 times\n", "'n' is present 12 times\n", "'w' is present 3 times\n", "'y' is present 7 times\n", "'.' is present 6 times\n", "'l' is present 8 times\n", "'W' is present 1 times\n", "'h' is present 6 times\n", "'d' is present 9 times\n", "'g' is present 3 times\n", "'t' is present 7 times\n", "''' is present 1 times\n", "'s' is present 2 times\n", "'u' is present 5 times\n", "',' is present 1 times\n", "'f' is present 1 times\n", "'c' is present 2 times\n", "'k' is present 1 times\n" ] } ], "source": [ "string = \"Brave Sir Robin ran away. Bravely ran away away. When danger reared it's ugly head, he bravely turned his tail and fled. Brave Sir Robin turned about and gallantly he chickened out...\"\n", "\n", "letter_count = {} # Instantiate an empty dictionnary.\n", "for letter in string: \n", " if not letter in letter_count: # Test whether the letter is already present in the dictionnary.\n", " letter_count[letter] = 0 # If not, initialize its count value to 0.\n", " \n", " letter_count[ letter ] += 1 # Increment the number of time the letter has been seen.\n", "\n", "# Let's now print the results:\n", "for letter in letter_count.keys(): # for each key in the dictionnary\n", " print( \"'\", letter, \"' is present \" , letter_count[letter], ' times', sep='')\n", "\n", "\n", "# Bonus: iterating over key value pairs in a dictionary can be elegantly achieved \n", "# using a dictionary's \"items()\" method:\n", "for letter, count in letter_count.items():\n", " print( \"'\", letter, \"' is present \" , count, ' times', sep='')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Exercise 2.3\n", "Write a function that returns the reverse complement of a string containing a DNA sequence. \n", "Test your function with the sequence `ATAGAGCGATCGATCCCTAGCTA`, compare your results with what you can get using this online tool." ] }, { "cell_type": "code", "execution_count": 82, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "original : ATAGAGCGATCGATCCCTAGCTA\n", "reverse complemented: TAGCTAGGGATCGATCGCTCTAT\n", "Is our result correct? True\n" ] } ], "source": [ "def revComp(seq):\n", " \"\"\"returns the reverse complement of a sequence given as argument\"\"\"\n", " \n", " reverseComplementedSeq = \"\" # Create an empty string variable that will be used to store the function's output.\n", " \n", " for nucleotide in seq : # For each nucleotide in the sequence...\n", "\n", " # Find the complement of the current nucleotide.\n", " # Note: instead of an if...elif...else structure, we could use a dictionnary of the form {nucleotide:complement}.\n", " if nucleotide == 'A':\n", " complement = 'T'\n", " elif nucleotide == 'T':\n", " complement = 'A'\n", " elif nucleotide == 'G':\n", " complement = 'C'\n", " elif nucleotide == 'C':\n", " complement = 'G'\n", " else: # In case the nucleotide it is not A T G or C -> error!\n", " print(\"Unkown nucleotide :\", nucleotide)\n", " print(\"Abort!\")\n", " return None\n", " \n", " # lastly, I put the current nucleotide AT THE BEGINNING of the resulting sequence, so that this accounts for the reversing\n", " reverseComplementedSeq += complement\n", " \n", " return reverseComplementedSeq[::-1]\n", "\n", "# Let's test our function:\n", "mySeq = \"ATAGAGCGATCGATCCCTAGCTA\"\n", "revcompseq = revComp(mySeq)\n", "print(\"original :\", mySeq)\n", "print(\"reverse complemented:\", revcompseq)\n", "\n", "# Check against output of the online tool:\n", "online_result = \"TAGCTAGGGATCGATCGCTCTAT\"\n", "print(\"Is our result correct?\", revcompseq == online_result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Exercise 2.4\n", "Write a function that accepts a DNA sequence as an argument and returns its %GC content." ] }, { "cell_type": "code", "execution_count": 77, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The 2 functions returned the same result.\n", "GC% of the sequence: 47.82608695652174\n" ] } ], "source": [ "# First implementation, using a loop.\n", "def get_GC_percent(seq):\n", " nGC = 0 # counter of GC.\n", " for nuc in seq:\n", " if nuc in 'GC': \n", " nGC += 1 # If the nucleotide is a G or C, increment the counter of GC.\n", " \n", " seq_size = len(seq)\n", " return nGC / seq_size * 100.\n", "\n", "# Second implementation, using the \"count()\" methods of str.\n", "def get_GC_percent_2( seq ):\n", " nGC = seq.count('G') + seq.count('C')\n", " return nGC / len(seq) * 100\n", "\n", "mySeq = \"ATAGAGCGATCGATCCCTAGCTA\"\n", "GC1 = get_GC_percent(mySeq)\n", "GC2 = get_GC_percent_2(mySeq)\n", "\n", "if GC1 != GC2 :\n", " print(\"ERROR! The 2 functions returned different results:\" , GC1 , GC2)\n", "else:\n", " print(\"The 2 functions returned the same result.\")\n", " print(\"GC% of the sequence:\" , GC1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first implementation takes a bit longer to design and write, however it may be easier to understand for someone who does not know the particular of `str` in Python.\n", "\n", "Also, the first implementation may be faster than the second implementation because it iterates over the nucleotides of `seq` only once, whereas the second function does it twice (in each call of the `count` method). This is however mitigated by the fact that native python methods and function are often quite efficient, and it is not easy to outperform them.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Addtional exercise 2.1\n", "A little bit of a puzzle:" ] }, { "cell_type": "code", "execution_count": 70, "metadata": { "scrolled": true }, "outputs": [], "source": [ "def mysterious_function(n):\n", " if n <= 1:\n", " return 1\n", " return n * mysterious_function(n-1)" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The mysterious function returned: 24\n", "The mysterious function returned: 6\n", "The mysterious function returned: 2\n" ] } ], "source": [ "print(\"The mysterious function returned:\", mysterious_function(4))\n", "print(\"The mysterious function returned:\", mysterious_function(3))\n", "print(\"The mysterious function returned:\", mysterious_function(2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. What does `mysterious_function` do?\n", "\n", "`mysterious_function` takes a number `n` and multiplies it by the results of `mysterious_function(n-1)`, unless `n` is lower or equal to 1 in which case it returns 1.\n", "\n", "Let's see what happens with `n = 4`.\n", "* `mysterious_function(4)` -> 4 * `mysterious_function(3)`\n", "* `mysterious_function(4)` -> 4 * 3 * `mysterious_function(2)`\n", "* `mysterious_function(4)` -> 4 * 3 * 2 * `mysterious_function(1)`\n", "* `mysterious_function(4)` -> 4 * 3 * 2 * 1\n", "* `mysterious_function(4)` -> 24\n", "\n", "So, `mysterious_function` computes the product of all positive integers lower or equal to `n`. In other words, it computes a factorial!\n", "\n", "Having a function calling itself is called a **recursion**. \n", "It is a method commonly used when the solution to a problem can be defined using solution(s) to smaller instance(s) of the same problem.\n", "\n", "2. Write a function that gives the same result, but using a `for` loop." ] }, { "cell_type": "code", "execution_count": 72, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4! = 24\n", "4! = 24\n" ] } ], "source": [ "def factorial_using_loop(n):\n", " if n <= 1: # Treating the special case where n is lower or equal to 1.\n", " return 1 # Note: anytime the \"return\" keyword is called, we exit the function.\n", " \n", " fact = 1\n", " for i in range(2 , int(n) + 1): # Going from 1 to n. Remember that in \"range()\"\" the stopping point is excluded.\n", " fact *= i # Increment the factorial.\n", " #print(fact)\n", " return fact\n", "\n", "# Testing using 4\n", "print('4! =', factorial_using_loop(4))\n", "print('4! =', factorial_using_loop(4.0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Addtional exercise 2.2\n", "The Collatz conjecture describes the following procedure:\n", "* Start with any positive integer `n`. \n", "* Then each term is obtained from the previous term as follows: if the previous term is even, the next term is one half of the previous term. If the previous term is odd, the next term is 3 times the previous term plus 1. \n", "\n", "The conjecture is that no matter the value of `n`, the sequence will always reach 1.\n", "1. Write a function that takes an integer `n` as argument and returns the number of steps it took to reach `1` following the described procedure.\n", "\n", "> You can test your function by checking that `13` leads to the following sequence of size 10:\n", ">\n", "> 13 -> 40 -> 20 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1\n" ] }, { "cell_type": "code", "execution_count": 73, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collatz sequence length for value 13 is: 10\n" ] } ], "source": [ "# Let's code 2 functions: one that gives the next number in the sequence, \n", "# and one that creates the sequence until it reaches 1\n", "def next_collatz(n):\n", " \"\"\"Divide by 2 if n is even, otherwise multiply by 3 and add 1\"\"\"\n", " if n % 2 == 0:\n", " r = n / 2\n", " else:\n", " r = 3 * n + 1\n", " return r\n", " \n", "\n", "def Collatz_sequence_length(n):\n", " \"\"\"Returns the length of the Collatz sequence of n\"\"\"\n", " seq_length = 1 # Counter for sequence length.\n", " while n > 1: # while loop testing for the stopping condition. \n", " n = next_collatz(n) # Compute the next number in the sequence.\n", " seq_length += 1\n", " \n", " return seq_length\n", "\n", "n=13\n", "print(\"Collatz sequence length for value\", n, 'is:', Collatz_sequence_length(n))\n", "\n", "\n", "# Bonus:\n", "# Simple \"if... else...\" constructs can often be condensed to a single line with \n", "# the \"value A if condition else value B\" structure.\n", "def next_collatz_2(n):\n", " r = n / 2 if n % 2 == 0 else 3 * n + 1\n", " return r" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2. How would you account for a case where the conjecture is false (i.e., the sequence never reaches 1) ?\n", "With the current implementation, if `n` invalidates the conjecture we are stuck in an infinite loop.\n", "One way to account for that could be to limit the number of steps allowed. This is effective and easy to code, but then the choice of stopping threshold is arbitrary and could lead to false positives...\n" ] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Error: only 20 iterations supported...\n", "Collatz sequence length for value 101 is: None \n", "\n" ] } ], "source": [ "# Here is an example of how we can introduce a new optional \"max_iter\" argument to\n", "# stop the function if it doesn't converge to 1 quickly enough.\n", "def Collatz_sequence_length(n, max_iter=500):\n", " \"\"\"Returns the length of the Collatz sequence of n. If the \n", " sequence length exeeds the maximum number of allowed iterations, \n", " the function returns None.\n", " \"\"\"\n", " seq_length = 1 # Counter for sequence length.\n", " while n > 1: # while loop testing for the stopping condition. \n", " n = next_collatz(n) # Compute the next number in the sequence.\n", " seq_length += 1\n", " if seq_length > max_iter:\n", " print(\"Error: only\", max_iter, \"iterations supported...\")\n", " return None\n", " \n", " # If the sequence length exeeds the maximum number of allowed iterations, \n", " # we return None.\n", " return seq_length\n", "\n", "n=101\n", "print(\"Collatz sequence length for value\", n, 'is:', Collatz_sequence_length(n, 20), '\\n')" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Collatz sequence length for value 0 is: 1\n", "Collatz sequence length for value 1 is: 1\n", "Collatz sequence length for value 2 is: 2\n", "Collatz sequence length for value 3 is: 8\n", "Collatz sequence length for value 4 is: 3\n", "Collatz sequence length for value 5 is: 6\n", "Collatz sequence length for value 6 is: 9\n", "Collatz sequence length for value 7 is: 17\n", "Collatz sequence length for value 8 is: 4\n", "Collatz sequence length for value 9 is: 20\n", "Collatz sequence length for value 10 is: 7\n", "Collatz sequence length for value 11 is: 15\n", "Collatz sequence length for value 12 is: 10\n", "Collatz sequence length for value 13 is: 10\n", "Collatz sequence length for value 14 is: 18\n", "Collatz sequence length for value 15 is: 18\n", "Collatz sequence length for value 16 is: 5\n", "Collatz sequence length for value 17 is: 13\n", "Collatz sequence length for value 18 is: 21\n", "Collatz sequence length for value 19 is: 21\n", "Collatz sequence length for value 20 is: 8\n", "Collatz sequence length for value 21 is: 8\n", "Collatz sequence length for value 22 is: 16\n", "Collatz sequence length for value 23 is: 16\n", "Collatz sequence length for value 24 is: 11\n", "Collatz sequence length for value 25 is: 24\n", "Collatz sequence length for value 26 is: 11\n", "Collatz sequence length for value 27 is: 112\n", "Collatz sequence length for value 28 is: 19\n", "Collatz sequence length for value 29 is: 19\n" ] } ], "source": [ "# And here one more solution where we additionaly implement a check to see whether \n", "# a number in the sequence was already encountered, which would indicate that we\n", "# are stuck in an infinite sequence...\n", "def Collatz_sequence_length(n, max_iter=500):\n", " \"\"\"Returns the length of the Collatz sequence of n. If the \n", " sequence length exeeds the maximum number of allowed iterations, \n", " the function returns None.\n", " \"\"\"\n", " collatz_seq = [n] # Now we keep track of all values in the sequence.\n", " while n > 1: \n", " n = next_collatz(n)\n", " # Check that the new number in the sequence in not already found in the sequence.\n", " # This would indicate that the sequence is infinite... (well, so far no one was \n", " # able to find a number that would not converge to 1!)\n", " if n in collatz_seq:\n", " print(\"Error: the number\", n, \"was already detected earlier in the sequence. This will be infinite.\")\n", " return None\n", " \n", " collatz_seq.append(n)\n", " if len(collatz_seq) > max_iter:\n", " print(\"Error: only\", max_iter, \"iterations supported...\")\n", " return None\n", "\n", " return len(collatz_seq)\n", "\n", "# Let's test a bunch of numbers.\n", "for n in range(30):\n", " print(\"Collatz sequence length for value\", n, 'is:', Collatz_sequence_length(n))\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Addtional exercise 2.3\n", "Write a function that takes a string of mixed letters and digits, and returns a list of the words (groups of letters) and numbers (group of digits) of the string. \n", "For instance, the string `\"Nobody0expects42the2048Spanish1492Inquisition!\"` \n", "should give `[ \"Nobody\" , 0 , \"expects\" , 42 , \"the\" , 2048 , \"Spanish\" , 1492 , \"Inquisition!\" ]`\n", "\n", "> This exercise can be a tad harder than what it looks like. Don't despair and take your time; you can make it !" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Nobody0expects42the2048Spanish1492Inquisition! \n", "\t-> ['Nobody', 0, 'expects', 42, 'the', 2048, 'Spanish', 1492, 'Inquisition!']\n" ] } ], "source": [ "test_string = \"Nobody0expects42the2048Spanish1492Inquisition!\"\n", "\n", "# This solution relies on the method .isdigit() of str which returns True if all characters in a string are digit\n", "def split_numbers_and_words(line):\n", " \"\"\"takes a string mixing letters and digits and transform it into a list \n", " of the words (groups of letters) and numbers (group of digits) of the string.\n", " \"\"\"\n", " split_line = [] # This will store the return value: the splitted input line.\n", " if not line: # Handle the special case of an empty input string. \n", " return split_line # Note that \"not line\" is simply a shortcut for \"len(line) == 0\"\n", " \n", " previous_is_digit = line[0].isdigit() # This will store whether or not the previous character was a digit.\n", " current_group = \"\" # This will store the word or number we are currently reading.\n", " \n", " for c in line:\n", " current_is_digit = c.isdigit() # Is the current character a digit ?\n", " \n", " if current_is_digit == previous_is_digit : \n", " # the current character has the same status than the previous character \n", " # -> the word or number grows.\n", " current_group += c\n", " else:\n", " # the current character does not have the same status than the previous character \n", " # -> this indicates the end of a word or number.\n", " if previous_is_digit:\n", " current_group = int(current_group) # If the group we just completed is a number, convert it to an integer.\n", " \n", " split_line.append(current_group) # Add the word or number to the list of results.\n", " \n", " # Now we start a new word with the current character.\n", " current_group = c\n", " previous_is_digit = current_is_digit\n", " \n", " # Now, the last thing we need to do is to add the last group to our output list.\n", " if previous_is_digit:\n", " current_group = int(current_group) # If we are building a number, convert it to an integer\n", " split_line.append(current_group) # Add the word or number to the list of results\n", " \n", " return split_line\n", "\n", "\n", "print(test_string, '\\n\\t->', split_numbers_and_words(test_string))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 }