8월 30, 2023

C Node 구조체로 연결, 제거, 추가 해보기

특정 자료구조를 100% 이해하기 가장 좋은 방법은 그것을 코드로 구현해보는 것이고 그럴 때 가장 좋은 방법은 C를 사용하는 것이라고 생각한다. 

 

나는 연결리스트를 C로 구현해보았다. 먼저 직접 코드를 작성해본 뒤에 아래 코드를 참고해보면 좋을 것 같다. 많은 언어 중에 Java나 C++이 아닌 low level 언어인 C를 사용하면 좋다고 한 이유는 가장 제약이 많이 따르는 만큼 자료구조에 대한 이해를 하기 좋기 때문이다. 또한 익숙한 class가 아닌 구조체를 사용하면서 코드 연습을 가장 잘 할 수 있는 언어이기도 하다. 

 

주로 C에서 연결리스트를 구현할 때에는 node라는 struct를 하나 만들어주고 그 안에 몇 번째인지를 알려주는 index, 해당 node가 들고 있는 값을 알려주는 data, 그리고 다음 node와 연결해주기 위한 struct node * 형식의 next를 가진다. 이 세 가지 구성을 잘 사용하면 우리가 원하는 방식의 linked list를 구현할 수 있다. 

 

먼저 위에 언급한 것처럼 node struct부터 만들어주겠다.

 

typedef struct node {
	int index;
	int data;
	struct node* next;
}NODE;

 

다음으로 만든 구조체를 활용하여 초기 세팅을 해주자.

 

NODE* head = NULL;


NODE* init(int data) {
	NODE* tmp = (NODE*)malloc(sizeof(NODE));

	tmp->data = data;
	tmp->next = NULL;
	return tmp;
}

 

맨 처음에는 node의 head가 가리키고 있는 것이 없으므로 NULL로 세팅을 해준 뒤 하나의 노드를 만들기 위해 init이라는 함수를 사용해주자. 우리는 사용자가 원하는 값으로 node를 세팅해줄 것이므로 인자로 data라는 parameter를 받아와서 node의 data를 해당 값으로, 그리고 다음 node는 NULL 값으로 연결해주자. 처음에 malloc의 과정이 필요하다. C를 사용하기 때문에 이러한 과정이 다소 귀찮게 느껴질 수는 있으나 이런 과정을 계속 해보다 보면 코딩 실력도 늘고 자료구조에 대한 이해도도 올라간다.

 


다음으로는 원하는 data가 있는 node를 찾는 과정이다. search 함수를 만들어 사용하겠다.

NODE* search(int data) {
	//data 노드 찾기
	//있으면 해당 노드의 주소를  return
	//없으면 NULL return 하기 
	NODE* tmp = head;
	while (tmp) {
		if (tmp->data == data) {
			return tmp;

		}
		tmp = tmp->next;
	}
	return NULL;
}

처음 head node부터 시작하여 쭉 노드를 순회하다가, 원하는 data 값과 노드의 데이터 값이 일치하면 해당 노드를 반환해주는 형식이다. 만약 일치하지 않는다면 계속 다음 노드를 순회하면서 찾을 때까지 순회하고 결국 찾지 못하면 NULL을 반환해준다.


다음으로 새로운 node를 추가하는 과정이다.

void add(int prev, int data) {
	//prev 노드 앞에 data 추가 (삽입)
	//예 10-20-30
	//add(20,100); 10-100-20-30
	//prev가 -1이면 맨 뒤에 추가 
	//최초 노드 생성인 경우 (head 로 등록)
	//2. 맨 앞에 삽입하는 경우
	//3. 중간/맨 뒤에 삽입하는 경우 

	NODE* new_node = init(data);
	NODE* tmp = head;
	NODE* find = NULL;
	if (!head) {
		head = new_node; return;
	}

	else if (prev != -1 ) {
		find = search(prev);
		if (!find) {
			printf("해당 노드가 없습니다.\n");
			return;
		}
		if (find == head) {
			new_node->next = find;
			head = new_node;
			return;
		}
		else {
			NODE* prev = tmp;
			while (prev) {
				if (prev->next == find) {
					break;
				}
				prev = prev->next;
			}
			prev->next =new_node;
			new_node->next = find;
			return;
		}
	}

	if (prev == -1) {
		while (tmp->next) {
			tmp = tmp->next;
		}
		tmp->next = new_node;
	}
	}

가장 마지막 노드에 추가하는 것이 아니라 원하는 prev 노드를 받아와 추가를 하는 것이기 때문에 조건을 나누어서 작성했다.


노드를 추가했다면 삭제하는 것 또한 필요하다.

void remove(int data) {
	//index번 위치에 노드를 삭제하는것
		//삭제 실패(미등록 노드를 삭제할 경우) 시 "미등록 노드"를 출력 
		//해당 data node 삭제 
	NODE* tmp = head;
	NODE* find = search(data);
	if (!head) {
		printf("제거할 노드가 없습니다.");
		return;
	}
	if (!find) {
		printf("미등록 노드입니다");
		return;
	}
	if (find == head) {
		head = head->next;
		free(tmp);
		return;
	}
	NODE* prev = tmp;
	while (prev) {
		if (prev->next == find) {
			break;
		}
		prev = prev->next;
	}
	prev->next = find->next;
	free(find);
}

이 또한 어떠한 노드를 삭제하냐에 따라서 경우가 달라질 수 있기 때문에 경우를 나누어서 처리했다. (만약 읽어보면서 이해되지 않는 부분이 있다면 댓글을 남겨주시면 답변해드릴게요! )


다음으로는 모든 node들을 순회하면서 값을 print 해주는 함수이다.

void print_all() {
	//모든 노드를 출력하세요
	NODE* tmp = head;
	while (tmp) {
		printf("%d ", tmp->data);
		tmp = tmp->next;
	}
}

마찬가지로 head부터 시작하여 해당 node의 data를 출력하고 다음 노드로 넘겨주는 과정을 반복한다.


남의 코드를 읽어보는 과정과 자신이 직접 짜본 코드는 매우 다르다. 아래 전체 코드를 첨부하였고 직접 해보면서 자료구조에 대한 이해를 늘려보는 것을 매우매우 추천한다! 

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

typedef struct node {
	int index;
	int data;
	struct node* next;
}NODE;

NODE* head = NULL;

//가장 앞 노드 삭제하는 경우 생각

NODE* init(int data) {
	NODE* tmp = (NODE*)malloc(sizeof(NODE));

	tmp->data = data;
	tmp->next = NULL;
	return tmp;
}
NODE* search(int data) {
	//data 노드 찾기
	//있으면 해당 노드의 주소를  return
	//없으면 NULL return 하기 
	NODE* tmp = head;
	while (tmp) {
		if (tmp->data == data) {
			return tmp;

		}
		tmp = tmp->next;
	}
	return NULL;
}
void add(int prev, int data) {
	//prev 노드 앞에 data 추가 (삽입)
	//예 10-20-30
	//add(20,100); 10-100-20-30
	//prev가 -1이면 맨 뒤에 추가 
	//최초 노드 생성인 경우 (head 로 등록)
	//2. 맨 앞에 삽입하는 경우
	//3. 중간/맨 뒤에 삽입하는 경우 

	NODE* new_node = init(data);
	NODE* tmp = head;
	NODE* find = NULL;
	if (!head) {
		head = new_node; return;
	}

	else if (prev != -1 ) {
		find = search(prev);
		if (!find) {
			printf("해당 노드가 없습니다.\n");
			return;
		}
		if (find == head) {
			new_node->next = find;
			head = new_node;
			return;
		}
		else {
			NODE* prev = tmp;
			while (prev) {
				if (prev->next == find) {
					break;
				}
				prev = prev->next;
			}
			prev->next =new_node;
			new_node->next = find;
			return;
		}
	}

	if (prev == -1) {
		while (tmp->next) {
			tmp = tmp->next;
		}
		tmp->next = new_node;
	}
	}

void remove(int data) {
	//index번 위치에 노드를 삭제하는것
		//삭제 실패(미등록 노드를 삭제할 경우) 시 "미등록 노드"를 출력 
		//해당 data node 삭제 
	NODE* tmp = head;
	NODE* find = search(data);
	if (!head) {
		printf("제거할 노드가 없습니다.");
		return;
	}
	if (!find) {
		printf("미등록 노드입니다");
		return;
	}
	if (find == head) {
		head = head->next;
		free(tmp);
		return;
	}
	NODE* prev = tmp;
	while (prev) {
		if (prev->next == find) {
			break;
		}
		prev = prev->next;
	}
	prev->next = find->next;
	free(find);
}

void print_all() {
	//모든 노드를 출력하세요
	NODE* tmp = head;
	while (tmp) {
		printf("%d ", tmp->data);
		tmp = tmp->next;
	}
}
void main() {
	int data, select, prev;
	NODE* result = NULL;
	while (1) {
		print_all();
		printf("1. 추가 \n2. 삭제 \n3. 검색 \n0.종료 \n입력: ");
		scanf_s("%d", &select);
		switch (select) {
		case 1:
			printf("새정수: ");
			scanf_s("%d", &data);
			printf("어느 노드 앞에 추가?(마지막은 -1) : ");
			scanf_s("%d", &prev);
			add(prev, data);
			break;
		case 2:
			printf("삭제할 정수를 입력하세요...\n");
			scanf_s("%d", &data);
			remove(data);
			break;
		case 3:
			printf("검색할 정수의 입력하세요...\n");
			scanf_s("%d", &data);
			result = search(data);
			if (result) {
				printf("%d(은)는 있습니다. \n", data);
			}
			else {
				printf("%d(은)는 미등록 노드입니다. \n", data);
			}
			break;
		case 0:
			exit(0);
		}
		system("pause");
		system("cls");
	}
}

8월 30, 2023

Github Issue란? 협업 시 활용법 & Issue 작성법

협업을 위해 자주 사용하는 Github, 그 중에서도 유용하게 소통을 위해 사용할 수 있는 Github Issue 기능에 대해 알아보자. 

 

Github issue는 여러 가지로 활용될 수 있는데 말 그대로 해당 repo에 대한 issue를 보고할 수도 있고, 나의 경우에는 개발 협업 프로젝트를 진행할 때 bug를 report하거나 현재 나의 진행경과를 팀원들에게 알려주거나, 해야 할 개발 사항을 보고하는 데 유용하게 사용했다. 



Github Issue에 들어가면 위와 같이 Code, Issues, Pull Requests, Actions, Projects, Wiki, Security, Insights, Settings와 같은 Tab을 볼 수 있다. 내가 현재 들어간 repository에는 open issues가 존재해서 Issues 옆에 2라는 숫자가 보이는데, 처음 만든 repository의 경우 아무것도 보이지 않을 것이다.

 

우선 새로운 Issue부터 만들어주자.


위와 같은 부분에서 오른쪽의 New issue 버튼을 클릭해주면 새로운 issue를 생성해줄 수 있다. 



우선 Title 부분에서는 말 그대로 Issue의 Title이다. 아래 Leave a comment 부분에는 더 자세하게 issue의 내용을 작성할 수 있다. 

 

Issue를 잘 작성하는 사람들은 오른쪽 메뉴 부분을 유용하게 사용한다.


Assignees 부분에는 해당 내용을 개발해야 하는 사람들, 즉 해당 issue에 할당된 사람을 클릭해준다. 특정 기능을 개발할 때에는 여러 명이 동시에 assign 되는 경우가 있는데 그럴 경우 한 눈에 누가 assign 되었는지 보기 편하다. 꼭 여러명이 assign 되지 않아도 나는 팀원들에게 '내가 이 기능 개발할게' 라는 것을 알릴 목적으로 나 스스로를 assginee로 지정하기도 했었다.



설정과 비슷한 아이콘을 클릭하면 위와 같이 'Assign up to 10 people to this issue'라고 나오게 되고 아래 추천 ID가 나온다. 해당 ID를 클릭해도 되고 직접 검색해서 추가해도 된다. 



다음으로 Labels 부분을 누르면 해당 Issue를 어떤 용도로 활용할 것인지를 클릭하는 것이다. bug의 경우 '내가 이거 써봤는데 ~한 bug 발견했어! 고쳐줘!' 라는 느낌으로 사용할 수 있다. 내가 협업 프로젝트를 하면서 잘 사용했던 enhancement label은 새로운 기능이나 요청사항을 제안하는 느낌으로 사용할 수 있다. 새로운 기능을 개발해야 하면 우리 이거 개발해야 되고 assignee는 ~야 라고 알려주는 식이다. 

 

또한 자신이 어떠한 기능을 개발하고 있는데 어려움에 마주하면 'help wanted'라는 label로 다른 멤버에게 도움을 요청할 수도 있다. 

 

그리고 Labels는 원하면 새로 만들 수도 있고 수정도 가능하다. 'Edit labels'를 눌러 수정이 가능하니 원하는 새로운 label이 있다면 새롭게 추가하자. 나는 'refactoring' label을 새롭게 만들어서 사용한 적이 있다. 


아래 Projects와 Milestone의 경우에는 해당 Issue가 어디에 해당되는 것인지를 나타내주는 칸이라고 생각하면 된다. 나의 경우에는 프로젝트 deadline 전까지 여러 milestone을 생성하고 to-do list 느낌으로 사용하기도 했다. 

 

물론 아무것도 클릭하지 않고 title만 작성해도 issue submit이 가능해진다. 

 

작성을 하고 'Submit new issue'를 누르게 되면 issue 작성이 완료된 것이다. 만약 issue를 작성했는데 오른쪽 label을 작성하는 것을 까먹었다? 해도 걱정할 필요가 없다. 작성된 issue를 누르고 이후 label을 추가해도 된다. 

 

특히 협업 프로젝트를 할 때 issue 작성의 중요성은 여러 번 강조해도 지나치다. 팀원들과 남아있는 task가 무엇인지 remind하기도 좋으며, bug가 있거나 도움을 요청할 수도 있기 때문이다.

 

issue를 submit 했다면 해당 issue는 open issue로 올라가게 된다. 예를 들어 내가 '배포 직전 해야 할 것' 이라는 title로 issue를 올렸다고 가정해보자. 



해당 issue를 클릭해 들어가 보면 위와 같이 Leave a comment라고 되어 있는 부분이 있을 것이다. 다른 사람의 issue에 comment를 추가할 수도 있고, 만약 해당 issue가 해결되었다면 'Close issue'를 눌러 issue를 닫을 수도 있다. 

 

그러면 closed 된 issue는 어디서 볼 수 있는가?

 

다시 issue tab으로 들어가보면



이렇게 open 된 issue들의 개수와 closed 된 issue들의 개수를 볼 수 있다. closed 부분을 클릭하면 닫혀진 issue들도 모두 확인할 수 있다. 

 

아주 가끔 실수로 issue를 close하기도 하는데, 그런 경우에 다시 open issue로 만들고 싶다면 어떻게 할까? 간단하다. 해당 issue를 다시 클릭해서 들어가면,



아까 봤던 것과 비슷한 이미지를 확인할 수 있는데 여기의 'reopen issue'를 누르면 된다. 단순히 실수로 issue를 닫았을 때 뿐만 아니라, 내가 해결했다고 생각했는데 다시 문제가 재발했거나 비슷한 새로운 문제를 찾았을 때도 사용할 수 있다. 

 

이런 식으로 Github issue는 단순해보이지만 다양한 기능을 가지고 있다. 이 Github issue를 잘 쓸수록 협업 프로젝트의 quality를 높일 수 있다고 생각한다. 모두 issue 사용법에 익숙해지고 불편하더라도 자주 활용할 수 있도록 노력하자!