Queues
A queue is a line at the counter. People join at the back and are served from the front, so the first in is the first out. Anything that must be handled in arrival order is a queue: customers, print jobs, messages.
Why not a list?
A list can do it: append at the back, pop(0) at the front. But pop(0)
has to shift every remaining item one place left, so it gets slower as the
list grows. For a few items nobody notices. For thousands it matters.
Python's standard library has a type built for this: deque (say "deck"),
from the collections module. It is fast at both ends.
from collections import deque
line = deque()
line.append("Ada")
line.append("Grace")
line.append("Linus")
print(line)
served = line.popleft()
print("Serving", served)
print(line)
append adds at the back as usual. popleft removes and returns the item at
the front. The from collections import deque line brings the type in;
collections is part of Python, nothing to install.
The rest of deque
A deque works like a list for most things: len, in, looping, [0] for
the front and [-1] for the back. It also has appendleft and pop, so it
can be a stack too, or a line that people can jump.
from collections import deque
line = deque(["Ada", "Grace"])
line.appendleft("Linus")
print(line)
print(line[0], line[-1])
print(len(line))
Only the last few
deque(maxlen=n) keeps at most n items. Adding at the back of a full one
silently drops the item at the front, the oldest. That is exactly "the last
three things that happened".
from collections import deque
recent = deque(maxlen=3)
for item in ["apple", "bread", "milk", "eggs", "flour"]:
recent.append(item)
print(list(recent))
The queue loop
The standard shape: while the queue is not empty, take from the front, deal
with it, and possibly add new things to the back. An empty deque counts as
False, just like an empty list, so while orders: runs until it is
drained. It is how you process work that generates more work.
from collections import deque
orders = deque(["bread", "cake"])
while orders:
order = orders.popleft()
print("Making", order)
if order == "cake":
orders.append("icing")
Try it
Serve in order
arrivals is a list of customer names in the order they joined the line.
Write serve_all(arrivals) that uses a deque to serve them one at a time
from the front, and returns a list of the names in the order they were
served. Do not change arrivals itself; the starter copies it into a deque
for you.
serve_all(["Ada", "Grace", "Linus"]) -> ["Ada", "Grace", "Linus"]
It looks trivial, and it is: the point is to write the queue loop once.
from collections import deque
def serve_all(arrivals):
line = deque(arrivals)
served = []
while line:
...
return served
Move to the back
A customer at the front of the line forgot their wallet and goes to the back.
Write to_back(line) that takes a deque, moves the front item to the back,
and returns the deque you were given, not a new one. An empty deque is
returned unchanged.
to_back(deque(["Ada", "Grace", "Linus"])) -> deque(["Grace", "Linus", "Ada"])
from collections import deque
def to_back(line):
...
Recent items
Write last_n(items, n) that returns a list of the last n items of
items, in their original order, using a deque with maxlen. If there
are fewer than n items, return all of them.
last_n(["apple", "bread", "milk", "eggs"], 2) -> ["milk", "eggs"]
last_n(["apple"], 3) -> ["apple"]
from collections import deque
def last_n(items, n):
recent = deque(maxlen=n)
for item in items:
...
return list(recent)
Next: Priority queues.