25
\$\begingroup\$

Though this program tells the truth (unless you change the source code), it does tell Fibonacci numbers, depending on the user's input. Keeping in mind of all (or most) of the feedback given from previous questions, I wrote this program to see how well I have progressed.

def get_fibonacci_sequence(max):
    '''
    Returns the Fibonacci sequence from the
    first to the nth Fibonacci number.
    '''

    fib1 = 1
    fib2 = 1
    result = [fib1, fib2]
    for i in range(2, max):
        result.append(fib1 + fib2)
        fib1 = fib2
        fib2 = result[i]
    return result

def get_nth_fibonacci(n):
    '''Returns the nth Fibonacci number.'''

    return get_fibonacci_sequence(n)[n - 1]

def main():
    print("Would you like to have the sequence or the number?")
    print("Enter 's' for sequence, and anything else for the number.")
    seqOrNum = input("> ")
    if seqOrNum == 's':
        print("Up to which number would you like the sequence up to?")
        max = int(input("> "))
        print("The sequence up to the {}th Fibonacci number:".format(max))
        print(get_fibonacci_sequence(max))
    else:
        print("Which Fibonacci number would you like to get?")
        max = int(input("> "))
        print("The {}th Fibonacci number is:".format(max),
              get_nth_fibonacci(max))

if __name__ == "__main__":
    main()

Example Output:

Would you like to have the sequence or the number?
Enter 's' for sequence, and anything else for the number.
> s
Up to which number would you like the sequence up to?
> 10
The sequence up to the 10th Fibonacci number:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Concerns:

  1. Are my docstrings well-used?
  2. Does it follow conventions?
\$\endgroup\$
16
\$\begingroup\$

Generators, not lists

Rather than returning a list of Fibonacci numbers, it would be much more useful to simply return an infinite generator of them. That is, yield them. This way, you don't even need the max:

def get_fibonacci_numbers():
    '''
    Generator for all the fibonacci numbers
    '''
    fib1 = 1
    fib2 = 1
    yield fib1
    yield fib2
    while True:
        yield fib1 + fib2
        fib1 = fib2
        fib2 = fib1 + fib2

Note that double assigns are actually better done in one go for clarity:

def get_fibonacci_numbers():
    '''
    Generator for all the fibonacci numbers
    '''
    fib1, fib2 = 0, 1
    while True:
        yield fib2
        fib1, fib2 = fib2, fib1 + fib2

If you want the first n numbers, you can use itertools.islice.

Why a generator?

You can easily turn a generator into a list of specific size, so you're not losing any functionality this way. But there's simply so much you can do with a lightweight generator that you can't do with a list. Consider some simple problems - what's the first Fibonacci number over 1000?

>>> next(dropwhile(lambda x: x < 1000, get_fibonacci_numbers()))

Euler problem #2: find the sum of the even-valued fibonacci numbers under 4 million?

>>> sum(i for i in takewhile(lambda x: x < 4e6, get_fibonacci_numbers()) if i%2 == 0)

Can't do either with a list. At least, not unless you know where to stop up front.

If you want just the nth one, that's:

def get_nth_fibonacci(n):
    return next(itertools.islice(
        get_fibonacci_numbers(),
        n, None))
| improve this answer | |
\$\endgroup\$
6
\$\begingroup\$

It's definitely a good use of docstrings, but it would be better to make the get_fibonnaci_sequence docstring one line. Brief language and slight short cuts make this easy:

def get_fibonacci_sequence(max):
    '''Return Fibonacci sequence from 1st-nth Fibonacci number.'''

You do largely follow the conventions right. Good names and formatting but I have some notes. You can assign fib1 = fib2 = 1 in one line, which is shorter but a bit clearer too.

Also it's odd to store the two latest values in the sequence alongside integer stored values. If you want to save on time accessing the list, then stick with that and update fib1 and fib2 before appending the value to the list.

for i in range(2, max):
    fib2 = fib1 + fib2
    fib1 = fib2
    result.append(fib2)
return result

You can do both assignments in one line however, as Python has intelligent multiple assignment:

    fib1, fib2 = fib2, fib1 + fib2

The right hand side is evaluated fully before any assignment is performed, so this works for your needs since this is how it goes:

    # fib1 = 1, fib2 = 2
    fib1, fib2 = fib2, fib1 + fib2
    fib1, fib2 = 2, 1 + 2
    fib1, fib2 = 2, 3

Now that the evaluation is finished, both values can be assigned.

You could format the sequence nicer with '\n'.join(get_fibonacci_sequence(max)) to put each on a new line, or ', ' if you want a comma between each value. These can look nicer than the default list format.

At that, you should avoid using max since it's a builtin function in Python that you're shadowing. Instead you can generally use max_value. In this case num or number would even work, I think max is a bit askew from what you actually intend it to be.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.