import { CodingContractName } from "@enums"; import { CodingContractSignatures } from "@nsdefs"; import { algorithmicStockTrader } from "./contracts/AlgorithmicStockTrader"; import { arrayJumpingGame } from "./contracts/ArrayJumpingGame"; import { compression } from "./contracts/Compression"; import { encryption } from "./contracts/Encryption"; import { findAllValidMathExpressions } from "./contracts/FindAllValidMathExpressions"; import { findLargestPrimeFactor } from "./contracts/FindLargestPrimeFactor"; import { generateIPAddresses } from "./contracts/GenerateIPAddresses"; import { hammingCode } from "./contracts/HammingCode"; import { mergeOverlappingIntervals } from "./contracts/MergeOverlappingIntervals"; import { minimumPathSumInATriangle } from "./contracts/MinimumPathSumInATriangle"; import { proper2ColoringOfAGraph } from "./contracts/Proper2ColoringOfAGraph"; import { sanitizeParenthesesInExpression } from "./contracts/SanitizeParenthesesInExpression"; import { shortestPathInAGrid } from "./contracts/ShortestPathInAGrid"; import { spiralizeMatrix } from "./contracts/SpiralizeMatrix"; import { squareRoot } from "./contracts/SquareRoot"; import { subarrayWithMaximumSum } from "./contracts/SubarrayWithMaximumSum"; import { totalPrimesInRange } from "./contracts/TotalPrimesInRange"; import { totalWaysToSum } from "./contracts/TotalWaysToSum"; import { uniquePathsInAGrid } from "./contracts/UniquePathsInAGrid"; import { largestRectangle } from "./contracts/LargestRectangle"; // This is the base interface, but should not be used for // typechecking individual entries. Use the two types below for that. interface CodingContractType { /** * Function that returns a string with the problem's description. * Requires the 'data' of a Contract as input */ desc: (data: Data) => string; /** Difficulty of the contract. Higher is harder. */ difficulty: number; /** Function that generates a valid 'state' for a contract type */ generate: () => State; /** Function that returns an answer, if possible, for a given contract */ getAnswer: (data: Data) => Answer | null; /** * Transforms the 'state' for a contract into its 'data'. The state is * stored persistently as JSON, so it must be serializable. The data is what * is given to the user and shown in the description. If this function is * omitted, it will be the identity function (i.e. State == Data). * You can use this to make problems where the "solver" is not a function * that can be copy-pasted to user code to solve the problem. */ getData?: (state: State) => Data; /** How many tries you get. Defaults to 10. */ numTries?: number; /** Function that checks whether the players answer is correct. */ solver: (state: State, answer: Answer) => boolean; /** Function that converts string answers to the expected answer format. */ convertAnswer: (answer: string) => Answer | null; /** Function that validates the format of the provided answer. */ validateAnswer: (answer: unknown) => answer is Answer; } // This simple alias uses State == Data, and omits getData since it won't be used in this case. type CodingContractSimpleType = Omit, "getData">; // This alias has unique State and Data, and requires getData. type CodingContractComplexType = Omit, "getData"> & { getData: (state: State) => Data; }; type CodingContractDefinitions> = { [T in keyof Signatures]: Signatures[T] extends [unknown, unknown, unknown] ? CodingContractComplexType : CodingContractSimpleType; }; export type CodingContractTypes = CodingContractDefinitions; /* Helper functions for Coding Contract implementations */ export function removeBracketsFromArrayString(str: string): string { let strCpy: string = str; if (strCpy.startsWith("[")) { strCpy = strCpy.slice(1); } if (strCpy.endsWith("]")) { strCpy = strCpy.slice(0, -1); } return strCpy; } /** * This function only performs very simple checks to add the outermost optional brackets. Callers must perform other * preprocessing steps and postprocessing validations. For example: * - "[ [0, 1]]" will be incorrectly wrapped and converted to "[[[0,1]]]". Callers need to remove redundant whitespace. * - "[[1,2],3]" is not an array of arrays, but this function will return it as is. Callers need to call validateAnswer. * * Note: * - "" will always be converted to an empty array ([]). * - When parsing an array of arrays (isArrayOfArray = true), "[]" will be converted to an empty array ([]), not an * array containing an empty array ([[]]). */ export function parseArrayString(answer: string, isArrayOfArray = false): unknown { let modifiedAnswer = answer.trim(); if (isArrayOfArray && modifiedAnswer === "[]") { return []; } // If it doesn't start with any bracket, it's definitely "naked". if (!modifiedAnswer.startsWith("[")) { modifiedAnswer = `[${modifiedAnswer}]`; } else if (isArrayOfArray && !modifiedAnswer.startsWith("[[")) { // If it's supposed to be an array of arrays but only has one "[". modifiedAnswer = `[${modifiedAnswer}]`; } try { return JSON.parse(modifiedAnswer); } catch (error) { console.error(`Invalid answer: ${answer}`); console.error(error); return null; } } export function removeQuotesFromString(str: string): string { let strCpy: string = str; if (strCpy.startsWith('"') || strCpy.startsWith("'")) { strCpy = strCpy.slice(1); } if (strCpy.endsWith('"') || strCpy.endsWith("'")) { strCpy = strCpy.slice(0, -1); } return strCpy; } export function convert2DArrayToString(arr: number[][]): string { const components: string[] = []; for (const e of arr) { let s = String(e); s = ["[", s, "]"].join(""); components.push(s); } return components.join(",").replace(/\s/g, ""); } export const CodingContractDefinitions: CodingContractTypes = { ...algorithmicStockTrader, ...arrayJumpingGame, ...compression, ...encryption, ...findAllValidMathExpressions, ...findLargestPrimeFactor, ...generateIPAddresses, ...hammingCode, ...largestRectangle, ...mergeOverlappingIntervals, ...minimumPathSumInATriangle, ...proper2ColoringOfAGraph, ...totalPrimesInRange, ...sanitizeParenthesesInExpression, ...shortestPathInAGrid, ...spiralizeMatrix, ...squareRoot, ...subarrayWithMaximumSum, ...totalWaysToSum, ...uniquePathsInAGrid, }; // This untyped variant is easier to work with when the specific type is not known. // The specific shape is already checked by the CodingContractDefinitions type, so it is safe to assert the type. export const CodingContractTypes = CodingContractDefinitions as Record< CodingContractName, CodingContractType >;