2
votes

I'm trying to simulate a printer using a linked queue to manage print jobs. A print job would be processed in the order of submission, and there are 2 printers. Each print job has a number of pages, and will tie up a printer for a certain number of cycles selected by the user.

So for example, if there are 3 jobs, job 1 (5 pages), job 2 (7 pages) and job 3 (13 pages), after 6 cycles (printed pages), one printer would have job 2 on it with 1 page left, and the other would have job 3 on it with 12 pages left.

I was able to deal with 2 jobs in the queue but I don't know how to handle the third job, as I want the third job to be processed only if a printer is available.

3
You might want to look at simpy simpy.readthedocs.org/en/latest/index.html ; there is a code example very similar to what you want, simpy.readthedocs.org/en/latest/examples/carwash.html - Hugh Bothwell

3 Answers

1
votes

This is about as clear a solution as you'll ever find - three classes (PrintJob, JobQueue, and Printer) and a main() function to do the simulation:

class PrintJob:
    def __init__(self, name, pages):
        self.name = name
        self.pages = pages
        self.remaining = pages

    def tick(self):
        if self.remaining:
            self.remaining -= 1
        print("Job {}: {} / {} pages remain".format(self.name, self.remaining, self.pages))

    def is_done(self):
        return self.remaining == 0

class JobQueue:
    def __init__(self):
        self.jobs = []

    def enqueue(self, job):
        self.jobs.append(job)

    def dequeue(self):
        return self.jobs.pop(0)

    def is_empty(self):
        return not self.jobs

class Printer:
    def __init__(self, name, job_queue):
        self.name = name
        self.job_queue = job_queue
        self.current_job = None

    def tick(self):
        # make sure we have a job to work on
        if self.current_job is None:
            if self.job_queue.is_empty():
                print("Printer {} is idle".format(self.name))
                return
            else:
                self.current_job = self.job_queue.dequeue()
                print("Printer {} got job {}".format(self.name, self.current_job.name))
        # print!
        print("Printer {} is printing...".format(self.name))
        self.current_job.tick()
        # see if job is finished
        if self.current_job.is_done():
            print("Printer {} finished job {}".format(self.name, self.current_job.name))
            self.current_job = None

    def is_idle(self):
        return self.current_job is None

def main():
    # create job queue and two printers
    jobs = JobQueue()
    p1 = Printer(1, jobs)
    p2 = Printer(2, jobs)

    # add jobs
    jobs.enqueue(PrintJob(1, 5))
    jobs.enqueue(PrintJob(2, 7))
    jobs.enqueue(PrintJob(3, 13))

    # cycle until queue is empty
    tick = 0
    while not (jobs.is_empty() and p1.is_idle() and p2.is_idle()):
        tick += 1
        print("\n=== Tick {} ===".format(tick))
        p1.tick()
        p2.tick()

    print("\nAll done! Time for a pint...")

if __name__ == "__main__":
    main()

and the final result looks like

=== Tick 1 ===
Printer 1 got job 1
Printer 1 is printing...
Job 1: 4 / 5 pages remain
Printer 2 got job 2
Printer 2 is printing...
Job 2: 6 / 7 pages remain

=== Tick 2 ===
Printer 1 is printing...
Job 1: 3 / 5 pages remain
Printer 2 is printing...
Job 2: 5 / 7 pages remain

=== Tick 3 ===
Printer 1 is printing...
Job 1: 2 / 5 pages remain
Printer 2 is printing...
Job 2: 4 / 7 pages remain

=== Tick 4 ===
Printer 1 is printing...
Job 1: 1 / 5 pages remain
Printer 2 is printing...
Job 2: 3 / 7 pages remain

=== Tick 5 ===
Printer 1 is printing...
Job 1: 0 / 5 pages remain
Printer 1 finished job 1
Printer 2 is printing...
Job 2: 2 / 7 pages remain

=== Tick 6 ===
Printer 1 got job 3
Printer 1 is printing...
Job 3: 12 / 13 pages remain
Printer 2 is printing...
Job 2: 1 / 7 pages remain

=== Tick 7 ===
Printer 1 is printing...
Job 3: 11 / 13 pages remain
Printer 2 is printing...
Job 2: 0 / 7 pages remain
Printer 2 finished job 2

=== Tick 8 ===
Printer 1 is printing...
Job 3: 10 / 13 pages remain
Printer 2 is idle

=== Tick 9 ===
Printer 1 is printing...
Job 3: 9 / 13 pages remain
Printer 2 is idle

=== Tick 10 ===
Printer 1 is printing...
Job 3: 8 / 13 pages remain
Printer 2 is idle

=== Tick 11 ===
Printer 1 is printing...
Job 3: 7 / 13 pages remain
Printer 2 is idle

=== Tick 12 ===
Printer 1 is printing...
Job 3: 6 / 13 pages remain
Printer 2 is idle

=== Tick 13 ===
Printer 1 is printing...
Job 3: 5 / 13 pages remain
Printer 2 is idle

=== Tick 14 ===
Printer 1 is printing...
Job 3: 4 / 13 pages remain
Printer 2 is idle

=== Tick 15 ===
Printer 1 is printing...
Job 3: 3 / 13 pages remain
Printer 2 is idle

=== Tick 16 ===
Printer 1 is printing...
Job 3: 2 / 13 pages remain
Printer 2 is idle

=== Tick 17 ===
Printer 1 is printing...
Job 3: 1 / 13 pages remain
Printer 2 is idle

=== Tick 18 ===
Printer 1 is printing...
Job 3: 0 / 13 pages remain
Printer 1 finished job 3
Printer 2 is idle

All done! Time for a pint...
1
votes

It depends. What are you prioritizing your print job on? The time it takes to complete a printing job? The order in which jobs are received in the queue?

If it is the latter, it would be something like this:

# printer is a class that contains a state 'idle' and a method 'print'

current_job = None
while ! job_queue.empty():
  if current_job != job_queue.peak(0)
    current_job = job_queue.pop(0)

  if printer1.idle():
    printer1.print_job(current_job)
  elif printer2.idle():
    printer2.print_job(current_job)
  else:
    # do nothing
    pass

  printer1.print()
  printer2.print()

Building on this, you can also have another state variable inside you printer class called 'pages_todo' which is an integer that tells you how many pages left for the current printer object to change its idle state to true.

class Printer:
  def __init__(self):
    idle = false
    pages_todo = 0

  def print_job(pages):
    pages_todo += pages

  def print():
    pages_todo -= 1 if pages_todo > 0
1
votes

My Solution implemented with a Queue

class Queue:
    def __init__(self):
        self.items = []
        self.current_task = None

    def isEmpty(self):
        return self.items == []

    def enqueue(self, item):
        # ideally we should enqueue item by item
        self.items = item[1]
        self.current_task = item[0]

    def dequeue(self):
        return self.items.pop()

    def size(self):
        return len(self.items)

job1 = (1, list(range(5)))  # range serves as our list of pages
job2 = (2, list(range(7)))
job3 = (3, list(range(13)))

list_of_jobs = [job3, job2, job1]

# Two printers
printer1 = Queue()
printer2 = Queue()

for _ in range(6):
    if not printer1.isEmpty():
        printer1.dequeue()
    elif list_of_jobs:
        printer1.enqueue(list_of_jobs.pop())
        printer1.dequeue()

    if not printer2.isEmpty():
        printer2.dequeue()
    elif list_of_jobs:
        printer2.enqueue(list_of_jobs.pop())
        printer2.dequeue()

Output

print('printer 1 is working on {} page(s) and is on job {} while printer 2 is working on {} page(s) and is on job {}'.format(printer1.size(), printer1.current_task, printer2.size(), printer2.current_task))