mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
MISC: Make implicit string conversion consistent across all coding contracts (#2608)
This commit is contained in:
@@ -83,6 +83,41 @@ export function removeBracketsFromArrayString(str: string): string {
|
||||
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("'")) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { filterTruthy } from "../../utils/helpers/ArrayHelpers";
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
import { CodingContractTypes, removeBracketsFromArrayString, removeQuotesFromString } from "../ContractTypes";
|
||||
import { CodingContractTypes, parseArrayString } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
|
||||
export const findAllValidMathExpressions: Pick<CodingContractTypes, CodingContractName.FindAllValidMathExpressions> = {
|
||||
@@ -107,10 +106,12 @@ export const findAllValidMathExpressions: Pick<CodingContractTypes, CodingContra
|
||||
return result.every((sol) => solutions.has(sol));
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const sanitized = removeBracketsFromArrayString(ans).split(",");
|
||||
return filterTruthy(sanitized).map((s) => removeQuotesFromString(s.replace(/\s/g, "")));
|
||||
const parsedAnswer = parseArrayString(ans);
|
||||
if (!findAllValidMathExpressions[CodingContractName.FindAllValidMathExpressions].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is string[] =>
|
||||
typeof ans === "object" && Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
validateAnswer: (ans): ans is string[] => Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CodingContractName } from "@enums";
|
||||
import { CodingContractTypes, removeBracketsFromArrayString } from "../ContractTypes";
|
||||
import { CodingContractTypes, parseArrayString } from "../ContractTypes";
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
|
||||
@@ -67,10 +67,12 @@ export const generateIPAddresses: Pick<CodingContractTypes, CodingContractName.G
|
||||
return ret.length === answer.length && ret.every((ip) => answer.includes(ip));
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const sanitized = removeBracketsFromArrayString(ans).replace(/\s/g, "");
|
||||
return sanitized.split(",").map((ip) => ip.replace(/^(?<quote>['"])([\d.]*)\k<quote>$/g, "$2"));
|
||||
const parsedAnswer = parseArrayString(ans);
|
||||
if (!generateIPAddresses[CodingContractName.GenerateIPAddresses].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is string[] =>
|
||||
typeof ans === "object" && Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
validateAnswer: (ans): ans is string[] => Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
import { CodingContractTypes } from "../ContractTypes";
|
||||
import { parseArrayString, CodingContractTypes } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
|
||||
export const largestRectangle: Pick<CodingContractTypes, CodingContractName.LargestRectangleInAMatrix> = {
|
||||
@@ -152,13 +152,7 @@ Answer: [[0,0],[3,1]]
|
||||
return userArea === (solution[1][0] - solution[0][0] + 1) * (solution[1][1] - solution[0][1] + 1);
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
let parsedAnswer: unknown;
|
||||
try {
|
||||
parsedAnswer = JSON.parse(ans);
|
||||
} catch (error) {
|
||||
console.error("Invalid answer:", error);
|
||||
return null;
|
||||
}
|
||||
const parsedAnswer = parseArrayString(ans.replace(/\s/g, ""), true);
|
||||
if (!largestRectangle[CodingContractName.LargestRectangleInAMatrix].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
import { CodingContractTypes, convert2DArrayToString, removeBracketsFromArrayString } from "../ContractTypes";
|
||||
import { CodingContractTypes, convert2DArrayToString, parseArrayString } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
|
||||
export const mergeOverlappingIntervals: Pick<CodingContractTypes, CodingContractName.MergeOverlappingIntervals> = {
|
||||
@@ -70,20 +70,13 @@ export const mergeOverlappingIntervals: Pick<CodingContractTypes, CodingContract
|
||||
return result.length === answer.length && result.every((a, i) => a[0] === answer[i][0] && a[1] === answer[i][1]);
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const arrayRegex = /\[\d+,\d+\]/g;
|
||||
const matches = ans.replace(/\s/g, "").match(arrayRegex);
|
||||
if (matches === null) return null;
|
||||
const arr = matches.map((a) =>
|
||||
removeBracketsFromArrayString(a)
|
||||
.split(",")
|
||||
.map((n) => parseInt(n)),
|
||||
);
|
||||
// An inline function is needed here, so that TS knows this returns true if it matches the type
|
||||
if (((a: number[][]): a is [number, number][] => a.every((n) => n.length === 2))(arr)) return arr;
|
||||
return null;
|
||||
const parsedAnswer = parseArrayString(ans.replace(/\s/g, ""), true);
|
||||
if (!mergeOverlappingIntervals[CodingContractName.MergeOverlappingIntervals].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is [number, number][] =>
|
||||
typeof ans === "object" &&
|
||||
Array.isArray(ans) &&
|
||||
ans.every((a) => Array.isArray(a) && a.length === 2 && a.every((n) => typeof n === "number")),
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CodingContractTypes, removeBracketsFromArrayString } from "../ContractTypes";
|
||||
import { CodingContractTypes, parseArrayString } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
|
||||
export const proper2ColoringOfAGraph: Pick<CodingContractTypes, CodingContractName.Proper2ColoringOfAGraph> = {
|
||||
@@ -121,14 +121,12 @@ export const proper2ColoringOfAGraph: Pick<CodingContractTypes, CodingContractNa
|
||||
return data[1].every(([a, b]) => answer[a] !== answer[b]);
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const sanitized = removeBracketsFromArrayString(ans).replace(/\s/g, "");
|
||||
if (sanitized === "") return [];
|
||||
const arr = sanitized.split(",").map((s) => parseInt(s, 10));
|
||||
// An inline function is needed here, so that TS knows this returns true if it matches the type
|
||||
if (((a): a is (1 | 0)[] => !a.some((n) => n !== 1 && n !== 0))(arr)) return arr;
|
||||
return null;
|
||||
const parsedAnswer = parseArrayString(ans.replace(/\s/g, ""));
|
||||
if (!proper2ColoringOfAGraph[CodingContractName.Proper2ColoringOfAGraph].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is (1 | 0)[] =>
|
||||
typeof ans === "object" && Array.isArray(ans) && !ans.some((a) => a !== 1 && a !== 0),
|
||||
validateAnswer: (ans): ans is (1 | 0)[] => Array.isArray(ans) && !ans.some((a) => a !== 1 && a !== 0),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
import { CodingContractTypes, removeBracketsFromArrayString, removeQuotesFromString } from "../ContractTypes";
|
||||
import { CodingContractTypes, parseArrayString } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
|
||||
export const sanitizeParenthesesInExpression: Pick<
|
||||
@@ -113,10 +113,16 @@ export const sanitizeParenthesesInExpression: Pick<
|
||||
return res.every((sol) => answer.includes(sol));
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const sanitized = removeBracketsFromArrayString(ans).split(",");
|
||||
return sanitized.map((s) => removeQuotesFromString(s.replace(/\s/g, "")));
|
||||
const parsedAnswer = parseArrayString(ans);
|
||||
if (
|
||||
!sanitizeParenthesesInExpression[CodingContractName.SanitizeParenthesesInExpression].validateAnswer(
|
||||
parsedAnswer,
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is string[] =>
|
||||
typeof ans === "object" && Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
validateAnswer: (ans): ans is string[] => Array.isArray(ans) && ans.every((s) => typeof s === "string"),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ export const shortestPathInAGrid: Pick<CodingContractTypes, CodingContractName.S
|
||||
" [[0,1],\n",
|
||||
" [1,0]]\n",
|
||||
"\n",
|
||||
"Answer: ''",
|
||||
`Answer: ""`,
|
||||
].join(" ");
|
||||
},
|
||||
difficulty: 7,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CodingContractName } from "@enums";
|
||||
import { removeBracketsFromArrayString, type CodingContractTypes } from "../ContractTypes";
|
||||
import { parseArrayString, type CodingContractTypes } from "../ContractTypes";
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
|
||||
@@ -127,10 +127,12 @@ export const spiralizeMatrix: Pick<CodingContractTypes, CodingContractName.Spira
|
||||
return spiral.length === answer.length && spiral.every((n, i) => n === answer[i]);
|
||||
},
|
||||
convertAnswer: (ans) => {
|
||||
const sanitized = removeBracketsFromArrayString(ans).replace(/\s/g, "").split(",");
|
||||
return sanitized.map((s) => parseInt(s));
|
||||
const parsedAnswer = parseArrayString(ans);
|
||||
if (!spiralizeMatrix[CodingContractName.SpiralizeMatrix].validateAnswer(parsedAnswer)) {
|
||||
return null;
|
||||
}
|
||||
return parsedAnswer;
|
||||
},
|
||||
validateAnswer: (ans): ans is number[] =>
|
||||
typeof ans === "object" && Array.isArray(ans) && ans.every((n) => typeof n === "number"),
|
||||
validateAnswer: (ans): ans is number[] => Array.isArray(ans) && ans.every((n) => typeof n === "number"),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,24 +40,92 @@ The [`getContractTypes`](../../../../../markdown/bitburner.codingcontract.getcon
|
||||
|
||||
## Submitting Solutions
|
||||
|
||||
### General rules
|
||||
|
||||
Different contract problem types will require different types of solutions.
|
||||
Some may be numbers, others may be strings or arrays.
|
||||
|
||||
If a contract asks for a specific solution format, then use that.
|
||||
Otherwise, follow these rules when submitting solutions:
|
||||
|
||||
- String-type solutions should **not** have quotation marks surrounding the string (unless specifically asked for).
|
||||
- String-type solutions (e.g., Shortest Path in a Grid) should **not** have quotation marks surrounding the string (unless specifically asked for). For example, if your answer is `foo` (3 characters: f, o, o), just submit those 3 characters. Don't submit `"foo"` (5 characters).
|
||||
Only quotation marks that are part of the actual string solution should be included.
|
||||
- With array-of-strings solutions (e.g., Generate IP Addresses), you need to use double quotes surrounding the string values. Don't use single quotes (`''`) or backticks (\`\`). For example, if your answer is an array containing `foo` (3 characters: f, o, o) and `bar` (3 characters: b, a, r), you should submit `["foo", "bar"]`. Don't submit `['foo', 'bar']`.
|
||||
- Array-type solutions should be submitted with each element in the array separated by commas.
|
||||
Brackets are optional.
|
||||
For example, both of the following are valid solution formats:
|
||||
- `1,2,3`
|
||||
- `[1,2,3]`
|
||||
- If the solution is a multidimensional array, then all arrays that are not the outer-most array DO require the brackets.
|
||||
For example, an array of arrays can be submitted as one of the following:
|
||||
- Numeric solutions should be submitted normally, as expected.
|
||||
- Read the description carefully. Some contracts (e.g., the "Square Root" contract) clearly specify the expected solution format.
|
||||
- If the solution format is not a string, you should not convert the answer to a string. Read the next sections carefully if you do so.
|
||||
|
||||
### String conversion
|
||||
|
||||
For convenience (e.g., submitting the answer via the UI) and backward compatibility, the game accepts a string answer even when
|
||||
the solution format is not a string. In these cases, the game converts your string answer to the expected format. However,
|
||||
this conversion has many pitfalls.
|
||||
|
||||
String conversion only matters when you submit the answer via the UI (your answer, typed in the text box, is always a string). When you call the `ns.codingcontract.attempt` API, you should never convert your non-string answer to a string unless specifically asked for.
|
||||
|
||||
First, with arrays, the outermost pair of brackets is optional. For example, both of the following are valid solution formats:
|
||||
|
||||
- `1,2,3`
|
||||
- `[1,2,3]`
|
||||
|
||||
Note:
|
||||
|
||||
- If the solution is a multidimensional array, then all arrays that are not the outermost array DO require the brackets. For example, an array of arrays can be submitted as one of the following:
|
||||
- `[1,2],[3,4]`
|
||||
- `[[1,2],[3,4]]`
|
||||
- The empty string is converted to an empty array.
|
||||
- `"[]"` (the string that contains only 2 bracket characters; the double quotes are not part of that string) is converted to an empty array.
|
||||
|
||||
Numeric solutions should be submitted normally, as expected
|
||||
Second, in the UI:
|
||||
|
||||
- If your answer is an empty string, you must leave the text box empty. Do NOT use `""`, `''`, or \`\`.
|
||||
- If the answer is a non-empty string, type it as is. For example, if your answer is the word `foo`, type `foo` (3 characters: f, o, o). Do NOT add any types of quotes.
|
||||
- If the answer is an array that contains strings, use double quotes for strings. Do NOT use single quotes or backticks. For example, if your answer is an array containing the word `foo`, type `["foo"]` (7 characters: square bracket, double quote, f, o, o, double quote, square bracket). The brackets are optional, as stated above, but we recommend including them.
|
||||
|
||||
### Tips
|
||||
|
||||
If a contract does not expect a string, you should not submit a string. For contracts that do not expect a string
|
||||
solution, your answer should never be a string, so if you submit a string, it means that you converted your non-string
|
||||
answer to a string. This is usually the wrong thing to do.
|
||||
|
||||
Remember, string conversion is for UI convenience and backward compatibility. If you use NS APIs, do not perform any
|
||||
string conversion unless specifically asked for.
|
||||
|
||||
For example, suppose a contract requires the answer to be an array containing strings, and you determine that those
|
||||
strings are `foo` and `bar`. Your code should look like this:
|
||||
|
||||
```js
|
||||
const firstString = "foo";
|
||||
const secondString = "bar";
|
||||
const answer = [firstString, secondString];
|
||||
ns.codingcontract.attempt(answer, "filename.cct");
|
||||
```
|
||||
|
||||
There is no conversion!
|
||||
|
||||
In the "General rules" section above, with array-of-strings solutions, we say `Don't use single quotes or backticks`.
|
||||
However, this code works:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
const firstString = 'foo'; // Single quotes
|
||||
const secondString = 'bar'; // Single quotes
|
||||
const answer = [firstString, secondString];
|
||||
ns.codingcontract.attempt(answer, "filename.cct");
|
||||
```
|
||||
|
||||
Why is that?
|
||||
|
||||
In this code, you submit an array containing 2 strings. In JS, `"foo"` and `'foo'` are the same string. However, if you
|
||||
submit your answer as a string, you need to convert your array to a string, and the string `["foo", "bar"]` is not the
|
||||
same as the string `['foo', 'bar']`.
|
||||
|
||||
Internally, we use `JSON.parse` to convert the string answer, and `['foo', 'bar']` is not a valid string representation
|
||||
of an array. In JSON, a string needs to be enclosed by double quotes. Using single quotes or backticks is not allowed.
|
||||
|
||||
This is another example of why you should not convert your answer to a string when not requested. If you submit your
|
||||
array as it is, you do not need to care about the quote types.
|
||||
|
||||
## Rewards
|
||||
|
||||
|
||||
@@ -582,5 +582,47 @@ export const breakingChanges300: VersionBreakingChange = {
|
||||
"They now use the hostname as provided.",
|
||||
showWarning: false,
|
||||
},
|
||||
{
|
||||
brokenAPIs: [
|
||||
{ name: "ns.codingcontract.attempt" },
|
||||
|
||||
{ name: "FindAllValidMathExpressions" },
|
||||
{ name: "GenerateIPAddresses" },
|
||||
{ name: "LargestRectangleInAMatrix" },
|
||||
{ name: "MergeOverlappingIntervals" },
|
||||
{ name: "Proper2ColoringOfAGraph" },
|
||||
{ name: "SanitizeParenthesesInExpression" },
|
||||
{ name: "SpiralizeMatrix" },
|
||||
|
||||
{ name: "Find All Valid Math Expressions" },
|
||||
{ name: "Generate IP Addresses" },
|
||||
{ name: "Largest Rectangle in a Matrix" },
|
||||
{ name: "Merge Overlapping Intervals" },
|
||||
{ name: "Proper 2-Coloring of a Graph" },
|
||||
{ name: "Sanitize Parentheses in Expression" },
|
||||
{ name: "Spiralize Matrix" },
|
||||
],
|
||||
info:
|
||||
"If you pass a string to ns.codingcontract.attempt() for contracts that require a non-string answer, the\n" +
|
||||
"game will convert that string to the expected format. This string conversion was inconsistent and had many\n" +
|
||||
`undocumented behaviors. Now the rules are consistent and well-documented. Please check the "Coding Contracts"\n` +
|
||||
"page for more information.\n" +
|
||||
"There are 7 contracts that are affected by this change:\n" +
|
||||
"- Find All Valid Math Expressions\n" +
|
||||
"- Generate IP Addresses\n" +
|
||||
"- Largest Rectangle in a Matrix\n" +
|
||||
"- Merge Overlapping Intervals\n" +
|
||||
"- Proper 2-Coloring of a Graph\n" +
|
||||
"- Sanitize Parentheses in Expression\n" +
|
||||
"- Spiralize Matrix\n" +
|
||||
"Note that this change only affects the string conversion. The solution format of these contracts is an array,\n" +
|
||||
"so if you pass the solution, which is an array, as is, you will not have any problems.\n" +
|
||||
`If your code converts the array to a string, you should check these contracts, especially the "Sanitize\n` +
|
||||
`Parentheses in Expression" contract and others that require a string array.\n` +
|
||||
`- Sanitize Parentheses in Expression: Previously, if you passed an empty string to this contract, it was\n` +
|
||||
"converted to an array containing an empty string. Now, it's converted to an empty array.\n" +
|
||||
`- Read the "General rules", "String conversion", and "Tips" sections on the "Coding Contracts" page carefully.`,
|
||||
showWarning: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -629,6 +629,8 @@ Error: ${e}`,
|
||||
person.persistentIntelligenceData.exp = person.exp.intelligence;
|
||||
person.overrideIntelligence();
|
||||
}
|
||||
}
|
||||
if (ver < 48) {
|
||||
showAPIBreaks("3.0.0", breakingChanges300);
|
||||
}
|
||||
}
|
||||
|
||||
173
test/jest/CodingContract/AnswerConversion.test.ts
Normal file
173
test/jest/CodingContract/AnswerConversion.test.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { CodingContractTypes, parseArrayString } from "../../../src/CodingContract/ContractTypes";
|
||||
import { CodingContractName } from "../../../src/Enums";
|
||||
import { getRecordEntries } from "../../../src/Types/Record";
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, "error").mockImplementation(jest.fn());
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("Utility functions", () => {
|
||||
test("parseArrayString", () => {
|
||||
expect(parseArrayString("")).toStrictEqual([]);
|
||||
expect(parseArrayString("[]")).toStrictEqual([]);
|
||||
|
||||
expect(parseArrayString(`""`)).toStrictEqual([""]);
|
||||
expect(parseArrayString(`[""]`)).toStrictEqual([""]);
|
||||
expect(parseArrayString(`"foo"`)).toStrictEqual(["foo"]);
|
||||
expect(parseArrayString(`["foo"]`)).toStrictEqual(["foo"]);
|
||||
|
||||
expect(parseArrayString("0")).toStrictEqual([0]);
|
||||
expect(parseArrayString("0,1")).toStrictEqual([0, 1]);
|
||||
expect(parseArrayString("[0,1]")).toStrictEqual([0, 1]);
|
||||
|
||||
expect(parseArrayString(`[[]]`, true)).toStrictEqual([[]]);
|
||||
expect(parseArrayString(`[[0]]`, true)).toStrictEqual([[0]]);
|
||||
expect(parseArrayString(`[[0,1],[2,3]]`, true)).toStrictEqual([
|
||||
[0, 1],
|
||||
[2, 3],
|
||||
]);
|
||||
|
||||
// Incorrectly wrapped as documented
|
||||
expect(parseArrayString(`[ [0]]`, true)).toStrictEqual([[[0]]]);
|
||||
// Preprocessing redundant whitespace
|
||||
expect(parseArrayString(`[ [0]]`.replace(/\s/g, ""), true)).toStrictEqual([[0]]);
|
||||
// Return as-is as documented
|
||||
expect(parseArrayString(`[[1,2],3]`, true)).toStrictEqual([[1, 2], 3]);
|
||||
|
||||
expect(parseArrayString("[")).toStrictEqual(null);
|
||||
expect(parseArrayString("]")).toStrictEqual(null);
|
||||
expect(parseArrayString("foo")).toStrictEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Array", () => {
|
||||
for (const [name, cct] of getRecordEntries(CodingContractTypes)) {
|
||||
if (
|
||||
![
|
||||
CodingContractName.FindAllValidMathExpressions,
|
||||
CodingContractName.GenerateIPAddresses,
|
||||
CodingContractName.MergeOverlappingIntervals,
|
||||
CodingContractName.Proper2ColoringOfAGraph,
|
||||
CodingContractName.SanitizeParenthesesInExpression,
|
||||
CodingContractName.SpiralizeMatrix,
|
||||
CodingContractName.LargestRectangleInAMatrix,
|
||||
].includes(name)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
test(name, () => {
|
||||
expect(cct.convertAnswer("[")).toStrictEqual(null);
|
||||
expect(cct.convertAnswer("]")).toStrictEqual(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("String array", () => {
|
||||
for (const [name, cct] of getRecordEntries(CodingContractTypes)) {
|
||||
if (
|
||||
![
|
||||
CodingContractName.FindAllValidMathExpressions,
|
||||
CodingContractName.GenerateIPAddresses,
|
||||
CodingContractName.SanitizeParenthesesInExpression,
|
||||
].includes(name)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
test(name, () => {
|
||||
expect(cct.convertAnswer("")).toStrictEqual([]);
|
||||
expect(cct.convertAnswer("[]")).toStrictEqual([]);
|
||||
|
||||
expect(cct.convertAnswer(`""`)).toStrictEqual([""]);
|
||||
expect(cct.convertAnswer(`[""]`)).toStrictEqual([""]);
|
||||
expect(cct.convertAnswer(`"",""`)).toStrictEqual(["", ""]);
|
||||
expect(cct.convertAnswer(`["",""]`)).toStrictEqual(["", ""]);
|
||||
expect(cct.convertAnswer(`"foo"`)).toStrictEqual(["foo"]);
|
||||
expect(cct.convertAnswer(`"foo","bar"`)).toStrictEqual(["foo", "bar"]);
|
||||
expect(cct.convertAnswer(` "foo", "bar" `)).toStrictEqual(["foo", "bar"]);
|
||||
|
||||
expect(cct.convertAnswer(`"foo`)).toStrictEqual(null);
|
||||
expect(cct.convertAnswer(`foo"`)).toStrictEqual(null);
|
||||
expect(cct.convertAnswer(`'foo'`)).toStrictEqual(null);
|
||||
expect(cct.convertAnswer(`\`foo\``)).toStrictEqual(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("Number array", () => {
|
||||
for (const [name, cct] of getRecordEntries(CodingContractTypes)) {
|
||||
if (![CodingContractName.Proper2ColoringOfAGraph, CodingContractName.SpiralizeMatrix].includes(name)) {
|
||||
continue;
|
||||
}
|
||||
test(name, () => {
|
||||
expect(cct.convertAnswer("")).toStrictEqual([]);
|
||||
expect(cct.convertAnswer("[]")).toStrictEqual([]);
|
||||
|
||||
expect(cct.convertAnswer("0")).toStrictEqual([0]);
|
||||
expect(cct.convertAnswer("[0]")).toStrictEqual([0]);
|
||||
expect(cct.convertAnswer("0,1")).toStrictEqual([0, 1]);
|
||||
expect(cct.convertAnswer("[0,1]")).toStrictEqual([0, 1]);
|
||||
expect(cct.convertAnswer("[ 0, 1]")).toStrictEqual([0, 1]);
|
||||
|
||||
// Common issues with parseInt in old implementations of convertAnswer
|
||||
expect(cct.convertAnswer("123abc")).toStrictEqual(null);
|
||||
expect(cct.convertAnswer(`"0"`)).toStrictEqual(null);
|
||||
expect(cct.convertAnswer("null")).toStrictEqual(null);
|
||||
expect(cct.convertAnswer("undefined")).toStrictEqual(null);
|
||||
if (name !== CodingContractName.Proper2ColoringOfAGraph) {
|
||||
expect(cct.convertAnswer("12.34")).toStrictEqual([12.34]);
|
||||
expect(cct.convertAnswer("1e3")).toStrictEqual([1000]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("Array of arrays", () => {
|
||||
test(CodingContractName.MergeOverlappingIntervals, () => {
|
||||
const cct = CodingContractTypes[CodingContractName.MergeOverlappingIntervals];
|
||||
// "" => []. With the current generate() and getAnswer(), both data and answer cannot be empty arrays, but in
|
||||
// theory, if the input is an empty array, the output is also an empty array. The fact that the input cannot be an
|
||||
// empty array is only an implementation detail of the internal functions, so an empty array is still a potentially
|
||||
// correct answer.
|
||||
expect(cct.convertAnswer("")).toStrictEqual([]);
|
||||
// "[]" => []
|
||||
expect(cct.convertAnswer("[]")).toStrictEqual([]);
|
||||
|
||||
expect(cct.convertAnswer("[0,0]")).toStrictEqual([[0, 0]]);
|
||||
expect(cct.convertAnswer("[0, 0]")).toStrictEqual([[0, 0]]);
|
||||
expect(cct.convertAnswer("[[0, 0]]")).toStrictEqual([[0, 0]]);
|
||||
expect(cct.convertAnswer("[ [0, 0]]")).toStrictEqual([[0, 0]]);
|
||||
expect(cct.convertAnswer("[1, 2], [3, 4]")).toStrictEqual([
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
]);
|
||||
expect(cct.convertAnswer("[[1, 2], [3, 4]]")).toStrictEqual([
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
]);
|
||||
expect(cct.convertAnswer("[[]]")).toStrictEqual(null);
|
||||
});
|
||||
test(CodingContractName.LargestRectangleInAMatrix, () => {
|
||||
const cct = CodingContractTypes[CodingContractName.LargestRectangleInAMatrix];
|
||||
expect(cct.convertAnswer("[0,0],[0,1]")).toStrictEqual([
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
]);
|
||||
expect(cct.convertAnswer("[[0,0],[0,1]]")).toStrictEqual([
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
]);
|
||||
expect(cct.convertAnswer("[ [0,0], [0,1]]")).toStrictEqual([
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
]);
|
||||
// "" => [], and an empty array is invalid.
|
||||
expect(cct.convertAnswer("")).toStrictEqual(null);
|
||||
// "[]" => [], and an empty array is invalid.
|
||||
expect(cct.convertAnswer("[]")).toStrictEqual(null);
|
||||
expect(cct.convertAnswer("[0,0]")).toStrictEqual(null);
|
||||
expect(cct.convertAnswer("[[]]")).toStrictEqual(null);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user