크래프톤 정글 TIL : 0729

lazyArtisan·2024년 7월 29일
0

정글 TIL

목록 보기
29/147

📚 Journal


어떤 동기가 "어떻게 해야 문제 풀이 방법을 떠올려낼 수 있냐"고 물어봤다.
내 코가 석자지만 일단 생각나는대로 대답했는데,
스스로도 생각을 더 정리해보면 좋을만한 질문일 것 같아 몇 줄 적어본다.

1.

코딩 테스트용 문제를 푸는 것을 포함하여,
어떤 코드를 작성하더라도 이 두 가지는 필수적으로 알아야 한다.

  1. 내가 명령하고자 하는 것이 무엇인가?
  2. 명령에 무슨 변수, 자료형, 알고리즘 등이 필요한가?

이 두 가지를 잘 알아내려면 또 다시 두 가지가 필요하다.

  1. 문제에서 무엇을 요구하는지 알아내는 사고력
  2. 비슷한 문제를 풀이해봤던 경험

2.

사고력은 단기간에 늘리기도 어렵고, 재능의 차이도 큰 영역이라고 생각한다.
다만 한 가지 확실한 것은, 정확한 방법으로 꾸준하게 노력한다면
조금씩이나마 늘기는 한다는 것이다. 마치 물방울이 바위를 뚫는 것처럼.

쉽고 편안한 문제들만 풀려고 하지 말고,
어렵고 불편한 문제들에서 어떻게든 단서를 찾아내어보려는 연습을 꾸준히 하다보면
사고력은 분명 성장한다.

어려운 책을 읽고, 어려운 문제를 풀고, 이해하기 위해 애쓰고,
똑똑한 사람들의 학습 방식을 모방하고, 스스로를 피드백하고,
하루종일 어떤 문제를 붙잡고 고민하다보면 어느새 성장한 자신을 만날 수 있다.

다만 그 성장의 폭이 자신이 기대하는 정도보다 작을 수 있고,
언제나 자신보다 적은 노력(혹은 높은 확률로, 더 열심히)으로
저 멀리 앞서가는 사람들이 존재한다는 것은 알고 있어야 하겠지만.

3.

경험은 사고력을 쌓아가는 과정에서 자연스럽게 따라온다.
인간의 뇌는 조금 변태같아서 힘들게 배울수록 기억을 잘 하기 때문이다.

기억을 잘 하면 다음에 비슷한 문제를 풀 때
풀이를 응용하거나 실마리를 잡을 수 있다.

무엇을 기억하느냐도 중요하다.
더 깊은 생각, 더 원론적인 생각을 할 수록
더 많은 문제에 적용할 수 있는 경험이 생긴다.

A -> Z로 결론이 났으니 끝. 이 아니라

A를 분해해보니 B가 되었고 C가 떠올랐는데 D를 시도해봤더니...
라는 식으로 사고 과정을 쪼개어
건질만한 것이 있는지, 틀린 점은 없었는지 점검해보아야 한다.

단순하게 '전부 쪼개어서 분석해본다' 정도로 생각하고 실천해보아도 좋을 것이다.

무엇을 어떻게 복기할지 생각하는 것마저도 재능의 영역이긴 하지만,
경험만큼은 방법론과 근성이 조금이나마 힘을 쓸 수 있다.
시간은 누구에게나 똑같이 주어지기 때문이다.
그마저도 재능 있는 사람들은 남들 5배속, 10배속으로 보긴 하지만.

4.

너무 재능 재능거리면서 암울하게 굴긴 했는데,
명백한 사실을 없는 것마냥 취급하고 싶진 않았다.

그래도 어쩌겠는가, 삶이 아무리 불공평하고 아니꼬와도
하고 싶은게 있으면 하는거고 해야 하는게 있으면 하는거지.

그리고 원래 인생의 모든게 그렇듯이 남 일을 말하기엔 쉽고
내 삶에 적용하긴 어려운 법인 것 같다.
나도 사고력이랑 경험 부족해서 더 열심히 해야되는데 안 함.



📝 정글 코드리뷰 강의 - 류석영 교수


저번에 유튜브 링크 줬던거 오늘 보고 필기

구글 사례

  • TDD (Test-Driven Development)
    - 필요한 것과 구현 사이엔 괴리가 있다.
    - 그래서 테스트를 코드랑 조화시킨다.
  • CI (Continuous integration)
    - 테스트 안 돌리면 커밋도 못함.
  • Pair Programming
    - 두 명이 같이 앉아서 코딩하면 주니어가 빨리 배울 수 있음
    - 이런 라이브러리 써볼까? 구글링은 어떻게 할까? 등...
  • Code reivew
    - 코드 감시하면 더 잘하게 된다
  • LinkedIn : CD (Continuous deployment)
    - 구글에 있던 Kevin Scott이라는 사람이 링크드인으로 이직했는데 코드들이 답도 없어서 2달 동안 서비스 문 닫고 CD 개발 이식했다고 함.

코드 리뷰란 무엇인가?

  • software development workflow의 일부분
  • 동료한테서 이 코드 올려도 될까요? 피드백 받는거
  • 이런 건 아님
    - 소프트웨어 설계를 어떻게 할까? (이런건 다같이 앉아서 회의해야되는거)
    - 우리 서비스에 어떤 기능을 넣을까?

코드 리뷰가 왜 필요한가?

  • 당신의 CL(바꾼거)을 이해하기 쉽게 만듬.
    - 당신의 동료가 당신의 코드를 이해할 수 있어야 함.
  • 당신이 CL에 동료가 남긴 의견으로부터 tips & lessons을 배움.
    - 숙련된 개발자로부터 코딩 스타일과 팁을 배움.
    - 당신의 팀이 공통된 코딩 스타일을 공유할 수 있음.
  • 결함을 줄일 수 있음.
  • 당신의 coding decision에 대한 개발 역사를 보관함. (다른 사람도 알게되니까)
    - 동료의 의견은 코드 설계와 결정 사항에 대해 이해하는 데 큰 도움 됨
    - 새로 온 개발자가 committed logs와 의견으로부터 코드의 구조와 결정 사항 이해할 수 있음
  • 당신의 코드에 일관적인 코딩 스타일을 유지할 수 있음.
    - 새로 온 개발자가 기존의 코딩 스타일을 따를 수 있도록 도와줌.
    - 일관적인 코딩 스타일은 이후에 코드를 refactoring하거나 디버깅할 때 큰 도움이 됨.

좋은 코드 리뷰란?

: 이건 ~한 이유로 ~식으로 바꾸는게 좋습니다. (+근거 레퍼런스까지 첨부)

코드 리뷰가 일으킬 수 있는 문제

  • 거칠고 무례한 의견 때문에 CL owner가 의욕 상실
  • 리뷰가 늦어지면 개발 기간이 늦어짐
  • 코드 리뷰를 제대로 하려면 시간이 걸림
  • 경험이 부족한 개발자의 잘못된 CL을 리뷰하느라 숙련된 개발자의 시간이 허비될 수 있음.
  • 코드 리뷰를 위해서는 어느 정도 숙련된 개발자가 필요
    -> 그래서 이런건 코드 리뷰를 잘해주는 문화가 필요

구글 코드 스타일 가이드

  • 반드시 가이드 라인 맞춰야 됨 (안 맞추면 리뷰로 혼남) (일부 예외 허용)
  • 그 코드 언어의 전문가 / 프로젝트 오너 이 둘은 코드 리뷰 꼭 해야됨
  • 코드는 쓰는 사람이 아니라 읽는 사람을 위한 것이다 (암묵적 형변환 같은거 나중에 에러나면 찾기 힘듬)
  • 새로 들어온 사람들도 이미 있는 코드 스타일에 맞춰주셈
  • (특히 C++ 같은거) 내가 새로운 기능 안다고 뽐내면서 막 쓰지 마라
  • 당장 개발 일정 급하면 최적화는 살짝 후순위로 둬도 된다.

스타일 가이드 정착시키는 법

  • 도구를 써서 스타일 통일시키기
  • Readability & ownership이 중요하다

코드 잘 짜는 팁

  • 함수 작고 명확하게 짜라

Why Is Testing So Important?

  • Testing Rocks! Debug Sucks!
    - 디버깅은 보통 문제를 찾는 데 엄청 시간이 오래 걸림.
    - 테스팅은 새로 작성한 코드에서도 결함을 검출할 수 있음.
    - 테스팅은 테스트 코드를 필요로 하기 때문에, 유지보수 부담을 줄임.
  • Project Scalability
    - 새로 온 개발자도 테스트 코드를 잘 작성해서 프로젝트에 기여할 수 있음.
    - 동료나 외부 기여자에게서 도움을 받기에 가장 적합함

Testing Types

  • Unit Testing
    - 개별 함수, 인터페이스, 클래스 테스트
    - 무조건 해야됨. 기본임.
    - 근데 함수같은건 작동 환경에 따라 달라지기 때문에 충분치 않음
  • Integration Testing
    - 테스트 환경에서 잘 작동하는지 테스트
  • Regression Testing
    - 예전거하고 비슷하게 작동하는지 테스트

리팩토링 해야하는 이유

  • SW 설계 개선
    - Refactoring이 없으면 프로그램의 설계는 낡아짐
    - 설계가 좋지 않은 코드는 보통 같은 일을 하는데 코드가 길고, 같은 일을 여러 곳에서 함
  • 이해하기 쉬움
    - 대부분의 SW 개발 환경에서, 누군가 언젠가는 당신의 코드를 읽어야 할 때가 오기 때문에, 그들을 위해 이해하기 쉬운 코드를 작성해야 함.
  • 결함 검출할 때도 있음.
  • 프로그램 속도 향상
    - 프로그램에 대해 더 잘 이해하기 때문에


📝 배운 것들


🏷️ for문 조건과 증감만 사용하기

for (; root; root = root->left)
  • 초기화 부분: 없음 (; 앞 부분이 비어 있음).
  • 조건 부분: root가 NULL이 아닐 때까지 반복.
  • 증감 부분: root를 root->left로 업데이트하여 계속 왼쪽 자식 노드로 이동.

🏷️ 후위 연산자 제대로 쓰는 법

output[i++] = node;

연산이 이루어진 이후에 i가 증가하게 할 수 있다.



📦 자료 구조 구현 과제


📌 Q2_E_BT

int maxHeight(BTNode *node)
{
    // NULL이 아니라면 그냥 넘어간다
    // 꼬리 노드의 자식이라면 return -1
    // 모든 maxheight는 여기서 시작한다
    if(node==NULL)
        return -1;

    // l과 r에서 maxheight 받아오기
    int l = maxHeight(node->left);
    int r = maxHeight(node->right);

    // l과 r 중 최댓값 반환
    // 자식이 있는 노드는 여기서 return한다
    if (l>r){
        return l + 1;
    } else {
        return r + 1;
    }
}

옆자리 잘하는 동기 코드 본 다음 외워서 풀었다.
사고 과정이 어땠는지 물어봤는데 결국 경험인듯.
재귀 함수 많이 안 풀어보긴 했다.
다 풀면 재귀 함수 연습해봐야할듯.

📌 Q3_E_BT

int countOneChildNodes(BTNode *node)
{
    if(node==NULL){
        return 0;
    }

    int l=countOneChildNodes(node->left);
    int r=countOneChildNodes(node->right);
    
    return ((node->left != NULL) != (node->right != NULL)) ? l+r+1 : l+r;
}

이전 코드 살짝만 고침.

return ((node->left) ^ (node->right)) ? l+r+1 : l+r; 라고 해서 오류가 났었다.
코파일럿한테 고쳐달라고 요청해서
return ((node->left != NULL) != (node->right != NULL)) ? l+r+1 : l+r; 이렇게 바꿈.
node->left는 포인터라서 그 자체로 비교하면 안된다고 한다.

📌 Q4_E_BT

int sumOfOddNodes(BTNode *node)

{
    if(node==NULL){
        return 0;
    }

    int l=sumOfOddNodes(node->left);
    int r=sumOfOddNodes(node->right);
    
    return (node->item % 2 != 0) ? l+r+(node->item) : l+r;
}

나머지도 다 비슷한 느낌.

📌 Q5_E_BT

void mirrorTree(BTNode *node)
{
    if(node==NULL){
        return;
    }

    BTNode *temp = node->left;
    node->left = node->right;
    node->right = temp;

    mirrorTree(node->left);
    mirrorTree(node->right);
    
    return;
}

이 문제는 잠깐 헤매긴 했다.

📌 Q6_E_BT

void printSmallerValues(BTNode *node, int m)
{
	if(node==NULL){
        return;
    }

    if(node->item < m){
        printf("%d ", node->item);
    }

    printSmallerValues(node->left,m);
    printSmallerValues(node->right,m);

    return;
}

📌 Q7_E_BT

int smallestValue(BTNode *node)

{
    if(node==NULL){
        return INT_MAX;
    }

    int l=smallestValue(node->left);
    int r=smallestValue(node->right);
    int n=node->item;
    int min;

    min = (l < r) ? l : r;
    min = (min < n) ? min : n;
    
    return min;
}

#include <limits.h> 사용함

📌 Q8_E_BT

int hasGreatGrandchild(BTNode *node)
{
    // NULL이 아니라면 그냥 넘어간다
    // 꼬리 노드의 자식이라면 return 0
    // 모든 maxheight는 여기서 시작한다
    if(node==NULL)
        return 0;

    // l과 r에서 maxheight 받아오기
    int l = hasGreatGrandchild(node->left);
    int r = hasGreatGrandchild(node->right);

    // l과 r 중 최댓값 반환
    // 자식이 있는 노드는 여기서 return한다
    int max = (l>r) ? l+1 : r+1;
    if(max >= 4) {
        printf("%d", node->item);
    }
    return max;
}

📌 Q1_F_BST

void levelOrderTraversal(BSTNode* root)
{
	// 루트 노드 큐에 넣는다
	// while문 : 큐가 빌 때까지
	// 노드를 뺀다
	// 노드를 출력한다
	// 노드 왼쪽 자식 오른쪽 자식 큐에 넣는다

	Queue *Qptr = (Queue*)calloc(1,sizeof(Queue));
	enqueue(&(Qptr->head), &(Qptr->tail), root);
	while(!(isEmpty(Qptr->head))){
		BSTNode *newPtr = dequeue(&(Qptr->head), &(Qptr->tail));
		printf("%d ", newPtr->item);
		if(newPtr->left != NULL)
			enqueue(&(Qptr->head), &(Qptr->tail), newPtr->left);
		if(newPtr->right != NULL)
			enqueue(&(Qptr->head), &(Qptr->tail), newPtr->right);
	}
	free(Qptr);
}

미리 정의해둘 수 있으면 포인터 찍으면 되는데,
연산 결과를 더블 포인터에 넣으려고 할 땐 &를 붙여주면 된다.

📌 Q2_F_BST

void inOrderTraversal(BSTNode *root)
{
	 /* add your code here */
	 Stack *s;
	 BSTNode *node = root;
	 while(!(isEmpty(s)) || node != NULL){
		if(node != NULL){
			push(s, node);
			node = node->left;
		}
		else {
			node = pop(s);
			printf("%d", node->item);
			node = node->right;
		}
	 }
	 return;
}

처음에 짠 오답 코드.
슈도 코드를 보고 거의 그대로 짰다.

왼쪽으로 무지성으로 내려가다가
NULL을 만나면 NULL의 부모 노드 꺼내고 오른쪽 형제 노드 탐색한다.
오른쪽 형제가 NULL이 아니면 또 무지성으로 내려가는데
오른쪽 형제가 NULL이면 스택에 들어있던 부모 노드의 부모 노드 꺼낸다.
이거 반복하면 중위 순회.

그런데 node->right에 값이 없을 때 segmentation fault가 일어났다.
방지하려고 if문을 넣었더니 while문이 제대로 돌아가질 않았다.
답답한 마음에 코파일럿으로 고쳐봤다.

void inOrderTraversal(BSTNode *root)
{
	 /* add your code here */
	 Stack *s;
	 BSTNode *node = root;
	 s = malloc(sizeof(Stack)); // Initialize the stack
	 s->top = NULL; // Set the top of the stack to NULL
	 while(!(isEmpty(s)) || node != NULL){
		if(node != NULL){
			push(s, node);
			node = node->left;
		}
		else {
			node = pop(s);
			printf("%d ", node->item);
			node = node->right;
		}
	 }
	 free(s); // Free the memory allocated for the stack
	 return;
}

슈도 코드를 보고 짰던 로직 자체가 c에서는 안 통하는건가 생각했는데,
의외로 node->right의 문제가 아니라 애초에 스택에 malloc 하고 초기화 해줘야했다.

📌 Q3_F_BST

void preOrderIterative(BSTNode *root)
{
	Stack *s;
	BSTNode *node = root;
	s = malloc(sizeof(Stack));
	s->top = NULL;
	push(s, node);
	while(!(isEmpty(s))){
		node = pop(s);
		if(node != NULL){
			push(s, node->right);
			push(s, node->left);
		}
	 }
	 free(s);
	 return;
}

이번엔 다른 거 안 보고 해봤는데 의외로 금방 구현이 됐다.
왜 한 번에 됨? 전위라서 바로 프린트하면 되니까 쉬웠는듯.

(아니면 테케만 통과 가능하고 나머지는 안되는 코드인 걸수도 있음.
다른 블로그 코드들 봤는데 다들 NULL if문 쓴거 좀 신경쓰임)

📌 Q4_F_BST

	BSTNode* p = NULL;
	Stack *s;
	s = malloc(sizeof(Stack));
	s->top = NULL;
	while (1) {
		for (; root; root = root->left) {  // 왼쪽 아래까지 이동
			push(s,root);
		}

		while (1) {
			root = pop(s);
			if (!root){
				free(s);
				return; 
			}

			if (!root->right || p == root->right) {
				printf("[%d] ", root->item);
				p = root;
			}
			else {
				push(s,root);
				root = root->right;
				break;
			}
		}
	}

걍 답 봄 : https://songsite123.tistory.com/26

0개의 댓글