🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

practicode

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

practicode - npm Package Compare versions

Comparing version
0.1.12
to
0.1.13
+399
assets/lessons/java/en.json
{
"schema_version": 1,
"programming_language": "java",
"ui_language": "en",
"lessons": {
"java-output": {
"title": "Stdout",
"concept": "This Java topic is useful when a judge compares every newline and character. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on building score=7 from an int before println adds the newline. Watch the line that concatenates the label with score; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Using print when the expected output needs the newline added by println.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Where is the integer converted into the text score=7?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses System.out.println with a computed value. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-variables-types": {
"title": "Variables and types",
"concept": "This Java topic is useful when primitive values and object references must be declared before use. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on combining a String, int, and boolean into one report line. Watch the line that sets score and passed consistently; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing only the boolean while leaving the numeric score at its old value.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which variables decide the three fields in Ada:7:true?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses typed local variables that agree with the report. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-numbers-operators": {
"title": "Numbers and operators",
"concept": "This Java topic is useful when division and remainder must stay integer-based in many stdin problems. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on deriving quotient and remainder from the same total and size. Watch the line that uses / and % with the same operands; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Using a different divisor for the quotient than for the remainder.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Why does 17 / 5 produce 3 instead of 3.4?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses integer division and remainder. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-strings": {
"title": "Strings",
"concept": "This Java topic is useful when text cleanup and substring extraction happen before comparison or output. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on trimming spaces before taking the ok prefix. Watch the line that calls trim before substring; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Taking substring indexes before removing surrounding spaces.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"What value does raw still hold after cleaned is computed?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses String methods without mutating the original text. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-control-flow": {
"title": "Control flow",
"concept": "This Java topic is useful when branches and loops decide which values enter an accumulator. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on looping over 1..3 and adding only odd numbers. Watch the line that guards the addition with n % 2 == 1; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Adding every loop value and hoping the final total still matches.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which iterations enter the if body, and which are skipped?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses if inside a for loop. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-methods": {
"title": "Methods",
"concept": "This Java topic is useful when a reusable calculation needs a named boundary. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on passing width and height into area. Watch the line that returns multiplication instead of printing inside the method; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Returning perimeter from a method whose callers expect area.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which line sends the computed value back to main?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses method parameters and return value. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-input": {
"title": "Input parsing",
"concept": "This Java topic is useful when stdin arrives as bytes and must become typed tokens. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on reading System.in once and summing parsed integers. Watch the line that splits on whitespace before Integer.parseInt; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Parsing only the first token when the judge can send many numbers.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Where does text become an int rather than a String?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses readAllBytes, split, and Integer.parseInt. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-arrays-collections": {
"title": "Arrays and collections",
"concept": "This Java topic is useful when fixed arrays, growable List, keyed Map, and unique Set solve different container jobs. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on copying array values into List, Map, and Set views. Watch the line that uses merge for the Map total and add for the Set; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Using nums.length when the problem asks for the numeric total.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which collection stores order, which stores lookup, and which removes duplicates?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses List, Map, and Set APIs with an array source. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-classes-objects": {
"title": "Classes and objects",
"concept": "This Java topic is useful when objects need instance state plus behavior. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on creating Counter and calling add. Watch the line that updates this.value before returning; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing new, fields, and instance methods.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates new, fields, and instance methods?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses new, fields, and instance methods. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-constructors": {
"title": "Constructors",
"concept": "This Java topic is useful when new objects must start with valid field values. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on storing width and height before area runs. Watch the line that assigns constructor parameters into final fields; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing constructor parameters and this.field assignment.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates constructor parameters and this.field assignment?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses constructor parameters and this.field assignment. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-encapsulation": {
"title": "Encapsulation",
"concept": "This Java topic is useful when callers should update state through methods instead of touching fields. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on adding points through Score.add. Watch the line that changes the private points field only after validation; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing private fields with public methods.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates private fields with public methods?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses private fields with public methods. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-static-members": {
"title": "Static members",
"concept": "This Java topic is useful when a value belongs to the class rather than one instance. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on calling Scale.apply through the class. Watch the line that multiplies by the shared FACTOR; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing static final constants and static methods.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates static final constants and static methods?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses static final constants and static methods. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-enum-switch": {
"title": "Enum and switch",
"concept": "This Java topic is useful when a fixed set of states must map to explicit results. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on switching Status.DONE to ok. Watch the line that handles the DONE branch directly; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing enum constants and switch expression branches.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates enum constants and switch expression branches?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses enum constants and switch expression branches. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-exceptions": {
"title": "Exceptions",
"concept": "This Java topic is useful when bad input can be recovered without hiding unrelated failures. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on catching NumberFormatException and returning a fallback. Watch the line that catches the specific parse failure; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing try/catch around Integer.parseInt.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates try/catch around Integer.parseInt?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses try/catch around Integer.parseInt. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-generics": {
"title": "Generics",
"concept": "This Java topic is useful when one method must keep the caller's element type. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on returning the last item from List<T>. Watch the line that uses T instead of Object; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing generic method <T> with List<T>.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates generic method <T> with List<T>?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses generic method <T> with List<T>. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-interfaces": {
"title": "Interfaces",
"concept": "This Java topic is useful when callers should depend on behavior rather than a concrete class. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on passing User as Named. Watch the line that implements name with the public signature; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing implements and interface-typed parameters.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates implements and interface-typed parameters?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses implements and interface-typed parameters. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-inheritance-composition": {
"title": "Inheritance and composition",
"concept": "This Java topic is useful when a subclass also needs a helper object to finish behavior. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on PremiumUser combining inherited baseScore with Bonus. Watch the line that calls bonus.apply(baseScore()); changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing extends plus a composed field.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates extends plus a composed field?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses extends plus a composed field. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-records": {
"title": "Records",
"concept": "This Java topic is useful when immutable data carriers need less boilerplate. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on using generated accessors x() and y(). Watch the line that adds both record components; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing record components and generated accessors.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates record components and generated accessors?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses record components and generated accessors. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-optional": {
"title": "Optional",
"concept": "This Java topic is useful when a missing value must be handled explicitly. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on filtering an Optional before orElse. Watch the line that keeps ok present instead of empty; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing Optional.of, filter, and orElse.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates Optional.of, filter, and orElse?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses Optional.of, filter, and orElse. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-streams-lambdas": {
"title": "Streams and lambdas",
"concept": "This Java topic is useful when collection processing is clearer as a pipeline. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on filtering even numbers then squaring them. Watch the line that uses filter before mapToInt; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing lambda predicates and terminal sum.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates lambda predicates and terminal sum?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses lambda predicates and terminal sum. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-comparators-sorting": {
"title": "Comparators and sorting",
"concept": "This Java topic is useful when objects need ordering separate from their data shape. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on sorting users by score descending. Watch the line that builds a Comparator from User::score; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing Comparator.comparingInt and reversed.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates Comparator.comparingInt and reversed?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses Comparator.comparingInt and reversed. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-try-with-resources": {
"title": "Try-with-resources",
"concept": "This Java topic is useful when resources must close even when code exits early. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on reading bytes inside a managed block. Watch the line that declares the resource in the try header; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing AutoCloseable in a try header.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates AutoCloseable in a try header?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses AutoCloseable in a try header. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-packages-imports": {
"title": "Packages and imports",
"concept": "This Java topic is useful when single-file exercises still need classes from JDK packages. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on importing List and ArrayList. Watch the line that uses imported names without package qualification; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing import declarations in a package-free Solution.java.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates import declarations in a package-free Solution.java?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses import declarations in a package-free Solution.java. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-annotations": {
"title": "Annotations",
"concept": "This Java topic is useful when metadata belongs on declarations rather than in runtime logic. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on defining @Audit and annotating a method. Watch the line that keeps normal method return behavior; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing @interface and annotated method declarations.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates @interface and annotated method declarations?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses @interface and annotated method declarations. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-sealed-classes": {
"title": "Sealed classes",
"concept": "This Java topic is useful when a domain hierarchy should list its permitted implementations. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on restricting Shape to Rect and Dot. Watch the line that computes measure in a permitted record; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing sealed interface, permits, and final records.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates sealed interface, permits, and final records?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses sealed interface, permits, and final records. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-testing-assert": {
"title": "Testing and assert",
"concept": "This Java topic is useful when small checks should fail near the broken method. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on checking addTwo before printing. Watch the line that throws AssertionError when behavior disagrees; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing pure helper plus explicit assertion.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates pure helper plus explicit assertion?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses pure helper plus explicit assertion. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-equality-hashcode": {
"title": "equals and hashCode",
"concept": "This Java topic is useful when HashSet must recognize equal value objects. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on adding two equal Point objects. Watch the line that compares fields and hashes the same fields; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing equals(Object) with matching hashCode.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates equals(Object) with matching hashCode?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses equals(Object) with matching hashCode. Keep the single-file judge shape and make the existing case pass through that syntax."
},
"java-overloading-varargs": {
"title": "Overloading and varargs",
"concept": "This Java topic is useful when method calls can choose among signatures and collect extra arguments. The lesson stays inside one Solution.java file, so the syntax is visible in the value setup, the Java API call, and the exact stdout line.",
"worked_example": "The example focuses on summing first plus the rest array. Watch the line that loops through every varargs value; changing that line changes the judged output for a concrete reason.",
"common_mistakes": [
"Changing the printed literal instead of fixing int... rest and overloaded label.",
"Printing the final answer literally skips the Java construct this lesson is meant to practice."
],
"self_check": [
"Which expression demonstrates int... rest and overloaded label?",
"If the input value or object state changed, which expression would need to change before stdout is printed?"
],
"exercise_prompt": "Edit the Solution.java exercise so it uses int... rest and overloaded label. Keep the single-file judge shape and make the existing case pass through that syntax."
}
}
}
{
"schema_version": 1,
"programming_language": "java",
"ui_language": "es",
"lessons": {
"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 se centra en formar score=7 desde un int antes de que println añada el salto. Sigue la línea que concatena la etiqueta con score; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Usar print cuando la salida esperada necesita el salto que agrega println.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Dónde se convierte el entero en el texto score=7?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar System.out.println con un valor calculado. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en combinar un String, un int y un boolean en una línea. Sigue la línea que mantiene score y passed coherentes; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el boolean y dejar score con el valor anterior.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué variables determinan las tres partes de Ada:7:true?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar variables locales tipadas coherentes con el reporte. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en obtener cociente y resto desde el mismo total y size. Sigue la línea que usa / y % con los mismos operandos; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Usar un divisor distinto para el cociente y para el resto.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Por qué 17 / 5 produce 3 y no 3.4?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar división entera y resto. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en recortar espacios antes de tomar el prefijo ok. Sigue la línea que llama a trim antes de substring; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Calcular substring antes de quitar los espacios alrededor.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué valor conserva raw después de calcular cleaned?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar métodos de String sin mutar el texto original. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en recorrer 1..3 y sumar solo impares. Sigue la línea que protege la suma con n % 2 == 1; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Sumar todos los valores del bucle esperando que el total coincida.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué iteraciones entran al if y cuáles se saltan?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar if dentro de un bucle for. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en pasar width y height a area. Sigue la línea que devuelve la multiplicación en vez de imprimir dentro del método; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Devolver perímetro cuando el llamador espera área.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué línea devuelve el valor calculado a main?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar parámetros y valor de retorno. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en leer System.in una vez y sumar enteros parseados. Sigue la línea que divide por espacios antes de Integer.parseInt; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Parsear solo el primer token cuando el juez puede enviar muchos números.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Dónde el texto pasa de String a int?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar readAllBytes, split e Integer.parseInt. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en pasar valores de array a List, Map y Set. Sigue la línea que usa merge para el total del Map y add para el Set; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Usar nums.length cuando el problema pide el total numérico.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué colección guarda orden, cuál búsqueda y cuál elimina duplicados?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar APIs List, Map y Set con un array de origen. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en crear Counter y llamar a add. Sigue la línea que actualiza this.value antes de devolver; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir new, campos y métodos de instancia.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra new, campos y métodos de instancia?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar new, campos y métodos de instancia. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en guardar width y height antes de ejecutar area. Sigue la línea que asigna parámetros del constructor a campos final; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir parámetros de constructor y asignación this.field.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra parámetros de constructor y asignación this.field?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar parámetros de constructor y asignación this.field. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en sumar puntos mediante Score.add. Sigue la línea que cambia el campo private points solo tras validar; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir campos private con métodos public.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra campos private con métodos public?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar campos private con métodos public. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en llamar Scale.apply desde la clase. Sigue la línea que multiplica por FACTOR compartido; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir constantes static final y métodos static.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra constantes static final y métodos static?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar constantes static final y métodos static. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en convertir Status.DONE en ok con switch. Sigue la línea que maneja directamente la rama DONE; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir constantes enum y ramas de switch.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra constantes enum y ramas de switch?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar constantes enum y ramas de switch. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en capturar NumberFormatException y devolver fallback. Sigue la línea que captura el fallo específico de parseo; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir try/catch alrededor de Integer.parseInt.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra try/catch alrededor de Integer.parseInt?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar try/catch alrededor de Integer.parseInt. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en devolver el último elemento de List<T>. Sigue la línea que usa T en vez de Object; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir método genérico <T> con List<T>.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra método genérico <T> con List<T>?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar método genérico <T> con List<T>. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en pasar User como Named. Sigue la línea que implementa name con la firma public; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir implements y parámetros de tipo interfaz.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra implements y parámetros de tipo interfaz?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar implements y parámetros de tipo interfaz. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en PremiumUser combina baseScore heredado con Bonus. Sigue la línea que llama bonus.apply(baseScore()); si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir extends más un campo compuesto.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra extends más un campo compuesto?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar extends más un campo compuesto. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en usar los accesores generados x() e y(). Sigue la línea que suma ambos componentes del record; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir componentes record y accesores generados.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra componentes record y accesores generados?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar componentes record y accesores generados. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en filtrar Optional antes de orElse. Sigue la línea que mantiene ok presente en vez de empty; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir Optional.of, filter y orElse.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra Optional.of, filter y orElse?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar Optional.of, filter y orElse. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en filtrar pares y luego elevarlos al cuadrado. Sigue la línea que usa filter antes de mapToInt; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir predicados lambda y terminal sum.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra predicados lambda y terminal sum?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar predicados lambda y terminal sum. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en ordenar usuarios por score descendente. Sigue la línea que crea un Comparator desde User::score; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir Comparator.comparingInt y reversed.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra Comparator.comparingInt y reversed?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar Comparator.comparingInt y reversed. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en leer bytes dentro de un bloque gestionado. Sigue la línea que declara el recurso en la cabecera try; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir AutoCloseable en cabecera try.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra AutoCloseable en cabecera try?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar AutoCloseable en cabecera try. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en importar List y ArrayList. Sigue la línea que usa nombres importados sin calificación completa; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir declaraciones import en Solution.java sin package.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra declaraciones import en Solution.java sin package?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar declaraciones import en Solution.java sin package. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en definir @Audit y anotar un método. Sigue la línea que mantiene el retorno normal del método; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir @interface y método anotado.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra @interface y método anotado?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar @interface y método anotado. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en restringir Shape a Rect y Dot. Sigue la línea que calcula measure en un record permitido; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir sealed interface, permits y records final.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra sealed interface, permits y records final?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar sealed interface, permits y records final. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en comprobar addTwo antes de imprimir. Sigue la línea que lanza AssertionError cuando el comportamiento difiere; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir helper puro más aserción explícita.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra helper puro más aserción explícita?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar helper puro más aserción explícita. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en agregar dos objetos Point iguales. Sigue la línea que compara campos y hashea los mismos campos; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir equals(Object) con hashCode coherente.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra equals(Object) con hashCode coherente?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar equals(Object) con hashCode coherente. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
},
"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 se centra en sumar first más el array rest. Sigue la línea que recorre cada valor varargs; si cambia, cambia la salida evaluada por una razón concreta.",
"common_mistakes": [
"Cambiar solo el literal impreso en vez de corregir int... rest y label sobrecargado.",
"Imprimir la respuesta final como literal evita practicar la construcción de Java de esta lección."
],
"self_check": [
"¿Qué expresión demuestra int... rest y label sobrecargado?",
"Si cambia la entrada o el estado del objeto, ¿qué expresión debe cambiar antes de imprimir en stdout?"
],
"exercise_prompt": "Edita el ejercicio Solution.java para usar int... rest y label sobrecargado. Conserva la forma de archivo único y haz que el caso pase mediante esa sintaxis."
}
}
}
{
"schema_version": 1,
"programming_language": "java",
"ui_language": "ja",
"lessons": {
"java-output": {
"title": "標準出力",
"concept": "標準出力は判定が改行と文字をそのまま比較するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではint から score=7 を作り println が改行を付ける流れに注目します。label と score を連結する行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"期待出力に改行が必要なのに print を使い、最後の行が一致しません。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"整数値が score=7 という文字列になる場所はどこですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、計算した値を System.out.println で出力する方法を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-variables-types": {
"title": "変数と型",
"concept": "変数と型はプリミティブ値とオブジェクト参照を使う前に宣言するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではString、int、boolean を一つの報告行にまとめる流れに注目します。score と passed を一貫して設定する行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"boolean だけを変えて数値の score を古い値のままにします。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"Ada:7:true の三つの部分を決める変数はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、出力行と一致する型付きローカル変数を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-numbers-operators": {
"title": "数値と演算子",
"concept": "数値と演算子は多くの stdin 問題で除算と剰余を整数として扱うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例では同じ total と size から商と余りを求める流れに注目します。同じオペランドに / と % を使う行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"商と余りで別々の除数を使ってしまいます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"17 / 5 が 3.4 ではなく 3 になる理由は何ですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、整数除算と剰余を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-strings": {
"title": "文字列",
"concept": "文字列は比較や出力の前に文字列の整形と部分抽出を行うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例では空白を取り除いてから ok の接頭辞を取り出す流れに注目します。substring の前に trim を呼ぶ行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"前後の空白を取り除く前に substring の位置を決めます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"cleaned を作った後、raw にはどの値が残っていますか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、元の文字列を変更しない String メソッドを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-control-flow": {
"title": "制御フロー",
"concept": "制御フローは分岐とループがどの値を累積するかを決めるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例では1 から 3 まで回し奇数だけを足す流れに注目します。n % 2 == 1 で加算を守る行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"すべての値を足して、合計だけ偶然合うことを期待します。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"どの反復が if 本体に入り、どれが飛ばされますか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、for ループ内の if 条件を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-methods": {
"title": "メソッド",
"concept": "メソッドは再利用する計算に名前付きの境界が必要なときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではwidth と height を area に渡す流れに注目します。メソッド内で出力せず乗算結果を返す行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"呼び出し側が面積を期待するメソッドで周長を返します。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"計算結果を main に返す行はどこですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、メソッド引数と戻り値を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-input": {
"title": "入力の解析",
"concept": "入力の解析はstdin のバイト列を型付きトークンに変えるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではSystem.in を一度読み、解析した整数を合計する流れに注目します。Integer.parseInt の前に空白で分割する行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"判定が複数の数値を渡すのに最初のトークンだけ解析します。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"文字列が String ではなく int になる場所はどこですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、readAllBytes、split、Integer.parseIntを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-arrays-collections": {
"title": "配列とコレクション",
"concept": "配列とコレクションは配列は長さが固定された値のまとまりで、List、Map、Set はそれぞれ増える順序、キー検索、一意性を扱うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例では配列の値を List、Map、Set の見方へ移す流れに注目します。Map の合計には merge、Set には add を使う行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"問題が数値の合計を求めているのに nums.length を出力します。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"順序、検索、重複除去をそれぞれ担当するコレクションはどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、配列を元にした List、Map、Set APIを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-classes-objects": {
"title": "クラスとオブジェクト",
"concept": "クラスとオブジェクトはオブジェクトが状態と振る舞いを一緒に持つときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではCounter を作って add を呼ぶ流れに注目します。返す前に this.value を更新する行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"new、フィールド、インスタンスメソッドを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"new、フィールド、インスタンスメソッドを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、new、フィールド、インスタンスメソッドを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-constructors": {
"title": "コンストラクタ",
"concept": "コンストラクタは新しいオブジェクトが正しいフィールド値で始まるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではarea の前に width と height を保存する流れに注目します。コンストラクタ引数を final フィールドへ代入する行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"コンストラクタ引数と this.field 代入を直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"コンストラクタ引数と this.field 代入を示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、コンストラクタ引数と this.field 代入を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-encapsulation": {
"title": "カプセル化",
"concept": "カプセル化は呼び出し側がフィールドを直接触らずメソッドで状態を変えるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではScore.add で点数を足す流れに注目します。検証後に private points フィールドだけを変える行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"private フィールドと public メソッドを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"private フィールドと public メソッドを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、private フィールドと public メソッドを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-static-members": {
"title": "static メンバー",
"concept": "static メンバーは値が一つのインスタンスではなくクラスに属するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではクラス経由で Scale.apply を呼ぶ流れに注目します。共有 FACTOR を掛ける行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"static final 定数と static メソッドを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"static final 定数と static メソッドを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、static final 定数と static メソッドを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-enum-switch": {
"title": "enum と switch",
"concept": "enum と switchは固定された状態集合を明示的な結果へ変えるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではStatus.DONE を ok へ switch する流れに注目します。DONE 分岐を直接扱う行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"enum 定数と switch 式の分岐を直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"enum 定数と switch 式の分岐を示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、enum 定数と switch 式の分岐を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-exceptions": {
"title": "例外",
"concept": "例外は不正な入力を無関係な失敗まで隠さず回復するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではNumberFormatException を捕まえて fallback を返す流れに注目します。特定の解析失敗だけを catch する行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"Integer.parseInt を囲む try/catchを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"Integer.parseInt を囲む try/catchを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、Integer.parseInt を囲む try/catchを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-generics": {
"title": "ジェネリクス",
"concept": "ジェネリクスは一つのメソッドが呼び出し側の要素型を保つときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではList<T> から最後の要素を返す流れに注目します。Object ではなく T を使う行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"List<T> を受けるジェネリックメソッド <T>を直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"List<T> を受けるジェネリックメソッド <T>を示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、List<T> を受けるジェネリックメソッド <T>を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-interfaces": {
"title": "インターフェイス",
"concept": "インターフェイスは呼び出し側が具象クラスでなく振る舞いの契約に依存するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではUser を Named として渡す流れに注目します。public シグネチャで name を実装する行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"implements とインターフェイス型引数を直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"implements とインターフェイス型引数を示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、implements とインターフェイス型引数を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-inheritance-composition": {
"title": "継承と合成",
"concept": "継承と合成はサブクラスが振る舞いを完成させるため補助オブジェクトも使うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではPremiumUser が継承した baseScore と Bonus を組み合わせる流れに注目します。bonus.apply(baseScore()) を呼ぶ行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"extends と合成フィールドを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"extends と合成フィールドを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、extends と合成フィールドを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-records": {
"title": "record",
"concept": "recordは不変データキャリアの定型コードを減らすときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例では生成されたアクセサ x() と y() を使う流れに注目します。二つの record コンポーネントを足す行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"record コンポーネントと生成アクセサを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"record コンポーネントと生成アクセサを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、record コンポーネントと生成アクセサを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-optional": {
"title": "Optional",
"concept": "Optionalは存在しない値を明示的に扱うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではorElse の前に Optional を filter する流れに注目します。empty ではなく ok を保持する行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"Optional.of、filter、orElseを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"Optional.of、filter、orElseを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、Optional.of、filter、orElseを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-streams-lambdas": {
"title": "Stream とラムダ",
"concept": "Stream とラムダはコレクション処理をパイプラインとして表すときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例では偶数を filter してから二乗する流れに注目します。mapToInt の前に filter を使う行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"ラムダ述語と終端 sumを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"ラムダ述語と終端 sumを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、ラムダ述語と終端 sumを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-comparators-sorting": {
"title": "Comparator とソート",
"concept": "Comparator とソートはオブジェクトにデータ形状とは別の順序が必要なときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではユーザーを score 降順で並べる流れに注目します。User::score から Comparator を作る行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"Comparator.comparingInt と reversedを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"Comparator.comparingInt と reversedを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、Comparator.comparingInt と reversedを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-try-with-resources": {
"title": "try-with-resources",
"concept": "try-with-resourcesはコードが早く抜けてもリソースを閉じるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例では管理されたブロック内で byte を読む流れに注目します。try ヘッダーでリソースを宣言する行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"try ヘッダー内の AutoCloseableを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"try ヘッダー内の AutoCloseableを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、try ヘッダー内の AutoCloseableを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-packages-imports": {
"title": "package と import",
"concept": "package と importは単一ファイル演習でも JDK パッケージのクラスを使うときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではList と ArrayList を import する流れに注目します。完全修飾名なしで import した名前を使う行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"package のない Solution.java での import 宣言を直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"package のない Solution.java での import 宣言を示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、package のない Solution.java での import 宣言を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-annotations": {
"title": "アノテーション",
"concept": "アノテーションはメタデータを実行ロジックでなく宣言に付けるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例では@Audit を定義しメソッドへ付ける流れに注目します。通常のメソッド戻り値は保つ行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"@interface とアノテーション付きメソッド宣言を直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"@interface とアノテーション付きメソッド宣言を示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、@interface とアノテーション付きメソッド宣言を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-sealed-classes": {
"title": "sealed クラス",
"concept": "sealed クラスはドメイン階層が許可された実装を列挙するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではShape を Rect と Dot に制限する流れに注目します。許可された record 内で measure を計算する行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"sealed interface、permits、final recordを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"sealed interface、permits、final recordを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、sealed interface、permits、final recordを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-testing-assert": {
"title": "テストと assert",
"concept": "テストと assertは小さな検査を壊れたメソッドの近くで失敗させるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例では出力前に addTwo を検査する流れに注目します。振る舞いが違えば AssertionError を投げる行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"純粋な helper と明示的 assertionを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"純粋な helper と明示的 assertionを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、純粋な helper と明示的 assertionを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-equality-hashcode": {
"title": "equals と hashCode",
"concept": "equals と hashCodeはHashSet が同じ値オブジェクトを認識するときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例では等しい Point オブジェクトを二つ追加する流れに注目します。フィールドを比較し同じフィールドを hash する行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"hashCode と合う equals(Object)を直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"hashCode と合う equals(Object)を示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、hashCode と合う equals(Object)を使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
},
"java-overloading-varargs": {
"title": "オーバーロードと varargs",
"concept": "オーバーロードと varargsはメソッド呼び出しがシグネチャを選び追加引数を集めるときに必要です。このレッスンは単一の Solution.java の中で、値の準備、Java API の呼び出し、正確な stdout までの流れを示します。",
"worked_example": "例ではfirst と rest 配列を足す流れに注目します。すべての varargs 値をループする行を追うと、出力が変わる理由をコードの経路として説明できます。",
"common_mistakes": [
"int... rest とオーバーロードされた labelを直さず、出力文字列だけを変えます。",
"最終結果だけを文字列で出力すると、このレッスンで練習する Java 構文を使ったことになりません。"
],
"self_check": [
"int... rest とオーバーロードされた labelを示す式はどれですか。",
"入力値やオブジェクトの状態が変わったら、stdout の前にどの式を変える必要がありますか。"
],
"exercise_prompt": "Solution.java の演習を直し、int... rest とオーバーロードされた labelを使ってください。単一ファイルの判定形式を保ち、その構文でケースを通してください。"
}
}
}
{
"schema_version": 1,
"programming_language": "java",
"ui_language": "ko",
"lessons": {
"java-output": {
"title": "표준 출력",
"concept": "표준 출력는 judge가 줄바꿈과 문자를 그대로 비교할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 int 값으로 score=7을 만든 뒤 println이 줄바꿈을 붙이는 흐름에 집중한다. label과 score를 연결하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"예상 출력에 줄바꿈이 필요한데 print를 써서 마지막 줄이 맞지 않는다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"정수 값이 score=7 텍스트로 바뀌는 지점은 어디인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 계산된 값을 System.out.println으로 출력하는 방식를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-variables-types": {
"title": "변수와 타입",
"concept": "변수와 타입는 primitive 값과 객체 참조를 쓰기 전에 선언해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 String, int, boolean을 한 줄 보고서로 합치는 흐름에 집중한다. score와 passed를 일관되게 맞추는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"boolean만 바꾸고 숫자 score는 이전 값으로 남겨 둔다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"Ada:7:true의 세 칸을 결정하는 변수는 각각 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 출력 보고서와 일치하는 타입 있는 지역 변수를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-numbers-operators": {
"title": "숫자와 연산자",
"concept": "숫자와 연산자는 많은 stdin 문제에서 나눗셈과 나머지를 정수 기준으로 유지해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 같은 total과 size에서 몫과 나머지를 구하는 흐름에 집중한다. 같은 피연산자에 /와 %를 쓰는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"몫과 나머지에 서로 다른 나누는 값을 사용한다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"17 / 5가 3.4가 아니라 3이 되는 이유는 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 정수 나눗셈과 나머지 연산를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-strings": {
"title": "문자열",
"concept": "문자열는 비교나 출력 전에 텍스트 정리와 부분 문자열 추출이 필요할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 공백을 제거한 뒤 ok 접두사를 꺼내는 흐름에 집중한다. substring 전에 trim을 호출하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"앞뒤 공백을 제거하기 전에 substring 인덱스를 잡는다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"cleaned를 만든 뒤 raw에는 어떤 값이 남아 있는가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 원본 텍스트를 바꾸지 않는 String 메서드를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-control-flow": {
"title": "제어 흐름",
"concept": "제어 흐름는 분기와 반복문이 누적값에 들어갈 값을 결정할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 1부터 3까지 반복하며 홀수만 더하는 흐름에 집중한다. n % 2 == 1 조건으로 더하기를 지키는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"반복되는 모든 값을 더해 놓고 최종 합이 우연히 맞기를 기대한다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"어떤 반복이 if 본문에 들어가고 어떤 반복은 건너뛰는가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 for 반복문 안의 if 조건를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-methods": {
"title": "메서드",
"concept": "메서드는 재사용 계산에 이름 있는 경계가 필요할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 width와 height를 area에 전달하는 흐름에 집중한다. 메서드 안에서 출력하지 않고 곱셈 결과를 반환하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"호출자가 넓이를 기대하는 메서드에서 둘레를 반환한다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"계산된 값을 main으로 돌려보내는 줄은 어디인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 메서드 매개변수와 반환값를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-input": {
"title": "입력 파싱",
"concept": "입력 파싱는 stdin 바이트를 타입 있는 토큰으로 바꿔야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 System.in을 한 번 읽고 파싱한 정수를 합산하는 흐름에 집중한다. Integer.parseInt 전에 공백 기준으로 나누는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"judge가 여러 숫자를 줄 수 있는데 첫 토큰만 파싱한다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"텍스트가 String이 아니라 int가 되는 지점은 어디인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 readAllBytes, split, Integer.parseInt를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-arrays-collections": {
"title": "배열과 컬렉션",
"concept": "배열과 컬렉션는 고정 길이 배열, 늘어나는 List, 키 기반 Map, 유일값 Set이 서로 다른 컨테이너 역할을 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 배열 값을 List, Map, Set 관점으로 옮기는 흐름에 집중한다. Map 합계에는 merge를, Set에는 add를 쓰는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"문제가 숫자 합계를 요구하는데 nums.length를 출력한다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"순서, 조회, 중복 제거를 각각 담당하는 컬렉션은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 배열을 출발점으로 한 List, Map, Set API를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-classes-objects": {
"title": "클래스와 객체",
"concept": "클래스와 객체는 객체가 인스턴스 상태와 동작을 함께 가져야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 Counter를 만들고 add를 호출하는 흐름에 집중한다. 반환 전에 this.value를 갱신하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"new, 필드, 인스턴스 메서드를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"new, 필드, 인스턴스 메서드를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 new, 필드, 인스턴스 메서드를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-constructors": {
"title": "생성자",
"concept": "생성자는 새 객체가 올바른 필드값으로 시작해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 area가 실행되기 전에 width와 height를 저장하는 흐름에 집중한다. 생성자 매개변수를 final 필드에 대입하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"생성자 매개변수와 this.field 대입를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"생성자 매개변수와 this.field 대입를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 생성자 매개변수와 this.field 대입를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-encapsulation": {
"title": "캡슐화",
"concept": "캡슐화는 호출자가 필드를 직접 만지지 않고 메서드로 상태를 바꿔야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 Score.add를 통해 점수를 더하는 흐름에 집중한다. 검증 뒤 private points 필드만 바꾸는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"private 필드와 public 메서드를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"private 필드와 public 메서드를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 private 필드와 public 메서드를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-static-members": {
"title": "static 멤버",
"concept": "static 멤버는 값이 특정 객체가 아니라 클래스에 속해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 클래스를 통해 Scale.apply를 호출하는 흐름에 집중한다. 공유 FACTOR를 곱하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"static final 상수와 static 메서드를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"static final 상수와 static 메서드를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 static final 상수와 static 메서드를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-enum-switch": {
"title": "enum과 switch",
"concept": "enum과 switch는 정해진 상태 집합을 명시적 결과로 바꿔야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 Status.DONE을 ok로 switch하는 흐름에 집중한다. DONE 분기를 직접 처리하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"enum 상수와 switch 표현식 분기를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"enum 상수와 switch 표현식 분기를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 enum 상수와 switch 표현식 분기를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-exceptions": {
"title": "예외",
"concept": "예외는 잘못된 입력을 관련 없는 실패까지 숨기지 않고 복구해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 NumberFormatException을 잡아 fallback을 반환하는 흐름에 집중한다. 구체적인 파싱 실패만 catch하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"Integer.parseInt 주변의 try/catch를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"Integer.parseInt 주변의 try/catch를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 Integer.parseInt 주변의 try/catch를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-generics": {
"title": "제네릭",
"concept": "제네릭는 하나의 메서드가 호출자의 원소 타입을 보존해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 List<T>에서 마지막 원소를 반환하는 흐름에 집중한다. Object 대신 T를 쓰는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"List<T>를 받는 제네릭 메서드 <T>를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"List<T>를 받는 제네릭 메서드 <T>를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 List<T>를 받는 제네릭 메서드 <T>를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-interfaces": {
"title": "인터페이스",
"concept": "인터페이스는 호출자가 구체 클래스보다 동작 계약에 기대야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 User를 Named로 전달하는 흐름에 집중한다. public 시그니처로 name을 구현하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"implements와 인터페이스 타입 매개변수를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"implements와 인터페이스 타입 매개변수를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 implements와 인터페이스 타입 매개변수를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-inheritance-composition": {
"title": "상속과 합성",
"concept": "상속과 합성는 하위 클래스가 동작을 완성하려면 도움 객체도 필요할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 PremiumUser가 상속받은 baseScore와 Bonus를 합치는 흐름에 집중한다. bonus.apply(baseScore())를 호출하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"extends와 합성 필드를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"extends와 합성 필드를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 extends와 합성 필드를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-records": {
"title": "record",
"concept": "record는 불변 데이터 묶음에 반복 코드가 적어야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 생성된 접근자 x()와 y()를 쓰는 흐름에 집중한다. 두 record 컴포넌트를 모두 더하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"record 컴포넌트와 생성 접근자를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"record 컴포넌트와 생성 접근자를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 record 컴포넌트와 생성 접근자를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-optional": {
"title": "Optional",
"concept": "Optional는 없는 값을 명시적으로 처리해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 orElse 전에 Optional을 filter하는 흐름에 집중한다. empty 대신 ok를 가진 상태로 유지하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"Optional.of, filter, orElse를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"Optional.of, filter, orElse를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 Optional.of, filter, orElse를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-streams-lambdas": {
"title": "stream과 lambda",
"concept": "stream과 lambda는 컬렉션 처리가 파이프라인으로 더 분명할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 짝수만 filter한 뒤 제곱하는 흐름에 집중한다. mapToInt 전에 filter를 쓰는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"lambda 조건과 terminal sum를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"lambda 조건과 terminal sum를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 lambda 조건과 terminal sum를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-comparators-sorting": {
"title": "Comparator와 정렬",
"concept": "Comparator와 정렬는 객체의 데이터 모양과 별도로 정렬 기준이 필요할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 사용자를 점수 내림차순으로 정렬하는 흐름에 집중한다. User::score로 Comparator를 만드는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"Comparator.comparingInt와 reversed를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"Comparator.comparingInt와 reversed를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 Comparator.comparingInt와 reversed를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-try-with-resources": {
"title": "try-with-resources",
"concept": "try-with-resources는 코드가 일찍 끝나도 리소스가 닫혀야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 관리되는 블록 안에서 byte를 읽는 흐름에 집중한다. try 헤더에 리소스를 선언하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"try 헤더의 AutoCloseable를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"try 헤더의 AutoCloseable를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 try 헤더의 AutoCloseable를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-packages-imports": {
"title": "package와 import",
"concept": "package와 import는 단일 파일 실습에서도 JDK 패키지의 클래스가 필요할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 List와 ArrayList를 import하는 흐름에 집중한다. 패키지 전체 이름 없이 import된 이름을 쓰는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"package 없는 Solution.java의 import 선언를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"package 없는 Solution.java의 import 선언를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 package 없는 Solution.java의 import 선언를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-annotations": {
"title": "annotation",
"concept": "annotation는 메타데이터가 실행 로직이 아니라 선언에 붙어야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 @Audit를 정의하고 메서드에 붙이는 흐름에 집중한다. 일반 메서드 반환 동작은 유지하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"@interface와 annotation이 붙은 메서드 선언를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"@interface와 annotation이 붙은 메서드 선언를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 @interface와 annotation이 붙은 메서드 선언를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-sealed-classes": {
"title": "sealed 클래스",
"concept": "sealed 클래스는 도메인 계층이 허용된 구현을 나열해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 Shape를 Rect와 Dot으로 제한하는 흐름에 집중한다. 허용된 record 안에서 measure를 계산하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"sealed interface, permits, final record를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"sealed interface, permits, final record를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 sealed interface, permits, final record를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-testing-assert": {
"title": "테스트와 assert",
"concept": "테스트와 assert는 작은 검사가 깨진 메서드 가까이에서 실패해야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 출력 전에 addTwo를 검사하는 흐름에 집중한다. 동작이 다르면 AssertionError를 던지는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"순수 helper와 명시적 assertion를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"순수 helper와 명시적 assertion를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 순수 helper와 명시적 assertion를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-equality-hashcode": {
"title": "equals와 hashCode",
"concept": "equals와 hashCode는 HashSet이 같은 값 객체를 알아봐야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 같은 Point 객체 두 개를 추가하는 흐름에 집중한다. 필드를 비교하고 같은 필드를 hash하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"hashCode와 맞는 equals(Object)를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"hashCode와 맞는 equals(Object)를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 hashCode와 맞는 equals(Object)를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
},
"java-overloading-varargs": {
"title": "overloading과 varargs",
"concept": "overloading과 varargs는 메서드 호출이 시그니처를 고르고 추가 인자를 모아야 할 때 필요하다. 이 레슨은 단일 Solution.java 안에서 값 준비, Java API 호출, 정확한 stdout 출력이 어떻게 이어지는지 보여 준다.",
"worked_example": "예제는 first와 나머지 배열을 더하는 흐름에 집중한다. 모든 varargs 값을 반복하는 줄을 확인하면 출력이 왜 바뀌는지 코드 경로로 설명할 수 있다.",
"common_mistakes": [
"int... rest와 overloaded label를 고치지 않고 출력 문자열만 바꾼다.",
"최종 답을 문자열로만 출력하면 이 레슨에서 연습해야 할 Java 문법을 건너뛰게 된다."
],
"self_check": [
"int... rest와 overloaded label를 보여 주는 표현식은 무엇인가?",
"입력값이나 객체 상태가 바뀌면 stdout 전에 어느 표현식을 바꿔야 하는가?"
],
"exercise_prompt": "Solution.java 실습을 고쳐 int... rest와 overloaded label를 사용하라. 단일 파일 judge 구조를 유지하고 그 문법을 통해 케이스를 통과시켜라."
}
}
}
{
"schema_version": 1,
"programming_language": "java",
"ui_language": "zh",
"lessons": {
"java-output": {
"title": "标准输出",
"concept": "标准输出用于评测逐字符比较换行和文本。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示先用 int 组成 score=7,再由 println 添加换行。跟着把 label 和 score 拼接起来这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"需要 println 的换行时却使用 print,导致末尾不匹配。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"整数在哪里变成 score=7 这段文本?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用用 System.out.println 输出计算出的值。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-variables-types": {
"title": "变量与类型",
"concept": "变量与类型用于使用基本值和对象引用前必须声明类型。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示把 String、int 和 boolean 合成一行报告。跟着让 score 和 passed 保持一致这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"只改 boolean,却把数字 score 留成旧值。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"Ada:7:true 的三个部分分别由哪些变量决定?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用与报告输出一致的带类型局部变量。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-numbers-operators": {
"title": "数字与运算符",
"concept": "数字与运算符用于许多 stdin 题目需要保持整数除法和取余。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示从同一个 total 和 size 得到商与余数。跟着对同一组操作数使用 / 和 %这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"商和余数使用了不同的除数。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"为什么 17 / 5 得到 3 而不是 3.4?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用整数除法和取余。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-strings": {
"title": "字符串",
"concept": "字符串用于比较或输出前需要清理文本并提取子串。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示先去掉空格再取 ok 前缀。跟着在 substring 之前调用 trim这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"在去掉首尾空格之前就取 substring 下标。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"计算 cleaned 后 raw 仍然保存什么值?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用不会修改原文本的 String 方法。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-control-flow": {
"title": "控制流",
"concept": "控制流用于分支和循环决定哪些值进入累加器。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示遍历 1 到 3 并只累加奇数。跟着用 n % 2 == 1 保护累加这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"把每个循环值都加上,却希望总和碰巧正确。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪些迭代会进入 if 主体,哪些会跳过?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用for 循环中的 if 条件。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-methods": {
"title": "方法",
"concept": "方法用于可复用计算需要命名边界。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示把 width 和 height 传入 area。跟着返回乘积而不是在方法里打印这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"调用方期望面积时却返回周长。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪一行把计算值返回给 main?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用方法参数和返回值。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-input": {
"title": "输入解析",
"concept": "输入解析用于stdin 字节必须变成带类型的 token。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示读取一次 System.in 并累加解析出的整数。跟着在 Integer.parseInt 前按空白分割这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"评测可能给多个数字,却只解析第一个 token。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"文本在哪里从 String 变成 int?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用readAllBytes、split 和 Integer.parseInt。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-arrays-collections": {
"title": "数组与集合",
"concept": "数组与集合用于固定数组、可增长 List、键值 Map 和唯一 Set 负责不同容器任务。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示把数组值放入 List、Map 和 Set 的视角。跟着Map 总和用 merge,Set 用 add这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"题目要求数值总和时却输出 nums.length。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个集合保存顺序、哪个负责查找、哪个去重?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用以数组为来源使用 List、Map、Set API。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-classes-objects": {
"title": "类与对象",
"concept": "类与对象用于对象需要同时拥有实例状态和行为。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示创建 Counter 并调用 add。跟着返回前更新 this.value这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正new、字段和实例方法,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现new、字段和实例方法?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用new、字段和实例方法。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-constructors": {
"title": "构造器",
"concept": "构造器用于新对象必须以有效字段值开始。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示在 area 运行前保存 width 和 height。跟着把构造器参数赋给 final 字段这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正构造器参数和 this.field 赋值,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现构造器参数和 this.field 赋值?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用构造器参数和 this.field 赋值。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-encapsulation": {
"title": "封装",
"concept": "封装用于调用方应通过方法更新状态而不是直接访问字段。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示通过 Score.add 增加分数。跟着验证后只修改 private points 字段这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正private 字段和 public 方法,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现private 字段和 public 方法?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用private 字段和 public 方法。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-static-members": {
"title": "static 成员",
"concept": "static 成员用于值属于类而不是某个实例。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示通过类调用 Scale.apply。跟着乘以共享的 FACTOR这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正static final 常量和 static 方法,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现static final 常量和 static 方法?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用static final 常量和 static 方法。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-enum-switch": {
"title": "enum 与 switch",
"concept": "enum 与 switch用于固定状态集合需要映射为明确结果。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示将 Status.DONE switch 成 ok。跟着直接处理 DONE 分支这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正enum 常量和 switch 表达式分支,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现enum 常量和 switch 表达式分支?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用enum 常量和 switch 表达式分支。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-exceptions": {
"title": "异常",
"concept": "异常用于坏输入需要恢复但不能隐藏无关失败。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示捕获 NumberFormatException 并返回 fallback。跟着只捕获具体解析失败这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正围绕 Integer.parseInt 的 try/catch,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现围绕 Integer.parseInt 的 try/catch?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用围绕 Integer.parseInt 的 try/catch。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-generics": {
"title": "泛型",
"concept": "泛型用于一个方法需要保留调用方的元素类型。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示从 List<T> 返回最后一个元素。跟着使用 T 而不是 Object这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正接收 List<T> 的泛型方法 <T>,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现接收 List<T> 的泛型方法 <T>?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用接收 List<T> 的泛型方法 <T>。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-interfaces": {
"title": "接口",
"concept": "接口用于调用方应依赖行为契约而不是具体类。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示把 User 当作 Named 传入。跟着用 public 签名实现 name这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正implements 和接口类型参数,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现implements 和接口类型参数?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用implements 和接口类型参数。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-inheritance-composition": {
"title": "继承与组合",
"concept": "继承与组合用于子类还需要辅助对象来完成行为。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示PremiumUser 结合继承的 baseScore 和 Bonus。跟着调用 bonus.apply(baseScore())这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正extends 加组合字段,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现extends 加组合字段?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用extends 加组合字段。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-records": {
"title": "record",
"concept": "record用于不可变数据载体需要减少样板代码。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示使用生成的访问器 x() 和 y()。跟着累加两个 record 组件这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正record 组件和生成访问器,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现record 组件和生成访问器?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用record 组件和生成访问器。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-optional": {
"title": "Optional",
"concept": "Optional用于缺失值必须显式处理。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示在 orElse 前过滤 Optional。跟着保持 ok 存在而不是 empty这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正Optional.of、filter 和 orElse,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现Optional.of、filter 和 orElse?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用Optional.of、filter 和 orElse。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-streams-lambdas": {
"title": "Stream 与 lambda",
"concept": "Stream 与 lambda用于集合处理用流水线更清晰。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示先过滤偶数再平方。跟着在 mapToInt 前使用 filter这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正lambda 谓词和终端 sum,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现lambda 谓词和终端 sum?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用lambda 谓词和终端 sum。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-comparators-sorting": {
"title": "Comparator 与排序",
"concept": "Comparator 与排序用于对象需要与数据形状分离的排序规则。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示按分数降序排序用户。跟着从 User::score 构建 Comparator这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正Comparator.comparingInt 和 reversed,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现Comparator.comparingInt 和 reversed?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用Comparator.comparingInt 和 reversed。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-try-with-resources": {
"title": "try-with-resources",
"concept": "try-with-resources用于代码提前退出时资源也必须关闭。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示在托管块中读取字节。跟着在 try 头部声明资源这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正try 头部中的 AutoCloseable,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现try 头部中的 AutoCloseable?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用try 头部中的 AutoCloseable。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-packages-imports": {
"title": "package 与 import",
"concept": "package 与 import用于单文件练习仍需要 JDK 包中的类。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示导入 List 和 ArrayList。跟着无需全限定名即可使用导入名称这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正无 package 的 Solution.java 中的 import 声明,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现无 package 的 Solution.java 中的 import 声明?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用无 package 的 Solution.java 中的 import 声明。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-annotations": {
"title": "注解",
"concept": "注解用于元数据应附着在声明而不是运行逻辑中。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示定义 @Audit 并标注方法。跟着保持普通方法返回行为这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正@interface 和带注解的方法声明,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现@interface 和带注解的方法声明?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用@interface 和带注解的方法声明。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-sealed-classes": {
"title": "sealed 类",
"concept": "sealed 类用于领域层次需要列出允许的实现。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示将 Shape 限制为 Rect 和 Dot。跟着在允许的 record 中计算 measure这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正sealed interface、permits 和 final record,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现sealed interface、permits 和 final record?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用sealed interface、permits 和 final record。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-testing-assert": {
"title": "测试与 assert",
"concept": "测试与 assert用于小检查应在出错方法附近失败。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示打印前检查 addTwo。跟着行为不一致时抛出 AssertionError这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正纯 helper 加显式断言,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现纯 helper 加显式断言?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用纯 helper 加显式断言。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-equality-hashcode": {
"title": "equals 与 hashCode",
"concept": "equals 与 hashCode用于HashSet 必须识别相等的值对象。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示加入两个相等的 Point 对象。跟着比较字段并哈希同一组字段这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正与 hashCode 匹配的 equals(Object),只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现与 hashCode 匹配的 equals(Object)?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用与 hashCode 匹配的 equals(Object)。保持单文件评测结构,并让现有用例通过这项语法。"
},
"java-overloading-varargs": {
"title": "重载与 varargs",
"concept": "重载与 varargs用于方法调用会选择签名并收集额外参数。本课保持单个 Solution.java,让你看到值的准备、Java API 调用以及精确 stdout 输出如何连接。",
"worked_example": "示例重点展示累加 first 和 rest 数组。跟着遍历每个 varargs 值这一行看,就能解释为什么输出会随代码路径变化。",
"common_mistakes": [
"不修正int... rest 和重载的 label,只改输出字符串。",
"只把最终答案写进 println 会绕过本课要练习的 Java 语法。"
],
"self_check": [
"哪个表达式体现int... rest 和重载的 label?",
"如果输入值或对象状态改变,stdout 前的哪个表达式也必须改变?"
],
"exercise_prompt": "修改 Solution.java 练习,使用int... rest 和重载的 label。保持单文件评测结构,并让现有用例通过这项语法。"
}
}
}
{
"schema_version": 1,
"programming_language": "python",
"ui_language": "en",
"lessons": {
"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": "`name = 'Ada'; score = 7; print(f'{name}:{score}')` uses an f-string to format existing values as `Ada:7` without adding debug text.",
"common_mistakes": [
"Leaving extra debug prints in a submission that is judged by exact stdout.",
"Printing a container such as `['Ada', 7]` when the expected output is plain text.",
"Using `return` at top level instead of printing the final answer."
],
"self_check": [
"Which characters does `print` add when you do not pass `end=`?",
"If the value is correct but stdout has one extra line, what will the judge compare?"
],
"exercise_prompt": "Use the given `name` and `score` variables to print exactly `Ada:7`, with no extra labels or debug output."
},
"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": "`count = 1; count = count + 2` reads the old integer, creates a new integer, and binds `count` to that new value before printing `3`.",
"common_mistakes": [
"Reading `=` as a mathematical equality test instead of assignment.",
"Reusing a short name for unrelated meanings in the same solution.",
"Expecting a rebinding to mutate every earlier value automatically."
],
"self_check": [
"After `x = 1` and `x = x + 2`, what does `x` refer to?",
"Which temporary value deserves a name in the code you are reading?"
],
"exercise_prompt": "Rebind `word` to the expected text, then print the name rather than hard-coding a second literal in `print`."
},
"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": "`total // size` and `total % size` split `7` items into groups of `2`, producing quotient `3` and remainder `1`.",
"common_mistakes": [
"Using `/` when the task expects integer division.",
"Forgetting that `%` is the remainder after division, not a percentage.",
"Mixing float output into a judge answer that expects integers."
],
"self_check": [
"What are the values of `7 // 2` and `7 % 2`?",
"When would `round` be the wrong replacement for `//`?"
],
"exercise_prompt": "Change the arithmetic so the starter prints the integer quotient and remainder as `3:1`."
},
"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": "`text = 'python'; text[1:4]` reads indexes 1, 2, and 3, so the printed slice is `yth`.",
"common_mistakes": [
"Expecting the stop index in a slice to be included.",
"Trying to assign into `text[0]` even though strings are immutable.",
"Counting indexes from 1 instead of 0."
],
"self_check": [
"What does `'code'[1]` read?",
"Why does `'xokx'[1:3]` produce exactly two characters?"
],
"exercise_prompt": "Use a slice of `text` so the output is exactly `ok`, not the surrounding marker characters."
},
"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 example visits `1`, `2`, and `3`, adds only odd values, and prints `4` after the loop finishes.",
"common_mistakes": [
"Putting accumulation code outside the loop by one indentation level.",
"Using `range(n)` while expecting it to include `n`.",
"Writing a `while` loop whose condition never changes."
],
"self_check": [
"Which lines run once per item in the loop?",
"What values are produced by `range(1, 4)`?"
],
"exercise_prompt": "Edit the loop body so it adds only the odd numbers and prints the final total after the loop."
},
"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": "`area(3, 4)` passes two arguments, the function multiplies them, and the caller prints the returned `12`.",
"common_mistakes": [
"Forgetting to call a function after defining it.",
"Printing a value where the caller needs it returned.",
"Returning too early from inside a loop."
],
"self_check": [
"What value is returned if execution reaches the end of a function without `return`?",
"Which part of this program should own the final `print`?"
],
"exercise_prompt": "Fix the function body so it returns rectangle area, then let the existing call print the result."
},
"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": "`[int(token) for token in sys.stdin.read().split()]` converts all whitespace-separated input tokens into integers before summing them.",
"common_mistakes": [
"Calling `input()` repeatedly when one bulk read is simpler for unknown line counts.",
"Forgetting that `split()` produces strings until `int` is applied.",
"Mixing parsing code into the middle of the algorithm so it is hard to test."
],
"self_check": [
"What does `sys.stdin.read()` return before conversion?",
"Where would you handle an empty input string?"
],
"exercise_prompt": "Parse all integers from stdin and print their sum for the provided judge case."
},
"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": "`scores['Ada']` finds Ada's list of scores, and `sum(...)` calculates the total for that key without scanning unrelated names.",
"common_mistakes": [
"Using a list index when the data is identified by a meaningful key.",
"Looking up a dict key before deciding what should happen if it is missing.",
"Printing `len(nums)` when the problem asks for the sum of the numbers."
],
"self_check": [
"When should a value become a dict key instead of a list position?",
"What does `scores['Ada']` retrieve in the example?"
],
"exercise_prompt": "Use the list stored under `Ada` to print the sum of its numbers without hard-coding `5`."
},
"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": "`pair = ('o', 'k')` keeps order for joining, while `set(pair)` records that there are two distinct letters.",
"common_mistakes": [
"Using a set when output order matters.",
"Trying to change a tuple item in place.",
"Forgetting that duplicate set inserts do not increase its length."
],
"self_check": [
"Which part of the example needs tuple order?",
"What would `len(set(['a', 'a']))` return?"
],
"exercise_prompt": "Build a set from the tuple and print both the joined tuple text and the count of unique items."
},
"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": "`[n * n for n in nums if n % 2 == 0]` squares only even numbers, producing values that can be summed immediately.",
"common_mistakes": [
"Putting so much logic in a comprehension that a normal loop would be clearer.",
"Forgetting that the output expression appears before the `for`.",
"Using a comprehension only for side effects such as printing."
],
"self_check": [
"Which part of the comprehension chooses the output value?",
"Which condition removes the unwanted item in the starter?"
],
"exercise_prompt": "Adjust the filter so the comprehension keeps the letters needed to print `ok`."
},
"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 example tries to parse text as an integer and uses `except ValueError` only for the parse failure path.",
"common_mistakes": [
"Catching every exception with bare `except:` and hiding bugs.",
"Putting unrelated code inside the `try` block.",
"Using exceptions for ordinary branches that an `if` could express directly."
],
"self_check": [
"Why is `except ValueError` better than a bare catch here?",
"What value should the program use after the parse fails?"
],
"exercise_prompt": "Handle the expected conversion error by assigning the recovery value that makes the output `12`."
},
"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 `StringIO` handle behaves like a small file; reading inside the `with` block captures `ok` before the handle is closed.",
"common_mistakes": [
"Trying to read a handle after the context manager has already closed it.",
"Opening files without a `with` block and forgetting cleanup.",
"Catching cleanup errors by accident with an overly broad exception handler."
],
"self_check": [
"Which line must run before the handle closes?",
"What cleanup does `with open(...)` normally give you?"
],
"exercise_prompt": "Read from the managed handle inside the `with` block, then print the saved text outside the block."
},
"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": "`math.ceil(2.1)` calls the standard-library function through the imported module and prints `3`.",
"common_mistakes": [
"Rewriting a standard-library helper instead of importing it.",
"Shadowing an imported module with a variable of the same name.",
"Putting imports deep inside code without a reason."
],
"self_check": [
"What name becomes available after `import math`?",
"Why does PEP 8 prefer imports near the top of the file?"
],
"exercise_prompt": "Use the imported `math` module to round upward so the result is `3`."
},
"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": "`Point(2, 3)` creates an object with `x` and `y` fields, and the example adds those fields like any other attributes.",
"common_mistakes": [
"Using a dataclass for behavior-heavy objects that need explicit methods first.",
"Forgetting the `@` when applying the decorator.",
"Changing mutable default fields without using the right default factory."
],
"self_check": [
"Which constructor call is generated for `Point`?",
"What fields should be read to calculate the sum?"
],
"exercise_prompt": "Use both dataclass fields so the point prints the sum of its coordinates."
},
"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": "`Iterable[int]` tells the reader that `total` can consume any iterable of integers and returns an `int` after calling `sum`.",
"common_mistakes": [
"Assuming annotations enforce every value at runtime by themselves.",
"Writing a complex type before the data shape is stable.",
"Letting the implementation contradict the annotated return type."
],
"self_check": [
"What does `Iterable[int]` promise about the argument?",
"Which line proves the function actually returns an integer total?"
],
"exercise_prompt": "Make the annotated function return the sum of the iterable it receives."
},
"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": "`next(countdown(3))` starts the generator, reaches the first `yield`, and receives `3` without building a whole list.",
"common_mistakes": [
"Calling a generator function and expecting the body to run immediately.",
"Forgetting that `next` can raise `StopIteration` after values are exhausted.",
"Building a list when a lazy stream would avoid unnecessary memory."
],
"self_check": [
"When does the body of `words()` actually run?",
"What is the first value yielded by the fixed starter?"
],
"exercise_prompt": "Yield `ok` as the first generated value so the existing `next` call prints it."
},
"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": "`make_adder(2)` returns a lambda that remembers `delta = 2`, so calling it with `3` prints `5`.",
"common_mistakes": [
"Using lambda for multi-step logic that deserves a named function.",
"Expecting a closure to copy a changing variable's old value automatically.",
"Forgetting to return the inner function from the outer function."
],
"self_check": [
"Which value is remembered by the lambda in `make_suffix`?",
"Why does `make_suffix('ok')` return a function instead of a string?"
],
"exercise_prompt": "Return a lambda that appends the captured suffix to its argument."
},
"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 identity decorator returns `fn` unchanged, so `word()` still calls the original function and prints `ok`.",
"common_mistakes": [
"Returning the result of calling `fn()` instead of returning a function object.",
"Forgetting that decorator code runs at definition time.",
"Adding a decorator when a simple helper call would be easier to read."
],
"self_check": [
"What argument does `identity` receive?",
"What must be returned for `word` to stay callable?"
],
"exercise_prompt": "Make the decorator return the original function so the decorated call prints `ok`."
},
"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 example sorts users by the score field with `key=lambda item: item[1]` and uses `reverse=True` to put the highest score first.",
"common_mistakes": [
"Sorting tuples by their default first field when the task asks for a later field.",
"Mutating with `.sort()` when the original order is still needed elsewhere.",
"Writing a key function that returns text for numeric ordering."
],
"self_check": [
"Which tuple field is the score?",
"Why does `reverse=True` matter for choosing the best user?"
],
"exercise_prompt": "Sort by score descending so the selected user is `Lin:5`."
},
"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 example counts each color and also groups words by first letter, then prints the count and group size for `red`.",
"common_mistakes": [
"Manually initializing every count when `Counter` can count the iterable.",
"Using a normal dict and then appending to a missing list key.",
"Grouping by the wrong key, such as the whole word instead of its first letter."
],
"self_check": [
"What does `Counter(words)['red']` return?",
"What list does `groups['r']` contain after grouping?"
],
"exercise_prompt": "Fill both the counter and the first-letter groups so the output is `2 2`."
},
"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 example adds `start` on the left and `end` on the right, then removes one value from each end.",
"common_mistakes": [
"Using `list.pop(0)` repeatedly for queue behavior.",
"Mixing up `append` and `appendleft`.",
"Reading from an empty deque without checking whether work remains."
],
"self_check": [
"Which method adds a value to the left side?",
"After adding both ends, what should `popleft` and `pop` return?"
],
"exercise_prompt": "Add the missing left and right values, then print the two removed end values."
},
"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": "`chain.from_iterable(parts)` flattens the nested one-letter lists lazily, and `join` consumes that iterator to print `ok`.",
"common_mistakes": [
"Forgetting that many itertools results are iterators, not reusable lists.",
"Slicing the input before the iterator sees all needed values.",
"Using a nested loop when a small itertools helper states the intent clearly."
],
"self_check": [
"What does `chain.from_iterable` flatten in the example?",
"Why does the starter lose the second letter?"
],
"exercise_prompt": "Flatten both inner lists lazily so the joined result is `ok`."
},
"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": "`PurePosixPath('logs/app.txt')` can report `stem` as `app` and `suffix` as `.txt` without touching the real filesystem.",
"common_mistakes": [
"Splitting paths by `/` even when path rules differ by platform.",
"Confusing `name`, `stem`, and `suffix`.",
"Using `Path` filesystem methods in a judge exercise that only needs pure path parsing."
],
"self_check": [
"What is the stem of `logs/app.txt`?",
"Why does `PurePosixPath` avoid filesystem access here?"
],
"exercise_prompt": "Use path properties to print the stem and suffix as `app:.txt`."
},
"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 example checks `add_two(3) == 5` before printing `ok`, so a broken function fails before producing success output.",
"common_mistakes": [
"Changing the assertion to match broken code instead of fixing the code.",
"Depending on assert for user-facing validation in optimized runtime modes.",
"Testing only the final print and not the small function that does the work."
],
"self_check": [
"What behavior should `add_two` guarantee?",
"Which line would fail if the function returned the input unchanged?"
],
"exercise_prompt": "Fix the function and the assertion so the check protects the intended `+2` behavior before printing `ok`."
},
"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": "`main` awaits `label()`, receives `ok`, prints it, and `asyncio.run(main())` starts the event loop for that coroutine.",
"common_mistakes": [
"Printing a coroutine object instead of awaiting it.",
"Calling `asyncio.run` from code that is already inside a running event loop.",
"Using async syntax for CPU-only work that has no waiting point."
],
"self_check": [
"Which expression actually waits for `label()` to finish?",
"Why does this exercise use `asyncio.run(main())` at the file boundary?"
],
"exercise_prompt": "Await the coroutine inside `main` and print the awaited result."
}
}
}
{
"schema_version": 1,
"programming_language": "python",
"ui_language": "es",
"lessons": {
"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 f-string usa `name` y `score` para formar `Ada:7`; los nombres de variables y las comillas no aparecen en stdout.",
"common_mistakes": [
"Dejar un print de depuración en la salida final.",
"Imprimir la representación de una lista cuando se espera texto plano.",
"Usar `return` en el nivel superior en vez de imprimir la respuesta."
],
"self_check": [
"¿Qué añade `print` al final por defecto?",
"¿Qué compara el juez si hay una línea extra?"
],
"exercise_prompt": "Usa `name` y `score` para imprimir solamente `Ada:7`."
},
"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": "`count = count + 2` lee el valor anterior, crea un entero nuevo y vuelve a enlazar `count` antes de imprimir `3`.",
"common_mistakes": [
"Leer `=` como comparación matemática.",
"Reutilizar el mismo nombre corto para significados distintos.",
"Esperar que un reenlace modifique automáticamente objetos anteriores."
],
"self_check": [
"Después de `x = 1; x = x + 2`, ¿qué vale `x`?",
"¿Qué valor temporal merece un nombre claro?"
],
"exercise_prompt": "Reenlaza `word` con el texto esperado e imprime ese nombre."
},
"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": "`7 // 2` calcula el cociente `3`; `7 % 2` calcula el resto `1`, que juntos describen la división entera.",
"common_mistakes": [
"Usar `/` cuando la respuesta pide división entera.",
"Tratar `%` como porcentaje en vez de resto.",
"Mezclar una salida float en un formato esperado de enteros."
],
"self_check": [
"¿Qué producen `7 // 2` y `7 % 2`?",
"¿Por qué `round` no reemplaza a `//`?"
],
"exercise_prompt": "Cambia la aritmética para imprimir el cociente y el resto como `3:1`."
},
"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": "`text[1:4]` lee los índices 1, 2 y 3 de `python`; el índice final 4 marca el límite y no se incluye.",
"common_mistakes": [
"Incluir mentalmente el índice final del slice.",
"Intentar asignar en `text[0]` aunque la cadena sea inmutable.",
"Contar posiciones desde 1 en vez de 0."
],
"self_check": [
"¿Qué lee `'code'[1]`?",
"¿Por qué `'xokx'[1:3]` tiene dos caracteres?"
],
"exercise_prompt": "Usa un slice para imprimir solo `ok`, sin los marcadores alrededor."
},
"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 código visita 1, 2 y 3, suma solo los impares y emite el total después de terminar el bucle.",
"common_mistakes": [
"Indentar la acumulación fuera del bucle.",
"Esperar que `range(n)` incluya `n`.",
"Crear un `while` cuya condición nunca cambia."
],
"self_check": [
"¿Qué líneas se ejecutan una vez por iteración?",
"¿Qué valores genera `range(1, 4)`?"
],
"exercise_prompt": "Edita el cuerpo del bucle para sumar solo los impares."
},
"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": "`area(3, 4)` pasa dos argumentos, la función los multiplica y el llamador imprime el `12` devuelto.",
"common_mistakes": [
"Definir una función y olvidar llamarla.",
"Imprimir cuando el llamador necesita un valor devuelto.",
"Retornar demasiado pronto dentro de un bucle."
],
"self_check": [
"¿Qué devuelve una función que llega al final sin `return`?",
"¿Dónde debería estar el print final?"
],
"exercise_prompt": "Haz que la función devuelva área, no perímetro."
},
"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": "`sys.stdin.read().split()` produce tokens de texto; aplicar `int` a cada token permite sumar números reales, no cadenas.",
"common_mistakes": [
"Usar muchas llamadas `input()` cuando una lectura completa es más simple.",
"Olvidar que `split()` devuelve cadenas.",
"Mezclar parsing con el algoritmo hasta hacerlo difícil de probar."
],
"self_check": [
"¿Qué tipo devuelve `sys.stdin.read()`?",
"¿Dónde manejarías una entrada vacía?"
],
"exercise_prompt": "Convierte todos los enteros de stdin y muestra su suma."
},
"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 acceso `scores['Ada']` obtiene solo la lista de Ada y `sum` calcula ese total sin revisar otros nombres.",
"common_mistakes": [
"Usar un índice de lista cuando los datos tienen una clave clara.",
"Consultar una clave sin decidir qué pasa si falta.",
"Imprimir la longitud cuando el problema pide la suma."
],
"self_check": [
"¿Cuándo conviene una clave de diccionario en vez de una posición?",
"¿Qué valor devuelve `scores['Ada']`?"
],
"exercise_prompt": "Usa la lista guardada bajo `Ada` para imprimir la suma sin escribir `5` directamente."
},
"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": "La tupla `('o', 'k')` conserva el orden para `join`, mientras `set(pair)` confirma que hay dos letras distintas.",
"common_mistakes": [
"Usar un conjunto cuando el orden de salida importa.",
"Intentar modificar una posición de una tupla.",
"Creer que insertar duplicados aumenta el tamaño del conjunto."
],
"self_check": [
"¿Qué parte necesita el orden de la tupla?",
"¿Qué vale `len(set(['a', 'a']))`?"
],
"exercise_prompt": "Construye el conjunto desde la tupla e imprime el texto unido y el conteo único."
},
"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": "`[n * n for n in nums if n % 2 == 0]` conserva pares, los eleva al cuadrado y deja una lista lista para sumar.",
"common_mistakes": [
"Meter demasiadas condiciones en una sola comprensión.",
"Olvidar que la expresión de salida va antes del `for`.",
"Usar comprensión solo por efectos secundarios como imprimir."
],
"self_check": [
"¿Qué parte elige el valor producido?",
"¿Qué condición elimina la letra no deseada?"
],
"exercise_prompt": "Ajusta el filtro para conservar las letras que forman `ok`."
},
"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 código intenta convertir texto a entero y usa `except ValueError` solo para esa falla de conversión.",
"common_mistakes": [
"Atrapar todo con `except:` y ocultar errores reales.",
"Poner código no relacionado dentro del `try`.",
"Usar excepciones para ramas normales que caben en un `if`."
],
"self_check": [
"¿Por qué `except ValueError` es más claro que un catch amplio?",
"¿Qué valor debe usarse después del fallo?"
],
"exercise_prompt": "Recupérate del fallo de conversión con el valor que permite imprimir `12`."
},
"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": "`StringIO` actúa como un archivo pequeño; leer dentro del bloque guarda `ok` antes de que el manejador se cierre.",
"common_mistakes": [
"Leer un manejador después de que el contexto lo cerró.",
"Abrir archivos sin `with` y olvidar cerrarlos.",
"Ocultar errores de limpieza con una captura demasiado amplia."
],
"self_check": [
"¿Qué línea debe ejecutarse antes del cierre?",
"¿Qué limpieza aporta normalmente `with open(...)`?"
],
"exercise_prompt": "Lee dentro del bloque `with` y luego imprime el texto guardado."
},
"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": "`math.ceil(2.1)` llama la función de redondeo hacia arriba del módulo importado y produce `3`.",
"common_mistakes": [
"Reescribir una utilidad que ya está en la biblioteca estándar.",
"Sobrescribir el nombre de un módulo importado con una variable.",
"Esconder imports dentro de código sin necesidad."
],
"self_check": [
"¿Qué nombre queda disponible tras `import math`?",
"¿Por qué PEP 8 coloca imports cerca del inicio?"
],
"exercise_prompt": "Usa el módulo `math` importado para redondear hacia arriba."
},
"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": "`Point(2, 3)` usa el constructor generado y permite leer `x` e `y` como atributos normales.",
"common_mistakes": [
"Usar dataclass antes de diseñar comportamiento importante.",
"Olvidar el `@` del decorador.",
"Poner valores mutables por defecto sin factory adecuada."
],
"self_check": [
"¿Qué constructor se genera para `Point`?",
"¿Qué campos hay que leer para sumar coordenadas?"
],
"exercise_prompt": "Lee ambos campos para imprimir la suma del punto."
},
"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": "`Iterable[int]` comunica que la función acepta cualquier iterable de enteros y devuelve un `int` calculado con `sum`.",
"common_mistakes": [
"Creer que las anotaciones validan todo en runtime.",
"Escribir tipos complejos antes de estabilizar la forma de datos.",
"Devolver algo que contradice la anotación."
],
"self_check": [
"¿Qué promete `Iterable[int]` sobre el argumento?",
"¿Qué línea prueba que se devuelve un total entero?"
],
"exercise_prompt": "Haz que la función anotada devuelva la suma del iterable recibido."
},
"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": "`next(countdown(3))` inicia el generador, llega al primer `yield` y recibe `3` sin construir una lista.",
"common_mistakes": [
"Esperar que el cuerpo corra al llamar la función generadora.",
"Olvidar que `next` puede lanzar `StopIteration`.",
"Construir una lista grande cuando bastaba un flujo perezoso."
],
"self_check": [
"¿Cuándo se ejecuta realmente `words()`?",
"¿Cuál debe ser el primer valor yielded?"
],
"exercise_prompt": "Haz que el primer `yield` produzca `ok`. No fijes la respuesta; conserva el patrón practicado."
},
"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": "`make_adder(2)` devuelve una lambda que recuerda `delta = 2`; al llamarla con `3` imprime `5`.",
"common_mistakes": [
"Poner lógica de varios pasos dentro de lambda.",
"Esperar que un closure copie automáticamente un valor antiguo de una variable cambiante.",
"Olvidar devolver la función interna."
],
"self_check": [
"¿Qué valor recuerda la lambda de `make_suffix`?",
"¿Por qué `make_suffix('ok')` devuelve una función?"
],
"exercise_prompt": "Devuelve una lambda que añada el sufijo capturado."
},
"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 decorador identidad devuelve `fn` sin cambios, por eso `word()` sigue llamando la función original y produce `ok`.",
"common_mistakes": [
"Devolver `fn()` en vez del objeto función.",
"Creer que el decorador se ejecuta solo al llamar la función.",
"Añadir decorador cuando una llamada auxiliar sería más legible."
],
"self_check": [
"¿Qué argumento recibe `identity`?",
"¿Qué debe devolverse para que `word` siga siendo invocable?"
],
"exercise_prompt": "Devuelve la función original desde el decorador."
},
"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": "La ordenación usa `key=lambda item: item[1]` para comparar puntuaciones y `reverse=True` para poner primero la mayor.",
"common_mistakes": [
"Ordenar tuplas por el primer campo cuando se pide otro campo.",
"Usar `.sort()` aunque se necesite conservar el orden original.",
"Devolver texto desde key para una comparación numérica."
],
"self_check": [
"¿Qué campo de la tupla es la puntuación?",
"¿Por qué hace falta `reverse=True`?"
],
"exercise_prompt": "Ordena por puntuación descendente para seleccionar `Lin:5`."
},
"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": "Counter cuenta colores y defaultdict agrupa palabras por inicial; después se imprimen el conteo de `red` y el tamaño del grupo `r`.",
"common_mistakes": [
"Repetir inicialización manual en lugar de usar Counter.",
"Hacer append sobre una lista ausente en un dict normal.",
"Agrupar por la palabra completa cuando se pide la inicial."
],
"self_check": [
"¿Qué devuelve `Counter(words)['red']`?",
"¿Qué contiene `groups['r']` tras agrupar?"
],
"exercise_prompt": "Completa conteo y agrupación por inicial para imprimir `2 2`."
},
"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 código agrega `start` a la izquierda y `end` a la derecha, luego quita un valor de cada extremo.",
"common_mistakes": [
"Usar `list.pop(0)` repetidamente para una cola.",
"Confundir `append` con `appendleft`.",
"Sacar de un deque vacío sin comprobarlo."
],
"self_check": [
"¿Qué método agrega por la izquierda?",
"¿Qué deben devolver `popleft` y `pop` después de agregar ambos extremos?"
],
"exercise_prompt": "Agrega los valores faltantes y muestra los dos extremos removidos."
},
"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": "`chain.from_iterable(parts)` aplana las listas internas de letras y `join` consume ese iterador para formar `ok`.",
"common_mistakes": [
"Tratar resultados de itertools como listas reutilizables.",
"Recortar la entrada antes de que el iterador vea todos los valores.",
"Ocultar una intención simple detrás de bucles anidados."
],
"self_check": [
"¿Qué aplana `chain.from_iterable`?",
"¿Por qué el código inicial pierde la segunda letra?"
],
"exercise_prompt": "Aplana ambas listas internas de forma perezosa para formar `ok`."
},
"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": "`PurePosixPath('logs/app.txt')` obtiene `stem` y `suffix` sin abrir ningún archivo real.",
"common_mistakes": [
"Dividir rutas siempre con `/` manualmente.",
"Confundir `name`, `stem` y `suffix`.",
"Tocar el sistema de archivos cuando solo se requiere parsear la ruta."
],
"self_check": [
"¿Cuál es el stem de `logs/app.txt`?",
"¿Por qué `PurePosixPath` evita acceso al sistema de archivos?"
],
"exercise_prompt": "Usa propiedades de la ruta para imprimir `app:.txt`."
},
"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 código verifica `add_two(3) == 5` antes de imprimir `ok`; una función rota falla antes de aparentar éxito.",
"common_mistakes": [
"Cambiar la aserción para que acepte código roto.",
"Depender de assert para validación de usuarios en producción.",
"Probar solo el print final y no la función pequeña."
],
"self_check": [
"¿Qué debe garantizar `add_two`?",
"¿Qué línea fallaría si la función devuelve la entrada sin cambios?"
],
"exercise_prompt": "Arregla la función y la aserción para proteger el comportamiento `+2`."
},
"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": "`main` espera `label()` con await, recibe `ok` y `asyncio.run` arranca la corrutina superior.",
"common_mistakes": [
"Imprimir el objeto corrutina sin hacer await.",
"Llamar `asyncio.run` dentro de un bucle de eventos ya activo.",
"Usar async para trabajo solo de CPU que no espera nada."
],
"self_check": [
"¿Qué expresión espera a que termine `label()`?",
"¿Por qué `asyncio.run` está en el borde del archivo?"
],
"exercise_prompt": "Haz await dentro de `main` e imprime el resultado recibido."
}
}
}
{
"schema_version": 1,
"programming_language": "python",
"ui_language": "ja",
"lessons": {
"py-output": {
"title": "print と標準出力",
"concept": "`print` は計算結果を判定対象の標準出力へ出す境界である。引数を文字列化し、複数引数の間に空白を入れ、既定で改行するため、余分な出力はそのまま不一致になる。",
"worked_example": "f-string は `name` と `score` の値から `Ada:7` を作る。変数名や引用符は表示されない。",
"common_mistakes": [
"最終提出にデバッグ用の print を残す。",
"平文が必要なのにリストの表示形式を出す。",
"トップレベルで `return` すれば出力になると思う。"
],
"self_check": [
"print は既定で末尾に何を追加するか。",
"1 行多い stdout を判定器はどう扱うか。"
],
"exercise_prompt": "`name` と `score` を使い、余分な文字なしで `Ada:7` だけを出力する。"
},
"py-variables": {
"title": "変数",
"concept": "変数名はオブジェクトへの束縛である。`=` は等価比較ではなく、以後その名前を読んだときにどの値を見るかを決める。",
"worked_example": "`count = count + 2` は古い値を読み、新しい整数を作り、`count` をその値へ束縛し直してから `3` を出力する。",
"common_mistakes": [
"`=` を数学の等号として読む。",
"同じ短い名前を別の意味に使い回す。",
"再束縛が過去のオブジェクトまで変更すると考える。"
],
"self_check": [
"`x = 1; x = x + 2` の後で `x` は何を指すか。",
"どの中間値に名前を付けると読みやすいか。"
],
"exercise_prompt": "`word` を期待する文字列に束縛し直し、その名前を出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-numbers": {
"title": "数値",
"concept": "Python の整数は実用上大きな値も扱える。`/` は float、`//` は整数商、`%` は余り、`**` は累乗なので、問題の形式に合う演算子を選ぶ。",
"worked_example": "`7 // 2` は商 `3`、`7 % 2` は余り `1` を作り、整数除算の結果を分けて示す。",
"common_mistakes": [
"整数商が必要な場面で `/` を使う。",
"`%` を余りではなく百分率と考える。",
"整数出力に float 表記を混ぜる。"
],
"self_check": [
"`7 // 2` と `7 % 2` は何になるか。",
"`round` が `//` の代わりにならない場面はどこか。"
],
"exercise_prompt": "演算を直して、商と余りを `3:1` と出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-strings": {
"title": "文字列",
"concept": "文字列は変更できない Unicode 文字の列である。インデックスは 1 文字を読み、スライスは終端を含まない範囲を読み、元の文字列は変わらない。",
"worked_example": "`text[1:4]` は `python` の 1、2、3 番目だけを読む。終端の 4 番目は範囲の外である。",
"common_mistakes": [
"スライスの終端インデックスを含めて考える。",
"`text[0]` へ代入して文字列を変更しようとする。",
"位置を 1 から数える。"
],
"self_check": [
"`'code'[1]` は何を読むか。",
"`'xokx'[1:3]` が 2 文字になる理由は何か。"
],
"exercise_prompt": "スライスを使って、周囲の印ではなく `ok` だけを出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-control-flow": {
"title": "制御フロー",
"concept": "`if` はブロックを選び、`for` は iterable を消費し、`while` は条件が変わるまで繰り返す。Python ではインデントが構文なので、行の位置が実行範囲を決める。",
"worked_example": "コードは 1、2、3 を訪問し、奇数だけを合計して、ループ後に最終合計を出力する。 合計がいつ更新されるかを確認する例でもある。",
"common_mistakes": [
"加算行をループの外へインデントしてしまう。",
"`range(n)` が n を含むと思い込む。",
"条件が変わらない while ループを書く。"
],
"self_check": [
"どの行が各反復で実行されるか。",
"`range(1, 4)` はどの値を出すか。"
],
"exercise_prompt": "ループ本体を直し、奇数だけを合計する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-functions": {
"title": "関数",
"concept": "`def` は呼び出し可能な関数オブジェクトを作る。引数は仮引数に入り、`return` は呼び出し元へ値を返す。関数内の print とは役割が違う。",
"worked_example": "`area(3, 4)` は 2 つの引数を渡し、関数が掛け算した結果 `12` を呼び出し元が出力する。",
"common_mistakes": [
"関数を定義しただけで呼び出さない。",
"呼び出し元が値を必要とするのに関数内で出力だけする。",
"ループ内で早すぎる return を書く。"
],
"self_check": [
"return なしで終端に到達した関数は何を返すか。",
"最後の print はどこに置くべきか。"
],
"exercise_prompt": "関数が周長ではなく面積を返すように直す。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-input": {
"title": "入力の解析",
"concept": "競技プログラミングの入力は stdin のテキストから始まる。一度読んでトークン化し、型変換を済ませてから通常の値として解くと流れが明確になる。",
"worked_example": "`sys.stdin.read().split()` は文字列トークンを作る。各トークンへ `int` を適用してから合計する。",
"common_mistakes": [
"行数が不明なのに `input()` を何度も呼ぶ。",
"split の結果がまだ文字列であることを忘れる。",
"解析処理をアルゴリズムの途中へ混ぜる。"
],
"self_check": [
"`sys.stdin.read()` の戻り値の型は何か。",
"空入力はどの段階で扱うべきか。"
],
"exercise_prompt": "stdin の整数をすべて変換し、その合計を出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-lists-dicts": {
"title": "リストと辞書",
"concept": "リストは順序付きの値を保持し、辞書はキーから値を直接引く。名前、文字、ID で探すデータなら、位置を走査するより辞書のほうが意図を表しやすい。",
"worked_example": "`scores['Ada']` は Ada の点数リストだけを取り出し、`sum` がそのリストの合計を計算する。",
"common_mistakes": [
"明確なキーがあるのにリストの位置で探す。",
"存在しないキーの扱いを決めずに添字アクセスする。",
"合計が必要なのに長さを出力する。"
],
"self_check": [
"どんなときにリスト位置より辞書キーがよいか。",
"`scores['Ada']` は何を返すか。"
],
"exercise_prompt": "`Ada` の下にあるリストを使い、`5` を直書きせず合計を出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-tuples-sets": {
"title": "タプルとセット",
"concept": "タプルは位置が固定された値のまとまりで、セットは重複しない要素と高速な membership 判定に向く。順序が必要な出力ではセットだけに頼らない。",
"worked_example": "タプル `('o', 'k')` は結合順を保ち、`set(pair)` は異なる文字が 2 個あることを示す。",
"common_mistakes": [
"出力順が必要なのにセットを使う。",
"タプルの要素をその場で変更しようとする。",
"重複挿入でセットの長さが増えると思う。"
],
"self_check": [
"どの処理がタプルの順序を必要とするか。",
"`len(set(['a', 'a']))` はいくつか。"
],
"exercise_prompt": "タプルからセットを作り、結合した文字列とユニーク数を出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-comprehensions": {
"title": "内包表記",
"concept": "内包表記は出力式、ループ、任意のフィルタでコレクションを作る。単純な変換や抽出なら読みやすいが、複数手順の副作用には通常のループが向く。",
"worked_example": "`[n * n for n in nums if n % 2 == 0]` は偶数だけを残して二乗し、合計できるリストを作る。",
"common_mistakes": [
"条件を詰め込みすぎて読みにくくする。",
"出力式が `for` の前にあることを忘れる。",
"print など副作用だけのために内包表記を使う。"
],
"self_check": [
"生成される値を決める部分はどこか。",
"不要な文字を落とす条件はどれか。"
],
"exercise_prompt": "フィルタを調整して `ok` を作る文字だけを残す。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-errors": {
"title": "例外",
"concept": "例外は通常経路と回復可能な失敗を分ける。`try` は小さく保ち、`ValueError` のような具体的な例外だけを捕まえる。",
"worked_example": "文字列を整数へ変換する行だけが失敗候補で、変換失敗だけを `except ValueError` が処理する。",
"common_mistakes": [
"bare except で本物のバグまで隠す。",
"関係ない処理まで try に入れる。",
"if で書ける通常分岐を例外にする。"
],
"self_check": [
"広い catch より `except ValueError` がよい理由は何か。",
"失敗後に使う回復値は何か。"
],
"exercise_prompt": "変換失敗を処理し、`12` が出る回復値を入れる。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-files-context": {
"title": "ファイルとコンテキストマネージャ",
"concept": "`with` は管理されたスコープに入り、抜けるときに cleanup を実行する。ファイルや contextlib の道具は、途中で抜けても閉じる処理を保証する。",
"worked_example": "`StringIO` は小さなファイルのように振る舞う。`with` ブロック内で読んだ値なら、ブロック外で表示できる。",
"common_mistakes": [
"コンテキスト終了後に閉じたハンドルを読む。",
"with を使わず開いたファイルを閉じ忘れる。",
"広すぎる例外処理で cleanup の問題を隠す。"
],
"self_check": [
"ハンドルが閉じる前に実行すべき行はどれか。",
"`with open(...)` は通常どんな後始末をするか。"
],
"exercise_prompt": "`with` 内で読み、保存した文字列を外で出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-modules-imports": {
"title": "モジュールと import",
"concept": "モジュールは再利用コードを名前空間にまとめる。`import math` は標準ライブラリを使えるようにし、PEP 8 は依存関係を見やすくするため import を上部に置く。",
"worked_example": "`math.ceil(2.1)` は import したモジュールの切り上げ関数を呼び、`3` を返す。",
"common_mistakes": [
"標準ライブラリにある関数を作り直す。",
"import したモジュール名を変数で上書きする。",
"理由なく import をコードの奥に隠す。"
],
"self_check": [
"`import math` の後に使える名前は何か。",
"PEP 8 が import を先頭付近に置く理由は何か。"
],
"exercise_prompt": "import 済みの `math` で切り上げ結果を出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-dataclasses": {
"title": "dataclass",
"concept": "`@dataclass` はデータ保持が中心のクラスに、初期化や表示などの定型メソッドを生成する。フィールド名と型ヒントが定義にまとまる。",
"worked_example": "`Point(2, 3)` は生成されたコンストラクタを使い、`x` と `y` を通常の属性として読める。",
"common_mistakes": [
"振る舞いが中心の設計に先に dataclass を付ける。",
"デコレータの `@` を忘れる。",
"可変デフォルトを factory なしで置く。"
],
"self_check": [
"`Point` にはどんなコンストラクタができるか。",
"座標の合計にはどのフィールドが必要か。"
],
"exercise_prompt": "2 つのフィールドを読み、座標の合計を出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-typing": {
"title": "型ヒント",
"concept": "型ヒントは読者、エディタ、型チェッカへ期待する形を伝える。実行時の値は動的なので、関数本体も注釈の契約に合っていなければならない。",
"worked_example": "`Iterable[int]` は整数を反復できる値を受け取り、`sum` した `int` を返す意図を示す。",
"common_mistakes": [
"注釈が実行時にすべて検証すると考える。",
"データ形状が固まる前に複雑な型を書く。",
"戻り値注釈と違う値を返す。"
],
"self_check": [
"`Iterable[int]` は引数について何を約束するか。",
"整数合計を返すことはどの行で分かるか。"
],
"exercise_prompt": "注釈付き関数が受け取った iterable の合計を返すようにする。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-generators": {
"title": "イテレータとジェネレータ",
"concept": "イテレータは値を 1 つずつ生成する。ジェネレータ関数は `yield` を使い、呼び出し時には生成器オブジェクトを返し、次の値を求められた時に本体を進める。",
"worked_example": "`next(countdown(3))` は生成器を開始し、最初の `yield` で `3` を受け取る。リスト全体は作らない。",
"common_mistakes": [
"生成器関数を呼ぶだけで本体が走ると思う。",
"値が尽きた後の `StopIteration` を忘れる。",
"lazy な流れでよいのに大きなリストを作る。"
],
"self_check": [
"`words()` の本体はいつ実行されるか。",
"修正後の最初の yield 値は何か。"
],
"exercise_prompt": "最初の `yield` が `ok` を返すようにする。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-lambdas-closures": {
"title": "ラムダとクロージャ",
"concept": "`lambda` は 1 つの式から小さな関数を作る。その関数が外側の名前を使うとクロージャになり、外側の呼び出しが終わっても値を覚えている。",
"worked_example": "`make_adder(2)` が返す lambda は `delta = 2` を覚え、後で `3` を渡すと `5` を返す。",
"common_mistakes": [
"複数手順の処理を lambda に押し込む。",
"変化する変数の古い値が自動コピーされると思う。",
"内側の関数を return し忘れる。"
],
"self_check": [
"`make_suffix` の lambda は何を覚えるか。",
"`make_suffix('ok')` が文字列ではなく関数を返す理由は何か。"
],
"exercise_prompt": "捕捉した suffix を引数へ付ける lambda を返す。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-decorators": {
"title": "デコレータ",
"concept": "デコレータは関数定義時に実行される。元の関数オブジェクトを受け取り、その名前に最終的に束縛するオブジェクトを返す。",
"worked_example": "identity デコレータは `fn` をそのまま返すため、`word()` は元の関数を呼び `ok` を返す。",
"common_mistakes": [
"関数オブジェクトではなく `fn()` の結果を返す。",
"デコレータが呼び出し時にだけ動くと思う。",
"補助関数呼び出しで済む場面にデコレータを追加する。"
],
"self_check": [
"`identity` はどんな引数を受け取るか。",
"`word` を呼び出し可能に保つには何を返すべきか。"
],
"exercise_prompt": "デコレータから元の関数を返す。 答えだけを直書きせず、この課題の構文を残して修正する。 出力が期待値だけになることも確認する。"
},
"py-sorting-keys": {
"title": "ソートと key 関数",
"concept": "`sorted` は新しいソート済みリストを返す。`key` 関数は各要素から比較に使う値を取り出し、タプルやオブジェクトを特定フィールドで並べられる。",
"worked_example": "`key=lambda item: item[1]` は点数フィールドを比較に使い、`reverse=True` で最大点を先頭に置く。",
"common_mistakes": [
"必要なフィールドではなくタプルの先頭で並べる。",
"元の順序が必要なのに `.sort()` で破壊する。",
"数値比較に文字列を key として返す。"
],
"self_check": [
"タプルのどのフィールドが点数か。",
"最高点を選ぶのに `reverse=True` が必要な理由は何か。"
],
"exercise_prompt": "点数の降順で並べ、`Lin:5` を選ぶ。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-counter-defaultdict": {
"title": "Counter と defaultdict",
"concept": "`Counter` は hash 可能な値を数え、`defaultdict(list)` は新しいグループに空リストを自動で作る。数え上げとグループ化の初期化コードを減らせる。",
"worked_example": "Counter は色を数え、defaultdict は最初の文字ごとに単語を集める。最後に `red` の数と `r` グループの大きさを出す。",
"common_mistakes": [
"Counter の代わりに手動初期化を繰り返す。",
"通常の dict の存在しないリストへ append する。",
"先頭文字ではなく単語全体でグループ化する。"
],
"self_check": [
"`Counter(words)['red']` は何を返すか。",
"`groups['r']` には何が入るか。"
],
"exercise_prompt": "単語数と先頭文字グループの両方を作り、`2 2` を出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-deque": {
"title": "deque",
"concept": "`deque` は両端キューで、左右どちらの append/pop も効率がよい。BFS や前から取り除くスライディングウィンドウでよく使う。",
"worked_example": "左に `start`、右に `end` を追加し、左右の端から 1 つずつ取り出す。 取り出し順がキュー動作の要点であることを示す。",
"common_mistakes": [
"キュー処理に `list.pop(0)` を繰り返す。",
"`append` と `appendleft` の向きを取り違える。",
"空の deque から取り出す。"
],
"self_check": [
"左端へ追加するメソッドは何か。",
"両端追加後に `popleft` と `pop` は何を返すべきか。"
],
"exercise_prompt": "不足している左右の値を追加し、取り出した 2 つを出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-itertools": {
"title": "itertools",
"concept": "`itertools` は `chain`、`islice`、`product`、`pairwise` などの lazy な反復部品を提供する。必要になるまで中間リストを作らない。",
"worked_example": "`chain.from_iterable(parts)` は内側の 1 文字リストを平坦化し、`join` がその iterator を消費して `ok` を作る。",
"common_mistakes": [
"itertools の結果を何度も使えるリストだと思う。",
"入力を先に切って必要な値を失う。",
"意図が明確な標準道具を使わずネストしたループで隠す。"
],
"self_check": [
"`chain.from_iterable` は何を平坦化するか。",
"開始コードはなぜ 2 文字目を失うか。"
],
"exercise_prompt": "2 つの内側リストを lazy に平坦化し、`ok` を作る。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-pathlib": {
"title": "pathlib",
"concept": "`pathlib` はパスを文字列ではなくオブジェクトとして扱う。`name`、`stem`、`suffix`、`parent` を使うと区切り文字の手作業が減る。",
"worked_example": "`PurePosixPath('logs/app.txt')` は実ファイルを開かずに stem `app` と suffix `.txt` を取り出せる。",
"common_mistakes": [
"常に `/` でパス文字列を分割する。",
"`name`、`stem`、`suffix` を混同する。",
"純粋なパス解析だけでよいのにファイルシステムへ触る。"
],
"self_check": [
"`logs/app.txt` の stem は何か。",
"ここで `PurePosixPath` が適切な理由は何か。"
],
"exercise_prompt": "path の属性を使い、`app:.txt` を出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-testing-assert": {
"title": "テストと assert",
"concept": "`assert` は小さな例やテストで条件を確認する文である。テストフレームワークはこの確認を繰り返せる形に整理するが、期待値と実際値を比べる考え方は同じである。",
"worked_example": "`add_two(3) == 5` を確認してから `ok` を出すため、壊れた関数は成功表示の前に止まる。",
"common_mistakes": [
"壊れたコードに合わせて assert を変える。",
"ユーザー入力の検証を assert だけに任せる。",
"小さな関数を試さず最後の出力だけを見る。"
],
"self_check": [
"`add_two` は何を保証すべきか。",
"入力をそのまま返したらどの行が失敗するか。"
],
"exercise_prompt": "関数と assert を直し、`+2` の動作を確認してから `ok` を出す。 答えだけを直書きせず、この課題の構文を残して修正する。"
},
"py-async": {
"title": "非同期の概念",
"concept": "`async def` はコルーチン関数を作る。呼び出すとコルーチンオブジェクトになり、別のコルーチン内で `await` して結果を受け取り、境界で `asyncio.run` が実行する。",
"worked_example": "`main` が `label()` を await して `ok` を受け取り、`asyncio.run` が最上位コルーチンを起動する。",
"common_mistakes": [
"await せずにコルーチンオブジェクトを表示する。",
"既に動くイベントループ内で `asyncio.run` を呼ぶ。",
"待ち点のない CPU 処理へ async を付ける。"
],
"self_check": [
"どの式が `label()` の完了を待つか。",
"ファイル境界で `asyncio.run` を使う理由は何か。"
],
"exercise_prompt": "`main` の中で await し、受け取った結果を出力する。 答えだけを直書きせず、この課題の構文を残して修正する。"
}
}
}
{
"schema_version": 1,
"programming_language": "python",
"ui_language": "ko",
"lessons": {
"py-output": {
"title": "print와 표준 출력",
"concept": "`print`는 계산 결과가 채점기에서 보이는 표준 출력으로 나가는 경계다. 기본으로 인자 사이에 공백을 넣고 줄바꿈을 붙이므로, 문제 풀이에서는 불필요한 한 줄도 오답이 된다.",
"worked_example": "예제는 f-string으로 이미 있는 `name`과 `score`를 `Ada:7` 형식으로 만든다. 따옴표나 변수 이름은 출력되지 않는다.",
"common_mistakes": [
"채점 출력에 디버그용 print를 남긴다.",
"평문이 필요한데 리스트나 튜플 표현 그대로 출력한다.",
"최종 결과를 top-level에서 return하려고 한다."
],
"self_check": [
"채점 출력에 디버그용 print를 남긴다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "주어진 `name`과 `score`를 사용해 `Ada:7`만 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-variables": {
"title": "변수",
"concept": "변수 이름은 객체에 붙은 이름표다. `=`는 같은지 비교하는 기호가 아니라 이름을 값에 묶는 동작이며, 다시 대입하면 이후 그 이름을 읽을 때 새 값을 보게 된다.",
"worked_example": "`count = count + 2`는 예전 값을 읽어 새 정수를 만든 뒤 `count`가 그 새 값을 가리키게 한다.",
"common_mistakes": [
"`=`를 값 비교로 읽는다.",
"서로 다른 의미에 같은 짧은 이름을 계속 재사용한다.",
"재대입이 이전 객체 전체를 바꾼다고 생각한다."
],
"self_check": [
"`=`를 값 비교로 읽는다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "`word`를 기대 문자열로 다시 묶고 그 이름을 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-numbers": {
"title": "숫자",
"concept": "Python의 정수는 큰 값도 다룰 수 있고, `/`, `//`, `%`, `**`는 서로 다른 숫자 의미를 가진다. 몫과 나머지가 필요한 문제에서 실수 나눗셈을 쓰면 출력 형식부터 어긋난다.",
"worked_example": "`7 // 2`는 몫 `3`, `7 % 2`는 나머지 `1`을 만든다. 둘을 같이 쓰면 나눗셈 구조가 분명해진다.",
"common_mistakes": [
"정수 몫이 필요한 곳에 `/`를 사용한다.",
"`%`를 나머지가 아니라 퍼센트로 착각한다.",
"정수 출력 문제에 실수 문자열을 섞는다."
],
"self_check": [
"정수 몫이 필요한 곳에 `/`를 사용한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "몫과 나머지를 사용해 `3:1` 형식을 만들라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-strings": {
"title": "문자열",
"concept": "문자열은 바꿀 수 없는 문자 시퀀스다. 인덱스는 한 문자를 읽고, 슬라이스는 끝 인덱스를 포함하지 않는 구간을 읽으며, 이어 붙이기는 새 문자열을 만든다.",
"worked_example": "`text[1:4]`는 1번, 2번, 3번 인덱스만 읽는다. 끝 인덱스 4의 문자는 포함하지 않는다.",
"common_mistakes": [
"슬라이스의 끝 인덱스가 포함된다고 생각한다.",
"문자열 한 글자를 직접 대입해 바꾸려 한다.",
"인덱스를 1부터 센다."
],
"self_check": [
"슬라이스의 끝 인덱스가 포함된다고 생각한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "표시 문자 사이의 `ok`만 슬라이스로 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-control-flow": {
"title": "제어 흐름",
"concept": "`if`는 실행할 블록을 고르고, `for`는 iterable을 하나씩 소비하며, `while`은 조건이 바뀔 때까지 반복한다. Python에서는 들여쓰기가 문법이라 한 칸의 위치가 실행 범위를 바꾼다.",
"worked_example": "예제는 1부터 3까지 방문하면서 홀수일 때만 합계에 더한다. 반복이 끝난 뒤 한 번만 최종 합계를 출력한다.",
"common_mistakes": [
"누적 코드를 반복문 밖으로 들여쓴다.",
"`range(n)`이 n까지 포함한다고 생각한다.",
"조건이 변하지 않는 while 루프를 만든다."
],
"self_check": [
"누적 코드를 반복문 밖으로 들여쓴다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "홀수만 합산하도록 반복문 본문을 고치라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-functions": {
"title": "함수",
"concept": "`def`는 호출 가능한 함수를 만들고, 매개변수는 전달된 인자를 받는다. `return`은 호출자에게 값을 돌려주며 함수 안에서 출력하는 것과는 역할이 다르다.",
"worked_example": "`area(3, 4)` 호출은 두 인자를 전달하고, 함수가 곱한 값을 돌려주면 호출자가 그 값을 출력한다.",
"common_mistakes": [
"정의만 하고 호출하지 않는다.",
"호출자가 값이 필요한데 함수 안에서 출력만 한다.",
"반복문 안에서 너무 빨리 return한다."
],
"self_check": [
"정의만 하고 호출하지 않는다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "함수가 둘레가 아니라 넓이를 반환하게 하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-input": {
"title": "입력 파싱",
"concept": "코딩 테스트 입력은 표준 입력의 텍스트에서 시작한다. 보통 한 번 읽고, 공백으로 토큰을 나누고, 필요한 타입으로 바꾼 뒤 알고리즘은 변환된 값으로만 풀면 읽기 쉽다.",
"worked_example": "`sys.stdin.read().split()`으로 토큰을 얻고 각 토큰에 `int`를 적용해야 숫자 합계를 낼 수 있다.",
"common_mistakes": [
"여러 줄 입력을 작은 input 호출 여러 개로 억지 처리한다.",
"split 결과가 여전히 문자열임을 잊는다.",
"파싱과 알고리즘을 뒤섞어 확인하기 어렵게 만든다."
],
"self_check": [
"여러 줄 입력을 작은 input 호출 여러 개로 억지 처리한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "표준 입력의 모든 정수를 파싱해 합계를 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-lists-dicts": {
"title": "리스트와 딕셔너리",
"concept": "리스트는 순서 있는 값을 인덱스와 반복으로 다룬다. 딕셔너리는 키를 값에 매핑하므로 이름, 문자, ID처럼 의미 있는 식별자로 바로 찾아야 할 때 적합하다.",
"worked_example": "`scores['Ada']`는 Ada의 점수 리스트를 바로 찾는다. 그 리스트를 `sum`에 넘기면 다른 키는 신경 쓰지 않아도 된다.",
"common_mistakes": [
"의미 있는 키가 있는데 리스트 인덱스로 찾으려 한다.",
"없는 키를 조회할 때의 동작을 정하지 않는다.",
"합계를 요구하는데 길이를 출력한다."
],
"self_check": [
"의미 있는 키가 있는데 리스트 인덱스로 찾으려 한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "`Ada` 키 아래의 리스트를 사용해 합계를 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-tuples-sets": {
"title": "튜플과 세트",
"concept": "튜플은 위치가 고정된 작은 기록에 좋고, 세트는 중복 없는 원소와 빠른 포함 검사용이다. 출력 순서가 필요하면 튜플이나 리스트를 유지하고, 유일성 판단만 세트에 맡긴다.",
"worked_example": "튜플 `('o', 'k')`는 출력 순서를 보존하고, `set(pair)`는 서로 다른 문자가 두 개인지 확인한다.",
"common_mistakes": [
"출력 순서가 중요한데 세트를 사용한다.",
"튜플 원소를 제자리에서 바꾸려 한다.",
"세트에 중복을 넣으면 길이가 늘어난다고 생각한다."
],
"self_check": [
"출력 순서가 중요한데 세트를 사용한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "튜플을 이어 붙이고 유일한 원소 수도 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-comprehensions": {
"title": "컴프리헨션",
"concept": "컴프리헨션은 출력 표현식, 반복, 선택적 필터를 한 식에 묶어 컬렉션을 만든다. 단순 변환이나 필터에는 강하지만, 여러 단계 부작용은 보통 반복문이 더 읽기 쉽다.",
"worked_example": "예제의 컴프리헨션은 짝수만 통과시키고 그 값을 제곱한다. 결과 리스트는 바로 합계 계산에 쓸 수 있다.",
"common_mistakes": [
"한 줄에 너무 많은 조건과 변환을 넣는다.",
"출력 표현식이 for 앞에 온다는 순서를 잊는다.",
"출력 같은 부작용만 위해 컴프리헨션을 쓴다."
],
"self_check": [
"한 줄에 너무 많은 조건과 변환을 넣는다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "필터를 고쳐 필요한 글자만 남겨 `ok`를 만들라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-errors": {
"title": "예외",
"concept": "예외는 정상 흐름과 복구 가능한 실패 흐름을 분리한다. 실패할 수 있는 줄만 작게 `try`에 넣고, 복구할 수 있는 구체적인 예외만 `except ValueError`처럼 잡아야 한다.",
"worked_example": "문자열을 정수로 바꾸다 실패하는 줄만 `try`에 두고, 실패하면 복구 값을 넣어 프로그램을 계속 진행한다.",
"common_mistakes": [
"bare except로 모든 예외를 숨긴다.",
"관련 없는 코드까지 try 안에 넣는다.",
"if로 충분한 정상 분기를 예외로 처리한다."
],
"self_check": [
"bare except로 모든 예외를 숨긴다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "예상한 변환 실패에서 복구 값을 넣어 `12`를 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-files-context": {
"title": "파일과 컨텍스트 매니저",
"concept": "`with`는 컨텍스트 매니저에 들어갔다가 블록이 끝나면 정리 코드를 실행한다. 파일 핸들이나 contextlib 도구는 이 패턴으로 닫기 같은 정리를 안정적으로 처리한다.",
"worked_example": "`StringIO` 핸들은 작은 파일처럼 동작한다. `with` 블록 안에서 읽어 둔 값은 블록 밖에서 출력할 수 있다.",
"common_mistakes": [
"with 블록이 끝난 뒤 닫힌 핸들을 읽으려 한다.",
"파일을 열고 닫기를 잊는다.",
"너무 넓은 예외 처리로 정리 오류를 가린다."
],
"self_check": [
"with 블록이 끝난 뒤 닫힌 핸들을 읽으려 한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "닫히기 전에 핸들에서 읽고 저장한 값을 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-modules-imports": {
"title": "모듈과 import",
"concept": "모듈은 재사용 코드를 이름공간으로 묶는다. `import math`는 표준 라이브러리를 현재 파일에서 쓸 수 있게 하고, PEP 8처럼 import를 위쪽에 두면 의존성이 먼저 보인다.",
"worked_example": "`math.ceil(2.1)`은 가져온 모듈의 올림 함수를 호출한다. 같은 이름을 직접 만들 필요가 없다.",
"common_mistakes": [
"표준 라이브러리 함수를 직접 다시 만든다.",
"가져온 모듈 이름을 변수로 덮어쓴다.",
"이유 없이 import를 코드 깊숙이 숨긴다."
],
"self_check": [
"표준 라이브러리 함수를 직접 다시 만든다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "가져온 `math` 모듈로 올림 결과를 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-dataclasses": {
"title": "데이터클래스",
"concept": "`@dataclass`는 데이터를 담는 클래스의 생성자와 표현 같은 반복 코드를 만들어 준다. 필드 이름과 타입 힌트가 클래스 정의에 붙어 있어 작은 도메인 값을 읽기 쉽다.",
"worked_example": "`Point(2, 3)` 생성자는 dataclass가 만들어 준다. 필드 `x`와 `y`는 일반 속성처럼 읽는다.",
"common_mistakes": [
"행동이 많은 객체에 데이터클래스만 먼저 붙인다.",
"데코레이터의 `@`를 빠뜨린다.",
"가변 기본값을 default_factory 없이 둔다."
],
"self_check": [
"행동이 많은 객체에 데이터클래스만 먼저 붙인다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "두 필드를 모두 읽어 좌표 합을 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-typing": {
"title": "타입 힌트",
"concept": "타입 힌트는 독자와 도구에게 기대하는 값의 모양을 알려 준다. Python 실행 자체는 여전히 동적이므로, 주석처럼 써 놓은 타입과 실제 함수 본문이 맞아야 의미가 있다.",
"worked_example": "`Iterable[int]`는 정수들을 반복해서 줄 수 있는 값을 받는다는 뜻이다. 본문은 그 약속대로 `sum`을 반환한다.",
"common_mistakes": [
"타입 힌트가 런타임 검증을 자동으로 한다고 믿는다.",
"자료 모양이 안정되기 전에 지나치게 복잡한 타입을 쓴다.",
"반환 타입 주석과 다른 값을 반환한다."
],
"self_check": [
"타입 힌트가 런타임 검증을 자동으로 한다고 믿는다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "힌트가 붙은 함수가 받은 iterable의 합을 반환하게 하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-generators": {
"title": "이터레이터와 제너레이터",
"concept": "이터레이터는 값을 하나씩 만든다. 제너레이터 함수는 `yield`에서 값을 내보내고 멈췄다가, 호출자가 다음 값을 요구할 때 이어서 실행한다.",
"worked_example": "`next(countdown(3))`가 호출될 때 제너레이터 본문이 처음 실행되고 첫 `yield` 값인 3을 받는다.",
"common_mistakes": [
"제너레이터 함수를 호출하면 본문이 바로 돈다고 생각한다.",
"값이 끝난 뒤 next가 StopIteration을 낼 수 있음을 잊는다.",
"큰 흐름을 굳이 리스트로 모두 만든다."
],
"self_check": [
"제너레이터 함수를 호출하면 본문이 바로 돈다고 생각한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "첫 번째 yield 값이 `ok`가 되게 하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-lambdas-closures": {
"title": "람다와 클로저",
"concept": "`lambda`는 작은 표현식 함수를 만든다. 그 함수가 바깥 함수의 이름을 기억하면 클로저가 되어, 바깥 호출이 끝난 뒤에도 그 값을 사용할 수 있다.",
"worked_example": "`make_adder(2)`가 돌려준 람다는 `delta` 값을 기억한다. 나중에 3을 넣어도 2를 더할 수 있다.",
"common_mistakes": [
"여러 단계 로직을 람다에 억지로 넣는다.",
"클로저가 변하는 변수의 예전 값을 자동 복사한다고 생각한다.",
"안쪽 함수를 반환하지 않는다."
],
"self_check": [
"여러 단계 로직을 람다에 억지로 넣는다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "캡처한 접미사를 붙이는 람다를 반환하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-decorators": {
"title": "데코레이터",
"concept": "데코레이터는 함수가 정의되는 순간 원래 함수 객체를 받아 새로 묶일 함수 객체를 돌려준다. `@decorator` 문법은 함수를 값으로 넘기는 축약 문법이다.",
"worked_example": "identity 데코레이터는 받은 함수를 그대로 돌려준다. 그래서 장식된 `word()` 호출은 원래 함수와 같은 값을 낸다.",
"common_mistakes": [
"함수 객체 대신 `fn()` 호출 결과를 반환한다.",
"데코레이터 실행 시점을 호출 시점으로 착각한다.",
"도우미 호출이면 충분한데 데코레이터를 추가한다."
],
"self_check": [
"함수 객체 대신 `fn()` 호출 결과를 반환한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "데코레이터가 원래 함수를 그대로 돌려주게 하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-sorting-keys": {
"title": "정렬과 key 함수",
"concept": "`sorted`는 새 리스트를 반환하고 원본 순서를 직접 바꾸지 않는다. `key` 함수는 각 원소에서 비교 기준을 꺼내므로 튜플이나 객체를 특정 필드로 정렬할 수 있다.",
"worked_example": "예제는 점수 필드를 key로 쓰고 내림차순을 지정해 가장 높은 점수의 사용자를 맨 앞에 둔다.",
"common_mistakes": [
"점수 대신 튜플의 첫 필드인 이름으로 정렬한다.",
"원본 순서가 필요한데 `.sort()`로 직접 바꾼다.",
"숫자 비교에 문자열 key를 반환한다."
],
"self_check": [
"점수 대신 튜플의 첫 필드인 이름으로 정렬한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "점수 기준 내림차순으로 정렬해 `Lin:5`를 고르라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-counter-defaultdict": {
"title": "Counter와 defaultdict",
"concept": "`Counter`는 해시 가능한 값을 세는 딕셔너리 계열이고, `defaultdict(list)`는 없는 그룹 키에 빈 리스트를 자동으로 만든다. 카운팅과 그룹핑에서 초기화 코드를 줄인다.",
"worked_example": "예제는 단어를 세는 일과 첫 글자로 묶는 일을 각각 표준 컬렉션에 맡긴다. 그래서 `2 2`라는 두 숫자가 같은 입력에서 나온다.",
"common_mistakes": [
"Counter가 할 일을 수동 초기화 코드로 반복한다.",
"없는 리스트 키에 바로 append한다.",
"그룹 기준을 단어 전체로 잘못 잡는다."
],
"self_check": [
"Counter가 할 일을 수동 초기화 코드로 반복한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "단어 세기와 첫 글자 그룹핑을 모두 채우라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-deque": {
"title": "deque",
"concept": "`deque`는 양쪽 끝에서 넣고 빼는 작업이 빠른 큐다. BFS 큐나 앞쪽 제거가 잦은 슬라이딩 윈도에서는 리스트의 `pop(0)`보다 이 타입이 맞다.",
"worked_example": "왼쪽에는 `start`, 오른쪽에는 `end`를 넣고 양쪽 끝에서 하나씩 꺼내 출력한다.",
"common_mistakes": [
"큐를 리스트 `pop(0)`으로 반복 처리한다.",
"append와 appendleft 방향을 바꿔 쓴다.",
"빈 deque에서 바로 꺼낸다."
],
"self_check": [
"큐를 리스트 `pop(0)`으로 반복 처리한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "왼쪽과 오른쪽 값을 넣고 양끝에서 꺼내 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-itertools": {
"title": "itertools",
"concept": "`itertools`는 체인, 슬라이스, 조합처럼 반복 패턴을 lazy iterator로 제공한다. 중간 리스트를 만들지 않고도 반복 파이프라인의 의도를 코드로 드러낼 수 있다.",
"worked_example": "`chain.from_iterable(parts)`는 한 글자 리스트들을 게으르게 펼치고, `join`이 그 값을 소비해 문자열을 만든다.",
"common_mistakes": [
"itertools 결과가 재사용 가능한 리스트라고 생각한다.",
"입력을 먼저 잘라 필요한 값을 잃는다.",
"간단한 도구로 될 일을 중첩 반복문으로 숨긴다."
],
"self_check": [
"itertools 결과가 재사용 가능한 리스트라고 생각한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "두 내부 리스트를 모두 lazy하게 펼쳐 `ok`를 만들라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-pathlib": {
"title": "pathlib",
"concept": "`pathlib`는 경로를 문자열 조각이 아니라 객체로 다룬다. `name`, `stem`, `suffix`, `parent` 같은 속성으로 구분자 처리 없이 경로의 의미 있는 부분을 읽는다.",
"worked_example": "`PurePosixPath('logs/app.txt')`는 파일을 열지 않고도 stem `app`과 suffix `.txt`를 알려 준다.",
"common_mistakes": [
"경로를 항상 `/`로 직접 나눈다.",
"name, stem, suffix를 혼동한다.",
"순수 파싱이면 충분한데 실제 파일 접근을 시도한다."
],
"self_check": [
"경로를 항상 `/`로 직접 나눈다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "경로 속성으로 `app:.txt`를 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-testing-assert": {
"title": "테스트와 assert",
"concept": "`assert`는 작은 예제나 테스트에서 불변 조건을 확인하는 간단한 문장이다. 실제 프로젝트의 테스트 함수도 결국 기대값과 실제값을 반복 가능하게 비교한다는 점은 같다.",
"worked_example": "예제는 `add_two(3) == 5`를 먼저 확인한다. 함수가 틀리면 `ok`를 출력하기 전에 실패한다.",
"common_mistakes": [
"깨진 코드에 맞춰 assert를 바꾼다.",
"사용자 입력 검증을 assert에만 의존한다.",
"작은 함수는 확인하지 않고 최종 출력만 본다."
],
"self_check": [
"깨진 코드에 맞춰 assert를 바꾼다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "함수와 assert가 `+2` 동작을 확인하게 고치라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
},
"py-async": {
"title": "비동기 개념",
"concept": "`async def`는 코루틴 함수를 만들고, 호출하면 아직 실행되지 않은 코루틴 객체가 생긴다. 다른 코루틴 안에서 `await`해야 결과를 받고, 파일 끝에서는 `asyncio.run`이 실행을 시작한다.",
"worked_example": "`main`이 `label()`을 await해서 결과 `ok`를 받은 뒤 출력하고, `asyncio.run`이 최상위 코루틴을 실행한다.",
"common_mistakes": [
"코루틴 객체를 await 없이 출력한다.",
"이미 실행 중인 이벤트 루프 안에서 asyncio.run을 다시 부른다.",
"기다릴 작업이 없는데 async 문법을 붙인다."
],
"self_check": [
"코루틴 객체를 await 없이 출력한다?",
"이 실습에서 수정해야 할 한 줄은 무엇인가?"
],
"exercise_prompt": "`main` 안에서 코루틴을 await하고 그 결과를 출력하라. 출력만 맞추려고 하드코딩하지 말고 해당 문법을 유지하라."
}
}
}
{
"schema_version": 1,
"programming_language": "python",
"ui_language": "zh",
"lessons": {
"py-output": {
"title": "print 与标准输出",
"concept": "`print` 是计算结果进入判题可见标准输出的边界。它会把参数转成文本,多个参数之间加空格,并默认追加换行,所以 stdout 必须精确匹配。",
"worked_example": "f-string 使用 `name` 和 `score` 的值组成 `Ada:7`;变量名和引号本身不会出现在输出中。",
"common_mistakes": [
"在最终输出中留下调试用 print。",
"需要纯文本时打印列表的表示形式。",
"在顶层使用 `return`,以为它会变成输出。"
],
"self_check": [
"print 默认会在末尾加什么?",
"多一行 stdout 时判题器会怎样比较?"
],
"exercise_prompt": "使用 `name` 和 `score`,只输出 `Ada:7`。 不要只硬编码最终答案,要保留本课练习的语法。"
},
"py-variables": {
"title": "变量",
"concept": "变量名是到对象的绑定。`=` 不是相等比较,而是决定之后读取这个名字时会得到哪个对象。 这个绑定关系会直接影响后续每一次读取。",
"worked_example": "`count = count + 2` 先读取旧值,创建新整数,再把 `count` 重新绑定到新值并输出 `3`。",
"common_mistakes": [
"把 `=` 当作数学相等。",
"同一个短名字反复表示不同含义。",
"认为重新绑定会自动修改过去的对象。"
],
"self_check": [
"`x = 1; x = x + 2` 后 `x` 是什么?",
"哪个中间值值得起一个清楚的名字?"
],
"exercise_prompt": "把 `word` 重新绑定为期望文本,并打印这个名字。 不要只硬编码最终答案,要保留本课练习的语法。"
},
"py-numbers": {
"title": "数字",
"concept": "Python 整数能处理很大的值,而数字运算符含义不同:`/` 得到 float,`//` 得到整数商,`%` 得到余数,`**` 表示乘方。",
"worked_example": "`7 // 2` 得到商 `3`,`7 % 2` 得到余数 `1`,二者一起描述整数除法结果。",
"common_mistakes": [
"需要整数商时使用 `/`。",
"把 `%` 当作百分比而不是余数。",
"期望整数输出时混入 float 字符串。"
],
"self_check": [
"`7 // 2` 和 `7 % 2` 分别是什么?",
"为什么 `round` 不能替代 `//`?"
],
"exercise_prompt": "修改算术表达式,输出商和余数 `3:1`。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-strings": {
"title": "字符串",
"concept": "字符串是不可变的 Unicode 字符序列。索引读取一个字符,切片读取左闭右开的范围,原字符串不会被改变。",
"worked_example": "`text[1:4]` 读取 `python` 中索引 1、2、3 的字符;终点 4 只是边界。",
"common_mistakes": [
"以为切片会包含终点索引。,导致切片结果多一位。",
"尝试给 `text[0]` 赋值来修改字符串。",
"把索引从 1 开始数。,导致字符位置判断错误。"
],
"self_check": [
"`'code'[1]` 读取什么?",
"为什么 `'xokx'[1:3]` 正好有两个字符?"
],
"exercise_prompt": "用切片只输出中间的 `ok`。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-control-flow": {
"title": "控制流",
"concept": "`if` 选择代码块,`for` 消费 iterable,`while` 在条件变化前重复执行。Python 的缩进就是语法,移动一行会改变真实控制流。",
"worked_example": "代码访问 1、2、3,只把奇数加入总和,并在循环结束后输出最终结果。 这能检查缩进和循环范围是否正确。",
"common_mistakes": [
"把累加语句缩进到循环外。",
"以为 `range(n)` 会包含 n。",
"写出条件永远不变的 while 循环。"
],
"self_check": [
"哪些行每次迭代都会执行?",
"`range(1, 4)` 会产生哪些值?"
],
"exercise_prompt": "修改循环体,只累加奇数。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-functions": {
"title": "函数",
"concept": "`def` 创建可调用的函数对象。参数接收实参,`return` 把值交回调用者;在函数内部 print 和返回计算结果不是一回事。",
"worked_example": "`area(3, 4)` 传入两个参数,函数相乘并把 `12` 返回给调用处打印。 调用者只负责打印返回值。",
"common_mistakes": [
"只定义函数却忘记调用。,导致程序没有实际运行。",
"调用者需要返回值时只在函数里打印。",
"在循环中过早 return。"
],
"self_check": [
"没有 return 到达结尾的函数返回什么?",
"最终 print 应该放在哪里?"
],
"exercise_prompt": "让函数返回面积,而不是周长。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-input": {
"title": "输入解析",
"concept": "编程题输入从 stdin 文本开始。常见做法是一次读入,按空白拆成 token,转换类型,然后让算法只处理普通值。",
"worked_example": "`sys.stdin.read().split()` 得到字符串 token;对每个 token 调用 `int` 后才能进行数值求和。",
"common_mistakes": [
"未知行数时反复调用 `input()`。",
"忘记 split 的结果仍然是字符串。",
"把解析逻辑混进算法中间。"
],
"self_check": [
"`sys.stdin.read()` 返回什么类型?",
"空输入应该在哪一步处理?"
],
"exercise_prompt": "转换 stdin 中所有整数并输出总和。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-lists-dicts": {
"title": "列表和字典",
"concept": "列表按顺序保存值,字典把键映射到值。真实题目中,如果数据由姓名、字符或 ID 标识,直接按键查找通常比手动扫描位置更清楚。",
"worked_example": "`scores['Ada']` 直接取出 Ada 的分数列表,`sum` 只计算这份列表,不需要检查其他名字。",
"common_mistakes": [
"有明确键时仍用列表位置查找。",
"没有决定缺失键怎么办就直接索引字典。",
"题目要求求和却输出长度。"
],
"self_check": [
"什么时候字典键比列表位置更合适?",
"`scores['Ada']` 返回什么?"
],
"exercise_prompt": "使用 `Ada` 对应的列表求和,不要硬编码 `5`。 不要只硬编码最终答案,要保留本课练习的语法。"
},
"py-tuples-sets": {
"title": "元组和集合",
"concept": "元组适合保存位置固定的小记录,集合保存唯一成员并能快速判断是否存在。若输出顺序重要,不要只依赖集合。",
"worked_example": "元组 `('o', 'k')` 保留拼接顺序,而 `set(pair)` 表明不同字符有两个。",
"common_mistakes": [
"输出顺序重要时使用集合。",
"尝试原地修改元组元素。,因为元组是不可变序列。",
"认为向集合插入重复值会增加长度。"
],
"self_check": [
"哪一步需要元组顺序? 它怎样影响最终输出?",
"`len(set(['a', 'a']))` 是多少?"
],
"exercise_prompt": "从元组创建集合,并输出拼接文本和唯一元素个数。 不要只硬编码最终答案,要保留本课练习的语法。"
},
"py-comprehensions": {
"title": "推导式",
"concept": "推导式用输出表达式、循环和可选过滤条件创建集合。简单映射和过滤很适合,包含多步副作用时普通循环更清楚。",
"worked_example": "`[n * n for n in nums if n % 2 == 0]` 只保留偶数,平方后形成可求和的列表。",
"common_mistakes": [
"把过多条件塞进一条推导式。",
"忘记输出表达式写在 `for` 前面。",
"只为了 print 这类副作用使用推导式。"
],
"self_check": [
"哪一部分决定生成的值? 它位于 for 前面还是后面?",
"哪个条件去掉不需要的字母?"
],
"exercise_prompt": "调整过滤条件,保留组成 `ok` 的字母。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-errors": {
"title": "异常",
"concept": "异常把正常路径和可恢复失败分开。`try` 应尽量小,只捕获像 `ValueError` 这样明确能恢复的异常。",
"worked_example": "只有字符串转整数这一行可能失败,`except ValueError` 只处理这个转换失败。",
"common_mistakes": [
"用裸 `except` 隐藏真正的 bug。",
"把无关代码放进 try 块。",
"用异常表达普通 if 分支。"
],
"self_check": [
"为什么 `except ValueError` 比宽泛捕获更清楚?",
"失败后应该使用哪个恢复值?"
],
"exercise_prompt": "处理转换失败,赋值为能输出 `12` 的恢复值。 不要只硬编码最终答案,要保留本课练习的语法。"
},
"py-files-context": {
"title": "文件和上下文管理器",
"concept": "`with` 进入受管理的作用域,离开时运行清理逻辑。文件对象和 contextlib 工具用这个模式确保资源会被关闭。",
"worked_example": "`StringIO` 像小文件一样工作;在 `with` 块内读取的值可以保存到块外打印。",
"common_mistakes": [
"上下文结束后读取已经关闭的句柄。",
"不用 `with` 打开文件然后忘记关闭。",
"用过宽异常处理隐藏清理问题。"
],
"self_check": [
"句柄关闭前必须运行哪一行?",
"`with open(...)` 通常帮你做什么清理?"
],
"exercise_prompt": "在 `with` 块内读取,并在块外打印保存的文本。 不要只硬编码最终答案,要保留本课练习的语法。"
},
"py-modules-imports": {
"title": "模块和 import",
"concept": "模块把可复用代码组织在命名空间中。`import math` 让标准库模块可用,而 PEP 8 建议把 import 放在文件顶部以便先看到依赖。",
"worked_example": "`math.ceil(2.1)` 调用已导入模块中的向上取整函数,得到 `3`。 这样不用自己实现取整逻辑。",
"common_mistakes": [
"重写标准库中已有的函数。",
"用变量覆盖已导入的模块名。",
"没有理由地把 import 藏在深层代码里。"
],
"self_check": [
"`import math` 后哪个名字可用?",
"PEP 8 为什么建议 import 靠近文件顶部?"
],
"exercise_prompt": "使用已导入的 `math` 输出向上取整结果。 不要只硬编码最终答案,要保留本课练习的语法。"
},
"py-dataclasses": {
"title": "数据类",
"concept": "`@dataclass` 为主要保存数据的类生成常规方法。字段名、类型提示和构造方式集中在类定义中,避免手写样板代码。",
"worked_example": "`Point(2, 3)` 使用生成的构造器,`x` 和 `y` 可以像普通属性一样读取。",
"common_mistakes": [
"行为很多的对象也先套 dataclass。",
"忘记装饰器前面的 `@`。",
"可变默认值不用合适的 factory。"
],
"self_check": [
"`Point` 会得到怎样的构造器?",
"求坐标和需要读取哪些字段?"
],
"exercise_prompt": "读取两个字段并输出坐标和。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-typing": {
"title": "类型提示",
"concept": "类型提示向读者、编辑器和类型检查器描述期望形状。Python 运行时仍然是动态的,所以函数体也必须符合注解表达的契约。",
"worked_example": "`Iterable[int]` 表示参数能迭代出整数,函数用 `sum` 返回一个 `int`。",
"common_mistakes": [
"以为注解会在运行时自动验证所有值。",
"数据形状未稳定时写复杂类型。",
"返回值和注解矛盾。,让读者和工具都被误导。"
],
"self_check": [
"`Iterable[int]` 对参数承诺了什么?",
"哪一行证明函数返回整数总和?"
],
"exercise_prompt": "让带注解的函数返回收到的 iterable 的总和。 不要只硬编码最终答案,要保留本课练习的语法。"
},
"py-generators": {
"title": "迭代器和生成器",
"concept": "迭代器一次产生一个值。生成器函数使用 `yield`,调用时先返回生成器对象,只有调用者请求下一个值时才推进函数体。",
"worked_example": "`next(countdown(3))` 启动生成器,在第一个 `yield` 处收到 `3`,无需创建完整列表。",
"common_mistakes": [
"以为调用生成器函数会立刻执行函数体。",
"忘记值耗尽后 `next` 会触发 `StopIteration`。",
"本可惰性处理却构建大列表。"
],
"self_check": [
"`words()` 的函数体什么时候运行?",
"修正后的第一个 yield 值是什么?"
],
"exercise_prompt": "让第一个 `yield` 产生 `ok`。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-lambdas-closures": {
"title": "lambda 和闭包",
"concept": "`lambda` 创建只有一个表达式的小函数。当这个函数使用外层函数的名字时,它形成闭包并在外层调用结束后仍记住该值。",
"worked_example": "`make_adder(2)` 返回的 lambda 记住 `delta = 2`,之后传入 `3` 会得到 `5`。",
"common_mistakes": [
"把多步骤逻辑硬塞进 lambda。",
"以为闭包会自动复制变化变量的旧值。",
"忘记返回内部函数。,外层调用就拿不到可调用对象。"
],
"self_check": [
"`make_suffix` 中 lambda 记住了什么?",
"为什么 `make_suffix('ok')` 返回函数而不是字符串?"
],
"exercise_prompt": "返回一个把捕获的 suffix 追加到参数后的 lambda。 不要只硬编码最终答案,要保留本课练习的语法。"
},
"py-decorators": {
"title": "装饰器",
"concept": "装饰器在函数定义时运行。它接收原始函数对象,并返回最终绑定到函数名上的对象。 这个返回值决定之后调用函数名时执行什么。",
"worked_example": "identity 装饰器原样返回 `fn`,所以 `word()` 仍调用原函数并返回 `ok`。",
"common_mistakes": [
"返回 `fn()` 的调用结果而不是函数对象。",
"以为装饰器只在函数调用时运行。",
"简单辅助函数调用即可时添加装饰器。"
],
"self_check": [
"`identity` 接收什么参数?",
"为了让 `word` 仍可调用,应返回什么?"
],
"exercise_prompt": "从装饰器返回原始函数。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-sorting-keys": {
"title": "排序和 key 函数",
"concept": "`sorted` 返回新的有序列表。`key` 函数为每个元素取出比较值,因此可以按元组或对象的某个字段排序。",
"worked_example": "`key=lambda item: item[1]` 选择分数字段比较,`reverse=True` 让最高分排在最前面。",
"common_mistakes": [
"题目要求其他字段时仍按元组第一项排序。",
"需要保留原顺序时使用 `.sort()` 原地修改。",
"数值排序的 key 返回字符串。"
],
"self_check": [
"元组中哪个字段是分数? 它为什么不能按名字比较?",
"为什么选择最高分需要 `reverse=True`?"
],
"exercise_prompt": "按分数降序排序,选出 `Lin:5`。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-counter-defaultdict": {
"title": "Counter 和 defaultdict",
"concept": "`Counter` 统计可哈希值,`defaultdict(list)` 为新分组自动创建空列表。它们能减少计数和分组时的手动初始化。",
"worked_example": "Counter 统计颜色,defaultdict 按首字母收集单词,最后输出 `red` 的次数和 `r` 组大小。",
"common_mistakes": [
"重复手动初始化计数而不用 Counter。",
"对普通 dict 中不存在的列表键直接 append。",
"需要首字母分组时按整个单词分组。"
],
"self_check": [
"`Counter(words)['red']` 返回什么?",
"`groups['r']` 中有什么?"
],
"exercise_prompt": "补全计数和首字母分组,输出 `2 2`。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-deque": {
"title": "deque",
"concept": "`deque` 是双端队列,左右两端 append 和 pop 都高效。BFS 队列和经常从前端删除的滑动窗口通常使用它。",
"worked_example": "代码把 `start` 放到左边、`end` 放到右边,然后从左右两端各取一个值。 这正是双端队列要练习的方向感。",
"common_mistakes": [
"用 `list.pop(0)` 反复模拟队列。",
"混淆 `append` 和 `appendleft` 的方向。",
"不检查就从空 deque 取值。"
],
"self_check": [
"哪个方法向左端添加? 它和右端添加有什么区别?",
"添加两端后 `popleft` 和 `pop` 应返回什么?"
],
"exercise_prompt": "添加缺失的左右值,并输出取出的两端。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-itertools": {
"title": "itertools",
"concept": "`itertools` 提供 `chain`、`islice`、`product`、`pairwise` 等惰性迭代工具。除非被消费,它们不会构建中间列表。",
"worked_example": "`chain.from_iterable(parts)` 展平内部的一字母列表,`join` 消费该迭代器得到 `ok`。",
"common_mistakes": [
"把 itertools 结果当作可反复使用的列表。",
"在迭代器看到所有值之前先切掉输入。",
"不用清晰的标准工具而写嵌套循环。"
],
"self_check": [
"`chain.from_iterable` 展平了什么?",
"初始代码为什么丢了第二个字母?"
],
"exercise_prompt": "惰性展平两个内部列表,组成 `ok`。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-pathlib": {
"title": "pathlib",
"concept": "`pathlib` 把路径表示为对象。`name`、`stem`、`suffix`、`parent` 等属性能避免手动拆字符串,并说明需要路径的哪一部分。",
"worked_example": "`PurePosixPath('logs/app.txt')` 不打开真实文件,也能取出 stem `app` 和 suffix `.txt`。",
"common_mistakes": [
"总是用 `/` 手动拆路径字符串。",
"混淆 `name`、`stem` 和 `suffix`。",
"只需要纯路径解析时访问文件系统。"
],
"self_check": [
"`logs/app.txt` 的 stem 是什么?",
"这里为什么适合用 `PurePosixPath`?"
],
"exercise_prompt": "使用路径属性输出 `app:.txt`。 不要只硬编码最终答案,要保留本课练习的语法。 并让 stdout 与题目要求完全一致。"
},
"py-testing-assert": {
"title": "测试和 assert",
"concept": "`assert` 在小例子或测试中检查条件。测试框架把这些检查组织成可重复运行的函数,但核心仍是比较期望行为和实际行为。",
"worked_example": "代码先检查 `add_two(3) == 5` 再输出 `ok`,所以坏函数会在假装成功前失败。",
"common_mistakes": [
"把断言改成接受错误代码。",
"在生产用户输入验证中只依赖 assert。",
"只测最终 print,不测小函数。"
],
"self_check": [
"`add_two` 应保证什么行为?",
"如果函数原样返回输入,哪一行会失败?"
],
"exercise_prompt": "修正函数和断言,保护 `+2` 行为后再输出 `ok`。 不要只硬编码最终答案,要保留本课练习的语法。"
},
"py-async": {
"title": "异步概念",
"concept": "`async def` 创建协程函数。调用它得到协程对象;在另一个协程中用 `await` 取得结果,文件边界用 `asyncio.run` 驱动最外层协程。",
"worked_example": "`main` await `label()` 得到 `ok`,`asyncio.run` 启动最外层协程。",
"common_mistakes": [
"不 await 就打印协程对象。",
"在已有事件循环中再次调用 `asyncio.run`。",
"没有等待点的 CPU 工作也强行 async。"
],
"self_check": [
"哪个表达式真正等待 `label()` 完成?",
"为什么在文件边界使用 `asyncio.run`?"
],
"exercise_prompt": "在 `main` 中 await,并打印收到的结果。 不要只硬编码最终答案,要保留本课练习的语法。"
}
}
}
# Lesson Catalog Assets
Lesson copy is split first by programming language, then by UI language:
```text
assets/lessons/<programming-language>/<ui-language>.json
```
Example:
```text
assets/lessons/python/ko.json
assets/lessons/typescript/en.json
```
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`
- `concept`
- `worked_example`
- `common_mistakes`
- `self_check`
- `exercise_prompt`
The Rust loader treats these fields as required. Missing study copy should fail tests instead of falling back to generic text at runtime.
{
"schema_version": 1,
"programming_language": "rust",
"ui_language": "en",
"lessons": {
"rust-output": {
"title": "Output",
"concept": "Output focuses on this Rust skill: formatting values with println! and matching judge-exact stdout. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: formatting values with println! and matching judge-exact stdout. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: formatting values with println! and matching judge-exact stdout.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Output."
],
"self_check": [
"Which expression or item demonstrates this skill: formatting values with println! and matching judge-exact stdout?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: formatting values with println! and matching judge-exact stdout. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-variables": {
"title": "Bindings and mutability",
"concept": "Bindings and mutability focuses on this Rust skill: choosing immutable let bindings first and using mut only where a value really changes. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: choosing immutable let bindings first and using mut only where a value really changes. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: choosing immutable let bindings first and using mut only where a value really changes.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Bindings and mutability."
],
"self_check": [
"Which expression or item demonstrates this skill: choosing immutable let bindings first and using mut only where a value really changes?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: choosing immutable let bindings first and using mut only where a value really changes. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-numbers-tuples": {
"title": "Numbers and tuples",
"concept": "Numbers and tuples focuses on this Rust skill: combining explicit numeric types with tuple field access such as pair.0 and pair.1. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: combining explicit numeric types with tuple field access such as pair.0 and pair.1. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: combining explicit numeric types with tuple field access such as pair.0 and pair.1.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Numbers and tuples."
],
"self_check": [
"Which expression or item demonstrates this skill: combining explicit numeric types with tuple field access such as pair.0 and pair.1?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: combining explicit numeric types with tuple field access such as pair.0 and pair.1. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-strings": {
"title": "Strings",
"concept": "Strings focuses on this Rust skill: deciding when owned String is needed and when a borrowed &str slice is enough. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: deciding when owned String is needed and when a borrowed &str slice is enough. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: deciding when owned String is needed and when a borrowed &str slice is enough.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Strings."
],
"self_check": [
"Which expression or item demonstrates this skill: deciding when owned String is needed and when a borrowed &str slice is enough?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: deciding when owned String is needed and when a borrowed &str slice is enough. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-control-flow": {
"title": "Control flow",
"concept": "Control flow focuses on this Rust skill: using if as an expression and for ranges to build a value instead of branching by habit. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: using if as an expression and for ranges to build a value instead of branching by habit. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: using if as an expression and for ranges to build a value instead of branching by habit.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Control flow."
],
"self_check": [
"Which expression or item demonstrates this skill: using if as an expression and for ranges to build a value instead of branching by habit?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: using if as an expression and for ranges to build a value instead of branching by habit. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-functions": {
"title": "Functions",
"concept": "Functions focuses on this Rust skill: writing signatures whose parameter and return types document the calculation boundary. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: writing signatures whose parameter and return types document the calculation boundary. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: writing signatures whose parameter and return types document the calculation boundary.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Functions."
],
"self_check": [
"Which expression or item demonstrates this skill: writing signatures whose parameter and return types document the calculation boundary?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: writing signatures whose parameter and return types document the calculation boundary. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-structs-impl": {
"title": "Structs and impl",
"concept": "Structs and impl focuses on this Rust skill: grouping domain fields in a struct and putting behavior that needs those fields in impl. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: grouping domain fields in a struct and putting behavior that needs those fields in impl. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: grouping domain fields in a struct and putting behavior that needs those fields in impl.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Structs and impl."
],
"self_check": [
"Which expression or item demonstrates this skill: grouping domain fields in a struct and putting behavior that needs those fields in impl?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: grouping domain fields in a struct and putting behavior that needs those fields in impl. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-enum-match": {
"title": "Enums and match",
"concept": "Enums and match focuses on this Rust skill: modeling alternatives with enum variants and handling each variant exhaustively with match. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: modeling alternatives with enum variants and handling each variant exhaustively with match. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: modeling alternatives with enum variants and handling each variant exhaustively with match.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Enums and match."
],
"self_check": [
"Which expression or item demonstrates this skill: modeling alternatives with enum variants and handling each variant exhaustively with match?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: modeling alternatives with enum variants and handling each variant exhaustively with match. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-option": {
"title": "Option and if let",
"concept": "Option and if let focuses on this Rust skill: making missing values explicit with Option<T> and unpacking the Some case safely. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: making missing values explicit with Option<T> and unpacking the Some case safely. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: making missing values explicit with Option<T> and unpacking the Some case safely.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Option and if let."
],
"self_check": [
"Which expression or item demonstrates this skill: making missing values explicit with Option<T> and unpacking the Some case safely?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: making missing values explicit with Option<T> and unpacking the Some case safely. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-modules-use": {
"title": "Modules and use",
"concept": "Modules and use focuses on this Rust skill: controlling namespace, privacy, and local paths with mod, pub, and use. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: controlling namespace, privacy, and local paths with mod, pub, and use. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: controlling namespace, privacy, and local paths with mod, pub, and use.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Modules and use."
],
"self_check": [
"Which expression or item demonstrates this skill: controlling namespace, privacy, and local paths with mod, pub, and use?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: controlling namespace, privacy, and local paths with mod, pub, and use. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-input": {
"title": "Input parsing",
"concept": "Input parsing focuses on this Rust skill: reading stdin once, splitting text into tokens, and converting tokens into typed values. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: reading stdin once, splitting text into tokens, and converting tokens into typed values. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: reading stdin once, splitting text into tokens, and converting tokens into typed values.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Input parsing."
],
"self_check": [
"Which expression or item demonstrates this skill: reading stdin once, splitting text into tokens, and converting tokens into typed values?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: reading stdin once, splitting text into tokens, and converting tokens into typed values. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-vec-hashmap": {
"title": "Vec and HashMap",
"concept": "Vec and HashMap focuses on this Rust skill: choosing Vec for ordered data and HashMap entry APIs for keyed counting and lookup. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: choosing Vec for ordered data and HashMap entry APIs for keyed counting and lookup. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: choosing Vec for ordered data and HashMap entry APIs for keyed counting and lookup.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Vec and HashMap."
],
"self_check": [
"Which expression or item demonstrates this skill: choosing Vec for ordered data and HashMap entry APIs for keyed counting and lookup?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: choosing Vec for ordered data and HashMap entry APIs for keyed counting and lookup. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-borrowing-slices": {
"title": "Borrowing and slices",
"concept": "Borrowing and slices focuses on this Rust skill: passing borrowed slices so functions can read data without taking ownership. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: passing borrowed slices so functions can read data without taking ownership. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: passing borrowed slices so functions can read data without taking ownership.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Borrowing and slices."
],
"self_check": [
"Which expression or item demonstrates this skill: passing borrowed slices so functions can read data without taking ownership?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: passing borrowed slices so functions can read data without taking ownership. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-result": {
"title": "Result and ?",
"concept": "Result and ? focuses on this Rust skill: returning recoverable errors with Result<T, E> and propagating them with the ? operator. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: returning recoverable errors with Result<T, E> and propagating them with the ? operator. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: returning recoverable errors with Result<T, E> and propagating them with the ? operator.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Result and ?."
],
"self_check": [
"Which expression or item demonstrates this skill: returning recoverable errors with Result<T, E> and propagating them with the ? operator?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: returning recoverable errors with Result<T, E> and propagating them with the ? operator. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-ownership": {
"title": "Ownership and borrowing",
"concept": "Ownership and borrowing focuses on this Rust skill: tracking when a String moves, when it is returned, and when a reference only borrows it. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: tracking when a String moves, when it is returned, and when a reference only borrows it. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: tracking when a String moves, when it is returned, and when a reference only borrows it.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Ownership and borrowing."
],
"self_check": [
"Which expression or item demonstrates this skill: tracking when a String moves, when it is returned, and when a reference only borrows it?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: tracking when a String moves, when it is returned, and when a reference only borrows it. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-iterators": {
"title": "Iterators and closures",
"concept": "Iterators and closures focuses on this Rust skill: composing lazy iterator adapters with closures and consuming them with sum or collect. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: composing lazy iterator adapters with closures and consuming them with sum or collect. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: composing lazy iterator adapters with closures and consuming them with sum or collect.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Iterators and closures."
],
"self_check": [
"Which expression or item demonstrates this skill: composing lazy iterator adapters with closures and consuming them with sum or collect?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: composing lazy iterator adapters with closures and consuming them with sum or collect. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-generics": {
"title": "Generics",
"concept": "Generics focuses on this Rust skill: writing one function over T while preserving concrete type checking at compile time. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: writing one function over T while preserving concrete type checking at compile time. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: writing one function over T while preserving concrete type checking at compile time.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Generics."
],
"self_check": [
"Which expression or item demonstrates this skill: writing one function over T while preserving concrete type checking at compile time?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: writing one function over T while preserving concrete type checking at compile time. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-traits": {
"title": "Traits and bounds",
"concept": "Traits and bounds focuses on this Rust skill: describing required behavior with traits and using bounds to call that behavior generically. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: describing required behavior with traits and using bounds to call that behavior generically. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: describing required behavior with traits and using bounds to call that behavior generically.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Traits and bounds."
],
"self_check": [
"Which expression or item demonstrates this skill: describing required behavior with traits and using bounds to call that behavior generically?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: describing required behavior with traits and using bounds to call that behavior generically. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-lifetimes": {
"title": "Lifetimes",
"concept": "Lifetimes focuses on this Rust skill: annotating relationships between borrowed inputs and borrowed outputs without extending storage. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: annotating relationships between borrowed inputs and borrowed outputs without extending storage. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: annotating relationships between borrowed inputs and borrowed outputs without extending storage.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Lifetimes."
],
"self_check": [
"Which expression or item demonstrates this skill: annotating relationships between borrowed inputs and borrowed outputs without extending storage?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: annotating relationships between borrowed inputs and borrowed outputs without extending storage. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-traits-lifetimes": {
"title": "Trait objects and dyn dispatch",
"concept": "Trait objects and dyn dispatch focuses on this Rust skill: using &dyn Trait when runtime dispatch is worth the flexibility over a single generic type. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: using &dyn Trait when runtime dispatch is worth the flexibility over a single generic type. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: using &dyn Trait when runtime dispatch is worth the flexibility over a single generic type.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Trait objects and dyn dispatch."
],
"self_check": [
"Which expression or item demonstrates this skill: using &dyn Trait when runtime dispatch is worth the flexibility over a single generic type?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: using &dyn Trait when runtime dispatch is worth the flexibility over a single generic type. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-testing": {
"title": "Tests and assertions",
"concept": "Tests and assertions focuses on this Rust skill: separating pure logic from main so #[test] functions and assert macros can verify behavior. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: separating pure logic from main so #[test] functions and assert macros can verify behavior. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: separating pure logic from main so #[test] functions and assert macros can verify behavior.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Tests and assertions."
],
"self_check": [
"Which expression or item demonstrates this skill: separating pure logic from main so #[test] functions and assert macros can verify behavior?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: separating pure logic from main so #[test] functions and assert macros can verify behavior. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-smart-pointers": {
"title": "Smart pointers",
"concept": "Smart pointers focuses on this Rust skill: using Box<T> to own heap data while still working through pointer-like deref behavior. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: using Box<T> to own heap data while still working through pointer-like deref behavior. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: using Box<T> to own heap data while still working through pointer-like deref behavior.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Smart pointers."
],
"self_check": [
"Which expression or item demonstrates this skill: using Box<T> to own heap data while still working through pointer-like deref behavior?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: using Box<T> to own heap data while still working through pointer-like deref behavior. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-interior-mutability": {
"title": "Interior mutability",
"concept": "Interior mutability focuses on this Rust skill: using RefCell<T> when runtime borrow checking is the simplest correct model. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: using RefCell<T> when runtime borrow checking is the simplest correct model. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: using RefCell<T> when runtime borrow checking is the simplest correct model.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Interior mutability."
],
"self_check": [
"Which expression or item demonstrates this skill: using RefCell<T> when runtime borrow checking is the simplest correct model?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: using RefCell<T> when runtime borrow checking is the simplest correct model. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-concurrency": {
"title": "Threads and join",
"concept": "Threads and join focuses on this Rust skill: moving work into thread::spawn and using join to recover the worker result safely. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: moving work into thread::spawn and using join to recover the worker result safely. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: moving work into thread::spawn and using join to recover the worker result safely.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Threads and join."
],
"self_check": [
"Which expression or item demonstrates this skill: moving work into thread::spawn and using join to recover the worker result safely?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: moving work into thread::spawn and using join to recover the worker result safely. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-shared-state": {
"title": "Shared state with Arc and Mutex",
"concept": "Shared state with Arc and Mutex focuses on this Rust skill: combining Arc<T> for shared ownership with Mutex<T> for one-at-a-time mutation. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: combining Arc<T> for shared ownership with Mutex<T> for one-at-a-time mutation. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: combining Arc<T> for shared ownership with Mutex<T> for one-at-a-time mutation.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Shared state with Arc and Mutex."
],
"self_check": [
"Which expression or item demonstrates this skill: combining Arc<T> for shared ownership with Mutex<T> for one-at-a-time mutation?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: combining Arc<T> for shared ownership with Mutex<T> for one-at-a-time mutation. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-async-await": {
"title": "Async and await",
"concept": "Async and await focuses on this Rust skill: recognizing that async creates a Future and that running it requires an executor or runtime. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: recognizing that async creates a Future and that running it requires an executor or runtime. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: recognizing that async creates a Future and that running it requires an executor or runtime.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Async and await."
],
"self_check": [
"Which expression or item demonstrates this skill: recognizing that async creates a Future and that running it requires an executor or runtime?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: recognizing that async creates a Future and that running it requires an executor or runtime. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-macros": {
"title": "macro_rules!",
"concept": "macro_rules! focuses on this Rust skill: matching token patterns with macro_rules! and expanding repeated Rust code at compile time. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: matching token patterns with macro_rules! and expanding repeated Rust code at compile time. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: matching token patterns with macro_rules! and expanding repeated Rust code at compile time.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in macro_rules!."
],
"self_check": [
"Which expression or item demonstrates this skill: matching token patterns with macro_rules! and expanding repeated Rust code at compile time?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: matching token patterns with macro_rules! and expanding repeated Rust code at compile time. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-unsafe": {
"title": "Unsafe Rust",
"concept": "Unsafe Rust focuses on this Rust skill: placing unverifiable operations in unsafe blocks while keeping the surrounding API as safe as possible. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: placing unverifiable operations in unsafe blocks while keeping the surrounding API as safe as possible. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: placing unverifiable operations in unsafe blocks while keeping the surrounding API as safe as possible.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Unsafe Rust."
],
"self_check": [
"Which expression or item demonstrates this skill: placing unverifiable operations in unsafe blocks while keeping the surrounding API as safe as possible?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: placing unverifiable operations in unsafe blocks while keeping the surrounding API as safe as possible. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
},
"rust-cargo-workspaces": {
"title": "Cargo packages and workspaces",
"concept": "Cargo packages and workspaces focuses on this Rust skill: understanding when Cargo package structure and cargo check --workspace matter beyond one file. Learn it by tracing the typed value through the example, from creation through move or borrow decisions to the expression that reaches stdout.",
"worked_example": "The example turns that skill into a complete, compiling Rust program: understanding when Cargo package structure and cargo check --workspace matter beyond one file. Read the signature or item definition first, then follow the value into println! so syntax stays tied to behavior.",
"common_mistakes": [
"Hard-coding the final output instead of exercising this skill: understanding when Cargo package structure and cargo check --workspace matter beyond one file.",
"Deleting the Rust construct that the lesson is meant to practice just to silence a compiler error.",
"Ignoring the type, ownership, lifetime, path, or thread boundary involved in Cargo packages and workspaces."
],
"self_check": [
"Which expression or item demonstrates this skill: understanding when Cargo package structure and cargo check --workspace matter beyond one file?",
"What type, borrow, lifetime, module path, or thread boundary must be valid before stdout is printed?",
"If the input value changes, which line should change and which line should stay stable?"
],
"exercise_prompt": "Complete the exercise around this skill: understanding when Cargo package structure and cargo check --workspace matter beyond one file. Keep the intended Rust construct in the solution; do not bypass the lesson by printing the expected line directly."
}
}
}
{
"schema_version": 1,
"programming_language": "rust",
"ui_language": "es",
"lessons": {
"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 convierte esa habilidad en un programa Rust completo que compila: formatear valores con println! y producir stdout exacto para el juez. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: formatear valores con println! y producir stdout exacto para el juez.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Salida."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: formatear valores con println! y producir stdout exacto para el juez?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: formatear valores con println! y producir stdout exacto para el juez. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: preferir vinculaciones let inmutables y usar mut solo cuando el valor cambia de verdad. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: preferir vinculaciones let inmutables y usar mut solo cuando el valor cambia de verdad.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Vinculaciones y mutabilidad."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: preferir vinculaciones let inmutables y usar mut solo cuando el valor cambia de verdad?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: preferir vinculaciones let inmutables y usar mut solo cuando el valor cambia de verdad. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: combinar tipos numéricos explícitos con acceso a campos de tupla como pair.0 y pair.1. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: combinar tipos numéricos explícitos con acceso a campos de tupla como pair.0 y pair.1.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Números y tuplas."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: combinar tipos numéricos explícitos con acceso a campos de tupla como pair.0 y pair.1?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: combinar tipos numéricos explícitos con acceso a campos de tupla como pair.0 y pair.1. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: decidir cuándo hace falta un String con propiedad y cuándo basta un &str prestado. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: decidir cuándo hace falta un String con propiedad y cuándo basta un &str prestado.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Cadenas."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: decidir cuándo hace falta un String con propiedad y cuándo basta un &str prestado?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: decidir cuándo hace falta un String con propiedad y cuándo basta un &str prestado. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: usar if como expresión y rangos for para construir valores, no solo ramificar por costumbre. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: usar if como expresión y rangos for para construir valores, no solo ramificar por costumbre.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Flujo de control."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: usar if como expresión y rangos for para construir valores, no solo ramificar por costumbre?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: usar if como expresión y rangos for para construir valores, no solo ramificar por costumbre. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: escribir firmas donde los tipos de parámetros y retorno documenten el límite del cálculo. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: escribir firmas donde los tipos de parámetros y retorno documenten el límite del cálculo.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Funciones."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: escribir firmas donde los tipos de parámetros y retorno documenten el límite del cálculo?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: escribir firmas donde los tipos de parámetros y retorno documenten el límite del cálculo. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: agrupar campos del dominio en un struct y poner en impl el comportamiento que usa esos campos. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: agrupar campos del dominio en un struct y poner en impl el comportamiento que usa esos campos.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Structs e impl."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: agrupar campos del dominio en un struct y poner en impl el comportamiento que usa esos campos?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: agrupar campos del dominio en un struct y poner en impl el comportamiento que usa esos campos. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: modelar alternativas con variantes enum y cubrir cada variante de forma exhaustiva con match. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: modelar alternativas con variantes enum y cubrir cada variante de forma exhaustiva con match.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Enums y match."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: modelar alternativas con variantes enum y cubrir cada variante de forma exhaustiva con match?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: modelar alternativas con variantes enum y cubrir cada variante de forma exhaustiva con match. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: hacer explícitos los valores ausentes con Option<T> y extraer Some de forma segura. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: hacer explícitos los valores ausentes con Option<T> y extraer Some de forma segura.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Option e if let."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: hacer explícitos los valores ausentes con Option<T> y extraer Some de forma segura?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: hacer explícitos los valores ausentes con Option<T> y extraer Some de forma segura. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: controlar espacio de nombres, privacidad y rutas locales con mod, pub y use. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: controlar espacio de nombres, privacidad y rutas locales con mod, pub y use.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Módulos y use."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: controlar espacio de nombres, privacidad y rutas locales con mod, pub y use?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: controlar espacio de nombres, privacidad y rutas locales con mod, pub y use. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: leer stdin una vez, dividir texto en tokens y convertirlos a valores tipados. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: leer stdin una vez, dividir texto en tokens y convertirlos a valores tipados.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Parseo de entrada."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: leer stdin una vez, dividir texto en tokens y convertirlos a valores tipados?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: leer stdin una vez, dividir texto en tokens y convertirlos a valores tipados. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: elegir Vec para datos ordenados y la API entry de HashMap para conteo y búsqueda por clave. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: elegir Vec para datos ordenados y la API entry de HashMap para conteo y búsqueda por clave.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Vec y HashMap."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: elegir Vec para datos ordenados y la API entry de HashMap para conteo y búsqueda por clave?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: elegir Vec para datos ordenados y la API entry de HashMap para conteo y búsqueda por clave. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: pasar slices prestados para que las funciones lean datos sin tomar propiedad. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: pasar slices prestados para que las funciones lean datos sin tomar propiedad.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Préstamos y slices."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: pasar slices prestados para que las funciones lean datos sin tomar propiedad?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: pasar slices prestados para que las funciones lean datos sin tomar propiedad. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: devolver errores recuperables con Result<T, E> y propagarlos con el operador ?. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: devolver errores recuperables con Result<T, E> y propagarlos con el operador ?.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Result y ?."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: devolver errores recuperables con Result<T, E> y propagarlos con el operador ??",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: devolver errores recuperables con Result<T, E> y propagarlos con el operador ?. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: seguir cuándo un String se mueve, cuándo vuelve y cuándo una referencia solo lo presta. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: seguir cuándo un String se mueve, cuándo vuelve y cuándo una referencia solo lo presta.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Propiedad y préstamos."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: seguir cuándo un String se mueve, cuándo vuelve y cuándo una referencia solo lo presta?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: seguir cuándo un String se mueve, cuándo vuelve y cuándo una referencia solo lo presta. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: componer adaptadores iteradores perezosos con closures y consumirlos con sum o collect. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: componer adaptadores iteradores perezosos con closures y consumirlos con sum o collect.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Iteradores y closures."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: componer adaptadores iteradores perezosos con closures y consumirlos con sum o collect?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: componer adaptadores iteradores perezosos con closures y consumirlos con sum o collect. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: escribir una función sobre T manteniendo chequeo de tipos concretos en compilación. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: escribir una función sobre T manteniendo chequeo de tipos concretos en compilación.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Genéricos."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: escribir una función sobre T manteniendo chequeo de tipos concretos en compilación?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: escribir una función sobre T manteniendo chequeo de tipos concretos en compilación. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: describir comportamiento requerido con traits y usar bounds para invocarlo genéricamente. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: describir comportamiento requerido con traits y usar bounds para invocarlo genéricamente.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Traits y bounds."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: describir comportamiento requerido con traits y usar bounds para invocarlo genéricamente?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: describir comportamiento requerido con traits y usar bounds para invocarlo genéricamente. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: anotar relaciones entre entradas y salidas prestadas sin alargar el almacenamiento. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: anotar relaciones entre entradas y salidas prestadas sin alargar el almacenamiento.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Lifetimes."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: anotar relaciones entre entradas y salidas prestadas sin alargar el almacenamiento?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: anotar relaciones entre entradas y salidas prestadas sin alargar el almacenamiento. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: usar &dyn Trait cuando la flexibilidad del despacho en tiempo de ejecución compensa frente a un tipo genérico. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: usar &dyn Trait cuando la flexibilidad del despacho en tiempo de ejecución compensa frente a un tipo genérico.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Objetos trait y despacho dyn."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: usar &dyn Trait cuando la flexibilidad del despacho en tiempo de ejecución compensa frente a un tipo genérico?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: usar &dyn Trait cuando la flexibilidad del despacho en tiempo de ejecución compensa frente a un tipo genérico. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: separar lógica pura de main para que funciones #[test] y macros assert verifiquen comportamiento. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: separar lógica pura de main para que funciones #[test] y macros assert verifiquen comportamiento.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Pruebas y aserciones."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: separar lógica pura de main para que funciones #[test] y macros assert verifiquen comportamiento?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: separar lógica pura de main para que funciones #[test] y macros assert verifiquen comportamiento. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: usar Box<T> para poseer datos en heap y trabajar con comportamiento tipo puntero mediante deref. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: usar Box<T> para poseer datos en heap y trabajar con comportamiento tipo puntero mediante deref.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Smart pointers."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: usar Box<T> para poseer datos en heap y trabajar con comportamiento tipo puntero mediante deref?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: usar Box<T> para poseer datos en heap y trabajar con comportamiento tipo puntero mediante deref. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: usar RefCell<T> cuando el chequeo de préstamos en ejecución es el modelo correcto más simple. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: usar RefCell<T> cuando el chequeo de préstamos en ejecución es el modelo correcto más simple.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Mutabilidad interior."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: usar RefCell<T> cuando el chequeo de préstamos en ejecución es el modelo correcto más simple?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: usar RefCell<T> cuando el chequeo de préstamos en ejecución es el modelo correcto más simple. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: mover trabajo a thread::spawn y usar join para recuperar con seguridad el resultado del worker. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: mover trabajo a thread::spawn y usar join para recuperar con seguridad el resultado del worker.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Hilos y join."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: mover trabajo a thread::spawn y usar join para recuperar con seguridad el resultado del worker?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: mover trabajo a thread::spawn y usar join para recuperar con seguridad el resultado del worker. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: combinar Arc<T> para propiedad compartida con Mutex<T> para mutación de a una vez. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: combinar Arc<T> para propiedad compartida con Mutex<T> para mutación de a una vez.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Estado compartido con Arc y Mutex."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: combinar Arc<T> para propiedad compartida con Mutex<T> para mutación de a una vez?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: combinar Arc<T> para propiedad compartida con Mutex<T> para mutación de a una vez. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: reconocer que async crea un Future y que ejecutarlo requiere un executor o runtime. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: reconocer que async crea un Future y que ejecutarlo requiere un executor o runtime.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Async y await."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: reconocer que async crea un Future y que ejecutarlo requiere un executor o runtime?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: reconocer que async crea un Future y que ejecutarlo requiere un executor o runtime. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: emparejar patrones de tokens con macro_rules! y expandir código Rust repetido en compilación. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: emparejar patrones de tokens con macro_rules! y expandir código Rust repetido en compilación.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en macro_rules!."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: emparejar patrones de tokens con macro_rules! y expandir código Rust repetido en compilación?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: emparejar patrones de tokens con macro_rules! y expandir código Rust repetido en compilación. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: poner operaciones no verificables en bloques unsafe manteniendo la API externa lo más segura posible. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: poner operaciones no verificables en bloques unsafe manteniendo la API externa lo más segura posible.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Rust unsafe."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: poner operaciones no verificables en bloques unsafe manteniendo la API externa lo más segura posible?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: poner operaciones no verificables en bloques unsafe manteniendo la API externa lo más segura posible. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
},
"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 convierte esa habilidad en un programa Rust completo que compila: entender cuándo la estructura de paquetes Cargo y cargo check --workspace importan más allá de un archivo. Lee primero la firma o la definición del item y sigue el valor hasta println! para unir sintaxis y comportamiento.",
"common_mistakes": [
"Escribir directamente la salida final en vez de ejercitar esta habilidad: entender cuándo la estructura de paquetes Cargo y cargo check --workspace importan más allá de un archivo.",
"Borrar la construcción de Rust que esta lección quiere practicar solo para callar un error del compilador.",
"Ignorar el límite de tipo, propiedad, lifetime, ruta o hilo que aparece en Paquetes Cargo y workspaces."
],
"self_check": [
"Qué expresión o item del ejemplo demuestra esta habilidad: entender cuándo la estructura de paquetes Cargo y cargo check --workspace importan más allá de un archivo?",
"Qué tipo, préstamo, lifetime, ruta de módulo o frontera de hilo debe ser válido antes de imprimir stdout?",
"Si cambia el valor de entrada, qué línea debería cambiar y cuál debería permanecer estable?"
],
"exercise_prompt": "Completa el ejercicio alrededor de esta habilidad: entender cuándo la estructura de paquetes Cargo y cargo check --workspace importan más allá de un archivo. Mantén la construcción Rust prevista; no esquives la lección imprimiendo directamente la salida esperada."
}
}
}
{
"schema_version": 1,
"programming_language": "rust",
"ui_language": "ja",
"lessons": {
"rust-output": {
"title": "出力",
"concept": "出力 の目標は次の Rust 文法です: println! のフォーマット文字列で値を出力し、判定に必要な stdout を正確に合わせること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: println! のフォーマット文字列で値を出力し、判定に必要な stdout を正確に合わせること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: println! のフォーマット文字列で値を出力し、判定に必要な stdout を正確に合わせること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"出力 に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: println! のフォーマット文字列で値を出力し、判定に必要な stdout を正確に合わせること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: println! のフォーマット文字列で値を出力し、判定に必要な stdout を正確に合わせること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-variables": {
"title": "束縛と可変性",
"concept": "束縛と可変性 の目標は次の Rust 文法です: まず不変の let 束縛を使い、本当に値が変わる場所だけ mut を付けること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: まず不変の let 束縛を使い、本当に値が変わる場所だけ mut を付けること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: まず不変の let 束縛を使い、本当に値が変わる場所だけ mut を付けること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"束縛と可変性 に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: まず不変の let 束縛を使い、本当に値が変わる場所だけ mut を付けること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: まず不変の let 束縛を使い、本当に値が変わる場所だけ mut を付けること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-numbers-tuples": {
"title": "数値とタプル",
"concept": "数値とタプル の目標は次の Rust 文法です: 明示的な数値型と pair.0、pair.1 のようなタプルフィールドアクセスを組み合わせること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 明示的な数値型と pair.0、pair.1 のようなタプルフィールドアクセスを組み合わせること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 明示的な数値型と pair.0、pair.1 のようなタプルフィールドアクセスを組み合わせること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"数値とタプル に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 明示的な数値型と pair.0、pair.1 のようなタプルフィールドアクセスを組み合わせること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 明示的な数値型と pair.0、pair.1 のようなタプルフィールドアクセスを組み合わせること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-strings": {
"title": "文字列",
"concept": "文字列 の目標は次の Rust 文法です: 所有する String が必要な場面と、借用した &str スライスで十分な場面を分けること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 所有する String が必要な場面と、借用した &str スライスで十分な場面を分けること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 所有する String が必要な場面と、借用した &str スライスで十分な場面を分けること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"文字列 に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 所有する String が必要な場面と、借用した &str スライスで十分な場面を分けること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 所有する String が必要な場面と、借用した &str スライスで十分な場面を分けること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-control-flow": {
"title": "制御フロー",
"concept": "制御フロー の目標は次の Rust 文法です: 習慣的に分岐を書くのではなく、if 式と for の範囲反復で値を作ること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 習慣的に分岐を書くのではなく、if 式と for の範囲反復で値を作ること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 習慣的に分岐を書くのではなく、if 式と for の範囲反復で値を作ること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"制御フロー に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 習慣的に分岐を書くのではなく、if 式と for の範囲反復で値を作ること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 習慣的に分岐を書くのではなく、if 式と for の範囲反復で値を作ること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-functions": {
"title": "関数",
"concept": "関数 の目標は次の Rust 文法です: 引数型と戻り値型が計算の境界を説明するようにシグネチャを書くこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 引数型と戻り値型が計算の境界を説明するようにシグネチャを書くこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 引数型と戻り値型が計算の境界を説明するようにシグネチャを書くこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"関数 に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 引数型と戻り値型が計算の境界を説明するようにシグネチャを書くこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 引数型と戻り値型が計算の境界を説明するようにシグネチャを書くこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-structs-impl": {
"title": "構造体と impl",
"concept": "構造体と impl の目標は次の Rust 文法です: ドメインのフィールドを struct にまとめ、そのフィールドを使う振る舞いを impl に置くこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: ドメインのフィールドを struct にまとめ、そのフィールドを使う振る舞いを impl に置くこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: ドメインのフィールドを struct にまとめ、そのフィールドを使う振る舞いを impl に置くこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"構造体と impl に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: ドメインのフィールドを struct にまとめ、そのフィールドを使う振る舞いを impl に置くこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: ドメインのフィールドを struct にまとめ、そのフィールドを使う振る舞いを impl に置くこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-enum-match": {
"title": "enum と match",
"concept": "enum と match の目標は次の Rust 文法です: enum の variant で選択肢を表し、match ですべての場合を網羅的に扱うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: enum の variant で選択肢を表し、match ですべての場合を網羅的に扱うこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: enum の variant で選択肢を表し、match ですべての場合を網羅的に扱うこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"enum と match に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: enum の variant で選択肢を表し、match ですべての場合を網羅的に扱うこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: enum の variant で選択肢を表し、match ですべての場合を網羅的に扱うこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-option": {
"title": "Option と if let",
"concept": "Option と if let の目標は次の Rust 文法です: 存在しない可能性を Option<T> で明示し、Some の場合だけ安全に取り出すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 存在しない可能性を Option<T> で明示し、Some の場合だけ安全に取り出すこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 存在しない可能性を Option<T> で明示し、Some の場合だけ安全に取り出すこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"Option と if let に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 存在しない可能性を Option<T> で明示し、Some の場合だけ安全に取り出すこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 存在しない可能性を Option<T> で明示し、Some の場合だけ安全に取り出すこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-modules-use": {
"title": "モジュールと use",
"concept": "モジュールと use の目標は次の Rust 文法です: mod、pub、use で名前空間、公開範囲、ローカルパスを制御すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: mod、pub、use で名前空間、公開範囲、ローカルパスを制御すること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: mod、pub、use で名前空間、公開範囲、ローカルパスを制御すること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"モジュールと use に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: mod、pub、use で名前空間、公開範囲、ローカルパスを制御すること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: mod、pub、use で名前空間、公開範囲、ローカルパスを制御すること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-input": {
"title": "入力の解析",
"concept": "入力の解析 の目標は次の Rust 文法です: stdin を一度読み、テキストをトークンに分け、型付きの値へ変換すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: stdin を一度読み、テキストをトークンに分け、型付きの値へ変換すること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: stdin を一度読み、テキストをトークンに分け、型付きの値へ変換すること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"入力の解析 に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: stdin を一度読み、テキストをトークンに分け、型付きの値へ変換すること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: stdin を一度読み、テキストをトークンに分け、型付きの値へ変換すること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-vec-hashmap": {
"title": "Vec と HashMap",
"concept": "Vec と HashMap の目標は次の Rust 文法です: 順序付きデータには Vec、キーで数える処理や検索には HashMap の entry API を選ぶこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 順序付きデータには Vec、キーで数える処理や検索には HashMap の entry API を選ぶこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 順序付きデータには Vec、キーで数える処理や検索には HashMap の entry API を選ぶこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"Vec と HashMap に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 順序付きデータには Vec、キーで数える処理や検索には HashMap の entry API を選ぶこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 順序付きデータには Vec、キーで数える処理や検索には HashMap の entry API を選ぶこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-borrowing-slices": {
"title": "借用とスライス",
"concept": "借用とスライス の目標は次の Rust 文法です: 関数が所有権を取らずにデータを読めるよう、借用したスライスを渡すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 関数が所有権を取らずにデータを読めるよう、借用したスライスを渡すこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 関数が所有権を取らずにデータを読めるよう、借用したスライスを渡すこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"借用とスライス に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 関数が所有権を取らずにデータを読めるよう、借用したスライスを渡すこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 関数が所有権を取らずにデータを読めるよう、借用したスライスを渡すこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-result": {
"title": "Result と ?",
"concept": "Result と ? の目標は次の Rust 文法です: Result<T, E> で回復可能なエラーを返し、? 演算子で呼び出し元へ伝播すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: Result<T, E> で回復可能なエラーを返し、? 演算子で呼び出し元へ伝播すること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: Result<T, E> で回復可能なエラーを返し、? 演算子で呼び出し元へ伝播すること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"Result と ? に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: Result<T, E> で回復可能なエラーを返し、? 演算子で呼び出し元へ伝播すること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: Result<T, E> で回復可能なエラーを返し、? 演算子で呼び出し元へ伝播すること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-ownership": {
"title": "所有権と借用",
"concept": "所有権と借用 の目標は次の Rust 文法です: String がいつムーブされ、いつ返され、参照がいつ借用だけを行うか追跡すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: String がいつムーブされ、いつ返され、参照がいつ借用だけを行うか追跡すること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: String がいつムーブされ、いつ返され、参照がいつ借用だけを行うか追跡すること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"所有権と借用 に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: String がいつムーブされ、いつ返され、参照がいつ借用だけを行うか追跡すること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: String がいつムーブされ、いつ返され、参照がいつ借用だけを行うか追跡すること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-iterators": {
"title": "イテレータとクロージャ",
"concept": "イテレータとクロージャ の目標は次の Rust 文法です: 遅延評価の iterator アダプタをクロージャで組み合わせ、sum や collect で消費すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 遅延評価の iterator アダプタをクロージャで組み合わせ、sum や collect で消費すること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 遅延評価の iterator アダプタをクロージャで組み合わせ、sum や collect で消費すること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"イテレータとクロージャ に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 遅延評価の iterator アダプタをクロージャで組み合わせ、sum や collect で消費すること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 遅延評価の iterator アダプタをクロージャで組み合わせ、sum や collect で消費すること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-generics": {
"title": "ジェネリクス",
"concept": "ジェネリクス の目標は次の Rust 文法です: コンパイル時の具体的な型チェックを保ったまま、T に対して動く関数を書くこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: コンパイル時の具体的な型チェックを保ったまま、T に対して動く関数を書くこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: コンパイル時の具体的な型チェックを保ったまま、T に対して動く関数を書くこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"ジェネリクス に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: コンパイル時の具体的な型チェックを保ったまま、T に対して動く関数を書くこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: コンパイル時の具体的な型チェックを保ったまま、T に対して動く関数を書くこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-traits": {
"title": "トレイトと境界",
"concept": "トレイトと境界 の目標は次の Rust 文法です: trait で必要な振る舞いを表し、境界を使ってジェネリックコードから呼び出すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: trait で必要な振る舞いを表し、境界を使ってジェネリックコードから呼び出すこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: trait で必要な振る舞いを表し、境界を使ってジェネリックコードから呼び出すこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"トレイトと境界 に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: trait で必要な振る舞いを表し、境界を使ってジェネリックコードから呼び出すこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: trait で必要な振る舞いを表し、境界を使ってジェネリックコードから呼び出すこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-lifetimes": {
"title": "ライフタイム",
"concept": "ライフタイム の目標は次の Rust 文法です: 保存期間を伸ばすのではなく、借用入力と借用出力の関係を注釈すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 保存期間を伸ばすのではなく、借用入力と借用出力の関係を注釈すること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 保存期間を伸ばすのではなく、借用入力と借用出力の関係を注釈すること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"ライフタイム に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 保存期間を伸ばすのではなく、借用入力と借用出力の関係を注釈すること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 保存期間を伸ばすのではなく、借用入力と借用出力の関係を注釈すること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-traits-lifetimes": {
"title": "トレイトオブジェクトと dyn ディスパッチ",
"concept": "トレイトオブジェクトと dyn ディスパッチ の目標は次の Rust 文法です: 単一のジェネリック型より実行時ディスパッチの柔軟性が必要なとき &dyn Trait を使うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 単一のジェネリック型より実行時ディスパッチの柔軟性が必要なとき &dyn Trait を使うこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 単一のジェネリック型より実行時ディスパッチの柔軟性が必要なとき &dyn Trait を使うこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"トレイトオブジェクトと dyn ディスパッチ に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 単一のジェネリック型より実行時ディスパッチの柔軟性が必要なとき &dyn Trait を使うこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 単一のジェネリック型より実行時ディスパッチの柔軟性が必要なとき &dyn Trait を使うこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-testing": {
"title": "テストとアサーション",
"concept": "テストとアサーション の目標は次の Rust 文法です: 純粋なロジックを main から分け、#[test] 関数と assert マクロで振る舞いを検証すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 純粋なロジックを main から分け、#[test] 関数と assert マクロで振る舞いを検証すること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 純粋なロジックを main から分け、#[test] 関数と assert マクロで振る舞いを検証すること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"テストとアサーション に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 純粋なロジックを main から分け、#[test] 関数と assert マクロで振る舞いを検証すること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 純粋なロジックを main から分け、#[test] 関数と assert マクロで振る舞いを検証すること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-smart-pointers": {
"title": "スマートポインタ",
"concept": "スマートポインタ の目標は次の Rust 文法です: Box<T> でヒープ上のデータを所有しつつ、ポインタのような deref 動作を使うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: Box<T> でヒープ上のデータを所有しつつ、ポインタのような deref 動作を使うこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: Box<T> でヒープ上のデータを所有しつつ、ポインタのような deref 動作を使うこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"スマートポインタ に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: Box<T> でヒープ上のデータを所有しつつ、ポインタのような deref 動作を使うこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: Box<T> でヒープ上のデータを所有しつつ、ポインタのような deref 動作を使うこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-interior-mutability": {
"title": "内部可変性",
"concept": "内部可変性 の目標は次の Rust 文法です: 実行時の借用チェックが最も単純で正しいモデルになるとき RefCell<T> を使うこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 実行時の借用チェックが最も単純で正しいモデルになるとき RefCell<T> を使うこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 実行時の借用チェックが最も単純で正しいモデルになるとき RefCell<T> を使うこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"内部可変性 に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 実行時の借用チェックが最も単純で正しいモデルになるとき RefCell<T> を使うこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 実行時の借用チェックが最も単純で正しいモデルになるとき RefCell<T> を使うこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-concurrency": {
"title": "スレッドと join",
"concept": "スレッドと join の目標は次の Rust 文法です: thread::spawn に作業を移し、join で worker の結果を安全に回収すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: thread::spawn に作業を移し、join で worker の結果を安全に回収すること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: thread::spawn に作業を移し、join で worker の結果を安全に回収すること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"スレッドと join に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: thread::spawn に作業を移し、join で worker の結果を安全に回収すること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: thread::spawn に作業を移し、join で worker の結果を安全に回収すること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-shared-state": {
"title": "Arc と Mutex の共有状態",
"concept": "Arc と Mutex の共有状態 の目標は次の Rust 文法です: Arc<T> で共有所有権を作り、Mutex<T> で一度に一つの変更だけを許すこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: Arc<T> で共有所有権を作り、Mutex<T> で一度に一つの変更だけを許すこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: Arc<T> で共有所有権を作り、Mutex<T> で一度に一つの変更だけを許すこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"Arc と Mutex の共有状態 に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: Arc<T> で共有所有権を作り、Mutex<T> で一度に一つの変更だけを許すこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: Arc<T> で共有所有権を作り、Mutex<T> で一度に一つの変更だけを許すこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-async-await": {
"title": "async と await",
"concept": "async と await の目標は次の Rust 文法です: async が Future を作り、それを実行するには executor や runtime が必要だと理解すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: async が Future を作り、それを実行するには executor や runtime が必要だと理解すること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: async が Future を作り、それを実行するには executor や runtime が必要だと理解すること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"async と await に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: async が Future を作り、それを実行するには executor や runtime が必要だと理解すること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: async が Future を作り、それを実行するには executor や runtime が必要だと理解すること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-macros": {
"title": "macro_rules!",
"concept": "macro_rules! の目標は次の Rust 文法です: macro_rules! でトークンパターンを照合し、繰り返しの Rust コードをコンパイル時に展開すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: macro_rules! でトークンパターンを照合し、繰り返しの Rust コードをコンパイル時に展開すること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: macro_rules! でトークンパターンを照合し、繰り返しの Rust コードをコンパイル時に展開すること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"macro_rules! に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: macro_rules! でトークンパターンを照合し、繰り返しの Rust コードをコンパイル時に展開すること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: macro_rules! でトークンパターンを照合し、繰り返しの Rust コードをコンパイル時に展開すること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-unsafe": {
"title": "unsafe Rust",
"concept": "unsafe Rust の目標は次の Rust 文法です: コンパイラが検証できない操作を unsafe ブロックに置き、周囲の API はできるだけ安全に保つこと。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: コンパイラが検証できない操作を unsafe ブロックに置き、周囲の API はできるだけ安全に保つこと。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: コンパイラが検証できない操作を unsafe ブロックに置き、周囲の API はできるだけ安全に保つこと。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"unsafe Rust に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: コンパイラが検証できない操作を unsafe ブロックに置き、周囲の API はできるだけ安全に保つこと?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: コンパイラが検証できない操作を unsafe ブロックに置き、周囲の API はできるだけ安全に保つこと。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
},
"rust-cargo-workspaces": {
"title": "Cargo パッケージとワークスペース",
"concept": "Cargo パッケージとワークスペース の目標は次の Rust 文法です: 単一ファイルを越えて Cargo のパッケージ構造と cargo check --workspace が必要になる場面を理解すること。例で値が作られる場所、ムーブまたは借用される場所、最後に stdout へ届く式を順に追跡してください。",
"worked_example": "この例は次の目標をコンパイル可能な Rust プログラムで示します: 単一ファイルを越えて Cargo のパッケージ構造と cargo check --workspace が必要になる場面を理解すること。まずシグネチャや item 定義を読み、値が println! へ進む流れを追ってください。",
"common_mistakes": [
"次の目標を練習せず、最後の出力文字列だけを固定で書くこと: 単一ファイルを越えて Cargo のパッケージ構造と cargo check --workspace が必要になる場面を理解すること。",
"コンパイルエラーを消すために、この単元で練習する Rust 構文そのものを削ってしまうこと。",
"Cargo パッケージとワークスペース に関わる型、所有権、ライフタイム、パス、スレッド境界を確認しないこと。"
],
"self_check": [
"例のどの式または item がこの目標を示していますか: 単一ファイルを越えて Cargo のパッケージ構造と cargo check --workspace が必要になる場面を理解すること?",
"stdout が出る前に、どの型、借用、ライフタイム、モジュールパス、スレッド境界が正しい必要がありますか?",
"入力値が変わるなら、どの行が変わり、どの行は安定しているべきですか?"
],
"exercise_prompt": "次の目標を実際に練習するようにコードを完成させてください: 単一ファイルを越えて Cargo のパッケージ構造と cargo check --workspace が必要になる場面を理解すること。意図された Rust 構文を残し、期待出力だけを直接表示して単元を回避しないでください。"
}
}
}
{
"schema_version": 1,
"programming_language": "rust",
"ui_language": "ko",
"lessons": {
"rust-output": {
"title": "출력",
"concept": "출력 단원에서는 다음 Rust 문법 목표를 익힙니다: println! 포맷 문자열로 값을 넣고 채점기가 요구하는 stdout을 정확히 맞추는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: println! 포맷 문자열로 값을 넣고 채점기가 요구하는 stdout을 정확히 맞추는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: println! 포맷 문자열로 값을 넣고 채점기가 요구하는 stdout을 정확히 맞추는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"출력에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: println! 포맷 문자열로 값을 넣고 채점기가 요구하는 stdout을 정확히 맞추는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: println! 포맷 문자열로 값을 넣고 채점기가 요구하는 stdout을 정확히 맞추는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-variables": {
"title": "바인딩과 가변성",
"concept": "바인딩과 가변성 단원에서는 다음 Rust 문법 목표를 익힙니다: 기본은 불변 let 바인딩으로 두고 값이 실제로 바뀌는 지점에만 mut을 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 기본은 불변 let 바인딩으로 두고 값이 실제로 바뀌는 지점에만 mut을 쓰는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 기본은 불변 let 바인딩으로 두고 값이 실제로 바뀌는 지점에만 mut을 쓰는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"바인딩과 가변성에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 기본은 불변 let 바인딩으로 두고 값이 실제로 바뀌는 지점에만 mut을 쓰는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 기본은 불변 let 바인딩으로 두고 값이 실제로 바뀌는 지점에만 mut을 쓰는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-numbers-tuples": {
"title": "숫자와 튜플",
"concept": "숫자와 튜플 단원에서는 다음 Rust 문법 목표를 익힙니다: 명시적인 숫자 타입과 pair.0, pair.1 같은 튜플 필드 접근을 함께 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 명시적인 숫자 타입과 pair.0, pair.1 같은 튜플 필드 접근을 함께 쓰는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 명시적인 숫자 타입과 pair.0, pair.1 같은 튜플 필드 접근을 함께 쓰는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"숫자와 튜플에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 명시적인 숫자 타입과 pair.0, pair.1 같은 튜플 필드 접근을 함께 쓰는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 명시적인 숫자 타입과 pair.0, pair.1 같은 튜플 필드 접근을 함께 쓰는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-strings": {
"title": "문자열",
"concept": "문자열 단원에서는 다음 Rust 문법 목표를 익힙니다: 소유하는 String이 필요한 때와 빌린 &str 슬라이스로 충분한 때를 구분하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 소유하는 String이 필요한 때와 빌린 &str 슬라이스로 충분한 때를 구분하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 소유하는 String이 필요한 때와 빌린 &str 슬라이스로 충분한 때를 구분하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"문자열에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 소유하는 String이 필요한 때와 빌린 &str 슬라이스로 충분한 때를 구분하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 소유하는 String이 필요한 때와 빌린 &str 슬라이스로 충분한 때를 구분하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-control-flow": {
"title": "제어 흐름",
"concept": "제어 흐름 단원에서는 다음 Rust 문법 목표를 익힙니다: 습관적인 분기 대신 if 표현식과 for 범위 반복으로 값을 만들어 내는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 습관적인 분기 대신 if 표현식과 for 범위 반복으로 값을 만들어 내는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 습관적인 분기 대신 if 표현식과 for 범위 반복으로 값을 만들어 내는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"제어 흐름에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 습관적인 분기 대신 if 표현식과 for 범위 반복으로 값을 만들어 내는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 습관적인 분기 대신 if 표현식과 for 범위 반복으로 값을 만들어 내는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-functions": {
"title": "함수",
"concept": "함수 단원에서는 다음 Rust 문법 목표를 익힙니다: 매개변수와 반환 타입이 계산의 경계를 설명하도록 함수 시그니처를 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 매개변수와 반환 타입이 계산의 경계를 설명하도록 함수 시그니처를 쓰는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 매개변수와 반환 타입이 계산의 경계를 설명하도록 함수 시그니처를 쓰는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"함수에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 매개변수와 반환 타입이 계산의 경계를 설명하도록 함수 시그니처를 쓰는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 매개변수와 반환 타입이 계산의 경계를 설명하도록 함수 시그니처를 쓰는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-structs-impl": {
"title": "구조체와 impl",
"concept": "구조체와 impl 단원에서는 다음 Rust 문법 목표를 익힙니다: 도메인 필드를 struct로 묶고 그 필드를 쓰는 동작을 impl에 두는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 도메인 필드를 struct로 묶고 그 필드를 쓰는 동작을 impl에 두는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 도메인 필드를 struct로 묶고 그 필드를 쓰는 동작을 impl에 두는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"구조체와 impl에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 도메인 필드를 struct로 묶고 그 필드를 쓰는 동작을 impl에 두는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 도메인 필드를 struct로 묶고 그 필드를 쓰는 동작을 impl에 두는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-enum-match": {
"title": "enum과 match",
"concept": "enum과 match 단원에서는 다음 Rust 문법 목표를 익힙니다: enum variant로 가능한 경우를 모델링하고 match로 모든 경우를 빠짐없이 처리하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: enum variant로 가능한 경우를 모델링하고 match로 모든 경우를 빠짐없이 처리하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: enum variant로 가능한 경우를 모델링하고 match로 모든 경우를 빠짐없이 처리하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"enum과 match에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: enum variant로 가능한 경우를 모델링하고 match로 모든 경우를 빠짐없이 처리하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: enum variant로 가능한 경우를 모델링하고 match로 모든 경우를 빠짐없이 처리하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-option": {
"title": "Option과 if let",
"concept": "Option과 if let 단원에서는 다음 Rust 문법 목표를 익힙니다: 없을 수 있는 값을 Option<T>로 명시하고 Some인 경우만 안전하게 꺼내는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 없을 수 있는 값을 Option<T>로 명시하고 Some인 경우만 안전하게 꺼내는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 없을 수 있는 값을 Option<T>로 명시하고 Some인 경우만 안전하게 꺼내는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"Option과 if let에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 없을 수 있는 값을 Option<T>로 명시하고 Some인 경우만 안전하게 꺼내는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 없을 수 있는 값을 Option<T>로 명시하고 Some인 경우만 안전하게 꺼내는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-modules-use": {
"title": "모듈과 use",
"concept": "모듈과 use 단원에서는 다음 Rust 문법 목표를 익힙니다: mod, pub, use로 네임스페이스와 공개 범위, 지역 경로를 제어하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: mod, pub, use로 네임스페이스와 공개 범위, 지역 경로를 제어하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: mod, pub, use로 네임스페이스와 공개 범위, 지역 경로를 제어하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"모듈과 use에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: mod, pub, use로 네임스페이스와 공개 범위, 지역 경로를 제어하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: mod, pub, use로 네임스페이스와 공개 범위, 지역 경로를 제어하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-input": {
"title": "입력 파싱",
"concept": "입력 파싱 단원에서는 다음 Rust 문법 목표를 익힙니다: stdin을 한 번 읽고 텍스트를 토큰으로 나눈 뒤 타입 있는 값으로 변환하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: stdin을 한 번 읽고 텍스트를 토큰으로 나눈 뒤 타입 있는 값으로 변환하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: stdin을 한 번 읽고 텍스트를 토큰으로 나눈 뒤 타입 있는 값으로 변환하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"입력 파싱에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: stdin을 한 번 읽고 텍스트를 토큰으로 나눈 뒤 타입 있는 값으로 변환하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: stdin을 한 번 읽고 텍스트를 토큰으로 나눈 뒤 타입 있는 값으로 변환하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-vec-hashmap": {
"title": "Vec과 HashMap",
"concept": "Vec과 HashMap 단원에서는 다음 Rust 문법 목표를 익힙니다: 순서 있는 데이터에는 Vec, 키 기반 카운팅과 조회에는 HashMap entry API를 고르는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 순서 있는 데이터에는 Vec, 키 기반 카운팅과 조회에는 HashMap entry API를 고르는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 순서 있는 데이터에는 Vec, 키 기반 카운팅과 조회에는 HashMap entry API를 고르는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"Vec과 HashMap에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 순서 있는 데이터에는 Vec, 키 기반 카운팅과 조회에는 HashMap entry API를 고르는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 순서 있는 데이터에는 Vec, 키 기반 카운팅과 조회에는 HashMap entry API를 고르는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-borrowing-slices": {
"title": "빌림과 슬라이스",
"concept": "빌림과 슬라이스 단원에서는 다음 Rust 문법 목표를 익힙니다: 함수가 소유권을 가져가지 않고 데이터를 읽도록 빌린 슬라이스를 넘기는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 함수가 소유권을 가져가지 않고 데이터를 읽도록 빌린 슬라이스를 넘기는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 함수가 소유권을 가져가지 않고 데이터를 읽도록 빌린 슬라이스를 넘기는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"빌림과 슬라이스에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 함수가 소유권을 가져가지 않고 데이터를 읽도록 빌린 슬라이스를 넘기는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 함수가 소유권을 가져가지 않고 데이터를 읽도록 빌린 슬라이스를 넘기는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-result": {
"title": "Result와 ?",
"concept": "Result와 ? 단원에서는 다음 Rust 문법 목표를 익힙니다: Result<T, E>로 복구 가능한 오류를 반환하고 ? 연산자로 호출자에게 전파하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: Result<T, E>로 복구 가능한 오류를 반환하고 ? 연산자로 호출자에게 전파하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: Result<T, E>로 복구 가능한 오류를 반환하고 ? 연산자로 호출자에게 전파하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"Result와 ?에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: Result<T, E>로 복구 가능한 오류를 반환하고 ? 연산자로 호출자에게 전파하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: Result<T, E>로 복구 가능한 오류를 반환하고 ? 연산자로 호출자에게 전파하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-ownership": {
"title": "소유권과 빌림",
"concept": "소유권과 빌림 단원에서는 다음 Rust 문법 목표를 익힙니다: String이 언제 이동하고 언제 반환되며 참조가 언제 빌리기만 하는지 추적하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: String이 언제 이동하고 언제 반환되며 참조가 언제 빌리기만 하는지 추적하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: String이 언제 이동하고 언제 반환되며 참조가 언제 빌리기만 하는지 추적하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"소유권과 빌림에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: String이 언제 이동하고 언제 반환되며 참조가 언제 빌리기만 하는지 추적하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: String이 언제 이동하고 언제 반환되며 참조가 언제 빌리기만 하는지 추적하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-iterators": {
"title": "이터레이터와 클로저",
"concept": "이터레이터와 클로저 단원에서는 다음 Rust 문법 목표를 익힙니다: 지연 실행되는 iterator adapter를 클로저로 조합하고 sum이나 collect로 소비하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 지연 실행되는 iterator adapter를 클로저로 조합하고 sum이나 collect로 소비하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 지연 실행되는 iterator adapter를 클로저로 조합하고 sum이나 collect로 소비하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"이터레이터와 클로저에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 지연 실행되는 iterator adapter를 클로저로 조합하고 sum이나 collect로 소비하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 지연 실행되는 iterator adapter를 클로저로 조합하고 sum이나 collect로 소비하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-generics": {
"title": "제네릭",
"concept": "제네릭 단원에서는 다음 Rust 문법 목표를 익힙니다: 컴파일 시점의 구체 타입 검사를 유지하면서 T에 대해 동작하는 함수를 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 컴파일 시점의 구체 타입 검사를 유지하면서 T에 대해 동작하는 함수를 쓰는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 컴파일 시점의 구체 타입 검사를 유지하면서 T에 대해 동작하는 함수를 쓰는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"제네릭에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 컴파일 시점의 구체 타입 검사를 유지하면서 T에 대해 동작하는 함수를 쓰는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 컴파일 시점의 구체 타입 검사를 유지하면서 T에 대해 동작하는 함수를 쓰는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-traits": {
"title": "트레이트와 바운드",
"concept": "트레이트와 바운드 단원에서는 다음 Rust 문법 목표를 익힙니다: trait로 필요한 동작을 설명하고 bound로 그 동작을 제네릭 코드에서 호출하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: trait로 필요한 동작을 설명하고 bound로 그 동작을 제네릭 코드에서 호출하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: trait로 필요한 동작을 설명하고 bound로 그 동작을 제네릭 코드에서 호출하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"트레이트와 바운드에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: trait로 필요한 동작을 설명하고 bound로 그 동작을 제네릭 코드에서 호출하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: trait로 필요한 동작을 설명하고 bound로 그 동작을 제네릭 코드에서 호출하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-lifetimes": {
"title": "라이프타임",
"concept": "라이프타임 단원에서는 다음 Rust 문법 목표를 익힙니다: 저장 기간을 늘리는 것이 아니라 빌린 입력과 빌린 출력의 관계를 표시하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 저장 기간을 늘리는 것이 아니라 빌린 입력과 빌린 출력의 관계를 표시하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 저장 기간을 늘리는 것이 아니라 빌린 입력과 빌린 출력의 관계를 표시하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"라이프타임에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 저장 기간을 늘리는 것이 아니라 빌린 입력과 빌린 출력의 관계를 표시하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 저장 기간을 늘리는 것이 아니라 빌린 입력과 빌린 출력의 관계를 표시하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-traits-lifetimes": {
"title": "트레이트 객체와 dyn 디스패치",
"concept": "트레이트 객체와 dyn 디스패치 단원에서는 다음 Rust 문법 목표를 익힙니다: 하나의 제네릭 타입보다 런타임 디스패치의 유연성이 필요할 때 &dyn Trait을 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 하나의 제네릭 타입보다 런타임 디스패치의 유연성이 필요할 때 &dyn Trait을 쓰는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 하나의 제네릭 타입보다 런타임 디스패치의 유연성이 필요할 때 &dyn Trait을 쓰는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"트레이트 객체와 dyn 디스패치에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 하나의 제네릭 타입보다 런타임 디스패치의 유연성이 필요할 때 &dyn Trait을 쓰는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 하나의 제네릭 타입보다 런타임 디스패치의 유연성이 필요할 때 &dyn Trait을 쓰는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-testing": {
"title": "테스트와 assert",
"concept": "테스트와 assert 단원에서는 다음 Rust 문법 목표를 익힙니다: 순수 로직을 main 밖으로 분리해 #[test] 함수와 assert 매크로가 동작을 검증하게 하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 순수 로직을 main 밖으로 분리해 #[test] 함수와 assert 매크로가 동작을 검증하게 하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 순수 로직을 main 밖으로 분리해 #[test] 함수와 assert 매크로가 동작을 검증하게 하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"테스트와 assert에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 순수 로직을 main 밖으로 분리해 #[test] 함수와 assert 매크로가 동작을 검증하게 하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 순수 로직을 main 밖으로 분리해 #[test] 함수와 assert 매크로가 동작을 검증하게 하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-smart-pointers": {
"title": "스마트 포인터",
"concept": "스마트 포인터 단원에서는 다음 Rust 문법 목표를 익힙니다: Box<T>로 힙 데이터를 소유하면서 포인터처럼 역참조되는 동작을 활용하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: Box<T>로 힙 데이터를 소유하면서 포인터처럼 역참조되는 동작을 활용하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: Box<T>로 힙 데이터를 소유하면서 포인터처럼 역참조되는 동작을 활용하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"스마트 포인터에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: Box<T>로 힙 데이터를 소유하면서 포인터처럼 역참조되는 동작을 활용하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: Box<T>로 힙 데이터를 소유하면서 포인터처럼 역참조되는 동작을 활용하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-interior-mutability": {
"title": "내부 가변성",
"concept": "내부 가변성 단원에서는 다음 Rust 문법 목표를 익힙니다: 런타임 빌림 검사가 가장 단순하고 올바른 모델일 때 RefCell<T>를 쓰는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 런타임 빌림 검사가 가장 단순하고 올바른 모델일 때 RefCell<T>를 쓰는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 런타임 빌림 검사가 가장 단순하고 올바른 모델일 때 RefCell<T>를 쓰는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"내부 가변성에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 런타임 빌림 검사가 가장 단순하고 올바른 모델일 때 RefCell<T>를 쓰는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 런타임 빌림 검사가 가장 단순하고 올바른 모델일 때 RefCell<T>를 쓰는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-concurrency": {
"title": "스레드와 join",
"concept": "스레드와 join 단원에서는 다음 Rust 문법 목표를 익힙니다: thread::spawn으로 작업을 옮기고 join으로 worker 결과를 안전하게 회수하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: thread::spawn으로 작업을 옮기고 join으로 worker 결과를 안전하게 회수하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: thread::spawn으로 작업을 옮기고 join으로 worker 결과를 안전하게 회수하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"스레드와 join에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: thread::spawn으로 작업을 옮기고 join으로 worker 결과를 안전하게 회수하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: thread::spawn으로 작업을 옮기고 join으로 worker 결과를 안전하게 회수하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-shared-state": {
"title": "Arc와 Mutex의 공유 상태",
"concept": "Arc와 Mutex의 공유 상태 단원에서는 다음 Rust 문법 목표를 익힙니다: Arc<T>로 공유 소유권을 만들고 Mutex<T>로 한 번에 하나의 변경만 허용하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: Arc<T>로 공유 소유권을 만들고 Mutex<T>로 한 번에 하나의 변경만 허용하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: Arc<T>로 공유 소유권을 만들고 Mutex<T>로 한 번에 하나의 변경만 허용하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"Arc와 Mutex의 공유 상태에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: Arc<T>로 공유 소유권을 만들고 Mutex<T>로 한 번에 하나의 변경만 허용하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: Arc<T>로 공유 소유권을 만들고 Mutex<T>로 한 번에 하나의 변경만 허용하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-async-await": {
"title": "async와 await",
"concept": "async와 await 단원에서는 다음 Rust 문법 목표를 익힙니다: async가 Future를 만들고 이를 실행하려면 executor나 runtime이 필요하다는 점을 이해하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: async가 Future를 만들고 이를 실행하려면 executor나 runtime이 필요하다는 점을 이해하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: async가 Future를 만들고 이를 실행하려면 executor나 runtime이 필요하다는 점을 이해하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"async와 await에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: async가 Future를 만들고 이를 실행하려면 executor나 runtime이 필요하다는 점을 이해하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: async가 Future를 만들고 이를 실행하려면 executor나 runtime이 필요하다는 점을 이해하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-macros": {
"title": "macro_rules!",
"concept": "macro_rules! 단원에서는 다음 Rust 문법 목표를 익힙니다: macro_rules!로 토큰 패턴을 매칭하고 반복되는 Rust 코드를 컴파일 시점에 확장하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: macro_rules!로 토큰 패턴을 매칭하고 반복되는 Rust 코드를 컴파일 시점에 확장하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: macro_rules!로 토큰 패턴을 매칭하고 반복되는 Rust 코드를 컴파일 시점에 확장하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"macro_rules!에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: macro_rules!로 토큰 패턴을 매칭하고 반복되는 Rust 코드를 컴파일 시점에 확장하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: macro_rules!로 토큰 패턴을 매칭하고 반복되는 Rust 코드를 컴파일 시점에 확장하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-unsafe": {
"title": "unsafe Rust",
"concept": "unsafe Rust 단원에서는 다음 Rust 문법 목표를 익힙니다: 컴파일러가 검증할 수 없는 연산을 unsafe 블록 안에 두고 주변 API는 최대한 안전하게 유지하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 컴파일러가 검증할 수 없는 연산을 unsafe 블록 안에 두고 주변 API는 최대한 안전하게 유지하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 컴파일러가 검증할 수 없는 연산을 unsafe 블록 안에 두고 주변 API는 최대한 안전하게 유지하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"unsafe Rust에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 컴파일러가 검증할 수 없는 연산을 unsafe 블록 안에 두고 주변 API는 최대한 안전하게 유지하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 컴파일러가 검증할 수 없는 연산을 unsafe 블록 안에 두고 주변 API는 최대한 안전하게 유지하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
},
"rust-cargo-workspaces": {
"title": "Cargo 패키지와 워크스페이스",
"concept": "Cargo 패키지와 워크스페이스 단원에서는 다음 Rust 문법 목표를 익힙니다: 단일 파일을 넘어 Cargo 패키지 구조와 cargo check --workspace가 필요한 때를 이해하는 법. 예제에서 값이 생성되는 위치, 이동하거나 빌리는 지점, 마지막에 stdout으로 이어지는 표현식을 차례대로 추적하세요.",
"worked_example": "풀이 예제는 이 목표를 컴파일되는 Rust 프로그램 안에서 보여줍니다: 단일 파일을 넘어 Cargo 패키지 구조와 cargo check --workspace가 필요한 때를 이해하는 법. 먼저 시그니처나 item 정의를 읽고, 값이 println!까지 어떻게 이어지는지 따라가세요.",
"common_mistakes": [
"다음 목표를 연습하지 않고 마지막 출력 문자열만 하드코딩하는 실수: 단일 파일을 넘어 Cargo 패키지 구조와 cargo check --workspace가 필요한 때를 이해하는 법.",
"컴파일 오류를 없애려고 이 단원이 연습하려는 Rust 구문 자체를 지워 버리는 실수.",
"Cargo 패키지와 워크스페이스에 포함된 타입, 소유권, 라이프타임, 경로, 스레드 경계를 확인하지 않는 실수."
],
"self_check": [
"예제의 어떤 표현식이나 item이 이 목표를 보여 주나요: 단일 파일을 넘어 Cargo 패키지 구조와 cargo check --workspace가 필요한 때를 이해하는 법?",
"stdout이 출력되기 전에 어떤 타입, 빌림, 라이프타임, 모듈 경로, 스레드 경계가 맞아야 하나요?",
"입력값이 바뀐다면 어느 줄이 바뀌어야 하고 어느 줄은 그대로 유지되어야 하나요?"
],
"exercise_prompt": "다음 목표를 직접 연습하도록 실습 코드를 완성하세요: 단일 파일을 넘어 Cargo 패키지 구조와 cargo check --workspace가 필요한 때를 이해하는 법. 의도한 Rust 구문을 유지하고, 기대 출력만 직접 찍어서 단원을 우회하지 마세요."
}
}
}
{
"schema_version": 1,
"programming_language": "rust",
"ui_language": "zh",
"lessons": {
"rust-output": {
"title": "输出",
"concept": "输出 的 Rust 语法目标是:用 println! 格式化值,并精确匹配评测需要的 stdout。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:用 println! 格式化值,并精确匹配评测需要的 stdout。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:用 println! 格式化值,并精确匹配评测需要的 stdout。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 输出 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:用 println! 格式化值,并精确匹配评测需要的 stdout?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:用 println! 格式化值,并精确匹配评测需要的 stdout。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-variables": {
"title": "绑定与可变性",
"concept": "绑定与可变性 的 Rust 语法目标是:优先使用不可变 let 绑定,只在值确实变化的位置使用 mut。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:优先使用不可变 let 绑定,只在值确实变化的位置使用 mut。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:优先使用不可变 let 绑定,只在值确实变化的位置使用 mut。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 绑定与可变性 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:优先使用不可变 let 绑定,只在值确实变化的位置使用 mut?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:优先使用不可变 let 绑定,只在值确实变化的位置使用 mut。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-numbers-tuples": {
"title": "数字与元组",
"concept": "数字与元组 的 Rust 语法目标是:结合显式数字类型与 pair.0、pair.1 这样的元组字段访问。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:结合显式数字类型与 pair.0、pair.1 这样的元组字段访问。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:结合显式数字类型与 pair.0、pair.1 这样的元组字段访问。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 数字与元组 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:结合显式数字类型与 pair.0、pair.1 这样的元组字段访问?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:结合显式数字类型与 pair.0、pair.1 这样的元组字段访问。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-strings": {
"title": "字符串",
"concept": "字符串 的 Rust 语法目标是:区分何时需要拥有所有权的 String,何时借用的 &str 切片就足够。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:区分何时需要拥有所有权的 String,何时借用的 &str 切片就足够。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:区分何时需要拥有所有权的 String,何时借用的 &str 切片就足够。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 字符串 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:区分何时需要拥有所有权的 String,何时借用的 &str 切片就足够?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:区分何时需要拥有所有权的 String,何时借用的 &str 切片就足够。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-control-flow": {
"title": "控制流",
"concept": "控制流 的 Rust 语法目标是:把 if 当作表达式并用 for 范围构造值,而不是机械地写分支。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:把 if 当作表达式并用 for 范围构造值,而不是机械地写分支。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:把 if 当作表达式并用 for 范围构造值,而不是机械地写分支。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 控制流 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:把 if 当作表达式并用 for 范围构造值,而不是机械地写分支?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:把 if 当作表达式并用 for 范围构造值,而不是机械地写分支。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-functions": {
"title": "函数",
"concept": "函数 的 Rust 语法目标是:用参数类型和返回类型清楚表达计算边界。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:用参数类型和返回类型清楚表达计算边界。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:用参数类型和返回类型清楚表达计算边界。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 函数 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:用参数类型和返回类型清楚表达计算边界?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:用参数类型和返回类型清楚表达计算边界。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-structs-impl": {
"title": "结构体与 impl",
"concept": "结构体与 impl 的 Rust 语法目标是:把领域字段组织到 struct 中,并把依赖这些字段的行为放进 impl。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:把领域字段组织到 struct 中,并把依赖这些字段的行为放进 impl。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:把领域字段组织到 struct 中,并把依赖这些字段的行为放进 impl。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 结构体与 impl 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:把领域字段组织到 struct 中,并把依赖这些字段的行为放进 impl?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:把领域字段组织到 struct 中,并把依赖这些字段的行为放进 impl。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-enum-match": {
"title": "枚举与 match",
"concept": "枚举与 match 的 Rust 语法目标是:用 enum 变体建模不同情况,并用 match 穷尽处理每一种情况。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:用 enum 变体建模不同情况,并用 match 穷尽处理每一种情况。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:用 enum 变体建模不同情况,并用 match 穷尽处理每一种情况。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 枚举与 match 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:用 enum 变体建模不同情况,并用 match 穷尽处理每一种情况?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:用 enum 变体建模不同情况,并用 match 穷尽处理每一种情况。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-option": {
"title": "Option 与 if let",
"concept": "Option 与 if let 的 Rust 语法目标是:用 Option<T> 明确表示可能缺失的值,并安全处理 Some 分支。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:用 Option<T> 明确表示可能缺失的值,并安全处理 Some 分支。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:用 Option<T> 明确表示可能缺失的值,并安全处理 Some 分支。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 Option 与 if let 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:用 Option<T> 明确表示可能缺失的值,并安全处理 Some 分支?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:用 Option<T> 明确表示可能缺失的值,并安全处理 Some 分支。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-modules-use": {
"title": "模块与 use",
"concept": "模块与 use 的 Rust 语法目标是:用 mod、pub、use 控制命名空间、可见性与本地路径。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:用 mod、pub、use 控制命名空间、可见性与本地路径。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:用 mod、pub、use 控制命名空间、可见性与本地路径。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 模块与 use 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:用 mod、pub、use 控制命名空间、可见性与本地路径?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:用 mod、pub、use 控制命名空间、可见性与本地路径。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-input": {
"title": "输入解析",
"concept": "输入解析 的 Rust 语法目标是:一次读取 stdin,把文本拆成 token,再转换成有类型的值。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:一次读取 stdin,把文本拆成 token,再转换成有类型的值。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:一次读取 stdin,把文本拆成 token,再转换成有类型的值。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 输入解析 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:一次读取 stdin,把文本拆成 token,再转换成有类型的值?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:一次读取 stdin,把文本拆成 token,再转换成有类型的值。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-vec-hashmap": {
"title": "Vec 与 HashMap",
"concept": "Vec 与 HashMap 的 Rust 语法目标是:有顺序的数据使用 Vec,按键计数和查找使用 HashMap 的 entry API。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:有顺序的数据使用 Vec,按键计数和查找使用 HashMap 的 entry API。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:有顺序的数据使用 Vec,按键计数和查找使用 HashMap 的 entry API。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 Vec 与 HashMap 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:有顺序的数据使用 Vec,按键计数和查找使用 HashMap 的 entry API?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:有顺序的数据使用 Vec,按键计数和查找使用 HashMap 的 entry API。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-borrowing-slices": {
"title": "借用与切片",
"concept": "借用与切片 的 Rust 语法目标是:传递借用的切片,让函数读取数据而不取得所有权。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:传递借用的切片,让函数读取数据而不取得所有权。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:传递借用的切片,让函数读取数据而不取得所有权。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 借用与切片 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:传递借用的切片,让函数读取数据而不取得所有权?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:传递借用的切片,让函数读取数据而不取得所有权。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-result": {
"title": "Result 与 ?",
"concept": "Result 与 ? 的 Rust 语法目标是:用 Result<T, E> 返回可恢复错误,并用 ? 运算符向调用方传播。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:用 Result<T, E> 返回可恢复错误,并用 ? 运算符向调用方传播。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:用 Result<T, E> 返回可恢复错误,并用 ? 运算符向调用方传播。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 Result 与 ? 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:用 Result<T, E> 返回可恢复错误,并用 ? 运算符向调用方传播?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:用 Result<T, E> 返回可恢复错误,并用 ? 运算符向调用方传播。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-ownership": {
"title": "所有权与借用",
"concept": "所有权与借用 的 Rust 语法目标是:追踪 String 何时移动、何时返回,以及引用何时只是借用它。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:追踪 String 何时移动、何时返回,以及引用何时只是借用它。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:追踪 String 何时移动、何时返回,以及引用何时只是借用它。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 所有权与借用 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:追踪 String 何时移动、何时返回,以及引用何时只是借用它?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:追踪 String 何时移动、何时返回,以及引用何时只是借用它。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-iterators": {
"title": "迭代器与闭包",
"concept": "迭代器与闭包 的 Rust 语法目标是:用闭包组合惰性 iterator 适配器,并用 sum 或 collect 消费它们。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:用闭包组合惰性 iterator 适配器,并用 sum 或 collect 消费它们。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:用闭包组合惰性 iterator 适配器,并用 sum 或 collect 消费它们。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 迭代器与闭包 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:用闭包组合惰性 iterator 适配器,并用 sum 或 collect 消费它们?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:用闭包组合惰性 iterator 适配器,并用 sum 或 collect 消费它们。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-generics": {
"title": "泛型",
"concept": "泛型 的 Rust 语法目标是:编写作用于 T 的函数,同时保留编译期的具体类型检查。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:编写作用于 T 的函数,同时保留编译期的具体类型检查。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:编写作用于 T 的函数,同时保留编译期的具体类型检查。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 泛型 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:编写作用于 T 的函数,同时保留编译期的具体类型检查?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:编写作用于 T 的函数,同时保留编译期的具体类型检查。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-traits": {
"title": "trait 与边界",
"concept": "trait 与边界 的 Rust 语法目标是:用 trait 描述所需行为,并用边界在泛型代码中调用该行为。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:用 trait 描述所需行为,并用边界在泛型代码中调用该行为。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:用 trait 描述所需行为,并用边界在泛型代码中调用该行为。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 trait 与边界 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:用 trait 描述所需行为,并用边界在泛型代码中调用该行为?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:用 trait 描述所需行为,并用边界在泛型代码中调用该行为。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-lifetimes": {
"title": "生命周期",
"concept": "生命周期 的 Rust 语法目标是:标注借用输入与借用输出之间的关系,而不是延长存储时间。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:标注借用输入与借用输出之间的关系,而不是延长存储时间。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:标注借用输入与借用输出之间的关系,而不是延长存储时间。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 生命周期 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:标注借用输入与借用输出之间的关系,而不是延长存储时间?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:标注借用输入与借用输出之间的关系,而不是延长存储时间。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-traits-lifetimes": {
"title": "trait 对象与 dyn 分发",
"concept": "trait 对象与 dyn 分发 的 Rust 语法目标是:当运行时分发的灵活性比单一泛型类型更重要时使用 &dyn Trait。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:当运行时分发的灵活性比单一泛型类型更重要时使用 &dyn Trait。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:当运行时分发的灵活性比单一泛型类型更重要时使用 &dyn Trait。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 trait 对象与 dyn 分发 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:当运行时分发的灵活性比单一泛型类型更重要时使用 &dyn Trait?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:当运行时分发的灵活性比单一泛型类型更重要时使用 &dyn Trait。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-testing": {
"title": "测试与断言",
"concept": "测试与断言 的 Rust 语法目标是:把纯逻辑从 main 分离出来,让 #[test] 函数和 assert 宏验证行为。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:把纯逻辑从 main 分离出来,让 #[test] 函数和 assert 宏验证行为。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:把纯逻辑从 main 分离出来,让 #[test] 函数和 assert 宏验证行为。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 测试与断言 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:把纯逻辑从 main 分离出来,让 #[test] 函数和 assert 宏验证行为?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:把纯逻辑从 main 分离出来,让 #[test] 函数和 assert 宏验证行为。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-smart-pointers": {
"title": "智能指针",
"concept": "智能指针 的 Rust 语法目标是:用 Box<T> 拥有堆上的数据,同时利用类似指针的解引用行为。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:用 Box<T> 拥有堆上的数据,同时利用类似指针的解引用行为。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:用 Box<T> 拥有堆上的数据,同时利用类似指针的解引用行为。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 智能指针 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:用 Box<T> 拥有堆上的数据,同时利用类似指针的解引用行为?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:用 Box<T> 拥有堆上的数据,同时利用类似指针的解引用行为。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-interior-mutability": {
"title": "内部可变性",
"concept": "内部可变性 的 Rust 语法目标是:当运行时借用检查是最简单且正确的模型时使用 RefCell<T>。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:当运行时借用检查是最简单且正确的模型时使用 RefCell<T>。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:当运行时借用检查是最简单且正确的模型时使用 RefCell<T>。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 内部可变性 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:当运行时借用检查是最简单且正确的模型时使用 RefCell<T>?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:当运行时借用检查是最简单且正确的模型时使用 RefCell<T>。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-concurrency": {
"title": "线程与 join",
"concept": "线程与 join 的 Rust 语法目标是:把工作移动到 thread::spawn 中,并用 join 安全取回 worker 结果。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:把工作移动到 thread::spawn 中,并用 join 安全取回 worker 结果。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:把工作移动到 thread::spawn 中,并用 join 安全取回 worker 结果。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 线程与 join 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:把工作移动到 thread::spawn 中,并用 join 安全取回 worker 结果?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:把工作移动到 thread::spawn 中,并用 join 安全取回 worker 结果。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-shared-state": {
"title": "Arc 与 Mutex 的共享状态",
"concept": "Arc 与 Mutex 的共享状态 的 Rust 语法目标是:用 Arc<T> 共享所有权,并用 Mutex<T> 保证一次只允许一个修改。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:用 Arc<T> 共享所有权,并用 Mutex<T> 保证一次只允许一个修改。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:用 Arc<T> 共享所有权,并用 Mutex<T> 保证一次只允许一个修改。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 Arc 与 Mutex 的共享状态 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:用 Arc<T> 共享所有权,并用 Mutex<T> 保证一次只允许一个修改?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:用 Arc<T> 共享所有权,并用 Mutex<T> 保证一次只允许一个修改。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-async-await": {
"title": "async 与 await",
"concept": "async 与 await 的 Rust 语法目标是:理解 async 会创建 Future,而运行它需要 executor 或 runtime。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:理解 async 会创建 Future,而运行它需要 executor 或 runtime。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:理解 async 会创建 Future,而运行它需要 executor 或 runtime。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 async 与 await 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:理解 async 会创建 Future,而运行它需要 executor 或 runtime?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:理解 async 会创建 Future,而运行它需要 executor 或 runtime。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-macros": {
"title": "macro_rules!",
"concept": "macro_rules! 的 Rust 语法目标是:用 macro_rules! 匹配 token 模式,并在编译期展开重复的 Rust 代码。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:用 macro_rules! 匹配 token 模式,并在编译期展开重复的 Rust 代码。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:用 macro_rules! 匹配 token 模式,并在编译期展开重复的 Rust 代码。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 macro_rules! 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:用 macro_rules! 匹配 token 模式,并在编译期展开重复的 Rust 代码?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:用 macro_rules! 匹配 token 模式,并在编译期展开重复的 Rust 代码。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-unsafe": {
"title": "unsafe Rust",
"concept": "unsafe Rust 的 Rust 语法目标是:把编译器无法验证的操作放入 unsafe 块,同时让周围 API 尽量保持安全。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:把编译器无法验证的操作放入 unsafe 块,同时让周围 API 尽量保持安全。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:把编译器无法验证的操作放入 unsafe 块,同时让周围 API 尽量保持安全。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 unsafe Rust 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:把编译器无法验证的操作放入 unsafe 块,同时让周围 API 尽量保持安全?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:把编译器无法验证的操作放入 unsafe 块,同时让周围 API 尽量保持安全。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
},
"rust-cargo-workspaces": {
"title": "Cargo 包与工作空间",
"concept": "Cargo 包与工作空间 的 Rust 语法目标是:理解何时需要超出单文件的 Cargo 包结构与 cargo check --workspace。阅读示例时,依次追踪值在哪里创建、在哪里移动或借用,以及哪个表达式最终到达 stdout。",
"worked_example": "这个示例在可编译的 Rust 程序中展示目标:理解何时需要超出单文件的 Cargo 包结构与 cargo check --workspace。先读签名或 item 定义,再跟随值进入 println!,让语法和行为绑定。",
"common_mistakes": [
"没有练习这个目标,只把最终输出字符串硬编码出来:理解何时需要超出单文件的 Cargo 包结构与 cargo check --workspace。",
"为了消除编译错误,把本课要练习的 Rust 构造直接删掉。",
"没有检查 Cargo 包与工作空间 中涉及的类型、所有权、生命周期、路径或线程边界。"
],
"self_check": [
"示例中的哪个表达式或 item 展示了这个目标:理解何时需要超出单文件的 Cargo 包结构与 cargo check --workspace?",
"stdout 打印前,哪些类型、借用、生命周期、模块路径或线程边界必须有效?",
"如果输入值变化,哪一行应该变化,哪一行应该保持稳定?"
],
"exercise_prompt": "请围绕这个目标完成练习代码:理解何时需要超出单文件的 Cargo 包结构与 cargo check --workspace。保留本课需要的 Rust 构造,不要直接打印期望输出绕过练习。"
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://practicode.local/schemas/lesson-catalog.schema.json",
"title": "Practicode Lesson Catalog",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"programming_language",
"ui_language",
"lessons"
],
"properties": {
"schema_version": {
"const": 1
},
"programming_language": {
"type": "string",
"minLength": 1
},
"ui_language": {
"type": "string",
"enum": [
"en",
"ko",
"ja",
"zh",
"es"
]
},
"lessons": {
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/lessonCopy"
}
}
},
"$defs": {
"lessonCopy": {
"type": "object",
"additionalProperties": false,
"required": [
"title",
"concept",
"worked_example",
"common_mistakes",
"self_check",
"exercise_prompt"
],
"properties": {
"title": {
"type": "string",
"minLength": 1
},
"concept": {
"type": "string",
"minLength": 45
},
"worked_example": {
"type": "string",
"minLength": 45
},
"common_mistakes": {
"type": "array",
"minItems": 2,
"items": {
"type": "string",
"minLength": 12
}
},
"self_check": {
"type": "array",
"minItems": 2,
"items": {
"type": "string",
"minLength": 12
}
},
"exercise_prompt": {
"type": "string",
"minLength": 45
}
}
}
}
}
{
"schema_version": 1,
"programming_language": "ts",
"ui_language": "en",
"lessons": {
"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 keeps the relevant value in a named binding, applies the TypeScript construct, and prints the result through the same path the judge will inspect.",
"common_mistakes": [
"In Console and stdout, printing the expected text while skipping the rule that console output is the judge-facing contract, so the lesson separates console.log's newline from byte-exact process.stdout.write output.",
"Using any or a forced cast where Console and stdout should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Console and stdout, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Console and stdout stop before output?"
],
"exercise_prompt": "Complete the TODO for Console and stdout. Keep this value rule in the code path: Console output is the judge-facing contract, so the lesson separates console.log's newline from byte-exact process.stdout.write output. Make the expected output without replacing it with a literal print."
},
"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": "In the example, the important value is constrained before it is printed, so a wrong key, branch, or return type would be caught during type checking instead of hidden in output.",
"common_mistakes": [
"In let and const, printing the expected text while skipping the rule that const marks bindings that should not be reassigned, while let makes the changing accumulator in a solution visible.",
"Using any or a forced cast where let and const should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates let and const, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in let and const stop before output?"
],
"exercise_prompt": "Complete the TODO for let and const. Keep this value rule in the code path: const marks bindings that should not be reassigned, while let makes the changing accumulator in a solution visible. Make the expected output without replacing it with a literal print."
},
"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 example is intentionally a complete Node-runnable .ts file: the type-level part disappears at runtime, while the JavaScript value still proves the construct was used correctly.",
"common_mistakes": [
"In Primitive types, printing the expected text while skipping the rule that string, number, and boolean annotations describe the scalar values that parsing and branching code expects.",
"Using any or a forced cast where Primitive types should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Primitive types, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Primitive types stop before output?"
],
"exercise_prompt": "Complete the TODO for Primitive types. Keep this value rule in the code path: string, number, and boolean annotations describe the scalar values that parsing and branching code expects. Make the expected output without replacing it with a literal print."
},
"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 keeps the relevant value in a named binding, applies the TypeScript construct, and prints the result through the same path the judge will inspect.",
"common_mistakes": [
"In Strings and templates, printing the expected text while skipping the rule that template literals keep formatting next to the values, and string methods such as trim return a new string.",
"Using any or a forced cast where Strings and templates should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Strings and templates, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Strings and templates stop before output?"
],
"exercise_prompt": "Complete the TODO for Strings and templates. Keep this value rule in the code path: Template literals keep formatting next to the values, and string methods such as trim return a new string. Make the expected output without replacing it with a literal print."
},
"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": "In the example, the important value is constrained before it is printed, so a wrong key, branch, or return type would be caught during type checking instead of hidden in output.",
"common_mistakes": [
"In Arrays and tuples, printing the expected text while skipping the rule that arrays are ordered groups of one element type; tuples fix position and type when a tiny record matters.",
"Using any or a forced cast where Arrays and tuples should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Arrays and tuples, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Arrays and tuples stop before output?"
],
"exercise_prompt": "Complete the TODO for Arrays and tuples. Keep this value rule in the code path: Arrays are ordered groups of one element type; tuples fix position and type when a tiny record matters. Make the expected output without replacing it with a literal print."
},
"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 example is intentionally a complete Node-runnable .ts file: the type-level part disappears at runtime, while the JavaScript value still proves the construct was used correctly.",
"common_mistakes": [
"In Object types, printing the expected text while skipping the rule that object types name the required fields so a calculation cannot accidentally ignore width, height, or another property.",
"Using any or a forced cast where Object types should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Object types, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Object types stop before output?"
],
"exercise_prompt": "Complete the TODO for Object types. Keep this value rule in the code path: Object types name the required fields so a calculation cannot accidentally ignore width, height, or another property. Make the expected output without replacing it with a literal print."
},
"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 keeps the relevant value in a named binding, applies the TypeScript construct, and prints the result through the same path the judge will inspect.",
"common_mistakes": [
"In Functions, printing the expected text while skipping the rule that function parameter and return annotations make the boundary between parsed input, calculation, and output explicit.",
"Using any or a forced cast where Functions should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Functions, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Functions stop before output?"
],
"exercise_prompt": "Complete the TODO for Functions. Keep this value rule in the code path: Function parameter and return annotations make the boundary between parsed input, calculation, and output explicit. Make the expected output without replacing it with a literal print."
},
"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": "In the example, the important value is constrained before it is printed, so a wrong key, branch, or return type would be caught during type checking instead of hidden in output.",
"common_mistakes": [
"In Node stdin parsing, printing the expected text while skipping the rule that node solutions usually read file descriptor 0 once, split whitespace, and convert tokens before numeric work.",
"Using any or a forced cast where Node stdin parsing should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Node stdin parsing, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Node stdin parsing stop before output?"
],
"exercise_prompt": "Complete the TODO for Node stdin parsing. Keep this value rule in the code path: Node solutions usually read file descriptor 0 once, split whitespace, and convert tokens before numeric work. Make the expected output without replacing it with a literal print."
},
"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 example is intentionally a complete Node-runnable .ts file: the type-level part disappears at runtime, while the JavaScript value still proves the construct was used correctly.",
"common_mistakes": [
"In Control flow, printing the expected text while skipping the rule that if and loops decide which values are accumulated, so branch conditions must match the intended data rule.",
"Using any or a forced cast where Control flow should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Control flow, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Control flow stop before output?"
],
"exercise_prompt": "Complete the TODO for Control flow. Keep this value rule in the code path: if and loops decide which values are accumulated, so branch conditions must match the intended data rule. Make the expected output without replacing it with a literal print."
},
"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 keeps the relevant value in a named binding, applies the TypeScript construct, and prints the result through the same path the judge will inspect.",
"common_mistakes": [
"In Union and narrowing, printing the expected text while skipping the rule that union values need a runtime check before string-only or number-only operations become safe.",
"Using any or a forced cast where Union and narrowing should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Union and narrowing, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Union and narrowing stop before output?"
],
"exercise_prompt": "Complete the TODO for Union and narrowing. Keep this value rule in the code path: Union values need a runtime check before string-only or number-only operations become safe. Make the expected output without replacing it with a literal print."
},
"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": "In the example, the important value is constrained before it is printed, so a wrong key, branch, or return type would be caught during type checking instead of hidden in output.",
"common_mistakes": [
"In Literal types, printing the expected text while skipping the rule that literal unions restrict commands and modes to exact values such as 'left' or 'right'.",
"Using any or a forced cast where Literal types should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Literal types, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Literal types stop before output?"
],
"exercise_prompt": "Complete the TODO for Literal types. Keep this value rule in the code path: Literal unions restrict commands and modes to exact values such as 'left' or 'right'. Make the expected output without replacing it with a literal print."
},
"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 example is intentionally a complete Node-runnable .ts file: the type-level part disappears at runtime, while the JavaScript value still proves the construct was used correctly.",
"common_mistakes": [
"In Optional and nullish, printing the expected text while skipping the rule that optional fields can be undefined, and ?? preserves valid falsey data such as score 0.",
"Using any or a forced cast where Optional and nullish should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Optional and nullish, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Optional and nullish stop before output?"
],
"exercise_prompt": "Complete the TODO for Optional and nullish. Keep this value rule in the code path: Optional fields can be undefined, and ?? preserves valid falsey data such as score 0. Make the expected output without replacing it with a literal print."
},
"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 keeps the relevant value in a named binding, applies the TypeScript construct, and prints the result through the same path the judge will inspect.",
"common_mistakes": [
"In Interfaces and type aliases, printing the expected text while skipping the rule that interfaces name object contracts, while type aliases also name unions, tuples, and composed type expressions.",
"Using any or a forced cast where Interfaces and type aliases should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Interfaces and type aliases, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Interfaces and type aliases stop before output?"
],
"exercise_prompt": "Complete the TODO for Interfaces and type aliases. Keep this value rule in the code path: Interfaces name object contracts, while type aliases also name unions, tuples, and composed type expressions. Make the expected output without replacing it with a literal print."
},
"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": "In the example, the important value is constrained before it is printed, so a wrong key, branch, or return type would be caught during type checking instead of hidden in output.",
"common_mistakes": [
"In Generics, printing the expected text while skipping the rule that generics keep the caller's element type when reusable code reads from an array or returns a value.",
"Using any or a forced cast where Generics should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Generics, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Generics stop before output?"
],
"exercise_prompt": "Complete the TODO for Generics. Keep this value rule in the code path: Generics keep the caller's element type when reusable code reads from an array or returns a value. Make the expected output without replacing it with a literal print."
},
"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 example is intentionally a complete Node-runnable .ts file: the type-level part disappears at runtime, while the JavaScript value still proves the construct was used correctly.",
"common_mistakes": [
"In keyof and typeof, printing the expected text while skipping the rule that typeof captures a value's static shape, and keyof turns that shape into the allowed key union.",
"Using any or a forced cast where keyof and typeof should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates keyof and typeof, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in keyof and typeof stop before output?"
],
"exercise_prompt": "Complete the TODO for keyof and typeof. Keep this value rule in the code path: typeof captures a value's static shape, and keyof turns that shape into the allowed key union. Make the expected output without replacing it with a literal print."
},
"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 keeps the relevant value in a named binding, applies the TypeScript construct, and prints the result through the same path the judge will inspect.",
"common_mistakes": [
"In Indexed access types, printing the expected text while skipping the rule that indexed access types reuse property and element types from an existing model instead of repeating them by hand.",
"Using any or a forced cast where Indexed access types should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Indexed access types, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Indexed access types stop before output?"
],
"exercise_prompt": "Complete the TODO for Indexed access types. Keep this value rule in the code path: Indexed access types reuse property and element types from an existing model instead of repeating them by hand. Make the expected output without replacing it with a literal print."
},
"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": "In the example, the important value is constrained before it is printed, so a wrong key, branch, or return type would be caught during type checking instead of hidden in output.",
"common_mistakes": [
"In Mapped types, printing the expected text while skipping the rule that mapped types transform every key in an object type, which keeps derived shapes aligned with the source.",
"Using any or a forced cast where Mapped types should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Mapped types, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Mapped types stop before output?"
],
"exercise_prompt": "Complete the TODO for Mapped types. Keep this value rule in the code path: Mapped types transform every key in an object type, which keeps derived shapes aligned with the source. Make the expected output without replacing it with a literal print."
},
"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 example is intentionally a complete Node-runnable .ts file: the type-level part disappears at runtime, while the JavaScript value still proves the construct was used correctly.",
"common_mistakes": [
"In Conditional types, printing the expected text while skipping the rule that conditional types choose a branch from a type relationship, and infer extracts the part that matched.",
"Using any or a forced cast where Conditional types should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Conditional types, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Conditional types stop before output?"
],
"exercise_prompt": "Complete the TODO for Conditional types. Keep this value rule in the code path: Conditional types choose a branch from a type relationship, and infer extracts the part that matched. Make the expected output without replacing it with a literal print."
},
"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 keeps the relevant value in a named binding, applies the TypeScript construct, and prints the result through the same path the judge will inspect.",
"common_mistakes": [
"In Utility types, printing the expected text while skipping the rule that utility types such as Pick and Partial express common object transformations without inventing a new helper type.",
"Using any or a forced cast where Utility types should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Utility types, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Utility types stop before output?"
],
"exercise_prompt": "Complete the TODO for Utility types. Keep this value rule in the code path: Utility types such as Pick and Partial express common object transformations without inventing a new helper type. Make the expected output without replacing it with a literal print."
},
"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": "In the example, the important value is constrained before it is printed, so a wrong key, branch, or return type would be caught during type checking instead of hidden in output.",
"common_mistakes": [
"In Discriminated unions, printing the expected text while skipping the rule that a shared literal tag lets switch narrow each variant so rectangle fields are used only on rectangles.",
"Using any or a forced cast where Discriminated unions should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Discriminated unions, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Discriminated unions stop before output?"
],
"exercise_prompt": "Complete the TODO for Discriminated unions. Keep this value rule in the code path: A shared literal tag lets switch narrow each variant so rectangle fields are used only on rectangles. Make the expected output without replacing it with a literal print."
},
"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 example is intentionally a complete Node-runnable .ts file: the type-level part disappears at runtime, while the JavaScript value still proves the construct was used correctly.",
"common_mistakes": [
"In Async and Promise, printing the expected text while skipping the rule that async functions return Promise values, and await is the point where the fulfilled number re-enters normal flow.",
"Using any or a forced cast where Async and Promise should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Async and Promise, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Async and Promise stop before output?"
],
"exercise_prompt": "Complete the TODO for Async and Promise. Keep this value rule in the code path: async functions return Promise values, and await is the point where the fulfilled number re-enters normal flow. Make the expected output without replacing it with a literal print."
},
"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 keeps the relevant value in a named binding, applies the TypeScript construct, and prints the result through the same path the judge will inspect.",
"common_mistakes": [
"In Error handling, printing the expected text while skipping the rule that catch receives an unknown failure, so code should narrow it before choosing a recovery value.",
"Using any or a forced cast where Error handling should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Error handling, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Error handling stop before output?"
],
"exercise_prompt": "Complete the TODO for Error handling. Keep this value rule in the code path: catch receives an unknown failure, so code should narrow it before choosing a recovery value. Make the expected output without replacing it with a literal print."
},
"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": "In the example, the important value is constrained before it is printed, so a wrong key, branch, or return type would be caught during type checking instead of hidden in output.",
"common_mistakes": [
"In Modules and exports, printing the expected text while skipping the rule that export marks the values a file offers to other files; Node still decides the module system by its own rules.",
"Using any or a forced cast where Modules and exports should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Modules and exports, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Modules and exports stop before output?"
],
"exercise_prompt": "Complete the TODO for Modules and exports. Keep this value rule in the code path: export marks the values a file offers to other files; Node still decides the module system by its own rules. Make the expected output without replacing it with a literal print."
},
"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 example is intentionally a complete Node-runnable .ts file: the type-level part disappears at runtime, while the JavaScript value still proves the construct was used correctly.",
"common_mistakes": [
"In Classes and access modifiers, printing the expected text while skipping the rule that classes attach methods to state, and private documents which state should stay behind the method boundary.",
"Using any or a forced cast where Classes and access modifiers should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Classes and access modifiers, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Classes and access modifiers stop before output?"
],
"exercise_prompt": "Complete the TODO for Classes and access modifiers. Keep this value rule in the code path: Classes attach methods to state, and private documents which state should stay behind the method boundary. Make the expected output without replacing it with a literal print."
},
"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 keeps the relevant value in a named binding, applies the TypeScript construct, and prints the result through the same path the judge will inspect.",
"common_mistakes": [
"In readonly, printing the expected text while skipping the rule that readonly lets callers read configuration-like data while preventing replacement or mutation through that type.",
"Using any or a forced cast where readonly should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates readonly, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in readonly stop before output?"
],
"exercise_prompt": "Complete the TODO for readonly. Keep this value rule in the code path: readonly lets callers read configuration-like data while preventing replacement or mutation through that type. Make the expected output without replacing it with a literal print."
},
"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": "In the example, the important value is constrained before it is printed, so a wrong key, branch, or return type would be caught during type checking instead of hidden in output.",
"common_mistakes": [
"In satisfies and as const, printing the expected text while skipping the rule that as const preserves route literals, while satisfies checks the wider Record contract without widening those literals.",
"Using any or a forced cast where satisfies and as const should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates satisfies and as const, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in satisfies and as const stop before output?"
],
"exercise_prompt": "Complete the TODO for satisfies and as const. Keep this value rule in the code path: as const preserves route literals, while satisfies checks the wider Record contract without widening those literals. Make the expected output without replacing it with a literal print."
},
"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 example is intentionally a complete Node-runnable .ts file: the type-level part disappears at runtime, while the JavaScript value still proves the construct was used correctly.",
"common_mistakes": [
"In Iterables, printing the expected text while skipping the rule that for...of consumes any iterable, so arrays, strings, and sets can share one collection loop shape.",
"Using any or a forced cast where Iterables should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates Iterables, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in Iterables stop before output?"
],
"exercise_prompt": "Complete the TODO for Iterables. Keep this value rule in the code path: for...of consumes any iterable, so arrays, strings, and sets can share one collection loop shape. Make the expected output without replacing it with a literal print."
},
"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 keeps the relevant value in a named binding, applies the TypeScript construct, and prints the result through the same path the judge will inspect.",
"common_mistakes": [
"In map, filter, and reduce, printing the expected text while skipping the rule that filter selects items, map transforms them, and reduce folds the sequence into the final answer.",
"Using any or a forced cast where map, filter, and reduce should make a bad key, branch, field, or return value impossible."
],
"self_check": [
"Which variable or expression demonstrates map, filter, and reduce, and what value reaches stdout after it?",
"What wrong input, key, branch, or field would the type rule in map, filter, and reduce stop before output?"
],
"exercise_prompt": "Complete the TODO for map, filter, and reduce. Keep this value rule in the code path: filter selects items, map transforms them, and reduce folds the sequence into the final answer. Make the expected output without replacing it with a literal print."
}
}
}
{
"schema_version": 1,
"programming_language": "ts",
"ui_language": "es",
"lessons": {
"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 código de ejemplo deja el valor relevante en un enlace con nombre, aplica el constructo de TypeScript y lo imprime por la ruta que revisa el juez.",
"common_mistakes": [
"En Consola y stdout, imprimir el texto esperado mientras se omite esta regla: 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 any o una conversión forzada donde Consola y stdout debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Consola y stdout, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Consola y stdout?"
],
"exercise_prompt": "Completa el TODO de Consola y stdout. Conserva esta regla de valores en la ruta del código: 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. Produce la salida esperada sin reemplazarla por un print literal."
},
"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": "En el ejemplo, el valor importante queda limitado antes de imprimirse, así una clave, rama o retorno incorrecto no queda escondido en la salida.",
"common_mistakes": [
"En let y const, imprimir el texto esperado mientras se omite esta regla: const marca enlaces que no se reasignan, mientras let deja visible el acumulador que cambia en la solución.",
"Usar any o una conversión forzada donde let y const debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra let y const, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de let y const?"
],
"exercise_prompt": "Completa el TODO de let y const. Conserva esta regla de valores en la ruta del código: const marca enlaces que no se reasignan, mientras let deja visible el acumulador que cambia en la solución. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 es un único archivo .ts ejecutable por Node: la parte de tipos desaparece en runtime y el valor JavaScript demuestra el uso correcto.",
"common_mistakes": [
"En Tipos primitivos, imprimir el texto esperado mientras se omite esta regla: Las anotaciones string, number y boolean describen los escalares que esperan el parseo y las ramas.",
"Usar any o una conversión forzada donde Tipos primitivos debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Tipos primitivos, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Tipos primitivos?"
],
"exercise_prompt": "Completa el TODO de Tipos primitivos. Conserva esta regla de valores en la ruta del código: Las anotaciones string, number y boolean describen los escalares que esperan el parseo y las ramas. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 código de ejemplo deja el valor relevante en un enlace con nombre, aplica el constructo de TypeScript y lo imprime por la ruta que revisa el juez.",
"common_mistakes": [
"En Cadenas y plantillas, imprimir el texto esperado mientras se omite esta regla: Las plantillas literales formatean junto al valor, y métodos como trim devuelven una cadena nueva.",
"Usar any o una conversión forzada donde Cadenas y plantillas debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Cadenas y plantillas, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Cadenas y plantillas?"
],
"exercise_prompt": "Completa el TODO de Cadenas y plantillas. Conserva esta regla de valores en la ruta del código: Las plantillas literales formatean junto al valor, y métodos como trim devuelven una cadena nueva. Produce la salida esperada sin reemplazarla por un print literal."
},
"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": "En el ejemplo, el valor importante queda limitado antes de imprimirse, así una clave, rama o retorno incorrecto no queda escondido en la salida.",
"common_mistakes": [
"En Arreglos y tuplas, imprimir el texto esperado mientras se omite esta regla: Los arreglos guardan valores ordenados de un tipo; las tuplas fijan posición y tipo en registros pequeños.",
"Usar any o una conversión forzada donde Arreglos y tuplas debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Arreglos y tuplas, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Arreglos y tuplas?"
],
"exercise_prompt": "Completa el TODO de Arreglos y tuplas. Conserva esta regla de valores en la ruta del código: Los arreglos guardan valores ordenados de un tipo; las tuplas fijan posición y tipo en registros pequeños. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 es un único archivo .ts ejecutable por Node: la parte de tipos desaparece en runtime y el valor JavaScript demuestra el uso correcto.",
"common_mistakes": [
"En Tipos de objeto, imprimir el texto esperado mientras se omite esta regla: Los tipos de objeto nombran campos requeridos para no omitir width, height u otra propiedad en el cálculo.",
"Usar any o una conversión forzada donde Tipos de objeto debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Tipos de objeto, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Tipos de objeto?"
],
"exercise_prompt": "Completa el TODO de Tipos de objeto. Conserva esta regla de valores en la ruta del código: Los tipos de objeto nombran campos requeridos para no omitir width, height u otra propiedad en el cálculo. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 código de ejemplo deja el valor relevante en un enlace con nombre, aplica el constructo de TypeScript y lo imprime por la ruta que revisa el juez.",
"common_mistakes": [
"En Funciones, imprimir el texto esperado mientras se omite esta regla: Las anotaciones de parámetros y retorno hacen explícito el límite entre entrada parseada, cálculo y salida.",
"Usar any o una conversión forzada donde Funciones debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Funciones, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Funciones?"
],
"exercise_prompt": "Completa el TODO de Funciones. Conserva esta regla de valores en la ruta del código: Las anotaciones de parámetros y retorno hacen explícito el límite entre entrada parseada, cálculo y salida. Produce la salida esperada sin reemplazarla por un print literal."
},
"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": "En el ejemplo, el valor importante queda limitado antes de imprimirse, así una clave, rama o retorno incorrecto no queda escondido en la salida.",
"common_mistakes": [
"En Parseo de stdin en Node, imprimir el texto esperado mientras se omite esta regla: Las soluciones en Node suelen leer una vez el descriptor 0, separar por espacios y convertir tokens antes de calcular.",
"Usar any o una conversión forzada donde Parseo de stdin en Node debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Parseo de stdin en Node, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Parseo de stdin en Node?"
],
"exercise_prompt": "Completa el TODO de Parseo de stdin en Node. Conserva esta regla de valores en la ruta del código: Las soluciones en Node suelen leer una vez el descriptor 0, separar por espacios y convertir tokens antes de calcular. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 es un único archivo .ts ejecutable por Node: la parte de tipos desaparece en runtime y el valor JavaScript demuestra el uso correcto.",
"common_mistakes": [
"En Flujo de control, imprimir el texto esperado mientras se omite esta regla: if y los bucles deciden qué valores se acumulan, así que la condición debe coincidir con la regla de datos.",
"Usar any o una conversión forzada donde Flujo de control debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Flujo de control, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Flujo de control?"
],
"exercise_prompt": "Completa el TODO de Flujo de control. Conserva esta regla de valores en la ruta del código: if y los bucles deciden qué valores se acumulan, así que la condición debe coincidir con la regla de datos. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 código de ejemplo deja el valor relevante en un enlace con nombre, aplica el constructo de TypeScript y lo imprime por la ruta que revisa el juez.",
"common_mistakes": [
"En Uniones y estrechamiento, imprimir el texto esperado mientras se omite esta regla: Los valores union necesitan una comprobación en runtime antes de usar operaciones solo de string o solo de number.",
"Usar any o una conversión forzada donde Uniones y estrechamiento debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Uniones y estrechamiento, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Uniones y estrechamiento?"
],
"exercise_prompt": "Completa el TODO de Uniones y estrechamiento. Conserva esta regla de valores en la ruta del código: Los valores union necesitan una comprobación en runtime antes de usar operaciones solo de string o solo de number. Produce la salida esperada sin reemplazarla por un print literal."
},
"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": "En el ejemplo, el valor importante queda limitado antes de imprimirse, así una clave, rama o retorno incorrecto no queda escondido en la salida.",
"common_mistakes": [
"En Tipos literales, imprimir el texto esperado mientras se omite esta regla: Las uniones literales limitan comandos y modos a valores exactos como 'left' o 'right'.",
"Usar any o una conversión forzada donde Tipos literales debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Tipos literales, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Tipos literales?"
],
"exercise_prompt": "Completa el TODO de Tipos literales. Conserva esta regla de valores en la ruta del código: Las uniones literales limitan comandos y modos a valores exactos como 'left' o 'right'. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 es un único archivo .ts ejecutable por Node: la parte de tipos desaparece en runtime y el valor JavaScript demuestra el uso correcto.",
"common_mistakes": [
"En Opcional y nullish, imprimir el texto esperado mientras se omite esta regla: Los campos opcionales pueden ser undefined, y ?? conserva datos falsy válidos como la puntuación 0.",
"Usar any o una conversión forzada donde Opcional y nullish debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Opcional y nullish, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Opcional y nullish?"
],
"exercise_prompt": "Completa el TODO de Opcional y nullish. Conserva esta regla de valores en la ruta del código: Los campos opcionales pueden ser undefined, y ?? conserva datos falsy válidos como la puntuación 0. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 código de ejemplo deja el valor relevante en un enlace con nombre, aplica el constructo de TypeScript y lo imprime por la ruta que revisa el juez.",
"common_mistakes": [
"En Interfaces y alias de tipo, imprimir el texto esperado mientras se omite esta regla: Las interfaces nombran contratos de objeto; los alias también nombran uniones, tuplas y expresiones compuestas.",
"Usar any o una conversión forzada donde Interfaces y alias de tipo debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Interfaces y alias de tipo, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Interfaces y alias de tipo?"
],
"exercise_prompt": "Completa el TODO de Interfaces y alias de tipo. Conserva esta regla de valores en la ruta del código: Las interfaces nombran contratos de objeto; los alias también nombran uniones, tuplas y expresiones compuestas. Produce la salida esperada sin reemplazarla por un print literal."
},
"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": "En el ejemplo, el valor importante queda limitado antes de imprimirse, así una clave, rama o retorno incorrecto no queda escondido en la salida.",
"common_mistakes": [
"En Genéricos, imprimir el texto esperado mientras se omite esta regla: Los genéricos conservan el tipo de elemento del llamador cuando el código reusable lee arreglos o devuelve valores.",
"Usar any o una conversión forzada donde Genéricos debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Genéricos, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Genéricos?"
],
"exercise_prompt": "Completa el TODO de Genéricos. Conserva esta regla de valores en la ruta del código: Los genéricos conservan el tipo de elemento del llamador cuando el código reusable lee arreglos o devuelve valores. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 es un único archivo .ts ejecutable por Node: la parte de tipos desaparece en runtime y el valor JavaScript demuestra el uso correcto.",
"common_mistakes": [
"En keyof y typeof, imprimir el texto esperado mientras se omite esta regla: typeof captura la forma estática de un valor, y keyof la convierte en la unión de claves permitidas.",
"Usar any o una conversión forzada donde keyof y typeof debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra keyof y typeof, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de keyof y typeof?"
],
"exercise_prompt": "Completa el TODO de keyof y typeof. Conserva esta regla de valores en la ruta del código: typeof captura la forma estática de un valor, y keyof la convierte en la unión de claves permitidas. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 código de ejemplo deja el valor relevante en un enlace con nombre, aplica el constructo de TypeScript y lo imprime por la ruta que revisa el juez.",
"common_mistakes": [
"En Tipos de acceso indexado, imprimir el texto esperado mientras se omite esta regla: Los tipos de acceso indexado reutilizan tipos de propiedad y elemento de un modelo existente sin repetirlos.",
"Usar any o una conversión forzada donde Tipos de acceso indexado debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Tipos de acceso indexado, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Tipos de acceso indexado?"
],
"exercise_prompt": "Completa el TODO de Tipos de acceso indexado. Conserva esta regla de valores en la ruta del código: Los tipos de acceso indexado reutilizan tipos de propiedad y elemento de un modelo existente sin repetirlos. Produce la salida esperada sin reemplazarla por un print literal."
},
"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": "En el ejemplo, el valor importante queda limitado antes de imprimirse, así una clave, rama o retorno incorrecto no queda escondido en la salida.",
"common_mistakes": [
"En Tipos mapeados, imprimir el texto esperado mientras se omite esta regla: Los tipos mapeados transforman cada clave de un objeto y mantienen las formas derivadas alineadas.",
"Usar any o una conversión forzada donde Tipos mapeados debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Tipos mapeados, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Tipos mapeados?"
],
"exercise_prompt": "Completa el TODO de Tipos mapeados. Conserva esta regla de valores en la ruta del código: Los tipos mapeados transforman cada clave de un objeto y mantienen las formas derivadas alineadas. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 es un único archivo .ts ejecutable por Node: la parte de tipos desaparece en runtime y el valor JavaScript demuestra el uso correcto.",
"common_mistakes": [
"En Tipos condicionales, imprimir el texto esperado mientras se omite esta regla: Los tipos condicionales eligen una rama según la relación de tipos, e infer extrae la parte coincidente.",
"Usar any o una conversión forzada donde Tipos condicionales debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Tipos condicionales, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Tipos condicionales?"
],
"exercise_prompt": "Completa el TODO de Tipos condicionales. Conserva esta regla de valores en la ruta del código: Los tipos condicionales eligen una rama según la relación de tipos, e infer extrae la parte coincidente. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 código de ejemplo deja el valor relevante en un enlace con nombre, aplica el constructo de TypeScript y lo imprime por la ruta que revisa el juez.",
"common_mistakes": [
"En Tipos utilitarios, imprimir el texto esperado mientras se omite esta regla: Tipos como Pick y Partial expresan transformaciones comunes de objetos sin crear otro tipo auxiliar.",
"Usar any o una conversión forzada donde Tipos utilitarios debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Tipos utilitarios, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Tipos utilitarios?"
],
"exercise_prompt": "Completa el TODO de Tipos utilitarios. Conserva esta regla de valores en la ruta del código: Tipos como Pick y Partial expresan transformaciones comunes de objetos sin crear otro tipo auxiliar. Produce la salida esperada sin reemplazarla por un print literal."
},
"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": "En el ejemplo, el valor importante queda limitado antes de imprimirse, así una clave, rama o retorno incorrecto no queda escondido en la salida.",
"common_mistakes": [
"En Uniones discriminadas, imprimir el texto esperado mientras se omite esta regla: Una etiqueta literal compartida permite que switch estreche cada variante y use campos de rectángulo solo allí.",
"Usar any o una conversión forzada donde Uniones discriminadas debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Uniones discriminadas, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Uniones discriminadas?"
],
"exercise_prompt": "Completa el TODO de Uniones discriminadas. Conserva esta regla de valores en la ruta del código: Una etiqueta literal compartida permite que switch estreche cada variante y use campos de rectángulo solo allí. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 es un único archivo .ts ejecutable por Node: la parte de tipos desaparece en runtime y el valor JavaScript demuestra el uso correcto.",
"common_mistakes": [
"En async y Promise, imprimir el texto esperado mientras se omite esta regla: Las funciones async devuelven Promise, y await es donde el número resuelto vuelve al flujo normal.",
"Usar any o una conversión forzada donde async y Promise debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra async y Promise, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de async y Promise?"
],
"exercise_prompt": "Completa el TODO de async y Promise. Conserva esta regla de valores en la ruta del código: Las funciones async devuelven Promise, y await es donde el número resuelto vuelve al flujo normal. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 código de ejemplo deja el valor relevante en un enlace con nombre, aplica el constructo de TypeScript y lo imprime por la ruta que revisa el juez.",
"common_mistakes": [
"En Manejo de errores, imprimir el texto esperado mientras se omite esta regla: catch recibe un fallo unknown, así que conviene estrecharlo antes de elegir un valor de recuperación.",
"Usar any o una conversión forzada donde Manejo de errores debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Manejo de errores, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Manejo de errores?"
],
"exercise_prompt": "Completa el TODO de Manejo de errores. Conserva esta regla de valores en la ruta del código: catch recibe un fallo unknown, así que conviene estrecharlo antes de elegir un valor de recuperación. Produce la salida esperada sin reemplazarla por un print literal."
},
"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": "En el ejemplo, el valor importante queda limitado antes de imprimirse, así una clave, rama o retorno incorrecto no queda escondido en la salida.",
"common_mistakes": [
"En Módulos y exports, imprimir el texto esperado mientras se omite esta regla: export marca los valores que un archivo ofrece a otros; Node sigue decidiendo el sistema de módulos.",
"Usar any o una conversión forzada donde Módulos y exports debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Módulos y exports, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Módulos y exports?"
],
"exercise_prompt": "Completa el TODO de Módulos y exports. Conserva esta regla de valores en la ruta del código: export marca los valores que un archivo ofrece a otros; Node sigue decidiendo el sistema de módulos. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 es un único archivo .ts ejecutable por Node: la parte de tipos desaparece en runtime y el valor JavaScript demuestra el uso correcto.",
"common_mistakes": [
"En Clases y modificadores, imprimir el texto esperado mientras se omite esta regla: Las clases unen métodos al estado, y private documenta qué estado debe quedar detrás del límite del método.",
"Usar any o una conversión forzada donde Clases y modificadores debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Clases y modificadores, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Clases y modificadores?"
],
"exercise_prompt": "Completa el TODO de Clases y modificadores. Conserva esta regla de valores en la ruta del código: Las clases unen métodos al estado, y private documenta qué estado debe quedar detrás del límite del método. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 código de ejemplo deja el valor relevante en un enlace con nombre, aplica el constructo de TypeScript y lo imprime por la ruta que revisa el juez.",
"common_mistakes": [
"En readonly, imprimir el texto esperado mientras se omite esta regla: readonly permite leer datos de configuración y evita reemplazarlos o mutarlos a través de ese tipo.",
"Usar any o una conversión forzada donde readonly debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra readonly, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de readonly?"
],
"exercise_prompt": "Completa el TODO de readonly. Conserva esta regla de valores en la ruta del código: readonly permite leer datos de configuración y evita reemplazarlos o mutarlos a través de ese tipo. Produce la salida esperada sin reemplazarla por un print literal."
},
"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": "En el ejemplo, el valor importante queda limitado antes de imprimirse, así una clave, rama o retorno incorrecto no queda escondido en la salida.",
"common_mistakes": [
"En satisfies y as const, imprimir el texto esperado mientras se omite esta regla: as const conserva literales de rutas, mientras satisfies comprueba el contrato Record sin ampliarlos.",
"Usar any o una conversión forzada donde satisfies y as const debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra satisfies y as const, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de satisfies y as const?"
],
"exercise_prompt": "Completa el TODO de satisfies y as const. Conserva esta regla de valores en la ruta del código: as const conserva literales de rutas, mientras satisfies comprueba el contrato Record sin ampliarlos. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 es un único archivo .ts ejecutable por Node: la parte de tipos desaparece en runtime y el valor JavaScript demuestra el uso correcto.",
"common_mistakes": [
"En Iterables, imprimir el texto esperado mientras se omite esta regla: for...of consume cualquier iterable, así que arreglos, cadenas y sets comparten la misma forma de bucle.",
"Usar any o una conversión forzada donde Iterables debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra Iterables, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de Iterables?"
],
"exercise_prompt": "Completa el TODO de Iterables. Conserva esta regla de valores en la ruta del código: for...of consume cualquier iterable, así que arreglos, cadenas y sets comparten la misma forma de bucle. Produce la salida esperada sin reemplazarla por un print literal."
},
"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 código de ejemplo deja el valor relevante en un enlace con nombre, aplica el constructo de TypeScript y lo imprime por la ruta que revisa el juez.",
"common_mistakes": [
"En map, filter y reduce, imprimir el texto esperado mientras se omite esta regla: filter selecciona elementos, map los transforma y reduce pliega la secuencia hasta la respuesta final.",
"Usar any o una conversión forzada donde map, filter y reduce debería impedir una clave, rama, campo o retorno incorrecto."
],
"self_check": [
"¿Qué variable o expresión demuestra map, filter y reduce, y qué valor llega después a stdout?",
"¿Qué entrada, clave, rama o campo incorrecto debería detener la regla de tipos de map, filter y reduce?"
],
"exercise_prompt": "Completa el TODO de map, filter y reduce. Conserva esta regla de valores en la ruta del código: filter selecciona elementos, map los transforma y reduce pliega la secuencia hasta la respuesta final. Produce la salida esperada sin reemplazarla por un print literal."
}
}
}
{
"schema_version": 1,
"programming_language": "ts",
"ui_language": "ja",
"lessons": {
"ts-output": {
"title": "コンソールと stdout",
"concept": "コンソール出力はジャッジが比較する契約なので、console.log の改行と process.stdout.write の厳密な出力を分けて考える。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。",
"worked_example": "例のコードは関係する値を名前付きの束縛に置き、TypeScript 構文を適用してからジャッジが見る同じ出力経路へ流す。",
"common_mistakes": [
"コンソールと stdout で次の規則を飛ばし、期待出力だけを合わせること: コンソール出力はジャッジが比較する契約なので、console.log の改行と process.stdout.write の厳密な出力を分けて考える。",
"コンソールと stdout が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"コンソールと stdout を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、コンソールと stdout の型規則はどこで止めるべきか。"
],
"exercise_prompt": "コンソールと stdout の TODO を完成させる。コード経路にこの規則を残すこと: コンソール出力はジャッジが比較する契約なので、console.log の改行と process.stdout.write の厳密な出力を分けて考える。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-let-const": {
"title": "let と const",
"concept": "const は再代入しない束縛を示し、let は解法の中で変化する累積値を明確にする。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。",
"worked_example": "この例では重要な値が出力前に制限されるため、誤ったキー、分岐、戻り値型が出力の裏に隠れない。",
"common_mistakes": [
"let と const で次の規則を飛ばし、期待出力だけを合わせること: const は再代入しない束縛を示し、let は解法の中で変化する累積値を明確にする。",
"let と const が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"let と const を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、let と const の型規則はどこで止めるべきか。"
],
"exercise_prompt": "let と const の TODO を完成させる。コード経路にこの規則を残すこと: const は再代入しない束縛を示し、let は解法の中で変化する累積値を明確にする。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-primitives": {
"title": "プリミティブ型",
"concept": "string、number、boolean の注釈は、解析や分岐が期待するスカラー値を表す。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。",
"worked_example": "例は Node でそのまま実行できる単一の .ts ファイルだ。型レベルの部分は実行時に消え、JavaScript の値が構文の使い方を示す。",
"common_mistakes": [
"プリミティブ型 で次の規則を飛ばし、期待出力だけを合わせること: string、number、boolean の注釈は、解析や分岐が期待するスカラー値を表す。",
"プリミティブ型 が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"プリミティブ型 を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、プリミティブ型 の型規則はどこで止めるべきか。"
],
"exercise_prompt": "プリミティブ型 の TODO を完成させる。コード経路にこの規則を残すこと: string、number、boolean の注釈は、解析や分岐が期待するスカラー値を表す。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-strings-templates": {
"title": "文字列とテンプレート",
"concept": "テンプレートリテラルは値の近くで整形し、trim などの文字列メソッドは新しい文字列を返す。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。",
"worked_example": "例のコードは関係する値を名前付きの束縛に置き、TypeScript 構文を適用してからジャッジが見る同じ出力経路へ流す。",
"common_mistakes": [
"文字列とテンプレート で次の規則を飛ばし、期待出力だけを合わせること: テンプレートリテラルは値の近くで整形し、trim などの文字列メソッドは新しい文字列を返す。",
"文字列とテンプレート が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"文字列とテンプレート を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、文字列とテンプレート の型規則はどこで止めるべきか。"
],
"exercise_prompt": "文字列とテンプレート の TODO を完成させる。コード経路にこの規則を残すこと: テンプレートリテラルは値の近くで整形し、trim などの文字列メソッドは新しい文字列を返す。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-arrays-tuples": {
"title": "配列とタプル",
"concept": "配列は同じ要素型を順序付きで持ち、タプルは小さなレコードの位置と型を固定する。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。",
"worked_example": "この例では重要な値が出力前に制限されるため、誤ったキー、分岐、戻り値型が出力の裏に隠れない。",
"common_mistakes": [
"配列とタプル で次の規則を飛ばし、期待出力だけを合わせること: 配列は同じ要素型を順序付きで持ち、タプルは小さなレコードの位置と型を固定する。",
"配列とタプル が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"配列とタプル を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、配列とタプル の型規則はどこで止めるべきか。"
],
"exercise_prompt": "配列とタプル の TODO を完成させる。コード経路にこの規則を残すこと: 配列は同じ要素型を順序付きで持ち、タプルは小さなレコードの位置と型を固定する。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-objects": {
"title": "オブジェクト型",
"concept": "オブジェクト型は必要なフィールドを名前で固定し、width や height などの取り落としを防ぐ。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。",
"worked_example": "例は Node でそのまま実行できる単一の .ts ファイルだ。型レベルの部分は実行時に消え、JavaScript の値が構文の使い方を示す。",
"common_mistakes": [
"オブジェクト型 で次の規則を飛ばし、期待出力だけを合わせること: オブジェクト型は必要なフィールドを名前で固定し、width や height などの取り落としを防ぐ。",
"オブジェクト型 が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"オブジェクト型 を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、オブジェクト型 の型規則はどこで止めるべきか。"
],
"exercise_prompt": "オブジェクト型 の TODO を完成させる。コード経路にこの規則を残すこと: オブジェクト型は必要なフィールドを名前で固定し、width や height などの取り落としを防ぐ。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-functions": {
"title": "関数",
"concept": "関数の引数型と戻り値型は、解析済み入力、計算、出力の境界を明確にする。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。",
"worked_example": "例のコードは関係する値を名前付きの束縛に置き、TypeScript 構文を適用してからジャッジが見る同じ出力経路へ流す。",
"common_mistakes": [
"関数 で次の規則を飛ばし、期待出力だけを合わせること: 関数の引数型と戻り値型は、解析済み入力、計算、出力の境界を明確にする。",
"関数 が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"関数 を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、関数 の型規則はどこで止めるべきか。"
],
"exercise_prompt": "関数 の TODO を完成させる。コード経路にこの規則を残すこと: 関数の引数型と戻り値型は、解析済み入力、計算、出力の境界を明確にする。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-input": {
"title": "Node stdin の解析",
"concept": "Node の解法では通常、ファイル記述子 0 を一度読み、空白で分割してから数値へ変換する。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。",
"worked_example": "この例では重要な値が出力前に制限されるため、誤ったキー、分岐、戻り値型が出力の裏に隠れない。",
"common_mistakes": [
"Node stdin の解析 で次の規則を飛ばし、期待出力だけを合わせること: Node の解法では通常、ファイル記述子 0 を一度読み、空白で分割してから数値へ変換する。",
"Node stdin の解析 が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"Node stdin の解析 を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、Node stdin の解析 の型規則はどこで止めるべきか。"
],
"exercise_prompt": "Node stdin の解析 の TODO を完成させる。コード経路にこの規則を残すこと: Node の解法では通常、ファイル記述子 0 を一度読み、空白で分割してから数値へ変換する。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-control-flow": {
"title": "制御フロー",
"concept": "if とループはどの値を蓄積するかを決めるため、条件は意図したデータ規則と一致する必要がある。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。",
"worked_example": "例は Node でそのまま実行できる単一の .ts ファイルだ。型レベルの部分は実行時に消え、JavaScript の値が構文の使い方を示す。",
"common_mistakes": [
"制御フロー で次の規則を飛ばし、期待出力だけを合わせること: if とループはどの値を蓄積するかを決めるため、条件は意図したデータ規則と一致する必要がある。",
"制御フロー が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"制御フロー を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、制御フロー の型規則はどこで止めるべきか。"
],
"exercise_prompt": "制御フロー の TODO を完成させる。コード経路にこの規則を残すこと: if とループはどの値を蓄積するかを決めるため、条件は意図したデータ規則と一致する必要がある。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-union-narrowing": {
"title": "ユニオンと絞り込み",
"concept": "ユニオン値は、文字列専用や数値専用の操作の前に実行時チェックで絞り込む必要がある。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。",
"worked_example": "例のコードは関係する値を名前付きの束縛に置き、TypeScript 構文を適用してからジャッジが見る同じ出力経路へ流す。",
"common_mistakes": [
"ユニオンと絞り込み で次の規則を飛ばし、期待出力だけを合わせること: ユニオン値は、文字列専用や数値専用の操作の前に実行時チェックで絞り込む必要がある。",
"ユニオンと絞り込み が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"ユニオンと絞り込み を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、ユニオンと絞り込み の型規則はどこで止めるべきか。"
],
"exercise_prompt": "ユニオンと絞り込み の TODO を完成させる。コード経路にこの規則を残すこと: ユニオン値は、文字列専用や数値専用の操作の前に実行時チェックで絞り込む必要がある。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-literal-types": {
"title": "リテラル型",
"concept": "リテラルユニオンは、コマンドやモードを 'left' や 'right' のような正確な値に制限する。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。",
"worked_example": "この例では重要な値が出力前に制限されるため、誤ったキー、分岐、戻り値型が出力の裏に隠れない。",
"common_mistakes": [
"リテラル型 で次の規則を飛ばし、期待出力だけを合わせること: リテラルユニオンは、コマンドやモードを 'left' や 'right' のような正確な値に制限する。",
"リテラル型 が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"リテラル型 を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、リテラル型 の型規則はどこで止めるべきか。"
],
"exercise_prompt": "リテラル型 の TODO を完成させる。コード経路にこの規則を残すこと: リテラルユニオンは、コマンドやモードを 'left' や 'right' のような正確な値に制限する。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-optional-nullish": {
"title": "オプショナルと nullish",
"concept": "オプショナルフィールドは undefined になり得て、?? は点数 0 のような有効な falsy 値を保つ。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。",
"worked_example": "例は Node でそのまま実行できる単一の .ts ファイルだ。型レベルの部分は実行時に消え、JavaScript の値が構文の使い方を示す。",
"common_mistakes": [
"オプショナルと nullish で次の規則を飛ばし、期待出力だけを合わせること: オプショナルフィールドは undefined になり得て、?? は点数 0 のような有効な falsy 値を保つ。",
"オプショナルと nullish が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"オプショナルと nullish を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、オプショナルと nullish の型規則はどこで止めるべきか。"
],
"exercise_prompt": "オプショナルと nullish の TODO を完成させる。コード経路にこの規則を残すこと: オプショナルフィールドは undefined になり得て、?? は点数 0 のような有効な falsy 値を保つ。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-interfaces-aliases": {
"title": "インターフェイスと型エイリアス",
"concept": "インターフェイスはオブジェクト契約に名前を付け、型エイリアスはユニオンやタプルにも名前を付けられる。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。",
"worked_example": "例のコードは関係する値を名前付きの束縛に置き、TypeScript 構文を適用してからジャッジが見る同じ出力経路へ流す。",
"common_mistakes": [
"インターフェイスと型エイリアス で次の規則を飛ばし、期待出力だけを合わせること: インターフェイスはオブジェクト契約に名前を付け、型エイリアスはユニオンやタプルにも名前を付けられる。",
"インターフェイスと型エイリアス が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"インターフェイスと型エイリアス を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、インターフェイスと型エイリアス の型規則はどこで止めるべきか。"
],
"exercise_prompt": "インターフェイスと型エイリアス の TODO を完成させる。コード経路にこの規則を残すこと: インターフェイスはオブジェクト契約に名前を付け、型エイリアスはユニオンやタプルにも名前を付けられる。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-generics": {
"title": "ジェネリクス",
"concept": "ジェネリクスは、再利用コードが配列から読み取る時や値を返す時に呼び出し側の要素型を保つ。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。",
"worked_example": "この例では重要な値が出力前に制限されるため、誤ったキー、分岐、戻り値型が出力の裏に隠れない。",
"common_mistakes": [
"ジェネリクス で次の規則を飛ばし、期待出力だけを合わせること: ジェネリクスは、再利用コードが配列から読み取る時や値を返す時に呼び出し側の要素型を保つ。",
"ジェネリクス が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"ジェネリクス を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、ジェネリクス の型規則はどこで止めるべきか。"
],
"exercise_prompt": "ジェネリクス の TODO を完成させる。コード経路にこの規則を残すこと: ジェネリクスは、再利用コードが配列から読み取る時や値を返す時に呼び出し側の要素型を保つ。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-keyof-typeof": {
"title": "keyof と typeof",
"concept": "typeof は値の静的な形を取り出し、keyof はその形を許可されたキーのユニオンに変える。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。",
"worked_example": "例は Node でそのまま実行できる単一の .ts ファイルだ。型レベルの部分は実行時に消え、JavaScript の値が構文の使い方を示す。",
"common_mistakes": [
"keyof と typeof で次の規則を飛ばし、期待出力だけを合わせること: typeof は値の静的な形を取り出し、keyof はその形を許可されたキーのユニオンに変える。",
"keyof と typeof が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"keyof と typeof を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、keyof と typeof の型規則はどこで止めるべきか。"
],
"exercise_prompt": "keyof と typeof の TODO を完成させる。コード経路にこの規則を残すこと: typeof は値の静的な形を取り出し、keyof はその形を許可されたキーのユニオンに変える。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-indexed-access": {
"title": "インデックスアクセス型",
"concept": "インデックスアクセス型は、既存モデルのプロパティ型や要素型を手書きで重複させず再利用する。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。",
"worked_example": "例のコードは関係する値を名前付きの束縛に置き、TypeScript 構文を適用してからジャッジが見る同じ出力経路へ流す。",
"common_mistakes": [
"インデックスアクセス型 で次の規則を飛ばし、期待出力だけを合わせること: インデックスアクセス型は、既存モデルのプロパティ型や要素型を手書きで重複させず再利用する。",
"インデックスアクセス型 が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"インデックスアクセス型 を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、インデックスアクセス型 の型規則はどこで止めるべきか。"
],
"exercise_prompt": "インデックスアクセス型 の TODO を完成させる。コード経路にこの規則を残すこと: インデックスアクセス型は、既存モデルのプロパティ型や要素型を手書きで重複させず再利用する。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-mapped-types": {
"title": "マップ型",
"concept": "マップ型はオブジェクト型のすべてのキーを変換し、派生形を元の型と同期させる。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。",
"worked_example": "この例では重要な値が出力前に制限されるため、誤ったキー、分岐、戻り値型が出力の裏に隠れない。",
"common_mistakes": [
"マップ型 で次の規則を飛ばし、期待出力だけを合わせること: マップ型はオブジェクト型のすべてのキーを変換し、派生形を元の型と同期させる。",
"マップ型 が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"マップ型 を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、マップ型 の型規則はどこで止めるべきか。"
],
"exercise_prompt": "マップ型 の TODO を完成させる。コード経路にこの規則を残すこと: マップ型はオブジェクト型のすべてのキーを変換し、派生形を元の型と同期させる。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-conditional-types": {
"title": "条件型",
"concept": "条件型は型の関係から分岐を選び、infer は一致した部分を取り出す。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。",
"worked_example": "例は Node でそのまま実行できる単一の .ts ファイルだ。型レベルの部分は実行時に消え、JavaScript の値が構文の使い方を示す。",
"common_mistakes": [
"条件型 で次の規則を飛ばし、期待出力だけを合わせること: 条件型は型の関係から分岐を選び、infer は一致した部分を取り出す。",
"条件型 が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"条件型 を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、条件型 の型規則はどこで止めるべきか。"
],
"exercise_prompt": "条件型 の TODO を完成させる。コード経路にこの規則を残すこと: 条件型は型の関係から分岐を選び、infer は一致した部分を取り出す。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-utility-types": {
"title": "ユーティリティ型",
"concept": "Pick や Partial などのユーティリティ型は、新しい補助型を作らず一般的なオブジェクト変換を表す。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。",
"worked_example": "例のコードは関係する値を名前付きの束縛に置き、TypeScript 構文を適用してからジャッジが見る同じ出力経路へ流す。",
"common_mistakes": [
"ユーティリティ型 で次の規則を飛ばし、期待出力だけを合わせること: Pick や Partial などのユーティリティ型は、新しい補助型を作らず一般的なオブジェクト変換を表す。",
"ユーティリティ型 が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"ユーティリティ型 を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、ユーティリティ型 の型規則はどこで止めるべきか。"
],
"exercise_prompt": "ユーティリティ型 の TODO を完成させる。コード経路にこの規則を残すこと: Pick や Partial などのユーティリティ型は、新しい補助型を作らず一般的なオブジェクト変換を表す。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-discriminated-unions": {
"title": "判別可能ユニオン",
"concept": "共通のリテラルタグにより switch が各バリアントを絞り込み、長方形のフィールドを長方形だけで使える。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。",
"worked_example": "この例では重要な値が出力前に制限されるため、誤ったキー、分岐、戻り値型が出力の裏に隠れない。",
"common_mistakes": [
"判別可能ユニオン で次の規則を飛ばし、期待出力だけを合わせること: 共通のリテラルタグにより switch が各バリアントを絞り込み、長方形のフィールドを長方形だけで使える。",
"判別可能ユニオン が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"判別可能ユニオン を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、判別可能ユニオン の型規則はどこで止めるべきか。"
],
"exercise_prompt": "判別可能ユニオン の TODO を完成させる。コード経路にこの規則を残すこと: 共通のリテラルタグにより switch が各バリアントを絞り込み、長方形のフィールドを長方形だけで使える。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-async-promise": {
"title": "async と Promise",
"concept": "async 関数は Promise を返し、await の地点で解決済みの数値が通常の流れに戻る。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。",
"worked_example": "例は Node でそのまま実行できる単一の .ts ファイルだ。型レベルの部分は実行時に消え、JavaScript の値が構文の使い方を示す。",
"common_mistakes": [
"async と Promise で次の規則を飛ばし、期待出力だけを合わせること: async 関数は Promise を返し、await の地点で解決済みの数値が通常の流れに戻る。",
"async と Promise が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"async と Promise を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、async と Promise の型規則はどこで止めるべきか。"
],
"exercise_prompt": "async と Promise の TODO を完成させる。コード経路にこの規則を残すこと: async 関数は Promise を返し、await の地点で解決済みの数値が通常の流れに戻る。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-error-handling": {
"title": "エラー処理",
"concept": "catch は unknown の失敗値を受け取るため、回復値を選ぶ前に絞り込む必要がある。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。",
"worked_example": "例のコードは関係する値を名前付きの束縛に置き、TypeScript 構文を適用してからジャッジが見る同じ出力経路へ流す。",
"common_mistakes": [
"エラー処理 で次の規則を飛ばし、期待出力だけを合わせること: catch は unknown の失敗値を受け取るため、回復値を選ぶ前に絞り込む必要がある。",
"エラー処理 が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"エラー処理 を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、エラー処理 の型規則はどこで止めるべきか。"
],
"exercise_prompt": "エラー処理 の TODO を完成させる。コード経路にこの規則を残すこと: catch は unknown の失敗値を受け取るため、回復値を選ぶ前に絞り込む必要がある。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-modules": {
"title": "モジュールと export",
"concept": "export はファイルが他のファイルへ提供する値を示し、モジュール方式は Node の規則で決まる。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。",
"worked_example": "この例では重要な値が出力前に制限されるため、誤ったキー、分岐、戻り値型が出力の裏に隠れない。",
"common_mistakes": [
"モジュールと export で次の規則を飛ばし、期待出力だけを合わせること: export はファイルが他のファイルへ提供する値を示し、モジュール方式は Node の規則で決まる。",
"モジュールと export が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"モジュールと export を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、モジュールと export の型規則はどこで止めるべきか。"
],
"exercise_prompt": "モジュールと export の TODO を完成させる。コード経路にこの規則を残すこと: export はファイルが他のファイルへ提供する値を示し、モジュール方式は Node の規則で決まる。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-classes": {
"title": "クラスとアクセス修飾子",
"concept": "クラスは状態にメソッドを結び付け、private はメソッド境界の内側に留める状態を示す。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。",
"worked_example": "例は Node でそのまま実行できる単一の .ts ファイルだ。型レベルの部分は実行時に消え、JavaScript の値が構文の使い方を示す。",
"common_mistakes": [
"クラスとアクセス修飾子 で次の規則を飛ばし、期待出力だけを合わせること: クラスは状態にメソッドを結び付け、private はメソッド境界の内側に留める状態を示す。",
"クラスとアクセス修飾子 が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"クラスとアクセス修飾子 を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、クラスとアクセス修飾子 の型規則はどこで止めるべきか。"
],
"exercise_prompt": "クラスとアクセス修飾子 の TODO を完成させる。コード経路にこの規則を残すこと: クラスは状態にメソッドを結び付け、private はメソッド境界の内側に留める状態を示す。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-readonly": {
"title": "readonly",
"concept": "readonly は設定のようなデータを読めるようにしつつ、その型経由の置換や変更を防ぐ。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。",
"worked_example": "例のコードは関係する値を名前付きの束縛に置き、TypeScript 構文を適用してからジャッジが見る同じ出力経路へ流す。",
"common_mistakes": [
"readonly で次の規則を飛ばし、期待出力だけを合わせること: readonly は設定のようなデータを読めるようにしつつ、その型経由の置換や変更を防ぐ。",
"readonly が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"readonly を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、readonly の型規則はどこで止めるべきか。"
],
"exercise_prompt": "readonly の TODO を完成させる。コード経路にこの規則を残すこと: readonly は設定のようなデータを読めるようにしつつ、その型経由の置換や変更を防ぐ。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-satisfies-as-const": {
"title": "satisfies と as const",
"concept": "as const はルートのリテラルを保持し、satisfies はそれを広げずに Record 契約を検査する。 この構文は、実行時の値と TypeScript が意図する形を同じ流れに保つ。",
"worked_example": "この例では重要な値が出力前に制限されるため、誤ったキー、分岐、戻り値型が出力の裏に隠れない。",
"common_mistakes": [
"satisfies と as const で次の規則を飛ばし、期待出力だけを合わせること: as const はルートのリテラルを保持し、satisfies はそれを広げずに Record 契約を検査する。",
"satisfies と as const が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"satisfies と as const を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、satisfies と as const の型規則はどこで止めるべきか。"
],
"exercise_prompt": "satisfies と as const の TODO を完成させる。コード経路にこの規則を残すこと: as const はルートのリテラルを保持し、satisfies はそれを広げずに Record 契約を検査する。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-iterables": {
"title": "イテラブル",
"concept": "for...of は任意のイテラブルを消費するため、配列、文字列、Set が同じループ形を共有できる。 stdin/stdout の解法では、出力だけを合わせて型が守るべきデータの流れを失う失敗を防ぐ。",
"worked_example": "例は Node でそのまま実行できる単一の .ts ファイルだ。型レベルの部分は実行時に消え、JavaScript の値が構文の使い方を示す。",
"common_mistakes": [
"イテラブル で次の規則を飛ばし、期待出力だけを合わせること: for...of は任意のイテラブルを消費するため、配列、文字列、Set が同じループ形を共有できる。",
"イテラブル が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"イテラブル を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、イテラブル の型規則はどこで止めるべきか。"
],
"exercise_prompt": "イテラブル の TODO を完成させる。コード経路にこの規則を残すこと: for...of は任意のイテラブルを消費するため、配列、文字列、Set が同じループ形を共有できる。 リテラル出力に置き換えず期待出力を作る。"
},
"ts-array-methods": {
"title": "map、filter、reduce",
"concept": "filter は項目を選び、map は変換し、reduce は列を最終結果へ畳み込む。 ここでの型注釈は飾りではなく、この境界を通れる値の形を説明するものだ。",
"worked_example": "例のコードは関係する値を名前付きの束縛に置き、TypeScript 構文を適用してからジャッジが見る同じ出力経路へ流す。",
"common_mistakes": [
"map、filter、reduce で次の規則を飛ばし、期待出力だけを合わせること: filter は項目を選び、map は変換し、reduce は列を最終結果へ畳み込む。",
"map、filter、reduce が悪いキー、分岐、フィールド、戻り値を止めるべき場面で any や強制キャストに逃げること。"
],
"self_check": [
"map、filter、reduce を示す変数や式はどれで、その後どの値が stdout に届くか。",
"入力、キー、分岐、フィールドが間違った時、map、filter、reduce の型規則はどこで止めるべきか。"
],
"exercise_prompt": "map、filter、reduce の TODO を完成させる。コード経路にこの規則を残すこと: filter は項目を選び、map は変換し、reduce は列を最終結果へ畳み込む。 リテラル出力に置き換えず期待出力を作る。"
}
}
}
{
"schema_version": 1,
"programming_language": "ts",
"ui_language": "ko",
"lessons": {
"ts-output": {
"title": "콘솔과 stdout",
"concept": "콘솔 출력은 채점기가 비교하는 계약이므로 console.log의 자동 줄바꿈과 process.stdout.write의 정확한 바이트 출력을 구분한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.",
"worked_example": "예제 코드는 관련 값을 이름 있는 바인딩에 두고 TypeScript 문법을 적용한 뒤, 채점기가 보는 같은 출력 경로로 결과를 보낸다.",
"common_mistakes": [
"콘솔과 stdout에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 콘솔 출력은 채점기가 비교하는 계약이므로 console.log의 자동 줄바꿈과 process.stdout.write의 정확한 바이트 출력을 구분한다.",
"콘솔과 stdout가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"콘솔과 stdout를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 콘솔과 stdout의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "콘솔과 stdout의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 콘솔 출력은 채점기가 비교하는 계약이므로 console.log의 자동 줄바꿈과 process.stdout.write의 정확한 바이트 출력을 구분한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-let-const": {
"title": "let과 const",
"concept": "const는 재할당하지 않을 바인딩을 표시하고, let은 풀이 중 변하는 누적값을 분명하게 드러낸다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.",
"worked_example": "이 예제에서는 중요한 값이 출력되기 전에 제한되므로 잘못된 키, 분기, 반환 타입이 단순 출력 뒤에 숨지 않는다.",
"common_mistakes": [
"let과 const에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: const는 재할당하지 않을 바인딩을 표시하고, let은 풀이 중 변하는 누적값을 분명하게 드러낸다.",
"let과 const가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"let과 const를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 let과 const의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "let과 const의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: const는 재할당하지 않을 바인딩을 표시하고, let은 풀이 중 변하는 누적값을 분명하게 드러낸다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-primitives": {
"title": "기본 타입",
"concept": "string, number, boolean 주석은 파싱과 분기 코드가 기대하는 스칼라 값을 설명한다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.",
"worked_example": "예제는 Node에서 바로 실행되는 단일 .ts 파일이다. 타입 수준 부분은 런타임에서 사라지지만 JavaScript 값은 문법이 제대로 쓰였음을 보여 준다.",
"common_mistakes": [
"기본 타입에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: string, number, boolean 주석은 파싱과 분기 코드가 기대하는 스칼라 값을 설명한다.",
"기본 타입가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"기본 타입를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 기본 타입의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "기본 타입의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: string, number, boolean 주석은 파싱과 분기 코드가 기대하는 스칼라 값을 설명한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-strings-templates": {
"title": "문자열과 템플릿",
"concept": "템플릿 리터럴은 값을 바로 옆에서 포맷하고, trim 같은 문자열 메서드는 새 문자열을 돌려준다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.",
"worked_example": "예제 코드는 관련 값을 이름 있는 바인딩에 두고 TypeScript 문법을 적용한 뒤, 채점기가 보는 같은 출력 경로로 결과를 보낸다.",
"common_mistakes": [
"문자열과 템플릿에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 템플릿 리터럴은 값을 바로 옆에서 포맷하고, trim 같은 문자열 메서드는 새 문자열을 돌려준다.",
"문자열과 템플릿가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"문자열과 템플릿를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 문자열과 템플릿의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "문자열과 템플릿의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 템플릿 리터럴은 값을 바로 옆에서 포맷하고, trim 같은 문자열 메서드는 새 문자열을 돌려준다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-arrays-tuples": {
"title": "배열과 튜플",
"concept": "배열은 순서가 있는 값의 묶음이고, 튜플은 작은 레코드에서 위치와 타입을 함께 고정한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.",
"worked_example": "이 예제에서는 중요한 값이 출력되기 전에 제한되므로 잘못된 키, 분기, 반환 타입이 단순 출력 뒤에 숨지 않는다.",
"common_mistakes": [
"배열과 튜플에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 배열은 순서가 있는 값의 묶음이고, 튜플은 작은 레코드에서 위치와 타입을 함께 고정한다.",
"배열과 튜플가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"배열과 튜플를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 배열과 튜플의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "배열과 튜플의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 배열은 순서가 있는 값의 묶음이고, 튜플은 작은 레코드에서 위치와 타입을 함께 고정한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-objects": {
"title": "객체 타입",
"concept": "객체 타입은 필요한 필드를 이름으로 고정해 width, height 같은 속성을 계산에서 빠뜨리지 않게 한다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.",
"worked_example": "예제는 Node에서 바로 실행되는 단일 .ts 파일이다. 타입 수준 부분은 런타임에서 사라지지만 JavaScript 값은 문법이 제대로 쓰였음을 보여 준다.",
"common_mistakes": [
"객체 타입에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 객체 타입은 필요한 필드를 이름으로 고정해 width, height 같은 속성을 계산에서 빠뜨리지 않게 한다.",
"객체 타입가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"객체 타입를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 객체 타입의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "객체 타입의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 객체 타입은 필요한 필드를 이름으로 고정해 width, height 같은 속성을 계산에서 빠뜨리지 않게 한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-functions": {
"title": "함수",
"concept": "함수 매개변수와 반환 타입은 파싱된 입력, 계산, 출력 사이의 경계를 명확히 한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.",
"worked_example": "예제 코드는 관련 값을 이름 있는 바인딩에 두고 TypeScript 문법을 적용한 뒤, 채점기가 보는 같은 출력 경로로 결과를 보낸다.",
"common_mistakes": [
"함수에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 함수 매개변수와 반환 타입은 파싱된 입력, 계산, 출력 사이의 경계를 명확히 한다.",
"함수가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"함수를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 함수의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "함수의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 함수 매개변수와 반환 타입은 파싱된 입력, 계산, 출력 사이의 경계를 명확히 한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-input": {
"title": "Node stdin 파싱",
"concept": "Node 풀이는 보통 파일 디스크립터 0을 한 번 읽고 공백으로 나눈 뒤 숫자 계산 전에 토큰을 변환한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.",
"worked_example": "이 예제에서는 중요한 값이 출력되기 전에 제한되므로 잘못된 키, 분기, 반환 타입이 단순 출력 뒤에 숨지 않는다.",
"common_mistakes": [
"Node stdin 파싱에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: Node 풀이는 보통 파일 디스크립터 0을 한 번 읽고 공백으로 나눈 뒤 숫자 계산 전에 토큰을 변환한다.",
"Node stdin 파싱가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"Node stdin 파싱를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 Node stdin 파싱의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "Node stdin 파싱의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: Node 풀이는 보통 파일 디스크립터 0을 한 번 읽고 공백으로 나눈 뒤 숫자 계산 전에 토큰을 변환한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-control-flow": {
"title": "제어 흐름",
"concept": "if와 반복문은 어떤 값을 누적할지 결정하므로 분기 조건이 의도한 데이터 규칙과 맞아야 한다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.",
"worked_example": "예제는 Node에서 바로 실행되는 단일 .ts 파일이다. 타입 수준 부분은 런타임에서 사라지지만 JavaScript 값은 문법이 제대로 쓰였음을 보여 준다.",
"common_mistakes": [
"제어 흐름에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: if와 반복문은 어떤 값을 누적할지 결정하므로 분기 조건이 의도한 데이터 규칙과 맞아야 한다.",
"제어 흐름가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"제어 흐름를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 제어 흐름의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "제어 흐름의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: if와 반복문은 어떤 값을 누적할지 결정하므로 분기 조건이 의도한 데이터 규칙과 맞아야 한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-union-narrowing": {
"title": "유니온과 좁히기",
"concept": "유니온 값은 문자열 전용 또는 숫자 전용 연산을 하기 전에 런타임 검사를 통해 좁혀야 안전하다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.",
"worked_example": "예제 코드는 관련 값을 이름 있는 바인딩에 두고 TypeScript 문법을 적용한 뒤, 채점기가 보는 같은 출력 경로로 결과를 보낸다.",
"common_mistakes": [
"유니온과 좁히기에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 유니온 값은 문자열 전용 또는 숫자 전용 연산을 하기 전에 런타임 검사를 통해 좁혀야 안전하다.",
"유니온과 좁히기가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"유니온과 좁히기를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 유니온과 좁히기의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "유니온과 좁히기의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 유니온 값은 문자열 전용 또는 숫자 전용 연산을 하기 전에 런타임 검사를 통해 좁혀야 안전하다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-literal-types": {
"title": "리터럴 타입",
"concept": "리터럴 유니온은 명령이나 모드를 'left', 'right'처럼 정확한 값으로 제한한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.",
"worked_example": "이 예제에서는 중요한 값이 출력되기 전에 제한되므로 잘못된 키, 분기, 반환 타입이 단순 출력 뒤에 숨지 않는다.",
"common_mistakes": [
"리터럴 타입에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 리터럴 유니온은 명령이나 모드를 'left', 'right'처럼 정확한 값으로 제한한다.",
"리터럴 타입가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"리터럴 타입를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 리터럴 타입의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "리터럴 타입의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 리터럴 유니온은 명령이나 모드를 'left', 'right'처럼 정확한 값으로 제한한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-optional-nullish": {
"title": "옵셔널과 nullish",
"concept": "옵셔널 필드는 undefined일 수 있고, ??는 점수 0 같은 유효한 falsy 값을 보존한다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.",
"worked_example": "예제는 Node에서 바로 실행되는 단일 .ts 파일이다. 타입 수준 부분은 런타임에서 사라지지만 JavaScript 값은 문법이 제대로 쓰였음을 보여 준다.",
"common_mistakes": [
"옵셔널과 nullish에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 옵셔널 필드는 undefined일 수 있고, ??는 점수 0 같은 유효한 falsy 값을 보존한다.",
"옵셔널과 nullish가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"옵셔널과 nullish를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 옵셔널과 nullish의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "옵셔널과 nullish의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 옵셔널 필드는 undefined일 수 있고, ??는 점수 0 같은 유효한 falsy 값을 보존한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-interfaces-aliases": {
"title": "인터페이스와 타입 별칭",
"concept": "인터페이스는 객체 계약을 이름 붙이고, 타입 별칭은 유니온, 튜플, 조합된 타입 표현식까지 이름 붙인다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.",
"worked_example": "예제 코드는 관련 값을 이름 있는 바인딩에 두고 TypeScript 문법을 적용한 뒤, 채점기가 보는 같은 출력 경로로 결과를 보낸다.",
"common_mistakes": [
"인터페이스와 타입 별칭에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 인터페이스는 객체 계약을 이름 붙이고, 타입 별칭은 유니온, 튜플, 조합된 타입 표현식까지 이름 붙인다.",
"인터페이스와 타입 별칭가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"인터페이스와 타입 별칭를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 인터페이스와 타입 별칭의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "인터페이스와 타입 별칭의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 인터페이스는 객체 계약을 이름 붙이고, 타입 별칭은 유니온, 튜플, 조합된 타입 표현식까지 이름 붙인다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-generics": {
"title": "제네릭",
"concept": "제네릭은 재사용 코드가 배열에서 읽거나 값을 반환할 때 호출자의 요소 타입을 유지한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.",
"worked_example": "이 예제에서는 중요한 값이 출력되기 전에 제한되므로 잘못된 키, 분기, 반환 타입이 단순 출력 뒤에 숨지 않는다.",
"common_mistakes": [
"제네릭에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 제네릭은 재사용 코드가 배열에서 읽거나 값을 반환할 때 호출자의 요소 타입을 유지한다.",
"제네릭가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"제네릭를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 제네릭의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "제네릭의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 제네릭은 재사용 코드가 배열에서 읽거나 값을 반환할 때 호출자의 요소 타입을 유지한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-keyof-typeof": {
"title": "keyof와 typeof",
"concept": "typeof는 값의 정적 모양을 가져오고, keyof는 그 모양을 허용된 키 유니온으로 바꾼다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.",
"worked_example": "예제는 Node에서 바로 실행되는 단일 .ts 파일이다. 타입 수준 부분은 런타임에서 사라지지만 JavaScript 값은 문법이 제대로 쓰였음을 보여 준다.",
"common_mistakes": [
"keyof와 typeof에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: typeof는 값의 정적 모양을 가져오고, keyof는 그 모양을 허용된 키 유니온으로 바꾼다.",
"keyof와 typeof가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"keyof와 typeof를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 keyof와 typeof의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "keyof와 typeof의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: typeof는 값의 정적 모양을 가져오고, keyof는 그 모양을 허용된 키 유니온으로 바꾼다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-indexed-access": {
"title": "인덱스 접근 타입",
"concept": "인덱스 접근 타입은 기존 모델의 속성 타입과 요소 타입을 손으로 반복하지 않고 재사용한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.",
"worked_example": "예제 코드는 관련 값을 이름 있는 바인딩에 두고 TypeScript 문법을 적용한 뒤, 채점기가 보는 같은 출력 경로로 결과를 보낸다.",
"common_mistakes": [
"인덱스 접근 타입에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 인덱스 접근 타입은 기존 모델의 속성 타입과 요소 타입을 손으로 반복하지 않고 재사용한다.",
"인덱스 접근 타입가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"인덱스 접근 타입를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 인덱스 접근 타입의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "인덱스 접근 타입의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 인덱스 접근 타입은 기존 모델의 속성 타입과 요소 타입을 손으로 반복하지 않고 재사용한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-mapped-types": {
"title": "매핑된 타입",
"concept": "매핑된 타입은 객체 타입의 모든 키를 변환해 파생된 모양이 원본과 어긋나지 않게 한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.",
"worked_example": "이 예제에서는 중요한 값이 출력되기 전에 제한되므로 잘못된 키, 분기, 반환 타입이 단순 출력 뒤에 숨지 않는다.",
"common_mistakes": [
"매핑된 타입에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 매핑된 타입은 객체 타입의 모든 키를 변환해 파생된 모양이 원본과 어긋나지 않게 한다.",
"매핑된 타입가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"매핑된 타입를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 매핑된 타입의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "매핑된 타입의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 매핑된 타입은 객체 타입의 모든 키를 변환해 파생된 모양이 원본과 어긋나지 않게 한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-conditional-types": {
"title": "조건부 타입",
"concept": "조건부 타입은 타입 관계에 따라 분기를 고르고, infer는 매칭된 부분을 꺼낸다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.",
"worked_example": "예제는 Node에서 바로 실행되는 단일 .ts 파일이다. 타입 수준 부분은 런타임에서 사라지지만 JavaScript 값은 문법이 제대로 쓰였음을 보여 준다.",
"common_mistakes": [
"조건부 타입에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 조건부 타입은 타입 관계에 따라 분기를 고르고, infer는 매칭된 부분을 꺼낸다.",
"조건부 타입가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"조건부 타입를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 조건부 타입의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "조건부 타입의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 조건부 타입은 타입 관계에 따라 분기를 고르고, infer는 매칭된 부분을 꺼낸다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-utility-types": {
"title": "유틸리티 타입",
"concept": "Pick과 Partial 같은 유틸리티 타입은 새 헬퍼 타입을 만들지 않고 흔한 객체 변환을 표현한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.",
"worked_example": "예제 코드는 관련 값을 이름 있는 바인딩에 두고 TypeScript 문법을 적용한 뒤, 채점기가 보는 같은 출력 경로로 결과를 보낸다.",
"common_mistakes": [
"유틸리티 타입에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: Pick과 Partial 같은 유틸리티 타입은 새 헬퍼 타입을 만들지 않고 흔한 객체 변환을 표현한다.",
"유틸리티 타입가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"유틸리티 타입를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 유틸리티 타입의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "유틸리티 타입의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: Pick과 Partial 같은 유틸리티 타입은 새 헬퍼 타입을 만들지 않고 흔한 객체 변환을 표현한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-discriminated-unions": {
"title": "식별 가능한 유니온",
"concept": "공통 리터럴 태그는 switch가 각 변형을 좁히게 해서 직사각형 필드는 직사각형에서만 쓰이게 한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.",
"worked_example": "이 예제에서는 중요한 값이 출력되기 전에 제한되므로 잘못된 키, 분기, 반환 타입이 단순 출력 뒤에 숨지 않는다.",
"common_mistakes": [
"식별 가능한 유니온에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 공통 리터럴 태그는 switch가 각 변형을 좁히게 해서 직사각형 필드는 직사각형에서만 쓰이게 한다.",
"식별 가능한 유니온가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"식별 가능한 유니온를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 식별 가능한 유니온의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "식별 가능한 유니온의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 공통 리터럴 태그는 switch가 각 변형을 좁히게 해서 직사각형 필드는 직사각형에서만 쓰이게 한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-async-promise": {
"title": "async와 Promise",
"concept": "async 함수는 Promise 값을 반환하고, await 지점에서 완료된 숫자가 일반 흐름으로 다시 들어온다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.",
"worked_example": "예제는 Node에서 바로 실행되는 단일 .ts 파일이다. 타입 수준 부분은 런타임에서 사라지지만 JavaScript 값은 문법이 제대로 쓰였음을 보여 준다.",
"common_mistakes": [
"async와 Promise에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: async 함수는 Promise 값을 반환하고, await 지점에서 완료된 숫자가 일반 흐름으로 다시 들어온다.",
"async와 Promise가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"async와 Promise를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 async와 Promise의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "async와 Promise의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: async 함수는 Promise 값을 반환하고, await 지점에서 완료된 숫자가 일반 흐름으로 다시 들어온다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-error-handling": {
"title": "오류 처리",
"concept": "catch는 unknown 실패 값을 받으므로 복구 값을 고르기 전에 먼저 좁혀야 한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.",
"worked_example": "예제 코드는 관련 값을 이름 있는 바인딩에 두고 TypeScript 문법을 적용한 뒤, 채점기가 보는 같은 출력 경로로 결과를 보낸다.",
"common_mistakes": [
"오류 처리에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: catch는 unknown 실패 값을 받으므로 복구 값을 고르기 전에 먼저 좁혀야 한다.",
"오류 처리가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"오류 처리를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 오류 처리의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "오류 처리의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: catch는 unknown 실패 값을 받으므로 복구 값을 고르기 전에 먼저 좁혀야 한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-modules": {
"title": "모듈과 export",
"concept": "export는 파일이 다른 파일에 제공할 값을 표시하며, 모듈 방식은 여전히 Node의 규칙이 결정한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.",
"worked_example": "이 예제에서는 중요한 값이 출력되기 전에 제한되므로 잘못된 키, 분기, 반환 타입이 단순 출력 뒤에 숨지 않는다.",
"common_mistakes": [
"모듈과 export에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: export는 파일이 다른 파일에 제공할 값을 표시하며, 모듈 방식은 여전히 Node의 규칙이 결정한다.",
"모듈과 export가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"모듈과 export를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 모듈과 export의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "모듈과 export의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: export는 파일이 다른 파일에 제공할 값을 표시하며, 모듈 방식은 여전히 Node의 규칙이 결정한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-classes": {
"title": "클래스와 접근 제한자",
"concept": "클래스는 상태에 메서드를 붙이고, private은 메서드 경계 뒤에 머물러야 할 상태를 문서화한다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.",
"worked_example": "예제는 Node에서 바로 실행되는 단일 .ts 파일이다. 타입 수준 부분은 런타임에서 사라지지만 JavaScript 값은 문법이 제대로 쓰였음을 보여 준다.",
"common_mistakes": [
"클래스와 접근 제한자에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: 클래스는 상태에 메서드를 붙이고, private은 메서드 경계 뒤에 머물러야 할 상태를 문서화한다.",
"클래스와 접근 제한자가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"클래스와 접근 제한자를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 클래스와 접근 제한자의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "클래스와 접근 제한자의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: 클래스는 상태에 메서드를 붙이고, private은 메서드 경계 뒤에 머물러야 할 상태를 문서화한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-readonly": {
"title": "readonly",
"concept": "readonly는 설정 같은 데이터를 읽을 수 있게 하되 그 타입을 통해 교체하거나 변경하지 못하게 한다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.",
"worked_example": "예제 코드는 관련 값을 이름 있는 바인딩에 두고 TypeScript 문법을 적용한 뒤, 채점기가 보는 같은 출력 경로로 결과를 보낸다.",
"common_mistakes": [
"readonly에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: readonly는 설정 같은 데이터를 읽을 수 있게 하되 그 타입을 통해 교체하거나 변경하지 못하게 한다.",
"readonly가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"readonly를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 readonly의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "readonly의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: readonly는 설정 같은 데이터를 읽을 수 있게 하되 그 타입을 통해 교체하거나 변경하지 못하게 한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-satisfies-as-const": {
"title": "satisfies와 as const",
"concept": "as const는 라우트 리터럴을 보존하고, satisfies는 그 리터럴을 넓히지 않은 채 더 넓은 Record 계약을 검사한다. 이 문법은 런타임 값과 TypeScript가 의도한 모양이 함께 움직이도록 잡아 준다.",
"worked_example": "이 예제에서는 중요한 값이 출력되기 전에 제한되므로 잘못된 키, 분기, 반환 타입이 단순 출력 뒤에 숨지 않는다.",
"common_mistakes": [
"satisfies와 as const에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: as const는 라우트 리터럴을 보존하고, satisfies는 그 리터럴을 넓히지 않은 채 더 넓은 Record 계약을 검사한다.",
"satisfies와 as const가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"satisfies와 as const를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 satisfies와 as const의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "satisfies와 as const의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: as const는 라우트 리터럴을 보존하고, satisfies는 그 리터럴을 넓히지 않은 채 더 넓은 Record 계약을 검사한다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-iterables": {
"title": "이터러블",
"concept": "for...of는 모든 이터러블을 소비하므로 배열, 문자열, Set이 같은 반복문 모양을 공유할 수 있다. stdin/stdout 풀이에서는 출력값만 맞추고 타입이 보장해야 할 데이터 흐름을 놓치는 일을 막는다.",
"worked_example": "예제는 Node에서 바로 실행되는 단일 .ts 파일이다. 타입 수준 부분은 런타임에서 사라지지만 JavaScript 값은 문법이 제대로 쓰였음을 보여 준다.",
"common_mistakes": [
"이터러블에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: for...of는 모든 이터러블을 소비하므로 배열, 문자열, Set이 같은 반복문 모양을 공유할 수 있다.",
"이터러블가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"이터러블를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 이터러블의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "이터러블의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: for...of는 모든 이터러블을 소비하므로 배열, 문자열, Set이 같은 반복문 모양을 공유할 수 있다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
},
"ts-array-methods": {
"title": "map, filter, reduce",
"concept": "filter는 항목을 고르고, map은 변환하며, reduce는 시퀀스를 최종 답 하나로 접는다. 여기서 타입 주석은 장식이 아니라 이 경계를 통과할 수 있는 값의 모양을 설명한다.",
"worked_example": "예제 코드는 관련 값을 이름 있는 바인딩에 두고 TypeScript 문법을 적용한 뒤, 채점기가 보는 같은 출력 경로로 결과를 보낸다.",
"common_mistakes": [
"map, filter, reduce에서 다음 규칙을 건너뛰고 기대 출력만 맞추는 실수: filter는 항목을 고르고, map은 변환하며, reduce는 시퀀스를 최종 답 하나로 접는다.",
"map, filter, reduce가 잘못된 키, 분기, 필드, 반환값을 막아야 하는 자리에서 any나 강제 캐스트로 지나가는 실수."
],
"self_check": [
"map, filter, reduce를 보여 주는 변수나 표현식은 무엇이고, 그 뒤 어떤 값이 stdout에 도달하는가?",
"입력, 키, 분기, 필드가 잘못되면 map, filter, reduce의 타입 규칙 중 어디가 먼저 막아야 하는가?"
],
"exercise_prompt": "map, filter, reduce의 TODO를 완성하라. 코드 경로에 이 규칙을 남겨라: filter는 항목을 고르고, map은 변환하며, reduce는 시퀀스를 최종 답 하나로 접는다. 리터럴 출력으로 바꾸지 말고 기대 출력을 만들어라."
}
}
}
{
"schema_version": 1,
"programming_language": "ts",
"ui_language": "zh",
"lessons": {
"ts-output": {
"title": "控制台与 stdout",
"concept": "控制台输出是评测比较的契约,因此要区分 console.log 的自动换行和 process.stdout.write 的逐字节输出。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。",
"worked_example": "示例代码把相关值放进有名字的绑定,应用 TypeScript 构造,再通过评测会检查的同一路径输出结果。",
"common_mistakes": [
"在控制台与 stdout中跳过这条规则,只硬凑期望输出:控制台输出是评测比较的契约,因此要区分 console.log 的自动换行和 process.stdout.write 的逐字节输出。",
"本该由控制台与 stdout阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了控制台与 stdout,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,控制台与 stdout 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成控制台与 stdout中的 TODO。保留这条值规则:控制台输出是评测比较的契约,因此要区分 console.log 的自动换行和 process.stdout.write 的逐字节输出。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-let-const": {
"title": "let 与 const",
"concept": "const 标出不会重新赋值的绑定,let 则让解题过程中会变化的累加值更清楚。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。",
"worked_example": "在这个示例里,关键值会先被约束再输出,所以错误的键、分支或返回类型不会藏在输出后面,也不会绕过类型检查。",
"common_mistakes": [
"在let 与 const中跳过这条规则,只硬凑期望输出:const 标出不会重新赋值的绑定,let 则让解题过程中会变化的累加值更清楚。",
"本该由let 与 const阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了let 与 const,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,let 与 const 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成let 与 const中的 TODO。保留这条值规则:const 标出不会重新赋值的绑定,let 则让解题过程中会变化的累加值更清楚。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-primitives": {
"title": "基础类型",
"concept": "string、number 和 boolean 注解描述了解析与分支代码期望的标量值。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。",
"worked_example": "示例是 Node 可直接运行的单个 .ts 文件:类型层面的部分在运行时消失,JavaScript 值仍证明语法用对了。",
"common_mistakes": [
"在基础类型中跳过这条规则,只硬凑期望输出:string、number 和 boolean 注解描述了解析与分支代码期望的标量值。",
"本该由基础类型阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了基础类型,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,基础类型 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成基础类型中的 TODO。保留这条值规则:string、number 和 boolean 注解描述了解析与分支代码期望的标量值。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-strings-templates": {
"title": "字符串与模板",
"concept": "模板字面量把格式和数值放在一起,trim 等字符串方法会返回新的字符串。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。",
"worked_example": "示例代码把相关值放进有名字的绑定,应用 TypeScript 构造,再通过评测会检查的同一路径输出结果。",
"common_mistakes": [
"在字符串与模板中跳过这条规则,只硬凑期望输出:模板字面量把格式和数值放在一起,trim 等字符串方法会返回新的字符串。",
"本该由字符串与模板阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了字符串与模板,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,字符串与模板 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成字符串与模板中的 TODO。保留这条值规则:模板字面量把格式和数值放在一起,trim 等字符串方法会返回新的字符串。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-arrays-tuples": {
"title": "数组与元组",
"concept": "数组保存有顺序的同类值,元组在小记录中同时固定位置和类型。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。",
"worked_example": "在这个示例里,关键值会先被约束再输出,所以错误的键、分支或返回类型不会藏在输出后面,也不会绕过类型检查。",
"common_mistakes": [
"在数组与元组中跳过这条规则,只硬凑期望输出:数组保存有顺序的同类值,元组在小记录中同时固定位置和类型。",
"本该由数组与元组阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了数组与元组,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,数组与元组 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成数组与元组中的 TODO。保留这条值规则:数组保存有顺序的同类值,元组在小记录中同时固定位置和类型。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-objects": {
"title": "对象类型",
"concept": "对象类型把必需字段写清楚,避免计算时漏掉 width、height 等属性。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。",
"worked_example": "示例是 Node 可直接运行的单个 .ts 文件:类型层面的部分在运行时消失,JavaScript 值仍证明语法用对了。",
"common_mistakes": [
"在对象类型中跳过这条规则,只硬凑期望输出:对象类型把必需字段写清楚,避免计算时漏掉 width、height 等属性。",
"本该由对象类型阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了对象类型,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,对象类型 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成对象类型中的 TODO。保留这条值规则:对象类型把必需字段写清楚,避免计算时漏掉 width、height 等属性。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-functions": {
"title": "函数",
"concept": "函数参数和返回类型把已解析输入、计算与输出之间的边界写清楚。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。",
"worked_example": "示例代码把相关值放进有名字的绑定,应用 TypeScript 构造,再通过评测会检查的同一路径输出结果。",
"common_mistakes": [
"在函数中跳过这条规则,只硬凑期望输出:函数参数和返回类型把已解析输入、计算与输出之间的边界写清楚。",
"本该由函数阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了函数,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,函数 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成函数中的 TODO。保留这条值规则:函数参数和返回类型把已解析输入、计算与输出之间的边界写清楚。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-input": {
"title": "Node stdin 解析",
"concept": "Node 解法通常一次读取文件描述符 0,按空白切分,再在数值计算前转换 token。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。",
"worked_example": "在这个示例里,关键值会先被约束再输出,所以错误的键、分支或返回类型不会藏在输出后面,也不会绕过类型检查。",
"common_mistakes": [
"在Node stdin 解析中跳过这条规则,只硬凑期望输出:Node 解法通常一次读取文件描述符 0,按空白切分,再在数值计算前转换 token。",
"本该由Node stdin 解析阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了Node stdin 解析,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,Node stdin 解析 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成Node stdin 解析中的 TODO。保留这条值规则:Node 解法通常一次读取文件描述符 0,按空白切分,再在数值计算前转换 token。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-control-flow": {
"title": "控制流",
"concept": "if 和循环决定哪些值进入累加,因此分支条件必须符合题目的数据规则。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。",
"worked_example": "示例是 Node 可直接运行的单个 .ts 文件:类型层面的部分在运行时消失,JavaScript 值仍证明语法用对了。",
"common_mistakes": [
"在控制流中跳过这条规则,只硬凑期望输出:if 和循环决定哪些值进入累加,因此分支条件必须符合题目的数据规则。",
"本该由控制流阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了控制流,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,控制流 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成控制流中的 TODO。保留这条值规则:if 和循环决定哪些值进入累加,因此分支条件必须符合题目的数据规则。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-union-narrowing": {
"title": "联合与收窄",
"concept": "联合类型的值在使用仅属于字符串或数字的操作前,需要先通过运行时检查收窄。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。",
"worked_example": "示例代码把相关值放进有名字的绑定,应用 TypeScript 构造,再通过评测会检查的同一路径输出结果。",
"common_mistakes": [
"在联合与收窄中跳过这条规则,只硬凑期望输出:联合类型的值在使用仅属于字符串或数字的操作前,需要先通过运行时检查收窄。",
"本该由联合与收窄阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了联合与收窄,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,联合与收窄 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成联合与收窄中的 TODO。保留这条值规则:联合类型的值在使用仅属于字符串或数字的操作前,需要先通过运行时检查收窄。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-literal-types": {
"title": "字面量类型",
"concept": "字面量联合把命令或模式限制为 'left'、'right' 这样的精确值。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。",
"worked_example": "在这个示例里,关键值会先被约束再输出,所以错误的键、分支或返回类型不会藏在输出后面,也不会绕过类型检查。",
"common_mistakes": [
"在字面量类型中跳过这条规则,只硬凑期望输出:字面量联合把命令或模式限制为 'left'、'right' 这样的精确值。",
"本该由字面量类型阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了字面量类型,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,字面量类型 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成字面量类型中的 TODO。保留这条值规则:字面量联合把命令或模式限制为 'left'、'right' 这样的精确值。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-optional-nullish": {
"title": "可选与空值合并",
"concept": "可选字段可能是 undefined,而 ?? 会保留分数 0 这样的有效假值。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。",
"worked_example": "示例是 Node 可直接运行的单个 .ts 文件:类型层面的部分在运行时消失,JavaScript 值仍证明语法用对了。",
"common_mistakes": [
"在可选与空值合并中跳过这条规则,只硬凑期望输出:可选字段可能是 undefined,而 ?? 会保留分数 0 这样的有效假值。",
"本该由可选与空值合并阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了可选与空值合并,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,可选与空值合并 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成可选与空值合并中的 TODO。保留这条值规则:可选字段可能是 undefined,而 ?? 会保留分数 0 这样的有效假值。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-interfaces-aliases": {
"title": "接口与类型别名",
"concept": "接口为对象契约命名,类型别名还能为联合、元组和组合类型表达式命名。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。",
"worked_example": "示例代码把相关值放进有名字的绑定,应用 TypeScript 构造,再通过评测会检查的同一路径输出结果。",
"common_mistakes": [
"在接口与类型别名中跳过这条规则,只硬凑期望输出:接口为对象契约命名,类型别名还能为联合、元组和组合类型表达式命名。",
"本该由接口与类型别名阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了接口与类型别名,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,接口与类型别名 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成接口与类型别名中的 TODO。保留这条值规则:接口为对象契约命名,类型别名还能为联合、元组和组合类型表达式命名。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-generics": {
"title": "泛型",
"concept": "泛型让可复用代码在读取数组或返回值时保留调用方的元素类型。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。",
"worked_example": "在这个示例里,关键值会先被约束再输出,所以错误的键、分支或返回类型不会藏在输出后面,也不会绕过类型检查。",
"common_mistakes": [
"在泛型中跳过这条规则,只硬凑期望输出:泛型让可复用代码在读取数组或返回值时保留调用方的元素类型。",
"本该由泛型阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了泛型,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,泛型 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成泛型中的 TODO。保留这条值规则:泛型让可复用代码在读取数组或返回值时保留调用方的元素类型。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-keyof-typeof": {
"title": "keyof 与 typeof",
"concept": "typeof 捕获值的静态形状,keyof 把这个形状转换为允许的键联合。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。",
"worked_example": "示例是 Node 可直接运行的单个 .ts 文件:类型层面的部分在运行时消失,JavaScript 值仍证明语法用对了。",
"common_mistakes": [
"在keyof 与 typeof中跳过这条规则,只硬凑期望输出:typeof 捕获值的静态形状,keyof 把这个形状转换为允许的键联合。",
"本该由keyof 与 typeof阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了keyof 与 typeof,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,keyof 与 typeof 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成keyof 与 typeof中的 TODO。保留这条值规则:typeof 捕获值的静态形状,keyof 把这个形状转换为允许的键联合。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-indexed-access": {
"title": "索引访问类型",
"concept": "索引访问类型复用已有模型中的属性类型和元素类型,避免手写重复。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。",
"worked_example": "示例代码把相关值放进有名字的绑定,应用 TypeScript 构造,再通过评测会检查的同一路径输出结果。",
"common_mistakes": [
"在索引访问类型中跳过这条规则,只硬凑期望输出:索引访问类型复用已有模型中的属性类型和元素类型,避免手写重复。",
"本该由索引访问类型阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了索引访问类型,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,索引访问类型 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成索引访问类型中的 TODO。保留这条值规则:索引访问类型复用已有模型中的属性类型和元素类型,避免手写重复。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-mapped-types": {
"title": "映射类型",
"concept": "映射类型转换对象类型中的每个键,让派生形状与来源保持一致。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。",
"worked_example": "在这个示例里,关键值会先被约束再输出,所以错误的键、分支或返回类型不会藏在输出后面,也不会绕过类型检查。",
"common_mistakes": [
"在映射类型中跳过这条规则,只硬凑期望输出:映射类型转换对象类型中的每个键,让派生形状与来源保持一致。",
"本该由映射类型阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了映射类型,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,映射类型 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成映射类型中的 TODO。保留这条值规则:映射类型转换对象类型中的每个键,让派生形状与来源保持一致。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-conditional-types": {
"title": "条件类型",
"concept": "条件类型根据类型关系选择分支,infer 会抽出匹配到的部分。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。",
"worked_example": "示例是 Node 可直接运行的单个 .ts 文件:类型层面的部分在运行时消失,JavaScript 值仍证明语法用对了。",
"common_mistakes": [
"在条件类型中跳过这条规则,只硬凑期望输出:条件类型根据类型关系选择分支,infer 会抽出匹配到的部分。",
"本该由条件类型阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了条件类型,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,条件类型 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成条件类型中的 TODO。保留这条值规则:条件类型根据类型关系选择分支,infer 会抽出匹配到的部分。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-utility-types": {
"title": "工具类型",
"concept": "Pick 和 Partial 等工具类型无需自定义新辅助类型,就能表达常见对象转换。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。",
"worked_example": "示例代码把相关值放进有名字的绑定,应用 TypeScript 构造,再通过评测会检查的同一路径输出结果。",
"common_mistakes": [
"在工具类型中跳过这条规则,只硬凑期望输出:Pick 和 Partial 等工具类型无需自定义新辅助类型,就能表达常见对象转换。",
"本该由工具类型阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了工具类型,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,工具类型 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成工具类型中的 TODO。保留这条值规则:Pick 和 Partial 等工具类型无需自定义新辅助类型,就能表达常见对象转换。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-discriminated-unions": {
"title": "可辨识联合",
"concept": "共享的字面量标签让 switch 收窄每个变体,使矩形字段只在矩形上使用。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。",
"worked_example": "在这个示例里,关键值会先被约束再输出,所以错误的键、分支或返回类型不会藏在输出后面,也不会绕过类型检查。",
"common_mistakes": [
"在可辨识联合中跳过这条规则,只硬凑期望输出:共享的字面量标签让 switch 收窄每个变体,使矩形字段只在矩形上使用。",
"本该由可辨识联合阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了可辨识联合,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,可辨识联合 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成可辨识联合中的 TODO。保留这条值规则:共享的字面量标签让 switch 收窄每个变体,使矩形字段只在矩形上使用。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-async-promise": {
"title": "async 与 Promise",
"concept": "async 函数返回 Promise,await 是已完成数值重新进入普通流程的位置。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。",
"worked_example": "示例是 Node 可直接运行的单个 .ts 文件:类型层面的部分在运行时消失,JavaScript 值仍证明语法用对了。",
"common_mistakes": [
"在async 与 Promise中跳过这条规则,只硬凑期望输出:async 函数返回 Promise,await 是已完成数值重新进入普通流程的位置。",
"本该由async 与 Promise阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了async 与 Promise,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,async 与 Promise 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成async 与 Promise中的 TODO。保留这条值规则:async 函数返回 Promise,await 是已完成数值重新进入普通流程的位置。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-error-handling": {
"title": "错误处理",
"concept": "catch 接收 unknown 的失败值,因此在选择恢复值前应先收窄。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。",
"worked_example": "示例代码把相关值放进有名字的绑定,应用 TypeScript 构造,再通过评测会检查的同一路径输出结果。",
"common_mistakes": [
"在错误处理中跳过这条规则,只硬凑期望输出:catch 接收 unknown 的失败值,因此在选择恢复值前应先收窄。",
"本该由错误处理阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了错误处理,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,错误处理 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成错误处理中的 TODO。保留这条值规则:catch 接收 unknown 的失败值,因此在选择恢复值前应先收窄。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-modules": {
"title": "模块与 export",
"concept": "export 标记文件提供给其他文件的值;模块系统仍由 Node 的规则决定。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。",
"worked_example": "在这个示例里,关键值会先被约束再输出,所以错误的键、分支或返回类型不会藏在输出后面,也不会绕过类型检查。",
"common_mistakes": [
"在模块与 export中跳过这条规则,只硬凑期望输出:export 标记文件提供给其他文件的值;模块系统仍由 Node 的规则决定。",
"本该由模块与 export阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了模块与 export,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,模块与 export 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成模块与 export中的 TODO。保留这条值规则:export 标记文件提供给其他文件的值;模块系统仍由 Node 的规则决定。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-classes": {
"title": "类与访问修饰符",
"concept": "类把方法绑定到状态上,private 说明哪些状态应留在方法边界之后。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。",
"worked_example": "示例是 Node 可直接运行的单个 .ts 文件:类型层面的部分在运行时消失,JavaScript 值仍证明语法用对了。",
"common_mistakes": [
"在类与访问修饰符中跳过这条规则,只硬凑期望输出:类把方法绑定到状态上,private 说明哪些状态应留在方法边界之后。",
"本该由类与访问修饰符阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了类与访问修饰符,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,类与访问修饰符 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成类与访问修饰符中的 TODO。保留这条值规则:类把方法绑定到状态上,private 说明哪些状态应留在方法边界之后。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-readonly": {
"title": "readonly",
"concept": "readonly 允许读取配置类数据,同时防止通过该类型替换或修改它。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。",
"worked_example": "示例代码把相关值放进有名字的绑定,应用 TypeScript 构造,再通过评测会检查的同一路径输出结果。",
"common_mistakes": [
"在readonly中跳过这条规则,只硬凑期望输出:readonly 允许读取配置类数据,同时防止通过该类型替换或修改它。",
"本该由readonly阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了readonly,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,readonly 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成readonly中的 TODO。保留这条值规则:readonly 允许读取配置类数据,同时防止通过该类型替换或修改它。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-satisfies-as-const": {
"title": "satisfies 与 as const",
"concept": "as const 保留路由字面量,satisfies 在不扩大这些字面量的情况下检查更宽的 Record 契约。 这个语法让运行时的值和 TypeScript 期望的形状保持在同一条路径上。",
"worked_example": "在这个示例里,关键值会先被约束再输出,所以错误的键、分支或返回类型不会藏在输出后面,也不会绕过类型检查。",
"common_mistakes": [
"在satisfies 与 as const中跳过这条规则,只硬凑期望输出:as const 保留路由字面量,satisfies 在不扩大这些字面量的情况下检查更宽的 Record 契约。",
"本该由satisfies 与 as const阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了satisfies 与 as const,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,satisfies 与 as const 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成satisfies 与 as const中的 TODO。保留这条值规则:as const 保留路由字面量,satisfies 在不扩大这些字面量的情况下检查更宽的 Record 契约。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-iterables": {
"title": "可迭代对象",
"concept": "for...of 可消费任何可迭代对象,因此数组、字符串和 Set 能共享同一种循环形态。 在 stdin/stdout 解法中,它能避免只拼出输出而丢掉类型应保证的数据流。",
"worked_example": "示例是 Node 可直接运行的单个 .ts 文件:类型层面的部分在运行时消失,JavaScript 值仍证明语法用对了。",
"common_mistakes": [
"在可迭代对象中跳过这条规则,只硬凑期望输出:for...of 可消费任何可迭代对象,因此数组、字符串和 Set 能共享同一种循环形态。",
"本该由可迭代对象阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了可迭代对象,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,可迭代对象 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成可迭代对象中的 TODO。保留这条值规则:for...of 可消费任何可迭代对象,因此数组、字符串和 Set 能共享同一种循环形态。 不要改成字面量输出,要让原有值路径产生期望结果。"
},
"ts-array-methods": {
"title": "map、filter 与 reduce",
"concept": "filter 选择元素,map 转换元素,reduce 把序列折叠成最终答案。 这里的类型注解不是装饰,而是在说明哪些值可以穿过这个边界。",
"worked_example": "示例代码把相关值放进有名字的绑定,应用 TypeScript 构造,再通过评测会检查的同一路径输出结果。",
"common_mistakes": [
"在map、filter 与 reduce中跳过这条规则,只硬凑期望输出:filter 选择元素,map 转换元素,reduce 把序列折叠成最终答案。",
"本该由map、filter 与 reduce阻止错误键、分支、字段或返回值时,却用 any 或强制转换绕过去。"
],
"self_check": [
"哪个变量或表达式体现了map、filter 与 reduce,之后哪个值到达 stdout?",
"输入、键、分支或字段出错时,map、filter 与 reduce 的类型规则应先在哪里拦住它?"
],
"exercise_prompt": "完成map、filter 与 reduce中的 TODO。保留这条值规则:filter 选择元素,map 转换元素,reduce 把序列折叠成最终答案。 不要改成字面量输出,要让原有值路径产生期望结果。"
}
}
}
<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">&gt; 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">&gt; 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>
# Commands
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.
## Common
| Command | Action |
| --- | --- |
| `/home` | Return to the Learn syntax / 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 |
| `/back` | In practice, go back through problem history; in learn mode, open the previous lesson |
| `/doctor` | Check local runtimes and show install hints |
| `/help` | Show in-app help |
| `/exit` | Quit |
## Practice
| Command | Action |
| --- | --- |
| `/problems` | Browse problems with `up/down` or `j/k`, open with `Enter` |
| `/open <id>` | Open by number, id, or slug |
| `/answer` | Show the reference answer |
| `/generate <request>` | Ask AI to create a new local problem in the background |
`/next` is local-first: it opens unsolved local problems before asking AI. If no local problem remains, `/next` may generate one in the foreground and pause editing until generation finishes.
Examples:
```text
/generate a slightly harder string problem
/generate hashmap practice, easy
/generate sorting problem, no graph yet
```
## Learning
| Command | Action |
| --- | --- |
| `/learn` | Open syntax learning |
| `/run` | Validate the current exercise |
| `/ask <question>` | Ask about the current lesson, worked example, or exercise |
| `/next` | Open the next lesson |
| `/back` | Open the previous lesson |
## AI Help
| Command | Action |
| --- | --- |
| `/ask <question>` | In learn mode, ask about the current lesson; in practice mode, ask about the current problem and submission |
| `/hint` | Ask the selected AI for a concise hint |
| `/hint <request>` | Ask about the current problem and submission |
| `/provider codex` | Use Codex |
| `/provider claude` | Use Claude Code |
| `/model auto` | Use the provider default model |
| `/model <name>` | Use a specific provider model |
| `/effort auto` | Use the provider default effort |
| `/effort low`, `/effort medium`, `/effort high`, `/effort xhigh` | Set AI effort |
Claude also supports `/effort max`.
## Profile And Preferences
| Command | Action |
| --- | --- |
| `/profile` | Show your current user profile |
| `/difficulty auto` | Set gradual progression |
| `/difficulty easy`, `/difficulty medium`, `/difficulty hard` | Pin future problem difficulty |
| `/topics <list>` | Set preferred topics |
| `/avoid <list>` | Set topics to avoid |
| `/generate-languages <list|all>` | Limit generated answer languages |
| `/generate-ui <list|all>` | Limit generated problem text languages |
| `/note` | Edit problem-generation notes |
| `/notes` | Show saved problem-generation notes |
| `/language python`, `/language ts`, `/language java`, `/language rust` | Set code language |
| `/ui en`, `/ui ko`, `/ui ja`, `/ui zh`, `/ui es` | Set UI language |
| `/theme dark`, `/theme light` | Set theme |
| `/update` | Show update instructions |
Inside `/profile`, use `up/down` to move and `Space` or `Enter` to cycle common settings. Use slash commands for free-form lists such as `/topics arrays, strings`.
## Aliases
Older command names such as `/prev`, `/previous`, `/list`, `/giveup`, `/give`, `/lang`, `/settings`, and `/quit` still work.

Sorry, the diff of this file is too big to display

use super::*;
use crate::process::which;
use std::process::Command;
#[derive(Clone, Copy, Eq, PartialEq)]
enum DoctorStatus {
Ok,
Missing,
Update,
}
struct DoctorCheck {
status: DoctorStatus,
name: &'static str,
detail: String,
install: Option<&'static str>,
}
impl PracticodeApp {
pub(super) fn action_doctor(&mut self) {
let output = doctor_text(
&self.state.settings.ui_language,
&self.state.settings.language,
&self.state.settings.ai_provider,
);
self.write_text_output(&output);
}
}
fn doctor_text(lang: &str, current_language: &str, ai_provider: &str) -> String {
doctor_text_with(
lang,
current_language,
ai_provider,
|name| which(name).is_some(),
command_version,
)
}
fn doctor_text_with<F, V>(
lang: &str,
current_language: &str,
ai_provider: &str,
mut has_command: F,
mut command_version: V,
) -> String
where
F: FnMut(&str) -> bool,
V: FnMut(&str, &[&str]) -> Option<String>,
{
let mut lines = vec![
ui_text(lang, "doctor_title").to_string(),
String::new(),
format!(
"{}: {}",
ui_text(lang, "doctor_current_language"),
syntax_language_name(&normalize_language(current_language))
),
String::new(),
ui_text(lang, "doctor_runtime_checks").to_string(),
];
for check in runtime_checks(&mut has_command, &mut command_version) {
push_check(lang, &mut lines, check);
}
lines.push(String::new());
lines.push(ui_text(lang, "doctor_optional_ai").to_string());
push_check(lang, &mut lines, ai_check(ai_provider, &mut has_command));
lines.join("\n")
}
fn runtime_checks<F, V>(has_command: &mut F, command_version: &mut V) -> Vec<DoctorCheck>
where
F: FnMut(&str) -> bool,
V: FnMut(&str, &[&str]) -> Option<String>,
{
vec![
python_check(has_command),
node_check(has_command, command_version),
java_check(has_command),
rust_check(has_command),
]
}
fn python_check<F>(has_command: &mut F) -> DoctorCheck
where
F: FnMut(&str) -> bool,
{
let command = if has_command("python3") {
Some("python3")
} else if has_command("python") {
Some("python")
} else {
None
};
DoctorCheck {
status: if command.is_some() {
DoctorStatus::Ok
} else {
DoctorStatus::Missing
},
name: "Python",
detail: command.unwrap_or("python3 or python").to_string(),
install: command.is_none().then_some(PYTHON_INSTALL),
}
}
fn node_check<F, V>(has_command: &mut F, command_version: &mut V) -> DoctorCheck
where
F: FnMut(&str) -> bool,
V: FnMut(&str, &[&str]) -> Option<String>,
{
if !has_command("node") {
return DoctorCheck {
status: DoctorStatus::Missing,
name: "TypeScript",
detail: "node >= 22.6.0".to_string(),
install: Some(NODE_INSTALL),
};
}
let version = command_version("node", &["--version"]).unwrap_or_else(|| "unknown".to_string());
let ok = node_supports_strip_types(&version);
DoctorCheck {
status: if ok {
DoctorStatus::Ok
} else {
DoctorStatus::Update
},
name: "TypeScript",
detail: if ok {
format!("node {version}")
} else {
format!("Node.js >= 22.6.0 ({version})")
},
install: (!ok).then_some(NODE_INSTALL),
}
}
fn java_check<F>(has_command: &mut F) -> DoctorCheck
where
F: FnMut(&str) -> bool,
{
let has_javac = has_command("javac");
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",
};
DoctorCheck {
status: if has_javac && has_java {
DoctorStatus::Ok
} else {
DoctorStatus::Missing
},
name: "Java",
detail: missing.to_string(),
install: (!(has_javac && has_java)).then_some(JAVA_INSTALL),
}
}
fn rust_check<F>(has_command: &mut F) -> DoctorCheck
where
F: FnMut(&str) -> bool,
{
let ok = has_command("rustc");
DoctorCheck {
status: if ok {
DoctorStatus::Ok
} else {
DoctorStatus::Missing
},
name: "Rust",
detail: if ok { "rustc" } else { "missing rustc" }.to_string(),
install: (!ok).then_some(RUST_INSTALL),
}
}
fn ai_check<F>(ai_provider: &str, has_command: &mut F) -> DoctorCheck
where
F: FnMut(&str) -> bool,
{
let provider = normalize_ai_provider(ai_provider);
let command = if provider == "claude" {
"claude"
} else {
"codex"
};
let ok = has_command(command);
DoctorCheck {
status: if ok {
DoctorStatus::Ok
} else {
DoctorStatus::Missing
},
name: if provider == "claude" {
"Claude Code"
} else {
"Codex"
},
detail: if ok {
command.to_string()
} else {
format!("missing {command}")
},
install: (!ok).then_some(if provider == "claude" {
CLAUDE_INSTALL
} else {
CODEX_INSTALL
}),
}
}
fn push_check(lang: &str, lines: &mut Vec<String>, check: DoctorCheck) {
lines.push(format!(
"{} {}: {}",
status_label(lang, check.status),
check.name,
check.detail
));
if let Some(install) = check.install {
lines.push(format!(" {}:", ui_text(lang, "doctor_install")));
lines.extend(install.lines().map(|line| format!(" {line}")));
}
}
fn status_label(lang: &str, status: DoctorStatus) -> &'static str {
match status {
DoctorStatus::Ok => ui_text(lang, "doctor_ok"),
DoctorStatus::Missing => ui_text(lang, "doctor_missing"),
DoctorStatus::Update => ui_text(lang, "doctor_update"),
}
}
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())
}
fn node_supports_strip_types(version: &str) -> bool {
version_at_least(version.trim_start_matches('v'), 22, 6, 0)
}
fn version_at_least(version: &str, major: u64, minor: u64, patch: u64) -> bool {
let mut parts = version
.split(['.', '-'])
.take(3)
.map(|part| part.parse::<u64>().unwrap_or(0));
let found = (
parts.next().unwrap_or(0),
parts.next().unwrap_or(0),
parts.next().unwrap_or(0),
);
found >= (major, minor, patch)
}
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 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.";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn doctor_output_includes_install_help_for_missing_runtimes() {
let output = doctor_text_with("en", "python", "codex", |_| false, |_, _| None);
assert!(output.contains("Doctor"));
assert!(output.contains("Runtime checks"));
assert!(output.contains("MISSING Python"));
assert!(output.contains("Install"));
assert!(output.contains("brew install python"));
assert!(output.contains("brew install node"));
assert!(output.contains("winget install -e --id Python.Python.3.12"));
}
#[test]
fn doctor_marks_old_node_as_update_needed() {
let output = doctor_text_with(
"en",
"ts",
"codex",
|name| matches!(name, "node" | "codex"),
|name, _| (name == "node").then(|| "v22.5.0".to_string()),
);
assert!(output.contains("UPDATE TypeScript"));
assert!(output.contains("Node.js >= 22.6.0"));
}
}
+39
-3

@@ -14,2 +14,7 @@ {

"update": "update",
"home": "Home",
"home_preview": "Preview",
"home_learn_choice": "Learn syntax",
"home_practice_choice": "Practice coding tests",
"home_help": "Arrows choose | Enter/Space open | / commands",
"command_placeholder": "Type /, move with up/down, Enter runs",

@@ -22,2 +27,4 @@ "palette_hint": "up/down select | Enter run | Esc cancel",

"hint_code": "Esc then / command",
"hint_problem": "/run judge | /next problem | /home",
"hint_learn": "/run validate | /ask question | /next lesson | /back lesson | /home",
"hint_idle": "/ command | ? help",

@@ -33,9 +40,13 @@ "hint_busy": "Working | q quit",

"cmd_edit": "Return to the code editor",
"cmd_next": "Open the next problem",
"cmd_home": "Open home",
"cmd_doctor": "Check local runtimes",
"cmd_next": "Open the next item in this mode",
"cmd_generate": "Generate a new problem in the background",
"cmd_prev": "Open the previous problem",
"cmd_prev": "Open the previous item in this mode",
"cmd_list": "Browse problems",
"cmd_open": "Open by number, id, or slug",
"cmd_giveup": "Show the reference answer",
"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",

@@ -69,2 +80,10 @@ "cmd_profile": "Show user profile",

"update_check_failed": "Could not check for updates.",
"doctor_title": "Doctor",
"doctor_current_language": "Current language",
"doctor_runtime_checks": "Runtime checks",
"doctor_optional_ai": "Optional AI",
"doctor_ok": "OK",
"doctor_missing": "MISSING",
"doctor_update": "UPDATE",
"doctor_install": "Install",
"generating_next": "Generating next problem",

@@ -77,3 +96,20 @@ "already_busy": "Already busy.",

"run_pass_next": "Next: /next",
"run_fail_next": "Fix: press Esc or e to edit, then /run"
"run_fail_next": "Fix: press Esc or e to edit, then /run",
"syntax": "Syntax",
"syntax_language": "Language",
"syntax_level": "Level",
"syntax_progress": "Progress",
"syntax_complete": "complete",
"syntax_open": "open",
"syntax_concept": "Concept",
"syntax_worked_example": "Worked example",
"syntax_common_mistakes": "Common mistakes",
"syntax_self_check": "Self-check",
"syntax_exercise": "Exercise",
"syntax_exercise_prompt": "Before you run, predict the output. Then run the starter and edit it until the expected output matches.",
"syntax_references": "References",
"syntax_basic": "basic",
"syntax_intermediate": "intermediate",
"syntax_advanced": "advanced",
"exercise_result": "Exercise result"
}

@@ -14,2 +14,7 @@ {

"update": "actualizacion",
"home": "Inicio",
"home_preview": "Vista previa",
"home_learn_choice": "Aprender sintaxis",
"home_practice_choice": "Practicar pruebas de codigo",
"home_help": "Flechas elegir | Enter/Space abrir | / comandos",
"command_placeholder": "Escribe /, elige con arriba/abajo, Enter ejecuta",

@@ -22,2 +27,4 @@ "palette_hint": "arriba/abajo elegir | Enter ejecutar | Esc cancelar",

"hint_code": "Esc y luego / comando",
"hint_problem": "/run evaluar | /next problema | /home",
"hint_learn": "/run validar | /ask pregunta | /next lección | /back lección | /home",
"hint_idle": "/ comando | ? ayuda",

@@ -33,8 +40,11 @@ "hint_busy": "Trabajando | q salir",

"cmd_edit": "Volver al editor de codigo",
"cmd_next": "Abrir el siguiente problema",
"cmd_home": "Abrir inicio",
"cmd_doctor": "Comprobar runtimes locales",
"cmd_next": "Abrir el siguiente elemento de este modo",
"cmd_generate": "Generar un problema nuevo en segundo plano",
"cmd_prev": "Abrir el problema anterior",
"cmd_prev": "Abrir el elemento anterior de este modo",
"cmd_list": "Abrir la lista de problemas",
"cmd_open": "Abrir por numero, 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",

@@ -69,2 +79,10 @@ "cmd_ai": "Preguntar a AI sobre el problema y codigo actuales",

"update_check_failed": "No se pudo comprobar actualizaciones.",
"doctor_title": "Diagnostico",
"doctor_current_language": "Lenguaje actual",
"doctor_runtime_checks": "Comprobaciones de runtime",
"doctor_optional_ai": "AI opcional",
"doctor_ok": "OK",
"doctor_missing": "FALTA",
"doctor_update": "ACTUALIZAR",
"doctor_install": "Instalar",
"generating_next": "Generando el siguiente problema",

@@ -77,3 +95,21 @@ "already_busy": "Ya hay una tarea en curso.",

"run_pass_next": "Siguiente: /next",
"run_fail_next": "Corrige: pulsa Esc o e para editar, luego /run"
"run_fail_next": "Corrige: pulsa Esc o e para editar, luego /run",
"syntax": "Sintaxis",
"syntax_language": "Lenguaje",
"syntax_level": "Nivel",
"syntax_progress": "Progreso",
"syntax_complete": "completo",
"syntax_open": "pendiente",
"syntax_concept": "Concepto",
"syntax_worked_example": "Ejemplo resuelto",
"syntax_exercise": "Ejercicio",
"syntax_exercise_prompt": "Antes de ejecutar, predice la salida. Luego ejecuta el inicio y edítalo hasta que coincida.",
"syntax_references": "Referencias",
"syntax_basic": "básico",
"syntax_intermediate": "intermedio",
"syntax_advanced": "avanzado",
"exercise_result": "Resultado",
"cmd_ask": "Preguntar a la IA sobre la lección o el código actual",
"syntax_common_mistakes": "Errores frecuentes",
"syntax_self_check": "Autoevaluación"
}

@@ -14,2 +14,7 @@ {

"update": "update",
"home": "ホーム",
"home_preview": "プレビュー",
"home_learn_choice": "文法学習",
"home_practice_choice": "コーディングテスト練習",
"home_help": "矢印 選択 | Enter/Space 開く | / コマンド",
"command_placeholder": "/ を入力し上下で選択、Enter で実行",

@@ -22,2 +27,4 @@ "palette_hint": "上下 選択 | Enter 実行 | Esc キャンセル",

"hint_code": "Esc の後 / コマンド",
"hint_problem": "/run 判定 | /next 問題 | /home",
"hint_learn": "/run 検証 | /ask 質問 | /next レッスン | /back レッスン | /home",
"hint_idle": "/ コマンド | ? ヘルプ",

@@ -33,8 +40,11 @@ "hint_busy": "処理中 | q 終了",

"cmd_edit": "コードエディタに戻る",
"cmd_next": "次の問題を開く",
"cmd_home": "ホームを開く",
"cmd_doctor": "ローカルランタイムを確認",
"cmd_next": "現在のモードで次を開く",
"cmd_generate": "新しい問題をバックグラウンド生成",
"cmd_prev": "前の問題を開く",
"cmd_prev": "現在のモードで前を開く",
"cmd_list": "問題一覧を開く",
"cmd_open": "番号、id、slug で問題を開く",
"cmd_giveup": "解答を見る",
"cmd_learn": "文法学習を開く",
"cmd_hint": "現在の問題のヒントを依頼",

@@ -69,2 +79,10 @@ "cmd_ai": "現在の問題とコードについて AI に質問",

"update_check_failed": "更新を確認できませんでした。",
"doctor_title": "環境診断",
"doctor_current_language": "現在の言語",
"doctor_runtime_checks": "ランタイム確認",
"doctor_optional_ai": "任意の AI",
"doctor_ok": "OK",
"doctor_missing": "不足",
"doctor_update": "更新が必要",
"doctor_install": "インストール",
"generating_next": "次の問題を生成中",

@@ -77,3 +95,21 @@ "already_busy": "すでに処理中です。",

"run_pass_next": "次: /next",
"run_fail_next": "修正: Esc または e で編集し、/run"
"run_fail_next": "修正: Esc または e で編集し、/run",
"syntax": "文法",
"syntax_language": "言語",
"syntax_level": "レベル",
"syntax_progress": "進捗",
"syntax_complete": "完了",
"syntax_open": "未完了",
"syntax_concept": "要点",
"syntax_worked_example": "解説付き例",
"syntax_exercise": "演習",
"syntax_exercise_prompt": "実行する前に出力を予想してください。その後スターターを実行し、期待出力に合うまで編集してください。",
"syntax_references": "参考",
"syntax_basic": "基礎",
"syntax_intermediate": "中級",
"syntax_advanced": "上級",
"exercise_result": "検証結果",
"cmd_ask": "現在のレッスンやコードについてAIに質問",
"syntax_common_mistakes": "よくある間違い",
"syntax_self_check": "セルフチェック"
}

@@ -14,2 +14,7 @@ {

"update": "업데이트",
"home": "홈",
"home_preview": "미리보기",
"home_learn_choice": "문법 학습",
"home_practice_choice": "코딩테스트 연습",
"home_help": "방향키 선택 | Enter/Space 열기 | / 명령",
"command_placeholder": "/ 입력 후 위/아래로 선택, Enter 실행",

@@ -22,2 +27,4 @@ "palette_hint": "위/아래 선택 | Enter 실행 | Esc 취소",

"hint_code": "Esc 후 / 명령",
"hint_problem": "/run 채점 | /next 문제 | /home",
"hint_learn": "/run 검증 | /ask 질문 | /next 레슨 | /back 레슨 | /home",
"hint_idle": "/ 명령 | ? 도움말",

@@ -33,9 +40,13 @@ "hint_busy": "작업 중 | q 종료",

"cmd_edit": "코드 편집기로 돌아가기",
"cmd_next": "다음 문제 열기",
"cmd_home": "홈 열기",
"cmd_doctor": "로컬 런타임 확인",
"cmd_next": "현재 모드에서 다음 항목 열기",
"cmd_generate": "새 문제를 백그라운드에서 생성",
"cmd_prev": "이전 문제 열기",
"cmd_prev": "현재 모드에서 이전 항목 열기",
"cmd_list": "문제 목록 열기",
"cmd_open": "번호, id, slug로 문제 열기",
"cmd_giveup": "정답 보기",
"cmd_learn": "문법 학습 열기",
"cmd_hint": "현재 문제 힌트 요청",
"cmd_ask": "현재 레슨이나 코드에 대해 AI에게 질문",
"cmd_ai": "현재 문제와 코드에 대해 AI에게 질문",

@@ -69,2 +80,10 @@ "cmd_profile": "사용자 프로필 보기",

"update_check_failed": "업데이트를 확인할 수 없습니다.",
"doctor_title": "환경 진단",
"doctor_current_language": "현재 언어",
"doctor_runtime_checks": "런타임 확인",
"doctor_optional_ai": "선택 AI",
"doctor_ok": "정상",
"doctor_missing": "없음",
"doctor_update": "업데이트 필요",
"doctor_install": "설치",
"generating_next": "다음 문제 생성 중",

@@ -77,3 +96,20 @@ "already_busy": "이미 작업 중입니다.",

"run_pass_next": "다음: /next",
"run_fail_next": "수정: Esc 또는 e로 편집한 뒤 /run"
"run_fail_next": "수정: Esc 또는 e로 편집한 뒤 /run",
"syntax": "문법",
"syntax_language": "언어",
"syntax_level": "레벨",
"syntax_progress": "진도",
"syntax_complete": "완료",
"syntax_open": "진행 전",
"syntax_concept": "핵심 개념",
"syntax_worked_example": "풀이 예제",
"syntax_common_mistakes": "흔한 실수",
"syntax_self_check": "자가 점검",
"syntax_exercise": "실습",
"syntax_exercise_prompt": "실행하기 전에 출력을 먼저 예상해 보세요. 그런 다음 스타터 코드를 실행하고 기대 출력과 맞을 때까지 수정하세요.",
"syntax_references": "참고",
"syntax_basic": "기초",
"syntax_intermediate": "중급",
"syntax_advanced": "심화",
"exercise_result": "검증 결과"
}

@@ -14,2 +14,7 @@ {

"update": "更新",
"home": "主页",
"home_preview": "预览",
"home_learn_choice": "语法学习",
"home_practice_choice": "编程测试练习",
"home_help": "方向键 选择 | Enter/Space 打开 | / 命令",
"command_placeholder": "输入 / 后用上下键选择,Enter 执行",

@@ -22,2 +27,4 @@ "palette_hint": "上下 选择 | Enter 执行 | Esc 取消",

"hint_code": "Esc 后输入 / 命令",
"hint_problem": "/run 评测 | /next 题目 | /home",
"hint_learn": "/run 验证 | /ask 提问 | /next 课程 | /back 课程 | /home",
"hint_idle": "/ 命令 | ? 帮助",

@@ -33,8 +40,11 @@ "hint_busy": "处理中 | q 退出",

"cmd_edit": "回到代码编辑器",
"cmd_next": "打开下一题",
"cmd_home": "打开主页",
"cmd_doctor": "检查本地运行时",
"cmd_next": "打开当前模式的下一项",
"cmd_generate": "后台生成新题",
"cmd_prev": "打开上一题",
"cmd_prev": "打开当前模式的上一项",
"cmd_list": "打开题目列表",
"cmd_open": "按编号、id 或 slug 打开题目",
"cmd_giveup": "显示参考答案",
"cmd_learn": "打开语法学习",
"cmd_hint": "请求当前题目的提示",

@@ -69,2 +79,10 @@ "cmd_ai": "向 AI 询问当前题目和代码",

"update_check_failed": "无法检查更新。",
"doctor_title": "环境诊断",
"doctor_current_language": "当前语言",
"doctor_runtime_checks": "运行时检查",
"doctor_optional_ai": "可选 AI",
"doctor_ok": "正常",
"doctor_missing": "缺失",
"doctor_update": "需要更新",
"doctor_install": "安装",
"generating_next": "正在生成下一题",

@@ -77,3 +95,21 @@ "already_busy": "已有任务正在运行。",

"run_pass_next": "下一步: /next",
"run_fail_next": "修复: 按 Esc 或 e 编辑,然后 /run"
"run_fail_next": "修复: 按 Esc 或 e 编辑,然后 /run",
"syntax": "语法",
"syntax_language": "语言",
"syntax_level": "级别",
"syntax_progress": "进度",
"syntax_complete": "完成",
"syntax_open": "未完成",
"syntax_concept": "核心概念",
"syntax_worked_example": "讲解示例",
"syntax_exercise": "练习",
"syntax_exercise_prompt": "运行前先预测输出。然后运行初始代码,并编辑到输出符合预期。",
"syntax_references": "参考",
"syntax_basic": "基础",
"syntax_intermediate": "中级",
"syntax_advanced": "高级",
"exercise_result": "验证结果",
"cmd_ask": "向 AI 询问当前课程或代码",
"syntax_common_mistakes": "常见错误",
"syntax_self_check": "自我检查"
}
+1
-1

@@ -456,3 +456,3 @@ # This file is automatically @generated by Cargo.

name = "practicode"
version = "0.1.12"
version = "0.1.13"
dependencies = [

@@ -459,0 +459,0 @@ "anyhow",

[package]
name = "practicode"
version = "0.1.12"
version = "0.1.13"
edition = "2024"

@@ -5,0 +5,0 @@ description = "Local-first coding-test practice in a Rust terminal UI with optional AI help."

@@ -14,2 +14,3 @@ # Architecture

- `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/progress.rs` owns give-up/next/previous/pass history transitions.

@@ -34,2 +35,4 @@ - `src/core/problem_files.rs` owns generated problem README/index file writes.

- `src/text.rs` owns terminal text editing and markdown/plain rendering helpers.
- `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).

@@ -41,2 +44,3 @@ ## Extension Rules

- Add persisted user profile settings to `Settings`, normalize them in `normalize_settings`, and cover old-state compatibility with tests.
- Add syntax lesson copy under `assets/lessons/<programming-language>/<ui-language>.json`; every supported UI language must define the required study fields.
- Keep provider-specific behavior in `src/ai.rs`; TUI should ask for status or start tasks, not know provider internals.

@@ -43,0 +47,0 @@ - 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.

@@ -72,2 +72,3 @@ # Contributing

| `src/core.rs` | Problem storage, state, rendering, judging |
| `src/core/syntax.rs` | Built-in syntax lessons, exercises, and lesson-copy loading |
| `src/i18n.rs` | Loads UI strings from `assets/i18n/*.json` |

@@ -78,2 +79,3 @@ | `src/tui.rs` | Ratatui app, editor, command parser |

| `src/process.rs` | Process execution helpers |
| `assets/lessons/` | Syntax lesson study copy split by programming language and UI language |
| `tests/` | Integration tests split by module |

@@ -88,2 +90,3 @@

- 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 the root [README](../README.md) focused on users.

@@ -90,0 +93,0 @@ - Use relative links for repo-local docs and assets.

@@ -63,5 +63,7 @@ # Maintaining

| [../README.md](../README.md) | Users installing and running practicode |
| [COMMANDS.md](COMMANDS.md) | Users looking up slash commands and aliases |
| [CONTRIBUTING.md](CONTRIBUTING.md) | External contributors opening issues or pull requests |
| [MAINTAINING.md](MAINTAINING.md) | Maintainers reviewing, triaging, and releasing |
| [problem-authoring-notes.md](problem-authoring-notes.md) | AI/local problem generation rules |
| [../assets/lessons/README.md](../assets/lessons/README.md) | Syntax lesson catalog layout and required fields |

@@ -68,0 +70,0 @@ ## Maintainer References

@@ -11,4 +11,7 @@ # Problem Authoring Notes

- Make examples small enough to verify by hand.
- Public examples should illustrate the format and one or two edge cases, not exhaust the solution.
- Include enough hidden cases to catch empty/min/max, duplicates, ties, ordering, and whitespace mistakes.
- Keep answers for `python`, `ts`, `java`, and `rust` in `.practicode/problem_bank.json`; never put answers in `README.md`.
- Do not create `solution.*`, `test_solution.*`, or answer-revealing files inside `problems/NNN-slug/`.
The learner's editable code belongs under `submissions/`, and answer keys belong only in the local bank or built-in data.

@@ -35,1 +38,4 @@ ## Difficulty

- ICPC judging guidelines: https://icpc.global/regionals/regional-contest-cookbook-judging-guidelines
- MIT Teaching + Learning Lab, worked examples: https://tll.mit.edu/teaching-resources/how-people-learn/worked-examples/
- Roediger, Agarwal, McDaniel, and McDermott, test-enhanced learning: https://pdf.retrievalpractice.org/guide/Roediger_Agarwal_etal_2011_JEPA.pdf
- Parsons problems literature review: https://juholeinonen.com/assets/pdf/ericson2022parsons.pdf
{
"name": "practicode",
"version": "0.1.12",
"version": "0.1.13",
"description": "Local-first coding-test practice in a Rust terminal UI with optional AI help.",

@@ -27,2 +27,3 @@ "license": "MIT",

"docs/",
"!docs/superpowers/",
"scripts/",

@@ -29,0 +30,0 @@ "src/",

+129
-116

@@ -12,23 +12,25 @@ # practicode

![practicode terminal UI](assets/practicode-terminal.svg)
Personal coding practice, right in your terminal.
`practicode` is a small Rust TUI for stdin/stdout practice: problem on the left, code on the right, judge loop in the same 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.
<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>
</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>
</figure>
## What You Get
- Local stdin/stdout judging for Python, TypeScript, Java, and Rust.
- A two-pane terminal UI with problem text, editor, output, and command palette.
- Local-first problem history under ignored `.practicode/`, `problems/`, and `submissions/` paths.
- Optional Codex or Claude Code help for hints and generated next problems.
- 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
### Prerequisites
- Node.js 18+ for npm installation.
- Rust and Cargo. The npm package builds the Rust binary during install, and also on first run if needed.
- A runtime for the language you practice in: Python, Node.js for TypeScript, JDK for Java, or Rust.
### npm

@@ -41,5 +43,6 @@

The npm package has a `postinstall` step that runs `cargo build --release --locked` from the package root so the Rust TUI binary is ready. Set `PRACTICODE_SKIP_BUILD=1` to skip that install-time build; the `practicode` launcher will try the same locked Cargo build on first run if the binary is missing.
The npm package builds the Rust binary during install. Set `PRACTICODE_SKIP_BUILD=1` to skip that step; the launcher will try the same locked Cargo build on first run if the binary is missing.
### Cargo
<details>
<summary>Cargo</summary>

@@ -51,4 +54,7 @@ ```bash

### Local checkout
</details>
<details>
<summary>Local checkout</summary>
```bash

@@ -61,105 +67,128 @@ git clone https://github.com/baba9811/practicode.git

### Check Install
</details>
### Language runtimes
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`.
<details>
<summary>macOS</summary>
```bash
practicode --version
practicode --smoke
practicode --help
brew install python node
brew install --cask temurin@21
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
## Daily Loop
</details>
On first run, practicode opens a setup panel. After that, the code editor starts focused.
<details>
<summary>Windows</summary>
```text
first run: review /profile setup
write code
Esc, then /
choose /run
choose /next when it passes
```powershell
winget install -e --id Python.Python.3.12
winget install -e --id OpenJS.NodeJS.LTS
winget install -e --id EclipseAdoptium.Temurin.21.JDK
winget install -e --id Rustlang.Rustup
```
Typing `/` outside the editor opens the command palette. Use `up/down` to move, `Enter` to run or complete the selected command, and `Esc` to cancel. Press `?` for in-app help or `Ctrl+C` to quit.
Restart the terminal after installing so `python`, `node`, `javac`, and `rustc` are on `PATH`.
Hints, failed cases, answers, and user profile panels are copy-friendly: drag in the output pane to use your terminal's normal text selection/copy behavior. When the code editor is visible in the right pane, mouse focus is enabled for that editor. Use `Esc`, `e`, or `/code` to return from output to code.
</details>
Submissions are saved as you type under `submissions/<problem-id>/solution.<ext>`.
<details>
<summary>Ubuntu / Debian</summary>
## CLI Flags
```bash
sudo apt update
sudo apt install -y python3 nodejs npm openjdk-21-jdk curl build-essential
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
| Flag | Action |
| --- | --- |
| `--help`, `-h` | Show non-interactive help |
| `--version`, `-V` | Print the installed version |
| `--smoke` | Print the current problem title and exit |
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.
## Commands
</details>
| Command | Action |
| --- | --- |
| `/run` | Judge the current submission |
| `/code` | Return to the code editor |
| `/next` | Open the next unsolved problem, or ask AI only when none remain |
| `/generate easy string problem` | Ask AI to create a new problem in the background |
| `/back` | Go back through problem history |
| `/problems` | Browse problems with `up/down` or `j/k`, open with `Enter` |
| `/open 2` | Open by number, id, or slug |
| `/answer` | Show the reference answer |
| `/hint` | Ask the selected AI for a concise hint |
| `/hint explain my bug` | Ask the selected AI about the current problem and submission |
| `/profile` | Show your current user profile |
| `/difficulty auto` | Set difficulty preference: `auto`, `easy`, `medium`, `hard` |
| `/topics arrays, strings` | Set preferred topics for future problems |
| `/avoid dp, graph` | Set topics to avoid in future problems |
| `/generate-languages python, rust` | Limit generated answer languages, or use `all` |
| `/generate-ui ko, en` | Limit generated problem text languages, or use `all` |
| `/provider codex` | Set AI provider and show local CLI/daemon status |
| `/model auto` | Use the provider default model for `/hint` and AI-backed `/next` |
| `/effort auto` | Use the provider default effort, or set `low`, `medium`, `high`, `xhigh`; Claude also supports `max` |
| `/note` | Edit problem-generation notes used by AI-backed `/next` and `/generate` |
| `/notes` | Show saved problem-generation notes |
| `/language python` | Set code language: `python`, `ts`, `java`, `rust` |
| `/ui en` | Set UI language: `en`, `ko`, `ja`, `zh`, `es` |
| `/theme dark` | Set theme: `dark` or `light` |
| `/update` | Show update instructions when a newer version is available |
| `/exit` | Quit |
Verify runtimes:
Older command names such as `/prev`, `/list`, `/giveup`, and `/lang` still work as aliases.
```bash
python3 --version
node --version
javac -version
rustc --version
```
The default UI language is English. Switch it any time with `/ui ko`, `/ui ja`, `/ui zh`, or `/ui es`.
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/).
Your user profile is saved in `.practicode/problem-state.json`. It keeps UI language, code language, theme, preferred difficulty, preferred topics, topics to avoid, generation language scope, and AI provider/model/effort. `auto` difficulty follows gradual progression; a fixed difficulty asks local selection and AI generation to prefer that level.
After starting practicode, run `/doctor` to check these runtimes from inside the TUI.
Inside `/profile`, use `up/down` to move and `Space` or `Enter` to cycle common settings, AI provider/model/effort, or generated answer/UI languages. The notes row opens an editor for `.practicode/problem_notes.md`. Use slash commands for free-form lists such as `/topics arrays, strings`.
Check the install:
## Problem Flow
```bash
practicode --version
practicode --smoke
practicode --help
```
`/next` is local-first: it opens the next unsolved local problem before generating anything. When no unsolved problem remains, it asks the selected AI provider to create one.
## Update
Use `/generate <request>` when you explicitly want to create a new problem in the background while you keep solving the current one. The generated problem stays local and `/next` will pick it up later.
npm is the primary install path:
If `/next` has to generate because no local problem remains, it runs in the foreground. Editing and commands are paused so state cannot change halfway through that AI task. Press `Space` for the warmup timing drill, or `q`/`Ctrl+C` to quit.
```bash
npm update -g practicode
```
```text
/generate a slightly harder string problem
/generate hashmap practice, easy
/generate sorting problem, no graph yet
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>
```bash
cargo install --force practicode
```
Codex is the default provider:
</details>
```text
/provider codex
/model auto
/effort auto
<details>
<summary>Local checkout update</summary>
```bash
git pull --ff-only
npm install
npm start
```
Claude Code is also supported:
</details>
## First Run
On first run, choose a mode:
```text
/provider claude
/model sonnet
/effort high
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
```
Use arrow keys to move on the home screen, and `Enter` or `Space` to open the selected mode.
## Commands
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.
Most-used commands:
| 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 |
See [docs/COMMANDS.md](docs/COMMANDS.md) for the full command list, aliases, AI generation commands, and profile settings.
## Local Data
Generated problems and submissions stay local:

@@ -171,27 +200,24 @@

| `.practicode/problem_notes.md` | Optional personal problem-generation notes |
| `.practicode/problem-state.json` | Current problem, history, settings |
| `.practicode/problem-state.json` | Current problem, history, and settings |
| `problems/` | Generated problem markdown/index files |
| `submissions/` | Your answer files |
Those paths are ignored by git, so your practice history stays yours.
Those paths are ignored by git.
## Update
## Safety
The app checks for newer npm releases in the background and shows `/update` in the status line when one is available. Disable that check with `PRACTICODE_NO_UPDATE_CHECK=1`.
- `/run` executes your local submission as a normal process. It is not an OS sandbox.
- `/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 commit tokens, private prompts, `.env`, `.npmrc`, `.practicode/`, `problems/`, or `submissions/`.
```bash
npm update -g practicode
cargo install --force practicode
```
Security reporting details live in [SECURITY.md](SECURITY.md).
## Safety And Security
## Contributing
- `/run` executes your local submission as a normal process. practicode runs it from `.practicode/build/<problem-id>/run`, but this is not an OS sandbox. Only run code you trust.
- `/hint` sends the current problem and submission to the selected AI provider CLI.
- AI-backed `/next` and `/generate` can run a custom shell command from `settings.ai_next_command`; save only commands you trust.
- npm installs run the package `postinstall` script described above. It only invokes Cargo with the checked-in lockfile from this package root; it does not read local `.env`/`.npmrc` files or contact the configured AI provider.
- npm releases are published from GitHub Actions with registry signatures and provenance enabled in `package.json`. The release workflow is also prepared for npm Trusted Publishing/OIDC; maintainers should prefer that over long-lived publish tokens when the package setting is enabled on npm.
- Local `.env`, `.npmrc`, `.practicode/`, `problems/`, and `submissions/` are ignored by git. Do not commit tokens, private prompts, or answer keys.
- 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)
## Development Checks
Local checks:

@@ -203,19 +229,6 @@ ```bash

cargo run -- --smoke
cargo audit --deny warnings
npm pack --dry-run
npm run smoke
```
CI runs the Rust audit gate with `cargo audit --deny warnings`; release publishing stops before crates.io/npm publish if that audit fails. This repo has no npm dependencies or lockfile today, so `npm audit` and `pnpm audit` are not applicable until a matching lockfile is added.
## Contributing
External contributions use the fork and pull request flow in [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md).
Maintainer-only review and release notes live in [docs/MAINTAINING.md](docs/MAINTAINING.md).
Code layout and extension boundaries live in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
## License
practicode is MIT licensed. Third-party dependency license notes are in [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md).
use crate::{
core::{
AppState, CLAUDE_AI_EFFORTS, LANGUAGES, PROBLEM_NOTES_PATH, Problem, Settings,
UI_LANGUAGES, ensure_submission, normalize_ai_provider, render_problem,
SyntaxLesson, UI_LANGUAGES, ensure_submission, ensure_syntax_submission,
normalize_ai_provider, render_problem, syntax_lesson_study_context,
},

@@ -28,2 +29,23 @@ process::{run_capture, sh_quote, shell_process, unique_temp_path, which},

pub fn build_lesson_ai_prompt(
lesson: &SyntaxLesson,
settings: &Settings,
prompt: &str,
relative: &str,
code: &str,
latest_result: &str,
) -> String {
let latest_result = if latest_result.trim().is_empty() {
"(none yet)"
} else {
latest_result.trim()
};
format!(
"You are a concise Socratic programming tutor for the current syntax lesson. Answer conceptual questions about the lesson, worked example, and learner's current exercise. Prefer explanation, guiding questions, and tiny examples. Do not give the full exercise solution unless the user explicitly asks for the answer.\n\nUser question:\n{prompt}\n\nCurrent syntax lesson context:\n{}\n\nCurrent {} exercise ({relative}):\n```{}\n{code}\n```\n\nLatest validation result:\n{latest_result}",
syntax_lesson_study_context(lesson, &settings.ui_language),
settings.language,
settings.language
)
}
pub fn run_ai_prompt(root: &Path, problem: &Problem, settings: &Settings, prompt: &str) -> String {

@@ -52,2 +74,27 @@ let solution = match ensure_submission(root, problem, settings) {

pub fn run_ai_lesson_prompt(
root: &Path,
lesson: &SyntaxLesson,
settings: &Settings,
prompt: &str,
latest_result: &str,
) -> String {
let exercise = match ensure_syntax_submission(root, lesson) {
Ok(path) => path,
Err(error) => return format!("AI prompt failed\n{error}"),
};
let code = fs::read_to_string(&exercise).unwrap_or_default();
let relative = exercise
.strip_prefix(root)
.unwrap_or(&exercise)
.display()
.to_string();
let full_prompt =
build_lesson_ai_prompt(lesson, settings, prompt, &relative, &code, latest_result);
match normalize_ai_provider(&settings.ai_provider).as_str() {
"claude" => run_claude_prompt(root, settings, &full_prompt),
_ => run_codex_prompt(root, settings, &full_prompt),
}
}
pub fn run_ai_next(root: &Path, state: &AppState, force: bool, request: &str) -> String {

@@ -182,3 +229,3 @@ if state.settings.next_source != "ai" && !force {

format!(
"Read AGENTS.md, docs/problem-authoring-notes.md if present, .practicode/problem_notes.md if present, problems/INDEX.md if present, .practicode/problem_bank.json if present, and .practicode/problem-state.json. Create exactly one new non-duplicate coding practice problem. The built-in 001-hello-world already exists, so do not duplicate it. User request: {}. User profile: difficulty preference: {}; preferred topics: {}; avoid topics: {}; code language: {}; UI language: {}; generated answer languages: {}; generated UI languages: {}. Treat difficulty auto as gradual progression from state; otherwise prefer the requested difficulty unless the direct user request conflicts. Make the smallest valid edits: update .practicode/problem_bank.json, one problem directory, problems/INDEX.md, and .practicode/problem-state.json. Do not include the answer in the problem statement.",
"Read AGENTS.md, docs/problem-authoring-notes.md if present, .practicode/problem_notes.md if present, problems/INDEX.md if present, .practicode/problem_bank.json if present, and .practicode/problem-state.json. Create exactly one new non-duplicate coding practice problem. The built-in 001-hello-world already exists, so do not duplicate it. User request: {}. User profile: difficulty preference: {}; preferred topics: {}; avoid topics: {}; code language: {}; UI language: {}; generated answer languages: {}; generated UI languages: {}. Treat difficulty auto as gradual progression from state; otherwise prefer the requested difficulty unless the direct user request conflicts. Make the smallest valid edits: update .practicode/problem_bank.json, one problem directory, problems/INDEX.md, and .practicode/problem-state.json. Do not include the answer in the problem statement. Do not create solution.*, test_solution.*, or any answer-revealing file inside the problem directory.",
if request.is_empty() {

@@ -201,3 +248,3 @@ "(none)"

format!(
"Read AGENTS.md, docs/problem-authoring-notes.md if present, .practicode/problem_notes.md if present, problems/INDEX.md if present, .practicode/problem_bank.json if present, and .practicode/problem-state.json. Create exactly one new non-duplicate coding practice problem for later use. The built-in 001-hello-world already exists, so do not duplicate it. User request: {}. User profile: difficulty preference: {}; preferred topics: {}; avoid topics: {}; current code language: {}; current UI language: {}; generated answer languages: {}; generated UI languages: {}. Treat difficulty auto as gradual progression from state; otherwise prefer the requested difficulty unless the direct user request conflicts. Make the smallest valid edits: update .practicode/problem_bank.json, one problem directory, and problems/INDEX.md. Preserve .practicode/problem-state.json current_problem, history, solved, and settings; do not switch the current problem. Do not include the answer in the problem statement.",
"Read AGENTS.md, docs/problem-authoring-notes.md if present, .practicode/problem_notes.md if present, problems/INDEX.md if present, .practicode/problem_bank.json if present, and .practicode/problem-state.json. Create exactly one new non-duplicate coding practice problem for later use. The built-in 001-hello-world already exists, so do not duplicate it. User request: {}. User profile: difficulty preference: {}; preferred topics: {}; avoid topics: {}; current code language: {}; current UI language: {}; generated answer languages: {}; generated UI languages: {}. Treat difficulty auto as gradual progression from state; otherwise prefer the requested difficulty unless the direct user request conflicts. Make the smallest valid edits: update .practicode/problem_bank.json, one problem directory, and problems/INDEX.md. Preserve .practicode/problem-state.json current_problem, history, solved, and settings; do not switch the current problem. Do not include the answer in the problem statement. Do not create solution.*, test_solution.*, or any answer-revealing file inside the problem directory.",
if request.is_empty() {

@@ -204,0 +251,0 @@ "(none)"

@@ -21,2 +21,3 @@ use crate::process::{CommandSpec, run_capture, which};

mod state;
mod syntax;

@@ -35,1 +36,2 @@ pub use crate::i18n::{UI_LANGUAGES, normalize_ui_language, ui_text};

pub use state::*;
pub use syntax::*;

@@ -38,3 +38,22 @@ use super::*;

let language = normalize_language(&settings.language);
let command = match command_for(root, &path, &language) {
judge_path(root, &problem.id, &path, &language, &problem.cases)
}
pub fn judge_path(
root: &Path,
id: &str,
path: &Path,
language: &str,
cases: &[IoCase],
) -> JudgeResult {
if cases.is_empty() {
return JudgeResult {
passed: false,
passed_cases: 0,
total_cases: 0,
output: "problem has no judge cases".to_string(),
};
}
let language = normalize_language(language);
let command = match command_for(root, path, &language) {
Ok(Some(command)) => command,

@@ -45,4 +64,4 @@ Ok(None) => {

passed_cases: 0,
total_cases: problem.cases.len(),
output: format!("Missing runtime for {}", settings.language),
total_cases: cases.len(),
output: format!("Missing runtime for {language}"),
};

@@ -54,3 +73,3 @@ }

passed_cases: 0,
total_cases: problem.cases.len(),
total_cases: cases.len(),
output: format!("compile failed\n{error}"),

@@ -60,3 +79,3 @@ };

};
let run_dir = root.join(".practicode/build").join(&problem.id).join("run");
let run_dir = root.join(".practicode/build").join(id).join("run");
if let Err(error) = fs::create_dir_all(&run_dir) {

@@ -66,3 +85,3 @@ return JudgeResult {

passed_cases: 0,
total_cases: problem.cases.len(),
total_cases: cases.len(),
output: error.to_string(),

@@ -74,3 +93,3 @@ };

let mut lines = Vec::new();
for (index, case) in problem.cases.iter().enumerate() {
for (index, case) in cases.iter().enumerate() {
let mut process = Command::new(&command.program);

@@ -91,2 +110,5 @@ process.args(&command.args).current_dir(&run_dir);

lines.push(format!("Case {}: PASS", index + 1));
if !run.stdout.trim().is_empty() {
push_labeled_block(&mut lines, "Stdout", run.stdout.trim_end());
}
if !run.stderr.trim().is_empty() {

@@ -100,5 +122,5 @@ push_labeled_block(&mut lines, "Stderr", run.stderr.trim_end());

}
push_labeled_block(&mut lines, "Got", run.stdout.trim_end());
push_labeled_block(&mut lines, "Input", case.input.trim_end());
push_labeled_block(&mut lines, "Expected", expected);
push_labeled_block(&mut lines, "Got", run.stdout.trim_end());
if !run.stderr.trim().is_empty() {

@@ -112,5 +134,5 @@ push_labeled_block(&mut lines, "Stderr", run.stderr.trim_end());

JudgeResult {
passed: passed == problem.cases.len(),
passed: passed == cases.len(),
passed_cases: passed,
total_cases: problem.cases.len(),
total_cases: cases.len(),
output: lines.join("\n"),

@@ -201,2 +223,3 @@ }

.args([
"--edition=2024".to_string(),
path.display().to_string(),

@@ -203,0 +226,0 @@ "-o".to_string(),

@@ -20,2 +20,4 @@ use super::*;

pub theme: String,
#[serde(default = "default_start_mode")]
pub start_mode: String,
#[serde(default = "default_difficulty")]

@@ -51,2 +53,3 @@ pub difficulty: String,

theme: default_theme(),
start_mode: default_start_mode(),
difficulty: default_difficulty(),

@@ -108,2 +111,6 @@ topics: Vec::new(),

pub suggested_next_difficulty: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub syntax_progress: HashMap<String, Vec<String>>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub current_syntax_lesson: HashMap<String, String>,
}

@@ -152,2 +159,6 @@

pub fn default_start_mode() -> String {
"home".to_string()
}
pub fn default_editor() -> String {

@@ -154,0 +165,0 @@ "vim".to_string()

@@ -15,2 +15,4 @@ use super::*;

suggested_next_difficulty: "easy".to_string(),
syntax_progress: HashMap::new(),
current_syntax_lesson: HashMap::new(),
});

@@ -29,2 +31,4 @@ }

normalize_settings(&mut state.settings);
state.syntax_progress = normalize_syntax_progress(&state.syntax_progress);
state.current_syntax_lesson = normalize_current_syntax_lessons(&state.current_syntax_lesson);
if state.history.is_empty() {

@@ -48,2 +52,6 @@ state.history.push(HistoryItem {

history: &'a [HistoryItem],
#[serde(skip_serializing_if = "HashMap::is_empty")]
syntax_progress: &'a HashMap<String, Vec<String>>,
#[serde(skip_serializing_if = "HashMap::is_empty")]
current_syntax_lesson: &'a HashMap<String, String>,
}

@@ -62,2 +70,4 @@

history: &state.history,
syntax_progress: &state.syntax_progress,
current_syntax_lesson: &state.current_syntax_lesson,
};

@@ -74,2 +84,3 @@ fs::write(path, serde_json::to_string_pretty(&file)? + "\n")?;

}
settings.start_mode = normalize_start_mode(&settings.start_mode);
settings.difficulty = normalize_difficulty(&settings.difficulty);

@@ -87,1 +98,9 @@ settings.topics = normalize_topic_list(&settings.topics);

}
pub fn normalize_start_mode(mode: &str) -> String {
let mode = mode.trim().to_lowercase();
match mode.as_str() {
"home" | "learn" | "problems" => mode,
_ => "home".to_string(),
}
}
use anyhow::{Context, Result};
use std::{
env,
io::Write,
io::{ErrorKind, Write},
path::PathBuf,

@@ -32,3 +32,7 @@ process::{Command, Stdio},

if let Some(stdin) = child.stdin.as_mut() {
stdin.write_all(input.as_bytes()).context("write stdin")?;
match stdin.write_all(input.as_bytes()) {
Ok(()) => {}
Err(error) if error.kind() == ErrorKind::BrokenPipe => {}
Err(error) => return Err(error).context("write stdin"),
}
}

@@ -84,1 +88,20 @@ drop(child.stdin.take());

}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn run_capture_tolerates_child_that_exits_before_reading_stdin() {
let mut command = shell_process("exit 0");
let output = run_capture(
&mut command,
&"x".repeat(1024 * 1024),
Duration::from_secs(5),
)
.unwrap();
assert_eq!(output.code, Some(0));
assert!(!output.timed_out);
}
}

@@ -12,3 +12,3 @@ use unicode_width::UnicodeWidthStr;

if in_fence {
out.push(line.to_string());
out.push(format!(" {line}"));
continue;

@@ -15,0 +15,0 @@ }

use crate::{
ai::{
ModelCatalog, append_problem_note, available_models, provider_status, read_problem_notes,
run_ai_generate, run_ai_next, run_ai_prompt,
run_ai_generate, run_ai_lesson_prompt, run_ai_next, run_ai_prompt,
},
core::{
AI_PROVIDERS, AppState, CLAUDE_AI_EFFORTS, CODEX_AI_EFFORTS, DIFFICULTIES, HistoryItem,
LANGUAGES, PROBLEM_NOTES_PATH, Problem, STATE_PATH, THEMES, UI_LANGUAGES,
ensure_problem_files, ensure_submission, ext_for, give_up, judge, load_bank, load_state,
localized, next_problem, normalize_ai_effort, normalize_ai_provider, normalize_difficulty,
normalize_language, normalize_next_source, normalize_ui_language, parse_language_list,
parse_topic_list, parse_ui_language_list, previous_problem, problem_by_id, record_pass,
save_state, template_for, ui_text,
LANGUAGES, PROBLEM_NOTES_PATH, Problem, THEMES, UI_LANGUAGES, current_syntax_lesson,
ensure_problem_files, ensure_submission, ensure_syntax_submission, ext_for, give_up, judge,
judge_path, load_bank, load_state, localized, next_problem, next_syntax_lesson,
normalize_ai_effort, normalize_ai_provider, normalize_difficulty, normalize_language,
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,
},

@@ -47,2 +49,3 @@ text::{

mod commands;
mod doctor;
mod editor;

@@ -71,2 +74,4 @@ mod events;

enum Focus {
Home,
Left,
Code,

@@ -78,2 +83,15 @@ Command,

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum AppMode {
Home,
Problems,
Learn,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum HomeChoice {
Learn,
Problems,
}
pub struct PracticodeApp {

@@ -90,2 +108,5 @@ root: PathBuf,

output: String,
learn_result: String,
left_scroll: u16,
output_scroll: u16,
output_is_markdown: bool,

@@ -96,2 +117,7 @@ showing_model_status: bool,

focus: Focus,
mode: AppMode,
home_choice: HomeChoice,
home_area: Rect,
home_learn_area: Rect,
home_problems_area: Rect,
list_cursor: Option<usize>,

@@ -118,2 +144,3 @@ settings_cursor: Option<usize>,

last_update_check: Option<Instant>,
left_area: Rect,
code_area: Rect,

@@ -137,3 +164,2 @@ output_area: Rect,

pub fn new(root: PathBuf) -> Result<Self> {
let first_run = !root.join(STATE_PATH).exists();
let bank = load_bank(&root)?;

@@ -155,2 +181,5 @@ let state = load_state(&root, &bank)?;

output: String::new(),
learn_result: String::new(),
left_scroll: 0,
output_scroll: 0,
output_is_markdown: false,

@@ -161,2 +190,7 @@ showing_model_status: false,

focus: Focus::Code,
mode: AppMode::Problems,
home_choice: HomeChoice::Learn,
home_area: Rect::default(),
home_learn_area: Rect::default(),
home_problems_area: Rect::default(),
list_cursor: None,

@@ -183,2 +217,3 @@ settings_cursor: None,

last_update_check: None,
left_area: Rect::default(),
code_area: Rect::default(),

@@ -191,7 +226,7 @@ output_area: Rect::default(),

app.load_code_editor()?;
if first_run {
save_state(&app.root, &app.state)?;
app.show_profile_with_intro(
"Welcome to practicode\n\nUse the setup panel below first.",
);
save_state(&app.root, &app.state)?;
match app.state.settings.start_mode.as_str() {
"learn" => app.action_learn("")?,
"problems" => app.action_practice()?,
_ => app.action_home()?,
}

@@ -263,2 +298,11 @@ Ok(app)

pub fn set_home_choice_areas_for_test(&mut self, learn: Rect, problems: Rect) {
self.home_learn_area = learn;
self.home_problems_area = problems;
}
pub fn set_home_area_for_test(&mut self, area: Rect) {
self.home_area = area;
}
pub fn busy_label(&self) -> &str {

@@ -300,2 +344,6 @@ &self.busy_label

pub fn learn_result_for_test(&self) -> &str {
&self.learn_result
}
pub fn command_suggestions_for_test(&self) -> Vec<String> {

@@ -302,0 +350,0 @@ self.command_suggestions()

use super::*;
impl PracticodeApp {
pub(super) fn home_text(&self) -> String {
let lang = &self.state.settings.ui_language;
let learn_label = ui_text(lang, "home_learn_choice");
let practice_label = ui_text(lang, "home_practice_choice");
let help = ui_text(lang, "home_help");
let learn = if self.home_choice == HomeChoice::Learn {
format!("> {learn_label}")
} else {
format!(" {learn_label}")
};
let problems = if self.home_choice == HomeChoice::Problems {
format!("> {practice_label}")
} else {
format!(" {practice_label}")
};
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}"
)
}
pub(super) fn action_home(&mut self) -> Result<()> {
self.mode = AppMode::Home;
self.state.settings.start_mode = "home".to_string();
save_state(&self.root, &self.state)?;
self.learn_result.clear();
self.left_scroll = 0;
self.output_scroll = 0;
self.show_output = false;
self.settings_cursor = None;
self.list_cursor = None;
self.focus = Focus::Home;
self.output = self.home_text();
self.output_is_markdown = false;
Ok(())
}
pub(super) fn action_practice(&mut self) -> Result<()> {
self.mode = AppMode::Problems;
self.state.settings.start_mode = "problems".to_string();
save_state(&self.root, &self.state)?;
self.load_code_editor()?;
self.settings_cursor = None;
self.list_cursor = None;
self.show_output = false;
self.focus = Focus::Code;
Ok(())
}
pub(super) fn move_home_choice(&mut self) {
self.home_choice = match self.home_choice {
HomeChoice::Learn => HomeChoice::Problems,
HomeChoice::Problems => HomeChoice::Learn,
};
self.output = self.home_text();
}
pub(super) fn open_home_choice(&mut self) -> Result<()> {
match self.home_choice {
HomeChoice::Learn => self.action_learn(""),
HomeChoice::Problems => self.action_practice(),
}
}
pub(super) fn action_edit(&mut self) -> Result<()> {
if self.mode == AppMode::Home {
return self.action_practice();
}
self.editing_notes = false;
self.load_code_editor()?;
self.settings_cursor = None;
self.left_scroll = 0;
self.output_scroll = 0;
self.show_output = false;

@@ -14,2 +82,10 @@ self.focus = Focus::Code;

pub(super) fn action_run(&mut self) -> Result<()> {
if self.mode == AppMode::Learn {
return self.action_exercise();
}
if self.mode == AppMode::Home {
self.mode = AppMode::Problems;
self.state.settings.start_mode = "problems".to_string();
save_state(&self.root, &self.state)?;
}
self.save_code()?;

@@ -36,2 +112,8 @@ let result = judge(&self.root, &self.problem, &self.state.settings);

pub(super) fn action_next(&mut self, request: &str) -> Result<()> {
if self.mode == AppMode::Learn {
return self.action_next_lesson();
}
self.mode = AppMode::Problems;
self.state.settings.start_mode = "problems".to_string();
save_state(&self.root, &self.state)?;
self.check_background_generation();

@@ -71,2 +153,8 @@ let request = request.trim();

pub(super) fn start_background_generation(&mut self, request: String) {
self.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.");
return;
}
let root = self.root.clone();

@@ -83,2 +171,4 @@ let state = self.state.clone();

self.settings_cursor = None;
self.left_scroll = 0;
self.output_scroll = 0;
self.show_output = false;

@@ -149,2 +239,10 @@ self.focus = Focus::Code;

pub(super) fn action_previous(&mut self) -> Result<()> {
if self.mode == AppMode::Learn {
return self.action_prev_lesson();
}
if self.mode == AppMode::Home {
self.mode = AppMode::Problems;
self.state.settings.start_mode = "problems".to_string();
save_state(&self.root, &self.state)?;
}
let old_problem = self.state.current_problem.clone();

@@ -164,2 +262,7 @@ self.problem = previous_problem(&self.root, &self.bank, &mut self.state)?;

pub(super) fn action_give_up(&mut self) -> Result<()> {
if self.mode == AppMode::Home {
self.mode = AppMode::Problems;
self.state.settings.start_mode = "problems".to_string();
save_state(&self.root, &self.state)?;
}
let answer = give_up(&self.root, &self.problem, &mut self.state)?;

@@ -174,2 +277,93 @@ let language = normalize_language(&self.state.settings.language);

pub(super) fn action_learn(&mut self, language: &str) -> Result<()> {
let language = language.trim();
if !language.is_empty() && !LANGUAGES.contains(&language) {
self.write_text_output("Usage: /learn or /learn python|ts|java|rust");
return Ok(());
}
if !language.is_empty() {
self.state.settings.language = language.to_string();
}
self.mode = AppMode::Learn;
self.state.settings.start_mode = "learn".to_string();
self.learn_result.clear();
self.left_scroll = 0;
self.output_scroll = 0;
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);
save_state(&self.root, &self.state)?;
self.load_syntax_editor()?;
self.show_current_syntax_lesson();
Ok(())
}
pub(super) fn action_exercise(&mut self) -> Result<()> {
if self.mode != AppMode::Learn {
self.action_learn("")?;
}
self.save_syntax_code()?;
let lesson = current_syntax_lesson(&self.state, &self.state.settings.language);
let path = ensure_syntax_submission(&self.root, lesson)?;
let cases = syntax_cases(lesson);
let result = judge_path(
&self.root,
&format!(".syntax-{}-{}", lesson.language, lesson.id),
&path,
lesson.language,
&cases,
);
if result.passed {
record_syntax_pass(&mut self.state, lesson.language, lesson.id);
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
);
self.learn_result = format!("{headline}\n{}", result.output.trim_end());
self.show_current_syntax_lesson();
Ok(())
}
pub(super) fn action_next_lesson(&mut self) -> Result<()> {
self.mode = AppMode::Learn;
let language = self.state.settings.language.clone();
next_syntax_lesson(&mut self.state, &language, 1);
save_state(&self.root, &self.state)?;
self.load_syntax_editor()?;
self.learn_result.clear();
self.left_scroll = 0;
self.output_scroll = 0;
self.show_current_syntax_lesson();
Ok(())
}
pub(super) fn action_prev_lesson(&mut self) -> Result<()> {
self.mode = AppMode::Learn;
let language = self.state.settings.language.clone();
next_syntax_lesson(&mut self.state, &language, -1);
save_state(&self.root, &self.state)?;
self.load_syntax_editor()?;
self.learn_result.clear();
self.left_scroll = 0;
self.output_scroll = 0;
self.show_current_syntax_lesson();
Ok(())
}
pub(super) fn show_current_syntax_lesson(&mut self) {
let lesson = current_syntax_lesson(&self.state, &self.state.settings.language);
self.output = render_syntax_lesson(lesson, &self.state);
self.left_scroll = 0;
self.output_is_markdown = true;
self.show_output = false;
self.settings_cursor = None;
self.list_cursor = None;
self.focus = Focus::Code;
}
pub(super) fn action_cycle_language(&mut self) -> Result<()> {

@@ -204,3 +398,10 @@ let current = LANGUAGES

self.settings_cursor = None;
self.show_output = false;
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;

@@ -213,3 +414,8 @@ Ok(())

save_state(&self.root, &self.state)?;
self.write_text_output(&format!("UI language: {}", self.state.settings.ui_language));
if self.mode == AppMode::Learn {
self.left_scroll = 0;
self.show_current_syntax_lesson();
} else {
self.write_text_output(&format!("UI language: {}", self.state.settings.ui_language));
}
Ok(())

@@ -299,2 +505,3 @@ }

};
self.output_scroll = 0;
self.output_is_markdown = false;

@@ -365,2 +572,3 @@ self.show_output = true;

self.editing_notes = true;
self.output_scroll = 0;
self.show_output = true;

@@ -367,0 +575,0 @@ self.focus = Focus::Output;

@@ -38,2 +38,5 @@ use super::*;

"code" | "edit" | "e" => self.action_edit()?,
"home" => self.action_home()?,
"doctor" => self.action_doctor(),
"learn" => self.action_learn(arg)?,
"next" | "n" => self.action_next(arg)?,

@@ -43,3 +46,3 @@ "generate" | "gen" | "new" => self.action_generate(arg),

"answer" | "giveup" | "give" | "g" => self.action_give_up()?,
"problems" | "list" => self.start_problem_list(),
"problems" | "list" => self.start_problem_list()?,
"open" | "o" if !arg.is_empty() => self.open_problem(arg)?,

@@ -128,3 +131,8 @@ "language" | "lang" if arg.is_empty() => self.action_cycle_language()?,

"hint" if arg.is_empty() => {
self.start_ai_prompt("Give one concise hint for the current problem.")?
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."
};
self.start_ai_prompt(prompt)?
}

@@ -131,0 +139,0 @@ "hint" | "ask" | "ai" if !arg.is_empty() => self.start_ai_prompt(arg)?,

@@ -43,8 +43,38 @@ use super::*;

let query = query.to_lowercase();
self.command_choices()
let trimmed = query.trim_start();
let choices = self.command_choices();
if trimmed.is_empty() {
return self
.default_command_inserts()
.iter()
.filter_map(|insert| choices.iter().find(|hint| hint.insert == *insert).cloned())
.collect();
}
choices
.into_iter()
.filter(|hint| hint.insert.starts_with(query.trim_start()))
.filter(|hint| hint.insert.starts_with(trimmed))
.collect()
}
pub(super) fn default_command_inserts(&self) -> &'static [&'static str] {
match self.mode {
AppMode::Home => &["learn", "problems", "doctor", "profile", "help", "quit"],
AppMode::Problems => &[
"run",
"next",
"back",
"problems",
"answer",
"hint ",
"generate ",
"profile",
"doctor",
"home",
],
AppMode::Learn => &[
"run", "next", "back", "ask ", "learn", "problems", "profile", "doctor", "home",
],
}
}
pub(super) fn command_choices(&self) -> Vec<CommandChoice> {

@@ -51,0 +81,0 @@ let mut choices = Vec::new();

@@ -7,3 +7,2 @@ #[derive(Clone, Copy)]

pub(super) keep_open: bool,
pub(super) help: bool,
}

@@ -17,3 +16,2 @@

keep_open: false,
help: true,
},

@@ -25,5 +23,16 @@ CommandHint {

keep_open: false,
help: true,
},
CommandHint {
insert: "home",
display: "/home",
desc_key: "cmd_home",
keep_open: false,
},
CommandHint {
insert: "doctor",
display: "/doctor",
desc_key: "cmd_doctor",
keep_open: false,
},
CommandHint {
insert: "next",

@@ -33,3 +42,2 @@ display: "/next",

keep_open: false,
help: true,
},

@@ -41,3 +49,2 @@ CommandHint {

keep_open: true,
help: true,
},

@@ -49,3 +56,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -57,3 +63,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -65,3 +70,2 @@ CommandHint {

keep_open: true,
help: true,
},

@@ -73,5 +77,10 @@ CommandHint {

keep_open: false,
help: true,
},
CommandHint {
insert: "learn",
display: "/learn",
desc_key: "cmd_learn",
keep_open: false,
},
CommandHint {
insert: "hint ",

@@ -81,5 +90,10 @@ display: "/hint <request>",

keep_open: true,
help: true,
},
CommandHint {
insert: "ask ",
display: "/ask <question>",
desc_key: "cmd_ask",
keep_open: true,
},
CommandHint {
insert: "profile",

@@ -89,3 +103,2 @@ display: "/profile",

keep_open: false,
help: true,
},

@@ -97,3 +110,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -105,3 +117,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -113,3 +124,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -121,3 +131,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -129,3 +138,2 @@ CommandHint {

keep_open: true,
help: true,
},

@@ -137,3 +145,2 @@ CommandHint {

keep_open: true,
help: true,
},

@@ -145,3 +152,2 @@ CommandHint {

keep_open: true,
help: true,
},

@@ -153,3 +159,2 @@ CommandHint {

keep_open: true,
help: true,
},

@@ -161,3 +166,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -169,3 +173,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -177,3 +180,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -185,3 +187,2 @@ CommandHint {

keep_open: true,
help: false,
},

@@ -193,3 +194,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -201,3 +201,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -209,3 +208,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -217,3 +215,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -225,3 +222,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -233,3 +229,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -241,3 +236,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -249,3 +243,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -257,3 +250,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -265,3 +257,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -273,3 +264,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -281,3 +271,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -289,3 +278,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -297,3 +285,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -305,3 +292,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -313,3 +299,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -321,3 +306,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -329,3 +313,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -337,3 +320,2 @@ CommandHint {

keep_open: false,
help: false,
},

@@ -345,3 +327,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -353,3 +334,2 @@ CommandHint {

keep_open: false,
help: true,
},

@@ -361,4 +341,9 @@ CommandHint {

keep_open: false,
help: true,
},
CommandHint {
insert: "quit",
display: "/quit",
desc_key: "cmd_exit",
keep_open: false,
},
];

@@ -162,2 +162,12 @@ use crate::text::{byte_index, char_len, compose_hangul_jamo, display_width, prefix};

pub(super) fn move_page_up(&mut self, height: usize) {
self.row = self.row.saturating_sub(height.max(1));
self.col = self.col.min(char_len(&self.lines[self.row]));
}
pub(super) fn move_page_down(&mut self, height: usize) {
self.row = (self.row + height.max(1)).min(self.lines.len().saturating_sub(1));
self.col = self.col.min(char_len(&self.lines[self.row]));
}
fn ensure_cursor(&mut self) {

@@ -164,0 +174,0 @@ if self.lines.is_empty() {

@@ -27,10 +27,66 @@ use super::*;

}
let position = Position::new(mouse.column, mouse.row);
if matches!(
mouse.kind,
MouseEventKind::ScrollUp | MouseEventKind::ScrollDown
) {
let delta = if matches!(mouse.kind, MouseEventKind::ScrollUp) {
-3
} else {
3
};
if self.show_output && self.output_area.contains(position) {
self.focus = Focus::Output;
self.scroll_output(delta);
} else if !self.show_output && self.left_area.contains(position) {
self.focus = Focus::Left;
self.scroll_left(delta);
} else if !self.show_output
&& self.mode == AppMode::Learn
&& !self.learn_result.is_empty()
&& self.output_area.contains(position)
{
self.focus = Focus::Output;
self.scroll_output(delta);
} else if !self.show_output && self.code_area.contains(position) {
self.focus = Focus::Code;
if delta < 0 {
self.editor.move_page_up(delta.unsigned_abs());
} else {
self.editor.move_page_down(delta as usize);
}
}
return Ok(());
}
if !matches!(mouse.kind, MouseEventKind::Down(MouseButton::Left)) {
return Ok(());
}
let position = Position::new(mouse.column, mouse.row);
if self.command_area.contains(position) {
self.focus_command();
} else if self.mode == AppMode::Home && self.home_learn_area.contains(position) {
self.focus = Focus::Home;
self.home_choice = HomeChoice::Learn;
self.open_home_choice()?;
} else if self.mode == AppMode::Home && self.home_problems_area.contains(position) {
self.focus = Focus::Home;
self.home_choice = HomeChoice::Problems;
self.open_home_choice()?;
} else if self.mode == AppMode::Home
&& !self.show_output
&& self.home_area.contains(position)
{
self.focus = Focus::Home;
} else if self.show_output && self.output_area.contains(position) {
self.focus = Focus::Output;
} else if !self.show_output
&& self.mode != AppMode::Home
&& self.left_area.contains(position)
{
self.focus = Focus::Left;
} else if !self.show_output
&& self.mode == AppMode::Learn
&& !self.learn_result.is_empty()
&& self.output_area.contains(position)
{
self.focus = Focus::Output;
} else if self.code_area.contains(position) {

@@ -48,3 +104,3 @@ self.action_edit()?;

self.command_palette_cursor = 0;
self.focus = Focus::None;
self.focus = self.resting_focus();
}

@@ -115,2 +171,8 @@ KeyCode::Enter => {

KeyCode::Down => self.editor.move_down(),
KeyCode::PageUp => self
.editor
.move_page_up(self.code_area.height.saturating_sub(2) as usize),
KeyCode::PageDown => self
.editor
.move_page_down(self.code_area.height.saturating_sub(2) as usize),
_ => {}

@@ -150,2 +212,8 @@ }

KeyCode::Down => self.note_editor.move_down(),
KeyCode::PageUp => self
.note_editor
.move_page_up(self.output_area.height.saturating_sub(2) as usize),
KeyCode::PageDown => self
.note_editor
.move_page_down(self.output_area.height.saturating_sub(2) as usize),
_ => {}

@@ -187,7 +255,26 @@ }

}
if self.handle_scroll_key(key) {
return Ok(());
}
if key.code == KeyCode::Esc && self.show_output {
self.show_output = false;
self.focus = Focus::Code;
if self.mode == AppMode::Home {
self.action_home()?;
} else if self.mode == AppMode::Learn {
self.show_current_syntax_lesson();
} else {
self.show_output = false;
self.focus = Focus::Code;
}
return Ok(());
}
if self.mode == AppMode::Home {
match key.code {
KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right => {
self.move_home_choice()
}
KeyCode::Enter | KeyCode::Char(' ') => self.open_home_choice()?,
_ => self.handle_global_shortcut(key)?,
}
return Ok(());
}
self.handle_global_shortcut(key)

@@ -213,2 +300,59 @@ }

fn handle_scroll_key(&mut self, key: KeyEvent) -> bool {
let Some(delta) = self.scroll_delta_for_key(key) else {
return false;
};
match self.focus {
Focus::Left => self.scroll_left(delta),
Focus::Output => self.scroll_output(delta),
_ => return false,
}
true
}
fn scroll_delta_for_key(&self, key: KeyEvent) -> Option<isize> {
let page = match self.focus {
Focus::Left => self.left_area.height.saturating_sub(2).max(1) as isize,
Focus::Output => self.output_area.height.saturating_sub(2).max(1) as isize,
_ => 1,
};
match key.code {
KeyCode::Up | KeyCode::Char('k') if key.modifiers.is_empty() => Some(-1),
KeyCode::Down | KeyCode::Char('j') if key.modifiers.is_empty() => Some(1),
KeyCode::PageUp => Some(-page),
KeyCode::PageDown => Some(page),
_ => None,
}
}
fn scroll_left(&mut self, delta: isize) {
let text = if self.mode == AppMode::Learn {
render_markdown_plain(&self.output)
} else {
render_problem_tui(&self.problem, &self.state.settings.ui_language)
};
self.left_scroll = scrolled(
self.left_scroll,
delta,
text.lines().count(),
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)
} else {
self.output.clone()
};
self.output_scroll = scrolled(
self.output_scroll,
delta,
text.lines().count(),
self.output_area,
);
}
pub(super) fn focus_command(&mut self) {

@@ -223,2 +367,10 @@ if self.command.is_empty() {

pub(super) fn resting_focus(&self) -> Focus {
if self.mode == AppMode::Home && !self.show_output {
Focus::Home
} else {
Focus::None
}
}
pub(super) fn submit_command(&mut self, value: &str) -> Result<()> {

@@ -233,1 +385,12 @@ let value = value

}
fn scrolled(current: u16, delta: isize, lines: usize, area: Rect) -> u16 {
let viewport = area.height.saturating_sub(2).max(1) as usize;
let max = lines.saturating_sub(viewport).min(u16::MAX as usize) as u16;
let next = if delta < 0 {
current.saturating_sub(delta.unsigned_abs() as u16)
} else {
current.saturating_add(delta as u16)
};
next.min(max)
}

@@ -5,2 +5,5 @@ use super::*;

pub(super) fn load_code_editor(&mut self) -> Result<()> {
if self.mode == AppMode::Learn {
return self.load_syntax_editor();
}
let path = ensure_submission(&self.root, &self.problem, &self.state.settings)?;

@@ -13,2 +16,5 @@ let text = fs::read_to_string(path).unwrap_or_default();

pub(super) fn save_code(&self) -> Result<()> {
if self.mode == AppMode::Learn {
return self.save_syntax_code();
}
let path = ensure_submission(&self.root, &self.problem, &self.state.settings)?;

@@ -19,5 +25,24 @@ fs::write(path, self.editor.text())?;

pub(super) fn start_problem_list(&mut self) {
pub(super) fn load_syntax_editor(&mut self) -> Result<()> {
let lesson = current_syntax_lesson(&self.state, &self.state.settings.language);
let path = ensure_syntax_submission(&self.root, lesson)?;
let text = fs::read_to_string(path).unwrap_or_default();
self.editor.set_text(&text);
Ok(())
}
pub(super) fn save_syntax_code(&self) -> Result<()> {
let lesson = current_syntax_lesson(&self.state, &self.state.settings.language);
let path = ensure_syntax_submission(&self.root, lesson)?;
fs::write(path, self.editor.text())?;
Ok(())
}
pub(super) fn start_problem_list(&mut self) -> Result<()> {
self.mode = AppMode::Problems;
self.state.settings.start_mode = "problems".to_string();
save_state(&self.root, &self.state)?;
self.list_cursor = Some(self.current_problem_index());
self.write_text_output(&self.render_problem_list());
Ok(())
}

@@ -101,2 +126,4 @@

self.state.current_problem = self.problem.id.clone();
self.mode = AppMode::Problems;
self.state.settings.start_mode = "problems".to_string();
if !self

@@ -103,0 +130,0 @@ .state

@@ -5,2 +5,16 @@ use super::*;

pub(super) fn status_text(&self) -> String {
if self.mode == AppMode::Home && !self.show_output {
return format!(" PRACTICODE | 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);
return format!(
" PRACTICODE | learn | {} | {} | {done}/{total} | code:{} | {} ",
syntax_language_name(&self.state.settings.language),
lesson.id,
self.state.settings.language,
self.mode_hint(),
);
}
let code_status = self.submission_status(&self.problem).0;

@@ -82,2 +96,8 @@ let activity = if self.busy_label.is_empty() {

}
if self.mode == AppMode::Home && !self.show_output {
return ui_text(lang, "home_help");
}
if self.mode == AppMode::Learn && self.focus == Focus::Code {
return ui_text(lang, "hint_learn");
}
match (self.focus, self.list_cursor.is_some(), self.show_output) {

@@ -88,3 +108,3 @@ (Focus::Command, _, _) => ui_text(lang, "hint_command"),

(_, _, true) => ui_text(lang, "hint_output"),
(Focus::Code, _, _) => ui_text(lang, "hint_code"),
(Focus::Code, _, _) => ui_text(lang, "hint_problem"),
_ => ui_text(lang, "hint_idle"),

@@ -96,12 +116,26 @@ }

let lang = &self.state.settings.ui_language;
let commands = COMMAND_HINTS
let choices = self.command_choices();
let commands = self
.default_command_inserts()
.iter()
.filter(|hint| hint.help)
.filter_map(|insert| choices.iter().find(|hint| hint.insert == *insert))
.map(|hint| format!("- `{}` {}", hint.display, ui_text(lang, hint.desc_key)))
.collect::<Vec<_>>()
.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."
}
};
format!(
"# {}\n\n## {}\n\n1. Type code in the right pane.\n2. Press `Esc`, then choose `/run` from the command palette.\n3. Use `/next` when it passes.\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- `/` 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.",
ui_text(lang, "help_title"),
ui_text(lang, "daily_loop"),
daily_loop,
ui_text(lang, "commands"),

@@ -108,0 +142,0 @@ commands,

@@ -15,6 +15,13 @@ use super::*;

let settings = self.state.settings.clone();
let mode = self.mode;
let lesson = current_syntax_lesson(&self.state, &self.state.settings.language);
let latest_result = self.learn_result.clone();
let prompt = prompt.to_string();
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let output = run_ai_prompt(&root, &problem, &settings, &prompt);
let output = if mode == AppMode::Learn {
run_ai_lesson_prompt(&root, lesson, &settings, &prompt, &latest_result)
} else {
run_ai_prompt(&root, &problem, &settings, &prompt)
};
let _ = tx.send(TaskResult::AiPrompt(output));

@@ -136,2 +143,3 @@ });

self.output = self.model_status_text();
self.output_scroll = 0;
self.output_is_markdown = false;

@@ -141,2 +149,3 @@ self.show_output = true;

self.output = self.profile_text();
self.output_scroll = 0;
self.output_is_markdown = false;

@@ -243,2 +252,3 @@ self.show_output = true;

self.output = output.to_string();
self.output_scroll = 0;
self.output_is_markdown = true;

@@ -254,2 +264,3 @@ self.show_output = true;

self.output = output.trim_end().to_string();
self.output_scroll = 0;
self.output_is_markdown = false;

@@ -262,2 +273,3 @@ self.show_output = true;

self.output = self.model_status_text();
self.output_scroll = 0;
self.output_is_markdown = false;

@@ -264,0 +276,0 @@ self.showing_model_status = true;

use super::*;
impl PracticodeApp {
pub(super) fn home_preview_text(&self) -> String {
match self.home_choice {
HomeChoice::Learn => {
let (done, total) =
syntax_progress_count(&self.state, &self.state.settings.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)
)
}
HomeChoice::Problems => {
format!(
"Practice\n\nCurrent: {}\nDifficulty: {}\nStatus: {}\n\n/run judges submissions\n/next opens the next problem",
self.problem.id,
self.problem.difficulty,
self.problem_status(&self.problem)
)
}
}
}
pub(super) fn draw(&mut self, frame: &mut Frame) {

@@ -18,4 +39,38 @@ let size = frame.area();

.split(vertical[0]);
self.code_area = if self.show_output {
if self.mode == AppMode::Home {
self.left_area = Rect::default();
self.home_area = body[0];
let choices = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(7),
Constraint::Length(7),
Constraint::Min(1),
])
.split(body[0]);
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 {

@@ -26,2 +81,6 @@ body[1]

vertical[0]
} else if self.mode == AppMode::Home {
body[1]
} else if let Some((_, result_area)) = right_panes {
result_area
} else {

@@ -34,15 +93,40 @@ self.code_area

if !self.show_output {
let problem = Paragraph::new(problem_view::render(
&self.problem,
&self.state.settings.ui_language,
light,
))
.style(Self::pane_style(light))
.block(Self::block(
ui_text(&self.state.settings.ui_language, "problem"),
light,
false,
))
.wrap(Wrap { trim: false });
frame.render_widget(problem, body[0]);
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]);
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, body[1]);
} else {
let left = if self.mode == AppMode::Learn {
Text::from(render_markdown_plain(&self.output))
} else {
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")
} else {
ui_text(&self.state.settings.ui_language, "problem")
};
let problem = Paragraph::new(left)
.style(Self::pane_style(light))
.block(Self::block(title, light, self.focus == Focus::Left))
.wrap(Wrap { trim: false })
.scroll((self.left_scroll, 0));
frame.render_widget(problem, body[0]);
}
}

@@ -70,13 +154,32 @@

))
.wrap(Wrap { trim: false });
.wrap(Wrap { trim: false })
.scroll((self.output_scroll, 0));
frame.render_widget(output, self.output_area);
} else {
} else if self.mode != AppMode::Home {
let code = self
.editor
.visible_text(body[1].height.saturating_sub(2) as usize);
let title = format!("solution.{}", ext_for(&self.state.settings.language));
.visible_text(self.code_area.height.saturating_sub(2) as usize);
let title = if self.mode == AppMode::Learn {
format!("exercise.{}", ext_for(&self.state.settings.language))
} else {
format!("solution.{}", ext_for(&self.state.settings.language))
};
let code = Paragraph::new(code)
.style(Self::pane_style(light))
.block(Self::block(&title, light, self.focus == Focus::Code));
frame.render_widget(code, body[1]);
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);
}
}

@@ -377,2 +480,5 @@

use super::*;
use crossterm::event::{
KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
};
use ratatui::{Terminal, backend::TestBackend};

@@ -388,2 +494,12 @@

fn buffer_text(terminal: &Terminal<TestBackend>) -> String {
terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect()
}
#[test]

@@ -401,2 +517,78 @@ fn output_uses_full_body_so_terminal_drag_selection_has_no_side_pane() {

}
#[test]
fn learn_result_keeps_lesson_and_splits_right_pane() {
let mut app = PracticodeApp::new(tmp_root("learn-result-split")).unwrap();
app.handle_command("learn python").unwrap();
app.handle_command("run").unwrap();
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend).unwrap();
terminal.draw(|frame| app.draw(frame)).unwrap();
assert!(app.output.contains("Syntax"));
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);
}
#[test]
fn lesson_pane_scrolls_vertically() {
let mut app = PracticodeApp::new(tmp_root("lesson-scroll")).unwrap();
app.handle_command("learn python").unwrap();
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend).unwrap();
terminal.draw(|frame| app.draw(frame)).unwrap();
assert!(buffer_text(&terminal).contains("Language: Python"));
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))
.unwrap();
terminal.draw(|frame| app.draw(frame)).unwrap();
assert!(!buffer_text(&terminal).contains("Language: Python"));
}
#[test]
fn output_pane_scrolls_vertically() {
let mut app = PracticodeApp::new(tmp_root("output-scroll")).unwrap();
app.handle_command("help").unwrap();
let backend = TestBackend::new(80, 12);
let mut terminal = Terminal::new(backend).unwrap();
terminal.draw(|frame| app.draw(frame)).unwrap();
assert!(buffer_text(&terminal).contains("Help"));
app.handle_key(KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE))
.unwrap();
terminal.draw(|frame| app.draw(frame)).unwrap();
assert!(!buffer_text(&terminal).contains("Help"));
}
#[test]
fn home_pane_title_is_active_when_home_has_focus() {
let mut app = PracticodeApp::new(tmp_root("home-active-title")).unwrap();
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend).unwrap();
terminal.draw(|frame| app.draw(frame)).unwrap();
let text = terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect::<String>();
assert!(text.contains("> Home"));
}
}
# Third-Party Licenses
This file summarizes third-party dependency license metadata for practicode
0.1.0, checked from Cargo.lock and Cargo metadata.
This file summarizes third-party dependency license metadata for the locked
practicode dependency graph, checked from Cargo.lock and Cargo metadata.

@@ -24,3 +24,3 @@ This is not legal advice.

| crossterm | 0.29.0 | MIT |
| ratatui | 0.29.0 | MIT |
| ratatui | 0.30.2 | MIT |
| serde | 1.0.228 | MIT OR Apache-2.0 |

@@ -33,17 +33,18 @@ | serde_json | 1.0.150 | MIT OR Apache-2.0 |

The locked Rust dependency graph contains 87 third-party packages.
The locked Rust dependency graph contains 94 third-party packages.
| License expression | Packages |
| --- | ---: |
| MIT OR Apache-2.0 | 50 |
| MIT | 20 |
| MIT OR Apache-2.0 | 53 |
| MIT | 23 |
| MIT/Apache-2.0 | 5 |
| Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT | 5 |
| Apache-2.0 OR MIT | 1 |
| Apache-2.0 OR MIT | 3 |
| Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT | 3 |
| (MIT OR Apache-2.0) AND Unicode-3.0 | 1 |
| Apache-2.0 | 1 |
| Apache-2.0 OR BSL-1.0 | 1 |
| Apache-2.0/MIT | 1 |
| MIT / Apache-2.0 | 1 |
| MIT OR Apache-2.0 OR CC0-1.0 | 1 |
| Unlicense OR MIT | 1 |
| Zlib | 1 |
| Apache-2.0 OR BSL-1.0 | 1 |
| (MIT OR Apache-2.0) AND Unicode-3.0 | 1 |

@@ -50,0 +51,0 @@ No GPL, LGPL, AGPL, or MPL dependencies were detected in the locked Rust