Reassembling the tower clock
A restorer has a church clock in pieces and a list of which part must go back before which. Produce one order that puts everything back, or show none exists.
A church tower clock has been stripped to the frame for cleaning. The restorer's notebook says which part has to be back in before which, and nothing else.
The problem
The clock came apart into parts numbered pieces, 0 to parts - 1: great
wheel, escapement, bushings, motion work, hands. Some physically block others —
the pendulum cannot hang until the crutch is on.
The notebook records these as pairs. A pair [a, b] means piece a must go
back before piece b. A piece with no pair pointing at it can go in whenever
the restorer likes.
Produce one order in which every piece goes back into the frame without breaking a pair; any order that respects the notebook is acceptable. If the notebook contradicts itself — some piece is, through a chain of pairs, required before itself — return an empty list.
Input. parts — the number of pieces. before — a list of [a, b] pairs,
each meaning a goes in before b.
Output. A list of all parts piece numbers in a valid reassembly order, or
[] if no such order exists.
Example.
parts = 4
before = [[2, 0], [0, 3], [3, 1]] -> [2, 0, 3, 1]
Nothing points at piece 2, so it goes in first. That frees 0, which frees 3, which frees 1.
A second example, with two loose pieces, then stuck:
parts = 4
before = [[0, 1], [1, 2], [2, 1]] -> []
Pieces 0 and 3 go in without trouble. Then 1 waits for 2 and 2 waits for 1.
Two pieces are still on the bench, so the answer is the empty list — not [0, 3].
Constraints.
1 <= parts <= 20000 <= len(before) <= 50000 <= a, b < partsanda != b- the same pair may appear more than once
Hints
Hint 1
Which piece can go in first? Anything no pair points at. Once it is in, cross it out of every pair it starts — do any new pieces become free?
Hint 2
Keep a count per piece of how many pairs still point at it. A piece is ready the moment its count hits zero.
Hint 3
You never need to look for the cycle. If there is one, some count never reaches zero and the answer comes back short.
Approach
Brute force
Try every permutation of the pieces and check each against every pair. At
parts = 2000 the number of orders has more digits than the notebook has pairs.
The insight
A piece with no unmet pair pointing at it can always go in next, and fitting it can only free other pieces, never block them.
Choosing such a piece cannot be wrong: nothing waits on it, so putting it in early breaks no pair. Repeat, and either every piece is eventually freed — that is the order — or the queue runs dry while pieces remain, each waiting on another remaining piece: a cycle, so no order exists.
Algorithm
- Build
frees[a], the piecesaunblocks, andwaiting[b], the number of pairs pointing atb. Count duplicates twice on both sides. - Put every piece with
waiting == 0into a queue. - Pop a piece, append it to the order, and for each
binfrees[piece]decrementwaiting[b]; when it reaches zero, pushb. - When the queue empties, return the order if it holds all
partspieces, otherwise[].
Complexity
Time O(parts + pairs) — each piece is queued once and each pair decrements one counter once. Space O(parts + pairs) for the lists, counts and queue.
Solution
"""Reassembling the tower clock — peel off the pieces nothing is waiting on."""
from collections import deque
def solve(parts, before):
frees = [[] for _ in range(parts)]
waiting = [0] * parts
for a, b in before:
frees[a].append(b)
waiting[b] += 1 # one unit per pair, duplicates included
ready = deque(p for p in range(parts) if waiting[p] == 0)
order = []
while ready:
# Invariant: every piece in `order` has all its pairs satisfied, and
# `ready` holds exactly the pieces whose every blocker is already in.
piece = ready.popleft()
order.append(piece)
for b in frees[piece]:
waiting[b] -= 1
if waiting[b] == 0:
ready.append(b)
# A piece on a cycle keeps a positive count forever and is never appended,
# so a short order is the whole cycle test.
return order if len(order) == parts else []The cases that ran
TESTS = [
((4, [[2, 0], [0, 3], [3, 1]]), [2, 0, 3, 1]),
((4, [[0, 1], [1, 2], [2, 1]]), []), # two loose pieces, then stuck
((1, []), [0]), # a single piece
((3, [[0, 1], [1, 2], [2, 0]]), []), # every piece on the cycle
((5, [[4, 3], [3, 2], [2, 1], [1, 0]]), [4, 3, 2, 1, 0]), # chain against the numbering
((3, [[0, 1], [0, 1], [1, 2]]), [0, 1, 2]), # the same pair written twice
((2, [[1, 0]]), [1, 0]), # the shortest possible chain
((2000, [[i, i + 1] for i in range(1999)]), list(range(2000))), # the longest allowed chain
]Pitfalls
- Returning the partial order on a cycle. Compare the length against
parts; a short list such as[0, 3]looks like a valid answer and is not. - Storing
frees[a]as a set while countingwaiting[b]per pair. A duplicated pair adds two to the count but subtracts one, sobnever reaches zero and a sound notebook reports a cycle. Count pairs on both sides, or dedupe both.
Variants
- Winters on the cut — the same peeling, but a whole layer at a time, and the question is how many layers.
- Dye house shade index — the pairs are not given; they have to be read out of a sorted list first.