Complicated list view is slower than straight loop
I saw an interesting solution for programming exercises in leetcode It's not even about the problem / solution itself, so you can read it from the link if you like. The highly voted solution, however, is one liner:
Fragment 1
def fizzBuzz2(n):
return ["Fizz" * (not i%3) + "Buzz" * (not i%5) or str(i) for i in range(1, n+1)]
Fragment 2
def fizzBuzz(n):
out = []
for i in range(1, n+1):
if i % 3 == 0 and i % 5 == 0:
out.append("FizzBuzz")
elif i % 3 == 0:
out.append("Fizz")
elif i % 5 == 0:
out.append("Buzz")
else:
out.append(str(i))
return out
However, I expected the list comprehension to beat the general loop, but when I timed it and it didn't. Even doing this, Snippet 2 has more instructions.
What makes Snippet 1 slow?
source to share
your snippet
def fizzBuzz2(n):
return ["Fizz" * (not i%3) + "Buzz" * (not i%5) or str(i) for i in range(1, n+1)]
does a lot of string concatenations (even with empty strings). I have exchanged this with an additional modular operation that preserves the concatenation and is already faster.
def fizzBuzz3(n):
return ["FizzBuzz" if not i%15 else "Fizz" if not i%3 else "Buzz" if not i%5 else str(i) for i in range(1, n+1)]
and BTW on my machine, both understandings are faster than the "classic" approach, so I get different results than you stated:
your comp: 4.927702903747559
my listcomp: 4.343341112136841
classical: 6.015967845916748
So my optimized listcomp wins (and seems to wins on your machine) even though I am not satisfied with the additional module operations caused by listcomp's flow control)
My test protocol runs 10,000 times the operation with n=1000
:
import time
start_time = time.time()
for i in range(10000):
fizzBuzz2(1000)
print("your comp:",time.time()-start_time)
start_time = time.time()
for i in range(10000):
fizzBuzz3(1000)
print("my listcomp:",time.time()-start_time)
start_time = time.time()
for i in range(10000):
fizzBuzz(1000)
print("classical:",time.time()-start_time)
note that even with pre-computation of modules in the "classic" approach it drops to 5.375272035598755
seconds (which is good), but still worse than a list because of all the instructions (you also kill the speed of listcomp by calling the save method of modular computation). I am guessing that python is not the proper language to get top speed in this case.
source to share
Many people have already answered your question, but I want to add that there are even faster solutions. It doesn't seem right to me to check the numbers in retrospect, although you already know where Fizz, Buzz and FizzBuzz will be. Try this instead:
def fizz_buzz(n):
result = []
for i in range(1, n + 1, 15):
result += [str(i), str(i + 1), 'Fizz',
str(i + 3), 'Buzz', 'Fizz',
str(i + 6), str(i + 7), 'Fizz', 'Buzz',
str(i + 10), 'Fizz', str(i + 12), str(i + 13),
'FizzBuzz']
return result[:n - len(result)]
start_time = time()
for i in range(10000):
fizz_buzz(1000)
print("fizz_buzz:", time() - start_time)
Compared to previous answers:
your comp: 3.3942723274230957
my listcomp: 2.586350202560425
classical: 3.879168748855591
fizz_buzz: 1.6374053955078125
source to share
While they were looking into implementations, here's a fun snippet that's faster than a single list comprehension, but not nearly as fast as @seenorths' solution:
from math import ceil
def fizz_buzz(n):
result = list(range(n))
result[::3] = ["Fizz"] * ceil(n / 3)
result[::5] = ["Buzz"] * ceil(n / 5)
result[::15] = ["FizzBuzz"] * ceil(n / 15)
return list(map(str, result))
source to share