Dynamic Data Structures (Pt.3)
C. Papachristos
Robotic Workers Lab
University of Nevada, Reno
CS-202
Course , Projects , Labs:
Your 8th Project Deadline is this Wednesday 4/15.
Monday | Tuesday | Wednesday | Thursday | Friday | | Sunday |
|
|
| Lab (8 Sections) |
| | |
| CLASS | PASS Session | CLASS |
| | |
PASS Session | | Project DEADLINE | NEW Project | PASS Session | | PASS Session |
Course Week
CS-202 C. Papachristos
Today’s Topics
CS-202 C. Papachristos
Dynamic Data Structures
“Container Adaptors”
Queues(s)
Queue(s)
CS-202 C. Papachristos
The Basics
A Dynamic Data Structure type, with its own semantics.
An ordered group of homogeneous items.
Operational semantics:
Queue(s)
CS-202 C. Papachristos
The Basics
A Dynamic Data Structure type, with its own semantics.
Operational semantics (continued):
Queue(s)
CS-202 C. Papachristos
Applications
Queues are appropriate to handle for many real-world situations:
In bureaucracy - A line to be served at the DMV.
Queues have numerous computer (science)-related applications:
For a CPU – Concurrent programming (Note: Not the same as Multi-threading)
For a printer – Serving a request to print a document.
Cross-field simulations & case-studies:
Queue(s)
CS-202 C. Papachristos
Queue Operations
A complete Queue-based ADT implementation has to support the following functionalities:
Queue empty()
bool empty() const;
/** Determines whether the queue is empty.
* @return True if the queue is empty, otherwise false.
*/
Queue Operations
CS-202 C. Papachristos
? |
? |
? |
?
Data |
… |
… |
Next
…
Data |
… |
… |
Next
# 0
Data |
… |
… |
Next
# N
Non-empty Queue: false
Empty Queue: true
Back
Front
Back
Front
N/A
N/A
N/A
?
Queue Operations
CS-202 C. Papachristos
Queue push()-ing
When a new element needs to be added to the queue:
Called an “enqueue” operation (also push, append, etc.)
Data |
… |
… |
Next
# N
Data |
… |
… |
Next
# N-1
Data |
… |
… |
Next
…
Data |
… |
… |
Next
# 0
Data |
… |
… |
Next
?
Back
N/A
Front
Queue Operations
CS-202 C. Papachristos
Queue push()-ing
void push(const DataType & value);
/** Inserts an element at the back of a queue.
* @param value The value of the element to be inserted.
* @post If the insertion is successful, a new DataType element of value is at the back of the queue.
*/
Data |
… |
… |
Next
# N-1
Data |
… |
… |
Next
# N-2
Data |
… |
… |
Next
…
Data |
… |
… |
Next
# 0
Data |
… |
… |
Next
# N
Back
N/A
Front
Queue Operations
CS-202 C. Papachristos
Queue pop()-ping
When an element needs to be removed from the queue:
Called an “dequeue” operation (also pop, remove, etc.)
Data |
… |
… |
Next
# N-1
Data |
… |
… |
Next
…
Data |
… |
… |
Next
# 1
Data |
… |
… |
Next
# 0
Data |
… |
… |
Next
Front
Back
N/A
# N
Queue pop()-ing
void pop();
/** Dequeues the front of a queue.
* @post If the queue is not empty, the element that was added to the queue earliest is deleted.
*/
Queue Operations
CS-202 C. Papachristos
Data |
… |
… |
Next
# N-1
Data |
… |
… |
Next
…
Data |
… |
… |
Next
# 0
? |
? |
? |
?
Data |
… |
… |
Next
Front
N/A
Back
?
# N
Queue Operations
CS-202 C. Papachristos
Queue front()
When the front element needs to be accessed:
Called an “getFront” operation (also front, first etc.)
Data |
… |
… |
Next
# N-1
Data |
… |
… |
Next
…
Data |
… |
… |
Next
# 1
Data |
… |
… |
Next
# 0
Data |
… |
… |
Next
# N
N/A
Front
Back
Queue front()
DataType & front();
const DataType & front() const;
/** Retrieves the element at the front of a queue
* @pre The queue is not empty.
* @return If the queue is not empty, the return value is a (const) reference to the earliest added element. Otherwise result is undefined.
*/
Queue Operations
CS-202 C. Papachristos
Data |
… |
… |
Next
# N-1
Data |
… |
… |
Next
…
Data |
… |
… |
Next
# 1
Data |
… |
… |
Next
# 0
Data |
… |
… |
Next
# N
N/A
Front
Back
Queue front()
DataType & front();
const DataType & front() const;
/** Retrieves the element at the front of a queue
* @pre The queue is not empty.
* @return If the queue is not empty, the return value is a (const) reference to the earliest added element. Otherwise result in undefined.
*/
Queue Operations
CS-202 C. Papachristos
? |
? |
? |
?
Remember: From the C++11 standard:
[dcl.ref] [...] a NULL reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a NULL pointer, which causes �undefined behavior.
N/A
Front
?
Queue Operations
CS-202 C. Papachristos
Queue back() (outside of specifications)
When the last element needs to be accessed:
Called an “getBack” operation (also back, last, etc.)
Data |
… |
… |
Next
# N
Data |
… |
… |
Next
…
Data |
… |
… |
Next
# 1
Data |
… |
… |
Next
# 0
Data |
… |
… |
Next
# N-1
Front
Back
N/A
Queue back() (outside of specifications)
DataType & back();
const DataType & back() const;
/** Retrieves the element at the end of a queue
* @pre The queue is not empty.
* @return If the queue is not empty, the return value is a (const) reference to the earliest added element. Otherwise result is undefined.
*/
Queue Operations
CS-202 C. Papachristos
Data |
… |
… |
Next
# N
Data |
… |
… |
Next
…
Data |
… |
… |
Next
# 1
Data |
… |
… |
Next
# 0
Data |
… |
… |
Next
# N-1
Front
Back
N/A
Queue back() (outside of specifications)
DataType & back();
const DataType & back() const;
/** Retrieves the element at the end of a queue
* @pre The queue is not empty.
* @return If the queue is not empty, the return value is a (const) reference to the last added element. Otherwise result is undefined.
*/
Queue Operations
CS-202 C. Papachristos
Remember: From the C++11 standard:
[dcl.ref] [...] a NULL reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a NULL pointer, which causes undefined behavior.
? |
? |
? |
?
Back
?
Queue Implementations
CS-202 C. Papachristos
“Standard” Implementations
A complete Queue-based ADT implementation encompasses a subset of “Sequential Container” ADT functionalities.
A Linked-List with two-ended access :
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
A Queue can be implemented with an array, as shown here.
The “valid” array elements subset.
These array elements do not concern the program at this point.
int |
4 |
int |
8 |
int |
6 |
m_arr [0]
m_arr [1]
m_arr [2]
int |
… |
m_arr […]
int |
… |
m_arr [3]
int |
… |
m_arr [98]
int |
… |
m_arr [99]
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
A Queue can be implemented with an array, as shown here.
The “easiest” implementation keeps track of:
And “remembers”:
m_size := 3
m_front := 0
m_back := 2
m_capacity := 100
int |
4 |
int |
8 |
int |
6 |
int |
… |
m_arr [0]
m_arr [1]
m_arr [2]
m_arr [98]
int |
… |
m_arr [99]
int |
… |
m_arr […]
int |
… |
m_arr [3]
m_size := 2
m_front := 1
m_back := 2
m_capcaity := 100
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
A Queue pop() (dequeue) operation – Naïve approach.
When an element is removed from the Queue:
int |
4 |
int |
8 |
int |
6 |
m_arr [0]
m_arr [1]
m_arr [2]
int |
… |
m_arr […]
int |
… |
m_arr [3]
Note:
pop() does not clear contents, it only updates the�Queue values that keep track of its state.
int |
… |
m_arr [98]
int |
… |
m_arr [99]
m_size := 3
m_front := 1
m_back := 3
m_capacity := 100
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
A Queue push() (enqueue) operation – Naïve approach.
When an element is pushed to the Queue:
int |
4 |
int |
8 |
int |
6 |
m_arr [0]
m_arr [1]
m_arr [2]
int |
… |
m_arr […]
int |
9 |
m_arr [3]
Note:
push(…) overwrites contents, and also updates the�Queue values that keep track of its state.
int |
… |
m_arr [98]
int |
… |
m_arr [99]
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
Queue Naïve approach issues.
For a sequence of operations: ADADADADADADADADA... (A:Add, D: Delete)
int |
4 |
int |
8 |
int |
6 |
m_arr [0]
m_arr [1]
m_arr [2]
int |
… |
m_arr [4]
int |
9 |
m_arr [3]
int |
… |
m_arr […]
int |
… |
m_arr [99]
int |
4 |
int |
8 |
int |
6 |
m_arr [0]
m_arr [1]
m_arr [2]
int |
… |
m_arr [4]
int |
9 |
m_arr [3]
int |
… |
m_arr […]
int |
… |
m_arr [99]
int |
4 |
int |
8 |
int |
6 |
m_arr [0]
m_arr [1]
m_arr [2]
int |
13 |
m_arr [4]
int |
9 |
m_arr [3]
int |
… |
m_arr […]
int |
… |
m_arr [99]
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
Queue Naïve approach issues.
For a sequence of operations: ADADADADADADADADA... (A:Add, D: Delete)
int |
4 |
int |
8 |
int |
… |
m_arr [0]
m_arr [1]
m_arr […]
int |
11 |
m_arr [97]
int |
2 |
m_arr [96]
int |
5 |
m_arr [98]
int |
… |
m_arr [99]
int |
4 |
int |
8 |
int |
… |
m_arr [0]
m_arr [1]
m_arr […]
int |
11 |
m_arr [97]
int |
2 |
m_arr [96]
int |
5 |
m_arr [98]
int |
… |
m_arr [99]
int |
4 |
int |
8 |
int |
… |
m_arr [0]
m_arr [1]
m_arr […]
int |
11 |
m_arr [97]
int |
2 |
m_arr [96]
int |
5 |
m_arr [98]
int |
1 |
m_arr [99]
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
Queue Naïve approach issues.
int |
4 |
int |
8 |
int |
… |
m_arr [0]
m_arr [1]
m_arr […]
int |
11 |
m_arr [97]
int |
2 |
m_arr [96]
int |
5 |
m_arr [98]
int |
1 |
m_arr [99]
???
m_size := 3
m_front := 97
m_back := 99
m_capacity := 100
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
A “simple” solution – Upon condition of Queue rear overflow:
int |
11 |
int |
5 |
int |
1 |
m_arr [0]
m_arr [1]
m_arr [2]
int |
11 |
m_arr [97]
int |
2 |
m_arr […]
int |
5 |
m_arr [98]
int |
1 |
m_arr [99]
int |
4 |
int |
8 |
int |
… |
m_arr [0]
m_arr [1]
m_arr […]
int |
11 |
m_arr [97]
int |
2 |
m_arr [96]
int |
5 |
m_arr [98]
int |
1 |
m_arr [99]
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
An “elegant” solution – The circular buffer paradigm:
char |
… |
m_arr […]
char |
A |
m_arr [97]
char |
… |
m_arr [96]
char |
B |
m_arr [98]
char |
C |
m_arr [99]
char |
… |
char |
… |
m_arr [0]
m_arr [1]
m_size := 3 m_front := 97
m_capacity := 100 m_back := 99
???
charQueue.push('D');
[99]
[0]
[98]
[1]
[97]
[2]
[…]
[…]
Advance m_back to next circular array position !
m_back = (m_back + 1) % m_capacity;
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
An “elegant” solution – The circular buffer paradigm:
char |
… |
m_arr […]
char |
A |
m_arr [97]
char |
… |
m_arr [96]
char |
B |
m_arr [98]
char |
C |
m_arr [99]
char |
D |
char |
… |
m_arr [0]
m_arr [1]
char |
… |
m_arr […]
char |
A |
m_arr [97]
char |
… |
m_arr [96]
char |
B |
m_arr [98]
char |
C |
m_arr [99]
char |
… |
char |
… |
m_arr [0]
m_arr [1]
m_size := 4 m_front := 97
m_capacity := 100 m_back := 0
charQueue.push('D');
[99]
[0]
[98]
[1]
[97]
[2]
[…]
[…]
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
An “elegant” solution – The circular buffer paradigm:
[99]
[0]
[98]
[1]
[97]
[2]
[…]
[…]
Advance m_front to next circular array position !
m_front = (m_front + 1) % m_capacity;
char |
A |
m_arr [99]
char |
B |
char |
C |
char |
D |
m_arr [0]
m_arr [1]
m_arr [2]
char |
??? |
m_arr [97]
char |
??? |
m_arr […]
char |
??? |
m_arr [98]
m_size := 4 m_front := 99
m_capacity := 100 m_back := 2
???
charQueue.pop();
char |
??? |
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
An “elegant” solution – The circular buffer paradigm:
m_size := 3 m_front := 0
m_capacity := 100 m_back := 2
charQueue.pop();
[99]
[0]
[98]
[1]
[97]
[2]
[…]
[…]
char |
A |
m_arr [99]
char |
B |
char |
C |
char |
D |
m_arr [0]
m_arr [1]
m_arr [2]
char |
??? |
m_arr [97]
char |
??? |
m_arr […]
char |
??? |
m_arr [98]
m_arr [99]
char |
B |
char |
C |
char |
D |
m_arr [0]
m_arr [1]
m_arr [2]
char |
??? |
m_arr [97]
char |
??? |
m_arr […]
char |
??? |
m_arr [98]
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
The circular buffer:
But:
[99]
[0]
[98]
[1]
[97]
[2]
[…]
[…]
[99]
[0]
[98]
[1]
[97]
[2]
[…]
[…]
[99]
[0]
[98]
[1]
[97]
[2]
[…]
[…]
front
back
front
back
back
front
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
Circular array issues (continued):
An empty Queue, after a dequeue operation:
m_front := 97 m_back := 97
intQueue.pop();
int |
… |
int |
… |
int |
… |
m_arr [0]
m_arr [1]
m_arr […]
int |
11 |
m_arr [97]
int |
… |
m_arr [96]
int |
… |
m_arr [98]
int |
… |
m_arr [99]
m_front := 98 m_back := 97
int |
… |
int |
… |
int |
… |
m_arr [0]
m_arr [1]
m_arr […]
int |
11 |
m_arr [97]
int |
… |
m_arr [96]
int |
… |
m_arr [98]
int |
… |
m_arr [99]
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
Circular array issues (continued):
A full Queue, after an enqueue operation:
m_front := 98 m_back := 96
intQueue.push(9);
m_front := 98 m_back := 97
int |
11 |
int |
5 |
int |
… |
m_arr [0]
m_arr [1]
m_arr […]
int |
9 |
m_arr [97]
int |
2 |
m_arr [96]
int |
5 |
m_arr [98]
int |
1 |
m_arr [99]
int |
11 |
int |
5 |
int |
… |
m_arr [0]
m_arr [1]
m_arr […]
int |
… |
m_arr [97]
int |
2 |
m_arr [96]
int |
5 |
m_arr [98]
int |
1 |
m_arr [99]
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
Circular buffer specifications to detect �full-Queue & empty-Queue conditions:
Queue Initialization:
[99]
[0]
[98]
[1]
[97]
[2]
[…]
[…]
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
Queue Insertion (at the back):
if ( !full() ) {
m_back = (m_back+1) % m_capacity;
m_arr[m_back] = newElement;
++m_size;
}
Queue Removal (from the front):
if ( !empty() ) {
m_front = (m_front+1) % m_capacity;
--m_size;
}
Keeping track of Queue size via a helper element-counting variable.
Advancing back & front indexes in the array as data are push’ed & pop’ped.
Circular Array-based Queue(s)
CS-202 C. Papachristos
Circular Array Implementation(s)
class ArrayQueue{
public:
ArrayQueue();
ArrayQueue(size_t count, const DataType & val);
ArrayQueue(const ArrayQueue & other);
~ArrayQueue();
ArrayQueue & operator=(const ArrayQueue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
void resize(size_t count);
DataType * m_arr;
size_t m_front, m_back;
size_t m_size, m_capacity;
};
typedef some-pod-or-class-or-struct-type DataType;
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s)
A Queue can be implemented with a Linked List, as shown here.
Data |
… |
… |
m_next
0x…
Node 1
Data |
… |
… |
m_next
0x…
Node 0
m_front
Data |
… |
… |
m_next
0x…
Node n
Data |
… |
… |
m_next
0x…
Node …
NULL
m_back
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s) – Uncommon Case
A Queue can be implemented with a Linked List, as shown here.
Data |
… |
… |
m_next
0x…
Node 1
Data |
… |
… |
m_next
0x…
Node 0
Data |
… |
… |
m_next
0x…
Node n
Data |
… |
… |
m_next
0x…
Node …
m_back
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s)
A Queue push() (enqueue) operation.
Elements are exclusively pushed to the back of the Queue.
DataType |
… |
… |
m_next
0x…
Node 1
DataType |
… |
… |
m_next
0x…
Node 0
m_front
DataType |
… |
… |
m_next
0x…
Node n
DataType |
… |
… |
m_next
0x…
Node …
NULL
m_back
DataType |
… |
… |
m_next
0x…
Node ?
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s)
A Queue push() (enqueue) operation.
Elements are exclusively pushed to the back of the Queue.
DataType |
… |
… |
m_next
0x…
Node 1
DataType |
… |
… |
m_next
0x…
Node 0
m_front
DataType |
… |
… |
m_next
0x…
Node n-1
DataType |
… |
… |
m_next
0x…
Node …
NULL
m_back
DataType |
… |
… |
m_next
0x…
Node k
2)
3)
1)
1) Node * newNode_Pt = new Node(Data(…), nullptr);
2) m_back->m_next = newNode_Pt;
3) m_back = newNode_Pt;
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s) – Uncommon Case
A Queue push() (enqueue) operation.
Elements are exclusively pushed to the back of the Queue.
DataType |
… |
… |
m_next
0x…
Node 1
DataType |
… |
… |
m_next
0x…
Node 0
DataType |
… |
… |
m_next
0x…
Node n-1
DataType |
… |
… |
m_next
0x…
Node …
m_back
DataType |
… |
… |
m_next
0x…
Node ?
DataType |
… |
… |
m_next
0x…
Node n
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s) – Uncommon Case
A Queue push() (enqueue) operation.
Elements are exclusively pushed to the back of the Queue.
DataType |
… |
… |
m_next
0x…
Node 1
DataType |
… |
… |
m_next
0x…
Node 0
DataType |
… |
… |
m_next
0x…
Node n-1
DataType |
… |
… |
m_next
0x…
Node …
DataType |
… |
… |
m_next
0x…
Node k
DataType |
… |
… |
m_next
0x…
Node n
1) Node * newNode_Pt = new Node(Data(…), m_back->m_next);
2) m_back->m_next = newNode_Pt;
3) m_back = newNode_Pt;
2)
3)
1)
m_back
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s)
A Queue push() (enqueue) operation.
An originally empty Queue.
NULL
m_front
m_back
DataType |
… |
… |
m_next
0x…
Node n
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s)
A Queue push() (enqueue) operation.
An originally empty Queue.
NULL
m_front
m_back
DataType |
… |
… |
m_next
0x…
Node n
1) Node * newNode_Pt =� new Node(Data(…), nullptr);
2) m_back = newNode_Pt;
3) m_front = newNode_Pt;
m_back
DataType |
… |
… |
m_next
0x…
Node n
1) Node * newNode_Pt = new Node(Data(…));
2) newNode_Pt->m_next = newNode_Pt;
3) m_back = newNode_Pt;
Uncommon Case
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s)
A Queue pop() (dequeue) operation.
Elements are exclusively popped from the front of the Queue.
DataType |
… |
… |
m_next
0x…
Node 1
DataType |
… |
… |
m_next
0x…
Node 0
m_front
DataType |
… |
… |
m_next
0x…
Node n
DataType |
… |
… |
m_next
0x…
Node …
NULL
m_back
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s)
A Queue pop() (dequeue) operation.
Elements are exclusively popped from the front of the Queue.
DataType |
… |
… |
m_next
0x…
Node 1
DataType |
… |
… |
m_next
0x…
Node 0
m_front
DataType |
… |
… |
m_next
0x…
Node n
DataType |
… |
… |
m_next
0x…
Node …
NULL
m_back
del_pt
1) Node * del_pt = m_front;
2) m_front = m_front->m_next;
3) delete del_pt;
2)
3)
1)
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s) – Uncommon Case
A Queue pop() (dequeue) operation.
Elements are exclusively popped from the front of the Queue.
DataType |
… |
… |
m_next
0x…
Node 1
DataType |
… |
… |
m_next
0x…
Node 0
DataType |
… |
… |
m_next
0x…
Node n
DataType |
… |
… |
m_next
0x…
Node …
m_back
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s) – Uncommon Case
A Queue pop() (dequeue) operation.
Elements are exclusively popped from the front of the Queue.
DataType |
… |
… |
m_next
0x…
Node 1
DataType |
… |
… |
m_next
0x…
Node 0
DataType |
… |
… |
m_next
0x…
Node n
DataType |
… |
… |
m_next
0x…
Node …
m_back
1) Node * del_pt = m_back->m_next;
2) m_back->m_next = del_pt->m_next;
3) delete del_pt;
DataType |
… |
… |
m_next
0x…
Node 0
del_pt
1)
2)
3)
Forward List-based Queue(s)
CS-202 C. Papachristos
List-based Implementation(s)
class ListQueue {
public:
ListQueue();
ListQueue(size_t count, const DataType & val);
ListQueue(const ListQueue & other);
~ListQueue();
ListQueue & operator=(const ListQueue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
Node * m_front;
Node * m_back;
size_t m_size; //tentative
};
typedef some-od-or-class-or-struct-type DataType;
class Node {
friend class ListQueue;
public:
Node()
: m_next(nullptr)
{ }
Node(const DataType & data,
Node * next = nullptr)
: m_next(next)
, m_data(data)
{ }
Node(const Node & other)
: m_next(other.m_next)
, m_data(other.m_data)
{ }
DataType & data(){
return m_data;
}
const DataType & data() const{
return m_data;
}
private:
Node * m_next;
DataType m_data;
};
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
Queue Implementation(s)
class ArrayQueue{
public:
ArrayQueue();
ArrayQueue(size_t count, const DataType & val);
ArrayQueue(const ArrayQueue & other);
~ArrayQueue();
ArrayQueue & operator=(const ArrayQueue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & head();
DataType & tail();
private:
void resize(size_t count);
DataType * m_arr;
size_t m_head, m_tail;
size_t m_size, m_capacity;
};
class ListQueue {
public:
ListQueue();
ListQueue(size_t count, const DataType & val);
ListQueue(const ListQueue & other);
~ListQueue();
ListQueue & operator=(const ListQueue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
Node * m_front;
Node * m_back;
size_t m_size; //tentative
};
Difference lies not in�public Interface, but�in private�Implementation !
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
2 Ways to Refactor to acquire a class Queue
Since the public Interface is common, we can design a common�class Queue that leverages Implementation mechanics differently!
How?�A) Refactor our class to have a common m_container member which can be a � Dynamic Container of Different Types !
C) For some arbitrary Dynamic Container class to be “compatible” with class Queue, it should “support” all the functionalities invoked in the public Interface of Queue.
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
1st Way to Refactor: Run-time Construction & Dispatch
class IContainer {
// just a collection of pure virtual methods, in order to enforce that� // every derived class from IContainer will have these implemented,
// i.e., will have the same public interface
public:
virtual bool empty() const = 0;
virtual size_t size() const = 0;
virtual Node * insertAfter(const Node * pos, DataType & value) = 0;
virtual Node * eraseAfter(const Node * pos) = 0;
virtual void clear() = 0;
virtual Node * head() = 0;
virtual Node * tail() = 0;
...
private:
// no members necessary, just an "interface" class
};
#ifndef QUEUE_H_
#define QUEUE_H_
#include <Container/ IContainer.h >
class Queue{
public:
Queue(std::string type="forwardlist");
Queue(size_t count, const DataType & val,
std::string type="forwardlist");
Queue(const Queue & other,
std::string type="forwardlist");
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
IContainer * m_container;
};
#endif
“Common” Header File: Queue.h
Remember:�Abstract classes as “unifying” Interface classes to derive from:
Any class that is-an IContainer (i.e., Derives from and Implements it)�is “compatible” to be used for m_container !
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
1st Way to Refactor: Run-time Construction & Dispatch
#ifndef QUEUE_H_
#define QUEUE_H_
#include <Container/ IContainer.h >
class Queue{
public:
Queue(std::string type="forwardlist");
Queue(size_t count, const DataType & val,
std::string type="forwardlist");
Queue(const Queue & other,
std::string type="forwardlist");
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
IContainer * m_container;
};
#endif
“Common” Header File: Queue.h
Remember:�Virtual methods are dynamically dispatched at run-time:
class ForwardListContainer : public IContainer {
public:
ForwardListContainer();
ForwarListContainer(size_t count, const DataType & val);
ForwardListContainer(const ForwardlistContainer & other);
~ForwardListContainer();
ForwardListContainer & operator=(const ForwardListContainer & other);
virtual bool empty() const override;
virtual size_t size() const override;
virtual Node * insertAfter(const Node * pos, DataType & value) override;
virtual Node * eraseAfter(const Node * pos) override;
virtual void clear() override;
virtual Node * head() override;
virtual Node * tail() override;
...
private:
Node * m_head;
Node * m_tail;
};
Note: Also necessary to have
class Node { … };
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
1st Way to Refactor: Run-time Construction & Dispatch
#ifndef QUEUE_H_
#define QUEUE_H_
#include <Container/ IContainer.h >
class Queue{
public:
Queue(std::string type="forwardlist");
Queue(size_t count, const DataType & val,
std::string type="forwardlist");
Queue(const Queue & other,
std::string type="forwardlist");
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
IContainer * m_container;
};
#endif
“Common” Header File: Queue.h
class ForwardListContainer : public IContainer {
public:
...
virtual Node * insertAfter(const Node * pos, DataType & value) override{
Node * newNode_pt = new Node(value, pos->m_next);
if empty() { m_head = m_tail = newNode_pt; }
else { pos->m_next = newNode_pt; }
return newNode_pt;
}
virtual Node * tail() override{
return m_tail;
}
private:
Node * m_head, * m_tail;
};
void Queue::push(const DataType & value){
m_container-> insertAfter (m_container-> tail() ,
value);
}
If the Queue code is in terms of the IContainer public Interface, the appropriate method is called in at run-time!
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
1st Way to Refactor: Run-time Construction & Dispatch
#ifndef QUEUE_H_
#define QUEUE_H_
#include <Container/ IContainer.h >
class Queue{
public:
Queue(std::string type="forwardlist");
Queue(size_t count, const DataType & val,
std::string type="forwardlist");
Queue(const Queue & other,
std::string type="forwardlist");
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
IContainer * m_container;
};
#endif
“Common” Header File: Queue.h
Remember:�Virtual methods are dynamically dispatched at run-time:
class ListContainer : public IContainer {
public:
ListContainer();
ListContainer(size_t count, const DataType & val);
ListContainer(const ListContainer & other);
~ListContainer();
ListContainer & operator=(const ListContainer & other);
virtual bool empty() const override;
virtual size_t size() const override;
virtual Node * insertAfter(const Node * pos, DataType & value) override;
virtual Node * eraseAfter(const Node * pos) override;
virtual void clear() override;
virtual Node * head() override;
virtual Node * tail() override;
...
private:
Node * m_head;
Node * m_tail;
};
Note: Also necessary to have
class Node { … };
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
1st Way to Refactor: Run-time Construction & Dispatch
#ifndef QUEUE_H_
#define QUEUE_H_
#include <Container/ IContainer.h >
class Queue{
public:
Queue(std::string type="forwardlist");
Queue(size_t count, const DataType & val,
std::string type="forwardlist");
Queue(const Queue & other,
std::string type="forwardlist");
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
IContainer * m_container;
};
#endif
“Common” Header File: Queue.h
If the Queue code is in terms of the IContainer public Interface, the appropriate method is called in at run-time!
class ListContainer : public IContainer {
public:
...
virtual Node * insertAfter(const Node * pos, DataType & value) override{
Node * newNode_pt = new Node(value, pos->m_next, pos);
if empty() { m_head = m_tail = newNode_pt; }
else { pos->m_next = newNode_pt; newNode_pt->m_next->m_prev = newNode_pt;}
return newNode_pt;
}
virtual Node * tail() override{
return m_tail;
}
private:
Node * m_head, * m_tail;
};
void Queue::push(const DataType & value){
m_container-> insertAfter (m_container-> tail() ,
value);
}
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
1st Way to Refactor: Run-time Construction & Dispatch
#ifndef QUEUE_H_
#define QUEUE_H_
#include <Container/ IContainer.h >
class Queue{
public:
Queue(std::string type="forwardlist");
Queue(size_t count, const DataType & val,
std::string type="forwardlist");
Queue(const Queue & other,
std::string type="forwardlist");
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
IContainer * m_container;
};
#endif
“Common” Header File: Queue.h
BUT (Limitations)
A) Construction of a Queue requires to instantiate the m_container-pointed object as well.
Queue::Queue( std::string type )
{
if (type == "forwardlist")
m_container = new ForwardListContainer();
else if (type == "list")
m_container = new ListContainer();
else
throw std::runtime_error(type + " is not supported");
}
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
1st Way to Refactor: Run-time Construction & Dispatch
#ifndef QUEUE_H_
#define QUEUE_H_
#include <Container/ IContainer.h >
class Queue{
public:
Queue(std::string type="forwardlist");
Queue(size_t count, const DataType & val,
std::string type="forwardlist");
Queue(const Queue & other,
std::string type="forwardlist");
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
IContainer * m_container;
};
#endif
“Common” Header File: Queue.h
BUT (Limitations)
B) Dynamic Type Compatibility is restrictive.
class ListContainer : public IContainer {
...
private:
Node * m_head;
Node * m_tail;
};
class ForwardListContainer : public IContainer {
...
private:
Node * m_head;
Node * m_tail;
};
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
1st Way to Refactor: Run-time Construction & Dispatch
#ifndef QUEUE_H_
#define QUEUE_H_
#include <Container/ IContainer.h >
class Queue{
public:
Queue(std::string type="forwardlist");
Queue(size_t count, const DataType & val,
std::string type="forwardlist");
Queue(const Queue & other,
std::string type="forwardlist");
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
IContainer * m_container;
};
#endif
“Common” Header File: Queue.h
BUT (Limitations)
C) Dynamic Type Compatibility is very restrictive.
class ListContainer … { … private: Node * m_head, * m_tail; };
Node * ListContainer::insertAfter(const Node * pos,
DataType & value){
Node * newNode_pt = new Node(value, pos->m_next, pos);
…
}
class ForwardListContainer … { … private: Node * m_head, * m_tail; };
Node * ForwardListContainer::insertAfter(const Node * pos,
DataType & value){
Node * newNode_pt = new Node(value, pos->m_next);
…
}
Doubly-Linked requires Node to support both prev & next!
But, Singly-Linked could do with a Node with just next!
Note: Could perhaps introduce an INode interface class, but this creates further�complication with more interface designs & dynamic dispatch obscurity!
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
1st Way to Refactor: Run-time Construction & Dispatch
#ifndef QUEUE_H_
#define QUEUE_H_
#include <Container/ IContainer.h >
class Queue{
public:
Queue(std::string type="forwardlist");
Queue(size_t count, const DataType & val,
std::string type="forwardlist");
Queue(const Queue & other,
std::string type="forwardlist");
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
IContainer * m_container;
};
#endif
“Common” Header File: Queue.h
BUT (Limitations)
D) Dynamic Type Compatibility is too restrictive.
class CircArrayContainer : public IContainer {
public:
CircArrayContainer();
CircArrayContainer(size_t count, const DataType & val);
CircArrayContainer(const CircArrayContainer & other);
~CircArrayContainer();
CircArrayContainer & operator=(const CircArrayContainer & other);
virtual bool empty() const override;
virtual size_t size() const override;
virtual Node * insertAfter(const Node * pos, DataType & value) override;
virtual Node * eraseAfter(const Node * pos) override;
virtual void clear() override;
virtual Node * head() override;
virtual Node * tail() override;
...
private:
void resize(size_t count);
DataType * m_arr;
size_t m_head, m_tail;
size_t m_size, m_capacity;
};
How can we specify via a Node *�the element in the array of DataType
objects ?
This has become infeasible / un-necessarily complicated …
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
2nd Way to Refactor: Compile-time Determined Composition
#ifndef QUEUE_H_
#define QUEUE_H_
class Queue{
public:
Queue();
Queue(size_t count, const DataType & val);
Queue(const Queue & other);
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
Container m_container;
};
#endif
1st Header File: CircularArrayQueue.h
#ifndef CIRCULAR_ARRAY_QUEUE_H_
#define CIRCULAR_ARRAY_QUEUE_H_
#include <CircularArray/CircularArray.h>
// When we say: "Container" the compiler will understand
// it as an alias for "CircularArray" which is a class
typedef CircularArray Container;
#include <Queue/Queue.h> //Dependent on "Container"
#endif
2nd Header File: ForwardListQueue.h
#ifndef FORWARD_LIST_QUEUE_H_
#define FORWARD_LIST_QUEUE_H_
#include <ForwardList/ForwardList.h>
// When we say: "Container" the compiler will understand
// it as an alias for "ForwardList" which is a class
typedef ForwardList Container;
#include <Queue/Queue.h> //Dependent on "Container"
#endif
OR
“Common” Header File: Queue.h
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
2nd Way to Refactor: Compile-time Determined Composition
#ifndef QUEUE_H_
#define QUEUE_H_
class Queue{
public:
Queue();
Queue(size_t count, const DataType & val);
Queue(const Queue & other);
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
Container m_container;
};
#endif
class ForwardList {
public:
ForwardList();
ForwardList(size_t count, const DataType & val);
ForwardList(const ForwardList & other);
~ForwardList();
ForwardList & operator=(const ForwardList & other);
bool empty() const;
size_t size() const;
Node * insertAfter(const Node * pos, DataType & value);
Node * eraseAfter(const Node * pos);
void clear();
Node * head();
Node * tail();
...
private:
Node * m_head;
Node * m_tail;
};
Queue::Queue() : m_container() { }
void Queue::push(const DataType & value){
m_container. insertAfter (m_container. tail() , value);
}
“Common” Header File: Queue.h
Now things should make sense when compiling (or not at all)
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
2nd Way to Refactor: Compile-time Determined Composition
#ifndef QUEUE_H_
#define QUEUE_H_
class Queue{
public:
Queue();
Queue(size_t count, const DataType & val);
Queue(const Queue & other);
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
Container m_container;
};
#endif
class CircularArray {
public:
CircularArray();
CircularArray(size_t count, const DataType & val);
CircularArray(const CircularArray & other);
~CircularArray();
CircularArray & operator=(const CircularArray & other);
bool empty() const;
size_t size() const;
DataType * insertAfter(const DataType * pos, DataType & value);
DataType * eraseAfter(const DataType * pos);
void clear();
DataType * head();
DataType * tail();
...
private:
void resize(size_t count);
DataType * m_arr;
size_t m_head, m_tail;
size_t m_size, m_capacity;
};
Queue::Queue() : m_container() { }
void Queue::push(const DataType & value){
m_container. insertAfter (m_container. tail() , value);
}
“Common” Header File: Queue.h
Now things should make sense when compiling (or not at all)
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
2nd Way to Refactor: Compile-time Determined Composition
#ifndef QUEUE_H_
#define QUEUE_H_
class Queue{
public:
Queue();
Queue(size_t count, const DataType & val);
Queue(const Queue & other);
~Queue();
Queue & operator=(const Queue & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
Container m_container;
};
#endif
#include <Queue/CircularArrayQueue.h>
int main() {
Queue queue;
queue.push_back(DataType(…));
return 0;
}
“Common” Header File: Queue.h
We have to #include the appropriate header:
A) An implementation using Circular Array container-based Queues
#include <Queue/ForwardListQueue.h>
int main() {
Queue queue;
queue.push_back(DataType(…));
return 0;
}
B) An implementation using Forward List container-based Queues
#include <Queue/CircularArrayQueue.h>
#include <Queue/ForwardListQueue.h>
int main() {
Queue ??? circulararray_queue;
Queue ??? forwardlist_queue;
}
C) BUT Can we do both in the same Translation Unit ?
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
2nd Way to Refactor: Compile-time Determined Composition
#define CONCAT_HELPER(a,b) a ## b
#define CONCAT(a,b) CONCAT_HELPER(a,b)
#define CLASSNAME CONCAT(__CONTAINER__,Queue)
class CLASSNAME{
public:
CLASSNAME();
CLASSNAME(size_t count, const DataType & val);
CLASSNAME(const CLASSNAME & other);
~CLASSNAME();
CLASSNAME & operator=(const CLASSNAME & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
__CONTAINER__ m_container;
};
“Common” Header File: Queue.h
1st Header File: CircularArrayQueue.h
#ifndef CIRCULAR_ARRAY_QUEUE_H_
#define CIRCULAR_ARRAY_QUEUE_H_
#include <CircularArray/CircularArray.h>
#define __CONTAINER__ CircularArray
#include <Queue/Queue.h> //Dependent on "__CONTAINER__"
#undef __CONTAINER__
#endif
2nd Header File: ForwardListQueue.h
#ifndef FORWARD_LIST_QUEUE_H_
#define FORWARD_LIST_QUEUE_H_
#include <ForwardList/ForwardList.h>
#define __CONTAINER__ ForwardList
#include <Queue/Queue.h> //Dependent on "__CONTAINER__"
#undef __CONTAINER__
#endif
We have to create 2 different Queue classes …
Or we can pass the task�to the preprocessor !
Coming soon: C++ enables us to do this much more elegantly!
Advanced: Queue(s) as Container Adaptors
CS-202 C. Papachristos
2nd Way to Refactor: Compile-time Determined Composition
#define CONCAT_HELPER(a,b) a ## b
#define CONCAT(a,b) CONCAT_HELPER(a,b)
#define CLASSNAME CONCAT(__CONTAINER__,Queue)
class CLASSNAME{
public:
CLASSNAME();
CLASSNAME(size_t count, const DataType & val);
CLASSNAME(const CLASSNAME & other);
~CLASSNAME();
CLASSNAME & operator=(const CLASSNAME & other);
bool empty() const;
size_t size() const;
void push(const DataType & value);
void pop();
void clear();
DataType & front();
DataType & back();
private:
__CONTAINER__ m_container;
};
“Common” Header File: Queue.h
#include <Queue/CircularArrayQueue.h>
#include <Queue/ForwardListQueue.h>
int main() {
CircularArrayQueue circulararray_queue;
ForwardListQueue forwardlist_queue;
circulararray_queue. push_back (DataType(…));
forwardlist_queue. push_back (DataType(…));
return 0;
}
And now we in fact have 2 different�(__CONTAINER__Queue)classes …
Queue(s)
CS-202 C. Papachristos
Queue Applications
A “Simulation”
Goal
Queue(s)
CS-202 C. Papachristos
Queue Applications
A Discrete–Event “Simulation” – example:
As customers arrive, they go to the back of the line:
Time for Questions !
CS-202
CS-202 C. Papachristos