Q:

Map a function to all elements of a tuple

I'm solving exercise 3 from lecture 3 of Berkeley's CS 61A (2012):

Fill in the definition of map_tuple. map_tuple takes in a function and a tuple as arguments and applies the function to each element of the tuple.
def map_tuple(func, tup):
    """Applies func to each element of tup and returns a new tuple.
    >>> a = (1, 2, 3, 4)
    >>> func = lambda x: x * x
    >>> map_tuple(func, a)
    (1, 4, 9, 16)
    """

I know that Python has a built-in map function, but at this point in the course, the only operations on tuples that we have studied are indexing [1] [-1], slicing [1:], and concatenation +, so my solution needs to restrict itself accordingly.
My solution 1:
def map_tuple(func, tup):
    """Applies func to each element of tup and returns a new tuple.

    >>> a = (1, 2, 3, 4)
    >>> func = lambda x: x * x
    >>> map_tuple(func, a)
    (1, 4, 9, 16)
    """
    length = len(tup)
    count = 0
    new_tuple = ()
    while count < length:
        new_tuple = new_tuple +  (func(tup[count]),)
        count = count + 1
    return new_tuple

My solution 2:
def map_tuple_recursive(func, tup):
    """Applies func to each element of tup and returns a new tuple.

    >>> a = (1, 2, 3, 4)
    >>> func = lambda x: x * x
    >>> map_tuple(func, a)
    (1, 4, 9, 16)
    """
    length = len(tup)
    def new_tuple(count):
        if count == length:
            return ()
        else:
            return (func(tup[count]), ) + new_tuple(count + 1)
    return new_tuple(0)

How can these solutions be improved?

A:

Assuming you cannot us generator expressions (which technically are not mutable but I can see being outside the scope of the assignment), solution 1 can be simplified by iterating over the items of the tuple, and by use the += in-place append:
def map_tuple(func, tup):
    """
    Applies func to each element of tup and returns a new tuple.

    >>> a = (1, 2, 3, 4)
    >>> func = lambda x: x * x
    >>> map_tuple(func, a)
    (1, 4, 9, 16)
    """
    new_tuple = ()
    for itup in tup:
        new_tuple += (func(itup),)
    return new_tuple

The second can be simplified by looking for the case where the tuple is empty, and if it is not empty return the map of everything except the last element, plus the function applied to the last element:
def map_tuple_recursive(func, tup):
    """Applies func to each element of tup and returns a new tuple.

    >>> a = (1, 2, 3, 4)
    >>> func = lambda x: x * x
    >>> map_tuple(func, a)
    (1, 4, 9, 16)
    """
    if not tup:
       return ()
    return map_tuple_recursive(func, tup[:-1]) + (func(tup[-1],)

Using a generator expression lets you do this, but maybe outside the scope of what you are allowed to do:
def map_tuple_gen(func, tup):
    """
    Applies func to each element of tup and returns a new tuple.

    >>> a = (1, 2, 3, 4)
    >>> func = lambda x: x * x
    >>> map_tuple(func, a)
    (1, 4, 9, 16)
    """
    return tuple(func(itup) for itup in tup)

