# These are problems from Exam3 in Spring 2025.  There were 4 versions
# of the exam and all of them had the same problem.

# (5 points) Write a recursive function that takes as argument a
#   string and returns the mirror version, with the reverse of the
#   string appended at the end. Your solution must be recursive.  Don't
#   turn the string into a list. Here are some sample runs:
# 
# >>> mirror("")
# ''
# >>> mirror("a")
# 'aa'
# >>> mirror("abcdefg")
# 'abcdefggfedcba'
# >>> 
# \end{verbatim}

def mirror( s ):
    if not s:
       return s
    else:
       return s[0] + mirror( s[1:] ) + s[0]

# ----------------------------------------------------------------------

# (5 points) Write the function oddsEvens below that takes two
#   lists of integers and returns a new list that contains only the odd
#   numbers from the first list and the even numbers from the second
#   list, in that order.  This function is not recursive.   
# 
# >>> oddsEvens( [1, 2, 3, 4, 10], [2, 8, 4, 3, 1, 10] )
# [1, 3, 2, 8, 4, 10]

def oddsEvens( L1, L2 ):
    return [x for x in L1 if x%2==1] + [x for x in L2 if x%2==0]

def oddsEvens2( L1, L2 ):
    odds = []
    evens = []
    for x in L1:
        if x%2==1:
            odds.append( x )
    for x in L2:
        if x%2==0:
            evens.append( x )
    returns odds + evens

def isOdd(x):
    return bool(x%2)

def isEven(x):
    return not x%2

def filter( lst, f ):
    if not lst:
        return []
    else:
        rest = filter( lst[1:], f )
        return (rest if not f(lst[0]) else [lst[0]] + rest)

def oddsEnds3( L1, L2 ):
    return filter( L1, isOdd ) + filter( L2, isEven )

# ----------------------------------------------------------------------

#  (10 points) A polygon is a closed figure made up of line
#   segments (triangle, rectangle, etc.).  You can specify a polygon by
#   giving a list of its vertices (corner points).  Specify a vertex by
#   a tuple {\tt (x, y)} and a polygon by a list of vertices.  The size
#   of the polygon is the number of vertices (n >= 3) with n sides
#   connecting them, in order.  (There's a side from the final point
#   back to the first point in the list.)  Fill in the methods for the
#   class on the next page.  Assume you have a function dist(pt1, pt2)
#   that computes the distance between points pt1 and pt2; you
#   don't have to write that.  Below is some sample behavior:
# 
# >>> p1 = Polygon( [(0,0), (4,3)] )
# Error: Too few sides.
# >>> p2 = Polygon( [(0,0), (0, 2), (2, 2), (2, 0)] )
# >>> print(p2)
# Polygon with vertices: [(0, 0), (0, 2), (2, 2), (2, 0)]
# >>> p2.getVertices()
# [(0, 0), (0, 2), (2, 2), (2, 0)]
# >>> p2.size()
# 4
# >>> p2.perimeter()
# 8.0

import math

# Students didn't have to write this one. 
def dist( pt1, pt2 ):
    """ Distance from pt1 to pt2. """
    x1, y1 = pt1
    x2, y2 = pt2
    return math.sqrt( (x2 - x1)**2 + (y2 - y1)**2 )

class Polygon:
   def __init__( self, listOfVertices ):
      """Each vertex is a tuple/pair of coordinates (x, y).  Print an 
         error if fewer than 3 vertices.  Make attributes private."""
      if len(listOfVertices) < 3:
         print("Error: Too few sides")
      else:
         self.__verts = listOfVertices
        
   def __str__( self ):
      """Description of the polygon for printing.  You can assume str(L)
      will return the string representation of a list L of vertices."""
      return "Polygon with vertices: " + str(self.__verts)

   def size ( self ):
      """Return the number of vertices."""
      return len( self.__verts )

   def getVertices( self ):
      """Return the list of vertices."""
      return self.__verts

   def perimeter( self ):
      """Return the sum of the lengths of the sides of the polygon."
      sum = 0
      verts = self.__verts
      for i in range( len(verts)-1 ):
          sum += dist( verts[i], verts[i+1] )
      sum += dist( verts[-1], verts[0] )
      return sum

# ----------------------------------------------------------------------

# This wasn't from a previous exam but it's an interesting problem:

# Write a Python function that takes a string as input and returns a
# dictionary where keys are the characters in the string and values
# are their respective frequencies (counts).
# 
# >>> createDictWithLetterCounts( "abcabdegrst" )
# {'t': 1, 's': 1, 'r': 1, 'g': 1, 'e': 1, 'd': 1, 'b': 2, 'a': 2, 'c': 1}
# >>> createDictWithLetterCounts( "" )
# {}
# >>> createDictWithLetterCounts( "ab" )
# {'b': 1, 'a': 1}
# >>> 

def createDictWithLetterCounts( s ):
    if not s:
        return {}
    else:
        d = createDictWithLetterCounts( s[1:] )
        if s[0] in d:
            d[s[0]] += 1
        else:
            d[s[0]] = 1
        return d
