Programs developed using Data Structures and Algorithms.
Homework for University of Trento's Programming 1 course.
The first programming course of University of Trento's C.S. bachelor degree is Programming 1. This course aims to teach basic concepts of programming using C++. Moreover, it gives a complete problem solving foundation necessary to tackle advanced university programming courses. This pages illustrates the most challenging programs given to students as homework between Sept. 2022 and Jan. 2023 (semester in which I took the course).
- By following this course, I learned to code in C++. I had never coded in this language before (I had experience mainly in Javascript and Python). I struggled a lot at the beginning of the course, especially while learning concepts like dynamic allocation and pointers, but by doing many exercises I understood them and I was gradually able to complete every assignment given in the course.
- Even if I had already studied recursion and knew its use cases, this course let me understand recursion at its core by understanding how compilers parse recursive code to optimize it. This is put in practice by transforming recursive function written as tail recursive into equivalent iterative functions. This simplifies developers' lives especially when dealing with trees, where recursion allows to perform basic operations in a very readable way.
This course taught me basic data structures and algorithms. I had never studied them before, but I found them fascinating I learned:
- Stacks
- Sorting algorithms
- Search algorithms
- Queues
- Priority Queues
- Binary Search Trees
- Linked lists
This has been a great opportunity to start my interview preparation journey. Starting to solve basic DSA problems, in order to understand what I will need to focus on in the future in order to pass technical job interviews.
After having completed this course my problem solving skills improved, especially for solving basic data structures problems. I learned that in order to solve these problems it's better to rephrase the problem first, thinking about: inputs, outputs and simple examples. Afterwards it's necessary to think about the logic which needs to be implemented in order to solve the problem. If it's very difficult to figure out an algorithm to process thought examples, then it may be useful to think about a simplified version of the problem and reflect on the logic which needs to be set up to solve it.
This exercise was about modeling a library using a Binary Search tree containing Book objects. The most interesting feature we were asked to implement was a function which searched for all the books of a specific author in the tree, saving them in a Linked List.
#include <cstddef>
#include <cstring>
#include <cstdlib>
#include <iostream>
using namespace std;
#define MAXSSIZE 50
struct book {
char title[MAXSSIZE];
char author[MAXSSIZE];
char ISBN[MAXSSIZE];
int id;
};
struct tree {
book info;
tree * left;
tree * right;
};
void printBook(const book & l) {
cout << "==============================" << endl;
cout << "title: " << l.title << endl;
cout << "author: " << l.author << endl;
cout << "ISBN: " << l.ISBN << endl;
cout << "Id: " << l.id << endl;
cout << "==============================" << endl;
}
book readBook() {
book res;
char b[100];
cout << "title: ";
cin >> b;
strncpy(res.title, b, MAXSSIZE);
cout << "author: ";
cin >> b;
strncpy(res.author, b, MAXSSIZE);
cout << "ISBN: ";
cin >> b;
strncpy(res.ISBN, b, MAXSSIZE);
cout << "Id: ";
cin >> b;
res.id = atoi(b);
return res;
}
int compare(const book & l1, const book & l2) {
return strcmp(l1.author, l2.author);
}
int compareAuthor(const char * author, const book & l) {
return strcmp(author, l.author);
}
struct node {
book val;
node * next;
};
void init(tree * & t) {
t = (tree *)NULL;
}
void deinit(tree * & t) {
if (t != NULL) {
deinit(t->left);
deinit(t->right);
delete t;
}
}
void insert(tree * & t, const book & v) {
if (t == (tree*)NULL) {
t = new (nothrow) tree;
if (t != (tree*)NULL) {
t->info = v;
t->left = (tree*)NULL;
t->right = (tree*)NULL;
}
}
else if (compare(v, t->info) <= 0)
insert(t->left, v);
else if (compare(v, t->info) > 0)
insert(t->right, v);
}
void orderedPrint(const tree * t) {
if (t != (tree *)NULL) {
orderedPrint(t->left);
printBook(t->info);
orderedPrint(t->right);
}
}
void print_it(node * current) {
while (current != NULL) {
printBook(current->val);
current = current -> next;
}
}
void deinit_it(node * current) {
while (current != NULL) {
node * t = current->next;
delete current;
current = t;
}
}
void print_all_aux(const tree * t, const char * author, node * & firstNode, node * & list) {
if (t != (tree *)NULL) {
if (compareAuthor(author, t->info) == 0) {
// In this case, go only to left, because by specification
// can only find on the left elements with the same author,
// and thus avoid looking on right, thus avoiding
// useless searches.
print_all_aux(t->left, author, firstNode, list);
// printBook(t->info);
node * n = new node;
n->val = t->info;
if (firstNode == NULL) firstNode = n;
if (list != NULL) list->next = n;
list = n;
}
if (compareAuthor(author, t->info) < 0) {
print_all_aux(t->left, author, firstNode, list);
} else {
print_all_aux(t->right, author, firstNode, list);
}
}
}
node * printAll(const tree * t, const char * author) {
node * firstNode, * list;
firstNode = list = NULL;
print_all_aux(t, author, firstNode, list);
return firstNode;
}
int main() {
tree * library;
init(library);
char res;
do {
cout << "Options: " << endl
<< "Insert (i)" << endl
<< "Ordered print (o)" << endl
<< "Print all (t)" << endl
<< "end (f)" << endl;
cout << ": ";
cin >> res;
switch (res) {
case 'i': {
book l = readBook();
insert(library, l);
break;
}
case 'o': {
cout << "library:" << endl;
orderedPrint(library);
cout << endl;
break;
}
case 't': {
char author[MAXSSIZE];
cout << "author: ";
cin >> author;
cout << endl << "All books of author "" << author << "" sono:" << endl;
node * f = printAll(library, author);
print_it(f);
deinit_it(f);
break;
}
case 'f':
break;
default:
cout << "Unknown operation" << endl;
break;
}
} while (res != 'f');
deinit(library);
}
Simple programs which asks for a matrix in input and calculates the determinant using Sarrus' algorithm. By default the matrix is 3x3 but it can be used for any quadratic matrix.
#include <iostream>
#include <cstdlib>
using namespace std;
const int ROWS = 3;
const int COLS = 3;
void init(int matrix[][COLS]);
void print(const int matrix[][COLS]);
int sarrus(const int matrix[][COLS]);
int multiplyDiagonal(const int matrix[][COLS], int startCol, bool inverse);
int main() {
srand(time(NULL));
int matrix[ROWS][COLS];
init(matrix);
print(matrix);
int d = sarrus(matrix);
cout << "determinant " << d << endl;
return 0;
}
void init(int matrix[][COLS]) {
for (int row = 0; row < ROWS ; row++) {
for (int col = 0 ; col < COLS ; col++) {
matrix[row][col] = rand() % 10;
}
}
}
void print(const int matrix[][COLS]) {
for (int row = 0 ; row < ROWS ; row++) {
for (int col = 0 ; col < COLS ; col++) {
cout << matrix[row][col] << " ";
}
cout << endl;
}
cout << endl;
}
int sarrus(const int matrix[][COLS]) {
return multiplyDiagonal(matrix, 0, false)
+ multiplyDiagonal(matrix, 1, false)
+ multiplyDiagonal(matrix, 2, false)
- multiplyDiagonal(matrix, 0, true)
- multiplyDiagonal(matrix, 1, true)
- multiplyDiagonal(matrix, 2, true);
}
int multiplyDiagonal(const int matrix[][COLS], int startCol, bool inverse) {
int val = 1;
int s = inverse ? -1 : 1;
for (int row = 0 ; row < ROWS ; row++) {
int col = (startCol+row*s) % COLS;
col = (col < 0) ? col+COLS : col;
val *= matrix[row][col];
}
return val;
}
A program which takes a boolean expression as input. This has to be prefixed, so each operator (& and !) precedes its values. The program parses the expression, evaluating it with the support of a stack. If for whatever reason the evaluating function find an unexpected values for the expression to be correctly resolved, it sets the is valid parameter to false. Otherwise, if the expression is correctly formated it returns its computer value 'T' or 'F'.
Examples:
F&T : Invalid expression
&F&TT : F
#ifndef STACK_H
#define STACK_H
#include <iostream>
using namespace std;
struct node {
char value;
node* next;
};
typedef node* list;
void init();
void deinit();
bool push(char);
bool top(char &);
bool pop();
bool empty();
#endif
#include "stack.h"
static list stack;
bool empty () {
return (stack == NULL);
}
void init() {
stack = NULL;
}
bool top (char &n) {
bool res;
if (empty()) {
res = false;
}
else {
n=stack->value;
res = true;
}
return res;
}
bool push (char n) {
bool res;
list newNode = new node;
if (newNode==NULL) {
res = false;
}
else {
newNode->value = n;
newNode->next = stack;
stack = newNode;
res = true;
}
return res;
}
bool pop () {
bool res;
if (empty()) {
res = false;
}
else {
list firstNode = stack;
stack = stack->next;
delete firstNode;
res = true;
}
return res;
}
void deinit() {
while (pop()) {
;
}
}
#include <iostream>
#include "stack.h"
using namespace std;
int strlen(char str[]) {
int l = 0;
for (int i = 0; str[i] != '