Gra FizzBuzz wykorzystywana jest na wielu rozmowach kwalifikacyjnych na programistę.
Sprawdź, czy zdasz taki test.
Wystarczy 10 minut, żeby się zapoznać z tą metodą i zwiększyć swoje szanse przy kolejnej rozmowie.
FizzBuzz to jedno z popularniejszych praktycznych zadań podczas technicznej rekrutacji na programistę. Test FizzBuzz jest zazwyczaj wykorzystywany do wstępnego odfiltrowania osób, które zwyczajnie nie potrafią programować – może wydać się to dość dziwne, ale na wstępnych rozmowach kwalifikacyjnych pojawia się zadziwiająco dużo osób, które mają problemy z rozwiązaniem nawet wyjątkowo prostych problemów programistycznych.
No dobrze, ale jaki to ma związek z dziećmi?
[SprawnyProgramista_intro /]Spis treści
- 1 Dziecięca gra FizzBuzz
- 2 Zasady gry FizzBuzz dla programistów
- 3 Pierwsze rozwiązanie problemu FizzBuzz
- 4 Co jest nie tak z moim rozwiązaniem?
- 5 Poprawiona wersja implementacji testu FizzBuzz
- 6 Na co uważać i czego unikać – główne błędy?
- 7 Alternatywna implementacja FizzBuzz #1. – konkatenacja stringów
- 8 Alternatywna implementacja FizzBuzz #2. – dodatkowa flaga boolean
- 9 Alternatywna implementacja FizzBuzz #3. – switch
- 10 Alternatywna implementacja FizzBuzz #4. – potrójny operator warunkowy (ang. ternary operator)
- 11 Alternatywna implementacja FizzBuzz #5. – rekurencja
- 12 Alternatywna implementacja FizzBuzz #6. – tablica
- 13 Alternatywna implementacja FizzBuzz #7. – wyrażenia lambda i tablica
- 14 Alternatywna implementacja FizzBuzz #8. – wyrażenia lambda i strumienie
- 15 Alternatywna implementacja FizzBuzz #9. – programowanie obiektowe
- 16 Alternatywna implementacja FizzBuzz #10. – wersja korporacyjna
- 17 TDD i testy jednostkowe
- 18 Czy to już wszystko? – różne wariacje gry FizzBuzz
- 19 Teraz Twoja kolej
- 20 Podsumowanie FizzBuzz i co dalej?
- 21 20+ BONUSOWYCH materiałów z programowania
Dziecięca gra FizzBuzz
Pierwotnie test FizzBuzz zyskał popularność jako gra przeznaczona dla dzieci, mająca na celu nauczenie ich dzielenia.
Dzieci siadały w kółeczku i kolejno musiały wymieniać liczby całkowite:
- pierwsze dziecko – jeden;
- drugie dziecko – dwa;
- trzecie – trzy;
- i tak dalej…
Cała zabawa polegała na tym, że jeżeli liczba była podzielna przez trzy, to – zamiast podania samej liczby – trzeba było krzyknąć Fizz, jeżeli była podzielna przez pięć, to Buzz, a jeżeli przez trzy i pięć, to FizzBuzz.
Świetny i wyjątkowo prosty sposób na zabawę, a jednocześnie skuteczna zachęta do nauki matematyki.
Zabawa stała się na tyle popularna, że zyskała wiele różnych odsłon, np. w formie zadań programistycznych.
Zasady gry FizzBuzz dla programistów
W podstawowej wersji dla programistów zadanie zazwyczaj przyjmuje następującą formę.
Wypisz wszystkie liczby od 1 do 100, jednak jeżeli liczba jest podzielna przez:
- trzy – wypisz „Fizz”,
- pięć – wypisz „Buzz”,
- trzy i pięć wypisz „FizzBuzz”.
Niby nic trudnego – trzy „ify”, kilka linijek kodu i problem rozwiązany. Interview zaliczone.
Ale, ale…
Nie tak szybko 🙂 Wbrew pozorom nawet na podstawie tak prostego zadania można bardzo dużo dowiedzieć się o kandydacie.
Jak się za chwilę przekonasz, w zadaniu jest ukrytych kilka pułapek programistycznych i można je rozwiązać na naprawdę wiele – w tym również błędnych – sposobów.
Pierwsze rozwiązanie problemu FizzBuzz
Zacznijmy od podstawowej implementacji, która jest jednocześnie najczęściej udzielaną odpowiedzią.
for (int i = 1; i <= 100; i++) { if (i % 3 == 0) { if (i % 5 == 0) { System.out.println("FizzBuzz"); } else { System.out.println("Fizz"); } } else if (i % 5 == 0) { System.out.println("Buzz"); } else { System.out.println(String.valueOf(i)); } }
Zadanie zostało rozwiązane, udało nam się udowodnić, że znamy podstawową składnię Javy.
Jeżeli to Ci wystarczy, możesz skończyć czytać ten tekst już w tym momencie.
Jeżeli jednak masz odrobinę większe ambicje i chciałbyś dowiedzieć się, w jaki sposób można wykazać się przed rekruterem czymś więcej, niż tylko podstawową składnią języka, to zapraszam do dalszej lektury.
Co jest nie tak z moim rozwiązaniem?
Powyższy kod zwraca poprawne wyniki – czyli robi to, co najważniejsze – działa. Jeżeli jednak chcesz pokazać się z jak najlepszej strony podczas rozmowy rekrutacyjnej – a zakładam, że tak właśnie jest – to rozłóżmy teraz wspólnie to zadanie na czynniki pierwsze i zastanówmy się, co możemy jeszcze poprawić.
Pamiętaj – nie ma czegoś takiego jak uniwersalnie idealne rozwiązanie.
Może co najwyżej istnieć bardzo dobre rozwiązanie w ramach naszych konkretnych założeń.
Przykładowo:
- inaczej będzie wyglądał kod napisany z myślą o minimalnym zużyciu pamięci;
- inaczej, jeżeli naszym głównym celem będzie czytelność rozwiązania;
- a jeszcze inaczej, gdy naszym głównym celem jest napisanie kodu, który w przyszłości będzie możliwie łatwy do rozbudowy o nowe funkcjonalności.
Dlatego, zanim pochwalimy się, że zadanie jest skończone, warto jasno zadeklarować przy jakich założeniach powstała ta implementacja i dopytać, czy nie ma potrzeby, żebyśmy dostosowali ją do specyficznych wymagań.
Pokażesz się wtedy jako osoba, która zwraca uwagę na coś więcej niż tylko jej kilka linijek kodu i umie dostosować swoje zadanie do większej całości.
My jednak musimy coś założyć, żeby móc pójść dalej. Dlatego przyjmiemy w miarę możliwości ogólne założenie, że chcemy przygotować uniwersalne rozwiązanie oraz dodatkowo pochwalić się podstawową znajomością dobrych praktyk programistycznych, przy jednoczesnym zachowaniu czytelności kodu – czyli wszystko po trochu, bez przesadnej optymalizacji pod żadnym kątem.
Poprawiona wersja implementacji testu FizzBuzz
Jeżeli przyjrzymy się bliżej temu rozwiązaniu, to dojdziemy do wniosku, że struktura „if’ów” jest źle dobrana, przez co mamy powielone sprawdzenie tych samych warunków. Wpływa to również na pogorszenie czytelności całego rozwiązania.
Jednym ze sposobów na poprawienie implementacji jest spłaszczenie struktury warunków do jednego poziomu zagnieżdżenia. Jednak, żeby to zrobić, trzeba zauważyć, że napis FizzBuzz będzie wyświetlony tylko w przypadku, gdy liczba jest podzielna przez 15.
Poprawiona implementacja mogłaby wyglądać tak:
for (int i = 1; i <= 100; i++) { if (i % 15 == 0) { System.out.println("FizzBuzz"); } else if (i % 3 == 0) { System.out.println("Fizz"); } else if (i % 5 == 0) { System.out.println("Buzz"); } else { System.out.println(i); } }
Sprawdzenie najpierw podzielności przez 15 poprawia czytelność i dodatkowo unikamy duplikacji kodu.
Jednak ten warunek nie wynika bezpośrednio z treści zadania i trzeba zauważyć poniższą zależność.
=> i%15==0
Nie jest to jednak jedyny sposób na rozwiązanie tego problemu. Prześledźmy teraz najczęściej występujące błędy oraz kilka alternatywnych rozwiązań.
Na co uważać i czego unikać – główne błędy?
- źle dobrana struktura „ifów” – omówiliśmy sobie to szczegółowo w poprzednich punktach;
- nie pchaj wszystkiego na siłę do jednego wora – nie staraj się za wszelką cenę wykorzystać całej swojej wiedzy w tym jednym zadaniu. Przekolorowane rozwiązania, w których na siłę upychasz różne możliwości, wcale nie wyglądają dobrze. Pamiętaj o jednej z ważniejszych zasadach programistycznych: KISS (ang. keep it simple, stupid),
- egzotyczne rozwiązania – staraj się zachować standardy i raczej unikaj nad wyraz twórczych rozwiązań. Przykładowo, resztę z dzielenia modulo w Javie można obliczyć, skorzystać z operatora modulo: x % y, ale jak ktoś się postara, to można poradzić sobie również bez niego: x-x/y*y.
Alternatywna implementacja FizzBuzz #1. – konkatenacja stringów
W tym rozwiązaniu, zamiast co chwilę wyświetlać wyniki po małym kawałku na standardowe wyjście, zbieramy wyniki i dopiero na samym końcu jedną komendą je wyświetlamy.
Standardowa konkatenacja stringów może być zastąpiona klasą StringBuilder – więcej na temat samej klasy StringBuilder oraz jej zalet w porównaniu do ręcznego łączenia stringów przeczytasz w podlinkowanym artykule.
for (int i = 1; i <= 100; i++) { String result = ""; if (i % 3 == 0) { result = "Fizz"; } if (i % 5 == 0) { result += "Buzz"; } if (result.isEmpty()) { result += i; } System.out.println(result); }
Alternatywna implementacja FizzBuzz #2. – dodatkowa flaga boolean
Wykorzystanie pomocniczej zmiennej jest alternatywą do sprawdzania warunku na modulo 15.
for (int i = 1; i <= 100; i++) { boolean fizzOrBuzz = false; if (i % 3 == 0) { System.out.print("Fizz"); fizzOrBuzz = true; } if (i % 5 == 0) { System.out.print("Buzz"); fizzOrBuzz = true; } if (fizzOrBuzz) { System.out.println(); } else { System.out.println(String.valueOf(i)); } }
Alternatywna implementacja FizzBuzz #3. – switch
W tej wersji implementacji całkowicie rezygnujemy z instrukcji warunkowej if i zastępujemy ją instrukcją switch.
Moim zdaniem takie rozwiązanie jest już trochę przekombinowane i powoli zaczynamy tracić na czytelności.
for (int i = 1; i <= 100; i++) { String value; switch (i % 15) { case 3: case 6: case 9: case 12: // podzielne przez 3 value = "Fizz"; break; case 5: case 10: // podzielne przez 5 value = "Buzz"; break; case 0: // podzielne przez 3 i 5 value = "FizzBuzz"; break; default: value = Integer.toString(i); } System.out.println(value); }
Alternatywna implementacja FizzBuzz #4. – potrójny operator warunkowy (ang. ternary operator)
Korzystając z potrójnego operatora warunkowego (ang. ternary operator), moglibyśmy nasze rozwiązanie zmieścić nawet w jednej linijce kodu.
Tylko czy warto? – tu już sami musicie sobie odpowiedzieć.
for (int i = 1; i <= 100; i++) { System.out.println(i % 15 != 0 ? i % 5 != 0 ? i % 3 != 0 ? String.valueOf(i) : "Fizz" : "Buzz" : "FizzBuzz"); }
Alternatywna implementacja FizzBuzz #5. – rekurencja
Rekurencja jest potężnym orężem w ręku doświadczonego programisty. Dzięki niej można w stosunkowo prosty sposób rozwiązać nawet bardzo skomplikowane problemy. Mimo wszystko nie jest to uniwersalne rozwiązanie, które rozwiąże za nas wszystkie nasze problemy.
Pamiętaj o zasadzie złotego młotka (ang. golden hammer) i z rozwagą dobieraj rozwiązania do Twojego problemu.
System.out.println(fizzBuzz(100)); String fizzBuzz(int n) { String s = ""; if (n == 0) { return s; } if ((n % 5) == 0) { s = "Buzz" + s; } if ((n % 3) == 0) { s = "Fizz" + s; } if (s.equals("")) { s = n + ""; } return fizzBuzz(n - 1) + s + "\n"; }
Alternatywna implementacja FizzBuzz #6. – tablica
W tej wersji rozwiązania problemu FizzBuzz posłużymy się tablicą.
for (int i = 1; i <= 100; i++) { String[] array = new String[]{i + "", "Fizz", "Buzz", "FizzBuzz"}; int index = (i % 3 == 0 ? 1 : 0) + (i % 5 == 0 ? 2 : 0); System.out.println(array[index]); }
Alternatywna implementacja FizzBuzz #7. – wyrażenia lambda i tablica
Gdybyś chciał popisać się znajomością wyrażeń lambda, to do zadania FizzBuzz mógłbyś wykorzystać takie rozwiązanie.
int[] x = new int[101]; Arrays.setAll(x, j -> j++); Arrays.stream(x).forEach(i -> { if (i == 0) return; String output = ""; if (i % 3 == 0) output += "Fizz"; if (i % 5 == 0) output += "Buzz"; if (output.equals("")) output += i; System.out.println(output); });
Alternatywna implementacja FizzBuzz #8. – wyrażenia lambda i strumienie
Poniższe rozwiązanie wykorzystuje wyrażenia lambda oraz strumienie java (ang. java stream).
IntStream.rangeClosed(1, 100) .mapToObj(i -> { if (i % (3 * 5) == 0) { return "FizzBuzz"; } else if (i % 3 == 0) { return "Fizz"; } else if (i % 5 == 0) { return "Buzz"; } else { return Integer.toString(i); } }) .forEach(System.out::println);
Alternatywna implementacja FizzBuzz #9. – programowanie obiektowe
Na sam koniec zostawiłem implementację w stylu OOP, gdzie wykorzystujemy programowanie obiektowe do zamodelowania rozwiązania.
Zobacz, że dzięki takiej implementacji, nie ograniczamy się na sztywno do liczb 3 i 5 oraz napisów Fizz i Buzz. Wydzielenie logiki do osobnej klasy pozwoliło przygotować bardziej uniwersalne rozwiązanie.
Sound sound = new Sound(3, "Fizz", new Sound(5, "Buzz")); for (int i = 1; i <= 100; i++) { System.out.println(sound.generate(i)); }
oraz klasa Sound.
class Sound { private final int trigger; private final String sound; private final Sound next; public Sound(int trigger, String sound, Sound next) { this.trigger = trigger; this.sound = sound; this.next = next; } public Sound(int trigger, String sound) { this(trigger, sound, null); } public String generate(int n) { StringBuilder sb = new StringBuilder(); generate(sb, n); return sb.length() == 0 ? String.valueOf(n) : sb.toString(); } private void generate(StringBuilder sb, int n) { if (n % trigger == 0) { sb.append(sound); } if (next != null) { next.generate(sb, n); } } }
Alternatywna implementacja FizzBuzz #10. – wersja korporacyjna
Jeżeli chociaż raz miałeś okazję pracować z wielkim i „ciężkim” projektem korporacyjnym, to doskonale wiesz, czego można się spodziewać po tego typu implementacji.
FizzBuzz w wersji korporacyjnej ma wykorzystane chyba wszystkie możliwe interfejsy i wzorce projektowe.
Tylko konia z rzędem temu, kto byłby w stanie napisać taki kod podczas rozmowy kwalifikacyjnej lub chociaż zrozumieć go w tym czasie 😉
Tutaj link do GitHub – FizzBuzz EnterpriseEdition.
TDD i testy jednostkowe
A co Ty na to, żeby dodatkowo przetestować swoje rozwiązanie?
Jeżeli podczas rozmowy kwalifikacyjnej dodatkowo wykażesz się znajomością dobrych praktyk i dbałością o jakość Twojego rozwiązania, może to stanowić duży plus na Twoją korzyść.
Problem FizzBuzz idealnie nadaje się do rozwiązania z wykorzystaniem testów jednostkowych i TDD (ang. test driven development).
ZOBACZ : Testowanie oprogramowania
Czy to już wszystko? – różne wariacje gry FizzBuzz
FizzBuzz jest na tyle popularnym problemem, że doczekał się wielu różnych modyfikacji i rozszerzeń – zaczynając od dość prostych, a kończąc na prawdziwych programistycznych wyzwaniach.
Poniżej kilka przykładowych wariacji:
- zmiana zakresu z 1–100 do np. 100–1000 itp;
- zmiana lub dodanie liczb, na podstawie których sprawdzamy podzielność, np. słowo Zazz dla każdej liczby podzielnej przez 10;
- zmiana systemu liczbowego, na którym operujemy, np. z dziesiętnego na ósemkowy;
- 3, 5, 7 Fizz Buzz Woof – gdzie jeżeli w sprawdzanej liczbie wystąpi nasza szukana, wyświetlamy odpowiadające jej słowo klucz – przykładowo liczba 3307 powinna być zamieniona na FizzFizzWoof .
Teraz Twoja kolej
Ja już rozwiązałem FizzBuzz – teraz Twoja kolej.
Zachęcam, żebyś spróbował zmierzyć się z tym zadaniem i podzielił się swoją implementacją. Szczególnie przydatne mogą okazać się inne niż Java języki programowania – takie jak: C/C++, C#, Python, PHP itd.
Rozwiązaniem możesz podzielić się w komentarzu poniżej lub na grupie.
Podsumowanie FizzBuzz i co dalej?
FizzBuzz jest już swego rodzaju klasykiem technicznych rozmów kwalifikacyjnych. Dzięki temu szybkiemu ćwiczeniu w mig można sprawdzić, jak kandydat porusza się w IDE oraz jakie stosuje podejście przy programowaniu. Zadanie jest na tyle uniwersalne, że niezależnie od technologii, w której pracujesz, warto się z nim zmierzyć.
Nie twierdzę jednak, że dzięki FizzBuzz uda się potwierdzić, że ktoś jest świetnym developerem, jednak z łatwością zidentyfikujemy tych słabych. A to już coś na początek procesu rekrutacji.
Jakie Ty miałeś pytania na rozmowie kwalifikacyjnej? Podeślij mi je, to wspólnie je rozwiążemy.
Jeżeli natomiast szukasz więcej przykładowych pytań rekrutacyjnych, to dołącz do newslettera i odbierz przykładową listę z zadaniami.
Chcesz przygotować fantastyczne CV i zabłysnąć na rozmowie kwalifikacyjnej? Sprawdź najnowsze e-booki:
CV Programisty oraz Rozmowa kwalifikacyjna Programisty
20+ BONUSOWYCH materiałów z programowania
e-book – „8 rzeczy, które musisz wiedzieć, żeby dostać pracę jako programista”,
e-book – „Java Cheat Sheet”,
checklista – „Pytania rekrutacyjne”
i wiele, wiele wiecej!
13 Comments
FizzBuzz to chyba tylko w USA, u nas raczej się nie stosuje. Jeszcze się nie spotkałem, żeby ktoś zadał takie pytanie. Tak samo się nie spotkałem żeby ktoś zdawał pytania przy tablicy, to też w stanach raczej tak robią.
Chyba że artykuł to tłumaczenie z angielskiego.
Cześć Jakub. To wcale nie jest tak rzadko spotykane. Zdarzyło mi się już odpowiadać na pytania przy flipcharcie i rozrysowywać swoje rozwiązanie na kartce.
Jeżeli chodzi o samo FizzBuzz, to miałem lekko zmodyfikowaną wersję – nie było to nazwane FizzBuzz, tylko trzeba było wyświetlić jakiś napis, jak liczba jest podzielna przez inną liczbę.
Artykuł nie jest tłumaczony.
mnie na rozmowie o pracę zapytano czy wiem co to jest fizzbuzz, ale nie chcieli patrzeć jak to piszę
Pojechałeś z tymi wszystkimi sposobami 🙂 Jeśli nie ma żadnych dodatkowych wskazań, to myślę, że chyba jednak klasyczne rozwiązanie najlepsze. Wrzucam rozwiązanie w Pythonie:
def fizz_buzz():
for i in range(1,101):
if i % 3 == 0 and i % 5 == 0:
print(’FizzBuzz’)
elif i % 3 == 0:
print(’Fizz’)
elif i % 5 == 0:
print(’Buzz’)
else:
print(i)
Cześć Beata. Trochę taki był cel, żeby pokazać możliwie dużo sposobów 🙂
Oczywiście, jak nie ma specyficznych wymagań, to nie ma co się wygłupiać.
ps. Dzięki za rozwiązanie w Python.
Tak samo w Polsce nie słyszałem :)) jednak u nas przechodzi tradycja + repo :))
Jednak fajnie zadanie dla własne zabawy :))
Co do pytań to miałem czasami dość nietypowe: Co czytałem ostatnio, jak się rozwijałem, czy byłem na jakiś eventach, czy mam ulubione blogi/strony które sledze + trochę kodu i rozwiązanie bugów.
Witam, czy istnieje możliwość wytłumaczenia rozwiązania obiektowego?
Pozdrawiam
Cześć. W czym dokładnie jest problem? Zobacz co dokładnie dzieje się w konstruktorze (przypisanie zmiennej 'next’) i później w metodzie 'generate’ wywołanie rekurencyjne tej samej metody.
Spróbuj to prześledzić krok po kroku, wtedy powinno to być jaśniejsze.
const fizzBuzz = n => (
(n % 3 === 0 ? 'Fizz’ : ”)
+ (n % 5 === 0 ? 'Buzz’ : ”)
) || n;
lub jak ktoś woli w jednej linicje
const fizzBuzz = n => (n % 3 === 0 ? 'Fizz’ : ”) + (n % 5 === 0 ? 'Buzz’ : ”) || n;
import { range } from 'lodash’;
const fizzBuzz = range(100).map(n => (
(n % 3 === 0 ? 'Fizz’ : ”)
+ (n % 5 === 0 ? 'Buzz’ : ”)
) || n);
lub jak ktoś woli w jednej linicje
const fizzBuzz = range(100).map(n => (n % 3 === 0 ? 'Fizz’ : ”) + (n % 5 === 0 ? 'Buzz’ : ”) || n);
W Polsce nie słyszałem, żeby się stosowało FizzBuzz’a, ale fajny sposób na sprawdzenie umiejętności programisty podczas rozmowy kwalifikacyjnej. Pracodawca od razu może sprawdzić umiejętności programisty 😀
W delphi (pascalu) tak mniej więcej:
function fizzBuzz(number: integer): string;
begin
result := inttostr(number);
if i mod 3 == 0 then
result := 'Fizz’
else if i mod 5 == 0 then
result := 'Buzz’
else if (i mod 3 == 0) and (i mod 5 == 0) then
result := 'FizzBuzz’;
end;
for i := 1 to 100 do
memo1.Lines.Add(fizzBuzz(i));
No jak się pisze na kolanie, to jednak nawet w tak prostej funkcji można popełnić błąd. 🙂
Czyli powinno być np. tak:
function fizzBuzz(number: integer): string;
begin
result := inttostr(number);
if number mod 3 == 0 then
result := 'Fizz’
else if number mod 5 == 0 then
result := 'Buzz’
else if (number mod 3 == 0) and (number mod 5 == 0) then
result := 'FizzBuzz’;
end;