0

I'm trying to solve this LeetCode problem, Print Zero Even Odd:

enter image description here enter image description here

I've attempted the following solution using threading.Condition objects:

import threading
from typing import Callable, Optional


class ZeroEvenOdd:
    def __init__(self, n: int):
        self.n = n
        self.i = 0
        self.last_printed: Optional[int] = None
        self.condition = threading.Condition()

    def zero(self, printNumber: Callable[[int], None]) -> None:
        with self.condition:
            self.condition.wait_for(lambda: self.last_printed is None or self.last_printed > 0)
            if self.done:
                return
            printNumber(0)
            self.last_printed = 0
            self.i += 1
            self.condition.notify_all()

    def even(self, printNumber: Callable[[int], None]) -> None:
        with self.condition:
            self.condition.wait_for(lambda: self.last_printed == 0 and self.i % 2 == 0)
            if self.done:
                return
            self._print_and_notify()

    def odd(self, printNumber: Callable[[int], None]) -> None:
        with self.condition:
            self.condition.wait_for(lambda: self.last_printed == 0 and self.i % 2 == 1)
            if self.done:
                return
            self._print_and_notify()

    def _print_and_notify(self) -> None:
        printNumber(self.i)
        self.last_printed = self.i
        self.condition.notify_all()

    @property
    def done(self) -> bool:
        if self.last_printed is not None and self.last_printed >= self.n:
            self.condition.release()
            self.condition.notify_all()
            return True
        return False


def printNumber(x: int) -> None:
    print(x)


zero_even_odd = ZeroEvenOdd(n=5)
threadA = threading.Thread(target=zero_even_odd.zero, args=(printNumber,))
threadB = threading.Thread(target=zero_even_odd.even, args=(printNumber,))
threadC = threading.Thread(target=zero_even_odd.odd, args=(printNumber,))


if __name__ == "__main__":
    threadA.start()
    threadB.start()
    threadC.start()

However, what I find when I run this is that it prints 0, then 1 and then hangs indefinitely:

> python print_zero_even_odd.py
0
1
^CException ignored in: <module 'threading' from '/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py'>
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 1308, in _shutdown
    lock.acquire()
KeyboardInterrupt

I'm a bit stumped why, after odd() has been called the first time, zero() is not called again. After all, after printing the number 1 in odd(), self.last_printed gets set to 1, which should trigger the wait_for() condition for the zero() method, self.last_printed > 0.

Any idea why this program is not working as intended?

Kurt Peek
  • 52,165
  • 91
  • 301
  • 526
  • 1
    Read [Python Lambdas and Variable Bindings](https://stackoverflow.com/a/2731158/7414759) – stovfl Aug 19 '19 at 06:10
  • 1
    Um... besides the possible problem about lambda, why should `zero()` be called again while there is no loop in your code...? If you use `threadA.is_alive()` to check the thread, it has completed and exited already. – Sraw Aug 19 '19 at 06:12

1 Answers1

0

As pointed out by Sraw, there was no loop in my code, so no reason to expect zero() to be called again. I ended up solving this in a different way using threading.Lock objects:

import threading
from typing import Callable, Optional


class ZeroEvenOdd:
    def __init__(self, n: int):
        self.n = n
        self.i = 0

        self.lock_zero = threading.Lock()
        self.lock_odd = threading.Lock()
        self.lock_even = threading.Lock()

        self.lock_odd.acquire()
        self.lock_even.acquire()

    def zero(self, printNumber: Callable[[int], None]) -> None:
        for _ in range(self.n):
            self.lock_zero.acquire()
            printNumber(0)

            if self.i % 2 == 0:
                self.lock_odd.release()
            else:
                self.lock_even.release()

    def even(self, printNumber: Callable[[int], None]) -> None:
        for i in range(2, self.n + 1, 2):
            self.lock_even.acquire()
            printNumber(i)
            self.i = i
            self.lock_zero.release()

    def odd(self, printNumber: Callable[[int], None]) -> None:
        for i in range(1, self.n + 1, 2):
            self.lock_odd.acquire()
            printNumber(i)
            self.i = i
            self.lock_zero.release()


def printNumber(x: int) -> None:
    print(x)


zero_even_odd = ZeroEvenOdd(n=5)
threadA = threading.Thread(target=zero_even_odd.zero, args=(printNumber,))
threadB = threading.Thread(target=zero_even_odd.even, args=(printNumber,))
threadC = threading.Thread(target=zero_even_odd.odd, args=(printNumber,))


if __name__ == "__main__":
    threadA.start()
    threadB.start()
    threadC.start()

This prints the desired output:

> python print_zero_even_odd.py
0
1
0
2
0
3
0
4
0
5

and solves the LeetCode problem in quite a fast and memory-efficient way:

enter image description here

Kurt Peek
  • 52,165
  • 91
  • 301
  • 526