practicode
Advanced tools
| { | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "$id": "https://practicode.local/schemas/course.schema.json", | ||
| "title": "Practicode Executable Course", | ||
| "type": "object", | ||
| "additionalProperties": false, | ||
| "required": ["schema_version", "runtime", "lessons"], | ||
| "properties": { | ||
| "schema_version": { | ||
| "const": 1 | ||
| }, | ||
| "runtime": { | ||
| "enum": ["python", "ts", "java", "rust"] | ||
| }, | ||
| "lessons": { | ||
| "type": "array", | ||
| "minItems": 1, | ||
| "items": { | ||
| "$ref": "#/$defs/lesson" | ||
| } | ||
| } | ||
| }, | ||
| "$defs": { | ||
| "lesson": { | ||
| "type": "object", | ||
| "additionalProperties": false, | ||
| "required": [ | ||
| "id", | ||
| "aliases", | ||
| "track", | ||
| "kind", | ||
| "level", | ||
| "title", | ||
| "body", | ||
| "example", | ||
| "starter", | ||
| "cases", | ||
| "refs" | ||
| ], | ||
| "properties": { | ||
| "id": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "aliases": { | ||
| "type": "array", | ||
| "items": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "uniqueItems": true | ||
| }, | ||
| "track": { | ||
| "enum": ["core", "lab"] | ||
| }, | ||
| "kind": { | ||
| "enum": ["lesson", "checkpoint", "capstone"] | ||
| }, | ||
| "level": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "title": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "body": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "example": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "starter": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "cases": { | ||
| "type": "array", | ||
| "minItems": 1, | ||
| "items": { | ||
| "$ref": "#/$defs/case" | ||
| } | ||
| }, | ||
| "refs": { | ||
| "type": "array", | ||
| "minItems": 1, | ||
| "items": { | ||
| "type": "string", | ||
| "format": "uri", | ||
| "pattern": "^https://" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "case": { | ||
| "type": "object", | ||
| "additionalProperties": false, | ||
| "required": ["input", "output"], | ||
| "properties": { | ||
| "input": { | ||
| "type": "string" | ||
| }, | ||
| "output": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| { | ||
| "schema_version": 1, | ||
| "runtime": "java", | ||
| "lessons": [ | ||
| { | ||
| "id": "java-output", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Exact standard output", | ||
| "body": "Build one line from stdin data and choose print or println deliberately; the judge normalizes CRLF to LF, while every other whitespace difference remains significant.", | ||
| "example": "class Solution {\n public static void main(String[] args) {\n String learner = \"Mina\";\n int points = 4;\n System.out.print(learner + \":\" + points + \"\\n\");\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n String[] parts = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\");\n String name = parts[0];\n int score = Integer.parseInt(parts[1]);\n // TODO: format the two parsed values with the required separator.\n System.out.println(name + \"-\" + score);\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "Ada 7\n", "output": "Ada:7\n" }, | ||
| { "input": "Lin 0\n", "output": "Lin:0\n" }, | ||
| { "input": "José 12\n", "output": "José:12\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/PrintStream.html#println(java.lang.String)", | ||
| "https://dev.java/learn/" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-input", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "UTF-8 standard input", | ||
| "body": "Read every stdin byte, decode with UTF-8 explicitly, split only when text is nonblank, and total any number of signed integer tokens.", | ||
| "example": "import java.io.ByteArrayInputStream;\nimport java.nio.charset.StandardCharsets;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n var demo = new ByteArrayInputStream(\"4 6\".getBytes(StandardCharsets.UTF_8));\n String text = new String(demo.readAllBytes(), StandardCharsets.UTF_8);\n int sum = 0;\n for (String token : text.split(\"\\\\s+\")) sum += Integer.parseInt(token);\n System.out.println(\"sum=\" + sum);\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n String text = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip();\n int sum = 1;\n // TODO: accumulate the decoded integer tokens, including the blank-input case.\n if (!text.isEmpty()) for (String token : text.split(\"\\\\s+\")) sum += Integer.parseInt(token);\n System.out.println(sum);\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "2 3\n", "output": "5\n" }, | ||
| { "input": "-1\n5 2\n", "output": "6\n" }, | ||
| { "input": "", "output": "0\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/charset/StandardCharsets.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/InputStream.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-variables-types", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Typed values and final bindings", | ||
| "body": "Parse a String and two ints, derive a boolean with a typed comparison, and remember that final blocks reassignment rather than mutation inside an object.", | ||
| "example": "class Solution {\n public static void main(String[] args) {\n final String name = \"Mira\";\n int score = 2;\n int threshold = 3;\n boolean passed = score >= threshold;\n System.out.println(\"report=\" + name + \":\" + score + \":\" + passed);\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n String[] parts = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\");\n final String name = parts[0];\n int score = Integer.parseInt(parts[1]);\n int threshold = Integer.parseInt(parts[2]);\n // TODO: include the threshold itself in the passing range.\n boolean passed = score > threshold;\n System.out.println(name + \":\" + score + \":\" + passed);\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "Ada 7 5\n", "output": "Ada:7:true\n" }, | ||
| { "input": "Lin 4 5\n", "output": "Lin:4:false\n" }, | ||
| { "input": "Bo 0 0\n", "output": "Bo:0:true\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-numbers-operators", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Truncating integer arithmetic", | ||
| "body": "Compute quotient and remainder with Java int operators; division truncates toward zero, and the remainder keeps the dividend's sign.", | ||
| "example": "class Solution {\n public static void main(String[] args) {\n int dividend = 19;\n int divisor = 4;\n System.out.println(\"quotient=\" + dividend / divisor + \",remainder=\" + dividend % divisor);\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n String[] parts = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\");\n int dividend = Integer.parseInt(parts[0]);\n int divisor = Integer.parseInt(parts[1]);\n // TODO: preserve Java's integer quotient and remainder rules.\n System.out.println(Math.floorDiv(dividend, divisor) + \":\" + Math.floorMod(dividend, divisor));\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "17 5\n", "output": "3:2\n" }, | ||
| { "input": "-17 5\n", "output": "-3:-2\n" }, | ||
| { "input": "17 -5\n", "output": "-3:2\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.17", | ||
| "https://dev.java/learn/numbers-strings/numbers/" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-strings", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "String cleanup and content equality", | ||
| "body": "Strip surrounding Unicode whitespace, remove a matching ASCII bracket pair, and treat String length and indices as UTF-16 code units.", | ||
| "example": "class Solution {\n public static void main(String[] args) {\n String raw = \" {demo} \";\n String text = raw.strip();\n if (text.startsWith(\"{\") && text.endsWith(\"}\")) text = text.substring(1, text.length() - 1);\n System.out.println(\"cleaned=\" + text);\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n String text = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip();\n // TODO: remove one matching square or curly wrapper from the stripped content.\n System.out.println(text);\n }\n}\n", | ||
| "cases": [ | ||
| { "input": " [ok] \n", "output": "ok\n" }, | ||
| { "input": "{Java}\n", "output": "Java\n" }, | ||
| { "input": "[서울]\n", "output": "서울\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/String.html", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-3.html#jls-3.10.5" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-control-flow", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Branches and bounded loops", | ||
| "body": "Walk from one through an inclusive limit and add only odd values, using a boolean condition because Java integers are not truth values.", | ||
| "example": "class Solution {\n public static void main(String[] args) {\n int total = 0;\n for (int value = 1; value <= 7; value++) {\n if (value % 2 != 0) total += value;\n }\n System.out.println(\"oddSum=\" + total);\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n int limit = Integer.parseInt(new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip());\n int total = 0;\n // TODO: make the loop accumulate the intended parity.\n for (int value = 1; value <= limit; value++) if (value % 2 == 0) total += value;\n System.out.println(total);\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "3\n", "output": "4\n" }, | ||
| { "input": "6\n", "output": "9\n" }, | ||
| { "input": "0\n", "output": "0\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html", | ||
| "https://dev.java/learn/language-basics/controlling-flow/" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-enum-switch", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Exhaustive enum switch expressions", | ||
| "body": "Represent three fixed states with an enum and map each constant through an arrow-style switch expression without fall-through.", | ||
| "example": "class Solution {\n enum Mode { STARTED, STOPPED }\n\n public static void main(String[] args) {\n Mode mode = Mode.STARTED;\n String action = switch (mode) {\n case STARTED -> \"go\";\n case STOPPED -> \"wait\";\n };\n System.out.println(\"state=\" + action);\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n enum State { TODO, DONE, BLOCKED }\n\n public static void main(String[] args) throws Exception {\n State state = State.valueOf(new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip());\n // TODO: give every enum constant its intended switch result.\n String result = switch (state) {\n case TODO -> \"pending\";\n case DONE -> \"ok\";\n case BLOCKED -> \"blocked\";\n };\n System.out.println(result);\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "TODO\n", "output": "work\n" }, | ||
| { "input": "DONE\n", "output": "ok\n" }, | ||
| { "input": "BLOCKED\n", "output": "blocked\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-methods", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "checkpoint", | ||
| "level": "basic", | ||
| "title": "Typed static method checkpoint", | ||
| "body": "Put an integer area calculation behind a typed static method, parse its arguments in main, and call the method rather than duplicating its expression.", | ||
| "example": "class Solution {\n static int area(int width, int height) {\n return width * height;\n }\n\n public static void main(String[] args) {\n System.out.println(\"area=\" + area(2, 5));\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n static int area(int width, int height) {\n // TODO: return the rectangular calculation from this method boundary.\n return width + height;\n }\n\n public static void main(String[] args) throws Exception {\n String[] parts = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\");\n System.out.println(area(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])));\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "3 4\n", "output": "12\n" }, | ||
| { "input": "0 5\n", "output": "0\n" }, | ||
| { "input": "7 1\n", "output": "7\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.4", | ||
| "https://dev.java/learn/classes-objects/more-on-classes/" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-overloading-varargs", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Overloads and varargs arrays", | ||
| "body": "Let a varargs overload total an int array, then delegate to the fixed-arity overload that formats a name and one computed total.", | ||
| "example": "class Solution {\n static String label(String name, int total) { return name + \":\" + total; }\n static String label(String name, int... values) {\n int total = 0;\n for (int value : values) total += value;\n return label(name, total);\n }\n public static void main(String[] args) {\n System.out.println(\"result=\" + label(\"Mia\", 1, 4));\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\n\nclass Solution {\n static String label(String name, int total) { return name + \":\" + total; }\n static String label(String name, int... values) {\n // TODO: reduce the varargs array before selecting the fixed overload.\n return label(name, values.length);\n }\n public static void main(String[] args) throws Exception {\n String[] parts = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\");\n int[] values = Arrays.stream(parts, 1, parts.length).mapToInt(Integer::parseInt).toArray();\n System.out.println(label(parts[0], values));\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "Ada 2 3\n", "output": "Ada:5\n" }, | ||
| { "input": "Lin -1 4 2\n", "output": "Lin:5\n" }, | ||
| { "input": "Bo 0\n", "output": "Bo:0\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.12", | ||
| "https://dev.java/learn/classes-objects/more-on-classes/" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-packages-imports", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Imports and fully qualified names", | ||
| "body": "Use an imported ArrayList implementation through a fully qualified java.util.List type; imports shorten source names but do not load dependencies.", | ||
| "example": "import java.util.ArrayList;\n\nclass Solution {\n public static void main(String[] args) {\n java.util.List<String> words = new ArrayList<>();\n words.add(\"package\");\n words.add(\"demo\");\n System.out.println(\"joined=\" + String.join(\"\", words));\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Arrays;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n String text = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip();\n java.util.List<String> words = new ArrayList<>();\n if (!text.isEmpty()) words.addAll(Arrays.asList(text.split(\"\\\\s+\")));\n // TODO: join the values while preserving the imported and qualified type roles.\n System.out.println(String.join(\" \" , words));\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "o k\n", "output": "ok\n" }, | ||
| { "input": "Java 21\n", "output": "Java21\n" }, | ||
| { "input": "", "output": "\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-7.html#jls-7.5", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ArrayList.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-arrays-collections", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Collection observations", | ||
| "body": "Parse an int array, copy values into a List, count them in a Map, and report the Map-derived sum, List size, and Set uniqueness.", | ||
| "example": "import java.util.*;\n\nclass Solution {\n public static void main(String[] args) {\n int[] values = {4, 5};\n List<Integer> list = new ArrayList<>();\n Map<Integer, Integer> counts = new HashMap<>();\n Set<Integer> unique = new HashSet<>();\n for (int value : values) { list.add(value); counts.merge(value, 1, Integer::sum); unique.add(value); }\n int sum = counts.entrySet().stream().mapToInt(e -> e.getKey() * e.getValue()).sum();\n System.out.println(\"sum=\" + sum + \",count=\" + list.size() + \",unique=\" + unique.size());\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\nimport java.util.*;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n String text = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip();\n int[] values = text.isEmpty() ? new int[0] : Arrays.stream(text.split(\"\\\\s+\")).mapToInt(Integer::parseInt).toArray();\n List<Integer> list = new ArrayList<>();\n Map<Integer, Integer> counts = new HashMap<>();\n Set<Integer> unique = new HashSet<>();\n for (int value : values) { list.add(value); counts.merge(value, 1, Integer::sum); unique.add(value); }\n // TODO: derive the first field from the frequency map entries.\n int sum = values.length;\n System.out.println(sum + \":\" + list.size() + \":\" + unique.size());\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "2 3\n", "output": "5:2:2\n" }, | ||
| { "input": "2 2 3\n", "output": "7:3:2\n" }, | ||
| { "input": "", "output": "0:0:0\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html", | ||
| "https://dev.java/learn/api/collections-framework/" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-generics", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Generic element preservation", | ||
| "body": "Write a last-element helper whose type parameter connects the List element type to the return type without raw collections or casts.", | ||
| "example": "import java.util.List;\n\nclass Solution {\n static <T> T last(List<T> values) { return values.get(values.size() - 1); }\n public static void main(String[] args) {\n System.out.println(\"last=\" + last(List.of(\"red\", \"blue\")));\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.List;\n\nclass Solution {\n static <T> T last(List<T> values) {\n // TODO: select the element at the generic list's far end.\n return values.get(0);\n }\n public static void main(String[] args) throws Exception {\n List<String> words = Arrays.asList(new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\"));\n System.out.println(last(words));\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "skip ok\n", "output": "ok\n" }, | ||
| { "input": "one\n", "output": "one\n" }, | ||
| { "input": "a b c\n", "output": "c\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://dev.java/learn/generics/", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.5" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-streams-lambdas", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Lazy stream pipelines", | ||
| "body": "Filter even integers, square them with IntStream.map, and trigger the lazy pipeline with one sum terminal operation.", | ||
| "example": "import java.util.stream.IntStream;\n\nclass Solution {\n public static void main(String[] args) {\n int total = IntStream.of(2, 6).filter(n -> n % 2 == 0).map(n -> n * n).sum();\n System.out.println(\"squares=\" + total);\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n String text = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip();\n int[] values = text.isEmpty() ? new int[0] : Arrays.stream(text.split(\"\\\\s+\")).mapToInt(Integer::parseInt).toArray();\n // TODO: keep the intended subset before squaring and terminating the stream.\n int total = Arrays.stream(values).filter(n -> n % 2 != 0).map(n -> n * n).sum();\n System.out.println(total);\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "1 2 3 4\n", "output": "20\n" }, | ||
| { "input": "-2 -1 0\n", "output": "4\n" }, | ||
| { "input": "1 3\n", "output": "0\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/IntStream.html#map(java.util.function.IntUnaryOperator)", | ||
| "https://dev.java/learn/api/streams/" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-comparators-sorting", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Comparator chains", | ||
| "body": "Order users by descending score and then ascending name, avoiding subtraction comparators that can overflow.", | ||
| "example": "import java.util.*;\n\nclass Solution {\n record User(String name, int score) {}\n public static void main(String[] args) {\n var users = new ArrayList<>(List.of(new User(\"Kai\", 2), new User(\"Bea\", 2)));\n users.sort(Comparator.comparingInt(User::score).reversed().thenComparing(User::name));\n System.out.println(\"best=\" + users.get(0).name() + \":\" + users.get(0).score());\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\nimport java.util.*;\n\nclass Solution {\n record User(String name, int score) {}\n public static void main(String[] args) throws Exception {\n String[] parts = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\");\n List<User> users = new ArrayList<>();\n for (int i = 0; i < parts.length; i += 2) users.add(new User(parts[i], Integer.parseInt(parts[i + 1])));\n // TODO: compose the requested primary and tie-break directions.\n users.sort(Comparator.comparingInt(User::score).thenComparing(User::name));\n User best = users.get(0);\n System.out.println(best.name() + \":\" + best.score());\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "Ada 3 Lin 5 Bo 4\n", "output": "Lin:5\n" }, | ||
| { "input": "Zed 5 Amy 5\n", "output": "Amy:5\n" }, | ||
| { "input": "Solo -1\n", "output": "Solo:-1\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Comparator.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-classes-objects", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Per-instance object state", | ||
| "body": "Create a Counter object from parsed state, send it a delta through a method, and observe the field owned by that instance.", | ||
| "example": "class Solution {\n static class Counter {\n int value;\n Counter(int value) { this.value = value; }\n void add(int delta) { value += delta; }\n }\n public static void main(String[] args) {\n Counter counter = new Counter(10);\n counter.add(2);\n System.out.println(\"value=\" + counter.value);\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n static class Counter {\n int value;\n Counter(int value) { this.value = value; }\n void add(int delta) {\n // TODO: update this object's field with the received delta.\n delta += value;\n }\n }\n public static void main(String[] args) throws Exception {\n String[] parts = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\");\n Counter counter = new Counter(Integer.parseInt(parts[0]));\n counter.add(Integer.parseInt(parts[1]));\n System.out.println(counter.value);\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "3 2\n", "output": "5\n" }, | ||
| { "input": "0 7\n", "output": "7\n" }, | ||
| { "input": "-2 1\n", "output": "-1\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://dev.java/learn/classes-objects/creating-objects/", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-constructors", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Constructor-established invariants", | ||
| "body": "Initialize both final dimensions in a no-return-type constructor and use this to distinguish fields from same-named parameters.", | ||
| "example": "class Solution {\n static class Rectangle {\n final int width;\n final int height;\n Rectangle(int width, int height) { this.width = width; this.height = height; }\n int area() { return width * height; }\n }\n public static void main(String[] args) {\n System.out.println(\"area=\" + new Rectangle(2, 5).area());\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n static class Rectangle {\n final int width;\n final int height;\n Rectangle(int width, int height) {\n this.width = width;\n // TODO: initialize height from its same-named constructor argument.\n this.height = 1;\n }\n int area() { return width * height; }\n }\n public static void main(String[] args) throws Exception {\n String[] parts = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\");\n System.out.println(new Rectangle(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])).area());\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "3 4\n", "output": "12\n" }, | ||
| { "input": "0 5\n", "output": "0\n" }, | ||
| { "input": "7 1\n", "output": "7\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.8", | ||
| "https://dev.java/learn/classes-objects/creating-objects/" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-encapsulation", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Private state with enforced rules", | ||
| "body": "Keep balance private and expose a deposit method that accepts positive changes only; access control alone does not enforce an invariant.", | ||
| "example": "class Solution {\n static class Account {\n private int balance;\n void deposit(int amount) { if (amount > 0) balance += amount; }\n int balance() { return balance; }\n }\n public static void main(String[] args) {\n Account account = new Account();\n account.deposit(2); account.deposit(-7); account.deposit(4);\n System.out.println(\"balance=\" + account.balance());\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n static class Account {\n private int balance;\n void deposit(int amount) {\n // TODO: guard the state transition with the account invariant.\n balance += amount;\n }\n int balance() { return balance; }\n }\n public static void main(String[] args) throws Exception {\n String text = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip();\n Account account = new Account();\n if (!text.isEmpty()) for (String token : text.split(\"\\\\s+\")) account.deposit(Integer.parseInt(token));\n System.out.println(account.balance());\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "5 -2 3\n", "output": "8\n" }, | ||
| { "input": "-1\n", "output": "0\n" }, | ||
| { "input": "", "output": "0\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-6.html#jls-6.6" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-static-members", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Class-owned static constants", | ||
| "body": "Apply one static final scaling factor shared by every call, while separating class ownership from the deeper mutability of referenced objects.", | ||
| "example": "class Solution {\n static final int FACTOR = 3;\n static int scale(int value) { return value * FACTOR; }\n public static void main(String[] args) { System.out.println(\"scaled=\" + scale(4)); }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n // TODO: correct the class-owned multiplier used by every invocation.\n static final int FACTOR = 1;\n static int scale(int value) { return value * FACTOR; }\n public static void main(String[] args) throws Exception {\n int value = Integer.parseInt(new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip());\n System.out.println(scale(value));\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "2\n", "output": "6\n" }, | ||
| { "input": "0\n", "output": "0\n" }, | ||
| { "input": "-2\n", "output": "-6\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.3.1.1" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-interfaces", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Behavior through an interface", | ||
| "body": "Pass an implementation through a Named interface and call its public contract without depending on the concrete User type.", | ||
| "example": "import java.util.Locale;\n\nclass Solution {\n interface Named { String name(); }\n record User(String name) implements Named {}\n static String render(Named value) { return value.name().toUpperCase(Locale.ROOT); }\n public static void main(String[] args) { System.out.println(\"name=\" + render(new User(\"Mira\"))); }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\nimport java.util.Locale;\n\nclass Solution {\n interface Named { String name(); }\n record User(String name) implements Named {}\n static String render(Named value) {\n // TODO: implement the interface-facing text transformation.\n return value.name().toLowerCase(Locale.ROOT);\n }\n public static void main(String[] args) throws Exception {\n String name = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip();\n System.out.println(render(new User(name)));\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "ok\n", "output": "OK\n" }, | ||
| { "input": "Ada\n", "output": "ADA\n" }, | ||
| { "input": "é\n", "output": "É\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-inheritance-composition", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Inheritance plus composition", | ||
| "body": "Reuse substitutable base state through inheritance and model an independent bonus with a composed helper object.", | ||
| "example": "class Solution {\n static class BaseScore { protected final int base; BaseScore(int base) { this.base = base; } }\n record Bonus(int value) {}\n static class Player extends BaseScore {\n private final Bonus bonus;\n Player(int base, int bonus) { super(base); this.bonus = new Bonus(bonus); }\n int total() { return base + bonus.value(); }\n }\n public static void main(String[] args) { System.out.println(\"total=\" + new Player(10, 2).total()); }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n static class BaseScore { protected final int base; BaseScore(int base) { this.base = base; } }\n record Bonus(int value) {}\n static class Player extends BaseScore {\n private final Bonus bonus;\n Player(int base, int bonus) { super(base); this.bonus = new Bonus(bonus); }\n int total() {\n // TODO: include the composed object's contribution.\n return base;\n }\n }\n public static void main(String[] args) throws Exception {\n String[] parts = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\");\n System.out.println(new Player(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])).total());\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "3 2\n", "output": "5\n" }, | ||
| { "input": "0 7\n", "output": "7\n" }, | ||
| { "input": "-2 1\n", "output": "-1\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://dev.java/learn/inheritance/", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.1.4" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-exceptions", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Narrow exception recovery", | ||
| "body": "Return a parsed integer when valid and recover with the supplied fallback only from NumberFormatException, an unchecked parsing failure.", | ||
| "example": "class Solution {\n static int parseOr(String text, int fallback) {\n try { return Integer.parseInt(text); }\n catch (NumberFormatException error) { return fallback; }\n }\n public static void main(String[] args) { System.out.println(\"parsed=\" + parseOr(\"oops\", 4)); }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n static int parseOr(String text, int fallback) {\n try { return Integer.parseInt(text); }\n catch (NumberFormatException error) {\n // TODO: recover with the caller-provided value.\n return 0;\n }\n }\n public static void main(String[] args) throws Exception {\n String[] parts = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\");\n System.out.println(parseOr(parts[0], Integer.parseInt(parts[1])));\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "7 12\n", "output": "7\n" }, | ||
| { "input": "bad 12\n", "output": "12\n" }, | ||
| { "input": "-3 9\n", "output": "-3\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-11.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/NumberFormatException.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-optional", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Optional filtering and fallback", | ||
| "body": "Model a possibly accepted token with Optional, retain present text only when it has at most three characters, and use orElse rather than calling get on an empty container.", | ||
| "example": "import java.util.Optional;\n\nclass Solution {\n public static void main(String[] args) {\n String value = Optional.of(\"cat\").filter(s -> s.length() <= 3).orElse(\"missing\");\n System.out.println(\"value=\" + value);\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\nimport java.util.Optional;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n String token = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip();\n Optional<String> value = token.equals(\"-\") ? Optional.empty() : Optional.of(token);\n // TODO: retain only an accepted token of at most three characters before choosing the fallback.\n System.out.println(value.filter(s -> s.length() > 3).orElse(\"missing\"));\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "ok\n", "output": "ok\n" }, | ||
| { "input": "cat\n", "output": "cat\n" }, | ||
| { "input": "toolong\n", "output": "missing\n" }, | ||
| { "input": "-\n", "output": "missing\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html", | ||
| "https://dev.java/learn/api/streams/optionals/" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-try-with-resources", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Observed try-with-resources", | ||
| "body": "Place the received bytes in a managed stream, read from that resource inside the try block, decode as UTF-8, and normalize the observed content.", | ||
| "example": "import java.io.ByteArrayInputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Locale;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n try (var resource = new ByteArrayInputStream(\" demo \".getBytes(StandardCharsets.UTF_8))) {\n String text = new String(resource.readAllBytes(), StandardCharsets.UTF_8).strip().toUpperCase(Locale.ROOT);\n System.out.println(\"normalized=\" + text);\n }\n }\n}\n", | ||
| "starter": "import java.io.ByteArrayInputStream;\nimport java.nio.charset.StandardCharsets;\n\nclass Solution {\n public static void main(String[] args) throws Exception {\n byte[] input = System.in.readAllBytes();\n try (var resource = new ByteArrayInputStream(input)) {\n String text = new String(resource.readAllBytes(), StandardCharsets.UTF_8).strip();\n // TODO: normalize the resource content and handle its empty form.\n System.out.println(text);\n }\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "ok\n", "output": "OK\n" }, | ||
| { "input": " Ada \n", "output": "ADA\n" }, | ||
| { "input": "", "output": "EMPTY\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.20.3", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/AutoCloseable.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-equality-hashcode", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Equality and hash consistency", | ||
| "body": "Compare both Point coordinates with pattern instanceof and keep hashCode consistent so HashSet can honor logical equality. Unequal points may legally collide; equal points must never produce different hashes.", | ||
| "example": "import java.util.*;\n\nclass Solution {\n record Point(int x, int y) {}\n public static void main(String[] args) {\n Set<Point> points = new HashSet<>();\n points.add(new Point(1, 1)); points.add(new Point(1, 1));\n System.out.println(\"setSize=\" + points.size());\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\nimport java.util.*;\n\nclass Solution {\n static final class Point {\n final int x, y;\n Point(int x, int y) { this.x = x; this.y = y; }\n // TODO: compare both coordinates while preserving equal-implies-same-hash.\n @Override public boolean equals(Object value) { return value instanceof Point other && x == other.x; }\n @Override public int hashCode() { return Integer.hashCode(x); }\n }\n public static void main(String[] args) throws Exception {\n int[] n = Arrays.stream(new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\")).mapToInt(Integer::parseInt).toArray();\n Set<Point> points = new HashSet<>();\n points.add(new Point(n[0], n[1])); points.add(new Point(n[2], n[3]));\n System.out.println(points.size());\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "2 3 2 3\n", "output": "1\n" }, | ||
| { "input": "2 3 2 4\n", "output": "2\n" }, | ||
| { "input": "0 0 1 0\n", "output": "2\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#equals(java.lang.Object)", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashSet.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-records", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "checkpoint", | ||
| "level": "advanced", | ||
| "title": "Defensive record snapshot checkpoint", | ||
| "body": "Store a List in a record compact constructor with List.copyOf, mutate the source afterward, and sum the stable snapshot to expose shallow immutability.", | ||
| "example": "import java.util.*;\n\nclass Solution {\n record Snapshot(List<Integer> values) {\n Snapshot { values = List.copyOf(values); }\n }\n public static void main(String[] args) {\n List<Integer> source = new ArrayList<>(List.of(4, 5));\n Snapshot snapshot = new Snapshot(source);\n source.add(6);\n System.out.println(\"snapshot=\" + snapshot.values().stream().mapToInt(Integer::intValue).sum());\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\nimport java.util.*;\n\nclass Solution {\n record Snapshot(List<Integer> values) {\n // TODO: detach the record component from its mutable source list.\n }\n public static void main(String[] args) throws Exception {\n String text = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip();\n List<Integer> source = new ArrayList<>();\n if (!text.isEmpty()) for (String token : text.split(\"\\\\s+\")) source.add(Integer.parseInt(token));\n Snapshot snapshot = new Snapshot(source);\n source.remove(source.size() - 1);\n System.out.println(snapshot.values().stream().mapToInt(Integer::intValue).sum());\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "2 3\n", "output": "5\n" }, | ||
| { "input": "-1 1\n", "output": "0\n" }, | ||
| { "input": "7\n", "output": "7\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Record.html", | ||
| "https://dev.java/learn/records/" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-annotations", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Runtime annotation metadata", | ||
| "body": "Retain a method annotation at runtime, restrict its target, and retrieve its prefix through reflection before formatting normalized input.", | ||
| "example": "import java.lang.annotation.*;\nimport java.util.Locale;\n\nclass Solution {\n @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)\n @interface Label { String value(); }\n @Label(\"trace\") static String normalize(String text) { return text.toUpperCase(Locale.ROOT); }\n public static void main(String[] args) throws Exception {\n Label label = Solution.class.getDeclaredMethod(\"normalize\", String.class).getAnnotation(Label.class);\n System.out.println(label.value() + \":\" + normalize(\"demo\"));\n }\n}\n", | ||
| "starter": "import java.lang.annotation.*;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Locale;\n\nclass Solution {\n @Target(ElementType.METHOD)\n @interface Label { String value(); }\n @Label(\"audit\") static String normalize(String text) { return text.toUpperCase(Locale.ROOT); }\n public static void main(String[] args) throws Exception {\n String text = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip();\n // TODO: make the annotation observable to this reflective lookup.\n Label label = Solution.class.getDeclaredMethod(\"normalize\", String.class).getAnnotation(Label.class);\n String prefix = label == null ? \"missing\" : label.value();\n System.out.println(prefix + \":\" + normalize(text));\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "ok\n", "output": "audit:OK\n" }, | ||
| { "input": "Ada\n", "output": "audit:ADA\n" }, | ||
| { "input": "é\n", "output": "audit:É\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/annotation/Retention.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/Method.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-sealed-classes", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Sealed shapes and pattern switch", | ||
| "body": "Close a Shape hierarchy with permits and evaluate every record subtype using Java 21 record patterns in an exhaustive switch expression.", | ||
| "example": "class Solution {\n sealed interface Shape permits Rect, Circle, Dot {}\n record Rect(int width, int height) implements Shape {}\n record Circle(int radius) implements Shape {}\n record Dot() implements Shape {}\n static int metric(Shape shape) {\n return switch (shape) {\n case Rect(int w, int h) -> w * h;\n case Circle(int r) -> r * 2;\n case Dot() -> 0;\n };\n }\n public static void main(String[] args) { System.out.println(\"metric=\" + metric(new Rect(2, 5))); }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\n\nclass Solution {\n sealed interface Shape permits Rect, Circle, Dot {}\n record Rect(int width, int height) implements Shape {}\n record Circle(int radius) implements Shape {}\n record Dot() implements Shape {}\n static int metric(Shape shape) {\n return switch (shape) {\n case Rect(int w, int h) -> w * h;\n // TODO: compute the circular case's requested measure.\n case Circle(int r) -> r;\n case Dot() -> 0;\n };\n }\n public static void main(String[] args) throws Exception {\n String[] parts = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip().split(\"\\\\s+\");\n Shape shape = switch (parts[0]) {\n case \"rect\" -> new Rect(Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));\n case \"circle\" -> new Circle(Integer.parseInt(parts[1]));\n default -> new Dot();\n };\n System.out.println(metric(shape));\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "rect 3 4\n", "output": "12\n" }, | ||
| { "input": "circle 5\n", "output": "10\n" }, | ||
| { "input": "dot\n", "output": "0\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "java-testing-assert", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "capstone", | ||
| "level": "advanced", | ||
| "title": "Always-active capstone checks", | ||
| "body": "Parse User records, order them by score and name, select a fallback for empty input, and validate helpers with an explicit check that cannot be disabled.", | ||
| "example": "import java.util.*;\n\nclass Solution {\n record User(String name, int score) {}\n static User choose(List<User> users) {\n return users.stream().sorted(Comparator.comparingInt(User::score).reversed().thenComparing(User::name)).findFirst().orElse(new User(\"none\", 0));\n }\n static void check(boolean condition, String message) { if (!condition) throw new AssertionError(message); }\n public static void main(String[] args) {\n User winner = choose(List.of(new User(\"Zoe\", 2), new User(\"Ada\", 2)));\n check(choose(List.of()).name().equals(\"none\"), \"empty fallback\");\n System.out.println(\"winner=\" + winner.name() + \":\" + winner.score());\n }\n}\n", | ||
| "starter": "import java.nio.charset.StandardCharsets;\nimport java.util.*;\n\nclass Solution {\n record User(String name, int score) {}\n static User choose(List<User> users) {\n // TODO: make equal scores use the required deterministic name order.\n return users.stream().sorted(Comparator.comparingInt(User::score).reversed()).findFirst().orElse(new User(\"none\", 0));\n }\n static void check(boolean condition, String message) { if (!condition) throw new AssertionError(message); }\n public static void main(String[] args) throws Exception {\n String text = new String(System.in.readAllBytes(), StandardCharsets.UTF_8).strip();\n List<User> users = new ArrayList<>();\n if (!text.isEmpty()) {\n String[] parts = text.split(\"\\\\s+\");\n for (int i = 0; i < parts.length; i += 2) users.add(new User(parts[i], Integer.parseInt(parts[i + 1])));\n }\n check(choose(List.of()).equals(new User(\"none\", 0)), \"fallback contract\");\n User winner = choose(users);\n System.out.println(winner.name() + \":\" + winner.score());\n }\n}\n", | ||
| "cases": [ | ||
| { "input": "Ada 3 Lin 5 Bo 4\n", "output": "Lin:5\n" }, | ||
| { "input": "Zed 5 Amy 5\n", "output": "Amy:5\n" }, | ||
| { "input": "", "output": "none:0\n" } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.10", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/AssertionError.html" | ||
| ] | ||
| } | ||
| ] | ||
| } |
| { | ||
| "schema_version": 1, | ||
| "runtime": "python", | ||
| "lessons": [ | ||
| { | ||
| "id": "py-output", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Exact output with print", | ||
| "body": "print converts each argument, inserts sep between arguments, and writes end afterward. Online judges compare stdout exactly, so an extra space or missing newline is observable behavior.", | ||
| "example": "name = \"Mina\"\nscore = 9\nprint(name, score, sep=\"=\", end=\"!\\n\")\n", | ||
| "starter": "import sys\n\nname, score = sys.stdin.read().split()\n# TODO: choose print separators and a line ending for the required record\nprint(name, score)\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada 7\n", | ||
| "output": "Ada:7\n" | ||
| }, | ||
| { | ||
| "input": "Lin 0\n", | ||
| "output": "Lin:0\n" | ||
| }, | ||
| { | ||
| "input": "José 12\n", | ||
| "output": "José:12\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/library/functions.html#print" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-input", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Read stdin once", | ||
| "body": "sys.stdin.read returns the complete input as text. Splitting without an explicit separator accepts spaces, newlines, and repeated whitespace; an empty stream produces no tokens and therefore a zero sum.", | ||
| "example": "raw = \"10\\n4\"\nvalues = [int(token) for token in raw.split()]\nprint(sum(values))\n", | ||
| "starter": "import sys\n\nraw = sys.stdin.read()\n# TODO: convert every whitespace-delimited token instead of only the first\nvalues = [int(token) for token in raw.split()[:1]]\nprint(sum(values))\n", | ||
| "cases": [ | ||
| { | ||
| "input": "2 3\n", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "-1\n5 2\n", | ||
| "output": "6\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/library/sys.html#sys.stdin" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-variables", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Names and rebinding", | ||
| "body": "Assignment binds a name to an object; augmented assignment then rebinds the accumulator to the computed integer. The original integer is unchanged because integers are immutable, while closures may later read the current value stored in an enclosing cell.", | ||
| "example": "total = 10\ntotal += 5\nprint(total)\n", | ||
| "starter": "import sys\n\nleft, right = map(int, sys.stdin.read().split())\ntotal = left\n# TODO: rebind the accumulator using the second parsed value\nprint(total)\n", | ||
| "cases": [ | ||
| { | ||
| "input": "1 2\n", | ||
| "output": "3\n" | ||
| }, | ||
| { | ||
| "input": "-4 9\n", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "0 0\n", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/reference/simple_stmts.html#assignment-statements" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-numbers", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Floor quotient and remainder", | ||
| "body": "For a nonzero divisor, integer // rounds the quotient toward negative infinity and % supplies the paired remainder. That differs from truncating division: negative operands can make the quotient one smaller, and the remainder follows the divisor's sign.", | ||
| "example": "dividend = -11\ndivisor = 4\nprint(f\"{dividend // divisor}:{dividend % divisor}\")\n", | ||
| "starter": "import sys\n\ndividend, divisor = map(int, sys.stdin.read().split())\n# TODO: compute the integer quotient with Python's floor rule\nprint(f\"{dividend / divisor}:{dividend % divisor}\")\n", | ||
| "cases": [ | ||
| { | ||
| "input": "7 2\n", | ||
| "output": "3:1\n" | ||
| }, | ||
| { | ||
| "input": "-7 2\n", | ||
| "output": "-4:1\n" | ||
| }, | ||
| { | ||
| "input": "7 -2\n", | ||
| "output": "-4:-1\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/reference/expressions.html#binary-arithmetic-operations" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-tuples-sets", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Ordered pairs and unique members", | ||
| "body": "A tuple preserves the two input positions, while set(pair) retains one instance of each hashable value. Empty braces create a dict rather than a set, so an empty set must be written as set().", | ||
| "example": "pair = (\"r\", \"s\", \"r\")\nunique = set(pair)\nprint(\"\".join(pair), len(unique))\n", | ||
| "starter": "import sys\n\npair = tuple(sys.stdin.read().split())\n# TODO: derive the unique members from the ordered pair\nunique = set()\nprint(\"\".join(pair), len(unique))\n", | ||
| "cases": [ | ||
| { | ||
| "input": "o k\n", | ||
| "output": "ok 2\n" | ||
| }, | ||
| { | ||
| "input": "a a\n", | ||
| "output": "aa 1\n" | ||
| }, | ||
| { | ||
| "input": "é e\n", | ||
| "output": "ée 2\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/tutorial/datastructures.html#tuples-and-sequences" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-strings", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Unicode-aware string slicing", | ||
| "body": "Python str values are immutable sequences of Unicode code points. A half-open slice can remove the first and last code point without altering the source, including when those boundary values are emoji.", | ||
| "example": "wrapped = \"★서울★\"\nprint(wrapped[1:-1])\n", | ||
| "starter": "import sys\n\ntext = sys.stdin.read().rstrip(\"\\n\")\n# TODO: slice away both boundary code points\nprint(text[1:])\n", | ||
| "cases": [ | ||
| { | ||
| "input": "xokx\n", | ||
| "output": "ok\n" | ||
| }, | ||
| { | ||
| "input": "[rust]\n", | ||
| "output": "rust\n" | ||
| }, | ||
| { | ||
| "input": "🙂go🙂\n", | ||
| "output": "go\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/library/stdtypes.html#text-sequence-type-str" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-pathlib", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Path components with pathlib", | ||
| "body": "PurePosixPath parses POSIX path structure without touching the filesystem. stem removes only the final suffix, suffix reports that final extension, and a leading-dot filename such as .env has no suffix.", | ||
| "example": "from pathlib import PurePosixPath\n\npath = PurePosixPath(\"/srv/report.csv\")\nprint(f\"{path.stem}:{path.suffix}\")\n", | ||
| "starter": "import sys\nfrom pathlib import PurePosixPath\n\npath = PurePosixPath(sys.stdin.read().strip())\n# TODO: report the final stem and final suffix as separate properties\nprint(path.name)\n", | ||
| "cases": [ | ||
| { | ||
| "input": "logs/app.txt\n", | ||
| "output": "app:.txt\n" | ||
| }, | ||
| { | ||
| "input": "/tmp/archive.tar.gz\n", | ||
| "output": "archive.tar:.gz\n" | ||
| }, | ||
| { | ||
| "input": ".env\n", | ||
| "output": ".env:\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/library/pathlib.html#pathlib.PurePath.stem" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-control-flow", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Branches, loops, and range bounds", | ||
| "body": "Indentation determines which statements belong to a loop or branch. range excludes its stop, so including n requires n + 1; the condition then selects only odd values before they reach the accumulator.", | ||
| "example": "limit = 7\ntotal = 0\nfor value in range(1, limit + 1):\n if value % 2:\n total += value\nprint(total)\n", | ||
| "starter": "import sys\n\nlimit = int(sys.stdin.read().strip())\ntotal = 0\nfor value in range(1, limit + 1):\n # TODO: select the parity requested by the exercise\n if value % 2 == 0:\n total += value\nprint(total)\n", | ||
| "cases": [ | ||
| { | ||
| "input": "3\n", | ||
| "output": "4\n" | ||
| }, | ||
| { | ||
| "input": "6\n", | ||
| "output": "9\n" | ||
| }, | ||
| { | ||
| "input": "0\n", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/tutorial/controlflow.html#the-range-function" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-functions", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "checkpoint", | ||
| "level": "basic", | ||
| "title": "Checkpoint: return a computed area", | ||
| "body": "A function boundary separates parsing, calculation, and output. Parameters receive the two dimensions, return hands the product back to the caller, and only the outer print writes judge-visible text.", | ||
| "example": "def area(width, height):\n return width * height\n\nprint(area(8, 2))\n", | ||
| "starter": "import sys\n\ndef area(width, height):\n # TODO: return the rectangle calculation to the caller\n return width + height\n\nwidth, height = map(int, sys.stdin.read().split())\nprint(area(width, height))\n", | ||
| "cases": [ | ||
| { | ||
| "input": "3 4\n", | ||
| "output": "12\n" | ||
| }, | ||
| { | ||
| "input": "0 5\n", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "7 1\n", | ||
| "output": "7\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/tutorial/controlflow.html#defining-functions" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-modules-imports", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Visible dependencies through modules", | ||
| "body": "import math binds the module namespace, making math.ceil's origin explicit at the call site. Star imports hide where names came from and make collisions harder to review.", | ||
| "example": "import math\n\nvalue = -3.8\nprint(math.ceil(value))\n", | ||
| "starter": "import math\nimport sys\n\nvalue = float(sys.stdin.read().strip())\n# TODO: call the upward-rounding operation through the module namespace\nprint(math.floor(value))\n", | ||
| "cases": [ | ||
| { | ||
| "input": "2.1\n", | ||
| "output": "3\n" | ||
| }, | ||
| { | ||
| "input": "-2.1\n", | ||
| "output": "-2\n" | ||
| }, | ||
| { | ||
| "input": "5.0\n", | ||
| "output": "5\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/tutorial/modules.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-lambdas-closures", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Closures capture cells, not snapshots", | ||
| "body": "A nested function can read a binding from an enclosing scope after that outer call returns. The captured cell is resolved when the closure runs, which is why lambdas created by one loop commonly all observe the loop's final value.", | ||
| "example": "readers = [lambda: index for index in range(3)]\nprint([reader() for reader in readers])\n", | ||
| "starter": "import sys\n\ndef make_adder(delta):\n # TODO: return a closure that uses the enclosing binding\n return lambda value: value\n\ndelta, value = map(int, sys.stdin.read().split())\nprint(make_adder(delta)(value))\n", | ||
| "cases": [ | ||
| { | ||
| "input": "2 3\n", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "-1 4\n", | ||
| "output": "3\n" | ||
| }, | ||
| { | ||
| "input": "0 0\n", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/reference/executionmodel.html#resolution-of-names" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-decorators", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Definition-time decorators", | ||
| "body": "Decorator expressions run when the def statement executes, while the returned wrapper runs on each later call. functools.wraps copies identifying metadata so tooling still sees the wrapped function's name and documentation.", | ||
| "example": "from functools import wraps\n\ndef tag(function):\n @wraps(function)\n def wrapper(value):\n return f\"<{function(value).upper()}>\"\n return wrapper\n\n@tag\ndef label(value):\n return value\n\nprint(label(\"Mina\"))\n", | ||
| "starter": "import sys\nfrom functools import wraps\n\ndef bracket(function):\n @wraps(function)\n def wrapper(value):\n # TODO: decorate the transformed return value at call time\n return function(value).upper()\n return wrapper\n\n@bracket\ndef label(value):\n return value\n\nprint(label(sys.stdin.read().strip()))\n", | ||
| "cases": [ | ||
| { | ||
| "input": "ok\n", | ||
| "output": "[OK]\n" | ||
| }, | ||
| { | ||
| "input": "Ada\n", | ||
| "output": "[ADA]\n" | ||
| }, | ||
| { | ||
| "input": "é\n", | ||
| "output": "[É]\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/library/functools.html#functools.wraps" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-async", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Drive real coroutines with asyncio", | ||
| "body": "Calling an async function creates a coroutine object but does not schedule it. asyncio.run drives the top-level coroutine, gather schedules the child awaitables, and sleep(0) provides an actual suspension point while preserving result order.", | ||
| "example": "import asyncio\n\nasync def upper(word):\n await asyncio.sleep(0)\n return word.upper()\n\nasync def main():\n results = await asyncio.gather(upper(\"go\"), upper(\"now\"))\n print(*results)\n\nasyncio.run(main())\n", | ||
| "starter": "import asyncio\nimport sys\n\nasync def double(value):\n await asyncio.sleep(0)\n return value * 2\n\nasync def main():\n values = [int(token) for token in sys.stdin.read().split()]\n # TODO: gather and await one double coroutine for each parsed value\n results = values\n print(*results)\n\nasyncio.run(main())\n", | ||
| "cases": [ | ||
| { | ||
| "input": "1 2\n", | ||
| "output": "2 4\n" | ||
| }, | ||
| { | ||
| "input": "-1 3\n", | ||
| "output": "-2 6\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/library/asyncio-runner.html#asyncio.run" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-lists-dicts", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Group list values by dictionary key", | ||
| "body": "Lists retain every score in insertion order, while a dict maps each name to its own list. setdefault can create a fresh list per missing key, and get handles a query that never appeared without raising KeyError.", | ||
| "example": "scores = {\"Mina\": [8, 1], \"Kai\": [2]}\nquery = \"Mina\"\nprint(sum(scores.get(query, [])))\n", | ||
| "starter": "import sys\n\ntokens = sys.stdin.read().split()\nquery = tokens[0]\nscores = {}\n# TODO: group every following name-score pair, not only the first one\nname, score = tokens[1], int(tokens[2])\nscores.setdefault(name, []).append(score)\nprint(sum(scores.get(query, [])))\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada Ada 2 Lin 4 Ada 3\n", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "Lin Ada 2 Lin -1 Lin 4\n", | ||
| "output": "3\n" | ||
| }, | ||
| { | ||
| "input": "Bo Ada 1 Lin 2\n", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/tutorial/datastructures.html#dictionaries" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-sorting-keys", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Multi-field sorting keys", | ||
| "body": "A key tuple can encode descending score as a negated number and ascending name as the second field. Python's sort is stable, but stability alone preserves input order on ties rather than inventing the required alphabetical rule.", | ||
| "example": "users = [(\"Mina\", 7), (\"Ana\", 7), (\"Bo\", 9)]\nbest = sorted(users, key=lambda item: (-item[1], item[0]))[0]\nprint(f\"{best[0]}:{best[1]}\")\n", | ||
| "starter": "import sys\n\nusers = []\nfor token in sys.stdin.read().split():\n name, score = token.rsplit(\":\", 1)\n users.append((name, int(score)))\n# TODO: rank by the numeric result before applying the tie-break\nbest = sorted(users, key=lambda item: item[0])[0]\nprint(f\"{best[0]}:{best[1]}\")\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada:3 Lin:5 Bo:4\n", | ||
| "output": "Lin:5\n" | ||
| }, | ||
| { | ||
| "input": "Zed:5 Amy:5\n", | ||
| "output": "Amy:5\n" | ||
| }, | ||
| { | ||
| "input": "Solo:-1\n", | ||
| "output": "Solo:-1\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/howto/sorting.html#sort-stability-and-complex-sorts" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-counter-defaultdict", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Count and group with collections", | ||
| "body": "Counter records frequencies of hashable values, and defaultdict(list) supplies a fresh bucket while grouping. Indexing a missing defaultdict key creates that entry, whereas get can inspect an absent group without mutating the mapping.", | ||
| "example": "from collections import Counter, defaultdict\n\nquery = \"owl\"\nwords = [\"oak\", \"owl\", \"ox\", \"owl\", \"otter\"]\ncounts = Counter(words)\ngroups = defaultdict(list)\nfor word in words:\n groups[word[0]].append(word)\nprint(counts[query], len(groups.get(query[0], [])))\n", | ||
| "starter": "import sys\nfrom collections import Counter, defaultdict\n\nquery, *words = sys.stdin.read().split()\n# TODO: count and group every word instead of only the first one\ncounts = Counter(words[:1])\ngroups = defaultdict(list)\nif words:\n groups[words[0][0]].append(words[0])\nprint(counts[query], len(groups.get(query[0], [])))\n", | ||
| "cases": [ | ||
| { | ||
| "input": "red red blue red\n", | ||
| "output": "2 2\n" | ||
| }, | ||
| { | ||
| "input": "blue red blue black blue\n", | ||
| "output": "2 3\n" | ||
| }, | ||
| { | ||
| "input": "x a b\n", | ||
| "output": "0 0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/library/collections.html#collections.Counter" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-deque", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Constant-time operations at both ends", | ||
| "body": "collections.deque supports append, appendleft, pop, and popleft at opposite ends. A list can pop efficiently from its right, but removing index zero shifts the remaining elements and is not the equivalent queue operation.", | ||
| "example": "from collections import deque\n\nqueue = deque([\"center\"])\nqueue.appendleft(\"first\")\nqueue.append(\"last\")\nprint(queue.popleft(), queue.pop())\n", | ||
| "starter": "import sys\nfrom collections import deque\n\nmiddle, left, right = sys.stdin.read().split()\nqueue = deque([middle])\n# TODO: place the second token on the opposite end from the third\nqueue.append(left)\nqueue.append(right)\nprint(queue.popleft(), queue.pop())\n", | ||
| "cases": [ | ||
| { | ||
| "input": "middle start end\n", | ||
| "output": "start end\n" | ||
| }, | ||
| { | ||
| "input": "x a b\n", | ||
| "output": "a b\n" | ||
| }, | ||
| { | ||
| "input": "z α ω\n", | ||
| "output": "α ω\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/library/collections.html#collections.deque" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-comprehensions", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "checkpoint", | ||
| "level": "intermediate", | ||
| "title": "Checkpoint: filter, transform, then total", | ||
| "body": "A list comprehension reads from left to right as an output expression, an iteration clause, and an optional filter. It eagerly builds the selected squares; a generator expression would instead feed a lazy, single-pass pipeline.", | ||
| "example": "numbers = [5, 6, 7, 8]\neven_squares = [number * number for number in numbers if number % 2 == 0]\nprint(sum(even_squares))\n", | ||
| "starter": "import sys\n\nnumbers = [int(token) for token in sys.stdin.read().split()]\n# TODO: keep the requested parity while building squared values\nsquares = [number * number for number in numbers if number % 2 != 0]\nprint(sum(squares))\n", | ||
| "cases": [ | ||
| { | ||
| "input": "1 2 3 4\n", | ||
| "output": "20\n" | ||
| }, | ||
| { | ||
| "input": "-2 -1 0\n", | ||
| "output": "4\n" | ||
| }, | ||
| { | ||
| "input": "1 3\n", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/tutorial/datastructures.html#list-comprehensions" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-generators", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Lazy countdown generators", | ||
| "body": "Calling a generator function returns a generator object before its body executes. Each advance runs until yield, saves the suspended state, and resumes later; exhausting the iterator produces no more values.", | ||
| "example": "def reverse_letters(text):\n for letter in reversed(text):\n yield letter\n\nprint(*reverse_letters(\"abc\"))\n", | ||
| "starter": "import sys\n\ndef countdown(number):\n # TODO: continue yielding while decreasing toward the stopping boundary\n if number > 0:\n yield number\n\nnumber = int(sys.stdin.read().strip())\nprint(*countdown(number))\n", | ||
| "cases": [ | ||
| { | ||
| "input": "3\n", | ||
| "output": "3 2 1\n" | ||
| }, | ||
| { | ||
| "input": "1\n", | ||
| "output": "1\n" | ||
| }, | ||
| { | ||
| "input": "0\n", | ||
| "output": "\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/reference/expressions.html#generator-iterator-methods" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-itertools", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Flatten iterables lazily", | ||
| "body": "itertools.chain.from_iterable visits each inner iterable in sequence without first copying every element into a new list. The resulting iterator is single-pass, so a second consumer sees only whatever remains.", | ||
| "example": "from itertools import chain\n\ngroups = (\"py\", \"thon\")\nprint(\"\".join(chain.from_iterable(groups)))\n", | ||
| "starter": "import sys\nfrom itertools import chain\n\ngroups = sys.stdin.read().split()\n# TODO: let the chain consume every inner character group\ncharacters = chain.from_iterable(groups[:1])\nprint(\"\".join(characters))\n", | ||
| "cases": [ | ||
| { | ||
| "input": "o k\n", | ||
| "output": "ok\n" | ||
| }, | ||
| { | ||
| "input": "ab cd\n", | ||
| "output": "abcd\n" | ||
| }, | ||
| { | ||
| "input": "1 23 4\n", | ||
| "output": "1234\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/library/itertools.html#itertools.chain.from_iterable" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-errors", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Recover narrowly from ValueError", | ||
| "body": "Only conversion of the first token belongs inside the try block. Catching ValueError selects the supplied fallback for malformed numeric text without swallowing unrelated programming errors.", | ||
| "example": "candidate = \"oops\"\nfallback = \"-8\"\ntry:\n value = int(candidate)\nexcept ValueError:\n value = int(fallback)\nprint(value)\n", | ||
| "starter": "import sys\n\ncandidate, fallback = sys.stdin.read().split()\ntry:\n value = int(candidate)\nexcept ValueError:\n # TODO: recover from conversion using the supplied token\n value = 0\nprint(value)\n", | ||
| "cases": [ | ||
| { | ||
| "input": "7 12\n", | ||
| "output": "7\n" | ||
| }, | ||
| { | ||
| "input": "bad 12\n", | ||
| "output": "12\n" | ||
| }, | ||
| { | ||
| "input": "-3 9\n", | ||
| "output": "-3\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/tutorial/errors.html#handling-exceptions" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-files-context", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Guaranteed context-manager exit", | ||
| "body": "The with statement calls __enter__, runs the managed block, and invokes __exit__ on normal or exceptional departure. StringIO supplies file-like behavior in memory, allowing the exercise to observe resource reading without filesystem state.", | ||
| "example": "from io import StringIO\n\nwith StringIO(\" Seoul \") as handle:\n text = handle.read().strip()\nprint(text.upper() or \"EMPTY\")\n", | ||
| "starter": "import sys\nfrom io import StringIO\n\nwith StringIO(sys.stdin.read()) as handle:\n text = handle.read()\n # TODO: strip surrounding whitespace before normalizing the managed text\nprint(text.upper() or \"EMPTY\")\n", | ||
| "cases": [ | ||
| { | ||
| "input": "ok\n", | ||
| "output": "OK\n" | ||
| }, | ||
| { | ||
| "input": " Ada \n", | ||
| "output": "ADA\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "EMPTY\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/reference/compound_stmts.html#the-with-statement" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-dataclasses", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Classes and generated data-model methods", | ||
| "body": "A class defines behavior shared by instances, and @dataclass generates field-based construction, representation, and equality. Generated methods do not freeze mutable fields or enforce annotations at runtime; explicit methods still carry domain behavior.", | ||
| "example": "from dataclasses import dataclass\n\n@dataclass\nclass Point:\n x: int\n y: int\n\n def total(self):\n return self.x + self.y\n\npoint = Point(4, 5)\nprint(f\"{point.total()}:{point == Point(4, 5)}\")\n", | ||
| "starter": "import sys\nfrom dataclasses import dataclass\n\n@dataclass\nclass Point:\n x: int\n y: int\n\n def total(self):\n # TODO: combine both instance fields in this behavior\n return self.x\n\nx, y = map(int, sys.stdin.read().split())\npoint = Point(x, y)\nprint(f\"{point.total()}:{point == Point(x, y)}\")\n", | ||
| "cases": [ | ||
| { | ||
| "input": "2 3\n", | ||
| "output": "5:True\n" | ||
| }, | ||
| { | ||
| "input": "-2 3\n", | ||
| "output": "1:True\n" | ||
| }, | ||
| { | ||
| "input": "0 0\n", | ||
| "output": "0:True\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/library/dataclasses.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-typing", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Inspect annotations without runtime enforcement", | ||
| "body": "Annotations describe a callable contract for readers and static tools, but Python does not automatically reject mismatched argument values. get_type_hints resolves the recorded annotations; the function body must still perform the promised calculation.", | ||
| "example": "from typing import Iterable, get_type_hints\n\ndef head(words: Iterable[str]) -> str:\n return next(iter(words), \"missing\")\n\nhints = get_type_hints(head)\nprint(hints[\"return\"].__name__, head([\"elm\", \"oak\"]))\n", | ||
| "starter": "import sys\nfrom typing import Iterable, get_type_hints\n\ndef total(values: Iterable[int]) -> int:\n # TODO: total the complete iterable instead of returning only its first item\n return next(iter(values), 0)\n\nvalues = [int(token) for token in sys.stdin.read().split()]\nhints = get_type_hints(total)\nlabel = \"typed\" if hints.get(\"return\") is int and \"values\" in hints else \"untyped\"\nprint(f\"{label}:{total(values)}\")\n", | ||
| "cases": [ | ||
| { | ||
| "input": "2 3\n", | ||
| "output": "typed:5\n" | ||
| }, | ||
| { | ||
| "input": "-2\n", | ||
| "output": "typed:-2\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "typed:0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/library/typing.html#typing.get_type_hints" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "py-testing-assert", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "capstone", | ||
| "level": "advanced", | ||
| "title": "Capstone: deterministic word ranking", | ||
| "body": "The capstone combines typed functions, Counter, and an explicit sort key to select the highest frequency with an alphabetical tie-break. Internal assert statements document development invariants, but Python removes assert bytecode under -O, so assertions must never protect input, security, or data integrity.", | ||
| "example": "from collections import Counter\nfrom typing import Iterable\n\ndef most_common(words: Iterable[str]) -> tuple[str, int]:\n materialized = list(words)\n counts = Counter(materialized)\n assert sum(counts.values()) == len(materialized)\n if not counts:\n return \"empty\", 0\n return sorted(counts.items(), key=lambda item: (-item[1], item[0]))[0]\n\nword, count = most_common(\"pear plum pear apple plum\".split())\nprint(f\"{word}:{count}\")\n", | ||
| "starter": "import sys\nfrom collections import Counter\nfrom typing import Iterable\n\ndef most_common(words: Iterable[str]) -> tuple[str, int]:\n materialized = list(words)\n counts = Counter(materialized)\n assert sum(counts.values()) == len(materialized)\n if not counts:\n return \"empty\", 0\n # TODO: make equal frequencies deterministic with the required ranking rule\n return counts.most_common(1)[0]\n\nword, count = most_common(sys.stdin.read().split())\nprint(f\"{word}:{count}\")\n", | ||
| "cases": [ | ||
| { | ||
| "input": "red blue red\n", | ||
| "output": "red:2\n" | ||
| }, | ||
| { | ||
| "input": "b a b a\n", | ||
| "output": "a:2\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "empty:0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://docs.python.org/3.12/reference/simple_stmts.html#the-assert-statement" | ||
| ] | ||
| } | ||
| ] | ||
| } |
| { | ||
| "schema_version": 1, | ||
| "content_version": "0.2.0", | ||
| "review_profiles": { | ||
| "en-python": { | ||
| "author": { | ||
| "identity": "/root", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "blind_verifier": { | ||
| "identity": "/root/task6_whole_reviewer", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "disagreements": [], | ||
| "resolution": "approved" | ||
| }, | ||
| "en-ts": { | ||
| "author": { | ||
| "identity": "/root", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "blind_verifier": { | ||
| "identity": "/root/task6_whole_reviewer", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "disagreements": [], | ||
| "resolution": "approved" | ||
| }, | ||
| "en-java": { | ||
| "author": { | ||
| "identity": "/root", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "blind_verifier": { | ||
| "identity": "/root/task6_whole_reviewer", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "disagreements": [], | ||
| "resolution": "approved" | ||
| }, | ||
| "en-rust": { | ||
| "author": { | ||
| "identity": "/root", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "blind_verifier": { | ||
| "identity": "/root/task6_whole_reviewer", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "disagreements": [], | ||
| "resolution": "approved" | ||
| }, | ||
| "ko": { | ||
| "author": { | ||
| "identity": "/root/task7_korean_audit", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "blind_verifier": { | ||
| "identity": "/root/task6_whole_reviewer", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "disagreements": [], | ||
| "resolution": "approved" | ||
| }, | ||
| "ja": { | ||
| "author": { | ||
| "identity": "/root/task7_japanese_audit", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "blind_verifier": { | ||
| "identity": "/root/task6_rust_reviewer", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "disagreements": [], | ||
| "resolution": "approved" | ||
| }, | ||
| "zh": { | ||
| "author": { | ||
| "identity": "/root/task8_chinese_audit", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "blind_verifier": { | ||
| "identity": "/root/task8_spanish_audit", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "disagreements": [], | ||
| "resolution": "approved" | ||
| }, | ||
| "es": { | ||
| "author": { | ||
| "identity": "/root/task8_spanish_audit", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "blind_verifier": { | ||
| "identity": "/root/task8_chinese_audit", | ||
| "verdict": "approved", | ||
| "open_high_severity_findings": 0 | ||
| }, | ||
| "disagreements": [], | ||
| "resolution": "approved" | ||
| } | ||
| }, | ||
| "sources": { | ||
| "python": [ | ||
| "https://docs.python.org/3.12/howto/sorting.html#sort-stability-and-complex-sorts", | ||
| "https://docs.python.org/3.12/library/asyncio-runner.html#asyncio.run", | ||
| "https://docs.python.org/3.12/library/collections.html#collections.Counter", | ||
| "https://docs.python.org/3.12/library/collections.html#collections.deque", | ||
| "https://docs.python.org/3.12/library/dataclasses.html", | ||
| "https://docs.python.org/3.12/library/functions.html#print", | ||
| "https://docs.python.org/3.12/library/functools.html#functools.wraps", | ||
| "https://docs.python.org/3.12/library/itertools.html#itertools.chain.from_iterable", | ||
| "https://docs.python.org/3.12/library/pathlib.html#pathlib.PurePath.stem", | ||
| "https://docs.python.org/3.12/library/stdtypes.html#text-sequence-type-str", | ||
| "https://docs.python.org/3.12/library/sys.html#sys.stdin", | ||
| "https://docs.python.org/3.12/library/typing.html#typing.get_type_hints", | ||
| "https://docs.python.org/3.12/reference/compound_stmts.html#the-with-statement", | ||
| "https://docs.python.org/3.12/reference/executionmodel.html#resolution-of-names", | ||
| "https://docs.python.org/3.12/reference/expressions.html#binary-arithmetic-operations", | ||
| "https://docs.python.org/3.12/reference/expressions.html#generator-iterator-methods", | ||
| "https://docs.python.org/3.12/reference/simple_stmts.html#assignment-statements", | ||
| "https://docs.python.org/3.12/reference/simple_stmts.html#the-assert-statement", | ||
| "https://docs.python.org/3.12/tutorial/controlflow.html#defining-functions", | ||
| "https://docs.python.org/3.12/tutorial/controlflow.html#the-range-function", | ||
| "https://docs.python.org/3.12/tutorial/datastructures.html#dictionaries", | ||
| "https://docs.python.org/3.12/tutorial/datastructures.html#list-comprehensions", | ||
| "https://docs.python.org/3.12/tutorial/datastructures.html#tuples-and-sequences", | ||
| "https://docs.python.org/3.12/tutorial/errors.html#handling-exceptions", | ||
| "https://docs.python.org/3.12/tutorial/modules.html" | ||
| ], | ||
| "ts": [ | ||
| "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN", | ||
| "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt", | ||
| "https://nodejs.org/docs/latest-v22.x/api/fs.html#fsreadfilesyncpath-options", | ||
| "https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout", | ||
| "https://nodejs.org/docs/latest-v22.x/api/typescript.html", | ||
| "https://nodejs.org/docs/latest-v22.x/api/typescript.html#determining-module-system", | ||
| "https://nodejs.org/docs/latest-v22.x/api/typescript.html#type-stripping", | ||
| "https://www.typescriptlang.org/docs/handbook/2/classes.html#member-visibility", | ||
| "https://www.typescriptlang.org/docs/handbook/2/conditional-types.html", | ||
| "https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#arrays", | ||
| "https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types", | ||
| "https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#strictnullchecks-on", | ||
| "https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean", | ||
| "https://www.typescriptlang.org/docs/handbook/2/functions.html", | ||
| "https://www.typescriptlang.org/docs/handbook/2/generics.html", | ||
| "https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html", | ||
| "https://www.typescriptlang.org/docs/handbook/2/keyof-types.html", | ||
| "https://www.typescriptlang.org/docs/handbook/2/mapped-types.html", | ||
| "https://www.typescriptlang.org/docs/handbook/2/modules.html", | ||
| "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#control-flow-analysis", | ||
| "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions", | ||
| "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#typeof-type-guards", | ||
| "https://www.typescriptlang.org/docs/handbook/2/objects.html", | ||
| "https://www.typescriptlang.org/docs/handbook/2/objects.html#interface-extension-vs-intersection", | ||
| "https://www.typescriptlang.org/docs/handbook/2/objects.html#readonly-properties", | ||
| "https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types", | ||
| "https://www.typescriptlang.org/docs/handbook/iterators-and-generators.html#iterables", | ||
| "https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-7.html#asyncawait-support-in-es6-targets-node-v4", | ||
| "https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions", | ||
| "https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-4.html#defaulting-to-the-unknown-type-in-catch-variables---useunknownincatchvariables", | ||
| "https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator", | ||
| "https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-9.html", | ||
| "https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype", | ||
| "https://www.typescriptlang.org/docs/handbook/variable-declarations.html" | ||
| ], | ||
| "java": [ | ||
| "https://dev.java/learn/", | ||
| "https://dev.java/learn/api/collections-framework/", | ||
| "https://dev.java/learn/api/streams/", | ||
| "https://dev.java/learn/api/streams/optionals/", | ||
| "https://dev.java/learn/classes-objects/creating-objects/", | ||
| "https://dev.java/learn/classes-objects/more-on-classes/", | ||
| "https://dev.java/learn/generics/", | ||
| "https://dev.java/learn/inheritance/", | ||
| "https://dev.java/learn/language-basics/controlling-flow/", | ||
| "https://dev.java/learn/numbers-strings/numbers/", | ||
| "https://dev.java/learn/records/", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/InputStream.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/PrintStream.html#println(java.lang.String)", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/AssertionError.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/AutoCloseable.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/NumberFormatException.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#equals(java.lang.Object)", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Record.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/String.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/annotation/Retention.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/Method.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/charset/StandardCharsets.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ArrayList.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Comparator.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashSet.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html", | ||
| "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/IntStream.html#map(java.util.function.IntUnaryOperator)", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-11.html", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.10", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.11", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.20.3", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.12", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.17", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-3.html#jls-3.10.5", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.5", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-6.html#jls-6.6", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-7.html#jls-7.5", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.1.4", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.3.1.1", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.4", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.8", | ||
| "https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html" | ||
| ], | ||
| "rust": [ | ||
| "https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html", | ||
| "https://doc.rust-lang.org/book/ch03-03-how-functions-work.html", | ||
| "https://doc.rust-lang.org/book/ch03-05-control-flow.html", | ||
| "https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html", | ||
| "https://doc.rust-lang.org/book/ch04-03-slices.html", | ||
| "https://doc.rust-lang.org/book/ch05-03-method-syntax.html", | ||
| "https://doc.rust-lang.org/book/ch06-00-enums.html", | ||
| "https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html", | ||
| "https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html", | ||
| "https://doc.rust-lang.org/book/ch10-01-syntax.html", | ||
| "https://doc.rust-lang.org/book/ch10-02-traits.html", | ||
| "https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html", | ||
| "https://doc.rust-lang.org/book/ch11-03-test-organization.html", | ||
| "https://doc.rust-lang.org/book/ch15-01-box.html", | ||
| "https://doc.rust-lang.org/book/ch15-05-interior-mutability.html", | ||
| "https://doc.rust-lang.org/book/ch16-03-shared-state.html", | ||
| "https://doc.rust-lang.org/book/ch17-01-futures-and-syntax.html", | ||
| "https://doc.rust-lang.org/book/ch18-02-trait-objects.html", | ||
| "https://doc.rust-lang.org/cargo/commands/index.html", | ||
| "https://doc.rust-lang.org/cargo/reference/workspaces.html", | ||
| "https://doc.rust-lang.org/edition-guide/rust-2024/index.html", | ||
| "https://doc.rust-lang.org/edition-guide/rust-2024/unsafe-op-in-unsafe-fn.html", | ||
| "https://doc.rust-lang.org/reference/expressions/match-expr.html", | ||
| "https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators", | ||
| "https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator", | ||
| "https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility", | ||
| "https://doc.rust-lang.org/reference/macros-by-example.html", | ||
| "https://doc.rust-lang.org/reference/paths.html#path-qualifiers", | ||
| "https://doc.rust-lang.org/reference/trait-bounds.html#lifetime-bounds", | ||
| "https://doc.rust-lang.org/reference/types/pointer.html#shared-references-", | ||
| "https://doc.rust-lang.org/reference/unsafe-blocks.html", | ||
| "https://doc.rust-lang.org/reference/visibility-and-privacy.html", | ||
| "https://doc.rust-lang.org/std/boxed/struct.Box.html", | ||
| "https://doc.rust-lang.org/std/cell/struct.RefCell.html", | ||
| "https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry", | ||
| "https://doc.rust-lang.org/std/future/fn.ready.html", | ||
| "https://doc.rust-lang.org/std/io/trait.Read.html#method.read_to_string", | ||
| "https://doc.rust-lang.org/std/iter/trait.Iterator.html", | ||
| "https://doc.rust-lang.org/std/macro.assert_eq.html", | ||
| "https://doc.rust-lang.org/std/macro.println.html", | ||
| "https://doc.rust-lang.org/std/marker/trait.Copy.html", | ||
| "https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html", | ||
| "https://doc.rust-lang.org/std/option/enum.Option.html", | ||
| "https://doc.rust-lang.org/std/primitive.str.html", | ||
| "https://doc.rust-lang.org/std/result/enum.Result.html", | ||
| "https://doc.rust-lang.org/std/str/struct.Chars.html", | ||
| "https://doc.rust-lang.org/std/sync/struct.Arc.html", | ||
| "https://doc.rust-lang.org/std/sync/struct.Mutex.html", | ||
| "https://doc.rust-lang.org/std/task/trait.Wake.html", | ||
| "https://doc.rust-lang.org/std/thread/fn.spawn.html", | ||
| "https://doc.rust-lang.org/std/thread/struct.JoinHandle.html#method.join", | ||
| "https://doc.rust-lang.org/std/vec/struct.Vec.html" | ||
| ] | ||
| }, | ||
| "catalogs": [ | ||
| { | ||
| "path": "assets/lessons/python/en.json", | ||
| "programming_language": "python", | ||
| "ui_language": "en", | ||
| "sha256": "065da2122928ece00f8807dfb42ccab69196a951b04e3c2c79c3e5842ebd5254", | ||
| "lesson_count": 25, | ||
| "lesson_ids": [ | ||
| "py-output", | ||
| "py-input", | ||
| "py-variables", | ||
| "py-numbers", | ||
| "py-tuples-sets", | ||
| "py-strings", | ||
| "py-pathlib", | ||
| "py-control-flow", | ||
| "py-functions", | ||
| "py-modules-imports", | ||
| "py-lambdas-closures", | ||
| "py-decorators", | ||
| "py-async", | ||
| "py-lists-dicts", | ||
| "py-sorting-keys", | ||
| "py-counter-defaultdict", | ||
| "py-deque", | ||
| "py-comprehensions", | ||
| "py-generators", | ||
| "py-itertools", | ||
| "py-errors", | ||
| "py-files-context", | ||
| "py-dataclasses", | ||
| "py-typing", | ||
| "py-testing-assert" | ||
| ], | ||
| "review_profile": "en-python" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/python/ko.json", | ||
| "programming_language": "python", | ||
| "ui_language": "ko", | ||
| "sha256": "c630a4fce1c803dd7e3b31ddf78b317278baa23aae1fe419e03f103853204e27", | ||
| "lesson_count": 25, | ||
| "lesson_ids": [ | ||
| "py-output", | ||
| "py-input", | ||
| "py-variables", | ||
| "py-numbers", | ||
| "py-tuples-sets", | ||
| "py-strings", | ||
| "py-pathlib", | ||
| "py-control-flow", | ||
| "py-functions", | ||
| "py-modules-imports", | ||
| "py-lambdas-closures", | ||
| "py-decorators", | ||
| "py-async", | ||
| "py-lists-dicts", | ||
| "py-sorting-keys", | ||
| "py-counter-defaultdict", | ||
| "py-deque", | ||
| "py-comprehensions", | ||
| "py-generators", | ||
| "py-itertools", | ||
| "py-errors", | ||
| "py-files-context", | ||
| "py-dataclasses", | ||
| "py-typing", | ||
| "py-testing-assert" | ||
| ], | ||
| "review_profile": "ko" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/python/ja.json", | ||
| "programming_language": "python", | ||
| "ui_language": "ja", | ||
| "sha256": "4e5028c404bc618abeb3f34d347253f20aad0eece098a729d11ab0385393757d", | ||
| "lesson_count": 25, | ||
| "lesson_ids": [ | ||
| "py-output", | ||
| "py-input", | ||
| "py-variables", | ||
| "py-numbers", | ||
| "py-tuples-sets", | ||
| "py-strings", | ||
| "py-pathlib", | ||
| "py-control-flow", | ||
| "py-functions", | ||
| "py-modules-imports", | ||
| "py-lambdas-closures", | ||
| "py-decorators", | ||
| "py-async", | ||
| "py-lists-dicts", | ||
| "py-sorting-keys", | ||
| "py-counter-defaultdict", | ||
| "py-deque", | ||
| "py-comprehensions", | ||
| "py-generators", | ||
| "py-itertools", | ||
| "py-errors", | ||
| "py-files-context", | ||
| "py-dataclasses", | ||
| "py-typing", | ||
| "py-testing-assert" | ||
| ], | ||
| "review_profile": "ja" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/python/zh.json", | ||
| "programming_language": "python", | ||
| "ui_language": "zh", | ||
| "sha256": "75175361d061d453cf8ce211fc1c7490891a7de2f46950e7dd93f190707837bd", | ||
| "lesson_count": 25, | ||
| "lesson_ids": [ | ||
| "py-output", | ||
| "py-input", | ||
| "py-variables", | ||
| "py-numbers", | ||
| "py-tuples-sets", | ||
| "py-strings", | ||
| "py-pathlib", | ||
| "py-control-flow", | ||
| "py-functions", | ||
| "py-modules-imports", | ||
| "py-lambdas-closures", | ||
| "py-decorators", | ||
| "py-async", | ||
| "py-lists-dicts", | ||
| "py-sorting-keys", | ||
| "py-counter-defaultdict", | ||
| "py-deque", | ||
| "py-comprehensions", | ||
| "py-generators", | ||
| "py-itertools", | ||
| "py-errors", | ||
| "py-files-context", | ||
| "py-dataclasses", | ||
| "py-typing", | ||
| "py-testing-assert" | ||
| ], | ||
| "review_profile": "zh" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/python/es.json", | ||
| "programming_language": "python", | ||
| "ui_language": "es", | ||
| "sha256": "2ea3ddbc953459fd1c017b92139d4d1ff98ca26ee61a7d5f87eb6454cedeb4c2", | ||
| "lesson_count": 25, | ||
| "lesson_ids": [ | ||
| "py-output", | ||
| "py-input", | ||
| "py-variables", | ||
| "py-numbers", | ||
| "py-tuples-sets", | ||
| "py-strings", | ||
| "py-pathlib", | ||
| "py-control-flow", | ||
| "py-functions", | ||
| "py-modules-imports", | ||
| "py-lambdas-closures", | ||
| "py-decorators", | ||
| "py-async", | ||
| "py-lists-dicts", | ||
| "py-sorting-keys", | ||
| "py-counter-defaultdict", | ||
| "py-deque", | ||
| "py-comprehensions", | ||
| "py-generators", | ||
| "py-itertools", | ||
| "py-errors", | ||
| "py-files-context", | ||
| "py-dataclasses", | ||
| "py-typing", | ||
| "py-testing-assert" | ||
| ], | ||
| "review_profile": "es" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/typescript/en.json", | ||
| "programming_language": "ts", | ||
| "ui_language": "en", | ||
| "sha256": "0768c19ce3079371de7fbdd8dd24c999f4a92f84d0c7b09784a95e2ca1be096c", | ||
| "lesson_count": 28, | ||
| "lesson_ids": [ | ||
| "ts-output", | ||
| "ts-input", | ||
| "ts-primitives", | ||
| "ts-let-const", | ||
| "ts-strings-templates", | ||
| "ts-arrays-tuples", | ||
| "ts-iterables", | ||
| "ts-array-methods", | ||
| "ts-objects", | ||
| "ts-classes", | ||
| "ts-readonly", | ||
| "ts-control-flow", | ||
| "ts-functions", | ||
| "ts-union-narrowing", | ||
| "ts-literal-types", | ||
| "ts-optional-nullish", | ||
| "ts-interfaces-aliases", | ||
| "ts-generics", | ||
| "ts-keyof-typeof", | ||
| "ts-indexed-access", | ||
| "ts-mapped-types", | ||
| "ts-conditional-types", | ||
| "ts-utility-types", | ||
| "ts-satisfies-as-const", | ||
| "ts-discriminated-unions", | ||
| "ts-async-promise", | ||
| "ts-modules", | ||
| "ts-error-handling" | ||
| ], | ||
| "review_profile": "en-ts" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/typescript/ko.json", | ||
| "programming_language": "ts", | ||
| "ui_language": "ko", | ||
| "sha256": "0855b1c55e1e9ed729f2d2af5037fc59db9f45e13bed2fc2f9c910b302eb2c27", | ||
| "lesson_count": 28, | ||
| "lesson_ids": [ | ||
| "ts-output", | ||
| "ts-input", | ||
| "ts-primitives", | ||
| "ts-let-const", | ||
| "ts-strings-templates", | ||
| "ts-arrays-tuples", | ||
| "ts-iterables", | ||
| "ts-array-methods", | ||
| "ts-objects", | ||
| "ts-classes", | ||
| "ts-readonly", | ||
| "ts-control-flow", | ||
| "ts-functions", | ||
| "ts-union-narrowing", | ||
| "ts-literal-types", | ||
| "ts-optional-nullish", | ||
| "ts-interfaces-aliases", | ||
| "ts-generics", | ||
| "ts-keyof-typeof", | ||
| "ts-indexed-access", | ||
| "ts-mapped-types", | ||
| "ts-conditional-types", | ||
| "ts-utility-types", | ||
| "ts-satisfies-as-const", | ||
| "ts-discriminated-unions", | ||
| "ts-async-promise", | ||
| "ts-modules", | ||
| "ts-error-handling" | ||
| ], | ||
| "review_profile": "ko" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/typescript/ja.json", | ||
| "programming_language": "ts", | ||
| "ui_language": "ja", | ||
| "sha256": "4d69fc45d80e9e447709628f230cb5afad3feb25b4a28adb409cdb2d33cc4da0", | ||
| "lesson_count": 28, | ||
| "lesson_ids": [ | ||
| "ts-output", | ||
| "ts-input", | ||
| "ts-primitives", | ||
| "ts-let-const", | ||
| "ts-strings-templates", | ||
| "ts-arrays-tuples", | ||
| "ts-iterables", | ||
| "ts-array-methods", | ||
| "ts-objects", | ||
| "ts-classes", | ||
| "ts-readonly", | ||
| "ts-control-flow", | ||
| "ts-functions", | ||
| "ts-union-narrowing", | ||
| "ts-literal-types", | ||
| "ts-optional-nullish", | ||
| "ts-interfaces-aliases", | ||
| "ts-generics", | ||
| "ts-keyof-typeof", | ||
| "ts-indexed-access", | ||
| "ts-mapped-types", | ||
| "ts-conditional-types", | ||
| "ts-utility-types", | ||
| "ts-satisfies-as-const", | ||
| "ts-discriminated-unions", | ||
| "ts-async-promise", | ||
| "ts-modules", | ||
| "ts-error-handling" | ||
| ], | ||
| "review_profile": "ja" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/typescript/zh.json", | ||
| "programming_language": "ts", | ||
| "ui_language": "zh", | ||
| "sha256": "ec9ce0c33bfb2734d90e6a5903a675759b68250206464134c9fe6810c1c08644", | ||
| "lesson_count": 28, | ||
| "lesson_ids": [ | ||
| "ts-output", | ||
| "ts-input", | ||
| "ts-primitives", | ||
| "ts-let-const", | ||
| "ts-strings-templates", | ||
| "ts-arrays-tuples", | ||
| "ts-iterables", | ||
| "ts-array-methods", | ||
| "ts-objects", | ||
| "ts-classes", | ||
| "ts-readonly", | ||
| "ts-control-flow", | ||
| "ts-functions", | ||
| "ts-union-narrowing", | ||
| "ts-literal-types", | ||
| "ts-optional-nullish", | ||
| "ts-interfaces-aliases", | ||
| "ts-generics", | ||
| "ts-keyof-typeof", | ||
| "ts-indexed-access", | ||
| "ts-mapped-types", | ||
| "ts-conditional-types", | ||
| "ts-utility-types", | ||
| "ts-satisfies-as-const", | ||
| "ts-discriminated-unions", | ||
| "ts-async-promise", | ||
| "ts-modules", | ||
| "ts-error-handling" | ||
| ], | ||
| "review_profile": "zh" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/typescript/es.json", | ||
| "programming_language": "ts", | ||
| "ui_language": "es", | ||
| "sha256": "5fef07dc38af83e8918c48cdaf519719f4d6cba403b8aeddb415b8f497671e11", | ||
| "lesson_count": 28, | ||
| "lesson_ids": [ | ||
| "ts-output", | ||
| "ts-input", | ||
| "ts-primitives", | ||
| "ts-let-const", | ||
| "ts-strings-templates", | ||
| "ts-arrays-tuples", | ||
| "ts-iterables", | ||
| "ts-array-methods", | ||
| "ts-objects", | ||
| "ts-classes", | ||
| "ts-readonly", | ||
| "ts-control-flow", | ||
| "ts-functions", | ||
| "ts-union-narrowing", | ||
| "ts-literal-types", | ||
| "ts-optional-nullish", | ||
| "ts-interfaces-aliases", | ||
| "ts-generics", | ||
| "ts-keyof-typeof", | ||
| "ts-indexed-access", | ||
| "ts-mapped-types", | ||
| "ts-conditional-types", | ||
| "ts-utility-types", | ||
| "ts-satisfies-as-const", | ||
| "ts-discriminated-unions", | ||
| "ts-async-promise", | ||
| "ts-modules", | ||
| "ts-error-handling" | ||
| ], | ||
| "review_profile": "es" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/java/en.json", | ||
| "programming_language": "java", | ||
| "ui_language": "en", | ||
| "sha256": "1076d83194220b01ca2a3eb6bd9ef04eec7458eea15a0d6d7c2b5a5498b36e0c", | ||
| "lesson_count": 28, | ||
| "lesson_ids": [ | ||
| "java-output", | ||
| "java-input", | ||
| "java-variables-types", | ||
| "java-numbers-operators", | ||
| "java-strings", | ||
| "java-control-flow", | ||
| "java-enum-switch", | ||
| "java-methods", | ||
| "java-overloading-varargs", | ||
| "java-packages-imports", | ||
| "java-arrays-collections", | ||
| "java-generics", | ||
| "java-streams-lambdas", | ||
| "java-comparators-sorting", | ||
| "java-classes-objects", | ||
| "java-constructors", | ||
| "java-encapsulation", | ||
| "java-static-members", | ||
| "java-interfaces", | ||
| "java-inheritance-composition", | ||
| "java-exceptions", | ||
| "java-optional", | ||
| "java-try-with-resources", | ||
| "java-equality-hashcode", | ||
| "java-records", | ||
| "java-annotations", | ||
| "java-sealed-classes", | ||
| "java-testing-assert" | ||
| ], | ||
| "review_profile": "en-java" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/java/ko.json", | ||
| "programming_language": "java", | ||
| "ui_language": "ko", | ||
| "sha256": "3336e5d9e6d907eb61248327a6351796cf54792ae8da90571862f426818224ca", | ||
| "lesson_count": 28, | ||
| "lesson_ids": [ | ||
| "java-output", | ||
| "java-input", | ||
| "java-variables-types", | ||
| "java-numbers-operators", | ||
| "java-strings", | ||
| "java-control-flow", | ||
| "java-enum-switch", | ||
| "java-methods", | ||
| "java-overloading-varargs", | ||
| "java-packages-imports", | ||
| "java-arrays-collections", | ||
| "java-generics", | ||
| "java-streams-lambdas", | ||
| "java-comparators-sorting", | ||
| "java-classes-objects", | ||
| "java-constructors", | ||
| "java-encapsulation", | ||
| "java-static-members", | ||
| "java-interfaces", | ||
| "java-inheritance-composition", | ||
| "java-exceptions", | ||
| "java-optional", | ||
| "java-try-with-resources", | ||
| "java-equality-hashcode", | ||
| "java-records", | ||
| "java-annotations", | ||
| "java-sealed-classes", | ||
| "java-testing-assert" | ||
| ], | ||
| "review_profile": "ko" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/java/ja.json", | ||
| "programming_language": "java", | ||
| "ui_language": "ja", | ||
| "sha256": "51d1359b6d2b25882da2d0a6ed5d0e23f0eb7e93d2f4304b909840c52c8a53ee", | ||
| "lesson_count": 28, | ||
| "lesson_ids": [ | ||
| "java-output", | ||
| "java-input", | ||
| "java-variables-types", | ||
| "java-numbers-operators", | ||
| "java-strings", | ||
| "java-control-flow", | ||
| "java-enum-switch", | ||
| "java-methods", | ||
| "java-overloading-varargs", | ||
| "java-packages-imports", | ||
| "java-arrays-collections", | ||
| "java-generics", | ||
| "java-streams-lambdas", | ||
| "java-comparators-sorting", | ||
| "java-classes-objects", | ||
| "java-constructors", | ||
| "java-encapsulation", | ||
| "java-static-members", | ||
| "java-interfaces", | ||
| "java-inheritance-composition", | ||
| "java-exceptions", | ||
| "java-optional", | ||
| "java-try-with-resources", | ||
| "java-equality-hashcode", | ||
| "java-records", | ||
| "java-annotations", | ||
| "java-sealed-classes", | ||
| "java-testing-assert" | ||
| ], | ||
| "review_profile": "ja" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/java/zh.json", | ||
| "programming_language": "java", | ||
| "ui_language": "zh", | ||
| "sha256": "691c3a3a7442f40750309e2caed636c0209827fd29cd6a503c1284b7ac466076", | ||
| "lesson_count": 28, | ||
| "lesson_ids": [ | ||
| "java-output", | ||
| "java-input", | ||
| "java-variables-types", | ||
| "java-numbers-operators", | ||
| "java-strings", | ||
| "java-control-flow", | ||
| "java-enum-switch", | ||
| "java-methods", | ||
| "java-overloading-varargs", | ||
| "java-packages-imports", | ||
| "java-arrays-collections", | ||
| "java-generics", | ||
| "java-streams-lambdas", | ||
| "java-comparators-sorting", | ||
| "java-classes-objects", | ||
| "java-constructors", | ||
| "java-encapsulation", | ||
| "java-static-members", | ||
| "java-interfaces", | ||
| "java-inheritance-composition", | ||
| "java-exceptions", | ||
| "java-optional", | ||
| "java-try-with-resources", | ||
| "java-equality-hashcode", | ||
| "java-records", | ||
| "java-annotations", | ||
| "java-sealed-classes", | ||
| "java-testing-assert" | ||
| ], | ||
| "review_profile": "zh" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/java/es.json", | ||
| "programming_language": "java", | ||
| "ui_language": "es", | ||
| "sha256": "01967773a754ef7065936a33d93699a8fee891b6649c004a3f172712f87b140d", | ||
| "lesson_count": 28, | ||
| "lesson_ids": [ | ||
| "java-output", | ||
| "java-input", | ||
| "java-variables-types", | ||
| "java-numbers-operators", | ||
| "java-strings", | ||
| "java-control-flow", | ||
| "java-enum-switch", | ||
| "java-methods", | ||
| "java-overloading-varargs", | ||
| "java-packages-imports", | ||
| "java-arrays-collections", | ||
| "java-generics", | ||
| "java-streams-lambdas", | ||
| "java-comparators-sorting", | ||
| "java-classes-objects", | ||
| "java-constructors", | ||
| "java-encapsulation", | ||
| "java-static-members", | ||
| "java-interfaces", | ||
| "java-inheritance-composition", | ||
| "java-exceptions", | ||
| "java-optional", | ||
| "java-try-with-resources", | ||
| "java-equality-hashcode", | ||
| "java-records", | ||
| "java-annotations", | ||
| "java-sealed-classes", | ||
| "java-testing-assert" | ||
| ], | ||
| "review_profile": "es" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/rust/en.json", | ||
| "programming_language": "rust", | ||
| "ui_language": "en", | ||
| "sha256": "e849463ffaed47926efd2ebf9a16d896476844dee19f6d9522d4e5f9e2943bbd", | ||
| "lesson_count": 29, | ||
| "lesson_ids": [ | ||
| "rust-output", | ||
| "rust-variables", | ||
| "rust-numbers-tuples", | ||
| "rust-input", | ||
| "rust-vec-hashmap", | ||
| "rust-control-flow", | ||
| "rust-iterators", | ||
| "rust-functions", | ||
| "rust-modules-use", | ||
| "rust-generics", | ||
| "rust-traits", | ||
| "rust-macros", | ||
| "rust-cargo-workspaces", | ||
| "rust-ownership", | ||
| "rust-smart-pointers", | ||
| "rust-interior-mutability", | ||
| "rust-strings", | ||
| "rust-borrowing-slices", | ||
| "rust-lifetimes", | ||
| "rust-traits-lifetimes", | ||
| "rust-unsafe", | ||
| "rust-structs-impl", | ||
| "rust-enum-match", | ||
| "rust-option", | ||
| "rust-result", | ||
| "rust-testing", | ||
| "rust-async-await", | ||
| "rust-concurrency", | ||
| "rust-shared-state" | ||
| ], | ||
| "review_profile": "en-rust" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/rust/ko.json", | ||
| "programming_language": "rust", | ||
| "ui_language": "ko", | ||
| "sha256": "ebf18e50e6c1a471f557f2145f9a273993ecc93005439bac43328a986b2bb48c", | ||
| "lesson_count": 29, | ||
| "lesson_ids": [ | ||
| "rust-output", | ||
| "rust-variables", | ||
| "rust-numbers-tuples", | ||
| "rust-input", | ||
| "rust-vec-hashmap", | ||
| "rust-control-flow", | ||
| "rust-iterators", | ||
| "rust-functions", | ||
| "rust-modules-use", | ||
| "rust-generics", | ||
| "rust-traits", | ||
| "rust-macros", | ||
| "rust-cargo-workspaces", | ||
| "rust-ownership", | ||
| "rust-smart-pointers", | ||
| "rust-interior-mutability", | ||
| "rust-strings", | ||
| "rust-borrowing-slices", | ||
| "rust-lifetimes", | ||
| "rust-traits-lifetimes", | ||
| "rust-unsafe", | ||
| "rust-structs-impl", | ||
| "rust-enum-match", | ||
| "rust-option", | ||
| "rust-result", | ||
| "rust-testing", | ||
| "rust-async-await", | ||
| "rust-concurrency", | ||
| "rust-shared-state" | ||
| ], | ||
| "review_profile": "ko" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/rust/ja.json", | ||
| "programming_language": "rust", | ||
| "ui_language": "ja", | ||
| "sha256": "55f16ee8f4e22bfef5a03e5f408fe0c4f4ef2b605f238f62eef7f68715ef591f", | ||
| "lesson_count": 29, | ||
| "lesson_ids": [ | ||
| "rust-output", | ||
| "rust-variables", | ||
| "rust-numbers-tuples", | ||
| "rust-input", | ||
| "rust-vec-hashmap", | ||
| "rust-control-flow", | ||
| "rust-iterators", | ||
| "rust-functions", | ||
| "rust-modules-use", | ||
| "rust-generics", | ||
| "rust-traits", | ||
| "rust-macros", | ||
| "rust-cargo-workspaces", | ||
| "rust-ownership", | ||
| "rust-smart-pointers", | ||
| "rust-interior-mutability", | ||
| "rust-strings", | ||
| "rust-borrowing-slices", | ||
| "rust-lifetimes", | ||
| "rust-traits-lifetimes", | ||
| "rust-unsafe", | ||
| "rust-structs-impl", | ||
| "rust-enum-match", | ||
| "rust-option", | ||
| "rust-result", | ||
| "rust-testing", | ||
| "rust-async-await", | ||
| "rust-concurrency", | ||
| "rust-shared-state" | ||
| ], | ||
| "review_profile": "ja" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/rust/zh.json", | ||
| "programming_language": "rust", | ||
| "ui_language": "zh", | ||
| "sha256": "74c53b5afa55f91f8018a49d468e826f736a825f7be42205a29fe1d2b4bcac9a", | ||
| "lesson_count": 29, | ||
| "lesson_ids": [ | ||
| "rust-output", | ||
| "rust-variables", | ||
| "rust-numbers-tuples", | ||
| "rust-input", | ||
| "rust-vec-hashmap", | ||
| "rust-control-flow", | ||
| "rust-iterators", | ||
| "rust-functions", | ||
| "rust-modules-use", | ||
| "rust-generics", | ||
| "rust-traits", | ||
| "rust-macros", | ||
| "rust-cargo-workspaces", | ||
| "rust-ownership", | ||
| "rust-smart-pointers", | ||
| "rust-interior-mutability", | ||
| "rust-strings", | ||
| "rust-borrowing-slices", | ||
| "rust-lifetimes", | ||
| "rust-traits-lifetimes", | ||
| "rust-unsafe", | ||
| "rust-structs-impl", | ||
| "rust-enum-match", | ||
| "rust-option", | ||
| "rust-result", | ||
| "rust-testing", | ||
| "rust-async-await", | ||
| "rust-concurrency", | ||
| "rust-shared-state" | ||
| ], | ||
| "review_profile": "zh" | ||
| }, | ||
| { | ||
| "path": "assets/lessons/rust/es.json", | ||
| "programming_language": "rust", | ||
| "ui_language": "es", | ||
| "sha256": "ec8af7af9afcc10f1a344b431f9027d6b160b47c2124703f1df4c821844843b1", | ||
| "lesson_count": 29, | ||
| "lesson_ids": [ | ||
| "rust-output", | ||
| "rust-variables", | ||
| "rust-numbers-tuples", | ||
| "rust-input", | ||
| "rust-vec-hashmap", | ||
| "rust-control-flow", | ||
| "rust-iterators", | ||
| "rust-functions", | ||
| "rust-modules-use", | ||
| "rust-generics", | ||
| "rust-traits", | ||
| "rust-macros", | ||
| "rust-cargo-workspaces", | ||
| "rust-ownership", | ||
| "rust-smart-pointers", | ||
| "rust-interior-mutability", | ||
| "rust-strings", | ||
| "rust-borrowing-slices", | ||
| "rust-lifetimes", | ||
| "rust-traits-lifetimes", | ||
| "rust-unsafe", | ||
| "rust-structs-impl", | ||
| "rust-enum-match", | ||
| "rust-option", | ||
| "rust-result", | ||
| "rust-testing", | ||
| "rust-async-await", | ||
| "rust-concurrency", | ||
| "rust-shared-state" | ||
| ], | ||
| "review_profile": "es" | ||
| } | ||
| ] | ||
| } |
| { | ||
| "schema_version": 1, | ||
| "runtime": "rust", | ||
| "lessons": [ | ||
| { | ||
| "id": "rust-output", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Format exact output with println!", | ||
| "body": "Rust checks formatting placeholders at compile time. println! writes the formatted values and then one newline, so punctuation belongs in the format string.", | ||
| "example": "fn main() {\n let tool = \"rustc\";\n let edition = 2024;\n println!(\"{tool}-{edition}\");\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn render(name: &str, score: &str) -> String {\n // TODO: use the exact field separator required by stdout.\n format!(\"{name} {score}\")\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let mut fields = input.split_whitespace();\n let name = fields.next().unwrap();\n let score = fields.next().unwrap();\n println!(\"{}\", render(name, score));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada 7", | ||
| "output": "Ada:7\n" | ||
| }, | ||
| { | ||
| "input": "Lin 0", | ||
| "output": "Lin:0\n" | ||
| }, | ||
| { | ||
| "input": "José 12", | ||
| "output": "José:12\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/std/macro.println.html", | ||
| "https://doc.rust-lang.org/edition-guide/rust-2024/index.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-variables", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Choose mutation or shadowing deliberately", | ||
| "body": "Bindings are immutable unless declared mut. Reassignment changes a mutable place, while shadowing with let creates a fresh binding that may even have a different type.", | ||
| "example": "fn main() {\n let label = \"count\";\n let mut value = 2;\n value += 3;\n let value = value * 2;\n println!(\"{label}:{value}\");\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn accumulate(values: &[i64]) -> i64 {\n let mut total = 1_i64;\n // TODO: choose the operation and identity for an additive accumulator.\n for &value in values { total *= value; }\n total\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let mut fields = input.split_whitespace();\n let label = fields.next().unwrap();\n let values = fields.map(|field| field.parse::<i64>().unwrap()).collect::<Vec<_>>();\n println!(\"{label}:{}\", accumulate(&values));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "sum 1 2", | ||
| "output": "sum:3\n" | ||
| }, | ||
| { | ||
| "input": "net -2 5", | ||
| "output": "net:3\n" | ||
| }, | ||
| { | ||
| "input": "zero 0 0", | ||
| "output": "zero:0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-numbers-tuples", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Pair integer quotient and remainder", | ||
| "body": "Signed integer division truncates toward zero, and the remainder keeps the dividend sign. Inputs exclude a zero divisor and the overflowing i64::MIN divided by -1 pair.", | ||
| "example": "fn main() {\n let result = (23_i64 / 4, 23_i64 % 4);\n println!(\"{}:{}\", result.0, result.1);\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn quotient_remainder(left: i64, right: i64) -> (i64, i64) {\n // TODO: put quotient and remainder in the requested tuple positions.\n (left % right, left / right)\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let values = input.split_whitespace().map(|part| part.parse::<i64>().unwrap()).collect::<Vec<_>>();\n let pair = quotient_remainder(values[0], values[1]);\n println!(\"{}:{}\", pair.0, pair.1);\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "17 5", | ||
| "output": "3:2\n" | ||
| }, | ||
| { | ||
| "input": "-17 5", | ||
| "output": "-3:-2\n" | ||
| }, | ||
| { | ||
| "input": "17 -5", | ||
| "output": "-3:2\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-input", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Read and tokenize all standard input", | ||
| "body": "Read::read_to_string consumes stdin into owned UTF-8 text. split_whitespace handles spaces and line breaks, while parsing each token remains a fallible Result operation.", | ||
| "example": "use std::io::Read;\n\nfn main() {\n let mut source = &b\"8 5\"[..];\n let mut text = String::new();\n source.read_to_string(&mut text).unwrap();\n let total: i32 = text.split_whitespace().map(|part| part.parse::<i32>().unwrap()).sum();\n println!(\"{total}\");\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn combine(input: &str) -> i64 {\n // TODO: combine the parsed stream with the additive operation and identity.\n input.split_whitespace().map(|part| part.parse::<i64>().unwrap()).product()\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n println!(\"{}\", combine(&input));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "2 3", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "-1\n5 2", | ||
| "output": "6\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/std/io/trait.Read.html#method.read_to_string" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-vec-hashmap", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Keep order in Vec and counts in HashMap", | ||
| "body": "Vec preserves insertion order; HashMap provides keyed lookup but no stable iteration order. The entry API updates a count without a separate contains check.", | ||
| "example": "use std::collections::HashMap;\n\nfn main() {\n let tags = vec![\"red\", \"blue\", \"red\"];\n let mut counts = HashMap::new();\n for tag in tags { *counts.entry(tag).or_insert(0) += 1; }\n println!(\"{}\", counts.len());\n}\n", | ||
| "starter": "use std::collections::HashMap;\nuse std::io::{self, Read};\n\nfn summarize(query: &str, values: &[&str]) -> (usize, usize) {\n let counts = HashMap::<&str, usize>::new();\n // TODO: populate counts from values through the entry API.\n (values.len(), counts.get(query).copied().unwrap_or(0))\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let mut fields = input.split_whitespace();\n let query = fields.next().unwrap();\n let values = fields.collect::<Vec<_>>();\n let (length, count) = summarize(query, &values);\n println!(\"{length} {count}\");\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "red red blue red", | ||
| "output": "3 2\n" | ||
| }, | ||
| { | ||
| "input": "x a b", | ||
| "output": "2 0\n" | ||
| }, | ||
| { | ||
| "input": "a a a a", | ||
| "output": "3 3\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.entry", | ||
| "https://doc.rust-lang.org/std/vec/struct.Vec.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-control-flow", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Use expression branches and inclusive ranges", | ||
| "body": "if selects a value only when every branch has a compatible type. The range 1..=n includes n, whereas 1..n stops before it.", | ||
| "example": "fn main() {\n let state = if 7 > 5 { \"ready\" } else { \"wait\" };\n let triangular: i32 = (1..=3).sum();\n println!(\"{state}:{triangular}\");\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn summarize(n: i64) -> (&'static str, i64) {\n let kind = if n % 2 == 0 { \"even\" } else { \"odd\" };\n // TODO: make the range include its upper boundary.\n let total = (1..n).sum();\n (kind, total)\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let (kind, total) = summarize(input.trim().parse().unwrap());\n println!(\"{kind}:{total}\");\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "3", | ||
| "output": "odd:6\n" | ||
| }, | ||
| { | ||
| "input": "4", | ||
| "output": "even:10\n" | ||
| }, | ||
| { | ||
| "input": "0", | ||
| "output": "even:0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch03-05-control-flow.html", | ||
| "https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-iterators", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Consume a lazy iterator pipeline", | ||
| "body": "Iterator adapters describe work without running it. A terminal consumer such as sum drives filter and map while preserving the item ownership chosen upstream.", | ||
| "example": "fn main() {\n let preview = (1..).filter(|n| n % 3 == 0).take(3).collect::<Vec<_>>();\n println!(\"{preview:?}\");\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn even_square_total(values: &[i64]) -> i64 {\n // TODO: transform each retained even value before the terminal sum.\n values.iter().copied().filter(|number| number % 2 == 0).sum()\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let values = input.split_whitespace().map(|part| part.parse::<i64>().unwrap()).collect::<Vec<_>>();\n println!(\"{}\", even_square_total(&values));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "1 2 3 4", | ||
| "output": "20\n" | ||
| }, | ||
| { | ||
| "input": "-2 -1 0", | ||
| "output": "4\n" | ||
| }, | ||
| { | ||
| "input": "1 3", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/std/iter/trait.Iterator.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-functions", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "checkpoint", | ||
| "level": "basic", | ||
| "title": "Return a typed expression from a function", | ||
| "body": "Function parameters and return types are explicit. A final expression has no semicolon; adding one turns the block result into unit and causes a type mismatch.", | ||
| "example": "fn greeting(name: &str) -> String {\n format!(\"hello, {name}\")\n}\n\nfn main() {\n println!(\"{}\", greeting(\"Ferris\"));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn area(width: i64, height: i64) -> i64 {\n // TODO: return the geometric operation as the trailing expression.\n width + height\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let dimensions = input.split_whitespace().map(|part| part.parse::<i64>().unwrap()).collect::<Vec<_>>();\n println!(\"{}\", area(dimensions[0], dimensions[1]));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "3 4", | ||
| "output": "12\n" | ||
| }, | ||
| { | ||
| "input": "0 5", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "7 1", | ||
| "output": "7\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch03-03-how-functions-work.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-modules-use", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Resolve a public item through use", | ||
| "body": "A module controls an item path and pub controls visibility. A use declaration brings that path into scope; it does not make a private item public. The classifier's inclusive passing boundary is 80.", | ||
| "example": "mod units {\n pub fn twice(value: i32) -> i32 { value * 2 }\n}\n\nuse units::twice;\n\nfn main() {\n println!(\"{}\", twice(21));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nmod grading {\n pub fn verdict(score: i64) -> &'static str {\n // TODO: include the documented boundary in the passing branch.\n if score > 90 { \"pass\" } else { \"retry\" }\n }\n}\n\nuse grading::verdict;\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n println!(\"{}\", verdict(input.trim().parse().unwrap()));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "91", | ||
| "output": "pass\n" | ||
| }, | ||
| { | ||
| "input": "80", | ||
| "output": "pass\n" | ||
| }, | ||
| { | ||
| "input": "79", | ||
| "output": "retry\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html", | ||
| "https://doc.rust-lang.org/reference/visibility-and-privacy.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-generics", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Constrain a reusable function with Copy", | ||
| "body": "A type parameter makes one function work for many element types. The Copy bound permits returning an element value from a shared slice without moving out of it.", | ||
| "example": "fn first_copy<T: Copy>(values: &[T]) -> Option<T> {\n values.first().copied()\n}\n\nfn main() {\n println!(\"{:?}\", first_copy(&[8_u8, 9]));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn last_copy<T: Copy>(values: &[T]) -> Option<T> {\n // TODO: select the requested edge of the slice without moving from it.\n values.first().copied()\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let values = input.split_whitespace().map(|part| part.parse::<i64>().unwrap()).collect::<Vec<_>>();\n println!(\"{}\", last_copy(&values).unwrap());\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "1 2 3", | ||
| "output": "3\n" | ||
| }, | ||
| { | ||
| "input": "-1", | ||
| "output": "-1\n" | ||
| }, | ||
| { | ||
| "input": "0 5", | ||
| "output": "5\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch10-01-syntax.html", | ||
| "https://doc.rust-lang.org/std/marker/trait.Copy.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-traits", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Format through a trait bound", | ||
| "body": "A trait names shared behavior, and a generic bound requires that behavior at the call site. Methods can borrow fields instead of cloning owned data.", | ||
| "example": "trait Size {\n fn size(&self) -> usize;\n}\n\nimpl<T> Size for Vec<T> {\n fn size(&self) -> usize { self.len() }\n}\n\nfn main() {\n println!(\"items={}\", vec![2, 4, 6].size());\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\ntrait Summary { fn summary(&self) -> String; }\nstruct ScoreCard { name: String, score: i64 }\nimpl Summary for ScoreCard {\n // TODO: include both borrowed fields in the formatted summary.\n fn summary(&self) -> String { format!(\"{}\", self.name) }\n}\nfn render<T: Summary>(value: &T) -> String { value.summary() }\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let mut fields = input.split_whitespace();\n let card = ScoreCard { name: fields.next().unwrap().to_owned(), score: fields.next().unwrap().parse().unwrap() };\n println!(\"{}\", render(&card));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada 3", | ||
| "output": "Ada: 3\n" | ||
| }, | ||
| { | ||
| "input": "Lin 0", | ||
| "output": "Lin: 0\n" | ||
| }, | ||
| { | ||
| "input": "Bo 12", | ||
| "output": "Bo: 12\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch10-02-traits.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-macros", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Expand a small expression macro", | ||
| "body": "An exported macro can call a defining-crate helper through $crate, so expansion is independent of the caller path. The expanded helper call is then typechecked normally.", | ||
| "example": "pub fn helper(value: i32) -> i32 { value + 1 }\n\n#[macro_export]\nmacro_rules! increment {\n ($value:expr) => {\n $crate::helper($value)\n };\n}\n\nmod caller {\n pub fn run(value: i32) -> i32 {\n crate::increment!(value)\n }\n}\n\nfn main() {\n println!(\"{}\", caller::run(6));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\npub fn helper(name: &str) -> String {\n // TODO: make the defining-crate helper produce the required greeting.\n format!(\"greeting {name}\")\n}\n\n#[macro_export]\nmacro_rules! greet {\n ($name:expr) => {\n $crate::helper($name)\n };\n}\n\nmod caller {\n pub fn greet_from(name: &str) -> String {\n crate::greet!(name)\n }\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n println!(\"{}\", caller::greet_from(input.trim()));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Rust", | ||
| "output": "hi Rust\n" | ||
| }, | ||
| { | ||
| "input": "Ada", | ||
| "output": "hi Ada\n" | ||
| }, | ||
| { | ||
| "input": "é", | ||
| "output": "hi é\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/reference/macros-by-example.html", | ||
| "https://doc.rust-lang.org/reference/paths.html#path-qualifiers" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-cargo-workspaces", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Recognize workspace-wide Cargo commands", | ||
| "body": "This single-file runner cannot create or validate a Cargo workspace. The lab is limited to mapping a maintenance intent to its workspace-wide command; it does not claim workspace mastery.", | ||
| "example": "fn main() {\n let orientation = \"cargo metadata --no-deps\";\n println!(\"{orientation}\");\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn workspace_command(intent: &str) -> String {\n // TODO: add the workspace-wide flags required for each supported intent.\n format!(\"cargo {intent}\")\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n println!(\"{}\", workspace_command(input.trim()));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "check", | ||
| "output": "cargo check --workspace\n" | ||
| }, | ||
| { | ||
| "input": "test", | ||
| "output": "cargo test --workspace\n" | ||
| }, | ||
| { | ||
| "input": "fmt", | ||
| "output": "cargo fmt --all -- --check\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/cargo/reference/workspaces.html", | ||
| "https://doc.rust-lang.org/cargo/commands/index.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-ownership", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Move a String and return its owner", | ||
| "body": "Passing String by value transfers its fixed-size pointer, length, and capacity metadata while retaining the existing UTF-8 buffer when one was allocated; empty input may remain allocation-free. main trims line endings in that original buffer before moving it into describe.", | ||
| "example": "fn wrap(text: String) -> String {\n format!(\"[{text}]\")\n}\n\nfn main() {\n let owned = String::from(\"owned\");\n println!(\"{}\", wrap(owned));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn trim_line_ending(text: &mut String) {\n while text.ends_with('\\n') || text.ends_with('\\r') { text.pop(); }\n}\n\nfn describe(text: String) -> (String, usize) {\n // TODO: measure the UTF-8 buffer before returning the same owner.\n let bytes = text.chars().count();\n (text, bytes)\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n trim_line_ending(&mut input);\n let (text, bytes) = describe(input);\n println!(\"{text}:{bytes}\");\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "rust", | ||
| "output": "rust:4\n" | ||
| }, | ||
| { | ||
| "input": "é", | ||
| "output": "é:2\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": ":0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-smart-pointers", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Give a recursive list a known size with Box", | ||
| "body": "A recursive enum cannot contain itself directly because its size would be infinite. Box<List> inserts a known-size owning pointer at each recursive link.", | ||
| "example": "enum Node {\n More(i32, Box<Node>),\n End,\n}\n\nfn total(node: &Node) -> i32 {\n match node { Node::More(value, next) => value + total(next), Node::End => 0 }\n}\n\nfn main() {\n let nodes = Node::More(4, Box::new(Node::More(5, Box::new(Node::End))));\n println!(\"{}\", total(&nodes));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nenum List { Cons(i64, Box<List>), Nil }\nimpl List {\n fn sum(&self) -> i64 { match self { List::Cons(value, next) => value + next.sum(), List::Nil => 0 } }\n}\nfn build(values: &[i64]) -> List {\n // TODO: fold every value, not only the first, into boxed recursive Cons nodes.\n match values.first() {\n Some(value) => List::Cons(*value, Box::new(List::Nil)),\n None => List::Nil,\n }\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let values = input.split_whitespace().map(|part| part.parse::<i64>().unwrap()).collect::<Vec<_>>();\n println!(\"{}\", build(&values).sum());\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "1 2 3", | ||
| "output": "6\n" | ||
| }, | ||
| { | ||
| "input": "-1 1", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch15-01-box.html", | ||
| "https://doc.rust-lang.org/std/boxed/struct.Box.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-interior-mutability", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Mutate single-threaded state through RefCell", | ||
| "body": "RefCell moves borrow checking to runtime. An owned RefCell<T> can be Send when T is Send, but RefCell is not Sync and cannot provide concurrently shared mutation; use Mutex for that role.", | ||
| "example": "use std::cell::Cell;\n\nfn main() {\n let visits = Cell::new(4);\n visits.set(visits.get() + 1);\n println!(\"{}\", visits.get());\n}\n", | ||
| "starter": "use std::cell::RefCell;\nuse std::io::{self, Read};\n\nstruct EventLog { events: RefCell<Vec<String>> }\nimpl EventLog {\n fn record(&self, event: &str) {\n let mut events = self.events.borrow_mut();\n // TODO: retain earlier events instead of resetting the log on every call.\n events.clear();\n events.push(event.to_owned());\n }\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let log = EventLog { events: RefCell::new(Vec::new()) };\n for event in input.split_whitespace() { log.record(event); }\n println!(\"{}\", log.events.borrow().len());\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "a b", | ||
| "output": "2\n" | ||
| }, | ||
| { | ||
| "input": "one", | ||
| "output": "1\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch15-05-interior-mutability.html", | ||
| "https://doc.rust-lang.org/std/cell/struct.RefCell.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-strings", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Separate UTF-8 bytes from Unicode scalars", | ||
| "body": "str::len returns bytes, chars iterates Unicode scalar values, and neither operation counts user-perceived grapheme clusters. Integer indexing is unavailable; byte-range slicing must land on UTF-8 boundaries.", | ||
| "example": "fn main() {\n let text = \"🦀ace\";\n let first = &text[..4];\n println!(\"{}:{}:{first}\", text.len(), text.chars().count());\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn describe(text: &str) -> String {\n // TODO: keep UTF-8 byte length distinct from the scalar count.\n let bytes = text.chars().count();\n let scalars = text.chars().count();\n let first = text.chars().next().unwrap();\n format!(\"{bytes}:{scalars}:{first}\")\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n println!(\"{}\", describe(input.trim_end()));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "rust", | ||
| "output": "4:4:r\n" | ||
| }, | ||
| { | ||
| "input": "é", | ||
| "output": "2:1:é\n" | ||
| }, | ||
| { | ||
| "input": "🦀a", | ||
| "output": "5:2:🦀\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/std/primitive.str.html", | ||
| "https://doc.rust-lang.org/std/str/struct.Chars.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-borrowing-slices", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "checkpoint", | ||
| "level": "intermediate", | ||
| "title": "Return a word slice borrowed from its line", | ||
| "body": "A &str slice refers into existing text without allocation. Its validity is tied to the owner, and modern borrow checking follows the last use rather than keeping every borrow until a lexical block ends.", | ||
| "example": "fn key(pair: &str) -> &str {\n pair.split_once(':').map_or(pair, |(head, _)| head)\n}\n\nfn main() {\n let record = String::from(\"color:blue\");\n println!(\"{}\", key(&record));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn first_word(line: &str) -> &str {\n // TODO: select the first borrowed word rather than the opposite edge.\n line.split_whitespace().last().unwrap_or(\"\")\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n println!(\"{}\", first_word(&input));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "rust rules", | ||
| "output": "rust\n" | ||
| }, | ||
| { | ||
| "input": " single ", | ||
| "output": "single\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch04-03-slices.html", | ||
| "https://doc.rust-lang.org/reference/types/pointer.html#shared-references-" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-lifetimes", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Relate two input borrows with one lifetime", | ||
| "body": "Lifetime annotations describe how references are related; they do not keep data alive. The selected side is based on Unicode scalar count, with the left side winning a tie.", | ||
| "example": "fn shorter<'a>(left: &'a str, right: &'a str) -> &'a str {\n if left.len() <= right.len() { left } else { right }\n}\n\nfn main() {\n println!(\"{}\", shorter(\"short\", \"longer\"));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn longer<'a>(left: &'a str, _right: &'a str) -> &'a str {\n // TODO: compare Unicode scalar counts and keep the documented tie rule.\n left\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let (left, right) = input.trim_end().split_once('|').unwrap();\n println!(\"{}\", longer(left, right));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "borrow|rs", | ||
| "output": "borrow\n" | ||
| }, | ||
| { | ||
| "input": "é|aa", | ||
| "output": "aa\n" | ||
| }, | ||
| { | ||
| "input": "ab|cd", | ||
| "output": "ab\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html", | ||
| "https://doc.rust-lang.org/reference/trait-bounds.html#lifetime-bounds" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-traits-lifetimes", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Return borrowed text through dyn Trait", | ||
| "body": "A trait object enables runtime dispatch when concrete widget types differ. The object-safe draw method borrows self and returns text tied to that borrow.", | ||
| "example": "trait Named {\n fn name(&self) -> &str;\n}\n\nstruct Tag(String);\nimpl Named for Tag { fn name(&self) -> &str { &self.0 } }\n\nfn show(value: &dyn Named) -> &str { value.name() }\n\nfn main() {\n println!(\"{}\", show(&Tag(String::from(\"dynamic\"))));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\ntrait Draw { fn draw(&self) -> &str; }\nstruct Button { text: String }\nstruct Label { text: String }\nimpl Draw for Button { fn draw(&self) -> &str { &self.text } }\nimpl Draw for Label { fn draw(&self) -> &str { &self.text } }\nfn render(widget: &dyn Draw) -> &str {\n let text = widget.draw();\n // TODO: return the complete borrowed text instead of dropping one scalar.\n let start = text.char_indices().nth(1).map_or(text.len(), |(index, _)| index);\n &text[start..]\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let command = input.trim_end();\n if let Some(text) = command.strip_prefix(\"label:\") {\n let label = Label { text: text.to_owned() };\n println!(\"{}\", render(&label));\n } else {\n let button = Button { text: command.to_owned() };\n println!(\"{}\", render(&button));\n }\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "button", | ||
| "output": "button\n" | ||
| }, | ||
| { | ||
| "input": "label:ok", | ||
| "output": "ok\n" | ||
| }, | ||
| { | ||
| "input": "label:Rust", | ||
| "output": "Rust\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch18-02-trait-objects.html", | ||
| "https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-unsafe", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Encapsulate one justified raw-pointer read", | ||
| "body": "Unsafe Rust permits specific operations but does not disable ownership or lifetime rules. In Rust 2024, unsafe_op_in_unsafe_fn warns by default; an explicit unsafe block keeps each operation and invariant auditable.", | ||
| "example": "fn first(values: &[i32]) -> i32 {\n assert!(!values.is_empty());\n // SAFETY: index zero is in bounds because the slice was checked as nonempty.\n unsafe { *values.get_unchecked(0) }\n}\n\nfn main() {\n println!(\"{}\", first(&[9, 8]));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn read_copy(value: &i64) -> i64 {\n // TODO: derive a valid raw pointer, state its invariant, and read it in an explicit unsafe block.\n value.saturating_add(1)\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let value = input.trim().parse::<i64>().unwrap();\n println!(\"{}\", read_copy(&value));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "7", | ||
| "output": "7\n" | ||
| }, | ||
| { | ||
| "input": "0", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "-3", | ||
| "output": "-3\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/edition-guide/rust-2024/unsafe-op-in-unsafe-fn.html", | ||
| "https://doc.rust-lang.org/reference/unsafe-blocks.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-structs-impl", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Borrow self in a struct method", | ||
| "body": "An impl block attaches behavior to a type. Methods take self, &self, or &mut self explicitly; associated functions omit self and are called with Type::name.", | ||
| "example": "struct Counter { value: i32 }\n\nimpl Counter {\n fn doubled(&self) -> i32 { self.value * 2 }\n}\n\nfn main() {\n let counter = Counter { value: 11 };\n println!(\"{}\", counter.doubled());\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nstruct Rectangle { width: i64, height: i64 }\nimpl Rectangle {\n fn area(&self) -> i64 {\n // TODO: calculate from both borrowed fields with the geometric operation.\n self.width + self.height\n }\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let values = input.split_whitespace().map(|part| part.parse::<i64>().unwrap()).collect::<Vec<_>>();\n let rectangle = Rectangle { width: values[0], height: values[1] };\n println!(\"{}\", rectangle.area());\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "3 4", | ||
| "output": "12\n" | ||
| }, | ||
| { | ||
| "input": "0 5", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "7 1", | ||
| "output": "7\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch05-03-method-syntax.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-enum-match", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Evaluate every command enum variant", | ||
| "body": "Enum variants can carry different payloads while remaining one type. match must cover every possible variant; a wildcard compiles but can conceal behavior required by a future variant.", | ||
| "example": "enum Light { Red, Amber, Green }\n\nfn seconds(light: Light) -> u8 {\n match light { Light::Red => 30, Light::Amber => 3, Light::Green => 20 }\n}\n\nfn main() {\n println!(\"{}\", seconds(Light::Amber));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nenum Command { Add(i64, i64), Mul(i64, i64), Quit }\nfn parse(input: &str) -> Command {\n let mut fields = input.split_whitespace();\n match fields.next().unwrap() {\n \"add\" => Command::Add(fields.next().unwrap().parse().unwrap(), fields.next().unwrap().parse().unwrap()),\n \"mul\" => Command::Mul(fields.next().unwrap().parse().unwrap(), fields.next().unwrap().parse().unwrap()),\n \"quit\" => Command::Quit,\n _ => unreachable!(),\n }\n}\nfn evaluate(command: Command) -> i64 {\n // TODO: replace the catch-all with correct Mul and Quit arms.\n match command {\n Command::Add(left, right) => left + right,\n _ => i64::MIN,\n }\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n println!(\"{}\", evaluate(parse(&input)));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "add 2 3", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "mul 2 3", | ||
| "output": "6\n" | ||
| }, | ||
| { | ||
| "input": "quit", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch06-00-enums.html", | ||
| "https://doc.rust-lang.org/reference/expressions/match-expr.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-option", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Handle an absent first scalar with Option", | ||
| "body": "chars().next returns Option<char> because the string may be empty. Matching Some and None makes absence explicit without a null value or an avoidable panic.", | ||
| "example": "fn checked_half(value: i32) -> Option<i32> {\n (value % 2 == 0).then_some(value / 2)\n}\n\nfn main() {\n println!(\"{:?}\", checked_half(14));\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn show_first(text: &str) -> String {\n // TODO: choose the first scalar and handle absence with the documented word.\n text.chars().last().map_or_else(|| String::from(\"TODO\"), |value| value.to_string())\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n println!(\"{}\", show_first(input.trim_end()));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "rust", | ||
| "output": "r\n" | ||
| }, | ||
| { | ||
| "input": "서울", | ||
| "output": "서\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "empty\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/std/option/enum.Option.html", | ||
| "https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-result", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Propagate token parse failure with question mark", | ||
| "body": "Result carries either a value or an error. The ? operator returns early from the current Result-returning function; it propagates rather than catches the parse failure.", | ||
| "example": "fn positive(text: &str) -> Result<i32, &'static str> {\n let value = text.parse::<i32>().map_err(|_| \"not an integer\")?;\n if value > 0 { Ok(value) } else { Err(\"not positive\") }\n}\n\nfn main() {\n println!(\"{:?}\", positive(\"8\"));\n}\n", | ||
| "starter": "use std::io::{self, Read};\nuse std::num::ParseIntError;\n\nfn sum_tokens(input: &str) -> Result<i64, ParseIntError> {\n // TODO: fold every token and propagate the first failed conversion.\n input.split_whitespace().next().unwrap_or(\"invalid\").parse::<i64>()\n}\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n match sum_tokens(&input) {\n Ok(total) => println!(\"{total}\"),\n Err(_) => println!(\"error\"),\n }\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "2 3", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "-1 1", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "2 bad", | ||
| "output": "error\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/std/result/enum.Result.html", | ||
| "https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-testing", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Run assertions in the ordinary executable", | ||
| "body": "Inline assert_eq! calls in main execute under this plain rustc runner. Functions marked #[test] require a test harness started by cargo test or rustc --test and are not run by an ordinary binary build.", | ||
| "example": "fn triple(value: i64) -> i64 { value * 3 }\n\nfn main() {\n let result = triple(4);\n assert_eq!(result, 12);\n println!(\"checks passed\");\n}\n", | ||
| "starter": "use std::io::{self, Read};\n\nfn add_two(value: i64) -> i64 { value }\n\nfn main() {\n assert_eq!(add_two(-2), 0);\n assert_eq!(add_two(3), 5);\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n println!(\"{}\", add_two(input.trim().parse::<i64>().unwrap()));\n // TODO: implement add_two, keep both assertions, then remove this guard.\n compile_error!(\"finish the TODO before compiling\");\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "3", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "-2", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "0", | ||
| "output": "2\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch11-03-test-organization.html", | ||
| "https://doc.rust-lang.org/std/macro.assert_eq.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-async-await", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Poll one known-ready future to completion", | ||
| "body": "Calling an async function creates a Future but does not execute its body. This bounded standard-library executor polls exactly once and is valid only for futures documented to be immediately ready.", | ||
| "example": "use std::future::{ready, Future};\nuse std::sync::Arc;\nuse std::task::{Context, Poll, Wake, Waker};\n\nstruct Noop;\nimpl Wake for Noop { fn wake(self: Arc<Self>) {} }\n\nfn block_on_ready<F: Future>(future: F) -> F::Output {\n let waker = Waker::from(Arc::new(Noop));\n let mut context = Context::from_waker(&waker);\n let mut future = Box::pin(future);\n match future.as_mut().poll(&mut context) {\n Poll::Ready(value) => value,\n Poll::Pending => panic!(\"executor accepts only ready futures\"),\n }\n}\n\nasync fn message() -> &'static str { ready(\"future:ready\").await }\n\nfn main() { println!(\"{}\", block_on_ready(message())); }\n", | ||
| "starter": "use std::future::{ready, Future};\nuse std::io::{self, Read};\nuse std::sync::Arc;\nuse std::task::{Context, Poll, Wake, Waker};\n\nstruct Noop;\nimpl Wake for Noop { fn wake(self: Arc<Self>) {} }\nfn block_on_ready<F: Future>(future: F) -> F::Output {\n let waker = Waker::from(Arc::new(Noop));\n let mut context = Context::from_waker(&waker);\n let mut future = Box::pin(future);\n match future.as_mut().poll(&mut context) { Poll::Ready(value) => value, Poll::Pending => panic!(\"ready future became pending\") }\n}\nasync fn double(value: i64) -> i64 {\n // TODO: transform the value inside the awaited ready future.\n ready(value).await\n}\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n println!(\"{}\", block_on_ready(double(input.trim().parse().unwrap())));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "2", | ||
| "output": "4\n" | ||
| }, | ||
| { | ||
| "input": "0", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "-3", | ||
| "output": "-6\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/book/ch17-01-futures-and-syntax.html", | ||
| "https://doc.rust-lang.org/std/future/fn.ready.html", | ||
| "https://doc.rust-lang.org/std/task/trait.Wake.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-concurrency", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Move work into a thread and join it", | ||
| "body": "thread::spawn creates an independently scheduled operating-system thread that may start before or after the caller continues. The captured i64 is Copy, so move copies it into the closure without invalidating a caller binding; join waits for the worker result.", | ||
| "example": "use std::thread;\n\nfn main() {\n let handle = thread::spawn(|| 6 * 7);\n println!(\"{}\", handle.join().expect(\"worker panicked\"));\n}\n", | ||
| "starter": "use std::io::{self, Read};\nuse std::thread;\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let value = input.trim().parse::<i64>().unwrap();\n // TODO: perform the requested transformation inside the moved closure.\n let handle = thread::spawn(move || value);\n println!(\"{}\", handle.join().expect(\"worker panicked\"));\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "2", | ||
| "output": "4\n" | ||
| }, | ||
| { | ||
| "input": "0", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "-3", | ||
| "output": "-6\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/std/thread/fn.spawn.html", | ||
| "https://doc.rust-lang.org/std/thread/struct.JoinHandle.html#method.join" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "rust-shared-state", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "capstone", | ||
| "level": "advanced", | ||
| "title": "Capstone: join workers around Arc<Mutex> state", | ||
| "body": "Each input owns one worker that adds its square to a shared i64. Arc shares ownership, Mutex serializes access, lock can block or report poisoning, and the guard scope releases the lock before join completion.", | ||
| "example": "use std::sync::{Arc, Mutex};\nuse std::thread;\n\nfn main() {\n let total = Arc::new(Mutex::new(0));\n let handles = (0..2).map(|_| {\n let total = Arc::clone(&total);\n thread::spawn(move || {\n let mut guard = total.lock().expect(\"mutex poisoned\");\n *guard += 1;\n })\n }).collect::<Vec<_>>();\n for handle in handles { handle.join().expect(\"worker panicked\"); }\n let answer = *total.lock().expect(\"mutex poisoned\");\n println!(\"{answer}\");\n}\n", | ||
| "starter": "use std::io::{self, Read};\nuse std::sync::{Arc, Mutex};\nuse std::thread;\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n let values = input.split_whitespace().map(|part| part.parse::<i64>().unwrap()).collect::<Vec<_>>();\n let total = Arc::new(Mutex::new(0_i64));\n let mut handles = Vec::new();\n for value in values {\n let total = Arc::clone(&total);\n handles.push(thread::spawn(move || {\n // TODO: transform the owned value before entering the short lock scope.\n let mut guard = total.lock().expect(\"worker mutex poisoned\");\n *guard += value;\n }));\n }\n for handle in handles { handle.join().expect(\"worker panicked\"); }\n let answer = *total.lock().expect(\"main mutex poisoned\");\n println!(\"{answer}\");\n}\n", | ||
| "cases": [ | ||
| { | ||
| "input": "1 2 3", | ||
| "output": "14\n" | ||
| }, | ||
| { | ||
| "input": "-2 3", | ||
| "output": "13\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://doc.rust-lang.org/std/sync/struct.Mutex.html", | ||
| "https://doc.rust-lang.org/std/sync/struct.Arc.html", | ||
| "https://doc.rust-lang.org/book/ch16-03-shared-state.html" | ||
| ] | ||
| } | ||
| ] | ||
| } |
| { | ||
| "schema_version": 1, | ||
| "runtime": "ts", | ||
| "lessons": [ | ||
| { | ||
| "id": "ts-output", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Write exact stdout bytes", | ||
| "body": "Send a formatted record through process.stdout.write so the separator and final newline are deliberate. This also establishes that Node decides runtime behavior after TypeScript annotations are stripped; module syntax gets its own orientation lab.", | ||
| "example": "const learner: string = \"Mina\";\nconst points: number = 4;\nprocess.stdout.write(`demo ${learner}=${points}\\n`);\n", | ||
| "starter": "const input = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst [name = \"\", score = \"\"] = input.split(/\\s+/);\n// TODO: make each byte of the record intentional.\nprocess.stdout.write(`${name}${score}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada 7\n", | ||
| "output": "Ada:7\n" | ||
| }, | ||
| { | ||
| "input": "Lin 0\n", | ||
| "output": "Lin:0\n" | ||
| }, | ||
| { | ||
| "input": "José 12\n", | ||
| "output": "José:12\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://nodejs.org/docs/latest-v22.x/api/process.html#processstdout", | ||
| "https://nodejs.org/docs/latest-v22.x/api/typescript.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-input", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Tokenize Node standard input", | ||
| "body": "Read fd 0 once as UTF-8, split only when trimmed text is nonempty, convert every token, and add the numbers. The empty-input branch prevents a phantom zero token while preserving the stdin harness used later.", | ||
| "example": "const sample: string = \"4 6\";\nconst total: number = sample.split(/\\s+/).map(Number).reduce((sum, value) => sum + value, 0);\nprocess.stdout.write(`demo total=${total}\\n`);\n", | ||
| "starter": "const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst values: number[] = raw ? raw.split(/\\s+/).map(Number) : [];\n// TODO: combine the complete token stream.\nconst total: number = values[0] ?? 0;\nprocess.stdout.write(`${total}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "2 3\n", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "-1\n5 2\n", | ||
| "output": "6\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://nodejs.org/docs/latest-v22.x/api/fs.html#fsreadfilesyncpath-options", | ||
| "https://nodejs.org/docs/latest-v22.x/api/typescript.html#type-stripping" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-primitives", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Convert text into primitive values", | ||
| "body": "Parse a name, score, and threshold into string and number values before comparing them. An annotation is removed at runtime, so it documents and checks code but cannot turn input text into a number; const binding and UTF-16 string details are explored in nearby labs.", | ||
| "example": "const label: string = \"demo\";\nconst count: number = 3;\nconst ready: boolean = count > 1;\nprocess.stdout.write(`${label}:${count}:${ready}\\n`);\n", | ||
| "starter": "const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst [name = \"\", scoreText = \"\", thresholdText = \"\"] = raw.split(/\\s+/);\n// TODO: cross the text-to-number boundary explicitly.\nconst score: number = scoreText.length;\nconst threshold: number = thresholdText.length;\nconst status: string = score >= threshold ? \"pass\" : \"retry\";\nprocess.stdout.write(`${name}:${score}:${status}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada 7 5\n", | ||
| "output": "Ada:7:pass\n" | ||
| }, | ||
| { | ||
| "input": "Lin 4 5\n", | ||
| "output": "Lin:4:retry\n" | ||
| }, | ||
| { | ||
| "input": "Bo 0 0\n", | ||
| "output": "Bo:0:pass\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-9.html", | ||
| "https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-let-const", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Rebind an accumulator, keep its label", | ||
| "body": "Keep the label in a const binding and update a numeric total through let. const prevents rebinding that variable; it does not freeze an object stored in the binding.", | ||
| "example": "const category: string = \"demo\";\nlet total: number = 5;\ntotal += -2;\nprocess.stdout.write(`${category}:${total}\\n`);\n", | ||
| "starter": "const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst [label = \"\", firstText = \"0\", secondText = \"0\"] = raw.split(/\\s+/);\nconst first: number = Number(firstText);\nconst second: number = Number(secondText);\n// TODO: update a mutable total without rebinding the label.\nconst total: number = first;\nprocess.stdout.write(`${label}:${total}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "sum 1 2\n", | ||
| "output": "sum:3\n" | ||
| }, | ||
| { | ||
| "input": "net -2 5\n", | ||
| "output": "net:3\n" | ||
| }, | ||
| { | ||
| "input": "zero 0 0\n", | ||
| "output": "zero:0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/variable-declarations.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-strings-templates", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Trim the name, preserve the score", | ||
| "body": "Remove only one terminal LF or CRLF from the pipe-delimited record, trim the name field, and leave every score character intact. JavaScript strings still use UTF-16 code units for length and indexing.", | ||
| "example": "const record: string = \" Mira |9 \\n\".replace(/\\r?\\n$/, \"\");\nconst [rawName, score] = record.split(\"|\");\nprocess.stdout.write(`demo ${rawName.trim()}=${score}\\n`);\n", | ||
| "starter": "const raw = require(\"node:fs\").readFileSync(0, \"utf8\").replace(/\\r?\\n$/, \"\");\nconst [name = \"\", score = \"\"] = raw.split(\"|\");\n// TODO: normalize only the human-readable name field.\nprocess.stdout.write(`${name}:${score}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": " Ada |7 \n", | ||
| "output": "Ada:7 \n" | ||
| }, | ||
| { | ||
| "input": "Lin|0\n", | ||
| "output": "Lin:0\n" | ||
| }, | ||
| { | ||
| "input": " José |12\n", | ||
| "output": "José:12\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-arrays-tuples", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Return a named total as a tuple", | ||
| "body": "Gather the numeric tail of stdin into an array, total it, and place the name and sum in a fixed-position tuple. Tuples remain ordinary Arrays at runtime; iterable consumption and map/filter/reduce are optional follow-on labs.", | ||
| "example": "const demoValues: number[] = [1, 4];\nconst demoPair: [string, number] = [\"Neo\", demoValues.reduce((sum, value) => sum + value, 0)];\nprocess.stdout.write(`demo ${demoPair[0]}=${demoPair[1]}\\n`);\n", | ||
| "starter": "const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst [name = \"\", ...numberTexts] = raw ? raw.split(/\\s+/) : [\"\"];\nconst values: number[] = numberTexts.map(Number);\n// TODO: derive the tuple's numeric position from every value.\nconst result: [string, number] = [name, 0];\nprocess.stdout.write(`${result[0]}:${result[1]}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada 2 3\n", | ||
| "output": "Ada:5\n" | ||
| }, | ||
| { | ||
| "input": "Lin -1 4 2\n", | ||
| "output": "Lin:5\n" | ||
| }, | ||
| { | ||
| "input": "Bo\n", | ||
| "output": "Bo:0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-iterables", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Consume an Iterable with for-of", | ||
| "body": "Accept Iterable<string> and concatenate values through for...of. An iterable need not support numeric indexing, and an iterator obtained from one may be single-pass rather than reusable.", | ||
| "example": "function collectDemo(values: Iterable<string>): string {\n let text = \"\";\n for (const value of values) text += value;\n return text;\n}\nprocess.stdout.write(`demo:${collectDemo(new Set([\"T\", \"S\"]))}\\n`);\n", | ||
| "starter": "const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst tokens: string[] = raw ? raw.split(/\\s+/) : [];\n// TODO: visit the iterable instead of selecting one array slot.\nconst joined: string = tokens[0] ?? \"\";\nprocess.stdout.write(`${joined}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "o k\n", | ||
| "output": "ok\n" | ||
| }, | ||
| { | ||
| "input": "a b c\n", | ||
| "output": "abc\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/iterators-and-generators.html#iterables" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-array-methods", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Filter, square, and reduce safely", | ||
| "body": "Select even values, square the survivors, and fold them with an explicit zero initial value. Supplying that initializer makes an empty filtered array a defined case rather than a reduce exception.", | ||
| "example": "const demoNumbers: number[] = [2, 5, 6];\nconst demoTotal = demoNumbers.filter((value) => value > 3).map((value) => value * 2).reduce((sum, value) => sum + value, 0);\nprocess.stdout.write(`demo:${demoTotal}\\n`);\n", | ||
| "starter": "const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst values: number[] = raw ? raw.split(/\\s+/).map(Number) : [];\n// TODO: select the intended parity before squaring and folding.\nconst total = values.filter((value) => value % 2 !== 0).map((value) => value * value).reduce((sum, value) => sum + value, 0);\nprocess.stdout.write(`${total}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "1 2 3 4\n", | ||
| "output": "20\n" | ||
| }, | ||
| { | ||
| "input": "-2 -1 0\n", | ||
| "output": "4\n" | ||
| }, | ||
| { | ||
| "input": "1 3\n", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#arrays" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-objects", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Calculate through a structural object", | ||
| "body": "Parse dimensions into a Rectangle-shaped value and pass it to an area function. TypeScript checks compatible members rather than nominal identity; class runtime state and shallow readonly views remain optional labs.", | ||
| "example": "type DemoSize = { width: number; height: number };\nconst painted = { width: 2, height: 6, color: \"blue\" };\nfunction demoArea(size: DemoSize): number { return size.width * size.height; }\nprocess.stdout.write(`demo:${demoArea(painted)}\\n`);\n", | ||
| "starter": "type Rectangle = { width: number; height: number };\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst [widthText = \"0\", heightText = \"0\"] = raw.split(/\\s+/);\n// TODO: preserve the parsed height instead of a placeholder dimension.\nconst rectangle: Rectangle = { width: Number(widthText), height: 1 };\nconst area = (value: Rectangle): number => value.width * value.height;\nprocess.stdout.write(`${area(rectangle)}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "3 4\n", | ||
| "output": "12\n" | ||
| }, | ||
| { | ||
| "input": "0 5\n", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "7 1\n", | ||
| "output": "7\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/objects.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-classes", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Update state behind a class method", | ||
| "body": "Store a counter value in a class and change it only through increment. TypeScript private is a checker restriction that is erased; JavaScript #private fields, by contrast, enforce a runtime boundary.", | ||
| "example": "class DemoCounter {\n private value: number = 8;\n increment(): number { this.value += 1; return this.value; }\n}\nprocess.stdout.write(`demo:${new DemoCounter().increment()}\\n`);\n", | ||
| "starter": "class Counter {\n private value: number;\n constructor(value: number) { this.value = value; }\n // TODO: change the stored state before returning it.\n increment(): number { return this.value; }\n}\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nprocess.stdout.write(`${new Counter(Number(raw)).increment()}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "1\n", | ||
| "output": "2\n" | ||
| }, | ||
| { | ||
| "input": "-1\n", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "5\n", | ||
| "output": "6\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/classes.html#member-visibility" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-readonly", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Read a shallow readonly configuration", | ||
| "body": "Accept a readonly name and readonly numeric sequence, then total the sequence without mutation. readonly is type-only and shallow: it neither freezes the runtime object nor recursively protects nested mutable values.", | ||
| "example": "type DemoConfig = { readonly name: string; readonly scores: readonly number[] };\nconst demoConfig: DemoConfig = { name: \"sample\", scores: [3, 4] };\nprocess.stdout.write(`demo:${demoConfig.name}=${demoConfig.scores.length}\\n`);\n", | ||
| "starter": "type Config = { readonly name: string; readonly scores: readonly number[] };\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst [name = \"\", ...scoreTexts] = raw ? raw.split(/\\s+/) : [\"\"];\nconst config: Config = { name, scores: scoreTexts.map(Number) };\n// TODO: derive the total without mutating the readonly view.\nconfig.scores.push(0);\nconst total = config.scores.reduce((sum, score) => sum + score, 0);\nprocess.stdout.write(`${config.name}:${total}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada 2 3\n", | ||
| "output": "Ada:5\n" | ||
| }, | ||
| { | ||
| "input": "Lin\n", | ||
| "output": "Lin:0\n" | ||
| }, | ||
| { | ||
| "input": "Bo -1 4\n", | ||
| "output": "Bo:3\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/objects.html#readonly-properties" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-control-flow", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "basic", | ||
| "title": "Keep an inclusive loop boundary", | ||
| "body": "Sum odd integers from one through n with an explicit branch and loop bound. Strict equality avoids coercion, while <= rather than < decides whether an odd endpoint participates.", | ||
| "example": "const limit: number = 5;\nlet evenTotal = 0;\nfor (let value = 1; value <= limit; value += 1) if (value % 2 === 0) evenTotal += value;\nprocess.stdout.write(`demo:${evenTotal}\\n`);\n", | ||
| "starter": "const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst n: number = Number(raw);\nlet total = 0;\n// TODO: include every value in the stated closed range.\nfor (let value = 1; value < n; value += 1) if (value % 2 === 1) total += value;\nprocess.stdout.write(`${total}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "3\n", | ||
| "output": "4\n" | ||
| }, | ||
| { | ||
| "input": "6\n", | ||
| "output": "9\n" | ||
| }, | ||
| { | ||
| "input": "0\n", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#control-flow-analysis" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-functions", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "checkpoint", | ||
| "level": "basic", | ||
| "title": "Checkpoint: typed calculation boundary", | ||
| "body": "Turn raw dimension text into a number-returning function and print its result. Parameter and return annotations expose a broken contract before execution; the branches and loops from the control-flow lab belong inside such boundaries.", | ||
| "example": "function perimeter(width: number, height: number): number {\n return 2 * (width + height);\n}\nprocess.stdout.write(`demo:${perimeter(2, 5)}\\n`);\n", | ||
| "starter": "const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\n// TODO: make the declared result agree with the calculation.\nfunction area(input: string): string {\n const [width, height] = input.split(/\\s+/).map(Number);\n return width * height;\n}\nprocess.stdout.write(`${area(raw)}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "3 4\n", | ||
| "output": "12\n" | ||
| }, | ||
| { | ||
| "input": "0 5\n", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "7 1\n", | ||
| "output": "7\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/functions.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-union-narrowing", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Narrow a union before formatting", | ||
| "body": "Represent parsed input as string | number, then use typeof so each branch calls methods available on its member. A runtime string must be validated before becoming a literal union; a cast alone cannot perform that check.", | ||
| "example": "function demoFormat(value: string | number): string {\n return typeof value === \"string\" ? value.toLowerCase() : value.toFixed(1);\n}\nprocess.stdout.write(`demo:${demoFormat(3.14)}\\n`);\n", | ||
| "starter": "const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst [kind = \"\", text = \"\"] = raw.split(/\\s+/);\nconst value: string | number = kind === \"n\" ? Number(text) : text;\n// TODO: prove which union member is present before using its methods.\nconst formatted: string = value.toUpperCase();\nprocess.stdout.write(`${formatted}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "s ok\n", | ||
| "output": "OK\n" | ||
| }, | ||
| { | ||
| "input": "n 2.4\n", | ||
| "output": "2\n" | ||
| }, | ||
| { | ||
| "input": "n -1.6\n", | ||
| "output": "-2\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#typeof-type-guards" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-literal-types", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Validate text before a literal union", | ||
| "body": "Accept only left or right after checking the actual stdin string, then call a Direction function. Writing `as Direction` changes the checker's view but neither tests nor changes the runtime value.", | ||
| "example": "type DemoDirection = \"north\" | \"south\";\nconst arrow = (direction: DemoDirection): string => direction === \"north\" ? \"N\" : \"S\";\nprocess.stdout.write(`demo:${arrow(\"north\")}\\n`);\n", | ||
| "starter": "type Direction = \"left\" | \"right\";\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst turn = (direction: Direction): string => direction === \"left\" ? \"L\" : \"R\";\n// TODO: establish the literal union with a runtime check.\nconst direction = raw as Direction;\nprocess.stdout.write(`${turn(direction)}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "left\n", | ||
| "output": "L\n" | ||
| }, | ||
| { | ||
| "input": "right\n", | ||
| "output": "R\n" | ||
| }, | ||
| { | ||
| "input": "up\n", | ||
| "output": "invalid\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-optional-nullish", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Default only nullish scores", | ||
| "body": "Model an absent score as null and use ?? so the numeric value zero survives. Logical OR also replaces other falsy values, making it the wrong defaulting operator for a legitimate zero.", | ||
| "example": "const demoCount: number | undefined = 0;\nprocess.stdout.write(`demo:${demoCount ?? 8}\\n`);\n", | ||
| "starter": "const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst [name = \"\", scoreText = \"none\"] = raw.split(/\\s+/);\nconst score: number | null = scoreText === \"none\" ? null : Number(scoreText);\n// TODO: distinguish absence from a falsy numeric score.\nconst shown: number = score || 10;\nprocess.stdout.write(`${name}:${shown}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada 0\n", | ||
| "output": "Ada:0\n" | ||
| }, | ||
| { | ||
| "input": "Lin none\n", | ||
| "output": "Lin:10\n" | ||
| }, | ||
| { | ||
| "input": "Bo 5\n", | ||
| "output": "Bo:5\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#strictnullchecks-on" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-interfaces-aliases", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Compose structural contracts", | ||
| "body": "Combine an interface-provided name with a type-alias score through an intersection and format the resulting object. Interfaces may declaration-merge, aliases can name unions, and both remain checker-only structural descriptions; classes, readonly, and satisfies labs refine those boundaries.", | ||
| "example": "interface Tagged { tag: string }\ntype Weighted = { weight: number };\ntype DemoItem = Tagged & Weighted;\nconst demoItem: DemoItem = { tag: \"sample\", weight: 6 };\nprocess.stdout.write(`demo:${demoItem.tag}=${demoItem.weight}\\n`);\n", | ||
| "starter": "interface Named { name: string }\ntype Scored = { score: number };\ntype Entry = Named & Scored;\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst [name = \"\", scoreText = \"0\"] = raw.split(/\\s+/);\n// TODO: build the intersection without altering parsed data.\nconst entry: Entry = { name, score: Number(scoreText) + 1 };\nprocess.stdout.write(`${entry.name}:${entry.score}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada 5\n", | ||
| "output": "Ada:5\n" | ||
| }, | ||
| { | ||
| "input": "Lin 0\n", | ||
| "output": "Lin:0\n" | ||
| }, | ||
| { | ||
| "input": "Bo -2\n", | ||
| "output": "Bo:-2\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/objects.html#interface-extension-vs-intersection" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-generics", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Preserve an element type through first", | ||
| "body": "Write first<T> so callers receive T | undefined without any, a cast, or a non-null assertion. keyof, indexed access, mapped, conditional, and utility types are optional transformations built on the same principle of deriving rather than repeating source types.", | ||
| "example": "function last<T>(items: readonly T[]): T | undefined { return items[items.length - 1]; }\nconst demoValue = last([\"a\", \"b\", \"c\"]);\nprocess.stdout.write(`demo:${demoValue ?? \"empty\"}\\n`);\n", | ||
| "starter": "function first<T>(items: readonly T[]): T | undefined {\n // TODO: return the leading element when it exists.\n return items[1];\n}\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst tokens: string[] = raw ? raw.split(/\\s+/) : [];\nprocess.stdout.write(`${first(tokens) ?? \"none\"}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "ok skip\n", | ||
| "output": "ok\n" | ||
| }, | ||
| { | ||
| "input": "one\n", | ||
| "output": "one\n" | ||
| }, | ||
| { | ||
| "input": "", | ||
| "output": "none\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/generics.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-keyof-typeof", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Guard a runtime key for keyof", | ||
| "body": "Derive the key union from a limits value with keyof typeof, then narrow stdin with Object.hasOwn. Unlike the in operator, an own-property check rejects inherited names such as toString and constructor.", | ||
| "example": "const demoLimits = { tiny: 1, huge: 9 } as const;\ntype DemoKey = keyof typeof demoLimits;\nfunction isDemoKey(value: string): value is DemoKey { return Object.hasOwn(demoLimits, value); }\nconst demoInput: string = \"huge\";\nconst demoResult = isDemoKey(demoInput) ? String(demoLimits[demoInput]) : \"missing\";\nprocess.stdout.write(`demo:${demoResult}\\n`);\n", | ||
| "starter": "const limits = { small: 2, large: 5 } as const;\ntype LimitKey = keyof typeof limits;\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\n// TODO: prove the external text names an own property before indexing.\nconst key = raw as LimitKey;\nprocess.stdout.write(`${limits[key]}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "small\n", | ||
| "output": "2\n" | ||
| }, | ||
| { | ||
| "input": "large\n", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "medium\n", | ||
| "output": "invalid\n" | ||
| }, | ||
| { | ||
| "input": "toString\n", | ||
| "output": "invalid\n" | ||
| }, | ||
| { | ||
| "input": "constructor\n", | ||
| "output": "invalid\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/keyof-types.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-indexed-access", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Reuse an array element type", | ||
| "body": "Name a score with User[\"scores\"][number] so the element type follows the model. That derived annotation still requires explicit conversion because stdin arrives as a string.", | ||
| "example": "type DemoUser = { tags: string[] };\nconst demoTag: DemoUser[\"tags\"][number] = \"typed\";\nprocess.stdout.write(`demo:${demoTag}\\n`);\n", | ||
| "starter": "type User = { scores: number[] };\ntype Score = User[\"scores\"][number];\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\n// TODO: convert the boundary value before assigning the derived type.\nconst score: Score = raw;\nprocess.stdout.write(`${score}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "5\n", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "0\n", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "-2\n", | ||
| "output": "-2\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-mapped-types", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Map every feature key to a flag", | ||
| "body": "Transform the Feature union into a complete boolean object, then report the enabled key. A mapped type requires both search and share, fixing the earlier impossible exercise whose requested edit and expected result disagreed.", | ||
| "example": "type DemoFeature = \"dark\" | \"cache\";\ntype DemoFlags = { [Key in DemoFeature]: boolean };\nconst demoFlags: DemoFlags = { dark: true, cache: false };\nprocess.stdout.write(`demo:${demoFlags.dark ? \"dark\" : \"cache\"}\\n`);\n", | ||
| "starter": "type Feature = \"search\" | \"share\";\ntype Flags = { [Key in Feature]: boolean };\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\n// TODO: provide one boolean for every mapped feature key.\nconst flags: Flags = { search: raw === \"search\" };\nconst enabled: Feature | \"none\" = flags.search ? \"search\" : flags.share ? \"share\" : \"none\";\nprocess.stdout.write(`${enabled}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "search\n", | ||
| "output": "search\n" | ||
| }, | ||
| { | ||
| "input": "share\n", | ||
| "output": "share\n" | ||
| }, | ||
| { | ||
| "input": "none\n", | ||
| "output": "none\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/mapped-types.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-conditional-types", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Extract an element with a conditional type", | ||
| "body": "Use infer inside a conditional type to derive the member of string[]. The conditional is evaluated only by the checker; it does not branch on stdin at runtime.", | ||
| "example": "type DemoElement<T> = T extends readonly (infer Item)[] ? Item : T;\nconst demoNumber: DemoElement<number[]> = 11;\nprocess.stdout.write(`demo:${demoNumber}\\n`);\n", | ||
| "starter": "type ElementType<T> = T extends readonly (infer Item)[] ? Item : T;\ntype TextElement = ElementType<string[]>;\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\n// TODO: assign a value compatible with the extracted member type.\nconst value: TextElement = Number(raw);\nprocess.stdout.write(`${value}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "ok\n", | ||
| "output": "ok\n" | ||
| }, | ||
| { | ||
| "input": "hello\n", | ||
| "output": "hello\n" | ||
| }, | ||
| { | ||
| "input": "42\n", | ||
| "output": "42\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/conditional-types.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-utility-types", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Patch selected fields with utility types", | ||
| "body": "Represent an optional score update as Partial<Pick<User, \"score\">> and supply zero only when that property is absent. Partial changes static optionality; it neither inserts properties nor defaults runtime values.", | ||
| "example": "type DemoProfile = { name: string; active: boolean };\nconst demoPatch: Partial<Pick<DemoProfile, \"active\">> = { active: true };\nprocess.stdout.write(`demo:${demoPatch.active ?? false}\\n`);\n", | ||
| "starter": "type User = { name: string; score: number };\ntype ScorePatch = Partial<Pick<User, \"score\">>;\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst [name = \"\", scoreText] = raw.split(/\\s+/);\n// TODO: leave an omitted score absent rather than inventing one.\nconst patch: ScorePatch = { score: scoreText === undefined ? 1 : Number(scoreText) };\nprocess.stdout.write(`${name}:${patch.score ?? 0}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "Ada 5\n", | ||
| "output": "Ada:5\n" | ||
| }, | ||
| { | ||
| "input": "Lin\n", | ||
| "output": "Lin:0\n" | ||
| }, | ||
| { | ||
| "input": "Bo -2\n", | ||
| "output": "Bo:-2\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-satisfies-as-const", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "advanced", | ||
| "title": "Check routes while preserving literals", | ||
| "body": "Define route strings with as const, check their Record shape through satisfies, and require Object.hasOwn before indexing external text. satisfies is not a cast, and as const neither freezes the object nor admits inherited keys.", | ||
| "example": "const demoStatuses = { ready: \"R\", waiting: \"W\" } as const satisfies Record<string, string>;\ntype DemoStatusKey = keyof typeof demoStatuses;\nfunction hasDemoStatus(value: string): value is DemoStatusKey { return Object.hasOwn(demoStatuses, value); }\nconst demoInput: string = \"ready\";\nconst demoResult = hasDemoStatus(demoInput) ? demoStatuses[demoInput] : \"missing\";\nprocess.stdout.write(`demo:${demoResult}\\n`);\n", | ||
| "starter": "const routes = { home: \"/\", user: \"/users\" } as const satisfies Record<string, string>;\ntype RouteKey = keyof typeof routes;\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\n// TODO: validate the external text as an own route key.\nconst key = raw as RouteKey;\nprocess.stdout.write(`${routes[key]}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "home\n", | ||
| "output": "/\n" | ||
| }, | ||
| { | ||
| "input": "user\n", | ||
| "output": "/users\n" | ||
| }, | ||
| { | ||
| "input": "other\n", | ||
| "output": "invalid\n" | ||
| }, | ||
| { | ||
| "input": "toString\n", | ||
| "output": "invalid\n" | ||
| }, | ||
| { | ||
| "input": "constructor\n", | ||
| "output": "invalid\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#the-satisfies-operator", | ||
| "https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-discriminated-unions", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "checkpoint", | ||
| "level": "advanced", | ||
| "title": "Checkpoint: exhaust a tagged shape union", | ||
| "body": "Parse rectangle and circle records into variants sharing a literal kind, then switch exhaustively with a never check. Literal tags enable sound narrowing, while satisfies can preserve such tags in configuration objects.", | ||
| "example": "type DemoResult = { kind: \"ok\"; value: number } | { kind: \"error\"; message: string };\nfunction showDemo(result: DemoResult): string {\n switch (result.kind) { case \"ok\": return `value=${result.value}`; case \"error\": return result.message; }\n}\nprocess.stdout.write(`demo:${showDemo({ kind: \"ok\", value: 8 })}\\n`);\n", | ||
| "starter": "type Shape = { kind: \"rect\"; width: number; height: number } | { kind: \"circle\"; radius: number };\nfunction measure(shape: Shape): number {\n if (shape.kind === \"rect\") return shape.width * shape.height;\n // TODO: finish the remaining variant before proving exhaustiveness.\n const exhaustive: never = shape;\n return exhaustive;\n}\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nconst [kind, first = \"0\", second = \"0\"] = raw.split(/\\s+/);\nconst shape: Shape = kind === \"rect\" ? { kind, width: Number(first), height: Number(second) } : { kind: \"circle\", radius: Number(first) };\nprocess.stdout.write(`${measure(shape)}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "rect 3 4\n", | ||
| "output": "12\n" | ||
| }, | ||
| { | ||
| "input": "circle 5\n", | ||
| "output": "10\n" | ||
| }, | ||
| { | ||
| "input": "rect 0 7\n", | ||
| "output": "0\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-async-promise", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Await a typed promise", | ||
| "body": "Return Promise<number> from an async function, await it in main, and attach a top-level rejection handler. Every async function returns a Promise even when its return statement names a plain number.", | ||
| "example": "async function increment(value: number): Promise<number> { return value + 1; }\nasync function demo(): Promise<void> {\n const value = await increment(9);\n process.stdout.write(`demo:${value}\\n`);\n}\ndemo().catch((error: unknown) => process.stdout.write(`demo-error:${String(error)}\\n`));\n", | ||
| "starter": "async function double(value: number): Promise<number> {\n // TODO: resolve with the requested transformation.\n return value;\n}\nasync function main(): Promise<void> {\n const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\n process.stdout.write(`${await double(Number(raw))}\\n`);\n}\nmain().catch((error: unknown) => process.stdout.write(`error:${String(error)}\\n`));\n", | ||
| "cases": [ | ||
| { | ||
| "input": "2\n", | ||
| "output": "4\n" | ||
| }, | ||
| { | ||
| "input": "0\n", | ||
| "output": "0\n" | ||
| }, | ||
| { | ||
| "input": "-3\n", | ||
| "output": "-6\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-7.html#asyncawait-support-in-es6-targets-node-v4" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-modules", | ||
| "aliases": [], | ||
| "track": "lab", | ||
| "kind": "lesson", | ||
| "level": "intermediate", | ||
| "title": "Orient a public API in a single-file runner", | ||
| "body": "Model a public label function behind an explicit API object. This exercise intentionally does not claim multi-file import/export mastery: the judge runs one .ts file under an arbitrary nearest package.json, while real Node projects choose ESM or CommonJS through extensions and package metadata.", | ||
| "example": "type DemoApi = { transform(value: string): string };\nconst demoApi: DemoApi = { transform: (value) => value.split(\"\").reverse().join(\"\") };\nprocess.stdout.write(`demo:${demoApi.transform(\"module\")}\\n`);\n", | ||
| "starter": "type LabelApi = { label(value: string): string };\n// TODO: implement the public surface promised by this local API shape.\nconst api: LabelApi = { label: (value) => value.toLowerCase() };\nconst raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\nprocess.stdout.write(`${api.label(raw)}\\n`);\n", | ||
| "cases": [ | ||
| { | ||
| "input": "ok\n", | ||
| "output": "OK\n" | ||
| }, | ||
| { | ||
| "input": "Ada\n", | ||
| "output": "ADA\n" | ||
| }, | ||
| { | ||
| "input": "é\n", | ||
| "output": "É\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://nodejs.org/docs/latest-v22.x/api/typescript.html#determining-module-system", | ||
| "https://www.typescriptlang.org/docs/handbook/2/modules.html" | ||
| ] | ||
| }, | ||
| { | ||
| "id": "ts-error-handling", | ||
| "aliases": [], | ||
| "track": "core", | ||
| "kind": "capstone", | ||
| "level": "advanced", | ||
| "title": "Capstone: validate and execute commands", | ||
| "body": "Inputs use add or divide commands. Parse them into a discriminated union, reject partial or non-finite numbers, reject zero division, execute asynchronously, and narrow catch values from unknown. Number.parseInt accepts a numeric prefix such as 12px and returns NaN for bad text instead of throwing, so full-token validation must be explicit.", | ||
| "example": "function inspect(token: string): string {\n const value = Number.parseInt(token, 10);\n return Number.isNaN(value) ? \"NaN\" : String(value);\n}\ntry {\n throw \"sample\";\n} catch (error: unknown) {\n const caught = typeof error === \"string\" ? error : \"other\";\n process.stdout.write(`demo:${inspect(\"12px\")}:${inspect(\"bad\")}:${caught}\\n`);\n}\n", | ||
| "starter": "type Command = { kind: \"add\"; left: number; right: number } | { kind: \"divide\"; left: number; right: number };\nfunction parse(input: string): Command {\n const [kind, leftText = \"\", rightText = \"\"] = input.split(/\\s+/);\n // TODO: validate complete finite numeric tokens and reject zero division.\n const left = Number.parseInt(leftText, 10);\n const right = Number.parseInt(rightText, 10);\n return kind === \"divide\" ? { kind, left, right } : { kind: \"add\", left, right };\n}\nasync function execute(command: Command): Promise<number> {\n return command.kind === \"add\" ? command.left + command.right : command.left / command.right;\n}\nasync function main(): Promise<void> {\n const raw = require(\"node:fs\").readFileSync(0, \"utf8\").trim();\n process.stdout.write(`${await execute(parse(raw))}\\n`);\n}\nmain().catch((error: unknown) => process.stdout.write(`${error instanceof Error ? \"invalid\" : \"unknown\"}\\n`));\n", | ||
| "cases": [ | ||
| { | ||
| "input": "add 2 3\n", | ||
| "output": "5\n" | ||
| }, | ||
| { | ||
| "input": "divide 7 2\n", | ||
| "output": "3.5\n" | ||
| }, | ||
| { | ||
| "input": "add 12px 1\n", | ||
| "output": "invalid\n" | ||
| }, | ||
| { | ||
| "input": "divide 1 0\n", | ||
| "output": "invalid\n" | ||
| }, | ||
| { | ||
| "input": "add 1e309 1\n", | ||
| "output": "invalid\n" | ||
| } | ||
| ], | ||
| "refs": [ | ||
| "https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-4.html#defaulting-to-the-unknown-type-in-catch-variables---useunknownincatchvariables", | ||
| "https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions", | ||
| "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt", | ||
| "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN" | ||
| ] | ||
| } | ||
| ] | ||
| } |
| declare const process: { | ||
| stdout: { write(value: string): void }; | ||
| }; | ||
| declare function require(id: "node:fs"): { | ||
| readFileSync(fd: number, encoding: "utf8"): string; | ||
| }; |
+356
| const { spawnSync } = require("node:child_process"); | ||
| const { createHash, randomBytes } = require("node:crypto"); | ||
| const { | ||
| chmodSync, | ||
| createReadStream, | ||
| createWriteStream, | ||
| existsSync, | ||
| mkdirSync, | ||
| readFileSync, | ||
| readdirSync, | ||
| renameSync, | ||
| rmSync, | ||
| statSync, | ||
| writeFileSync, | ||
| } = require("node:fs"); | ||
| const http = require("node:http"); | ||
| const https = require("node:https"); | ||
| const { homedir } = require("node:os"); | ||
| const path = require("node:path"); | ||
| const { Transform } = require("node:stream"); | ||
| const { pipeline } = require("node:stream/promises"); | ||
| const REQUEST_TIMEOUT_MS = 15_000; | ||
| const MAX_REDIRECTS = 5; | ||
| const MAX_MANIFEST_BYTES = 1024 * 1024; | ||
| const STALE_TEMPORARY_FILE_MS = 60 * 60 * 1000; | ||
| const RELEASE_ROOT = "https://github.com/baba9811/practicode/releases/download"; | ||
| function assetFor(platform, arch) { | ||
| const targets = { | ||
| "darwin-arm64": "practicode-aarch64-apple-darwin", | ||
| "darwin-x64": "practicode-x86_64-apple-darwin", | ||
| "linux-arm64": "practicode-aarch64-unknown-linux-musl", | ||
| "linux-x64": "practicode-x86_64-unknown-linux-musl", | ||
| "win32-x64": "practicode-x86_64-pc-windows-msvc.exe", | ||
| }; | ||
| const asset = targets[`${platform}-${arch}`]; | ||
| if (!asset) { | ||
| throw new Error( | ||
| `Unsupported platform: ${platform}/${arch}. ` + | ||
| "Use cargo install practicode or practicode --docker on a supported Docker host.", | ||
| ); | ||
| } | ||
| return asset; | ||
| } | ||
| function cacheDirectory(env = process.env, platform = process.platform, home = homedir()) { | ||
| if (env.PRACTICODE_CACHE_DIR) return path.resolve(env.PRACTICODE_CACHE_DIR); | ||
| if (platform === "win32") { | ||
| return path.join(env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "practicode"); | ||
| } | ||
| if (platform === "darwin") return path.join(home, "Library", "Caches", "practicode"); | ||
| return path.join(env.XDG_CACHE_HOME || path.join(home, ".cache"), "practicode"); | ||
| } | ||
| function checksumFor(manifest, asset) { | ||
| for (const line of manifest.split(/\r?\n/)) { | ||
| const match = line.match(/^([0-9a-fA-F]{64})\s+\*?(.+)$/); | ||
| if (match && match[2] === asset) return match[1].toLowerCase(); | ||
| } | ||
| throw new Error(`SHA256SUMS does not contain ${asset}`); | ||
| } | ||
| function sha256File(filename) { | ||
| return new Promise((resolve, reject) => { | ||
| const hash = createHash("sha256"); | ||
| const input = createReadStream(filename); | ||
| input.on("data", (chunk) => hash.update(chunk)); | ||
| input.on("error", reject); | ||
| input.on("end", () => resolve(hash.digest("hex"))); | ||
| }); | ||
| } | ||
| function responseFor(rawUrl, timeoutMs, redirectsLeft = MAX_REDIRECTS) { | ||
| return new Promise((resolve, reject) => { | ||
| const url = new URL(rawUrl); | ||
| const client = url.protocol === "https:" ? https : url.protocol === "http:" ? http : null; | ||
| if (!client) { | ||
| reject(new Error(`Unsupported release URL protocol: ${url.protocol}`)); | ||
| return; | ||
| } | ||
| const request = client.get( | ||
| url, | ||
| { headers: { "user-agent": "practicode-npm-launcher" } }, | ||
| (response) => { | ||
| const status = response.statusCode || 0; | ||
| if (status >= 300 && status < 400 && response.headers.location) { | ||
| response.resume(); | ||
| if (redirectsLeft === 0) { | ||
| reject(new Error(`Too many redirects while downloading ${rawUrl}`)); | ||
| return; | ||
| } | ||
| const redirected = new URL(response.headers.location, url); | ||
| if (url.protocol === "https:" && redirected.protocol !== "https:") { | ||
| reject(new Error(`Refused insecure redirect for ${rawUrl}`)); | ||
| return; | ||
| } | ||
| responseFor(redirected, timeoutMs, redirectsLeft - 1).then(resolve, reject); | ||
| return; | ||
| } | ||
| if (status !== 200) { | ||
| response.resume(); | ||
| reject(new Error(`HTTP ${status} while downloading ${rawUrl}`)); | ||
| return; | ||
| } | ||
| resolve(response); | ||
| }, | ||
| ); | ||
| request.setTimeout(timeoutMs, () => request.destroy(new Error(`Download timed out: ${rawUrl}`))); | ||
| request.on("error", reject); | ||
| }); | ||
| } | ||
| async function downloadText(url, timeoutMs) { | ||
| const response = await responseFor(url, timeoutMs); | ||
| const chunks = []; | ||
| let bytes = 0; | ||
| for await (const chunk of response) { | ||
| bytes += chunk.length; | ||
| if (bytes > MAX_MANIFEST_BYTES) { | ||
| response.destroy(); | ||
| throw new Error(`Checksum manifest is unexpectedly large: ${url}`); | ||
| } | ||
| chunks.push(chunk); | ||
| } | ||
| return Buffer.concat(chunks).toString("utf8"); | ||
| } | ||
| async function downloadFile(url, filename, timeoutMs) { | ||
| const response = await responseFor(url, timeoutMs); | ||
| const hash = createHash("sha256"); | ||
| const tap = new Transform({ | ||
| transform(chunk, _encoding, callback) { | ||
| hash.update(chunk); | ||
| callback(null, chunk); | ||
| }, | ||
| }); | ||
| await pipeline(response, tap, createWriteStream(filename, { flags: "wx", mode: 0o600 })); | ||
| return hash.digest("hex"); | ||
| } | ||
| function temporaryName(filename) { | ||
| return `${filename}.tmp-${process.pid}-${randomBytes(6).toString("hex")}`; | ||
| } | ||
| function cleanTemporaryFiles(versionDir, asset) { | ||
| if (!existsSync(versionDir)) return; | ||
| const prefixes = [`${asset}.tmp-`, `${asset}.sha256.tmp-`]; | ||
| for (const name of readdirSync(versionDir)) { | ||
| const filename = path.join(versionDir, name); | ||
| try { | ||
| if ( | ||
| prefixes.some((prefix) => name.startsWith(prefix)) && | ||
| Date.now() - statSync(filename).mtimeMs > STALE_TEMPORARY_FILE_MS | ||
| ) { | ||
| rmSync(filename, { force: true }); | ||
| } | ||
| } catch (error) { | ||
| if (error?.code !== "ENOENT") throw error; | ||
| } | ||
| } | ||
| } | ||
| async function cachedBinaryIsValid(binary, asset) { | ||
| const checksumFile = `${binary}.sha256`; | ||
| if (!existsSync(binary) || !existsSync(checksumFile)) return false; | ||
| try { | ||
| const expected = checksumFor(readFileSync(checksumFile, "utf8"), asset); | ||
| return (await sha256File(binary)) === expected; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| async function ensureBinary({ | ||
| version, | ||
| platform = process.platform, | ||
| arch = process.arch, | ||
| cacheDir = cacheDirectory(), | ||
| releaseBaseUrl = `${RELEASE_ROOT}/v${version}`, | ||
| requestTimeoutMs = REQUEST_TIMEOUT_MS, | ||
| }) { | ||
| const asset = assetFor(platform, arch); | ||
| const versionDir = path.join(cacheDir, version); | ||
| const binary = path.join(versionDir, asset); | ||
| cleanTemporaryFiles(versionDir, asset); | ||
| if (await cachedBinaryIsValid(binary, asset)) { | ||
| if (platform !== "win32") chmodSync(binary, 0o755); | ||
| return binary; | ||
| } | ||
| mkdirSync(versionDir, { recursive: true }); | ||
| const temporaryBinary = temporaryName(binary); | ||
| const temporaryChecksum = temporaryName(`${binary}.sha256`); | ||
| try { | ||
| const manifest = await downloadText(`${releaseBaseUrl}/SHA256SUMS`, requestTimeoutMs); | ||
| const expected = checksumFor(manifest, asset); | ||
| const actual = await downloadFile(`${releaseBaseUrl}/${asset}`, temporaryBinary, requestTimeoutMs); | ||
| if (actual !== expected) { | ||
| throw new Error(`Checksum mismatch for ${asset}: expected ${expected}, received ${actual}`); | ||
| } | ||
| if (platform !== "win32") chmodSync(temporaryBinary, 0o755); | ||
| writeFileSync(temporaryChecksum, `${expected} ${asset}\n`, { flag: "wx", mode: 0o600 }); | ||
| try { | ||
| renameSync(temporaryBinary, binary); | ||
| } catch (error) { | ||
| if (existsSync(binary) && (await sha256File(binary)) === expected) { | ||
| rmSync(temporaryBinary, { force: true }); | ||
| } else { | ||
| rmSync(binary, { force: true }); | ||
| renameSync(temporaryBinary, binary); | ||
| } | ||
| } | ||
| try { | ||
| renameSync(temporaryChecksum, `${binary}.sha256`); | ||
| } catch (error) { | ||
| const installedChecksum = `${binary}.sha256`; | ||
| if ( | ||
| existsSync(installedChecksum) && | ||
| checksumFor(readFileSync(installedChecksum, "utf8"), asset) === expected | ||
| ) { | ||
| rmSync(temporaryChecksum, { force: true }); | ||
| } else { | ||
| rmSync(installedChecksum, { force: true }); | ||
| renameSync(temporaryChecksum, installedChecksum); | ||
| } | ||
| } | ||
| return binary; | ||
| } catch (error) { | ||
| rmSync(temporaryBinary, { force: true }); | ||
| rmSync(temporaryChecksum, { force: true }); | ||
| const detail = error instanceof Error ? error.message : String(error); | ||
| throw new Error( | ||
| `Unable to download a verified Practicode ${version} binary. ${detail}\n` + | ||
| "If you are offline, reconnect once to populate the cache, use practicode --docker, " + | ||
| "or install explicitly with cargo install practicode.", | ||
| ); | ||
| } | ||
| } | ||
| function dockerInstallCommand(platform = process.platform) { | ||
| if (platform === "win32") return "winget install -e --id Docker.DockerDesktop"; | ||
| if (platform === "darwin") return "brew install --cask docker"; | ||
| return "https://docs.docker.com/engine/install/"; | ||
| } | ||
| function printDockerHelp() { | ||
| console.error("practicode: Docker is required for --docker sandbox mode."); | ||
| console.error(`Install Docker: ${dockerInstallCommand()}`); | ||
| console.error("Start Docker, then run practicode --docker again."); | ||
| } | ||
| function runDockerSandbox(root, version, forwardedArgs) { | ||
| const dockerImage = `practicode-sandbox:${version}`; | ||
| const dockerVersion = spawnSync("docker", ["version"], { stdio: "ignore" }); | ||
| if (dockerVersion.error || dockerVersion.status !== 0) { | ||
| if (dockerVersion.error) { | ||
| console.error(`practicode: failed to run docker: ${dockerVersion.error.message}`); | ||
| } | ||
| printDockerHelp(); | ||
| return 1; | ||
| } | ||
| const build = spawnSync("docker", ["build", "-t", dockerImage, root], { stdio: "inherit" }); | ||
| if (build.error || build.status !== 0) { | ||
| if (build.error) console.error(`practicode: failed to build Docker sandbox: ${build.error.message}`); | ||
| else console.error("practicode: Docker sandbox build failed."); | ||
| printDockerHelp(); | ||
| return build.status || 1; | ||
| } | ||
| const dataHome = path.resolve( | ||
| process.env.PRACTICODE_HOME || path.join(homedir(), ".practicode"), | ||
| ); | ||
| mkdirSync(dataHome, { recursive: true }); | ||
| const runArgs = [ | ||
| "run", | ||
| "--rm", | ||
| process.stdin.isTTY ? "-it" : "-i", | ||
| "--init", | ||
| "--network", | ||
| "none", | ||
| "--cpus", | ||
| "2", | ||
| "--memory", | ||
| "1g", | ||
| "--pids-limit", | ||
| "256", | ||
| "--cap-drop", | ||
| "ALL", | ||
| "--security-opt", | ||
| "no-new-privileges", | ||
| "--read-only", | ||
| "--tmpfs", | ||
| "/tmp:rw,nosuid,nodev,size=256m,mode=1777", | ||
| "--mount", | ||
| `type=bind,source=${process.cwd()},target=/workspace,readonly`, | ||
| "--mount", | ||
| `type=bind,source=${dataHome},target=/data`, | ||
| "-w", | ||
| "/workspace", | ||
| "-e", | ||
| `TERM=${process.env.TERM || "xterm-256color"}`, | ||
| "-e", | ||
| "HOME=/tmp", | ||
| "-e", | ||
| "PRACTICODE_HOME=/data", | ||
| "-e", | ||
| "PRACTICODE_NO_UPDATE_CHECK=1", | ||
| ]; | ||
| if (process.env.COLORTERM) runArgs.push("-e", `COLORTERM=${process.env.COLORTERM}`); | ||
| if (typeof process.getuid === "function" && typeof process.getgid === "function") { | ||
| runArgs.push("--user", `${process.getuid()}:${process.getgid()}`); | ||
| } | ||
| runArgs.push(dockerImage, ...forwardedArgs); | ||
| const run = spawnSync("docker", runArgs, { cwd: process.cwd(), stdio: "inherit" }); | ||
| if (run.error) { | ||
| console.error(`practicode: failed to run Docker sandbox: ${run.error.message}`); | ||
| printDockerHelp(); | ||
| return 1; | ||
| } | ||
| return run.status ?? 1; | ||
| } | ||
| async function main(args, root = path.resolve(__dirname, "..")) { | ||
| const packageJson = require(path.join(root, "package.json")); | ||
| const dockerIndex = args.indexOf("--docker"); | ||
| if (dockerIndex !== -1) { | ||
| const forwarded = [...args]; | ||
| forwarded.splice(dockerIndex, 1); | ||
| return runDockerSandbox(root, packageJson.version, forwarded); | ||
| } | ||
| const binary = await ensureBinary({ | ||
| version: packageJson.version, | ||
| cacheDir: cacheDirectory(), | ||
| releaseBaseUrl: | ||
| process.env.PRACTICODE_RELEASE_BASE_URL || `${RELEASE_ROOT}/v${packageJson.version}`, | ||
| }); | ||
| const run = spawnSync(binary, args, { cwd: process.cwd(), stdio: "inherit" }); | ||
| if (run.error) { | ||
| throw new Error(`Failed to run cached Practicode binary: ${run.error.message}`); | ||
| } | ||
| return run.status ?? 1; | ||
| } | ||
| module.exports = { | ||
| assetFor, | ||
| cacheDirectory, | ||
| checksumFor, | ||
| ensureBinary, | ||
| main, | ||
| sha256File, | ||
| }; |
| # A Credible Path To 10,000 Stars | ||
| Ten thousand stars cannot be engineered as a release checkbox. They can be earned by making one promise unusually clear and dependable: **15 focused minutes a day to become productive in another programming language, entirely from the terminal.** | ||
| This plan uses public project signals and opt-in feedback. Practicode will not add in-app analytics, accounts, streak pressure, referral popups, or telemetry to chase a vanity metric. | ||
| ## Product Standard | ||
| Before promotion, every release should preserve: | ||
| - a one-command install that does not compile Rust; | ||
| - a useful first session in under one minute; | ||
| - executable, independently reviewed curriculum copy in all five UI languages; | ||
| - deterministic offline mastery, review, and judging; | ||
| - a responsive `60x16` minimum-terminal experience and clear keyboard help; | ||
| - transparent AI/privacy boundaries; | ||
| - reproducible release assets and checksums. | ||
| Stars follow retention and trust. If those standards regress, pause promotion and fix the product. | ||
| ## Milestones | ||
| | Stage | Product proof | Distribution work | Community work | | ||
| | --- | --- | --- | --- | | ||
| | 0 → 1,000 | 0.2.0 prebuilt install, polished 30-second demo, clean first session | Coordinated launch post with one promise and one command | Fast bug triage, label precise starter issues | | ||
| | 1,000 → 3,000 | Publish opt-in learner stories and lesson corrections | Technical posts comparing one concept across four languages | Document the lesson-review workflow; recognize contributors in releases | | ||
| | 3,000 → 10,000 | Stable releases, public curriculum quality report, strong install reliability | Educator/team case studies, conference demos, translated launch material | Recurring focused correction/accessibility sprints without expanding scope | | ||
| ## Launch Package | ||
| Each announcement should contain only what a developer needs to decide in 30 seconds: | ||
| 1. The 15-minute switching-languages promise. | ||
| 2. A short terminal recording showing Predict → Exercise → Result → review scheduling. | ||
| 3. `npm install -g practicode && practicode`. | ||
| 4. Four concrete trust facts: 110 lessons, 337 cases, five localized curricula and core learning loops, no account/telemetry. | ||
| 5. One focused request: try a language you already know and report the first inaccurate or awkward lesson. | ||
| Reuse the same factual launch package on the GitHub release, project website/readme, developer communities, and social posts. Change the introduction for each audience; do not manufacture engagement or mass-post identical spam. | ||
| ## Feedback Flywheel | ||
| - Turn a precise report into a small, linked issue within 48 hours when possible. | ||
| - Add a regression case before changing executable lesson behavior. | ||
| - Record independent content review and exact hashes in the manifest. | ||
| - Ship a concise release note that names the corrected learner outcome. | ||
| - Invite the reporter to verify the fix, without asking for a star. | ||
| This converts attention into visible product quality and makes future contributors confident that lesson corrections are taken seriously. | ||
| ## Privacy-Safe Scoreboard | ||
| Review monthly using only public or opt-in evidence: | ||
| - GitHub stars, forks, contributors, issue response time, and release downloads; | ||
| - npm and crates.io public download counts; | ||
| - CI/release success rate and launcher failure reports; | ||
| - opt-in survey answers for first-session completion and seven-day return; | ||
| - number and severity of independently verified lesson corrections. | ||
| Do not infer learner behavior from local progress files or add telemetry. A large star count with broken installs, unresolved lesson errors, or slow triage is not success. | ||
| ## Scope Guardrails | ||
| Until the four existing courses show strong completion evidence, do not dilute the product with extra languages, cloud sync, social leagues, plugin marketplaces, or streak mechanics. Improve lesson accuracy, onboarding, accessibility, release reliability, and contributor turnaround first. |
| use super::*; | ||
| const DAY_SECONDS: u64 = 86_400; | ||
| const THREE_DAYS_SECONDS: u64 = 259_200; | ||
| const SEVEN_DAYS_SECONDS: u64 = 604_800; | ||
| pub(super) fn unix_timestamp_now() -> u64 { | ||
| std::time::SystemTime::now() | ||
| .duration_since(std::time::UNIX_EPOCH) | ||
| .unwrap_or_default() | ||
| .as_secs() | ||
| } | ||
| pub fn record_syntax_result( | ||
| state: &mut AppState, | ||
| language: &str, | ||
| lesson_id: &str, | ||
| passed: bool, | ||
| now: u64, | ||
| assisted: bool, | ||
| ) { | ||
| let language = normalize_language(language); | ||
| let lessons = syntax_lessons_for(&language); | ||
| record_syntax_result_for_lessons(state, &language, lesson_id, passed, now, assisted, &lessons); | ||
| } | ||
| pub(crate) fn record_syntax_result_for_lessons( | ||
| state: &mut AppState, | ||
| language: &str, | ||
| lesson_id: &str, | ||
| passed: bool, | ||
| now: u64, | ||
| assisted: bool, | ||
| lessons: &[&SyntaxLesson], | ||
| ) { | ||
| let Some(lesson) = lessons.iter().find(|lesson| lesson.id == lesson_id) else { | ||
| return; | ||
| }; | ||
| let mastery = state | ||
| .syntax_mastery | ||
| .entry(language.to_string()) | ||
| .or_default() | ||
| .entry(lesson_id.to_string()) | ||
| .or_default(); | ||
| mastery.attempts = mastery.attempts.saturating_add(1); | ||
| if (mastery.stage != MasteryStage::New | ||
| && syntax_review_due_at(mastery, now).is_none_or(|due_at| due_at > now)) | ||
| || (passed && assisted && lesson.kind == SyntaxKind::Capstone) | ||
| { | ||
| return; | ||
| } | ||
| if passed { | ||
| let (stage, delay) = match mastery.stage { | ||
| MasteryStage::New => (MasteryStage::Practiced, DAY_SECONDS), | ||
| MasteryStage::Practiced => (MasteryStage::Retained, THREE_DAYS_SECONDS), | ||
| MasteryStage::Retained | MasteryStage::Mastered => { | ||
| (MasteryStage::Mastered, SEVEN_DAYS_SECONDS) | ||
| } | ||
| }; | ||
| mastery.stage = stage; | ||
| mastery.review_due_at = now.saturating_add(delay); | ||
| } else { | ||
| mastery.stage = match mastery.stage { | ||
| MasteryStage::Mastered => MasteryStage::Retained, | ||
| MasteryStage::Retained => MasteryStage::Practiced, | ||
| MasteryStage::Practiced | MasteryStage::New => MasteryStage::New, | ||
| }; | ||
| mastery.review_due_at = now; | ||
| } | ||
| if syntax_course_completed(state, language, lessons) | ||
| && !state | ||
| .completed_syntax_courses | ||
| .iter() | ||
| .any(|completed| completed == language) | ||
| { | ||
| state.completed_syntax_courses.push(language.to_string()); | ||
| } | ||
| } | ||
| pub(crate) fn syntax_review_due_at(mastery: &LessonMastery, now: u64) -> Option<u64> { | ||
| let max_delay = match mastery.stage { | ||
| MasteryStage::New => return None, | ||
| MasteryStage::Practiced => DAY_SECONDS, | ||
| MasteryStage::Retained => THREE_DAYS_SECONDS, | ||
| MasteryStage::Mastered => SEVEN_DAYS_SECONDS, | ||
| }; | ||
| Some(if mastery.review_due_at > now.saturating_add(max_delay) { | ||
| now | ||
| } else { | ||
| mastery.review_due_at | ||
| }) | ||
| } | ||
| fn syntax_course_completed(state: &AppState, language: &str, lessons: &[&SyntaxLesson]) -> bool { | ||
| let Some(mastery) = state.syntax_mastery.get(language) else { | ||
| return false; | ||
| }; | ||
| let retained = |lesson: &&SyntaxLesson| { | ||
| mastery.get(lesson.id).is_some_and(|progress| { | ||
| matches!( | ||
| progress.stage, | ||
| MasteryStage::Retained | MasteryStage::Mastered | ||
| ) | ||
| }) | ||
| }; | ||
| let all_core_retained = lessons | ||
| .iter() | ||
| .filter(|lesson| lesson.track == SyntaxTrack::Core) | ||
| .all(retained); | ||
| let checkpoints_passed = lessons | ||
| .iter() | ||
| .filter(|lesson| lesson.track == SyntaxTrack::Core && lesson.kind == SyntaxKind::Checkpoint) | ||
| .all(retained); | ||
| let capstone_retained = lessons | ||
| .iter() | ||
| .find(|lesson| lesson.track == SyntaxTrack::Core && lesson.kind == SyntaxKind::Capstone) | ||
| .is_some_and(retained); | ||
| all_core_retained && checkpoints_passed && capstone_retained | ||
| } | ||
| pub fn due_syntax_lessons( | ||
| state: &AppState, | ||
| language: &str, | ||
| now: u64, | ||
| limit: usize, | ||
| ) -> Vec<&'static SyntaxLesson> { | ||
| let language = normalize_language(language); | ||
| let lessons = syntax_lessons_for(&language); | ||
| due_syntax_lessons_for(state, &language, now, limit, &lessons) | ||
| } | ||
| fn due_syntax_lessons_for<'a>( | ||
| state: &AppState, | ||
| language: &str, | ||
| now: u64, | ||
| limit: usize, | ||
| lessons: &[&'a SyntaxLesson], | ||
| ) -> Vec<&'a SyntaxLesson> { | ||
| let mut due = due_syntax_lesson_candidates(state, language, now, lessons); | ||
| due.sort_by_key(|&(review_due_at, index, _)| (review_due_at, index)); | ||
| due.into_iter() | ||
| .take(limit.min(2)) | ||
| .map(|(_, _, lesson)| lesson) | ||
| .collect() | ||
| } | ||
| pub fn due_syntax_lesson_count(state: &AppState, language: &str, now: u64) -> usize { | ||
| let language = normalize_language(language); | ||
| let lessons = syntax_lessons_for(&language); | ||
| due_syntax_lesson_candidates(state, &language, now, &lessons).len() | ||
| } | ||
| fn due_syntax_lesson_candidates<'a>( | ||
| state: &AppState, | ||
| language: &str, | ||
| now: u64, | ||
| lessons: &[&'a SyntaxLesson], | ||
| ) -> Vec<(u64, usize, &'a SyntaxLesson)> { | ||
| let Some(mastery) = state.syntax_mastery.get(language) else { | ||
| return Vec::new(); | ||
| }; | ||
| lessons | ||
| .iter() | ||
| .copied() | ||
| .enumerate() | ||
| .filter_map(|(index, lesson)| { | ||
| let progress = mastery.get(lesson.id)?; | ||
| let review_due_at = syntax_review_due_at(progress, now)?; | ||
| (lesson.track != SyntaxTrack::Lab && review_due_at <= now).then_some(( | ||
| review_due_at, | ||
| index, | ||
| lesson, | ||
| )) | ||
| }) | ||
| .collect() | ||
| } | ||
| pub fn record_syntax_test_out(state: &mut AppState, language: &str, lesson_ids: &[&str], now: u64) { | ||
| let language = normalize_language(language); | ||
| let lessons = syntax_lessons_for(&language); | ||
| record_syntax_test_out_for_lessons(state, &language, lesson_ids, now, &lessons); | ||
| } | ||
| fn record_syntax_test_out_for_lessons( | ||
| state: &mut AppState, | ||
| language: &str, | ||
| lesson_ids: &[&str], | ||
| now: u64, | ||
| lessons: &[&SyntaxLesson], | ||
| ) { | ||
| for lesson_id in lesson_ids { | ||
| let Some(lesson) = super::syntax::resolve_syntax_lesson(lessons, lesson_id) else { | ||
| continue; | ||
| }; | ||
| if lesson.track != SyntaxTrack::Core || lesson.kind != SyntaxKind::Lesson { | ||
| continue; | ||
| } | ||
| let mastery = state | ||
| .syntax_mastery | ||
| .entry(language.to_string()) | ||
| .or_default() | ||
| .entry(lesson.id.to_string()) | ||
| .or_default(); | ||
| mastery.attempts = mastery.attempts.saturating_add(1); | ||
| if mastery.stage == MasteryStage::New { | ||
| mastery.stage = MasteryStage::Practiced; | ||
| mastery.review_due_at = now.saturating_add(DAY_SECONDS); | ||
| } | ||
| } | ||
| } | ||
| pub fn migrate_syntax_mastery(state: &mut AppState, now: u64) { | ||
| for language in LANGUAGES { | ||
| normalize_syntax_mastery_for_lessons(state, language, &syntax_lessons_for(language)); | ||
| } | ||
| let legacy = std::mem::take(&mut state.syntax_progress); | ||
| for (language, ids) in legacy { | ||
| if !LANGUAGES.contains(&language.as_str()) { | ||
| state.syntax_progress.insert(language, ids); | ||
| continue; | ||
| } | ||
| let lessons = syntax_lessons_for(&language); | ||
| let unknown = migrate_syntax_mastery_for_lessons(state, &language, ids, now, &lessons); | ||
| if !unknown.is_empty() { | ||
| state.syntax_progress.insert(language, unknown); | ||
| } | ||
| } | ||
| } | ||
| fn normalize_syntax_mastery_for_lessons( | ||
| state: &mut AppState, | ||
| language: &str, | ||
| lessons: &[&SyntaxLesson], | ||
| ) { | ||
| let Some(mut stored) = state.syntax_mastery.remove(language) else { | ||
| return; | ||
| }; | ||
| let mut normalized = HashMap::new(); | ||
| for lesson in lessons { | ||
| let mut current = stored.remove(lesson.id); | ||
| for alias in lesson.aliases { | ||
| if let Some(alias_mastery) = stored.remove(*alias) { | ||
| current = Some(match current { | ||
| Some(canonical) => merge_lesson_mastery(canonical, alias_mastery), | ||
| None => alias_mastery, | ||
| }); | ||
| } | ||
| } | ||
| if let Some(mastery) = current { | ||
| normalized.insert(lesson.id.to_string(), mastery); | ||
| } | ||
| } | ||
| normalized.extend(stored); | ||
| if !normalized.is_empty() { | ||
| state | ||
| .syntax_mastery | ||
| .insert(language.to_string(), normalized); | ||
| } | ||
| } | ||
| fn merge_lesson_mastery(mut canonical: LessonMastery, mut alias: LessonMastery) -> LessonMastery { | ||
| let rank = |stage| match stage { | ||
| MasteryStage::New => 0, | ||
| MasteryStage::Practiced => 1, | ||
| MasteryStage::Retained => 2, | ||
| MasteryStage::Mastered => 3, | ||
| }; | ||
| let attempts = canonical.attempts.max(alias.attempts); | ||
| if rank(alias.stage) > rank(canonical.stage) { | ||
| alias.attempts = attempts; | ||
| alias | ||
| } else { | ||
| canonical.attempts = attempts; | ||
| canonical | ||
| } | ||
| } | ||
| fn migrate_syntax_mastery_for_lessons( | ||
| state: &mut AppState, | ||
| language: &str, | ||
| ids: Vec<String>, | ||
| now: u64, | ||
| lessons: &[&SyntaxLesson], | ||
| ) -> Vec<String> { | ||
| let mut unknown = Vec::new(); | ||
| for id in ids { | ||
| let Some(canonical) = | ||
| super::syntax::resolve_syntax_lesson(lessons, &id).map(|lesson| lesson.id) | ||
| else { | ||
| unknown.push(id); | ||
| continue; | ||
| }; | ||
| state | ||
| .syntax_mastery | ||
| .entry(language.to_string()) | ||
| .or_default() | ||
| .entry(canonical.to_string()) | ||
| .and_modify(|mastery| { | ||
| if mastery.stage == MasteryStage::New { | ||
| mastery.stage = MasteryStage::Practiced; | ||
| mastery.review_due_at = now; | ||
| mastery.attempts = mastery.attempts.saturating_add(1); | ||
| } | ||
| }) | ||
| .or_insert(LessonMastery { | ||
| stage: MasteryStage::Practiced, | ||
| review_due_at: now, | ||
| attempts: 1, | ||
| }); | ||
| } | ||
| unknown | ||
| } | ||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| fn state() -> AppState { | ||
| AppState { | ||
| current_problem: "001-hello-world".to_string(), | ||
| settings: Settings::default(), | ||
| solved: Vec::new(), | ||
| history: Vec::new(), | ||
| suggested_next_difficulty: "easy".to_string(), | ||
| syntax_progress: HashMap::new(), | ||
| current_syntax_lesson: HashMap::new(), | ||
| syntax_mastery: HashMap::new(), | ||
| completed_syntax_courses: Vec::new(), | ||
| } | ||
| } | ||
| fn lesson(id: &'static str, track: SyntaxTrack, kind: SyntaxKind) -> SyntaxLesson { | ||
| SyntaxLesson { | ||
| id, | ||
| aliases: &[], | ||
| language: "rust", | ||
| track, | ||
| kind, | ||
| level: "basic", | ||
| title: id, | ||
| body: id, | ||
| example: "fn main() {}", | ||
| exercise: SyntaxExercise { | ||
| prompt: id, | ||
| starter: id, | ||
| cases: &[], | ||
| }, | ||
| refs: &[], | ||
| } | ||
| } | ||
| #[test] | ||
| fn syntax_mastery_assistance_capstone_checkpoints_and_labs_gate_completion() { | ||
| let course = [ | ||
| lesson("core", SyntaxTrack::Core, SyntaxKind::Lesson), | ||
| lesson("checkpoint", SyntaxTrack::Core, SyntaxKind::Checkpoint), | ||
| lesson("capstone", SyntaxTrack::Core, SyntaxKind::Capstone), | ||
| lesson("lab", SyntaxTrack::Lab, SyntaxKind::Lesson), | ||
| ]; | ||
| let lessons = course.iter().collect::<Vec<_>>(); | ||
| let mut state = state(); | ||
| for now in [1_000, 87_400] { | ||
| record_syntax_result_for_lessons(&mut state, "rust", "core", true, now, true, &lessons); | ||
| record_syntax_result_for_lessons( | ||
| &mut state, | ||
| "rust", | ||
| "checkpoint", | ||
| true, | ||
| now, | ||
| false, | ||
| &lessons, | ||
| ); | ||
| } | ||
| record_syntax_result_for_lessons( | ||
| &mut state, "rust", "capstone", true, 1_000, true, &lessons, | ||
| ); | ||
| assert_eq!( | ||
| state.syntax_mastery["rust"]["core"].stage, | ||
| MasteryStage::Retained | ||
| ); | ||
| assert_eq!( | ||
| state.syntax_mastery["rust"]["capstone"].stage, | ||
| MasteryStage::New | ||
| ); | ||
| assert!(state.completed_syntax_courses.is_empty()); | ||
| for now in [1_000, 87_400] { | ||
| record_syntax_result_for_lessons( | ||
| &mut state, "rust", "capstone", true, now, false, &lessons, | ||
| ); | ||
| } | ||
| assert_eq!( | ||
| state.syntax_mastery["rust"]["capstone"].stage, | ||
| MasteryStage::Retained | ||
| ); | ||
| assert_eq!(state.completed_syntax_courses, ["rust"]); | ||
| } | ||
| #[test] | ||
| fn syntax_mastery_due_reviews_exclude_labs() { | ||
| let course = [ | ||
| lesson("core", SyntaxTrack::Core, SyntaxKind::Lesson), | ||
| lesson("lab", SyntaxTrack::Lab, SyntaxKind::Lesson), | ||
| ]; | ||
| let lessons = course.iter().collect::<Vec<_>>(); | ||
| let mut state = state(); | ||
| record_syntax_result_for_lessons(&mut state, "rust", "core", true, 1_000, false, &lessons); | ||
| record_syntax_result_for_lessons(&mut state, "rust", "lab", true, 1_000, false, &lessons); | ||
| let due = due_syntax_lessons_for(&state, "rust", 100_000, 10, &lessons) | ||
| .into_iter() | ||
| .map(|lesson| lesson.id) | ||
| .collect::<Vec<_>>(); | ||
| assert_eq!(due, ["core"]); | ||
| } | ||
| #[test] | ||
| fn syntax_mastery_test_out_seeds_only_core_lessons() { | ||
| let course = [ | ||
| lesson("core", SyntaxTrack::Core, SyntaxKind::Lesson), | ||
| lesson("checkpoint", SyntaxTrack::Core, SyntaxKind::Checkpoint), | ||
| lesson("capstone", SyntaxTrack::Core, SyntaxKind::Capstone), | ||
| lesson("lab", SyntaxTrack::Lab, SyntaxKind::Lesson), | ||
| ]; | ||
| let lessons = course.iter().collect::<Vec<_>>(); | ||
| let mut state = state(); | ||
| record_syntax_test_out_for_lessons( | ||
| &mut state, | ||
| "rust", | ||
| &["core", "checkpoint", "capstone", "lab"], | ||
| 1_000, | ||
| &lessons, | ||
| ); | ||
| assert_eq!( | ||
| state.syntax_mastery["rust"].keys().collect::<Vec<_>>(), | ||
| ["core"] | ||
| ); | ||
| assert_eq!( | ||
| state.syntax_mastery["rust"]["core"].stage, | ||
| MasteryStage::Practiced | ||
| ); | ||
| } | ||
| #[test] | ||
| fn syntax_mastery_migration_maps_aliases_to_current_ids() { | ||
| let mut current = lesson("current", SyntaxTrack::Core, SyntaxKind::Lesson); | ||
| current.aliases = &["legacy"]; | ||
| let lessons = [¤t]; | ||
| let mut state = state(); | ||
| let unknown = migrate_syntax_mastery_for_lessons( | ||
| &mut state, | ||
| "rust", | ||
| vec!["legacy".to_string()], | ||
| 1_000, | ||
| &lessons, | ||
| ); | ||
| assert!(unknown.is_empty()); | ||
| assert_eq!( | ||
| state.syntax_mastery["rust"]["current"], | ||
| LessonMastery { | ||
| stage: MasteryStage::Practiced, | ||
| review_due_at: 1_000, | ||
| attempts: 1, | ||
| } | ||
| ); | ||
| } | ||
| #[test] | ||
| fn syntax_mastery_migration_prefers_an_exact_id_over_an_alias() { | ||
| let mut first = lesson("first", SyntaxTrack::Core, SyntaxKind::Lesson); | ||
| first.aliases = &["second"]; | ||
| let second = lesson("second", SyntaxTrack::Core, SyntaxKind::Lesson); | ||
| let lessons = [&first, &second]; | ||
| let mut state = state(); | ||
| migrate_syntax_mastery_for_lessons( | ||
| &mut state, | ||
| "rust", | ||
| vec!["second".to_string()], | ||
| 1_000, | ||
| &lessons, | ||
| ); | ||
| assert!(state.syntax_mastery["rust"].contains_key("second")); | ||
| assert!(!state.syntax_mastery["rust"].contains_key("first")); | ||
| } | ||
| #[test] | ||
| fn syntax_mastery_migration_normalizes_existing_alias_keys_idempotently() { | ||
| let mut first = lesson("first", SyntaxTrack::Core, SyntaxKind::Lesson); | ||
| first.aliases = &["old-first"]; | ||
| let mut second = lesson("second", SyntaxTrack::Core, SyntaxKind::Lesson); | ||
| second.aliases = &["old-second"]; | ||
| let lessons = [&first, &second]; | ||
| let mut state = state(); | ||
| state.syntax_mastery.insert( | ||
| "rust".to_string(), | ||
| HashMap::from([ | ||
| ( | ||
| "first".to_string(), | ||
| LessonMastery { | ||
| stage: MasteryStage::Retained, | ||
| review_due_at: 700, | ||
| attempts: 4, | ||
| }, | ||
| ), | ||
| ( | ||
| "old-first".to_string(), | ||
| LessonMastery { | ||
| stage: MasteryStage::Practiced, | ||
| review_due_at: 500, | ||
| attempts: 2, | ||
| }, | ||
| ), | ||
| ( | ||
| "second".to_string(), | ||
| LessonMastery { | ||
| stage: MasteryStage::Practiced, | ||
| review_due_at: 500, | ||
| attempts: 2, | ||
| }, | ||
| ), | ||
| ( | ||
| "old-second".to_string(), | ||
| LessonMastery { | ||
| stage: MasteryStage::Retained, | ||
| review_due_at: 700, | ||
| attempts: 4, | ||
| }, | ||
| ), | ||
| ( | ||
| "unknown".to_string(), | ||
| LessonMastery { | ||
| stage: MasteryStage::Mastered, | ||
| review_due_at: 900, | ||
| attempts: 9, | ||
| }, | ||
| ), | ||
| ]), | ||
| ); | ||
| normalize_syntax_mastery_for_lessons(&mut state, "rust", &lessons); | ||
| let normalized = state.syntax_mastery["rust"].clone(); | ||
| normalize_syntax_mastery_for_lessons(&mut state, "rust", &lessons); | ||
| assert_eq!(state.syntax_mastery["rust"], normalized); | ||
| assert_eq!(normalized["first"].stage, MasteryStage::Retained); | ||
| assert_eq!(normalized["first"].attempts, 4); | ||
| assert_eq!(normalized["first"].review_due_at, 700); | ||
| assert_eq!(normalized["second"].stage, MasteryStage::Retained); | ||
| assert_eq!(normalized["second"].attempts, 4); | ||
| assert_eq!(normalized["second"].review_due_at, 700); | ||
| assert_eq!(normalized["unknown"].stage, MasteryStage::Mastered); | ||
| assert!(!normalized.contains_key("old-first")); | ||
| assert!(!normalized.contains_key("old-second")); | ||
| } | ||
| } |
| use super::*; | ||
| #[cfg(test)] | ||
| use crate::core::SyntaxKind; | ||
| use crate::core::{ | ||
| MasteryStage, SyntaxLesson, SyntaxTrack, due_syntax_lesson_count, due_syntax_lessons, | ||
| syntax_lessons_for, | ||
| }; | ||
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
| pub enum LearningStep { | ||
| Review, | ||
| Delta, | ||
| Predict, | ||
| Exercise, | ||
| Reflect, | ||
| Complete, | ||
| } | ||
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
| pub(super) enum LearningView { | ||
| Lesson, | ||
| Code, | ||
| Result, | ||
| } | ||
| pub(super) fn unix_time_now() -> u64 { | ||
| std::time::SystemTime::now() | ||
| .duration_since(std::time::UNIX_EPOCH) | ||
| .unwrap_or_default() | ||
| .as_secs() | ||
| } | ||
| pub(super) fn learning_step_label(language: &str, step: LearningStep) -> &'static str { | ||
| ui_text( | ||
| language, | ||
| match step { | ||
| LearningStep::Review => "learning_step_review", | ||
| LearningStep::Delta => "learning_step_delta", | ||
| LearningStep::Predict => "learning_step_predict", | ||
| LearningStep::Exercise => "learning_step_exercise", | ||
| LearningStep::Reflect => "learning_step_reflect", | ||
| LearningStep::Complete => "learning_step_complete", | ||
| }, | ||
| ) | ||
| } | ||
| pub(super) fn learning_view_label(language: &str, view: LearningView) -> &'static str { | ||
| ui_text( | ||
| language, | ||
| match view { | ||
| LearningView::Lesson => "learning_view_lesson", | ||
| LearningView::Code => "learning_view_code", | ||
| LearningView::Result => "learning_view_result", | ||
| }, | ||
| ) | ||
| } | ||
| #[derive(Clone, Copy, Debug)] | ||
| struct LearningItem { | ||
| lesson_id: &'static str, | ||
| review: bool, | ||
| } | ||
| #[derive(Debug)] | ||
| pub(super) struct LearningSession { | ||
| guided: bool, | ||
| queue: Vec<LearningItem>, | ||
| index: usize, | ||
| step: LearningStep, | ||
| view: LearningView, | ||
| assisted: bool, | ||
| } | ||
| pub(super) enum LearningAdvance { | ||
| Step, | ||
| Item(&'static str), | ||
| Blocked, | ||
| Complete, | ||
| Manual, | ||
| } | ||
| impl LearningSession { | ||
| pub(super) fn inactive() -> Self { | ||
| Self { | ||
| guided: false, | ||
| queue: Vec::new(), | ||
| index: 0, | ||
| step: LearningStep::Complete, | ||
| view: LearningView::Code, | ||
| assisted: false, | ||
| } | ||
| } | ||
| pub(super) fn start(state: &AppState, language: &str, now: u64) -> Self { | ||
| let language = normalize_language(language); | ||
| let lessons = syntax_lessons_for(&language); | ||
| let due = due_syntax_lessons(state, &language, now, 2); | ||
| let queue = session_queue(state, &language, &lessons, &due); | ||
| let step = queue | ||
| .first() | ||
| .map(|item| { | ||
| if item.review { | ||
| LearningStep::Review | ||
| } else { | ||
| LearningStep::Delta | ||
| } | ||
| }) | ||
| .unwrap_or(LearningStep::Complete); | ||
| Self { | ||
| guided: true, | ||
| queue, | ||
| index: 0, | ||
| step, | ||
| view: LearningView::Lesson, | ||
| assisted: false, | ||
| } | ||
| } | ||
| pub(super) fn step(&self) -> LearningStep { | ||
| self.step | ||
| } | ||
| pub(super) fn is_guided(&self) -> bool { | ||
| self.guided | ||
| } | ||
| pub(super) fn view(&self) -> LearningView { | ||
| self.view | ||
| } | ||
| pub(super) fn set_view(&mut self, view: LearningView) { | ||
| self.view = view; | ||
| } | ||
| pub(super) fn current_lesson_id(&self) -> Option<&'static str> { | ||
| self.queue.get(self.index).map(|item| item.lesson_id) | ||
| } | ||
| #[cfg(test)] | ||
| fn queue_ids(&self) -> Vec<&'static str> { | ||
| self.queue.iter().map(|item| item.lesson_id).collect() | ||
| } | ||
| pub(super) fn assisted(&self) -> bool { | ||
| self.assisted | ||
| } | ||
| pub(super) fn can_judge(&self) -> bool { | ||
| !self.guided || self.step == LearningStep::Exercise | ||
| } | ||
| pub(super) fn mark_assisted(&mut self) { | ||
| if !self.guided | ||
| || (self.queue.get(self.index).is_some() | ||
| && matches!( | ||
| self.step, | ||
| LearningStep::Review | ||
| | LearningStep::Delta | ||
| | LearningStep::Predict | ||
| | LearningStep::Exercise | ||
| )) | ||
| { | ||
| self.assisted = true; | ||
| } | ||
| } | ||
| pub(super) fn finish_judge(&mut self, passed: bool) { | ||
| self.assisted = false; | ||
| self.view = LearningView::Result; | ||
| if !self.guided || self.step == LearningStep::Complete || self.queue.is_empty() { | ||
| return; | ||
| } | ||
| self.step = if passed { | ||
| LearningStep::Reflect | ||
| } else { | ||
| LearningStep::Exercise | ||
| }; | ||
| } | ||
| pub(super) fn advance(&mut self) -> LearningAdvance { | ||
| match self.step { | ||
| LearningStep::Review | LearningStep::Delta => { | ||
| self.step = LearningStep::Predict; | ||
| self.view = LearningView::Lesson; | ||
| LearningAdvance::Step | ||
| } | ||
| LearningStep::Predict => { | ||
| self.step = LearningStep::Exercise; | ||
| self.view = LearningView::Code; | ||
| LearningAdvance::Step | ||
| } | ||
| LearningStep::Exercise => LearningAdvance::Blocked, | ||
| LearningStep::Reflect if self.index + 1 < self.queue.len() => { | ||
| self.assisted = false; | ||
| self.index += 1; | ||
| let item = self.queue[self.index]; | ||
| self.step = if item.review { | ||
| LearningStep::Review | ||
| } else { | ||
| LearningStep::Delta | ||
| }; | ||
| self.view = LearningView::Lesson; | ||
| LearningAdvance::Item(item.lesson_id) | ||
| } | ||
| LearningStep::Reflect => { | ||
| self.assisted = false; | ||
| self.step = LearningStep::Complete; | ||
| self.view = LearningView::Lesson; | ||
| LearningAdvance::Complete | ||
| } | ||
| LearningStep::Complete => { | ||
| self.assisted = false; | ||
| LearningAdvance::Manual | ||
| } | ||
| } | ||
| } | ||
| pub(super) fn cycle_view(&mut self) { | ||
| self.view = match self.view { | ||
| LearningView::Lesson => LearningView::Code, | ||
| LearningView::Code => LearningView::Result, | ||
| LearningView::Result => LearningView::Lesson, | ||
| }; | ||
| } | ||
| } | ||
| fn session_queue( | ||
| state: &AppState, | ||
| language: &str, | ||
| lessons: &[&SyntaxLesson], | ||
| due: &[&SyntaxLesson], | ||
| ) -> Vec<LearningItem> { | ||
| let mut queue = due | ||
| .iter() | ||
| .copied() | ||
| .map(|lesson| LearningItem { | ||
| lesson_id: lesson.id, | ||
| review: true, | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
| if let Some(lesson) = lessons.iter().copied().find(|lesson| { | ||
| lesson.track == SyntaxTrack::Core | ||
| && state | ||
| .syntax_mastery | ||
| .get(language) | ||
| .and_then(|mastery| mastery.get(lesson.id)) | ||
| .is_none_or(|progress| progress.stage == MasteryStage::New) | ||
| && !queue.iter().any(|item| item.lesson_id == lesson.id) | ||
| }) { | ||
| queue.push(LearningItem { | ||
| lesson_id: lesson.id, | ||
| review: false, | ||
| }); | ||
| } | ||
| queue | ||
| } | ||
| pub(super) fn render_learning_step( | ||
| lesson: Option<&SyntaxLesson>, | ||
| state: &AppState, | ||
| step: LearningStep, | ||
| ) -> String { | ||
| let language = &state.settings.ui_language; | ||
| if step == LearningStep::Complete { | ||
| return format!( | ||
| "# {}\n\n{}", | ||
| learning_step_label(language, step), | ||
| ui_text(language, "learning_complete_body") | ||
| ); | ||
| } | ||
| let Some(lesson) = lesson else { | ||
| return String::new(); | ||
| }; | ||
| let title = crate::core::localized_syntax_title(lesson, language); | ||
| let body = match step { | ||
| LearningStep::Review => crate::core::localized_syntax_objective(lesson, language), | ||
| LearningStep::Delta => crate::core::localized_syntax_language_delta(lesson, language), | ||
| LearningStep::Predict => crate::core::localized_syntax_prediction_prompt(lesson, language), | ||
| LearningStep::Exercise => learning_exercise(lesson, language), | ||
| LearningStep::Reflect => crate::core::localized_syntax_transfer_trap(lesson, language), | ||
| LearningStep::Complete => unreachable!(), | ||
| }; | ||
| format!( | ||
| "# {}: {title}\n\n{body}", | ||
| learning_step_label(language, step) | ||
| ) | ||
| } | ||
| fn learning_exercise(lesson: &SyntaxLesson, language: &str) -> String { | ||
| let prompt = crate::core::localized_syntax_exercise_prompt(lesson, language); | ||
| let Some(case) = lesson.exercise.cases.first() else { | ||
| return prompt; | ||
| }; | ||
| format!( | ||
| "{}\n\n{}\n\n```\n{}\n```\n\n{}\n\n```\n{}\n```", | ||
| prompt, | ||
| ui_text(language, "input"), | ||
| case.input.trim_end(), | ||
| ui_text(language, "output"), | ||
| case.output.trim_end() | ||
| ) | ||
| } | ||
| pub(super) fn progress_text(state: &AppState, now: u64) -> String { | ||
| let language = normalize_language(&state.settings.language); | ||
| let lessons = syntax_lessons_for(&language); | ||
| let mastery = state.syntax_mastery.get(&language); | ||
| let count = |stage| { | ||
| lessons | ||
| .iter() | ||
| .filter(|lesson| lesson.track == SyntaxTrack::Core) | ||
| .filter(|lesson| { | ||
| mastery | ||
| .and_then(|items| items.get(lesson.id)) | ||
| .is_some_and(|item| item.stage == stage) | ||
| }) | ||
| .count() | ||
| }; | ||
| let (_, core) = crate::core::syntax_core_progress_count(state, &language); | ||
| let practiced = count(MasteryStage::Practiced); | ||
| let retained = count(MasteryStage::Retained); | ||
| let mastered = count(MasteryStage::Mastered); | ||
| let new = core.saturating_sub(practiced + retained + mastered); | ||
| let due = due_syntax_lesson_count(state, &language, now); | ||
| let ui_language = &state.settings.ui_language; | ||
| format!( | ||
| "{}\n{}: {}\n{}: {core}\n{}: {}\n{}: {}\n{}: {}\n{}: {}\n{}: {due}", | ||
| ui_text(ui_language, "progress_title"), | ||
| ui_text(ui_language, "progress_language"), | ||
| syntax_language_name(&language), | ||
| ui_text(ui_language, "progress_core"), | ||
| ui_text(ui_language, "mastery_new"), | ||
| new, | ||
| ui_text(ui_language, "mastery_practiced"), | ||
| practiced, | ||
| ui_text(ui_language, "mastery_retained"), | ||
| retained, | ||
| ui_text(ui_language, "mastery_mastered"), | ||
| mastered, | ||
| ui_text(ui_language, "learning_due_reviews"), | ||
| ) | ||
| } | ||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| fn lesson(id: &'static str, track: SyntaxTrack, kind: SyntaxKind) -> SyntaxLesson { | ||
| SyntaxLesson { | ||
| id, | ||
| aliases: &[], | ||
| language: "rust", | ||
| track, | ||
| kind, | ||
| level: "basic", | ||
| title: id, | ||
| body: id, | ||
| example: "fn main() {}", | ||
| exercise: crate::core::SyntaxExercise { | ||
| prompt: id, | ||
| starter: id, | ||
| cases: &[], | ||
| }, | ||
| refs: &[], | ||
| } | ||
| } | ||
| fn state() -> AppState { | ||
| AppState { | ||
| current_problem: "001-hello-world".to_string(), | ||
| settings: crate::core::Settings::default(), | ||
| solved: Vec::new(), | ||
| history: Vec::new(), | ||
| suggested_next_difficulty: "easy".to_string(), | ||
| syntax_progress: HashMap::new(), | ||
| current_syntax_lesson: HashMap::new(), | ||
| syntax_mastery: HashMap::new(), | ||
| completed_syntax_courses: Vec::new(), | ||
| } | ||
| } | ||
| #[test] | ||
| fn synthetic_checkpoint_and_capstone_are_queued_and_ai_cannot_grant_capstone_progress() { | ||
| let lab = lesson("lab", SyntaxTrack::Lab, SyntaxKind::Lesson); | ||
| let checkpoint = lesson("checkpoint", SyntaxTrack::Core, SyntaxKind::Checkpoint); | ||
| let capstone = lesson("capstone", SyntaxTrack::Core, SyntaxKind::Capstone); | ||
| let lessons = [&lab, &checkpoint, &capstone]; | ||
| let mut state = state(); | ||
| let checkpoint_queue = session_queue(&state, "rust", &lessons, &[]); | ||
| assert_eq!(checkpoint_queue[0].lesson_id, "checkpoint"); | ||
| state | ||
| .syntax_mastery | ||
| .entry("rust".to_string()) | ||
| .or_default() | ||
| .insert( | ||
| "checkpoint".to_string(), | ||
| crate::core::LessonMastery { | ||
| stage: MasteryStage::Practiced, | ||
| review_due_at: u64::MAX, | ||
| attempts: 1, | ||
| }, | ||
| ); | ||
| let capstone_queue = session_queue(&state, "rust", &lessons, &[]); | ||
| assert_eq!(capstone_queue[0].lesson_id, "capstone"); | ||
| let mut session = LearningSession { | ||
| guided: true, | ||
| queue: capstone_queue, | ||
| index: 0, | ||
| step: LearningStep::Exercise, | ||
| view: LearningView::Code, | ||
| assisted: false, | ||
| }; | ||
| session.mark_assisted(); | ||
| let assisted = session.assisted(); | ||
| crate::core::record_syntax_result_for_lessons( | ||
| &mut state, "rust", "capstone", true, 1_000, assisted, &lessons, | ||
| ); | ||
| session.finish_judge(true); | ||
| assert!(!session.assisted()); | ||
| assert_eq!(session.step(), LearningStep::Reflect); | ||
| assert_eq!( | ||
| state.syntax_mastery["rust"]["capstone"].stage, | ||
| MasteryStage::New | ||
| ); | ||
| crate::core::record_syntax_result_for_lessons( | ||
| &mut state, "rust", "capstone", true, 1_000, false, &lessons, | ||
| ); | ||
| assert_eq!( | ||
| state.syntax_mastery["rust"]["capstone"].stage, | ||
| MasteryStage::Practiced | ||
| ); | ||
| } | ||
| #[test] | ||
| fn assistance_lives_only_until_the_current_item_judge_or_boundary() { | ||
| let queue = vec![ | ||
| LearningItem { | ||
| lesson_id: "first", | ||
| review: false, | ||
| }, | ||
| LearningItem { | ||
| lesson_id: "second", | ||
| review: false, | ||
| }, | ||
| ]; | ||
| let mut session = LearningSession { | ||
| guided: true, | ||
| queue, | ||
| index: 0, | ||
| step: LearningStep::Delta, | ||
| view: LearningView::Code, | ||
| assisted: false, | ||
| }; | ||
| for step in [ | ||
| LearningStep::Review, | ||
| LearningStep::Delta, | ||
| LearningStep::Predict, | ||
| LearningStep::Exercise, | ||
| ] { | ||
| session.step = step; | ||
| session.assisted = false; | ||
| session.mark_assisted(); | ||
| assert!(session.assisted(), "{step:?}"); | ||
| } | ||
| session.step = LearningStep::Delta; | ||
| session.assisted = false; | ||
| session.mark_assisted(); | ||
| assert!(session.assisted()); | ||
| assert!(matches!(session.advance(), LearningAdvance::Step)); | ||
| assert_eq!(session.step(), LearningStep::Predict); | ||
| assert!(session.assisted()); | ||
| assert!(matches!(session.advance(), LearningAdvance::Step)); | ||
| assert_eq!(session.step(), LearningStep::Exercise); | ||
| assert!(session.assisted()); | ||
| session.finish_judge(true); | ||
| assert_eq!(session.step(), LearningStep::Reflect); | ||
| assert!(!session.assisted()); | ||
| session.mark_assisted(); | ||
| assert!(!session.assisted()); | ||
| session.assisted = true; | ||
| assert!(matches!(session.advance(), LearningAdvance::Item("second"))); | ||
| assert!(!session.assisted()); | ||
| session.step = LearningStep::Reflect; | ||
| session.assisted = true; | ||
| assert!(matches!(session.advance(), LearningAdvance::Complete)); | ||
| assert!(!session.assisted()); | ||
| session.assisted = true; | ||
| assert!(matches!(session.advance(), LearningAdvance::Manual)); | ||
| assert!(!session.assisted()); | ||
| let mut inactive = LearningSession::inactive(); | ||
| inactive.mark_assisted(); | ||
| assert!(inactive.assisted()); | ||
| inactive.finish_judge(true); | ||
| assert!(!inactive.assisted()); | ||
| } | ||
| #[test] | ||
| fn manual_assisted_capstone_pass_cannot_advance_mastery() { | ||
| let capstone = lesson("capstone", SyntaxTrack::Core, SyntaxKind::Capstone); | ||
| let lessons = [&capstone]; | ||
| let mut state = state(); | ||
| let mut session = LearningSession::inactive(); | ||
| session.mark_assisted(); | ||
| crate::core::record_syntax_result_for_lessons( | ||
| &mut state, | ||
| "rust", | ||
| "capstone", | ||
| true, | ||
| 1_000, | ||
| session.assisted(), | ||
| &lessons, | ||
| ); | ||
| session.finish_judge(true); | ||
| assert_eq!(state.syntax_mastery["rust"]["capstone"].attempts, 1); | ||
| assert_eq!( | ||
| state.syntax_mastery["rust"]["capstone"].stage, | ||
| MasteryStage::New | ||
| ); | ||
| assert!(!session.assisted()); | ||
| } | ||
| #[test] | ||
| fn guided_judge_gate_allows_only_exercise_while_inactive_browsing_can_run() { | ||
| let mut session = LearningSession { | ||
| guided: true, | ||
| queue: vec![LearningItem { | ||
| lesson_id: "lesson", | ||
| review: false, | ||
| }], | ||
| index: 0, | ||
| step: LearningStep::Review, | ||
| view: LearningView::Code, | ||
| assisted: false, | ||
| }; | ||
| for step in [ | ||
| LearningStep::Review, | ||
| LearningStep::Delta, | ||
| LearningStep::Predict, | ||
| LearningStep::Reflect, | ||
| LearningStep::Complete, | ||
| ] { | ||
| session.step = step; | ||
| assert!(!session.can_judge(), "{step:?}"); | ||
| } | ||
| session.step = LearningStep::Exercise; | ||
| assert!(session.can_judge()); | ||
| assert!(LearningSession::inactive().can_judge()); | ||
| } | ||
| #[test] | ||
| fn session_queue_orders_two_due_reviews_then_one_new_core_item() { | ||
| let mut state = state(); | ||
| state.syntax_mastery.insert( | ||
| "python".to_string(), | ||
| [ | ||
| ("py-output", 300), | ||
| ("py-variables", 100), | ||
| ("py-numbers", 200), | ||
| ] | ||
| .into_iter() | ||
| .map(|(id, review_due_at)| { | ||
| ( | ||
| id.to_string(), | ||
| crate::core::LessonMastery { | ||
| stage: MasteryStage::Practiced, | ||
| review_due_at, | ||
| attempts: 1, | ||
| }, | ||
| ) | ||
| }) | ||
| .collect(), | ||
| ); | ||
| let session = LearningSession::start(&state, "python", 100_000); | ||
| assert_eq!( | ||
| session.queue_ids(), | ||
| ["py-variables", "py-numbers", "py-input"] | ||
| ); | ||
| assert_eq!(session.step(), LearningStep::Review); | ||
| assert_eq!(session.view(), LearningView::Lesson); | ||
| } | ||
| #[test] | ||
| fn learning_view_cycles_code_result_lesson() { | ||
| let mut session = LearningSession { | ||
| guided: true, | ||
| queue: vec![LearningItem { | ||
| lesson_id: "lesson", | ||
| review: false, | ||
| }], | ||
| index: 0, | ||
| step: LearningStep::Delta, | ||
| view: LearningView::Code, | ||
| assisted: false, | ||
| }; | ||
| session.cycle_view(); | ||
| assert_eq!(session.view(), LearningView::Result); | ||
| session.cycle_view(); | ||
| assert_eq!(session.view(), LearningView::Lesson); | ||
| session.cycle_view(); | ||
| assert_eq!(session.view(), LearningView::Code); | ||
| } | ||
| #[test] | ||
| fn inactive_judge_selects_result_without_changing_manual_step() { | ||
| let mut session = LearningSession::inactive(); | ||
| session.mark_assisted(); | ||
| session.finish_judge(false); | ||
| assert!(!session.assisted()); | ||
| assert_eq!(session.step(), LearningStep::Complete); | ||
| assert_eq!(session.view(), LearningView::Result); | ||
| } | ||
| #[test] | ||
| fn progress_counts_untouched_core_lessons_as_new() { | ||
| let mut state = state(); | ||
| state.settings.language = "python".to_string(); | ||
| let core = syntax_lessons_for("python") | ||
| .into_iter() | ||
| .filter(|lesson| lesson.track == SyntaxTrack::Core) | ||
| .count(); | ||
| let progress = progress_text(&state, 1_000); | ||
| assert!(progress.contains(&format!("New: {core}")), "{progress}"); | ||
| } | ||
| #[test] | ||
| fn progress_does_not_double_count_explicit_new_mastery() { | ||
| let mut state = state(); | ||
| state.settings.language = "python".to_string(); | ||
| state.syntax_mastery.insert( | ||
| "python".to_string(), | ||
| [ | ||
| ("py-output", MasteryStage::New), | ||
| ("py-variables", MasteryStage::Practiced), | ||
| ] | ||
| .into_iter() | ||
| .map(|(id, stage)| { | ||
| ( | ||
| id.to_string(), | ||
| crate::core::LessonMastery { | ||
| stage, | ||
| review_due_at: 0, | ||
| attempts: 1, | ||
| }, | ||
| ) | ||
| }) | ||
| .collect(), | ||
| ); | ||
| let core = syntax_lessons_for("python") | ||
| .into_iter() | ||
| .filter(|lesson| lesson.track == SyntaxTrack::Core) | ||
| .count(); | ||
| let progress = progress_text(&state, 1_000); | ||
| assert!( | ||
| progress.contains(&format!("New: {}", core - 1)), | ||
| "{progress}" | ||
| ); | ||
| } | ||
| } |
+18
-0
| .git | ||
| .DS_Store | ||
| .aws | ||
| .cache | ||
| .cargo | ||
| .claude | ||
| .codex | ||
| .config | ||
| .env | ||
| .env.* | ||
| .gnupg | ||
| .idea | ||
| .npm | ||
| .npmrc | ||
| .practicode | ||
| .pytest_cache | ||
| .rustup | ||
| .ssh | ||
| .superpowers | ||
| .venv | ||
| .vscode | ||
| .worktrees | ||
| coverage | ||
| node_modules | ||
@@ -7,0 +25,0 @@ problems |
+185
-6
@@ -16,4 +16,7 @@ { | ||
| "home_preview": "Preview", | ||
| "home_learn_choice": "Learn syntax", | ||
| "home_learn_choice": "Continue today's session", | ||
| "home_practice_choice": "Practice coding tests", | ||
| "home_learn_description": "Review what's due, learn one core item, and validate the exercise.", | ||
| "home_practice_description": "Solve stdin/stdout coding-test problems.", | ||
| "home_next_step": "Next step", | ||
| "home_help": "Arrows choose | Enter/Space open | / commands", | ||
@@ -28,3 +31,4 @@ "command_placeholder": "Type /, move with up/down, Enter runs", | ||
| "hint_problem": "/run judge | /next problem | /home", | ||
| "hint_learn": "/run validate | /ask question | /next lesson | /back lesson | /home", | ||
| "hint_learn": "F5 run | F6 view | /next step | /lesson reference | /ask | /home", | ||
| "hint_result": "drag select to copy | F6 view | /next step | /home", | ||
| "hint_idle": "/ command | ? help", | ||
@@ -49,5 +53,7 @@ "hint_busy": "Working | q quit", | ||
| "cmd_learn": "Open syntax learning", | ||
| "cmd_hint": "Ask for a hint about the current problem", | ||
| "cmd_ask": "Ask AI about the current lesson or code", | ||
| "cmd_ai": "Ask AI about the current problem and code", | ||
| "cmd_lesson": "Open the complete reference lesson", | ||
| "cmd_progress": "Show a shareable learning summary", | ||
| "cmd_hint": "Send problem/lesson, code, and latest result/context to the selected AI provider for a hint", | ||
| "cmd_ask": "Send problem/lesson, code, and latest result/context to the selected AI provider for an answer", | ||
| "cmd_ai": "Send problem/lesson, code, and latest result/context to the selected AI provider", | ||
| "cmd_profile": "Show user profile", | ||
@@ -112,3 +118,176 @@ "cmd_difficulty": "Set preferred difficulty", | ||
| "syntax_advanced": "advanced", | ||
| "exercise_result": "Exercise result" | ||
| "exercise_result": "Exercise result", | ||
| "learning_shortcuts": "F1 help | F5 run | F6 Lesson/Code/Result", | ||
| "learning_step_review": "Review", | ||
| "learning_step_delta": "Language delta", | ||
| "learning_step_predict": "Predict", | ||
| "learning_step_exercise": "Exercise", | ||
| "learning_step_reflect": "Reflect", | ||
| "learning_step_complete": "Session complete", | ||
| "learning_complete_body": "Today's guided session is complete. Use /next to browse lessons or /home to choose another mode.", | ||
| "learning_run_gate": "Next: use /next until Exercise, then /run or F5.", | ||
| "progress_title": "Learning progress", | ||
| "progress_language": "Language", | ||
| "progress_core": "Core", | ||
| "progress_practiced": "Practiced", | ||
| "progress_retained": "Retained", | ||
| "progress_mastered": "Mastered", | ||
| "progress_due": "Due", | ||
| "learning_due_reviews": "Due", | ||
| "mastery_new": "New", | ||
| "mastery_practiced": "Practiced", | ||
| "mastery_retained": "Retained", | ||
| "mastery_mastered": "Mastered", | ||
| "judge_failure_compile": "Compile", | ||
| "judge_failure_typecheck": "TypeCheck", | ||
| "judge_failure_runtime": "Runtime", | ||
| "judge_failure_timeout": "Timeout", | ||
| "judge_failure_output": "Output", | ||
| "learning_view_lesson": "Lesson", | ||
| "learning_view_code": "Code", | ||
| "learning_view_result": "Result", | ||
| "resize_required": "Resize the terminal to at least 60x16.", | ||
| "focus_active": "ACTIVE", | ||
| "result_pass": "PASS", | ||
| "result_fail": "FAIL", | ||
| "result_empty": "No result yet.", | ||
| "help_home_loop": "1. Choose Learn or Practice.\n2. Move with arrow keys and open with Enter or Space.\n3. Press `/` for commands.", | ||
| "help_learn_loop": "1. Read the lesson.\n2. Edit the exercise.\n3. Use `/run`, then `/next` or `/back`.", | ||
| "help_problem_loop": "1. Edit your solution.\n2. Press Esc, then choose `/run` from the command palette.\n3. Use `/next` after it passes.", | ||
| "help_palette_open": "`/` opens the command palette outside the editor.", | ||
| "help_palette_move": "`↑/↓` selects a command and Enter accepts it.", | ||
| "help_palette_close": "Esc cancels the command palette or leaves output.", | ||
| "help_stdout": "stdout is shown when a case fails.", | ||
| "help_stderr": "stderr is shown without changing the expected stdout.", | ||
| "home_practice_preview_title": "Practice", | ||
| "home_current": "Current", | ||
| "home_status": "Status", | ||
| "home_practice_run": "Use /run to judge the submission.", | ||
| "home_practice_next": "Use /next to open the next problem.", | ||
| "mode_home": "home", | ||
| "mode_learn": "learn", | ||
| "status_code": "code", | ||
| "status_idle": "idle", | ||
| "status_assigned": "assigned", | ||
| "status_solved": "solved", | ||
| "status_not_started": "not started", | ||
| "status_missing": "missing", | ||
| "status_template": "template", | ||
| "status_empty": "empty", | ||
| "status_written": "written", | ||
| "status_auto": "auto", | ||
| "status_easy": "easy", | ||
| "status_medium": "medium", | ||
| "status_hard": "hard", | ||
| "status_background_generation": "background generation", | ||
| "hint_notes": "notes: type to edit | Esc profile", | ||
| "next_source_help": "/next opens unsolved local problems first and asks AI only when none remain. Use /generate <request> to create one in the background.", | ||
| "empty_value": "<empty>", | ||
| "list_closed": "Closed list.", | ||
| "result_mastery": "Mastery", | ||
| "result_review_days": "Next review (days)", | ||
| "result_retry_no_review": "Retry this exercise; no review is scheduled.", | ||
| "judge_case": "Case", | ||
| "judge_input": "Input", | ||
| "judge_expected": "Expected", | ||
| "judge_got": "Got", | ||
| "judge_stdout": "Stdout", | ||
| "judge_stderr": "Stderr", | ||
| "judge_error": "Error", | ||
| "judge_compile": "Compile", | ||
| "judge_hidden": "hidden", | ||
| "judge_timeout_detail": "timeout: 5s", | ||
| "judge_no_cases": "No judge cases are available.", | ||
| "judge_missing_runtime": "Missing runtime for {runtime}", | ||
| "judge_process_exit": "process exited with status {status}", | ||
| "busy_ai_thinking": "{provider} is thinking", | ||
| "elapsed_seconds": "{seconds}s", | ||
| "generation_started": "Generating in background.", | ||
| "generation_already_running": "A background generation is already running. Keep solving; /next will use the new problem when it finishes.", | ||
| "generation_duplicate": "Generation is already running; duplicate /generate was skipped.", | ||
| "generation_save_failed": "Could not save practice mode before generation.", | ||
| "generation_generated": "Generated {count} problem(s) in the background. Use /next.", | ||
| "generation_failed": "Background generation failed. Use /generate to retry.", | ||
| "generation_finished": "Background generation finished. Use /problems to review.", | ||
| "generation_reload_failed": "Background generation finished, but the problem bank could not be reloaded.", | ||
| "generation_exit_status": "Exit status: {status}", | ||
| "generation_partial_count": "New problems added to the bank despite the failure: {count}.", | ||
| "judge_unknown_status": "unknown", | ||
| "judge_missing_typescript_tool": "Missing TypeScript runtime: {tool}", | ||
| "hint_learn_compact": "F1 help | F5 run | F6 view | /next step", | ||
| "hint_problem_compact": "F1 help | F6 view | /run judge | /next problem", | ||
| "hint_home_compact": "F1 help | arrows choose | Enter open", | ||
| "pane_exercise": "Exercise", | ||
| "pane_solution": "Solution", | ||
| "settings_title": "User profile", | ||
| "settings_instructions": "Use up/down to move; Space/Enter changes.", | ||
| "settings_code_language": "Code language", | ||
| "settings_ui_language": "UI language", | ||
| "settings_theme": "Theme", | ||
| "settings_difficulty": "Difficulty", | ||
| "settings_preferred_topics": "Preferred topics", | ||
| "settings_avoid_topics": "Avoid topics", | ||
| "settings_generated_answer_languages": "Generated answer languages", | ||
| "settings_generated_ui_languages": "Generated UI languages", | ||
| "settings_provider_default": "auto (provider default)", | ||
| "settings_problem_notes": "Edit problem notes", | ||
| "settings_answer_toggles": "Generated answer language toggles", | ||
| "settings_ui_toggles": "Generated problem text language toggles", | ||
| "settings_commands": "Commands", | ||
| "settings_none": "(none)", | ||
| "settings_all": "all", | ||
| "settings_ai_provider": "AI provider", | ||
| "settings_ai_model": "AI model", | ||
| "settings_ai_effort": "AI effort", | ||
| "settings_model_loading": "loading", | ||
| "settings_model_load_hint": "/model to load", | ||
| "settings_note_action": "Enter", | ||
| "model_use_default_model": "Use /model auto to let the provider choose its default.", | ||
| "model_use_default_effort": "Use /effort auto to let the provider choose its default.", | ||
| "model_loading": "Loading provider model list...", | ||
| "model_unavailable": "Provider model list is unavailable.", | ||
| "model_custom_hint": "Use /model <name> for a known model.", | ||
| "model_available_efforts": "Available efforts: {efforts}", | ||
| "model_available_models": "Available models:", | ||
| "note_saved": "Problem note saved to {path}.", | ||
| "notes_empty": "No notes yet. Use /note to edit problem-generation notes.", | ||
| "notes_title": "Problem notes ({path})", | ||
| "first_problem": "Already at the first known problem.", | ||
| "answer_for_language": "Answer for {language}:", | ||
| "learn_usage": "Usage: /learn or /learn python|ts|java|rust", | ||
| "ui_language_set": "UI language: {language}", | ||
| "theme_set": "Theme: {theme}", | ||
| "difficulty_options": "Difficulty: auto, easy, medium, or hard.", | ||
| "practice_shortcuts": "F1 help | F6 Problem/Code | /run judge | /next", | ||
| "problem_list_title": "Problems", | ||
| "problem_list_id": "ID", | ||
| "problem_list_difficulty": "Difficulty", | ||
| "problem_list_status": "Status", | ||
| "problem_list_code": "Code", | ||
| "problem_list_name": "Title", | ||
| "problem_list_hint": "up/down or j/k select | Enter open | Esc close", | ||
| "problem_not_found": "Problem not found: {query}\nTry /problems.", | ||
| "update_checking": "Checking for updates...", | ||
| "ai_next_command_saved": "AI next command saved.", | ||
| "unknown_command": "Unknown command: {command}\nTry /help.", | ||
| "next_unavailable": "No next problem is available yet.", | ||
| "next_failed": "Could not open the next problem", | ||
| "provider_cli_found": "{provider} CLI found.", | ||
| "provider_cli_missing": "{provider} CLI not found. Install {install} or choose {command}.", | ||
| "provider_codex_daemon_available": "Codex CLI found. App-server daemon is available.", | ||
| "provider_codex_direct_fallback": "Codex CLI found. App-server daemon is unavailable; practicode will use codex exec directly.", | ||
| "model_cli_missing": "{provider} CLI not found; choose {command} or install {install}.", | ||
| "model_claude_presets": "Bundled Claude presets from Claude Code {version} --help. Efforts: {efforts}.", | ||
| "model_codex_daemon_unavailable": "Codex app-server daemon is unavailable; install the standalone Codex app to list models, or use /model <name>.", | ||
| "model_codex_query_failed": "Could not query the Codex model list; using bundled/cache model presets.", | ||
| "model_codex_empty": "Codex app-server returned no models.", | ||
| "doctor_missing_tool": "missing {tool}", | ||
| "doctor_node_required": "node >= 22.6.0 required", | ||
| "doctor_tsc_unreadable": "unreadable tsc version; TypeScript 5.9.x required", | ||
| "doctor_tsc_required": "TypeScript 5.9.x required ({version})", | ||
| "doctor_unknown_version": "unknown", | ||
| "doctor_node_install_linux": "Ubuntu/Debian: download Node.js LTS from {url}", | ||
| "doctor_codex_install": "Install Codex CLI, or switch with /provider claude.", | ||
| "doctor_claude_install": "Install Claude Code, or switch with /provider codex.", | ||
| "ai_context_disclosure": "Sends lesson/problem + code/result → chosen AI" | ||
| } |
+209
-30
@@ -13,7 +13,10 @@ { | ||
| "source": "fuente", | ||
| "update": "actualizacion", | ||
| "update": "actualización", | ||
| "home": "Inicio", | ||
| "home_preview": "Vista previa", | ||
| "home_learn_choice": "Aprender sintaxis", | ||
| "home_practice_choice": "Practicar pruebas de codigo", | ||
| "home_learn_choice": "Continuar la sesión de hoy", | ||
| "home_practice_choice": "Practicar pruebas de código", | ||
| "home_learn_description": "Repasa lo pendiente, aprende un elemento central y valida el ejercicio.", | ||
| "home_practice_description": "Resuelve problemas de entrada y salida estándar.", | ||
| "home_next_step": "Siguiente paso", | ||
| "home_help": "Flechas elegir | Enter/Space abrir | / comandos", | ||
@@ -28,3 +31,4 @@ "command_placeholder": "Escribe /, elige con arriba/abajo, Enter ejecuta", | ||
| "hint_problem": "/run evaluar | /next problema | /home", | ||
| "hint_learn": "/run validar | /ask pregunta | /next lección | /back lección | /home", | ||
| "hint_learn": "F5 ejecutar | F6 vista | /next paso | /lesson referencia | /ask | /home", | ||
| "hint_result": "arrastra para copiar | F6 vista | /next paso | /home", | ||
| "hint_idle": "/ comando | ? ayuda", | ||
@@ -36,8 +40,8 @@ "hint_busy": "Trabajando | q salir", | ||
| "keys": "Teclas", | ||
| "debug_prints": "Salida de depuracion", | ||
| "cmd_run": "Evalua la solucion actual", | ||
| "cmd_code": "Volver al editor de codigo", | ||
| "cmd_edit": "Volver al editor de codigo", | ||
| "debug_prints": "Salida de depuración", | ||
| "cmd_run": "Evalúa la solución actual", | ||
| "cmd_code": "Volver al editor de código", | ||
| "cmd_edit": "Volver al editor de código", | ||
| "cmd_home": "Abrir inicio", | ||
| "cmd_doctor": "Comprobar runtimes locales", | ||
| "cmd_doctor": "Comprobar entornos de ejecución locales", | ||
| "cmd_next": "Abrir el siguiente elemento de este modo", | ||
@@ -47,7 +51,9 @@ "cmd_generate": "Generar un problema nuevo en segundo plano", | ||
| "cmd_list": "Abrir la lista de problemas", | ||
| "cmd_open": "Abrir por numero, id o slug", | ||
| "cmd_open": "Abrir por número, id o slug", | ||
| "cmd_giveup": "Mostrar la respuesta de referencia", | ||
| "cmd_learn": "Abrir aprendizaje de sintaxis", | ||
| "cmd_hint": "Pedir una pista para el problema actual", | ||
| "cmd_ai": "Preguntar a AI sobre el problema y codigo actuales", | ||
| "cmd_lesson": "Abrir la lección de referencia completa", | ||
| "cmd_progress": "Mostrar un resumen de aprendizaje compartible", | ||
| "cmd_hint": "Envía problema/lección, código y último resultado/contexto al proveedor de IA elegido para pedir una pista", | ||
| "cmd_ai": "Envía problema/lección, código y último resultado/contexto al proveedor de IA elegido", | ||
| "cmd_profile": "Mostrar perfil de usuario", | ||
@@ -59,27 +65,27 @@ "cmd_difficulty": "Configurar dificultad preferida", | ||
| "cmd_generate_ui": "Configurar idiomas de UI generados", | ||
| "cmd_provider": "Configurar AI provider", | ||
| "cmd_model": "Configurar AI model", | ||
| "cmd_model_auto": "Usar el modelo predeterminado del provider", | ||
| "cmd_model_available": "Usar un modelo disponible del provider", | ||
| "cmd_provider": "Configurar proveedor de IA", | ||
| "cmd_model": "Configurar modelo de IA", | ||
| "cmd_model_auto": "Usar el modelo predeterminado del proveedor", | ||
| "cmd_model_available": "Usar un modelo disponible del proveedor", | ||
| "cmd_model_custom": "Escribir un nombre de modelo", | ||
| "cmd_effort": "Configurar AI effort", | ||
| "cmd_effort_auto": "Usar el effort predeterminado del provider", | ||
| "cmd_effort_max": "Usar Claude max effort", | ||
| "cmd_effort": "Configurar intensidad de razonamiento", | ||
| "cmd_effort_auto": "Usar la intensidad predeterminada del proveedor", | ||
| "cmd_effort_max": "Usar la intensidad máxima de Claude", | ||
| "cmd_note": "Editar notas para generar problemas", | ||
| "cmd_notes": "Ver notas guardadas", | ||
| "cmd_lang": "Configurar lenguaje de codigo", | ||
| "cmd_lang": "Configurar lenguaje de código", | ||
| "cmd_ui": "Configurar idioma de UI", | ||
| "cmd_theme": "Configurar tema", | ||
| "cmd_source": "Configurar fuente del siguiente problema", | ||
| "cmd_update": "Mostrar instrucciones de actualizacion", | ||
| "cmd_update": "Mostrar instrucciones de actualización", | ||
| "cmd_help": "Abrir ayuda", | ||
| "cmd_exit": "Salir", | ||
| "update_available": "Hay una nueva version", | ||
| "update_none": "practicode esta actualizado.", | ||
| "update_check_disabled": "La comprobacion de actualizaciones esta desactivada.", | ||
| "update_available": "Hay una nueva versión", | ||
| "update_none": "practicode está actualizado.", | ||
| "update_check_disabled": "La comprobación de actualizaciones está desactivada.", | ||
| "update_check_failed": "No se pudo comprobar actualizaciones.", | ||
| "doctor_title": "Diagnostico", | ||
| "doctor_title": "Diagnóstico", | ||
| "doctor_current_language": "Lenguaje actual", | ||
| "doctor_runtime_checks": "Comprobaciones de runtime", | ||
| "doctor_optional_ai": "AI opcional", | ||
| "doctor_runtime_checks": "Comprobaciones de entornos de ejecución", | ||
| "doctor_optional_ai": "IA opcional", | ||
| "doctor_ok": "OK", | ||
@@ -92,3 +98,3 @@ "doctor_missing": "FALTA", | ||
| "busy_warmup": "Calentamiento: pulsa Space cuando * llegue al centro.", | ||
| "busy_commands_paused": "Los comandos quedan pausados hasta que termine la generacion.", | ||
| "busy_commands_paused": "Los comandos quedan pausados hasta que termine la generación.", | ||
| "hits": "Aciertos", | ||
@@ -113,5 +119,178 @@ "misses": "Fallos", | ||
| "exercise_result": "Resultado", | ||
| "cmd_ask": "Preguntar a la IA sobre la lección o el código actual", | ||
| "cmd_ask": "Envía problema/lección, código y último resultado/contexto al proveedor de IA elegido para preguntar", | ||
| "syntax_common_mistakes": "Errores frecuentes", | ||
| "syntax_self_check": "Autoevaluación" | ||
| "syntax_self_check": "Autoevaluación", | ||
| "learning_shortcuts": "F1 ayuda | F5 ejecutar | F6 Lección/Código/Resultado", | ||
| "learning_step_review": "Repaso", | ||
| "learning_step_delta": "Diferencia del lenguaje", | ||
| "learning_step_predict": "Predicción", | ||
| "learning_step_exercise": "Ejercicio", | ||
| "learning_step_reflect": "Reflexión", | ||
| "learning_step_complete": "Sesión completada", | ||
| "learning_complete_body": "La sesión guiada de hoy está completa. Usa /next para explorar lecciones o /home para elegir otro modo.", | ||
| "learning_run_gate": "Siguiente: usa /next hasta Ejercicio y luego /run o F5.", | ||
| "progress_title": "Progreso de aprendizaje", | ||
| "progress_language": "Lenguaje", | ||
| "progress_core": "Central", | ||
| "progress_practiced": "Practicado", | ||
| "progress_retained": "Retenido", | ||
| "progress_mastered": "Dominado", | ||
| "progress_due": "Pendiente", | ||
| "learning_due_reviews": "Repasos pendientes", | ||
| "mastery_new": "Nuevo", | ||
| "mastery_practiced": "Practicado", | ||
| "mastery_retained": "Retenido", | ||
| "mastery_mastered": "Dominado", | ||
| "judge_failure_compile": "Compilación", | ||
| "judge_failure_typecheck": "Comprobación de tipos", | ||
| "judge_failure_runtime": "Ejecución", | ||
| "judge_failure_timeout": "Tiempo agotado", | ||
| "judge_failure_output": "Salida", | ||
| "learning_view_lesson": "Lección", | ||
| "learning_view_code": "Código", | ||
| "learning_view_result": "Resultado", | ||
| "resize_required": "Amplía la terminal a 60x16 como mínimo.", | ||
| "focus_active": "ACTIVO", | ||
| "result_pass": "APROBADO", | ||
| "result_fail": "FALLÓ", | ||
| "result_empty": "Aún no hay resultados.", | ||
| "help_home_loop": "1. Elige Aprender o Practicar.\n2. Muévete con las flechas y abre con Enter o Space.\n3. Pulsa `/` para ver los comandos.", | ||
| "help_learn_loop": "1. Lee la lección.\n2. Edita el ejercicio.\n3. Usa `/run` y después `/next` o `/back`.", | ||
| "help_problem_loop": "1. Edita la solución.\n2. Pulsa Esc y elige `/run` en la paleta de comandos.\n3. Usa `/next` cuando apruebe.", | ||
| "help_palette_open": "`/` abre la paleta de comandos fuera del editor.", | ||
| "help_palette_move": "`↑/↓` selecciona un comando y Enter lo acepta.", | ||
| "help_palette_close": "Esc cancela la paleta de comandos o cierra la salida.", | ||
| "help_stdout": "stdout se muestra cuando falla un caso.", | ||
| "help_stderr": "stderr se muestra sin cambiar el stdout esperado.", | ||
| "home_practice_preview_title": "Práctica", | ||
| "home_current": "Actual", | ||
| "home_status": "Estado", | ||
| "home_practice_run": "Usa /run para evaluar la solución.", | ||
| "home_practice_next": "Usa /next para abrir el siguiente problema.", | ||
| "mode_home": "Inicio", | ||
| "mode_learn": "Aprendizaje", | ||
| "status_code": "código", | ||
| "status_idle": "en espera", | ||
| "status_assigned": "asignado", | ||
| "status_solved": "resuelto", | ||
| "status_not_started": "sin empezar", | ||
| "status_missing": "no existe", | ||
| "status_template": "plantilla", | ||
| "status_empty": "vacío", | ||
| "status_written": "escrito", | ||
| "status_auto": "automática", | ||
| "status_easy": "fácil", | ||
| "status_medium": "media", | ||
| "status_hard": "difícil", | ||
| "status_background_generation": "generación en segundo plano", | ||
| "hint_notes": "escribe notas | Esc perfil", | ||
| "next_source_help": "/next abre primero los problemas locales sin resolver y solo pide ayuda a la IA cuando no queda ninguno. Usa /generate <petición> para crear uno en segundo plano.", | ||
| "empty_value": "<vacío>", | ||
| "list_closed": "Lista cerrada.", | ||
| "result_mastery": "Dominio", | ||
| "result_review_days": "Días hasta el próximo repaso", | ||
| "result_retry_no_review": "Reintenta este ejercicio; todavía no hay un repaso programado.", | ||
| "judge_case": "Caso", | ||
| "judge_input": "Entrada", | ||
| "judge_expected": "Salida esperada", | ||
| "judge_got": "Salida obtenida", | ||
| "judge_stdout": "Salida estándar", | ||
| "judge_stderr": "Error estándar", | ||
| "judge_error": "Error", | ||
| "judge_compile": "Compilación", | ||
| "judge_hidden": "oculto", | ||
| "judge_timeout_detail": "tiempo agotado: 5 s", | ||
| "judge_no_cases": "No hay casos de evaluación.", | ||
| "judge_missing_runtime": "Falta el entorno de ejecución de {runtime}.", | ||
| "judge_process_exit": "el proceso terminó con estado {status}", | ||
| "busy_ai_thinking": "{provider} está pensando", | ||
| "elapsed_seconds": "{seconds} s", | ||
| "generation_started": "Generando en segundo plano.", | ||
| "generation_already_running": "Ya hay una generación en segundo plano. Sigue resolviendo; cuando termine, /next usará el problema nuevo.", | ||
| "generation_duplicate": "La generación ya está en curso; se omitió el /generate duplicado.", | ||
| "generation_save_failed": "No se pudo guardar el modo de práctica antes de generar.", | ||
| "generation_generated": "Se generaron {count} problema(s) en segundo plano. Usa /next.", | ||
| "generation_failed": "La generación en segundo plano falló. Usa /generate para reintentar.", | ||
| "generation_finished": "La generación en segundo plano terminó. Usa /problems para revisarla.", | ||
| "generation_reload_failed": "La generación terminó, pero no se pudo recargar el banco de problemas.", | ||
| "generation_exit_status": "Estado de salida: {status}", | ||
| "generation_partial_count": "Problemas nuevos añadidos al banco a pesar del fallo: {count}.", | ||
| "judge_unknown_status": "desconocido", | ||
| "judge_missing_typescript_tool": "Falta el entorno de ejecución de TypeScript: {tool}", | ||
| "hint_learn_compact": "F1 ayuda | F5 ejecutar | F6 vista | /next", | ||
| "hint_problem_compact": "F1 ayuda | F6 vista | /run | /next", | ||
| "hint_home_compact": "F1 ayuda | flechas elegir | Enter abrir", | ||
| "pane_exercise": "Ejercicio", | ||
| "pane_solution": "Solución", | ||
| "settings_title": "Perfil de usuario", | ||
| "settings_instructions": "Sube/baja; Space/Enter cambia.", | ||
| "settings_code_language": "Lenguaje de código", | ||
| "settings_ui_language": "Idioma de la interfaz", | ||
| "settings_theme": "Tema", | ||
| "settings_difficulty": "Dificultad", | ||
| "settings_preferred_topics": "Temas preferidos", | ||
| "settings_avoid_topics": "Temas que evitar", | ||
| "settings_generated_answer_languages": "Lenguajes de respuesta generados", | ||
| "settings_generated_ui_languages": "Idiomas de enunciado generados", | ||
| "settings_provider_default": "auto (predeterminado del proveedor)", | ||
| "settings_problem_notes": "Editar notas para generar problemas", | ||
| "settings_answer_toggles": "Alternar lenguajes de respuesta", | ||
| "settings_ui_toggles": "Alternar idiomas del enunciado", | ||
| "settings_commands": "Comandos", | ||
| "settings_none": "(ninguno)", | ||
| "settings_all": "todos", | ||
| "settings_ai_provider": "Proveedor de IA", | ||
| "settings_ai_model": "Modelo de IA", | ||
| "settings_ai_effort": "Esfuerzo de IA", | ||
| "settings_model_loading": "cargando", | ||
| "settings_model_load_hint": "cargar con /model", | ||
| "settings_note_action": "Enter", | ||
| "model_use_default_model": "Usa /model auto para dejar el modelo al proveedor.", | ||
| "model_use_default_effort": "Usa /effort auto para dejar el esfuerzo al proveedor.", | ||
| "model_loading": "Cargando la lista de modelos del proveedor...", | ||
| "model_unavailable": "La lista de modelos del proveedor no está disponible.", | ||
| "model_custom_hint": "Usa /model <name> para indicar un modelo conocido.", | ||
| "model_available_efforts": "Esfuerzos disponibles: {efforts}", | ||
| "model_available_models": "Modelos disponibles:", | ||
| "note_saved": "Nota del problema guardada en {path}.", | ||
| "notes_empty": "Aún no hay notas. Usa /note para editar las notas de generación.", | ||
| "notes_title": "Notas del problema ({path})", | ||
| "first_problem": "Ya estás en el primer problema conocido.", | ||
| "answer_for_language": "Respuesta para {language}:", | ||
| "learn_usage": "Uso: /learn o /learn python|ts|java|rust", | ||
| "ui_language_set": "Idioma de la interfaz: {language}", | ||
| "theme_set": "Tema: {theme}", | ||
| "difficulty_options": "Dificultad: auto, easy, medium o hard.", | ||
| "practice_shortcuts": "F1 ayuda | F6 Problema/Código | /run | /next", | ||
| "problem_list_title": "Problemas", | ||
| "problem_list_id": "ID", | ||
| "problem_list_difficulty": "Dificultad", | ||
| "problem_list_status": "Estado", | ||
| "problem_list_code": "Código", | ||
| "problem_list_name": "Título", | ||
| "problem_list_hint": "sube/baja o j/k | Enter abre | Esc cierra", | ||
| "problem_not_found": "No se encontró el problema: {query}\nUsa /problems.", | ||
| "update_checking": "Buscando actualizaciones...", | ||
| "ai_next_command_saved": "Se guardó el comando de siguiente problema con IA.", | ||
| "unknown_command": "Comando desconocido: {command}\nUsa /help.", | ||
| "next_unavailable": "Aún no hay otro problema disponible.", | ||
| "next_failed": "No se pudo abrir el siguiente problema", | ||
| "provider_cli_found": "Se encontró la CLI de {provider}.", | ||
| "provider_cli_missing": "No se encontró la CLI de {provider}. Instala {install} o elige {command}.", | ||
| "provider_codex_daemon_available": "Se encontró la CLI de Codex. El servicio de app-server está disponible.", | ||
| "provider_codex_direct_fallback": "Se encontró la CLI de Codex. El servicio de app-server no está disponible; practicode usará codex exec directamente.", | ||
| "model_cli_missing": "No se encontró la CLI de {provider}; elige {command} o instala {install}.", | ||
| "model_claude_presets": "Preajustes de Claude incluidos según Claude Code {version} --help. Esfuerzos: {efforts}.", | ||
| "model_codex_daemon_unavailable": "El servicio de app-server de Codex no está disponible; instala la aplicación independiente para listar modelos o usa /model <name>.", | ||
| "model_codex_query_failed": "No se pudo consultar la lista de modelos de Codex; se usarán preajustes incluidos/en caché.", | ||
| "model_codex_empty": "El app-server de Codex no devolvió modelos.", | ||
| "doctor_missing_tool": "falta {tool}", | ||
| "doctor_node_required": "se requiere node >= 22.6.0", | ||
| "doctor_tsc_unreadable": "no se pudo leer la versión de tsc; se requiere TypeScript 5.9.x", | ||
| "doctor_tsc_required": "se requiere TypeScript 5.9.x ({version})", | ||
| "doctor_unknown_version": "desconocida", | ||
| "doctor_node_install_linux": "Ubuntu/Debian: descarga Node.js LTS desde {url}", | ||
| "doctor_codex_install": "Instala Codex CLI o cambia con /provider claude.", | ||
| "doctor_claude_install": "Instala Claude Code o cambia con /provider codex.", | ||
| "ai_context_disclosure": "Envía lección/problema y código/resultado a la IA elegida" | ||
| } |
+194
-15
@@ -12,8 +12,11 @@ { | ||
| "answer": "解答", | ||
| "source": "source", | ||
| "update": "update", | ||
| "source": "出典", | ||
| "update": "更新", | ||
| "home": "ホーム", | ||
| "home_preview": "プレビュー", | ||
| "home_learn_choice": "文法学習", | ||
| "home_learn_choice": "今日のセッションを続ける", | ||
| "home_practice_choice": "コーディングテスト練習", | ||
| "home_learn_description": "期限の復習を確認し、コア項目を一つ学んで演習を検証します。", | ||
| "home_practice_description": "標準入出力のコーディングテスト問題を解きます。", | ||
| "home_next_step": "次のステップ", | ||
| "home_help": "矢印 選択 | Enter/Space 開く | / コマンド", | ||
@@ -28,3 +31,4 @@ "command_placeholder": "/ を入力し上下で選択、Enter で実行", | ||
| "hint_problem": "/run 判定 | /next 問題 | /home", | ||
| "hint_learn": "/run 検証 | /ask 質問 | /next レッスン | /back レッスン | /home", | ||
| "hint_learn": "F5 実行 | F6 表示 | /next ステップ | /lesson 参照 | /ask | /home", | ||
| "hint_result": "ドラッグ選択でコピー | F6 表示 | /next ステップ | /home", | ||
| "hint_idle": "/ コマンド | ? ヘルプ", | ||
@@ -49,4 +53,6 @@ "hint_busy": "処理中 | q 終了", | ||
| "cmd_learn": "文法学習を開く", | ||
| "cmd_hint": "現在の問題のヒントを依頼", | ||
| "cmd_ai": "現在の問題とコードについて AI に質問", | ||
| "cmd_lesson": "完全な参照レッスンを開く", | ||
| "cmd_progress": "共有できる学習概要を表示", | ||
| "cmd_hint": "問題/レッスン、コード、直近の結果/文脈を選択中のAIプロバイダーへ送りヒントを得る", | ||
| "cmd_ai": "問題/レッスン、コード、直近の結果/文脈を選択中のAIプロバイダーへ送る", | ||
| "cmd_profile": "ユーザープロファイルを表示", | ||
@@ -58,10 +64,10 @@ "cmd_difficulty": "希望難易度を設定", | ||
| "cmd_generate_ui": "生成問題の UI 言語を設定", | ||
| "cmd_provider": "AI provider を設定", | ||
| "cmd_model": "AI model を設定", | ||
| "cmd_model_auto": "provider の既定モデルを使用", | ||
| "cmd_model_available": "利用可能な provider モデルを使用", | ||
| "cmd_provider": "AIプロバイダーを設定", | ||
| "cmd_model": "AIモデルを設定", | ||
| "cmd_model_auto": "プロバイダーの既定モデルを使用", | ||
| "cmd_model_available": "利用可能なプロバイダーのモデルを使用", | ||
| "cmd_model_custom": "モデル名を直接入力", | ||
| "cmd_effort": "AI effort を設定", | ||
| "cmd_effort_auto": "provider の既定 effort を使用", | ||
| "cmd_effort_max": "Claude max effort を使用", | ||
| "cmd_effort": "AIの推論強度を設定", | ||
| "cmd_effort_auto": "プロバイダーの既定の推論強度を使用", | ||
| "cmd_effort_max": "Claudeの最大推論強度を使用", | ||
| "cmd_note": "問題生成メモを編集", | ||
@@ -111,5 +117,178 @@ "cmd_notes": "保存済みメモを見る", | ||
| "exercise_result": "検証結果", | ||
| "cmd_ask": "現在のレッスンやコードについてAIに質問", | ||
| "cmd_ask": "問題/レッスン、コード、直近の結果/文脈を選択中のAIプロバイダーへ送り質問する", | ||
| "syntax_common_mistakes": "よくある間違い", | ||
| "syntax_self_check": "セルフチェック" | ||
| "syntax_self_check": "セルフチェック", | ||
| "learning_shortcuts": "F1 ヘルプ | F5 実行 | F6 レッスン/コード/結果", | ||
| "learning_step_review": "復習", | ||
| "learning_step_delta": "言語の違い", | ||
| "learning_step_predict": "予測", | ||
| "learning_step_exercise": "演習", | ||
| "learning_step_reflect": "振り返り", | ||
| "learning_step_complete": "セッション完了", | ||
| "learning_complete_body": "今日のガイド付きセッションは完了です。/next でレッスンを閲覧するか、/home で別のモードを選んでください。", | ||
| "learning_run_gate": "次: 演習まで /next で進み、その後 /run または F5 を使ってください。", | ||
| "progress_title": "学習進捗", | ||
| "progress_language": "言語", | ||
| "progress_core": "コア", | ||
| "progress_practiced": "練習済み", | ||
| "progress_retained": "定着", | ||
| "progress_mastered": "習得", | ||
| "progress_due": "復習期限", | ||
| "learning_due_reviews": "復習期限", | ||
| "mastery_new": "新規", | ||
| "mastery_practiced": "練習済み", | ||
| "mastery_retained": "定着", | ||
| "mastery_mastered": "習得", | ||
| "judge_failure_compile": "コンパイル", | ||
| "judge_failure_typecheck": "型チェック", | ||
| "judge_failure_runtime": "実行時", | ||
| "judge_failure_timeout": "時間切れ", | ||
| "judge_failure_output": "出力", | ||
| "learning_view_lesson": "レッスン", | ||
| "learning_view_code": "コード", | ||
| "learning_view_result": "結果", | ||
| "resize_required": "ターミナルを60x16以上に広げてください。", | ||
| "focus_active": "選択中", | ||
| "result_pass": "合格", | ||
| "result_fail": "不合格", | ||
| "result_empty": "実行結果はまだありません。", | ||
| "help_home_loop": "1. 学習または練習を選択します。\n2. 矢印キーで移動し、EnterまたはSpaceで開きます。\n3. `/`でコマンドを開きます。", | ||
| "help_learn_loop": "1. レッスンを読みます。\n2. 演習コードを編集します。\n3. `/run`の後に`/next`または`/back`を使います。", | ||
| "help_problem_loop": "1. 解答コードを編集します。\n2. Escを押し、コマンドパレットから`/run`を選びます。\n3. 合格したら`/next`を使います。", | ||
| "help_palette_open": "エディタの外で`/`を押すとコマンドパレットが開きます。", | ||
| "help_palette_move": "`↑/↓`でコマンドを選び、Enterで確定します。", | ||
| "help_palette_close": "Escでコマンドパレットを閉じるか、出力画面から戻ります。", | ||
| "help_stdout": "ケースが失敗するとstdoutを表示します。", | ||
| "help_stderr": "stderrは期待するstdoutに影響せず表示されます。", | ||
| "home_practice_preview_title": "コーディングテスト練習", | ||
| "home_current": "現在", | ||
| "home_status": "状態", | ||
| "home_practice_run": "/runで提出を判定します。", | ||
| "home_practice_next": "/nextで次の問題を開きます。", | ||
| "mode_home": "ホーム", | ||
| "mode_learn": "学習", | ||
| "status_code": "コード", | ||
| "status_idle": "待機中", | ||
| "status_assigned": "割り当て済み", | ||
| "status_solved": "解決済み", | ||
| "status_not_started": "未着手", | ||
| "status_missing": "なし", | ||
| "status_template": "テンプレート", | ||
| "status_empty": "空", | ||
| "status_written": "編集済み", | ||
| "status_auto": "自動", | ||
| "status_easy": "易しい", | ||
| "status_medium": "普通", | ||
| "status_hard": "難しい", | ||
| "status_background_generation": "バックグラウンド生成", | ||
| "hint_notes": "メモを入力 | Esc プロファイル", | ||
| "next_source_help": "/nextは未解決のローカル問題を先に開き、残っていない場合だけAIに依頼します。/generate <依頼>でバックグラウンド生成できます。", | ||
| "empty_value": "<空>", | ||
| "list_closed": "一覧を閉じました。", | ||
| "result_mastery": "習熟度", | ||
| "result_review_days": "次の復習までの日数", | ||
| "result_retry_no_review": "この演習を再試行してください。復習予定はまだありません。", | ||
| "judge_case": "ケース", | ||
| "judge_input": "入力", | ||
| "judge_expected": "期待する出力", | ||
| "judge_got": "実際の出力", | ||
| "judge_stdout": "標準出力", | ||
| "judge_stderr": "標準エラー", | ||
| "judge_error": "エラー", | ||
| "judge_compile": "コンパイル", | ||
| "judge_hidden": "非表示", | ||
| "judge_timeout_detail": "時間切れ: 5秒", | ||
| "judge_no_cases": "判定ケースがありません。", | ||
| "judge_missing_runtime": "{runtime}のランタイムがありません。", | ||
| "judge_process_exit": "プロセス終了ステータス {status}", | ||
| "busy_ai_thinking": "{provider} が考え中", | ||
| "elapsed_seconds": "{seconds}秒", | ||
| "generation_started": "バックグラウンドで生成中です。", | ||
| "generation_already_running": "バックグラウンド生成はすでに実行中です。そのまま問題を解けば、完了後に /next で新しい問題を利用できます。", | ||
| "generation_duplicate": "生成がすでに実行中のため、重複した /generate をスキップしました。", | ||
| "generation_save_failed": "生成前に練習モードを保存できませんでした。", | ||
| "generation_generated": "バックグラウンドで問題を{count}件生成しました。/nextを使用してください。", | ||
| "generation_failed": "バックグラウンド生成に失敗しました。/generateで再試行してください。", | ||
| "generation_finished": "バックグラウンド生成が完了しました。/problemsで確認してください。", | ||
| "generation_reload_failed": "バックグラウンド生成は完了しましたが、問題バンクを再読み込みできませんでした。", | ||
| "generation_exit_status": "終了ステータス: {status}", | ||
| "generation_partial_count": "失敗しましたが、問題バンクには新しい問題が{count}件反映されました。", | ||
| "judge_unknown_status": "不明", | ||
| "judge_missing_typescript_tool": "TypeScriptランタイムがありません: {tool}", | ||
| "hint_learn_compact": "F1 ヘルプ | F5 実行 | F6 表示 | /next", | ||
| "hint_problem_compact": "F1 ヘルプ | F6 表示 | /run | /next", | ||
| "hint_home_compact": "F1 ヘルプ | 矢印で選択 | Enterで開く", | ||
| "pane_exercise": "演習", | ||
| "pane_solution": "解答", | ||
| "settings_title": "ユーザープロファイル", | ||
| "settings_instructions": "上下で移動・Space/Enterで変更", | ||
| "settings_code_language": "コード言語", | ||
| "settings_ui_language": "UI言語", | ||
| "settings_theme": "テーマ", | ||
| "settings_difficulty": "難易度", | ||
| "settings_preferred_topics": "優先トピック", | ||
| "settings_avoid_topics": "避けるトピック", | ||
| "settings_generated_answer_languages": "生成する解答の言語", | ||
| "settings_generated_ui_languages": "生成する問題文の言語", | ||
| "settings_provider_default": "auto(プロバイダー既定)", | ||
| "settings_problem_notes": "問題生成メモを編集", | ||
| "settings_answer_toggles": "生成する解答言語の切替", | ||
| "settings_ui_toggles": "生成する問題文言語の切替", | ||
| "settings_commands": "コマンド", | ||
| "settings_none": "(なし)", | ||
| "settings_all": "すべて", | ||
| "settings_ai_provider": "AIプロバイダー", | ||
| "settings_ai_model": "AIモデル", | ||
| "settings_ai_effort": "AI推論レベル", | ||
| "settings_model_loading": "読込中", | ||
| "settings_model_load_hint": "/modelで読込", | ||
| "settings_note_action": "Enter", | ||
| "model_use_default_model": "既定モデルをプロバイダーに任せるには /model auto を使います。", | ||
| "model_use_default_effort": "既定の推論レベルをプロバイダーに任せるには /effort auto を使います。", | ||
| "model_loading": "プロバイダーのモデル一覧を読込中...", | ||
| "model_unavailable": "プロバイダーのモデル一覧を利用できません。", | ||
| "model_custom_hint": "既知のモデルは /model <name> で指定できます。", | ||
| "model_available_efforts": "利用できる推論レベル: {efforts}", | ||
| "model_available_models": "利用できるモデル:", | ||
| "note_saved": "問題メモを {path} に保存しました。", | ||
| "notes_empty": "メモはまだありません。/note で問題生成メモを編集できます。", | ||
| "notes_title": "問題メモ ({path})", | ||
| "first_problem": "先頭の既知の問題です。", | ||
| "answer_for_language": "{language} の解答:", | ||
| "learn_usage": "使い方: /learn または /learn python|ts|java|rust", | ||
| "ui_language_set": "UI言語: {language}", | ||
| "theme_set": "テーマ: {theme}", | ||
| "difficulty_options": "難易度: auto、easy、medium、hard のいずれか", | ||
| "practice_shortcuts": "F1 ヘルプ | F6 問題/コード | /run | /next", | ||
| "problem_list_title": "問題一覧", | ||
| "problem_list_id": "ID", | ||
| "problem_list_difficulty": "難易度", | ||
| "problem_list_status": "状態", | ||
| "problem_list_code": "コード", | ||
| "problem_list_name": "タイトル", | ||
| "problem_list_hint": "上下または j/k で選択 | Enter で開く | Esc で閉じる", | ||
| "problem_not_found": "問題が見つかりません: {query}\n/problems を使ってください。", | ||
| "update_checking": "更新を確認中...", | ||
| "ai_next_command_saved": "AIの次問題コマンドを保存しました。", | ||
| "unknown_command": "不明なコマンド: {command}\n/help を使ってください。", | ||
| "next_unavailable": "次の問題はまだありません。", | ||
| "next_failed": "次の問題を開けませんでした", | ||
| "provider_cli_found": "{provider} CLIが見つかりました。", | ||
| "provider_cli_missing": "{provider} CLIがありません。{install}をインストールするか、{command}を選んでください。", | ||
| "provider_codex_daemon_available": "Codex CLIが見つかりました。アプリサーバーデーモンを利用できます。", | ||
| "provider_codex_direct_fallback": "Codex CLIが見つかりました。アプリサーバーデーモンを利用できないため、practicodeはcodex execを直接使います。", | ||
| "model_cli_missing": "{provider} CLIがありません。{command}を選ぶか、{install}をインストールしてください。", | ||
| "model_claude_presets": "Claude Code {version} --helpに基づく内蔵Claudeプリセットです。推論レベル: {efforts}。", | ||
| "model_codex_daemon_unavailable": "Codexアプリサーバーデーモンを利用できません。モデル一覧には単体Codexアプリをインストールするか、/model <name>を使ってください。", | ||
| "model_codex_query_failed": "Codexモデル一覧を取得できないため、内蔵/キャッシュ済みプリセットを使います。", | ||
| "model_codex_empty": "Codexアプリサーバーからモデルが返りませんでした。", | ||
| "doctor_missing_tool": "{tool}がありません", | ||
| "doctor_node_required": "node >= 22.6.0が必要", | ||
| "doctor_tsc_unreadable": "tscのバージョンを読めません。TypeScript 5.9.xが必要です", | ||
| "doctor_tsc_required": "TypeScript 5.9.xが必要 ({version})", | ||
| "doctor_unknown_version": "不明", | ||
| "doctor_node_install_linux": "Ubuntu/Debian: {url}からNode.js LTSをダウンロード", | ||
| "doctor_codex_install": "Codex CLIをインストールするか、/provider claudeへ切り替えてください。", | ||
| "doctor_claude_install": "Claude Codeをインストールするか、/provider codexへ切り替えてください。", | ||
| "ai_context_disclosure": "レッスン/問題・コード・結果を選択中のAIへ送信" | ||
| } |
+192
-13
@@ -16,4 +16,7 @@ { | ||
| "home_preview": "미리보기", | ||
| "home_learn_choice": "문법 학습", | ||
| "home_learn_choice": "오늘의 세션 계속하기", | ||
| "home_practice_choice": "코딩테스트 연습", | ||
| "home_learn_description": "복습할 항목을 확인하고 핵심 항목 하나를 배운 뒤 실습을 검증합니다.", | ||
| "home_practice_description": "표준 입출력 코딩테스트 문제를 풉니다.", | ||
| "home_next_step": "다음 단계", | ||
| "home_help": "방향키 선택 | Enter/Space 열기 | / 명령", | ||
@@ -28,3 +31,4 @@ "command_placeholder": "/ 입력 후 위/아래로 선택, Enter 실행", | ||
| "hint_problem": "/run 채점 | /next 문제 | /home", | ||
| "hint_learn": "/run 검증 | /ask 질문 | /next 레슨 | /back 레슨 | /home", | ||
| "hint_learn": "F5 실행 | F6 보기 | /next 단계 | /lesson 참고 | /ask | /home", | ||
| "hint_result": "드래그 선택으로 복사 | F6 보기 | /next 단계 | /home", | ||
| "hint_idle": "/ 명령 | ? 도움말", | ||
@@ -49,5 +53,7 @@ "hint_busy": "작업 중 | q 종료", | ||
| "cmd_learn": "문법 학습 열기", | ||
| "cmd_hint": "현재 문제 힌트 요청", | ||
| "cmd_ask": "현재 레슨이나 코드에 대해 AI에게 질문", | ||
| "cmd_ai": "현재 문제와 코드에 대해 AI에게 질문", | ||
| "cmd_lesson": "전체 참고 레슨 열기", | ||
| "cmd_progress": "공유 가능한 학습 요약 보기", | ||
| "cmd_hint": "문제/레슨, 코드, 최근 결과/맥락을 선택한 AI 제공자에 보내 힌트 받기", | ||
| "cmd_ask": "문제/레슨, 코드, 최근 결과/맥락을 선택한 AI 제공자에 보내 질문하기", | ||
| "cmd_ai": "문제/레슨, 코드, 최근 결과/맥락을 선택한 AI 제공자에 보내기", | ||
| "cmd_profile": "사용자 프로필 보기", | ||
@@ -59,10 +65,10 @@ "cmd_difficulty": "선호 난이도 설정", | ||
| "cmd_generate_ui": "생성 문제의 UI 언어 설정", | ||
| "cmd_provider": "AI provider 설정", | ||
| "cmd_model": "AI model 설정", | ||
| "cmd_model_auto": "provider 기본 모델 사용", | ||
| "cmd_model_available": "사용 가능한 provider 모델 선택", | ||
| "cmd_provider": "AI 제공자 설정", | ||
| "cmd_model": "AI 모델 설정", | ||
| "cmd_model_auto": "제공자 기본 모델 사용", | ||
| "cmd_model_available": "사용 가능한 제공자 모델 선택", | ||
| "cmd_model_custom": "모델 이름 직접 입력", | ||
| "cmd_effort": "AI effort 설정", | ||
| "cmd_effort_auto": "provider 기본 effort 사용", | ||
| "cmd_effort_max": "Claude max effort 사용", | ||
| "cmd_effort": "AI 추론 강도 설정", | ||
| "cmd_effort_auto": "제공자 기본 추론 강도 사용", | ||
| "cmd_effort_max": "Claude 최대 추론 강도 사용", | ||
| "cmd_note": "문제 생성 메모 편집", | ||
@@ -113,3 +119,176 @@ "cmd_notes": "저장된 메모 보기", | ||
| "syntax_advanced": "심화", | ||
| "exercise_result": "검증 결과" | ||
| "exercise_result": "검증 결과", | ||
| "learning_shortcuts": "F1 도움말 | F5 실행 | F6 레슨/코드/결과", | ||
| "learning_step_review": "복습", | ||
| "learning_step_delta": "언어 차이", | ||
| "learning_step_predict": "예측", | ||
| "learning_step_exercise": "실습", | ||
| "learning_step_reflect": "회고", | ||
| "learning_step_complete": "세션 완료", | ||
| "learning_complete_body": "오늘의 안내 세션을 마쳤습니다. /next로 레슨을 둘러보거나 /home에서 다른 모드를 선택하세요.", | ||
| "learning_run_gate": "다음: 실습 단계까지 /next로 이동한 뒤 /run 또는 F5를 사용하세요.", | ||
| "progress_title": "학습 진도", | ||
| "progress_language": "언어", | ||
| "progress_core": "핵심", | ||
| "progress_practiced": "연습함", | ||
| "progress_retained": "기억함", | ||
| "progress_mastered": "숙달함", | ||
| "progress_due": "복습 예정", | ||
| "learning_due_reviews": "복습 예정", | ||
| "mastery_new": "새 항목", | ||
| "mastery_practiced": "연습함", | ||
| "mastery_retained": "기억함", | ||
| "mastery_mastered": "숙달함", | ||
| "judge_failure_compile": "컴파일", | ||
| "judge_failure_typecheck": "타입 검사", | ||
| "judge_failure_runtime": "실행", | ||
| "judge_failure_timeout": "시간 초과", | ||
| "judge_failure_output": "출력", | ||
| "learning_view_lesson": "레슨", | ||
| "learning_view_code": "코드", | ||
| "learning_view_result": "결과", | ||
| "resize_required": "터미널 크기를 60x16 이상으로 조정하세요.", | ||
| "focus_active": "활성", | ||
| "result_pass": "통과", | ||
| "result_fail": "실패", | ||
| "result_empty": "아직 실행 결과가 없습니다.", | ||
| "help_home_loop": "1. 학습 또는 연습을 선택하세요.\n2. 방향키로 이동하고 Enter 또는 Space로 여세요.\n3. `/`를 눌러 명령을 여세요.", | ||
| "help_learn_loop": "1. 레슨을 읽으세요.\n2. 실습 코드를 수정하세요.\n3. `/run`을 실행한 뒤 `/next` 또는 `/back`을 사용하세요.", | ||
| "help_problem_loop": "1. 풀이 코드를 작성하세요.\n2. Esc를 누른 뒤 명령 팔레트에서 `/run`을 선택하세요.\n3. 통과하면 `/next`를 사용하세요.", | ||
| "help_palette_open": "편집기 밖에서 `/`를 누르면 명령 팔레트가 열립니다.", | ||
| "help_palette_move": "`↑/↓`로 명령을 선택하고 Enter로 확정합니다.", | ||
| "help_palette_close": "Esc로 명령 팔레트를 닫거나 출력 화면에서 나갑니다.", | ||
| "help_stdout": "케이스가 실패하면 stdout을 표시합니다.", | ||
| "help_stderr": "stderr는 기대 stdout을 바꾸지 않고 표시합니다.", | ||
| "home_practice_preview_title": "코딩테스트 연습", | ||
| "home_current": "현재 문제", | ||
| "home_status": "상태", | ||
| "home_practice_run": "/run으로 제출을 채점하세요.", | ||
| "home_practice_next": "/next로 다음 문제를 여세요.", | ||
| "mode_home": "홈", | ||
| "mode_learn": "학습", | ||
| "status_code": "코드", | ||
| "status_idle": "대기", | ||
| "status_assigned": "배정됨", | ||
| "status_solved": "해결함", | ||
| "status_not_started": "시작 전", | ||
| "status_missing": "없음", | ||
| "status_template": "템플릿", | ||
| "status_empty": "비어 있음", | ||
| "status_written": "작성함", | ||
| "status_auto": "자동", | ||
| "status_easy": "쉬움", | ||
| "status_medium": "보통", | ||
| "status_hard": "어려움", | ||
| "status_background_generation": "백그라운드 생성", | ||
| "hint_notes": "메모 입력 | Esc 프로필", | ||
| "next_source_help": "/next는 풀지 않은 로컬 문제를 먼저 열고, 남은 문제가 없을 때만 AI에 요청합니다. /generate <요청>으로 백그라운드에서 문제를 만드세요.", | ||
| "empty_value": "<비어 있음>", | ||
| "list_closed": "목록을 닫았습니다.", | ||
| "result_mastery": "숙련도", | ||
| "result_review_days": "다음 복습까지(일)", | ||
| "result_retry_no_review": "이 실습을 다시 시도하세요. 복습 일정은 아직 없습니다.", | ||
| "judge_case": "케이스", | ||
| "judge_input": "입력", | ||
| "judge_expected": "기대 출력", | ||
| "judge_got": "실제 출력", | ||
| "judge_stdout": "표준 출력", | ||
| "judge_stderr": "표준 오류", | ||
| "judge_error": "오류", | ||
| "judge_compile": "컴파일", | ||
| "judge_hidden": "숨김", | ||
| "judge_timeout_detail": "시간 초과: 5초", | ||
| "judge_no_cases": "채점 케이스가 없습니다.", | ||
| "judge_missing_runtime": "{runtime} 런타임이 없습니다.", | ||
| "judge_process_exit": "프로세스 종료 상태 {status}", | ||
| "busy_ai_thinking": "{provider}가 생각 중", | ||
| "elapsed_seconds": "{seconds}초", | ||
| "generation_started": "백그라운드에서 생성 중입니다.", | ||
| "generation_already_running": "백그라운드 생성이 이미 진행 중입니다. 계속 문제를 풀면 완료 후 /next에서 새 문제를 사용할 수 있습니다.", | ||
| "generation_duplicate": "생성이 이미 진행 중이어서 중복 /generate를 건너뛰었습니다.", | ||
| "generation_save_failed": "생성 전에 연습 모드를 저장하지 못했습니다.", | ||
| "generation_generated": "백그라운드에서 문제 {count}개를 생성했습니다. /next를 사용하세요.", | ||
| "generation_failed": "백그라운드 생성에 실패했습니다. /generate로 다시 시도하세요.", | ||
| "generation_finished": "백그라운드 생성이 끝났습니다. /problems에서 확인하세요.", | ||
| "generation_reload_failed": "백그라운드 생성은 끝났지만 문제 목록을 다시 불러오지 못했습니다.", | ||
| "generation_exit_status": "종료 상태: {status}", | ||
| "generation_partial_count": "실패했지만 문제 목록에 새 문제 {count}개가 반영되었습니다.", | ||
| "judge_unknown_status": "알 수 없음", | ||
| "judge_missing_typescript_tool": "TypeScript 런타임이 없습니다: {tool}", | ||
| "hint_learn_compact": "F1 도움 | F5 실행 | F6 보기 | /next", | ||
| "hint_problem_compact": "F1 도움 | F6 보기 | /run | /next", | ||
| "hint_home_compact": "F1 도움 | 방향키 선택 | Enter 열기", | ||
| "pane_exercise": "실습", | ||
| "pane_solution": "정답", | ||
| "settings_title": "사용자 프로필", | ||
| "settings_instructions": "위/아래 이동 · Space/Enter 변경", | ||
| "settings_code_language": "코드 언어", | ||
| "settings_ui_language": "UI 언어", | ||
| "settings_theme": "테마", | ||
| "settings_difficulty": "난이도", | ||
| "settings_preferred_topics": "선호 주제", | ||
| "settings_avoid_topics": "피할 주제", | ||
| "settings_generated_answer_languages": "생성 정답 언어", | ||
| "settings_generated_ui_languages": "생성 문제 언어", | ||
| "settings_provider_default": "auto (제공자 기본값)", | ||
| "settings_problem_notes": "문제 생성 메모 편집", | ||
| "settings_answer_toggles": "생성 정답 언어 토글", | ||
| "settings_ui_toggles": "생성 문제 언어 토글", | ||
| "settings_commands": "명령", | ||
| "settings_none": "(없음)", | ||
| "settings_all": "전체", | ||
| "settings_ai_provider": "AI 제공자", | ||
| "settings_ai_model": "AI 모델", | ||
| "settings_ai_effort": "AI 추론 강도", | ||
| "settings_model_loading": "불러오는 중", | ||
| "settings_model_load_hint": "/model로 불러오기", | ||
| "settings_note_action": "Enter", | ||
| "model_use_default_model": "제공자가 기본 모델을 고르게 하려면 /model auto를 사용하세요.", | ||
| "model_use_default_effort": "제공자가 기본 추론 강도를 고르게 하려면 /effort auto를 사용하세요.", | ||
| "model_loading": "제공자 모델 목록을 불러오는 중...", | ||
| "model_unavailable": "제공자 모델 목록을 사용할 수 없습니다.", | ||
| "model_custom_hint": "알고 있는 모델은 /model <name>으로 설정하세요.", | ||
| "model_available_efforts": "사용 가능한 추론 강도: {efforts}", | ||
| "model_available_models": "사용 가능한 모델:", | ||
| "note_saved": "문제 메모를 {path}에 저장했습니다.", | ||
| "notes_empty": "메모가 없습니다. /note로 문제 생성 메모를 편집하세요.", | ||
| "notes_title": "문제 메모 ({path})", | ||
| "first_problem": "첫 번째 문제입니다.", | ||
| "answer_for_language": "정답 ({language}):", | ||
| "learn_usage": "사용법: /learn 또는 /learn python|ts|java|rust", | ||
| "ui_language_set": "UI 언어: {language}", | ||
| "theme_set": "테마: {theme}", | ||
| "difficulty_options": "난이도: auto, easy, medium, hard 중 하나", | ||
| "practice_shortcuts": "F1 도움 | F6 문제/코드 | /run | /next", | ||
| "problem_list_title": "문제 목록", | ||
| "problem_list_id": "ID", | ||
| "problem_list_difficulty": "난이도", | ||
| "problem_list_status": "상태", | ||
| "problem_list_code": "코드", | ||
| "problem_list_name": "제목", | ||
| "problem_list_hint": "위/아래 또는 j/k 선택 | Enter 열기 | Esc 닫기", | ||
| "problem_not_found": "문제를 찾지 못했습니다: {query}\n/problems를 사용하세요.", | ||
| "update_checking": "업데이트 확인 중...", | ||
| "ai_next_command_saved": "AI 다음 문제 명령을 저장했습니다.", | ||
| "unknown_command": "알 수 없는 명령: {command}\n/help를 사용하세요.", | ||
| "next_unavailable": "아직 다음 문제가 없습니다.", | ||
| "next_failed": "다음 문제를 열지 못했습니다", | ||
| "provider_cli_found": "{provider} CLI를 찾았습니다.", | ||
| "provider_cli_missing": "{provider} CLI가 없습니다. {install} 설치 또는 {command} 선택이 필요합니다.", | ||
| "provider_codex_daemon_available": "Codex CLI를 찾았습니다. 앱 서버 데몬을 사용할 수 있습니다.", | ||
| "provider_codex_direct_fallback": "Codex CLI를 찾았습니다. 앱 서버 데몬이 없어 practicode가 codex exec를 직접 사용합니다.", | ||
| "model_cli_missing": "{provider} CLI가 없습니다. {command}를 선택하거나 {install}을 설치하세요.", | ||
| "model_claude_presets": "Claude Code {version} --help 기준 내장 Claude 프리셋입니다. 추론 강도: {efforts}.", | ||
| "model_codex_daemon_unavailable": "Codex 앱 서버 데몬을 사용할 수 없습니다. 모델 목록을 보려면 독립형 Codex 앱을 설치하거나 /model <name>을 사용하세요.", | ||
| "model_codex_query_failed": "Codex 모델 목록을 조회하지 못해 내장/캐시 모델 프리셋을 사용합니다.", | ||
| "model_codex_empty": "Codex 앱 서버가 모델을 반환하지 않았습니다.", | ||
| "doctor_missing_tool": "{tool} 없음", | ||
| "doctor_node_required": "node >= 22.6.0 필요", | ||
| "doctor_tsc_unreadable": "tsc 버전을 읽을 수 없음; TypeScript 5.9.x 필요", | ||
| "doctor_tsc_required": "TypeScript 5.9.x 필요 ({version})", | ||
| "doctor_unknown_version": "알 수 없음", | ||
| "doctor_node_install_linux": "Ubuntu/Debian: {url}에서 Node.js LTS 다운로드", | ||
| "doctor_codex_install": "Codex CLI를 설치하거나 /provider claude로 전환하세요.", | ||
| "doctor_claude_install": "Claude Code를 설치하거나 /provider codex로 전환하세요.", | ||
| "ai_context_disclosure": "레슨/문제·코드·결과를 선택한 AI로 전송" | ||
| } |
+192
-13
@@ -16,4 +16,7 @@ { | ||
| "home_preview": "预览", | ||
| "home_learn_choice": "语法学习", | ||
| "home_learn_choice": "继续今天的学习", | ||
| "home_practice_choice": "编程测试练习", | ||
| "home_learn_description": "先复习到期内容,再学习一个核心项目并验证练习。", | ||
| "home_practice_description": "练习标准输入输出编程题。", | ||
| "home_next_step": "下一步", | ||
| "home_help": "方向键 选择 | Enter/Space 打开 | / 命令", | ||
@@ -28,3 +31,4 @@ "command_placeholder": "输入 / 后用上下键选择,Enter 执行", | ||
| "hint_problem": "/run 评测 | /next 题目 | /home", | ||
| "hint_learn": "/run 验证 | /ask 提问 | /next 课程 | /back 课程 | /home", | ||
| "hint_learn": "F5 运行 | F6 视图 | /next 步骤 | /lesson 参考 | /ask | /home", | ||
| "hint_result": "拖拽选择复制 | F6 视图 | /next 步骤 | /home", | ||
| "hint_idle": "/ 命令 | ? 帮助", | ||
@@ -49,4 +53,6 @@ "hint_busy": "处理中 | q 退出", | ||
| "cmd_learn": "打开语法学习", | ||
| "cmd_hint": "请求当前题目的提示", | ||
| "cmd_ai": "向 AI 询问当前题目和代码", | ||
| "cmd_lesson": "打开完整参考课程", | ||
| "cmd_progress": "显示可分享的学习摘要", | ||
| "cmd_hint": "将题目/课程、代码和最新结果/上下文发送给所选 AI 提供商以获取提示", | ||
| "cmd_ai": "将题目/课程、代码和最新结果/上下文发送给所选 AI 提供商", | ||
| "cmd_profile": "显示用户配置", | ||
@@ -58,10 +64,10 @@ "cmd_difficulty": "设置偏好难度", | ||
| "cmd_generate_ui": "设置生成题目的 UI 语言", | ||
| "cmd_provider": "设置 AI provider", | ||
| "cmd_model": "设置 AI model", | ||
| "cmd_model_auto": "使用 provider 默认模型", | ||
| "cmd_model_available": "使用可用的 provider 模型", | ||
| "cmd_provider": "设置AI提供方", | ||
| "cmd_model": "设置AI模型", | ||
| "cmd_model_auto": "使用提供方的默认模型", | ||
| "cmd_model_available": "使用提供方的可用模型", | ||
| "cmd_model_custom": "输入模型名称", | ||
| "cmd_effort": "设置 AI effort", | ||
| "cmd_effort_auto": "使用 provider 默认 effort", | ||
| "cmd_effort_max": "使用 Claude max effort", | ||
| "cmd_effort": "设置AI推理强度", | ||
| "cmd_effort_auto": "使用提供方的默认推理强度", | ||
| "cmd_effort_max": "使用Claude的最大推理强度", | ||
| "cmd_note": "编辑出题备注", | ||
@@ -111,5 +117,178 @@ "cmd_notes": "查看保存的备注", | ||
| "exercise_result": "验证结果", | ||
| "cmd_ask": "向 AI 询问当前课程或代码", | ||
| "cmd_ask": "将题目/课程、代码和最新结果/上下文发送给所选 AI 提供商以提问", | ||
| "syntax_common_mistakes": "常见错误", | ||
| "syntax_self_check": "自我检查" | ||
| "syntax_self_check": "自我检查", | ||
| "learning_shortcuts": "F1 帮助 | F5 运行 | F6 课程/代码/结果", | ||
| "learning_step_review": "复习", | ||
| "learning_step_delta": "语言差异", | ||
| "learning_step_predict": "预测", | ||
| "learning_step_exercise": "练习", | ||
| "learning_step_reflect": "回顾", | ||
| "learning_step_complete": "本次学习完成", | ||
| "learning_complete_body": "今天的引导学习已完成。使用 /next 浏览课程,或使用 /home 选择其他模式。", | ||
| "learning_run_gate": "下一步:用 /next 进入练习步骤,然后使用 /run 或 F5。", | ||
| "progress_title": "学习进度", | ||
| "progress_language": "语言", | ||
| "progress_core": "核心", | ||
| "progress_practiced": "已练习", | ||
| "progress_retained": "已巩固", | ||
| "progress_mastered": "已掌握", | ||
| "progress_due": "待复习", | ||
| "learning_due_reviews": "待复习", | ||
| "mastery_new": "新内容", | ||
| "mastery_practiced": "已练习", | ||
| "mastery_retained": "已巩固", | ||
| "mastery_mastered": "已掌握", | ||
| "judge_failure_compile": "编译", | ||
| "judge_failure_typecheck": "类型检查", | ||
| "judge_failure_runtime": "运行时", | ||
| "judge_failure_timeout": "超时", | ||
| "judge_failure_output": "输出", | ||
| "learning_view_lesson": "课程", | ||
| "learning_view_code": "代码", | ||
| "learning_view_result": "结果", | ||
| "resize_required": "请将终端调整到至少60x16。", | ||
| "focus_active": "当前", | ||
| "result_pass": "通过", | ||
| "result_fail": "未通过", | ||
| "result_empty": "还没有运行结果。", | ||
| "help_home_loop": "1. 选择学习或练习。\n2. 用方向键移动,按Enter或Space打开。\n3. 按`/`打开命令。", | ||
| "help_learn_loop": "1. 阅读课程。\n2. 编辑练习代码。\n3. 执行`/run`,然后使用`/next`或`/back`。", | ||
| "help_problem_loop": "1. 编辑解题代码。\n2. 按Esc,再从命令面板选择`/run`。\n3. 通过后使用`/next`。", | ||
| "help_palette_open": "在编辑器外按`/`可打开命令面板。", | ||
| "help_palette_move": "用`↑/↓`选择命令,按Enter确认。", | ||
| "help_palette_close": "按Esc取消命令面板或离开输出页面。", | ||
| "help_stdout": "测试用例失败时会显示stdout。", | ||
| "help_stderr": "stderr会单独显示,不会改变预期stdout。", | ||
| "home_practice_preview_title": "编程测试练习", | ||
| "home_current": "当前", | ||
| "home_status": "状态", | ||
| "home_practice_run": "使用/run评测提交。", | ||
| "home_practice_next": "使用/next打开下一题。", | ||
| "mode_home": "主页", | ||
| "mode_learn": "学习", | ||
| "status_code": "代码", | ||
| "status_idle": "空闲", | ||
| "status_assigned": "已分配", | ||
| "status_solved": "已解决", | ||
| "status_not_started": "未开始", | ||
| "status_missing": "无", | ||
| "status_template": "模板", | ||
| "status_empty": "空", | ||
| "status_written": "已编写", | ||
| "status_auto": "自动", | ||
| "status_easy": "简单", | ||
| "status_medium": "中等", | ||
| "status_hard": "困难", | ||
| "status_background_generation": "后台生成", | ||
| "hint_notes": "编辑备注 | Esc 配置", | ||
| "next_source_help": "/next会先打开未解决的本地题目,只在没有剩余题目时请求AI。使用/generate <要求>可在后台生成题目。", | ||
| "empty_value": "<空>", | ||
| "list_closed": "已关闭列表。", | ||
| "result_mastery": "掌握程度", | ||
| "result_review_days": "距离下次复习的天数", | ||
| "result_retry_no_review": "请重试此练习,目前尚未安排复习。", | ||
| "judge_case": "用例", | ||
| "judge_input": "输入", | ||
| "judge_expected": "预期输出", | ||
| "judge_got": "实际输出", | ||
| "judge_stdout": "标准输出", | ||
| "judge_stderr": "标准错误", | ||
| "judge_error": "错误", | ||
| "judge_compile": "编译", | ||
| "judge_hidden": "已隐藏", | ||
| "judge_timeout_detail": "超时: 5秒", | ||
| "judge_no_cases": "没有评测用例。", | ||
| "judge_missing_runtime": "缺少{runtime}运行时。", | ||
| "judge_process_exit": "进程退出状态 {status}", | ||
| "busy_ai_thinking": "{provider} 正在思考", | ||
| "elapsed_seconds": "{seconds}秒", | ||
| "generation_started": "正在后台生成。", | ||
| "generation_already_running": "后台生成已在进行中。请继续解题;完成后可通过/next使用新题目。", | ||
| "generation_duplicate": "生成已在进行中,已跳过重复的/generate。", | ||
| "generation_save_failed": "生成前无法保存练习模式。", | ||
| "generation_generated": "已在后台生成{count}道题。请使用/next。", | ||
| "generation_failed": "后台生成失败。请使用/generate重试。", | ||
| "generation_finished": "后台生成已完成。请使用/problems查看。", | ||
| "generation_reload_failed": "后台生成已完成,但无法重新加载题库。", | ||
| "generation_exit_status": "退出状态:{status}", | ||
| "generation_partial_count": "尽管生成失败,题库中仍新增了{count}道题。", | ||
| "judge_unknown_status": "未知", | ||
| "judge_missing_typescript_tool": "缺少TypeScript运行时:{tool}", | ||
| "hint_learn_compact": "F1 帮助 | F5 运行 | F6 视图 | /next", | ||
| "hint_problem_compact": "F1 帮助 | F6 视图 | /run | /next", | ||
| "hint_home_compact": "F1 帮助 | 方向键选择 | Enter 打开", | ||
| "pane_exercise": "练习", | ||
| "pane_solution": "解答", | ||
| "settings_title": "用户设置", | ||
| "settings_instructions": "上下移动 · Space/Enter 更改", | ||
| "settings_code_language": "代码语言", | ||
| "settings_ui_language": "界面语言", | ||
| "settings_theme": "主题", | ||
| "settings_difficulty": "难度", | ||
| "settings_preferred_topics": "偏好主题", | ||
| "settings_avoid_topics": "避开主题", | ||
| "settings_generated_answer_languages": "生成答案语言", | ||
| "settings_generated_ui_languages": "生成题目语言", | ||
| "settings_provider_default": "auto(提供商默认值)", | ||
| "settings_problem_notes": "编辑出题备注", | ||
| "settings_answer_toggles": "切换生成答案语言", | ||
| "settings_ui_toggles": "切换生成题目语言", | ||
| "settings_commands": "命令", | ||
| "settings_none": "(无)", | ||
| "settings_all": "全部", | ||
| "settings_ai_provider": "AI 提供商", | ||
| "settings_ai_model": "AI 模型", | ||
| "settings_ai_effort": "AI 推理强度", | ||
| "settings_model_loading": "加载中", | ||
| "settings_model_load_hint": "用 /model 加载", | ||
| "settings_note_action": "Enter", | ||
| "model_use_default_model": "使用 /model auto 让提供商选择默认模型。", | ||
| "model_use_default_effort": "使用 /effort auto 让提供商选择默认推理强度。", | ||
| "model_loading": "正在加载提供商模型列表...", | ||
| "model_unavailable": "无法使用提供商模型列表。", | ||
| "model_custom_hint": "可用 /model <name> 指定已知模型。", | ||
| "model_available_efforts": "可用推理强度: {efforts}", | ||
| "model_available_models": "可用模型:", | ||
| "note_saved": "题目备注已保存至 {path}。", | ||
| "notes_empty": "尚无备注。使用 /note 编辑出题备注。", | ||
| "notes_title": "题目备注 ({path})", | ||
| "first_problem": "已经是已知的第一题。", | ||
| "answer_for_language": "{language} 解答:", | ||
| "learn_usage": "用法: /learn 或 /learn python|ts|java|rust", | ||
| "ui_language_set": "界面语言: {language}", | ||
| "theme_set": "主题: {theme}", | ||
| "difficulty_options": "难度: auto、easy、medium 或 hard", | ||
| "practice_shortcuts": "F1 帮助 | F6 题目/代码 | /run | /next", | ||
| "problem_list_title": "题目列表", | ||
| "problem_list_id": "ID", | ||
| "problem_list_difficulty": "难度", | ||
| "problem_list_status": "状态", | ||
| "problem_list_code": "代码", | ||
| "problem_list_name": "标题", | ||
| "problem_list_hint": "上下或 j/k 选择 | Enter 打开 | Esc 关闭", | ||
| "problem_not_found": "未找到题目: {query}\n请使用 /problems。", | ||
| "update_checking": "正在检查更新...", | ||
| "ai_next_command_saved": "已保存 AI 下一题命令。", | ||
| "unknown_command": "未知命令: {command}\n请使用 /help。", | ||
| "next_unavailable": "暂时没有下一题。", | ||
| "next_failed": "无法打开下一题", | ||
| "provider_cli_found": "已找到 {provider} CLI。", | ||
| "provider_cli_missing": "未找到 {provider} CLI。请安装 {install} 或选择 {command}。", | ||
| "provider_codex_daemon_available": "已找到 Codex CLI。应用服务器守护进程可用。", | ||
| "provider_codex_direct_fallback": "已找到 Codex CLI。应用服务器守护进程不可用;practicode 将直接使用 codex exec。", | ||
| "model_cli_missing": "未找到 {provider} CLI;请选择 {command} 或安装 {install}。", | ||
| "model_claude_presets": "基于 Claude Code {version} --help 的内置 Claude 预设。推理强度: {efforts}。", | ||
| "model_codex_daemon_unavailable": "Codex 应用服务器守护进程不可用;请安装独立 Codex 应用以列出模型,或使用 /model <name>。", | ||
| "model_codex_query_failed": "无法查询 Codex 模型列表;将使用内置/缓存的模型预设。", | ||
| "model_codex_empty": "Codex 应用服务器未返回模型。", | ||
| "doctor_missing_tool": "缺少 {tool}", | ||
| "doctor_node_required": "需要 node >= 22.6.0", | ||
| "doctor_tsc_unreadable": "无法读取 tsc 版本;需要 TypeScript 5.9.x", | ||
| "doctor_tsc_required": "需要 TypeScript 5.9.x({version})", | ||
| "doctor_unknown_version": "未知", | ||
| "doctor_node_install_linux": "Ubuntu/Debian: 从 {url} 下载 Node.js LTS", | ||
| "doctor_codex_install": "请安装 Codex CLI,或用 /provider claude 切换。", | ||
| "doctor_claude_install": "请安装 Claude Code,或用 /provider codex 切换。", | ||
| "ai_context_disclosure": "将课程/题目、代码和结果发送给所选 AI" | ||
| } |
+363
-279
@@ -7,422 +7,506 @@ { | ||
| "java-output": { | ||
| "title": "Stdout", | ||
| "concept": "Use Stdout when a judge compares every newline and character. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Stdout to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Exact standard output", | ||
| "concept": "`System.out.print` emits its argument, while `println` also terminates the line. This judge normalizes CRLF to LF; every other whitespace difference remains significant, including spaces and extra blank lines.", | ||
| "worked_example": "The example builds `Mina:4` from a `String` and an `int`, then includes one explicit newline in the text passed to `print`. No hidden conversion or extra label is added by the output API.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Stdout rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Stdout exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Stdout when a judge compares every newline and character." | ||
| "Using a space or hyphen where the required separator is a colon.", | ||
| "Calling `println` on text that already ends with a newline and producing a blank line." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Stdout before the `example:` output?", | ||
| "Which TODO line in the Stdout exercise must derive the value from its own data instead of copying the demo?" | ||
| "What bytes follow the digit when `print` receives a string ending in `\\n`?", | ||
| "Would replacing the example call with `println` preserve its exact output?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Stdout to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Parse the learner and score, compose one colon-separated line, and emit exactly one trailing newline.", | ||
| "objective": "Produce judge-clean stdout whose separator and line ending come from deliberate formatting choices.", | ||
| "language_delta": "Unlike languages with a built-in `print` function, Java routes console output through the `System.out` stream and distinguishes `print` from `println`.", | ||
| "prediction_prompt": "Before running, write down the complete character sequence from the example, including its final line break.", | ||
| "transfer_trap": "A visually identical terminal display can still fail if one version has a missing or doubled newline." | ||
| }, | ||
| "java-input": { | ||
| "title": "UTF-8 standard input", | ||
| "concept": "`System.in` supplies bytes, not already-decoded text. `readAllBytes` plus `StandardCharsets.UTF_8` makes decoding portable; classes outside `java.lang` need an import or a fully qualified name.", | ||
| "worked_example": "A `ByteArrayInputStream` stands in for stdin, its bytes are decoded explicitly, and two tokens are added. The example prints a labeled demonstration total rather than any exercise answer.", | ||
| "common_mistakes": [ | ||
| "Constructing a `String` from bytes with the platform-default charset.", | ||
| "Splitting blank input into a token and attempting to parse an empty integer." | ||
| ], | ||
| "self_check": [ | ||
| "Why is `StandardCharsets` imported while `String` is available without an import?", | ||
| "Which branch prevents the empty-input case from reaching `Integer.parseInt`?" | ||
| ], | ||
| "exercise_prompt": "Decode all stdin as UTF-8, sum each signed integer token, and leave the accumulator at zero when no token exists.", | ||
| "objective": "Decode stdin as UTF-8 and sum valid signed integer tokens separated by the default-regex whitespace accepted by `\\s+`, returning zero for blank input.", | ||
| "language_delta": "Some runtimes expose stdin as text by default; Java exposes an `InputStream`, so charset selection belongs in the program.", | ||
| "prediction_prompt": "Predict the total for tokens split across several lines, then predict the result for zero input bytes.", | ||
| "transfer_trap": "An import only shortens a source-level type name; it neither installs nor dynamically loads the referenced JDK class." | ||
| }, | ||
| "java-variables-types": { | ||
| "title": "Variables and types", | ||
| "concept": "Use Variables and types when primitive values and object references must be declared before use. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Variables and types to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Typed values and final bindings", | ||
| "concept": "Local declarations give each value a compile-time type. Primitives carry values, references identify objects, `final` prevents rebinding, and `static` state belongs to a class rather than one instance.", | ||
| "worked_example": "The demo keeps the name binding final, compares a mutable integer score with its threshold, and stores the comparison in a boolean before rendering the report.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Variables and types rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Variables and types exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Variables and types when primitive values and object references must be declared before use." | ||
| "Treating equality with the threshold as a failure when the rule says at least.", | ||
| "Assuming a final reference makes every reachable object deeply immutable." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Variables and types before the `example:` output?", | ||
| "Which TODO line in the Variables and types exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which declaration could be reassigned in the example, and which one could not?", | ||
| "What boolean results when score and threshold are both zero?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Variables and types to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Keep the parsed values in suitable declared types and derive the inclusive threshold report.", | ||
| "objective": "Connect `String`, `int`, and `boolean` values in one typed report without losing the boundary case.", | ||
| "language_delta": "Java's local types and final modifier are compile-time constraints; final resembles a fixed binding, not an immutable-object guarantee.", | ||
| "prediction_prompt": "Evaluate the comparison first for a score above the threshold and then for an exactly equal score.", | ||
| "transfer_trap": "Do not transfer JavaScript truthiness into Java: an integer cannot stand where a boolean condition is required." | ||
| }, | ||
| "java-numbers-operators": { | ||
| "title": "Numbers and operators", | ||
| "concept": "Use Numbers and operators when division and remainder must stay integer-based in many stdin problems. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Numbers and operators to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Truncating integer arithmetic", | ||
| "concept": "For two `int` operands, `/` produces an integer quotient truncated toward zero and `%` produces the corresponding remainder. Numeric parameter types also participate in compile-time overload selection.", | ||
| "worked_example": "Nineteen divided by four yields a quotient of four and remainder three. The labels make this demonstration output distinct from the compact exercise format.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Numbers and operators rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Numbers and operators exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Numbers and operators when division and remainder must stay integer-based in many stdin problems." | ||
| "Replacing `/` with `Math.floorDiv` and changing negative quotients.", | ||
| "Deriving the remainder with floor-mod rules instead of Java's `%` operator." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Numbers and operators before the `example:` output?", | ||
| "Which TODO line in the Numbers and operators exercise must derive the value from its own data instead of copying the demo?" | ||
| "For a negative dividend and positive divisor, which direction does the quotient move?", | ||
| "How can you verify `dividend == quotient * divisor + remainder` for each case?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Numbers and operators to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Parse two nonzero-divisor integers and print Java's quotient and remainder separated by a colon.", | ||
| "objective": "Compute signed integer division without importing floor-division behavior from another language.", | ||
| "language_delta": "Python's `//` floors toward negative infinity, whereas Java integer `/` truncates toward zero.", | ||
| "prediction_prompt": "Predict both fields for `-17 / 5` before checking the runtime result.", | ||
| "transfer_trap": "Changing either operand to floating point selects different arithmetic and no longer teaches integer truncation." | ||
| }, | ||
| "java-strings": { | ||
| "title": "Strings", | ||
| "concept": "Use Strings when text cleanup and substring extraction happen before comparison or output. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Strings to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "String cleanup and content equality", | ||
| "concept": "`strip` removes leading and trailing code points recognized by `Character.isWhitespace` and returns a stripped String without mutating the source. Indexing and length use UTF-16 code units, while `.equals` compares contents and `==` compares references.", | ||
| "worked_example": "The sample strips spaces from a curly-wrapped word, verifies its delimiters, and extracts the interior with `substring`. The original string remains unchanged.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Strings rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Strings exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Strings when text cleanup and substring extraction happen before comparison or output." | ||
| "Using `==` to decide whether two separately created strings contain the same text.", | ||
| "Removing the first and last code unit without first confirming a matching wrapper pair." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Strings before the `example:` output?", | ||
| "Which TODO line in the Strings exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does the substring end index equal `length() - 1` rather than `length()`?", | ||
| "What does UTF-16 length count when a character is represented by a surrogate pair?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Strings to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Strip the input and remove one surrounding `[]` or `{}` pair while preserving its Unicode interior.", | ||
| "objective": "Normalize wrapped text without mutating String data or relying on reference identity.", | ||
| "language_delta": "Java and JavaScript both index UTF-16 code units; Python string indices instead follow Unicode code points.", | ||
| "prediction_prompt": "Trace the stripped length and substring bounds for the bracketed Korean input.", | ||
| "transfer_trap": "Neither UTF-16 code units nor Unicode code points are user-perceived grapheme clusters." | ||
| }, | ||
| "java-control-flow": { | ||
| "title": "Control flow", | ||
| "concept": "Use Control flow when branches and loops decide which values enter an accumulator. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Control flow to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Branches and bounded loops", | ||
| "concept": "A `for` loop controls the inclusive numeric range and an `if` filters odd values. Java requires the branch expression to be boolean, so the remainder comparison must be explicit.", | ||
| "worked_example": "The counter advances from one through seven. Only values with a nonzero remainder after division by two enter the accumulator, producing a labeled odd sum.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Control flow rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Control flow exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Control flow when branches and loops decide which values enter an accumulator." | ||
| "Using `< limit` and silently dropping the upper bound when it is odd.", | ||
| "Writing an integer directly as an `if` condition as though Java supported numeric truthiness." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Control flow before the `example:` output?", | ||
| "Which TODO line in the Control flow exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which loop values contribute when the limit is six?", | ||
| "Why does the loop condition use `<=` rather than `<` for this task?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Control flow to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Traverse the inclusive range and accumulate only odd integers before printing the total.", | ||
| "objective": "Coordinate loop bounds, a parity branch, and an accumulator for zero and positive limits.", | ||
| "language_delta": "Java conditions must have boolean type, unlike languages that coerce zero and nonzero integers in branches.", | ||
| "prediction_prompt": "List every value visited for limit three, marking which ones change the total.", | ||
| "transfer_trap": "A range helper in another language may exclude its endpoint by default; this Java loop spells out the inclusive boundary." | ||
| }, | ||
| "java-methods": { | ||
| "title": "Methods", | ||
| "concept": "Use Methods when a reusable calculation needs a named boundary. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Methods to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-enum-switch": { | ||
| "title": "Exhaustive enum switch expressions", | ||
| "concept": "An enum defines a finite set of singleton constants. A switch expression can map every constant to a value with arrow rules and no accidental fall-through.", | ||
| "worked_example": "The example maps `STARTED` and `STOPPED` to separate actions, assigns the switch result to a String, and prefixes it only for demonstration.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Methods rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Methods exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Methods when a reusable calculation needs a named boundary." | ||
| "Adding a `default` arm that hides an unhandled enum constant after the enum evolves.", | ||
| "Expecting arrow-style cases to continue into the following case body." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Methods before the `example:` output?", | ||
| "Which TODO line in the Methods exercise must derive the value from its own data instead of copying the demo?" | ||
| "What compiler feedback appears if one enum constant lacks an arm and no default exists?", | ||
| "Does the switch expression execute more than one arrow rule for a single state?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Methods to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Correct the mapping for each task state while keeping the switch expression exhaustive.", | ||
| "objective": "Convert a closed enum state into text without mutable result plumbing or fall-through.", | ||
| "language_delta": "Java switch expressions yield values, unlike older statement-only switch forms that commonly needed assignment and `break`.", | ||
| "prediction_prompt": "Choose the exact arm entered by `BLOCKED` and decide whether any later arm can run.", | ||
| "transfer_trap": "Exhaustiveness over an enum does not make arbitrary strings safe; `valueOf` still rejects unknown names." | ||
| }, | ||
| "java-input": { | ||
| "title": "Input parsing", | ||
| "concept": "Use Input parsing when stdin arrives as bytes and must become typed tokens. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Input parsing to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-methods": { | ||
| "title": "Typed static method checkpoint", | ||
| "concept": "A method signature fixes parameter and return types, while Java passes each argument value into a new parameter variable. This checkpoint keeps the calculation behind one typed static method instead of duplicating it in main.", | ||
| "worked_example": "The calculation lives in `area(int, int)`, and main supplies separate demo dimensions. The return value crosses the method boundary before formatting.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Input parsing rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Input parsing exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Input parsing when stdin arrives as bytes and must become typed tokens." | ||
| "Repeating the multiplication in main instead of completing the named method.", | ||
| "Believing a method can replace the caller's reference variable merely because both variables refer to one object." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Input parsing before the `example:` output?", | ||
| "Which TODO line in the Input parsing exercise must derive the value from its own data instead of copying the demo?" | ||
| "What return type and two parameter types does `area` declare?", | ||
| "Why should main call `area` instead of repeating the multiplication expression?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Input parsing to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Finish the typed area method and keep stdin parsing and stdout formatting in main.", | ||
| "objective": "Demonstrate a reusable value-returning boundary rather than embedding all logic in the entry point.", | ||
| "language_delta": "Java checks the parameter and return types at compile time, and this static method can be called without constructing a Solution object.", | ||
| "prediction_prompt": "Trace the two parsed integers into parameters, through the return statement, and back to println.", | ||
| "transfer_trap": "Passing an object would copy its reference value, not give the method a way to replace the caller's variable." | ||
| }, | ||
| "java-arrays-collections": { | ||
| "title": "Arrays and collections", | ||
| "concept": "Use Arrays and collections when fixed arrays, growable List, keyed Map, and unique Set solve different container jobs. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Arrays and collections to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-overloading-varargs": { | ||
| "title": "Overloads and varargs arrays", | ||
| "concept": "Overloading gives one method name multiple signatures. The compiler selects an applicable signature, and an `int...` parameter is represented as an `int[]` inside its method body.", | ||
| "worked_example": "The varargs overload totals two scores, then delegates the computed integer to the fixed-arity formatter. That second call resolves to the non-varargs signature.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Arrays and collections rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Arrays and collections exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Arrays and collections when fixed arrays, growable List, keyed Map, and unique Set solve different container jobs." | ||
| "Returning the number of varargs elements when the task asks for their numeric sum.", | ||
| "Assuming overload dispatch waits until runtime to inspect the actual array contents." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Arrays and collections before the `example:` output?", | ||
| "Which TODO line in the Arrays and collections exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does `label(name, total)` choose the two-argument overload?", | ||
| "What array reaches the varargs method for a user followed by a single zero?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Arrays and collections to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Reduce the parsed score array and delegate its result to the formatting overload.", | ||
| "objective": "Observe both compile-time overload resolution and the array nature of variable arity.", | ||
| "language_delta": "Unlike JavaScript rest parameters, Java varargs participates in static applicability and overload ranking.", | ||
| "prediction_prompt": "Identify the selected overload at each of the two calls in the example before calculating output.", | ||
| "transfer_trap": "Adding a broad overload can make a call ambiguous even though each individual implementation looks valid." | ||
| }, | ||
| "java-classes-objects": { | ||
| "title": "Classes and objects", | ||
| "concept": "Use Classes and objects when objects need instance state plus behavior. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Classes and objects to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-packages-imports": { | ||
| "title": "Imports and fully qualified names", | ||
| "concept": "An import lets source code use a simple type name. The same compilation unit may refer to another type by its package-qualified name, as with imported `ArrayList` assigned to `java.util.List`.", | ||
| "worked_example": "The declared variable exposes the List interface while the constructor names ArrayList through an import. Joining the stored words proves the collection is actually used.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Classes and objects rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Classes and objects exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Classes and objects when objects need instance state plus behavior." | ||
| "Thinking an import downloads a library or executes initialization code.", | ||
| "Importing two types with the same simple name and expecting Java to guess which one is intended." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Classes and objects before the `example:` output?", | ||
| "Which TODO line in the Classes and objects exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which source spelling would work if the `ArrayList` import were removed?", | ||
| "Why can `String` be written without either an import or `java.lang.String`?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Classes and objects to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Populate the qualified List with the imported implementation and concatenate its tokens without separators.", | ||
| "objective": "Distinguish namespace convenience from type availability while observing one real collection.", | ||
| "language_delta": "Java imports types into a compilation unit's naming context; they are not Python module execution or JavaScript module loading.", | ||
| "prediction_prompt": "Resolve every simple and qualified type name in the example to its package before running it.", | ||
| "transfer_trap": "A wildcard import does not include subpackages and cannot resolve an otherwise ambiguous simple name." | ||
| }, | ||
| "java-constructors": { | ||
| "title": "Constructors", | ||
| "concept": "Use Constructors when new objects must start with valid field values. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Constructors to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-arrays-collections": { | ||
| "title": "Collection observations", | ||
| "concept": "Arrays have fixed length, while generic List, Map, and Set types expose different collection behaviors. Streams can derive aggregates from those views without changing the source collections.", | ||
| "worked_example": "Each array value enters a list, frequency map, and unique set. The sum is reconstructed from map keys times counts, so every printed field observes the advertised structure.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Constructors rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Constructors exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Constructors when new objects must start with valid field values." | ||
| "Printing the array length where the contract asks for a sum derived from Map entries.", | ||
| "Confusing list size, distinct set size, and number of map keys when duplicates appear." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Constructors before the `example:` output?", | ||
| "Which TODO line in the Constructors exercise must derive the value from its own data instead of copying the demo?" | ||
| "For `2 2 3`, what does each collection contain after the loop?", | ||
| "Which operation is terminal in the map-entry stream that computes the total?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Constructors to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Populate all three collection views and calculate the first report field from frequencies rather than the source array.", | ||
| "objective": "Explain three observable collection semantics through one duplicate-sensitive report.", | ||
| "language_delta": "Java arrays are reified fixed-size objects, while generic collection element types are checked statically and largely erased at runtime.", | ||
| "prediction_prompt": "Draw the List, Map, and Set states for repeated input before computing the three output fields.", | ||
| "transfer_trap": "A stream pipeline is lazy until its terminal operation, and a consumed stream cannot be reused." | ||
| }, | ||
| "java-encapsulation": { | ||
| "title": "Encapsulation", | ||
| "concept": "Use Encapsulation when callers should update state through methods instead of touching fields. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Encapsulation to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-generics": { | ||
| "title": "Generic element preservation", | ||
| "concept": "A type parameter links input and output types: `<T> T last(List<T>)` returns the same element type accepted by the list. Raw types discard that useful check.", | ||
| "worked_example": "Type inference chooses String for the list of colors. The helper selects the last position and returns a String without an explicit cast.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Encapsulation rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Encapsulation exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Encapsulation when callers should update state through methods instead of touching fields." | ||
| "Using raw `List` and moving a preventable type error to runtime.", | ||
| "Indexing with zero and accidentally returning the first element for multi-item input." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Encapsulation before the `example:` output?", | ||
| "Which TODO line in the Encapsulation exercise must derive the value from its own data instead of copying the demo?" | ||
| "What does T become for `List<Integer>` passed to the same helper?", | ||
| "Which precondition is necessary before subtracting one from `size()`?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Encapsulation to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Complete the reusable last-element selection while preserving the list's element type.", | ||
| "objective": "Use one type variable to express a relationship that callers can rely on without casts.", | ||
| "language_delta": "Java generics normally use erasure, unlike reified arrays, yet compile-time type relationships still prevent many invalid calls.", | ||
| "prediction_prompt": "Infer the method's T and the selected index for each provided word list.", | ||
| "transfer_trap": "Erasure does not make raw collections safe; it merely removes much generic representation from runtime." | ||
| }, | ||
| "java-static-members": { | ||
| "title": "Static members", | ||
| "concept": "Use Static members when a value belongs to the class rather than one instance. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Static members to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-streams-lambdas": { | ||
| "title": "Lazy stream pipelines", | ||
| "concept": "A stream describes element processing until a terminal operation requests results. Here an `IntStream` uses a filtering lambda and `IntStream.map` for squaring; `sum` performs the traversal once.", | ||
| "worked_example": "`IntStream.of` creates two demo values, the predicate retains both evens, and the mapping squares them before the terminal total is labeled.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Static members rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Static members exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Static members when a value belongs to the class rather than one instance." | ||
| "Building a filter and map pipeline but never invoking a terminal operation.", | ||
| "Trying to consume the same Stream object again after `sum` has closed its traversal." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Static members before the `example:` output?", | ||
| "Which TODO line in the Static members exercise must derive the value from its own data instead of copying the demo?" | ||
| "At what point are the example's predicate and mapping actually evaluated?", | ||
| "Why does zero survive the even filter yet contribute nothing to the squared total?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Static members to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Keep even integers, square them, and produce one terminal sum from the parsed sequence.", | ||
| "objective": "Connect lazy intermediate operations to an observable numeric result.", | ||
| "language_delta": "Java streams are single-use traversal pipelines, not reusable collection containers or JavaScript arrays.", | ||
| "prediction_prompt": "Write the sequence after filtering and again after mapping for the mixed negative case.", | ||
| "transfer_trap": "A stream's encounter order and laziness do not imply parallel execution; parallelism is a separate choice." | ||
| }, | ||
| "java-enum-switch": { | ||
| "title": "Enum and switch", | ||
| "concept": "Use Enum and switch when a fixed set of states must map to explicit results. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Enum and switch to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-comparators-sorting": { | ||
| "title": "Comparator chains", | ||
| "concept": "`comparingInt`, `reversed`, and `thenComparing` state each ordering rule directly. This avoids subtraction, which can overflow for extreme integers.", | ||
| "worked_example": "Kai and Bea tie on score, so the secondary ascending name comparison places Bea first. The demo reads that first record after sorting.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Enum and switch rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Enum and switch exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Enum and switch when a fixed set of states must map to explicit results." | ||
| "Reversing the entire chained comparator and unintentionally reversing the name tie-break too.", | ||
| "Returning `right.score() - left.score()` and violating ordering through integer overflow." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Enum and switch before the `example:` output?", | ||
| "Which TODO line in the Enum and switch exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which comparator stage decides between two equal scores?", | ||
| "Why does the single negative-score user remain the winner in its one-element list?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Enum and switch to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Compose descending score order with ascending name order, then print the first user.", | ||
| "objective": "Encode a stable business ordering as readable comparator composition.", | ||
| "language_delta": "Java Comparator objects make ordering explicit and reusable; operators such as `<` cannot directly sort arbitrary records.", | ||
| "prediction_prompt": "Order the three-user case by hand, applying only one comparator stage at a time.", | ||
| "transfer_trap": "`reversed()` applies to the comparator built so far, so call placement changes tie behavior." | ||
| }, | ||
| "java-exceptions": { | ||
| "title": "Exceptions", | ||
| "concept": "Use Exceptions when bad input can be recovered without hiding unrelated failures. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Exceptions to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-classes-objects": { | ||
| "title": "Per-instance object state", | ||
| "concept": "Each Counter object owns a separate `value` field. The `add` method receives a local `delta` parameter and must update the receiver's field through `this` or an unambiguous field name.", | ||
| "worked_example": "One Counter starts at ten and receives a delta through `add`. The method changes `this` instance's field, and main observes twelve afterward.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Exceptions rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Exceptions exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Exceptions when bad input can be recovered without hiding unrelated failures." | ||
| "Changing only the parameter variable and expecting the object's field to follow.", | ||
| "Making all state static, causing otherwise independent instances to share one value." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Exceptions before the `example:` output?", | ||
| "Which TODO line in the Exceptions exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which storage location changes inside `add`, and which local variable merely carries an argument?", | ||
| "What would happen if two Counter instances started with different values?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Exceptions to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Send the parsed delta to a Counter method that updates the receiver's own state.", | ||
| "objective": "Observe identity and encapsulated mutation through a minimal class instance.", | ||
| "language_delta": "Java instance fields live with each object, while a method parameter is a local variable created for that call.", | ||
| "prediction_prompt": "Track the Counter field separately from the `delta` parameter before and after the method call.", | ||
| "transfer_trap": "Private access prevents outside reads and writes, but only method logic can protect an invariant." | ||
| }, | ||
| "java-generics": { | ||
| "title": "Generics", | ||
| "concept": "Use Generics when one method must keep the caller's element type. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Generics to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-constructors": { | ||
| "title": "Constructor-established invariants", | ||
| "concept": "A constructor has the class name and no return type. It must assign every blank final instance field before normal completion, often using `this.field` to disambiguate parameters.", | ||
| "worked_example": "The Rectangle constructor transfers two arguments into final dimensions. Its area method can then assume both fields were established.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Generics rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Generics exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Generics when one method must keep the caller's element type." | ||
| "Writing `void Rectangle(...)`, which declares a method instead of a constructor.", | ||
| "Assigning a parameter to itself and leaving the similarly named field uninitialized." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Generics before the `example:` output?", | ||
| "Which TODO line in the Generics exercise must derive the value from its own data instead of copying the demo?" | ||
| "What does the left side of `this.width = width` identify?", | ||
| "Why does the compiler reject a constructor path that omits a final field assignment?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Generics to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Initialize both dimensions from the constructor arguments so area can use valid final fields.", | ||
| "objective": "Establish object state once at construction without placeholder dimensions.", | ||
| "language_delta": "Java constructors are special declarations, not ordinary value-returning initializer functions.", | ||
| "prediction_prompt": "Resolve each same-named field and parameter in the two assignments before calculating area.", | ||
| "transfer_trap": "Final fields prevent reassignment after construction but do not recursively freeze referenced values." | ||
| }, | ||
| "java-interfaces": { | ||
| "title": "Interfaces", | ||
| "concept": "Use Interfaces when callers should depend on behavior rather than a concrete class. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Interfaces to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-encapsulation": { | ||
| "title": "Private state with enforced rules", | ||
| "concept": "A private field hides representation from unrelated code. The exposed operation must still validate requested changes; visibility and invariants solve different problems.", | ||
| "worked_example": "The Account accepts two positive deposits and ignores the negative request. Its accessor exposes only the resulting integer balance.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Interfaces rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Interfaces exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Interfaces when callers should depend on behavior rather than a concrete class." | ||
| "Assuming `private` automatically rejects invalid values passed through a method.", | ||
| "Returning a mutable internal collection directly and bypassing the intended boundary." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Interfaces before the `example:` output?", | ||
| "Which TODO line in the Interfaces exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which call in the example leaves the balance unchanged?", | ||
| "Could code inside Account still assign an invalid value to its private field?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Interfaces to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Place the positivity rule inside deposit and report the state only through its accessor.", | ||
| "objective": "Enforce a small invariant at the method that owns the state transition.", | ||
| "language_delta": "Java access modifiers are compile-time and runtime member-access rules, not automatic domain validation.", | ||
| "prediction_prompt": "Apply each parsed delta in order and record whether the guard accepts it.", | ||
| "transfer_trap": "A getter can leak encapsulation if it returns a mutable internal object rather than a safe view or copy." | ||
| }, | ||
| "java-inheritance-composition": { | ||
| "title": "Inheritance and composition", | ||
| "concept": "Use Inheritance and composition when a subclass also needs a helper object to finish behavior. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Inheritance and composition to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-static-members": { | ||
| "title": "Class-owned static constants", | ||
| "concept": "A static field belongs to the class and is shared by calls and instances. `static final` fixes its binding, although an object reached through such a binding could remain mutable.", | ||
| "worked_example": "The scaling method reads `FACTOR` through class context and multiplies a demo value. No instance is needed to access either static member.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Inheritance and composition rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Inheritance and composition exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Inheritance and composition when a subclass also needs a helper object to finish behavior." | ||
| "Creating an object solely to call a static utility and obscuring class ownership.", | ||
| "Reading `final` as a promise of deep immutability for every possible field type." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Inheritance and composition before the `example:` output?", | ||
| "Which TODO line in the Inheritance and composition exercise must derive the value from its own data instead of copying the demo?" | ||
| "How many FACTOR storage locations exist if ten Solution objects are created?", | ||
| "Can the scale method read an instance field without receiving or creating an instance?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Inheritance and composition to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Correct the shared factor so the static scaling method handles positive, zero, and negative input.", | ||
| "objective": "Use class-level state deliberately and distinguish it from per-object fields.", | ||
| "language_delta": "Java's `static` attaches a member to a declared class, unlike a module-level binding that has no containing type.", | ||
| "prediction_prompt": "Substitute the class factor into the multiplication for each signed input.", | ||
| "transfer_trap": "A static mutable field introduces shared state across every instance and often needs synchronization." | ||
| }, | ||
| "java-records": { | ||
| "title": "Records", | ||
| "concept": "Use Records when immutable data carriers need less boilerplate. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Records to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-interfaces": { | ||
| "title": "Behavior through an interface", | ||
| "concept": "An interface names a public behavioral contract. Code accepting `Named` can call `name()` without knowing whether the implementation is a record, class, or another permitted type.", | ||
| "worked_example": "A User record implements Named automatically through its accessor. The renderer consumes only the interface and performs locale-stable uppercasing.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Records rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Records exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Records when immutable data carriers need less boilerplate." | ||
| "Reducing an implementing method's visibility even though interface methods are public.", | ||
| "Typing the renderer as User and needlessly coupling it to one implementation." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Records before the `example:` output?", | ||
| "Which TODO line in the Records exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which method is available through a variable declared as Named?", | ||
| "Could a different class participate without extending User?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Records to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Implement the uppercase transformation through the interface-facing helper.", | ||
| "objective": "Call shared behavior against a contract rather than a concrete representation.", | ||
| "language_delta": "Java interfaces are nominal: a class must declare implementation, unlike TypeScript's structural compatibility.", | ||
| "prediction_prompt": "Follow the record accessor through the Named reference before applying `Locale.ROOT` case conversion.", | ||
| "transfer_trap": "Default interface methods can provide behavior, but they do not turn interface fields into per-instance state." | ||
| }, | ||
| "java-optional": { | ||
| "title": "Optional", | ||
| "concept": "Use Optional when a missing value must be handled explicitly. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Optional to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-inheritance-composition": { | ||
| "title": "Inheritance plus composition", | ||
| "concept": "Inheritance models a substitutable is-a relationship and composition models a has-a relationship. The Player reuses base score state while owning a separate Bonus value.", | ||
| "worked_example": "Construction sends the base argument to `super` and creates the composed record for the bonus. `total` reads both sources to produce twelve.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Optional rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Optional exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Optional when a missing value must be handled explicitly." | ||
| "Ignoring the composed field and returning inherited state alone.", | ||
| "Using inheritance only to reuse code when the subtype cannot honor the base type's expectations." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Optional before the `example:` output?", | ||
| "Which TODO line in the Optional exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which constructor initializes the protected base field?", | ||
| "Why is Bonus held as a field rather than used as Player's superclass?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Optional to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Combine the inherited base value with the composed bonus in the total method.", | ||
| "objective": "Separate two reuse mechanisms according to their semantic relationships.", | ||
| "language_delta": "Java permits one class superclass but multiple implemented interfaces, making composition especially useful for independent parts.", | ||
| "prediction_prompt": "Trace the two constructor arguments into their different objects before adding the final total.", | ||
| "transfer_trap": "Protected visibility allows selected access; it does not make an arbitrary has-a relationship into a sound subtype." | ||
| }, | ||
| "java-streams-lambdas": { | ||
| "title": "Streams and lambdas", | ||
| "concept": "Use Streams and lambdas when collection processing is clearer as a pipeline. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Streams and lambdas to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-exceptions": { | ||
| "title": "Narrow exception recovery", | ||
| "concept": "`Integer.parseInt` reports malformed integer syntax with unchecked `NumberFormatException`. Catch that precise failure at the parsing boundary so unrelated defects are not mistaken for invalid input.", | ||
| "worked_example": "Parsing `oops` enters the narrow catch and returns the caller's fallback. Other unexpected runtime defects would not be swallowed by this helper.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Streams and lambdas rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Streams and lambdas exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Streams and lambdas when collection processing is clearer as a pipeline." | ||
| "Catching `Exception` and hiding unrelated programming errors.", | ||
| "Expecting parseInt to return an Optional or sentinel instead of throwing for malformed text." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Streams and lambdas before the `example:` output?", | ||
| "Which TODO line in the Streams and lambdas exercise must derive the value from its own data instead of copying the demo?" | ||
| "Does a valid negative integer enter the catch block?", | ||
| "Why is catching `NumberFormatException` safer here than catching every Exception?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Streams and lambdas to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Return a successful parsed value or the supplied fallback only when numeric syntax is invalid.", | ||
| "objective": "Recover at the smallest relevant exception boundary without masking other faults.", | ||
| "language_delta": "Java distinguishes checked exceptions that must be handled or declared from unchecked RuntimeException subclasses such as NumberFormatException.", | ||
| "prediction_prompt": "Choose the try or catch return path for each candidate token before evaluating its fallback.", | ||
| "transfer_trap": "A valid negative integer is data, not an error; only malformed numeric syntax selects this fallback path." | ||
| }, | ||
| "java-comparators-sorting": { | ||
| "title": "Comparators and sorting", | ||
| "concept": "Use Comparators and sorting when objects need ordering separate from their data shape. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Comparators and sorting to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-optional": { | ||
| "title": "Optional filtering and fallback", | ||
| "concept": "Optional represents a return value that may be absent. `filter` retains a present value only when its predicate succeeds, and `orElse` supplies a safe fallback without `get()`.", | ||
| "worked_example": "The present three-character word `cat` satisfies the at-most-three predicate, so the fallback is not selected. A label distinguishes the demonstration line.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Comparators and sorting rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Comparators and sorting exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Comparators and sorting when objects need ordering separate from their data shape." | ||
| "Calling `get()` without proving presence and risking `NoSuchElementException`.", | ||
| "Using Optional fields and parameters everywhere instead of mainly as an absence-aware return type." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Comparators and sorting before the `example:` output?", | ||
| "Which TODO line in the Comparators and sorting exercise must derive the value from its own data instead of copying the demo?" | ||
| "Does `filter` invoke its predicate when the Optional is already empty?", | ||
| "Which path produces `missing` for the dash sentinel?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Comparators and sorting to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Construct possible presence, retain only a token of at most three characters, and choose the fallback declaratively.", | ||
| "objective": "Separate absent input and overlong text from accepted present text of at most three characters without unsafe extraction.", | ||
| "language_delta": "Optional is an explicit container, unlike nullable references that carry no method-enforced handling path.", | ||
| "prediction_prompt": "For each of the four cases, mark the Optional as present or empty after construction and again after filtering.", | ||
| "transfer_trap": "Optional does not replace exceptions for failures that need diagnostic information." | ||
| }, | ||
| "java-try-with-resources": { | ||
| "title": "Try-with-resources", | ||
| "concept": "Use Try-with-resources when resources must close even when code exits early. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Try-with-resources to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Observed try-with-resources", | ||
| "concept": "A try-with-resources declaration closes its AutoCloseable value on normal and abrupt exit. Reading from the declared resource makes lifetime management observable rather than decorative.", | ||
| "worked_example": "The managed ByteArrayInputStream contains demo bytes. Its contents are read inside the block, decoded with UTF-8, stripped, and uppercased before automatic close.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Try-with-resources rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Try-with-resources exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Try-with-resources when resources must close even when code exits early." | ||
| "Declaring a resource but continuing to read from a separate untracked stream.", | ||
| "Forgetting that a close failure may be attached as suppressed when the body already threw." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Try-with-resources before the `example:` output?", | ||
| "Which TODO line in the Try-with-resources exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which object is guaranteed to receive `close()` after the example block?", | ||
| "What text should the empty byte sequence become before printing?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Try-with-resources to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Normalize the bytes actually read from the managed stream and give empty content its named form.", | ||
| "objective": "Tie deterministic resource lifetime to the data path whose result reaches stdout.", | ||
| "language_delta": "Java's statement encodes RAII-like cleanup in control flow even though general object lifetimes are garbage-collected.", | ||
| "prediction_prompt": "Trace input bytes through resource construction, reading, decoding, stripping, and case conversion.", | ||
| "transfer_trap": "Garbage collection is not a timely substitute for closing files, sockets, or other external resources." | ||
| }, | ||
| "java-packages-imports": { | ||
| "title": "Packages and imports", | ||
| "concept": "Use Packages and imports when single-file exercises still need classes from JDK packages. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Packages and imports to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-equality-hashcode": { | ||
| "title": "Equality and hash consistency", | ||
| "concept": "Logical equality must be reflexive, symmetric, transitive, consistent, and null-safe. Whenever two objects are equal, their hash codes must also match for hash collections to work.", | ||
| "worked_example": "A record supplies component-based equals and hashCode automatically, so two identical Points collapse to one HashSet entry.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Packages and imports rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Packages and imports exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Packages and imports when single-file exercises still need classes from JDK packages." | ||
| "Comparing only one coordinate and declaring distinct points equal.", | ||
| "Overriding equals while leaving a hashCode implementation that can differ for equal objects." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Packages and imports before the `example:` output?", | ||
| "Which TODO line in the Packages and imports exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why may unequal objects still share a hash code even though equal objects may not differ?", | ||
| "How does pattern `instanceof Point other` combine the type test and cast?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Packages and imports to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Make both Point coordinates participate in equals while preserving the equal-implies-same-hash rule, then observe HashSet size.", | ||
| "objective": "Implement a value contract that remains correct inside hashed collections.", | ||
| "language_delta": "Java's default Object equality is identity-based; records opt into component value semantics automatically.", | ||
| "prediction_prompt": "Classify each point pair as equal or unequal before deciding whether HashSet adds a second entry.", | ||
| "transfer_trap": "A good hash distribution improves performance, but the equal-implies-same-hash rule is the correctness requirement." | ||
| }, | ||
| "java-annotations": { | ||
| "title": "Annotations", | ||
| "concept": "Use Annotations when metadata belongs on declarations rather than in runtime logic. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Annotations to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-records": { | ||
| "title": "Defensive record snapshot checkpoint", | ||
| "concept": "Records make component references final and generate value members, but they are only shallowly immutable. `List.copyOf` in a compact constructor detaches the component from a mutable source and rejects null elements.", | ||
| "worked_example": "The compact constructor copies `[4, 5]`; adding another value to the original list cannot change the record's stored list, whose sum remains nine.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Annotations rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Annotations exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Annotations when metadata belongs on declarations rather than in runtime logic." | ||
| "Calling a record deeply immutable while storing a caller-owned mutable List directly.", | ||
| "Breaking equals and hash consistency by adding unrelated mutable state to value semantics." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Annotations before the `example:` output?", | ||
| "Which TODO line in the Annotations exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which reference is reassigned to the copy inside the compact constructor?", | ||
| "Why does removing an item from the source affect a record component that retained the same mutable list?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Annotations to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Defensively copy the list component before the source's final item is removed, then sum the retained snapshot.", | ||
| "objective": "Prove shallow record immutability and repair its aliasing boundary with an immutable copy.", | ||
| "language_delta": "A Java record is concise nominal data, not a recursively frozen object graph or a TypeScript structural type.", | ||
| "prediction_prompt": "Draw the source and component references before and after `List.copyOf`, then remove the source's final item.", | ||
| "transfer_trap": "Final record components prevent reassignment; they do not prevent mutation of a mutable object stored without defensive copying." | ||
| }, | ||
| "java-sealed-classes": { | ||
| "title": "Sealed classes", | ||
| "concept": "Use Sealed classes when a domain hierarchy should list its permitted implementations. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Sealed classes to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-annotations": { | ||
| "title": "Runtime annotation metadata", | ||
| "concept": "`@Target(METHOD)` restricts placement and `@Retention(RUNTIME)` preserves metadata for reflection. Without runtime retention, `getAnnotation` cannot observe the declared prefix.", | ||
| "worked_example": "Reflection locates the normalize method and reads its `trace` Label. The normal method call transforms demo text, and the reflected metadata supplies the prefix.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Sealed classes rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Sealed classes exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Sealed classes when a domain hierarchy should list its permitted implementations." | ||
| "Relying on the default CLASS retention when runtime reflection is required.", | ||
| "Searching a different method signature and receiving no matching reflective Method." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Sealed classes before the `example:` output?", | ||
| "Which TODO line in the Sealed classes exercise must derive the value from its own data instead of copying the demo?" | ||
| "At which retention policy does the VM expose annotations to reflection?", | ||
| "What target prevents Label from being placed on a field?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Sealed classes to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Change the annotation declaration so its method metadata survives into the running program.", | ||
| "objective": "Observe an annotation value through reflection rather than treating metadata as unused decoration.", | ||
| "language_delta": "Java annotations require explicit retention for runtime use; many type-system annotations need only compile-time visibility.", | ||
| "prediction_prompt": "Predict whether `getAnnotation` returns an object or null before and after adding runtime retention.", | ||
| "transfer_trap": "Retention controls availability, not behavior: reflection code must still read and apply the annotation." | ||
| }, | ||
| "java-testing-assert": { | ||
| "title": "Testing and assert", | ||
| "concept": "Use Testing and assert when small checks should fail near the broken method. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Testing and assert to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-sealed-classes": { | ||
| "title": "Sealed shapes and pattern switch", | ||
| "concept": "A sealed interface restricts direct implementors to its permits list. Java 21 pattern switch can deconstruct each record subtype and yield a value without a catch-all default.", | ||
| "worked_example": "The Rect pattern binds width and height directly, and the exhaustive switch computes its metric. Circle and Dot remain visibly handled alternatives.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Testing and assert rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Testing and assert exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Testing and assert when small checks should fail near the broken method." | ||
| "Using virtual methods only and never demonstrating that the hierarchy is closed to other direct subtypes.", | ||
| "Adding a default arm that conceals a newly permitted shape from compiler exhaustiveness feedback." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Testing and assert before the `example:` output?", | ||
| "Which TODO line in the Testing and assert exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which permitted record's components are multiplied in its pattern arm?", | ||
| "Why can the switch omit default and still compile under Java 21?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Testing and assert to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Correct the Circle record-pattern arm while preserving the exhaustive sealed-hierarchy switch.", | ||
| "objective": "Use closed subtype knowledge and record deconstruction in one observable expression.", | ||
| "language_delta": "Sealed types provide nominal hierarchy closure, unlike an unrestricted base class or a structurally inferred union.", | ||
| "prediction_prompt": "Select the runtime pattern for each shape token and bind its component variables before calculating.", | ||
| "transfer_trap": "Sealing controls direct extension according to permits rules; it does not automatically make instances immutable." | ||
| }, | ||
| "java-equality-hashcode": { | ||
| "title": "equals and hashCode", | ||
| "concept": "Use equals and hashCode when HashSet must recognize equal value objects. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying equals and hashCode to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "java-testing-assert": { | ||
| "title": "Always-active capstone checks", | ||
| "concept": "This capstone combines UTF-8 parsing, methods, records, generic collections, streams, comparator chains, Optional fallback behavior, and explicit failure checks. Java `assert` statements are normally disabled unless the VM starts with `-ea`, so correctness checks that must always run use an ordinary method throwing AssertionError.", | ||
| "worked_example": "Two equal-score records are ordered by name, the helper verifies its empty fallback with `check`, and a labeled winner is printed. Imports, record accessors, stream termination, and explicit ordering all contribute.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the equals and hashCode rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the equals and hashCode exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use equals and hashCode when HashSet must recognize equal value objects." | ||
| "Using Java `assert` for a mandatory validation while forgetting that the judge does not enable assertions.", | ||
| "Sorting scores alone and letting encounter order decide equal-score winners." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies equals and hashCode before the `example:` output?", | ||
| "Which TODO line in the equals and hashCode exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which capstone rules remain active even when the JVM lacks `-ea`?", | ||
| "How do enum and sealed closure, overloads and varargs, imports, generics, streams, comparators, constructors, encapsulation, static state, interfaces, inheritance and composition, Optional, managed resources, equality, and annotation retention guide extensions of this program?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying equals and hashCode to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| }, | ||
| "java-overloading-varargs": { | ||
| "title": "Overloading and varargs", | ||
| "concept": "Use Overloading and varargs when method calls can choose among signatures and collect extra arguments. Keep the value path visible in one Solution.java file: setup, the Java construct, then stdout.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Overloading and varargs to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Overloading and varargs rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Overloading and varargs exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Overloading and varargs when method calls can choose among signatures and collect extra arguments." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Overloading and varargs before the `example:` output?", | ||
| "Which TODO line in the Overloading and varargs exercise must derive the value from its own data instead of copying the demo?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Overloading and varargs to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Complete deterministic best-user selection and retain the explicit empty-result check before emitting the winner.", | ||
| "objective": "Integrate the core path in a testable record-and-collection program and connect each of the sixteen lab rules to an honest extension decision.", | ||
| "language_delta": "Unlike Python's optimization-removable assert and Java's disabled-by-default assert, an explicit method call always evaluates under normal execution.", | ||
| "prediction_prompt": "Order the tied users by both comparator stages, then separately trace the empty list through its fallback and check.", | ||
| "transfer_trap": "Assertions are for internal assumptions, not validating untrusted input; mandatory checks need ordinary control flow and clear failures." | ||
| } | ||
| } | ||
| } |
+363
-279
@@ -7,422 +7,506 @@ { | ||
| "java-output": { | ||
| "title": "Salida estándar", | ||
| "concept": "Salida estándar importa cuando el juez compara cada carácter y salto de línea. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Salida estándar. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Salida estándar exacta", | ||
| "concept": "`System.out.print` emite su argumento y `println` además termina la línea. El evaluador normaliza CRLF a LF; cualquier otra diferencia de espacios o líneas vacías sigue siendo significativa.", | ||
| "worked_example": "El ejemplo construye `Mina:4` con un `String` y un `int`, e incluye un `\n` explícito en el texto entregado a `print`. La API no añade etiquetas ni conversiones ocultas.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Salida estándar en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Salida estándar.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Salida estándar importa cuando el juez compara cada carácter y salto de línea." | ||
| "Usar un espacio o guion cuando el separador requerido son dos puntos.", | ||
| "Llamar a `println` con una cadena que ya termina en nueva línea y producir una línea vacía adicional." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Salida estándar antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Salida estándar debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué sigue al dígito cuando `print` recibe una cadena terminada en `\n`?", | ||
| "¿Conservaría la salida exacta sustituir esa llamada por `println`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Salida estándar a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Analiza el nombre y la puntuación, compón una línea separada por dos puntos y emite exactamente una terminación de línea.", | ||
| "objective": "Producir una salida limpia cuyo separador y final procedan de decisiones de formato explícitas.", | ||
| "language_delta": "Java dirige la consola a través del flujo `System.out` y distingue `print` de `println`, en vez de ofrecer una función global.", | ||
| "prediction_prompt": "Escribe la secuencia completa de caracteres del ejemplo, incluida la nueva línea final.", | ||
| "transfer_trap": "Dos resultados visualmente iguales pueden diferir por un salto ausente o duplicado; solo CRLF y LF se normalizan aquí." | ||
| }, | ||
| "java-input": { | ||
| "title": "Entrada estándar en UTF-8", | ||
| "concept": "`System.in` proporciona bytes, no texto ya decodificado. `readAllBytes` con `StandardCharsets.UTF_8` hace explícito el juego de caracteres; los tipos fuera de `java.lang` requieren importación o nombre completo.", | ||
| "worked_example": "Un `ByteArrayInputStream` representa la entrada, sus bytes se decodifican de forma explícita y se suman dos tokens. La línea de demostración queda etiquetada y no coincide con los casos.", | ||
| "common_mistakes": [ | ||
| "Construir un `String` con el juego de caracteres predeterminado de la plataforma.", | ||
| "Dividir una entrada vacía e intentar convertir un token que también está vacío." | ||
| ], | ||
| "self_check": [ | ||
| "¿Por qué se importa `StandardCharsets` mientras `String` no necesita importación?", | ||
| "¿Qué rama impide que la entrada vacía alcance `Integer.parseInt`?" | ||
| ], | ||
| "exercise_prompt": "Decodifica toda la entrada como UTF-8, suma cada token entero con signo y conserva cero cuando no exista ninguno.", | ||
| "objective": "Sumar enteros válidos separados por los espacios en blanco reconocidos por la expresión `\\s+`, devolviendo cero para texto en blanco.", | ||
| "language_delta": "Otros entornos entregan texto directamente; Java expone un `InputStream`, por lo que elegir la codificación es responsabilidad del programa.", | ||
| "prediction_prompt": "Predice primero la suma de tokens en varias líneas y después el resultado de recibir cero bytes.", | ||
| "transfer_trap": "Una importación solo acorta un nombre de tipo en el código; no instala ni carga dinámicamente la clase del JDK." | ||
| }, | ||
| "java-variables-types": { | ||
| "title": "Variables y tipos", | ||
| "concept": "Variables y tipos importa cuando los valores primitivos y las referencias a objetos deben declararse antes de usarse. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Variables y tipos. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Valores tipados y enlaces `final`", | ||
| "concept": "Cada declaración local fija un tipo de compilación. Los primitivos contienen valores, las referencias señalan objetos, `final` impide reasignar y el estado `static` pertenece a la clase.", | ||
| "worked_example": "La muestra mantiene final el nombre, compara una puntuación mutable con su umbral y guarda el resultado en un booleano antes de formar el informe.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Variables y tipos en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Variables y tipos.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Variables y tipos importa cuando los valores primitivos y las referencias a objetos deben declararse antes de usarse." | ||
| "Considerar fallida una puntuación igual al umbral aunque la regla diga como mínimo.", | ||
| "Suponer que una referencia `final` vuelve profundamente inmutable todo objeto alcanzable." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Variables y tipos antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Variables y tipos debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué declaración de la muestra puede reasignarse y cuál no?", | ||
| "¿Qué booleano resulta si puntuación y umbral valen cero?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Variables y tipos a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Conserva los valores analizados en tipos adecuados y deriva el informe usando una comparación inclusiva con el umbral.", | ||
| "objective": "Conectar `String`, `int` y `boolean` sin perder el caso exacto de frontera.", | ||
| "language_delta": "Los tipos locales y `final` se comprueban al compilar; un enlace fijo no garantiza que el objeto referenciado sea inmutable.", | ||
| "prediction_prompt": "Evalúa la comparación con una puntuación superior y luego con otra exactamente igual al umbral.", | ||
| "transfer_trap": "Java no aplica la verdad implícita de JavaScript: un entero no puede ocupar directamente una condición booleana." | ||
| }, | ||
| "java-numbers-operators": { | ||
| "title": "Números y operadores", | ||
| "concept": "Números y operadores importa cuando la división y el resto deben mantenerse enteros en muchos problemas. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Números y operadores. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Aritmética entera truncada", | ||
| "concept": "Con dos operandos `int`, `/` genera un cociente truncado hacia cero y `%` el resto correspondiente. Los tipos numéricos de los parámetros también intervienen en la elección de sobrecarga.", | ||
| "worked_example": "Diecinueve dividido entre cuatro produce cociente cuatro y resto tres. Las etiquetas diferencian esa salida de demostración del formato compacto evaluado.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Números y operadores en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Números y operadores.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Números y operadores importa cuando la división y el resto deben mantenerse enteros en muchos problemas." | ||
| "Sustituir `/` por `Math.floorDiv` y cambiar los cocientes negativos.", | ||
| "Calcular el resto con reglas de módulo inferior en vez del operador `%` de Java." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Números y operadores antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Números y operadores debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "Con dividendo negativo y divisor positivo, ¿hacia qué dirección se trunca?", | ||
| "¿Cómo verificas `dividend == quotient * divisor + remainder` en cada caso?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Números y operadores a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Analiza dos enteros con divisor no nulo e imprime el cociente y resto de Java separados por dos puntos.", | ||
| "objective": "Calcular división entera con signo sin importar el redondeo inferior de otro lenguaje.", | ||
| "language_delta": "`//` de Python redondea hacia menos infinito; `/` entre enteros Java trunca hacia cero.", | ||
| "prediction_prompt": "Predice ambos campos de `-17 / 5` y `-17 % 5` antes de ejecutar.", | ||
| "transfer_trap": "Cambiar un operando a punto flotante selecciona otra aritmética y deja de demostrar truncamiento entero." | ||
| }, | ||
| "java-strings": { | ||
| "title": "Cadenas", | ||
| "concept": "Cadenas importa cuando la limpieza de texto y substring ocurren antes de comparar o imprimir. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Cadenas. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Limpieza e igualdad de cadenas", | ||
| "concept": "`strip` elimina de los bordes los puntos de código reconocidos por `Character.isWhitespace` y devuelve otro `String`. Los índices cuentan unidades UTF-16; `.equals` compara contenido y `==`, referencias.", | ||
| "worked_example": "La muestra recorta espacios de una palabra entre llaves, comprueba los delimitadores y extrae el interior con `substring`. La cadena original permanece intacta.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Cadenas en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Cadenas.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Cadenas importa cuando la limpieza de texto y substring ocurren antes de comparar o imprimir." | ||
| "Usar `==` para decidir si dos cadenas creadas por separado contienen el mismo texto.", | ||
| "Quitar la primera y última unidad sin comprobar antes que formen una pareja válida de delimitadores." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Cadenas antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Cadenas debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué el índice final de `substring` es `length() - 1`?", | ||
| "¿Qué cuenta `length()` cuando un símbolo usa un par sustituto UTF-16?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Cadenas a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Aplica `strip` y elimina una pareja exterior `[]` o `{}` sin alterar el contenido Unicode interno.", | ||
| "objective": "Normalizar texto delimitado sin mutar cadenas ni depender de identidad de referencia.", | ||
| "language_delta": "Java y JavaScript indexan unidades UTF-16; Python indexa puntos de código Unicode.", | ||
| "prediction_prompt": "Sigue la longitud recortada y los límites de `substring` para la entrada coreana entre corchetes.", | ||
| "transfer_trap": "Ni una unidad UTF-16 ni un punto de código equivalen necesariamente a un grafema percibido." | ||
| }, | ||
| "java-control-flow": { | ||
| "title": "Flujo de control", | ||
| "concept": "Flujo de control importa cuando ramas y bucles deciden qué valores entran al acumulador. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Flujo de control. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Ramas y bucles acotados", | ||
| "concept": "Un bucle `for` controla el intervalo numérico inclusivo y un `if` filtra los impares. Java exige una expresión booleana, por lo que la comparación del resto debe ser explícita.", | ||
| "worked_example": "El contador avanza del uno al siete. Solo los valores cuyo resto al dividir por dos no es cero entran en el acumulador, que produce una suma impar etiquetada.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Flujo de control en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Flujo de control.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Flujo de control importa cuando ramas y bucles deciden qué valores entran al acumulador." | ||
| "Usar `< limit` y omitir silenciosamente el límite superior cuando es impar.", | ||
| "Escribir un entero como condición de `if`, como si Java aceptara verdad numérica." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Flujo de control antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Flujo de control debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué valores contribuyen cuando el límite es seis?", | ||
| "¿Por qué la condición usa `<=` y no `<`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Flujo de control a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Recorre el intervalo inclusivo, acumula únicamente enteros impares y después imprime el total calculado.", | ||
| "objective": "Coordinar límite, rama de paridad y acumulador para cero y límites positivos.", | ||
| "language_delta": "Las condiciones Java deben tener tipo booleano, a diferencia de lenguajes que convierten enteros cero y no cero.", | ||
| "prediction_prompt": "Enumera todas las visitas con límite tres y marca cuáles modifican el total.", | ||
| "transfer_trap": "Una API de rango de otro lenguaje puede excluir el extremo; este bucle Java expresa su inclusión directamente." | ||
| }, | ||
| "java-methods": { | ||
| "title": "Métodos", | ||
| "concept": "Métodos importa cuando un cálculo reutilizable necesita un límite con nombre. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Métodos. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-enum-switch": { | ||
| "title": "Expresiones `switch` exhaustivas sobre un `enum`", | ||
| "concept": "Un `enum` define un conjunto finito de constantes únicas. Una expresión `switch` asigna un valor a cada constante mediante reglas con flecha, sin ejecución en cascada accidental hacia el caso siguiente.", | ||
| "worked_example": "El ejemplo transforma `STARTED` y `STOPPED` en acciones distintas, guarda el resultado del `switch` en un `String` y solo entonces añade una etiqueta de muestra.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Métodos en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Métodos.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Métodos importa cuando un cálculo reutilizable necesita un límite con nombre." | ||
| "Añadir `default` y ocultar una constante que quede sin tratar cuando evolucione el `enum`.", | ||
| "Esperar que un caso con flecha continúe ejecutando el cuerpo siguiente." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Métodos antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Métodos debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué informa el compilador si falta una constante y no existe `default`?", | ||
| "¿Puede una expresión `switch` ejecutar dos reglas con flecha para un solo estado?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Métodos a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Corrige la correspondencia textual de cada estado y conserva la exhaustividad de la expresión `switch`.", | ||
| "objective": "Convertir un estado cerrado en texto sin resultado mutable ni ejecución en cascada entre casos.", | ||
| "language_delta": "Las expresiones `switch` producen valores; las formas antiguas de sentencia solían necesitar asignación y `break`.", | ||
| "prediction_prompt": "Elige la regla exacta de `BLOCKED` y decide si puede ejecutarse una posterior.", | ||
| "transfer_trap": "La exhaustividad del `enum` no valida cadenas arbitrarias: `valueOf` todavía rechaza nombres desconocidos." | ||
| }, | ||
| "java-input": { | ||
| "title": "Lectura de entrada", | ||
| "concept": "Lectura de entrada importa cuando stdin llega como bytes y debe convertirse en tokens tipados. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Lectura de entrada. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-methods": { | ||
| "title": "Punto de control: método estático tipado", | ||
| "concept": "La firma fija parámetros y retorno, y cada argumento se copia a una variable de parámetro. El cálculo permanece en un método estático tipado en vez de repetirse en `main`.", | ||
| "worked_example": "`area(int, int)` contiene el cálculo y `main` le entrega dimensiones de muestra separadas. El valor devuelto cruza la frontera antes de formatearse.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Lectura de entrada en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Lectura de entrada.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Lectura de entrada importa cuando stdin llega como bytes y debe convertirse en tokens tipados." | ||
| "Repetir la multiplicación en `main` en lugar de completar el método nombrado.", | ||
| "Creer que un método sustituye la variable de referencia de quien llama por compartir el mismo objeto." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Lectura de entrada antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Lectura de entrada debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué retorno y qué dos parámetros declara `area`?", | ||
| "¿Por qué debe `main` llamar al método en vez de repetir la expresión?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Lectura de entrada a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Completa el método que devuelve el área y deja el análisis de entrada y el formato de salida dentro de `main`.", | ||
| "objective": "Demostrar una frontera reutilizable con retorno en vez de concentrar toda la lógica en la entrada.", | ||
| "language_delta": "Java comprueba la firma al compilar y permite llamar a este método `static` sin construir un objeto `Solution`.", | ||
| "prediction_prompt": "Sigue ambos enteros desde los parámetros, por `return`, hasta el `println` de quien llama.", | ||
| "transfer_trap": "Pasar un objeto copia su referencia; no concede al método la facultad de reemplazar la variable exterior." | ||
| }, | ||
| "java-arrays-collections": { | ||
| "title": "Arrays y colecciones", | ||
| "concept": "Arrays y colecciones importa cuando arrays fijos, List expandible, Map por clave y Set único resuelven tareas distintas. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Arrays y colecciones. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-overloading-varargs": { | ||
| "title": "Sobrecargas y argumentos variables (`varargs`)", | ||
| "concept": "La sobrecarga ofrece varias firmas para un nombre. El compilador elige una aplicable y un parámetro de argumentos variables `int...` se representa como `int[]` dentro del método.", | ||
| "worked_example": "La sobrecarga con argumentos variables suma dos puntuaciones y delega el entero calculado a la firma de aridad fija. Esa segunda llamada selecciona la versión de aridad fija.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Arrays y colecciones en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Arrays y colecciones.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Arrays y colecciones importa cuando arrays fijos, List expandible, Map por clave y Set único resuelven tareas distintas." | ||
| "Devolver la cantidad de argumentos cuando se solicita la suma de sus valores.", | ||
| "Suponer que la selección de sobrecarga espera a inspeccionar el contenido del array en ejecución." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Arrays y colecciones antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Arrays y colecciones debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué `label(name, total)` elige la firma de dos argumentos?", | ||
| "¿Qué array recibe el método variable para un usuario seguido de un único cero?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Arrays y colecciones a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Reduce el array de puntuaciones y delega el resultado numérico a la sobrecarga encargada del formato.", | ||
| "objective": "Observar la selección en compilación y la naturaleza de array de la aridad variable.", | ||
| "language_delta": "A diferencia de los parámetros `rest` de JavaScript, los argumentos variables de Java participan en la aplicabilidad y la prioridad estáticas.", | ||
| "prediction_prompt": "Identifica la sobrecarga elegida en cada llamada antes de calcular la salida.", | ||
| "transfer_trap": "Añadir una firma demasiado amplia puede volver ambigua una llamada aunque cada implementación aislada sea válida." | ||
| }, | ||
| "java-classes-objects": { | ||
| "title": "Clases y objetos", | ||
| "concept": "Clases y objetos importa cuando los objetos necesitan estado de instancia y comportamiento. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Clases y objetos. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-packages-imports": { | ||
| "title": "Importaciones y nombres completamente cualificados", | ||
| "concept": "Una importación permite usar un nombre de tipo simple. La misma unidad puede referirse a otro tipo por su nombre de paquete, como `java.util.List` con un `ArrayList` importado.", | ||
| "worked_example": "La variable expone la interfaz `List` y el constructor nombra `ArrayList` mediante importación. Unir las palabras almacenadas demuestra que la colección se usa de verdad.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Clases y objetos en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Clases y objetos.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Clases y objetos importa cuando los objetos necesitan estado de instancia y comportamiento." | ||
| "Creer que una importación descarga una biblioteca o ejecuta su inicialización.", | ||
| "Importar dos tipos con el mismo nombre simple y esperar que Java adivine el deseado." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Clases y objetos antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Clases y objetos debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué nombre funcionaría si se eliminara la importación de `ArrayList`?", | ||
| "¿Por qué `String` puede escribirse sin importar `java.lang.String`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Clases y objetos a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Llena la `List` cualificada con la implementación importada y concatena sus tokens sin separadores.", | ||
| "objective": "Distinguir la comodidad del espacio de nombres de la disponibilidad del tipo observando una colección real.", | ||
| "language_delta": "Las importaciones Java cambian el contexto de nombres de la unidad; no ejecutan módulos Python ni cargan módulos JavaScript.", | ||
| "prediction_prompt": "Resuelve cada nombre simple y cualificado del ejemplo hasta su paquete.", | ||
| "transfer_trap": "Una importación con asterisco no incluye subpaquetes ni resuelve un nombre simple ambiguo." | ||
| }, | ||
| "java-constructors": { | ||
| "title": "Constructores", | ||
| "concept": "Constructores importa cuando los objetos nuevos deben empezar con campos válidos. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Constructores. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-arrays-collections": { | ||
| "title": "Observaciones sobre colecciones", | ||
| "concept": "Los arrays tienen longitud fija, mientras que los tipos genéricos `List`, `Map` y `Set` ofrecen comportamientos de colección distintos. Los `Stream` pueden derivar agregados de esas vistas sin modificar las colecciones fuente.", | ||
| "worked_example": "Cada valor del array entra en una lista, un mapa de frecuencias y un conjunto. La suma se reconstruye multiplicando cada clave por su conteo, de modo que cada campo observa su estructura.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Constructores en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Constructores.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Constructores importa cuando los objetos nuevos deben empezar con campos válidos." | ||
| "Imprimir la longitud del array cuando el contrato pide una suma derivada de entradas del mapa.", | ||
| "Confundir el tamaño de la lista, la cantidad de elementos distintos del conjunto y el número de claves del mapa cuando aparecen duplicados." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Constructores antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Constructores debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "Para `2 2 3`, ¿qué contiene cada colección al terminar el bucle?", | ||
| "¿Qué operación terminal calcula el total del flujo de entradas?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Constructores a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Llena las tres vistas y calcula el primer campo desde las frecuencias del mapa, no directamente desde el array fuente.", | ||
| "objective": "Explicar tres semánticas observables mediante un informe sensible a duplicados.", | ||
| "language_delta": "Los arrays Java son objetos reificados y fijos; los tipos genéricos de colección se comprueban y en gran parte se borran.", | ||
| "prediction_prompt": "Dibuja los estados de `List`, `Map` y `Set` para una entrada repetida antes de calcular.", | ||
| "transfer_trap": "Un flujo es perezoso hasta una operación terminal y no puede reutilizarse después de consumirlo." | ||
| }, | ||
| "java-encapsulation": { | ||
| "title": "Encapsulación", | ||
| "concept": "Encapsulación importa cuando los llamadores deben cambiar estado mediante métodos y no campos. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Encapsulación. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-generics": { | ||
| "title": "Conservar el tipo de elemento con genéricos", | ||
| "concept": "Un parámetro de tipo enlaza entrada y salida: `<T> T last(List<T>)` devuelve el mismo tipo que admite la lista. Un tipo crudo elimina esa garantía útil.", | ||
| "worked_example": "La inferencia elige `String` para la lista de colores. El auxiliar selecciona la última posición y devuelve una cadena sin conversión explícita.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Encapsulación en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Encapsulación.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Encapsulación importa cuando los llamadores deben cambiar estado mediante métodos y no campos." | ||
| "Usar un `List` crudo y desplazar a ejecución un error que podía evitarse al compilar.", | ||
| "Indexar con cero y devolver accidentalmente el primer elemento cuando hay varios." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Encapsulación antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Encapsulación debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué representa `T` para un `List<Integer>`?", | ||
| "¿Qué precondición hace segura la posición `size() - 1`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Encapsulación a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Completa la selección reutilizable del último elemento y conserva el tipo declarado por la lista de quien llama.", | ||
| "objective": "Expresar con una variable de tipo una relación fiable para quien llama, sin conversiones.", | ||
| "language_delta": "Los genéricos Java suelen borrarse, a diferencia de los arrays reificados, pero sus relaciones estáticas previenen llamadas inválidas.", | ||
| "prediction_prompt": "Infiere `T` y el índice elegido para cada lista de palabras suministrada.", | ||
| "transfer_trap": "El borrado no vuelve seguras las colecciones crudas; solo retira gran parte de la representación genérica en ejecución." | ||
| }, | ||
| "java-static-members": { | ||
| "title": "Miembros static", | ||
| "concept": "Miembros static importa cuando un valor pertenece a la clase y no a una instancia. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Miembros static. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-streams-lambdas": { | ||
| "title": "Canalizaciones perezosas de `Stream`", | ||
| "concept": "Un flujo describe el procesamiento hasta que una operación terminal pide resultados. Aquí `IntStream` filtra con una lambda, eleva al cuadrado mediante `IntStream.map` y `sum` recorre una vez.", | ||
| "worked_example": "`IntStream.of` crea dos valores distintos, el predicado conserva los pares y el mapeo los eleva antes de que el total terminal reciba una etiqueta.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Miembros static en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Miembros static.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Miembros static importa cuando un valor pertenece a la clase y no a una instancia." | ||
| "Construir `filter` y `map` sin invocar ninguna operación terminal.", | ||
| "Intentar consumir de nuevo el mismo objeto `Stream` después de `sum`." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Miembros static antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Miembros static debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿En qué momento se evalúan realmente el predicado y el mapeo?", | ||
| "¿Por qué cero supera el filtro par pero no aumenta la suma de cuadrados?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Miembros static a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Conserva los enteros pares, elévalos al cuadrado y produce una única suma terminal de la secuencia analizada.", | ||
| "objective": "Conectar operaciones intermedias perezosas con un resultado numérico observable.", | ||
| "language_delta": "Los flujos Java son recorridos de un solo uso, no contenedores reutilizables ni arrays JavaScript.", | ||
| "prediction_prompt": "Escribe la secuencia tras filtrar y después de mapear el caso con negativos.", | ||
| "transfer_trap": "El orden y la pereza de un flujo no implican paralelismo; esa es una elección aparte." | ||
| }, | ||
| "java-enum-switch": { | ||
| "title": "Enum y switch", | ||
| "concept": "Enum y switch importa cuando un conjunto fijo de estados debe mapearse a resultados explícitos. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Enum y switch. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-comparators-sorting": { | ||
| "title": "Cadenas de comparadores", | ||
| "concept": "`comparingInt`, `reversed` y `thenComparing` expresan directamente cada regla. Así se evita restar puntuaciones, operación que puede desbordarse con enteros extremos.", | ||
| "worked_example": "`Kai` y `Bea` empatan; la segunda comparación ascendente coloca a `Bea` primero. La muestra lee ese registro tras ordenar.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Enum y switch en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Enum y switch.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Enum y switch importa cuando un conjunto fijo de estados debe mapearse a resultados explícitos." | ||
| "Invertir toda la cadena y cambiar sin querer también el desempate por nombre.", | ||
| "Devolver `right.score() - left.score()` y violar el orden por desbordamiento." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Enum y switch antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Enum y switch debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué etapa decide entre puntuaciones iguales?", | ||
| "¿Por qué un único usuario con puntuación negativa sigue siendo ganador?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Enum y switch a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Compón puntuación descendente con nombre ascendente e imprime el primer usuario resultante.", | ||
| "objective": "Codificar una regla estable de negocio como composición legible de comparadores.", | ||
| "language_delta": "Los objetos `Comparator` vuelven reutilizable un orden; el operador `<` no ordena directamente registros arbitrarios.", | ||
| "prediction_prompt": "Ordena a mano el caso de tres usuarios aplicando una sola etapa cada vez.", | ||
| "transfer_trap": "`reversed()` afecta al comparador construido hasta ese punto; su posición modifica los empates." | ||
| }, | ||
| "java-exceptions": { | ||
| "title": "Excepciones", | ||
| "concept": "Excepciones importa cuando una entrada inválida debe recuperarse sin ocultar fallos no relacionados. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Excepciones. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-classes-objects": { | ||
| "title": "Estado independiente por instancia", | ||
| "concept": "Cada objeto `Counter` posee su propio campo `value`. El método `add` recibe un parámetro local `delta` y debe actualizar el campo de quien lo recibe mediante `this` o un nombre inequívoco.", | ||
| "worked_example": "Un contador comienza en diez y recibe un incremento en `add`. El método cambia el campo de esa instancia y `main` observa doce después.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Excepciones en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Excepciones.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Excepciones importa cuando una entrada inválida debe recuperarse sin ocultar fallos no relacionados." | ||
| "Modificar solo la variable de parámetro y esperar que el campo del objeto la siga.", | ||
| "Declarar todo el estado `static` y hacer que instancias independientes compartan un valor." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Excepciones antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Excepciones debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué almacenamiento cambia en `add` y qué variable solo transporta el argumento?", | ||
| "¿Qué ocurriría con dos contadores iniciados en valores diferentes?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Excepciones a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Envía el incremento analizado a un método de `Counter` que actualice el estado propio de la instancia receptora.", | ||
| "objective": "Observar identidad y mutación encapsulada mediante una instancia mínima.", | ||
| "language_delta": "Los campos de instancia viven en cada objeto; un parámetro es una variable local creada para una llamada concreta.", | ||
| "prediction_prompt": "Sigue por separado el campo del contador y el parámetro `delta` antes y después de llamar.", | ||
| "transfer_trap": "El acceso privado bloquea escrituras exteriores, pero solo la lógica del método puede proteger una invariante." | ||
| }, | ||
| "java-generics": { | ||
| "title": "Genéricos", | ||
| "concept": "Genéricos importa cuando un método debe conservar el tipo de elemento del llamador. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Genéricos. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-constructors": { | ||
| "title": "Invariantes establecidas por el constructor", | ||
| "concept": "Un constructor lleva el nombre de la clase y carece de tipo de retorno. Antes de terminar debe asignar todos los campos `final` vacíos; `this.field` distingue campos de parámetros homónimos.", | ||
| "worked_example": "El constructor de `Rectangle` transfiere dos argumentos a dimensiones finales. Desde entonces, `area` puede asumir que ambos campos fueron establecidos.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Genéricos en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Genéricos.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Genéricos importa cuando un método debe conservar el tipo de elemento del llamador." | ||
| "Escribir `void Rectangle(...)` y declarar un método en lugar de un constructor.", | ||
| "Asignar un parámetro a sí mismo y dejar sin inicializar el campo del mismo nombre." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Genéricos antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Genéricos debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué identifica el lado izquierdo de `this.width = width`?", | ||
| "¿Por qué se rechaza una ruta que omita un campo final?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Genéricos a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Inicializa ambas dimensiones desde los argumentos para que `area` utilice campos finales válidos, sin valores provisionales.", | ||
| "objective": "Establecer una vez el estado del objeto al construirlo.", | ||
| "language_delta": "Los constructores Java son declaraciones especiales, no funciones inicializadoras normales que devuelven un valor.", | ||
| "prediction_prompt": "Resuelve cada campo y parámetro homónimo en las asignaciones antes de calcular el área.", | ||
| "transfer_trap": "Los campos finales no pueden reasignarse, pero no congelan recursivamente los objetos referenciados." | ||
| }, | ||
| "java-interfaces": { | ||
| "title": "Interfaces", | ||
| "concept": "Interfaces importa cuando los llamadores deben depender del comportamiento y no de una clase concreta. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Interfaces. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-encapsulation": { | ||
| "title": "Estado privado con reglas aplicadas", | ||
| "concept": "Un campo privado oculta su representación. La operación expuesta todavía debe validar los cambios solicitados: visibilidad e invariantes resuelven problemas diferentes.", | ||
| "worked_example": "`Account` acepta dos depósitos positivos e ignora la petición negativa. Su método de consulta expone únicamente el saldo entero resultante.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Interfaces en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Interfaces.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Interfaces importa cuando los llamadores deben depender del comportamiento y no de una clase concreta." | ||
| "Suponer que `private` rechaza automáticamente valores inválidos enviados a un método.", | ||
| "Devolver una colección interna mutable y permitir que se eluda la frontera prevista." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Interfaces antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Interfaces debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué llamada deja sin cambios el saldo de la muestra?", | ||
| "¿Podría el código interno asignar todavía un valor inválido al campo privado?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Interfaces a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Coloca la regla de positividad dentro de `deposit` e informa del estado únicamente mediante su método de consulta.", | ||
| "objective": "Aplicar una invariante pequeña en el método propietario de la transición.", | ||
| "language_delta": "Los modificadores de acceso regulan miembros en compilación y ejecución; no implementan automáticamente validación de dominio.", | ||
| "prediction_prompt": "Aplica cada incremento en orden y registra si la condición de validación lo acepta.", | ||
| "transfer_trap": "Un getter rompe el encapsulamiento si devuelve un objeto interno mutable en vez de una vista o copia segura." | ||
| }, | ||
| "java-inheritance-composition": { | ||
| "title": "Herencia y composición", | ||
| "concept": "Herencia y composición importa cuando una subclase también necesita un objeto auxiliar. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Herencia y composición. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-static-members": { | ||
| "title": "Constantes estáticas pertenecientes a la clase", | ||
| "concept": "Un campo `static` pertenece a la clase y se comparte entre llamadas e instancias. `static final` fija su enlace, aunque un objeto alcanzado por él todavía podría ser mutable.", | ||
| "worked_example": "El método de escala lee `FACTOR` en contexto de clase y multiplica un valor de muestra. Ningún miembro estático necesita una instancia.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Herencia y composición en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Herencia y composición.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Herencia y composición importa cuando una subclase también necesita un objeto auxiliar." | ||
| "Crear un objeto solo para llamar a una utilidad estática y ocultar que pertenece a la clase.", | ||
| "Leer `final` como promesa de inmutabilidad profunda para cualquier tipo de campo." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Herencia y composición antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Herencia y composición debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Cuántos almacenamientos de `FACTOR` existen si se crean diez objetos?", | ||
| "¿Puede `scale` leer un campo de instancia sin recibir o crear una?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Herencia y composición a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Corrige el factor compartido para que el método estático escale entradas positivas, cero y negativas.", | ||
| "objective": "Usar estado de clase de forma deliberada y separarlo de los campos por objeto.", | ||
| "language_delta": "`static` adjunta el miembro a una clase declarada, no a un enlace libre de módulo.", | ||
| "prediction_prompt": "Sustituye el factor de clase en la multiplicación para cada entrada con signo.", | ||
| "transfer_trap": "Un campo estático mutable comparte estado entre todas las instancias y suele necesitar sincronización." | ||
| }, | ||
| "java-records": { | ||
| "title": "Records", | ||
| "concept": "Records importa cuando los portadores de datos inmutables necesitan menos repetición. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Records. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-interfaces": { | ||
| "title": "Comportamiento a través de una interfaz", | ||
| "concept": "Una interfaz nombra un contrato público. El código que recibe `Named` puede llamar a `name()` sin conocer si la implementación es `record`, clase u otro tipo declarado.", | ||
| "worked_example": "Un `record User` implementa `Named` mediante su método de acceso. El renderizador solo consume la interfaz y convierte el nombre con reglas de mayúsculas estables.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Records en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Records.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Records importa cuando los portadores de datos inmutables necesitan menos repetición." | ||
| "Reducir la visibilidad del método implementado aunque los métodos de interfaz sean públicos.", | ||
| "Tipar el renderizador como `User` y acoplarlo sin necesidad a una implementación." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Records antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Records debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué método está disponible mediante una variable `Named`?", | ||
| "¿Puede participar otra clase sin extender `User`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Records a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Implementa la conversión a mayúsculas dentro del auxiliar que recibe el contrato de interfaz, no una representación concreta.", | ||
| "objective": "Invocar comportamiento compartido contra un contrato nominal.", | ||
| "language_delta": "Las interfaces Java son nominales y deben declararse; TypeScript suele aceptar compatibilidad estructural implícita.", | ||
| "prediction_prompt": "Sigue el método de acceso del `record` a través de `Named` antes de aplicar `Locale.ROOT`.", | ||
| "transfer_trap": "Los métodos predeterminados aportan comportamiento, pero los campos de interfaz no se vuelven estado por instancia." | ||
| }, | ||
| "java-optional": { | ||
| "title": "Optional", | ||
| "concept": "Optional importa cuando un valor ausente debe manejarse explícitamente. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Optional. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-inheritance-composition": { | ||
| "title": "Herencia junto con composición", | ||
| "concept": "La herencia modela una relación sustituible «es un» y la composición una relación «tiene un». `Player` reutiliza la puntuación base y posee por separado un `Bonus`.", | ||
| "worked_example": "La construcción envía el valor base a `super` y crea el `record` compuesto para la bonificación. `total` lee ambas fuentes y obtiene doce.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Optional en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Optional.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Optional importa cuando un valor ausente debe manejarse explícitamente." | ||
| "Ignorar el campo compuesto y devolver únicamente el estado heredado.", | ||
| "Heredar solo para reutilizar código aunque el subtipo no respete las expectativas de la base." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Optional antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Optional debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué constructor inicializa el campo protegido de la base?", | ||
| "¿Por qué `Bonus` es un campo y no la superclase de `Player`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Optional a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Combina en `total` el valor heredado de la base con la bonificación almacenada por composición.", | ||
| "objective": "Separar dos mecanismos de reutilización según su relación semántica.", | ||
| "language_delta": "Java permite una superclase y varias interfaces, lo que vuelve especialmente útil componer partes independientes.", | ||
| "prediction_prompt": "Sigue los dos argumentos del constructor hacia sus objetos distintos antes de sumarlos.", | ||
| "transfer_trap": "La visibilidad protegida permite accesos concretos; no convierte una relación de posesión en subtipo válido." | ||
| }, | ||
| "java-streams-lambdas": { | ||
| "title": "Streams y lambdas", | ||
| "concept": "Streams y lambdas importa cuando el procesamiento de colecciones es más claro como tubería. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Streams y lambdas. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-exceptions": { | ||
| "title": "Recuperación precisa de excepciones", | ||
| "concept": "`Integer.parseInt` comunica sintaxis mal formada con `NumberFormatException`, que no es comprobada. Capturarla justo en la frontera de análisis evita confundir otros defectos con entrada inválida.", | ||
| "worked_example": "Analizar `oops` entra en el `catch` concreto y devuelve el valor alternativo. Otros fallos inesperados no quedan absorbidos por este auxiliar.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Streams y lambdas en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Streams y lambdas.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Streams y lambdas importa cuando el procesamiento de colecciones es más claro como tubería." | ||
| "Capturar `Exception` y ocultar errores de programación no relacionados.", | ||
| "Esperar que `parseInt` devuelva un `Optional` o centinela en vez de lanzar ante texto mal formado." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Streams y lambdas antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Streams y lambdas debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Un entero negativo válido entra en el bloque `catch`?", | ||
| "¿Por qué capturar `NumberFormatException` es más seguro que capturar cualquier excepción?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Streams y lambdas a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Devuelve el entero analizado o el valor alternativo suministrado únicamente cuando la sintaxis numérica sea inválida.", | ||
| "objective": "Recuperarse en la frontera relevante más pequeña sin enmascarar otros fallos.", | ||
| "language_delta": "Java distingue excepciones comprobadas de subclases no comprobadas de `RuntimeException`, como `NumberFormatException`.", | ||
| "prediction_prompt": "Elige la ruta `try` o `catch` para cada token antes de evaluar su alternativa.", | ||
| "transfer_trap": "Un entero negativo válido es un dato; solo la sintaxis numérica mal formada selecciona la recuperación." | ||
| }, | ||
| "java-comparators-sorting": { | ||
| "title": "Comparadores y ordenación", | ||
| "concept": "Comparadores y ordenación importa cuando los objetos necesitan orden separado de su forma de datos. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Comparadores y ordenación. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-optional": { | ||
| "title": "Filtrado y alternativa con `Optional`", | ||
| "concept": "`Optional` representa un resultado posiblemente ausente. `filter` conserva un valor presente si cumple el predicado y `orElse` aporta una alternativa sin usar `get()`.", | ||
| "worked_example": "La palabra presente `cat` tiene tres caracteres y satisface el predicado de longitud máxima de tres, por lo que no se elige la alternativa. Una etiqueta distingue la línea de demostración.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Comparadores y ordenación en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Comparadores y ordenación.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Comparadores y ordenación importa cuando los objetos necesitan orden separado de su forma de datos." | ||
| "Llamar a `get()` sin probar presencia y arriesgar `NoSuchElementException`.", | ||
| "Usar `Optional` indiscriminadamente en campos y parámetros en vez de como retorno que hace explícita la ausencia." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Comparadores y ordenación antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Comparadores y ordenación debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Ejecuta `filter` su predicado si el `Optional` ya está vacío?", | ||
| "¿Qué ruta produce `missing` para el guion centinela?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Comparadores y ordenación a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Construye ausencia para el guion `-`, conserva únicamente los tokens presentes de como máximo tres caracteres y elige de forma declarativa el valor alternativo.", | ||
| "objective": "Separar la entrada ausente y el texto demasiado largo del texto presente aceptado de como máximo tres caracteres, sin extracción insegura.", | ||
| "language_delta": "`Optional` es un contenedor explícito; una referencia anulable no ofrece por sí misma una ruta de manejo mediante métodos.", | ||
| "prediction_prompt": "Para cada uno de los cuatro casos, marca el `Optional` como presente o vacío después de construirlo y de nuevo después de filtrarlo.", | ||
| "transfer_trap": "`Optional` no sustituye excepciones cuando un fallo necesita información diagnóstica." | ||
| }, | ||
| "java-try-with-resources": { | ||
| "title": "try-with-resources", | ||
| "concept": "try-with-resources importa cuando los recursos deben cerrarse aunque el código salga temprano. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de try-with-resources. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Recursos observables con `try-with-resources`", | ||
| "concept": "Una declaración `try-with-resources` cierra su `AutoCloseable` tanto en salida normal como abrupta. Leer el recurso declarado hace observable su ciclo de vida.", | ||
| "worked_example": "El `ByteArrayInputStream` administrado contiene bytes de muestra. Dentro del bloque se leen, decodifican como UTF-8, recortan y convierten a mayúsculas antes del cierre automático.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de try-with-resources en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de try-with-resources.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: try-with-resources importa cuando los recursos deben cerrarse aunque el código salga temprano." | ||
| "Declarar un recurso pero seguir leyendo desde otro flujo que no está administrado.", | ||
| "Olvidar que un fallo de cierre puede quedar suprimido si el cuerpo ya lanzó una excepción." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica try-with-resources antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de try-with-resources debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué objeto recibe con garantía `close()` al salir del bloque?", | ||
| "¿En qué texto deben convertirse cero bytes antes de imprimir?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando try-with-resources a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Normaliza los bytes realmente leídos del flujo administrado y asigna a un contenido vacío su representación nombrada.", | ||
| "objective": "Vincular el ciclo de vida determinista del recurso con la ruta de datos que alcanza la salida.", | ||
| "language_delta": "La sentencia incorpora limpieza similar a RAII en el control de flujo aunque la memoria general sea recolectada.", | ||
| "prediction_prompt": "Sigue bytes, construcción, lectura, decodificación, `strip` y mayúsculas en ese orden.", | ||
| "transfer_trap": "La recolección de basura no sustituye un cierre oportuno de archivos, sockets u otros recursos externos." | ||
| }, | ||
| "java-packages-imports": { | ||
| "title": "Paquetes e imports", | ||
| "concept": "Paquetes e imports importa cuando los ejercicios de archivo único aún necesitan clases de paquetes JDK. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Paquetes e imports. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-equality-hashcode": { | ||
| "title": "Consistencia entre igualdad y hash", | ||
| "concept": "La igualdad lógica debe ser reflexiva, simétrica, transitiva, consistente y segura ante nulo. Si dos objetos son iguales, sus códigos hash también deben coincidir.", | ||
| "worked_example": "Un `record` genera `equals` y `hashCode` por componentes; por eso dos `Point` idénticos se reducen a una entrada de `HashSet`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Paquetes e imports en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Paquetes e imports.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Paquetes e imports importa cuando los ejercicios de archivo único aún necesitan clases de paquetes JDK." | ||
| "Comparar una sola coordenada y declarar iguales puntos diferentes.", | ||
| "Sobrescribir `equals` y conservar un `hashCode` que difiera para objetos iguales." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Paquetes e imports antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Paquetes e imports debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué objetos desiguales pueden compartir hash aunque los iguales no pueden diferir?", | ||
| "¿Cómo combina `instanceof Point other` la prueba y la conversión?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Paquetes e imports a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Incluye ambas coordenadas en `equals`, conserva la regla del hash y observa después el tamaño de `HashSet`.", | ||
| "objective": "Implementar un contrato de valor correcto dentro de colecciones hash.", | ||
| "language_delta": "`Object` usa igualdad de identidad; los `record` adoptan automáticamente semántica por componentes.", | ||
| "prediction_prompt": "Clasifica cada pareja de puntos antes de decidir si el conjunto añade una segunda entrada.", | ||
| "transfer_trap": "Una buena distribución mejora el rendimiento; la igualdad que implica el mismo hash es el requisito de corrección." | ||
| }, | ||
| "java-annotations": { | ||
| "title": "Anotaciones", | ||
| "concept": "Anotaciones importa cuando los metadatos pertenecen a declaraciones y no a lógica de ejecución. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Anotaciones. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-records": { | ||
| "title": "Punto de control: instantánea defensiva con `record`", | ||
| "concept": "Los `record` fijan referencias de componentes y generan miembros de valor, pero solo son superficialmente inmutables. `List.copyOf` en el constructor compacto separa la lista mutable y rechaza elementos nulos.", | ||
| "worked_example": "El constructor copia `[4, 5]`; añadir después otro valor a la lista original no cambia la lista almacenada, cuya suma permanece en nueve.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Anotaciones en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Anotaciones.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Anotaciones importa cuando los metadatos pertenecen a declaraciones y no a lógica de ejecución." | ||
| "Llamar profundamente inmutable al `record` mientras guarda directamente una `List` mutable del llamador.", | ||
| "Añadir estado mutable ajeno a los componentes y romper la coherencia de igualdad y hash." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Anotaciones antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Anotaciones debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué referencia se reasigna a la copia dentro del constructor compacto?", | ||
| "¿Por qué quitar un elemento de la fuente afecta al componente si ambos conservaron la misma lista?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Anotaciones a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Copia defensivamente el componente de lista antes de que la fuente pierda su último elemento y suma después la instantánea retenida.", | ||
| "objective": "Demostrar la inmutabilidad superficial y reparar la frontera de alias mediante una copia inmutable.", | ||
| "language_delta": "Un `record` Java es dato nominal conciso, no un grafo recursivamente congelado ni un tipo estructural TypeScript.", | ||
| "prediction_prompt": "Dibuja las referencias antes y después de `List.copyOf` y elimina luego el último elemento fuente.", | ||
| "transfer_trap": "Un componente final impide reasignar; no evita mutar un objeto almacenado sin copia defensiva." | ||
| }, | ||
| "java-sealed-classes": { | ||
| "title": "Clases sealed", | ||
| "concept": "Clases sealed importa cuando una jerarquía de dominio debe listar implementaciones permitidas. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Clases sealed. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-annotations": { | ||
| "title": "Metadatos de anotación en ejecución", | ||
| "concept": "`@Target(METHOD)` limita la colocación y `@Retention(RUNTIME)` conserva metadatos para reflexión. Sin retención en ejecución, `getAnnotation` no puede observar el prefijo.", | ||
| "worked_example": "La reflexión encuentra `normalize` y lee su etiqueta `trace`. La llamada normal transforma el texto y los metadatos reflejados aportan el prefijo.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Clases sealed en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Clases sealed.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Clases sealed importa cuando una jerarquía de dominio debe listar implementaciones permitidas." | ||
| "Confiar en la retención predeterminada `CLASS` cuando se necesita reflexión durante la ejecución.", | ||
| "Buscar otra firma de método y no obtener el objeto de reflexión esperado." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Clases sealed antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Clases sealed debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué política permite a la VM exponer la anotación a reflexión?", | ||
| "¿Qué destino impide colocar `Label` sobre un campo?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Clases sealed a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Cambia la declaración de la anotación para que sus metadatos de método sobrevivan en el programa ejecutado.", | ||
| "objective": "Observar mediante reflexión el valor anotado en vez de tratarlo como decoración sin uso.", | ||
| "language_delta": "Las anotaciones Java necesitan retención explícita para uso dinámico; muchas de tipos solo requieren visibilidad al compilar.", | ||
| "prediction_prompt": "Predice si `getAnnotation` devuelve objeto o nulo antes y después de añadir retención.", | ||
| "transfer_trap": "La retención controla disponibilidad, no comportamiento; el código todavía debe leer y aplicar los metadatos." | ||
| }, | ||
| "java-testing-assert": { | ||
| "title": "Pruebas y assert", | ||
| "concept": "Pruebas y assert importa cuando las comprobaciones pequeñas deben fallar cerca del método roto. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Pruebas y assert. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-sealed-classes": { | ||
| "title": "Formas selladas y `switch` con patrones", | ||
| "concept": "Una interfaz sellada restringe implementadores directos según `permits`; en una misma unidad pueden inferirse si se omite la cláusula. Java 21 deconstruye `record` en un `switch` exhaustivo sin `default`.", | ||
| "worked_example": "El patrón `Rect` enlaza ancho y alto y calcula su métrica. `Circle` y `Dot` siguen apareciendo como alternativas explícitas del `switch`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Pruebas y assert en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Pruebas y assert.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Pruebas y assert importa cuando las comprobaciones pequeñas deben fallar cerca del método roto." | ||
| "Usar solo métodos virtuales y no demostrar que la jerarquía está cerrada a subtipos directos ajenos.", | ||
| "Añadir `default` y ocultar al compilador una forma nueva permitida." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Pruebas y assert antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Pruebas y assert debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué componentes de `record` se multiplican en la rama rectangular?", | ||
| "¿Por qué puede compilar el `switch` de Java 21 sin `default`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Pruebas y assert a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Corrige la rama con patrón `Circle` y conserva la exhaustividad sobre toda la jerarquía sellada.", | ||
| "objective": "Usar el conocimiento de subtipos cerrados y la deconstrucción de `record` en una expresión observable.", | ||
| "language_delta": "Los tipos sellados cierran nominalmente una jerarquía, a diferencia de una clase base abierta o una unión estructural inferida.", | ||
| "prediction_prompt": "Selecciona el patrón de cada token de forma y enlaza sus componentes antes de calcular.", | ||
| "transfer_trap": "Sellar controla la extensión directa; no vuelve automáticamente inmutables las instancias." | ||
| }, | ||
| "java-equality-hashcode": { | ||
| "title": "equals y hashCode", | ||
| "concept": "equals y hashCode importa cuando HashSet debe reconocer objetos de valor iguales. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de equals y hashCode. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "java-testing-assert": { | ||
| "title": "Proyecto final: comprobaciones siempre activas", | ||
| "concept": "El proyecto combina UTF-8, métodos, `record`, colecciones, flujos, comparadores y alternativas. `assert` suele estar desactivado sin `-ea`; una regla obligatoria usa aquí un método que lanza `AssertionError`.", | ||
| "worked_example": "Dos registros empatados se ordenan por nombre; `check` verifica la alternativa vacía y luego se imprime un ganador etiquetado. Esa llamada se ejecuta sin depender de `-ea`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de equals y hashCode en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de equals y hashCode.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: equals y hashCode importa cuando HashSet debe reconocer objetos de valor iguales." | ||
| "Usar `assert` para validación obligatoria aunque el evaluador no habilita aserciones Java.", | ||
| "Ordenar solo por puntuación y dejar que la llegada decida los empates." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica equals y hashCode antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de equals y hashCode debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué comprobación sigue activa cuando la JVM se inicia sin `-ea`?", | ||
| "¿Cómo combinan el comparador y `Optional` la selección y la alternativa vacía?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando equals y hashCode a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| }, | ||
| "java-overloading-varargs": { | ||
| "title": "Sobrecarga y varargs", | ||
| "concept": "Sobrecarga y varargs importa cuando las llamadas eligen firmas y agrupan argumentos extra. La lección se mantiene en un solo Solution.java para que se vea la preparación del valor, la llamada a la API de Java y la línea exacta de stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Sobrecarga y varargs. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Sobrecarga y varargs en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Sobrecarga y varargs.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Sobrecarga y varargs importa cuando las llamadas eligen firmas y agrupan argumentos extra." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Sobrecarga y varargs antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Sobrecarga y varargs debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Sobrecarga y varargs a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Completa la selección determinista del mejor usuario y conserva la comprobación explícita del resultado vacío antes de emitirlo.", | ||
| "objective": "Integrar la ruta principal en un programa de `record` y colecciones que pueda comprobarse siempre.", | ||
| "language_delta": "Tanto el `assert` de Python, eliminable bajo optimización, como el de Java, desactivado de forma predeterminada, pueden desaparecer; una llamada ordinaria se evalúa siempre.", | ||
| "prediction_prompt": "Ordena el empate con ambas etapas y sigue por separado la lista vacía a través de su alternativa y `check`.", | ||
| "transfer_trap": "Las aserciones expresan supuestos internos; la entrada no confiable exige control normal y fallos claros." | ||
| } | ||
| } | ||
| } |
+363
-279
@@ -7,422 +7,506 @@ { | ||
| "java-output": { | ||
| "title": "標準出力", | ||
| "concept": "標準出力は判定が改行と文字をそのまま比較するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「標準出力」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "標準出力を正確に組み立てる", | ||
| "concept": "`System.out.print`は引数だけを出力し、`println`はさらに行を終えます。この判定器はCRLFをLFへ正規化しますが、空白や余分な空行など、それ以外の差は全て意味を持ちます。", | ||
| "worked_example": "例は`String`の名前と`int`の点数から`Mina:4`を作り、`print`へ渡す文字列内に改行を1つ明示します。出力APIは見出しを足さず、隠れた変換も行いません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「標準出力」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「標準出力」演習の TODO を解かないこと。", | ||
| "標準出力は判定が改行と文字をそのまま比較するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "必要なコロンの代わりに、空白やハイフンを区切りとして使うことです。", | ||
| "既に改行で終わる文字列へ`println`を使い、空行を1つ増やすことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「標準出力」を適用してから `example:` 出力へつながりますか。", | ||
| "「標準出力」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "末尾が`\\n`の文字列を`print`へ渡すと、数字の後には何が出力されますか。", | ||
| "例の`print`を`println`へ変えても、標準出力は完全に同じですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「標準出力」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "学習者名と点数を解析し、コロン区切りの1行を作って、末尾改行をちょうど1つ出力してください。", | ||
| "objective": "区切りと行末を意図的に選び、判定対象として余分な文字のない標準出力を作ります。", | ||
| "language_delta": "組み込みの`print`関数を持つ言語と異なり、Javaは`System.out`ストリームへ出力し、`print`と`println`を区別します。", | ||
| "prediction_prompt": "実行前に、例が出力する最後の改行まで含む全ての文字を順に書いてください。", | ||
| "transfer_trap": "端末上で同じに見えても、改行が欠ける場合と2つある場合は別の出力です。見た目だけで判断しないでください。" | ||
| }, | ||
| "java-input": { | ||
| "title": "標準入力をUTF-8でデコードする", | ||
| "concept": "`System.in`が供給するのはデコード済み文字列ではなくバイトです。`readAllBytes`と`StandardCharsets.UTF_8`を組み合わせれば移植可能にデコードでき、`java.lang`外のクラスにはインポートか完全修飾名が必要です。", | ||
| "worked_example": "`ByteArrayInputStream`で標準入力を代用し、バイトを明示的にデコードして2トークンを加えます。例は演習の答えではなく、ラベル付きの別の合計を出力します。", | ||
| "common_mistakes": [ | ||
| "バイトから`String`を作るとき、実行基盤既定の文字コードへ依存することです。", | ||
| "空入力を1トークンへ分割し、空文字列を整数として解析しようとすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "`StandardCharsets`にはインポートが必要で、`String`には不要なのはなぜですか。", | ||
| "空入力ケースが`Integer.parseInt`へ到達するのを防ぐ分岐はどれですか。" | ||
| ], | ||
| "exercise_prompt": "標準入力全体をUTF-8でデコードし、符号付き整数トークンを合計し、トークンがなければ`0`を保ってください。", | ||
| "objective": "既定正規表現の`\\s+`で区切られた有効な整数を合計し、空入力では`0`を返します。", | ||
| "language_delta": "標準入力を最初から文字列として提供する環境もありますが、Javaの`InputStream`では文字コードの選択もプログラムの責任です。", | ||
| "prediction_prompt": "複数行に分かれたトークンの合計と、入力バイトが`0`個の場合の結果をそれぞれ予測してください。", | ||
| "transfer_trap": "インポートはソース内の型名を短くするだけで、JDKクラスをインストールしたり動的に読み込んだりする操作ではありません。" | ||
| }, | ||
| "java-variables-types": { | ||
| "title": "変数と型", | ||
| "concept": "変数と型はプリミティブ値とオブジェクト参照を使う前に宣言するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「変数と型」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "型付きの値と`final`束縛", | ||
| "concept": "局所変数の宣言は各値へコンパイル時型を与えます。プリミティブは値を持ち、参照はオブジェクトを指し、`final`は再束縛を防ぎ、`static`状態はインスタンスではなくクラスに属します。", | ||
| "worked_example": "例は名前の束縛を`final`にし、可変な整数点を基準値と比較して、その真偽値結果をレポートへ整形します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「変数と型」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「変数と型」演習の TODO を解かないこと。", | ||
| "変数と型はプリミティブ値とオブジェクト参照を使う前に宣言するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "規則が「以上」なのに、点数と基準値が等しい場合を失敗扱いすることです。", | ||
| "`final`参照から到達するオブジェクトまで、全て深く不変になると考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「変数と型」を適用してから `example:` 出力へつながりますか。", | ||
| "「変数と型」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "例で再代入できる宣言と、できない宣言はそれぞれどれですか。", | ||
| "点数と基準値がともに`0`なら、比較結果は何ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「変数と型」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "解析済みの各値を適切な宣言型へ保持し、境界を含む合否レポートを作ってください。", | ||
| "objective": "`String`、`int`、`boolean`を1つの型付きレポートへ結び、等値の境界ケースも保ちます。", | ||
| "language_delta": "Javaの局所型と`final`はコンパイル時制約です。`final`は束縛を固定しますが、参照先オブジェクトの不変性までは保証しません。", | ||
| "prediction_prompt": "点数が基準を上回る場合と、ちょうど等しい場合について、比較式を先に評価してください。", | ||
| "transfer_trap": "JavaScriptの真値/偽値を持ち込まないでください。Javaでは整数を真偽値条件の位置に置けません。" | ||
| }, | ||
| "java-numbers-operators": { | ||
| "title": "数値と演算子", | ||
| "concept": "数値と演算子は多くの stdin 問題で除算と剰余を整数として扱うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「数値と演算子」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "`0`方向へ切り捨てる整数演算", | ||
| "concept": "両被演算子が`int`なら、`/`は`0`方向へ切り捨てた整数商、`%`は対応する余りを返します。数値仮引数型はコンパイル時のオーバーロード選択にも使われます。", | ||
| "worked_example": "`19`を`4`で割ると商は`4`、余りは`3`です。例はラベルを付け、演習の簡潔な出力形式とは区別しています。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「数値と演算子」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「数値と演算子」演習の TODO を解かないこと。", | ||
| "数値と演算子は多くの stdin 問題で除算と剰余を整数として扱うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`/`を`Math.floorDiv`へ置き換え、負の商の規則を変えることです。", | ||
| "Javaの`%`ではなく、床関数に基づく剰余の規則で余りを導くことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「数値と演算子」を適用してから `example:` 出力へつながりますか。", | ||
| "「数値と演算子」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "被除数が負、除数が正なら、商はどの方向へ丸められますか。", | ||
| "各ケースで`dividend == quotient * divisor + remainder`をどう確認できますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「数値と演算子」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "除数が`0`でない2整数を解析し、Javaの商と余りをコロン区切りで出力してください。", | ||
| "objective": "他言語の床除算を持ち込まず、符号付き整数除算を計算します。", | ||
| "language_delta": "Pythonの`//`は負の無限大方向へ床しますが、Javaの整数`/`は`0`方向へ切り捨てます。", | ||
| "prediction_prompt": "実行前に、`-17 / 5`の商と余りの両フィールドを予測してください。", | ||
| "transfer_trap": "一方でも浮動小数点へ変えると別の演算が選ばれ、整数切り捨ての学習ではなくなります。" | ||
| }, | ||
| "java-strings": { | ||
| "title": "文字列", | ||
| "concept": "文字列は比較や出力の前に文字列の整形と部分抽出を行うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「文字列」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "文字列の整形と内容比較", | ||
| "concept": "`strip`は`Character.isWhitespace`が空白と認識する先頭・末尾のコードポイントを除いた新しい`String`を返し、元の文字列は変更しません。添字と長さはUTF-16コードユニット単位で、`.equals`は内容を、`==`は参照を比較します。", | ||
| "worked_example": "例は波括弧で囲まれた語から空白を除き、対応する区切りを確認して、`substring`で内側を取り出します。元の文字列はそのままです。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「文字列」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「文字列」演習の TODO を解かないこと。", | ||
| "文字列は比較や出力の前に文字列の整形と部分抽出を行うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "別々に作られた2つの文字列内容を`==`で比較することです。", | ||
| "対応する囲み記号か確認せず、最初と最後のコードユニットを削ることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「文字列」を適用してから `example:` 出力へつながりますか。", | ||
| "「文字列」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`substring`の終端が`length()`ではなく`length() - 1`になるのはなぜですか。", | ||
| "サロゲートペアで表される文字に対し、UTF-16の`length`は何を数えますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「文字列」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "入力を`strip`し、対応する`[]`または`{}`を1組だけ除いて、Unicodeの内側を保ってください。", | ||
| "objective": "`String`を変更せず、参照同一性にも頼らず、囲まれた文字列を正規化します。", | ||
| "language_delta": "JavaとJavaScriptはUTF-16コードユニットで添字を扱いますが、Pythonの文字列添字はUnicodeコードポイント単位です。", | ||
| "prediction_prompt": "韓国語を角括弧で囲んだ入力について、`strip`後の長さと`substring`境界を追ってください。", | ||
| "transfer_trap": "UTF-16コードユニットもUnicodeコードポイントも、利用者が目で認識する書記素と常に同じではありません。" | ||
| }, | ||
| "java-control-flow": { | ||
| "title": "制御フロー", | ||
| "concept": "制御フローは分岐とループがどの値を累積するかを決めるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「制御フロー」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "分岐と境界付きループ", | ||
| "concept": "`for`ループは両端を含む数値範囲を制御し、`if`は奇数だけを選びます。Javaでは分岐条件が`boolean`でなければならないため、余りとの比較を明示する必要があります。", | ||
| "worked_example": "カウンターは`1`から`7`まで進み、`2`で割った余りが`0`でない値だけが合計へ入ります。結果はラベル付きの奇数和です。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「制御フロー」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「制御フロー」演習の TODO を解かないこと。", | ||
| "制御フローは分岐とループがどの値を累積するかを決めるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "条件を`< limit`にし、上限が奇数の場合にその値を黙って落とすことです。", | ||
| "数値を直接`if`条件へ置き、Javaにも数値真偽変換があると考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「制御フロー」を適用してから `example:` 出力へつながりますか。", | ||
| "「制御フロー」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "上限が`6`なら、どのループ値が合計へ寄与しますか。", | ||
| "この演習でループ条件に`<`ではなく`<=`を使うのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「制御フロー」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "包含範囲を反復し、奇数だけを累積してから合計を出力してください。", | ||
| "objective": "上限が`0`または正の場合に、ループ境界、偶奇分岐、累積値を正しく連携させます。", | ||
| "language_delta": "`0`と非`0`を条件へ変換する言語と違い、Javaの条件式は`boolean`型でなければなりません。", | ||
| "prediction_prompt": "上限が`3`のときに訪れる全値を列挙し、合計を変える値へ印を付けてください。", | ||
| "transfer_trap": "他言語の範囲補助機能は末尾を既定で含まない場合があります。このJavaループでは両端を含む境界を明示しています。" | ||
| }, | ||
| "java-methods": { | ||
| "title": "メソッド", | ||
| "concept": "メソッドは再利用する計算に名前付きの境界が必要なときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「メソッド」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-enum-switch": { | ||
| "title": "網羅的な`enum`と`switch`式", | ||
| "concept": "`enum`は有限個の列挙定数を定義し、各定数は一意のインスタンスです。`switch`式は矢印規則で各定数を値へ写せるため、意図しないフォールスルーがありません。", | ||
| "worked_example": "例は`STARTED`と`STOPPED`を別の動作へ写し、`switch`結果を`String`へ代入してから例用の接頭辞を付けます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「メソッド」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「メソッド」演習の TODO を解かないこと。", | ||
| "メソッドは再利用する計算に名前付きの境界が必要なときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`default`アームを加え、`enum`に定数が追加されたときの未処理を隠すことです。", | ||
| "矢印形式のケースが、次のケース本文まで続けて実行されると思うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「メソッド」を適用してから `example:` 出力へつながりますか。", | ||
| "「メソッド」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`default`なしで`enum`定数のアームが欠けると、コンパイラは何を報告しますか。", | ||
| "1つの状態に対し、`switch`式が複数の矢印規則を実行しますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「メソッド」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "`switch`式の網羅性を保ったまま、各課題状態から文字列への対応を修正してください。", | ||
| "objective": "可変な結果変数やフォールスルーを使わず、閉じた`enum`状態を文字列へ変換します。", | ||
| "language_delta": "Javaの`switch`式は値を`yield`できます。代入と`break`が必要だった古い文形式とは異なります。", | ||
| "prediction_prompt": "`BLOCKED`が入る正確なアームを選び、その後のアームが動くか判断してください。", | ||
| "transfer_trap": "`enum`の網羅性は任意の文字列を安全にしません。未知名を`valueOf`へ渡すと依然として拒否されます。" | ||
| }, | ||
| "java-input": { | ||
| "title": "入力の解析", | ||
| "concept": "入力の解析はstdin のバイト列を型付きトークンに変えるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「入力の解析」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-methods": { | ||
| "title": "チェックポイント:型付き静的メソッド", | ||
| "concept": "メソッドシグネチャは仮引数型と返り値型を定め、Javaは各実引数の値を新しい仮引数変数へ渡します。このチェックポイントでは、`main`に掛け算を重複させず、型付きの`static`メソッド1つに計算をまとめます。", | ||
| "worked_example": "計算は`area(int, int)`に置かれ、`main`は別の例寸法を渡します。返り値がメソッド境界を越えてから整形されます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「入力の解析」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「入力の解析」演習の TODO を解かないこと。", | ||
| "入力の解析はstdin のバイト列を型付きトークンに変えるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "名前付きメソッドを完成させず、`main`内で同じ掛け算を重複することです。", | ||
| "呼び出し元とメソッドの変数が同じオブジェクトを指せば、メソッドが呼び出し元の参照変数自体も置き換えられると考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「入力の解析」を適用してから `example:` 出力へつながりますか。", | ||
| "「入力の解析」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`area`はどの返り値型と2つの仮引数型を宣言していますか。", | ||
| "`main`で掛け算を繰り返さず`area`を呼ぶべきなのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「入力の解析」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "型付きの面積メソッドを完成させ、標準入力の解析と出力整形は`main`へ残してください。", | ||
| "objective": "全処理を入口の`main`へ埋め込まず、再利用可能で値を返す境界を示します。", | ||
| "language_delta": "Javaは仮引数型と返り値型をコンパイル時に検査し、この`static`メソッドは`Solution`オブジェクトを作らずに呼び出せます。", | ||
| "prediction_prompt": "解析した2整数が仮引数へ入り、`return`を通って`println`へ戻るまでを追ってください。", | ||
| "transfer_trap": "オブジェクトを渡す場合もコピーされるのは参照値であり、メソッドが呼び出し元の変数を置き換えられるわけではありません。" | ||
| }, | ||
| "java-arrays-collections": { | ||
| "title": "配列とコレクション", | ||
| "concept": "配列とコレクションは配列は長さが固定された値のまとまりで、List、Map、Set はそれぞれ増える順序、キー検索、一意性を扱うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「配列とコレクション」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-overloading-varargs": { | ||
| "title": "オーバーロードと可変長引数配列", | ||
| "concept": "オーバーロードは1つのメソッド名へ複数シグネチャを与えます。コンパイラが適用可能なシグネチャを選び、`int...`仮引数はメソッド本文では`int[]`です。", | ||
| "worked_example": "可変長引数側のオーバーロードが2点を合計し、その整数を固定引数個数の整形処理へ渡します。2回目の呼び出しは可変長引数でないシグネチャを選びます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「配列とコレクション」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「配列とコレクション」演習の TODO を解かないこと。", | ||
| "配列とコレクションは配列は長さが固定された値のまとまりで、List、Map、Set はそれぞれ増える順序、キー検索、一意性を扱うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "数値の合計が必要なのに、可変長引数要素数を返すことです。", | ||
| "実際の配列内容を見て、実行時にオーバーロード解決が行われると思うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「配列とコレクション」を適用してから `example:` 出力へつながりますか。", | ||
| "「配列とコレクション」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`label(name, total)`が2実引数のオーバーロードを選ぶのはなぜですか。", | ||
| "名前の後に`0`が1つある呼び出しでは、可変長引数メソッドへどの配列が届きますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「配列とコレクション」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "解析済み点数配列を合計し、その結果を書式用オーバーロードへ委譲してください。", | ||
| "objective": "コンパイル時のオーバーロード解決と、可変引数個数が配列になる性質を両方観察します。", | ||
| "language_delta": "JavaScriptの残余引数と違い、Javaの可変長引数は静的な適用可否とオーバーロード順位に参加します。", | ||
| "prediction_prompt": "出力を計算する前に、例の2つの呼び出しがそれぞれ選ぶオーバーロードを特定してください。", | ||
| "transfer_trap": "広すぎるオーバーロードを追加すると、各実装が単独では正しくても呼び出しが曖昧になる場合があります。" | ||
| }, | ||
| "java-classes-objects": { | ||
| "title": "クラスとオブジェクト", | ||
| "concept": "クラスとオブジェクトはオブジェクトが状態と振る舞いを一緒に持つときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「クラスとオブジェクト」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-packages-imports": { | ||
| "title": "インポートと完全修飾名", | ||
| "concept": "インポートを使うとソース内で単純型名を書けます。同じコンパイル単位で、インポートした`ArrayList`を完全修飾名`java.util.List`の変数へ代入することもできます。", | ||
| "worked_example": "宣言型は`List`インターフェースを公開し、コンストラクターはインポートされた`ArrayList`を使います。格納語を結合するため、コレクションが実際に利用されていることも分かります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「クラスとオブジェクト」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「クラスとオブジェクト」演習の TODO を解かないこと。", | ||
| "クラスとオブジェクトはオブジェクトが状態と振る舞いを一緒に持つときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "インポートがライブラリをダウンロードしたり初期化コードを実行したりすると考えることです。", | ||
| "同じ単純名の2型をインポートし、Javaが自動で選ぶと期待することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「クラスとオブジェクト」を適用してから `example:` 出力へつながりますか。", | ||
| "「クラスとオブジェクト」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`ArrayList`のインポートを除いた場合、どのソース表記なら動きますか。", | ||
| "`String`をインポートや`java.lang.String`なしで書けるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「クラスとオブジェクト」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "完全修飾された`List`へインポート済み実装を使ってトークンを追加し、区切りなしで結合してください。", | ||
| "objective": "名前空間上の便宜と型の利用可否を区別し、実際のコレクションを観察します。", | ||
| "language_delta": "Javaのインポートはコンパイル単位の名前解決を変えます。Pythonモジュールの実行やJavaScriptモジュール読み込みとは異なります。", | ||
| "prediction_prompt": "例の単純型名と完全修飾型名を、実行前に全てパッケージへ解決してください。", | ||
| "transfer_trap": "ワイルドカードインポートは下位パッケージを含まず、曖昧な単純型名を解決することもできません。" | ||
| }, | ||
| "java-constructors": { | ||
| "title": "コンストラクタ", | ||
| "concept": "コンストラクタは新しいオブジェクトが正しいフィールド値で始まるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「コンストラクタ」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-arrays-collections": { | ||
| "title": "コレクションごとの観察値", | ||
| "concept": "配列は長さ固定で、ジェネリックな`List`、`Map`、`Set`はそれぞれ異なるコレクション動作を提供します。ストリームは元のコレクションを変更せず、それらのビューから集計値を導けます。", | ||
| "worked_example": "各配列値をリスト、頻度`Map`、一意集合へ入れます。合計は`Map`のキーと出現回数から再構築され、全出力フィールドが対応する構造を観察します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「コンストラクタ」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「コンストラクタ」演習の TODO を解かないこと。", | ||
| "コンストラクタは新しいオブジェクトが正しいフィールド値で始まるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`Map`エントリーから導く合計が必要なのに、配列の長さを出力することです。", | ||
| "重複時にリストの大きさ、`Set`の一意数、`Map`キー数を混同することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「コンストラクタ」を適用してから `example:` 出力へつながりますか。", | ||
| "「コンストラクタ」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "入力`2 2 3`について、ループ後の各コレクション内容は何ですか。", | ||
| "`Map`エントリーのストリームで合計を計算する終端操作はどれですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「コンストラクタ」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "3つのコレクション表示を作り、最初のレポート値は元配列でなく頻度から計算してください。", | ||
| "objective": "重複へ反応する1つのレポートで、3種類のコレクション意味を説明します。", | ||
| "language_delta": "Java配列は実行時にも要素型を持つ固定長オブジェクトですが、ジェネリックコレクションの要素型は主に静的に検査され、実行時には大部分が消去されます。", | ||
| "prediction_prompt": "重複入力について`List`、`Map`、`Set`の状態を描いてから、3出力フィールドを計算してください。", | ||
| "transfer_trap": "ストリームパイプラインは終端操作まで遅延し、1度消費したストリームを再利用できません。" | ||
| }, | ||
| "java-encapsulation": { | ||
| "title": "カプセル化", | ||
| "concept": "カプセル化は呼び出し側がフィールドを直接触らずメソッドで状態を変えるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「カプセル化」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-generics": { | ||
| "title": "ジェネリックで要素型を保つ", | ||
| "concept": "型仮引数は入力と出力の型を結びます。`<T> T last(List<T>)`は受け取ったリストと同じ要素型を返し、生の型はその有用な検査を失います。", | ||
| "worked_example": "色のリストから型推論が`String`を選びます。補助関数は最後の位置を選び、明示的キャストなしで`String`を返します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「カプセル化」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「カプセル化」演習の TODO を解かないこと。", | ||
| "カプセル化は呼び出し側がフィールドを直接触らずメソッドで状態を変えるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "生の`List`を使い、防げる型エラーを実行時まで遅らせることです。", | ||
| "添字`0`を使い、複数要素入力で先頭を返すことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「カプセル化」を適用してから `example:` 出力へつながりますか。", | ||
| "「カプセル化」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "同じ補助関数へ`List<Integer>`を渡すと`T`は何になりますか。", | ||
| "`size()`から`1`を引く前に、どの事前条件が必要ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「カプセル化」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "リストの要素型を保ちながら、再利用可能な最後の要素選択を完成してください。", | ||
| "objective": "1つの型変数で、呼び出し元がキャストなしに頼れる入出力関係を表します。", | ||
| "language_delta": "Javaジェネリックは通常型消去を使い、実行時型を持つ配列とは異なります。それでもコンパイル時の型関係は多くの誤呼び出しを防ぎます。", | ||
| "prediction_prompt": "各単語リストについて、メソッドの`T`と選択される添字を推論してください。", | ||
| "transfer_trap": "型消去は生のコレクションを安全にする仕組みではありません。ジェネリック表現の多くを実行時から除くだけです。" | ||
| }, | ||
| "java-static-members": { | ||
| "title": "static メンバー", | ||
| "concept": "static メンバーは値が一つのインスタンスではなくクラスに属するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「static メンバー」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-streams-lambdas": { | ||
| "title": "遅延するストリームパイプライン", | ||
| "concept": "ストリームは終端操作が結果を要求するまで、要素処理を記述するだけです。ここでは`IntStream`のラムダで偶数を選び、`IntStream.map`で平方し、`sum`が1回だけ走査します。", | ||
| "worked_example": "`IntStream.of`が例用の2値を作り、述語が偶数を残し、対応付けで平方してから終端合計へ渡します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「static メンバー」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「static メンバー」演習の TODO を解かないこと。", | ||
| "static メンバーは値が一つのインスタンスではなくクラスに属するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`filter`と`map`を組んでも終端操作を呼ばず、処理が動いたと思うことです。", | ||
| "`sum`で消費済みの同じストリームオブジェクトをもう一度使うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「static メンバー」を適用してから `example:` 出力へつながりますか。", | ||
| "「static メンバー」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "例の述語と対応付けが実際に評価されるのはいつですか。", | ||
| "`0`は偶数`filter`を通るのに、平方合計へ何も加えないのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「static メンバー」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "偶数だけを残して平方し、解析済み列から1つの終端合計を作ってください。", | ||
| "objective": "遅延する中間操作を、観察できる数値結果へつなぎます。", | ||
| "language_delta": "Javaストリームは1度だけ使う走査パイプラインであり、再利用可能なコレクションコンテナやJavaScript配列ではありません。", | ||
| "prediction_prompt": "負数を含むケースについて、`filter`後と`map`後の列をそれぞれ書いてください。", | ||
| "transfer_trap": "ストリームの遅延性や出現順序は並列実行を意味しません。並列性は別に選択する機能です。" | ||
| }, | ||
| "java-enum-switch": { | ||
| "title": "enum と switch", | ||
| "concept": "enum と switchは固定された状態集合を明示的な結果へ変えるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「enum と switch」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-comparators-sorting": { | ||
| "title": "比較器を段階的に合成する", | ||
| "concept": "`comparingInt`、`reversed`、`thenComparing`は各順序規則を直接表します。極端な整数でオーバーフローする可能性がある減算比較を避けられます。", | ||
| "worked_example": "`Kai`と`Bea`は同点なので、第2条件の名前昇順で`Bea`が先になります。例はソート後の先頭レコードを読みます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「enum と switch」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「enum と switch」演習の TODO を解かないこと。", | ||
| "enum と switchは固定された状態集合を明示的な結果へ変えるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "合成済み比較器全体を反転し、名前の同点条件まで降順にすることです。", | ||
| "`right.score() - left.score()`を返し、整数オーバーフローで順序契約を壊すことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「enum と switch」を適用してから `example:` 出力へつながりますか。", | ||
| "「enum と switch」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "同点の2人を決める比較器段階はどれですか。", | ||
| "負の点数でも1人だけのリストなら、その人が先頭になるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「enum と switch」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "同点の入力も含めて点数降順と名前昇順を合成し、先頭の利用者を出力してください。", | ||
| "objective": "安定した業務上の順序を、読みやすい比較器合成として表します。", | ||
| "language_delta": "Javaの比較器オブジェクトは順序を明示して再利用できます。`<`のような演算子だけで任意レコードをソートすることはできません。", | ||
| "prediction_prompt": "3人ケースを、比較器の各段階を1つずつ適用して手で並べてください。", | ||
| "transfer_trap": "`reversed()`はその時点までに構築した比較器へ作用するため、呼ぶ位置で同点処理が変わります。" | ||
| }, | ||
| "java-exceptions": { | ||
| "title": "例外", | ||
| "concept": "例外は不正な入力を無関係な失敗まで隠さず回復するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「例外」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-classes-objects": { | ||
| "title": "インスタンスごとに所有するオブジェクト状態", | ||
| "concept": "各`Counter`オブジェクトは独立した`value`フィールドを持ちます。`add`メソッドは局所仮引数`delta`を受け取り、`this`または曖昧でないフィールド名を通してレシーバーのフィールドを更新する必要があります。", | ||
| "worked_example": "1つの`Counter`が`10`から始まり、`add`へ差分を渡します。メソッドは`this`インスタンスのフィールドを変更し、`main`から`12`を観察できます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「例外」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「例外」演習の TODO を解かないこと。", | ||
| "例外は不正な入力を無関係な失敗まで隠さず回復するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "仮引数変数だけを変更し、オブジェクトフィールドも自動で変わると期待することです。", | ||
| "全状態を`static`にし、本来独立なインスタンス同士で1つの値を共有することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「例外」を適用してから `example:` 出力へつながりますか。", | ||
| "「例外」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`add`内で変わる保存場所と、実引数を運ぶだけの局所変数はどれですか。", | ||
| "異なる初期値を持つ2つの`Counter`インスタンスを作ると、それぞれの値はどう変わりますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「例外」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "解析した差分を、レシーバー自身の状態を更新する`Counter`メソッドへ渡してください。", | ||
| "objective": "最小のクラスインスタンスを通して、同一性とカプセル化された変更を観察します。", | ||
| "language_delta": "Javaのインスタンスフィールドは各オブジェクトに属し、メソッドの仮引数は呼び出しごとに作られる局所変数です。", | ||
| "prediction_prompt": "メソッド呼び出し前後で、`Counter`フィールドと`delta`仮引数を別々に追跡してください。", | ||
| "transfer_trap": "`private`アクセスは外部からの読み書きを防ぎますが、不変条件を守るのはメソッド内の処理です。" | ||
| }, | ||
| "java-generics": { | ||
| "title": "ジェネリクス", | ||
| "concept": "ジェネリクスは一つのメソッドが呼び出し側の要素型を保つときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「ジェネリクス」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-constructors": { | ||
| "title": "コンストラクターで不変条件を確立する", | ||
| "concept": "コンストラクターはクラス名と同じで返り値型を持ちません。通常終了までに全ての未初期化`final`インスタンスフィールドを代入し、同名仮引数は`this.field`で区別します。", | ||
| "worked_example": "`Rectangle`コンストラクターは2実引数を`final`の寸法へ移します。その後の`area`メソッドは両フィールドが確立済みだと仮定できます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「ジェネリクス」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「ジェネリクス」演習の TODO を解かないこと。", | ||
| "ジェネリクスは一つのメソッドが呼び出し側の要素型を保つときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`void Rectangle(...)`と書き、コンストラクターではなくメソッドを宣言することです。", | ||
| "仮引数を自分自身へ代入し、同名フィールドを未初期化のままにすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「ジェネリクス」を適用してから `example:` 出力へつながりますか。", | ||
| "「ジェネリクス」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`this.width = width`の左辺は何を指しますか。", | ||
| "`final`フィールドへの代入を省く経路をコンパイラが拒否するのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「ジェネリクス」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "両寸法をコンストラクター実引数から初期化し、`area`が妥当な`final`フィールドを使えるようにしてください。", | ||
| "objective": "仮の寸法を使わず、オブジェクト状態を構築時に1度だけ確立します。", | ||
| "language_delta": "Javaコンストラクターは特殊な宣言であり、値を返す通常の初期化関数ではありません。", | ||
| "prediction_prompt": "面積を求める前に、同名のフィールドと仮引数を2つの代入でそれぞれ解決してください。", | ||
| "transfer_trap": "`final`フィールドは構築後の再代入を防ぎますが、参照先の値を再帰的に凍結しません。" | ||
| }, | ||
| "java-interfaces": { | ||
| "title": "インターフェイス", | ||
| "concept": "インターフェイスは呼び出し側が具象クラスでなく振る舞いの契約に依存するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「インターフェイス」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-encapsulation": { | ||
| "title": "`private`状態と明示的な規則", | ||
| "concept": "`private`フィールドは無関係なコードから内部表現を隠します。ただし公開操作は要求された変更を自ら検証する必要があり、可視性と不変条件は別の問題です。", | ||
| "worked_example": "`Account`は2つの正の入金を受け入れ、負の要求を無視します。アクセサーからは結果の整数残高だけを公開します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「インターフェイス」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「インターフェイス」演習の TODO を解かないこと。", | ||
| "インターフェイスは呼び出し側が具象クラスでなく振る舞いの契約に依存するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`private`を付ければ、メソッドへ渡された不正値も自動で拒否されると考えることです。", | ||
| "可変な内部コレクションを直接返し、意図した境界を迂回させることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「インターフェイス」を適用してから `example:` 出力へつながりますか。", | ||
| "「インターフェイス」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "例のどの呼び出しが残高を変更しませんか。", | ||
| "`Account`内部のコードなら、`private`フィールドへ不正値を代入できますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「インターフェイス」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "正数だけを受け入れる規則を`deposit`内へ置き、状態はアクセサーからだけ報告してください。", | ||
| "objective": "状態遷移を所有するメソッドで、小さな不変条件を強制します。", | ||
| "language_delta": "Javaのアクセス修飾子はコンパイル時と実行時のメンバーアクセス規則であり、ドメイン検証を自動生成しません。", | ||
| "prediction_prompt": "解析された各差分を順に適用し、ガードが受理するかを記録してください。", | ||
| "transfer_trap": "取得メソッドが可変な内部オブジェクトを返すとカプセル化を漏らします。必要なら安全な表示かコピーを返してください。" | ||
| }, | ||
| "java-inheritance-composition": { | ||
| "title": "継承と合成", | ||
| "concept": "継承と合成はサブクラスが振る舞いを完成させるため補助オブジェクトも使うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「継承と合成」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-static-members": { | ||
| "title": "クラスが所有する静的定数", | ||
| "concept": "静的フィールドはクラスに属し、全呼び出しとインスタンスで共有されます。`static final`は束縛を固定しますが、その参照先オブジェクトが可変な場合まで不変にはしません。", | ||
| "worked_example": "倍率計算メソッドはクラスコンテキストから`FACTOR`を読み、例値へ掛けます。どちらの静的メンバーへアクセスするにもインスタンスは不要です。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「継承と合成」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「継承と合成」演習の TODO を解かないこと。", | ||
| "継承と合成はサブクラスが振る舞いを完成させるため補助オブジェクトも使うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "静的ユーティリティを呼ぶためだけにオブジェクトを作り、クラス所有であることを不明瞭にすることです。", | ||
| "全てのフィールド型について`final`が深い不変性を約束すると考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「継承と合成」を適用してから `example:` 出力へつながりますか。", | ||
| "「継承と合成」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`Solution`オブジェクトを10個作っても、`FACTOR`の保存場所はいくつですか。", | ||
| "`scale`メソッドはインスタンスを受け取らずにインスタンスフィールドを読めますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「継承と合成」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "共有係数を修正し、静的な倍率計算メソッドが正、`0`、負の入力を扱えるようにしてください。", | ||
| "objective": "クラス単位状態を意図的に使い、インスタンスごとのフィールドと区別します。", | ||
| "language_delta": "Javaの`static`はメンバーを宣言クラスへ結び付けます。包含型を持たないモジュール単位束縛とは異なります。", | ||
| "prediction_prompt": "符号付き各入力について、クラス係数を掛け算へ代入して結果を求めてください。", | ||
| "transfer_trap": "可変な静的フィールドは全インスタンス間の共有状態になり、並行利用では同期が必要になる場合があります。" | ||
| }, | ||
| "java-records": { | ||
| "title": "record", | ||
| "concept": "recordは不変データキャリアの定型コードを減らすときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「record」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-interfaces": { | ||
| "title": "インターフェースを通して振る舞いを呼ぶ", | ||
| "concept": "インターフェースは公開された振る舞いの契約へ名前を付けます。`Named`を受け取るコードは、実装がレコードかクラスかを知らなくても`name()`を呼べます。", | ||
| "worked_example": "`User`レコードはアクセサーにより`Named`を実装します。描画処理はインターフェースだけを受け取り、ロケールに依存しない大文字化を行います。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「record」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「record」演習の TODO を解かないこと。", | ||
| "recordは不変データキャリアの定型コードを減らすときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "インターフェースメソッドが公開なのに、実装メソッドの可視性を狭めることです。", | ||
| "描画処理を`User`型へ限定し、1つの実装へ不要に結合することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「record」を適用してから `example:` 出力へつながりますか。", | ||
| "「record」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`Named`として宣言された変数から、どのメソッドを呼べますか。", | ||
| "別クラスも`User`を継承せず、この契約へ参加できますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「record」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "インターフェースを受け取る補助関数を通して、名前の大文字変換を実装してください。", | ||
| "objective": "具体的な表現ではなく、共通の契約に対して振る舞いを呼びます。", | ||
| "language_delta": "Javaインターフェースは公称的で、クラスが実装を宣言します。TypeScriptの構造的互換性とは異なります。", | ||
| "prediction_prompt": "`Named`参照からレコードアクセサーへ進み、`Locale.ROOT`による変換までを追ってください。", | ||
| "transfer_trap": "既定値インターフェースメソッドは振る舞いを提供できますが、インターフェースフィールドをインスタンスごとの状態にはしません。" | ||
| }, | ||
| "java-optional": { | ||
| "title": "Optional", | ||
| "concept": "Optionalは存在しない値を明示的に扱うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「Optional」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-inheritance-composition": { | ||
| "title": "継承と合成を組み合わせる", | ||
| "concept": "継承は置換可能な「〜である」関係、合成は「〜を持つ」関係を表します。`Player`は基底の点数状態を再利用しながら、独立した`Bonus`値を所有します。", | ||
| "worked_example": "構築時に基底実引数を`super`へ渡し、追加点用レコードを別に作ります。`total`は両方を読んで`12`を返します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「Optional」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「Optional」演習の TODO を解かないこと。", | ||
| "Optionalは存在しない値を明示的に扱うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "合成されたフィールドを無視し、継承した状態だけを返すことです。", | ||
| "サブタイプが基底型の期待を守れないのに、コード再利用だけのため継承することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「Optional」を適用してから `example:` 出力へつながりますか。", | ||
| "「Optional」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`protected`な基底フィールドを初期化するコンストラクターはどれですか。", | ||
| "`Bonus`を親クラスではなくフィールドとして持つのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「Optional」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "`total`メソッドで、継承した基底値と合成した追加点を合計してください。", | ||
| "objective": "2つの再利用機構を、その意味的な関係に従って分けます。", | ||
| "language_delta": "Javaクラスが持てる親クラスは1つですが、インターフェースは複数実装できるため、独立した部品には合成が特に有用です。", | ||
| "prediction_prompt": "2つのコンストラクター実引数が別々のオブジェクトへ入る経路を追ってから、最終合計を求めてください。", | ||
| "transfer_trap": "`protected`可視性があっても、任意の「〜を持つ」関係が妥当なサブタイプになるわけではありません。" | ||
| }, | ||
| "java-streams-lambdas": { | ||
| "title": "Stream とラムダ", | ||
| "concept": "Stream とラムダはコレクション処理をパイプラインとして表すときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「Stream とラムダ」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-exceptions": { | ||
| "title": "狭い例外境界で回復する", | ||
| "concept": "`Integer.parseInt`は不正な整数構文を非検査例外`NumberFormatException`で報告します。無関係な不具合を不正入力と取り違えないよう、解析境界でこの例外だけを捕捉します。", | ||
| "worked_example": "`oops`の解析だけが狭い`catch`へ入り、呼び出し元指定の代替値を返します。その他の予期しない実行時不具合は、この補助関数に隠されません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「Stream とラムダ」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「Stream とラムダ」演習の TODO を解かないこと。", | ||
| "Stream とラムダはコレクション処理をパイプラインとして表すときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`Exception`全体を捕捉し、無関係なプログラミングエラーまで隠すことです。", | ||
| "不正文字列に対し、`parseInt`が`Optional`や番兵値を返すと期待することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「Stream とラムダ」を適用してから `example:` 出力へつながりますか。", | ||
| "「Stream とラムダ」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "有効な負の整数は`catch`ブロックへ入りますか。", | ||
| "ここで全ての`Exception`ではなく`NumberFormatException`だけを捕捉する方が安全なのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「Stream とラムダ」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "数値構文が不正な場合だけ指定代替値を返し、それ以外は解析済み値を返してください。", | ||
| "objective": "無関係な失敗を隠さず、最小の関係ある例外境界で回復します。", | ||
| "language_delta": "Javaでは処理または宣言が必要な検査対象例外と、`NumberFormatException`のような非検査`RuntimeException`を区別します。", | ||
| "prediction_prompt": "各候補トークンについて、代替値を評価する前に`try`と`catch`のどちらから`return`するか選んでください。", | ||
| "transfer_trap": "有効な負の整数はデータであり、エラーではありません。不正な数値構文だけが代替値経路を選びます。" | ||
| }, | ||
| "java-comparators-sorting": { | ||
| "title": "Comparator とソート", | ||
| "concept": "Comparator とソートはオブジェクトにデータ形状とは別の順序が必要なときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「Comparator とソート」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-optional": { | ||
| "title": "`Optional`の`filter`と代替値", | ||
| "concept": "`Optional`は返り値が不在かもしれないことを表します。`filter`は述語を満たす存在値だけを残し、`orElse`は危険な`get()`なしに代替値を供給します。", | ||
| "worked_example": "`Optional`内の語`cat`は3文字以内という長さ条件を満たすため、代替値は選ばれません。出力ラベルで例行を区別しています。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「Comparator とソート」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「Comparator とソート」演習の TODO を解かないこと。", | ||
| "Comparator とソートはオブジェクトにデータ形状とは別の順序が必要なときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "存在を証明せず`get()`を呼び、`NoSuchElementException`を起こすことです。", | ||
| "不在を表す返り値以外にも、フィールドや仮引数へ無差別に`Optional`を使うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「Comparator とソート」を適用してから `example:` 出力へつながりますか。", | ||
| "「Comparator とソート」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "既に空な`Optional`へ`filter`すると、述語は呼ばれますか。", | ||
| "ダッシュ番兵値のケースで`missing`を作るのはどの経路ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「Comparator とソート」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "ダッシュ番兵値を不在にし、存在するトークンは3文字以内のものだけを残して、宣言的に代替値を選んでください。", | ||
| "objective": "安全でない値の取り出しをせず、不在入力と条件を満たす値あり文字列を区別します。", | ||
| "language_delta": "`Optional`は不在処理用メソッドを持つ明示的コンテナであり、処理経路を強制しない`null`許容参照とは異なります。", | ||
| "prediction_prompt": "4つのケースそれぞれで、構築直後と`filter`後の`Optional`が値ありか空かを記してください。", | ||
| "transfer_trap": "`Optional`は診断情報を伴う失敗のための例外を置き換えるものではありません。" | ||
| }, | ||
| "java-try-with-resources": { | ||
| "title": "try-with-resources", | ||
| "concept": "try-with-resourcesはコードが早く抜けてもリソースを閉じるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「try-with-resources」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "観察可能な`try-with-resources`", | ||
| "concept": "`try-with-resources`宣言は、正常終了でも途中終了でも`AutoCloseable`値をクローズします。宣言したリソースから実際に読むことで、寿命管理を飾りではなくデータ経路へ結び付けます。", | ||
| "worked_example": "管理対象の`ByteArrayInputStream`には例バイトがあります。ブロック内で読み、UTF-8でデコードし、`strip`して大文字化してから自動クローズします。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「try-with-resources」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「try-with-resources」演習の TODO を解かないこと。", | ||
| "try-with-resourcesはコードが早く抜けてもリソースを閉じるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "リソースを宣言しながら、別の追跡されていないストリームから読み続けることです。", | ||
| "本文が既に例外を送出した場合、クローズ失敗が抑制された例外として付くことを忘れることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「try-with-resources」を適用してから `example:` 出力へつながりますか。", | ||
| "「try-with-resources」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "例のブロック後に`close()`が保証されるオブジェクトはどれですか。", | ||
| "空のバイト列は出力前にどの文字列へ変えるべきですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「try-with-resources」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "管理ストリームから実際に読んだバイトを正規化し、空内容には定義済みの名前を与えてください。", | ||
| "objective": "確実なリソース寿命を、標準出力へ届くデータ経路と結び付けます。", | ||
| "language_delta": "Javaは一般オブジェクトをガベージコレクションしますが、`try-with-resources`は外部リソースの確実な後始末を制御フローへ明示します。", | ||
| "prediction_prompt": "入力バイトがリソース構築、読み取り、デコード、`strip`、大文字化を通る順を追ってください。", | ||
| "transfer_trap": "ガベージコレクションはファイル、ソケットなど外部リソースを適時クローズする代わりにはなりません。" | ||
| }, | ||
| "java-packages-imports": { | ||
| "title": "package と import", | ||
| "concept": "package と importは単一ファイル演習でも JDK パッケージのクラスを使うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「package と import」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-equality-hashcode": { | ||
| "title": "等値性とハッシュの整合性", | ||
| "concept": "論理的等値性には反射性、対称性、推移性、一貫性、`null`安全性が必要です。ハッシュコレクションを正しく動かすには、等しい2オブジェクトが必ず同じハッシュコードを持たなければなりません。", | ||
| "worked_example": "レコードはコンポーネントに基づく`equals`と`hashCode`を自動生成するため、同じ2つの`Point`は`HashSet`で1エントリーにまとまります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「package と import」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「package と import」演習の TODO を解かないこと。", | ||
| "package と importは単一ファイル演習でも JDK パッケージのクラスを使うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "一方の座標だけを比べ、異なる点を等しいと判定することです。", | ||
| "`equals`をオーバーライドしながら、等しいオブジェクトで異なる値を返し得る`hashCode`を残すことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「package と import」を適用してから `example:` 出力へつながりますか。", | ||
| "「package と import」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "等しくないオブジェクトは同じハッシュコードでもよく、等しいオブジェクトは異なってはいけないのはなぜですか。", | ||
| "パターン`instanceof Point other`は型検査とキャストをどう組み合わせますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「package と import」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "両座標を`equals`へ参加させ、等値なら同ハッシュという規則を保ち、`HashSet`の大きさを観察してください。", | ||
| "objective": "ハッシュコレクション内でも正しく動く値契約を実装します。", | ||
| "language_delta": "Javaの既定`Object.equals`は同一性比較ですが、レコードはコンポーネント値意味論を自動生成します。", | ||
| "prediction_prompt": "各`Point`組を等しいか分類してから、`HashSet`が2つ目を追加するか判断してください。", | ||
| "transfer_trap": "ハッシュ分布の良さは性能を改善しますが、等しいオブジェクトが同じハッシュを持つことは正しさの必須条件です。" | ||
| }, | ||
| "java-annotations": { | ||
| "title": "アノテーション", | ||
| "concept": "アノテーションはメタデータを実行ロジックでなく宣言に付けるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「アノテーション」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-records": { | ||
| "title": "チェックポイント:レコードの防御的スナップショット", | ||
| "concept": "レコードはコンポーネント参照を`final`にして値メソッドを生成しますが、不変性は浅いものです。コンパクトコンストラクター内の`List.copyOf`は可変ソースから切り離し、`null`要素も拒否します。", | ||
| "worked_example": "コンパクトコンストラクターが`[4, 5]`をコピーします。その後元リストへ値を加えても、レコード内リストの合計は`9`のままです。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「アノテーション」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「アノテーション」演習の TODO を解かないこと。", | ||
| "アノテーションはメタデータを実行ロジックでなく宣言に付けるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "呼び出し元所有の可変`List`を直接保存しながら、レコードを深く不変だと呼ぶことです。", | ||
| "値意味論へ無関係な可変状態を加え、`equals`とハッシュの一貫性を壊すことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「アノテーション」を適用してから `example:` 出力へつながりますか。", | ||
| "「アノテーション」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "コンパクトコンストラクター内でコピーへ再代入される参照はどれですか。", | ||
| "元リストと同じ参照を保持すると、ソースから要素を除いたときコンポーネントも変わるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「アノテーション」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "ソースの最後の要素が除かれる前にリストコンポーネントを防御的にコピーし、保持したスナップショットを合計してください。", | ||
| "objective": "レコードの浅い不変性を示し、不変コピーでエイリアス境界を修復します。", | ||
| "language_delta": "Javaレコードは簡潔な公称的データであり、再帰的に凍結されたオブジェクトグラフでもTypeScriptの構造型でもありません。", | ||
| "prediction_prompt": "`List.copyOf`の前後でソースとコンポーネントの参照を描き、その後ソース末尾を除いてください。", | ||
| "transfer_trap": "`final`コンポーネントは再代入を防ぎますが、防御的コピーなしに保存した可変オブジェクトの変更は防ぎません。" | ||
| }, | ||
| "java-sealed-classes": { | ||
| "title": "sealed クラス", | ||
| "concept": "sealed クラスはドメイン階層が許可された実装を列挙するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「sealed クラス」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-annotations": { | ||
| "title": "実行時に読めるアノテーションメタデータ", | ||
| "concept": "`@Target(METHOD)`は配置先をメソッドへ制限し、`@Retention(RUNTIME)`はリフレクション用にメタデータを実行時まで残します。実行時保持方針がなければ`getAnnotation`で接頭辞を読めません。", | ||
| "worked_example": "リフレクションが`normalize`メソッドを見つけ、その`trace`用`Label`を読みます。通常のメソッド呼び出しが文字列を変換し、メタデータが接頭辞を提供します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「sealed クラス」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「sealed クラス」演習の TODO を解かないこと。", | ||
| "sealed クラスはドメイン階層が許可された実装を列挙するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "実行時リフレクションが必要なのに、既定の`CLASS`保持方針へ頼ることです。", | ||
| "異なるメソッドシグネチャを検索し、対応する`Method`を得られないことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「sealed クラス」を適用してから `example:` 出力へつながりますか。", | ||
| "「sealed クラス」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "仮想マシンがアノテーションをリフレクションへ公開する保持方針はどれですか。", | ||
| "`Label`をフィールドへ置けないようにする対象は何ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「sealed クラス」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "メソッドメタデータが実行中のプログラムまで残るよう、アノテーション宣言を変更してください。", | ||
| "objective": "未使用の飾りとせず、リフレクションを通してアノテーション値を観察します。", | ||
| "language_delta": "Javaアノテーションは実行時利用のため明示的保持方針が必要です。型システムだけで使うアノテーションはコンパイル時可視性で十分な場合があります。", | ||
| "prediction_prompt": "実行時保持方針の追加前後で、`getAnnotation`がオブジェクトと`null`のどちらを返すか予測してください。", | ||
| "transfer_trap": "保持方針が決めるのは利用可能性であり振る舞いではありません。リフレクションコードが読み、適用する処理は別に必要です。" | ||
| }, | ||
| "java-testing-assert": { | ||
| "title": "テストと assert", | ||
| "concept": "テストと assertは小さな検査を壊れたメソッドの近くで失敗させるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「テストと assert」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-sealed-classes": { | ||
| "title": "`sealed`図形とパターン`switch`", | ||
| "concept": "`sealed`インターフェースは直接実装できる型を`permits`リストへ制限します。Java 21のパターン`switch`は各レコードサブタイプを分解し、網羅性を隠す`default`なしに値を`yield`できます。", | ||
| "worked_example": "`Rect`パターンが幅と高さを直接束縛し、網羅的`switch`が値を計算します。`Circle`と`Dot`も処理済みの選択肢として明示されています。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「テストと assert」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「テストと assert」演習の TODO を解かないこと。", | ||
| "テストと assertは小さな検査を壊れたメソッドの近くで失敗させるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "仮想メソッドだけを使い、他の直接サブタイプへ閉じていることを示さないことです。", | ||
| "`default`アームで、許可型が追加されたときのコンパイラによる未処理通知を隠すことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「テストと assert」を適用してから `example:` 出力へつながりますか。", | ||
| "「テストと assert」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "どの許可レコードのコンポーネントをパターンアーム内で掛けますか。", | ||
| "Java 21で`default`なしの`switch`がコンパイルできるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「テストと assert」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "`sealed`階層の網羅的`switch`を保ったまま、`Circle`レコードパターンの計算を修正してください。", | ||
| "objective": "閉じたサブタイプ情報とレコード分解を、1つの観察可能な式で使います。", | ||
| "language_delta": "`sealed`型は公称的階層を閉じます。制限のない基底クラスや構造的に推論されるユニオンとは異なります。", | ||
| "prediction_prompt": "各図形トークンについて実行時パターンを選び、コンポーネント変数を束縛してから計算してください。", | ||
| "transfer_trap": "`sealed`は`permits`規則に従い直接継承を制御しますが、インスタンスを自動的に不変にはしません。" | ||
| }, | ||
| "java-equality-hashcode": { | ||
| "title": "equals と hashCode", | ||
| "concept": "equals と hashCodeはHashSet が同じ値オブジェクトを認識するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「equals と hashCode」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "java-testing-assert": { | ||
| "title": "総合演習:常に動く明示的な検査", | ||
| "concept": "この総合演習はUTF-8解析、メソッド、レコード、ジェネリックコレクション、ストリーム、比較器、`Optional`、明示的失敗検査を統合します。Javaの`assert`は通常`-ea`なしでは無効なので、必須検査は`AssertionError`を投げる通常メソッドで行います。", | ||
| "worked_example": "同点の2レコードを名前順で並べ、補助関数が空代替値を`check`し、ラベル付きの勝者を出します。インポート、レコードアクセサー、終端操作、順序規則が全て関わります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「equals と hashCode」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「equals と hashCode」演習の TODO を解かないこと。", | ||
| "equals と hashCodeはHashSet が同じ値オブジェクトを認識するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "判定環境がアサーションを有効にしないのに、必須検証をJavaの`assert`へ置くことです。", | ||
| "点数だけをソートし、同点の勝者を出現順に任せることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「equals と hashCode」を適用してから `example:` 出力へつながりますか。", | ||
| "「equals と hashCode」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "JVMへ`-ea`がなくても、この総合演習のどの検査は必ず動きますか。", | ||
| "列挙型と`sealed`階層の閉鎖性、オーバーロードと可変長引数、インポート、ジェネリクス、ストリーム、`Comparator`、コンストラクター、カプセル化、`static`状態、インターフェース、継承と合成、`Optional`、管理リソース、等値性とハッシュ、アノテーション保持を、このプログラムの拡張へどう適用しますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「equals と hashCode」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| }, | ||
| "java-overloading-varargs": { | ||
| "title": "オーバーロードと varargs", | ||
| "concept": "オーバーロードと varargsはメソッド呼び出しがシグネチャを選び追加引数を集めるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「オーバーロードと varargs」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「オーバーロードと varargs」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「オーバーロードと varargs」演習の TODO を解かないこと。", | ||
| "オーバーロードと varargsはメソッド呼び出しがシグネチャを選び追加引数を集めるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「オーバーロードと varargs」を適用してから `example:` 出力へつながりますか。", | ||
| "「オーバーロードと varargs」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「オーバーロードと varargs」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "決定的な最良利用者選択を完成し、勝者を出力する前の明示的な空結果検査を残してください。", | ||
| "objective": "中核経路をテスト可能なレコードとコレクションプログラムへ統合し、16の演習規則を拡張時の適用判断へ結び付けます。", | ||
| "language_delta": "Pythonの最適化で消える`assert`やJavaの既定無効な`assert`と違い、通常メソッドの明示的呼び出しは通常の実行でも必ず評価されます。", | ||
| "prediction_prompt": "同点利用者を比較器の2段階で並べ、別に空リストが代替値と`check`を通る経路を追ってください。", | ||
| "transfer_trap": "アサーションは内部仮定の確認用であり、信頼できない入力の検証用ではありません。必須条件には通常の制御フローと明確な失敗を使ってください。" | ||
| } | ||
| } | ||
| } |
+363
-279
@@ -7,422 +7,506 @@ { | ||
| "java-output": { | ||
| "title": "표준 출력", | ||
| "concept": "표준 출력는 judge가 줄바꿈과 문자를 그대로 비교할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 표준 출력 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "표준 출력 한 줄 정확히 만들기", | ||
| "concept": "`System.out.print`는 인자를 쓰고 `println`은 줄도 끝낸다. 이 채점기는 `CRLF`를 `LF`로 정규화하지만 공백과 빈 줄을 포함한 다른 차이는 그대로 비교한다.", | ||
| "worked_example": "예제는 문자열과 정수로 `Mina:4`를 만들고 `print`에 넘기는 텍스트 안에 줄바꿈 하나를 명시한다. 출력 API가 별도 표시를 보태지 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 표준 출력 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 표준 출력 실습의 TODO를 풀지 않는다.", | ||
| "표준 출력는 judge가 줄바꿈과 문자를 그대로 비교할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "요구된 콜론 대신 공백이나 하이픈을 구분자로 쓴다.", | ||
| "이미 줄바꿈으로 끝나는 텍스트를 `println`에 넘겨 빈 줄을 하나 더 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 표준 출력 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "표준 출력 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`print`가 `\\n`으로 끝나는 문자열을 받으면 숫자 뒤에 무엇이 쓰이는가?", | ||
| "예제 호출을 `println`으로 바꾸어도 같은 출력이 되는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 표준 출력 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "학습자 이름과 점수를 파싱해 콜론으로 구분한 레코드를 만들고 끝에 줄바꿈 하나만 붙여 표준 출력에 써라.", | ||
| "objective": "구분자와 줄 끝을 의도적으로 선택해 불필요한 문자가 없는 표준 출력을 만든다.", | ||
| "language_delta": "Java는 내장 함수 대신 `System.out` 스트림으로 콘솔 출력을 보내며 `print`와 `println`의 줄 끝 동작을 구분한다.", | ||
| "prediction_prompt": "예제가 만드는 전체 문자 순서를 마지막 줄바꿈까지 포함해 실행 전에 적어 보라.", | ||
| "transfer_trap": "터미널에서 똑같아 보여도 줄바꿈이 없거나 두 번이면 채점 결과가 달라질 수 있다." | ||
| }, | ||
| "java-input": { | ||
| "title": "UTF-8 표준 입력 해석하기", | ||
| "concept": "`System.in`은 이미 해석된 텍스트가 아니라 바이트를 제공한다. `readAllBytes`와 `StandardCharsets.UTF_8`을 함께 사용해야 환경과 무관하게 같은 문자열을 얻는다.", | ||
| "worked_example": "예제는 `ByteArrayInputStream`을 표준 입력처럼 사용해 바이트를 명시적으로 해석하고 두 토큰을 더한다. 실습 답과 다른 표시가 붙은 합계를 출력한다.", | ||
| "common_mistakes": [ | ||
| "플랫폼 기본 문자 인코딩으로 바이트에서 문자열을 만들어 결과를 환경에 맡긴다.", | ||
| "빈 입력도 토큰 하나로 분리해 빈 문자열을 정수로 파싱한다." | ||
| ], | ||
| "self_check": [ | ||
| "`StandardCharsets`는 가져오기가 필요한데 `String`은 필요 없는 이유는 무엇인가?", | ||
| "어느 분기가 빈 입력을 `Integer.parseInt`에 넘기지 않게 하는가?" | ||
| ], | ||
| "exercise_prompt": "표준 입력 바이트 전체를 UTF-8로 해석하고 `\\s+`로 나눈 부호 있는 정수를 모두 더하되 빈 입력은 0으로 처리하라.", | ||
| "objective": "여러 종류의 공백으로 구분된 유효한 정수 토큰을 UTF-8로 읽고 빈 입력까지 합산한다.", | ||
| "language_delta": "텍스트 표준 입력을 바로 주는 실행 환경과 달리 Java는 `InputStream`을 주므로 문자 인코딩 선택이 프로그램 책임이다.", | ||
| "prediction_prompt": "여러 줄에 나뉜 정수의 합과 입력 바이트가 하나도 없을 때의 결과를 각각 예상하라.", | ||
| "transfer_trap": "가져오기는 소스에서 타입 이름을 줄일 뿐 JDK 클래스를 설치하거나 동적으로 불러오지 않는다." | ||
| }, | ||
| "java-variables-types": { | ||
| "title": "변수와 타입", | ||
| "concept": "변수와 타입는 primitive 값과 객체 참조를 쓰기 전에 선언해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 변수와 타입 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "타입이 있는 값과 `final` 바인딩", | ||
| "concept": "지역 선언은 각 값의 컴파일 시점 타입을 정한다. 기본 타입은 값을 담고 참조는 객체를 가리키며 `final`은 재대입만 막고 `static` 상태는 인스턴스가 아니라 클래스에 속한다.", | ||
| "worked_example": "예제는 이름 바인딩을 `final`로 두고 변경 가능한 정수 점수를 기준값과 비교해 결과를 불리언에 저장한 뒤 보고서를 만든다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 변수와 타입 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 변수와 타입 실습의 TODO를 풀지 않는다.", | ||
| "변수와 타입는 primitive 값과 객체 참조를 쓰기 전에 선언해야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "점수가 기준값 이상이어야 한다는 규칙인데도 두 값이 같은 경계를 실패로 처리한다.", | ||
| "`final` 참조가 도달 가능한 객체 전체를 깊게 불변으로 만든다고 생각한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 변수와 타입 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "변수와 타입 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "예제 선언 중 다시 대입할 수 있는 값과 없는 값은 각각 무엇인가?", | ||
| "점수와 기준값이 모두 0일 때 비교 결과는 어떤 불리언인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 변수와 타입 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "파싱한 이름, 점수, 기준값을 알맞은 선언 타입에 보관하고 점수가 기준값 이상인지 비교해 상태를 만든 뒤 하나의 보고서로 출력하라.", | ||
| "objective": "`String`, `int`, `boolean` 값을 연결하면서 동점 경계를 잃지 않는다.", | ||
| "language_delta": "Java 지역 타입과 `final`은 컴파일 제약이며, `final`은 고정 바인딩에 가깝고 객체의 불변성을 보장하지는 않는다.", | ||
| "prediction_prompt": "기준보다 큰 점수와 정확히 같은 점수에 대해 비교식을 먼저 평가하라.", | ||
| "transfer_trap": "JavaScript의 참·거짓 변환을 옮기지 마라. Java 조건 위치에는 정수가 아니라 불리언이 필요하다." | ||
| }, | ||
| "java-numbers-operators": { | ||
| "title": "숫자와 연산자", | ||
| "concept": "숫자와 연산자는 많은 stdin 문제에서 나눗셈과 나머지를 정수 기준으로 유지해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 숫자와 연산자 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "0을 향해 절삭하는 정수 나눗셈", | ||
| "concept": "두 `int` 피연산자의 `/`는 소수 부분을 0을 향해 절삭한 몫을, `%`는 그 몫에 대응하는 나머지를 만든다. 피연산자를 실수로 바꾸면 다른 산술이 선택된다.", | ||
| "worked_example": "`19`를 `4`로 나누면 몫 `4`, 나머지 `3`이다. 예제의 표시는 실습에서 요구하는 간결한 형식과 출력을 구분한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 숫자와 연산자 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 숫자와 연산자 실습의 TODO를 풀지 않는다.", | ||
| "숫자와 연산자는 많은 stdin 문제에서 나눗셈과 나머지를 정수 기준으로 유지해야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`/`를 `Math.floorDiv`로 바꾸어 음수 몫의 규칙을 내림으로 변경한다.", | ||
| "Java `%` 대신 내림 나머지 규칙으로 나머지를 유도한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 숫자와 연산자 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "숫자와 연산자 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "피제수가 음수이고 제수가 양수일 때 몫은 어느 방향으로 움직이는가?", | ||
| "각 사례에서 `피제수 == 몫 * 제수 + 나머지`를 어떻게 확인할 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 숫자와 연산자 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "제수가 0이 아닌 두 정수를 파싱해 Java의 몫과 나머지를 콜론으로 구분하여 출력하라.", | ||
| "objective": "다른 언어의 내림 규칙을 가져오지 않고 부호 있는 Java 정수 나눗셈을 계산한다.", | ||
| "language_delta": "Python `//`는 음의 무한대 방향으로 내림하지만 Java의 정수 `/`는 소수 부분을 0을 향해 절삭한다.", | ||
| "prediction_prompt": "실행 결과를 보기 전에 `-17 / 5`의 몫과 나머지를 모두 계산하라.", | ||
| "transfer_trap": "한 피연산자라도 부동소수점으로 바꾸면 더 이상 정수 절삭을 배우는 연산이 아니다." | ||
| }, | ||
| "java-strings": { | ||
| "title": "문자열", | ||
| "concept": "문자열는 비교나 출력 전에 텍스트 정리와 부분 문자열 추출이 필요할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 문자열 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "문자열 정리와 내용 동등성", | ||
| "concept": "`strip`은 양 끝에서 `Character.isWhitespace`가 참으로 판정하는 코드 포인트를 제거해 새 문자열을 돌려준다. 길이와 인덱스는 UTF-16 코드 단위를 따르고 `.equals`는 내용, `==`는 참조를 비교한다.", | ||
| "worked_example": "예제는 공백을 뺀 중괄호 문자열의 구분자를 확인한 뒤 `substring`으로 내부를 꺼낸다. 원래 문자열은 바뀌지 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 문자열 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 문자열 실습의 TODO를 풀지 않는다.", | ||
| "문자열는 비교나 출력 전에 텍스트 정리와 부분 문자열 추출이 필요할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "서로 따로 만든 문자열의 내용이 같은지 `==`로 판단한다.", | ||
| "한 쌍의 같은 래퍼인지 확인하지 않고 첫 코드 단위와 마지막 코드 단위를 제거한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 문자열 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "문자열 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`substring` 끝 인덱스가 `length()`가 아니라 `length() - 1`인 이유는 무엇인가?", | ||
| "서로게이트 쌍으로 표현된 문자는 UTF-16 길이에서 몇 단위가 될 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 문자열 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "입력 주변 공백을 제거하고 `[]` 또는 `{}` 한 쌍만 확인해 유니코드 내부 문자열을 보존한 채 출력하라.", | ||
| "objective": "문자열을 변경하거나 참조 동일성에 기대지 않고 감싼 텍스트를 정규화한다.", | ||
| "language_delta": "Java와 JavaScript는 UTF-16 코드 단위로 인덱싱하지만 Python 문자열 인덱스는 유니코드 코드 포인트를 따른다.", | ||
| "prediction_prompt": "괄호로 감싼 한국어 입력을 공백 제거한 뒤 길이와 부분 문자열 경계를 추적하라.", | ||
| "transfer_trap": "UTF-16 코드 단위도 유니코드 코드 포인트도 사용자가 보는 그래핌과 항상 같지는 않다." | ||
| }, | ||
| "java-control-flow": { | ||
| "title": "제어 흐름", | ||
| "concept": "제어 흐름는 분기와 반복문이 누적값에 들어갈 값을 결정할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 제어 흐름 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "분기와 포함 반복 경계", | ||
| "concept": "`for` 반복문이 상한을 포함한 숫자 범위를 제어하고 `if`가 홀수만 거른다. Java 조건은 반드시 불리언이어야 하며 숫자의 0과 0이 아님을 자동으로 조건으로 해석하지 않는다.", | ||
| "worked_example": "카운터가 1부터 7까지 전진하고 2로 나눈 나머지가 0이 아닌 값만 누적해 표시가 붙은 홀수 합계를 만든다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 제어 흐름 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 제어 흐름 실습의 TODO를 풀지 않는다.", | ||
| "제어 흐름는 분기와 반복문이 누적값에 들어갈 값을 결정할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`< limit`을 사용해 홀수인 상한을 조용히 빠뜨린다.", | ||
| "정수를 `if` 조건에 직접 써서 숫자 참·거짓 변환이 있다고 가정한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 제어 흐름 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "제어 흐름 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "상한이 6일 때 실제 합계에 들어가는 반복 값은 무엇인가?", | ||
| "반복 조건을 `< limit`으로 바꾸면 어느 입력에서 마지막 홀수가 누락되는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 제어 흐름 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "1부터 입력 상한까지 포함해 순회하고 홀수만 누적한 뒤 합계를 표준 출력 한 줄로 보고하라.", | ||
| "objective": "0과 양수 상한에서 반복 경계, 홀짝 분기, 누적값을 함께 맞춘다.", | ||
| "language_delta": "Java 조건은 불리언 타입이어야 하며 0과 0이 아닌 정수를 조건으로 바꾸는 언어와 다르다.", | ||
| "prediction_prompt": "상한 3에서 방문하는 값을 모두 쓰고 합계를 바꾸는 값을 표시하라.", | ||
| "transfer_trap": "끝값을 제외하는 다른 언어의 범위 습관을 옮기면 `<=`가 필요한 포함 반복에서 홀수 상한을 빠뜨린다." | ||
| }, | ||
| "java-methods": { | ||
| "title": "메서드", | ||
| "concept": "메서드는 재사용 계산에 이름 있는 경계가 필요할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 메서드 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-enum-switch": { | ||
| "title": "열거형 스위치 표현식 빠짐없이 쓰기", | ||
| "concept": "열거형은 유한한 단일 객체 상수 집합을 정의한다. 스위치 표현식은 화살표 규칙으로 모든 상수를 값에 대응시키며 의도치 않은 다음 갈래 실행이 없다.", | ||
| "worked_example": "예제는 `STARTED`와 `STOPPED`를 다른 동작으로 바꾸고 스위치 결과를 문자열에 저장한 뒤 예제 접두사를 붙인다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 메서드 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 메서드 실습의 TODO를 풀지 않는다.", | ||
| "메서드는 재사용 계산에 이름 있는 경계가 필요할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`default` 갈래를 넣어 열거형이 커졌을 때 처리하지 않은 상수를 숨긴다.", | ||
| "화살표 갈래도 다음 갈래 본문까지 계속 실행된다고 생각한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 메서드 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "메서드 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`default` 없이 열거형 상수 하나를 빠뜨리면 컴파일러가 무엇을 알려 주는가?", | ||
| "상태 하나에 화살표 규칙이 둘 이상 실행되는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 메서드 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "작업 상태마다 올바른 텍스트를 돌려주도록 매핑을 고치고 스위치 표현식의 완전성은 유지하라.", | ||
| "objective": "닫힌 열거형 상태를 가변 결과 변수나 다음 갈래 실행 없이 텍스트로 바꾼다.", | ||
| "language_delta": "Java 스위치 표현식은 값을 산출하므로 결과 변수 대입과 `break`가 필요했던 기존 문장형 스위치와 다르다.", | ||
| "prediction_prompt": "`BLOCKED`가 들어갈 정확한 갈래와 뒤 갈래가 실행될 수 있는지 판단하라.", | ||
| "transfer_trap": "열거형 스위치가 완전해도 임의 문자열까지 안전해지지는 않는다. `valueOf`는 알 수 없는 이름을 거부한다." | ||
| }, | ||
| "java-input": { | ||
| "title": "입력 파싱", | ||
| "concept": "입력 파싱는 stdin 바이트를 타입 있는 토큰으로 바꿔야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 입력 파싱 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-methods": { | ||
| "title": "체크포인트: 타입이 있는 정적 메서드", | ||
| "concept": "`static int area(int width, int height)`는 두 `int` 값을 복사해 매개변수로 받고 계산한 `int`를 호출자에게 반환한다. 입력 파싱과 출력은 진입점에 남는다.", | ||
| "worked_example": "넓이 계산은 `area(int, int)` 안에 있고 `main`은 별도 예제 치수를 값으로 넘긴다. 반환된 숫자가 메서드 경계를 건넌 뒤 출력된다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 입력 파싱 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 입력 파싱 실습의 TODO를 풀지 않는다.", | ||
| "입력 파싱는 stdin 바이트를 타입 있는 토큰으로 바꿔야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "이름 붙은 메서드를 완성하지 않고 `main`에서 곱셈을 다시 쓴다.", | ||
| "메서드 안에서 출력하고 값을 반환하지 않아 계산과 표준 출력 책임을 섞는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 입력 파싱 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "입력 파싱 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "호출할 때 두 `int` 값은 호출자 변수와 매개변수 사이에서 어떻게 전달되는가?", | ||
| "시그니처의 어느 부분이 호출자에게 `int` 반환을 약속하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 입력 파싱 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "값을 반환하는 타입이 지정된 넓이 메서드를 완성하고 표준 입력 파싱과 표준 출력 형식은 `main`에 남겨라.", | ||
| "objective": "모든 계산을 진입점에 넣지 않고 타입이 있는 재사용 가능한 반환 경계를 보여 준다.", | ||
| "language_delta": "Java의 기본 타입 인자는 값으로 복사되며 선언한 매개변수와 반환 타입은 컴파일 단계에서 검사된다.", | ||
| "prediction_prompt": "두 정수가 매개변수, `return` 문, `println`으로 이동하는 순서를 추적하라.", | ||
| "transfer_trap": "표현식 본문이 값을 암묵적으로 반환하는 언어와 달리 Java 메서드는 선언한 반환 타입에 맞는 `return`이 필요하다." | ||
| }, | ||
| "java-arrays-collections": { | ||
| "title": "배열과 컬렉션", | ||
| "concept": "배열과 컬렉션는 고정 길이 배열, 늘어나는 List, 키 기반 Map, 유일값 Set이 서로 다른 컨테이너 역할을 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 배열과 컬렉션 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-overloading-varargs": { | ||
| "title": "오버로드와 가변 인자 배열 연결하기", | ||
| "concept": "오버로딩은 한 메서드 이름에 여러 시그니처를 둔다. 컴파일러가 적용 가능한 시그니처를 고르고 `int...`는 메서드 본문에서 `int[]`로 표현된다.", | ||
| "worked_example": "가변 인자 오버로드가 두 점수를 합산한 뒤 계산된 정수를 인자가 두 개인 형식화 오버로드에 넘긴다. 두 번째 호출은 가변 인자가 아닌 시그니처를 선택한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 배열과 컬렉션 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 배열과 컬렉션 실습의 TODO를 풀지 않는다.", | ||
| "배열과 컬렉션는 고정 길이 배열, 늘어나는 List, 키 기반 Map, 유일값 Set이 서로 다른 컨테이너 역할을 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "숫자 합이 필요한데 가변 인자 원소 개수를 반환한다.", | ||
| "오버로드 디스패치가 실행 중 배열 내용을 보고 결정된다고 생각한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 배열과 컬렉션 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "배열과 컬렉션 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`label(name, total)`이 두 인자 오버로드를 고르는 이유는 무엇인가?", | ||
| "사용자 뒤에 0 하나만 오면 가변 인자 메서드에 어떤 배열이 전달되는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 배열과 컬렉션 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "파싱한 점수 배열을 합산하고 그 결과를 이름과 정수를 받는 형식화 오버로드에 위임하라.", | ||
| "objective": "컴파일 시점 오버로드 선택과 가변 인자가 배열이라는 사실을 함께 관찰한다.", | ||
| "language_delta": "Java 가변 인자는 JavaScript 나머지 매개변수와 달리 정적 적용 가능성과 오버로드 순위에 참여한다.", | ||
| "prediction_prompt": "예제의 두 호출에서 각각 선택되는 오버로드를 출력 계산 전에 식별하라.", | ||
| "transfer_trap": "넓은 오버로드 하나를 추가하면 개별 구현이 유효해도 호출이 모호해질 수 있다." | ||
| }, | ||
| "java-classes-objects": { | ||
| "title": "클래스와 객체", | ||
| "concept": "클래스와 객체는 객체가 인스턴스 상태와 동작을 함께 가져야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 클래스와 객체 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-packages-imports": { | ||
| "title": "`import`와 완전 수식 이름 구분하기", | ||
| "concept": "`import`를 쓰면 소스에서 타입의 간단한 이름을 사용할 수 있다. 같은 컴파일 단위에서 가져온 `ArrayList`를 완전 수식 이름인 `java.util.List` 타입 변수에 대입할 수도 있다.", | ||
| "worked_example": "변수는 `List` 인터페이스 타입으로 선언하고 생성자에는 가져온 `ArrayList`를 사용한다. 저장한 단어를 이어 붙여 컬렉션이 실제로 쓰였음을 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 클래스와 객체 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 클래스와 객체 실습의 TODO를 풀지 않는다.", | ||
| "클래스와 객체는 객체가 인스턴스 상태와 동작을 함께 가져야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`import`가 라이브러리를 내려받거나 초기화 코드를 실행한다고 생각한다.", | ||
| "간단한 이름이 같은 두 타입을 가져오고 Java가 자동으로 하나를 고를 것으로 기대한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 클래스와 객체 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "클래스와 객체 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`ArrayList`의 `import`를 지워도 동작하게 만들 수 있는 완전한 타입 이름은 무엇인가?", | ||
| "`String`은 `import`나 `java.lang.String` 표기 없이 쓸 수 있는 이유가 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 클래스와 객체 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "가져온 `ArrayList` 구현체로 완전 수식 이름의 `java.util.List`를 채우고 입력 토큰을 구분자 없이 순서대로 연결해 출력하라.", | ||
| "objective": "이름을 간단히 쓰는 편의와 타입 자체의 가용성을 구분하면서 실제 컬렉션을 사용한다.", | ||
| "language_delta": "Java의 `import`는 컴파일 단위의 이름 문맥에 타입을 추가할 뿐 Python 모듈 실행이나 JavaScript 모듈 로딩이 아니다.", | ||
| "prediction_prompt": "실행 전에 예제의 간단한 타입 이름과 완전 수식 이름을 실제 패키지에 대응시켜라.", | ||
| "transfer_trap": "와일드카드 `import`는 하위 패키지를 포함하지 않으며 모호한 간단한 이름을 해결하지 못한다." | ||
| }, | ||
| "java-constructors": { | ||
| "title": "생성자", | ||
| "concept": "생성자는 새 객체가 올바른 필드값으로 시작해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 생성자 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-arrays-collections": { | ||
| "title": "배열과 세 컬렉션 관찰하기", | ||
| "concept": "배열은 길이가 고정되고 제네릭 `List`, `Map`, `Set`은 서로 다른 컬렉션 동작을 제공한다. 스트림은 원본 컬렉션을 바꾸지 않고 이런 뷰에서 집계를 만든다.", | ||
| "worked_example": "각 배열 값이 목록, 빈도 맵, 고유 세트에 들어간다. 합계는 맵의 키와 개수 곱으로 복원되어 각 출력 필드가 다른 구조를 관찰한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 생성자 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 생성자 실습의 TODO를 풀지 않는다.", | ||
| "생성자는 새 객체가 올바른 필드값으로 시작해야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "맵 항목에서 계산해야 하는 합 대신 원본 배열 길이를 첫 필드로 출력한다.", | ||
| "중복이 있을 때 목록 크기, 세트 고유 수, 맵 키 수를 같은 값으로 생각한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 생성자 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "생성자 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`2 2 3`을 처리한 뒤 세 컬렉션에는 각각 무엇이 들어 있는가?", | ||
| "맵 항목 스트림에서 합을 실제로 수행하는 종단 연산은 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 생성자 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "배열 값을 목록, 빈도 맵, 세트에 모두 반영하고 첫 보고값은 원본이 아니라 맵의 빈도 항목에서 계산하라.", | ||
| "objective": "중복에 민감한 보고서 하나로 세 컬렉션의 관찰 가능한 의미를 설명한다.", | ||
| "language_delta": "Java 배열은 실행 중 길이가 고정된 객체이고 제네릭 컬렉션 원소 타입은 정적으로 검사된 뒤 대부분 지워진다.", | ||
| "prediction_prompt": "반복 입력에서 합계를 구하기 전에 목록, 맵, 세트 상태를 각각 그려 보라.", | ||
| "transfer_trap": "스트림 파이프라인은 종단 연산 전까지 지연되며 한 번 소비한 스트림은 다시 사용할 수 없다." | ||
| }, | ||
| "java-encapsulation": { | ||
| "title": "캡슐화", | ||
| "concept": "캡슐화는 호출자가 필드를 직접 만지지 않고 메서드로 상태를 바꿔야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 캡슐화 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-generics": { | ||
| "title": "제네릭 원소 타입 보존하기", | ||
| "concept": "타입 매개변수는 입력과 출력의 관계를 잇는다. `<T> T last(List<T>)`는 목록이 받은 것과 같은 원소 타입을 반환하며 원시 `List`는 이 검사를 포기한다.", | ||
| "worked_example": "색상 문자열 목록에서 타입 추론이 `T`를 문자열로 정한다. 도우미는 마지막 위치를 골라 타입 변환 없이 문자열을 반환한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 캡슐화 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 캡슐화 실습의 TODO를 풀지 않는다.", | ||
| "캡슐화는 호출자가 필드를 직접 만지지 않고 메서드로 상태를 바꿔야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "원시 `List`를 사용해 막을 수 있는 타입 오류를 실행 시점으로 미룬다.", | ||
| "인덱스 0을 선택해 여러 원소 입력에서도 첫 원소를 반환한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 캡슐화 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "캡슐화 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "같은 도우미에 `List<Integer>`를 넘기면 `T`는 무엇인가?", | ||
| "`size()`에서 1을 빼기 전에 필요한 사전 조건은 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 캡슐화 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "목록의 원소 타입을 그대로 보존하면서 비어 있지 않은 입력의 마지막 원소를 고르는 재사용 가능한 메서드를 완성하라.", | ||
| "objective": "호출자가 타입 변환 없이 의존할 수 있는 입력-출력 관계를 타입 변수 하나로 표현한다.", | ||
| "language_delta": "Java 제네릭은 보통 타입 소거를 사용해 실행 시 원소 타입이 남는 배열과 다르지만, 컴파일 시점의 타입 관계는 잘못된 호출을 막는다.", | ||
| "prediction_prompt": "각 단어 목록에서 메서드의 `T`와 선택될 마지막 인덱스를 추론하라.", | ||
| "transfer_trap": "타입 소거는 원시 컬렉션을 안전하게 만들지 않는다. 실행 시 표현에서 제네릭 정보 대부분을 제거할 뿐이다." | ||
| }, | ||
| "java-static-members": { | ||
| "title": "static 멤버", | ||
| "concept": "static 멤버는 값이 특정 객체가 아니라 클래스에 속해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 static 멤버 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-streams-lambdas": { | ||
| "title": "지연 스트림 파이프라인 소비하기", | ||
| "concept": "스트림은 종단 연산이 결과를 요청할 때까지 원소 처리 과정을 기술한다. `IntStream`의 필터와 `map`은 중간 단계이고 `sum`이 한 번 순회한다.", | ||
| "worked_example": "`IntStream.of`의 두 예제 값이 짝수 조건식을 통과하고 매핑에서 제곱된 뒤 종단 합계에 표시가 붙는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 static 멤버 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 static 멤버 실습의 TODO를 풀지 않는다.", | ||
| "static 멤버는 값이 특정 객체가 아니라 클래스에 속해야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "필터와 맵 파이프라인만 만들고 종단 연산을 호출하지 않는다.", | ||
| "`sum`으로 소비가 끝난 같은 스트림 객체를 다시 순회한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 static 멤버 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "static 멤버 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "예제 조건식과 매핑은 실제로 어느 시점에 평가되는가?", | ||
| "0은 짝수 필터를 통과해도 제곱 합에 영향을 주지 않는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 static 멤버 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "파싱한 정수 스트림에서 짝수만 남기고 제곱한 뒤 종단 연산 하나로 합계를 계산하라.", | ||
| "objective": "지연 중간 연산을 관찰 가능한 숫자 결과와 연결한다.", | ||
| "language_delta": "Java 스트림은 재사용 컬렉션이나 JavaScript 배열이 아니라 한 번만 소비하는 순회 파이프라인이다.", | ||
| "prediction_prompt": "음수가 섞인 사례에서 필터 뒤 시퀀스와 맵 뒤 시퀀스를 각각 적어 보라.", | ||
| "transfer_trap": "스트림의 순서와 지연 실행은 병렬 실행을 뜻하지 않으며 병렬성은 별도 선택이다." | ||
| }, | ||
| "java-enum-switch": { | ||
| "title": "enum과 switch", | ||
| "concept": "enum과 switch는 정해진 상태 집합을 명시적 결과로 바꿔야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 enum과 switch 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-comparators-sorting": { | ||
| "title": "비교자 단계로 정렬 규칙 쓰기", | ||
| "concept": "`comparingInt`, `reversed`, `thenComparing`은 정렬 기준을 단계별로 직접 표현한다. 극단 정수에서 오버플로할 수 있는 뺄셈 비교자를 피한다.", | ||
| "worked_example": "`Kai`와 `Bea`의 점수가 같아 이름 오름차순 2차 비교가 `Bea`를 먼저 둔다. 정렬 후 첫 레코드를 읽는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 enum과 switch 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 enum과 switch 실습의 TODO를 풀지 않는다.", | ||
| "enum과 switch는 정해진 상태 집합을 명시적 결과로 바꿔야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "완성된 연결 전체를 뒤집어 이름 동점 기준까지 내림차순으로 만든다.", | ||
| "`right.score() - left.score()`를 반환해 정수 오버플로로 순서 계약을 어긴다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 enum과 switch 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "enum과 switch 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "점수가 같은 두 사용자는 어느 비교자 단계에서 결정되는가?", | ||
| "음수 점수 사용자 하나뿐인 목록에서도 그 사용자가 승자인 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 enum과 switch 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "점수 내림차순 비교자에 이름 오름차순 동점 기준을 이어 붙이고 정렬 결과의 첫 사용자를 출력하라.", | ||
| "objective": "정렬 요구사항을 읽기 쉽고 재사용 가능한 비교자 합성으로 표현한다.", | ||
| "language_delta": "Java 비교자 객체는 임의 레코드의 순서를 명시하지만 `<` 같은 연산자로는 객체를 직접 정렬할 수 없다.", | ||
| "prediction_prompt": "세 사용자 사례에 비교자 단계를 하나씩 적용해 손으로 순서를 정하라.", | ||
| "transfer_trap": "`reversed()`는 그 시점까지 만든 비교자에 적용되므로 호출 위치가 동점 기준까지 바꿀 수 있다." | ||
| }, | ||
| "java-exceptions": { | ||
| "title": "예외", | ||
| "concept": "예외는 잘못된 입력을 관련 없는 실패까지 숨기지 않고 복구해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 예외 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-classes-objects": { | ||
| "title": "인스턴스별 객체 상태 바꾸기", | ||
| "concept": "각 객체는 자기 인스턴스 필드에 상태를 저장하고 메서드를 통해 동작한다. 생성자가 초기 상태를 만들며 `static` 필드와 달리 서로 다른 인스턴스의 상태는 공유되지 않는다.", | ||
| "worked_example": "카운터 하나가 `10`에서 시작해 `add`로 변화량을 받는다. 메서드는 지역 매개변수가 아니라 해당 `this`의 필드를 바꾸고 `main`은 이후 `12`를 관찰한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 예외 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 예외 실습의 TODO를 풀지 않는다.", | ||
| "예외는 잘못된 입력을 관련 없는 실패까지 숨기지 않고 복구해야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "매개변수만 바꾸고 객체 필드도 따라 바뀔 것으로 기대한다.", | ||
| "모든 상태를 `static`으로 만들어 독립적이어야 할 인스턴스가 값을 공유하게 한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 예외 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "예외 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`add` 안에서 바뀌는 인스턴스 필드와 인자만 전달하는 지역 매개변수는 각각 무엇인가?", | ||
| "서로 다른 카운터 인스턴스 둘을 만들면 한쪽의 `add`가 다른 쪽 상태도 바꾸는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 예외 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "파싱한 변화량을 카운터 메서드에 넘겨 수신자 자신의 인스턴스 필드를 갱신하고 그 상태를 출력하라.", | ||
| "objective": "작은 클래스 인스턴스로 객체 정체성과 캡슐화된 변경을 관찰한다.", | ||
| "language_delta": "Java 클래스는 명목적으로 선언된 타입과 명시적 생성자를 사용하므로 구조적 모양만으로 호환성을 판단하는 객체 타입과 다르다.", | ||
| "prediction_prompt": "메서드 호출 전후에 카운터 필드와 `delta` 매개변수를 별도로 추적하라.", | ||
| "transfer_trap": "JavaScript의 평범한 객체처럼 모든 값이 같은 속성 저장소를 공유한다고 생각하지 마라. 인스턴스 필드는 객체마다 따로 존재한다." | ||
| }, | ||
| "java-generics": { | ||
| "title": "제네릭", | ||
| "concept": "제네릭는 하나의 메서드가 호출자의 원소 타입을 보존해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 제네릭 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-constructors": { | ||
| "title": "생성자에서 불변 조건 세우기", | ||
| "concept": "생성자는 클래스와 같은 이름을 가지며 반환 타입이 없다. 정상 완료 전에 모든 초기값 없는 `final` 인스턴스 필드를 대입해야 하고 `this.field`로 같은 이름의 매개변수와 구분한다.", | ||
| "worked_example": "직사각형 생성자가 두 인자를 `final` 치수 필드로 옮긴다. 이후 넓이 메서드는 두 필드가 설정됐다고 믿고 계산할 수 있다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 제네릭 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 제네릭 실습의 TODO를 풀지 않는다.", | ||
| "제네릭는 하나의 메서드가 호출자의 원소 타입을 보존해야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`void Rectangle(...)`로 써서 생성자가 아니라 일반 메서드를 선언한다.", | ||
| "매개변수를 자기 자신에 대입해 같은 이름의 필드를 초기화하지 않는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 제네릭 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "제네릭 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`this.width = width`의 왼쪽은 어떤 저장 위치를 가리키는가?", | ||
| "`final` 필드 대입을 빠뜨린 생성자 경로를 컴파일러가 거부하는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 제네릭 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "두 생성자 인자를 해당 인스턴스의 `final` 치수 필드에 대입해 넓이가 임시값 없이 계산되게 하라.", | ||
| "objective": "임시 치수 없이 객체 생성 시점에 상태를 한 번 확립한다.", | ||
| "language_delta": "Java 생성자는 값을 반환하는 일반 초기화 함수가 아니라 특별한 선언이다.", | ||
| "prediction_prompt": "넓이를 계산하기 전에 같은 이름의 두 대입에서 필드와 매개변수를 각각 구분하라.", | ||
| "transfer_trap": "`final` 필드는 생성 뒤 재대입을 막지만 참조가 가리키는 객체까지 재귀적으로 동결하지 않는다." | ||
| }, | ||
| "java-interfaces": { | ||
| "title": "인터페이스", | ||
| "concept": "인터페이스는 호출자가 구체 클래스보다 동작 계약에 기대야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 인터페이스 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-encapsulation": { | ||
| "title": "비공개 상태에서 규칙 강제하기", | ||
| "concept": "비공개 필드는 관련 없는 코드에서 표현을 숨긴다. 외부에 노출한 연산은 요청된 변경을 별도로 검증해야 하며 가시성과 불변 조건은 다른 문제다.", | ||
| "worked_example": "계정은 양수 입금 두 개를 받아들이고 음수 요청은 무시한다. 접근자는 최종 정수 잔액만 노출한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 인터페이스 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 인터페이스 실습의 TODO를 풀지 않는다.", | ||
| "인터페이스는 호출자가 구체 클래스보다 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`private`가 메서드로 들어온 잘못된 값까지 자동 거부한다고 생각한다.", | ||
| "가변 내부 컬렉션을 그대로 반환해 의도한 경계를 우회하게 한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 인터페이스 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "인터페이스 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "예제에서 잔액을 바꾸지 않는 호출은 무엇인가?", | ||
| "계정 내부 코드는 비공개 필드에 잘못된 값을 직접 넣을 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 인터페이스 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "양수 조건을 `deposit` 메서드 안에 두고 상태는 접근자를 통해서만 읽어 표준 출력으로 보고하라.", | ||
| "objective": "상태 전이를 소유한 메서드에서 작은 불변 조건을 강제한다.", | ||
| "language_delta": "Java 접근 수식자는 컴파일 시점과 실행 시점 멤버 접근 규칙이지 도메인 검증을 자동 생성하지 않는다.", | ||
| "prediction_prompt": "파싱한 변화량을 순서대로 적용하며 가드가 각 값을 받는지 기록하라.", | ||
| "transfer_trap": "접근자가 가변 내부 객체를 그대로 반환하면 안전한 뷰나 복사본 없이 내부 상태가 노출되어 캡슐화가 깨진다." | ||
| }, | ||
| "java-inheritance-composition": { | ||
| "title": "상속과 합성", | ||
| "concept": "상속과 합성는 하위 클래스가 동작을 완성하려면 도움 객체도 필요할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 상속과 합성 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-static-members": { | ||
| "title": "클래스가 소유한 정적 상수", | ||
| "concept": "정적 필드는 클래스에 속해 모든 호출과 인스턴스가 공유한다. `static final`은 바인딩을 고정하지만 그 바인딩이 가리키는 객체의 변경까지 막지는 않는다.", | ||
| "worked_example": "배율 계산 메서드는 클래스 문맥에서 `FACTOR`를 읽어 예제 값을 곱한다. 두 정적 멤버에 접근하기 위한 인스턴스는 필요 없다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 상속과 합성 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 상속과 합성 실습의 TODO를 풀지 않는다.", | ||
| "상속과 합성는 하위 클래스가 동작을 완성하려면 도움 객체도 필요할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "정적 유틸리티를 호출하려고 불필요한 객체를 만들어 클래스 소유권을 흐린다.", | ||
| "모든 필드 타입에서 `final`이 깊은 불변성을 약속한다고 해석한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 상속과 합성 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "상속과 합성 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`Solution` 객체를 열 개 만들어도 `FACTOR` 저장 위치는 몇 개인가?", | ||
| "배율 계산 메서드가 인스턴스를 받거나 만들지 않고 인스턴스 필드를 읽을 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 상속과 합성 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "공유 계수를 요구한 값으로 고쳐 정적 배율 계산 메서드가 양수, 0, 음수 입력을 모두 처리하게 하라.", | ||
| "objective": "클래스-수준 상태를 의도적으로 사용하고 객체별 필드와 구분한다.", | ||
| "language_delta": "Java `static`은 멤버를 선언된 클래스에 붙이며 소유 타입이 없는 모듈-수준 바인딩과 다르다.", | ||
| "prediction_prompt": "부호가 다른 각 입력에 클래스 계수를 대입해 곱셈 결과를 계산하라.", | ||
| "transfer_trap": "가변 정적 필드는 모든 인스턴스에 공유 상태를 만들며 흔히 동기화가 필요하다." | ||
| }, | ||
| "java-records": { | ||
| "title": "record", | ||
| "concept": "record는 불변 데이터 묶음에 반복 코드가 적어야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 record 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-interfaces": { | ||
| "title": "인터페이스 계약으로 동작 호출하기", | ||
| "concept": "인터페이스는 공개 동작 계약에 이름을 붙인다. `Named`를 받는 코드는 구현이 레코드인지 클래스인지 몰라도 `name()`을 호출할 수 있다.", | ||
| "worked_example": "사용자 레코드는 자동 생성된 접근자로 `Named`의 `name()`을 구현한다. 표현 도우미는 인터페이스만 받고 로케일에 영향받지 않는 대문자 변환을 수행한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 record 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 record 실습의 TODO를 풀지 않는다.", | ||
| "record는 불변 데이터 묶음에 반복 코드가 적어야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "인터페이스 메서드가 공개인데 구현 메서드의 가시성을 더 좁힌다.", | ||
| "표현 도우미의 매개변수를 사용자 타입으로 고정해 구현 하나에 불필요하게 결합한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 record 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "record 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`Named`로 선언된 변수에서 사용할 수 있는 메서드는 무엇인가?", | ||
| "다른 클래스도 사용자 타입을 상속하지 않고 이 계약에 참여할 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 record 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "구체적인 사용자 타입이 아니라 `Named` 인터페이스를 받는 도우미를 통해 이름을 `Locale.ROOT` 기준의 대문자로 바꾸어 출력하라.", | ||
| "objective": "구체 표현 대신 계약을 대상으로 공통 동작을 호출한다.", | ||
| "language_delta": "Java 인터페이스는 클래스가 구현을 선언하는 명목적 계약이며 TypeScript의 구조적 호환성과 다르다.", | ||
| "prediction_prompt": "대문자 변환 전에 레코드 접근자의 값이 `Named` 참조를 거쳐 전달되는 경로를 따라가라.", | ||
| "transfer_trap": "인터페이스의 기본 메서드가 동작을 제공해도 인터페이스 필드를 인스턴스별 상태로 만들지는 않는다." | ||
| }, | ||
| "java-optional": { | ||
| "title": "Optional", | ||
| "concept": "Optional는 없는 값을 명시적으로 처리해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 Optional 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-inheritance-composition": { | ||
| "title": "상속과 합성 함께 쓰기", | ||
| "concept": "상속은 하위 타입이 상위 타입을 대체할 수 있는 관계를, 합성은 객체가 다른 부품을 소유하는 관계를 모델링한다. 플레이어는 상위 클래스의 점수 상태를 재사용하면서 별도의 보너스 값을 소유한다.", | ||
| "worked_example": "생성 과정은 상위 클래스용 인자를 `super`에 보내고 보너스용 레코드를 따로 만든다. `total`이 두 출처를 읽어 `12`를 만든다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 Optional 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 Optional 실습의 TODO를 풀지 않는다.", | ||
| "Optional는 없는 값을 명시적으로 처리해야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "합성된 필드를 무시하고 상속된 상태만 반환한다.", | ||
| "하위 타입이 상위 타입의 계약을 지킬 수 없는데 코드 재사용만 위해 상속을 쓴다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 Optional 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "Optional 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "상위 클래스의 `protected` 필드는 어느 생성자에서 초기화되는가?", | ||
| "보너스가 플레이어의 상위 클래스가 아니라 필드인 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 Optional 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "플레이어의 `total` 메서드에서 상속받은 상위 클래스 값과 보유한 보너스 값을 더해 두 재사용 관계를 모두 관찰하라.", | ||
| "objective": "의미 관계에 맞춰 상속과 합성이라는 두 재사용 기법을 분리한다.", | ||
| "language_delta": "Java 클래스는 상위 클래스 하나만 가질 수 있지만 인터페이스는 여러 개 구현할 수 있어 독립 부품에는 합성이 특히 유용하다.", | ||
| "prediction_prompt": "두 생성자 인자가 서로 다른 객체에 저장되는 과정을 따라간 뒤 합계를 구하라.", | ||
| "transfer_trap": "`protected` 가시성은 일부 접근을 허용할 뿐 임의의 소유 관계를 올바른 하위 타입 관계로 만들지 않는다." | ||
| }, | ||
| "java-streams-lambdas": { | ||
| "title": "stream과 lambda", | ||
| "concept": "stream과 lambda는 컬렉션 처리가 파이프라인으로 더 분명할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 stream과 lambda 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-exceptions": { | ||
| "title": "좁은 예외 경계에서 복구하기", | ||
| "concept": "`Integer.parseInt`는 잘못된 숫자 문법을 비검사 예외인 `NumberFormatException`으로 알린다. 변환만 작은 `try`에 두고 그 예외만 잡아 관련 없는 결함을 숨기지 않는다.", | ||
| "worked_example": "`oops`를 파싱하면 `NumberFormatException` 전용 `catch`에 들어가 호출자가 준 대체값을 반환한다. 다른 실행 오류는 이 도우미가 삼키지 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 stream과 lambda 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 stream과 lambda 실습의 TODO를 풀지 않는다.", | ||
| "stream과 lambda는 컬렉션 처리가 파이프라인으로 더 분명할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`Exception`을 잡아 무관한 프로그래밍 오류까지 숨긴다.", | ||
| "`parseInt`가 잘못된 텍스트에서 옵셔널이나 센티널을 반환한다고 기대한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 stream과 lambda 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "stream과 lambda 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "유효한 음수 정수도 `catch` 블록에 들어가는가?", | ||
| "숫자 변환과 무관한 `NullPointerException`까지 이 `catch`가 처리해야 하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 stream과 lambda 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "후보 토큰을 정수로 파싱하고 숫자 문법이 잘못된 경우에만 제공된 대체값을 반환하라.", | ||
| "objective": "다른 결함을 가리지 않고 가장 작은 관련 예외 경계에서 복구한다.", | ||
| "language_delta": "Java는 처리하거나 선언해야 하는 검사 예외와 `NumberFormatException` 같은 비검사 예외를 구분한다.", | ||
| "prediction_prompt": "각 후보 토큰에서 정상 반환과 `catch`의 대체값 반환 중 어느 경로가 선택되는지 먼저 정하라.", | ||
| "transfer_trap": "모든 `Exception`을 한꺼번에 잡으면 잘못된 숫자 문법과 프로그래밍 결함의 차이가 사라진다." | ||
| }, | ||
| "java-comparators-sorting": { | ||
| "title": "Comparator와 정렬", | ||
| "concept": "Comparator와 정렬는 객체의 데이터 모양과 별도로 정렬 기준이 필요할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 Comparator와 정렬 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-optional": { | ||
| "title": "옵셔널 필터와 대체값", | ||
| "concept": "옵셔널은 반환값이 없을 수 있음을 표현한다. `filter`는 값이 있을 때 조건을 검사해 통과한 값만 남기고 `orElse`는 위험한 `get()` 없이 대체값을 제공한다.", | ||
| "worked_example": "값이 있는 `cat`은 세 글자 이하라는 길이 조건을 통과하므로 대체값이 선택되지 않는다. 예제 줄에는 구분용 표시만 붙는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 Comparator와 정렬 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 Comparator와 정렬 실습의 TODO를 풀지 않는다.", | ||
| "Comparator와 정렬는 객체의 데이터 모양과 별도로 정렬 기준이 필요할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "값 존재를 증명하지 않고 `get()`을 호출해 `NoSuchElementException`을 만든다.", | ||
| "값이 없을 수 있는 반환 타입이라는 주된 용도 대신 모든 필드와 매개변수에 옵셔널을 쓴다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 Comparator와 정렬 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "Comparator와 정렬 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "옵셔널이 이미 빈 상태이면 `filter` 조건식을 호출하는가?", | ||
| "대시 센티널에서 `missing`을 만드는 경로는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 Comparator와 정렬 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "대시 입력은 빈 `Optional`로 만들고, 그 밖의 입력에서는 세 글자 이하인 토큰만 `filter`로 남긴 뒤 대체값을 선택해 출력하라.", | ||
| "objective": "안전하지 않은 추출 없이 누락된 입력과 조건을 통과해 남은 텍스트를 구분한다.", | ||
| "language_delta": "옵셔널은 처리 메서드를 가진 명시적 컨테이너이며 아무 경로도 강제하지 않는 널 허용 참조와 다르다.", | ||
| "prediction_prompt": "네 입력 사례 각각에서 `Optional`을 만든 직후와 세 글자 이하 필터를 적용한 뒤 값이 있는지, 빈 상태인지 표시하라.", | ||
| "transfer_trap": "옵셔널은 원인 진단이 필요한 실패의 예외를 대신하지 않는다." | ||
| }, | ||
| "java-try-with-resources": { | ||
| "title": "try-with-resources", | ||
| "concept": "try-with-resources는 코드가 일찍 끝나도 리소스가 닫혀야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 try-with-resources 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "자원 자동 닫기 동작 관찰하기", | ||
| "concept": "자원 자동 닫기 구문은 정상 종료와 예외로 인한 종료 모두에서 `AutoCloseable` 값을 닫는다. 선언한 자원에서 직접 읽어 수명 관리가 실제 데이터 경로에 참여하게 한다.", | ||
| "worked_example": "관리되는 `ByteArrayInputStream`의 예제 바이트를 블록 안에서 읽고 UTF-8로 해석해 공백 제거와 대문자 변환을 한 뒤 자동으로 닫는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 try-with-resources 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 try-with-resources 실습의 TODO를 풀지 않는다.", | ||
| "try-with-resources는 코드가 일찍 끝나도 리소스가 닫혀야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "자원을 선언하고도 실제 읽기는 추적되지 않는 다른 스트림에서 수행한다.", | ||
| "본문이 이미 예외를 던졌을 때 닫기 실패가 억제된 예외로 붙을 수 있음을 잊는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 try-with-resources 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "try-with-resources 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "예제 블록 뒤 반드시 `close()` 호출을 받는 객체는 무엇인가?", | ||
| "빈 바이트 시퀀스는 출력 전에 어떤 지정 텍스트가 되어야 하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 try-with-resources 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "관리되는 스트림에서 실제로 읽은 바이트를 UTF-8 텍스트로 정규화하고 빈 내용에는 지정된 이름을 사용하라.", | ||
| "objective": "결정적인 자원 수명을 표준 출력에 도달하는 데이터 경로와 연결한다.", | ||
| "language_delta": "일반 객체 수명은 가비지 컬렉션 대상이어도 Java의 자원 자동 닫기 문법은 제어 흐름에 범위 기반 정리 동작을 명시한다.", | ||
| "prediction_prompt": "입력 바이트가 자원 생성, 읽기, 디코딩, 공백 제거, 대문자 변환을 거치는 순서를 추적하라.", | ||
| "transfer_trap": "가비지 컬렉션은 파일이나 소켓 같은 외부 자원을 제때 닫는 방법이 아니다." | ||
| }, | ||
| "java-packages-imports": { | ||
| "title": "package와 import", | ||
| "concept": "package와 import는 단일 파일 실습에서도 JDK 패키지의 클래스가 필요할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 package와 import 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-equality-hashcode": { | ||
| "title": "동등성과 해시 일관성 지키기", | ||
| "concept": "논리적 동등성은 반사성, 대칭성, 추이성, 일관성, 널 안전성을 지켜야 한다. 동등하다고 판정한 두 객체는 해시 컬렉션을 위해 같은 해시 코드도 가져야 한다.", | ||
| "worked_example": "레코드가 구성요소 기반 `equals`와 `hashCode`를 자동 제공하므로 좌표가 같은 두 `Point`는 해시 세트 항목 하나로 합쳐진다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 package와 import 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 package와 import 실습의 TODO를 풀지 않는다.", | ||
| "package와 import는 단일 파일 실습에서도 JDK 패키지의 클래스가 필요할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "좌표 하나만 비교해 서로 다른 `Point`를 동등하다고 판정한다.", | ||
| "`equals`만 재정의하고 동등한 객체에 다른 값을 낼 수 있는 `hashCode`를 남긴다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 package와 import 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "package와 import 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "동등한 객체는 해시가 달라선 안 되지만 동등하지 않은 객체가 해시를 공유할 수 있는 이유는 무엇인가?", | ||
| "`instanceof Point other` 패턴은 타입 검사와 타입 변환을 어떻게 합치는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 package와 import 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "점의 두 좌표가 모두 `equals`에 참여하게 하고 같은 값은 같은 해시를 갖게 유지한 뒤 해시 세트 크기를 관찰하라.", | ||
| "objective": "해시 기반 컬렉션 안에서도 올바른 값 계약을 구현한다.", | ||
| "language_delta": "Java `Object`의 기본 동등성은 동일성 기반이지만 레코드는 구성요소 기반의 값 동등성을 자동으로 제공한다.", | ||
| "prediction_prompt": "각 `Point` 쌍을 동등 또는 비동등으로 분류한 뒤 해시 세트가 두 번째 항목을 추가할지 정하라.", | ||
| "transfer_trap": "좋은 해시 분포는 성능에 유리하지만 동등한 값은 같은 해시여야 한다는 규칙은 정확성 요구다." | ||
| }, | ||
| "java-annotations": { | ||
| "title": "annotation", | ||
| "concept": "annotation는 메타데이터가 실행 로직이 아니라 선언에 붙어야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 annotation 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-records": { | ||
| "title": "체크포인트: 레코드 스냅샷 방어하기", | ||
| "concept": "레코드는 구성요소 참조를 재대입할 수 없게 하고 값 관련 메서드를 생성하지만 얕게만 불변이다. 간결 생성자의 `List.copyOf`가 가변 원본과 저장 구성요소를 분리한다.", | ||
| "worked_example": "간결 생성자가 `[4, 5]`를 복사하므로 원본 목록에 값을 더해도 레코드의 저장 목록은 바뀌지 않고 합은 `9`로 남는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 annotation 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 annotation 실습의 TODO를 풀지 않는다.", | ||
| "annotation는 메타데이터가 실행 로직이 아니라 선언에 붙어야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "호출자 소유 가변 목록을 그대로 저장한 레코드를 깊이 불변이라고 부른다.", | ||
| "값 의미에 관련 없는 가변 상태를 추가해 `equals`와 해시 일관성을 깨뜨린다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 annotation 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "annotation 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "간결 생성자 안에서 어느 구성요소 매개변수가 불변 복사본으로 다시 대입되는가?", | ||
| "가변 목록을 그대로 저장하면 원본에서 항목을 제거할 때 레코드에도 보이는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 annotation 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "원본의 마지막 항목을 제거하기 전에 목록 구성요소를 방어적으로 복사하고 레코드에 남은 스냅샷의 합계를 구하라.", | ||
| "objective": "레코드의 얕은 불변성을 확인하고 불변 복사본으로 원본과 저장값의 별칭 공유를 끊는다.", | ||
| "language_delta": "Java 레코드는 간결한 명목적 데이터 타입이며 재귀적으로 동결된 객체 그래프나 TypeScript 구조 타입은 아니다.", | ||
| "prediction_prompt": "`List.copyOf` 전후의 원본과 구성요소 참조를 그린 뒤 원본 마지막 항목을 제거하라.", | ||
| "transfer_trap": "레코드 구성요소 참조의 재대입 금지는 복사 없이 저장한 가변 객체의 내부 변경까지 막지 못한다." | ||
| }, | ||
| "java-sealed-classes": { | ||
| "title": "sealed 클래스", | ||
| "concept": "sealed 클래스는 도메인 계층이 허용된 구현을 나열해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 sealed 클래스 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-annotations": { | ||
| "title": "런타임 애너테이션 메타데이터 읽기", | ||
| "concept": "`@Target(METHOD)`는 애너테이션을 붙일 위치를 메서드로 제한하고 `@Retention(RUNTIME)`은 리플렉션으로 메타데이터를 읽을 수 있게 보존한다. 런타임 유지 정책이 없으면 선언된 접두사를 읽을 수 없다.", | ||
| "worked_example": "리플렉션으로 `normalize` 메서드를 찾아 `trace`라는 표시를 읽는다. 일반 메서드 호출이 예제 텍스트를 바꾸고, 리플렉션으로 얻은 메타데이터가 접두사를 제공한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 sealed 클래스 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 sealed 클래스 실습의 TODO를 풀지 않는다.", | ||
| "sealed 클래스는 도메인 계층이 허용된 구현을 나열해야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "런타임 리플렉션이 필요한데 기본 `CLASS` 유지 정책에 의존한다.", | ||
| "다른 메서드 시그니처를 검색해 일치하는 리플렉션용 `Method` 객체를 얻지 못한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 sealed 클래스 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "sealed 클래스 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "가상 머신이 애너테이션을 리플렉션에 노출하는 유지 정책은 무엇인가?", | ||
| "애너테이션을 필드에 붙이지 못하게 하는 대상 설정은 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 sealed 클래스 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "메서드에 붙은 애너테이션 메타데이터가 런타임까지 남도록 선언의 유지 정책을 고쳐라.", | ||
| "objective": "메타데이터를 쓰지 않는 장식으로 두지 않고 리플렉션으로 애너테이션 값을 관찰한다.", | ||
| "language_delta": "Java 애너테이션을 런타임에 사용하려면 명시적인 유지 정책이 필요하지만 타입 체계용 애너테이션은 컴파일 시점 가시성만 필요할 수 있다.", | ||
| "prediction_prompt": "런타임 유지 정책을 추가하기 전과 후에 `getAnnotation`이 객체와 `null` 중 무엇을 반환하는지 예측하라.", | ||
| "transfer_trap": "유지 정책은 가용성만 정하며 리플렉션 코드가 메타데이터를 읽고 적용해야 실제 동작이 생긴다." | ||
| }, | ||
| "java-testing-assert": { | ||
| "title": "테스트와 assert", | ||
| "concept": "테스트와 assert는 작은 검사가 깨진 메서드 가까이에서 실패해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 테스트와 assert 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-sealed-classes": { | ||
| "title": "봉인된 도형과 패턴 스위치", | ||
| "concept": "봉인된 인터페이스는 직접 구현 타입을 `permits` 목록으로 제한한다. Java 21 패턴 스위치는 각 레코드 하위 타입을 분해해 포괄적인 기본값 없이 값을 산출할 수 있다.", | ||
| "worked_example": "`Rect` 패턴이 너비와 높이를 직접 바인딩하고 완전한 스위치가 측정값을 계산한다. `Circle`과 `Dot`도 명시적으로 처리된다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 테스트와 assert 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 테스트와 assert 실습의 TODO를 풀지 않는다.", | ||
| "테스트와 assert는 작은 검사가 깨진 메서드 가까이에서 실패해야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "닫힌 계층을 패턴으로 관찰하지 않고 가상 메서드만 호출한다.", | ||
| "기본값 갈래를 넣어 새 허용된 도형 누락에 대한 컴파일러 피드백을 숨긴다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 테스트와 assert 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "테스트와 assert 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "어느 허용된 레코드의 구성요소가 패턴 갈래에서 곱해지는가?", | ||
| "Java 21에서 스위치가 기본값 없이도 컴파일되는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 테스트와 assert 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "`Circle` 레코드 패턴 갈래의 계산을 고치고 봉인된 계층의 모든 하위 타입을 다루는 스위치 완전성은 유지하라.", | ||
| "objective": "닫힌 하위 타입 지식과 레코드 분해를 하나의 관찰 가능한 표현식에 사용한다.", | ||
| "language_delta": "봉인된 타입은 제한 없는 기반 클래스나 구조적으로 추론한 유니온과 달리 명목적 계층을 닫는다.", | ||
| "prediction_prompt": "각 도형 토큰이 선택할 런타임 패턴과 바인딩되는 구성요소 변수를 계산 전에 적어 보라.", | ||
| "transfer_trap": "봉인은 허용 목록 규칙에 따른 직접 확장만 제어하며 인스턴스를 자동으로 불변하게 만들지 않는다." | ||
| }, | ||
| "java-equality-hashcode": { | ||
| "title": "equals와 hashCode", | ||
| "concept": "equals와 hashCode는 HashSet이 같은 값 객체를 알아봐야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 equals와 hashCode 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "java-testing-assert": { | ||
| "title": "캡스톤: 항상 실행되는 검사", | ||
| "concept": "이 종합 과제는 UTF-8 파싱, 메서드, 레코드, 제네릭 컬렉션, 스트림, 비교자, 옵셔널을 통합한다. Java `assert`는 `-ea` 없이는 꺼지므로 필수 검사는 `AssertionError`를 던지는 일반 메서드로 둔다.", | ||
| "worked_example": "동점 레코드를 이름으로 정렬하고 `check` 도우미가 빈 대체값을 검증한 뒤 표시가 붙은 승자를 출력한다. 명시적인 정렬과 종단 스트림이 모두 참여한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 equals와 hashCode 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 equals와 hashCode 실습의 TODO를 풀지 않는다.", | ||
| "equals와 hashCode는 HashSet이 같은 값 객체를 알아봐야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "채점 JVM이 검사를 활성화하지 않는데 필수 검증을 Java의 `assert`에 맡긴다.", | ||
| "점수만 정렬해 동점 승자를 등장 순서에 맡긴다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 equals와 hashCode 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "equals와 hashCode 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "JVM에 `-ea`가 없어도 계속 실행되는 종합 과제 검사는 무엇인가?", | ||
| "빈 목록 대체값과 동점 비교자는 서로 어떤 실패를 독립적으로 막는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 equals와 hashCode 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| }, | ||
| "java-overloading-varargs": { | ||
| "title": "overloading과 varargs", | ||
| "concept": "overloading과 varargs는 메서드 호출이 시그니처를 고르고 추가 인자를 모아야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 overloading과 varargs 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 overloading과 varargs 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 overloading과 varargs 실습의 TODO를 풀지 않는다.", | ||
| "overloading과 varargs는 메서드 호출이 시그니처를 고르고 추가 인자를 모아야 할 때 필요하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 overloading과 varargs 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "overloading과 varargs 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 overloading과 varargs 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "결정적인 최고 사용자 선택을 완성하고 승자를 출력하기 전에 빈 결과를 확인하는 명시적 검사를 그대로 유지하라.", | ||
| "objective": "핵심 경로를 테스트 가능한 레코드와 컬렉션 프로그램으로 통합하고 필수 검사를 항상 실행한다.", | ||
| "language_delta": "Python의 `assert`는 최적화 모드에서 제거되고 Java의 `assert`는 기본 비활성화지만 일반 메서드 호출은 정상 실행에서 항상 평가된다.", | ||
| "prediction_prompt": "동점 사용자를 비교자 두 단계로 정렬한 뒤 빈 목록의 대체값과 검사 경로를 별도로 추적하라.", | ||
| "transfer_trap": "`assert`는 내부 가정을 검사할 때 쓴다. 신뢰하지 않는 입력과 반드시 지켜야 할 조건은 일반 제어문과 명확한 실패로 검증하라." | ||
| } | ||
| } | ||
| } |
+363
-279
@@ -7,422 +7,506 @@ { | ||
| "java-output": { | ||
| "title": "标准输出", | ||
| "concept": "标准输出用于评测逐字符比较换行和文本。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示标准输出的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "精确的标准输出", | ||
| "concept": "`System.out.print` 写出参数,`println` 还会结束当前行。评测器把 `CRLF` 规范为 `LF`,除此以外的空格、缺失换行和额外空行都具有实际意义。", | ||
| "worked_example": "示例由一个 `String` 与一个 `int` 构造 `Mina:4`,并把一个显式换行放进传给 `print` 的文本。输出接口不会另加标签。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把标准输出的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决标准输出练习里的 TODO。", | ||
| "没有确认“标准输出用于评测逐字符比较换行和文本。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "用空格或连字符替代要求的冒号。", | ||
| "文本本身已有换行又调用 `println`,从而多出空白行。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用标准输出,然后才到达 `example:` 输出?", | ||
| "标准输出练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`print` 接收以 `\\n` 结束的字符串时,数字之后写出什么?", | ||
| "把示例调用改成 `println` 后,输出还会完全相同吗?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把标准输出应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "解析学习者姓名与分数,组成一条冒号分隔记录,并有意选择输出方法,使末尾恰好只有一个换行且没有其他空白。", | ||
| "objective": "分隔符和行尾都由明确格式决定,三组记录均产生干净且稳定的标准输出。", | ||
| "language_delta": "Java 通过 `System.out` 流输出,并区分 `print` 与 `println`;这不同于直接提供全局打印函数的语言。", | ||
| "prediction_prompt": "运行前写出示例产生的完整字符序列,并把最后换行也标明。", | ||
| "transfer_trap": "终端上看起来相同的两行,仍可能因缺少或重复换行而不同;但此评测会统一两种常见行尾编码。" | ||
| }, | ||
| "java-input": { | ||
| "title": "以 `UTF-8` 解码标准输入", | ||
| "concept": "`System.in` 提供字节而不是现成文本。配合 `readAllBytes` 与 `StandardCharsets.UTF_8` 可明确解码;不在 `java.lang` 中的类需要导入或完整名称。", | ||
| "worked_example": "`ByteArrayInputStream` 代替标准输入,字节以 `UTF-8` 解码,两个片段转成整数后相加。演示输出带标签,不会泄露练习结果。", | ||
| "common_mistakes": [ | ||
| "从字节构造字符串时依赖平台默认字符集。", | ||
| "把空输入拆成片段,再尝试解析空整数。" | ||
| ], | ||
| "self_check": [ | ||
| "为何要导入 `StandardCharsets`,而 `String` 无需导入?", | ||
| "哪条分支阻止空输入到达 `Integer.parseInt`?" | ||
| ], | ||
| "exercise_prompt": "以 `UTF-8` 解码全部标准输入,把默认正则空白分隔的每个合法有符号整数相加;没有片段时累加器必须保持零。", | ||
| "objective": "单行、多行与零字节输入都能通过同一解码路径得到正确总和。", | ||
| "language_delta": "部分运行时默认把标准输入暴露为文本,Java 提供的是 `InputStream`,所以字符集选择属于程序契约。", | ||
| "prediction_prompt": "先预测数字分布在多行时的总和,再预测完全没有输入字节时的结果。", | ||
| "transfer_trap": "导入只缩短源码中的类型名称,既不会安装依赖,也不会动态加载所引用的 JDK 类。" | ||
| }, | ||
| "java-variables-types": { | ||
| "title": "变量与类型", | ||
| "concept": "变量与类型用于使用基本值和对象引用前必须声明类型。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示变量与类型的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "带类型的值与 `final` 绑定", | ||
| "concept": "局部声明让每个值拥有编译期类型;基本类型直接保存值,引用指向对象,`final` 阻止重新绑定却不保证对象深层不可变。类级状态则由 `static` 拥有。", | ||
| "worked_example": "演示把姓名引用固定,比较可变整数分数与门槛,并先把结果存入布尔变量,再组成报告;引用不可重绑并未冻结任何对象。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把变量与类型的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决变量与类型练习里的 TODO。", | ||
| "没有确认“变量与类型用于使用基本值和对象引用前必须声明类型。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "规则是至少达到门槛,却把刚好相等判为失败。", | ||
| "以为 `final` 引用会递归冻结所有可达对象。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用变量与类型,然后才到达 `example:` 输出?", | ||
| "变量与类型练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "示例中哪个声明可再次赋值,哪个不能?", | ||
| "分数和门槛都为零时,比较结果是什么?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把变量与类型应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把姓名、分数与门槛保存在适当声明类型中,使用包含相等边界的比较产生布尔报告;零分零门槛也必须判为通过。", | ||
| "objective": "在一条记录中连接 `String`、`int` 与 `boolean`,同时保留相等边界。", | ||
| "language_delta": "Java 局部类型和 `final` 是编译期约束;固定引用类似不可重绑名称,却不承诺引用对象不可修改。", | ||
| "prediction_prompt": "先计算高于门槛的比较,再单独计算恰好等于门槛的情况。", | ||
| "transfer_trap": "不要把 JavaScript 真假性带入 Java:整数不能直接放在要求布尔值的条件位置。" | ||
| }, | ||
| "java-numbers-operators": { | ||
| "title": "数字与运算符", | ||
| "concept": "数字与运算符用于许多 stdin 题目需要保持整数除法和取余。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示数字与运算符的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "朝零截断的整数运算", | ||
| "concept": "两个 `int` 使用 `/` 时,商朝零截断;`%` 给出配套余数,余数符号跟随被除数。商乘除数再加余数必须重建被除数。", | ||
| "worked_example": "19 除以 4 得到商 4、余数 3;两者满足除法恒等式,示例再用文字标签与练习要求的紧凑格式区分。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把数字与运算符的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决数字与运算符练习里的 TODO。", | ||
| "没有确认“数字与运算符用于许多 stdin 题目需要保持整数除法和取余。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "换成 `Math.floorDiv`,导致负数商改为向下取整。", | ||
| "以向下取模规则推导余数,而没有使用 Java 的 `%`。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用数字与运算符,然后才到达 `example:` 输出?", | ||
| "数字与运算符练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "被除数为负、除数为正时,商朝哪个方向取整?", | ||
| "如何用乘法与加法核验每组商余数?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把数字与运算符应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "解析两个整数并保证除数非零,使用 Java 原生整数 `/` 与 `%`,按冒号顺序输出商和余数;三个符号组合都需维持恒等式。", | ||
| "objective": "不引入其他语言的向下取整规则,也能正确完成有符号整数除法。", | ||
| "language_delta": "Python 的 `//` 朝负无穷取整,而 Java 的整数 `/` 朝零截断;负数案例最能暴露差异。", | ||
| "prediction_prompt": "执行前计算 `-17 / 5` 与 `-17 % 5` 两个字段。", | ||
| "transfer_trap": "任一操作数改成浮点数都会选择不同运算,不再展示整数截断语义。" | ||
| }, | ||
| "java-strings": { | ||
| "title": "字符串", | ||
| "concept": "字符串用于比较或输出前需要清理文本并提取子串。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示字符串的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "字符串清理与内容比较", | ||
| "concept": "`strip` 删除两侧由 `Character.isWhitespace` 识别的码点并返回新 `String`,不会修改来源。长度与索引按 `UTF-16` 编码单元计算;`.equals` 比内容,`==` 比引用。", | ||
| "worked_example": "示例先清理花括号文本,确认首尾是一对匹配分隔符,再用 `substring` 取得内部;原字符串保持不变。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把字符串的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决字符串练习里的 TODO。", | ||
| "没有确认“字符串用于比较或输出前需要清理文本并提取子串。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "用 `==` 判断两个独立构造字符串的文本内容。", | ||
| "未确认匹配括号,就机械删除首尾编码单元。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用字符串,然后才到达 `example:` 输出?", | ||
| "字符串练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "为何子串结束索引是 `length() - 1` 而不是 `length()`?", | ||
| "一个字符由代理对表示时,`length` 实际计算什么?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把字符串应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "先对输入调用 `strip`,确认外层是一对方括号或花括号后只移除这一对,并完整保留其中的 Unicode 文本。", | ||
| "objective": "不依赖引用相等,也不修改原字符串,即可规范三种包裹文本。", | ||
| "language_delta": "Java 和 JavaScript 都索引 `UTF-16` 单元;Python 字符串索引则以 Unicode 码点为单位。", | ||
| "prediction_prompt": "针对带方括号的韩文输入,逐步写出清理后长度和子串边界。", | ||
| "transfer_trap": "`UTF-16` 单元和 Unicode 码点都不等于用户感知的字素簇。" | ||
| }, | ||
| "java-control-flow": { | ||
| "title": "控制流", | ||
| "concept": "控制流用于分支和循环决定哪些值进入累加器。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示控制流的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "分支与有界循环", | ||
| "concept": "`for` 控制从一到上界的闭区间,`if` 只让奇数进入累加器。Java 条件必须是布尔值,不能把非零整数当作真。", | ||
| "worked_example": "计数器从 1 前进到 7,只有除以二后余数非零的值参与,因此累加 1、3、5、7,并以独立标签输出所得总和。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把控制流的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决控制流练习里的 TODO。", | ||
| "没有确认“控制流用于分支和循环决定哪些值进入累加器。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "使用 `< limit`,上界恰为奇数时悄悄遗漏它。", | ||
| "直接把整数写进 `if` 条件,误以为 Java 支持数字真假性。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用控制流,然后才到达 `example:` 输出?", | ||
| "控制流练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "上界为六时,哪些循环值会贡献总和?", | ||
| "上界为零时,循环体为什么一次也不运行?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把控制流应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "遍历从一到输入上界的完整闭区间,只把奇数加入累加器;零边界、奇数终点与偶数终点均须正确输出。", | ||
| "objective": "协调循环边界、奇偶分支和累加器,覆盖零及正上界。", | ||
| "language_delta": "Java 要求条件具有 `boolean` 类型,不像某些语言会把零与非零数强制转换为真假。", | ||
| "prediction_prompt": "上界为三时,列出每个访问值并标记哪些会改变总和。", | ||
| "transfer_trap": "箭头式 `switch` 避免贯穿,但普通循环仍需正确选择初始化、条件和更新。" | ||
| }, | ||
| "java-methods": { | ||
| "title": "方法", | ||
| "concept": "方法用于可复用计算需要命名边界。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示方法的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-enum-switch": { | ||
| "title": "穷尽的枚举 `switch` 表达式", | ||
| "concept": "枚举定义有限的单例常量集合。`switch` 表达式可用箭头规则把每个常量映射为值,不会意外贯穿到下一分支。", | ||
| "worked_example": "示例分别把 `STARTED` 与 `STOPPED` 映射成动作,再把表达式结果赋给字符串,仅在演示输出时增加前缀。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把方法的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决方法练习里的 TODO。", | ||
| "没有确认“方法用于可复用计算需要命名边界。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "添加宽泛 `default`,枚举扩展后反而隐藏未处理常量。", | ||
| "误以为箭头分支会继续执行下一分支体。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用方法,然后才到达 `example:` 输出?", | ||
| "方法练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "缺少一个枚举分支且没有默认分支时,编译器会如何反馈?", | ||
| "单个状态会执行几个箭头规则?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把方法应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "修正三个任务状态各自的文本映射,同时保持 `switch` 表达式穷尽且不增加掩盖遗漏的默认分支。", | ||
| "objective": "无需可变结果变量或贯穿行为,把封闭枚举状态转换成文本。", | ||
| "language_delta": "现代 Java `switch` 表达式直接产生值,早期仅语句形式常需赋值和 `break`。", | ||
| "prediction_prompt": "选择 `BLOCKED` 进入的确切分支,并判断之后是否还会执行其他分支。", | ||
| "transfer_trap": "枚举上的穷尽性不会让任意字符串自动安全;`valueOf` 仍会拒绝未知名称。" | ||
| }, | ||
| "java-input": { | ||
| "title": "输入解析", | ||
| "concept": "输入解析用于stdin 字节必须变成带类型的 token。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示输入解析的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-methods": { | ||
| "title": "检查点:带类型的静态方法", | ||
| "concept": "方法签名固定参数与返回类型,每个实参值都会进入新的形参变量。此检查点只让面积计算位于一个带类型的静态方法中,避免在入口重复表达式。", | ||
| "worked_example": "面积计算位于 `area(int, int)`,入口方法提供独立演示尺寸;返回值先穿过方法边界,再参与输出格式化。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把输入解析的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决输入解析练习里的 TODO。", | ||
| "没有确认“输入解析用于stdin 字节必须变成带类型的 token。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "在入口方法重复乘法,而不完成具名面积方法。", | ||
| "认为被调用方法能替换调用方的引用变量,只因两者一度指向同一对象。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用输入解析,然后才到达 `example:` 输出?", | ||
| "输入解析练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`area` 声明了什么返回类型和哪两个形参类型?", | ||
| "为什么入口应调用 `area`,而不是再次写出乘法表达式?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把输入解析应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "完成返回矩形乘积的静态面积方法,把标准输入解析和最终输出继续保留在入口方法;零尺寸也不得绕过调用。", | ||
| "objective": "用可复用的返回值边界表达计算,而不是把全部逻辑塞进入口。", | ||
| "language_delta": "Java 在编译期检查形参与返回类型,而且静态面积方法无需构造 `Solution` 对象即可调用。", | ||
| "prediction_prompt": "追踪两个已解析整数进入形参、经过返回语句、再回到 `println` 的路径。", | ||
| "transfer_trap": "若传入对象,Java 复制的是引用值;被调用方法仍不能据此替换调用方保存引用的变量。" | ||
| }, | ||
| "java-arrays-collections": { | ||
| "title": "数组与集合", | ||
| "concept": "数组与集合用于固定数组、可增长 List、键值 Map 和唯一 Set 负责不同容器任务。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示数组与集合的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-overloading-varargs": { | ||
| "title": "重载与可变参数数组", | ||
| "concept": "重载让同一方法名拥有多个签名,编译器选择适用者;`int...` 形参进入方法体后就是 `int[]`。", | ||
| "worked_example": "可变参数重载先汇总两项分数,再把计算出的整数委托给固定参数格式化重载;第二次调用因此选择非可变参数签名。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把数组与集合的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决数组与集合练习里的 TODO。", | ||
| "没有确认“数组与集合用于固定数组、可增长 List、键值 Map 和唯一 Set 负责不同容器任务。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "题目要求数值总和,却返回可变参数元素个数。", | ||
| "以为重载分派要等到运行时检查数组内容。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用数组与集合,然后才到达 `example:` 输出?", | ||
| "数组与集合练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "为何 `label(name, total)` 选择双参数重载?", | ||
| "姓名后只有一个零时,可变参数方法收到什么数组?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把数组与集合应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "汇总解析后的完整分数数组,再把该总和委托给固定参数格式化重载;单项零与多项负数都须选择正确签名。", | ||
| "objective": "同时观察编译期重载解析和可变参数在方法体内的数组性质。", | ||
| "language_delta": "Java 可变参数参与静态适用性与重载排名,不同于 JavaScript 的剩余参数分派。", | ||
| "prediction_prompt": "计算输出前,依次指出示例两次调用分别选中的重载。", | ||
| "transfer_trap": "新增一个过宽重载可能令调用产生歧义,即使每个实现单独看都合法。" | ||
| }, | ||
| "java-classes-objects": { | ||
| "title": "类与对象", | ||
| "concept": "类与对象用于对象需要同时拥有实例状态和行为。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示类与对象的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-packages-imports": { | ||
| "title": "导入与完整限定名称", | ||
| "concept": "导入让源码使用简单类型名,同一编译单元也可用包限定名称引用其他类型,例如把导入的 `ArrayList` 赋给 `java.util.List`。", | ||
| "worked_example": "变量以 `List` 接口声明,构造器通过导入使用 `ArrayList`。连接其中单词证明集合确实参与运行。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把类与对象的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决类与对象练习里的 TODO。", | ||
| "没有确认“类与对象用于对象需要同时拥有实例状态和行为。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "认为导入会下载库或执行初始化代码。", | ||
| "导入两个同名类型后,期待 Java 自动猜出目标。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用类与对象,然后才到达 `example:` 输出?", | ||
| "类与对象练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "移除 `ArrayList` 导入后,哪种完整写法仍能工作?", | ||
| "为什么 `String` 不需要导入或写成 `java.lang.String`?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把类与对象应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把所有输入片段加入以完整接口名声明、由导入实现创建的列表,再无分隔符连接;空输入仍须输出一行。", | ||
| "objective": "在实际使用集合时,区分命名便利与类型可用性。", | ||
| "language_delta": "Java 导入只改变编译单元的名称解析,不会像 Python 模块执行或 JavaScript 模块加载那样运行代码。", | ||
| "prediction_prompt": "运行前把示例里的每个简单名和限定名解析到所属包。", | ||
| "transfer_trap": "通配导入不包含子包,也不能解决两个简单名称本就含糊的情况。" | ||
| }, | ||
| "java-constructors": { | ||
| "title": "构造器", | ||
| "concept": "构造器用于新对象必须以有效字段值开始。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示构造器的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-arrays-collections": { | ||
| "title": "观察数组与三种集合", | ||
| "concept": "数组长度固定,泛型 `List`、`Map`、`Set` 则分别表达顺序、键值频率与唯一性。流可从映射条目推导聚合,但终止操作才真正执行遍历。", | ||
| "worked_example": "每个数组值都进入列表、频率映射和唯一集合;总和从键乘出现次数重建,因此三个输出字段都确实观察所标容器。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把构造器的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决构造器练习里的 TODO。", | ||
| "没有确认“构造器用于新对象必须以有效字段值开始。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "契约要求从映射条目求和,却直接输出数组长度。", | ||
| "存在重复值时混淆列表大小、集合大小与映射键数。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用构造器,然后才到达 `example:` 输出?", | ||
| "构造器练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "输入 `2 2 3` 后,三个集合各保存什么?", | ||
| "计算映射总和的流以哪个终止操作结束?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把构造器应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "填充列表、频率映射和唯一集合,并从频率条目计算报告第一字段;重复输入和空输入都不得回头读取数组长度冒充总和。", | ||
| "objective": "用一条对重复敏感的报告解释三种可观察集合语义。", | ||
| "language_delta": "Java 数组是保留运行时元素类型的定长对象,泛型集合元素类型主要在静态检查后被擦除。", | ||
| "prediction_prompt": "先画出重复输入时 `List`、`Map`、`Set` 的状态,再计算三个字段。", | ||
| "transfer_trap": "流直到终止操作才执行,而且一次消费后不能重复使用同一个流对象。" | ||
| }, | ||
| "java-encapsulation": { | ||
| "title": "封装", | ||
| "concept": "封装用于调用方应通过方法更新状态而不是直接访问字段。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示封装的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-generics": { | ||
| "title": "泛型元素类型保持", | ||
| "concept": "类型参数把输入与输出连接起来:`<T> T last(List<T>)` 返回列表相同的元素类型。原始类型会丢弃这种有用检查。", | ||
| "worked_example": "颜色列表使类型推断选择 `String`,辅助方法取得最后位置并直接返回字符串,无需强制转换。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把封装的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决封装练习里的 TODO。", | ||
| "没有确认“封装用于调用方应通过方法更新状态而不是直接访问字段。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "使用原始 `List`,把本可预防的类型错误推迟到运行时。", | ||
| "固定读取索引零,多项输入时错误返回首元素。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用封装,然后才到达 `example:` 输出?", | ||
| "封装练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "传入 `List<Integer>` 时,同一辅助方法的 `T` 是什么?", | ||
| "从 `size()` 减一前需要满足哪项前置条件?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把封装应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "完成泛型列表最后元素的选择,保持调用方元素类型且不使用原始集合或强制转换;单项与多项列表都要读取最远端。", | ||
| "objective": "用一个类型变量表达调用方无需转换即可依赖的输入输出关系。", | ||
| "language_delta": "Java 泛型通常擦除,数组却保留运行时元素类型;即便如此,编译期关系仍能阻止许多无效调用。", | ||
| "prediction_prompt": "对每组词列表,先推断方法的 `T`,再计算选中的索引。", | ||
| "transfer_trap": "擦除不代表原始集合安全,只表示大量泛型表示不会保留到运行时。" | ||
| }, | ||
| "java-static-members": { | ||
| "title": "static 成员", | ||
| "concept": "static 成员用于值属于类而不是某个实例。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示static 成员的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-streams-lambdas": { | ||
| "title": "惰性流与 Lambda", | ||
| "concept": "流先描述元素处理,直到终止操作请求结果。这里 `IntStream` 以 Lambda 筛选偶数,再用 `map` 平方,最后 `sum` 只遍历一次。", | ||
| "worked_example": "`IntStream.of` 建立两个演示值,谓词保留两个偶数,映射平方后由终止求和产生带标签结果。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把static 成员的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决static 成员练习里的 TODO。", | ||
| "没有确认“static 成员用于值属于类而不是某个实例。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "只建立筛选和映射,却从未调用终止操作。", | ||
| "`sum` 已消费遍历后,再次使用同一个流。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用static 成员,然后才到达 `example:` 输出?", | ||
| "static 成员练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "示例的谓词与映射在什么时刻真正求值?", | ||
| "零为何通过偶数筛选,却不增加平方总和?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把static 成员应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "从解析序列中保留偶数、将其平方,并用一个终止 `sum` 得到结果;混合负数和没有偶数的案例都须自然处理。", | ||
| "objective": "把惰性中间操作连接到一个可观察的数值结果。", | ||
| "language_delta": "Java 流是单次遍历流水线,不是可重用集合容器,也不是 JavaScript 数组。", | ||
| "prediction_prompt": "针对含负数案例,分别写出筛选后和映射后的序列。", | ||
| "transfer_trap": "流的遇见顺序与惰性不代表自动并行,并行执行需要另行选择。" | ||
| }, | ||
| "java-enum-switch": { | ||
| "title": "enum 与 switch", | ||
| "concept": "enum 与 switch用于固定状态集合需要映射为明确结果。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示enum 与 switch的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-comparators-sorting": { | ||
| "title": "组合 `Comparator` 排序规则", | ||
| "concept": "`comparingInt`、`reversed` 与 `thenComparing` 逐项表达排序规则,避免用减法比较极端整数时溢出。", | ||
| "worked_example": "Kai 与 Bea 分数相同,第二阶段按姓名升序让 Bea 先出现;示例排序后读取第一条记录。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把enum 与 switch的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决enum 与 switch练习里的 TODO。", | ||
| "没有确认“enum 与 switch用于固定状态集合需要映射为明确结果。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "反转整条已组合比较器,连姓名平局规则也被颠倒。", | ||
| "返回左右分数之差,整数溢出后破坏比较契约。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用enum 与 switch,然后才到达 `example:` 输出?", | ||
| "enum 与 switch练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "分数相等时由哪个比较阶段决定顺序?", | ||
| "只有一个负分用户时,为什么它仍是列表第一项?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把enum 与 switch应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "组合分数降序与姓名升序两个方向,排序后输出第一位用户;同分案例必须由第二规则决定,而不是依赖输入先后。", | ||
| "objective": "把稳定业务顺序编码为清晰且可复用的比较器组合。", | ||
| "language_delta": "Java `Comparator` 对象可为任意记录显式定义顺序,普通 `<` 运算符不能直接排序对象。", | ||
| "prediction_prompt": "手工排序三用户案例,每次只应用一个比较阶段并记录变化。", | ||
| "transfer_trap": "`reversed()` 只反转此前已经构建的比较器,调用位置不同会改变平局行为。" | ||
| }, | ||
| "java-exceptions": { | ||
| "title": "异常", | ||
| "concept": "异常用于坏输入需要恢复但不能隐藏无关失败。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示异常的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-classes-objects": { | ||
| "title": "每个实例独立拥有状态", | ||
| "concept": "每个对象拥有自己的实例字段,方法为该状态提供行为。参数只是局部值,只有明确写入接收者字段,实例才会发生变化。", | ||
| "worked_example": "计数器从 10 开始,通过 `add` 接收增量 2;方法修改当前实例的字段,入口随后观察到 12。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把异常的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决异常练习里的 TODO。", | ||
| "没有确认“异常用于坏输入需要恢复但不能隐藏无关失败。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "只改变参数变量,误以为对象字段会随之更新。", | ||
| "把全部状态声明为 `static`,让本应独立的实例共享一个值。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用异常,然后才到达 `example:` 输出?", | ||
| "异常练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`add` 中哪一个储存位置必须改变,哪个局部变量只携带实参?", | ||
| "两个计数器实例为何应能保有不同数值?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把异常应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把解析后的增量传给计数器方法,并让该方法更新接收者自己的字段;不得只改形参,正负初值都须由实例保存结果。", | ||
| "objective": "通过最小类实例观察对象身份与受方法封装的修改。", | ||
| "language_delta": "Java 类使用名义声明类型与显式构造器,不同于只按结构兼容的对象形状。", | ||
| "prediction_prompt": "方法调用前后分别追踪计数器字段与 `delta` 参数,切勿把两者混为一处。", | ||
| "transfer_trap": "私有访问可阻止外部直接写入,却只有方法逻辑才能真正保护领域不变量。" | ||
| }, | ||
| "java-generics": { | ||
| "title": "泛型", | ||
| "concept": "泛型用于一个方法需要保留调用方的元素类型。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示泛型的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-constructors": { | ||
| "title": "由构造器建立不变量", | ||
| "concept": "构造器名称与类相同且没有返回类型;正常结束前必须给每个空白 `final` 实例字段赋值,常用 `this.field` 区分同名形参。", | ||
| "worked_example": "矩形构造器把两个实参分别写入固定尺寸;对象正常创建后,面积方法便可假定宽高两个字段都已经建立。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把泛型的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决泛型练习里的 TODO。", | ||
| "没有确认“泛型用于一个方法需要保留调用方的元素类型。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "写成 `void Rectangle(...)`,实际声明普通方法而非构造器。", | ||
| "把形参赋给自身,留下同名字段未初始化。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用泛型,然后才到达 `example:` 输出?", | ||
| "泛型练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`this.width = width` 左侧与右侧分别指什么?", | ||
| "为何漏赋一个 `final` 字段的构造路径会被编译器拒绝?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把泛型应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "在构造器中用两个同名实参分别初始化宽和高,使面积方法始终读取有效固定字段;禁止以一作为占位高度。", | ||
| "objective": "对象创建时一次性建立完整状态,而不留下占位尺寸。", | ||
| "language_delta": "Java 构造器是特殊声明,不是返回初始化值的普通函数。", | ||
| "prediction_prompt": "计算面积前,先解析两条赋值中字段与同名形参的对应关系。", | ||
| "transfer_trap": "固定字段防止构造后重新赋值,却不会递归冻结它可能引用的对象。" | ||
| }, | ||
| "java-interfaces": { | ||
| "title": "接口", | ||
| "concept": "接口用于调用方应依赖行为契约而不是具体类。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示接口的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-encapsulation": { | ||
| "title": "私有状态与显式规则", | ||
| "concept": "私有字段向无关代码隐藏表示,但公开操作仍须验证请求变化;可见性与领域不变量解决的是两类问题。", | ||
| "worked_example": "账户依次接收正二、负七、正四,方法接受两次正存款并忽略负请求;读取方法最终只暴露计算后的余额六。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把接口的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决接口练习里的 TODO。", | ||
| "没有确认“接口用于调用方应依赖行为契约而不是具体类。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "认为 `private` 会自动拒绝经方法传入的非法值。", | ||
| "直接返回可变内部集合,绕过原本打算维护的边界。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用接口,然后才到达 `example:` 输出?", | ||
| "接口练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "示例中哪一次调用不会改变余额?", | ||
| "账户类内部代码仍能给私有字段写入非法值吗?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把接口应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把只接受正增量的规则放进存款方法,并只通过读取方法报告余额;负数和空输入不得修改初始零状态。", | ||
| "objective": "在真正拥有状态转移的方法处执行一个清晰不变量。", | ||
| "language_delta": "Java 访问修饰符在编译期和运行时限制成员访问,却不会自动执行领域验证。", | ||
| "prediction_prompt": "依次应用每个解析增量,并记录守卫接受或拒绝哪一项。", | ||
| "transfer_trap": "读取方法若返回可变内部对象,仍会泄漏封装;应根据需要返回安全视图或副本。" | ||
| }, | ||
| "java-inheritance-composition": { | ||
| "title": "继承与组合", | ||
| "concept": "继承与组合用于子类还需要辅助对象来完成行为。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示继承与组合的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-static-members": { | ||
| "title": "类拥有的 `static` 常量", | ||
| "concept": "静态字段属于类,由所有调用与实例共享。`static final` 固定字段绑定,但若绑定的是对象,该对象内部仍可能可变。", | ||
| "worked_example": "缩放方法在类上下文读取 `FACTOR` 并乘演示数值,访问两个静态成员都不需要创建实例。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把继承与组合的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决继承与组合练习里的 TODO。", | ||
| "没有确认“继承与组合用于子类还需要辅助对象来完成行为。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "只为调用静态工具而创建对象,掩盖类级所有权。", | ||
| "把 `final` 误读成任意字段类型的深层不可变承诺。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用继承与组合,然后才到达 `example:` 输出?", | ||
| "继承与组合练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "创建十个类实例后,`FACTOR` 有几个储存位置?", | ||
| "静态方法没有实例时能直接读取实例字段吗?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把继承与组合应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "修正全类共享的缩放因子,让静态方法对正数、零和负数都执行同一乘法;不要创建无意义实例,也不要把因子移入每对象字段。", | ||
| "objective": "有意使用类级状态,并把它与每对象字段清楚区分。", | ||
| "language_delta": "Java 的 `static` 把成员附着到声明类,不同于没有容器类型的模块级绑定。", | ||
| "prediction_prompt": "把类因子代入三个有符号输入的乘法,逐项预测结果。", | ||
| "transfer_trap": "可变静态字段会让所有实例共享状态,多线程访问时往往还需要同步。" | ||
| }, | ||
| "java-records": { | ||
| "title": "record", | ||
| "concept": "record用于不可变数据载体需要减少样板代码。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示record的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-interfaces": { | ||
| "title": "通过接口调用共同行为", | ||
| "concept": "接口为公开行为命名契约。接收 `Named` 的代码可调用 `name()`,无需知道实现来自记录、普通类还是其他声明类型。", | ||
| "worked_example": "用户记录通过访问器实现 `Named`,渲染器只依赖接口,并以 `Locale.ROOT` 执行稳定的大写转换。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把record的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决record练习里的 TODO。", | ||
| "没有确认“record用于不可变数据载体需要减少样板代码。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "缩小实现方法可见性,尽管接口方法本来就是公开的。", | ||
| "让渲染器直接依赖用户记录,平白耦合单一实现。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用record,然后才到达 `example:` 输出?", | ||
| "record练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "声明为 `Named` 的变量可调用哪个方法?", | ||
| "另一个类不继承用户记录,也能参与该契约吗?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把record应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "通过面向接口的辅助方法完成稳定大写转换;拉丁与重音文本都只依赖 `Named` 契约,不读取具体记录表示。", | ||
| "objective": "针对行为契约调用共同功能,而不是绑定具体实现。", | ||
| "language_delta": "Java 接口是名义式的,实现类必须明确声明;这不同于 TypeScript 的结构兼容。", | ||
| "prediction_prompt": "先沿 `Named` 引用调用记录访问器,再应用 `Locale.ROOT` 大小写转换。", | ||
| "transfer_trap": "接口默认方法可以提供行为,却不会让接口字段变成每个实例独有的状态。" | ||
| }, | ||
| "java-optional": { | ||
| "title": "Optional", | ||
| "concept": "Optional用于缺失值必须显式处理。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示Optional的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-inheritance-composition": { | ||
| "title": "继承配合组合", | ||
| "concept": "继承表达可替代的“是一个”关系,组合表达“拥有一个”关系。玩家复用基类分数状态,同时持有独立的奖励记录。", | ||
| "worked_example": "构造时把基础值 10 交给 `super`,另行创建值为 2 的奖励对象;总分方法读取继承与组合两处来源得到 12。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把Optional的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决Optional练习里的 TODO。", | ||
| "没有确认“Optional用于缺失值必须显式处理。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "忽略组合字段,只返回继承得到的基础值。", | ||
| "仅为复用代码使用继承,尽管子类无法满足基类预期。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用Optional,然后才到达 `example:` 输出?", | ||
| "Optional练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪个构造器初始化受保护的基础字段?", | ||
| "奖励为何适合作为字段,而不是玩家的超类?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把Optional应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "在总分方法中把继承的基础值与组合奖励相加;两个构造参数必须进入各自对象,零和负基础值也不得遗漏奖励。", | ||
| "objective": "依据语义关系区分并同时观察两种复用机制。", | ||
| "language_delta": "Java 只允许一个类超类,却可实现多个接口,因此独立组成部分尤其适合组合。", | ||
| "prediction_prompt": "相加前追踪两个构造参数分别进入基类字段和奖励记录的路径。", | ||
| "transfer_trap": "受保护可见性只授权特定访问,不会把任意拥有关系自动变成健全子类型。" | ||
| }, | ||
| "java-streams-lambdas": { | ||
| "title": "Stream 与 lambda", | ||
| "concept": "Stream 与 lambda用于集合处理用流水线更清晰。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示Stream 与 lambda的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-exceptions": { | ||
| "title": "窄范围异常恢复", | ||
| "concept": "`Integer.parseInt` 以未检查的 `NumberFormatException` 报告畸形整数。只捕获这一失败,才能使用备用值而不吞掉无关程序缺陷。", | ||
| "worked_example": "解析 `oops` 进入精确捕获并返回调用方提供的备用值;其他意外运行错误不会被辅助方法隐藏。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把Stream 与 lambda的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决Stream 与 lambda练习里的 TODO。", | ||
| "没有确认“Stream 与 lambda用于集合处理用流水线更清晰。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "捕获整个 `Exception`,掩盖无关编程错误。", | ||
| "以为 `parseInt` 对畸形文本返回 `Optional` 或哨兵值。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用Stream 与 lambda,然后才到达 `example:` 输出?", | ||
| "Stream 与 lambda练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "合法负整数会进入捕获块吗?", | ||
| "哪个表达式才是此处希望恢复的异常来源?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把Stream 与 lambda应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "成功解析时返回原数值,仅在数字语法非法并抛出 `NumberFormatException` 时采用调用方备用值;其他失败不得被捕获。", | ||
| "objective": "只在最小相关异常边界恢复输入,并保证任何其他故障不会被掩盖。", | ||
| "language_delta": "Java 区分必须处理或声明的已检查异常,以及 `NumberFormatException` 这类未检查运行时异常。", | ||
| "prediction_prompt": "对每个候选片段,在计算备用值前先选择正常返回或捕获返回路径。", | ||
| "transfer_trap": "数据缺失、格式畸形和输入输出故障意义不同,一个全捕获路径会抹平这些区别。" | ||
| }, | ||
| "java-comparators-sorting": { | ||
| "title": "Comparator 与排序", | ||
| "concept": "Comparator 与排序用于对象需要与数据形状分离的排序规则。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示Comparator 与排序的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-optional": { | ||
| "title": "用 `Optional` 筛选并回退", | ||
| "concept": "`Optional` 表示可能缺失的返回值;`filter` 只在现有值满足谓词时保留它,`orElse` 无需危险的 `get()` 即可提供备用文本。", | ||
| "worked_example": "现有单词 `cat` 满足不超过三个字符的长度谓词,所以不会选择备用值;演示标签只用于区分示例行,不参与容器判断。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把Comparator 与排序的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决Comparator 与排序练习里的 TODO。", | ||
| "没有确认“Comparator 与排序用于对象需要与数据形状分离的排序规则。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "未证明存在就调用 `get()`,冒险触发 `NoSuchElementException`。", | ||
| "在字段和参数中到处使用 `Optional`,而不是主要用于表达可能缺失的返回。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用Comparator 与排序,然后才到达 `example:` 输出?", | ||
| "Comparator 与排序练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "容器本来为空时,`filter` 会调用谓词吗?", | ||
| "短横线占位值 `-` 通过哪条路径得到 `missing`?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把Comparator 与排序应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "先把短横线占位值 `-` 转换为缺失、其他文本转换为存在,再只保留长度不超过三个字符的片段,并用 `orElse` 声明式选择备用值。", | ||
| "objective": "无需不安全提取,即可区分缺失输入、过长文本与不超过三个字符的现有文本。", | ||
| "language_delta": "`Optional` 是显式容器,但仍允许错误调用 `get()`;它提供处理路径,不会自动强制每个调用方安全。", | ||
| "prediction_prompt": "对四组案例分别标记构造后和筛选后的容器状态。", | ||
| "transfer_trap": "需要诊断信息的失败应使用合适异常或结果模型,`Optional` 不能取代它们。" | ||
| }, | ||
| "java-try-with-resources": { | ||
| "title": "try-with-resources", | ||
| "concept": "try-with-resources用于代码提前退出时资源也必须关闭。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示try-with-resources的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "可观察的资源自动关闭", | ||
| "concept": "资源式 `try` 无论正常结束,还是因返回、跳转或异常提前退出,都会关闭声明的 `AutoCloseable` 值。真正从该资源读取,才能让生存期管理参与数据路径而非仅作装饰。", | ||
| "worked_example": "受管 `ByteArrayInputStream` 保存 `demo` 字节,区块内以 `UTF-8` 解码、去空白并大写,随后自动关闭。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把try-with-resources的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决try-with-resources练习里的 TODO。", | ||
| "没有确认“try-with-resources用于代码提前退出时资源也必须关闭。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "声明一个资源,却继续从另一个未受管流读取。", | ||
| "忘记区块已抛错时,关闭失败可能作为受抑制异常附加。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用try-with-resources,然后才到达 `example:` 输出?", | ||
| "try-with-resources练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "示例区块结束后,哪个对象保证收到 `close()`?", | ||
| "空字节序列在输出前应转换成什么文本?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把try-with-resources应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "只处理从受管流实际读出的字节,按 `UTF-8` 解码、去空白并大写;空内容使用约定名称,离开区块后不再访问资源。", | ||
| "objective": "把确定性资源生存期连接到最终到达标准输出的数据。", | ||
| "language_delta": "Java 以控制流语句编码类似资源获取即初始化的清理,尽管普通对象生存期由垃圾回收管理。", | ||
| "prediction_prompt": "追踪输入字节经过资源构造、读取、解码、去空白和大小写转换的每一步。", | ||
| "transfer_trap": "垃圾回收不能及时替代文件、套接字及其他外部资源的关闭。" | ||
| }, | ||
| "java-packages-imports": { | ||
| "title": "package 与 import", | ||
| "concept": "package 与 import用于单文件练习仍需要 JDK 包中的类。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示package 与 import的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-equality-hashcode": { | ||
| "title": "相等与散列一致性", | ||
| "concept": "逻辑相等必须自反、对称、传递、稳定且能处理空值。两个对象若相等,其散列码也必须相同,散列集合才能遵守值语义。", | ||
| "worked_example": "记录自动提供基于组件值的 `equals` 与 `hashCode`,所以两个坐标相同的点在 `HashSet` 中合并为一项。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把package 与 import的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决package 与 import练习里的 TODO。", | ||
| "没有确认“package 与 import用于单文件练习仍需要 JDK 包中的类。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "只比较一个坐标,把不同点判成相等。", | ||
| "重写 `equals` 后保留可能让相等对象散列不同的实现。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用package 与 import,然后才到达 `example:` 输出?", | ||
| "package 与 import练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "为何不相等对象可以碰巧共享散列码,而相等对象不能散列不同?", | ||
| "模式 `instanceof Point other` 如何同时完成类型判断和绑定?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把package 与 import应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "让两个坐标都参与 `equals`,并同步维持相等对象散列相同的规则,再通过 `HashSet` 大小观察两个点是否合并。", | ||
| "objective": "实现一个在散列集合中仍正确工作的值契约。", | ||
| "language_delta": "Java `Object` 默认相等按身份判断,记录则自动采用组件值语义。", | ||
| "prediction_prompt": "先把每对点归类为相等或不等,再判断集合是否增加第二项。", | ||
| "transfer_trap": "良好散列分布只改善性能;相等必有相同散列才是正确性要求。" | ||
| }, | ||
| "java-annotations": { | ||
| "title": "注解", | ||
| "concept": "注解用于元数据应附着在声明而不是运行逻辑中。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示注解的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-records": { | ||
| "title": "检查点:防御性记录快照", | ||
| "concept": "记录只让组件引用不可重新赋值,并自动生成值成员,因此仅具浅层不可变性。紧凑构造器中的 `List.copyOf` 可切断可变来源别名,并拒绝空元素。", | ||
| "worked_example": "紧凑构造器复制 `[4, 5]` 后,向原列表添加新值无法改变记录内列表,其总和仍为 9。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把注解的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决注解练习里的 TODO。", | ||
| "没有确认“注解用于元数据应附着在声明而不是运行逻辑中。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "直接储存调用方可变 `List`,却声称记录深层不可变。", | ||
| "向值语义加入无关可变状态,破坏相等与散列一致性。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用注解,然后才到达 `example:` 输出?", | ||
| "注解练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "紧凑构造器内哪个引用被重新绑定到副本?", | ||
| "若记录保留同一可变列表,删除来源末项为何会影响组件值?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把注解应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "在来源列表删除末项之前,以紧凑构造器防御性复制记录组件,再对保留快照求和;单元素与负数列表也不得失去内容。", | ||
| "objective": "证明记录仅浅层不可变,并用不可变副本修复别名边界。", | ||
| "language_delta": "Java 记录是简洁的名义数据,不是递归冻结对象图,也不是 TypeScript 的结构类型。", | ||
| "prediction_prompt": "画出 `List.copyOf` 前后的来源与组件引用,再模拟删除来源最后一项。", | ||
| "transfer_trap": "固定记录组件只禁止重新赋值;未经防御复制时,其中的可变对象仍可改变。" | ||
| }, | ||
| "java-sealed-classes": { | ||
| "title": "sealed 类", | ||
| "concept": "sealed 类用于领域层次需要列出允许的实现。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示sealed 类的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-annotations": { | ||
| "title": "运行时注解元数据", | ||
| "concept": "`@Target(METHOD)` 限制放置位置,`@Retention(RUNTIME)` 保留元数据供反射读取;没有运行时保留策略,`getAnnotation` 看不到声明前缀。", | ||
| "worked_example": "反射找到规范化方法并读取 `trace` 标签,普通方法调用把 `demo` 文本大写,反射元数据提供输出前缀。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把sealed 类的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决sealed 类练习里的 TODO。", | ||
| "没有确认“sealed 类用于领域层次需要列出允许的实现。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "需要运行时反射,却依赖默认的 `CLASS` 保留策略。", | ||
| "搜索不同方法签名,得不到预期反射方法。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用sealed 类,然后才到达 `example:` 输出?", | ||
| "sealed 类练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪一种保留策略会让虚拟机向反射暴露注解?", | ||
| "哪个目标限制可阻止标签放在字段上?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把sealed 类应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "修改注解声明,使方法元数据保留到运行程序中,反射查询能取得 audit 前缀;不得在普通逻辑中硬编码该前缀。", | ||
| "objective": "通过反射观察注解值,而不是把元数据当成无用装饰。", | ||
| "language_delta": "Java 注解若要在运行时使用必须明确选择保留策略,很多类型系统注解只需编译期可见。", | ||
| "prediction_prompt": "分别预测加入运行时保留策略前后,`getAnnotation` 返回对象还是空值。", | ||
| "transfer_trap": "保留策略只控制可见性,不会自动产生行为;程序仍须主动读取并应用注解。" | ||
| }, | ||
| "java-testing-assert": { | ||
| "title": "测试与 assert", | ||
| "concept": "测试与 assert用于小检查应在出错方法附近失败。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示测试与 assert的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-sealed-classes": { | ||
| "title": "密封形状与模式 `switch`", | ||
| "concept": "密封接口把直接实现者限制在 `permits` 列表。Java 21 模式 `switch` 可解构每个记录子类型并产生值,无需宽泛默认分支。", | ||
| "worked_example": "矩形模式直接绑定宽和高并把两者相乘,穷尽表达式产生度量;圆与点仍作为清楚可见且分别处理的其他选择。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把测试与 assert的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决测试与 assert练习里的 TODO。", | ||
| "没有确认“测试与 assert用于小检查应在出错方法附近失败。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "只使用虚方法,却没有展示层次对其他直接子类型关闭。", | ||
| "添加默认分支,使新许可形状逃过编译器的穷尽反馈。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用测试与 assert,然后才到达 `example:` 输出?", | ||
| "测试与 assert练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪个获准的记录类型中的组件会在分支中相乘?", | ||
| "为什么 Java 21 的该表达式没有默认分支也能编译?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把测试与 assert应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "修正圆记录模式分支的度量,同时保留密封层次的穷尽 `switch`;矩形、圆和点都要由各自解构分支处理。", | ||
| "objective": "在一个可观察表达式中结合封闭子类型知识与记录解构。", | ||
| "language_delta": "密封类型提供名义层次关闭,不同于无限制基类或结构推断的联合。", | ||
| "prediction_prompt": "对每个形状词选择运行时模式,先绑定组成变量再计算度量。", | ||
| "transfer_trap": "密封只按许可规则控制直接扩展,不会自动让实例不可变。" | ||
| }, | ||
| "java-equality-hashcode": { | ||
| "title": "equals 与 hashCode", | ||
| "concept": "equals 与 hashCode用于HashSet 必须识别相等的值对象。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示equals 与 hashCode的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "java-testing-assert": { | ||
| "title": "综合:始终执行的检查", | ||
| "concept": "综合程序连接 `UTF-8` 解析、方法、记录、泛型集合、流、比较器与空值回退。Java `assert` 默认关闭,必须始终运行的检查应调用普通方法并抛出 `AssertionError`。", | ||
| "worked_example": "两个同分记录按姓名排序,辅助方法以 `check` 验证空列表回退,再输出带标签优胜者;该检查不依赖虚拟机 `-ea`。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把equals 与 hashCode的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决equals 与 hashCode练习里的 TODO。", | ||
| "没有确认“equals 与 hashCode用于HashSet 必须识别相等的值对象。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| "用 Java `assert` 做强制验证,却忘记评测器未开启断言。", | ||
| "只按分数排序,让同分优胜者依赖遇见顺序。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用equals 与 hashCode,然后才到达 `example:` 输出?", | ||
| "equals 与 hashCode练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "虚拟机没有 `-ea` 时,哪种综合检查仍会执行?", | ||
| "同分记录要经过哪两个比较阶段才能得到确定优胜者?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把equals 与 hashCode应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| }, | ||
| "java-overloading-varargs": { | ||
| "title": "重载与 varargs", | ||
| "concept": "重载与 varargs用于方法调用会选择签名并收集额外参数。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示重载与 varargs的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把重载与 varargs的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决重载与 varargs练习里的 TODO。", | ||
| "没有确认“重载与 varargs用于方法调用会选择签名并收集额外参数。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。”这个要点,只去凑 judge 输出。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用重载与 varargs,然后才到达 `example:` 输出?", | ||
| "重载与 varargs练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把重载与 varargs应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "为最佳用户选择补上姓名升序的同分规则,并保留始终执行的空结果检查;普通、同分和空输入都要产生确定记录。", | ||
| "objective": "把核心路径整合为可测试的记录与集合程序,同时让必要验证不受断言开关影响。", | ||
| "language_delta": "Python 优化模式会移除断言,Java 断言默认禁用;普通方法调用在正常执行中始终求值。", | ||
| "prediction_prompt": "先按两个比较阶段排列同分用户,再单独追踪空列表经过回退和 `check` 的路径。", | ||
| "transfer_trap": "断言适合内部假设,不适合验证不可信输入;强制条件需要普通控制流和清晰失败。" | ||
| } | ||
| } | ||
| } |
+324
-249
@@ -7,377 +7,452 @@ { | ||
| "py-output": { | ||
| "title": "print and stdout", | ||
| "concept": "`print` is the boundary between a Python calculation and judge-visible output. It converts each argument to text, inserts spaces between arguments, and ends with a newline by default, so exact stdout matters.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying print and stdout to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Exact output with print", | ||
| "concept": "Python's print function converts its arguments to text, places sep between them, and appends end. Those defaults are convenient for people but remain part of the exact stdout contract checked by a judge.", | ||
| "worked_example": "The sample joins Mina and 9 with an equals sign, then replaces the usual newline ending with an exclamation mark followed by a newline. Both punctuation choices come from print arguments rather than manual concatenation.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the print and stdout rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the print and stdout exercise.", | ||
| "Matching the judge output without checking this lesson idea: `print` is the boundary between a Python calculation and judge-visible output." | ||
| "Leaving the default separating space in place when punctuation must touch both values.", | ||
| "Sending debug labels to stdout, where the judge treats them as additional answer bytes." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies print and stdout before the `example:` output?", | ||
| "Which TODO line in the print and stdout exercise must derive the value from its own data instead of copying the demo?" | ||
| "What characters are written by print(\"A\", \"B\", sep=\":\", end=\"!\")?", | ||
| "Which keyword controls the text emitted after the final argument?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying print and stdout to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Read the name and score, then configure print so they form one colon-delimited record terminated by exactly one newline.", | ||
| "objective": "Given any of the three input records, produce byte-for-byte matching stdout with no debug prefix or surplus whitespace.", | ||
| "language_delta": "Unlike a raw stream write, print supplies both a separator and a line ending. Establishing this boundary early makes every later Python exercise's output contract explicit.", | ||
| "prediction_prompt": "Before running it, write the exact bytes produced by print(\"x\", 0, sep=\"=\", end=\"?\").", | ||
| "transfer_trap": "Do not assume another language's println or console logger shares Python's configurable sep behavior; verify its spacing and newline rules." | ||
| }, | ||
| "py-variables": { | ||
| "title": "Variables", | ||
| "concept": "A variable name is bound to an object by assignment. Rebinding the same name changes what future reads see, while any previous object keeps existing until no references need it.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Variables to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-input": { | ||
| "title": "Read stdin once", | ||
| "concept": "sys.stdin.read gathers the complete stream as one string. Calling split with no separator recognizes arbitrary whitespace, and mapping int across an empty token list leaves sum with its valid identity value of zero.", | ||
| "worked_example": "The demonstration stores a two-line string containing 10 and 4, splits it without caring where the newline occurs, converts both tokens, and prints 14. No interactive prompt is involved.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Variables rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Variables exercise.", | ||
| "Matching the judge output without checking this lesson idea: A variable name is bound to an object by assignment." | ||
| "Using split(\" \"), which leaves newline boundaries inside tokens and treats repeated spaces awkwardly.", | ||
| "Calling input repeatedly for a token stream, then failing when data is wrapped onto different lines." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Variables before the `example:` output?", | ||
| "Which TODO line in the Variables exercise must derive the value from its own data instead of copying the demo?" | ||
| "What list does an empty string produce after calling split with no argument?", | ||
| "Why does one call to sys.stdin.read handle both the single-line and multiline cases?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Variables to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Convert every whitespace-delimited token from the already-read stream and print their total, including the empty-input case.", | ||
| "objective": "Accept tokens separated by spaces or newlines and compute the correct integer sum without special-casing a particular layout.", | ||
| "language_delta": "Python exposes stdin as a text stream here; conversion remains explicit because split returns strings. Later parsers can reuse the same read-once boundary before adding structure.", | ||
| "prediction_prompt": "Predict the value of sum(int(token) for token in \" -2\\n5 \".split()).", | ||
| "transfer_trap": "Scanner APIs in other ecosystems may skip whitespace automatically, but Python's raw stream text does not become numbers until int is called." | ||
| }, | ||
| "py-numbers": { | ||
| "title": "Numbers", | ||
| "concept": "Python integers have arbitrary precision, and numeric operators choose different meanings: `/` returns a float, `//` floors, `%` gives the remainder, and `**` powers. Problems often need the integer operators, not decimal division.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Numbers to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-variables": { | ||
| "title": "Names and rebinding", | ||
| "concept": "A Python variable is a name bound to an object, not a typed storage box. Updating an integer accumulator computes another integer and rebinds the name; an enclosing function instead exposes a cell whose current binding is read when a closure executes.", | ||
| "worked_example": "The sample first binds total to 10 and then augmented assignment rebinds total to 15. Nothing mutates the integer object 10, because integer values are immutable.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Numbers rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Numbers exercise.", | ||
| "Matching the judge output without checking this lesson idea: Python integers have arbitrary precision, and numeric operators choose different meanings: `/` returns a float, `//` floors, `%` gives the remainder, and `**` powers." | ||
| "Describing integer augmented assignment as changing the original integer object in place.", | ||
| "Assuming lambdas created in one loop freeze each iteration value instead of sharing the late-read cell." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Numbers before the `example:` output?", | ||
| "Which TODO line in the Numbers exercise must derive the value from its own data instead of copying the demo?" | ||
| "After total = left and total += right, which name is rebound?", | ||
| "What value do three lambda functions closing over the same completed loop variable observe?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Numbers to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Start the accumulator from the first parsed integer, then rebind it using the second integer before printing.", | ||
| "objective": "Demonstrate rebinding by producing the arithmetic total for positive, negative, and zero-valued inputs.", | ||
| "language_delta": "Python assignment changes name-to-object bindings without declaring mutability on the name. Closures preserve access to binding cells, so call time—not creation time—can determine the observed value.", | ||
| "prediction_prompt": "For readers = [lambda: n for n in range(3)], predict [reader() for reader in readers].", | ||
| "transfer_trap": "JavaScript let creates a fresh loop binding in common loops, so copying a closure pattern from there can hide Python's late-binding behavior." | ||
| }, | ||
| "py-strings": { | ||
| "title": "Strings", | ||
| "concept": "A string is an immutable sequence of Unicode characters. Indexing reads one character, slicing reads a half-open range, and operations such as concatenation create a new string instead of editing the old one.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Strings to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-numbers": { | ||
| "title": "Floor quotient and remainder", | ||
| "concept": "Integer floor division chooses the greatest integer no larger than the mathematical quotient. The modulo result is paired so dividend equals divisor times quotient plus remainder, and a nonzero remainder carries the divisor's sign.", | ||
| "worked_example": "With -11 divided by 4, Python produces a floor quotient of -3 and a remainder of 1. Multiplying -3 by 4 and adding 1 reconstructs -11.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Strings rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Strings exercise.", | ||
| "Matching the judge output without checking this lesson idea: A string is an immutable sequence of Unicode characters." | ||
| "Replacing floor division with int(dividend / divisor), which truncates negative results toward zero.", | ||
| "Assuming modulo is always nonnegative even when the divisor itself is negative." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Strings before the `example:` output?", | ||
| "Which TODO line in the Strings exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which identity connects dividend, divisor, quotient, and remainder?", | ||
| "Why is -7 // 2 one less than int(-7 / 2)?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Strings to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Parse two nonzero-divisor integers and print their Python floor quotient beside the corresponding remainder.", | ||
| "objective": "Correctly handle both positive and negative divisor signs while preserving Python's division identity.", | ||
| "language_delta": "Python // floors signed quotients, while JavaScript and TypeScript number division stays fractional (7 / 2 === 3.5); Java and Rust signed integer division truncate toward zero. The paired values can be returned as a tuple, whereas a set would discard position and duplicates.", | ||
| "prediction_prompt": "Compute both values in the pairs (-7 // 2, -7 % 2) and (7 // -2, 7 % -2).", | ||
| "transfer_trap": "Do not translate / mechanically: JavaScript and TypeScript number operands keep 7 / 2 === 3.5, Java and Rust integer operands truncate, and Python // floors. Signed quotient/remainder cases expose the difference." | ||
| }, | ||
| "py-control-flow": { | ||
| "title": "Control flow", | ||
| "concept": "`if` selects a block, `for` consumes an iterable, and `while` repeats until a condition changes. Python uses indentation as syntax, so moving one line changes which branch or loop owns it.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Control flow to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-tuples-sets": { | ||
| "title": "Ordered pairs and unique members", | ||
| "concept": "A tuple keeps position and duplicates, making it suitable for the two-item input record. A set removes duplicate hashable members and supports membership semantics, but its iteration order should not define answer formatting.", | ||
| "worked_example": "The tuple (\"r\", \"s\", \"r\") joins back to rsr, while converting it to a set leaves two distinct letters. The two outputs intentionally observe different properties of the same source.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Control flow rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Control flow exercise.", | ||
| "Matching the judge output without checking this lesson idea: `if` selects a block, `for` consumes an iterable, and `while` repeats until a condition changes." | ||
| "Writing empty braces for an empty set and accidentally constructing a dictionary.", | ||
| "Joining the set rather than the tuple, thereby discarding duplicates and dependable position." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Control flow before the `example:` output?", | ||
| "Which TODO line in the Control flow exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does the pair (\"a\", \"a\") still contain two positions?", | ||
| "Which constructor creates an empty set without ambiguity?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Control flow to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Keep the parsed values in tuple order for joining and derive a separate set solely for the uniqueness count.", | ||
| "objective": "Print the ordered concatenation and the number of distinct members for duplicate and Unicode inputs.", | ||
| "language_delta": "Tuple unpacking and set construction are separate runtime operations; annotations are not required for either container to enforce its behavior.", | ||
| "prediction_prompt": "Predict both observations for pair = (\"é\", \"e\", \"é\"): the joined tuple and len(set(pair)).", | ||
| "transfer_trap": "A TypeScript tuple is primarily a checker constraint, whereas this Python tuple is a distinct immutable runtime sequence." | ||
| }, | ||
| "py-functions": { | ||
| "title": "Functions", | ||
| "concept": "`def` creates a callable object with parameters and a body. `return` sends a value to the caller; printing inside the function is separate from returning the calculation for later use.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Functions to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-strings": { | ||
| "title": "Unicode-aware string slicing", | ||
| "concept": "Python str indexing and slicing operate on Unicode code points, not UTF-8 bytes. Slices use a half-open interval and create a new string, leaving the immutable source untouched.", | ||
| "worked_example": "The star characters occupy the first and last positions around 서울. Selecting positions 1 through the position before -1 produces 서울 without needing byte offsets.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Functions rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Functions exercise.", | ||
| "Matching the judge output without checking this lesson idea: `def` creates a callable object with parameters and a body." | ||
| "Using strip to remove delimiters even though strip removes any matching boundary characters, not one exact marker.", | ||
| "Calling a code point a user-perceived character when combining marks or emoji sequences may span several code points." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Functions before the `example:` output?", | ||
| "Which TODO line in the Functions exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which source indices are excluded by text[1:-1]?", | ||
| "Why can the same slice remove a one-code-point emoji boundary safely?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Functions to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Remove exactly the first and final code points with one half-open slice, then print the interior text.", | ||
| "objective": "Produce the unwrapped interior for ASCII, bracketed, and emoji-delimited strings without mutating the input.", | ||
| "language_delta": "Python code-point indexing differs from Java and JavaScript UTF-16 code units and from Rust UTF-8 byte indices; none of those units automatically represents a displayed grapheme cluster.", | ||
| "prediction_prompt": "Predict both len(\"🙂go🙂\") and the result of \"🙂go🙂\"[1:-1].", | ||
| "transfer_trap": "Code-point-safe slicing is still not grapheme-aware; a displayed symbol built from combining marks may occupy multiple Python positions." | ||
| }, | ||
| "py-input": { | ||
| "title": "Input parsing", | ||
| "concept": "Coding-test input arrives as text on stdin. A common pattern is to read once, split whitespace into tokens, convert the tokens, and keep the solving logic separate from parsing.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Input parsing to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-pathlib": { | ||
| "title": "Path components with pathlib", | ||
| "concept": "PurePosixPath models path syntax without requiring the path to exist. Its stem property removes the final suffix, and suffix reports only that last extension rather than every dotted segment.", | ||
| "worked_example": "For /srv/report.csv, the final component is report.csv, the stem is report, and the suffix is .csv. The demonstration combines the two queried properties after parsing the path once.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Input parsing rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Input parsing exercise.", | ||
| "Matching the judge output without checking this lesson idea: Coding-test input arrives as text on stdin." | ||
| "Splitting on periods by hand, which mishandles directories, multiple suffixes, and leading-dot names.", | ||
| "Expecting archive.tar.gz to have stem archive when only the final .gz suffix is removed." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Input parsing before the `example:` output?", | ||
| "Which TODO line in the Input parsing exercise must derive the value from its own data instead of copying the demo?" | ||
| "What stem and suffix does PurePosixPath(\".env\") expose?", | ||
| "Why is PurePosixPath appropriate when the exercise must not touch the host filesystem?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Input parsing to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Parse the POSIX path object and report its final stem followed by its final suffix.", | ||
| "objective": "Handle ordinary files, multi-extension archives, and a dotfile through pathlib properties rather than string heuristics.", | ||
| "language_delta": "Pure path classes perform lexical path operations; concrete Path additionally offers filesystem access and follows the host platform's path flavor.", | ||
| "prediction_prompt": "Before executing code, determine PurePosixPath(\"a/bundle.min.js\").stem and .suffix.", | ||
| "transfer_trap": "Windows separators passed to PurePosixPath remain ordinary characters, so choose the path flavor that matches the data contract." | ||
| }, | ||
| "py-lists-dicts": { | ||
| "title": "Lists and dicts", | ||
| "concept": "Lists store ordered values and are read by index or iteration. Dicts map keys to values, which makes lookup, grouping, and counting clearer than searching a list by hand.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Lists and dicts to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-control-flow": { | ||
| "title": "Branches, loops, and range bounds", | ||
| "concept": "Python indentation owns the loop and conditional bodies. range stops before its second argument, so an inclusive upper bound requires adjustment before the odd-number branch filters values.", | ||
| "worked_example": "The sample iterates from 1 through 7 by using stop 8. Only 1, 3, 5, and 7 enter the accumulator, producing 16.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Lists and dicts rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Lists and dicts exercise.", | ||
| "Matching the judge output without checking this lesson idea: Lists store ordered values and are read by index or iteration." | ||
| "Passing limit directly as range's stop and silently omitting an odd upper boundary.", | ||
| "Misindenting the accumulation so every value is added regardless of the condition." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Lists and dicts before the `example:` output?", | ||
| "Which TODO line in the Lists and dicts exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which integers are generated by range(1, 4)?", | ||
| "Where must total += value be indented to remain conditional?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Lists and dicts to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Loop across the inclusive interval from one to the parsed bound and add only odd integers.", | ||
| "objective": "Return the correct odd sum at a zero boundary, an odd endpoint, and an even endpoint.", | ||
| "language_delta": "Python match/case can express closed structural branches, while ordinary if remains enough for parity. A deque-consuming loop uses the same explicit branch and stopping discipline when processing a queue.", | ||
| "prediction_prompt": "List the values added when limit is 6, then predict the final accumulator.", | ||
| "transfer_trap": "Brace-based languages do not use indentation as syntax, and some range APIs are inclusive; copying loop bounds mechanically creates off-by-one errors." | ||
| }, | ||
| "py-tuples-sets": { | ||
| "title": "Tuples and sets", | ||
| "concept": "A tuple is a fixed-position sequence, useful for small records such as coordinates or pairs. A set stores unique hashable values and is the usual tool for deduplication and membership tests.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Tuples and sets to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-functions": { | ||
| "title": "Checkpoint: return a computed area", | ||
| "concept": "The area function owns one calculation and returns its result. Parsing remains outside, and the caller alone prints, so the same function can be tested, imported, wrapped, or called from synchronous or asynchronous surrounding code without hidden output.", | ||
| "worked_example": "area(8, 2) multiplies its arguments and returns 16. The outer print consumes that returned integer; the function body never writes to stdout.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Tuples and sets rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Tuples and sets exercise.", | ||
| "Matching the judge output without checking this lesson idea: A tuple is a fixed-position sequence, useful for small records such as coordinates or pairs." | ||
| "Printing inside area and then printing its None return value from the caller.", | ||
| "Computing a perimeter because width plus height looks plausible on the first example." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Tuples and sets before the `example:` output?", | ||
| "Which TODO line in the Tuples and sets exercise must derive the value from its own data instead of copying the demo?" | ||
| "What value is returned when either rectangle dimension is zero?", | ||
| "Which line is the sole stdout boundary in the sample program?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Tuples and sets to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Implement the returning calculation, then leave parsing and the final print at the call site.", | ||
| "objective": "Use a reusable function to compute all three rectangle areas, including a zero dimension.", | ||
| "language_delta": "Imports bind function objects, closures retain cells used by functions, and decorators replace a function binding with a wrapper. Calling an async function is different again: it creates a coroutine that still needs awaiting or scheduling.", | ||
| "prediction_prompt": "Predict both area(7, 1) and the value a function would return if its body only called print.", | ||
| "transfer_trap": "Languages with expression-bodied functions may return implicitly; a normal Python def returns None unless execution reaches an explicit return value." | ||
| }, | ||
| "py-comprehensions": { | ||
| "title": "Comprehensions", | ||
| "concept": "A comprehension builds a collection from an expression, a loop, and optional filters. It is best for a simple map or filter that can be read in one pass.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Comprehensions to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-modules-imports": { | ||
| "title": "Visible dependencies through modules", | ||
| "concept": "An import statement executes module loading as needed and binds a name in the current namespace. Keeping the math qualifier at the call site makes ceil's source clear and avoids wildcard collisions.", | ||
| "worked_example": "math.ceil(-3.8) returns -3, the least integer greater than or equal to the input. Negative upward rounding moves toward zero in this case, unlike floor.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Comprehensions rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Comprehensions exercise.", | ||
| "Matching the judge output without checking this lesson idea: A comprehension builds a collection from an expression, a loop, and optional filters." | ||
| "Choosing math.floor for negative values because both operations are casually described as rounding.", | ||
| "Using a star import and losing the visible connection between ceil and the math module." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Comprehensions before the `example:` output?", | ||
| "Which TODO line in the Comprehensions exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does math.ceil(-2.1) equal -2 rather than -3?", | ||
| "Which namespace binding is created by import math?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Comprehensions to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Parse the floating-point value and invoke the upward-rounding function through its module namespace.", | ||
| "objective": "Produce correct ceilings for positive, negative, and already-integral inputs using the standard library.", | ||
| "language_delta": "Python caches imported modules in sys.modules during a process, while each import statement still performs name binding in its own scope.", | ||
| "prediction_prompt": "Without running Python, compare math.ceil(-0.2) with math.floor(-0.2).", | ||
| "transfer_trap": "Java package imports and JavaScript module loading have different runtime models; the shared keyword does not imply Python's execution and cache semantics." | ||
| }, | ||
| "py-errors": { | ||
| "title": "Exceptions", | ||
| "concept": "Exceptions separate normal flow from recoverable failure. Keep the `try` block small, catch a specific exception such as `ValueError`, and recover only when you know the replacement behavior.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Exceptions to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-lambdas-closures": { | ||
| "title": "Closures capture cells, not snapshots", | ||
| "concept": "A closure keeps access to names from an enclosing function even after that call ends. Free names are resolved when the inner function executes, so loop-created lambdas usually share one cell instead of storing separate historical values.", | ||
| "worked_example": "All three readers are created while index changes, but they run after the loop has finished. Each call therefore reads the final cell value 2, producing [2, 2, 2].", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Exceptions rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Exceptions exercise.", | ||
| "Matching the judge output without checking this lesson idea: Exceptions separate normal flow from recoverable failure." | ||
| "Expecting each lambda in a loop to remember the iteration value visible at its creation line.", | ||
| "Adding a default argument without understanding that defaults freeze values because they are evaluated at definition time." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Exceptions before the `example:` output?", | ||
| "Which TODO line in the Exceptions exercise must derive the value from its own data instead of copying the demo?" | ||
| "When is the free name index looked up in the demonstrated lambdas?", | ||
| "How would lambda index=index: index change the three results?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Exceptions to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Return an adder closure whose call reads the delta binding created by the factory invocation.", | ||
| "objective": "Create callable adders that retain distinct positive, negative, and zero deltas after their factory calls return.", | ||
| "language_delta": "lambda is only expression syntax for a function; it follows the same lexical name-resolution and closure-cell model as a nested def.", | ||
| "prediction_prompt": "Predict the list from [reader() for reader in [lambda n=n: n for n in range(3)]].", | ||
| "transfer_trap": "JavaScript's block-scoped loop bindings often produce one captured value per iteration, which is not Python's default loop-cell behavior." | ||
| }, | ||
| "py-files-context": { | ||
| "title": "Files and context managers", | ||
| "concept": "`with` enters a context manager, runs the block, and guarantees cleanup afterward. File objects and contextlib utilities use this to close resources even if the block exits early.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Files and context managers to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-decorators": { | ||
| "title": "Definition-time decorators", | ||
| "concept": "Applying @bracket evaluates the decorator after the function body is created and rebinds the function name to the returned wrapper. functools.wraps preserves metadata while the wrapper transforms each later call.", | ||
| "worked_example": "The tag decorator creates one metadata-preserving wrapper around label. Calling label(\"Mina\") uppercases the original result at call time and surrounds it with angle brackets.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Files and context managers rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Files and context managers exercise.", | ||
| "Matching the judge output without checking this lesson idea: `with` enters a context manager, runs the block, and guarantees cleanup afterward." | ||
| "Calling the wrapped function while defining the decorator instead of returning a callable wrapper.", | ||
| "Omitting functools.wraps and surprising introspection, help output, or tools that rely on the original metadata." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Files and context managers before the `example:` output?", | ||
| "Which TODO line in the Files and context managers exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which event happens once at definition time, and which behavior repeats for every call?", | ||
| "Why must wrapper accept the value that callers pass to the decorated function?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Files and context managers to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Complete the wrapper so it uppercases the original return value, adds square brackets, and retains function metadata.", | ||
| "objective": "Transform ASCII and Unicode call results through a parameterized wrapper without changing the decorated function's body.", | ||
| "language_delta": "Python decorators are ordinary callable application tied to def syntax; wraps copies selected attributes but does not alter the wrapper's execution semantics.", | ||
| "prediction_prompt": "If a decorator prints \"decorate\" and its wrapper prints \"call\", which word appears when def executes and which appears on invocation?", | ||
| "transfer_trap": "A Java annotation is metadata rather than an automatic callable wrapper, so the same @ shape does not promise the same runtime behavior." | ||
| }, | ||
| "py-modules-imports": { | ||
| "title": "Modules and imports", | ||
| "concept": "A module groups reusable code behind a namespace. `import math` binds the module name, while style guides put imports at the top so dependencies are visible before the program logic starts.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Modules and imports to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-async": { | ||
| "title": "Drive real coroutines with asyncio", | ||
| "concept": "An async def call creates a coroutine object without running it to completion. asyncio.run supplies an event loop, gather schedules the provided awaitables, and await asyncio.sleep(0) yields control before each result is returned.", | ||
| "worked_example": "Two upper coroutines both reach a real suspension point. gather waits for them and returns results in argument order, so the final print emits GO NOW regardless of completion interleaving.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Modules and imports rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Modules and imports exercise.", | ||
| "Matching the judge output without checking this lesson idea: A module groups reusable code behind a namespace." | ||
| "Calling a coroutine and treating the resulting coroutine object as if it were the computed value.", | ||
| "Using time.sleep inside async code, which blocks the event-loop thread instead of yielding." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Modules and imports before the `example:` output?", | ||
| "Which TODO line in the Modules and imports exercise must derive the value from its own data instead of copying the demo?" | ||
| "What component actually drives main after main() creates a coroutine?", | ||
| "Does asyncio.gather order its returned values by completion time or argument position?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Modules and imports to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Create one double coroutine per parsed integer, gather and await them, then print the ordered results.", | ||
| "objective": "Run real suspended coroutines for multiple, signed, and empty inputs without unawaited-coroutine warnings.", | ||
| "language_delta": "Asyncio is cooperative: execution switches at awaits rather than making ordinary Python statements parallel. Calling a coroutine alone neither schedules nor executes it.", | ||
| "prediction_prompt": "What list results from await asyncio.gather(double(3), double(-1)) when double awaits sleep(0)?", | ||
| "transfer_trap": "A JavaScript Promise-producing function begins under different scheduling rules, so do not import its eager-execution intuition into Python coroutine calls." | ||
| }, | ||
| "py-dataclasses": { | ||
| "title": "Dataclasses", | ||
| "concept": "`@dataclass` generates routine methods for classes that mainly hold data. It keeps field names, type hints, construction, and representation close together without hand-written boilerplate.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Dataclasses to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-lists-dicts": { | ||
| "title": "Group list values by dictionary key", | ||
| "concept": "Each dictionary key identifies one independent list of scores. setdefault creates a bucket only when needed, and get(query, []) turns an absent requested name into an empty sequence whose sum is zero.", | ||
| "worked_example": "Mina maps to [8, 1], so looking up that list and summing it yields 9. Kai's separate list demonstrates that each key owns a different grouping bucket.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Dataclasses rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Dataclasses exercise.", | ||
| "Matching the judge output without checking this lesson idea: `@dataclass` generates routine methods for classes that mainly hold data." | ||
| "Indexing scores[query] even though the requested name may never occur in the input pairs.", | ||
| "Reusing one list for every key, causing scores added for one person to appear under all names." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Dataclasses before the `example:` output?", | ||
| "Which TODO line in the Dataclasses exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which token is the query, and at what index do the name-score pairs begin?", | ||
| "Why is sum(scores.get(missing, [])) a valid zero rather than an exception?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Dataclasses to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Walk the remaining tokens two at a time, append each numeric score to its name's list, and total the queried group.", | ||
| "objective": "Return the grouped total for repeated names, signed scores, and a missing query.", | ||
| "language_delta": "Sorting labs add explicit primary and tie keys; Counter and defaultdict specialize frequency/group creation; deque is the right sequence when both ends matter. Plain dict plus list remains clearest for this keyed accumulation.", | ||
| "prediction_prompt": "For query Bo with only Ada and Lin pairs, predict both scores.get(query, []) and its sum.", | ||
| "transfer_trap": "JavaScript object property keys are string-oriented and inherit prototype concerns; Python dict accepts hashable keys and get does not insert a missing entry." | ||
| }, | ||
| "py-typing": { | ||
| "title": "Type hints", | ||
| "concept": "Type hints describe expected shapes for readers, editors, and type checkers. They do not automatically prevent a wrong runtime value, so the function body still has to implement the contract.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Type hints to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-sorting-keys": { | ||
| "title": "Multi-field sorting keys", | ||
| "concept": "Python compares tuple keys from left to right. Negating the numeric score makes larger scores sort first, while the unmodified name supplies ascending alphabetical order only when scores tie.", | ||
| "worked_example": "Bo has score 9, so the primary key selects Bo ahead of Mina and Ana. Their tied score 7 would place Ana before Mina through the secondary field.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Type hints rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Type hints exercise.", | ||
| "Matching the judge output without checking this lesson idea: Type hints describe expected shapes for readers, editors, and type checkers." | ||
| "Setting reverse=True on a score-name tuple and accidentally reversing the alphabetical tie-break too.", | ||
| "Depending on stable input order even though the requirement explicitly names a secondary ordering rule." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Type hints before the `example:` output?", | ||
| "Which TODO line in the Type hints exercise must derive the value from its own data instead of copying the demo?" | ||
| "What key tuples are produced for Zed:5 and Amy:5?", | ||
| "Why does sorting by name alone fail when a later record has a higher score?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Type hints to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Parse each name-score record and select the first result under descending score and ascending name.", | ||
| "objective": "Choose the correct winner for distinct scores, tied scores, and a single negative-score record.", | ||
| "language_delta": "sorted returns a new list and guarantees stability. A key function computes comparison data once per element rather than acting as a two-argument comparator.", | ||
| "prediction_prompt": "Which item wins between (\"Zed\", 5) and (\"Amy\", 5) under key (-score, name)?", | ||
| "transfer_trap": "Comparator subtraction used in other languages can overflow and still does not encode a separate name tie-break; tuple keys avoid both issues here." | ||
| }, | ||
| "py-generators": { | ||
| "title": "Iterators and generators", | ||
| "concept": "An iterator produces values one at a time. A generator function uses `yield`, which returns a generator object first and runs the body only when the caller asks for the next value.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Iterators and generators to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-counter-defaultdict": { | ||
| "title": "Count and group with collections", | ||
| "concept": "Counter answers frequency queries directly, including zero for missing elements. defaultdict(list) creates independent buckets during indexing, while get can inspect a missing initial without creating a new mapping entry.", | ||
| "worked_example": "The word owl appears twice among five data words. Every word begins with o, so the grouped initial bucket has length 5 and the sample prints two different statistics.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Iterators and generators rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Iterators and generators exercise.", | ||
| "Matching the judge output without checking this lesson idea: An iterator produces values one at a time." | ||
| "Including the leading query token among the data words and inflating its frequency.", | ||
| "Reading groups[missing_initial] only to inspect it, thereby mutating the defaultdict as a side effect." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Iterators and generators before the `example:` output?", | ||
| "Which TODO line in the Iterators and generators exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does Counter(words)[\"absent\"] return zero without a prior membership check?", | ||
| "Which operation creates a defaultdict bucket: indexing or get?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Iterators and generators to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Count the data words, group them by first character, and report the query frequency plus its initial-group size.", | ||
| "objective": "Produce both statistics for present, repeated, and entirely missing queries.", | ||
| "language_delta": "These collections refine dict behavior rather than replacing it: Counter supplies numeric defaults, and defaultdict delegates missing-value construction to its factory.", | ||
| "prediction_prompt": "Given query x and data a b, determine whether groups.get(\"x\", []) inserts an x key.", | ||
| "transfer_trap": "A normal dict still raises KeyError on missing indexing; Counter's zero and defaultdict's factory are type-specific behaviors, not universal mapping rules." | ||
| }, | ||
| "py-lambdas-closures": { | ||
| "title": "Lambdas and closures", | ||
| "concept": "`lambda` creates a small expression function. When that function uses a name from an outer function, it forms a closure and remembers that outer value after the outer call returns.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Lambdas and closures to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-deque": { | ||
| "title": "Constant-time operations at both ends", | ||
| "concept": "A deque stores a sequence optimized for additions and removals on either end. appendleft and popleft target the left boundary, while append and pop target the right boundary.", | ||
| "worked_example": "Starting with center, the sample places first on the left and last on the right. Removing one value from each end prints first last and leaves center untouched.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Lambdas and closures rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Lambdas and closures exercise.", | ||
| "Matching the judge output without checking this lesson idea: `lambda` creates a small expression function." | ||
| "Using append for both incoming endpoints, which leaves the original middle value at the left.", | ||
| "Treating list.pop(0) as equally suitable for a growing queue despite its element-shifting cost." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Lambdas and closures before the `example:` output?", | ||
| "Which TODO line in the Lambdas and closures exercise must derive the value from its own data instead of copying the demo?" | ||
| "After appendleft(left), which value will popleft return?", | ||
| "Which deque operation removes the rightmost item?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Lambdas and closures to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Place the second token at the left and the third at the right, then remove and print those endpoints.", | ||
| "objective": "Preserve the middle element while reporting correctly ordered ASCII and Unicode endpoints.", | ||
| "language_delta": "Deque operations pair naturally with control-flow loops that repeatedly consume a frontier. Random indexing remains a list strength, not deque's purpose.", | ||
| "prediction_prompt": "Trace deque([\"m\"]) after appendleft(\"l\"), append(\"r\"), popleft(), and pop().", | ||
| "transfer_trap": "Queue classes in other standard libraries may expose offer and poll instead; match semantic ends rather than translating method names literally." | ||
| }, | ||
| "py-decorators": { | ||
| "title": "Decorators", | ||
| "concept": "A decorator is called when a function is defined. It receives the original function object and returns the object that the function name should refer to afterward.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Decorators to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-comprehensions": { | ||
| "title": "Checkpoint: filter, transform, then total", | ||
| "concept": "The comprehension evaluates its filter for each source number and only then computes the squared output expression for accepted values. The resulting list exists eagerly before sum consumes it.", | ||
| "worked_example": "Among 5, 6, 7, and 8, only 6 and 8 pass the even test. Their squares 36 and 64 form a list whose total is 100.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Decorators rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Decorators exercise.", | ||
| "Matching the judge output without checking this lesson idea: A decorator is called when a function is defined." | ||
| "Reversing the parity predicate and quietly summing odd squares instead.", | ||
| "Packing nested conditions and side effects into a comprehension until its evaluation order becomes opaque." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Decorators before the `example:` output?", | ||
| "Which TODO line in the Decorators exercise must derive the value from its own data instead of copying the demo?" | ||
| "Does the output expression run for an element rejected by the trailing if clause?", | ||
| "What list is built from [-2, -1, 0] under the even-square rule?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Decorators to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Build the even squares with one readable list comprehension, then print their sum.", | ||
| "objective": "Handle mixed signs and an empty selection while demonstrating filter-before-transform behavior.", | ||
| "language_delta": "A generator expression would yield squares lazily, and itertools can compose single-pass streams without materializing them. This checkpoint deliberately builds a list so that eager collection construction is observable.", | ||
| "prediction_prompt": "Write the exact intermediate list produced from [1, 2, 3, 4] before sum runs.", | ||
| "transfer_trap": "JavaScript array callbacks and Java streams arrange similar stages with different eagerness; do not assume Python's bracketed comprehension is lazy." | ||
| }, | ||
| "py-sorting-keys": { | ||
| "title": "Sorting and key functions", | ||
| "concept": "`sorted` returns a new list and leaves the original iterable alone. A `key` function computes the comparison value for each item, which is how records are sorted by one field.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Sorting and key functions to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-generators": { | ||
| "title": "Lazy countdown generators", | ||
| "concept": "A function containing yield returns a generator iterator when called. Its body starts on the first advance, pauses with local state saved at each yield, and eventually raises StopIteration when no path yields again.", | ||
| "worked_example": "reverse_letters(\"abc\") initially creates an iterator without traversing text. Expanding it into print advances three times and emits c, b, and a separated by spaces.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Sorting and key functions rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Sorting and key functions exercise.", | ||
| "Matching the judge output without checking this lesson idea: `sorted` returns a new list and leaves the original iterable alone." | ||
| "Expecting statements before the first yield to run when the generator function is merely called.", | ||
| "Trying to iterate the same exhausted generator again and expecting its previous values to reappear." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Sorting and key functions before the `example:` output?", | ||
| "Which TODO line in the Sorting and key functions exercise must derive the value from its own data instead of copying the demo?" | ||
| "At what operation does countdown's while body begin executing?", | ||
| "Why does print(*countdown(0)) still produce a newline?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Sorting and key functions to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Yield the current positive number, decrease the suspended local state, and repeat until reaching zero.", | ||
| "objective": "Generate complete, one-item, and empty countdown sequences without building an intermediate list.", | ||
| "language_delta": "Generator functions are iterator factories; each call creates fresh suspended state. A normal return ends iteration rather than emitting another sequence element.", | ||
| "prediction_prompt": "After next is called once on countdown(3), what value is yielded and what local number resumes afterward?", | ||
| "transfer_trap": "A JavaScript generator uses related syntax but different iterator result objects; Python next returns the yielded value directly or raises StopIteration." | ||
| }, | ||
| "py-counter-defaultdict": { | ||
| "title": "Counter and defaultdict", | ||
| "concept": "`Counter` is a dict subclass for counts, and `defaultdict(list)` creates a new list for missing groups. They remove the noisy `if key not in dict` code from common counting and grouping tasks.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Counter and defaultdict to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-itertools": { | ||
| "title": "Flatten iterables lazily", | ||
| "concept": "chain.from_iterable accepts one outer iterable and advances through each inner iterable in turn. It exposes a lazy stream, avoiding a temporary flattened list and retaining only the iterator state needed for the current group.", | ||
| "worked_example": "The outer tuple contains py and thon. Chaining iterates the characters of the first string, then the second, and join consumes that stream to form python.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Counter and defaultdict rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Counter and defaultdict exercise.", | ||
| "Matching the judge output without checking this lesson idea: `Counter` is a dict subclass for counts, and `defaultdict(list)` creates a new list for missing groups." | ||
| "Using chain(groups) and receiving each whole inner string instead of its characters.", | ||
| "Partially consuming the chain for inspection and then expecting join to restart from the beginning." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Counter and defaultdict before the `example:` output?", | ||
| "Which TODO line in the Counter and defaultdict exercise must derive the value from its own data instead of copying the demo?" | ||
| "How many outer arguments does chain.from_iterable receive in this exercise?", | ||
| "What remains after next has consumed the first character from the flattened iterator?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Counter and defaultdict to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Pass every parsed character group through chain.from_iterable and join the resulting single-pass stream.", | ||
| "objective": "Flatten groups of unequal lengths into the correct concatenated text without nested list construction.", | ||
| "language_delta": "itertools operations return iterators and defer work until a consumer advances them. join is the terminal consumer that materializes the final string here.", | ||
| "prediction_prompt": "Predict list(chain.from_iterable([\"ab\", \"c\"])) before considering the final join.", | ||
| "transfer_trap": "Reusing a stream abstraction from another language may raise, cache, or rebuild; Python iterators generally retain mutable consumption state." | ||
| }, | ||
| "py-deque": { | ||
| "title": "deque", | ||
| "concept": "`deque` is a double-ended queue with efficient append and pop operations on both ends. It is the standard-library choice for BFS queues and sliding-window front removal.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying deque to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-errors": { | ||
| "title": "Recover narrowly from ValueError", | ||
| "concept": "int raises ValueError when text does not describe an integer. Keeping only that conversion in a small try block and catching that precise exception makes the fallback policy visible without masking unrelated defects.", | ||
| "worked_example": "Converting oops raises ValueError, so control enters the handler and converts the supplied fallback -8. The valid fallback then becomes the printed result.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the deque rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the deque exercise.", | ||
| "Matching the judge output without checking this lesson idea: `deque` is a double-ended queue with efficient append and pop operations on both ends." | ||
| "Catching Exception around the whole program and turning unrelated bugs into apparently valid fallback output.", | ||
| "Treating a signed string such as -3 as invalid even though int parses it normally." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies deque before the `example:` output?", | ||
| "Which TODO line in the deque exercise must derive the value from its own data instead of copying the demo?" | ||
| "Does a valid first token execute the except block?", | ||
| "Which statement alone can raise the ValueError that this handler intends to recover from?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying deque to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Attempt conversion of the first token and use the second token only when that specific conversion raises ValueError.", | ||
| "objective": "Distinguish valid negative input from malformed text and apply the provided fallback exactly when needed.", | ||
| "language_delta": "A context manager complements narrow exception handling by guaranteeing exit cleanup even when a block raises. try chooses recovery behavior; with owns resource lifetime.", | ||
| "prediction_prompt": "Trace the two branches for candidate \"-3\" and candidate \"bad\" when both share fallback \"9\".", | ||
| "transfer_trap": "JavaScript parseInt(\"bad\") returns NaN instead of throwing, so a copied try/catch strategy would never select the fallback there." | ||
| }, | ||
| "py-itertools": { | ||
| "title": "itertools", | ||
| "concept": "`itertools` contains lazy iterator building blocks such as `chain`, `islice`, `product`, and `pairwise`. They describe iteration pipelines without building intermediate lists unless you ask for them.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying itertools to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-files-context": { | ||
| "title": "Guaranteed context-manager exit", | ||
| "concept": "The with protocol obtains a managed value through __enter__ and calls __exit__ whenever the block leaves, including exceptional exits. StringIO gives the same read-and-close shape without depending on a real file.", | ||
| "worked_example": "The in-memory handle contains padded Seoul text. Reading inside the managed block, stripping whitespace, and uppercasing after exit produces SEOUL while the handle itself is already closed.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the itertools rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the itertools exercise.", | ||
| "Matching the judge output without checking this lesson idea: `itertools` contains lazy iterator building blocks such as `chain`, `islice`, `product`, and `pairwise`." | ||
| "Attempting to read the handle after the with block, when the StringIO resource has been closed.", | ||
| "Assuming __exit__ runs only on success and manually duplicating cleanup in exception branches." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies itertools before the `example:` output?", | ||
| "Which TODO line in the itertools exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which operation must happen before indentation leaves the managed block?", | ||
| "What normalized value remains when the resource contains only whitespace?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying itertools to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Read through the context-managed handle, normalize its content, and print EMPTY only when no text remains.", | ||
| "objective": "Observe guaranteed resource exit while correctly handling padded, ordinary, and empty in-memory input.", | ||
| "language_delta": "with delegates entry and exit behavior to the object's protocol rather than hard-coding files. The value read into text remains usable after the resource closes.", | ||
| "prediction_prompt": "If handle.read() returns \" \", predict text after strip and the expression text.upper() or \"EMPTY\".", | ||
| "transfer_trap": "A JavaScript using declaration or Java try-with-resources has its own protocol; Python specifically dispatches __enter__ and __exit__." | ||
| }, | ||
| "py-pathlib": { | ||
| "title": "pathlib", | ||
| "concept": "`pathlib` models paths as objects instead of raw strings. Properties such as `name`, `stem`, `suffix`, and `parent` make file-path code clearer and less dependent on manual separators.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying pathlib to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-dataclasses": { | ||
| "title": "Classes and generated data-model methods", | ||
| "concept": "A class supplies instance behavior through methods, and @dataclass derives init, repr, and equality from declared fields. The decorator does not make nested values immutable, and field annotations do not become runtime validators.", | ||
| "worked_example": "Point(4, 5) stores two fields, its total method returns 9, and generated equality reports True for a second Point with the same field values.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the pathlib rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the pathlib exercise.", | ||
| "Matching the judge output without checking this lesson idea: `pathlib` models paths as objects instead of raw strings." | ||
| "Assuming generated equality or a frozen option recursively freezes mutable objects stored in fields.", | ||
| "Declaring `values: list[int] = []` is rejected as a mutable dataclass default; use `field(default_factory=list)` per instance or `ClassVar` for intentional sharing." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies pathlib before the `example:` output?", | ||
| "Which TODO line in the pathlib exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which equality implementation is used when two Point instances are compared?", | ||
| "Does declaring x: int prevent Point(\"wrong\", 5) from being constructed at runtime?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying pathlib to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Implement Point.total with both instance fields, then observe the method result and dataclass-generated equality.", | ||
| "objective": "Construct equal points and compute signed coordinate totals through explicit class behavior.", | ||
| "language_delta": "The typing lab can inspect the same annotations, but ordinary Python execution does not enforce them. A dataclass reduces data-model boilerplate without replacing domain methods or validation.", | ||
| "prediction_prompt": "Predict Point(2, 3) == Point(2, 3), then consider how changing one field affects the result.", | ||
| "transfer_trap": "Java records are shallowly immutable in their components, while a normal Python dataclass remains mutable unless configured otherwise." | ||
| }, | ||
| "py-testing-assert": { | ||
| "title": "Testing and assert", | ||
| "concept": "`assert` is a compact check for an invariant in examples and small tests. Real projects usually put those checks in repeatable test functions, but the core idea is still expected versus actual behavior.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Testing and assert to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-typing": { | ||
| "title": "Inspect annotations without runtime enforcement", | ||
| "concept": "Function annotations are metadata consumed by readers, editors, and optional static type checkers. get_type_hints resolves the recorded objects at runtime, but it neither converts arguments nor blocks calls whose values disagree.", | ||
| "worked_example": "The head function records Iterable[str] and str annotations. Introspection identifies the return type as str, while the function itself independently returns elm from the supplied list.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Testing and assert rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Testing and assert exercise.", | ||
| "Matching the judge output without checking this lesson idea: `assert` is a compact check for an invariant in examples and small tests." | ||
| "Believing an int annotation automatically rejects a string before the function body executes.", | ||
| "Confusing introspection of a promised type with verification that every return path actually honors it." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Testing and assert before the `example:` output?", | ||
| "Which TODO line in the Testing and assert exercise must derive the value from its own data instead of copying the demo?" | ||
| "What object does get_type_hints(total)[\"return\"] expose?", | ||
| "Which part of total performs addition: the annotation or the return expression?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Testing and assert to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Keep the Iterable[int] and int contract, implement its total, and use resolved hints to label the observable result.", | ||
| "objective": "Verify annotation metadata and compute totals for multiple, single, and empty iterables.", | ||
| "language_delta": "Python can execute annotated code without running a type checker. TypeScript also erases types at runtime, but its normal toolchain performs checker validation before execution.", | ||
| "prediction_prompt": "If total is called with [\"2\"], will the annotation reject the call, and what operation eventually determines the outcome?", | ||
| "transfer_trap": "Annotations document expected values; they do not parse input strings, coerce types, or supply runtime overload dispatch." | ||
| }, | ||
| "py-async": { | ||
| "title": "Async concepts", | ||
| "concept": "`async def` creates a coroutine function. Calling it creates a coroutine object, `await` waits for its result inside another coroutine, and `asyncio.run` drives the top-level coroutine in this single-file exercise.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Async concepts to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "py-testing-assert": { | ||
| "title": "Capstone: deterministic word ranking", | ||
| "concept": "The final program materializes an iterable, counts words, validates development invariants, and sorts frequency entries by descending count plus ascending word. Empty input has an explicit result instead of relying on an exception.", | ||
| "worked_example": "Pear and plum each occur twice, so count alone cannot decide. The alphabetical secondary key selects pear, and the sample prints pear:2 after its Counter invariants hold.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Async concepts rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Async concepts exercise.", | ||
| "Matching the judge output without checking this lesson idea: `async def` creates a coroutine function." | ||
| "Using Counter.most_common(1) without an explicit alphabetical rule, which leaves equal counts dependent on encounter order.", | ||
| "Using assert for input or security validation even though optimized Python removes assertion bytecode." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Async concepts before the `example:` output?", | ||
| "Which TODO line in the Async concepts exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which two fields form the sort key, and why is the count negated?", | ||
| "What happens to assert statements when the interpreter starts with -O?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Async concepts to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Replace encounter-order tie behavior with deterministic count-and-word ranking while retaining the typed function, Counter, and internal invariants.", | ||
| "objective": "Select the most frequent word with an alphabetical tie-break and return the documented empty result when no words exist.", | ||
| "language_delta": "The capstone reuses exact output, parsing, rebinding, collections, functions, eager sorting, and typed class-era data reasoning. Assertions are optional development checks; under -O they vanish and therefore cannot carry required program behavior.", | ||
| "prediction_prompt": "For input b a b a, list Counter's entries and predict the winner under key (-count, word).", | ||
| "transfer_trap": "Java assertions may also be disabled, but its flag and runtime model differ; in either language use normal validation for conditions that must always execute." | ||
| } | ||
| } | ||
| } |
+324
-249
@@ -7,377 +7,452 @@ { | ||
| "py-output": { | ||
| "title": "print y stdout", | ||
| "concept": "`print` es el punto donde un cálculo se convierte en salida visible para el juez. Convierte sus argumentos a texto, separa argumentos con espacios y añade salto de línea por defecto, así que stdout debe coincidir exactamente.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de print y stdout. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Salida exacta con `print`", | ||
| "concept": "`print` convierte sus argumentos en texto, coloca `sep` entre ellos y añade `end` al final. Esos valores predeterminados también forman parte de la salida exacta que comprueba el evaluador.", | ||
| "worked_example": "El código une `Mina` y `9` con un signo igual y sustituye el salto de línea habitual por un signo de exclamación seguido de `\n`. Ambos signos proceden de los argumentos de `print`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de print y stdout en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de print y stdout.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `print` es el punto donde un cálculo se convierte en salida visible para el juez." | ||
| "Conservar el espacio separador predeterminado cuando los dos valores deben tocar los dos puntos.", | ||
| "Enviar etiquetas de depuración a la salida estándar, donde cuentan como parte de la respuesta." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica print y stdout antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de print y stdout debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué caracteres escribe `print(\"A\", \"B\", sep=\":\", end=\"!\")`?", | ||
| "¿Qué parámetro controla el texto posterior al último argumento?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando print y stdout a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Lee el nombre y la puntuación; configura `print` para formar un registro separado por dos puntos y terminado por un único salto de línea.", | ||
| "objective": "Producir para los tres registros una salida exacta, sin prefijos de depuración ni espacios sobrantes.", | ||
| "language_delta": "A diferencia de una escritura directa en un flujo, `print` aporta separador y terminación configurables; ambos deben decidirse de forma explícita.", | ||
| "prediction_prompt": "Antes de ejecutarlo, escribe la salida exacta de `print(\"x\", 0, sep=\"=\", end=\"?\")`.", | ||
| "transfer_trap": "No supongas que `println` o un registrador de consola de otro lenguaje comparte el `sep` configurable de Python." | ||
| }, | ||
| "py-variables": { | ||
| "title": "Variables", | ||
| "concept": "Una variable es un nombre enlazado a un objeto. Asignar con `=` no compara igualdad; cambia qué objeto leerá ese nombre a partir de ese punto.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Variables. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-input": { | ||
| "title": "Leer la entrada estándar una sola vez", | ||
| "concept": "`sys.stdin.read()` reúne todo el flujo en una cadena. `split()` sin separador reconoce cualquier espacio en blanco; si no hay tokens, `sum` conserva su identidad válida, cero.", | ||
| "worked_example": "La demostración guarda `10` y `4` en dos líneas, separa ambos tokens sin depender del salto de línea, los convierte a enteros e imprime `14`. No solicita datos de forma interactiva.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Variables en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Variables.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Una variable es un nombre enlazado a un objeto." | ||
| "Usar `split(\" \")`, que no trata bien los saltos de línea ni varios espacios consecutivos.", | ||
| "Llamar repetidamente a `input()` y fallar cuando los mismos tokens se distribuyen en otras líneas." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Variables antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Variables debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué lista produce una cadena vacía al aplicar `split()` sin argumentos?", | ||
| "¿Por qué una sola llamada a `sys.stdin.read()` cubre entradas de una o varias líneas?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Variables a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Convierte todos los tokens separados por espacios en blanco del flujo ya leído e imprime su suma, incluido el caso de entrada vacía.", | ||
| "objective": "Sumar enteros separados por espacios o saltos de línea sin crear ramas especiales para su distribución.", | ||
| "language_delta": "Aquí Python expone la entrada como texto; `split` sigue devolviendo cadenas, de modo que la conversión numérica debe ser explícita.", | ||
| "prediction_prompt": "Calcula `sum(int(token) for token in \" -2\n5 \".split())` sin ejecutar el programa.", | ||
| "transfer_trap": "Algunos escáneres omiten espacios automáticamente, pero el texto crudo de Python no se vuelve numérico hasta llamar a `int`." | ||
| }, | ||
| "py-numbers": { | ||
| "title": "Números", | ||
| "concept": "Los enteros de Python no tienen límite práctico de tamaño, y los operadores numéricos expresan ideas distintas. `/` produce float, `//` produce cociente entero, `%` produce resto y `**` potencia.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Números. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-variables": { | ||
| "title": "Nombres y reasignación", | ||
| "concept": "Una variable de Python es un nombre enlazado a un objeto, no una caja tipada. Actualizar un acumulador entero crea otro entero y vuelve a enlazar el nombre; una clausura lee más tarde la celda exterior vigente.", | ||
| "worked_example": "Primero `total` queda enlazado a `10`; después, la asignación aumentada lo vuelve a enlazar a `15`. El objeto entero `10` no cambia porque los enteros son inmutables.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Números en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Números.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Los enteros de Python no tienen límite práctico de tamaño, y los operadores numéricos expresan ideas distintas." | ||
| "Describir `+=` sobre enteros como una mutación del objeto original.", | ||
| "Creer que cada lambda creada en un bucle congela por sí sola el valor de esa iteración." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Números antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Números debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "Tras `total = left` y `total += right`, ¿qué nombre se vuelve a enlazar?", | ||
| "¿Qué observan tres lambdas que comparten la misma variable de un bucle ya terminado?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Números a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Inicializa el acumulador con el primer entero analizado y reasígnalo mediante el segundo entero antes de imprimirlo.", | ||
| "objective": "Mostrar la reasignación al sumar entradas positivas, negativas y nulas.", | ||
| "language_delta": "Python cambia enlaces entre nombres y objetos sin declarar mutable el nombre. Las clausuras conservan acceso a celdas, cuyo valor puede decidirse al invocarlas.", | ||
| "prediction_prompt": "Para `readers = [lambda: n for n in range(3)]`, predice `[reader() for reader in readers]`.", | ||
| "transfer_trap": "Un bucle habitual con `let` en JavaScript crea enlaces por iteración; trasladar esa intuición oculta la captura tardía de Python." | ||
| }, | ||
| "py-strings": { | ||
| "title": "Cadenas", | ||
| "concept": "Una cadena es una secuencia inmutable de caracteres Unicode. El índice lee un carácter y el slice lee un rango semiabierto sin cambiar la cadena original.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Cadenas. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-numbers": { | ||
| "title": "Cociente redondeado hacia abajo y resto", | ||
| "concept": "La división entera `//` elige el mayor entero que no supera el cociente matemático. El resto asociado cumple `dividendo = divisor * cociente + resto` y, si no es cero, tiene el signo del divisor.", | ||
| "worked_example": "Para `-11 // 4`, Python obtiene `-3`, y `-11 % 4` vale `1`. Al reconstruir `4 * -3 + 1` se recupera exactamente `-11`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Cadenas en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Cadenas.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Una cadena es una secuencia inmutable de caracteres Unicode." | ||
| "Sustituir `//` por `int(dividendo / divisor)`, que trunca los resultados negativos hacia cero.", | ||
| "Suponer que el módulo siempre es no negativo incluso cuando el divisor es negativo." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Cadenas antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Cadenas debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué identidad relaciona dividendo, divisor, cociente y resto?", | ||
| "¿Por qué `-7 // 2` es uno menos que `int(-7 / 2)`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Cadenas a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Analiza dos enteros con divisor distinto de cero e imprime el cociente de Python redondeado hacia abajo junto con su resto correspondiente.", | ||
| "objective": "Resolver correctamente divisores de ambos signos sin romper la identidad de la división de Python.", | ||
| "language_delta": "Python redondea `//` hacia menos infinito; Java y Rust truncan la división entera hacia cero, mientras TypeScript conserva un cociente fraccionario con `number`.", | ||
| "prediction_prompt": "Calcula `(-7 // 2, -7 % 2)` y `(7 // -2, 7 % -2)` antes de ejecutar nada.", | ||
| "transfer_trap": "No traduzcas `/` de forma mecánica entre lenguajes: los operandos y la regla de redondeo cambian el resultado con signos distintos." | ||
| }, | ||
| "py-control-flow": { | ||
| "title": "Flujo de control", | ||
| "concept": "`if` elige un bloque, `for` consume un iterable y `while` repite mientras cambie una condición. La indentación es sintaxis, por lo que una línea movida cambia el flujo real.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Flujo de control. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-tuples-sets": { | ||
| "title": "Pares ordenados y miembros únicos", | ||
| "concept": "Una tupla conserva posiciones y duplicados, por lo que sirve para el registro de entrada. Un conjunto elimina miembros hashables repetidos, pero su iteración no debe definir el formato de la respuesta.", | ||
| "worked_example": "La tupla `(\"r\", \"s\", \"r\")` vuelve a unirse como `rsr`; al convertirla en conjunto quedan dos letras distintas. Cada resultado observa una propiedad diferente de la misma fuente.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Flujo de control en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Flujo de control.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `if` elige un bloque, `for` consume un iterable y `while` repite mientras cambie una condición." | ||
| "Escribir `{}` para un conjunto vacío y crear en realidad un diccionario.", | ||
| "Unir el conjunto en vez de la tupla, perdiendo duplicados y posiciones fiables." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Flujo de control antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Flujo de control debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué `(\"a\", \"a\")` todavía contiene dos posiciones?", | ||
| "¿Qué constructor crea un conjunto vacío sin ambigüedad?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Flujo de control a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Mantén los valores analizados en orden de tupla para unirlos y deriva aparte un conjunto destinado únicamente a contar elementos únicos.", | ||
| "objective": "Imprimir la concatenación ordenada y la cantidad de miembros distintos para duplicados y texto Unicode.", | ||
| "language_delta": "Desempaquetar una tupla y construir un conjunto son operaciones reales en tiempo de ejecución; no necesitan anotaciones para conservar su comportamiento.", | ||
| "prediction_prompt": "Para `pair = (\"é\", \"e\", \"é\")`, predice la unión de la tupla y `len(set(pair))`.", | ||
| "transfer_trap": "Una tupla de TypeScript es ante todo una restricción del verificador; la tupla de Python es una secuencia inmutable en ejecución." | ||
| }, | ||
| "py-functions": { | ||
| "title": "Funciones", | ||
| "concept": "`def` crea un objeto invocable. Los parámetros reciben argumentos y `return` entrega un valor al llamador; imprimir dentro de la función no es lo mismo que devolver el cálculo.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Funciones. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-strings": { | ||
| "title": "Cortes de texto conscientes de Unicode", | ||
| "concept": "Los índices y cortes de `str` trabajan con puntos de código Unicode, no con bytes UTF-8. Un corte usa un intervalo semiabierto y crea otra cadena sin modificar la fuente inmutable.", | ||
| "worked_example": "Las estrellas ocupan las posiciones exterior izquierda y derecha de `서울`. El corte `[1:-1]` devuelve `서울` sin calcular desplazamientos de bytes.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Funciones en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Funciones.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `def` crea un objeto invocable." | ||
| "Usar `strip` para quitar delimitadores exactos, aunque elimina cualquier carácter coincidente en ambos extremos.", | ||
| "Confundir un punto de código con un grafema visible, que puede contener marcas combinadas o varios símbolos." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Funciones antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Funciones debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué posiciones de la fuente excluye `text[1:-1]`?", | ||
| "¿Por qué el mismo corte elimina con seguridad un emoji de un solo punto de código?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Funciones a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Quita exactamente el primer y el último punto de código mediante un corte semiabierto e imprime el contenido interior.", | ||
| "objective": "Extraer el interior de cadenas ASCII, delimitadas por corchetes y rodeadas de emoji sin mutar la entrada.", | ||
| "language_delta": "Los índices de Python cuentan puntos de código; Java y JavaScript usan unidades UTF-16, y Rust expone desplazamientos UTF-8 para los cortes.", | ||
| "prediction_prompt": "Predice `len(\"🙂go🙂\")` y el resultado de `\"🙂go🙂\"[1:-1]`.", | ||
| "transfer_trap": "Un corte seguro por puntos de código aún no entiende grafemas: un símbolo con marcas combinadas puede ocupar varias posiciones." | ||
| }, | ||
| "py-input": { | ||
| "title": "Lectura de entrada", | ||
| "concept": "La entrada de concursos llega como texto en stdin. El patrón común es leer una vez, separar tokens, convertir tipos y resolver después con valores normales.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Lectura de entrada. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-pathlib": { | ||
| "title": "Componentes de ruta con `pathlib`", | ||
| "concept": "`PurePosixPath` modela la sintaxis de una ruta sin exigir que exista. `stem` elimina el último sufijo y `suffix` devuelve solo esa extensión final, no todos los segmentos con punto.", | ||
| "worked_example": "En `/srv/report.csv`, el componente final es `report.csv`, el nombre base sin el último sufijo (`stem`) es `report` y el sufijo es `.csv`. La ruta se interpreta una sola vez antes de consultar ambas propiedades.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Lectura de entrada en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Lectura de entrada.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: La entrada de concursos llega como texto en stdin." | ||
| "Separar manualmente por puntos y fallar con directorios, varias extensiones o nombres que empiezan por punto.", | ||
| "Esperar que `archive.tar.gz` tenga como `stem` `archive`, aunque solo se elimina el sufijo final `.gz`." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Lectura de entrada antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Lectura de entrada debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué `stem` y `suffix` expone `PurePosixPath(\".env\")`?", | ||
| "¿Por qué conviene una ruta pura cuando el ejercicio no debe tocar el sistema de archivos?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Lectura de entrada a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Interpreta la ruta POSIX y muestra su `stem` (nombre base sin el último sufijo) seguido del último sufijo, usando las propiedades de `PurePosixPath`.", | ||
| "objective": "Resolver archivos normales, extensiones múltiples y un archivo oculto sin heurísticas sobre cadenas.", | ||
| "language_delta": "Las rutas puras solo realizan operaciones léxicas; `Path` añade acceso al sistema de archivos y adopta el formato de la plataforma anfitriona.", | ||
| "prediction_prompt": "Determina `PurePosixPath(\"a/bundle.min.js\").stem` y `.suffix` antes de ejecutar Python.", | ||
| "transfer_trap": "Las barras inversas de Windows son caracteres normales para `PurePosixPath`; elige el tipo de ruta acorde con el contrato de datos." | ||
| }, | ||
| "py-lists-dicts": { | ||
| "title": "Listas y diccionarios", | ||
| "concept": "Las listas guardan valores en orden y los diccionarios conectan claves con valores. En problemas reales, esa diferencia decide si conviene recorrer posiciones o buscar directamente por nombre, letra o identificador.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Listas y diccionarios. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-control-flow": { | ||
| "title": "Ramas, bucles y límites de `range`", | ||
| "concept": "La indentación delimita los cuerpos de bucles y condiciones. `range` se detiene antes de su segundo argumento, de modo que un extremo inclusivo necesita ajustarse antes de filtrar números impares.", | ||
| "worked_example": "Con límite `7`, el código usa parada `8` y recorre del `1` al `7`. Solo `1`, `3`, `5` y `7` entran en el acumulador, cuyo resultado es `16`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Listas y diccionarios en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Listas y diccionarios.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Las listas guardan valores en orden y los diccionarios conectan claves con valores." | ||
| "Pasar el límite directamente como parada y omitir en silencio un extremo impar.", | ||
| "Indentar mal la acumulación y sumar todos los valores aunque la condición sea falsa." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Listas y diccionarios antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Listas y diccionarios debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué enteros genera `range(1, 4)`?", | ||
| "¿Dónde debe indentarse `total += value` para depender de la rama?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Listas y diccionarios a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Recorre el intervalo inclusivo desde uno hasta el límite analizado y añade al acumulador únicamente los enteros impares.", | ||
| "objective": "Obtener la suma impar correcta para límite cero, extremo impar y extremo par.", | ||
| "language_delta": "Python usa la indentación como sintaxis y sus rangos excluyen la parada; otros lenguajes suelen usar llaves y APIs con límites distintos.", | ||
| "prediction_prompt": "Enumera los valores que se suman con límite `6` y calcula después el acumulador final.", | ||
| "transfer_trap": "Copiar límites desde una API de rango inclusivo produce errores de una unidad aunque la condición de paridad sea correcta." | ||
| }, | ||
| "py-tuples-sets": { | ||
| "title": "Tuplas y conjuntos", | ||
| "concept": "Una tupla agrupa valores en posiciones fijas; un conjunto guarda miembros únicos y permite pruebas de pertenencia rápidas. No uses conjunto si el orden de salida importa.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Tuplas y conjuntos. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-functions": { | ||
| "title": "Punto de control: devolver un área", | ||
| "concept": "La función `area` encapsula un cálculo y devuelve el resultado. La lectura queda fuera y solo quien llama imprime, por lo que la función puede probarse o reutilizarse sin salida oculta.", | ||
| "worked_example": "`area(8, 2)` multiplica sus argumentos y devuelve `16`. El `print` exterior consume ese entero; el cuerpo de la función no escribe en la salida estándar.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Tuplas y conjuntos en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Tuplas y conjuntos.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Una tupla agrupa valores en posiciones fijas; un conjunto guarda miembros únicos y permite pruebas de pertenencia rápidas." | ||
| "Imprimir dentro de `area` y luego imprimir también el `None` devuelto implícitamente.", | ||
| "Calcular un perímetro porque sumar ancho y alto parece válido en un único ejemplo." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Tuplas y conjuntos antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Tuplas y conjuntos debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué devuelve el área si una dimensión del rectángulo es cero?", | ||
| "¿Qué línea constituye el único límite de salida en la muestra?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Tuplas y conjuntos a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Implementa el cálculo que devuelve el área y conserva el análisis de entrada y la impresión final en el lugar de llamada.", | ||
| "objective": "Usar una función reutilizable para las tres áreas, incluida una dimensión nula.", | ||
| "language_delta": "Las importaciones enlazan funciones, las clausuras retienen celdas y los decoradores sustituyen el enlace por un envoltorio; una corrutina además debe esperarse.", | ||
| "prediction_prompt": "Predice `area(7, 1)` y qué devolvería una función cuyo cuerpo solo ejecutara `print`.", | ||
| "transfer_trap": "Algunos lenguajes devuelven implícitamente una expresión; una función `def` normal devuelve `None` si no alcanza un `return` con valor." | ||
| }, | ||
| "py-comprehensions": { | ||
| "title": "Comprensiones", | ||
| "concept": "Una comprensión combina expresión de salida, bucle y filtros opcionales para construir una colección. Sirve para mapas y filtros simples que se leen de izquierda a derecha.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Comprensiones. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-modules-imports": { | ||
| "title": "Dependencias visibles mediante módulos", | ||
| "concept": "Una sentencia `import` carga el módulo cuando corresponde y enlaza un nombre en el espacio actual. Mantener `math.ceil` cualificado muestra su origen y evita colisiones de importaciones globales.", | ||
| "worked_example": "`math.ceil(-3.8)` devuelve `-3`, el menor entero mayor o igual que la entrada. Con un número negativo, redondear hacia arriba se acerca aquí a cero y no equivale a `floor`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Comprensiones en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Comprensiones.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Una comprensión combina expresión de salida, bucle y filtros opcionales para construir una colección." | ||
| "Elegir `math.floor` para negativos porque ambas operaciones se describen informalmente como redondeo.", | ||
| "Usar una importación con asterisco y perder la relación visible entre `ceil` y `math`." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Comprensiones antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Comprensiones debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué `math.ceil(-2.1)` vale `-2` y no `-3`?", | ||
| "¿Qué enlace de espacio de nombres crea `import math`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Comprensiones a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Analiza el número de punto flotante e invoca mediante el espacio de nombres del módulo la función que redondea hacia arriba.", | ||
| "objective": "Aplicar correctamente el redondeo hacia arriba a valores positivos, negativos y ya enteros con la biblioteca estándar.", | ||
| "language_delta": "Python almacena módulos cargados en `sys.modules`, aunque cada sentencia `import` sigue enlazando nombres en su propio ámbito.", | ||
| "prediction_prompt": "Compara `math.ceil(-0.2)` con `math.floor(-0.2)` sin ejecutar el intérprete.", | ||
| "transfer_trap": "Las importaciones de paquetes Java y los módulos JavaScript tienen otros modelos de ejecución; compartir una palabra clave no iguala su semántica." | ||
| }, | ||
| "py-errors": { | ||
| "title": "Excepciones", | ||
| "concept": "Las excepciones separan el camino normal de un fallo recuperable. Mantén pequeño el `try`, captura una excepción concreta y asigna una recuperación que tenga sentido.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Excepciones. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-lambdas-closures": { | ||
| "title": "Las clausuras capturan celdas, no instantáneas", | ||
| "concept": "Una clausura conserva acceso a nombres de una función exterior después de que esta termine. Los nombres libres se resuelven al ejecutar la función interna; las lambdas de un bucle suelen compartir una celda.", | ||
| "worked_example": "Las tres funciones se crean mientras cambia `index`, pero se invocan al finalizar el bucle. Todas leen entonces el valor final `2`, por lo que el resultado es `[2, 2, 2]`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Excepciones en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Excepciones.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Las excepciones separan el camino normal de un fallo recuperable." | ||
| "Esperar que cada lambda recuerde automáticamente el valor visible en su línea de creación.", | ||
| "Añadir un argumento predeterminado sin entender que se evalúa al definir la función y por eso congela el valor." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Excepciones antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Excepciones debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Cuándo se consulta el nombre libre `index` en las lambdas mostradas?", | ||
| "¿Cómo cambia los tres resultados `lambda index=index: index`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Excepciones a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Devuelve una clausura sumadora cuya invocación lea el enlace `delta` creado por esa llamada concreta a la fábrica.", | ||
| "objective": "Crear sumadores invocables que retengan incrementos positivos, negativos y nulos por separado.", | ||
| "language_delta": "`lambda` solo ofrece sintaxis de expresión para una función y comparte el mismo modelo de resolución léxica que un `def` anidado.", | ||
| "prediction_prompt": "Predice `[reader() for reader in [lambda n=n: n for n in range(3)]]`.", | ||
| "transfer_trap": "Los enlaces de bloque de un bucle JavaScript suelen capturar un valor por iteración; Python no crea esas celdas separadas de forma predeterminada." | ||
| }, | ||
| "py-files-context": { | ||
| "title": "Archivos y context managers", | ||
| "concept": "`with` entra en un contexto administrado y ejecuta la limpieza al salir. Archivos y utilidades de contextlib usan este patrón para cerrar recursos aunque el bloque termine antes.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Archivos y context managers. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-decorators": { | ||
| "title": "Decoradores en el momento de definición", | ||
| "concept": "Aplicar `@bracket` evalúa el decorador tras crear el cuerpo y vuelve a enlazar el nombre con el envoltorio devuelto. `functools.wraps` conserva metadatos mientras cada llamada transforma el resultado.", | ||
| "worked_example": "El decorador `tag` crea un envoltorio para `label`. Al llamar `label(\"Mina\")`, el resultado original se convierte a mayúsculas y queda rodeado por signos angulares.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Archivos y context managers en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Archivos y context managers.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `with` entra en un contexto administrado y ejecuta la limpieza al salir." | ||
| "Invocar la función decorada mientras se define el decorador en vez de devolver un envoltorio invocable.", | ||
| "Omitir `functools.wraps` y romper introspección, ayuda o herramientas que esperan los metadatos originales." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Archivos y context managers antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Archivos y context managers debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué sucede una vez al definir y qué comportamiento se repite en cada llamada?", | ||
| "¿Por qué `wrapper` debe aceptar el valor enviado a la función decorada?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Archivos y context managers a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Completa el envoltorio para convertir a mayúsculas el valor original, añadir corchetes y conservar los metadatos de la función.", | ||
| "objective": "Transformar resultados ASCII y Unicode sin modificar el cuerpo de la función decorada.", | ||
| "language_delta": "Los decoradores son aplicación ordinaria de invocables ligada a `def`; `wraps` copia ciertos atributos, pero no cambia la ejecución del envoltorio.", | ||
| "prediction_prompt": "Si el decorador imprime `decorate` y el envoltorio imprime `call`, ¿cuándo aparece cada palabra?", | ||
| "transfer_trap": "Una anotación Java es metadato, no un envoltorio automático; la misma forma con `@` no implica el mismo comportamiento." | ||
| }, | ||
| "py-modules-imports": { | ||
| "title": "Módulos e import", | ||
| "concept": "Un módulo agrupa código reutilizable bajo un espacio de nombres. `import math` hace visible la biblioteca estándar y PEP 8 recomienda ubicar imports arriba para mostrar dependencias temprano.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Módulos e import. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-async": { | ||
| "title": "Ejecutar corrutinas reales con `asyncio`", | ||
| "concept": "Llamar a `async def` crea una corrutina sin completarla. `asyncio.run` aporta el bucle de eventos, `gather` coordina los objetos esperables y `await asyncio.sleep(0)` cede el control antes de devolver.", | ||
| "worked_example": "Las corrutinas que convierten `go` y `now` llegan a una suspensión real. `gather` espera ambas y conserva el orden de argumentos, así que se imprime `GO NOW` aunque se intercalen.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Módulos e import en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Módulos e import.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Un módulo agrupa código reutilizable bajo un espacio de nombres." | ||
| "Tratar el objeto corrutina devuelto por una llamada como si ya fuera el valor calculado.", | ||
| "Usar `time.sleep` dentro de código asíncrono y bloquear el hilo del bucle en lugar de cederlo." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Módulos e import antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Módulos e import debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué componente impulsa `main` después de que `main()` crea su corrutina?", | ||
| "¿Ordena `asyncio.gather` sus resultados por finalización o por posición de argumento?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Módulos e import a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Crea una corrutina `double` por cada entero analizado, reúnelas con `gather`, espéralas e imprime los resultados en orden.", | ||
| "objective": "Ejecutar suspensiones reales con varias entradas, signos distintos y entrada vacía, sin corrutinas abandonadas.", | ||
| "language_delta": "`asyncio` es cooperativo: cambia de tarea en los `await`; crear una corrutina por sí solo no la programa ni ejecuta.", | ||
| "prediction_prompt": "Predice la lista de `await asyncio.gather(double(3), double(-1))` si `double` espera `sleep(0)`.", | ||
| "transfer_trap": "Una función que produce `Promise` en JavaScript comienza bajo otras reglas; no traslades esa intuición de inicio inmediato a una corrutina Python." | ||
| }, | ||
| "py-dataclasses": { | ||
| "title": "Dataclasses", | ||
| "concept": "`@dataclass` genera métodos rutinarios para clases que principalmente contienen datos. Mantiene campos, type hints y construcción juntos sin escribir boilerplate manual.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Dataclasses. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-lists-dicts": { | ||
| "title": "Agrupar listas mediante claves de diccionario", | ||
| "concept": "Cada clave identifica una lista independiente de puntuaciones. `setdefault` crea el grupo cuando hace falta y `get(query, [])` convierte un nombre ausente en una secuencia cuya suma es cero.", | ||
| "worked_example": "`Mina` se asocia con `[8, 1]`, cuya suma es `9`. La lista separada de `Kai` demuestra que cada clave mantiene su propio grupo.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Dataclasses en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Dataclasses.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `@dataclass` genera métodos rutinarios para clases que principalmente contienen datos." | ||
| "Acceder a `scores[query]` aunque el nombre solicitado puede no aparecer entre los pares.", | ||
| "Reutilizar la misma lista para todas las claves y mezclar las puntuaciones de personas diferentes." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Dataclasses antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Dataclasses debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué token es la consulta y en qué posición comienzan los pares nombre-puntuación?", | ||
| "¿Por qué `sum(scores.get(missing, []))` produce cero sin excepción?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Dataclasses a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Recorre de dos en dos los tokens restantes, añade cada puntuación numérica a la lista de su nombre y suma el grupo consultado.", | ||
| "objective": "Devolver el total agrupado para nombres repetidos, puntuaciones con signo y una consulta ausente.", | ||
| "language_delta": "`Counter` y `defaultdict` especializan conteos y creación de grupos; un `dict` de listas sigue siendo la opción clara para esta acumulación por clave.", | ||
| "prediction_prompt": "Si la consulta es `Bo` y solo hay pares de `Ada` y `Lin`, predice `scores.get(query, [])` y su suma.", | ||
| "transfer_trap": "Los objetos JavaScript heredan claves de prototipo; un diccionario Python admite claves hashables y `get` no inserta una entrada ausente." | ||
| }, | ||
| "py-typing": { | ||
| "title": "Type hints", | ||
| "concept": "Los type hints describen formas esperadas para lectores, editores y verificadores. Python sigue ejecutando valores dinámicamente, así que el cuerpo debe cumplir el contrato anotado.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Type hints. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-sorting-keys": { | ||
| "title": "Claves de ordenación con varios campos", | ||
| "concept": "Python compara las tuplas de clave de izquierda a derecha. Negar la puntuación coloca primero los valores mayores; el nombre sin modificar decide el orden alfabético ascendente en los empates.", | ||
| "worked_example": "`Bo` tiene `9` y gana por el campo principal. `Mina` y `Ana` empatan con `7`; entre ellas, el segundo campo coloca `Ana` antes que `Mina`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Type hints en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Type hints.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Los type hints describen formas esperadas para lectores, editores y verificadores." | ||
| "Usar `reverse=True` sobre la tupla completa y revertir también el desempate alfabético.", | ||
| "Depender del orden estable de entrada cuando el requisito exige una segunda regla explícita." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Type hints antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Type hints debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué tuplas de clave producen `Zed:5` y `Amy:5`?", | ||
| "¿Por qué ordenar solo por nombre falla si otro registro tiene mayor puntuación?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Type hints a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Analiza cada registro y selecciona el primero al ordenar por puntuación descendente y, en caso de empate, por nombre ascendente.", | ||
| "objective": "Elegir el ganador con puntuaciones distintas, empates y un único registro negativo.", | ||
| "language_delta": "`sorted` crea otra lista y es estable. Una función `key` calcula una representación por elemento, en vez de comparar pares repetidamente.", | ||
| "prediction_prompt": "¿Qué registro gana entre `(\"Zed\", 5)` y `(\"Amy\", 5)` con clave `(-score, name)`?", | ||
| "transfer_trap": "Restar valores en un comparador de otro lenguaje puede desbordarse y tampoco expresa el desempate nominal; una tupla evita ambos problemas." | ||
| }, | ||
| "py-generators": { | ||
| "title": "Iteradores y generadores", | ||
| "concept": "Un iterador produce valores uno a uno. Una función generadora usa `yield`, devuelve primero un objeto generador y ejecuta su cuerpo cuando el llamador pide el siguiente valor.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Iteradores y generadores. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-counter-defaultdict": { | ||
| "title": "Contar y agrupar con `collections`", | ||
| "concept": "`Counter` responde cero para un elemento ausente. `defaultdict(list)` crea grupos independientes al indexar, mientras `get` puede consultar una inicial sin añadir una clave nueva.", | ||
| "worked_example": "`owl` aparece dos veces entre cinco palabras; todas empiezan por `o`, por lo que ese grupo tiene longitud cinco. La muestra imprime dos estadísticas distintas.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Iteradores y generadores en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Iteradores y generadores.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Un iterador produce valores uno a uno." | ||
| "Incluir el token inicial de consulta entre los datos e inflar su frecuencia.", | ||
| "Leer `groups[missing_initial]` solo para consultar y mutar el `defaultdict` como efecto secundario." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Iteradores y generadores antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Iteradores y generadores debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué `Counter(words)[\"absent\"]` devuelve cero sin comprobar pertenencia?", | ||
| "¿Qué operación crea un grupo: el indexado o `get`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Iteradores y generadores a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Cuenta las palabras de datos, agrúpalas por su primer carácter e informa de la frecuencia consultada y del tamaño de su grupo inicial.", | ||
| "objective": "Calcular ambas estadísticas para consultas presentes, repetidas y completamente ausentes.", | ||
| "language_delta": "`Counter` y `defaultdict` especializan el comportamiento de `dict`: el primero aporta cero para una frecuencia ausente y el segundo delega en una fábrica la creación de valores faltantes.", | ||
| "prediction_prompt": "Con consulta `x` y datos `a b`, decide si `groups.get(\"x\", [])` inserta la clave `x`.", | ||
| "transfer_trap": "Un `dict` normal todavía lanza `KeyError` al indexar una ausencia; esos valores predeterminados pertenecen a tipos concretos." | ||
| }, | ||
| "py-lambdas-closures": { | ||
| "title": "Lambdas y closures", | ||
| "concept": "`lambda` crea una función de una expresión. Si esa función usa un nombre de una función externa, forma un closure y recuerda ese valor después de que la llamada externa termina.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Lambdas y closures. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-deque": { | ||
| "title": "Operaciones de tiempo constante en ambos extremos", | ||
| "concept": "Una `deque` optimiza inserciones y extracciones en los dos extremos. `appendleft` y `popleft` actúan a la izquierda; `append` y `pop`, a la derecha.", | ||
| "worked_example": "Partiendo de `center`, la muestra coloca `first` a la izquierda y `last` a la derecha. Al retirar ambos extremos imprime `first last` y deja intacto el centro.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Lambdas y closures en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Lambdas y closures.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `lambda` crea una función de una expresión." | ||
| "Usar `append` para los dos extremos y dejar el valor central en la posición izquierda.", | ||
| "Tratar `list.pop(0)` como una cola equivalente aunque desplaza elementos al crecer." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Lambdas y closures antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Lambdas y closures debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "Después de `appendleft(left)`, ¿qué valor devolverá `popleft()`?", | ||
| "¿Qué operación de `deque` elimina el elemento situado más a la derecha?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Lambdas y closures a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Coloca el segundo token a la izquierda y el tercero a la derecha; después retira e imprime esos dos extremos en orden.", | ||
| "objective": "Conservar el centro e informar correctamente de extremos ASCII y Unicode.", | ||
| "language_delta": "Las colas de búsqueda consumen repetidamente un frente; la indexación aleatoria sigue siendo una ventaja de las listas, no de `deque`.", | ||
| "prediction_prompt": "Sigue `deque([\"m\"])` tras `appendleft(\"l\")`, `append(\"r\")`, `popleft()` y `pop()`.", | ||
| "transfer_trap": "Otras bibliotecas llaman `offer` o `poll` a operaciones de cola; traduce el extremo semántico, no el nombre del método." | ||
| }, | ||
| "py-decorators": { | ||
| "title": "Decoradores", | ||
| "concept": "Un decorador se ejecuta cuando se define la función. Recibe el objeto función original y devuelve el objeto que quedará enlazado al nombre de la función.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Decoradores. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-comprehensions": { | ||
| "title": "Punto de control: filtrar, transformar y sumar", | ||
| "concept": "La comprensión evalúa el filtro para cada número y solo calcula el cuadrado de los aceptados. La lista resultante se materializa por completo antes de que `sum` la consuma.", | ||
| "worked_example": "Entre `5`, `6`, `7` y `8`, solo pasan `6` y `8`. Sus cuadrados `36` y `64` forman una lista cuyo total es `100`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Decoradores en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Decoradores.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Un decorador se ejecuta cuando se define la función." | ||
| "Invertir el predicado de paridad y sumar silenciosamente cuadrados impares.", | ||
| "Acumular condiciones anidadas y efectos secundarios hasta ocultar el orden de evaluación." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Decoradores antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Decoradores debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Se ejecuta la expresión de salida para un elemento rechazado por el `if` final?", | ||
| "¿Qué lista genera `[-2, -1, 0]` bajo la regla de cuadrados pares?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Decoradores a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Construye los cuadrados pares con una única comprensión legible y, después de materializar esa lista, imprime su suma.", | ||
| "objective": "Resolver signos mezclados y una selección vacía mostrando que se filtra antes de transformar.", | ||
| "language_delta": "Una expresión generadora produciría valores de forma perezosa; aquí los corchetes hacen visible la construcción inmediata de la lista.", | ||
| "prediction_prompt": "Escribe la lista intermedia exacta obtenida de `[1, 2, 3, 4]` antes de aplicar `sum`.", | ||
| "transfer_trap": "Los métodos de arreglos JavaScript y los flujos Java tienen otra estrategia de evaluación; una comprensión entre corchetes no es perezosa." | ||
| }, | ||
| "py-sorting-keys": { | ||
| "title": "Ordenación y funciones key", | ||
| "concept": "`sorted` crea una lista ordenada nueva. Una función `key` extrae el valor que se comparará para cada elemento, por ejemplo una puntuación dentro de una tupla.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Ordenación y funciones key. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-generators": { | ||
| "title": "Generadores perezosos de cuenta atrás", | ||
| "concept": "Una función que contiene `yield` devuelve un iterador generador. El cuerpo comienza al primer avance, conserva su estado local en cada pausa y termina con `StopIteration`.", | ||
| "worked_example": "`reverse_letters(\"abc\")` crea primero un iterador sin recorrer el texto. Al expandirlo en `print`, tres avances producen `c`, `b` y `a` separados por espacios.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Ordenación y funciones key en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Ordenación y funciones key.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `sorted` crea una lista ordenada nueva." | ||
| "Esperar que el código anterior al primer `yield` se ejecute al crear el generador.", | ||
| "Volver a recorrer un generador agotado y esperar que reaparezcan sus valores." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Ordenación y funciones key antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Ordenación y funciones key debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué operación inicia el cuerpo `while` de `countdown`?", | ||
| "¿Por qué `print(*countdown(0))` todavía produce un salto de línea?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Ordenación y funciones key a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Cede el número positivo actual, reduce el estado local suspendido y repite hasta alcanzar cero sin construir una lista intermedia.", | ||
| "objective": "Generar cuentas completas, de un elemento y vacías con estado suspendido independiente.", | ||
| "language_delta": "Cada llamada a una función generadora fabrica un estado nuevo; un `return` normal finaliza la iteración y no emite otro elemento.", | ||
| "prediction_prompt": "Tras un único `next(countdown(3))`, ¿qué valor sale y con qué número local se reanuda luego?", | ||
| "transfer_trap": "Un generador JavaScript devuelve objetos de resultado distintos; `next` en Python entrega el valor o lanza `StopIteration`." | ||
| }, | ||
| "py-counter-defaultdict": { | ||
| "title": "Counter y defaultdict", | ||
| "concept": "`Counter` cuenta valores hashables y `defaultdict(list)` crea listas vacías para grupos nuevos. Juntos eliminan mucha inicialización manual en conteo y agrupación.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Counter y defaultdict. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-itertools": { | ||
| "title": "Aplanar iterables de forma perezosa", | ||
| "concept": "`chain.from_iterable` recibe un iterable exterior y avanza por cada iterable interior. Expone un flujo perezoso sin crear una lista aplanada temporal.", | ||
| "worked_example": "La tupla exterior contiene `py` y `thon`. El iterador encadenado recorre primero los caracteres del primer texto y luego los del segundo; `join` consume el flujo y forma `python`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Counter y defaultdict en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Counter y defaultdict.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `Counter` cuenta valores hashables y `defaultdict(list)` crea listas vacías para grupos nuevos." | ||
| "Usar `chain(groups)` y obtener cada cadena interior completa en lugar de sus caracteres.", | ||
| "Consumir parte del iterador para inspeccionarlo y esperar que `join` vuelva a empezar." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Counter y defaultdict antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Counter y defaultdict debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Cuántos argumentos exteriores recibe `chain.from_iterable` en este ejercicio?", | ||
| "¿Qué queda después de que `next` consuma el primer carácter del flujo aplanado?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Counter y defaultdict a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Pasa todos los grupos analizados a `chain.from_iterable` y une en una cadena el flujo de un solo recorrido resultante.", | ||
| "objective": "Aplanar grupos de longitudes distintas sin construir listas anidadas temporales.", | ||
| "language_delta": "Las operaciones de `itertools` aplazan el trabajo hasta que un consumidor avanza; `join` materializa aquí la cadena final.", | ||
| "prediction_prompt": "Predice `list(chain.from_iterable([\"ab\", \"c\"]))` antes de considerar el `join` final.", | ||
| "transfer_trap": "Un flujo de otro ecosistema puede reiniciarse o almacenar resultados; los iteradores Python suelen conservar un estado de consumo mutable." | ||
| }, | ||
| "py-deque": { | ||
| "title": "deque", | ||
| "concept": "`deque` es una cola de dos extremos con append y pop eficientes a ambos lados. Es la estructura habitual para BFS y ventanas donde se elimina por el frente.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de deque. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-errors": { | ||
| "title": "Recuperación precisa de `ValueError`", | ||
| "concept": "`int` lanza `ValueError` cuando el texto no representa un entero. Limitar el bloque `try` a esa conversión y capturar la excepción concreta evita ocultar defectos ajenos.", | ||
| "worked_example": "Convertir `oops` produce `ValueError`; el control entra en `except` y convierte el valor alternativo `-8`, que pasa a ser el resultado impreso.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de deque en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de deque.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `deque` es una cola de dos extremos con append y pop eficientes a ambos lados." | ||
| "Capturar `Exception` alrededor de todo el programa y disfrazar errores no relacionados como salidas válidas.", | ||
| "Tratar `-3` como texto inválido aunque `int` acepta normalmente el signo." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica deque antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de deque debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Una primera entrada válida ejecuta el bloque `except`?", | ||
| "¿Qué sentencia puede lanzar exactamente el `ValueError` que se desea recuperar?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando deque a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Intenta convertir el primer token y utiliza el segundo únicamente cuando esa conversión concreta lance `ValueError`.", | ||
| "objective": "Distinguir un entero negativo válido de texto mal formado y aplicar el valor alternativo solo cuando corresponde.", | ||
| "language_delta": "`try` selecciona una recuperación; un gestor `with` resuelve otra responsabilidad, garantizar la salida y limpieza del recurso.", | ||
| "prediction_prompt": "Sigue las ramas para candidatos `-3` y `bad` cuando ambos comparten el valor alternativo `9`.", | ||
| "transfer_trap": "`parseInt(\"bad\")` en JavaScript devuelve `NaN` en vez de lanzar; copiar allí este `try/except` nunca elegiría la alternativa." | ||
| }, | ||
| "py-itertools": { | ||
| "title": "itertools", | ||
| "concept": "`itertools` ofrece bloques perezosos como `chain`, `islice`, `product` y `pairwise`. Describen pipelines de iteración sin listas intermedias salvo que se consuman.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de itertools. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-files-context": { | ||
| "title": "Salida garantizada de un gestor de contexto", | ||
| "concept": "El protocolo `with` obtiene el valor mediante `__enter__` y llama a `__exit__` al abandonar el bloque, incluso por excepción. `StringIO` reproduce lectura y cierre sin un archivo real.", | ||
| "worked_example": "El recurso en memoria contiene ` Seoul ` con espacios. Se lee dentro del bloque, se normaliza y luego produce `SEOUL`; al imprimirlo, el recurso ya está cerrado.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de itertools en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de itertools.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `itertools` ofrece bloques perezosos como `chain`, `islice`, `product` y `pairwise`." | ||
| "Intentar leer el recurso después del bloque `with`, cuando `StringIO` ya se cerró.", | ||
| "Creer que `__exit__` solo se ejecuta si todo termina bien y duplicar manualmente la limpieza." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica itertools antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de itertools debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué operación debe realizarse antes de salir de la indentación administrada?", | ||
| "¿Qué valor normalizado queda si el recurso solo contiene espacios?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando itertools a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Lee el recurso dentro de su contexto, normaliza el contenido y muestra `EMPTY` únicamente cuando no quede texto.", | ||
| "objective": "Observar el cierre garantizado y resolver contenido ordinario, rodeado de espacios y vacío en memoria.", | ||
| "language_delta": "`with` delega entrada y salida al protocolo del objeto; el texto copiado antes del cierre sigue siendo utilizable después.", | ||
| "prediction_prompt": "Si `handle.read()` devuelve dos espacios, predice `text` tras `strip` y luego `text.upper() or \"EMPTY\"`.", | ||
| "transfer_trap": "`using` de JavaScript y `try-with-resources` de Java usan otros protocolos; Python despacha específicamente `__enter__` y `__exit__`." | ||
| }, | ||
| "py-pathlib": { | ||
| "title": "pathlib", | ||
| "concept": "`pathlib` representa rutas como objetos. Propiedades como `name`, `stem`, `suffix` y `parent` evitan cortar strings a mano y aclaran qué parte de la ruta se necesita.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de pathlib. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-dataclasses": { | ||
| "title": "Clases de datos y métodos generados", | ||
| "concept": "Una clase aporta comportamiento de instancia y `@dataclass` deriva inicialización, representación e igualdad de los campos. No congela objetos anidados ni convierte anotaciones en validadores.", | ||
| "worked_example": "`Point(4, 5)` almacena dos campos; su método `total` devuelve `9` y la igualdad generada da `True` frente a otro punto con los mismos valores.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de pathlib en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de pathlib.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `pathlib` representa rutas como objetos." | ||
| "Suponer que la igualdad generada o `frozen=True` inmoviliza recursivamente los valores mutables guardados.", | ||
| "Declarar `values: list[int] = []`: Python 3.12 rechaza ese valor mutable; usa `field(default_factory=list)` por instancia o `ClassVar` si debe compartirse." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica pathlib antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de pathlib debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué implementación de igualdad compara dos instancias de `Point`?", | ||
| "¿La anotación `x: int` impide construir `Point(\"wrong\", 5)` en ejecución?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando pathlib a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Implementa `Point.total` con ambos campos y observa tanto el resultado del método como la igualdad creada por `dataclass`.", | ||
| "objective": "Construir puntos iguales y calcular totales con coordenadas de cualquier signo.", | ||
| "language_delta": "Las anotaciones pueden inspeccionarse pero Python normal no las impone; `dataclass` reduce repetición sin sustituir métodos ni validación de dominio.", | ||
| "prediction_prompt": "Predice `Point(2, 3) == Point(2, 3)` y después el efecto de cambiar uno de los campos.", | ||
| "transfer_trap": "Un `record` Java tiene componentes superficialmente inmutables; una clase de datos Python normal sigue siendo mutable salvo configuración explícita." | ||
| }, | ||
| "py-testing-assert": { | ||
| "title": "Pruebas y assert", | ||
| "concept": "`assert` comprueba una condición en ejemplos pequeños. Los frameworks de pruebas organizan esas comprobaciones para repetirlas, pero la idea central sigue siendo comparar comportamiento esperado y real.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Pruebas y assert. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-typing": { | ||
| "title": "Inspeccionar anotaciones sin imponerlas", | ||
| "concept": "Las anotaciones de función son metadatos para personas, editores y verificadores opcionales. `get_type_hints` las resuelve en ejecución, pero no convierte argumentos ni bloquea valores incompatibles.", | ||
| "worked_example": "`head` registra `Iterable[str]` como entrada y `str` como salida. La introspección identifica ese retorno, mientras el cuerpo devuelve por separado `elm` de la lista.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Pruebas y assert en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Pruebas y assert.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `assert` comprueba una condición en ejemplos pequeños." | ||
| "Creer que una anotación `int` rechaza automáticamente una cadena antes de entrar en la función.", | ||
| "Confundir la inspección de una promesa de tipo con comprobar que todas las rutas la cumplen." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Pruebas y assert antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Pruebas y assert debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué objeto expone `get_type_hints(total)[\"return\"]`?", | ||
| "¿Qué parte de `total` realiza la suma: la anotación o la expresión devuelta?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Pruebas y assert a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Conserva el contrato `Iterable[int] -> int`, implementa la suma y usa las anotaciones resueltas para etiquetar el resultado observable.", | ||
| "objective": "Verificar metadatos de tipos y sumar iterables múltiples, unitarios y vacíos.", | ||
| "language_delta": "Python puede ejecutar código anotado sin verificador; TypeScript también borra tipos, pero su flujo habitual comprueba antes de ejecutar.", | ||
| "prediction_prompt": "Si se llama `total([\"2\"])`, ¿la anotación lo rechaza o decide el resultado la operación del cuerpo?", | ||
| "transfer_trap": "Las anotaciones documentan valores esperados; no analizan texto, no convierten tipos ni proporcionan sobrecarga dinámica." | ||
| }, | ||
| "py-async": { | ||
| "title": "Conceptos async", | ||
| "concept": "`async def` crea una función de corrutina. Llamarla produce un objeto corrutina; `await` obtiene su resultado dentro de otra corrutina y `asyncio.run` ejecuta la corrutina superior.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Conceptos async. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "py-testing-assert": { | ||
| "title": "Proyecto final: clasificación determinista de palabras", | ||
| "concept": "El programa final materializa el iterable, cuenta palabras, valida invariantes de desarrollo y ordena por frecuencia descendente y palabra ascendente. La entrada vacía tiene una respuesta explícita.", | ||
| "worked_example": "`pear` y `plum` aparecen dos veces. La frecuencia no decide el empate, así que la segunda clave elige alfabéticamente `pear`; después se imprime `pear:2`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Conceptos async en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Conceptos async.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: `async def` crea una función de corrutina." | ||
| "Usar `Counter.most_common(1)` sin desempate alfabético y depender del orden de aparición.", | ||
| "Validar entrada o seguridad con `assert`, aunque Python optimizado elimina esas instrucciones." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Conceptos async antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Conceptos async debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué dos campos forman la clave y por qué se niega el conteo?", | ||
| "¿Qué ocurre con `assert` cuando el intérprete se inicia con `-O`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Conceptos async a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Sustituye el desempate por orden de llegada por una clasificación determinista de conteo y palabra, conservando la función tipada y sus invariantes.", | ||
| "objective": "Elegir la palabra más frecuente, resolver empates alfabéticamente y documentar el resultado de la entrada vacía.", | ||
| "language_delta": "Este proyecto reúne salida exacta, análisis, reasignación, colecciones, funciones, ordenación y tipos; las aserciones solo verifican supuestos de desarrollo.", | ||
| "prediction_prompt": "Para `b a b a`, enumera las entradas de `Counter` y predice el ganador con clave `(-count, word)`.", | ||
| "transfer_trap": "Las aserciones Java también pueden desactivarse; en ambos lenguajes, una condición obligatoria necesita control de flujo normal." | ||
| } | ||
| } | ||
| } |
+324
-249
@@ -7,377 +7,452 @@ { | ||
| "py-output": { | ||
| "title": "print と標準出力", | ||
| "concept": "`print` は計算結果を判定対象の標準出力へ出す境界である。引数を文字列化し、複数引数の間に空白を入れ、既定で改行するため、余分な出力はそのまま不一致になる。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「print と標準出力」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "`print`で正確に出力する", | ||
| "concept": "`print`は各引数を文字列に変換し、引数間に`sep`、末尾に`end`を出力します。既定値の空白と改行も、判定対象となる標準出力の一部です。", | ||
| "worked_example": "例では`Mina`と`9`を等号で連結し、通常の改行を感嘆符と改行に置き換えています。どちらの記号も手作業の連結ではなく、`print`のキーワード引数で指定しています。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「print と標準出力」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「print と標準出力」演習の TODO を解かないこと。", | ||
| "`print` は計算結果を判定対象の標準出力へ出す境界である。引数を文字列化し、複数引数の間に空白を入れ、既定で改行するため、余分な出力はそのまま不一致になる。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "値の間に記号が必要なのに、既定の区切りである空白を残してしまうことです。", | ||
| "デバッグ用の見出しを標準出力へ書き、解答に余分な文字を加えてしまうことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「print と標準出力」を適用してから `example:` 出力へつながりますか。", | ||
| "「print と標準出力」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`print(\"A\", \"B\", sep=\":\", end=\"!\")`は、どの文字を順に出力しますか。", | ||
| "最後の引数の後に出力する文字列は、どのキーワードで指定しますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「print と標準出力」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "名前と点数を読み、`print`を設定して、コロンで区切られた1行を改行ちょうど1つで終えてください。", | ||
| "objective": "3つの入力すべてで、デバッグ用の接頭辞や余分な空白を加えず、期待値と同じ標準出力を作ります。", | ||
| "language_delta": "生のストリーム書き込みと異なり、`print`は区切りと行末の既定値を持ちます。この違いを理解すると、以降のPython演習でも出力契約を明確にできます。", | ||
| "prediction_prompt": "実行前に、`print(\"x\", 0, sep=\"=\", end=\"?\")`が出力する文字を正確に書いてください。", | ||
| "transfer_trap": "他言語の`println`やコンソール出力にもPythonの`sep`と同じ機能があると思い込まず、空白と改行の規則を確認してください。" | ||
| }, | ||
| "py-variables": { | ||
| "title": "変数", | ||
| "concept": "変数名はオブジェクトへの束縛である。`=` は等価比較ではなく、以後その名前を読んだときにどの値を見るかを決める。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「変数」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-input": { | ||
| "title": "標準入力を一度で読む", | ||
| "concept": "`sys.stdin.read`は標準入力全体を1つの文字列として読みます。引数なしの`split`は種類を問わず空白を区切りとし、空のトークン列を合計すると加法単位元の`0`になります。", | ||
| "worked_example": "例は`10`と`4`を2行に分けた文字列を読み、改行位置を特別扱いせず分割して整数へ変換し、`14`を出力します。対話的な入力要求は行いません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「変数」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「変数」演習の TODO を解かないこと。", | ||
| "変数名はオブジェクトへの束縛である。`=` は等価比較ではなく、以後その名前を読んだときにどの値を見るかを決める。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`split(\" \")`を使い、改行や連続した空白を正しく区切れないことです。", | ||
| "トークンごとに`input`を呼び、入力の折り返し方が変わると読み取りに失敗することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「変数」を適用してから `example:` 出力へつながりますか。", | ||
| "「変数」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "空文字列に引数なしの`split`を呼ぶと、どのリストになりますか。", | ||
| "`sys.stdin.read`を1回だけ呼べば、1行入力と複数行入力の両方を扱えるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「変数」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "読み取り済みの文字列に含まれる全トークンを整数へ変換し、入力が空の場合も含めて合計を出力してください。", | ||
| "objective": "空白または改行で区切られた整数を、配置ごとの特別処理なしで正しく合計します。", | ||
| "language_delta": "ここでの標準入力はテキストストリームです。`split`が返すのは文字列なので、数値への変換は明示的に行う必要があります。", | ||
| "prediction_prompt": "`sum(int(token) for token in \" -2\\n5 \".split())`の値を予測してください。", | ||
| "transfer_trap": "他の環境のスキャナが空白を自動で読み飛ばしても、Pythonで読み取った生のテキストは`int`を呼ぶまで数値になりません。" | ||
| }, | ||
| "py-numbers": { | ||
| "title": "数値", | ||
| "concept": "Python の整数は実用上大きな値も扱える。`/` は float、`//` は整数商、`%` は余り、`**` は累乗なので、問題の形式に合う演算子を選ぶ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「数値」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-variables": { | ||
| "title": "名前の束縛と再束縛", | ||
| "concept": "Pythonの変数は型付きの箱ではなく、オブジェクトに束縛された名前です。整数の累積値を更新すると新しい整数へ名前が再束縛され、クロージャは外側の名前を保持するセルを呼び出し時に参照します。", | ||
| "worked_example": "例では最初に`total`を`10`へ束縛し、拡張代入によって`15`へ再束縛します。整数`10`自体は不変なので、元のオブジェクトを変更してはいません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「数値」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「数値」演習の TODO を解かないこと。", | ||
| "Python の整数は実用上大きな値も扱える。`/` は float、`//` は整数商、`%` は余り、`**` は累乗なので、問題の形式に合う演算子を選ぶ。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "整数への拡張代入が、元の整数オブジェクトをその場で変更すると説明することです。", | ||
| "同じループで作ったラムダが各反復時の値を保存すると考え、共有セルの遅延参照を見落とすことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「数値」を適用してから `example:` 出力へつながりますか。", | ||
| "「数値」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`total = left`の後に`total += right`を実行すると、どの名前が再束縛されますか。", | ||
| "終了した同じループ変数を閉じ込めた3つのラムダは、通常どの値を読みますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「数値」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "最初の整数を累積値へ束縛し、2番目の整数を使って再束縛してから出力してください。", | ||
| "objective": "正数、負数、`0`を含む入力で算術和を作り、再束縛の動きを確認します。", | ||
| "language_delta": "Pythonでは名前に可変性を宣言せず、代入で名前とオブジェクトの関係を変えます。クロージャが読む値は、作成時ではなく呼び出し時のセルの内容で決まる場合があります。", | ||
| "prediction_prompt": "`readers = [lambda: n for n in range(3)]`の後、`[reader() for reader in readers]`を予測してください。", | ||
| "transfer_trap": "JavaScriptの一般的な`let`ループは反復ごとに新しい束縛を作るため、そのクロージャの直感をPythonへ持ち込まないでください。" | ||
| }, | ||
| "py-strings": { | ||
| "title": "文字列", | ||
| "concept": "文字列は変更できない Unicode 文字の列である。インデックスは 1 文字を読み、スライスは終端を含まない範囲を読み、元の文字列は変わらない。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「文字列」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-numbers": { | ||
| "title": "床除算の商と余り", | ||
| "concept": "整数の`//`は、数学的な商以下で最大の整数を返します。余りは`dividend == divisor * quotient + remainder`を満たし、`0`でない余りは除数と同じ符号になります。", | ||
| "worked_example": "`-11`を`4`で割ると、床方向の商は`-3`、余りは`1`です。`-3 * 4 + 1`で元の`-11`を復元できます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「文字列」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「文字列」演習の TODO を解かないこと。", | ||
| "文字列は変更できない Unicode 文字の列である。インデックスは 1 文字を読み、スライスは終端を含まない範囲を読み、元の文字列は変わらない。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`int(dividend / divisor)`で置き換え、負の値を`0`方向へ切り捨ててしまうことです。", | ||
| "除数が負の場合にも、余りは必ず非負だと思い込むことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「文字列」を適用してから `example:` 出力へつながりますか。", | ||
| "「文字列」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "被除数、除数、商、余りを結ぶ恒等式は何ですか。", | ||
| "`-7 // 2`が`int(-7 / 2)`より1小さくなるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「文字列」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "除数が`0`でない2整数を読み、Pythonの床除算による商と対応する余りを並べて出力してください。", | ||
| "objective": "除数の符号が正負どちらでも、Pythonの除算恒等式を保つ商と余りを求めます。", | ||
| "language_delta": "Pythonの`//`は符号付き商を床方向へ丸めます。JavaとRustの整数除算は`0`方向へ切り捨て、JavaScriptとTypeScriptの`number`除算では`7 / 2`が`3.5`になります。", | ||
| "prediction_prompt": "`(-7 // 2, -7 % 2)`と`(7 // -2, 7 % -2)`の両方を計算してください。", | ||
| "transfer_trap": "同じ`/`記号でも、他言語では小数除算や`0`方向の整数除算になります。符号の異なる入力で規則を確かめてください。" | ||
| }, | ||
| "py-control-flow": { | ||
| "title": "制御フロー", | ||
| "concept": "`if` はブロックを選び、`for` は iterable を消費し、`while` は条件が変わるまで繰り返す。Python ではインデントが構文なので、行の位置が実行範囲を決める。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「制御フロー」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-tuples-sets": { | ||
| "title": "順序付きタプルと一意な集合", | ||
| "concept": "タプルは位置と重複を保つため、入力レコードの並びを表せます。集合はハッシュ可能な要素の重複を除きますが、反復順を解答の整形に利用してはいけません。", | ||
| "worked_example": "タプル`(\"r\", \"s\", \"r\")`を結合すると`rsr`のままですが、集合へ変換すると異なる文字は2つです。同じ元データの別の性質を観察しています。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「制御フロー」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「制御フロー」演習の TODO を解かないこと。", | ||
| "`if` はブロックを選び、`for` は iterable を消費し、`while` は条件が変わるまで繰り返す。Python ではインデントが構文なので、行の位置が実行範囲を決める。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "空の集合を`{}`と書き、辞書を作ってしまうことです。", | ||
| "タプルではなく集合を結合し、重複と安定した位置を失うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「制御フロー」を適用してから `example:` 出力へつながりますか。", | ||
| "「制御フロー」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "タプル`(\"a\", \"a\")`に2つの位置が残るのはなぜですか。", | ||
| "曖昧さなく空の集合を作るコンストラクタは何ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「制御フロー」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "結合にはタプルの順序を使い、一意な要素数を数えるためだけに別の集合を作ってください。", | ||
| "objective": "重複やUnicode文字を含む入力でも、元の順序による連結結果と一意な要素数を出力します。", | ||
| "language_delta": "Pythonではタプルと集合がどちらも実行時の別コンテナです。型注釈がなくても、順序や重複に関する挙動は変わりません。", | ||
| "prediction_prompt": "`pair = (\"é\", \"e\", \"é\")`について、結合結果と`len(set(pair))`を予測してください。", | ||
| "transfer_trap": "TypeScriptのタプルは主に型検査上の制約ですが、Pythonのタプルは実行時にも不変なシーケンスです。" | ||
| }, | ||
| "py-functions": { | ||
| "title": "関数", | ||
| "concept": "`def` は呼び出し可能な関数オブジェクトを作る。引数は仮引数に入り、`return` は呼び出し元へ値を返す。関数内の print とは役割が違う。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「関数」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-strings": { | ||
| "title": "Unicodeコードポイントのスライス", | ||
| "concept": "Pythonの`str`はUTF-8バイトではなくUnicodeコードポイント単位で添字とスライスを扱います。スライスは半開区間で新しい文字列を作り、不変な元文字列は変更しません。", | ||
| "worked_example": "星印が`서울`の先頭と末尾を囲んでいます。`1`から`-1`の直前までを選ぶと、バイト位置を数えずに`서울`を取り出せます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「関数」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「関数」演習の TODO を解かないこと。", | ||
| "`def` は呼び出し可能な関数オブジェクトを作る。引数は仮引数に入り、`return` は呼び出し元へ値を返す。関数内の print とは役割が違う。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "正確に1つの境界記号を除く場面で`strip`を使い、両端の該当文字を必要以上に削ることです。", | ||
| "結合文字や絵文字列が複数コードポイントになり得るのに、コードポイントを常に画面上の1文字と呼ぶことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「関数」を適用してから `example:` 出力へつながりますか。", | ||
| "「関数」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`text[1:-1]`は元文字列のどの添字を除外しますか。", | ||
| "境界が1コードポイントの絵文字でも同じスライスを安全に使えるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「関数」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "半開区間のスライスを1回使って先頭と末尾のコードポイントを除き、内側の文字列を出力してください。", | ||
| "objective": "ASCII、括弧、絵文字で囲まれた文字列から、元を変更せず内側を取り出します。", | ||
| "language_delta": "Pythonのコードポイント添字は、JavaとJavaScriptのUTF-16コード単位やRustのUTF-8バイト境界と異なります。いずれも、表示上の書記素クラスタを自動的に表す単位ではありません。", | ||
| "prediction_prompt": "`len(\"🙂go🙂\")`と`\"🙂go🙂\"[1:-1]`の結果を予測してください。", | ||
| "transfer_trap": "コードポイント単位で安全でも書記素単位とは限りません。結合文字で構成された表示上の1文字は複数位置を占めます。" | ||
| }, | ||
| "py-input": { | ||
| "title": "入力の解析", | ||
| "concept": "競技プログラミングの入力は stdin のテキストから始まる。一度読んでトークン化し、型変換を済ませてから通常の値として解くと流れが明確になる。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「入力の解析」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-pathlib": { | ||
| "title": "`pathlib`でパス要素を読む", | ||
| "concept": "`PurePosixPath`は実在確認をせずPOSIXパス構文を扱います。`stem`は最後の接尾辞だけを除き、`suffix`も最後の拡張子だけを返します。", | ||
| "worked_example": "`/srv/report.csv`の末尾要素は`report.csv`、`stem`は`report`、`suffix`は`.csv`です。例は一度解析したパスから2つの属性を読みます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「入力の解析」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「入力の解析」演習の TODO を解かないこと。", | ||
| "競技プログラミングの入力は stdin のテキストから始まる。一度読んでトークン化し、型変換を済ませてから通常の値として解くと流れが明確になる。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "ピリオドで文字列を手分割し、ディレクトリ、多重拡張子、ドットファイルを誤って扱うことです。", | ||
| "`archive.tar.gz`の`stem`が`archive`になると思い、最後の`.gz`だけが除かれる規則を見落とすことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「入力の解析」を適用してから `example:` 出力へつながりますか。", | ||
| "「入力の解析」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`PurePosixPath(\".env\")`の`stem`と`suffix`は何ですか。", | ||
| "ホストのファイルシステムへ触れてはいけない演習に`PurePosixPath`が適するのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「入力の解析」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "POSIXパスをオブジェクトへ変換し、最後の`stem`と最後の`suffix`を順に報告してください。", | ||
| "objective": "通常ファイル、多重拡張子、ドットファイルを文字列の推測ではなく`pathlib`の属性で処理します。", | ||
| "language_delta": "純粋パスは字句上の操作だけを行います。具体的な`Path`はファイルシステム操作も提供し、ホストのパス形式に従います。", | ||
| "prediction_prompt": "`PurePosixPath(\"a/bundle.min.js\").stem`と`.suffix`を実行前に求めてください。", | ||
| "transfer_trap": "`PurePosixPath`に渡したWindowsの区切り文字は通常文字として扱われます。入力契約に合うパス形式を選んでください。" | ||
| }, | ||
| "py-lists-dicts": { | ||
| "title": "リストと辞書", | ||
| "concept": "リストは順序付きの値を保持し、辞書はキーから値を直接引く。名前、文字、ID で探すデータなら、位置を走査するより辞書のほうが意図を表しやすい。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「リストと辞書」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-control-flow": { | ||
| "title": "分岐、ループ、`range`の境界", | ||
| "concept": "Pythonではインデントがループと条件分岐の本体を決めます。`range`は第2引数を含まないため、上限を含めるには停止値を調整してから奇数を選びます。", | ||
| "worked_example": "例は停止値を`8`にして`1`から`7`まで反復します。`1`、`3`、`5`、`7`だけを加え、合計は`16`です。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「リストと辞書」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「リストと辞書」演習の TODO を解かないこと。", | ||
| "リストは順序付きの値を保持し、辞書はキーから値を直接引く。名前、文字、ID で探すデータなら、位置を走査するより辞書のほうが意図を表しやすい。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "上限をそのまま`range`の停止値へ渡し、奇数の上限を黙って除外することです。", | ||
| "加算行のインデントを誤り、条件に関係なく全値を加えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「リストと辞書」を適用してから `example:` 出力へつながりますか。", | ||
| "「リストと辞書」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`range(1, 4)`はどの整数を生成しますか。", | ||
| "`total += value`を条件付きにするには、どのブロックへインデントしますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「リストと辞書」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "`1`から入力された上限までを含めて反復し、奇数だけを合計してください。", | ||
| "objective": "上限が`0`、奇数、偶数の場合に、含まれる奇数の合計を正しく返します。", | ||
| "language_delta": "Pythonのインデントは構文ですが、中括弧を使う言語では見た目の字下げだけでは実行範囲を決めません。また範囲APIが終端を含むかも言語ごとに異なります。", | ||
| "prediction_prompt": "上限が`6`のときに加算される値を列挙し、最終合計を予測してください。", | ||
| "transfer_trap": "他言語の包含範囲を機械的に移すと1つずれます。Pythonの`range`が停止値を含まないことを確認してください。" | ||
| }, | ||
| "py-tuples-sets": { | ||
| "title": "タプルとセット", | ||
| "concept": "タプルは位置が固定された値のまとまりで、セットは重複しない要素と高速な membership 判定に向く。順序が必要な出力ではセットだけに頼らない。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「タプルとセット」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-functions": { | ||
| "title": "チェックポイント:面積を返す関数", | ||
| "concept": "`area`は計算だけを担当し、その結果を返します。入力解析は外側に残し、呼び出し側だけが出力すれば、関数をテスト、インポート、装飾、非同期処理から再利用できます。", | ||
| "worked_example": "`area(8, 2)`は引数を掛けて`16`を返します。外側の`print`が返り値を受け取り、関数本体は標準出力へ何も書きません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「タプルとセット」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「タプルとセット」演習の TODO を解かないこと。", | ||
| "タプルは位置が固定された値のまとまりで、セットは重複しない要素と高速な membership 判定に向く。順序が必要な出力ではセットだけに頼らない。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`area`内で出力し、呼び出し側でもその`None`返り値を出力することです。", | ||
| "最初の例だけに合いそうな加算を選び、面積ではなく別の量を計算することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「タプルとセット」を適用してから `example:` 出力へつながりますか。", | ||
| "「タプルとセット」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "長方形の一辺が`0`なら、関数は何を返しますか。", | ||
| "例で標準出力へ書く唯一の行はどれですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「タプルとセット」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "値を返す計算を実装し、解析と最後の出力は呼び出し側に残してください。", | ||
| "objective": "一辺が`0`のケースを含む3つの長方形を、再利用可能な関数で計算します。", | ||
| "language_delta": "通常のPython関数は明示的な`return`へ到達しなければ`None`を返します。式本体が暗黙に返る言語の感覚とは異なります。", | ||
| "prediction_prompt": "`area(7, 1)`の値と、本文が`print`だけの関数の返り値をそれぞれ予測してください。", | ||
| "transfer_trap": "式形式の関数が暗黙に値を返す言語から移ると、Pythonの通常の`def`でも同じだと誤解しやすいので注意してください。" | ||
| }, | ||
| "py-comprehensions": { | ||
| "title": "内包表記", | ||
| "concept": "内包表記は出力式、ループ、任意のフィルタでコレクションを作る。単純な変換や抽出なら読みやすいが、複数手順の副作用には通常のループが向く。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「内包表記」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-modules-imports": { | ||
| "title": "モジュールで依存先を明示する", | ||
| "concept": "`import`は必要に応じてモジュールを読み込み、現在の名前空間へ名前を束縛します。呼び出し時に`math.ceil`と修飾すれば出所が明確で、ワイルドカードの衝突も避けられます。", | ||
| "worked_example": "`math.ceil(-3.8)`は入力以上で最小の整数`-3`を返します。この負数の切り上げは`0`へ近づき、`floor`とは反対です。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「内包表記」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「内包表記」演習の TODO を解かないこと。", | ||
| "内包表記は出力式、ループ、任意のフィルタでコレクションを作る。単純な変換や抽出なら読みやすいが、複数手順の副作用には通常のループが向く。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "どちらも丸めと呼ばれるため、負数に`math.floor`を選んでしまうことです。", | ||
| "ワイルドカードインポートを使い、`ceil`がどのモジュール由来か分からなくすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「内包表記」を適用してから `example:` 出力へつながりますか。", | ||
| "「内包表記」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`math.ceil(-2.1)`が`-3`ではなく`-2`になるのはなぜですか。", | ||
| "`import math`は現在の名前空間にどの名前を束縛しますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「内包表記」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "浮動小数点値を読み、モジュール名を付けて切り上げ関数を呼び出してください。", | ||
| "objective": "標準ライブラリを使い、正数、負数、整数値の切り上げを正しく出力します。", | ||
| "language_delta": "Pythonは同一プロセス内で読み込んだモジュールを`sys.modules`へキャッシュしますが、各`import`文はそのスコープへの名前束縛も行います。", | ||
| "prediction_prompt": "実行せずに`math.ceil(-0.2)`と`math.floor(-0.2)`を比較してください。", | ||
| "transfer_trap": "JavaのパッケージインポートやJavaScriptのモジュール読み込みは実行モデルが異なります。同じ語からPythonと同じキャッシュ動作を推測しないでください。" | ||
| }, | ||
| "py-errors": { | ||
| "title": "例外", | ||
| "concept": "例外は通常経路と回復可能な失敗を分ける。`try` は小さく保ち、`ValueError` のような具体的な例外だけを捕まえる。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「例外」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-lambdas-closures": { | ||
| "title": "クロージャが保持するセル", | ||
| "concept": "クロージャは外側の呼び出しが終わった後も、そのスコープの名前へアクセスできます。自由変数は内側の関数を実行するときに解決されるため、ループ内のラムダは通常1つのセルを共有します。", | ||
| "worked_example": "3つの`reader`は`index`が変わる途中で作られますが、実行はループ終了後です。全呼び出しが最後のセル値`2`を読み、結果は`[2, 2, 2]`になります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「例外」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「例外」演習の TODO を解かないこと。", | ||
| "例外は通常経路と回復可能な失敗を分ける。`try` は小さく保ち、`ValueError` のような具体的な例外だけを捕まえる。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "ループ内の各ラムダが、作成行で見えていた反復値を保存すると考えることです。", | ||
| "既定引数が定義時に評価される理由を理解せず、偶然の修正として追加することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「例外」を適用してから `example:` 出力へつながりますか。", | ||
| "「例外」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "例のラムダで自由変数`index`が検索されるのはいつですか。", | ||
| "`lambda index=index: index`にすると3つの結果はどう変わりますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「例外」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "ファクトリー呼び出しで作られた`delta`束縛を、呼び出し時に読む加算クロージャを返してください。", | ||
| "objective": "正、負、`0`の異なる`delta`をファクトリー終了後も保持する呼び出し可能な関数を作ります。", | ||
| "language_delta": "`lambda`は式だけで書く関数構文であり、ネストした`def`と同じレキシカルな名前解決とクロージャセルを使います。", | ||
| "prediction_prompt": "`[lambda n=n: n for n in range(3)]`で作った各関数を呼ぶと、どのリストになりますか。", | ||
| "transfer_trap": "JavaScriptのブロックスコープ付きループは反復ごとに束縛を作ることが多く、その捕捉動作はPythonの既定と異なります。" | ||
| }, | ||
| "py-files-context": { | ||
| "title": "ファイルとコンテキストマネージャ", | ||
| "concept": "`with` は管理されたスコープに入り、抜けるときに cleanup を実行する。ファイルや contextlib の道具は、途中で抜けても閉じる処理を保証する。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「ファイルとコンテキストマネージャ」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-decorators": { | ||
| "title": "定義時に適用されるデコレータ", | ||
| "concept": "`@bracket`は関数本体の作成後に評価され、関数名を返されたラッパーへ再束縛します。`functools.wraps`はメタデータを保ち、ラッパーはその後の各呼び出しを変換します。", | ||
| "worked_example": "`tag`は`label`を1つのラッパーで包みます。`label(\"Mina\")`を呼ぶと元の返り値を大文字にし、実行時に山括弧で囲みます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「ファイルとコンテキストマネージャ」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「ファイルとコンテキストマネージャ」演習の TODO を解かないこと。", | ||
| "`with` は管理されたスコープに入り、抜けるときに cleanup を実行する。ファイルや contextlib の道具は、途中で抜けても閉じる処理を保証する。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "呼び出し可能なラッパーを返さず、デコレータ定義中に元の関数を実行することです。", | ||
| "`functools.wraps`を省くと、元の関数名やヘルプ表示を参照するツールが誤ったメタデータを受け取ります。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「ファイルとコンテキストマネージャ」を適用してから `example:` 出力へつながりますか。", | ||
| "「ファイルとコンテキストマネージャ」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "定義時に一度だけ起きる処理と、呼び出すたびに起きる処理はそれぞれ何ですか。", | ||
| "ラッパーが装飾対象へ渡される`value`を受け取る必要があるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「ファイルとコンテキストマネージャ」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "ラッパーで元の返り値を大文字にして角括弧を加え、関数メタデータも保持してください。", | ||
| "objective": "装飾対象の本体を変えず、ASCIIとUnicodeの返り値を引数付きラッパーで変換します。", | ||
| "language_delta": "Pythonのデコレータは`def`構文に結び付いた通常の呼び出し可能オブジェクトの適用です。`wraps`は属性を写しますが、ラッパーの実行意味は変えません。", | ||
| "prediction_prompt": "デコレータが`decorate`、ラッパーが`call`を出力する場合、各語は定義時と呼び出し時のどちらに現れますか。", | ||
| "transfer_trap": "Javaのアノテーションはメタデータであり、自動的に関数をラッパーへ置き換えません。同じ`@`記号から同じ実行動作を推測しないでください。" | ||
| }, | ||
| "py-modules-imports": { | ||
| "title": "モジュールと import", | ||
| "concept": "モジュールは再利用コードを名前空間にまとめる。`import math` は標準ライブラリを使えるようにし、PEP 8 は依存関係を見やすくするため import を上部に置く。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「モジュールと import」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-async": { | ||
| "title": "`asyncio`でコルーチンを実行する", | ||
| "concept": "`async def`の呼び出しは、完了まで実行せずコルーチンオブジェクトを作ります。`asyncio.run`がイベントループを用意し、`gather`が待機可能値をスケジュールし、`await asyncio.sleep(0)`で制御を譲ります。", | ||
| "worked_example": "2つの`upper`コルーチンは実際の中断点へ到達します。`gather`は両方を待ち、完了順ではなく引数順で結果を返すため、出力は`GO NOW`です。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「モジュールと import」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「モジュールと import」演習の TODO を解かないこと。", | ||
| "モジュールは再利用コードを名前空間にまとめる。`import math` は標準ライブラリを使えるようにし、PEP 8 は依存関係を見やすくするため import を上部に置く。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "コルーチンを呼んだだけで、返されたオブジェクトを計算済みの値として扱うことです。", | ||
| "非同期コード内で`time.sleep`を使い、制御を譲らずイベントループのスレッドを止めることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「モジュールと import」を適用してから `example:` 出力へつながりますか。", | ||
| "「モジュールと import」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`main()`がコルーチンを作った後、実際にそれを進める要素は何ですか。", | ||
| "`asyncio.gather`の返り値は完了順と引数順のどちらに並びますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「モジュールと import」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "各整数について`double`コルーチンを作り、まとめてスケジュールして`await`し、順序を保った結果を出力してください。", | ||
| "objective": "複数、符号付き、空の入力で実際に中断するコルーチンを実行し、未`await`警告を残しません。", | ||
| "language_delta": "`asyncio`は協調的に動き、通常の文が並列になるのではなく`await`で実行を切り替えます。コルーチンを呼ぶだけではスケジュールも実行もされません。", | ||
| "prediction_prompt": "`double`が`sleep(0)`を`await`するとき、`await asyncio.gather(double(3), double(-1))`のリストを予測してください。", | ||
| "transfer_trap": "JavaScriptの`Promise`を返す関数は開始時期の規則が異なります。その実行開始の直感をPythonのコルーチン呼び出しへ持ち込まないでください。" | ||
| }, | ||
| "py-dataclasses": { | ||
| "title": "dataclass", | ||
| "concept": "`@dataclass` はデータ保持が中心のクラスに、初期化や表示などの定型メソッドを生成する。フィールド名と型ヒントが定義にまとまる。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「dataclass」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-lists-dicts": { | ||
| "title": "辞書のキーごとにリストをまとめる", | ||
| "concept": "各辞書キーは独立した点数リストを指します。`setdefault`は必要なときだけバケットを作り、`get(query, [])`は未知の名前を空シーケンスとして扱うため合計が`0`になります。", | ||
| "worked_example": "`Mina`には`[8, 1]`が対応し、そのリストの合計は`9`です。`Kai`は別のリストを持つので、キーごとにバケットが独立していることも分かります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「dataclass」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「dataclass」演習の TODO を解かないこと。", | ||
| "`@dataclass` はデータ保持が中心のクラスに、初期化や表示などの定型メソッドを生成する。フィールド名と型ヒントが定義にまとまる。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "問い合わせ名が入力にない可能性を無視して`scores[query]`を参照することです。", | ||
| "全キーで同じリストを再利用し、1人の点数を別の名前にも混ぜることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「dataclass」を適用してから `example:` 出力へつながりますか。", | ||
| "「dataclass」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "どのトークンが問い合わせで、名前と点数の組はどの位置から始まりますか。", | ||
| "`sum(scores.get(missing, []))`が例外ではなく`0`になるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「dataclass」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "残りのトークンを名前と点数の組としてたどり、名前ごとのリストへ追加して、問い合わせ対象の合計を求めてください。", | ||
| "objective": "名前の重複、負の点数、存在しない問い合わせについて、グループ合計を正しく返します。", | ||
| "language_delta": "`Counter`や`defaultdict`は特化した集計を提供しますが、このキー別累積には通常の`dict`と`list`が最も直接的です。", | ||
| "prediction_prompt": "問い合わせが`Bo`で、データに`Ada`と`Lin`しかない場合、`scores.get(query, [])`とその合計を予測してください。", | ||
| "transfer_trap": "JavaScriptの通常オブジェクトにはプロトタイプ由来のキーがありますが、Pythonの`dict.get`は不在キーを挿入せず、ハッシュ可能な値をキーにできます。" | ||
| }, | ||
| "py-typing": { | ||
| "title": "型ヒント", | ||
| "concept": "型ヒントは読者、エディタ、型チェッカへ期待する形を伝える。実行時の値は動的なので、関数本体も注釈の契約に合っていなければならない。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「型ヒント」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-sorting-keys": { | ||
| "title": "複数項目のソートキー", | ||
| "concept": "Pythonはタプルキーを左から順に比較します。数値点を負にすると高得点が先になり、同点の場合だけ元の名前が昇順の第2条件になります。", | ||
| "worked_example": "`Bo`は`9`点なので、`7`点の`Mina`と`Ana`より先です。後者2人だけを比べるなら、名前の第2条件で`Ana`が先になります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「型ヒント」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「型ヒント」演習の TODO を解かないこと。", | ||
| "型ヒントは読者、エディタ、型チェッカへ期待する形を伝える。実行時の値は動的なので、関数本体も注釈の契約に合っていなければならない。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "点数と名前のタプルへ`reverse=True`を指定し、名前の同点条件まで降順にすることです。", | ||
| "要件に第2条件があるのに、安定ソートによる入力順へ依存することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「型ヒント」を適用してから `example:` 出力へつながりますか。", | ||
| "「型ヒント」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`Zed:5`と`Amy:5`にはどのキーのタプルが作られますか。", | ||
| "名前だけで並べると、後のレコードが高得点の場合に失敗するのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「型ヒント」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "各名前と点数を解析し、点数降順・名前昇順の順序で先頭になるレコードを選んでください。", | ||
| "objective": "異なる点数、同点、負の点数1件の各ケースで正しい勝者を選びます。", | ||
| "language_delta": "`sorted`は新しいリストを返し、安定性も保証します。`key`関数は2項比較ではなく、各要素の比較用データを1回作ります。", | ||
| "prediction_prompt": "キーが`(-score, name)`なら、`(\"Zed\", 5)`と`(\"Amy\", 5)`のどちらが先ですか。", | ||
| "transfer_trap": "他言語の減算式比較器はオーバーフローの恐れがあり、名前の同点条件も表しません。ここではタプルキーで両方を避けてください。" | ||
| }, | ||
| "py-generators": { | ||
| "title": "イテレータとジェネレータ", | ||
| "concept": "イテレータは値を 1 つずつ生成する。ジェネレータ関数は `yield` を使い、呼び出し時には生成器オブジェクトを返し、次の値を求められた時に本体を進める。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「イテレータとジェネレータ」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-counter-defaultdict": { | ||
| "title": "`Counter`で数え`defaultdict`でまとめる", | ||
| "concept": "`Counter`は不在要素の頻度も`0`で返します。`defaultdict(list)`は添字アクセス時に独立したバケットを作り、`get`なら不在の頭文字を挿入せず確認できます。", | ||
| "worked_example": "5語のうち`owl`は2回現れます。全語の頭文字は`o`なので、そのグループの長さは`5`となり、例は異なる2つの統計量を出力します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「イテレータとジェネレータ」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「イテレータとジェネレータ」演習の TODO を解かないこと。", | ||
| "イテレータは値を 1 つずつ生成する。ジェネレータ関数は `yield` を使い、呼び出し時には生成器オブジェクトを返し、次の値を求められた時に本体を進める。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "先頭の問い合わせトークンまでデータ語として数え、頻度を増やすことです。", | ||
| "確認だけのために`groups[missing]`を読み、`defaultdict`へ副作用でキーを追加することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「イテレータとジェネレータ」を適用してから `example:` 出力へつながりますか。", | ||
| "「イテレータとジェネレータ」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "事前の存在確認なしで`Counter(words)[\"absent\"]`が`0`になるのはなぜですか。", | ||
| "`defaultdict`のバケットを作るのは添字アクセスと`get`のどちらですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「イテレータとジェネレータ」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "データ語の頻度を数え、先頭文字ごとにまとめ、問い合わせ語の頻度と同じ頭文字の語数を報告してください。", | ||
| "objective": "存在する語、重複する語、完全に不在の語で2つの統計を正しく出力します。", | ||
| "language_delta": "これらは`dict`を置き換えるのではなく、不在時の動作を特化します。`Counter`は数値`0`、`defaultdict`はファクトリーが作る値を使います。", | ||
| "prediction_prompt": "問い合わせが`x`、データが`a b`のとき、`groups.get(\"x\", [])`が`x`キーを挿入するか予測してください。", | ||
| "transfer_trap": "通常の`dict`は不在キーへの添字アクセスで`KeyError`を送出します。`Counter`の`0`や`defaultdict`の自動生成を、全ての辞書へ一般化しないでください。" | ||
| }, | ||
| "py-lambdas-closures": { | ||
| "title": "ラムダとクロージャ", | ||
| "concept": "`lambda` は 1 つの式から小さな関数を作る。その関数が外側の名前を使うとクロージャになり、外側の呼び出しが終わっても値を覚えている。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「ラムダとクロージャ」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-deque": { | ||
| "title": "両端を定数時間で操作する", | ||
| "concept": "`deque`は左右どちらの端でも追加と削除を効率よく行うシーケンスです。`appendleft`と`popleft`は左端、`append`と`pop`は右端を操作します。", | ||
| "worked_example": "`center`から始め、`first`を左、`last`を右へ置きます。両端から1つずつ取り出すと`first last`となり、中央要素は残ります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「ラムダとクロージャ」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「ラムダとクロージャ」演習の TODO を解かないこと。", | ||
| "`lambda` は 1 つの式から小さな関数を作る。その関数が外側の名前を使うとクロージャになり、外側の呼び出しが終わっても値を覚えている。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "両方の端点を`append`し、元の中央要素を左端に残すことです。", | ||
| "大きくなるキューで要素移動が必要な`list.pop(0)`を同等の操作として使うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「ラムダとクロージャ」を適用してから `example:` 出力へつながりますか。", | ||
| "「ラムダとクロージャ」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`appendleft(left)`の後、`popleft`はどの値を返しますか。", | ||
| "`deque`の右端を削除する操作は何ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「ラムダとクロージャ」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "2番目のトークンを左端、3番目を右端へ置き、その両端を取り出して出力してください。", | ||
| "objective": "中央要素を保持したまま、ASCIIとUnicodeの端点を正しい順で報告します。", | ||
| "language_delta": "探索境界を両端から処理する制御フローには`deque`が適します。ランダムな添字アクセスは`list`の強みであり、`deque`の目的ではありません。", | ||
| "prediction_prompt": "`deque([\"m\"])`へ`appendleft(\"l\")`、`append(\"r\")`、`popleft()`、`pop()`を順に適用してください。", | ||
| "transfer_trap": "他の標準ライブラリでは`offer`や`poll`という名前も使われます。名前を直訳せず、どちらの端を操作するかで対応付けてください。" | ||
| }, | ||
| "py-decorators": { | ||
| "title": "デコレータ", | ||
| "concept": "デコレータは関数定義時に実行される。元の関数オブジェクトを受け取り、その名前に最終的に束縛するオブジェクトを返す。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「デコレータ」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-comprehensions": { | ||
| "title": "チェックポイント:選別、変換、合計", | ||
| "concept": "内包表記は各元要素へ先に条件を評価し、通過した値だけに出力式を適用します。角括弧で作る結果は`sum`が消費する前に実体化されたリストです。", | ||
| "worked_example": "`5`、`6`、`7`、`8`のうち偶数は`6`と`8`です。平方した`36`と`64`のリストを作り、合計は`100`になります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「デコレータ」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「デコレータ」演習の TODO を解かないこと。", | ||
| "デコレータは関数定義時に実行される。元の関数オブジェクトを受け取り、その名前に最終的に束縛するオブジェクトを返す。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "偶奇の条件を反転し、奇数の平方を合計することです。", | ||
| "副作用や多段の条件を詰め込み、評価順が読めない内包表記にすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「デコレータ」を適用してから `example:` 出力へつながりますか。", | ||
| "「デコレータ」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "末尾の`if`で除外された要素にも出力式は実行されますか。", | ||
| "`[-2, -1, 0]`へ偶数平方の規則を適用すると、どのリストになりますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「デコレータ」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "読みやすい1つのリスト内包表記で偶数の平方を作り、その合計を出力してください。", | ||
| "objective": "符号が混ざる入力と選択結果が空の入力で、選別後に変換する順序を示します。", | ||
| "language_delta": "ジェネレーター式なら平方を遅延生成でき、`itertools`も中間リストなしで流れを構成できます。このチェックポイントは実体化を観察するため意図的にリストを作ります。", | ||
| "prediction_prompt": "`[1, 2, 3, 4]`から`sum`の前に作られる中間リストを正確に書いてください。", | ||
| "transfer_trap": "JavaScriptの配列コールバックやJavaのストリームは似た段階を別の評価時期で処理します。Pythonの角括弧内包表記を遅延評価だと思わないでください。" | ||
| }, | ||
| "py-sorting-keys": { | ||
| "title": "ソートと key 関数", | ||
| "concept": "`sorted` は新しいソート済みリストを返す。`key` 関数は各要素から比較に使う値を取り出し、タプルやオブジェクトを特定フィールドで並べられる。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「ソートと key 関数」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-generators": { | ||
| "title": "遅延実行するカウントダウンジェネレーター", | ||
| "concept": "`yield`を含む関数は、呼び出すとジェネレーターイテレーターを返します。本体は最初に進めた時に始まり、各`yield`で局所状態を保存して止まり、最後は`StopIteration`になります。", | ||
| "worked_example": "`reverse_letters(\"abc\")`を呼んだ時点では走査しません。`print`が展開する際に3回進み、`c`、`b`、`a`を空白区切りで出力します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「ソートと key 関数」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「ソートと key 関数」演習の TODO を解かないこと。", | ||
| "`sorted` は新しいソート済みリストを返す。`key` 関数は各要素から比較に使う値を取り出し、タプルやオブジェクトを特定フィールドで並べられる。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "ジェネレーター関数を呼んだだけで、最初の`yield`より前の文も実行されたと思うことです。", | ||
| "一度使い切った同じジェネレーターを再反復し、以前の値が再び出ると期待することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「ソートと key 関数」を適用してから `example:` 出力へつながりますか。", | ||
| "「ソートと key 関数」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`countdown`の`while`本体が実行を始める操作は何ですか。", | ||
| "`print(*countdown(0))`でも改行が1つ出るのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「ソートと key 関数」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "正の現在値を`yield`し、保存された局所値を減らし、`0`へ到達するまで繰り返してください。", | ||
| "objective": "中間リストを作らず、複数値、1値、空のカウントダウン列を完全に生成します。", | ||
| "language_delta": "ジェネレーター関数はイテレーターのファクトリーであり、呼び出すたびに新しい中断状態を作ります。通常の`return`は次の要素を出さず反復を終えます。", | ||
| "prediction_prompt": "`countdown(3)`へ最初の`next`を呼ぶと何を`yield`し、再開時の局所値はいくつになりますか。", | ||
| "transfer_trap": "JavaScriptのジェネレーターは似た構文でも、イテレーター結果オブジェクトを返します。Pythonの`next`は値を直接返すか`StopIteration`を送出します。" | ||
| }, | ||
| "py-counter-defaultdict": { | ||
| "title": "Counter と defaultdict", | ||
| "concept": "`Counter` は hash 可能な値を数え、`defaultdict(list)` は新しいグループに空リストを自動で作る。数え上げとグループ化の初期化コードを減らせる。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「Counter と defaultdict」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-itertools": { | ||
| "title": "イテラブルを遅延平坦化する", | ||
| "concept": "`chain.from_iterable`は外側のイテラブルを1つ受け取り、各内側イテラブルを順番に進めます。中間の平坦化済みリストを作らず、現在位置のイテレーター状態だけを保持します。", | ||
| "worked_example": "外側のタプルには`py`と`thon`があります。最初の文字列、次の文字列の順に文字を流し、`join`が消費して`python`を作ります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「Counter と defaultdict」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「Counter と defaultdict」演習の TODO を解かないこと。", | ||
| "`Counter` は hash 可能な値を数え、`defaultdict(list)` は新しいグループに空リストを自動で作る。数え上げとグループ化の初期化コードを減らせる。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`chain(groups)`を使い、各文字ではなく内側の文字列全体を要素として受け取ることです。", | ||
| "確認のため途中まで消費し、その後の`join`が先頭から再開すると考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「Counter と defaultdict」を適用してから `example:` 出力へつながりますか。", | ||
| "「Counter と defaultdict」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "この演習で`chain.from_iterable`へ渡す外側の引数はいくつですか。", | ||
| "平坦化したイテレーターから最初の1文字を`next`で取ると、何が残りますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「Counter と defaultdict」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "解析した全ての文字グループを`chain.from_iterable`へ渡し、1回だけ消費できる流れを`join`してください。", | ||
| "objective": "長さの異なるグループを、入れ子リストを作らず正しい文字列へ平坦化します。", | ||
| "language_delta": "`itertools`の操作はイテレーターを返し、消費処理が進めるまで処理を遅らせます。ここでは`join`が最終文字列を実体化する終端消費処理です。", | ||
| "prediction_prompt": "最終的な`join`を考える前に、`list(chain.from_iterable([\"ab\", \"c\"]))`を予測してください。", | ||
| "transfer_trap": "他言語のストリームが再構築やキャッシュを行う場合でも、Pythonのイテレーターは一般に消費位置を持ち、使い切ると戻りません。" | ||
| }, | ||
| "py-deque": { | ||
| "title": "deque", | ||
| "concept": "`deque` は両端キューで、左右どちらの append/pop も効率がよい。BFS や前から取り除くスライディングウィンドウでよく使う。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「deque」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-errors": { | ||
| "title": "`ValueError`だけから回復する", | ||
| "concept": "`int`は整数を表さない文字列に`ValueError`を送出します。その変換だけを小さな`try`へ置き、正確な例外型だけを捕捉すると、無関係な不具合を隠さず代替値方針を示せます。", | ||
| "worked_example": "`oops`の変換で`ValueError`が発生するため、ハンドラーが与えられた代替値`-8`を変換します。有効な代替値がそのまま出力値になります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「deque」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「deque」演習の TODO を解かないこと。", | ||
| "`deque` は両端キューで、左右どちらの append/pop も効率がよい。BFS や前から取り除くスライディングウィンドウでよく使う。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "プログラム全体を`except Exception`で囲み、別のバグまで正常な代替値出力へ変えることです。", | ||
| "`-3`のような符号付き文字列も不正だと思い、`int`が通常解析できる値を除外することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「deque」を適用してから `example:` 出力へつながりますか。", | ||
| "「deque」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "最初のトークンが有効なら`except`ブロックは実行されますか。", | ||
| "この`try`内で、回復対象の`ValueError`を送出し得る文はどれですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「deque」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "最初のトークンの変換を試し、その変換が`ValueError`になった場合だけ2番目を使ってください。", | ||
| "objective": "有効な負数と不正な文字列を区別し、必要な場合にだけ指定代替値を適用します。", | ||
| "language_delta": "`try`は回復方法を選び、`with`はリソースの寿命を管理します。例外時にも後始末するコンテキストマネージャーとは役割が異なります。", | ||
| "prediction_prompt": "代替値が`9`のとき、候補`-3`と候補`bad`の2経路を追跡してください。", | ||
| "transfer_trap": "JavaScriptの`parseInt(\"bad\")`は例外ではなく`NaN`を返すため、同じ`try`/`catch`方針では代替値へ入りません。" | ||
| }, | ||
| "py-itertools": { | ||
| "title": "itertools", | ||
| "concept": "`itertools` は `chain`、`islice`、`product`、`pairwise` などの lazy な反復部品を提供する。必要になるまで中間リストを作らない。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「itertools」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-files-context": { | ||
| "title": "コンテキストマネージャーの確実な終了処理", | ||
| "concept": "`with`プロトコルは`__enter__`で管理対象を得て、正常終了でも例外終了でもブロックを離れる際に`__exit__`を呼びます。`StringIO`なら実ファイルに依存せず同じ読み取りとクローズの形を試せます。", | ||
| "worked_example": "メモリー上のハンドルには空白で囲まれた`Seoul`があります。管理範囲内で読み、空白を除き、終了後に大文字化すると、ハンドルは閉じたまま`SEOUL`を得られます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「itertools」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「itertools」演習の TODO を解かないこと。", | ||
| "`itertools` は `chain`、`islice`、`product`、`pairwise` などの lazy な反復部品を提供する。必要になるまで中間リストを作らない。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`with`を出た後に、閉じた`StringIO`ハンドルから再度読もうとすることです。", | ||
| "`__exit__`は成功時だけ動くと思い、例外分岐ごとに後始末を重複して書くことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「itertools」を適用してから `example:` 出力へつながりますか。", | ||
| "「itertools」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "インデントが管理ブロックを離れる前に実行すべき操作は何ですか。", | ||
| "リソースの内容が空白だけなら、正規化後にどの値が残りますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「itertools」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "コンテキスト管理されたハンドルから読み、内容を正規化し、文字が残らない場合だけ`EMPTY`を出力してください。", | ||
| "objective": "リソースの確実な終了を保ちながら、空白付き、通常、空のメモリー入力を処理します。", | ||
| "language_delta": "`with`はファイル専用構文ではなく、対象オブジェクトのエントリー/終了プロトコルへ処理を委ねます。読み取った文字列はリソースを閉じた後も使えます。", | ||
| "prediction_prompt": "`handle.read()`が`\" \"`を返すとき、`strip`後の`text`と`text.upper() or \"EMPTY\"`を予測してください。", | ||
| "transfer_trap": "Javaの`try-with-resources`などにも類似機能がありますが、Pythonは`__enter__`と`__exit__`を呼ぶ固有のプロトコルです。" | ||
| }, | ||
| "py-pathlib": { | ||
| "title": "pathlib", | ||
| "concept": "`pathlib` はパスを文字列ではなくオブジェクトとして扱う。`name`、`stem`、`suffix`、`parent` を使うと区切り文字の手作業が減る。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「pathlib」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-dataclasses": { | ||
| "title": "クラスと自動生成されるデータモデル", | ||
| "concept": "クラスはメソッドでインスタンスの振る舞いを提供し、`@dataclass`は宣言フィールドから`__init__`、`__repr__`、等値比較を生成します。入れ子の値を不変にせず、型注釈も実行時検証器にはなりません。", | ||
| "worked_example": "`Point(4, 5)`は2フィールドを持ち、`total`メソッドは`9`を返します。同じフィールド値を持つ別の`Point`との比較は、自動生成された等値処理により`True`です。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「pathlib」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「pathlib」演習の TODO を解かないこと。", | ||
| "`pathlib` はパスを文字列ではなくオブジェクトとして扱う。`name`、`stem`、`suffix`、`parent` を使うと区切り文字の手作業が減る。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "自動生成された等値比較や`frozen`指定が、フィールド内の可変オブジェクトまで再帰的に凍結すると考えることです。", | ||
| "`values: list[int] = []`は可変なデータクラス既定値として拒否されます。インスタンスごとに`field(default_factory=list)`を使うか、意図的に共有するなら`ClassVar`を使います。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「pathlib」を適用してから `example:` 出力へつながりますか。", | ||
| "「pathlib」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "2つの`Point`インスタンスを比較すると、どの等値実装が使われますか。", | ||
| "`x: int`と宣言すれば`Point(\"wrong\", 5)`を実行時に拒否しますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「pathlib」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "2つのインスタンスフィールドを使う`Point.total`を実装し、メソッド結果とデータクラスの等値比較を観察してください。", | ||
| "objective": "符号付き座標の合計をクラスの明示的な振る舞いで計算し、同値の`Point`を構築します。", | ||
| "language_delta": "型注釈は別の型付け演習で検査できますが、通常のPython実行は強制しません。データクラスは定型処理を減らしてもドメインメソッドや検証を置き換えません。", | ||
| "prediction_prompt": "`Point(2, 3) == Point(2, 3)`を予測し、一方のフィールドだけ変えた場合も考えてください。", | ||
| "transfer_trap": "Javaのレコードコンポーネントは浅く不変ですが、通常のPythonデータクラスは設定しない限り可変です。" | ||
| }, | ||
| "py-testing-assert": { | ||
| "title": "テストと assert", | ||
| "concept": "`assert` は小さな例やテストで条件を確認する文である。テストフレームワークはこの確認を繰り返せる形に整理するが、期待値と実際値を比べる考え方は同じである。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「テストと assert」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-typing": { | ||
| "title": "実行時強制をしない型注釈", | ||
| "concept": "関数注釈は読者、エディター、任意の静的型チェッカーが使うメタデータです。`get_type_hints`は記録された型オブジェクトを実行時に解決しますが、引数変換も不一致呼び出しの阻止も行いません。", | ||
| "worked_example": "`head`は`Iterable[str]`と`str`を注釈に記録します。イントロスペクションは返り値型が`str`だと示し、関数本体は独立にリストから`elm`を返します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「テストと assert」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「テストと assert」演習の TODO を解かないこと。", | ||
| "`assert` は小さな例やテストで条件を確認する文である。テストフレームワークはこの確認を繰り返せる形に整理するが、期待値と実際値を比べる考え方は同じである。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`int`注釈が、関数本体の実行前に文字列引数を自動拒否すると考えることです。", | ||
| "期待型のイントロスペクションと、全`return`経路が実際に型を守る検証を混同することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「テストと assert」を適用してから `example:` 出力へつながりますか。", | ||
| "「テストと assert」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`get_type_hints(total)[\"return\"]`はどのオブジェクトを公開しますか。", | ||
| "`total`で加算を行うのは注釈と`return`式のどちらですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「テストと assert」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "`Iterable[int]`から`int`を返す契約を保ち、合計を実装して、解決済みヒントと結果を表示してください。", | ||
| "objective": "複数、1つ、空のイテラブルを合計しながら、注釈メタデータを確認します。", | ||
| "language_delta": "Pythonは型チェッカーを動かさず注釈付きコードを実行できます。TypeScriptも型を実行時に消去しますが、通常のツールチェーンでは実行前に型チェッカーを通します。", | ||
| "prediction_prompt": "`total([\"2\"])`を呼ぶと注釈が拒否しますか。また最終結果を決める実際の操作は何ですか。", | ||
| "transfer_trap": "注釈は期待値を説明するだけで、入力文字列の解析、型変換、実行時のオーバーロード解決は行いません。" | ||
| }, | ||
| "py-async": { | ||
| "title": "非同期の概念", | ||
| "concept": "`async def` はコルーチン関数を作る。呼び出すとコルーチンオブジェクトになり、別のコルーチン内で `await` して結果を受け取り、境界で `asyncio.run` が実行する。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「非同期の概念」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "py-testing-assert": { | ||
| "title": "総合演習:単語順位を決定的にする", | ||
| "concept": "最終プログラムはイテラブルを実体化し、単語を数え、開発時の不変条件を確認し、頻度降順と単語昇順で並べます。空入力には例外へ頼らない明示的な結果があります。", | ||
| "worked_example": "`pear`と`plum`はどちらも2回なので頻度だけでは決まりません。第2条件の辞書順で`pear`を選び、不変条件の成立後に`pear:2`を出力します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「非同期の概念」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「非同期の概念」演習の TODO を解かないこと。", | ||
| "`async def` はコルーチン関数を作る。呼び出すとコルーチンオブジェクトになり、別のコルーチン内で `await` して結果を受け取り、境界で `asyncio.run` が実行する。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`Counter.most_common(1)`だけを使い、同頻度の結果を出現順へ依存させることです。", | ||
| "最適化実行ではアサーションバイトコードが除かれるのに、入力検証やセキュリティ条件を`assert`へ任せることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「非同期の概念」を適用してから `example:` 出力へつながりますか。", | ||
| "「非同期の概念」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "ソートキーを構成する2フィールドは何で、頻度を負にするのはなぜですか。", | ||
| "インタープリターを`-O`で起動すると`assert`文はどうなりますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「非同期の概念」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "型付き関数、`Counter`、内部不変条件を保ちつつ、出現順による同点処理を頻度と単語による決定的な順位へ置き換えてください。", | ||
| "objective": "最頻単語を同頻度なら辞書順で選び、単語がない場合は定義済みの空結果を返します。", | ||
| "language_delta": "この総合演習は正確な出力、解析、再束縛、コレクション、関数、即時に評価されるソート、型の考え方を統合します。`assert`は`-O`で消えるため、必須の挙動には使えません。", | ||
| "prediction_prompt": "入力が`b a b a`なら`Counter`の各エントリーを列挙し、`(-count, word)`で勝つ単語を予測してください。", | ||
| "transfer_trap": "Javaのアサーションも無効化され得ますが、フラグと実行モデルは異なります。どちらでも必ず実行すべき検証には通常の制御フローを使ってください。" | ||
| } | ||
| } | ||
| } |
+324
-249
@@ -7,377 +7,452 @@ { | ||
| "py-output": { | ||
| "title": "print와 표준 출력", | ||
| "concept": "`print`는 계산 결과가 채점기에서 보이는 표준 출력으로 나가는 경계다. 기본으로 인자 사이에 공백을 넣고 줄바꿈을 붙이므로, 문제 풀이에서는 불필요한 한 줄도 오답이 된다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 print와 표준 출력 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "`print`로 출력 형식 맞추기", | ||
| "concept": "`print`는 인자를 문자열로 바꾸고 `sep`를 사이에 넣은 뒤 `end`를 덧붙인다. 기본 공백과 줄바꿈도 채점기가 비교하는 표준 출력의 일부다.", | ||
| "worked_example": "예제는 `Mina`와 `9` 사이에 등호를 넣고, 기본 줄바꿈 대신 느낌표와 줄바꿈으로 끝낸다. 두 구두점은 문자열 연결이 아니라 `print` 인자로 정해진다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 print와 표준 출력 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 print와 표준 출력 실습의 TODO를 풀지 않는다.", | ||
| "`print`는 계산 결과가 채점기에서 보이는 표준 출력으로 나가는 경계다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "콜론이 값에 붙어야 하는데 기본 구분 공백을 그대로 둔다.", | ||
| "디버그 문구를 표준 출력에 보내 정답 외 문자가 함께 비교되게 한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 print와 표준 출력 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "print와 표준 출력 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`print(\"A\", \"B\", sep=\":\", end=\"!\")`가 쓰는 문자를 순서대로 말할 수 있는가?", | ||
| "마지막 인자 뒤에 붙는 텍스트를 정하는 키워드는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 print와 표준 출력 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "이름과 점수를 읽은 뒤 `print`의 구분자와 끝 문자를 설정해 `이름:점수` 한 줄만 출력하라.", | ||
| "objective": "주어진 세 입력에서 디버그 접두어나 남는 공백 없이 요구한 표준 출력을 바이트 단위로 맞춘다.", | ||
| "language_delta": "원시 스트림 쓰기와 달리 Python의 `print`는 구분자와 줄 끝을 모두 제공하므로 두 기본값까지 출력 계약으로 다뤄야 한다.", | ||
| "prediction_prompt": "실행 전에 `print(\"x\", 0, sep=\"=\", end=\"?\")`가 만드는 정확한 문자열을 적어 보라.", | ||
| "transfer_trap": "다른 언어의 `println`이나 콘솔 함수에도 Python의 `sep`가 있다고 가정하지 말고 공백과 줄바꿈 규칙을 따로 확인하라." | ||
| }, | ||
| "py-variables": { | ||
| "title": "변수", | ||
| "concept": "변수 이름은 객체에 붙은 이름표다. `=`는 같은지 비교하는 기호가 아니라 이름을 값에 묶는 동작이며, 다시 대입하면 이후 그 이름을 읽을 때 새 값을 보게 된다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 변수 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-input": { | ||
| "title": "표준 입력을 한 번에 읽기", | ||
| "concept": "`sys.stdin.read()`는 표준 입력 전체를 하나의 문자열로 모은다. 인자 없는 `split()`은 여러 종류의 공백을 경계로 삼으며, 빈 토큰 목록의 합은 0이다.", | ||
| "worked_example": "예제의 두 줄에는 `10`과 `4`가 하나씩 있다. 줄 위치와 무관하게 두 토큰을 정수로 바꾸어 합하면 `14`가 되며 대화형 입력 요청은 필요 없다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 변수 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 변수 실습의 TODO를 풀지 않는다.", | ||
| "변수 이름은 객체에 붙은 이름표다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`split(\" \")`을 써서 줄바꿈이나 연속 공백이 토큰 안에 남게 한다.", | ||
| "토큰마다 `input()`을 호출해 같은 데이터가 여러 줄로 감싸졌을 때 파싱을 깨뜨린다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 변수 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "변수 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "빈 문자열에 인자 없는 `split()`을 호출하면 어떤 목록이 생기는가?", | ||
| "표준 입력을 한 번만 읽어도 한 줄 입력과 여러 줄 입력을 함께 처리할 수 있는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 변수 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "이미 읽은 표준 입력을 공백 기준 토큰으로 나누고 각 토큰을 정수로 변환해 모두 더하라. 빈 입력도 0을 출력해야 한다.", | ||
| "objective": "공백과 줄바꿈이 섞인 입력을 별도 분기 없이 처리하고 정수 합계를 계산한다.", | ||
| "language_delta": "Python은 여기서 표준 입력을 텍스트 스트림으로 제공하지만 `split()` 결과는 문자열이므로 숫자 변환은 명시해야 한다.", | ||
| "prediction_prompt": "`sum(int(token) for token in \" -2\\n5 \".split())`의 값을 실행 전에 예측하라.", | ||
| "transfer_trap": "일부 언어의 스캐너는 공백을 건너뛰며 숫자를 바로 읽지만 Python의 원시 입력 문자열은 `int`를 호출해야 정수가 된다." | ||
| }, | ||
| "py-numbers": { | ||
| "title": "숫자", | ||
| "concept": "Python의 정수는 큰 값도 다룰 수 있고, `/`, `//`, `%`, `**`는 서로 다른 숫자 의미를 가진다. 몫과 나머지가 필요한 문제에서 실수 나눗셈을 쓰면 출력 형식부터 어긋난다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 숫자 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-variables": { | ||
| "title": "이름을 새 값에 다시 묶기", | ||
| "concept": "Python 변수는 타입이 고정된 저장 상자가 아니라 객체에 붙은 이름이다. 정수 누적값을 갱신하면 원래 정수를 바꾸는 대신 새 정수를 계산해 이름을 다시 묶는다.", | ||
| "worked_example": "예제는 먼저 `total`을 `10`에 묶고 복합 대입으로 `15`에 다시 묶는다. 정수 객체 `10`은 불변이므로 그 객체 자체가 수정된 것은 아니다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 숫자 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 숫자 실습의 TODO를 풀지 않는다.", | ||
| "Python의 정수는 큰 값도 다룰 수 있고, `/`, `//`, `%`, `**`는 서로 다른 숫자 의미를 가진다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "정수 복합 대입이 기존 정수 객체 내부를 수정한다고 설명한다.", | ||
| "반복문에서 만든 람다가 각 반복 값을 자동으로 복사해 둔다고 생각한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 숫자 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "숫자 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`total = left` 다음 `total += right`가 실행되면 어느 이름의 바인딩이 달라지는가?", | ||
| "같은 반복 변수 셀을 닫아 둔 세 람다는 반복 종료 뒤 어떤 값을 읽는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 숫자 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "첫 번째 정수로 누적값을 시작하고 두 번째 정수를 더해 같은 이름을 다시 묶은 뒤 결과를 출력하라.", | ||
| "objective": "양수, 음수, 0이 포함된 사례에서 재바인딩을 사용해 산술 합계를 만든다.", | ||
| "language_delta": "Python 대입은 이름과 객체의 관계를 바꾸며 이름에 가변성 선언을 요구하지 않는다. 클로저는 생성 순간의 값이 아니라 바인딩 셀을 보존할 수 있다.", | ||
| "prediction_prompt": "`readers = [lambda: n for n in range(3)]` 뒤 `[reader() for reader in readers]`의 결과를 예상하라.", | ||
| "transfer_trap": "JavaScript의 일반적인 `let` 반복 바인딩은 반복마다 새로 생기므로 그 직관을 Python 클로저에 옮기면 지연 바인딩을 놓친다." | ||
| }, | ||
| "py-strings": { | ||
| "title": "문자열", | ||
| "concept": "문자열은 바꿀 수 없는 문자 시퀀스다. 인덱스는 한 문자를 읽고, 슬라이스는 끝 인덱스를 포함하지 않는 구간을 읽으며, 이어 붙이기는 새 문자열을 만든다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 문자열 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-numbers": { | ||
| "title": "내림 몫과 나머지 계산하기", | ||
| "concept": "정수의 `//`는 수학적 몫보다 크지 않은 가장 큰 정수를 고른다. 나머지는 `피제수 = 제수 × 몫 + 나머지`를 만족하며 0이 아니면 제수와 같은 부호다.", | ||
| "worked_example": "`-11`을 `4`로 나누면 내림 몫은 `-3`, 나머지는 `1`이다. `4 × -3 + 1`을 계산하면 원래 피제수 `-11`이 복원된다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 문자열 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 문자열 실습의 TODO를 풀지 않는다.", | ||
| "문자열은 바꿀 수 없는 문자 시퀀스다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`int(dividend / divisor)`로 바꾸어 음수 몫을 0 방향으로 잘라 버린다.", | ||
| "제수가 음수인 사례에서도 나머지는 항상 0 이상이라고 가정한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 문자열 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "문자열 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "피제수, 제수, 몫, 나머지를 연결하는 항등식은 무엇인가?", | ||
| "`-7 // 2`가 `int(-7 / 2)`보다 1 작은 이유를 설명할 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 문자열 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "제수가 0이 아닌 두 정수를 읽어 Python의 내림 몫과 그에 대응하는 나머지를 나란히 출력하라.", | ||
| "objective": "제수의 부호가 양수이거나 음수인 경우 모두 Python 나눗셈 항등식을 지킨다.", | ||
| "language_delta": "Python `//`는 부호 있는 몫을 내림한다. JavaScript와 TypeScript의 숫자 나눗셈은 소수를 유지하고 Java와 Rust의 정수 나눗셈은 0 방향으로 절삭한다.", | ||
| "prediction_prompt": "`(-7 // 2, -7 % 2)`와 `(7 // -2, 7 % -2)`의 두 값을 각각 계산하라.", | ||
| "transfer_trap": "다른 언어의 `/`를 기계적으로 옮기지 마라. `7 / 2`의 결과와 음수 몫 규칙이 언어마다 다르다." | ||
| }, | ||
| "py-control-flow": { | ||
| "title": "제어 흐름", | ||
| "concept": "`if`는 실행할 블록을 고르고, `for`는 iterable을 하나씩 소비하며, `while`은 조건이 바뀔 때까지 반복한다. Python에서는 들여쓰기가 문법이라 한 칸의 위치가 실행 범위를 바꾼다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 제어 흐름 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-tuples-sets": { | ||
| "title": "순서쌍과 고유 원소 구분하기", | ||
| "concept": "튜플은 위치와 중복을 보존하므로 입력 레코드에 알맞다. 세트는 해시 가능한 중복 원소를 제거하지만 반복 순서를 답의 형식으로 사용해서는 안 된다.", | ||
| "worked_example": "튜플 `(\"r\", \"s\", \"r\")`을 이어 붙이면 `rsr`이지만 세트로 바꾸면 서로 다른 글자는 두 개다. 같은 원본에서 순서와 고유성이라는 다른 성질을 관찰한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 제어 흐름 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 제어 흐름 실습의 TODO를 풀지 않는다.", | ||
| "`if`는 실행할 블록을 고르고, `for`는 iterable을 하나씩 소비하며, `while`은 조건이 바뀔 때까지 반복한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "빈 세트를 만들 때 `{}`를 써서 빈 딕셔너리를 생성한다.", | ||
| "튜플 대신 세트를 이어 붙여 중복과 안정적인 위치를 잃는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 제어 흐름 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "제어 흐름 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "튜플 `(\"a\", \"a\")`가 두 위치를 그대로 갖는 이유는 무엇인가?", | ||
| "모호하지 않게 빈 세트를 만드는 생성자는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 제어 흐름 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "입력값은 튜플 순서대로 이어 붙이고, 별도의 세트는 서로 다른 원소 수를 셀 때만 사용하라.", | ||
| "objective": "중복과 유니코드가 있는 입력에서도 원래 순서의 결합 결과와 고유 원소 수를 출력한다.", | ||
| "language_delta": "Python 튜플과 세트는 실행 시점에 서로 다른 컨테이너이며 타입 주석 없이도 각 동작을 수행한다.", | ||
| "prediction_prompt": "`pair = (\"é\", \"e\", \"é\")`일 때 튜플을 이은 문자열과 `len(set(pair))`를 예측하라.", | ||
| "transfer_trap": "TypeScript 튜플은 주로 타입 검사기의 제약이지만 Python 튜플은 실행 중에도 존재하는 불변 시퀀스다." | ||
| }, | ||
| "py-functions": { | ||
| "title": "함수", | ||
| "concept": "`def`는 호출 가능한 함수를 만들고, 매개변수는 전달된 인자를 받는다. `return`은 호출자에게 값을 돌려주며 함수 안에서 출력하는 것과는 역할이 다르다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 함수 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-strings": { | ||
| "title": "유니코드 코드 포인트 슬라이스", | ||
| "concept": "Python `str`의 인덱스와 슬라이스는 UTF-8 바이트가 아니라 유니코드 코드 포인트를 기준으로 한다. 반열린 구간은 새 문자열을 만들고 원본은 바꾸지 않는다.", | ||
| "worked_example": "별표 코드 포인트가 `서울`의 처음과 끝을 감싼다. 인덱스 `1`부터 마지막 직전까지 선택하면 바이트 위치를 계산하지 않고 `서울`만 얻는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 함수 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 함수 실습의 TODO를 풀지 않는다.", | ||
| "`def`는 호출 가능한 함수를 만들고, 매개변수는 전달된 인자를 받는다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "정확히 한 쌍의 구분자를 없애야 하는데 `strip`을 써서 경계의 같은 문자를 여러 개 제거한다.", | ||
| "결합 문자나 이모지 시퀀스도 언제나 코드 포인트 하나라고 부른다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 함수 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "함수 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`text[1:-1]`에서 포함되지 않는 원본 인덱스는 무엇인가?", | ||
| "코드 포인트 하나짜리 이모지 경계를 같은 슬라이스로 안전하게 뺄 수 있는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 함수 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "반열린 슬라이스 한 번으로 첫 코드 포인트와 마지막 코드 포인트만 제거한 내부 문자열을 출력하라.", | ||
| "objective": "ASCII, 괄호, 이모지로 감싼 문자열에서 원본을 바꾸지 않고 내부를 꺼낸다.", | ||
| "language_delta": "Python 인덱스는 코드 포인트를 따른다. Java와 JavaScript는 UTF-16 코드 단위, Rust 문자열은 UTF-8 바이트 경계를 사용한다.", | ||
| "prediction_prompt": "`len(\"🙂go🙂\")`와 `\"🙂go🙂\"[1:-1]`의 결과를 각각 예상하라.", | ||
| "transfer_trap": "코드 포인트에 안전한 슬라이스도 그래핌 단위는 아니다. 결합 표시로 보이는 글자 하나가 여러 위치를 차지할 수 있다." | ||
| }, | ||
| "py-input": { | ||
| "title": "입력 파싱", | ||
| "concept": "코딩 테스트 입력은 표준 입력의 텍스트에서 시작한다. 보통 한 번 읽고, 공백으로 토큰을 나누고, 필요한 타입으로 바꾼 뒤 알고리즘은 변환된 값으로만 풀면 읽기 쉽다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 입력 파싱 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-pathlib": { | ||
| "title": "`pathlib`로 경로 구성요소 읽기", | ||
| "concept": "`PurePosixPath`는 실제 파일 존재 여부와 무관하게 POSIX 경로 문법을 다룬다. `stem`은 마지막 접미사만 떼고 `suffix`도 마지막 확장자 하나만 돌려준다.", | ||
| "worked_example": "`/srv/report.csv`의 마지막 이름은 `report.csv`, 마지막 접미사를 뺀 이름은 `report`, 접미사는 `.csv`다. 경로를 한 번 해석한 뒤 두 속성을 조합한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 입력 파싱 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 입력 파싱 실습의 TODO를 풀지 않는다.", | ||
| "코딩 테스트 입력은 표준 입력의 텍스트에서 시작한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "마침표로 직접 분리해 디렉터리, 여러 확장자, 점으로 시작하는 이름을 잘못 처리한다.", | ||
| "`archive.tar.gz`의 `stem`이 `archive`라고 생각해 마지막 `.gz`만 제거되는 규칙을 놓친다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 입력 파싱 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "입력 파싱 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`PurePosixPath(\".env\")`의 `stem`과 `suffix`는 각각 무엇인가?", | ||
| "호스트 파일 시스템을 건드리면 안 되는 실습에 `PurePosixPath`가 알맞은 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 입력 파싱 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "입력된 POSIX 경로를 순수 경로 객체로 해석하고 마지막 `stem`과 마지막 `suffix`를 차례로 출력하라.", | ||
| "objective": "일반 파일, 여러 확장자의 압축 파일, 점 파일을 문자열 편법 없이 경로 속성으로 처리한다.", | ||
| "language_delta": "순수 경로 클래스는 어휘적 연산만 한다. 구체적인 `Path`는 파일 시스템 접근도 제공하며 실행 환경의 경로 형식을 따른다.", | ||
| "prediction_prompt": "`PurePosixPath(\"a/bundle.min.js\").stem`과 `.suffix`를 실행 전에 정하라.", | ||
| "transfer_trap": "`PurePosixPath`에 넣은 윈도우 역슬래시는 평범한 문자다. 데이터 계약에 맞는 경로 형식을 골라야 한다." | ||
| }, | ||
| "py-lists-dicts": { | ||
| "title": "리스트와 딕셔너리", | ||
| "concept": "리스트는 순서 있는 값을 인덱스와 반복으로 다룬다. 딕셔너리는 키를 값에 매핑하므로 이름, 문자, ID처럼 의미 있는 식별자로 바로 찾아야 할 때 적합하다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 리스트와 딕셔너리 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-control-flow": { | ||
| "title": "분기와 범위 경계 맞추기", | ||
| "concept": "Python에서는 들여쓰기가 반복문과 조건문의 본문을 결정한다. `range`는 두 번째 인자를 포함하지 않으므로 상한을 포함하려면 범위를 조정해야 한다.", | ||
| "worked_example": "예제는 끝값 `8`을 사용해 `1`부터 `7`까지 순회한다. 그중 `1`, `3`, `5`, `7`만 누적해 합계 `16`을 만든다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 리스트와 딕셔너리 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 리스트와 딕셔너리 실습의 TODO를 풀지 않는다.", | ||
| "리스트는 순서 있는 값을 인덱스와 반복으로 다룬다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "상한을 그대로 `range`의 끝값으로 넘겨 홀수인 마지막 수를 빠뜨린다.", | ||
| "누적문 들여쓰기를 잘못해 조건과 관계없이 모든 값을 더한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 리스트와 딕셔너리 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "리스트와 딕셔너리 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`range(1, 4)`가 만드는 정수를 순서대로 말할 수 있는가?", | ||
| "`total += value`는 홀수일 때만 실행되도록 어디에 들여써야 하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 리스트와 딕셔너리 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "1부터 입력된 상한까지 포함하는 범위를 순회하고 조건문을 통과한 홀수만 누적해 합계를 출력하라.", | ||
| "objective": "상한이 0, 홀수, 짝수인 사례에서 올바른 홀수 합계를 구한다.", | ||
| "language_delta": "Python의 일반적인 `range` 끝은 배타적이며 들여쓰기가 문법이다. 중괄호 언어나 포함 범위 API와 그대로 대응하지 않는다.", | ||
| "prediction_prompt": "상한이 `6`일 때 실제로 더해지는 값을 나열하고 최종 누적값을 계산하라.", | ||
| "transfer_trap": "끝값을 포함하는 다른 언어의 범위나 중괄호 기반 블록을 그대로 번역하면 경계값을 한 칸 잘못 잡거나 블록 범위를 틀릴 수 있다." | ||
| }, | ||
| "py-tuples-sets": { | ||
| "title": "튜플과 세트", | ||
| "concept": "튜플은 위치가 고정된 작은 기록에 좋고, 세트는 중복 없는 원소와 빠른 포함 검사용이다. 출력 순서가 필요하면 튜플이나 리스트를 유지하고, 유일성 판단만 세트에 맡긴다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 튜플과 세트 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-functions": { | ||
| "title": "체크포인트: 계산값 반환하기", | ||
| "concept": "넓이 함수는 계산 하나를 맡고 결과를 반환한다. 파싱과 출력은 호출자에게 남겨 두면 같은 함수를 테스트하거나 가져오거나 감싸도 숨은 표준 출력이 생기지 않는다.", | ||
| "worked_example": "`area(8, 2)`는 두 인자를 곱해 `16`을 반환한다. 바깥의 출력문만 그 정수를 소비하며 함수 본문은 표준 출력에 쓰지 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 튜플과 세트 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 튜플과 세트 실습의 TODO를 풀지 않는다.", | ||
| "튜플은 위치가 고정된 작은 기록에 좋고, 세트는 중복 없는 원소와 빠른 포함 검사용이다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "함수 안에서 출력한 뒤 호출자가 `None` 반환값까지 다시 출력한다.", | ||
| "첫 사례만 보고 너비와 높이를 더해 둘레와도 다른 값을 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 튜플과 세트 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "튜플과 세트 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "직사각형의 한 변이 0이면 함수가 반환해야 할 값은 무엇인가?", | ||
| "예제에서 표준 출력 경계는 어느 한 줄뿐인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 튜플과 세트 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "값을 반환하는 넓이 계산을 완성하고 입력 파싱과 최종 출력은 호출 위치에 그대로 두라.", | ||
| "objective": "0인 변을 포함한 세 직사각형의 넓이를 하나의 재사용 가능한 함수로 계산한다.", | ||
| "language_delta": "일반 `def`는 명시적 반환값에 도달하지 않으면 `None`을 돌려준다. 비동기 함수 호출은 값 대신 아직 실행이 필요한 코루틴을 만든다.", | ||
| "prediction_prompt": "`area(7, 1)`의 값과 본문에서 출력만 한 함수의 반환값을 각각 예측하라.", | ||
| "transfer_trap": "표현식 본문이 암묵적으로 값을 반환하는 언어의 습관을 일반 Python 함수에 적용하지 마라." | ||
| }, | ||
| "py-comprehensions": { | ||
| "title": "컴프리헨션", | ||
| "concept": "컴프리헨션은 출력 표현식, 반복, 선택적 필터를 한 식에 묶어 컬렉션을 만든다. 단순 변환이나 필터에는 강하지만, 여러 단계 부작용은 보통 반복문이 더 읽기 쉽다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 컴프리헨션 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-modules-imports": { | ||
| "title": "모듈 이름으로 의존성 드러내기", | ||
| "concept": "`import`는 필요한 모듈을 불러와 현재 이름공간에 이름을 묶는다. 호출할 때 `math.ceil`처럼 모듈 이름을 남기면 함수의 출처가 보이고 와일드카드 충돌도 피할 수 있다.", | ||
| "worked_example": "`math.ceil(-3.8)`은 입력보다 작지 않은 최소 정수 `-3`을 반환한다. 음수에서 위쪽 반올림은 이 경우 0 쪽으로 이동하며 `floor`와 다르다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 컴프리헨션 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 컴프리헨션 실습의 TODO를 풀지 않는다.", | ||
| "컴프리헨션은 출력 표현식, 반복, 선택적 필터를 한 식에 묶어 컬렉션을 만든다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "둘 다 반올림이라고 생각해 음수 입력에도 `math.floor`를 고른다.", | ||
| "별표 가져오기로 `ceil`이 어느 모듈에서 왔는지 숨기고 이름 충돌 가능성을 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 컴프리헨션 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "컴프리헨션 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`math.ceil(-2.1)`이 `-3`이 아니라 `-2`인 이유는 무엇인가?", | ||
| "`import math`가 현재 이름공간에 만드는 바인딩은 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 컴프리헨션 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "표준 입력의 실수를 파싱하고 `math` 모듈 이름을 통해 올림 함수를 호출해 정수 결과를 출력하라.", | ||
| "objective": "양수, 음수, 이미 정수인 입력을 표준 라이브러리의 올림 규칙으로 처리한다.", | ||
| "language_delta": "Python은 프로세스 안에서 불러온 모듈을 `sys.modules`에 캐시하지만 각 가져오기 문은 자기 범위에 이름을 별도로 묶는다.", | ||
| "prediction_prompt": "실행하지 않고 `math.ceil(-0.2)`와 `math.floor(-0.2)`의 결과를 비교하라.", | ||
| "transfer_trap": "Java의 패키지 가져오기나 JavaScript 모듈 로딩은 Python 모듈의 실행·캐시 모델과 같지 않다." | ||
| }, | ||
| "py-errors": { | ||
| "title": "예외", | ||
| "concept": "예외는 정상 흐름과 복구 가능한 실패 흐름을 분리한다. 실패할 수 있는 줄만 작게 `try`에 넣고, 복구할 수 있는 구체적인 예외만 `except ValueError`처럼 잡아야 한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 예외 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-lambdas-closures": { | ||
| "title": "클로저가 읽는 바인딩 셀", | ||
| "concept": "클로저는 바깥 함수 호출이 끝난 뒤에도 그 범위의 이름에 접근한다. 자유 이름은 내부 함수가 실행될 때 해석되므로 반복문 람다는 보통 과거 값을 따로 저장하지 않는다.", | ||
| "worked_example": "세 읽기 함수는 `index`가 변하는 동안 만들어지지만 반복이 끝난 뒤 실행된다. 모두 같은 셀의 최종값 `2`를 읽어 `[2, 2, 2]`가 된다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 예외 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 예외 실습의 TODO를 풀지 않는다.", | ||
| "예외는 정상 흐름과 복구 가능한 실패 흐름을 분리한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "반복문 안에서 만든 각 람다가 생성 당시의 반복 값을 자동으로 기억한다고 생각한다.", | ||
| "기본 인자가 정의 시점에 평가되어 값을 고정한다는 이유를 모른 채 우연한 처방으로만 사용한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 예외 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "예외 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "예제 람다의 자유 이름 `index`는 어느 시점에 조회되는가?", | ||
| "`lambda index=index: index`로 바꾸면 세 결과가 어떻게 달라지는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 예외 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "팩터리 호출에서 만들어진 `delta` 바인딩을 나중 호출 때 읽는 덧셈 클로저를 반환하라.", | ||
| "objective": "양수, 음수, 0인 서로 다른 증분을 팩터리 호출 뒤에도 보존하는 호출 가능 객체를 만든다.", | ||
| "language_delta": "`lambda`는 표현식 하나짜리 함수 문법일 뿐이며 중첩 `def`와 같은 어휘적 이름 탐색과 클로저 셀을 사용한다.", | ||
| "prediction_prompt": "`[lambda n=n: n for n in range(3)]`의 각 함수를 호출한 목록을 예측하라.", | ||
| "transfer_trap": "JavaScript의 블록 범위 반복 바인딩은 반복마다 별도 값을 캡처하는 경우가 많아 Python 기본 동작과 다르다." | ||
| }, | ||
| "py-files-context": { | ||
| "title": "파일과 컨텍스트 매니저", | ||
| "concept": "`with`는 컨텍스트 매니저에 들어갔다가 블록이 끝나면 정리 코드를 실행한다. 파일 핸들이나 contextlib 도구는 이 패턴으로 닫기 같은 정리를 안정적으로 처리한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 파일과 컨텍스트 매니저 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-decorators": { | ||
| "title": "정의 시점에 함수 감싸기", | ||
| "concept": "`@bracket`는 함수 본문이 만들어진 뒤 데코레이터를 적용하고 함수 이름을 반환된 래퍼에 다시 묶는다. `functools.wraps`는 원래 함수의 주요 메타데이터를 보존한다.", | ||
| "worked_example": "태그 데코레이터는 `label` 주위에 래퍼 하나를 만든다. `label(\"Mina\")`를 호출하면 원래 결과를 대문자로 바꾸고 꺾쇠로 감싼다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 파일과 컨텍스트 매니저 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 파일과 컨텍스트 매니저 실습의 TODO를 풀지 않는다.", | ||
| "`with`는 컨텍스트 매니저에 들어갔다가 블록이 끝나면 정리 코드를 실행한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "데코레이터를 정의하면서 원래 함수를 호출해 버리고 호출 가능한 래퍼를 반환하지 않는다.", | ||
| "`functools.wraps`를 빼서 도움말과 실행 중 검사 도구가 래퍼의 메타데이터만 보게 한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 파일과 컨텍스트 매니저 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "파일과 컨텍스트 매니저 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "정의 때 한 번 일어나는 일과 매 호출마다 반복되는 일을 구분할 수 있는가?", | ||
| "래퍼가 장식된 함수의 호출 인자를 받아야 하는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 파일과 컨텍스트 매니저 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "원래 반환값을 대문자로 바꾸고 대괄호로 감싸되 함수 메타데이터를 유지하도록 래퍼를 완성하라.", | ||
| "objective": "장식된 함수 본문을 바꾸지 않고 ASCII와 유니코드 호출 결과를 변환한다.", | ||
| "language_delta": "Python 데코레이터는 `def` 문법에 연결된 일반 호출 적용이다. `wraps`는 속성을 복사하지만 래퍼 실행 의미를 바꾸지는 않는다.", | ||
| "prediction_prompt": "데코레이터가 `decorate`, 래퍼가 `call`을 출력한다면 두 단어가 각각 언제 나타나는지 답하라.", | ||
| "transfer_trap": "Java 애너테이션은 자동 함수 래퍼가 아니라 메타데이터이므로 같은 `@` 모양만 보고 실행 동작을 기대하면 안 된다." | ||
| }, | ||
| "py-modules-imports": { | ||
| "title": "모듈과 import", | ||
| "concept": "모듈은 재사용 코드를 이름공간으로 묶는다. `import math`는 표준 라이브러리를 현재 파일에서 쓸 수 있게 하고, PEP 8처럼 import를 위쪽에 두면 의존성이 먼저 보인다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 모듈과 import 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-async": { | ||
| "title": "`asyncio`로 코루틴 실행하기", | ||
| "concept": "`async def` 호출은 끝까지 실행된 값이 아니라 코루틴 객체를 만든다. `asyncio.run`이 이벤트 루프를 제공하고 `asyncio.gather`가 대기 가능한 객체를 스케줄하며 `await` 지점에서 제어를 양보한다.", | ||
| "worked_example": "두 `upper` 코루틴은 실제 중단 지점에 도달한다. `gather`는 둘을 기다린 뒤 완료 순서가 아니라 인자 순서로 결과를 돌려주므로 `GO NOW`가 출력된다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 모듈과 import 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 모듈과 import 실습의 TODO를 풀지 않는다.", | ||
| "모듈은 재사용 코드를 이름공간으로 묶는다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "코루틴을 호출해 얻은 객체를 이미 계산된 결과처럼 취급한다.", | ||
| "비동기 코드에서 `time.sleep`을 사용해 이벤트 루프 스레드를 막는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 모듈과 import 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "모듈과 import 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`main()`이 코루틴을 만든 뒤 실제로 이를 구동하는 구성요소는 무엇인가?", | ||
| "`asyncio.gather`의 반환값 순서는 완료 시점과 인자 위치 중 무엇을 따르는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 모듈과 import 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "입력 정수마다 두 배를 반환하는 코루틴을 만들고 `asyncio.gather`로 모두 모아 기다린 뒤 입력 순서대로 출력하라.", | ||
| "objective": "여러 값, 부호 있는 값, 빈 입력에서 실제 중단되는 코루틴을 경고 없이 실행한다.", | ||
| "language_delta": "`asyncio`는 `await`에서 협력적으로 전환할 뿐 일반 Python 문장을 병렬로 만들지 않는다. 코루틴 호출만으로는 예약도 실행도 되지 않는다.", | ||
| "prediction_prompt": "`double`이 `sleep(0)`을 대기할 때 `await asyncio.gather(double(3), double(-1))`의 목록을 예상하라.", | ||
| "transfer_trap": "JavaScript의 프로미스를 반환하는 함수는 시작 규칙이 다르므로 그 즉시 실행 직관을 Python 코루틴 호출에 옮기지 마라." | ||
| }, | ||
| "py-dataclasses": { | ||
| "title": "데이터클래스", | ||
| "concept": "`@dataclass`는 데이터를 담는 클래스의 생성자와 표현 같은 반복 코드를 만들어 준다. 필드 이름과 타입 힌트가 클래스 정의에 붙어 있어 작은 도메인 값을 읽기 쉽다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 데이터클래스 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-lists-dicts": { | ||
| "title": "딕셔너리 키별로 점수 모으기", | ||
| "concept": "딕셔너리의 각 키는 독립된 점수 목록을 가리킨다. `setdefault`는 필요할 때만 버킷을 만들고 `get(query, [])`는 없는 이름을 빈 시퀀스로 관찰하게 한다.", | ||
| "worked_example": "`Mina`의 목록에는 `8`과 `1`이 들어 있어 합은 `9`다. `Kai`의 별도 목록은 키마다 서로 다른 그룹 버킷을 가져야 함을 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 데이터클래스 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 데이터클래스 실습의 TODO를 풀지 않는다.", | ||
| "`@dataclass`는 데이터를 담는 클래스의 생성자와 표현 같은 반복 코드를 만들어 준다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "조회할 이름이 입력 쌍에 없을 수 있는데 `scores[query]`로 바로 인덱싱한다.", | ||
| "모든 키가 같은 목록 객체를 공유하게 만들어 한 사람의 점수가 다른 그룹에도 나타난다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 데이터클래스 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "데이터클래스 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "토큰 중 조회 이름은 어디에 있고 이름-점수 쌍은 어느 위치부터 시작하는가?", | ||
| "없는 키에 대해 `sum(scores.get(query, []))`가 예외 대신 0인 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 데이터클래스 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "남은 토큰을 이름과 점수 쌍으로 순회해 이름별 목록에 추가하고 요청된 그룹의 합계를 구하라.", | ||
| "objective": "이름 중복, 음수 점수, 존재하지 않는 조회가 있는 사례를 같은 그룹화 로직으로 처리한다.", | ||
| "language_delta": "Python `dict`는 해시 가능한 키를 받고 `get`은 누락 키를 삽입하지 않는다. JavaScript 일반 객체의 문자열 키와 프로토타입 문제와 다르다.", | ||
| "prediction_prompt": "조회 이름이 `Bo`이고 `Ada`, `Lin` 쌍만 있다면 `scores.get(query, [])`와 그 합을 예측하라.", | ||
| "transfer_trap": "JavaScript 객체처럼 모든 키를 문자열 속성으로 생각하거나 누락 조회가 딕셔너리에 항목을 만든다고 가정하지 마라." | ||
| }, | ||
| "py-typing": { | ||
| "title": "타입 힌트", | ||
| "concept": "타입 힌트는 독자와 도구에게 기대하는 값의 모양을 알려 준다. Python 실행 자체는 여전히 동적이므로, 주석처럼 써 놓은 타입과 실제 함수 본문이 맞아야 의미가 있다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 타입 힌트 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-sorting-keys": { | ||
| "title": "여러 필드 정렬 키 만들기", | ||
| "concept": "Python은 튜플 키를 왼쪽부터 비교한다. 점수에 음수를 붙이면 큰 점수가 먼저 오고, 점수가 같을 때만 원래 이름이 오름차순 기준이 된다.", | ||
| "worked_example": "`Bo`의 점수 `9`가 1차 기준에서 `Mina`와 `Ana`의 `7`보다 앞선다. 두 동점 이름끼리는 2차 기준으로 `Ana`가 먼저다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 타입 힌트 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 타입 힌트 실습의 TODO를 풀지 않는다.", | ||
| "타입 힌트는 독자와 도구에게 기대하는 값의 모양을 알려 준다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "점수-이름 튜플 전체에 `reverse=True`를 적용해 이름 동점 기준까지 내림차순으로 만든다.", | ||
| "명시된 2차 기준 대신 안정 정렬의 원래 입력 순서에 기대어 동점을 결정한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 타입 힌트 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "타입 힌트 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`Zed:5`와 `Amy:5`에 대해 만들어지는 정렬 키를 각각 적을 수 있는가?", | ||
| "이름만 정렬하면 뒤에 더 높은 점수의 레코드가 있을 때 왜 실패하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 타입 힌트 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "이름-점수 레코드를 파싱하고 점수 내림차순, 이름 오름차순 정렬에서 첫 항목을 선택하라.", | ||
| "objective": "서로 다른 점수, 동점, 음수 점수 하나인 사례에서 요구한 우승자를 고른다.", | ||
| "language_delta": "`sorted`는 새 목록을 반환하고 안정적이다. 키 함수는 두 원소를 매번 비교하지 않고 각 원소의 비교 자료를 한 번 계산한다.", | ||
| "prediction_prompt": "키가 `(-score, name)`일 때 `(\"Zed\", 5)`와 `(\"Amy\", 5)` 중 누가 앞서는지 정하라.", | ||
| "transfer_trap": "다른 언어의 점수 뺄셈 비교자는 오버플로할 수 있고 이름 동점 규칙도 표현하지 못한다. 여기서는 튜플 키가 둘을 함께 해결한다." | ||
| }, | ||
| "py-generators": { | ||
| "title": "이터레이터와 제너레이터", | ||
| "concept": "이터레이터는 값을 하나씩 만든다. 제너레이터 함수는 `yield`에서 값을 내보내고 멈췄다가, 호출자가 다음 값을 요구할 때 이어서 실행한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 이터레이터와 제너레이터 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-counter-defaultdict": { | ||
| "title": "`Counter`로 세고 `defaultdict`로 묶기", | ||
| "concept": "`Counter`는 없는 원소의 빈도를 0으로 답한다. `defaultdict(list)`는 인덱싱할 때 독립 버킷을 만들지만 `get`으로 누락 항목을 관찰하면 새 키를 삽입하지 않는다.", | ||
| "worked_example": "데이터 단어 중 `owl`은 두 번 나타난다. 다섯 단어가 모두 `o`로 시작하므로 예제는 빈도 `2`와 첫 글자 그룹 크기 `5`를 구분해 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 이터레이터와 제너레이터 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 이터레이터와 제너레이터 실습의 TODO를 풀지 않는다.", | ||
| "이터레이터는 값을 하나씩 만든다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "맨 앞 조회 토큰까지 데이터 단어에 포함해 조회 빈도를 하나 늘린다.", | ||
| "누락 그룹을 확인하려고 `groups[key]`를 읽어 딕셔너리를 부수 효과로 변경한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 이터레이터와 제너레이터 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "이터레이터와 제너레이터 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "사전 포함 여부 검사 없이 `Counter(words)[\"absent\"]`가 0인 이유는 무엇인가?", | ||
| "`defaultdict` 버킷을 만드는 동작은 인덱싱과 `get` 중 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 이터레이터와 제너레이터 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "데이터 단어의 빈도를 세고 첫 글자별로 묶은 뒤 조회 단어의 빈도와 해당 첫 글자 그룹 크기를 출력하라.", | ||
| "objective": "존재하는 조회, 반복 조회, 완전히 누락된 조회에서 두 통계를 올바르게 만든다.", | ||
| "language_delta": "두 컬렉션은 `dict`를 대체하지 않고 특화한다. `Counter`는 숫자 기본값을, `defaultdict`는 생성 함수 기반 누락값 생성을 제공한다.", | ||
| "prediction_prompt": "조회가 `x`, 데이터가 `a b`일 때 `groups.get(\"x\", [])`가 `x` 키를 삽입하는지 판단하라.", | ||
| "transfer_trap": "일반 딕셔너리 인덱싱도 누락 키에서 0이나 빈 목록을 준다고 일반화하지 마라. 그 동작은 각 특화 타입에만 있다." | ||
| }, | ||
| "py-lambdas-closures": { | ||
| "title": "람다와 클로저", | ||
| "concept": "`lambda`는 작은 표현식 함수를 만든다. 그 함수가 바깥 함수의 이름을 기억하면 클로저가 되어, 바깥 호출이 끝난 뒤에도 그 값을 사용할 수 있다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 람다와 클로저 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-deque": { | ||
| "title": "양쪽 끝을 상수 시간에 다루기", | ||
| "concept": "`deque`는 양 끝 삽입과 제거에 맞춘 시퀀스다. `appendleft`와 `popleft`는 왼쪽, `append`와 `pop`은 오른쪽 경계를 조작한다.", | ||
| "worked_example": "`center`로 시작해 `first`를 왼쪽, `last`를 오른쪽에 둔다. 양 끝을 하나씩 제거하면 `first last`가 나오고 가운데 값은 남는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 람다와 클로저 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 람다와 클로저 실습의 TODO를 풀지 않는다.", | ||
| "`lambda`는 작은 표현식 함수를 만든다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "두 끝값을 모두 `append`해 원래 가운데 값이 왼쪽 끝에 남게 한다.", | ||
| "커지는 큐에서도 원소 이동이 필요한 `list.pop(0)`이 똑같이 적합하다고 생각한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 람다와 클로저 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "람다와 클로저 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`appendleft(left)` 뒤 `popleft()`가 반환하는 값은 무엇인가?", | ||
| "오른쪽 끝 원소를 제거하는 덱 연산은 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 람다와 클로저 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "두 번째 토큰을 왼쪽, 세 번째 토큰을 오른쪽에 넣고 그 두 끝값을 제거한 순서대로 출력하라.", | ||
| "objective": "가운데 원소를 보존하면서 ASCII와 유니코드 끝값을 올바른 순서로 보고한다.", | ||
| "language_delta": "덱은 탐색 경계를 반복해서 꺼내는 제어문과 잘 맞지만, 임의 위치 조회는 목록의 강점이지 덱의 목적이 아니다.", | ||
| "prediction_prompt": "`deque([\"m\"])`에 `appendleft(\"l\")`, `append(\"r\")`, `popleft()`, `pop()`을 차례로 적용해 보라.", | ||
| "transfer_trap": "다른 표준 라이브러리의 큐가 `offer`와 `poll`을 쓰더라도 메서드 이름이 아니라 어느 끝을 다루는지 대응시켜라." | ||
| }, | ||
| "py-decorators": { | ||
| "title": "데코레이터", | ||
| "concept": "데코레이터는 함수가 정의되는 순간 원래 함수 객체를 받아 새로 묶일 함수 객체를 돌려준다. `@decorator` 문법은 함수를 값으로 넘기는 축약 문법이다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 데코레이터 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-comprehensions": { | ||
| "title": "체크포인트: 거르고 변환해 합산하기", | ||
| "concept": "컴프리헨션은 각 원본 숫자에 필터를 먼저 적용하고 통과한 값에만 제곱 표현식을 계산한다. 만들어진 목록은 `sum`이 소비하기 전에 메모리에 완성된다.", | ||
| "worked_example": "`5`, `6`, `7`, `8` 중 `6`과 `8`만 짝수 검사를 통과한다. 제곱값 `36`, `64`가 목록을 이루고 합은 `100`이다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 데코레이터 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 데코레이터 실습의 TODO를 풀지 않는다.", | ||
| "데코레이터는 함수가 정의되는 순간 원래 함수 객체를 받아 새로 묶일 함수 객체를 돌려준다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "홀짝 조건을 반대로 써서 홀수 제곱의 합을 조용히 계산한다.", | ||
| "부수 효과와 중첩 조건을 한 컴프리헨션에 넣어 평가 순서를 읽기 어렵게 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 데코레이터 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "데코레이터 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "뒤쪽 `if`에서 탈락한 원소에도 앞쪽 출력 표현식이 실행되는가?", | ||
| "`[-2, -1, 0]`에 짝수 제곱 규칙을 적용하면 어떤 목록이 생기는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 데코레이터 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "입력 정수 중 짝수만 거르는 읽기 쉬운 목록 컴프리헨션 하나로 제곱 목록을 만들고 그 합을 출력하라.", | ||
| "objective": "부호가 섞인 입력과 선택 결과가 빈 입력을 처리하며 필터가 변환보다 먼저 적용됨을 보여 준다.", | ||
| "language_delta": "제너레이터 표현식과 `itertools`는 지연될 수 있지만 대괄호 컴프리헨션은 즉시 목록을 만든다. 이 체크포인트는 그 차이를 관찰한다.", | ||
| "prediction_prompt": "`[1, 2, 3, 4]`에서 `sum` 전에 만들어지는 정확한 중간 목록을 적어 보라.", | ||
| "transfer_trap": "JavaScript 배열 콜백이나 Java 스트림과 단계 모양이 비슷해도 평가 시점은 다르다. Python 목록 컴프리헨션을 지연식으로 보지 마라." | ||
| }, | ||
| "py-sorting-keys": { | ||
| "title": "정렬과 key 함수", | ||
| "concept": "`sorted`는 새 리스트를 반환하고 원본 순서를 직접 바꾸지 않는다. `key` 함수는 각 원소에서 비교 기준을 꺼내므로 튜플이나 객체를 특정 필드로 정렬할 수 있다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 정렬과 key 함수 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-generators": { | ||
| "title": "지연 카운트다운 제너레이터 만들기", | ||
| "concept": "`yield`가 있는 함수를 호출하면 제너레이터 이터레이터가 생긴다. 본문은 첫 다음 값 요청에서 시작하고 `yield`마다 지역 상태를 보존한 채 멈추며 끝나면 `StopIteration`을 일으킨다.", | ||
| "worked_example": "`reverse_letters(\"abc\")` 호출 직후에는 문자열을 순회하지 않는다. 출력 확장이 세 번 전진시키며 `c`, `b`, `a`를 차례로 꺼낸다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 정렬과 key 함수 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 정렬과 key 함수 실습의 TODO를 풀지 않는다.", | ||
| "`sorted`는 새 리스트를 반환하고 원본 순서를 직접 바꾸지 않는다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "제너레이터 함수를 호출하는 순간 첫 `yield` 앞의 문장도 실행된다고 생각한다.", | ||
| "이미 소진한 같은 제너레이터를 다시 순회하면 이전 값이 재생될 것으로 기대한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 정렬과 key 함수 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "정렬과 key 함수 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "카운트다운의 `while` 본문은 어느 연산에서 처음 실행되는가?", | ||
| "`print(*countdown(0))`도 줄바꿈 하나를 만드는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 정렬과 key 함수 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "현재 양수를 `yield`로 내보내고 중단된 지역값을 줄이는 과정을 0에 도달할 때까지 반복하라.", | ||
| "objective": "중간 목록 없이 여러 값, 한 값, 빈 카운트다운 시퀀스를 생성한다.", | ||
| "language_delta": "제너레이터 함수는 이터레이터 생성 함수이므로 호출마다 새 중단 상태를 만든다. 일반 `return`은 값을 더 내보내지 않고 반복을 끝낸다.", | ||
| "prediction_prompt": "`countdown(3)`에 `next`를 한 번 호출하면 무엇을 내보내고 다음 재개 때 지역 숫자는 얼마인지 답하라.", | ||
| "transfer_trap": "JavaScript 제너레이터는 비슷한 문법을 쓰지만 이터레이터 결과 객체를 돌려준다. Python `next`는 값 자체를 주거나 `StopIteration`을 일으킨다." | ||
| }, | ||
| "py-counter-defaultdict": { | ||
| "title": "Counter와 defaultdict", | ||
| "concept": "`Counter`는 해시 가능한 값을 세는 딕셔너리 계열이고, `defaultdict(list)`는 없는 그룹 키에 빈 리스트를 자동으로 만든다. 카운팅과 그룹핑에서 초기화 코드를 줄인다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 Counter와 defaultdict 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-itertools": { | ||
| "title": "이터러블을 지연 방식으로 평탄화하기", | ||
| "concept": "`chain.from_iterable`은 바깥 이터러블 하나를 받아 내부 이터러블을 차례로 전진시킨다. 임시 평탄 목록 없이 현재 그룹에 필요한 이터레이터 상태만 유지한다.", | ||
| "worked_example": "바깥 튜플은 `py`와 `thon`을 담는다. `chain`이 첫 문자열의 글자 뒤에 둘째 문자열의 글자를 내고 `join`이 소비해 `python`을 만든다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 Counter와 defaultdict 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 Counter와 defaultdict 실습의 TODO를 풀지 않는다.", | ||
| "`Counter`는 해시 가능한 값을 세는 딕셔너리 계열이고, `defaultdict(list)`는 없는 그룹 키에 빈 리스트를 자동으로 만든다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`chain(groups)`를 사용해 내부 글자가 아니라 각 문자열 전체를 항목으로 받는다.", | ||
| "확인하려고 연결 일부를 소비한 뒤 `join`이 처음부터 다시 시작할 것으로 기대한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 Counter와 defaultdict 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "Counter와 defaultdict 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "이 실습에서 `chain.from_iterable`이 받는 바깥 인자는 몇 개인가?", | ||
| "평탄 이터레이터에서 첫 글자를 `next`로 소비한 뒤 무엇이 남는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 Counter와 defaultdict 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "파싱한 모든 문자 그룹을 `chain.from_iterable`에 넘기고 단일 통과 스트림을 이어 붙여 출력하라.", | ||
| "objective": "길이가 다른 그룹을 중첩 목록 없이 하나의 올바른 문자열로 평탄화한다.", | ||
| "language_delta": "`itertools` 연산은 이터레이터를 반환해 소비자가 전진할 때까지 작업을 미룬다. 여기서는 `join`이 최종 문자열을 만드는 종단 소비자다.", | ||
| "prediction_prompt": "최종 `join`을 생각하기 전에 `list(chain.from_iterable([\"ab\", \"c\"]))`를 예측하라.", | ||
| "transfer_trap": "다른 언어의 스트림이 캐시되거나 재생된다고 해서 Python 이터레이터도 그렇다고 보지 마라. 일반 이터레이터는 소비 상태를 유지한다." | ||
| }, | ||
| "py-deque": { | ||
| "title": "deque", | ||
| "concept": "`deque`는 양쪽 끝에서 넣고 빼는 작업이 빠른 큐다. BFS 큐나 앞쪽 제거가 잦은 슬라이딩 윈도에서는 리스트의 `pop(0)`보다 이 타입이 맞다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 deque 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-errors": { | ||
| "title": "`ValueError`만 좁게 복구하기", | ||
| "concept": "`int`는 텍스트가 정수를 나타내지 않으면 `ValueError`를 일으킨다. 그 변환만 작은 `try`에 넣고 정확한 예외만 잡아야 다른 결함을 숨기지 않는다.", | ||
| "worked_example": "`oops` 변환이 `ValueError`를 일으켜 처리기가 대체값 `-8`을 정수로 바꾼다. 유효한 대체값이 최종 출력이 된다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 deque 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 deque 실습의 TODO를 풀지 않는다.", | ||
| "`deque`는 양쪽 끝에서 넣고 빼는 작업이 빠른 큐다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "프로그램 전체를 `except Exception`으로 감싸 관련 없는 버그도 정상 대체값처럼 보이게 한다.", | ||
| "`-3` 같은 부호 있는 문자열을 `int`가 정상 파싱할 수 없다고 판단한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 deque 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "deque 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "첫 토큰이 유효하면 `except` 블록이 실행되는가?", | ||
| "이 처리기가 복구하려는 `ValueError`를 일으킬 수 있는 문장은 어느 것인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 deque 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "첫 토큰의 정수 변환을 시도하고 그 변환이 `ValueError`일 때만 두 번째 토큰을 대체값으로 사용하라.", | ||
| "objective": "유효한 음수와 잘못된 텍스트를 구분해 필요한 경우에만 제공된 대체값을 적용한다.", | ||
| "language_delta": "`try`는 복구 정책을 선택하고 `with`는 자원 수명을 맡는다. 두 기능은 실패를 다루지만 책임이 다르다.", | ||
| "prediction_prompt": "같은 대체값 `9`를 두고 후보가 `-3`인 경우와 `bad`인 경우의 분기를 각각 추적하라.", | ||
| "transfer_trap": "JavaScript `parseInt(\"bad\")`는 예외 대신 `NaN`을 반환하므로 Python의 `try`/`except` 패턴을 그대로 옮겨도 대체값 분기가 실행되지 않는다." | ||
| }, | ||
| "py-itertools": { | ||
| "title": "itertools", | ||
| "concept": "`itertools`는 체인, 슬라이스, 조합처럼 반복 패턴을 lazy iterator로 제공한다. 중간 리스트를 만들지 않고도 반복 파이프라인의 의도를 코드로 드러낼 수 있다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 itertools 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-files-context": { | ||
| "title": "컨텍스트 관리자 종료 보장하기", | ||
| "concept": "`with` 프로토콜은 `__enter__`로 관리값을 얻고 블록이 어떤 방식으로 끝나도 `__exit__`를 호출한다. `StringIO`로 실제 파일 없이 같은 읽기·닫기 수명을 관찰할 수 있다.", | ||
| "worked_example": "메모리 핸들에는 공백으로 감싼 `Seoul`이 있다. 관리 블록 안에서 읽고 공백을 제거한 값을 블록 뒤 대문자로 바꾸면 핸들은 닫혔어도 `SEOUL`은 남는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 itertools 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 itertools 실습의 TODO를 풀지 않는다.", | ||
| "`itertools`는 체인, 슬라이스, 조합처럼 반복 패턴을 lazy iterator로 제공한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`with` 블록이 끝나 닫힌 `StringIO` 핸들에서 다시 읽으려 한다.", | ||
| "`__exit__`가 성공할 때만 실행된다고 생각해 예외 분기마다 정리 코드를 중복한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 itertools 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "itertools 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "들여쓰기가 관리 블록을 벗어나기 전에 반드시 끝내야 하는 읽기 연산은 무엇인가?", | ||
| "자원 내용이 공백뿐이라면 정규화 뒤 어떤 값이 남는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 itertools 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "컨텍스트로 관리되는 핸들 안에서 내용을 읽고 정규화한 뒤 텍스트가 없을 때만 `EMPTY`를 출력하라.", | ||
| "objective": "앞뒤 공백, 일반 텍스트, 빈 메모리 입력에서 자원 종료와 결과 보존을 함께 확인한다.", | ||
| "language_delta": "Python `with`는 파일에 고정된 문법이 아니라 객체의 `__enter__`와 `__exit__`에 진입·종료를 위임한다.", | ||
| "prediction_prompt": "`handle.read()`가 공백 두 칸이면 `strip()` 뒤 값과 `text.upper() or \"EMPTY\"`의 결과를 예측하라.", | ||
| "transfer_trap": "Java의 자원 자동 닫기 구문과 JavaScript의 `using`은 각자 다른 자원 프로토콜을 사용한다. Python 메서드 이름을 그대로 기대하지 마라." | ||
| }, | ||
| "py-pathlib": { | ||
| "title": "pathlib", | ||
| "concept": "`pathlib`는 경로를 문자열 조각이 아니라 객체로 다룬다. `name`, `stem`, `suffix`, `parent` 같은 속성으로 구분자 처리 없이 경로의 의미 있는 부분을 읽는다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 pathlib 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-dataclasses": { | ||
| "title": "클래스 동작과 데이터 클래스 생성 메서드", | ||
| "concept": "클래스는 메서드로 인스턴스 동작을 제공하고 `@dataclass`는 선언 필드에서 생성자, 표현, 동등성을 만든다. 필드 애너테이션은 실행 시점 검증기가 아니며 내부 객체를 불변으로 만들지도 않는다.", | ||
| "worked_example": "`Point(4, 5)`는 두 필드를 저장하고 `total` 메서드는 `9`를 반환한다. 필드값이 같은 두 번째 `Point`와 비교하면 자동 생성된 동등성 덕분에 `True`가 된다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 pathlib 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 pathlib 실습의 TODO를 풀지 않는다.", | ||
| "`pathlib`는 경로를 문자열 조각이 아니라 객체로 다룬다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "생성된 동등성이나 `frozen=True`가 필드 안의 가변 객체까지 재귀적으로 얼린다고 생각한다.", | ||
| "`values: list[int] = []`처럼 가변 기본값을 직접 쓰면 Python 3.12 데이터 클래스가 `ValueError`로 거부한다. 인스턴스별 목록은 `field(default_factory=list)`, 의도적인 클래스 변수는 `ClassVar`로 구분해야 한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 pathlib 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "pathlib 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "두 `Point` 인스턴스를 비교할 때 어느 동등성 구현이 사용되는가?", | ||
| "`x: int` 선언이 `Point(\"wrong\", 5)` 생성을 실행 중에 막는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 pathlib 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "두 인스턴스 필드를 사용하는 `Point.total`을 완성하고 메서드 결과와 데이터 클래스가 만든 동등성을 관찰하라.", | ||
| "objective": "동등한 `Point` 객체를 구성하고 부호 있는 좌표의 합계를 명시적인 클래스 동작으로 계산한다.", | ||
| "language_delta": "Python 데이터 클래스는 데이터 모델 반복 코드를 줄이지만 검증과 도메인 메서드를 대신하지 않는다. 일반 설정에서는 인스턴스도 가변이다.", | ||
| "prediction_prompt": "`Point(2, 3) == Point(2, 3)`의 결과와 한쪽 필드를 바꾼 뒤 결과를 예상하라.", | ||
| "transfer_trap": "Java 레코드는 구성요소 참조가 얕게 고정되지만 일반 Python 데이터 클래스는 별도 설정 없이는 필드 재대입도 가능하다." | ||
| }, | ||
| "py-testing-assert": { | ||
| "title": "테스트와 assert", | ||
| "concept": "`assert`는 작은 예제나 테스트에서 불변 조건을 확인하는 간단한 문장이다. 실제 프로젝트의 테스트 함수도 결국 기대값과 실제값을 반복 가능하게 비교한다는 점은 같다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 테스트와 assert 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-typing": { | ||
| "title": "애너테이션을 검사하되 강제하지 않기", | ||
| "concept": "함수 애너테이션은 독자, 편집기, 선택적 정적 타입 검사기가 쓰는 메타데이터다. `get_type_hints`가 실행 중 기록을 해석할 수 있지만 인자를 변환하거나 잘못된 호출을 차단하지 않는다.", | ||
| "worked_example": "`head`는 `Iterable[str]` 입력과 `str` 반환을 기록한다. 실행 중 검사는 반환 타입 정보를 확인하고 함수 본문은 별도로 목록에서 `elm`을 반환한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 테스트와 assert 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 테스트와 assert 실습의 TODO를 풀지 않는다.", | ||
| "`assert`는 작은 예제나 테스트에서 불변 조건을 확인하는 간단한 문장이다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`int` 애너테이션이 함수 실행 전에 문자열 인자를 자동으로 거부한다고 믿는다.", | ||
| "약속된 타입을 실행 중 검사하는 일을 모든 반환 경로가 그 약속을 지키는지 검증하는 일과 혼동한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 테스트와 assert 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "테스트와 assert 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`get_type_hints(total)[\"return\"]`은 어떤 객체를 보여 주는가?", | ||
| "합계를 실제로 계산하는 것은 애너테이션과 반환 표현식 중 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 테스트와 assert 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "`Iterable[int]` 입력과 `int` 반환 계약을 유지하며 합계를 구현하고, 해석된 타입 힌트로 관찰 결과에 표시를 붙여라.", | ||
| "objective": "여러 원소, 한 원소, 빈 이터러블의 합을 계산하고 애너테이션 메타데이터도 확인한다.", | ||
| "language_delta": "Python은 타입 검사기 없이 애너테이션 코드도 실행한다. TypeScript 역시 타입을 지우지만 일반 도구 체인은 실행 전에 검사기를 거친다.", | ||
| "prediction_prompt": "`total([\"2\"])` 호출을 애너테이션이 막는지, 실제 결과는 어느 연산에서 결정되는지 답하라.", | ||
| "transfer_trap": "애너테이션은 예상 타입을 기록할 뿐 입력 문자열 파싱, 암묵적 형 변환, 실행 중 오버로드 선택을 제공하지 않는다." | ||
| }, | ||
| "py-async": { | ||
| "title": "비동기 개념", | ||
| "concept": "`async def`는 코루틴 함수를 만들고, 호출하면 아직 실행되지 않은 코루틴 객체가 생긴다. 다른 코루틴 안에서 `await`해야 결과를 받고, 파일 끝에서는 `asyncio.run`이 실행을 시작한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 비동기 개념 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "py-testing-assert": { | ||
| "title": "캡스톤: 결정적인 단어 순위", | ||
| "concept": "최종 프로그램은 이터러블을 목록으로 구체화하고 단어를 센 뒤 빈도 내림차순과 단어 오름차순으로 정렬한다. 빈 입력은 예외에 기대지 않고 명시적인 결과를 가져야 한다.", | ||
| "worked_example": "`pear`와 `plum`은 각각 두 번이므로 빈도만으로 승자를 정할 수 없다. 알파벳 2차 기준이 `pear`를 골라 `pear:2`가 된다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 비동기 개념 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 비동기 개념 실습의 TODO를 풀지 않는다.", | ||
| "`async def`는 코루틴 함수를 만들고, 호출하면 아직 실행되지 않은 코루틴 객체가 생긴다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "명시적 동점 규칙 없이 `Counter.most_common(1)`을 사용해 등장 순서에 결과를 맡긴다.", | ||
| "최적화된 Python에서 사라지는 `assert`로 사용자 입력이나 보안 조건을 검증한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 비동기 개념 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "비동기 개념 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "정렬 키의 두 필드는 무엇이며 빈도에 음수를 붙이는 이유는 무엇인가?", | ||
| "Python 인터프리터를 `-O`로 시작하면 `assert` 문은 어떻게 되는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 비동기 개념 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "등장 순서에 따른 동점 처리를 빈도와 단어 기준의 결정적 순위로 바꾸고 `Counter`와 내부 불변식 검사는 유지하라.", | ||
| "objective": "최빈 단어를 알파벳 동점 규칙으로 고르고 단어가 없을 때 문서화된 빈 결과를 반환한다.", | ||
| "language_delta": "이 종합 과제는 출력, 파싱, 재바인딩, 컬렉션, 함수, 즉시 정렬을 통합한다. `assert`는 개발용 검사이며 `-O`에서 제거된다.", | ||
| "prediction_prompt": "입력이 `b a b a`일 때 카운터 항목을 나열하고 `(-count, word)` 기준의 승자를 예측하라.", | ||
| "transfer_trap": "Java의 `assert`도 비활성화될 수 있다. 어느 언어든 반드시 실행되어야 하는 검증은 일반 제어 흐름으로 작성하라." | ||
| } | ||
| } | ||
| } |
+324
-249
@@ -7,377 +7,452 @@ { | ||
| "py-output": { | ||
| "title": "print 与标准输出", | ||
| "concept": "`print` 是计算结果进入判题可见标准输出的边界。它会把参数转成文本,多个参数之间加空格,并默认追加换行,所以 stdout 必须精确匹配。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示print 与标准输出的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "用 `print` 精确输出", | ||
| "concept": "Python 的 `print` 会把参数转换成文本,以 `sep` 连接参数,并在末尾写入 `end`。默认空格和换行同样属于可观察的输出,任何多余字符都会改变评测结果。", | ||
| "worked_example": "对姓名 Mina 和分数 9 的一次 `print` 调用显式设置 `sep=\"=\"` 与 `end=\"!\\n\"`。因此等号、感叹号和换行都由命名参数控制,而不是手工拼接字符串。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把print 与标准输出的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决print 与标准输出练习里的 TODO。", | ||
| "没有确认“`print` 是计算结果进入判题可见标准输出的边界。它会把参数转成文本,多个参数之间加空格,并默认追加换行,所以 stdout 必须精确匹配。”这个要点,只去凑 judge 输出。" | ||
| "保留默认分隔空格,导致冒号两侧出现题目没有要求的空白。", | ||
| "把调试说明写入标准输出,使正确记录之外又多出一段文本。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用print 与标准输出,然后才到达 `example:` 输出?", | ||
| "print 与标准输出练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`print(\"A\", \"B\", sep=\":\", end=\"!\")` 会依次写出哪些字符?", | ||
| "哪个命名参数决定最后一个实参之后写出的内容?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把print 与标准输出应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "读取姓名和分数,调整输出的分隔符与行尾,使两项形成一条以冒号分隔、仅含一个结尾换行的记录。", | ||
| "objective": "面对三组姓名与分数输入,都能生成没有调试前缀或多余空白的精确标准输出。", | ||
| "language_delta": "与原始流写入不同,`print` 会主动提供分隔符和行尾;先掌握这条边界,后续练习才能可靠地区分计算值与最终文本。", | ||
| "prediction_prompt": "运行前写出 `print(\"x\", 0, sep=\"=\", end=\"?\")` 产生的完整字符序列。", | ||
| "transfer_trap": "不要假定其他语言的逐行输出函数也支持 Python 的 `sep`;空格和换行规则必须按各自运行时确认。" | ||
| }, | ||
| "py-variables": { | ||
| "title": "变量", | ||
| "concept": "变量名是到对象的绑定。`=` 不是相等比较,而是决定之后读取这个名字时会得到哪个对象。 这个绑定关系会直接影响后续每一次读取。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示变量的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-input": { | ||
| "title": "一次读完标准输入", | ||
| "concept": "`sys.stdin.read` 把完整输入流收集为一个字符串。无参数的 `split` 能识别空格、换行及连续空白;空文本产生空列表,而 `sum` 对空序列返回加法单位元零。", | ||
| "worked_example": "示例字符串在两行中保存 10 和 4。拆分位置不依赖换行布局,两个文本片段转换为整数后求和得到 14,全程不需要交互式提示。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把变量的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决变量练习里的 TODO。", | ||
| "没有确认“变量名是到对象的绑定。`=` 不是相等比较,而是决定之后读取这个名字时会得到哪个对象。”这个要点,只去凑 judge 输出。" | ||
| "使用 `split(\" \")`,从而把换行留在片段中,并错误处理连续空格。", | ||
| "反复调用交互式读取函数,输入一旦换行方式变化就漏掉数字。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用变量,然后才到达 `example:` 输出?", | ||
| "变量练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "空字符串调用无参数 `split` 后会得到什么列表?", | ||
| "为什么一次 `sys.stdin.read` 能同时覆盖单行与多行案例?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把变量应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把已经读入的文本中每个空白分隔片段转换为整数并求总和,同时让空输入自然得到零,并确认跨行与连续空白不会漏项。", | ||
| "objective": "不为具体排版写分支,也能正确处理空格分隔、跨行分隔和完全为空的整数输入。", | ||
| "language_delta": "这里的 Python 标准输入已经是文本,但 `split` 返回的仍是字符串;数值转换必须显式发生,之后更复杂的解析器也可复用同一读取边界。", | ||
| "prediction_prompt": "先计算 `\" -2\\n5 \".split()` 的结果,再预测把各项转成整数后求和的值。", | ||
| "transfer_trap": "某些生态的扫描器会自动跳过空白并直接给出数字,Python 的原始流文本却不会在调用 `int` 前自行变成整数。" | ||
| }, | ||
| "py-numbers": { | ||
| "title": "数字", | ||
| "concept": "Python 整数能处理很大的值,而数字运算符含义不同:`/` 得到 float,`//` 得到整数商,`%` 得到余数,`**` 表示乘方。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示数字的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-variables": { | ||
| "title": "名称绑定与重新绑定", | ||
| "concept": "Python 变量是绑定到对象的名称,并非带固定类型的储存格。整数累加会计算出另一个不可变整数,再把名称绑定到新对象;闭包读取的则是外层绑定单元的当前内容。", | ||
| "worked_example": "示例先让 `total` 指向 10,增强赋值再让它指向 15。原来的整数对象 10 没有被原地修改,变化发生在名称与对象的关系上。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把数字的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决数字练习里的 TODO。", | ||
| "没有确认“Python 整数能处理很大的值,而数字运算符含义不同:`/` 得到 float,`//` 得到整数商,`%` 得到余数,`**` 表示乘方。”这个要点,只去凑 judge 输出。" | ||
| "把整数增强赋值描述成修改原整数对象,而忽略重新绑定。", | ||
| "误以为同一循环创建的闭包各自冻结了当时的循环值。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用数字,然后才到达 `example:` 输出?", | ||
| "数字练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "执行 `total = left` 后再累加右值,哪个名称被重新绑定?", | ||
| "多个闭包共享同一个已结束循环的绑定单元时会读到哪个时刻的值?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把数字应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "让累加名称先绑定第一个整数,再依据第二个整数重新绑定,最后只输出计算后的数值,不要把加法硬编码在输出语句中。", | ||
| "objective": "通过正数、负数和零的组合,展示重新绑定仍能得到正确的算术总和。", | ||
| "language_delta": "Python 不要求在名称上声明可变性;闭包保留的是对绑定单元的访问,因此调用时刻而非创建时刻可能决定观察到的值。", | ||
| "prediction_prompt": "预测 `[lambda: n for n in range(3)]` 创建的三个函数在循环结束后依次调用会返回什么。", | ||
| "transfer_trap": "JavaScript 的块级循环绑定常为每轮创建新绑定,照搬那里的闭包直觉会掩盖 Python 默认的延迟读取。" | ||
| }, | ||
| "py-strings": { | ||
| "title": "字符串", | ||
| "concept": "字符串是不可变的 Unicode 字符序列。索引读取一个字符,切片读取左闭右开的范围,原字符串不会被改变。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示字符串的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-numbers": { | ||
| "title": "向下取整的商与余数", | ||
| "concept": "整数运算 `//` 选择不大于数学商的最大整数。配套的 `%` 保证被除数等于除数乘商再加余数;余数非零时,其符号跟随除数。", | ||
| "worked_example": "-11 除以 4 时,Python 给出商 -3、余数 1。把 -3 乘以 4 再加 1,正好重建原被除数 -11。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把字符串的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决字符串练习里的 TODO。", | ||
| "没有确认“字符串是不可变的 Unicode 字符序列。索引读取一个字符,切片读取左闭右开的范围,原字符串不会被改变。”这个要点,只去凑 judge 输出。" | ||
| "用 `int(dividend / divisor)` 代替向下取整,负数结果会错误地朝零截断。", | ||
| "认为余数永远非负,忽略除数为负时余数也可能为负。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用字符串,然后才到达 `example:` 输出?", | ||
| "字符串练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "被除数、除数、商与余数之间必须满足哪条恒等式?", | ||
| "为什么 `-7 // 2` 比 `int(-7 / 2)` 小一?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把字符串应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "读取两个整数并保证除数非零,按照 Python 的向下取整规则输出商及其对应余数,并用正负符号组合核对除法恒等式。", | ||
| "objective": "在除数为正或负的案例中都维持除法恒等式,并给出正确的有符号商与余数。", | ||
| "language_delta": "Python 的 `//` 朝负无穷取整;JavaScript 与 TypeScript 的普通数值除法保留小数,而 Java 和 Rust 的有符号整数除法朝零截断。", | ||
| "prediction_prompt": "分别算出 `(-7 // 2, -7 % 2)` 与 `(7 // -2, 7 % -2)` 的两项。", | ||
| "transfer_trap": "不要机械替换除号:`7 / 2` 在 JavaScript 和 TypeScript 中是 3.5,Java 与 Rust 整数运算朝零截断,Python 的 `//` 则向下取整。" | ||
| }, | ||
| "py-control-flow": { | ||
| "title": "控制流", | ||
| "concept": "`if` 选择代码块,`for` 消费 iterable,`while` 在条件变化前重复执行。Python 的缩进就是语法,移动一行会改变真实控制流。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示控制流的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-tuples-sets": { | ||
| "title": "有序元组与唯一成员", | ||
| "concept": "元组保留位置和重复项,适合表达固定长度的输入记录;集合只保留可哈希成员的唯一值并支持成员判断,但输出顺序不应依赖集合的迭代次序。", | ||
| "worked_example": "元组 `(\"r\", \"s\", \"r\")` 连接后仍是 `rsr`,转换为集合后却只剩两种字母。两项输出刻意观察同一来源的不同性质。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把控制流的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决控制流练习里的 TODO。", | ||
| "没有确认“`if` 选择代码块,`for` 消费 iterable,`while` 在条件变化前重复执行。Python 的缩进就是语法,移动一行会改变真实控制流。”这个要点,只去凑 judge 输出。" | ||
| "用空花括号表示空集合,实际却创建了字典。", | ||
| "连接集合而非元组,导致重复项与可靠位置同时丢失。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用控制流,然后才到达 `example:` 输出?", | ||
| "控制流练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "为什么元组 `(\"a\", \"a\")` 仍然具有两个位置?", | ||
| "哪个构造方式能无歧义地创建空集合?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把控制流应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "按元组原有顺序连接输入值,另行从该元组建立集合,仅用它计算不同成员的数量;连接结果仍须保留重复项和原始次序。", | ||
| "objective": "面对重复值和 Unicode 文本,都能输出有序连接结果与唯一成员数。", | ||
| "language_delta": "元组拆包和集合构造都是独立的运行时操作,不需要类型注解才会保留各自的容器语义。", | ||
| "prediction_prompt": "对元组 `(\"é\", \"e\", \"é\")`,先预测连接文本,再预测集合长度。", | ||
| "transfer_trap": "TypeScript 元组主要约束静态位置类型,而 Python 元组本身就是独立的不可变运行时序列。" | ||
| }, | ||
| "py-functions": { | ||
| "title": "函数", | ||
| "concept": "`def` 创建可调用的函数对象。参数接收实参,`return` 把值交回调用者;在函数内部 print 和返回计算结果不是一回事。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示函数的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-strings": { | ||
| "title": "按 Unicode 码点切片", | ||
| "concept": "Python 的 `str` 索引和切片以 Unicode 码点为单位,而不是 `UTF-8` 字节。切片采用左闭右开区间并产生新字符串,原文本保持不可变。", | ||
| "worked_example": "星号分别位于“서울”的第一和最后位置。选择索引 1 到倒数第一位置之前的区间,就能取得中间文本,不必计算字节偏移。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把函数的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决函数练习里的 TODO。", | ||
| "没有确认“`def` 创建可调用的函数对象。参数接收实参,`return` 把值交回调用者;在函数内部 print 和返回计算结果不是一回事。”这个要点,只去凑 judge 输出。" | ||
| "用 `strip` 删除边界标记,却忘记它会移除任意匹配字符而非恰好一个标记。", | ||
| "把码点等同于用户看到的字符;组合符号或表情序列可能含多个码点。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用函数,然后才到达 `example:` 输出?", | ||
| "函数练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "切片 `text[1:-1]` 会排除来源中的哪些索引?", | ||
| "为什么同一切片能安全移除各占一个码点的表情边界?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把函数应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "用一次左闭右开切片恰好去掉首尾码点,并输出中间的完整文本;不要用 `strip` 猜测边界标记,也不要按字节取位置。", | ||
| "objective": "在拉丁文本、括号文本和表情边界三类输入中,都能不修改原值地取得内部字符串。", | ||
| "language_delta": "Python 索引码点;Java 与 JavaScript 索引 `UTF-16` 编码单元,Rust 字符串切片还必须落在合法的 `UTF-8` 字节边界。", | ||
| "prediction_prompt": "先预测 `len(\"🙂go🙂\")`,再写出 `\"🙂go🙂\"[1:-1]` 的结果。", | ||
| "transfer_trap": "码点安全仍不等于字素安全;由组合附加符组成的一个可见符号可能占据多个 Python 位置。" | ||
| }, | ||
| "py-input": { | ||
| "title": "输入解析", | ||
| "concept": "编程题输入从 stdin 文本开始。常见做法是一次读入,按空白拆成 token,转换类型,然后让算法只处理普通值。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示输入解析的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-pathlib": { | ||
| "title": "用 `pathlib` 读取路径组成", | ||
| "concept": "`PurePosixPath` 只分析路径语法,不要求目标真实存在。属性 `stem` 移除最后一个后缀,`suffix` 也只报告最后一段扩展名,而非所有含点片段。", | ||
| "worked_example": "路径 `/srv/report.csv` 的末段是 `report.csv`,主干为 `report`,后缀为 `.csv`。示例只解析一次对象,再组合两个属性。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把输入解析的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决输入解析练习里的 TODO。", | ||
| "没有确认“编程题输入从 stdin 文本开始。常见做法是一次读入,按空白拆成 token,转换类型,然后让算法只处理普通值。”这个要点,只去凑 judge 输出。" | ||
| "手工按句点拆分,因目录、多重后缀或点开头文件而得到错误结果。", | ||
| "误以为 `archive.tar.gz` 的主干是 `archive`,忽略这里只去掉最后的 `.gz`。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用输入解析,然后才到达 `example:` 输出?", | ||
| "输入解析练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`PurePosixPath(\".env\")` 的主干和后缀分别是什么?", | ||
| "为什么不接触宿主文件系统时应选择纯路径对象?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把输入解析应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把输入解析为 POSIX 纯路径对象,并依次报告最后主干和最后后缀;多重扩展名与点开头文件也必须由对象属性处理。", | ||
| "objective": "无需字符串猜测,即可正确处理普通文件、多重扩展名和点开头文件。", | ||
| "language_delta": "纯路径类只执行词法运算;具体的 `Path` 还提供文件系统访问,并遵循宿主平台的路径风格。", | ||
| "prediction_prompt": "执行前判断 `PurePosixPath(\"a/bundle.min.js\")` 的 `stem` 与 `suffix`。", | ||
| "transfer_trap": "传给 `PurePosixPath` 的 Windows 分隔符会被当成普通字符,因此路径风格必须匹配数据契约。" | ||
| }, | ||
| "py-lists-dicts": { | ||
| "title": "列表和字典", | ||
| "concept": "列表按顺序保存值,字典把键映射到值。真实题目中,如果数据由姓名、字符或 ID 标识,直接按键查找通常比手动扫描位置更清楚。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示列表和字典的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-control-flow": { | ||
| "title": "分支、循环与范围边界", | ||
| "concept": "Python 以缩进决定循环体和条件体。`range` 不包含第二个参数,要覆盖上界就必须调整停止值,然后再让奇偶条件筛选进入累加器的数字。", | ||
| "worked_example": "停止值设为 8 后,示例从 1 遍历到 7。只有 1、3、5、7 进入累加,总和为 16。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把列表和字典的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决列表和字典练习里的 TODO。", | ||
| "没有确认“列表按顺序保存值,字典把键映射到值。真实题目中,如果数据由姓名、字符或 ID 标识,直接按键查找通常比手动扫描位置更清楚。”这个要点,只去凑 judge 输出。" | ||
| "直接把上界交给 `range`,悄悄漏掉恰好为奇数的终点。", | ||
| "把累加语句缩进到条件外,使每个数都被加入。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用列表和字典,然后才到达 `example:` 输出?", | ||
| "列表和字典练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`range(1, 4)` 会依次产生哪些整数?", | ||
| "累加语句应处于哪一级缩进,才能只在条件成立时执行?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把列表和字典应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "遍历从一到输入上界的闭区间,只把奇数加入累加器并输出最终总和;零上界不得进入循环,奇数终点不得被遗漏。", | ||
| "objective": "在零边界、奇数终点和偶数终点下都能得到正确的奇数总和。", | ||
| "language_delta": "Python 条件可利用整数的真假性,缩进又直接参与语法;Rust 和 Java 则要求条件值明确为布尔类型。", | ||
| "prediction_prompt": "当上界为 6 时,列出所有会进入累加器的值,再算出最终结果。", | ||
| "transfer_trap": "花括号语言不以缩进划分代码块,其他范围接口也可能包含终点;照搬边界常造成差一错误。" | ||
| }, | ||
| "py-tuples-sets": { | ||
| "title": "元组和集合", | ||
| "concept": "元组适合保存位置固定的小记录,集合保存唯一成员并能快速判断是否存在。若输出顺序重要,不要只依赖集合。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示元组和集合的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-functions": { | ||
| "title": "检查点:返回矩形面积", | ||
| "concept": "面积函数只负责一项计算并返回结果;解析留在外部,调用方独自输出。这样的函数可被测试、导入或包装,不会夹带隐藏的终端文本。", | ||
| "worked_example": "`area(8, 2)` 将两个参数相乘并返回 16,外层 `print` 再消费这个整数。函数体本身没有写入标准输出。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把元组和集合的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决元组和集合练习里的 TODO。", | ||
| "没有确认“元组适合保存位置固定的小记录,集合保存唯一成员并能快速判断是否存在。若输出顺序重要,不要只依赖集合。”这个要点,只去凑 judge 输出。" | ||
| "在函数内部打印,又让调用方打印其返回的 `None`。", | ||
| "因为第一个案例看似合理而误把宽加高当成面积。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用元组和集合,然后才到达 `example:` 输出?", | ||
| "元组和集合练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "矩形任一边长为零时,函数应返回什么值?", | ||
| "示例中唯一负责标准输出的是哪一行?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把元组和集合应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "完成返回数值的面积计算,并让输入解析与最终输出继续留在调用位置;函数体不得自行打印,也要正确处理零边长。", | ||
| "objective": "通过一个可复用函数处理三组矩形尺寸,包括某一维为零的边界。", | ||
| "language_delta": "普通 Python 函数若没有执行到带值的 `return` 就返回 `None`;这与某些语言的表达式函数自动返回规则不同。", | ||
| "prediction_prompt": "分别预测 `area(7, 1)`,以及函数体只调用 `print` 而不写 `return` 时调用所得的值。", | ||
| "transfer_trap": "不要把表达式函数的隐式返回习惯带进普通 `def`;输出一个值也不等于把它返回给调用者。" | ||
| }, | ||
| "py-comprehensions": { | ||
| "title": "推导式", | ||
| "concept": "推导式用输出表达式、循环和可选过滤条件创建集合。简单映射和过滤很适合,包含多步副作用时普通循环更清楚。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示推导式的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-modules-imports": { | ||
| "title": "通过模块显示依赖来源", | ||
| "concept": "`import` 会按需执行模块加载,并在当前命名空间绑定名称。调用时保留 `math` 限定符能清楚表明 `ceil` 的来源,也避免星号导入造成名称冲突。", | ||
| "worked_example": "`math.ceil(-3.8)` 返回大于等于输入的最小整数 -3。对这个负数而言,向上取整朝零移动,与向下取整的方向不同。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把推导式的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决推导式练习里的 TODO。", | ||
| "没有确认“推导式用输出表达式、循环和可选过滤条件创建集合。简单映射和过滤很适合,包含多步副作用时普通循环更清楚。”这个要点,只去凑 judge 输出。" | ||
| "因两者都被口语称作取整而选择 `math.floor`,负数案例立即出错。", | ||
| "使用星号导入,失去 `ceil` 与 `math` 模块之间可见的联系。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用推导式,然后才到达 `example:` 输出?", | ||
| "推导式练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "为什么 `math.ceil(-2.1)` 是 -2 而不是 -3?", | ||
| "执行 `import math` 后,当前作用域新增了哪个绑定?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把推导式应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "解析浮点输入,并通过模块命名空间调用朝正无穷取整的标准库操作;负数与已有整数的案例都应沿用同一调用路径。", | ||
| "objective": "使用标准库正确处理正数、负数和本来就是整数的浮点输入。", | ||
| "language_delta": "同一进程中 Python 会在 `sys.modules` 缓存已加载模块,但每条导入语句仍会在自身作用域执行名称绑定。", | ||
| "prediction_prompt": "不运行程序,比较 `math.ceil(-0.2)` 与 `math.floor(-0.2)`。", | ||
| "transfer_trap": "Java 包导入和 JavaScript 模块加载采用不同运行模型;相似关键字并不意味着 Python 的执行与缓存语义。" | ||
| }, | ||
| "py-errors": { | ||
| "title": "异常", | ||
| "concept": "异常把正常路径和可恢复失败分开。`try` 应尽量小,只捕获像 `ValueError` 这样明确能恢复的异常。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示异常的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-lambdas-closures": { | ||
| "title": "闭包捕获绑定单元而非快照", | ||
| "concept": "闭包在外层调用结束后仍能访问其中的名称。自由名称在内层函数执行时解析,因此同一循环创建的多个匿名函数通常共享一个绑定单元,而不是各存一份历史值。", | ||
| "worked_example": "三个读取函数在 `index` 变化期间创建,却都在循环完成后执行。每次调用都读取最终值 2,所以列表结果是 `[2, 2, 2]`。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把异常的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决异常练习里的 TODO。", | ||
| "没有确认“异常把正常路径和可恢复失败分开。`try` 应尽量小,只捕获像 `ValueError` 这样明确能恢复的异常。”这个要点,只去凑 judge 输出。" | ||
| "以为循环中的每个 `lambda` 会自动保存创建那一刻的迭代值。", | ||
| "用默认参数修复问题,却不理解默认值是在函数定义时求值并冻结的。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用异常,然后才到达 `example:` 输出?", | ||
| "异常练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "示例里的自由名称 `index` 在创建时还是调用时查找?", | ||
| "改成 `lambda index=index: index` 后,三个结果为何不同?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把异常应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "让工厂返回一个加法闭包,使其调用时读取该次工厂调用创建的 `delta` 绑定;工厂返回后,正负增量仍须独立生效。", | ||
| "objective": "工厂返回后,仍能让正数、负数与零增量各自作用于传入值。", | ||
| "language_delta": "`lambda` 只是单表达式函数写法,与嵌套 `def` 遵循相同的词法名称解析和闭包单元模型。", | ||
| "prediction_prompt": "预测 `[lambda n=n: n for n in range(3)]` 生成的函数依次调用后形成的列表。", | ||
| "transfer_trap": "JavaScript 块级循环常为每轮产生独立绑定,不能据此推断 Python 循环变量也会自动形成快照。" | ||
| }, | ||
| "py-files-context": { | ||
| "title": "文件和上下文管理器", | ||
| "concept": "`with` 进入受管理的作用域,离开时运行清理逻辑。文件对象和 contextlib 工具用这个模式确保资源会被关闭。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示文件和上下文管理器的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-decorators": { | ||
| "title": "定义执行时应用装饰器", | ||
| "concept": "执行 `def` 时,函数对象先创建,随后装饰表达式接收它,并把名称重新绑定到返回的包装函数。`functools.wraps` 复制识别元数据,包装行为则在每次调用时发生。", | ||
| "worked_example": "`tag` 只创建一次保留元数据的包装层。调用 `label(\"Mina\")` 时,包装层把原返回值转成大写,再加上尖括号。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把文件和上下文管理器的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决文件和上下文管理器练习里的 TODO。", | ||
| "没有确认“`with` 进入受管理的作用域,离开时运行清理逻辑。文件对象和 contextlib 工具用这个模式确保资源会被关闭。”这个要点,只去凑 judge 输出。" | ||
| "在构造装饰器时就调用原函数,而不是返回一个可调用包装层。", | ||
| "省略 `functools.wraps`,让帮助信息或反射工具看到错误的名称与文档。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用文件和上下文管理器,然后才到达 `example:` 输出?", | ||
| "文件和上下文管理器练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪个动作只在 `def` 执行时发生,哪个转换会在每次调用时重复?", | ||
| "为什么包装函数必须接收调用者传给被装饰函数的参数?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把文件和上下文管理器应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "补全包装层:保留原函数元数据,在调用时把原返回文本大写并加上方括号;不得改动被装饰函数的函数体。", | ||
| "objective": "不改被装饰函数体,也能正确转换拉丁字母和重音字符的调用结果。", | ||
| "language_delta": "Python 装饰器是与 `def` 语法结合的普通可调用对象应用;`wraps` 只复制选定属性,不改变包装层的执行逻辑。", | ||
| "prediction_prompt": "若装饰器打印“decorate”,包装层打印“call”,两段文本分别会在定义执行和函数调用的哪个阶段出现?", | ||
| "transfer_trap": "Java 注解主要承载元数据,并不会自动创建可调用包装层;相同的 `@` 外形不代表相同运行行为。" | ||
| }, | ||
| "py-modules-imports": { | ||
| "title": "模块和 import", | ||
| "concept": "模块把可复用代码组织在命名空间中。`import math` 让标准库模块可用,而 PEP 8 建议把 import 放在文件顶部以便先看到依赖。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示模块和 import的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-async": { | ||
| "title": "用 `asyncio` 驱动协程", | ||
| "concept": "调用 `async def` 只创建协程对象,不会自动执行到结束。`asyncio.run` 提供事件循环,`gather` 安排多个可等待对象,`await asyncio.sleep(0)` 则产生真实的让出点。", | ||
| "worked_example": "两个大写转换协程都会到达暂停点。`gather` 等待全部完成,并按传入顺序返回结果,因此即使完成顺序交错,最终仍输出 `GO NOW`。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把模块和 import的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决模块和 import练习里的 TODO。", | ||
| "没有确认“模块把可复用代码组织在命名空间中。`import math` 让标准库模块可用,而 PEP 8 建议把 import 放在文件顶部以便先看到依赖。”这个要点,只去凑 judge 输出。" | ||
| "把调用协程得到的对象误当作已经算出的结果。", | ||
| "在异步函数中使用阻塞式睡眠,冻结事件循环所在的线程。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用模块和 import,然后才到达 `example:` 输出?", | ||
| "模块和 import练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`main()` 创建协程后,哪个组件真正反复推进它?", | ||
| "`asyncio.gather` 的返回顺序依据完成先后还是参数位置?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把模块和 import应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "为每个输入整数创建一个加倍协程,通过 `gather` 统一等待,再按输入顺序输出所有结果。", | ||
| "objective": "多值、有符号值和空输入都能经过真实暂停点完成,并且不产生未等待协程警告。", | ||
| "language_delta": "`asyncio` 采用协作式调度,只在等待点切换任务;单独调用协程既不会安排它,也不会执行函数体。", | ||
| "prediction_prompt": "当加倍协程内部先等待零秒时,预测 `await asyncio.gather(double(3), double(-1))` 返回的列表。", | ||
| "transfer_trap": "JavaScript 返回 `Promise` 的函数具有不同启动规则,不要把其较早执行的直觉套到 Python 协程调用。" | ||
| }, | ||
| "py-dataclasses": { | ||
| "title": "数据类", | ||
| "concept": "`@dataclass` 为主要保存数据的类生成常规方法。字段名、类型提示和构造方式集中在类定义中,避免手写样板代码。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示数据类的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-lists-dicts": { | ||
| "title": "按字典键分组列表值", | ||
| "concept": "字典的每个键对应一份独立分数列表。`setdefault` 只在缺键时建立桶,`get(query, [])` 则让未出现的查询名称得到空序列,其总和自然为零。", | ||
| "worked_example": "Mina 映射到 `[8, 1]`,查出后求和得到 9。Kai 对应另一份列表,说明不同键不会共享同一个分组桶。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把数据类的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决数据类练习里的 TODO。", | ||
| "没有确认“`@dataclass` 为主要保存数据的类生成常规方法。字段名、类型提示和构造方式集中在类定义中,避免手写样板代码。”这个要点,只去凑 judge 输出。" | ||
| "直接读取 `scores[query]`,却没有考虑查询名称可能从未出现在数据对中。", | ||
| "让所有键复用同一列表,导致某个人的分数出现在其他名称下。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用数据类,然后才到达 `example:` 输出?", | ||
| "数据类练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "输入中的第一个片段扮演什么角色,姓名与分数对从哪里开始?", | ||
| "为什么缺失查询的空列表求和会得到零而不是异常?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把数据类应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "从查询名称后的片段开始,每次读取一组姓名和分数,加入对应列表,最后汇总被查询分组;查询缺失时必须得到零而非异常。", | ||
| "objective": "重复姓名、有符号分数以及完全缺失的查询都能得到正确分组总和。", | ||
| "language_delta": "普通字典配合列表最直接地表达按键累加;`Counter`、`defaultdict` 与 `deque` 分别适合频率、自动分组和双端序列。", | ||
| "prediction_prompt": "查询 Bo 而数据中只有 Ada 与 Lin 时,预测 `scores.get(query, [])` 及其总和。", | ||
| "transfer_trap": "JavaScript 普通对象受原型键影响;Python 字典接受任意可哈希键,而且 `get` 查询缺键时不会插入新项。" | ||
| }, | ||
| "py-typing": { | ||
| "title": "类型提示", | ||
| "concept": "类型提示向读者、编辑器和类型检查器描述期望形状。Python 运行时仍然是动态的,所以函数体也必须符合注解表达的契约。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示类型提示的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-sorting-keys": { | ||
| "title": "多字段排序键", | ||
| "concept": "Python 从左到右比较元组键。对分数取负可让高分排前,保持姓名原值则只在分数相同时按字母升序决定次序。", | ||
| "worked_example": "Bo 的分数 9 先赢得主排序;Mina 与 Ana 同为 7 分时,第二字段会让 Ana 排在 Mina 前。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把类型提示的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决类型提示练习里的 TODO。", | ||
| "没有确认“类型提示向读者、编辑器和类型检查器描述期望形状。Python 运行时仍然是动态的,所以函数体也必须符合注解表达的契约。”这个要点,只去凑 judge 输出。" | ||
| "对分数与姓名组成的整体设置反向排序,连姓名的平局规则也一并颠倒。", | ||
| "依赖稳定排序保留输入次序,却忽略需求已经指定了第二排序字段。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用类型提示,然后才到达 `example:` 输出?", | ||
| "类型提示练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "Zed:5 与 Amy:5 分别产生什么排序键?", | ||
| "为何只按姓名排序会漏掉后来出现的更高分记录?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把类型提示应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "解析每条姓名与分数记录,按分数降序、姓名升序排列,并选择第一项输出;平分案例不能依赖原输入次序。", | ||
| "objective": "不同分数、相同分数和单条负分记录都能选出确定且正确的优胜项。", | ||
| "language_delta": "`sorted` 返回新列表并保证稳定;键函数每个元素只计算一次比较数据,而不是充当双参数比较器。", | ||
| "prediction_prompt": "在键 `(-score, name)` 下,判断 `(\"Zed\", 5)` 与 `(\"Amy\", 5)` 谁先出现。", | ||
| "transfer_trap": "其他语言常见的减法比较器可能溢出,也没有自然表达独立的姓名平局规则;元组键同时避免两点。" | ||
| }, | ||
| "py-generators": { | ||
| "title": "迭代器和生成器", | ||
| "concept": "迭代器一次产生一个值。生成器函数使用 `yield`,调用时先返回生成器对象,只有调用者请求下一个值时才推进函数体。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示迭代器和生成器的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-counter-defaultdict": { | ||
| "title": "用 `Counter` 计数并以 `defaultdict` 分组", | ||
| "concept": "`Counter` 可直接查询频率,缺失元素得到零;`defaultdict(list)` 在索引缺键时建立独立列表,而 `get` 能检查缺失首字母且不插入新键。", | ||
| "worked_example": "五个数据词中 owl 出现两次,且所有词都以 o 开头,所以对应分组长度为五。示例输出的是频率与同首字母组大小两种统计。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把迭代器和生成器的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决迭代器和生成器练习里的 TODO。", | ||
| "没有确认“迭代器一次产生一个值。生成器函数使用 `yield`,调用时先返回生成器对象,只有调用者请求下一个值时才推进函数体。”这个要点,只去凑 judge 输出。" | ||
| "把开头的查询词也算进数据,导致频率被人为增加。", | ||
| "只为查看缺失组就使用索引,从而意外改变 `defaultdict`。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用迭代器和生成器,然后才到达 `example:` 输出?", | ||
| "迭代器和生成器练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "为何 `Counter(words)[\"absent\"]` 不经成员判断也会返回零?", | ||
| "索引和 `get` 中,哪个操作会创建 `defaultdict` 的新桶?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把迭代器和生成器应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "统计查询词之后的所有数据词,并按首字符分组,再报告查询频率与对应首字符组大小;检查缺失组时不得插入新键。", | ||
| "objective": "查询存在、重复出现或完全缺失时,都能同时给出两项准确统计。", | ||
| "language_delta": "这些容器细化而非替代字典:`Counter` 提供数值默认值,`defaultdict` 把缺键值的创建交给工厂。", | ||
| "prediction_prompt": "查询 x、数据为 a 和 b 时,判断 `groups.get(\"x\", [])` 是否会插入 x 键。", | ||
| "transfer_trap": "普通字典缺键索引仍会抛出 `KeyError`;零值和工厂行为只属于相应容器,不能推广到所有映射。" | ||
| }, | ||
| "py-lambdas-closures": { | ||
| "title": "lambda 和闭包", | ||
| "concept": "`lambda` 创建只有一个表达式的小函数。当这个函数使用外层函数的名字时,它形成闭包并在外层调用结束后仍记住该值。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示lambda 和闭包的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-deque": { | ||
| "title": "双端常数时间操作", | ||
| "concept": "`deque` 针对序列两端的添加和删除优化。`appendleft` 与 `popleft` 操作左端,`append` 与 `pop` 操作右端。", | ||
| "worked_example": "从 `center` 开始,先将 `first` 放到左端,再将 `last` 放到右端。随后两端各取一个会输出 `first last`,中间值仍留在队列里。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把lambda 和闭包的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决lambda 和闭包练习里的 TODO。", | ||
| "没有确认“`lambda` 创建只有一个表达式的小函数。当这个函数使用外层函数的名字时,它形成闭包并在外层调用结束后仍记住该值。”这个要点,只去凑 judge 输出。" | ||
| "两项都用 `append` 加入,使原本的中间值仍处于最左端。", | ||
| "把列表的 `pop(0)` 当作增长队列的等价操作,忽视其移动后续元素的代价。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用lambda 和闭包,然后才到达 `example:` 输出?", | ||
| "lambda 和闭包练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "执行 `appendleft(left)` 后,`popleft` 会返回哪个值?", | ||
| "哪一个双端队列操作会删除最右项?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把lambda 和闭包应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把第二个输入放在左端、第三个放在右端,随后删除并按左右顺序输出这两个端点;原先的中间元素应继续留在队列。", | ||
| "objective": "保留中间元素,同时对拉丁字母和希腊字母端点都维持正确顺序。", | ||
| "language_delta": "双端操作适合反复消费搜索前沿;随机索引仍是列表的优势,并不是 `deque` 的设计重点。", | ||
| "prediction_prompt": "逐步追踪 `deque([\"m\"])` 经左加 l、右加 r、左删和右删后的每个返回值。", | ||
| "transfer_trap": "其他标准库队列可能使用 `offer` 或 `poll` 等名称;迁移时应对应左右端语义,而非逐字翻译方法名。" | ||
| }, | ||
| "py-decorators": { | ||
| "title": "装饰器", | ||
| "concept": "装饰器在函数定义时运行。它接收原始函数对象,并返回最终绑定到函数名上的对象。 这个返回值决定之后调用函数名时执行什么。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示装饰器的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-comprehensions": { | ||
| "title": "检查点:筛选、变换再求和", | ||
| "concept": "列表推导式会先为每个来源数字检查末尾条件,只对通过者计算前面的平方表达式。所得列表在 `sum` 消费前已经完整建立。", | ||
| "worked_example": "5、6、7、8 中只有 6 和 8 通过偶数判断,平方后形成 `[36, 64]`,总和为 100。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把装饰器的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决装饰器练习里的 TODO。", | ||
| "没有确认“装饰器在函数定义时运行。它接收原始函数对象,并返回最终绑定到函数名上的对象。”这个要点,只去凑 judge 输出。" | ||
| "把奇偶谓词写反,程序安静地改为累加奇数平方。", | ||
| "在推导式塞入多层条件和副作用,使求值顺序难以看清。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用装饰器,然后才到达 `example:` 输出?", | ||
| "装饰器练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "被末尾条件拒绝的元素还会执行前面的输出表达式吗?", | ||
| "按偶数平方规则处理 `[-2, -1, 0]` 会建立什么列表?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把装饰器应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "用一条清晰的列表推导式收集所有偶数的平方,再输出该列表的总和;全为奇数时应建立空列表并自然求得零。", | ||
| "objective": "混合正负数以及没有偶数的输入,都能体现先筛选后变换的行为。", | ||
| "language_delta": "生成器表达式会惰性地产生平方,`itertools` 也可组合单次流;此检查点刻意建立列表,让立即物化可被观察。", | ||
| "prediction_prompt": "在调用 `sum` 前,写出输入 `[1, 2, 3, 4]` 形成的确切中间列表。", | ||
| "transfer_trap": "JavaScript 数组回调与 Java 流有相似阶段却求值时机不同;方括号形式的 Python 推导式并不惰性。" | ||
| }, | ||
| "py-sorting-keys": { | ||
| "title": "排序和 key 函数", | ||
| "concept": "`sorted` 返回新的有序列表。`key` 函数为每个元素取出比较值,因此可以按元组或对象的某个字段排序。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示排序和 key 函数的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-generators": { | ||
| "title": "惰性倒计时生成器", | ||
| "concept": "含 `yield` 的函数在调用时返回生成器迭代器。函数体到第一次推进才开始,每次产出后保存局部状态并暂停,所有路径结束时产生 `StopIteration`。", | ||
| "worked_example": "调用 `reverse_letters(\"abc\")` 起初不会遍历文本;把它展开给 `print` 后才推进三次,依次产生 c、b、a。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把排序和 key 函数的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决排序和 key 函数练习里的 TODO。", | ||
| "没有确认“`sorted` 返回新的有序列表。`key` 函数为每个元素取出比较值,因此可以按元组或对象的某个字段排序。”这个要点,只去凑 judge 输出。" | ||
| "认为仅调用生成器函数时,第一处 `yield` 之前的语句已经执行。", | ||
| "再次遍历同一个已耗尽生成器,期待旧值重新出现。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用排序和 key 函数,然后才到达 `example:` 输出?", | ||
| "排序和 key 函数练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "什么操作会让倒计时函数的循环体首次开始执行?", | ||
| "为什么展开 `countdown(0)` 仍会让外层 `print` 写出换行?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把排序和 key 函数应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "当前数为正时逐次产出它,恢复后递减保存的局部值,直到零边界停止;不得预先建立完整倒计时列表。", | ||
| "objective": "不建立中间列表,也能生成完整倒计时、单项倒计时和空序列。", | ||
| "language_delta": "生成器函数是迭代器工厂,每次调用都有新的暂停状态;普通 `return` 会结束迭代而非再产出一项。", | ||
| "prediction_prompt": "对 `countdown(3)` 首次调用 `next` 后,产出什么值,恢复执行时局部数字又是多少?", | ||
| "transfer_trap": "JavaScript 生成器返回带完成标记的迭代结果对象;Python 的 `next` 直接给出值,结束时抛出 `StopIteration`。" | ||
| }, | ||
| "py-counter-defaultdict": { | ||
| "title": "Counter 和 defaultdict", | ||
| "concept": "`Counter` 统计可哈希值,`defaultdict(list)` 为新分组自动创建空列表。它们能减少计数和分组时的手动初始化。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示Counter 和 defaultdict的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-itertools": { | ||
| "title": "惰性展平多个可迭代值", | ||
| "concept": "`chain.from_iterable` 接收一个外层可迭代对象,并依次推进每个内层对象。它只保留当前组所需状态,不必先复制出完整的扁平列表。", | ||
| "worked_example": "外层元组包含 `py` 与 `thon`。链先遍历第一段字符,再遍历第二段,最后由 `join` 消费单次流并组成 `python`。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把Counter 和 defaultdict的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决Counter 和 defaultdict练习里的 TODO。", | ||
| "没有确认“`Counter` 统计可哈希值,`defaultdict(list)` 为新分组自动创建空列表。它们能减少计数和分组时的手动初始化。”这个要点,只去凑 judge 输出。" | ||
| "使用 `chain(groups)`,得到整段字符串而不是各段内部字符。", | ||
| "为调试先消费链的一部分,随后误以为 `join` 会从开头重启。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用Counter 和 defaultdict,然后才到达 `example:` 输出?", | ||
| "Counter 和 defaultdict练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "此处 `chain.from_iterable` 接收几个外层参数?", | ||
| "扁平迭代器的首字符已被 `next` 取走后,还剩哪些内容?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把Counter 和 defaultdict应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把全部输入字符组交给 `chain.from_iterable`,再连接这条只能向前消费一次的字符流。", | ||
| "objective": "不构造嵌套扁平列表,也能正确连接长度不同的所有字符组。", | ||
| "language_delta": "`itertools` 操作返回迭代器并延迟工作,直到消费者推进;这里的 `join` 是最终物化字符串的终点。", | ||
| "prediction_prompt": "先预测 `list(chain.from_iterable([\"ab\", \"c\"]))`,再思考连接后的文本。", | ||
| "transfer_trap": "其他语言的流可能缓存或重新建立;Python 迭代器通常保存可变的消费位置,不能默认重放。" | ||
| }, | ||
| "py-deque": { | ||
| "title": "deque", | ||
| "concept": "`deque` 是双端队列,左右两端 append 和 pop 都高效。BFS 队列和经常从前端删除的滑动窗口通常使用它。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示deque的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-errors": { | ||
| "title": "只从 `ValueError` 恢复", | ||
| "concept": "当文本不是整数时,`int` 抛出 `ValueError`。只把该转换放进小型 `try`,并捕获精确异常,可清楚表达备用策略而不掩盖无关程序缺陷。", | ||
| "worked_example": "转换 `oops` 触发 `ValueError`,控制流进入处理分支,再把给定备用文本 -8 转成整数并输出。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把deque的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决deque练习里的 TODO。", | ||
| "没有确认“`deque` 是双端队列,左右两端 append 和 pop 都高效。BFS 队列和经常从前端删除的滑动窗口通常使用它。”这个要点,只去凑 judge 输出。" | ||
| "包住整个程序并捕获所有异常,把无关缺陷伪装成合法备用结果。", | ||
| "把 -3 这样的带符号文本误判为非法,尽管 `int` 能正常解析。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用deque,然后才到达 `example:` 输出?", | ||
| "deque练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "第一个片段有效时,会进入 `except` 分支吗?", | ||
| "哪一条语句才是此处理器打算恢复的 `ValueError` 来源?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把deque应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "尝试转换第一个片段,仅当该转换抛出 `ValueError` 时才改用第二个片段;合法负数必须走正常路径,其他异常不得被吞掉。", | ||
| "objective": "区分合法负数与畸形文本,只在需要恢复时准确采用给定备用值。", | ||
| "language_delta": "上下文管理器即使代码块抛错也负责退出清理;`try` 选择恢复路径,`with` 管理资源生存期,两者职责不同。", | ||
| "prediction_prompt": "备用值同为 9 时,分别追踪候选文本 `-3` 与 `bad` 会经过哪个分支。", | ||
| "transfer_trap": "JavaScript 的 `parseInt(\"bad\")` 返回 `NaN` 而非抛错,照搬 `try` 策略不会在那里触发备用分支。" | ||
| }, | ||
| "py-itertools": { | ||
| "title": "itertools", | ||
| "concept": "`itertools` 提供 `chain`、`islice`、`product`、`pairwise` 等惰性迭代工具。除非被消费,它们不会构建中间列表。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示itertools的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-files-context": { | ||
| "title": "保证上下文管理器退出", | ||
| "concept": "`with` 协议通过 `__enter__` 取得受管值,并在正常或异常离开代码块时调用 `__exit__`。`StringIO` 能在不依赖真实文件的情况下展示相同读写与关闭形态。", | ||
| "worked_example": "内存句柄保存两侧带空白的 Seoul。受管区块内先读取并去除空白,离开后把已保存文本转为大写;此时句柄本身已经关闭。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把itertools的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决itertools练习里的 TODO。", | ||
| "没有确认“`itertools` 提供 `chain`、`islice`、`product`、`pairwise` 等惰性迭代工具。除非被消费,它们不会构建中间列表。”这个要点,只去凑 judge 输出。" | ||
| "离开 `with` 后再次读取句柄,却忘记 `StringIO` 资源已经关闭。", | ||
| "认为 `__exit__` 只在成功时运行,于是在异常分支重复编写清理。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用itertools,然后才到达 `example:` 输出?", | ||
| "itertools练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪个读取动作必须在缩进离开受管区块前完成?", | ||
| "资源内容全为空白时,规范化后剩下什么值?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把itertools应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "在上下文管理器内读取并去除文本两侧空白,离开后大写输出;没有内容时输出约定的空值名称,且关闭后不再访问句柄。", | ||
| "objective": "普通文本、两侧带空白的文本和空输入都能体现资源必然退出且结果仍可使用。", | ||
| "language_delta": "`with` 把进入与退出委托给对象协议,而非硬编码文件;读入普通变量的文本在资源关闭后仍然有效。", | ||
| "prediction_prompt": "若 `handle.read()` 得到两个空格,预测去除空白后的值以及大写值与 `EMPTY` 之间的选择。", | ||
| "transfer_trap": "Java 的资源语句和 JavaScript 的资源协议各有不同约定;Python 明确调用 `__enter__` 与 `__exit__`。" | ||
| }, | ||
| "py-pathlib": { | ||
| "title": "pathlib", | ||
| "concept": "`pathlib` 把路径表示为对象。`name`、`stem`、`suffix`、`parent` 等属性能避免手动拆字符串,并说明需要路径的哪一部分。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示pathlib的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-dataclasses": { | ||
| "title": "类与自动生成的数据模型方法", | ||
| "concept": "类通过方法提供实例行为,`@dataclass` 则依据声明字段生成初始化、表示与相等比较。它不会递归冻结嵌套值,字段注解也不会自动成为运行时验证器。", | ||
| "worked_example": "`Point(4, 5)` 保存两个字段,`total` 返回 9;另一个字段值相同的点会被自动生成的相等方法判定为 `True`。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把pathlib的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决pathlib练习里的 TODO。", | ||
| "没有确认“`pathlib` 把路径表示为对象。`name`、`stem`、`suffix`、`parent` 等属性能避免手动拆字符串,并说明需要路径的哪一部分。”这个要点,只去凑 judge 输出。" | ||
| "以为自动相等或冻结选项会递归冻结字段中的可变对象。", | ||
| "声明 `values: list[int] = []` 会因可变数据类默认值被拒绝;应以 `field(default_factory=list)` 为每个实例新建,只有有意共享时才用 `ClassVar`。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用pathlib,然后才到达 `example:` 输出?", | ||
| "pathlib练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "比较两个 `Point` 实例时,实际使用哪种相等实现?", | ||
| "声明 `x: int` 会在运行时阻止构造 `Point(\"wrong\", 5)` 吗?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把pathlib应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "让 `Point.total` 同时使用两个实例字段,再观察方法结果与数据类生成的相等比较。", | ||
| "objective": "构造相等的点,并通过显式实例行为正确计算正负坐标总和。", | ||
| "language_delta": "普通 Python 执行不会强制字段注解;数据类只减少模型样板,不会代替领域方法、验证或深层不可变性。", | ||
| "prediction_prompt": "先判断 `Point(2, 3) == Point(2, 3)`,再说明改变一个字段会怎样影响结果。", | ||
| "transfer_trap": "Java `record` 的组件引用不可重新赋值,但只保证浅层不可变;普通 Python 数据类默认仍可修改。" | ||
| }, | ||
| "py-testing-assert": { | ||
| "title": "测试和 assert", | ||
| "concept": "`assert` 在小例子或测试中检查条件。测试框架把这些检查组织成可重复运行的函数,但核心仍是比较期望行为和实际行为。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示测试和 assert的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-typing": { | ||
| "title": "检查注解而不误认运行时强制", | ||
| "concept": "函数注解是供读者、编辑器与可选静态检查器使用的元数据。`get_type_hints` 能在运行时解析记录的对象,却不会转换参数,也不会阻止值与注解不符的调用。", | ||
| "worked_example": "`head` 记录了 `Iterable[str]` 和 `str` 注解。反射可识别返回类型,而函数体则独立从给定列表取出 `elm`。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把测试和 assert的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决测试和 assert练习里的 TODO。", | ||
| "没有确认“`assert` 在小例子或测试中检查条件。测试框架把这些检查组织成可重复运行的函数,但核心仍是比较期望行为和实际行为。”这个要点,只去凑 judge 输出。" | ||
| "认为 `int` 注解会在函数体运行前自动拒绝字符串。", | ||
| "把读取承诺类型的元数据误当成验证所有返回路径。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用测试和 assert,然后才到达 `example:` 输出?", | ||
| "测试和 assert练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`get_type_hints(total)[\"return\"]` 会暴露哪个对象?", | ||
| "真正执行加法的是注解还是函数的返回表达式?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把测试和 assert应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "保留 `Iterable[int]` 到 `int` 的契约,汇总完整可迭代值,并依据解析后的注解标记结果。", | ||
| "objective": "验证注解元数据,同时正确计算多项、单项与空可迭代输入的总和。", | ||
| "language_delta": "Python 即使没有运行类型检查器也会执行注解代码;TypeScript 同样在运行时擦除类型,但通常先由工具链检查源代码。", | ||
| "prediction_prompt": "把 `[\"2\"]` 传给 `total` 时,注解会不会拒绝调用,最终又是哪项运算决定结果?", | ||
| "transfer_trap": "注解描述期望值,却不会解析输入字符串、执行类型强制转换或提供运行时重载选择。" | ||
| }, | ||
| "py-async": { | ||
| "title": "异步概念", | ||
| "concept": "`async def` 创建协程函数。调用它得到协程对象;在另一个协程中用 `await` 取得结果,文件边界用 `asyncio.run` 驱动最外层协程。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示异步概念的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "py-testing-assert": { | ||
| "title": "综合:确定性的词频排名", | ||
| "concept": "最终程序先物化可迭代词语、统计频率并检查开发期不变量,再按次数降序与单词升序排列条目。空输入有明确结果,不依赖异常。", | ||
| "worked_example": "pear 与 plum 都出现两次,仅凭频率无法决定。按字母升序的第二字段选出 pear,计数不变量成立后输出 `pear:2`。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把异步概念的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决异步概念练习里的 TODO。", | ||
| "没有确认“`async def` 创建协程函数。调用它得到协程对象;在另一个协程中用 `await` 取得结果,文件边界用 `asyncio.run` 驱动最外层协程。”这个要点,只去凑 judge 输出。" | ||
| "只用 `Counter.most_common(1)`,使同频结果依赖最先出现的顺序。", | ||
| "用 `assert` 保护输入或安全条件,忽略优化模式会删除断言字节码。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用异步概念,然后才到达 `example:` 输出?", | ||
| "异步概念练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "排序键包含哪两个字段,次数为何要取负?", | ||
| "解释器以 `-O` 启动时,`assert` 语句会发生什么?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把异步概念应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把依赖遇见顺序的同频选择改为次数与单词共同决定的排名,同时保留类型化函数、计数器和内部不变量。", | ||
| "objective": "选择出现最多的词,同频时按字母升序决定,并为无词输入返回约定结果。", | ||
| "language_delta": "断言只是可选开发检查,优化模式下会消失,不能承载必要行为;综合题还复用了精确输出、解析、集合与显式排序键。", | ||
| "prediction_prompt": "对输入 `b a b a` 列出计数条目,并按 `(-count, word)` 判断最终优胜词。", | ||
| "transfer_trap": "Java 断言也可能关闭,虽然开关与运行模型不同;两个语言都应以普通验证实现必须始终执行的条件。" | ||
| } | ||
| } | ||
| } |
+29
-12
@@ -1,20 +0,14 @@ | ||
| # Lesson Catalog Assets | ||
| # Lesson Catalog Contract | ||
| Lesson copy is split first by programming language, then by UI language: | ||
| Practicode embeds four executable courses and five complete prose catalogs per course: | ||
| ```text | ||
| assets/lessons/<programming-language>/course.json | ||
| assets/lessons/<programming-language>/<ui-language>.json | ||
| ``` | ||
| Example: | ||
| Programming-language directories are `python`, `typescript`, `java`, and `rust`. UI catalogs are `en`, `ko`, `ja`, `zh`, and `es`. The TypeScript runtime key remains `ts`. | ||
| ```text | ||
| assets/lessons/python/ko.json | ||
| assets/lessons/typescript/en.json | ||
| ``` | ||
| `course.json` owns ordered IDs, core/lab classification, examples, starters, deterministic cases, and official references. A locale file owns exactly these ten fields for every course ID: | ||
| Each file follows `schema.json` and contains exactly one programming language plus one UI language. When adding a programming language such as Go, add a new directory with `en.json`, `ko.json`, `ja.json`, `zh.json`, and `es.json`. | ||
| Required lesson copy fields: | ||
| - `title` | ||
@@ -26,3 +20,26 @@ - `concept` | ||
| - `exercise_prompt` | ||
| - `objective` | ||
| - `language_delta` | ||
| - `prediction_prompt` | ||
| - `transfer_trap` | ||
| The Rust loader treats these fields as required. Missing study copy should fail tests instead of falling back to generic text at runtime. | ||
| There is no generic lesson fallback. Missing, extra, reordered, or templated copy fails the content suite. | ||
| ## Quality Gate | ||
| Every exercise must have three to five bounded cases with distinct inputs and outputs. References must pass; starters, visible-output hardcodes, and declared semantic mutants must fail through the expected compiler/type/runtime/output boundary. | ||
| Every final catalog is recorded in `review-manifest.json` with its exact SHA-256 hash, full ID coverage, official source set, distinct author and blind-verifier identities, resolved disagreements, and zero open high-severity findings. The manifest covers 20 catalogs and 550 localized lesson records. | ||
| After an independently reviewed lesson change: | ||
| ```bash | ||
| node scripts/check-lessons.js --refresh | ||
| node scripts/check-lessons.js | ||
| cargo test --test i18n | ||
| cargo test --test lesson_quality -- --test-threads=1 | ||
| ``` | ||
| Inspect the manifest diff. `--refresh` only updates mechanical hashes, IDs, counts, and source coverage; it does not replace an independent review or approve prose. | ||
| Prefer official, version-specific primary documentation. Keep code identifiers and operators in backticks, localize explanatory prose, and ensure each mistake/check is specific to the actual starter and cases. |
+372
-285
@@ -7,437 +7,524 @@ { | ||
| "rust-output": { | ||
| "title": "Output", | ||
| "concept": "Use Output to practice formatting values with println! and matching judge-exact stdout. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Output to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Make stdout byte-exact", | ||
| "concept": "The println! macro validates its format arguments during compilation and appends exactly one line feed after the rendered text.", | ||
| "worked_example": "The sample combines a tool name and an edition number with a hyphen, demonstrating captured format arguments without copying the exercise answer.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Output rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Output exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Output to practice formatting values with println!" | ||
| "Calling println! as though it were a function omits the required exclamation mark.", | ||
| "Adding a space around the colon changes judged stdout even though the values are correct." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Output before the `example:` output?", | ||
| "Which TODO line in the Output exercise must derive the value from its own data instead of copying the demo?" | ||
| "Can you point to the characters produced by the format string and the newline added by the macro?", | ||
| "Would print! or println! be appropriate if the required output had no trailing line break?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Output to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Read a name and score, then render the two fields with the exact delimiter shown by the cases.", | ||
| "objective": "Produce a compile-checked formatted line whose bytes match the required stdout.", | ||
| "language_delta": "Unlike string concatenation in many languages, Rust formatting keeps the template and typed arguments separate and validates their relationship.", | ||
| "prediction_prompt": "Before running, predict whether punctuation outside the braces is repeated for every substituted value.", | ||
| "transfer_trap": "Do not transfer the idea that println! is an ordinary variadic function; it is a macro expansion with a statically known format string." | ||
| }, | ||
| "rust-variables": { | ||
| "title": "Bindings and mutability", | ||
| "concept": "Use Bindings and mutability to practice choosing immutable let bindings first and using mut only where a value really changes. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Bindings and mutability to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Track changing state with one mutable binding", | ||
| "concept": "A mut binding permits assignment to the same storage. A later let with the same name is shadowing: it introduces a new binding rather than mutating the old one.", | ||
| "worked_example": "The demonstration updates value once and then shadows it with a transformed result, making both mechanisms visible in a six-line program.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Bindings and mutability rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Bindings and mutability exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Bindings and mutability to practice choosing immutable let bindings first and using mut only where a value really changes." | ||
| "Writing value += 1 without mut is rejected because the original binding cannot be assigned.", | ||
| "Assuming shadowing performs an in-place update obscures when the old binding stops being accessible." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Bindings and mutability before the `example:` output?", | ||
| "Which TODO line in the Bindings and mutability exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which line in the sample mutates storage, and which line creates a new name binding?", | ||
| "Does the immutable label need mut merely because it is printed after the accumulator changes?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Bindings and mutability to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Keep the label fixed while summing every following integer into a mutable accumulator.", | ||
| "objective": "Distinguish an intentionally mutable running total from immutable contextual data.", | ||
| "language_delta": "Rust makes binding mutability explicit; shadowing can change type without declaring a variable mutable.", | ||
| "prediction_prompt": "Predict the total after each loop iteration before checking the final formatted line.", | ||
| "transfer_trap": "The numeric tuple lab extends this rule with inferred and explicit number types, tuple products, and the contrast between mut and shadowing." | ||
| }, | ||
| "rust-numbers-tuples": { | ||
| "title": "Numbers and tuples", | ||
| "concept": "Use Numbers and tuples to practice combining explicit numeric types with tuple field access such as pair.0 and pair.1. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Numbers and tuples to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Observe signed division through a tuple", | ||
| "concept": "For valid i64 operands, slash truncates the quotient toward zero and percent returns the corresponding remainder. The divisor is nonzero and the i64::MIN / -1 overflow pair is excluded.", | ||
| "worked_example": "Dividing twenty-three by four supplies a positive quotient and remainder that can be inspected independently as result.0 and result.1.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Numbers and tuples rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Numbers and tuples exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Numbers and tuples to practice combining explicit numeric types with tuple field access such as pair.0 and pair.1." | ||
| "Importing Python floor-division expectations gives the wrong quotient for a negative dividend.", | ||
| "Swapping tuple indices preserves compilation while reversing the required output fields." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Numbers and tuples before the `example:` output?", | ||
| "Which TODO line in the Numbers and tuples exercise must derive the value from its own data instead of copying the demo?" | ||
| "For negative seventeen divided by five, does truncation move toward zero or negative infinity?", | ||
| "Can you verify dividend equals quotient times divisor plus remainder, and identify the excluded overflow pair?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Numbers and tuples to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Parse two integers with a nonzero divisor, excluding i64::MIN divided by -1, and print the quotient-remainder tuple in order.", | ||
| "objective": "Compute and destructure a two-value numeric result while respecting Rust signed arithmetic.", | ||
| "language_delta": "Rust integer slash differs from Python double-slash: it truncates rather than floors when signs differ.", | ||
| "prediction_prompt": "Calculate the quotient and remainder for the mixed-sign fixture before invoking rustc.", | ||
| "transfer_trap": "Do not infer floating-point division from the slash token; operand types determine integer behavior here." | ||
| }, | ||
| "rust-strings": { | ||
| "title": "Strings", | ||
| "concept": "Use Strings to practice deciding when owned String is needed and when a borrowed &str slice is enough. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Strings to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-input": { | ||
| "title": "Consume a whitespace-delimited stdin stream", | ||
| "concept": "The Read trait method fills an owned String, after which split_whitespace visits every token across arbitrary line boundaries. Empty input produces an empty iterator.", | ||
| "worked_example": "A byte slice implements Read, so the example exercises read_to_string deterministically without depending on terminal input.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Strings rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Strings exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Strings to practice deciding when owned String is needed and when a borrowed &str slice is enough." | ||
| "Reading only one line silently ignores valid tokens that follow a newline.", | ||
| "Treating parse as infallible beyond the stated valid-input contract turns malformed data into an unexplained panic." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Strings before the `example:` output?", | ||
| "Which TODO line in the Strings exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does sum over an empty iterator yield the additive identity?", | ||
| "Which imported trait makes read_to_string available on standard input?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Strings to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Read stdin to completion, parse all whitespace-separated integers, and print their sum.", | ||
| "objective": "Handle single-line, multiline, and empty valid input through one tokenization path.", | ||
| "language_delta": "Rust parsing returns Result rather than coercing text to a number; unwrap is justified here only because the exercise guarantees integer tokens.", | ||
| "prediction_prompt": "Predict whether the multiline case differs from a space-separated case after split_whitespace.", | ||
| "transfer_trap": "The Vec and HashMap lab builds on this owned token buffer to preserve ordered values while also maintaining keyed counts." | ||
| }, | ||
| "rust-control-flow": { | ||
| "title": "Control flow", | ||
| "concept": "Use Control flow to practice using if as an expression and for ranges to build a value instead of branching by habit. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Control flow to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-vec-hashmap": { | ||
| "title": "Combine sequence and lookup collections", | ||
| "concept": "The first token is a query; the remaining borrowed words stay ordered in a Vec while a HashMap built with entry records frequencies.", | ||
| "worked_example": "The sample counts repeated color tags, then prints the number of distinct keys rather than relying on map iteration order.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Control flow rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Control flow exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Control flow to practice using if as an expression and for ranges to build a value instead of branching by habit." | ||
| "Printing HashMap iteration directly makes output depend on an unspecified order.", | ||
| "Indexing counts[query] panics when the query never appears among the values." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Control flow before the `example:` output?", | ||
| "Which TODO line in the Control flow exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which collection answers how many values were supplied, and which answers how often the query occurred?", | ||
| "Why does get followed by copied and unwrap_or handle an absent key without mutation?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Control flow to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Store all value tokens in order, count them by key, and report value length plus query frequency.", | ||
| "objective": "Select Vec for ordered storage and HashMap for safe keyed aggregation in the same program.", | ||
| "language_delta": "Unlike insertion-ordered dictionaries in some languages, Rust standard HashMap intentionally offers no iteration-order promise.", | ||
| "prediction_prompt": "Trace the missing-query case and predict whether any map entry is created by get.", | ||
| "transfer_trap": "Do not replace entry with two lookups unless measurement establishes a reason; the occupied-or-vacant operation already expresses the update." | ||
| }, | ||
| "rust-functions": { | ||
| "title": "Functions", | ||
| "concept": "Use Functions to practice writing signatures whose parameter and return types document the calculation boundary. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Functions to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-control-flow": { | ||
| "title": "Let branches return a label", | ||
| "concept": "Rust if is an expression, so the parity branches yield the string later formatted beside an inclusive-range sum. Both branch values must share a type.", | ||
| "worked_example": "The worked program chooses a readiness label and separately consumes an inclusive integer range to calculate a triangular number.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Functions rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Functions exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Functions to practice writing signatures whose parameter and return types document the calculation boundary." | ||
| "Writing 1..n drops the upper endpoint and changes every positive total.", | ||
| "Returning a string from one branch and an integer from the other fails because one if expression needs one resulting type." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Functions before the `example:` output?", | ||
| "Which TODO line in the Functions exercise must derive the value from its own data instead of copying the demo?" | ||
| "What sequence does 1..=0 produce, and what identity does sum return for it?", | ||
| "Is parity decided before or after the total is calculated, and does that ordering affect ownership?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Functions to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Classify the input as odd or even and pair that label with the inclusive sum from one through n.", | ||
| "objective": "Build one output from a typed branch expression and a boundary-sensitive range calculation.", | ||
| "language_delta": "Rust conditions require bool; numeric truthiness from C, JavaScript, or Python does not apply.", | ||
| "prediction_prompt": "For n equal to four, list the exact range elements before predicting the displayed total.", | ||
| "transfer_trap": "The iterator lab reexpresses traversal with lazy adapters; this core lesson keeps the equivalent loop and range boundaries visible first." | ||
| }, | ||
| "rust-structs-impl": { | ||
| "title": "Structs and impl", | ||
| "concept": "Use Structs and impl to practice grouping domain fields in a struct and putting behavior that needs those fields in impl. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Structs and impl to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-iterators": { | ||
| "title": "Drive filter and map with a terminal sum", | ||
| "concept": "Parsing produces owned integers; filter borrows each candidate for its predicate, map receives accepted items, and sum finally consumes the pipeline.", | ||
| "worked_example": "An unbounded range becomes safe in the sample because take limits consumption before collect requests a concrete Vec.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Structs and impl rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Structs and impl exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Structs and impl to practice grouping domain fields in a struct and putting behavior that needs those fields in impl." | ||
| "Creating map and filter adapters without a terminal operation performs no transformations.", | ||
| "Omitting the square map computes the sum of even inputs rather than the sum of their squares." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Structs and impl before the `example:` output?", | ||
| "Which TODO line in the Structs and impl exercise must derive the value from its own data instead of copying the demo?" | ||
| "At which method call does iteration actually begin?", | ||
| "Why is zero retained by the even predicate yet harmless after squaring?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Structs and impl to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Parse the integer stream, retain evens, square them lazily, and consume the pipeline into one total.", | ||
| "objective": "Express a multi-stage numeric traversal through iterator ownership and one terminal consumer.", | ||
| "language_delta": "Iterator chains resemble streams in other languages, but Rust adapters encode borrowing and item ownership in their types.", | ||
| "prediction_prompt": "Predict each intermediate item sequence for negative two, negative one, and zero.", | ||
| "transfer_trap": "Do not collect between every adapter; an intermediate Vec is unnecessary when sum can pull items directly." | ||
| }, | ||
| "rust-enum-match": { | ||
| "title": "Enums and match", | ||
| "concept": "Use Enums and match to practice modeling alternatives with enum variants and handling each variant exhaustively with match. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Enums and match to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-functions": { | ||
| "title": "Checkpoint: values cross a function boundary", | ||
| "concept": "The area function receives two i64 values by value and returns an i64 through its trailing expression. main remains responsible for input and output.", | ||
| "worked_example": "A separate greeting function returns an allocated String made by format!, showing expression return syntax on nonnumeric data.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Enums and match rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Enums and match exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Enums and match to practice modeling alternatives with enum variants and handling each variant exhaustively with match." | ||
| "Putting a semicolon after width times height changes the body result to unit.", | ||
| "Printing inside area instead of returning a value couples calculation to one output context." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Enums and match before the `example:` output?", | ||
| "Which TODO line in the Enums and match exercise must derive the value from its own data instead of copying the demo?" | ||
| "Can area be called twice without any hidden state from the first call?", | ||
| "Which signature fragment promises the compiler and caller that an i64 comes back?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Enums and match to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Parse width and height, call a pure area function, and print only the returned value.", | ||
| "objective": "Separate typed computation from stdin and stdout at a reusable function boundary.", | ||
| "language_delta": "Rust has no implicit return keyword requirement for the final expression, but semicolon placement is semantically significant.", | ||
| "prediction_prompt": "Before compiling, predict the function body type both with and without a trailing semicolon.", | ||
| "transfer_trap": "The module, generic, trait, macro, and Cargo orientation labs extend this boundary into reusable items, expansion, crates, packages, and workspaces." | ||
| }, | ||
| "rust-option": { | ||
| "title": "Option and if let", | ||
| "concept": "Use Option and if let to practice making missing values explicit with Option<T> and unpacking the Some case safely. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Option and if let to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-modules-use": { | ||
| "title": "Shorten a visible module path", | ||
| "concept": "The grading module exposes verdict with pub, and use introduces its short name in the surrounding scope. These are separate name-resolution and visibility decisions.", | ||
| "worked_example": "A nested units item is public and then imported before main calls it, so the sample shows the complete path-to-short-name flow.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Option and if let rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Option and if let exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Option and if let to practice making missing values explicit with Option<T> and unpacking the Some case safely." | ||
| "Adding use for a private sibling function does not bypass privacy.", | ||
| "Writing a relative path from the wrong module can resolve to a different namespace or fail entirely." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Option and if let before the `example:` output?", | ||
| "Which TODO line in the Option and if let exercise must derive the value from its own data instead of copying the demo?" | ||
| "Could main call grading::verdict directly without the use declaration?", | ||
| "Which keyword changes whether code outside grading may call verdict?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Option and if let to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Keep the grading function public and imported, then correct its inclusive boundary so every score of 80 or more passes.", | ||
| "objective": "Resolve a public item through use and apply its inclusive 80-point classification boundary.", | ||
| "language_delta": "Rust use is closer to a lexical alias than a runtime import that loads a package.", | ||
| "prediction_prompt": "Predict verdicts for 79, 80, and 91, then explain whether use changes the function's behavior or only its local path.", | ||
| "transfer_trap": "Cargo packages and crates define larger build boundaries; this single-file lab demonstrates only language modules and paths." | ||
| }, | ||
| "rust-modules-use": { | ||
| "title": "Modules and use", | ||
| "concept": "Use Modules and use to practice controlling namespace, privacy, and local paths with mod, pub, and use. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Modules and use to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-generics": { | ||
| "title": "Return a copied element from a generic slice", | ||
| "concept": "last_copy accepts a shared slice of any T implementing Copy. copied converts Option<&T> to Option<T> without claiming that every Rust type is cheaply duplicable.", | ||
| "worked_example": "The companion function retrieves the first u8 from a borrowed array and displays its Option result with debug formatting.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Modules and use rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Modules and use exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Modules and use to practice controlling namespace, privacy, and local paths with mod, pub, and use." | ||
| "Leaving off the Copy bound prevents extracting a T value through copied.", | ||
| "Calling clone unconditionally would require Clone and could hide a more expensive operation." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Modules and use before the `example:` output?", | ||
| "Which TODO line in the Modules and use exercise must derive the value from its own data instead of copying the demo?" | ||
| "What is borrowed by last_copy, and what small value is returned?", | ||
| "Would String satisfy this exact bound, and if not, should the API return a reference instead?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Modules and use to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Implement a generic last-element helper and apply it to the parsed integer slice.", | ||
| "objective": "Use a trait bound that precisely justifies copying a value through a shared borrow.", | ||
| "language_delta": "Generic syntax resembles templates, but Rust checks one constrained definition rather than accepting arbitrary operations during later expansion.", | ||
| "prediction_prompt": "Predict the Option produced by a one-element slice before unwrap is applied by main.", | ||
| "transfer_trap": "Do not broaden the bound to Clone merely for familiarity; Copy is the behavior this numeric exercise actually needs." | ||
| }, | ||
| "rust-input": { | ||
| "title": "Input parsing", | ||
| "concept": "Use Input parsing to practice reading stdin once, splitting text into tokens, and converting tokens into typed values. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Input parsing to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-traits": { | ||
| "title": "Require summarizable values without cloning", | ||
| "concept": "ScoreCard implements Summary by borrowing self. The generic render function accepts any borrowed value satisfying that trait bound and returns the produced text.", | ||
| "worked_example": "The Size example implements a tiny behavior for every Vec element type, then calls the method through normal dot syntax.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Input parsing rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Input parsing exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Input parsing to practice reading stdin once, splitting text into tokens, and converting tokens into typed values." | ||
| "Moving self.name out of a shared self borrow is forbidden and unnecessary for formatting.", | ||
| "Using a concrete ScoreCard parameter in render loses the reuse supplied by the Summary contract." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Input parsing before the `example:` output?", | ||
| "Which TODO line in the Input parsing exercise must derive the value from its own data instead of copying the demo?" | ||
| "Does calling summary consume the card or leave it usable?", | ||
| "Where is the requirement T implements Summary stated for the generic function?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Input parsing to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Construct a score card and render its borrowed fields through a Summary-bound helper.", | ||
| "objective": "Define shared behavior with a trait and call it without surrendering ownership of the implementing value.", | ||
| "language_delta": "Rust traits can provide static dispatch through generics; unlike inheritance, they do not require an object hierarchy.", | ||
| "prediction_prompt": "Predict whether render could accept two unrelated structs that both implement Summary.", | ||
| "transfer_trap": "Do not clone owned fields merely to quiet a borrowing error; formatting accepts references to displayable values." | ||
| }, | ||
| "rust-vec-hashmap": { | ||
| "title": "Vec and HashMap", | ||
| "concept": "Use Vec and HashMap to practice choosing Vec for ordered data and HashMap entry APIs for keyed counting and lookup. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Vec and HashMap to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-macros": { | ||
| "title": "Transform matched tokens into an expression", | ||
| "concept": "greet! is exported and expands to $crate::helper, anchoring helper lookup in the defining crate before ordinary type checking.", | ||
| "worked_example": "caller::run invokes the exported increment! macro from a nested module, yet $crate still resolves helper at the defining crate root.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Vec and HashMap rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Vec and HashMap exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Vec and HashMap to practice choosing Vec for ordered data and HashMap entry APIs for keyed counting and lookup." | ||
| "Forgetting the exclamation mark asks the compiler to find a function instead of invoking a macro.", | ||
| "Expanding an input expression twice can execute side effects twice even when the resulting text looks simple." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Vec and HashMap before the `example:` output?", | ||
| "Which TODO line in the Vec and HashMap exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does $crate keep helper lookup stable when a macro is invoked through another path?", | ||
| "Where is the expanded helper call typechecked after macro matching finishes?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Vec and HashMap to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Complete the defining-crate greeting helper invoked by the exported macro through the nested caller module, using the trimmed stdin name.", | ||
| "objective": "Export a macro whose expansion resolves a helper hygienically through $crate.", | ||
| "language_delta": "Macros operate on Rust syntax before runtime; they are not string substitution or reflection.", | ||
| "prediction_prompt": "Expand crate::greet!(name) inside caller::greet_from and identify the exact helper path rustc resolves.", | ||
| "transfer_trap": "If an exported macro needs a helper from its own crate, a caller-relative path is fragile; $crate anchors that lookup." | ||
| }, | ||
| "rust-borrowing-slices": { | ||
| "title": "Borrowing and slices", | ||
| "concept": "Use Borrowing and slices to practice passing borrowed slices so functions can read data without taking ownership. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Borrowing and slices to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-cargo-workspaces": { | ||
| "title": "Orient yourself to Cargo workspace scope", | ||
| "concept": "A workspace coordinates member packages and a shared target directory. Here the observable skill is only choosing commands that address all members.", | ||
| "worked_example": "The example prints a metadata inspection command, deliberately avoiding any suggestion that this rustc-only file has loaded a real Cargo manifest.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Borrowing and slices rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Borrowing and slices exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Borrowing and slices to practice passing borrowed slices so functions can read data without taking ownership." | ||
| "Assuming cargo test in one member necessarily covers every workspace member can leave packages unchecked.", | ||
| "Treating this text mapping as proof of workspace configuration would overstate what the runner executed." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Borrowing and slices before the `example:` output?", | ||
| "Which TODO line in the Borrowing and slices exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which flag asks cargo check or cargo test to select the entire workspace?", | ||
| "Why does rustfmt receive its check arguments after a double dash in the formatting command?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Borrowing and slices to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Map check, test, and fmt intents to the documented workspace-oriented command lines.", | ||
| "objective": "Identify safe workspace-wide maintenance commands without pretending to execute a multi-package build.", | ||
| "language_delta": "Cargo workspaces are a build-tool feature, not a Rust language construct that plain rustc can verify.", | ||
| "prediction_prompt": "Given the intent fmt, predict which arguments belong to Cargo and which are forwarded to rustfmt.", | ||
| "transfer_trap": "Do not promote this lab into the core path until the judge can construct a temporary workspace and inspect real member behavior." | ||
| }, | ||
| "rust-result": { | ||
| "title": "Result and ?", | ||
| "concept": "Use Result and ? to practice returning recoverable errors with Result<T, E> and propagating them with the ? operator. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Result and ? to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-ownership": { | ||
| "title": "Transfer and recover String ownership", | ||
| "concept": "main removes line endings from its original String and moves that owner into describe. String carries fixed-size pointer, length, and capacity metadata; nonempty input has a UTF-8 buffer, while empty input may remain allocation-free.", | ||
| "worked_example": "wrap consumes its String metadata and takes ownership of the existing buffer; main cannot reuse the moved binding afterward.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Result and ? rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Result and ? exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Result and ?" | ||
| "Trying to read the caller binding after passing it by value triggers a moved-value error.", | ||
| "Adding clone to every ownership error creates unnecessary allocation instead of choosing a suitable parameter or return shape." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Result and ? before the `example:` output?", | ||
| "Which TODO line in the Result and ? exercise must derive the value from its own data instead of copying the demo?" | ||
| "At what exact call does ownership leave main, and at what pattern does an owner come back?", | ||
| "Why can describe call len before moving text into the tuple?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Result and ? to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Trim line endings in the original stdin String, move it into describe, and return that owner with its UTF-8 byte length.", | ||
| "objective": "Move and return the same String owner across a by-value function boundary without cloning; empty input may remain allocation-free.", | ||
| "language_delta": "Moving a String transfers fixed-size pointer, length, and capacity metadata; it neither copies the heap buffer nor reduces the value to one pointer word.", | ||
| "prediction_prompt": "Predict the byte count for é before comparing it with the later scalar-count lesson.", | ||
| "transfer_trap": "Box and Arc make alternate ownership structures explicit, while RefCell moves borrowing checks to runtime; learn this ordinary move before either extension." | ||
| }, | ||
| "rust-ownership": { | ||
| "title": "Ownership and borrowing", | ||
| "concept": "Use Ownership and borrowing to practice tracking when a String moves, when it is returned, and when a reference only borrows it. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Ownership and borrowing to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-smart-pointers": { | ||
| "title": "Own the next recursive node indirectly", | ||
| "concept": "List::Cons stores its tail in Box<List>, making every enum value a finite known size while retaining exclusive ownership of the recursive chain.", | ||
| "worked_example": "The sample constructs two boxed links and walks them through shared references, so traversal does not consume the list.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Ownership and borrowing rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Ownership and borrowing exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Ownership and borrowing to practice tracking when a String moves, when it is returned, and when a reference only borrows it." | ||
| "Placing List directly inside Cons creates an infinitely sized type rejected by rustc.", | ||
| "Saying Box makes code faster confuses its purpose; the relevant property is indirection with ownership and known pointer size." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Ownership and borrowing before the `example:` output?", | ||
| "Which TODO line in the Ownership and borrowing exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which part of Cons has fixed size even when the remaining list is arbitrarily long?", | ||
| "Why does building from a reversed iterator preserve the original token order?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Ownership and borrowing to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Fold parsed integers into a boxed recursive list and print the sum found by traversal.", | ||
| "objective": "Use Box to make a recursive owning data type representable and visit it without moving nodes.", | ||
| "language_delta": "A garbage-collected language may hide recursive indirection; Rust requires the ownership edge to be explicit in the type.", | ||
| "prediction_prompt": "Sketch the Cons chain produced from three tokens before evaluating the recursive sum.", | ||
| "transfer_trap": "Arc and Rc express shared ownership, but this list has exactly one owner for every tail and needs only Box." | ||
| }, | ||
| "rust-iterators": { | ||
| "title": "Iterators and closures", | ||
| "concept": "Use Iterators and closures to practice composing lazy iterator adapters with closures and consuming them with sum or collect. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Iterators and closures to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-interior-mutability": { | ||
| "title": "Borrow mutably at runtime inside an immutable owner", | ||
| "concept": "EventLog exposes record through &self while RefCell checks each mutable and shared borrow dynamically. The mutable guard ends after push, before the final shared borrow.", | ||
| "worked_example": "Cell handles a Copy integer without lending references, so the example increments a counter through an immutable binding.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Iterators and closures rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Iterators and closures exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Iterators and closures to practice composing lazy iterator adapters with closures and consuming them with sum or collect." | ||
| "Holding borrow_mut while taking borrow causes a runtime panic due to overlapping access.", | ||
| "An owned RefCell<T> may move to another thread when T is Send, but RefCell is not Sync and cannot be shared for concurrent mutation." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Iterators and closures before the `example:` output?", | ||
| "Which TODO line in the Iterators and closures exercise must derive the value from its own data instead of copying the demo?" | ||
| "Where does the RefMut guard from borrow_mut stop being live in record?", | ||
| "Which rule remains enforced despite the owner itself never being declared mut?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Iterators and closures to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Record every input token through &self and report the final RefCell-backed vector length.", | ||
| "objective": "Apply interior mutability only within a short, nonoverlapping, single-threaded borrow scope.", | ||
| "language_delta": "Unlike ordinary Rust references, RefCell postpones exclusivity checks until execution rather than removing them.", | ||
| "prediction_prompt": "Predict what would happen if record kept one mutable guard while requesting a second.", | ||
| "transfer_trap": "RefCell is not a synchronization primitive: use Mutex when multiple threads share and mutate the same value." | ||
| }, | ||
| "rust-generics": { | ||
| "title": "Generics", | ||
| "concept": "Use Generics to practice writing one function over T while preserving concrete type checking at compile time. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Generics to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-strings": { | ||
| "title": "Measure three different views of UTF-8 text", | ||
| "concept": "String and str store UTF-8. len reports storage bytes, chars yields Unicode scalar values, and a valid range slice must begin and end at character boundaries.", | ||
| "worked_example": "The crab scalar occupies four bytes, so the example safely slices the first four-byte range and reports a byte count different from its scalar count.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Generics rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Generics exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Generics to practice writing one function over T while preserving concrete type checking at compile time." | ||
| "Using text[0] is impossible because one byte is not necessarily one character.", | ||
| "Slicing at an arbitrary byte offset can panic even when the offset is below len." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Generics before the `example:` output?", | ||
| "Which TODO line in the Generics exercise must derive the value from its own data instead of copying the demo?" | ||
| "How many bytes and scalar values does an accented e contain in the fixture spelling?", | ||
| "Why is the crab prefix range four bytes rather than one?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Generics to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "For the input text, print UTF-8 byte length, scalar count, and first scalar as separate fields.", | ||
| "objective": "Choose len, chars, and a boundary-safe scalar lookup according to the measurement requested.", | ||
| "language_delta": "Rust UTF-8 byte length differs from Java and JavaScript UTF-16 code-unit length, and grapheme segmentation would require additional Unicode logic.", | ||
| "prediction_prompt": "Predict every field for the crab-plus-a case before compiling.", | ||
| "transfer_trap": "Do not transfer ASCII indexing intuition to str; byte positions are implementation offsets with validity constraints." | ||
| }, | ||
| "rust-traits": { | ||
| "title": "Traits and bounds", | ||
| "concept": "Use Traits and bounds to practice describing required behavior with traits and using bounds to call that behavior generically. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Traits and bounds to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-borrowing-slices": { | ||
| "title": "Checkpoint: lend a slice of owned input", | ||
| "concept": "main owns the String buffer while first_word temporarily borrows it and returns a &str into that same storage. No word allocation occurs.", | ||
| "worked_example": "The key helper uses split_once to lend the prefix of an owned record until the record is no longer needed.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Traits and bounds rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Traits and bounds exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Traits and bounds to practice describing required behavior with traits and using bounds to call that behavior generically." | ||
| "Returning a slice of a local String would leave a reference to dropped storage and is rejected.", | ||
| "Calling to_owned inside first_word changes the API and performs an allocation the slice can avoid." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Traits and bounds before the `example:` output?", | ||
| "Which TODO line in the Traits and bounds exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which value must remain alive while the returned first-word slice is printed?", | ||
| "Why does empty input safely produce an empty borrowed string instead of requiring unwrap?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Traits and bounds to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Borrow the complete stdin buffer and return its first whitespace-delimited word as &str.", | ||
| "objective": "Relate a returned slice to the caller-owned String and handle the absence of words safely.", | ||
| "language_delta": "Rust references carry compile-time validity relationships; garbage-collected languages often permit substring values without exposing their ownership connection.", | ||
| "prediction_prompt": "Predict whether leading whitespace belongs to the returned slice when split_whitespace is used.", | ||
| "transfer_trap": "Lifetime annotations, dyn trait results, and unsafe pointer obligations all build on this same owner-bounded slice relationship rather than extending the data." | ||
| }, | ||
| "rust-lifetimes": { | ||
| "title": "Lifetimes", | ||
| "concept": "Use Lifetimes to practice annotating relationships between borrowed inputs and borrowed outputs without extending storage. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Lifetimes to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Name the lifetime shared by a returned choice", | ||
| "concept": "longer may return either argument, so one lifetime parameter relates both input references to the output. The caller still owns all underlying text.", | ||
| "worked_example": "shorter needs the explicit 'a because two borrowed inputs leave output lifetime elision unable to choose which input supplies the returned reference.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Lifetimes rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Lifetimes exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Lifetimes to practice annotating relationships between borrowed inputs and borrowed outputs without extending storage." | ||
| "Assuming output elision can choose between two input references fails; the relationship must be written explicitly.", | ||
| "Comparing str::len here would rank UTF-8 bytes, not the requested Unicode scalar values." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Lifetimes before the `example:` output?", | ||
| "Which TODO line in the Lifetimes exercise must derive the value from its own data instead of copying the demo?" | ||
| "What data does the lifetime parameter allocate or retain at runtime? Nothing.", | ||
| "Why must the returned reference be valid for a period supported by whichever input was selected?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Lifetimes to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Split the input at the bar and return the scalar-longer borrowed side, preferring left on ties.", | ||
| "objective": "Annotate a reference relationship while leaving ownership and storage duration unchanged.", | ||
| "language_delta": "Lifetime syntax is compile-time reasoning about borrows, unlike a reference-counting handle that actively keeps an allocation alive.", | ||
| "prediction_prompt": "Compare é with aa first by bytes and then by scalar count; predict why the requested winner changes.", | ||
| "transfer_trap": "Do not add static to silence the compiler; that would demand globally lasting data instead of describing the actual caller borrows." | ||
| }, | ||
| "rust-traits-lifetimes": { | ||
| "title": "Trait objects and dyn dispatch", | ||
| "concept": "Use Trait objects and dyn dispatch to practice using &dyn Trait when runtime dispatch is worth the flexibility over a single generic type. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Trait objects and dyn dispatch to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Dispatch dynamically while lending the result", | ||
| "concept": "render accepts &dyn Draw, chooses the concrete implementation through a vtable, and returns a &str borrowed from the widget for no longer than that widget borrow.", | ||
| "worked_example": "The Named example sends a Tag through a trait object and receives its inner text without cloning the owned String.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Trait objects and dyn dispatch rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Trait objects and dyn dispatch exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Trait objects and dyn dispatch to practice using &dyn Trait when runtime dispatch is worth the flexibility over a single generic type." | ||
| "Adding a generic method without a `where Self: Sized` restriction can make Draw incompatible with dyn dispatch.", | ||
| "Returning a reference to a temporary String created inside draw would dangle and is rejected." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Trait objects and dyn dispatch before the `example:` output?", | ||
| "Which TODO line in the Trait objects and dyn dispatch exercise must derive the value from its own data instead of copying the demo?" | ||
| "What remains owned by Button or Label after render returns?", | ||
| "Which call is dynamically dispatched, and which lifetime is inferred for the returned str?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Trait objects and dyn dispatch to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Build the requested widget and render its borrowed label through a dyn Draw reference.", | ||
| "objective": "Combine object-safe runtime dispatch with a result whose lifetime follows the object borrow.", | ||
| "language_delta": "A Rust trait object is explicit dynamic dispatch, unlike a generic trait bound that is normally monomorphized for each concrete type.", | ||
| "prediction_prompt": "Predict which implementation receives label:Rust and how long its returned slice remains valid.", | ||
| "transfer_trap": "Do not box every value automatically; a short-lived &dyn Draw borrow is enough because main owns each concrete widget." | ||
| }, | ||
| "rust-testing": { | ||
| "title": "Tests and assertions", | ||
| "concept": "Use Tests and assertions to practice separating pure logic from main so #[test] functions and assert macros can verify behavior. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Tests and assertions to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-unsafe": { | ||
| "title": "Make the safety obligation local and explicit", | ||
| "concept": "read_copy converts a valid shared reference to a raw pointer and performs exactly one unsafe read. Its comment names liveness, alignment, and copyability assumptions.", | ||
| "worked_example": "The sample proves index zero exists before using get_unchecked inside a small block whose invariant can be reviewed in isolation.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Tests and assertions rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Tests and assertions exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Tests and assertions to practice separating pure logic from main so #[test] functions and assert macros can verify behavior." | ||
| "Using unsafe as a broad escape hatch does not make a dangling or misaligned pointer valid.", | ||
| "In Rust 2024, an unsafe operation directly inside unsafe fn triggers the warn-by-default unsafe_op_in_unsafe_fn lint; denying that lint promotes it to an error." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Tests and assertions before the `example:` output?", | ||
| "Which TODO line in the Tests and assertions exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which facts about pointer make pointer.read valid at that line?", | ||
| "What safe reference continues to guarantee the pointee remains alive during the operation?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Tests and assertions to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Wrap a raw-pointer copy of the parsed integer behind a safe function with a documented invariant.", | ||
| "objective": "Constrain unsafe code to one auditable operation while preserving a safe caller contract.", | ||
| "language_delta": "Unsafe relaxes checks only for listed operations; Rust 2024 warns when an unsafe fn performs an unsafe operation outside an explicit unsafe block, which documents the audited scope.", | ||
| "prediction_prompt": "Imagine the reference were dropped before the read and identify which stated safety fact would become false.", | ||
| "transfer_trap": "Do not mark the entire function unsafe when callers need no extra obligation and the wrapper can establish every precondition internally." | ||
| }, | ||
| "rust-smart-pointers": { | ||
| "title": "Smart pointers", | ||
| "concept": "Use Smart pointers to practice using Box<T> to own heap data while still working through pointer-like deref behavior. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Smart pointers to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-structs-impl": { | ||
| "title": "Place borrowed behavior beside its data", | ||
| "concept": "Rectangle owns two dimensions, while area borrows &self and leaves the instance available after computing. The method call supplies that receiver automatically.", | ||
| "worked_example": "Counter demonstrates another read-only method whose receiver is borrowed rather than consumed or mutably borrowed.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Smart pointers rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Smart pointers exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Smart pointers to practice using Box<T> to own heap data while still working through pointer-like deref behavior." | ||
| "Declaring area with self would consume the rectangle even though calculation only reads fields.", | ||
| "Confusing Rectangle::area with a no-receiver associated function overlooks the explicit &self parameter." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Smart pointers before the `example:` output?", | ||
| "Which TODO line in the Smart pointers exercise must derive the value from its own data instead of copying the demo?" | ||
| "Can rectangle.area be called twice, and which receiver choice makes that possible?", | ||
| "How could a generic bound or trait later provide similar method behavior across several types?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Smart pointers to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Construct a Rectangle from parsed dimensions and print the result of its borrowing area method.", | ||
| "objective": "Model related fields as a struct and attach nonconsuming behavior through impl.", | ||
| "language_delta": "Rust methods make receiver ownership explicit rather than hiding whether an object call consumes, mutates, or borrows its target.", | ||
| "prediction_prompt": "Predict whether changing &self to &mut self is necessary for a pure multiplication.", | ||
| "transfer_trap": "Generic and trait labs can layer reusable behavior on structs, but one concrete impl is the clearest boundary for this core lesson." | ||
| }, | ||
| "rust-interior-mutability": { | ||
| "title": "Interior mutability", | ||
| "concept": "Use Interior mutability to practice using RefCell<T> when runtime borrow checking is the simplest correct model. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Interior mutability to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-enum-match": { | ||
| "title": "Make closed alternatives exhaustive", | ||
| "concept": "Command represents add, multiply, or quit with variant-specific data. evaluate names every case, letting rustc flag the match if another variant is later introduced.", | ||
| "worked_example": "The traffic-light match covers all three unit variants and returns one u8 duration from the expression.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Interior mutability rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Interior mutability exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Interior mutability to practice using RefCell<T> when runtime borrow checking is the simplest correct model." | ||
| "A wildcard arm suppresses the useful nonexhaustive diagnostic when the enum grows.", | ||
| "Reading payload fields before deciding the command makes the payload-free quit variant awkward or unsafe." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Interior mutability before the `example:` output?", | ||
| "Which TODO line in the Interior mutability exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which variants own two integers, and which owns no payload?", | ||
| "What compiler assistance is lost when every unmatched command is swallowed by underscore?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Interior mutability to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Parse one command into its enum variant and evaluate it with explicit exhaustive match arms.", | ||
| "objective": "Represent a closed command set with payload-carrying variants and total pattern matching.", | ||
| "language_delta": "Rust match exhaustiveness is checked statically; switch statements in some languages permit missing cases or fall-through.", | ||
| "prediction_prompt": "Predict the bound names available in each arm before calculating its result.", | ||
| "transfer_trap": "Option is also an enum: its Some and None variants deserve the same deliberate handling in the following lab." | ||
| }, | ||
| "rust-concurrency": { | ||
| "title": "Threads and join", | ||
| "concept": "Use Threads and join to practice moving work into thread::spawn and using join to recover the worker result safely. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Threads and join to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-option": { | ||
| "title": "Branch on presence instead of unwrapping", | ||
| "concept": "The first Unicode scalar is Some(char) for nonempty text and None otherwise. An explicit match produces meaningful output for both ordinary states.", | ||
| "worked_example": "checked_half uses then_some to build an Option only when an integer is even, and debug formatting reveals the wrapper.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Threads and join rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Threads and join exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Threads and join to practice moving work into thread::spawn and using join to recover the worker result safely." | ||
| "Calling unwrap treats expected empty input as a panic-worthy programmer error.", | ||
| "Using bytes().next would return the first UTF-8 byte rather than the first Unicode scalar." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Threads and join before the `example:` output?", | ||
| "Which TODO line in the Threads and join exercise must derive the value from its own data instead of copying the demo?" | ||
| "What payload type is stored inside Some for chars().next?", | ||
| "Which branch runs for a zero-length trimmed string?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Threads and join to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Inspect the optional first scalar and print either that char or the documented absence word.", | ||
| "objective": "Handle a normal missing value exhaustively without nulls, sentinels, or panic.", | ||
| "language_delta": "Option encodes absence in the type, unlike nullable references that can be forgotten until runtime.", | ||
| "prediction_prompt": "Predict the scalar selected from the Korean fixture rather than reasoning in raw bytes.", | ||
| "transfer_trap": "Reserve unwrap for states whose absence truly violates an invariant; empty user input is explicitly part of this exercise." | ||
| }, | ||
| "rust-shared-state": { | ||
| "title": "Shared state with Arc and Mutex", | ||
| "concept": "Use Shared state with Arc and Mutex to practice combining Arc<T> for shared ownership with Mutex<T> for one-at-a-time mutation. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Shared state with Arc and Mutex to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-result": { | ||
| "title": "Carry parse errors across a pure helper", | ||
| "concept": "sum_tokens uses try_fold so each integer conversion can apply ?. The first ParseIntError exits the helper, while main decides how that failure appears on stdout.", | ||
| "worked_example": "positive shows question-mark propagation followed by a second domain check, making clear that propagation and error recovery are separate decisions.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Shared state with Arc and Mutex rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Shared state with Arc and Mutex exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Shared state with Arc and Mutex to practice combining Arc<T> for shared ownership with Mutex<T> for one-at-a-time mutation." | ||
| "Reading ? as a catch operator hides that it immediately returns the error from the enclosing function.", | ||
| "Unwrapping every token prevents main from converting malformed input into the required error line." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Shared state with Arc and Mutex before the `example:` output?", | ||
| "Which TODO line in the Shared state with Arc and Mutex exercise must derive the value from its own data instead of copying the demo?" | ||
| "What is the return type of the function containing the question mark?", | ||
| "Does try_fold visit tokens after one parse returns Err?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Shared state with Arc and Mutex to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Sum all valid integer tokens through Result and print a stable error marker if any parse fails.", | ||
| "objective": "Separate fallible conversion from user-facing recovery while preserving the original error path.", | ||
| "language_delta": "Rust Result makes recoverable failure explicit in signatures rather than using exceptions that can cross unmarked call boundaries.", | ||
| "prediction_prompt": "Trace the accumulator for two followed by bad and identify the exact point where control returns.", | ||
| "transfer_trap": "Executable assertions can test this pure helper, and an async function can likewise return a Future whose eventual output contains success or failure." | ||
| }, | ||
| "rust-async-await": { | ||
| "title": "Async and await", | ||
| "concept": "Use Async and await to practice recognizing that async creates a Future and that running it requires an executor or runtime. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Async and await to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-testing": { | ||
| "title": "Keep checks on the code path the judge executes", | ||
| "concept": "The reference binary calls assert_eq! before reading stdin, so those checks genuinely run. A cfg(test) module would be absent from this ordinary compilation mode.", | ||
| "worked_example": "The triple example performs an inline assertion and then prints a non-answer status line, proving the check ran without invoking a test harness.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Async and await rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Async and await exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Async and await to practice recognizing that async creates a Future and that running it requires an executor or runtime." | ||
| "Assuming #[test] functions run in a normal rustc binary gives false confidence because the harness was never built.", | ||
| "Using assertions for untrusted input validation can panic rather than returning a controlled user-facing error." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Async and await before the `example:` output?", | ||
| "Which TODO line in the Async and await exercise must derive the value from its own data instead of copying the demo?" | ||
| "If an inline equality is wrong, does execution reach the later println?", | ||
| "Which commands activate harness tests? cargo test and rustc --test do; plain rustc does not." | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Async and await to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Implement add_two, retain two executable boundary assertions, and print the function result for stdin.", | ||
| "objective": "Leave a runnable correctness check in the same execution mode used by this lesson judge.", | ||
| "language_delta": "Rust test attributes depend on compilation mode; unlike always-called assertions in main, they need cargo test or rustc --test to generate and run the harness.", | ||
| "prediction_prompt": "Predict the process behavior if add_two returns its input unchanged while the inline checks remain.", | ||
| "transfer_trap": "Do not claim unit-test mastery from this lab alone; it teaches executable assertions and the exact boundary of the available runner." | ||
| }, | ||
| "rust-macros": { | ||
| "title": "macro_rules!", | ||
| "concept": "Use macro_rules! to practice matching token patterns with macro_rules! and expanding repeated Rust code at compile time. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying macro_rules! to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-async-await": { | ||
| "title": "Execute an await with a deliberately bounded executor", | ||
| "concept": "double awaits std::future::ready, and block_on_ready polls the resulting Future once. Pending is a hard error because this miniature executor has no wake-and-repoll loop.", | ||
| "worked_example": "message contains a real .await and is driven by the same one-poll mechanism, producing observable execution instead of merely constructing and dropping a future.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the macro_rules! rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the macro_rules! exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use macro_rules!" | ||
| "Calling double without polling its Future executes none of the async body.", | ||
| "Using this one-poll helper for timers, I/O, or any potentially pending future is incorrect." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies macro_rules! before the `example:` output?", | ||
| "Which TODO line in the macro_rules! exercise must derive the value from its own data instead of copying the demo?" | ||
| "Where does the future first run: at the async call or at poll?", | ||
| "What explicit bound makes a no-op waker acceptable in this exercise? The awaited future is immediately ready." | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying macro_rules! to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Transform the parsed number inside the ready future and drive that async computation to its output.", | ||
| "objective": "Observe a genuine await while keeping the executor honest about its single-poll limitation.", | ||
| "language_delta": "Rust async is inert until an executor polls the Future; it does not automatically start work like an eagerly scheduled task abstraction.", | ||
| "prediction_prompt": "Predict which match arm receives the ready future and why no second poll occurs.", | ||
| "transfer_trap": "Use a production executor when work can yield Pending; extending this teaching helper into a scheduler is outside the runner scope." | ||
| }, | ||
| "rust-unsafe": { | ||
| "title": "Unsafe Rust", | ||
| "concept": "Use Unsafe Rust to practice placing unverifiable operations in unsafe blocks while keeping the surrounding API as safe as possible. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Unsafe Rust to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-concurrency": { | ||
| "title": "Wait for an owned worker result", | ||
| "concept": "The parsed i64 is Copy, so a move closure receives its own copied value and would not invalidate the caller's binding. join synchronizes with that worker and yields its computed value.", | ||
| "worked_example": "A handle for a small multiplication is joined before output, so the demonstration cannot exit before the worker result is available.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Unsafe Rust rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Unsafe Rust exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Unsafe Rust to practice placing unverifiable operations in unsafe blocks while keeping the surrounding API as safe as possible." | ||
| "Dropping JoinHandle detaches the worker; it does not implicitly wait.", | ||
| "Borrowing stack data into a spawned closure usually fails the static lifetime requirement because the worker could outlive that stack frame." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Unsafe Rust before the `example:` output?", | ||
| "Which TODO line in the Unsafe Rust exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which copied value crosses the thread boundary, and why could the caller's original i64 remain usable?", | ||
| "What failure does expect convert into a main-thread panic when join reports Err?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Unsafe Rust to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Capture the parsed Copy integer in one worker, double it there, join, and print the returned value.", | ||
| "objective": "Coordinate one thread result explicitly instead of relying on timing or process shutdown.", | ||
| "language_delta": "Unlike an inert async Future, spawn creates an independently scheduled OS thread that may begin before or after the caller continues; join is the synchronization point.", | ||
| "prediction_prompt": "Predict which result join guarantees without assuming whether the worker or caller receives CPU time first.", | ||
| "transfer_trap": "Shared mutation is unnecessary for one returned value; Arc and Mutex belong in the multiworker capstone." | ||
| }, | ||
| "rust-cargo-workspaces": { | ||
| "title": "Cargo packages and workspaces", | ||
| "concept": "Use Cargo packages and workspaces to practice understanding when Cargo package structure and cargo check --workspace matter beyond one file. Read the type or item boundary first, then follow the value into the labeled demo output.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Cargo packages and workspaces to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "rust-shared-state": { | ||
| "title": "Capstone: scope shared mutation across joined workers", | ||
| "concept": "main owns the input Vec and spawns one worker per integer. Every closure owns its value plus an Arc clone, computes outside the critical section, locks briefly, and then drops its MutexGuard.", | ||
| "worked_example": "The sample launches two genuine threads, joins both handles, and reads the protected total only after all updates have completed.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Cargo packages and workspaces rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Cargo packages and workspaces exercise.", | ||
| "Matching the judge output without checking this lesson idea: Use Cargo packages and workspaces to practice understanding when Cargo package structure and cargo check --workspace matter beyond one file." | ||
| "Calling lock is not an instant field access: it may block and can return a poisoned error after another holder panics.", | ||
| "Holding the guard while doing unrelated work lengthens contention; compute the square before acquiring the mutex." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Cargo packages and workspaces before the `example:` output?", | ||
| "Which TODO line in the Cargo packages and workspaces exercise must derive the value from its own data instead of copying the demo?" | ||
| "Are all JoinHandles consumed before main takes its final guard?", | ||
| "Which lab rules appear here: Vec iteration, Arc ownership, thread moves, Mutex guard scope, and explicit poison handling?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Cargo packages and workspaces to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Spawn and join one worker for each input, adding every square into a shared Arc<Mutex<i64>> total.", | ||
| "objective": "Integrate ownership, iteration, threading, synchronization, and deterministic output in one bounded program.", | ||
| "language_delta": "Arc manages shared lifetime and Mutex manages exclusive access; RefCell is not Sync and cannot protect concurrently shared mutation.", | ||
| "prediction_prompt": "Predict why scheduling order cannot change the final sum after every worker is joined.", | ||
| "transfer_trap": "Arc is the shared smart pointer here rather than singly owning Box; an owned RefCell<T> may move when T is Send but cannot be shared concurrently, and unsafe is unnecessary because Mutex enforces the aliasing invariant." | ||
| } | ||
| } | ||
| } |
+372
-285
@@ -7,437 +7,524 @@ { | ||
| "rust-output": { | ||
| "title": "Salida", | ||
| "concept": "Salida se centra en esta habilidad de Rust: formatear valores con println! y producir stdout exacto para el juez. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Salida. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Salida estándar exacta en bytes", | ||
| "concept": "La macro `println!` valida sus argumentos de formato durante la compilación y añade exactamente un salto LF después del texto representado.", | ||
| "worked_example": "La muestra une el nombre de una herramienta y una edición mediante un guion. Los argumentos capturados demuestran el formato sin copiar la respuesta del ejercicio.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Salida en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Salida.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Salida se centra en esta habilidad de Rust: formatear valores con println!" | ||
| "Llamar `println` como una función y omitir el signo de exclamación obligatorio.", | ||
| "Añadir espacios alrededor de los dos puntos y cambiar la salida aunque los valores sean correctos." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Salida antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Salida debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué caracteres proceden de la plantilla y cuál añade la macro al final?", | ||
| "¿Usarías `print!` o `println!` si no se permitiera una nueva línea final?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Salida a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Lee un nombre y una puntuación y representa ambos con el delimitador exacto mostrado por los casos.", | ||
| "objective": "Producir una línea comprobada al compilar cuyos bytes coincidan con la salida requerida.", | ||
| "language_delta": "El formato de Rust mantiene separados plantilla y argumentos tipados y comprueba su relación, en vez de depender de concatenaciones implícitas.", | ||
| "prediction_prompt": "Antes de ejecutar, decide si la puntuación situada fuera de las llaves se repite por cada valor sustituido.", | ||
| "transfer_trap": "`println!` no es una función variádica ordinaria: es una expansión de macro con plantilla conocida estáticamente." | ||
| }, | ||
| "rust-variables": { | ||
| "title": "Vinculaciones y mutabilidad", | ||
| "concept": "Vinculaciones y mutabilidad se centra en esta habilidad de Rust: preferir vinculaciones let inmutables y usar mut solo cuando el valor cambia de verdad. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Vinculaciones y mutabilidad. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Estado cambiante con un único enlace mutable", | ||
| "concept": "Un enlace `mut` permite asignar de nuevo al mismo almacenamiento. Otro `let` con igual nombre produce sombreado: crea un enlace nuevo en lugar de mutar el anterior.", | ||
| "worked_example": "La demostración actualiza `value` una vez y luego lo sombrea con un resultado transformado. Ambos mecanismos quedan visibles en pocas líneas.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Vinculaciones y mutabilidad en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Vinculaciones y mutabilidad.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Vinculaciones y mutabilidad se centra en esta habilidad de Rust: preferir vinculaciones let inmutables y usar mut solo cuando el valor cambia de verdad." | ||
| "Escribir `value += 1` sin `mut`; el enlace original no admite otra asignación.", | ||
| "Describir el sombreado como actualización in situ y ocultar cuándo deja de ser accesible el enlace anterior." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Vinculaciones y mutabilidad antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Vinculaciones y mutabilidad debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué línea modifica almacenamiento y cuál introduce otro enlace?", | ||
| "¿Necesita `mut` la etiqueta inmutable solo porque se imprime después de cambiar el total?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Vinculaciones y mutabilidad a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Mantén fija la etiqueta y suma cada entero posterior en un acumulador declarado expresamente como mutable.", | ||
| "objective": "Distinguir un total mutable intencionado de los datos de contexto inmutables.", | ||
| "language_delta": "Rust hace explícita la mutabilidad del enlace; el sombreado puede incluso cambiar de tipo sin declarar una variable mutable.", | ||
| "prediction_prompt": "Predice el total después de cada vuelta antes de comprobar la línea final formateada.", | ||
| "transfer_trap": "No confundas sombrear con reasignar: el primero crea otro enlace y puede cambiar su tipo." | ||
| }, | ||
| "rust-numbers-tuples": { | ||
| "title": "Números y tuplas", | ||
| "concept": "Números y tuplas se centra en esta habilidad de Rust: combinar tipos numéricos explícitos con acceso a campos de tupla como pair.0 y pair.1. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Números y tuplas. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "División con signo observada en una tupla", | ||
| "concept": "Para operandos `i64` válidos, `/` trunca hacia cero y `%` devuelve el resto asociado. El divisor no es cero y se excluye el desbordamiento `i64::MIN / -1`.", | ||
| "worked_example": "Dividir veintitrés entre cuatro produce cociente y resto positivos, que se consultan por separado como `result.0` y `result.1`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Números y tuplas en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Números y tuplas.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Números y tuplas se centra en esta habilidad de Rust: combinar tipos numéricos explícitos con acceso a campos de tupla como pair.0 y pair.1." | ||
| "Aplicar la división con redondeo hacia abajo de Python y obtener otro cociente con dividendo negativo.", | ||
| "Intercambiar los índices de la tupla: compila, pero invierte los campos requeridos." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Números y tuplas antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Números y tuplas debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "Para menos diecisiete entre cinco, ¿el truncamiento avanza hacia cero o menos infinito?", | ||
| "¿Qué identidad verifica el resto y qué pareja de desbordamiento queda excluida?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Números y tuplas a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Analiza dos enteros, con divisor no nulo y sin `i64::MIN / -1`, e imprime en orden la tupla cociente-resto.", | ||
| "objective": "Calcular y desestructurar dos resultados respetando la aritmética con signo de Rust.", | ||
| "language_delta": "La barra entera de Rust trunca cuando difieren los signos; `//` de Python redondea hacia menos infinito.", | ||
| "prediction_prompt": "Calcula cociente y resto del caso con signos mezclados antes de invocar `rustc`.", | ||
| "transfer_trap": "El símbolo `/` no implica punto flotante: los tipos de operandos determinan la aritmética." | ||
| }, | ||
| "rust-strings": { | ||
| "title": "Cadenas", | ||
| "concept": "Cadenas se centra en esta habilidad de Rust: decidir cuándo hace falta un String con propiedad y cuándo basta un &str prestado. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Cadenas. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-input": { | ||
| "title": "Consumir una entrada separada por espacios", | ||
| "concept": "El método del trait `Read` llena un `String` propio y `split_whitespace` visita todos los tokens aunque cambien de línea. Una entrada vacía produce un iterador vacío.", | ||
| "worked_example": "Un segmento de bytes implementa `Read`; por eso la muestra prueba `read_to_string` de forma determinista sin depender de un terminal.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Cadenas en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Cadenas.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Cadenas se centra en esta habilidad de Rust: decidir cuándo hace falta un String con propiedad y cuándo basta un &str prestado." | ||
| "Leer una sola línea e ignorar tokens válidos posteriores a un salto.", | ||
| "Tratar `parse` como infalible fuera del contrato y convertir texto mal formado en un pánico sin explicación." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Cadenas antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Cadenas debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué `sum` sobre un iterador vacío devuelve la identidad aditiva?", | ||
| "¿Qué `trait` importado hace disponible `read_to_string` sobre stdin?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Cadenas a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Lee la entrada completa, analiza todos los enteros separados por espacios en blanco e imprime su suma.", | ||
| "objective": "Resolver entradas válidas de una línea, varias líneas y vacías mediante la misma tokenización.", | ||
| "language_delta": "El análisis Rust devuelve `Result` en vez de convertir por coerción; aquí `unwrap` solo se justifica porque los tokens están garantizados.", | ||
| "prediction_prompt": "Decide si el caso multilínea difiere del equivalente separado solo por espacios tras `split_whitespace`.", | ||
| "transfer_trap": "No reduzcas la entrada a su primera línea: el contrato define un flujo completo de tokens." | ||
| }, | ||
| "rust-control-flow": { | ||
| "title": "Flujo de control", | ||
| "concept": "Flujo de control se centra en esta habilidad de Rust: usar if como expresión y rangos for para construir valores, no solo ramificar por costumbre. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Flujo de control. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-vec-hashmap": { | ||
| "title": "Combinar una secuencia con una tabla de consulta", | ||
| "concept": "El primer token es la consulta; las palabras restantes permanecen ordenadas en un `Vec` y un `HashMap` construido con `entry` registra sus frecuencias.", | ||
| "worked_example": "La muestra cuenta etiquetas de color repetidas y luego imprime la cantidad de claves distintas sin depender del orden de iteración del mapa.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Flujo de control en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Flujo de control.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Flujo de control se centra en esta habilidad de Rust: usar if como expresión y rangos for para construir valores, no solo ramificar por costumbre." | ||
| "Imprimir directamente la iteración de `HashMap` y depender de un orden no especificado.", | ||
| "Indexar `counts[query]` y provocar pánico cuando la consulta no aparece." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Flujo de control antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Flujo de control debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué colección informa cuántos valores llegaron y cuál cuántas veces apareció la consulta?", | ||
| "¿Por qué `get().copied().unwrap_or(0)` resuelve una ausencia sin mutar?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Flujo de control a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Guarda los tokens de valores en orden, cuenta cada clave e informa de la longitud y frecuencia solicitadas.", | ||
| "objective": "Elegir `Vec` para orden y `HashMap` para agregación segura dentro del mismo programa.", | ||
| "language_delta": "El `HashMap` estándar de Rust no promete orden de iteración, a diferencia de ciertos diccionarios ordenados por inserción.", | ||
| "prediction_prompt": "Sigue una consulta ausente y decide si `get` crea alguna entrada nueva.", | ||
| "transfer_trap": "No sustituyas `entry` por dos consultas sin una medición: la operación ocupada o vacante ya expresa la actualización." | ||
| }, | ||
| "rust-functions": { | ||
| "title": "Funciones", | ||
| "concept": "Funciones se centra en esta habilidad de Rust: escribir firmas donde los tipos de parámetros y retorno documenten el límite del cálculo. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Funciones. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-control-flow": { | ||
| "title": "Hacer que las ramas devuelvan una etiqueta", | ||
| "concept": "`if` es una expresión: las ramas de paridad producen la cadena que se presenta junto a una suma de rango inclusivo. Ambos valores de rama deben compartir tipo.", | ||
| "worked_example": "La muestra elige una etiqueta de preparación y consume aparte un rango entero inclusivo para calcular un número triangular.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Funciones en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Funciones.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Funciones se centra en esta habilidad de Rust: escribir firmas donde los tipos de parámetros y retorno documenten el límite del cálculo." | ||
| "Escribir `1..n`, perder el extremo superior y cambiar cada total positivo.", | ||
| "Devolver cadena en una rama y entero en otra, aunque una expresión `if` necesita un solo tipo." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Funciones antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Funciones debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué secuencia genera `1..=0` y qué identidad devuelve `sum`?", | ||
| "¿La paridad se decide antes o después del total y afecta eso a la propiedad?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Funciones a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Clasifica la entrada como par o impar y acompaña esa etiqueta con la suma inclusiva desde uno hasta `n`.", | ||
| "objective": "Construir una salida mediante una rama tipada y un rango sensible a sus límites.", | ||
| "language_delta": "Las condiciones Rust requieren `bool`; no existe conversión implícita de números a booleanos como en C, JavaScript o Python.", | ||
| "prediction_prompt": "Para `n = 4`, enumera los elementos exactos del rango y calcula después el total mostrado.", | ||
| "transfer_trap": "Un rango semiabierto de otro lenguaje puede parecer igual y omitir silenciosamente el último valor." | ||
| }, | ||
| "rust-structs-impl": { | ||
| "title": "Structs e impl", | ||
| "concept": "Structs e impl se centra en esta habilidad de Rust: agrupar campos del dominio en un struct y poner en impl el comportamiento que usa esos campos. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Structs e impl. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-iterators": { | ||
| "title": "Impulsar filtro y transformación con una suma terminal", | ||
| "concept": "El análisis crea enteros propios; `filter` presta cada candidato, `map` recibe los aceptados y `sum` consume finalmente toda la tubería.", | ||
| "worked_example": "Un rango no acotado resulta seguro porque `take` limita el consumo antes de que `collect` solicite un `Vec` concreto.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Structs e impl en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Structs e impl.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Structs e impl se centra en esta habilidad de Rust: agrupar campos del dominio en un struct y poner en impl el comportamiento que usa esos campos." | ||
| "Crear adaptadores `map` y `filter` sin operación terminal, por lo que no se transforma nada.", | ||
| "Omitir el cuadrado y sumar los valores pares originales." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Structs e impl antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Structs e impl debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿En qué llamada comienza realmente la iteración?", | ||
| "¿Por qué cero pasa el filtro par pero no aumenta el total tras elevarse?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Structs e impl a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Analiza los enteros, conserva los pares, elévalos perezosamente al cuadrado y consume la tubería en una suma.", | ||
| "objective": "Expresar un recorrido numérico de varias etapas con un único consumidor terminal.", | ||
| "language_delta": "Las cadenas de iterador codifican préstamos y propiedad de elementos en sus tipos, además de ser perezosas.", | ||
| "prediction_prompt": "Predice cada secuencia intermedia para menos dos, menos uno y cero.", | ||
| "transfer_trap": "No uses `collect` entre cada adaptador; `sum` puede solicitar los elementos directamente sin un `Vec` temporal." | ||
| }, | ||
| "rust-enum-match": { | ||
| "title": "Enums y match", | ||
| "concept": "Enums y match se centra en esta habilidad de Rust: modelar alternativas con variantes enum y cubrir cada variante de forma exhaustiva con match. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Enums y match. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-functions": { | ||
| "title": "Punto de control: valores a través de una función", | ||
| "concept": "`area` recibe dos `i64` por valor y devuelve otro mediante su expresión final. `main` conserva la responsabilidad de la entrada y la salida.", | ||
| "worked_example": "Una función distinta devuelve un `String` asignado por `format!`; así muestra el retorno por expresión con datos no numéricos.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Enums y match en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Enums y match.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Enums y match se centra en esta habilidad de Rust: modelar alternativas con variantes enum y cubrir cada variante de forma exhaustiva con match." | ||
| "Colocar punto y coma tras `width * height` y cambiar el resultado del cuerpo a unidad.", | ||
| "Imprimir dentro de `area` en vez de devolver, acoplando el cálculo a una salida concreta." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Enums y match antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Enums y match debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Puede llamarse dos veces `area` sin estado oculto de la primera?", | ||
| "¿Qué parte de la firma promete que vuelve un `i64`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Enums y match a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Analiza ancho y alto, llama a una función `area` pura e imprime únicamente el valor que devuelve.", | ||
| "objective": "Separar el cálculo tipado de stdin y stdout mediante una frontera reutilizable.", | ||
| "language_delta": "La última expresión puede retornarse sin `return`, pero un punto y coma cambia de forma significativa el tipo del cuerpo.", | ||
| "prediction_prompt": "Predice el tipo del cuerpo con y sin punto y coma final antes de compilar.", | ||
| "transfer_trap": "No traslades un retorno implícito sin revisar la puntuación: en Rust, el punto y coma produce `()`." | ||
| }, | ||
| "rust-option": { | ||
| "title": "Option e if let", | ||
| "concept": "Option e if let se centra en esta habilidad de Rust: hacer explícitos los valores ausentes con Option<T> y extraer Some de forma segura. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Option e if let. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-modules-use": { | ||
| "title": "Acortar una ruta de módulo visible", | ||
| "concept": "El módulo `grading` expone `verdict` con `pub` y `use` introduce su nombre corto en el ámbito exterior. Visibilidad y resolución de nombres son decisiones separadas.", | ||
| "worked_example": "Un elemento anidado `units` se declara público y después se importa antes de llamarlo desde `main`, mostrando todo el recorrido de ruta a alias.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Option e if let en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Option e if let.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Option e if let se centra en esta habilidad de Rust: hacer explícitos los valores ausentes con Option<T> y extraer Some de forma segura." | ||
| "Añadir `use` a una función privada hermana no elude la privacidad.", | ||
| "Escribir una ruta relativa desde otro módulo y resolver otro espacio de nombres o ninguno." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Option e if let antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Option e if let debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Podría `main` llamar `grading::verdict` sin la declaración `use`?", | ||
| "¿Qué palabra permite invocar `verdict` desde fuera de `grading`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Option e if let a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Mantén pública e importada la función y corrige el límite inclusivo para que apruebe toda puntuación mayor o igual que 80.", | ||
| "objective": "Resolver un elemento público mediante `use` y aplicar correctamente la frontera de 80 puntos.", | ||
| "language_delta": "`use` se parece a un alias léxico, no a una importación de ejecución que cargue paquetes.", | ||
| "prediction_prompt": "Predice los veredictos de 79, 80 y 91; explica después si `use` cambia conducta o solo la ruta.", | ||
| "transfer_trap": "Paquetes y crates de Cargo son límites mayores; este laboratorio de un archivo solo muestra módulos del lenguaje." | ||
| }, | ||
| "rust-modules-use": { | ||
| "title": "Módulos y use", | ||
| "concept": "Módulos y use se centra en esta habilidad de Rust: controlar espacio de nombres, privacidad y rutas locales con mod, pub y use. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Módulos y use. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-generics": { | ||
| "title": "Copiar el último elemento de un segmento genérico", | ||
| "concept": "`last_copy` acepta un `slice` compartido de cualquier `T: Copy`. `copied` convierte `Option<&T>` en `Option<T>` sin afirmar que todo tipo Rust sea barato de duplicar.", | ||
| "worked_example": "La función compañera obtiene el primer `u8` de un array prestado y muestra su `Option` mediante formato de depuración.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Módulos y use en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Módulos y use.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Módulos y use se centra en esta habilidad de Rust: controlar espacio de nombres, privacidad y rutas locales con mod, pub y use." | ||
| "Omitir el límite `Copy` e intentar extraer un `T` propio mediante `copied`.", | ||
| "Llamar siempre a `clone`, exigir `Clone` y ocultar una operación potencialmente más costosa." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Módulos y use antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Módulos y use debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué presta `last_copy` y qué valor pequeño devuelve?", | ||
| "¿Cumple `String` este límite o convendría devolver una referencia?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Módulos y use a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Implementa un auxiliar genérico para el último elemento y aplícalo al `slice` de enteros analizados.", | ||
| "objective": "Usar un límite de `trait` que justifique exactamente copiar un valor a través de un préstamo compartido.", | ||
| "language_delta": "Rust comprueba una definición genérica restringida; no aplaza la validación de operaciones arbitrarias hasta una monomorfización posterior.", | ||
| "prediction_prompt": "Predice el `Option` de un `slice` unitario antes de que `main` aplique `unwrap`.", | ||
| "transfer_trap": "No amplíes a `Clone` por costumbre; `Copy` expresa el comportamiento que necesitan estos enteros." | ||
| }, | ||
| "rust-input": { | ||
| "title": "Parseo de entrada", | ||
| "concept": "Parseo de entrada se centra en esta habilidad de Rust: leer stdin una vez, dividir texto en tokens y convertirlos a valores tipados. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Parseo de entrada. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-traits": { | ||
| "title": "Exigir valores resumibles sin clonarlos", | ||
| "concept": "`ScoreCard` implementa `Summary` prestando `self`. La función genérica `render` acepta una referencia a cualquier valor con ese límite y devuelve el texto producido.", | ||
| "worked_example": "La muestra `Size` implementa un comportamiento pequeño para cualquier `Vec<T>` y lo invoca con sintaxis de método.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Parseo de entrada en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Parseo de entrada.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Parseo de entrada se centra en esta habilidad de Rust: leer stdin una vez, dividir texto en tokens y convertirlos a valores tipados." | ||
| "Mover `self.name` fuera de un préstamo compartido, acción prohibida e innecesaria para formatear.", | ||
| "Recibir un `ScoreCard` concreto en `render` y perder la reutilización aportada por `Summary`." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Parseo de entrada antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Parseo de entrada debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Llamar a `summary` consume la tarjeta o la deja utilizable?", | ||
| "¿Dónde declara la función que `T` implementa `Summary`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Parseo de entrada a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Construye una tarjeta y representa sus campos prestados mediante un auxiliar limitado por `Summary`.", | ||
| "objective": "Definir comportamiento compartido e invocarlo sin ceder la propiedad del valor implementador.", | ||
| "language_delta": "Los `trait` permiten despacho estático a través de genéricos sin exigir una jerarquía de herencia.", | ||
| "prediction_prompt": "Decide si `render` puede aceptar dos structs sin relación que implementen `Summary`.", | ||
| "transfer_trap": "No clones campos propios para silenciar un error de préstamo; el formato admite referencias a valores mostrables." | ||
| }, | ||
| "rust-vec-hashmap": { | ||
| "title": "Vec y HashMap", | ||
| "concept": "Vec y HashMap se centra en esta habilidad de Rust: elegir Vec para datos ordenados y la API entry de HashMap para conteo y búsqueda por clave. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Vec y HashMap. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-macros": { | ||
| "title": "Transformar tokens emparejados en una expresión", | ||
| "concept": "`greet!` se exporta y expande a `$crate::helper`, por lo que la búsqueda del auxiliar queda anclada en el crate de definición antes de comprobar tipos.", | ||
| "worked_example": "`caller::run` invoca la macro exportada `increment!` desde un módulo anidado, pero `$crate` todavía resuelve `helper` en la raíz que la definió.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Vec y HashMap en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Vec y HashMap.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Vec y HashMap se centra en esta habilidad de Rust: elegir Vec para datos ordenados y la API entry de HashMap para conteo y búsqueda por clave." | ||
| "Olvidar `!` y pedir al compilador una función en vez de una macro.", | ||
| "Expandir dos veces una expresión de entrada y repetir también sus efectos secundarios." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Vec y HashMap antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Vec y HashMap debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué `$crate` estabiliza la búsqueda si la macro se invoca desde otra ruta?", | ||
| "¿Dónde se comprueba el tipo de la llamada expandida después del emparejamiento?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Vec y HashMap a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Completa el auxiliar de saludo del crate de definición, invocado por la macro exportada desde el módulo anidado, usando el nombre recortado.", | ||
| "objective": "Exportar una macro cuya expansión resuelva higiénicamente un auxiliar mediante `$crate`.", | ||
| "language_delta": "Las macros trabajan sobre sintaxis antes de la ejecución; no son sustitución textual ni reflexión.", | ||
| "prediction_prompt": "Expande `crate::greet!(name)` dentro de `caller::greet_from` e identifica la ruta resuelta.", | ||
| "transfer_trap": "Una ruta relativa al invocador es frágil para auxiliares propios; `$crate` fija el crate de definición." | ||
| }, | ||
| "rust-borrowing-slices": { | ||
| "title": "Préstamos y slices", | ||
| "concept": "Préstamos y slices se centra en esta habilidad de Rust: pasar slices prestados para que las funciones lean datos sin tomar propiedad. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Préstamos y slices. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-cargo-workspaces": { | ||
| "title": "Orientarse en un espacio de trabajo de Cargo", | ||
| "concept": "Un espacio de trabajo coordina paquetes miembros y un directorio de destino compartido. Este ejecutor solo puede comprobar qué comandos seleccionan todos los miembros.", | ||
| "worked_example": "El ejemplo imprime un comando de inspección de metadatos y deja claro que este archivo compilado con `rustc` no ha cargado un manifiesto real.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Préstamos y slices en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Préstamos y slices.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Préstamos y slices se centra en esta habilidad de Rust: pasar slices prestados para que las funciones lean datos sin tomar propiedad." | ||
| "Suponer que `cargo test` desde un miembro cubre necesariamente todo el espacio de trabajo.", | ||
| "Tratar esta correspondencia de texto como prueba de una configuración que el ejecutor nunca construyó." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Préstamos y slices antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Préstamos y slices debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué opción selecciona todo el espacio de trabajo para `cargo check` o `cargo test`?", | ||
| "¿Por qué los argumentos de comprobación de `rustfmt` aparecen después de `--`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Préstamos y slices a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Asocia las intenciones `check`, `test` y `fmt` con las líneas documentadas para todo el espacio de trabajo.", | ||
| "objective": "Identificar mantenimiento seguro de todos los miembros sin fingir una compilación multipaquete.", | ||
| "language_delta": "Los espacios de trabajo pertenecen a Cargo; `rustc` no puede verificar esa estructura del proyecto.", | ||
| "prediction_prompt": "Para `fmt`, separa qué argumentos recibe Cargo y cuáles se reenvían a `rustfmt`.", | ||
| "transfer_trap": "No conviertas este laboratorio en ruta principal hasta que el evaluador cree un espacio de trabajo temporal y observe miembros reales." | ||
| }, | ||
| "rust-result": { | ||
| "title": "Result y ?", | ||
| "concept": "Result y ? se centra en esta habilidad de Rust: devolver errores recuperables con Result<T, E> y propagarlos con el operador ?. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Result y ?. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-ownership": { | ||
| "title": "Transferir y recuperar la propiedad de un `String`", | ||
| "concept": "`main` quita los finales de línea del `String` original y mueve su propietario a `describe`. `String` contiene puntero, longitud y capacidad; la entrada vacía puede permanecer sin asignación dinámica.", | ||
| "worked_example": "`wrap` consume los metadatos del `String` y toma la propiedad del búfer existente. Después, `main` ya no puede reutilizar el enlace movido.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Result y ? en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Result y ?.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Result y ?" | ||
| "Leer el enlace exterior después de pasarlo por valor y obtener un error de valor movido.", | ||
| "Añadir `clone` ante cualquier error de propiedad y asignar memoria innecesaria en vez de elegir otra firma." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Result y ? antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Result y ? debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿En qué llamada sale la propiedad de `main` y mediante qué patrón recupera `main` un propietario?", | ||
| "¿Por qué `describe` puede llamar a `len` antes de mover el texto a la tupla?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Result y ? a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Elimina los finales de línea dentro del `String` original, muévelo a `describe` y devuélvelo junto con su longitud UTF-8.", | ||
| "objective": "Mover y devolver el mismo propietario sin clonar; una entrada vacía puede seguir sin reservar memoria.", | ||
| "language_delta": "Mover un `String` transfiere tres metadatos de tamaño fijo y no copia su búfer ni reduce el valor a una sola palabra de puntero.", | ||
| "prediction_prompt": "Predice la cantidad de bytes de `é` antes de compararla con el conteo de escalares posterior.", | ||
| "transfer_trap": "`Box`, `Arc` y `RefCell` expresan otras estructuras; aprende primero este movimiento ordinario sin copias." | ||
| }, | ||
| "rust-ownership": { | ||
| "title": "Propiedad y préstamos", | ||
| "concept": "Propiedad y préstamos se centra en esta habilidad de Rust: seguir cuándo un String se mueve, cuándo vuelve y cuándo una referencia solo lo presta. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Propiedad y préstamos. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-smart-pointers": { | ||
| "title": "Poseer indirectamente el siguiente nodo recursivo", | ||
| "concept": "`List::Cons` guarda su cola en `Box<List>`. Así cada `enum` tiene tamaño finito conocido y mantiene propiedad exclusiva sobre la cadena recursiva.", | ||
| "worked_example": "La muestra construye dos enlaces en cajas y los recorre mediante referencias compartidas, por lo que visitar la lista no la consume.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Propiedad y préstamos en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Propiedad y préstamos.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Propiedad y préstamos se centra en esta habilidad de Rust: seguir cuándo un String se mueve, cuándo vuelve y cuándo una referencia solo lo presta." | ||
| "Colocar `List` directamente dentro de `Cons` y crear un tipo de tamaño infinito rechazado.", | ||
| "Afirmar que `Box` acelera el código; aquí importa la indirección propietaria de tamaño conocido." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Propiedad y préstamos antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Propiedad y préstamos debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué parte de `Cons` mantiene tamaño fijo aunque la cola sea arbitrariamente larga?", | ||
| "¿Por qué construir desde un iterador invertido conserva el orden original?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Propiedad y préstamos a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Pliega los enteros analizados en una lista recursiva con cajas e imprime la suma obtenida al recorrerla.", | ||
| "objective": "Representar un tipo recursivo propietario mediante `Box` y visitarlo sin mover sus nodos.", | ||
| "language_delta": "Un lenguaje con recolección puede ocultar la indirección; Rust exige que la arista de propiedad aparezca en el tipo.", | ||
| "prediction_prompt": "Dibuja la cadena `Cons` de tres tokens antes de evaluar su suma recursiva.", | ||
| "transfer_trap": "`Arc` y `Rc` expresan propiedad compartida; esta cola tiene un único propietario y solo necesita `Box`." | ||
| }, | ||
| "rust-iterators": { | ||
| "title": "Iteradores y closures", | ||
| "concept": "Iteradores y closures se centra en esta habilidad de Rust: componer adaptadores iteradores perezosos con closures y consumirlos con sum o collect. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Iteradores y closures. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-interior-mutability": { | ||
| "title": "Préstamo mutable dinámico dentro de un propietario inmutable", | ||
| "concept": "`EventLog` expone `record` mediante `&self` y `RefCell` comprueba dinámicamente préstamos compartidos y mutables. La guarda mutable termina después de `push`, antes del préstamo final.", | ||
| "worked_example": "`Cell` maneja un entero `Copy` sin prestar referencias, de modo que la muestra incrementa un contador a través de un enlace inmutable.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Iteradores y closures en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Iteradores y closures.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Iteradores y closures se centra en esta habilidad de Rust: componer adaptadores iteradores perezosos con closures y consumirlos con sum o collect." | ||
| "Mantener `borrow_mut` y pedir a la vez `borrow`, lo que provoca pánico por accesos solapados.", | ||
| "Un `RefCell<T>` propio puede moverse a otro hilo si `T: Send`, pero no es `Sync` y no protege mutación compartida concurrente." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Iteradores y closures antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Iteradores y closures debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Dónde deja de estar viva la guarda `RefMut` creada dentro de `record`?", | ||
| "¿Qué exclusividad se mantiene aunque el propietario nunca se declare `mut`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Iteradores y closures a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Registra cada token mediante `&self` e informa de la longitud final del vector almacenado en `RefCell`.", | ||
| "objective": "Aplicar mutabilidad interior con préstamos cortos, no solapados y de un solo hilo.", | ||
| "language_delta": "`RefCell` pospone las reglas de exclusividad hasta la ejecución; no las elimina.", | ||
| "prediction_prompt": "Predice qué ocurre si `record` conserva una guarda mutable y solicita otra.", | ||
| "transfer_trap": "`RefCell` no sincroniza hilos; usa `Mutex` cuando varios comparten y modifican el mismo valor." | ||
| }, | ||
| "rust-generics": { | ||
| "title": "Genéricos", | ||
| "concept": "Genéricos se centra en esta habilidad de Rust: escribir una función sobre T manteniendo chequeo de tipos concretos en compilación. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Genéricos. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-strings": { | ||
| "title": "Medir tres vistas distintas de texto UTF-8", | ||
| "concept": "`String` y `str` almacenan UTF-8. `len` cuenta bytes, `chars` produce valores escalares Unicode y un corte válido debe comenzar y terminar en fronteras de carácter.", | ||
| "worked_example": "El escalar del cangrejo ocupa cuatro bytes; por eso la muestra corta de forma segura el rango de cuatro bytes e informa de cantidades de bytes y escalares diferentes.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Genéricos en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Genéricos.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Genéricos se centra en esta habilidad de Rust: escribir una función sobre T manteniendo chequeo de tipos concretos en compilación." | ||
| "Intentar `text[0]`, aunque un byte no equivale necesariamente a un carácter.", | ||
| "Cortar en un desplazamiento de byte arbitrario y provocar pánico aunque sea menor que `len`." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Genéricos antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Genéricos debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Cuántos bytes y valores escalares contiene la `é` de los casos?", | ||
| "¿Por qué el prefijo del cangrejo abarca cuatro bytes y no uno?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Genéricos a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Imprime por separado la longitud UTF-8 en bytes, el conteo escalar y el primer escalar del texto recibido.", | ||
| "objective": "Elegir `len`, `chars` y una consulta segura según la medida solicitada.", | ||
| "language_delta": "Rust cuenta bytes UTF-8; Java y JavaScript usan unidades UTF-16, y segmentar grafemas requeriría lógica Unicode adicional.", | ||
| "prediction_prompt": "Predice los tres campos para el caso cangrejo seguido de `a` antes de compilar.", | ||
| "transfer_trap": "No apliques índices ASCII a `str`: sus posiciones son desplazamientos en bytes sujetos a fronteras de carácter válidas." | ||
| }, | ||
| "rust-traits": { | ||
| "title": "Traits y bounds", | ||
| "concept": "Traits y bounds se centra en esta habilidad de Rust: describir comportamiento requerido con traits y usar bounds para invocarlo genéricamente. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Traits y bounds. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-borrowing-slices": { | ||
| "title": "Punto de control: prestar un segmento de la entrada propia", | ||
| "concept": "`main` posee el búfer `String`; `first_word` lo presta temporalmente y devuelve un `&str` dentro del mismo almacenamiento. No se asigna memoria para la palabra.", | ||
| "worked_example": "El auxiliar `key` usa `split_once` para prestar el prefijo de un registro propio hasta que ese registro deja de necesitarse.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Traits y bounds en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Traits y bounds.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Traits y bounds se centra en esta habilidad de Rust: describir comportamiento requerido con traits y usar bounds para invocarlo genéricamente." | ||
| "Devolver un `slice` de un `String` local y dejar una referencia a almacenamiento ya destruido.", | ||
| "Llamar a `to_owned` dentro de `first_word` y cambiar la API con una asignación evitable." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Traits y bounds antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Traits y bounds debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué valor debe seguir vivo mientras se imprime la primera palabra prestada?", | ||
| "¿Por qué la entrada vacía produce con seguridad `&str` vacío sin `unwrap`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Traits y bounds a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Presta el búfer completo de entrada y devuelve como `&str` su primera palabra separada por espacios.", | ||
| "objective": "Relacionar el `slice` devuelto con el `String` del llamador y manejar la ausencia sin asignar.", | ||
| "language_delta": "Las referencias Rust expresan relaciones de validez en compilación; otros entornos suelen ocultar el vínculo de propiedad de una subcadena.", | ||
| "prediction_prompt": "Decide si el espacio inicial pertenece al resultado cuando se usa `split_whitespace`.", | ||
| "transfer_trap": "Los tiempos de vida, objetos de `trait` y punteros inseguros parten de esta misma relación limitada por el propietario." | ||
| }, | ||
| "rust-lifetimes": { | ||
| "title": "Lifetimes", | ||
| "concept": "Lifetimes se centra en esta habilidad de Rust: anotar relaciones entre entradas y salidas prestadas sin alargar el almacenamiento. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Lifetimes. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Relacionar los tiempos de vida de una elección devuelta", | ||
| "concept": "`longer` puede devolver cualquiera de sus argumentos; un parámetro de tiempo de vida relaciona las dos entradas con la salida. El texto subyacente sigue perteneciendo al llamador.", | ||
| "worked_example": "`shorter` necesita `'a` explícita porque dos entradas prestadas impiden a las reglas de elisión elegir de cuál procede la referencia devuelta.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Lifetimes en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Lifetimes.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Lifetimes se centra en esta habilidad de Rust: anotar relaciones entre entradas y salidas prestadas sin alargar el almacenamiento." | ||
| "Esperar que la elisión seleccione entre dos referencias de entrada; esa relación debe escribirse.", | ||
| "Comparar `str::len` y ordenar bytes UTF-8 cuando se piden valores escalares Unicode." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Lifetimes antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Lifetimes debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué memoria asigna o retiene una anotación de tiempo de vida en ejecución? Ninguna.", | ||
| "¿Por qué la salida solo puede durar lo permitido por la entrada elegida?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Lifetimes a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Separa la entrada por la barra y devuelve prestado el lado con más escalares, prefiriendo el izquierdo en un empate.", | ||
| "objective": "Anotar una relación de referencias sin cambiar propiedad ni duración del almacenamiento.", | ||
| "language_delta": "Los tiempos de vida expresan razonamiento estático sobre préstamos; no son contadores que mantengan activa una asignación.", | ||
| "prediction_prompt": "Compara `é` con `aa` primero por bytes y luego por escalares y explica el ganador.", | ||
| "transfer_trap": "No añadas `'static` para callar el compilador; exigiría datos globales en vez de describir los préstamos reales." | ||
| }, | ||
| "rust-traits-lifetimes": { | ||
| "title": "Objetos trait y despacho dyn", | ||
| "concept": "Objetos trait y despacho dyn se centra en esta habilidad de Rust: usar &dyn Trait cuando la flexibilidad del despacho en tiempo de ejecución compensa frente a un tipo genérico. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Objetos trait y despacho dyn. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Despacho dinámico con un resultado prestado", | ||
| "concept": "`render` acepta `&dyn Draw`, elige la implementación mediante una tabla virtual y devuelve un `&str` prestado del componente como máximo durante el préstamo de este.", | ||
| "worked_example": "La muestra envía un `Tag` a través de un objeto de `trait Named` y recibe su texto interior sin clonar el `String` propietario.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Objetos trait y despacho dyn en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Objetos trait y despacho dyn.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Objetos trait y despacho dyn se centra en esta habilidad de Rust: usar &dyn Trait cuando la flexibilidad del despacho en tiempo de ejecución compensa frente a un tipo genérico." | ||
| "Añadir un método genérico sin restricción `where Self: Sized`; esa excepción es necesaria para mantener compatible con `dyn` el resto del `trait`.", | ||
| "Devolver una referencia a un `String` temporal creado dentro de `draw`, que quedaría colgante." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Objetos trait y despacho dyn antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Objetos trait y despacho dyn debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué sigue perteneciendo a `Button` o `Label` después de volver de `render`?", | ||
| "¿Qué llamada usa despacho dinámico y qué tiempo de vida se infiere para el `str` devuelto?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Objetos trait y despacho dyn a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Construye el componente solicitado y representa su etiqueta prestada mediante una referencia `dyn Draw`.", | ||
| "objective": "Combinar despacho dinámico seguro para objetos con un resultado ligado al préstamo receptor.", | ||
| "language_delta": "Un objeto de `trait` elige en ejecución; un límite genérico suele monomorfizarse para cada tipo concreto.", | ||
| "prediction_prompt": "Predice qué implementación recibe `label:Rust` y hasta cuándo es válida su referencia devuelta.", | ||
| "transfer_trap": "No encierres todo en `Box`: `main` ya posee el componente y basta un préstamo corto `&dyn Draw`." | ||
| }, | ||
| "rust-testing": { | ||
| "title": "Pruebas y aserciones", | ||
| "concept": "Pruebas y aserciones se centra en esta habilidad de Rust: separar lógica pura de main para que funciones #[test] y macros assert verifiquen comportamiento. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Pruebas y aserciones. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-unsafe": { | ||
| "title": "Obligación de seguridad local y explícita", | ||
| "concept": "`read_copy` convierte una referencia compartida válida en puntero crudo y realiza una lectura insegura. El comentario documenta tiempo de vida, alineación y posibilidad de copia.", | ||
| "worked_example": "La muestra demuestra que existe el índice cero antes de llamar `get_unchecked` dentro de un bloque pequeño cuya invariante puede revisarse.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Pruebas y aserciones en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Pruebas y aserciones.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Pruebas y aserciones se centra en esta habilidad de Rust: separar lógica pura de main para que funciones #[test] y macros assert verifiquen comportamiento." | ||
| "Usar `unsafe` como escape general, aunque no vuelve válido un puntero colgante o mal alineado.", | ||
| "En Rust 2024, una operación insegura directa dentro de `unsafe fn` activa por defecto `unsafe_op_in_unsafe_fn`; configurar ese `lint` como `deny` convierte la advertencia en error." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Pruebas y aserciones antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Pruebas y aserciones debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué hechos hacen válida la llamada `pointer.read` en esa línea?", | ||
| "¿Qué referencia segura garantiza que el dato siga vivo durante la operación?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Pruebas y aserciones a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Oculta la copia mediante puntero crudo detrás de una función segura con una invariante documentada.", | ||
| "objective": "Limitar lo inseguro a una operación auditable sin imponer obligaciones adicionales a quien llama.", | ||
| "language_delta": "Rust 2024 advierte si una `unsafe fn` ejecuta una operación insegura fuera de un bloque explícito, que delimita la revisión.", | ||
| "prediction_prompt": "Imagina que se destruye la referencia antes de leer e identifica qué condición deja de cumplirse.", | ||
| "transfer_trap": "No marques insegura toda la función si el envoltorio puede establecer internamente cada precondición." | ||
| }, | ||
| "rust-smart-pointers": { | ||
| "title": "Smart pointers", | ||
| "concept": "Smart pointers se centra en esta habilidad de Rust: usar Box<T> para poseer datos en heap y trabajar con comportamiento tipo puntero mediante deref. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Smart pointers. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-structs-impl": { | ||
| "title": "Comportamiento prestado junto a sus datos", | ||
| "concept": "`Rectangle` posee dos dimensiones; `area` presta `&self` y deja disponible la instancia después del cálculo. La llamada de método suministra automáticamente ese receptor.", | ||
| "worked_example": "`Counter` muestra otro método de solo lectura cuyo receptor se presta en vez de consumirse o prestarse de forma mutable.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Smart pointers en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Smart pointers.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Smart pointers se centra en esta habilidad de Rust: usar Box<T> para poseer datos en heap y trabajar con comportamiento tipo puntero mediante deref." | ||
| "Declarar `area(self)` y consumir el rectángulo aunque el cálculo solo lee campos.", | ||
| "Confundir `Rectangle::area` con una función asociada sin receptor e ignorar `&self`." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Smart pointers antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Smart pointers debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Puede llamarse dos veces `rectangle.area()` y qué receptor lo permite?", | ||
| "¿Sería necesario `&mut self` para una multiplicación que no cambia campos?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Smart pointers a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Construye un `Rectangle` desde las dimensiones analizadas e imprime el resultado de su método `area` no consumidor.", | ||
| "objective": "Modelar campos relacionados y añadir comportamiento mediante `impl` sin consumir el valor.", | ||
| "language_delta": "Los receptores Rust revelan si una llamada consume, muta o solo presta el objeto.", | ||
| "prediction_prompt": "Decide si cambiar `&self` por `&mut self` aporta algo a una multiplicación pura.", | ||
| "transfer_trap": "Genéricos y `trait` pueden generalizar después el comportamiento; un `impl` concreto basta para esta frontera." | ||
| }, | ||
| "rust-interior-mutability": { | ||
| "title": "Mutabilidad interior", | ||
| "concept": "Mutabilidad interior se centra en esta habilidad de Rust: usar RefCell<T> cuando el chequeo de préstamos en ejecución es el modelo correcto más simple. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Mutabilidad interior. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-enum-match": { | ||
| "title": "Alternativas cerradas y exhaustivas", | ||
| "concept": "`Command` representa sumar, multiplicar o salir con datos propios de cada variante. `evaluate` nombra todos los casos y `rustc` avisa si se incorpora otra variante.", | ||
| "worked_example": "El `match` del semáforo cubre tres variantes sin carga y devuelve una duración `u8` desde la expresión.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Mutabilidad interior en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Mutabilidad interior.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Mutabilidad interior se centra en esta habilidad de Rust: usar RefCell<T> cuando el chequeo de préstamos en ejecución es el modelo correcto más simple." | ||
| "Usar una rama comodín y perder el diagnóstico útil cuando crezca el `enum`.", | ||
| "Leer cargas antes de decidir la variante y complicar el caso `Quit`, que no posee ninguna." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Mutabilidad interior antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Mutabilidad interior debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué variantes contienen dos enteros y cuál no contiene datos?", | ||
| "¿Qué ayuda del compilador se pierde al absorber todo con `_`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Mutabilidad interior a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Analiza el comando en su variante de `enum` y evalúalo mediante brazos explícitos y exhaustivos de `match`.", | ||
| "objective": "Representar un conjunto cerrado con cargas por variante y coincidencia total de patrones.", | ||
| "language_delta": "Rust comprueba la exhaustividad estáticamente; otros `switch` permiten omisiones o ejecución en cascada entre casos.", | ||
| "prediction_prompt": "Predice qué nombres se enlazan en cada brazo antes de calcular.", | ||
| "transfer_trap": "`Option` también es un `enum`; sus variantes `Some` y `None` merecen el mismo manejo deliberado." | ||
| }, | ||
| "rust-concurrency": { | ||
| "title": "Hilos y join", | ||
| "concept": "Hilos y join se centra en esta habilidad de Rust: mover trabajo a thread::spawn y usar join para recuperar con seguridad el resultado del worker. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Hilos y join. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-option": { | ||
| "title": "Ramificar por presencia sin forzar el valor", | ||
| "concept": "El primer escalar Unicode es `Some(char)` para texto no vacío y `None` en caso contrario. Un `match` explícito produce una salida útil para ambos estados normales.", | ||
| "worked_example": "`checked_half` usa `then_some` para crear un `Option` solo cuando el entero es par, y el formato de depuración muestra el envoltorio.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Hilos y join en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Hilos y join.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Hilos y join se centra en esta habilidad de Rust: mover trabajo a thread::spawn y usar join para recuperar con seguridad el resultado del worker." | ||
| "Llamar a `unwrap` y tratar la entrada vacía esperada como un error de programación.", | ||
| "Usar `bytes().next()` y obtener el primer byte UTF-8 en vez del escalar Unicode." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Hilos y join antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Hilos y join debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué carga contiene `Some` en el resultado de `chars().next()`?", | ||
| "¿Qué rama se ejecuta para una cadena recortada de longitud cero?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Hilos y join a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Examina el primer escalar opcional e imprime ese `char` o la palabra de ausencia documentada.", | ||
| "objective": "Manejar exhaustivamente una ausencia normal sin nulos, centinelas ni pánico.", | ||
| "language_delta": "`Option` incorpora la ausencia al tipo, a diferencia de referencias anulables que pueden olvidarse hasta ejecutar.", | ||
| "prediction_prompt": "Predice el escalar elegido en el caso coreano, sin razonar como si fueran bytes.", | ||
| "transfer_trap": "Reserva `unwrap` para ausencias que violen una invariante; aquí el texto vacío forma parte del contrato." | ||
| }, | ||
| "rust-shared-state": { | ||
| "title": "Estado compartido con Arc y Mutex", | ||
| "concept": "Estado compartido con Arc y Mutex se centra en esta habilidad de Rust: combinar Arc<T> para propiedad compartida con Mutex<T> para mutación de a una vez. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Estado compartido con Arc y Mutex. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-result": { | ||
| "title": "Propagar errores de análisis desde un auxiliar puro", | ||
| "concept": "`sum_tokens` usa `try_fold` para aplicar `?` a cada conversión. El primer `ParseIntError` sale del auxiliar y `main` decide cómo representarlo.", | ||
| "worked_example": "`positive` muestra la propagación con `?` seguida de otra validación de dominio, separando claramente propagación y recuperación.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Estado compartido con Arc y Mutex en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Estado compartido con Arc y Mutex.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Estado compartido con Arc y Mutex se centra en esta habilidad de Rust: combinar Arc<T> para propiedad compartida con Mutex<T> para mutación de a una vez." | ||
| "Leer `?` como captura, aunque devuelve inmediatamente el error desde la función exterior.", | ||
| "Forzar cada token y evitar que `main` convierta una entrada mal formada en la línea estable requerida." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Estado compartido con Arc y Mutex antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Estado compartido con Arc y Mutex debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Cuál es el retorno de la función que contiene el signo de interrogación?", | ||
| "¿Visita `try_fold` tokens posteriores al primer `Err`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Estado compartido con Arc y Mutex a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Suma tokens enteros mediante `Result` e imprime un marcador estable si falla cualquiera de las conversiones.", | ||
| "objective": "Separar conversión falible y recuperación visible conservando la ruta original del error.", | ||
| "language_delta": "`Result` hace explícito el fallo recuperable en la firma, en vez de permitir excepciones que crucen llamadas sin marcar.", | ||
| "prediction_prompt": "Sigue el acumulador para `2 bad` e identifica dónde retorna el control.", | ||
| "transfer_trap": "No conviertas cada fallo en pánico; quien llama posee aquí la política de salida." | ||
| }, | ||
| "rust-async-await": { | ||
| "title": "Async y await", | ||
| "concept": "Async y await se centra en esta habilidad de Rust: reconocer que async crea un Future y que ejecutarlo requiere un executor o runtime. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Async y await. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-testing": { | ||
| "title": "Comprobaciones dentro de la ruta que ejecuta el evaluador", | ||
| "concept": "El binario de referencia llama `assert_eq!` antes de leer stdin, por lo que esas comprobaciones se ejecutan. Un módulo `cfg(test)` no existe en esta compilación ordinaria.", | ||
| "worked_example": "La muestra `triple` ejecuta una aserción en línea y después imprime un estado que no es respuesta; demuestra la comprobación sin arnés de pruebas.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Async y await en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Async y await.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Async y await se centra en esta habilidad de Rust: reconocer que async crea un Future y que ejecutarlo requiere un executor o runtime." | ||
| "Suponer que funciones `#[test]` corren en un binario normal de `rustc`, aunque nunca se construyó el arnés.", | ||
| "Usar aserciones para validar entrada no confiable y provocar pánico en lugar de un error controlado." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Async y await antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Async y await debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "Si una igualdad en línea es falsa, ¿se alcanza el `println!` posterior?", | ||
| "¿Qué activa el arnés: `cargo test` o `rustc --test`, frente a `rustc` normal?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Async y await a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Implementa `add_two`, conserva dos aserciones ejecutables de frontera e imprime el resultado para la entrada.", | ||
| "objective": "Dejar una comprobación real en el mismo modo de ejecución usado por esta lección.", | ||
| "language_delta": "Los atributos de prueba dependen del modo de compilación; una aserción llamada en `main` no necesita generar un arnés.", | ||
| "prediction_prompt": "Predice el proceso si `add_two` devuelve su argumento sin cambios mientras siguen las aserciones.", | ||
| "transfer_trap": "Este laboratorio enseña aserciones ejecutables y el límite del ejecutor, no dominio completo de pruebas unitarias." | ||
| }, | ||
| "rust-macros": { | ||
| "title": "macro_rules!", | ||
| "concept": "macro_rules! se centra en esta habilidad de Rust: emparejar patrones de tokens con macro_rules! y expandir código Rust repetido en compilación. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de macro_rules!. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-async-await": { | ||
| "title": "Ejecutar `await` con un ejecutor deliberadamente limitado", | ||
| "concept": "`double` espera `std::future::ready` y `block_on_ready` sondea el `Future` una sola vez. `Pending` es un error porque este ejecutor no despierta ni vuelve a sondear.", | ||
| "worked_example": "`message` contiene un `.await` real y el mismo mecanismo de un solo sondeo lo impulsa, haciendo observable la ejecución en vez de abandonar un futuro creado.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de macro_rules! en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de macro_rules!.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: macro_rules!" | ||
| "Llamar a `double` sin sondear su `Future`, por lo que no se ejecuta el cuerpo asíncrono.", | ||
| "Usar este auxiliar de un sondeo para temporizadores, E/S o cualquier futuro que pueda quedar pendiente." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica macro_rules! antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de macro_rules! debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Cuándo empieza a ejecutarse el futuro: al llamar a `double` o al hacer `poll`?", | ||
| "¿Qué condición permite aquí un despertador que no hace nada? El futuro esperado está listo inmediatamente." | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando macro_rules! a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Transforma el número dentro del futuro listo e impulsa ese cálculo asíncrono hasta obtener su salida.", | ||
| "objective": "Observar un `await` genuino sin ocultar la limitación de un solo sondeo.", | ||
| "language_delta": "El código `async` de Rust permanece inerte hasta que un ejecutor sondea el `Future`; no inicia trabajo automáticamente.", | ||
| "prediction_prompt": "Predice qué brazo de `match` recibe el futuro listo y por qué no hay un segundo sondeo.", | ||
| "transfer_trap": "Usa un ejecutor de producción si puede aparecer `Pending`; convertir este auxiliar en planificador queda fuera de este entorno." | ||
| }, | ||
| "rust-unsafe": { | ||
| "title": "Rust unsafe", | ||
| "concept": "Rust unsafe se centra en esta habilidad de Rust: poner operaciones no verificables en bloques unsafe manteniendo la API externa lo más segura posible. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Rust unsafe. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-concurrency": { | ||
| "title": "Esperar el resultado propio de un trabajador", | ||
| "concept": "El `i64` analizado implementa `Copy`, así que una clausura `move` recibe su copia sin invalidar necesariamente el enlace exterior. `join` sincroniza y devuelve el cálculo.", | ||
| "worked_example": "La muestra crea un hilo para una multiplicación y consume su manejador antes de imprimir; el proceso no puede salir antes de disponer del resultado.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Rust unsafe en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Rust unsafe.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Rust unsafe se centra en esta habilidad de Rust: poner operaciones no verificables en bloques unsafe manteniendo la API externa lo más segura posible." | ||
| "Descartar `JoinHandle` separa el trabajador; no espera implícitamente su finalización.", | ||
| "Prestar datos de pila a una clausura lanzada y fallar el requisito `'static` porque el hilo podría durar más." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Rust unsafe antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Rust unsafe debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué valor copiado cruza al hilo y por qué podría seguir utilizándose el original?", | ||
| "¿Qué error de `join` convierte `expect` en pánico del hilo principal?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Rust unsafe a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Captura el entero `Copy` en un trabajador, duplícalo allí, espera con `join` e imprime el valor devuelto.", | ||
| "objective": "Coordinar explícitamente un resultado sin depender de tiempos ni del cierre del proceso.", | ||
| "language_delta": "`spawn` crea un hilo del sistema planificado independientemente; puede empezar antes o después de que continúe el hilo llamador, y `join` es el punto de sincronización.", | ||
| "prediction_prompt": "Predice qué resultado garantiza `join` sin suponer qué hilo obtiene CPU primero.", | ||
| "transfer_trap": "No hace falta estado compartido para un único retorno; `Arc` y `Mutex` pertenecen al proyecto de varios trabajadores." | ||
| }, | ||
| "rust-cargo-workspaces": { | ||
| "title": "Paquetes Cargo y workspaces", | ||
| "concept": "Paquetes Cargo y workspaces se centra en esta habilidad de Rust: entender cuándo la estructura de paquetes Cargo y cargo check --workspace importan más allá de un archivo. Apréndela siguiendo el valor tipado en el ejemplo, desde su creación hasta las decisiones de movimiento o préstamo y la expresión que llega a stdout.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Paquetes Cargo y workspaces. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "rust-shared-state": { | ||
| "title": "Proyecto final: mutación compartida entre trabajadores unidos", | ||
| "concept": "`main` posee el `Vec` y crea un hilo por entero. Cada clausura posee su valor y un clon de `Arc`, calcula fuera de la sección crítica y mantiene la guarda de `Mutex` brevemente.", | ||
| "worked_example": "La muestra lanza dos hilos reales, consume ambos manejadores y solo entonces lee el total protegido; ninguna actualización queda pendiente.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Paquetes Cargo y workspaces en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Paquetes Cargo y workspaces.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Paquetes Cargo y workspaces se centra en esta habilidad de Rust: entender cuándo la estructura de paquetes Cargo y cargo check --workspace importan más allá de un archivo." | ||
| "Tratar `lock` como acceso instantáneo, aunque puede bloquear o devolver envenenamiento tras un pánico.", | ||
| "Mantener la guarda durante cálculos ajenos y alargar la contención; el cuadrado se calcula antes de bloquear." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Paquetes Cargo y workspaces antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Paquetes Cargo y workspaces debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Se consumen todos los `JoinHandle` antes de tomar la guarda final?", | ||
| "¿Dónde aparecen iteración, clones de `Arc`, movimientos a hilos y ámbito de `MutexGuard`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Paquetes Cargo y workspaces a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Lanza y espera un trabajador por entrada y añade cada cuadrado a un total compartido `Arc<Mutex<i64>>`.", | ||
| "objective": "Integrar propiedad, iteración, hilos, sincronización y salida determinista en un programa acotado.", | ||
| "language_delta": "`Arc` gestiona un tiempo de vida compartido y `Mutex` la exclusión; `RefCell` no es `Sync` y no protege mutación concurrente.", | ||
| "prediction_prompt": "Explica por qué el orden de planificación no cambia la suma final después de unir todos los hilos.", | ||
| "transfer_trap": "Aquí corresponde `Arc`, no `Box`; `Mutex` ya impone la invariante de alias, por lo que `unsafe` sería innecesario." | ||
| } | ||
| } | ||
| } |
+372
-285
@@ -7,437 +7,524 @@ { | ||
| "rust-output": { | ||
| "title": "出力", | ||
| "concept": "出力 の目標は次の Rust 文法です: println! のフォーマット文字列で値を出力し、判定に必要な stdout を正確に合わせること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「出力」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "標準出力を正確に一致させる", | ||
| "concept": "`println!`マクロは書式指定と引数の対応をコンパイル時に検査し、整形した文字列の後へ改行をちょうど1つ追加します。", | ||
| "worked_example": "例はツール名とエディション番号をハイフンで結び、演習の答えを写さずに、変数を直接参照する書式引数を示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「出力」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「出力」演習の TODO を解かないこと。", | ||
| "出力 の目標は次の Rust 文法です: println! という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`println!`を通常関数のように書き、必須の感嘆符を省くことです。", | ||
| "コロンの前後へ空白を加え、値が正しくても標準出力を変えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「出力」を適用してから `example:` 出力へつながりますか。", | ||
| "「出力」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "書式文字列が作る文字と、マクロが追加する改行を分けて示せますか。", | ||
| "末尾改行が不要な出力なら、`print!`と`println!`のどちらが適切ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「出力」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "名前と点数を読み、ケースで示された区切りを使って2項目を正確に整形してください。", | ||
| "objective": "コンパイル時に検査された1行を作り、必要な標準出力と同じバイト列を出します。", | ||
| "language_delta": "多くの言語の文字列連結と違い、Rustの書式はテンプレートと型付き引数を分け、その対応をコンパイル時に検証します。", | ||
| "prediction_prompt": "実行前に、中括弧の外にある句読点が置換値ごとに繰り返されるか予測してください。", | ||
| "transfer_trap": "`println!`を可変個引数の通常関数だと思わないでください。静的に既知の書式文字列を展開するマクロです。" | ||
| }, | ||
| "rust-variables": { | ||
| "title": "束縛と可変性", | ||
| "concept": "束縛と可変性 の目標は次の Rust 文法です: まず不変の let 束縛を使い、本当に値が変わる場所だけ mut を付けること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「束縛と可変性」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "1つの可変束縛で状態を追う", | ||
| "concept": "`mut`付き束縛には同じ保存場所へ再代入できます。同名の新しい`let`はシャドーイングであり、元を変更せず別の束縛を導入します。", | ||
| "worked_example": "例は`value`を1回更新した後、変換済み結果で同名をシャドーイングし、2つの仕組みを短いプログラム内で区別します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「束縛と可変性」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「束縛と可変性」演習の TODO を解かないこと。", | ||
| "束縛と可変性 の目標は次の Rust 文法です: まず不変の let 束縛を使い、本当に値が変わる場所だけ mut を付けること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`mut`なしで`value += 1`を書き、不変束縛へ再代入することです。", | ||
| "シャドーイングをその場での更新と説明し、古い束縛へアクセスできなくなる時点を隠すことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「束縛と可変性」を適用してから `example:` 出力へつながりますか。", | ||
| "「束縛と可変性」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "例のどの行が保存場所を変更し、どの行が新しい束縛を作りますか。", | ||
| "累積値が変わった後に出力するだけなら、ラベルにも`mut`が必要ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「束縛と可変性」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "ラベルは不変のまま、後続の全整数を1つの可変累積値へ加えてください。", | ||
| "objective": "意図的に変わる合計と、文脈を示す不変データを区別します。", | ||
| "language_delta": "Rustは束縛の可変性を明示します。シャドーイングなら変数を可変にせず、同じ名前で型まで変えられます。", | ||
| "prediction_prompt": "最終整形行を確認する前に、各ループ反復後の合計を予測してください。", | ||
| "transfer_trap": "再代入とシャドーイングを同じものとして扱う言語感覚を持ち込まず、どちらが新しい束縛を作るか確認してください。" | ||
| }, | ||
| "rust-numbers-tuples": { | ||
| "title": "数値とタプル", | ||
| "concept": "数値とタプル の目標は次の Rust 文法です: 明示的な数値型と pair.0、pair.1 のようなタプルフィールドアクセスを組み合わせること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「数値とタプル」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "符号付き除算をタプルで観察する", | ||
| "concept": "有効な`i64`被演算子では、`/`は商を`0`方向へ切り捨て、`%`は対応する余りを返します。除数`0`とオーバーフローする`i64::MIN / -1`は入力契約から除外されます。", | ||
| "worked_example": "`23`を`4`で割った商と余りをタプルへ入れ、`result.0`と`result.1`から独立に観察します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「数値とタプル」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「数値とタプル」演習の TODO を解かないこと。", | ||
| "数値とタプル の目標は次の Rust 文法です: 明示的な数値型と pair.0、pair.1 のようなタプルフィールドアクセスを組み合わせること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "Pythonの床除算を持ち込み、負の被除数で誤った商を求めることです。", | ||
| "タプル添字を入れ替え、コンパイルは通るまま出力フィールドを逆にすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「数値とタプル」を適用してから `example:` 出力へつながりますか。", | ||
| "「数値とタプル」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`-17 / 5`の切り捨ては`0`と負の無限大のどちらへ進みますか。", | ||
| "除算恒等式を確認し、除外されたオーバーフローの組も特定できますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「数値とタプル」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "除数が`0`でなく、`i64::MIN / -1`でもない2整数を解析し、商と余りを順に出力してください。", | ||
| "objective": "Rustの符号付き演算規則を守り、2つの数値結果をタプルとして分解します。", | ||
| "language_delta": "Rustの整数`/`は、符号が異なる場合に床するPythonの`//`と違い、`0`方向へ切り捨てます。", | ||
| "prediction_prompt": "`rustc`を呼ぶ前に、符号が異なるテストデータの商と余りを計算してください。", | ||
| "transfer_trap": "同じスラッシュでも被演算子型が整数なら浮動小数点除算にはなりません。型が演算の意味を決めます。" | ||
| }, | ||
| "rust-strings": { | ||
| "title": "文字列", | ||
| "concept": "文字列 の目標は次の Rust 文法です: 所有する String が必要な場面と、借用した &str スライスで十分な場面を分けること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「文字列」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-input": { | ||
| "title": "空白区切りの標準入力を最後まで読む", | ||
| "concept": "`Read`トレイトの`read_to_string`は所有する`String`へ入力全体を読み込み、その後`split_whitespace`が行境界に関係なく全トークンをたどります。空入力なら空のイテレーターになります。", | ||
| "worked_example": "バイトスライスも`Read`を実装するため、例は終端入力へ依存せず`read_to_string`を決定的に試します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「文字列」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「文字列」演習の TODO を解かないこと。", | ||
| "文字列 の目標は次の Rust 文法です: 所有する String が必要な場面と、借用した &str スライスで十分な場面を分けること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "1行だけを読み、改行後に続く有効なトークンを無視することです。", | ||
| "有効入力という契約外でも`parse`が必ず成功すると考え、説明のないパニックにすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「文字列」を適用してから `example:` 出力へつながりますか。", | ||
| "「文字列」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "空イテレーターの`sum`が加法単位元を返すのはなぜですか。", | ||
| "標準入力で`read_to_string`を使えるようにするインポート済み`trait`は何ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「文字列」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "標準入力を最後まで読み、空白区切りの全整数を解析して合計を出力してください。", | ||
| "objective": "1行、複数行、空の有効入力を1つのトークン化経路で扱います。", | ||
| "language_delta": "Rustの数値解析は文字列を暗黙変換せず`Result`を返します。ここで`unwrap`できるのは、整数トークンが保証された演習だからです。", | ||
| "prediction_prompt": "`split_whitespace`後に、複数行ケースと空白区切りケースへ差が残るか予測してください。", | ||
| "transfer_trap": "次の`Vec`と`HashMap`の演習では、この所有入力バッファーを基に、値の順序を保ちながらキーごとの件数も管理します。" | ||
| }, | ||
| "rust-control-flow": { | ||
| "title": "制御フロー", | ||
| "concept": "制御フロー の目標は次の Rust 文法です: 習慣的に分岐を書くのではなく、if 式と for の範囲反復で値を作ること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「制御フロー」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-vec-hashmap": { | ||
| "title": "順序コレクションと検索コレクションを組み合わせる", | ||
| "concept": "先頭トークンは問い合わせで、残りの借用語は順序を保つ`Vec`へ入り、`entry`APIで構築する`HashMap`が頻度を記録します。", | ||
| "worked_example": "例は繰り返す色タグを数え、`HashMap`の反復順へ頼らず、異なるキーの数を出力します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「制御フロー」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「制御フロー」演習の TODO を解かないこと。", | ||
| "制御フロー の目標は次の Rust 文法です: 習慣的に分岐を書くのではなく、if 式と for の範囲反復で値を作ること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`HashMap`の反復結果を直接出力し、規定されない順序へ依存することです。", | ||
| "問い合わせが値にない場合に`counts[query]`を使い、パニックすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「制御フロー」を適用してから `example:` 出力へつながりますか。", | ||
| "「制御フロー」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "供給された値の総数と、問い合わせの出現回数は、それぞれどのコレクションが答えますか。", | ||
| "`get`、`copied`、`unwrap_or`なら、不在キーを追加せず扱えるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「制御フロー」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "全値トークンを順序通り保存し、キーごとに数え、値の総数と問い合わせ頻度を報告してください。", | ||
| "objective": "同じプログラムで、順序保存には`Vec`、安全なキー集計には`HashMap`を選びます。", | ||
| "language_delta": "挿入順を保証する辞書もありますが、Rust標準の`HashMap`は反復順を意図的に保証しません。", | ||
| "prediction_prompt": "問い合わせが不在のケースを追い、`get`によって`HashMap`エントリーが作られるか予測してください。", | ||
| "transfer_trap": "理由を計測せず2回検索へ置き換えないでください。`entry`の`Occupied`/`Vacant`操作が更新意図を既に表しています。" | ||
| }, | ||
| "rust-functions": { | ||
| "title": "関数", | ||
| "concept": "関数 の目標は次の Rust 文法です: 引数型と戻り値型が計算の境界を説明するようにシグネチャを書くこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「関数」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-control-flow": { | ||
| "title": "分岐式からラベルを返す", | ||
| "concept": "Rustの`if`は式なので、偶奇の各分岐が文字列を返し、包含範囲の合計と整形できます。1つの式になるため、両分岐の型は一致する必要があります。", | ||
| "worked_example": "例は即時完了状態のラベルを選び、別に包含整数範囲を消費して三角数を計算します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「関数」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「関数」演習の TODO を解かないこと。", | ||
| "関数 の目標は次の Rust 文法です: 引数型と戻り値型が計算の境界を説明するようにシグネチャを書くこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`1..n`を使い、上端を落として全ての正入力の合計を変えることです。", | ||
| "一方の分岐で文字列、他方で整数を返し、`if`式の結果型を一致させないことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「関数」を適用してから `example:` 出力へつながりますか。", | ||
| "「関数」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`1..=0`はどの列を作り、`sum`はどの単位元を返しますか。", | ||
| "偶奇判定と合計計算の順は所有権へ影響しますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「関数」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "入力を奇数または偶数へ分類し、そのラベルと`1`から`n`までの包含合計を組み合わせてください。", | ||
| "objective": "型の揃った分岐式と境界に敏感な範囲計算から、1つの出力を作ります。", | ||
| "language_delta": "Rustの条件は`bool`でなければなりません。C、JavaScript、Pythonの数値真偽変換は使えません。", | ||
| "prediction_prompt": "`n`が`4`のとき、表示合計を求める前に範囲要素を正確に列挙してください。", | ||
| "transfer_trap": "他言語の範囲が終端を含むかを推測せず、Rustでは`..`と`..=`を明示的に使い分けてください。" | ||
| }, | ||
| "rust-structs-impl": { | ||
| "title": "構造体と impl", | ||
| "concept": "構造体と impl の目標は次の Rust 文法です: ドメインのフィールドを struct にまとめ、そのフィールドを使う振る舞いを impl に置くこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「構造体と impl」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-iterators": { | ||
| "title": "`filter`と`map`を終端合計で駆動する", | ||
| "concept": "解析で所有する整数を作り、`filter`は述語用に各候補を借用し、`map`は通過した値を受け取り、最後に`sum`がパイプラインを消費します。", | ||
| "worked_example": "例の無限範囲は、`collect`が具体的な`Vec`を要求する前に`take`が消費量を制限するため安全です。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「構造体と impl」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「構造体と impl」演習の TODO を解かないこと。", | ||
| "構造体と impl の目標は次の Rust 文法です: ドメインのフィールドを struct にまとめ、そのフィールドを使う振る舞いを impl に置くこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "終端操作なしで`map`と`filter`を作り、変換が実行されたと思うことです。", | ||
| "平方する`map`を省き、偶数自体の合計を求めることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「構造体と impl」を適用してから `example:` 出力へつながりますか。", | ||
| "「構造体と impl」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "反復が実際に始まるメソッド呼び出しはどれですか。", | ||
| "`0`が偶数述語を通り、平方後も合計を変えないのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「構造体と impl」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "整数ストリームを解析し、偶数を残して遅延的に平方し、1つの合計へ消費してください。", | ||
| "objective": "イテレーターの所有権と1つの終端消費処理で、多段の数値走査を表します。", | ||
| "language_delta": "他言語のストリームに似ていますが、Rustのアダプター型には要素の所有権と借用も符号化されます。", | ||
| "prediction_prompt": "`-2`、`-1`、`0`について、各中間段階の要素列を予測してください。", | ||
| "transfer_trap": "アダプター間で毎回`collect`しないでください。`sum`が直接要素を引き出せるため、中間`Vec`は不要です。" | ||
| }, | ||
| "rust-enum-match": { | ||
| "title": "enum と match", | ||
| "concept": "enum と match の目標は次の Rust 文法です: enum の variant で選択肢を表し、match ですべての場合を網羅的に扱うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「enum と match」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-functions": { | ||
| "title": "チェックポイント:関数境界を値が越える", | ||
| "concept": "`area`は2つの`i64`を値で受け取り、末尾式から`i64`を返します。標準入力と標準出力は引き続き`main`が担当します。", | ||
| "worked_example": "別の挨拶関数は`format!`で確保した`String`を返し、数値以外でも末尾式による`return`を示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「enum と match」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「enum と match」演習の TODO を解かないこと。", | ||
| "enum と match の目標は次の Rust 文法です: enum の variant で選択肢を表し、match ですべての場合を網羅的に扱うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "幅と高さの積の後へセミコロンを置き、本文の結果をユニットへ変えることです。", | ||
| "値を返さず`area`内で出力し、計算を1つの出力文脈へ結合することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「enum と match」を適用してから `example:` 出力へつながりますか。", | ||
| "「enum と match」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`area`は最初の呼び出しの隠れた状態なしに2回呼べますか。", | ||
| "`i64`が戻るとコンパイラと呼び出し元へ約束するシグネチャ部分はどれですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「enum と match」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "幅と高さを解析し、純粋な`area`関数を呼び、返された値だけを出力してください。", | ||
| "objective": "型付き計算を標準入出力から分離し、再利用可能な関数境界へ置きます。", | ||
| "language_delta": "Rustは末尾式なら`return`キーワードを省けますが、セミコロンの有無が意味を変えます。", | ||
| "prediction_prompt": "コンパイル前に、末尾セミコロンがある場合とない場合の関数本文型を予測してください。", | ||
| "transfer_trap": "他言語の暗黙`return`規則を持ち込まず、Rustでは末尾式と文をセミコロンで区別してください。" | ||
| }, | ||
| "rust-option": { | ||
| "title": "Option と if let", | ||
| "concept": "Option と if let の目標は次の Rust 文法です: 存在しない可能性を Option<T> で明示し、Some の場合だけ安全に取り出すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「Option と if let」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-modules-use": { | ||
| "title": "公開モジュールパスを短くする", | ||
| "concept": "`grading`モジュールは`pub`で`verdict`を公開し、`use`は外側スコープへ短い名前を導入します。可視性と名前解決は別の判断です。", | ||
| "worked_example": "入れ子の`units`要素を公開し、`main`が呼ぶ前にインポートすることで、完全パスから短い名前までの経路を示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「Option と if let」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「Option と if let」演習の TODO を解かないこと。", | ||
| "Option と if let の目標は次の Rust 文法です: 存在しない可能性を Option<T> で明示し、Some の場合だけ安全に取り出すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`private`な同階層関数へ`use`を加えれば可視性を迂回できると考えることです。", | ||
| "誤ったモジュールから相対パスを書き、別名前空間へ解決するか失敗することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「Option と if let」を適用してから `example:` 出力へつながりますか。", | ||
| "「Option と if let」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`use`宣言なしでも`main`は`grading::verdict`を直接呼べますか。", | ||
| "`grading`外から`verdict`を呼べるようにするキーワードは何ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「Option と if let」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "判定関数を公開してインポートしたまま、`80`点以上が全て合格になるよう包含境界を修正してください。", | ||
| "objective": "`use`で公開項目を解決し、`80`点を含む分類境界を適用します。", | ||
| "language_delta": "Rustの`use`は、実行時にパッケージを読み込むインポートではなく、名前を現在のスコープへ導入する仕組みです。", | ||
| "prediction_prompt": "`79`、`80`、`91`の判定結果を予測し、`use`が関数の動作を変えるのか局所パスだけを変えるのか説明してください。", | ||
| "transfer_trap": "Cargoパッケージとクレートはより大きなビルド境界です。この単一ファイル演習が扱うのは言語モジュールとパスだけです。" | ||
| }, | ||
| "rust-modules-use": { | ||
| "title": "モジュールと use", | ||
| "concept": "モジュールと use の目標は次の Rust 文法です: mod、pub、use で名前空間、公開範囲、ローカルパスを制御すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「モジュールと use」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-generics": { | ||
| "title": "ジェネリックスライスから要素をコピーして返す", | ||
| "concept": "`last_copy`は`Copy`を実装する任意の`T`の共有スライスを受け取ります。`copied`は`Option<&T>`を`Option<T>`へ変え、全てのRust型が安価に複製できるとは主張しません。", | ||
| "worked_example": "別の関数は借用配列から最初の`u8`を取り、`Option`結果をデバッグ書式で表示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「モジュールと use」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「モジュールと use」演習の TODO を解かないこと。", | ||
| "モジュールと use の目標は次の Rust 文法です: mod、pub、use で名前空間、公開範囲、ローカルパスを制御すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`Copy`境界を省き、借用した値から`T`を取り出せなくすることです。", | ||
| "常に`clone`を呼び、より広い`Clone`境界と高価かもしれない処理を隠すことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「モジュールと use」を適用してから `example:` 出力へつながりますか。", | ||
| "「モジュールと use」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`last_copy`が借用するものと、返す小さな値は何ですか。", | ||
| "`String`はこの境界を満たしますか。満たさないなら参照を返す設計はどうですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「モジュールと use」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "ジェネリックな末尾要素補助関数を実装し、解析済み整数スライスへ適用してください。", | ||
| "objective": "共有借用から値をコピーできる理由を、正確な`trait`境界で表します。", | ||
| "language_delta": "ジェネリック構文はテンプレートに似ていますが、Rustは後の展開で任意操作を許すのではなく、制約付き定義を検査します。", | ||
| "prediction_prompt": "`main`が`unwrap`する前に、1要素スライスから作られる`Option`を予測してください。", | ||
| "transfer_trap": "馴染みだけで境界を`Clone`へ広げないでください。この数値演習が必要とするのは`Copy`です。" | ||
| }, | ||
| "rust-input": { | ||
| "title": "入力の解析", | ||
| "concept": "入力の解析 の目標は次の Rust 文法です: stdin を一度読み、テキストをトークンに分け、型付きの値へ変換すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「入力の解析」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-traits": { | ||
| "title": "`clone`せず共通の振る舞いを要求する", | ||
| "concept": "`ScoreCard`は`self`を借用して`Summary`を実装します。ジェネリックな`render`は`trait`境界を満たす値を借用し、生成された文字列を返します。", | ||
| "worked_example": "`Size`の例は全ての`Vec`要素型へ小さな振る舞いを実装し、通常のドット構文でメソッドを呼びます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「入力の解析」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「入力の解析」演習の TODO を解かないこと。", | ||
| "入力の解析 の目標は次の Rust 文法です: stdin を一度読み、テキストをトークンに分け、型付きの値へ変換すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "共有借用の`self`から`self.name`を`move`しようとすることです。整形には不要です。", | ||
| "`render`の仮引数を具体的な`ScoreCard`にし、`Summary`契約による再利用を失うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「入力の解析」を適用してから `example:` 出力へつながりますか。", | ||
| "「入力の解析」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`summary`呼び出しはカードを消費しますか。それとも後でも使えますか。", | ||
| "ジェネリック関数で`T`が`Summary`を実装する要件はどこに書かれますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「入力の解析」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "点数カードを構築し、`Summary`境界付き補助関数から借用フィールドを整形してください。", | ||
| "objective": "`trait`で共通動作を定義し、実装値の所有権を手放さず呼びます。", | ||
| "language_delta": "Rust`trait`はジェネリック経由の静的ディスパッチを提供でき、継承用のオブジェクト階層を必要としません。", | ||
| "prediction_prompt": "互いに無関係な2`struct`がともに`Summary`を実装すれば、`render`へ渡せるか予測してください。", | ||
| "transfer_trap": "借用エラーを消すためだけに所有フィールドを`clone`しないでください。整形処理は表示可能な値の参照を受け取れます。" | ||
| }, | ||
| "rust-vec-hashmap": { | ||
| "title": "Vec と HashMap", | ||
| "concept": "Vec と HashMap の目標は次の Rust 文法です: 順序付きデータには Vec、キーで数える処理や検索には HashMap の entry API を選ぶこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「Vec と HashMap」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-macros": { | ||
| "title": "照合したトークンを安全な式へ展開する", | ||
| "concept": "`#[macro_export]`が付いた`greet!`は`$crate::helper`へ展開され、通常の型検査前に補助関数検索を定義元クレートへ固定します。", | ||
| "worked_example": "入れ子モジュールの`caller::run`が`#[macro_export]`付きの`increment!`を呼んでも、`$crate`は定義元クレートルートの補助関数へ解決されます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「Vec と HashMap」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「Vec と HashMap」演習の TODO を解かないこと。", | ||
| "Vec と HashMap の目標は次の Rust 文法です: 順序付きデータには Vec、キーで数える処理や検索には HashMap の entry API を選ぶこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "感嘆符を忘れ、コンパイラへマクロでなく通常関数を探させることです。", | ||
| "入力式を2回展開し、見た目の結果が単純でも副作用を2回実行することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「Vec と HashMap」を適用してから `example:` 出力へつながりますか。", | ||
| "「Vec と HashMap」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "別パスからマクロを呼んでも、`$crate`が補助関数検索を安定させるのはなぜですか。", | ||
| "マクロ`match`後、展開された補助関数呼び出しはどこで型検査されますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「Vec と HashMap」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "入れ子の呼び出し元モジュールから`#[macro_export]`付きマクロ経由で呼ばれる、定義元クレートの挨拶補助関数を完成してください。", | ||
| "objective": "`$crate`により補助関数を衛生的に解決するマクロを`#[macro_export]`で公開します。", | ||
| "language_delta": "マクロは実行前にRust構文を処理し、単なる文字列置換でもリフレクションでもありません。", | ||
| "prediction_prompt": "`caller::greet_from`内の`crate::greet!(name)`を展開し、`rustc`が解決する正確な補助関数パスを示してください。", | ||
| "transfer_trap": "`#[macro_export]`付きマクロが定義元補助関数を使うなら、呼び出し元基準のパスは壊れやすく、`$crate`で固定する必要があります。" | ||
| }, | ||
| "rust-borrowing-slices": { | ||
| "title": "借用とスライス", | ||
| "concept": "借用とスライス の目標は次の Rust 文法です: 関数が所有権を取らずにデータを読めるよう、借用したスライスを渡すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「借用とスライス」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-cargo-workspaces": { | ||
| "title": "Cargoワークスペース全体を操作する", | ||
| "concept": "ワークスペースは複数メンバーパッケージと共有のビルド出力ディレクトリをまとめます。この演習で観察するのは、全メンバーを対象にするコマンドの選択だけです。", | ||
| "worked_example": "例はメタデータ確認コマンドを出力するだけで、`rustc`だけで動くファイルが実際のCargoマニフェストを読み込んだとは主張しません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「借用とスライス」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「借用とスライス」演習の TODO を解かないこと。", | ||
| "借用とスライス の目標は次の Rust 文法です: 関数が所有権を取らずにデータを読めるよう、借用したスライスを渡すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "1つのメンバーで`cargo test`すれば、必ず全ワークスペースメンバーを検査できると思うことです。", | ||
| "文字列対応だけでワークスペース設定を検証したと主張し、実行環境の能力を誇張することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「借用とスライス」を適用してから `example:` 出力へつながりますか。", | ||
| "「借用とスライス」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`cargo check`や`cargo test`でワークスペース全体を選ぶフラグは何ですか。", | ||
| "書式コマンドで`rustfmt`用引数を二重ダッシュの後へ置くのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「借用とスライス」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "検査、テスト、整形の意図を、文書化されたワークスペース向けコマンド行へ対応付けてください。", | ||
| "objective": "複数パッケージビルドを実行したと装わず、安全なワークスペース全体の保守コマンドを特定します。", | ||
| "language_delta": "Cargoワークスペースはビルドツールの機能であり、通常の`rustc`だけが検証できるRust言語構文ではありません。", | ||
| "prediction_prompt": "意図が`fmt`なら、どの引数をCargoが解釈し、どれを`rustfmt`へ転送するか予測してください。", | ||
| "transfer_trap": "一時ワークスペースを構築して実メンバーを確認できる環境になるまで、この導入演習を中核経路へ昇格しないでください。" | ||
| }, | ||
| "rust-result": { | ||
| "title": "Result と ?", | ||
| "concept": "Result と ? の目標は次の Rust 文法です: Result<T, E> で回復可能なエラーを返し、? 演算子で呼び出し元へ伝播すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「Result と ?」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-ownership": { | ||
| "title": "`String`の所有権を渡して回収する", | ||
| "concept": "`main`は元の`String`バッファーから行終端を取り除き、その所有権を`describe`へ移します。空でない入力にはUTF-8バッファーがありますが、空入力はメモリを確保しない場合があります。`String`値はポインター、長さ、容量の固定サイズメタデータです。", | ||
| "worked_example": "`wrap`は`String`メタデータを消費して既存バッファーの所有権を得るため、`main`は`move`後の束縛を再利用できません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「Result と ?」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「Result と ?」演習の TODO を解かないこと。", | ||
| "Result と ? という要点を確認せず、judge の出力だけを合わせること。" | ||
| "値渡し後に呼び出し元側の束縛を読み、移動済み値エラーを起こすことです。", | ||
| "全所有権エラーへ`clone`を加え、適切な仮引数や`return`形で避けられるメモリ確保を増やすことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「Result と ?」を適用してから `example:` 出力へつながりますか。", | ||
| "「Result と ?」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "所有権が`main`を離れる正確な呼び出しと、所有者が戻るパターンはどこですか。", | ||
| "`describe`は文字列をタプルへ`move`する前に、なぜ`len`を呼べますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「Result と ?」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "元の標準入力`String`で行終端を除き、`describe`へ`move`し、その所有者とUTF-8バイト長を返してください。", | ||
| "objective": "`clone`も所有者の紛失もなく、値渡しの関数境界で`String`を追跡します。空入力はメモリを確保しない場合があります。", | ||
| "language_delta": "`String`の`move`はポインター、長さ、容量のメタデータを移し、ヒープバッファーをコピーせず、値をポインター1単語へ縮めることもありません。", | ||
| "prediction_prompt": "後のスカラー値数レッスンと比べる前に、`é`のUTF-8バイト数を予測してください。", | ||
| "transfer_trap": "`Box`や`Arc`、`RefCell`へ進む前に、通常の`move`がメモリ確保の複製ではないことを理解してください。" | ||
| }, | ||
| "rust-ownership": { | ||
| "title": "所有権と借用", | ||
| "concept": "所有権と借用 の目標は次の Rust 文法です: String がいつムーブされ、いつ返され、参照がいつ借用だけを行うか追跡すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「所有権と借用」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-smart-pointers": { | ||
| "title": "再帰ノードを間接的に所有する", | ||
| "concept": "`List::Cons`は末尾を`Box<List>`へ保存します。再帰連鎖を排他的に所有しながら、各`enum`値を有限で既知の大きさにできます。", | ||
| "worked_example": "例は2つの`Box`化されたリンクを構築し、共有参照でたどるため、走査してもリストを消費しません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「所有権と借用」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「所有権と借用」演習の TODO を解かないこと。", | ||
| "所有権と借用 の目標は次の Rust 文法です: String がいつムーブされ、いつ返され、参照がいつ借用だけを行うか追跡すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`Cons`内へ`List`を直接置き、無限大きさの型として`rustc`に拒否されることです。", | ||
| "`Box`の目的を高速化だと説明し、所有権を持つ既知大きさの間接参照という性質を見落とすことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「所有権と借用」を適用してから `example:` 出力へつながりますか。", | ||
| "「所有権と借用」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "残りのリストがどれだけ長くても、`Cons`のどの部分は固定大きさですか。", | ||
| "逆順イテレーターから構築すると、元トークン順が保たれるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「所有権と借用」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "解析済み整数を`Box`化された再帰的リストへ畳み込み、走査で求めた合計を出力してください。", | ||
| "objective": "`Box`で再帰的所有型を表現可能にし、ノードを`move`せず訪れます。", | ||
| "language_delta": "ガベージコレクション言語は再帰間接参照を隠す場合がありますが、Rustは型へ所有権辺を明示します。", | ||
| "prediction_prompt": "再帰的合計を評価する前に、3トークンからできる`Cons`連鎖を描いてください。", | ||
| "transfer_trap": "`Arc`と`Rc`は共有所有権用です。このリストの各末尾には所有者が1つだけなので`Box`で十分です。" | ||
| }, | ||
| "rust-iterators": { | ||
| "title": "イテレータとクロージャ", | ||
| "concept": "イテレータとクロージャ の目標は次の Rust 文法です: 遅延評価の iterator アダプタをクロージャで組み合わせ、sum や collect で消費すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「イテレータとクロージャ」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-interior-mutability": { | ||
| "title": "不変所有者の内側を実行時に可変借用する", | ||
| "concept": "`EventLog`は`&self`で`record`を公開し、`RefCell`が共有借用と可変借用を実行時に検査します。可変ガードは`push`後、最後の共有借用より前に終了します。", | ||
| "worked_example": "`Cell`は参照を貸さず`Copy`整数を扱えるため、例では不変束縛を通してカウンターを1増やします。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「イテレータとクロージャ」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「イテレータとクロージャ」演習の TODO を解かないこと。", | ||
| "イテレータとクロージャ の目標は次の Rust 文法です: 遅延評価の iterator アダプタをクロージャで組み合わせ、sum や collect で消費すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`borrow_mut`のガードを保持したまま`borrow`し、重複アクセスで実行時パニックを起こすことです。", | ||
| "`T: Send`なら所有する`RefCell<T>`自体は別スレッドへ`move`できますが、`RefCell`は`Sync`でなく並行共有できない点を見落とすことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「イテレータとクロージャ」を適用してから `example:` 出力へつながりますか。", | ||
| "「イテレータとクロージャ」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`record`内で`borrow_mut`の`RefMut`ガードはどこで生存を終えますか。", | ||
| "所有者へ`mut`を付けなくても、どの排他規則は引き続き強制されますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「イテレータとクロージャ」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "`&self`経由で全入力トークンを記録し、`RefCell`内のベクターの最終長を報告してください。", | ||
| "objective": "単一スレッドで、互いに重ならない短い借用範囲だけに内部可変性を限定します。", | ||
| "language_delta": "`RefCell`は通常参照の排他検査を除くのではなく、コンパイル時から実行時へ延期します。", | ||
| "prediction_prompt": "`record`が1つの可変ガードを保持したまま2つ目を要求すると、何が起きるか予測してください。", | ||
| "transfer_trap": "`RefCell`は同期プリミティブではありません。複数スレッドで同じ値を共有して変更するなら`Mutex`を使ってください。" | ||
| }, | ||
| "rust-generics": { | ||
| "title": "ジェネリクス", | ||
| "concept": "ジェネリクス の目標は次の Rust 文法です: コンパイル時の具体的な型チェックを保ったまま、T に対して動く関数を書くこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「ジェネリクス」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-strings": { | ||
| "title": "UTF-8文字列を3つの見方で測る", | ||
| "concept": "`String`と`str`はUTF-8を保存します。`len`は保存バイト数、`chars`はUnicodeスカラー値を返し、範囲スライスの両端は文字境界でなければなりません。", | ||
| "worked_example": "蟹のスカラー値は4バイトを占めるため、例は先頭4バイトの範囲を安全にスライスし、バイト数とスカラー値数の違いを報告します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「ジェネリクス」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「ジェネリクス」演習の TODO を解かないこと。", | ||
| "ジェネリクス の目標は次の Rust 文法です: コンパイル時の具体的な型チェックを保ったまま、T に対して動く関数を書くこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "1バイトが常に1文字だと思い、使用できない`text[0]`のような添字を試すことです。", | ||
| "オフセットが`len`未満なら安全だと考え、文字境界でないバイト位置をスライスしてパニックすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「ジェネリクス」を適用してから `example:` 出力へつながりますか。", | ||
| "「ジェネリクス」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "テストデータのアクセント付き`e`は、何バイトと何スカラー値を持ちますか。", | ||
| "蟹接頭辞の範囲が`1`バイトでなく`4`バイトなのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「ジェネリクス」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "入力文字列について、UTF-8バイト長、スカラー値数、最初のスカラー値を別フィールドとして出力してください。", | ||
| "objective": "要求された測定に応じて、`len`、`chars`、境界安全なスカラー値取得を選びます。", | ||
| "language_delta": "RustのUTF-8バイト長はJavaやJavaScriptのUTF-16コードユニット長と異なり、書記素分割にはさらにUnicode処理が必要です。", | ||
| "prediction_prompt": "コンパイル前に、蟹と`a`を組み合わせたケースの全フィールドを予測してください。", | ||
| "transfer_trap": "ASCIIの添字感覚を`str`へ移さないでください。バイト位置には有効な文字境界という制約があります。" | ||
| }, | ||
| "rust-traits": { | ||
| "title": "トレイトと境界", | ||
| "concept": "トレイトと境界 の目標は次の Rust 文法です: trait で必要な振る舞いを表し、境界を使ってジェネリックコードから呼び出すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「トレイトと境界」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-borrowing-slices": { | ||
| "title": "チェックポイント:所有入力のスライスを貸す", | ||
| "concept": "`main`が`String`バッファーを所有し、`first_word`は一時的に借用して同じ保存領域を指す`&str`を返します。単語用の新しいメモリ確保はありません。", | ||
| "worked_example": "別の補助関数は`split_once`を使い、所有レコードが必要でなくなるまで、その接頭辞を借用します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「トレイトと境界」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「トレイトと境界」演習の TODO を解かないこと。", | ||
| "トレイトと境界 の目標は次の Rust 文法です: trait で必要な振る舞いを表し、境界を使ってジェネリックコードから呼び出すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "局所`String`のスライスを返し、`drop`済み保存領域への参照を作ろうとすることです。", | ||
| "`first_word`内で`to_owned`し、スライスなら避けられるメモリ確保と別APIを作ることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「トレイトと境界」を適用してから `example:` 出力へつながりますか。", | ||
| "「トレイトと境界」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "返された最初の単語スライスを出力する間、どの値が生存している必要がありますか。", | ||
| "空入力でも`unwrap`なしに空の借用文字列を返せるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「トレイトと境界」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "標準入力バッファー全体を借用し、最初の空白区切り語を`&str`として返してください。", | ||
| "objective": "返すスライスを呼び出し元所有の`String`へ関連付け、語がない場合も安全に扱います。", | ||
| "language_delta": "Rust参照はコンパイル時の有効期間関係を持ちます。ガベージコレクション言語では部分文字列と所有者の関係が見えない場合があります。", | ||
| "prediction_prompt": "`split_whitespace`を使うと、先頭の空白が返されるスライスへ含まれるか予測してください。", | ||
| "transfer_trap": "ライフタイム注釈や`dyn Trait`の返り値もデータを延命せず、この所有者境界の関係を記述します。" | ||
| }, | ||
| "rust-lifetimes": { | ||
| "title": "ライフタイム", | ||
| "concept": "ライフタイム の目標は次の Rust 文法です: 保存期間を伸ばすのではなく、借用入力と借用出力の関係を注釈すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「ライフタイム」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "返す選択肢に共通ライフタイムを付ける", | ||
| "concept": "`longer`はどちらの引数も返し得るため、1つのライフタイム仮引数で2入力参照と出力を関連付けます。元文字列の所有権は呼び出し元に残ります。", | ||
| "worked_example": "`shorter`には借用入力が2つあり、出力ライフタイム省略規則だけでは返り値がどちら由来か選べないため、明示的な`'a`が必要です。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「ライフタイム」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「ライフタイム」演習の TODO を解かないこと。", | ||
| "ライフタイム の目標は次の Rust 文法です: 保存期間を伸ばすのではなく、借用入力と借用出力の関係を注釈すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "2入力のどちらかを返す場合も、出力ライフタイムを自動推論できると思うことです。関係を明示する必要があります。", | ||
| "ここで`str::len`を使い、要求されたUnicodeスカラー値数でなくUTF-8バイト数を比較することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「ライフタイム」を適用してから `example:` 出力へつながりますか。", | ||
| "「ライフタイム」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "ライフタイム仮引数は実行時にデータを確保したり保持したりしますか。", | ||
| "返された参照が、選ばれたどちらの入力でも支えられる期間だけ有効である必要があるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「ライフタイム」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "入力を縦棒で分け、スカラー値数が長い側を借用して返し、同数なら左を選んでください。", | ||
| "objective": "所有権や保存期間を変えず、参照同士の関係を注釈します。", | ||
| "language_delta": "ライフタイム構文は借用に関するコンパイル時の推論です。メモリ割り当てを生存させる参照カウント型ハンドルとは異なります。", | ||
| "prediction_prompt": "`é`と`aa`をバイト数とスカラー値数の両方で比較し、要求結果が変わる理由を予測してください。", | ||
| "transfer_trap": "コンパイラを黙らせるため`'static`を付けないでください。実際の呼び出し元借用ではなく、全期間生存するデータを要求してしまいます。" | ||
| }, | ||
| "rust-traits-lifetimes": { | ||
| "title": "トレイトオブジェクトと dyn ディスパッチ", | ||
| "concept": "トレイトオブジェクトと dyn ディスパッチ の目標は次の Rust 文法です: 単一のジェネリック型より実行時ディスパッチの柔軟性が必要なとき &dyn Trait を使うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「トレイトオブジェクトと dyn ディスパッチ」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "動的ディスパッチしながら返り値を貸す", | ||
| "concept": "`render`は`&dyn Draw`を受け、仮想関数表から具体的実装を選び、ウィジェットの借用期間を超えない`&str`を返します。", | ||
| "worked_example": "`Named`の例は`Tag`を`trait`オブジェクトとして渡し、所有する`String`を`clone`せず内側文字列を受け取ります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「トレイトオブジェクトと dyn ディスパッチ」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「トレイトオブジェクトと dyn ディスパッチ」演習の TODO を解かないこと。", | ||
| "トレイトオブジェクトと dyn ディスパッチ の目標は次の Rust 文法です: 単一のジェネリック型より実行時ディスパッチの柔軟性が必要なとき &dyn Trait を使うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`where Self: Sized`制約を付けずにジェネリックメソッドを追加すると、`Draw`が`dyn`ディスパッチと互換でなくなる場合があります。", | ||
| "`draw`内で作った一時`String`への参照を返そうとし、無効参照になることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「トレイトオブジェクトと dyn ディスパッチ」を適用してから `example:` 出力へつながりますか。", | ||
| "「トレイトオブジェクトと dyn ディスパッチ」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`render`後も`Button`または`Label`が所有し続けるものは何ですか。", | ||
| "動的ディスパッチされる呼び出しと、返された`str`に推論されるライフタイムは何ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「トレイトオブジェクトと dyn ディスパッチ」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "要求されたウィジェットを作り、`dyn Draw`参照を通して借用したラベルを描画してください。", | ||
| "objective": "オブジェクト安全な実行時ディスパッチと、オブジェクト借用に従う返り値ライフタイムを組み合わせます。", | ||
| "language_delta": "Rust`trait`オブジェクトは明示的な動的ディスパッチです。具体型ごとに通常単相化されるジェネリック`trait`境界とは異なります。", | ||
| "prediction_prompt": "`label:Rust`を受け取る実装と、その返却スライスが有効な期間を予測してください。", | ||
| "transfer_trap": "全値を自動的に`Box`化しないでください。`main`が具体的ウィジェットを所有するため、短い`&dyn Draw`借用で十分です。" | ||
| }, | ||
| "rust-testing": { | ||
| "title": "テストとアサーション", | ||
| "concept": "テストとアサーション の目標は次の Rust 文法です: 純粋なロジックを main から分け、#[test] 関数と assert マクロで振る舞いを検証すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「テストとアサーション」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-unsafe": { | ||
| "title": "安全性の責務を小さく明示する", | ||
| "concept": "`read_copy`は有効な共有参照を生のポインターへ変え、`unsafe`な読み取りを1回だけ行います。コメントには生存、アラインメント、コピー可能性の前提を記します。", | ||
| "worked_example": "例は添字`0`の存在を先に証明し、独立してレビューできる小さなブロック内だけで`get_unchecked`を使います。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「テストとアサーション」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「テストとアサーション」演習の TODO を解かないこと。", | ||
| "テストとアサーション の目標は次の Rust 文法です: 純粋なロジックを main から分け、#[test] 関数と assert マクロで振る舞いを検証すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`unsafe`を万能な逃げ道として使っても、無効な参照や整列していないポインターは有効になりません。", | ||
| "Rust 2024で`unsafe fn`本文へ`unsafe`操作を直接置くと既定で警告され、リントを拒否設定すればエラーになる点を見落とすことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「テストとアサーション」を適用してから `example:` 出力へつながりますか。", | ||
| "「テストとアサーション」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "その行の`pointer.read`を有効にするポインターの事実は何ですか。", | ||
| "操作中に参照先が生存すると保証し続ける安全な参照はどれですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「テストとアサーション」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "解析済み整数の生ポインターコピーを、安全な関数と文書化した不変条件の内側へ閉じ込めてください。", | ||
| "objective": "`unsafe`コードを監査可能な1操作へ制限し、安全な呼び出し元契約を保ちます。", | ||
| "language_delta": "`unsafe`が緩和するのは列挙された操作だけです。Rust 2024は`unsafe fn`内でも明示ブロック外の`unsafe`操作へ既定で警告します。", | ||
| "prediction_prompt": "読み取り前に参照を`drop`したと仮定し、記述した安全性のどの事実が偽になるか特定してください。", | ||
| "transfer_trap": "呼び出し元に追加責務がなくラッパーが全事前条件を確立できるなら、関数全体を`unsafe`にしないでください。" | ||
| }, | ||
| "rust-smart-pointers": { | ||
| "title": "スマートポインタ", | ||
| "concept": "スマートポインタ の目標は次の Rust 文法です: Box<T> でヒープ上のデータを所有しつつ、ポインタのような deref 動作を使うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「スマートポインタ」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-structs-impl": { | ||
| "title": "データのそばへ借用する振る舞いを置く", | ||
| "concept": "`Rectangle`は2寸法を所有し、`area`は`&self`を借用して計算後もインスタンスを利用可能にします。メソッド呼び出し構文がレシーバーを自動で渡します。", | ||
| "worked_example": "`Counter`の例も、レシーバーを消費も可変借用もせず読むメソッドを示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「スマートポインタ」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「スマートポインタ」演習の TODO を解かないこと。", | ||
| "スマートポインタ の目標は次の Rust 文法です: Box<T> でヒープ上のデータを所有しつつ、ポインタのような deref 動作を使うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "フィールドを読むだけなのに`area(self)`とし、長方形を消費することです。", | ||
| "`Rectangle::area`をレシーバーなしの関連関数と考え、明示された`&self`仮引数を見落とすことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「スマートポインタ」を適用してから `example:` 出力へつながりますか。", | ||
| "「スマートポインタ」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`rectangle.area`を2回呼べますか。それを可能にするレシーバーはどれですか。", | ||
| "後で複数型へ同じメソッド動作を与えるなら、ジェネリック境界や`trait`をどう使えますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「スマートポインタ」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "解析済み寸法から`Rectangle`を作り、借用する`area`メソッドの結果を出力してください。", | ||
| "objective": "関連フィールドを`struct`へまとめ、消費しない振る舞いを`impl`で付けます。", | ||
| "language_delta": "Rustメソッドはレシーバーの所有権を明示し、オブジェクト風の呼び出しが対象を消費、変更、借用するかを隠しません。", | ||
| "prediction_prompt": "純粋な掛け算に`&self`を`&mut self`へ変える必要があるか予測してください。", | ||
| "transfer_trap": "ジェネリックや`trait`を追加する前に、1つの具体的`impl`がこの中核レッスンには最も明瞭な境界です。" | ||
| }, | ||
| "rust-interior-mutability": { | ||
| "title": "内部可変性", | ||
| "concept": "内部可変性 の目標は次の Rust 文法です: 実行時の借用チェックが最も単純で正しいモデルになるとき RefCell<T> を使うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「内部可変性」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-enum-match": { | ||
| "title": "閉じた選択肢を網羅する", | ||
| "concept": "`Command`は`add`、`multiply`、終了をバリアント固有データとともに表します。`evaluate`が全ケースを名指しするため、後でバリアントを追加すると`rustc`が未処理`match`を報告します。", | ||
| "worked_example": "信号機の`match`はデータを持たない3バリアントを全て扱い、式から1つの`u8`時間を返します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「内部可変性」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「内部可変性」演習の TODO を解かないこと。", | ||
| "内部可変性 の目標は次の Rust 文法です: 実行時の借用チェックが最も単純で正しいモデルになるとき RefCell<T> を使うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "ワイルドカードアームで、`enum`追加時に役立つ非網羅診断を抑えることです。", | ||
| "コマンドを判別する前にペイロードを読み、ペイロードのない終了バリアントを扱いにくくすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「内部可変性」を適用してから `example:` 出力へつながりますか。", | ||
| "「内部可変性」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "2整数を所有するバリアントと、ペイロードを持たないバリアントはどれですか。", | ||
| "未一致コマンドを全てアンダースコアで飲み込むと、どのコンパイラ支援を失いますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「内部可変性」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "1つのコマンドを`enum`バリアントへ解析し、明示的で網羅的な`match`アームで評価してください。", | ||
| "objective": "ペイロード付きバリアントを持つ閉じたコマンド集合と、完全なパターンマッチングを表します。", | ||
| "language_delta": "Rustは`match`の網羅性を静的に検査します。言語によっては`switch`でケース欠落やフォールスルーが許されます。", | ||
| "prediction_prompt": "計算前に、各アームで利用可能になる束縛名を予測してください。", | ||
| "transfer_trap": "`Option`も`enum`です。次のレッスンでも`Some`と`None`を同じように意図して扱ってください。" | ||
| }, | ||
| "rust-concurrency": { | ||
| "title": "スレッドと join", | ||
| "concept": "スレッドと join の目標は次の Rust 文法です: thread::spawn に作業を移し、join で worker の結果を安全に回収すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「スレッドと join」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-option": { | ||
| "title": "`unwrap`せず存在有無を分岐する", | ||
| "concept": "非空文字列の最初のUnicodeスカラー値は`Some(char)`、空なら`None`です。明示的な`match`で、どちらも通常状態として意味ある出力を作ります。", | ||
| "worked_example": "`checked_half`は偶数の場合だけ`then_some`で`Option`を作り、デバッグ書式でラッパーを観察します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「スレッドと join」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「スレッドと join」演習の TODO を解かないこと。", | ||
| "スレッドと join の目標は次の Rust 文法です: thread::spawn に作業を移し、join で worker の結果を安全に回収すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "仕様で認められた空入力をプログラミングエラーとみなし、`unwrap`でパニックすることです。", | ||
| "`bytes().next`を使い、最初のUnicodeスカラー値ではなくUTF-8の先頭バイトを返すことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「スレッドと join」を適用してから `example:` 出力へつながりますか。", | ||
| "「スレッドと join」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`chars().next`の`Some`内に保存されるペイロード型は何ですか。", | ||
| "`trim`済み文字列の長さが`0`なら、どの分岐が動きますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「スレッドと join」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "任意の先頭スカラー値を確認し、その文字か、定義済みの不在語を出力してください。", | ||
| "objective": "`null`、番兵値、パニックを使わず、通常の欠損値を網羅的に扱います。", | ||
| "language_delta": "`Option`は型へ不在を符号化します。実行時まで忘れられる`null`許容参照とは異なります。", | ||
| "prediction_prompt": "生のバイトではなく、韓国語テストデータから選ばれるスカラー値を予測してください。", | ||
| "transfer_trap": "`unwrap`は不在が本当に不変条件違反の時だけ使ってください。この演習では空入力が明示的なケースです。" | ||
| }, | ||
| "rust-shared-state": { | ||
| "title": "Arc と Mutex の共有状態", | ||
| "concept": "Arc と Mutex の共有状態 の目標は次の Rust 文法です: Arc<T> で共有所有権を作り、Mutex<T> で一度に一つの変更だけを許すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「Arc と Mutex の共有状態」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-result": { | ||
| "title": "解析エラーを純粋補助関数から伝える", | ||
| "concept": "`sum_tokens`は`try_fold`を使い、各整数変換へ`?`を適用します。最初の`ParseIntError`で補助関数を抜け、失敗を標準出力へどう表すかは`main`が決めます。", | ||
| "worked_example": "`positive`は`?`で解析エラーを呼び出し元へ伝播した後に別のドメイン検査を行い、伝播と回復判断が別であることを示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「Arc と Mutex の共有状態」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「Arc と Mutex の共有状態」演習の TODO を解かないこと。", | ||
| "Arc と Mutex の共有状態 の目標は次の Rust 文法です: Arc<T> で共有所有権を作り、Mutex<T> で一度に一つの変更だけを許すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`?`を例外の捕捉演算子と考え、囲んでいる関数から即座に`Err`を返す動作を見落とすことです。", | ||
| "全トークンを`unwrap`し、不正入力を必要なエラー行へ変換できなくすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「Arc と Mutex の共有状態」を適用してから `example:` 出力へつながりますか。", | ||
| "「Arc と Mutex の共有状態」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`?`を含む関数の返り値型は何ですか。", | ||
| "1つの`parse`が`Err`を返した後、`try_fold`は後続トークンを訪れますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「Arc と Mutex の共有状態」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "有効な整数トークンを`Result`経由で合計し、1つでも解析に失敗したら一定のエラー表示を出力してください。", | ||
| "objective": "失敗し得る変換と利用者向け回復を分け、元のエラー経路を保ちます。", | ||
| "language_delta": "Rustの`Result`は回復可能な失敗をシグネチャへ明示します。失敗がシグネチャに現れない例外とは異なります。", | ||
| "prediction_prompt": "`2 bad`の累積値を追い、制御が戻る正確な地点を特定してください。", | ||
| "transfer_trap": "`?`はエラーを処理して成功値へ変えません。どの層が利用者向け表示を選ぶかを明確にしてください。" | ||
| }, | ||
| "rust-async-await": { | ||
| "title": "async と await", | ||
| "concept": "async と await の目標は次の Rust 文法です: async が Future を作り、それを実行するには executor や runtime が必要だと理解すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「async と await」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-testing": { | ||
| "title": "判定器が実行する経路へ検査を置く", | ||
| "concept": "見本プログラムは標準入力を読む前に`assert_eq!`を呼ぶため、検査が実際に動きます。通常コンパイルモードでは`cfg(test)`モジュールはバイナリへ含まれません。", | ||
| "worked_example": "`triple`の例はインラインアサーションを実行してから、答えと無関係な状態行を出し、テストハーネスなしでも検査が動いたことを示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「async と await」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「async と await」演習の TODO を解かないこと。", | ||
| "async と await の目標は次の Rust 文法です: async が Future を作り、それを実行するには executor や runtime が必要だと理解すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "通常の`rustc`バイナリでも`#[test]`関数が動くと思い、生成されないテストハーネスを当てにすることです。", | ||
| "信頼できない入力の検証にアサーションを使い、制御されたエラーでなくパニックにすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「async と await」を適用してから `example:` 出力へつながりますか。", | ||
| "「async と await」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "インラインの等値検査が失敗したら、その後の`println!`へ到達しますか。", | ||
| "テストハーネスを有効にするのは`cargo test`、`rustc --test`、通常の`rustc`のどれですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「async と await」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "`add_two`を実装し、実行可能な境界アサーションを2つ残して、標準入力への関数結果を出力してください。", | ||
| "objective": "このレッスンの判定器と同じ実行モードに、実際に動く正しさ検査を残します。", | ||
| "language_delta": "Rustのテスト属性はコンパイルモードに依存します。`main`から常に呼ぶアサーションと違い、`cargo test`か`rustc --test`がテストハーネスを生成する必要があります。", | ||
| "prediction_prompt": "インライン検査を残したまま`add_two`が入力をそのまま返すと、プロセスがどうなるか予測してください。", | ||
| "transfer_trap": "この演習だけでユニットテストを習得したとは主張しません。実行可能アサーションと、利用可能な実行環境の境界を学ぶレッスンです。" | ||
| }, | ||
| "rust-macros": { | ||
| "title": "macro_rules!", | ||
| "concept": "macro_rules! の目標は次の Rust 文法です: macro_rules! でトークンパターンを照合し、繰り返しの Rust コードをコンパイル時に展開すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「macro_rules!」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-async-await": { | ||
| "title": "制限を明示した実行器で`await`する", | ||
| "concept": "`double`は`std::future::ready`を`await`し、`block_on_ready`は得られた`Future`を1回だけ`poll`します。再開通知して再`poll`するループがないため、`Pending`は明示的なエラーです。", | ||
| "worked_example": "`message`には本物の`.await`があり、同じ1回`poll`の仕組みで駆動されます。`Future`を作って`drop`するだけでなく、実行を観察できます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「macro_rules!」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「macro_rules!」演習の TODO を解かないこと。", | ||
| "macro_rules! という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`double`を呼ぶだけで`Future`を`poll`せず、`async`本文が実行されたと思うことです。", | ||
| "タイマー、I/Oなど`Pending`になり得る`Future`へ、この1回`poll`補助関数を使うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「macro_rules!」を適用してから `example:` 出力へつながりますか。", | ||
| "「macro_rules!」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`Future`が初めて動くのは`async`関数呼び出し時と`poll`時のどちらですか。", | ||
| "無操作`Waker`を使える明示的な条件は何ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「macro_rules!」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "即時完了`Future`内で解析済み数値を変換し、その`async`計算を出力まで駆動してください。", | ||
| "objective": "1回`poll`という実行器の限界を正直に保ちつつ、本物の`await`を観察します。", | ||
| "language_delta": "Rustの`Future`は実行器に`poll`されるまで処理を開始しません。呼び出した時点で動き始める即時実行型の非同期処理とは異なります。", | ||
| "prediction_prompt": "即時完了`Future`がどの`match`アームへ入り、2回目の`poll`が不要な理由を予測してください。", | ||
| "transfer_trap": "処理が`Pending`になり得るなら本番用実行器を使ってください。この教育用補助関数をスケジューラーへ拡張する範囲ではありません。" | ||
| }, | ||
| "rust-unsafe": { | ||
| "title": "unsafe Rust", | ||
| "concept": "unsafe Rust の目標は次の Rust 文法です: コンパイラが検証できない操作を unsafe ブロックに置き、周囲の API はできるだけ安全に保つこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「unsafe Rust」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-concurrency": { | ||
| "title": "ワーカーの所有結果を待つ", | ||
| "concept": "解析済み`i64`は`Copy`なので、`move`クロージャはコピーされた値を所有し、呼び出し元の束縛を無効にしません。`join`がワーカーと同期して計算結果を返します。", | ||
| "worked_example": "小さな掛け算を行うスレッドのハンドルを出力前に`join`するため、ワーカー結果を得る前にプログラムが終了しません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「unsafe Rust」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「unsafe Rust」演習の TODO を解かないこと。", | ||
| "unsafe Rust の目標は次の Rust 文法です: コンパイラが検証できない操作を unsafe ブロックに置き、周囲の API はできるだけ安全に保つこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`JoinHandle`を`drop`すれば暗黙に待つと思うことです。実際にはワーカーを切り離します。", | ||
| "`spawn`したクロージャへスタックデータを借用し、ワーカーがスタックフレームより長生きし得るため`'static`要件に失敗することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「unsafe Rust」を適用してから `example:` 出力へつながりますか。", | ||
| "「unsafe Rust」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "スレッド境界を越えるコピー値は何で、呼び出し元側の元`i64`も使えるのはなぜですか。", | ||
| "`join`が`Err`なら、`expect`はどの失敗を`main`スレッドのパニックへ変えますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「unsafe Rust」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "解析済みの`Copy`整数を1ワーカーへ捕捉し、そこで2倍して`join`し、返り値を出力してください。", | ||
| "objective": "実行タイミングやプロセス終了へ頼らず、1つのスレッド結果を明示的に調整します。", | ||
| "language_delta": "`poll`されるまで動かない非同期`Future`と違い、`spawn`は独立してスケジュールされるOSスレッドを作ります。呼び出し元との実行順は不定で、`join`が同期点です。", | ||
| "prediction_prompt": "ワーカーと呼び出し元のどちらが先にCPUを得るか仮定せず、`join`が保証する結果を予測してください。", | ||
| "transfer_trap": "1つの返り値に共有可変状態は不要です。`Arc`と`Mutex`は複数ワーカーの総合演習で使います。" | ||
| }, | ||
| "rust-cargo-workspaces": { | ||
| "title": "Cargo パッケージとワークスペース", | ||
| "concept": "Cargo パッケージとワークスペース の目標は次の Rust 文法です: 単一ファイルを越えて Cargo のパッケージ構造と cargo check --workspace が必要になる場面を理解すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「Cargo パッケージとワークスペース」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "rust-shared-state": { | ||
| "title": "総合演習:`join`するワーカー間の共有変更を限定する", | ||
| "concept": "`main`は入力`Vec`を所有し、整数ごとにワーカーを`spawn`します。各クロージャは値と`Arc`の複製を所有し、排他区間外で計算し、短時間だけ`lock`して`MutexGuard`を`drop`します。", | ||
| "worked_example": "例は本物のスレッドを2つ開始し、両ハンドルを`join`してから、全更新完了後の保護された合計を読みます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「Cargo パッケージとワークスペース」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「Cargo パッケージとワークスペース」演習の TODO を解かないこと。", | ||
| "Cargo パッケージとワークスペース の目標は次の Rust 文法です: 単一ファイルを越えて Cargo のパッケージ構造と cargo check --workspace が必要になる場面を理解すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`lock`を即時のフィールドアクセスだと思うことです。別のスレッドがロックを保持していれば待つ場合があり、その保持中のパニック後にはポイズンエラーも返します。", | ||
| "無関係な計算中もガードを保持し、競合時間を長くすることです。平方は`Mutex`取得前に求めます。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「Cargo パッケージとワークスペース」を適用してから `example:` 出力へつながりますか。", | ||
| "「Cargo パッケージとワークスペース」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`main`が最後のガードを取る前に、全`JoinHandle`は消費されていますか。", | ||
| "ここでは`Vec`反復、`Arc`所有権、スレッド`move`、ガード範囲、ポイズン処理をどこで使いますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「Cargo パッケージとワークスペース」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "入力ごとにワーカーを`spawn`して`join`し、各平方を共有`Arc<Mutex<i64>>`合計へ加えてください。", | ||
| "objective": "所有権、反復、スレッド、同期、決定的出力を、1つの完結したプログラムへ統合します。", | ||
| "language_delta": "`Arc`は共有寿命、`Mutex`は排他アクセスを管理します。`RefCell`は`Sync`でなく、並行共有された変更を保護できません。", | ||
| "prediction_prompt": "全ワーカーを`join`した後なら、スケジュール順が最終合計を変えない理由を予測してください。", | ||
| "transfer_trap": "ここでは共有ポインターに`Arc`、排他性に`Mutex`を使えば十分です。`RefCell`や`unsafe`を追加して同期規則を弱めないでください。" | ||
| } | ||
| } | ||
| } |
+372
-285
@@ -7,437 +7,524 @@ { | ||
| "rust-output": { | ||
| "title": "출력", | ||
| "concept": "출력 단원에서는 다음 Rust 문법 목표를 익힙니다: println! 포맷 문자열로 값을 넣고 채점기가 요구하는 stdout을 정확히 맞추는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 출력 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "형식이 정확한 표준 출력 만들기", | ||
| "concept": "`println!` 매크로는 컴파일 중 형식 문자열과 인자의 관계를 검사하고 변환된 텍스트 뒤에 줄바꿈 하나를 붙인다.", | ||
| "worked_example": "예제는 도구 이름과 에디션 숫자를 하이픈으로 연결한다. 형식 문자열의 캡처 문법을 보여 주면서 실습의 콜론 답은 그대로 노출하지 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 출력 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 출력 실습의 TODO를 풀지 않는다.", | ||
| "출력 단원에서는 다음 Rust 문법 목표를 익힙니다: println! 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "느낌표를 빼고 `println!`을 일반 함수처럼 호출한다.", | ||
| "콜론 주변에 공백을 넣어 값은 맞아도 요구된 표준 출력과 다르게 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 출력 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "출력 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "형식 문자열이 만드는 문자와 매크로가 덧붙이는 줄바꿈을 구분할 수 있는가?", | ||
| "마지막 줄바꿈이 없어야 한다면 `print!`와 `println!` 중 무엇이 알맞은가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 출력 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "이름과 점수를 읽고 사례에 표시된 정확한 구분자로 두 값을 형식화해 한 줄로 출력하라.", | ||
| "objective": "컴파일 단계에서 검사된 형식 한 줄을 요구된 표준 출력과 정확히 맞춘다.", | ||
| "language_delta": "문자열 연결과 달리 Rust 형식화는 틀과 타입이 있는 인자를 분리하고 둘의 관계를 컴파일 중 검사한다.", | ||
| "prediction_prompt": "중괄호 밖의 구두점이 치환 값마다 반복되는지 실행 전에 판단하라.", | ||
| "transfer_trap": "`println!`은 가변 인자 함수가 아니라 정적으로 알려진 형식 문자열을 확장하는 매크로다." | ||
| }, | ||
| "rust-variables": { | ||
| "title": "바인딩과 가변성", | ||
| "concept": "바인딩과 가변성 단원에서는 다음 Rust 문법 목표를 익힙니다: 기본은 불변 let 바인딩으로 두고 값이 실제로 바뀌는 지점에만 mut을 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 바인딩과 가변성 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "가변 바인딩으로 누적 상태 추적하기", | ||
| "concept": "`mut` 바인딩은 같은 저장 위치에 다시 대입할 수 있다. 같은 이름의 새 `let`은 이전 값을 수정하지 않고 새 바인딩을 만드는 섀도잉이다.", | ||
| "worked_example": "예제는 값을 한 번 변경한 뒤 변환된 결과로 같은 이름을 섀도잉한다. 짧은 프로그램 안에서 재대입과 새 바인딩을 구분한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 바인딩과 가변성 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 바인딩과 가변성 실습의 TODO를 풀지 않는다.", | ||
| "바인딩과 가변성 단원에서는 다음 Rust 문법 목표를 익힙니다: 기본은 불변 let 바인딩으로 두고 값이 실제로 바뀌는 지점에만 mut을 쓰는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`mut` 없이 `value += 1`을 써서 불변 바인딩에 대입한다.", | ||
| "섀도잉을 제자리 변경이라고 설명해 이전 바인딩이 가려지는 시점을 놓친다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 바인딩과 가변성 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "바인딩과 가변성 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "예제에서 저장값을 바꾸는 줄과 새 바인딩을 만드는 줄은 각각 무엇인가?", | ||
| "누적값이 바뀐 뒤 출력된다는 이유만으로 불변 표시에도 `mut`가 필요한가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 바인딩과 가변성 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "표시는 불변으로 유지하고 뒤따르는 모든 정수는 하나의 가변 누적값에 더해 최종 형식으로 출력하라.", | ||
| "objective": "의도적으로 변하는 합계와 바뀌지 않는 문맥 데이터를 구별한다.", | ||
| "language_delta": "Rust는 바인딩 가변성을 명시하며 섀도잉은 원래 변수를 가변으로 만들지 않고 타입도 바꿀 수 있다.", | ||
| "prediction_prompt": "각 반복 직후 누적값을 순서대로 계산한 뒤 마지막 형식 문자열을 예상하라.", | ||
| "transfer_trap": "섀도잉과 `mut`는 모두 이름이 달라 보이게 할 수 있지만 저장 위치를 갱신하는지 새 바인딩을 만드는지가 다르다." | ||
| }, | ||
| "rust-numbers-tuples": { | ||
| "title": "숫자와 튜플", | ||
| "concept": "숫자와 튜플 단원에서는 다음 Rust 문법 목표를 익힙니다: 명시적인 숫자 타입과 pair.0, pair.1 같은 튜플 필드 접근을 함께 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 숫자와 튜플 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "부호 있는 나눗셈을 튜플로 관찰하기", | ||
| "concept": "유효한 `i64` 피연산자에서 `/`는 몫의 소수 부분을 0을 향해 절삭하고 `%`는 대응하는 나머지를 만든다. 제수 0과 `i64::MIN / -1` 오버플로 조합은 제외된다.", | ||
| "worked_example": "`23`을 `4`로 나눈 양의 몫과 나머지를 튜플에 넣고 `.0`, `.1`로 각각 관찰한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 숫자와 튜플 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 숫자와 튜플 실습의 TODO를 풀지 않는다.", | ||
| "숫자와 튜플 단원에서는 다음 Rust 문법 목표를 익힙니다: 명시적인 숫자 타입과 pair.0, pair.1 같은 튜플 필드 접근을 함께 쓰는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "Python의 내림 나눗셈을 옮겨 음수 피제수의 몫을 잘못 계산한다.", | ||
| "튜플 인덱스를 바꾸어 컴파일은 되지만 출력 필드 순서를 뒤집는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 숫자와 튜플 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "숫자와 튜플 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`-17`을 `5`로 나눌 때 몫은 0과 음의 무한대 중 어느 쪽으로 움직이는가?", | ||
| "나눗셈 항등식을 확인하고 제외된 오버플로 쌍을 말할 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 숫자와 튜플 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "제수가 0이 아니고 오버플로 제외 쌍도 아닌 두 정수를 파싱해 몫-나머지 튜플을 순서대로 출력하라.", | ||
| "objective": "Rust의 부호 있는 정수 산술을 지키며 두 값을 계산하고 튜플에서 분해한다.", | ||
| "language_delta": "Rust 정수 `/`는 부호가 다를 때도 소수 부분을 0을 향해 절삭하므로 내림하는 Python `//`와 다르다.", | ||
| "prediction_prompt": "컴파일 전에 부호가 섞인 시험 사례의 몫과 나머지를 계산하라.", | ||
| "transfer_trap": "연산자 모양만 보고 실수 나눗셈으로 추론하지 마라. 피연산자 타입이 여기의 정수 의미를 정한다." | ||
| }, | ||
| "rust-strings": { | ||
| "title": "문자열", | ||
| "concept": "문자열 단원에서는 다음 Rust 문법 목표를 익힙니다: 소유하는 String이 필요한 때와 빌린 &str 슬라이스로 충분한 때를 구분하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 문자열 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-input": { | ||
| "title": "공백으로 구분된 표준 입력 모두 소비하기", | ||
| "concept": "`Read::read_to_string`은 소유한 `String`을 채우고 `split_whitespace`는 줄 경계와 무관하게 모든 토큰을 방문한다. 빈 입력은 빈 반복자를 만든다.", | ||
| "worked_example": "바이트 슬라이스도 `Read`를 구현하므로 예제는 터미널 입력 없이 `read_to_string` 동작을 결정적으로 실행한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 문자열 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 문자열 실습의 TODO를 풀지 않는다.", | ||
| "문자열 단원에서는 다음 Rust 문법 목표를 익힙니다: 소유하는 String이 필요한 때와 빌린 &str 슬라이스로 충분한 때를 구분하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "첫 줄만 읽어 다음 줄의 유효한 토큰을 조용히 무시한다.", | ||
| "유효 입력 계약 밖에서도 파싱이 절대 실패하지 않는다고 가정해 설명 없는 패닉을 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 문자열 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "문자열 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "빈 반복자의 합이 덧셈 항등값이 되는 이유는 무엇인가?", | ||
| "표준 입력에서 `read_to_string`을 사용할 수 있게 하는 가져온 트레이트는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 문자열 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "표준 입력 끝까지 하나의 문자열로 읽고 모든 공백 구분 정수를 파싱해 합계를 출력하라.", | ||
| "objective": "한 줄 입력, 여러 줄 입력, 유효한 빈 입력을 하나의 토큰화 경로로 처리한다.", | ||
| "language_delta": "Rust 파싱은 텍스트를 강제로 숫자로 바꾸지 않고 `Result`를 반환하며 이 실습의 `unwrap`은 유효 정수 계약 때문에만 정당하다.", | ||
| "prediction_prompt": "`split_whitespace` 뒤 여러 줄 사례가 같은 토큰의 한 줄 사례와 달라지는지 예측하라.", | ||
| "transfer_trap": "한 줄 입력 API에 익숙해도 표준 입력 전체가 여러 줄로 감싸질 수 있음을 놓치지 마라." | ||
| }, | ||
| "rust-control-flow": { | ||
| "title": "제어 흐름", | ||
| "concept": "제어 흐름 단원에서는 다음 Rust 문법 목표를 익힙니다: 습관적인 분기 대신 if 표현식과 for 범위 반복으로 값을 만들어 내는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 제어 흐름 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-vec-hashmap": { | ||
| "title": "순서 보존과 키 조회 함께 사용하기", | ||
| "concept": "첫 토큰은 조회어이고 나머지 빌린 단어는 `Vec`에 순서대로 남긴다. `HashMap::entry`는 같은 단어의 빈도를 안전하게 누적한다.", | ||
| "worked_example": "예제는 반복된 색상 태그를 센 뒤 맵 반복 순서에 기대지 않고 서로 다른 키 개수를 출력한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 제어 흐름 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 제어 흐름 실습의 TODO를 풀지 않는다.", | ||
| "제어 흐름 단원에서는 다음 Rust 문법 목표를 익힙니다: 습관적인 분기 대신 if 표현식과 for 범위 반복으로 값을 만들어 내는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`HashMap` 반복 결과를 직접 출력해 명시되지 않은 순서에 답을 맡긴다.", | ||
| "조회어가 값에 없을 수 있는데 `counts[query]`로 인덱싱해 패닉을 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 제어 흐름 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "제어 흐름 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "입력된 값의 개수와 조회어 빈도는 각각 어느 컬렉션이 답하는가?", | ||
| "`get`, `copied`, `unwrap_or`가 누락 키를 변경 없이 처리하는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 제어 흐름 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "모든 값 토큰을 원래 순서로 저장하면서 키별 빈도를 세고 값 길이와 조회어 빈도를 출력하라.", | ||
| "objective": "같은 프로그램에서 순서 저장에는 `Vec`, 안전한 키 집계에는 `HashMap`을 선택한다.", | ||
| "language_delta": "삽입 순서를 보장하는 일부 딕셔너리와 달리 Rust 표준 `HashMap`은 반복 순서를 약속하지 않는다.", | ||
| "prediction_prompt": "조회어가 없을 때 `get`이 맵 항목을 새로 만드는지 추적하라.", | ||
| "transfer_trap": "측정 근거 없이 `entry`를 두 번 조회하는 코드로 바꾸지 마라. 점유 또는 빈 상태를 한 번에 다루는 연산이 갱신 의도를 이미 표현한다." | ||
| }, | ||
| "rust-functions": { | ||
| "title": "함수", | ||
| "concept": "함수 단원에서는 다음 Rust 문법 목표를 익힙니다: 매개변수와 반환 타입이 계산의 경계를 설명하도록 함수 시그니처를 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 함수 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-control-flow": { | ||
| "title": "분기 표현식과 포함 범위 합치기", | ||
| "concept": "Rust의 `if`는 표현식이므로 홀짝 분기가 문자열 표시를 산출할 수 있다. 두 분기 결과는 같은 타입이어야 하며 합계는 포함 범위에서 계산한다.", | ||
| "worked_example": "예제는 준비 상태 표시를 고르고 별도로 포함 정수 범위를 소비해 삼각수를 계산한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 함수 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 함수 실습의 TODO를 풀지 않는다.", | ||
| "함수 단원에서는 다음 Rust 문법 목표를 익힙니다: 매개변수와 반환 타입이 계산의 경계를 설명하도록 함수 시그니처를 쓰는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`1..n`을 사용해 상한을 제외하고 모든 양수 합계를 바꾼다.", | ||
| "한 분기는 문자열, 다른 분기는 정수를 반환해 하나의 `if` 결과 타입을 맞추지 못한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 함수 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "함수 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`1..=0`은 어떤 시퀀스를 만들며 그 합의 항등값은 무엇인가?", | ||
| "홀짝 판정과 합계 계산 순서는 소유권에 영향을 주는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 함수 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "입력을 홀수 또는 짝수로 분류하고 그 표시를 1부터 `n`까지 포함한 합계와 함께 출력하라.", | ||
| "objective": "타입이 맞는 분기식과 경계에 민감한 범위 계산으로 출력 하나를 만든다.", | ||
| "language_delta": "Rust 조건에는 `bool`이 필요하며 `C`, JavaScript, Python의 숫자 참·거짓 변환은 적용되지 않는다.", | ||
| "prediction_prompt": "`n=4`일 때 포함 범위 원소를 나열한 뒤 표시될 합계를 구하라.", | ||
| "transfer_trap": "배타 범위에 익숙한 습관으로 상한을 누락하거나 분기마다 다른 타입을 반환하지 마라." | ||
| }, | ||
| "rust-structs-impl": { | ||
| "title": "구조체와 impl", | ||
| "concept": "구조체와 impl 단원에서는 다음 Rust 문법 목표를 익힙니다: 도메인 필드를 struct로 묶고 그 필드를 쓰는 동작을 impl에 두는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 구조체와 impl 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-iterators": { | ||
| "title": "필터와 맵을 종단 합으로 구동하기", | ||
| "concept": "파싱은 소유한 정수를 만들고 `filter` 조건식은 후보를 빌리며 `map`은 통과한 항목을 받는다. 마지막 `sum`이 지연 파이프라인을 소비한다.", | ||
| "worked_example": "예제의 끝없는 범위는 `take`가 소비량을 제한한 뒤 `collect`가 구체적인 `Vec`을 요청하므로 안전하게 끝난다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 구조체와 impl 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 구조체와 impl 실습의 TODO를 풀지 않는다.", | ||
| "구조체와 impl 단원에서는 다음 Rust 문법 목표를 익힙니다: 도메인 필드를 struct로 묶고 그 필드를 쓰는 동작을 impl에 두는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "종단 연산 없이 `map`과 `filter` 어댑터만 만들어 아무 변환도 실행하지 않는다.", | ||
| "제곱 매핑을 빼서 짝수 제곱의 합 대신 짝수 자체의 합을 구한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 구조체와 impl 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "구조체와 impl 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "어느 메서드 호출에서 실제 반복이 시작되는가?", | ||
| "0이 짝수 조건식을 통과해도 제곱 합에 영향을 주지 않는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 구조체와 impl 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "정수 스트림을 파싱하고 짝수만 남겨 지연 방식으로 제곱한 뒤 파이프라인을 합계 하나로 소비하라.", | ||
| "objective": "반복자 소유권과 종단 소비자 하나로 여러 단계 숫자 순회를 표현한다.", | ||
| "language_delta": "Rust 이터레이터 연결은 다른 스트림과 닮았지만 어댑터 타입에 빌림과 항목 소유권을 함께 담는다.", | ||
| "prediction_prompt": "`-2`, `-1`, `0`이 각 중간 단계를 지날 때 남는 항목을 예측하라.", | ||
| "transfer_trap": "어댑터 사이마다 `collect`하지 마라. `sum`이 직접 항목을 당길 수 있어 중간 `Vec`은 필요 없다." | ||
| }, | ||
| "rust-enum-match": { | ||
| "title": "enum과 match", | ||
| "concept": "enum과 match 단원에서는 다음 Rust 문법 목표를 익힙니다: enum variant로 가능한 경우를 모델링하고 match로 모든 경우를 빠짐없이 처리하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 enum과 match 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-functions": { | ||
| "title": "체크포인트: 함수 경계를 건너는 값", | ||
| "concept": "`area`는 두 `i64`를 값으로 받고 마지막 표현식으로 `i64`를 반환한다. 표준 입력과 출력 책임은 `main`에 남는다.", | ||
| "worked_example": "별도 인사말 함수는 `format!`으로 할당한 `String`을 반환해 숫자가 아닌 자료에서도 표현식 반환 문법을 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 enum과 match 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 enum과 match 실습의 TODO를 풀지 않는다.", | ||
| "enum과 match 단원에서는 다음 Rust 문법 목표를 익힙니다: enum variant로 가능한 경우를 모델링하고 match로 모든 경우를 빠짐없이 처리하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "너비와 높이 곱 뒤에 세미콜론을 붙여 함수 본문 결과를 유닛으로 바꾼다.", | ||
| "`area` 안에서 출력해 계산을 특정 출력 문맥에 결합한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 enum과 match 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "enum과 match 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "첫 호출의 숨은 상태 없이 `area`를 두 번 호출할 수 있는가?", | ||
| "`i64`가 돌아온다는 약속은 시그니처의 어느 부분인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 enum과 match 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "너비와 높이를 파싱해 순수한 넓이 함수를 호출하고 반환된 값만 표준 출력에 써라.", | ||
| "objective": "타입이 있는 계산을 재사용 가능한 함수 경계에서 입출력과 분리한다.", | ||
| "language_delta": "Rust는 마지막 표현식에 `return`을 요구하지 않지만 세미콜론 유무가 반환 타입을 바꾼다.", | ||
| "prediction_prompt": "곱셈 뒤 세미콜론이 있을 때와 없을 때 함수 본문 타입을 각각 예측하라.", | ||
| "transfer_trap": "출력문으로 계산 결과를 대신하지 말고 값 반환과 표준 출력 경계를 구분하라." | ||
| }, | ||
| "rust-option": { | ||
| "title": "Option과 if let", | ||
| "concept": "Option과 if let 단원에서는 다음 Rust 문법 목표를 익힙니다: 없을 수 있는 값을 Option<T>로 명시하고 Some인 경우만 안전하게 꺼내는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 Option과 if let 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-modules-use": { | ||
| "title": "공개 모듈 경로 줄이기", | ||
| "concept": "`grading` 모듈은 `pub`로 `verdict`를 공개하고 `use`는 바깥 범위에 짧은 이름을 가져온다. 공개 범위와 이름 해석은 서로 별개의 결정이다.", | ||
| "worked_example": "중첩 모듈의 공개 함수를 `use`로 현재 범위에 가져온 뒤 `main`에서 짧은 이름으로 호출하는 전체 경로를 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 Option과 if let 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 Option과 if let 실습의 TODO를 풀지 않는다.", | ||
| "Option과 if let 단원에서는 다음 Rust 문법 목표를 익힙니다: 없을 수 있는 값을 Option<T>로 명시하고 Some인 경우만 안전하게 꺼내는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "같은 수준의 비공개 함수에 `use`를 추가하면 가시성 규칙을 우회할 수 있다고 생각한다.", | ||
| "잘못된 모듈 기준의 상대 경로를 써서 다른 이름공간을 찾거나 이름 해석에 실패한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 Option과 if let 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "Option과 if let 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`use` 선언 없이도 `main`이 `grading::verdict` 전체 경로로 호출할 수 있는가?", | ||
| "`grading` 모듈 바깥 코드의 호출 가능 여부를 바꾸는 키워드는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 Option과 if let 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "`verdict` 함수의 공개 상태와 `use` 선언을 유지하고, 80점을 포함해 그 이상의 모든 점수가 합격하도록 경계 조건을 고쳐라.", | ||
| "objective": "공개 항목을 `use`로 해석하고 80점을 포함하는 합격 분류 경계를 정확히 적용한다.", | ||
| "language_delta": "Rust `use`는 패키지를 실행 중 불러오는 동작보다 컴파일 단계에서 경로를 짧게 만드는 별칭에 가깝다.", | ||
| "prediction_prompt": "79점, 80점, 91점의 판정 결과를 각각 예측한 뒤 `use`가 함수 동작을 바꾸는지, 현재 범위의 호출 경로만 줄이는지 설명하라.", | ||
| "transfer_trap": "Cargo 패키지와 크레이트는 더 큰 빌드 경계이며 이 단일 파일 실습은 언어 모듈과 경로만 보여 준다." | ||
| }, | ||
| "rust-modules-use": { | ||
| "title": "모듈과 use", | ||
| "concept": "모듈과 use 단원에서는 다음 Rust 문법 목표를 익힙니다: mod, pub, use로 네임스페이스와 공개 범위, 지역 경로를 제어하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 모듈과 use 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-generics": { | ||
| "title": "제네릭 슬라이스에서 마지막 값 복사하기", | ||
| "concept": "`last_copy`는 `Copy`를 구현한 모든 `T`의 공유 슬라이스를 받는다. `copied`는 `Option<&T>`를 `Option<T>`로 바꾸되 모든 Rust 타입이 싸게 복제된다고 주장하지 않는다.", | ||
| "worked_example": "예제 도우미는 빌린 배열에서 첫 `u8`을 가져와 `Option` 결과를 디버그 형식으로 표시한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 모듈과 use 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 모듈과 use 실습의 TODO를 풀지 않는다.", | ||
| "모듈과 use 단원에서는 다음 Rust 문법 목표를 익힙니다: mod, pub, use로 네임스페이스와 공개 범위, 지역 경로를 제어하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`Copy` 제약을 빼고 `copied`로 `T` 값을 꺼내려 한다.", | ||
| "무조건 `clone`을 호출해 `Clone` 요구와 더 비싼 연산 가능성을 숨긴다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 모듈과 use 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "모듈과 use 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`last_copy`가 빌리는 것과 반환하는 작은 값은 각각 무엇인가?", | ||
| "`String`이 이 제약을 만족하지 않는다면 참조 반환이 더 알맞을 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 모듈과 use 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "제네릭 마지막 원소 도우미를 구현하고 파싱한 정수 슬라이스에 적용해 값이 있는 결과를 출력하라.", | ||
| "objective": "공유 빌림을 통해 값을 복사할 수 있음을 정확히 정당화하는 트레이트 제약을 사용한다.", | ||
| "language_delta": "Rust 제네릭은 이후 임의 연산을 허용하는 템플릿이 아니라 제약된 정의 하나를 검사한다.", | ||
| "prediction_prompt": "원소 하나짜리 슬라이스가 만들 `Option`을 `main`에서 `unwrap`하기 전에 예측하라.", | ||
| "transfer_trap": "익숙하다는 이유로 제약을 `Clone`으로 넓히지 마라. 이 숫자 실습에 필요한 동작은 `Copy`다." | ||
| }, | ||
| "rust-input": { | ||
| "title": "입력 파싱", | ||
| "concept": "입력 파싱 단원에서는 다음 Rust 문법 목표를 익힙니다: stdin을 한 번 읽고 텍스트를 토큰으로 나눈 뒤 타입 있는 값으로 변환하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 입력 파싱 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-traits": { | ||
| "title": "복제 없이 요약 가능한 값 받기", | ||
| "concept": "점수 카드는 `&self`를 빌려 요약을 구현한다. 제네릭 `render`는 그 트레이트 제약을 만족하는 모든 빌린 값을 받아 생성된 텍스트를 반환한다.", | ||
| "worked_example": "크기 예제는 모든 `Vec` 원소 타입에 작은 동작을 구현하고 일반적인 점 문법으로 메서드를 호출한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 입력 파싱 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 입력 파싱 실습의 TODO를 풀지 않는다.", | ||
| "입력 파싱 단원에서는 다음 Rust 문법 목표를 익힙니다: stdin을 한 번 읽고 텍스트를 토큰으로 나눈 뒤 타입 있는 값으로 변환하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "공유된 `self` 빌림에서 `self.name`을 이동시키려 한다.", | ||
| "`render` 매개변수를 구체 점수 카드로 고정해 요약 계약의 재사용성을 잃는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 입력 파싱 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "입력 파싱 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`summary` 호출은 카드를 소비하는가, 호출 뒤에도 사용할 수 있는가?", | ||
| "`T`가 요약을 구현해야 한다는 요구는 제네릭 함수의 어디에 적히는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 입력 파싱 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "요약 구현에서 빌린 두 필드를 형식화하고 `Summary` 제약을 만족하는 도우미에 점수 카드의 참조를 넘겨 결과를 출력하라.", | ||
| "objective": "공통 동작을 트레이트로 정의하고 구현값의 소유권을 포기하지 않은 채 호출한다.", | ||
| "language_delta": "Rust 트레이트는 제네릭을 통한 정적 디스패치를 제공하며 상속을 위한 객체 계층을 요구하지 않는다.", | ||
| "prediction_prompt": "서로 관련 없는 두 구조체가 모두 요약을 구현하면 `render`가 둘 다 받을 수 있는지 판단하라.", | ||
| "transfer_trap": "빌림 오류를 잠재우려고 소유 필드를 복제하지 마라. 형식화는 표시 가능한 값의 참조를 받을 수 있다." | ||
| }, | ||
| "rust-vec-hashmap": { | ||
| "title": "Vec과 HashMap", | ||
| "concept": "Vec과 HashMap 단원에서는 다음 Rust 문법 목표를 익힙니다: 순서 있는 데이터에는 Vec, 키 기반 카운팅과 조회에는 HashMap entry API를 고르는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 Vec과 HashMap 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-macros": { | ||
| "title": "매칭한 토큰을 표현식으로 확장하기", | ||
| "concept": "외부에 공개한 `greet!`는 `$crate::helper`로 확장되어 일반 타입 검사 전에 도우미를 조회할 기준을 매크로가 정의된 크레이트에 고정한다.", | ||
| "worked_example": "중첩 모듈의 `caller::run`이 외부에 공개한 `increment!`를 호출해도 `$crate`는 매크로가 정의된 크레이트 루트의 도우미를 찾는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 Vec과 HashMap 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 Vec과 HashMap 실습의 TODO를 풀지 않는다.", | ||
| "Vec과 HashMap 단원에서는 다음 Rust 문법 목표를 익힙니다: 순서 있는 데이터에는 Vec, 키 기반 카운팅과 조회에는 HashMap entry API를 고르는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "느낌표를 빼 컴파일러가 매크로가 아니라 함수를 찾게 한다.", | ||
| "입력 표현식을 두 번 확장해 겉보기 결과와 달리 부수 효과도 두 번 실행한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 Vec과 HashMap 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "Vec과 HashMap 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "다른 경로에서 매크로를 호출해도 `$crate`가 도우미 조회를 안정적으로 만드는 이유는 무엇인가?", | ||
| "매크로 매칭 뒤 확장된 도우미 호출은 어디에서 타입 검사되는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 Vec과 HashMap 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "중첩된 호출자 모듈에서 외부에 공개한 매크로가 `$crate` 경로로 호출하는 인사말 도우미를 완성하고, 앞뒤 공백을 제거한 입력 이름을 사용하라.", | ||
| "objective": "매크로를 정의한 크레이트의 도우미를 `$crate`로 안정적으로 찾는 매크로를 외부에 공개한다.", | ||
| "language_delta": "Rust 매크로는 실행 전 문법을 다루며 문자열 치환이나 리플렉션이 아니다.", | ||
| "prediction_prompt": "`caller::greet_from` 안의 `crate::greet!(name)`을 확장해 `rustc`가 찾는 도우미 경로를 적어 보라.", | ||
| "transfer_trap": "외부에 공개한 매크로가 자기 크레이트의 도우미를 쓴다면 호출자 기준 상대 경로는 깨지기 쉽다. `$crate`를 쓰면 조회 기준을 정의 크레이트에 고정할 수 있다." | ||
| }, | ||
| "rust-borrowing-slices": { | ||
| "title": "빌림과 슬라이스", | ||
| "concept": "빌림과 슬라이스 단원에서는 다음 Rust 문법 목표를 익힙니다: 함수가 소유권을 가져가지 않고 데이터를 읽도록 빌린 슬라이스를 넘기는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 빌림과 슬라이스 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-cargo-workspaces": { | ||
| "title": "Cargo 워크스페이스 범위 파악하기", | ||
| "concept": "워크스페이스는 멤버 패키지와 공유 `target` 디렉터리를 조정한다. 이 실습의 관찰 가능한 능력은 모든 멤버를 대상으로 하는 명령을 고르는 데 한정된다.", | ||
| "worked_example": "예제는 메타데이터 검사 명령을 출력해 `rustc` 전용 파일이 실제 Cargo 매니페스트를 읽었다고 과장하지 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 빌림과 슬라이스 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 빌림과 슬라이스 실습의 TODO를 풀지 않는다.", | ||
| "빌림과 슬라이스 단원에서는 다음 Rust 문법 목표를 익힙니다: 함수가 소유권을 가져가지 않고 데이터를 읽도록 빌린 슬라이스를 넘기는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "한 멤버에서 `cargo test`를 실행하면 워크스페이스 전체가 자동 검사된다고 생각한다.", | ||
| "명령 텍스트 매핑만으로 실제 워크스페이스 구성이 검증됐다고 주장한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 빌림과 슬라이스 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "빌림과 슬라이스 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`cargo check`와 `cargo test`가 워크스페이스 전체를 선택하게 하는 플래그는 무엇인가?", | ||
| "형식화 명령에서 `rustfmt` 인자는 왜 이중 하이픈 뒤에 오는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 빌림과 슬라이스 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "검사, 테스트, 형식화 의도를 문서화된 워크스페이스 전체 대상 명령줄에 각각 대응시켜 출력하라.", | ||
| "objective": "다중 패키지 빌드를 실행한 척하지 않고 안전한 워크스페이스 유지 명령을 식별한다.", | ||
| "language_delta": "Cargo 워크스페이스는 빌드 도구 기능이며 일반 `rustc`가 검증하는 Rust 언어 구문이 아니다.", | ||
| "prediction_prompt": "의도가 형식화일 때 Cargo가 받는 인자와 `rustfmt`에 전달되는 인자를 구분하라.", | ||
| "transfer_trap": "실행기가 임시 워크스페이스를 만들고 멤버 동작을 검사하기 전에는 이 실습을 핵심 경로로 승격하지 마라." | ||
| }, | ||
| "rust-result": { | ||
| "title": "Result와 ?", | ||
| "concept": "Result와 ? 단원에서는 다음 Rust 문법 목표를 익힙니다: Result<T, E>로 복구 가능한 오류를 반환하고 ? 연산자로 호출자에게 전파하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 Result와 ? 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-ownership": { | ||
| "title": "`String` 소유권 넘기고 되찾기", | ||
| "concept": "`main`은 소유한 `String`의 UTF-8 버퍼에서 줄 끝을 제거한 뒤 그 값을 `describe`로 이동한다. `String` 표현은 포인터, 길이, 용량 메타데이터이며 빈 문자열은 힙 할당이 없을 수도 있다.", | ||
| "worked_example": "`wrap`은 `String`을 값으로 받아 포인터·길이·용량 메타데이터와 함께 기존 버퍼의 소유권을 넘겨받는다. 이동 뒤 `main`은 이전 바인딩을 다시 사용할 수 없다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 Result와 ? 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 Result와 ? 실습의 TODO를 풀지 않는다.", | ||
| "Result와 ? 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "값으로 넘긴 뒤 호출자 바인딩을 다시 읽어 이동된 값 오류를 만든다.", | ||
| "모든 소유권 오류에 `clone`을 추가해 알맞은 매개변수나 반환 모양 대신 불필요한 할당을 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 Result와 ? 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "Result와 ? 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "어느 호출에서 소유권이 `main`을 떠나고 어느 패턴에서 소유자가 돌아오는가?", | ||
| "`describe`가 튜플로 텍스트를 이동하기 전에 `len`을 호출할 수 있는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 Result와 ? 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "소유한 표준 입력 `String`에서 줄 끝을 제자리로 제거하고 `describe`에 이동한 뒤 같은 소유값과 UTF-8 바이트 길이를 함께 반환하라.", | ||
| "objective": "복제하거나 잃지 않고 소유한 `String`과 그 UTF-8 버퍼를 값 전달 함수 경계 너머로 추적한다.", | ||
| "language_delta": "`String` 이동은 포인터, 길이, 용량 메타데이터를 옮길 뿐 힙 버퍼를 복사하거나 한 포인터 워드로 줄이지 않는다.", | ||
| "prediction_prompt": "뒤의 스칼라 수 실습과 비교하기 전에 `é`의 UTF-8 바이트 수를 예측하라.", | ||
| "transfer_trap": "`Box`, `Arc`, `RefCell`을 쓰기 전에 평범한 이동과 소유자 반환으로 해결되는지 먼저 확인하라." | ||
| }, | ||
| "rust-ownership": { | ||
| "title": "소유권과 빌림", | ||
| "concept": "소유권과 빌림 단원에서는 다음 Rust 문법 목표를 익힙니다: String이 언제 이동하고 언제 반환되며 참조가 언제 빌리기만 하는지 추적하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 소유권과 빌림 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-smart-pointers": { | ||
| "title": "재귀 노드를 `Box`로 간접 소유하기", | ||
| "concept": "`List::Cons`가 꼬리를 `Box<List>`에 저장하면 각 열거형 값의 크기가 유한하게 정해지면서 재귀 연결을 배타적으로 소유한다.", | ||
| "worked_example": "예제는 `Box`로 감싼 연결 두 개를 만들고 공유 참조로 순회하므로 목록을 소비하지 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 소유권과 빌림 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 소유권과 빌림 실습의 TODO를 풀지 않는다.", | ||
| "소유권과 빌림 단원에서는 다음 Rust 문법 목표를 익힙니다: String이 언제 이동하고 언제 반환되며 참조가 언제 빌리기만 하는지 추적하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`Cons` 안에 `List`를 직접 넣어 크기를 계산할 수 없는 재귀 타입을 만든다.", | ||
| "`Box`의 목적을 속도 향상이라고 설명한다. 여기서 중요한 성질은 소유권을 유지하는 간접 참조와 고정된 포인터 크기다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 소유권과 빌림 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "소유권과 빌림 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "나머지 목록 길이와 무관하게 `Cons` 안에서 크기가 고정되는 부분은 무엇인가?", | ||
| "역순 반복자에서 목록을 만들면 원래 토큰 순서가 보존되는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 소유권과 빌림 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "파싱한 정수를 접어 `Box` 기반 재귀 목록을 만들고, 노드를 이동시키지 않고 순회해 합계를 출력하라.", | ||
| "objective": "`Box`로 재귀 소유하는 타입의 크기를 정하고 노드를 소비하지 않은 채 방문한다.", | ||
| "language_delta": "가비지 컬렉션 언어는 재귀 간접 참조를 숨길 수 있지만 Rust는 소유 연결을 타입에 명시한다.", | ||
| "prediction_prompt": "토큰 세 개로 만들어지는 `Cons` 연결을 그린 뒤 재귀 합계를 계산하라.", | ||
| "transfer_trap": "꼬리마다 소유자가 하나뿐인 목록에는 공유 포인터보다 `Box`가 정확한 도구다." | ||
| }, | ||
| "rust-iterators": { | ||
| "title": "이터레이터와 클로저", | ||
| "concept": "이터레이터와 클로저 단원에서는 다음 Rust 문법 목표를 익힙니다: 지연 실행되는 iterator adapter를 클로저로 조합하고 sum이나 collect로 소비하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 이터레이터와 클로저 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-interior-mutability": { | ||
| "title": "`RefCell`로 단일 스레드 내부 변경하기", | ||
| "concept": "`EventLog`는 `&self`를 받는 `record` 안에서 `RefCell`을 통해 내용을 바꾼다. `RefCell`은 공유 빌림과 가변 빌림의 배타성 규칙을 런타임에 검사하며, 가변 가드는 `push` 호출문이 끝날 때 해제되어 최종 공유 빌림과 겹치지 않는다.", | ||
| "worked_example": "예제의 `Cell`은 참조를 빌려주지 않고 `Copy` 정수를 다뤄 불변 바인딩을 통해 카운터를 증가시킨다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 이터레이터와 클로저 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 이터레이터와 클로저 실습의 TODO를 풀지 않는다.", | ||
| "이터레이터와 클로저 단원에서는 다음 Rust 문법 목표를 익힙니다: 지연 실행되는 iterator adapter를 클로저로 조합하고 sum이나 collect로 소비하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`borrow_mut` 가드를 유지한 채 `borrow`를 요청해 실행 중 겹침 패닉을 만든다.", | ||
| "`T: Send`인 소유 `RefCell<T>`는 스레드로 이동할 수 있지만 `!Sync`라 동시 변경에 공유할 수 없음을 혼동한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 이터레이터와 클로저 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "이터레이터와 클로저 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`record` 안에서 `borrow_mut`의 `RefMut` 가드는 어디에서 수명을 끝내는가?", | ||
| "소유자를 `mut`로 선언하지 않아도 계속 강제되는 규칙은 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 이터레이터와 클로저 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "`&self`를 통해 모든 입력 토큰을 기록하고 짧은 `RefCell` 가변 빌림이 끝난 뒤 내부 벡터의 길이를 출력하라.", | ||
| "objective": "짧고 겹치지 않는 단일 스레드 빌림 범위에서만 내부 가변성을 사용한다.", | ||
| "language_delta": "`RefCell`은 일반 Rust 참조의 배타성 검사를 없애지 않고 실행 시점으로 옮긴다.", | ||
| "prediction_prompt": "`record`가 가변 가드를 유지한 채 두 번째 빌림을 요청하면 어떤 일이 생기는지 예측하라.", | ||
| "transfer_trap": "`RefCell`은 동기화 기본 타입이 아니다. 여러 스레드가 같은 값을 변경하려면 `Mutex`를 사용하라." | ||
| }, | ||
| "rust-generics": { | ||
| "title": "제네릭", | ||
| "concept": "제네릭 단원에서는 다음 Rust 문법 목표를 익힙니다: 컴파일 시점의 구체 타입 검사를 유지하면서 T에 대해 동작하는 함수를 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 제네릭 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-strings": { | ||
| "title": "UTF-8 텍스트의 세 관점 측정하기", | ||
| "concept": "`String`과 `str`은 UTF-8을 저장한다. `len`은 바이트 수, `chars`는 유니코드 스칼라를 제공하고 범위 슬라이스는 문자 경계에서 시작하고 끝나야 한다.", | ||
| "worked_example": "게 이모지는 UTF-8 네 바이트이므로 예제는 첫 4바이트 범위를 안전하게 슬라이스하고 바이트 수와 스칼라 수가 다름을 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 제네릭 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 제네릭 실습의 TODO를 풀지 않는다.", | ||
| "제네릭 단원에서는 다음 Rust 문법 목표를 익힙니다: 컴파일 시점의 구체 타입 검사를 유지하면서 T에 대해 동작하는 함수를 쓰는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "한 바이트가 반드시 한 문자라고 보고 `text[0]` 인덱싱을 시도한다.", | ||
| "오프셋이 `len`보다 작기만 하면 된다고 생각해 임의 바이트 경계에서 슬라이스하고 패닉한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 제네릭 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "제네릭 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "시험 사례의 `é`는 몇 바이트와 몇 스칼라로 이루어지는가?", | ||
| "게 이모지 접두사의 범위가 1이 아니라 4바이트인 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 제네릭 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "입력 텍스트의 UTF-8 바이트 길이, 유니코드 스칼라 수, 첫 스칼라를 서로 다른 필드로 출력하라.", | ||
| "objective": "요청한 측정에 따라 `len`, `chars`, 안전한 첫 스칼라 조회를 구분해 선택한다.", | ||
| "language_delta": "Rust UTF-8 바이트 길이는 Java와 JavaScript의 UTF-16 코드 단위 길이와 다르며 그래핌 분할에는 추가 유니코드 로직이 필요하다.", | ||
| "prediction_prompt": "게 이모지와 `a`가 이어진 사례의 모든 출력 필드를 컴파일 전에 예측하라.", | ||
| "transfer_trap": "ASCII 인덱싱 직관을 `str`에 옮기지 마라. 바이트 위치는 유효성 제약이 있는 저장 오프셋이다." | ||
| }, | ||
| "rust-traits": { | ||
| "title": "트레이트와 바운드", | ||
| "concept": "트레이트와 바운드 단원에서는 다음 Rust 문법 목표를 익힙니다: trait로 필요한 동작을 설명하고 bound로 그 동작을 제네릭 코드에서 호출하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 트레이트와 바운드 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-borrowing-slices": { | ||
| "title": "체크포인트: 소유 입력의 슬라이스 빌려주기", | ||
| "concept": "`main`이 `String` 버퍼를 소유하는 동안 `first_word`가 잠시 빌려 같은 저장소 안의 `&str`을 반환한다. 새 단어 할당은 없다.", | ||
| "worked_example": "예제 도우미는 `split_once`로 소유 레코드의 접두사를 빌려 레코드가 필요한 동안만 돌려준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 트레이트와 바운드 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 트레이트와 바운드 실습의 TODO를 풀지 않는다.", | ||
| "트레이트와 바운드 단원에서는 다음 Rust 문법 목표를 익힙니다: trait로 필요한 동작을 설명하고 bound로 그 동작을 제네릭 코드에서 호출하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "지역 `String`의 슬라이스를 반환해 이미 해제된 저장소를 가리키려 한다.", | ||
| "`first_word` 안에서 `to_owned`를 호출해 피할 수 있는 할당과 다른 API 모양을 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 트레이트와 바운드 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "트레이트와 바운드 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "반환된 첫 단어 슬라이스를 출력하는 동안 살아 있어야 하는 소유자는 무엇인가?", | ||
| "빈 입력이 `unwrap` 없이 빈 빌린 문자열을 안전하게 만드는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 트레이트와 바운드 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "표준 입력 버퍼 전체를 빌리고 공백으로 구분된 첫 단어를 새 할당 없는 `&str`로 반환하라.", | ||
| "objective": "반환 슬라이스를 호출자 소유 `String`과 연결하고 단어가 없는 상태도 안전하게 처리한다.", | ||
| "language_delta": "Rust 참조는 컴파일 시점 유효 관계를 담지만 가비지 컬렉션 언어의 부분 문자열은 소유 연결을 드러내지 않는 경우가 많다.", | ||
| "prediction_prompt": "`split_whitespace`를 쓸 때 맨 앞 공백이 반환 슬라이스에 포함되는지 예측하라.", | ||
| "transfer_trap": "수명, 트레이트 객체 결과, 안전하지 않은 포인터도 같은 소유자 경계를 바탕으로 하며 데이터 수명을 늘리지는 않는다." | ||
| }, | ||
| "rust-lifetimes": { | ||
| "title": "라이프타임", | ||
| "concept": "라이프타임 단원에서는 다음 Rust 문법 목표를 익힙니다: 저장 기간을 늘리는 것이 아니라 빌린 입력과 빌린 출력의 관계를 표시하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 라이프타임 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "반환 선택이 공유하는 수명 이름 붙이기", | ||
| "concept": "`longer`는 두 인자 중 하나를 반환할 수 있어 수명 매개변수 하나로 두 입력 참조와 출력을 연결한다. 원본 텍스트의 소유자는 여전히 호출자다.", | ||
| "worked_example": "`shorter`는 빌린 입력이 둘이라 출력 수명 생략 규칙만으로 어느 입력을 반환하는지 고를 수 없어 명시적인 `'a`가 필요하다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 라이프타임 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 라이프타임 실습의 TODO를 풀지 않는다.", | ||
| "라이프타임 단원에서는 다음 Rust 문법 목표를 익힙니다: 저장 기간을 늘리는 것이 아니라 빌린 입력과 빌린 출력의 관계를 표시하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "출력 생략 규칙이 두 입력 참조 중 하나를 자동 선택한다고 가정한다.", | ||
| "요구가 유니코드 스칼라 수인데 `str::len`으로 UTF-8 바이트 길이를 비교한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 라이프타임 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "라이프타임 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "수명 매개변수가 실행 중 할당하거나 보존하는 데이터가 있는가?", | ||
| "반환 참조는 선택된 입력이 지원하는 기간 동안 유효해야 하는 이유가 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 라이프타임 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "입력을 `|`에서 나누고 유니코드 스칼라 수가 더 많은 쪽의 빌린 슬라이스를 반환하되, 동점이면 왼쪽을 선택하라.", | ||
| "objective": "소유권이나 저장 기간을 바꾸지 않고 참조 관계를 애너테이션으로 표현한다.", | ||
| "language_delta": "수명 문법은 빌림 관계를 나타내는 컴파일 시점 표기이며, 할당을 실제로 유지하는 참조 카운팅 포인터와 다르다.", | ||
| "prediction_prompt": "`é`와 `aa`를 바이트 수와 스칼라 수로 각각 비교해 요구된 승자가 왜 달라지는지 설명하라.", | ||
| "transfer_trap": "컴파일러를 잠재우려고 `'static`을 붙이지 마라. 실제 호출자 빌림 관계보다 긴 전역 데이터를 요구하게 된다." | ||
| }, | ||
| "rust-traits-lifetimes": { | ||
| "title": "트레이트 객체와 dyn 디스패치", | ||
| "concept": "트레이트 객체와 dyn 디스패치 단원에서는 다음 Rust 문법 목표를 익힙니다: 하나의 제네릭 타입보다 런타임 디스패치의 유연성이 필요할 때 &dyn Trait을 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 트레이트 객체와 dyn 디스패치 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "동적 디스패치 결과를 빌려 반환하기", | ||
| "concept": "`render`는 `&dyn Draw`를 받고 가상 함수 테이블을 통해 구체 구현을 고른 뒤 위젯 빌림보다 오래 살지 않는 `&str`을 반환한다.", | ||
| "worked_example": "이름표 예제는 태그를 트레이트 객체로 넘겨 소유한 `String`을 복제하지 않고 내부 텍스트를 빌려 받는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 트레이트 객체와 dyn 디스패치 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 트레이트 객체와 dyn 디스패치 실습의 TODO를 풀지 않는다.", | ||
| "트레이트 객체와 dyn 디스패치 단원에서는 다음 Rust 문법 목표를 익힙니다: 하나의 제네릭 타입보다 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`Draw`에 제한 없는 제네릭 메서드를 추가해 트레이트를 `dyn`과 호환되지 않게 만든다. 그런 메서드가 필요하면 `where Self: Sized`로 트레이트 객체의 호출 대상에서 제외해야 한다.", | ||
| "`draw` 안에서 만든 임시 `String`의 참조를 반환하려 해 이미 해제된 값을 가리키게 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 트레이트 객체와 dyn 디스패치 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "트레이트 객체와 dyn 디스패치 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`render` 반환 뒤에도 `Button`이나 `Label`이 계속 소유하는 것은 무엇인가?", | ||
| "어느 호출이 동적으로 디스패치되고 반환 `&str`에는 어떤 수명이 추론되는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 트레이트 객체와 dyn 디스패치 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "요청된 위젯을 만들고 `&dyn Draw` 참조를 `render`에 넘겨 빌린 표시 문자열을 출력하라.", | ||
| "objective": "트레이트 객체에 안전한 동적 디스패치와 객체 빌림을 따르는 반환 수명을 결합한다.", | ||
| "language_delta": "Rust 트레이트 객체는 명시적 동적 디스패치를 사용하며 구체 타입마다 보통 단형화되는 제네릭 트레이트 제약과 다르다.", | ||
| "prediction_prompt": "`label:Rust`에서 어느 구현이 호출되고 반환 슬라이스가 언제까지 유효한지 예측하라.", | ||
| "transfer_trap": "모든 값을 `Box`에 넣지 마라. `main`이 구체 위젯을 소유하므로 짧은 `&dyn Draw` 빌림이면 충분하다." | ||
| }, | ||
| "rust-testing": { | ||
| "title": "테스트와 assert", | ||
| "concept": "테스트와 assert 단원에서는 다음 Rust 문법 목표를 익힙니다: 순수 로직을 main 밖으로 분리해 #[test] 함수와 assert 매크로가 동작을 검증하게 하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 테스트와 assert 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-unsafe": { | ||
| "title": "안전 의무를 작은 범위에 명시하기", | ||
| "concept": "`read_copy`는 유효한 공유 참조를 원시 포인터로 바꾸고 안전하지 않은 읽기 한 번만 수행한다. 주석은 값이 살아 있음, 올바른 정렬, 복사 가능성이라는 전제 조건을 기록한다.", | ||
| "worked_example": "예제는 인덱스 0의 존재를 증명한 뒤 고립된 작은 블록에서 `get_unchecked`를 사용해 불변 조건을 검토하기 쉽게 한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 테스트와 assert 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 테스트와 assert 실습의 TODO를 풀지 않는다.", | ||
| "테스트와 assert 단원에서는 다음 Rust 문법 목표를 익힙니다: 순수 로직을 main 밖으로 분리해 #[test] 함수와 assert 매크로가 동작을 검증하게 하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`unsafe`를 넓은 탈출구로 쓰면 댕글링 포인터나 잘못된 정렬도 유효해진다고 생각한다.", | ||
| "Rust 2024의 `unsafe_op_in_unsafe_fn`은 기본 경고이며 거부 설정할 때 오류가 된다는 차이를 놓친다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 테스트와 assert 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "테스트와 assert 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "해당 줄에서 `pointer.read`가 유효하려면 포인터에 관해 어떤 사실이 성립해야 하는가?", | ||
| "연산 동안 가리키는 값이 살아 있음을 계속 보장하는 안전한 참조는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 테스트와 assert 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "파싱한 정수를 원시 포인터로 복사하는 연산을, 안전 조건을 문서화한 안전한 함수와 작은 `unsafe` 블록 안에 감춰라.", | ||
| "objective": "안전한 호출자 계약을 유지하면서 안전하지 않은 코드를 검토 가능한 연산 하나로 제한한다.", | ||
| "language_delta": "Rust 2024는 `unsafe fn` 안에서도 명시적인 블록 밖의 안전하지 않은 연산을 기본 경고로 알려 검토 범위를 드러내게 한다.", | ||
| "prediction_prompt": "읽기 전에 참조가 해제됐다고 가정하고 문서화한 안전 사실 중 무엇이 거짓이 되는지 찾아라.", | ||
| "transfer_trap": "호출자에 추가 의무가 없고 래퍼가 사전 조건을 세울 수 있다면 함수 전체를 `unsafe`로 표시하지 마라." | ||
| }, | ||
| "rust-smart-pointers": { | ||
| "title": "스마트 포인터", | ||
| "concept": "스마트 포인터 단원에서는 다음 Rust 문법 목표를 익힙니다: Box<T>로 힙 데이터를 소유하면서 포인터처럼 역참조되는 동작을 활용하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 스마트 포인터 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-structs-impl": { | ||
| "title": "데이터 곁에 빌린 동작 두기", | ||
| "concept": "직사각형은 두 치수를 소유하고 `area`는 `&self`를 빌려 계산 뒤에도 인스턴스를 계속 사용할 수 있게 한다. 메서드 호출 문법이 수신자를 자동으로 전달한다.", | ||
| "worked_example": "카운터는 값을 소비하거나 가변으로 빌리지 않고 읽기만 하는 또 다른 수신자 메서드를 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 스마트 포인터 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 스마트 포인터 실습의 TODO를 풀지 않는다.", | ||
| "스마트 포인터 단원에서는 다음 Rust 문법 목표를 익힙니다: Box<T>로 힙 데이터를 소유하면서 포인터처럼 역참조되는 동작을 활용하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "읽기만 하는 계산에 `self`를 받아 직사각형을 소비한다.", | ||
| "명시적인 `&self` 매개변수를 놓치고 `Rectangle::area`를 수신자 없는 연관 함수로 본다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 스마트 포인터 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "스마트 포인터 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`rectangle.area`를 두 번 호출할 수 있게 하는 수신자 선택은 무엇인가?", | ||
| "나중에 여러 타입에 같은 동작을 제공하려면 제네릭 제약이나 트레이트를 어떻게 사용할 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 스마트 포인터 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "파싱한 치수로 직사각형을 만들고 인스턴스를 소비하지 않는 `area` 메서드의 결과를 출력하라.", | ||
| "objective": "관련 필드를 구조체로 묶고 `impl`에 소비하지 않는 동작을 붙인다.", | ||
| "language_delta": "Rust 메서드는 객체 호출이 대상을 소비하는지, 변경하는지, 빌리는지를 수신자 타입에 명시한다.", | ||
| "prediction_prompt": "순수 곱셈에 `&self`를 `&mut self`로 바꿀 필요가 있는지 판단하라.", | ||
| "transfer_trap": "재사용을 미리 추상화하지 말고 이 핵심 단원에서는 구체 `impl` 하나로 수신자 경계를 먼저 익혀라." | ||
| }, | ||
| "rust-interior-mutability": { | ||
| "title": "내부 가변성", | ||
| "concept": "내부 가변성 단원에서는 다음 Rust 문법 목표를 익힙니다: 런타임 빌림 검사가 가장 단순하고 올바른 모델일 때 RefCell<T>를 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 내부 가변성 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-enum-match": { | ||
| "title": "닫힌 선택지를 완전하게 매치하기", | ||
| "concept": "명령 열거형은 덧셈, 곱셈, 종료를 배리언트별 데이터와 함께 표현한다. `evaluate`가 모든 경우를 명시하면 새 배리언트 추가 시 `rustc`가 누락을 알려 준다.", | ||
| "worked_example": "신호등 예제의 매치는 연관 데이터가 없는 세 배리언트를 모두 다뤄 하나의 `u8` 지속 시간을 반환한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 내부 가변성 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 내부 가변성 실습의 TODO를 풀지 않는다.", | ||
| "내부 가변성 단원에서는 다음 Rust 문법 목표를 익힙니다: 런타임 빌림 검사가 가장 단순하고 올바른 모델일 때 RefCell<T>를 쓰는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "와일드카드 갈래로 열거형이 커졌을 때 유용한 불완전 매치 진단을 숨긴다.", | ||
| "명령을 고르기 전에 연관 데이터를 읽으려 해 데이터가 없는 종료 배리언트 처리를 어색하게 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 내부 가변성 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "내부 가변성 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "두 정수를 소유하는 배리언트와 연관 데이터가 없는 배리언트는 각각 무엇인가?", | ||
| "모든 나머지 명령을 `_`로 삼키면 어느 컴파일러 도움을 잃는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 내부 가변성 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "입력 명령 하나를 해당 열거형 배리언트로 파싱하고 명시적인 매치 갈래로 빠짐없이 평가하라.", | ||
| "objective": "연관 데이터를 가진 닫힌 명령 집합과 완전한 패턴 매칭을 표현한다.", | ||
| "language_delta": "Rust 매치 완전성은 정적으로 검사되며 일부 언어의 스위치처럼 갈래 누락이나 다음 갈래 실행을 허용하지 않는다.", | ||
| "prediction_prompt": "결과를 계산하기 전에 각 갈래에서 사용할 수 있는 바인딩 이름을 적어 보라.", | ||
| "transfer_trap": "`Option`도 열거형이므로 다음 실습의 `Some`과 `None`도 같은 의도로 모두 처리하라." | ||
| }, | ||
| "rust-concurrency": { | ||
| "title": "스레드와 join", | ||
| "concept": "스레드와 join 단원에서는 다음 Rust 문법 목표를 익힙니다: thread::spawn으로 작업을 옮기고 join으로 worker 결과를 안전하게 회수하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 스레드와 join 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-option": { | ||
| "title": "값 꺼내기 대신 값 존재 여부 분기하기", | ||
| "concept": "첫 유니코드 스칼라는 비어 있지 않은 텍스트에서 `Some(char)`, 빈 텍스트에서 `None`이다. 명시적인 매치가 두 정상 상태에 의미 있는 출력을 만든다.", | ||
| "worked_example": "`checked_half`는 짝수일 때만 `then_some`으로 값을 만들고 디버그 형식으로 `Option` 래퍼까지 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 스레드와 join 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 스레드와 join 실습의 TODO를 풀지 않는다.", | ||
| "스레드와 join 단원에서는 다음 Rust 문법 목표를 익힙니다: thread::spawn으로 작업을 옮기고 join으로 worker 결과를 안전하게 회수하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "예상 가능한 빈 입력을 프로그래머 오류로 보고 `unwrap`해 패닉한다.", | ||
| "`bytes().next()`로 첫 유니코드 스칼라가 아니라 첫 UTF-8 바이트를 반환한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 스레드와 join 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "스레드와 join 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`chars().next()`가 만든 `Some` 안의 값 타입은 무엇인가?", | ||
| "공백을 다듬은 문자열 길이가 0이면 어느 분기가 실행되는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 스레드와 join 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "선택적인 첫 유니코드 스칼라를 검사해 값이 있으면 그 문자를, 없으면 지정된 부재 표시를 출력하라.", | ||
| "objective": "널, 센티널, 패닉 없이 정상적인 누락값을 완전하게 처리한다.", | ||
| "language_delta": "`Option`은 부재를 타입에 넣어 실행 때까지 잊기 쉬운 널 허용 참조와 다르다.", | ||
| "prediction_prompt": "한국어 시험 사례에서 원시 바이트가 아니라 선택되는 첫 스칼라를 예측하라.", | ||
| "transfer_trap": "`unwrap`은 부재가 진짜 불변 조건 위반일 때만 써라. 빈 사용자 입력은 이 실습의 정상 사례다." | ||
| }, | ||
| "rust-shared-state": { | ||
| "title": "Arc와 Mutex의 공유 상태", | ||
| "concept": "Arc와 Mutex의 공유 상태 단원에서는 다음 Rust 문법 목표를 익힙니다: Arc<T>로 공유 소유권을 만들고 Mutex<T>로 한 번에 하나의 변경만 허용하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 Arc와 Mutex의 공유 상태 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-result": { | ||
| "title": "순수 도우미 밖으로 파싱 오류 전달하기", | ||
| "concept": "`sum_tokens`는 `try_fold`에서 각 정수 변환에 `?`를 적용한다. 첫 `ParseIntError`에서 도우미가 즉시 반환되고 `main`이 사용자용 출력으로 복구한다.", | ||
| "worked_example": "`positive`는 `?`로 오류를 전달한 뒤 별도 도메인 검사를 수행해 오류 전달과 복구 결정이 다른 일임을 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 Arc와 Mutex의 공유 상태 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 Arc와 Mutex의 공유 상태 실습의 TODO를 풀지 않는다.", | ||
| "Arc와 Mutex의 공유 상태 단원에서는 다음 Rust 문법 목표를 익힙니다: Arc<T>로 공유 소유권을 만들고 Mutex<T>로 한 번에 하나의 변경만 허용하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`?`를 오류를 잡아 복구하는 연산자로 오해해 바깥 함수를 즉시 끝내며 오류를 전달한다는 사실을 놓친다.", | ||
| "모든 토큰에 `unwrap`을 사용해 `main`이 잘못된 입력을 안정적인 오류 줄로 바꾸지 못하게 한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 Arc와 Mutex의 공유 상태 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "Arc와 Mutex의 공유 상태 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`?`를 포함한 함수의 반환 타입은 무엇이어야 하는가?", | ||
| "한 토큰 파싱이 `Err`이면 `try_fold`가 뒤 토큰도 방문하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 Arc와 Mutex의 공유 상태 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "모든 유효 정수 토큰을 `Result` 경로로 합산하고 하나라도 파싱에 실패하면 정해진 오류 표시를 출력하라.", | ||
| "objective": "실패 가능한 변환과 사용자 복구를 분리하면서 원래 오류 경로를 보존한다.", | ||
| "language_delta": "Rust `Result`는 시그니처에 드러나지 않은 채 호출 경계를 넘는 예외와 달리 복구 가능한 실패를 반환 타입에 명시한다.", | ||
| "prediction_prompt": "`2` 다음 `bad`를 처리할 때 누적값과 제어가 반환되는 정확한 지점을 추적하라.", | ||
| "transfer_trap": "`?`가 오류를 삼키거나 복구한다고 생각하지 마라. 어느 호출 경계에서 오류를 사용자에게 보여 줄지는 별도로 결정해야 한다." | ||
| }, | ||
| "rust-async-await": { | ||
| "title": "async와 await", | ||
| "concept": "async와 await 단원에서는 다음 Rust 문법 목표를 익힙니다: async가 Future를 만들고 이를 실행하려면 executor나 runtime이 필요하다는 점을 이해하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 async와 await 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-testing": { | ||
| "title": "채점 실행 경로에 검사 남기기", | ||
| "concept": "정답 실행 파일은 표준 입력을 읽기 전에 `assert_eq!`를 호출하므로 검사가 실제로 실행된다. `cfg(test)` 모듈은 이런 일반 컴파일 모드에 포함되지 않는다.", | ||
| "worked_example": "세 배 예제는 인라인 검사를 수행한 뒤 답과 무관한 상태 줄을 출력해 테스트 하네스 없이도 검사가 실행됐음을 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 async와 await 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 async와 await 실습의 TODO를 풀지 않는다.", | ||
| "async와 await 단원에서는 다음 Rust 문법 목표를 익힙니다: async가 Future를 만들고 이를 실행하려면 executor나 runtime이 필요하다는 점을 이해하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "일반 `rustc` 실행 파일에서도 `#[test]` 함수가 실행된다고 가정해 테스트 하네스 없이 안심한다.", | ||
| "신뢰하지 않는 입력 검증에 `assert`를 사용해 제어된 오류 대신 패닉한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 async와 await 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "async와 await 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "인라인 동등성 검사가 틀리면 뒤의 `println!`까지 실행되는가?", | ||
| "테스트 하네스를 활성화하는 명령과 일반 `rustc`의 차이는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 async와 await 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "`add_two`를 구현하고 경계값 검사 두 개를 실행 경로에 유지한 뒤 표준 입력에 대한 함수 결과를 출력하라.", | ||
| "objective": "이 실습 채점기와 같은 실행 모드에 실제로 실행되는 정확성 검사를 남긴다.", | ||
| "language_delta": "Rust 테스트 속성은 `cargo test`나 `rustc --test`가 테스트 하네스를 만들 때만 실행되며 `main`의 일반 호출과 다르다.", | ||
| "prediction_prompt": "`add_two`가 입력을 그대로 반환하고 인라인 검사가 남아 있다면 프로세스가 어떻게 끝나는지 예상하라.", | ||
| "transfer_trap": "이 실습은 실제로 실행되는 검사와 테스트 하네스의 경계만 가르치며, 이것만으로 단위 테스트를 익혔다고 볼 수는 없다." | ||
| }, | ||
| "rust-macros": { | ||
| "title": "macro_rules!", | ||
| "concept": "macro_rules! 단원에서는 다음 Rust 문법 목표를 익힙니다: macro_rules!로 토큰 패턴을 매칭하고 반복되는 Rust 코드를 컴파일 시점에 확장하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 macro_rules! 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-async-await": { | ||
| "title": "제한된 실행기로 `await` 실행하기", | ||
| "concept": "`double`은 `std::future::ready`를 기다리고 `block_on_ready`는 반환된 `Future`를 한 번만 폴링한다. 깨운 뒤 다시 폴링하는 반복문이 없으므로 `Pending`이 나오면 즉시 실패한다.", | ||
| "worked_example": "`message`는 실제 `.await`를 포함하고 같은 단일 폴링 방식으로 실행된다. 퓨처를 만들고 버리는 데 그치지 않고 결과까지 관찰한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 macro_rules! 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 macro_rules! 실습의 TODO를 풀지 않는다.", | ||
| "macro_rules! 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`double`을 호출하고 `Future`를 폴링하지 않아 비동기 본문을 전혀 실행하지 않는다.", | ||
| "타이머, `I/O`처럼 `Pending`이 될 수 있는 퓨처에 이 단일 폴링 도우미를 사용한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 macro_rules! 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "macro_rules! 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "퓨처는 비동기 호출과 폴링 중 어느 시점에 처음 실행되는가?", | ||
| "아무 동작도 하지 않는 웨이커를 허용할 수 있는 명시적 조건은 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 macro_rules! 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "즉시 준비되는 퓨처 안에서 파싱한 숫자를 변환하고, 제한된 실행기로 비동기 계산을 실행해 결과를 꺼내라.", | ||
| "objective": "실제 `.await`를 관찰하면서 실행기가 한 번만 폴링한다는 한계를 정확히 드러낸다.", | ||
| "language_delta": "Rust 비동기 코드는 실행기가 `Future`를 폴링할 때까지 실행되지 않으며, 즉시 예약되는 작업처럼 자동으로 시작하지 않는다.", | ||
| "prediction_prompt": "즉시 준비되는 퓨처가 어느 매치 갈래에 들어가며 두 번째 폴링이 필요 없는 이유를 설명하라.", | ||
| "transfer_trap": "`Pending`이 가능한 작업에는 실제 비동기 실행기를 사용하라. 이 학습용 도우미를 스케줄러로 확장하는 일은 실습 범위 밖이다." | ||
| }, | ||
| "rust-unsafe": { | ||
| "title": "unsafe Rust", | ||
| "concept": "unsafe Rust 단원에서는 다음 Rust 문법 목표를 익힙니다: 컴파일러가 검증할 수 없는 연산을 unsafe 블록 안에 두고 주변 API는 최대한 안전하게 유지하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 unsafe Rust 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-concurrency": { | ||
| "title": "작업 스레드 결과를 조인해 기다리기", | ||
| "concept": "파싱한 `i64`는 `Copy`이므로 `move` 클로저에는 값의 복사본이 들어가고 호출자 쪽 바인딩도 계속 사용할 수 있다. `join`은 작업 스레드가 끝날 때까지 기다린 뒤 계산값을 반환한다.", | ||
| "worked_example": "작은 곱셈 작업 스레드의 핸들을 출력 전에 조인하므로 결과가 준비되기 전에 예제가 끝날 수 없다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 unsafe Rust 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 unsafe Rust 실습의 TODO를 풀지 않는다.", | ||
| "unsafe Rust 단원에서는 다음 Rust 문법 목표를 익힙니다: 컴파일러가 검증할 수 없는 연산을 unsafe 블록 안에 두고 주변 API는 최대한 안전하게 유지하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`JoinHandle`을 버리면 자동으로 기다리는 것이 아니라 작업 스레드가 분리된다.", | ||
| "스레드 생성 클로저에서 스택 데이터를 빌려 작업 스레드가 그 스택 프레임보다 오래 살 수 있다는 `'static` 요구를 어긴다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 unsafe Rust 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "unsafe Rust 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "어떤 값의 복사본이 스레드 경계를 건너며 호출자의 원래 `i64`가 남을 수 있는 이유는 무엇인가?", | ||
| "`join`이 `Err`이면 `expect`는 이를 메인 스레드의 어떤 실패로 바꾸는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 unsafe Rust 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "파싱한 `Copy` 정수를 작업 스레드 하나에 캡처해 그 안에서 두 배로 만들고 조인한 반환값을 출력하라.", | ||
| "objective": "실행 시간이나 프로세스 종료에 기대지 않고 스레드 결과 하나를 명시적으로 동기화한다.", | ||
| "language_delta": "아직 폴링되지 않은 비동기 퓨처와 달리 `spawn`은 독립적으로 예약되는 OS 스레드를 만들며 작업 스레드와 호출자 중 어느 쪽이 먼저 실행될지는 보장하지 않는다.", | ||
| "prediction_prompt": "작업 스레드와 호출자 중 누가 CPU를 먼저 받는지 가정하지 말고 `join`이 보장하는 결과를 예측하라.", | ||
| "transfer_trap": "반환값 하나에는 공유 변경이 필요 없다. `Arc`와 `Mutex`는 여러 작업 스레드 종합 과제에서 사용한다." | ||
| }, | ||
| "rust-cargo-workspaces": { | ||
| "title": "Cargo 패키지와 워크스페이스", | ||
| "concept": "Cargo 패키지와 워크스페이스 단원에서는 다음 Rust 문법 목표를 익힙니다: 단일 파일을 넘어 Cargo 패키지 구조와 cargo check --workspace가 필요한 때를 이해하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 Cargo 패키지와 워크스페이스 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "rust-shared-state": { | ||
| "title": "캡스톤: 조인된 작업 스레드의 공유 변경", | ||
| "concept": "`main`은 입력 `Vec`을 소유하고 정수마다 작업 스레드를 만든다. 클로저는 값과 `Arc` 복제본을 소유하고 잠금 밖에서 계산한 뒤 짧게 `MutexGuard`를 잡는다.", | ||
| "worked_example": "예제는 실제 스레드 둘을 만들고 핸들을 모두 조인한 뒤에만 보호된 합계를 읽는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 Cargo 패키지와 워크스페이스 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 Cargo 패키지와 워크스페이스 실습의 TODO를 풀지 않는다.", | ||
| "Cargo 패키지와 워크스페이스 단원에서는 다음 Rust 문법 목표를 익힙니다: 단일 파일을 넘어 Cargo 패키지 구조와 cargo check --workspace가 필요한 때를 이해하는 법. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`lock`을 즉시 끝나는 필드 접근처럼 여긴다. 잠금을 기다릴 수 있고, 다른 보유자가 패닉한 뒤에는 잠금 오염 오류를 반환할 수도 있다.", | ||
| "관계없는 계산 동안 가드를 유지해 경합을 늘린다. 제곱은 뮤텍스를 얻기 전에 계산해야 한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 Cargo 패키지와 워크스페이스 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "Cargo 패키지와 워크스페이스 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`main`이 마지막 가드를 얻기 전에 모든 `JoinHandle`이 소비되는가?", | ||
| "`Vec` 순회, `Arc` 소유, 스레드 이동, 가드 범위, 잠금 오염 오류 처리는 각각 어디에 나타나는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 Cargo 패키지와 워크스페이스 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "입력마다 작업 스레드를 만들고 모두 조인하며 각 제곱을 공유 `Arc<Mutex<i64>>` 합계에 더하라.", | ||
| "objective": "소유권, 반복, 스레드, 동기화, 결정적 출력을 하나의 범위가 명확한 프로그램에 통합한다.", | ||
| "language_delta": "`Arc`는 여러 소유자의 공유 소유권과 수명을, `Mutex`는 배타적 접근을 관리한다. `RefCell`은 `Sync`가 아니어서 동시 변경을 보호하지 못한다.", | ||
| "prediction_prompt": "모든 작업 스레드를 조인하면 스케줄링 순서가 최종 제곱합을 바꾸지 못하는 이유를 설명하라.", | ||
| "transfer_trap": "소유 `RefCell<T>`는 `T: Send`일 때 이동할 수 있어도 동시 공유는 안 된다. 여기서는 `Mutex`가 있어 `unsafe`도 필요 없다." | ||
| } | ||
| } | ||
| } |
+372
-285
@@ -7,437 +7,524 @@ { | ||
| "rust-output": { | ||
| "title": "输出", | ||
| "concept": "输出 的 Rust 语法目标是:用 println! 格式化值,并精确匹配评测需要的 stdout。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示输出的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "让标准输出精确匹配", | ||
| "concept": "宏 `println!` 会在编译期检查格式字符串与实参关系,并在渲染文本后恰好附加一个换行;格式字符串中的标点同样属于输出。", | ||
| "worked_example": "示例用连字符连接工具名与版本号,展示捕获式格式参数以及整数的自动格式化,却没有复制练习要求的冒号记录。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把输出的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决输出练习里的 TODO。", | ||
| "没有确认“输出 的 Rust 语法目标是:用 println!”这个要点,只去凑 judge 输出。" | ||
| "忘记感叹号,把 `println!` 当成普通函数调用。", | ||
| "在冒号两侧增加空格,数值虽对但标准输出已经改变。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用输出,然后才到达 `example:` 输出?", | ||
| "输出练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "格式字符串产生哪些字符,宏又额外增加哪一个字符?", | ||
| "若契约不允许末尾换行,应选择 `print!` 还是 `println!`?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把输出应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "读取姓名与分数,通过格式字符串生成案例要求的分隔记录,再由 `println!` 写出;冒号周围不得有空格,行尾只能出现一次。", | ||
| "objective": "产生经过编译期格式检查且字节与标准输出契约一致的一行文本。", | ||
| "language_delta": "Rust 格式化把模板与带类型实参分开,并在编译期验证两者关系,不必依赖多次字符串连接。", | ||
| "prediction_prompt": "运行前判断花括号外的标点是否会随每个替换值重复,并写出最终行尾。", | ||
| "transfer_trap": "`println!` 不是普通可变参数函数,而是使用静态已知格式字符串展开的宏。" | ||
| }, | ||
| "rust-variables": { | ||
| "title": "绑定与可变性", | ||
| "concept": "绑定与可变性 的 Rust 语法目标是:优先使用不可变 let 绑定,只在值确实变化的位置使用 mut。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示绑定与可变性的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "用一个可变绑定追踪状态", | ||
| "concept": "`mut` 允许对同一储存位置再次赋值;随后同名的 `let` 是遮蔽,会创建新绑定,甚至可以改变类型,并非修改旧绑定。", | ||
| "worked_example": "示例先把 `value` 从 2 改为 5,再以同名新绑定保存其两倍。一次赋值与一次遮蔽在短程序中分别可见。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把绑定与可变性的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决绑定与可变性练习里的 TODO。", | ||
| "没有确认“绑定与可变性 的 Rust 语法目标是:优先使用不可变 let 绑定,只在值确实变化的位置使用 mut。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "未声明 `mut` 就使用增强赋值,编译器会拒绝写入。", | ||
| "把遮蔽描述为原地更新,忽略旧绑定在何时不可再访问。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用绑定与可变性,然后才到达 `example:` 输出?", | ||
| "绑定与可变性练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "示例哪一行修改储存,哪一行建立同名新绑定?", | ||
| "标签只在累加后输出,是否因此也需要 `mut`?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把绑定与可变性应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "保持第一个标签绑定不可变,把其后每个整数加入一个从加法单位元开始的可变累加器;不得使用乘积或依赖固定项数。", | ||
| "objective": "清楚区分有意变化的运行总和与始终不变的上下文标签。", | ||
| "language_delta": "Rust 要求显式声明绑定可变性;遮蔽却可以在不使用 `mut` 的情况下建立不同类型的新值。", | ||
| "prediction_prompt": "检查最终格式行前,先预测循环每次迭代后的累计总和。", | ||
| "transfer_trap": "后续数字元组练习会加入显式数值类型与元组访问,但 `mut` 和遮蔽的区别保持不变。" | ||
| }, | ||
| "rust-numbers-tuples": { | ||
| "title": "数字与元组", | ||
| "concept": "数字与元组 的 Rust 语法目标是:结合显式数字类型与 pair.0、pair.1 这样的元组字段访问。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示数字与元组的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "用元组观察有符号除法", | ||
| "concept": "对合法 `i64` 操作数,`/` 的商朝零截断,`%` 返回配套余数。除数保证非零,并排除会溢出的 `i64::MIN / -1` 组合。", | ||
| "worked_example": "23 除以 4 产生正商 5 与余数 3,可分别通过元组位置 `.0` 和 `.1` 观察。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把数字与元组的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决数字与元组练习里的 TODO。", | ||
| "没有确认“数字与元组 的 Rust 语法目标是:结合显式数字类型与 pair.0、pair.1 这样的元组字段访问。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "沿用 Python 向下取整直觉,负被除数时得到错误商。", | ||
| "交换元组位置,代码仍能编译却反转输出字段。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用数字与元组,然后才到达 `example:` 输出?", | ||
| "数字与元组练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "-17 除以 5 时,截断朝零还是朝负无穷?", | ||
| "如何核验除法恒等式,并指出被排除的溢出组合?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把数字与元组应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "解析两个整数,确认除数非零且不出现最小整数除以负一,再按商、余数顺序建立元组并输出两个位置。", | ||
| "objective": "尊重 Rust 有符号算术,同时正确计算并拆解一个双值结果。", | ||
| "language_delta": "Rust 整数 `/` 在符号不同时朝零截断,Python 的 `//` 却朝负无穷取整。", | ||
| "prediction_prompt": "调用 `rustc` 前,计算混合符号案例的商与余数。", | ||
| "transfer_trap": "不能仅凭斜线符号推断浮点除法;这里的整数操作数决定了截断行为。" | ||
| }, | ||
| "rust-strings": { | ||
| "title": "字符串", | ||
| "concept": "字符串 的 Rust 语法目标是:区分何时需要拥有所有权的 String,何时借用的 &str 切片就足够。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示字符串的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-input": { | ||
| "title": "消费空白分隔的输入流", | ||
| "concept": "`Read::read_to_string` 把完整标准输入填入自有 `String`,随后 `split_whitespace` 跨任意行边界访问每个片段;空输入产生空迭代器。", | ||
| "worked_example": "字节切片同样实现 `Read`,因此示例可确定地练习 `read_to_string`,无需依赖交互终端。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把字符串的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决字符串练习里的 TODO。", | ||
| "没有确认“字符串 的 Rust 语法目标是:区分何时需要拥有所有权的 String,何时借用的 &str 切片就足够。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "只读一行,悄悄遗漏换行后的合法片段。", | ||
| "在未保证输入有效时随意 `unwrap`,把畸形数据变成难解释的崩溃。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用字符串,然后才到达 `example:` 输出?", | ||
| "字符串练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "空迭代器调用 `sum` 为何得到加法单位元?", | ||
| "哪个导入特征让标准输入拥有 `read_to_string` 方法?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把字符串应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把标准输入读到结束,按所有空白解析保证合法的有符号整数,并用加法汇总;单行、多行与空输入不得采用不同捷径。", | ||
| "objective": "通过一条片段化路径处理单行、多行和空的合法输入。", | ||
| "language_delta": "Rust 数字解析返回 `Result` 而不执行隐式强制转换;此处只有因为契约保证整数片段才可使用 `unwrap`。", | ||
| "prediction_prompt": "先判断 `split_whitespace` 后,多行案例与用空格写在一行的案例是否仍有差异。", | ||
| "transfer_trap": "后续 `Vec` 与 `HashMap` 会复用这份自有片段缓冲,同时表达有序值与按键频率。" | ||
| }, | ||
| "rust-control-flow": { | ||
| "title": "控制流", | ||
| "concept": "控制流 的 Rust 语法目标是:把 if 当作表达式并用 for 范围构造值,而不是机械地写分支。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示控制流的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-vec-hashmap": { | ||
| "title": "组合顺序序列与按键查找", | ||
| "concept": "第一个片段是查询,其余借用词按顺序保存在 `Vec`,同时由 `HashMap` 的 `entry` 接口记录频率。标准散列表不承诺迭代顺序。", | ||
| "worked_example": "示例统计 red、blue、red 三个颜色标签,最后只输出两个不同键的数量,没有打印任何次序未定义的映射遍历结果。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把控制流的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决控制流练习里的 TODO。", | ||
| "没有确认“控制流 的 Rust 语法目标是:把 if 当作表达式并用 for 范围构造值,而不是机械地写分支。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "直接打印 `HashMap` 迭代结果,使输出依赖未指定顺序。", | ||
| "使用索引读取缺失查询,导致程序崩溃。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用控制流,然后才到达 `example:` 输出?", | ||
| "控制流练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪个容器回答值的总数,哪个回答查询出现次数?", | ||
| "为何 `get`、`copied` 与 `unwrap_or` 能不修改映射地处理缺键?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把控制流应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "保持全部值片段的原顺序,并通过 `entry` 填充频率映射,最后报告值数量和查询次数;缺失查询不得插入或崩溃。", | ||
| "objective": "在同一程序中为有序储存选择 `Vec`,为安全按键聚合选择 `HashMap`。", | ||
| "language_delta": "不同于某些保持插入次序的字典,Rust 标准 `HashMap` 有意不提供迭代顺序保证。", | ||
| "prediction_prompt": "追踪查询缺失案例,并判断 `get` 是否会创建任何映射条目。", | ||
| "transfer_trap": "无需用两次查找替代 `entry`;占用或空缺操作已经直接表达更新意图。" | ||
| }, | ||
| "rust-functions": { | ||
| "title": "函数", | ||
| "concept": "函数 的 Rust 语法目标是:用参数类型和返回类型清楚表达计算边界。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示函数的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-control-flow": { | ||
| "title": "让分支返回标签", | ||
| "concept": "Rust 的 `if` 是表达式,奇偶分支会产生稍后输出的字符串;两个分支必须具有兼容类型。范围 `1..=n` 包含终点。", | ||
| "worked_example": "示例先根据七大于五选择就绪标签,再独立消费从一到三且包含终点的整数范围,计算出对应三角数六。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把函数的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决函数练习里的 TODO。", | ||
| "没有确认“函数 的 Rust 语法目标是:用参数类型和返回类型清楚表达计算边界。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "写成 `1..n`,漏掉上界并改变每个正数总和。", | ||
| "一条分支返回字符串、另一条返回整数,使单个表达式没有统一类型。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用函数,然后才到达 `example:` 输出?", | ||
| "函数练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`1..=0` 产生什么序列,`sum` 对它返回哪个单位元?", | ||
| "奇偶判断与总和计算的先后会改变所有权吗?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把函数应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把输入分类为奇数或偶数,再与从一到输入值的包含终点总和配成一条记录;零应形成空范围和偶数标签。", | ||
| "objective": "从带类型的分支表达式与边界敏感的范围计算构造一次输出。", | ||
| "language_delta": "Rust 条件必须是 `bool`,不存在 C、JavaScript 或 Python 式数字真假性。", | ||
| "prediction_prompt": "输入四时,先列出范围中每个元素,再预测显示的总和。", | ||
| "transfer_trap": "迭代器练习会以惰性适配器重述遍历,此处先把同等范围边界清楚展示。" | ||
| }, | ||
| "rust-structs-impl": { | ||
| "title": "结构体与 impl", | ||
| "concept": "结构体与 impl 的 Rust 语法目标是:把领域字段组织到 struct 中,并把依赖这些字段的行为放进 impl。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示结构体与 impl的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-iterators": { | ||
| "title": "由终止求和驱动筛选与映射", | ||
| "concept": "解析产生自有整数;`filter` 为谓词借用候选项,`map` 接收通过者,最终 `sum` 才真正消费整条流水线。", | ||
| "worked_example": "无界范围之所以安全,是因为 `take` 在 `collect` 请求具体 `Vec` 前限制了消费数量。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把结构体与 impl的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决结构体与 impl练习里的 TODO。", | ||
| "没有确认“结构体与 impl 的 Rust 语法目标是:把领域字段组织到 struct 中,并把依赖这些字段的行为放进 impl。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "只创建筛选与映射适配器,没有终止操作,因此任何转换都未发生。", | ||
| "省略平方映射,算成偶数原值之和。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用结构体与 impl,然后才到达 `example:` 输出?", | ||
| "结构体与 impl练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "迭代在调用哪个方法时真正开始?", | ||
| "零为何通过偶数谓词,却在平方后不增加结果?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把结构体与 impl应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "解析整数流,惰性保留偶数并平方,再由一个终止 `sum` 消费为总和;负数与全奇数输入都不建立中间集合。", | ||
| "objective": "通过所有权明确的适配器和一个终止消费者表达多阶段遍历。", | ||
| "language_delta": "Rust 迭代链类似其他语言的流,但适配器类型还编码借用方式与元素所有权。", | ||
| "prediction_prompt": "对 -2、-1、0,依次预测筛选后与映射后的项目序列。", | ||
| "transfer_trap": "不要在每个适配器间 `collect`;`sum` 可直接拉取元素,中间 `Vec` 没有必要。" | ||
| }, | ||
| "rust-enum-match": { | ||
| "title": "枚举与 match", | ||
| "concept": "枚举与 match 的 Rust 语法目标是:用 enum 变体建模不同情况,并用 match 穷尽处理每一种情况。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示枚举与 match的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-functions": { | ||
| "title": "检查点:值穿过函数边界", | ||
| "concept": "面积函数按值接收两个 `i64`,并由末尾表达式返回 `i64`;入口函数继续独自负责读取与输出。末尾分号会把块结果改成单元值。", | ||
| "worked_example": "独立问候函数借助 `format!` 返回分配的 `String`,用非数值数据展示表达式返回写法。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把枚举与 match的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决枚举与 match练习里的 TODO。", | ||
| "没有确认“枚举与 match 的 Rust 语法目标是:用 enum 变体建模不同情况,并用 match 穷尽处理每一种情况。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "在宽乘高后添加分号,使函数体结果变成单元值。", | ||
| "在面积函数中打印而不是返回,把计算耦合到单一输出场景。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用枚举与 match,然后才到达 `example:` 输出?", | ||
| "枚举与 match练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "面积函数能否连续调用两次而不保留第一次隐藏状态?", | ||
| "签名的哪一段向编译器与调用方承诺返回 `i64`?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把枚举与 match应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "解析宽高,调用没有隐藏状态的面积函数,并只输出其返回值;乘法必须作为正确类型的末尾表达式,不能改在入口重复计算。", | ||
| "objective": "在可复用函数边界区分带类型计算、标准输入与标准输出。", | ||
| "language_delta": "Rust 末尾表达式无需写 `return`,但分号位置会直接改变块的语义与类型。", | ||
| "prediction_prompt": "编译前分别预测乘法末尾有分号与无分号时的函数体类型。", | ||
| "transfer_trap": "模块、泛型、特征和宏会扩展此边界,但都不改变返回表达式与输出职责的区分。" | ||
| }, | ||
| "rust-option": { | ||
| "title": "Option 与 if let", | ||
| "concept": "Option 与 if let 的 Rust 语法目标是:用 Option<T> 明确表示可能缺失的值,并安全处理 Some 分支。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示Option 与 if let的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-modules-use": { | ||
| "title": "缩短公开模块路径", | ||
| "concept": "模块用 `pub` 暴露评分函数,`use` 在外围作用域引入短名称;可见性和名称解析是两项独立决定。", | ||
| "worked_example": "嵌套 `units` 中的项目公开后先被导入,入口再调用它,完整展示从限定路径到短名称的过程。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把Option 与 if let的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决Option 与 if let练习里的 TODO。", | ||
| "没有确认“Option 与 if let 的 Rust 语法目标是:用 Option<T> 明确表示可能缺失的值,并安全处理 Some 分支。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "为私有同级函数添加 `use`,却以为能绕过隐私规则。", | ||
| "从错误模块写相对路径,解析到不同命名空间或直接失败。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用Option 与 if let,然后才到达 `example:` 输出?", | ||
| "Option 与 if let练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "没有 `use` 时,入口能直接调用 `grading::verdict` 吗?", | ||
| "哪个关键字决定模块外代码能否调用评分函数?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把Option 与 if let应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "保留评分函数的公开性与导入路径,只修正包含边界,使 80 分及以上都通过;79、80、91 应展示边界两侧。", | ||
| "objective": "通过 `use` 解析公开项目,并应用包含 80 分的分类规则。", | ||
| "language_delta": "Rust `use` 更像词法别名,而不是在运行时加载包的导入操作。", | ||
| "prediction_prompt": "先判断 79、80、91 的结果,再说明 `use` 只改变路径还是也改变函数行为。", | ||
| "transfer_trap": "Cargo 包和 crate 定义更大的构建边界,此单文件练习只覆盖语言模块及路径。" | ||
| }, | ||
| "rust-modules-use": { | ||
| "title": "模块与 use", | ||
| "concept": "模块与 use 的 Rust 语法目标是:用 mod、pub、use 控制命名空间、可见性与本地路径。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示模块与 use的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-generics": { | ||
| "title": "从泛型切片复制最后元素", | ||
| "concept": "辅助函数接收实现 `Copy` 的任意 `T` 的共享切片;`copied` 把 `Option<&T>` 变为 `Option<T>`,并未声称每种 Rust 类型都能廉价复制。", | ||
| "worked_example": "配套函数从借用的 `[8_u8, 9]` 数组取得首个 `u8`,再以调试格式展示其 `Option` 结果,全程无需强制转换。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把模块与 use的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决模块与 use练习里的 TODO。", | ||
| "没有确认“模块与 use 的 Rust 语法目标是:用 mod、pub、use 控制命名空间、可见性与本地路径。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "省略 `Copy` 约束,便无法通过 `copied` 取出 `T` 值。", | ||
| "无条件调用 `clone`,扩大到 `Clone` 并可能隐藏昂贵操作。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用模块与 use,然后才到达 `example:` 输出?", | ||
| "模块与 use练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "函数借用了什么,又返回了哪个小值?", | ||
| "`String` 是否满足该约束;若不满足,接口是否应返回引用?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把模块与 use应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "实现泛型切片最后元素辅助函数,并把它用于解析后的整数切片;不得移动切片内容,也不要把精确的 `Copy` 约束扩大。", | ||
| "objective": "使用恰好能证明共享借用中复制值合理的特征约束。", | ||
| "language_delta": "泛型写法类似模板,但 Rust 检查一个受约束定义,而不是等到后续展开才接受任意操作。", | ||
| "prediction_prompt": "入口调用 `unwrap` 前,预测单元素切片产生的 `Option`。", | ||
| "transfer_trap": "不要为熟悉感改用更宽的 `Clone`;数值练习真正需要的行为就是 `Copy`。" | ||
| }, | ||
| "rust-input": { | ||
| "title": "输入解析", | ||
| "concept": "输入解析 的 Rust 语法目标是:一次读取 stdin,把文本拆成 token,再转换成有类型的值。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示输入解析的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-traits": { | ||
| "title": "借用实现共享摘要行为", | ||
| "concept": "分数卡通过借用 `self` 实现摘要特征,泛型渲染函数接受任何满足该约束的借用值,并返回生成文本,不会取得卡片所有权。", | ||
| "worked_example": "`Size` 示例为任意元素类型的 `Vec` 实现返回长度的小型行为,再以普通点语法在三项向量上调用方法。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把输入解析的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决输入解析练习里的 TODO。", | ||
| "没有确认“输入解析 的 Rust 语法目标是:一次读取 stdin,把文本拆成 token,再转换成有类型的值。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "试图从共享借用移出姓名字段,既不允许也没有格式化必要。", | ||
| "把渲染参数固定为分数卡,丢失特征契约提供的复用。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用输入解析,然后才到达 `example:` 输出?", | ||
| "输入解析练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "调用摘要后,卡片是否仍归原所有者使用?", | ||
| "泛型函数在哪里声明 `T` 必须实现摘要特征?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把输入解析应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "从输入建立分数卡,并通过受摘要特征约束的辅助函数借用姓名与分数进行渲染;不得克隆或移出自有字段。", | ||
| "objective": "定义共享行为,并在不放弃实现值所有权的前提下调用它。", | ||
| "language_delta": "Rust 特征可通过泛型提供静态分派,不需要继承式对象层次。", | ||
| "prediction_prompt": "判断两个互不相关、但都实现摘要特征的结构能否交给同一渲染函数。", | ||
| "transfer_trap": "不要为了消除借用错误就克隆自有字段,格式化接口本来可以接收可显示值的引用。" | ||
| }, | ||
| "rust-vec-hashmap": { | ||
| "title": "Vec 与 HashMap", | ||
| "concept": "Vec 与 HashMap 的 Rust 语法目标是:有顺序的数据使用 Vec,按键计数和查找使用 HashMap 的 entry API。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示Vec 与 HashMap的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-macros": { | ||
| "title": "在导出宏中卫生地引用定义方 crate", | ||
| "concept": "导出的 `greet!` 展开为 `$crate::helper`,先把辅助函数解析锚定在定义方 crate 根,再像普通代码一样进行类型检查。", | ||
| "worked_example": "嵌套模块中的 `caller::run` 调用导出宏 `increment!`,但 `$crate` 仍把 `helper` 解析到定义方 crate 根。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把Vec 与 HashMap的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决Vec 与 HashMap练习里的 TODO。", | ||
| "没有确认“Vec 与 HashMap 的 Rust 语法目标是:有顺序的数据使用 Vec,按键计数和查找使用 HashMap 的 entry API。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "忘记感叹号,编译器便寻找普通函数而不是调用宏。", | ||
| "在展开中使用输入表达式两次,使其中副作用也执行两次。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用Vec 与 HashMap,然后才到达 `example:` 输出?", | ||
| "Vec 与 HashMap练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "从不同路径调用宏时,`$crate` 为何仍能稳定找到辅助函数?", | ||
| "宏匹配结束后,展开的辅助调用在哪里接受类型检查?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把Vec 与 HashMap应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "补全定义方 crate 根的问候辅助函数,让嵌套调用者经 `#[macro_export]` 与 `$crate` 取得正确结果;输入姓名只在入口去空白一次。", | ||
| "objective": "导出一个通过 `$crate` 卫生解析辅助函数的宏。", | ||
| "language_delta": "宏在运行时之前操作 Rust 语法,不是字符串替换,也不是反射。", | ||
| "prediction_prompt": "在嵌套调用函数中展开 `crate::greet!(name)`,指出 `rustc` 解析的确切辅助路径。", | ||
| "transfer_trap": "导出宏若要调用自身 crate 的辅助项目,调用方相对路径很脆弱,必须由 `$crate` 锚定。" | ||
| }, | ||
| "rust-borrowing-slices": { | ||
| "title": "借用与切片", | ||
| "concept": "借用与切片 的 Rust 语法目标是:传递借用的切片,让函数读取数据而不取得所有权。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示借用与切片的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-cargo-workspaces": { | ||
| "title": "识别 Cargo 工作空间命令范围", | ||
| "concept": "工作空间协调多个成员包与共享构建目录。此单文件运行器只能验证面向全部成员的命令文本,不能创建清单或证明真实多包构建。", | ||
| "worked_example": "示例打印元数据检查命令,并明确不暗示纯 `rustc` 文件已经加载任何 Cargo 清单。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把借用与切片的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决借用与切片练习里的 TODO。", | ||
| "没有确认“借用与切片 的 Rust 语法目标是:传递借用的切片,让函数读取数据而不取得所有权。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "在某个成员内运行普通测试,却以为必然覆盖工作空间每个成员。", | ||
| "把三条文本映射当作已经验证工作空间配置。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用借用与切片,然后才到达 `example:` 输出?", | ||
| "借用与切片练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪个标志让 `cargo check` 或 `cargo test` 选择整个工作空间?", | ||
| "格式化命令为何在双连字符后把检查参数转交给 `rustfmt`?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把借用与切片应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "仅把 `check`、`test`、`fmt` 三种意图映射为文档规定的工作空间命令;不要声称执行真实多包构建,也不得混淆 Cargo 与 `rustfmt` 参数。", | ||
| "objective": "识别安全的全工作空间维护命令,同时如实限定运行器能力。", | ||
| "language_delta": "Cargo 工作空间属于构建工具,并非普通 `rustc` 能验证的 Rust 语言构造。", | ||
| "prediction_prompt": "意图为 `fmt` 时,区分哪些参数由 Cargo 处理,哪些传给 `rustfmt`。", | ||
| "transfer_trap": "只有评测器能创建临时工作空间并检查真实成员行为后,此主题才适合提升为核心路径。" | ||
| }, | ||
| "rust-result": { | ||
| "title": "Result 与 ?", | ||
| "concept": "Result 与 ? 的 Rust 语法目标是:用 Result<T, E> 返回可恢复错误,并用 ? 运算符向调用方传播。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示Result 与 ?的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-ownership": { | ||
| "title": "转移并取回 `String` 所有权", | ||
| "concept": "入口先在原 `String` 缓冲中就地删除行尾,再把所有者移动给描述函数。`String` 元数据包含指针、长度和容量;非空值拥有 `UTF-8` 缓冲,空值可能不分配。", | ||
| "worked_example": "`wrap` 消费 `String` 的元数据并取得既有缓冲所有权,移动后入口不能再次使用旧绑定。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把Result 与 ?的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决Result 与 ?练习里的 TODO。", | ||
| "没有确认“Result 与 ?”这个要点,只去凑 judge 输出。" | ||
| "按值传递后仍读取调用方绑定,触发已移动值错误。", | ||
| "每逢所有权错误就增加 `clone`,制造不必要的分配而不调整参数或返回形态。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用Result 与 ?,然后才到达 `example:` 输出?", | ||
| "Result 与 ?练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "所有权在何次调用离开入口,又在何种模式绑定中回来?", | ||
| "为何描述函数可先调用 `len`,随后再把文本移入元组?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把Result 与 ?应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "在原标准输入 `String` 中去掉 `CR` 与 `LF` 行尾,再把同一所有者移动进描述函数,并连同 `UTF-8` 字节长度返回;禁止克隆。", | ||
| "objective": "不复制缓冲地跨按值函数边界移动并返回同一个 `String`,空输入可以保持无分配。", | ||
| "language_delta": "移动 `String` 会转移固定大小的指针、长度和容量元数据,既不复制堆缓冲,也不是只传一个指针字。", | ||
| "prediction_prompt": "先预测 é 的字节数,再与后续 Unicode 标量计数区分。", | ||
| "transfer_trap": "`Box` 与 `Arc` 表达其他所有权结构,`RefCell` 把借用检查延后;普通移动应先于这些扩展掌握。" | ||
| }, | ||
| "rust-ownership": { | ||
| "title": "所有权与借用", | ||
| "concept": "所有权与借用 的 Rust 语法目标是:追踪 String 何时移动、何时返回,以及引用何时只是借用它。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示所有权与借用的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-smart-pointers": { | ||
| "title": "间接拥有递归节点", | ||
| "concept": "递归列表的 `Cons` 通过 `Box<List>` 保存尾部,使每个枚举值具有有限已知大小,同时维持整条链的排他所有权。", | ||
| "worked_example": "示例建立分别保存 4 和 5 的两个装箱链接,并通过共享引用递归遍历得到九,因此求和过程不会消费列表。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把所有权与借用的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决所有权与借用练习里的 TODO。", | ||
| "没有确认“所有权与借用 的 Rust 语法目标是:追踪 String 何时移动、何时返回,以及引用何时只是借用它。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "把 `List` 直接放进 `Cons`,形成无限大小类型而被编译器拒绝。", | ||
| "声称 `Box` 必然加速;这里关键是带所有权、已知指针大小的间接层。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用所有权与借用,然后才到达 `example:` 输出?", | ||
| "所有权与借用练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "即使剩余列表任意长,`Cons` 的哪个部分大小固定?", | ||
| "为什么从反向迭代折叠能保留原片段顺序?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把所有权与借用应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把全部解析整数反向折叠成由 `Box` 连接的递归 `Cons` 节点,再通过共享借用遍历求和;空输入应构造 `Nil`。", | ||
| "objective": "以 `Box` 让具有所有权的递归数据类型可表示,并在不移动节点的情况下访问。", | ||
| "language_delta": "垃圾回收语言可能隐藏递归间接层,Rust 要求所有权边在类型中明确出现。", | ||
| "prediction_prompt": "求递归总和前,画出三个片段形成的 `Cons` 链。", | ||
| "transfer_trap": "`Arc` 与 `Rc` 用于共享所有权,此列表每条尾边恰有一个所有者,只需要 `Box`。" | ||
| }, | ||
| "rust-iterators": { | ||
| "title": "迭代器与闭包", | ||
| "concept": "迭代器与闭包 的 Rust 语法目标是:用闭包组合惰性 iterator 适配器,并用 sum 或 collect 消费它们。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示迭代器与闭包的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-interior-mutability": { | ||
| "title": "在不可变所有者内部运行时可变借用", | ||
| "concept": "事件日志通过 `&self` 暴露记录操作,`RefCell` 在运行时检查共享与可变借用。可变守卫在推入元素后结束,最终共享借用才不会重叠。", | ||
| "worked_example": "`Cell` 可处理 `Copy` 整数而不借出引用,所以示例能通过不可变绑定把访问计数从四增加到五。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把迭代器与闭包的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决迭代器与闭包练习里的 TODO。", | ||
| "没有确认“迭代器与闭包 的 Rust 语法目标是:用闭包组合惰性 iterator 适配器,并用 sum 或 collect 消费它们。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "持有 `borrow_mut` 时又请求 `borrow`,因重叠访问在运行时触发 `panic`。", | ||
| "把 `RefCell` 当同步工具;它不是 `Sync`,不能保护多线程共享修改。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用迭代器与闭包,然后才到达 `example:` 输出?", | ||
| "迭代器与闭包练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "记录方法中的 `RefMut` 守卫在什么位置结束生存?", | ||
| "所有者未声明 `mut` 时,哪条排他规则仍会被执行?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把迭代器与闭包应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "经 `&self` 为每个输入片段追加一条事件,保持每次可变借用短且不重叠,最后用共享借用报告 `RefCell<Vec<String>>` 的长度。", | ||
| "objective": "只在短小、无重叠、单线程作用域内应用内部可变性。", | ||
| "language_delta": "`RefCell<T>` 在 `T: Send` 时可随所有权移到别的线程,但它不是 `Sync`;共享并发修改应使用 `Mutex`。", | ||
| "prediction_prompt": "若记录方法持有一个可变守卫时又请求第二个,预测运行结果。", | ||
| "transfer_trap": "`RefCell` 只是把排他检查推迟到执行期,并未删除规则或提供线程同步。" | ||
| }, | ||
| "rust-generics": { | ||
| "title": "泛型", | ||
| "concept": "泛型 的 Rust 语法目标是:编写作用于 T 的函数,同时保留编译期的具体类型检查。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示泛型的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-strings": { | ||
| "title": "区分 `UTF-8` 文本的三种视图", | ||
| "concept": "`String` 与 `str` 储存 `UTF-8`;`len` 报告字节数,`chars` 产生 Unicode 标量,范围切片则必须从合法字符边界开始并结束。", | ||
| "worked_example": "螃蟹标量占四字节,示例从 `🦀ace` 安全切出首个四字节范围,并同时显示整段文本的字节数与标量数并不相同。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把泛型的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决泛型练习里的 TODO。", | ||
| "没有确认“泛型 的 Rust 语法目标是:编写作用于 T 的函数,同时保留编译期的具体类型检查。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "尝试 `text[0]`,忽略一个字节不一定对应一个字符。", | ||
| "在任意字节偏移切片,即使偏移小于长度也可能崩溃。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用泛型,然后才到达 `example:` 输出?", | ||
| "泛型练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "案例写法中的重音 e 含几个字节和几个标量?", | ||
| "螃蟹前缀范围为何是四字节而不是一字节?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把泛型应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "对输入文本分别报告 `UTF-8` 字节长度、Unicode 标量数量与首个标量;三项必须用适合各自视图的操作,不能按整数索引字符串。", | ||
| "objective": "依据所问度量正确选择 `len`、`chars` 与边界安全的首标量查找。", | ||
| "language_delta": "Rust 的 `UTF-8` 字节长度不同于 Java 与 JavaScript 的 `UTF-16` 单元长度;字素分割还需要额外 Unicode 逻辑。", | ||
| "prediction_prompt": "编译前预测螃蟹加字母案例的三个输出字段。", | ||
| "transfer_trap": "不要把 ASCII 索引直觉移到 `str`;字节位置只是带有效性约束的储存偏移。" | ||
| }, | ||
| "rust-traits": { | ||
| "title": "trait 与边界", | ||
| "concept": "trait 与边界 的 Rust 语法目标是:用 trait 描述所需行为,并用边界在泛型代码中调用该行为。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示trait 与边界的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-borrowing-slices": { | ||
| "title": "检查点:借出自有输入的切片", | ||
| "concept": "入口拥有 `String` 缓冲,首词函数临时借用它,并返回指向同一储存的 `&str`;整个过程不为单词另行分配。", | ||
| "worked_example": "`key` 使用 `split_once` 借出自有记录的前缀,该切片只在记录仍存活时有效。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把trait 与边界的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决trait 与边界练习里的 TODO。", | ||
| "没有确认“trait 与边界 的 Rust 语法目标是:用 trait 描述所需行为,并用边界在泛型代码中调用该行为。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "返回局部 `String` 的切片会指向已释放储存,因此被拒绝。", | ||
| "在首词函数内调用 `to_owned`,改变接口并增加本可避免的分配。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用trait 与边界,然后才到达 `example:` 输出?", | ||
| "trait 与边界练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "打印返回切片期间,哪个值必须继续存活?", | ||
| "空输入为何能安全返回空借用字符串而无需 `unwrap`?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把trait 与边界应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "借用完整标准输入缓冲,通过空白迭代返回第一个词的 `&str`;有前导空白和完全为空时都不得分配或崩溃。", | ||
| "objective": "把返回切片关联到调用方拥有的 `String`,并安全处理没有单词的情况。", | ||
| "language_delta": "Rust 引用携带编译期有效期关系,垃圾回收语言往往允许子串值而不暴露其所有权联系。", | ||
| "prediction_prompt": "使用 `split_whitespace` 时,前导空白是否属于返回切片?", | ||
| "transfer_trap": "生命周期、动态特征结果与不安全指针义务都建立在所有者限制的关系上,不会延长数据本身。" | ||
| }, | ||
| "rust-lifetimes": { | ||
| "title": "生命周期", | ||
| "concept": "生命周期 的 Rust 语法目标是:标注借用输入与借用输出之间的关系,而不是延长存储时间。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示生命周期的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "命名返回选择共享的生命周期", | ||
| "concept": "较长函数可能返回任一参数,因此必须用一个显式生命周期参数把两个输入引用与输出关联;调用方仍拥有全部底层文本,注解不会延长储存。", | ||
| "worked_example": "较短函数有两个借用输入,输出省略规则无法判断返回来自哪一个,所以需要显式 `'a` 表达共同关系。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把生命周期的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决生命周期练习里的 TODO。", | ||
| "没有确认“生命周期 的 Rust 语法目标是:标注借用输入与借用输出之间的关系,而不是延长存储时间。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "相信输出省略能在两个输入引用间自动选择,实际必须写明关系。", | ||
| "用 `str::len` 比较,得到 `UTF-8` 字节数而非要求的 Unicode 标量数。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用生命周期,然后才到达 `example:` 输出?", | ||
| "生命周期练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "生命周期参数在运行时分配或保留了什么?什么也没有。", | ||
| "为何返回引用必须由实际被选输入能够支持的时段覆盖?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把生命周期应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "以竖线拆分输入,比较两侧 Unicode 标量数量并返回较长一侧的借用;相等时选择左侧,且输出引用继续受输入缓冲限制。", | ||
| "objective": "在不改变所有权或储存时长的情况下标注引用关系。", | ||
| "language_delta": "生命周期是关于借用的编译期推理,不同于会主动延长分配生存的引用计数句柄。", | ||
| "prediction_prompt": "分别按字节与标量比较 é 和 aa,解释为何所需优胜方会改变。", | ||
| "transfer_trap": "不要添加 `'static` 只为消除错误,那会要求全局持久数据,而不是描述实际调用方借用。" | ||
| }, | ||
| "rust-traits-lifetimes": { | ||
| "title": "trait 对象与 dyn 分发", | ||
| "concept": "trait 对象与 dyn 分发 的 Rust 语法目标是:当运行时分发的灵活性比单一泛型类型更重要时使用 &dyn Trait。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示trait 对象与 dyn 分发的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "动态分派并借出结果", | ||
| "concept": "渲染函数接收 `&dyn Draw`,经虚表选择具体实现,并返回从组件借出的 `&str`;结果不能比该组件借用存活更久。", | ||
| "worked_example": "`Named` 示例把标签通过特征对象传入,再借出其内部文本,无需克隆自有 `String`。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把trait 对象与 dyn 分发的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决trait 对象与 dyn 分发练习里的 TODO。", | ||
| "没有确认“trait 对象与 dyn 分发 的 Rust 语法目标是:当运行时分发的灵活性比单一泛型类型更重要时使用 &dyn Trait。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "添加不受 `Self: Sized` 限制的泛型方法,使特征无法用于动态分派。", | ||
| "返回 `draw` 内临时创建字符串的引用,形成悬垂并被拒绝。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用trait 对象与 dyn 分发,然后才到达 `example:` 输出?", | ||
| "trait 对象与 dyn 分发练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "渲染返回后,哪些文本仍归按钮或标签所有?", | ||
| "哪个调用动态分派,返回 `str` 又推断出怎样的生命周期?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把trait 对象与 dyn 分发应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "按输入建立按钮或标签,通过短期 `&dyn Draw` 引用渲染其完整借用文本;不得删除首标量、克隆文本或返回临时值引用。", | ||
| "objective": "把对象安全的运行时分派与跟随对象借用的结果组合起来。", | ||
| "language_delta": "Rust 特征对象显式进行动态分派,泛型特征约束通常会为每种具体类型单态化。", | ||
| "prediction_prompt": "预测 `label:Rust` 选择哪个实现,以及返回切片能保持有效到何时。", | ||
| "transfer_trap": "无需自动装箱每个值;入口拥有具体组件,短期 `&dyn Draw` 借用已经足够。" | ||
| }, | ||
| "rust-testing": { | ||
| "title": "测试与断言", | ||
| "concept": "测试与断言 的 Rust 语法目标是:把纯逻辑从 main 分离出来,让 #[test] 函数和 assert 宏验证行为。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示测试与断言的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-unsafe": { | ||
| "title": "把安全义务限制在局部", | ||
| "concept": "安全包装函数可把有效共享引用转成原始指针,并只执行一次不安全读取;注释必须说明存活、对齐和可复制性前提。", | ||
| "worked_example": "示例先证明索引零存在,再于小型 `unsafe` 区块调用 `get_unchecked`,使不变量可单独审查。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把测试与断言的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决测试与断言练习里的 TODO。", | ||
| "没有确认“测试与断言 的 Rust 语法目标是:把纯逻辑从 main 分离出来,让 #[test] 函数和 assert 宏验证行为。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "把 `unsafe` 当成通用逃生口,它不会让悬垂或未对齐指针变合法。", | ||
| "把 Rust 2024 的默认警告误称硬错误;只有拒绝相应 `lint` 后才会升级。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用测试与断言,然后才到达 `example:` 输出?", | ||
| "测试与断言练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪些指针事实使 `read` 在该行有效?", | ||
| "哪个安全引用保证读取期间指向值仍然存活?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把测试与断言应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "从已解析整数的共享引用取得有效原始指针,在安全函数内部用一个显式 `unsafe` 区块复制读取,并紧邻写明完整不变量。", | ||
| "objective": "把不安全代码限制为一项可审查操作,同时维持调用方的安全契约。", | ||
| "language_delta": "Rust 2024 的 `unsafe_op_in_unsafe_fn` 默认发出警告;显式区块让审计范围可见,设置为拒绝时才成为错误。", | ||
| "prediction_prompt": "假设读取前引用已失效,指出注释中的哪条安全事实会变成假。", | ||
| "transfer_trap": "若包装层能在内部建立所有前置条件,调用方无需额外义务,就不应把整个函数标成不安全。" | ||
| }, | ||
| "rust-smart-pointers": { | ||
| "title": "智能指针", | ||
| "concept": "智能指针 的 Rust 语法目标是:用 Box<T> 拥有堆上的数据,同时利用类似指针的解引用行为。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示智能指针的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-structs-impl": { | ||
| "title": "把借用行为放在数据旁", | ||
| "concept": "矩形拥有两个尺寸,面积方法只借用 `&self`,计算后实例仍可使用;点语法会自动提供接收者引用。", | ||
| "worked_example": "计数器以数值 11 建立实例,只读方法返回其两倍;接收者在调用中被共享借用,而不是消费或可变借用。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把智能指针的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决智能指针练习里的 TODO。", | ||
| "没有确认“智能指针 的 Rust 语法目标是:用 Box<T> 拥有堆上的数据,同时利用类似指针的解引用行为。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "把面积接收者写成 `self`,尽管计算只读字段却消费矩形。", | ||
| "把 `Rectangle::area` 当成无接收者关联函数,忽略显式 `&self` 参数。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用智能指针,然后才到达 `example:` 输出?", | ||
| "智能指针练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "面积方法能连续调用两次吗,哪个接收者选择使之成立?", | ||
| "以后怎样用特征为多种类型提供类似方法行为?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把智能指针应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "从解析尺寸构造矩形,通过借用接收者的面积方法计算乘积并输出;方法不得消费或可变借用实例,也不能把乘法改成加法。", | ||
| "objective": "把相关字段建模为结构,并通过 `impl` 附加不消费对象的行为。", | ||
| "language_delta": "Rust 方法显式说明接收者所有权,不隐藏一次对象调用究竟消费、修改还是借用目标。", | ||
| "prediction_prompt": "纯乘法是否需要把 `&self` 改为 `&mut self`?执行前说明原因。", | ||
| "transfer_trap": "泛型与特征可在结构上叠加复用行为,此核心练习用一个具体 `impl` 保持边界最清楚。" | ||
| }, | ||
| "rust-interior-mutability": { | ||
| "title": "内部可变性", | ||
| "concept": "内部可变性 的 Rust 语法目标是:当运行时借用检查是最简单且正确的模型时使用 RefCell<T>。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示内部可变性的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-enum-match": { | ||
| "title": "穷尽处理封闭选择", | ||
| "concept": "命令枚举表示加法、乘法或退出,并让不同变体携带各自数据。求值函数明确列出每一项,日后新增变体时 `rustc` 会指出遗漏。", | ||
| "worked_example": "交通灯匹配覆盖红、黄、绿三个无负载变体,并由表达式为黄色返回三秒这一 `u8` 持续时间。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把内部可变性的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决内部可变性练习里的 TODO。", | ||
| "没有确认“内部可变性 的 Rust 语法目标是:当运行时借用检查是最简单且正确的模型时使用 RefCell<T>。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "使用通配分支,枚举增长时失去有用的非穷尽诊断。", | ||
| "先读取负载再判断命令,使没有负载的退出变体难以安全表示。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用内部可变性,然后才到达 `example:` 输出?", | ||
| "内部可变性练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪些变体拥有两个整数,哪个没有负载?", | ||
| "下划线吞掉所有未匹配命令后,会失去什么编译器帮助?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把内部可变性应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把一条输入解析为对应命令枚举,再用显式加法、乘法与退出分支穷尽求值;不得以通配分支返回哨兵。", | ||
| "objective": "以携带负载的变体与完整模式匹配表示封闭命令集合。", | ||
| "language_delta": "Rust 在静态阶段检查 `match` 穷尽性,某些语言的 `switch` 仍允许遗漏或贯穿。", | ||
| "prediction_prompt": "计算结果前,先写出每个分支中能够绑定的名称。", | ||
| "transfer_trap": "`Option` 也是枚举,后续练习中的 `Some` 与 `None` 同样需要有意处理。" | ||
| }, | ||
| "rust-concurrency": { | ||
| "title": "线程与 join", | ||
| "concept": "线程与 join 的 Rust 语法目标是:把工作移动到 thread::spawn 中,并用 join 安全取回 worker 结果。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示线程与 join的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-option": { | ||
| "title": "按存在性分支而不强取", | ||
| "concept": "非空文本的第一个 Unicode 标量是 `Some(char)`,空文本则是 `None`。显式匹配能为两种正常状态产生有意义输出。", | ||
| "worked_example": "`checked_half` 只在整数为偶数时用 `then_some` 构造 `Option`,调试格式会显示外层容器。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把线程与 join的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决线程与 join练习里的 TODO。", | ||
| "没有确认“线程与 join 的 Rust 语法目标是:把工作移动到 thread::spawn 中,并用 join 安全取回 worker 结果。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "对预期空输入调用 `unwrap`,把正常缺失当作程序员错误崩溃。", | ||
| "使用 `bytes().next`,取得首个 `UTF-8` 字节而非首个 Unicode 标量。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用线程与 join,然后才到达 `example:` 输出?", | ||
| "线程与 join练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`chars().next()` 的 `Some` 中储存什么负载类型?", | ||
| "去除行尾后字符串长度为零时会进入哪个分支?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把线程与 join应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "检查可选的首个 Unicode 标量,有值时输出该字符,缺失时输出约定单词;韩文与空输入都不得使用字节或强取。", | ||
| "objective": "不借助空引用、哨兵或崩溃,穷尽处理正常缺失值。", | ||
| "language_delta": "`Option` 把缺失编码进类型,不像可空引用那样可能直到运行时才被遗忘。", | ||
| "prediction_prompt": "不要按原始字节推断,先预测韩文案例选择的首标量。", | ||
| "transfer_trap": "只有缺失真正违反不变量时才适合 `unwrap`;空用户输入已明确属于此练习。" | ||
| }, | ||
| "rust-shared-state": { | ||
| "title": "Arc 与 Mutex 的共享状态", | ||
| "concept": "Arc 与 Mutex 的共享状态 的 Rust 语法目标是:用 Arc<T> 共享所有权,并用 Mutex<T> 保证一次只允许一个修改。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示Arc 与 Mutex 的共享状态的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-result": { | ||
| "title": "让解析错误穿过纯辅助函数", | ||
| "concept": "`try_fold` 让每次整数转换都可使用 `?`;首个 `ParseIntError` 会立即离开辅助函数,入口再决定如何把失败显示给用户。", | ||
| "worked_example": "`positive` 先用问号传播解析失败,再执行领域检查,说明传播与恢复是两项独立决定。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把Arc 与 Mutex 的共享状态的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决Arc 与 Mutex 的共享状态练习里的 TODO。", | ||
| "没有确认“Arc 与 Mutex 的共享状态 的 Rust 语法目标是:用 Arc<T> 共享所有权,并用 Mutex<T> 保证一次只允许一个修改。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "把 `?` 当成捕获操作,忽略它会从外围函数立即返回错误。", | ||
| "对每个片段强取,使入口无法把畸形输入转换成受控错误行。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用Arc 与 Mutex 的共享状态,然后才到达 `example:` 输出?", | ||
| "Arc 与 Mutex 的共享状态练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "包含问号的函数必须返回什么类型?", | ||
| "一次解析得到 `Err` 后,`try_fold` 还会访问后续片段吗?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把Arc 与 Mutex 的共享状态应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "通过 `Result` 与 `try_fold` 汇总全部合法整数,传播首次解析错误;入口匹配成功总和或稳定错误标记,不得在辅助函数内崩溃。", | ||
| "objective": "保留原错误路径,同时把可能失败的转换与面向用户的恢复分开。", | ||
| "language_delta": "Rust `Result` 在签名中显式表示可恢复失败,不使用可跨越无标记调用边界的异常。", | ||
| "prediction_prompt": "追踪先输入二再输入 bad 的累加器,并指出控制流返回的确切位置。", | ||
| "transfer_trap": "纯辅助函数可由可执行断言检查,异步函数也可让最终输出包含成功或失败;两者都不等于吞错。" | ||
| }, | ||
| "rust-async-await": { | ||
| "title": "async 与 await", | ||
| "concept": "async 与 await 的 Rust 语法目标是:理解 async 会创建 Future,而运行它需要 executor 或 runtime。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示async 与 await的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-testing": { | ||
| "title": "在评测实际执行路径保留检查", | ||
| "concept": "普通二进制会在读取输入前直接调用 `assert_eq!`,所以这些检查确实运行。带 `#[test]` 的函数只有通过 `cargo test` 或 `rustc --test` 建立测试框架后才执行。", | ||
| "worked_example": "三倍函数以四得到十二,先执行内联相等断言,再输出与答案无关的状态行,证明普通二进制无需测试框架也会运行检查。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把async 与 await的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决async 与 await练习里的 TODO。", | ||
| "没有确认“async 与 await 的 Rust 语法目标是:理解 async 会创建 Future,而运行它需要 executor 或 runtime。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "认为普通 `rustc` 二进制会自动运行 `#[test]`,其实测试框架从未建立。", | ||
| "用断言验证不可信输入,程序会崩溃而不是返回受控错误。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用async 与 await,然后才到达 `example:` 输出?", | ||
| "async 与 await练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "内联相等检查失败后,执行还能到达后面的 `println!` 吗?", | ||
| "哪些命令会启用测试框架,普通 `rustc` 又为什么不会?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把async 与 await应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "实现加二函数,保留两个在普通二进制中实际执行的边界断言,移除故意的编译阻断后再处理标准输入并输出函数结果。", | ||
| "objective": "在此练习评测使用的同一种执行模式中留下可运行正确性检查。", | ||
| "language_delta": "Rust 测试属性依赖编译模式;入口中始终调用的断言无需测试框架,`#[test]` 则需要专门生成并运行框架。", | ||
| "prediction_prompt": "若加二函数原样返回输入而两个内联检查仍在,预测进程会如何结束。", | ||
| "transfer_trap": "此练习只教授可执行断言与运行器边界,不能据此声称已掌握完整单元测试。" | ||
| }, | ||
| "rust-macros": { | ||
| "title": "macro_rules!", | ||
| "concept": "macro_rules! 的 Rust 语法目标是:用 macro_rules! 匹配 token 模式,并在编译期展开重复的 Rust 代码。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示macro_rules!的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-async-await": { | ||
| "title": "用有意受限的执行器运行等待", | ||
| "concept": "加倍函数等待 `std::future::ready`,小型执行器只轮询所得 `Future` 一次。若返回 `Pending` 就立即报错,因为它没有唤醒后重新轮询的循环。", | ||
| "worked_example": "消息函数包含真实 `.await`,并由同一单轮询机制驱动;程序观察到执行,而不是只创建后丢弃 `Future`。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把macro_rules!的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决macro_rules!练习里的 TODO。", | ||
| "没有确认“macro_rules!”这个要点,只去凑 judge 输出。" | ||
| "只调用异步函数却不轮询,其函数体完全不会执行。", | ||
| "把单轮询辅助器用于计时器、输入输出或任何可能暂停的 `Future`。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用macro_rules!,然后才到达 `example:` 输出?", | ||
| "macro_rules!练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "异步调用时还是首次 `poll` 时,函数体才真正开始?", | ||
| "哪个明确限制让无操作唤醒器在此练习中可接受?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把macro_rules!应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "在立即就绪的 `Future` 内把解析数字变为两倍,并由单轮询执行器驱动到输出;不得把辅助器扩展到可能返回 `Pending` 的工作。", | ||
| "objective": "观察真实 `.await`,同时诚实保留执行器只能单次轮询的上限。", | ||
| "language_delta": "Rust 异步体在执行器轮询前保持惰性,不会像急切安排的任务抽象那样自动开始。", | ||
| "prediction_prompt": "预测立即就绪值会进入哪个 `Poll` 分支,并说明为何不需要第二次轮询。", | ||
| "transfer_trap": "工作可能返回 `Pending` 时应采用生产执行器,不能把教学辅助器偷偷发展成调度器。" | ||
| }, | ||
| "rust-unsafe": { | ||
| "title": "unsafe Rust", | ||
| "concept": "unsafe Rust 的 Rust 语法目标是:把编译器无法验证的操作放入 unsafe 块,同时让周围 API 尽量保持安全。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示unsafe Rust的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-concurrency": { | ||
| "title": "等待自有工作线程结果", | ||
| "concept": "解析的 `i64` 实现 `Copy`,所以 `move` 闭包取得自己的复制值,不会使调用方绑定失效。`join` 与工作线程同步并返回计算结果。", | ||
| "worked_example": "示例创建计算六乘七的线程句柄,并在输出 42 前连接,因此主程序不会在工作结果可用前退出。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把unsafe Rust的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决unsafe Rust练习里的 TODO。", | ||
| "没有确认“unsafe Rust 的 Rust 语法目标是:把编译器无法验证的操作放入 unsafe 块,同时让周围 API 尽量保持安全。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "丢弃 `JoinHandle` 只会分离线程,并不会自动等待。", | ||
| "把栈借用捕获进新线程,通常无法满足线程可能活得更久的 `'static` 要求。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用unsafe Rust,然后才到达 `example:` 输出?", | ||
| "unsafe Rust练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪个复制值跨过线程边界,调用方原整数为何仍可使用?", | ||
| "线程崩溃使 `join` 返回错误时,`expect` 会把它转成什么?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把unsafe Rust应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把解析后的 `Copy` 整数移动进一个工作线程,在闭包内计算两倍,随后 `join` 并输出返回值;不得依赖调度先后或进程退出。", | ||
| "objective": "显式协调一个线程结果,而不是依赖时间或关闭进程。", | ||
| "language_delta": "不同于惰性 `Future`,`spawn` 创建独立调度的操作系统线程,它可能在调用方继续前或后开始;`join` 才是同步点。", | ||
| "prediction_prompt": "不假设谁先取得处理器时间,只判断 `join` 最终保证返回哪个结果。", | ||
| "transfer_trap": "单个返回值无需共享修改;`Arc` 与 `Mutex` 应留给多工作线程综合题。" | ||
| }, | ||
| "rust-cargo-workspaces": { | ||
| "title": "Cargo 包与工作空间", | ||
| "concept": "Cargo 包与工作空间 的 Rust 语法目标是:理解何时需要超出单文件的 Cargo 包结构与 cargo check --workspace。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示Cargo 包与工作空间的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "rust-shared-state": { | ||
| "title": "综合:限制已连接线程的共享修改", | ||
| "concept": "入口拥有输入 `Vec`,为每个整数创建工作线程。闭包拥有该值和一份 `Arc`,先在锁外计算平方,再短暂锁定并让 `MutexGuard` 尽快离开作用域。", | ||
| "worked_example": "示例启动两个真实线程,消费全部句柄完成连接,然后才读取受保护总和,因此更新不会在最终读取后才到达。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把Cargo 包与工作空间的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决Cargo 包与工作空间练习里的 TODO。", | ||
| "没有确认“Cargo 包与工作空间 的 Rust 语法目标是:理解何时需要超出单文件的 Cargo 包结构与 cargo check --workspace。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。”这个要点,只去凑 judge 输出。" | ||
| "把 `lock` 当成立即字段读取;它可能阻塞,也可能因持锁线程崩溃返回中毒错误。", | ||
| "持有守卫做无关计算,延长争用;平方应在取得互斥锁前完成。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用Cargo 包与工作空间,然后才到达 `example:` 输出?", | ||
| "Cargo 包与工作空间练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "入口取得最终守卫前,所有 `JoinHandle` 是否都已消费?", | ||
| "此处如何同时用到迭代、`Arc` 所有权、线程移动、短锁作用域与中毒处理?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把Cargo 包与工作空间应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "为每个输入建立并连接一个线程,在线程内先计算平方,再把它加入共享 `Arc<Mutex<i64>>` 总和;空输入仍应确定地输出零。", | ||
| "objective": "在一个有界程序中整合所有权、迭代、线程、同步与确定输出。", | ||
| "language_delta": "`Arc` 管理共享生存期,`Mutex` 管理排他访问;`RefCell` 不是 `Sync`,无法保护并发共享修改。", | ||
| "prediction_prompt": "解释为何所有线程都连接后,调度次序无法改变最终平方和。", | ||
| "transfer_trap": "此处需要共享的 `Arc` 而非单所有者 `Box`;`RefCell<T>` 即使可随所有权移动,也不能被多线程并发共享,且互斥锁已使不安全代码多余。" | ||
| } | ||
| } | ||
| } |
@@ -82,2 +82,18 @@ { | ||
| "minLength": 45 | ||
| }, | ||
| "objective": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "language_delta": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "prediction_prompt": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| }, | ||
| "transfer_trap": { | ||
| "type": "string", | ||
| "minLength": 1 | ||
| } | ||
@@ -84,0 +100,0 @@ } |
@@ -7,422 +7,506 @@ { | ||
| "ts-output": { | ||
| "title": "Console and stdout", | ||
| "concept": "Console output is the judge-facing contract, so the lesson separates console.log's newline from byte-exact process.stdout.write output. The type annotation is not decoration here; it describes which values may cross this lesson's boundary.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Console and stdout to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Write exact stdout bytes", | ||
| "concept": "process.stdout.write emits only the string you supply, making separators and the trailing newline part of the program's contract. This first core task also marks the boundary between TypeScript checking and Node execution.", | ||
| "worked_example": "The demonstration keeps a typed name and point value, then writes a deliberately different `demo Mina=4` record. Its annotation disappears before Node runs, but its template characters remain observable bytes.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Console and stdout rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Console and stdout exercise.", | ||
| "Matching the judge output without checking this lesson idea: Console output is the judge-facing contract, so the lesson separates console.log's newline from byte-exact process.stdout.write output." | ||
| "Using console.log when the judge expects control over the final newline.", | ||
| "Joining name and score without a separator, which creates ambiguous output such as `Ada7`." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Console and stdout before the `example:` output?", | ||
| "Which TODO line in the Console and stdout exercise must derive the value from its own data instead of copying the demo?" | ||
| "What exact character follows the learner name in the demonstration?", | ||
| "Which part of the write call is responsible for the line ending?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Console and stdout to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Read the two stdin fields and format one colon-delimited line through process.stdout.write.", | ||
| "objective": "Produce `name:score` followed by exactly one newline for every supplied record.", | ||
| "language_delta": "Unlike `console.log`, `process.stdout.write` adds no spacing or newline; Node runs the stripped JavaScript and resolves module behavior independently of annotations.", | ||
| "prediction_prompt": "Before execution, spell out the bytes emitted for a Unicode name and a two-digit score.", | ||
| "transfer_trap": "A browser console displays values for inspection, whereas an online judge compares the program's stdout text." | ||
| }, | ||
| "ts-let-const": { | ||
| "title": "let and const", | ||
| "concept": "const marks bindings that should not be reassigned, while let makes the changing accumulator in a solution visible. Use the syntax to keep the runtime value and the intended TypeScript shape moving together.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying let and const to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-input": { | ||
| "title": "Tokenize Node standard input", | ||
| "concept": "Node exposes redirected stdin as file descriptor zero. Reading it once, trimming it, and guarding the empty string creates a reliable number-token pipeline for later lessons.", | ||
| "worked_example": "The sample works from a fixed `4 6` string so it cannot solve the exercise cases. Splitting, Number conversion, and a zero-initialized reduce turn those two demo tokens into ten.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the let and const rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the let and const exercise.", | ||
| "Matching the judge output without checking this lesson idea: const marks bindings that should not be reassigned, while let makes the changing accumulator in a solution visible." | ||
| "Calling split on an empty trimmed string and accidentally converting its sole empty token to zero.", | ||
| "Reading only the first line even though judge input may place values across several lines." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies let and const before the `example:` output?", | ||
| "Which TODO line in the let and const exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does the token array become empty when trimmed stdin has no characters?", | ||
| "How does the reduce initializer determine the total for blank input?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying let and const to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Replace the first-token shortcut with a sum over every whitespace-separated number.", | ||
| "objective": "Return the arithmetic total for same-line, multiline, and empty stdin.", | ||
| "language_delta": "readFileSync(0, \"utf8\") is a Node runtime API; TypeScript checks the declared shim but does not perform the read itself.", | ||
| "prediction_prompt": "Predict the token array and total for `-1` followed by a newline and `5 2`.", | ||
| "transfer_trap": "Repeated prompt-style reads from other languages do not match Node's simple whole-stream judge harness." | ||
| }, | ||
| "ts-primitives": { | ||
| "title": "Primitive types", | ||
| "concept": "string, number, and boolean annotations describe the scalar values that parsing and branching code expects. In a stdin/stdout solution, this prevents the common shortcut where the printed value no longer matches the typed data.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Primitive types to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "title": "Convert text into primitive values", | ||
| "concept": "A string annotation describes a value to the checker, while Number performs the runtime conversion. The pass decision must compare numeric score and threshold rather than their textual spellings.", | ||
| "worked_example": "The worked program declares one string, one number, and one boolean, then prints a demo record. Each runtime value already has the primitive shape claimed by its annotation.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Primitive types rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Primitive types exercise.", | ||
| "Matching the judge output without checking this lesson idea: string, number, and boolean annotations describe the scalar values that parsing and branching code expects." | ||
| "Annotating scoreText as number without actually converting the incoming string.", | ||
| "Relying on relational coercion, which hides where invalid numeric text enters the calculation." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Primitive types before the `example:` output?", | ||
| "Which TODO line in the Primitive types exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which expression in the exercise changes a token from string semantics to number semantics?", | ||
| "Why must equality at a zero threshold count as a passing score?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Primitive types to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Parse both numeric fields, compare them inclusively, and keep the name as text.", | ||
| "objective": "Emit the parsed score and the correct pass-or-retry classification for three boundary cases.", | ||
| "language_delta": "TypeScript 5.9 can reject inconsistent primitive uses, but annotations are erasable and cannot coerce stdin; const and template details live in optional labs.", | ||
| "prediction_prompt": "Decide the status of `Bo 0 0` before evaluating the comparison operator.", | ||
| "transfer_trap": "In languages with runtime type declarations, a declared numeric variable may trigger conversion rules; TypeScript declarations alone never do." | ||
| }, | ||
| "ts-strings-templates": { | ||
| "title": "Strings and templates", | ||
| "concept": "Template literals keep formatting next to the values, and string methods such as trim return a new string. The type annotation is not decoration here; it describes which values may cross this lesson's boundary.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Strings and templates to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-let-const": { | ||
| "title": "Rebind an accumulator, keep its label", | ||
| "concept": "Use const for the stable label binding and let for a total that changes. Choosing either keyword says whether the binding may be reassigned, not whether an object behind it is frozen.", | ||
| "worked_example": "The demonstration fixes `demo` as a label, starts a let total at five, and adds negative two. Its final value proves reassignment occurred without changing the label binding.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Strings and templates rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Strings and templates exercise.", | ||
| "Matching the judge output without checking this lesson idea: Template literals keep formatting next to the values, and string methods such as trim return a new string." | ||
| "Declaring an accumulator with const and then trying to assign a new numeric value.", | ||
| "Assuming a const object cannot have one of its writable properties changed." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Strings and templates before the `example:` output?", | ||
| "Which TODO line in the Strings and templates exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which of the two demo bindings receives a second assignment?", | ||
| "Would const prevent `settings.enabled = false` when settings refers to a mutable object?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Strings and templates to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Accumulate both parsed numbers through a reassignable total while leaving the label fixed.", | ||
| "objective": "Show that positive, negative, and zero additions preserve the requested label and arithmetic result.", | ||
| "language_delta": "JavaScript enforces const binding rules at runtime; the number annotations are separately erased checker information.", | ||
| "prediction_prompt": "Trace the total after each assignment for the `net -2 5` input.", | ||
| "transfer_trap": "Rust immutable bindings and Java final references have related names but different ownership and object-mutation consequences." | ||
| }, | ||
| "ts-arrays-tuples": { | ||
| "title": "Arrays and tuples", | ||
| "concept": "Arrays are ordered groups of one element type; tuples fix position and type when a tiny record matters. Use the syntax to keep the runtime value and the intended TypeScript shape moving together.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Arrays and tuples to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-strings-templates": { | ||
| "title": "Trim the name, preserve the score", | ||
| "concept": "The pipe separates two fields with different whitespace rules. Remove one terminal line ending, trim the name, and preserve every score character—including trailing spaces—before interpolation.", | ||
| "worked_example": "A fixed `Mira` record ends with two score spaces plus a newline. The regular expression removes only that newline, so the demonstration keeps both spaces beside the interpolated score.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Arrays and tuples rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Arrays and tuples exercise.", | ||
| "Matching the judge output without checking this lesson idea: Arrays are ordered groups of one element type; tuples fix position and type when a tiny record matters." | ||
| "Calling trimEnd on the whole record and silently deleting meaningful score padding.", | ||
| "Using string.length as a count of user-perceived characters when surrogate pairs may occupy two UTF-16 units." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Arrays and tuples before the `example:` output?", | ||
| "Which TODO line in the Arrays and tuples exercise must derive the value from its own data instead of copying the demo?" | ||
| "After stripping the terminal newline from ` Mira |9 `, which spaces remain in each field?", | ||
| "Why does trimming rawName leave the score's two trailing spaces untouched?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Arrays and tuples to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Strip one terminal CRLF or LF, trim only the name side, and interpolate the unchanged score after a colon.", | ||
| "objective": "Format all three records while retaining significant spaces at the end of the score field.", | ||
| "language_delta": "JavaScript trimEnd removes all trailing whitespace, whereas the anchored replacement here removes one record terminator; string indexes still count UTF-16 code units.", | ||
| "prediction_prompt": "Mark the exact spaces remaining after `/\\r?\\n$/` processes ` Ada |7 ` followed by LF.", | ||
| "transfer_trap": "A general strip or trim operation from another language can erase payload whitespace when only the transport newline should disappear." | ||
| }, | ||
| "ts-objects": { | ||
| "title": "Object types", | ||
| "concept": "Object types name the required fields so a calculation cannot accidentally ignore width, height, or another property. In a stdin/stdout solution, this prevents the common shortcut where the printed value no longer matches the typed data.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Object types to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-arrays-tuples": { | ||
| "title": "Return a named total as a tuple", | ||
| "concept": "The first token names the record and the remaining tokens form a number array. A `[string, number]` tuple then records the stable meaning of its two positions.", | ||
| "worked_example": "The example totals a separate `[1, 4]` array and stores `Neo` plus that sum in a typed pair. Accessing positions zero and one shows that the tuple is still an Array at runtime.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Object types rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Object types exercise.", | ||
| "Matching the judge output without checking this lesson idea: Object types name the required fields so a calculation cannot accidentally ignore width, height, or another property." | ||
| "Treating every token as numeric and losing the leading name before reduction.", | ||
| "Expecting tuple annotations to create a different runtime container from Array." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Object types before the `example:` output?", | ||
| "Which TODO line in the Object types exercise must derive the value from its own data instead of copying the demo?" | ||
| "What type does the second position of the demo pair accept?", | ||
| "How should the numeric tail behave when the input contains only `Bo`?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Object types to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Reduce the complete numeric tail and store the name and total in the declared tuple.", | ||
| "objective": "Handle multiple values, negative values, and an empty numeric tail through one pair shape.", | ||
| "language_delta": "Tuple position checks disappear during Node execution; iterable and array-method labs explore alternative ways to consume the same sequence.", | ||
| "prediction_prompt": "For `Lin -1 4 2`, list the array contents before calculating the tuple's second slot.", | ||
| "transfer_trap": "A tuple is not a small immutable record at runtime, unlike value-oriented constructs some other languages provide." | ||
| }, | ||
| "ts-functions": { | ||
| "title": "Functions", | ||
| "concept": "Function parameter and return annotations make the boundary between parsed input, calculation, and output explicit. The type annotation is not decoration here; it describes which values may cross this lesson's boundary.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Functions to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-iterables": { | ||
| "title": "Consume an Iterable with for-of", | ||
| "concept": "Iterable<string> promises a sequence protocol, not array indexes. for...of asks that protocol for values and therefore works for arrays, sets, strings, and custom iterables.", | ||
| "worked_example": "The demonstration passes a Set containing `T` and `S` to a collector. Iteration produces the two insertion-ordered values without calling an array-only method.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Functions rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Functions exercise.", | ||
| "Matching the judge output without checking this lesson idea: Function parameter and return annotations make the boundary between parsed input, calculation, and output explicit." | ||
| "Indexing an Iterable even though the interface does not guarantee numeric properties.", | ||
| "Reusing the same iterator after it has already advanced to completion." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Functions before the `example:` output?", | ||
| "Which TODO line in the Functions exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which capability does for...of require from its input?", | ||
| "Why does an empty token list still produce a newline rather than no stdout at all?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Functions to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Traverse all stdin tokens through iteration and concatenate them in encounter order.", | ||
| "objective": "Join two, three, or zero tokens without depending on array indexing.", | ||
| "language_delta": "TypeScript's Iterable type describes the JavaScript iteration protocol; it adds no collection or replay behavior at runtime.", | ||
| "prediction_prompt": "Walk the iterator states for `a b c` and predict the accumulator after each step.", | ||
| "transfer_trap": "Python iterators and Java streams are also consumable, but their exact replay and ownership conventions are not interchangeable." | ||
| }, | ||
| "ts-input": { | ||
| "title": "Node stdin parsing", | ||
| "concept": "Node solutions usually read file descriptor 0 once, split whitespace, and convert tokens before numeric work. Use the syntax to keep the runtime value and the intended TypeScript shape moving together.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Node stdin parsing to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-array-methods": { | ||
| "title": "Filter, square, and reduce safely", | ||
| "concept": "A readable sequence pipeline first selects even numbers, then squares them, then folds the products. Giving reduce an initial zero defines the empty-selection result.", | ||
| "worked_example": "The demo filters `[2, 5, 6]` by a different condition and doubles its survivors before summing. That distinct pipeline illustrates method order without revealing the exercise result.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Node stdin parsing rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Node stdin parsing exercise.", | ||
| "Matching the judge output without checking this lesson idea: Node solutions usually read file descriptor 0 once, split whitespace, and convert tokens before numeric work." | ||
| "Mapping before filtering when the predicate is supposed to inspect original values.", | ||
| "Omitting reduce's initializer and causing an exception when no even item survives." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Node stdin parsing before the `example:` output?", | ||
| "Which TODO line in the Node stdin parsing exercise must derive the value from its own data instead of copying the demo?" | ||
| "At what stage does negative two become positive four?", | ||
| "What value enters reduce first when the filtered array is empty?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Node stdin parsing to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Correct the parity predicate while retaining the square and zero-initialized fold.", | ||
| "objective": "Compute the even-square total for mixed, negative, and all-odd token sets.", | ||
| "language_delta": "These methods are standard JavaScript Array operations; TypeScript infers callback types but does not make the pipeline lazy.", | ||
| "prediction_prompt": "Write the intermediate arrays produced from `-2 -1 0` before adding them.", | ||
| "transfer_trap": "Rust iterator adapters are lazy until consumed, unlike JavaScript array methods that allocate or traverse immediately at each stage." | ||
| }, | ||
| "ts-control-flow": { | ||
| "title": "Control flow", | ||
| "concept": "if and loops decide which values are accumulated, so branch conditions must match the intended data rule. In a stdin/stdout solution, this prevents the common shortcut where the printed value no longer matches the typed data.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Control flow to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-objects": { | ||
| "title": "Calculate through a structural object", | ||
| "concept": "A Rectangle type requires width and height members, and the area function accepts any compatible object. No class declaration or shared nominal identity is needed for this calculation.", | ||
| "worked_example": "The worked object also has a color property, yet a separately bound value can enter the two-member `DemoSize` function. The extra field remains present at runtime and is simply unused.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Control flow rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Control flow exercise.", | ||
| "Matching the judge output without checking this lesson idea: if and loops decide which values are accumulated, so branch conditions must match the intended data rule." | ||
| "Leaving one parsed dimension out of the object and silently substituting a placeholder.", | ||
| "Assuming two values need the same constructor name before TypeScript considers their shapes compatible." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Control flow before the `example:` output?", | ||
| "Which TODO line in the Control flow exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which members of `painted` does demoArea actually read?", | ||
| "Why does a zero width correctly force the computed area to zero?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Control flow to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Construct a Rectangle from both stdin dimensions and pass it through the typed area function.", | ||
| "objective": "Calculate products for ordinary, zero-width, and one-height rectangles using structural data.", | ||
| "language_delta": "Object types are checker descriptions erased before Node runs; class methods create runtime behavior and readonly only restricts typed access shallowly.", | ||
| "prediction_prompt": "Predict the object value and multiplication result for dimensions `7 1`.", | ||
| "transfer_trap": "Java commonly emphasizes declared class identity, while TypeScript usually accepts independently created values with compatible members." | ||
| }, | ||
| "ts-union-narrowing": { | ||
| "title": "Union and narrowing", | ||
| "concept": "Union values need a runtime check before string-only or number-only operations become safe. The type annotation is not decoration here; it describes which values may cross this lesson's boundary.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Union and narrowing to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-classes": { | ||
| "title": "Update state behind a class method", | ||
| "concept": "A Counter instance owns mutable state and exposes an increment operation. The method must change that stored value before returning it, so later calls observe the update.", | ||
| "worked_example": "The sample class begins at eight and increments once to nine. Its private modifier helps the checker police source access while the emitted runtime field remains an ordinary property.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Union and narrowing rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Union and narrowing exercise.", | ||
| "Matching the judge output without checking this lesson idea: Union values need a runtime check before string-only or number-only operations become safe." | ||
| "Returning `this.value + 1` without storing the new state for subsequent calls.", | ||
| "Treating TypeScript private as the same runtime mechanism as a JavaScript `#value` field." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Union and narrowing before the `example:` output?", | ||
| "Which TODO line in the Union and narrowing exercise must derive the value from its own data instead of copying the demo?" | ||
| "After two calls on one DemoCounter instance, what value should its method return?", | ||
| "Which class privacy form survives into JavaScript as an enforced access restriction?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Union and narrowing to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Modify increment so it updates the instance field and returns the resulting counter value.", | ||
| "objective": "Advance positive, negative, and larger starting values by exactly one.", | ||
| "language_delta": "The `private` keyword here is erased checker metadata; ECMAScript hash-prefixed fields have runtime privacy semantics.", | ||
| "prediction_prompt": "Trace the stored field before and after a call starting from negative one.", | ||
| "transfer_trap": "Java private access is enforced by the JVM, so its runtime boundary is stronger than TypeScript's erased modifier." | ||
| }, | ||
| "ts-literal-types": { | ||
| "title": "Literal types", | ||
| "concept": "Literal unions restrict commands and modes to exact values such as 'left' or 'right'. Use the syntax to keep the runtime value and the intended TypeScript shape moving together.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Literal types to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-readonly": { | ||
| "title": "Read a shallow readonly configuration", | ||
| "concept": "A readonly Config lets consumers inspect the name and score sequence without mutating through that type. Marking the nested array readonly is necessary because property readonly alone only blocks replacement.", | ||
| "worked_example": "The demo declares a readonly property and a readonly number array, then reads the array length. It never attempts a write, so strict checking and stripped Node execution both succeed.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Literal types rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Literal types exercise.", | ||
| "Matching the judge output without checking this lesson idea: Literal unions restrict commands and modes to exact values such as 'left' or 'right'." | ||
| "Calling push on a readonly number array and expecting strict TypeScript to permit it.", | ||
| "Believing readonly invokes Object.freeze or recursively locks nested runtime objects." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Literal types before the `example:` output?", | ||
| "Which TODO line in the Literal types exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does the starter fail before any test input reaches Node?", | ||
| "Would another mutable alias to the same underlying array still be able to push?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Literal types to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Remove the forbidden mutation and calculate the score total from the readonly view.", | ||
| "objective": "Type-check the solution strictly, then report each configured name with its summed scores.", | ||
| "language_delta": "readonly is shallow and type-only unless nested members also receive readonly types; neither annotation freezes JavaScript memory.", | ||
| "prediction_prompt": "Predict the compiler diagnostic at `config.scores.push(0)` rather than predicting stdout.", | ||
| "transfer_trap": "A Rust immutable borrow prevents mutation through that borrow under ownership rules, which is not equivalent to an erasable TypeScript view." | ||
| }, | ||
| "ts-optional-nullish": { | ||
| "title": "Optional and nullish", | ||
| "concept": "Optional fields can be undefined, and ?? preserves valid falsey data such as score 0. In a stdin/stdout solution, this prevents the common shortcut where the printed value no longer matches the typed data.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Optional and nullish to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-control-flow": { | ||
| "title": "Keep an inclusive loop boundary", | ||
| "concept": "The loop visits integers from one through n and a strict modulo comparison selects odd values. Whether the endpoint participates is encoded by the relational operator in the loop condition.", | ||
| "worked_example": "The sample walks through five but accumulates even values, yielding a deliberately unrelated demo total. Its concise branch still uses strict equality and a closed upper bound.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Optional and nullish rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Optional and nullish exercise.", | ||
| "Matching the judge output without checking this lesson idea: Optional fields can be undefined, and ??" | ||
| "Using `< n` and dropping an odd endpoint that belongs to the requested range.", | ||
| "Writing loose equality where JavaScript coercion could make unrelated values compare equal." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Optional and nullish before the `example:` output?", | ||
| "Which TODO line in the Optional and nullish exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which loop value contributes last when n equals three?", | ||
| "Why does the six case pass even in the flawed starter, and which case exposes the bound bug?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Optional and nullish to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Repair the loop boundary while preserving the odd-number branch and accumulator.", | ||
| "objective": "Sum every odd integer in the inclusive range for positive and zero limits.", | ||
| "language_delta": "JavaScript conditions use runtime booleans and coercion rules; TypeScript control-flow analysis separately narrows values along proven branches.", | ||
| "prediction_prompt": "Enumerate visited values for n equal to three under `<` and under `<=`.", | ||
| "transfer_trap": "Python range excludes its stop by default, so translating that familiar loop literally can omit the TypeScript endpoint." | ||
| }, | ||
| "ts-interfaces-aliases": { | ||
| "title": "Interfaces and type aliases", | ||
| "concept": "Interfaces name object contracts, while type aliases also name unions, tuples, and composed type expressions. The type annotation is not decoration here; it describes which values may cross this lesson's boundary.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Interfaces and type aliases to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-functions": { | ||
| "title": "Checkpoint: typed calculation boundary", | ||
| "concept": "A function signature promises both accepted input and returned output. Strict checking should reject the starter because a numeric multiplication cannot fulfill its declared string result.", | ||
| "worked_example": "The demonstration defines a number-returning perimeter function over two numeric parameters. Its fixed dimensions produce a demo value unrelated to any judged area case.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Interfaces and type aliases rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Interfaces and type aliases exercise.", | ||
| "Matching the judge output without checking this lesson idea: Interfaces name object contracts, while type aliases also name unions, tuples, and composed type expressions." | ||
| "Changing the multiplication to a string merely to satisfy an incorrect return annotation.", | ||
| "Printing inside the calculation function and losing the distinction between returning data and writing stdout." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Interfaces and type aliases before the `example:` output?", | ||
| "Which TODO line in the Interfaces and type aliases exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which line establishes the promised return type of area?", | ||
| "At what point do the two parsed tokens become numeric arguments to multiplication?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Interfaces and type aliases to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Correct the function's return contract so strict checking accepts the numeric calculation.", | ||
| "objective": "Pass the checker and compute three rectangle areas through one annotated function.", | ||
| "language_delta": "TypeScript validates parameter and result uses before execution; loop and branch behavior inside the function is still ordinary JavaScript control flow.", | ||
| "prediction_prompt": "Predict the TypeCheck failure category before considering any of the case outputs.", | ||
| "transfer_trap": "Languages with runtime method signatures may enforce a return contract during execution or compilation; TypeScript erases it after checking." | ||
| }, | ||
| "ts-generics": { | ||
| "title": "Generics", | ||
| "concept": "Generics keep the caller's element type when reusable code reads from an array or returns a value. Use the syntax to keep the runtime value and the intended TypeScript shape moving together.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Generics to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-union-narrowing": { | ||
| "title": "Narrow a union before formatting", | ||
| "concept": "A value of type string | number exposes only operations safe for both members. A typeof branch proves which runtime primitive is present and unlocks its specific formatting method.", | ||
| "worked_example": "The demo lowercases strings but formats a fixed number to one decimal place. It demonstrates both possible branches without matching the exercise's uppercase or integer formatting.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Generics rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Generics exercise.", | ||
| "Matching the judge output without checking this lesson idea: Generics keep the caller's element type when reusable code reads from an array or returns a value." | ||
| "Calling toUpperCase directly on a union that may contain a number.", | ||
| "Using the input's kind token as a cast instead of constructing and narrowing the actual runtime value." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Generics before the `example:` output?", | ||
| "Which TODO line in the Generics exercise must derive the value from its own data instead of copying the demo?" | ||
| "What does typeof report for the numeric member created by Number(text)?", | ||
| "Why does toFixed(0) turn negative 1.6 into negative two?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Generics to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Introduce a runtime narrowing branch and apply the appropriate formatter to each union member.", | ||
| "objective": "Uppercase string commands and round numeric commands while satisfying strict type checking.", | ||
| "language_delta": "Narrowing combines checker reasoning with a JavaScript runtime test; arbitrary stdin still needs validation before entering a literal union.", | ||
| "prediction_prompt": "Choose the branch and formatted text for `n -1.6` before running the file.", | ||
| "transfer_trap": "Python permits method calls until runtime failure, while TypeScript rejects an unsafe union operation during the checker phase." | ||
| }, | ||
| "ts-keyof-typeof": { | ||
| "title": "keyof and typeof", | ||
| "concept": "typeof captures a value's static shape, and keyof turns that shape into the allowed key union. In a stdin/stdout solution, this prevents the common shortcut where the printed value no longer matches the typed data.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying keyof and typeof to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-literal-types": { | ||
| "title": "Validate text before a literal union", | ||
| "concept": "Direction contains exactly two string literals. Since stdin begins as an unrestricted string, a runtime comparison must establish membership before the value can safely enter turn.", | ||
| "worked_example": "The demonstration uses a different north-or-south union and passes a known literal directly. Its branch produces a labeled demo arrow without validating external data.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the keyof and typeof rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the keyof and typeof exercise.", | ||
| "Matching the judge output without checking this lesson idea: typeof captures a value's static shape, and keyof turns that shape into the allowed key union." | ||
| "Casting arbitrary text to Direction and assuming the assertion performed a runtime check.", | ||
| "Letting the binary turn function treat every unknown direction as the right-hand case." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies keyof and typeof before the `example:` output?", | ||
| "Which TODO line in the keyof and typeof exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does `raw as Direction` allow `up` to reach turn at runtime?", | ||
| "After checking both accepted strings, what narrowed type does raw have inside the true branch?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying keyof and typeof to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Guard the stdin string against both Direction members and report invalid text separately.", | ||
| "objective": "Map left and right to their short forms while rejecting an unrecognized command.", | ||
| "language_delta": "Literal unions constrain checked source, but JavaScript receives ordinary strings after erasure and therefore needs an explicit membership test.", | ||
| "prediction_prompt": "Predict the flawed starter's branch for `up`, then compare it with the required rejection path.", | ||
| "transfer_trap": "A closed enum in another language may validate construction automatically; a TypeScript string union creates no runtime container." | ||
| }, | ||
| "ts-indexed-access": { | ||
| "title": "Indexed access types", | ||
| "concept": "Indexed access types reuse property and element types from an existing model instead of repeating them by hand. The type annotation is not decoration here; it describes which values may cross this lesson's boundary.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Indexed access types to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-optional-nullish": { | ||
| "title": "Default only nullish scores", | ||
| "concept": "The parsed score is either a number or null. Nullish coalescing supplies ten only for null or undefined, preserving zero as an intentional numeric result.", | ||
| "worked_example": "The demo gives an optional count the value zero and applies `?? 8`. Printing zero demonstrates the precise absence rule without sharing exercise names or fallback values.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Indexed access types rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Indexed access types exercise.", | ||
| "Matching the judge output without checking this lesson idea: Indexed access types reuse property and element types from an existing model instead of repeating them by hand." | ||
| "Using logical OR and replacing a legitimate zero because zero is falsy.", | ||
| "Leaving the text `none` unparsed and mixing string absence markers with numeric calculations." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Indexed access types before the `example:` output?", | ||
| "Which TODO line in the Indexed access types exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which values trigger the right operand of `??`?", | ||
| "What runtime value is assigned when scoreText contains the word none?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Indexed access types to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Switch to nullish defaulting so an explicit zero survives and only absence receives ten.", | ||
| "objective": "Distinguish zero, null, and a positive score in the emitted name-score records.", | ||
| "language_delta": "With strictNullChecks, TypeScript makes null explicit in the union; JavaScript's `??` operator supplies the actual runtime semantics.", | ||
| "prediction_prompt": "Evaluate both sides of the default expression for `Ada 0` before execution.", | ||
| "transfer_trap": "Python's `or` and JavaScript's `||` share broad falsy behavior, so neither is a direct substitute for nullish coalescing." | ||
| }, | ||
| "ts-mapped-types": { | ||
| "title": "Mapped types", | ||
| "concept": "Mapped types transform every key in an object type, which keeps derived shapes aligned with the source. Use the syntax to keep the runtime value and the intended TypeScript shape moving together.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Mapped types to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-interfaces-aliases": { | ||
| "title": "Compose structural contracts", | ||
| "concept": "Named is an interface, Scored is a type alias, and their intersection requires both fields. The resulting Entry is accepted because of its members, not a constructor lineage.", | ||
| "worked_example": "A separate demo intersects Tagged with Weighted and builds one compatible object. Reading its tag and weight illustrates composition without duplicating the judged model.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Mapped types rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Mapped types exercise.", | ||
| "Matching the judge output without checking this lesson idea: Mapped types transform every key in an object type, which keeps derived shapes aligned with the source." | ||
| "Adding one to the parsed score while constructing the intersection and corrupting source data.", | ||
| "Assuming an interface and an equivalent object alias create different runtime kinds." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Mapped types before the `example:` output?", | ||
| "Which TODO line in the Mapped types exercise must derive the value from its own data instead of copying the demo?" | ||
| "Which two properties are required before a value satisfies Entry?", | ||
| "Can a type alias name a union even though an interface cannot directly do so?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Mapped types to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Construct the intersection from the unchanged parsed name and numeric score.", | ||
| "objective": "Preserve positive, zero, and negative scores while constructing and printing one structurally typed object.", | ||
| "language_delta": "Interfaces can declaration-merge while aliases can express unions; both disappear at runtime, and related labs add class behavior, readonly views, or satisfies checks.", | ||
| "prediction_prompt": "Write the Entry object created from `Bo -2` before interpolating its fields.", | ||
| "transfer_trap": "Java interfaces participate in nominal declarations and JVM metadata, unlike TypeScript's erased structural interface." | ||
| }, | ||
| "ts-conditional-types": { | ||
| "title": "Conditional types", | ||
| "concept": "Conditional types choose a branch from a type relationship, and infer extracts the part that matched. In a stdin/stdout solution, this prevents the common shortcut where the printed value no longer matches the typed data.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Conditional types to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-generics": { | ||
| "title": "Preserve an element type through first", | ||
| "concept": "first<T> keeps the caller's element type and returns undefined when no element exists. The implementation needs neither any nor a non-null assertion because its return union models emptiness.", | ||
| "worked_example": "The sample defines a generic last function and calls it with three strings. Its result demonstrates inference of string | undefined while deliberately selecting the opposite end.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Conditional types rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Conditional types exercise.", | ||
| "Matching the judge output without checking this lesson idea: Conditional types choose a branch from a type relationship, and infer extracts the part that matched." | ||
| "Returning index one and confusing the second element with the leading element.", | ||
| "Forcing a value with `!` even though an empty readonly array is a legitimate input." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Conditional types before the `example:` output?", | ||
| "Which TODO line in the Conditional types exercise must derive the value from its own data instead of copying the demo?" | ||
| "What does T become when first receives a string array?", | ||
| "Why is undefined part of the declared result even when some cases are nonempty?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Conditional types to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Return index zero from the generic helper and retain the explicit empty fallback at the caller.", | ||
| "objective": "Select the first token for two nonempty inputs and `none` for an empty stream.", | ||
| "language_delta": "Generics preserve relationships during checking and are erased for Node; keyof, indexed access, mapped, conditional, and utility types build further derivations from source types.", | ||
| "prediction_prompt": "Infer the return type and runtime value for a one-element token array.", | ||
| "transfer_trap": "Java generics use erasure with different variance rules, while Rust monomorphizes many generic uses; identical angle brackets do not imply identical machinery." | ||
| }, | ||
| "ts-utility-types": { | ||
| "title": "Utility types", | ||
| "concept": "Utility types such as Pick and Partial express common object transformations without inventing a new helper type. The type annotation is not decoration here; it describes which values may cross this lesson's boundary.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Utility types to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-keyof-typeof": { | ||
| "title": "Guard a runtime key for keyof", | ||
| "concept": "typeof derives the static shape of limits and keyof derives its allowed property names. A predicate backed by Object.hasOwn accepts only properties stored directly on that object.", | ||
| "worked_example": "The example derives `tiny | huge`, treats `huge` as external text, and narrows it through an own-key predicate before indexing the demo limits.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Utility types rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Utility types exercise.", | ||
| "Matching the judge output without checking this lesson idea: Utility types such as Pick and Partial express common object transformations without inventing a new helper type." | ||
| "Using `value in limits`, which also accepts inherited prototype names such as toString.", | ||
| "Confusing JavaScript's value-level typeof operator with TypeScript's type-query position." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Utility types before the `example:` output?", | ||
| "Which TODO line in the Utility types exercise must derive the value from its own data instead of copying the demo?" | ||
| "What union is produced by `keyof typeof limits`?", | ||
| "Why does Object.hasOwn reject constructor even though the in operator sees it?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Utility types to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Add an own-property type guard before indexing limits and choose the invalid result outside that branch.", | ||
| "objective": "Resolve both declared sizes while rejecting absent and prototype-inherited property names.", | ||
| "language_delta": "keyof has no Node runtime representation; Object.hasOwn supplies JavaScript evidence that the string denotes a direct property rather than a prototype member.", | ||
| "prediction_prompt": "Compare `Object.hasOwn(limits, \"toString\")` with `\"toString\" in limits` before choosing the branch.", | ||
| "transfer_trap": "Some dictionary membership operators traverse inherited state; an own-key requirement must exclude the prototype chain explicitly." | ||
| }, | ||
| "ts-discriminated-unions": { | ||
| "title": "Discriminated unions", | ||
| "concept": "A shared literal tag lets switch narrow each variant so rectangle fields are used only on rectangles. Use the syntax to keep the runtime value and the intended TypeScript shape moving together.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Discriminated unions to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-indexed-access": { | ||
| "title": "Reuse an array element type", | ||
| "concept": "User[\"scores\"][number] extracts the element type already declared by the model. Reusing it avoids a second handwritten score type that could drift later.", | ||
| "worked_example": "The worked model derives a tag element from a string array and assigns the fixed word `typed`. Both indexed selections exist only during checking.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Discriminated unions rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Discriminated unions exercise.", | ||
| "Matching the judge output without checking this lesson idea: A shared literal tag lets switch narrow each variant so rectangle fields are used only on rectangles." | ||
| "Assigning raw stdin text to a derived number type without runtime conversion.", | ||
| "Repeating `number` manually everywhere and losing the connection to the source model." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Discriminated unions before the `example:` output?", | ||
| "Which TODO line in the Discriminated unions exercise must derive the value from its own data instead of copying the demo?" | ||
| "What type results after first selecting scores and then indexing it with number?", | ||
| "Which phase reports the starter's string-to-number mismatch?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Discriminated unions to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Convert stdin before assigning it to the indexed-access score type.", | ||
| "objective": "Pass strict checking and echo positive, zero, and negative numeric values.", | ||
| "language_delta": "Indexed access computes a static type and emits no lookup code; Number is still required to cross the JavaScript input boundary.", | ||
| "prediction_prompt": "Classify the starter failure as TypeCheck before considering any stdout case.", | ||
| "transfer_trap": "Schema-derived types in other ecosystems may generate runtime validators too, but this TypeScript operator supplies checking only." | ||
| }, | ||
| "ts-async-promise": { | ||
| "title": "Async and Promise", | ||
| "concept": "async functions return Promise values, and await is the point where the fulfilled number re-enters normal flow. In a stdin/stdout solution, this prevents the common shortcut where the printed value no longer matches the typed data.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Async and Promise to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-mapped-types": { | ||
| "title": "Map every feature key to a flag", | ||
| "concept": "Flags iterates over the Feature union and requires one boolean property for each member. That completeness prevents a new feature name from silently lacking configuration.", | ||
| "worked_example": "The demo maps `dark | cache` to two booleans and reports the enabled demo key. Both required keys appear even though only one is true.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Async and Promise rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Async and Promise exercise.", | ||
| "Matching the judge output without checking this lesson idea: async functions return Promise values, and await is the point where the fulfilled number re-enters normal flow." | ||
| "Omitting share and expecting the mapped type to treat it as optional.", | ||
| "Printing a generic success word when the cases require the actual enabled feature name." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Async and Promise before the `example:` output?", | ||
| "Which TODO line in the Async and Promise exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does the incomplete starter fail before Node reads stdin?", | ||
| "What should both booleans be when the raw command is `none`?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Async and Promise to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Supply the missing mapped property and derive search, share, or none from both flags.", | ||
| "objective": "Make the checker enforce complete feature coverage while producing all three feasible runtime results.", | ||
| "language_delta": "Mapped types generate object requirements for the checker; they do not allocate properties or iterate keys during Node execution.", | ||
| "prediction_prompt": "Predict the TypeCheck diagnostic for the object containing only search.", | ||
| "transfer_trap": "A runtime map or dictionary is a data structure, whereas a TypeScript mapped type is a compile-time transformation." | ||
| }, | ||
| "ts-error-handling": { | ||
| "title": "Error handling", | ||
| "concept": "catch receives an unknown failure, so code should narrow it before choosing a recovery value. The type annotation is not decoration here; it describes which values may cross this lesson's boundary.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Error handling to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-conditional-types": { | ||
| "title": "Extract an element with a conditional type", | ||
| "concept": "ElementType tests whether T is an array-like type and uses infer to name its member. For string[] the selected checker branch is string.", | ||
| "worked_example": "The demonstration instantiates the same pattern with number[] and assigns eleven. No runtime condition inspects that value; the type selection is already erased.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Error handling rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Error handling exercise.", | ||
| "Matching the judge output without checking this lesson idea: catch receives an unknown failure, so code should narrow it before choosing a recovery value." | ||
| "Assigning Number(raw) after the conditional type has resolved to string.", | ||
| "Expecting `T extends ...` to choose a branch based on the contents of stdin." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Error handling before the `example:` output?", | ||
| "Which TODO line in the Error handling exercise must derive the value from its own data instead of copying the demo?" | ||
| "What type does Item represent when T is readonly string[]?", | ||
| "Can Node observe which conditional-type branch the checker selected?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Error handling to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Assign the incoming text to the extracted string element type without numeric conversion.", | ||
| "objective": "Type-check and reproduce three different text inputs, including characters that look numeric.", | ||
| "language_delta": "Conditional types run in TypeScript's type system only; JavaScript conditional expressions are separate emitted runtime syntax.", | ||
| "prediction_prompt": "Decide whether the text `42` changes the static TextElement type before running it.", | ||
| "transfer_trap": "Compile-time template or trait mechanisms in other languages may generate executable code, unlike this fully erased selection." | ||
| }, | ||
| "ts-modules": { | ||
| "title": "Modules and exports", | ||
| "concept": "export marks the values a file offers to other files; Node still decides the module system by its own rules. Use the syntax to keep the runtime value and the intended TypeScript shape moving together.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Modules and exports to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-utility-types": { | ||
| "title": "Patch selected fields with utility types", | ||
| "concept": "Pick selects score from User and Partial makes that selected property optional. An empty patch represents no update; it does not manufacture a score value.", | ||
| "worked_example": "The demonstration builds an optional active-only patch for another profile model. Reading with a boolean fallback shows how the runtime object may or may not contain the property.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Modules and exports rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Modules and exports exercise.", | ||
| "Matching the judge output without checking this lesson idea: export marks the values a file offers to other files; Node still decides the module system by its own rules." | ||
| "Inserting score one when the token is absent instead of leaving the patch empty.", | ||
| "Assuming Partial recursively changes nested properties or fills them with defaults." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Modules and exports before the `example:` output?", | ||
| "Which TODO line in the Modules and exports exercise must derive the value from its own data instead of copying the demo?" | ||
| "What object should represent the Lin input when no score token exists?", | ||
| "Which original User field is excluded by the Pick portion of ScorePatch?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Modules and exports to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Create either an empty score patch or a converted numeric score based on token presence.", | ||
| "objective": "Report supplied positive and negative scores while defaulting the genuinely missing property to zero.", | ||
| "language_delta": "Utility types rewrite static object shapes and emit nothing; ordinary JavaScript object construction determines which keys exist at runtime.", | ||
| "prediction_prompt": "Sketch the patch object for each of `Ada 5` and `Lin` before coalescing its score.", | ||
| "transfer_trap": "Optional fields in serialized data still need runtime parsing and validation even when a TypeScript Partial describes their source shape." | ||
| }, | ||
| "ts-classes": { | ||
| "title": "Classes and access modifiers", | ||
| "concept": "Classes attach methods to state, and private documents which state should stay behind the method boundary. In a stdin/stdout solution, this prevents the common shortcut where the printed value no longer matches the typed data.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Classes and access modifiers to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-satisfies-as-const": { | ||
| "title": "Check routes while preserving literals", | ||
| "concept": "as const retains exact route strings and satisfies verifies the Record contract without replacing those literals. Object.hasOwn separately proves that external text names a direct route property.", | ||
| "worked_example": "The sample checks two status codes, narrows the text `ready` with an own-key predicate, and then reads the preserved `R` literal. The object remains normal JavaScript data after stripping.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Classes and access modifiers rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Classes and access modifiers exercise.", | ||
| "Matching the judge output without checking this lesson idea: Classes attach methods to state, and private documents which state should stay behind the method boundary." | ||
| "Using the in operator and accidentally accepting inherited names such as constructor as routes.", | ||
| "Believing as const freezes the route object or recursively prevents runtime mutation." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Classes and access modifiers before the `example:` output?", | ||
| "Which TODO line in the Classes and access modifiers exercise must derive the value from its own data instead of copying the demo?" | ||
| "How does satisfies differ from an `as Record<string, string>` assertion here?", | ||
| "Why does Object.hasOwn establish a stronger route condition than the in operator?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Classes and access modifiers to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Guard the requested route as an own property before indexing the satisfies-checked constant object.", | ||
| "objective": "Return both preserved paths while rejecting unknown and prototype-inherited names.", | ||
| "language_delta": "satisfies checks compatibility without casting, as const preserves literal types and marks literal-expression properties readonly at compile time without freezing runtime objects, and Object.hasOwn supplies the runtime membership proof.", | ||
| "prediction_prompt": "Evaluate `Object.hasOwn(routes, \"constructor\")` before deciding whether route indexing is allowed.", | ||
| "transfer_trap": "The in operator walks JavaScript's prototype chain; it is too broad when a configuration must accept only its declared own keys." | ||
| }, | ||
| "ts-readonly": { | ||
| "title": "readonly", | ||
| "concept": "readonly lets callers read configuration-like data while preventing replacement or mutation through that type. The type annotation is not decoration here; it describes which values may cross this lesson's boundary.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying readonly to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-discriminated-unions": { | ||
| "title": "Checkpoint: exhaust a tagged shape union", | ||
| "concept": "Each Shape variant owns a literal kind and its corresponding measurements. Switching on that shared tag narrows fields safely, while assigning the impossible remainder to never checks exhaustiveness.", | ||
| "worked_example": "The worked union models success or error and returns from both switch cases. Its unrelated tags demonstrate the same narrowing mechanism without calculating rectangle or circle measurements.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the readonly rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the readonly exercise.", | ||
| "Matching the judge output without checking this lesson idea: readonly lets callers read configuration-like data while preventing replacement or mutation through that type." | ||
| "Reading radius before proving that the value is the circle variant.", | ||
| "Adding a new kind later while leaving a default branch that silently hides the missing case." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies readonly before the `example:` output?", | ||
| "Which TODO line in the readonly exercise must derive the value from its own data instead of copying the demo?" | ||
| "What exact type remains after the rectangle branch has returned?", | ||
| "Why does assigning a circle to never produce the starter's TypeCheck failure?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying readonly to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Handle the circle variant, then leave the never assertion to guard future additions.", | ||
| "objective": "Pass strict exhaustiveness checking and measure two tagged shapes across three cases.", | ||
| "language_delta": "The literal discriminant exists at runtime and drives JavaScript branching; TypeScript's narrowing and never proof are erased checker reasoning.", | ||
| "prediction_prompt": "Follow `circle 5` through parsing, tag narrowing, and the numeric return before execution.", | ||
| "transfer_trap": "A wildcard match in Rust or default switch elsewhere can conceal new variants just as an overly broad TypeScript fallback can." | ||
| }, | ||
| "ts-satisfies-as-const": { | ||
| "title": "satisfies and as const", | ||
| "concept": "as const preserves route literals, while satisfies checks the wider Record contract without widening those literals. Use the syntax to keep the runtime value and the intended TypeScript shape moving together.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying satisfies and as const to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-async-promise": { | ||
| "title": "Await a typed promise", | ||
| "concept": "An async function always returns a Promise, even when it returns a number expression. await unwraps the fulfilled number inside main, and a final catch observes rejected completion.", | ||
| "worked_example": "The demonstration asynchronously increments nine, awaits ten, and emits a demo record. Its rejection callback accepts unknown rather than assuming every thrown value is Error.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the satisfies and as const rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the satisfies and as const exercise.", | ||
| "Matching the judge output without checking this lesson idea: as const preserves route literals, while satisfies checks the wider Record contract without widening those literals." | ||
| "Returning the original number from double and forgetting the requested transformation.", | ||
| "Calling main without observing its rejected Promise at the top-level boundary." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies satisfies and as const before the `example:` output?", | ||
| "Which TODO line in the satisfies and as const exercise must derive the value from its own data instead of copying the demo?" | ||
| "What is the static return type of double even before it contains an await expression?", | ||
| "When does the output call run relative to fulfillment of the Promise returned by double?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying satisfies and as const to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Resolve double with twice its argument while retaining await and the top-level rejection path.", | ||
| "objective": "Produce doubled results for positive, zero, and negative inputs through asynchronous control flow.", | ||
| "language_delta": "Promise scheduling is JavaScript runtime behavior; TypeScript verifies Promise<number> and catch values but does not provide an executor.", | ||
| "prediction_prompt": "Predict the fulfilled value carried by double for negative three.", | ||
| "transfer_trap": "Calling a Python coroutine does not execute it until scheduled, while invoking this async JavaScript function immediately returns an already-running Promise chain." | ||
| }, | ||
| "ts-iterables": { | ||
| "title": "Iterables", | ||
| "concept": "for...of consumes any iterable, so arrays, strings, and sets can share one collection loop shape. In a stdin/stdout solution, this prevents the common shortcut where the printed value no longer matches the typed data.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying Iterables to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-modules": { | ||
| "title": "Orient a public API in a single-file runner", | ||
| "concept": "This lab practices describing a public callable surface with a local API object. It deliberately stops short of claiming a real import graph because the judge supplies only one .ts file.", | ||
| "worked_example": "The sample exposes a transform function through DemoApi and reverses `module`. The object makes the boundary visible without depending on ESM or CommonJS resolution.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the Iterables rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the Iterables exercise.", | ||
| "Matching the judge output without checking this lesson idea: for...of consumes any iterable, so arrays, strings, and sets can share one collection loop shape." | ||
| "Assuming an export declaration alone proves that another file can resolve and execute the module.", | ||
| "Lowercasing the value even though the promised label behavior requires normalization to uppercase." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies Iterables before the `example:` output?", | ||
| "Which TODO line in the Iterables exercise must derive the value from its own data instead of copying the demo?" | ||
| "What method signature must the local LabelApi implementation satisfy?", | ||
| "Which project settings would Node need before a genuine multi-file ESM exercise could be trusted?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying Iterables to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Implement the declared local API behavior and transform stdin text to uppercase.", | ||
| "objective": "Exercise a typed public surface for ASCII and accented text without pretending to import another file.", | ||
| "language_delta": "Node chooses ESM or CommonJS from extensions and package metadata; a fixed single-file runner cannot honestly test both exporting and importing modules.", | ||
| "prediction_prompt": "Predict the method result for `é` using JavaScript's Unicode-aware uppercase conversion.", | ||
| "transfer_trap": "Module systems in browsers, bundlers, and CommonJS resolve files differently, so passing this orientation lab is not multi-file mastery." | ||
| }, | ||
| "ts-array-methods": { | ||
| "title": "map, filter, and reduce", | ||
| "concept": "filter selects items, map transforms them, and reduce folds the sequence into the final answer. The type annotation is not decoration here; it describes which values may cross this lesson's boundary.", | ||
| "worked_example": "The worked code labels its demo output with `example:` while applying map, filter, and reduce to separate data. Follow the value-changing line before stdout; the exercise uses the same idea without the label on its own case.", | ||
| "ts-error-handling": { | ||
| "title": "Capstone: validate and execute commands", | ||
| "concept": "The command parser must validate whole numeric tokens, reject division by zero, and construct a discriminated command. Async execution returns a Promise, while the final catch narrows unknown before labeling failure.", | ||
| "worked_example": "The example proves two subtle facts: parseInt accepts the numeric prefix of `12px`, and parsing `bad` yields NaN rather than throwing. A caught string is also narrowed with typeof before use.", | ||
| "common_mistakes": [ | ||
| "Changing only the final output line while leaving the map, filter, and reduce rule out of the code path.", | ||
| "Copying the labeled demo output instead of solving the TODO in the map, filter, and reduce exercise.", | ||
| "Matching the judge output without checking this lesson idea: filter selects items, map transforms them, and reduce folds the sequence into the final answer." | ||
| "Using parseInt for validation and accidentally accepting a token with trailing letters.", | ||
| "Writing `catch (error)` as though every JavaScript throw produces an Error instance." | ||
| ], | ||
| "self_check": [ | ||
| "Which expression in the example applies map, filter, and reduce before the `example:` output?", | ||
| "Which TODO line in the map, filter, and reduce exercise must derive the value from its own data instead of copying the demo?" | ||
| "Why does Number reject `12px` while Number.parseInt accepts part of it?", | ||
| "Which command branch performs the zero-divisor check before asynchronous execution?" | ||
| ], | ||
| "exercise_prompt": "Complete the TODO by applying map, filter, and reduce to the starter data. Keep stdout judge-clean; the `example:` label belongs only to the worked example." | ||
| "exercise_prompt": "Build a strict parser, execute the exhaustive command union, and narrow failures from unknown.", | ||
| "objective": "Calculate valid add and divide commands while writing invalid for partial or non-finite numbers and division by zero.", | ||
| "language_delta": "TypeScript strict catch treats failures as unknown because JavaScript may throw any value; parseInt returns NaN on wholly invalid text and never enters catch by itself.", | ||
| "prediction_prompt": "Trace `add 12px 1` through conversion and identify the exact validation that rejects it.", | ||
| "transfer_trap": "Exception-only parsing patterns from other languages do not transfer: JavaScript numeric conversion often reports failure with NaN instead of throwing." | ||
| } | ||
| } | ||
| } |
@@ -7,422 +7,506 @@ { | ||
| "ts-output": { | ||
| "title": "Consola y stdout", | ||
| "concept": "La salida de consola es el contrato que compara el juez, por eso se distingue el salto de console.log de la escritura exacta con process.stdout.write. La anotación de tipo no es adorno: describe qué valores pueden cruzar este límite.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Consola y stdout. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Escribir una salida estándar exacta", | ||
| "concept": "`process.stdout.write` emite únicamente la cadena recibida; por tanto, el separador y el salto final pertenecen al contrato. TypeScript comprueba el código antes de que Node ejecute el JavaScript resultante.", | ||
| "worked_example": "La muestra conserva un nombre y unos puntos tipados, pero escribe el registro distinto `demo Mina=4`. La anotación desaparece antes de ejecutar; los caracteres de la plantilla sí llegan a la salida.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Consola y stdout en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Consola y stdout.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: La salida de consola es el contrato que compara el juez, por eso se distingue el salto de console.log de la escritura exacta con process.stdout.write." | ||
| "Usar `console.log` cuando se necesita controlar con precisión el salto de línea final.", | ||
| "Juntar nombre y puntuación sin separador y crear una salida ambigua como `Ada7`." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Consola y stdout antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Consola y stdout debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué carácter sigue al nombre en el registro de demostración?", | ||
| "¿Qué parte de la llamada aporta la terminación de línea?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Consola y stdout a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Lee los dos campos de la entrada estándar y forma con `process.stdout.write` una línea separada por dos puntos.", | ||
| "objective": "Producir `nombre:puntuación` seguido de exactamente un salto de línea para cada registro.", | ||
| "language_delta": "A diferencia de `console.log`, `process.stdout.write` no añade espacios ni nueva línea; las anotaciones tampoco modifican esos bytes al ejecutarse Node.", | ||
| "prediction_prompt": "Detalla los caracteres emitidos para un nombre Unicode y una puntuación de dos cifras.", | ||
| "transfer_trap": "Una consola de navegador presenta valores para inspección; un evaluador compara el texto real de la salida estándar." | ||
| }, | ||
| "ts-let-const": { | ||
| "title": "let y const", | ||
| "concept": "const marca enlaces que no se reasignan, mientras let deja visible el acumulador que cambia en la solución. Usa la sintaxis para que el valor en runtime y la forma esperada por TypeScript avancen juntos.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de let y const. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-input": { | ||
| "title": "Dividir la entrada estándar de Node en tokens", | ||
| "concept": "Node expone la entrada redirigida como descriptor de archivo 0. Leerla una vez, eliminar sus bordes y tratar aparte la cadena vacía crea una secuencia numérica fiable.", | ||
| "worked_example": "La muestra usa la cadena fija `4 6`, ajena a los casos evaluados. La separación, la conversión con `Number` y un `reduce` iniciado en cero producen `10`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de let y const en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de let y const.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: const marca enlaces que no se reasignan, mientras let deja visible el acumulador que cambia en la solución." | ||
| "Dividir una cadena vacía ya recortada y convertir su único token vacío en cero por accidente.", | ||
| "Leer solo la primera línea aunque los valores puedan distribuirse entre varias." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica let y const antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de let y const debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué la lista de tokens debe quedar vacía si no hay caracteres?", | ||
| "¿Cómo determina el valor inicial de `reduce` el total sin entrada?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando let y const a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Sustituye el atajo del primer token por una suma de todos los números separados por cualquier espacio en blanco.", | ||
| "objective": "Calcular el total para entradas de una línea, varias líneas y contenido vacío.", | ||
| "language_delta": "`readFileSync(0, \"utf8\")` pertenece a Node; TypeScript comprueba la declaración disponible, pero no realiza la lectura por sí mismo.", | ||
| "prediction_prompt": "Predice la lista de tokens y el total para `-1`, un salto de línea y `5 2`.", | ||
| "transfer_trap": "Las lecturas interactivas repetidas de otros lenguajes no representan el flujo completo que recibe este ejecutor de Node." | ||
| }, | ||
| "ts-primitives": { | ||
| "title": "Tipos primitivos", | ||
| "concept": "Las anotaciones string, number y boolean describen los escalares que esperan el parseo y las ramas. En soluciones de stdin/stdout evita el atajo de imprimir algo que ya no coincide con los datos tipados.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Tipos primitivos. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "title": "Convertir texto en valores primitivos", | ||
| "concept": "Una anotación `string` describe un valor al verificador, mientras `Number` realiza la conversión en ejecución. La decisión debe comparar puntuación y umbral numéricos, no sus grafías textuales.", | ||
| "worked_example": "El ejemplo declara una cadena, un número y un booleano, y luego imprime un registro distinto. Cada valor ya tiene en ejecución la forma primitiva indicada.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Tipos primitivos en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Tipos primitivos.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Las anotaciones string, number y boolean describen los escalares que esperan el parseo y las ramas." | ||
| "Anotar `scoreText` como número sin convertir realmente la cadena recibida.", | ||
| "Depender de coerciones relacionales y ocultar dónde entra un texto numérico inválido." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Tipos primitivos antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Tipos primitivos debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué expresión cambia un token de semántica textual a numérica?", | ||
| "¿Por qué una puntuación igual a un umbral cero cuenta como aprobada?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Tipos primitivos a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Convierte los dos campos numéricos, compáralos de forma inclusiva y conserva el nombre como texto.", | ||
| "objective": "Emitir la puntuación analizada y la clasificación correcta en tres casos de frontera.", | ||
| "language_delta": "TypeScript 5.9 detecta usos primitivos incompatibles, pero borra las anotaciones; estas nunca convierten por sí solas la entrada de Node.", | ||
| "prediction_prompt": "Decide el estado de `Bo 0 0` antes de evaluar el operador de comparación.", | ||
| "transfer_trap": "Una declaración numérica puede imponer reglas en otros lenguajes; en TypeScript no sustituye a la conversión de JavaScript." | ||
| }, | ||
| "ts-strings-templates": { | ||
| "title": "Cadenas y plantillas", | ||
| "concept": "Las plantillas literales formatean junto al valor, y métodos como trim devuelven una cadena nueva. La anotación de tipo no es adorno: describe qué valores pueden cruzar este límite.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Cadenas y plantillas. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-let-const": { | ||
| "title": "Reasignar un acumulador y conservar su etiqueta", | ||
| "concept": "Usa `const` para el enlace estable de la etiqueta y `let` para un total que cambia. Estas palabras controlan la reasignación del enlace, no la congelación de un objeto referenciado.", | ||
| "worked_example": "La demostración fija `demo`, inicia un total mutable en cinco y añade menos dos. El total termina en tres sin cambiar el enlace de la etiqueta.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Cadenas y plantillas en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Cadenas y plantillas.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Las plantillas literales formatean junto al valor, y métodos como trim devuelven una cadena nueva." | ||
| "Declarar el acumulador con `const` e intentar asignarle después otro número.", | ||
| "Suponer que las propiedades escribibles de un objeto enlazado con `const` quedan inmóviles." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Cadenas y plantillas antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Cadenas y plantillas debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Cuál de los dos enlaces de la muestra recibe una segunda asignación?", | ||
| "¿Impide `const` ejecutar `settings.enabled = false` sobre un objeto mutable?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Cadenas y plantillas a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Acumula ambos números analizados en un total reasignable y mantén fija la etiqueta que encabeza la respuesta.", | ||
| "objective": "Conservar etiqueta y suma correctas con valores positivos, negativos y nulos.", | ||
| "language_delta": "JavaScript aplica `const` en ejecución; las anotaciones `number` son información separada que TypeScript elimina.", | ||
| "prediction_prompt": "Sigue el valor de `total` tras cada asignación para la entrada `net -2 5`.", | ||
| "transfer_trap": "Los enlaces inmutables de Rust y las referencias `final` de Java tienen consecuencias diferentes sobre propiedad y mutación." | ||
| }, | ||
| "ts-arrays-tuples": { | ||
| "title": "Arreglos y tuplas", | ||
| "concept": "Los arreglos guardan valores ordenados de un tipo; las tuplas fijan posición y tipo en registros pequeños. Usa la sintaxis para que el valor en runtime y la forma esperada por TypeScript avancen juntos.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Arreglos y tuplas. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-strings-templates": { | ||
| "title": "Recortar el nombre y conservar la puntuación", | ||
| "concept": "La barra vertical separa campos con reglas distintas. Elimina solo una terminación CRLF o LF, recorta el nombre y conserva cada carácter de la puntuación, incluidos sus espacios finales.", | ||
| "worked_example": "El registro fijo de `Mira` termina con dos espacios tras la puntuación y luego una nueva línea. La expresión regular quita únicamente ese terminador de línea, por lo que ambos espacios sobreviven.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Arreglos y tuplas en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Arreglos y tuplas.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Los arreglos guardan valores ordenados de un tipo; las tuplas fijan posición y tipo en registros pequeños." | ||
| "Aplicar `trimEnd()` al registro completo y borrar el espaciado significativo de la puntuación.", | ||
| "Interpretar `string.length` como grafemas visibles, aunque cuenta unidades UTF-16." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Arreglos y tuplas antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Arreglos y tuplas debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "Tras quitar la nueva línea de ` Mira |9 `, ¿qué espacios quedan en cada campo?", | ||
| "¿Por qué recortar `rawName` no afecta a los dos espacios de `score`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Arreglos y tuplas a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Quita una sola terminación CRLF o LF, recorta únicamente el lado del nombre e interpola la puntuación intacta tras dos puntos.", | ||
| "objective": "Formatear tres registros sin perder los espacios significativos situados al final de la puntuación.", | ||
| "language_delta": "`trimEnd` elimina todo espacio final; el reemplazo anclado solo retira el terminador de línea, y los índices siguen contando unidades UTF-16.", | ||
| "prediction_prompt": "Marca los espacios que permanecen después de aplicar `/\\r?\\n$/` a ` Ada |7 ` seguido de LF.", | ||
| "transfer_trap": "Una operación general de recorte puede destruir datos cuando el contrato solo autoriza eliminar el salto de línea del registro." | ||
| }, | ||
| "ts-objects": { | ||
| "title": "Tipos de objeto", | ||
| "concept": "Los tipos de objeto nombran campos requeridos para no omitir width, height u otra propiedad en el cálculo. En soluciones de stdin/stdout evita el atajo de imprimir algo que ya no coincide con los datos tipados.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Tipos de objeto. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-arrays-tuples": { | ||
| "title": "Devolver un total nombrado como tupla", | ||
| "concept": "El primer token nombra el registro y los restantes forman un array numérico. La tupla `[string, number]` fija el significado de sus dos posiciones para el verificador.", | ||
| "worked_example": "La muestra suma el array separado `[1, 4]` y guarda `Neo` junto al total en una pareja tipada. Las posiciones cero y uno siguen perteneciendo a un `Array` en ejecución.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Tipos de objeto en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Tipos de objeto.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Los tipos de objeto nombran campos requeridos para no omitir width, height u otra propiedad en el cálculo." | ||
| "Tratar todos los tokens como números y perder el nombre inicial antes de reducir.", | ||
| "Esperar que la anotación de tupla cree un contenedor distinto de `Array` en JavaScript." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Tipos de objeto antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Tipos de objeto debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué tipo admite la segunda posición de la pareja?", | ||
| "¿Qué total corresponde cuando la entrada solo contiene `Bo`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Tipos de objeto a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Reduce toda la cola numérica y guarda el nombre y el total en la tupla declarada antes de imprimir sus posiciones.", | ||
| "objective": "Resolver varios valores, números negativos y una cola vacía mediante una única forma de pareja.", | ||
| "language_delta": "Las comprobaciones posicionales desaparecen al ejecutar Node; la tupla continúa siendo un array JavaScript mutable.", | ||
| "prediction_prompt": "Para `Lin -1 4 2`, enumera el array numérico antes de calcular la segunda posición.", | ||
| "transfer_trap": "Esta tupla no se convierte en un pequeño registro inmutable, a diferencia de ciertos valores nominales de otros lenguajes." | ||
| }, | ||
| "ts-functions": { | ||
| "title": "Funciones", | ||
| "concept": "Las anotaciones de parámetros y retorno hacen explícito el límite entre entrada parseada, cálculo y salida. La anotación de tipo no es adorno: describe qué valores pueden cruzar este límite.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Funciones. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-iterables": { | ||
| "title": "Consumir un `Iterable` con `for...of`", | ||
| "concept": "`Iterable<string>` promete un protocolo secuencial, no índices de array. `for...of` solicita valores mediante ese protocolo y funciona con arrays, conjuntos, cadenas e iterables propios.", | ||
| "worked_example": "La demostración pasa un `Set` con `T` y `S` a un recolector. La iteración obtiene ambos valores en orden de inserción sin usar un método exclusivo de arrays.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Funciones en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Funciones.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Las anotaciones de parámetros y retorno hacen explícito el límite entre entrada parseada, cálculo y salida." | ||
| "Indexar un `Iterable` aunque su interfaz no garantiza propiedades numéricas.", | ||
| "Reutilizar el mismo iterador después de haberlo consumido hasta el final." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Funciones antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Funciones debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué capacidad exige `for...of` a su entrada?", | ||
| "¿Por qué una lista vacía todavía produce un salto de línea?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Funciones a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Recorre todos los tokens de la entrada mediante iteración y concaténalos respetando el orden en que aparecen.", | ||
| "objective": "Unir dos, tres o ningún token sin depender de índices de array.", | ||
| "language_delta": "El tipo `Iterable` describe el protocolo JavaScript y no añade almacenamiento, reinicio ni otra colección durante la ejecución.", | ||
| "prediction_prompt": "Sigue los estados del iterador para `a b c` y anota el acumulador después de cada paso.", | ||
| "transfer_trap": "Iteradores Python y flujos Java también se consumen, pero sus reglas de repetición y propiedad no son intercambiables." | ||
| }, | ||
| "ts-input": { | ||
| "title": "Parseo de stdin en Node", | ||
| "concept": "Las soluciones en Node suelen leer una vez el descriptor 0, separar por espacios y convertir tokens antes de calcular. Usa la sintaxis para que el valor en runtime y la forma esperada por TypeScript avancen juntos.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Parseo de stdin en Node. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-array-methods": { | ||
| "title": "Filtrar, elevar al cuadrado y reducir", | ||
| "concept": "Una secuencia legible selecciona primero números pares, después los eleva al cuadrado y finalmente pliega los productos. Iniciar `reduce` en cero define el resultado de una selección vacía.", | ||
| "worked_example": "La muestra filtra `[2, 5, 6]` con otra condición, duplica los supervivientes y los suma. Ese recorrido distinto muestra el orden de métodos sin revelar la respuesta evaluada.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Parseo de stdin en Node en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Parseo de stdin en Node.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Las soluciones en Node suelen leer una vez el descriptor 0, separar por espacios y convertir tokens antes de calcular." | ||
| "Transformar antes de filtrar cuando el predicado debe observar los valores originales.", | ||
| "Omitir el valor inicial de `reduce` y provocar una excepción si no sobrevive ningún par." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Parseo de stdin en Node antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Parseo de stdin en Node debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿En qué etapa menos dos se convierte en cuatro positivo?", | ||
| "¿Qué valor recibe primero `reduce` cuando el array filtrado está vacío?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Parseo de stdin en Node a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Corrige el predicado de paridad y conserva tanto el cuadrado posterior como el pliegue iniciado explícitamente en cero.", | ||
| "objective": "Calcular la suma de cuadrados pares con signos mezclados y conjuntos exclusivamente impares.", | ||
| "language_delta": "TypeScript infiere los tipos de las funciones pasadas a esos métodos, pero no vuelve perezosa la canalización.", | ||
| "prediction_prompt": "Escribe los arrays intermedios producidos desde `-2 -1 0` antes de sumarlos.", | ||
| "transfer_trap": "Los adaptadores de iterador Rust son perezosos hasta su consumo; los métodos de arrays JavaScript recorren cada etapa de inmediato." | ||
| }, | ||
| "ts-control-flow": { | ||
| "title": "Flujo de control", | ||
| "concept": "if y los bucles deciden qué valores se acumulan, así que la condición debe coincidir con la regla de datos. En soluciones de stdin/stdout evita el atajo de imprimir algo que ya no coincide con los datos tipados.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Flujo de control. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-objects": { | ||
| "title": "Calcular mediante un objeto estructural", | ||
| "concept": "El tipo `Rectangle` exige `width` y `height`; `area` acepta cualquier objeto compatible. No hace falta una clase ni compartir una identidad nominal para efectuar el cálculo.", | ||
| "worked_example": "El objeto mostrado también tiene `color`, pero una vista con dos miembros entra en `demoArea`. La propiedad adicional sigue presente en ejecución y simplemente no se consulta.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Flujo de control en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Flujo de control.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: if y los bucles deciden qué valores se acumulan, así que la condición debe coincidir con la regla de datos." | ||
| "Omitir una dimensión analizada y sustituirla silenciosamente por un valor provisional.", | ||
| "Suponer que dos objetos necesitan el mismo constructor para ser compatibles estructuralmente." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Flujo de control antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Flujo de control debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué miembros de `painted` lee realmente `demoArea`?", | ||
| "¿Por qué un ancho cero fuerza correctamente un área cero?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Flujo de control a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Construye un `Rectangle` con las dos dimensiones de la entrada y pásalo a la función tipada que multiplica sus campos.", | ||
| "objective": "Calcular productos para rectángulos normales, de ancho cero y de altura uno usando datos estructurales.", | ||
| "language_delta": "Los tipos de objeto se borran antes de Node; las clases sí crean comportamiento y `readonly` solo restringe de forma superficial el acceso tipado.", | ||
| "prediction_prompt": "Predice el objeto construido y el producto para las dimensiones `7 1`.", | ||
| "transfer_trap": "Java suele exigir identidad de clase declarada; TypeScript acepta valores creados por separado si sus miembros son compatibles." | ||
| }, | ||
| "ts-union-narrowing": { | ||
| "title": "Uniones y estrechamiento", | ||
| "concept": "Los valores union necesitan una comprobación en runtime antes de usar operaciones solo de string o solo de number. La anotación de tipo no es adorno: describe qué valores pueden cruzar este límite.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Uniones y estrechamiento. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-classes": { | ||
| "title": "Actualizar estado detrás de un método de clase", | ||
| "concept": "Una instancia de `Counter` posee estado mutable y expone `increment`. El método debe guardar el nuevo valor antes de devolverlo para que llamadas posteriores observen el cambio.", | ||
| "worked_example": "La clase de muestra comienza en ocho y se incrementa hasta nueve. `private` limita el acceso durante la comprobación, pero el campo emitido sigue siendo una propiedad ordinaria.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Uniones y estrechamiento en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Uniones y estrechamiento.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Los valores union necesitan una comprobación en runtime antes de usar operaciones solo de string o solo de number." | ||
| "Devolver `this.value + 1` sin almacenar el resultado para la siguiente llamada.", | ||
| "Confundir `private` de TypeScript con un campo JavaScript `#value`, cuya privacidad sí existe en ejecución." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Uniones y estrechamiento antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Uniones y estrechamiento debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "Tras dos llamadas sobre la misma instancia, ¿qué valor debe devolver el método?", | ||
| "¿Qué forma de privacidad de clase sobrevive como restricción en JavaScript?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Uniones y estrechamiento a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Modifica `increment` para actualizar el campo de instancia y devolver el valor resultante del contador.", | ||
| "objective": "Avanzar exactamente una unidad desde valores iniciales positivos, negativos y mayores.", | ||
| "language_delta": "Aquí `private` es información borrada del verificador; los campos con prefijo `#` de ECMAScript conservan privacidad en ejecución.", | ||
| "prediction_prompt": "Sigue el campo almacenado antes y después de una llamada que comienza en menos uno.", | ||
| "transfer_trap": "La JVM impone el acceso `private` de Java, por lo que su frontera en ejecución es más fuerte." | ||
| }, | ||
| "ts-literal-types": { | ||
| "title": "Tipos literales", | ||
| "concept": "Las uniones literales limitan comandos y modos a valores exactos como 'left' o 'right'. Usa la sintaxis para que el valor en runtime y la forma esperada por TypeScript avancen juntos.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Tipos literales. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-readonly": { | ||
| "title": "Leer una vista superficial de solo lectura", | ||
| "concept": "Una configuración `readonly` permite consultar nombre y puntuaciones sin mutar a través de ese tipo. El array interior también debe declararse `readonly`, pues la propiedad sola solo impide reemplazarlo.", | ||
| "worked_example": "La muestra declara una propiedad y un array numérico de solo lectura, y consulta su longitud. No intenta escribir, así que supera la comprobación estricta y después se ejecuta.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Tipos literales en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Tipos literales.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Las uniones literales limitan comandos y modos a valores exactos como 'left' o 'right'." | ||
| "Llamar a `push` sobre `readonly number[]` y esperar que TypeScript estricto lo acepte.", | ||
| "Creer que `readonly` invoca `Object.freeze` o congela recursivamente la memoria de JavaScript." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Tipos literales antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Tipos literales debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué el código inicial falla antes de que Node reciba un caso?", | ||
| "¿Podría otra referencia mutable al mismo array añadir elementos?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Tipos literales a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Elimina la mutación prohibida y calcula la suma de puntuaciones leyendo únicamente la vista superficial de solo lectura.", | ||
| "objective": "Superar la comprobación estricta e informar del nombre junto con el total configurado.", | ||
| "language_delta": "`readonly` es superficial y solo tipado salvo que los miembros internos también lo sean; ninguna anotación congela objetos en ejecución.", | ||
| "prediction_prompt": "Predice el diagnóstico del compilador en `config.scores.push(0)`, no una salida de Node.", | ||
| "transfer_trap": "Un préstamo inmutable de Rust participa en reglas de propiedad; no equivale a una vista TypeScript que se borra." | ||
| }, | ||
| "ts-optional-nullish": { | ||
| "title": "Opcional y nullish", | ||
| "concept": "Los campos opcionales pueden ser undefined, y ?? conserva datos falsy válidos como la puntuación 0. En soluciones de stdin/stdout evita el atajo de imprimir algo que ya no coincide con los datos tipados.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Opcional y nullish. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-control-flow": { | ||
| "title": "Conservar un extremo inclusivo en el bucle", | ||
| "concept": "El bucle visita los enteros desde uno hasta `n` y una comparación estricta del módulo selecciona los impares. El operador relacional de la condición decide si participa el extremo.", | ||
| "worked_example": "La muestra recorre hasta cinco pero acumula pares y obtiene un total distinto. Aun así muestra una rama estricta y un límite superior cerrado.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Opcional y nullish en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Opcional y nullish.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Los campos opcionales pueden ser undefined, y ??" | ||
| "Usar `< n` y omitir un extremo impar que pertenece al intervalo solicitado.", | ||
| "Usar igualdad flexible y permitir que la coerción compare valores de tipos distintos." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Opcional y nullish antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Opcional y nullish debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué valor del bucle contribuye en último lugar si `n` vale tres?", | ||
| "¿Qué caso revela el error de límite aunque el caso seis pueda pasar?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Opcional y nullish a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Repara el extremo del bucle y conserva tanto la rama de números impares como el acumulador numérico.", | ||
| "objective": "Sumar todos los enteros impares del intervalo inclusivo para límites positivos y cero.", | ||
| "language_delta": "JavaScript evalúa booleanos en ejecución; TypeScript, aparte, estrecha tipos a lo largo de las ramas demostradas.", | ||
| "prediction_prompt": "Enumera las visitas con `n = 3` usando primero `<` y después `<=`.", | ||
| "transfer_trap": "`range` de Python excluye su parada; trasladar literalmente ese patrón puede omitir el extremo TypeScript." | ||
| }, | ||
| "ts-interfaces-aliases": { | ||
| "title": "Interfaces y alias de tipo", | ||
| "concept": "Las interfaces nombran contratos de objeto; los alias también nombran uniones, tuplas y expresiones compuestas. La anotación de tipo no es adorno: describe qué valores pueden cruzar este límite.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Interfaces y alias de tipo. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-functions": { | ||
| "title": "Punto de control: frontera de cálculo tipada", | ||
| "concept": "La firma promete qué entrada acepta una función y qué resultado devuelve. La comprobación estricta rechaza el código inicial porque una multiplicación numérica no satisface un retorno declarado `string`.", | ||
| "worked_example": "La demostración define un perímetro que devuelve `number` a partir de dos parámetros. Sus dimensiones fijas generan un valor ajeno a cualquier caso de área.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Interfaces y alias de tipo en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Interfaces y alias de tipo.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Las interfaces nombran contratos de objeto; los alias también nombran uniones, tuplas y expresiones compuestas." | ||
| "Convertir la multiplicación en texto solo para satisfacer una anotación de retorno equivocada.", | ||
| "Imprimir dentro de la función de cálculo y perder la separación entre devolver datos y escribir salida." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Interfaces y alias de tipo antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Interfaces y alias de tipo debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué parte de la firma establece el tipo de retorno prometido por `area`?", | ||
| "¿Cuándo se convierten los tokens analizados en argumentos numéricos?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Interfaces y alias de tipo a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Corrige el contrato de retorno para que la comprobación estricta acepte el cálculo numérico sin convertirlo en texto.", | ||
| "objective": "Superar el verificador y calcular tres áreas mediante una función anotada.", | ||
| "language_delta": "TypeScript valida parámetros y resultados antes de ejecutar; dentro de la función, ramas y bucles siguen siendo JavaScript ordinario.", | ||
| "prediction_prompt": "Clasifica primero el fallo como `TypeCheck`, antes de considerar las salidas de los casos.", | ||
| "transfer_trap": "Otros lenguajes pueden imponer firmas durante compilación o ejecución; TypeScript las elimina después de comprobarlas." | ||
| }, | ||
| "ts-generics": { | ||
| "title": "Genéricos", | ||
| "concept": "Los genéricos conservan el tipo de elemento del llamador cuando el código reusable lee arreglos o devuelve valores. Usa la sintaxis para que el valor en runtime y la forma esperada por TypeScript avancen juntos.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Genéricos. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-union-narrowing": { | ||
| "title": "Estrechar una unión antes de formatearla", | ||
| "concept": "Un valor `string | number` solo ofrece operaciones seguras para ambos miembros. Una rama `typeof` demuestra qué primitivo existe en ejecución y habilita su formateador específico.", | ||
| "worked_example": "La muestra pone cadenas en minúsculas y formatea un número fijo con un decimal. Así recorre ambas ramas sin coincidir con las transformaciones evaluadas.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Genéricos en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Genéricos.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Los genéricos conservan el tipo de elemento del llamador cuando el código reusable lee arreglos o devuelve valores." | ||
| "Llamar directamente a `toUpperCase` sobre una unión que también puede contener un número.", | ||
| "Usar el token de categoría como aserción en vez de construir y comprobar el valor real." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Genéricos antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Genéricos debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué devuelve `typeof` para el miembro creado mediante `Number(text)`?", | ||
| "¿Por qué `toFixed(0)` convierte menos 1,6 en menos dos?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Genéricos a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Añade una rama de estrechamiento en ejecución y aplica a cada miembro de la unión su formateador adecuado.", | ||
| "objective": "Convertir comandos de texto a mayúsculas y redondear los numéricos con comprobación estricta.", | ||
| "language_delta": "El estrechamiento combina una prueba JavaScript real con razonamiento del verificador; una aserción arbitraria no aporta evidencia.", | ||
| "prediction_prompt": "Selecciona la rama y el texto formateado para `n -1.6` antes de ejecutar.", | ||
| "transfer_trap": "Python permite la llamada hasta que falle en ejecución; TypeScript rechaza antes una operación insegura sobre la unión." | ||
| }, | ||
| "ts-keyof-typeof": { | ||
| "title": "keyof y typeof", | ||
| "concept": "typeof captura la forma estática de un valor, y keyof la convierte en la unión de claves permitidas. En soluciones de stdin/stdout evita el atajo de imprimir algo que ya no coincide con los datos tipados.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de keyof y typeof. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-literal-types": { | ||
| "title": "Validar texto antes de una unión literal", | ||
| "concept": "`Direction` contiene exactamente dos literales de cadena. Como la entrada comienza siendo texto sin restricciones, una comparación en ejecución debe probar la pertenencia antes de llamar a `turn`.", | ||
| "worked_example": "La demostración usa otra unión, norte o sur, y pasa directamente un literal conocido. La rama genera una flecha etiquetada sin fingir que validó entrada externa.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de keyof y typeof en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de keyof y typeof.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: typeof captura la forma estática de un valor, y keyof la convierte en la unión de claves permitidas." | ||
| "Convertir texto arbitrario con `as Direction` y creer que la aserción realizó una validación real.", | ||
| "Dejar que una función binaria trate cualquier dirección desconocida como si fuera `right`." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica keyof y typeof antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de keyof y typeof debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué `raw as Direction` deja que `up` llegue a `turn`?", | ||
| "¿Qué tipo estrechado tiene `raw` después de comprobar los dos literales permitidos?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando keyof y typeof a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Comprueba la cadena recibida frente a ambos miembros de `Direction` y comunica por separado cualquier texto inválido.", | ||
| "objective": "Transformar `left` y `right` en sus abreviaturas y rechazar un comando desconocido.", | ||
| "language_delta": "La unión limita el código comprobado, pero desaparece; JavaScript sigue recibiendo cadenas normales y necesita una prueba de pertenencia.", | ||
| "prediction_prompt": "Predice qué rama defectuosa toma `up` y compárala con la ruta de rechazo requerida.", | ||
| "transfer_trap": "Un enum cerrado de otro lenguaje puede validar su construcción; una unión literal TypeScript no crea ningún contenedor en ejecución." | ||
| }, | ||
| "ts-indexed-access": { | ||
| "title": "Tipos de acceso indexado", | ||
| "concept": "Los tipos de acceso indexado reutilizan tipos de propiedad y elemento de un modelo existente sin repetirlos. La anotación de tipo no es adorno: describe qué valores pueden cruzar este límite.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Tipos de acceso indexado. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-optional-nullish": { | ||
| "title": "Aplicar un valor solo cuando falta la puntuación", | ||
| "concept": "La puntuación analizada es `number | null`. `??` aporta diez únicamente para `null` o `undefined` y conserva cero como resultado numérico intencionado.", | ||
| "worked_example": "La muestra asigna cero a un conteo opcional y aplica `?? 8`. Que se imprima cero demuestra la regla de ausencia sin reutilizar los nombres ni el valor alternativo del ejercicio.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Tipos de acceso indexado en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Tipos de acceso indexado.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Los tipos de acceso indexado reutilizan tipos de propiedad y elemento de un modelo existente sin repetirlos." | ||
| "Usar `||` y sustituir un cero legítimo porque JavaScript lo considera falso.", | ||
| "Dejar `none` como cadena y mezclar el marcador textual con cálculos numéricos." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Tipos de acceso indexado antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Tipos de acceso indexado debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué valores activan el operando derecho de `??`?", | ||
| "¿Qué valor de ejecución se crea cuando `scoreText` contiene `none`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Tipos de acceso indexado a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Usa coalescencia nula para conservar una puntuación cero y aplicar diez únicamente cuando el dato esté ausente.", | ||
| "objective": "Distinguir cero, nulo y una puntuación positiva en los registros emitidos.", | ||
| "language_delta": "Con `strictNullChecks`, el nulo forma parte explícita de la unión; el operador JavaScript `??` aporta la semántica real.", | ||
| "prediction_prompt": "Evalúa la expresión predeterminada para `Ada 0` antes de ejecutar Node.", | ||
| "transfer_trap": "`or` de Python y `||` de JavaScript comparten un concepto amplio de falsedad; ninguno sustituye a `??`." | ||
| }, | ||
| "ts-mapped-types": { | ||
| "title": "Tipos mapeados", | ||
| "concept": "Los tipos mapeados transforman cada clave de un objeto y mantienen las formas derivadas alineadas. Usa la sintaxis para que el valor en runtime y la forma esperada por TypeScript avancen juntos.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Tipos mapeados. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-interfaces-aliases": { | ||
| "title": "Componer contratos estructurales", | ||
| "concept": "`Named` es una interfaz, `Scored` un alias y su intersección exige ambos campos. `Entry` se acepta por sus miembros, no por una cadena de constructores.", | ||
| "worked_example": "La muestra intersecta `Tagged` con `Weighted`, construye un objeto compatible y lee su etiqueta y peso. La composición es visible sin duplicar el modelo evaluado.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Tipos mapeados en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Tipos mapeados.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Los tipos mapeados transforman cada clave de un objeto y mantienen las formas derivadas alineadas." | ||
| "Alterar la puntuación al construir la intersección en vez de conservar el dato analizado.", | ||
| "Suponer que una interfaz y un alias de objeto equivalente crean clases distintas en ejecución." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Tipos mapeados antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Tipos mapeados debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué dos propiedades necesita un valor para satisfacer `Entry`?", | ||
| "¿Puede un alias nombrar una unión aunque una interfaz no lo haga directamente?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Tipos mapeados a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Construye la intersección con el nombre sin cambios y la puntuación ya convertida, y escribe directamente esos dos campos.", | ||
| "objective": "Conservar puntuaciones positivas, cero y negativas al construir e imprimir un objeto estructural.", | ||
| "language_delta": "Las interfaces pueden fusionar declaraciones y los alias expresan uniones; ambos se borran y no crean clases JavaScript.", | ||
| "prediction_prompt": "Escribe el objeto `Entry` creado desde `Bo -2` antes de interpolar sus campos.", | ||
| "transfer_trap": "Una interfaz Java participa en declaraciones nominales y metadatos de JVM; la interfaz TypeScript es estructural y se elimina." | ||
| }, | ||
| "ts-conditional-types": { | ||
| "title": "Tipos condicionales", | ||
| "concept": "Los tipos condicionales eligen una rama según la relación de tipos, e infer extrae la parte coincidente. En soluciones de stdin/stdout evita el atajo de imprimir algo que ya no coincide con los datos tipados.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Tipos condicionales. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-generics": { | ||
| "title": "Conservar el tipo de elemento con `first`", | ||
| "concept": "`first<T>` conserva el tipo de elemento de quien llama y devuelve `undefined` si no existe ninguno. La unión de retorno modela el vacío sin `any` ni aserciones de no nulidad.", | ||
| "worked_example": "La muestra define la función genérica opuesta, `last`, y la llama con tres cadenas. El resultado infiere `string | undefined` mientras selecciona deliberadamente el otro extremo.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Tipos condicionales en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Tipos condicionales.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Los tipos condicionales eligen una rama según la relación de tipos, e infer extrae la parte coincidente." | ||
| "Devolver el índice uno y confundir el segundo elemento con el primero.", | ||
| "Forzar un valor con `!` aunque un array vacío de solo lectura sea una entrada válida." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Tipos condicionales antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Tipos condicionales debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué representa `T` cuando `first` recibe un array de cadenas?", | ||
| "¿Por qué `undefined` pertenece al retorno aunque algunos casos no estén vacíos?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Tipos condicionales a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Devuelve la posición cero desde el auxiliar genérico y conserva en quien llama la alternativa explícita para el vacío.", | ||
| "objective": "Seleccionar el primer token de dos entradas no vacías y `none` cuando no haya ninguno.", | ||
| "language_delta": "Los genéricos preservan relaciones durante la comprobación y desaparecen en Node; no crean versiones de contenedor en ejecución.", | ||
| "prediction_prompt": "Infiere el tipo de retorno y el valor real para un array de un solo token.", | ||
| "transfer_trap": "Java borra genéricos con otras reglas de varianza y Rust suele monomorfizarlos; los mismos corchetes angulares no implican igual mecanismo." | ||
| }, | ||
| "ts-utility-types": { | ||
| "title": "Tipos utilitarios", | ||
| "concept": "Tipos como Pick y Partial expresan transformaciones comunes de objetos sin crear otro tipo auxiliar. La anotación de tipo no es adorno: describe qué valores pueden cruzar este límite.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Tipos utilitarios. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-keyof-typeof": { | ||
| "title": "Validar una clave de ejecución para `keyof`", | ||
| "concept": "`typeof` deriva la forma estática de `limits` y `keyof` obtiene sus claves permitidas. Un predicado con `Object.hasOwn` acepta únicamente propiedades directas del objeto.", | ||
| "worked_example": "La muestra deriva `tiny | huge`, trata `huge` como texto externo y lo estrecha mediante una comprobación de clave propia antes de indexar.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Tipos utilitarios en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Tipos utilitarios.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Tipos como Pick y Partial expresan transformaciones comunes de objetos sin crear otro tipo auxiliar." | ||
| "Usar `value in limits`, que también acepta nombres heredados como `toString`.", | ||
| "Confundir el operador JavaScript `typeof` con su posición de consulta dentro del sistema de tipos." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Tipos utilitarios antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Tipos utilitarios debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué unión produce `keyof typeof limits`?", | ||
| "¿Por qué `Object.hasOwn` rechaza `constructor` aunque el operador `in` lo encuentre?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Tipos utilitarios a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Añade una guarda de tipos basada en propiedades propias antes de indexar `limits` y devuelve `invalid` fuera de la rama estrechada.", | ||
| "objective": "Resolver los dos tamaños declarados y rechazar claves ausentes o heredadas del prototipo.", | ||
| "language_delta": "`keyof` no existe en Node; `Object.hasOwn` aporta la evidencia JavaScript de que la cadena nombra una propiedad propia.", | ||
| "prediction_prompt": "Compara `Object.hasOwn(limits, \"toString\")` con `\"toString\" in limits`.", | ||
| "transfer_trap": "Algunas operaciones de pertenencia recorren estado heredado; un contrato de claves propias debe excluir expresamente el prototipo." | ||
| }, | ||
| "ts-discriminated-unions": { | ||
| "title": "Uniones discriminadas", | ||
| "concept": "Una etiqueta literal compartida permite que switch estreche cada variante y use campos de rectángulo solo allí. Usa la sintaxis para que el valor en runtime y la forma esperada por TypeScript avancen juntos.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Uniones discriminadas. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-indexed-access": { | ||
| "title": "Reutilizar el tipo de elemento de una secuencia", | ||
| "concept": "`User[\"scores\"][number]` extrae el tipo de elemento ya declarado por el modelo. Reutilizarlo evita escribir otra definición de puntuación que pueda divergir.", | ||
| "worked_example": "El modelo de muestra deriva el elemento de un array de etiquetas y asigna la palabra fija `typed`. Ambas selecciones solo existen durante la comprobación.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Uniones discriminadas en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Uniones discriminadas.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Una etiqueta literal compartida permite que switch estreche cada variante y use campos de rectángulo solo allí." | ||
| "Asignar texto crudo de la entrada a un tipo numérico derivado sin conversión en ejecución.", | ||
| "Repetir `number` manualmente y perder el vínculo con el modelo de origen." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Uniones discriminadas antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Uniones discriminadas debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué tipo resulta de seleccionar primero `scores` e indexarlo después con `number`?", | ||
| "¿En qué fase se informa la incompatibilidad entre cadena y número?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Uniones discriminadas a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Convierte la entrada antes de asignarla al tipo de puntuación obtenido mediante acceso indexado.", | ||
| "objective": "Superar la comprobación estricta y repetir valores positivos, cero y negativos como números.", | ||
| "language_delta": "El acceso indexado calcula un tipo y no emite una consulta; `Number` sigue siendo necesario en la frontera de entrada JavaScript.", | ||
| "prediction_prompt": "Clasifica el fallo inicial como `TypeCheck` antes de considerar cualquier salida.", | ||
| "transfer_trap": "Un esquema de otro ecosistema puede generar validadores; este operador TypeScript solo proporciona comprobación estática." | ||
| }, | ||
| "ts-async-promise": { | ||
| "title": "async y Promise", | ||
| "concept": "Las funciones async devuelven Promise, y await es donde el número resuelto vuelve al flujo normal. En soluciones de stdin/stdout evita el atajo de imprimir algo que ya no coincide con los datos tipados.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de async y Promise. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-mapped-types": { | ||
| "title": "Asignar una bandera a cada funcionalidad", | ||
| "concept": "`Flags` recorre la unión `Feature` y exige una propiedad booleana para cada miembro. Esa totalidad evita que una funcionalidad nueva quede sin configuración.", | ||
| "worked_example": "La demostración transforma `dark | cache` en dos booleanos e informa de la clave activa. Las dos propiedades existen aunque solo una sea verdadera.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de async y Promise en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de async y Promise.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Las funciones async devuelven Promise, y await es donde el número resuelto vuelve al flujo normal." | ||
| "Omitir `share` y esperar que el tipo mapeado la considere opcional.", | ||
| "Imprimir una palabra genérica aunque los casos requieren el nombre de la funcionalidad activada." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica async y Promise antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de async y Promise debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué el objeto incompleto falla antes de que Node lea la entrada?", | ||
| "¿Qué valores tienen ambas banderas cuando el comando es `none`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando async y Promise a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Añade la propiedad mapeada ausente y deriva `search`, `share` o `none` a partir de las dos banderas.", | ||
| "objective": "Exigir cobertura completa durante la comprobación y producir los tres resultados posibles.", | ||
| "language_delta": "Los tipos mapeados generan requisitos para el verificador; no asignan propiedades ni recorren claves al ejecutar Node.", | ||
| "prediction_prompt": "Predice el diagnóstico para un objeto que solo contiene la bandera `search`.", | ||
| "transfer_trap": "Un `Map` de ejecución almacena datos; un tipo mapeado de TypeScript es únicamente una transformación estática." | ||
| }, | ||
| "ts-error-handling": { | ||
| "title": "Manejo de errores", | ||
| "concept": "catch recibe un fallo unknown, así que conviene estrecharlo antes de elegir un valor de recuperación. La anotación de tipo no es adorno: describe qué valores pueden cruzar este límite.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Manejo de errores. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-conditional-types": { | ||
| "title": "Extraer un elemento con un tipo condicional", | ||
| "concept": "`ElementType` comprueba si `T` tiene forma de array y usa `infer` para nombrar su miembro. Con `string[]`, la rama elegida por el verificador es `string`.", | ||
| "worked_example": "La demostración aplica el mismo patrón a `number[]` y asigna once. Ninguna condición de ejecución inspecciona ese valor porque la selección ya fue eliminada.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Manejo de errores en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Manejo de errores.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: catch recibe un fallo unknown, así que conviene estrecharlo antes de elegir un valor de recuperación." | ||
| "Asignar `Number(raw)` aunque el tipo condicional se haya resuelto como cadena.", | ||
| "Esperar que `T extends ...` elija una rama a partir del contenido recibido por stdin." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Manejo de errores antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Manejo de errores debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué tipo representa `Item` si `T` es `readonly string[]`?", | ||
| "¿Puede Node observar qué rama del tipo condicional eligió el verificador?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Manejo de errores a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Asigna el texto recibido al tipo de elemento `string` extraído, sin introducir una conversión numérica.", | ||
| "objective": "Comprobar y repetir tres textos distintos, incluidos caracteres con aspecto numérico.", | ||
| "language_delta": "Los tipos condicionales actúan solo dentro del sistema TypeScript; una expresión condicional JavaScript es sintaxis de ejecución diferente.", | ||
| "prediction_prompt": "Decide si el texto `42` cambia el tipo estático `TextElement` antes de ejecutar.", | ||
| "transfer_trap": "Plantillas de compilación de otros lenguajes pueden generar código; esta selección se borra por completo." | ||
| }, | ||
| "ts-modules": { | ||
| "title": "Módulos y exports", | ||
| "concept": "export marca los valores que un archivo ofrece a otros; Node sigue decidiendo el sistema de módulos. Usa la sintaxis para que el valor en runtime y la forma esperada por TypeScript avancen juntos.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Módulos y exports. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-utility-types": { | ||
| "title": "Actualizar campos elegidos con tipos de utilidad", | ||
| "concept": "`Pick` selecciona `score` de `User` y `Partial` vuelve opcional esa propiedad. Un objeto vacío representa que no hay actualización; no fabrica una puntuación.", | ||
| "worked_example": "La muestra crea una actualización opcional de `active` para otro perfil. La lectura con alternativa booleana muestra que la propiedad puede estar o no presente.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Módulos y exports en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Módulos y exports.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: export marca los valores que un archivo ofrece a otros; Node sigue decidiendo el sistema de módulos." | ||
| "Insertar puntuación uno cuando falta el token en lugar de dejar vacío el objeto de actualización.", | ||
| "Suponer que `Partial` modifica recursivamente miembros anidados o les asigna valores predeterminados." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Módulos y exports antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Módulos y exports debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué objeto debe representar la entrada `Lin` sin puntuación?", | ||
| "¿Qué campo original de `User` excluye la parte `Pick` de `ScorePatch`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Módulos y exports a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Crea una actualización vacía o una puntuación numérica convertida según exista o no el segundo token.", | ||
| "objective": "Informar puntuaciones positivas y negativas y usar cero solo para una propiedad realmente ausente.", | ||
| "language_delta": "Los tipos utilitarios reescriben formas estáticas sin emitir código; la construcción JavaScript determina qué claves existen.", | ||
| "prediction_prompt": "Dibuja los objetos de actualización para `Ada 5` y `Lin` antes de aplicar `??` a `score`.", | ||
| "transfer_trap": "Los campos opcionales de datos serializados todavía requieren análisis y validación aunque `Partial` describa su forma." | ||
| }, | ||
| "ts-classes": { | ||
| "title": "Clases y modificadores", | ||
| "concept": "Las clases unen métodos al estado, y private documenta qué estado debe quedar detrás del límite del método. En soluciones de stdin/stdout evita el atajo de imprimir algo que ya no coincide con los datos tipados.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Clases y modificadores. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-satisfies-as-const": { | ||
| "title": "Comprobar rutas y conservar sus literales", | ||
| "concept": "`as const` conserva las cadenas literales y `satisfies` verifica el contrato `Record` sin sustituir sus tipos. `Object.hasOwn` prueba aparte que el texto externo sea una propiedad directa.", | ||
| "worked_example": "La muestra comprueba dos códigos, estrecha `ready` mediante una clave propia y lee el literal `R`. Tras eliminar los tipos, el objeto sigue siendo datos JavaScript normales.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Clases y modificadores en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Clases y modificadores.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: Las clases unen métodos al estado, y private documenta qué estado debe quedar detrás del límite del método." | ||
| "Usar `in` y aceptar por accidente nombres heredados como `constructor`.", | ||
| "Creer que `as const` ejecuta `Object.freeze` o impide recursivamente la mutación en memoria." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Clases y modificadores antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Clases y modificadores debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿En qué difiere `satisfies` de afirmar `as Record<string, string>`?", | ||
| "¿Por qué `Object.hasOwn` demuestra una condición más fuerte que `in`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Clases y modificadores a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Valida la ruta solicitada como propiedad directa antes de indexar el objeto constante comprobado con `satisfies`.", | ||
| "objective": "Devolver las dos rutas y rechazar nombres desconocidos o heredados del prototipo.", | ||
| "language_delta": "`satisfies` comprueba la compatibilidad sin convertir el tipo; `as const` conserva los tipos literales y marca como de solo lectura, durante la compilación, las propiedades de esa expresión literal, sin congelar el objeto en ejecución; `Object.hasOwn` aporta la prueba real de pertenencia.", | ||
| "prediction_prompt": "Evalúa `Object.hasOwn(routes, \"constructor\")` antes de decidir si se permite indexar.", | ||
| "transfer_trap": "El operador `in` recorre la cadena de prototipos y resulta demasiado amplio para una configuración de claves declaradas." | ||
| }, | ||
| "ts-readonly": { | ||
| "title": "readonly", | ||
| "concept": "readonly permite leer datos de configuración y evita reemplazarlos o mutarlos a través de ese tipo. La anotación de tipo no es adorno: describe qué valores pueden cruzar este límite.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de readonly. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-discriminated-unions": { | ||
| "title": "Punto de control: agotar una unión etiquetada", | ||
| "concept": "Cada variante de `Shape` posee un `kind` literal y sus medidas. Ramificar sobre esa etiqueta estrecha los campos de forma segura; asignar el resto imposible a `never` comprueba la exhaustividad.", | ||
| "worked_example": "La muestra modela éxito o error y devuelve en los dos casos del `switch`. Sus etiquetas ajenas muestran el mismo estrechamiento sin calcular rectángulos ni círculos.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de readonly en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de readonly.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: readonly permite leer datos de configuración y evita reemplazarlos o mutarlos a través de ese tipo." | ||
| "Leer `radius` antes de demostrar que el valor corresponde a la variante circular.", | ||
| "Añadir otra etiqueta y mantener una rama general que oculte silenciosamente el caso faltante." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica readonly antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de readonly debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué tipo exacto queda después de que la rama rectangular haya retornado?", | ||
| "¿Por qué asignar un círculo a `never` provoca el fallo inicial?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando readonly a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Implementa la variante circular y conserva la aserción `never` para proteger futuras ampliaciones de la unión.", | ||
| "objective": "Superar la exhaustividad estricta y medir dos formas etiquetadas en tres casos.", | ||
| "language_delta": "La etiqueta existe y dirige JavaScript; el estrechamiento y la prueba `never` son razonamiento eliminado del verificador.", | ||
| "prediction_prompt": "Sigue `circle 5` desde el análisis y el estrechamiento hasta su retorno numérico.", | ||
| "transfer_trap": "Un comodín de Rust o un `default` amplio también puede esconder variantes nuevas y anular la comprobación exhaustiva." | ||
| }, | ||
| "ts-satisfies-as-const": { | ||
| "title": "satisfies y as const", | ||
| "concept": "as const conserva literales de rutas, mientras satisfies comprueba el contrato Record sin ampliarlos. Usa la sintaxis para que el valor en runtime y la forma esperada por TypeScript avancen juntos.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de satisfies y as const. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-async-promise": { | ||
| "title": "Esperar una promesa tipada", | ||
| "concept": "Una función `async` devuelve siempre una `Promise`, aunque retorne un número. `await` obtiene el número cumplido dentro de `main`, y el `catch` final observa un posible rechazo.", | ||
| "worked_example": "La demostración incrementa nueve de forma asíncrona, espera diez y después llama directamente a `process.stdout.write`. Esa escritura no es un callback de la promesa; ocurre tras el `await`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de satisfies y as const en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de satisfies y as const.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: as const conserva literales de rutas, mientras satisfies comprueba el contrato Record sin ampliarlos." | ||
| "Devolver el número original desde `double` y olvidar la transformación solicitada.", | ||
| "Invocar `main` sin observar el rechazo de la promesa en la frontera superior." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica satisfies y as const antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de satisfies y as const debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Cuál es el retorno estático de `double` aunque su cuerpo no contenga todavía otro `await`?", | ||
| "¿Cuándo se ejecuta la escritura respecto al cumplimiento de la promesa de `double`?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando satisfies y as const a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Haz que `double` resuelva el doble de su argumento y conserva tanto el `await` como el manejo del rechazo en el nivel superior.", | ||
| "objective": "Producir dobles positivos, cero y negativos mediante control asíncrono real.", | ||
| "language_delta": "La planificación de promesas pertenece a JavaScript; TypeScript comprueba `Promise<number>` y errores `unknown`, pero no proporciona un ejecutor.", | ||
| "prediction_prompt": "Predice el valor cumplido que transporta `double` para menos tres.", | ||
| "transfer_trap": "Llamar a una corrutina Python no la ejecuta; invocar esta función JavaScript devuelve de inmediato una cadena de promesas ya iniciada." | ||
| }, | ||
| "ts-iterables": { | ||
| "title": "Iterables", | ||
| "concept": "for...of consume cualquier iterable, así que arreglos, cadenas y sets comparten la misma forma de bucle. En soluciones de stdin/stdout evita el atajo de imprimir algo que ya no coincide con los datos tipados.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de Iterables. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-modules": { | ||
| "title": "Orientar una API pública en un ejecutor de un solo archivo", | ||
| "concept": "Este laboratorio describe una superficie invocable mediante un objeto API local. No afirma probar un grafo de importaciones porque el evaluador solo proporciona un archivo `.ts`.", | ||
| "worked_example": "La muestra expone `transform` mediante `DemoApi` e invierte `module`. El objeto hace visible la frontera sin depender de la resolución de `ESM` o `CommonJS`.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de Iterables en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de Iterables.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: for...of consume cualquier iterable, así que arreglos, cadenas y sets comparten la misma forma de bucle." | ||
| "Suponer que una declaración `export` demuestra por sí sola que otro archivo puede resolver y ejecutar el módulo.", | ||
| "Convertir a minúsculas aunque el comportamiento público prometido exige mayúsculas." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica Iterables antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de Iterables debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Qué firma debe cumplir la implementación local de `LabelApi`?", | ||
| "¿Qué configuración necesitaría Node para comprobar honestamente un ejercicio `ESM` con varios archivos?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando Iterables a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Implementa el comportamiento de la API local declarada y transforma a mayúsculas el texto recibido por la entrada estándar.", | ||
| "objective": "Practicar una superficie pública tipada con texto ASCII y acentuado sin fingir una importación entre archivos.", | ||
| "language_delta": "Node decide entre `ESM` y `CommonJS` mediante extensiones y metadatos; un ejecutor fijo de un solo archivo no puede probar ambas partes de un módulo.", | ||
| "prediction_prompt": "Predice el resultado del método para `é` usando la conversión Unicode a mayúsculas.", | ||
| "transfer_trap": "Navegadores, empaquetadores y `CommonJS` resuelven archivos de manera distinta; superar este laboratorio no equivale a dominar módulos múltiples." | ||
| }, | ||
| "ts-array-methods": { | ||
| "title": "map, filter y reduce", | ||
| "concept": "filter selecciona elementos, map los transforma y reduce pliega la secuencia hasta la respuesta final. La anotación de tipo no es adorno: describe qué valores pueden cruzar este límite.", | ||
| "worked_example": "El ejemplo usa una salida de demostración con la etiqueta `example:` para mostrar el flujo de map, filter y reduce. Sigue qué línea cambia el valor antes de stdout; el ejercicio aplica la misma idea a sus propios datos.", | ||
| "ts-error-handling": { | ||
| "title": "Proyecto final: validar y ejecutar comandos", | ||
| "concept": "El analizador debe validar tokens numéricos completos y finitos, rechazar división por cero y construir un comando discriminado. La ejecución devuelve una promesa y el `catch` estrecha `unknown`.", | ||
| "worked_example": "La muestra prueba que `parseInt` acepta el prefijo de `12px` y que `bad` produce `NaN`, no una excepción. También estrecha una cadena lanzada antes de usarla.", | ||
| "common_mistakes": [ | ||
| "Cambiar solo la línea final de salida sin dejar la regla de map, filter y reduce en la ruta del código.", | ||
| "Copiar la salida de demostración con `example:` en vez de resolver el TODO del ejercicio de map, filter y reduce.", | ||
| "Ajustar solo la salida del juez sin comprobar esta idea: filter selecciona elementos, map los transforma y reduce pliega la secuencia hasta la respuesta final." | ||
| "Usar `parseInt` como validación y aceptar por accidente letras posteriores al número.", | ||
| "Escribir el `catch` como si cualquier valor lanzado fuera siempre una instancia de `Error`." | ||
| ], | ||
| "self_check": [ | ||
| "¿Qué expresión del ejemplo aplica map, filter y reduce antes de llegar a la salida `example:`?", | ||
| "¿Qué línea TODO del ejercicio de map, filter y reduce debe derivar el valor desde sus propios datos y no desde la demostración?" | ||
| "¿Por qué `Number` rechaza `12px` mientras `Number.parseInt` aprovecha una parte?", | ||
| "¿Qué rama comprueba el divisor cero antes de la ejecución asíncrona?" | ||
| ], | ||
| "exercise_prompt": "Completa el TODO aplicando map, filter y reduce a los datos del ejercicio. Mantén stdout limpio para el juez; la etiqueta `example:` pertenece solo al ejemplo." | ||
| "exercise_prompt": "Construye un analizador estricto, ejecuta exhaustivamente la unión de comandos y estrecha desde `unknown` cualquier fallo capturado.", | ||
| "objective": "Calcular comandos válidos de suma y división, y escribir `invalid` para tokens numéricos parciales o no finitos y para la división por cero.", | ||
| "language_delta": "Un `catch` estricto recibe `unknown` porque JavaScript puede lanzar cualquier cosa; `parseInt` devuelve `NaN` para texto totalmente inválido y no lanza.", | ||
| "prediction_prompt": "Sigue `add 12px 1` durante la conversión e identifica la validación exacta que lo rechaza.", | ||
| "transfer_trap": "Los patrones basados solo en excepciones no se trasladan: muchas conversiones JavaScript comunican el fallo con `NaN`." | ||
| } | ||
| } | ||
| } |
@@ -7,422 +7,506 @@ { | ||
| "ts-output": { | ||
| "title": "コンソールと stdout", | ||
| "concept": "コンソール出力はジャッジが比較する契約なので、console.log の改行と process.stdout.write の厳密な出力を分けて考える。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「コンソールと stdout」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "標準出力のバイト列を正確に設計する", | ||
| "concept": "`process.stdout.write`は渡した文字列だけを出力するため、区切り文字と末尾の改行もプログラムの契約です。型検査を行うTypeScriptと、生成後のJavaScriptを実行するNodeの境界もここで確認します。", | ||
| "worked_example": "例は型付きの名前と点数を使い、演習とは異なる`demo Mina=4`を出力します。型注釈はNode実行前に消えますが、テンプレートリテラル内の記号は出力に残ります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「コンソールと stdout」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「コンソールと stdout」演習の TODO を解かないこと。", | ||
| "コンソール出力はジャッジが比較する契約なので、console.log の改行と process.stdout.write の厳密な出力を分けて考える。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "末尾の改行を細かく制御すべき場面で`console.log`を使うことです。", | ||
| "名前と点数を区切らず、`Ada7`のように境界が分からない出力にすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「コンソールと stdout」を適用してから `example:` 出力へつながりますか。", | ||
| "「コンソールと stdout」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "例の名前の直後には、どの文字が出力されますか。", | ||
| "`write`呼び出しのどの部分が行末を作っていますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「コンソールと stdout」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "標準入力の2項目を読み、`process.stdout.write`でコロン区切りの1行へ整形してください。", | ||
| "objective": "全入力で`name:score`の直後に改行をちょうど1つ出力します。", | ||
| "language_delta": "`console.log`と違い、`process.stdout.write`は空白も改行も追加しません。Nodeのモジュール解決や実行は、消去される型注釈とは別の仕組みです。", | ||
| "prediction_prompt": "Unicodeの名前と2桁の点数を渡したときに出力される文字列を、改行まで含めて書いてください。", | ||
| "transfer_trap": "ブラウザーコンソールは調査用に値を表示しますが、採点環境は標準出力の文字列を比較します。同じ用途だと思わないでください。" | ||
| }, | ||
| "ts-let-const": { | ||
| "title": "let と const", | ||
| "concept": "const は再代入しない束縛を示し、let は解法の中で変化する累積値を明確にする。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「let と const」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-input": { | ||
| "title": "Nodeの標準入力をトークン化する", | ||
| "concept": "Nodeではリダイレクトされた標準入力をファイル記述子`0`から読めます。全体を一度で読み、末尾を整え、空文字列を分岐すれば、以降の演習でも安全に数値トークンを作れます。", | ||
| "worked_example": "例は固定文字列`4 6`を分割し、`Number`で変換して、初期値`0`の`reduce`で`10`にします。固定値なので演習の入力ケースを代わりに解くものではありません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「let と const」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「let と const」演習の TODO を解かないこと。", | ||
| "const は再代入しない束縛を示し、let は解法の中で変化する累積値を明確にする。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "空の`trim`済み文字列をそのまま`split`し、唯一の空トークンを`0`へ変換することです。", | ||
| "最初の行だけを読み、複数行に配置された入力値を失うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「let と const」を適用してから `example:` 出力へつながりますか。", | ||
| "「let と const」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`trim`後に1文字もなければ、トークン配列を空にする必要があるのはなぜですか。", | ||
| "`reduce`の初期値は空入力の合計をどのように決めますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「let と const」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "先頭トークンだけを使う処理を、空白で区切られた全数値の合計へ置き換えてください。", | ||
| "objective": "同一行、複数行、空の標準入力について正しい算術和を返します。", | ||
| "language_delta": "`readFileSync(0, \"utf8\")`はNodeの実行時APIです。TypeScriptは宣言された型を検査しますが、入力読み取り自体は行いません。", | ||
| "prediction_prompt": "`-1`の後に改行と`5 2`が続く入力について、トークン配列と合計を予測してください。", | ||
| "transfer_trap": "他言語の対話式読み取り処理を繰り返す設計は、Nodeで標準入力全体を受け取るこの実行環境にはそのまま移せません。" | ||
| }, | ||
| "ts-primitives": { | ||
| "title": "プリミティブ型", | ||
| "concept": "string、number、boolean の注釈は、解析や分岐が期待するスカラー値を表す。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「プリミティブ型」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "title": "文字列をプリミティブ値へ変換する", | ||
| "concept": "`string`注釈は型チェッカーへ値の形を伝え、`Number`が実行時の変換を行います。合否は点数と基準値を文字列の綴りではなく数値として比較する必要があります。", | ||
| "worked_example": "例は`string`、`number`、`boolean`を1つずつ宣言し、別の例レコードを出します。各実行時値は、注釈が示すプリミティブの形と一致しています。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「プリミティブ型」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「プリミティブ型」演習の TODO を解かないこと。", | ||
| "string、number、boolean の注釈は、解析や分岐が期待するスカラー値を表す。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "入力文字列を変換せず、`scoreText`へ`number`注釈だけを付けることです。", | ||
| "暗黙の比較時変換へ頼り、不正な数値文字列が入る境界を見えなくすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「プリミティブ型」を適用してから `example:` 出力へつながりますか。", | ||
| "「プリミティブ型」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "入力トークンを文字列の意味から数値の意味へ変える式はどれですか。", | ||
| "基準値と等しい点数も合格に含める必要があるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「プリミティブ型」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "2つの数値フィールドを解析して包含比較を行い、名前は文字列のまま保持してください。", | ||
| "objective": "境界を含む3ケースで、解析済み点数と正しい`pass`または`retry`を出力します。", | ||
| "language_delta": "TypeScript 5.9はプリミティブの不整合を検出できますが、注釈は消去され、標準入力を変換しません。変換処理は実行時コードとして必要です。", | ||
| "prediction_prompt": "比較演算子を評価する前に、入力`Bo 0 0`の判定を決めてください。", | ||
| "transfer_trap": "実行時の型宣言が変換を起こす言語と異なり、TypeScriptで数値と宣言しただけでは文字列は数値になりません。" | ||
| }, | ||
| "ts-strings-templates": { | ||
| "title": "文字列とテンプレート", | ||
| "concept": "テンプレートリテラルは値の近くで整形し、trim などの文字列メソッドは新しい文字列を返す。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「文字列とテンプレート」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-let-const": { | ||
| "title": "累積値だけを再束縛する", | ||
| "concept": "変わらないラベルには`const`、更新する合計には`let`を使います。どちらを選ぶかは束縛の再代入可否を表し、参照先オブジェクト全体を凍結する指定ではありません。", | ||
| "worked_example": "例は`demo`ラベルを固定し、`let total`を`5`から始めて`-2`を加えます。ラベルを変えずに合計だけが`3`へ再代入されます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「文字列とテンプレート」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「文字列とテンプレート」演習の TODO を解かないこと。", | ||
| "テンプレートリテラルは値の近くで整形し、trim などの文字列メソッドは新しい文字列を返す。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "累積値を`const`で宣言してから、新しい数値を代入しようとすることです。", | ||
| "`const`が可変オブジェクトのプロパティ変更まで禁止すると考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「文字列とテンプレート」を適用してから `example:` 出力へつながりますか。", | ||
| "「文字列とテンプレート」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "例の2つの束縛のうち、2回目の代入を受けるのはどちらですか。", | ||
| "可変オブジェクトを参照する`const settings`で`settings.enabled = false`は可能ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「文字列とテンプレート」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "ラベルを固定したまま、再代入可能な合計へ解析済みの2数を累積してください。", | ||
| "objective": "正、負、`0`の加算でも、指定ラベルと算術結果を保ちます。", | ||
| "language_delta": "JavaScriptは`const`の束縛規則を実行時にも強制しますが、数値の型注釈は別に消去される型チェッカー情報です。", | ||
| "prediction_prompt": "入力`net -2 5`について、各代入後の`total`を順に追ってください。", | ||
| "transfer_trap": "Rustの不変束縛やJavaの`final`参照は名前が似ていても、所有権や参照先変更の規則が同じではありません。" | ||
| }, | ||
| "ts-arrays-tuples": { | ||
| "title": "配列とタプル", | ||
| "concept": "配列は同じ要素型を順序付きで持ち、タプルは小さなレコードの位置と型を固定する。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「配列とタプル」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-strings-templates": { | ||
| "title": "名前だけを`trim`し点数を保つ", | ||
| "concept": "パイプの左右は空白規則が異なるフィールドです。末尾の行終端を1つだけ除き、名前だけを`trim`し、点数側は末尾の空白も含めてそのまま補間へ渡します。", | ||
| "worked_example": "固定レコードの`Mira`には点数の後ろに空白2つと改行があります。正規表現は改行だけを除くため、例でも点数側の2空白が残ります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「配列とタプル」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「配列とタプル」演習の TODO を解かないこと。", | ||
| "配列は同じ要素型を順序付きで持ち、タプルは小さなレコードの位置と型を固定する。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "レコード全体へ`trimEnd`を呼び、意味のある点数の余白まで削除することです。", | ||
| "サロゲートペアがUTF-16で2コードユニットを占めるのに、`string.length`を画面上の文字数とみなすことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「配列とタプル」を適用してから `example:` 出力へつながりますか。", | ||
| "「配列とタプル」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "` Mira |9 `から行終端だけを除いた後、左右にはどの空白が残りますか。", | ||
| "`rawName`だけを`trim`すれば、点数の末尾空白が保たれるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「配列とタプル」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "末尾のCRLFまたはLFを1つ除き、名前側だけを`trim`して、点数は変更せずコロンの後へ挿入してください。", | ||
| "objective": "3レコードすべてで、点数フィールド末尾の意味ある空白を保って整形します。", | ||
| "language_delta": "JavaScriptの`trimEnd`は末尾空白を全て除きますが、ここでの末尾固定正規表現は転送用の行終端1つだけを除きます。", | ||
| "prediction_prompt": "` Ada |7 `の後にLFがある入力へ`/\\r?\\n$/`を適用し、残る空白を全て示してください。", | ||
| "transfer_trap": "他言語の一般的な`strip`や`trim`は、行終端だけでなくペイロードの空白も消す可能性があります。" | ||
| }, | ||
| "ts-objects": { | ||
| "title": "オブジェクト型", | ||
| "concept": "オブジェクト型は必要なフィールドを名前で固定し、width や height などの取り落としを防ぐ。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「オブジェクト型」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-arrays-tuples": { | ||
| "title": "名前と合計をタプルで返す", | ||
| "concept": "先頭トークンがレコード名で、残りは数値配列です。`[string, number]`タプルは、2つの位置が持つ意味を型チェッカーへ固定して伝えます。", | ||
| "worked_example": "例は別の配列`[1, 4]`を合計し、`Neo`と結果を型付き組へ入れます。位置`0`と`1`で読めることから、実行時には通常の配列だと分かります。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「オブジェクト型」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「オブジェクト型」演習の TODO を解かないこと。", | ||
| "オブジェクト型は必要なフィールドを名前で固定し、width や height などの取り落としを防ぐ。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "全トークンを数値として扱い、`reduce`前に先頭の名前を失うことです。", | ||
| "タプル注釈が配列とは異なる実行時コンテナを作ると考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「オブジェクト型」を適用してから `example:` 出力へつながりますか。", | ||
| "「オブジェクト型」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "例組の2番目の位置は、どの型を受け取りますか。", | ||
| "入力が`Bo`だけなら、数値部分の合計をどう扱いますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「オブジェクト型」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "数値部分全体を`reduce`し、名前と合計を宣言済みタプルへ格納してください。", | ||
| "objective": "複数値、負数、数値部分が空の入力を、同じタプル形式で処理します。", | ||
| "language_delta": "タプルの位置検査はNode実行時には消えます。実行時コンテナは配列のままで、型だけが位置の意味を制約します。", | ||
| "prediction_prompt": "`Lin -1 4 2`について、タプルの2番目を計算する前の数値配列を書いてください。", | ||
| "transfer_trap": "値オブジェクトとして不変なタプルを持つ言語と違い、TypeScriptのタプルは実行時には小さな不変レコードではありません。" | ||
| }, | ||
| "ts-functions": { | ||
| "title": "関数", | ||
| "concept": "関数の引数型と戻り値型は、解析済み入力、計算、出力の境界を明確にする。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「関数」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-iterables": { | ||
| "title": "`for...of`で`Iterable`を消費する", | ||
| "concept": "`Iterable<string>`が約束するのは反復プロトコルであり、数値添字ではありません。`for...of`はそのプロトコルから値を受け取るため、配列、`Set`、文字列、独自イテラブルに使えます。", | ||
| "worked_example": "例は`T`と`S`を持つ`Set`を収集処理へ渡します。配列専用メソッドを呼ばず、挿入順の2値を反復して連結します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「関数」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「関数」演習の TODO を解かないこと。", | ||
| "関数の引数型と戻り値型は、解析済み入力、計算、出力の境界を明確にする。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "インターフェースが数値プロパティを保証しないのに、`Iterable`を添字で読むことです。", | ||
| "最後まで進めた同じイテレーターを再利用し、値が再び得られると思うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「関数」を適用してから `example:` 出力へつながりますか。", | ||
| "「関数」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`for...of`は入力にどの能力を要求しますか。", | ||
| "トークンが空でも、出力に改行が1つ現れるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「関数」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "標準入力の全トークンを反復し、出現順のまま連結してください。", | ||
| "objective": "2個、3個、`0`個のトークンを、配列添字へ依存せず結合します。", | ||
| "language_delta": "TypeScriptの`Iterable`型はJavaScriptの反復プロトコルを説明するだけで、コレクション化や再生機能を実行時に追加しません。", | ||
| "prediction_prompt": "`a b c`のイテレーター状態を順に進め、各段階の累積文字列を予測してください。", | ||
| "transfer_trap": "PythonのイテレーターやJavaのストリームも消費されますが、再利用と所有権の細部まで同一だとは考えないでください。" | ||
| }, | ||
| "ts-input": { | ||
| "title": "Node stdin の解析", | ||
| "concept": "Node の解法では通常、ファイル記述子 0 を一度読み、空白で分割してから数値へ変換する。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「Node stdin の解析」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-array-methods": { | ||
| "title": "選別、平方、初期値付き`reduce`", | ||
| "concept": "読みやすいパイプラインは、まず偶数を選び、次に平方し、最後に積み上げます。`reduce`へ初期値`0`を渡せば、選択結果が空でも合計が定義されます。", | ||
| "worked_example": "例は`[2, 5, 6]`へ別の条件を使い、残った値を2倍して合計します。メソッドの順序は示しますが、演習の答えは使っていません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「Node stdin の解析」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「Node stdin の解析」演習の TODO を解かないこと。", | ||
| "Node の解法では通常、ファイル記述子 0 を一度読み、空白で分割してから数値へ変換する。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "条件が元の値を調べるべきなのに、先に`map`で変換することです。", | ||
| "`reduce`の初期値を省き、偶数が1つもないと例外にすることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「Node stdin の解析」を適用してから `example:` 出力へつながりますか。", | ||
| "「Node stdin の解析」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`-2`が正の`4`になるのはパイプラインのどの段階ですか。", | ||
| "`filter`後の配列が空なら、`reduce`へ最初に入る値は何ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「Node stdin の解析」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "平方処理と初期値`0`の畳み込みを保ったまま、偶奇の条件を修正してください。", | ||
| "objective": "符号混在と全て奇数のトークン列で、偶数平方の合計を求めます。", | ||
| "language_delta": "これらはJavaScript配列の即時操作です。TypeScriptはコールバック型を推論しますが、パイプラインを遅延評価にはしません。", | ||
| "prediction_prompt": "`-2 -1 0`から、合計前に各段階で作られる配列を書いてください。", | ||
| "transfer_trap": "Rustのイテレーターアダプターは消費処理まで遅延しますが、JavaScriptの配列メソッドは各段階で走査や配列作成を行います。" | ||
| }, | ||
| "ts-control-flow": { | ||
| "title": "制御フロー", | ||
| "concept": "if とループはどの値を蓄積するかを決めるため、条件は意図したデータ規則と一致する必要がある。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「制御フロー」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-objects": { | ||
| "title": "構造的オブジェクトで面積を計算する", | ||
| "concept": "`Rectangle`型は`width`と`height`を要求し、`area`はその形に合うオブジェクトを受け取ります。クラス宣言や同じコンストラクター由来であることは、この計算に不要です。", | ||
| "worked_example": "例の`painted`には余分な`color`がありますが、2メンバーを要求する`DemoSize`の関数へ渡せます。余分なフィールドは実行時に残り、単に読まれません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「制御フロー」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「制御フロー」演習の TODO を解かないこと。", | ||
| "if とループはどの値を蓄積するかを決めるため、条件は意図したデータ規則と一致する必要がある。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "解析した一方の寸法をオブジェクトへ入れず、仮の値で置き換えることです。", | ||
| "同じコンストラクター名を持たなければ、TypeScriptが互換な形と認めないと考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「制御フロー」を適用してから `example:` 出力へつながりますか。", | ||
| "「制御フロー」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`demoArea`は`painted`のどのメンバーを実際に読みますか。", | ||
| "`width`が`0`なら面積も`0`になるのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「制御フロー」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "標準入力の2寸法から`Rectangle`を作り、型付きの面積関数へ渡してください。", | ||
| "objective": "通常、幅`0`、高さ`1`の長方形を構造的データで計算します。", | ||
| "language_delta": "オブジェクト型はNode実行前に消える型チェッカー記述です。クラスは実行時メソッドを作り、`readonly`は型を通した変更だけを浅く制限します。", | ||
| "prediction_prompt": "寸法`7 1`から作られるオブジェクトと掛け算の結果を予測してください。", | ||
| "transfer_trap": "Javaでは宣言されたクラス同一性を重視しますが、TypeScriptは独立に作られた値でもメンバーが合えば通常受け入れます。" | ||
| }, | ||
| "ts-union-narrowing": { | ||
| "title": "ユニオンと絞り込み", | ||
| "concept": "ユニオン値は、文字列専用や数値専用の操作の前に実行時チェックで絞り込む必要がある。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「ユニオンと絞り込み」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-classes": { | ||
| "title": "クラスメソッドで状態を更新する", | ||
| "concept": "`Counter`インスタンスは可変状態を所有し、`increment`操作を公開します。メソッドは新しい値を返す前にフィールド自体を更新し、後の呼び出しから変更が見えるようにします。", | ||
| "worked_example": "例クラスは`8`から始まり、1回の増加で`9`になります。`private`はソースへのアクセスを型チェッカーで制限しますが、生成後のフィールドは通常のJavaScriptプロパティです。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「ユニオンと絞り込み」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「ユニオンと絞り込み」演習の TODO を解かないこと。", | ||
| "ユニオン値は、文字列専用や数値専用の操作の前に実行時チェックで絞り込む必要がある。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`this.value + 1`を返すだけで、新しい状態をフィールドへ保存しないことです。", | ||
| "TypeScriptの`private`とJavaScriptの`#value`を同じ実行時機構だと考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「ユニオンと絞り込み」を適用してから `example:` 出力へつながりますか。", | ||
| "「ユニオンと絞り込み」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "同じ`DemoCounter`へ2回メソッドを呼ぶと、2回目は何を返すべきですか。", | ||
| "JavaScript生成後も実行時のアクセス制限として残る`private`フィールド構文はどれですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「ユニオンと絞り込み」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "`increment`がインスタンスフィールドを更新し、更新後のカウンター値を返すよう修正してください。", | ||
| "objective": "正、負、大きな開始値の全てを正確に1増やします。", | ||
| "language_delta": "ここでの`private`キーワードは消去される型チェッカー情報です。ECMAScriptの`#`付きフィールドは実行時にも可視性を持ちます。", | ||
| "prediction_prompt": "開始値が`-1`の呼び出し前後で、保存されたフィールドを追跡してください。", | ||
| "transfer_trap": "Javaの`private`アクセスはJVMにも強制されるため、TypeScriptの消去される修飾子より実行時境界が強くなります。" | ||
| }, | ||
| "ts-literal-types": { | ||
| "title": "リテラル型", | ||
| "concept": "リテラルユニオンは、コマンドやモードを 'left' や 'right' のような正確な値に制限する。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「リテラル型」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-readonly": { | ||
| "title": "浅い`readonly`設定を読む", | ||
| "concept": "`readonly Config`を通すと、名前と点数列を変更せず読めます。プロパティへの`readonly`だけでは配列の置換しか防がないため、内側の配列型にも`readonly`が必要です。", | ||
| "worked_example": "例は`readonly`プロパティと`readonly`数値配列を宣言し、長さだけを読みます。書き込みを試みないため厳格検査を通り、消去後のNode実行も成功します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「リテラル型」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「リテラル型」演習の TODO を解かないこと。", | ||
| "リテラルユニオンは、コマンドやモードを 'left' や 'right' のような正確な値に制限する。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`readonly`数値配列へ`push`し、厳格TypeScriptが許可すると期待することです。", | ||
| "`readonly`が`Object.freeze`を呼び、入れ子オブジェクトも再帰的に凍結すると考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「リテラル型」を適用してから `example:` 出力へつながりますか。", | ||
| "「リテラル型」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "初期コードが入力をNodeへ渡す前に失敗するのはなぜですか。", | ||
| "同じ元配列への可変エイリアスが別にあれば、そこから`push`できますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「リテラル型」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "禁止された変更を除き、`readonly`表示から点数の合計を計算してください。", | ||
| "objective": "厳格検査を通過し、各設定名と点数合計を報告します。", | ||
| "language_delta": "`readonly`は浅い型レベルの制約です。入れ子にも`readonly`型を付けない限り深くならず、実行時に`Object.freeze`のような凍結は行いません。", | ||
| "prediction_prompt": "標準出力ではなく、`config.scores.push(0)`に対するコンパイラ診断を予測してください。", | ||
| "transfer_trap": "Rustの不変借用は所有権規則により変更を防ぎます。消去されるTypeScriptの`readonly`表示と同一ではありません。" | ||
| }, | ||
| "ts-optional-nullish": { | ||
| "title": "オプショナルと nullish", | ||
| "concept": "オプショナルフィールドは undefined になり得て、?? は点数 0 のような有効な falsy 値を保つ。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「オプショナルと nullish」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-control-flow": { | ||
| "title": "ループの包含上限を保つ", | ||
| "concept": "ループは`1`から`n`までを訪れ、厳格な剰余比較で奇数を選びます。終端値を含むかどうかは、ループ条件の関係演算子が決めます。", | ||
| "worked_example": "例は`5`までを訪れますが、演習とは別に偶数を合計します。簡潔な分岐でも厳格等値性と包含上限を使っています。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「オプショナルと nullish」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「オプショナルと nullish」演習の TODO を解かないこと。", | ||
| "オプショナルフィールドは undefined になり得て、?? という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`< n`を使い、含めるべき奇数の上限を落とすことです。", | ||
| "緩い等値性を使い、JavaScriptの型変換で無関係な値まで等しいと判定することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「オプショナルと nullish」を適用してから `example:` 出力へつながりますか。", | ||
| "「オプショナルと nullish」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`n`が`3`なら、最後に合計へ寄与するループ値は何ですか。", | ||
| "誤った初期コードでも`6`ケースが通り、別ケースが境界不具合を示すのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「オプショナルと nullish」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "奇数分岐と累積値を保ったまま、ループの上限条件を修正してください。", | ||
| "objective": "正の上限と`0`について、包含範囲の全奇数を合計します。", | ||
| "language_delta": "JavaScript条件は実行時真偽値と変換規則を使い、TypeScriptの制御フロー解析は証明済み分岐に沿って型を別途絞ります。", | ||
| "prediction_prompt": "`n`が`3`のとき、`<`と`<=`で訪れる値をそれぞれ列挙してください。", | ||
| "transfer_trap": "Pythonの`range`は停止値を含まないため、そのループを直訳するとTypeScriptでも終端を落としてしまいます。" | ||
| }, | ||
| "ts-interfaces-aliases": { | ||
| "title": "インターフェイスと型エイリアス", | ||
| "concept": "インターフェイスはオブジェクト契約に名前を付け、型エイリアスはユニオンやタプルにも名前を付けられる。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「インターフェイスと型エイリアス」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-functions": { | ||
| "title": "チェックポイント:型付き計算境界", | ||
| "concept": "関数シグネチャは受け取る入力と返す出力の両方を約束します。初期コードは数値の掛け算で`string`返り値を満たそうとするため、厳格検査で拒否されるべきです。", | ||
| "worked_example": "例は2つの数値仮引数から周長を返す別関数です。固定寸法の例値を使うため、採点対象の面積ケースとは重なりません。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「インターフェイスと型エイリアス」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「インターフェイスと型エイリアス」演習の TODO を解かないこと。", | ||
| "インターフェイスはオブジェクト契約に名前を付け、型エイリアスはユニオンやタプルにも名前を付けられる。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "誤った返り値注釈に合わせるため、掛け算の結果を文字列へ変えることです。", | ||
| "計算関数内で出力し、データを返すことと標準出力を書くことを混ぜることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「インターフェイスと型エイリアス」を適用してから `example:` 出力へつながりますか。", | ||
| "「インターフェイスと型エイリアス」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`area`が約束する返り値型を決める行はどれですか。", | ||
| "解析した2トークンが、掛け算用の数値実引数になるのはどの時点ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「インターフェイスと型エイリアス」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "数値計算を厳格型チェッカーが受け入れるよう、関数の返り値契約を修正してください。", | ||
| "objective": "型チェッカーを通過し、1つの注釈付き関数で3つの長方形の面積を計算します。", | ||
| "language_delta": "TypeScriptは実行前に仮引数と結果の使い方を検査しますが、生成後の関数内ループや分岐は通常のJavaScriptです。", | ||
| "prediction_prompt": "各ケースの出力を考える前に、初期コードの失敗分類が`TypeCheck`になる理由を予測してください。", | ||
| "transfer_trap": "実行時シグネチャを持つ言語と異なり、TypeScriptの契約は検査後に消去されます。修正すべきなのは計算ではなく誤った型の場合もあります。" | ||
| }, | ||
| "ts-generics": { | ||
| "title": "ジェネリクス", | ||
| "concept": "ジェネリクスは、再利用コードが配列から読み取る時や値を返す時に呼び出し側の要素型を保つ。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「ジェネリクス」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-union-narrowing": { | ||
| "title": "ユニオンを絞ってから整形する", | ||
| "concept": "`string | number`で安全に直接使えるのは両メンバー共通の操作だけです。`typeof`分岐で実行時プリミティブを証明すると、それぞれ固有の書式メソッドを呼べます。", | ||
| "worked_example": "例は文字列を小文字にし、固定数値を小数1桁にします。演習の大文字化や整数整形とは別の処理で、ユニオンの両分岐を示しています。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「ジェネリクス」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「ジェネリクス」演習の TODO を解かないこと。", | ||
| "ジェネリクスは、再利用コードが配列から読み取る時や値を返す時に呼び出し側の要素型を保つ。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "数値の可能性があるユニオンへ直接`toUpperCase`を呼ぶことです。", | ||
| "入力の種別トークンをキャストとして使い、実際の値を構築して絞る処理を省くことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「ジェネリクス」を適用してから `example:` 出力へつながりますか。", | ||
| "「ジェネリクス」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`Number(text)`で作った数値メンバーへ`typeof`を使うと何を返しますか。", | ||
| "`toFixed(0)`が`-1.6`を`-2`へ整形するのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「ジェネリクス」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "実行時の絞り込み分岐を加え、各ユニオンメンバーへ適切な書式処理を適用してください。", | ||
| "objective": "厳格検査を満たしながら、文字列コマンドを大文字化し、数値コマンドを丸めます。", | ||
| "language_delta": "絞り込みはJavaScriptの実行時検査と型チェッカーの推論を組み合わせます。任意の標準入力をリテラルユニオンへ入れる前にも別の検証が必要です。", | ||
| "prediction_prompt": "`n -1.6`が選ぶ分岐と、整形後の文字列を実行前に決めてください。", | ||
| "transfer_trap": "Pythonは危険なメソッド呼び出しも実行時まで許しますが、TypeScriptはユニオンに安全でない操作を型チェッカー段階で拒否します。" | ||
| }, | ||
| "ts-keyof-typeof": { | ||
| "title": "keyof と typeof", | ||
| "concept": "typeof は値の静的な形を取り出し、keyof はその形を許可されたキーのユニオンに変える。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「keyof と typeof」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-literal-types": { | ||
| "title": "リテラルユニオンへ入れる前に検証する", | ||
| "concept": "`Direction`は2つの文字列リテラルだけを含みます。一方、標準入力は制限のない文字列なので、`turn`へ安全に渡す前に実行時比較でメンバーだと証明します。", | ||
| "worked_example": "例は別の`north | south`ユニオンへ既知のリテラルを直接渡します。外部データを検証せず、例用の矢印ラベルを作るだけです。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「keyof と typeof」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「keyof と typeof」演習の TODO を解かないこと。", | ||
| "typeof は値の静的な形を取り出し、keyof はその形を許可されたキーのユニオンに変える。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "任意の文字列を`as Direction`でキャストし、実行時検査も済んだと思うことです。", | ||
| "2択の`turn`で未知の方向を全て右方向として処理することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「keyof と typeof」を適用してから `example:` 出力へつながりますか。", | ||
| "「keyof と typeof」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`raw as Direction`なら、なぜ`up`も実行時に`turn`へ到達できますか。", | ||
| "許可された2文字列を確認した分岐内で、`raw`はどの型へ絞られますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「keyof と typeof」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "標準入力が`Direction`の2メンバーのどちらかを確認し、それ以外は別に`invalid`として報告してください。", | ||
| "objective": "`left`と`right`を短縮形へ変換し、未知のコマンドを拒否します。", | ||
| "language_delta": "リテラルユニオンは検査対象ソースを制限しますが、型消去後のJavaScriptには通常の文字列しかないため、明示的な所属判定検査が必要です。", | ||
| "prediction_prompt": "誤った初期コードへ`up`を渡したときの分岐を予測し、必要な拒否経路と比較してください。", | ||
| "transfer_trap": "他言語の閉じた`enum`は構築時に値を検証する場合がありますが、TypeScriptの文字列ユニオンは実行時コンテナを作りません。" | ||
| }, | ||
| "ts-indexed-access": { | ||
| "title": "インデックスアクセス型", | ||
| "concept": "インデックスアクセス型は、既存モデルのプロパティ型や要素型を手書きで重複させず再利用する。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「インデックスアクセス型」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-optional-nullish": { | ||
| "title": "`null`または`undefined`の点数だけを補う", | ||
| "concept": "解析済み点数は`number | null`です。`null`合体演算子`??`は`null`または`undefined`の場合だけ`10`を補い、意図された数値`0`はそのまま保ちます。", | ||
| "worked_example": "例は任意個数へ`0`を入れて`?? 8`を適用し、`0`を出力します。演習とは別の名前と代替値を使いながら、不在だけを補う規則を示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「インデックスアクセス型」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「インデックスアクセス型」演習の TODO を解かないこと。", | ||
| "インデックスアクセス型は、既存モデルのプロパティ型や要素型を手書きで重複させず再利用する。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "論理和演算子`||`を使い、偽値である正当な`0`まで代替値へ置き換えることです。", | ||
| "文字列`none`を解析せず、文字列の不在印と数値計算を混ぜることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「インデックスアクセス型」を適用してから `example:` 出力へつながりますか。", | ||
| "「インデックスアクセス型」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`??`の右辺を評価させる値は何ですか。", | ||
| "`scoreText`が`none`なら、実行時にどの値へ解析しますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「インデックスアクセス型」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "明示された`0`を保ち、不在の場合だけ`10`にするよう`null`系既定値へ修正してください。", | ||
| "objective": "名前と点数の出力で、`0`、`null`、正の点数を区別します。", | ||
| "language_delta": "`strictNullChecks`は型ユニオンへ`null`を明示し、実際に代替値を選ぶ意味はJavaScriptの`??`演算子が提供します。", | ||
| "prediction_prompt": "入力`Ada 0`について、既定値式の左辺と最終値を実行前に評価してください。", | ||
| "transfer_trap": "Pythonの`or`とJavaScriptの`||`は広い偽値規則を使うため、`null`合体演算子`??`の代用にはなりません。" | ||
| }, | ||
| "ts-mapped-types": { | ||
| "title": "マップ型", | ||
| "concept": "マップ型はオブジェクト型のすべてのキーを変換し、派生形を元の型と同期させる。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「マップ型」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-interfaces-aliases": { | ||
| "title": "構造的な契約を合成する", | ||
| "concept": "`Named`はインターフェース、`Scored`は型エイリアスで、交差型の`Entry`は両方のフィールドを要求します。コンストラクターの系譜ではなく、メンバーが揃うことで受け入れられます。", | ||
| "worked_example": "例は別の`Tagged`と`Weighted`を交差型で組み合わせ、互換オブジェクトを1つ作ります。タグと重みを読むことで、採点モデルを複製せず合成を示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「マップ型」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「マップ型」演習の TODO を解かないこと。", | ||
| "マップ型はオブジェクト型のすべてのキーを変換し、派生形を元の型と同期させる。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "交差型を構築するとき解析済み点数へ`1`を加え、入力データを変えてしまうことです。", | ||
| "インターフェースと同じ形のオブジェクトエイリアスが、異なる実行時種別を作ると思うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「マップ型」を適用してから `example:` 出力へつながりますか。", | ||
| "「マップ型」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "値が`Entry`を満たすには、どの2プロパティが必要ですか。", | ||
| "インターフェースでは直接表せないユニオンを型エイリアスなら名付けられますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「マップ型」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "解析した名前と数値点を変更せず、両契約の交差型オブジェクトを作ってください。", | ||
| "objective": "構造的に型付けされた1つのオブジェクトを構築して出力し、正、`0`、負の点数をそのまま保ちます。", | ||
| "language_delta": "インターフェースは宣言結合ができ、エイリアスはユニオンを表せますが、どちらも実行時には消えます。クラスや`readonly`は別の性質を加えます。", | ||
| "prediction_prompt": "`Bo -2`から作る`Entry`オブジェクトを、補間前に書いてください。", | ||
| "transfer_trap": "Javaのインターフェースは公称的な宣言とJVMメタデータに関わりますが、TypeScriptのインターフェースは消去される構造的契約です。" | ||
| }, | ||
| "ts-conditional-types": { | ||
| "title": "条件型", | ||
| "concept": "条件型は型の関係から分岐を選び、infer は一致した部分を取り出す。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「条件型」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-generics": { | ||
| "title": "`first`で要素型を保つ", | ||
| "concept": "`first<T>`は呼び出し側の要素型を保ち、要素がなければ`undefined`を返します。空を返り値のユニオンで表すため、`any`も非`null`アサーションも不要です。", | ||
| "worked_example": "例はジェネリックな`last`を定義し、3つの文字列で呼びます。反対側の端を選びながら、返り値が`string | undefined`と推論されることを示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「条件型」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「条件型」演習の TODO を解かないこと。", | ||
| "条件型は型の関係から分岐を選び、infer は一致した部分を取り出す。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "添字`1`を返し、先頭要素と2番目の要素を取り違えることです。", | ||
| "空の`readonly`配列も正当な入力なのに、`!`で値の存在を強制することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「条件型」を適用してから `example:` 出力へつながりますか。", | ||
| "「条件型」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "文字列配列を`first`へ渡すと`T`は何になりますか。", | ||
| "一部のケースが非空でも、返り値に`undefined`が必要なのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「条件型」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "ジェネリック補助関数から添字`0`を返し、呼び出し側の明示的な空代替値を保ってください。", | ||
| "objective": "2つの非空入力では先頭トークンを、空入力では`none`を選びます。", | ||
| "language_delta": "ジェネリックは型チェッカー内で型関係を保ち、Node実行前に消えます。`keyof`やインデックス付きアクセスなどは、この関係を別の型から導出します。", | ||
| "prediction_prompt": "1要素のトークン配列を渡した場合の推論型と実行時値を予測してください。", | ||
| "transfer_trap": "Javaジェネリックの型消去と変性、Rustの単相化は異なります。同じ山括弧から同じ実装方式を推測しないでください。" | ||
| }, | ||
| "ts-utility-types": { | ||
| "title": "ユーティリティ型", | ||
| "concept": "Pick や Partial などのユーティリティ型は、新しい補助型を作らず一般的なオブジェクト変換を表す。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「ユーティリティ型」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-keyof-typeof": { | ||
| "title": "実行時キーを`keyof`へ絞る", | ||
| "concept": "型位置の`typeof`は`limits`の静的な形を導き、`keyof`は許可されたプロパティ名のユニオンを作ります。`Object.hasOwn`を使う述語なら、オブジェクト自身のキーだけを受け入れます。", | ||
| "worked_example": "例はキーのユニオンを導き、外部から得た文字列`huge`が`limits`自身のプロパティ名かを`Object.hasOwn`で検査してから、そのキーで`limits`を添字参照します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「ユーティリティ型」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「ユーティリティ型」演習の TODO を解かないこと。", | ||
| "Pick や Partial などのユーティリティ型は、新しい補助型を作らず一般的なオブジェクト変換を表す。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`value in limits`を使い、`toString`のようなプロトタイプ由来の名前まで受け入れることです。", | ||
| "JavaScriptの値段階の`typeof`と、TypeScriptの型問い合わせ位置を混同することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「ユーティリティ型」を適用してから `example:` 出力へつながりますか。", | ||
| "「ユーティリティ型」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`keyof typeof limits`から、どのユニオンが作られますか。", | ||
| "`in`では見える`constructor`を`Object.hasOwn`が拒否するのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「ユーティリティ型」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "`limits`を添字参照する前に自身のプロパティ型ガードを追加し、それ以外は`invalid`を選んでください。", | ||
| "objective": "宣言された2つのサイズを正しく取得し、不在キーとプロトタイプ由来のキーを拒否します。", | ||
| "language_delta": "`keyof`にはNode上の実体がありません。JavaScriptの`Object.hasOwn`が、文字列が直接プロパティであるという実行時証拠を与えます。", | ||
| "prediction_prompt": "`Object.hasOwn(limits, \"toString\")`と`\"toString\" in limits`を比較してから分岐を選んでください。", | ||
| "transfer_trap": "辞書の所属判定が継承状態までたどる環境では、自身のキー要件を満たすためプロトタイプ連鎖を明示的に除外する必要があります。" | ||
| }, | ||
| "ts-discriminated-unions": { | ||
| "title": "判別可能ユニオン", | ||
| "concept": "共通のリテラルタグにより switch が各バリアントを絞り込み、長方形のフィールドを長方形だけで使える。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「判別可能ユニオン」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-indexed-access": { | ||
| "title": "配列要素型を再利用する", | ||
| "concept": "`User[\"scores\"][number]`はモデルに宣言済みの要素型を抽出します。別の点数型を手書きしないため、元モデルが変わったときの不一致を防げます。", | ||
| "worked_example": "例は文字列配列からタグ要素型を導き、固定語`typed`を代入します。どちらのインデックス付き選択も型チェッカー内だけに存在します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「判別可能ユニオン」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「判別可能ユニオン」演習の TODO を解かないこと。", | ||
| "共通のリテラルタグにより switch が各バリアントを絞り込み、長方形のフィールドを長方形だけで使える。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "実行時変換をせず、生の標準入力文字列を導出済みの`number`型へ代入することです。", | ||
| "各所へ`number`を重複記述し、ソースモデルとの関係を失うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「判別可能ユニオン」を適用してから `example:` 出力へつながりますか。", | ||
| "「判別可能ユニオン」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "最初に`scores`を選び、次に`number`で添字指定するとどの型になりますか。", | ||
| "初期コードの文字列から数値への不一致は、どの段階で報告されますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「判別可能ユニオン」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "インデックスアクセスで得た点数型へ代入する前に、標準入力を数値へ変換してください。", | ||
| "objective": "厳格検査を通過し、正、`0`、負の数値をそのまま出力します。", | ||
| "language_delta": "インデックス付きアクセスは静的型だけを計算し、実行時検索コードを出しません。JavaScript入力境界を越えるには`Number`が引き続き必要です。", | ||
| "prediction_prompt": "標準出力ケースを見る前に、初期コードの失敗を`TypeCheck`へ分類してください。", | ||
| "transfer_trap": "他環境のスキーマ由来型が実行時検証器も生成しても、このTypeScript演算子は型チェッカー情報だけを提供します。" | ||
| }, | ||
| "ts-async-promise": { | ||
| "title": "async と Promise", | ||
| "concept": "async 関数は Promise を返し、await の地点で解決済みの数値が通常の流れに戻る。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「async と Promise」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-mapped-types": { | ||
| "title": "全機能キーをフラグへ写す", | ||
| "concept": "`Flags`は`Feature`ユニオンの各メンバーを反復し、全キーに真偽値プロパティを要求します。新しい機能を追加したとき、設定だけが黙って欠けることを防ぎます。", | ||
| "worked_example": "例は`dark | cache`を2つの真偽値へ写し、有効な例キーを報告します。真なのは1つでも、要求されるキーは両方存在します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「async と Promise」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「async と Promise」演習の TODO を解かないこと。", | ||
| "async 関数は Promise を返し、await の地点で解決済みの数値が通常の流れに戻る。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`share`を省き、マップされた型が自動的に任意扱いすると考えることです。", | ||
| "ケースが実際の有効機能名を求めているのに、一般的な成功語だけを出力することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「async と Promise」を適用してから `example:` 出力へつながりますか。", | ||
| "「async と Promise」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "不完全な初期コードがNodeで入力を読む前に失敗するのはなぜですか。", | ||
| "入力が`none`なら2つの真偽値はそれぞれ何ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「async と Promise」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "欠けたマップされたプロパティを追加し、2つのフラグから`search`、`share`、`none`を導いてください。", | ||
| "objective": "型チェッカーに全機能の網羅を強制させ、実行時の3結果も正しく作ります。", | ||
| "language_delta": "マップされた型は型チェッカー用のオブジェクト要件を生成しますが、Node実行中にプロパティを作ったりキーを反復したりしません。", | ||
| "prediction_prompt": "`search`だけを含むオブジェクトに対し、どの`TypeCheck`診断が出るか予測してください。", | ||
| "transfer_trap": "実行時の`Map`や辞書はデータ構造ですが、TypeScriptのマップされた型はコンパイル時変換です。" | ||
| }, | ||
| "ts-error-handling": { | ||
| "title": "エラー処理", | ||
| "concept": "catch は unknown の失敗値を受け取るため、回復値を選ぶ前に絞り込む必要がある。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「エラー処理」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-conditional-types": { | ||
| "title": "条件型で要素を抽出する", | ||
| "concept": "`ElementType`は`T`が配列状かを調べ、`infer`でそのメンバーへ名前を付けます。`string[]`なら型チェッカーが選ぶ分岐は`string`です。", | ||
| "worked_example": "例は同じパターンを`number[]`へ適用し、`11`を代入します。実行時条件は値を調べず、型の選択は既に消去されています。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「エラー処理」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「エラー処理」演習の TODO を解かないこと。", | ||
| "catch は unknown の失敗値を受け取るため、回復値を選ぶ前に絞り込む必要がある。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "条件型が`string`へ解決した後で`Number(raw)`を代入することです。", | ||
| "`T extends ...`が標準入力の内容に応じて実行時分岐を選ぶと考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「エラー処理」を適用してから `example:` 出力へつながりますか。", | ||
| "「エラー処理」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`T`が`readonly string[]`なら`Item`はどの型ですか。", | ||
| "型チェッカーが選んだ条件分岐をNodeから観察できますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「エラー処理」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "数値変換をせず、受け取った文字列を抽出済みの文字列要素型へ代入してください。", | ||
| "objective": "数値に見える文字を含む3つの異なる文字列を、検査を通してそのまま再出力します。", | ||
| "language_delta": "条件型はTypeScriptの型システム内だけで動きます。JavaScriptの条件式は別の、出力される実行時構文です。", | ||
| "prediction_prompt": "文字列`42`の内容が静的な`TextElement`型を変えるか、実行前に判断してください。", | ||
| "transfer_trap": "他言語のコンパイル時テンプレートや`trait`が実行コードを生成しても、この条件選択は完全に消去されます。" | ||
| }, | ||
| "ts-modules": { | ||
| "title": "モジュールと export", | ||
| "concept": "export はファイルが他のファイルへ提供する値を示し、モジュール方式は Node の規則で決まる。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「モジュールと export」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-utility-types": { | ||
| "title": "ユーティリティ型で選択フィールドをパッチする", | ||
| "concept": "`Pick`は`User`から`score`を選び、`Partial`はそのプロパティを任意にします。空のパッチは更新なしを表し、点数値を勝手に作りません。", | ||
| "worked_example": "例は別プロファイルから`active`だけを任意パッチにします。真偽値代替値で読むことで、実行時オブジェクトにプロパティがある場合とない場合を示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「モジュールと export」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「モジュールと export」演習の TODO を解かないこと。", | ||
| "export はファイルが他のファイルへ提供する値を示し、モジュール方式は Node の規則で決まる。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "点数トークンがないとき、空パッチの代わりに`score: 1`を挿入することです。", | ||
| "`Partial`が入れ子プロパティも再帰的に変え、既定値まで補うと考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「モジュールと export」を適用してから `example:` 出力へつながりますか。", | ||
| "「モジュールと export」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "点数トークンがない`Lin`入力は、どのオブジェクトで表すべきですか。", | ||
| "`ScorePatch`の`Pick`部分が元の`User`から除外するフィールドは何ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「モジュールと export」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "トークンの有無に応じて、空の点数パッチか、数値変換した点数を持つパッチを作ってください。", | ||
| "objective": "指定された正負の点数を報告し、本当に不在のプロパティだけを`0`へ既定値します。", | ||
| "language_delta": "ユーティリティ型は静的なオブジェクト形を変換するだけでコードを出しません。実行時にどのキーが存在するかはJavaScriptオブジェクトの構築が決めます。", | ||
| "prediction_prompt": "`Ada 5`と`Lin`それぞれのパッチオブジェクトを、点数へ既定値を適用する前に描いてください。", | ||
| "transfer_trap": "直列化されたデータの任意フィールドは、TypeScriptの`Partial`で表しても実行時の解析と検証が別途必要です。" | ||
| }, | ||
| "ts-classes": { | ||
| "title": "クラスとアクセス修飾子", | ||
| "concept": "クラスは状態にメソッドを結び付け、private はメソッド境界の内側に留める状態を示す。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「クラスとアクセス修飾子」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-satisfies-as-const": { | ||
| "title": "リテラルを保ったままルートを検査する", | ||
| "concept": "`as const`はルート文字列をリテラル型のまま保ち、`satisfies`はその型を広げず`Record`契約への適合を検査します。外部文字列が直接プロパティかは`Object.hasOwn`で別に証明します。", | ||
| "worked_example": "例は2つの状態コードを検査し、外部から得た文字列`ready`が`demoStatuses`自身のプロパティ名かを`Object.hasOwn`で検査してから、保たれたリテラル`R`を読みます。型消去後もオブジェクトは通常のJavaScriptデータです。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「クラスとアクセス修飾子」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「クラスとアクセス修飾子」演習の TODO を解かないこと。", | ||
| "クラスは状態にメソッドを結び付け、private はメソッド境界の内側に留める状態を示す。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`in`演算子を使い、`constructor`のような継承名までルートとして受け入れることです。", | ||
| "`as const`がオブジェクトを凍結し、実行時にも再帰的な変更を防ぐと考えることです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「クラスとアクセス修飾子」を適用してから `example:` 出力へつながりますか。", | ||
| "「クラスとアクセス修飾子」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "ここで`satisfies`は`as Record<string, string>`というアサーションとどう違いますか。", | ||
| "`Object.hasOwn`が`in`より強いルート条件を証明するのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「クラスとアクセス修飾子」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "`satisfies`で検査された定数オブジェクトを添字参照する前に、要求されたルート名がそのオブジェクト自身のプロパティか確認してください。", | ||
| "objective": "保たれた2つのパスを返し、未知名とプロトタイプ由来名を拒否します。", | ||
| "language_delta": "`satisfies`はキャストせず互換性を調べ、`as const`はリテラル型とコンパイル時`readonly`を保ちますが、実行時オブジェクトを凍結しません。", | ||
| "prediction_prompt": "ルート添字を許可する前に、`Object.hasOwn(routes, \"constructor\")`を評価してください。", | ||
| "transfer_trap": "JavaScriptの`in`はプロトタイプ連鎖をたどるため、宣言した自身キーだけを許す設定には広すぎます。" | ||
| }, | ||
| "ts-readonly": { | ||
| "title": "readonly", | ||
| "concept": "readonly は設定のようなデータを読めるようにしつつ、その型経由の置換や変更を防ぐ。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「readonly」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-discriminated-unions": { | ||
| "title": "チェックポイント:タグ付きユニオンを網羅する", | ||
| "concept": "各`Shape`バリアントはリテラルの`kind`と対応する寸法を持ちます。共通タグで`switch`すれば安全にフィールドを絞れ、残りを`never`へ代入すると網羅性を検査できます。", | ||
| "worked_example": "例は`success`または`error`の別ユニオンをモデル化し、両`switch`ケースから返します。長方形や円を計算せず、同じ絞り込み機構だけを示します。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「readonly」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「readonly」演習の TODO を解かないこと。", | ||
| "readonly は設定のようなデータを読めるようにしつつ、その型経由の置換や変更を防ぐ。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "値が円バリアントだと証明する前に`radius`を読むことです。", | ||
| "後から種別を追加しても、広い既定値分岐で未処理ケースを隠すことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「readonly」を適用してから `example:` 出力へつながりますか。", | ||
| "「readonly」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "長方形分岐が`return`した後、残る値の正確な型は何ですか。", | ||
| "円を`never`へ代入すると初期コードが`TypeCheck`で失敗するのはなぜですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「readonly」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "円バリアントを処理し、将来の追加を検知する`never`アサーションは残してください。", | ||
| "objective": "厳格な網羅性検査を通り、3ケースの2種類のタグ付き図形を計測します。", | ||
| "language_delta": "リテラル判別子は実行時にも残ってJavaScript分岐を動かしますが、TypeScriptの絞り込みと`never`証明は消去されます。", | ||
| "prediction_prompt": "`circle 5`を解析、タグによる絞り込み、数値返り値まで順に追ってください。", | ||
| "transfer_trap": "Rustのワイルドカード`match`や他言語の既定値`switch`も、新バリアントの未処理を隠せます。網羅性を保つ分岐を残してください。" | ||
| }, | ||
| "ts-satisfies-as-const": { | ||
| "title": "satisfies と as const", | ||
| "concept": "as const はルートのリテラルを保持し、satisfies はそれを広げずに Record 契約を検査する。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「satisfies と as const」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-async-promise": { | ||
| "title": "型付き`Promise`を`await`する", | ||
| "concept": "`async`関数は、本文が数値式を返すだけでも常に`Promise`を返します。`main`内の`await`が履行済み数値を取り出し、最後の`catch`が拒否された完了を観察します。", | ||
| "worked_example": "例は非同期に`9`を1増やし、`10`を`await`して例レコードを出します。拒否コールバックは、`throw`される値が常にエラーとは限らないため`unknown`を受けます。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「satisfies と as const」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「satisfies と as const」演習の TODO を解かないこと。", | ||
| "as const はルートのリテラルを保持し、satisfies はそれを広げずに Record 契約を検査する。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`double`から元の数値を返し、要求された変換を忘れることです。", | ||
| "最上位で`main`の拒否された`Promise`を観察しないことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「satisfies と as const」を適用してから `example:` 出力へつながりますか。", | ||
| "「satisfies と as const」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "本文に`await`がなくても、`double`の静的`return`型は何ですか。", | ||
| "`double`が返す`Promise`の履行に対して、出力処理はいつ実行されますか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「satisfies と as const」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "`double`が引数の2倍を解決するようにし、`await`と最上位拒否経路を保ってください。", | ||
| "objective": "正、`0`、負の入力を非同期の制御フローで2倍して出力します。", | ||
| "language_delta": "`Promise`スケジューリングはJavaScript実行時の動作です。TypeScriptは`Promise<number>`や`catch`値を検査しますが、実行器を提供しません。", | ||
| "prediction_prompt": "`double`へ`-3`を渡したとき、履行済み`Promise`が運ぶ値を予測してください。", | ||
| "transfer_trap": "Pythonのコルーチンはスケジュールされるまで実行されませんが、このJavaScriptの`async`関数は呼び出すと直ちに実行を開始し、`Promise`を返します。" | ||
| }, | ||
| "ts-iterables": { | ||
| "title": "イテラブル", | ||
| "concept": "for...of は任意のイテラブルを消費するため、配列、文字列、Set が同じループ形を共有できる。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「イテラブル」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-modules": { | ||
| "title": "単一ファイルで公開APIを設計する", | ||
| "concept": "この演習は局所APIオブジェクトで公開する呼び出し面を型付けします。実行環境が`.ts`1ファイルだけを渡すため、実在しないインポートグラフを検証したとは主張しません。", | ||
| "worked_example": "例は`transform`関数を`DemoApi`として公開し、`module`を反転します。ESMやCommonJS解決へ依存せず、API境界だけを見える形にしています。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「イテラブル」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「イテラブル」演習の TODO を解かないこと。", | ||
| "for...of は任意のイテラブルを消費するため、配列、文字列、Set が同じループ形を共有できる。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "`export`宣言だけで、別ファイルからの解決と実行まで確認できたと思うことです。", | ||
| "契約が大文字化を要求するのに、値を小文字へ変換することです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「イテラブル」を適用してから `example:` 出力へつながりますか。", | ||
| "「イテラブル」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "局所の`LabelApi`実装は、どのメソッドシグネチャを満たす必要がありますか。", | ||
| "本物の複数ファイルESM演習には、Node側のどのプロジェクト設定が必要ですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「イテラブル」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "宣言された局所APIの振る舞いを実装し、標準入力文字列を大文字へ変換してください。", | ||
| "objective": "実際には別ファイルをインポートせず、ASCIIとアクセント付き文字列で型付けされた公開APIを実行します。", | ||
| "language_delta": "Nodeは拡張子やパッケージメタデータからESMとCommonJSを選びます。固定単一ファイル実行環境では`export`とインポートの両方を正直に検査できません。", | ||
| "prediction_prompt": "JavaScriptのUnicode対応大文字変換を使ったとき、入力`é`のメソッド結果を予測してください。", | ||
| "transfer_trap": "ブラウザー、バンドラー、CommonJSではファイル解決が異なります。この導入演習の通過を複数ファイルモジュール習得と混同しないでください。" | ||
| }, | ||
| "ts-array-methods": { | ||
| "title": "map、filter、reduce", | ||
| "concept": "filter は項目を選び、map は変換し、reduce は列を最終結果へ畳み込む。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。", | ||
| "worked_example": "この例では `example:` ラベル付きのデモ出力で「map、filter、reduce」の流れを示す。値が stdout に届く前にどの行で変わるかを読み、演習では同じ考えを別のデータに適用する。", | ||
| "ts-error-handling": { | ||
| "title": "総合演習:コマンドを検証して実行する", | ||
| "concept": "コマンドパーサーは数値トークン全体を検証し、`0`除算を拒否して、判別可能なコマンドを構築します。非同期実行は`Promise`を返し、最後の`catch`はエラー表示の前に`unknown`を絞ります。", | ||
| "worked_example": "例は`parseInt`が`12px`の数値接頭辞を受け入れ、`bad`では例外でなく`NaN`を返すことを示します。捕捉した文字列も`typeof`で絞ってから使います。", | ||
| "common_mistakes": [ | ||
| "最後の出力行だけを変えて、「map、filter、reduce」の規則をコード経路に残さないこと。", | ||
| "`example:` ラベル付きのデモ出力をそのまま写し、「map、filter、reduce」演習の TODO を解かないこと。", | ||
| "filter は項目を選び、map は変換し、reduce は列を最終結果へ畳み込む。 という要点を確認せず、judge の出力だけを合わせること。" | ||
| "検証に`parseInt`を使い、末尾に文字があるトークンまで受け入れることです。", | ||
| "JavaScriptでは任意の値を`throw`できるのに、全`catch`値をエラーインスタンスとして扱うことです。" | ||
| ], | ||
| "self_check": [ | ||
| "例ではどの式が「map、filter、reduce」を適用してから `example:` 出力へつながりますか。", | ||
| "「map、filter、reduce」演習の TODO 行は、デモではなく自分のデータからどの値を作る必要がありますか。" | ||
| "`Number`が`12px`を拒否し、`Number.parseInt`が一部を受け入れるのはなぜですか。", | ||
| "非同期実行前に除数`0`を確認するコマンド分岐はどれですか。" | ||
| ], | ||
| "exercise_prompt": "TODO を完成させ、演習データに「map、filter、reduce」を適用する。stdout は judge が比較できる形に保ち、`example:` ラベルは例だけに残す。" | ||
| "exercise_prompt": "厳密なパーサーを作り、コマンドユニオンを網羅的に実行し、`unknown`の失敗値を絞ってください。", | ||
| "objective": "有効な加算と除算を計算し、部分的な数値、非有限数、`0`除算には`invalid`を出力します。", | ||
| "language_delta": "厳格なTypeScriptではJavaScriptが任意値を`throw`できるため`catch`を`unknown`として扱います。`parseInt`は完全に不正な文字列へ`NaN`を返すだけで、自ら例外を送出しません。", | ||
| "prediction_prompt": "`add 12px 1`を変換から追い、どの検証が拒否するか特定してください。", | ||
| "transfer_trap": "例外だけで解析失敗を表す言語のパターンは移せません。JavaScriptの数値変換は失敗を`NaN`で報告することがあります。" | ||
| } | ||
| } | ||
| } |
@@ -7,422 +7,506 @@ { | ||
| "ts-output": { | ||
| "title": "콘솔과 stdout", | ||
| "concept": "콘솔 출력은 채점기가 비교하는 계약이므로 console.log의 자동 줄바꿈과 process.stdout.write의 정확한 바이트 출력을 구분한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 콘솔과 stdout 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "표준 출력 한 줄 직접 쓰기", | ||
| "concept": "`process.stdout.write`는 전달한 문자열만 내보내므로 구분자와 마지막 줄바꿈도 프로그램이 책임진다. 타입 검사가 끝난 뒤 Node는 타입이 제거된 JavaScript를 실행한다.", | ||
| "worked_example": "예제는 타입이 지정된 이름과 점수를 `demo Mina=4`라는 별도 레코드로 쓴다. 타입 표시는 사라지지만 템플릿의 등호와 줄바꿈은 출력에 남는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 콘솔과 stdout 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 콘솔과 stdout 실습의 TODO를 풀지 않는다.", | ||
| "콘솔 출력은 채점기가 비교하는 계약이므로 console.log의 자동 줄바꿈과 process.stdout.write의 정확한 바이트 출력을 구분한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "마지막 줄바꿈까지 직접 제어해야 하는데 `console.log`의 자동 줄바꿈에 의존한다.", | ||
| "이름과 점수 사이 구분자를 빼서 `Ada7`처럼 의미가 모호한 출력을 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 콘솔과 stdout 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "콘솔과 stdout 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "예제에서 학습자 이름 바로 뒤에 오는 문자는 무엇인가?", | ||
| "쓰기 호출의 어느 부분이 줄 끝 문자를 담당하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 콘솔과 stdout 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "표준 입력의 두 필드를 읽고 `process.stdout.write`로 콜론으로 구분된 레코드와 줄바꿈 하나만 출력하라.", | ||
| "objective": "모든 입력 레코드를 `이름:점수` 형식과 줄바꿈 하나로 정확히 출력한다.", | ||
| "language_delta": "`console.log`와 달리 `process.stdout.write`는 공백이나 줄바꿈을 보태지 않으며 타입 애너테이션도 Node 실행 전에 제거된다.", | ||
| "prediction_prompt": "유니코드 이름과 두 자리 점수를 넣었을 때 템플릿이 만드는 문자와 마지막 줄바꿈을 순서대로 적어 보라.", | ||
| "transfer_trap": "브라우저 콘솔은 값을 관찰하기 위한 화면이지만 채점기는 프로그램의 표준 출력 텍스트를 비교한다." | ||
| }, | ||
| "ts-let-const": { | ||
| "title": "let과 const", | ||
| "concept": "const는 재할당하지 않을 바인딩을 표시하고, let은 풀이 중 변하는 누적값을 분명하게 드러낸다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 let과 const 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-input": { | ||
| "title": "Node 표준 입력 토큰화하기", | ||
| "concept": "Node는 리다이렉트된 표준 입력을 파일 설명자 0으로 노출한다. 전체를 한 번 읽고 앞뒤 공백을 처리한 뒤 빈 문자열을 분기하면 안정적인 숫자 토큰 경계가 된다.", | ||
| "worked_example": "예제는 고정 문자열 `4 6`을 분리하고 `Number`로 변환한 뒤 초기값 0의 `reduce`로 `10`을 만든다. 실제 입력 사례를 그대로 풀지는 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 let과 const 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 let과 const 실습의 TODO를 풀지 않는다.", | ||
| "const는 재할당하지 않을 바인딩을 표시하고, let은 풀이 중 변하는 누적값을 분명하게 드러낸다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "빈 문자열을 그대로 분리해 생긴 빈 토큰을 숫자 0으로 바꾼다.", | ||
| "첫 줄만 읽어 여러 줄에 걸쳐 들어온 값을 빠뜨린다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 let과 const 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "let과 const 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "공백을 제거한 표준 입력이 비어 있을 때 토큰 배열을 왜 빈 배열로 만들어야 하는가?", | ||
| "`reduce`의 초기값 0은 빈 입력의 합계를 어떻게 결정하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 let과 const 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "첫 토큰만 출력하는 코드를 바꾸어 표준 입력의 모든 공백 구분 숫자를 변환하고 초기값 0에서 합산하라.", | ||
| "objective": "한 줄, 여러 줄, 빈 표준 입력에서 같은 경로로 산술 합계를 구한다.", | ||
| "language_delta": "`readFileSync(0, \"utf8\")`은 Node 실행 환경의 API이며 TypeScript는 선언된 모양을 검사할 뿐 입력을 대신 읽지 않는다.", | ||
| "prediction_prompt": "`-1` 다음 줄에 `5 2`가 들어오면 토큰 배열과 최종 합계가 무엇인지 예상하라.", | ||
| "transfer_trap": "다른 언어의 반복적인 대화형 입력 방식은 Node의 전체 스트림 기반 채점 실행과 맞지 않는다." | ||
| }, | ||
| "ts-primitives": { | ||
| "title": "기본 타입", | ||
| "concept": "string, number, boolean 주석은 파싱과 분기 코드가 기대하는 스칼라 값을 설명한다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 기본 타입 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "title": "텍스트를 기본 타입 값으로 바꾸기", | ||
| "concept": "`string` 애너테이션은 타입 검사기에 값의 모양을 알리지만 실행 중 숫자 변환은 `Number`가 수행한다. 합격 판단은 숫자 점수와 기준값을 비교해야 한다.", | ||
| "worked_example": "예제는 문자열, 숫자, 불리언 값을 하나씩 선언하고 별도 레코드를 출력한다. 각 실행 값은 애너테이션이 약속한 기본 타입과 일치한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 기본 타입 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 기본 타입 실습의 TODO를 풀지 않는다.", | ||
| "string, number, boolean 주석은 파싱과 분기 코드가 기대하는 스칼라 값을 설명한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "입력 문자열을 변환하지 않은 채 점수 변수에 숫자 타입만 적는다.", | ||
| "암묵적 형 변환에 기대어 잘못된 숫자 텍스트가 들어오는 경계를 숨긴다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 기본 타입 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "기본 타입 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "입력 토큰을 문자열 의미에서 숫자 의미로 바꾸는 식은 무엇인가?", | ||
| "점수가 기준값과 같은 0 경계도 합격이어야 하는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 기본 타입 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "이름은 문자열로 보존하고 점수와 기준값은 명시적으로 숫자로 변환한 뒤 점수가 기준값 이상인지 비교해 상태를 정하고 출력하라.", | ||
| "objective": "세 경계 사례에서 변환된 점수와 올바른 합격 또는 재시도 상태를 출력한다.", | ||
| "language_delta": "TypeScript 5.9는 일관되지 않은 기본 타입 사용을 거부할 수 있지만 지워지는 애너테이션은 표준 입력을 변환하지 못한다.", | ||
| "prediction_prompt": "`Bo 0 0`을 받으면 비교식이 어떤 상태를 선택하는지 실행 전에 판단하라.", | ||
| "transfer_trap": "실행 중 타입 선언이 변환까지 일으키는 언어와 달리 TypeScript의 숫자 선언만으로 문자열이 숫자가 되지는 않는다." | ||
| }, | ||
| "ts-strings-templates": { | ||
| "title": "문자열과 템플릿", | ||
| "concept": "템플릿 리터럴은 값을 바로 옆에서 포맷하고, trim 같은 문자열 메서드는 새 문자열을 돌려준다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 문자열과 템플릿 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-let-const": { | ||
| "title": "누적값만 다시 대입하기", | ||
| "concept": "바뀌지 않는 표시에는 `const`, 계산 중 달라지는 합계에는 `let`을 사용한다. 두 키워드는 바인딩 재대입 가능성을 정하며 참조한 객체 전체를 얼리는 장치가 아니다.", | ||
| "worked_example": "예제는 표시 `demo`를 고정하고 합계를 `5`로 시작해 `-2`를 더한다. 표시는 그대로인 채 합계만 `3`으로 다시 대입된다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 문자열과 템플릿 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 문자열과 템플릿 실습의 TODO를 풀지 않는다.", | ||
| "템플릿 리터럴은 값을 바로 옆에서 포맷하고, trim 같은 문자열 메서드는 새 문자열을 돌려준다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "누적값을 `const`로 선언한 뒤 새 숫자를 대입하려 한다.", | ||
| "`const` 객체의 쓰기 가능한 속성도 변경할 수 없다고 생각한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 문자열과 템플릿 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "문자열과 템플릿 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "예제의 두 바인딩 중 두 번째 대입을 받는 것은 무엇인가?", | ||
| "가변 객체를 가리키는 `settings`가 `const`라면 `settings.enabled = false`를 막는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 문자열과 템플릿 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "표시는 고정된 바인딩으로 두고 두 입력 숫자는 다시 대입 가능한 누적값을 통해 더해 표시와 함께 출력하라.", | ||
| "objective": "양수, 음수, 0의 덧셈에서도 요청된 표시와 산술 결과를 함께 보존한다.", | ||
| "language_delta": "JavaScript는 `const` 재대입 규칙을 실행 중에도 지키지만 숫자 타입 애너테이션은 검사 뒤 제거된다.", | ||
| "prediction_prompt": "입력이 `net -2 5`일 때 각 대입 뒤 누적값을 차례로 추적하라.", | ||
| "transfer_trap": "Rust의 불변 바인딩과 Java의 `final` 참조는 이름이 비슷해도 소유권과 객체 변경 규칙이 서로 다르다." | ||
| }, | ||
| "ts-arrays-tuples": { | ||
| "title": "배열과 튜플", | ||
| "concept": "배열은 순서가 있는 값의 묶음이고, 튜플은 작은 레코드에서 위치와 타입을 함께 고정한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 배열과 튜플 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-strings-templates": { | ||
| "title": "이름만 다듬고 점수 공백 보존하기", | ||
| "concept": "파이프 양쪽 필드는 공백 규칙이 다르다. 레코드 끝의 `CRLF` 또는 `LF` 하나만 제거하고 이름만 다듬어 점수 뒤 공백은 템플릿에 그대로 남겨야 한다.", | ||
| "worked_example": "고정된 `Mira` 레코드의 점수 뒤에는 공백 두 칸과 줄바꿈이 있다. 정규식은 줄바꿈만 제거하므로 보간된 점수 옆 공백 두 칸은 유지된다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 배열과 튜플 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 배열과 튜플 실습의 TODO를 풀지 않는다.", | ||
| "배열은 순서가 있는 값의 묶음이고, 튜플은 작은 레코드에서 위치와 타입을 함께 고정한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "레코드 전체에 `trimEnd`를 호출해 의미 있는 점수 뒤 공백까지 지운다.", | ||
| "UTF-16 서로게이트 쌍이 두 코드 단위를 차지할 수 있는데 문자열 길이를 사용자가 보는 글자 수로 본다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 배열과 튜플 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "배열과 튜플 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "` Mira |9 ` 뒤 줄바꿈만 제거하면 각 필드에 어느 공백이 남는가?", | ||
| "`rawName`만 다듬으면 점수의 뒤쪽 공백이 보존되는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 배열과 튜플 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "레코드 끝의 `CRLF`나 `LF` 하나만 없애고 이름 쪽만 다듬은 뒤 점수 원문을 콜론 뒤에 보간하라.", | ||
| "objective": "세 레코드 모두에서 점수 필드 끝의 유효한 공백을 보존하며 형식을 맞춘다.", | ||
| "language_delta": "`trimEnd`는 모든 후행 공백을 지우지만 여기의 끝 고정 정규식은 전송용 줄 끝 하나만 제거한다. 인덱스는 UTF-16 코드 단위를 센다.", | ||
| "prediction_prompt": "` Ada |7 ` 뒤에 `LF`가 있을 때 `/\\r?\\n$/` 처리 후 남는 공백을 위치별로 표시하라.", | ||
| "transfer_trap": "다른 언어의 일반적인 `trim`이나 `strip`을 쓰면 전송 줄바꿈뿐 아니라 데이터에 속한 공백도 사라질 수 있다." | ||
| }, | ||
| "ts-objects": { | ||
| "title": "객체 타입", | ||
| "concept": "객체 타입은 필요한 필드를 이름으로 고정해 width, height 같은 속성을 계산에서 빠뜨리지 않게 한다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 객체 타입 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-arrays-tuples": { | ||
| "title": "이름과 합계를 튜플로 반환하기", | ||
| "concept": "첫 토큰은 레코드 이름이고 나머지는 숫자 배열이다. `[string, number]` 튜플은 두 위치의 의미를 검사하지만 실행 중 컨테이너는 여전히 JavaScript 배열이다.", | ||
| "worked_example": "예제는 별도 배열 `[1, 4]`의 합과 `Neo`를 타입이 지정된 쌍에 넣는다. 0번과 1번 위치 접근은 튜플도 실행 중에는 배열임을 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 객체 타입 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 객체 타입 실습의 TODO를 풀지 않는다.", | ||
| "객체 타입은 필요한 필드를 이름으로 고정해 width, height 같은 속성을 계산에서 빠뜨리지 않게 한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "첫 이름까지 숫자로 변환해 합산 대상과 레코드 표시를 섞는다.", | ||
| "튜플 애너테이션이 배열과 다른 불변 실행 객체를 만든다고 생각한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 객체 타입 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "객체 타입 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "예제 쌍의 두 번째 위치에는 어떤 타입만 들어갈 수 있는가?", | ||
| "입력이 이름 `Bo` 하나뿐이면 숫자 꼬리의 합은 어떻게 정해야 하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 객체 타입 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "첫 토큰은 이름으로 남기고 전체 숫자 꼬리를 초기값 0에서 합산해 선언된 튜플의 두 위치에 저장하라.", | ||
| "objective": "여러 값, 음수 값, 빈 숫자 꼬리를 하나의 이름-합계 쌍으로 처리한다.", | ||
| "language_delta": "튜플 위치 검사는 Node 실행 전에 사라지며 배열과 이터러블을 소비하는 실제 동작은 JavaScript 규칙을 따른다.", | ||
| "prediction_prompt": "`Lin -1 4 2`에서 튜플 두 번째 위치를 계산하기 직전 숫자 배열을 적어 보라.", | ||
| "transfer_trap": "값 지향 레코드를 제공하는 언어와 달리 TypeScript 튜플은 실행 중 작은 불변 레코드가 아니다." | ||
| }, | ||
| "ts-functions": { | ||
| "title": "함수", | ||
| "concept": "함수 매개변수와 반환 타입은 파싱된 입력, 계산, 출력 사이의 경계를 명확히 한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 함수 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-iterables": { | ||
| "title": "`for...of`로 이터러블 소비하기", | ||
| "concept": "`Iterable<string>`은 숫자 인덱스가 아니라 반복 프로토콜을 약속한다. `for...of`는 그 프로토콜로 값을 요청하므로 배열, 세트, 문자열, 사용자 정의 이터러블에 적용된다.", | ||
| "worked_example": "예제는 `T`, `S`를 담은 세트를 수집 함수에 넘긴다. 배열 전용 메서드 없이 삽입 순서의 두 값을 반복해 이어 붙인다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 함수 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 함수 실습의 TODO를 풀지 않는다.", | ||
| "함수 매개변수와 반환 타입은 파싱된 입력, 계산, 출력 사이의 경계를 명확히 한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "이터러블 인터페이스에 숫자 속성이 보장되지 않는데 인덱싱한다.", | ||
| "끝까지 진행된 같은 이터레이터를 다시 쓰면 값이 처음부터 재생된다고 기대한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 함수 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "함수 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`for...of`가 입력에 요구하는 기능은 무엇인가?", | ||
| "빈 토큰 목록도 출력이 완전히 없는 대신 줄바꿈을 하나 만드는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 함수 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "표준 입력의 모든 토큰을 인덱스 없이 `for...of`로 순회하고 만난 순서대로 문자열 하나에 이어 붙여 출력하라.", | ||
| "objective": "두 토큰, 세 토큰, 빈 입력을 배열 인덱스에 기대지 않고 결합한다.", | ||
| "language_delta": "TypeScript 이터러블 타입은 JavaScript 반복 프로토콜을 설명할 뿐 실행 중 컬렉션이나 재생 기능을 추가하지 않는다.", | ||
| "prediction_prompt": "`a b c`의 이터레이터 상태를 따라가며 각 반복 직후 누적 문자열을 예측하라.", | ||
| "transfer_trap": "Python 이터레이터와 Java 스트림도 소비되지만 재사용 가능성이나 소유 규칙까지 같다고 가정하지 마라." | ||
| }, | ||
| "ts-input": { | ||
| "title": "Node stdin 파싱", | ||
| "concept": "Node 풀이는 보통 파일 디스크립터 0을 한 번 읽고 공백으로 나눈 뒤 숫자 계산 전에 토큰을 변환한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 Node stdin 파싱 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-array-methods": { | ||
| "title": "짝수를 거르고 제곱해 접기", | ||
| "concept": "읽기 쉬운 배열 파이프라인은 짝수를 고른 뒤 제곱하고 결과를 하나로 접는다. `reduce`에 초기값 0을 주면 선택 결과가 비어도 합계가 정의된다.", | ||
| "worked_example": "예제는 `[2, 5, 6]`을 다른 조건으로 거르고 통과값을 두 배 한 뒤 더한다. 메서드 순서를 보여 주지만 실습의 짝수 제곱 결과를 노출하지 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 Node stdin 파싱 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 Node stdin 파싱 실습의 TODO를 풀지 않는다.", | ||
| "Node 풀이는 보통 파일 디스크립터 0을 한 번 읽고 공백으로 나눈 뒤 숫자 계산 전에 토큰을 변환한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "원래 값을 검사해야 하는 조건인데 먼저 변환한 뒤 필터링한다.", | ||
| "`reduce` 초기값을 생략해 짝수가 하나도 없을 때 예외를 만든다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 Node stdin 파싱 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "Node stdin 파싱 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "음수 `-2`는 어느 단계에서 양수 `4`가 되는가?", | ||
| "필터 결과가 비어 있으면 `reduce`에 처음 들어가는 값은 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 Node stdin 파싱 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "짝수만 통과하도록 조건을 고치고 제곱 변환과 초기값 0의 합산 단계는 유지해 최종 숫자를 출력하라.", | ||
| "objective": "부호가 섞인 값과 전부 홀수인 값에서 짝수 제곱의 합계를 계산한다.", | ||
| "language_delta": "이 메서드는 JavaScript 배열 연산이며 TypeScript가 콜백 타입을 추론해도 파이프라인을 지연 실행으로 바꾸지는 않는다.", | ||
| "prediction_prompt": "`-2 -1 0`이 필터와 제곱 단계를 지날 때 생기는 중간 배열을 차례로 적어 보라.", | ||
| "transfer_trap": "Rust 이터레이터 어댑터는 소비 전까지 지연되지만 JavaScript 배열 메서드는 각 단계에서 즉시 순회하거나 배열을 만든다." | ||
| }, | ||
| "ts-control-flow": { | ||
| "title": "제어 흐름", | ||
| "concept": "if와 반복문은 어떤 값을 누적할지 결정하므로 분기 조건이 의도한 데이터 규칙과 맞아야 한다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 제어 흐름 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-objects": { | ||
| "title": "구조적 객체로 넓이 계산하기", | ||
| "concept": "`Rectangle` 타입은 `width`와 `height` 멤버를 요구하며 넓이 함수는 그 모양과 호환되는 객체를 받는다. 같은 클래스 이름이나 명목적 계보는 필요 없다.", | ||
| "worked_example": "예제 객체에는 `color`도 있지만 두 멤버만 요구하는 함수에 들어갈 수 있다. 추가 속성은 실행 중 남아 있어도 계산에서는 읽지 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 제어 흐름 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 제어 흐름 실습의 TODO를 풀지 않는다.", | ||
| "if와 반복문은 어떤 값을 누적할지 결정하므로 분기 조건이 의도한 데이터 규칙과 맞아야 한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "파싱한 한 변을 객체에서 빠뜨리고 임시 고정값으로 대신한다.", | ||
| "생성자 이름이 같아야만 TypeScript가 두 객체 모양을 호환된다고 본다고 생각한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 제어 흐름 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "제어 흐름 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "예제 함수가 `painted`에서 실제로 읽는 멤버는 무엇인가?", | ||
| "너비가 0이면 넓이도 0이어야 하는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 제어 흐름 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "표준 입력의 두 치수를 모두 숫자로 바꾸어 `Rectangle` 객체를 만들고 타입이 지정된 넓이 함수에 넘겨라.", | ||
| "objective": "일반, 너비 0, 높이 1인 직사각형을 구조적 데이터로 계산한다.", | ||
| "language_delta": "객체 타입은 검사기 설명으로 실행 전에 사라진다. 클래스 메서드는 실행 동작을 만들고 `readonly`는 타입을 통한 접근만 얕게 제한한다.", | ||
| "prediction_prompt": "치수가 `7 1`일 때 만들어지는 객체와 곱셈 결과를 실행 전에 적어 보라.", | ||
| "transfer_trap": "Java는 선언된 클래스 정체성을 중시하지만 TypeScript는 독립적으로 만든 값도 필요한 멤버가 맞으면 받아들이는 경우가 많다." | ||
| }, | ||
| "ts-union-narrowing": { | ||
| "title": "유니온과 좁히기", | ||
| "concept": "유니온 값은 문자열 전용 또는 숫자 전용 연산을 하기 전에 런타임 검사를 통해 좁혀야 안전하다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 유니온과 좁히기 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-classes": { | ||
| "title": "클래스 메서드 뒤에서 상태 갱신하기", | ||
| "concept": "카운터 인스턴스는 가변 상태를 소유하고 증가 연산을 메서드로 노출한다. 메서드는 저장된 필드를 바꾼 뒤 반환해야 다음 호출도 변경된 값을 본다.", | ||
| "worked_example": "예제 클래스는 `8`에서 시작해 한 번 증가한 `9`를 반환한다. `private`은 소스 접근을 검사하지만 생성된 JavaScript 필드는 일반 속성으로 남는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 유니온과 좁히기 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 유니온과 좁히기 실습의 TODO를 풀지 않는다.", | ||
| "유니온 값은 문자열 전용 또는 숫자 전용 연산을 하기 전에 런타임 검사를 통해 좁혀야 안전하다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`this.value + 1`만 반환하고 새 상태를 필드에 저장하지 않는다.", | ||
| "TypeScript `private`을 JavaScript의 실행 중 비공개 `#value`와 같은 기능으로 본다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 유니온과 좁히기 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "유니온과 좁히기 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "하나의 인스턴스에서 메서드를 두 번 호출하면 두 번째 반환값은 어떻게 달라지는가?", | ||
| "JavaScript로 변환된 뒤에도 접근 제한을 강제하는 클래스 필드 문법은 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 유니온과 좁히기 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "`increment`가 인스턴스 필드 자체를 1 증가시키고 변경된 카운터 값을 반환하도록 메서드 본문을 고쳐라.", | ||
| "objective": "양수, 음수, 큰 시작값을 각각 정확히 1만큼 전진시킨다.", | ||
| "language_delta": "여기의 `private` 키워드는 지워지는 검사기 메타데이터지만 `#`로 시작하는 자바스크립트 표준 필드는 실행 중에도 비공개다.", | ||
| "prediction_prompt": "시작값 `-1`에서 호출하기 전과 후의 저장 필드를 차례로 추적하라.", | ||
| "transfer_trap": "Java의 `private` 접근은 JVM에서도 강제되므로 지워지는 TypeScript 수식자보다 실행 경계가 강하다." | ||
| }, | ||
| "ts-literal-types": { | ||
| "title": "리터럴 타입", | ||
| "concept": "리터럴 유니온은 명령이나 모드를 'left', 'right'처럼 정확한 값으로 제한한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 리터럴 타입 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-readonly": { | ||
| "title": "얕은 읽기 전용 설정 사용하기", | ||
| "concept": "읽기 전용 설정 타입은 속성 교체를 막는다. 점수 배열의 원소 변경까지 해당 타입을 통해 막으려면 내부 배열 타입에도 `readonly`를 지정해야 한다.", | ||
| "worked_example": "예제는 읽기 전용 속성과 읽기 전용 숫자 배열을 선언해 길이만 읽는다. 쓰기를 시도하지 않아 엄격한 검사와 Node 실행을 모두 통과한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 리터럴 타입 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 리터럴 타입 실습의 TODO를 풀지 않는다.", | ||
| "리터럴 유니온은 명령이나 모드를 'left', 'right'처럼 정확한 값으로 제한한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "읽기 전용 숫자 배열에 `push`를 호출해 엄격한 타입 검사를 통과할 것으로 기대한다.", | ||
| "`readonly`가 `Object.freeze`를 호출하거나 중첩 객체를 재귀적으로 잠근다고 믿는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 리터럴 타입 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "리터럴 타입 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "시작 코드는 입력이 Node에 도달하기도 전에 왜 실패하는가?", | ||
| "같은 실제 배열을 가리키는 별도의 가변 별칭은 여전히 `push`할 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 리터럴 타입 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "금지된 배열 변경을 제거하고 읽기 전용 관점을 통해 점수를 순회해 합계를 계산한 뒤 설정 이름과 함께 출력하라.", | ||
| "objective": "엄격한 타입 검사를 통과하고 각 설정 이름과 점수 합계를 보고한다.", | ||
| "language_delta": "`readonly`는 중첩 멤버에도 표시하지 않으면 얕고 타입 전용이다. 어떤 애너테이션도 JavaScript 메모리를 얼리지 않는다.", | ||
| "prediction_prompt": "표준 출력을 예상하기 전에 `config.scores.push(0)`에서 나올 타입 검사 오류를 설명하라.", | ||
| "transfer_trap": "Rust의 불변 빌림은 소유 규칙으로 그 참조를 통한 변경을 막으므로, 실행 전에 지워지는 TypeScript의 읽기 전용 관점과 같지 않다." | ||
| }, | ||
| "ts-optional-nullish": { | ||
| "title": "옵셔널과 nullish", | ||
| "concept": "옵셔널 필드는 undefined일 수 있고, ??는 점수 0 같은 유효한 falsy 값을 보존한다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 옵셔널과 nullish 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-control-flow": { | ||
| "title": "포함되는 반복 상한 유지하기", | ||
| "concept": "반복문은 1부터 `n`까지 방문하고 엄격한 나머지 비교가 홀수만 고른다. 끝값 참여 여부는 반복 조건의 관계 연산자에 달려 있다.", | ||
| "worked_example": "예제는 `5`까지 순회하지만 짝수를 더해 별도 합계를 만든다. 실습과 다른 조건이어도 엄격한 동등 비교와 포함 상한을 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 옵셔널과 nullish 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 옵셔널과 nullish 실습의 TODO를 풀지 않는다.", | ||
| "옵셔널 필드는 undefined일 수 있고, ??는 점수 0 같은 유효한 falsy 값을 보존한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`< n`을 사용해 범위에 포함되어야 할 홀수 끝값을 빠뜨린다.", | ||
| "느슨한 동등 비교로 JavaScript의 암묵적 형 변환이 관계없는 값을 같게 만들 여지를 둔다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 옵셔널과 nullish 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "옵셔널과 nullish 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`n`이 3이면 마지막으로 합계에 들어가는 반복 값은 무엇인가?", | ||
| "잘못된 시작 코드도 6 사례는 통과하는데 어느 사례가 상한 오류를 드러내는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 옵셔널과 nullish 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "홀수 조건과 누적값은 유지하면서 반복 상한이 입력값을 포함하도록 관계 연산자를 고쳐 합계를 출력하라.", | ||
| "objective": "양수 상한과 0 상한에서 포함 범위의 모든 홀수를 더한다.", | ||
| "language_delta": "JavaScript 조건은 실행 중 불리언과 형 변환 규칙을 따르고 TypeScript의 제어 흐름 분석은 별도로 분기 안 타입을 좁힌다.", | ||
| "prediction_prompt": "`n=3`일 때 `<`와 `<=`가 각각 방문하는 값을 나열하라.", | ||
| "transfer_trap": "끝을 제외하는 Python `range` 습관을 그대로 번역하면 TypeScript 반복문의 마지막 값이 누락될 수 있다." | ||
| }, | ||
| "ts-interfaces-aliases": { | ||
| "title": "인터페이스와 타입 별칭", | ||
| "concept": "인터페이스는 객체 계약을 이름 붙이고, 타입 별칭은 유니온, 튜플, 조합된 타입 표현식까지 이름 붙인다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 인터페이스와 타입 별칭 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-functions": { | ||
| "title": "체크포인트: 타입이 있는 계산 경계", | ||
| "concept": "함수 시그니처는 받을 입력과 반환할 출력을 함께 약속한다. 숫자 곱셈 결과는 문자열 반환 약속을 충족할 수 없으므로 엄격한 검사가 시작 코드를 거부해야 한다.", | ||
| "worked_example": "예제는 두 숫자 매개변수로 둘레를 계산하고 숫자를 반환한다. 고정 치수의 결과는 채점되는 넓이 사례와 겹치지 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 인터페이스와 타입 별칭 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 인터페이스와 타입 별칭 실습의 TODO를 풀지 않는다.", | ||
| "인터페이스는 객체 계약을 이름 붙이고, 타입 별칭은 유니온, 튜플, 조합된 타입 표현식까지 이름 붙인다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "잘못된 반환 애너테이션에 맞추려고 곱셈 결과를 문자열로 바꾼다.", | ||
| "계산 함수 안에서 출력해 값 반환과 표준 출력의 책임을 섞는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 인터페이스와 타입 별칭 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "인터페이스와 타입 별칭 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`area`가 약속하는 반환 타입은 어느 줄에서 정해지는가?", | ||
| "파싱한 두 토큰은 어느 지점에서 곱셈 가능한 숫자 인자가 되는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 인터페이스와 타입 별칭 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "함수의 반환 계약을 숫자 계산과 일치시키고 입력 변환과 최종 출력은 함수 바깥 경계에 유지하라.", | ||
| "objective": "엄격한 검사를 통과하며 애너테이션이 있는 함수 하나로 세 직사각형 넓이를 계산한다.", | ||
| "language_delta": "TypeScript는 실행 전에 매개변수와 결과 사용을 검사하지만 함수 내부의 반복과 분기는 평범한 JavaScript 제어문으로 실행된다.", | ||
| "prediction_prompt": "사례 출력보다 먼저 시작 코드가 어떤 타입 검사 실패 범주에 속하는지 예측하라.", | ||
| "transfer_trap": "실행 중 메서드 시그니처가 유지되는 언어와 달리 TypeScript 반환 타입은 검사 뒤 제거된다." | ||
| }, | ||
| "ts-generics": { | ||
| "title": "제네릭", | ||
| "concept": "제네릭은 재사용 코드가 배열에서 읽거나 값을 반환할 때 호출자의 요소 타입을 유지한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 제네릭 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-union-narrowing": { | ||
| "title": "유니온을 좁힌 뒤 형식 지정하기", | ||
| "concept": "`string | number` 값에는 두 멤버 모두에 안전한 연산만 바로 쓸 수 있다. `typeof` 분기는 실제 기본 타입을 증명해 각 타입 전용 형식 메서드를 사용할 수 있게 한다.", | ||
| "worked_example": "예제는 문자열을 소문자로 바꾸고 고정 숫자를 소수점 한 자리로 표시한다. 두 분기를 보여 주되 실습의 대문자와 정수 형식은 그대로 답하지 않는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 제네릭 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 제네릭 실습의 TODO를 풀지 않는다.", | ||
| "제네릭은 재사용 코드가 배열에서 읽거나 값을 반환할 때 호출자의 요소 타입을 유지한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "숫자일 수도 있는 유니온에 `toUpperCase`를 바로 호출한다.", | ||
| "입력 종류 토큰을 단순 단언으로 사용하고 실제 실행 값을 만들고 좁히는 단계를 건너뛴다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 제네릭 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "제네릭 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`Number(text)`가 만든 숫자 멤버에 `typeof`를 적용하면 무엇이 나오는가?", | ||
| "`toFixed(0)`가 `-1.6`을 `-2`로 표시하는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 제네릭 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "실행 중 타입을 확인하는 분기를 추가하고 문자열과 숫자 유니온 각 멤버에 맞는 형식 메서드를 적용하라.", | ||
| "objective": "엄격한 검사를 만족하며 문자열 명령은 대문자로, 숫자 명령은 반올림해 출력한다.", | ||
| "language_delta": "타입 좁히기는 JavaScript의 실행 검사와 TypeScript의 검사기 추론을 결합한다. 임의 표준 입력은 리터럴 유니온에 넣기 전에도 검증해야 한다.", | ||
| "prediction_prompt": "`n -1.6`이 어느 분기를 선택하고 어떤 텍스트가 되는지 실행 전에 정하라.", | ||
| "transfer_trap": "Python은 잘못된 메서드 호출을 실행 때까지 허용할 수 있지만 TypeScript는 안전하지 않은 유니온 연산을 검사 단계에서 거부한다." | ||
| }, | ||
| "ts-keyof-typeof": { | ||
| "title": "keyof와 typeof", | ||
| "concept": "typeof는 값의 정적 모양을 가져오고, keyof는 그 모양을 허용된 키 유니온으로 바꾼다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 keyof와 typeof 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-literal-types": { | ||
| "title": "입력 문자열을 리터럴 유니온으로 검증하기", | ||
| "concept": "방향 타입은 두 문자열 리터럴만 포함한다. 표준 입력은 제한 없는 문자열이므로 실행 중 비교로 포함 여부를 증명한 뒤에야 안전하게 `turn`에 넘길 수 있다.", | ||
| "worked_example": "예제는 북쪽과 남쪽의 별도 유니온에 이미 알려진 리터럴을 넘긴다. 외부 데이터를 검증하지 않고 표시가 붙은 화살표를 만든다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 keyof와 typeof 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 keyof와 typeof 실습의 TODO를 풀지 않는다.", | ||
| "typeof는 값의 정적 모양을 가져오고, keyof는 그 모양을 허용된 키 유니온으로 바꾼다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "임의 문자열을 `Direction`으로 단언하면 실행 검사까지 끝났다고 생각한다.", | ||
| "두 방향 함수가 알 수 없는 모든 입력을 오른쪽 방향으로 처리하게 둔다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 keyof와 typeof 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "keyof와 typeof 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`raw as Direction`을 쓰면 잘못된 `up`도 실행 중 `turn`에 들어가는 이유는 무엇인가?", | ||
| "두 허용 문자열을 모두 비교한 참 분기 안에서 `raw`의 타입은 무엇으로 좁혀지는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 keyof와 typeof 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "표준 입력 문자열을 `Direction`의 두 멤버와 각각 비교하고, 허용되지 않은 텍스트는 별도 `invalid` 결과로 보고하라.", | ||
| "objective": "왼쪽과 오른쪽을 짧은 표기로 바꾸고 알 수 없는 명령은 거부한다.", | ||
| "language_delta": "리터럴 유니온은 검사되는 소스를 제한하지만 JavaScript에는 평범한 문자열만 남으므로 포함 여부 검사를 직접 실행해야 한다.", | ||
| "prediction_prompt": "잘못된 시작 코드가 `up`을 어느 분기로 보내는지 예상하고 요구되는 거부 경로와 비교하라.", | ||
| "transfer_trap": "다른 언어의 닫힌 열거형은 생성 시 검증될 수 있지만 TypeScript 문자열 유니온은 실행 컨테이너를 만들지 않는다." | ||
| }, | ||
| "ts-indexed-access": { | ||
| "title": "인덱스 접근 타입", | ||
| "concept": "인덱스 접근 타입은 기존 모델의 속성 타입과 요소 타입을 손으로 반복하지 않고 재사용한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 인덱스 접근 타입 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-optional-nullish": { | ||
| "title": "널인 점수에만 기본값 주기", | ||
| "concept": "파싱한 점수는 숫자 또는 `null`이다. `??`는 `null`이나 `undefined`일 때만 10을 사용하므로 유효한 숫자 0을 그대로 보존한다.", | ||
| "worked_example": "예제의 선택적 개수는 0이고 `?? 8`을 적용해도 0이 출력된다. 실습과 다른 대체값으로 정확한 결측 규칙만 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 인덱스 접근 타입 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 인덱스 접근 타입 실습의 TODO를 풀지 않는다.", | ||
| "인덱스 접근 타입은 기존 모델의 속성 타입과 요소 타입을 손으로 반복하지 않고 재사용한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "논리합 연산자를 사용해 거짓으로 취급되는 유효 점수 0까지 기본값으로 바꾼다.", | ||
| "텍스트 `none`을 파싱하지 않고 문자열 결측 표시를 숫자 계산에 섞는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 인덱스 접근 타입 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "인덱스 접근 타입 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`??`의 오른쪽 피연산자를 실행하게 하는 값은 무엇인가?", | ||
| "`scoreText`가 `none`이면 실행 값으로 무엇을 저장해야 하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 인덱스 접근 타입 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "명시적 0은 유지하고 결측일 때만 10을 사용하도록 논리합 연산자를 널 병합 연산자로 바꿔 레코드를 출력하라.", | ||
| "objective": "출력 레코드에서 0, `null`, 양수 점수를 서로 구분한다.", | ||
| "language_delta": "`strictNullChecks`는 유니온에 `null`을 명시하고 실제 기본값 선택 의미는 JavaScript의 `??` 연산자가 제공한다.", | ||
| "prediction_prompt": "`Ada 0`에서 기본값 식의 왼쪽과 최종 결과를 실행 전에 평가하라.", | ||
| "transfer_trap": "Python의 `or`와 JavaScript의 `||`는 더 넓은 거짓 값 규칙을 쓰므로 널 병합 연산자의 대체가 아니다." | ||
| }, | ||
| "ts-mapped-types": { | ||
| "title": "매핑된 타입", | ||
| "concept": "매핑된 타입은 객체 타입의 모든 키를 변환해 파생된 모양이 원본과 어긋나지 않게 한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 매핑된 타입 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-interfaces-aliases": { | ||
| "title": "구조적 계약 두 개 합성하기", | ||
| "concept": "`Named` 인터페이스와 `Scored` 타입 별칭의 교차 타입은 두 필드를 모두 요구한다. 결과인 `Entry`는 생성자 계보가 아니라 가진 멤버로 호환성을 판단한다.", | ||
| "worked_example": "예제는 별도의 `Tagged`와 `Weighted`를 교차해 호환 객체를 만든다. 실습 모델을 복사하지 않고 태그와 가중치 필드를 직접 읽어 합성을 관찰한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 매핑된 타입 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 매핑된 타입 실습의 TODO를 풀지 않는다.", | ||
| "매핑된 타입은 객체 타입의 모든 키를 변환해 파생된 모양이 원본과 어긋나지 않게 한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "교차 객체를 만들며 파싱한 점수에 1을 더해 원본 데이터를 훼손한다.", | ||
| "같은 모양의 인터페이스와 객체 타입 별칭이 실행 중 서로 다른 종류의 값을 만든다고 생각한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 매핑된 타입 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "매핑된 타입 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "값이 `Entry`를 만족하려면 어느 두 속성이 필요한가?", | ||
| "인터페이스가 직접 표현하지 못하는 유니온에 타입 별칭을 사용할 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 매핑된 타입 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "파싱한 이름과 숫자 점수를 바꾸지 않고 두 구조적 계약을 만족하는 교차 객체를 구성한 뒤 두 필드를 직접 출력하라.", | ||
| "objective": "교차 객체의 필드를 직접 읽어 양수, 0, 음수 점수를 원본 그대로 보존한다.", | ||
| "language_delta": "인터페이스는 선언 병합이 가능하고 타입 별칭은 유니온도 표현하지만 둘 다 실행 전에 사라진다.", | ||
| "prediction_prompt": "`Bo -2`를 받았을 때 출력 전에 만들어지는 `Entry` 객체를 적어 보라.", | ||
| "transfer_trap": "Java 인터페이스는 명목적 선언과 JVM 메타데이터에 참여하므로 지워지는 TypeScript 구조적 인터페이스와 다르다." | ||
| }, | ||
| "ts-conditional-types": { | ||
| "title": "조건부 타입", | ||
| "concept": "조건부 타입은 타입 관계에 따라 분기를 고르고, infer는 매칭된 부분을 꺼낸다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 조건부 타입 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-generics": { | ||
| "title": "첫 원소의 타입 보존하기", | ||
| "concept": "`first<T>`는 호출자의 원소 타입을 유지하고 원소가 없으면 `undefined`를 반환한다. 반환 유니온이 빈 상태를 표현하므로 `any`나 널 아님 단언이 필요 없다.", | ||
| "worked_example": "예제는 제네릭 `last`를 세 문자열에 호출한다. 반대쪽 끝을 고르면서도 반환 타입이 `string | undefined`로 추론되는 모습을 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 조건부 타입 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 조건부 타입 실습의 TODO를 풀지 않는다.", | ||
| "조건부 타입은 타입 관계에 따라 분기를 고르고, infer는 매칭된 부분을 꺼낸다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "인덱스 1을 반환해 첫 원소와 두 번째 원소를 혼동한다.", | ||
| "빈 읽기 전용 배열도 유효한 입력인데 `!`로 값이 반드시 있다고 강제한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 조건부 타입 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "조건부 타입 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "문자열 배열을 `first`에 넘기면 `T`는 무엇이 되는가?", | ||
| "일부 사례가 비어 있지 않아도 반환 타입에 `undefined`가 포함되어야 하는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 조건부 타입 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "제네릭 도우미가 인덱스 0을 반환하게 하고 호출 위치의 명시적인 빈 배열 대체값은 그대로 유지하라.", | ||
| "objective": "두 비어 있지 않은 입력에서는 첫 토큰을, 빈 스트림에서는 `none`을 선택한다.", | ||
| "language_delta": "제네릭 관계는 검사 중 보존되지만 Node 실행에는 남지 않는다. `keyof`, 인덱스 기반 접근, 매핑된 타입은 이 관계에서 새 타입을 파생한다.", | ||
| "prediction_prompt": "원소 하나짜리 토큰 배열에서 추론되는 반환 타입과 실행 값을 예상하라.", | ||
| "transfer_trap": "Java 제네릭의 타입 소거와 Rust의 단형화는 서로 달라 같은 꺾쇠 문법만으로 동작을 일반화할 수 없다." | ||
| }, | ||
| "ts-utility-types": { | ||
| "title": "유틸리티 타입", | ||
| "concept": "Pick과 Partial 같은 유틸리티 타입은 새 헬퍼 타입을 만들지 않고 흔한 객체 변환을 표현한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 유틸리티 타입 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-keyof-typeof": { | ||
| "title": "입력 키가 직접 소유한 속성인지 검증하기", | ||
| "concept": "타입 위치의 `typeof`는 `limits`의 정적 모양을 얻고 `keyof`는 허용 속성 이름을 만든다. `Object.hasOwn` 기반 조건식만 객체가 직접 가진 키를 받아들인다.", | ||
| "worked_example": "예제는 `tiny | huge`를 파생하고 외부 텍스트 `huge`를 직접 소유 키 조건식으로 좁힌 뒤 제한값을 인덱싱한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 유틸리티 타입 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 유틸리티 타입 실습의 TODO를 풀지 않는다.", | ||
| "Pick과 Partial 같은 유틸리티 타입은 새 헬퍼 타입을 만들지 않고 흔한 객체 변환을 표현한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`value in limits`를 사용해 `toString` 같은 프로토타입 상속 이름까지 허용한다.", | ||
| "값에 적용하는 JavaScript `typeof`와 타입 질의 위치의 TypeScript `typeof`를 혼동한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 유틸리티 타입 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "유틸리티 타입 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`keyof typeof limits`가 만드는 문자열 유니온은 무엇인가?", | ||
| "`in`이 발견하는 `constructor`를 `Object.hasOwn`은 거부하는 이유가 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 유틸리티 타입 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "`limits`를 인덱싱하기 전에 입력 문자열을 직접 소유 속성으로 확인하는 타입 가드를 추가하고 나머지는 유효하지 않은 입력으로 처리하라.", | ||
| "objective": "선언된 두 크기는 조회하고 누락 키와 프로토타입 상속 속성 이름은 거부한다.", | ||
| "language_delta": "`keyof`는 Node에 남지 않으며 `Object.hasOwn`이 문자열이 직접 속성을 가리킨다는 실행 증거를 제공한다.", | ||
| "prediction_prompt": "`Object.hasOwn(limits, \"toString\")`과 `\"toString\" in limits`의 결과를 비교하라.", | ||
| "transfer_trap": "`in`과 같은 포함 여부 연산자가 프로토타입 체인까지 탐색하는 언어에서는 직접 소유한 키인지 별도로 확인해야 한다." | ||
| }, | ||
| "ts-discriminated-unions": { | ||
| "title": "식별 가능한 유니온", | ||
| "concept": "공통 리터럴 태그는 switch가 각 변형을 좁히게 해서 직사각형 필드는 직사각형에서만 쓰이게 한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 식별 가능한 유니온 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-indexed-access": { | ||
| "title": "기존 배열의 원소 타입 재사용하기", | ||
| "concept": "`User[\"scores\"][number]`는 모델에 이미 선언된 원소 타입을 꺼낸다. 같은 숫자 타입을 다시 손으로 쓰지 않아 원본 모델과 어긋날 위험을 줄인다.", | ||
| "worked_example": "예제 모델은 문자열 배열에서 태그 원소 타입을 파생해 `typed`를 대입한다. 두 인덱스 기반 선택은 검사 중에만 존재한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 식별 가능한 유니온 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 식별 가능한 유니온 실습의 TODO를 풀지 않는다.", | ||
| "공통 리터럴 태그는 switch가 각 변형을 좁히게 해서 직사각형 필드는 직사각형에서만 쓰이게 한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "표준 입력 문자열을 실행 중 변환하지 않고 파생된 숫자 타입에 대입한다.", | ||
| "모든 위치에 `number`를 반복해 원본 모델과의 연결을 잃는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 식별 가능한 유니온 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "식별 가능한 유니온 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "먼저 `scores`를 선택하고 다시 `[number]`로 인덱싱하면 어떤 타입이 되는가?", | ||
| "시작 코드의 문자열-숫자 불일치는 실행과 타입 검사 중 어느 단계에서 보고되는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 식별 가능한 유니온 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "인덱스 접근으로 파생한 점수 타입에 대입하기 전에 표준 입력 텍스트를 실행 중 숫자로 변환하라.", | ||
| "objective": "엄격한 검사를 통과하고 양수, 0, 음수의 숫자 값을 그대로 출력한다.", | ||
| "language_delta": "인덱스 기반 접근은 정적 타입만 계산하며 조회 코드를 만들지 않는다. JavaScript 입력 경계에서는 여전히 `Number`가 필요하다.", | ||
| "prediction_prompt": "어떤 표준 출력 사례보다 먼저 시작 코드 실패가 타입 검사인지 분류하라.", | ||
| "transfer_trap": "다른 생태계의 스키마 파생 타입은 실행 검증기도 만들 수 있지만 TypeScript 연산자는 검사만 제공한다." | ||
| }, | ||
| "ts-async-promise": { | ||
| "title": "async와 Promise", | ||
| "concept": "async 함수는 Promise 값을 반환하고, await 지점에서 완료된 숫자가 일반 흐름으로 다시 들어온다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 async와 Promise 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-mapped-types": { | ||
| "title": "모든 기능 키를 플래그로 매핑하기", | ||
| "concept": "플래그 타입은 기능 유니온을 순회해 각 멤버마다 불리언 속성을 요구한다. 이 완전성 검사는 새 기능이 설정 없이 조용히 빠지는 일을 막는다.", | ||
| "worked_example": "예제는 `dark | cache`를 두 불리언으로 매핑하고 켜진 예제 키를 보고한다. 하나만 참이어도 두 필수 키가 모두 존재한다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 async와 Promise 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 async와 Promise 실습의 TODO를 풀지 않는다.", | ||
| "async 함수는 Promise 값을 반환하고, await 지점에서 완료된 숫자가 일반 흐름으로 다시 들어온다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`share`를 빼고 매핑된 타입이 그 속성을 자동으로 선택 사항으로 볼 것이라 기대한다.", | ||
| "사례가 실제 기능 이름을 요구하는데 일반적인 성공 문구만 출력한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 async와 Promise 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "async와 Promise 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "불완전한 시작 코드는 Node가 입력을 읽기 전 왜 실패하는가?", | ||
| "원시 명령이 `none`이면 두 불리언은 각각 무엇이어야 하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 async와 Promise 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "빠진 매핑된 속성을 추가해 두 플래그를 완성하고 실행 값에서 `search`, `share`, `none` 중 하나를 도출하라.", | ||
| "objective": "검사기가 기능 전체를 강제하게 하면서 가능한 세 실행 결과를 모두 만든다.", | ||
| "language_delta": "매핑된 타입은 검사기에 객체 요구를 만들지만 Node 실행 중 속성을 할당하거나 키를 순회하지 않는다.", | ||
| "prediction_prompt": "`search` 속성만 있는 객체에 대해 TypeScript가 보고할 누락 항목을 예측하라.", | ||
| "transfer_trap": "실행 중 맵이나 딕셔너리는 데이터 구조지만 TypeScript의 매핑된 타입은 컴파일 시점 변환이다." | ||
| }, | ||
| "ts-error-handling": { | ||
| "title": "오류 처리", | ||
| "concept": "catch는 unknown 실패 값을 받으므로 복구 값을 고르기 전에 먼저 좁혀야 한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 오류 처리 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-conditional-types": { | ||
| "title": "조건부 타입으로 원소 추출하기", | ||
| "concept": "`ElementType`은 `T`가 배열 모양인지 검사하고 `infer`로 원소에 이름을 붙인다. `string[]`에서는 검사기 분기가 `string`을 선택한다.", | ||
| "worked_example": "예제는 같은 패턴에 `number[]`를 넣어 `11`을 대입한다. 실행 조건은 그 값을 검사하지 않으며 타입 선택은 이미 제거되어 있다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 오류 처리 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 오류 처리 실습의 TODO를 풀지 않는다.", | ||
| "catch는 unknown 실패 값을 받으므로 복구 값을 고르기 전에 먼저 좁혀야 한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "조건부 타입 결과가 문자열인데 원시 입력에 `Number`를 적용해 숫자를 대입한다.", | ||
| "`T extends ...`가 표준 입력 내용에 따라 실행 분기를 고른다고 기대한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 오류 처리 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "오류 처리 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`T`가 `readonly string[]`이면 항목은 어떤 타입인가?", | ||
| "Node는 검사기가 어느 조건부 타입 분기를 골랐는지 관찰할 수 있는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 오류 처리 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "숫자로 변환하지 않은 입력 텍스트를 조건부 타입에서 추출한 문자열 원소 타입에 그대로 대입하라.", | ||
| "objective": "숫자처럼 보이는 문자를 포함한 서로 다른 세 텍스트를 타입 검사 후 원문대로 출력한다.", | ||
| "language_delta": "조건부 타입은 TypeScript 타입 체계에서만 평가되며 JavaScript 조건식은 별개의 실행 문법이다.", | ||
| "prediction_prompt": "입력 텍스트 `42`가 정적 `TextElement` 타입을 바꾸는지 실행 전에 판단하라.", | ||
| "transfer_trap": "다른 언어의 컴파일 시점 템플릿이나 트레이트가 실행 코드를 만들 수 있어도 이 타입 선택은 완전히 지워진다." | ||
| }, | ||
| "ts-modules": { | ||
| "title": "모듈과 export", | ||
| "concept": "export는 파일이 다른 파일에 제공할 값을 표시하며, 모듈 방식은 여전히 Node의 규칙이 결정한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 모듈과 export 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-utility-types": { | ||
| "title": "유틸리티 타입으로 점수 패치 만들기", | ||
| "concept": "`Pick`은 `User`에서 점수를 고르고 `Partial`은 그 속성을 선택 사항으로 만든다. 빈 패치는 갱신이 없다는 뜻이며 점수값을 자동 생성하지 않는다.", | ||
| "worked_example": "예제는 다른 프로필 모델에서 선택적 활성 상태만 패치를 만든다. 불리언 대체값으로 실행 객체에 속성이 있을 수도 없을 수도 있음을 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 모듈과 export 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 모듈과 export 실습의 TODO를 풀지 않는다.", | ||
| "export는 파일이 다른 파일에 제공할 값을 표시하며, 모듈 방식은 여전히 Node의 규칙이 결정한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "점수 토큰이 없는데 1을 넣어 빈 패치 대신 임의 갱신을 만든다.", | ||
| "`Partial`이 중첩 속성도 재귀적으로 바꾸거나 기본값을 채운다고 생각한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 모듈과 export 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "모듈과 export 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "점수 토큰이 없는 `Lin` 입력은 어떤 객체로 표현해야 하는가?", | ||
| "`ScorePatch`의 `Pick` 부분은 원래 사용자의 어느 필드를 제외하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 모듈과 export 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "점수 토큰 존재 여부에 따라 빈 패치 또는 변환된 숫자 점수를 가진 패치를 만들고 누락일 때만 0을 사용하라.", | ||
| "objective": "제공된 양수와 음수 점수는 보고하고 실제로 누락된 속성만 0으로 처리한다.", | ||
| "language_delta": "유틸리티 타입은 정적 객체 모양만 바꾸고 아무 코드도 내보내지 않는다. 실제 키 존재 여부는 JavaScript 객체 생성이 정한다.", | ||
| "prediction_prompt": "`Ada 5`와 `Lin`에서 점수를 병합하기 전 패치 객체를 각각 그려 보라.", | ||
| "transfer_trap": "TypeScript `Partial`이 직렬화 데이터의 실행 파싱과 검증까지 해 준다고 가정하지 마라." | ||
| }, | ||
| "ts-classes": { | ||
| "title": "클래스와 접근 제한자", | ||
| "concept": "클래스는 상태에 메서드를 붙이고, private은 메서드 경계 뒤에 머물러야 할 상태를 문서화한다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 클래스와 접근 제한자 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-satisfies-as-const": { | ||
| "title": "리터럴 경로를 보존하며 계약 검사하기", | ||
| "concept": "`as const`는 정확한 경로 문자열을 보존하고 `satisfies`는 리터럴을 넓히지 않은 채 레코드 계약을 확인한다. 외부 키는 `Object.hasOwn`으로 따로 검증한다.", | ||
| "worked_example": "예제는 두 상태 코드를 검사하고 텍스트 `ready`를 직접 소유 키 조건식으로 좁혀 보존된 리터럴 `R`을 읽는다. 실행 중에는 평범한 객체다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 클래스와 접근 제한자 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 클래스와 접근 제한자 실습의 TODO를 풀지 않는다.", | ||
| "클래스는 상태에 메서드를 붙이고, private은 메서드 경계 뒤에 머물러야 할 상태를 문서화한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`in` 연산자를 써서 `constructor` 같은 상속 이름도 경로로 허용한다.", | ||
| "`as const`가 경로 객체를 실행 중 동결하거나 중첩 변경을 모두 막는다고 믿는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 클래스와 접근 제한자 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "클래스와 접근 제한자 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "여기서 `satisfies`는 `as Record<string, string>` 단언과 어떻게 다른가?", | ||
| "`Object.hasOwn`이 `in`보다 강한 경로 조건을 제공하는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 클래스와 접근 제한자 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "`satisfies`로 검사된 상수 객체를 인덱싱하기 전에 요청 경로가 직접 소유 속성인지 확인하라.", | ||
| "objective": "보존된 두 경로를 반환하고 알 수 없는 이름과 프로토타입 상속 이름은 거부한다.", | ||
| "language_delta": "`satisfies`는 타입 단언 없이 호환성을 검사하고 `as const`는 리터럴 타입과 읽기 전용 추론을 보존하지만 실행 객체를 얼리지는 않는다.", | ||
| "prediction_prompt": "경로 인덱싱을 결정하기 전에 `Object.hasOwn(routes, \"constructor\")`의 결과를 평가하라.", | ||
| "transfer_trap": "JavaScript `in`은 프로토타입 체인을 따라가기 때문에 선언된 직접 경로만 받아야 하는 설정에는 범위가 너무 넓다." | ||
| }, | ||
| "ts-readonly": { | ||
| "title": "readonly", | ||
| "concept": "readonly는 설정 같은 데이터를 읽을 수 있게 하되 그 타입을 통해 교체하거나 변경하지 못하게 한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 readonly 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-discriminated-unions": { | ||
| "title": "체크포인트: 태그 유니온 빠짐없이 처리하기", | ||
| "concept": "도형의 각 변형은 리터럴 `kind`와 해당 치수를 함께 가진다. 공통 태그의 스위치가 필드를 안전하게 좁히고 남을 수 없는 값을 `never`에 대입해 완전성을 검사한다.", | ||
| "worked_example": "예제 유니온은 성공과 오류를 모델링하고 두 스위치 사례에서 모두 반환한다. 직사각형이나 원 계산 없이 같은 타입 좁히기 원리를 보여 준다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 readonly 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 readonly 실습의 TODO를 풀지 않는다.", | ||
| "readonly는 설정 같은 데이터를 읽을 수 있게 하되 그 타입을 통해 교체하거나 변경하지 못하게 한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "값이 원 변형임을 증명하기 전에 `radius`를 읽는다.", | ||
| "새 종류를 추가한 뒤 넓은 기본값 분기로 누락 처리를 숨긴다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 readonly 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "readonly 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "직사각형 분기가 반환된 뒤 남을 수 있는 정확한 타입은 무엇인가?", | ||
| "시작 코드에서 원을 `never`에 대입하면 타입 검사가 실패하는 이유는 무엇인가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 readonly 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "`circle` 변형 계산을 추가하고 미래의 변형 누락을 막는 `never` 완전성 검사는 마지막에 유지하라.", | ||
| "objective": "엄격한 완전성 검사를 통과하고 세 사례에 등장하는 두 태그 도형을 측정한다.", | ||
| "language_delta": "리터럴 판별자는 실행 중 JavaScript 분기를 이끌지만 TypeScript의 타입 좁히기와 `never` 증명은 검사 후 제거된다.", | ||
| "prediction_prompt": "`circle 5`가 파싱, 태그 타입 좁히기, 숫자 반환을 거치는 경로를 실행 전에 추적하라.", | ||
| "transfer_trap": "Rust의 와일드카드 매치나 다른 언어의 기본값 스위치도 TypeScript의 넓은 대체값처럼 새 변형 누락을 숨길 수 있다." | ||
| }, | ||
| "ts-satisfies-as-const": { | ||
| "title": "satisfies와 as const", | ||
| "concept": "as const는 라우트 리터럴을 보존하고, satisfies는 그 리터럴을 넓히지 않은 채 더 넓은 Record 계약을 검사한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 satisfies와 as const 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-async-promise": { | ||
| "title": "타입이 있는 프로미스 대기하기", | ||
| "concept": "`async` 함수는 숫자 표현식을 반환해도 항상 프로미스를 돌려준다. `await`가 메인 함수 안에서 완료된 숫자를 꺼내고 마지막 오류 처리 경계가 거부된 프로미스를 관찰한다.", | ||
| "worked_example": "예제는 비동기로 `9`를 증가시켜 완료값 `10`을 기다린 뒤 별도 레코드를 쓴다. 거부 콜백은 던져진 값이 항상 `Error`라고 가정하지 않고 `unknown`을 받는다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 satisfies와 as const 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 satisfies와 as const 실습의 TODO를 풀지 않는다.", | ||
| "as const는 라우트 리터럴을 보존하고, satisfies는 그 리터럴을 넓히지 않은 채 더 넓은 Record 계약을 검사한다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "`double`에서 원래 숫자를 그대로 반환해 요구된 두 배 변환을 빠뜨린다.", | ||
| "최상위에서 `main`의 거부된 프로미스를 관찰하는 경로를 두지 않는다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 satisfies와 as const 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "satisfies와 as const 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "본문에 `await`가 아직 없어도 `double`의 정적 반환 타입은 무엇인가?", | ||
| "`await`가 프로미스 완료값을 꺼낸 뒤 `process.stdout.write` 호출은 어느 순서로 실행되는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 satisfies와 as const 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "`double`이 입력의 두 배로 완료되게 하고 `await`와 최상위 프로미스 거부 처리 경계는 그대로 유지하라.", | ||
| "objective": "양수, 0, 음수를 비동기 제어 경로를 통해 두 배로 만들어 출력한다.", | ||
| "language_delta": "프로미스 스케줄링은 JavaScript 실행 의미이며 TypeScript는 `Promise<number>`와 오류 포착 값의 타입을 검사할 뿐 실행기를 제공하지 않는다.", | ||
| "prediction_prompt": "입력이 `-3`일 때 `double`이 완료하는 값을 예측하라.", | ||
| "transfer_trap": "Python 코루틴은 예약되기 전 실행되지 않지만 JavaScript 비동기 함수 호출은 이미 진행을 시작한 프로미스를 즉시 반환한다." | ||
| }, | ||
| "ts-iterables": { | ||
| "title": "이터러블", | ||
| "concept": "for...of는 모든 이터러블을 소비하므로 배열, 문자열, Set이 같은 반복문 모양을 공유할 수 있다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 이터러블 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-modules": { | ||
| "title": "단일 파일에서 공개 API 경계 익히기", | ||
| "concept": "이 실습은 지역 API 객체로 공개 호출 표면을 설명한다. 실행기가 `.ts` 파일 하나만 제공하므로 실제 가져오기 그래프를 검증했다고 주장하지 않는다.", | ||
| "worked_example": "예제는 `DemoApi`로 변환 함수를 노출하고 `module`을 뒤집는다. ESM이나 CommonJS 이름 해석 없이도 객체 경계를 관찰할 수 있다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 이터러블 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 이터러블 실습의 TODO를 풀지 않는다.", | ||
| "for...of는 모든 이터러블을 소비하므로 배열, 문자열, Set이 같은 반복문 모양을 공유할 수 있다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "내보내기 선언 하나만으로 다른 파일이 모듈을 찾고 실행할 수 있음이 검증됐다고 생각한다.", | ||
| "API가 대문자 정규화를 약속하는데 값을 소문자로 바꾼다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 이터러블 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "이터러블 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "지역 `LabelApi` 구현이 만족해야 할 메서드 시그니처는 무엇인가?", | ||
| "진짜 다중 파일 ESM 실습에는 Node의 어느 프로젝트 설정이 더 필요한가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 이터러블 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "선언된 지역 API 동작을 구현하고 표준 입력 텍스트를 대문자로 변환해 공개 메서드의 결과로 출력하라.", | ||
| "objective": "다른 파일을 가져온 척하지 않고 ASCII와 악센트 문자의 타입이 있는 공개 표면을 연습한다.", | ||
| "language_delta": "Node는 확장자와 패키지 메타데이터에서 ESM 또는 CommonJS를 고르므로 고정 단일 파일 실행기는 양쪽 가져오기/내보내기를 모두 검증할 수 없다.", | ||
| "prediction_prompt": "JavaScript의 유니코드 대문자 변환을 적용한 `é`의 메서드 결과를 예측하라.", | ||
| "transfer_trap": "브라우저, 번들러, CommonJS는 파일 경로를 서로 다르게 해석하므로 이 실습 통과를 다중 파일 모듈 숙달로 보지 마라." | ||
| }, | ||
| "ts-array-methods": { | ||
| "title": "map, filter, reduce", | ||
| "concept": "filter는 항목을 고르고, map은 변환하며, reduce는 시퀀스를 최종 답 하나로 접는다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.", | ||
| "worked_example": "예제는 `example:` 라벨이 붙은 데모 출력으로 map, filter, reduce 흐름을 보여 준다. 값이 stdout으로 가기 전에 어느 줄에서 바뀌는지 보고, 실습에서는 같은 생각을 별도 데이터에 적용한다.", | ||
| "ts-error-handling": { | ||
| "title": "캡스톤: 명령 검증하고 실행하기", | ||
| "concept": "명령 파서는 숫자 토큰 전체를 검증하고 0으로 나누기를 거부한 뒤 판별 가능한 명령을 만든다. 비동기 실행은 프로미스를 반환하고 마지막 오류 처리에서는 `unknown`을 좁힌다.", | ||
| "worked_example": "예제는 `parseInt`가 `12px`의 숫자 접두사를 받아들이고 `bad`에서는 예외 대신 `NaN`을 돌려준다는 점을 보여 준다. 던져진 문자열도 `typeof`로 좁힌다.", | ||
| "common_mistakes": [ | ||
| "마지막 출력 줄만 바꾸고 map, filter, reduce 규칙을 코드 경로에 남기지 않는다.", | ||
| "`example:` 라벨이 붙은 데모 출력을 그대로 복사해 map, filter, reduce 실습의 TODO를 풀지 않는다.", | ||
| "filter는 항목을 고르고, map은 변환하며, reduce는 시퀀스를 최종 답 하나로 접는다. 이 핵심을 확인하지 않고 judge 출력만 맞춘다." | ||
| "검증에 `parseInt`를 써서 뒤에 문자가 붙은 토큰을 부분 숫자로 받아들인다.", | ||
| "JavaScript에서는 어떤 값도 던질 수 있는데 오류 처리 값이 항상 `Error` 인스턴스라고 가정한다." | ||
| ], | ||
| "self_check": [ | ||
| "예제에서 map, filter, reduce 흐름이 적용된 뒤 `example:` 출력으로 이어지는 표현식은 무엇인가?", | ||
| "map, filter, reduce 실습의 TODO 줄은 데모가 아니라 자기 데이터에서 어떤 값을 만들어야 하는가?" | ||
| "`Number`는 `12px`를 거부하지만 `Number.parseInt`는 일부를 받는 이유는 무엇인가?", | ||
| "어느 명령 분기가 비동기 실행 전에 0인 제수를 검사하는가?" | ||
| ], | ||
| "exercise_prompt": "TODO를 완성해 실습 데이터에 map, filter, reduce 흐름을 적용하라. stdout은 judge가 비교할 형태로 깨끗하게 두고, `example:` 라벨은 예제에만 남긴다." | ||
| "exercise_prompt": "토큰 전체를 확인하는 엄격한 파서를 만들고 명령 유니온을 빠짐없이 실행하며 `unknown` 실패값을 좁혀라.", | ||
| "objective": "유효한 덧셈과 나눗셈은 계산하고 부분 숫자, 비유한 숫자, 0으로 나누기는 최상위 오류 처리 경계에서 `invalid`로 출력한다.", | ||
| "language_delta": "엄격한 TypeScript 오류 처리에서는 JavaScript가 어떤 값도 던질 수 있어 `unknown`을 쓴다. `parseInt`의 `NaN`은 오류 처리 구문을 실행하지 않는다.", | ||
| "prediction_prompt": "`add 12px 1`이 변환을 지날 때 어느 정확한 검증에서 거부되는지 추적하라.", | ||
| "transfer_trap": "예외만으로 파싱 실패를 표현하는 언어의 패턴은 JavaScript에 그대로 맞지 않는다. 숫자 변환은 흔히 `NaN`으로 실패를 알린다." | ||
| } | ||
| } | ||
| } |
@@ -7,422 +7,506 @@ { | ||
| "ts-output": { | ||
| "title": "控制台与 stdout", | ||
| "concept": "控制台输出是评测比较的契约,因此要区分 console.log 的自动换行和 process.stdout.write 的逐字节输出。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示控制台与 stdout的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "精确写入标准输出", | ||
| "concept": "`process.stdout.write` 只写入提供的字符串,不会自动增加分隔符或换行。因此冒号与末尾换行都是程序契约的一部分;类型注解在 Node 执行前会被移除。", | ||
| "worked_example": "示例保留带类型的姓名和分数,却刻意写出不同的 `demo Mina=4` 记录。注解不会进入 JavaScript,模板字符串里的等号与换行却会成为可观察文本。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把控制台与 stdout的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决控制台与 stdout练习里的 TODO。", | ||
| "没有确认“控制台输出是评测比较的契约,因此要区分 console.log 的自动换行和 process.stdout.write 的逐字节输出。”这个要点,只去凑 judge 输出。" | ||
| "改用 `console.log`,失去对最后换行的明确控制。", | ||
| "直接连接姓名和分数,产生 `Ada7` 这类边界不清的输出。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用控制台与 stdout,然后才到达 `example:` 输出?", | ||
| "控制台与 stdout练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "示例中姓名后紧接着的确切字符是什么?", | ||
| "写入调用的哪一段负责产生行尾?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把控制台与 stdout应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "读取标准输入中的姓名与分数,通过 `process.stdout.write` 形成一条冒号分隔记录,并确保末尾恰好只有一个换行。", | ||
| "objective": "每组输入都能得到 `name:score` 形态且没有额外空格、说明文字或重复换行。", | ||
| "language_delta": "`console.log` 会自行附加换行,而 `process.stdout.write` 不添加任何字符;Node 只运行擦除类型后的 JavaScript。", | ||
| "prediction_prompt": "执行前逐字符写出一个 Unicode 姓名与两位分数经过模板插值后的完整输出。", | ||
| "transfer_trap": "浏览器控制台主要供检查值,在线评测比较的却是程序标准输出;两种表面相似的显示不能混用。" | ||
| }, | ||
| "ts-let-const": { | ||
| "title": "let 与 const", | ||
| "concept": "const 标出不会重新赋值的绑定,let 则让解题过程中会变化的累加值更清楚。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示let 与 const的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-input": { | ||
| "title": "解析 Node 标准输入", | ||
| "concept": "Node 把重定向标准输入暴露为文件描述符零。一次读取、去除外围空白,并为空字符串设置分支,可建立稳定的数字片段流水线。", | ||
| "worked_example": "固定文本 `4 6` 与练习案例不同;它先按空白拆分,再用 `Number` 转换,最后从零开始折叠,两项合计为十。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把let 与 const的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决let 与 const练习里的 TODO。", | ||
| "没有确认“const 标出不会重新赋值的绑定,let 则让解题过程中会变化的累加值更清楚。”这个要点,只去凑 judge 输出。" | ||
| "对空白后的空字符串直接拆分,把唯一的空片段转换成伪造的零。", | ||
| "只读取第一行,输入数字分布到多行时遗漏后续内容。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用let 与 const,然后才到达 `example:` 输出?", | ||
| "let 与 const练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "去除空白后没有字符时,为什么数字数组必须为空?", | ||
| "折叠初始值如何决定空输入的总和?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把let 与 const应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "去掉只取首项的捷径,遍历完整的空白分隔数字序列求和;单行、多行与空输入都必须沿同一解析路径处理。", | ||
| "objective": "同一行、跨多行以及完全为空的标准输入,都能得到正确算术总和。", | ||
| "language_delta": "`readFileSync(0, \"utf8\")` 是 Node 运行时接口;TypeScript 只检查声明形状,并不代替实际读取。", | ||
| "prediction_prompt": "输入先是 -1,下一行是 5 和 2 时,先列出数组内容,再计算折叠结果。", | ||
| "transfer_trap": "其他语言常见的逐次提示式读取并不匹配 Node 一次读取完整输入流的方式,照搬会使换行布局影响逻辑。" | ||
| }, | ||
| "ts-primitives": { | ||
| "title": "基础类型", | ||
| "concept": "string、number 和 boolean 注解描述了解析与分支代码期望的标量值。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示基础类型的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "title": "把文本转换为基本值", | ||
| "concept": "`string` 注解只向检查器描述值,而 `Number` 才执行运行时转换。通过与否必须比较数值分数和门槛,不能比较文本拼写或长度。", | ||
| "worked_example": "示例声明一个字符串、一个数字和一个布尔值,再输出独立演示记录。三个运行时值都已经具有注解所描述的基本形态。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把基础类型的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决基础类型练习里的 TODO。", | ||
| "没有确认“string、number 和 boolean 注解描述了解析与分支代码期望的标量值。”这个要点,只去凑 judge 输出。" | ||
| "只把输入片段标注成 `number`,却没有真正转换字符串。", | ||
| "依赖关系运算的隐式强制转换,使非法数字从何处进入计算变得不清楚。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用基础类型,然后才到达 `example:` 输出?", | ||
| "基础类型练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "练习中哪个表达式把片段从字符串语义变成数字语义?", | ||
| "门槛为零且分数也为零时,为什么应当通过?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把基础类型应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "显式转换分数和门槛两个数字字段,使用包含相等边界的比较,同时让姓名始终保持文本值并原样进入记录。", | ||
| "objective": "三个边界案例都能输出已解析分数以及正确的通过或重试分类。", | ||
| "language_delta": "TypeScript 5.9 可拒绝不一致的基本类型用法,但注解会被擦除,无法替标准输入执行数值转换。", | ||
| "prediction_prompt": "在求值比较运算符前,先判断输入 `Bo 0 0` 应进入哪个状态。", | ||
| "transfer_trap": "某些带运行时类型声明的语言可能在赋值处触发转换;TypeScript 声明本身绝不会改变输入值。" | ||
| }, | ||
| "ts-strings-templates": { | ||
| "title": "字符串与模板", | ||
| "concept": "模板字面量把格式和数值放在一起,trim 等字符串方法会返回新的字符串。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示字符串与模板的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-let-const": { | ||
| "title": "累加值可重绑,标签保持固定", | ||
| "concept": "稳定标签使用 `const`,需要变化的总和使用 `let`。两个关键字约束的是绑定能否再次赋值,并不说明绑定所指对象是否被冻结。", | ||
| "worked_example": "示例让 `demo` 标签保持不变,把总和从 5 加上 -2 后变为 3。数值绑定发生重赋值,而标签绑定没有变化。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把字符串与模板的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决字符串与模板练习里的 TODO。", | ||
| "没有确认“模板字面量把格式和数值放在一起,trim 等字符串方法会返回新的字符串。”这个要点,只去凑 judge 输出。" | ||
| "用 `const` 声明累加器,随后又尝试给它分配新的数值。", | ||
| "误认为 `const` 指向的对象不能修改任何可写属性。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用字符串与模板,然后才到达 `example:` 输出?", | ||
| "字符串与模板练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "两个示例绑定中,哪一个接收了第二次赋值?", | ||
| "若 `settings` 指向可变对象,`const` 会禁止修改其 `enabled` 属性吗?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把字符串与模板应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "固定输入标签,把两个已转换数字累加到允许重赋值的总和变量中;正数、负数与零都不得通过硬编码输出处理。", | ||
| "objective": "标签始终原样保留,而三种加法组合都产生正确的算术结果。", | ||
| "language_delta": "JavaScript 在运行时执行 `const` 绑定规则,数值注解则是随后会被擦除的检查信息。", | ||
| "prediction_prompt": "针对 `net -2 5`,按每次赋值顺序追踪总和变量的值。", | ||
| "transfer_trap": "Rust 的不可变绑定和 Java 的 `final` 引用拥有不同所有权及对象修改后果,不能只凭相似名称类推。" | ||
| }, | ||
| "ts-arrays-tuples": { | ||
| "title": "数组与元组", | ||
| "concept": "数组保存有顺序的同类值,元组在小记录中同时固定位置和类型。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示数组与元组的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-strings-templates": { | ||
| "title": "只整理姓名,保留分数字符", | ||
| "concept": "竖线分隔的两个字段采用不同空白规则:仅删除一个末尾 `LF` 或 `CRLF`,去除姓名两侧空白,却保留分数字段中的每个字符,包括结尾空格。", | ||
| "worked_example": "固定记录 Mira 的分数后带两个空格和一个换行。锚定正则只去掉换行,因此模板插值时两个分数空格仍然存在。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把数组与元组的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决数组与元组练习里的 TODO。", | ||
| "没有确认“数组保存有顺序的同类值,元组在小记录中同时固定位置和类型。”这个要点,只去凑 judge 输出。" | ||
| "对整条记录调用 `trimEnd`,悄悄删除有意义的分数填充。", | ||
| "把字符串长度当成用户看到的字符数,忽略代理对会占两个 `UTF-16` 单元。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用数组与元组,然后才到达 `example:` 输出?", | ||
| "数组与元组练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "删除末尾换行后,` Mira |9 ` 两个字段各保留哪些空格?", | ||
| "为什么只整理姓名不会碰到分数末尾的两个空格?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把数组与元组应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "先精确移除一个末尾 `CRLF` 或 `LF`,仅对竖线左侧姓名去除空白,再把右侧分数字符毫无删改地放到冒号后。", | ||
| "objective": "三条记录都能规范姓名,并完整保留分数字段末端具有意义的空格。", | ||
| "language_delta": "JavaScript 的 `trimEnd` 删除全部尾随空白,锚定替换却只移除一段传输行尾;索引仍按 `UTF-16` 单元计算。", | ||
| "prediction_prompt": "标出 ` Ada |7 ` 后跟一个 `LF` 经末尾正则处理后仍留下的每个空格。", | ||
| "transfer_trap": "其他语言的通用去空白操作也可能删除有效载荷;这里只应移除传输换行,不能清洗整个记录。" | ||
| }, | ||
| "ts-objects": { | ||
| "title": "对象类型", | ||
| "concept": "对象类型把必需字段写清楚,避免计算时漏掉 width、height 等属性。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示对象类型的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-arrays-tuples": { | ||
| "title": "用元组返回姓名与总和", | ||
| "concept": "第一个输入片段是姓名,其余片段组成数字数组。类型 `[string, number]` 固定两个位置的静态含义,但运行时仍是普通 JavaScript 数组。", | ||
| "worked_example": "示例先汇总独立数组 `[1, 4]`,再把 Neo 与总和放入带类型的二元组。通过位置零和一读取,能看到运行时容器仍为数组。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把对象类型的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决对象类型练习里的 TODO。", | ||
| "没有确认“对象类型把必需字段写清楚,避免计算时漏掉 width、height 等属性。”这个要点,只去凑 judge 输出。" | ||
| "把每个片段都当成数字,折叠前丢失开头姓名。", | ||
| "误以为元组注解会创建不同于 `Array` 的运行时容器。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用对象类型,然后才到达 `example:` 输出?", | ||
| "对象类型练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "示例二元组的第二位置允许什么类型?", | ||
| "输入只有 `Bo` 而没有数字尾部时,总和应该如何得到?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把对象类型应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "汇总姓名后的完整数字数组,把原姓名与总和放进声明好的二元组;负数尾部和空数字尾部都须由同一折叠处理。", | ||
| "objective": "多项数值、含负数数值和没有数值三类输入都能通过一种二元形态表示。", | ||
| "language_delta": "元组的位置检查在 Node 执行前消失;后续可迭代与数组方法练习会用其他方式消费同一序列。", | ||
| "prediction_prompt": "对 `Lin -1 4 2`,先列出数字数组,再写出二元组第二位置的值。", | ||
| "transfer_trap": "TypeScript 元组在运行时并不是小型不可变记录,这与某些语言的值记录构造不同。" | ||
| }, | ||
| "ts-functions": { | ||
| "title": "函数", | ||
| "concept": "函数参数和返回类型把已解析输入、计算与输出之间的边界写清楚。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示函数的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-iterables": { | ||
| "title": "用 `for...of` 消费可迭代对象", | ||
| "concept": "`Iterable<string>` 承诺迭代协议,不承诺数字索引。`for...of` 通过该协议取值,因此可处理数组、集合、字符串和自定义可迭代对象。", | ||
| "worked_example": "示例把含 T、S 的集合交给收集函数,按插入顺序得到两项,无需调用数组专属方法,也没有假设输入存在数字索引。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把函数的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决函数练习里的 TODO。", | ||
| "没有确认“函数参数和返回类型把已解析输入、计算与输出之间的边界写清楚。”这个要点,只去凑 judge 输出。" | ||
| "对可迭代接口使用数字索引,尽管接口并未保证这些属性。", | ||
| "迭代器已经推进到结束后仍重复使用,期待旧值重现。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用函数,然后才到达 `example:` 输出?", | ||
| "函数练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`for...of` 要求输入实现哪一种能力?", | ||
| "空片段列表为什么仍应产生一个输出换行?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把函数应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "通过迭代协议访问标准输入的全部片段,并按遇见顺序连接;不得只读取数组中的某个固定位置,也要覆盖空序列。", | ||
| "objective": "不依赖数组索引,正确连接两项、三项或零项输入。", | ||
| "language_delta": "TypeScript 的可迭代类型只描述 JavaScript 迭代协议,不会在运行时增加集合储存或重放行为。", | ||
| "prediction_prompt": "逐步写出处理 `a b c` 时,每次迭代后累积文本的状态。", | ||
| "transfer_trap": "Python 迭代器与 Java 流同样会被消费,但重放规则和所有权约定并不相同。" | ||
| }, | ||
| "ts-input": { | ||
| "title": "Node stdin 解析", | ||
| "concept": "Node 解法通常一次读取文件描述符 0,按空白切分,再在数值计算前转换 token。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示Node stdin 解析的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-array-methods": { | ||
| "title": "安全地筛选、平方并折叠", | ||
| "concept": "清晰的数组流水线先选择偶数,再计算其平方,最后折叠所有乘积。给 `reduce` 提供初始零,才能定义没有元素通过时的结果。", | ||
| "worked_example": "示例对 `[2, 5, 6]` 使用另一条件,并把通过者加倍后求和。它展示方法顺序,却不复用练习的偶数平方结果。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把Node stdin 解析的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决Node stdin 解析练习里的 TODO。", | ||
| "没有确认“Node 解法通常一次读取文件描述符 0,按空白切分,再在数值计算前转换 token。”这个要点,只去凑 judge 输出。" | ||
| "先变换后筛选,使谓词检查的已不是原始数值。", | ||
| "省略 `reduce` 初始值,全部元素被筛掉时抛出异常。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用Node stdin 解析,然后才到达 `example:` 输出?", | ||
| "Node stdin 解析练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "负二在哪一个阶段会变成正四?", | ||
| "筛选结果为空时,折叠器最初取得什么累计值?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把Node stdin 解析应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "修正奇偶筛选条件,同时保留平方映射和从零开始的折叠;混合符号及全奇数输入都必须给出定义良好的总和。", | ||
| "objective": "混合整数、负数和全奇数序列都能得到准确的偶数平方和。", | ||
| "language_delta": "这些方法属于标准 JavaScript 数组并逐阶段遍历;TypeScript 只推断回调类型,不会把流水线改成惰性执行。", | ||
| "prediction_prompt": "输入 `-2 -1 0` 时,写出筛选后和平方后的两个中间数组。", | ||
| "transfer_trap": "Rust 迭代适配器在消费前保持惰性,JavaScript 数组方法却会在每个阶段立即遍历或分配。" | ||
| }, | ||
| "ts-control-flow": { | ||
| "title": "控制流", | ||
| "concept": "if 和循环决定哪些值进入累加,因此分支条件必须符合题目的数据规则。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示控制流的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-objects": { | ||
| "title": "通过结构化对象计算面积", | ||
| "concept": "矩形类型要求宽与高两个成员,面积函数可接收任何结构兼容的对象。此计算不需要相同构造器名称,也不需要先声明类。", | ||
| "worked_example": "示例对象还含颜色属性,绑定到变量后仍能传给只读取宽和高的函数。多余字段继续存在于运行时,只是没有参与计算。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把控制流的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决控制流练习里的 TODO。", | ||
| "没有确认“if 和循环决定哪些值进入累加,因此分支条件必须符合题目的数据规则。”这个要点,只去凑 judge 输出。" | ||
| "构造对象时漏掉一个已解析尺寸,悄悄用占位数替代真实输入。", | ||
| "认为两个值必须来自同名构造器,TypeScript 才会接受其形状。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用控制流,然后才到达 `example:` 输出?", | ||
| "控制流练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "面积函数实际读取彩色对象的哪些成员?", | ||
| "宽为零时为什么乘积必然为零?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把控制流应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "用标准输入中的两个尺寸构造完整矩形对象,再交给带类型的面积函数;不得把高度固定成占位值,零宽案例也必须自然得到零。", | ||
| "objective": "普通矩形、零宽矩形和高度为一的矩形都通过结构数据得到正确乘积。", | ||
| "language_delta": "对象类型是执行前擦除的检查器描述;类方法才产生运行时行为,`readonly` 也只提供浅层类型访问限制。", | ||
| "prediction_prompt": "输入尺寸 `7 1` 时,先写出对象形状,再预测乘法结果。", | ||
| "transfer_trap": "Java 通常强调声明类身份,TypeScript 则通常接受独立创建但成员兼容的值。" | ||
| }, | ||
| "ts-union-narrowing": { | ||
| "title": "联合与收窄", | ||
| "concept": "联合类型的值在使用仅属于字符串或数字的操作前,需要先通过运行时检查收窄。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示联合与收窄的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-classes": { | ||
| "title": "通过类方法更新实例状态", | ||
| "concept": "每个计数器实例拥有可变字段,并通过递增方法暴露状态变化。方法必须先存回新值再返回,后续调用才能观察到更新。", | ||
| "worked_example": "示例实例从 8 开始,递增一次得到 9。`private` 帮助检查器限制源码访问,但发出的 JavaScript 字段仍是普通属性。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把联合与收窄的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决联合与收窄练习里的 TODO。", | ||
| "没有确认“联合类型的值在使用仅属于字符串或数字的操作前,需要先通过运行时检查收窄。”这个要点,只去凑 judge 输出。" | ||
| "只返回 `this.value + 1` 而不保存,下一次调用仍从旧状态开始。", | ||
| "把 TypeScript 的 `private` 当成 JavaScript `#value` 字段的运行时隐私。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用联合与收窄,然后才到达 `example:` 输出?", | ||
| "联合与收窄练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "同一实例连续调用两次后,第二次应返回哪个状态?", | ||
| "哪一种类字段写法会在 JavaScript 运行时真正限制访问?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把联合与收窄应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "修改递增方法,使它先更新接收者自己的字段再返回结果;正数、负数和较大初值都只能增加一次,并让后续调用观察新状态。", | ||
| "objective": "任意给定起点都由同一实例方法准确前进一步,并保存该变化。", | ||
| "language_delta": "这里的 `private` 是会被擦除的检查信息;以井号开头的 ECMAScript 字段才具有运行时隐私语义。", | ||
| "prediction_prompt": "起点为 -1 时,分别记录调用前字段、存回的新字段和返回值。", | ||
| "transfer_trap": "Java 的私有访问由虚拟机执行,边界比 TypeScript 被擦除的修饰符更强。" | ||
| }, | ||
| "ts-literal-types": { | ||
| "title": "字面量类型", | ||
| "concept": "字面量联合把命令或模式限制为 'left'、'right' 这样的精确值。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示字面量类型的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-readonly": { | ||
| "title": "读取浅层只读配置", | ||
| "concept": "只读配置允许消费者查看姓名与分数序列,却禁止通过该类型修改。仅把属性标成只读只会阻止替换,因此嵌套数组也必须声明为只读;两者都不会冻结运行时对象。", | ||
| "worked_example": "示例把姓名属性和数字数组都声明为只读,只读取数组长度而不写入,所以严格检查与擦除后的 Node 执行都能成功。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把字面量类型的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决字面量类型练习里的 TODO。", | ||
| "没有确认“字面量联合把命令或模式限制为 'left'、'right' 这样的精确值。”这个要点,只去凑 judge 输出。" | ||
| "对只读数字数组调用 `push`,期待严格 TypeScript 放行。", | ||
| "认为 `readonly` 会调用 `Object.freeze` 或递归锁住嵌套对象。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用字面量类型,然后才到达 `example:` 输出?", | ||
| "字面量类型练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "为何起始代码在任何测试输入到达 Node 前就会失败?", | ||
| "另一个指向同一底层数组的可变别名仍能添加元素吗?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把字面量类型应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "删除对只读视图的非法写入,仅通过读取已有分数计算总和;空数组与有符号分数都不得依赖修改容器。", | ||
| "objective": "先通过严格类型检查,再为每个配置姓名输出原分数序列的准确总和。", | ||
| "language_delta": "`readonly` 默认浅层且只存在于类型系统;即使嵌套成员也标注只读,JavaScript 内存仍未被冻结。", | ||
| "prediction_prompt": "不要预测输出,先判断 `config.scores.push(0)` 会触发哪类编译诊断。", | ||
| "transfer_trap": "Rust 的不可变借用受所有权规则保护,并不等同于 TypeScript 中可被擦除的只读视图。" | ||
| }, | ||
| "ts-optional-nullish": { | ||
| "title": "可选与空值合并", | ||
| "concept": "可选字段可能是 undefined,而 ?? 会保留分数 0 这样的有效假值。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示可选与空值合并的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-control-flow": { | ||
| "title": "保持包含终点的循环边界", | ||
| "concept": "循环应访问从一到上界的所有整数,并用严格余数比较选择奇数。终点是否参与完全由循环条件中的关系运算符决定。", | ||
| "worked_example": "示例遍历到 5,却故意累计偶数,得到与练习无关的演示总和。其短分支仍展示严格相等和闭合上界。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把可选与空值合并的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决可选与空值合并练习里的 TODO。", | ||
| "没有确认“可选字段可能是 undefined,而 ??”这个要点,只去凑 judge 输出。" | ||
| "使用 `< n`,漏掉本应参与的奇数终点。", | ||
| "使用宽松相等,让 JavaScript 强制转换造成无关值相等。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用可选与空值合并,然后才到达 `example:` 输出?", | ||
| "可选与空值合并练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "上界为三时,最后一个贡献总和的循环值是什么?", | ||
| "为何上界六可能掩盖缺陷,而上界三能暴露它?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把可选与空值合并应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "只修复循环的闭区间边界,保留奇数判断和累加器;零上界不得进入循环,奇数上界不能被遗漏,偶数终点也不得额外加入。", | ||
| "objective": "正上界与零上界都能正确汇总一到终点之间的全部奇数。", | ||
| "language_delta": "JavaScript 条件使用运行时布尔与强制规则,TypeScript 的控制流分析则沿已证明分支另外收窄静态类型。", | ||
| "prediction_prompt": "分别列出上界三在 `<` 和 `<=` 条件下会访问的值。", | ||
| "transfer_trap": "Python 的 `range` 默认排除停止值,照搬熟悉边界会错误漏掉 TypeScript 循环的终点。" | ||
| }, | ||
| "ts-interfaces-aliases": { | ||
| "title": "接口与类型别名", | ||
| "concept": "接口为对象契约命名,类型别名还能为联合、元组和组合类型表达式命名。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示接口与类型别名的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-functions": { | ||
| "title": "检查点:带类型的计算边界", | ||
| "concept": "函数签名同时承诺可接收的输入与返回结果。起始代码的乘法产生数字,却声明返回字符串,严格检查应在执行前拒绝这一矛盾。", | ||
| "worked_example": "示例定义接收两个数字并返回数字的周长函数,固定尺寸产生与面积案例不同的演示值。签名与实际返回表达式保持一致。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把接口与类型别名的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决接口与类型别名练习里的 TODO。", | ||
| "没有确认“接口为对象契约命名,类型别名还能为联合、元组和组合类型表达式命名。”这个要点,只去凑 judge 输出。" | ||
| "为迎合错误的返回注解而把正确乘法结果转换成字符串。", | ||
| "在计算函数内部打印,混淆返回数据与写入标准输出。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用接口与类型别名,然后才到达 `example:` 输出?", | ||
| "接口与类型别名练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪一处声明建立了面积函数的返回承诺?", | ||
| "两个输入片段在什么位置变成参与乘法的数字?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把接口与类型别名应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "修正函数的返回类型契约,使严格检查接受现有数值计算;解析和外层输出边界保持不变,也不得改成字符串结果。", | ||
| "objective": "一个带注解函数既能通过检查,又能计算三组矩形面积。", | ||
| "language_delta": "TypeScript 在执行前验证参数和结果用法;函数内部的分支、循环与乘法仍是普通 JavaScript 行为。", | ||
| "prediction_prompt": "不看任何案例输出,先判断起始代码会归类为哪一种类型检查失败。", | ||
| "transfer_trap": "带运行时方法签名的语言可能在编译或执行期强制契约;TypeScript 检查后会擦除它。" | ||
| }, | ||
| "ts-generics": { | ||
| "title": "泛型", | ||
| "concept": "泛型让可复用代码在读取数组或返回值时保留调用方的元素类型。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示泛型的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-union-narrowing": { | ||
| "title": "格式化前收窄联合类型", | ||
| "concept": "`string | number` 只暴露两个成员都安全的操作。使用 `typeof` 检查真实运行时基本值后,检查器才允许调用各自专属的格式化方法。", | ||
| "worked_example": "示例把字符串转小写,把固定数字保留一位小数。它展示两条分支,却不复用练习要求的大写或整数格式。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把泛型的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决泛型练习里的 TODO。", | ||
| "没有确认“泛型让可复用代码在读取数组或返回值时保留调用方的元素类型。”这个要点,只去凑 judge 输出。" | ||
| "直接在可能为数字的联合值上调用 `toUpperCase`。", | ||
| "把输入类别片段当成类型断言,而没有构造并检查实际运行值。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用泛型,然后才到达 `example:` 输出?", | ||
| "泛型练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`Number(text)` 创建的成员由 `typeof` 报告为什么?", | ||
| "为什么 `toFixed(0)` 会把 -1.6 格式化为 -2?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把泛型应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "增加一条真实的运行时收窄分支:字符串成员转大写,数字成员按零位小数格式化;联合值未经证明前不得调用专属方法。", | ||
| "objective": "字符串命令和正负数字命令都能通过严格检查并采用各自格式。", | ||
| "language_delta": "收窄把 JavaScript 运行时判断与检查器推理连接起来;任意标准输入仍须验证,强制断言不会提供证据。", | ||
| "prediction_prompt": "对 `n -1.6`,先选择收窄分支,再写出格式化文本。", | ||
| "transfer_trap": "Python 允许方法调用直到运行时失败,TypeScript 则会在检查阶段拒绝不安全的联合操作。" | ||
| }, | ||
| "ts-keyof-typeof": { | ||
| "title": "keyof 与 typeof", | ||
| "concept": "typeof 捕获值的静态形状,keyof 把这个形状转换为允许的键联合。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示keyof 与 typeof的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-literal-types": { | ||
| "title": "进入字面量联合前验证文本", | ||
| "concept": "方向类型只含 `left` 与 `right` 两个字符串字面量。标准输入最初仍是无限制字符串,必须用运行时比较证明成员资格,才能安全交给方向函数。", | ||
| "worked_example": "示例使用另一组 `north` 与 `south` 的联合,并直接传入已知字面量。它能产生演示箭头,却没有验证外部数据。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把keyof 与 typeof的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决keyof 与 typeof练习里的 TODO。", | ||
| "没有确认“typeof 捕获值的静态形状,keyof 把这个形状转换为允许的键联合。”这个要点,只去凑 judge 输出。" | ||
| "把任意文本强制断言成方向类型,误以为断言执行了运行时检查。", | ||
| "让二分方向函数把所有未知词都当成右转。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用keyof 与 typeof,然后才到达 `example:` 输出?", | ||
| "keyof 与 typeof练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "为什么 `raw as Direction` 会让 `up` 在运行时进入方向函数?", | ||
| "同时检查两个合法词后,真分支中的 `raw` 会收窄成什么类型?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把keyof 与 typeof应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "对标准输入明确比较两个允许的方向字面量,通过时才调用映射函数,其他文本统一报告无效;不得用类型断言冒充验证。", | ||
| "objective": "左右方向映射为短标记,同时拒绝未识别的命令文本。", | ||
| "language_delta": "字面量联合约束被检查的源码,擦除后 JavaScript 仍接收普通字符串,因此成员判断必须真实执行。", | ||
| "prediction_prompt": "先预测缺陷版本对 `up` 会走哪条分支,再与正确拒绝路径比较。", | ||
| "transfer_trap": "其他语言的封闭枚举可能在构造时自动验证;TypeScript 字符串联合不会生成任何运行时容器。" | ||
| }, | ||
| "ts-indexed-access": { | ||
| "title": "索引访问类型", | ||
| "concept": "索引访问类型复用已有模型中的属性类型和元素类型,避免手写重复。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示索引访问类型的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-optional-nullish": { | ||
| "title": "仅为空值提供默认分数", | ||
| "concept": "解析后的分数是数字或 `null`。空值合并只对 `null` 与 `undefined` 使用备用值,因此有意义的数字零会被保留。", | ||
| "worked_example": "示例把可选计数设为零并应用 `?? 8`,输出仍为零。它证明精确的缺失规则,却不复用练习姓名或备用分数。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把索引访问类型的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决索引访问类型练习里的 TODO。", | ||
| "没有确认“索引访问类型复用已有模型中的属性类型和元素类型,避免手写重复。”这个要点,只去凑 judge 输出。" | ||
| "使用逻辑或,因为零是假值而错误替换合法分数。", | ||
| "不解析文本 `none`,让字符串缺失标记混入数值计算。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用索引访问类型,然后才到达 `example:` 输出?", | ||
| "索引访问类型练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "哪些运行时值会触发 `??` 的右操作数?", | ||
| "分数文本是 `none` 时,变量实际得到什么值?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把索引访问类型应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "改用空值合并,使明确的零保持为零,只有真正缺失才使用分数 10;姓名、正分数与输出形态均保持原样。", | ||
| "objective": "输出记录能清楚区分零、空值以及正常正分数。", | ||
| "language_delta": "开启严格空值检查后,TypeScript 在联合中显式表示 `null`;实际默认行为仍由 JavaScript 的 `??` 执行。", | ||
| "prediction_prompt": "对 `Ada 0`,先判断默认表达式左侧值,再说明右侧是否求值。", | ||
| "transfer_trap": "Python 的 `or` 与 JavaScript 的 `||` 都使用宽泛真假规则,不能代替只针对空值的合并运算。" | ||
| }, | ||
| "ts-mapped-types": { | ||
| "title": "映射类型", | ||
| "concept": "映射类型转换对象类型中的每个键,让派生形状与来源保持一致。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示映射类型的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-interfaces-aliases": { | ||
| "title": "组合结构化契约", | ||
| "concept": "姓名由接口命名,分数由类型别名描述,两者交叉后要求对象同时具有两个字段。兼容性来自成员结构,而不是构造器继承关系。", | ||
| "worked_example": "独立演示把标签结构与权重结构交叉,再建立一个兼容对象。读取两项成员展示组合,而不会重复练习模型。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把映射类型的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决映射类型练习里的 TODO。", | ||
| "没有确认“映射类型转换对象类型中的每个键,让派生形状与来源保持一致。”这个要点,只去凑 judge 输出。" | ||
| "构造交叉对象时擅自给已解析分数加一,破坏来源数据。", | ||
| "认为接口和等价对象别名会生成不同运行时种类。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用映射类型,然后才到达 `example:` 输出?", | ||
| "映射类型练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "值必须同时具备哪两个属性才能满足交叉类型?", | ||
| "类型别名能命名联合,而接口能否直接做到同一件事?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把映射类型应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "用未改动的姓名和转换后的数值分数构造交叉对象,再直接读取两个属性形成输出;零和负分也必须原样保留。", | ||
| "objective": "正分、零分与负分都能经过同一个结构化对象准确输出。", | ||
| "language_delta": "接口可进行声明合并,类型别名可表达联合;两者在运行时都消失,类与只读视图另有不同职责。", | ||
| "prediction_prompt": "输入 `Bo -2` 时,先写出交叉对象的两个属性,再考虑模板插值。", | ||
| "transfer_trap": "Java 接口参与名义声明与虚拟机元数据,不同于 TypeScript 被擦除的结构接口。" | ||
| }, | ||
| "ts-conditional-types": { | ||
| "title": "条件类型", | ||
| "concept": "条件类型根据类型关系选择分支,infer 会抽出匹配到的部分。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示条件类型的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-generics": { | ||
| "title": "泛型保留首元素类型", | ||
| "concept": "`first<T>` 保持调用方元素类型,空序列则返回 `undefined`。返回联合已经表达缺失,因此实现无需 `any`、强制断言或非空断言。", | ||
| "worked_example": "示例定义相反方向的泛型末项函数,并传入三个字符串。推断结果是字符串或 `undefined`,运行时选择最后一项。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把条件类型的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决条件类型练习里的 TODO。", | ||
| "没有确认“条件类型根据类型关系选择分支,infer 会抽出匹配到的部分。”这个要点,只去凑 judge 输出。" | ||
| "返回索引一,把第二项误认为首项。", | ||
| "用非空断言强取值,忽略空只读数组是合法输入。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用条件类型,然后才到达 `example:` 输出?", | ||
| "条件类型练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "传入字符串数组时,`T` 会推断成什么?", | ||
| "为何即使部分案例非空,返回类型仍包含 `undefined`?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把条件类型应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "让泛型辅助函数返回索引零,并保留调用方的显式空值备用文本;单项、多项和空片段数组都不得越界假设。", | ||
| "objective": "两个非空输入选择首片段,空输入稳定输出约定的 `none`。", | ||
| "language_delta": "泛型在检查阶段维持类型关系,Node 执行前被擦除;后续键提取、映射和条件类型在其上继续派生。", | ||
| "prediction_prompt": "推断单元素字符串数组调用后的静态返回类型与实际值。", | ||
| "transfer_trap": "Java 泛型采用不同擦除和变型规则,Rust 常对泛型实例化;相同尖括号不代表相同实现机制。" | ||
| }, | ||
| "ts-utility-types": { | ||
| "title": "工具类型", | ||
| "concept": "Pick 和 Partial 等工具类型无需自定义新辅助类型,就能表达常见对象转换。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示工具类型的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-keyof-typeof": { | ||
| "title": "为 `keyof` 验证运行时自有键", | ||
| "concept": "类型查询从限制对象取得静态形状,`keyof` 再派生允许属性名。由 `Object.hasOwn` 支撑的谓词只接受直接储存在该对象上的属性。", | ||
| "worked_example": "示例派生 tiny 与 huge 两个键,把 `huge` 当作外部文本,并在索引演示对象前通过自有键谓词收窄。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把工具类型的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决工具类型练习里的 TODO。", | ||
| "没有确认“Pick 和 Partial 等工具类型无需自定义新辅助类型,就能表达常见对象转换。”这个要点,只去凑 judge 输出。" | ||
| "使用 `in`,连原型继承的 `toString` 等名称也接受。", | ||
| "混淆 JavaScript 值级 `typeof` 运算与 TypeScript 类型查询位置。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用工具类型,然后才到达 `example:` 输出?", | ||
| "工具类型练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`keyof typeof limits` 会产生哪个字面量联合?", | ||
| "为何 `Object.hasOwn` 拒绝 `constructor`,而 `in` 能看到它?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把工具类型应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "建立基于 `Object.hasOwn` 的自有属性类型守卫,证明外部文本合法后才索引限制对象;缺失键和原型键统一判为无效。", | ||
| "objective": "两个声明尺寸都可解析,同时拒绝不存在及从原型继承的属性名。", | ||
| "language_delta": "`keyof` 在 Node 中没有运行表示;`Object.hasOwn` 才提供字符串对应直接属性的 JavaScript 证据。", | ||
| "prediction_prompt": "先比较 `Object.hasOwn(limits, \"toString\")` 与 `\"toString\" in limits`。", | ||
| "transfer_trap": "某些字典成员运算会遍历继承状态;要求自有键时必须显式排除原型链。" | ||
| }, | ||
| "ts-discriminated-unions": { | ||
| "title": "可辨识联合", | ||
| "concept": "共享的字面量标签让 switch 收窄每个变体,使矩形字段只在矩形上使用。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示可辨识联合的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-indexed-access": { | ||
| "title": "复用数组元素类型", | ||
| "concept": "`User[\"scores\"][number]` 从既有模型提取元素类型,避免另写一份可能日后偏离的分数定义。它只在检查阶段求值。", | ||
| "worked_example": "演示模型从字符串数组派生标签元素,并赋值固定单词 `typed`。两次索引选择都不会生成运行时查找代码。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把可辨识联合的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决可辨识联合练习里的 TODO。", | ||
| "没有确认“共享的字面量标签让 switch 收窄每个变体,使矩形字段只在矩形上使用。”这个要点,只去凑 judge 输出。" | ||
| "未经转换就把标准输入字符串赋给派生数字类型。", | ||
| "到处手写 `number`,切断字段与来源模型之间的联系。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用可辨识联合,然后才到达 `example:` 输出?", | ||
| "可辨识联合练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "先选择 `scores` 再以 `number` 索引后得到什么类型?", | ||
| "起始代码的字符串到数字不匹配会在哪个阶段报告?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把可辨识联合应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "跨越输入边界时先执行数值转换,再把结果赋给索引访问派生的分数类型;正数、零与负数均沿同一路径输出。", | ||
| "objective": "通过严格检查,并准确回显三种符号的数值输入。", | ||
| "language_delta": "索引访问只计算静态类型,不发出属性查找;真正把 JavaScript 字符串转成数字仍需 `Number`。", | ||
| "prediction_prompt": "在考虑任何标准输出前,把起始代码失败归类为类型检查问题。", | ||
| "transfer_trap": "其他生态从模型生成类型时可能附带运行时验证器,此 TypeScript 运算符只提供静态检查。" | ||
| }, | ||
| "ts-async-promise": { | ||
| "title": "async 与 Promise", | ||
| "concept": "async 函数返回 Promise,await 是已完成数值重新进入普通流程的位置。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示async 与 Promise的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-mapped-types": { | ||
| "title": "把每个功能键映射为开关", | ||
| "concept": "映射类型遍历功能字面量联合,并要求每个成员都有一个布尔属性。完整性保证日后增加新功能名时,不会悄悄漏掉配置。", | ||
| "worked_example": "示例把 `dark` 与 `cache` 映射成两个布尔值,并报告启用的演示键。即使仅一个为真,两个必需属性仍同时存在。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把async 与 Promise的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决async 与 Promise练习里的 TODO。", | ||
| "没有确认“async 函数返回 Promise,await 是已完成数值重新进入普通流程的位置。”这个要点,只去凑 judge 输出。" | ||
| "省略 `share`,误以为映射属性默认可选。", | ||
| "输出笼统的成功词,而案例要求实际启用的功能名称。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用async 与 Promise,然后才到达 `example:` 输出?", | ||
| "async 与 Promise练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "不完整对象为何在 Node 读取输入前就失败?", | ||
| "原始命令为 `none` 时,两个布尔值各应是什么?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把async 与 Promise应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "补上缺失的映射属性,让对象同时覆盖 `search` 与 `share`,再依据两个开关导出三种可能结果;不得绕过检查器。", | ||
| "objective": "检查器保证功能覆盖完整,运行时又能产生 `search`、`share` 或 `none`。", | ||
| "language_delta": "映射类型只为检查器生成对象要求,不会在 Node 执行时分配属性或迭代键。", | ||
| "prediction_prompt": "预测只有 `search` 属性的对象会收到哪项类型检查诊断。", | ||
| "transfer_trap": "运行时映射或字典是储存数据的结构,TypeScript 映射类型却是纯编译期变换。" | ||
| }, | ||
| "ts-error-handling": { | ||
| "title": "错误处理", | ||
| "concept": "catch 接收 unknown 的失败值,因此在选择恢复值前应先收窄。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示错误处理的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-conditional-types": { | ||
| "title": "用条件类型提取元素", | ||
| "concept": "条件类型判断 `T` 是否为只读数组形态,并用 `infer` 命名其中成员;传入 `string[]` 时,检查器选择的结果就是字符串。", | ||
| "worked_example": "示例以 `number[]` 实例化同一模式并赋值 11。没有运行时条件检查该数值,因为类型选择早已被擦除。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把错误处理的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决错误处理练习里的 TODO。", | ||
| "没有确认“catch 接收 unknown 的失败值,因此在选择恢复值前应先收窄。”这个要点,只去凑 judge 输出。" | ||
| "条件类型已解析成字符串后,仍把 `Number(raw)` 的数字赋给它。", | ||
| "以为 `T extends ...` 会依据标准输入内容在运行时选择分支。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用错误处理,然后才到达 `example:` 输出?", | ||
| "错误处理练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "`T` 为只读字符串数组时,`Item` 代表什么?", | ||
| "Node 能观察检查器选中了哪条条件类型分支吗?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把错误处理应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "把原始输入文本直接赋给提取出的字符串元素类型,不执行数值转换;即使字符看起来像数字,也仍须保持文本语义。", | ||
| "objective": "三种文本输入均能通过类型检查并原样输出,包括外形像数字的字符。", | ||
| "language_delta": "条件类型只在 TypeScript 类型系统运行,JavaScript 条件表达式则是另一种会发出代码的运行语法。", | ||
| "prediction_prompt": "输入文本 `42` 是否会改变静态的字符串元素类型?执行前先说明原因。", | ||
| "transfer_trap": "其他语言的模板或特征机制可能生成可执行代码,此处的类型选择却会完全擦除。" | ||
| }, | ||
| "ts-modules": { | ||
| "title": "模块与 export", | ||
| "concept": "export 标记文件提供给其他文件的值;模块系统仍由 Node 的规则决定。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示模块与 export的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-utility-types": { | ||
| "title": "用工具类型描述局部更新", | ||
| "concept": "`Pick` 从用户模型选出分数,`Partial` 再让该属性可选。空对象代表没有更新,并不会自动制造任何分数值。", | ||
| "worked_example": "示例为另一配置模型建立仅含可选 `active` 属性的更新对象,用布尔备用值读取,展示运行时属性可以存在也可以缺失。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把模块与 export的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决模块与 export练习里的 TODO。", | ||
| "没有确认“export 标记文件提供给其他文件的值;模块系统仍由 Node 的规则决定。”这个要点,只去凑 judge 输出。" | ||
| "分数片段缺失时自行插入一,而不是保持更新对象为空。", | ||
| "认为 `Partial` 会递归修改嵌套属性或填入默认值。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用模块与 export,然后才到达 `example:` 输出?", | ||
| "模块与 export练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "Lin 没有分数片段时,哪个对象最准确表示无更新?", | ||
| "分数更新类型通过 `Pick` 排除了原用户模型的哪个字段?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把模块与 export应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "依据分数片段是否存在,建立空更新对象或含已转换数字的更新对象;只有真正缺失时,最终读取才使用零。", | ||
| "objective": "提供的正负分数保持原值,真正缺少属性时才稳定回退为零。", | ||
| "language_delta": "工具类型只重写静态对象形状且不发出代码;运行时究竟有哪些键,仍由普通 JavaScript 对象构造决定。", | ||
| "prediction_prompt": "在空值合并前,分别画出 `Ada 5` 与 `Lin` 对应的更新对象。", | ||
| "transfer_trap": "序列化数据里的可选字段仍需运行时解析与验证,TypeScript 的 `Partial` 不能承担这项工作。" | ||
| }, | ||
| "ts-classes": { | ||
| "title": "类与访问修饰符", | ||
| "concept": "类把方法绑定到状态上,private 说明哪些状态应留在方法边界之后。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示类与访问修饰符的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-satisfies-as-const": { | ||
| "title": "校验路由并保留字面量", | ||
| "concept": "`as const` 保留精确路由字符串,`satisfies` 检查 `Record` 契约而不把这些字面量改成宽泛类型。外部文本仍须由 `Object.hasOwn` 证明是直接属性。", | ||
| "worked_example": "示例检查两个状态码,用自有键谓词收窄 `ready` 后读取保留的 `R` 字面量。擦除类型后,对象仍是普通 JavaScript 数据。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把类与访问修饰符的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决类与访问修饰符练习里的 TODO。", | ||
| "没有确认“类把方法绑定到状态上,private 说明哪些状态应留在方法边界之后。”这个要点,只去凑 judge 输出。" | ||
| "使用 `in`,意外把 `constructor` 等继承名称当成路由。", | ||
| "相信 `as const` 会冻结对象或递归阻止运行时修改。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用类与访问修饰符,然后才到达 `example:` 输出?", | ||
| "类与访问修饰符练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "这里的 `satisfies` 与强制断言为 `Record<string, string>` 有何差别?", | ||
| "为何 `Object.hasOwn` 比 `in` 提供更严格的路由条件?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把类与访问修饰符应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "先用自有属性守卫验证请求路由,再索引经 `satisfies` 检查的常量对象;未知键和原型继承键必须走无效分支。", | ||
| "objective": "返回两个保留的路径字面量,同时拒绝未知及原型链上的名称。", | ||
| "language_delta": "`satisfies` 检查兼容性而不执行强制转换;`as const` 保留字面量类型,并在编译期把该字面量表达式的属性标为只读,但不会冻结运行时对象。", | ||
| "prediction_prompt": "先求 `Object.hasOwn(routes, \"constructor\")`,再决定是否允许路由索引。", | ||
| "transfer_trap": "JavaScript 的 `in` 会沿原型链查找;配置仅接受声明的自有键时,它的范围过宽。" | ||
| }, | ||
| "ts-readonly": { | ||
| "title": "readonly", | ||
| "concept": "readonly 允许读取配置类数据,同时防止通过该类型替换或修改它。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示readonly的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-discriminated-unions": { | ||
| "title": "检查点:穷尽带标签的形状联合", | ||
| "concept": "每种形状拥有字面量标签及对应尺寸。根据共享标签分支后,字段可被安全收窄;把理论上不可能的剩余值赋给 `never` 可检查穷尽性。", | ||
| "worked_example": "示例联合表示成功或失败,并在两条 `switch` 分支都返回。无关标签展示同样的收窄机制,却不计算矩形或圆。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把readonly的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决readonly练习里的 TODO。", | ||
| "没有确认“readonly 允许读取配置类数据,同时防止通过该类型替换或修改它。”这个要点,只去凑 judge 输出。" | ||
| "尚未证明形状是圆就读取半径。", | ||
| "后来增加新标签,却保留宽泛默认分支悄悄隐藏遗漏。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用readonly,然后才到达 `example:` 输出?", | ||
| "readonly练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "矩形分支返回后,剩余值的精确类型是什么?", | ||
| "为何把圆赋给 `never` 会造成起始代码的类型检查失败?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把readonly应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "补全圆形分支后继续保留 `never` 穷尽断言;矩形、圆形及零尺寸都必须通过各自标签访问合法字段。", | ||
| "objective": "通过严格穷尽检查,并在三组案例中计算两种带标签形状。", | ||
| "language_delta": "字面量判别字段存在于运行时并驱动 JavaScript 分支,TypeScript 的收窄推理与 `never` 证明随后会被擦除。", | ||
| "prediction_prompt": "执行前追踪 `circle 5` 从解析、标签收窄到数值返回的全过程。", | ||
| "transfer_trap": "Rust 通配匹配或其他语言默认分支也会掩盖新增变体,效果类似过宽的 TypeScript 备用分支。" | ||
| }, | ||
| "ts-satisfies-as-const": { | ||
| "title": "satisfies 与 as const", | ||
| "concept": "as const 保留路由字面量,satisfies 在不扩大这些字面量的情况下检查更宽的 Record 契约。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示satisfies 与 as const的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-async-promise": { | ||
| "title": "等待带类型的 `Promise`", | ||
| "concept": "异步函数即使返回表达式是数字,也总会返回 `Promise`。`await` 在 `main` 中等待其完成并取得数字,最外层 `catch` 则观察拒绝结果。", | ||
| "worked_example": "示例异步地把 9 增加到 10,等待后输出演示记录。拒绝回调把值视为 `unknown`,没有假定所有抛出物都是 `Error`。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把satisfies 与 as const的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决satisfies 与 as const练习里的 TODO。", | ||
| "没有确认“as const 保留路由字面量,satisfies 在不扩大这些字面量的情况下检查更宽的 Record 契约。”这个要点,只去凑 judge 输出。" | ||
| "加倍函数仍返回原数字,忘记要求的数值变换。", | ||
| "调用 `main` 后不观察返回 `Promise` 的拒绝状态。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用satisfies 与 as const,然后才到达 `example:` 输出?", | ||
| "satisfies 与 as const练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "即使函数体里没有 `await`,加倍函数的静态返回类型是什么?", | ||
| "标准输出写入会在 `Promise` 完成前还是完成后发生?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把satisfies 与 as const应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "让异步加倍函数以参数两倍完成,同时保留 `await` 与最外层拒绝处理;正数、零和负数都须经过异步调用边界。", | ||
| "objective": "三种符号输入都能通过异步控制流得到正确的两倍结果。", | ||
| "language_delta": "调度属于 JavaScript 运行时;TypeScript 只验证 `Promise<number>` 和捕获值类型,并不提供执行器。", | ||
| "prediction_prompt": "输入 -3 时,先预测加倍函数最终完成时携带的数值。", | ||
| "transfer_trap": "调用 Python 协程不会自动执行,而此 JavaScript 异步函数调用会立即返回已经开始执行的 `Promise` 链。" | ||
| }, | ||
| "ts-iterables": { | ||
| "title": "可迭代对象", | ||
| "concept": "for...of 可消费任何可迭代对象,因此数组、字符串和 Set 能共享同一种循环形态。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示可迭代对象的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-modules": { | ||
| "title": "单文件运行器中的公开接口", | ||
| "concept": "此练习只用本地对象描述公开可调用接口,不声称建立真实导入图;评测器只提供一个 TypeScript 文件,无法诚实验证多文件解析。", | ||
| "worked_example": "示例通过独立接口暴露文本反转方法,把 `module` 反转。对象让边界可见,却不依赖 `ESM` 或 `CommonJS` 的文件解析。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把可迭代对象的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决可迭代对象练习里的 TODO。", | ||
| "没有确认“for...of 可消费任何可迭代对象,因此数组、字符串和 Set 能共享同一种循环形态。”这个要点,只去凑 judge 输出。" | ||
| "以为写出导出声明就证明另一个文件能够解析并执行模块。", | ||
| "继续把文本转小写,违背本地接口承诺的大写行为。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用可迭代对象,然后才到达 `example:` 输出?", | ||
| "可迭代对象练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "本地标签接口要求实现怎样的方法签名?", | ||
| "真实多文件 `ESM` 练习还需要 Node 的哪些项目设置?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把可迭代对象应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "实现声明好的本地公开接口,把标准输入文本转换为大写;只验证单文件可调用边界,不添加虚假的导入或导出流程。", | ||
| "objective": "在不假装导入另一文件的前提下,对拉丁字母和重音文本练习带类型的公开接口。", | ||
| "language_delta": "Node 依据扩展名与包元数据选择 `ESM` 或 `CommonJS`;固定单文件运行器无法同时验证真实导出和导入。", | ||
| "prediction_prompt": "依据 JavaScript 的 Unicode 大写转换,预测方法接收 `é` 后的结果。", | ||
| "transfer_trap": "浏览器、打包器与 `CommonJS` 解析文件的方式不同,通过此定向练习不等于掌握多文件模块。" | ||
| }, | ||
| "ts-array-methods": { | ||
| "title": "map、filter 与 reduce", | ||
| "concept": "filter 选择元素,map 转换元素,reduce 把序列折叠成最终答案。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。", | ||
| "worked_example": "示例用带 `example:` 标签的演示输出展示map、filter 与 reduce的流程。先看值在到达 stdout 前由哪一行改变,练习中再把同一思路用到另一组数据上。", | ||
| "ts-error-handling": { | ||
| "title": "综合:严格验证并执行命令", | ||
| "concept": "命令解析器必须验证完整且有限的数字片段、拒绝除零,再构造带判别字段的命令。异步执行返回 `Promise`,最终捕获处把 `unknown` 收窄后再标记失败。", | ||
| "worked_example": "示例揭示两个陷阱:`parseInt` 会接受 `12px` 的数字前缀,解析 `bad` 则得到 `NaN` 而非抛错;捕获到的字符串也需先用 `typeof` 收窄。", | ||
| "common_mistakes": [ | ||
| "只改最后的输出行,却没有把map、filter 与 reduce的规则留在代码路径里。", | ||
| "直接复制带 `example:` 标签的演示输出,没有解决map、filter 与 reduce练习里的 TODO。", | ||
| "没有确认“filter 选择元素,map 转换元素,reduce 把序列折叠成最终答案。”这个要点,只去凑 judge 输出。" | ||
| "把 `parseInt` 当作验证器,错误接受末尾带字母的数字片段。", | ||
| "假定 JavaScript 抛出的每个值都是 `Error` 实例。" | ||
| ], | ||
| "self_check": [ | ||
| "示例中哪个表达式先应用map、filter 与 reduce,然后才到达 `example:` 输出?", | ||
| "map、filter 与 reduce练习里的 TODO 行必须根据自己的数据生成什么值,而不是复制演示?" | ||
| "为何 `Number` 拒绝 `12px`,而 `Number.parseInt` 会接受其中一部分?", | ||
| "零除检查应在异步执行前由哪一阶段完成?" | ||
| ], | ||
| "exercise_prompt": "完成 TODO,把map、filter 与 reduce应用到练习数据上。stdout 要保持为 judge 可比较的干净输出;`example:` 标签只属于示例。" | ||
| "exercise_prompt": "建立严格解析器,确认命令、完整有限数字及非零除数,再穷尽执行命令联合;失败从 `unknown` 收窄后统一输出无效标记。", | ||
| "objective": "合法加法和除法输出数值;部分数字、非有限数字和除数为零的情况均输出 `invalid`。", | ||
| "language_delta": "严格捕获使用 `unknown`,因为 JavaScript 可抛出任意值;`parseInt` 对完全非法文本返回 `NaN`,本身不会进入捕获分支。", | ||
| "prediction_prompt": "追踪 `add 12px 1` 的转换过程,并指出哪一项完整片段验证会拒绝它。", | ||
| "transfer_trap": "其他语言的异常式数字解析不能直接迁移;JavaScript 数值转换经常以 `NaN` 报告失败,而不是抛出异常。" | ||
| } | ||
| } | ||
| } |
@@ -1,36 +0,40 @@ | ||
| <svg width="1200" height="720" viewBox="0 0 1200 720" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
| <rect width="1200" height="720" fill="#070b12"/> | ||
| <rect x="54" y="42" width="1092" height="636" rx="10" fill="#0b111a" stroke="#214c5c" stroke-width="2"/> | ||
| <rect x="76" y="68" width="560" height="500" rx="4" fill="#0f1720" stroke="#f8e71c" stroke-width="2"/> | ||
| <rect x="664" y="68" width="458" height="500" rx="4" fill="#0d141c" stroke="#00c2d1" stroke-width="2"/> | ||
| <text x="92" y="91" fill="#f8e71c" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">> Home</text> | ||
| <text x="680" y="91" fill="#16d6e8" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">Preview</text> | ||
| <text x="96" y="136" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="28" font-weight="700">Practicode</text> | ||
| <text x="96" y="208" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="22" font-weight="700">> Learn syntax</text> | ||
| <text x="126" y="242" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="18">Read a short syntax lesson.</text> | ||
| <text x="126" y="274" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="18">Validate the exercise.</text> | ||
| <text x="96" y="344" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="22" font-weight="700"> Practice coding tests</text> | ||
| <text x="126" y="378" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="18">Solve stdin/stdout problems.</text> | ||
| <text x="96" y="464" fill="#a6adc8" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17">Arrows choose | Enter/Space open | / commands</text> | ||
| <text x="694" y="144" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="24" font-weight="700">Learning</text> | ||
| <text x="694" y="202" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="18">Language: Python</text> | ||
| <text x="694" y="244" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="18">Progress: 0/12</text> | ||
| <rect x="694" y="306" width="368" height="98" rx="4" fill="#101d28" stroke="#29495a"/> | ||
| <text x="718" y="346" fill="#7dd3fc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17">/run validates exercises</text> | ||
| <text x="718" y="382" fill="#7dd3fc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17">/next opens the next lesson</text> | ||
| <rect x="76" y="580" width="1046" height="30" fill="#152033"/> | ||
| <text x="94" y="601" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">PRACTICODE | home | Arrows choose | Enter/Space open | / commands</text> | ||
| <rect x="76" y="622" width="1046" height="48" rx="2" fill="#0b1017" stroke="#00c2d1" stroke-width="2"/> | ||
| <text x="94" y="643" fill="#16d6e8" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">Command</text> | ||
| <text x="94" y="660" fill="#64748b" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="18">Type /, move with up/down, Enter runs</text> | ||
| <svg xmlns="http://www.w3.org/2000/svg" width="1112" height="630" viewBox="0 0 1112 630" role="img" aria-label="Practicode · Home"> | ||
| <rect width="1112" height="630" rx="14" fill="#0f172a"/> | ||
| <rect x="1" y="1" width="1110" height="628" rx="13" fill="none" stroke="#334155"/> | ||
| <circle cx="18" cy="18" r="5" fill="#fb7185"/><circle cx="34" cy="18" r="5" fill="#fbbf24"/><circle cx="50" cy="18" r="5" fill="#4ade80"/> | ||
| <text x="70" y="23" fill="#94a3b8" font-size="13" font-family="ui-monospace, SFMono-Regular, Consolas, Liberation Mono, monospace">Practicode · Home</text> | ||
| <g fill="#e5e7eb" font-size="14" font-family="ui-monospace, SFMono-Regular, Consolas, Liberation Mono, monospace" xml:space="preserve"> | ||
| <text x="16" y="48">┌> Continue today's session──────────────────────┐┌Preview─────────────────────────────────────────────────────────────┐</text> | ||
| <text x="16" y="66">│Review what's due, learn one core item, and ││Continue today's session │</text> | ||
| <text x="16" y="84">│validate the exercise. ││ │</text> | ||
| <text x="16" y="102">│ ││Language: Python │</text> | ||
| <text x="16" y="120">│ ││Progress: 0/12 │</text> | ||
| <text x="16" y="138">│ ││Due: 0 │</text> | ||
| <text x="16" y="156">└────────────────────────────────────────────────┘│Next step: Language delta │</text> | ||
| <text x="16" y="174">┌Practice coding tests───────────────────────────┐│ │</text> | ||
| <text x="16" y="192">│Solve stdin/stdout coding-test problems. ││ │</text> | ||
| <text x="16" y="210">│ ││ │</text> | ||
| <text x="16" y="228">│ ││ │</text> | ||
| <text x="16" y="246">│ ││ │</text> | ||
| <text x="16" y="264">│ ││ │</text> | ||
| <text x="16" y="282">└────────────────────────────────────────────────┘│ │</text> | ||
| <text x="16" y="300">Arrows choose | Enter/Space open | / commands │ │</text> | ||
| <text x="16" y="318"> │ │</text> | ||
| <text x="16" y="336"> │ │</text> | ||
| <text x="16" y="354"> │ │</text> | ||
| <text x="16" y="372"> │ │</text> | ||
| <text x="16" y="390"> │ │</text> | ||
| <text x="16" y="408"> │ │</text> | ||
| <text x="16" y="426"> │ │</text> | ||
| <text x="16" y="444"> │ │</text> | ||
| <text x="16" y="462"> │ │</text> | ||
| <text x="16" y="480"> │ │</text> | ||
| <text x="16" y="498"> │ │</text> | ||
| <text x="16" y="516"> │ │</text> | ||
| <text x="16" y="534"> │ │</text> | ||
| <text x="16" y="552"> │ │</text> | ||
| <text x="16" y="570"> └────────────────────────────────────────────────────────────────────┘</text> | ||
| <text x="16" y="588"> F1 help | arrows choose | Enter open </text> | ||
| <text x="16" y="606">Command: Type /, move with up/down, Enter runs </text> | ||
| </g> | ||
| </svg> |
@@ -1,36 +0,40 @@ | ||
| <svg width="1200" height="720" viewBox="0 0 1200 720" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
| <rect width="1200" height="720" fill="#070b12"/> | ||
| <rect x="54" y="42" width="1092" height="636" rx="10" fill="#0b111a" stroke="#214c5c" stroke-width="2"/> | ||
| <rect x="76" y="68" width="560" height="430" rx="4" fill="#0f1720" stroke="#00c2d1" stroke-width="2"/> | ||
| <rect x="664" y="68" width="458" height="430" rx="4" fill="#0d141c" stroke="#f8e71c" stroke-width="2"/> | ||
| <text x="92" y="91" fill="#16d6e8" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">Problem</text> | ||
| <text x="680" y="91" fill="#f8e71c" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">> solution.py</text> | ||
| <text x="96" y="128" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="22" font-weight="700">001. Hello World</text> | ||
| <text x="96" y="164" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">Difficulty: easy Topics: io</text> | ||
| <text x="96" y="206" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17">Print exactly Hello, World! to stdout.</text> | ||
| <text x="96" y="286" fill="#00d2e8" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17" font-weight="700">Input</text> | ||
| <rect x="96" y="304" width="486" height="44" rx="3" fill="#101d28" stroke="#29495a"/> | ||
| <text x="112" y="332" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">No input.</text> | ||
| <text x="96" y="388" fill="#00d2e8" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="17" font-weight="700">Output</text> | ||
| <rect x="96" y="406" width="486" height="66" rx="3" fill="#101d28" stroke="#29495a"/> | ||
| <text x="112" y="446" fill="#f6c177" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">Hello, World!</text> | ||
| <text x="690" y="128" fill="#64748b" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16"> 1</text> | ||
| <text x="738" y="128" fill="#7dd3fc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">print</text> | ||
| <text x="790" y="128" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="16">('Hello, World!')</text> | ||
| <rect x="76" y="512" width="1046" height="30" fill="#152033"/> | ||
| <text x="94" y="533" fill="#c8d3f5" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">PRACTICODE | 001-hello-world | easy | idle | assigned | code:written | python | Esc then / command</text> | ||
| <rect x="76" y="558" width="1046" height="48" rx="2" fill="#0b1017" stroke="#00c2d1" stroke-width="2"/> | ||
| <text x="94" y="579" fill="#16d6e8" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15" font-weight="700">Command</text> | ||
| <text x="94" y="596" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="18">/</text> | ||
| <rect x="76" y="614" width="1046" height="46" rx="2" fill="#101923" stroke="#214c5c"/> | ||
| <text x="94" y="641" fill="#f8fafc" font-family="SFMono-Regular, Menlo, Consolas, monospace" font-size="15">/run /next /hint <request> /profile /difficulty auto /topics arrays, strings</text> | ||
| <svg xmlns="http://www.w3.org/2000/svg" width="1112" height="630" viewBox="0 0 1112 630" role="img" aria-label="Practicode · Learn"> | ||
| <rect width="1112" height="630" rx="14" fill="#0f172a"/> | ||
| <rect x="1" y="1" width="1110" height="628" rx="13" fill="none" stroke="#334155"/> | ||
| <circle cx="18" cy="18" r="5" fill="#fb7185"/><circle cx="34" cy="18" r="5" fill="#fbbf24"/><circle cx="50" cy="18" r="5" fill="#4ade80"/> | ||
| <text x="70" y="23" fill="#94a3b8" font-size="13" font-family="ui-monospace, SFMono-Regular, Consolas, Liberation Mono, monospace">Practicode · Learn</text> | ||
| <g fill="#e5e7eb" font-size="14" font-family="ui-monospace, SFMono-Regular, Consolas, Liberation Mono, monospace" xml:space="preserve"> | ||
| <text x="16" y="48">┌> Lesson────────────────────────────────────────┐┌Code · Exercise.rs──────────────────────────────────────────────────┐</text> | ||
| <text x="16" y="66">│Language delta: Make stdout byte-exact ││> 1 use std::io::{self, Read}; │</text> | ||
| <text x="16" y="84">│ ││ 2 │</text> | ||
| <text x="16" y="102">│Unlike string concatenation in many languages, ││ 3 fn render(name: &str, score: &str) -> String { │</text> | ||
| <text x="16" y="120">│Rust formatting keeps the template and typed ││ 4 // TODO: use the exact field separator required by stdout. │</text> | ||
| <text x="16" y="138">│arguments separate and validates their ││ 5 format!("{name} {score}") │</text> | ||
| <text x="16" y="156">│relationship. ││ 6 } │</text> | ||
| <text x="16" y="174">│ ││ 7 │</text> | ||
| <text x="16" y="192">│ ││ 8 fn main() { │</text> | ||
| <text x="16" y="210">│ ││ 9 let mut input = String::new(); │</text> | ||
| <text x="16" y="228">│ ││ 10 io::stdin().read_to_string(&mut input).unwrap(); │</text> | ||
| <text x="16" y="246">│ ││ 11 let mut fields = input.split_whitespace(); │</text> | ||
| <text x="16" y="264">│ ││ 12 let name = fields.next().unwrap(); │</text> | ||
| <text x="16" y="282">│ ││ 13 let score = fields.next().unwrap(); │</text> | ||
| <text x="16" y="300">│ ││ 14 println!("{}", render(name, score)); │</text> | ||
| <text x="16" y="318">│ ││ 15 } │</text> | ||
| <text x="16" y="336">│ ││ 16 │</text> | ||
| <text x="16" y="354">│ ││ │</text> | ||
| <text x="16" y="372">│ ││ │</text> | ||
| <text x="16" y="390">│ ││ │</text> | ||
| <text x="16" y="408">│ ││ │</text> | ||
| <text x="16" y="426">│ ││ │</text> | ||
| <text x="16" y="444">│ ││ │</text> | ||
| <text x="16" y="462">│ ││ │</text> | ||
| <text x="16" y="480">│ ││ │</text> | ||
| <text x="16" y="498">│ ││ │</text> | ||
| <text x="16" y="516">│ ││ │</text> | ||
| <text x="16" y="534">│ ││ │</text> | ||
| <text x="16" y="552">│ ││ │</text> | ||
| <text x="16" y="570">└────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────────┘</text> | ||
| <text x="16" y="588"> F1 help | F5 run | F6 view | /next step </text> | ||
| <text x="16" y="606">Command: Type /, move with up/down, Enter runs </text> | ||
| </g> | ||
| </svg> |
+9
-165
| #!/usr/bin/env node | ||
| const { spawnSync } = require("node:child_process"); | ||
| const { existsSync, mkdirSync } = require("node:fs"); | ||
| const { homedir } = require("node:os"); | ||
| const path = require("node:path"); | ||
| const { main } = require("./launcher.js"); | ||
| const root = path.resolve(__dirname, ".."); | ||
| const packageJson = require(path.join(root, "package.json")); | ||
| const dockerImage = `practicode-sandbox:${packageJson.version}`; | ||
| const exe = path.join( | ||
| root, | ||
| "target", | ||
| "release", | ||
| process.platform === "win32" ? "practicode.exe" : "practicode", | ||
| main(process.argv.slice(2)).then( | ||
| (status) => { | ||
| process.exitCode = status; | ||
| }, | ||
| (error) => { | ||
| console.error(`practicode: ${error instanceof Error ? error.message : String(error)}`); | ||
| process.exitCode = 1; | ||
| }, | ||
| ); | ||
| const args = process.argv.slice(2); | ||
| function rustInstallCommand() { | ||
| if (process.platform === "win32") { | ||
| return "winget install -e --id Rustlang.Rustup"; | ||
| } | ||
| if (process.platform === "darwin") { | ||
| return "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"; | ||
| } | ||
| return "sudo apt update && sudo apt install -y curl build-essential && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"; | ||
| } | ||
| function printRustInstallHelp() { | ||
| console.error("practicode: Rust/Cargo is required to build the binary on first run."); | ||
| console.error(`Install Rust: ${rustInstallCommand()}`); | ||
| console.error("Then restart your terminal and run practicode again."); | ||
| console.error("More options: https://www.rust-lang.org/tools/install"); | ||
| } | ||
| function dockerInstallCommand() { | ||
| if (process.platform === "win32") { | ||
| return "winget install -e --id Docker.DockerDesktop"; | ||
| } | ||
| if (process.platform === "darwin") { | ||
| return "brew install --cask docker"; | ||
| } | ||
| return "Ubuntu: https://docs.docker.com/engine/install/ubuntu/ | Debian: https://docs.docker.com/engine/install/debian/"; | ||
| } | ||
| function printDockerHelp() { | ||
| console.error("practicode: Docker is required for --docker sandbox mode."); | ||
| console.error(`Install Docker: ${dockerInstallCommand()}`); | ||
| console.error("Start Docker, then run practicode --docker again."); | ||
| console.error("More options: https://docs.docker.com/get-docker/"); | ||
| } | ||
| function runDockerSandbox(forwardedArgs) { | ||
| const version = spawnSync("docker", ["version"], { stdio: "ignore" }); | ||
| if (version.error || version.status !== 0) { | ||
| if (version.error) { | ||
| console.error(`practicode: failed to run docker: ${version.error.message}`); | ||
| } | ||
| printDockerHelp(); | ||
| process.exit(1); | ||
| } | ||
| const build = spawnSync("docker", ["build", "-t", dockerImage, root], { | ||
| stdio: "inherit", | ||
| }); | ||
| if (build.error) { | ||
| console.error(`practicode: failed to build Docker sandbox: ${build.error.message}`); | ||
| printDockerHelp(); | ||
| process.exit(1); | ||
| } | ||
| if (build.status !== 0) { | ||
| console.error("practicode: Docker sandbox build failed."); | ||
| printDockerHelp(); | ||
| process.exit(build.status ?? 1); | ||
| } | ||
| const dataHome = path.resolve( | ||
| process.env.PRACTICODE_HOME || path.join(homedir(), ".practicode"), | ||
| ); | ||
| mkdirSync(dataHome, { recursive: true }); | ||
| const runArgs = [ | ||
| "run", | ||
| "--rm", | ||
| process.stdin.isTTY ? "-it" : "-i", | ||
| "--init", | ||
| "--network", | ||
| "none", | ||
| "--cpus", | ||
| "2", | ||
| "--memory", | ||
| "1g", | ||
| "--pids-limit", | ||
| "256", | ||
| "--cap-drop", | ||
| "ALL", | ||
| "--security-opt", | ||
| "no-new-privileges", | ||
| "--read-only", | ||
| "--tmpfs", | ||
| "/tmp:rw,nosuid,nodev,size=256m,mode=1777", | ||
| "--mount", | ||
| `type=bind,source=${process.cwd()},target=/workspace`, | ||
| "--mount", | ||
| `type=bind,source=${dataHome},target=/data`, | ||
| "-w", | ||
| "/workspace", | ||
| "-e", | ||
| `TERM=${process.env.TERM || "xterm-256color"}`, | ||
| "-e", | ||
| "HOME=/tmp", | ||
| "-e", | ||
| "PRACTICODE_HOME=/data", | ||
| "-e", | ||
| "PRACTICODE_NO_UPDATE_CHECK=1", | ||
| ]; | ||
| if (process.env.COLORTERM) { | ||
| runArgs.push("-e", `COLORTERM=${process.env.COLORTERM}`); | ||
| } | ||
| if (typeof process.getuid === "function" && typeof process.getgid === "function") { | ||
| runArgs.push("--user", `${process.getuid()}:${process.getgid()}`); | ||
| } | ||
| runArgs.push(dockerImage, ...forwardedArgs); | ||
| const run = spawnSync("docker", runArgs, { | ||
| cwd: process.cwd(), | ||
| stdio: "inherit", | ||
| }); | ||
| if (run.error) { | ||
| console.error(`practicode: failed to run Docker sandbox: ${run.error.message}`); | ||
| printDockerHelp(); | ||
| process.exit(1); | ||
| } | ||
| process.exit(run.status ?? 1); | ||
| } | ||
| const dockerIndex = args.indexOf("--docker"); | ||
| if (dockerIndex !== -1) { | ||
| args.splice(dockerIndex, 1); | ||
| runDockerSandbox(args); | ||
| } | ||
| if (!existsSync(exe)) { | ||
| const build = spawnSync( | ||
| "cargo", | ||
| ["build", "--release", "--locked", "--manifest-path", path.join(root, "Cargo.toml")], | ||
| { stdio: "inherit" }, | ||
| ); | ||
| if (build.error) { | ||
| console.error(`practicode: failed to run cargo: ${build.error.message}`); | ||
| printRustInstallHelp(); | ||
| process.exit(1); | ||
| } | ||
| if (build.status !== 0) { | ||
| printRustInstallHelp(); | ||
| process.exit(build.status ?? 1); | ||
| } | ||
| } | ||
| const run = spawnSync(exe, args, { | ||
| cwd: process.cwd(), | ||
| stdio: "inherit", | ||
| }); | ||
| if (run.error) { | ||
| console.error(`practicode: failed to run binary: ${run.error.message}`); | ||
| process.exit(1); | ||
| } | ||
| process.exit(run.status ?? 1); |
+1
-1
@@ -456,3 +456,3 @@ # This file is automatically @generated by Cargo. | ||
| name = "practicode" | ||
| version = "0.1.20" | ||
| version = "0.2.0" | ||
| dependencies = [ | ||
@@ -459,0 +459,0 @@ "anyhow", |
+14
-4
| [package] | ||
| name = "practicode" | ||
| version = "0.1.20" | ||
| version = "0.2.0" | ||
| edition = "2024" | ||
| description = "Local-first coding-test practice in a Rust terminal UI with optional AI help." | ||
| description = "Offline-first programming language lessons and coding practice in a Rust terminal UI." | ||
| readme = "README.md" | ||
@@ -10,4 +10,14 @@ license = "MIT" | ||
| homepage = "https://github.com/baba9811/practicode" | ||
| keywords = ["algorithms", "ai", "coding-practice", "competitive", "tui"] | ||
| keywords = ["learning", "coding-practice", "programming", "terminal", "tui"] | ||
| categories = ["command-line-utilities", "development-tools", "text-editors"] | ||
| include = [ | ||
| "/src/**", | ||
| "/assets/**", | ||
| "/docs/**", | ||
| "/Cargo.lock", | ||
| "/LICENSE", | ||
| "/README.md", | ||
| "/SECURITY.md", | ||
| "/THIRD_PARTY_LICENSES.md", | ||
| ] | ||
@@ -17,3 +27,3 @@ [dependencies] | ||
| crossterm = "0.29" | ||
| ratatui = { version = "0.30", default-features = false, features = ["crossterm"] } | ||
| ratatui = { version = "0.30", default-features = false, features = ["crossterm", "unstable-rendered-line-info"] } | ||
| serde = { version = "1", features = ["derive"] } | ||
@@ -20,0 +30,0 @@ serde_json = "1" |
+34
-4
@@ -1,10 +0,41 @@ | ||
| FROM node:24-bookworm-slim AS node | ||
| FROM rust:1-bookworm | ||
| FROM rust:1-bookworm AS rust | ||
| FROM node:22-bookworm-slim AS node | ||
| RUN npm install --global typescript@5.9.3 \ | ||
| && npm cache clean --force | ||
| FROM eclipse-temurin:21-jdk-jammy AS java | ||
| FROM python:3.12-bookworm | ||
| RUN apt-get update \ | ||
| && apt-get install -y --no-install-recommends ca-certificates python3 default-jdk-headless \ | ||
| && apt-get install -y --no-install-recommends ca-certificates \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
| COPY --from=rust /usr/local/cargo /usr/local/cargo | ||
| COPY --from=rust /usr/local/rustup /usr/local/rustup | ||
| COPY --from=node /usr/local/bin/node /usr/local/bin/node | ||
| COPY --from=node /usr/local/lib/node_modules/typescript /usr/local/lib/node_modules/typescript | ||
| COPY --from=java /opt/java/openjdk /opt/java/openjdk | ||
| ENV RUSTUP_HOME=/usr/local/rustup | ||
| ENV CARGO_HOME=/usr/local/cargo | ||
| ENV JAVA_HOME=/opt/java/openjdk | ||
| ENV PATH=/opt/java/openjdk/bin:/usr/local/cargo/bin:$PATH | ||
| RUN set -eu; \ | ||
| ln -s /usr/local/lib/node_modules/typescript/bin/tsc /usr/local/bin/tsc; \ | ||
| ln -s /opt/java/openjdk/bin/java /usr/local/bin/java; \ | ||
| ln -s /opt/java/openjdk/bin/javac /usr/local/bin/javac; \ | ||
| ln -s /usr/local/cargo/bin/cargo /usr/local/bin/cargo; \ | ||
| ln -s /usr/local/cargo/bin/rustc /usr/local/bin/rustc; \ | ||
| python_version="$(python3 --version 2>&1)"; \ | ||
| node_version="$(node --version)"; \ | ||
| typescript_version="$(tsc --version)"; \ | ||
| java_version="$(javac -version 2>&1)"; \ | ||
| case "$python_version" in "Python 3.12."*) ;; *) exit 1 ;; esac; \ | ||
| case "$node_version" in "v22."*) ;; *) exit 1 ;; esac; \ | ||
| test "$typescript_version" = "Version 5.9.3"; \ | ||
| case "$java_version" in "javac 21."*) ;; *) exit 1 ;; esac; \ | ||
| printf '%s\n%s\n%s\n%s\n' "$python_version" "$node_version" "$typescript_version" "$java_version"; \ | ||
| rustc --version | ||
| WORKDIR /opt/practicode | ||
@@ -19,5 +50,4 @@ COPY Cargo.toml Cargo.lock ./ | ||
| ENV HOME=/tmp | ||
| ENV PATH=/usr/local/cargo/bin:$PATH | ||
| ENV PRACTICODE_NO_UPDATE_CHECK=1 | ||
| WORKDIR /workspace | ||
| ENTRYPOINT ["practicode"] |
@@ -11,6 +11,7 @@ # Architecture | ||
| - `src/core/state.rs` owns state loading, saving, and settings normalization. | ||
| - `src/core/learning.rs` owns deterministic mastery transitions, due-review selection, completion, and legacy progress migration. | ||
| - `src/core/language.rs` owns language/provider normalization, templates, and extension mapping. | ||
| - `src/core/render.rs` owns plain/markdown problem rendering. | ||
| - `src/core/judge.rs` owns submission file creation, runtime commands, compilation, and judging. | ||
| - `src/core/syntax.rs` owns built-in syntax lesson ordering, starter exercises, localized lesson-copy loading, and syntax progress helpers. | ||
| - `src/core/syntax.rs` validates and loads embedded executable course assets plus localized lesson copy. | ||
| - `src/core/progress.rs` owns give-up/next/previous/pass history transitions. | ||
@@ -25,2 +26,3 @@ - `src/core/problem_files.rs` owns generated problem README/index file writes. | ||
| - `src/tui/events.rs` owns keyboard/mouse event routing. | ||
| - `src/tui/learning.rs` owns the Review/Delta/Predict/Exercise/Reflect session queue and learning views. | ||
| - `src/tui/tasks.rs` owns background AI/update/model tasks and output writing helpers. | ||
@@ -38,3 +40,5 @@ - `src/tui/view.rs` owns Ratatui drawing, pane styling, mouse-capture toggles, and cursor placement. | ||
| - `assets/i18n/*.json` stores UI labels and command text. | ||
| - `assets/lessons/<programming-language>/<ui-language>.json` stores required lesson study copy. See [../assets/lessons/README.md](../assets/lessons/README.md). | ||
| - `assets/lessons/<programming-language>/course.json` stores ordered executable lessons, cases, and primary references. | ||
| - `assets/lessons/<programming-language>/<ui-language>.json` stores required lesson study copy, while `review-manifest.json` records independently reviewed content hashes. See [../assets/lessons/README.md](../assets/lessons/README.md). | ||
| - `bin/launcher.js` maps npm installs to versioned GitHub Release binaries, verifies checksums, and owns the per-user binary cache. `bin/practicode.js` is only its executable entry point. | ||
@@ -47,2 +51,3 @@ ## Extension Rules | ||
| - Add syntax lesson copy under `assets/lessons/<programming-language>/<ui-language>.json`; every supported UI language must define the required study fields. | ||
| - Keep deterministic cases and executable behavior in `course.json`, never in localized prose. Refresh review evidence only after an independent agent verifies the final hash. | ||
| - Keep provider-specific behavior in `src/ai.rs`; TUI should ask for status or start tasks, not know provider internals. | ||
@@ -55,2 +60,2 @@ - Keep foreground and background generation flows separate: `/next` may block when no local problem exists, while `/generate` must preserve the current problem and user profile state. | ||
| See [MAINTAINING.md](MAINTAINING.md) for tag-based releases. | ||
| Tags build five native binaries before crates.io/npm publication. The npm native path never invokes Cargo; `--docker` remains an explicit source-image build. See [MAINTAINING.md](MAINTAINING.md). |
+11
-4
@@ -9,6 +9,7 @@ # Commands | ||
| | --- | --- | | ||
| | `/home` | Return to the Learn syntax / Practice coding tests chooser | | ||
| | `/home` | Return to the Continue today's session / Practice coding tests chooser | | ||
| | `/run` | Judge the current submission or syntax exercise | | ||
| | `/code` | Return to the code editor | | ||
| | `/next` | In practice, open the next problem; in learn mode, open the next lesson | | ||
| | `/vim` | Open the code editor (compatibility alias) | | ||
| | `/next` | In practice, open the next problem; in guided learning, advance the current step and then the queue | | ||
| | `/back` | In practice, go back through problem history; in learn mode, open the previous lesson | | ||
@@ -45,5 +46,9 @@ | `/doctor` | Check local runtimes and show install hints | | ||
| | `/ask <question>` | Ask about the current lesson, worked example, or exercise | | ||
| | `/next` | Open the next lesson | | ||
| | `/next` | Advance Review → Delta → Predict → Exercise → Reflect, then open the next queued lesson | | ||
| | `/back` | Open the previous lesson | | ||
| | `/lesson` | Show the complete localized lesson reference | | ||
| | `/progress` | Show a privacy-safe core/due/retained/mastered summary | | ||
| Learning sessions queue at most two due reviews before one new core lesson. `F5` runs the current exercise, `F6` cycles Lesson/Code/Result, and `F1` opens contextual help. AI-assisted capstone attempts cannot complete a course until a later unassisted pass. | ||
| ## AI Help | ||
@@ -65,2 +70,4 @@ | ||
| Invoking `/ask` or `/hint` sends the current problem or lesson, submission code, latest result, and your request to the selected provider CLI. AI-backed `/next` and `/generate` run from `PRACTICODE_HOME` with the configured provider's local permissions; they can read learning state, the problem bank, notes, indexes, and submissions and can update generated problem files. A custom `ai_next_command` has whatever access that program is granted. Review those settings before enabling AI. | ||
| ## Profile And Preferences | ||
@@ -88,2 +95,2 @@ | ||
| Older command names such as `/prev`, `/previous`, `/list`, `/giveup`, `/give`, `/lang`, `/settings`, and `/quit` still work. | ||
| Older command names such as `/vim`, `/prev`, `/previous`, `/list`, `/giveup`, `/give`, `/lang`, `/settings`, and `/quit` still work. |
+19
-5
@@ -49,4 +49,4 @@ # Contributing | ||
| - Rust stable with Cargo, rustfmt, and clippy. | ||
| - Node.js 18+ for the npm wrapper and package checks. | ||
| - Optional local runtimes for judging: Python, Node.js, JDK, and Rust. | ||
| - Node.js 18+ for the npm launcher and package checks. | ||
| - For the complete executable curriculum gate: Python 3.12, Node 22, TypeScript 5.9.3, JDK 21, and stable Rust. | ||
@@ -59,3 +59,3 @@ Common commands: | ||
| cargo test | ||
| npm run smoke | ||
| npm run test:launcher | ||
| ``` | ||
@@ -89,7 +89,20 @@ | ||
| - Put UI strings in [assets/i18n](../assets/i18n), not inline in Rust code. | ||
| - Keep English localization complete first; other locales can be partial because the runtime falls back per key to English. | ||
| - Put syntax lesson study copy in [assets/lessons](../assets/lessons). Unlike UI strings, lesson copy is required for every supported UI language. | ||
| - Keep every UI and lesson catalog complete; incomplete locale prose does not fall back silently. | ||
| - Put syntax lesson study copy in [assets/lessons](../assets/lessons) and follow its executable-content contract. | ||
| - Keep the root [README](../README.md) focused on users. | ||
| - Use relative links for repo-local docs and assets. | ||
| ## Lesson Changes | ||
| Lesson corrections and new cases need evidence beyond a prose diff: | ||
| 1. Make the English behavior, starter, cases, and primary references agree. | ||
| 2. Update all affected locales without changing identifiers or operators. | ||
| 3. Run the focused runtime/mutation test and the i18n suite. | ||
| 4. Ask an independent agent that did not author the change to read the final records against the code and references. A human reviewer is not required. | ||
| 5. Resolve every Critical/Important finding, then update the relevant review profile verdict if needed. | ||
| 6. Run `node scripts/check-lessons.js --refresh`, inspect the hash diff, and run the checker again. | ||
| CI rejects stale hashes, incomplete 4×5 coverage, self-approval identities, unresolved disagreements, and open high-severity findings. | ||
| ## Problem Authoring | ||
@@ -115,2 +128,3 @@ | ||
| - User-facing text is in `assets/i18n/*.json`. | ||
| - Changed lessons have executable evidence and an independent review manifest hash. | ||
| - Local generated data, secrets, tokens, and answer keys are not committed. | ||
@@ -117,0 +131,0 @@ |
+13
-5
@@ -38,12 +38,18 @@ # Maintaining | ||
| ```bash | ||
| make release VERSION=0.1.3 | ||
| make release VERSION=0.2.0 | ||
| ``` | ||
| The release script checks versions, runs tests, creates the version commit and tag, and pushes `main` plus the tag. | ||
| The release script checks Cargo/npm/lock versions, runs the complete lesson, launcher, package, clippy, test, and isolated smoke gates, creates the version commit and tag, then atomically pushes `main` plus the tag. | ||
| The tag workflow first verifies that the exact tag commit belongs to `origin/main`, validates again, and builds five native assets: macOS Intel/Apple Silicon, Linux x64/arm64 musl, and Windows x64. Each binary is smoked on its matching hosted runner. A draft GitHub Release receives all binaries plus `SHA256SUMS`, then publication proceeds in dependency order: crates.io, the public GitHub Release, and finally npm. This guarantees that a newly published npm launcher can download public assets immediately. | ||
| Both registry jobs use the GitHub `release` environment. Keep its deployment policy restricted to tag pattern `v*.*.*` with no required human reviewer; the workflow itself accepts only exact `vMAJOR.MINOR.PATCH` tags reachable from `main`. Keep workflow Actions pinned to full commit SHAs and update their adjacent version comments and pins together. | ||
| Verify publication: | ||
| ```bash | ||
| gh run list --limit 5 | ||
| npm view practicode version | ||
| gh run list --workflow release.yml --limit 5 | ||
| gh run watch <run-id> --exit-status | ||
| gh release view v0.2.0 --json assets --jq '.assets[].name' | ||
| npm view practicode@0.2.0 version | ||
| npm view practicode dist.signatures dist.attestations --json | ||
@@ -53,2 +59,4 @@ cargo search practicode --limit 1 | ||
| Download the published assets to a temporary directory and verify `SHA256SUMS` before announcing the release. | ||
| Do not print or commit tokens. Local `.env` and `.npmrc` are ignored; GitHub Actions uses `NPM_TOKEN` and `CRATES_TOKEN` repository secrets. | ||
@@ -58,3 +66,3 @@ | ||
| Socket.dev indexes the npm package page at <https://socket.dev/npm/package/practicode>. It may lag behind npm immediately after a release; verify npm first with `npm view practicode version`, then re-check Socket after indexing catches up. The npm package should not include install lifecycle scripts; the Node launcher may build with Cargo on first use instead. | ||
| Socket.dev indexes the npm package page at <https://socket.dev/npm/package/practicode>. It may lag behind npm immediately after a release; verify npm first, then re-check Socket after indexing catches up. The npm package must not include install lifecycle scripts. Its Node launcher downloads the immutable versioned release asset, verifies SHA-256, installs it atomically in a per-user cache, and never invokes Cargo on the native path. | ||
@@ -61,0 +69,0 @@ ## Documentation Ownership |
+6
-4
| { | ||
| "name": "practicode", | ||
| "version": "0.1.20", | ||
| "description": "Local-first coding-test practice in a Rust terminal UI with optional AI help.", | ||
| "version": "0.2.0", | ||
| "description": "Offline-first programming language lessons and coding practice in a Rust terminal UI.", | ||
| "license": "MIT", | ||
@@ -20,3 +20,4 @@ "repository": { | ||
| "build": "cargo build --release --locked", | ||
| "smoke": "node bin/practicode.js --smoke" | ||
| "smoke": "node bin/practicode.js --smoke", | ||
| "test:launcher": "node --test tests/launcher.test.js" | ||
| }, | ||
@@ -41,3 +42,4 @@ "files": [ | ||
| "coding-practice", | ||
| "competitive-programming", | ||
| "learning", | ||
| "programming-languages", | ||
| "ratatui", | ||
@@ -44,0 +46,0 @@ "rust", |
+124
-149
@@ -1,37 +0,26 @@ | ||
| # practicode | ||
| # Practicode | ||
| [](https://www.rust-lang.org/) | ||
| [](https://ratatui.rs/) | ||
| [](#local-data) | ||
| [](#commands) | ||
| [](https://github.com/baba9811/practicode/actions/workflows/ci.yml) | ||
| [](https://crates.io/crates/practicode) | ||
| [](https://www.npmjs.com/package/practicode) | ||
| [](https://github.com/baba9811/practicode/actions/workflows/ci.yml) | ||
| [](https://socket.dev/npm/package/practicode) | ||
| [](https://www.npmjs.com/package/practicode) | ||
| [](https://github.com/baba9811/practicode/stargazers) | ||
| [](LICENSE) | ||
| Personal coding practice, right in your terminal. | ||
| Learn a programming language in 15 focused minutes a day—without leaving your terminal. | ||
| `practicode` is a small Rust TUI for syntax exercises and stdin/stdout coding-test practice. It keeps your problems, settings, and submissions local by default. | ||
| Practicode is an offline-first Rust TUI for developers moving between Python, TypeScript, Java, and Rust. It combines short, executable lessons, delayed review, a real local compiler/judge, and optional AI help. No account, streak, or telemetry is required. | ||
| <figure> | ||
| <img src="assets/practicode-home.svg" alt="Practicode home screen with Learn syntax and Practice coding tests choices"> | ||
| <figcaption>First run opens a small home screen for choosing learning or practice.</figcaption> | ||
| <img src="assets/practicode-home.svg" alt="Actual Practicode home TUI render showing guided learning and coding-test practice choices"> | ||
| <figcaption>Actual TUI render: choose a guided learning session or open-ended coding-test practice.</figcaption> | ||
| </figure> | ||
| <figure> | ||
| <img src="assets/practicode-terminal.svg" alt="Practicode terminal UI with problem text, code editor, status line, and command palette"> | ||
| <figcaption>Practice mode keeps the problem, editor, judge output, and command palette in one terminal.</figcaption> | ||
| <img src="assets/practicode-terminal.svg" alt="Actual Practicode learning TUI render with a lesson, code editor, and shortcuts"> | ||
| <figcaption>Actual TUI render: read, predict, edit, run, and reflect in one responsive terminal.</figcaption> | ||
| </figure> | ||
| ## What You Get | ||
| ## Start In One Minute | ||
| - Syntax learning and coding-test practice from one entry screen. | ||
| - Local judging for Python, TypeScript, Java, and Rust. | ||
| - A scrollable Ratatui UI with lesson/problem text, editor, run output, result status, and command palette. | ||
| - Optional Codex or Claude Code help for hints and generated problems. | ||
| ## Install | ||
| ### npm | ||
| ```bash | ||
@@ -42,32 +31,69 @@ npm install -g practicode | ||
| The npm package ships no install lifecycle script. The launcher runs the locked Cargo build on first use if the binary is missing. | ||
| The npm launcher downloads the matching prebuilt binary, verifies its SHA-256 checksum, and caches it in your user directory. Rust is not needed for the npm install path. | ||
| User data is stored under `~/.practicode` by default, regardless of the directory where you run the command. | ||
| Then choose **Continue today's session** and press `Enter`. Core lessons and progress work locally; network access is only needed for the first binary download, update checks, and AI features you explicitly invoke. | ||
| <details> | ||
| <summary>Cargo</summary> | ||
| ## Why Practicode | ||
| ```bash | ||
| cargo install practicode | ||
| practicode | ||
| ``` | ||
| - **A real daily loop:** up to two due reviews, one new core lesson, one prediction, one executable exercise, and one transfer prompt. | ||
| - **Trustworthy practice:** the curriculum CI compiles or runs 110 exercises against 337 deterministic cases and 54 semantic mutants. | ||
| - **Actual language differences:** lessons explain runtime versus type checker, ownership versus borrowing, value versus identity, imports versus visibility, and other transfer traps. | ||
| - **Mastery that waits:** successful core work moves through Practiced, Retained, and Mastered on a 1/3/7-day review schedule. | ||
| - **Fully local fundamentals:** without invoking optional AI, progress, source code, submissions, lessons, and judging stay on your machine. | ||
| - **Responsive terminal UX:** side-by-side panes on wide terminals, focused Lesson/Code/Result views on narrow terminals, keyboard and mouse support, dark and light themes. | ||
| - **Five-language curriculum and core loop:** every lesson and the main learning flow are localized in English, Korean, Japanese, Simplified Chinese, and Spanish. | ||
| - **Optional AI, limited authority:** Codex or Claude can explain and hint, but only deterministic judge results advance mastery. | ||
| </details> | ||
| ## The 15-Minute Session | ||
| <details> | ||
| <summary>Local checkout</summary> | ||
| | Step | What you do | What Practicode records | | ||
| | --- | --- | --- | | ||
| | Review | Recall the objective of up to two due lessons | Due order; attempts begin when the review exercise is judged | | ||
| | Language delta | Compare this language with ones you already know | Nothing yet—reading is not mastery | | ||
| | Predict | Pause and decide what the example or edge case will do | Nothing—this is a deliberate recall step | | ||
| | Exercise | Edit the starter and run real local cases | Pass/failure kind and attempt count | | ||
| | Reflect | Read the transfer trap after a passing run | Next 1/3/7-day review | | ||
| ```bash | ||
| git clone https://github.com/baba9811/practicode.git | ||
| cd practicode | ||
| npm install | ||
| npm start | ||
| ``` | ||
| `/progress` shows a privacy-safe summary with no paths or submitted code. Labs remain optional; the 12-item core path in each language determines course completion. | ||
| </details> | ||
| ## Curriculum | ||
| ### Language runtimes | ||
| | Language | Core path | Optional labs | Total | | ||
| | --- | ---: | ---: | ---: | | ||
| | Python 3.12 | 12 | 13 | 25 | | ||
| | TypeScript 5.9 / Node 22 | 12 | 16 | 28 | | ||
| | Java 21 | 12 | 16 | 28 | | ||
| | Rust 2024 | 12 | 17 | 29 | | ||
| Install only the languages you plan to practice. Python uses `python3` or `python`; TypeScript uses `node --experimental-strip-types`; Java uses `javac` and `java`; Rust uses `rustc`. | ||
| The courses are designed for experienced developers switching languages, not for memorizing isolated syntax. Every record includes a concept, worked example, common mistakes, self-checks, observable objective, language delta, prediction prompt, exercise, and transfer trap. | ||
| ## Controls | ||
| Press `/` outside the editor to open the command palette. The most useful controls are: | ||
| | Key or command | Action | | ||
| | --- | --- | | ||
| | `F1` | Open contextual help | | ||
| | `F5` or `/run` | Compile/run and judge the current exercise | | ||
| | `F6` | Cycle Lesson/Code/Result in Learn or Problem/Code in Practice | | ||
| | `/next` | Advance the guided step or open the next item | | ||
| | `/lesson` | Open the complete lesson reference | | ||
| | `/progress` | Show the shareable mastery summary | | ||
| | `/ask <question>` | Ask optional AI about the current learning context | | ||
| | `/doctor` | Check the installed language runtimes | | ||
| | `/home` | Return to the Learn/Practice chooser | | ||
| See [the command reference](docs/COMMANDS.md) for practice mode, aliases, generation, and profile settings. | ||
| ## Language Runtimes | ||
| The Practicode binary is prebuilt, but local judging needs the runtime for the language you exercise: | ||
| - Python: Python 3.12 | ||
| - TypeScript: Node.js 22 and `tsc` 5.9.3 | ||
| - Java: JDK 21 (`javac` and `java`) | ||
| - Rust: stable Rust with the 2024 edition | ||
| Install only what you plan to study, then run `/doctor` inside Practicode. | ||
| <details> | ||
@@ -77,3 +103,4 @@ <summary>macOS</summary> | ||
| ```bash | ||
| brew install python node | ||
| brew install python@3.12 node@22 | ||
| npm install -g typescript@5.9.3 | ||
| brew install --cask temurin@21 | ||
@@ -91,2 +118,3 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh | ||
| winget install -e --id OpenJS.NodeJS.LTS | ||
| npm install -g typescript@5.9.3 | ||
| winget install -e --id EclipseAdoptium.Temurin.21.JDK | ||
@@ -96,3 +124,3 @@ winget install -e --id Rustlang.Rustup | ||
| Restart the terminal after installing so `python`, `node`, `javac`, and `rustc` are on `PATH`. | ||
| Restart the terminal after installation. | ||
@@ -104,13 +132,14 @@ </details> | ||
| Use your distribution packages or the vendors' repositories for Python 3.12, Node 22, and JDK 21, then install the pinned TypeScript checker and Rust: | ||
| ```bash | ||
| sudo apt update | ||
| sudo apt install -y python3 nodejs npm openjdk-21-jdk curl build-essential | ||
| npm install -g typescript@5.9.3 | ||
| curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh | ||
| ``` | ||
| If `node --version` is below `v22.6.0`, install a newer Node.js from the official Node.js downloads or your preferred version manager before using TypeScript practice. | ||
| Official installers: [Python](https://docs.python.org/3/using/), [Node.js](https://nodejs.org/en/download), [Temurin](https://adoptium.net/installation/), and [Rust](https://www.rust-lang.org/tools/install). | ||
| </details> | ||
| Verify runtimes: | ||
| Verify the toolchain: | ||
@@ -120,2 +149,3 @@ ```bash | ||
| node --version | ||
| tsc --version | ||
| javac -version | ||
@@ -125,60 +155,26 @@ rustc --version | ||
| References: [Python](https://docs.python.org/3/using/), [Node.js](https://nodejs.org/en/download), [Rust](https://www.rust-lang.org/tools/install), [Eclipse Temurin](https://adoptium.net/installation/). | ||
| ## Docker Sandbox | ||
| After starting practicode, run `/doctor` to check these runtimes from inside the TUI. | ||
| To get all four supported language toolchains and keep submissions out of normal host processes: | ||
| Check the install: | ||
| ```bash | ||
| practicode --version | ||
| practicode --smoke | ||
| practicode --help | ||
| ``` | ||
| ### Docker sandbox | ||
| If you do not want submissions to run directly on your host, use the npm launcher sandbox: | ||
| ```bash | ||
| practicode --docker | ||
| ``` | ||
| The launcher builds a local `practicode-sandbox:<version>` image, then runs the TUI in Docker. The current directory is mounted at `/workspace` for legacy-data migration, while the host data directory is mounted at `/data` so progress survives container removal. The container runs without network access, with a read-only root filesystem, a writable `/tmp`, dropped Linux capabilities, `no-new-privileges`, and CPU/memory/process limits. | ||
| The npm launcher builds a local image with Python 3.12, Node 22, TypeScript 5.9.3, JDK 21, and stable Rust. It runs without network access, drops Linux capabilities, uses a read-only root filesystem, limits CPU/memory/processes, mounts the current workspace read-only, and gives the container write access only to the Practicode data directory. | ||
| Install Docker first if needed: | ||
| Docker is shared-kernel isolation, not a guarantee against every container escape. Check the image with: | ||
| ```bash | ||
| # macOS | ||
| brew install --cask docker | ||
| # Windows | ||
| winget install -e --id Docker.DockerDesktop | ||
| # Ubuntu / Debian | ||
| # Use Docker's official apt repository: | ||
| # Ubuntu: https://docs.docker.com/engine/install/ubuntu/ | ||
| # Debian: https://docs.docker.com/engine/install/debian/ | ||
| ``` | ||
| After starting Docker, check the sandbox: | ||
| ```bash | ||
| practicode --docker --smoke | ||
| ``` | ||
| ## Update | ||
| ## Other Install Paths | ||
| npm is the primary install path: | ||
| ```bash | ||
| npm update -g practicode | ||
| ``` | ||
| The app checks npm for newer releases in the background and shows `/update` in the status line when one is available. Disable that check with `PRACTICODE_NO_UPDATE_CHECK=1`. | ||
| <details> | ||
| <summary>Cargo update</summary> | ||
| <summary>Cargo</summary> | ||
| ```bash | ||
| cargo install --force practicode | ||
| cargo install practicode | ||
| practicode | ||
| ``` | ||
@@ -189,8 +185,8 @@ | ||
| <details> | ||
| <summary>Local checkout update</summary> | ||
| <summary>Source checkout</summary> | ||
| ```bash | ||
| git pull --ff-only | ||
| npm install | ||
| npm start | ||
| git clone https://github.com/baba9811/practicode.git | ||
| cd practicode | ||
| cargo run -- | ||
| ``` | ||
@@ -200,79 +196,58 @@ | ||
| ## First Run | ||
| Prebuilt npm binaries support macOS Intel/Apple Silicon, Linux x64/arm64, and Windows x64. On another target, use Cargo or Docker. | ||
| On first run, choose a mode: | ||
| ## Binary Cache And Offline Use | ||
| ```text | ||
| Learn syntax: study the concept, worked example, mistakes, and self-check; use /ask when stuck; edit the exercise, /run, then /next | ||
| Practice coding tests: write code, Esc, /run, then /next when it passes | ||
| ``` | ||
| Every native npm launch revalidates the cached binary against its stored SHA-256 checksum. A failed or partial download is removed before execution. | ||
| Use arrow keys to move on the home screen, and `Enter` or `Space` to open the selected mode. | ||
| | Platform | Default cache root | | ||
| | --- | --- | | ||
| | macOS | `~/Library/Caches/practicode/` | | ||
| | Linux | `$XDG_CACHE_HOME/practicode/` or `~/.cache/practicode/` | | ||
| | Windows | `%LOCALAPPDATA%\practicode\` | | ||
| ## Commands | ||
| Set `PRACTICODE_CACHE_DIR` to choose another cache. Once a version has been verified, it can launch offline from that version-and-platform-specific cache. | ||
| Type `/` outside the editor to open the command palette. Use `up/down` to move, `Enter` to run or complete the selected command, and `Esc` to cancel. | ||
| Update npm installs with: | ||
| Most-used commands: | ||
| ```bash | ||
| npm update -g practicode | ||
| ``` | ||
| | Command | Action | | ||
| | --- | --- | | ||
| | `/home` | Return to the mode chooser | | ||
| | `/run` | Judge the current submission or syntax exercise | | ||
| | `/ask <question>` | Ask AI about the current lesson or problem without leaving the TUI | | ||
| | `/next` | Open the next problem or lesson | | ||
| | `/back` | Go to the previous problem or lesson | | ||
| | `/doctor` | Check local runtimes and show install hints | | ||
| | `/profile` | Edit language, theme, difficulty, topics, and AI settings | | ||
| Practicode checks npm for newer versions in the background. Disable that check with `PRACTICODE_NO_UPDATE_CHECK=1`. | ||
| See [docs/COMMANDS.md](docs/COMMANDS.md) for the full command list, aliases, AI generation commands, and profile settings. | ||
| ## Local Data And Privacy | ||
| ## Local Data | ||
| User data lives under `~/.practicode` by default (`%USERPROFILE%\.practicode` on Windows): | ||
| Generated problems, settings, and submissions live in one user-data directory: | ||
| | Path | Purpose | | ||
| | --- | --- | | ||
| | `~/.practicode/problem_bank.json` | Local/custom/generated problems | | ||
| | `~/.practicode/problem_notes.md` | Optional personal problem-generation notes | | ||
| | `~/.practicode/problem-state.json` | Current problem, history, and settings | | ||
| | `~/.practicode/problems/` | Generated problem markdown/index files | | ||
| | `~/.practicode/submissions/` | Your answer files | | ||
| | `problem-state.json` | Settings, history, and learning mastery | | ||
| | `problem_bank.json` | Local, custom, and generated problems | | ||
| | `problem_notes.md` | Optional generation preferences | | ||
| | `problems/` | Generated problem statements and indexes | | ||
| | `submissions/` | Your source files | | ||
| On Windows, `~` means `%USERPROFILE%`. Set `PRACTICODE_HOME` to use another directory: | ||
| Set `PRACTICODE_HOME=/another/path` to relocate all user data. | ||
| ```bash | ||
| PRACTICODE_HOME=/path/to/practicode-data practicode | ||
| ``` | ||
| `/run` executes local source as a normal child process unless Docker mode is active. When invoked, `/ask` and `/hint` send the current problem or lesson, submission code, latest result, and your question to the selected provider CLI. AI-backed `/next` and `/generate` run that CLI from `PRACTICODE_HOME`; they may read the local state, bank, notes, problem index, and submissions and may create or update generated problem files under the CLI's configured `workspace-write`/`acceptEdits` permissions. Custom `ai_next_command` programs inherit the access you give them. Review your provider and custom-command settings before enabling AI. Environment variables are scrubbed before judging, and hidden expected values are not printed in failure logs. See [SECURITY.md](SECURITY.md). | ||
| When upgrading from `0.1.19` or earlier, launch `practicode` once from the old practice directory while the new data directory is empty. State, problems, and submissions are copied into the new location; the originals are not changed, and disposable build output is not copied. If the old practice directory was your home directory itself, choose a new empty `PRACTICODE_HOME` and keep that override configured afterward; automatic sibling-folder copying is disabled when the old metadata path already equals the default global path. | ||
| ## Practice Mode | ||
| ## Safety | ||
| The second home option keeps classic stdin/stdout coding-test work beside the guided curriculum. It includes a local problem bank, gradual difficulty, exact-output judging, problem history, and optional AI generation. Learn mode and Practice mode share the editor and judge but keep their progress rules separate. | ||
| - `/run` executes your local submission as a normal process. It is not an OS sandbox. | ||
| - `practicode --docker` runs the TUI and judge in a restricted Docker container, but Docker is still a shared-kernel container runtime, not a guarantee against every escape. | ||
| - `/run` scrubs inherited environment variables and hides case input/expected output in failure logs. | ||
| - `/hint`, AI-backed `/next`, and `/generate` send the current problem/submission context to the selected provider CLI. | ||
| - `settings.ai_next_command` can run a custom shell command. Save only commands you trust. | ||
| - Do not publish tokens, private prompts, `.env`, `.npmrc`, or the contents of your practicode data directory. | ||
| Security reporting details live in [SECURITY.md](SECURITY.md). | ||
| ## Contributing | ||
| - External contribution flow: [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) | ||
| - Maintainer and release notes: [docs/MAINTAINING.md](docs/MAINTAINING.md) | ||
| - Code layout and extension rules: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | ||
| The fastest contributions are a precise lesson correction, a reproducible terminal bug, or a focused accessibility improvement. | ||
| Local checks: | ||
| - [Contribution guide](docs/CONTRIBUTING.md) | ||
| - [Lesson catalog contract](assets/lessons/README.md) | ||
| - [Architecture](docs/ARCHITECTURE.md) | ||
| - [Maintainer and release guide](docs/MAINTAINING.md) | ||
| - [Privacy-safe growth plan](docs/GROWTH.md) | ||
| ```bash | ||
| cargo fmt --check | ||
| cargo test | ||
| cargo clippy --all-targets --all-features -- -D warnings | ||
| cargo run -- --smoke | ||
| ``` | ||
| Run the full local gate with `make test`. Changed lesson content additionally requires executable cases, a refreshed content hash, and an independent agent review recorded in the lesson review manifest; a human reviewer is not required. | ||
| ## License | ||
| practicode is MIT licensed. Third-party dependency license notes are in [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md). | ||
| Practicode is MIT licensed. Third-party dependency notices are in [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md). |
+122
-51
@@ -5,3 +5,3 @@ use crate::{ | ||
| SyntaxLesson, UI_LANGUAGES, ensure_submission, ensure_syntax_submission, | ||
| normalize_ai_provider, render_problem, syntax_lesson_study_context, | ||
| normalize_ai_provider, render_problem, syntax_lesson_study_context, ui_text, | ||
| }, | ||
@@ -134,4 +134,32 @@ process::{run_capture, sh_quote, shell_process, unique_temp_path, which}, | ||
| pub(crate) enum AiGenerationResult { | ||
| Succeeded(String), | ||
| Failed { status: Option<i32>, detail: String }, | ||
| FailedToRun(String), | ||
| } | ||
| pub fn run_ai_generate(root: &Path, state: &AppState, request: &str) -> String { | ||
| let provider = normalize_ai_provider(&state.settings.ai_provider); | ||
| match run_ai_generate_result(root, state, request) { | ||
| AiGenerationResult::Succeeded(detail) => { | ||
| format!("{provider} background generation finished\n{detail}") | ||
| .trim() | ||
| .to_string() | ||
| } | ||
| AiGenerationResult::Failed { status, detail } => format!( | ||
| "{provider} background generation failed ({})\n{detail}", | ||
| status.unwrap_or(-1) | ||
| ), | ||
| AiGenerationResult::FailedToRun(detail) => { | ||
| format!("{provider} background generation failed\n{detail}") | ||
| } | ||
| } | ||
| } | ||
| pub(crate) fn run_ai_generate_result( | ||
| root: &Path, | ||
| state: &AppState, | ||
| request: &str, | ||
| ) -> AiGenerationResult { | ||
| let provider = normalize_ai_provider(&state.settings.ai_provider); | ||
| let command = if state.settings.next_ai_command().trim().is_empty() { | ||
@@ -152,15 +180,9 @@ default_ai_generate_command(root, &state.settings, request) | ||
| Ok(run) if run.code == Some(0) => { | ||
| let output = output_text(&run.stdout, &run.stderr); | ||
| format!("{provider} background generation finished\n{output}") | ||
| .trim() | ||
| .to_string() | ||
| AiGenerationResult::Succeeded(output_text(&run.stdout, &run.stderr)) | ||
| } | ||
| Ok(run) => { | ||
| let output = output_text(&run.stdout, &run.stderr); | ||
| format!( | ||
| "{provider} background generation failed ({})\n{output}", | ||
| run.code.unwrap_or(-1) | ||
| ) | ||
| } | ||
| Err(error) => format!("{provider} background generation failed\n{error}"), | ||
| Ok(run) => AiGenerationResult::Failed { | ||
| status: run.code, | ||
| detail: output_text(&run.stdout, &run.stderr), | ||
| }, | ||
| Err(error) => AiGenerationResult::FailedToRun(error.to_string()), | ||
| } | ||
@@ -183,28 +205,47 @@ } | ||
| pub fn provider_status(provider: &str) -> String { | ||
| match normalize_ai_provider(provider).as_str() { | ||
| "claude" => { | ||
| if which("claude").is_some() { | ||
| "Claude CLI found.".to_string() | ||
| } else { | ||
| "Claude CLI not found. Install Claude Code or choose /provider codex.".to_string() | ||
| } | ||
| pub fn provider_status(provider: &str, lang: &str) -> String { | ||
| let provider = normalize_ai_provider(provider); | ||
| let command = if provider == "claude" { | ||
| "claude" | ||
| } else { | ||
| "codex" | ||
| }; | ||
| provider_status_with( | ||
| &provider, | ||
| lang, | ||
| which(command).is_some(), | ||
| provider == "codex" && codex_daemon_path().is_some_and(|path| path.exists()), | ||
| ) | ||
| } | ||
| fn provider_status_with(provider: &str, lang: &str, found: bool, daemon: bool) -> String { | ||
| if provider == "claude" { | ||
| if found { | ||
| return ui_text(lang, "provider_cli_found").replace("{provider}", "Claude"); | ||
| } | ||
| _ => { | ||
| if which("codex").is_none() { | ||
| return "Codex CLI not found. Install Codex CLI or choose /provider claude." | ||
| .to_string(); | ||
| } | ||
| if codex_daemon_path().is_some_and(|path| path.exists()) { | ||
| "Codex CLI found. App-server daemon is available.".to_string() | ||
| } else { | ||
| "Codex CLI found. App-server daemon is not available; practicode will use codex exec directly.".to_string() | ||
| } | ||
| } | ||
| return ui_text(lang, "provider_cli_missing") | ||
| .replace("{provider}", "Claude") | ||
| .replace("{install}", "Claude Code") | ||
| .replace("{command}", "/provider codex"); | ||
| } | ||
| if !found { | ||
| return ui_text(lang, "provider_cli_missing") | ||
| .replace("{provider}", "Codex") | ||
| .replace("{install}", "Codex CLI") | ||
| .replace("{command}", "/provider claude"); | ||
| } | ||
| ui_text( | ||
| lang, | ||
| if daemon { | ||
| "provider_codex_daemon_available" | ||
| } else { | ||
| "provider_codex_direct_fallback" | ||
| }, | ||
| ) | ||
| .to_string() | ||
| } | ||
| pub fn available_models(provider: &str) -> ModelCatalog { | ||
| pub fn available_models(provider: &str, lang: &str) -> ModelCatalog { | ||
| match normalize_ai_provider(provider).as_str() { | ||
| "codex" => codex_models(), | ||
| "codex" => codex_models(lang), | ||
| "claude" => ModelCatalog { | ||
@@ -215,7 +256,10 @@ models: CLAUDE_MODEL_FALLBACKS | ||
| .collect(), | ||
| message: Some(format!( | ||
| "Bundled Claude presets from Claude Code {} --help. Efforts: {}.", | ||
| claude_version().unwrap_or_else(|| "current".to_string()), | ||
| CLAUDE_AI_EFFORTS.join(", ") | ||
| )), | ||
| message: Some( | ||
| ui_text(lang, "model_claude_presets") | ||
| .replace( | ||
| "{version}", | ||
| &claude_version().unwrap_or_else(|| "?".to_string()), | ||
| ) | ||
| .replace("{efforts}", &CLAUDE_AI_EFFORTS.join(", ")), | ||
| ), | ||
| }, | ||
@@ -285,3 +329,3 @@ _ => ModelCatalog::default(), | ||
| fn codex_models() -> ModelCatalog { | ||
| fn codex_models(lang: &str) -> ModelCatalog { | ||
| if which("codex").is_none() { | ||
@@ -291,3 +335,6 @@ return ModelCatalog { | ||
| message: Some( | ||
| "Codex CLI not found; choose /provider claude or install Codex CLI.".to_string(), | ||
| ui_text(lang, "model_cli_missing") | ||
| .replace("{provider}", "Codex") | ||
| .replace("{command}", "/provider claude") | ||
| .replace("{install}", "Codex CLI"), | ||
| ), | ||
@@ -299,3 +346,3 @@ }; | ||
| models: codex_cached_models(), | ||
| message: Some("Codex app-server daemon is unavailable; install the standalone Codex app to list models, or use /model <name>.".to_string()), | ||
| message: Some(ui_text(lang, "model_codex_daemon_unavailable").to_string()), | ||
| }; | ||
@@ -312,5 +359,3 @@ } | ||
| models: codex_cached_models(), | ||
| message: Some( | ||
| "Could not query Codex model list; using bundled/cache model presets.".to_string(), | ||
| ), | ||
| message: Some(ui_text(lang, "model_codex_query_failed").to_string()), | ||
| }; | ||
@@ -323,7 +368,5 @@ }; | ||
| message: Some(if detail.is_empty() { | ||
| "Could not query Codex model list; using bundled/cache model presets.".to_string() | ||
| ui_text(lang, "model_codex_query_failed").to_string() | ||
| } else { | ||
| format!( | ||
| "Could not query Codex model list; using bundled/cache model presets: {detail}" | ||
| ) | ||
| format!("{}: {detail}", ui_text(lang, "model_codex_query_failed")) | ||
| }), | ||
@@ -336,3 +379,3 @@ }; | ||
| models, | ||
| message: Some("Codex app-server returned no models.".to_string()), | ||
| message: Some(ui_text(lang, "model_codex_empty").to_string()), | ||
| } | ||
@@ -610,3 +653,3 @@ } else { | ||
| mod tests { | ||
| use super::parse_model_list; | ||
| use super::*; | ||
@@ -619,2 +662,30 @@ #[test] | ||
| } | ||
| #[test] | ||
| fn provider_status_localizes_every_app_owned_branch() { | ||
| let cases = [ | ||
| ("claude", true, false, "CLI를 찾았습니다"), | ||
| ("claude", false, false, "CLI가 없습니다"), | ||
| ("codex", false, false, "CLI가 없습니다"), | ||
| ("codex", true, true, "데몬을 사용할 수 있습니다"), | ||
| ("codex", true, false, "codex exec를 직접 사용합니다"), | ||
| ]; | ||
| for (provider, found, daemon, expected) in cases { | ||
| let status = provider_status_with(provider, "ko", found, daemon); | ||
| assert!(status.contains(expected), "{provider}:{expected}: {status}"); | ||
| assert!(!status.contains("not found"), "{status}"); | ||
| assert!(!status.contains("is available"), "{status}"); | ||
| } | ||
| } | ||
| #[test] | ||
| fn bundled_model_notice_localizes_copy_and_preserves_raw_tokens() { | ||
| let catalog = available_models("claude", "zh"); | ||
| let message = catalog.message.unwrap(); | ||
| assert!(message.contains("内置 Claude 预设"), "{message}"); | ||
| assert!(message.contains("--help"), "{message}"); | ||
| assert!(message.contains("auto, low, medium"), "{message}"); | ||
| assert!(!message.contains("Bundled Claude presets"), "{message}"); | ||
| } | ||
| } |
+10
-0
@@ -15,2 +15,3 @@ use crate::process::{CommandSpec, run_capture, which}; | ||
| mod language; | ||
| mod learning; | ||
| mod model; | ||
@@ -28,2 +29,6 @@ mod problem_files; | ||
| pub use language::*; | ||
| #[cfg(test)] | ||
| pub(crate) use learning::record_syntax_result_for_lessons; | ||
| pub(crate) use learning::syntax_review_due_at; | ||
| pub use learning::*; | ||
| pub use model::*; | ||
@@ -38,1 +43,6 @@ pub use problem_files::*; | ||
| pub use syntax::*; | ||
| pub(crate) use syntax::{ | ||
| localized_syntax_exercise_prompt, localized_syntax_language_delta, localized_syntax_objective, | ||
| localized_syntax_prediction_prompt, localized_syntax_title, localized_syntax_transfer_trap, | ||
| syntax_core_progress_count, | ||
| }; |
+473
-19
| use super::*; | ||
| use std::env; | ||
| const TYPESCRIPT_TYPECHECK_FLAGS: [&str; 8] = [ | ||
| "--noEmit", | ||
| "--strict", | ||
| "--target", | ||
| "ES2022", | ||
| "--module", | ||
| "nodenext", | ||
| "--moduleResolution", | ||
| "nodenext", | ||
| ]; | ||
| const JAVA_RELEASE_FLAGS: [&str; 2] = ["--release", "21"]; | ||
| const RUST_EDITION_FLAG: &str = "--edition=2024"; | ||
| const MAX_COMPILER_DIAGNOSTIC_LINES: usize = 12; | ||
| const MAX_COMPILER_DIAGNOSTIC_BYTES: usize = 4_096; | ||
| const DIAGNOSTIC_TRUNCATED_MARKER: &str = "[diagnostic truncated]"; | ||
| #[derive(Debug)] | ||
| struct CommandFailure { | ||
| kind: JudgeFailureKind, | ||
| detail: String, | ||
| } | ||
| impl std::fmt::Display for CommandFailure { | ||
| fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| formatter.write_str(&self.detail) | ||
| } | ||
| } | ||
| impl std::error::Error for CommandFailure {} | ||
| pub fn normalize_judge_output(output: &str) -> String { | ||
| output.replace("\r\n", "\n") | ||
| } | ||
| pub fn ensure_submission(root: &Path, problem: &Problem, settings: &Settings) -> Result<PathBuf> { | ||
@@ -24,2 +58,3 @@ let language = normalize_language(&settings.language); | ||
| total_cases: 0, | ||
| failure_kind: None, | ||
| output: "problem has no judge cases".to_string(), | ||
@@ -35,2 +70,3 @@ }; | ||
| total_cases: problem.cases.len(), | ||
| failure_kind: None, | ||
| output: error.to_string(), | ||
@@ -51,2 +87,16 @@ }; | ||
| ) -> JudgeResult { | ||
| judge_path_with(root, id, path, language, cases, command_for) | ||
| } | ||
| fn judge_path_with<F>( | ||
| root: &Path, | ||
| id: &str, | ||
| path: &Path, | ||
| language: &str, | ||
| cases: &[IoCase], | ||
| mut prepare: F, | ||
| ) -> JudgeResult | ||
| where | ||
| F: FnMut(&Path, &Path, &str) -> Result<Option<CommandSpec>>, | ||
| { | ||
| if cases.is_empty() { | ||
@@ -57,2 +107,3 @@ return JudgeResult { | ||
| total_cases: 0, | ||
| failure_kind: None, | ||
| output: "problem has no judge cases".to_string(), | ||
@@ -62,3 +113,3 @@ }; | ||
| let language = normalize_language(language); | ||
| let command = match command_for(root, path, &language) { | ||
| let command = match prepare(root, path, &language) { | ||
| Ok(Some(command)) => command, | ||
@@ -70,2 +121,3 @@ Ok(None) => { | ||
| total_cases: cases.len(), | ||
| failure_kind: None, | ||
| output: format!("Missing runtime for {language}"), | ||
@@ -75,2 +127,10 @@ }; | ||
| Err(error) => { | ||
| let failure_kind = error | ||
| .downcast_ref::<CommandFailure>() | ||
| .map(|failure| failure.kind) | ||
| .unwrap_or(if language == "ts" { | ||
| JudgeFailureKind::TypeCheck | ||
| } else { | ||
| JudgeFailureKind::Compile | ||
| }); | ||
| return JudgeResult { | ||
@@ -80,3 +140,4 @@ passed: false, | ||
| total_cases: cases.len(), | ||
| output: format!("compile failed\n{error}"), | ||
| failure_kind: Some(failure_kind), | ||
| output: error.to_string(), | ||
| }; | ||
@@ -91,2 +152,3 @@ } | ||
| total_cases: cases.len(), | ||
| failure_kind: None, | ||
| output: error.to_string(), | ||
@@ -98,2 +160,3 @@ }; | ||
| let mut lines = Vec::new(); | ||
| let mut failure_kind = None; | ||
| for (index, case) in cases.iter().enumerate() { | ||
@@ -106,2 +169,3 @@ let mut process = Command::new(&command.program); | ||
| Err(error) => { | ||
| failure_kind = Some(JudgeFailureKind::Runtime); | ||
| lines.push(format!("Case {}: FAIL", index + 1)); | ||
@@ -112,4 +176,4 @@ push_labeled_block(&mut lines, "Error", &error.to_string()); | ||
| }; | ||
| let got = run.stdout.trim(); | ||
| let expected = case.output.trim(); | ||
| let got = normalize_judge_output(&run.stdout); | ||
| let expected = normalize_judge_output(&case.output); | ||
| if !run.timed_out && run.code == Some(0) && got == expected { | ||
@@ -125,7 +189,25 @@ passed += 1; | ||
| } else { | ||
| failure_kind = Some(if run.timed_out { | ||
| JudgeFailureKind::Timeout | ||
| } else if run.code != Some(0) { | ||
| JudgeFailureKind::Runtime | ||
| } else { | ||
| JudgeFailureKind::Output | ||
| }); | ||
| lines.push(format!("Case {}: FAIL", index + 1)); | ||
| if run.timed_out { | ||
| push_labeled_block(&mut lines, "Error", "timeout: 5s"); | ||
| } else if run.code != Some(0) { | ||
| push_labeled_block( | ||
| &mut lines, | ||
| "Error", | ||
| &format!( | ||
| "process exited with status {}", | ||
| run.code | ||
| .map(|code| code.to_string()) | ||
| .unwrap_or_else(|| "unknown".to_string()) | ||
| ), | ||
| ); | ||
| } | ||
| push_labeled_block(&mut lines, "Got", run.stdout.trim_end()); | ||
| push_labeled_block(&mut lines, "Got", &got); | ||
| push_labeled_block(&mut lines, "Input", "<hidden>"); | ||
@@ -144,2 +226,3 @@ push_labeled_block(&mut lines, "Expected", "<hidden>"); | ||
| total_cases: cases.len(), | ||
| failure_kind, | ||
| output: lines.join("\n"), | ||
@@ -151,6 +234,6 @@ } | ||
| lines.push(String::new()); | ||
| lines.push(label.to_string()); | ||
| if body.is_empty() { | ||
| lines.push(" <empty>".to_string()); | ||
| lines.push(format!("{label}: <empty>")); | ||
| } else { | ||
| lines.push(label.to_string()); | ||
| lines.extend(body.lines().map(|line| format!(" {line}"))); | ||
@@ -199,9 +282,3 @@ } | ||
| })), | ||
| "ts" => Ok(which("node").map(|program| CommandSpec { | ||
| program, | ||
| args: vec![ | ||
| "--experimental-strip-types".to_string(), | ||
| path.display().to_string(), | ||
| ], | ||
| })), | ||
| "ts" => compile_typescript(root, path), | ||
| "java" => compile_java(root, path), | ||
@@ -213,2 +290,177 @@ "rust" => compile_rust(root, path), | ||
| fn compile_typescript(root: &Path, path: &Path) -> Result<Option<CommandSpec>> { | ||
| compile_typescript_with(root, path, which, |program, args| { | ||
| let mut command = Command::new(program); | ||
| command.args(args).current_dir(root); | ||
| run_capture(&mut command, "", Duration::from_secs(30)) | ||
| }) | ||
| } | ||
| fn compile_typescript_with<F, R>( | ||
| root: &Path, | ||
| path: &Path, | ||
| mut find: F, | ||
| mut run: R, | ||
| ) -> Result<Option<CommandSpec>> | ||
| where | ||
| F: FnMut(&str) -> Option<PathBuf>, | ||
| R: FnMut(&Path, &[String]) -> Result<crate::process::RunOutput>, | ||
| { | ||
| let Some(tsc) = find("tsc") else { | ||
| return Err(missing_typescript_tool_failure("tsc").into()); | ||
| }; | ||
| let version = run(&tsc, &["--version".to_string()])?; | ||
| validate_typescript_version(&version)?; | ||
| let build = root | ||
| .join("build") | ||
| .join(path.parent().and_then(Path::file_name).unwrap_or_default()) | ||
| .join("typescript"); | ||
| fs::create_dir_all(&build)?; | ||
| let shim = build.join("node-shim.d.ts"); | ||
| let type_root = build.join("type-roots"); | ||
| if type_root.exists() { | ||
| fs::remove_dir_all(&type_root)?; | ||
| } | ||
| fs::create_dir_all(&type_root)?; | ||
| fs::write( | ||
| &shim, | ||
| include_str!("../../assets/typescript/node-shim.d.ts"), | ||
| )?; | ||
| let mut args = TYPESCRIPT_TYPECHECK_FLAGS.map(str::to_string).to_vec(); | ||
| args.extend([ | ||
| "--typeRoots".to_string(), | ||
| type_root.display().to_string(), | ||
| shim.display().to_string(), | ||
| path.display().to_string(), | ||
| ]); | ||
| let output = run(&tsc, &args)?; | ||
| if compiler_gate_failed(&output) { | ||
| return Err(compiler_failure(JudgeFailureKind::TypeCheck, &output).into()); | ||
| } | ||
| let Some(node) = find("node") else { | ||
| return Err(missing_typescript_tool_failure("node").into()); | ||
| }; | ||
| Ok(Some(CommandSpec { | ||
| program: node, | ||
| args: vec![ | ||
| "--experimental-strip-types".to_string(), | ||
| path.display().to_string(), | ||
| ], | ||
| })) | ||
| } | ||
| fn compiler_detail(output: &crate::process::RunOutput) -> String { | ||
| let stdout = normalize_judge_output(&output.stdout); | ||
| let stderr = normalize_judge_output(&output.stderr); | ||
| let detail = match (stdout.trim_end(), stderr.trim_end()) { | ||
| ("", "") => String::new(), | ||
| ("", stderr) => stderr.to_string(), | ||
| (stdout, "") => stdout.to_string(), | ||
| (stdout, stderr) => format!("{stdout}\n{stderr}"), | ||
| }; | ||
| let detail = if output.timed_out { | ||
| if detail.is_empty() { | ||
| "timeout: 30s".to_string() | ||
| } else { | ||
| format!("timeout: 30s\n{detail}") | ||
| } | ||
| } else if detail.is_empty() { | ||
| "compiler exited without a diagnostic".to_string() | ||
| } else { | ||
| detail | ||
| }; | ||
| bound_compiler_detail(&detail) | ||
| } | ||
| fn bound_compiler_detail(detail: &str) -> String { | ||
| let mut lines = 1; | ||
| let mut end = detail.len(); | ||
| for (index, char) in detail.char_indices() { | ||
| if index + char.len_utf8() > MAX_COMPILER_DIAGNOSTIC_BYTES | ||
| || (char == '\n' && lines == MAX_COMPILER_DIAGNOSTIC_LINES) | ||
| { | ||
| end = index; | ||
| break; | ||
| } | ||
| if char == '\n' { | ||
| lines += 1; | ||
| } | ||
| } | ||
| if end == detail.len() { | ||
| detail.to_string() | ||
| } else { | ||
| format!( | ||
| "{}\n{DIAGNOSTIC_TRUNCATED_MARKER}", | ||
| detail[..end].trim_end() | ||
| ) | ||
| } | ||
| } | ||
| fn compiler_failure(kind: JudgeFailureKind, output: &crate::process::RunOutput) -> CommandFailure { | ||
| CommandFailure { | ||
| kind: if output.timed_out { | ||
| JudgeFailureKind::Timeout | ||
| } else { | ||
| kind | ||
| }, | ||
| detail: compiler_detail(output), | ||
| } | ||
| } | ||
| fn compiler_gate_failed(output: &crate::process::RunOutput) -> bool { | ||
| output.timed_out || output.code != Some(0) | ||
| } | ||
| pub(crate) fn typescript_version_is_supported(output: &str) -> bool { | ||
| let Some(version) = output.trim().strip_prefix("Version ") else { | ||
| return false; | ||
| }; | ||
| let mut parts = version.split('.'); | ||
| matches!( | ||
| ( | ||
| parts.next().and_then(|part| part.parse::<u64>().ok()), | ||
| parts.next().and_then(|part| part.parse::<u64>().ok()), | ||
| parts.next().and_then(|part| part.parse::<u64>().ok()), | ||
| parts.next(), | ||
| ), | ||
| (Some(5), Some(9), Some(_), None) | ||
| ) | ||
| } | ||
| fn validate_typescript_version( | ||
| output: &crate::process::RunOutput, | ||
| ) -> std::result::Result<(), CommandFailure> { | ||
| if compiler_gate_failed(output) { | ||
| return Err(compiler_failure(JudgeFailureKind::TypeCheck, output)); | ||
| } | ||
| if typescript_version_is_supported(&output.stdout) { | ||
| return Ok(()); | ||
| } | ||
| let reported = output.stdout.trim(); | ||
| let detail = format!( | ||
| "TypeScript 5.9.x required; found {}", | ||
| if reported.is_empty() { | ||
| "unreadable version output" | ||
| } else { | ||
| reported | ||
| } | ||
| ); | ||
| Err(CommandFailure { | ||
| kind: JudgeFailureKind::TypeCheck, | ||
| detail: bound_compiler_detail(&detail), | ||
| }) | ||
| } | ||
| fn missing_typescript_tool_failure(tool: &str) -> CommandFailure { | ||
| CommandFailure { | ||
| kind: if tool == "tsc" { | ||
| JudgeFailureKind::TypeCheck | ||
| } else { | ||
| JudgeFailureKind::Runtime | ||
| }, | ||
| detail: format!("Missing runtime for TypeScript: {tool}"), | ||
| } | ||
| } | ||
| fn compile_java(root: &Path, path: &Path) -> Result<Option<CommandSpec>> { | ||
@@ -228,2 +480,3 @@ let Some(javac) = which("javac") else { | ||
| compile | ||
| .args(JAVA_RELEASE_FLAGS) | ||
| .args([ | ||
@@ -236,4 +489,4 @@ "-d", | ||
| let output = run_capture(&mut compile, "", Duration::from_secs(30))?; | ||
| if output.code != Some(0) { | ||
| return Err(anyhow!(output.stderr.trim().to_string())); | ||
| if compiler_gate_failed(&output) { | ||
| return Err(compiler_failure(JudgeFailureKind::Compile, &output).into()); | ||
| } | ||
@@ -265,4 +518,4 @@ Ok(Some(CommandSpec { | ||
| compile | ||
| .arg(RUST_EDITION_FLAG) | ||
| .args([ | ||
| "--edition=2024".to_string(), | ||
| path.display().to_string(), | ||
@@ -274,4 +527,4 @@ "-o".to_string(), | ||
| let output = run_capture(&mut compile, "", Duration::from_secs(30))?; | ||
| if output.code != Some(0) { | ||
| return Err(anyhow!(output.stderr.trim().to_string())); | ||
| if compiler_gate_failed(&output) { | ||
| return Err(compiler_failure(JudgeFailureKind::Compile, &output).into()); | ||
| } | ||
@@ -283,1 +536,202 @@ Ok(Some(CommandSpec { | ||
| } | ||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| #[test] | ||
| fn labeled_blocks_distinguish_semantic_empty_from_literal_empty_output() { | ||
| let mut semantic_empty = Vec::new(); | ||
| push_labeled_block(&mut semantic_empty, "Got", ""); | ||
| assert_eq!(semantic_empty, ["", "Got: <empty>"]); | ||
| let mut literal_empty = Vec::new(); | ||
| push_labeled_block(&mut literal_empty, "Got", "<empty>"); | ||
| assert_eq!(literal_empty, ["", "Got", " <empty>"]); | ||
| } | ||
| #[test] | ||
| fn compiler_contract_flags_are_explicit() { | ||
| assert_eq!( | ||
| TYPESCRIPT_TYPECHECK_FLAGS, | ||
| [ | ||
| "--noEmit", | ||
| "--strict", | ||
| "--target", | ||
| "ES2022", | ||
| "--module", | ||
| "nodenext", | ||
| "--moduleResolution", | ||
| "nodenext", | ||
| ] | ||
| ); | ||
| assert_eq!(JAVA_RELEASE_FLAGS, ["--release", "21"]); | ||
| assert_eq!(RUST_EDITION_FLAG, "--edition=2024"); | ||
| } | ||
| #[test] | ||
| fn compiler_gate_rejects_timeout_even_with_zero_exit_code() { | ||
| let output = crate::process::RunOutput { | ||
| code: Some(0), | ||
| stdout: String::new(), | ||
| stderr: "partial diagnostic\n".to_string(), | ||
| timed_out: true, | ||
| }; | ||
| let failure = compiler_failure(JudgeFailureKind::Compile, &output); | ||
| assert!(compiler_gate_failed(&output)); | ||
| assert_eq!(failure.kind, JudgeFailureKind::Timeout); | ||
| assert!(failure.detail.starts_with("timeout: 30s\n")); | ||
| assert!(failure.detail.contains("partial diagnostic")); | ||
| } | ||
| #[test] | ||
| fn missing_typescript_tools_are_classified_by_stage() { | ||
| assert_eq!( | ||
| missing_typescript_tool_failure("tsc").kind, | ||
| JudgeFailureKind::TypeCheck | ||
| ); | ||
| assert_eq!( | ||
| missing_typescript_tool_failure("node").kind, | ||
| JudgeFailureKind::Runtime | ||
| ); | ||
| } | ||
| #[test] | ||
| fn compiler_diagnostics_are_bounded_without_splitting_utf8() { | ||
| let mut stderr = "solution.ts(1,1): error TS9999: first useful message".to_string(); | ||
| for line in 0..40 { | ||
| stderr.push_str(&format!("\n{line}: {}", "한".repeat(300))); | ||
| } | ||
| let output = crate::process::RunOutput { | ||
| code: Some(1), | ||
| stdout: String::new(), | ||
| stderr, | ||
| timed_out: false, | ||
| }; | ||
| let detail = compiler_detail(&output); | ||
| assert!(detail.starts_with("solution.ts(1,1): error TS9999: first useful message")); | ||
| assert!(detail.contains("[diagnostic truncated]")); | ||
| assert!(detail.lines().count() <= 13); | ||
| assert!(detail.len() <= 4_128); | ||
| assert!(!detail.contains('\u{fffd}')); | ||
| } | ||
| #[test] | ||
| fn typescript_version_contract_accepts_only_5_9() { | ||
| assert!(typescript_version_is_supported("Version 5.9.0")); | ||
| assert!(typescript_version_is_supported("Version 5.9.99\n")); | ||
| assert!(!typescript_version_is_supported("Version 5.8.4")); | ||
| assert!(!typescript_version_is_supported("Version 6.0.0")); | ||
| assert!(!typescript_version_is_supported("Version 5.9")); | ||
| assert!(!typescript_version_is_supported("garbled")); | ||
| } | ||
| #[test] | ||
| fn typescript_version_probe_rejects_bad_exit_output_and_timeout() { | ||
| let output = |code, stdout: &str, stderr: &str, timed_out| crate::process::RunOutput { | ||
| code, | ||
| stdout: stdout.to_string(), | ||
| stderr: stderr.to_string(), | ||
| timed_out, | ||
| }; | ||
| assert!( | ||
| validate_typescript_version(&output(Some(0), "Version 5.9.3\n", "", false)).is_ok() | ||
| ); | ||
| for reported in ["Version 5.8.4\n", "Version 6.0.0\n", "garbled\n"] { | ||
| let failure = | ||
| validate_typescript_version(&output(Some(0), reported, "", false)).unwrap_err(); | ||
| assert_eq!(failure.kind, JudgeFailureKind::TypeCheck); | ||
| assert!(failure.detail.contains("TypeScript 5.9.x")); | ||
| } | ||
| let failure = | ||
| validate_typescript_version(&output(Some(1), "", "version probe failed\n", false)) | ||
| .unwrap_err(); | ||
| assert_eq!(failure.kind, JudgeFailureKind::TypeCheck); | ||
| assert!(failure.detail.contains("version probe failed")); | ||
| let failure = | ||
| validate_typescript_version(&output(Some(0), "Version 5.9.3\n", "", true)).unwrap_err(); | ||
| assert_eq!(failure.kind, JudgeFailureKind::Timeout); | ||
| assert!(failure.detail.contains("timeout: 30s")); | ||
| } | ||
| #[test] | ||
| fn typescript_typecheck_runs_before_resolving_node_with_isolated_types() { | ||
| use std::{cell::RefCell, rc::Rc}; | ||
| let root = crate::process::unique_temp_path("practicode-ts-staging", "dir"); | ||
| fs::create_dir_all(&root).unwrap(); | ||
| let path = root.join("solution.ts"); | ||
| fs::write(&path, "const value: number = 'bad';\nconsole.log(value);\n").unwrap(); | ||
| let stale_type_root = root | ||
| .join("build") | ||
| .join(root.file_name().unwrap()) | ||
| .join("typescript/type-roots"); | ||
| fs::create_dir_all(&stale_type_root).unwrap(); | ||
| fs::write( | ||
| stale_type_root.join("stale.d.ts"), | ||
| "declare const stale: true;\n", | ||
| ) | ||
| .unwrap(); | ||
| let events = Rc::new(RefCell::new(Vec::new())); | ||
| let prepare_events = Rc::clone(&events); | ||
| let result = judge_path_with( | ||
| &root, | ||
| "injected-typescript-staging", | ||
| &path, | ||
| "ts", | ||
| &[IoCase { | ||
| input: String::new(), | ||
| output: "bad\n".to_string(), | ||
| }], | ||
| move |root, path, _| { | ||
| let find_events = Rc::clone(&prepare_events); | ||
| let run_events = Rc::clone(&prepare_events); | ||
| compile_typescript_with( | ||
| root, | ||
| path, | ||
| move |name| { | ||
| find_events.borrow_mut().push(format!("find:{name}")); | ||
| (name == "tsc").then(|| PathBuf::from("fake-tsc")) | ||
| }, | ||
| move |_, args| { | ||
| if args == ["--version"] { | ||
| run_events.borrow_mut().push("run:version".to_string()); | ||
| return Ok(crate::process::RunOutput { | ||
| code: Some(0), | ||
| stdout: "Version 5.9.3\n".to_string(), | ||
| stderr: String::new(), | ||
| timed_out: false, | ||
| }); | ||
| } | ||
| run_events.borrow_mut().push("run:typecheck".to_string()); | ||
| let type_root = Path::new( | ||
| &args[args.iter().position(|arg| arg == "--typeRoots").unwrap() + 1], | ||
| ); | ||
| assert!(type_root.is_dir()); | ||
| assert_eq!(fs::read_dir(type_root).unwrap().count(), 0); | ||
| Ok(crate::process::RunOutput { | ||
| code: Some(1), | ||
| stdout: "solution.ts(1,7): error TS2322: bad type\n".to_string(), | ||
| stderr: String::new(), | ||
| timed_out: false, | ||
| }) | ||
| }, | ||
| ) | ||
| }, | ||
| ); | ||
| assert_eq!(result.failure_kind, Some(JudgeFailureKind::TypeCheck)); | ||
| assert!(result.output.contains("TS2322")); | ||
| assert_eq!( | ||
| events.borrow().as_slice(), | ||
| ["find:tsc", "run:version", "run:typecheck"] | ||
| ); | ||
| let _ = fs::remove_dir_all(root); | ||
| } | ||
| } |
+34
-0
@@ -98,2 +98,22 @@ use super::*; | ||
| #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] | ||
| #[serde(rename_all = "snake_case")] | ||
| pub enum MasteryStage { | ||
| #[default] | ||
| New, | ||
| Practiced, | ||
| Retained, | ||
| Mastered, | ||
| } | ||
| #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] | ||
| pub struct LessonMastery { | ||
| #[serde(default)] | ||
| pub stage: MasteryStage, | ||
| #[serde(default)] | ||
| pub review_due_at: u64, | ||
| #[serde(default)] | ||
| pub attempts: u64, | ||
| } | ||
| #[derive(Clone, Debug, Serialize, Deserialize)] | ||
@@ -114,2 +134,6 @@ pub struct AppState { | ||
| pub current_syntax_lesson: HashMap<String, String>, | ||
| #[serde(default, skip_serializing_if = "HashMap::is_empty")] | ||
| pub syntax_mastery: HashMap<String, HashMap<String, LessonMastery>>, | ||
| #[serde(default, skip_serializing_if = "Vec::is_empty")] | ||
| pub completed_syntax_courses: Vec<String>, | ||
| } | ||
@@ -138,2 +162,11 @@ | ||
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
| pub enum JudgeFailureKind { | ||
| Compile, | ||
| TypeCheck, | ||
| Runtime, | ||
| Timeout, | ||
| Output, | ||
| } | ||
| #[derive(Clone, Debug)] | ||
@@ -144,2 +177,3 @@ pub struct JudgeResult { | ||
| pub total_cases: usize, | ||
| pub failure_kind: Option<JudgeFailureKind>, | ||
| pub output: String, | ||
@@ -146,0 +180,0 @@ } |
@@ -87,5 +87,5 @@ use super::*; | ||
| lines.push(format!(" {}:", ui_text(&lang, "input"))); | ||
| push_case_text(&mut lines, &case.input); | ||
| push_case_text(&mut lines, &case.input, &lang); | ||
| lines.push(format!(" {}:", ui_text(&lang, "output"))); | ||
| push_case_text(&mut lines, &case.output); | ||
| push_case_text(&mut lines, &case.output, &lang); | ||
| } | ||
@@ -103,6 +103,6 @@ lines.join("\n").trim_end().to_string() | ||
| fn push_case_text(lines: &mut Vec<String>, body: &str) { | ||
| fn push_case_text(lines: &mut Vec<String>, body: &str, ui_language: &str) { | ||
| let body = body.trim_end(); | ||
| if body.is_empty() { | ||
| lines.push(" <empty>".to_string()); | ||
| lines.push(format!(" {}", ui_text(ui_language, "empty_value"))); | ||
| } else { | ||
@@ -109,0 +109,0 @@ for line in body.lines() { |
+223
-3
| use super::*; | ||
| use std::{fs::OpenOptions, io::Write}; | ||
| pub fn load_state(root: &Path, bank: &[Problem]) -> Result<AppState> { | ||
| let path = root.join(STATE_PATH); | ||
| let backup = sibling_path(&path, ".bak"); | ||
| if !path.exists() { | ||
| match fs::symlink_metadata(&backup) { | ||
| Ok(metadata) if metadata.file_type().is_file() => { | ||
| let contents = | ||
| fs::read(&backup).with_context(|| format!("read {}", backup.display()))?; | ||
| replace_state_file(&path, &contents).with_context(|| { | ||
| format!("restore {} from {}", path.display(), backup.display()) | ||
| })?; | ||
| } | ||
| Ok(_) => {} | ||
| Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} | ||
| Err(error) => { | ||
| return Err(error).with_context(|| format!("inspect backup {}", backup.display())); | ||
| } | ||
| } | ||
| } | ||
| if !path.exists() { | ||
| return Ok(AppState { | ||
@@ -17,2 +35,4 @@ current_problem: bank[0].id.clone(), | ||
| current_syntax_lesson: HashMap::new(), | ||
| syntax_mastery: HashMap::new(), | ||
| completed_syntax_courses: Vec::new(), | ||
| }); | ||
@@ -33,3 +53,3 @@ } | ||
| normalize_suggested_difficulty(&state.suggested_next_difficulty); | ||
| state.syntax_progress = normalize_syntax_progress(&state.syntax_progress); | ||
| migrate_syntax_mastery(&mut state, super::learning::unix_timestamp_now()); | ||
| state.current_syntax_lesson = normalize_current_syntax_lessons(&state.current_syntax_lesson); | ||
@@ -58,2 +78,6 @@ if state.history.is_empty() { | ||
| current_syntax_lesson: &'a HashMap<String, String>, | ||
| #[serde(skip_serializing_if = "HashMap::is_empty")] | ||
| syntax_mastery: &'a HashMap<String, HashMap<String, LessonMastery>>, | ||
| #[serde(skip_serializing_if = "Vec::is_empty")] | ||
| completed_syntax_courses: &'a Vec<String>, | ||
| } | ||
@@ -63,3 +87,4 @@ | ||
| if let Some(parent) = path.parent() { | ||
| fs::create_dir_all(parent)?; | ||
| fs::create_dir_all(parent) | ||
| .with_context(|| format!("create state directory {}", parent.display()))?; | ||
| } | ||
@@ -75,7 +100,122 @@ let file = StateFile { | ||
| current_syntax_lesson: &state.current_syntax_lesson, | ||
| syntax_mastery: &state.syntax_mastery, | ||
| completed_syntax_courses: &state.completed_syntax_courses, | ||
| }; | ||
| fs::write(path, serde_json::to_string_pretty(&file)? + "\n")?; | ||
| let text = serde_json::to_string_pretty(&file) | ||
| .with_context(|| format!("serialize {}", path.display()))? | ||
| + "\n"; | ||
| replace_state_file(&path, text.as_bytes()) | ||
| } | ||
| fn sibling_path(path: &Path, suffix: &str) -> PathBuf { | ||
| let mut name = path.file_name().unwrap_or_default().to_os_string(); | ||
| name.push(suffix); | ||
| path.with_file_name(name) | ||
| } | ||
| fn replace_state_file(path: &Path, contents: &[u8]) -> Result<()> { | ||
| let temporary = sibling_path(path, ".tmp"); | ||
| let backup = sibling_path(path, ".bak"); | ||
| remove_file_if_exists(&temporary)?; | ||
| let result = (|| { | ||
| let mut file = OpenOptions::new() | ||
| .create(true) | ||
| .truncate(true) | ||
| .write(true) | ||
| .open(&temporary) | ||
| .with_context(|| format!("open {}", temporary.display()))?; | ||
| file.write_all(contents) | ||
| .with_context(|| format!("write {}", temporary.display()))?; | ||
| file.flush() | ||
| .with_context(|| format!("flush {}", temporary.display()))?; | ||
| file.sync_all() | ||
| .with_context(|| format!("sync {}", temporary.display()))?; | ||
| drop(file); | ||
| replace_state_file_inner(path, &temporary, &backup) | ||
| })(); | ||
| if result.is_err() && temporary.exists() { | ||
| let _ = fs::remove_file(&temporary); | ||
| } | ||
| result | ||
| } | ||
| fn remove_file_if_exists(path: &Path) -> Result<()> { | ||
| match fs::remove_file(path) { | ||
| Ok(()) => Ok(()), | ||
| Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()), | ||
| Err(error) => Err(error).with_context(|| format!("remove stale {}", path.display())), | ||
| } | ||
| } | ||
| #[cfg(unix)] | ||
| fn replace_state_file_inner(path: &Path, temporary: &Path, backup: &Path) -> Result<()> { | ||
| if path.exists() { | ||
| remove_file_if_exists(backup)?; | ||
| fs::copy(path, backup) | ||
| .with_context(|| format!("preserve {} as {}", path.display(), backup.display()))?; | ||
| } | ||
| fs::rename(temporary, path) | ||
| .with_context(|| format!("replace {} with {}", path.display(), temporary.display())) | ||
| } | ||
| #[cfg(windows)] | ||
| fn replace_state_file_inner(path: &Path, temporary: &Path, backup: &Path) -> Result<()> { | ||
| let had_primary = path.exists(); | ||
| replace_state_file_windows_with( | ||
| path, | ||
| temporary, | ||
| backup, | ||
| had_primary, | ||
| remove_file_if_exists, | ||
| |from, to| { | ||
| fs::rename(from, to)?; | ||
| Ok(()) | ||
| }, | ||
| ) | ||
| } | ||
| #[cfg(any(windows, test))] | ||
| fn replace_state_file_windows_with( | ||
| path: &Path, | ||
| temporary: &Path, | ||
| backup: &Path, | ||
| had_primary: bool, | ||
| mut remove_backup: impl FnMut(&Path) -> Result<()>, | ||
| mut rename: impl FnMut(&Path, &Path) -> Result<()>, | ||
| ) -> Result<()> { | ||
| if had_primary { | ||
| remove_backup(backup)?; | ||
| rename(path, backup) | ||
| .with_context(|| format!("preserve {} as {}", path.display(), backup.display()))?; | ||
| } | ||
| if let Err(replace_error) = rename(temporary, path) { | ||
| if had_primary && let Err(restore_error) = rename(backup, path) { | ||
| bail!( | ||
| "replace {}: {replace_error}; restore {}: {restore_error}", | ||
| path.display(), | ||
| backup.display() | ||
| ); | ||
| } | ||
| bail!( | ||
| "replace {} with {}: {replace_error}", | ||
| path.display(), | ||
| temporary.display() | ||
| ); | ||
| } | ||
| Ok(()) | ||
| } | ||
| #[cfg(not(any(unix, windows)))] | ||
| fn replace_state_file_inner(path: &Path, temporary: &Path, backup: &Path) -> Result<()> { | ||
| if path.exists() { | ||
| remove_file_if_exists(backup)?; | ||
| fs::copy(path, backup) | ||
| .with_context(|| format!("preserve {} as {}", path.display(), backup.display()))?; | ||
| } | ||
| fs::rename(temporary, path) | ||
| .with_context(|| format!("replace {} with {}", path.display(), temporary.display())) | ||
| } | ||
| pub fn normalize_settings(settings: &mut Settings) { | ||
@@ -117,1 +257,81 @@ settings.language = normalize_language(&settings.language); | ||
| } | ||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use std::cell::RefCell; | ||
| #[test] | ||
| fn windows_recovery_keeps_backup_when_primary_is_absent_and_replace_fails() { | ||
| let operations = RefCell::new(Vec::new()); | ||
| let error = replace_state_file_windows_with( | ||
| Path::new("problem-state.json"), | ||
| Path::new("problem-state.json.tmp"), | ||
| Path::new("problem-state.json.bak"), | ||
| false, | ||
| |path| { | ||
| operations | ||
| .borrow_mut() | ||
| .push(format!("remove {}", path.display())); | ||
| Ok(()) | ||
| }, | ||
| |from, to| { | ||
| operations | ||
| .borrow_mut() | ||
| .push(format!("rename {} {}", from.display(), to.display())); | ||
| Err(anyhow!("injected replacement failure")) | ||
| }, | ||
| ) | ||
| .unwrap_err() | ||
| .to_string(); | ||
| assert!(error.contains("injected replacement failure")); | ||
| assert_eq!( | ||
| operations.into_inner(), | ||
| ["rename problem-state.json.tmp problem-state.json"] | ||
| ); | ||
| } | ||
| #[test] | ||
| fn windows_replacement_restores_primary_after_new_file_rename_fails() { | ||
| let operations = RefCell::new(Vec::new()); | ||
| let error = replace_state_file_windows_with( | ||
| Path::new("problem-state.json"), | ||
| Path::new("problem-state.json.tmp"), | ||
| Path::new("problem-state.json.bak"), | ||
| true, | ||
| |path| { | ||
| operations | ||
| .borrow_mut() | ||
| .push(format!("remove {}", path.display())); | ||
| Ok(()) | ||
| }, | ||
| |from, to| { | ||
| operations | ||
| .borrow_mut() | ||
| .push(format!("rename {} {}", from.display(), to.display())); | ||
| if from == Path::new("problem-state.json.tmp") { | ||
| Err(anyhow!("injected replacement failure")) | ||
| } else { | ||
| Ok(()) | ||
| } | ||
| }, | ||
| ) | ||
| .unwrap_err() | ||
| .to_string(); | ||
| assert_eq!( | ||
| operations.into_inner(), | ||
| [ | ||
| "remove problem-state.json.bak", | ||
| "rename problem-state.json problem-state.json.bak", | ||
| "rename problem-state.json.tmp problem-state.json", | ||
| "rename problem-state.json.bak problem-state.json", | ||
| ] | ||
| ); | ||
| assert_eq!( | ||
| error, | ||
| "replace problem-state.json with problem-state.json.tmp: injected replacement failure" | ||
| ); | ||
| } | ||
| } |
+61
-11
| use crate::{ | ||
| ai::{ | ||
| ModelCatalog, append_problem_note, available_models, provider_status, read_problem_notes, | ||
| run_ai_generate, run_ai_lesson_prompt, run_ai_next, run_ai_prompt, | ||
| AiGenerationResult, ModelCatalog, append_problem_note, available_models, provider_status, | ||
| read_problem_notes, run_ai_generate_result, run_ai_lesson_prompt, run_ai_next, | ||
| run_ai_prompt, | ||
| }, | ||
@@ -13,5 +14,6 @@ core::{ | ||
| normalize_next_source, normalize_ui_language, parse_language_list, parse_topic_list, | ||
| parse_ui_language_list, previous_problem, problem_by_id, record_pass, record_syntax_pass, | ||
| render_problem_tui, render_syntax_lesson, save_state, set_current_syntax_lesson, | ||
| syntax_cases, syntax_language_name, syntax_progress_count, template_for, ui_text, | ||
| parse_ui_language_list, previous_problem, problem_by_id, record_pass, record_syntax_result, | ||
| render_syntax_lesson, save_state, set_current_syntax_lesson, syntax_cases, | ||
| syntax_core_progress_count, syntax_language_name, syntax_review_due_at, template_for, | ||
| ui_text, | ||
| }, | ||
@@ -53,2 +55,3 @@ text::{ | ||
| mod events; | ||
| mod learning; | ||
| mod problem_list; | ||
@@ -62,2 +65,7 @@ mod problem_view; | ||
| pub use self::editor::TextEditor; | ||
| pub use self::learning::LearningStep; | ||
| use self::learning::{ | ||
| LearningAdvance, LearningSession, LearningView, learning_step_label, learning_view_label, | ||
| progress_text, render_learning_step, unix_time_now, | ||
| }; | ||
@@ -97,2 +105,18 @@ const UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(30 * 60); | ||
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||
| enum PracticeView { | ||
| Problem, | ||
| Code, | ||
| } | ||
| fn localized_status(language: &str, status: &str) -> String { | ||
| let key = format!("status_{status}"); | ||
| let localized = ui_text(language, &key); | ||
| if localized.is_empty() { | ||
| status.to_string() | ||
| } else { | ||
| localized.to_string() | ||
| } | ||
| } | ||
| pub struct PracticodeApp { | ||
@@ -110,2 +134,3 @@ root: PathBuf, | ||
| learn_result: String, | ||
| learning_session: LearningSession, | ||
| left_scroll: u16, | ||
@@ -120,2 +145,3 @@ output_scroll: u16, | ||
| home_choice: HomeChoice, | ||
| practice_view: PracticeView, | ||
| home_area: Rect, | ||
@@ -127,3 +153,3 @@ home_learn_area: Rect, | ||
| busy_label: String, | ||
| busy_body: String, | ||
| busy_arg: String, | ||
| busy_started: Option<Instant>, | ||
@@ -134,6 +160,6 @@ busy_frame: usize, | ||
| task_rx: Option<Receiver<TaskResult>>, | ||
| generate_rx: Option<Receiver<String>>, | ||
| generate_rx: Option<Receiver<AiGenerationResult>>, | ||
| generate_bank_len: usize, | ||
| generate_started: Option<Instant>, | ||
| generate_notice: Option<String>, | ||
| generate_notice: Option<GenerationNotice>, | ||
| update_rx: Option<Receiver<UpdateCheck>>, | ||
@@ -151,4 +177,7 @@ model_rx: Option<Receiver<ModelCatalog>>, | ||
| command_area: Rect, | ||
| command_palette_area: Rect, | ||
| mouse_capture: bool, | ||
| should_quit: bool, | ||
| #[cfg(test)] | ||
| ai_spawn_disabled: bool, | ||
| } | ||
@@ -165,2 +194,16 @@ | ||
| enum GenerationNotice { | ||
| Started, | ||
| Duplicate, | ||
| Generated(usize), | ||
| Failed { | ||
| status: Option<i32>, | ||
| detail: String, | ||
| added: usize, | ||
| reload_error: Option<String>, | ||
| }, | ||
| Finished, | ||
| ReloadFailed(String), | ||
| } | ||
| impl PracticodeApp { | ||
@@ -185,2 +228,3 @@ pub fn new(root: PathBuf) -> Result<Self> { | ||
| learn_result: String::new(), | ||
| learning_session: LearningSession::inactive(), | ||
| left_scroll: 0, | ||
@@ -195,2 +239,3 @@ output_scroll: 0, | ||
| home_choice: HomeChoice::Learn, | ||
| practice_view: PracticeView::Problem, | ||
| home_area: Rect::default(), | ||
@@ -202,3 +247,3 @@ home_learn_area: Rect::default(), | ||
| busy_label: String::new(), | ||
| busy_body: String::new(), | ||
| busy_arg: String::new(), | ||
| busy_started: None, | ||
@@ -225,4 +270,7 @@ busy_frame: 0, | ||
| command_area: Rect::default(), | ||
| command_palette_area: Rect::default(), | ||
| mouse_capture: false, | ||
| should_quit: false, | ||
| #[cfg(test)] | ||
| ai_spawn_disabled: false, | ||
| }; | ||
@@ -241,3 +289,2 @@ app.load_code_editor()?; | ||
| self.start_update_check(); | ||
| self.start_model_check(); | ||
| while !self.should_quit { | ||
@@ -250,3 +297,2 @@ self.sync_mouse_capture(); | ||
| self.maybe_start_periodic_update_check(); | ||
| self.start_model_check(); | ||
| self.check_models(); | ||
@@ -352,2 +398,6 @@ if event::poll(Duration::from_millis(100))? { | ||
| pub fn learning_step_for_test(&self) -> LearningStep { | ||
| self.learning_session.step() | ||
| } | ||
| pub fn command_suggestions_for_test(&self) -> Vec<String> { | ||
@@ -354,0 +404,0 @@ self.command_suggestions() |
+603
-68
| use super::*; | ||
| use crate::core::{JudgeFailureKind, JudgeResult}; | ||
| fn judge_headline(result: &JudgeResult, language: &str) -> String { | ||
| let failure = result.failure_kind.map(|kind| { | ||
| ui_text( | ||
| language, | ||
| match kind { | ||
| JudgeFailureKind::Compile => "judge_failure_compile", | ||
| JudgeFailureKind::TypeCheck => "judge_failure_typecheck", | ||
| JudgeFailureKind::Runtime => "judge_failure_runtime", | ||
| JudgeFailureKind::Timeout => "judge_failure_timeout", | ||
| JudgeFailureKind::Output => "judge_failure_output", | ||
| }, | ||
| ) | ||
| }); | ||
| format!( | ||
| "{} {}/{}{}", | ||
| ui_text( | ||
| language, | ||
| if result.passed { | ||
| "result_pass" | ||
| } else { | ||
| "result_fail" | ||
| } | ||
| ), | ||
| result.passed_cases, | ||
| result.total_cases, | ||
| failure.map(|kind| format!(" [{kind}]")).unwrap_or_default() | ||
| ) | ||
| } | ||
| fn judge_label_key(label: &str) -> Option<&'static str> { | ||
| match label { | ||
| "Input" => Some("judge_input"), | ||
| "Expected" => Some("judge_expected"), | ||
| "Got" => Some("judge_got"), | ||
| "Stdout" => Some("judge_stdout"), | ||
| "Stderr" => Some("judge_stderr"), | ||
| "Error" => Some("judge_error"), | ||
| "Compile" => Some("judge_compile"), | ||
| _ => None, | ||
| } | ||
| } | ||
| fn localized_judge_result_output(result: &JudgeResult, language: &str) -> String { | ||
| let output = &result.output; | ||
| if output == "problem has no judge cases" { | ||
| return ui_text(language, "judge_no_cases").to_string(); | ||
| } | ||
| if let Some(tool) = output.strip_prefix("Missing runtime for TypeScript: ") | ||
| && matches!(tool, "tsc" | "node") | ||
| { | ||
| return ui_text(language, "judge_missing_typescript_tool").replace("{tool}", tool); | ||
| } | ||
| if let Some(runtime) = output.strip_prefix("Missing runtime for ") | ||
| && LANGUAGES.contains(&runtime) | ||
| { | ||
| return ui_text(language, "judge_missing_runtime").replace("{runtime}", runtime); | ||
| } | ||
| if matches!( | ||
| result.failure_kind, | ||
| Some(JudgeFailureKind::Compile | JudgeFailureKind::TypeCheck) | ||
| ) { | ||
| return output.clone(); | ||
| } | ||
| let structured = output.lines().any(|line| { | ||
| line.strip_prefix("Case ") | ||
| .and_then(|rest| rest.split_once(": ")) | ||
| .is_some_and(|(case, outcome)| { | ||
| case.parse::<usize>().is_ok() && matches!(outcome, "PASS" | "FAIL") | ||
| }) | ||
| }); | ||
| if !structured { | ||
| return output.to_string(); | ||
| } | ||
| let mut body_label = ""; | ||
| output | ||
| .lines() | ||
| .map(|line| { | ||
| if let Some((case, outcome)) = line | ||
| .strip_prefix("Case ") | ||
| .and_then(|rest| rest.split_once(": ")) | ||
| && case.parse::<usize>().is_ok() | ||
| && matches!(outcome, "PASS" | "FAIL") | ||
| { | ||
| body_label = ""; | ||
| let outcome = ui_text( | ||
| language, | ||
| if outcome == "PASS" { | ||
| "result_pass" | ||
| } else { | ||
| "result_fail" | ||
| }, | ||
| ); | ||
| return format!("{} {case}: {outcome}", ui_text(language, "judge_case")); | ||
| } | ||
| if let Some(label) = line.strip_suffix(": <empty>") | ||
| && let Some(key) = judge_label_key(label) | ||
| { | ||
| body_label = ""; | ||
| return format!( | ||
| "{}: {}", | ||
| ui_text(language, key), | ||
| ui_text(language, "empty_value") | ||
| ); | ||
| } | ||
| if let Some(key) = judge_label_key(line) { | ||
| body_label = line; | ||
| return ui_text(language, key).to_string(); | ||
| } | ||
| if matches!(body_label, "Input" | "Expected") && line == " <hidden>" { | ||
| return format!(" <{}>", ui_text(language, "judge_hidden")); | ||
| } | ||
| if body_label == "Error" && line == " timeout: 5s" { | ||
| return format!(" {}", ui_text(language, "judge_timeout_detail")); | ||
| } | ||
| if body_label == "Error" | ||
| && let Some(status) = line.strip_prefix(" process exited with status ") | ||
| && (status == "unknown" || status.parse::<i32>().is_ok()) | ||
| { | ||
| let status = if status == "unknown" { | ||
| ui_text(language, "judge_unknown_status") | ||
| } else { | ||
| status | ||
| }; | ||
| return format!( | ||
| " {}", | ||
| ui_text(language, "judge_process_exit").replace("{status}", status) | ||
| ); | ||
| } | ||
| if !line.is_empty() && !line.starts_with(" ") { | ||
| body_label = ""; | ||
| } | ||
| line.to_string() | ||
| }) | ||
| .collect::<Vec<_>>() | ||
| .join("\n") | ||
| } | ||
| fn learning_state_text( | ||
| lesson: &crate::core::SyntaxLesson, | ||
| mastery: &crate::core::LessonMastery, | ||
| now: u64, | ||
| language: &str, | ||
| ) -> String { | ||
| let stage = ui_text( | ||
| language, | ||
| match mastery.stage { | ||
| crate::core::MasteryStage::New => "mastery_new", | ||
| crate::core::MasteryStage::Practiced => "mastery_practiced", | ||
| crate::core::MasteryStage::Retained => "mastery_retained", | ||
| crate::core::MasteryStage::Mastered => "mastery_mastered", | ||
| }, | ||
| ); | ||
| let review_due_at = if lesson.track == crate::core::SyntaxTrack::Core { | ||
| syntax_review_due_at(mastery, now) | ||
| } else { | ||
| None | ||
| }; | ||
| let review = if let Some(due_at) = review_due_at { | ||
| let days = due_at.saturating_sub(now).saturating_add(86_399) / 86_400; | ||
| format!("{}: {days}", ui_text(language, "result_review_days")) | ||
| } else { | ||
| ui_text(language, "result_retry_no_review").to_string() | ||
| }; | ||
| format!("{}: {stage}\n{review}", ui_text(language, "result_mastery")) | ||
| } | ||
| impl PracticodeApp { | ||
| pub(super) fn transition_mode(&mut self, mode: AppMode) { | ||
| if self.mode == AppMode::Learn && mode != AppMode::Learn { | ||
| self.learning_session = LearningSession::inactive(); | ||
| } | ||
| self.mode = mode; | ||
| } | ||
| pub(super) fn home_text(&self) -> String { | ||
@@ -20,3 +194,5 @@ let lang = &self.state.settings.ui_language; | ||
| format!( | ||
| "Practicode\n\n{learn}\n Read a short syntax lesson and validate the exercise.\n\n{problems}\n Solve stdin/stdout coding-test problems.\n\n{help}" | ||
| "Practicode\n\n{learn}\n {}\n\n{problems}\n {}\n\n{help}", | ||
| ui_text(lang, "home_learn_description"), | ||
| ui_text(lang, "home_practice_description") | ||
| ) | ||
@@ -26,3 +202,3 @@ } | ||
| pub(super) fn action_home(&mut self) -> Result<()> { | ||
| self.mode = AppMode::Home; | ||
| self.transition_mode(AppMode::Home); | ||
| self.state.settings.start_mode = "home".to_string(); | ||
@@ -43,3 +219,4 @@ save_state(&self.root, &self.state)?; | ||
| pub(super) fn action_practice(&mut self) -> Result<()> { | ||
| self.mode = AppMode::Problems; | ||
| self.transition_mode(AppMode::Problems); | ||
| self.practice_view = PracticeView::Problem; | ||
| self.state.settings.start_mode = "problems".to_string(); | ||
@@ -51,3 +228,3 @@ save_state(&self.root, &self.state)?; | ||
| self.show_output = false; | ||
| self.focus = Focus::Code; | ||
| self.focus = Focus::Left; | ||
| Ok(()) | ||
@@ -73,6 +250,11 @@ } | ||
| if self.mode == AppMode::Home { | ||
| return self.action_practice(); | ||
| self.action_practice()?; | ||
| } | ||
| self.editing_notes = false; | ||
| self.load_code_editor()?; | ||
| if self.mode == AppMode::Learn { | ||
| self.learning_session.set_view(LearningView::Code); | ||
| self.show_current_syntax_lesson(); | ||
| return Ok(()); | ||
| } | ||
| self.settings_cursor = None; | ||
@@ -82,2 +264,3 @@ self.left_scroll = 0; | ||
| self.show_output = false; | ||
| self.practice_view = PracticeView::Code; | ||
| self.focus = Focus::Code; | ||
@@ -92,3 +275,3 @@ Ok(()) | ||
| if self.mode == AppMode::Home { | ||
| self.mode = AppMode::Problems; | ||
| self.transition_mode(AppMode::Problems); | ||
| self.state.settings.start_mode = "problems".to_string(); | ||
@@ -102,8 +285,3 @@ save_state(&self.root, &self.state)?; | ||
| } | ||
| let headline = format!( | ||
| "{} {}/{}", | ||
| if result.passed { "PASS" } else { "FAIL" }, | ||
| result.passed_cases, | ||
| result.total_cases | ||
| ); | ||
| let headline = judge_headline(&result, &self.state.settings.ui_language); | ||
| let next_step = if result.passed { | ||
@@ -114,3 +292,4 @@ ui_text(&self.state.settings.ui_language, "run_pass_next") | ||
| }; | ||
| self.write_text_output(&format!("{headline}\n{}\n\n{next_step}", result.output)); | ||
| let detail = localized_judge_result_output(&result, &self.state.settings.ui_language); | ||
| self.write_text_output(&format!("{headline}\n{detail}\n\n{next_step}")); | ||
| Ok(()) | ||
@@ -121,5 +300,5 @@ } | ||
| if self.mode == AppMode::Learn { | ||
| return self.action_next_lesson(); | ||
| return self.action_next_learning(); | ||
| } | ||
| self.mode = AppMode::Problems; | ||
| self.transition_mode(AppMode::Problems); | ||
| self.state.settings.start_mode = "problems".to_string(); | ||
@@ -136,9 +315,11 @@ save_state(&self.root, &self.state)?; | ||
| self.show_output = false; | ||
| self.focus = Focus::Code; | ||
| self.practice_view = PracticeView::Problem; | ||
| self.focus = Focus::Left; | ||
| return Ok(()); | ||
| } | ||
| if self.generate_rx.is_some() { | ||
| self.write_text_output( | ||
| "A background generation is already running. Keep solving; /next will pick up the new problem when it finishes.", | ||
| ); | ||
| self.write_text_output(ui_text( | ||
| &self.state.settings.ui_language, | ||
| "generation_already_running", | ||
| )); | ||
| return Ok(()); | ||
@@ -153,5 +334,6 @@ } | ||
| if self.task_rx.is_some() || self.generate_rx.is_some() { | ||
| let message = "Generation is already running; skipped duplicate /generate."; | ||
| self.generate_notice = Some(message.to_string()); | ||
| self.write_text_output(message); | ||
| let notice = GenerationNotice::Duplicate; | ||
| let message = self.generation_notice_text(¬ice); | ||
| self.generate_notice = Some(notice); | ||
| self.write_text_output(&message); | ||
| return; | ||
@@ -163,6 +345,9 @@ } | ||
| pub(super) fn start_background_generation(&mut self, request: String) { | ||
| self.mode = AppMode::Problems; | ||
| self.transition_mode(AppMode::Problems); | ||
| self.state.settings.start_mode = "problems".to_string(); | ||
| if save_state(&self.root, &self.state).is_err() { | ||
| self.write_text_output("Could not save practice mode before generation."); | ||
| if let Err(error) = save_state(&self.root, &self.state) { | ||
| self.write_text_output(&format!( | ||
| "{}\n{error}", | ||
| ui_text(&self.state.settings.ui_language, "generation_save_failed") | ||
| )); | ||
| return; | ||
@@ -174,7 +359,7 @@ } | ||
| thread::spawn(move || { | ||
| let _ = tx.send(run_ai_generate(&root, &state, &request)); | ||
| let _ = tx.send(run_ai_generate_result(&root, &state, &request)); | ||
| }); | ||
| self.generate_bank_len = self.bank.len(); | ||
| self.generate_started = Some(Instant::now()); | ||
| self.generate_notice = Some("Generating in background.".to_string()); | ||
| self.generate_notice = Some(GenerationNotice::Started); | ||
| self.generate_rx = Some(rx); | ||
@@ -198,6 +383,3 @@ self.settings_cursor = None; | ||
| } | ||
| self.start_busy( | ||
| "next", | ||
| ui_text(&self.state.settings.ui_language, "generating_next"), | ||
| ); | ||
| self.start_busy("next", ""); | ||
| let root = self.root.clone(); | ||
@@ -234,4 +416,5 @@ let state = self.state.clone(); | ||
| } else { | ||
| let unavailable = ui_text(&self.state.settings.ui_language, "next_unavailable"); | ||
| self.write_text_output(&format!( | ||
| "{}{}No next problem is available yet.", | ||
| "{}{}{unavailable}", | ||
| if output.is_empty() { "" } else { &output }, | ||
@@ -246,3 +429,4 @@ if output.is_empty() { "" } else { "\n\n" } | ||
| self.show_output = false; | ||
| self.focus = Focus::Code; | ||
| self.practice_view = PracticeView::Problem; | ||
| self.focus = Focus::Left; | ||
| Ok(()) | ||
@@ -253,6 +437,7 @@ } | ||
| if self.mode == AppMode::Learn { | ||
| self.learning_session = LearningSession::inactive(); | ||
| return self.action_prev_lesson(); | ||
| } | ||
| if self.mode == AppMode::Home { | ||
| self.mode = AppMode::Problems; | ||
| self.transition_mode(AppMode::Problems); | ||
| self.state.settings.start_mode = "problems".to_string(); | ||
@@ -264,3 +449,3 @@ save_state(&self.root, &self.state)?; | ||
| if self.state.current_problem == old_problem { | ||
| self.write_text_output("Already at the first known problem."); | ||
| self.write_text_output(ui_text(&self.state.settings.ui_language, "first_problem")); | ||
| } else { | ||
@@ -270,3 +455,4 @@ self.load_code_editor()?; | ||
| self.show_output = false; | ||
| self.focus = Focus::Code; | ||
| self.practice_view = PracticeView::Problem; | ||
| self.focus = Focus::Left; | ||
| } | ||
@@ -278,3 +464,3 @@ Ok(()) | ||
| if self.mode == AppMode::Home { | ||
| self.mode = AppMode::Problems; | ||
| self.transition_mode(AppMode::Problems); | ||
| self.state.settings.start_mode = "problems".to_string(); | ||
@@ -285,4 +471,6 @@ save_state(&self.root, &self.state)?; | ||
| let language = normalize_language(&self.state.settings.language); | ||
| let heading = ui_text(&self.state.settings.ui_language, "answer_for_language") | ||
| .replace("{language}", &language); | ||
| self.write_output(&format!( | ||
| "Answer for {language}:\n\n```{language}\n{}\n```", | ||
| "{heading}\n\n```{language}\n{}\n```", | ||
| answer.trim_end() | ||
@@ -296,3 +484,3 @@ )); | ||
| if !language.is_empty() && !LANGUAGES.contains(&language) { | ||
| self.write_text_output("Usage: /learn or /learn python|ts|java|rust"); | ||
| self.write_text_output(ui_text(&self.state.settings.ui_language, "learn_usage")); | ||
| return Ok(()); | ||
@@ -303,3 +491,3 @@ } | ||
| } | ||
| self.mode = AppMode::Learn; | ||
| self.transition_mode(AppMode::Learn); | ||
| self.state.settings.start_mode = "learn".to_string(); | ||
@@ -310,4 +498,6 @@ self.learn_result.clear(); | ||
| let language = self.state.settings.language.clone(); | ||
| let lesson = current_syntax_lesson(&self.state, &language); | ||
| set_current_syntax_lesson(&mut self.state, &language, lesson.id); | ||
| self.learning_session = LearningSession::start(&self.state, &language, unix_time_now()); | ||
| if let Some(lesson_id) = self.learning_session.current_lesson_id() { | ||
| set_current_syntax_lesson(&mut self.state, &language, lesson_id); | ||
| } | ||
| save_state(&self.root, &self.state)?; | ||
@@ -323,2 +513,9 @@ self.load_syntax_editor()?; | ||
| } | ||
| if !self.learning_session.can_judge() { | ||
| self.learn_result = | ||
| ui_text(&self.state.settings.ui_language, "learning_run_gate").to_string(); | ||
| self.learning_session.set_view(LearningView::Result); | ||
| self.show_current_syntax_lesson(); | ||
| return Ok(()); | ||
| } | ||
| self.save_syntax_code()?; | ||
@@ -335,14 +532,34 @@ let lesson = current_syntax_lesson(&self.state, &self.state.settings.language); | ||
| ); | ||
| if result.passed { | ||
| record_syntax_pass(&mut self.state, lesson.language, lesson.id); | ||
| save_state(&self.root, &self.state)?; | ||
| } | ||
| let assisted = self.learning_session.assisted(); | ||
| let now = unix_time_now(); | ||
| record_syntax_result( | ||
| &mut self.state, | ||
| lesson.language, | ||
| lesson.id, | ||
| result.passed, | ||
| now, | ||
| assisted, | ||
| ); | ||
| let learning_state = learning_state_text( | ||
| lesson, | ||
| &self.state.syntax_mastery[lesson.language][lesson.id], | ||
| now, | ||
| &self.state.settings.ui_language, | ||
| ); | ||
| self.learning_session.finish_judge(result.passed); | ||
| save_state(&self.root, &self.state)?; | ||
| self.output_scroll = 0; | ||
| let headline = format!( | ||
| "{} {}/{}", | ||
| if result.passed { "PASS" } else { "FAIL" }, | ||
| result.passed_cases, | ||
| result.total_cases | ||
| let headline = judge_headline(&result, &self.state.settings.ui_language); | ||
| let next_step = ui_text( | ||
| &self.state.settings.ui_language, | ||
| if result.passed { | ||
| "run_pass_next" | ||
| } else { | ||
| "run_fail_next" | ||
| }, | ||
| ); | ||
| self.learn_result = format!("{headline}\n{}", result.output.trim_end()); | ||
| self.learn_result = format!( | ||
| "{headline}\n{}\n\n{learning_state}\n\n{next_step}", | ||
| localized_judge_result_output(&result, &self.state.settings.ui_language).trim_end() | ||
| ); | ||
| self.show_current_syntax_lesson(); | ||
@@ -352,4 +569,26 @@ Ok(()) | ||
| pub(super) fn action_next_learning(&mut self) -> Result<()> { | ||
| match self.learning_session.advance() { | ||
| LearningAdvance::Step | LearningAdvance::Blocked | LearningAdvance::Complete => { | ||
| self.show_current_syntax_lesson(); | ||
| } | ||
| LearningAdvance::Item(lesson_id) => { | ||
| let language = self.state.settings.language.clone(); | ||
| set_current_syntax_lesson(&mut self.state, &language, lesson_id); | ||
| save_state(&self.root, &self.state)?; | ||
| self.load_syntax_editor()?; | ||
| self.learn_result.clear(); | ||
| self.show_current_syntax_lesson(); | ||
| } | ||
| LearningAdvance::Manual => { | ||
| self.learning_session = LearningSession::inactive(); | ||
| self.action_next_lesson()?; | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
| pub(super) fn action_next_lesson(&mut self) -> Result<()> { | ||
| self.mode = AppMode::Learn; | ||
| self.transition_mode(AppMode::Learn); | ||
| self.learning_session = LearningSession::inactive(); | ||
| let language = self.state.settings.language.clone(); | ||
@@ -367,3 +606,4 @@ next_syntax_lesson(&mut self.state, &language, 1); | ||
| pub(super) fn action_prev_lesson(&mut self) -> Result<()> { | ||
| self.mode = AppMode::Learn; | ||
| self.transition_mode(AppMode::Learn); | ||
| self.learning_session = LearningSession::inactive(); | ||
| let language = self.state.settings.language.clone(); | ||
@@ -382,3 +622,7 @@ next_syntax_lesson(&mut self.state, &language, -1); | ||
| let lesson = current_syntax_lesson(&self.state, &self.state.settings.language); | ||
| self.output = render_syntax_lesson(lesson, &self.state); | ||
| self.output = if self.learning_session.is_guided() { | ||
| render_learning_step(Some(lesson), &self.state, self.learning_session.step()) | ||
| } else { | ||
| render_syntax_lesson(lesson, &self.state) | ||
| }; | ||
| self.left_scroll = 0; | ||
@@ -389,5 +633,36 @@ self.output_is_markdown = true; | ||
| self.list_cursor = None; | ||
| self.focus = Focus::Code; | ||
| self.focus = match self.learning_session.view() { | ||
| LearningView::Lesson => Focus::Left, | ||
| LearningView::Code => Focus::Code, | ||
| LearningView::Result => Focus::Output, | ||
| }; | ||
| } | ||
| pub(super) fn action_lesson(&mut self) { | ||
| let lesson = current_syntax_lesson(&self.state, &self.state.settings.language); | ||
| let output = render_syntax_lesson(lesson, &self.state); | ||
| self.write_output(&output); | ||
| } | ||
| pub(super) fn action_progress(&mut self) { | ||
| let output = progress_text(&self.state, unix_time_now()); | ||
| self.write_text_output(&output); | ||
| } | ||
| pub(super) fn cycle_learning_view(&mut self) { | ||
| if self.mode == AppMode::Problems { | ||
| self.practice_view = match self.practice_view { | ||
| PracticeView::Problem => PracticeView::Code, | ||
| PracticeView::Code => PracticeView::Problem, | ||
| }; | ||
| self.focus = match self.practice_view { | ||
| PracticeView::Problem => Focus::Left, | ||
| PracticeView::Code => Focus::Code, | ||
| }; | ||
| } else if self.mode == AppMode::Learn { | ||
| self.learning_session.cycle_view(); | ||
| self.show_current_syntax_lesson(); | ||
| } | ||
| } | ||
| pub(super) fn action_cycle_language(&mut self) -> Result<()> { | ||
@@ -419,14 +694,13 @@ let current = LANGUAGES | ||
| self.state.settings.language = language.to_string(); | ||
| if self.mode == AppMode::Learn { | ||
| return self.action_learn(language); | ||
| } | ||
| save_state(&self.root, &self.state)?; | ||
| self.load_code_editor()?; | ||
| self.settings_cursor = None; | ||
| if self.mode == AppMode::Learn { | ||
| self.learn_result.clear(); | ||
| self.left_scroll = 0; | ||
| self.output_scroll = 0; | ||
| self.show_current_syntax_lesson(); | ||
| } else { | ||
| self.show_output = false; | ||
| } | ||
| self.focus = Focus::Code; | ||
| self.show_output = false; | ||
| self.focus = match self.practice_view { | ||
| PracticeView::Problem => Focus::Left, | ||
| PracticeView::Code => Focus::Code, | ||
| }; | ||
| Ok(()) | ||
@@ -442,3 +716,6 @@ } | ||
| } else { | ||
| self.write_text_output(&format!("UI language: {}", self.state.settings.ui_language)); | ||
| self.write_text_output( | ||
| &ui_text(&self.state.settings.ui_language, "ui_language_set") | ||
| .replace("{language}", &self.state.settings.ui_language), | ||
| ); | ||
| } | ||
@@ -451,3 +728,5 @@ Ok(()) | ||
| save_state(&self.root, &self.state)?; | ||
| self.write_text_output(&format!("Theme: {theme}")); | ||
| self.write_text_output( | ||
| &ui_text(&self.state.settings.ui_language, "theme_set").replace("{theme}", theme), | ||
| ); | ||
| Ok(()) | ||
@@ -459,3 +738,6 @@ } | ||
| if !DIFFICULTIES.contains(&difficulty.as_str()) { | ||
| self.write_text_output("Difficulty: auto, easy, medium, or hard."); | ||
| self.write_text_output(ui_text( | ||
| &self.state.settings.ui_language, | ||
| "difficulty_options", | ||
| )); | ||
| return Ok(()); | ||
@@ -621,1 +903,254 @@ } | ||
| } | ||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| fn synthetic_lesson(track: crate::core::SyntaxTrack) -> crate::core::SyntaxLesson { | ||
| crate::core::SyntaxLesson { | ||
| id: "synthetic", | ||
| aliases: &[], | ||
| language: "rust", | ||
| track, | ||
| kind: crate::core::SyntaxKind::Lesson, | ||
| level: "basic", | ||
| title: "synthetic", | ||
| body: "synthetic", | ||
| example: "fn main() {}", | ||
| exercise: crate::core::SyntaxExercise { | ||
| prompt: "synthetic", | ||
| starter: "fn main() {}", | ||
| cases: &[], | ||
| }, | ||
| refs: &[], | ||
| } | ||
| } | ||
| fn localize(output: &str, kind: Option<JudgeFailureKind>) -> String { | ||
| localized_judge_result_output( | ||
| &JudgeResult { | ||
| passed: false, | ||
| passed_cases: 0, | ||
| total_cases: 1, | ||
| failure_kind: kind, | ||
| output: output.to_string(), | ||
| }, | ||
| "ko", | ||
| ) | ||
| } | ||
| #[test] | ||
| fn judge_localization_preserves_tool_owned_compiler_diagnostics() { | ||
| let raw = "Case 1: compiler-owned diagnostic\nTS2322: raw detail"; | ||
| assert_eq!(localize(raw, Some(JudgeFailureKind::TypeCheck)), raw); | ||
| assert_eq!( | ||
| localize( | ||
| "Case 1: FAIL\n\nGot\n value", | ||
| Some(JudgeFailureKind::Output) | ||
| ), | ||
| "케이스 1: 실패\n\n실제 출력\n value" | ||
| ); | ||
| let structured_looking = "Case 1: FAIL\nTS2322: compiler-owned detail"; | ||
| let result = JudgeResult { | ||
| passed: false, | ||
| passed_cases: 0, | ||
| total_cases: 1, | ||
| failure_kind: Some(JudgeFailureKind::TypeCheck), | ||
| output: structured_looking.to_string(), | ||
| }; | ||
| assert_eq!( | ||
| localized_judge_result_output(&result, "ko"), | ||
| structured_looking | ||
| ); | ||
| let missing_tool = "Missing runtime for TypeScript: tsc 5.9"; | ||
| assert_eq!( | ||
| localize(missing_tool, Some(JudgeFailureKind::TypeCheck)), | ||
| missing_tool | ||
| ); | ||
| for (tool, kind) in [ | ||
| ("tsc", JudgeFailureKind::TypeCheck), | ||
| ("node", JudgeFailureKind::Runtime), | ||
| ] { | ||
| let raw = format!("Missing runtime for TypeScript: {tool}"); | ||
| assert_eq!( | ||
| localize(&raw, Some(kind)), | ||
| format!("TypeScript 런타임이 없습니다: {tool}") | ||
| ); | ||
| } | ||
| } | ||
| #[test] | ||
| fn judge_localization_preserves_indented_program_output_byte_for_byte() { | ||
| let output = "Case 1: PASS\n\nGot\n <empty>\n\nStdout\n <hidden>"; | ||
| assert_eq!( | ||
| localize(output, None), | ||
| "케이스 1: 통과\n\n실제 출력\n <empty>\n\n표준 출력\n <hidden>" | ||
| ); | ||
| let non_numeric_case = "Case literal: PASS\n\nGot\n value"; | ||
| assert_eq!(localize(non_numeric_case, None), non_numeric_case); | ||
| } | ||
| #[test] | ||
| fn judge_localization_uses_provenance_for_empty_and_hidden_markers() { | ||
| let output = "Case 1: FAIL\n\nGot: <empty>\n\nInput\n <hidden>\n\nExpected\n <hidden>"; | ||
| assert_eq!( | ||
| localize(output, Some(JudgeFailureKind::Output)), | ||
| "케이스 1: 실패\n\n실제 출력: <비어 있음>\n\n입력\n <숨김>\n\n기대 출력\n <숨김>" | ||
| ); | ||
| } | ||
| #[test] | ||
| fn judge_localization_translates_only_known_error_provenance() { | ||
| let output = "Case 1: FAIL\n\nError\n process exited with status 7\n\nGot\n literal"; | ||
| assert_eq!( | ||
| localize(output, Some(JudgeFailureKind::Runtime)), | ||
| "케이스 1: 실패\n\n오류\n 프로세스 종료 상태 7\n\n실제 출력\n literal" | ||
| ); | ||
| let unknown = "Case 1: FAIL\n\nError\n No such file or directory"; | ||
| assert_eq!( | ||
| localize(unknown, Some(JudgeFailureKind::Runtime)), | ||
| "케이스 1: 실패\n\n오류\n No such file or directory" | ||
| ); | ||
| let malformed = "Case 1: FAIL\n\nError\n process exited with status 7 extra"; | ||
| assert_eq!( | ||
| localize(malformed, Some(JudgeFailureKind::Runtime)), | ||
| "케이스 1: 실패\n\n오류\n process exited with status 7 extra" | ||
| ); | ||
| let unknown_status = "Case 1: FAIL\n\nError\n process exited with status unknown"; | ||
| assert_eq!( | ||
| localize(unknown_status, Some(JudgeFailureKind::Runtime)), | ||
| "케이스 1: 실패\n\n오류\n 프로세스 종료 상태 알 수 없음" | ||
| ); | ||
| } | ||
| #[test] | ||
| fn judge_localization_handles_no_cases_and_missing_runtime_app_prose() { | ||
| assert_eq!( | ||
| localize("problem has no judge cases", None), | ||
| "채점 케이스가 없습니다." | ||
| ); | ||
| assert_eq!( | ||
| localize("Missing runtime for python", None), | ||
| "python 런타임이 없습니다." | ||
| ); | ||
| for malformed in [ | ||
| "Missing runtime for ruby", | ||
| "Missing runtime for python\nraw detail", | ||
| ] { | ||
| assert_eq!(localize(malformed, None), malformed); | ||
| } | ||
| } | ||
| #[test] | ||
| fn assisted_new_capstone_result_has_retry_instead_of_fake_review() { | ||
| let mastery = crate::core::LessonMastery { | ||
| stage: crate::core::MasteryStage::New, | ||
| review_due_at: 0, | ||
| attempts: 1, | ||
| }; | ||
| let lesson = synthetic_lesson(crate::core::SyntaxTrack::Core); | ||
| let text = learning_state_text(&lesson, &mastery, 1_000, "en"); | ||
| assert!(text.contains("Mastery: New"), "{text}"); | ||
| assert!( | ||
| text.contains("Retry this exercise; no review is scheduled."), | ||
| "{text}" | ||
| ); | ||
| assert!(!text.contains("Next review (days)"), "{text}"); | ||
| } | ||
| #[test] | ||
| fn learning_state_schedules_core_pass_but_not_lab_pass() { | ||
| let now = 1_000; | ||
| let passed = crate::core::LessonMastery { | ||
| stage: crate::core::MasteryStage::Practiced, | ||
| review_due_at: now + 86_400, | ||
| attempts: 1, | ||
| }; | ||
| let lab = synthetic_lesson(crate::core::SyntaxTrack::Lab); | ||
| let core = synthetic_lesson(crate::core::SyntaxTrack::Core); | ||
| let lab_text = learning_state_text(&lab, &passed, now, "en"); | ||
| let core_text = learning_state_text(&core, &passed, now, "en"); | ||
| assert!( | ||
| lab_text.contains("Retry this exercise; no review is scheduled."), | ||
| "{lab_text}" | ||
| ); | ||
| assert!(!lab_text.contains("Next review (days)"), "{lab_text}"); | ||
| assert!(core_text.contains("Next review (days): 1"), "{core_text}"); | ||
| } | ||
| fn localized_app(name: &str, language: &str) -> PracticodeApp { | ||
| let root = crate::process::unique_temp_path(name, "dir"); | ||
| std::fs::create_dir_all(&root).unwrap(); | ||
| let mut app = PracticodeApp::new(root).unwrap(); | ||
| app.state.settings.ui_language = language.to_string(); | ||
| app | ||
| } | ||
| #[test] | ||
| fn first_problem_feedback_uses_the_selected_locale() { | ||
| let mut app = localized_app("practicode-first-problem-locale", "ko"); | ||
| app.action_previous().unwrap(); | ||
| assert_eq!(app.output, ui_text("ko", "first_problem")); | ||
| } | ||
| #[test] | ||
| fn answer_heading_uses_the_selected_locale() { | ||
| let mut app = localized_app("practicode-answer-locale", "ja"); | ||
| app.action_give_up().unwrap(); | ||
| let heading = ui_text("ja", "answer_for_language").replace("{language}", "python"); | ||
| assert!(app.output.starts_with(&heading), "{}", app.output); | ||
| assert!(!app.output.starts_with("Answer for"), "{}", app.output); | ||
| } | ||
| #[test] | ||
| fn command_feedback_uses_the_selected_locale() { | ||
| let mut app = localized_app("practicode-command-feedback-locale", "es"); | ||
| app.action_learn("ruby").unwrap(); | ||
| assert_eq!(app.output, ui_text("es", "learn_usage")); | ||
| app.set_theme("light").unwrap(); | ||
| assert_eq!( | ||
| app.output, | ||
| ui_text("es", "theme_set").replace("{theme}", "light") | ||
| ); | ||
| app.set_difficulty("impossible").unwrap(); | ||
| assert_eq!(app.output, ui_text("es", "difficulty_options")); | ||
| app.set_ui_language("zh").unwrap(); | ||
| assert_eq!( | ||
| app.output, | ||
| ui_text("zh", "ui_language_set").replace("{language}", "zh") | ||
| ); | ||
| } | ||
| #[test] | ||
| fn unavailable_next_problem_uses_the_selected_locale() { | ||
| let mut app = localized_app("practicode-next-unavailable-locale", "zh"); | ||
| save_state(&app.root, &app.state).unwrap(); | ||
| let current = app.state.current_problem.clone(); | ||
| app.finish_next_problem(String::new(), current, false) | ||
| .unwrap(); | ||
| assert_eq!(app.output, ui_text("zh", "next_unavailable")); | ||
| } | ||
| } |
+105
-17
@@ -25,7 +25,2 @@ use super::*; | ||
| } | ||
| if value.starts_with("vim") { | ||
| self.list_cursor = None; | ||
| self.write_text_output("The code editor is already open on the right."); | ||
| return Ok(()); | ||
| } | ||
| let (command, arg) = value.split_once(char::is_whitespace).unwrap_or((value, "")); | ||
@@ -38,6 +33,8 @@ let arg = arg.trim(); | ||
| "run" | "r" => self.action_run()?, | ||
| "code" | "edit" | "e" => self.action_edit()?, | ||
| "code" | "edit" | "e" | "vim" => self.action_edit()?, | ||
| "home" => self.action_home()?, | ||
| "doctor" => self.action_doctor(), | ||
| "learn" => self.action_learn(arg)?, | ||
| "lesson" => self.action_lesson(), | ||
| "progress" => self.action_progress(), | ||
| "next" | "n" => self.action_next(arg)?, | ||
@@ -83,9 +80,16 @@ "generate" | "gen" | "new" => self.action_generate(arg), | ||
| save_state(&self.root, &self.state)?; | ||
| self.write_text_output("AI next command saved."); | ||
| self.write_text_output(ui_text( | ||
| &self.state.settings.ui_language, | ||
| "ai_next_command_saved", | ||
| )); | ||
| } | ||
| "provider" | "ai-provider" if arg.is_empty() => { | ||
| self.write_text_output(&format!( | ||
| "AI provider: {}\n{}", | ||
| "{}: {}\n{}", | ||
| ui_text(&self.state.settings.ui_language, "settings_ai_provider"), | ||
| self.state.settings.ai_provider, | ||
| provider_status(&self.state.settings.ai_provider) | ||
| provider_status( | ||
| &self.state.settings.ai_provider, | ||
| &self.state.settings.ui_language | ||
| ) | ||
| )); | ||
@@ -106,5 +110,9 @@ } | ||
| self.write_text_output(&format!( | ||
| "AI provider: {}\n{}", | ||
| "{}: {}\n{}", | ||
| ui_text(&self.state.settings.ui_language, "settings_ai_provider"), | ||
| self.state.settings.ai_provider, | ||
| provider_status(&self.state.settings.ai_provider) | ||
| provider_status( | ||
| &self.state.settings.ai_provider, | ||
| &self.state.settings.ui_language | ||
| ) | ||
| )); | ||
@@ -132,7 +140,14 @@ } | ||
| "effort" | "reasoning" | "ai-effort" => self.set_ai_effort(arg)?, | ||
| "hint" if arg.is_empty() => { | ||
| let prompt = if self.mode == AppMode::Learn { | ||
| "Give one concise hint about the current lesson exercise without giving the full solution." | ||
| } else { | ||
| "Give one concise hint for the current problem." | ||
| "hint" | "ask" if arg.is_empty() => { | ||
| let prompt = match (command, self.mode) { | ||
| ("hint", AppMode::Learn) => { | ||
| "Give one concise hint about the current lesson exercise without giving the full solution." | ||
| } | ||
| ("ask", AppMode::Learn) => { | ||
| "Explain the current learning step with one guiding question and no full solution." | ||
| } | ||
| ("hint", _) => "Give one concise hint for the current problem.", | ||
| _ => { | ||
| "Explain the current problem with one guiding question and no full solution." | ||
| } | ||
| }; | ||
@@ -147,3 +162,6 @@ self.start_ai_prompt(prompt)? | ||
| "exit" | "quit" | "q" => self.should_quit = true, | ||
| _ => self.write_text_output(&format!("Unknown command: {value}\nTry /help.")), | ||
| _ => self.write_text_output( | ||
| &ui_text(&self.state.settings.ui_language, "unknown_command") | ||
| .replace("{command}", value), | ||
| ), | ||
| } | ||
@@ -153,1 +171,71 @@ Ok(()) | ||
| } | ||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| fn app(language: &str) -> PracticodeApp { | ||
| let root = crate::process::unique_temp_path("practicode-command-handler-locale", "dir"); | ||
| std::fs::create_dir_all(&root).unwrap(); | ||
| let mut app = PracticodeApp::new(root).unwrap(); | ||
| app.state.settings.ui_language = language.to_string(); | ||
| app | ||
| } | ||
| #[test] | ||
| fn command_handler_feedback_uses_the_selected_locale() { | ||
| let mut app = app("ja"); | ||
| app.handle_command("vim").unwrap(); | ||
| assert_eq!(app.mode, AppMode::Problems); | ||
| assert_eq!(app.practice_view, PracticeView::Code); | ||
| assert_eq!(app.focus, Focus::Code); | ||
| assert!(!app.show_output); | ||
| app.handle_command("ai-next-command true").unwrap(); | ||
| assert_eq!(app.output, ui_text("ja", "ai_next_command_saved")); | ||
| app.handle_command("unknown-example").unwrap(); | ||
| assert_eq!( | ||
| app.output, | ||
| ui_text("ja", "unknown_command").replace("{command}", "unknown-example") | ||
| ); | ||
| app.handle_command("provider").unwrap(); | ||
| assert!( | ||
| app.output | ||
| .starts_with(&format!("{}: ", ui_text("ja", "settings_ai_provider"))), | ||
| "{}", | ||
| app.output | ||
| ); | ||
| } | ||
| #[test] | ||
| fn vim_alias_matches_only_the_exact_command_from_every_view() { | ||
| for setup in [None, Some("learn"), Some("help")] { | ||
| let mut app = app("en"); | ||
| if let Some(command) = setup { | ||
| app.handle_command(command).unwrap(); | ||
| } | ||
| app.handle_command("vim").unwrap(); | ||
| if setup == Some("learn") { | ||
| assert_eq!(app.mode, AppMode::Learn); | ||
| assert_eq!(app.learning_session.view(), LearningView::Code); | ||
| } else { | ||
| assert_eq!(app.mode, AppMode::Problems, "setup: {setup:?}"); | ||
| assert_eq!(app.practice_view, PracticeView::Code, "setup: {setup:?}"); | ||
| } | ||
| assert_eq!(app.focus, Focus::Code, "setup: {setup:?}"); | ||
| assert!(!app.show_output, "setup: {setup:?}"); | ||
| } | ||
| let mut app = app("en"); | ||
| app.handle_command("vimbad").unwrap(); | ||
| assert_eq!( | ||
| app.output, | ||
| ui_text("en", "unknown_command").replace("{command}", "vimbad") | ||
| ); | ||
| } | ||
| } |
@@ -60,3 +60,5 @@ use super::*; | ||
| match self.mode { | ||
| AppMode::Home => &["learn", "problems", "doctor", "profile", "help", "quit"], | ||
| AppMode::Home => &[ | ||
| "learn", "progress", "problems", "doctor", "profile", "help", "quit", | ||
| ], | ||
| AppMode::Problems => &[ | ||
@@ -71,2 +73,3 @@ "run", | ||
| "profile", | ||
| "progress", | ||
| "doctor", | ||
@@ -76,3 +79,4 @@ "home", | ||
| AppMode::Learn => &[ | ||
| "run", "next", "back", "ask ", "learn", "problems", "profile", "doctor", "home", | ||
| "run", "next", "back", "lesson", "progress", "hint ", "ask ", "learn", "problems", | ||
| "profile", "doctor", "home", | ||
| ], | ||
@@ -79,0 +83,0 @@ } |
+12
-0
@@ -77,2 +77,14 @@ #[derive(Clone, Copy)] | ||
| CommandHint { | ||
| insert: "lesson", | ||
| display: "/lesson", | ||
| desc_key: "cmd_lesson", | ||
| keep_open: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "progress", | ||
| display: "/progress", | ||
| desc_key: "cmd_progress", | ||
| keep_open: false, | ||
| }, | ||
| CommandHint { | ||
| insert: "hint ", | ||
@@ -79,0 +91,0 @@ display: "/hint <request>", |
+324
-48
| use super::*; | ||
| use crate::process::which; | ||
| use crate::{ | ||
| core::typescript_version_is_supported, | ||
| process::{run_capture, which}, | ||
| }; | ||
| use std::process::Command; | ||
@@ -16,5 +19,17 @@ | ||
| detail: String, | ||
| install: Option<&'static str>, | ||
| install: Option<InstallHelp>, | ||
| } | ||
| #[derive(Clone, Copy)] | ||
| enum InstallHelp { | ||
| Python, | ||
| Node, | ||
| NodeAndTypeScript, | ||
| TypeScript, | ||
| Java, | ||
| Rust, | ||
| Codex, | ||
| Claude, | ||
| } | ||
| impl PracticodeApp { | ||
@@ -64,3 +79,3 @@ pub(super) fn action_doctor(&mut self) { | ||
| for check in runtime_checks(&mut has_command, &mut command_version) { | ||
| for check in runtime_checks(lang, &mut has_command, &mut command_version) { | ||
| push_check(lang, &mut lines, check); | ||
@@ -71,3 +86,7 @@ } | ||
| lines.push(ui_text(lang, "doctor_optional_ai").to_string()); | ||
| push_check(lang, &mut lines, ai_check(ai_provider, &mut has_command)); | ||
| push_check( | ||
| lang, | ||
| &mut lines, | ||
| ai_check(lang, ai_provider, &mut has_command), | ||
| ); | ||
@@ -77,3 +96,7 @@ lines.join("\n") | ||
| fn runtime_checks<F, V>(has_command: &mut F, command_version: &mut V) -> Vec<DoctorCheck> | ||
| fn runtime_checks<F, V>( | ||
| lang: &str, | ||
| has_command: &mut F, | ||
| command_version: &mut V, | ||
| ) -> Vec<DoctorCheck> | ||
| where | ||
@@ -84,10 +107,10 @@ F: FnMut(&str) -> bool, | ||
| vec![ | ||
| python_check(has_command), | ||
| node_check(has_command, command_version), | ||
| java_check(has_command), | ||
| rust_check(has_command), | ||
| python_check(lang, has_command), | ||
| node_check(lang, has_command, command_version), | ||
| java_check(lang, has_command), | ||
| rust_check(lang, has_command), | ||
| ] | ||
| } | ||
| fn python_check<F>(has_command: &mut F) -> DoctorCheck | ||
| fn python_check<F>(lang: &str, has_command: &mut F) -> DoctorCheck | ||
| where | ||
@@ -110,8 +133,14 @@ F: FnMut(&str) -> bool, | ||
| name: "Python", | ||
| detail: command.unwrap_or("python3 or python").to_string(), | ||
| install: command.is_none().then_some(PYTHON_INSTALL), | ||
| detail: command.map(str::to_string).unwrap_or_else(|| { | ||
| format!( | ||
| "{}; {}", | ||
| missing_tool(lang, "python3"), | ||
| missing_tool(lang, "python") | ||
| ) | ||
| }), | ||
| install: command.is_none().then_some(InstallHelp::Python), | ||
| } | ||
| } | ||
| fn node_check<F, V>(has_command: &mut F, command_version: &mut V) -> DoctorCheck | ||
| fn node_check<F, V>(lang: &str, has_command: &mut F, command_version: &mut V) -> DoctorCheck | ||
| where | ||
@@ -121,11 +150,65 @@ F: FnMut(&str) -> bool, | ||
| { | ||
| if !has_command("node") { | ||
| let has_node = has_command("node"); | ||
| let has_tsc = has_command("tsc"); | ||
| if !has_tsc { | ||
| let mut detail = missing_tool(lang, "tsc"); | ||
| if !has_node { | ||
| detail.push_str(&format!("; {}", ui_text(lang, "doctor_node_required"))); | ||
| } | ||
| return DoctorCheck { | ||
| status: DoctorStatus::Missing, | ||
| name: "TypeScript", | ||
| detail: "node >= 22.6.0".to_string(), | ||
| install: Some(NODE_INSTALL), | ||
| detail, | ||
| install: Some(if has_node { | ||
| InstallHelp::TypeScript | ||
| } else { | ||
| InstallHelp::NodeAndTypeScript | ||
| }), | ||
| }; | ||
| } | ||
| let version = command_version("node", &["--version"]).unwrap_or_else(|| "unknown".to_string()); | ||
| let Some(tsc_version) = command_version("tsc", &["--version"]) else { | ||
| let mut detail = ui_text(lang, "doctor_tsc_unreadable").to_string(); | ||
| if !has_node { | ||
| detail.push_str(&format!("; {}", ui_text(lang, "doctor_node_required"))); | ||
| } | ||
| return DoctorCheck { | ||
| status: DoctorStatus::Update, | ||
| name: "TypeScript", | ||
| detail, | ||
| install: Some(if has_node { | ||
| InstallHelp::TypeScript | ||
| } else { | ||
| InstallHelp::NodeAndTypeScript | ||
| }), | ||
| }; | ||
| }; | ||
| if !typescript_version_is_supported(&tsc_version) { | ||
| let mut detail = ui_text(lang, "doctor_tsc_required").replace("{version}", &tsc_version); | ||
| if !has_node { | ||
| detail.push_str(&format!("; {}", ui_text(lang, "doctor_node_required"))); | ||
| } | ||
| return DoctorCheck { | ||
| status: DoctorStatus::Update, | ||
| name: "TypeScript", | ||
| detail, | ||
| install: Some(if has_node { | ||
| InstallHelp::TypeScript | ||
| } else { | ||
| InstallHelp::NodeAndTypeScript | ||
| }), | ||
| }; | ||
| } | ||
| if !has_node { | ||
| return DoctorCheck { | ||
| status: DoctorStatus::Missing, | ||
| name: "TypeScript", | ||
| detail: format!( | ||
| "{}; tsc {tsc_version}", | ||
| ui_text(lang, "doctor_node_required") | ||
| ), | ||
| install: Some(InstallHelp::Node), | ||
| }; | ||
| } | ||
| let version = command_version("node", &["--version"]) | ||
| .unwrap_or_else(|| ui_text(lang, "doctor_unknown_version").to_string()); | ||
| let ok = node_supports_strip_types(&version); | ||
@@ -140,11 +223,14 @@ DoctorCheck { | ||
| detail: if ok { | ||
| format!("node {version}") | ||
| format!("node {version} + tsc {tsc_version}") | ||
| } else { | ||
| format!("Node.js >= 22.6.0 ({version})") | ||
| format!( | ||
| "{} ({version}); tsc {tsc_version}", | ||
| ui_text(lang, "doctor_node_required") | ||
| ) | ||
| }, | ||
| install: (!ok).then_some(NODE_INSTALL), | ||
| install: (!ok).then_some(InstallHelp::Node), | ||
| } | ||
| } | ||
| fn java_check<F>(has_command: &mut F) -> DoctorCheck | ||
| fn java_check<F>(lang: &str, has_command: &mut F) -> DoctorCheck | ||
| where | ||
@@ -155,7 +241,11 @@ F: FnMut(&str) -> bool, | ||
| let has_java = has_command("java"); | ||
| let missing = match (has_javac, has_java) { | ||
| (true, true) => "javac + java", | ||
| (false, true) => "missing javac", | ||
| (true, false) => "missing java", | ||
| (false, false) => "missing javac and java", | ||
| let detail = match (has_javac, has_java) { | ||
| (true, true) => "javac + java".to_string(), | ||
| (false, true) => missing_tool(lang, "javac"), | ||
| (true, false) => missing_tool(lang, "java"), | ||
| (false, false) => format!( | ||
| "{}; {}", | ||
| missing_tool(lang, "javac"), | ||
| missing_tool(lang, "java") | ||
| ), | ||
| }; | ||
@@ -169,8 +259,8 @@ DoctorCheck { | ||
| name: "Java", | ||
| detail: missing.to_string(), | ||
| install: (!(has_javac && has_java)).then_some(JAVA_INSTALL), | ||
| detail, | ||
| install: (!(has_javac && has_java)).then_some(InstallHelp::Java), | ||
| } | ||
| } | ||
| fn rust_check<F>(has_command: &mut F) -> DoctorCheck | ||
| fn rust_check<F>(lang: &str, has_command: &mut F) -> DoctorCheck | ||
| where | ||
@@ -187,8 +277,12 @@ F: FnMut(&str) -> bool, | ||
| name: "Rust", | ||
| detail: if ok { "rustc" } else { "missing rustc" }.to_string(), | ||
| install: (!ok).then_some(RUST_INSTALL), | ||
| detail: if ok { | ||
| "rustc".to_string() | ||
| } else { | ||
| missing_tool(lang, "rustc") | ||
| }, | ||
| install: (!ok).then_some(InstallHelp::Rust), | ||
| } | ||
| } | ||
| fn ai_check<F>(ai_provider: &str, has_command: &mut F) -> DoctorCheck | ||
| fn ai_check<F>(lang: &str, ai_provider: &str, has_command: &mut F) -> DoctorCheck | ||
| where | ||
@@ -218,8 +312,8 @@ F: FnMut(&str) -> bool, | ||
| } else { | ||
| format!("missing {command}") | ||
| missing_tool(lang, command) | ||
| }, | ||
| install: (!ok).then_some(if provider == "claude" { | ||
| CLAUDE_INSTALL | ||
| InstallHelp::Claude | ||
| } else { | ||
| CODEX_INSTALL | ||
| InstallHelp::Codex | ||
| }), | ||
@@ -229,2 +323,6 @@ } | ||
| fn missing_tool(lang: &str, tool: &str) -> String { | ||
| ui_text(lang, "doctor_missing_tool").replace("{tool}", tool) | ||
| } | ||
| fn push_check(lang: &str, lines: &mut Vec<String>, check: DoctorCheck) { | ||
@@ -239,6 +337,32 @@ lines.push(format!( | ||
| lines.push(format!(" {}:", ui_text(lang, "doctor_install"))); | ||
| lines.extend(install.lines().map(|line| format!(" {line}"))); | ||
| lines.extend( | ||
| install_lines(lang, install) | ||
| .into_iter() | ||
| .map(|line| format!(" {line}")), | ||
| ); | ||
| } | ||
| } | ||
| fn install_lines(lang: &str, install: InstallHelp) -> Vec<String> { | ||
| match install { | ||
| InstallHelp::Python => PYTHON_INSTALL.lines().map(str::to_string).collect(), | ||
| InstallHelp::Node | InstallHelp::NodeAndTypeScript => { | ||
| let mut lines = NODE_INSTALL.lines().map(str::to_string).collect::<Vec<_>>(); | ||
| lines.push( | ||
| ui_text(lang, "doctor_node_install_linux") | ||
| .replace("{url}", "https://nodejs.org/en/download"), | ||
| ); | ||
| if matches!(install, InstallHelp::NodeAndTypeScript) { | ||
| lines.push(TYPESCRIPT_INSTALL.to_string()); | ||
| } | ||
| lines | ||
| } | ||
| InstallHelp::TypeScript => vec![TYPESCRIPT_INSTALL.to_string()], | ||
| InstallHelp::Java => JAVA_INSTALL.lines().map(str::to_string).collect(), | ||
| InstallHelp::Rust => RUST_INSTALL.lines().map(str::to_string).collect(), | ||
| InstallHelp::Codex => vec![ui_text(lang, "doctor_codex_install").to_string()], | ||
| InstallHelp::Claude => vec![ui_text(lang, "doctor_claude_install").to_string()], | ||
| } | ||
| } | ||
| fn status_label(lang: &str, status: DoctorStatus) -> &'static str { | ||
@@ -253,9 +377,10 @@ match status { | ||
| fn command_version(program: &str, args: &[&str]) -> Option<String> { | ||
| Command::new(program) | ||
| .args(args) | ||
| .output() | ||
| .ok() | ||
| .filter(|output| output.status.success()) | ||
| .map(|output| String::from_utf8_lossy(&output.stdout).trim().to_string()) | ||
| .filter(|output| !output.is_empty()) | ||
| let mut command = Command::new(program); | ||
| command.args(args); | ||
| let output = run_capture(&mut command, "", Duration::from_secs(5)).ok()?; | ||
| if output.timed_out || output.code != Some(0) { | ||
| return None; | ||
| } | ||
| let output = output.stdout.trim().to_string(); | ||
| (!output.is_empty()).then_some(output) | ||
| } | ||
@@ -281,7 +406,7 @@ | ||
| const PYTHON_INSTALL: &str = "macOS: brew install python\nWindows: winget install -e --id Python.Python.3.12\nUbuntu/Debian: sudo apt install -y python3"; | ||
| const NODE_INSTALL: &str = "macOS: brew install node\nWindows: winget install -e --id OpenJS.NodeJS.LTS\nUbuntu/Debian: install Node.js LTS from https://nodejs.org/en/download"; | ||
| const NODE_INSTALL: &str = | ||
| "macOS: brew install node\nWindows: winget install -e --id OpenJS.NodeJS.LTS"; | ||
| const TYPESCRIPT_INSTALL: &str = "npm install -g typescript@5.9"; | ||
| const JAVA_INSTALL: &str = "macOS: brew install --cask temurin@21\nWindows: winget install -e --id EclipseAdoptium.Temurin.21.JDK\nUbuntu/Debian: sudo apt install -y openjdk-21-jdk"; | ||
| const RUST_INSTALL: &str = "macOS/Linux: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\nWindows: winget install -e --id Rustlang.Rustup"; | ||
| const CODEX_INSTALL: &str = "Install Codex CLI, or switch with /provider claude."; | ||
| const CLAUDE_INSTALL: &str = "Install Claude Code, or switch with /provider codex."; | ||
@@ -311,9 +436,160 @@ #[cfg(test)] | ||
| "codex", | ||
| |name| matches!(name, "node" | "tsc" | "codex"), | ||
| |name, _| match name { | ||
| "node" => Some("v22.5.0".to_string()), | ||
| "tsc" => Some("Version 5.9.3".to_string()), | ||
| _ => None, | ||
| }, | ||
| ); | ||
| assert!(output.contains("UPDATE TypeScript")); | ||
| assert!(output.contains("node >= 22.6.0 required")); | ||
| } | ||
| #[test] | ||
| fn doctor_reports_missing_tsc_when_node_is_ready() { | ||
| let output = doctor_text_with( | ||
| "en", | ||
| "ts", | ||
| "codex", | ||
| |name| matches!(name, "node" | "codex"), | ||
| |name, _| (name == "node").then(|| "v22.5.0".to_string()), | ||
| |name, _| (name == "node").then(|| "v22.6.0".to_string()), | ||
| ); | ||
| assert!(output.contains("MISSING TypeScript")); | ||
| assert!(output.contains("missing tsc")); | ||
| } | ||
| #[test] | ||
| fn doctor_reports_missing_node_when_tsc_is_ready() { | ||
| let output = doctor_text_with( | ||
| "en", | ||
| "ts", | ||
| "codex", | ||
| |name| matches!(name, "tsc" | "codex"), | ||
| |name, _| (name == "tsc").then(|| "Version 5.9.3".to_string()), | ||
| ); | ||
| assert!(output.contains("MISSING TypeScript")); | ||
| assert!(output.contains("node >= 22.6.0 required")); | ||
| assert!(!output.contains("missing tsc")); | ||
| } | ||
| #[test] | ||
| fn doctor_accepts_typescript_5_9_when_node_is_ready() { | ||
| let output = doctor_text_with( | ||
| "en", | ||
| "ts", | ||
| "codex", | ||
| |name| matches!(name, "node" | "tsc" | "codex"), | ||
| |name, _| match name { | ||
| "node" => Some("v22.6.0".to_string()), | ||
| "tsc" => Some("Version 5.9.3".to_string()), | ||
| _ => None, | ||
| }, | ||
| ); | ||
| assert!(output.contains("OK TypeScript")); | ||
| assert!(output.contains("tsc Version 5.9.3")); | ||
| } | ||
| #[test] | ||
| fn doctor_rejects_old_typescript() { | ||
| let output = doctor_text_with( | ||
| "en", | ||
| "ts", | ||
| "codex", | ||
| |name| matches!(name, "node" | "tsc" | "codex"), | ||
| |name, _| match name { | ||
| "node" => Some("v22.6.0".to_string()), | ||
| "tsc" => Some("Version 5.8.4".to_string()), | ||
| _ => None, | ||
| }, | ||
| ); | ||
| assert!(output.contains("UPDATE TypeScript")); | ||
| assert!(output.contains("Node.js >= 22.6.0")); | ||
| assert!(output.contains("TypeScript 5.9.x required")); | ||
| assert!(output.contains("Version 5.8.4")); | ||
| } | ||
| #[test] | ||
| fn doctor_rejects_future_typescript_major() { | ||
| let output = doctor_text_with( | ||
| "en", | ||
| "ts", | ||
| "codex", | ||
| |name| matches!(name, "node" | "tsc" | "codex"), | ||
| |name, _| match name { | ||
| "node" => Some("v22.6.0".to_string()), | ||
| "tsc" => Some("Version 6.0.0".to_string()), | ||
| _ => None, | ||
| }, | ||
| ); | ||
| assert!(output.contains("UPDATE TypeScript")); | ||
| assert!(output.contains("TypeScript 5.9.x required")); | ||
| assert!(output.contains("Version 6.0.0")); | ||
| } | ||
| #[test] | ||
| fn doctor_rejects_unreadable_typescript_version() { | ||
| let output = doctor_text_with( | ||
| "en", | ||
| "ts", | ||
| "codex", | ||
| |name| matches!(name, "node" | "tsc" | "codex"), | ||
| |name, _| (name == "node").then(|| "v22.6.0".to_string()), | ||
| ); | ||
| assert!(output.contains("UPDATE TypeScript")); | ||
| assert!(output.contains("unreadable tsc version")); | ||
| } | ||
| #[test] | ||
| fn doctor_missing_guidance_localizes_prose_and_preserves_install_commands() { | ||
| let output = doctor_text_with("ja", "python", "codex", |_| false, |_, _| None); | ||
| for expected in [ | ||
| "python3がありません", | ||
| "tscがありません", | ||
| "node >= 22.6.0が必要", | ||
| "javacがありません", | ||
| "rustcがありません", | ||
| "Codex CLIをインストール", | ||
| "Ubuntu/Debian: https://nodejs.org/en/downloadからNode.js LTSをダウンロード", | ||
| ] { | ||
| assert!(output.contains(expected), "{expected}: {output}"); | ||
| } | ||
| for command in [ | ||
| "brew install python", | ||
| "winget install -e --id OpenJS.NodeJS.LTS", | ||
| "npm install -g typescript@5.9", | ||
| "sudo apt install -y openjdk-21-jdk", | ||
| "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh", | ||
| "/provider claude", | ||
| ] { | ||
| assert!(output.contains(command), "{command}: {output}"); | ||
| } | ||
| assert!(!output.contains("missing "), "{output}"); | ||
| assert!(!output.contains("Install Codex"), "{output}"); | ||
| } | ||
| #[test] | ||
| fn doctor_update_guidance_localizes_prose_and_preserves_raw_versions() { | ||
| let output = doctor_text_with( | ||
| "es", | ||
| "ts", | ||
| "codex", | ||
| |name| matches!(name, "node" | "tsc" | "codex"), | ||
| |name, _| match name { | ||
| "node" => Some("v22.5.0 raw".to_string()), | ||
| "tsc" => Some("Version 5.8.4 raw".to_string()), | ||
| _ => None, | ||
| }, | ||
| ); | ||
| assert!(output.contains("se requiere TypeScript 5.9.x"), "{output}"); | ||
| assert!(output.contains("Version 5.8.4 raw"), "{output}"); | ||
| assert!(!output.contains("TypeScript 5.9.x required"), "{output}"); | ||
| } | ||
| } |
+55
-22
@@ -12,2 +12,17 @@ use super::*; | ||
| } | ||
| match key.code { | ||
| KeyCode::F(1) => { | ||
| self.handle_command("help")?; | ||
| return Ok(()); | ||
| } | ||
| KeyCode::F(5) => { | ||
| self.action_run()?; | ||
| return Ok(()); | ||
| } | ||
| KeyCode::F(6) => { | ||
| self.cycle_learning_view(); | ||
| return Ok(()); | ||
| } | ||
| _ => {} | ||
| } | ||
| if self.editing_notes { | ||
@@ -29,2 +44,5 @@ return self.handle_note_key(key); | ||
| let position = Position::new(mouse.column, mouse.row); | ||
| if self.command_palette_area.contains(position) { | ||
| return Ok(()); | ||
| } | ||
| if matches!( | ||
@@ -86,2 +104,5 @@ mouse.kind, | ||
| { | ||
| if self.mode == AppMode::Problems { | ||
| self.practice_view = PracticeView::Problem; | ||
| } | ||
| self.focus = Focus::Left; | ||
@@ -232,3 +253,9 @@ } else if !self.show_output | ||
| self.show_output = false; | ||
| self.focus = Focus::Code; | ||
| self.focus = if self.mode == AppMode::Problems | ||
| && self.practice_view == PracticeView::Problem | ||
| { | ||
| Focus::Left | ||
| } else { | ||
| Focus::Code | ||
| }; | ||
| } | ||
@@ -246,3 +273,6 @@ _ => self.handle_global_shortcut(key)?, | ||
| self.list_cursor = None; | ||
| self.write_text_output("Closed list."); | ||
| self.write_text_output(ui_text( | ||
| &self.state.settings.ui_language, | ||
| "list_closed", | ||
| )); | ||
| } | ||
@@ -266,2 +296,3 @@ _ => { | ||
| self.show_output = false; | ||
| self.practice_view = PracticeView::Code; | ||
| self.focus = Focus::Code; | ||
@@ -329,29 +360,31 @@ } | ||
| fn scroll_left(&mut self, delta: isize) { | ||
| let text = if self.mode == AppMode::Learn { | ||
| render_markdown_plain(&self.output) | ||
| let width = self.left_area.width.saturating_sub(2).max(1); | ||
| let lines = if self.mode == AppMode::Learn { | ||
| Paragraph::new(render_markdown_plain(&self.output)) | ||
| .wrap(Wrap { trim: false }) | ||
| .line_count(width) | ||
| } else { | ||
| render_problem_tui(&self.problem, &self.state.settings.ui_language) | ||
| Paragraph::new(problem_view::render( | ||
| &self.problem, | ||
| &self.state.settings.ui_language, | ||
| self.state.settings.theme == "light", | ||
| )) | ||
| .wrap(Wrap { trim: false }) | ||
| .line_count(width) | ||
| }; | ||
| self.left_scroll = scrolled( | ||
| self.left_scroll, | ||
| delta, | ||
| text.lines().count(), | ||
| self.left_area, | ||
| ); | ||
| self.left_scroll = scrolled(self.left_scroll, delta, lines, self.left_area); | ||
| } | ||
| fn scroll_output(&mut self, delta: isize) { | ||
| let text = if !self.show_output && self.mode == AppMode::Learn { | ||
| self.learn_result.clone() | ||
| } else if self.output_is_markdown { | ||
| render_markdown_plain(&self.output) | ||
| let width = self.output_area.width.saturating_sub(2).max(1); | ||
| let lines = if !self.show_output && self.mode == AppMode::Learn { | ||
| Paragraph::new(self.learn_result.clone()) | ||
| .wrap(Wrap { trim: false }) | ||
| .line_count(width) | ||
| } else { | ||
| self.output.clone() | ||
| Paragraph::new(self.output_text()) | ||
| .wrap(Wrap { trim: false }) | ||
| .line_count(width) | ||
| }; | ||
| self.output_scroll = scrolled( | ||
| self.output_scroll, | ||
| delta, | ||
| text.lines().count(), | ||
| self.output_area, | ||
| ); | ||
| self.output_scroll = scrolled(self.output_scroll, delta, lines, self.output_area); | ||
| } | ||
@@ -358,0 +391,0 @@ |
+87
-14
@@ -39,3 +39,3 @@ use super::*; | ||
| pub(super) fn start_problem_list(&mut self) -> Result<()> { | ||
| self.mode = AppMode::Problems; | ||
| self.transition_mode(AppMode::Problems); | ||
| self.state.settings.start_mode = "problems".to_string(); | ||
@@ -49,2 +49,3 @@ save_state(&self.root, &self.state)?; | ||
| pub(super) fn render_problem_list(&self) -> String { | ||
| let lang = &self.state.settings.ui_language; | ||
| let status_by_id = self | ||
@@ -60,5 +61,12 @@ .state | ||
| let mut lines = vec![ | ||
| "Problems".to_string(), | ||
| ui_text(lang, "problem_list_title").to_string(), | ||
| String::new(), | ||
| " # ID Difficulty Status Code Title".to_string(), | ||
| format!( | ||
| " # {} {} {} {} {}", | ||
| cell(ui_text(lang, "problem_list_id"), 18), | ||
| cell(ui_text(lang, "problem_list_difficulty"), 10), | ||
| cell(ui_text(lang, "problem_list_status"), 10), | ||
| cell(ui_text(lang, "problem_list_code"), 9), | ||
| ui_text(lang, "problem_list_name") | ||
| ), | ||
| ]; | ||
@@ -73,8 +81,5 @@ for (index, problem) in self.bank.iter().enumerate() { | ||
| let title = localized(&problem.title, &self.state.settings.ui_language); | ||
| let code_status = self.submission_status(problem).0; | ||
| lines.push(format!( | ||
| "{marker} {current} {:>2} {:<18} {:<10} {:<10} {:<9} {title}", | ||
| index + 1, | ||
| problem.id, | ||
| problem.difficulty, | ||
| let difficulty = localized_status(lang, &problem.difficulty); | ||
| let status = localized_status( | ||
| lang, | ||
| status_by_id | ||
@@ -84,6 +89,14 @@ .get(problem.id.as_str()) | ||
| .unwrap_or("-"), | ||
| code_status, | ||
| ); | ||
| let code_status = localized_status(lang, &self.submission_status(problem).0); | ||
| lines.push(format!( | ||
| "{marker} {current} {:>2} {} {} {} {} {title}", | ||
| index + 1, | ||
| cell(&problem.id, 18), | ||
| cell(&difficulty, 10), | ||
| cell(&status, 10), | ||
| cell(&code_status, 9), | ||
| )); | ||
| } | ||
| lines.push("\nup/down or j/k select | enter open | esc close".to_string()); | ||
| lines.push(format!("\n{}", ui_text(lang, "problem_list_hint"))); | ||
| lines.join("\n") | ||
@@ -123,3 +136,6 @@ } | ||
| let Some(problem) = self.find_problem(query).cloned() else { | ||
| self.write_text_output(&format!("Problem not found: {query}\nTry /problems.")); | ||
| self.write_text_output( | ||
| &ui_text(&self.state.settings.ui_language, "problem_not_found") | ||
| .replace("{query}", query), | ||
| ); | ||
| return Ok(()); | ||
@@ -129,3 +145,3 @@ }; | ||
| self.state.current_problem = self.problem.id.clone(); | ||
| self.mode = AppMode::Problems; | ||
| self.transition_mode(AppMode::Problems); | ||
| self.state.settings.start_mode = "problems".to_string(); | ||
@@ -147,3 +163,4 @@ if !self | ||
| self.show_output = false; | ||
| self.focus = Focus::Code; | ||
| self.practice_view = PracticeView::Problem; | ||
| self.focus = Focus::Left; | ||
| Ok(()) | ||
@@ -199,1 +216,57 @@ } | ||
| } | ||
| fn cell(value: &str, width: usize) -> String { | ||
| format!( | ||
| "{value}{}", | ||
| " ".repeat(width.saturating_sub(display_width(value))) | ||
| ) | ||
| } | ||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| fn app(language: &str) -> PracticodeApp { | ||
| let root = crate::process::unique_temp_path("practicode-problem-list-locale", "dir"); | ||
| std::fs::create_dir_all(&root).unwrap(); | ||
| let mut app = PracticodeApp::new(root).unwrap(); | ||
| app.state.settings.ui_language = language.to_string(); | ||
| app | ||
| } | ||
| #[test] | ||
| fn problem_list_localizes_app_owned_columns_and_values() { | ||
| let app = app("zh"); | ||
| let list = app.render_problem_list(); | ||
| for key in [ | ||
| "problem_list_title", | ||
| "problem_list_id", | ||
| "problem_list_difficulty", | ||
| "problem_list_status", | ||
| "problem_list_code", | ||
| "problem_list_name", | ||
| "problem_list_hint", | ||
| "status_easy", | ||
| "status_assigned", | ||
| "status_template", | ||
| ] { | ||
| assert!(list.contains(ui_text("zh", key)), "{key}: {list}"); | ||
| } | ||
| assert!(!list.contains("Problems"), "{list}"); | ||
| assert!(!list.contains("Difficulty"), "{list}"); | ||
| } | ||
| #[test] | ||
| fn missing_problem_feedback_uses_the_selected_locale() { | ||
| let mut app = app("ja"); | ||
| app.open_problem("does-not-exist").unwrap(); | ||
| assert_eq!( | ||
| app.output, | ||
| ui_text("ja", "problem_not_found").replace("{query}", "does-not-exist") | ||
| ); | ||
| } | ||
| } |
@@ -0,1 +1,2 @@ | ||
| use super::localized_status; | ||
| use crate::core::{Problem, localized, normalize_ui_language, ui_text}; | ||
@@ -60,3 +61,3 @@ use ratatui::{ | ||
| ui_text(&lang, "difficulty"), | ||
| problem.difficulty, | ||
| localized_status(&lang, &problem.difficulty), | ||
| ui_text(&lang, "topics"), | ||
@@ -100,3 +101,3 @@ problem.topics.join(", ") | ||
| ))); | ||
| push_code_lines(&mut lines, &case.input, code_style); | ||
| push_code_lines(&mut lines, &case.input, code_style, &lang); | ||
| lines.push(Line::from(Span::styled( | ||
@@ -106,3 +107,3 @@ format!(" {}", ui_text(&lang, "output")), | ||
| ))); | ||
| push_code_lines(&mut lines, &case.output, code_style); | ||
| push_code_lines(&mut lines, &case.output, code_style, &lang); | ||
| } | ||
@@ -126,3 +127,8 @@ Text::from(lines) | ||
| fn push_code_lines(lines: &mut Vec<Line<'static>>, body: &str, code_style: Style) { | ||
| fn push_code_lines( | ||
| lines: &mut Vec<Line<'static>>, | ||
| body: &str, | ||
| code_style: Style, | ||
| ui_language: &str, | ||
| ) { | ||
| let body = body.trim_end(); | ||
@@ -132,3 +138,3 @@ if body.is_empty() { | ||
| Span::raw(" "), | ||
| Span::styled("<empty>".to_string(), code_style), | ||
| Span::styled(ui_text(ui_language, "empty_value").to_string(), code_style), | ||
| ])); | ||
@@ -144,1 +150,44 @@ return; | ||
| } | ||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| #[test] | ||
| fn tui_problem_view_localizes_empty_example_values() { | ||
| let mut problem = crate::core::starter_problem(); | ||
| problem.examples[0].input.clear(); | ||
| problem.examples[0].output.clear(); | ||
| let text = render(&problem, "ko", false); | ||
| let rendered = text | ||
| .lines | ||
| .iter() | ||
| .flat_map(|line| line.spans.iter()) | ||
| .map(|span| span.content.as_ref()) | ||
| .collect::<String>(); | ||
| assert!(rendered.contains("<비어 있음>"), "{rendered}"); | ||
| assert!(!rendered.contains("<empty>"), "{rendered}"); | ||
| } | ||
| #[test] | ||
| fn tui_problem_view_localizes_difficulty_tokens() { | ||
| for language in ["ko", "ja", "zh", "es"] { | ||
| let problem = crate::core::starter_problem(); | ||
| let text = render(&problem, language, false); | ||
| let rendered = text | ||
| .lines | ||
| .iter() | ||
| .flat_map(|line| line.spans.iter()) | ||
| .map(|span| span.content.as_ref()) | ||
| .collect::<String>(); | ||
| assert!( | ||
| rendered.contains(ui_text(language, "status_easy")), | ||
| "{language}: {rendered}" | ||
| ); | ||
| assert!(!rendered.contains(": easy"), "{language}: {rendered}"); | ||
| } | ||
| } | ||
| } |
| use crate::core::{ | ||
| AI_PROVIDERS, AppState, CLAUDE_AI_EFFORTS, CODEX_AI_EFFORTS, DIFFICULTIES, LANGUAGES, THEMES, | ||
| UI_LANGUAGES, normalize_ai_effort, | ||
| UI_LANGUAGES, normalize_ai_effort, ui_text, | ||
| }; | ||
@@ -35,5 +35,5 @@ | ||
| let mut lines = vec![ | ||
| label(ui_language, "title").to_string(), | ||
| ui_text(ui_language, "settings_title").to_string(), | ||
| String::new(), | ||
| label(ui_language, "instructions").to_string(), | ||
| ui_text(ui_language, "settings_instructions").to_string(), | ||
| String::new(), | ||
@@ -45,3 +45,3 @@ row( | ||
| "{}: {}", | ||
| label(ui_language, "code_language"), | ||
| ui_text(ui_language, "settings_code_language"), | ||
| settings.language | ||
@@ -55,3 +55,3 @@ ), | ||
| "{}: {}", | ||
| label(ui_language, "ui_language"), | ||
| ui_text(ui_language, "settings_ui_language"), | ||
| settings.ui_language | ||
@@ -63,3 +63,7 @@ ), | ||
| 2, | ||
| &format!("{}: {}", label(ui_language, "theme"), settings.theme), | ||
| &format!( | ||
| "{}: {}", | ||
| ui_text(ui_language, "settings_theme"), | ||
| settings.theme | ||
| ), | ||
| ), | ||
@@ -71,3 +75,3 @@ row( | ||
| "{}: {}", | ||
| label(ui_language, "difficulty"), | ||
| ui_text(ui_language, "settings_difficulty"), | ||
| settings.difficulty | ||
@@ -77,11 +81,14 @@ ), | ||
| String::new(), | ||
| format!("{}: {topics}", label(ui_language, "preferred_topics")), | ||
| format!("{}: {avoid}", label(ui_language, "avoid_topics")), | ||
| format!( | ||
| "{}: {topics}", | ||
| ui_text(ui_language, "settings_preferred_topics") | ||
| ), | ||
| format!("{}: {avoid}", ui_text(ui_language, "settings_avoid_topics")), | ||
| format!( | ||
| "{}: {generate_languages}", | ||
| label(ui_language, "generated_answer_languages") | ||
| ui_text(ui_language, "settings_generated_answer_languages") | ||
| ), | ||
| format!( | ||
| "{}: {generate_ui_languages}", | ||
| label(ui_language, "generated_ui_languages") | ||
| ui_text(ui_language, "settings_generated_ui_languages") | ||
| ), | ||
@@ -91,3 +98,7 @@ row( | ||
| AI_PROVIDER_ROW, | ||
| &format!("AI provider: {}", settings.ai_provider), | ||
| &format!( | ||
| "{}: {}", | ||
| ui_text(ui_language, "settings_ai_provider"), | ||
| settings.ai_provider | ||
| ), | ||
| ), | ||
@@ -98,5 +109,6 @@ row( | ||
| &format!( | ||
| "AI model: {}{}", | ||
| "{}: {}{}", | ||
| ui_text(ui_language, "settings_ai_model"), | ||
| if settings.ai_model == "auto" { | ||
| label(ui_language, "provider_default") | ||
| ui_text(ui_language, "settings_provider_default") | ||
| } else { | ||
@@ -106,7 +118,7 @@ settings.ai_model.as_str() | ||
| if models_loading { | ||
| " (loading)" | ||
| format!(" ({})", ui_text(ui_language, "settings_model_loading")) | ||
| } else if available_models.is_empty() { | ||
| " (/model to load)" | ||
| format!(" ({})", ui_text(ui_language, "settings_model_load_hint")) | ||
| } else { | ||
| "" | ||
| String::new() | ||
| } | ||
@@ -119,5 +131,6 @@ ), | ||
| &format!( | ||
| "AI effort: {}", | ||
| "{}: {}", | ||
| ui_text(ui_language, "settings_ai_effort"), | ||
| if settings.ai_effort == "auto" { | ||
| label(ui_language, "provider_default") | ||
| ui_text(ui_language, "settings_provider_default") | ||
| } else { | ||
@@ -131,6 +144,10 @@ settings.ai_effort.as_str() | ||
| NOTE_ROW, | ||
| &format!("{}: Enter", label(ui_language, "problem_notes")), | ||
| &format!( | ||
| "{}: {}", | ||
| ui_text(ui_language, "settings_problem_notes"), | ||
| ui_text(ui_language, "settings_note_action") | ||
| ), | ||
| ), | ||
| String::new(), | ||
| label(ui_language, "answer_toggles").to_string(), | ||
| ui_text(ui_language, "settings_answer_toggles").to_string(), | ||
| ]; | ||
@@ -147,3 +164,3 @@ for (index, language) in LANGUAGES.iter().enumerate() { | ||
| lines.push(String::new()); | ||
| lines.push(label(ui_language, "ui_toggles").to_string()); | ||
| lines.push(ui_text(ui_language, "settings_ui_toggles").to_string()); | ||
| for (index, language) in UI_LANGUAGES.iter().enumerate() { | ||
@@ -160,3 +177,3 @@ let row_index = TOGGLE_START + LANGUAGES.len() + index; | ||
| String::new(), | ||
| label(ui_language, "commands").to_string(), | ||
| ui_text(ui_language, "settings_commands").to_string(), | ||
| "/profile".to_string(), | ||
@@ -248,48 +265,2 @@ "/difficulty auto|easy|medium|hard".to_string(), | ||
| fn label<'a>(ui_language: &str, key: &'a str) -> &'a str { | ||
| if ui_language == "ko" { | ||
| match key { | ||
| "title" => "사용자 프로필", | ||
| "instructions" => "위/아래로 이동하고 Space 또는 Enter로 변경/토글", | ||
| "code_language" => "코드 언어", | ||
| "ui_language" => "UI 언어", | ||
| "theme" => "테마", | ||
| "difficulty" => "난이도", | ||
| "preferred_topics" => "선호 주제", | ||
| "avoid_topics" => "피할 주제", | ||
| "generated_answer_languages" => "생성 정답 언어", | ||
| "generated_ui_languages" => "생성 문제 언어", | ||
| "provider_default" => "auto (provider 기본값)", | ||
| "problem_notes" => "문제 생성 메모 편집", | ||
| "answer_toggles" => "생성 정답 언어 토글", | ||
| "ui_toggles" => "생성 문제 언어 토글", | ||
| "commands" => "명령", | ||
| "none" => "(없음)", | ||
| "all" => "전체", | ||
| _ => key, | ||
| } | ||
| } else { | ||
| match key { | ||
| "title" => "User profile", | ||
| "instructions" => "Use up/down to move. Press Space or Enter to cycle/toggle.", | ||
| "code_language" => "Code language", | ||
| "ui_language" => "UI language", | ||
| "theme" => "Theme", | ||
| "difficulty" => "Difficulty", | ||
| "preferred_topics" => "Preferred topics", | ||
| "avoid_topics" => "Avoid topics", | ||
| "generated_answer_languages" => "Generated answer languages", | ||
| "generated_ui_languages" => "Generated UI languages", | ||
| "provider_default" => "auto (provider default)", | ||
| "problem_notes" => "Edit problem notes", | ||
| "answer_toggles" => "Generated answer language toggles", | ||
| "ui_toggles" => "Generated problem text language toggles", | ||
| "commands" => "Commands", | ||
| "none" => "(none)", | ||
| "all" => "all", | ||
| _ => key, | ||
| } | ||
| } | ||
| } | ||
| fn cycle_ai_model(state: &mut AppState, available_models: &[String]) { | ||
@@ -416,3 +387,3 @@ let mut models = vec!["auto"]; | ||
| if values.is_empty() { | ||
| label(ui_language, "none").to_string() | ||
| ui_text(ui_language, "settings_none").to_string() | ||
| } else { | ||
@@ -425,3 +396,3 @@ values.join(", ") | ||
| if values.is_empty() { | ||
| label(ui_language, "all").to_string() | ||
| ui_text(ui_language, "settings_all").to_string() | ||
| } else { | ||
@@ -428,0 +399,0 @@ values.join(", ") |
+168
-26
| use super::*; | ||
| impl PracticodeApp { | ||
| pub(super) fn status_text_for_width(&self, width: u16) -> String { | ||
| if self.task_rx.is_some() | ||
| || self.editing_notes | ||
| || self.focus == Focus::Command | ||
| || self.list_cursor.is_some() | ||
| || self.show_output | ||
| { | ||
| return format!(" {} ", self.mode_hint()); | ||
| } | ||
| if width > 120 { | ||
| return self.status_text(); | ||
| } | ||
| let lang = &self.state.settings.ui_language; | ||
| let key = match self.mode { | ||
| AppMode::Home => "hint_home_compact", | ||
| AppMode::Learn => "hint_learn_compact", | ||
| AppMode::Problems => "hint_problem_compact", | ||
| }; | ||
| format!(" {} ", ui_text(lang, key)) | ||
| } | ||
| pub(super) fn status_text(&self) -> String { | ||
| let lang = &self.state.settings.ui_language; | ||
| if self.mode == AppMode::Home && !self.show_output { | ||
| return format!(" PRACTICODE | home | {} ", self.mode_hint()); | ||
| return format!( | ||
| " PRACTICODE | {} | {} ", | ||
| ui_text(lang, "mode_home"), | ||
| self.mode_hint() | ||
| ); | ||
| } | ||
| if self.mode == AppMode::Learn { | ||
| let lesson = current_syntax_lesson(&self.state, &self.state.settings.language); | ||
| let (done, total) = syntax_progress_count(&self.state, &self.state.settings.language); | ||
| let (done, total) = | ||
| syntax_core_progress_count(&self.state, &self.state.settings.language); | ||
| let selected_focus = match self.learning_session.view() { | ||
| LearningView::Lesson => Focus::Left, | ||
| LearningView::Code => Focus::Code, | ||
| LearningView::Result => Focus::Output, | ||
| }; | ||
| let context = if !self.show_output && self.focus == selected_focus { | ||
| format!( | ||
| "{}: {} | {}", | ||
| ui_text(lang, "focus_active"), | ||
| learning_view_label(lang, self.learning_session.view()), | ||
| self.mode_hint() | ||
| ) | ||
| } else { | ||
| self.mode_hint().to_string() | ||
| }; | ||
| return format!( | ||
| " PRACTICODE | learn | {} | {} | {done}/{total} | code:{} | {} ", | ||
| " PRACTICODE | {} | {} | {} | {done}/{total} | {context} | {}:{} ", | ||
| ui_text(lang, "mode_learn"), | ||
| syntax_language_name(&self.state.settings.language), | ||
| lesson.id, | ||
| ui_text(lang, "status_code"), | ||
| self.state.settings.language, | ||
| self.mode_hint(), | ||
| ); | ||
| } | ||
| let code_status = self.submission_status(&self.problem).0; | ||
| let code_status = localized_status(lang, &self.submission_status(&self.problem).0); | ||
| let activity = if self.busy_label.is_empty() { | ||
| "idle".to_string() | ||
| ui_text(lang, "status_idle").to_string() | ||
| } else { | ||
| format!("{}{}", self.busy_body, self.busy_dots()) | ||
| let elapsed = self | ||
| .busy_started | ||
| .map(|started| started.elapsed().as_secs()) | ||
| .unwrap_or_default(); | ||
| format!( | ||
| "{}{} {}", | ||
| self.busy_text(), | ||
| self.busy_dots(), | ||
| self.elapsed_text(elapsed) | ||
| ) | ||
| }; | ||
@@ -38,7 +90,8 @@ let tail = if let Some(version) = self.update_notice.as_ref() { | ||
| format!( | ||
| " PRACTICODE | {} | {} | {} | {} | code:{} | {} | {} ", | ||
| " PRACTICODE | {} | {} | {} | {} | {}:{} | {} | {} ", | ||
| self.problem.id, | ||
| self.problem.difficulty, | ||
| self.problem_status(&self.problem), | ||
| localized_status(lang, &self.problem.difficulty), | ||
| localized_status(lang, &self.problem_status(&self.problem)), | ||
| activity, | ||
| ui_text(lang, "status_code"), | ||
| code_status, | ||
@@ -51,3 +104,3 @@ self.state.settings.language, | ||
| pub(super) fn next_source_help(&self) -> String { | ||
| "Next behavior: /next opens unsolved local problems first and asks AI only when none remain. Use /generate <request> to create a problem in the background.".to_string() | ||
| ui_text(&self.state.settings.ui_language, "next_source_help").to_string() | ||
| } | ||
@@ -61,8 +114,81 @@ | ||
| .unwrap_or_default(); | ||
| Some(format!("bg generate {elapsed}s")) | ||
| Some(format!( | ||
| "{} {}", | ||
| ui_text( | ||
| &self.state.settings.ui_language, | ||
| "status_background_generation" | ||
| ), | ||
| self.elapsed_text(elapsed) | ||
| )) | ||
| } else { | ||
| self.generate_notice.clone() | ||
| self.generate_notice | ||
| .as_ref() | ||
| .map(|notice| self.generation_notice_text(notice)) | ||
| } | ||
| } | ||
| pub(super) fn busy_text(&self) -> String { | ||
| let lang = &self.state.settings.ui_language; | ||
| match self.busy_label.as_str() { | ||
| "ai" => ui_text(lang, "busy_ai_thinking").replace("{provider}", &self.busy_arg), | ||
| "next" => ui_text(lang, "generating_next").to_string(), | ||
| _ => self.busy_arg.clone(), | ||
| } | ||
| } | ||
| pub(super) fn elapsed_text(&self, seconds: u64) -> String { | ||
| ui_text(&self.state.settings.ui_language, "elapsed_seconds") | ||
| .replace("{seconds}", &seconds.to_string()) | ||
| } | ||
| pub(super) fn generation_notice_text(&self, notice: &GenerationNotice) -> String { | ||
| let lang = &self.state.settings.ui_language; | ||
| match notice { | ||
| GenerationNotice::Started => ui_text(lang, "generation_started").to_string(), | ||
| GenerationNotice::Duplicate => ui_text(lang, "generation_duplicate").to_string(), | ||
| GenerationNotice::Generated(count) => { | ||
| ui_text(lang, "generation_generated").replace("{count}", &count.to_string()) | ||
| } | ||
| GenerationNotice::Failed { | ||
| status, | ||
| detail, | ||
| added, | ||
| reload_error, | ||
| } => { | ||
| let mut lines = vec![ui_text(lang, "generation_failed").to_string()]; | ||
| if let Some(status) = status { | ||
| lines.push( | ||
| ui_text(lang, "generation_exit_status") | ||
| .replace("{status}", &status.to_string()), | ||
| ); | ||
| } | ||
| if !detail.is_empty() { | ||
| lines.push(detail.clone()); | ||
| } | ||
| if *added > 0 { | ||
| lines.push( | ||
| ui_text(lang, "generation_partial_count") | ||
| .replace("{count}", &added.to_string()), | ||
| ); | ||
| } | ||
| if let Some(error) = reload_error { | ||
| lines.push(ui_text(lang, "generation_reload_failed").to_string()); | ||
| if !error.is_empty() { | ||
| lines.push(error.clone()); | ||
| } | ||
| } | ||
| lines.join("\n") | ||
| } | ||
| GenerationNotice::Finished => ui_text(lang, "generation_finished").to_string(), | ||
| GenerationNotice::ReloadFailed(detail) => { | ||
| let mut text = ui_text(lang, "generation_reload_failed").to_string(); | ||
| if !detail.is_empty() { | ||
| text.push('\n'); | ||
| text.push_str(detail); | ||
| } | ||
| text | ||
| } | ||
| } | ||
| } | ||
| pub(super) fn busy_dots(&self) -> String { | ||
@@ -96,3 +222,3 @@ ".".repeat((self.busy_frame / 8) % 4) | ||
| if self.editing_notes { | ||
| return "notes: type to edit, Esc profile"; | ||
| return ui_text(lang, "hint_notes"); | ||
| } | ||
@@ -102,5 +228,15 @@ if self.mode == AppMode::Home && !self.show_output { | ||
| } | ||
| if self.mode == AppMode::Learn && self.focus == Focus::Code { | ||
| if self.mode == AppMode::Learn | ||
| && !self.show_output | ||
| && self.learning_session.view() == LearningView::Result | ||
| && self.focus != Focus::Command | ||
| { | ||
| return ui_text(lang, "hint_result"); | ||
| } | ||
| if self.mode == AppMode::Learn && !self.show_output && self.focus != Focus::Command { | ||
| return ui_text(lang, "hint_learn"); | ||
| } | ||
| if self.mode == AppMode::Problems && !self.show_output && self.focus != Focus::Command { | ||
| return ui_text(lang, "hint_problem"); | ||
| } | ||
| match (self.focus, self.list_cursor.is_some(), self.show_output) { | ||
@@ -126,15 +262,15 @@ (Focus::Command, _, _) => ui_text(lang, "hint_command"), | ||
| .join("\n"); | ||
| let daily_loop = match self.mode { | ||
| AppMode::Home => { | ||
| "1. Choose Learn syntax or Practice coding tests.\n2. Use arrow keys to move and Enter/Space to open.\n3. Press `/` for commands." | ||
| } | ||
| AppMode::Learn => { | ||
| "1. Read the lesson on the left.\n2. Edit the exercise on the right.\n3. Use `/run`, then `/next` or `/back`." | ||
| } | ||
| AppMode::Problems => { | ||
| "1. Type code in the right pane.\n2. Press `Esc`, then choose `/run` from the command palette.\n3. Use `/next` when it passes." | ||
| } | ||
| let (daily_loop, shortcuts) = match self.mode { | ||
| AppMode::Home => (ui_text(lang, "help_home_loop"), ui_text(lang, "home_help")), | ||
| AppMode::Learn => ( | ||
| ui_text(lang, "help_learn_loop"), | ||
| ui_text(lang, "learning_shortcuts"), | ||
| ), | ||
| AppMode::Problems => ( | ||
| ui_text(lang, "help_problem_loop"), | ||
| ui_text(lang, "practice_shortcuts"), | ||
| ), | ||
| }; | ||
| format!( | ||
| "# {}\n\n## {}\n\n{}\n\n## {}\n\n{}\n\n## {}\n\n- `/` opens the command palette outside the editor.\n- `↑/↓` selects a command and `Enter` accepts it.\n- `Esc` cancels the command palette or leaves output.\n\n## {}\n\n- stdout is shown when a case fails.\n- stderr is shown without affecting the expected stdout.", | ||
| "# {}\n\n## {}\n\n{}\n\n## {}\n\n{}\n\n## {}\n\n- {}\n- {}\n- {}\n- {}\n\n## {}\n\n- {}\n- {}", | ||
| ui_text(lang, "help_title"), | ||
@@ -146,5 +282,11 @@ ui_text(lang, "daily_loop"), | ||
| ui_text(lang, "keys"), | ||
| shortcuts, | ||
| ui_text(lang, "help_palette_open"), | ||
| ui_text(lang, "help_palette_move"), | ||
| ui_text(lang, "help_palette_close"), | ||
| ui_text(lang, "debug_prints"), | ||
| ui_text(lang, "help_stdout"), | ||
| ui_text(lang, "help_stderr"), | ||
| ) | ||
| } | ||
| } |
+480
-40
| use super::*; | ||
| const UPDATE_CHECKING_TEXT: &str = "Checking for updates..."; | ||
| impl PracticodeApp { | ||
@@ -12,4 +10,11 @@ pub(super) fn start_ai_prompt(&mut self, prompt: &str) -> Result<()> { | ||
| self.save_code()?; | ||
| if self.mode == AppMode::Learn { | ||
| self.learning_session.mark_assisted(); | ||
| } | ||
| #[cfg(test)] | ||
| if self.ai_spawn_disabled { | ||
| return Ok(()); | ||
| } | ||
| let label = normalize_ai_provider(&self.state.settings.ai_provider); | ||
| self.start_busy("ai", &format!("{label} is thinking")); | ||
| self.start_busy("ai", &label); | ||
| let root = self.root.clone(); | ||
@@ -50,3 +55,6 @@ let problem = self.problem.clone(); | ||
| { | ||
| self.write_text_output(&format!("Next failed\n{error}")); | ||
| self.write_text_output(&format!( | ||
| "{}\n{error}", | ||
| ui_text(&self.state.settings.ui_language, "next_failed") | ||
| )); | ||
| } | ||
@@ -60,3 +68,3 @@ } | ||
| let output = self.generate_rx.as_ref().and_then(|rx| rx.try_recv().ok()); | ||
| let Some(output) = output else { | ||
| let Some(result) = output else { | ||
| return; | ||
@@ -67,3 +75,3 @@ }; | ||
| let old_len = self.generate_bank_len; | ||
| match load_bank(&self.root) { | ||
| let (added, reload_error) = match load_bank(&self.root) { | ||
| Ok(bank) => { | ||
@@ -73,16 +81,29 @@ let added = bank.len().saturating_sub(old_len); | ||
| let _ = save_state(&self.root, &self.state); | ||
| self.generate_notice = Some(if added > 0 { | ||
| format!("Generated {added} problem in background. Use /next.") | ||
| } else if output.contains("failed") { | ||
| "Background generation failed. Use /generate to retry.".to_string() | ||
| (added, None) | ||
| } | ||
| Err(error) => (0, Some(error.to_string())), | ||
| }; | ||
| self.generate_notice = Some(match result { | ||
| AiGenerationResult::Failed { status, detail } => GenerationNotice::Failed { | ||
| status, | ||
| detail, | ||
| added, | ||
| reload_error, | ||
| }, | ||
| AiGenerationResult::FailedToRun(detail) => GenerationNotice::Failed { | ||
| status: None, | ||
| detail, | ||
| added, | ||
| reload_error, | ||
| }, | ||
| AiGenerationResult::Succeeded(_) => { | ||
| if let Some(error) = reload_error { | ||
| GenerationNotice::ReloadFailed(error) | ||
| } else if added > 0 { | ||
| GenerationNotice::Generated(added) | ||
| } else { | ||
| "Background generation finished. Use /problems to review.".to_string() | ||
| }); | ||
| GenerationNotice::Finished | ||
| } | ||
| } | ||
| Err(error) => { | ||
| self.generate_notice = Some(format!( | ||
| "Background generation finished, but bank reload failed: {error}" | ||
| )); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
@@ -92,3 +113,4 @@ | ||
| let result = self.update_rx.as_ref().and_then(|rx| rx.try_recv().ok()); | ||
| let showing_update_check = self.output == UPDATE_CHECKING_TEXT; | ||
| let showing_update_check = | ||
| self.output == ui_text(&self.state.settings.ui_language, "update_checking"); | ||
| if let Some(result) = result { | ||
@@ -138,5 +160,6 @@ self.update_rx = None; | ||
| let query_provider = provider.clone(); | ||
| let ui_language = self.state.settings.ui_language.clone(); | ||
| let (tx, rx) = mpsc::channel(); | ||
| thread::spawn(move || { | ||
| let _ = tx.send(available_models(&query_provider)); | ||
| let _ = tx.send(available_models(&query_provider, &ui_language)); | ||
| }); | ||
@@ -168,8 +191,14 @@ self.available_models_provider = provider; | ||
| pub(super) fn model_status_text(&self) -> String { | ||
| let lang = &self.state.settings.ui_language; | ||
| let mut lines = vec![ | ||
| format!("AI provider: {}", self.state.settings.ai_provider), | ||
| format!( | ||
| "AI model: {}", | ||
| "{}: {}", | ||
| ui_text(lang, "settings_ai_provider"), | ||
| self.state.settings.ai_provider | ||
| ), | ||
| format!( | ||
| "{}: {}", | ||
| ui_text(lang, "settings_ai_model"), | ||
| if self.state.settings.ai_model == "auto" { | ||
| "auto (provider default)" | ||
| ui_text(lang, "settings_provider_default") | ||
| } else { | ||
@@ -180,5 +209,6 @@ self.state.settings.ai_model.as_str() | ||
| format!( | ||
| "AI effort: {}", | ||
| "{}: {}", | ||
| ui_text(lang, "settings_ai_effort"), | ||
| if self.state.settings.ai_effort == "auto" { | ||
| "auto (provider default)" | ||
| ui_text(lang, "settings_provider_default") | ||
| } else { | ||
@@ -188,7 +218,7 @@ self.state.settings.ai_effort.as_str() | ||
| ), | ||
| "Use /model auto to let the provider choose its default.".to_string(), | ||
| "Use /effort auto to let the provider choose its default.".to_string(), | ||
| ui_text(lang, "model_use_default_model").to_string(), | ||
| ui_text(lang, "model_use_default_effort").to_string(), | ||
| ]; | ||
| if self.model_rx.is_some() { | ||
| lines.push("Loading provider model list...".to_string()); | ||
| lines.push(ui_text(lang, "model_loading").to_string()); | ||
| } else if self.available_models.is_empty() { | ||
@@ -198,5 +228,5 @@ lines.push( | ||
| .clone() | ||
| .unwrap_or_else(|| "Provider model list is unavailable.".to_string()), | ||
| .unwrap_or_else(|| ui_text(lang, "model_unavailable").to_string()), | ||
| ); | ||
| lines.push("Use /model <name> for a known model.".to_string()); | ||
| lines.push(ui_text(lang, "model_custom_hint").to_string()); | ||
| } else { | ||
@@ -211,4 +241,6 @@ if let Some(message) = &self.model_message { | ||
| }; | ||
| lines.push(format!("Available efforts: {}", efforts.join(", "))); | ||
| lines.push("Available models:".to_string()); | ||
| lines.push( | ||
| ui_text(lang, "model_available_efforts").replace("{efforts}", &efforts.join(", ")), | ||
| ); | ||
| lines.push(ui_text(lang, "model_available_models").to_string()); | ||
| lines.extend( | ||
@@ -223,6 +255,6 @@ self.available_models | ||
| pub(super) fn start_busy(&mut self, label: &str, body: &str) { | ||
| pub(super) fn start_busy(&mut self, label: &str, arg: &str) { | ||
| self.settings_cursor = None; | ||
| self.busy_label = label.to_string(); | ||
| self.busy_body = body.to_string(); | ||
| self.busy_arg = arg.to_string(); | ||
| self.busy_started = Some(Instant::now()); | ||
@@ -238,3 +270,3 @@ self.busy_frame = 0; | ||
| self.busy_label.clear(); | ||
| self.busy_body.clear(); | ||
| self.busy_arg.clear(); | ||
| self.busy_started = None; | ||
@@ -310,3 +342,3 @@ self.busy_frame = 0; | ||
| } else if self.update_rx.is_some() { | ||
| self.write_text_output(UPDATE_CHECKING_TEXT); | ||
| self.write_text_output(ui_text(&lang, "update_checking")); | ||
| } else if matches!(self.update_check, Some(UpdateCheck::Disabled)) { | ||
@@ -323,3 +355,6 @@ self.write_text_output(ui_text(&lang, "update_check_disabled")); | ||
| append_problem_note(&self.root, note)?; | ||
| self.write_text_output(&format!("Problem note saved to {PROBLEM_NOTES_PATH}.")); | ||
| self.write_text_output( | ||
| &ui_text(&self.state.settings.ui_language, "note_saved") | ||
| .replace("{path}", PROBLEM_NOTES_PATH), | ||
| ); | ||
| Ok(()) | ||
@@ -331,5 +366,9 @@ } | ||
| if notes.is_empty() { | ||
| self.write_text_output("No notes yet. Use /note to edit problem-generation notes."); | ||
| self.write_text_output(ui_text(&self.state.settings.ui_language, "notes_empty")); | ||
| } else { | ||
| self.write_text_output(&format!("Problem notes ({PROBLEM_NOTES_PATH})\n\n{notes}")); | ||
| self.write_text_output(&format!( | ||
| "{}\n\n{notes}", | ||
| ui_text(&self.state.settings.ui_language, "notes_title") | ||
| .replace("{path}", PROBLEM_NOTES_PATH) | ||
| )); | ||
| } | ||
@@ -344,2 +383,57 @@ Ok(()) | ||
| fn output_text_content(app: &PracticodeApp) -> String { | ||
| app.output_text() | ||
| .lines | ||
| .iter() | ||
| .map(|line| { | ||
| line.spans | ||
| .iter() | ||
| .map(|span| span.content.as_ref()) | ||
| .collect::<String>() | ||
| }) | ||
| .collect::<Vec<_>>() | ||
| .join("\n") | ||
| } | ||
| fn finish_generation(app: &mut PracticodeApp, result: AiGenerationResult, old_len: usize) { | ||
| let (tx, rx) = std::sync::mpsc::channel(); | ||
| tx.send(result).unwrap(); | ||
| app.generate_bank_len = old_len; | ||
| app.generate_rx = Some(rx); | ||
| app.check_background_generation(); | ||
| } | ||
| fn learning_app(name: &str) -> PracticodeApp { | ||
| let root = crate::process::unique_temp_path(name, "dir"); | ||
| std::fs::create_dir_all(&root).unwrap(); | ||
| let mut app = PracticodeApp::new(root).unwrap(); | ||
| app.ai_spawn_disabled = true; | ||
| app.handle_command("learn python").unwrap(); | ||
| app | ||
| } | ||
| fn localized_app(name: &str, language: &str) -> PracticodeApp { | ||
| let root = crate::process::unique_temp_path(name, "dir"); | ||
| std::fs::create_dir_all(&root).unwrap(); | ||
| let mut app = PracticodeApp::new(root).unwrap(); | ||
| app.state.settings.ui_language = language.to_string(); | ||
| app | ||
| } | ||
| fn assert_learning_exit_clears_session( | ||
| name: &str, | ||
| leave: impl FnOnce(&mut PracticodeApp) -> Result<()>, | ||
| ) { | ||
| let mut app = learning_app(name); | ||
| app.handle_command("hint").unwrap(); | ||
| assert!(app.learning_session.assisted()); | ||
| leave(&mut app).unwrap(); | ||
| assert!(!app.learning_session.is_guided()); | ||
| assert!(!app.learning_session.assisted()); | ||
| app.handle_command("learn python").unwrap(); | ||
| assert!(!app.learning_session.assisted()); | ||
| } | ||
| #[test] | ||
@@ -353,3 +447,3 @@ fn update_check_refreshes_visible_checking_notice() { | ||
| app.update_rx = Some(rx); | ||
| app.write_text_output(UPDATE_CHECKING_TEXT); | ||
| app.write_text_output(ui_text("en", "update_checking")); | ||
@@ -360,2 +454,348 @@ app.check_update(); | ||
| } | ||
| #[test] | ||
| fn visible_update_check_uses_the_selected_locale() { | ||
| let mut app = localized_app("practicode-update-checking-locale", "ko"); | ||
| let (_tx, rx) = std::sync::mpsc::channel(); | ||
| app.update_rx = Some(rx); | ||
| app.show_update_notice(); | ||
| assert_eq!(app.output, ui_text("ko", "update_checking")); | ||
| } | ||
| #[test] | ||
| fn next_failure_heading_uses_the_selected_locale() { | ||
| let mut app = localized_app("practicode-next-failed-locale", "ja"); | ||
| std::fs::write(app.root.join("problem_bank.json"), "not json").unwrap(); | ||
| let (tx, rx) = std::sync::mpsc::channel(); | ||
| tx.send(TaskResult::Next { | ||
| output: String::new(), | ||
| old_problem: app.state.current_problem.clone(), | ||
| fallback_to_local: false, | ||
| }) | ||
| .unwrap(); | ||
| app.task_rx = Some(rx); | ||
| app.check_task(); | ||
| assert!( | ||
| app.output.starts_with(ui_text("ja", "next_failed")), | ||
| "{}", | ||
| app.output | ||
| ); | ||
| assert!(app.output.contains("parse"), "{}", app.output); | ||
| } | ||
| #[test] | ||
| fn profile_copy_renders_in_the_selected_locale() { | ||
| let app = localized_app("practicode-profile-locale", "ja"); | ||
| let profile = app.profile_text(); | ||
| for key in [ | ||
| "settings_title", | ||
| "settings_instructions", | ||
| "settings_code_language", | ||
| "settings_ai_provider", | ||
| "settings_ai_model", | ||
| "settings_model_load_hint", | ||
| "settings_problem_notes", | ||
| ] { | ||
| assert!(profile.contains(ui_text("ja", key)), "{key}: {profile}"); | ||
| } | ||
| assert!(!profile.contains("User profile"), "{profile}"); | ||
| assert!(!profile.contains("AI provider"), "{profile}"); | ||
| } | ||
| #[test] | ||
| fn model_status_renders_app_owned_copy_in_the_selected_locale() { | ||
| let app = localized_app("practicode-model-status-locale", "zh"); | ||
| let status = app.model_status_text(); | ||
| for key in [ | ||
| "settings_ai_provider", | ||
| "settings_ai_model", | ||
| "settings_ai_effort", | ||
| "model_use_default_model", | ||
| "model_use_default_effort", | ||
| "model_unavailable", | ||
| "model_custom_hint", | ||
| ] { | ||
| assert!(status.contains(ui_text("zh", key)), "{key}: {status}"); | ||
| } | ||
| assert!(!status.contains("AI provider"), "{status}"); | ||
| assert!(!status.contains("Provider model list"), "{status}"); | ||
| } | ||
| #[test] | ||
| fn notes_feedback_renders_in_the_selected_locale() { | ||
| let mut app = localized_app("practicode-notes-locale", "es"); | ||
| app.show_notes().unwrap(); | ||
| assert_eq!(app.output, ui_text("es", "notes_empty")); | ||
| app.append_note("prioriza límites").unwrap(); | ||
| assert_eq!( | ||
| app.output, | ||
| ui_text("es", "note_saved").replace("{path}", PROBLEM_NOTES_PATH) | ||
| ); | ||
| app.show_notes().unwrap(); | ||
| assert!( | ||
| app.output | ||
| .starts_with(&ui_text("es", "notes_title").replace("{path}", PROBLEM_NOTES_PATH)), | ||
| "{}", | ||
| app.output | ||
| ); | ||
| assert!(app.output.contains("prioriza límites"), "{}", app.output); | ||
| } | ||
| #[test] | ||
| fn busy_copy_and_elapsed_time_render_in_the_selected_locale() { | ||
| let root = crate::process::unique_temp_path("practicode-busy-locale", "dir"); | ||
| std::fs::create_dir_all(&root).unwrap(); | ||
| let mut app = PracticodeApp::new(root).unwrap(); | ||
| app.state.settings.ui_language = "ko".to_string(); | ||
| app.start_busy("ai", "codex"); | ||
| let output = output_text_content(&app); | ||
| let status = app.status_text(); | ||
| assert!(output.contains("codex가 생각 중"), "{output}"); | ||
| assert!(output.contains("0초"), "{output}"); | ||
| assert!(status.contains("codex가 생각 중"), "{status}"); | ||
| assert!(status.contains("0초"), "{status}"); | ||
| assert!(!output.contains("is thinking"), "{output}"); | ||
| assert!(!output.contains("0s"), "{output}"); | ||
| app.state.settings.ui_language = "ja".to_string(); | ||
| app.start_busy("next", ""); | ||
| let output = output_text_content(&app); | ||
| assert!(output.contains("次の問題を生成中"), "{output}"); | ||
| assert!(output.contains("0秒"), "{output}"); | ||
| assert!(!output.contains("Generating next problem"), "{output}"); | ||
| } | ||
| #[test] | ||
| fn background_generation_notices_render_in_the_selected_locale() { | ||
| let root = crate::process::unique_temp_path("practicode-generation-locale", "dir"); | ||
| std::fs::create_dir_all(&root).unwrap(); | ||
| let mut app = PracticodeApp::new(root.clone()).unwrap(); | ||
| app.state.settings.ui_language = "ko".to_string(); | ||
| finish_generation(&mut app, AiGenerationResult::Succeeded(String::new()), 0); | ||
| let generated = app.background_generation_status().unwrap(); | ||
| assert!(generated.contains("문제 1개"), "{generated}"); | ||
| assert!(!generated.contains("Generated"), "{generated}"); | ||
| finish_generation( | ||
| &mut app, | ||
| AiGenerationResult::Failed { | ||
| status: Some(7), | ||
| detail: "raw provider detail".to_string(), | ||
| }, | ||
| 0, | ||
| ); | ||
| let failed = app.background_generation_status().unwrap(); | ||
| assert!(failed.contains("백그라운드 생성에 실패"), "{failed}"); | ||
| assert!(failed.contains("raw provider detail"), "{failed}"); | ||
| assert!(failed.contains("문제 1개"), "{failed}"); | ||
| assert!(!failed.contains("Background generation failed"), "{failed}"); | ||
| app.state.settings.ui_language = "ja".to_string(); | ||
| let (tx, rx) = std::sync::mpsc::channel(); | ||
| app.generate_rx = Some(rx); | ||
| app.generate_started = Some(Instant::now()); | ||
| app.generate_notice = Some(GenerationNotice::Started); | ||
| let running = app.background_generation_status().unwrap(); | ||
| assert!(running.contains("バックグラウンド生成"), "{running}"); | ||
| assert!(running.contains("0秒"), "{running}"); | ||
| assert!(!running.contains("background generation"), "{running}"); | ||
| assert_eq!( | ||
| app.generation_notice_text(&GenerationNotice::Started), | ||
| "バックグラウンドで生成中です。" | ||
| ); | ||
| app.action_generate(""); | ||
| assert!(app.output.contains("重複した /generate"), "{}", app.output); | ||
| assert!(!app.output.contains("duplicate"), "{}", app.output); | ||
| drop(tx); | ||
| app.generate_rx = None; | ||
| let bank_len = app.bank.len(); | ||
| finish_generation( | ||
| &mut app, | ||
| AiGenerationResult::Succeeded(String::new()), | ||
| bank_len, | ||
| ); | ||
| let finished = app.background_generation_status().unwrap(); | ||
| assert!( | ||
| finished.contains("バックグラウンド生成が完了"), | ||
| "{finished}" | ||
| ); | ||
| assert!( | ||
| !finished.contains("Background generation finished"), | ||
| "{finished}" | ||
| ); | ||
| std::fs::write(root.join("problem_bank.json"), "not json").unwrap(); | ||
| let bank_len = app.bank.len(); | ||
| finish_generation( | ||
| &mut app, | ||
| AiGenerationResult::Succeeded(String::new()), | ||
| bank_len, | ||
| ); | ||
| let reload_failed = app.background_generation_status().unwrap(); | ||
| assert!( | ||
| reload_failed.contains("問題バンクを再読み込みできませんでした"), | ||
| "{reload_failed}" | ||
| ); | ||
| assert!(reload_failed.contains("parse"), "{reload_failed}"); | ||
| assert!( | ||
| !reload_failed.contains("bank reload failed"), | ||
| "{reload_failed}" | ||
| ); | ||
| finish_generation( | ||
| &mut app, | ||
| AiGenerationResult::Failed { | ||
| status: Some(9), | ||
| detail: "raw failed-generation detail".to_string(), | ||
| }, | ||
| bank_len, | ||
| ); | ||
| let failed_reload = app.background_generation_status().unwrap(); | ||
| assert!( | ||
| failed_reload.contains("バックグラウンド生成に失敗"), | ||
| "{failed_reload}" | ||
| ); | ||
| assert!( | ||
| failed_reload.contains("raw failed-generation detail"), | ||
| "{failed_reload}" | ||
| ); | ||
| assert!( | ||
| failed_reload.contains("問題バンクを再読み込みできませんでした"), | ||
| "{failed_reload}" | ||
| ); | ||
| assert!(failed_reload.contains("parse"), "{failed_reload}"); | ||
| } | ||
| #[test] | ||
| fn every_lesson_ai_command_marks_the_live_attempt_at_start() { | ||
| for (index, command) in ["hint", "hint one clue", "ask", "ask why", "ai explain this"] | ||
| .into_iter() | ||
| .enumerate() | ||
| { | ||
| let mut app = learning_app(&format!("practicode-ai-command-{index}")); | ||
| app.handle_command(command).unwrap(); | ||
| assert!(app.learning_session.assisted(), "{command}"); | ||
| assert!(app.task_rx.is_none(), "{command}"); | ||
| } | ||
| } | ||
| #[test] | ||
| fn every_manual_lesson_ai_command_marks_the_attempt_at_start() { | ||
| for (index, command) in ["hint", "hint one clue", "ask", "ask why", "ai explain this"] | ||
| .into_iter() | ||
| .enumerate() | ||
| { | ||
| let mut app = learning_app(&format!("practicode-manual-ai-command-{index}")); | ||
| app.handle_command("back").unwrap(); | ||
| assert!(!app.learning_session.is_guided()); | ||
| app.handle_command(command).unwrap(); | ||
| assert!(app.learning_session.assisted(), "{command}"); | ||
| assert!(app.task_rx.is_none(), "{command}"); | ||
| } | ||
| } | ||
| #[test] | ||
| fn lesson_ai_at_reflect_cannot_leak_into_the_next_item() { | ||
| let root = crate::process::unique_temp_path("practicode-ai-boundary", "dir"); | ||
| std::fs::create_dir_all(&root).unwrap(); | ||
| let bank = load_bank(&root).unwrap(); | ||
| let mut state = load_state(&root, &bank).unwrap(); | ||
| state.syntax_mastery.insert( | ||
| "python".to_string(), | ||
| HashMap::from([( | ||
| "py-output".to_string(), | ||
| crate::core::LessonMastery { | ||
| stage: crate::core::MasteryStage::Practiced, | ||
| review_due_at: 1, | ||
| attempts: 1, | ||
| }, | ||
| )]), | ||
| ); | ||
| save_state(&root, &state).unwrap(); | ||
| let mut app = PracticodeApp::new(root).unwrap(); | ||
| app.ai_spawn_disabled = true; | ||
| app.handle_command("learn python").unwrap(); | ||
| app.handle_command("next").unwrap(); | ||
| app.handle_command("next").unwrap(); | ||
| app.handle_command("ai explain this").unwrap(); | ||
| assert!(app.learning_session.assisted()); | ||
| app.learning_session.finish_judge(true); | ||
| assert!(!app.learning_session.assisted()); | ||
| app.handle_command("hint reflect").unwrap(); | ||
| assert!(!app.learning_session.assisted()); | ||
| app.handle_command("next").unwrap(); | ||
| assert_eq!(app.learning_session.current_lesson_id(), Some("py-input")); | ||
| assert!(!app.learning_session.assisted()); | ||
| } | ||
| #[test] | ||
| fn problem_ai_does_not_mark_a_suspended_lesson_attempt() { | ||
| let mut app = learning_app("practicode-ai-suspended-lesson"); | ||
| app.handle_command("home").unwrap(); | ||
| app.handle_command("ai explain this problem").unwrap(); | ||
| assert!(!app.learning_session.assisted()); | ||
| assert!(app.task_rx.is_none()); | ||
| } | ||
| #[test] | ||
| fn home_clears_the_assisted_learning_session() { | ||
| assert_learning_exit_clears_session("practicode-ai-home-boundary", |app| { | ||
| app.handle_command("home") | ||
| }); | ||
| } | ||
| #[test] | ||
| fn practice_clears_the_assisted_learning_session() { | ||
| assert_learning_exit_clears_session("practicode-ai-practice-boundary", |app| { | ||
| app.action_practice() | ||
| }); | ||
| } | ||
| #[test] | ||
| fn problem_list_clears_the_assisted_learning_session() { | ||
| assert_learning_exit_clears_session("practicode-ai-list-boundary", |app| { | ||
| app.handle_command("problems") | ||
| }); | ||
| } | ||
| #[test] | ||
| fn direct_problem_open_clears_the_assisted_learning_session() { | ||
| assert_learning_exit_clears_session("practicode-ai-open-boundary", |app| { | ||
| app.open_problem("1") | ||
| }); | ||
| } | ||
| #[test] | ||
| fn generation_clears_the_assisted_learning_session() { | ||
| assert_learning_exit_clears_session("practicode-ai-generate-boundary", |app| { | ||
| app.state.settings.ai_next_command = "true".to_string(); | ||
| app.start_background_generation(String::new()); | ||
| Ok(()) | ||
| }); | ||
| } | ||
| } |
+811
-139
@@ -8,14 +8,35 @@ use super::*; | ||
| let (done, total) = | ||
| syntax_progress_count(&self.state, &self.state.settings.language); | ||
| syntax_core_progress_count(&self.state, &self.state.settings.language); | ||
| let now = unix_time_now(); | ||
| let due = crate::core::due_syntax_lesson_count( | ||
| &self.state, | ||
| &self.state.settings.language, | ||
| now, | ||
| ); | ||
| let next = LearningSession::start(&self.state, &self.state.settings.language, now); | ||
| let lang = &self.state.settings.ui_language; | ||
| format!( | ||
| "Learning\n\nLanguage: {}\nProgress: {done}/{total}\n\n/run validates exercises\n/next moves to the next lesson", | ||
| syntax_language_name(&self.state.settings.language) | ||
| "{}\n\n{}: {}\n{}: {done}/{total}\n{}: {due}\n{}: {}", | ||
| ui_text(lang, "home_learn_choice"), | ||
| ui_text(lang, "progress_language"), | ||
| syntax_language_name(&self.state.settings.language), | ||
| ui_text(lang, "syntax_progress"), | ||
| ui_text(lang, "learning_due_reviews"), | ||
| ui_text(lang, "home_next_step"), | ||
| learning_step_label(lang, next.step()), | ||
| ) | ||
| } | ||
| HomeChoice::Problems => { | ||
| let lang = &self.state.settings.ui_language; | ||
| format!( | ||
| "Practice\n\nCurrent: {}\nDifficulty: {}\nStatus: {}\n\n/run judges submissions\n/next opens the next problem", | ||
| "{}\n\n{}: {}\n{}: {}\n{}: {}\n\n{}\n{}", | ||
| ui_text(lang, "home_practice_preview_title"), | ||
| ui_text(lang, "home_current"), | ||
| self.problem.id, | ||
| self.problem.difficulty, | ||
| self.problem_status(&self.problem) | ||
| ui_text(lang, "difficulty"), | ||
| localized_status(lang, &self.problem.difficulty), | ||
| ui_text(lang, "home_status"), | ||
| localized_status(lang, &self.problem_status(&self.problem)), | ||
| ui_text(lang, "home_practice_run"), | ||
| ui_text(lang, "home_practice_next"), | ||
| ) | ||
@@ -28,2 +49,21 @@ } | ||
| let size = frame.area(); | ||
| self.home_area = Rect::default(); | ||
| self.home_learn_area = Rect::default(); | ||
| self.home_problems_area = Rect::default(); | ||
| self.left_area = Rect::default(); | ||
| self.code_area = Rect::default(); | ||
| self.output_area = Rect::default(); | ||
| self.command_area = Rect::default(); | ||
| self.command_palette_area = Rect::default(); | ||
| let light = self.state.settings.theme == "light"; | ||
| if size.width < 60 || size.height < 16 { | ||
| frame.render_widget( | ||
| Paragraph::new(ui_text(&self.state.settings.ui_language, "resize_required")) | ||
| .style(Self::pane_style(light)) | ||
| .wrap(Wrap { trim: false }), | ||
| size, | ||
| ); | ||
| return; | ||
| } | ||
| let vertical = Layout::default() | ||
@@ -34,84 +74,131 @@ .direction(Direction::Vertical) | ||
| Constraint::Length(1), | ||
| Constraint::Length(3), | ||
| Constraint::Length(1), | ||
| ]) | ||
| .split(size); | ||
| let body = Layout::default() | ||
| let body = vertical[0]; | ||
| let panes = Layout::default() | ||
| .direction(Direction::Horizontal) | ||
| .constraints([Constraint::Percentage(58), Constraint::Percentage(42)]) | ||
| .split(vertical[0]); | ||
| if self.mode == AppMode::Home { | ||
| self.left_area = Rect::default(); | ||
| self.home_area = body[0]; | ||
| .constraints([Constraint::Percentage(42), Constraint::Percentage(58)]) | ||
| .split(body); | ||
| let wide = size.width >= 100; | ||
| if self.show_output { | ||
| self.output_area = body; | ||
| } else { | ||
| match self.mode { | ||
| AppMode::Home => { | ||
| self.home_area = if wide { panes[0] } else { body }; | ||
| if wide { | ||
| self.output_area = panes[1]; | ||
| } | ||
| } | ||
| AppMode::Learn if wide => { | ||
| self.left_area = panes[0]; | ||
| if self.learning_session.view() == LearningView::Result { | ||
| self.output_area = panes[1]; | ||
| } else { | ||
| self.code_area = panes[1]; | ||
| } | ||
| } | ||
| AppMode::Learn => match self.learning_session.view() { | ||
| LearningView::Lesson => self.left_area = body, | ||
| LearningView::Code => self.code_area = body, | ||
| LearningView::Result => self.output_area = body, | ||
| }, | ||
| AppMode::Problems if wide => { | ||
| self.left_area = panes[0]; | ||
| self.code_area = panes[1]; | ||
| } | ||
| AppMode::Problems => match self.practice_view { | ||
| PracticeView::Problem => self.left_area = body, | ||
| PracticeView::Code => self.code_area = body, | ||
| }, | ||
| } | ||
| } | ||
| if self.mode == AppMode::Home && !self.show_output { | ||
| let choices = Layout::default() | ||
| .direction(Direction::Vertical) | ||
| .constraints([ | ||
| Constraint::Length(7), | ||
| Constraint::Length(7), | ||
| Constraint::Min(1), | ||
| ]) | ||
| .split(body[0]); | ||
| .constraints(if wide { | ||
| [ | ||
| Constraint::Length(7), | ||
| Constraint::Length(7), | ||
| Constraint::Min(1), | ||
| ] | ||
| } else { | ||
| [ | ||
| Constraint::Length(6), | ||
| Constraint::Length(6), | ||
| Constraint::Min(1), | ||
| ] | ||
| }) | ||
| .split(self.home_area); | ||
| self.home_learn_area = choices[0]; | ||
| self.home_problems_area = choices[1]; | ||
| } else if self.show_output { | ||
| self.left_area = Rect::default(); | ||
| } else { | ||
| self.left_area = body[0]; | ||
| } | ||
| let right_panes = if !self.show_output | ||
| && self.mode == AppMode::Learn | ||
| && !self.learn_result.is_empty() | ||
| && body[1].height >= 6 | ||
| { | ||
| let result_height = (body[1].height / 3).clamp(3, 7); | ||
| let panes = Layout::default() | ||
| .direction(Direction::Vertical) | ||
| .constraints([Constraint::Min(3), Constraint::Length(result_height)]) | ||
| .split(body[1]); | ||
| Some((panes[0], panes[1])) | ||
| } else { | ||
| None | ||
| }; | ||
| self.code_area = if self.show_output || self.mode == AppMode::Home { | ||
| Rect::default() | ||
| } else if let Some((code_area, _)) = right_panes { | ||
| code_area | ||
| } else { | ||
| body[1] | ||
| }; | ||
| self.output_area = if self.show_output { | ||
| vertical[0] | ||
| } else if self.mode == AppMode::Home { | ||
| body[1] | ||
| } else if let Some((_, result_area)) = right_panes { | ||
| result_area | ||
| } else { | ||
| self.code_area | ||
| }; | ||
| self.command_area = vertical[2]; | ||
| let light = self.state.settings.theme == "light"; | ||
| if !self.show_output { | ||
| if self.mode == AppMode::Home { | ||
| let left = Paragraph::new(self.home_text()) | ||
| .style(Self::pane_style(light)) | ||
| .block(Self::block( | ||
| ui_text(&self.state.settings.ui_language, "home"), | ||
| light, | ||
| self.focus == Focus::Home, | ||
| )) | ||
| .wrap(Wrap { trim: false }); | ||
| frame.render_widget(left, body[0]); | ||
| for (area, choice, title, description) in [ | ||
| ( | ||
| self.home_learn_area, | ||
| HomeChoice::Learn, | ||
| ui_text(&self.state.settings.ui_language, "home_learn_choice"), | ||
| ui_text(&self.state.settings.ui_language, "home_learn_description"), | ||
| ), | ||
| ( | ||
| self.home_problems_area, | ||
| HomeChoice::Problems, | ||
| ui_text(&self.state.settings.ui_language, "home_practice_choice"), | ||
| ui_text( | ||
| &self.state.settings.ui_language, | ||
| "home_practice_description", | ||
| ), | ||
| ), | ||
| ] { | ||
| frame.render_widget( | ||
| Paragraph::new(description) | ||
| .style(Self::pane_style(light)) | ||
| .block(Self::block( | ||
| title, | ||
| light, | ||
| self.focus == Focus::Home && self.home_choice == choice, | ||
| )) | ||
| .wrap(Wrap { trim: false }), | ||
| area, | ||
| ); | ||
| } | ||
| let help_area = Rect::new( | ||
| self.home_area.x, | ||
| self.home_problems_area.bottom(), | ||
| self.home_area.width, | ||
| self.home_area | ||
| .bottom() | ||
| .saturating_sub(self.home_problems_area.bottom()), | ||
| ); | ||
| frame.render_widget( | ||
| Paragraph::new(ui_text(&self.state.settings.ui_language, "home_help")) | ||
| .style(Self::pane_style(light)), | ||
| help_area, | ||
| ); | ||
| let right = Paragraph::new(self.home_preview_text()) | ||
| .style(Self::pane_style(light)) | ||
| .block(Self::block( | ||
| ui_text(&self.state.settings.ui_language, "home_preview"), | ||
| if self.output_area.width > 0 { | ||
| let right = Paragraph::new(self.home_preview_text()) | ||
| .style(Self::pane_style(light)) | ||
| .block(Self::block( | ||
| ui_text(&self.state.settings.ui_language, "home_preview"), | ||
| light, | ||
| false, | ||
| )) | ||
| .wrap(Wrap { trim: false }); | ||
| frame.render_widget(right, self.output_area); | ||
| } | ||
| } else if self.left_area.width > 0 { | ||
| let left = if self.mode == AppMode::Learn { | ||
| markdown_text( | ||
| &self.output, | ||
| light, | ||
| false, | ||
| )) | ||
| .wrap(Wrap { trim: false }); | ||
| frame.render_widget(right, body[1]); | ||
| } else { | ||
| let left = if self.mode == AppMode::Learn { | ||
| markdown_text(&self.output, light) | ||
| ui_text(&self.state.settings.ui_language, "empty_value"), | ||
| ) | ||
| } else { | ||
@@ -121,3 +208,3 @@ problem_view::render(&self.problem, &self.state.settings.ui_language, light) | ||
| let title = if self.mode == AppMode::Learn { | ||
| ui_text(&self.state.settings.ui_language, "syntax") | ||
| ui_text(&self.state.settings.ui_language, "learning_view_lesson") | ||
| } else { | ||
@@ -131,3 +218,3 @@ ui_text(&self.state.settings.ui_language, "problem") | ||
| .scroll((self.left_scroll, 0)); | ||
| frame.render_widget(problem, body[0]); | ||
| frame.render_widget(problem, self.left_area); | ||
| } | ||
@@ -159,3 +246,3 @@ } | ||
| frame.render_widget(output, self.output_area); | ||
| } else if self.mode != AppMode::Home { | ||
| } else if self.mode != AppMode::Home && self.code_area.width > 0 { | ||
| let code = self | ||
@@ -165,5 +252,14 @@ .editor | ||
| let title = if self.mode == AppMode::Learn { | ||
| format!("exercise.{}", ext_for(&self.state.settings.language)) | ||
| format!( | ||
| "{} · {}.{}", | ||
| ui_text(&self.state.settings.ui_language, "learning_view_code"), | ||
| ui_text(&self.state.settings.ui_language, "pane_exercise"), | ||
| ext_for(&self.state.settings.language) | ||
| ) | ||
| } else { | ||
| format!("solution.{}", ext_for(&self.state.settings.language)) | ||
| format!( | ||
| "{}.{}", | ||
| ui_text(&self.state.settings.ui_language, "pane_solution"), | ||
| ext_for(&self.state.settings.language) | ||
| ) | ||
| }; | ||
@@ -174,19 +270,21 @@ let code = Paragraph::new(code) | ||
| frame.render_widget(code, self.code_area); | ||
| if self.mode == AppMode::Learn && !self.learn_result.is_empty() && right_panes.is_some() | ||
| { | ||
| let result = Paragraph::new(self.learn_result.clone()) | ||
| .style(Self::pane_style(light)) | ||
| .block(Self::block( | ||
| ui_text(&self.state.settings.ui_language, "exercise_result"), | ||
| light, | ||
| false, | ||
| )) | ||
| .wrap(Wrap { trim: false }) | ||
| .scroll((self.output_scroll, 0)); | ||
| frame.render_widget(result, self.output_area); | ||
| } | ||
| } else if self.mode == AppMode::Learn && self.output_area.width > 0 { | ||
| let text = if self.learn_result.is_empty() { | ||
| ui_text(&self.state.settings.ui_language, "result_empty").to_string() | ||
| } else { | ||
| self.learn_result.clone() | ||
| }; | ||
| let result = Paragraph::new(text) | ||
| .style(Self::pane_style(light)) | ||
| .block(Self::block( | ||
| ui_text(&self.state.settings.ui_language, "learning_view_result"), | ||
| light, | ||
| self.focus == Focus::Output, | ||
| )) | ||
| .wrap(Wrap { trim: false }) | ||
| .scroll((self.output_scroll, 0)); | ||
| frame.render_widget(result, self.output_area); | ||
| } | ||
| let status = Paragraph::new(self.status_text()).style(if light { | ||
| let status = Paragraph::new(self.status_text_for_width(size.width)).style(if light { | ||
| Style::default() | ||
@@ -204,14 +302,19 @@ .fg(Color::Blue) | ||
| let command_text = if self.focus == Focus::Command || !self.command.is_empty() { | ||
| let command_text = if self.focus == Focus::Command { | ||
| format!( | ||
| "[{}] {}", | ||
| ui_text(&self.state.settings.ui_language, "focus_active"), | ||
| self.command | ||
| ) | ||
| } else if !self.command.is_empty() { | ||
| self.command.clone() | ||
| } else { | ||
| ui_text(&self.state.settings.ui_language, "command_placeholder").to_string() | ||
| format!( | ||
| "{}: {}", | ||
| ui_text(&self.state.settings.ui_language, "command"), | ||
| ui_text(&self.state.settings.ui_language, "command_placeholder") | ||
| ) | ||
| }; | ||
| let command = Paragraph::new(command_text) | ||
| .style(Self::pane_style(light)) | ||
| .block(Self::block( | ||
| ui_text(&self.state.settings.ui_language, "command"), | ||
| light, | ||
| self.focus == Focus::Command, | ||
| )) | ||
| .wrap(Wrap { trim: false }); | ||
@@ -224,3 +327,6 @@ frame.render_widget(command, vertical[2]); | ||
| pub(super) fn wants_mouse_capture(&self) -> bool { | ||
| !self.show_output | ||
| !(self.show_output | ||
| || (self.mode == AppMode::Learn | ||
| && self.learning_session.view() == LearningView::Result | ||
| && self.focus != Focus::Command)) | ||
| } | ||
@@ -290,3 +396,8 @@ | ||
| let mut lines = vec![Line::from(Span::styled( | ||
| format!("{}{} {}s", self.busy_body, self.busy_dots(), elapsed), | ||
| format!( | ||
| "{}{} {}", | ||
| self.busy_text(), | ||
| self.busy_dots(), | ||
| self.elapsed_text(elapsed) | ||
| ), | ||
| title_style, | ||
@@ -322,20 +433,44 @@ ))]; | ||
| if self.output_is_markdown { | ||
| return markdown_text(&self.output, light); | ||
| return markdown_text( | ||
| &self.output, | ||
| light, | ||
| ui_text(&self.state.settings.ui_language, "empty_value"), | ||
| ); | ||
| } | ||
| let output = self.output.clone(); | ||
| let mut lines = Vec::new(); | ||
| let pass = format!( | ||
| "{} ", | ||
| ui_text(&self.state.settings.ui_language, "result_pass") | ||
| ); | ||
| let fail = format!( | ||
| "{} ", | ||
| ui_text(&self.state.settings.ui_language, "result_fail") | ||
| ); | ||
| let case = format!( | ||
| "{} ", | ||
| ui_text(&self.state.settings.ui_language, "judge_case") | ||
| ); | ||
| for line in output.lines() { | ||
| if line.is_empty() { | ||
| lines.push(Line::default()); | ||
| } else if line.starts_with("PASS ") | ||
| || line.starts_with("FAIL ") | ||
| || line.starts_with("Case ") | ||
| || line.starts_with("Next:") | ||
| || line.starts_with("Fix:") | ||
| } else if line.starts_with(&pass) | ||
| || line.starts_with(&fail) | ||
| || line.starts_with(&case) | ||
| || line == ui_text(&self.state.settings.ui_language, "run_pass_next") | ||
| || line == ui_text(&self.state.settings.ui_language, "run_fail_next") | ||
| { | ||
| lines.push(Line::from(Span::styled(line.to_string(), title_style))); | ||
| } else if matches!( | ||
| line, | ||
| "Input" | "Expected" | "Got" | "Stdout" | "Stderr" | "Compile" | "Error" | ||
| ) { | ||
| } else if [ | ||
| "judge_input", | ||
| "judge_expected", | ||
| "judge_got", | ||
| "judge_stdout", | ||
| "judge_stderr", | ||
| "judge_compile", | ||
| "judge_error", | ||
| ] | ||
| .into_iter() | ||
| .any(|key| line == ui_text(&self.state.settings.ui_language, key)) | ||
| { | ||
| lines.push(Line::from(Span::styled(line.to_string(), label_style))); | ||
@@ -354,3 +489,3 @@ } else if line.starts_with(" ") { | ||
| pub(super) fn draw_command_palette(&self, frame: &mut Frame, command_area: Rect) { | ||
| pub(super) fn draw_command_palette(&mut self, frame: &mut Frame, command_area: Rect) { | ||
| let suggestions = self.command_suggestions(); | ||
@@ -360,3 +495,9 @@ if suggestions.is_empty() || command_area.y < 3 { | ||
| } | ||
| let height = ((suggestions.len() + 3) as u16).min(14).min(command_area.y); | ||
| let show_ai_disclosure = suggestions | ||
| .iter() | ||
| .any(|hint| matches!(hint.desc_key, "cmd_hint" | "cmd_ask" | "cmd_ai")); | ||
| let disclosure_rows = usize::from(show_ai_disclosure); | ||
| let height = ((suggestions.len() + disclosure_rows + 3) as u16) | ||
| .min(14) | ||
| .min(command_area.y); | ||
| let area = Rect::new( | ||
@@ -368,4 +509,5 @@ command_area.x, | ||
| ); | ||
| self.command_palette_area = area; | ||
| let selected = self.command_palette_cursor.min(suggestions.len() - 1); | ||
| let visible = height.saturating_sub(2) as usize; | ||
| let visible = height.saturating_sub(3 + disclosure_rows as u16).max(1) as usize; | ||
| let start = selected.saturating_sub(visible.saturating_sub(1)); | ||
@@ -386,2 +528,7 @@ let mut lines = suggestions | ||
| .collect::<Vec<_>>(); | ||
| if show_ai_disclosure { | ||
| lines.push( | ||
| ui_text(&self.state.settings.ui_language, "ai_context_disclosure").to_string(), | ||
| ); | ||
| } | ||
| lines.push(ui_text(&self.state.settings.ui_language, "palette_hint").to_string()); | ||
@@ -466,8 +613,12 @@ frame.render_widget(Clear, area); | ||
| let before = prefix(&self.command, self.command_cursor); | ||
| let marker = format!( | ||
| "[{}] ", | ||
| ui_text(&self.state.settings.ui_language, "focus_active") | ||
| ); | ||
| let x = command_area | ||
| .x | ||
| .saturating_add(1) | ||
| .saturating_add(display_width(&marker) as u16) | ||
| .saturating_add(display_width(&before) as u16) | ||
| .min(command_area.right().saturating_sub(2)); | ||
| frame.set_cursor_position(Position::new(x, command_area.y.saturating_add(1))); | ||
| .min(command_area.right().saturating_sub(1)); | ||
| frame.set_cursor_position(Position::new(x, command_area.y)); | ||
| } | ||
@@ -489,3 +640,3 @@ Focus::Code if !self.show_output => { | ||
| fn markdown_text(markdown: &str, light: bool) -> Text<'static> { | ||
| fn markdown_text(markdown: &str, light: bool, empty_label: &str) -> Text<'static> { | ||
| let title_style = if light { | ||
@@ -530,3 +681,3 @@ Style::default() | ||
| if in_fence { | ||
| push_markdown_code_block(&mut lines, &code_lines, code_style); | ||
| push_markdown_code_block(&mut lines, &code_lines, code_style, empty_label); | ||
| code_lines.clear(); | ||
@@ -557,3 +708,3 @@ } | ||
| if in_fence { | ||
| push_markdown_code_block(&mut lines, &code_lines, code_style); | ||
| push_markdown_code_block(&mut lines, &code_lines, code_style, empty_label); | ||
| } | ||
@@ -567,2 +718,3 @@ Text::from(lines) | ||
| code_style: Style, | ||
| empty_label: &str, | ||
| ) { | ||
@@ -572,3 +724,3 @@ if code_lines.iter().all(|line| line.is_empty()) { | ||
| Span::raw(" "), | ||
| Span::styled(" <empty> ".to_string(), code_style), | ||
| Span::styled(format!(" {empty_label} "), code_style), | ||
| ])); | ||
@@ -589,2 +741,3 @@ return; | ||
| use super::*; | ||
| use crate::process::which; | ||
| use crossterm::event::{ | ||
@@ -613,3 +766,394 @@ KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind, | ||
| fn status_row_text(terminal: &Terminal<TestBackend>) -> String { | ||
| let buffer = terminal.backend().buffer(); | ||
| let row = buffer.area.height - 2; | ||
| (0..buffer.area.width) | ||
| .map(|x| buffer[(x, row)].symbol()) | ||
| .collect() | ||
| } | ||
| fn draw_at(app: &mut PracticodeApp, width: u16, height: u16) -> Terminal<TestBackend> { | ||
| let backend = TestBackend::new(width, height); | ||
| let mut terminal = Terminal::new(backend).unwrap(); | ||
| terminal.draw(|frame| app.draw(frame)).unwrap(); | ||
| terminal | ||
| } | ||
| #[test] | ||
| fn learning_layout_switches_at_the_exact_width_boundary() { | ||
| for (width, height) in [(60, 16), (80, 24), (99, 30)] { | ||
| let mut app = PracticodeApp::new(tmp_root(&format!("narrow-{width}"))).unwrap(); | ||
| app.handle_command("learn python").unwrap(); | ||
| let _terminal = draw_at(&mut app, width, height); | ||
| assert_eq!( | ||
| app.left_area, | ||
| Rect::new(0, 0, width, height - 2), | ||
| "{width}x{height}" | ||
| ); | ||
| assert_eq!(app.output_area, Rect::default(), "{width}x{height}"); | ||
| assert_eq!(app.code_area, Rect::default(), "{width}x{height}"); | ||
| assert_eq!(app.command_area.height, 1); | ||
| if width == 80 { | ||
| assert!(app.left_area.width.saturating_sub(2) > 60); | ||
| } | ||
| } | ||
| for (width, height) in [(100, 30), (140, 40)] { | ||
| let mut app = PracticodeApp::new(tmp_root(&format!("wide-{width}"))).unwrap(); | ||
| app.handle_command("learn python").unwrap(); | ||
| let _terminal = draw_at(&mut app, width, height); | ||
| assert!(app.left_area.width > 0, "{width}x{height}"); | ||
| assert!(app.code_area.width > 0, "{width}x{height}"); | ||
| assert!( | ||
| app.code_area.width > app.left_area.width, | ||
| "{width}x{height}" | ||
| ); | ||
| assert!(app.left_area.right() <= app.code_area.x, "{width}x{height}"); | ||
| assert_eq!(app.left_area.y, app.code_area.y); | ||
| assert_eq!(app.left_area.height, app.code_area.height); | ||
| assert_eq!(app.command_area.height, 1); | ||
| } | ||
| } | ||
| #[test] | ||
| fn undersized_terminal_renders_only_the_localized_resize_message() { | ||
| let mut app = PracticodeApp::new(tmp_root("resize-ko")).unwrap(); | ||
| app.set_ui_language("ko").unwrap(); | ||
| let _terminal = draw_at(&mut app, 60, 16); | ||
| for (width, height) in [(59, 15), (59, 16), (60, 15)] { | ||
| let terminal = draw_at(&mut app, width, height); | ||
| let text = buffer_text(&terminal); | ||
| let compact = text.replace(' ', ""); | ||
| assert!( | ||
| compact.contains("터미널크기를60x16이상으로조정하세요."), | ||
| "{width}x{height}: {text}" | ||
| ); | ||
| assert!(!text.contains("PRACTICODE"), "{width}x{height}: {text}"); | ||
| assert_eq!(app.home_area, Rect::default()); | ||
| assert_eq!(app.home_learn_area, Rect::default()); | ||
| assert_eq!(app.home_problems_area, Rect::default()); | ||
| assert_eq!(app.left_area, Rect::default()); | ||
| assert_eq!(app.code_area, Rect::default()); | ||
| assert_eq!(app.output_area, Rect::default()); | ||
| assert_eq!(app.command_area, Rect::default()); | ||
| } | ||
| app.handle_key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)) | ||
| .unwrap(); | ||
| assert!(app.should_quit); | ||
| } | ||
| #[test] | ||
| fn narrow_f6_selects_one_full_width_learning_view() { | ||
| let mut app = PracticodeApp::new(tmp_root("narrow-f6")).unwrap(); | ||
| app.handle_command("learn python").unwrap(); | ||
| let _terminal = draw_at(&mut app, 80, 24); | ||
| assert_eq!(app.left_area, Rect::new(0, 0, 80, 22)); | ||
| app.handle_key(KeyEvent::new(KeyCode::F(6), KeyModifiers::NONE)) | ||
| .unwrap(); | ||
| let _terminal = draw_at(&mut app, 80, 24); | ||
| assert_eq!(app.code_area, Rect::new(0, 0, 80, 22)); | ||
| assert_eq!(app.left_area, Rect::default()); | ||
| app.handle_key(KeyEvent::new(KeyCode::F(6), KeyModifiers::NONE)) | ||
| .unwrap(); | ||
| let terminal = draw_at(&mut app, 80, 24); | ||
| assert_eq!(app.left_area, Rect::default()); | ||
| assert_eq!(app.code_area, Rect::default()); | ||
| assert_eq!(app.output_area, Rect::new(0, 0, 80, 22)); | ||
| assert!(buffer_text(&terminal).contains("No result yet.")); | ||
| assert!(!app.wants_mouse_capture()); | ||
| assert!(app.status_text().contains("drag select to copy")); | ||
| app.focus_command(); | ||
| assert!(app.wants_mouse_capture()); | ||
| assert!(app.status_text().contains("Enter submit | Esc cancel")); | ||
| app.handle_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)) | ||
| .unwrap(); | ||
| app.handle_key(KeyEvent::new(KeyCode::F(6), KeyModifiers::NONE)) | ||
| .unwrap(); | ||
| let _terminal = draw_at(&mut app, 80, 24); | ||
| assert_eq!(app.left_area, Rect::new(0, 0, 80, 22)); | ||
| assert_eq!(app.code_area, Rect::default()); | ||
| assert_eq!(app.output_area, Rect::default()); | ||
| } | ||
| #[test] | ||
| fn command_palette_overlays_the_body_and_keeps_a_one_row_input() { | ||
| let mut app = PracticodeApp::new(tmp_root("palette-overlay")).unwrap(); | ||
| app.handle_command("learn python").unwrap(); | ||
| let _terminal = draw_at(&mut app, 80, 24); | ||
| let body = app.code_area; | ||
| assert_eq!(app.command_area.height, 1); | ||
| app.focus_command(); | ||
| let terminal = draw_at(&mut app, 80, 24); | ||
| assert_eq!(app.code_area, body); | ||
| assert_eq!(app.command_area.height, 1); | ||
| assert!(buffer_text(&terminal).contains("Commands")); | ||
| assert!( | ||
| buffer_text(&terminal).contains("up/down select | Enter run | Esc cancel"), | ||
| "palette hint was clipped" | ||
| ); | ||
| app.handle_mouse(MouseEvent { | ||
| kind: MouseEventKind::Down(MouseButton::Left), | ||
| column: 2, | ||
| row: 10, | ||
| modifiers: KeyModifiers::NONE, | ||
| }) | ||
| .unwrap(); | ||
| assert_eq!(app.focus, Focus::Command); | ||
| } | ||
| #[test] | ||
| fn ai_palette_disclosure_is_fully_visible_at_supported_widths() { | ||
| for language in UI_LANGUAGES { | ||
| for command in ["/hint", "/ask"] { | ||
| for width in [60, 80, 100] { | ||
| let mut app = PracticodeApp::new(tmp_root(&format!( | ||
| "ai-disclosure-{language}-{command}-{width}" | ||
| ))) | ||
| .unwrap(); | ||
| app.set_ui_language(language).unwrap(); | ||
| app.focus_command(); | ||
| app.command = command.to_string(); | ||
| app.command_cursor = char_len(command); | ||
| let terminal = draw_at(&mut app, width, 24); | ||
| let rendered = buffer_text(&terminal).replace(' ', ""); | ||
| let expected = ui_text(language, "ai_context_disclosure").replace(' ', ""); | ||
| assert!(!expected.is_empty(), "{language}: missing disclosure copy"); | ||
| assert!( | ||
| rendered.contains(&expected), | ||
| "{language} {command} {width}: {rendered}" | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| #[test] | ||
| fn narrow_home_cards_match_their_mouse_hitboxes_in_long_locales() { | ||
| for language in ["ko", "es"] { | ||
| let mut app = PracticodeApp::new(tmp_root(&format!("home-cards-{language}"))).unwrap(); | ||
| app.set_ui_language(language).unwrap(); | ||
| app.action_home().unwrap(); | ||
| let terminal = draw_at(&mut app, 60, 16); | ||
| let buffer = terminal.backend().buffer(); | ||
| assert_eq!( | ||
| buffer[(app.home_learn_area.x, app.home_learn_area.y)].symbol(), | ||
| "┌" | ||
| ); | ||
| assert_eq!( | ||
| buffer[(app.home_problems_area.x, app.home_problems_area.y)].symbol(), | ||
| "┌" | ||
| ); | ||
| assert!(app.home_learn_area.bottom() <= app.home_problems_area.y); | ||
| app.handle_mouse(MouseEvent { | ||
| kind: MouseEventKind::Down(MouseButton::Left), | ||
| column: app.home_problems_area.x + 2, | ||
| row: app.home_problems_area.y + 2, | ||
| modifiers: KeyModifiers::NONE, | ||
| }) | ||
| .unwrap(); | ||
| assert_eq!(app.mode, AppMode::Problems, "{language}"); | ||
| } | ||
| } | ||
| #[test] | ||
| fn wide_home_cards_match_their_mouse_hitboxes_in_all_locales() { | ||
| for language in UI_LANGUAGES { | ||
| let mut app = | ||
| PracticodeApp::new(tmp_root(&format!("wide-home-cards-{language}"))).unwrap(); | ||
| app.set_ui_language(language).unwrap(); | ||
| app.action_home().unwrap(); | ||
| let terminal = draw_at(&mut app, 140, 30); | ||
| let buffer = terminal.backend().buffer(); | ||
| assert_eq!( | ||
| buffer[(app.home_problems_area.x, app.home_problems_area.y)].symbol(), | ||
| "┌", | ||
| "{language}" | ||
| ); | ||
| app.handle_mouse(MouseEvent { | ||
| kind: MouseEventKind::Down(MouseButton::Left), | ||
| column: app.home_problems_area.x + 2, | ||
| row: app.home_problems_area.y + 2, | ||
| modifiers: KeyModifiers::NONE, | ||
| }) | ||
| .unwrap(); | ||
| assert_eq!(app.mode, AppMode::Problems, "{language}"); | ||
| } | ||
| } | ||
| #[test] | ||
| fn narrow_practice_opens_on_problem_and_f6_toggles_full_width_panes() { | ||
| let mut app = PracticodeApp::new(tmp_root("narrow-practice-toggle")).unwrap(); | ||
| app.action_practice().unwrap(); | ||
| let _terminal = draw_at(&mut app, 60, 16); | ||
| assert_eq!(app.left_area, Rect::new(0, 0, 60, 14)); | ||
| assert_eq!(app.code_area, Rect::default()); | ||
| app.handle_key(KeyEvent::new(KeyCode::F(6), KeyModifiers::NONE)) | ||
| .unwrap(); | ||
| let _terminal = draw_at(&mut app, 60, 16); | ||
| assert_eq!(app.left_area, Rect::default()); | ||
| assert_eq!(app.code_area, Rect::new(0, 0, 60, 14)); | ||
| app.handle_key(KeyEvent::new(KeyCode::F(6), KeyModifiers::NONE)) | ||
| .unwrap(); | ||
| let _terminal = draw_at(&mut app, 60, 16); | ||
| assert_eq!(app.left_area, Rect::new(0, 0, 60, 14)); | ||
| assert_eq!(app.code_area, Rect::default()); | ||
| } | ||
| #[test] | ||
| fn narrow_problem_scroll_uses_wrapped_visual_rows() { | ||
| let mut app = PracticodeApp::new(tmp_root("wrapped-problem-scroll")).unwrap(); | ||
| app.problem.statement.insert( | ||
| "en".to_string(), | ||
| "Read every wrapped word before editing. ".repeat(80), | ||
| ); | ||
| app.action_practice().unwrap(); | ||
| let _terminal = draw_at(&mut app, 60, 16); | ||
| for _ in 0..3 { | ||
| app.handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE)) | ||
| .unwrap(); | ||
| } | ||
| assert!( | ||
| app.left_scroll > 10, | ||
| "scroll stopped at {}", | ||
| app.left_scroll | ||
| ); | ||
| } | ||
| #[test] | ||
| fn command_cursor_stays_on_the_single_input_row_with_hangul() { | ||
| let mut app = PracticodeApp::new(tmp_root("command-cursor-ko")).unwrap(); | ||
| app.set_ui_language("ko").unwrap(); | ||
| app.focus_command(); | ||
| for char in "ㅇㅏㄴ".chars() { | ||
| app.insert_command_char(char); | ||
| } | ||
| let terminal = draw_at(&mut app, 60, 16); | ||
| let cursor = terminal.backend().cursor_position(); | ||
| assert_eq!(cursor.y, app.command_area.y); | ||
| assert!(cursor.x < app.command_area.right()); | ||
| } | ||
| #[test] | ||
| fn supported_locales_and_themes_render_at_boundary_sizes_without_panics() { | ||
| for language in UI_LANGUAGES { | ||
| for theme in THEMES { | ||
| let mut app = | ||
| PracticodeApp::new(tmp_root(&format!("matrix-{language}-{theme}"))).unwrap(); | ||
| app.set_ui_language(language).unwrap(); | ||
| app.set_theme(theme).unwrap(); | ||
| app.action_learn("python").unwrap(); | ||
| for (width, height) in [(60, 16), (80, 24), (100, 30), (140, 40)] { | ||
| let _terminal = draw_at(&mut app, width, height); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| #[test] | ||
| fn compact_learning_status_keeps_every_primary_action_visible() { | ||
| for language in UI_LANGUAGES { | ||
| for width in [60, 80, 99, 100] { | ||
| let mut app = | ||
| PracticodeApp::new(tmp_root(&format!("compact-status-{language}-{width}"))) | ||
| .unwrap(); | ||
| app.set_ui_language(language).unwrap(); | ||
| app.action_learn("python").unwrap(); | ||
| let terminal = draw_at(&mut app, width, 24); | ||
| let row = terminal.backend().buffer().area.height - 2; | ||
| let rendered = (0..width) | ||
| .map(|x| terminal.backend().buffer()[(x, row)].symbol()) | ||
| .collect::<String>(); | ||
| for action in ["/next", "F5", "F6", "F1"] { | ||
| assert!( | ||
| rendered.contains(action), | ||
| "{language} {width}: missing {action} in {rendered:?}" | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| #[test] | ||
| fn compact_practice_status_keeps_navigation_and_judging_visible() { | ||
| for language in UI_LANGUAGES { | ||
| let mut app = | ||
| PracticodeApp::new(tmp_root(&format!("compact-practice-status-{language}"))) | ||
| .unwrap(); | ||
| app.set_ui_language(language).unwrap(); | ||
| app.action_practice().unwrap(); | ||
| let terminal = draw_at(&mut app, 60, 24); | ||
| let row = terminal.backend().buffer().area.height - 2; | ||
| let rendered = (0..60) | ||
| .map(|x| terminal.backend().buffer()[(x, row)].symbol()) | ||
| .collect::<String>(); | ||
| for action in ["F1", "F6", "/run", "/next"] { | ||
| assert!( | ||
| rendered.contains(action), | ||
| "{language}: missing {action} in {rendered:?}" | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| #[test] | ||
| fn overlay_status_takes_priority_over_mode_at_narrow_and_wide_widths() { | ||
| for language in UI_LANGUAGES { | ||
| for width in [60, 121] { | ||
| let cases = [ | ||
| ("help", "hint_output"), | ||
| ("profile", "hint_settings"), | ||
| ("problems", "hint_list"), | ||
| ("note", "hint_notes"), | ||
| ]; | ||
| for (command, hint_key) in cases { | ||
| let mut app = PracticodeApp::new(tmp_root(&format!( | ||
| "overlay-status-{language}-{width}-{command}" | ||
| ))) | ||
| .unwrap(); | ||
| app.set_ui_language(language).unwrap(); | ||
| app.action_learn("python").unwrap(); | ||
| app.handle_command(command).unwrap(); | ||
| let terminal = draw_at(&mut app, width, 24); | ||
| let rendered = status_row_text(&terminal).replace(' ', ""); | ||
| let expected = ui_text(language, hint_key).replace(' ', ""); | ||
| assert!( | ||
| rendered.contains(&expected), | ||
| "{language} {width} {command}: {rendered}" | ||
| ); | ||
| assert!( | ||
| !rendered.contains("F5"), | ||
| "{language} {width} {command}: mode hint leaked: {rendered}" | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| #[test] | ||
| fn output_uses_full_body_so_terminal_drag_selection_has_no_side_pane() { | ||
@@ -623,3 +1167,3 @@ let mut app = PracticodeApp::new(tmp_root("full-output")).unwrap(); | ||
| assert_eq!(app.output_area, Rect::new(0, 0, 80, 20)); | ||
| assert_eq!(app.output_area, Rect::new(0, 0, 80, 22)); | ||
| assert_eq!(app.code_area, Rect::default()); | ||
@@ -629,5 +1173,7 @@ } | ||
| #[test] | ||
| fn learn_result_keeps_lesson_and_splits_right_pane() { | ||
| fn narrow_learn_result_uses_the_full_body() { | ||
| let mut app = PracticodeApp::new(tmp_root("learn-result-split")).unwrap(); | ||
| app.handle_command("learn python").unwrap(); | ||
| app.handle_command("next").unwrap(); | ||
| app.handle_command("next").unwrap(); | ||
| app.handle_command("run").unwrap(); | ||
@@ -639,13 +1185,63 @@ | ||
| assert!(app.output.contains("Syntax")); | ||
| assert!(app.output.contains("Exercise")); | ||
| assert!(app.learn_result.contains("FAIL")); | ||
| assert_ne!(app.output_area, Rect::new(0, 0, 80, 20)); | ||
| assert!(app.output_area.y > app.code_area.y); | ||
| assert_eq!(app.output_area.x, app.code_area.x); | ||
| assert_eq!(app.output_area, Rect::new(0, 0, 80, 22)); | ||
| assert_eq!(app.left_area, Rect::default()); | ||
| assert_eq!(app.code_area, Rect::default()); | ||
| } | ||
| #[test] | ||
| fn lesson_pane_scrolls_vertically() { | ||
| fn learning_gate_selects_visible_result_at_narrow_and_wide_widths() { | ||
| for (width, height) in [(80, 24), (100, 30)] { | ||
| let mut app = PracticodeApp::new(tmp_root(&format!("gate-visible-{width}"))).unwrap(); | ||
| app.handle_command("learn python").unwrap(); | ||
| app.handle_command("run").unwrap(); | ||
| let terminal = draw_at(&mut app, width, height); | ||
| let text = buffer_text(&terminal); | ||
| assert_eq!(app.learning_session.view(), LearningView::Result); | ||
| assert!( | ||
| text.contains("Next: use /next until Exercise"), | ||
| "{width}: {text}" | ||
| ); | ||
| assert!(app.output_area.width > 0, "{width}"); | ||
| assert_eq!(app.code_area, Rect::default(), "{width}"); | ||
| } | ||
| } | ||
| #[test] | ||
| fn manual_judge_selects_visible_result_at_narrow_and_wide_widths() { | ||
| if which("python3").or_else(|| which("python")).is_none() { | ||
| return; | ||
| } | ||
| for (width, height) in [(80, 24), (100, 30)] { | ||
| let mut app = PracticodeApp::new(tmp_root(&format!("manual-visible-{width}"))).unwrap(); | ||
| app.handle_command("learn python").unwrap(); | ||
| app.handle_command("back").unwrap(); | ||
| app.handle_command("run").unwrap(); | ||
| let terminal = draw_at(&mut app, width, height); | ||
| let text = buffer_text(&terminal); | ||
| assert!(!app.learning_session.is_guided()); | ||
| assert_eq!(app.learning_session.view(), LearningView::Result); | ||
| assert!(text.contains("FAIL"), "{width}: {text}"); | ||
| assert!( | ||
| app.learn_result | ||
| .contains("Retry this exercise; no review is scheduled."), | ||
| "{width}: {}", | ||
| app.learn_result | ||
| ); | ||
| assert!(!app.learn_result.contains("Next review (days)")); | ||
| assert!(app.output_area.width > 0, "{width}"); | ||
| assert_eq!(app.code_area, Rect::default(), "{width}"); | ||
| } | ||
| } | ||
| #[test] | ||
| fn reference_lesson_scrolls_vertically() { | ||
| let mut app = PracticodeApp::new(tmp_root("lesson-scroll")).unwrap(); | ||
| app.handle_command("learn python").unwrap(); | ||
| app.handle_command("lesson").unwrap(); | ||
@@ -657,9 +1253,2 @@ let backend = TestBackend::new(80, 24); | ||
| app.handle_mouse(MouseEvent { | ||
| kind: MouseEventKind::Down(MouseButton::Left), | ||
| column: 2, | ||
| row: 2, | ||
| modifiers: KeyModifiers::NONE, | ||
| }) | ||
| .unwrap(); | ||
| app.handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE)) | ||
@@ -696,3 +1285,3 @@ .unwrap(); | ||
| let backend = TestBackend::new(80, 12); | ||
| let backend = TestBackend::new(80, 16); | ||
| let mut terminal = Terminal::new(backend).unwrap(); | ||
@@ -710,2 +1299,20 @@ terminal.draw(|frame| app.draw(frame)).unwrap(); | ||
| #[test] | ||
| fn output_scroll_uses_wrapped_visual_rows() { | ||
| let mut app = PracticodeApp::new(tmp_root("wrapped-output-scroll")).unwrap(); | ||
| app.write_text_output(&"Inspect this wrapped output safely. ".repeat(80)); | ||
| let _terminal = draw_at(&mut app, 60, 16); | ||
| for _ in 0..3 { | ||
| app.handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE)) | ||
| .unwrap(); | ||
| } | ||
| assert!( | ||
| app.output_scroll > 10, | ||
| "scroll stopped at {}", | ||
| app.output_scroll | ||
| ); | ||
| } | ||
| #[test] | ||
| fn home_pane_title_is_active_when_home_has_focus() { | ||
@@ -725,4 +1332,69 @@ let mut app = PracticodeApp::new(tmp_root("home-active-title")).unwrap(); | ||
| .collect::<String>(); | ||
| assert!(text.contains("> Home")); | ||
| assert!(text.contains("> Continue today's session")); | ||
| } | ||
| #[test] | ||
| fn home_learning_preview_describes_the_guided_session() { | ||
| let app = PracticodeApp::new(tmp_root("home-learning-preview")).unwrap(); | ||
| let preview = app.home_preview_text(); | ||
| assert!(preview.contains("Continue today's session")); | ||
| assert!(preview.contains("Due: 0")); | ||
| assert!(preview.contains("Next step: Language delta")); | ||
| assert!(!preview.contains("moves to the next lesson")); | ||
| } | ||
| #[test] | ||
| fn home_preview_and_status_localize_problem_state_tokens() { | ||
| let mut app = PracticodeApp::new(tmp_root("home-problem-tokens-ko")).unwrap(); | ||
| app.set_ui_language("ko").unwrap(); | ||
| app.action_home().unwrap(); | ||
| app.home_choice = HomeChoice::Problems; | ||
| let preview = app.home_preview_text(); | ||
| assert!(preview.contains("난이도: 쉬움"), "{preview}"); | ||
| assert!(preview.contains("상태: 배정됨"), "{preview}"); | ||
| assert!(!preview.contains("easy"), "{preview}"); | ||
| app.action_practice().unwrap(); | ||
| let status = app.status_text(); | ||
| assert!(status.contains("| 쉬움 |"), "{status}"); | ||
| assert!(!status.contains("| easy |"), "{status}"); | ||
| } | ||
| #[test] | ||
| fn localized_judge_case_keeps_the_result_emphasis() { | ||
| let mut app = PracticodeApp::new(tmp_root("judge-case-style-ko")).unwrap(); | ||
| app.set_ui_language("ko").unwrap(); | ||
| app.output = "케이스 1: 실패".to_string(); | ||
| app.output_is_markdown = false; | ||
| let text = app.output_text(); | ||
| assert_eq!(text.lines[0].spans[0].style.fg, Some(Color::Yellow)); | ||
| } | ||
| #[test] | ||
| fn editor_pane_titles_are_localized_in_every_supported_locale() { | ||
| for language in UI_LANGUAGES { | ||
| let mut app = | ||
| PracticodeApp::new(tmp_root(&format!("localized-pane-title-{language}"))).unwrap(); | ||
| app.set_ui_language(language).unwrap(); | ||
| app.action_learn("rust").unwrap(); | ||
| let learn = draw_at(&mut app, 120, 30); | ||
| let learn_text = buffer_text(&learn).replace(' ', ""); | ||
| let expected = ui_text(language, "pane_exercise").replace(' ', ""); | ||
| assert!(learn_text.contains(&expected), "{language}: {learn_text}"); | ||
| app.action_practice().unwrap(); | ||
| app.action_edit().unwrap(); | ||
| let practice = draw_at(&mut app, 120, 30); | ||
| let practice_text = buffer_text(&practice).replace(' ', ""); | ||
| let expected = ui_text(language, "pane_solution").replace(' ', ""); | ||
| assert!( | ||
| practice_text.contains(&expected), | ||
| "{language}: {practice_text}" | ||
| ); | ||
| } | ||
| } | ||
| } |
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
1730360
34.26%87
14.47%16190
72.31%245
-9.26%11
83.33%1
Infinity%