11

I am new to python, and I was wondering if I could generate the fibonacci series using python's list comprehension feature. I don't know how list comprehensions are implemented. I tried the following (the intention was to generate the first five fibonacci numbers):

series=[]
series.append(1)
series.append(1)
series += [series[k-1]+series[k-2] for k in range(2,5)]

This piece of code throws the error: IndexError: list index out of range.

Let me know if it is even possible to generate such a series using a list comprehension.

martineau
  • 99,260
  • 22
  • 139
  • 249
PythonNewBie
  • 125
  • 1
  • 1
  • 5

8 Answers8

11

You cannot do it like that: the list comprehension is evaluated first, and then that list is added to series. So basically it would be like you would have written:

series=[]
series.append(1)
series.append(1)
temp = [series[k-1]+series[k-2] for k in range(2,5)]
series += temp

You can however solve this by using list comprehension as a way to force side effects, like for instance:

series=[]
series.append(1)
series.append(1)
[series.append(series[k-1]+series[k-2]) for k in range(2,5)]

Note that we here do not add the result to series. The list comprehension is only used such that .append is called on series. However some consider list comprehensions with side effects rather error prone: it is not very declarative and tends to introduce bugs if not done carefully.

Willem Van Onsem
  • 321,217
  • 26
  • 295
  • 405
  • [Using assignment expressions in python3.8](https://stackoverflow.com/a/64549057/4909087), you can bypass the list comprehension with side effects to create a new list of fib numbers. Whether it's better than using side effects (as you've done here) is debatable. – cs95 Oct 27 '20 at 06:08
7

If you know how many terms of the series you will need then you can write the code compactly without a list comprehension like this.

def Fibonacci(n):
    f0, f1 = 1, 1
    for _ in range(n):
        yield f0
        f0, f1 = f1, f0+f1

fibs = list(Fibonacci(10))
print (fibs)

If you want some indefinite number of terms then you could use this, which is very similar.

def Fibonacci():
    f0, f1 = 1, 1
    while True:
        yield f0
        f0, f1 = f1, f0+f1

fibs = []
for f in Fibonacci():
    fibs.append(f)
    if f>100:
        break
print (fibs)

When you need a potentially infinite collection of items you should perhaps consider either a function with one or more yield statements or a generator expression. I'd love to be able to make Fibonacci numbers with a generator expression but apparently one can't.

Bill Bell
  • 19,309
  • 5
  • 39
  • 53
7

We could write it as a clean Python list comprehension (or generator) using it's relationship to the golden ratio:

>>> series = [int((((1 + 5**0.5) / 2)**n - ((1 - 5**0.5) / 2)**n) / 5**0.5) for n in range(1, 21)]
>>> series
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
>>> 

or a little more nicely as:

>>> square_root_of_five = 5**0.5
>>> Phi = (1 + square_root_of_five) / 2
>>> phi = (1 - square_root_of_five) / 2
>>> 
>>> series = [int((Phi**n - phi**n) / square_root_of_five) for n in range(1, 21)]
>>> series
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
cdlane
  • 33,404
  • 4
  • 23
  • 63
4

To build on what Willem van Onsem said:

The conventional way to calculate the nth term of the fibonacci sequence is to sum the n-1 and n-2 terms, as you're aware. A list comprehension is designed to create a list with no side effects during the comprehension (apart from the creation of the single list). Storing the last 2 terms of the sequence during calculation of the sequence is a side-effect, therefore a list comprehension is ill-suited to the task on its own.

A safe way around this would be to make a closure generator (essentially a generator with some associated private state) that can be passed to the list comprehension such that the list comprehension does not have to worry about the details of what's being stored:

def fib_generator(n):

    def fib_n_generator():
        last = 1
        curr = 1

        if n == 0:
            return

        yield last
        if n == 1:
            return

        yield curr
        if n == 2:
            return

        ii = 2
        while ii < n:
            next = curr + last
            yield next
            last = curr
            curr = next
            ii += 1

    return fib_n_generator()

fib = [xx for xx in fib_generator(10)]
print(fib)
daphtdazz
  • 6,082
  • 29
  • 45
  • Thanks for the explanation in the first para that states `Storing the last 2 terms of the sequence during calculation of the sequence is a side-effect, therefore a list comprehension is ill-suited to the task on its own`. – Iqra. May 19 '20 at 11:07
  • However, even after spending 15+ mins I cannot understand what's the benefit of using yield in above code snippet. `fib = [xx for xx in fib_generator(10)]` would have still be called if `fib_generator(n)` be a function w/o generator. – Iqra. May 19 '20 at 11:39
  • 1
    The yield is crucial, else it's not a generator. Without the yield `fib_n_generator()` would just return one thing, not an iterable of things. I could have made my answer simpler though: I didn't need to nest the function, so it should have looked like Bill Bell's answer (which is why his has more upvotes ;-) ). I could also have re-written it to return a list instead of a generator, but that would defeat the main purpose of using a generator, which is to avoid unnecessary RAM usage. – daphtdazz May 19 '20 at 13:19
3

Using Assignment Expression (python >= 3.8):

s = [0, 1]
s += [(s := [s[1], s[0] + s[1]]) and s[1] for k in range(10)]

print (s)
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
cs95
  • 274,032
  • 76
  • 480
  • 537
0

List comprehension of the fibonacci serie, based on the explicit formula 1:

[int((0.5+5**0.5/2)**n/5**0.5+0.5) for n in range(21)]
0

Here's a one-line list comprehension solution that avoids the separate initialization step with nested ternary operators and the walrus operator (so needs Python 3.8), and also avoids the rapid onset of overflow problems that the explicit form can give you (with its **n component):

[
    0 if not i else
        (x := [0, 1]) and 1 if i == 1 else
            not x.append(x[-2] + x[-1]) and x[-1]
    for i in range(10)
]

Gives:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

This is faster than the explicit form for generating all of the values up to N. If, however, you don't want all of the values then the explicit form could be much faster, but it does suffer from overflow for some N between 1000 and 2000:

n = 2000
int((((1 + 5**0.5) / 2)**n - ((1 - 5**0.5) / 2)**n) / 5**0.5)

gives for me:

OverflowError: (34, 'Numerical result out of range')

whereas the "adding the last two values" approach can generate higher values for larger N. On my machine, I can keep going until some N between 300000 and 400000 before I run out of memory.

Thanks to Jonathan Gregory for leading me most of the way to this approach.

dhassell
  • 131
  • 5
-1

Using List comprehension :

n = int(input())
fibonacci_list = [0,1]
[fibonacci_list.append(fibonacci_list[k-1]+fibonacci_list[k-2]) for k in range(2,n)]

if n<=0:
   print('+ve numbers only')
elif n == 1:
   fibonacci_list = [fibonacci_list[0]]
   print(fibonacci_list)
else:
   print(fibonacci_list)

maybe it's a feasible solution for this problem...