hangul-trie 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- export declare class Trie {
1
+ declare class Trie {
2
2
  private root;
3
3
  size: number;
4
4
  constructor();
@@ -24,3 +24,5 @@ export declare class Trie {
24
24
  */
25
25
  autoComplete(str: string): string[];
26
26
  }
27
+
28
+ export { Trie };
package/dist/index.d.ts CHANGED
@@ -1 +1,28 @@
1
- export { Trie } from './core/index.js';
1
+ declare class Trie {
2
+ private root;
3
+ size: number;
4
+ constructor();
5
+ private static _dfs;
6
+ /**
7
+ * Trie 자료구조에 문자열을 삽입합니다.
8
+ */
9
+ insert(str: string): void;
10
+ /**
11
+ * 해당 문자열이 Trie에 존재하는지 검색합니다.
12
+ */
13
+ has(str: string): boolean;
14
+ /**
15
+ * 해당 문자열을 Trie 자료구조에서 제거합니다.
16
+ */
17
+ remove(str: string): void;
18
+ /**
19
+ * 현재 Trie 자료구조에 삽입된 모든 문자열을 반환합니다.
20
+ */
21
+ getAll(): string[];
22
+ /**
23
+ * 현재 문자열로부터 완성될 수 있는 문자열들을 반환합니다.
24
+ */
25
+ autoComplete(str: string): string[];
26
+ }
27
+
28
+ export { Trie };
package/dist/index.js CHANGED
@@ -1 +1,224 @@
1
- export { Trie } from './core/index.js';
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Trie: () => Trie
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/utils/index.ts
28
+ var import_es_hangul = require("es-hangul");
29
+ function commonPrefixLength(a, b) {
30
+ let len = 0;
31
+ while (len < a.length && len < b.length) {
32
+ if (a[len] !== b[len]) break;
33
+ len += 1;
34
+ }
35
+ return len;
36
+ }
37
+ function hasHangul(str) {
38
+ return [...str].some((s) => /^[가-힣ㄱ-ㅣ]$/.test(s));
39
+ }
40
+ function onlyHangulOrSpace(str) {
41
+ return [...str].every((s) => /^[가-힣ㄱ-ㅣ ]$/.test(s));
42
+ }
43
+ function integratedDisassemble(str) {
44
+ const isSomeHangul = hasHangul(str);
45
+ const isEveryHangul = onlyHangulOrSpace(str);
46
+ if (isEveryHangul) return (0, import_es_hangul.disassemble)(str);
47
+ if (isSomeHangul) {
48
+ const splitted = str.split(/([가-힣ㄱ-ㅣ]+)/);
49
+ return splitted.reduce((acc, cur) => {
50
+ if (cur.length === 0) return acc;
51
+ const isHangul = /^[가-힣ㄱ-ㅣ]$/.test(cur[0]);
52
+ if (!isHangul) return acc + cur;
53
+ return acc + (0, import_es_hangul.disassemble)(cur);
54
+ }, "");
55
+ }
56
+ return str;
57
+ }
58
+ function integratedAssemble(str) {
59
+ const isSomeHangul = hasHangul(str);
60
+ const isEveryHangul = onlyHangulOrSpace(str);
61
+ if (isEveryHangul) return (0, import_es_hangul.assemble)([str]);
62
+ if (isSomeHangul) {
63
+ const splitted = str.split(/([가-힣ㄱ-ㅣ]+)/);
64
+ return splitted.reduce((acc, cur) => {
65
+ if (cur.length === 0) return acc;
66
+ const isHangul = /^[가-힣ㄱ-ㅣ]$/.test(cur[0]);
67
+ if (!isHangul) return acc + cur;
68
+ return acc + (0, import_es_hangul.assemble)([...cur]);
69
+ }, "");
70
+ }
71
+ return str;
72
+ }
73
+
74
+ // src/core/index.ts
75
+ var TrieNode = class {
76
+ prefix;
77
+ isEndofStr;
78
+ children;
79
+ constructor(prefix = "") {
80
+ this.prefix = prefix;
81
+ this.isEndofStr = false;
82
+ this.children = {};
83
+ }
84
+ };
85
+ var Trie = class _Trie {
86
+ root;
87
+ size;
88
+ constructor() {
89
+ this.root = new TrieNode();
90
+ this.size = 0;
91
+ }
92
+ static _dfs(curNode, prefix, collectArray) {
93
+ const concatenatedPrefix = prefix + curNode.prefix;
94
+ if (curNode.isEndofStr) {
95
+ collectArray.push(integratedAssemble(concatenatedPrefix));
96
+ }
97
+ for (const childNode of Object.values(curNode.children)) {
98
+ _Trie._dfs(childNode, concatenatedPrefix, collectArray);
99
+ }
100
+ }
101
+ /**
102
+ * Trie 자료구조에 문자열을 삽입합니다.
103
+ */
104
+ insert(str) {
105
+ if (this.has(str)) return;
106
+ str = integratedDisassemble(str);
107
+ let node = this.root;
108
+ let i = 0;
109
+ while (i < str.length) {
110
+ const key = str[i];
111
+ const isChildExists = key in node.children;
112
+ if (!isChildExists) {
113
+ const newNode = new TrieNode(str.slice(i));
114
+ newNode.isEndofStr = true;
115
+ node.children[key] = newNode;
116
+ break;
117
+ }
118
+ node = node.children[key];
119
+ const prefixLen = commonPrefixLength(node.prefix, str.slice(i));
120
+ i += prefixLen;
121
+ if (prefixLen < node.prefix.length) {
122
+ const newNode = new TrieNode(node.prefix.slice(prefixLen));
123
+ newNode.isEndofStr = node.isEndofStr;
124
+ newNode.children = node.children;
125
+ node.prefix = node.prefix.slice(0, prefixLen);
126
+ node.children = { [newNode.prefix[0]]: newNode };
127
+ node.isEndofStr = i === str.length;
128
+ }
129
+ }
130
+ this.size += 1;
131
+ }
132
+ /**
133
+ * 해당 문자열이 Trie에 존재하는지 검색합니다.
134
+ */
135
+ has(str) {
136
+ str = integratedDisassemble(str);
137
+ let node = this.root;
138
+ let i = 0;
139
+ while (i < str.length) {
140
+ const key = str[i];
141
+ if (!(key in node.children)) return false;
142
+ node = node.children[key];
143
+ const prefixLen = commonPrefixLength(str.slice(i), node.prefix);
144
+ if (prefixLen !== node.prefix.length) return false;
145
+ i += prefixLen;
146
+ if (i === str.length) return node.isEndofStr;
147
+ }
148
+ return false;
149
+ }
150
+ /**
151
+ * 해당 문자열을 Trie 자료구조에서 제거합니다.
152
+ */
153
+ remove(str) {
154
+ str = integratedDisassemble(str);
155
+ let parentNode = null;
156
+ let node = this.root;
157
+ let i = 0;
158
+ while (i < str.length) {
159
+ const key = str[i];
160
+ if (!(key in node.children)) return;
161
+ parentNode = node;
162
+ node = node.children[key];
163
+ const prefixLen = commonPrefixLength(str.slice(i), node.prefix);
164
+ if (prefixLen !== node.prefix.length) return;
165
+ i += prefixLen;
166
+ if (i === str.length && node.isEndofStr) {
167
+ node.isEndofStr = false;
168
+ this.size -= 1;
169
+ const childrenKeys = Object.keys(node.children);
170
+ const childrenCount = childrenKeys.length;
171
+ if (childrenCount === 0) {
172
+ delete parentNode.children[node.prefix[0]];
173
+ const parentNodeChildrenKeys = Object.keys(parentNode.children);
174
+ const parentNodeChildrenCount = parentNodeChildrenKeys.length;
175
+ if (!parentNode.isEndofStr && parentNodeChildrenCount === 1) {
176
+ const childNode = parentNode.children[parentNodeChildrenKeys[0]];
177
+ parentNode.prefix += childNode.prefix;
178
+ parentNode.isEndofStr = childNode.isEndofStr;
179
+ parentNode.children = childNode.children;
180
+ }
181
+ } else if (childrenCount === 1) {
182
+ const childNode = node.children[childrenKeys[0]];
183
+ node.prefix += childNode.prefix;
184
+ node.isEndofStr = childNode.isEndofStr;
185
+ node.children = childNode.children;
186
+ }
187
+ return;
188
+ }
189
+ }
190
+ }
191
+ /**
192
+ * 현재 Trie 자료구조에 삽입된 모든 문자열을 반환합니다.
193
+ */
194
+ getAll() {
195
+ const result = [];
196
+ _Trie._dfs(this.root, "", result);
197
+ return result;
198
+ }
199
+ /**
200
+ * 현재 문자열로부터 완성될 수 있는 문자열들을 반환합니다.
201
+ */
202
+ autoComplete(str) {
203
+ const result = [];
204
+ str = integratedDisassemble(str);
205
+ let node = this.root;
206
+ let i = 0;
207
+ while (i < str.length) {
208
+ const key = str[i];
209
+ if (!(key in node.children)) return [];
210
+ node = node.children[key];
211
+ const remainStr = str.slice(i);
212
+ const prefixLen = commonPrefixLength(remainStr, node.prefix);
213
+ if (prefixLen === remainStr.length) break;
214
+ else if (prefixLen === node.prefix.length) i += prefixLen;
215
+ else return [];
216
+ }
217
+ _Trie._dfs(node, str.slice(0, i), result);
218
+ return result;
219
+ }
220
+ };
221
+ // Annotate the CommonJS export names for ESM import in node:
222
+ 0 && (module.exports = {
223
+ Trie
224
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,197 @@
1
+ // src/utils/index.ts
2
+ import { assemble, disassemble } from "es-hangul";
3
+ function commonPrefixLength(a, b) {
4
+ let len = 0;
5
+ while (len < a.length && len < b.length) {
6
+ if (a[len] !== b[len]) break;
7
+ len += 1;
8
+ }
9
+ return len;
10
+ }
11
+ function hasHangul(str) {
12
+ return [...str].some((s) => /^[가-힣ㄱ-ㅣ]$/.test(s));
13
+ }
14
+ function onlyHangulOrSpace(str) {
15
+ return [...str].every((s) => /^[가-힣ㄱ-ㅣ ]$/.test(s));
16
+ }
17
+ function integratedDisassemble(str) {
18
+ const isSomeHangul = hasHangul(str);
19
+ const isEveryHangul = onlyHangulOrSpace(str);
20
+ if (isEveryHangul) return disassemble(str);
21
+ if (isSomeHangul) {
22
+ const splitted = str.split(/([가-힣ㄱ-ㅣ]+)/);
23
+ return splitted.reduce((acc, cur) => {
24
+ if (cur.length === 0) return acc;
25
+ const isHangul = /^[가-힣ㄱ-ㅣ]$/.test(cur[0]);
26
+ if (!isHangul) return acc + cur;
27
+ return acc + disassemble(cur);
28
+ }, "");
29
+ }
30
+ return str;
31
+ }
32
+ function integratedAssemble(str) {
33
+ const isSomeHangul = hasHangul(str);
34
+ const isEveryHangul = onlyHangulOrSpace(str);
35
+ if (isEveryHangul) return assemble([str]);
36
+ if (isSomeHangul) {
37
+ const splitted = str.split(/([가-힣ㄱ-ㅣ]+)/);
38
+ return splitted.reduce((acc, cur) => {
39
+ if (cur.length === 0) return acc;
40
+ const isHangul = /^[가-힣ㄱ-ㅣ]$/.test(cur[0]);
41
+ if (!isHangul) return acc + cur;
42
+ return acc + assemble([...cur]);
43
+ }, "");
44
+ }
45
+ return str;
46
+ }
47
+
48
+ // src/core/index.ts
49
+ var TrieNode = class {
50
+ prefix;
51
+ isEndofStr;
52
+ children;
53
+ constructor(prefix = "") {
54
+ this.prefix = prefix;
55
+ this.isEndofStr = false;
56
+ this.children = {};
57
+ }
58
+ };
59
+ var Trie = class _Trie {
60
+ root;
61
+ size;
62
+ constructor() {
63
+ this.root = new TrieNode();
64
+ this.size = 0;
65
+ }
66
+ static _dfs(curNode, prefix, collectArray) {
67
+ const concatenatedPrefix = prefix + curNode.prefix;
68
+ if (curNode.isEndofStr) {
69
+ collectArray.push(integratedAssemble(concatenatedPrefix));
70
+ }
71
+ for (const childNode of Object.values(curNode.children)) {
72
+ _Trie._dfs(childNode, concatenatedPrefix, collectArray);
73
+ }
74
+ }
75
+ /**
76
+ * Trie 자료구조에 문자열을 삽입합니다.
77
+ */
78
+ insert(str) {
79
+ if (this.has(str)) return;
80
+ str = integratedDisassemble(str);
81
+ let node = this.root;
82
+ let i = 0;
83
+ while (i < str.length) {
84
+ const key = str[i];
85
+ const isChildExists = key in node.children;
86
+ if (!isChildExists) {
87
+ const newNode = new TrieNode(str.slice(i));
88
+ newNode.isEndofStr = true;
89
+ node.children[key] = newNode;
90
+ break;
91
+ }
92
+ node = node.children[key];
93
+ const prefixLen = commonPrefixLength(node.prefix, str.slice(i));
94
+ i += prefixLen;
95
+ if (prefixLen < node.prefix.length) {
96
+ const newNode = new TrieNode(node.prefix.slice(prefixLen));
97
+ newNode.isEndofStr = node.isEndofStr;
98
+ newNode.children = node.children;
99
+ node.prefix = node.prefix.slice(0, prefixLen);
100
+ node.children = { [newNode.prefix[0]]: newNode };
101
+ node.isEndofStr = i === str.length;
102
+ }
103
+ }
104
+ this.size += 1;
105
+ }
106
+ /**
107
+ * 해당 문자열이 Trie에 존재하는지 검색합니다.
108
+ */
109
+ has(str) {
110
+ str = integratedDisassemble(str);
111
+ let node = this.root;
112
+ let i = 0;
113
+ while (i < str.length) {
114
+ const key = str[i];
115
+ if (!(key in node.children)) return false;
116
+ node = node.children[key];
117
+ const prefixLen = commonPrefixLength(str.slice(i), node.prefix);
118
+ if (prefixLen !== node.prefix.length) return false;
119
+ i += prefixLen;
120
+ if (i === str.length) return node.isEndofStr;
121
+ }
122
+ return false;
123
+ }
124
+ /**
125
+ * 해당 문자열을 Trie 자료구조에서 제거합니다.
126
+ */
127
+ remove(str) {
128
+ str = integratedDisassemble(str);
129
+ let parentNode = null;
130
+ let node = this.root;
131
+ let i = 0;
132
+ while (i < str.length) {
133
+ const key = str[i];
134
+ if (!(key in node.children)) return;
135
+ parentNode = node;
136
+ node = node.children[key];
137
+ const prefixLen = commonPrefixLength(str.slice(i), node.prefix);
138
+ if (prefixLen !== node.prefix.length) return;
139
+ i += prefixLen;
140
+ if (i === str.length && node.isEndofStr) {
141
+ node.isEndofStr = false;
142
+ this.size -= 1;
143
+ const childrenKeys = Object.keys(node.children);
144
+ const childrenCount = childrenKeys.length;
145
+ if (childrenCount === 0) {
146
+ delete parentNode.children[node.prefix[0]];
147
+ const parentNodeChildrenKeys = Object.keys(parentNode.children);
148
+ const parentNodeChildrenCount = parentNodeChildrenKeys.length;
149
+ if (!parentNode.isEndofStr && parentNodeChildrenCount === 1) {
150
+ const childNode = parentNode.children[parentNodeChildrenKeys[0]];
151
+ parentNode.prefix += childNode.prefix;
152
+ parentNode.isEndofStr = childNode.isEndofStr;
153
+ parentNode.children = childNode.children;
154
+ }
155
+ } else if (childrenCount === 1) {
156
+ const childNode = node.children[childrenKeys[0]];
157
+ node.prefix += childNode.prefix;
158
+ node.isEndofStr = childNode.isEndofStr;
159
+ node.children = childNode.children;
160
+ }
161
+ return;
162
+ }
163
+ }
164
+ }
165
+ /**
166
+ * 현재 Trie 자료구조에 삽입된 모든 문자열을 반환합니다.
167
+ */
168
+ getAll() {
169
+ const result = [];
170
+ _Trie._dfs(this.root, "", result);
171
+ return result;
172
+ }
173
+ /**
174
+ * 현재 문자열로부터 완성될 수 있는 문자열들을 반환합니다.
175
+ */
176
+ autoComplete(str) {
177
+ const result = [];
178
+ str = integratedDisassemble(str);
179
+ let node = this.root;
180
+ let i = 0;
181
+ while (i < str.length) {
182
+ const key = str[i];
183
+ if (!(key in node.children)) return [];
184
+ node = node.children[key];
185
+ const remainStr = str.slice(i);
186
+ const prefixLen = commonPrefixLength(remainStr, node.prefix);
187
+ if (prefixLen === remainStr.length) break;
188
+ else if (prefixLen === node.prefix.length) i += prefixLen;
189
+ else return [];
190
+ }
191
+ _Trie._dfs(node, str.slice(0, i), result);
192
+ return result;
193
+ }
194
+ };
195
+ export {
196
+ Trie
197
+ };
package/package.json CHANGED
@@ -1,18 +1,35 @@
1
1
  {
2
2
  "name": "hangul-trie",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "한글을 지원하는 Compressed Trie 자료구조",
5
5
  "license": "MIT",
6
6
  "author": "Donghyeon Lee <ehdgus001928@gmail.com>",
7
- "type": "module",
7
+ "type": "commonjs",
8
8
  "main": "./dist/index.js",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": {
14
+ "types": "./dist/index.d.mts",
15
+ "default": "./dist/index.mjs"
16
+ },
17
+ "require": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ }
21
+ }
22
+ },
9
23
  "scripts": {
10
- "build": "tsc --build --clean && tsc",
24
+ "build": "tsup",
11
25
  "test": "vitest"
12
26
  },
13
- "files": ["dist"],
27
+ "files": [
28
+ "dist"
29
+ ],
14
30
  "devDependencies": {
15
31
  "@types/node": "^25.1.0",
32
+ "tsup": "^8.5.1",
16
33
  "typescript": "^5.9.3",
17
34
  "vitest": "^4.0.18"
18
35
  },
@@ -1,180 +0,0 @@
1
- import { commonPrefixLength, integratedAssemble, integratedDisassemble } from "@/utils/index.js";
2
- class TrieNode {
3
- prefix;
4
- isEndofStr;
5
- children;
6
- constructor(prefix = '') {
7
- this.prefix = prefix;
8
- this.isEndofStr = false;
9
- this.children = {};
10
- }
11
- }
12
- export class Trie {
13
- root;
14
- size;
15
- constructor() {
16
- this.root = new TrieNode();
17
- this.size = 0;
18
- }
19
- static _dfs(curNode, prefix, collectArray) {
20
- const concatenatedPrefix = prefix + curNode.prefix;
21
- if (curNode.isEndofStr) {
22
- collectArray.push(integratedAssemble(concatenatedPrefix));
23
- }
24
- for (const childNode of Object.values(curNode.children)) {
25
- Trie._dfs(childNode, concatenatedPrefix, collectArray);
26
- }
27
- }
28
- /**
29
- * Trie 자료구조에 문자열을 삽입합니다.
30
- */
31
- insert(str) {
32
- // 한글 분해
33
- str = integratedDisassemble(str);
34
- let node = this.root;
35
- let i = 0;
36
- while (i < str.length) {
37
- const key = str[i];
38
- const isChildExists = key in node.children;
39
- // 현재 글자를 key로 가지는 자식 노드가 없다면 새로 생성
40
- if (!isChildExists) {
41
- const newNode = new TrieNode(str.slice(i));
42
- newNode.isEndofStr = true;
43
- node.children[key] = newNode;
44
- break;
45
- }
46
- // 현재 글자를 key로 가지는 자식 노드로 이동
47
- node = node.children[key];
48
- // 공통 prefix 길이 계산
49
- const prefixLen = commonPrefixLength(node.prefix, str.slice(i));
50
- i += prefixLen;
51
- // 공통 prefixLen이 현재 노드의 prefix보다 작은 경우
52
- // 현재 노드의 prefix를 prefixLen 기준으로 노드 두 개로 쪼개기
53
- if (prefixLen < node.prefix.length) {
54
- const newNode = new TrieNode(node.prefix.slice(prefixLen));
55
- newNode.isEndofStr = node.isEndofStr;
56
- newNode.children = node.children;
57
- node.prefix = node.prefix.slice(0, prefixLen);
58
- node.children = { [newNode.prefix[0]]: newNode };
59
- // 공통 prefix가 삽입한 문자열의 끝부분인경우 현재 노드가 end 노드가 됨
60
- node.isEndofStr = i === str.length;
61
- }
62
- }
63
- this.size += 1;
64
- }
65
- /**
66
- * 해당 문자열이 Trie에 존재하는지 검색합니다.
67
- */
68
- has(str) {
69
- // 한글 분해
70
- str = integratedDisassemble(str);
71
- let node = this.root;
72
- let i = 0;
73
- while (i < str.length) {
74
- const key = str[i];
75
- // 현재 글자와 일치하는 경로가 없다면 검색 실패
76
- if (!(key in node.children))
77
- return false;
78
- node = node.children[key];
79
- const prefixLen = commonPrefixLength(str.slice(i), node.prefix);
80
- // 공통 prefix 길이가 현재 node prefix 길이와 일치하지 않다면
81
- // 검색하려는 문자열보다 현재 노드의 prefix가 더 긴 경우이므로 검색 실패
82
- if (prefixLen !== node.prefix.length)
83
- return false;
84
- i += prefixLen;
85
- // 공통 prefix가 검색하려는 문자열의 끝부분이라면
86
- // 현재 노드가 EndofStr인지 여부에 따라 검색 성공 실패 여부 결정
87
- if (i === str.length)
88
- return node.isEndofStr;
89
- }
90
- return false;
91
- }
92
- /**
93
- * 해당 문자열을 Trie 자료구조에서 제거합니다.
94
- */
95
- remove(str) {
96
- // 한글 분해
97
- str = integratedDisassemble(str);
98
- let parentNode = null;
99
- let node = this.root;
100
- let i = 0;
101
- while (i < str.length) {
102
- const key = str[i];
103
- if (!(key in node.children))
104
- return;
105
- parentNode = node;
106
- node = node.children[key];
107
- const prefixLen = commonPrefixLength(str.slice(i), node.prefix);
108
- if (prefixLen !== node.prefix.length)
109
- return;
110
- i += prefixLen;
111
- // 제거하려는 문자열과 일치하는 노드 검색 성공 시
112
- if (i === str.length && node.isEndofStr) {
113
- // 더 이상 EndofStr이 아니게 수정(문자열 제거)
114
- node.isEndofStr = false;
115
- this.size -= 1;
116
- const childrenKeys = Object.keys(node.children);
117
- const childrenCount = childrenKeys.length;
118
- // leaf 노드인 경우 해당 노드로 가는 key 제거
119
- if (childrenCount === 0) {
120
- delete parentNode.children[node.prefix[0]];
121
- const parentNodeChildrenKeys = Object.keys(parentNode.children);
122
- const parentNodeChildrenCount = parentNodeChildrenKeys.length;
123
- // 부모 노드가 EndofStr이 아니고 제거 후 부모 노드의 자식 노드가 하나뿐이라면 병합
124
- if (!parentNode.isEndofStr && parentNodeChildrenCount === 1) {
125
- const childNode = parentNode.children[parentNodeChildrenKeys[0]];
126
- parentNode.prefix += childNode.prefix;
127
- parentNode.isEndofStr = childNode.isEndofStr;
128
- parentNode.children = childNode.children;
129
- }
130
- }
131
- // 자식 노드가 1개인 중간 노드인 경우 자식 노드와 병합
132
- else if (childrenCount === 1) {
133
- const childNode = node.children[childrenKeys[0]];
134
- node.prefix += childNode.prefix;
135
- node.isEndofStr = childNode.isEndofStr;
136
- node.children = childNode.children;
137
- }
138
- // 그 이외는 별도의 추가 작업 필요 없이 작업 종료
139
- return;
140
- }
141
- }
142
- }
143
- /**
144
- * 현재 Trie 자료구조에 삽입된 모든 문자열을 반환합니다.
145
- */
146
- getAll() {
147
- const result = [];
148
- Trie._dfs(this.root, '', result);
149
- return result;
150
- }
151
- /**
152
- * 현재 문자열로부터 완성될 수 있는 문자열들을 반환합니다.
153
- */
154
- autoComplete(str) {
155
- const result = [];
156
- str = integratedDisassemble(str);
157
- let node = this.root;
158
- let i = 0;
159
- // 현재 문자열이 포함된 prefix를 가진 노드를 찾습니다.
160
- while (i < str.length) {
161
- const key = str[i];
162
- if (!(key in node.children))
163
- return [];
164
- node = node.children[key];
165
- const remainStr = str.slice(i);
166
- const prefixLen = commonPrefixLength(remainStr, node.prefix);
167
- // 현재 노드의 prefix가 공통 prefix인 경우
168
- if (prefixLen === remainStr.length)
169
- break;
170
- // 현재 문자열의 prefix가 공통 prefix인 경우
171
- else if (prefixLen === node.prefix.length)
172
- i += prefixLen;
173
- // 공통 prefix가 현재 노드의 prefix, 현재 문자열의 prefix 중 어느와도 일치하지 않는 경우
174
- else
175
- return [];
176
- }
177
- Trie._dfs(node, str.slice(0, i), result);
178
- return result;
179
- }
180
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,130 +0,0 @@
1
- import { Trie } from "@/core/index.js";
2
- import { commonPrefixLength, integratedAssemble, integratedDisassemble } from "@/utils/index.js";
3
- import { describe, expect, test } from "vitest";
4
- describe('Util functions test', () => {
5
- test('commonPrefixLength', () => {
6
- expect(commonPrefixLength('ㄱㄴ', 'ㄱ')).toBe(1);
7
- expect(commonPrefixLength('ㄱㄴ', 'ㄱ')).toBe(commonPrefixLength('ㄱ', 'ㄱㄴ'));
8
- expect(commonPrefixLength('ㄴㅏ ㅂㅣ', 'ㄴㅏ ㅂㅏㅇ')).toBe(4);
9
- });
10
- test('integratedDisassemble', () => {
11
- expect(integratedDisassemble('ㄱㄴㄷ')).toBe('ㄱㄴㄷ');
12
- expect(integratedDisassemble('가방과 나방')).toBe('ㄱㅏㅂㅏㅇㄱㅗㅏ ㄴㅏㅂㅏㅇ');
13
- expect(integratedDisassemble('abc')).toBe('abc');
14
- expect(integratedDisassemble('나비는 butterfly야')).toBe('ㄴㅏㅂㅣㄴㅡㄴ butterflyㅇㅑ');
15
- });
16
- test('integratedAssemble', () => {
17
- expect(integratedAssemble('ㄱㅏㅁ')).toBe('감');
18
- expect(integratedAssemble('ㄱㅏㅁㅏ')).toBe('가마');
19
- expect(integratedAssemble('abc')).toBe('abc');
20
- expect(integratedAssemble('ㄱㅏa ㅁㅜㄹ b치')).toBe('가a 물 b치');
21
- });
22
- });
23
- describe('Trie test', () => {
24
- test('Insert test', () => {
25
- const trie = new Trie();
26
- const input = [
27
- 'abc',
28
- 'ㄱㄴㄷ',
29
- '개굴개굴 개구리',
30
- '바나나는 banana야',
31
- ];
32
- input.forEach((value) => trie.insert(value));
33
- expect(trie.has('abc')).toBe(true);
34
- expect(trie.has('ㄱㄴㄷ')).toBe(true);
35
- expect(trie.has('개굴개굴 개구리')).toBe(true);
36
- expect(trie.has('바나나는 banana야')).toBe(true);
37
- expect(trie.has('abd')).toBe(false);
38
- expect(trie.has('ㄱㄴㄹ')).toBe(false);
39
- expect(trie.has('개굴개굴개구리')).toBe(false);
40
- expect(trie.size).toBe(4);
41
- });
42
- test('Remove test', () => {
43
- const trie = new Trie();
44
- const input = [
45
- 'ㄱㄴㄷ',
46
- 'ㄱㄴㄷㄹ',
47
- '가나다',
48
- '가나라',
49
- '나방',
50
- '나비',
51
- '나비야',
52
- ];
53
- input.forEach((value) => trie.insert(value));
54
- // leaf 노드 제거하는 경우
55
- trie.remove('ㄱㄴㄷㄹ');
56
- // leaf 노드 제거 후 부모 노드의 자식 노드가 1개인 경우
57
- trie.remove('가나다');
58
- // 자식 노드가 1개인 중간 노드인 경우
59
- trie.remove('나비');
60
- expect(trie.has('ㄱㄴㄷㄹ')).toBe(false);
61
- expect(trie.has('가나다')).toBe(false);
62
- expect(trie.has('나비')).toBe(false);
63
- expect(trie.has('ㄱㄴㄷ')).toBe(true);
64
- expect(trie.has('가나라')).toBe(true);
65
- expect(trie.has('나방')).toBe(true);
66
- expect(trie.has('나비야')).toBe(true);
67
- expect(trie.size).toBe(4);
68
- });
69
- test('GetAll test', () => {
70
- const trie = new Trie();
71
- const input = [
72
- 'abc',
73
- 'ㄱㄴㄷ',
74
- '개굴개굴 개구리',
75
- '나비는 butterfly야',
76
- 'ㄱㄴㄷㄹ',
77
- '가나다',
78
- '가나라',
79
- '나방',
80
- '나비',
81
- '나비야'
82
- ];
83
- input.forEach((value) => trie.insert(value));
84
- expect(trie.getAll().sort()).toStrictEqual(input.sort());
85
- expect(trie.size).toBe(input.length);
86
- });
87
- test('AutoComplete test', () => {
88
- const trie = new Trie();
89
- const input = [
90
- 'apple',
91
- 'apply',
92
- 'application',
93
- 'approach',
94
- '납부',
95
- '나비',
96
- '나방',
97
- '나쁜',
98
- ];
99
- input.forEach((value) => trie.insert(value));
100
- expect(trie.autoComplete('appll')).toStrictEqual([]);
101
- expect(trie.autoComplete('app').sort()).toStrictEqual([
102
- 'apple',
103
- 'apply',
104
- 'application',
105
- 'approach',
106
- ].sort());
107
- expect(trie.autoComplete('appl').sort()).toStrictEqual([
108
- 'apple',
109
- 'apply',
110
- 'application',
111
- ].sort());
112
- expect(trie.autoComplete('난').sort()).toStrictEqual([]);
113
- expect(trie.autoComplete('나').sort()).toStrictEqual([
114
- '납부',
115
- '나비',
116
- '나방',
117
- '나쁜',
118
- ].sort());
119
- expect(trie.autoComplete('납').sort()).toStrictEqual([
120
- '납부',
121
- '나비',
122
- '나방',
123
- ].sort());
124
- expect(trie.autoComplete('나ㅂ').sort()).toStrictEqual([
125
- '납부',
126
- '나비',
127
- '나방',
128
- ].sort());
129
- });
130
- });
@@ -1,13 +0,0 @@
1
- /**
2
- * disassemble 된 두 한글 문자열의 공통 prefix 길이를 반환합니다.
3
- */
4
- export declare function commonPrefixLength(a: string, b: string): number;
5
- /**
6
- * 한글을 초성/중성/종성 단위로 완전히 분해합니다.
7
- * 한글이 아닌 문자가 포함되어 있는 경우에도 분해합니다.
8
- */
9
- export declare function integratedDisassemble(str: string): string;
10
- /**
11
- * 문자열에 포함된 한글 문장과 문자를 한글 규칙에 맞게 합성합니다.
12
- */
13
- export declare function integratedAssemble(str: string): string;
@@ -1,68 +0,0 @@
1
- import { assemble, disassemble } from "es-hangul";
2
- /**
3
- * disassemble 된 두 한글 문자열의 공통 prefix 길이를 반환합니다.
4
- */
5
- export function commonPrefixLength(a, b) {
6
- let len = 0;
7
- while (len < a.length && len < b.length) {
8
- if (a[len] !== b[len])
9
- break;
10
- len += 1;
11
- }
12
- return len;
13
- }
14
- function hasHangul(str) {
15
- return [...str].some((s) => /^[가-힣ㄱ-ㅣ]$/.test(s));
16
- }
17
- function onlyHangulOrSpace(str) {
18
- return [...str].every((s) => /^[가-힣ㄱ-ㅣ ]$/.test(s));
19
- }
20
- /**
21
- * 한글을 초성/중성/종성 단위로 완전히 분해합니다.
22
- * 한글이 아닌 문자가 포함되어 있는 경우에도 분해합니다.
23
- */
24
- export function integratedDisassemble(str) {
25
- const isSomeHangul = hasHangul(str);
26
- const isEveryHangul = onlyHangulOrSpace(str);
27
- // 전부 한글
28
- if (isEveryHangul)
29
- return disassemble(str);
30
- // 일부만 한글
31
- if (isSomeHangul) {
32
- const splitted = str.split(/([가-힣ㄱ-ㅣ]+)/);
33
- return splitted.reduce((acc, cur) => {
34
- if (cur.length === 0)
35
- return acc;
36
- const isHangul = /^[가-힣ㄱ-ㅣ]$/.test(cur[0]);
37
- if (!isHangul)
38
- return acc + cur;
39
- return acc + disassemble(cur);
40
- }, '');
41
- }
42
- // 한글 미포함
43
- return str;
44
- }
45
- /**
46
- * 문자열에 포함된 한글 문장과 문자를 한글 규칙에 맞게 합성합니다.
47
- */
48
- export function integratedAssemble(str) {
49
- const isSomeHangul = hasHangul(str);
50
- const isEveryHangul = onlyHangulOrSpace(str);
51
- // 전부 한글
52
- if (isEveryHangul)
53
- return assemble([str]);
54
- // 일부만 한글
55
- if (isSomeHangul) {
56
- const splitted = str.split(/([가-힣ㄱ-ㅣ]+)/);
57
- return splitted.reduce((acc, cur) => {
58
- if (cur.length === 0)
59
- return acc;
60
- const isHangul = /^[가-힣ㄱ-ㅣ]$/.test(cur[0]);
61
- if (!isHangul)
62
- return acc + cur;
63
- return acc + assemble([...cur]);
64
- }, '');
65
- }
66
- // 한글 미포함
67
- return str;
68
- }