Leetcode 1195. Fizz Buzz Multithreaded

Problem Explanation

The FizzBuzz problem is a classic problem related to the divisibility of numbers. Here we are given a number n and we have to print numbers from 1 to n but with the following conditions:

  1. For numbers which are multiple of 3, print "fizz" instead of the number.
  2. For numbers which are multiple of 5, print "buzz" instead of the number.
  3. For numbers which are multiple of both 3 and 5, print "fizzbuzz".

For example, if n = 15, the output will be: 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz.

This problem adds an additional complexity of multithreading where, each of the four operations are executed by a different thread. Thread A will only verify if a number is divisible by 3 and output "fizz", Thread B will do the same, but for 5 and "buzz", Thread C will verify if the number is divisible by both 3 and 5 and output "fizzbuzz", and Thread D will only output the numbers.

Solution Approach

The problem can be solved by taking advantage of Semaphores which are a type of data structure used in concurrency models to manage and control the execution of threads. Semaphores are used to provide a signaling mechanism where a thread that is finished or meets a specific condition, signals other threads about the event. In this problem, we initialize 4 different Semaphores: fizzSemaphore, buzzSemaphore, fizzbuzzSemaphore, and numberSemaphore.

  1. fizzSemaphore: This Semaphore will signal the thread A to check for divisibility by 3 and output "fizz".

  2. buzzSemaphore: This Semaphore will signal the thread B to check for divisibility by 5 and output "buzz".

  3. fizzbuzzSemaphore: This Semaphore will signal the thread C to check for divisibility by both 3 and 5 and output "fizzbuzz".

  4. numberSemaphore: This Semaphore will signal the thread D to print the numbers.

The fizzbuzz method waits for fizzbuzzSemaphore and posts to numberSemaphore after outputting "fizzbuzz". Similarly, the fizz method waits for fizzSemaphore and the buzz method waits for buzzSemaphore, and both post to numberSemaphore after outputting "fizz" and "buzz", respectively.

The number method is a bit more complex, it waits for the numberSemaphore and posts either to fizzbuzzSemaphore, fizzSemaphore, buzzSemaphore or numberSemaphore, depending on the divisibility outcome.

This approach guarantees that the threads execute in the correct order, and prevents situations where threads are waiting indefinitely.

As for data structures, we are primarily using integers and Semaphores.

Java Solution

Please note: We are using semaphores from java.util.concurrent.Semaphore, as Java's standard library provides this functionality.

1
2java
3import java.util.concurrent.Semaphore;
4import java.util.function.IntConsumer;
5
6class FizzBuzz {
7    private int n;
8    private Semaphore fizzSem, buzzSem, fizzbuzzSem, numberSem;
9
10    public FizzBuzz(int n) {
11        this.n = n;
12        fizzSem = new Semaphore(0);
13        buzzSem = new Semaphore(0);
14        fizzbuzzSem = new Semaphore(0);
15        numberSem = new Semaphore(1);
16    }
17
18    void fizz(Runnable printFizz) throws InterruptedException {
19        for (int i = 1; i <= n; i++) {
20            if(i % 3 == 0 && i % 5 != 0) {
21                fizzSem.acquire();
22                printFizz.run();
23                numberSem.release();
24            }
25        }
26    }
27
28    void buzz(Runnable printBuzz) throws InterruptedException {
29        for (int i = 1; i <= n; i++) {
30            if(i % 5 == 0 && i % 3 != 0) {
31                buzzSem.acquire();
32                printBuzz.run();
33                numberSem.release();
34            }
35        }
36    }
37
38    void fizzbuzz(Runnable printFizzBuzz) throws InterruptedException {
39        for (int i = 1; i <= n; i++) {
40            if (i % 15 == 0) {
41                fizzbuzzSem.acquire();
42                printFizzBuzz.run();
43                numberSem.release();
44            }
45        }
46    }
47
48    void number(IntConsumer printNumber) throws InterruptedException {
49        for (int i = 1; i <= n; i++) {
50            numberSem.acquire();
51            if(i % 15 == 0) {
52                fizzbuzzSem.release();
53            } else if(i % 3 == 0) {
54                fizzSem.release();
55            } else if (i % 5 == 0) {
56                buzzSem.release();
57            } else {
58                printNumber.accept(i);
59                numberSem.release();
60            }
61        }
62    }
63}

Note: Due to the nature of the problem, other languages like Python, JavaScript, C++, and C# would require language-specific libraries or features to accomplish threading or multithreading, beyond the scope of basic data structures and algorithms. Also, Given Leetcode's platform restrictions, threading solutions can't be implemented in all these languages. Thus, the Java multithreaded solution is provided here.

Python Solution

The Python language does offer threading and Semaphore support via the threading module, however as mentioned earlier, designing a multithreaded solution requires a deep understanding of Python threads and Semaphores, which is beyond the scope of a basic algorithmic problem. However, a simple single-threaded FizzBuzz solution can be written in Python, as follows:

1
2python
3def fizzBuzz(n):
4    for i in range(1, n+1):
5        if i % 15 == 0:
6            print('FizzBuzz')
7        elif i % 3 == 0:
8            print('Fizz')
9        elif i % 5 == 0:
10            print('Buzz')
11        else:
12            print(i)
13fizzBuzz(15)

JavaScript Solution

JavaScript is a single-threaded language with event loop, which makes multithreaded programming not directly possible in JavaScript. However, with the help of HTML5 Web Workers, Worker Threads in Node.js, SharedArrayBuffer and Atomics one can achieve similar concurrency model as Java, but it is not that straightforward and beyond the scope of this problem. However, a simple single-threaded FizzBuzz solution can be written in JavaScript, as follows:

1
2javascript
3function fizzBuzz(n) {
4    for (let i = 1; i <= n; i++) {
5        let output = '';
6        if(i % 3 === 0) output += 'Fizz';
7        if(i % 5 === 0) output += 'Buzz';
8        console.log((output === '') ? i : output);
9    }
10}
11fizzBuzz(15);

In conclusion, the above solutions in Python, JavaScript, and Java demonstrate the logic required to solve the FizzBuzz problem. The problem is extended with multithreading in Java which makes it bit complex. Still, the problem helps in understanding the principles of multithreading and how Semaphores are used to manage and control the execution flow of threads. It's a very good problem to investigate if you're interested in exploring more about threads and concurrency.


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫