✨ 문자열과 변수를 함께 쓰는 형식(``) (``는 백틱(Back-ticks)이라고 부른다)
💻Example Code
const TemplateStrings1 = `This is a template string.`; const TemplateStrings2 = `In ES5 this is not legal.`; const name = 'David'; const TemplateStrings3 = `Hello ${name}, how are you?`; const TemplateStrings4 = String.raw`In ES5 "\n" is a line-feed.`;
😋 TemplateString1처럼 일반적인 문자열처럼 사용할 수 있으며, TemplateString2처럼 line-feed 입력 없이 줄바꿈을 할 수 있다. TemplateString3처럼 문자열에 변수를 섞어 쓸 수 있으며, TemplateString4처럼 String.raw와 함께 이스케이프 문자를 그대로 표현할 수 있다.
사용할 때 `을 열고 `로 닫아주면 되며, 문자열 안에 변수를 넣고 싶을 때는 ${변수명}을 작성해 주면 된다.
기본적으로, Math.random()은 0 ~ 0.999...까지 값을 얻을 수 있다(1은 포함되지 않는다) 하지만, 위 코드처럼 max값을 곱해주면 0 ~ 2.999...까지 값을 얻을 수 있다(3은 포함되지 않는다) 이렇게 범위를 정해서 내가 원하는 random 값을 받아볼 수 있다.
📌 만약 1부터 3까지 값을 받고 싶다면?
Math.ceil( Math.random * 최대 범위 ) 위와 같이 ceil(올림) method를 사용해주면 된다(여러가지 다른 방법도 있다)
console.log( Math.ceil(Math.random() * 3) );
📌 function을 사용하여 간편하게 이용하는 방법도 있다.
function getRandomInt(max){ return Math.floor( Math.random() * max ) + 1; }
const max = 3;
console.log( getRandomInt(max) );
위 function에서는 Math.floor( Math.random() * max ) + 1; 로 작성했다. 즉, 0 ~ 2.999...범위의 random 숫자를 버림(floor)해주고 +1을 더해 주는 방식이다. 이렇게 작성해도 1~3까지 범위의 random 숫자를 받아볼 수 있다.
😋 기본적으로 구구단 게임이라든지, 무언가 변칙적인 개발이 필요할 때 자주 사용되는 method! 종종 쓰이기 때문에, 한 번 잘 이해해두고 앞으로 쉽게 사용하자.
이 event들은 어떻게 관리될까? class를 이용해서 event를 관리하는 library를 작성해보자
class를 이용해 event library를 작성하면 위와 같이 작성할 수 있다.
생성자(constructor)를 이용해 object(map)을 선언해주고, on(), trigger(), off() method를 실행하며 각 event(key)마다 callback(value)을 넣어주면 된다. 또한, callback은 여러 개가 들어갈 수 있으므로 배열로 선언해주면 된다.
on()은 이벤트 등록을 위한 기능이므로, 해당 event가 있다면 push, 없다면 새로 key를 등록해준다.
trigger()는 이벤트를 실행시켜주므로, for...of를 이용해 obj안에 등록되어 있는 해당 event를 ()를 통해 실행시켜준다.
off()는 이벤트를 제거해주므로, delete를 이용해 obj안에 등록되어 있는 해당 event를 제거해주면 된다.
우리는 평소 사용 방법만 알면 event를 쉽게 등록하고 사용하거나 제거할 수 있다. 하지만, 우리 눈에 보이지 않는 event는 어떻게 관리되는 지 위의 내용을 통해 조금이나마 이해할 수 있다.
Q. 주어진 Node가 이진 탐색 트리(Binary Search Tree)로서 유효한지 확인하는 function을 작성하라. (left node는 node.data보다 작으며, right node는 node.data보다 크다는 것을 명심)
🎈 현재 node의 왼쪽 node는 현재 node보다 작으며, 오른쪽 node는 현재 node보다 큰 것이 BST다.
해당 node를 기준으로 작은가? 큰가?를 파악해주면 된다. 그러기 위해서는 기준을 잡아줄 max 또는 min을 설정해주면 비교하기가 더욱 쉬워 진다.
🧨 function의 argument로 min, max를 넣어주면 편할 것이고, max보다 크거나, min보다 작으면 false를 반환해줄 조건문이 있어야할 것이다. 또한, 트리를 탐색하며 검색하므로 재귀를 써주면 효율적일 것이다. 라는 생각을 하면서 문제 풀이를 시작해주면 된다.
🔮 argument로 min과 max에 default null을 설정해주고 시작하자.
우선, 17-23번째 코드가 가장 먼저 떠올리기 쉽다. root부터 탐색을 시작할 때, 왼쪽 노드가 있고 && 왼쪽 노드 값이 지금 노드의 값보다 크지 않으면 true다. 만약 커서 false를 return받으면, false를 계속 반환하여 처음 재귀를 호출한 조건문에 가서 끝낸다. 더 이상 탐색할 이유가 없기 때문이다. 만약 false 조건에 걸리지 않으면 이렇게 BST를 끝까지 내려가며 validate이 재귀로 작동한다. 이와 마찬가지로 오른쪽 노드도 생각해주면 된다.
재귀로 실행되며 max와 min을 확인하는 조건문을 살펴보자(9-15번째 코드) 왼쪽 노드는 무조건 상위 노드보다 크면 안되며, 오른쪽 노드는 무조건 상위 노드보다 작으면 안된다고 반복해왔다. 따라서, 왼쪽 노드를 검사할 때는 상위 노드를 max로 잡고, max보다 크면 false를 return해주면 된다. 또한, 오른쪽은 상위 기준으로 min보다 작으면 false를 return해준다.
예외 사항이 있다. 위 그림을 통해 예외 사항을 살펴보자. 왼쪽에 노드가 있으며 상위 노드보다 작거나, 오른쪽에 노드가 있으며 상위 노드보다 크지만 애초에 들어와서는 안 될 값을 체크해줘야 한다. '15'를 보자. 이 경우, -1노드의 오른쪽에 있으며 -1보다 큰 값을 갖고 있다. BST의 정의에 따르면 위치가맞긴 하다.
하지만, 애초에 '10'노드를 기준으로 오른쪽에 삽입되었어야 할 노드다. 이런 경우는 17-23번째 코드에서 작성한 validate 재귀로 해결된다.
왼쪽 노드를 검사할 때, max는 상위 노드 기준으로 입력되지만, 오른쪽 노드를 검사할 때는 max는 '-1'기준 상위 노드 값(0)이 유지되며, min값은 '15'로 노드가 위치하면서그 상위 노드 값인 '-1'이 들어가기 때문이다. 따라서 '15'는 max(0)보다는 크지만, min(-1)보다는 작기 때문에 false를 return한다. 이렇게 유효성 검사를 마무리해 줄 수 있다.
이렇게 반복해서 false를 확인해주고, false가 하나도 발생하지 않는다면 올바른 BST이므로 true를 반환해주면 된다.
📌 if(max && node.data >) 이런식으로 'max가 있을 때'로 전제하여 코드를 작성해주면 안 된다. 만약 -1값이 들어오면 -1은 false이기 때문에 해당 조건문을 실행하지 않기 때문이다. 또한, ture or false값을 최종적으로 받아야하기 때문에 return이 필요한 곳에 return은 잘 넣어주는 게 중요하다.
👏 이해하기 상당히 어려운 알고리즘이다. 계속 디버깅을 하면서 값의 이동과 재귀의 흐름을 파악해주는게 이해의 지름길이다!
Q. 이진 트리 구조의 Node class를 작성하고, insert(삽입), contains(검색) method를 추가하라. (생성자(constructor)는 data, left, right를 초기화 해야하며, insert()와 contains()는 data를 argument로 받는다. 또한, contains()는 해당 Node를 반환하거나 조건에 맞지 않는다면 null을 반환해야 한다.)
이진 탐색 트리의 정의(Definition of Binary Search Tree)
🎈 이진 트리(Binary Tree)란, Node는 left, right 양쪽 2가지 Node만 가질 수 있으며 left는 해당 Node의 값보다 작아야하고, right는 해당 Node의 값보다 커야하는 구조를 갖는 Tree를 칭한다.
🧨constructor()는 data, left, right을 가져야할 것이고, 각 값의 초기 선언을 어떻게 해야할 지 생각해보면 된다.
insert() method를 작성하기 위해서는 검색이 필요하기 때문에 재귀(recursion)을 사용해야 한다는 것, 조건문을 잘 활용해야 한다는 것 위 2가지를 명심하고 작성해 나가면 된다.
contains() method도 insert()와 비슷한 구조로 작성되지만, 값을 return해야 한다는 것을 명심하면 된다.
insert() method 작성에 도움이 될 만 한 이미지
contains() method 작성에 도움이 될 만 한 이미지
🔮 constructor()는 간단하다.
constructor(data){ this.data = data; this.left = null; this.right = null;} 위와 같이 작성해주면 된다.
insert() method를 작성해보자
들어오는 data가 this.data보다 작다면? 왼쪽 들어오는 data가 this.data보다 크다면? 오른쪽 왼쪽 또는 오른쪽에 Node가 있나? 있다면 재귀 insert(data) 왼쪽 또는 오른쪽에 Node가 없나? 없다면 new Node(data) 이 조건문만 잘 작성해주면 되고, 다시 검색이 필요할 때는 left or right의 insert()를 재귀로 call해주면 된다. 재귀로 다시 탐색할 때는, 한 단계 아래 tree로 이동한다는 걸 명심하자.
contains() method를 작성해보자
들어오는 data가 this.data와 같은가? 해당 Node return 들어오는 data가 this.data와 작은가? 작다면 재귀 contains(data) 들어오는 data가 this.data와 큰가? 크다면 재귀 contains(data) 들어오는 data가 this.data와 일치하는게 없다? 없다면 null return 위의 조건문을 잘 작성해주면 된다.
📌이때 명심해야할 것은, contains()에서는 조건문에 return이 필요하다는 것이다. 해당 Node를 찾거나 못 찾아도 값을 return해야하기 때문이다. 재귀를 사용하면 memory stack에 이전에 불러왔던 function이 쌓인다. 그리고 가장 나중에 진행한 function이 끝나면 마지막 function을 call한 function으로 돌아가서 작업을 마무리하게 된다. 따라서, return을 해주지 않으면, 해당 값을 끝까지 계속 지니면서 가져오지 못 한다.
insert()의 경우에는 return하는 값이 없기 때문에 return이 필요하지 않다.
Q. 입력 받은 Tree의 root를 이용해 Tree 각 층의 Node 너비(=개수)를 알아내는 function 작성하라.
--- Example Given: 0 / | \ 1 2 3 | | 4 5
Answer: [1, 3, 2]
🎈 Tree traverseBF(BFS) method와 비슷하다. 배열을 선언해 초기 값으로 root와 각 층의 끝을 알려주는 문자('s')를 넣어주고, 이런 형식으로 반복 해주면 된다. 또한, 이 과정에서 각 층의 Node를 count해줘야 한다.
🧨 위의 이미지를 보며 어떻게 풀어나갈 지 생각해보자
1. []의 초기값 선언 2. while 조건문은 어떻게? 3. while 내부에서 's'처리는 어떻게? 4. count 방식은 어떻게?
위 4가지만 잘 생각해주면 문제는 생각보다 쉽게 해결된다.
🔮 arr 배열에 [ root, 's' ]를 넣어주고 시작해야 한다. while 조건문으로 arr.length > 1을 줄 것이기 때문이다. 위와 같이 조건문을 작성하면, 마지막 's'가 남았을 때 while을 끝낼 수 있다. 여기서 counters 배열에 0을 넣고 시작하는 것도 중요한 팁이다. 보통 counter를 한다고 생각하면, count변수를 따로 만들어 count++를하고 반환할 결과 배열에 push(count)할 것이다. 하지만, 방금 작성한 방법을 사용하면 위의 while에서는 마지막 count를 결과 배열에 push하지 못한다.
전 글의 Tree traverseBF(orDF) method에서 했던 것처럼, arr.shift()을 해서 가장 첫 번째 index 값을 가져온다. 만약 's'면, 해당 width count는 끝났으며, 다음 level의 children이 모두 arr에 push된 것이므로, 새로운 counters를 push해줌과 동시에 해당 width의 끝을 알리는 's'도 push해준다. 's'가 아니라면, 위와 반대로 해당 level을 count하고 있으며, 다음 level의 children을 push하고 있는 것이므로 arr.push와 counters++를 진행해준다.