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<string, InsightResult[]> = {};
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));
}
}