Closes #11
This commit is contained in:
276
analysis/05-Message_Queue_Partitioning/readme.md
Normal file
276
analysis/05-Message_Queue_Partitioning/readme.md
Normal file
@@ -0,0 +1,276 @@
|
||||
# #05 — Message Queue Partitioning
|
||||
|
||||
## Problem
|
||||
|
||||
A communication subsystem maintains a singly linked intrusive queue of outgoing messages.
|
||||
|
||||
Each message contains a transmission identifier, a retry flag, and a pointer to the next message:
|
||||
|
||||
```cpp
|
||||
struct Message {
|
||||
uint32_t id;
|
||||
bool retry;
|
||||
Message* next;
|
||||
};
|
||||
```
|
||||
|
||||
After a transmission attempt, some messages may need to be retried.
|
||||
|
||||
Partition the original queue into two separate queues:
|
||||
|
||||
- the ready queue, containing messages that do not require another transmission attempt;
|
||||
- the retry queue, containing messages marked for retry.
|
||||
|
||||
The relative order of messages must be preserved in both queues.
|
||||
|
||||
### Requirements
|
||||
|
||||
- no dynamic memory allocation;
|
||||
- no copying of messages;
|
||||
- reuse the existing list nodes;
|
||||
- preserve the original order in both resulting queues;
|
||||
- process the queue in `O(n)` time.
|
||||
|
||||
## Example
|
||||
|
||||
### Input
|
||||
|
||||
```text
|
||||
A -> B -> C -> D
|
||||
```
|
||||
|
||||
```text
|
||||
A: retry = false
|
||||
B: retry = true
|
||||
C: retry = false
|
||||
D: retry = true
|
||||
```
|
||||
|
||||
### Result
|
||||
|
||||
Ready queue
|
||||
|
||||
```text
|
||||
A -> C
|
||||
```
|
||||
|
||||
Retry queue
|
||||
|
||||
```text
|
||||
B -> D
|
||||
```
|
||||
|
||||
The original queue is consumed during the operation, and every message must belong to exactly one of the two resulting queues.
|
||||
|
||||
---
|
||||
|
||||
# Analysis
|
||||
|
||||
At first glance, this looks like another linked list interview problem.
|
||||
|
||||
Traverse the list.
|
||||
|
||||
Check a flag.
|
||||
|
||||
Split the nodes into two lists.
|
||||
|
||||
Complexity: **O(n)**.
|
||||
|
||||
Simple.
|
||||
|
||||
Except this is one of those rare cases where the interview version is surprisingly close to a real engineering task.
|
||||
|
||||
The interesting part is not the algorithm.
|
||||
|
||||
The interesting part is what the algorithm is actually modifying.
|
||||
|
||||
---
|
||||
|
||||
## This Is Not About Two Lists
|
||||
|
||||
Each node already exists.
|
||||
|
||||
```cpp
|
||||
struct Message {
|
||||
uint32_t id;
|
||||
bool retry;
|
||||
Message* next;
|
||||
};
|
||||
```
|
||||
|
||||
No objects are created.
|
||||
|
||||
No objects are destroyed.
|
||||
|
||||
No messages are copied.
|
||||
|
||||
Only ownership changes.
|
||||
|
||||
The original outgoing queue disappears and every message becomes part of exactly one new queue.
|
||||
|
||||
That small detail changes the entire nature of the problem.
|
||||
|
||||
---
|
||||
|
||||
## The Real Challenge
|
||||
|
||||
The boolean itself is trivial.
|
||||
|
||||
```cpp
|
||||
retry == true
|
||||
```
|
||||
|
||||
is simply a classification.
|
||||
|
||||
The difficult part is maintaining the integrity of two intrusive queues while consuming a third one.
|
||||
|
||||
Every processed node must satisfy one invariant:
|
||||
|
||||
- belong to exactly one queue;
|
||||
- never be lost;
|
||||
- never appear twice;
|
||||
- never keep stale links into the original list.
|
||||
|
||||
Most bugs are not caused by the condition.
|
||||
|
||||
They are caused by pointer manipulation.
|
||||
|
||||
---
|
||||
|
||||
## Why Saving `next` Matters
|
||||
|
||||
The same pointer is used for two completely different purposes.
|
||||
|
||||
During traversal:
|
||||
|
||||
```text
|
||||
current -> next
|
||||
```
|
||||
|
||||
is how we reach the remaining nodes.
|
||||
|
||||
After insertion into a new queue:
|
||||
|
||||
```text
|
||||
current -> next
|
||||
```
|
||||
|
||||
becomes part of another list.
|
||||
|
||||
If the original `next` pointer is overwritten before it is saved, the remainder of the queue is simply lost.
|
||||
|
||||
This is one of the classic pitfalls of intrusive containers.
|
||||
|
||||
---
|
||||
|
||||
## Stable Partition
|
||||
|
||||
The requirements also say:
|
||||
|
||||
> preserve order
|
||||
|
||||
That sounds minor.
|
||||
|
||||
It isn't.
|
||||
|
||||
Appending to the head would produce:
|
||||
|
||||
```text
|
||||
D -> B
|
||||
```
|
||||
|
||||
instead of
|
||||
|
||||
```text
|
||||
B -> D
|
||||
```
|
||||
|
||||
The algorithm therefore performs a **stable partition**, preserving FIFO order in both resulting queues.
|
||||
|
||||
In a communication subsystem this is often essential because later messages may depend on earlier ones.
|
||||
|
||||
---
|
||||
|
||||
## Why No Allocation?
|
||||
|
||||
The requirement
|
||||
|
||||
```text
|
||||
no allocation
|
||||
```
|
||||
|
||||
isn't there to make the problem harder.
|
||||
|
||||
It reflects reality.
|
||||
|
||||
Communication stacks, embedded systems and real-time software often avoid dynamic allocation while processing packets or messages.
|
||||
|
||||
The messages already exist.
|
||||
|
||||
Only their position inside processing queues changes.
|
||||
|
||||
---
|
||||
|
||||
## Hidden Engineering Questions
|
||||
|
||||
The implementation itself is small.
|
||||
|
||||
The engineering questions are not.
|
||||
|
||||
For example:
|
||||
|
||||
- Who owns the original queue after partitioning?
|
||||
- Can another thread append messages during the operation?
|
||||
- Can an interrupt modify the queue?
|
||||
- What happens if the queue is already corrupted?
|
||||
- Can a message belong to multiple intrusive containers?
|
||||
- Should retry count also be updated?
|
||||
- Is there exponential backoff before retrying?
|
||||
|
||||
None of these appear in the problem statement.
|
||||
|
||||
All of them appear in production systems.
|
||||
|
||||
---
|
||||
|
||||
## What This Problem Actually Tests
|
||||
|
||||
Unlike many linked list exercises, this one evaluates something genuinely useful.
|
||||
|
||||
It tests whether a developer can safely manipulate ownership using pointers while preserving structural invariants.
|
||||
|
||||
The algorithm itself is almost secondary.
|
||||
|
||||
Correctness is everything.
|
||||
|
||||
---
|
||||
|
||||
## Key Takeaway
|
||||
|
||||
This is one of the few interview-style linked list problems that has a direct equivalent in production software.
|
||||
|
||||
Not because splitting a list is inherently interesting.
|
||||
|
||||
But because communication stacks, schedulers, networking software and embedded systems continuously reorganize intrusive queues exactly like this.
|
||||
|
||||
The interview version removes most of the surrounding system.
|
||||
|
||||
The engineering version adds ownership, invariants, concurrency and failure handling.
|
||||
|
||||
The pointer operations remain almost identical.
|
||||
|
||||
The responsibility does not.
|
||||
|
||||
---
|
||||
|
||||
## Project Perspective
|
||||
|
||||
> Exists in real engineering?
|
||||
|
||||
**Yes. Frequently.**
|
||||
|
||||
> Exists in interview form?
|
||||
|
||||
**Yes.**
|
||||
|
||||
One of the rare cases where the interview problem remains close to its real-world counterpart.
|
||||
Reference in New Issue
Block a user