import { InsightResult } from "../controller/IInsightFacade"; import { APPLYRULE, FILTER, Key, LOGICCOMPARISON, MCOMPARISON, Mfield, NEGATION, ORDER, SCOMPARISON, SORT, Sfield, TRANSFORMATIONS, } from "./QueryTree"; import Decimal from "decimal.js"; export default class QueryCollector { constructor() {} public collectFILTER(filter: FILTER, section: InsightResult): boolean { const filterKey = Object.keys(filter)[0]; switch (filterKey) { case "AND": return this.collectAND(filter as LOGICCOMPARISON, section); case "OR": return this.collectOR(filter as LOGICCOMPARISON, section); case "LT": return this.collectLT(filter as MCOMPARISON, section); case "GT": return this.collectGT(filter as MCOMPARISON, section); case "EQ": return this.collectEQ(filter as MCOMPARISON, section); case "IS": return this.collectIS(filter as SCOMPARISON, section); case "NOT": return this.collectNOT(filter as NEGATION, section); default: return false; //can be triggered by unit tests } } public collectAND(filter: LOGICCOMPARISON, section: InsightResult): boolean { const AND = filter.AND; return AND.every((filterAND) => this.collectFILTER(filterAND, section)); } public collectOR(filter: LOGICCOMPARISON, section: InsightResult): boolean { const OR = filter.OR; return OR.some((filterOR) => this.collectFILTER(filterOR, section)); } public collectLT(filter: MCOMPARISON, section: InsightResult): boolean { const LT = filter.LT; const values = this.getValues(LT, section); return values[0] < values[1]; } public collectGT(filter: MCOMPARISON, section: InsightResult): boolean { const GT = filter.GT; const values = this.getValues(GT, section); return values[0] > values[1]; } public collectEQ(filter: MCOMPARISON, section: InsightResult): boolean { const EQ = filter.EQ; const values = this.getValues(EQ, section); return values[0] === values[1]; } public collectIS(filter: SCOMPARISON, section: InsightResult): boolean { const IS = filter.IS; const values = this.getValues(IS, section); return this.collectWildcards(values[0] as string, values[1] as string, section); } public collectNOT(filter: NEGATION, section: InsightResult): boolean { const NOT = filter.NOT; return !this.collectFILTER(NOT, section); } public collectWildcards(value: string, inputstring: string, section: InsightResult): boolean { if (inputstring === "") { value === ""; } if (inputstring === "*" || inputstring === "**") { return true; } if (inputstring.startsWith("*")) { if (inputstring.endsWith("*")) { //*inputstring*, Contains inputstring return value.includes(inputstring.substring(1, inputstring.length - 1)); } else { //*inputstring, Ends with inputstring return value.endsWith(inputstring.substring(1)); } } else if (inputstring.endsWith("*")) { //inputstring*, Starts with inputstring return value.startsWith(inputstring.substring(0, inputstring.length - 1)); } else { //inputstring, Matches inputstring exactly return value === inputstring; } } public getValues(filter: any, section: InsightResult): (string | number)[] { const mkey = Object.keys(filter)[0]; const field = this.getField(mkey as Key); const fieldValue = section[field] as string | number; const filterValue = Object.values(filter)[0] as string | number; return [fieldValue, filterValue]; } public getField(key: Key): Mfield | Sfield { const index = key.indexOf("_"); return key.substring(index + 1) as Mfield | Sfield; } public collectCOLUMNS(columns: Key[], section: InsightResult): InsightResult { const result: InsightResult = {}; columns.forEach((column) => { result[column] = section[this.getField(column)]; }); return result; } public collectORDER(order: ORDER, sections: InsightResult[]): InsightResult[] { if (typeof order === "object") { return sections.sort((first, second) => this.collectSORT(order, first, second)); } else { return sections.sort((first, second) => (first[order] < second[order] ? -1 : 1)); } } public collectSORT(order: SORT, first: InsightResult, second: InsightResult): number { for (const key of order.keys) { if (first[key] === second[key]) { continue; } return order.dir === "UP" ? (first[key] < second[key] ? -1 : 1) : first[key] < second[key] ? 1 : -1; } return 0; } public collectTRANSFORMATIONS(transformations: TRANSFORMATIONS, results: InsightResult[]): InsightResult[] { const groups = this.collectGROUP(transformations.GROUP, results); return this.collectAPPLY(groups, transformations.GROUP, transformations.APPLY); } public collectGROUP(group: Key[], results: InsightResult[]): InsightResult[][] { const groupMap: Record = {}; results.forEach((result) => { const groupsKey = this.createGroupsKey(group, result); if (!groupMap[groupsKey]) { groupMap[groupsKey] = [result]; } else { groupMap[groupsKey].push(result); } }); return Object.values(groupMap); } public createGroupsKey(group: Key[], result: InsightResult): string { let firstKey = String(result[this.getField(group[0])]); for (let i = 1; i < group.length; i++) { firstKey += "_" + String(result[this.getField(group[i])]); } return firstKey; } public collectAPPLY(groups: InsightResult[][], group: Key[], apply: APPLYRULE[]): InsightResult[] { return groups.map((results: InsightResult[]) => { const newResult: InsightResult = this.collectAPPLYRULE(results, apply); const result = results[0]; group.forEach((fieldKey) => { const field = this.getField(fieldKey); newResult[field] = result[field]; }); return newResult; }); } public collectAPPLYRULE(group: InsightResult[], apply: APPLYRULE[]): InsightResult { const newResult: InsightResult = {}; apply.forEach((applyrule) => { const applykey = Object.keys(applyrule)[0]; const applyobject = Object.values(applyrule)[0]; newResult[applykey] = this.collectAPPLYTOKEN(group, applyobject); }); return newResult; } public collectAPPLYTOKEN(group: InsightResult[], applyobject: any): string | number { const applytoken = Object.keys(applyobject)[0]; const applyvalue = Object.values(applyobject)[0]; switch (applytoken) { case "MAX": return this.collectMAX(group, applyvalue as Key); case "MIN": return this.collectMIN(group, applyvalue as Key); case "AVG": return this.collectAVG(group, applyvalue as Key); case "COUNT": return this.collectCOUNT(group, applyvalue as Key); case "SUM": return this.collectSUM(group, applyvalue as Key); default: return 0; } } public collectMAX(group: InsightResult[], applyvalue: Key): number { const field = this.getField(applyvalue); let max = group[0][field] as number; group.forEach((result) => { const value = result[field] as number; max = max > value ? max : value; //max = Math.max(max, value); }); return max; } public collectMIN(group: InsightResult[], applyvalue: Key): number { const field = this.getField(applyvalue); let min = group[0][field] as number; group.forEach((result) => { const value = result[field] as number; min = Math.min(min, value); }); return min; } public collectAVG(group: InsightResult[], applyvalue: Key): number { const field = this.getField(applyvalue); let total = new Decimal(0); group.forEach((result) => { const value = new Decimal(result[field] as number); total = total.add(value); }); const avg = total.toNumber() / group.length; return Number(avg.toFixed(2)); } public collectCOUNT(group: InsightResult[], applyvalue: Key): number { const field = this.getField(applyvalue); const set = new Set(); group.forEach((result) => { const value = result[field]; set.add(value); }); return set.size; } public collectSUM(group: InsightResult[], applyvalue: Key): number { const field = this.getField(applyvalue); let sum = 0; group.forEach((result) => { const value = result[field] as number; sum += value; }); return Number(sum.toFixed(2)); } }