# 강남_M_Day13 리뷰 레포트 ### 참석자 - 나영균, 우연서, 이상아, 한혜경 ## 1. 코드 동작 이해 ``` - 가장 많은 체크포인트를 구현한 사람의 코드의 동작 원리를 이해하는 과정에서 발생한 건설적인 대화를 기록, 정리합니다. ``` - 연서님 코드 - 정규식을 이용해서 String을 토크나이징한다 - 토크나이징 한 배열을 각 타입을 맞게 객체로 변환한다 - 객체를 그래프로 만들고 재귀로 미션에서 요구하는 형식의 객체로 파싱한다. - 영균님 코드 - tokenizer에서 정규식을 이용하여 separator를 살려서 토크나이징 한다. - tokenizer에서 문자열 검사와 브라켓 검사를 하고 에러시 에러를 발생시킨다. - lexer에서 tokenizer결과를 typeMatching을 한다. - parser에서 브라켓들을 감지하여 1차원에 속하는 원소들은 그대로 mainArr에 넣고 브라켓이 열려서 2차원 이상의 원소들과 브라켓은 subArr에 넣는다. - 브라켓이 닫히면 subArr을 그대로 mainArr에 넣는다. - 위 작업이 끝나면 mainArr을 반복문으로 돌며 array인 경우 parser로 재귀하여 결과를 array와 치환한다. ## 2. 코드 동작 개선 ``` - 다함께 하나 이상의 체크포인트를 구현하기 위해 시도했던 문제 해결의 과정을 정리합니다. - 학습정리의 `스스로 확인할 사항`에 대해 고민한 내용을 공유하고 서로의 코드를 더 발전시킬 수 있는 형태로 내용을 기록합니다. ``` ### 나영균 - 재귀를 이용하여 구현하였는데 이를 스택과 반복문으로도 구현해보는 것이 좋겠다. - 매우매우 큰 구조의 경우 재귀로 실행할 시 stackoverflow가 발생할 것이다. - tokenizer에서 필요없는 요소를 처리하는 부분을 조금 더 중복되지 않고 효율적으로 구현한다면 좋을 것이다. - 문자열안에 들어오는 특수문자들과 브라켓의 예외상황을 처리하였지만 Array의 상황이외에도 더 많은 상황에 대하여 예외를 처리한다면 좋을것이다. ### 우연서 * 트리 구성 후 객체로 변환하고 있지만 토큰의 값을 깔끔하게 정리하고 두번에 걸친 파싱 작업을 한번으로 줄일 수 있을 것 같다. * 부모노드(현재 작업하고 있는 배열의 위치)를 어떤 방식으로 접근해야할지에 대한 방법으로 연결리스트를 선택했는데 배열의 스택으로도 부모노드를 기억할 수 있다. * 토큰의 경우들을 if 문으로 확인하였는데, object로 분리하여 상수처리해서 추가 제거가 용이하게 할 수 있을 것 같다. ### 이상아 * 재귀로 작성하였을 때, object의 자식의 자식의 자식 ... 을 탐색하는 것은 구현되었지만, 다시 나오는 것을 구현하지 못하였다. * for문 으로는 과제 제출시간 이후 구현하였지만, 재귀로 구현할 수 있었다면 더 좋았을 것 같다. ### 한혜경 - 현재 코드는 일차 배열에 대한 문자열만 분석한다. 재귀나 반복문을 사용하여 전체를 다 분석했어야 했는데 나중에 문제를 찾아서 고치기 힘들었다. tokenizer에서부터 작은 단위로 쪼개는 게 문제가 됐는데 "Hello, world"와 같은 ',' 구분자로 분류하는 방법을 찾다가 시간만 많이 쓰고 망했다. 일단 진행하고 이런 문제는 나중에 찾자. 그리고 여러 함수를 구현해야 할 때 이해가 되는 함수부터 구현하면 타고 올라가면서 좀 더 수월하게 구현할 수 있었을 것 같다. ### 스스로 확인할 사항 ### 나영균 #### 무한 중첩된 구조를 프로그래밍 하는데 효과적인 방법은 무엇인가? 재귀인가, 반복인가? - **Recursion** - 재귀를 통하여 프로그래밍을 하면 직관적으로 짤 수 있다. - 부수효과를 줄일 수 있다. - 재귀가 더 느린 이유는 함수콜이 모두다 스택에 쌓여야하기 때문이다. - 또한 메모리도 할당해야하는데 그 이유는 분리된 스코프를 유지해야 하기 때문이다. - 몇몇의 경우(Tree같은)에는 recursion이 가독성측면에서 좋은 경우가 있다. - **Iteration** - 성능적 효율이 더 중요한 요소라면 반복을 통해 처리하는 것이 좋다. - 재귀는 stackoverflow가 발생할 수 있다. > 실제로 recursion은 CPU의 내부적으로 Iteration처럼 작동하게 된다. > 적재적소에 사용하는것이 좋다고 생각한다. #### token, lexer, parser 역할을 나눠서 단계적으로 처리하고 있는가? - **tokenizer** - ' , [ ]를 기준으로 문자열을 분리하고 따옴표와 브라켓이 올바르게 있는지 확인합니다. - 또한 필요없는 원소인 ,와 ""와 " "를 제거합니다. - 처리된 배열을 리턴합니다. - **lexer** - tokenizer가 리턴한 배열을 인자로 받습니다. - typeAttacher로 매칭이 되는 type들을 tagging합니다. - 태깅한 배열을 리턴합니다. - **parser** - lexer가 리턴한 배열을 입력으로 받습니다. - 각각의 원소들을 분석하여 얼마다 깊은 차원으로 원소를 넣어야하는지 판단하고 더 깊은 차원의 배열은 재귀로 호출하여 처리합니다. - 최종 Object를 반환합니다. #### 에러처리에 대한 흐름제어를 할 수 있는가? - tokenizer, lexer, parser의 단계에서도 많은 단계로 모듈화하여 적용하여 배열의 에러와 문자열의 에러를 따로 catch할 수 있으며 원한다면 더 세부적인 에러처리를 구현할 수 있습니다. 이는 소규모 모듈로 분리한 설계의 장점입니다. ### 우연서 #### 무한 중첩된 구조를 프로그래밍 하는데 효과적인 방법은 무엇인가? 재귀인가, 반복인가? - 재귀의 경우 함수가 여러번 할당되기 때문에 메모리의 비효율이 발생할 수 있고 무한루프에 빠질 수 있다는 단점이 있다. 하지만 개발자가 작업에 용이하고 DFS의 경우 훨씬 단순하게 작업할 수 있다는 장점이 있다. - 반복의 경우에는 메모리 사용에 이점이 있지만 각 단계에서 체크해야 되는 것들이 있어서 개발에서 확인해야할 것들이 있다. #### token, lexer, parser 역할을 나눠서 단계적으로 처리하고 있는가? - 토큰을 나누고 각 단위에 맞게 값을 부여한 다음 파서에서 트리를 만드는 단계로 구성하고 있다. 단계별로 배열을 받고 리턴함으로써 테스트 코드로 각 함수의 테스트가 가능하다. #### 에러처리에 대한 흐름제어를 할 수 있는가? - 커스텀 익셉션을 만들어서 인자오류를 출력하고 있다. 파싱을 실행하는 함수에서 catch를 하기 때문에 하위 함수에서 에러가 발생해도 상위 함수에서 에러처리가 가능하다. ### 이상아 #### 무한 중첩된 구조를 프로그래밍 하는데 효과적인 방법은 무엇인가? 재귀인가, 반복인가? - **코드의 간결함과 가독성** 면에서는 재귀가 반복문보다 낫다. - 하지만 **성능** 면에서는 반복문이 재귀보다 낫다. 왜냐하면 재귀는 자신을 호출하고, 그 안에서 또 자신을 호출하고 ... 이것을 반복하기 때문이다. 이 과정에서 재귀 호출이 일정 횟수 이상을 넘어가면 알고리즘 실행 속도 또한 저하될 수 있다. - 각각의 단점을 개선하는 **꼬리재귀**라는 것이 있다. - 코드를 보자 ```javascript // 꼬리재귀가 아니다 int foo ( int n ) { if ( n == 0 ) { return 1; } return 2 * foo ( n - 1 ); } // 꼬리재귀 이다 int gcd ( int m, int n ) { if ( n == 0 ) { return m; } else { return gcd ( n, m%n ); } } ``` * 꼬리재귀의 경우 호출될 때 리턴되는 위치를 스택에 저장하지 않는다. - [출처 - 티스토리 블로그](https://makefortune2.tistory.com/98) #### token, lexer, parser 역할을 나눠서 단계적으로 처리하고 있는가? - token, lexer는 단계적으로 올바르게 처리하지만 parser는 제대로 처리하지 못한다. #### 에러처리에 대한 흐름제어를 할 수 있는가? - 구현하지 못하였다. ### 한혜경 #### 무한 중첩된 구조를 프로그래밍 하는데 효과적인 방법은 무엇인가? 재귀인가, 반복인가? - 시각적으로 보여주기에는 재귀함수를 이용하는 게 좋지만, 성능 측정을 위해 십만번, 백만번 반복하지 않아도 실용적인 몇 십번, 몇 백번 단위에서도 확연하게 성능 차이가 많이 나기 때문에 반복문을 사용하는 것이 더 낫다고 생각한다. #### token, lexer, parser 역할을 나눠서 단계적으로 처리하고 있는가? - token: 문자열을 받아 각각의 요소를 최소 단위로 분리해준다. 다만 내가 짠 코드는 안에 있는 요소를 모두 분리하지 못했다. - lexer: token으로 쪼개진 단위들 각각의 요소에 type과 value를 부여한다. - parser: 각각의 요소를 분석하여 부모 자식 관계를 보여준다. #### 에러처리에 대한 흐름제어를 할 수 있는가? - 문자열에서 '[]'로 묶이지 않거나 '"', "'" 와 같은 문자열이 한 쌍으로 묶여있지 않을 때 체크하기 위해 ```js match = { '[': 0, ']': 0, "'": 0, '"': 0 } ``` 를 이용하여 모든 문자열이 끝났을 때 각각의 개수가 같은지 확인하고 맞지 않으면 에러를 발생시켰다.(1차 배열일 때 적용됨) ## 3. Consideration ``` - 학습정리의 `다같이 확인할 사항`에 대해 충분히 토의하고 그 과정을 일목요연하게 정리합니다. ``` ### 다같이 확인할 사항 #### 소프트웨어 개발에서, token, lexer, parser의 역할은 각각 무엇인지 조사해보자. - token - 컴파일러에서의 토큰에서 해당하는 단위(명칭,상수,지정어,연산자,구분자 등)로 분할한다. - lexical analysis 결과로서 의미가 있는 최소 문자단위를 의미한다. - lexer : 어휘분석 - 컴퓨터과학에서 lexical analysis(어휘 분석)은 문자열을 sequence of tokens(의미가 첨부된 문자열)로 바꾸는 프로세스를 의미한다. Lexical analysis를 실행하는 프로그램은 lexer, tokenizer, 혹은 scanner라고 불린다(scanner는 lexer의 첫 단계를 의미하기도 한다. tokenizer와 lexer가 헷갈리는 이유일 것이다.). lexer는 일반적으로 parser와 함께 묶여있는 경우가 많다. - parser : 구문분석 - 컴파일러에서 생선된 토큰을 렉싱한 것을 파스 트리를 구성하는 것을 파싱이라고 하는데. 컴파일러에서의 파싱 방법에서는 상향파싱, 하양파싱 등이있다. - 파싱에 단계 토큰이 생성 가능한 것인지를 판별하고 구문을 분석한다. #### 이번 미션에서 구현한, token, lexer, parser의 각각 역할과 동작방식을 요약해서 설명해보자. - data: `['hi', 2, 3, [4, 5, [6, 7], 'h']]` - tokenizer 는 **의미있는 단위로 쪼개는 역할**. - `[`, `hi`, `2`, `[`, `4`, `5`, `6`, `7`, `]`, `'h'`, `]`, `]` - laxer는 **각각의 쪼개진 데이터에 의미를 부여**. ```javascript [{ type: 'LBracket', value: '[' }, { type: 'string', value: '\'hi\'' }, { type: 'number', value: 2 }, { type: 'number', value: 3 }, { type: 'LBracket', value: '[' }, { type: 'number', value: 4 }, { type: 'number', value: 5 }, { type: 'LBracket', value: '[' }, { type: 'number', value: 6 }, { type: 'number', value: 7 }, { type: 'RBracket', value: ']' }, { type: 'string', value: '\'h\'' }, { type: 'RBracket', value: ']' }, { type: 'RBracket', value: ']' } ] ``` - parser는 **의미를 부여한 데이터를 구조화**. ```javascript { type: "array", child: [ { type: "string", value: "'hi'" }, { type: "number", value: 2 }, { type: "number", value: 3 }, { type: "array", child: [ { type: "number", value: 4 }, { type: "number", value: 5 }, { type: "array", child: [ { type: "number", value: 6 }, { type: "number", value: 7 }] }, { type: "string", value: "'h'" } ] } ] }; ```