

재영
결국 핵심은 조합 문제.
계속해서 N개를 뽑아내면서 조합이 유효한지를 판별한다. 이는 최소성을 만족한다.
이때, 기존에 있던 키들에 대해서 겹치는 것이 있는지를 판별한다. 이는 유일성을 만족한다.
결과적으로 이 2가지를 만족하면 키를 등록한다.
모든 반복문을 돌린 후 결과를 반한환다.
const comb = (arr, count) => { if (count === 1) return arr.map((value) => [value]); const combinations = []; arr.forEach((value, index) => { const nextArr = arr.filter((_, nextIndex) => index < nextIndex); const result = comb(nextArr, count - 1).map((res) => [value, ...res]); combinations.push(...result); }); return combinations; }; const zip = (...args) => { const res = Array.from({ length: args[0].length }, () => []); args.forEach((row) => { row.forEach((col, colIndex) => { res[colIndex].push(col); }); }); return res; }; class AlphabetKeyIndexStrategy { constructor(indexCount, caseType = 'uppercase') { if (indexCount > 26) { throw new Error('알파벳 수보다 많습니다.'); } this._indexCount = indexCount; this._caseType = caseType; } run() { const indices = Array.from({ length: this._indexCount }, (_, i) => String.fromCharCode(65 + i) ); return this._caseType === 'uppercase' ? indices : indices.map((v) => v.toLowerCase()); } } class KeyGenerator { constructor(num, Strategy = AlphabetKeyIndexStrategy) { this.Strategy = new Strategy(num); } generate() { return this.Strategy.run(); } } class Table { constructor(rawData, ColumnKeyGenerator = KeyGenerator) { this._data = rawData; this._ColumnKeyGenerator = new ColumnKeyGenerator(this.colLength); } get rowLength() { return this._data.length; } get colLength() { return this._data?.[0]?.length ?? 0; } get columnKeys() { return this._ColumnKeyGenerator.generate(); } get columns() { const dataZip = zip(...this.data); return this.columnKeys.reduce( (acc, cur, index) => ({ ...acc, [cur]: dataZip[index], }), {} ); } get data() { return this._data; } } const hasSubset = (setArr, target) => { return setArr.every( (cmpArr) => !cmpArr.every((value) => target.includes(value)) ); }; const checkIsCandidateKey = (columns, combination, rowLength) => { const res = Array.from({ length: rowLength }, () => []); for (let i = 0; i < rowLength; i += 1) { combination.forEach((key) => { res[i].push(columns[key][i]); }); } return ( new Set(res.map((v) => v.join('👋🏻🖐🏻👋🏻세상을바꾸는뱅크몰👋🏻🖐🏻👋🏻'))).size === rowLength ); }; const getAllCandidateKeys = (table) => { const result = new Set(); for (let keyCount = 1; keyCount <= table.colLength; keyCount += 1) { const combinations = comb(table.columnKeys, keyCount); combinations.forEach((combination) => { const resultArr = [...result]; if ( hasSubset(resultArr, combination) && checkIsCandidateKey(table.columns, combination, table.rowLength) ) { result.add(combination); } }); } return [...result]; }; const solution = (relations) => { const table = new Table(relations); return getAllCandidateKeys(table).length; };