Compare commits
14 Commits
3985820bc6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| fc42369e54 | |||
| 7a329c639e | |||
| 360d2c3d21 | |||
| 063f3277bb | |||
| 4cb625ab9c | |||
| eda3bf691a | |||
| 233c53fb9d | |||
| cae7127193 | |||
| 848235fc8a | |||
| 463614284f | |||
| 5f7e37b79b | |||
| fc364dd739 | |||
| 939935c258 | |||
| e1ef02c4a6 |
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.user
|
||||||
BIN
analysis/04-Reverse_Linked_List/diagrams/00_initial_state.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,35 @@
|
|||||||
|
@startuml
|
||||||
|
title Step 0 — Initial State
|
||||||
|
|
||||||
|
object "Node 1" as n1 {
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 2" as n2 {
|
||||||
|
value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 3" as n3 {
|
||||||
|
value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
n1 --> n2 : next
|
||||||
|
n2 --> n3 : next
|
||||||
|
n3 --> "null" : next
|
||||||
|
|
||||||
|
object "previous" as prev
|
||||||
|
object "current" as cur
|
||||||
|
|
||||||
|
prev --> "null"
|
||||||
|
cur --> n1
|
||||||
|
|
||||||
|
note bottom
|
||||||
|
Before the loop starts:
|
||||||
|
|
||||||
|
previous = null
|
||||||
|
current = head
|
||||||
|
|
||||||
|
The original list is still intact.
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
BIN
analysis/04-Reverse_Linked_List/diagrams/01_save_next.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
38
analysis/04-Reverse_Linked_List/diagrams/01_save_next.puml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
@startuml
|
||||||
|
title Step 1 — Save next
|
||||||
|
|
||||||
|
object "Node 1" as n1 {
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 2" as n2 {
|
||||||
|
value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 3" as n3 {
|
||||||
|
value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
n1 --> n2 : next
|
||||||
|
n2 --> n3 : next
|
||||||
|
n3 --> "null" : next
|
||||||
|
|
||||||
|
object "previous" as prev
|
||||||
|
object "current" as cur
|
||||||
|
object "next" as nxt
|
||||||
|
|
||||||
|
prev --> "null"
|
||||||
|
cur --> n1
|
||||||
|
nxt --> n2
|
||||||
|
|
||||||
|
note right of nxt
|
||||||
|
next = current->next
|
||||||
|
|
||||||
|
We save the next node before changing
|
||||||
|
current->next.
|
||||||
|
|
||||||
|
Without this temporary pointer, the rest
|
||||||
|
of the original list would become unreachable.
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,38 @@
|
|||||||
|
@startuml
|
||||||
|
title Step 2 — Reverse current->next
|
||||||
|
|
||||||
|
object "Node 1" as n1 {
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 2" as n2 {
|
||||||
|
value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 3" as n3 {
|
||||||
|
value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
n1 --> "null" : next
|
||||||
|
n2 --> n3 : next
|
||||||
|
n3 --> "null" : next
|
||||||
|
|
||||||
|
object "previous" as prev
|
||||||
|
object "current" as cur
|
||||||
|
object "next" as nxt
|
||||||
|
|
||||||
|
prev --> "null"
|
||||||
|
cur --> n1
|
||||||
|
nxt --> n2
|
||||||
|
|
||||||
|
note right of n1
|
||||||
|
current->next = previous
|
||||||
|
|
||||||
|
Node 1 no longer points to Node 2.
|
||||||
|
It now points to the already reversed part.
|
||||||
|
|
||||||
|
At the first iteration, the reversed part is empty,
|
||||||
|
so Node 1 points to null.
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
BIN
analysis/04-Reverse_Linked_List/diagrams/03_move_pointers.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,39 @@
|
|||||||
|
@startuml
|
||||||
|
title Step 3 — Move previous and current
|
||||||
|
|
||||||
|
object "Node 1" as n1 {
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 2" as n2 {
|
||||||
|
value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 3" as n3 {
|
||||||
|
value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
n1 --> "null" : next
|
||||||
|
n2 --> n3 : next
|
||||||
|
n3 --> "null" : next
|
||||||
|
|
||||||
|
object "previous" as prev
|
||||||
|
object "current" as cur
|
||||||
|
|
||||||
|
prev --> n1
|
||||||
|
cur --> n2
|
||||||
|
|
||||||
|
note right
|
||||||
|
previous = current
|
||||||
|
current = next
|
||||||
|
|
||||||
|
The reversed part is now:
|
||||||
|
|
||||||
|
1 -> null
|
||||||
|
|
||||||
|
The remaining original part is still:
|
||||||
|
|
||||||
|
2 -> 3 -> null
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
BIN
analysis/04-Reverse_Linked_List/diagrams/04_second_iteration.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,37 @@
|
|||||||
|
@startuml
|
||||||
|
title Step 4 — Second Iteration After Reversing Node 2
|
||||||
|
|
||||||
|
object "Node 1" as n1 {
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 2" as n2 {
|
||||||
|
value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 3" as n3 {
|
||||||
|
value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
n2 --> n1 : next
|
||||||
|
n1 --> "null" : next
|
||||||
|
n3 --> "null" : next
|
||||||
|
|
||||||
|
object "previous" as prev
|
||||||
|
object "current" as cur
|
||||||
|
object "next" as nxt
|
||||||
|
|
||||||
|
prev --> n2
|
||||||
|
cur --> n3
|
||||||
|
nxt --> n3
|
||||||
|
|
||||||
|
note bottom
|
||||||
|
After processing Node 2:
|
||||||
|
|
||||||
|
2 -> 1 -> null
|
||||||
|
|
||||||
|
The reversed part grows from the front.
|
||||||
|
The remaining part starts at current.
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
BIN
analysis/04-Reverse_Linked_List/diagrams/05_final_state.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
35
analysis/04-Reverse_Linked_List/diagrams/05_final_state.puml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
@startuml
|
||||||
|
title Step 5 — Final State
|
||||||
|
|
||||||
|
object "Node 1" as n1 {
|
||||||
|
value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 2" as n2 {
|
||||||
|
value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
object "Node 3" as n3 {
|
||||||
|
value = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
n3 --> n2 : next
|
||||||
|
n2 --> n1 : next
|
||||||
|
n1 --> "null" : next
|
||||||
|
|
||||||
|
object "head" as head
|
||||||
|
object "previous" as prev
|
||||||
|
object "current" as cur
|
||||||
|
|
||||||
|
head --> n3
|
||||||
|
prev --> n3
|
||||||
|
cur --> "null"
|
||||||
|
|
||||||
|
note right of head
|
||||||
|
When current becomes null,
|
||||||
|
previous points to the new head.
|
||||||
|
|
||||||
|
head = previous
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
58
analysis/04-Reverse_Linked_List/diagrams/README.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Reverse Linked List — PlantUML Diagrams
|
||||||
|
|
||||||
|
This directory contains PlantUML diagrams for the three-pointer linked list reversal algorithm.
|
||||||
|
|
||||||
|
The diagrams use a small list:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 -> 2 -> 3 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
and show how it becomes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
3 -> 2 -> 1 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `00_initial_state.puml` — initial state before the loop
|
||||||
|
- `01_save_next.puml` — saving `next = current->next`
|
||||||
|
- `02_reverse_current_link.puml` — reversing `current->next`
|
||||||
|
- `03_move_pointers.puml` — moving `previous` and `current`
|
||||||
|
- `04_second_iteration.puml` — state after the second node is processed
|
||||||
|
- `05_final_state.puml` — final state after the loop
|
||||||
|
|
||||||
|
## Generate PNG Files
|
||||||
|
|
||||||
|
```sh
|
||||||
|
plantuml diagrams/*.puml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generate SVG Files
|
||||||
|
|
||||||
|
```sh
|
||||||
|
plantuml -tsvg diagrams/*.puml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Idea
|
||||||
|
|
||||||
|
During the loop, the list is logically split into two parts:
|
||||||
|
|
||||||
|
- `previous` points to the already reversed part
|
||||||
|
- `current` points to the node currently being processed
|
||||||
|
- `next` temporarily preserves access to the remaining original list
|
||||||
|
|
||||||
|
The key operation is:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
current->next = previous;
|
||||||
|
```
|
||||||
|
|
||||||
|
But this is only safe after saving:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* next = current->next;
|
||||||
|
```
|
||||||
|
|
||||||
|
Otherwise the remaining part of the original list would be lost.
|
||||||
74
analysis/04-Reverse_Linked_List/exapmle/reverse-linked-list/.gitignore
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# This file is used to ignore files which are generated
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
*~
|
||||||
|
*.autosave
|
||||||
|
*.a
|
||||||
|
*.core
|
||||||
|
*.moc
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*_pch.h.cpp
|
||||||
|
*_resource.rc
|
||||||
|
*.qm
|
||||||
|
.#*
|
||||||
|
*.*#
|
||||||
|
core
|
||||||
|
!core/
|
||||||
|
tags
|
||||||
|
.DS_Store
|
||||||
|
.directory
|
||||||
|
*.debug
|
||||||
|
Makefile*
|
||||||
|
*.prl
|
||||||
|
*.app
|
||||||
|
moc_*.cpp
|
||||||
|
ui_*.h
|
||||||
|
qrc_*.cpp
|
||||||
|
Thumbs.db
|
||||||
|
*.res
|
||||||
|
*.rc
|
||||||
|
/.qmake.cache
|
||||||
|
/.qmake.stash
|
||||||
|
|
||||||
|
# qtcreator generated files
|
||||||
|
*.pro.user*
|
||||||
|
CMakeLists.txt.user*
|
||||||
|
|
||||||
|
# xemacs temporary files
|
||||||
|
*.flc
|
||||||
|
|
||||||
|
# Vim temporary files
|
||||||
|
.*.swp
|
||||||
|
|
||||||
|
# Visual Studio generated files
|
||||||
|
*.ib_pdb_index
|
||||||
|
*.idb
|
||||||
|
*.ilk
|
||||||
|
*.pdb
|
||||||
|
*.sln
|
||||||
|
*.suo
|
||||||
|
*.vcproj
|
||||||
|
*vcproj.*.*.user
|
||||||
|
*.ncb
|
||||||
|
*.sdf
|
||||||
|
*.opensdf
|
||||||
|
*.vcxproj
|
||||||
|
*vcxproj.*
|
||||||
|
|
||||||
|
# MinGW generated files
|
||||||
|
*.Debug
|
||||||
|
*.Release
|
||||||
|
|
||||||
|
# Python byte code
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
# --------
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Reverse Linked List Example
|
||||||
|
|
||||||
|
This directory contains a small standalone C++ example for the classic three-pointer linked list reversal algorithm.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
g++ -std=c++17 -Wall -Wextra -pedantic main.cpp -o reverse_linked_list
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./reverse_linked_list
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Output
|
||||||
|
|
||||||
|
```text
|
||||||
|
Original list:
|
||||||
|
1 -> 2 -> 3 -> 4 -> 5 -> null
|
||||||
|
|
||||||
|
Reversed list:
|
||||||
|
5 -> 4 -> 3 -> 2 -> 1 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Memory walkthrough
|
||||||
|
|
||||||
|
see memory_walkthrough.md
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
/**
|
||||||
|
* @file main.cpp
|
||||||
|
* @brief Demonstrates the classic three-pointer algorithm for reversing a singly linked list.
|
||||||
|
*
|
||||||
|
* This example is intentionally small and self-contained.
|
||||||
|
* It is not meant to show that reversing linked lists is a common production task.
|
||||||
|
* Instead, it documents the interview pattern clearly enough that a reader unfamiliar
|
||||||
|
* with it can compile the program, run it, and inspect the output.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <initializer_list>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A minimal singly linked list node.
|
||||||
|
*
|
||||||
|
* Each node stores an integer value and a pointer to the next node.
|
||||||
|
* The last node in the list has @c next equal to @c nullptr.
|
||||||
|
*/
|
||||||
|
struct Node {
|
||||||
|
int value; ///< Payload stored in the node.
|
||||||
|
Node *next; ///< Pointer to the next node, or nullptr for the last node.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Appends a new value to the end of the list.
|
||||||
|
*
|
||||||
|
* @param head Reference to the head pointer of the list.
|
||||||
|
* @param value Value to store in the new node.
|
||||||
|
*
|
||||||
|
* This helper is used only to build the demonstration list.
|
||||||
|
* It keeps the example simple and avoids using STL containers for the list itself,
|
||||||
|
* because the goal is to demonstrate raw pointer manipulation.
|
||||||
|
*/
|
||||||
|
void appendNode (Node *&head, int value) {
|
||||||
|
Node *node = new Node{value, nullptr};
|
||||||
|
|
||||||
|
if (head == nullptr) {
|
||||||
|
head = node;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node *current = head;
|
||||||
|
|
||||||
|
while (current->next != nullptr)
|
||||||
|
current = current->next;
|
||||||
|
|
||||||
|
current->next = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a linked list from an initializer list.
|
||||||
|
*
|
||||||
|
* @param values Values to insert into the list in the given order.
|
||||||
|
* @return Pointer to the first node of the created list.
|
||||||
|
*
|
||||||
|
* The caller owns the returned list and must release it with freeList().
|
||||||
|
*/
|
||||||
|
Node *createList (std::initializer_list<int> values) {
|
||||||
|
Node *head = nullptr;
|
||||||
|
|
||||||
|
for (int value : values)
|
||||||
|
appendNode (head, value);
|
||||||
|
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Prints the list without modifying it.
|
||||||
|
*
|
||||||
|
* @param head Pointer to the first node of the list.
|
||||||
|
*
|
||||||
|
* Output example:
|
||||||
|
* @code
|
||||||
|
* 1 -> 2 -> 3 -> 4 -> null
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
void printList (const Node *head) {
|
||||||
|
const Node *current = head;
|
||||||
|
|
||||||
|
while (current != nullptr) {
|
||||||
|
std::cout << current->value << " -> ";
|
||||||
|
current = current->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "null" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reverses a singly linked list in place.
|
||||||
|
*
|
||||||
|
* @param head Pointer to the first node of the original list.
|
||||||
|
* @return Pointer to the first node of the reversed list.
|
||||||
|
*
|
||||||
|
* This is the classic three-pointer interview algorithm.
|
||||||
|
*
|
||||||
|
* The three pointers are:
|
||||||
|
*
|
||||||
|
* - @c previous — the already reversed part of the list
|
||||||
|
* - @c current — the node we are processing right now
|
||||||
|
* - @c next — the original next node saved before we overwrite @c current->next
|
||||||
|
*
|
||||||
|
* Why @c next is necessary:
|
||||||
|
*
|
||||||
|
* In a singly linked list, each node only knows where the next node is.
|
||||||
|
* When we execute:
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* current->next = previous;
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* we destroy the original forward link.
|
||||||
|
* Without saving it first, the rest of the list would be lost.
|
||||||
|
*
|
||||||
|
* The algorithm works by moving one node at a time from the original forward chain
|
||||||
|
* into the reversed chain.
|
||||||
|
*
|
||||||
|
* Initial state:
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* previous = null
|
||||||
|
* current = 1 -> 2 -> 3 -> 4 -> null
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* After processing node 1:
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* previous = 1 -> null
|
||||||
|
* current = 2 -> 3 -> 4 -> null
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* After processing node 2:
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* previous = 2 -> 1 -> null
|
||||||
|
* current = 3 -> 4 -> null
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* When @c current becomes @c nullptr, @c previous points to the new head.
|
||||||
|
*
|
||||||
|
* Complexity:
|
||||||
|
*
|
||||||
|
* - Time: O(n), because each node is visited once.
|
||||||
|
* - Extra memory: O(1), because only a fixed number of pointers is used.
|
||||||
|
*/
|
||||||
|
Node *reverseList (Node *head) {
|
||||||
|
Node *previous = nullptr;
|
||||||
|
Node *current = head;
|
||||||
|
|
||||||
|
while (current != nullptr) {
|
||||||
|
/*
|
||||||
|
* Save the original next node before changing current->next.
|
||||||
|
* Without this line, the rest of the list would become unreachable.
|
||||||
|
*/
|
||||||
|
Node *next = current->next;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reverse the direction of the link.
|
||||||
|
* The current node now points to the already reversed part.
|
||||||
|
*/
|
||||||
|
current->next = previous;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Move previous forward.
|
||||||
|
* The current node becomes the new head of the reversed part.
|
||||||
|
*/
|
||||||
|
previous = current;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Continue with the node that originally followed current.
|
||||||
|
*/
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Releases all nodes in the list.
|
||||||
|
*
|
||||||
|
* @param head Pointer to the first node of the list.
|
||||||
|
*
|
||||||
|
* This function is separated from the reversal and printing logic.
|
||||||
|
* It exists only because this example uses raw @c new to keep the node structure explicit.
|
||||||
|
*/
|
||||||
|
void freeList (Node *head) {
|
||||||
|
Node *current = head;
|
||||||
|
|
||||||
|
while (current != nullptr) {
|
||||||
|
Node *next = current->next;
|
||||||
|
delete current;
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Program entry point.
|
||||||
|
*
|
||||||
|
* Builds a small list, prints it, reverses it, prints it again,
|
||||||
|
* and finally releases all allocated nodes.
|
||||||
|
*/
|
||||||
|
int main() {
|
||||||
|
Node *list = createList ({1, 2, 3, 4, 5});
|
||||||
|
|
||||||
|
std::cout << "Original list:" << std::endl;
|
||||||
|
printList (list);
|
||||||
|
|
||||||
|
list = reverseList (list);
|
||||||
|
|
||||||
|
std::cout << "\nReversed list:" << std::endl;
|
||||||
|
printList (list);
|
||||||
|
|
||||||
|
freeList (list);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,827 @@
|
|||||||
|
# Reverse Linked List — Memory Walkthrough
|
||||||
|
|
||||||
|
This walkthrough explains the classic three-pointer linked list reversal using a memory-oriented view.
|
||||||
|
|
||||||
|
The goal is not only to show that the algorithm works, but also to show what happens to:
|
||||||
|
|
||||||
|
- stack variables
|
||||||
|
- heap nodes
|
||||||
|
- `next` fields inside each node
|
||||||
|
|
||||||
|
The example list contains three nodes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 -> 2 -> 3 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
For clarity, fake addresses are used:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Node 1: 0x1000
|
||||||
|
Node 2: 0x2000
|
||||||
|
Node 3: 0x3000
|
||||||
|
```
|
||||||
|
|
||||||
|
These addresses are illustrative only.
|
||||||
|
A real program will use different addresses.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Algorithm
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* reverseList(Node* head) {
|
||||||
|
Node* previous = nullptr;
|
||||||
|
Node* current = head;
|
||||||
|
|
||||||
|
while(current != nullptr) {
|
||||||
|
Node* next = current->next;
|
||||||
|
|
||||||
|
current->next = previous;
|
||||||
|
|
||||||
|
previous = current;
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The three important pointers are:
|
||||||
|
|
||||||
|
| Pointer | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `previous` | Head of the already reversed part |
|
||||||
|
| `current` | Node currently being processed |
|
||||||
|
| `next` | Saved pointer to the remaining original list |
|
||||||
|
|
||||||
|
The most important rule is:
|
||||||
|
|
||||||
|
> Save `next` before changing `current->next`.
|
||||||
|
|
||||||
|
Otherwise the rest of the original list may become unreachable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 0 — Initial State
|
||||||
|
|
||||||
|
Before the loop starts:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* previous = nullptr;
|
||||||
|
Node* current = head;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| head | 0x1000 |
|
||||||
|
| previous | nullptr |
|
||||||
|
| current | 0x1000 |
|
||||||
|
| next | not set |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heap
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=2000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=3000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical view
|
||||||
|
|
||||||
|
```text
|
||||||
|
head/current
|
||||||
|
|
|
||||||
|
v
|
||||||
|
1 -> 2 -> 3 -> null
|
||||||
|
|
||||||
|
previous -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point, nothing has been reversed yet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1 — Save `next`
|
||||||
|
|
||||||
|
Inside the first loop iteration:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* next = current->next;
|
||||||
|
```
|
||||||
|
|
||||||
|
`current` points to Node 1.
|
||||||
|
`current->next` points to Node 2.
|
||||||
|
|
||||||
|
So:
|
||||||
|
|
||||||
|
```text
|
||||||
|
next = 0x2000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| head | 0x1000 |
|
||||||
|
| previous | nullptr |
|
||||||
|
| current | 0x1000 |
|
||||||
|
| next | 0x2000 |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heap
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=2000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=3000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
Nothing in the heap changed yet.
|
||||||
|
|
||||||
|
The `next` stack variable only saves access to the rest of the list.
|
||||||
|
|
||||||
|
Without this temporary pointer, Node 2 and Node 3 could be lost after the next operation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2 — Reverse `current->next`
|
||||||
|
|
||||||
|
Now the algorithm changes the link:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
current->next = previous;
|
||||||
|
```
|
||||||
|
|
||||||
|
`current` is Node 1.
|
||||||
|
`previous` is `nullptr`.
|
||||||
|
|
||||||
|
So Node 1 no longer points to Node 2.
|
||||||
|
It now points to `nullptr`.
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| head | 0x1000 |
|
||||||
|
| previous | nullptr |
|
||||||
|
| current | 0x1000 |
|
||||||
|
| next | 0x2000 |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heap
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=3000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical view
|
||||||
|
|
||||||
|
```text
|
||||||
|
current
|
||||||
|
|
|
||||||
|
v
|
||||||
|
1 -> null
|
||||||
|
|
||||||
|
next
|
||||||
|
|
|
||||||
|
v
|
||||||
|
2 -> 3 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the key mutation.
|
||||||
|
|
||||||
|
The original list is now split into two logical parts:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Reversed part:
|
||||||
|
1 -> null
|
||||||
|
|
||||||
|
Remaining original part:
|
||||||
|
2 -> 3 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3 — Move `previous`
|
||||||
|
|
||||||
|
The algorithm advances the reversed part:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
previous = current;
|
||||||
|
```
|
||||||
|
|
||||||
|
`previous` now points to Node 1.
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| head | 0x1000 |
|
||||||
|
| previous | 0x1000 |
|
||||||
|
| current | 0x1000 |
|
||||||
|
| next | 0x2000 |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heap
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=3000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical view
|
||||||
|
|
||||||
|
```text
|
||||||
|
previous/current
|
||||||
|
|
|
||||||
|
v
|
||||||
|
1 -> null
|
||||||
|
|
||||||
|
next
|
||||||
|
|
|
||||||
|
v
|
||||||
|
2 -> 3 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
The reversed part now officially starts at `previous`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4 — Move `current`
|
||||||
|
|
||||||
|
The algorithm continues with the saved next node:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
current = next;
|
||||||
|
```
|
||||||
|
|
||||||
|
`current` now points to Node 2.
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| head | 0x1000 |
|
||||||
|
| previous | 0x1000 |
|
||||||
|
| current | 0x2000 |
|
||||||
|
| next | 0x2000 |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heap
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=3000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical view
|
||||||
|
|
||||||
|
```text
|
||||||
|
previous
|
||||||
|
|
|
||||||
|
v
|
||||||
|
1 -> null
|
||||||
|
|
||||||
|
current
|
||||||
|
|
|
||||||
|
v
|
||||||
|
2 -> 3 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
The first iteration is complete.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5 — Second Iteration: Save `next`
|
||||||
|
|
||||||
|
The loop repeats.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* next = current->next;
|
||||||
|
```
|
||||||
|
|
||||||
|
`current` points to Node 2.
|
||||||
|
Node 2 points to Node 3.
|
||||||
|
|
||||||
|
So:
|
||||||
|
|
||||||
|
```text
|
||||||
|
next = 0x3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| head | 0x1000 |
|
||||||
|
| previous | 0x1000 |
|
||||||
|
| current | 0x2000 |
|
||||||
|
| next | 0x3000 |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heap
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=3000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
Again, the heap has not changed yet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6 — Second Iteration: Reverse Link
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
current->next = previous;
|
||||||
|
```
|
||||||
|
|
||||||
|
`current` is Node 2.
|
||||||
|
`previous` is Node 1.
|
||||||
|
|
||||||
|
So Node 2 now points back to Node 1.
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| head | 0x1000 |
|
||||||
|
| previous | 0x1000 |
|
||||||
|
| current | 0x2000 |
|
||||||
|
| next | 0x3000 |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heap
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=1000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical view
|
||||||
|
|
||||||
|
```text
|
||||||
|
current
|
||||||
|
|
|
||||||
|
v
|
||||||
|
2 -> 1 -> null
|
||||||
|
|
||||||
|
next
|
||||||
|
|
|
||||||
|
v
|
||||||
|
3 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
The reversed part will become:
|
||||||
|
|
||||||
|
```text
|
||||||
|
2 -> 1 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
after `previous` moves to Node 2.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 7 — Second Iteration: Move Pointers
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
previous = current;
|
||||||
|
current = next;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| head | 0x1000 |
|
||||||
|
| previous | 0x2000 |
|
||||||
|
| current | 0x3000 |
|
||||||
|
| next | 0x3000 |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heap
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=1000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical view
|
||||||
|
|
||||||
|
```text
|
||||||
|
previous
|
||||||
|
|
|
||||||
|
v
|
||||||
|
2 -> 1 -> null
|
||||||
|
|
||||||
|
current
|
||||||
|
|
|
||||||
|
v
|
||||||
|
3 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
Now two nodes are reversed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 8 — Third Iteration: Save `next`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* next = current->next;
|
||||||
|
```
|
||||||
|
|
||||||
|
`current` is Node 3.
|
||||||
|
Node 3 points to `nullptr`.
|
||||||
|
|
||||||
|
So:
|
||||||
|
|
||||||
|
```text
|
||||||
|
next = nullptr
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| head | 0x1000 |
|
||||||
|
| previous | 0x2000 |
|
||||||
|
| current | 0x3000 |
|
||||||
|
| next | nullptr |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heap
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=1000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 9 — Third Iteration: Reverse Link
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
current->next = previous;
|
||||||
|
```
|
||||||
|
|
||||||
|
`current` is Node 3.
|
||||||
|
`previous` is Node 2.
|
||||||
|
|
||||||
|
So Node 3 now points to Node 2.
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| head | 0x1000 |
|
||||||
|
| previous | 0x2000 |
|
||||||
|
| current | 0x3000 |
|
||||||
|
| next | nullptr |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heap
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=1000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=2000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical view
|
||||||
|
|
||||||
|
```text
|
||||||
|
current
|
||||||
|
|
|
||||||
|
v
|
||||||
|
3 -> 2 -> 1 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
The whole list is now reversed, but the loop still needs to update the stack pointers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 10 — Third Iteration: Move Pointers
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
previous = current;
|
||||||
|
current = next;
|
||||||
|
```
|
||||||
|
|
||||||
|
Since `next` is `nullptr`, `current` becomes `nullptr`.
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| head | 0x1000 |
|
||||||
|
| previous | 0x3000 |
|
||||||
|
| current | nullptr |
|
||||||
|
| next | nullptr |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Heap
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=1000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=2000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logical view
|
||||||
|
|
||||||
|
```text
|
||||||
|
previous
|
||||||
|
|
|
||||||
|
v
|
||||||
|
3 -> 2 -> 1 -> null
|
||||||
|
|
||||||
|
current -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
The loop condition fails:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
while(current != nullptr)
|
||||||
|
```
|
||||||
|
|
||||||
|
because `current` is now `nullptr`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 11 — Return New Head
|
||||||
|
|
||||||
|
At the end:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
return previous;
|
||||||
|
```
|
||||||
|
|
||||||
|
`previous` points to Node 3.
|
||||||
|
|
||||||
|
Node 3 is the new head of the reversed list.
|
||||||
|
|
||||||
|
### Final stack view
|
||||||
|
|
||||||
|
```text
|
||||||
|
+----------+----------+
|
||||||
|
| Variable | Value |
|
||||||
|
+----------+----------+
|
||||||
|
| old head | 0x1000 |
|
||||||
|
| new head | 0x3000 |
|
||||||
|
+----------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Final heap view
|
||||||
|
|
||||||
|
```text
|
||||||
|
0x3000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 3 | next=2000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x2000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 2 | next=1000 |
|
||||||
|
+-----------+-----------+
|
||||||
|
|
||||||
|
0x1000
|
||||||
|
+-----------+-----------+
|
||||||
|
| value = 1 | next=null |
|
||||||
|
+-----------+-----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Final logical view
|
||||||
|
|
||||||
|
```text
|
||||||
|
new head
|
||||||
|
|
|
||||||
|
v
|
||||||
|
3 -> 2 -> 1 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why the Temporary `next` Pointer Matters
|
||||||
|
|
||||||
|
This line is not optional:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* next = current->next;
|
||||||
|
```
|
||||||
|
|
||||||
|
Without it, this operation:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
current->next = previous;
|
||||||
|
```
|
||||||
|
|
||||||
|
would overwrite the only pointer to the remaining original list.
|
||||||
|
|
||||||
|
For example, at the beginning:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 -> 2 -> 3 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
If Node 1 is changed to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
before saving Node 2, then Node 2 and Node 3 are no longer reachable from any local variable.
|
||||||
|
|
||||||
|
That is why the algorithm always follows this order:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* next = current->next; // preserve the remaining list
|
||||||
|
current->next = previous; // reverse the link
|
||||||
|
previous = current; // grow the reversed part
|
||||||
|
current = next; // continue with the remaining part
|
||||||
|
```
|
||||||
|
|
||||||
|
The order is the algorithm.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
During the algorithm:
|
||||||
|
|
||||||
|
- `previous` points to the already reversed part.
|
||||||
|
- `current` points to the node being processed.
|
||||||
|
- `next` preserves access to the not-yet-processed part.
|
||||||
|
- Only one `next` field is modified per iteration.
|
||||||
|
- No nodes are copied.
|
||||||
|
- No new list is allocated.
|
||||||
|
- The original nodes are relinked in-place.
|
||||||
|
|
||||||
|
The algorithm is small, but it is easy to get wrong because it mutates the structure while traversing it.
|
||||||
|
|
||||||
|
That is why a memory-level walkthrough is often more useful than just showing the final code.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Original list:
|
||||||
|
1 -> 2 -> 3 -> 4 -> 5 -> null
|
||||||
|
|
||||||
|
Reversed list:
|
||||||
|
5 -> 4 -> 3 -> 2 -> 1 -> null
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
TEMPLATE = app
|
||||||
|
CONFIG += console c++17
|
||||||
|
CONFIG -= app_bundle
|
||||||
|
CONFIG -= qt
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
main.cpp
|
||||||
260
analysis/04-Reverse_Linked_List/readme.md
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
# #04 — Reverse Linked List: Academic Exercise
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
A classic interview question:
|
||||||
|
|
||||||
|
Given a singly linked list:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Node {
|
||||||
|
int value;
|
||||||
|
Node* next;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Reverse the list:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 -> 2 -> 3 -> 4 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
into:
|
||||||
|
|
||||||
|
```text
|
||||||
|
4 -> 3 -> 2 -> 1 -> null
|
||||||
|
```
|
||||||
|
|
||||||
|
using:
|
||||||
|
|
||||||
|
* O(n) time
|
||||||
|
* O(1) additional memory
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typical Interview Solution
|
||||||
|
|
||||||
|
The standard solution uses three pointers:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node* previous = nullptr;
|
||||||
|
Node* current = head;
|
||||||
|
|
||||||
|
while(current) {
|
||||||
|
Node* next = current->next;
|
||||||
|
|
||||||
|
current->next = previous;
|
||||||
|
|
||||||
|
previous = current;
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
head = previous;
|
||||||
|
```
|
||||||
|
|
||||||
|
The candidate is expected to produce this solution quickly and correctly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What This Actually Tests
|
||||||
|
|
||||||
|
Despite its popularity, this problem tests a surprisingly narrow set of skills.
|
||||||
|
|
||||||
|
Primarily:
|
||||||
|
|
||||||
|
* Pointer manipulation
|
||||||
|
* Attention to detail
|
||||||
|
* Familiarity with linked lists
|
||||||
|
* Prior exposure to a common interview pattern
|
||||||
|
|
||||||
|
In many cases, prior exposure matters more than reasoning.
|
||||||
|
|
||||||
|
A candidate who has seen the problem twenty times may solve it in under a minute.
|
||||||
|
|
||||||
|
A senior engineer with years of production experience may need significantly longer if they have never encountered this specific exercise before.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why This Is Rare In Real Engineering
|
||||||
|
|
||||||
|
The interesting question is:
|
||||||
|
|
||||||
|
> When was the last time you actually reversed a linked list in production code?
|
||||||
|
|
||||||
|
For most engineers, the answer is:
|
||||||
|
|
||||||
|
> Almost never.
|
||||||
|
|
||||||
|
Modern systems rarely use linked lists as a primary data structure.
|
||||||
|
|
||||||
|
More commonly you will encounter:
|
||||||
|
|
||||||
|
* vectors
|
||||||
|
* deques
|
||||||
|
* ring buffers
|
||||||
|
* hash tables
|
||||||
|
* trees
|
||||||
|
* databases
|
||||||
|
* message queues
|
||||||
|
|
||||||
|
The embedded world is similar.
|
||||||
|
|
||||||
|
Typical structures include:
|
||||||
|
|
||||||
|
* circular buffers
|
||||||
|
* DMA buffers
|
||||||
|
* message queues
|
||||||
|
* routing tables
|
||||||
|
* state machines
|
||||||
|
|
||||||
|
Linked lists certainly exist.
|
||||||
|
|
||||||
|
However, fully reversing one is rarely a real business requirement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Hidden Assumption
|
||||||
|
|
||||||
|
The interview question starts with an assumption:
|
||||||
|
|
||||||
|
> You already have a linked list.
|
||||||
|
|
||||||
|
Real engineering often starts with a different question:
|
||||||
|
|
||||||
|
> Why is this a linked list in the first place?
|
||||||
|
|
||||||
|
That decision is usually far more important than the reversal algorithm itself.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Real-World Equivalent
|
||||||
|
|
||||||
|
Finding a true production equivalent is difficult.
|
||||||
|
|
||||||
|
Most real systems solve a different problem.
|
||||||
|
|
||||||
|
### Example 1: Event History Viewer
|
||||||
|
|
||||||
|
A user wants to see the newest events first.
|
||||||
|
|
||||||
|
A typical engineering solution is:
|
||||||
|
|
||||||
|
* iterate in reverse
|
||||||
|
* change presentation logic
|
||||||
|
* adjust query ordering
|
||||||
|
|
||||||
|
The underlying data structure often remains unchanged.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Example 2: CAN Trace Analysis
|
||||||
|
|
||||||
|
Suppose a trace contains millions of CAN frames.
|
||||||
|
|
||||||
|
The user wants the newest messages displayed at the top.
|
||||||
|
|
||||||
|
Nobody reverses the entire dataset.
|
||||||
|
|
||||||
|
Instead:
|
||||||
|
|
||||||
|
* reverse iteration is used
|
||||||
|
* the UI changes presentation order
|
||||||
|
* indexing structures provide efficient access
|
||||||
|
|
||||||
|
The stored data remains exactly as it was.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Real Engineers Usually Ask
|
||||||
|
|
||||||
|
A more practical engineering question would be:
|
||||||
|
|
||||||
|
> The user wants to view data in reverse order.
|
||||||
|
>
|
||||||
|
> Do we actually need to modify the data structure?
|
||||||
|
|
||||||
|
This question frequently leads to better solutions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Better Interview Question
|
||||||
|
|
||||||
|
Instead of asking:
|
||||||
|
|
||||||
|
> Reverse a linked list.
|
||||||
|
|
||||||
|
Consider asking:
|
||||||
|
|
||||||
|
> A system stores ten million records.
|
||||||
|
>
|
||||||
|
> Users want to view them in reverse order.
|
||||||
|
>
|
||||||
|
> What solution options exist, and what are their trade-offs?
|
||||||
|
|
||||||
|
Now the discussion becomes much more interesting:
|
||||||
|
|
||||||
|
* memory usage
|
||||||
|
* cache locality
|
||||||
|
* ownership
|
||||||
|
* indexing
|
||||||
|
* performance
|
||||||
|
* maintainability
|
||||||
|
* user requirements
|
||||||
|
|
||||||
|
In other words:
|
||||||
|
|
||||||
|
Engineering begins.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Mistakes
|
||||||
|
|
||||||
|
* ❌ Assuming data must be modified to change presentation order
|
||||||
|
* ❌ Ignoring alternative data structures
|
||||||
|
* ❌ Focusing on implementation before understanding requirements
|
||||||
|
* ❌ Treating algorithmic manipulation as the only valid solution
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Takeaway
|
||||||
|
|
||||||
|
Reverse Linked List is useful as an educational exercise.
|
||||||
|
|
||||||
|
It teaches pointer manipulation and careful reasoning about memory.
|
||||||
|
|
||||||
|
However, the problem itself rarely appears in production software in its original form.
|
||||||
|
|
||||||
|
The real engineering question is usually not:
|
||||||
|
|
||||||
|
> How do we reverse the list?
|
||||||
|
|
||||||
|
Instead it is:
|
||||||
|
|
||||||
|
> Do we need to reverse it at all?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Perspective
|
||||||
|
|
||||||
|
> Exists in real engineering?
|
||||||
|
|
||||||
|
**Partially**
|
||||||
|
|
||||||
|
Linked lists exist.
|
||||||
|
|
||||||
|
Complete list reversal is a very uncommon business requirement.
|
||||||
|
|
||||||
|
> Exists in interview form?
|
||||||
|
|
||||||
|
**Yes**
|
||||||
|
|
||||||
|
It remains one of the most common classic coding interview questions.
|
||||||
|
|
||||||
|
The exercise is valuable for learning pointer manipulation.
|
||||||
|
|
||||||
|
Its usefulness as a predictor of engineering ability is far less obvious.
|
||||||
|
|
||||||
|
|
||||||
|
## A More Engineering-Oriented Alternatives
|
||||||
|
|
||||||
|
If the goal is to eveluate pointer manipulation, linked-list traversal and in-place node relocation, a message queue partitioning task may provide a more realistic engineering scenario. This is idea for #05
|
||||||
74
analysis/05-Message_Queue_Partitioning/example/message_queue_partitioning/.gitignore
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# This file is used to ignore files which are generated
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
*~
|
||||||
|
*.autosave
|
||||||
|
*.a
|
||||||
|
*.core
|
||||||
|
*.moc
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*_pch.h.cpp
|
||||||
|
*_resource.rc
|
||||||
|
*.qm
|
||||||
|
.#*
|
||||||
|
*.*#
|
||||||
|
core
|
||||||
|
!core/
|
||||||
|
tags
|
||||||
|
.DS_Store
|
||||||
|
.directory
|
||||||
|
*.debug
|
||||||
|
Makefile*
|
||||||
|
*.prl
|
||||||
|
*.app
|
||||||
|
moc_*.cpp
|
||||||
|
ui_*.h
|
||||||
|
qrc_*.cpp
|
||||||
|
Thumbs.db
|
||||||
|
*.res
|
||||||
|
*.rc
|
||||||
|
/.qmake.cache
|
||||||
|
/.qmake.stash
|
||||||
|
|
||||||
|
# qtcreator generated files
|
||||||
|
*.pro.user*
|
||||||
|
CMakeLists.txt.user*
|
||||||
|
|
||||||
|
# xemacs temporary files
|
||||||
|
*.flc
|
||||||
|
|
||||||
|
# Vim temporary files
|
||||||
|
.*.swp
|
||||||
|
|
||||||
|
# Visual Studio generated files
|
||||||
|
*.ib_pdb_index
|
||||||
|
*.idb
|
||||||
|
*.ilk
|
||||||
|
*.pdb
|
||||||
|
*.sln
|
||||||
|
*.suo
|
||||||
|
*.vcproj
|
||||||
|
*vcproj.*.*.user
|
||||||
|
*.ncb
|
||||||
|
*.sdf
|
||||||
|
*.opensdf
|
||||||
|
*.vcxproj
|
||||||
|
*vcxproj.*
|
||||||
|
|
||||||
|
# MinGW generated files
|
||||||
|
*.Debug
|
||||||
|
*.Release
|
||||||
|
|
||||||
|
# Python byte code
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
# --------
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
struct Message {
|
||||||
|
std::uint32_t id;
|
||||||
|
bool retry;
|
||||||
|
Message *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MessageQueue {
|
||||||
|
Message *head;
|
||||||
|
Message *tail;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PartitionResult {
|
||||||
|
MessageQueue ready;
|
||||||
|
MessageQueue retry;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void append (MessageQueue &queue, Message *message) {
|
||||||
|
assert (message != nullptr);
|
||||||
|
assert (message->next == nullptr);
|
||||||
|
|
||||||
|
if (queue.tail == nullptr) {
|
||||||
|
queue.head = message;
|
||||||
|
queue.tail = message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.tail->next = message;
|
||||||
|
queue.tail = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
PartitionResult partition_messages (MessageQueue &source) {
|
||||||
|
PartitionResult result{
|
||||||
|
{nullptr, nullptr},
|
||||||
|
{nullptr, nullptr}
|
||||||
|
};
|
||||||
|
|
||||||
|
Message *current = source.head;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The source queue is consumed by this operation.
|
||||||
|
*
|
||||||
|
* Clearing it before traversal makes the ownership transfer explicit:
|
||||||
|
* every node taken from the original queue must be appended to exactly
|
||||||
|
* one of the two result queues.
|
||||||
|
*/
|
||||||
|
source.head = nullptr;
|
||||||
|
source.tail = nullptr;
|
||||||
|
|
||||||
|
while (current != nullptr) {
|
||||||
|
/*
|
||||||
|
* Save the traversal link before modifying current->next.
|
||||||
|
* The same intrusive link is reused by the destination queue.
|
||||||
|
*/
|
||||||
|
Message *next = current->next;
|
||||||
|
current->next = nullptr;
|
||||||
|
|
||||||
|
if (current->retry)
|
||||||
|
append (result.retry, current);
|
||||||
|
else
|
||||||
|
append (result.ready, current);
|
||||||
|
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_queue (const char *name, const MessageQueue &queue) {
|
||||||
|
std::cout << name << ": ";
|
||||||
|
|
||||||
|
const Message *current = queue.head;
|
||||||
|
|
||||||
|
if (current == nullptr) {
|
||||||
|
std::cout << "<empty>\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (current != nullptr) {
|
||||||
|
std::cout << current->id;
|
||||||
|
|
||||||
|
if (current->next != nullptr)
|
||||||
|
std::cout << " -> ";
|
||||||
|
|
||||||
|
current = current->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::size_t queue_size (const MessageQueue &queue) {
|
||||||
|
std::size_t size = 0;
|
||||||
|
const Message *current = queue.head;
|
||||||
|
|
||||||
|
while (current != nullptr) {
|
||||||
|
++size;
|
||||||
|
current = current->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void verify_queue (const MessageQueue &queue) {
|
||||||
|
if (queue.head == nullptr) {
|
||||||
|
assert (queue.tail == nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert (queue.tail != nullptr);
|
||||||
|
assert (queue.tail->next == nullptr);
|
||||||
|
|
||||||
|
const Message *current = queue.head;
|
||||||
|
|
||||||
|
while (current->next != nullptr)
|
||||||
|
current = current->next;
|
||||||
|
|
||||||
|
assert (current == queue.tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Message a{1U, false, nullptr};
|
||||||
|
Message b{2U, true, nullptr};
|
||||||
|
Message c{3U, false, nullptr};
|
||||||
|
Message d{4U, true, nullptr};
|
||||||
|
|
||||||
|
a.next = &b;
|
||||||
|
b.next = &c;
|
||||||
|
c.next = &d;
|
||||||
|
|
||||||
|
MessageQueue outgoing{&a, &d};
|
||||||
|
|
||||||
|
std::cout << "Before partition\n";
|
||||||
|
print_queue ("Outgoing", outgoing);
|
||||||
|
|
||||||
|
const PartitionResult result = partition_messages (outgoing);
|
||||||
|
|
||||||
|
std::cout << "\nAfter partition\n";
|
||||||
|
print_queue ("Outgoing", outgoing);
|
||||||
|
print_queue ("Ready", result.ready);
|
||||||
|
print_queue ("Retry", result.retry);
|
||||||
|
|
||||||
|
verify_queue (outgoing);
|
||||||
|
verify_queue (result.ready);
|
||||||
|
verify_queue (result.retry);
|
||||||
|
|
||||||
|
assert (outgoing.head == nullptr);
|
||||||
|
assert (outgoing.tail == nullptr);
|
||||||
|
|
||||||
|
assert (result.ready.head == &a);
|
||||||
|
assert (result.ready.tail == &c);
|
||||||
|
assert (a.next == &c);
|
||||||
|
assert (c.next == nullptr);
|
||||||
|
|
||||||
|
assert (result.retry.head == &b);
|
||||||
|
assert (result.retry.tail == &d);
|
||||||
|
assert (b.next == &d);
|
||||||
|
assert (d.next == nullptr);
|
||||||
|
|
||||||
|
assert (queue_size (result.ready) + queue_size (result.retry) == 4U);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
TEMPLATE = app
|
||||||
|
CONFIG += console c++17
|
||||||
|
CONFIG -= app_bundle
|
||||||
|
CONFIG -= qt
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
main.cpp
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
rectangle "Traversal"
|
||||||
|
|
||||||
|
() previous
|
||||||
|
() current
|
||||||
|
() next
|
||||||
|
|
||||||
|
previous --> current
|
||||||
|
current --> next
|
||||||
|
|
||||||
|
note right
|
||||||
|
|
||||||
|
save next
|
||||||
|
|
||||||
|
detach current
|
||||||
|
|
||||||
|
append to
|
||||||
|
destination queue
|
||||||
|
|
||||||
|
continue with next
|
||||||
|
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
BIN
analysis/05-Message_Queue_Partitioning/example/uml/iteration.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
[*] --> Outgoing
|
||||||
|
|
||||||
|
Outgoing --> Sent : success
|
||||||
|
|
||||||
|
Outgoing --> Retry : retry == true
|
||||||
|
|
||||||
|
Retry --> Outgoing : rescheduled
|
||||||
|
|
||||||
|
Sent --> [*]
|
||||||
|
|
||||||
|
@enduml
|
||||||
BIN
analysis/05-Message_Queue_Partitioning/example/uml/loop.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
rectangle "Outgoing Queue" as Q1
|
||||||
|
rectangle "Ready Queue" as Q2
|
||||||
|
rectangle "Retry Queue" as Q3
|
||||||
|
|
||||||
|
Q1 --> Q2 : move node
|
||||||
|
Q1 --> Q3 : move node
|
||||||
|
|
||||||
|
note bottom
|
||||||
|
|
||||||
|
Every message belongs
|
||||||
|
to exactly one queue.
|
||||||
|
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,39 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
left to right direction
|
||||||
|
skinparam linetype ortho
|
||||||
|
skinparam shadowing false
|
||||||
|
|
||||||
|
package "Before Partition" {
|
||||||
|
rectangle "A\nretry = false" as A
|
||||||
|
rectangle "B\nretry = true" as B
|
||||||
|
rectangle "C\nretry = false" as C
|
||||||
|
rectangle "D\nretry = true" as D
|
||||||
|
|
||||||
|
A --> B : next
|
||||||
|
B --> C : next
|
||||||
|
C --> D : next
|
||||||
|
}
|
||||||
|
|
||||||
|
package "After Partition" {
|
||||||
|
package "Ready Queue" {
|
||||||
|
rectangle "A" as ReadyA
|
||||||
|
rectangle "C" as ReadyC
|
||||||
|
|
||||||
|
ReadyA --> ReadyC : next
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Retry Queue" {
|
||||||
|
rectangle "B" as RetryB
|
||||||
|
rectangle "D" as RetryD
|
||||||
|
|
||||||
|
RetryB --> RetryD : next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
A ..> ReadyA : move
|
||||||
|
B ..> RetryB : move
|
||||||
|
C ..> ReadyC : move
|
||||||
|
D ..> RetryD : move
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,20 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
participant Queue
|
||||||
|
participant Algorithm
|
||||||
|
participant Ready
|
||||||
|
participant Retry
|
||||||
|
|
||||||
|
Queue -> Algorithm : get next node
|
||||||
|
|
||||||
|
alt retry == false
|
||||||
|
|
||||||
|
Algorithm -> Ready : append(node)
|
||||||
|
|
||||||
|
else retry == true
|
||||||
|
|
||||||
|
Algorithm -> Retry : append(node)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
After Width: | Height: | Size: 14 KiB |
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.
|
||||||
742
analysis/06-The_Myth_of_Clean_Input/readme.md
Normal file
@@ -0,0 +1,742 @@
|
|||||||
|
# #06 — The Myth of Clean Input
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Most algorithmic problems begin in roughly the same way:
|
||||||
|
|
||||||
|
> Given an array.
|
||||||
|
|
||||||
|
> Given a linked list.
|
||||||
|
|
||||||
|
> Given a vector of temperatures.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::vector<float> temperatures;
|
||||||
|
```
|
||||||
|
|
||||||
|
The task may then ask us to find the maximum value, calculate an average, remove duplicates, or perform some other operation.
|
||||||
|
|
||||||
|
The input is assumed to:
|
||||||
|
|
||||||
|
- already exist;
|
||||||
|
- have the expected type;
|
||||||
|
- use the expected representation;
|
||||||
|
- be free from corruption;
|
||||||
|
- contain values within valid ranges;
|
||||||
|
- be ready for use.
|
||||||
|
|
||||||
|
This assumption feels so natural that it is rarely noticed.
|
||||||
|
|
||||||
|
In real engineering, however, a clean object is rarely the starting point.
|
||||||
|
|
||||||
|
More often, it is the final result of a long processing chain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typical Interview Thinking
|
||||||
|
|
||||||
|
Consider a simple problem:
|
||||||
|
|
||||||
|
> Find the maximum temperature.
|
||||||
|
|
||||||
|
The candidate receives a ready-to-use container:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::vector<float> temperatures;
|
||||||
|
```
|
||||||
|
|
||||||
|
The solution may be almost trivial:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const auto max_temperature =
|
||||||
|
std::max_element(temperatures.begin(), temperatures.end());
|
||||||
|
```
|
||||||
|
|
||||||
|
From there, the discussion may cover:
|
||||||
|
|
||||||
|
- computational complexity;
|
||||||
|
- memory usage;
|
||||||
|
- empty input handling;
|
||||||
|
- use of the standard library;
|
||||||
|
- possible optimizations.
|
||||||
|
|
||||||
|
All attention is focused on the algorithm.
|
||||||
|
|
||||||
|
But one question is almost never asked:
|
||||||
|
|
||||||
|
> Where did this `std::vector<float>` come from?
|
||||||
|
|
||||||
|
Who received the original data?
|
||||||
|
|
||||||
|
Who verified the frame?
|
||||||
|
|
||||||
|
Who determined the format?
|
||||||
|
|
||||||
|
Who converted the raw sensor value into degrees?
|
||||||
|
|
||||||
|
Who decided that the resulting number could be trusted?
|
||||||
|
|
||||||
|
The interview starts with an already prepared object.
|
||||||
|
|
||||||
|
A real system must first create that object.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Clean Input Is Not a Starting Condition
|
||||||
|
|
||||||
|
Consider a temperature received from a remote sensor.
|
||||||
|
|
||||||
|
At the business-logic level, it may look like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
23.7 °C
|
||||||
|
```
|
||||||
|
|
||||||
|
But the system may have originally received nothing more than a sequence of bytes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
02 03 00 1A FF 7C 91 4D
|
||||||
|
```
|
||||||
|
|
||||||
|
Before those bytes can become a temperature, the data must pass through several stages:
|
||||||
|
|
||||||
|
```text
|
||||||
|
UART / CAN / TCP
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Receive raw bytes
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Extract a complete frame
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Validate frame length
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Verify checksum
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Check protocol version
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Deserialize the payload
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Identify the data source
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Convert byte order
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Apply scale and offset
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Check the physical range
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Normalized temperature
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Append to std::vector<float>
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Find the maximum value
|
||||||
|
```
|
||||||
|
|
||||||
|
The maximum-value algorithm is the final step and may be the simplest step in the entire chain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Cost of Clean Input
|
||||||
|
|
||||||
|
This declaration looks simple:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::vector<float> temperatures;
|
||||||
|
```
|
||||||
|
|
||||||
|
But that simplicity was not free.
|
||||||
|
|
||||||
|
Before business logic can receive such a container, the system may already have had to:
|
||||||
|
|
||||||
|
- receive data from an external source;
|
||||||
|
- identify message boundaries;
|
||||||
|
- detect corruption;
|
||||||
|
- check protocol-version compatibility;
|
||||||
|
- parse a binary representation;
|
||||||
|
- handle byte order;
|
||||||
|
- apply scaling;
|
||||||
|
- recognize reserved or unavailable values;
|
||||||
|
- verify physical plausibility;
|
||||||
|
- convert the result into an internal representation.
|
||||||
|
|
||||||
|
The algorithm may require one line of code.
|
||||||
|
|
||||||
|
The infrastructure that makes that line meaningful may require thousands.
|
||||||
|
|
||||||
|
This is why business logic is often simpler than the code surrounding it.
|
||||||
|
|
||||||
|
The formula may already be known.
|
||||||
|
|
||||||
|
The algorithm may already exist in the standard library.
|
||||||
|
|
||||||
|
The real work is ensuring that the transition from the external world to the object expected by that algorithm is correct.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Normalization: Valid Data Can Still Be Incomparable
|
||||||
|
|
||||||
|
Not every input problem is caused by corruption.
|
||||||
|
|
||||||
|
Sometimes each value is individually valid, but several values represent the same entity in different forms.
|
||||||
|
|
||||||
|
Consider a task that searches for duplicate vehicle identifiers.
|
||||||
|
|
||||||
|
An interview problem may provide this input:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ABC123
|
||||||
|
ABC123
|
||||||
|
ABC123
|
||||||
|
```
|
||||||
|
|
||||||
|
The result is obvious.
|
||||||
|
|
||||||
|
A real system may receive:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ABC123
|
||||||
|
abc123
|
||||||
|
ABC-123
|
||||||
|
ABC123
|
||||||
|
ABC123
|
||||||
|
```
|
||||||
|
|
||||||
|
At the string level, these values are different.
|
||||||
|
|
||||||
|
A duplicate-search algorithm will correctly report that they do not match.
|
||||||
|
|
||||||
|
At the domain level, however, they may represent the same object.
|
||||||
|
|
||||||
|
Before searching for duplicates, the system must define a canonical representation:
|
||||||
|
|
||||||
|
- Is character case significant?
|
||||||
|
- Are separators meaningful?
|
||||||
|
- Should surrounding whitespace be removed?
|
||||||
|
- Which characters are permitted?
|
||||||
|
- Is there a canonical format?
|
||||||
|
- What should happen when the input is ambiguous?
|
||||||
|
|
||||||
|
After normalization, the values may become:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ABC123
|
||||||
|
ABC123
|
||||||
|
ABC123
|
||||||
|
ABC123
|
||||||
|
ABC123
|
||||||
|
```
|
||||||
|
|
||||||
|
Only now is the duplicate-search algorithm solving the correct problem.
|
||||||
|
|
||||||
|
Before normalization, it was comparing representations rather than entities.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Normalization Is Part of the System Model
|
||||||
|
|
||||||
|
Normalization can look like little more than string cleanup.
|
||||||
|
|
||||||
|
In reality, it expresses domain rules.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
- letter case may be irrelevant for one identifier and essential for another;
|
||||||
|
- two file paths may refer to the same object while remaining different strings;
|
||||||
|
- phone numbers may contain different country prefixes and formatting;
|
||||||
|
- MAC addresses may use different separators;
|
||||||
|
- timestamps may use different time zones;
|
||||||
|
- measurements may use different units;
|
||||||
|
- sensor values may require calibration.
|
||||||
|
|
||||||
|
Normalization does not merely answer:
|
||||||
|
|
||||||
|
> How should this string be modified?
|
||||||
|
|
||||||
|
It answers:
|
||||||
|
|
||||||
|
> What does this system consider to be the same value?
|
||||||
|
|
||||||
|
There is no universal normalization procedure.
|
||||||
|
|
||||||
|
It depends on the protocol, the contract, and the meaning of the data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Not All Well-Formed Data Is Usable
|
||||||
|
|
||||||
|
Successfully parsing a message does not mean that its contents are safe to use.
|
||||||
|
|
||||||
|
Consider these temperatures:
|
||||||
|
|
||||||
|
```text
|
||||||
|
23.7
|
||||||
|
-40.0
|
||||||
|
65535
|
||||||
|
NaN
|
||||||
|
-273.15
|
||||||
|
```
|
||||||
|
|
||||||
|
Every one of these values may be successfully represented as a number.
|
||||||
|
|
||||||
|
Their meanings, however, are very different.
|
||||||
|
|
||||||
|
`23.7` may be a normal measurement.
|
||||||
|
|
||||||
|
`-40.0` may be valid, or it may be the lower limit of the sensor.
|
||||||
|
|
||||||
|
`65535` may represent unavailable data.
|
||||||
|
|
||||||
|
`NaN` may have appeared after an invalid calculation.
|
||||||
|
|
||||||
|
`-273.15` is numerically valid, but for a particular device it almost certainly indicates a problem.
|
||||||
|
|
||||||
|
This reveals several different levels of correctness.
|
||||||
|
|
||||||
|
### Structural Correctness
|
||||||
|
|
||||||
|
Can the message be parsed?
|
||||||
|
|
||||||
|
### Protocol Correctness
|
||||||
|
|
||||||
|
Does it conform to the expected protocol format and version?
|
||||||
|
|
||||||
|
### Numeric Correctness
|
||||||
|
|
||||||
|
Can the value be represented using the required type?
|
||||||
|
|
||||||
|
### Semantic Correctness
|
||||||
|
|
||||||
|
Does the value make sense within the domain?
|
||||||
|
|
||||||
|
Syntactic validity does not guarantee meaningful data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## From Raw Data to a Trusted Object
|
||||||
|
|
||||||
|
It is useful to view input handling not as one large validation step, but as a sequence of state transitions.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Raw bytes
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Framed data
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Integrity-checked frame
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Parsed message
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Normalized values
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Semantically valid object
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Trusted domain object
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Business algorithm
|
||||||
|
```
|
||||||
|
|
||||||
|
At every stage, the system gains stronger guarantees.
|
||||||
|
|
||||||
|
Raw bytes promise almost nothing.
|
||||||
|
|
||||||
|
After framing, the message boundaries are known.
|
||||||
|
|
||||||
|
After integrity checks, there is evidence that the data was not accidentally corrupted.
|
||||||
|
|
||||||
|
After parsing, typed fields exist.
|
||||||
|
|
||||||
|
After normalization, values use a consistent representation.
|
||||||
|
|
||||||
|
After semantic checks, the object is known to be acceptable within the domain.
|
||||||
|
|
||||||
|
Only then can the data be treated as trusted by a particular layer of the system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Trust Must Be Local
|
||||||
|
|
||||||
|
This leads to an important architectural principle:
|
||||||
|
|
||||||
|
> Data is not simply trusted or untrusted.
|
||||||
|
|
||||||
|
It is trusted only relative to a particular contract.
|
||||||
|
|
||||||
|
A transport layer may guarantee that:
|
||||||
|
|
||||||
|
- the complete frame was received;
|
||||||
|
- the checksum matches;
|
||||||
|
- the length is valid.
|
||||||
|
|
||||||
|
It cannot guarantee that a temperature is physically meaningful.
|
||||||
|
|
||||||
|
A parser may guarantee that:
|
||||||
|
|
||||||
|
- message fields were extracted successfully;
|
||||||
|
- their sizes and types match the protocol.
|
||||||
|
|
||||||
|
It cannot determine whether the value is acceptable for a specific device model.
|
||||||
|
|
||||||
|
That responsibility belongs to another layer.
|
||||||
|
|
||||||
|
Each layer checks its own invariants and passes a stronger representation to the next one.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Every Layer Earns Trust for the Next One
|
||||||
|
|
||||||
|
A clean object at an algorithm boundary is not a magical property of the data.
|
||||||
|
|
||||||
|
It is the result of fulfilled contracts.
|
||||||
|
|
||||||
|
One layer says:
|
||||||
|
|
||||||
|
> I verified the integrity of the frame.
|
||||||
|
|
||||||
|
The next says:
|
||||||
|
|
||||||
|
> I parsed the message according to a supported protocol version.
|
||||||
|
|
||||||
|
The next says:
|
||||||
|
|
||||||
|
> I converted the values into the system's internal units.
|
||||||
|
|
||||||
|
The next says:
|
||||||
|
|
||||||
|
> I confirmed that the object is valid within this domain.
|
||||||
|
|
||||||
|
Only then may the business logic assume:
|
||||||
|
|
||||||
|
> This is a valid temperature.
|
||||||
|
|
||||||
|
That assumption is not justified because the external world is reliable.
|
||||||
|
|
||||||
|
It is justified because the previous layers did their work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why Not Validate Everything Everywhere?
|
||||||
|
|
||||||
|
Distrusting input can lead to another bad conclusion:
|
||||||
|
|
||||||
|
> Every function should repeat every validation step.
|
||||||
|
|
||||||
|
That creates different problems:
|
||||||
|
|
||||||
|
- duplicated logic;
|
||||||
|
- contradictory checks;
|
||||||
|
- unclear ownership of responsibilities;
|
||||||
|
- more complex code;
|
||||||
|
- uncertainty about which guarantees already exist.
|
||||||
|
|
||||||
|
A function that accepts raw bytes must not assume that they are safe.
|
||||||
|
|
||||||
|
A function that accepts an object which can only be created after successful verification does not need to repeat the entire process.
|
||||||
|
|
||||||
|
Good architecture does not eliminate trust.
|
||||||
|
|
||||||
|
It makes the origin of trust explicit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Types as Evidence of the Path Already Taken
|
||||||
|
|
||||||
|
One practical way to express this is to use different types for different processing stages.
|
||||||
|
|
||||||
|
Instead of passing the same generic object through the entire system, the stages can be represented explicitly:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct RawFrame;
|
||||||
|
struct VerifiedFrame;
|
||||||
|
struct ParsedTemperatureMessage;
|
||||||
|
struct NormalizedTemperature;
|
||||||
|
```
|
||||||
|
|
||||||
|
The interfaces can then reflect the available guarantees:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::optional<VerifiedFrame>
|
||||||
|
verify_frame(const RawFrame& frame);
|
||||||
|
|
||||||
|
std::optional<ParsedTemperatureMessage>
|
||||||
|
parse_message(const VerifiedFrame& frame);
|
||||||
|
|
||||||
|
std::optional<NormalizedTemperature>
|
||||||
|
normalize_temperature(const ParsedTemperatureMessage& message);
|
||||||
|
```
|
||||||
|
|
||||||
|
Business logic can accept only the normalized value:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void process_temperature(const NormalizedTemperature& temperature);
|
||||||
|
```
|
||||||
|
|
||||||
|
This does not make the data absolutely true.
|
||||||
|
|
||||||
|
It makes the stages already completed explicit.
|
||||||
|
|
||||||
|
It also prevents raw input from being passed accidentally into code that expects a verified object.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Happens When Processing Fails?
|
||||||
|
|
||||||
|
Data evolution does not always end with a valid business object.
|
||||||
|
|
||||||
|
Every stage may reject the input:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Raw bytes
|
||||||
|
│
|
||||||
|
├── incomplete frame
|
||||||
|
├── unsupported version
|
||||||
|
├── invalid checksum
|
||||||
|
├── malformed payload
|
||||||
|
├── unknown sensor
|
||||||
|
├── invalid scaling
|
||||||
|
├── out-of-range value
|
||||||
|
└── valid temperature
|
||||||
|
```
|
||||||
|
|
||||||
|
This introduces another major part of real engineering that is usually absent from algorithmic problems:
|
||||||
|
|
||||||
|
- the message may need to be discarded;
|
||||||
|
- the failure may need to be logged;
|
||||||
|
- a diagnostic counter may need to be incremented;
|
||||||
|
- the source may need to be reconnected;
|
||||||
|
- the system may need to use the last known valid value;
|
||||||
|
- a component may enter a degraded mode;
|
||||||
|
- the failure may affect safety-related behavior.
|
||||||
|
|
||||||
|
In an interview problem, an invalid value is often just an edge case.
|
||||||
|
|
||||||
|
In a real system, it may trigger an entirely different operating scenario.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Algorithm Still Matters
|
||||||
|
|
||||||
|
None of this means that algorithms are unimportant.
|
||||||
|
|
||||||
|
Once data has been converted into a correct internal model, the algorithm still needs to be:
|
||||||
|
|
||||||
|
- correct;
|
||||||
|
- efficient;
|
||||||
|
- understandable;
|
||||||
|
- appropriate for the system constraints.
|
||||||
|
|
||||||
|
The problem begins when solving a task over a clean array is treated as a complete model of engineering ability.
|
||||||
|
|
||||||
|
An algorithm solves a problem under a set of assumptions.
|
||||||
|
|
||||||
|
An engineer must also:
|
||||||
|
|
||||||
|
- discover those assumptions;
|
||||||
|
- determine whether they are valid;
|
||||||
|
- assign responsibility for enforcing them;
|
||||||
|
- express the resulting guarantees in interfaces and architecture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What This Actually Tests
|
||||||
|
|
||||||
|
A problem over a ready-made container can test:
|
||||||
|
|
||||||
|
- knowledge of data structures;
|
||||||
|
- algorithmic reasoning;
|
||||||
|
- complexity analysis;
|
||||||
|
- recognition of known patterns;
|
||||||
|
- implementation accuracy.
|
||||||
|
|
||||||
|
It says much less about a candidate's ability to:
|
||||||
|
|
||||||
|
- work with external data sources;
|
||||||
|
- design trust boundaries;
|
||||||
|
- parse protocols;
|
||||||
|
- normalize representations;
|
||||||
|
- define semantic validity;
|
||||||
|
- design diagnostics;
|
||||||
|
- handle partial failures;
|
||||||
|
- create reliable contracts between layers.
|
||||||
|
|
||||||
|
This does not make the algorithmic task useless.
|
||||||
|
|
||||||
|
It only limits what can reasonably be concluded from it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Where the Interview Ends and Engineering Begins
|
||||||
|
|
||||||
|
An interview problem often presents this model:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Clean input
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Algorithm
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Result
|
||||||
|
```
|
||||||
|
|
||||||
|
A real system often looks more like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Physical world
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Electrical signal
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Raw bytes
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Transport framing
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Integrity checks
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Protocol parsing
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Version handling
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Normalization
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Semantic validation
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Domain object
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Algorithm
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
System decision
|
||||||
|
```
|
||||||
|
|
||||||
|
The interview begins near the end of this chain.
|
||||||
|
|
||||||
|
Engineering is responsible for the entire chain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Evolution of Data
|
||||||
|
|
||||||
|
We can now return to the temperature example.
|
||||||
|
|
||||||
|
Initially, the system does not have a temperature.
|
||||||
|
|
||||||
|
It has a signal.
|
||||||
|
|
||||||
|
Then it has bytes.
|
||||||
|
|
||||||
|
Then a frame.
|
||||||
|
|
||||||
|
Then a message.
|
||||||
|
|
||||||
|
Then a raw sensor value.
|
||||||
|
|
||||||
|
Then a value expressed in physical units.
|
||||||
|
|
||||||
|
Then a normalized and semantically valid measurement.
|
||||||
|
|
||||||
|
Only after all of that does a number appear that can safely be stored in a container and passed to an algorithm.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Signal
|
||||||
|
↓
|
||||||
|
Bytes
|
||||||
|
↓
|
||||||
|
Frame
|
||||||
|
↓
|
||||||
|
Verified frame
|
||||||
|
↓
|
||||||
|
Parsed message
|
||||||
|
↓
|
||||||
|
Raw sensor value
|
||||||
|
↓
|
||||||
|
Calibrated value
|
||||||
|
↓
|
||||||
|
Normalized temperature
|
||||||
|
↓
|
||||||
|
Trusted domain object
|
||||||
|
↓
|
||||||
|
std::vector<float>
|
||||||
|
↓
|
||||||
|
std::max_element
|
||||||
|
```
|
||||||
|
|
||||||
|
The maximum-search algorithm does not create the meaning of the data.
|
||||||
|
|
||||||
|
It consumes meaning that was established by the previous layers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Takeaway
|
||||||
|
|
||||||
|
Clean input is not a starting point.
|
||||||
|
|
||||||
|
It is an engineering result.
|
||||||
|
|
||||||
|
It exists only after the system has:
|
||||||
|
|
||||||
|
- identified the structure of the data;
|
||||||
|
- verified its integrity;
|
||||||
|
- understood its format;
|
||||||
|
- converted it into a canonical representation;
|
||||||
|
- checked its meaning;
|
||||||
|
- established a contract of trust.
|
||||||
|
|
||||||
|
The engineer's first question is therefore not:
|
||||||
|
|
||||||
|
> How do I process this array?
|
||||||
|
|
||||||
|
It is:
|
||||||
|
|
||||||
|
> Why can this array be trusted?
|
||||||
|
|
||||||
|
And then:
|
||||||
|
|
||||||
|
> Which layer guarantees that?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Perspective
|
||||||
|
|
||||||
|
> Exists in real engineering?
|
||||||
|
> Yes. Almost constantly.
|
||||||
|
|
||||||
|
> Exists in interview form?
|
||||||
|
> Usually not. Most of the data journey is hidden by the problem statement.
|
||||||
|
|
||||||
|
Algorithmic tasks are useful for evaluating work on already prepared structures.
|
||||||
|
|
||||||
|
But they usually begin with a result that a real system still has to produce.
|
||||||
|
|
||||||
|
That is the myth of clean input:
|
||||||
|
|
||||||
|
> Data does not arrive ready for the algorithm.
|
||||||
|
|
||||||
|
> Engineering makes it ready.
|
||||||