요즘 JS로 알고리즘 문제를 푸는 것이 꽤 재미있는 상태다.
2차원 배열은 기본이고 3차원 배열도 자주 쓰게 된다.
다차원 배열을 초기화하여 선언하는 과정에서 원소를 복사해서 넣어주게 되면
배열 복사 시 기본 특성인 shallow copy (얕은 복사, 주소 참조)로 인해
원본 배열을 변경하여도 새 배열이 같은 주소를 참조하여 두 배열의 값이 함께 변경되는 의도하지 않는 일이 발생한다.
따라서 초기화 시 shallow copy 되지 않도록
for문 혹은 map()을 이용해서 원소를 하나하나 넣어줄 수 있다.
또는 Array.from() 메서드를 사용할 수 있는데 요즘 나는 이 방법을 주로 사용하고 있다.
처음에는 단순 사용법만 외워 쓰다가 원리를 알고 써야겠다 싶어 공부하게 되었다.
1. ECMAScript 명세 무작정 읽고 알 수 있는 데 까지 해석해 보기
요약: 초기 인자 확인, 변수 셋팅
- mapfn인자가 있는지 체크하고, 있다면 isCallable 한 지 체크하여, mapping 값을 true로 둔다.
- items가 iterator 속성을 가지고 있는지 체크하여, usingIterator 객체로 둔다.
+) '?'와 '!'는 ECMAScript에서 사용되는 약어로 [Let A be ~ A를 B로 하자/두자.]의 상황에서
원하는 값이 나오는가? 에러가 나오는가? 의 분기 처리를 간편하게 표현하기 위해 사용된다.
Let A be ? B 일 때, 완료값 혹은 에러를 전달받는다.
Let A be ! B 일 때, 에러가 없는 케이스라고 확신하며 완료값을 전달받는다.
question mark, exclamation mark 링크에 사용법이 설명돼 있음.
1. this를 C로 둡니다.
2. mapfn 인자가 undefined라면
a. mapping을 false로 둡니다.
3. 그렇지 않고, mapfn 인자가 undefined가 아니지만
a. isCallable이 false라면 (-> 객체가 내부에 call 메서드를 가지고 있지 않다면)
TypeError를 throw 합니다.
b. 또는 mapfn 인자가 isCallable true 하다면, mapping을 true로 둡니다.
4. GetMethod()로 items가 iterator 속성을 가지고 있는지 체크하고,
있다면 결과를, 없다면 에러를 usingIterator 객체에 리턴합니다.
요약: items와 mapfn을 확인하여 값을 검증하고 배열 A의 len 속성을 set
- items가 iterator일 때, IsConstructor ? A === Contsruct(C) : A === []
- GetIteratorFromMethod(items, usingIterator)로 iteratorRecord 를 리턴 받아, iteratorRecord로 둔다.
- k === 0, k가 2^53-1이상이 아닐 때까지 while 반복한다.
- IteratorStep() 값을 next에 둔다. next가 DONE이 되면 반복자가 마지막에 도달했음을 나타내고 A를 리턴한다.
- mapping이 있다면 mapfn과 iterator값을 연산하는 과정을 거쳐 저장하고, 없다면 기존 iterator값만 mappedValue에 둔다.
- 각 step에서 IteratorClose(iteratorRecord, error) 결과로 Normal / Throw 리턴한다.
이 5번이 정말 어려웠는데, 추가로 알아야 할 점들이 너무 많았다 ...
5. usingIterator가 undefined가 아니라면 (-> iterator라면)
a. IsConstructor(C)가 true라면 (-> 객체가 내부에 constructor 메서드를 가지고 있다면)
i. Construct(C)를 A로 둡니다. (A === NormalCompletion, [[Type]]: NORMAL)
b. IsConstructor(C)가 true가 아니라면 (-> 객체 내부에 constructor 메서드가 없다면)
i. ArrayCreate(0)을 A로 둡니다. (A === [])
c. GetIteratorFromMethod(items, usingIterator)를 통해 iteratorRecord를 리턴받아 iteratorRecord로 둡니다.
d. k를 0으로 둡니다.
e. 반복하는데,
i. k가 2^53 - 1 보다 크거나 같을 경우
1. ThrowCompletion으로 error를 리턴합니다.
2. IteratorClose(iteratorRecord, error)로
객체 내부에 iterator 메서드를 가지고 있다면 NormalCompletion 결과를,
없다면 ThrowCompletion으로 에러를 리턴합니다.
ii. ToString(k)으로 k에 대한 숫자 값을 stringify 하여 Pk로 둡니다.
iii. IteratorStepValue(iteratorRecord)의 결과값을 next로 둡니다.
iv. next값이 DONE이라면, (-> iteratorRecord에서 반복자가 끝에 도달했음)
1. Set(A 배열, length, k에 대한 숫자 값, Throw 출력여부 true)를 통해 확인하여 예외가 발생하지 않으면
2. A를 리턴합니다.
v. 만약 mapping이 true라면,
1. Completion(Call(mapfn, thisArg, <<next, F(k)>>))를 통해
생성자를 call 하여 completionRecord를 mappedValue에 저장합니다.
2. IfAbruptCloseIterator로 불변성을 보장합니다. NormalCompletion 값들만 value로 둡니다.
vi. mapping이 true가 아니면,
1. mappedValue를 next로 둡니다.
vii. Completion()어쩌구를 defineStatus로 둡니다.
viii. IfAbruptCloseIterator(defineStatus, iteratorRecord)로 불변성을 보장합니다.
ix. k를 k+1로 설정합니다.
요약: items을 object로 만들고 length를 가진 배열 A를 생성
6. NOTE: items는 Iterable이 아니므로 array-like object라고 가정합니다.
7. ToObject(items)로 결과를 arrayLike 객체에 리턴합니다.
8. LengthOfArrayLike(arrayLike)의 결과 혹은 에러를 len 객체에 리턴합니다.
9. IsConstructor(C)가 true라면 (-> 객체가 내부에 constructor 메서드를 가지고 있다면)
a. Construct(C, F(len))을 A로 둡니다.
10. IsConstructor(C)가 true가 아니라면 (-> 객체 내부에 constructor 메서드가 없다면)
a. ArrayCreate(len)을 A로 둡니다.
요약: items와 mapfn을 연산한 값을 배열 A의 DataProperty로 set, A 배열을 리턴
11. k를 0으로 둡니다.
12. 반복하는데, k < len 동안,
a. ToString(k)으로 k에 대한 숫자 값을 stringify하여 Pk로 둡니다.
b. Get(arrayLike, Pk) 값을 리턴받아 kValue에 둡니다.
c. 만약 mapping이 true라면
i. Call(mapfn, thisArg, <<kValue, F(k)>>)를 통해 Call이 가능할 경우 mappedValue로 둡니다.
(가능하지 않을 경우 에러 throw)
d. mapping이 true가 아니라면
i. kValue를 mappedValue에 둡니다.
e. CreateDataPropertyOrThrow(A, Pk, mappedValue)로 set 합니다.
f. k를 k+1로 설정합니다.
13. Set(A 배열, length, len에 대한 숫자 값, Throw 출력여부 true) 를 통해 확인하여 예외가 발생하지 않는다면,
14. A를 리턴합니다.
어찌저찌 명세를 다 읽었고 많은 의문점이 남았다.
2. 코드로 확인하며 이해 안 되는 부분 다시 보기
V8의 array-from은 어떻게 구현되어있는지 확인해 보았다.
해석만 했을 때는 도통 알기 어려웠는데 코드를 같이 보니 좀 더 설명되는 부분이 있긴 했다. (여전히 안되기도한다..)
1. this가 의미하는 것은 뭘까?
일단 코드에서는 이 부분이다. 코드의 첫 시작 부분이다.
const c에 들어가는 값이 this인데, 잘 모르겠다 ... 모르겠지만, 뭔가 들어가겠거니 하고 일단 넘어간다.
2. while 반복문에서 k의 조건에 대해
실제 코드에서 장문의 주석과, 명세와는 조금 다른 조건의 코드를 볼 수 있었다.
사양을 충족시키려면 비효율적이라 ... 적절하게 대처했다! 라는 설명과 함께 :)
3. 5.e.ii, 12.b에서, stringify한 Pk값을 사용하는 것은 왜일까?
k값은 while을 순회하는 정수여야 할 것 같은데, 왜 stringify 처리를 하지... 라는 생각을 했는데
실제 코드에서 간단하게 요구사항을 건너 뛴 모습을 보니 ,,처리하지 않아도 되는구나 !?
이해가 되었다. :)
3. 아직도 어려운 부분 MDN 문서에서 확인하기
ECMAScript 명세 내용이 해석이 되어도
IsCallable(), IsConstructor(), Call(), 등은 무엇을 의미하는지, 왜 써야 하는지,
ThrowCompletion이 뭔지는 대충 알더라도 NormalCompletion는 대체 어떤 형태라는 건지, ...
while문을 두 번 반복하는 이유는 무엇인지 등등 어려운 내용들에 대한 의문점은
현재까지의 내 지식으로는 추측하여 이렇겠거니~ 결론지을 수 밖에 없었다.
MDN에서는 어떻게 설명하고 있는지 찾아 보았다.
이 부분에서 중간 배열을 생성하지 않는다는 점, 배열이 아직 생성 중이라는 점이
명세에서 while 부분을 설명한다는 것을 알 수 있다.
while 과정이 두 번 있었고 처음에는 len, 두번째는 data를 set했던 명세의 내용이 설명되어 있는 것을 알 수 있다.
무턱대고 Array.from()이 궁금하니까 한 번 탐구해 봐야지! 라고 쓰기 시작한 글이 꽤나 긴 글이 되었다.
시간을 꽤나 할애했는데 1. 영어 문서를 읽는 것이 어려워서 2. 파고들어가니 개념이 어려워서 3. 어려우니까 미루게 되어서
ㅋ_ㅋ
일반적으로 모든 메서드를 쓸 때마다 ECMAScript 문서를 찾아보며 쓸 필요는 없겠지만
어떤 라이브러리를 사용할 때도 공식 문서를 읽는 것처럼
JavaScript를 잘 사용하기 위해 공부하는 것은 좋을 것 같다 ...!
하지만 꽤나 시간이 많이 소요될 것 같다 ...!! 공부를 할 수록 점점 줄어들겠지
Array.from()이라는 메서드는 Array를 만들어주는 메서드야! 에서
Array.from()은 입력된 인자를 이렇게저렇게 검증해서 배열로 만들어 반환하는데
만약 mapfn 인자가 있다면 매 원소 순회시에 함께 연산한 결과를 반환하는 메서드야! 라는 부가설명을 붙일 수 있게 되었다.
'main > JS | TS' 카테고리의 다른 글
[수강후기] 유데미(Udemy) 【한글자막】 Typescript :기초부터 실전형 프로젝트까지 with React + NodeJS (0) | 2024.04.24 |
---|---|
실제로 타입스크립트의 컴파일 과정은 어떻게 동작할까 (1) | 2024.02.06 |
타입 확장하기 (Union/Intersection Type), 좁히기 (Type Guard) (0) | 2024.01.12 |
타입스크립트 고급 타입 (Index Signatures, Indexed Access Types, Mapped Types, Template Literal Types) (0) | 2024.01.05 |
JS의 list 함수 끝판왕 reduce와 친해지기 :) (1) | 2023.12.22 |