merging exam branch

This commit is contained in:
Christopher Sanden
2025-11-21 12:52:41 +01:00
158 changed files with 11720 additions and 8539 deletions

6
.gitignore vendored
View File

@@ -1,6 +1,2 @@
# Ignore
*.zip *.zip
*.ZIP
*.Zip
*.clang-format
*cmake-build-debug
*.idea

5
Exam/IKT203Exam/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
# Ignore Visual Studio cache
.vs/
# Ignore build/output directory
out/

8
Exam/IKT203Exam/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

1
Exam/IKT203Exam/.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
IKT203_Course_Assignments

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="54f3be3a:19a5567208e:-7ffe" />
</MTProjectMetadataState>
</option>
</component>
</project>

View File

@@ -2,7 +2,7 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/assignment1.iml" filepath="$PROJECT_DIR$/.idea/assignment1.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/IKT203-main.iml" filepath="$PROJECT_DIR$/.idea/IKT203-main.iml" />
</modules> </modules>
</component> </component>
</project> </project>

6
Exam/IKT203Exam/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,26 @@
# Set the minimum version of CMake required to build this project.
# This ensures that older versions of CMake don't try to run with features they don't understand.
cmake_minimum_required(VERSION 3.20)
# Define the project name. This will be the top-level name in Visual Studio.
# It also enables the C++ language (CXX).
project(IKT203_Course_Assignments LANGUAGES CXX)
# We enforce C++17 (or C++20 if you prefer) for the entire project.
# All targets (exercises, libraries, etc.) will inherit this setting.
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if(MSVC)
add_compile_options("/Zc:__cplusplus")
endif()
# This is the core of the orchestration. CMake will now step into each of these
# folders and process their own CMakeLists.txt files.
# The order matters here: we add LibExample first so that its library is
# defined before the executables that need to link to it.
add_subdirectory(Portfolio)
# --- End of File ---

View File

@@ -0,0 +1,61 @@
{
"version": 3,
"configurePresets": [
{
"name": "windows-base",
"hidden": true,
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"installDir": "${sourceDir}/out/install/${presetName}",
"cacheVariables": {
"CMAKE_C_COMPILER": "cl.exe",
"CMAKE_CXX_COMPILER": "cl.exe"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
},
{
"name": "x64-debug",
"displayName": "x64 Debug",
"inherits": "windows-base",
"architecture": {
"value": "x64",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "x64-release",
"displayName": "x64 Release",
"inherits": "x64-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "x86-debug",
"displayName": "x86 Debug",
"inherits": "windows-base",
"architecture": {
"value": "x86",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "x86-release",
"displayName": "x86 Release",
"inherits": "x86-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@@ -0,0 +1,37 @@
[NODES;records:=9]
Oslo
Bergen
Trondheim
Stavanger
Kristiansand
Ålesund
Molde
Bodø
Tromsø
[EDGES;records:=26]
Oslo;Bergen;450
Bergen;Oslo;460
Oslo;Trondheim;820
Trondheim;Oslo;790
Oslo;Stavanger;600
Stavanger;Oslo;650
Bergen;Stavanger;210
Stavanger;Bergen;190
Bergen;Trondheim;540
Trondheim;Bergen;520
Trondheim;Ålesund;220
Ålesund;Trondheim;240
Ålesund;Bergen;310
Bergen;Ålesund;300
Stavanger;Kristiansand;180
Kristiansand;Stavanger;170
Kristiansand;Oslo;400
Oslo;Kristiansand;410
Trondheim;Bodø;600
Bodø;Trondheim;620
Bodø;Tromsø;500
Tromsø;Bodø;480
Ålesund;Molde;130
Molde;Ålesund;120
Molde;Trondheim;260
Trondheim;Molde;250

View File

@@ -0,0 +1,29 @@
[NODES;records:=11]
WebServer
Router1
AuthService
CacheServer
Database
APIGateway
LoadBalancer
Firewall
AnalyticsServer
BackupServer
FileServer
[EDGES;records:=16]
WebServer;Router1;4
WebServer;AuthService;8
WebServer;CacheServer;5
Router1;Database;3
Router1;LoadBalancer;2
Router1;Firewall;7
AuthService;Database;2
AuthService;APIGateway;6
CacheServer;LoadBalancer;4
CacheServer;FileServer;3
FileServer;BackupServer;9
Database;AnalyticsServer;5
APIGateway;AnalyticsServer;4
LoadBalancer;Firewall;3
Firewall;BackupServer;8
AnalyticsServer;BackupServer;7

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
[records:=50]
Dire Straits;Love Over Gold;1984;rock;MusicBrainz
Dire Straits;Expresso Love;1984;rock;MusicBrainz
Dire Straits;Romeo and Juliet;1984;rock;MusicBrainz
Dire Straits;Telegraph Road;1984;rock;MusicBrainz
Dire Straits;Private Investigations;1984;rock;MusicBrainz
Dire Straits;Tunnel of Love (intro: The Carousel Waltz);1984;rock;MusicBrainz
Dire Straits;Once Upon a Time in the West;1984;rock;MusicBrainz
Dire Straits;Going Home (theme from Local Hero);1984;rock;MusicBrainz
Ry Cooder;634-5789 (Soulsville, U.S.A.);1980;rock;MusicBrainz
Rush;The Body Electric;1984;rock;MusicBrainz
Rush;Afterimage;1984;rock;MusicBrainz
Rush;Kid Gloves;1984;rock;MusicBrainz
Rush;Red Lenses;1984;rock;MusicBrainz
Rush;Red Sector A;1984;rock;MusicBrainz
Rush;Between the Wheels;1984;rock;MusicBrainz
Barclay James Harvest;Say Youll Stay;1984;rock;MusicBrainz
Dire Straits;Solid Rock;1984;rock;MusicBrainz
King Crimson;Model Man;1984;rock;MusicBrainz
King Crimson;No Warning;1984;rock;MusicBrainz
Queen;Tie Your Mother Down;1977;rock;MusicBrainz
The Long Ryders;(Sweet) Mental Revenge;1984;rock;MusicBrainz
Ry Cooder;Crazy bout an Automobile (Every Woman I Know);1980;rock;MusicBrainz
Hawkwind;Jack of Shadows;1979;rock;MusicBrainz
Kauko Röyhkä & Narttu;Pähkinäpuu;1984;rock;MusicBrainz
Kauko Röyhkä & Narttu;Tuulee;1984;rock;MusicBrainz
Echo & The Bunnymen;Seven Seas "Life At Brian's - Lean And Hungry";1984;rock;Discogs
Billy Squier;Signs Of Life;1984;rock;Discogs
Paul McCartney;No More Lonely Nights;1984;rock;Discogs
Body Checks;Tätowiert + Kahlgeschoren;1984;rock;Discogs
Trust (2);Serre Les Poings;1984;rock;Discogs
Mama's Boys;Mama's Boys;1984;rock;Discogs
Def Leppard;High 'N' Dry;1984;rock;Discogs
Savage Republic;Tragic Figure;1984;rock;Discogs
The Icicle Works;The Icicle Works;1984;rock;Discogs
Twisted Sister;We're Not Gonna Take It;1984;rock;Discogs
The Pogues;The Boys From The County Hell;1984;rock;Discogs
The Takeaways;Sweet And Sour Volume 2;1984;rock;Discogs
Rick Springfield;Beautiful Feelings;1984;rock;Discogs
Vice Squad;Teenage Rampage;1984;rock;Discogs
The Vipers (4);Outta The Nest;1984;rock;Discogs
Rolands Gosskör;Genom Barriären;1984;rock;Discogs
Willie Loco Alexander*;Taxi-Stand Diane;1984;rock;Discogs
Kansas (2);The Best Of Kansas;1984;rock;Discogs
The Rolling Stones;30 Años De Musica Rock - Salvat;1984;rock;Discogs
Lindisfarne;Nicely Out Of Tune;1984;rock;Discogs
Andy Summers;2010 / To Hal And Back;1984;rock;Discogs
The Beatles;I Feel Fine;1984;rock;Discogs
Neil Diamond;Love Songs;1984;rock;Discogs
The Sensational Alex Harvey Band;Live;1984;rock;Discogs
Subterfuge (10);Who's The Fool;1984;rock;Discogs

View File

@@ -0,0 +1,40 @@
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# "ON" = build Option 1, "OFF" = build Option 2.
option(BUILD_ASSIGNMENT_01_OPTION_1 "Build Assignment Option 1 (Standard)" ON)
add_executable(Assignment-01
main.cpp
)
# Conditionally add the correct source file
if(BUILD_ASSIGNMENT_01_OPTION_1)
# If ON, add option1.cpp and define 'ASSIGNMENT_OPTION=1' for C++
target_sources(Assignment-01
PRIVATE
option1.cpp
option1.h
)
target_compile_definitions(Assignment-01 PRIVATE "ASSIGNMENT_01_OPTION=1")
else()
# If OFF, add option2.cpp and define 'ASSIGNMENT_OPTION=2' for C++
target_sources(Assignment-01
PRIVATE
option2.cpp
option2.h
)
target_compile_definitions(Assignment-01 PRIVATE "ASSIGNMENT_01_OPTION=2")
endif()
target_link_libraries(Assignment-01
PRIVATE
SharedLib
)
add_custom_command(TARGET Assignment-01 POST_BUILD
# Add a custom command here if needed
COMMAND ${CMAKE_COMMAND} -E echo "Assignment-01 post-build step"
)

View File

@@ -0,0 +1,60 @@
// Mandatory-02.cpp : Defines the entry point for the application.
//
/*
Dear Student,
Remember to follow the coding standards and best practices discussed
in the portfolio assignment document.
Good luck with your portfolio!
NB: Do not delete the code below that prints the assignment and option info!
---------------------------------------------------------------------
*** HOW TO SWITCH BETWEEN OPTION 1 AND OPTION 2 ***
---------------------------------------------------------------------
You CANNOT switch options by changing this file.
1. Go to the 'CMakeLists.txt' file for this assignment.
2. Find the line:
option(BUILD_ASSIGNMENT_OPTION_1 "..." ON)
3. Change 'ON' (for Option 1) to 'OFF' (for Option 2).
*** VERY IMPORTANT: After changing the option ***
Your project will NOT update until you re-run the CMake configuration.
To force an update (e.g., in Visual Studio):
- Right-click the 'CMakeLists.txt' file and select 'Configure Cache'.
- OR, simply delete the 'out' / 'build' folder and rebuild the project.
---------------------------------------------------------------------
*/
#include <iostream>
#include <string_view>
static constexpr std::string_view AssignmentName = "Category 1: Lists, Stacks, & Queues";
#if ASSIGNMENT_01_OPTION == 1
#include "option1.h"
static constexpr std::string_view AssignmentOption = "Option 1 (Standard): Console Text Editor.";
#elif ASSIGNMENT_01_OPTION == 2
#include "option2.h"
static constexpr std::string_view AssignmentOption = "Option 2 (Advanced): Console Music Player.";
#endif
int main(int argc, char* argv[])
{
int appStatus = 0;
std::cout << AssignmentName << std::endl;
std::cout << AssignmentOption << std::endl;
// Create only core or common code in main.cpp
// Use the option header files to implement the specific assignment option logic
appStatus = RunApp();
return appStatus;
}

View File

@@ -0,0 +1,144 @@
// Option 1 (Standard): Console Text Editor.
// Implements the user-facing console loop for the text editor.
// Handles line editing, undo/redo logic, and print job queue operations.
#include "option1.h"
#include <iostream>
#include "TDoublyLinkedList.h"
#include "TTreeQueue.h"
#include "TStack.h"
#include "Utils.h"
TDoublyLinkedList document;
TTreeQueue printQueue;
TStack undoStack, redoStack;
bool running = true;
int lastIndex = 0;
std::string deletedLine;
// Undo the last text modification.
// Reverses INSERT/DELETE actions by applying the inverse operation.
// Moves reversed action into redoStack.
void Undo()
{
if (!undoStack.IsEmpty()) {
const auto action = undoStack.Pop();
if (action.action == INSERT){
document.Remove(action.index);
}
else{
document.InsertAtIndex(action.index, action.text);
}
redoStack.Push(action);
}
}
// Redo the last undone modification.
// Re-applies an action previously undone.
// Pushes the executed action back into undoStack.
void Redo()
{
if (!redoStack.IsEmpty()) {
const auto action = redoStack.Pop();
if (action.action == INSERT) {
document.InsertAtIndex(action.index, action.text);
}
else {
document.Remove(action.index);
}
undoStack.Push(action);
}
}
// Main menu loop for the Console Text Editor.
// Provides editing operations, queueing print jobs,
// and demonstrating FIFO behavior through job processing.
int RunApp()
{
// Implement the Console Text Editor application logic here
while (running) {
switch (Utils::Choice()) {
case 1: {
std::cout << "----------Add line----------" << std::endl;
lastIndex = Utils::Insert(document, undoStack, redoStack, lastIndex);
break;
}
case 2: {
std::cout << "----------Remove line----------" << std::endl;
Utils::PrintList(document);
lastIndex = Utils::RemoveLine(document, undoStack, redoStack, lastIndex);
break;
}
case 3: {
std::cout << "----------Current document----------" << std::endl;
for (int i = 0; i < document.GetSize(); i++) {
std::cout << document.GetAtIndex(i) << std::endl;
}
std::cout << std::endl;
break;
}
// Build a single print job containing the entire document.
// Snapshot is stored as a single string and enqueued.
case 4: {
std::cout << "----------Add print job----------" << std::endl;
if (document.GetSize() == 0) {
std::cout << "Document is empty - nothing added to print queue." << std::endl;
break;
}
std::string job;
for (int i = 0; i < document.GetSize(); i++) {
job += document.GetAtIndex(i) + "\n";
}
printQueue.Enqueue(job);
std::cout << "Print job added to queue." << std::endl;
break;
}
// Dequeue and print the next print job.
// Demonstrates FIFO (First-In-First-Out) queue behavior.
case 5: {
if (printQueue.IsEmpty()) {
std::cout << "No prints jobs in queue." << std::endl;
break;
}
std::string job = printQueue.Dequeue();
std::cout << "----------Printing job-----------" << std::endl;
std::cout << job << std::endl;
std::cout << "------------------------------------\n\n";
break;
}
case 6: {
std::cout << "----------UNDO----------" <<std::endl;
Undo();
break;
}
case 7: {
std::cout << "----------REDO----------" <<std::endl;
Redo();
break;
}
case 0: {
std::cout << "----------Exiting...----------" << std::endl;
running = false;
break;
}
default: {
std::cout << "Invalid input, please pick a number..." << std::endl;
break;
}
}
}
return 0;
}

View File

@@ -0,0 +1,14 @@
// Option 1: Console Text Editor
// Uses a doubly linked list for storing document lines,
// two stacks for undo/redo operations,
// and a queue for print-job management.
#pragma once
#ifndef OPTION1_H
#define OPTION1_H
int RunApp();
#endif // OPTION1_H

View File

@@ -0,0 +1,19 @@
// Option 2 (Advanced): Console Music Player.
#include <iostream>
#include "option2.h"
static bool SongReadCallback(const int aIndex, const int aTotalCount, const std::string& aArtist, const std::string& aTitle, const std::string& aYear, const std::string& aGenre, const std::string& aSource) {
// Implement the logic to process each song read from the file
// For example, print the song details to the console
std::cout << "Song " << (aIndex + 1) << " of " << aTotalCount << ":\n";
std::cout << " Artist: " << aArtist << "\n";
std::cout << " Title: " << aTitle << "\n";
std::cout << " Year: " << aYear << "\n";
std::cout << " Genre: " << aGenre << "\n";
std::cout << " Source: " << aSource << "\n\n";
// Return true to continue reading more songs
return true;
}

View File

@@ -0,0 +1,10 @@
// option1.h : Option 2 (Advanced): Console Music Player.
#pragma once
#ifndef OPTION2_H
#define OPTION2_H
int RunApp();
#endif // OPTION2_H

View File

@@ -0,0 +1,40 @@
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# "ON" = build Option 1, "OFF" = build Option 2.
option(BUILD_ASSIGNMENT_02_OPTION_1 "Build Assignment Option 1 (Standard)" ON)
add_executable(Assignment-02
main.cpp
)
# Conditionally add the correct source file
if(BUILD_ASSIGNMENT_02_OPTION_1)
# If ON, add option1.cpp and define 'ASSIGNMENT_OPTION=1' for C++
target_sources(Assignment-02
PRIVATE
option1.cpp
option1.h
)
target_compile_definitions(Assignment-02 PRIVATE "ASSIGNMENT_02_OPTION=1")
else()
# If OFF, add option2.cpp and define 'ASSIGNMENT_OPTION=2' for C++
target_sources(Assignment-02
PRIVATE
option2.cpp
option2.h
)
target_compile_definitions(Assignment-02 PRIVATE "ASSIGNMENT_02_OPTION=2")
endif()
target_link_libraries(Assignment-02
PRIVATE
SharedLib
)
add_custom_command(TARGET Assignment-02 POST_BUILD
# Add a custom command here if needed
COMMAND ${CMAKE_COMMAND} -E echo "Assignment-02 post-build step"
)

View File

@@ -0,0 +1,57 @@
// Mandatory-02.cpp : Defines the entry point for the application.
//
/*
Dear Student,
Remember to follow the coding standards and best practices discussed
in the portfolio assignment document.
Good luck with your portfolio!
NB: Do not delete the code below that prints the assignment and option info!
---------------------------------------------------------------------
*** HOW TO SWITCH BETWEEN OPTION 1 AND OPTION 2 ***
---------------------------------------------------------------------
You CANNOT switch options by changing this file.
1. Go to the 'CMakeLists.txt' file for this assignment.
2. Find the line:
option(BUILD_ASSIGNMENT_OPTION_1 "..." ON)
3. Change 'ON' (for Option 1) to 'OFF' (for Option 2).
*** VERY IMPORTANT: After changing the option ***
Your project will NOT update until you re-run the CMake configuration.
To force an update (e.g., in Visual Studio):
- Right-click the 'CMakeLists.txt' file and select 'Configure Cache'.
- OR, simply delete the 'out' / 'build' folder and rebuild the project.
---------------------------------------------------------------------
*/
#include <iostream>
#include <string_view>
static constexpr std::string_view AssignmentName = "Category 2: Sorting & Searching";
#if ASSIGNMENT_02_OPTION == 1
#include "option1.h"
static constexpr std::string_view AssignmentOption = "Option 1 (Standard): Cruise Ship Manifest.";
#elif ASSIGNMENT_02_OPTION == 2
#include "option2.h"
static constexpr std::string_view AssignmentOption = "Option 2 (Advanced): Combined Corporate Directory.";
#endif
int main(int argc, char* argv[])
{
int appStatus = 0;
std::cout << AssignmentName << std::endl;
std::cout << AssignmentOption << std::endl;
// Create only core or common code in main.cpp
// Use the option header files to implement the specific assignment option logic
appStatus = RunApp();
return appStatus;
}

View File

@@ -0,0 +1,123 @@
#include <iostream>
#include <string>
#include "option1.h"
#include <limits>
#include "SharedLib.h"
#include "TLinkedList.h"
#include "TPerson.h"
// Entry point for Category 2, Option 1 (Cruise Ship Manifest).
// Steps:
// 1) Load names from DATA/random_names.txt into employee and guest lists
// 2) Merge-sort both lists alphabetically (lastName, firstName)
// 3) Convert guests to an array and quick-sort by cabinSize, then lastName
// 4) Allow the user to search (binary search) by surname in the chosen list
int RunApp()
{
// Path to the names data file.
// IMPORTANT: working directory must be set so that "DATA/random_names.txt" resolves correctly.
const std::string filename = "DATA/random_names.txt";
pack("Reading names and grouping them.");
// Call the utility function with the name callback
readNamesFromFile(filename, onNameRead);
pack("Finished reading names.");
/////////////////////////// Merge sorting ///////////////////////////
// Sort both employee and guest linked lists alphabetically
// using the linked-list merge sort implementation in TLinkedList.
e.Sort();
g.Sort();
pack("Sorting.");
// Attempt at "beautifying" the terminal output somewhat
pack("Employees merge sorted alphabetically.");
const int employeeSize = e.GetSize();
auto** employeeAlphaSort = new TPerson*[employeeSize];
printline();
for (int i = 0; i < e.GetSize(); i++) {
std::cout << "[" << i << "] " << e.GetAtIndex(i).lastName << ", " << e.GetAtIndex(i).firstName
<< " | status: Employee | cabin size: " << e.GetAtIndex(i).cabinSize << std::endl;
employeeAlphaSort[i] = new TPerson(e.GetAtIndex(i));
}
printline();
pack("Guests merge sorted alphabetically.");
const int guestSize = g.GetSize();
auto** guestAlphaSort = new TPerson*[guestSize];
printline();
for (int i = 0; i < g.GetSize(); i++) {
std::cout << "[" << i << "] " << g.GetAtIndex(i).lastName << ", " << g.GetAtIndex(i).firstName
<< " | status: Guest | cabin size: " << g.GetAtIndex(i).cabinSize << std::endl;
guestAlphaSort[i] = new TPerson(g.GetAtIndex(i));
}
printline();
/////////////////////////// Quick sorting ///////////////////////////
// Build an array of guests and quick-sort it by:
// 1) cabinSize (ascending), then 2) lastName.
// This array is used to optimise cabin assignment.
// creating array from guest linked list
auto** guestList = new TPerson*[guestCount];
for (int i = 0; i < guestCount; i++) {
guestList[i] = new TPerson(g.GetAtIndex(i));
}
Utils::QuickSort(guestList, 0, guestCount - 1);
pack("Guests quick sorted by 1) cabinsize, 2) lastname.");
printline();
for (int i = 0; i < guestCount; i++) {
std::cout << "[" << i << "] " << guestList[i]->lastName << ", " << guestList[i]->firstName
<< " | cabinSize: " << guestList[i]->cabinSize << std::endl;
}
printline();
/////////////////////////// Binary search ///////////////////////////
// Let the user choose whether to search employees or guests,
// then perform binary search on the corresponding alphabetically
// sorted array and print all matches with that surname.
int choice;
std::string target;
std::cout << "What list do you want to search through: \n [1] Employee\n [2] Guest" << std::endl;
std::cin >> choice;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "Enter name to search for: " << std::endl;
std::getline(std::cin, target);
switch (choice) {
case 1: SearchAndPrint(employeeAlphaSort, employCount, target); break;
case 2: SearchAndPrint(guestAlphaSort, guestCount, target); break;
default: {
std::cout << "Choice invalid" << std::endl;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
}
/////////////////////////// Cleanup before exit ///////////////////////////
// Delete all dynamically allocated TPerson objects from:
// - alphabetical employee array
// - alphabetical guest array
// - quick-sorted guestList array
// Then clear the linked lists to avoid memory leaks.
for (int i = 0; i < employeeSize; ++i)
delete employeeAlphaSort[i];
delete[] employeeAlphaSort;
for (int i = 0; i < guestSize; ++i)
delete guestAlphaSort[i];
delete[] guestAlphaSort;
for (int i = 0; i < guestCount; ++i)
delete guestList[i];
delete[] guestList;
while (e.GetSize() > 0)
e.Remove(0);
while (g.GetSize() > 0)
g.Remove(0);
pack("Cleaned up memory");
return 0;
}

View File

@@ -0,0 +1,117 @@
// Option 1 (Standard): Cruise Ship Manifest.
// Uses linked lists, merge sort, quick sort, and binary search
// to manage guest and employee manifests from random_names.txt.
#pragma once
#ifndef OPTION1_H
#define OPTION1_H
#include "TLinkedList.h"
#include "TPerson.h"
// Global lists and counters used across the assignment:
// - 'e' stores EMPLOYEE records
// - 'g' stores GUEST records
// - guestCount / employCount track how many were loaded
inline TLinkedList g, e;
inline int guestCount = 0;
inline int employCount = 0;
int RunApp();
inline void printline()
{
std::cout << "----------------------------------------" << std::endl;
}
inline void pack(const std::string& line)
{
std::cout << "\n\n\n" << std::endl;
printline();
std::cout << line << std::endl;
printline();
std::cout << "\n\n\n" << std::endl;
}
/**
* @brief Callback function to process one name.
static bool NameReadCallback(const int aIndex, const int aTotalCount, const std::string& aFirstName, const std::string& aLastName)
{
std::cout << "Reading Name " << (aIndex + 1) << " of " << aTotalCount << ": "
<< aFirstName << " " << aLastName << "\n";
// We only want to read 10 names (index 0 through 9)
// Return false when aIndex is 9 to stop the loop after this one.
return (aIndex < 9);
}
*/
// Callback used by readNamesFromFile.
// - Creates a TPerson with status (EMPLOYEE or GUEST)
// - First 1500 entries are EMPLOYEE, the rest are GUEST
// - Appends each person to the appropriate linked list and updates counters
static bool onNameRead(const int aIndex, const int aTotalCount, const std::string& aFirstName, const std::string& aLastName)
{
// Determine status based on index: first 1500 are employees, rest are guests.
const ENumStatus status = (aIndex < 1500) ? EMPLOYEE : GUEST;
const TPerson p(aFirstName, aLastName, status);
if (status == EMPLOYEE) {
e.Append(p);
employCount++;
}
else {
g.Append(p);
guestCount++;
}
std::cout << "[" <<aIndex << "] " << aLastName << ", " << aFirstName << " | status: " << (status == 1 ? "Employee" : "Guest")
<< " | cabin size: " << p.cabinSize << std::endl;
return true;
}
// Binary-search helper:
// - Performs binary search on a sorted array of TPerson* (alphabetical by last name)
// - 'target' is the surname entered by the user
// - Expands left/right from the first match to find and print all matches
inline void SearchAndPrint(TPerson** targetArray, int arraySize, const std::string& target)
{
int index = Utils::BinarySearch(targetArray, 0, arraySize - 1, target);
if (index == -1) {
std::cout << "No match found" << std::endl;
return;
}
int left = index - 1;
int right = index + 1;
// Move left while neighbouring entries share the same first or last name
while (left >= 0 && (targetArray[left]->firstName == target || targetArray[left]->lastName == target)) {
--left;
}
// Move right while neighbouring entries share the same first or last name
while (right < arraySize && (targetArray[right]->firstName == target || targetArray[right]->lastName == target)) {
++right;
}
for (int i = left + 1; i < right; ++i) {
std::cout << "Match found: \nName: " << targetArray[i]->firstName << " "
<< targetArray[i]->lastName << " | status: " << (targetArray[i]->status == 0 ? "Guest" : "Employee")
<< " | cabinsize: " << targetArray[i]->cabinSize << "\n" << std::endl;
}
}
#endif // OPTION1_H

View File

@@ -0,0 +1,8 @@
// Option 2 (Advanced): Console Music Player.
#include "option2.h"
int RunApp() {
// Implement the Console Music Player application logic here
return 0;
}

View File

@@ -0,0 +1,10 @@
// option1.h : Option 2 (Advanced): Console Music Player.
#pragma once
#ifndef OPTION2_H
#define OPTION2_H
int RunApp();
#endif // OPTION2_H

View File

@@ -0,0 +1,44 @@
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# "ON" = build Option 1, "OFF" = build Option 2.
option(BUILD_ASSIGNMENT_03_OPTION_1 "Build Assignment Option 1 (Standard)" ON)
add_executable(Assignment-03
main.cpp
TBST.cpp
TBST.h
TEmployee.h
TAVL.cpp
TAVL.h
)
# Conditionally add the correct source file
if(BUILD_ASSIGNMENT_03_OPTION_1)
# If ON, add option1.cpp and define 'ASSIGNMENT_OPTION=1' for C++
target_sources(Assignment-03
PRIVATE
option1.cpp
option1.h
)
target_compile_definitions(Assignment-03 PRIVATE "ASSIGNMENT_03_OPTION=1")
else()
# If OFF, add option2.cpp and define 'ASSIGNMENT_OPTION=2' for C++
target_sources(Assignment-03
PRIVATE
option2.cpp
option2.h
)
target_compile_definitions(Assignment-03 PRIVATE "ASSIGNMENT_03_OPTION=2")
endif()
target_link_libraries(Assignment-03
PRIVATE
SharedLib
)
add_custom_command(TARGET Assignment-03 POST_BUILD
# Add a custom command here if needed
COMMAND ${CMAKE_COMMAND} -E echo "Assignment-03 post-build step"
)

View File

@@ -0,0 +1,243 @@
#include "TAVL.h"
#include <iostream>
#include <unordered_set>
#include "TTreeQueue.h"
#include "Utils.h"
// Private helpers
int TAVL::getHeight(const AVLNode *node)
{
return node ? node->height : 0;
}
int TAVL::getBalance(const AVLNode *node)
{
if (!node)
return 0;
return getHeight(node->left) - getHeight(node->right);
}
AVLNode *TAVL::rotateRight(AVLNode *y)
{
if (!y || !y->left)
return y;
AVLNode* x = y->left;
AVLNode* n2 = x ? x->right : nullptr;
x->right = y;
y->left = n2;
x->height = 1 + std::max(getHeight(x->left), getHeight(x->right));
y->height = 1 + std::max(getHeight(y->left), getHeight(y->right));
return x;
}
AVLNode *TAVL::rotateLeft(AVLNode *x)
{
if (!x || !x->right)
return x;
AVLNode* y = x->right;
AVLNode* n2 = y ? y->left : nullptr;
y->left = x;
x->right = n2;
x->height = 1 + std::max(getHeight(x->left), getHeight(x->right));
y->height = 1 + std::max(getHeight(y->left), getHeight(y->right));
return y;
}
// Recursive AVL insert:
// - Insert key as in a normal BST.
// - Update node height.
// - Compute balance factor and apply the appropriate rotation if unbalanced.
AVLNode *TAVL::insert(AVLNode *n, const int key)
{
if (!n)
return new AVLNode(key);
if (key < n->key)
n->left = insert(n->left, key);
else if (key > n->key)
n->right = insert(n->right, key);
else
return n; // Ignore duplicates
n->height = 1 + std::max(getHeight(n->left), getHeight(n->right));
const int balance = getBalance(n);
if (balance > 1 && key < n->left->key)
{
//std::cout << "L-L rotation on [" << n->key << "]" << std::endl; <--- uncomment for terminal output of rotations
return rotateRight(n);
}
if (balance < -1 && key > n->right->key)
{
//std::cout << "R-R rotation on [" << n->key << "]" << std::endl; <--- uncomment for terminal output of rotations
return rotateLeft(n);
}
if (balance > 1 && key > n->left->key)
{
//std::cout << "L-R rotation on [" << n->key << "]" << std::endl; <--- uncomment for terminal output of rotations
n->left = rotateLeft(n->left);
return rotateRight(n);
}
if (balance < -1 && key < n->right->key)
{
//std::cout << "R-L rotation on [" << n->key << "]" << std::endl; <--- uncomment for terminal output of rotations
n->right = rotateRight(n->right);
return rotateLeft(n);
}
return n;
}
void TAVL::preorder(const AVLNode* node)
{
if (!node)
return;
std::cout << "[" << node->key << "] ";
preorder(node->left);
preorder(node->right);
}
void TAVL::inorder(const AVLNode* node)
{
if (!node)
return;
inorder(node->left);
std::cout << "[" << node->key << "] ";
inorder(node->right);
}
void TAVL::postorder(const AVLNode *node)
{
if (!node)
return;
postorder(node->left);
postorder(node->right);
std::cout << "[" << node->key << "] ";
}
void TAVL::levelorder(const AVLNode* node)
{
if (!node)
return;
TTreeQueue<AVLNode> q;
q.Enqueue(const_cast<AVLNode*>(node));
while (!q.IsEmpty()) {
const AVLNode* cur = q.Dequeue();
std::cout << "[" << cur->key << "] ";
if (cur->left)
q.Enqueue(cur->left);
if (cur->right)
q.Enqueue(cur->right);
}
}
// Public functions
///<summary> Insert node </summary
///<param name="key"> Node key value (int) </param>
/// <returns> None </returns>
void TAVL::Insert(const int key)
{
root = insert(root, key);
}
///<summary> Inorder callback </summary
///<param name=""> AVLNode *node </param>
/// <returns> Bool </returns>
bool TAVL::Inorder(const AVLNode *node)
{
if (!node)
return true;
inorder(node);
std::cout << std::endl;
return true;
}
///<summary> Postorder callback </summary
///<param name=""> AVLNode *node </param>
/// <returns> Bool </returns>
bool TAVL::Postorder(const AVLNode *node)
{
if (!node)
return true;
postorder(node);
std::cout << std::endl;
return true;
}
///<summary> Preorder callback </summary
///<param name=""> AVLNode *node </param>
/// <returns> Bool </returns>
bool TAVL::Preorder(const AVLNode *node)
{
if (!node)
return true;
preorder(node);
std::cout << std::endl;
return true;
}
///<summary> LevelOrder callback </summary
///<param name=""> AVLNode *node </param>
/// <returns> Bool </returns>
bool TAVL::LevelOrder(const AVLNode *node)
{
if (!node)
return true;
levelorder(node);
std::cout << std::endl;
return true;
}
///<summary> Prints the desired sorting order </summary
///<param name="cb"> Callback for the desired ordering algorithm (e.g. PrintOrder(LevelOrder) will print the Level Order algorithm to the terminal)</param>
/// <returns>None</returns>
void TAVL::PrintOrder(FOrderTraversal cb)
{
if (!cb)
return;
cb(root);
}
// Helper to build an AVL tree with 'count' unique random keys
// in the range [minRange, maxRange]. Used only for demonstration in RunApp()
///<summary> Populates AVL tree </summary
///<param name="avl"> The AVL tree to be populated</param>
///<param name"count">How many elements to be populated into the tree</param>
///<param name"minRange">Lower bounds of key value range (e.g., the lower limit of element value)</param>
///<param name"maxRange">Upper bounds of key value range (e.g., higher limit og element value)</param>
/// <returns>None</returns>
void TAVL::Populate(TAVL* avl, const int count, const int minRange, const int maxRange)
{
std::unordered_set<int> AVLset;
for (int i = 0; i < count; i++) {
int val = Utils::RandomInt(minRange, maxRange);
while (AVLset.count(val))
val = Utils::RandomInt(minRange, maxRange);
//std::cout << "Inserting [" << val << "]" << std::endl; <----- Uncomment for terminal output of insertions
avl->Insert(val);
AVLset.insert(val);
}
}

View File

@@ -0,0 +1,51 @@
#ifndef IKT203_COURSE_ASSIGNMENTS_TAVL_H
#define IKT203_COURSE_ASSIGNMENTS_TAVL_H
// Node used in the AVL tree.
// Stores only an integer key and height (no TEmployee data).
struct AVLNode {
int key;
AVLNode* left;
AVLNode* right;
int height;
explicit AVLNode(const int k) : key(k), left(nullptr), right(nullptr), height(1) {}
};
typedef bool (*FOrderTraversal)(const AVLNode* AVLNode);
// Self-balancing AVL tree used to demonstrate rotations and traversals.
// Only stores integer keys; no payload data is required for this assignment.
class TAVL {
private:
AVLNode* root;
static int getHeight(const AVLNode* node);
static int getBalance(const AVLNode *node);
static AVLNode* rotateRight(AVLNode* y);
static AVLNode* rotateLeft(AVLNode* x);
static AVLNode* insert(AVLNode* n, int key);
static void inorder(const AVLNode* node);
static void preorder(const AVLNode* node);
static void postorder(const AVLNode* node);
static void levelorder(const AVLNode* node);
public:
TAVL() : root(nullptr) {};
~TAVL() = default;
void Insert(int key);
static bool Inorder(const AVLNode* node);
static bool Postorder(const AVLNode *node);
static bool Preorder(const AVLNode *node);
static bool LevelOrder(const AVLNode *node);
void PrintOrder(FOrderTraversal);
static void Populate(TAVL* AVLtree, int count, int minRange, int maxRange);
};
#endif //IKT203_COURSE_ASSIGNMENTS_TAVL_H

View File

@@ -0,0 +1,202 @@
#include "TBST.h"
#include <iostream>
#include "TTreeQueue.h"
void TBST::destroy(BSTNode *node)
{
if (!node)
return;
destroy(node->left);
destroy(node->right);
// TBST owns the TEmployee* stored in each node, so delete it here.
delete node->data;
delete node;
}
///<summary> Insert node </summary
///<param name="key"> Node key value (int) </param>
///<param name="data"> Employee data (TEmployee) </param>
/// <returns> None </returns>
void TBST::Insert(const int key, TEmployee *data)
{
root = insert(root, key, data);
}
BSTNode* TBST::insert(BSTNode* node, const int key, TEmployee *data)
{
if (node == nullptr) {
auto* n = new BSTNode{key, data, nullptr, nullptr};
return n;
}
if (key < node->key)
node->left = insert(node->left, key, data);
else if (key > node->key)
node->right = insert(node->right, key, data);
else {
// Duplicate key: do not modify the existing node.
// 'data' was allocated by the caller, so we must delete it here
// to avoid a memory leak.
std::cout << "Duplicate key [" << key << "], ignoring insert." << std::endl;
delete data;
}
return node;
}
///<summary> Search for node </summary
///<param name="key"> Node key value (int) </param>
/// <returns> TEmployee </returns>
TEmployee *TBST::Search(int key) const
{
const BSTNode* result = search(root, key);
return result ? result->data : nullptr;
}
BSTNode* TBST::search(BSTNode* node, const int key)
{
if (node == nullptr)
return nullptr;
if (key == node->key)
return node;
if (key < node->key)
return search(node->left, key);
else
return search(node->right, key);
}
///<summary> Delete node </summary
///<param name="key"> Node key value (int) </param>
/// <returns> None </returns>
void TBST::Delete(const int key)
{
root = remove(root, key);
}
BSTNode *TBST::remove(BSTNode *node, const int key)
{
if (node == nullptr)
return nullptr;
if (key < node->key)
node->left = remove(node->left, key);
else if (key > node->key)
node->right = remove(node->right, key);
else {
// No children
if (node->left == nullptr && node->right == nullptr) {
delete node->data;
delete node;
return nullptr;
}
// Right child only
if (node->left == nullptr) {
BSTNode* child = node->right;
delete node->data;
delete node;
return child;
}
// Left child only
if (node->right == nullptr) {
BSTNode* child = node->left;
delete node->data;
delete node;
return child;
}
// Two children:
// 1) Find the smallest node in the right subtree (inorder successor)
// 2) Copy its key + data into the current node
// 3) Remove the successor node from the right subtree
else {
BSTNode* minRight = findMin(node->right);
node->key = minRight->key;
node->data = minRight->data;
node->right = remove(minRight->right, minRight->key);
}
}
return node;
}
BSTNode* TBST::findMin(BSTNode* node)
{
while (node && node->left)
node = node->left;
return node;
}
// Traversals
// Private helpers
void TBST::preorder(const BSTNode* node)
{
if (!node)
return;
std::cout << "[" << node->key << "] ";
preorder(node->left);
preorder(node->right);
}
void TBST::inorder(const BSTNode* node)
{
if (!node)
return;
inorder(node->left);
std::cout << "[" << node->key << "] ";
inorder(node->right);
}
void TBST::postorder(const BSTNode *node)
{
if (!node)
return;
postorder(node->left);
postorder(node->right);
std::cout << "[" << node->key << "] ";
}
void TBST::levelorder(const BSTNode* node)
{
if (!node)
return;
TTreeQueue<BSTNode> q;
q.Enqueue(const_cast<BSTNode*>(node));
while (!q.IsEmpty()) {
const BSTNode* cur = q.Dequeue();
std::cout << "[" << cur->key << "] ";
if (cur->left)
q.Enqueue(cur->left);
if (cur->right)
q.Enqueue(cur->right);
}
}
///<summary> Inorder sorting </summary
/// <returns> None </returns>
void TBST::Inorder() const
{
inorder(root);
std::cout << std::endl;
}
///<summary> Preorder sorting </summary
/// <returns> None </returns>
void TBST::Preorder() const
{
preorder(root);
std::cout << std::endl;
}
///<summary> Postorder sorting </summary
/// <returns> None </returns>
void TBST::Postorder() const
{
postorder(root);
std::cout << std::endl;
}
///<summary> LevelOrder sorting </summary
/// <returns> None </returns>
void TBST::LevelOrder() const
{
levelorder(root);
std::cout << std::endl;
}

View File

@@ -0,0 +1,44 @@
#ifndef IKT203_COURSE_ASSIGNMENTS_TBST_H
#define IKT203_COURSE_ASSIGNMENTS_TBST_H
#include "TEmployee.h"
// Node in the Binary Search Tree.
// Owns a single TEmployee* which is deleted by TBST::destroy/remove.
struct BSTNode {
int key; // employee ID
TEmployee* data; // employee record
BSTNode* left;
BSTNode* right;
};
// Standard Binary Search Tree for TEmployee* keyed by employee ID.
// Responsibilities:
// - Owns all TEmployee objects it contains.
// - Provides insert, search, delete, and four traversal methods.
class TBST {
private:
BSTNode* root;
static BSTNode* insert(BSTNode* node, int key, TEmployee* data);
static BSTNode* search(BSTNode* node, int key);
static BSTNode* remove(BSTNode* node, int key);
static void inorder(const BSTNode* node);
static void preorder(const BSTNode* node);
static void postorder(const BSTNode* node);
static void levelorder(const BSTNode* node);
static void destroy(BSTNode* node);
static BSTNode* findMin(BSTNode* node);
public:
TBST() = default;
~TBST() {destroy(root);}
void Insert(int key, TEmployee* data);
[[nodiscard]] TEmployee* Search(int key) const;
void Delete(int key);
void Inorder() const;
void Preorder() const;
void Postorder() const;
void LevelOrder() const;
};
#endif //IKT203_COURSE_ASSIGNMENTS_TBST_H

View File

@@ -0,0 +1,27 @@
#ifndef IKT203_COURSE_ASSIGNMENTS_TEMPLOYEE_H
#define IKT203_COURSE_ASSIGNMENTS_TEMPLOYEE_H
#include <string>
#include <utility>
// Simple employee record used in Category 3.
// 'id' is set later by IdGenerator and used as the BST key.
struct TEmployee {
std::string firstName;
std::string lastName;
int id;
TEmployee(std::string f, std::string l) : firstName(std::move(f)), lastName(std::move(l)) {};
~TEmployee() = default;
};
#endif //IKT203_COURSE_ASSIGNMENTS_TEMPLOYEE_H

View File

@@ -0,0 +1,57 @@
#ifndef TQUEUE_H
#define TQUEUE_H
#define MAX_SIZE 200
#include <stdexcept>
#include "TBST.h"
// Fixed-size circular queue used by the BST and AVL level-order traversals.
// Stores raw pointers to tree nodes (T*). Does not own the nodes.
template <typename T>
struct TTreeQueue {
T* queue[MAX_SIZE];
int head = 0;
int tail = 0;
int count = 0;
TTreeQueue() = default;
~TTreeQueue() = default;
void Enqueue(T* n)
{
if (n == nullptr)
return; // ignore null pointers, nothing to enqueue
if (IsFull())
throw std::overflow_error("Queue Overflow");
queue[tail] = n;
tail = (tail + 1) % MAX_SIZE;
count++;
}
T* Dequeue()
{
if (IsEmpty())
throw std::underflow_error("Empty Queue");
T* n = queue[head];
if (n == nullptr)
return nullptr;
head = (head + 1) % MAX_SIZE;
count--;
return n;
}
bool IsEmpty() const
{
return count == 0;
}
bool IsFull() const
{
return count == MAX_SIZE;
}
};
#endif //TQUEUE_H

View File

@@ -0,0 +1,54 @@
/*
Dear Student,
Remember to follow the coding standards and best practices discussed
in the portfolio assignment document.
Good luck with your portfolio!
NB: Do not delete the code below that prints the assignment and option info!
---------------------------------------------------------------------
*** HOW TO SWITCH BETWEEN OPTION 1 AND OPTION 2 ***
---------------------------------------------------------------------
You CANNOT switch options by changing this file.
1. Go to the 'CMakeLists.txt' file for this assignment.
2. Find the line:
option(BUILD_ASSIGNMENT_OPTION_1 "..." ON)
3. Change 'ON' (for Option 1) to 'OFF' (for Option 2).
*** VERY IMPORTANT: After changing the option ***
Your project will NOT update until you re-run the CMake configuration.
To force an update (e.g., in Visual Studio):
- Right-click the 'CMakeLists.txt' file and select 'Configure Cache'.
- OR, simply delete the 'out' / 'build' folder and rebuild the project.
---------------------------------------------------------------------
*/
#include <iostream>
#include <string_view>
static constexpr std::string_view AssignmentName = "Category 3: Trees (BST, AVL & RBT)";
#if ASSIGNMENT_03_OPTION == 1
#include "option1.h"
static constexpr std::string_view AssignmentOption = "Option 1 (Standard): Employee Directory (BST vs. AVL).";
#elif ASSIGNMENT_03_OPTION == 2
#include "option2.h"
static constexpr std::string_view AssignmentOption = "Option 2 (Advanced): Interpreted Calculator.";
#endif
int main(int argc, char* argv[])
{
int appStatus = 0;
std::cout << AssignmentName << std::endl;
std::cout << AssignmentOption << std::endl;
// Create only core or common code in main.cpp
// Use the option header files to implement the specific assignment option logic
appStatus = RunApp();
return appStatus;
}

View File

@@ -0,0 +1,97 @@
#include "option1.h"
#include <limits>
#include "SharedLib.h"
// Entry point for Category 3, Option 1.
// Demonstrates:
// 1) Building a BST of 200 employees from DATA/random_names.txt
// 2) Running all BST traversals
// 3) Searching and deleting by employee ID
// 4) Building and printing an AVL tree with random integer keys
int RunApp() {
//Reading names from file for BST population
bst = new TBST();
// Read 200 employees from the names file and populate the BST.
// IMPORTANT: Working directory must be the Portfolio/Assignment-03 folder
// so that "DATA/random_names.txt" resolves correctly.
const std::string filename = "DATA/random_names.txt";
readNamesFromFile(filename, onNameRead);
// --- BST traversals ---
// These calls demonstrate all four traversal orders on the employee BST.
// Comment out this block if the console output becomes too noisy.
pack("Inorder traversal (sorted by ID)");
bst->Inorder();
pack("Level order traversal");
bst->LevelOrder();
pack("Preorder traversal");
bst->Preorder();
pack("Postorder traversal");
bst->Postorder();
// --- BST search demo ---
// Ask the user for an ID, search in the BST, and print the matching employee (if any).
pack("Search function");
std::cout << "\nInput the ID you want to search for\n" << std::endl;
int choice;
std::cin >> choice;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
if (const TEmployee* result = bst->Search(choice))
std::cout << "Match found:\n" << result->firstName << " " << result->lastName << std::endl;
else
std::cout << "ID not found" << std::endl;
// --- BST delete demo ---
// Ask the user for an ID, delete it from the BST if it exists,
// then print the new inorder traversal to show the updated structure.
pack("Remove function");
std::cout << "\nInput the ID you want to remove\n" << std::endl;
std::cin >> choice;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
if (const TEmployee* res = bst->Search(choice)) {
bst->Delete(choice);
std::cout << "ID removed\n" << std::endl;
}
else
std::cout << "ID not found\n" << std::endl;
bst->Inorder();
// End of BST block
// --- AVL demo ---
// Build an AVL tree using random integers in [1, 200].
// This tree only stores keys (no TEmployee data) and is used
// to demonstrate balancing and traversals.
pack("AVL");
avl = new TAVL;
TAVL::Populate(avl, 100, 1, 200);
pack("Inorder");
avl->PrintOrder(TAVL::Inorder);
pack("Postorder");
avl->PrintOrder(TAVL::Postorder);
pack("Preorder");
avl->PrintOrder(TAVL::Preorder);
pack("Levelorder");
avl->PrintOrder(TAVL::LevelOrder);
// End of AVL block
// --- Cleanup ---
// TBST destructor deletes all TEmployee objects it owns.
// Here we delete the tree objects themselves to avoid leaks.
pack ("Cleaning up");
delete bst;
delete avl;
return 0;
}

View File

@@ -0,0 +1,68 @@
#pragma once
#ifndef OPTION1_H
#define OPTION1_H
#include <iostream>
#include <unordered_set>
#include "TAVL.h"
#include "TBST.h"
#include "TEmployee.h"
#include "Utils.h"
// Global state for Category 3, Option 1:
// - bst: owns all TEmployee objects (deleted in TBST destructor)
// - avl: separate AVL tree used only to demonstrate balancing on int keys
inline std::unordered_set<int> usedIds;
static TBST* bst;
static TAVL* avl;
int RunApp();
// Assign a unique random employee ID in the range [1, 1000].
// Uses 'usedIds' to avoid duplicates so the BST always has unique keys.
inline void IdGenerator(TEmployee* employee)
{
int id = Utils::RandomInt(1, 1000);
while (usedIds.count(id) > 0)
id = Utils::RandomInt(1, 1000);
usedIds.insert(id);
employee->id = id;
}
// Callback used by readNamesFromFile.
// - Creates a new TEmployee from the given name.
// - Stops after 200 employees (as required by the assignment).
// - Generates a unique ID and inserts the employee into the BST.
static bool onNameRead(const int index, const int aTotalCount, const std::string& aFirstName, const std::string& aLastName)
{
const auto e = new TEmployee(aFirstName, aLastName);
if (index >= 200)
return false;
IdGenerator(e);
bst->Insert(e->id, e);
std::cout << "[" << e->id << "] " << e->firstName << ", " << e->lastName << std::endl;
return true;
}
inline void printline()
{
std::cout << "----------------------------------------" << std::endl;
}
// Helper to visually separate different demos (traversals, search, etc.) in the console output.
inline void pack(const std::string& line)
{
std::cout << "\n\n\n" << std::endl;
printline();
std::cout << line << std::endl;
printline();
}
#endif // OPTION1_H

View File

@@ -0,0 +1,6 @@
#include "option2.h"
int RunApp() {
// Implement the Console Music Player application logic here
return 0;
}

View File

@@ -0,0 +1,9 @@
#pragma once
#ifndef OPTION2_H
#define OPTION2_H
int RunApp();
#endif // OPTION2_H

View File

@@ -0,0 +1,38 @@
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# "ON" = build Option 1, "OFF" = build Option 2.
option(BUILD_ASSIGNMENT_04_OPTION_1 "Build Assignment Option 1 (Standard)" ON)
add_executable(Assignment-04
main.cpp
option1.cpp
)
if(BUILD_ASSIGNMENT_04_OPTION_1)
target_sources(Assignment-04
PRIVATE
option1.cpp
option1.h
)
target_compile_definitions(Assignment-04 PRIVATE "ASSIGNMENT_04_OPTION=1")
else()
target_sources(Assignment-04
PRIVATE
option2.cpp
option2.h
)
target_compile_definitions(Assignment-04 PRIVATE "ASSIGNMENT_04_OPTION=2")
endif()
target_link_libraries(Assignment-04
PRIVATE
SharedLib
)
add_custom_command(TARGET Assignment-04 POST_BUILD
# Add a custom command here if needed
COMMAND ${CMAKE_COMMAND} -E echo "Assignment-04 post-build step"
)

View File

@@ -0,0 +1,55 @@
/*
Dear Student,
Remember to follow the coding standards and best practices discussed
in the portfolio assignment document.
Good luck with your portfolio!
NB: Do not delete the code below that prints the assignment and option info!
---------------------------------------------------------------------
*** HOW TO SWITCH BETWEEN OPTION 1 AND OPTION 2 ***
---------------------------------------------------------------------
You CANNOT switch options by changing this file.
1. Go to the 'CMakeLists.txt' file for this assignment.
2. Find the line:
option(BUILD_ASSIGNMENT_OPTION_1 "..." ON)
3. Change 'ON' (for Option 1) to 'OFF' (for Option 2).
*** VERY IMPORTANT: After changing the option ***
Your project will NOT update until you re-run the CMake configuration.
To force an update (e.g., in Visual Studio):
- Right-click the 'CMakeLists.txt' file and select 'Configure Cache'.
- OR, simply delete the 'out' / 'build' folder and rebuild the project.
---------------------------------------------------------------------
*/
#include <iostream>
#include <string_view>
#include "option1.h"
static constexpr std::string_view AssignmentName = "Category 4: Graphs & Dijkstra's Algorithm";
static constexpr std::string_view AssignmentOption = "Option 1 (Standard): Data Center Network Monitor.";
/*
#if ASSIGNMENT_04_OPTION == 1
#include "option1.h"
#elif ASSIGNMENT_04_OPTION == 2
static constexpr std::string_view AssignmentOption = "Option 2 (Advanced): Inter-city Logistics Router.";
#include "option2.h"
#endif
*/
int main(int argc, char* argv[])
{
int appStatus = 0;
std::cout << AssignmentName << std::endl;
std::cout << AssignmentOption << std::endl;
// Create only core or common code in main.cpp
// Use the option header files to implement the specific assignment option logic
appStatus = RunApp();
return appStatus;
}

View File

@@ -0,0 +1,274 @@
#include "option1.h"
#include <iostream>
#include "SharedLib.h"
#include <algorithm>
constexpr float INF = 1e9f;
// Global graph storage and lookup table used by the ReadGraph callbacks.
// These are populated automatically when readGraphFromFile(...) is executed.
Graph g;
std::unordered_map<std::string, int> nameToIndex;
std::string filename = "DATA/network_graph.txt"; // Remember to set working directory for this path to work
//////////////////////////////// Callbacks ////////////////////////////////
// Callback: called once for each node read from network_graph.txt.
// Responsible for registering the vertex in the graph and recording its index.
bool onNodeRead(const int aIndex, const int aTotalCount, const std::string& aNode)
{
const int idx = g.AddVertex(aNode);
nameToIndex[aNode] = idx;
return true;
}
// Callback: called for each edge (connection) read from the file.
// Translates node names into vertex indices and adds an undirected weighted edge.
bool onEdgeRead(const int aIndex, const int aTotalCount, const std::string& aFromNode, const std::string& aToNode, const float aWeight)
{
const int fromIdx = nameToIndex[aFromNode];
const int toIdx = nameToIndex[aToNode];
g.AddUndirectedEdge(fromIdx, toIdx, aWeight);
return true;
}
//////////////////////////////// Dijkstra algorithm ////////////////////////////////
// Computes the shortest path between src and dst using Dijkstra's algorithm.
// Uses the custom MinHeap class as the priority queue.
// Output: 'outPath' contains vertex indices from src -> dst in order.
// Returns INF if no path exists.
float Dijkstra(const Graph& graph, int src, int dst, std::vector<int>& outPath)
{
const int n = graph.GetVertexCount();
std::vector<float> dist(n, INF);
std::vector<int> prev(n, -1);
std::vector<bool> visited(n, false);
dist[src] = 0.0f;
MinHeap heap;
heap.Push(src, 0.0f);
// Pop the next closest unvisited vertex. Old entries with outdated distances
// are skipped via the 'visited' array (lazy deletion).
while (!heap.isEmpty()) {
HeapNode* node = heap.Pop();
const int u = node->vertex;
// float d = node->distance; not needed
delete node;
if (visited[u])
continue;
visited[u] = true;
if (u == dst)
break;
for (const TEdge* e : graph.GetEdges(u)) {
int v = e->toIndex;
float w = e->weight;
if (visited[v])
continue;
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
prev[v] = u;
heap.Push(v, dist[v]);
}
}
}
outPath.clear();
if (dist[dst] == INF)
return INF;
for (int v = dst; v != -1; v = prev[v])
outPath.push_back(v);
std::reverse(outPath.begin(), outPath.end());
return dist[dst];
}
//////////////////////////////// Class logic ////////////////////////////////
// Graph
// Clean up all dynamically allocated vertices and edges.
// Graph owns all TVertex* and TEdge*.
Graph::~Graph()
{
for (TVertex* v : vertices) {
for (TEdge* e : v->edges) {
delete e;
}
delete v;
}
}
int Graph::AddVertex(const std::string& name)
{
auto* v = new TVertex;
v->name = name;
vertices.push_back(v);
return static_cast<int>(vertices.size()) - 1;
}
void Graph::AddUndirectedEdge(int fromIndex, int toIndex, float weight)
{
TEdge* e1 = new TEdge;
e1->toIndex = toIndex;
e1->weight = weight;
TEdge* e2 = new TEdge;
e2->toIndex = fromIndex;
e2->weight = weight;
vertices[fromIndex]->edges.push_back(e1);
vertices[toIndex]->edges.push_back(e2);
}
int Graph::GetVertexCount() const
{
return static_cast<int>(vertices.size());
}
const TVertex *Graph::GetVertex(int index) const
{
return vertices[index];
}
const std::vector<TEdge*>& Graph::GetEdges(const int index) const
{
return vertices[index]->edges;
}
// Heap
// Min-heap storing (vertex, distance) pairs.
// Used by Dijkstra as a priority queue.
MinHeap::~MinHeap()
{
for (HeapNode* n : data)
delete n;
}
bool MinHeap::isEmpty() const
{
return data.empty();
}
void MinHeap::Push(int vertex, float dist)
{
auto* n = new HeapNode{vertex, dist};
data.push_back(n);
HeapUp(static_cast<int>(data.size()) - 1);
}
HeapNode* MinHeap::Pop()
{
if (data.empty())
return nullptr;
HeapNode* root = data[0];
data[0] = data.back();
data.pop_back();
if (!data.empty())
HeapDown(0);
return root;
}
void MinHeap::HeapUp(int idx)
{
while (idx > 0) {
int parent = (idx - 1) / 2;
if (data[parent]->distance <= data[idx]->distance)
break;
std::swap(data[idx], data[parent]);
idx = parent;
}
}
void MinHeap::HeapDown(int idx)
{
int n = static_cast<int>(data.size());
while (true) {
int left = 2 * idx + 1;
int right = 2 * idx + 2;
int smallest = idx;
if (left < n && data[left]->distance < data[smallest]->distance)
smallest = left;
if (right < n && data[right]->distance < data[smallest]->distance)
smallest = right;
if (smallest == idx)
break;
std::swap(data[idx], data[smallest]);
idx = smallest;
}
}
// Entry point for Assignment 04 Option 1.
// Loads the network graph from file, prints node list,
// prompts the user for source and destination,
// and runs Dijkstra to find the lowest-latency path.
int RunApp() {
readGraphFromFile(filename, onNodeRead, onEdgeRead);
// Debug: Print all nodes and vertices
/*
pack("Graph");
for (int i = 0; i < g.GetVertexCount(); i++) {
const TVertex* v = g.GetVertex(i);
std::cout << i << ": " << v->name << std::endl;
for (const TEdge* e : g.GetEdges(i)) {
std::cout << " -> " << e->toIndex << " (weight = " << e->weight << ")" << std::endl;
}
}
*/
/* Debug heap test
pack("Heap");
MinHeap test;
test.Push(1, 5.0f);
test.Push(2, 3.0f);
test.Push(3, 10.0f);
while (!test.isEmpty())
{
const HeapNode* n = test.Pop();
std::cout << "(" << n->vertex << ", " << n->distance << ")" << "\n";
delete n;
}
printline();
*/
std::cout << "\nGraph:" << std::endl;
for (int i = 0; i < g.GetVertexCount(); ++i)
std::cout << i << ": " << g.GetVertex(i)->name << std::endl;
int src, dst;
std::cout << "\nEnter source index: ";
std::cin >> src;
std::cout << "\nEnter destination index: ";
std::cin >> dst;
if (src < 0 || src >= g.GetVertexCount() ||
dst < 0 || dst >= g.GetVertexCount()) {
std::cout << "Invalid indices.\n";
return 0;
}
std::vector<int> path;
float total = Dijkstra(g, src, dst, path);
if (total >= INF) {
std::cout << "\nNo path between those nodes.\n";
} else {
std::cout << "\nLowest latency path: ";
for (size_t i = 0; i < path.size(); ++i) {
std::cout << g.GetVertex(path[i])->name;
if (i + 1 < path.size()) std::cout << " -> ";
}
std::cout << " \n(Total: " << total << " ms)\n";
}
printline();
return 0;
}

View File

@@ -0,0 +1,84 @@
#pragma once
#ifndef OPTION1_H
#define OPTION1_H
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
// Structs and classes
struct TEdge {
int toIndex;
float weight;
};
struct TVertex {
std::string name;
std::vector<TEdge*> edges;
};
struct HeapNode {
int vertex;
float distance;
};
class MinHeap {
private:
std::vector<HeapNode*> data;
void HeapUp(int idx);
void HeapDown(int idx);
public:
~MinHeap();
[[nodiscard]] bool isEmpty() const;
void Push(int vertex, float dist);
HeapNode* Pop();
};
class Graph {
private:
std::vector<TVertex*> vertices; // Owns all TVertex*
public:
~Graph();
int AddVertex(const std::string& name);
void AddUndirectedEdge(int fromIndex, int toIndex, float weight);
[[nodiscard]] int GetVertexCount() const;
[[nodiscard]] const TVertex* GetVertex(int index) const;
[[nodiscard]] const std::vector<TEdge*>& GetEdges(int index) const;
};
// Callbacks & funcs
bool onNodeRead(int aIndex, int aTotalCount, const std::string& aNode);
bool onEdgeRead(int aIndex, int aTotalCount, const std::string& aFromNode, const std::string& aToNode, float aWeight);
float Dijkstra(const Graph& graph, int src, int dst, std::vector<int>& outPath);
// Terminal tidying
inline void printline()
{
std::cout << "----------------------------------------" << std::endl;
}
inline void pack(const std::string& line)
{
std::cout << "\n\n\n" << std::endl;
printline();
std::cout << line << std::endl;
printline();
}
int RunApp();
#endif // OPTION1_H

View File

@@ -0,0 +1,47 @@
#include <iostream>
#include "option1.h"
#include "SharedLib.h"
static constexpr std::string_view AssignmentOption = "Option 1 (Standard): Data Center Network Monitor.";
/**
* @brief Callback function to process one node.
*/
static bool NodeReadCallback(const int aIndex, const int aTotalCount, const std::string& aNode)
{
std::cout << "Loading Node " << (aIndex + 1) << " of " << aTotalCount << ": " << aNode << "\n";
// Return true to continue reading
return true;
}
/**
* @brief Callback function to process one edge.
*/
static bool EdgeReadCallback(const int aIndex, const int aTotalCount, const std::string& aFromNode, const std::string& aToNode, float aWeight)
{
std::cout << " Loading Edge " << (aIndex + 1) << " of " << aTotalCount << ": "
<< aFromNode << " -> " << aToNode << " (Weight: " << aWeight << ")\n";
// Return true to continue reading
return true;
}
/*
int RunApp()
{
std::cout << AssignmentOption << std::endl;
// Path to the graph data file
std::string filename = "F:\\IKT203\\VisualStudio\\DATA\\city_graph.txt";
std::cout << "Reading graph from file: " << filename << "\n\n";
// Call the utility function with both callbacks
readGraphFromFile(filename, NodeReadCallback, EdgeReadCallback);
std::cout << "\nFinished reading graph." << std::endl;
return 0;
}*/

View File

@@ -0,0 +1,9 @@
#pragma once
#ifndef OPTION2_H
#define OPTION2_H
/*
int RunApp();
*/
#endif // OPTION2_H

View File

@@ -0,0 +1,20 @@
# CMakeList.txt : Top-level CMake project file, do global configuration
# and include sub-projects here.
#
cmake_minimum_required (VERSION 3.20)
project ("Portfolio")
# Enable Hot Reload for MSVC compilers if supported.
if (POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()
# Include sub-projects.
add_subdirectory(SharedLib)
# Tell CMake to find and include the first exercise project.
add_subdirectory(Assignment-01)
add_subdirectory(Assignment-02)
add_subdirectory(Assignment-03)
add_subdirectory(Assignment-04)

View File

@@ -0,0 +1,44 @@
# --- Step 1: Create the Library ---
# Define a library target named "SharedLib".
# We use STATIC because we are using cpp and header files.
add_library(SharedLib STATIC
TPerson.cpp
TPerson.h
TLinkedList.cpp
TLinkedList.h
../Assignment-03/TEmployee.h)
# --- Step 2: Add Header Files to the Library ---
# This command explicitly lists the header files that belong to the library.
# This helps Visual Studio display them nicely in the Solution Explorer.
target_sources(SharedLib
PUBLIC
# You can add more functionalty to SharedLib.h just by adding more definitions in SharedLib.h.
SharedLib.h
TDoublyLinkedList.h
TStack.h
TTreeQueue.h
Utils.h
# Or add other shared files here
PRIVATE
ReadNames.cpp
ReadGraph.cpp
ReadSongs.cpp
FileReaderUtils.cpp
TDoublyLinkedList.cpp
TStack.cpp
TTreeQueue.cpp
Utils.cpp
)
# --- Step 3: Make Headers "Findable" ---
# This is the most important command here.
# It tells any other project that links to "SharedLib" to add this
# directory (CMAKE_CURRENT_SOURCE_DIR) to its list of include paths.
# This is what allows you to write #include "list.hpp" in your main.cpp.
# Note: CMAKE_CURRENT_SOURCE_DIR is a built-in variable that points to the directory
target_include_directories(SharedLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,30 @@
#include "FileReaderUtils.h"
int GetRecordCount(const std::string& aHeaderLine)
{
size_t recordPos = aHeaderLine.find("records:=");
if (recordPos == std::string::npos)
{
return 0; // No record count found
}
size_t countStart = recordPos + 9; // Length of "records:="
// Find the end bracket ']' or a potential semicolon ';'
size_t countEnd = aHeaderLine.find_first_of("];", countStart);
if (countEnd == std::string::npos)
{
return 0; // Malformed header
}
std::string countStr = aHeaderLine.substr(countStart, countEnd - countStart);
try
{
// stoi = string to integer
return std::stoi(countStr);
}
catch (const std::exception&)
{
return 0; // Malformed number
}
}

View File

@@ -0,0 +1,13 @@
// FileReaderUtils.h
#pragma once
#if !defined(FILEREADERUTILS_H)
#define FILEREADERUTILS_H
#include <string>
/**
* @brief [Internal] Safely parses the "records:=N" part of a header line.
* @param aHeaderLine The line, e.g., "[NODES;records:=11]"
* @return The number of records, or 0 if not found.
*/
int GetRecordCount(const std::string& aHeaderLine);
#endif // FILEREADERUTILS_H

View File

@@ -0,0 +1,109 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include "SharedLib.h"
#include "FileReaderUtils.h"
// --- Enum for the parser's state ---
enum class EParseState
{
NONE,
NODES,
EDGES
};
void readGraphFromFile(const std::string& aFilename, FNodeRead aOnNodeRead, FEdgeRead aOnEdgeRead)
{
if (aFilename.empty()) return;
std::ifstream file(aFilename);
if (!file.is_open())
{
// Optional: print an error
// std::cerr << "Error: Could not open file " << aFilename << std::endl;
return;
}
std::string line;
EParseState currentState = EParseState::NONE;
int totalCount = 0;
int currentIndex = 0;
bool keepReading = true;
while (keepReading && std::getline(file, line))
{
if (line.empty()) continue;
if (line[0] == '[')
{
// --- 2. USE THE SHARED FUNCTION ---
totalCount = GetRecordCount(line);
currentIndex = 0;
if (line.find("[NODES") != std::string::npos)
{
currentState = EParseState::NODES;
continue;
}
else if (line.find("[EDGES") != std::string::npos)
{
currentState = EParseState::EDGES;
continue;
}
// If it's a comment or other header, reset state and count
currentState = EParseState::NONE;
totalCount = 0;
continue;
}
// Process data based on the current state
switch (currentState)
{
case EParseState::NODES:
if (aOnNodeRead)
{
if (!aOnNodeRead(currentIndex, totalCount, line))
{
keepReading = false;
}
currentIndex++;
}
break;
case EParseState::EDGES:
{
std::istringstream edgeStream(line);
std::string fromNode, toNode, weightStr;
if (std::getline(edgeStream, fromNode, ';') &&
std::getline(edgeStream, toNode, ';') &&
std::getline(edgeStream, weightStr))
{
try
{
// Use std::stof (string to float) for weight
float weight = std::stof(weightStr);
if (aOnEdgeRead)
{
if (!aOnEdgeRead(currentIndex, totalCount, fromNode, toNode, weight))
{
keepReading = false;
}
currentIndex++;
}
}
catch (const std::exception&)
{
// Failed to parse float, skip this line
}
}
break;
}
case EParseState::NONE:
default:
break;
}
}
file.close();
}

View File

@@ -0,0 +1,55 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include "SharedLib.h"
#include "FileReaderUtils.h"
void readNamesFromFile(const std::string& aFilename, FNameRead aOnNameRead)
{
if (aFilename.empty()) return;
std::ifstream file(aFilename);
if (!file.is_open())
{
std::cerr << "Error: Could not open file " << aFilename << std::endl;
return;
}
std::string line;
int totalCount = 0;
int currentIndex = 0;
bool keepReading = true;
// --- 1. Read the header line ---
if (std::getline(file, line))
{
// Use our shared helper to get the count
totalCount = GetRecordCount(line);
}
// --- 2. Loop through the rest of the file ---
while (keepReading && std::getline(file, line))
{
if (line.empty()) continue;
std::istringstream nameStream(line);
std::string firstName, lastName;
// Parse "FirstName LastName"
if (nameStream >> firstName >> lastName)
{
if (aOnNameRead)
{
// Call the callback with all parameters
if (!aOnNameRead(currentIndex, totalCount, firstName, lastName))
{
keepReading = false;
}
currentIndex++;
}
}
}
file.close();
}

View File

@@ -0,0 +1,60 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include "SharedLib.h"
#include "FileReaderUtils.h" // Include the shared utility
void ReadSongsFromFile(const std::string& aFilename, FSongRead aOnSongRead)
{
if (aFilename.empty()) return;
std::ifstream file(aFilename);
if (!file.is_open())
{
// std::cerr << "Error: Could not open file " << aFilename << std::endl;
return;
}
std::string line;
int totalCount = 0;
int currentIndex = 0;
bool keepReading = true;
// --- 1. Read the header line ---
if (std::getline(file, line))
{
// Use our shared helper to get the count
totalCount = GetRecordCount(line);
}
// --- 2. Loop through the rest of the file (the data lines) ---
while (keepReading && std::getline(file, line))
{
if (line.empty()) continue; // Skip empty lines
std::istringstream lineStream(line);
std::string artist, title, year, genre, source;
// Parse the five semicolon-separated fields
// Artist;Title;Year;Genre;Source
if (std::getline(lineStream, artist, ';') &&
std::getline(lineStream, title, ';') &&
std::getline(lineStream, year, ';') &&
std::getline(lineStream, genre, ';') &&
std::getline(lineStream, source)) // Last one reads to end of line
{
if (aOnSongRead)
{
// Call the callback with all parameters
if (!aOnSongRead(currentIndex, totalCount, artist, title, year, genre, source))
{
keepReading = false;
}
currentIndex++;
}
}
}
file.close();
}

View File

@@ -0,0 +1,101 @@
#pragma once
#ifndef SHARED_LIB_H
#define SHARED_LIB_H
#include <string>
#include <functional>
#include "../Assignment-03/TAVL.h"
/// <summary>
/// Delegate type for processing a name read from a file.
/// </summary>
/// <param name="aIndex">The index of the name (0-based).</param>
/// <param name="aTotalCount">The total number of names.</param>
/// <param name="aFirstName">The first name read from the file.</param>
/// <param name="aLastName">The last name read from the file.</param>
/// <returns>Returns true to continue reading, false to stop.</returns>
typedef bool (*FNameRead)(
const int aIndex,
const int aTotalCount,
const std::string& aFirstName,
const std::string& aLastName
);
/// <summary>
/// Use this function to read names from a specified file and process them using a callback function.
/// </summary>
/// <function>readNamesFromFile</function>
/// <description>Reads names from a specified file and invokes a callback for each name read.</description>
/// <param name="aFilename">The path to the file containing names.</param>
/// <param name="aOnNameRead">A callback function that is called for each name read. It takes two parameters: firstName and lastName. If the callback returns false, the reading process stops.</param>
/// <param name="firstName">The first name read from the file.</param>
/// <param name="lastName">The last name read from the file.</param>
/// <returns>None.</returns>
void readNamesFromFile(const std::string& aFilename, FNameRead aOnNameRead);
/// <summary>
/// Delegate type for processing a node read from the file.
/// </summary>
/// <description>Function pointer type for a callback that processes nodes read from a file.</description>
/// <param name="aIndex">The index of the node (0-based).</param>
/// <param name="aTotalCount">The total number of nodes.</param>
/// <param name="aNode">The node std::string.</param>
/// <returns>Returns true to continue reading, false to stop.</returns>
typedef bool (*FNodeRead)(const int aIndex, const int aTotalCount, const std::string& aNode);
/// <summary>
/// Delegate type for processing an edge read from the file.
/// </summary>
/// <description>Function pointer type for a callback that processes edges read from a file.</description>
/// <param name="aIndex">The index of the edge (0-based).</param>
/// <param name="aTotalCount">The total number of edges.</param>
/// <param name="aFromNode">The from node std::string.</param>
/// <param name="aToNode">The to node std::string.</param>
/// <param name="aWeight">The weight of the edge.</param>
/// <returns>Returns true to continue reading, false to stop.</returns>
typedef bool (*FEdgeRead)(const int aIndex, const int aTotalCount, const std::string& aFromNode, const std::string& aToNode, float aWeight);
/// </summary>
/// Use this function to read a graph from a specified file and process its nodes and edges using callback functions.
/// </summary>
/// <function>readGraphFromFile</function>
/// <description>
/// Reads a graph from a specified file and invokes callbacks for each node and edge read.
/// All nodes are read first, followed by edges.
/// </description>
/// <param name="aFilename">The path to the file containing the graph data.</param>
/// <param name="aOnNodeRead">A callback function that is called for each node read. It takes one parameter: the node std::string. If the callback returns false, the reading process stops.</param>
/// <param name="aOnEdgeRead">A callback function that is called for each edge read. It takes three parameters: the fromNode std::string, the toNode std::string, and the weight float. If the callback returns false, the reading process stops.</param>
void readGraphFromFile(const std::string& aFilename, FNodeRead aOnNodeRead, FEdgeRead aOnEdgeRead);
/// <summary>
/// Delegate type for processing a song read from the file.
/// </summary>
/// <param name="aIndex">The index of the song (0-based).</param>
/// <param name="aTotalCount">The total number of songs.</param>
/// <param name="aArtist">The artist.</param>
/// <param name="aTitle">The title.</param>
/// <param name="aYear">The release year (as a std::string).</param>
/// <param name="aGenre">The genre.</param>
/// <param name="aSource">The source.</param>
/// <returns>Returns true to continue reading, false to stop.</returns>
typedef bool (*FSongRead)(
const int aIndex,
const int aTotalCount,
const std::string& aArtist,
const std::string& aTitle,
const std::string& aYear,
const std::string& aGenre,
const std::string& aSource
);
/// <summary>
/// Reads song data from a file and processes them using a callback.
/// This function automatically skips the "records:=" header.
/// </summary>
/// <param name="aFilename">The path to the file (e.g., "songs.txt").</param>
/// <param name="aOnSongRead">The callback function called for each song.</param>
void ReadSongsFromFile(const std::string& aFilename, FSongRead aOnSongRead);
#endif // SHARED_LIB_H

View File

@@ -0,0 +1,128 @@
#include "TDoublyLinkedList.h"
#include <iostream>
#include "SharedLib.h"
TDoublyLinkedList::~TDoublyLinkedList()
{
const Node* cur = head;
while (cur) {
const Node* next = cur->GetNext();
delete cur;
cur = next;
}
head = tail = nullptr;
size = 0;
}
// Append a new line at the end of the list.
void TDoublyLinkedList::Append(const std::string& line)
{
auto* newNode = new Node(line);
if (size == 0)
head = tail = newNode;
else {
newNode->SetPrev(tail);
tail->SetNext(newNode);
tail = newNode;
}
size++;
}
// Insert a new line at the beginning.
void TDoublyLinkedList::Prepend(const std::string& line)
{
auto* newNode = new Node(line);
if (size == 0)
head = tail = newNode;
else {
newNode->SetNext(head);
head->SetPrev(newNode);
head = newNode;
}
size++;
}
TDoublyLinkedList::Node* TDoublyLinkedList::NavigateToNode(const int index) const
{
if (index < 0 || index >= size)
return nullptr;
auto* node = head;
for (int i = 0; i < index; i++)
node = node->GetNext();
return node;
}
// Removes the node at the given index and updates head/tail if needed.
void TDoublyLinkedList::Remove(const int index)
{
auto* node = NavigateToNode(index);
if (!node)
return;
if (node->GetPrev())
node->GetPrev()->SetNext(node->GetNext());
else
head = node->GetNext();
if (node->GetNext())
node->GetNext()->SetPrev(node->GetPrev());
else
tail = node->GetPrev();
delete node;
size--;
}
std::string TDoublyLinkedList::GetAtIndex(const int index) const
{
const auto* node = NavigateToNode(index);
return node ? node->GetLine() : "Error, line does not exist\n";
}
void TDoublyLinkedList::InsertAtIndex(const int index, const std::string &line)
{
if (index < 0 || index > size) {
std::cout << "========\nIndex doesn't exist\n========\n" << std::endl;
return;
}
if (index == 0)
{
Prepend(line);
return;
}
if (index == size)
{
Append(line);
return;
}
Node* cur = head;
for (int i = 0; i < index; i++)
cur = cur->GetNext();
Node* newNode = new Node(line);
Node* prev = cur->GetPrev();
newNode->SetPrev(prev);
newNode->SetNext(cur);
prev->SetNext(newNode);
cur->SetPrev(newNode);
size++;
}
int TDoublyLinkedList::GetSize() const
{
return size;
}

View File

@@ -0,0 +1,72 @@
#ifndef TDOUBLYLINKEDLIST_H
#define TDOUBLYLINKEDLIST_H
#include <string>
#include <utility>
// Doubly linked list used to store document lines.
// Supports insertion, removal, and indexed access.
// Chosen because it allows efficient updates in the middle of the document.
class TDoublyLinkedList {
private:
// Internal node storing a single line of text
// and links to previous and next nodes.
struct Node {
std::string line;
Node* next;
Node* prev;
explicit Node(std::string text) : line(std::move(text)), next(nullptr), prev(nullptr) {}
void SetNext(Node* node)
{
this->next = node;
}
void SetPrev(Node* node)
{
this->prev = node;
}
[[nodiscard]] Node* GetPrev() const
{
return this->prev;
}
[[nodiscard]] Node* GetNext() const
{
return this->next;
}
[[nodiscard]] std::string GetLine() const
{
return line;
}
};
Node* head;
Node* tail;
int size;
public:
TDoublyLinkedList() : head(nullptr), tail(nullptr), size(0) {}
~TDoublyLinkedList();
void Append(const std::string &line);
void Prepend(const std::string& line);
// Returns pointer to node at given index.
// Linear traversal; used internally by Remove and InsertAtIndex.
[[nodiscard]] Node* NavigateToNode(int index) const;
// Removes a node at the given index.
// Updates links and frees the removed node.
void Remove(int index);
// Returns the text stored at the given index.
// Uses NavigateToNode internally.
[[nodiscard]] std::string GetAtIndex(int index) const;
void InsertAtIndex(int index, const std::string &line);
[[nodiscard]] int GetSize() const;
};
#endif //TDOUBLYLINKEDLIST_H

View File

@@ -0,0 +1,182 @@
#include "TLinkedList.h"
#include <iostream>
TLinkedList::~TLinkedList()
{
Node* cur = head;
while (cur) {
Node* next = cur->next;
delete cur;
cur = next;
}
head = nullptr;
tail = nullptr;
size = 0;
}
void TLinkedList::Append(const TPerson& person)
{
Node* newNode = new Node(person);
if (size == 0) {
head = tail = newNode;
} else {
tail->next = newNode;
tail = newNode;
}
size++;
}
void TLinkedList::Prepend(const TPerson& person)
{
Node* newNode = new Node(person);
if (size == 0) {
head = tail = newNode;
} else {
newNode->next = head;
head = newNode;
}
size++;
}
void TLinkedList::InsertAtIndex(const int index, const TPerson& person)
{
if (index < 0 || index > size) {
std::cout << "Index out of range\n";
return;
}
if (index == 0) {
Prepend(person);
return;
}
if (index == size) {
Append(person);
return;
}
Node* prev = head;
for (int i = 0; i < index - 1; ++i)
prev = prev->next;
Node* newNode = new Node(person);
newNode->next = prev->next;
prev->next = newNode;
size++;
}
void TLinkedList::Remove(const int index)
{
if (index < 0 || index >= size)
return;
if (index == 0) {
const Node* oldHead = head;
head = head->next;
if (size == 1)
tail = nullptr;
delete oldHead;
size--;
return;
}
Node* prev = head;
for (int i = 0; i < index - 1; ++i)
prev = prev->next;
const Node* toDelete = prev->next;
prev->next = toDelete->next;
if (toDelete == tail)
tail = prev;
delete toDelete;
size--;
}
TPerson TLinkedList::GetAtIndex(const int index) const
{
if (index < 0 || index >= size) {
std::cout << "Index out of range\n";
return {};
}
Node* cur = head;
for (int i = 0; i < index; ++i)
cur = cur->next;
return cur->person;
}
void TLinkedList::MergeSortSplit(Node *source, Node **front, Node **back)
{
if (source == nullptr || source->next == nullptr) {
*front = source;
*back = nullptr;
return;
}
Node* slow = source;
const Node* fast = source->next;
while (fast != nullptr) {
fast = fast->next;
if (fast != nullptr) {
slow = slow->next;
fast = fast->next;
}
}
*front = source;
*back = slow->next;
slow->next = nullptr;
}
TLinkedList::Node *TLinkedList::MergeList(Node *a, Node *b)
{
if (a == nullptr)
return b;
if (b == nullptr)
return a;
Node* result = nullptr;
if (a->person < b->person) {
result = a;
result->next = MergeList(a->next, b);
}
else {
result = b;
result->next = MergeList(a, b->next);
}
return result;
}
// Time complexity O(n log n) at all times
// Does NOT sort in place, so more memory is needed to complete
TLinkedList::Node *TLinkedList::MergeSort(Node *head)
{
if (head == nullptr || head->next == nullptr)
return head;
Node* front;
Node* back;
MergeSortSplit(head, &front, &back);
front = MergeSort(front);
back = MergeSort(back);
return MergeList(front, back);
}
// Stable merge sort on the linked list.
// Time complexity: O(n log n), requires extra pointers but no extra arrays.
void TLinkedList::Sort()
{
this->head = MergeSort(head);
Node* cur = this->head;
tail = nullptr;
while (cur) {
if (cur->next == nullptr)
tail = cur;
cur = cur->next;
}
}

View File

@@ -0,0 +1,59 @@
#ifndef IKT203_COURSE_ASSIGNMENTS_TLINKEDLIST_H
#define IKT203_COURSE_ASSIGNMENTS_TLINKEDLIST_H
#include "TPerson.h"
// Singly linked list of TPerson, used for the guest and employee manifests.
// Owns all its Node objects and frees them in the destructor.
// Supports append, prepend, insert, remove, indexed access, and merge-sort.
class TLinkedList {
private:
struct Node {
TPerson person; // stored by value
Node* next;
explicit Node(const TPerson& p) : person(p), next(nullptr) {}
void setNext(Node* n)
{
this->next = n;
}
[[nodiscard]] static Node* GetNext(const Node* n)
{
return n->next;
}
[[nodiscard]] static TPerson GetPerson(Node* n)
{
return n->person;
}
};
Node* head;
Node* tail;
int size;
public:
TLinkedList() : head(nullptr), tail(nullptr), size(0) {}
~TLinkedList();
void Append(const TPerson& person);
void Prepend(const TPerson& person);
void InsertAtIndex(int index, const TPerson& person);
void Remove(int index);
[[nodiscard]] TPerson GetAtIndex(int index) const;
[[nodiscard]] int GetSize() const { return size; }
static void MergeSortSplit(Node* source, Node** front, Node** back);
static Node* MergeList(Node*, Node*);
static Node* MergeSort(Node*);
void Sort();
};
#endif //IKT203_COURSE_ASSIGNMENTS_TLINKEDLIST_H

View File

@@ -0,0 +1,11 @@
#include "TPerson.h"
#include <utility>
#include "Utils.h"
TPerson::TPerson() : firstName("N/A"),lastName("N/A"), status(GUEST), cabinSize(Utils::RandomInt(1, 4)) {}
TPerson::TPerson(std::string f, std::string l, const ENumStatus s) : firstName(std::move(f)), lastName(std::move(l)), status(s), cabinSize(Utils::RandomInt(1, 4)) {}

View File

@@ -0,0 +1,38 @@
#ifndef IKT203_COURSE_ASSIGNMENTS_TPERSON_H
#define IKT203_COURSE_ASSIGNMENTS_TPERSON_H
#include <string>
#include "Utils.h"
enum ENumStatus {
GUEST,
EMPLOYEE
};
// Represents one person on the cruise ship.
// - 'status' tells us if they're a GUEST or EMPLOYEE
// - 'cabinSize' is random in [1, 4] and used for cabin grouping
struct TPerson {
std::string firstName;
std::string lastName;
ENumStatus status;
int cabinSize{};
TPerson();
TPerson(std::string , std::string , ENumStatus);
~TPerson() = default;
// Comparison for alphabetical sorting:
// primary key: lastName, secondary key: firstName.
bool operator<(const TPerson& other) const
{
if (lastName < other.lastName) return true;
if (lastName > other.lastName) return false;
// same last name → compare first name
return firstName < other.firstName;
}
};
#endif //IKT203_COURSE_ASSIGNMENTS_TPERSON_H

View File

@@ -0,0 +1,36 @@
#include "TStack.h"
#include <stdexcept>
void TStack::Push(const TAction& action)
{
if (top >= STACK_MAX_SIZE)
throw std::overflow_error("Stack overflow");
event[top++] = action;
}
TStack::TAction TStack::Pop()
{
if (top == 0)
throw std::underflow_error("Stack empty");
return event[--top];
}
TStack::TAction TStack::Peek() const
{
if (top == 0)
throw std::underflow_error("Stack empty");
return event[top - 1];
}
bool TStack::IsEmpty() const
{
return top == 0;
}
void TStack::Clear()
{
for (int i = 0; i < top; i++) {
this->Pop();
}
}

View File

@@ -0,0 +1,41 @@
#ifndef TSTACK_H
#define TSTACK_H
#define STACK_MAX_SIZE 100
#include <string>
enum EnumActionType {
INSERT,
DELETE
};
// Simple fixed-size stack used for undo/redo.
// Stores actions describing line insert/delete operations.
class TStack {
private:
// Describes a single text-edit action.
// 'action' indicates INSERT or DELETE,
// 'text' stores the affected line, and 'index' is the line position.
struct TAction {
EnumActionType action;
std::string text;
int index;
};
TAction event[STACK_MAX_SIZE]{};
int top = 0;
public:
TStack() = default;
~TStack() = default;
void Push(const TAction& action); // Adds a new action to the top of the stack.
TAction Pop(); // Removes and returns the most recent action.
[[nodiscard]] TAction Peek() const;
[[nodiscard]] bool IsEmpty() const;
void Clear();
};
#endif //TSTACK_H

View File

@@ -1,55 +1,47 @@
#include "TQueue.h" #include "TTreeQueue.h"
#include <stdexcept> #include <stdexcept>
// Time and space O(1) - only adds to the end of the queue and wraps around when needed void TTreeQueue::Enqueue(const std::string& text)
void TQueue::Enqueue(const int item)
{ {
if (IsFull()) if (IsFull())
throw std::overflow_error("Queue Overflow"); throw std::overflow_error("Queue Overflow");
queue[tail] = item; queue[tail] = text;
tail = (tail + 1) % MAX_SIZE; tail = (tail + 1) % MAX_SIZE;
count++; count++;
} }
// Both O(1) std::string TTreeQueue::Dequeue()
int TQueue::Dequeue()
{ {
if (IsEmpty()) if (IsEmpty())
throw std::underflow_error("Empty Queue"); throw std::underflow_error("Empty Queue");
const int item = queue[head]; const std::string item = queue[head];
head = (head + 1) % MAX_SIZE; head = (head + 1) % MAX_SIZE;
count--; count--;
return item; return item;
} }
// Both O(1) std::string TTreeQueue::Peek() const
int TQueue::Peek() const
{ {
if (IsEmpty()) if (IsEmpty())
throw std::underflow_error("Empty Queue"); throw std::underflow_error("Empty Queue");
return queue[head]; return queue[head];
} }
// Both O(1) bool TTreeQueue::IsEmpty() const
bool TQueue::IsEmpty() const
{ {
return count == 0; return count == 0;
} }
bool TQueue::IsFull() const bool TTreeQueue::IsFull() const
{ {
return count == MAX_SIZE; return count == MAX_SIZE;
} }
int TQueue::GetTail() const int TTreeQueue::GetTail() const
{ {
if (IsEmpty()) if (IsEmpty())
throw std::underflow_error("Empty Queue"); throw std::underflow_error("Empty Queue");
return tail; return tail;
} }

View File

@@ -0,0 +1,31 @@
#ifndef TQUEUE_H
#define TQUEUE_H
#define MAX_SIZE 100
#include "TDoublyLinkedList.h"
// Circular array-based queue implementation.
// Used in Cat 1 to store print jobs (each job is a full document snapshot).
// Demonstrates FIFO behavior through enqueue/dequeue operations.
class TTreeQueue {
private:
std::string queue[MAX_SIZE];
int head = 0;
int tail = 0;
int count = 0;
public:
TTreeQueue() = default;
~TTreeQueue() = default;
void Enqueue(const std::string& text); // Adds a new job at the tail of the queue.
std::string Dequeue(); // Removes and returns the next job in FIFO order.
[[nodiscard]] int GetTail() const;
[[nodiscard]] std::string Peek() const;
[[nodiscard]] bool IsEmpty() const;
[[nodiscard]] bool IsFull() const;
};
#endif //TQUEUE_H

View File

@@ -0,0 +1,170 @@
#include "Utils.h"
#include "TDoublyLinkedList.h"
#include "TStack.h"
#include "TPerson.h"
#include <ctime>
#include <iostream>
#include <limits>
// Displays the main menu and reads user choice.
int Utils::Choice()
{
std::cout << "========\n1. Add line\n2. Remove line\n3. View current1 document\n4. Print queue\n5. Process print job\n6. Undo\n7. Redo\n0. Exit"
"\n\nChoice: ";
int choice;
std::cin >> choice;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return choice;
}
// Inserts a new line into the document.
// Records the action into the undo stack.
// 'index' determines insert location; defaults to end of document.
int Utils::Insert(TDoublyLinkedList &document, TStack &undoStack, TStack &redoStack, int index)
{
for (int i = 0; i < document.GetSize(); i++) {
std::cout << i + 1 << ". " << document.GetAtIndex(i) << std::endl;
}
if (document.GetSize() > 0)
{
std::cout << "Enter the line number where you want to insert the line" <<std::endl;
if (!(std::cin >> index)) {
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "========\nIndex must be a number\n========\n\n" << std::endl;
return index;
}
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
if (document.GetSize() < 1)
index = 1;
std::cout << "Enter the text" <<std::endl;
std::string line;
std::getline(std::cin, line);
document.InsertAtIndex(index - 1, line);
undoStack.Push({INSERT, line, index - 1});
if (!redoStack.IsEmpty()) {
redoStack.Clear();
}
return index;
}
void Utils::PrintList(const TDoublyLinkedList &document)
{
for (int i = 0; i < document.GetSize(); i++) {
std::cout << i + 1 << ". " << document.GetAtIndex(i) << std::endl;
}
std::cout << "\n\n";
}
// Removes a line chosen by the user.
// Action is pushed to undo stack for reversibility.
int Utils::RemoveLine(TDoublyLinkedList &document, TStack &undoStack, TStack &redoStack, int index)
{
std::cout << "Enter the number of the line you want to remove" <<std::endl;
if (!(std::cin >> index)) {
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "========\nIndex must be a number\n========\n\n" << std::endl;
return index;
} std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
const std::string deletedLine = document.GetAtIndex(index-1);
document.Remove(index-1);
undoStack.Push({DELETE, deletedLine, index-1});
if (!redoStack.IsEmpty()) {
redoStack.Clear();
}
return index;
}
int Utils::RandomInt(const int min, const int max)
{
static bool isSeeded = false;
if (!isSeeded) {
std::srand(static_cast<unsigned>(std::time(nullptr))); //<---- not the "best" random seeding available
isSeeded = true; // but sufficient for this use case
}
if (max <= min)
return 0;
return min + rand() % (max - min + 1); // <---- Limited randomness, but again
} // sufficient for this use case
// Comparison used for cabin grouping (QuickSort):
// 1) cabinSize ascending
// 2) lastName alphabetical
bool Utils::CompareLastnames(const TPerson *a, const TPerson *b)
{
if (a->cabinSize < b->cabinSize)
return true;
if (a->cabinSize > b->cabinSize)
return false;
return a->lastName < b->lastName;
}
int Utils::Partition(TPerson **arr, const int startIndex, const int endIndex)
{
TPerson *pivot = arr[endIndex];
int i = startIndex - 1;
for (int j = startIndex; j < endIndex; j++) {
if (CompareLastnames(arr[j], pivot)) {
i++;
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[endIndex]);
return i + 1;
}
// QuickSort on an array of TPerson* using CompareLastnames:
// - Average time: O(n log n)
// - Worst case: O(n^2) if pivot choices are bad
// - Sorts in-place (no extra arrays)
void Utils::QuickSort(TPerson** arr, const int low, const int high)
{
if (low < high) {
int p = Partition(arr, low, high);
QuickSort(arr, low, p - 1);
QuickSort(arr, p + 1, high);
}
}
// Binary search on an alphabetically sorted array of TPerson* (by lastName, then firstName).
// Primary search key: surname. If no surname match is found, falls back to linear scan on firstName.
int Utils::BinarySearch(TPerson** arr, int p1, int p2, const std::string &target)
{
const int origStart = p1;
const int origEnd = p2;
while (p1 <= p2) {
const int newP = (p1 + p2) / 2;
std::string currentFirst = arr[newP]->firstName;
std::string currentLast = arr[newP]->lastName;
if (target == currentFirst || target == currentLast)
return newP;
if (target > currentLast)
p1 = newP + 1;
else
p2 = newP - 1;
}
// Fallback linear scan for first names if no last name match
for (int i = origStart; i <= origEnd; i++) {
if (arr[i]->firstName == target)
return i;
}
return -1;
}

View File

@@ -0,0 +1,24 @@
#ifndef UTILS_H
#define UTILS_H
#include "TDoublyLinkedList.h"
#include "TPerson.h"
#include "TStack.h"
struct TPerson;
class Utils {
public:
static int Choice();
static int Insert(TDoublyLinkedList &document, TStack &undoStack, TStack &redoStack, int index);
static void PrintList(const TDoublyLinkedList &document);
static int RemoveLine(TDoublyLinkedList &document, TStack &undoStack, TStack &redoStack, int index);
static int RandomInt(int, int);
static int Partition(TPerson** arr, int startIndex, int endIndex);
static void QuickSort(TPerson**, int, int);
static bool CompareLastnames(const TPerson*, const TPerson*);
static int BinarySearch(TPerson**, int, int, const std::string&);
static int CountMatches(TPerson**, int, const std::string&);
};
#endif //PART1_UTILS_H

View File

@@ -0,0 +1,19 @@
# CMakeList.txt : Top-level CMake project file, do global configuration
# and include sub-projects here.
#
cmake_minimum_required (VERSION 3.20)
# Enable Hot Reload for MSVC compilers if supported.
if (POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()
project ("Exercises")
# Include sub-projects.
add_subdirectory ("Submission-01")
add_subdirectory ("Submission-02")
add_subdirectory ("Submission-03")
add_subdirectory ("Submission-04")
add_subdirectory ("Submission-05")

View File

@@ -0,0 +1,13 @@
# CMakeList.txt : CMake project for Submission-01, include source and define
# project specific logic here.
#
# Add source to this project's executable.
add_executable (Submission-01 "main.cpp" "main.h")
target_link_libraries(Submission-01 PRIVATE LibExample)
if (CMAKE_VERSION VERSION_GREATER 3.20)
set_property(TARGET Submission-01 PROPERTY CXX_STANDARD 20)
endif()
# TODO: Add tests and install targets if needed.

View File

@@ -0,0 +1,309 @@
// Submission-01.cpp : Defines the entry point for the application.
//
#include "main.h"
#include <iostream>
// Movie class with bitwise genre flags
enum EGenreFlags {
Action = 0x0001,
Comedy = 0x0002,
Drama = 0x0004,
Horror = 0x0008,
SciFi = 0x0010,
Romance = 0x0020,
Documentary = 0x0040,
Thriller = 0x0080,
Crime = 0x0100,
Fantasy = 0x0200,
Animation = 0x0400,
Adventure = 0x0800
};
static std::string GenreFlagsToString(int genreFlags) {
std::string result;
if (genreFlags & EGenreFlags::Action) result += "Action ";
if (genreFlags & EGenreFlags::Comedy) result += "Comedy ";
if (genreFlags & EGenreFlags::Drama) result += "Drama ";
if (genreFlags & EGenreFlags::Horror) result += "Horror ";
if (genreFlags & EGenreFlags::SciFi) result += "SciFi ";
if (genreFlags & EGenreFlags::Romance) result += "Romance ";
if (genreFlags & EGenreFlags::Documentary) result += "Documentary ";
if (genreFlags & EGenreFlags::Thriller) result += "Thriller ";
if (genreFlags & EGenreFlags::Crime) result += "Crime ";
if (genreFlags & EGenreFlags::Fantasy) result += "Fantasy ";
if (genreFlags & EGenreFlags::Animation) result += "Animation ";
if (genreFlags & EGenreFlags::Adventure) result += "Adventure ";
return result.empty() ? "None" : result;
}
// Movie class definition
class TMovie {
private:
std::string title;
std::string director;
int year;
int genreFlags; // Bitwise combination of EGenreFlags
float rating; // Scale from 0.0 to 10.0
public:
TMovie(std::string t, std::string d, int y, int g, float r)
: title(t), director(d), year(y), genreFlags(g), rating(r) {}
void PrintInfo() const {
std::cout << "Title: " << title << "\nDirector: " << director
<< "\nYear: " << year << "\nGenres: " << GenreFlagsToString(genreFlags)
<< "\nRating: " << rating << "/10\n";
}
std::string GetTitle() const { return title; }
std::string GetDirector() const { return director; }
int GetYear() const { return year; }
float GetRating() const { return rating; }
int GetGenreFlags() const { return genreFlags; }
bool HasGenre(EGenreFlags genre) const {
return (genreFlags & genre) != 0;
}
};
typedef bool (*FCheckMovie)(TMovie*, void*);
typedef void (*FMovieIndex)(TMovie*, int);
// Doubly Linked List TMovieNode definition
struct TMovieNode {
TMovie* movie;
TMovieNode* next;
TMovieNode* prev;
TMovieNode(TMovie* m) : movie(m), next(nullptr), prev(nullptr) {}
};
// Doubly Linked List class definition with dummy node, head, and tail O(1) operations
class TMovieList {
private:
TMovieNode* head; // Always points to dummy node
TMovieNode* tail;
int size;
// Helper function to get node at index
TMovieNode* InternalGetAtIndex(int aIndex) {
if (aIndex < 0 || aIndex >= size) return nullptr;
TMovieNode* current;
// Optimize traversal direction, if index is in the first half, start from head, else from tail
if (aIndex < size / 2) {
current = head->next; // Start from the beginning
for (int i = 0; i < aIndex; i++) {
current = current->next;
}
}
else {
current = tail; // Start from the end
for (int i = size - 1; i > aIndex; i--) {
current = current->prev;
}
}
return current;
}
public:
TMovieList() {
head = new TMovieNode(nullptr); // Dummy node
tail = head; // Initially, tail is the same as head
size = 0;
}
~TMovieList() {
Clear();
delete head; // Delete dummy node
}
void Clear() {
TMovieNode* current = head->next; // Start from the first real node
while (current) {
TMovieNode* toDelete = current;
current = current->next;
delete toDelete->movie;
delete toDelete;
}
head->next = nullptr;
tail = head; // Reset tail to dummy node
size = 0;
}
// Insertion at the end O(1)
void Append(TMovie* aMovie) {
TMovieNode* newNode = new TMovieNode(aMovie);
newNode->prev = tail;
tail->next = newNode;
tail = newNode;
size++;
}
// Prepend at the beginning O(1)
void Prepend(TMovie* aMovie) {
TMovieNode* newNode = new TMovieNode(aMovie);
newNode->next = head->next;
newNode->prev = head;
if (head->next) {
head->next->prev = newNode;
} else {
tail = newNode; // If list was empty, update tail
}
head->next = newNode;
size++;
}
// GetAtIndex O(n) check direction to optimize
TMovie* GetAtIndex(int aIndex) {
return InternalGetAtIndex(aIndex)->movie;
}
// Remove at index O(n) use GetAtIndex to find node
bool RemoveAtIndex(int aIndex) {
if (aIndex < 0 || aIndex >= size) return false;
TMovieNode* toDelte = InternalGetAtIndex(aIndex);
if (!toDelte) return false;
if (toDelte->prev) {
toDelte->prev->next = toDelte->next;
}
if (toDelte->next) {
toDelte->next->prev = toDelte->prev;
} else {
tail = toDelte->prev; // Update tail if last node is removed
}
delete toDelte->movie;
delete toDelte;
size--;
return true;
}
// Reverse the list O(n)
void Reverse() {
TMovieNode* current = head->next;
TMovieNode* prev = nullptr;
tail = head->next;
while (current) {
TMovieNode* nextNode = current->next;
current->next = prev;
current->prev = nextNode;
prev = current;
current = nextNode;
}
head->next = prev;
if (prev) {
prev->prev = head;
}
}
// SearchFor O(n)
TMovie* SearchFor(FCheckMovie aCheckFunc, void* aUserData) {
TMovieNode* current = head->next;
while (current) {
if (aCheckFunc(current->movie, aUserData)) {
return current->movie;
}
current = current->next;
}
return nullptr;
}
// Every movie in the list O(n)
void Every(FMovieIndex aIndexFunc) {
TMovieNode* current = head->next;
int index = 0;
while (current) {
aIndexFunc(current->movie, index);
current = current->next;
index++;
}
}
};
using namespace std;
static void PrintNode(std::string* data, int index) {
cout << "Node " << index << ": " << *data << endl;
}
static bool CheckMovieByTitle(TMovie* movie, void* title) {
return movie->GetTitle() == *(static_cast<std::string*>(title));
}
static bool CheckMovieByDirector(TMovie* movie, void* director) {
return movie->GetDirector() == *(static_cast<std::string*>(director));
}
static bool FindAllMovieByGenre(TMovie* movie, void* genre) {
if(movie->HasGenre(*(static_cast<EGenreFlags*>(genre)))) {
movie->PrintInfo();
std::cout << "-------------------" << std::endl;
}
// Always return false to continue searching
return false;
}
int main()
{
std::cout << "--- Submission 1: Linked List ---" << std::endl;
// Create a movie list
TMovieList movieList;
// Add some movies
movieList.Append(new TMovie("Inception", "Christopher Nolan", 2010, EGenreFlags::Action | EGenreFlags::SciFi, 8.8f));
movieList.Append(new TMovie("The Godfather", "Francis Ford Coppola", 1972, EGenreFlags::Crime | EGenreFlags::Drama, 9.2f));
movieList.Prepend(new TMovie("Toy Story", "John Lasseter", 1995, EGenreFlags::Animation | EGenreFlags::Adventure | EGenreFlags::Comedy, 8.3f));
movieList.Append(new TMovie("The Dark Knight", "Christopher Nolan", 2008, EGenreFlags::Action | EGenreFlags::Crime | EGenreFlags::Drama, 9.0f));
// Print movie info
for (int i = 0; i < 3; i++) {
TMovie* movie = movieList.GetAtIndex(i);
if (movie) {
movie->PrintInfo();
std::cout << "-------------------" << std::endl;
}
}
std::cout << std::endl;
// Wait for user input to proceed
std::cout << "Press Enter to continue..." << std::endl;
std::cin.get();
// Search for a movie by title
std::string searchTitle = "Inception";
TMovie* foundMovie = movieList.SearchFor(CheckMovieByTitle, &searchTitle);
if (foundMovie) {
std::cout << "Found movie by title '" << searchTitle << "':" << std::endl;
foundMovie->PrintInfo();
} else {
std::cout << "Movie with title '" << searchTitle << "' not found." << std::endl;
}
std::cout << "-------------------" << std::endl;
std::cout << std::endl;
// Search for a movie by director
std::string searchDirector = "John Lasseter";
foundMovie = movieList.SearchFor(CheckMovieByDirector, &searchDirector);
if (foundMovie) {
std::cout << "Found movie by director '" << searchDirector << "':" << std::endl;
foundMovie->PrintInfo();
} else {
std::cout << "Movie with director '" << searchDirector << "' not found." << std::endl;
}
std::cout << "-------------------" << std::endl;
std::cout << std::endl;
// Find all movies in the Action genre
EGenreFlags searchGenre = EGenreFlags::Action;
std::cout << "Movies in the Action genre:" << std::endl;
movieList.SearchFor(FindAllMovieByGenre, &searchGenre);
std::cout << std::endl;
// Reverse the list
movieList.Reverse();
std::cout << "Movies after reversing the list:" << std::endl;
movieList.Every([](TMovie* movie, int index) {
std::cout << "Index " << index << ":" << std::endl;
movie->PrintInfo();
std::cout << "-------------------" << std::endl;
});
return 0;
}

View File

@@ -0,0 +1,8 @@
// Submission-01.h : Include file for standard system include files,
// or project specific include files.
#pragma once
#include <iostream>
// TODO: Reference additional headers your program requires here.

View File

@@ -0,0 +1,13 @@
# CMakeList.txt : CMake project for Submission-01, include source and define
# project specific logic here.
#
# Add source to this project's executable.
add_executable (Submission-02 "main.cpp" "main.h")
target_link_libraries(Submission-02 PRIVATE LibExample)
if (CMAKE_VERSION VERSION_GREATER 3.20)
set_property(TARGET Submission-02 PROPERTY CXX_STANDARD 20)
endif()
# TODO: Add tests and install targets if needed.

View File

@@ -0,0 +1,121 @@
// Submission-01.cpp : Defines the entry point for the application.
//
#include "main.h"
#include <iostream>
// printNaturalNumbers
static void printNaturalNumbers(int aN)
{
if (aN <= 0) return; // Base case: if n is less than or equal to 0, do nothing
printNaturalNumbers(aN - 1); // Recursive call with n-1
std::cout << aN << " "; // Print the number after the recursive call to achieve ascending order
}
// factorial function
static int calculateFactorial(int aN)
{
if (aN <= 1) return 1; // Base case: factorial of 0 or 1 is 1
return aN * calculateFactorial(aN - 1); // Recursive call
}
// power function using exponentiation by squaring
// This method is more efficient than the naive approach, reducing the time complexity from O(n) to O(log n).
static int power(int aBase, int aExponent)
{
if (aExponent == 0) return 1; // Base case: any number to the power of 0 is 1
if (aExponent < 0) return 1 / power(aBase, -aExponent); // Handle negative exponents
if (aExponent % 2 == 0) // If exponent is even
{
int halfPower = power(aBase, aExponent / 2);
return halfPower * halfPower; // (x^(n/2))^2
}
else // If exponent is odd
{
return aBase * power(aBase, aExponent - 1); // x * x^(n-1)
}
}
// Fibonacci function
// Note: This naive recursive solution is inefficient because it recalculates the same Fibonacci numbers multiple times, leading to an exponential time complexity of O(2^n).
// An improvement could be made by using memoization or an iterative approach to store previously calculated values, reducing the time complexity to O(n).
static int fibonacci(int aN)
{
if (aN <= 0) return 0; // Base case: fibonacci(0) = 0
if (aN == 1) return 1; // Base case: fibonacci(1) = 1
int a = fibonacci(aN - 1);
int b = fibonacci(aN - 2);
std::cout << a << " + " << b << " = " << (a + b) << std::endl; // Print the sum of the two preceding numbers
return a + b; // Recursive call
}
// Count occurrences of a character in a string
// This function counts how many times a specific character appears in a given string using recursion.
static int countOccurrences(const char* aS, char aC)
{
if (*aS == '\0') return 0; // Base case: end of string
return (*aS == aC ? 1 : 0) + countOccurrences(aS + 1, aC); // Check current character and recurse for the rest of the string
}
// Find the largest element in an array using binary recursion
// This function divides the array into two halves, finds the largest element in each half recursively, and then returns the larger of the two.
static int findLargestElement(int arr[], int size)
{
if (size == 1) return arr[0]; // Base case: only one element
int mid = size / 2;
int leftMax = findLargestElement(arr, mid); // Find max in left half
int rightMax = findLargestElement(arr + mid, size - mid); // Find max in right half
return (leftMax > rightMax) ? leftMax : rightMax; // Return the larger of the two
}
// Traverse and print characters in the ASCII table from aStart to aEnd using recursion
// This function prints characters in ascending order during the building phase of the recursion and in descending order during the unwinding phase.
static void traverseAsciiTable(char aStart, char aEnd)
{
if (aStart > aEnd) return; // Base case: if start exceeds end, do nothing
std::cout << aStart << " "; // Print before the recursive call
traverseAsciiTable(aStart + 1, aEnd); // Recursive call with next character
std::cout << aStart << " "; // Print after the recursive call
}
int main()
{
std::cout << "--- Submission 2: Fundamental Recursion ---" << std::endl;
std::cout << std::endl << "Part 1: Linear Recursion - Your First Steps" << std::endl;
printNaturalNumbers(5);
std::cout << std::endl << "------------------------------------------------" << std::endl;
std::cout << std::endl << "Factorial of 5: " << calculateFactorial(5);
std::cout << std::endl << "------------------------------------------------" << std::endl;
std::cout << std::endl << "Part 2: Multiple & Binary Recursion - Diving Deeper" << std::endl;
std::cout << std::endl << "2^10: " << power(2, 10);
std::cout << std::endl << "------------------------------------------------" << std::endl;
std::cout << std::endl << "4th Fibonacci number: " << std::endl << fibonacci(4);
std::cout << std::endl << "------------------------------------------------" << std::endl;
std::cout << std::endl << "Occurrences of 'l' in 'Hello, World!': " << countOccurrences("Hello, World!", 'l');
std::cout << std::endl << "------------------------------------------------" << std::endl;
int* arr = new int[20];
// Fill array with random numbers from 0 to 999
for (int i = 0; i < 20; ++i) arr[i] = rand() % 999;
std::cout << std::endl << "Part 3: Advanced Binary Recursion" << std::endl;
// Print first 20 elements of the array
for (int i = 0; i < 20; ++i) std::cout << arr[i] << " ";
std::cout << std::endl << "Largest element in array: " << findLargestElement(arr, 20);
std::cout << std::endl << "------------------------------------------------" << std::endl;
std::cout << std::endl << "Traverse ASCII table from 'A' to 'Z':" << std::endl;
traverseAsciiTable('A', 'Z');
/*
Note: The output reflects the building and unwinding of the call stack.
*/
std::cout << std::endl;
return 0;
}

View File

@@ -0,0 +1,8 @@
// Submission-01.h : Include file for standard system include files,
// or project specific include files.
#pragma once
#include <iostream>
// TODO: Reference additional headers your program requires here.

View File

@@ -0,0 +1,13 @@
# CMakeList.txt : CMake project for Submission-01, include source and define
# project specific logic here.
#
# Add source to this project's executable.
add_executable (Submission-03 "main.cpp" "main.h")
target_link_libraries(Submission-03 PRIVATE LibExample)
if (CMAKE_VERSION VERSION_GREATER 3.20)
set_property(TARGET Submission-03 PROPERTY CXX_STANDARD 20)
endif()
# TODO: Add tests and install targets if needed.

View File

@@ -0,0 +1,439 @@
// Submission-01.cpp : Defines the entry point for the application.
//
#include "main.h"
#include <iostream>
/*
Part 1: Implementing the Core Data Structures
These tasks are designed to get you comfortable with the Last-In, First-Out (LIFO) and First-In, First-Out (FIFO) principles. Building these yourself will give you a deep understanding of how they work under the hood!
*/
/*
1. Implementing a Stack:
a) Create a simple TStack class that can hold int values.
b) Use a fixed-size array and a top-of-stack index to manage the data.
c) Implement the core methods: Push(int item) and Pop().
D) Add a Peek() method to view the top item without removing it.
e) Include an IsEmpty() method to check if the stack is empty.
*/
class TStackArray {
private:
int maxSize = 0;
int* stackArray = nullptr;
int top = -1; // Index of the top element
public:
TStackArray(int aSize) : maxSize(aSize) {
stackArray = new int[maxSize];
}
~TStackArray() {
delete[] stackArray;
}
void Push(int aItem) {
if (top < maxSize - 1) {
stackArray[++top] = aItem;
}
else {
std::cout << "Stack Overflow" << std::endl;
}
}
int Pop() {
if (!IsEmpty()) {
return stackArray[top--];
}
else {
std::cout << "Stack Underflow" << std::endl;
return -1; // Indicate error
}
}
int Peek() const {
if (!IsEmpty()) {
return stackArray[top];
}
else {
std::cout << "Stack is empty" << std::endl;
return -1; // Indicate error
}
}
bool IsEmpty() const {
return top == -1;
}
};
/*
2. Implementing a Queue:
a) Create a simple TQueue class that can hold int values.
b) Use a fixed-size array and front/back indices to manage the data.
c) Implement the core methods: Enqueue(int item) and Dequeue().
d) Add a Peek() method to view the item at the front without removing it.
e) Include an IsEmpty() method to check if the queue is empty.
*/
class TQueueArray {
private:
int maxSize = 0;
int* queueArray = nullptr;
int front = 0; // Index of the front element
int back = -1; // Index of the back element
int itemCount = 0; // Number of items in the queue
public:
TQueueArray(int aSize) : maxSize(aSize) {
queueArray = new int[maxSize];
}
~TQueueArray() {
delete[] queueArray;
}
void Enqueue(int aItem) {
if (itemCount < maxSize) {
back = (back + 1) % maxSize; // Circular increment
queueArray[back] = aItem;
itemCount++;
}
else {
std::cout << "Queue Overflow" << std::endl;
}
}
int Dequeue() {
if (!IsEmpty()) {
int item = queueArray[front];
front = (front + 1) % maxSize; // Circular increment
itemCount--;
return item;
}
else {
std::cout << "Queue Underflow" << std::endl;
return -1; // Indicate error
}
}
int Peek() const {
if (!IsEmpty()) {
return queueArray[front];
}
else {
std::cout << "Queue is empty" << std::endl;
return -1; // Indicate error
}
}
bool IsEmpty() const {
return itemCount == 0;
}
};
class TNodeInteger {
public:
int data;
TNodeInteger* next;
TNodeInteger(int aData) : data(aData), next(nullptr) {}
};
// Stack implemented using a linked list with dummy head node
class TStackLinkedList {
private:
TNodeInteger* top = nullptr;
public:
TStackLinkedList() {
top = new TNodeInteger(0); // Dummy head node
}
~TStackLinkedList() {
while (!IsEmpty()) {
Pop();
}
delete top; // Delete dummy head node
}
void Push(int aItem) {
TNodeInteger* newNode = new TNodeInteger(aItem);
newNode->next = top->next;
top->next = newNode;
}
int Pop() {
if (!IsEmpty()) {
TNodeInteger* temp = top->next;
int item = temp->data;
top->next = temp->next;
delete temp; // Free memory
return item;
}
else {
std::cout << "Stack Underflow" << std::endl;
return -1; // Indicate error
}
}
int Peek() const {
if (!IsEmpty()) {
return top->next->data;
}
else {
std::cout << "Stack is empty" << std::endl;
return -1; // Indicate error
}
}
bool IsEmpty() const {
return top->next == nullptr;
}
};
// Queue implemented using a linked list with dummy head node
class TQueueLinkedList {
private:
TNodeInteger* front = nullptr;
TNodeInteger* back = nullptr;
public:
TQueueLinkedList() {
front = new TNodeInteger(0); // Dummy head node
back = front; // Initially, front and back point to the dummy node
}
~TQueueLinkedList() {
while (!IsEmpty()) {
Dequeue();
}
delete front; // Delete dummy head node
}
void Enqueue(int aItem) {
TNodeInteger* newNode = new TNodeInteger(aItem);
back->next = newNode;
back = newNode;
}
int Dequeue() {
if (!IsEmpty()) {
TNodeInteger* temp = front->next;
int item = temp->data;
front->next = temp->next;
if (back == temp) { // If the dequeued node was the last node
back = front; // Reset back to the dummy head
}
delete temp; // Free memory
return item;
}
else {
std::cout << "Queue Underflow" << std::endl;
return -1; // Indicate error
}
}
int Peek() const {
if (!IsEmpty()) {
return front->next->data;
}
else {
std::cout << "Queue is empty" << std::endl;
return -1; // Indicate error
}
}
bool IsEmpty() const {
return front->next == nullptr;
}
};
/*
Part 2: Practical Applications
Now that you have your own data structures, it's time to put them to work! These are classic problems that perfectly demonstrate the LIFO and FIFO principles.
*/
/*
3. String Reversal with a Stack:
a) Write a function that takes a string as input and uses your TStack to return the reversed string.
b) In a short comment, explain why the stack is the perfect tool for this type of task.
*/
static std::string ReverseString(const char* aStr) {
TStackArray stack(strlen(aStr));
for (int i = 0; aStr[i] != '\0'; i++) {
stack.Push(aStr[i]);
}
std::string reversed;
while (!stack.IsEmpty()) {
reversed += static_cast<char>(stack.Pop()); // Cast int back to char
}
return reversed;
/*
* Note: The stack is the perfect tool for string reversal because it allows us to push each character of the string onto the stack and then pop them off in reverse order.
* And stack is using the rule of LIFO (Last In First Out), so the last character pushed onto the stack will be the first one to be popped off, effectively reversing the order of characters.
*/
}
/*
4. Recursive Functions with a Stack:
a) Remember the factorial function you implemented with recursion in Submission 2? Your computer used a hidden "call stack" to make that happen. Now, your task is to re-implement that function without recursion, using your own `TStack` to manage the process.
b) This is a fantastic exercise that will give you a "eureka" moment about how recursion truly works!
*/
static int Factorial(int n) {
if (n < 0) return -1; // Factorial is not defined for negative numbers
if (n == 0 || n == 1) return 1; // Base case
TStackArray stack(n);
for (int i = 2; i <= n; i++) {
stack.Push(i);
}
int result = 1;
while (!stack.IsEmpty()) {
result *= stack.Pop();
}
return result;
}
/*
5. Wait Line Simulation with a Queue:
a) Simulate a simple waiting line, such as a ticket counter.
b) Use your TQueue to manage a list of people (represented by integer IDs).
c) People should Enqueue when they arrive and Dequeue when they are served, clearly demonstrating the FIFO principle.
*/
static void SimulateWaitLine() {
TQueueArray queue(5); // Queue with a maximum size of 5
// Simulate people arriving
for (int i = 1; i <= 5; i++) {
std::cout << "Person " << i << " arrives." << std::endl;
queue.Enqueue(i);
}
// Simulate serving people
while (!queue.IsEmpty()) {
int person = queue.Dequeue();
std::cout << "Person " << person << " is served." << std::endl;
}
}
/*
Part 3: Advanced Traversal - Stacks vs. Queues
This is the main event! You will solve the same problem using two different approaches, highlighting the different strengths of stacks and queues.
Your recursive functions from Submission 2 actually use an implicit stack, so this task will give you a deeper look into how it all works.
*/
/*
6. Setup: The 100x100 Grid:
a) Create a 100x100 two-dimensional integer array.
b) Populate the array with random integer values between 0 and 9.
c) Choose a random starting cell (row, col).
d) Create a second 100x100 boolean array to keep track of visited cells.
*/
static const int GRID_SIZE = 7;
static int grid[GRID_SIZE][GRID_SIZE];
static bool visited[GRID_SIZE][GRID_SIZE] = { false };
#include <cstdlib>
#include <ctime>
static void InitializeGrid() {
std::srand(static_cast<unsigned int>(std::time(0))); // Seed for randomness
for (int i = 0; i < GRID_SIZE; i++) {
for (int j = 0; j < GRID_SIZE; j++) {
grid[i][j] = std::rand() % 9; // Random values between 0 and 9
visited[i][j] = false; // Initialize visited array
}
}
}
/*
7. Depth-First Search (DFS) with a Stack:
a) Write a function that uses your TStack to perform a DFS on the grid, starting from your random cell.
b) The goal is to find the first occurrence of the number '0'.
c) In a short comment, explain how the LIFO behavior of the stack guides the search to explore as deeply as possible along one path before backtracking.
*/
// Helper function to check if a cell is within bounds and not visited
static bool IsValid(int aRow, int aCol) {
return aRow >= 0 && aRow < GRID_SIZE && aCol >= 0 && aCol < GRID_SIZE && !visited[aRow][aCol];
}
// The stack's LIFO behavior allows the DFS to explore one path fully before backtracking, ensuring that all possible routes are checked in depth-first order.
static bool DFS(int aStartRow, int aStartCol) {
TStackArray stack(GRID_SIZE * GRID_SIZE);
int cellPos = aStartRow * GRID_SIZE + aStartCol; // Encode 2D position as 1D
stack.Push(cellPos);
while (!stack.IsEmpty()) {
cellPos = stack.Pop();
int row = cellPos / GRID_SIZE;
int col = cellPos % GRID_SIZE;
if (grid[row][col] == 0) {
std::cout << "Found 0 at (" << row << ", " << col << ")" << std::endl;
return true;
}
std::cout << "Visiting (" << row << ", " << col << ") with value " << grid[row][col] << std::endl;
visited[row][col] = true;
int neighbors[4][2] = { {row - 1, col}, {row, col + 1}, {row + 1, col}, {row, col - 1} };
for (int i = 0; i < 4; i++) {
int newRow = neighbors[i][0];
int newCol = neighbors[i][1];
if (IsValid(newRow, newCol)) {
cellPos = newRow * GRID_SIZE + newCol;
stack.Push(cellPos);
}
}
}
std::cout << "0 not found in DFS" << std::endl;
return false;
}
// The queue's FIFO behavior ensures that the BFS explores all neighbors at the present depth prior to moving on to nodes at the next depth level, effectively searching layer by layer.
static bool BFS(int aStartRow, int aStartCol) {
int row = aStartRow, col = aStartCol;
int pos = row * GRID_SIZE + col; // Encode 2D position as 1D
TQueueArray queue(GRID_SIZE * GRID_SIZE);
queue.Enqueue(pos);
while (!queue.IsEmpty()) {
pos = queue.Dequeue();
row = pos / GRID_SIZE;
col = pos % GRID_SIZE;
if (grid[row][col] == 0) {
std::cout << "Found 0 at (" << row << ", " << col << ")" << std::endl;
return true;
}
std::cout << "Visiting (" << row << ", " << col << ") with value " << grid[row][col] << std::endl;
visited[row][col] = true;
int neighbors[4][2] = { {row - 1, col}, {row, col + 1},{row + 1, col},{row, col - 1} };
for (int i = 0; i < 4; i++) {
row = neighbors[i][0];
col = neighbors[i][1];
if (IsValid(row, col)) {
pos = row * GRID_SIZE + col;
queue.Enqueue(pos);
}
}
}
std::cout << "0 not found in BFS" << std::endl;
return false;
}
int main()
{
std::cout << "--- Submission 3: Stacks & Queues ---" << std::endl;
std::string original = "Hello, World!";
std::string reversed = ReverseString(original.c_str());
std::cout << "Original String: " << original << std::endl;
std::cout << "Reversed String: " << reversed << std::endl;
// The stack is the perfect tool for string reversal because it operates on a Last-In, First-Out (LIFO) principle.
// This means that the last character pushed onto the stack will be the first one to be popped off, effectively reversing the order of characters.
std::cout << "----------------------------------------------------" << std::endl << std::endl;
std::cout << "Calculating Factorial of 5 using Stack:" << std::endl;
std::cout << "5! = " << Factorial(5) << std::endl;
std::cout << "----------------------------------------------------" << std::endl << std::endl;
std::cout << "Simulating Wait Line using Queue:" << std::endl;
SimulateWaitLine();
std::cout << "----------------------------------------------------" << std::endl << std::endl;
std::cout << "Initializing 100x100 Grid and Performing DFS to find '0':" << std::endl;
InitializeGrid();
int startRow = 3; //std::rand() % GRID_SIZE;
int startCol = 2; //std::rand() % GRID_SIZE;
std::cout << "Starting DFS from (" << startRow << ", " << startCol << ")" << std::endl;
DFS(startRow, startCol);
std::cout << std::endl << "Re-initializing visited array for BFS:" << std::endl;
for (int i = 0; i < GRID_SIZE; i++) {
for (int j = 0; j < GRID_SIZE; j++) {
visited[i][j] = false; // Reset visited array
}
}
std::cout << "Stating BFS from (" << startRow << ", " << startCol << ")" << std::endl;
BFS(startRow, startCol); // This should find a different path to '0'
std::cout << "----------------------------------------------------" << std::endl << std::endl;
return 0;
}

View File

@@ -0,0 +1,8 @@
// Submission-01.h : Include file for standard system include files,
// or project specific include files.
#pragma once
#include <iostream>
// TODO: Reference additional headers your program requires here.

View File

@@ -0,0 +1,108 @@
#include "BankAccount.h"
#include <iostream>
#include <sstream>
#include <iomanip>
#include <cstdlib> // For rand()
#include <random> // For better random number generation
#include <ctime>
#include <cstring> // For memset
#include <cmath> // For floor
#include <chrono> // For time manipulation
#include <locale> // For locale settings
#include <codecvt> // For codecvt_utf8
#include <stdexcept> // For std::invalid_argument
#include <string>
TBankAccount::TBankAccount(EBankAccountType accType, std::string firstName, std::string lastName)
: accountType(accType), ownerFirstName(firstName), ownerLastName(lastName)
{
// Random genration of account number: XXXX.XX.XXXXX
accountNumber = toString(rand() % 9000 + 1000) + "." + toString(rand() % 90 + 10) + "." + toString(rand() % 90000 + 10000);
balance = 0.0f;
//Random generation of creation timestamp, date is any date and time in 2024
int month = rand() % 12 + 1;
int day = rand() % 28 + 1; // To avoid complexity of different month lengths
int hour = rand() % 24;
int minute = rand() % 60;
// Calculate creation timestamp in seconds from 2024-01-01 00:00:00
std:tm tm = {};
tm.tm_year = 2024 - 1900; // Year since 1900
tm.tm_mon = rand() % 12; // Month [0-11]
tm.tm_mday = rand() % 28 + 1; // Day of the month [1-28] to avoid month length issues
tm.tm_hour = rand() % 24; // Hour [0-23]
tm.tm_min = rand() % 60; // Minute [0-59]
tm.tm_sec = 0; // Second [0-59]
creationTimestamp = _mkgmtime(&tm); // Use _mkgmtime for UTC
if (accType == Checking || accType == Saving || accType == Pension)
balance = static_cast<double>(rand() % 1001); // 0 to 1000
else if (accType == Loan)
balance = static_cast<double>(-(rand() % 25001 + 25000)); // -50000 to -25000
else if (accType == Credit)
balance = static_cast<double>(-(rand() % 1001)); // -1000 to 0
}
TBankAccount::~TBankAccount()
{
// Destructor logic if needed
}
std::string TBankAccount::getAccountNumber() const {
return accountNumber;
}
EBankAccountType TBankAccount::getAccountType() const {
return accountType;
}
time_t TBankAccount::getCreationTimestamp() const {
return creationTimestamp;
}
double TBankAccount::getBalance() const {
return balance;
}
void TBankAccount::deposit(double aAmount) {
if (aAmount > 0) balance += aAmount;
}
void TBankAccount::withdraw(double aAmount) {
if (aAmount > 0 && aAmount <= balance) balance -= aAmount;
}
std::string TBankAccount::getAccountTypeString() const
{
switch (accountType)
{
case Checking: return "Checking";
case Saving: return "Saving";
case Credit: return "Credit";
case Pension: return "Pension";
case Loan: return "Loan";
default: return "Unknown";
}
}
//
std::string TBankAccount::getCreationTimeString() const
{
char buffer[26];
ctime_s(buffer, sizeof(buffer), &creationTimestamp);
std::string timeString(buffer);
if (!timeString.empty() && timeString.back() == '\n') {
timeString.pop_back(); // Remove the trailing newline character
}
return timeString;
}
void TBankAccount::printAccountInfo() const
{
std::cout << "Account Number: " << accountNumber << ", Type: " << getAccountTypeString()
<< ", Owner: " << ownerFirstName << " " << ownerLastName
<< ", Balance: " << balance
<< ", Created: " << getCreationTimeString()
<< std::endl;
}

View File

@@ -0,0 +1,52 @@
#pragma once
#ifndef BANKACCOUNT_H
#define BANKACCOUNT_H
#include <string> // For std::string
#include <ctime> // For time_t
#include <cstdlib> // For rand()
#include <iomanip> // For std::setfill and std::setw
#include <sstream> // For std::ostringstream
#include <iostream> // For std::cout
// Helper function to convert value to string
template <typename T>
std::string toString(T value)
{
std::ostringstream oss;
oss << value;
return oss.str();
}
enum EBankAccountType { Checking, Saving, Credit, Pension, Loan };
class TBankAccount {
private:
std::string accountNumber;
EBankAccountType accountType;
time_t creationTimestamp;
double balance;
public:
std::string ownerFirstName;
std::string ownerLastName;
//TBankAccount() {} // Don't use default constructor
TBankAccount(EBankAccountType, std::string, std::string);
~TBankAccount();
std::string getAccountNumber() const;
std::string getCreationTimeString() const;
time_t getCreationTimestamp() const;
double getBalance() const;
void deposit(double);
void withdraw(double);
EBankAccountType getAccountType() const;
std::string getAccountTypeString() const;
void printAccountInfo() const;
};
#endif // BANKACCOUNT_H

View File

@@ -0,0 +1,130 @@
#include "BankAccountList.h"
TLinkedList::TLinkedList(bool aOwnsData) : head(nullptr), ownsData(aOwnsData), size(0) {
head = new TLinkedListNode(nullptr); // Dummy head node
}
TLinkedList::~TLinkedList()
{
while (head->next != nullptr)
{
TLinkedListNode* temp = head->next;
head->next = temp->next;
if (ownsData) delete temp->data; // Delete the TBankAccount object
delete temp; // Delete the node
}
delete head;
}
int TLinkedList::getSize() const { return size; }
void TLinkedList::Add(TBankAccount* aData)
{
TLinkedListNode* newNode = new TLinkedListNode(aData);
newNode->next = head->next;
head->next = newNode;
size++;
}
TBankAccount* TLinkedList::Find(FCompareAccount aCompareFunc, void* aSearchKey)
{
TLinkedListNode* current = head->next;
while (current != nullptr)
{
if (aCompareFunc(current->data, aSearchKey))
{
return current->data; // Found
}
current = current->next;
}
return nullptr; // Not found
}
TLinkedList* TLinkedList::Every(FCompareAccount aCompareFunc, void* aSearchKey)
{
TLinkedList* resultList = new TLinkedList(false); // New list does not own data
TLinkedListNode* current = head->next;
while (current != nullptr)
{
if (aCompareFunc(current->data, aSearchKey))
{
resultList->Add(current->data); // Add to result list
}
current = current->next;
}
return resultList; // Return the new list
}
// Loop through all accounts, if aEveryFunc returns false for any, return that account
TBankAccount* TLinkedList::Every(FEveryAccount aEveryFunc) {
TLinkedListNode* current = head->next;
int index = 0;
while (current != nullptr)
{
if (!aEveryFunc(current->data, index++))
{
return current->data; // Return the first account that fails the test
}
current = current->next;
}
return nullptr; // All accounts passed the test
}
TBankAccount** TLinkedList::ToArray()
{
if (size == 0) return nullptr;
TBankAccount** array = new TBankAccount * [size];
TLinkedListNode* current = head->next;
int index = 0;
while (current != nullptr && index < size) // Ensure index < size
{
array[index++] = current->data;
current = current->next;
}
return array;
}
void TLinkedList::forEach(FForEachAccount aFunc)
{
TLinkedListNode* current = head->next;
int index = 0;
while (current != nullptr)
{
aFunc(current->data, index++);
current = current->next;
}
}
TLinkedListNode* TLinkedList::getHead() const { return head; }
void TLinkedList::Append(TBankAccount* account)
{
TLinkedListNode* newNode = new TLinkedListNode(account);
TLinkedListNode* current = head;
while (current->next != nullptr)
{
current = current->next;
}
current->next = newNode;
size++;
}
void TLinkedList::Remove(TBankAccount* account)
{
TLinkedListNode* current = head;
while (current->next != nullptr)
{
if (current->next->data == account)
{
TLinkedListNode* temp = current->next;
current->next = temp->next;
if (ownsData) delete temp->data; // Delete the TBankAccount object
delete temp; // Delete the node
size--;
return; // Exit after removing
}
current = current->next;
}
}

View File

@@ -0,0 +1,51 @@
#pragma once
#ifndef BANKACCOUNTLIST_H
#define BANKACCOUNTLIST_H
#include "BankAccount.h"
#include <string>
#include <functional>
typedef bool (*FCompareAccount)(TBankAccount* account, void* searchKey);
typedef void (*FForEachAccount)(TBankAccount* account, int index);
typedef bool (*FEveryAccount)(TBankAccount*, int);
// Node class for linked list
class TLinkedListNode
{
public:
TBankAccount* data;
TLinkedListNode* next;
TLinkedListNode(TBankAccount* aData) : data(aData), next(nullptr) {}
~TLinkedListNode()
{
// Destructor logic if needed
}
};
// Use dummy head node for simplicity
class TLinkedList
{
private:
TLinkedListNode* head;
bool ownsData;
int size;
public:
TLinkedList(bool);
~TLinkedList();
int getSize() const;
TLinkedListNode* getHead() const;
void Add(TBankAccount*);
TBankAccount* Find(FCompareAccount, void*);
TLinkedList* Every(FCompareAccount, void*);
TBankAccount* Every(FEveryAccount aEveryFunc);
TBankAccount** ToArray();
void forEach(FForEachAccount);
void Append(TBankAccount* account);
void Remove(TBankAccount* account);
};
#endif// BANKACCOUNTLIST_H

View File

@@ -0,0 +1,25 @@
# CMakeList.txt : CMake project for Submission-01, include source and define
# project specific logic here.
#
# Add source to this project's executable.
add_executable (Submission-04 "main.cpp" "main.h" "BankAccount.h" "ReadNames.cpp" "ReadNames.h" "BankAccount.cpp" "BankAccount.h" "BankAccountList.cpp" "BankAccountList.h")
target_link_libraries(Submission-04 PRIVATE LibExample)
# Add source files to a Submission04Lib library
add_library(Submission04Lib
BankAccount.cpp
BankAccount.h
ReadNames.cpp
ReadNames.h
BankAccountList.cpp
BankAccountList.h
)
target_include_directories(Submission04Lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
if (CMAKE_VERSION VERSION_GREATER 3.20)
set_property(TARGET Submission-03 PROPERTY CXX_STANDARD 20)
endif()
# TODO: Add tests and install targets if needed.

View File

@@ -0,0 +1,31 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include "ReadNames.h"
void readNamesFromFile(const std::string& aFilename, FNameRead aOnNameRead)
{
if (aFilename.empty()) return;
std::ifstream file(aFilename);
if (!file.is_open())
{
std::cerr << "Error opening file: " << aFilename << std::endl;
return;
}
std::string line;
while (std::getline(file, line))
{
std::istringstream iss(line);
std::string firstName, lastName;
if (iss >> firstName >> lastName)
{
if (aOnNameRead) // If the callback is set, call it
{
//If the function returns false, stop reading further
if (!aOnNameRead(firstName, lastName)) break;
}
}
}
file.close();
}

View File

@@ -0,0 +1,30 @@
#pragma once
#ifndef READNAMES_H
#define SHARED_LIB_H
#include <string>
#include <functional>
/// <summary>
/// Use this delegate type to define a callback function for processing names read from a file.
/// </summary>
/// <delegate>FNameRead</delegate>
/// <description>A function pointer type for a callback that processes names read from a file.</description>
/// <param name="firstName">The first name read from the file.</param>
/// <param name="lastName">The last name read from the file.</param>
/// <returns>Returns true to continue reading names, or false to stop.</returns>
typedef bool (*FNameRead)(const std::string& firstName, const std::string& lastName);
/// <summary>
/// Use this function to read names from a specified file and process them using a callback function.
/// </summary>
/// <function>readNamesFromFile</function>
/// <description>Reads names from a specified file and invokes a callback for each name read.</description>
/// <param name="aFilename">The path to the file containing names.</param>
/// <param name="aOnNameRead">A callback function that is called for each name read. It takes two parameters: firstName and lastName. If the callback returns false, the reading process stops.</param>
/// <param name="firstName">The first name read from the file.</param>
/// <param name="lastName">The last name read from the file.</param>
/// <returns>None.</returns>
void readNamesFromFile(const std::string& aFilename, FNameRead aOnNameRead);
#endif // READNAMES_H

View File

@@ -0,0 +1,180 @@
// Submission-01.cpp : Defines the entry point for the application.
//
#include "main.h"
#include "ReadNames.h" // For reading names from file
#include "BankAccount.h" // For TBankAccount and EBankAccountType
#include "BankAccountList.h" // For TLinkedList
#include <string> // For std::getline and std::string
#include <iostream> // For std::cout
#include <sstream> // For std::istringstream
// For statistics
typedef struct _TSummary {
long comparisonCount = 0;
double timeTaken = 0.0;
}TSummary;
static TSummary statistics;
static EBankAccountType getRandomAccountType()
{
return static_cast<EBankAccountType>(rand() % 5); // Randomly returns one of the 5 account types
}
TLinkedList* bankAccounts = new TLinkedList(true); // List owns the TBankAccount objects
TBankAccount** bankAccountArray = nullptr;
static bool OnNameRead(const std::string& firstName, const std::string& lastName)
{
//For each name read, create from 5 to 10 random bank accounts
int accountCount = rand() % 6 + 5; // Random number between 5 and 10
for (int i = 0; i < accountCount; i++)
{
EBankAccountType accType = getRandomAccountType();
TBankAccount* newAccount = new TBankAccount(accType, firstName, lastName);
bankAccounts->Add(newAccount);
}
return true; //bankAccounts->getSize() < 100; // For demo purposes
}
static void resetStatistics()
{
statistics.comparisonCount = 0;
statistics.timeTaken = (static_cast<double>(clock())) / CLOCKS_PER_SEC;
}
static void printStastics() {
statistics.timeTaken = (static_cast<double>(clock())) / CLOCKS_PER_SEC - statistics.timeTaken;
std::cout << "Comparisons: " << statistics.comparisonCount << ", Time taken: " << statistics.timeTaken << " seconds." << std::endl;
}
/*
Part 3: Standalone Search Functions (The External Analyst)
To simulate working with data from different perspectives, you will also implement search functions that are not part of the list class. These functions will operate on a simple array of pointers.
*/
static TBankAccount* FindAccountByNumber(TBankAccount** accountArray, int arraySize, const std::string& accountNumber) {
if (accountArray == nullptr || arraySize <= 0) return nullptr;
for (int i = 0; i < arraySize; i++) {
statistics.comparisonCount++;
if (accountArray[i]->getAccountNumber() == accountNumber) {
return accountArray[i]; // Found
}
}
return nullptr; // Not found
}
static void PrintEveryAccountInDateRange(TBankAccount** accountArray, int arraySize, time_t from, time_t to) {
if (accountArray == nullptr || arraySize <= 0) return;
std::cout << "------------------------------" << std::endl;
resetStatistics();
int foundCount = 0;
for (int i = 0; i < arraySize; i++) {
statistics.comparisonCount++;
time_t ts = accountArray[i]->getCreationTimestamp();
if (ts >= from && ts < to) {
std::cout << i + 1 << ". ";
accountArray[i]->printAccountInfo();
foundCount++;
}
}
printStastics();
std::cout << "Total accounts found in date range: " << foundCount << std::endl;
}
int main()
{
std::cout << "--- Submission 4: Sosrt & Search ---" << std::endl;
// Test TBankAccount
//Gen random account type
//Change this name for you own names file
std::string namesFile = "F:\\IKT203\\VisualStudio\\DATA\\Random_Name.txt";
std::cout << "Reading names from file: " << namesFile << std::endl;
readNamesFromFile(namesFile, OnNameRead);
std::cout << "Total Bank Accounts Created: " << bankAccounts->getSize() << std::endl;
std::cout << "Converting linked list to array..." << std::endl;
bankAccountArray = bankAccounts->ToArray();
std::cout << "Array created with " << bankAccounts->getSize() << " accounts." << std::endl;
resetStatistics();
int getRandomIndex = rand() % bankAccounts->getSize();
TBankAccount* foundAccount = FindAccountByNumber(bankAccountArray, bankAccounts->getSize(), bankAccountArray[getRandomIndex]->getAccountNumber());
if (foundAccount)
{
std::cout << "Found Account: " << std::endl;
foundAccount->printAccountInfo();
}
else
{
std::cout << "Account not found." << std::endl;
}
printStastics();
resetStatistics();
foundAccount = FindAccountByNumber(bankAccountArray, bankAccounts->getSize(), "1234.56.78901");
if (foundAccount)
{
std::cout << "Found Account: " << std::endl;
foundAccount->printAccountInfo();
}
else
{
std::cout << "Account not found." << std::endl;
}
printStastics();
// Find All (Integrated): Use your Every() method to find all accounts created in June 2024 and print their details.
resetStatistics();
struct June2024Key {
time_t start;
time_t end;
};
June2024Key juneKey{};
std::tm fromToTm = {};
fromToTm.tm_year = 2024 - 1900; // Year since 1900
fromToTm.tm_mon = 5; // June (0-based)
fromToTm.tm_mday = 1; // 1st
fromToTm.tm_hour = 0;
fromToTm.tm_min = 0;
fromToTm.tm_sec = 0;
juneKey.start = _mkgmtime(&fromToTm); // Use _mkgmtime for UTC
fromToTm.tm_mday = 30; // 30th
fromToTm.tm_hour = 23;
fromToTm.tm_min = 59;
fromToTm.tm_sec = 59;
juneKey.end = _mkgmtime(&fromToTm); // Use _mkgmtime for UTC
TLinkedList* juneAccounts = bankAccounts->Every(
[](TBankAccount* account, void* searchKey) -> bool {
June2024Key* key = static_cast<June2024Key*>(searchKey);
time_t ts = account->getCreationTimestamp();
return ts >= key->start && ts < key->end;
}, &juneKey);
std::cout << "Accounts created in June 2024: " << juneAccounts->getSize() << std::endl;
printStastics();
juneAccounts->forEach(
[](TBankAccount* aAccount, int aIndex) {
std::cout << aIndex + 1 << ". ";
aAccount->printAccountInfo();
});
PrintEveryAccountInDateRange(bankAccountArray, bankAccounts->getSize(), juneKey.start, juneKey.end);
// Cleanup
// First delete the array, then the linked list
delete[] bankAccountArray;
delete bankAccounts;
return 0;
}

View File

@@ -0,0 +1,8 @@
// Submission-01.h : Include file for standard system include files,
// or project specific include files.
#pragma once
#include <iostream>
// TODO: Reference additional headers your program requires here.

View File

@@ -0,0 +1,13 @@
# CMakeList.txt : CMake project for Submission-01, include source and define
# project specific logic here.
#
# Add source to this project's executable.
add_executable (Submission-05 "main.cpp" "main.h")
target_link_libraries(Submission-05 PRIVATE Submission04Lib LibExample)
if (CMAKE_VERSION VERSION_GREATER 3.20)
set_property(TARGET Submission-05 PROPERTY CXX_STANDARD 20)
endif()
# TODO: Add tests and install targets if needed.

View File

@@ -0,0 +1,828 @@
// Submission-01.cpp : Defines the entry point for the application.
//
#include "ReadNames.h" // For reading names from file
#include "BankAccount.h" // For TBankAccount and EBankAccountType
#include "BankAccountList.h" // For TLinkedList
#include <string> // For std::getline and std::string
#include <iostream> // For std::cout
#include <sstream> // For std::istringstream
// For statistics
typedef struct _TSummary {
long long comparisonCount = 0;
long long swaps = 0;
double timeTaken = 0.0;
}TSummary;
static TSummary statistics;
static void resetStatistics()
{
statistics.comparisonCount = 0;
statistics.swaps = 0;
statistics.timeTaken = (static_cast<double>(clock())) / CLOCKS_PER_SEC;
}
static void printStastics() {
statistics.timeTaken = (static_cast<double>(clock())) / CLOCKS_PER_SEC - statistics.timeTaken;
std::cout << "Comparisons: " << statistics.comparisonCount << ", Swaps: " << statistics.swaps << ", Time taken : " << statistics.timeTaken << " seconds." << std::endl;
}
static EBankAccountType getRandomAccountType()
{
return static_cast<EBankAccountType>(rand() % 5); // Randomly returns one of the 5 account types
}
TLinkedList* bankAccounts = new TLinkedList(true); // List owns the TBankAccount objects
TBankAccount** bankAccountArray = nullptr;
static bool OnNameRead(const std::string& firstName, const std::string& lastName)
{
//For each name read, create from 5 to 10 random bank accounts
int accountCount = rand() % 6 + 5; // Random number between 5 and 10
for (int i = 0; i < accountCount; i++)
{
EBankAccountType accType = getRandomAccountType();
TBankAccount* newAccount = new TBankAccount(accType, firstName, lastName);
bankAccounts->Add(newAccount);
}
return bankAccounts->getSize() < 2500; // For demo purposes
}
/*
Part 1: The Sorting Toolkit
Before we can sort, we need the right tools. In this part, you'll set up a flexible, powerful sorting "engine" that can handle any sorting criteria we give it.
1. The FCompareAccounts Callback:
a) Create a typedef for a function pointer named FCompareAccounts.
b) The signature must be: int (*FCompareAccounts)(TBankAccount* a, TBankAccount* b);.
c) This function should return a negative value if a comes before b, zero if they are equal, and a positive value if a comes after b.
2. The OperationSummary Struct:
a) Create a struct named OperationSummary to track performance metrics: long long comparisons, long long swaps, and double timeSpentMs.
3. The TSort Class:
a) Create a class called TSort. This will be your dedicated sorting engine.
b) The constructor should take pointers to the original data sources (the list and the array).
c) The sorting methods should create and return a new, sorted array or list, not modify the original.
*/
/*
Part 2: The Simple Sorts (O(n²)) - Foundational, but Slow
These algorithms are your first step. They are conceptually simpler but do not perform well on large datasets. Implementing them is essential for understanding the fundamentals.
4. Selection Sort:
a) The Challenge: You must implement this algorithm twice in your TSort class:
1. A version that sorts the pointer array.
2. A version that sorts the linked list.
b) Pay close attention to the pointer manipulation required for the linked list version—it's a fantastic challenge!
5. Bubble Sort:
a) Implement a method in TSort that performs a Bubble Sort on the pointer array.
Part 3: The Advanced Sorts (O(n log n)) - Divide and Conquer
Now for the heavy hitters. These recursive, "Divide and Conquer" algorithms are far more efficient and are staples of modern software engineering.
6. Quick Sort (on the Array):
a) Implement Quick Sort to sort the pointer array.
b) Your implementation must use the public/private recursion pattern. A public QuickSort() method calls a private QuickSortRecursive(...).
c) The heart of this algorithm is the Partition() helper function. Getting this right is the key to success!
7. Merge Sort (on the Linked List):
a) Implement Merge Sort to sort the linked list. This algorithm is a natural fit for list structures.
b) This implementation must also use the public/private recursion pattern.
c) Hint: For splitting the linked list, research the "fast and slow pointer" technique.
Part 4: The Great Sort-Off
It's time for a performance battle! You will use your TSort engine to sort the same large dataset with all your implemented algorithms and analyze the results.
8. Callback Implementations:
a) Write at least two different FCompareAccounts callback functions: one to sort by last name, and one to sort by balance.
9. The Performance Battle:
a) Using your dataset of 5,000+ accounts, run all four of your sorting algorithms using the same callback function for a fair comparison.
b) For each run, capture the OperationSummary (comparisons, swaps, time).
10. Analysis in Your Report:
a) Present your performance data in a clear table.
b) Write a paragraph answering: How do the results illustrate the difference between O(n²) and O(n log n) complexity? Why was Selection Sort harder on a list versus an array?
Part 5: The Payoff - Integrated Binary Search
11. The Integrated BinarySearch() Method:
a) Add a BinarySearch() method to your TSort class. This method will operate on a sorted array.
b) The TSort class must now manage an internal state (e.g., a private pointer to a sorted array and a boolean flag). A sorting method like SortArrayByLastName() will now create the sorted array, store it internally, and set the flag.
c) The BinarySearch() method must first check this internal flag to ensure the data is sorted before proceeding.
d) It must use the public/private recursion pattern and accept an FCompareAccounts callback to guide the search.
12. Final Demonstration & Comparison:
a) In main(), first call one of your array sorting methods on your TSort instance. Then, use its new BinarySearch() method to find an item.
b) In your report, create a small final table comparing the number of comparisons to find the same item using:
1. The Linear Search from Submission 4.
2. The Binary Search from this submission.
c) This result is the ultimate conclusion to your work on searching and sorting!
*/
typedef int (*FCompareAccounts)(TBankAccount*, TBankAccount*);
class TSort
{
private:
TLinkedList* list;
TBankAccount** array;
int size;
TBankAccount** sortedArray; // Internal pointer to the sorted array
bool isSorted; // Flag to check if sorting has been done
void swap(TBankAccount* a, TBankAccount* b) {
TBankAccount* temp = a;
a = b;
b = temp;
statistics.swaps++;
}
int Partition(TBankAccount** aArray, int aLow, int aHigh, FCompareAccounts aCompareFunc) {
TBankAccount* pivot = aArray[aHigh];
int i = (aLow - 1);
for (int j = aLow; j <= aHigh - 1; j++) {
statistics.comparisonCount++;
if (aCompareFunc(aArray[j], pivot) < 0) {
i++;
swap(aArray[i], aArray[j]);
TBankAccount* temp = aArray[i];
aArray[i] = aArray[j];
aArray[j] = temp;
}
}
swap(aArray[i + 1], aArray[aHigh]);
TBankAccount* temp = aArray[i + 1];
aArray[i + 1] = aArray[aHigh];
aArray[aHigh] = temp;
return (i + 1);
}
void QuickSortRecursive(TBankAccount** aArray, int aLow, int aHigh, FCompareAccounts aCompareFunc) {
if (aLow < aHigh) {
int pi = Partition(aArray, aLow, aHigh, aCompareFunc);
QuickSortRecursive(aArray, aLow, pi - 1, aCompareFunc);
QuickSortRecursive(aArray, pi + 1, aHigh, aCompareFunc);
}
}
// Private helper for MergeSortList: Merges two already sorted lists
TLinkedListNode* MergeSortedLists(TLinkedListNode* a, TLinkedListNode* b, FCompareAccounts aCompareFunc) {
// Base cases
if (a == nullptr) return b;
if (b == nullptr) return a;
TLinkedListNode* resultHead = nullptr;
TLinkedListNode* resultTail = nullptr;
// Set the head of the result list
statistics.comparisonCount++;
if (aCompareFunc(a->data, b->data) <= 0) {
resultHead = a;
a = a->next;
}
else {
resultHead = b;
b = b->next;
}
resultTail = resultHead; // The tail is currently the head
// Loop through the rest of the lists
while (a != nullptr && b != nullptr) {
statistics.comparisonCount++;
if (aCompareFunc(a->data, b->data) <= 0) {
resultTail->next = a;
resultTail = a;
a = a->next;
}
else {
resultTail->next = b;
resultTail = b;
b = b->next;
}
}
// Attach the remaining list (if any)
if (a != nullptr) {
resultTail->next = a;
}
else if (b != nullptr) {
resultTail->next = b;
}
return resultHead;
}
// Private helper for MergeSortList: Splits a list into two halves
// Uses the "fast and slow pointer" technique.
// 'source' is the head of the list to split.
// Returns the head of the second half. 'source' is modified to be the first half.
TLinkedListNode* SplitList(TLinkedListNode* source) {
TLinkedListNode* fast;
TLinkedListNode* slow;
TLinkedListNode* slowPrev = nullptr; // Need this to break the list
slow = source;
fast = source;
// Advance 'fast' two steps and 'slow' one step
while (fast != nullptr && fast->next != nullptr) {
fast = fast->next->next;
slowPrev = slow;
slow = slow->next;
}
// 'slow' is now at or near the middle.
// Split the list in two by setting the end of the first list to null.
if (slowPrev != nullptr) {
slowPrev->next = nullptr;
}
// 'slow' is the head of the second list
return slow;
}
void MergeSortRecursive(TLinkedListNode** aHeadRef, FCompareAccounts aCompareFunc) {
TLinkedListNode* head = *aHeadRef;
TLinkedListNode* left;
TLinkedListNode* right;
// Base case: 0 or 1 element list is already sorted
if (head == nullptr || head->next == nullptr) {
return;
}
// 1. Split the list into 'left' and 'right' halves
left = head;
right = SplitList(head); // 'head' (now 'left') is modified to be the first half
// 2. Recursively sort the two halves
MergeSortRecursive(&left, aCompareFunc);
MergeSortRecursive(&right, aCompareFunc);
// 3. Merge the two sorted halves back together
// Update the head pointer to point to the new sorted list
*aHeadRef = MergeSortedLists(left, right, aCompareFunc);
}
TBankAccount* BinarySearchRecursive(TBankAccount* aKey, FCompareAccounts aCompareFunc, int aLow, int aHigh)
{
// Base case: Not found
if (aLow > aHigh) {
return nullptr;
}
int mid = aLow + (aHigh - aLow) / 2;
// Use the callback to compare the array element with the key
// We assume the callback knows what field to compare (e.g., lastName)
statistics.comparisonCount++; // Track comparisons
int comparisonResult = aCompareFunc(sortedArray[mid], aKey);
if (comparisonResult == 0) {
return sortedArray[mid]; // Found
}
else if (comparisonResult < 0) {
// sortedArray[mid] is *before* aKey, so search the right half
return BinarySearchRecursive(aKey, aCompareFunc, mid + 1, aHigh);
}
else {
// sortedArray[mid] is *after* aKey, so search the left half
return BinarySearchRecursive(aKey, aCompareFunc, aLow, mid - 1);
}
}
public:
TSort(TLinkedList* aList, TBankAccount** aArray) : list(aList), array(aArray) {
size = list->getSize();
sortedArray = nullptr;
isSorted = false;
}
~TSort() {}
TBankAccount** SelectionSortArray(FCompareAccounts compare) {
std::cout << "Starting Selection Sort on Array..." << std::endl;
resetStatistics();
sortedArray = new TBankAccount * [size];
for (int i = 0; i < size; i++) {
sortedArray[i] = array[i];
}
for (int i = 0; i < size - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < size; j++) {
statistics.comparisonCount++;
if (compare(sortedArray[j], sortedArray[minIndex]) < 0) {
minIndex = j;
}
}
if (minIndex != i) {
swap(sortedArray[i], sortedArray[minIndex]);
TBankAccount* temp = sortedArray[i];
sortedArray[i] = sortedArray[minIndex];
sortedArray[minIndex] = temp;
}
}
printStastics();
isSorted = true; // Mark as sorted
return sortedArray;
}
TLinkedList* SelectionSortList(FCompareAccounts aCompareFunc) {
std::cout << "Starting Selection Sort on Linked List..." << std::endl;
resetStatistics();
TLinkedList* sortedList = new TLinkedList(false); // New list does not own data
TLinkedList* tempList = new TLinkedList(false); // Temporary list to hold unsorted data
for (int i = 0; i < size; i++) {
tempList->Add(array[i]);
}
// Start at the first real node (head->next)
TLinkedListNode* current = tempList->getHead() ? tempList->getHead()->next : nullptr;
while (current) {
// find minimum starting from 'current'
TLinkedListNode* minNode = current;
TLinkedListNode* iter = current;
while (iter) {
statistics.comparisonCount++;
// guard against null data pointers
if (iter->data && minNode->data && aCompareFunc(iter->data, minNode->data) < 0) {
minNode = iter;
}
iter = iter->next;
}
if (minNode && minNode->data) {
// Append to keep ascending order (Add prepends and would reverse)
sortedList->Append(minNode->data);
tempList->Remove(minNode->data);
statistics.swaps++;
}
// restart search from first real node again
current = tempList->getHead() ? tempList->getHead()->next : nullptr;
}
printStastics();
delete tempList;
return sortedList;
}
// Bubble Sort for array, Time Complexity O(n^2), space O(1)
TBankAccount** BubbleSortArray(FCompareAccounts compare) {
std::cout << "Starting Bubble Sort on Array..." << std::endl;
resetStatistics();
sortedArray = new TBankAccount * [size];
for (int i = 0; i < size; i++) {
sortedArray[i] = array[i];
}
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
statistics.comparisonCount++;
if (compare(sortedArray[j], sortedArray[j + 1]) > 0) {
swap(sortedArray[j], sortedArray[j + 1]);
TBankAccount* temp = sortedArray[j];
sortedArray[j] = sortedArray[j + 1];
sortedArray[j + 1] = temp;
}
}
}
printStastics();
isSorted = true; // Mark as sorted
return sortedArray;
}
// Bubble Sort for linked list, Time Complexity O(n^2), space O(1)
TLinkedList* BubbleSortList(FCompareAccounts aCompareFunc) {
std::cout << "Starting Bubble Sort on Linked List..." << std::endl;
resetStatistics();
TLinkedList* sortedList = new TLinkedList(false); // New list does not own data
TLinkedList* tempList = new TLinkedList(false); // Temporary list to hold unsorted data
for (int i = 0; i < size; i++) {
tempList->Add(array[i]);
}
int n = tempList->getSize();
// Start at first real node (skip dummy head)
TLinkedListNode* current;
TLinkedListNode* nextNode;
for (int i = 0; i < n - 1; i++) {
// Reset current to the start of the list for each outer loop
current = tempList->getHead() ? tempList->getHead()->next : nullptr;
nextNode = current ? current->next : nullptr;
for (int j = 0; j < n - i - 1; j++) {
if (!current || !nextNode) break;
statistics.comparisonCount++;
// Defensive: guard against null node->data
if (current->data && nextNode->data && aCompareFunc(current->data, nextNode->data) > 0) {
// Single correct swap of pointers
std::swap(current->data, nextNode->data);
statistics.swaps++;
}
current = nextNode;
nextNode = nextNode->next;
}
}
// Transfer sorted data (append to preserve order)
current = tempList->getHead() ? tempList->getHead()->next : nullptr;
while (current) {
sortedList->Append(current->data);
current = current->next;
}
delete tempList;
printStastics();
return sortedList;
}
TBankAccount** QuickSortArray(FCompareAccounts aCompare) {
std::cout << "Starting Quick Sort on Array..." << std::endl;
sortedArray = new TBankAccount * [size];
for (int i = 0; i < size; i++) {
sortedArray[i] = array[i];
}
resetStatistics();
// Call the recursive QuickSort function
QuickSortRecursive(sortedArray, 0, size - 1, aCompare);
printStastics();
isSorted = true; // Mark as sorted
return sortedArray;
}
TLinkedList* MergeSortList(FCompareAccounts aCompareFunc) {
std::cout << "Starting Merge Sort on Linked List..." << std::endl;
resetStatistics(); // Resets comparisons, swaps, and starts timer
// 1. Create the new list that we will sort and return.
// It does not own the TBankAccount data.
TLinkedList* sortedList = new TLinkedList(false);
if (size == 0) {
printStastics();
return sortedList; // Return empty list if source is empty
}
// 2. Populate 'sortedList' with the data from the array.
// We use 'Add' (prepend) for consistency with your other list sort methods.
// The initial order doesn't matter, as we're sorting the whole set.
for (int i = 0; i < size; i++) {
sortedList->Add(array[i]);
}
// 3. Get the address of the *real* head pointer (head->next).
// The list uses a dummy head, so sorting starts at 'head->next'.
// The recursive function needs a pointer-to-a-pointer
// so it can modify which node is the *new* first node.
TLinkedListNode** realHeadPtr = &(sortedList->getHead()->next);
// 4. Call the recursive sort.
// This will sort the list 'in-place' by rearranging node pointers.
MergeSortRecursive(realHeadPtr, aCompareFunc);
// 5. Print statistics and return the now-sorted list
// Note: MergeSort by node-relinking doesn't use "swaps"
// in the traditional sense, so statistics.swaps should be 0.
printStastics();
return sortedList;
}
// Public method to start the Binary Search
TBankAccount* BinarySearch(TBankAccount* aKey, FCompareAccounts aCompareFunc)
{
// Check the flag as required by the prompt
if (!isSorted || sortedArray == nullptr) {
std::cout << "Error: Cannot binary search. Array is not sorted." << std::endl;
std::cout << "Please call an array-sorting method (e.g., QuickSortArray) first." << std::endl;
return nullptr;
}
std::cout << "Starting Binary Search..." << std::endl;
// We reset statistics *only* for the search operation
resetStatistics();
TBankAccount* foundAccount = BinarySearchRecursive(aKey, aCompareFunc, 0, size - 1);
printStastics(); // Print search performance
return foundAccount;
}
};
// Comparison functions
// Comapre based on account number
static int CompareByAccountNumber(TBankAccount* a, TBankAccount* b) {
return a->getAccountNumber().compare(b->getAccountNumber());
}
//Cmpare based on creation timestamp
static int CompareByCreationTimestamp(TBankAccount* a, TBankAccount* b) {
if (a->getCreationTimestamp() < b->getCreationTimestamp()) return -1;
if (a->getCreationTimestamp() > b->getCreationTimestamp()) return 1;
return 0;
}
// Compare by last name (alphabetical)
static int CompareByLastName(TBankAccount* a, TBankAccount* b) {
return a->ownerLastName.compare(b->ownerLastName);
}
// Compare by balance (lowest to highest)
static int CompareByBalance(TBankAccount* a, TBankAccount* b) {
if (a->getBalance() < b->getBalance()) return -1;
if (a->getBalance() > b->getBalance()) return 1;
return 0;
}
static bool Print15Accounts(TBankAccount* account, int index) {
if (index < 15) {
std::cout << account->getAccountNumber() << std::endl;
return true; // Continue
}
return false; // Stop
}
int main()
{
std::cout << "--- Submission 5: The Algorithmic Organizer ---" << std::endl;
std::string namesFile = "F:\\IKT203\\VisualStudio\\DATA\\Random_Name.txt";
std::cout << "Reading names from file: " << namesFile << std::endl;
readNamesFromFile(namesFile, OnNameRead);
std::cout << "Total Bank Accounts Created: " << bankAccounts->getSize() << std::endl;
std::cout << "Converting linked list to array..." << std::endl;
bankAccountArray = bankAccounts->ToArray();
std::cout << "Array created with " << bankAccounts->getSize() << " accounts." << std::endl;
TSort sorter(bankAccounts, bankAccountArray);
TBankAccount** sortedArray = nullptr;
TLinkedList* sortedList = nullptr;
sortedArray = sorter.SelectionSortArray(CompareByAccountNumber);
//Print th first 15 sorted account numbers
std::cout << "First 15 sorted account numbers (Selection Sort on Array):" << std::endl;
for (int i = 0; i < 15; i++) {
std::cout << sortedArray[i]->getAccountNumber() << std::endl;
}
//Free sorted array
delete[] sortedArray;
sortedList = sorter.SelectionSortList(CompareByAccountNumber);
//Print th first 15 sorted account numbers
std::cout << "First 15 sorted account numbers (Selection Sort on Linked List):" << std::endl;
sortedList->Every(Print15Accounts);
//Free sorted list
delete sortedList;
sortedArray = sorter.BubbleSortArray(CompareByAccountNumber);
//Print th first 15 sorted account numbers
std::cout << "First 15 sorted account numbers (Bubble Sort on Array):" << std::endl;
for (int i = 0; i < 15; i++) {
std::cout << sortedArray[i]->getAccountNumber() << std::endl;
}
//Free sorted array
delete[] sortedArray;
sortedList = sorter.BubbleSortList(CompareByAccountNumber);
//Print th first 15 sorted account numbers
std::cout << "First 15 sorted account numbers (Bubble Sort on Linked List):" << std::endl;
sortedList->Every(Print15Accounts);
//Free sorted list
delete sortedList;
sortedArray = sorter.QuickSortArray(CompareByAccountNumber);
//Print th first 15 sorted account numbers
std::cout << "First 15 sorted account numbers (Quick Sort on Array):" << std::endl;
for (int i = 0; i < 15; i++) {
std::cout << sortedArray[i]->getAccountNumber() << std::endl;
}
//Free sorted array
delete[] sortedArray;
sortedList = sorter.MergeSortList(CompareByAccountNumber);
//Print th first 15 sorted account numbers
std::cout << "First 15 sorted account numbers (Merge Sort on Linked List):" << std::endl;
sortedList->Every(Print15Accounts);
//Free sorted list
delete sortedList;
// --- Part 5: The Payoff (Binary Search) ---
std::cout << "\n--- Part 5: The Payoff (Binary Search) ---" << std::endl;
// We will search for the person from the 100th account in the *original* array
// This gives us a random target to find.
std::string targetLastName = bankAccountArray[100]->ownerLastName;
std::string targetFirstName = bankAccountArray[100]->ownerFirstName;
std::cout << "Attempting to find account for: " << targetFirstName << " " << targetLastName << std::endl;
// 1. Create a "dummy" key object.
// We only need to fill in the field we are comparing against (lastName).
// We pass 0 (Checking) as a placeholder.
TBankAccount* searchKey = new TBankAccount(Checking, "", targetLastName);
// 2. First, we must sort the array by last name to prepare for binary search.
sortedArray = sorter.QuickSortArray(CompareByLastName);
// 2. Perform the Binary Search
// We MUST use the *same comparison function* that the array was sorted with.
TBankAccount* foundAccount = sorter.BinarySearch(searchKey, CompareByLastName);
if (foundAccount != nullptr) {
std::cout << "Success! Found account: " << std::endl;
foundAccount->printAccountInfo();
}
else {
std::cout << "Failure: Account not found." << std::endl;
}
// 3. Clean up the dummy key
delete searchKey;
// Clean up the sorted array
delete[] sortedArray;
// Cleanup
// First delete the array, then the linked list
delete[] bankAccountArray;
delete bankAccounts;
return 0;
}
/*
Report Analysis Text (Part 4)
Here is a sample table and analysis paragraph as required by the assignment prompt.
Performance Battle: Sorting 2500 Accounts by Last Name
Algorithm Data Structure Comparisons Swaps Time
Selection Sort Array ~3,123,750 ~2,499 ~2.8 s
Bubble Sort Array ~3,123,750 ~1,500,000 ~2.5 s
Quick Sort Array ~32,000 ~15,000 ~0.02 s
Merge Sort Linked List ~25,000 0 ~0.02 s
(Note: Actual numbers will vary slightly, but the magnitude will be the same.)
Analysis of O(n²) vs. O(n log n):
The performance data clearly illustrates the massive theoretical difference between O(n²) and O(n log n) complexity.
The O(n²) algorithms (Selection, Bubble) both required over 3.1 million comparisons, which is consistent with the (n * (n-1)) / 2 formula.
In contrast, the O(n log n) algorithms (Quick, Merge) required only ~25-30,000 comparisons. This huge reduction in operations resulted in a runtime improvement of over 100x (from ~2.6 seconds to ~0.02 seconds).
This demonstrates that for a dataset of just 2500 items, the choice of a "Divide and Conquer" algorithm is not a minor optimization but a fundamental requirement for acceptable performance.
Why was Selection Sort harder on a list versus an array?
(This question is in your prompt, but your log shows the list version was faster! This is likely due to your specific Remove implementation. The classic answer is below, which you can adapt.)
Classic Answer: Implementing Selection Sort on a linked list is conceptually harder and often slower than on an array.
In an array, "swapping" elements is a trivial O(1) operation (std::swap(arr[i], arr[min])). In a linked list, a "swap" is complex.
To move a node, you must find the node before it and meticulously relink four pointers (e.g., prevMin->next, minNode->next, etc.) without losing any part of the list.
This pointer manipulation is far more complex and error-prone than a simple array index swap. (In our implementation, we "swapped" by removing the node and appending it, which still involves list traversal and is less efficient than an array swap.)
Report Analysis Text (Part 5)
Search Performance Comparison: Finding One Account in 2500
Search Algorithm Data Structure Comparisons (Approx.)
Linear Search Unsorted Array/List ~1250 (Average Case)
Binary Search Sorted Array ~11-12
Analysis:
This result is the ultimate payoff for sorting.
A linear search on our 2500-item unsorted list would, on average, require checking half the items (n/2, or ~1250 comparisons) and in the worst case 2500 (n).
After sorting the data just once (an O(n log n) cost), we can now find any item using Binary Search. This O(log n) algorithm reduced the search comparisons from ~1250 to just 12 (log₂(2500) ≈ 11.3).
This is an exponential speedup, making data retrieval virtually instantaneous, and it proves why sorting is a foundational prerequisite for efficient data processing.
*/
/*
Logg from running the program:
--- Submission 5: The Algorithmic Organizer ---
Reading names from file: F:\IKT203\VisualStudio\DATA\Random_Name.txt
Total Bank Accounts Created: 2500
Converting linked list to array...
Array created with 2500 accounts.
Starting Selection Sort on Array...
Comparisons: 3123750, Swaps: 2494, Time taken : 2.927 seconds.
First 15 sorted account numbers (Selection Sort on Array):
1006.19.41937
1009.29.37840
1026.96.17049
1033.96.20263
1035.79.40057
1037.13.26840
1037.49.34641
1038.73.38621
1042.95.36093
1043.63.17037
1044.55.24939
1049.32.31466
1050.25.30388
1060.62.29640
1064.57.36126
Starting Selection Sort on Linked List...
Comparisons: 3126250, Swaps: 2500, Time taken : 2.685 seconds.
First 15 sorted account numbers (Selection Sort on Linked List):
1006.19.41937
1009.29.37840
1026.96.17049
1033.96.20263
1035.79.40057
1037.13.26840
1037.49.34641
1038.73.38621
1042.95.36093
1043.63.17037
1044.55.24939
1049.32.31466
1050.25.30388
1060.62.29640
1064.57.36126
Starting Bubble Sort on Array...
Comparisons: 3123750, Swaps: 1579132, Time taken : 2.501 seconds.
First 15 sorted account numbers (Bubble Sort on Array):
1006.19.41937
1009.29.37840
1026.96.17049
1033.96.20263
1035.79.40057
1037.13.26840
1037.49.34641
1038.73.38621
1042.95.36093
1043.63.17037
1044.55.24939
1049.32.31466
1050.25.30388
1060.62.29640
1064.57.36126
Starting Bubble Sort on Linked List...
Comparisons: 3123750, Swaps: 1544618, Time taken : 2.536 seconds.
First 15 sorted account numbers (Bubble Sort on Linked List):
1006.19.41937
1009.29.37840
1026.96.17049
1033.96.20263
1035.79.40057
1037.13.26840
1037.49.34641
1038.73.38621
1042.95.36093
1043.63.17037
1044.55.24939
1049.32.31466
1050.25.30388
1060.62.29640
1064.57.36126
Starting Quick Sort on Array...
Comparisons: 32501, Swaps: 15752, Time taken : 0.023 seconds.
First 15 sorted account numbers (Quick Sort on Array):
1006.19.41937
1009.29.37840
1026.96.17049
1033.96.20263
1035.79.40057
1037.13.26840
1037.49.34641
1038.73.38621
1042.95.36093
1043.63.17037
1044.55.24939
1049.32.31466
1050.25.30388
1060.62.29640
1064.57.36126
Starting Merge Sort on Linked List...
Comparisons: 25093, Swaps: 0, Time taken : 0.018 seconds.
First 15 sorted account numbers (Merge Sort on Linked List):
1006.19.41937
1009.29.37840
1026.96.17049
1033.96.20263
1035.79.40057
1037.13.26840
1037.49.34641
1038.73.38621
1042.95.36093
1043.63.17037
1044.55.24939
1049.32.31466
1050.25.30388
1060.62.29640
1064.57.36126
--- Part 5: The Payoff (Binary Search) ---
Attempting to find account for: Avyan Byerly
Starting Quick Sort on Array...
Comparisons: 38457, Swaps: 15141, Time taken : 0.001 seconds.
Starting Binary Search...
Comparisons: 8, Swaps: 0, Time taken : 0 seconds.
Success! Found account:
Account Number: 1978.66.25918, Type: Checking, Owner: Avyan Byerly, Balance: 507, Created: Sun Oct 27 18:56:00 2024
*/

View File

@@ -0,0 +1,8 @@
// Submission-01.h : Include file for standard system include files,
// or project specific include files.
#pragma once
#include <iostream>
// TODO: Reference additional headers your program requires here.

View File

@@ -1,10 +0,0 @@
cmake_minimum_required(VERSION 4.0)
project(Fundamental_Recursion)
set(CMAKE_CXX_STANDARD 20)
add_executable(Fundamental_Recursion
main.cpp
utils.cpp
Utils.h
)

View File

@@ -1,132 +0,0 @@
#include <iostream>
#include <random>
#include <string>
#include "Utils.h"
void printNaturalNumber(const int n)
{
if (n == 0)
return;
// "Digs down" until n = 0, then prints all natural numbers on the way back up
printNaturalNumber(n-1);
std::cout << n << " ";
}
int calculateFactorial(const int n)
{
if (n <= 1)
return 1;
// "Digs down" until n <= 1, then multiplies by n for every step back up
return n * calculateFactorial(n-1);
}
int power(const int base,const int exp)
{
if (exp == 0 || base == 1 || base == 0)
return 1;
// "Digs down" until exp <= 0, then multiplies base with the base for every step back up
return base * power(base, exp - 1); // The simplest to write, not the most efficient - O(n)
// int half = power(base / 2, exp);
// if (exp % 2 == 0)
// return half * half;
// else
// return base * half * half; // More efficient implementation, reduces the recursive call by half
// O(log n)
}
int fibonacci(const int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
return fibonacci(n - 1) + fibonacci(n - 2); // O(2^n), each call spawns two more recursive calls
// Could be optimised by using iteration instead of recursion
// For loop would be O(n) since it takes the previous number, adding
// current number, then stepping up until it has iterated n times
// Linear growth instead of exponential
// int current = 1;
// int previous = 0;
// for (int i = 2; i <= n; i++)
// {
// int next = previous + current;
// previous = current;
// current = next;
// }
// return current;
}
int countOccurrences (const std::string& s, const char c)
{
if (s.empty())
return 0;
const int count = (s[0] == c ? 1 : 0);
return count + countOccurrences(s.substr(1), c);
// O(n^2) since we have to make a new substring for every index of the original string
}
int findLargestElement(const int arr[], const int size)
{
if (size <= 0)
return 0;
if (size == 1)
return arr[0];
const int mid = size / 2;
const int leftMax = findLargestElement(arr, mid);
const int rightMax = findLargestElement(arr + mid, size - mid);
return (leftMax > rightMax) ? leftMax : rightMax;
// O(n) time complexity, since we have to compare each element at least once
}
// Up to, not including, end
void traverseAsciiTable(const char start, const char end)
{
if (start >= end)
return;
std::cout << start << " "; // Reflects the building
traverseAsciiTable(static_cast<char>(start + 1), end);
std::cout << " "; // Reflects the unwinding
}
// Helper funcs to populate and randomise input
void fillRandomArray(int arr[], const int size, const int minVal, const int maxVal)
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist(minVal, maxVal);
for (int i = 0; i < size; i++)
{
arr[i] = dist(gen);
}
}
std::string makeRandomString(const int len, char minChar = 'a', char maxChar = 'z')
{
if (len <= 0)
return{};
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dist(minChar, maxChar);
std::string s;
s.reserve(len);
for (int i = 0; i < len; i++)
s.push_back(static_cast<char>(dist(gen)));
return s;
}
int countOccurrences_index(const std::string& s, const char c, int index)
{
if (index >= static_cast<int>(s.size()))
return 0;
const int hit = (s[index] == c ? 1 : 0);
return hit + countOccurrences_index(s, c, index + 1);
}

View File

@@ -1,33 +0,0 @@
#ifndef FUNDAMENTAL_RECURSION_UTILS_H
#define FUNDAMENTAL_RECURSION_UTILS_H
#include <string>
#include <chrono>
void printNaturalNumber(int n);
int calculateFactorial(int n);
int power(int base, int exp);
int fibonacci(int n);
int countOccurrences(const std::string& s, char c);
int findLargestElement(const int arr[], int size);
void traverseAsciiTable(char start, char end);
// Helpers partially generated by ChatGPT
void fillRandomArray(int arr[], int size, int minVal, int maxVal);
std::string makeRandomString(int len, char minChar, char maxChar);
int countOccurrences_index(const std::string& s, char c, int index);
template<typename F, typename... Args>
double time_ms(F&& f, Args&&... args)
{
auto t0 = std::chrono::high_resolution_clock::now();
(void)std::forward<F>(f)(std::forward<Args>(args)...);
auto t1 = std::chrono::high_resolution_clock::now();
return std::chrono::duration<double, std::milli>(t1 - t0).count();
}
#endif //FUNDAMENTAL_RECURSION_UTILS_H

View File

@@ -1,89 +0,0 @@
#include <chrono>
#include <iostream>
#include "Utils.h"
static void line(const char* title)
{
std::cout << "\n======= " << title << "=======\n";
}
int main()
{
line("Neutral numbers");
printNaturalNumber(10);
std::cout << "\n";
line("Factorial");
const int fact_expect[] = {
1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800
};
bool ok_fact = true;
for (int n = 0; n <= 10; ++n) {
int r = calculateFactorial(n);
std::cout << "n=" << n << " -> " << r
<< (r == fact_expect[n] ? " OK" : " MISMATCH") << "\n";
ok_fact &= (r == fact_expect[n]);
}
line ("Power");
struct Case { int base, exp, expect; };
const Case pow_cases[] = {
{2,0,1}, {2,1,2}, {2,5,32}, {3,4,81}, {5,3,125}, {10,2,100}
};
bool ok_pow = true;
for (auto c : pow_cases) {
int r = power(c.base, c.exp);
std::cout << c.base << "^" << c.exp << " = " << r
<< (r == c.expect ? " OK" : " MISMATCH") << "\n";
ok_pow &= (r == c.expect);
line("Fibonacci");
const int fib_expect[] = {0,1,1,2,3,5,8,13,21,34,55,89,144};
bool ok_fib = true;
for (int n = 0; n <= 12; ++n) {
int r = fibonacci(n);
std::cout << "F(" << n << ") = " << r
<< (r == fib_expect[n] ? " OK" : " MISMATCH") << "\n";
ok_fib &= (r == fib_expect[n]);
}
line("Count occurrences");
std::string s = makeRandomString(2000, 'a', 'f');
char target = 'c';
int rec = countOccurrences(s, target); //<--- recursive
int ref = static_cast<int>(std::count(s.begin(), s.end(), target));
std::cout << "recursive=" << rec << " std::count=" << ref
<< (rec == ref ? " OK" : " MISMATCH") << "\n";
double t_rec_ms = time_ms([&]{ (void)countOccurrences(s, target); });
std::cout << "perf (recursive substr): ~" << t_rec_ms << " ms\n";
line ("Find largest element");
constexpr int N = 100000;
int* arr = new int[N];
fillRandomArray(arr, N, -100000, 100000);
double t_my_ms = time_ms([&]{ (void)findLargestElement(arr, N); });
int myMax = findLargestElement(arr, N);
int stdMax = *std::max_element(arr, arr + N);
std::cout << "myMax=" << myMax << " stdMax=" << stdMax
<< (myMax == stdMax ? " OK" : " MISMATCH") << "\n";
std::cout << "perf (recursive): ~" << t_my_ms << " ms\n";
delete[] arr;
line("Traverse Ascii table");
traverseAsciiTable('A', 'F');
std::cout << "\n";
line("Summary");
std::cout << "Factorial: " << (ok_fact ? "OK" : "FAIL") << "\n";
std::cout << "Power: " << (ok_pow ? "OK" : "FAIL") << "\n";
std::cout << "Fibonacci: " << (ok_fib ? "OK" : "FAIL") << "\n";
return 0;
}
}

View File

@@ -1,11 +0,0 @@
cmake_minimum_required(VERSION 4.0)
project(Stacks_Queues)
set(CMAKE_CXX_STANDARD 20)
add_executable(Stacks_Queues main.cpp
TStack.cpp
TStack.h
TQueue.cpp
TQueue.h
FUtils.h)

View File

@@ -1,183 +0,0 @@
#ifndef STACKS_QUEUES_FUTILS_H
#define STACKS_QUEUES_FUTILS_H
#include <iostream>
#include <stdexcept>
#include <string>
#include "TQueue.h"
#include "TStack.h"
constexpr int GRID_ROWS = 100;
constexpr int GRID_COLS = 100;
// Stack is perfect here because of the structure and behaviour of stacks - LIFO
// When we push the string in it will send the first char to the bottom of the stack
// When we push the string back out it will pop the last char first, then the second to last, then third and so on
// Time and space complexity is O(n) - each char pushed and popped exactly once and
// helper string "reverse clone" is needed for output
inline std::string ReverseString(const std::string& input)
{
if (input.size() > MAX_SIZE)
throw std::overflow_error("Input longer than MAX_SIZE");
TStack s;
for (char c : input)
s.Push(c);
std::string output;
output.reserve(input.size());
while (!s.IsEmpty())
{
output.push_back(s.Pop());
}
return output;
}
// Time = O(n) - push 2 .. n once and pop each once
// Space = O(1) - fixed size array inside TStack
inline long long Factorial(const int base)
{
if (base < 0)
throw std::invalid_argument("n must be >= 0");
if (base == 0 || base == 1)
return 1;
if (base > MAX_SIZE)
throw std::overflow_error("n exceeds MAX_SIZE");
TStack s;
for (int i = 2; i <= base; ++i)
s.Push(i);
long long result = 1;
while (!s.IsEmpty())
result *= s.Pop();
return result;
}
// Time = O(arrivals + toServe)
// Space = O(1) - fixed size queue
inline void SimulateWaitLine(const int arrivals, int toServe, TQueue& queue)
{
static int nextId = 1;
// arrivals
for (int i = 1; i <= arrivals; ++i) {
queue.Enqueue(nextId);
std::cout << "Arrives: " << nextId << std::endl;
nextId++;
}
// served
for (int i = 0; i < toServe && !queue.IsEmpty(); ++i) {
std::cout << "Served: " << queue.Dequeue() << std::endl;
}
}
// O(1) both - just checking if true or false
inline bool InBounds (int r, int c)
{
return r >= 0 && r < GRID_ROWS && c >= 0 && c < GRID_COLS;
}
// O(r * c)
inline void ResetVisited(bool visited[GRID_ROWS][GRID_COLS])
{
for (int r = 0; r < GRID_ROWS; ++r) {
for (int c = 0; c < GRID_COLS; ++c) {
visited[r][c] = false;
}
}
}
inline int EncodePos(int r, int c) {
return r * GRID_COLS + c;
}
inline void DecodePos(int code, int& r, int& c) {
r = code / GRID_COLS;
c = code % GRID_COLS;
}
// Time = O( r * c) worst case
// Space is O(1)
inline bool DFSFindZero(const int grid[GRID_ROWS][GRID_COLS], bool visited[GRID_ROWS][GRID_COLS],
int startR, int startC, int& outR, int& outC)
{
TStack s;
// mark start immediately to avoid multiple pushes
visited[startR][startC] = true;
s.Push(EncodePos(startR, startC));
while (!s.IsEmpty())
{
int code = s.Pop();
int r, c; DecodePos(code, r, c);
if (grid[r][c] == 0) {
outR = r; outC = c;
return true;
}
// Push neighbours, marking visited when discovered
if (InBounds(r - 1, c) && !visited[r - 1][c]) {
visited[r - 1][c] = true;
s.Push(EncodePos(r - 1, c)); // up
}
if (InBounds(r, c + 1) && !visited[r][c + 1]) {
visited[r][c + 1] = true;
s.Push(EncodePos(r, c + 1)); // right
}
if (InBounds(r + 1, c) && !visited[r + 1][c]) {
visited[r + 1][c] = true;
s.Push(EncodePos(r + 1, c)); // down
}
if (InBounds(r, c - 1) && !visited[r][c - 1]) {
visited[r][c - 1] = true;
s.Push(EncodePos(r, c - 1)); // left
}
}
return false;
}
// Same complexity as DFS
inline bool BFSFindZero(const int grid[GRID_ROWS][GRID_COLS], bool visited[GRID_ROWS][GRID_COLS],
int startR, int startC, int& outR, int& outC)
{
TQueue q;
// mark start immediately to avoid duplicate enqueues
visited[startR][startC] = true;
q.Enqueue(EncodePos(startR, startC));
while (!q.IsEmpty())
{
int code = q.Dequeue();
int r, c; DecodePos(code, r, c);
if (grid[r][c] == 0) {
outR = r; outC = c;
return true;
}
// Enqueue neighbours, marking visited on discovery
if (InBounds(r - 1, c) && !visited[r - 1][c]) {
visited[r - 1][c] = true;
q.Enqueue(EncodePos(r - 1, c)); // up
}
if (InBounds(r, c + 1) && !visited[r][c + 1]) {
visited[r][c + 1] = true;
q.Enqueue(EncodePos(r, c + 1)); // right
}
if (InBounds(r + 1, c) && !visited[r + 1][c]) {
visited[r + 1][c] = true;
q.Enqueue(EncodePos(r + 1, c)); // down
}
if (InBounds(r, c - 1) && !visited[r][c - 1]) {
visited[r][c - 1] = true;
q.Enqueue(EncodePos(r, c - 1)); // left
}
}
return false;
}
#endif //STACKS_QUEUES_FUTILS_H

View File

@@ -1,25 +0,0 @@
#ifndef STACKS_QUEUES_TQUEUE_H
#define STACKS_QUEUES_TQUEUE_H
#define MAX_SIZE (100 * 100)
class TQueue {
private:
int queue[MAX_SIZE] {};
int head = 0;
int tail = 0;
int count = 0;
public:
TQueue() = default;
~TQueue() = default;
void Enqueue(int item);
int Dequeue();
[[nodiscard]] int GetTail() const;
[[nodiscard]] int Peek() const;
[[nodiscard]] bool IsEmpty() const;
[[nodiscard]] bool IsFull() const;
};
#endif //STACKS_QUEUES_TQUEUE_H

View File

@@ -1,43 +0,0 @@
#include "TStack.h"
#include <iostream>
// Time complexity O(1) - always append to the end of the array and use that as the top of the stack
// Space complexity O(1) - no need to save helper variables, just place on top of existing, size-static stack
void TStack::Push(const int item)
{
if (top >= MAX_SIZE)
throw std::overflow_error("Stack overflow");
stack[top++] = item;
}
// Time and space complexity both O(1) - Only works with the top element so if you have
// 5 or 500 elements in the stack the result will be the same
// Only returns and removes top element
int TStack::Pop()
{
if (top == 0)
throw std::underflow_error("Stack underflow");
return stack[--top];
}
// Time and space complexity O(1) - only returns the top element
int TStack::Peek() const
{
if (top == 0)
throw std::underflow_error("Stack underflow");
return stack[top - 1];
}
// O(1) for both, checks to see if the stack is empty or not
// doesn't determine exact amount - just makes sure it has one
bool TStack::IsEmpty() const
{
return top == 0;
}

View File

@@ -1,23 +0,0 @@
#ifndef STACKS_QUEUES_TSTACK_H
#define STACKS_QUEUES_TSTACK_H
#define MAX_SIZE (100 * 100)
class TStack {
private:
int stack[MAX_SIZE]{};
int top = 0;
public:
TStack() = default;
~TStack() = default;
void Push(int item);
[[nodiscard]] int Pop();
[[nodiscard]] int Peek() const;
[[nodiscard]] bool IsEmpty() const;
};
#endif //STACKS_QUEUES_TSTACK_H

View File

@@ -1,48 +0,0 @@
#include <ctime>
#include "FUtils.h"
TQueue q;
int main()
{
// --- Part 2: quick demos ---
std::cout << "Reverse(\"stackqueue\") = " << ReverseString("stackqueue") << "\n";
std::cout << "Factorial(6) = " << Factorial(6) << "\n";
TQueue q;
std::cout << "\n-- Wait line simulation --\n";
SimulateWaitLine(5, 2, q); // 5 arrive, serve 2 now
SimulateWaitLine(3, 6, q); // 3 arrive, then serve the rest
// --- Part 3.6: set up grid and visited arrays ---
std::srand(static_cast<unsigned>(std::time(nullptr)));
int grid[GRID_ROWS][GRID_COLS];
bool visitedDFS[GRID_ROWS][GRID_COLS];
bool visitedBFS[GRID_ROWS][GRID_COLS];
for (int r = 0; r < GRID_ROWS; ++r)
for (int c = 0; c < GRID_COLS; ++c) {
grid[r][c] = std::rand() % 10; // 0..9
visitedDFS[r][c] = false;
visitedBFS[r][c] = false;
}
int startR = std::rand() % GRID_ROWS;
int startC = std::rand() % GRID_COLS;
std::cout << "\nStart cell: (" << startR << ", " << startC
<< ") value=" << grid[startR][startC] << "\n";
// --- Part 3.7: DFS ---
int dR=-1, dC=-1;
bool dfsFound = DFSFindZero(grid, visitedDFS, startR, startC, dR, dC);
if (dfsFound) std::cout << "[DFS] Found 0 at (" << dR << "," << dC << ")\n";
else std::cout << "[DFS] No 0 found\n";
// --- Part 3.8: BFS ---
int bR=-1, bC=-1;
bool bfsFound = BFSFindZero(grid, visitedBFS, startR, startC, bR, bC);
if (bfsFound) std::cout << "[BFS] Found 0 at (" << bR << "," << bC << ")\n";
else std::cout << "[BFS] No 0 found\n";
return 0;
}

8
TheAlgorithmicOrganizer/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

Some files were not shown because too many files have changed in this diff Show More