# NCKU CSIE PD1 Week15
👉[English](https://hackmd.io/@zhu424/NCKU-CSIE-PD1-2023-week-15-English)
> 如果已經知道 **Generic Programming** 是什麼
> 可以直接跳到 [Lab](#Lab)
## 如果我們要寫一個交換兩個變數的 function ...
對於 `int` 來說,我們可以這樣寫
```c
void my_swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main() {
int a = 1, b = 2;
my_swap(&a, &b);
printf("%d %d\n", a, b);
return 0;
}
```
對於 `float` 來說,我們可以這樣寫
```c
void my_swap(float *a, float *b) {
float tmp = *a;
*a = *b;
*b = tmp;
}
```
那如果我們想要交換的是這些型態呢?
- `long long`
- `unsigned int`
- `char`
- `char *`
- ```c
struct student {
char name[20];
int age;
};
```
- ```c
struct Node {
int data;
struct Node *next;
};
```
我們需要對每個型態都寫一個 `my_swap` function 嗎?
```c
void my_swap(long long *a, long long *b);
void my_swap(unsigned int *a, unsigned int *b);
void my_swap(char *a, char *b);
void my_swap(char **a, char **b);
void my_swap(struct student *a, struct student *b);
void my_swap(struct Node *a, struct Node *b);
// ...
// Implementations
void my_swap(long long *a, long long *b) {
long long tmp = *a;
*a = *b;
*b = tmp;
}
// ...
```
## Generic Programming
**Generic** (泛型) 是一種程式設計的技巧
可以讓程式碼更加的彈性、減少重複的程式碼
在 C 語言中
我們可以使用 `void *` 來實現泛型
在寫 Generic function 前
我們先來複習一下 `void *` 的特性
## `void *` 的特性
- `void *` 是一種特殊的 pointer
可以指向任何型態的資料
```c
int a = 1;
float b = 2.0;
char c = '3';
void *ptr;
ptr = &a;
printf("%d\n", *(int *)ptr);
ptr = &b;
printf("%f\n", *(float *)ptr);
ptr = &c;
printf("%c\n", *(char *)ptr);
```
- `void *` 不能直接 dereference
必須先轉型成正確的型態
```c
int a = 1;
void *ptr = &a;
printf("%d\n", *ptr); // Error
printf("%d\n", *(int *)ptr); // Correct
```
## 我們有用過 Generic Function 嗎?
在不知不覺中,我們已經用過很多 Generic Function 了
```c
#include <stdlib.h> // need include <-
int cmp(const void *a, const void *b){
return *(int *)a - *(int *)b;
}
int main() {
int arr[10] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
qsort(arr, 10, sizeof(int), cmp);
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
return 0;
}
```
<br>
其實 **`qsort`** 本身就是一個 Generic Function !
我們來看一下 `qsort` 的 declaration
<br>
```c
void qsort( void *ptr, size_t count, size_t size,
int (*comp)(const void *, const void *) );
```
> [c reference : qsort](https://en.cppreference.com/w/c/algorithm/qsort)
陣列傳入時,會被轉型成 `void *`
- 以 `count` 作為陣列的長度
- 以 `size` 來切割陣列 ( 這邊的 `size` 代表每個元素是多少 bytes )
- 以 `comp` 來決定排序的方式 ( 這邊的 `comp` 是一個 function pointer )
## Generic Function 實作
由 `qsort` 觀察
可以看到 Generic Function 有幾個特性
- `void *` 型態傳入
- 以 `size` 來切割資料
- 以 function pointer 來決定操作
## Generic Swap
那我們可以嘗試實作一個 Generic Swap
> **Hint** :
- `void *` 型態傳入
- 以 `size` 來切割資料
```c
void my_swap(void *a, void *b, size_t size) {
void *tmp = malloc(size);
memcpy(tmp, a, size);
memcpy(a, b, size);
memcpy(b, tmp, size);
free(tmp);
}
```
> [c reference : memcpy](https://en.cppreference.com/w/c/string/byte/memcpy) ( 需要 `#include <string.h>` )
也可以用 `strcpy` 來實作
```c
void my_swap(void *a, void *b, size_t size) {
char *tmp = malloc(size);
strcpy(tmp, a);
strcpy(a, b);
strcpy(b, tmp);
free(tmp);
}
```
或是用 for loop 來實作
```c
void my_swap(void *a, void *b, size_t size) {
char *tmp = malloc(size);
for(int i = 0; i < size; i++){
*((char *)tmp + i) = *((char *)a + i);
*((char *)a + i) = *((char *)b + i);
*((char *)b + i) = *((char *)tmp + i);
}
free(tmp);
}
```
> 因為 `char` 的大小是 1 byte
> 所以我們可以用 loop ㄧ個一個 byte 來交換
## Lab
請實作一個 **Generic Bubble Sort**
並將 :
- `student` 依照 `id` 由小到大排序
```c
struct student {
char name[20];
int id;
int age;
};
```
- `point` 依照 `x` 由小到大排序
```c
struct point {
float x;
float y;
};
```
<br>
**TODO** :
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student {
char name[20];
int id;
int age;
} student;
typedef struct point {
float x;
float y;
} point;
void swap(void *a, void *b, size_t size) {
// TODO
}
void bubble_sort(void *ptr, size_t count, size_t size,
int (*comp)(const void *, const void *)) {
// TODO
}
int cmp_student(/* TODO */) {
// TODO
}
int cmp_point(/* TODO */) {
// TODO
}
int main(){
student students[10] = {
{"Henry", 8, 25},
{"Alice", 1, 18},
{"David", 4, 21},
{"Eason", 5, 22},
{"Jason", 10, 27},
{"Bob", 2, 19},
{"Frank", 6, 23},
{"Cindy", 3, 20},
{"Iris", 9, 26},
{"Gina", 7, 24}
};
point points[10] = {
{19.0, 20.0},
{1.0, 2.0},
{5.0, 6.0},
{3.0, 4.0},
{13.0, 14.0},
{9.0, 10.0},
{15.0, 16.0},
{11.0, 12.0},
{17.0, 18.0},
{7.0, 8.0}
};
bubble_sort(students,/* TODO */);
bubble_sort(points,/* TODO */);
for (int i = 0; i < 10; i++) {
printf("student(name=%s,id=%d,age=%d)\n", students[i].name, students[i].id, students[i].age);
}
printf("====================================\n");
for (int i = 0; i < 10; i++) {
printf("point(x=%f,y=%f)\n", points[i].x, points[i].y);
}
return 0;
}
```
> **Hint** : int 的 bubble sort 如下
```c
void bubble_sort_int(int *arr, int n) {
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (arr[i] > arr[j]) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}
```
ans : https://gist.github.com/jason810496/073978e521feb5cdab41ec81fe56ae6b
結果應該要是這樣 :
```
student(name=Alice,id=1,age=18)
student(name=Bob,id=2,age=19)
student(name=Cindy,id=3,age=20)
student(name=David,id=4,age=21)
student(name=Eason,id=5,age=22)
student(name=Frank,id=6,age=23)
student(name=Gina,id=7,age=24)
student(name=Henry,id=8,age=25)
student(name=Iris,id=9,age=26)
student(name=Jason,id=10,age=27)
====================================
point(x=1.000000,y=2.000000)
point(x=3.000000,y=4.000000)
point(x=5.000000,y=6.000000)
point(x=7.000000,y=8.000000)
point(x=9.000000,y=10.000000)
point(x=11.000000,y=12.000000)
point(x=13.000000,y=14.000000)
point(x=15.000000,y=16.000000)
point(x=17.000000,y=18.000000)
point(x=19.000000,y=20.000000)
```
## 我們知道 Generic Programming 可以做什麼 ?
如 `qsort` 這種演算法可以寫成 Generic Function 來重複使用 <br>
又或是接下來會學到的 `linked list` 也可以寫成 Generic Type 來重複利用 <br>
<br>
:::spoiler Generic Linked List
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Define the student structure
typedef struct student {
char name[20];
int id;
int age;
} student;
// Define the node structure for the linked list
typedef struct Node {
void *data;
struct Node *next;
} Node;
// Function to create a new node with data
Node *createNode(void *data) {
Node *newNode = (Node *)malloc(sizeof(Node));
if (newNode == NULL) {
perror("Memory allocation error");
exit(EXIT_FAILURE);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// Function to insert a new node at the beginning of the linked list
Node *insertNodeAtBeginning(Node *head, void *data) {
Node *newNode = createNode(data);
newNode->next = head;
return newNode;
}
// Function to print a student's information
void printStudent(const void *data) {
const student *s = (const student *)data;
printf("ID: %d, Name: %s, Age: %d\n", s->id, s->name, s->age);
}
// Function to print the linked list
void printList(Node *head, void (*print)(const void *)) {
Node *current = head;
while (current != NULL) {
print(current->data);
current = current->next;
}
}
// Function to free the linked list
void freeList(Node *head) {
Node *current = head;
while (current != NULL) {
Node *next = current->next;
free(current->data);
free(current);
current = next;
}
}
int main() {
// Example usage with the student structure
Node *head = NULL;
// Create and insert student nodes
student *s1 = (student *)malloc(sizeof(student));
strcpy(s1->name, "Alice");
s1->id = 1;
s1->age = 20;
head = insertNodeAtBeginning(head, s1);
student *s2 = (student *)malloc(sizeof(student));
strcpy(s2->name, "Bob");
s2->id = 2;
s2->age = 22;
head = insertNodeAtBeginning(head, s2);
student *s3 = (student *)malloc(sizeof(student));
strcpy(s3->name, "Charlie");
s3->id = 3;
s3->age = 21;
head = insertNodeAtBeginning(head, s3);
// Print the linked list
printf("Linked List of Students:\n");
printList(head, printStudent);
// Clean up the linked list
freeList(head);
return 0;
}
```
:::
<br>
又或是更複雜的資料結構,如 `AVL Tree` 也可以寫成 Generic Type 來重複利用 <br>
:::spoiler Generic AVL Tree
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct AVLNode {
void *data;
struct AVLNode *left;
struct AVLNode *right;
int height;
} AVLNode;
// Function pointer type for data comparison
typedef int (*CompareFunction)(const void *, const void *);
// Function pointer type for data printing
typedef void (*PrintFunction)(const void *);
// Function to get the height of a node
int getHeight(AVLNode *node) {
return (node == NULL) ? 0 : node->height;
}
// Function to update the height of a node
void updateHeight(AVLNode *node) {
if (node != NULL) {
int leftHeight = getHeight(node->left);
int rightHeight = getHeight(node->right);
node->height = 1 + ((leftHeight > rightHeight) ? leftHeight : rightHeight);
}
}
// Function to get the balance factor of a node
int getBalanceFactor(AVLNode *node) {
return (node == NULL) ? 0 : getHeight(node->left) - getHeight(node->right);
}
// Right rotate a subtree rooted with y
AVLNode *rightRotate(AVLNode *y) {
AVLNode *x = y->left;
AVLNode *T2 = x->right;
// Perform rotation
x->right = y;
y->left = T2;
// Update heights
updateHeight(y);
updateHeight(x);
// Return new root
return x;
}
// Left rotate a subtree rooted with x
AVLNode *leftRotate(AVLNode *x) {
AVLNode *y = x->right;
AVLNode *T2 = y->left;
// Perform rotation
y->left = x;
x->right = T2;
// Update heights
updateHeight(x);
updateHeight(y);
// Return new root
return y;
}
// Balance the AVL tree after an insertion or deletion
AVLNode *balanceNode(AVLNode *node) {
// Update height of the current node
updateHeight(node);
// Get the balance factor of this node
int balance = getBalanceFactor(node);
// Left Heavy (Left-Left Case)
if (balance > 1 && getBalanceFactor(node->left) >= 0) {
return rightRotate(node);
}
// Left Heavy (Left-Right Case)
if (balance > 1 && getBalanceFactor(node->left) < 0) {
node->left = leftRotate(node->left);
return rightRotate(node);
}
// Right Heavy (Right-Right Case)
if (balance < -1 && getBalanceFactor(node->right) <= 0) {
return leftRotate(node);
}
// Right Heavy (Right-Left Case)
if (balance < -1 && getBalanceFactor(node->right) > 0) {
node->right = rightRotate(node->right);
return leftRotate(node);
}
// No rotation needed
return node;
}
// Insert a node into the AVL tree
AVLNode *insertNode(AVLNode *node, void *data, CompareFunction compare) {
// Standard BST insertion
if (node == NULL) {
AVLNode *newNode = (AVLNode *)malloc(sizeof(AVLNode));
newNode->data = data;
newNode->left = newNode->right = NULL;
newNode->height = 1;
return newNode;
}
// Perform standard BST insert
int cmpResult = compare(data, node->data);
if (cmpResult < 0) {
node->left = insertNode(node->left, data, compare);
} else if (cmpResult > 0) {
node->right = insertNode(node->right, data, compare);
} else {
// Duplicate data is not allowed
return node;
}
// Update height of the current node
updateHeight(node);
// Balance the tree
return balanceNode(node);
}
// Find target node in the AVL tree
AVLNode *findNode(AVLNode *node, void *target, CompareFunction compare) {
if (node == NULL) {
return NULL;
}
int cmpResult = compare(target, node->data);
if (cmpResult < 0) {
return findNode(node->left, target, compare);
} else if (cmpResult > 0) {
return findNode(node->right, target, compare);
} else {
return node;
}
}
// Function to free the AVL tree nodes
void freeAVLTree(AVLNode *root) {
if (root != NULL) {
freeAVLTree(root->left);
freeAVLTree(root->right);
free(root);
}
}
typedef struct student {
char name[20];
int id;
int age;
} student;
// Compare function for student
int compareStudent(const void *a, const void *b) {
return ((student *)a)->id - ((student *)b)->id;
}
// Compare function for finding student
int compareStudentId(const void *target_id, const void *p) {
return *((int *)target_id) - ((student *)p)->id;
}
// Print function for student
void printStudent(const void *data) {
student *s = (student *)data;
printf("Name: %s, ID: %d, Age: %d\n", s->name, s->id, s->age);
}
// Print AVL tree in-order
void printAVLTree(AVLNode *root, PrintFunction print) {
if (root != NULL) {
printAVLTree(root->left, print);
print(root->data);
printAVLTree(root->right, print);
}
}
int main() {
AVLNode *root = NULL;
student students[10] = {
{"Henry", 8, 25},
{"Alice", 1, 18},
{"David", 4, 21},
{"Eason", 5, 22},
{"Jason", 10, 27},
{"Bob", 2, 19},
{"Frank", 6, 23},
{"Cindy", 3, 20},
{"Iris", 9, 26},
{"Gina", 7, 24}
};
// Insert 10 students into the AVL tree
for (int i = 0; i < 10; i++) {
printf("Inserting student %d %s\n", students[i].id, students[i].name);
root = insertNode(root, &students[i], (CompareFunction)compareStudent);
}
// Print the AVL tree in-order
printf("Printing AVL tree in-order:\n");
printAVLTree(root, (PrintFunction)printStudent);
// Check student id = 5
printf("Checking student id = 5\n");
int target_id = 5;
AVLNode* result = (AVLNode *)findNode(root, &target_id, (CompareFunction)compareStudentId);
if (result == NULL) {
printf("Student not found\n");
} else {
printStudent(result->data);
}
// Check student id = 99
printf("Checking student id = 99\n");
target_id = 99;
result = (AVLNode *)findNode(root, &target_id, (CompareFunction)compareStudentId);
if (result == NULL) {
printf("Student not found\n");
} else {
printStudent(result->data);
}
// Clean up
freeAVLTree(root);
return 0;
}
```
:::