Diagnostic 2: Solutions and Learning

November 9th, 2020


The purpose of the Diagnostic was to help you self assess where you can improve. Every single person in this class has some area of the material where they could use some extra practice. In this document we are going to go over each of the problems in the diagnostic.

Here is a link to your answers:
http://cs106a.stanford.edu/cgi-bin/purplebookauth/diagnostic2.cgi

Here is the full distribution:

Regrade Requests
We try to grade as consistently as possible, but we are human and we might make mistakes. If you feel like one of your problems was misgraded, please file a regrade request on this form before Friday, November 13th at 11:59pm PST. Note that this is the only way to have your regrade request considered; in particular, asking your section leader to take a quick look to see whether a problem was misgraded isn't a way of short circuiting this process. We want you to have the credit you deserve, but filing a formal request helps us make sure that your request goes to the right person.

Can I get extra help?
Yes! Come to office hours and LaIR and we can go over any concepts from the diagnostic.

How can I check my answers?
With this document you can check your answer. For each problem we include

  1. Several common solutions
  2. Comments which explain the solutions
  3. The major concepts that the problem covers
  4. Which readings and which lecture videos relate to the problem

I wasn’t able to solve many of the problems on the diagnostic. Should I panic?
Absolutely not! It is normal to find these problems hard. If you didn’t do as well as you would have liked you have a two step procedure. Step 1: make sure you know the underlying concepts (for example, using the i variable in a for loop). Then if you know the concepts but you weren’t able to come up with solutions in time the simple solution is practice and review! Many people incorrectly think that they are not good at this. False! We all can code. You just need more experience. Check out the review session.

My answer is different from the provided solution. Is my answer wrong?
Not necessarily. You will learn a lot by looking at the difference between the teaching team solution and your work. Your answer might be great. There are many solutions.

Can I discuss my solution on ed?
Yes! You are free to share your solutions


Problem 1: Debugging & Tracing

Part A:
1) The source string and target list are passed as parameters in the wrong order.
This causes the the string to be treated as the list and the list to be  treated
as the string in fill_list_with_string. 
2) Resetting target list to clear any elements is a reassignment not a mutation.
The changes made to target_list in the rest of the function will not persist in
main. 
3) You cannot modify a string because it is immutable, so source_string[i] = ' '
will not work. 
Part B: Fix the code
          
def main():
   # set up a test string and list
   source_string = "abc123"
   target_list = []

   # call the function with the parameters in the correct order
   fill_list_with_string(target_list, source_string)

   # This should print out ['a', 'b', 'c', '1', '2', '3']
   print(target_list)

def fill_list_with_string(target_list, source_string):
   """
   This function populates target_list with each of the 
   characters in a source_string. This function modifies
   the target_list parameter.

   As an example:
   my_list = []
   fill_list_with_string(my_list, "hello")
   Will result in my_list having the value   
   ['h', 'e', 'l', 'l', 'o']
   """

   # Do not reassign to a new list to clear elements

   # loop over each index
   for i in range(len(source_string)):
      # extract the current character
      ch = source_string[i]

      # add it to the list
      target_list.append(ch)

      # Don't actually need to remove it from the string
    
      
Concepts:

One of the biggest concepts at play in this problem is mutability. Lists are mutable whereas strings are immutable. Once we know that, we have to consider if the changes made the mutable object is a binding or a mutation. In ths problem it was a binding, so none of the changes persist. Here is a handout where you can learn more about binding vs. mutation. Because strings are immutable, you cannot reset one char within that string. You have to build up an entirely new string with the new char in the given location. In this problem, we were not using the string for anything, so we do not actually need to remove the char from the string. If you did want to remove the char, you can build a new string using slices of the original string.

Problem 2: Strings



def find_snps(string_1, string_2):
    """
    Given two equal length strings, return a list of all indices 
    where the two strings have different characters.
    >>> find_snps("ATGCC", "ATTCA")
    [2, 4]
    """
    snps = []
    for i in range(len(dna_1)):
       ch_1 = dna_1[i]
       ch_2 = dna_2[i]
       if ch_1 != ch_2:
          snps.append(i)
    return snps
Concepts:

The two important concepts in this problem are looping over strings and building up a list. We use a for i in range loop rather than a for each loop here because we want to have access to the index. First, we need it to grab the char at that index in both strings. We also append that index to the list if the two characters are the same. If you found this problem confusing, you can checkout this lecture on strings or get some practice with the string problems on the additional practice handout

Problem 3: Lists and Files

  
  #there are many ways to solve this problem and we have listed a few below

#solution 1
def count_evens_in_column(filename):
    counts = []
    for line in open(filename):
    splits = line.split()
    if counts == []:
      for i in range(len(splits)):
        counts.append(0)
    for i in range(len(splits)):
      if int(splits[i]) % 2 == 0: #if the number is even
        counts[i] += 1
  return counts

    
#solution 2 using a 2D grid
def count_evens_in_column(filename):
    """
    Reads file, places into 2D grid, and then loops through columns of grid. 
    """
    with open(filname) as f:
        outer = []
        for line in f:
            lst = line.strip().split()
            outer.append(lst)
​
    results = []
    for i in range(len(outer[0])): # Go through all x's (start of col)
        count = 0
        for j in range(len(outer)): # Go through all y's (numbers within col)
            elem = outer[j][i]
            if int(elem) % 2 == 0: 
                count += 1
        results.append(count)
    return results

#solution 3 using a dict
def count_evens_in_column(filename):
    """
    Reads file line by line - uses a dictionary where the key is the index of the number 
    within a line, and the value is the current count of evens. Then, constructs a list
    once the dictionary is complete.
    """
    table = {}
    with open(filename) as f:
        for line in f:
            row = line.strip().split()
            for i in range(len(row)):
                if i not in table:
                    table[i] = 0
                if int(row[i]) % 2 == 0:
                    table[i] += 1
    
    results = []
    # As of Python 3.6, keys are sorted by order of insertion, so this is fine
    for key in table.keys():
        results.append(table[key])
​
    return results

#solution 4 using a list
def count_evens_in_column(filename):
    """
    Reads file and stores all numbers in a list. Using the (uniform) length of a line, goes through the first 
    N numbers, (where N is the length of the line), and goes N positions forward to find the next number for the next col.
    Uses this strategy to construct the final list. 
    """
    numbers = []
    length_line = None
    with open(filname) as f:
        for line in f:
            line = line.strip().split()
            if length_line is None:
                length_line = len(line) # Save the length of a line for later, so that we can hop through the list
            for num in line:
                numbers.append(num)
​
    # Hop through the list. We know that the first N numbers are the start of the column. This means 
    # that for each of the first N numbers, we just need to hop N positions to get the next number in the column
    results = []
    for i in range(length_line):
        col_even_count = 0 
        col = numbers[i::length_line] # Can also be done with a ~loop~
        for num in col:
            if int(num) % 2 == 0:
                col_even_count += 1
        results.append(col_even_count)
    return results
  
Concepts:

There are a lot of ways that one could go about solving this problem. The main concept is file reading and storing information about the file. You can choose the way in which you want to store the information. The idea is to update the information at each line in the file. For each line, you want to determine if each number is even. If it is even, you want to update that the count for even numbers in that column. If you want to look more at file reading, check out the lecture on file reading. You can also get practice with reading through files and storing information in the problems for Section 5 and Section 6.

Problem 4: 2D Lists

  
def make_true_grid(grid):
  # Find maximum number of columns across all rows
  # (Could be a separate function)
  max_cols = 0
  for i in range(len(grid)):
      cols = len(grid[i])
      if cols > max_cols:
          max_cols = cols

  # Pad each row to have extra elements (0’s), so grid is square
  for i in range(len(grid)):
      cols = len(grid[i])
      for j in range(max_cols - cols):
          grid[i].append(0)
  
Concepts:

The main concept in this problem is 2D lists. More specifically, this problem covered iterating over and appending to 2D lists where necessary. The key idea here was to find the maximum number of columns out of all of the inner lists. Then one would append 0s to all of the inner lists that have length less than the maximum number of columns. To review 2D lists, check out Lecture 11: More Lists. You can also get more practice with the problems in Secton 4.

Problem 5: Dictionaries

  
def common_key_value_pairs(dict1, dict2):
  result = {}
  for key in dict1:
      if key in dict2:
          if dict1[key] == dict2[key]:
              result[key] = dict1[key]
  return result
  
Concepts:

The main concept in this problem was dictionaries. Here, we loop through all of the keys in one of the dictionaries. If that key is also a key in the second dictionary, we compare the values for the keys in their respective dictionaries. If their values are equal, then this key/pair value is added to the result dictionary. Even though there are not many lines of code, a lot is happening in the few lines. To review dictionaries, check out Lecture 16: Dictonaries. You can also get practice with dictionaries by following the links under the Dictionary section in the Additional Practice Handout.

That’s all folks! Thanks for your hard work