리스트 | 배열 |
---|---|
인덱스가 없다 | 순서대로 인덱스가 있다 |
next 포인터를 가진 노드를 통해 연결된다 | 삽입과 삭제에 비용이 많이 들 수 있다(expensive) |
랜덤 액세스가 허용되지 않는다 | 특정 인덱스로 빠르게 접근할 수 있다 |
// val = 값
// next = 포인터
class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}
const first = new Node('Hi');
first.next = new Node('there');
first.next.next = new Node('how');
first.next.next.next = new Node('are');
first.next.next.next.next = new Node('you');
이 상태에서 first는 이렇게 생겼다. 두번째 사진은 next를 끝까지 펼친 모습이다.
// 의사코드
// 1. push 메서드는 한 value을 받아야 한다.
// 2. 메서드에 전달되는 value를 사용해서 새로운 Node를 생성해라.
// 3. 리스트에 head가 없다면, head와 tail은 새로 생성한 Node가 되도록 설정해라.
// 3-1. 그게 아니면, tail의 다음 프로퍼티로 새로운 Node를 설정하고 tail 프로퍼티는 새롭게 생성된 Node가 되도록 해라.
// 4. 길이를 1 추가해라.
// 5. 연결 리스트를 반환해라.
class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}
class SinglyLinkedList {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
push(val) {
const newNode = new Node(val);
if (!this.head) {
this.head = newNode;
this.tail = this.head;
} else {
this.tail.next = newNode;
this.tail = newNode;
}
this.length++;
return this;
}
}
const list = new SinglyLinkedList();
list.push('단일');
list.push('연결');
list.push('리스트');
// 의사코드
// 1. 리스트에 아무 노드도 없는 경우, undefined를 반환한다.
// 2. tail에 도달할 때까지 리스트를 순회한다.
// 3. 마지막에서 2번째 노드의 `next`를 `null`로 설정한다.
// 4. 마지막에서 2번째 노드를 tail로 설정한다.
// 5. 길이를 1 감소시킨다.
// 6. 제거된 노드의 값을 반환한다.
class SinglyLinkedList{
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
...
pop() {
if (!this.head) return undefined;
let current = this.head;
let newTail = current;
while (current.next) {
newTail = current;
current = current.next;
}
this.tail = newTail;
this.tail.next = null;
this.length--;
// length가 1일때 pop()을 실행할 경우 head와 tail이 같은 노드를 가리키기 때문에 초기화
if (this.length === 0) {
this.head = null;
this.tail = null;
}
return current;
}
}
const list = new SinglyLinkedList();
list.push('단일');
list.push('연결');
list.push('리스트');
// 의사코드
// 1. 노드가 없을 경우, undefined를 반환한다.
// 1-1. 노드가 있을 경우, 현재의 head를 변수에 저장하고
// 2. 현재 head의 next 노드를 가리키도록 헤드 속성을 업데이트한다.
// 3. 리스트의 길이를 1 감소시킨다.
// 4. 제거된 이전 head의 값을 반환한다.
...
shift() {
if (!this.head) return undefined;
let oldHead = this.head;
this.head = oldHead.next;
this.length--;
if (this.length === 0) {
this.head = null;
this.tail = null;
}
return oldHead;
}
// 의사코드
// 1. 메서드로 전달된 값을 사용해서 새로운 노드를 생성한다.
// 2. list에 head가 없으면 head와 tail은 새롭게 생성된 노드가 되도록 한다.
// 2-1. 노드가 이미 있는 경우, 새롭게 생성된 노드의 next를 현재의 head 값으로 설정한다.
// 3. list의 길이를 1 증가시킨다.
// 4. liked list를 반환한다.
unshift(val) {
const newNode = new Node(val);
if (!this.head) {
this.head = newNode;
this.tail = this.head;
} else {
newNode.next = this.head;
this.head = newNode;
}
this.length++;
return this;
}
// 의사코드
// 1. index를 받는다.
// 1-1. index가 음수이거나 리스트의 길이와 같거나 길이보다 클 경우 not working (return null)
// 2. 루프를 통해 인덱스가 지정하는 위치에 이를 때까지 반복해서 이동해서 해당 index 위치에 있는 노드를 반환한다.
get(index) {
if (index < 0 || index >= this.length) return null;
let count = 0;
let current = this.head;
while (count < index) {
current = current.next;
count++;
}
return current;
}
// 1. index와 value 받기
// 2. get 메서드를 사용해서 노드 찾기
// 3. 노드가 없을 경우 return false
// 4. 노드가 있을 경우 그 노드의 값이 value가 되도록하고 return true
set(index, val) {
const foundNode = this.get(index);
if (foundNode) {
foundNode.val = val;
return true;
} else return false;
}
강의에서 정적 메서드 내용이 지나갔는데 딥다이브에서 읽은 기억이 나서 찾아본 내용
MDN에서 정적 메서드는 애플리케이션 유틸리티 함수를 만드는데 사용된다고 한다.
class Student {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// 클래스 몸체에서 정의한 메서드는 기본적으로 프로토타입 메서드가 된다
fullName() {
return `Your full name is ${this.firstName} ${this.lastName}`;
}
// 정적 메서드
static enrollStudents(...students) {
return 'ENROLLING STUDENTS!';
}
}
let firstStudent = new Student('Colt', 'Steele');
let secondStudent = new Student('Blue', 'Steele');
// 정적 메서드를 사용하면 클래스의 인스턴스화 없이 호출될 수 있다.
Student.enrollStudents([firstStudent, secondStudent]);
📌 메서드 내부에서 인스턴스 프로퍼티를 참조할 필요가 있다면 this를 써야 하기 때문에 프로토타입 메서드로 정의해야 하고, 반대 경우라면 정적 메서드로 정의하는 것이 좋다(메서드 내부에 this가 없어도 프로토타입 메서드로 정의할 수 있지만 인스턴스를 생성한 다음 호출해야 하므로 불편하다).
class StaticMethodCall {
static staticMethod() {
return '정적 메서드 실행';
}
static anotherStaticMethod() {
// 동일 클래스 내 다른 정적 메서드 내에서 정적 메서드를 호출하는 경우
// 키워드 this를 사용할 수 있다
return this.staticMethod() + '(다른 정적 메서드에서)';
}
}
StaticMethodCall.staticMethod(); // '정적 메서드 실행'
StaticMethodCall.anotherStaticMethod(); // '정적 메서드 실행(다른 정적 메서드에서)'
class Triple {
static triple(n) {
n = n || 1;
return n * 3;
}
}
class BiggerTriple extends Triple {
static triple(n) {
return super.triple(n) * super.triple(n);
}
}
console.log(Triple.triple()); // 3
console.log(Triple.triple(6)); // 18
BiggerTriple.triple(3); // 81
const tp = new Triple();
console.log(BiggerTriple.triple(3)); // 81
console.log(tp.triple()); // TypeError: tp.triple is not a function
console.log(tp.constructor.triple(4)); // 12