diff --git a/problem-1/SymbolTableWithArray.js b/problem-1/SymbolTableWithArray.js index 761c9ef..3e6657b 100644 --- a/problem-1/SymbolTableWithArray.js +++ b/problem-1/SymbolTableWithArray.js @@ -1,4 +1,84 @@ +/* +다음은 비순차 심볼 테이블의 API입니다. 이 API를 지원하는 심볼 테이블을 구현해 +주세요. +내부 데이터 구조는 순서가 없는 배열을 사용해서 구현해 주세요. + +| 메서드 | 설명 | +| --- | --- | +| LinkedListST() | 심볼 테이블을 생성합니다 | +| get(key): Item? | 키와 연관된 값을 반환한다. 해당하는 키가 없으면 undefined를 반환 | +| put(key, value): void | 키/값 쌍을 테이블에 넣는다. | +| delete(key): void | 키에 해당하는 키/값 쌍을 삭제한다 | +| contains(key): Boolean | 키와 연관된 값이 존재하면 true, 존재하지 않으면 false를 반환한다 | +| isEmpty(): Boolean | 심볼 테이블이 비어있으면 true, 비어있지 않으면 false를 반환한다 | +| size(): Number | 테이블에 저장된 키/값 쌍의 개수를 반환한다 | +| keys(): Iterable | 테이블에 저장된 모든 키 목록 배열을 반환 | +| values(): Iterable | 테이블에 저장된 모든 값 목록 배열을 반환 | + +*/ class SymbolTable { + #keys = []; + #values = []; + #n = 0; + + constructor(maxCount = 10) { + this.#keys = new Array(maxCount); + this.#values = new Array(maxCount); + } + + get(key) { + for (let i = 0; i < this.#n; i++) { + if (key === this.#keys[i]) { + return this.#values[i]; + } + } + } + + put(key, value) { + for (let i = 0; i < this.#n; i++) { + if (key === this.#keys[i]) { + this.#values[i] = value; + return; + } + } + + this.#keys[this.#n] = key; + this.#values[this.#n] = value; + this.#n++; + } + + delete(key) { + for (let i = 0; i < this.#n; i++) { + if (key === this.#keys[i]) { + for (let j = i; j < this.#n - 1; j++) { + this.#keys[j] = this.#keys[j + 1]; + this.#values[j] = this.#values[j + 1]; + } + this.#n--; + return; + } + } + } + + contains(key) { + return !!this.get(key); + } + + size() { + return this.#n; + } + + isEmpty() { + return this.#n === 0; + } + + keys() { + return this.#keys; + } + + values() { + return this.#values; + } } module.exports = { diff --git a/problem-2/SymbolTable.js b/problem-2/SymbolTable.js index 6bb3c68..b18733a 100644 --- a/problem-2/SymbolTable.js +++ b/problem-2/SymbolTable.js @@ -1,3 +1,37 @@ +/** + * + * +다음은 순차 심볼 테이블의 API입니다. + +| 메서드 | 설명 | +| --- | --- | +| ST() | 심볼 테이블을 생성합니다 | +| get(key): Item? | 키와 연관된 값을 반환한다. 해당하는 키가 없으면 undefined를 반환 | +| put(key, value): void | 키/값 쌍을 테이블에 넣는다. | +| delete(key): void | 키에 해당하는 키/값 쌍을 삭제한다 | +| contains(key): Boolean | 키와 연관된 값이 존재하면 true, 존재하지 않으면 false를 반환한다 | +| isEmpty(): Boolean | 심볼 테이블이 비어있으면 true, 비어있지 않으면 false를 반환한다 | +| size(): Number | 테이블에 저장된 키/값 쌍의 개수를 반환한다 | +| min(): Key? | 가장 작은 키를 반환한다 | +| max(): Key? | 가장 큰 키를 반환한다 | +| floor(Key): Key? | 키보다 작거나 같은 값 중에 가장 큰 키를 반환 | +| ceiling(Key): Key? | 주어진 키보다 크거나 같은 가장 작은 키를 반환 | +| rank(Key): Key? | 주어진 키보다 작은 키의 개수 반환 | +| select(k): Key? | k번째 순위의 키를 반환 | +| deleteMin(): void | 가장 작은 키를 삭제한다 | +| deleteMax(): void | 가장 큰 키를 삭제한다 | +| keys(): Iterable | 테이블에 저장된 모든 키 목록 배열을 반환 | +| keysRange(low, high): Iterable | [low..high] 범위에 속하는 키 목록 배열 반환 | + +위 API를 참고하여 아래 기능들을 구현해 주세요. `rank()`를 활용해서 구현해야 합니다. + +* contains +* floor +* ceiling +* keysRange + +*/ + class SymbolTable { #keys; @@ -71,7 +105,8 @@ class SymbolTable { const mid = start + Math.floor((end - start) / 2); if (key < this.#keys[mid]) { return this.rank(key, start, mid - 1); - } if (key > this.#keys[mid]) { + } + if (key > this.#keys[mid]) { return this.rank(key, mid + 1, end); } return mid; @@ -106,15 +141,57 @@ class SymbolTable { } contains(key) { + return !!this.get(key); } floor(key) { + if (this.isEmpty()) { + return; + } + const i = this.rank(key); + if (i === 0) { + return this.#keys[i] === key ? key : undefined; + } + + if (key === this.#keys[i]) { + return key; + } + + return this.#keys[i - 1]; } ceiling(key) { + if (this.isEmpty()) { + return; + } + const i = this.rank(key); + if (i >= this.#n) { + return; + } + + return this.#keys[i]; } keysRange(start, end) { + const startIndex = this.rank(start); + const endIndex = this.rank(end); + + const array = []; + + for (let i = startIndex; i < endIndex; i++) { + array.push(this.#keys[i]); + } + + if (this.#keys[endIndex] === end) { + array.push(end); + } + + return array; + /* + return endIndex === end + ? this.#keys.slice(startIndex, endIndex ) + : this.#keys.slice(startIndex, endIndex + 1); + */ } } diff --git a/problem-3/SymbolTable.js b/problem-3/SymbolTable.js index 8bff19c..c2a51a8 100644 --- a/problem-3/SymbolTable.js +++ b/problem-3/SymbolTable.js @@ -9,10 +9,13 @@ class Node { n; - constructor(key, value, n) { + height; + + constructor(key, value, n, height = 0) { this.key = key; this.value = value; this.n = n; + this.height = height; } } @@ -46,7 +49,8 @@ class SymbolTable { if (key < node.key) { return this.#get(node.left, key); - } if (key > node.key) { + } + if (key > node.key) { return this.#get(node.right, key); } return node.value; @@ -70,6 +74,8 @@ class SymbolTable { } node.n = this.#size(node.left) + this.#size(node.right) + 1; + node.height = + 1 + Math.max(this.#height(node.left), this.#height(node.right)); return node; } @@ -89,6 +95,22 @@ class SymbolTable { return this.#max(this.#root).key; } + #min(node) { + if (node.left === undefined) { + return node; + } + + return this.#min(node.left); + } + + #max(node) { + if (node.right === undefined) { + return node; + } + + return this.#max(node.right); + } + floor(key) { return this.#floor(this.#root, key)?.key; } @@ -143,7 +165,8 @@ class SymbolTable { const t = this.#size(node.left); if (t > k) { return this.#select(node.left, k); - } if (t < k) { + } + if (t < k) { return this.#select(node.right, k - t - 1); } return node; @@ -160,7 +183,8 @@ class SymbolTable { if (key < node.key) { return this.#rank(node.left, key); - } if (key > node.key) { + } + if (key > node.key) { return 1 + this.#size(node.left) + this.#rank(node.right, key); } return this.#size(node.left); @@ -181,6 +205,8 @@ class SymbolTable { node.left = this.#deleteMin(node.left); node.n = this.#size(node.left) + this.#size(node.right) + 1; + node.height = + 1 + Math.max(this.#height(node.left), this.#height(node.right)); return node; } @@ -213,6 +239,9 @@ class SymbolTable { } node.n = this.#size(node.left) + this.#size(node.right) + 1; + + node.height = + 1 + Math.max(this.#height(node.left), this.#height(node.right)); return node; } @@ -254,20 +283,34 @@ class SymbolTable { } } - #min(node) { - if (node.left === undefined) { - return node; + /** + * + * + 2. 트리의 높이를 구하는 height를 구현해 주세요. 이때 2가지 버전의 구현을 만들어 + 주세요. 첫 번째는 재귀로 실행할 때마다 새로 계산해서 높이를 구하는 버전을 + 만들어 주세요. 두 번째는 `size()`와 비슷하게 각 노드에 트리의 높이를 담는 + 필드 변수를 활용해서 구현한 버전을 만들어 주세요. + + */ + height() { + return this.#height(this.#root); + } + + /* + #height(node) { + if (node === undefined) { + return -1; } - return this.#min(node.left); - } + return 1 + Math.max(this.#height(node.left), this.#height(node.right)); + }*/ - #max(node) { - if (node.right === undefined) { - return node; + #height(node) { + if (node === undefined) { + return -1; } - return this.#max(node.right); + return node.height; } } diff --git a/problem-5/LinearProbingHashTable.test.js b/problem-5/LinearProbingHashTable.test.js index e4a1b2b..0eaeda5 100644 --- a/problem-5/LinearProbingHashTable.test.js +++ b/problem-5/LinearProbingHashTable.test.js @@ -19,31 +19,53 @@ class LinearProbingHashTable { } hash(key) { - return this.#hash(key) + return this.#hash(key); } get(key) { + for ( + let i = this.#hash(key); + this.#keys[i] !== undefined; + i = (i + 1) % this.#M + ) { + if (this.#keys[i] === key) { + return this.#values[i]; + } + } } put(key, value) { + if (this.#N >= this.#M / 2) { + this.#resize(this.#M * 2); + } + let i; + for ( + i = this.#hash(key); + this.#keys[i] !== undefined; + i = (i + 1) % this.#M + ) { + if (this.#keys[i] === key) { + this.#values[i] = value; + return; + } + } + this.#keys[i] = key; + this.#values[i] = value; + this.#N++; } - delete(key) { - } + delete(key) {} - contains(key) { - } + contains(key) {} - keys() { - } + keys() {} - #resize(capacity) { - } + #resize(capacity) {} } const randomString = (max) => { - let result = ''; - const characters = 'abcdefghijklmnopqrstuvwxyz'; + let result = ""; + const characters = "abcdefghijklmnopqrstuvwxyz"; const charactersLength = characters.length; const length = Math.floor(Math.random() * max) + 1; @@ -53,73 +75,73 @@ const randomString = (max) => { } return result; -} +}; -test('이미 있는 키 값에 값을 추가하면 이전 값을 덮어쓴다', () => { +test("이미 있는 키 값에 값을 추가하면 이전 값을 덮어쓴다", () => { const st = new LinearProbingHashTable(); - st.put('foo', 'bar'); + st.put("foo", "bar"); - expect(st.get('foo')).toBe('bar'); + expect(st.get("foo")).toBe("bar"); - st.put('foo', 'other'); + st.put("foo", "other"); - expect(st.get('foo')).toBe('other'); + expect(st.get("foo")).toBe("other"); }); -test('삭제한 키를 조회하면 undefined를 반환한다', () => { +test("삭제한 키를 조회하면 undefined를 반환한다", () => { const st = new LinearProbingHashTable(); - st.put('foo', 'bar'); + st.put("foo", "bar"); - expect(st.get('foo')).toBe('bar'); + expect(st.get("foo")).toBe("bar"); - st.delete('foo'); + st.delete("foo"); - expect(st.get('foo')).toBeUndefined(); + expect(st.get("foo")).toBeUndefined(); }); -test('없는 키를 조회하면 undefined를 반환한다', () => { +test("없는 키를 조회하면 undefined를 반환한다", () => { const st = new LinearProbingHashTable(); - expect(st.get('foo')).toBeUndefined(); + expect(st.get("foo")).toBeUndefined(); }); -test('contains 해당하는 키와 값이 존재할 경우 true를 반환한다', () => { +test("contains 해당하는 키와 값이 존재할 경우 true를 반환한다", () => { const st = new LinearProbingHashTable(); - st.put('foo', 'bar'); + st.put("foo", "bar"); - expect(st.contains('foo')).toBe(true); + expect(st.contains("foo")).toBe(true); }); -test('contains 해당하는 키와 값이 없을 경우 false를 반환한다', () => { +test("contains 해당하는 키와 값이 없을 경우 false를 반환한다", () => { const st = new LinearProbingHashTable(); - expect(st.contains('foo')).toBe(false); + expect(st.contains("foo")).toBe(false); }); -test('심볼 테이블은 키에 해당하는 값을 저장한다', () => { +test("심볼 테이블은 키에 해당하는 값을 저장한다", () => { const st = new LinearProbingHashTable(); - st.put('foo', 'bar'); - st.put('something', 'that'); - st.put('this', 'is'); + st.put("foo", "bar"); + st.put("something", "that"); + st.put("this", "is"); - expect(st.get('foo')).toBe('bar'); - expect(st.get('something')).toBe('that'); - expect(st.get('this')).toBe('is'); + expect(st.get("foo")).toBe("bar"); + expect(st.get("something")).toBe("that"); + expect(st.get("this")).toBe("is"); - st.delete('this'); - st.delete('something'); - st.delete('foo'); + st.delete("this"); + st.delete("something"); + st.delete("foo"); - expect(st.get('foo')).toBeUndefined(); - expect(st.get('something')).toBeUndefined(); - expect(st.get('this')).toBeUndefined(); + expect(st.get("foo")).toBeUndefined(); + expect(st.get("something")).toBeUndefined(); + expect(st.get("this")).toBeUndefined(); }); -test('해시의 결과 같더라도, 키에 해당하는 값이 저장된다.', () => { +test("해시의 결과 같더라도, 키에 해당하는 값이 저장된다.", () => { const st = new LinearProbingHashTable(); let a; @@ -133,14 +155,14 @@ test('해시의 결과 같더라도, 키에 해당하는 값이 저장된다.', } } - st.put(a, '123'); - st.put(b, '456'); + st.put(a, "123"); + st.put(b, "456"); - expect(st.get(a)).toBe('123'); - expect(st.get(b)).toBe('456'); + expect(st.get(a)).toBe("123"); + expect(st.get(b)).toBe("456"); }); -test('keys는 모든 키 목록을 담은 배열을 반환한다', () => { +test("keys는 모든 키 목록을 담은 배열을 반환한다", () => { const st = new LinearProbingHashTable(); let a; @@ -154,16 +176,16 @@ test('keys는 모든 키 목록을 담은 배열을 반환한다', () => { } } - st.put(a, '123'); - st.put(b, '456'); - st.put('FOO', 'bar'); - st.put('HELLO', 'world'); + st.put(a, "123"); + st.put(b, "456"); + st.put("FOO", "bar"); + st.put("HELLO", "world"); const keys = st.keys(); expect(keys.length).toBe(4); expect(keys.includes(a)).toBe(true); expect(keys.includes(b)).toBe(true); - expect(keys.includes('FOO')).toBe(true); - expect(keys.includes('HELLO')).toBe(true); + expect(keys.includes("FOO")).toBe(true); + expect(keys.includes("HELLO")).toBe(true); }); diff --git a/problem-5/SeperateChainingHashTable.test.js b/problem-5/SeperateChainingHashTable.test.js index 6f9e2b1..2bfb6da 100644 --- a/problem-5/SeperateChainingHashTable.test.js +++ b/problem-5/SeperateChainingHashTable.test.js @@ -1,8 +1,18 @@ +const { + SymbolTableWithLinkedList, +} = require("../problem-1/SymbolTableWithLinkedList"); + class SeperateChainingHashTable { #M; + #st; constructor(maxCount = 997) { this.#M = maxCount; + + this.#st = new Array(maxCount); + for (let i = 0; i < this.#M; i++) { + this.#st[i] = new SymbolTableWithLinkedList(); + } } #hash(key) { @@ -14,28 +24,40 @@ class SeperateChainingHashTable { } hash(key) { - return this.#hash(key) + return this.#hash(key); } get(key) { + return this.#st[this.#hash(key)].get(key); } put(key, value) { + this.#st[this.#hash(key)].put(key, value); } delete(key, value) { + this.#st[this.#hash(key)].delete(key); } - + contains(key) { + return !!this.get(key); } keys() { + const array = []; + + for (let i = 0; i < this.#M; i++) { + const currentKeys = this.#st[i].keys(); + array.push(...currentKeys); + } + + return array; } } const randomString = (max) => { - let result = ''; - const characters = 'abcdefghijklmnopqrstuvwxyz'; + let result = ""; + const characters = "abcdefghijklmnopqrstuvwxyz"; const charactersLength = characters.length; const length = Math.floor(Math.random() * max) + 1; @@ -45,73 +67,73 @@ const randomString = (max) => { } return result; -} +}; -test('이미 있는 키 값에 값을 추가하면 이전 값을 덮어쓴다', () => { +test("이미 있는 키 값에 값을 추가하면 이전 값을 덮어쓴다", () => { const st = new SeperateChainingHashTable(); - st.put('foo', 'bar'); + st.put("foo", "bar"); - expect(st.get('foo')).toBe('bar'); + expect(st.get("foo")).toBe("bar"); - st.put('foo', 'other'); + st.put("foo", "other"); - expect(st.get('foo')).toBe('other'); + expect(st.get("foo")).toBe("other"); }); -test('삭제한 키를 조회하면 undefined를 반환한다', () => { +test("삭제한 키를 조회하면 undefined를 반환한다", () => { const st = new SeperateChainingHashTable(); - st.put('foo', 'bar'); + st.put("foo", "bar"); - expect(st.get('foo')).toBe('bar'); + expect(st.get("foo")).toBe("bar"); - st.delete('foo'); + st.delete("foo"); - expect(st.get('foo')).toBeUndefined(); + expect(st.get("foo")).toBeUndefined(); }); -test('없는 키를 조회하면 undefined를 반환한다', () => { +test("없는 키를 조회하면 undefined를 반환한다", () => { const st = new SeperateChainingHashTable(); - expect(st.get('foo')).toBeUndefined(); + expect(st.get("foo")).toBeUndefined(); }); -test('contains 해당하는 키와 값이 존재할 경우 true를 반환한다', () => { +test("contains 해당하는 키와 값이 존재할 경우 true를 반환한다", () => { const st = new SeperateChainingHashTable(); - st.put('foo', 'bar'); + st.put("foo", "bar"); - expect(st.contains('foo')).toBe(true); + expect(st.contains("foo")).toBe(true); }); -test('contains 해당하는 키와 값이 없을 경우 false를 반환한다', () => { +test("contains 해당하는 키와 값이 없을 경우 false를 반환한다", () => { const st = new SeperateChainingHashTable(); - expect(st.contains('foo')).toBe(false); + expect(st.contains("foo")).toBe(false); }); -test('심볼 테이블은 키에 해당하는 값을 저장한다', () => { +test("심볼 테이블은 키에 해당하는 값을 저장한다", () => { const st = new SeperateChainingHashTable(); - st.put('foo', 'bar'); - st.put('something', 'that'); - st.put('this', 'is'); + st.put("foo", "bar"); + st.put("something", "that"); + st.put("this", "is"); - expect(st.get('foo')).toBe('bar'); - expect(st.get('something')).toBe('that'); - expect(st.get('this')).toBe('is'); + expect(st.get("foo")).toBe("bar"); + expect(st.get("something")).toBe("that"); + expect(st.get("this")).toBe("is"); - st.delete('this'); - st.delete('something'); - st.delete('foo'); + st.delete("this"); + st.delete("something"); + st.delete("foo"); - expect(st.get('foo')).toBeUndefined(); - expect(st.get('something')).toBeUndefined(); - expect(st.get('this')).toBeUndefined(); + expect(st.get("foo")).toBeUndefined(); + expect(st.get("something")).toBeUndefined(); + expect(st.get("this")).toBeUndefined(); }); -test('해시의 결과 같더라도, 키에 해당하는 값이 저장된다.', () => { +test("해시의 결과 같더라도, 키에 해당하는 값이 저장된다.", () => { const st = new SeperateChainingHashTable(); let a; @@ -125,14 +147,14 @@ test('해시의 결과 같더라도, 키에 해당하는 값이 저장된다.', } } - st.put(a, '123'); - st.put(b, '456'); + st.put(a, "123"); + st.put(b, "456"); - expect(st.get(a)).toBe('123'); - expect(st.get(b)).toBe('456'); + expect(st.get(a)).toBe("123"); + expect(st.get(b)).toBe("456"); }); -test('keys는 모든 키 목록을 담은 배열을 반환한다', () => { +test("keys는 모든 키 목록을 담은 배열을 반환한다", () => { const st = new SeperateChainingHashTable(); let a; @@ -146,16 +168,16 @@ test('keys는 모든 키 목록을 담은 배열을 반환한다', () => { } } - st.put(a, '123'); - st.put(b, '456'); - st.put('FOO', 'bar'); - st.put('HELLO', 'world'); + st.put(a, "123"); + st.put(b, "456"); + st.put("FOO", "bar"); + st.put("HELLO", "world"); const keys = st.keys(); expect(keys.length).toBe(4); expect(keys.includes(a)).toBe(true); expect(keys.includes(b)).toBe(true); - expect(keys.includes('FOO')).toBe(true); - expect(keys.includes('HELLO')).toBe(true); + expect(keys.includes("FOO")).toBe(true); + expect(keys.includes("HELLO")).toBe(true); });