Skal vi kode 'Bum' til jobsamtalen?

FizzBuzz er en simpel test af en programmørs grundlæggende færdigheder, men kan også afsløre rutinerede programmørers skavanker.

Det skulle efter sigende være svært at finde nok uddannede programmører til at imødekomme efterspørgslen. Derfor er det nærliggende at prøve at rekruttere dem, der ikke har et eksamensbevis, men er selvlærte og har talent.

Men hvordan finder man ud af, om sådan en selvlært programmør kan kode?

Især i USA kan en begynderstilling som juniorudvikler være særdeles velbetalt i forhold til de fleste andre jobs. Derfor er det oplagt, at der vil være folk helt uden færdigheder, som prøver at få foden indenfor.

I 2007 foreslog en britisk softwareudvikler en simpel test. En ansøger skulle demonstrere sine programmeringsfærdigheder ved at skrive et program med inspiration fra børnelegen 'Bum', eller på engelsk 'FizzBuzz'.

Bum er en leg i skolen, som bruges til at lære om multiplikation og division. Man vælger eksempelvis 3-tabellen, og man skiftes derefter til at tælle op fra 1, og hver gang man kommer til et tal, som 3 går op i, siger man 'bum' i stedet for tallet. Man kan øge sværhedsgraden ved at have flere tabeller i spil på samme tid.

Den oprindelige formulering af spørgsmålet til jobansøgeren lød:

Write a program that prints the numbers from 1 to 100. But for multiples of three print 'Fizz' instead of the number and for the multiples of five print 'Buzz'. For numbers which are multiples of both three and five print 'FizzBuzz'.

Det er en meget enkel øvelse, der lettest løses med en for-løkke og tre if-statements, og samtidig kan vise, at udvikleren kender til modulus og har sin grundlæggende algebra på plads.

function FizzBuzz() {
    for (i = 1; i <= 100; i++) {
        if (i % 3 == 0 && i % 5 == 0) {
            console.log("FizzBuzz");
        }
        else if (i % 3 == 0 && i % 5 != 0) {
            console.log("Fizz");
        }
        else if (i % 3 != 0 && i % 5 == 0) {
            console.log("Buzz");
        }
        else {
            console.log(i);
        }
    }
}

Men det er også en øvelse, som let kan få øvede programmører til at falde i en fælde, hvor de ganske vist løser opgaven, men gør det på en måde, der ikke er hensigtsmæssig i alle udviklerjob.

På denne side kan man finde forskellige løsninger af FizzBuzz. De er interessante, fordi de viser forskelle mellem programmeringssprog, men de viser også problemer med at skrive brugbar og især læsbar kode.

Eksempelvis er det muligt at lave et enkelt check på modulus 15 i stedet for både at tjekke modulus 3 og modulus 5. Det virker som en god idé at løse opgaven på denne måde, fordi man kan fjerne et check, og man viser matematisk forståelse.

Men det er også en løsning, som øger afstanden mellem den oprindelige kravspecifikation og koden.

Sammenhængen mellem 3, 5 og 15 er ikke umiddelbar uden en kommentar i koden. Den enkle, ligefremme løsning indeholder et ekstra check, men er til gengæld en direkte oversættelse af kravene.

Er Fizz + Buzz det samme som FizzBuzz?

Der er også flere eksempler på, hvordan FizzBuzz kan løses i en enkelt kodelinje, eller ved at tjekke længden af en streng i stedet for at tjekke selve tallet. Det fører igen til kode, der fjerner sig fra beskrivelsen af problemet.

To eksempler i Python hentet fra linket oven for:

python -c "print '\n'.join(['Fizz'<em>(x % 3 == 2) + 'Buzz'</em>(x % 5 == 4) or str(x + 1) for x in range(100)])"
#
# Eller:
#
for i in xrange(0,101):
    s = ""
    if i % 3 == 0: s += "Fizz"
    if i % 5 == 0: s += "Buzz"
    print s if len(s) else i

Løsninger, hvor man opbygger en streng ved at tilføje eventuelt tomme strenge for hvert tjek, gør sig desuden skyldige i at antage, at 'Fizz' + 'Buzz' er det samme som 'FizzBuzz'. Det giver et korrekt output i den konkrete opgave, men udvikleren har fortolket opgaven for at kunne lave en bestemt løsning og dermed introduceret en potentiel fejlkilde.

Hvad er kravene - og hvad er optimering?

Kompakt kode kan være en elegant øvelse, men langt det meste kode, en udvikler skal skrive, skal blot opfylde de udstukne specifikationer og være til at teste og vedligeholde. Her kan det være væsentligt at prioritere læsbarhed højt.

Som udvikler bør man derfor være bevidst om opgaven. Hvis opgaven i stedet gik ud på, at man fik udleveret en FizzBuzz-implementering og blev bedt om at gøre den mere effektiv, så ville det være en god løsning at reducere antallet af check.

I min simple implementering ville det eksempelvis være en god optimering at starte med at ændre koden, så det hyppigst forekommende udfald er det, der bliver tjekket for først, hver gang løkken gennemløbes.

function FastFizzBuzz() {
    for (i = 1; i <= 100; i++) {
        if (i % 3 != 0 && i % 5 != 0) {
            console.log(i);
        }
        else if (i % 3 == 0 && i % 5 != 0) {
            console.log("Fizz");
        }
        else if (i % 3 != 0 && i % 5 == 0) {
            console.log("Buzz");
        }
        else {
            console.log("FizzBuzz");
        }
    }
}

En optimering bør ikke måles i antal kodelinjer, men snarere i forhold til hvordan koden gennemløbes, da det er dét, der vil give en reel forbedring.

Hvordan skal outputtet printes?

I den oprindelige formulering af FizzBuzz-spørgsmålet er der så et lille problem, som en dygtig udvikler i det mindste burde påpege. Outputtet er blot defineret som at printe tal og tekst, men det er ikke klart specificeret, hvordan det skal gøres.

Der er således forskellige løsninger, hvor outputtet printes som:

1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz ...
// Eller:
12Fizz4BuzzFizz78FizzBuzz ...
// Eller:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
...

Alle er gyldige løsninger, men de er også forskellige output. En god programmør burde bemærke i sit svar, at formatet for outputtet ikke er nærmere specificeret.

Bum-legen er ganske vist kun beregnet til at sikre sig, at en jobansøger kan finde ud af for-løkker og if-statements, men der ligger også en implicit test af ansøgerens evne til at fokusere på den givne opgave og være bevidst om sin fortolkning af de krav, opgaven definerer.

Softwareudvikling handler nemlig ikke kun om algoritmer. For mange er det en vigtigere del af arbejdet at kunne tage en beskrivelse af en arbejdsproces og omsætte den til funktioner, der korrekt udfører processen i programmet hver gang.

Så spørgsmålet er, om en test som FizzBuzz er egnet til at teste en uøvet programmørs evner, eller om der er andre egenskaber end simpelt algoritmedesign, der faktisk er vigtigere at teste for, inden man sætter en programmør foran tasterne.

Tips og korrekturforslag til denne historie sendes til tip@version2.dk

Følg forløbet

Kommentarer (31)

Peter Makholm Blogger

Jeg har set flere kandidater fejle på simple kodeopgaver, men at sidde til et interview og kode FizzBuzz er spild af min uanset hvilken side af bordet jeg sidder på.

Hvis man har 1000 ansøgere og en forventning om at højst 10% har bare de minimale programeringskundskaber der skal til før man gider at læse ansøgningen, så kan en opgave af et tilsvarende niveau måske indgå i en screening, men ellers?!?

Det eneste fornuftige svar på FizzBuzz er Joel Grus': http://joelgrus.com/2016/05/23/fizz-buzz-in-tensorflow/

Povl H. Pedersen

Man kan sætte ansøger til at løse den på 5, 10 eller 15 minutter før interview. Så er det kun ansøgers tid man spilder.

Der er mange måde at ændre den på, jeg ville nok lave:

for ($i=1;$ i<=100;$ i++) {  
  $done=0;  
   if ($i % 3 == 0 ) {  
      print "fizz";  
     $done=1;  
   }  
   if ($i % 5 == 0) {  
      print "buzz";  
      $done=1;  
   }  
   if (!$done) {  
      print $i;  
   }  
   print "\n";  
}

Lettere at læse, men selvfølgelig et ekstra funktionskald per iteration. print "\n".

Synes at den foreslåede i artiklen er overkompleks, og ligner noget lavet af en matematiker eller datalog, der har overtænkt problemet.
15, eller rettere 3 og 5 er ikke et specialtilfælde. Det er 3 = fizz og 5 = buzz der begge trigges uafhængigt.
Artiklens løsning vil hurtigt vokse i kompleksitet hvis vi laver fizzbuzzbum hvor vi tilføjer 7-tabellen. Min løsning vil kræve en lille if-sætning mere.

Nu skal det siges at jeg ikke lever af at kode. Så jeg belønnes ikke med jobsikkerhed ved at lave unødig kompleks kode.

Anders Clausen

Din løsning har det problem, at du evaluerer alle dine conditions hver gang, samtidigt med, at du har implicitte conditions som kun bliver anskueliggjort for andre programmører, når de har forstået din kode i sin helhed. Bare for at give lidt konstruktivt kritik :) Løsningen er såmænd elegant nok rent funktionelt.

Morten Hansen

Til gengæld udfører den højst 2 modulus beregninger hvor Jespers løsning laver mindst 2 :)
Regnekraftmæssigt er det ganske fornuftigt hvis man går op i den slags detaljer.

Povl H. Pedersen

Jeg synes ikke min kode er sværere at forstå, tværtimod er den lettere.
Reglerne er:
Deles med 3 sig fizz
Deles med 5 sig buzz
Deles med 3 og 5, så begge ovenstående.

Og som sagt er der færre modulo operationer som er tunge. Dog bruges en register variabel som der ofte vil kunne laves til en jump if not zero på (sidste if) - Så billig CPU i den ene if.

Jeg synes min version er letter at læse,og svarer til den metode de fleste personer vil bruge i virkeligheden. Først checke med 3 tabellen, så sige fizz og samtidig check 5 tabellen, og sige buzz hvis match.
Og meget lettere at udvide med eksempelvis en ekstra tabel.

Morten Olsen

80'erne ringede. De ville gerne have deres 6502 relevante optimeringer tilbage !

(Sagt med et glimt i øjet, og en opfordring til at læse lidt op på moderne CPU arkitekturer...)

Christian Nobel

var  
  b:integer;  
begin  
  for b:=1 to 100 do  
  begin  
    if (b mod 15)=0 then writeln('fizzbuzz')  
    else if (b mod 5)=0 then writeln('buzz')  
    else if (b mod 3)=0 then writeln('fizz')  
    else writeln(b);  
  end;  
end;

Super overskuelig.

Man kunne selvfølgelig også bruge en case sætning, men resultatet ville vel være det samme by the end of the day.

Jens Melgaard

""Den enkle, ligefremme løsning indeholder et ekstra check, men er til gengæld en direkte oversættelse af kravene.""

Hvis man rent faktisk er så god til at formulere krav i realiteten, så var det nok langt billigere at sende opgaven til Indien (eller tilsvarende sted).

Jeg har hørt de er gode til at følge krav specifikationer til punkt og prikke, hvad end de resultere i et fungerende system er mindre relevant.

Det er naturligvis sat lidt på spidsen overfor denne opgave, men da vi alle ved at krav sjældent er så præcise ville jeg nok velkomme noget der indikere at ansøgeren tænkte lidt over tingene frem for bare slavisk at implementere hvad der stod på papiret.

Dermed sagt at jeg ville se positivt på en der brugte %15 istedet.

Anders Clausen

@Morten: Deraf min kommentar om, at løsningen rent funktionelt er elegant :)

@Povl: Funktionelt er din løsning meget elegant - som nævnt. Men din sidste regel, "Deles med 3 og 5, så begge ovenstående.", ligger implicit i koden. Det kan potentielt give problemer, uanfægtet af, at det gør din kode mere effektiv. Eksempelvis kunne en programmør fejlagtigt antage mutual exclusion. Det er et klassisk trade-off mellem performance (og subjektivt, funktionel elegance) og maintainability.

Kim Madsen

Argh... jeg har overperformet i mit løsningsforslag....

Jeg burde jo kun have lavet en løsning fra 1 til 100... og lavede en fra 1 til 1000... binært... så den rigtige og langt nemmeste løsning at forstå, hvor det er meget svært at lave kode fejl, og som kan afvikles på selv den mest ydmyge lommeregner eller for den sags skyld... regnestok er:

print 1,2,"Fizz",4

Det er jo en ren snyde opgave da hverken "Buzz" eller "FizzBuzz" nogensinde kommer i brug... ;)

mvh
Kim Bo

Baldur Norddahl

(1 to 100).  
  map(n => if (n % 15 == 0) Left("FizzBuzz") else Right(n)).  
  map(_.right.flatMap(n => if (n % 3 == 0) Left("Fizz") else Right(n))).  
  map(_.right.flatMap(n => if (n % 5 == 0) Left("Buzz") else Right(n))).  
  foreach(n => println(n.fold(identity,_.toString)))

hvorfor gøre det sværere end det behøver være? :-)

Esben Bjerregaard

Vi får ALLE kandidater til at lave en simpel opgave.

Nogle gange FizzBuzz nogle gange noget andet.

Pointen er at se hvordan kandidaten løser opgaven. Opgaven skal være så simpel at alle kan løse den og løse den hurtigt. Det handler ikke så meget om, om syntaksen er 100% spot on. Det handler meget mere om hvordan kandidaten går til opgaven. Og det er bestemt ikke spild af tid - hvis en kandidat ikke brænder for at vise egne codeskills er kandidaten og vores virksomhed ikke et godt match anyway

Joel har mange gode pointer om job interviews her https://www.joelonsoftware.com/2006/10/25/the-guerrilla-guide-to-intervi...
B.la. "Most of the time in the interview, though, should be spent letting the candidate prove that they can write code."

Kim Madsen

Og med de ubehagelige fejl der af og til optræder i CPU'er mener jeg at man helt bør undgå modulus (og dermed division), og holde sig til simple lookup som selv den mest tåbelige CPU næsten ikke kan fejle på ;)

const a:array[1..100] of integer = (  
      0,0,1,0,2,1,0,0,1,2,0,1,0,0,3,0,0,1,0,2,  
      1,0,0,1,2,0,1,0,0,3,0,0,1,0,2,1,0,0,1,2,  
      0,1,0,0,3,0,0,1,0,2,1,0,0,1,2,0,1,0,0,3,  
      0,0,1,0,2,1,0,0,1,2,0,1,0,0,3,0,0,1,0,2,  
      1,0,0,1,2,0,1,0,0,3,0,0,1,0,2,1,0,0,1,2  
      );  
const s:array[0..3] of string = (  
      '','Fizz','Buzz','FizzBuzz'  
      );  
   
for i:=low(a) to high(a) do  
begin  
     if s[a[i]]='' then  
        writeln(i)  
     else  
         writeln(s[a[i]]);  
end;

mvh
Kim Bo

Brian Vraamark

Det kan også være at ansøgeren slet ikke vil bruge modulus og optimere performance lidt mere. Modsat Kims er den ikke afhængig af lookup og fungerer derfor med et hvilket som helst interval. ;)

Følgende benytter ikke modulus (division for at få resten, da det er den måde modulus ofte er implementeret). Alle tal øges kun med 1, derfor benyttes der ikke ADD, men INC (hvis compileren optimere korrekt) som er hurtigere.

Har det betydning på en i7 - næ, men måske på en langsom RFID læser.

    int three=1,five=1;  
    for (int i = 1; i <= 100; i++) {  
        if (three==3 && five==5) {  
             cout << "FizzBuzz";  
             three=0;  
             five=0;  
        }  
        else if (three==3 && five!=5) {  
             cout << "Fizz";  
             three=0;  
        }  
        else if (three!=3 && five==5) {  
             cout << "Buzz";  
             five=0;  
        }  
        else {  
             cout << i;  
        }  
        three++;  
        five++;          
    }
Brian Vraamark

Min kode kan i øvrigt reduceres lidt mere, da der ikke nogen er grund til !=5 og != 3, når der kun er to tal vi tester på (desværre tester man på 3 to gange hvor den ikke er det):

int three=1,five=1;    
    for (int i = 1; i <= 100; i++) {    
        if (three==3 && five==5) {    
             cout << "FizzBuzz";    
             three=0;    
             five=0;    
        }    
        else if (three==3) {    
             cout << "Fizz";    
             three=0;    
        }    
        else if (five==5) {    
             cout << "Buzz";    
             five=0;    
        }    
        else {    
             cout << i;    
        }    
        three++;    
        five++;            
    }
Rune Juhl Jacobsen

Pattern matching med guards i Erlang er nok noget af det pæneste det kan blive til:

-module(fizzbuzz).  
-compile(export_all).  
   
fizzbuzz(X) when X rem 15 =:= 0 ->  
    "FizzBuzz";  
fizzbuzz(X) when X rem 3 =:= 0 ->  
    "Fizz";  
fizzbuzz(X) when X rem 5 =:= 0 ->  
    "Buzz";  
fizzbuzz(X) -> X.  
   
run() ->  
    L = lists:seq(1, 100),  
    io:fwrite("~p~n", [lists:map(fun fizzbuzz:fizzbuzz/1, L)]).

(kør den som erlc fizzbuzz.erl &amp;&amp; erl -pa . -run fizzbuzz run -run init stop -noshell)

...men min løsning i Clojure er ganske læsbar:

(clojure.string/join  
  " "  
  (take 100 (map  
              #(cond  
                 (zero? (+ (mod % 3) (mod % 5))) "FizzBuzz"  
                 (zero? (mod % 3))               "Fizz"  
                 (zero? (mod % 5))               "Buzz"  
                 :else                           %)  
              (iterate inc 1))))

Jeg er ikke meget bekendt med Scala, men ud fra din kode går jeg ud fra at dit array bliver gennemløbet tre gange; hvad er fordelen der ift. et enkelt map med en lambda med en conditional? Er det bare en personlig præference/læsbarhed, ift. følgende?

(1 to 100).  
  map(n =>  
    if (n % 15 == 0)  
      Left("FizzBuzz")  
    else if (n % 3 == 0) Left("Fizz")  
    else if (n % 5 == 0) Left("Buzz")  
    else Right(n)).  
  foreach(n => println(n.fold(identity,_.toString)))
Mark Klitgaard
Baldur Norddahl

Jeg er ikke meget bekendt med Scala, men ud fra din kode går jeg ud fra at dit array bliver gennemløbet tre gange

God pointe, der skal indføres et .view således:

(1 to 100).view.    
    map(n => if (n % 15 == 0) Left("FizzBuzz") else Right(n)).    
    map(_.right.flatMap(n => if (n % 3 == 0) Left("Fizz") else Right(n))).    
    map(_.right.flatMap(n => if (n % 5 == 0) Left("Buzz") else Right(n))).    
    foreach(n => println(n.fold(identity,_.toString))) 

Forskellen er at et view er lazy så beregningen bliver først gennemført ved kaldet til foreach. På intet tidspunkt vil der eksistere et array eller liste med mellemresultater.

hvad er fordelen der ift. et enkelt map med en lambda med en conditional? Er det bare en personlig præference/læsbarhed, ift. følgende?

Der er ikke nogen fordele ved at skrive kode som jeg gjorde. Det var bare for at lave noget andet end det mest åbenlyse:

for(n <- 1 to 100)  
  println(if (n%15==0) "FizzBuzz" else if (n%3==0) "Fizz" else if (n%5==0) "Buzz" else n)

Pattern matching versionen ser således ud i Scala:

for(n <- 1 to 100) println(n match {  
    case n if n%15==0 => "FizzBuzz"  
    case n if n%3==0 => "Fizz"  
    case n if n%5==0 => "Buzz"  
    case n => n  
  })

Eller hvad med:

def fizz(n: Int) = if (n%3==0) "Fizz" else ""  
def buzz(n: Int) = if (n%5==0) "Buzz" else ""  
def number(n: Int, str: String) = if (str=="") n else str  
for(n <- 1 to 100) println(number(n,fizz(n)+buzz(n)))
Troels Tolstrup

Hvem har så trivielle opgaver at det giver nogen form for mening og se om de kan løse så simpel en "udfordring"? Problemstillingen og løsningen er så basal, at jeg ikke engang kan se hvordan den giver mulighed for videre teknisk diskussion. (Med mindre løsningen skulle ende i en kaliber som den linket til af Peter Makholm, det ville være awesome) Hvis vi snakker helt grønne kandidater uden nogen form for relevant uddannelse så kan jeg måske se ideen.

Jeg vil meget hellere finde relevante tekniske emner at diskutere baseret på den pågældende kandidats erfaring, men det kan man selvfølgelig ikke sætte en tilfældig HR konsulent til.

Martin Wolsing

Jeg synes den her Swift-løsning er ret elegant:

    for value in 0...100 {  
          switch (value % 3, value % 5) {  
               case (0,0): print ("FizzBuzz")  
               case (0,_): print ("Fizz")  
               case (_,0): print ("Buzz")  
               default: print String(value)  
        }  
    }
Peter Riise

Jeg benytter også altid en kode opgave under interviews. Vi har også benyttet FizzBizz. Men det kan sagtens være at den grundlæggende opgave kan være den samme, hvor man så beder kandidaten om at vise sine kundskaber på specifikke områder, f.eks. TDD/DI/IoC m.v.

Jeg er også enig i at det har meget stor værdi at se kandidaten arbejde, da det også kan give værdifulde informationer om arbejdsmetoder og tempo.

Søren B Simonsen

Eller bare med if og stadig med to kun modulus per tal, de fleste andre løsninger har mindst to modulus og er dermed langsommere

function FasterFizzBuzz() {
for (i = 1; i <= 100; i++) {
if (i % 3 == 0 ) {
if (i % 5 == 0 ) {
console.log("FizzBuzz");
} else {
console.log("Fizz");
}
} else {
if (i % 5 == 0 ) {
console.log("Buzz");
} else {
console.log(i);
}
}
}
}

Mads T. Jensen

Det der tager tid er modulus. Det er ikke hvor mange ifsætninger eller sammenligner der skal fyres af. Søren B Simonsens svar er 100% korrekt, ligesom det jeg har kogt op her;

function FastFizzBuzz() {  
    for (i = 1; i <= 100; i++) {  
        if (i % 3)   
        {  
            if (i % 5)   
                console.log(i);  
            else  
                console.log("Buzz");  
        }  
        else  
        {  
            if (i % 5)   
                console.log("Fizz");  
            else  
                console.log("FizzBuzz");  
        }  
    }  
}

og hvis man vil fjerne de 1-2 modulus pr. iteration, så lav to ekstra counters, der tæller op og en if-sætning der sætter dem til 0 ved henholdsvis 3 og 5. Det er ikke svære end det.

Theis Blickfeldt

Man skal også huske, at man skal skille sig ud fra mængden, hvis man vil gøre sig forhåbninger om at få jobbet :D

# Calculate CrossSum of a number:  
def cross_sum(number):  
    number_string = str(number);  
    number_length = len(number_string);  
    crosssum = 0;  
    for digits in range(0, number_length):  
        crosssum += int((number_string[digits]));  
    # Make recursive function-call if calculated crosssum still consists of multiple digits.  
    if(len(str(crosssum)) > 1):  
        crosssum = cross_sum(crosssum);  
    return crosssum;  
   
# Executeable Script:  
#---------------------------------  
def main(argv):  
    for i in range(1, 100 + 1):  
        number_string = str(i);  
        print_string = number_string;  
        FizzBuzz_Control_Cntr = 0;  
   
        # Check if number is divisible by 3:  
        number_crosssum = cross_sum(i);  
        if((number_crosssum == 3) or (number_crosssum == 6) or (number_crosssum == 9)):  
            print_string = "Fizz";  
            FizzBuzz_Control_Cntr += 1;  
   
        # Check if number is divisible by 5:  
        last_digit = int((number_string[len(number_string) - 1]));  
        if((last_digit == 0) or (last_digit == 5)):  
            print_string = "Buzz";  
            FizzBuzz_Control_Cntr += 1;  
   
        # Yield correct response to number:  
        if(FizzBuzz_Control_Cntr == 2):  
            print("FizzBuzz");  
        else:  
            print(print_string);  
    # Goodbye...  
    exit();
Log ind eller opret en konto for at skrive kommentarer

Partnernyheder

Welcome to a seminar on tools that help you become GDPR compliant!

Getting GDPR compliant by May 2018 implies a lot of activities covering the legal aspects, internal business processes, data management, and security technology.
28. feb 2017

Maja Rosendahl Larsen ansat hos Affecto

24. jan 2017

Introduction to Jedox – Affecto Seminar, Copenhagen

12. jan 2017