1359 changed files with 170732 additions and 49 deletions
@ -0,0 +1,16 @@ |
|||
# Open Educational Resources |
|||
|
|||
While many standards for open educational resources (OER) are available. |
|||
Unfortunetly many ignore the special needs for automatic evaluation and grading |
|||
for programming courses and parallel programming in particular. As such, until |
|||
a more universal format is determined, this project sticks with a very |
|||
simple file and directory based format and mostly plaintext based metadata. |
|||
Tools to work with the web platform and execution runners are provided. |
|||
|
|||
## Structure |
|||
|
|||
Lectures/courses and exercises are decoupled, to allow for multiple courses to |
|||
use the same exercise. |
|||
|
|||
./courses # a course allows to specify a sequence or directed graph of excercises |
|||
./exercises # provides a pool of exercises which can be used to create courses |
@ -0,0 +1 @@ |
|||
{"title": "C Advanced", "description": "Some further material for the C-Programming-Language"} |
@ -0,0 +1,10 @@ |
|||
Welcome to the C-Advanced course. This course is targeted for those, |
|||
who feel comfortable with pointers, memory management and all the basic |
|||
c syntax. |
|||
|
|||
We will guide you on, how to use c to implement your own data-structures, |
|||
writing your own libraries and give you some more advanced task to |
|||
test your abilities. |
|||
|
|||
Most of these task can take some time to get everything right. So don't be discouraged, whenn |
|||
need to take a break. Often those breaks help one see the mistakes or misunderstandings. |
@ -0,0 +1 @@ |
|||
{"title": "Introduction"} |
@ -0,0 +1 @@ |
|||
text/html |
@ -0,0 +1,8 @@ |
|||
|
|||
all: |
|||
gcc program.c -o program |
|||
#gcc solution.c -o solution |
|||
|
|||
clean: |
|||
rm -f program |
|||
rm -f solution |
@ -0,0 +1,28 @@ |
|||
The first basic data-structure everybody should learn is the singly linked list. |
|||
|
|||
In a singly linked list you save your data in nodes, a combination of data and a pointer |
|||
to the next node. To add nodes you simply allocate them and rewire the pointers, |
|||
such that it appears in the right spot in the list. |
|||
|
|||
To remove an element you have to rewire the previous and next node, such that their |
|||
pointers point to each other, and then free the memory. |
|||
|
|||
The idea behind this data-structures is, that you can add and remove elements easily |
|||
from the end, front and middle of the list. And because you just allocate more memory, |
|||
the list can grow dynamically. |
|||
|
|||
The problem is to get to the elements. |
|||
Because they are allocated on the heap the elements are all over the place and can only |
|||
be found via the pointers. So, when you want to get to the middle, you have to traverse |
|||
the linked list. |
|||
|
|||
Another downside of singly linked list is, that you can't go backwards. |
|||
That is were the doubly linked list comes in, but more on that later. |
|||
|
|||
The last disadvantage is, when you want to process lot's of data in a short amount of time, |
|||
you are often limited by cache misses. And linked list in general are terrible for that kind |
|||
of work, because most elements aren't in the same cache line. |
|||
|
|||
Your task will be to implement the functions for add, insert_after, remove and get. |
|||
It is often helpful to draw the operations you want to do on paper or |
|||
look at some pictures. |
@ -0,0 +1 @@ |
|||
{"title": "Singly Linked List"} |
@ -0,0 +1,66 @@ |
|||
#include <stdio.h> |
|||
|
|||
typedef struct node |
|||
{ |
|||
char Character; |
|||
struct node *Next; |
|||
}node; |
|||
|
|||
// NOTE: adds an element to the beginning
|
|||
void list_add(node **Head, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
} |
|||
|
|||
// NOTE: inserts after a specific node
|
|||
void list_insert_after(node *Node, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
} |
|||
|
|||
// NOTE: get's a node with the specified character
|
|||
node *list_get(node **Head, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
|
|||
return 0; |
|||
} |
|||
|
|||
// NOTE: removes a specific node from the list
|
|||
void list_remove(node **Head, node *Node) |
|||
{ |
|||
// TODO: implement this
|
|||
} |
|||
|
|||
// NOTE: frees the whole list
|
|||
void list_free(node **Head) |
|||
{ |
|||
// TODO: implement this
|
|||
} |
|||
|
|||
int main(int argc, char **argv) |
|||
{ |
|||
// NOTE: beginning of the list
|
|||
// right now the list is empty
|
|||
node *Head = 0; |
|||
|
|||
list_add(&Head, 'c'); |
|||
list_add(&Head, 'y'); |
|||
list_add(&Head, 'b'); |
|||
|
|||
node *ToBeRemoved = list_get(&Head, 'y'); |
|||
list_remove(Head, ToBeRemoved); |
|||
|
|||
list_add(&Head, 'a'); |
|||
node *InsertAfter = list_get(&Head, 'c'); |
|||
list_insert_after(InsertAfter, 'd'); |
|||
|
|||
for(node *Iter = Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
printf("%c ", Iter->Character); |
|||
} |
|||
printf("\n"); |
|||
|
|||
list_free(Head); |
|||
|
|||
} |
@ -0,0 +1 @@ |
|||
^a b c d $ |
@ -0,0 +1 @@ |
|||
program/match-regex |
@ -0,0 +1,8 @@ |
|||
|
|||
all: |
|||
gcc program.c -o program |
|||
#gcc solution.c -o solution |
|||
|
|||
clean: |
|||
rm -f program |
|||
rm -f solution |
@ -0,0 +1,11 @@ |
|||
The next step up from a sinlgy linked list is the double linked list. |
|||
It is basically the same. It just stores another pointer for the previous node. |
|||
This makes some operations way easier and some a little harder. |
|||
But most of the time it is a trade of worth taking. |
|||
|
|||
A common thing people do is to make double linked list(in short dlist) circular. |
|||
To mark the head and the tail they use a special node called the sentinel. |
|||
This makes some operations easier, but for now we will concentrate on the basic dlist. |
|||
|
|||
Your task is to change the singly linked list to a double linked list. |
|||
|
@ -0,0 +1 @@ |
|||
{"title": "Double Linked List"} |
@ -0,0 +1,111 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
typedef struct node |
|||
{ |
|||
char Character; |
|||
struct node *Next; |
|||
struct node *Prev; |
|||
}node; |
|||
|
|||
// TODO: change all the function to use a dlinked list
|
|||
node *alloc_node(char c, node *Next, node *Prev) |
|||
{ |
|||
node *NewNode = malloc(sizeof(node)); |
|||
NewNode->Character = c; |
|||
NewNode->Next = Next; |
|||
|
|||
return NewNode; |
|||
} |
|||
|
|||
// TIP: In the next 2 function you probably need to check for null-pointers
|
|||
// NOTE: adds an element to the beginning
|
|||
void list_add(node **Head, char c) |
|||
{ |
|||
node *NewNode = alloc_node(c, *Head, 0); |
|||
*Head = NewNode; |
|||
} |
|||
|
|||
// NOTE: inserts after a specific node
|
|||
void list_insert_after(node *Node, char c) |
|||
{ |
|||
node *NewNode = alloc_node(c, Node->Next, 0); |
|||
Node->Next = NewNode; |
|||
} |
|||
|
|||
// NOTE: get's a node with the specified character
|
|||
node *list_get(node **Head, char c) |
|||
{ |
|||
node *Result; |
|||
for(node *Iter = *Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
if(Iter->Character == c) |
|||
{ |
|||
Result = Iter; |
|||
break; |
|||
} |
|||
} |
|||
return Result; |
|||
} |
|||
|
|||
// NOTE: removes a specific node from the list
|
|||
void list_remove(node **Head, node *Node) |
|||
{ |
|||
// NOTE: this should get simpler
|
|||
// Find prev node
|
|||
node *Prev = 0; |
|||
for(node *Iter = *Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
if(Iter == Node) |
|||
break; |
|||
Prev = Iter; |
|||
} |
|||
if(Prev) |
|||
Prev->Next = Node->Next; |
|||
free(Node); |
|||
} |
|||
|
|||
// NOTE: frees the whole list
|
|||
void list_free(node **Head) |
|||
{ |
|||
for(node *Iter = *Head; Iter; ) |
|||
{ |
|||
node *ToFree = Iter; |
|||
Iter = Iter->Next; |
|||
|
|||
free(ToFree); |
|||
} |
|||
} |
|||
|
|||
int main(int argc, char **argv) |
|||
{ |
|||
node *Head = 0; |
|||
|
|||
list_add(&Head, 'c'); |
|||
list_add(&Head, 'y'); |
|||
list_add(&Head, 'b'); |
|||
|
|||
node *ToBeRemoved = list_get(&Head, 'y'); |
|||
list_remove(&Head, ToBeRemoved); |
|||
|
|||
list_add(&Head, 'a'); |
|||
node *InsertAfter = list_get(&Head, 'c'); |
|||
list_insert_after(InsertAfter, 'd'); |
|||
|
|||
// NOTE: prints out the whole list
|
|||
for(node *Iter = Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
printf("%c ", Iter->Character); |
|||
} |
|||
printf("\n"); |
|||
|
|||
// NOTE: prints out the previous Node of B, B and the next one
|
|||
node *B = list_get(&Head, 'b'); |
|||
if(B) |
|||
{ |
|||
printf("%s %c %s\n",B->Prev? B->Prev: "Null", B->Character, B->Next? B->Next: "Null"); |
|||
} |
|||
|
|||
list_free(&Head); |
|||
|
|||
} |
@ -0,0 +1 @@ |
|||
^a b c d\s*a b c\s*$ |
@ -0,0 +1,113 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
typedef struct node |
|||
{ |
|||
char Character; |
|||
struct node *Next; |
|||
struct node *Prev; |
|||
}node; |
|||
|
|||
// TODO: chane all the function to use a dlinked list
|
|||
node *alloc_node(char c, node *Next, node *Prev) |
|||
{ |
|||
node *NewNode = malloc(sizeof(node)); |
|||
NewNode->Character = c; |
|||
NewNode->Next = Next; |
|||
NewNode->Prev = Prev; |
|||
|
|||
return NewNode; |
|||
} |
|||
|
|||
// NOTE: adds an element to the beginning
|
|||
void list_add(node **Head, char c) |
|||
{ |
|||
node *NewNode = alloc_node(c, *Head, 0); |
|||
if(*Head) |
|||
(*Head)->Prev = NewNode; |
|||
*Head = NewNode; |
|||
} |
|||
|
|||
// NOTE: inserts after a specific node
|
|||
void list_insert_after(node *Node, char c) |
|||
{ |
|||
node *NewNode = alloc_node(c, Node->Next, Node); |
|||
node *Next = Node->Next; |
|||
if(Next) |
|||
{ |
|||
Next->Prev = NewNode; |
|||
} |
|||
Node->Next = NewNode; |
|||
} |
|||
|
|||
// NOTE: get's a node with the specified character
|
|||
node *list_get(node **Head, char c) |
|||
{ |
|||
node *Result; |
|||
for(node *Iter = *Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
if(Iter->Character == c) |
|||
{ |
|||
Result = Iter; |
|||
break; |
|||
} |
|||
} |
|||
return Result; |
|||
} |
|||
|
|||
// NOTE: removes a specific node from the list
|
|||
void list_remove(node **Head, node *Node) |
|||
{ |
|||
// NOTE: this should get simpler
|
|||
// Find prev node
|
|||
node *Prev = Node->Prev; |
|||
if(Prev) |
|||
Prev->Next = Node->Next; |
|||
node *Next = Node->Next; |
|||
Next->Prev = Prev; |
|||
|
|||
free(Node); |
|||
} |
|||
|
|||
// NOTE: frees the whole list
|
|||
void list_free(node **Head) |
|||
{ |
|||
for(node *Iter = *Head; Iter; ) |
|||
{ |
|||
node *ToFree = Iter; |
|||
Iter = Iter->Next; |
|||
|
|||
free(ToFree); |
|||
} |
|||
} |
|||
|
|||
int main(int argc, char **argv) |
|||
{ |
|||
node *Head = 0; |
|||
|
|||
list_add(&Head, 'c'); |
|||
list_add(&Head, 'y'); |
|||
list_add(&Head, 'b'); |
|||
|
|||
node *ToBeRemoved = list_get(&Head, 'y'); |
|||
list_remove(&Head, ToBeRemoved); |
|||
|
|||
list_add(&Head, 'a'); |
|||
node *InsertAfter = list_get(&Head, 'c'); |
|||
list_insert_after(InsertAfter, 'd'); |
|||
|
|||
// NOTE: Prints out the whole list
|
|||
for(node *Iter = Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
printf("%c ", Iter->Character); |
|||
} |
|||
printf("\n"); |
|||
|
|||
node *B = list_get(&Head, 'b'); |
|||
if(B) |
|||
{ |
|||
printf("%s %c %s\n",B->Prev? B->Prev: "Null", B->Character, B->Next? B->Next: "Null"); |
|||
} |
|||
list_free(&Head); |
|||
|
|||
} |
@ -0,0 +1,105 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
typedef struct node |
|||
{ |
|||
char Character; |
|||
struct node *Next; |
|||
}node; |
|||
|
|||
node *alloc_node(char c, node *Next) |
|||
{ |
|||
node *NewNode = malloc(sizeof(node)); |
|||
NewNode->Character = c; |
|||
NewNode->Next = Next; |
|||
|
|||
return NewNode; |
|||
} |
|||
|
|||
// NOTE: adds an element to the beginning
|
|||
void list_add(node **Head, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
node *NewNode = alloc_node(c, *Head); |
|||
*Head = NewNode; |
|||
} |
|||
|
|||
// NOTE: inserts after a specific node
|
|||
void list_insert_after(node *Node, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
node *NewNode = alloc_node(c, Node->Next); |
|||
Node->Next = NewNode; |
|||
} |
|||
|
|||
// NOTE: get's a node with the specified character
|
|||
node *list_get(node **Head, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
node *Result; |
|||
for(node *Iter = *Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
if(Iter->Character == c) |
|||
{ |
|||
Result = Iter; |
|||
break; |
|||
} |
|||
} |
|||
return Result; |
|||
} |
|||
|
|||
// NOTE: removes a specific node from the list
|
|||
void list_remove(node **Head, node *Node) |
|||
{ |
|||
// TODO: implement this
|
|||
// Find prev node
|
|||
node *Prev = 0; |
|||
for(node *Iter = *Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
if(Iter == Node) |
|||
break; |
|||
Prev = Iter; |
|||
} |
|||
if(Prev) |
|||
Prev->Next = Node->Next; |
|||
free(Node); |
|||
} |
|||
|
|||
// NOTE: frees the whole list
|
|||
void list_free(node **Head) |
|||
{ |
|||
for(node *Iter = *Head; Iter; ) |
|||
{ |
|||
node *ToFree = Iter; |
|||
Iter = Iter->Next; |
|||
|
|||
free(ToFree); |
|||
} |
|||
} |
|||
|
|||
int main(int argc, char **argv) |
|||
{ |
|||
// NOTE: beginning of the list
|
|||
// right now the list is empty
|
|||
node *Head = 0; |
|||
|
|||
list_add(&Head, 'c'); |
|||
list_add(&Head, 'y'); |
|||
list_add(&Head, 'b'); |
|||
|
|||
node *ToBeRemoved = list_get(&Head, 'y'); |
|||
list_remove(&Head, ToBeRemoved); |
|||
|
|||
list_add(&Head, 'a'); |
|||
node *InsertAfter = list_get(&Head, 'c'); |
|||
list_insert_after(InsertAfter, 'd'); |
|||
|
|||
for(node *Iter = Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
printf("%c ", Iter->Character); |
|||
} |
|||
printf("\n"); |
|||
|
|||
list_free(&Head); |
|||
|
|||
} |
@ -0,0 +1 @@ |
|||
program/match-regex |
@ -0,0 +1,8 @@ |
|||
|
|||
all: |
|||
gcc program.c -o program |
|||
#gcc solution.c -o solution |
|||
|
|||
clean: |
|||
rm -f program |
|||
rm -f solution |
@ -0,0 +1,21 @@ |
|||
The next data-structure is the hash table. |
|||
The hash table solves the problem of off having some key associated with |
|||
your data and wanting to get the entry in a table. |
|||
|
|||
In an normal array you would have to search the whole list. The same is |
|||
true for linked list. A hash table solves this problem by using the key as the index into an array. |
|||
|
|||
The key is normally referred to as the hash and is computed by putting your data trough a hash-function. |
|||
|
|||
The first problem you can easily imagine is, that two different entries get the same hash. This |
|||
is called a collision. There are different strategies of solving those collisions. |
|||
Some are inline solutions and use the next indices to store the entry others require other data-structures. |
|||
|
|||
Our hash-table will use a dlinked-list for these collision. If we detect a collision the new entry is |
|||
saved as the head of the linked list. |
|||
|
|||
A downside of a hash table is that it is hard to balance wasted space and avoiding collision. |
|||
You have to have a good hash function and good testing to determine with what kind of space you can |
|||
get away with. |
|||
|
|||
You task is to implement the hash table. |
@ -0,0 +1,53 @@ |
|||
node *alloc_node(int Value, int x, int y, node *Next) |
|||
{ |
|||
node *NewNode = malloc(sizeof(node)); |
|||
NewNode->Value = Value; |
|||
NewNode->x = x; |
|||
NewNode->y = y; |
|||
NewNode->Next = Next; |
|||
|
|||
return NewNode; |
|||
} |
|||
|
|||
void list_add(node **Head, int Value, int x, int y) |
|||
{ |
|||
node *NewNode = alloc_node(Value, x, y, *Head); |
|||
*Head = NewNode; |
|||
} |
|||
|
|||
node *list_get(node **Head, int x, int y) |
|||
{ |
|||
for(node *Iter = *Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
if(Iter->x == x && Iter->y == y) |
|||
{ |
|||
return Iter; |
|||
} |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
void list_remove(node **Head, node *Node) |
|||
{ |
|||
node *Prev = 0; |
|||
for(node *Iter = *Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
if(Iter == Node) |
|||
break; |
|||
Prev = Iter; |
|||
} |
|||
if(Prev) |
|||
Prev->Next = Node->Next; |
|||
free(Node); |
|||
} |
|||
|
|||
void list_free(node **Head) |
|||
{ |
|||
for(node *Iter = *Head; Iter; ) |
|||
{ |
|||
node *ToFree = Iter; |
|||
Iter = Iter->Next; |
|||
|
|||
free(ToFree); |
|||
} |
|||
} |
@ -0,0 +1 @@ |
|||
{"title": "Hash Table"} |
@ -0,0 +1,110 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
#define HASH_TABLE_SIZE 32 |
|||
|
|||
// NOTE: the data is some value, which is on a certain 2D point (x,y)
|
|||
typedef struct node |
|||
{ |
|||
// the data
|
|||
int x,y; |
|||
float Value; |
|||
|
|||
// next entry in the hash-table, if there were a collision
|
|||
struct node *Next; |
|||
}node; |
|||
|
|||
#include "linked_list.h" |
|||
|
|||
/*
|
|||
* Linked-list interface |
|||
* |
|||
* void list_add(node **Head, int Value, int x, int y); |
|||
* |
|||
* node *list_get(node **Head, int x, int y); |
|||
* |
|||
* void list_remove(node **Head, node *Node); |
|||
* |
|||
*/ |
|||
|
|||
// NOTE: this computes the hash slot of the entry
|
|||
int hash(int x, int y) |
|||
{ |
|||
int Result = (x + y)%HASH_TABLE_SIZE; |
|||
|
|||
if(Result < 0) |
|||
Result *= -1; |
|||
return Result; |
|||
} |
|||
|
|||
// NOTE: adds a new entry to the hash table.
|
|||
// new entries go at the head of the linked-list
|
|||
void hash_add(node **HashTable, float Value, int x, int y) |
|||
{ |
|||
// Don't forget to update the hash-tables pointer to the head.
|
|||
} |
|||
|
|||
// NOTE: gets the note at the position (x,y)
|
|||
node *hash_get(node **HashTable, int x, int y) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
// NOTE: removes the node at position (x,y),
|
|||
// if there is one
|
|||
void hash_remove(node **HashTable, int x, int y) |
|||
{ |
|||
|
|||
} |
|||
|
|||
int main(int argc, char *argv) |
|||
{ |
|||
node *HashTable[HASH_TABLE_SIZE] = {}; |
|||
|
|||
|
|||
// NOTE: some test values
|
|||
hash_add(HashTable, 1.0, 1, 0); |
|||
hash_add(HashTable, 3.0, 0, 1); |
|||
hash_add(HashTable, 5.0, 3, -2); |
|||
hash_add(HashTable, 7.0, 10, 3); |
|||
|
|||
// NOTE: test adding with collisions
|
|||
node *Node = hash_get(HashTable, 1, 0); |
|||
if(Node) |
|||
printf("Succesfully added value %f at position (%d,%d) to the HashTable\n", Node->Value, Node->x, Node->y); |
|||
else |
|||
printf("Failed to add or find value 1 at position (1,0)\n"); |
|||
|
|||
Node = hash_get(HashTable, 0, 1); |
|||
int added01 = 0; |
|||
if(Node) |
|||
{ |
|||
printf("Succesfully added value %f at position (%d,%d) to the HashTable\n", Node->Value, Node->x, Node->y); |
|||
added01 = 1; |
|||
} |
|||
else |
|||
printf("Failed to add or find value 3 at position (0,1)\n"); |
|||
|
|||
Node = hash_get(HashTable, 3, -2); |
|||
if(Node) |
|||
printf("Succesfully added value %f at position (%d,%d) to the HashTable\n", Node->Value, Node->x, Node->y); |
|||
else |
|||
printf("Failed to add or find value 5 at position (-2,-2)\n"); |
|||
|
|||
Node = hash_get(HashTable, 10, 3); |
|||
if(Node) |
|||
printf("Succesfully added value %f at position (%d,%d) to the HashTable\n", Node->Value, Node->x, Node->y); |
|||
else |
|||
printf("Failed to add or find value 7 at position (10,3)\n"); |
|||
|
|||
// NOTE: test removing element
|
|||
if(added01) |
|||
{ |
|||
hash_remove(HashTable, 0, 1); |
|||
Node = hash_get(HashTable, 0, 1); |
|||
if(!Node) |
|||
printf("Succesfully removed entrie at position (0,1) from the HashTable\n"); |
|||
else |
|||
printf("Failed to remove entrie at position (0,1)\n"); |
|||
} |
|||
} |
@ -0,0 +1 @@ |
|||
^Succesfully added value 1.000000 at position (1,0) to the HashTable\nSuccesfully added value 3.000000 at position (0,1) to the HashTable\nSuccesfully added value 5.000000 at position (3,-2) to the HashTable\nSuccesfully added value 7.000000 at position (10,3) to the HashTable\nSuccesfully removed entrie at position (0,1) from the HashTable$ |
@ -0,0 +1,116 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
#define HASH_TABLE_SIZE 32 |
|||
|
|||
// NOTE: the data is some value, which is on a certain 2D point (x,y)
|
|||
typedef struct node |
|||
{ |
|||
// the data
|
|||
int x,y; |
|||
float Value; |
|||
|
|||
// next entrie in the hashtable, if there were a collision
|
|||
struct node *Next; |
|||
}node; |
|||
|
|||
#include "linked_list.h" |
|||
|
|||
/*
|
|||
* Linked-list interface |
|||
* |
|||
* void list_add(node **Head, int Value, int x, int y); |
|||
* |
|||
* node *list_get(node **Head, int x, int y); |
|||
* |
|||
* void list_remove(node **Head, node *Node); |
|||
* |
|||
*/ |
|||
|
|||
int hash(int x, int y) |
|||
{ |
|||
int Result = (x + y)%HASH_TABLE_SIZE; |
|||
|
|||
if(Result < 0) |
|||
Result *= -1; |
|||
return Result; |
|||
} |
|||
|
|||
void hash_add(node **HashTable, float Value, int x, int y) |
|||
{ |
|||
int Hash = hash(x,y); |
|||
node *Head = HashTable[Hash]; |
|||
list_add(&Head, Value, x, y); |
|||
|
|||
// Don't forget to update the Hashtables pointer to the head.
|
|||
HashTable[Hash] = Head; |
|||
} |
|||
node *hash_get(node **HashTable, int x, int y) |
|||
{ |
|||
int Hash = hash(x,y); |
|||
node *Head = HashTable[Hash]; |
|||
node *Result = list_get(&Head, x, y); |
|||
|
|||
return Result; |
|||
} |
|||
|
|||
void hash_remove(node **HashTable, int x, int y) |
|||
{ |
|||
int Hash = hash(x,y); |
|||
node *Head = HashTable[Hash]; |
|||
node *Node = list_get(&Head, x, y); |
|||
if(Node) |
|||
list_remove(&Head, Node); |
|||
} |
|||
|
|||
int main(int argc, char *argv) |
|||
{ |
|||
node *HashTable[HASH_TABLE_SIZE] = {}; |
|||
|
|||
//payload D1 = {0, 0, 1.0, 0};
|
|||
|
|||
hash_add(HashTable, 1.0, 1, 0); |
|||
hash_add(HashTable, 3.0, 0, 1); |
|||
hash_add(HashTable, 5.0, 3, -2); |
|||
hash_add(HashTable, 7.0, 10, 3); |
|||
|
|||
// Note: Test adding with collisions
|
|||
node *Node = hash_get(HashTable, 1, 0); |
|||
if(Node) |
|||
printf("Succesfully added value %f at position (%d,%d) to the HashTable\n", Node->Value, Node->x, Node->y); |
|||
else |
|||
printf("Failed to add or find value 1 at position (1,0)\n"); |
|||
|
|||
Node = hash_get(HashTable, 0, 1); |
|||
int added01 = 0; |
|||
if(Node) |
|||
{ |
|||
printf("Succesfully added value %f at position (%d,%d) to the HashTable\n", Node->Value, Node->x, Node->y); |
|||
added01 = 1; |
|||
} |
|||
else |
|||
printf("Failed to add or find value 3 at position (0,1)\n"); |
|||
|
|||
Node = hash_get(HashTable, 3, -2); |
|||
if(Node) |
|||
printf("Succesfully added value %f at position (%d,%d) to the HashTable\n", Node->Value, Node->x, Node->y); |
|||
else |
|||
printf("Failed to add or find value 5 at position (-2,-2)\n"); |
|||
|
|||
Node = hash_get(HashTable, 10, 3); |
|||
if(Node) |
|||
printf("Succesfully added value %f at position (%d,%d) to the HashTable\n", Node->Value, Node->x, Node->y); |
|||
else |
|||
printf("Failed to add or find value 7 at position (10,3)\n"); |
|||
|
|||
// NOTE: Test removing element
|
|||
if(added01) |
|||
{ |
|||
hash_remove(HashTable, 0, 1); |
|||
Node = hash_get(HashTable, 0, 1); |
|||
if(!Node) |
|||
printf("Succesfully removed entrie at position (0,1) from the HashTable\n"); |
|||
else |
|||
printf("Failed to remove entrie at position (0,1)\n"); |
|||
} |
|||
} |
@ -0,0 +1,105 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
typedef struct node |
|||
{ |
|||
char Character; |
|||
struct node *Next; |
|||
}node; |
|||
|
|||
node *alloc_node(char c, node *Next) |
|||
{ |
|||
node *NewNode = malloc(sizeof(node)); |
|||
NewNode->Character = c; |
|||
NewNode->Next = Next; |
|||
|
|||
return NewNode; |
|||
} |
|||
|
|||
// NOTE: adds an element to the beginning
|
|||
void list_add(node **Head, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
node *NewNode = alloc_node(c, *Head); |
|||
*Head = NewNode; |
|||
} |
|||
|
|||
// NOTE: inserts after a specific node
|
|||
void list_insert_after(node *Node, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
node *NewNode = alloc_node(c, Node->Next); |
|||
Node->Next = NewNode; |
|||
} |
|||
|
|||
// NOTE: get's a node with the specified character
|
|||
node *list_get(node **Head, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
node *Result; |
|||
for(node *Iter = *Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
if(Iter->Character == c) |
|||
{ |
|||
Result = Iter; |
|||
break; |
|||
} |
|||
} |
|||
return Result; |
|||
} |
|||
|
|||
// NOTE: removes a specific node from the list
|
|||
void list_remove(node **Head, node *Node) |
|||
{ |
|||
// TODO: implement this
|
|||
// Find prev node
|
|||
node *Prev = 0; |
|||
for(node *Iter = *Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
if(Iter == Node) |
|||
break; |
|||
Prev = Iter; |
|||
} |
|||
if(Prev) |
|||
Prev->Next = Node->Next; |
|||
free(Node); |
|||
} |
|||
|
|||
// NOTE: frees the whole list
|
|||
void list_free(node **Head) |
|||
{ |
|||
for(node *Iter = *Head; Iter; ) |
|||
{ |
|||
node *ToFree = Iter; |
|||
Iter = Iter->Next; |
|||
|
|||
free(ToFree); |
|||
} |
|||
} |
|||
|
|||
int main(int argc, char **argv) |
|||
{ |
|||
// NOTE: beginning of the list
|
|||
// right now the list is empty
|
|||
node *Head = 0; |
|||
|
|||
list_add(&Head, 'c'); |
|||
list_add(&Head, 'y'); |
|||
list_add(&Head, 'b'); |
|||
|
|||
node *ToBeRemoved = list_get(&Head, 'y'); |
|||
list_remove(&Head, ToBeRemoved); |
|||
|
|||
list_add(&Head, 'a'); |
|||
node *InsertAfter = list_get(&Head, 'c'); |
|||
list_insert_after(InsertAfter, 'd'); |
|||
|
|||
for(node *Iter = Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
printf("%c ", Iter->Character); |
|||
} |
|||
printf("\n"); |
|||
|
|||
list_free(&Head); |
|||
|
|||
} |
@ -0,0 +1 @@ |
|||
program/match-regex |
@ -0,0 +1,8 @@ |
|||
|
|||
all: |
|||
gcc program.c -o program |
|||
#gcc solution.c -o solution |
|||
|
|||
clean: |
|||
rm -f program |
|||
rm -f solution |
@ -0,0 +1,18 @@ |
|||
As you noticed, we can use our basic data-structures, to form more complex ones, which fit more specific needs. |
|||
This time we will take a look at the queue. This data-structures allows you to perform operations on your data |
|||
with the **FIFO**(**F**irst **i**n **F**irst **o**ut) principle. This is critical, if you want to |
|||
process the oldest data first, just like in a queue in the real world. |
|||
|
|||
The queue data-structure is normally based on a linked list, such that it is also dynamically growing in size. |
|||
But this does not have to be true, but usually, if you are using libraries, it is. |
|||
|
|||
For the queue we want to perform three basic operations. |
|||
-first we want to put something at the end of the queue. We call that ``enqueue``. |
|||
-second we want to take something of the beginning of the queue. We call that ``dequeue``. |
|||
-third we want just look at the end of the queue without taking it of. We call that ``peak``. |
|||
|
|||
There are also other kinds of queues, which will not necessarily operate like a FIFO-queue. |
|||
For example the priority-queue, which will sort the member with the highest priority |
|||
to the beginning of the queue and uses a heap as it's underlying structure. |
|||
|
|||
Your task is to implement the queue on the basis of a singly linked list. |
@ -0,0 +1 @@ |
|||
{"title": "Queue"} |
@ -0,0 +1,70 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
typedef struct node |
|||
{ |
|||
char Character; |
|||
struct node *Next; |
|||
}node; |
|||
|
|||
typedef struct |
|||
{ |
|||
node *Head; |
|||
node *Tail; |
|||
}queue; |
|||
|
|||
node *alloc_node(char c, node *Next) |
|||
{ |
|||
node *NewNode = malloc(sizeof(node)); |
|||
NewNode->Character = c; |
|||
NewNode->Next = Next; |
|||
|
|||
return NewNode; |
|||
} |
|||
|
|||
// You could reuse the implementation of the singly linked list,
|
|||
// but in this case we can optimize our operations,
|
|||
// because we have pointers to the beginning end of the linked list.
|
|||
|
|||
// NOTE: this put elements on the end of the list
|
|||
// Don't forget to initialize the Head pointer
|
|||
void enqueue(queue *Queue, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
} |
|||
|
|||
// NOTE: This returns the char, because we don't want to manage
|
|||
// the memory in "user code"
|
|||
// Don't forget to free the node, otherwise this queue has a leak.
|
|||
char dequeue(queue *Queue) |
|||
{ |
|||
// TODO: implement this
|
|||
return 0; |
|||
} |
|||
|
|||
// NOTE: in this example we only use this to check if the list is empty.
|
|||
// To do that we could also track the element count, but the peak operation
|
|||
// has also some other use cases.
|
|||
node *peak(queue *Queue) |
|||
{ |
|||
// TODO: implement this
|
|||
return 0; |
|||
} |
|||
|
|||
// you can implement the queue-operations without ever searching through the list.
|
|||
int main(int argc, char **argv) |
|||
{ |
|||
queue Queue = {}; |
|||
char *String = "HelloWorld!"; |
|||
|
|||
// The String should come out as it went in.
|
|||
while(*String) |
|||
{ |
|||
enqueue(&Queue, *String++); |
|||
} |
|||
while(peak(&Queue)) |
|||
{ |
|||
printf("%c", dequeue(&Queue)); |
|||
} |
|||
|
|||
} |
@ -0,0 +1 @@ |
|||
^HelloWorld!$ |
@ -0,0 +1,69 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
typedef struct node |
|||
{ |
|||
char Character; |
|||
struct node *Next; |
|||
}node; |
|||
|
|||
typedef struct |
|||
{ |
|||
node *Head; |
|||
node *Tail; |
|||
}queue; |
|||
|
|||
node *alloc_node(char c, node *Next) |
|||
{ |
|||
node *NewNode = malloc(sizeof(node)); |
|||
NewNode->Character = c; |
|||
NewNode->Next = Next; |
|||
|
|||
return NewNode; |
|||
} |
|||
|
|||
void enqueue(queue *Queue, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
node *NewNode = alloc_node(c, 0); |
|||
if(Queue->Tail) |
|||
Queue->Tail->Next = NewNode; |
|||
Queue->Tail = NewNode; |
|||
if(!Queue->Head) |
|||
{ |
|||
Queue->Head = NewNode; |
|||
} |
|||
} |
|||
|
|||
|
|||
char dequeue(queue *Queue) |
|||
{ |
|||
// TODO: implement this
|
|||
node *First = Queue->Head; |
|||
char Result = First->Character; |
|||
Queue->Head = First->Next; |
|||
free(First); |
|||
return Result; |
|||
} |
|||
|
|||
node *peak(queue *Queue) |
|||
{ |
|||
// TODO: implement this
|
|||
return Queue->Head; |
|||
} |
|||
|
|||
int main(int argc, char **argv) |
|||
{ |
|||
queue Queue = {}; |
|||
char *String = "HelloWorld!"; |
|||
|
|||
while(*String) |
|||
{ |
|||
enqueue(&Queue, *String++); |
|||
} |
|||
while(peak(&Queue)) |
|||
{ |
|||
printf("%c", dequeue(&Queue)); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,105 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
typedef struct node |
|||
{ |
|||
char Character; |
|||
struct node *Next; |
|||
}node; |
|||
|
|||
node *alloc_node(char c, node *Next) |
|||
{ |
|||
node *NewNode = malloc(sizeof(node)); |
|||
NewNode->Character = c; |
|||
NewNode->Next = Next; |
|||
|
|||
return NewNode; |
|||
} |
|||
|
|||
// NOTE: adds an element to the beginning
|
|||
void list_add(node **Head, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
node *NewNode = alloc_node(c, *Head); |
|||
*Head = NewNode; |
|||
} |
|||
|
|||
// NOTE: inserts after a specific node
|
|||
void list_insert_after(node *Node, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
node *NewNode = alloc_node(c, Node->Next); |
|||
Node->Next = NewNode; |
|||
} |
|||
|
|||
// NOTE: get's a node with the specified character
|
|||
node *list_get(node **Head, char c) |
|||
{ |
|||
// TODO: implement this
|
|||
node *Result; |
|||
for(node *Iter = *Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
if(Iter->Character == c) |
|||
{ |
|||
Result = Iter; |
|||
break; |
|||
} |
|||
} |
|||
return Result; |
|||
} |
|||
|
|||
// NOTE: removes a specific node from the list
|
|||
void list_remove(node **Head, node *Node) |
|||
{ |
|||
// TODO: implement this
|
|||
// Find prev node
|
|||
node *Prev = 0; |
|||
for(node *Iter = *Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
if(Iter == Node) |
|||
break; |
|||
Prev = Iter; |
|||
} |
|||
if(Prev) |
|||
Prev->Next = Node->Next; |
|||
free(Node); |
|||
} |
|||
|
|||
// NOTE: frees the whole list
|
|||
void list_free(node **Head) |
|||
{ |
|||
for(node *Iter = *Head; Iter; ) |
|||
{ |
|||
node *ToFree = Iter; |
|||
Iter = Iter->Next; |
|||
|
|||
free(ToFree); |
|||
} |
|||
} |
|||
|
|||
int main(int argc, char **argv) |
|||
{ |
|||
// NOTE: beginning of the list
|
|||
// right now the list is empty
|
|||
node *Head = 0; |
|||
|
|||
list_add(&Head, 'c'); |
|||
list_add(&Head, 'y'); |
|||
list_add(&Head, 'b'); |
|||
|
|||
node *ToBeRemoved = list_get(&Head, 'y'); |
|||
list_remove(&Head, ToBeRemoved); |
|||
|
|||
list_add(&Head, 'a'); |
|||
node *InsertAfter = list_get(&Head, 'c'); |
|||
list_insert_after(InsertAfter, 'd'); |
|||
|
|||
for(node *Iter = Head; Iter; Iter = Iter->Next) |
|||
{ |
|||
printf("%c ", Iter->Character); |
|||
} |
|||
printf("\n"); |
|||
|
|||
list_free(&Head); |
|||
|
|||
} |
@ -0,0 +1 @@ |
|||
program/match-regex |
@ -0,0 +1,8 @@ |
|||
|
|||
all: |
|||
gcc program.c -o program |
|||
#gcc solution.c -o solution |
|||
|
|||
clean: |
|||
rm -f program |
|||
rm -f solution |
@ -0,0 +1,17 @@ |
|||
A data-structure often associated with the queue is the stack. |
|||
That's because they are basically the opposite of one another. |
|||
While the queue works on the FIFO-principle the stack works |
|||
on the **LIFO**-principle (**L**ast **i**n **f**irst **o**ut). |
|||
|
|||
Similar two the queue we have three basic operations, but they are called |
|||
different to distinguish between the stack and the queue. |
|||
- We put something at the top of the stack. That is called push. |
|||
- We take something from the top of the stack. That is called pop. |
|||
- As with the queue we sometimes one to just take a look at the top of the stack. |
|||
So a peak operation is nice, but not necessary |
|||
|
|||
Stacks are useful data-structures for parsing or when you want follow some data to the end |
|||
and then back trace to a previous state, like in depth-fist-search. |
|||
These are just some use cases for stacks, but they have many more. |
|||
|
|||
Your task is to implement the push, pop and peak functions for the stack. |
@ -0,0 +1 @@ |
|||
{"title": "Stack"} |
@ -0,0 +1,57 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
typedef struct node |
|||
{ |
|||
char Character; |
|||
struct node *Next; |
|||
}node; |
|||
|
|||
typedef struct |
|||
{ |
|||
node *Top; |
|||
}stack; |
|||
|
|||
node *alloc_node(char c, node *Next) |
|||
{ |
|||
node *NewNode = malloc(sizeof(node)); |
|||
NewNode->Character = c; |
|||
NewNode->Next = Next; |
|||
|
|||
return NewNode; |
|||
} |
|||
|
|||
void push(stack *Stack,char c) |
|||
{ |
|||
// TODO: implement this
|
|||
} |
|||
|
|||
// NOTE: as last time free the node here
|
|||
char pop(stack *Stack) |
|||
{ |
|||
// TODO: implement this
|
|||
return 0; |
|||
} |
|||
|
|||
node *peak(stack *Stack) |
|||
{ |
|||
// TODO: implement this
|
|||
return 0; |
|||
} |
|||
|
|||
int main(int argc, char **argv) |
|||
{ |
|||
stack Stack = {}; |
|||
char *String = "!dlroW ollaH"; |
|||
|
|||
// When everything works, the program should print Hello World!.
|
|||
while(*String) |
|||
{ |
|||
push(&Stack, *String++); |
|||
} |
|||
while(peak(&Stack)) |
|||
{ |
|||
printf("%c", pop(&Stack)); |
|||
} |
|||
|
|||
} |
@ -0,0 +1 @@ |
|||
^Hello World!$ |
@ -0,0 +1,62 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
typedef struct node |
|||
{ |
|||
char Character; |
|||
struct node *Next; |
|||
}node; |
|||
|
|||
typedef struct |
|||
{ |
|||
node *Top; |
|||
}stack; |
|||
|
|||
node *alloc_node(char c, node *Next) |
|||
{ |
|||
node *NewNode = malloc(sizeof(node)); |
|||
NewNode->Character = c; |
|||
NewNode->Next = Next; |
|||
|
|||
return NewNode; |
|||
} |
|||
|
|||
void push(stack *Stack,char c) |
|||
{ |
|||
// TODO: implement this
|
|||
node *NewNode = alloc_node(c, Stack->Top); |
|||
Stack->Top = NewNode; |
|||
} |
|||
|
|||
char pop(stack *Stack) |
|||
{ |
|||
// TODO: implement this
|
|||
char Result = Stack->Top->Character; |
|||
node *Top = Stack->Top->Next; |
|||
free(Stack->Top); |
|||
Stack->Top = Top; |
|||
return Result; |
|||
} |
|||
|
|||
node *peak(stack *Stack) |
|||
{ |
|||
// TODO: implement this
|
|||
return Stack->Top; |
|||
} |
|||
|
|||
int main(int argc, char **argv) |
|||
{ |
|||
stack Stack = {}; |
|||
char *String = "!dlroW olleH"; |
|||
|
|||
// When everything works, the program should print Hello World!.
|
|||
while(*String) |
|||
{ |
|||
push(&Stack, *String++); |
|||
} |
|||
while(peak(&Stack)) |
|||
{ |
|||
printf("%c", pop(&Stack)); |
|||
} |
|||
|
|||
} |
@ -0,0 +1 @@ |
|||
program/match-regex |
@ -0,0 +1,8 @@ |
|||
|
|||
all: |
|||
gcc program.c -o program |
|||
#gcc solution.c -o solution |
|||
|
|||
clean: |
|||
rm -f program |
|||
rm -f solution |
@ -0,0 +1,26 @@ |
|||
Now that we covered the basic linear data-structures, let's move on to once, which branch out. |
|||
This task is about trees. A tree consists of connected nodes and each node has a certain amount of children nodes. |
|||
The nodes, which don't have any children are called leaves and the start node is called a root. |
|||
A tree also doesn't have any circles, meaning that no path from one node should lead back to it's self. |
|||
Trees are often associated with sorting, hierarchical data or decisions. |
|||
|
|||
This description is for trees in general. The one we will discuss is called a binary-search-tree. |
|||
The only difference to the normal tree is, that it only has two children and all the left children have a lower |
|||
search value than the node and all the right nodes have a higher one. |
|||
These kind of trees are very useful for efficient searching. |
|||
|
|||
For this tree we want to be able to perform four operations. |
|||
- We want to be able to automatically insert an element into the right place. |
|||
- We want to delete an element without destroying the definition of the binary-search-tree |
|||
- We want to find an element in the tree |
|||
- And we want to print out all the elements in a sorted way |
|||
|
|||
Recursion is a good strategy to write functions for trees. |
|||
|
|||
All but the delete functions are straight forward. The delete function needs some special casing. |
|||
If there are both children present, you have to find the lowest value on the right and replace the |
|||
current one with it and then recursivly delete that one. |
|||
If there are less than two children, the deletion is trivial. |
|||
|
|||
Your task is to implement all these functions. |
|||
|
@ -0,0 +1 @@ |
|||
{"title": "Tree"} |
@ -0,0 +1,89 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
typedef struct node |
|||
{ |
|||
int Value; |