2

This is the problem:

Write a recursive function f that generates the sequence 0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875. The first two terms are 0 and 1, every other term is the average of the two previous.

>>> f(0)
0
>>> f(1)
1
>>> f(2)
0.5
>>> [(i,f(i)) for i in range(10)]
[(0, 0), (1, 1), (2, 0.5), (3, 0.75), (4, 0.625), (5, 0.6875), (6, 0.65625), (7, 0.671875), (8, 0.6640625), (9, 0.66796875)]

This is my code so far, I can't seem to figure it out. Any help/suggestions would be appreciated.

def f(n):
  if n==0:
    return 0
  if n==1:
    return 1
  else:
    return f(n-2)//f(n-1)
3
  • return f(n-2)//f(n-1)?? Is that the way to calculate an average? Commented May 9, 2017 at 15:03
  • Please indent your code properly (indent everything, not just the first line). Commented May 9, 2017 at 15:06
  • 4
    Also, please refrain from editing the code that is causing the problem based on the answers you are given (except cosmetically). Commented May 9, 2017 at 15:09

5 Answers 5

3

The recursive case is wrong:

return f(n-2)//f(n-1) # not the formula for the average of two numbers

The average of two numbers a and b is (a+b)/2. So you can define your function as:

def f(n):
    if n==0:
        return 0
    if n==1:
        return 1
    else:
        return (f(n-1)+f(n-2))/2

Or we can make it Python version-independent like:

def f(n):
    if n==0:
        return 0
    if n==1:
        return 1
    else:
        return 0.5*(f(n-1)+f(n-2))

You can then generate the sequence with list comprehension like:

>>> [f(i) for i in range(10)]
[0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]

or by using a map:

>>> list(map(f,range(10)))
[0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]
Sign up to request clarification or add additional context in comments.

11 Comments

Using 2.0 instead of 2 (in the division) will ensure that it also works in python 2.X
@SembeiNorimaki: yeah. But multiplying by 0.5 is in my opinion even better since multiplication is usually carried out faster than division on a FPU (iirc divsion is done by first inverting the second operand and then perform multiplication).
This does not generate a sequence, just computes the nth term.
@MadPhysicist: yeah but based on the code provided by the OP I had the impression that that was not the missing point. The code demonstrates list comprehension to generate such sequence.
@SembeiNorimaki. The issue is that the / operator does very different things in Python 2 and Python 3. Also, division by 2 is only a bit shift for integers, and this question is definitely not about integers.
|
2

So you have U0 = 0, U1 = 1, and Un = (U(n-1) + U(n-2)) / 2 for n > 1.

You just have to literally translate this as a function:

def f(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return (f(n-1) + f(n-2)) / 2

Now for generating the sequence of the Ui from 0 to n:

def generate_sequence(n):
    return [f(i) for i in range(n)]

This could (and really should) be optimized with memoization. Basically, you just have to store the previously computed results in a dictionary (while you could directly use a list instead in this case).

results = dict()
def memoized_f(n):
    if n in results:
        return results[n]
    else:
        results[n] = f(n)
        return results[n]

This way, f(n) will be computed only once for each n.

As a bonus, when memoized_f(n) is called, the results dictionary holds the values of f(i) from 0 to at least n.

6 Comments

The final solution is not recursive. Why not just append to the list as you go?
I would use an array (where the key is the index) to allow the user to print out the sequence immediately without having to sort it. Either way, nice work.
@MadPhysicist That's right. I find it more practical to have a basic function that generates the result for one single value at a time, and then generate what I need. However, when memoizing with a list, the said list holds the sequence of the values up to at least the given parameter.
You are absolutely right to do it that way. I would do the same. Just nitpicking based on OP's stated requirements :)
@MadPhysicist That's still a pretty good comment, because sometimes, nothing more than the requirement should be made, especially on hardware with limited storage. Which is generally not the case with Python, though :)
|
2

Recursion à la auxiliary function

You can define this using a simple auxiliary procedure and a couple state variables – the following f implementation evolves a linear iterative process

def f (n):
  def aux (n, a, b):
    if n == 0:
      return a
    else:
      return aux (n - 1, b, 0.5 * (a + b))
  return aux(n, 0, 1)

print([f(x) for x in range(10)])
# [0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]

Going generic

Or you can genericize the entire process in what I'll call fibx

from functools import reduce

def fibx (op, seed, n):
  [x,*xs] = seed
  if n == 0:
    return x
  else:
    return fibx(op, xs + [reduce(op, xs, x)], n - 1)

Now we could implement (eg) fib using fibx

from operator import add

def fib (n):
  return fibx (add, [0,1], n)

print([fib(x) for x in range(10)])
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Or we can implement your f using fibx and a custom operator

def f (n):
  return fibx (lambda a,b: 0.5 * (a + b), [0, 1], n)

print([f(x) for x in range(10)])
# [0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]

Wasted computations

Some answers here are recursing with (eg) 0.5 * (f(n-1) + f(n-2)) which duplicates heaps of work. n values around 40 take astronomically longer (minutes compared to milliseconds) to compute than the methods I've described here.

Look at the tree recursion fib(5) in this example: see how fib(3) and fib(2) are repeated several times? This is owed to a naïve implementation of the fib program. In this particular case, we can easily avoid this duplicated work using the auxiliary looping function (as demonstrated in my answer) or using memoisation (described in another answer)

Tree recursion like this results in O(n2), whereas linear iterative recursion in my answer is O(n)

https://mitpress.mit.edu/sicp/chapter1/node13.html


Generating a sequence for n

Another answer provided by @MadPhysicist generates a sequence for a single n input value – ie, f(9) will generate a list of the first 10 values. However, the implementation is simultaneously complex and naïve and wastes heaps of computations due to the same f(n-1), andf(n-2)` calls.

A tiny variation on our initial approach can generate the same sequence in a fraction of the time – f(40) using my code will take a fraction of a second whereas these bad tree recursion answers would take upwards of 2 minutes

(Changes in bold)

def f (n):
  def aux (n, acc, a, b):
    if n == 0:
      return acc + [a]
    else:
      return aux (n - 1, acc + [a], b, 0.5 * (a + b))
  return aux(n, [], 0, 1)

print(f(9))
# [0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]

Comments

1

If you want a function that generates your sequence in a single call, without having to call the function for each element of the list, you can store the values you compute as you unwind the stack:

def f(n, _sequence=None):
    if _sequence is None:
        _sequence = [0] * (n + 1)
    if n == 0 or n == 1:
        val = n
    else:
        f(n - 1, _sequence)
        f(n - 2, _sequence)
        val = 0.5 * (_sequence[n - 1] + _sequence[n - 2])
    _sequence[n] = val
    return _sequence

This has the advantage of not requiring multiple recursions over the same values as you would end up doing with [f(n) for n in range(...)] if f returned a single value.

You can use a more global form of memoization as suggested by @RightLeg to record the knowledge between multiple calls.

Unlike the other solutions, this function will actually generate the complete sequence as it goes. E.g., your original example would be:

>>> f(9)
[0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]

3 Comments

You're returning [None, 1] on 1. I think you'd better return a static result when given 0 or 1, namely: if n == 0: return [0] elif n == 1: return [0, 1], or something like if n in (0, 1): return [0, 1][:n+1] (which is IMO unnecessarily complicated). +1 for the idea though
@Rightleg. Problem solved. Initializing to [0] * (n+1) instead of [None] * (n+1). There was no good reason to use None besides just habit.
Tree recursion used in this answer wastes heaps of computations – f(40) takes almost 2 minutes. Other things like leaked private api, single-branch if statements, mutation/reassignments, and function side-effects feel very clumsy and awkward when using recursion (which has its roots in functional programming)
0

Another simple solution might look like this:

a=0.0
b=1.0
count = 0
def f(newavg, second, count):

    avg = (second+newavg)/2
    print avg

    count=count+1
    if count<8:
        newavg = avg
        f(second, avg, count)
f(a, b, count)

Granted this code just outputs to the monitor...if you want the output into a list just add the code in the recursion.

Also be careful to properly indent where required.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.