# FizzBuzz in different flavors

import sys
import functools
import time

fizz=3
buzz=5
nmax=20

def mod3FizzBuzz(n):
  if n%(fizz*buzz)==0:
    result='FizzBuzz'
  elif n%fizz==0:
    result='Fizz'
  elif n%buzz==0:
    result='Buzz'
  else:
    result=str(n)
  return result

def mod2FizzBuzz(n):
  result=''
  if n%fizz==0: result+='Fizz'
  if n%buzz==0: result+='Buzz'
  return result or str(n)

def plusFizzBuzz(n):
  return (str(n),'Fizz','Buzz','FizzBuzz')[int(n%fizz==0)+2*int(n%buzz==0)]

def plusFizzBuzz_match_case(n):  # Python 3.10+ only
  match int(n%fizz==0)+2*int(n%buzz==0):
    case 0: result=str(n)
    case 1: result='Fizz'
    case 2: result='Buzz'
    case 3: result='FizzBuzz'
    case _: result='****'
  return result

def mod1FizzBuzz(n):
  return ('FizzBuzz',str(n),str(n),'Fizz',str(n),'Buzz','Fizz',str(n),str(n),'Fizz','Buzz',str(n),'Fizz',str(n),str(n))[n%(fizz*buzz)]

def stringFizzBuzz(n):
  s='2NNFNBFNNFBNFNN'
  return {'N':str(n),'F':'Fizz','B':'Buzz','2':'FizzBuzz'}[s[n%(fizz*buzz)]]

def triggerFizzBuzzTupleIndex(n,triggers=(('Fizz',fizz),('Buzz',buzz))):
  result=''
  for i in range(len(triggers)):
    if n%triggers[i][1]==0: result+=triggers[i][0]
  return result or str(n)

def triggerFizzBuzzTuple(n,triggers=(('Fizz',fizz),('Buzz',buzz))):
  result=''
  for t in triggers:
    if n%t[1]==0: result+=t[0]
  return result or str(n)

def triggerFizzBuzzDict(n,triggers=(dict(text='Fizz',divisor=fizz),dict(text='Buzz',divisor=buzz))):
  result=''
  for t in triggers:
    if n%t['divisor']==0: result+=t['text']
  return result or str(n)

def ffizz(n):
  return n%fizz==0

def fbuzz(n):
  return n%buzz==0

def predicateFizzBuzz(n,triggers=(dict(text='Fizz',predicate=ffizz),dict(text='Buzz',predicate=fbuzz))):
  result=''
  for t in triggers:
    if t['predicate'](n): result+=t['text']
  return result or str(n)

def mapFilterReduceFizzBuzz(n,triggers=(dict(text='Fizz',predicate=ffizz),dict(text='Buzz',predicate=fbuzz))):
  parts=list(map(lambda t: t['text'],filter(lambda t: t['predicate'](n),triggers)))
  result=functools.reduce(lambda a,b:a+b,parts) if parts else str(n)
  return result

def RunLoop(nmax,text,algorithm,triggers=None):
  t0=time.time()
  if not triggers:
    for n in range(1,nmax+1):
      print(algorithm(n),end=',')
  else:
    for n in range(1,nmax+1):
      print(algorithm(n,triggers),end=',')
  print(text)
  print(text,round(time.time()-t0,3),file=sys.stderr)

RunLoop(nmax,'mod3',mod3FizzBuzz)
RunLoop(nmax,'mod2',mod2FizzBuzz)
RunLoop(nmax,'plus',plusFizzBuzz)
RunLoop(nmax,'plusMatch',plusFizzBuzz_match_case)
RunLoop(nmax,'mod1',mod1FizzBuzz)
RunLoop(nmax,'string',stringFizzBuzz)
RunLoop(nmax,'triggerTupleIndex',triggerFizzBuzzTupleIndex,triggers=(('Fizz',fizz),('Buzz',buzz)))
RunLoop(nmax,'triggerTuple',triggerFizzBuzzTuple,triggers=(('Fizz',fizz),('Buzz',buzz)))
RunLoop(nmax,'triggerDict',triggerFizzBuzzDict,triggers=(dict(text='Fizz',divisor=fizz),dict(text='Buzz',divisor=buzz)))
RunLoop(nmax,'predicate',predicateFizzBuzz,triggers=(dict(text='Fizz',predicate=ffizz),dict(text='Buzz',predicate=fbuzz)))
RunLoop(nmax,'mapFilterReduce',mapFilterReduceFizzBuzz,triggers=(dict(text='Fizz',predicate=ffizz),dict(text='Buzz',predicate=fbuzz)))
