크래프톤 정글 8기 37일 차 TIL 링크드리스트 구현하기 - C언어를 이용해 링크드 리스트를 직접 구현해보려고 한다. 아무래도 정글에서 준 과제는 링크드 리스트의 특성을 이용해 특정 함수를 구현하는 것을 요구하는데 과제만 해결하는 것을 넘어 직접 구현을 해 보면서 익히는 것이 더 좋을 것 같아서 직접 구현을 진행해 보려 한다. 링크드 리스트에 대해서 부족하지만 정리했던 내용을 확인하려면 이 전 포스팅 했던 글을 확인하는 것도 좋을 것 같다.
2025.04.10 - [크래프톤 정글] - 크래프톤 정글 8기 - TIL 31일차 (링크드 리스트 - C)
링크드 리스트 구현 전 준비
본격 적으로 구현을 시작하기 전 준비해야 할 사항이 있다. 먼저 나는 구현의 검증을 담당해 줄 테스트 코드를 미리 작성해 두었다. 그리고 그 테스트 코드를 실행하기 위해서는 mununit.h라는 테스트용 헤더 파일을 추가해야 한다. GPT의 힘으로 순수 minunit의 기능에 더해서 몇 가지 기능을 추가했다.
테스트 코드 검증용 MinUnit 포함
minunit.h
// minunit.h
#ifndef MINUNIT_H
#define MINUNIT_H
#include <stdio.h>
#include <time.h>
extern int tests_run;
extern int tests_failed;
#define mu_assert(message, test) do { if (!(test)) return message; } while (0)
#define mu_assert_eq_int(expected, actual) do { \
if ((expected) != (actual)) { \
static char msg[100]; \
sprintf(msg, "❌ Expected %d but got %d", (expected), (actual)); \
return msg; \
} \
} while (0)
// 확장된 테스트 실행 매크로
#define mu_run_test(test) do { \
clock_t start = clock(); \
printf("▶ Running %s...\n", #test); \
char *message = test(); tests_run++; \
clock_t end = clock(); \
double elapsed = (double)(end - start) / CLOCKS_PER_SEC * 1000; \
if (message) { \
tests_failed++; \
printf("❌ %s failed: %s\n\n", #test, message); \
} else { \
printf("✅ Passed %s (%.2f ms)\n\n", #test, elapsed); \
} \
} while (0)
extern int tests_run;
#endif
위 코드를 이용해 minunit.h 라는 헤더 파일을 만들어서 우리가 만들려고 하는 linkedlist.c 파일이 있는 공간에 위치시킨 상태에서 코드 작성을 시작할 것이다.
linkedlist.c 파일 초기 설정
#include <stdio.h>
#include <stdlib.h>
#include "minunit.h"
// ---[구현할 기능 정의]--- //
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* create_node(int data);
void insert_end(Node** head, int data);
void print_list(Node* head);
void free_list(Node* head);
void insert_front(Node** head, int data);
void delete_node(Node** head, int data);
Node* find_node(Node* head, int data);
void insert_at(Node** head, int index, int data);
void reverse_list(Node** head);
void sort_list(Node** head);
// --- [링크드 리스트 구현] --- //
// --- [메인 & 테스트 코드] --- //
static char * test_insert_end() {
Node* head = NULL;
insert_end(&head, 10);
insert_end(&head, 20);
insert_end(&head, 30);
mu_assert("error: head->data != 10", head->data == 10);
mu_assert("error: head->next->data != 20", head->next->data == 20);
mu_assert("error: head->next->next->data != 30", head->next->next->data == 30);
free_list(head);
return 0;
}
static char * test_insert_front() {
Node* head = NULL;
insert_front(&head, 10);
insert_front(&head, 20);
insert_front(&head, 30);
// 리스트: 30 -> 20 -> 10
mu_assert("insert_front failed at head", head->data == 30);
mu_assert("insert_front failed at 2nd", head->next->data == 20);
mu_assert("insert_front failed at 3rd", head->next->next->data == 10);
free_list(head);
return 0;
}
static char * test_delete_node() {
Node* head = NULL;
insert_end(&head, 10);
insert_end(&head, 20);
insert_end(&head, 30);
delete_node(&head, 20);
// 리스트: 10 -> 30
mu_assert("delete_node failed at head", head->data == 10);
mu_assert("delete_node failed at 2nd", head->next->data == 30);
mu_assert("delete_node failed: list too long", head->next->next == NULL);
free_list(head);
return 0;
}
static char * test_delete_from_empty_list() {
Node* head = NULL;
delete_node(&head, 10); // 아무 일도 없어야 함
mu_assert("delete_node on empty list should keep head NULL", head == NULL);
return 0;
}
static char * test_find_node() {
Node* head = NULL;
insert_end(&head, 5);
insert_end(&head, 10);
insert_end(&head, 15);
Node* found = find_node(head, 10);
mu_assert("find_node failed to find 10", found != NULL && found->data == 10);
Node* not_found = find_node(head, 99);
mu_assert("find_node should return NULL for 99", not_found == NULL);
free_list(head);
return 0;
}
static char * test_find_in_empty_list() {
Node* head = NULL;
Node* found = find_node(head, 10);
mu_assert("find_node on empty list should return NULL", found == NULL);
return 0;
}
static char * test_insert_at() {
Node* head = NULL;
insert_end(&head, 1);
insert_end(&head, 3);
insert_at(&head, 1, 2); // 1 -> 2 -> 3
mu_assert("insert_at failed at 1", head->data == 1);
mu_assert("insert_at failed at 2", head->next->data == 2);
mu_assert("insert_at failed at 3", head->next->next->data == 3);
free_list(head);
return 0;
}
static char * test_insert_at_out_of_bounds() {
Node* head = NULL;
insert_at(&head, 3, 100); // 인덱스 3은 잘못된 위치, 아무 일도 없어야 함
mu_assert("insert_at out of bounds should not modify list", head == NULL);
return 0;
}
static char * test_reverse_list() {
Node* head = NULL;
insert_end(&head, 1);
insert_end(&head, 2);
insert_end(&head, 3);
reverse_list(&head); // 3 -> 2 -> 1
mu_assert("reverse_list failed at 1", head->data == 3);
mu_assert("reverse_list failed at 2", head->next->data == 2);
mu_assert("reverse_list failed at 3", head->next->next->data == 1);
free_list(head);
return 0;
}
static char * test_reverse_single_node() {
Node* head = NULL;
insert_end(&head, 42);
reverse_list(&head);
mu_assert("reverse single node failed", head != NULL && head->data == 42 && head->next == NULL);
free_list(head);
return 0;
}
static char * test_reverse_empty_list() {
Node* head = NULL;
reverse_list(&head); // 아무 문제 없어야 함
mu_assert("reverse_list on empty list should keep head NULL", head == NULL);
return 0;
}
static char * test_sort_list() {
Node* head = NULL;
insert_end(&head, 30);
insert_end(&head, 10);
insert_end(&head, 20);
sort_list(&head); // 10 -> 20 -> 30
mu_assert("sort_list failed at 1", head->data == 10);
mu_assert("sort_list failed at 2", head->next->data == 20);
mu_assert("sort_list failed at 3", head->next->next->data == 30);
mu_assert("sort_list failed: list too long", head->next->next->next == NULL);
free_list(head);
return 0;
}
static char * test_sort_empty_list() {
Node* head = NULL;
sort_list(&head); // 아무 문제 없어야 함
mu_assert("sort_list on empty list should keep head NULL", head == NULL);
return 0;
}
static char * all_tests() {
mu_run_test(test_insert_end);
mu_run_test(test_insert_front);
mu_run_test(test_delete_node);
mu_run_test(test_find_node);
mu_run_test(test_insert_at);
mu_run_test(test_reverse_list);
mu_run_test(test_sort_list);
mu_run_test(test_delete_from_empty_list);
mu_run_test(test_find_in_empty_list);
mu_run_test(test_insert_at_out_of_bounds);
mu_run_test(test_reverse_single_node);
mu_run_test(test_reverse_empty_list);
mu_run_test(test_sort_empty_list);
if (tests_failed > 0) {
return "Some tests failed.";
}
return 0;
}
int tests_run = 0;
int tests_failed = 0;
int main(void) {
char *result = all_tests();
printf("========================================\n");
printf("Tests run: %d\n", tests_run);
printf("Tests passed: %d\n", tests_run - tests_failed);
printf("Tests failed: %d\n", tests_failed);
if (result != 0) {
printf("❌ TESTS FAILED\n");
return 1;
}
printf("✅ ALL TESTS PASSED\n");
return 0;
}
테스트 코드를 모두 한 파일 안에 몰아 두었더니 코드 길이가 매우 길어져서 확인하고 싶으면 위의 더 보기 버튼을 눌러서 코드를 확인하면 될 것이다. 만약 가능하다면 테스트 파일들만 따로 분리하고 ex) test_linkedlist.c 분리한 파일에서 linked list 구현 기능을 불러와서 사용해도 좋다. 다음 포스팅부터 본격적으로 구현을 진행해 보겠다.
'크래프톤 정글' 카테고리의 다른 글
링크드리스트(LinkedList) 구현 (C언어) : Part.3 - insert_front, print_list, find_node (0) | 2025.04.16 |
---|---|
링크드리스트(LinkedList) 구현 (C언어) : Part.2 - insert_end, free_lsit (0) | 2025.04.16 |
보이어-무어 문자열 검색 알고리즘 (0) | 2025.04.15 |
KMP (Knuth-Morris-Pratt) 문자열 검색 알고리즘 (0) | 2025.04.14 |
그래프톤 정글 8기 - 5주차 시작 전 회고 (0) | 2025.04.14 |