From ad5a6bcc71c5200a62e4865f814c857b039cfe0f Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Sat, 30 May 2026 15:28:40 -0400 Subject: [PATCH 1/4] Support multiple courses (closes #3) Restructure leanlings around courses so more than one progression can coexist. The exercise/solution files move under courses/intro/, and the tooling is taught the new layout. - Exercise gains a `course` field; path/solutionPath resolve to courses//{exercises,solutions}/... - New Course abstraction (Leanlings/Course.lean) with a mkCourse smart constructor that stamps the course id onto each exercise. - Config wraps the 70 exercises as the `intro` course and exposes courses/defaultCourse/getCourse. - AppState is course-aware: tracks currentCourse and namespaces completed entries as "/" so per-course progress never collides. - Main threads the active course through every command and adds `courses` (list) and `course ` (switch); reset hint now points at the real solution path. - CI verifies solutions via courses/*/solutions/*/*.lean. - README documents the course layout and new commands. --- .github/workflows/ci.yml | 2 +- Leanlings.lean | 1 + Leanlings/Config.lean | 26 ++- Leanlings/Course.lean | 37 +++++ Leanlings/Exercise.lean | 6 +- Leanlings/State.lean | 99 +++++++---- Main.lean | 156 +++++++++++------- README.md | 21 ++- .../intro/exercises}/00_intro/intro1.lean | 0 .../intro/exercises}/00_intro/intro2.lean | 0 .../intro/exercises}/00_intro/intro3.lean | 0 .../intro/exercises}/01_basics/basics1.lean | 0 .../intro/exercises}/01_basics/basics2.lean | 0 .../intro/exercises}/01_basics/basics3.lean | 0 .../intro/exercises}/01_basics/basics4.lean | 0 .../exercises}/02_definitions/defs1.lean | 0 .../exercises}/02_definitions/defs2.lean | 0 .../exercises}/02_definitions/defs3.lean | 0 .../exercises}/03_functions/functions1.lean | 0 .../exercises}/03_functions/functions2.lean | 0 .../exercises}/03_functions/functions3.lean | 0 .../exercises}/03_functions/functions4.lean | 0 .../intro/exercises}/04_control_flow/if1.lean | 0 .../intro/exercises}/04_control_flow/if2.lean | 0 .../exercises}/04_control_flow/match1.lean | 0 .../exercises}/05_structures/structs1.lean | 0 .../exercises}/05_structures/structs2.lean | 0 .../exercises}/05_structures/structs3.lean | 0 .../exercises}/06_inductive/inductive1.lean | 0 .../exercises}/06_inductive/inductive2.lean | 0 .../exercises}/06_inductive/inductive3.lean | 0 .../exercises}/07_recursion/recursion1.lean | 0 .../exercises}/07_recursion/recursion2.lean | 0 .../exercises}/07_recursion/recursion3.lean | 0 .../exercises}/07_recursion/recursion4.lean | 0 .../exercises}/08_proving_code/proving1.lean | 0 .../exercises}/08_proving_code/proving2.lean | 0 .../exercises}/08_proving_code/proving3.lean | 0 .../exercises}/09_propositions/props1.lean | 0 .../exercises}/09_propositions/props2.lean | 0 .../exercises}/09_propositions/props3.lean | 0 .../intro/exercises}/10_tactics/tactics1.lean | 0 .../intro/exercises}/10_tactics/tactics2.lean | 0 .../intro/exercises}/10_tactics/tactics3.lean | 0 .../intro/exercises}/10_tactics/tactics4.lean | 0 .../exercises}/11_induction/induction1.lean | 0 .../exercises}/11_induction/induction2.lean | 0 .../12_typeclasses/typeclasses1.lean | 0 .../12_typeclasses/typeclasses2.lean | 0 .../intro/exercises}/13_quiz/quiz1.lean | 0 .../intro/exercises}/14_do_notation/do1.lean | 0 .../intro/exercises}/14_do_notation/do2.lean | 0 .../intro/exercises}/14_do_notation/do3.lean | 0 .../intro/exercises}/15_io/io1.lean | 0 .../intro/exercises}/15_io/io2.lean | 0 .../exercises}/16_implicit/implicit1.lean | 0 .../exercises}/16_implicit/implicit2.lean | 0 .../intro/exercises}/17_arrays/arrays1.lean | 0 .../intro/exercises}/17_arrays/arrays2.lean | 0 .../intro/exercises}/18_namespaces/ns1.lean | 0 .../intro/exercises}/18_namespaces/ns2.lean | 0 .../intro/exercises}/19_quiz2/quiz2.lean | 0 .../intro/exercises}/20_exists/exists1.lean | 0 .../intro/exercises}/20_exists/exists2.lean | 0 .../exercises}/21_cases_have/cases1.lean | 0 .../exercises}/21_cases_have/cases2.lean | 0 .../intro/exercises}/21_cases_have/have1.lean | 0 .../intro/exercises}/22_calc/calc1.lean | 0 .../intro/exercises}/22_calc/calc2.lean | 0 .../exercises}/23_classical/classical1.lean | 0 .../exercises}/23_classical/classical2.lean | 0 .../intro/exercises}/24_nat_proofs/nat1.lean | 0 .../intro/exercises}/24_nat_proofs/nat2.lean | 0 .../intro/exercises}/24_nat_proofs/nat3.lean | 0 .../exercises}/25_list_proofs/list1.lean | 0 .../exercises}/25_list_proofs/list2.lean | 0 .../exercises}/25_list_proofs/list3.lean | 0 .../intro/exercises}/26_final_quiz/quiz3.lean | 0 .../intro/solutions}/00_intro/intro1.lean | 0 .../intro/solutions}/00_intro/intro2.lean | 0 .../intro/solutions}/00_intro/intro3.lean | 0 .../intro/solutions}/01_basics/basics1.lean | 0 .../intro/solutions}/01_basics/basics2.lean | 0 .../intro/solutions}/01_basics/basics3.lean | 0 .../intro/solutions}/01_basics/basics4.lean | 0 .../solutions}/02_definitions/defs1.lean | 0 .../solutions}/02_definitions/defs2.lean | 0 .../solutions}/02_definitions/defs3.lean | 0 .../solutions}/03_functions/functions1.lean | 0 .../solutions}/03_functions/functions2.lean | 0 .../solutions}/03_functions/functions3.lean | 0 .../solutions}/03_functions/functions4.lean | 0 .../intro/solutions}/04_control_flow/if1.lean | 0 .../intro/solutions}/04_control_flow/if2.lean | 0 .../solutions}/04_control_flow/match1.lean | 0 .../solutions}/05_structures/structs1.lean | 0 .../solutions}/05_structures/structs2.lean | 0 .../solutions}/05_structures/structs3.lean | 0 .../solutions}/06_inductive/inductive1.lean | 0 .../solutions}/06_inductive/inductive2.lean | 0 .../solutions}/06_inductive/inductive3.lean | 0 .../solutions}/07_recursion/recursion1.lean | 0 .../solutions}/07_recursion/recursion2.lean | 0 .../solutions}/07_recursion/recursion3.lean | 0 .../solutions}/07_recursion/recursion4.lean | 0 .../solutions}/08_proving_code/proving1.lean | 0 .../solutions}/08_proving_code/proving2.lean | 0 .../solutions}/08_proving_code/proving3.lean | 0 .../solutions}/09_propositions/props1.lean | 0 .../solutions}/09_propositions/props2.lean | 0 .../solutions}/09_propositions/props3.lean | 0 .../intro/solutions}/10_tactics/tactics1.lean | 0 .../intro/solutions}/10_tactics/tactics2.lean | 0 .../intro/solutions}/10_tactics/tactics3.lean | 0 .../intro/solutions}/10_tactics/tactics4.lean | 0 .../solutions}/11_induction/induction1.lean | 0 .../solutions}/11_induction/induction2.lean | 0 .../12_typeclasses/typeclasses1.lean | 0 .../12_typeclasses/typeclasses2.lean | 0 .../intro/solutions}/13_quiz/quiz1.lean | 0 .../intro/solutions}/14_do_notation/do1.lean | 0 .../intro/solutions}/14_do_notation/do2.lean | 0 .../intro/solutions}/14_do_notation/do3.lean | 0 .../intro/solutions}/15_io/io1.lean | 0 .../intro/solutions}/15_io/io2.lean | 0 .../solutions}/16_implicit/implicit1.lean | 0 .../solutions}/16_implicit/implicit2.lean | 0 .../intro/solutions}/17_arrays/arrays1.lean | 0 .../intro/solutions}/17_arrays/arrays2.lean | 0 .../intro/solutions}/18_namespaces/ns1.lean | 0 .../intro/solutions}/18_namespaces/ns2.lean | 0 .../intro/solutions}/19_quiz2/quiz2.lean | 0 .../intro/solutions}/20_exists/exists1.lean | 0 .../intro/solutions}/20_exists/exists2.lean | 0 .../solutions}/21_cases_have/cases1.lean | 0 .../solutions}/21_cases_have/cases2.lean | 0 .../intro/solutions}/21_cases_have/have1.lean | 0 .../intro/solutions}/22_calc/calc1.lean | 0 .../intro/solutions}/22_calc/calc2.lean | 0 .../solutions}/23_classical/classical1.lean | 0 .../solutions}/23_classical/classical2.lean | 0 .../intro/solutions}/24_nat_proofs/nat1.lean | 0 .../intro/solutions}/24_nat_proofs/nat2.lean | 0 .../intro/solutions}/24_nat_proofs/nat3.lean | 0 .../solutions}/25_list_proofs/list1.lean | 0 .../solutions}/25_list_proofs/list2.lean | 0 .../solutions}/25_list_proofs/list3.lean | 0 .../intro/solutions}/26_final_quiz/quiz3.lean | 0 148 files changed, 245 insertions(+), 103 deletions(-) create mode 100644 Leanlings/Course.lean rename {exercises => courses/intro/exercises}/00_intro/intro1.lean (100%) rename {exercises => courses/intro/exercises}/00_intro/intro2.lean (100%) rename {exercises => courses/intro/exercises}/00_intro/intro3.lean (100%) rename {exercises => courses/intro/exercises}/01_basics/basics1.lean (100%) rename {exercises => courses/intro/exercises}/01_basics/basics2.lean (100%) rename {exercises => courses/intro/exercises}/01_basics/basics3.lean (100%) rename {exercises => courses/intro/exercises}/01_basics/basics4.lean (100%) rename {exercises => courses/intro/exercises}/02_definitions/defs1.lean (100%) rename {exercises => courses/intro/exercises}/02_definitions/defs2.lean (100%) rename {exercises => courses/intro/exercises}/02_definitions/defs3.lean (100%) rename {exercises => courses/intro/exercises}/03_functions/functions1.lean (100%) rename {exercises => courses/intro/exercises}/03_functions/functions2.lean (100%) rename {exercises => courses/intro/exercises}/03_functions/functions3.lean (100%) rename {exercises => courses/intro/exercises}/03_functions/functions4.lean (100%) rename {exercises => courses/intro/exercises}/04_control_flow/if1.lean (100%) rename {exercises => courses/intro/exercises}/04_control_flow/if2.lean (100%) rename {exercises => courses/intro/exercises}/04_control_flow/match1.lean (100%) rename {exercises => courses/intro/exercises}/05_structures/structs1.lean (100%) rename {exercises => courses/intro/exercises}/05_structures/structs2.lean (100%) rename {exercises => courses/intro/exercises}/05_structures/structs3.lean (100%) rename {exercises => courses/intro/exercises}/06_inductive/inductive1.lean (100%) rename {exercises => courses/intro/exercises}/06_inductive/inductive2.lean (100%) rename {exercises => courses/intro/exercises}/06_inductive/inductive3.lean (100%) rename {exercises => courses/intro/exercises}/07_recursion/recursion1.lean (100%) rename {exercises => courses/intro/exercises}/07_recursion/recursion2.lean (100%) rename {exercises => courses/intro/exercises}/07_recursion/recursion3.lean (100%) rename {exercises => courses/intro/exercises}/07_recursion/recursion4.lean (100%) rename {exercises => courses/intro/exercises}/08_proving_code/proving1.lean (100%) rename {exercises => courses/intro/exercises}/08_proving_code/proving2.lean (100%) rename {exercises => courses/intro/exercises}/08_proving_code/proving3.lean (100%) rename {exercises => courses/intro/exercises}/09_propositions/props1.lean (100%) rename {exercises => courses/intro/exercises}/09_propositions/props2.lean (100%) rename {exercises => courses/intro/exercises}/09_propositions/props3.lean (100%) rename {exercises => courses/intro/exercises}/10_tactics/tactics1.lean (100%) rename {exercises => courses/intro/exercises}/10_tactics/tactics2.lean (100%) rename {exercises => courses/intro/exercises}/10_tactics/tactics3.lean (100%) rename {exercises => courses/intro/exercises}/10_tactics/tactics4.lean (100%) rename {exercises => courses/intro/exercises}/11_induction/induction1.lean (100%) rename {exercises => courses/intro/exercises}/11_induction/induction2.lean (100%) rename {exercises => courses/intro/exercises}/12_typeclasses/typeclasses1.lean (100%) rename {exercises => courses/intro/exercises}/12_typeclasses/typeclasses2.lean (100%) rename {exercises => courses/intro/exercises}/13_quiz/quiz1.lean (100%) rename {exercises => courses/intro/exercises}/14_do_notation/do1.lean (100%) rename {exercises => courses/intro/exercises}/14_do_notation/do2.lean (100%) rename {exercises => courses/intro/exercises}/14_do_notation/do3.lean (100%) rename {exercises => courses/intro/exercises}/15_io/io1.lean (100%) rename {exercises => courses/intro/exercises}/15_io/io2.lean (100%) rename {exercises => courses/intro/exercises}/16_implicit/implicit1.lean (100%) rename {exercises => courses/intro/exercises}/16_implicit/implicit2.lean (100%) rename {exercises => courses/intro/exercises}/17_arrays/arrays1.lean (100%) rename {exercises => courses/intro/exercises}/17_arrays/arrays2.lean (100%) rename {exercises => courses/intro/exercises}/18_namespaces/ns1.lean (100%) rename {exercises => courses/intro/exercises}/18_namespaces/ns2.lean (100%) rename {exercises => courses/intro/exercises}/19_quiz2/quiz2.lean (100%) rename {exercises => courses/intro/exercises}/20_exists/exists1.lean (100%) rename {exercises => courses/intro/exercises}/20_exists/exists2.lean (100%) rename {exercises => courses/intro/exercises}/21_cases_have/cases1.lean (100%) rename {exercises => courses/intro/exercises}/21_cases_have/cases2.lean (100%) rename {exercises => courses/intro/exercises}/21_cases_have/have1.lean (100%) rename {exercises => courses/intro/exercises}/22_calc/calc1.lean (100%) rename {exercises => courses/intro/exercises}/22_calc/calc2.lean (100%) rename {exercises => courses/intro/exercises}/23_classical/classical1.lean (100%) rename {exercises => courses/intro/exercises}/23_classical/classical2.lean (100%) rename {exercises => courses/intro/exercises}/24_nat_proofs/nat1.lean (100%) rename {exercises => courses/intro/exercises}/24_nat_proofs/nat2.lean (100%) rename {exercises => courses/intro/exercises}/24_nat_proofs/nat3.lean (100%) rename {exercises => courses/intro/exercises}/25_list_proofs/list1.lean (100%) rename {exercises => courses/intro/exercises}/25_list_proofs/list2.lean (100%) rename {exercises => courses/intro/exercises}/25_list_proofs/list3.lean (100%) rename {exercises => courses/intro/exercises}/26_final_quiz/quiz3.lean (100%) rename {.solutions => courses/intro/solutions}/00_intro/intro1.lean (100%) rename {.solutions => courses/intro/solutions}/00_intro/intro2.lean (100%) rename {.solutions => courses/intro/solutions}/00_intro/intro3.lean (100%) rename {.solutions => courses/intro/solutions}/01_basics/basics1.lean (100%) rename {.solutions => courses/intro/solutions}/01_basics/basics2.lean (100%) rename {.solutions => courses/intro/solutions}/01_basics/basics3.lean (100%) rename {.solutions => courses/intro/solutions}/01_basics/basics4.lean (100%) rename {.solutions => courses/intro/solutions}/02_definitions/defs1.lean (100%) rename {.solutions => courses/intro/solutions}/02_definitions/defs2.lean (100%) rename {.solutions => courses/intro/solutions}/02_definitions/defs3.lean (100%) rename {.solutions => courses/intro/solutions}/03_functions/functions1.lean (100%) rename {.solutions => courses/intro/solutions}/03_functions/functions2.lean (100%) rename {.solutions => courses/intro/solutions}/03_functions/functions3.lean (100%) rename {.solutions => courses/intro/solutions}/03_functions/functions4.lean (100%) rename {.solutions => courses/intro/solutions}/04_control_flow/if1.lean (100%) rename {.solutions => courses/intro/solutions}/04_control_flow/if2.lean (100%) rename {.solutions => courses/intro/solutions}/04_control_flow/match1.lean (100%) rename {.solutions => courses/intro/solutions}/05_structures/structs1.lean (100%) rename {.solutions => courses/intro/solutions}/05_structures/structs2.lean (100%) rename {.solutions => courses/intro/solutions}/05_structures/structs3.lean (100%) rename {.solutions => courses/intro/solutions}/06_inductive/inductive1.lean (100%) rename {.solutions => courses/intro/solutions}/06_inductive/inductive2.lean (100%) rename {.solutions => courses/intro/solutions}/06_inductive/inductive3.lean (100%) rename {.solutions => courses/intro/solutions}/07_recursion/recursion1.lean (100%) rename {.solutions => courses/intro/solutions}/07_recursion/recursion2.lean (100%) rename {.solutions => courses/intro/solutions}/07_recursion/recursion3.lean (100%) rename {.solutions => courses/intro/solutions}/07_recursion/recursion4.lean (100%) rename {.solutions => courses/intro/solutions}/08_proving_code/proving1.lean (100%) rename {.solutions => courses/intro/solutions}/08_proving_code/proving2.lean (100%) rename {.solutions => courses/intro/solutions}/08_proving_code/proving3.lean (100%) rename {.solutions => courses/intro/solutions}/09_propositions/props1.lean (100%) rename {.solutions => courses/intro/solutions}/09_propositions/props2.lean (100%) rename {.solutions => courses/intro/solutions}/09_propositions/props3.lean (100%) rename {.solutions => courses/intro/solutions}/10_tactics/tactics1.lean (100%) rename {.solutions => courses/intro/solutions}/10_tactics/tactics2.lean (100%) rename {.solutions => courses/intro/solutions}/10_tactics/tactics3.lean (100%) rename {.solutions => courses/intro/solutions}/10_tactics/tactics4.lean (100%) rename {.solutions => courses/intro/solutions}/11_induction/induction1.lean (100%) rename {.solutions => courses/intro/solutions}/11_induction/induction2.lean (100%) rename {.solutions => courses/intro/solutions}/12_typeclasses/typeclasses1.lean (100%) rename {.solutions => courses/intro/solutions}/12_typeclasses/typeclasses2.lean (100%) rename {.solutions => courses/intro/solutions}/13_quiz/quiz1.lean (100%) rename {.solutions => courses/intro/solutions}/14_do_notation/do1.lean (100%) rename {.solutions => courses/intro/solutions}/14_do_notation/do2.lean (100%) rename {.solutions => courses/intro/solutions}/14_do_notation/do3.lean (100%) rename {.solutions => courses/intro/solutions}/15_io/io1.lean (100%) rename {.solutions => courses/intro/solutions}/15_io/io2.lean (100%) rename {.solutions => courses/intro/solutions}/16_implicit/implicit1.lean (100%) rename {.solutions => courses/intro/solutions}/16_implicit/implicit2.lean (100%) rename {.solutions => courses/intro/solutions}/17_arrays/arrays1.lean (100%) rename {.solutions => courses/intro/solutions}/17_arrays/arrays2.lean (100%) rename {.solutions => courses/intro/solutions}/18_namespaces/ns1.lean (100%) rename {.solutions => courses/intro/solutions}/18_namespaces/ns2.lean (100%) rename {.solutions => courses/intro/solutions}/19_quiz2/quiz2.lean (100%) rename {.solutions => courses/intro/solutions}/20_exists/exists1.lean (100%) rename {.solutions => courses/intro/solutions}/20_exists/exists2.lean (100%) rename {.solutions => courses/intro/solutions}/21_cases_have/cases1.lean (100%) rename {.solutions => courses/intro/solutions}/21_cases_have/cases2.lean (100%) rename {.solutions => courses/intro/solutions}/21_cases_have/have1.lean (100%) rename {.solutions => courses/intro/solutions}/22_calc/calc1.lean (100%) rename {.solutions => courses/intro/solutions}/22_calc/calc2.lean (100%) rename {.solutions => courses/intro/solutions}/23_classical/classical1.lean (100%) rename {.solutions => courses/intro/solutions}/23_classical/classical2.lean (100%) rename {.solutions => courses/intro/solutions}/24_nat_proofs/nat1.lean (100%) rename {.solutions => courses/intro/solutions}/24_nat_proofs/nat2.lean (100%) rename {.solutions => courses/intro/solutions}/24_nat_proofs/nat3.lean (100%) rename {.solutions => courses/intro/solutions}/25_list_proofs/list1.lean (100%) rename {.solutions => courses/intro/solutions}/25_list_proofs/list2.lean (100%) rename {.solutions => courses/intro/solutions}/25_list_proofs/list3.lean (100%) rename {.solutions => courses/intro/solutions}/26_final_quiz/quiz3.lean (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d28bc55..ba8649c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - name: Verify all solutions run: | failed=0 - for f in .solutions/**/*.lean; do + for f in $(find courses -path '*/solutions/*/*.lean' | sort); do if ! lean "$f" 2>&1; then echo "FAIL: $f" failed=$((failed + 1)) diff --git a/Leanlings.lean b/Leanlings.lean index 9118a66..5d434f4 100644 --- a/Leanlings.lean +++ b/Leanlings.lean @@ -1,4 +1,5 @@ import Leanlings.Exercise +import Leanlings.Course import Leanlings.UI import Leanlings.Runner import Leanlings.State diff --git a/Leanlings/Config.lean b/Leanlings/Config.lean index 1920147..3a910ee 100644 --- a/Leanlings/Config.lean +++ b/Leanlings/Config.lean @@ -1,8 +1,9 @@ import Leanlings.Exercise +import Leanlings.Course namespace Leanlings.Config -def exercises : Array Exercise := #[ +private def introExercises : Array Exercise := #[ -- 00_intro { name := "intro1", dir := "00_intro", hint := "In Lean, strings are written between double quotes, like \"hello\".\nWhat string does the #guard check for?" }, @@ -198,10 +199,7 @@ def exercises : Array Exercise := #[ hint := "For functions: recurse on `.leaf` and `.node l v r`.\nFor induction proofs: `induction t` then `simp [f, g, ...]`.\nFor the existential: provide a `⟨witness, proof⟩` pair." } ] -def getExercise (name : String) : Option Exercise := - exercises.find? (·.name == name) - -def welcomeMessage : String := +private def introWelcome : String := "Welcome to Leanlings!\n\n" ++ "Leanlings will teach you Lean 4 through small exercises.\n\n" ++ "Here's how it works:\n" ++ @@ -211,7 +209,7 @@ def welcomeMessage : String := "4. Run `lake exe leanlings next` to advance\n\n" ++ "Or use `lake exe leanlings watch` for auto-checking!\n" -def finalMessage : String := +private def introFinal : String := "Congratulations! You've completed all Leanlings exercises!\n\n" ++ "You now have a solid foundation in Lean 4, including:\n" ++ " - Basic types, definitions, and functions\n" ++ @@ -233,4 +231,20 @@ def finalMessage : String := " - Mathematics in Lean: https://leanprover-community.github.io/mathematics_in_lean/\n" ++ " - Mathlib (Lean's math library): https://leanprover-community.github.io/mathlib4_docs/\n" +/-- The introductory course: programming and proof fundamentals in Lean 4. -/ +def intro : Course := + mkCourse "intro" "Introduction to Lean 4" + "Programming and theorem proving fundamentals — 70 exercises across 27 units." + introExercises (welcome := introWelcome) (final := introFinal) + +/-- All available courses, in display order. -/ +def courses : Array Course := #[intro] + +/-- The course used when none is selected or a stored selection is invalid. -/ +def defaultCourse : Course := intro + +/-- Find a course by its id. -/ +def getCourse (id : String) : Option Course := + courses.find? (·.id == id) + end Leanlings.Config diff --git a/Leanlings/Course.lean b/Leanlings/Course.lean new file mode 100644 index 0000000..a40c3be --- /dev/null +++ b/Leanlings/Course.lean @@ -0,0 +1,37 @@ +import Leanlings.Exercise + +namespace Leanlings + +/-- A single course: a named, ordered collection of exercises that lives under +`courses//`. Multiple courses can coexist; the active one is tracked in +`AppState`. -/ +structure Course where + /-- Slug used both as the `courses//` directory name and the CLI selector. -/ + id : String + /-- Human-readable title shown in listings. -/ + title : String + /-- One-line summary shown in `courses`. -/ + description : String + exercises : Array Exercise + /-- Shown when starting a course (progress at 0). -/ + welcome : String := "" + /-- Shown when every exercise in the course is complete. -/ + final : String := "" + deriving Inhabited + +/-- Build a course, stamping its `id` onto every exercise so each exercise can +resolve its own file path without the caller threading the course through. -/ +def mkCourse (id title description : String) (exercises : Array Exercise) + (welcome : String := "") (final : String := "") : Course := + { id, title, description, + exercises := exercises.map (fun e => { e with course := id }), + welcome, final } + +namespace Course + +/-- Find an exercise within this course by name. -/ +def getExercise (c : Course) (name : String) : Option Exercise := + c.exercises.find? (·.name == name) + +end Course +end Leanlings diff --git a/Leanlings/Exercise.lean b/Leanlings/Exercise.lean index 08857da..0e42a03 100644 --- a/Leanlings/Exercise.lean +++ b/Leanlings/Exercise.lean @@ -11,16 +11,18 @@ inductive ExerciseStatus where structure Exercise where name : String := "" dir : String := "" + /-- The id of the course this exercise belongs to. Stamped by `mkCourse`. -/ + course : String := "" hint : String := "" deriving Repr, BEq, Inhabited namespace Exercise def path (e : Exercise) : System.FilePath := - s!"exercises/{e.dir}/{e.name}.lean" + s!"courses/{e.course}/exercises/{e.dir}/{e.name}.lean" def solutionPath (e : Exercise) : System.FilePath := - s!".solutions/{e.dir}/{e.name}.lean" + s!"courses/{e.course}/solutions/{e.dir}/{e.name}.lean" end Exercise end Leanlings diff --git a/Leanlings/State.lean b/Leanlings/State.lean index db642af..c7fb07e 100644 --- a/Leanlings/State.lean +++ b/Leanlings/State.lean @@ -1,9 +1,15 @@ import Leanlings.Exercise +import Leanlings.Course namespace Leanlings -/-- Persistent state tracking exercise progress -/ +/-- Persistent state tracking progress across courses. + +`completed` entries are namespaced as `"/"` so progress +in one course never collides with another. `currentExercise` is a bare name +within `currentCourse`. -/ structure AppState where + currentCourse : String currentExercise : String completed : Array String deriving Repr @@ -12,61 +18,82 @@ namespace AppState def stateFile : System.FilePath := ".leanlings-state" -def initial (exercises : Array Exercise) : AppState := - { currentExercise := (exercises.getD 0 default).name, completed := #[] } +/-- The key under which an exercise's completion is recorded. -/ +def key (courseId name : String) : String := s!"{courseId}/{name}" + +def initial (course : Course) : AppState := + { currentCourse := course.id, + currentExercise := (course.exercises.getD 0 default).name, + completed := #[] } -/-- Load state from disk, or return initial state if no state file exists -/ -def load (exercises : Array Exercise) : IO AppState := do +/-- Load state from disk, or return initial state if no (valid) state file exists. + +A stored course or exercise that no longer exists is repaired to a sensible +default rather than failing. -/ +def load (courses : Array Course) (defaultCourse : Course) : IO AppState := do try let content ← IO.FS.readFile stateFile let lines := (content.splitOn "\n").filter (· != "") match lines with - | current :: rest => + | course :: current :: rest => let completed := (rest.filter (· != "---")).toArray - let validCompleted := completed.filter (fun name => exercises.any (·.name == name)) - let validCurrent := if exercises.any (·.name == current) then current - else match exercises.findSome? (fun ex => if !validCompleted.contains ex.name then some ex.name else none) with - | some name => name - | none => (exercises.getD 0 default).name - pure { currentExercise := validCurrent, completed := validCompleted } - | [] => pure (initial exercises) + let validCourse := if courses.any (·.id == course) then course else defaultCourse.id + let activeCourse := (courses.find? (·.id == validCourse)).getD defaultCourse + let validCurrent := + if activeCourse.exercises.any (·.name == current) then current + else match activeCourse.exercises.findSome? (fun ex => + if !completed.contains (key validCourse ex.name) then some ex.name else none) with + | some name => name + | none => (activeCourse.exercises.getD 0 default).name + pure { currentCourse := validCourse, currentExercise := validCurrent, completed } + | _ => pure (initial defaultCourse) catch _ => - pure (initial exercises) + pure (initial defaultCourse) /-- Save state to disk -/ def save (state : AppState) : IO Unit := do let completedStr := state.completed.foldl (fun acc s => acc ++ s ++ "\n") "" - let content := s!"{state.currentExercise}\n---\n{completedStr}" + let content := s!"{state.currentCourse}\n{state.currentExercise}\n---\n{completedStr}" IO.FS.writeFile stateFile content -/-- Check if an exercise has been completed -/ -def isCompleted (state : AppState) (name : String) : Bool := - state.completed.contains name +/-- Check if an exercise in the given course has been completed -/ +def isCompleted (state : AppState) (courseId name : String) : Bool := + state.completed.contains (key courseId name) + +/-- Mark an exercise in the given course as completed -/ +def markCompleted (state : AppState) (courseId name : String) : AppState := + let k := key courseId name + if state.completed.contains k then state + else { state with completed := state.completed.push k } -/-- Mark an exercise as completed -/ -def markCompleted (state : AppState) (name : String) : AppState := - if state.completed.contains name then state - else { state with completed := state.completed.push name } +/-- Switch the active course, moving to its first pending exercise. -/ +def switchCourse (state : AppState) (course : Course) : AppState := + let current := match course.exercises.findSome? (fun ex => + if !state.completed.contains (key course.id ex.name) then some ex.name else none) with + | some name => name + | none => (course.exercises.getD 0 default).name + { state with currentCourse := course.id, currentExercise := current } -/-- Find the next pending exercise -/ -def findNextPending (state : AppState) (exercises : Array Exercise) : Option String := - exercises.findSome? fun ex => - if !state.completed.contains ex.name then some ex.name else none +/-- Find the next pending exercise in the given course -/ +def findNextPending (state : AppState) (course : Course) : Option String := + course.exercises.findSome? fun ex => + if !state.completed.contains (key course.id ex.name) then some ex.name else none -/-- Mark current exercise done and advance to next pending -/ -def advance (state : AppState) (exercises : Array Exercise) : AppState := - let state := state.markCompleted state.currentExercise - match state.findNextPending exercises with +/-- Mark current exercise done and advance to next pending in the course -/ +def advance (state : AppState) (course : Course) : AppState := + let state := state.markCompleted course.id state.currentExercise + match state.findNextPending course with | some next => { state with currentExercise := next } | none => state -/-- Count completed exercises -/ -def countDone (state : AppState) : Nat := - state.completed.size +/-- Count completed exercises in the given course -/ +def countDone (state : AppState) (course : Course) : Nat := + course.exercises.foldl (fun n ex => + if state.completed.contains (key course.id ex.name) then n + 1 else n) 0 -/-- Check if all exercises are done -/ -def allDone (state : AppState) (exercises : Array Exercise) : Bool := - exercises.all (fun ex => state.completed.contains ex.name) +/-- Check if all exercises in the given course are done -/ +def allDone (state : AppState) (course : Course) : Bool := + course.exercises.all (fun ex => state.completed.contains (key course.id ex.name)) end AppState end Leanlings diff --git a/Main.lean b/Main.lean index 62fab0d..6535613 100644 --- a/Main.lean +++ b/Main.lean @@ -2,6 +2,13 @@ import Leanlings open Leanlings +/-- Load progress state together with the active course, repairing a stale or +missing course selection to the default. Every command starts here. -/ +def loadActive : IO (AppState × Course) := do + let state ← AppState.load Config.courses Config.defaultCourse + let course := (Config.getCourse state.currentCourse).getD Config.defaultCourse + return (state, course) + def showHelp : IO Unit := do IO.println "Usage: lake exe leanlings [command]\n" IO.println "Commands:" @@ -9,28 +16,31 @@ def showHelp : IO Unit := do IO.println " run Check the current exercise" IO.println " run Check a specific exercise" IO.println " watch Watch mode — auto-check on file changes" - IO.println " verify Check all exercises" + IO.println " verify Check all exercises in the current course" IO.println " hint Show a hint for the current exercise" IO.println " solution Show the solution for the current exercise" - IO.println " list List all exercises with status" + IO.println " list List exercises in the current course with status" IO.println " next Move to the next exercise" IO.println " reset Reset the current exercise (requires git)" IO.println " reset Reset a specific exercise" + IO.println " courses List available courses" + IO.println " course Switch to another course" IO.println " help Show this help" def showStatus : IO Unit := do - let state ← AppState.load Config.exercises - let done := state.countDone - let total := Config.exercises.size + let (state, course) ← loadActive + let done := state.countDone course + let total := course.exercises.size IO.println s!"\n{UI.bold "Leanlings"} — Learn Lean 4 one exercise at a time\n" + IO.println s!"{UI.dim "Course"}: {UI.bold course.title}" IO.println (UI.progressBar done total) IO.println "" if done == 0 then - IO.println Config.welcomeMessage - if state.allDone Config.exercises then - IO.println Config.finalMessage + IO.println course.welcome + if state.allDone course then + IO.println course.final else - match Config.getExercise state.currentExercise with + match course.getExercise state.currentExercise with | some ex => IO.println s!"Current exercise: {UI.bold ex.name}" IO.println s!" → {ex.path}" @@ -41,31 +51,31 @@ def showStatus : IO Unit := do IO.println s!"Error: exercise '{state.currentExercise}' not found" def runExercise (name : String) : IO UInt32 := do - match Config.getExercise name with + let (state, course) ← loadActive + match course.getExercise name with | some ex => IO.println s!"Checking {UI.bold ex.name}...\n" let status ← Runner.checkExercise ex Runner.displayResult ex status match status with | .success => - let state ← AppState.load Config.exercises - let state := state.markCompleted name + let state := state.markCompleted course.id name state.save - IO.println s!"\n{UI.progressBar state.countDone Config.exercises.size}" + IO.println s!"\n{UI.progressBar (state.countDone course) course.exercises.size}" IO.println s!"\nRun `lake exe leanlings next` to continue." return 0 | _ => return 1 | none => - IO.println s!"{UI.red "Error"}: exercise '{name}' not found" + IO.println s!"{UI.red "Error"}: exercise '{name}' not found in course '{course.id}'" return 1 def runCurrent : IO UInt32 := do - let state ← AppState.load Config.exercises + let (state, _) ← loadActive runExercise state.currentExercise def showHint : IO Unit := do - let state ← AppState.load Config.exercises - match Config.getExercise state.currentExercise with + let (state, course) ← loadActive + match course.getExercise state.currentExercise with | some ex => IO.println s!"\n{UI.cyan "Hint"} for {UI.bold ex.name}:\n" IO.println ex.hint @@ -73,26 +83,26 @@ def showHint : IO Unit := do IO.println "Error: current exercise not found" def listExercises : IO Unit := do - let state ← AppState.load Config.exercises - IO.println s!"\n{UI.bold "Exercises"}:\n" - for ex in Config.exercises do - let marker := if state.isCompleted ex.name then UI.green "✓" + let (state, course) ← loadActive + IO.println s!"\n{UI.bold "Exercises"} — {course.title}:\n" + for ex in course.exercises do + let marker := if state.isCompleted course.id ex.name then UI.green "✓" else if state.currentExercise == ex.name then UI.yellow "→" else " " IO.println s!" {marker} {ex.name}" IO.println "" - IO.println (UI.progressBar state.countDone Config.exercises.size) + IO.println (UI.progressBar (state.countDone course) course.exercises.size) def nextExercise : IO Unit := do - let state ← AppState.load Config.exercises - if !state.isCompleted state.currentExercise then + let (state, course) ← loadActive + if !state.isCompleted course.id state.currentExercise then IO.println s!"{UI.yellow "!"} Warning: current exercise '{state.currentExercise}' is not yet completed." IO.println " Skipping to next exercise anyway." - let idx := Config.exercises.findIdx? (·.name == state.currentExercise) + let idx := course.exercises.findIdx? (·.name == state.currentExercise) match idx with | some i => - if i + 1 < Config.exercises.size then - let next := Config.exercises[i + 1]! + if i + 1 < course.exercises.size then + let next := course.exercises[i + 1]! let state := { state with currentExercise := next.name } state.save IO.println s!"Current exercise: {UI.bold next.name}" @@ -103,7 +113,8 @@ def nextExercise : IO Unit := do IO.println "Error: could not find current exercise" def resetExercise (name : String) : IO UInt32 := do - match Config.getExercise name with + let (_, course) ← loadActive + match course.getExercise name with | some ex => let result ← IO.Process.output { cmd := "git" @@ -114,73 +125,76 @@ def resetExercise (name : String) : IO UInt32 := do return 0 else IO.println s!"{UI.red "Error"}: Could not reset. Make sure you're in a git repository." - IO.println s!" You can manually look at .solutions/{ex.dir}/{ex.name}.lean" + IO.println s!" You can manually look at {ex.solutionPath}" return 1 | none => - IO.println s!"{UI.red "Error"}: exercise '{name}' not found" + IO.println s!"{UI.red "Error"}: exercise '{name}' not found in course '{course.id}'" return 1 def verifyAll : IO UInt32 := do - IO.println s!"\n{UI.bold "Verifying all exercises"}...\n" + let (state, course) ← loadActive + IO.println s!"\n{UI.bold "Verifying all exercises"} — {course.title}...\n" + -- Recompute this course's completion from scratch, preserving other courses. + let others := state.completed.filter (fun k => !k.startsWith s!"{course.id}/") + let mut state := { state with completed := others } let mut doneCount : Nat := 0 - let mut state := { (← AppState.load Config.exercises) with completed := #[] } - for ex in Config.exercises do + for ex in course.exercises do let status ← Runner.checkExercise ex match status with | .success => IO.println s!" {UI.green "✓"} {ex.name}" doneCount := doneCount + 1 - state := state.markCompleted ex.name + state := state.markCompleted course.id ex.name | .compileError _ => IO.println s!" {UI.red "✗"} {ex.name}" | .hasSorry => IO.println s!" {UI.yellow "!"} {ex.name}" state.save - IO.println s!"\n{UI.progressBar doneCount Config.exercises.size}" - if doneCount == Config.exercises.size then - IO.println s!"\n{Config.finalMessage}" + IO.println s!"\n{UI.progressBar doneCount course.exercises.size}" + if doneCount == course.exercises.size then + IO.println s!"\n{course.final}" else - IO.println s!"\n{doneCount}/{Config.exercises.size} exercises completed." + IO.println s!"\n{doneCount}/{course.exercises.size} exercises completed." return 0 -partial def watchLoop (state : AppState) (lastContent : String) (firstRun : Bool) : IO UInt32 := do - if state.allDone Config.exercises then +partial def watchLoop (course : Course) (state : AppState) (lastContent : String) (firstRun : Bool) : IO UInt32 := do + if state.allDone course then IO.print UI.clearScreen - IO.println Config.finalMessage + IO.println course.final return 0 - match Config.getExercise state.currentExercise with + match course.getExercise state.currentExercise with | some ex => let content ← try IO.FS.readFile ex.path catch _ => -- File may be mid-write; retry on next poll IO.sleep 500 - return ← watchLoop state lastContent false + return ← watchLoop course state lastContent false if content != lastContent || firstRun then IO.print UI.clearScreen - IO.println s!"{UI.bold "Leanlings"} — Watch Mode\n" - IO.println s!"{UI.progressBar state.countDone Config.exercises.size}\n" + IO.println s!"{UI.bold "Leanlings"} — Watch Mode ({course.title})\n" + IO.println s!"{UI.progressBar (state.countDone course) course.exercises.size}\n" IO.println s!"Checking {UI.bold ex.name}...\n" let status ← Runner.checkExercise ex Runner.displayResult ex status match status with | .success => - let state := state.advance Config.exercises + let state := state.advance course state.save - if state.allDone Config.exercises then - IO.println s!"\n{Config.finalMessage}" + if state.allDone course then + IO.println s!"\n{course.final}" return 0 IO.println s!"\nMoving to next exercise: {UI.bold state.currentExercise}" IO.sleep 500 - watchLoop state "" true + watchLoop course state "" true | _ => IO.println s!"\n{UI.dim "Edit the file and save to re-check."}" IO.println s!"{UI.dim "Run `lake exe leanlings hint` in another terminal for a hint."}" IO.sleep 500 - watchLoop state content false + watchLoop course state content false else IO.sleep 500 - watchLoop state lastContent false + watchLoop course state lastContent false | none => IO.println s!"Error: exercise '{state.currentExercise}' not found" return 1 @@ -188,12 +202,12 @@ partial def watchLoop (state : AppState) (lastContent : String) (firstRun : Bool def watchMode : IO UInt32 := do IO.println s!"{UI.bold "Leanlings"} — Watch Mode" IO.println "Watching for file changes... (Ctrl+C to quit)\n" - let state ← AppState.load Config.exercises - watchLoop state "" true + let (state, course) ← loadActive + watchLoop course state "" true def showSolution : IO Unit := do - let state ← AppState.load Config.exercises - match Config.getExercise state.currentExercise with + let (state, course) ← loadActive + match course.getExercise state.currentExercise with | some ex => IO.println s!"\n{UI.yellow "Solution"} for {UI.bold ex.name}:\n" let content ← IO.FS.readFile ex.solutionPath @@ -201,6 +215,34 @@ def showSolution : IO Unit := do | none => IO.println "Error: current exercise not found" +def listCourses : IO Unit := do + let (state, _) ← loadActive + IO.println s!"\n{UI.bold "Courses"}:\n" + for c in Config.courses do + let marker := if c.id == state.currentCourse then UI.yellow "→" else " " + let done := state.countDone c + IO.println s!" {marker} {UI.bold c.id} ({done}/{c.exercises.size}) {c.title}" + IO.println s!" {UI.dim c.description}" + IO.println "" + IO.println "Switch with: lake exe leanlings course " + +def selectCourse (id : String) : IO UInt32 := do + match Config.getCourse id with + | some course => + let state ← AppState.load Config.courses Config.defaultCourse + let state := state.switchCourse course + state.save + IO.println s!"{UI.green "✓"} Switched to course {UI.bold course.title}" + IO.println s!"Current exercise: {UI.bold state.currentExercise}" + match course.getExercise state.currentExercise with + | some ex => IO.println s!" → {ex.path}" + | none => pure () + return 0 + | none => + IO.println s!"{UI.red "Error"}: course '{id}' not found" + IO.println "Run `lake exe leanlings courses` to see available courses." + return 1 + def main (args : List String) : IO UInt32 := do match args with | [] => showStatus; return 0 @@ -213,9 +255,11 @@ def main (args : List String) : IO UInt32 := do | ["list"] => listExercises; return 0 | ["next"] => nextExercise; return 0 | ["reset"] => - let state ← AppState.load Config.exercises + let (state, _) ← loadActive resetExercise state.currentExercise | ["reset", name] => resetExercise name + | ["courses"] => listCourses; return 0 + | ["course", id] => selectCourse id | ["help"] | ["-h"] | ["--help"] => showHelp; return 0 | _ => IO.println s!"{UI.red "Error"}: Unknown command" diff --git a/README.md b/README.md index 4c5c110..ec46fde 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,24 @@ progression. Each exercise gives you a file with `sorry` placeholders to fill in. A watch-mode compiler checks your work on every save and advances you to the next exercise automatically. +## Courses + +Leanlings is organized into **courses**, each a self-contained progression of +exercises living under `courses//`. The `intro` course (described below) is +the default. List what's available and switch between them at any time: + +``` +lake exe leanlings courses # list courses with per-course progress +lake exe leanlings course # switch to another course +``` + +Progress is tracked separately per course, so you can move between them without +losing your place. Each course has `exercises/` (the files you edit) and +`solutions/` (reference answers) under its directory. + ## What you'll learn -The 70 exercises are organized into 27 units across three arcs: +The `intro` course's 70 exercises are organized into 27 units across three arcs: **Arc 1 — Programming fundamentals** (units 0–7, 14–18) Strings, arithmetic, functions, control flow, structures, inductive types, @@ -55,7 +70,9 @@ lake exe leanlings solution # show the solution lake exe leanlings list # list all exercises lake exe leanlings next # skip to next exercise lake exe leanlings reset # reset current exercise -lake exe leanlings verify # check all exercises +lake exe leanlings verify # check all exercises in the current course +lake exe leanlings courses # list available courses +lake exe leanlings course X # switch to course X ``` ## Great resources for learning Lean 4 diff --git a/exercises/00_intro/intro1.lean b/courses/intro/exercises/00_intro/intro1.lean similarity index 100% rename from exercises/00_intro/intro1.lean rename to courses/intro/exercises/00_intro/intro1.lean diff --git a/exercises/00_intro/intro2.lean b/courses/intro/exercises/00_intro/intro2.lean similarity index 100% rename from exercises/00_intro/intro2.lean rename to courses/intro/exercises/00_intro/intro2.lean diff --git a/exercises/00_intro/intro3.lean b/courses/intro/exercises/00_intro/intro3.lean similarity index 100% rename from exercises/00_intro/intro3.lean rename to courses/intro/exercises/00_intro/intro3.lean diff --git a/exercises/01_basics/basics1.lean b/courses/intro/exercises/01_basics/basics1.lean similarity index 100% rename from exercises/01_basics/basics1.lean rename to courses/intro/exercises/01_basics/basics1.lean diff --git a/exercises/01_basics/basics2.lean b/courses/intro/exercises/01_basics/basics2.lean similarity index 100% rename from exercises/01_basics/basics2.lean rename to courses/intro/exercises/01_basics/basics2.lean diff --git a/exercises/01_basics/basics3.lean b/courses/intro/exercises/01_basics/basics3.lean similarity index 100% rename from exercises/01_basics/basics3.lean rename to courses/intro/exercises/01_basics/basics3.lean diff --git a/exercises/01_basics/basics4.lean b/courses/intro/exercises/01_basics/basics4.lean similarity index 100% rename from exercises/01_basics/basics4.lean rename to courses/intro/exercises/01_basics/basics4.lean diff --git a/exercises/02_definitions/defs1.lean b/courses/intro/exercises/02_definitions/defs1.lean similarity index 100% rename from exercises/02_definitions/defs1.lean rename to courses/intro/exercises/02_definitions/defs1.lean diff --git a/exercises/02_definitions/defs2.lean b/courses/intro/exercises/02_definitions/defs2.lean similarity index 100% rename from exercises/02_definitions/defs2.lean rename to courses/intro/exercises/02_definitions/defs2.lean diff --git a/exercises/02_definitions/defs3.lean b/courses/intro/exercises/02_definitions/defs3.lean similarity index 100% rename from exercises/02_definitions/defs3.lean rename to courses/intro/exercises/02_definitions/defs3.lean diff --git a/exercises/03_functions/functions1.lean b/courses/intro/exercises/03_functions/functions1.lean similarity index 100% rename from exercises/03_functions/functions1.lean rename to courses/intro/exercises/03_functions/functions1.lean diff --git a/exercises/03_functions/functions2.lean b/courses/intro/exercises/03_functions/functions2.lean similarity index 100% rename from exercises/03_functions/functions2.lean rename to courses/intro/exercises/03_functions/functions2.lean diff --git a/exercises/03_functions/functions3.lean b/courses/intro/exercises/03_functions/functions3.lean similarity index 100% rename from exercises/03_functions/functions3.lean rename to courses/intro/exercises/03_functions/functions3.lean diff --git a/exercises/03_functions/functions4.lean b/courses/intro/exercises/03_functions/functions4.lean similarity index 100% rename from exercises/03_functions/functions4.lean rename to courses/intro/exercises/03_functions/functions4.lean diff --git a/exercises/04_control_flow/if1.lean b/courses/intro/exercises/04_control_flow/if1.lean similarity index 100% rename from exercises/04_control_flow/if1.lean rename to courses/intro/exercises/04_control_flow/if1.lean diff --git a/exercises/04_control_flow/if2.lean b/courses/intro/exercises/04_control_flow/if2.lean similarity index 100% rename from exercises/04_control_flow/if2.lean rename to courses/intro/exercises/04_control_flow/if2.lean diff --git a/exercises/04_control_flow/match1.lean b/courses/intro/exercises/04_control_flow/match1.lean similarity index 100% rename from exercises/04_control_flow/match1.lean rename to courses/intro/exercises/04_control_flow/match1.lean diff --git a/exercises/05_structures/structs1.lean b/courses/intro/exercises/05_structures/structs1.lean similarity index 100% rename from exercises/05_structures/structs1.lean rename to courses/intro/exercises/05_structures/structs1.lean diff --git a/exercises/05_structures/structs2.lean b/courses/intro/exercises/05_structures/structs2.lean similarity index 100% rename from exercises/05_structures/structs2.lean rename to courses/intro/exercises/05_structures/structs2.lean diff --git a/exercises/05_structures/structs3.lean b/courses/intro/exercises/05_structures/structs3.lean similarity index 100% rename from exercises/05_structures/structs3.lean rename to courses/intro/exercises/05_structures/structs3.lean diff --git a/exercises/06_inductive/inductive1.lean b/courses/intro/exercises/06_inductive/inductive1.lean similarity index 100% rename from exercises/06_inductive/inductive1.lean rename to courses/intro/exercises/06_inductive/inductive1.lean diff --git a/exercises/06_inductive/inductive2.lean b/courses/intro/exercises/06_inductive/inductive2.lean similarity index 100% rename from exercises/06_inductive/inductive2.lean rename to courses/intro/exercises/06_inductive/inductive2.lean diff --git a/exercises/06_inductive/inductive3.lean b/courses/intro/exercises/06_inductive/inductive3.lean similarity index 100% rename from exercises/06_inductive/inductive3.lean rename to courses/intro/exercises/06_inductive/inductive3.lean diff --git a/exercises/07_recursion/recursion1.lean b/courses/intro/exercises/07_recursion/recursion1.lean similarity index 100% rename from exercises/07_recursion/recursion1.lean rename to courses/intro/exercises/07_recursion/recursion1.lean diff --git a/exercises/07_recursion/recursion2.lean b/courses/intro/exercises/07_recursion/recursion2.lean similarity index 100% rename from exercises/07_recursion/recursion2.lean rename to courses/intro/exercises/07_recursion/recursion2.lean diff --git a/exercises/07_recursion/recursion3.lean b/courses/intro/exercises/07_recursion/recursion3.lean similarity index 100% rename from exercises/07_recursion/recursion3.lean rename to courses/intro/exercises/07_recursion/recursion3.lean diff --git a/exercises/07_recursion/recursion4.lean b/courses/intro/exercises/07_recursion/recursion4.lean similarity index 100% rename from exercises/07_recursion/recursion4.lean rename to courses/intro/exercises/07_recursion/recursion4.lean diff --git a/exercises/08_proving_code/proving1.lean b/courses/intro/exercises/08_proving_code/proving1.lean similarity index 100% rename from exercises/08_proving_code/proving1.lean rename to courses/intro/exercises/08_proving_code/proving1.lean diff --git a/exercises/08_proving_code/proving2.lean b/courses/intro/exercises/08_proving_code/proving2.lean similarity index 100% rename from exercises/08_proving_code/proving2.lean rename to courses/intro/exercises/08_proving_code/proving2.lean diff --git a/exercises/08_proving_code/proving3.lean b/courses/intro/exercises/08_proving_code/proving3.lean similarity index 100% rename from exercises/08_proving_code/proving3.lean rename to courses/intro/exercises/08_proving_code/proving3.lean diff --git a/exercises/09_propositions/props1.lean b/courses/intro/exercises/09_propositions/props1.lean similarity index 100% rename from exercises/09_propositions/props1.lean rename to courses/intro/exercises/09_propositions/props1.lean diff --git a/exercises/09_propositions/props2.lean b/courses/intro/exercises/09_propositions/props2.lean similarity index 100% rename from exercises/09_propositions/props2.lean rename to courses/intro/exercises/09_propositions/props2.lean diff --git a/exercises/09_propositions/props3.lean b/courses/intro/exercises/09_propositions/props3.lean similarity index 100% rename from exercises/09_propositions/props3.lean rename to courses/intro/exercises/09_propositions/props3.lean diff --git a/exercises/10_tactics/tactics1.lean b/courses/intro/exercises/10_tactics/tactics1.lean similarity index 100% rename from exercises/10_tactics/tactics1.lean rename to courses/intro/exercises/10_tactics/tactics1.lean diff --git a/exercises/10_tactics/tactics2.lean b/courses/intro/exercises/10_tactics/tactics2.lean similarity index 100% rename from exercises/10_tactics/tactics2.lean rename to courses/intro/exercises/10_tactics/tactics2.lean diff --git a/exercises/10_tactics/tactics3.lean b/courses/intro/exercises/10_tactics/tactics3.lean similarity index 100% rename from exercises/10_tactics/tactics3.lean rename to courses/intro/exercises/10_tactics/tactics3.lean diff --git a/exercises/10_tactics/tactics4.lean b/courses/intro/exercises/10_tactics/tactics4.lean similarity index 100% rename from exercises/10_tactics/tactics4.lean rename to courses/intro/exercises/10_tactics/tactics4.lean diff --git a/exercises/11_induction/induction1.lean b/courses/intro/exercises/11_induction/induction1.lean similarity index 100% rename from exercises/11_induction/induction1.lean rename to courses/intro/exercises/11_induction/induction1.lean diff --git a/exercises/11_induction/induction2.lean b/courses/intro/exercises/11_induction/induction2.lean similarity index 100% rename from exercises/11_induction/induction2.lean rename to courses/intro/exercises/11_induction/induction2.lean diff --git a/exercises/12_typeclasses/typeclasses1.lean b/courses/intro/exercises/12_typeclasses/typeclasses1.lean similarity index 100% rename from exercises/12_typeclasses/typeclasses1.lean rename to courses/intro/exercises/12_typeclasses/typeclasses1.lean diff --git a/exercises/12_typeclasses/typeclasses2.lean b/courses/intro/exercises/12_typeclasses/typeclasses2.lean similarity index 100% rename from exercises/12_typeclasses/typeclasses2.lean rename to courses/intro/exercises/12_typeclasses/typeclasses2.lean diff --git a/exercises/13_quiz/quiz1.lean b/courses/intro/exercises/13_quiz/quiz1.lean similarity index 100% rename from exercises/13_quiz/quiz1.lean rename to courses/intro/exercises/13_quiz/quiz1.lean diff --git a/exercises/14_do_notation/do1.lean b/courses/intro/exercises/14_do_notation/do1.lean similarity index 100% rename from exercises/14_do_notation/do1.lean rename to courses/intro/exercises/14_do_notation/do1.lean diff --git a/exercises/14_do_notation/do2.lean b/courses/intro/exercises/14_do_notation/do2.lean similarity index 100% rename from exercises/14_do_notation/do2.lean rename to courses/intro/exercises/14_do_notation/do2.lean diff --git a/exercises/14_do_notation/do3.lean b/courses/intro/exercises/14_do_notation/do3.lean similarity index 100% rename from exercises/14_do_notation/do3.lean rename to courses/intro/exercises/14_do_notation/do3.lean diff --git a/exercises/15_io/io1.lean b/courses/intro/exercises/15_io/io1.lean similarity index 100% rename from exercises/15_io/io1.lean rename to courses/intro/exercises/15_io/io1.lean diff --git a/exercises/15_io/io2.lean b/courses/intro/exercises/15_io/io2.lean similarity index 100% rename from exercises/15_io/io2.lean rename to courses/intro/exercises/15_io/io2.lean diff --git a/exercises/16_implicit/implicit1.lean b/courses/intro/exercises/16_implicit/implicit1.lean similarity index 100% rename from exercises/16_implicit/implicit1.lean rename to courses/intro/exercises/16_implicit/implicit1.lean diff --git a/exercises/16_implicit/implicit2.lean b/courses/intro/exercises/16_implicit/implicit2.lean similarity index 100% rename from exercises/16_implicit/implicit2.lean rename to courses/intro/exercises/16_implicit/implicit2.lean diff --git a/exercises/17_arrays/arrays1.lean b/courses/intro/exercises/17_arrays/arrays1.lean similarity index 100% rename from exercises/17_arrays/arrays1.lean rename to courses/intro/exercises/17_arrays/arrays1.lean diff --git a/exercises/17_arrays/arrays2.lean b/courses/intro/exercises/17_arrays/arrays2.lean similarity index 100% rename from exercises/17_arrays/arrays2.lean rename to courses/intro/exercises/17_arrays/arrays2.lean diff --git a/exercises/18_namespaces/ns1.lean b/courses/intro/exercises/18_namespaces/ns1.lean similarity index 100% rename from exercises/18_namespaces/ns1.lean rename to courses/intro/exercises/18_namespaces/ns1.lean diff --git a/exercises/18_namespaces/ns2.lean b/courses/intro/exercises/18_namespaces/ns2.lean similarity index 100% rename from exercises/18_namespaces/ns2.lean rename to courses/intro/exercises/18_namespaces/ns2.lean diff --git a/exercises/19_quiz2/quiz2.lean b/courses/intro/exercises/19_quiz2/quiz2.lean similarity index 100% rename from exercises/19_quiz2/quiz2.lean rename to courses/intro/exercises/19_quiz2/quiz2.lean diff --git a/exercises/20_exists/exists1.lean b/courses/intro/exercises/20_exists/exists1.lean similarity index 100% rename from exercises/20_exists/exists1.lean rename to courses/intro/exercises/20_exists/exists1.lean diff --git a/exercises/20_exists/exists2.lean b/courses/intro/exercises/20_exists/exists2.lean similarity index 100% rename from exercises/20_exists/exists2.lean rename to courses/intro/exercises/20_exists/exists2.lean diff --git a/exercises/21_cases_have/cases1.lean b/courses/intro/exercises/21_cases_have/cases1.lean similarity index 100% rename from exercises/21_cases_have/cases1.lean rename to courses/intro/exercises/21_cases_have/cases1.lean diff --git a/exercises/21_cases_have/cases2.lean b/courses/intro/exercises/21_cases_have/cases2.lean similarity index 100% rename from exercises/21_cases_have/cases2.lean rename to courses/intro/exercises/21_cases_have/cases2.lean diff --git a/exercises/21_cases_have/have1.lean b/courses/intro/exercises/21_cases_have/have1.lean similarity index 100% rename from exercises/21_cases_have/have1.lean rename to courses/intro/exercises/21_cases_have/have1.lean diff --git a/exercises/22_calc/calc1.lean b/courses/intro/exercises/22_calc/calc1.lean similarity index 100% rename from exercises/22_calc/calc1.lean rename to courses/intro/exercises/22_calc/calc1.lean diff --git a/exercises/22_calc/calc2.lean b/courses/intro/exercises/22_calc/calc2.lean similarity index 100% rename from exercises/22_calc/calc2.lean rename to courses/intro/exercises/22_calc/calc2.lean diff --git a/exercises/23_classical/classical1.lean b/courses/intro/exercises/23_classical/classical1.lean similarity index 100% rename from exercises/23_classical/classical1.lean rename to courses/intro/exercises/23_classical/classical1.lean diff --git a/exercises/23_classical/classical2.lean b/courses/intro/exercises/23_classical/classical2.lean similarity index 100% rename from exercises/23_classical/classical2.lean rename to courses/intro/exercises/23_classical/classical2.lean diff --git a/exercises/24_nat_proofs/nat1.lean b/courses/intro/exercises/24_nat_proofs/nat1.lean similarity index 100% rename from exercises/24_nat_proofs/nat1.lean rename to courses/intro/exercises/24_nat_proofs/nat1.lean diff --git a/exercises/24_nat_proofs/nat2.lean b/courses/intro/exercises/24_nat_proofs/nat2.lean similarity index 100% rename from exercises/24_nat_proofs/nat2.lean rename to courses/intro/exercises/24_nat_proofs/nat2.lean diff --git a/exercises/24_nat_proofs/nat3.lean b/courses/intro/exercises/24_nat_proofs/nat3.lean similarity index 100% rename from exercises/24_nat_proofs/nat3.lean rename to courses/intro/exercises/24_nat_proofs/nat3.lean diff --git a/exercises/25_list_proofs/list1.lean b/courses/intro/exercises/25_list_proofs/list1.lean similarity index 100% rename from exercises/25_list_proofs/list1.lean rename to courses/intro/exercises/25_list_proofs/list1.lean diff --git a/exercises/25_list_proofs/list2.lean b/courses/intro/exercises/25_list_proofs/list2.lean similarity index 100% rename from exercises/25_list_proofs/list2.lean rename to courses/intro/exercises/25_list_proofs/list2.lean diff --git a/exercises/25_list_proofs/list3.lean b/courses/intro/exercises/25_list_proofs/list3.lean similarity index 100% rename from exercises/25_list_proofs/list3.lean rename to courses/intro/exercises/25_list_proofs/list3.lean diff --git a/exercises/26_final_quiz/quiz3.lean b/courses/intro/exercises/26_final_quiz/quiz3.lean similarity index 100% rename from exercises/26_final_quiz/quiz3.lean rename to courses/intro/exercises/26_final_quiz/quiz3.lean diff --git a/.solutions/00_intro/intro1.lean b/courses/intro/solutions/00_intro/intro1.lean similarity index 100% rename from .solutions/00_intro/intro1.lean rename to courses/intro/solutions/00_intro/intro1.lean diff --git a/.solutions/00_intro/intro2.lean b/courses/intro/solutions/00_intro/intro2.lean similarity index 100% rename from .solutions/00_intro/intro2.lean rename to courses/intro/solutions/00_intro/intro2.lean diff --git a/.solutions/00_intro/intro3.lean b/courses/intro/solutions/00_intro/intro3.lean similarity index 100% rename from .solutions/00_intro/intro3.lean rename to courses/intro/solutions/00_intro/intro3.lean diff --git a/.solutions/01_basics/basics1.lean b/courses/intro/solutions/01_basics/basics1.lean similarity index 100% rename from .solutions/01_basics/basics1.lean rename to courses/intro/solutions/01_basics/basics1.lean diff --git a/.solutions/01_basics/basics2.lean b/courses/intro/solutions/01_basics/basics2.lean similarity index 100% rename from .solutions/01_basics/basics2.lean rename to courses/intro/solutions/01_basics/basics2.lean diff --git a/.solutions/01_basics/basics3.lean b/courses/intro/solutions/01_basics/basics3.lean similarity index 100% rename from .solutions/01_basics/basics3.lean rename to courses/intro/solutions/01_basics/basics3.lean diff --git a/.solutions/01_basics/basics4.lean b/courses/intro/solutions/01_basics/basics4.lean similarity index 100% rename from .solutions/01_basics/basics4.lean rename to courses/intro/solutions/01_basics/basics4.lean diff --git a/.solutions/02_definitions/defs1.lean b/courses/intro/solutions/02_definitions/defs1.lean similarity index 100% rename from .solutions/02_definitions/defs1.lean rename to courses/intro/solutions/02_definitions/defs1.lean diff --git a/.solutions/02_definitions/defs2.lean b/courses/intro/solutions/02_definitions/defs2.lean similarity index 100% rename from .solutions/02_definitions/defs2.lean rename to courses/intro/solutions/02_definitions/defs2.lean diff --git a/.solutions/02_definitions/defs3.lean b/courses/intro/solutions/02_definitions/defs3.lean similarity index 100% rename from .solutions/02_definitions/defs3.lean rename to courses/intro/solutions/02_definitions/defs3.lean diff --git a/.solutions/03_functions/functions1.lean b/courses/intro/solutions/03_functions/functions1.lean similarity index 100% rename from .solutions/03_functions/functions1.lean rename to courses/intro/solutions/03_functions/functions1.lean diff --git a/.solutions/03_functions/functions2.lean b/courses/intro/solutions/03_functions/functions2.lean similarity index 100% rename from .solutions/03_functions/functions2.lean rename to courses/intro/solutions/03_functions/functions2.lean diff --git a/.solutions/03_functions/functions3.lean b/courses/intro/solutions/03_functions/functions3.lean similarity index 100% rename from .solutions/03_functions/functions3.lean rename to courses/intro/solutions/03_functions/functions3.lean diff --git a/.solutions/03_functions/functions4.lean b/courses/intro/solutions/03_functions/functions4.lean similarity index 100% rename from .solutions/03_functions/functions4.lean rename to courses/intro/solutions/03_functions/functions4.lean diff --git a/.solutions/04_control_flow/if1.lean b/courses/intro/solutions/04_control_flow/if1.lean similarity index 100% rename from .solutions/04_control_flow/if1.lean rename to courses/intro/solutions/04_control_flow/if1.lean diff --git a/.solutions/04_control_flow/if2.lean b/courses/intro/solutions/04_control_flow/if2.lean similarity index 100% rename from .solutions/04_control_flow/if2.lean rename to courses/intro/solutions/04_control_flow/if2.lean diff --git a/.solutions/04_control_flow/match1.lean b/courses/intro/solutions/04_control_flow/match1.lean similarity index 100% rename from .solutions/04_control_flow/match1.lean rename to courses/intro/solutions/04_control_flow/match1.lean diff --git a/.solutions/05_structures/structs1.lean b/courses/intro/solutions/05_structures/structs1.lean similarity index 100% rename from .solutions/05_structures/structs1.lean rename to courses/intro/solutions/05_structures/structs1.lean diff --git a/.solutions/05_structures/structs2.lean b/courses/intro/solutions/05_structures/structs2.lean similarity index 100% rename from .solutions/05_structures/structs2.lean rename to courses/intro/solutions/05_structures/structs2.lean diff --git a/.solutions/05_structures/structs3.lean b/courses/intro/solutions/05_structures/structs3.lean similarity index 100% rename from .solutions/05_structures/structs3.lean rename to courses/intro/solutions/05_structures/structs3.lean diff --git a/.solutions/06_inductive/inductive1.lean b/courses/intro/solutions/06_inductive/inductive1.lean similarity index 100% rename from .solutions/06_inductive/inductive1.lean rename to courses/intro/solutions/06_inductive/inductive1.lean diff --git a/.solutions/06_inductive/inductive2.lean b/courses/intro/solutions/06_inductive/inductive2.lean similarity index 100% rename from .solutions/06_inductive/inductive2.lean rename to courses/intro/solutions/06_inductive/inductive2.lean diff --git a/.solutions/06_inductive/inductive3.lean b/courses/intro/solutions/06_inductive/inductive3.lean similarity index 100% rename from .solutions/06_inductive/inductive3.lean rename to courses/intro/solutions/06_inductive/inductive3.lean diff --git a/.solutions/07_recursion/recursion1.lean b/courses/intro/solutions/07_recursion/recursion1.lean similarity index 100% rename from .solutions/07_recursion/recursion1.lean rename to courses/intro/solutions/07_recursion/recursion1.lean diff --git a/.solutions/07_recursion/recursion2.lean b/courses/intro/solutions/07_recursion/recursion2.lean similarity index 100% rename from .solutions/07_recursion/recursion2.lean rename to courses/intro/solutions/07_recursion/recursion2.lean diff --git a/.solutions/07_recursion/recursion3.lean b/courses/intro/solutions/07_recursion/recursion3.lean similarity index 100% rename from .solutions/07_recursion/recursion3.lean rename to courses/intro/solutions/07_recursion/recursion3.lean diff --git a/.solutions/07_recursion/recursion4.lean b/courses/intro/solutions/07_recursion/recursion4.lean similarity index 100% rename from .solutions/07_recursion/recursion4.lean rename to courses/intro/solutions/07_recursion/recursion4.lean diff --git a/.solutions/08_proving_code/proving1.lean b/courses/intro/solutions/08_proving_code/proving1.lean similarity index 100% rename from .solutions/08_proving_code/proving1.lean rename to courses/intro/solutions/08_proving_code/proving1.lean diff --git a/.solutions/08_proving_code/proving2.lean b/courses/intro/solutions/08_proving_code/proving2.lean similarity index 100% rename from .solutions/08_proving_code/proving2.lean rename to courses/intro/solutions/08_proving_code/proving2.lean diff --git a/.solutions/08_proving_code/proving3.lean b/courses/intro/solutions/08_proving_code/proving3.lean similarity index 100% rename from .solutions/08_proving_code/proving3.lean rename to courses/intro/solutions/08_proving_code/proving3.lean diff --git a/.solutions/09_propositions/props1.lean b/courses/intro/solutions/09_propositions/props1.lean similarity index 100% rename from .solutions/09_propositions/props1.lean rename to courses/intro/solutions/09_propositions/props1.lean diff --git a/.solutions/09_propositions/props2.lean b/courses/intro/solutions/09_propositions/props2.lean similarity index 100% rename from .solutions/09_propositions/props2.lean rename to courses/intro/solutions/09_propositions/props2.lean diff --git a/.solutions/09_propositions/props3.lean b/courses/intro/solutions/09_propositions/props3.lean similarity index 100% rename from .solutions/09_propositions/props3.lean rename to courses/intro/solutions/09_propositions/props3.lean diff --git a/.solutions/10_tactics/tactics1.lean b/courses/intro/solutions/10_tactics/tactics1.lean similarity index 100% rename from .solutions/10_tactics/tactics1.lean rename to courses/intro/solutions/10_tactics/tactics1.lean diff --git a/.solutions/10_tactics/tactics2.lean b/courses/intro/solutions/10_tactics/tactics2.lean similarity index 100% rename from .solutions/10_tactics/tactics2.lean rename to courses/intro/solutions/10_tactics/tactics2.lean diff --git a/.solutions/10_tactics/tactics3.lean b/courses/intro/solutions/10_tactics/tactics3.lean similarity index 100% rename from .solutions/10_tactics/tactics3.lean rename to courses/intro/solutions/10_tactics/tactics3.lean diff --git a/.solutions/10_tactics/tactics4.lean b/courses/intro/solutions/10_tactics/tactics4.lean similarity index 100% rename from .solutions/10_tactics/tactics4.lean rename to courses/intro/solutions/10_tactics/tactics4.lean diff --git a/.solutions/11_induction/induction1.lean b/courses/intro/solutions/11_induction/induction1.lean similarity index 100% rename from .solutions/11_induction/induction1.lean rename to courses/intro/solutions/11_induction/induction1.lean diff --git a/.solutions/11_induction/induction2.lean b/courses/intro/solutions/11_induction/induction2.lean similarity index 100% rename from .solutions/11_induction/induction2.lean rename to courses/intro/solutions/11_induction/induction2.lean diff --git a/.solutions/12_typeclasses/typeclasses1.lean b/courses/intro/solutions/12_typeclasses/typeclasses1.lean similarity index 100% rename from .solutions/12_typeclasses/typeclasses1.lean rename to courses/intro/solutions/12_typeclasses/typeclasses1.lean diff --git a/.solutions/12_typeclasses/typeclasses2.lean b/courses/intro/solutions/12_typeclasses/typeclasses2.lean similarity index 100% rename from .solutions/12_typeclasses/typeclasses2.lean rename to courses/intro/solutions/12_typeclasses/typeclasses2.lean diff --git a/.solutions/13_quiz/quiz1.lean b/courses/intro/solutions/13_quiz/quiz1.lean similarity index 100% rename from .solutions/13_quiz/quiz1.lean rename to courses/intro/solutions/13_quiz/quiz1.lean diff --git a/.solutions/14_do_notation/do1.lean b/courses/intro/solutions/14_do_notation/do1.lean similarity index 100% rename from .solutions/14_do_notation/do1.lean rename to courses/intro/solutions/14_do_notation/do1.lean diff --git a/.solutions/14_do_notation/do2.lean b/courses/intro/solutions/14_do_notation/do2.lean similarity index 100% rename from .solutions/14_do_notation/do2.lean rename to courses/intro/solutions/14_do_notation/do2.lean diff --git a/.solutions/14_do_notation/do3.lean b/courses/intro/solutions/14_do_notation/do3.lean similarity index 100% rename from .solutions/14_do_notation/do3.lean rename to courses/intro/solutions/14_do_notation/do3.lean diff --git a/.solutions/15_io/io1.lean b/courses/intro/solutions/15_io/io1.lean similarity index 100% rename from .solutions/15_io/io1.lean rename to courses/intro/solutions/15_io/io1.lean diff --git a/.solutions/15_io/io2.lean b/courses/intro/solutions/15_io/io2.lean similarity index 100% rename from .solutions/15_io/io2.lean rename to courses/intro/solutions/15_io/io2.lean diff --git a/.solutions/16_implicit/implicit1.lean b/courses/intro/solutions/16_implicit/implicit1.lean similarity index 100% rename from .solutions/16_implicit/implicit1.lean rename to courses/intro/solutions/16_implicit/implicit1.lean diff --git a/.solutions/16_implicit/implicit2.lean b/courses/intro/solutions/16_implicit/implicit2.lean similarity index 100% rename from .solutions/16_implicit/implicit2.lean rename to courses/intro/solutions/16_implicit/implicit2.lean diff --git a/.solutions/17_arrays/arrays1.lean b/courses/intro/solutions/17_arrays/arrays1.lean similarity index 100% rename from .solutions/17_arrays/arrays1.lean rename to courses/intro/solutions/17_arrays/arrays1.lean diff --git a/.solutions/17_arrays/arrays2.lean b/courses/intro/solutions/17_arrays/arrays2.lean similarity index 100% rename from .solutions/17_arrays/arrays2.lean rename to courses/intro/solutions/17_arrays/arrays2.lean diff --git a/.solutions/18_namespaces/ns1.lean b/courses/intro/solutions/18_namespaces/ns1.lean similarity index 100% rename from .solutions/18_namespaces/ns1.lean rename to courses/intro/solutions/18_namespaces/ns1.lean diff --git a/.solutions/18_namespaces/ns2.lean b/courses/intro/solutions/18_namespaces/ns2.lean similarity index 100% rename from .solutions/18_namespaces/ns2.lean rename to courses/intro/solutions/18_namespaces/ns2.lean diff --git a/.solutions/19_quiz2/quiz2.lean b/courses/intro/solutions/19_quiz2/quiz2.lean similarity index 100% rename from .solutions/19_quiz2/quiz2.lean rename to courses/intro/solutions/19_quiz2/quiz2.lean diff --git a/.solutions/20_exists/exists1.lean b/courses/intro/solutions/20_exists/exists1.lean similarity index 100% rename from .solutions/20_exists/exists1.lean rename to courses/intro/solutions/20_exists/exists1.lean diff --git a/.solutions/20_exists/exists2.lean b/courses/intro/solutions/20_exists/exists2.lean similarity index 100% rename from .solutions/20_exists/exists2.lean rename to courses/intro/solutions/20_exists/exists2.lean diff --git a/.solutions/21_cases_have/cases1.lean b/courses/intro/solutions/21_cases_have/cases1.lean similarity index 100% rename from .solutions/21_cases_have/cases1.lean rename to courses/intro/solutions/21_cases_have/cases1.lean diff --git a/.solutions/21_cases_have/cases2.lean b/courses/intro/solutions/21_cases_have/cases2.lean similarity index 100% rename from .solutions/21_cases_have/cases2.lean rename to courses/intro/solutions/21_cases_have/cases2.lean diff --git a/.solutions/21_cases_have/have1.lean b/courses/intro/solutions/21_cases_have/have1.lean similarity index 100% rename from .solutions/21_cases_have/have1.lean rename to courses/intro/solutions/21_cases_have/have1.lean diff --git a/.solutions/22_calc/calc1.lean b/courses/intro/solutions/22_calc/calc1.lean similarity index 100% rename from .solutions/22_calc/calc1.lean rename to courses/intro/solutions/22_calc/calc1.lean diff --git a/.solutions/22_calc/calc2.lean b/courses/intro/solutions/22_calc/calc2.lean similarity index 100% rename from .solutions/22_calc/calc2.lean rename to courses/intro/solutions/22_calc/calc2.lean diff --git a/.solutions/23_classical/classical1.lean b/courses/intro/solutions/23_classical/classical1.lean similarity index 100% rename from .solutions/23_classical/classical1.lean rename to courses/intro/solutions/23_classical/classical1.lean diff --git a/.solutions/23_classical/classical2.lean b/courses/intro/solutions/23_classical/classical2.lean similarity index 100% rename from .solutions/23_classical/classical2.lean rename to courses/intro/solutions/23_classical/classical2.lean diff --git a/.solutions/24_nat_proofs/nat1.lean b/courses/intro/solutions/24_nat_proofs/nat1.lean similarity index 100% rename from .solutions/24_nat_proofs/nat1.lean rename to courses/intro/solutions/24_nat_proofs/nat1.lean diff --git a/.solutions/24_nat_proofs/nat2.lean b/courses/intro/solutions/24_nat_proofs/nat2.lean similarity index 100% rename from .solutions/24_nat_proofs/nat2.lean rename to courses/intro/solutions/24_nat_proofs/nat2.lean diff --git a/.solutions/24_nat_proofs/nat3.lean b/courses/intro/solutions/24_nat_proofs/nat3.lean similarity index 100% rename from .solutions/24_nat_proofs/nat3.lean rename to courses/intro/solutions/24_nat_proofs/nat3.lean diff --git a/.solutions/25_list_proofs/list1.lean b/courses/intro/solutions/25_list_proofs/list1.lean similarity index 100% rename from .solutions/25_list_proofs/list1.lean rename to courses/intro/solutions/25_list_proofs/list1.lean diff --git a/.solutions/25_list_proofs/list2.lean b/courses/intro/solutions/25_list_proofs/list2.lean similarity index 100% rename from .solutions/25_list_proofs/list2.lean rename to courses/intro/solutions/25_list_proofs/list2.lean diff --git a/.solutions/25_list_proofs/list3.lean b/courses/intro/solutions/25_list_proofs/list3.lean similarity index 100% rename from .solutions/25_list_proofs/list3.lean rename to courses/intro/solutions/25_list_proofs/list3.lean diff --git a/.solutions/26_final_quiz/quiz3.lean b/courses/intro/solutions/26_final_quiz/quiz3.lean similarity index 100% rename from .solutions/26_final_quiz/quiz3.lean rename to courses/intro/solutions/26_final_quiz/quiz3.lean From 3e0f530b2cf2e28274f2ffb5b894efc979fae501 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Sat, 30 May 2026 19:02:37 -0400 Subject: [PATCH 2/4] Upgrade Lean toolchain to v4.30.0 Bump from v4.28.0 to the latest stable release. leanlings has no dependencies (no Mathlib), so the upgrade is self-contained: the tool builds clean and all 70 exercise solutions still compile under v4.30.0. --- lean-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lean-toolchain b/lean-toolchain index 4c685fa..af9e5d3 100644 --- a/lean-toolchain +++ b/lean-toolchain @@ -1 +1 @@ -leanprover/lean4:v4.28.0 +leanprover/lean4:v4.30.0 From 2270a0eb1ceb2ea3bc05503b5d5ffbb97fad279e Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Mon, 1 Jun 2026 22:50:52 -0400 Subject: [PATCH 3/4] Add Natural Number Game as a Mathlib-free `nng` course (closes #3 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port the Natural Number Game (leanprover-community/NNG4, Apache-2.0) into leanlings as a second course, with no Mathlib or GameServer dependency. Foundation (courses/nng/solutions/MyNatLib/, pure core Lean): - MyNat: an axiomatic Peano development of ℕ — type, opaque +/*/^ with their recursion-equation axioms, order (≤/<), and the Peano API (succ_inj, pred, zero_ne_succ), plus DecidableEq. - Core-only reimplementations of the tactic conveniences the levels need: lean3-style `induction … with d hd` and `cases … with` (via rec'/casesOn', handling dependent hypotheses), `use`, `apply … at` (a unifying elaborator), `nth_rewrite`, and `symm at`. Content: all 78 live levels across 9 worlds (Tutorial → Algorithm), each converted from the GameServer DSL into a leanlings exercise (the level's Introduction preserved as a comment) plus a reference solution. The solutions form one import-chain compiled as a Lean library, so `lake build` verifies every one. A handful of proofs that used Mathlib-only tactics (tauto/contrapose/decide) were rewritten in core Lean. The joke "FLT" level (solved by a sorry-cheat) is omitted. Tooling: - lakefile: the nng solutions are a `lean_lib` (default target). - Runner now checks exercises via `lake env lean` so course-library imports resolve. The intro course (no imports) is unaffected. - Config registers the `nng` course alongside `intro`. - CI verifies nng solutions via `lake build`; the file-by-file loop now covers only the standalone intro solutions. Licensing: courses/nng/ vendors NNG's Apache-2.0 LICENSE and adds a NOTICE stating the modifications; every ported file carries a provenance header. --- .github/workflows/ci.yml | 7 +- Leanlings/Config.lean | 200 ++++++++++++++++- Leanlings/Runner.lean | 11 +- README.md | 11 + courses/nng/LICENSE | 201 ++++++++++++++++++ courses/nng/NOTICE | 43 ++++ courses/nng/exercises/Addition/add_assoc.lean | 23 ++ courses/nng/exercises/Addition/add_comm.lean | 19 ++ .../exercises/Addition/add_right_comm.lean | 27 +++ courses/nng/exercises/Addition/succ_add.lean | 21 ++ courses/nng/exercises/Addition/zero_add.lean | 30 +++ .../AdvAddition/add_left_cancel.lean | 15 ++ .../AdvAddition/add_left_eq_self.lean | 14 ++ .../AdvAddition/add_left_eq_zero.lean | 15 ++ .../AdvAddition/add_right_cancel.lean | 16 ++ .../AdvAddition/add_right_eq_self.lean | 15 ++ .../AdvAddition/add_right_eq_zero.lean | 30 +++ .../AdvMultiplication/eq_succ_of_ne_zero.lean | 17 ++ .../AdvMultiplication/le_mul_right.lean | 19 ++ .../AdvMultiplication/mul_eq_zero.lean | 14 ++ .../AdvMultiplication/mul_le_mul_right.lean | 13 ++ .../AdvMultiplication/mul_left_cancel.lean | 24 +++ .../AdvMultiplication/mul_left_ne_zero.lean | 14 ++ .../AdvMultiplication/mul_ne_zero.lean | 15 ++ .../AdvMultiplication/mul_right_eq_one.lean | 17 ++ .../AdvMultiplication/mul_right_eq_self.lean | 14 ++ .../AdvMultiplication/one_le_of_ne_zero.lean | 13 ++ .../nng/exercises/Algorithm/add_algo1.lean | 24 +++ .../nng/exercises/Algorithm/add_algo2.lean | 20 ++ .../nng/exercises/Algorithm/add_algo3.lean | 24 +++ .../exercises/Algorithm/add_left_comm.lean | 21 ++ courses/nng/exercises/Algorithm/decide.lean | 42 ++++ courses/nng/exercises/Algorithm/decide2.lean | 14 ++ courses/nng/exercises/Algorithm/pred.lean | 30 +++ .../nng/exercises/Algorithm/succ_ne_succ.lean | 31 +++ .../nng/exercises/Algorithm/succ_ne_zero.lean | 28 +++ courses/nng/exercises/Implication/apply.lean | 18 ++ courses/nng/exercises/Implication/exact.lean | 19 ++ courses/nng/exercises/Implication/exact2.lean | 15 ++ courses/nng/exercises/Implication/intro.lean | 17 ++ courses/nng/exercises/Implication/intro2.lean | 15 ++ courses/nng/exercises/Implication/ne.lean | 26 +++ .../exercises/Implication/one_ne_zero.lean | 22 ++ .../nng/exercises/Implication/succ_inj.lean | 22 ++ .../nng/exercises/Implication/succ_inj2.lean | 19 ++ .../Implication/two_add_two_ne_five.lean | 16 ++ .../exercises/Implication/zero_ne_one.lean | 16 ++ .../exercises/LessOrEqual/le_antisymm.lean | 16 ++ courses/nng/exercises/LessOrEqual/le_one.lean | 15 ++ .../nng/exercises/LessOrEqual/le_refl.lean | 20 ++ .../exercises/LessOrEqual/le_succ_self.lean | 15 ++ .../nng/exercises/LessOrEqual/le_total.lean | 20 ++ .../nng/exercises/LessOrEqual/le_trans.lean | 15 ++ courses/nng/exercises/LessOrEqual/le_two.lean | 18 ++ .../nng/exercises/LessOrEqual/le_zero.lean | 16 ++ .../nng/exercises/LessOrEqual/or_symm.lean | 27 +++ .../exercises/LessOrEqual/succ_le_succ.lean | 16 ++ .../nng/exercises/LessOrEqual/zero_le.lean | 14 ++ .../nng/exercises/Multiplication/add_mul.lean | 18 ++ .../nng/exercises/Multiplication/mul_add.lean | 23 ++ .../exercises/Multiplication/mul_assoc.lean | 18 ++ .../exercises/Multiplication/mul_comm.lean | 20 ++ .../nng/exercises/Multiplication/mul_one.lean | 19 ++ .../nng/exercises/Multiplication/one_mul.lean | 17 ++ .../exercises/Multiplication/succ_mul.lean | 23 ++ .../nng/exercises/Multiplication/two_mul.lean | 16 ++ .../exercises/Multiplication/zero_mul.lean | 19 ++ courses/nng/exercises/Power/add_sq.lean | 16 ++ courses/nng/exercises/Power/mul_pow.lean | 20 ++ courses/nng/exercises/Power/one_pow.lean | 12 ++ courses/nng/exercises/Power/pow_add.lean | 16 ++ courses/nng/exercises/Power/pow_one.lean | 11 + courses/nng/exercises/Power/pow_pow.lean | 17 ++ courses/nng/exercises/Power/pow_two.lean | 15 ++ .../nng/exercises/Power/zero_pow_succ.lean | 16 ++ .../nng/exercises/Power/zero_pow_zero.lean | 19 ++ courses/nng/exercises/Tutorial/add_zero.lean | 35 +++ courses/nng/exercises/Tutorial/add_zero2.lean | 20 ++ courses/nng/exercises/Tutorial/rfl.lean | 29 +++ courses/nng/exercises/Tutorial/rw.lean | 22 ++ .../nng/exercises/Tutorial/rw_backwards.lean | 22 ++ .../exercises/Tutorial/succ_eq_add_one.lean | 29 +++ .../nng/exercises/Tutorial/two_eq_ss0.lean | 35 +++ courses/nng/exercises/Tutorial/twoaddtwo.lean | 18 ++ courses/nng/solutions/Addition/add_assoc.lean | 17 ++ courses/nng/solutions/Addition/add_comm.lean | 17 ++ .../solutions/Addition/add_right_comm.lean | 14 ++ courses/nng/solutions/Addition/succ_add.lean | 20 ++ courses/nng/solutions/Addition/zero_add.lean | 18 ++ .../AdvAddition/add_left_cancel.lean | 14 ++ .../AdvAddition/add_left_eq_self.lean | 14 ++ .../AdvAddition/add_left_eq_zero.lean | 12 ++ .../AdvAddition/add_right_cancel.lean | 19 ++ .../AdvAddition/add_right_eq_self.lean | 12 ++ .../AdvAddition/add_right_eq_zero.lean | 19 ++ .../AdvMultiplication/eq_succ_of_ne_zero.lean | 13 ++ .../AdvMultiplication/le_mul_right.lean | 14 ++ .../AdvMultiplication/mul_eq_zero.lean | 16 ++ .../AdvMultiplication/mul_le_mul_right.lean | 13 ++ .../AdvMultiplication/mul_left_cancel.lean | 28 +++ .../AdvMultiplication/mul_left_ne_zero.lean | 13 ++ .../AdvMultiplication/mul_ne_zero.lean | 17 ++ .../AdvMultiplication/mul_right_eq_one.lean | 17 ++ .../AdvMultiplication/mul_right_eq_self.lean | 11 + .../AdvMultiplication/one_le_of_ne_zero.lean | 14 ++ .../nng/solutions/Algorithm/add_algo1.lean | 15 ++ .../nng/solutions/Algorithm/add_algo2.lean | 13 ++ .../nng/solutions/Algorithm/add_algo3.lean | 13 ++ .../solutions/Algorithm/add_left_comm.lean | 14 ++ courses/nng/solutions/Algorithm/decide.lean | 11 + courses/nng/solutions/Algorithm/decide2.lean | 12 ++ courses/nng/solutions/Algorithm/pred.lean | 14 ++ .../nng/solutions/Algorithm/succ_ne_succ.lean | 13 ++ .../nng/solutions/Algorithm/succ_ne_zero.lean | 15 ++ courses/nng/solutions/Implication/apply.lean | 12 ++ courses/nng/solutions/Implication/exact.lean | 11 + courses/nng/solutions/Implication/exact2.lean | 13 ++ courses/nng/solutions/Implication/intro.lean | 12 ++ courses/nng/solutions/Implication/intro2.lean | 14 ++ courses/nng/solutions/Implication/ne.lean | 12 ++ .../solutions/Implication/one_ne_zero.lean | 12 ++ .../nng/solutions/Implication/succ_inj.lean | 14 ++ .../nng/solutions/Implication/succ_inj2.lean | 14 ++ .../Implication/two_add_two_ne_five.lean | 15 ++ .../solutions/Implication/zero_ne_one.lean | 14 ++ .../solutions/LessOrEqual/le_antisymm.lean | 19 ++ courses/nng/solutions/LessOrEqual/le_one.lean | 19 ++ .../nng/solutions/LessOrEqual/le_refl.lean | 13 ++ .../solutions/LessOrEqual/le_succ_self.lean | 13 ++ .../nng/solutions/LessOrEqual/le_total.lean | 32 +++ .../nng/solutions/LessOrEqual/le_trans.lean | 15 ++ courses/nng/solutions/LessOrEqual/le_two.lean | 26 +++ .../nng/solutions/LessOrEqual/le_zero.lean | 14 ++ .../nng/solutions/LessOrEqual/or_symm.lean | 15 ++ .../solutions/LessOrEqual/succ_le_succ.lean | 15 ++ .../nng/solutions/LessOrEqual/zero_le.lean | 13 ++ .../nng/solutions/Multiplication/add_mul.lean | 16 ++ .../nng/solutions/Multiplication/mul_add.lean | 18 ++ .../solutions/Multiplication/mul_assoc.lean | 21 ++ .../solutions/Multiplication/mul_comm.lean | 19 ++ .../nng/solutions/Multiplication/mul_one.lean | 15 ++ .../nng/solutions/Multiplication/one_mul.lean | 13 ++ .../solutions/Multiplication/succ_mul.lean | 24 +++ .../nng/solutions/Multiplication/two_mul.lean | 13 ++ .../solutions/Multiplication/zero_mul.lean | 18 ++ courses/nng/solutions/MyNatLib/Basic.lean | 133 ++++++++++++ courses/nng/solutions/MyNatLib/Decide.lean | 31 +++ courses/nng/solutions/MyNatLib/Peano.lean | 42 ++++ courses/nng/solutions/MyNatLib/Prelude.lean | 9 + courses/nng/solutions/MyNatLib/Tactic.lean | 108 ++++++++++ courses/nng/solutions/NNG.lean | 6 + courses/nng/solutions/Power/add_sq.lean | 19 ++ courses/nng/solutions/Power/mul_pow.lean | 19 ++ courses/nng/solutions/Power/one_pow.lean | 18 ++ courses/nng/solutions/Power/pow_add.lean | 16 ++ courses/nng/solutions/Power/pow_one.lean | 15 ++ courses/nng/solutions/Power/pow_pow.lean | 16 ++ courses/nng/solutions/Power/pow_two.lean | 15 ++ .../nng/solutions/Power/zero_pow_succ.lean | 14 ++ .../nng/solutions/Power/zero_pow_zero.lean | 12 ++ courses/nng/solutions/Tutorial/add_zero.lean | 13 ++ courses/nng/solutions/Tutorial/add_zero2.lean | 13 ++ courses/nng/solutions/Tutorial/rfl.lean | 12 ++ courses/nng/solutions/Tutorial/rw.lean | 13 ++ .../nng/solutions/Tutorial/rw_backwards.lean | 13 ++ .../solutions/Tutorial/succ_eq_add_one.lean | 14 ++ .../nng/solutions/Tutorial/two_eq_ss0.lean | 14 ++ courses/nng/solutions/Tutorial/twoaddtwo.lean | 18 ++ lakefile.lean | 23 ++ 169 files changed, 3560 insertions(+), 6 deletions(-) create mode 100644 courses/nng/LICENSE create mode 100644 courses/nng/NOTICE create mode 100644 courses/nng/exercises/Addition/add_assoc.lean create mode 100644 courses/nng/exercises/Addition/add_comm.lean create mode 100644 courses/nng/exercises/Addition/add_right_comm.lean create mode 100644 courses/nng/exercises/Addition/succ_add.lean create mode 100644 courses/nng/exercises/Addition/zero_add.lean create mode 100644 courses/nng/exercises/AdvAddition/add_left_cancel.lean create mode 100644 courses/nng/exercises/AdvAddition/add_left_eq_self.lean create mode 100644 courses/nng/exercises/AdvAddition/add_left_eq_zero.lean create mode 100644 courses/nng/exercises/AdvAddition/add_right_cancel.lean create mode 100644 courses/nng/exercises/AdvAddition/add_right_eq_self.lean create mode 100644 courses/nng/exercises/AdvAddition/add_right_eq_zero.lean create mode 100644 courses/nng/exercises/AdvMultiplication/eq_succ_of_ne_zero.lean create mode 100644 courses/nng/exercises/AdvMultiplication/le_mul_right.lean create mode 100644 courses/nng/exercises/AdvMultiplication/mul_eq_zero.lean create mode 100644 courses/nng/exercises/AdvMultiplication/mul_le_mul_right.lean create mode 100644 courses/nng/exercises/AdvMultiplication/mul_left_cancel.lean create mode 100644 courses/nng/exercises/AdvMultiplication/mul_left_ne_zero.lean create mode 100644 courses/nng/exercises/AdvMultiplication/mul_ne_zero.lean create mode 100644 courses/nng/exercises/AdvMultiplication/mul_right_eq_one.lean create mode 100644 courses/nng/exercises/AdvMultiplication/mul_right_eq_self.lean create mode 100644 courses/nng/exercises/AdvMultiplication/one_le_of_ne_zero.lean create mode 100644 courses/nng/exercises/Algorithm/add_algo1.lean create mode 100644 courses/nng/exercises/Algorithm/add_algo2.lean create mode 100644 courses/nng/exercises/Algorithm/add_algo3.lean create mode 100644 courses/nng/exercises/Algorithm/add_left_comm.lean create mode 100644 courses/nng/exercises/Algorithm/decide.lean create mode 100644 courses/nng/exercises/Algorithm/decide2.lean create mode 100644 courses/nng/exercises/Algorithm/pred.lean create mode 100644 courses/nng/exercises/Algorithm/succ_ne_succ.lean create mode 100644 courses/nng/exercises/Algorithm/succ_ne_zero.lean create mode 100644 courses/nng/exercises/Implication/apply.lean create mode 100644 courses/nng/exercises/Implication/exact.lean create mode 100644 courses/nng/exercises/Implication/exact2.lean create mode 100644 courses/nng/exercises/Implication/intro.lean create mode 100644 courses/nng/exercises/Implication/intro2.lean create mode 100644 courses/nng/exercises/Implication/ne.lean create mode 100644 courses/nng/exercises/Implication/one_ne_zero.lean create mode 100644 courses/nng/exercises/Implication/succ_inj.lean create mode 100644 courses/nng/exercises/Implication/succ_inj2.lean create mode 100644 courses/nng/exercises/Implication/two_add_two_ne_five.lean create mode 100644 courses/nng/exercises/Implication/zero_ne_one.lean create mode 100644 courses/nng/exercises/LessOrEqual/le_antisymm.lean create mode 100644 courses/nng/exercises/LessOrEqual/le_one.lean create mode 100644 courses/nng/exercises/LessOrEqual/le_refl.lean create mode 100644 courses/nng/exercises/LessOrEqual/le_succ_self.lean create mode 100644 courses/nng/exercises/LessOrEqual/le_total.lean create mode 100644 courses/nng/exercises/LessOrEqual/le_trans.lean create mode 100644 courses/nng/exercises/LessOrEqual/le_two.lean create mode 100644 courses/nng/exercises/LessOrEqual/le_zero.lean create mode 100644 courses/nng/exercises/LessOrEqual/or_symm.lean create mode 100644 courses/nng/exercises/LessOrEqual/succ_le_succ.lean create mode 100644 courses/nng/exercises/LessOrEqual/zero_le.lean create mode 100644 courses/nng/exercises/Multiplication/add_mul.lean create mode 100644 courses/nng/exercises/Multiplication/mul_add.lean create mode 100644 courses/nng/exercises/Multiplication/mul_assoc.lean create mode 100644 courses/nng/exercises/Multiplication/mul_comm.lean create mode 100644 courses/nng/exercises/Multiplication/mul_one.lean create mode 100644 courses/nng/exercises/Multiplication/one_mul.lean create mode 100644 courses/nng/exercises/Multiplication/succ_mul.lean create mode 100644 courses/nng/exercises/Multiplication/two_mul.lean create mode 100644 courses/nng/exercises/Multiplication/zero_mul.lean create mode 100644 courses/nng/exercises/Power/add_sq.lean create mode 100644 courses/nng/exercises/Power/mul_pow.lean create mode 100644 courses/nng/exercises/Power/one_pow.lean create mode 100644 courses/nng/exercises/Power/pow_add.lean create mode 100644 courses/nng/exercises/Power/pow_one.lean create mode 100644 courses/nng/exercises/Power/pow_pow.lean create mode 100644 courses/nng/exercises/Power/pow_two.lean create mode 100644 courses/nng/exercises/Power/zero_pow_succ.lean create mode 100644 courses/nng/exercises/Power/zero_pow_zero.lean create mode 100644 courses/nng/exercises/Tutorial/add_zero.lean create mode 100644 courses/nng/exercises/Tutorial/add_zero2.lean create mode 100644 courses/nng/exercises/Tutorial/rfl.lean create mode 100644 courses/nng/exercises/Tutorial/rw.lean create mode 100644 courses/nng/exercises/Tutorial/rw_backwards.lean create mode 100644 courses/nng/exercises/Tutorial/succ_eq_add_one.lean create mode 100644 courses/nng/exercises/Tutorial/two_eq_ss0.lean create mode 100644 courses/nng/exercises/Tutorial/twoaddtwo.lean create mode 100644 courses/nng/solutions/Addition/add_assoc.lean create mode 100644 courses/nng/solutions/Addition/add_comm.lean create mode 100644 courses/nng/solutions/Addition/add_right_comm.lean create mode 100644 courses/nng/solutions/Addition/succ_add.lean create mode 100644 courses/nng/solutions/Addition/zero_add.lean create mode 100644 courses/nng/solutions/AdvAddition/add_left_cancel.lean create mode 100644 courses/nng/solutions/AdvAddition/add_left_eq_self.lean create mode 100644 courses/nng/solutions/AdvAddition/add_left_eq_zero.lean create mode 100644 courses/nng/solutions/AdvAddition/add_right_cancel.lean create mode 100644 courses/nng/solutions/AdvAddition/add_right_eq_self.lean create mode 100644 courses/nng/solutions/AdvAddition/add_right_eq_zero.lean create mode 100644 courses/nng/solutions/AdvMultiplication/eq_succ_of_ne_zero.lean create mode 100644 courses/nng/solutions/AdvMultiplication/le_mul_right.lean create mode 100644 courses/nng/solutions/AdvMultiplication/mul_eq_zero.lean create mode 100644 courses/nng/solutions/AdvMultiplication/mul_le_mul_right.lean create mode 100644 courses/nng/solutions/AdvMultiplication/mul_left_cancel.lean create mode 100644 courses/nng/solutions/AdvMultiplication/mul_left_ne_zero.lean create mode 100644 courses/nng/solutions/AdvMultiplication/mul_ne_zero.lean create mode 100644 courses/nng/solutions/AdvMultiplication/mul_right_eq_one.lean create mode 100644 courses/nng/solutions/AdvMultiplication/mul_right_eq_self.lean create mode 100644 courses/nng/solutions/AdvMultiplication/one_le_of_ne_zero.lean create mode 100644 courses/nng/solutions/Algorithm/add_algo1.lean create mode 100644 courses/nng/solutions/Algorithm/add_algo2.lean create mode 100644 courses/nng/solutions/Algorithm/add_algo3.lean create mode 100644 courses/nng/solutions/Algorithm/add_left_comm.lean create mode 100644 courses/nng/solutions/Algorithm/decide.lean create mode 100644 courses/nng/solutions/Algorithm/decide2.lean create mode 100644 courses/nng/solutions/Algorithm/pred.lean create mode 100644 courses/nng/solutions/Algorithm/succ_ne_succ.lean create mode 100644 courses/nng/solutions/Algorithm/succ_ne_zero.lean create mode 100644 courses/nng/solutions/Implication/apply.lean create mode 100644 courses/nng/solutions/Implication/exact.lean create mode 100644 courses/nng/solutions/Implication/exact2.lean create mode 100644 courses/nng/solutions/Implication/intro.lean create mode 100644 courses/nng/solutions/Implication/intro2.lean create mode 100644 courses/nng/solutions/Implication/ne.lean create mode 100644 courses/nng/solutions/Implication/one_ne_zero.lean create mode 100644 courses/nng/solutions/Implication/succ_inj.lean create mode 100644 courses/nng/solutions/Implication/succ_inj2.lean create mode 100644 courses/nng/solutions/Implication/two_add_two_ne_five.lean create mode 100644 courses/nng/solutions/Implication/zero_ne_one.lean create mode 100644 courses/nng/solutions/LessOrEqual/le_antisymm.lean create mode 100644 courses/nng/solutions/LessOrEqual/le_one.lean create mode 100644 courses/nng/solutions/LessOrEqual/le_refl.lean create mode 100644 courses/nng/solutions/LessOrEqual/le_succ_self.lean create mode 100644 courses/nng/solutions/LessOrEqual/le_total.lean create mode 100644 courses/nng/solutions/LessOrEqual/le_trans.lean create mode 100644 courses/nng/solutions/LessOrEqual/le_two.lean create mode 100644 courses/nng/solutions/LessOrEqual/le_zero.lean create mode 100644 courses/nng/solutions/LessOrEqual/or_symm.lean create mode 100644 courses/nng/solutions/LessOrEqual/succ_le_succ.lean create mode 100644 courses/nng/solutions/LessOrEqual/zero_le.lean create mode 100644 courses/nng/solutions/Multiplication/add_mul.lean create mode 100644 courses/nng/solutions/Multiplication/mul_add.lean create mode 100644 courses/nng/solutions/Multiplication/mul_assoc.lean create mode 100644 courses/nng/solutions/Multiplication/mul_comm.lean create mode 100644 courses/nng/solutions/Multiplication/mul_one.lean create mode 100644 courses/nng/solutions/Multiplication/one_mul.lean create mode 100644 courses/nng/solutions/Multiplication/succ_mul.lean create mode 100644 courses/nng/solutions/Multiplication/two_mul.lean create mode 100644 courses/nng/solutions/Multiplication/zero_mul.lean create mode 100644 courses/nng/solutions/MyNatLib/Basic.lean create mode 100644 courses/nng/solutions/MyNatLib/Decide.lean create mode 100644 courses/nng/solutions/MyNatLib/Peano.lean create mode 100644 courses/nng/solutions/MyNatLib/Prelude.lean create mode 100644 courses/nng/solutions/MyNatLib/Tactic.lean create mode 100644 courses/nng/solutions/NNG.lean create mode 100644 courses/nng/solutions/Power/add_sq.lean create mode 100644 courses/nng/solutions/Power/mul_pow.lean create mode 100644 courses/nng/solutions/Power/one_pow.lean create mode 100644 courses/nng/solutions/Power/pow_add.lean create mode 100644 courses/nng/solutions/Power/pow_one.lean create mode 100644 courses/nng/solutions/Power/pow_pow.lean create mode 100644 courses/nng/solutions/Power/pow_two.lean create mode 100644 courses/nng/solutions/Power/zero_pow_succ.lean create mode 100644 courses/nng/solutions/Power/zero_pow_zero.lean create mode 100644 courses/nng/solutions/Tutorial/add_zero.lean create mode 100644 courses/nng/solutions/Tutorial/add_zero2.lean create mode 100644 courses/nng/solutions/Tutorial/rfl.lean create mode 100644 courses/nng/solutions/Tutorial/rw.lean create mode 100644 courses/nng/solutions/Tutorial/rw_backwards.lean create mode 100644 courses/nng/solutions/Tutorial/succ_eq_add_one.lean create mode 100644 courses/nng/solutions/Tutorial/two_eq_ss0.lean create mode 100644 courses/nng/solutions/Tutorial/twoaddtwo.lean diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba8649c..df7cb18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,10 +26,13 @@ jobs: - name: Build run: lake build - - name: Verify all solutions + # The `nng` course's solutions form a Lean library, so they are already + # compiled (and thus verified) by `lake build` above. Here we check the + # standalone `intro` solutions, which are compiled file-by-file. + - name: Verify intro solutions run: | failed=0 - for f in $(find courses -path '*/solutions/*/*.lean' | sort); do + for f in $(find courses/intro/solutions -path '*/*.lean' | sort); do if ! lean "$f" 2>&1; then echo "FAIL: $f" failed=$((failed + 1)) diff --git a/Leanlings/Config.lean b/Leanlings/Config.lean index 3a910ee..3b61a62 100644 --- a/Leanlings/Config.lean +++ b/Leanlings/Config.lean @@ -238,7 +238,205 @@ def intro : Course := introExercises (welcome := introWelcome) (final := introFinal) /-- All available courses, in display order. -/ -def courses : Array Course := #[intro] + +private def nngExercises : Array Exercise := #[ + -- Tutorial + { name := "rfl", dir := "Tutorial", + hint := "In order to use the tactic `rfl` you can enter it in the text box\n under the goal and hit \"Execute\"." }, + { name := "rw", dir := "Tutorial", + hint := "First execute `rw [h]` to replace the `y` with `x + 7`." }, + { name := "two_eq_ss0", dir := "Tutorial", + hint := "Start with `rw [two_eq_succ_one]` to begin to break `2` down into its definition." }, + { name := "rw_backwards", dir := "Tutorial", + hint := "Try `rw [← one_eq_succ_zero]` to change `succ 0` into `1`." }, + { name := "add_zero", dir := "Tutorial", + hint := "`rw [add_zero]` will change `b + 0` into `b`." }, + { name := "add_zero2", dir := "Tutorial", + hint := "Try `rw [add_zero c]`." }, + { name := "succ_eq_add_one", dir := "Tutorial", + hint := "Start by unravelling the `1`." }, + { name := "twoaddtwo", dir := "Tutorial", + hint := "`nth_rewrite 2 [two_eq_succ_one]` is I think quicker than `rw [two_eq_succ_one]`." }, + -- Addition + { name := "zero_add", dir := "Addition", + hint := "You can start a proof by induction on `n` by typing:\n `induction n with d hd`." }, + { name := "succ_add", dir := "Addition", + hint := "You might want to think about whether induction\n on `a` or `b` is the best idea." }, + { name := "add_comm", dir := "Addition", + hint := "Induction on `a` or `b` -- it's all the same in this one." }, + { name := "add_assoc", dir := "Addition", + hint := "Remember that when Lean writes `a + b + c`, it means `(a + b) + c`.\n If you are not sure where the brackets are in an expression, just hover\n your cursor over it and look at what gets highlighted. For example,\n hover over both `+` symbols on the left hand side of the goal and\n you'll see where the invisible brackets are." }, + { name := "add_right_comm", dir := "Addition", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + -- Multiplication + { name := "mul_one", dir := "Multiplication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "zero_mul", dir := "Multiplication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "succ_mul", dir := "Multiplication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "mul_comm", dir := "Multiplication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "one_mul", dir := "Multiplication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "two_mul", dir := "Multiplication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "mul_add", dir := "Multiplication", + hint := "You can do induction on any of the three variables. Some choices\n are harder to push through than others. Can you do the inductive step in\n 5 rewrites only?" }, + { name := "add_mul", dir := "Multiplication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "mul_assoc", dir := "Multiplication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + -- Power + { name := "zero_pow_zero", dir := "Power", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "zero_pow_succ", dir := "Power", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "pow_one", dir := "Power", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "one_pow", dir := "Power", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "pow_two", dir := "Power", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "pow_add", dir := "Power", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "mul_pow", dir := "Power", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "pow_pow", dir := "Power", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "add_sq", dir := "Power", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + -- Implication + { name := "exact", dir := "Implication", + hint := "The goal in this level is one of our hypotheses. Solve the goal by executing `exact h1`." }, + { name := "exact2", dir := "Implication", + hint := "You can use `rw [zero_add] at {h}` to rewrite at `{h}` instead\n of at the goal." }, + { name := "apply", dir := "Implication", + hint := "Start with `apply h2 at h1`. This will change `h1` to `y = 42`." }, + { name := "succ_inj", dir := "Implication", + hint := "Let's first get `h` into the form `succ x = succ 3` so we can\n apply `succ_inj`. First execute `rw [four_eq_succ_three] at h`\n to change the 4 on the right hand side." }, + { name := "succ_inj2", dir := "Implication", + hint := "Start with `apply succ_inj` to apply `succ_inj` to the *goal*." }, + { name := "intro", dir := "Implication", + hint := "Start with `intro h` to assume the hypothesis and call its proof `h`." }, + { name := "intro2", dir := "Implication", + hint := "Start with `intro h` to assume the hypothesis." }, + { name := "ne", dir := "Implication", + hint := "Remember that `h2` is a proof of `x = y → False`. Try\n `apply`ing `h2` either `at h1` or directly to the goal." }, + { name := "zero_ne_one", dir := "Implication", + hint := "Start with `intro h`." }, + { name := "one_ne_zero", dir := "Implication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "two_add_two_ne_five", dir := "Implication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + -- AdvAddition + { name := "add_right_cancel", dir := "AdvAddition", + hint := "Start with induction on `n`." }, + { name := "add_left_cancel", dir := "AdvAddition", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "add_left_eq_self", dir := "AdvAddition", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "add_right_eq_self", dir := "AdvAddition", + hint := "This state is not provable! Did you maybe use `rw [add_left_eq_self] at h`\n instead of `apply [add_left_eq_self] at h`? You can complare the two in the inventory." }, + { name := "add_right_eq_zero", dir := "AdvAddition", + hint := "Here we want to deal with the cases `b = 0` and `b ≠ 0` separately,\n so start with `cases b with d`." }, + { name := "add_left_eq_zero", dir := "AdvAddition", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + -- LessOrEqual + { name := "le_refl", dir := "LessOrEqual", + hint := "The reason `{x} ≤ {x}` is because `{x} = {x} + 0`.\n So you should start this proof with `use 0`." }, + { name := "zero_le", dir := "LessOrEqual", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "le_succ_self", dir := "LessOrEqual", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "le_trans", dir := "LessOrEqual", + hint := "Start with `cases {hxy} with a ha`." }, + { name := "le_zero", dir := "LessOrEqual", + hint := "You want to use `add_right_eq_zero`, which you already\n proved, but you'll have to start with `symm at` your hypothesis." }, + { name := "le_antisymm", dir := "LessOrEqual", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "or_symm", dir := "LessOrEqual", + hint := "We don't know whether to go left or right yet. So start with `cases {h} with hx hy`." }, + { name := "le_total", dir := "LessOrEqual", + hint := "Start with `induction {y} with d hd`." }, + { name := "succ_le_succ", dir := "LessOrEqual", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "le_one", dir := "LessOrEqual", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "le_two", dir := "LessOrEqual", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + -- AdvMultiplication + { name := "mul_le_mul_right", dir := "AdvMultiplication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "mul_left_ne_zero", dir := "AdvMultiplication", + hint := "We want to reduce this to a hypothesis `b = 0` and a goal `a * b = 0`,\n which is logically equivalent but much easier to prove. Remember that `X ≠ 0`\n is notation for `X = 0 → False`. Click on `Show more help!` if you need hints." }, + { name := "eq_succ_of_ne_zero", dir := "AdvMultiplication", + hint := "Start with `cases a with d` to do a case split on `a = 0` and `a = succ d`." }, + { name := "one_le_of_ne_zero", dir := "AdvMultiplication", + hint := "Use the previous lemma with `apply eq_succ_of_ne_zero at ha`." }, + { name := "le_mul_right", dir := "AdvMultiplication", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "mul_right_eq_one", dir := "AdvMultiplication", + hint := "We want to use `le_mul_right`, but we need a hypothesis `x * y ≠ 0`\n which we don't have. Yet. Execute `have h2 : x * y ≠ 0` (you can type `≠` with `\\\ne`).\n You'll be asked to\n prove it, and then you'll have a new hypothesis which you can apply\n `le_mul_right` to." }, + { name := "mul_ne_zero", dir := "AdvMultiplication", + hint := "Start with `apply eq_succ_of_ne_zero at ha` and `... at hb`" }, + { name := "mul_eq_zero", dir := "AdvMultiplication", + hint := "Start with `have h2 := mul_ne_zero a b`." }, + { name := "mul_left_cancel", dir := "AdvMultiplication", + hint := "The way to start this proof is `induction b with d hd generalizing c`." }, + { name := "mul_right_eq_self", dir := "AdvMultiplication", + hint := "Reduce to the previous lemma with `nth_rewrite 2 [← mul_one a] at h`" }, + -- Algorithm + { name := "add_left_comm", dir := "Algorithm", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "add_algo1", dir := "Algorithm", + hint := "Start with `repeat rw [add_assoc]` to push all the brackets to the right." }, + { name := "add_algo2", dir := "Algorithm", + hint := "Solve this level in one line with `simp only [add_left_comm, add_comm]`" }, + { name := "add_algo3", dir := "Algorithm", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "pred", dir := "Algorithm", + hint := "Start with `rw [← pred_succ a]` and take it from there." }, + { name := "succ_ne_zero", dir := "Algorithm", + hint := "Start with `intro h` (remembering that `X ≠ Y` is just notation\n for `X = Y → False`)." }, + { name := "succ_ne_succ", dir := "Algorithm", + hint := "Start with `contrapose! h`, to change the goal into its\n contrapositive, namely a hypothesis of `succ m = succ n` and a goal of `m = n`." }, + { name := "decide", dir := "Algorithm", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, + { name := "decide2", dir := "Algorithm", + hint := "Read the explanation at the top of the exercise file, then fill in the proof." }, +] + +private def nngWelcome : String := + "Welcome to the Natural Number Game!\n\n" ++ + "You will build the natural numbers from scratch — starting from `0` and\n" ++ + "`succ` — and prove theorems about addition, multiplication, powers, and\n" ++ + "inequalities, one tactic at a time.\n\n" ++ + "Each exercise file opens with an explanation; replace the `sorry` with a proof,\n" ++ + "then run `lake exe leanlings run` (or use watch mode).\n\n" ++ + "This course is a port of the Natural Number Game by Kevin Buzzard, Mohammad\n" ++ + "Pedramfar and contributors (https://github.com/leanprover-community/NNG4),\n" ++ + "used under the Apache-2.0 license. See courses/nng/NOTICE.\n" + +private def nngFinal : String := + "Congratulations! You've completed the Natural Number Game port!\n\n" ++ + "You built ℕ from the Peano axioms and proved, among other things:\n" ++ + " - 0 + n = n, commutativity and associativity of addition\n" ++ + " - commutativity and associativity of multiplication, distributivity\n" ++ + " - laws of powers\n" ++ + " - injectivity of succ and the basic theory of ≤\n\n" ++ + "For the full interactive experience (with the original tactic descriptions and\n" ++ + "the worlds you skipped here), play the original at\n" ++ + "https://adam.math.hhu.de/#/g/leanprover-community/nng4\n" + +/-- The Natural Number Game: building ℕ and its basic theory from the Peano +axioms. Ported from leanprover-community/NNG4 (Apache-2.0). -/ +def nng : Course := + mkCourse "nng" "Natural Number Game" + "Build ℕ from scratch and prove its basic theory — 78 levels across 9 worlds." + nngExercises (welcome := nngWelcome) (final := nngFinal) + +def courses : Array Course := #[intro, nng] /-- The course used when none is selected or a stored selection is invalid. -/ def defaultCourse : Course := intro diff --git a/Leanlings/Runner.lean b/Leanlings/Runner.lean index b70b9ea..41e1164 100644 --- a/Leanlings/Runner.lean +++ b/Leanlings/Runner.lean @@ -6,11 +6,16 @@ namespace Leanlings.Runner private def containsSubstr (s sub : String) : Bool := (s.splitOn sub).length > 1 -/-- Check a single exercise by running `lean` on it -/ +/-- Check a single exercise by running `lean` on it. + +We invoke it through `lake env lean` so that exercises which `import` a course +library (e.g. the `nng` course's `MyNat` development) resolve against the built +package. Exercises that import nothing (the `intro` course) are unaffected. +Requires the project to have been built (`lake build`). -/ def checkExercise (exercise : Exercise) : IO ExerciseStatus := do let output ← IO.Process.output { - cmd := "lean" - args := #[exercise.path.toString] + cmd := "lake" + args := #["env", "lean", exercise.path.toString] } -- Check for sorry first — if sorry is present, that's the primary issue -- (test failures caused by sorry are noise the user doesn't need to see) diff --git a/README.md b/README.md index ec46fde..33dcd3e 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,17 @@ Progress is tracked separately per course, so you can move between them without losing your place. Each course has `exercises/` (the files you edit) and `solutions/` (reference answers) under its directory. +Available courses: + +- **`intro`** — Learn Lean 4 as a programming language and proof assistant (the + default; described below). +- **`nng`** — A port of the [Natural Number Game](https://adam.math.hhu.de/#/g/leanprover-community/nng4): + build ℕ from the Peano axioms and prove its basic theory across 78 levels in 9 + worlds (addition, multiplication, powers, ≤, …). Unlike the original, this port + has **no Mathlib dependency** — the `MyNat` development is pure core Lean. It is + used under the Apache-2.0 license; see `courses/nng/NOTICE` for attribution and + the modifications made. + ## What you'll learn The `intro` course's 70 exercises are organized into 27 units across three arcs: diff --git a/courses/nng/LICENSE b/courses/nng/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/courses/nng/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/courses/nng/NOTICE b/courses/nng/NOTICE new file mode 100644 index 0000000..ee17895 --- /dev/null +++ b/courses/nng/NOTICE @@ -0,0 +1,43 @@ +leanlings "nng" course +======================= + +This course is a derivative work of the Natural Number Game (NNG4): + + https://github.com/leanprover-community/NNG4 + Copyright the Natural Number Game contributors + (Kevin Buzzard, Mohammad Pedramfar, and others) + +The Natural Number Game is licensed under the Apache License, Version 2.0. A +copy of that license is included alongside this file as `LICENSE`. You may +obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + +Modifications +------------- + +This course is NOT the original game. It has been substantially modified for +use inside leanlings, a standalone command-line exercise runner. In particular: + + * The dependency on Mathlib and on the `lean4game` GameServer framework has + been removed. The `MyNat` development under `solutions/MyNatLib/` is a + rewrite using only core Lean 4, and the small tactic conveniences the game + relies on (`induction … with`, `cases … with`, `use`, `apply … at`, + `nth_rewrite`, `symm at`) are reimplemented there in core Lean. + + * Each game "level" has been converted from the GameServer DSL + (`Statement`/`Hint`/`Branch`/…) into a plain leanlings exercise (the file + you edit, under `exercises/`) plus a reference solution (under + `solutions/`). The instructional text from each level's `Introduction` + is preserved as a comment at the top of the corresponding exercise. + + * A few proofs were rewritten to avoid Mathlib-only tactics (`tauto`, + `contrapose`, `decide`) using equivalent core-Lean reasoning. + + * The joke "Fermat's Last Theorem" level (whose intended solution is the + game's `xyzzy`/`sorry` cheat) has been omitted. Some advanced/WIP worlds + from the original are not included. + +Individual ported files carry a short header noting this provenance, per +section 4(b) of the License. Original tactic/theorem documentation and the full +interactive experience remain available in the upstream game. diff --git a/courses/nng/exercises/Addition/add_assoc.lean b/courses/nng/exercises/Addition/add_assoc.lean new file mode 100644 index 0000000..1c176f6 --- /dev/null +++ b/courses/nng/exercises/Addition/add_assoc.lean @@ -0,0 +1,23 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Addition.add_comm + +namespace MyNat + +/- +We've been adding up two numbers; in this level we will add up three. + + What does $x+y+z$ *mean*? It could either mean $(x+y)+z$, or it + could mean $x+(y+z)$. In Lean, $x+y+z$ means $(x+y)+z$. + + But why do we care which one it means; $(x+y)+z$ and $x+(y+z)$ are *equal*! + + That's true, but we didn't prove it yet. Let's prove it now by induction. +-/ +/-- On the set of natural numbers, addition is associative. +In other words, if $a, b$ and $c$ are arbitrary natural numbers, we have +$ (a + b) + c = a + (b + c). $ -/ +theorem add_assoc (a b c : ℕ) : a + b + c = a + (b + c) := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Addition/add_comm.lean b/courses/nng/exercises/Addition/add_comm.lean new file mode 100644 index 0000000..94fa887 --- /dev/null +++ b/courses/nng/exercises/Addition/add_comm.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Addition.succ_add + +namespace MyNat + +/- +[boss battle music] + +Look in your inventory to see the proofs you have available. +These should be enough. +-/ +/-- On the set of natural numbers, addition is commutative. +In other words, if `a` and `b` are arbitrary natural numbers, then +$a + b = b + a$. -/ +theorem add_comm (a b : ℕ) : a + b = b + a := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Addition/add_right_comm.lean b/courses/nng/exercises/Addition/add_right_comm.lean new file mode 100644 index 0000000..4a29028 --- /dev/null +++ b/courses/nng/exercises/Addition/add_right_comm.lean @@ -0,0 +1,27 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Addition.add_assoc + +namespace MyNat + +/- +`add_comm b c` is a proof that `b + c = c + b`. But if your goal +is `a + b + c = a + c + b` then `rw [add_comm b c]` will not +work! Because the goal means `(a + b) + c = (a + c) + b` so there +is no `b + c` term *directly* in the goal. + +Use associativity and commutativity to prove `add_right_comm`. +You don't need induction. `add_assoc` moves brackets around, +and `add_comm` moves variables around. + +Remember that you can do more targeted rewrites by +adding explicit variables as inputs to theorems. For example `rw [add_comm b]` +will only do rewrites of the form `b + ? = ? + b`, and `rw [add_comm b c]` +will only do rewrites of the form `b + c = c + b`. +-/ +/-- If $a, b$ and $c$ are arbitrary natural numbers, we have +$(a + b) + c = (a + c) + b$. -/ +theorem add_right_comm (a b c : ℕ) : a + b + c = a + c + b := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Addition/succ_add.lean b/courses/nng/exercises/Addition/succ_add.lean new file mode 100644 index 0000000..d9d544f --- /dev/null +++ b/courses/nng/exercises/Addition/succ_add.lean @@ -0,0 +1,21 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Addition.zero_add + +namespace MyNat + +/- +Oh no! On the way to `add_comm`, a wild `succ_add` appears. `succ_add a b` +is the proof that `(succ a) + b = succ (a + b)` for `a` and `b` numbers. +This result is what's standing in the way of `x + y = y + x`. Again +we have the problem that we are adding `b` to things, so we need +to use induction to split into the cases where `b = 0` and `b` is a successor. +-/ +/-- +For all natural numbers $a, b$, we have +$ \operatorname{succ}(a) + b = \operatorname{succ}(a + b)$. +-/ +theorem succ_add (a b : ℕ) : succ a + b = succ (a + b) := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Addition/zero_add.lean b/courses/nng/exercises/Addition/zero_add.lean new file mode 100644 index 0000000..3595d69 --- /dev/null +++ b/courses/nng/exercises/Addition/zero_add.lean @@ -0,0 +1,30 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.twoaddtwo + +namespace MyNat + +/- +In this level we're going to prove that $0+n=n$, where $n$ is a secret natural number. + +Wait, don't we already know that? No! We know that $n+0=n$, but that's `add_zero`. +This is `zero_add`, which is different. + +The difficulty with proving `0 + n = n` is that we do not have a *formula* for +`0 + n` in general, we can only use `add_zero` and `add_succ` once +we know whether `n` is `0` or a successor. The `induction` tactic splits into these two cases. + +The base case will require us to prove `0 + 0 = 0`, and the inductive step +will ask us to show that if `0 + d = d` then `0 + succ d = succ d`. Because +`0` and successor are the only way to make numbers, this will cover all the cases. + +See if you can do your first induction proof in Lean. + +(By the way, if you are still in the "Editor mode" from the last world, you can swap +back to "Typewriter mode" by clicking the `>_` button in the top right.) +-/ +/-- For all natural numbers $n$, we have $0 + n = n$. -/ +theorem zero_add (n : ℕ) : 0 + n = n := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvAddition/add_left_cancel.lean b/courses/nng/exercises/AdvAddition/add_left_cancel.lean new file mode 100644 index 0000000..4d23b12 --- /dev/null +++ b/courses/nng/exercises/AdvAddition/add_left_cancel.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_right_cancel + +namespace MyNat + +/- +`add_left_cancel a b n` is the theorem that $n+a=n+b\implies a=b$. +You can prove it by induction on `n` or you can deduce it from `add_right_cancel`. +-/ +/-- $n+a=n+b\implies a=b$. -/ +theorem add_left_cancel (a b n : ℕ) : n + a = n + b → a = b := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvAddition/add_left_eq_self.lean b/courses/nng/exercises/AdvAddition/add_left_eq_self.lean new file mode 100644 index 0000000..762c45f --- /dev/null +++ b/courses/nng/exercises/AdvAddition/add_left_eq_self.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_left_cancel + +namespace MyNat + +/- +`add_left_eq_self x y` is the theorem that $x + y = y\implies x=0.$ +-/ +/-- $x + y = y\implies x=0.$ -/ +theorem add_left_eq_self (x y : ℕ) : x + y = y → x = 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvAddition/add_left_eq_zero.lean b/courses/nng/exercises/AdvAddition/add_left_eq_zero.lean new file mode 100644 index 0000000..835c9f4 --- /dev/null +++ b/courses/nng/exercises/AdvAddition/add_left_eq_zero.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_right_eq_zero + +namespace MyNat + +/- +You can just mimic the previous proof to do this one -- or you can figure out a way +of using it. +-/ +/-- If $a+b=0$ then $b=0$. -/ +theorem add_left_eq_zero (a b : ℕ) : a + b = 0 → b = 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvAddition/add_right_cancel.lean b/courses/nng/exercises/AdvAddition/add_right_cancel.lean new file mode 100644 index 0000000..f3f616f --- /dev/null +++ b/courses/nng/exercises/AdvAddition/add_right_cancel.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.two_add_two_ne_five + +namespace MyNat + +/- +In this world I will mostly leave you on your own. + +`add_right_cancel a b n` is the theorem that $a+n=b+n\implies a=b$. +-/ +/-- $a+n=b+n\implies a=b$. -/ +theorem add_right_cancel (a b n : ℕ) : a + n = b + n → a = b := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvAddition/add_right_eq_self.lean b/courses/nng/exercises/AdvAddition/add_right_eq_self.lean new file mode 100644 index 0000000..943544e --- /dev/null +++ b/courses/nng/exercises/AdvAddition/add_right_eq_self.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_left_eq_self + +namespace MyNat + +/- +`add_right_eq_self x y` is the theorem that $x + y = x\implies y=0.$ +Two ways to do it spring to mind; I'll mention them when you've solved it. +-/ +/-- $x+y=x\implies y=0$. -/ +theorem add_right_eq_self (x y : ℕ) : x + y = x → y = 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvAddition/add_right_eq_zero.lean b/courses/nng/exercises/AdvAddition/add_right_eq_zero.lean new file mode 100644 index 0000000..bb3440b --- /dev/null +++ b/courses/nng/exercises/AdvAddition/add_right_eq_zero.lean @@ -0,0 +1,30 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_right_eq_self + +namespace MyNat + +/- +The next result we'll need in `≤` World is that if `a + b = 0` then `a = 0` and `b = 0`. +Let's prove one of these facts in this level, and the other in the next. + +## A new tactic: `cases` + +The `cases` tactic will split an object or hypothesis up into the possible ways +that it could have been created. + +For example, sometimes you want to deal with the two cases `b = 0` and `b = succ d` separately, +but don't need the inductive hypothesis `hd` that comes with `induction b with d hd`. +In this situation you can use `cases b with d` instead. There are two ways to make +a number: it's either zero or a successor. So you will end up with two goals, one +with `b = 0` and one with `b = succ d`. + +Another example: if you have a hypothesis `h : False` then you are done, because a false statement implies +any statement. Here `cases h` will close the goal, because there are *no* ways to +make a proof of `False`! So you will end up with no goals, meaning you have proved everything. +-/ +/-- If $a+b=0$ then $a=0$. -/ +theorem add_right_eq_zero (a b : ℕ) : a + b = 0 → a = 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvMultiplication/eq_succ_of_ne_zero.lean b/courses/nng/exercises/AdvMultiplication/eq_succ_of_ne_zero.lean new file mode 100644 index 0000000..49a5b43 --- /dev/null +++ b/courses/nng/exercises/AdvMultiplication/eq_succ_of_ne_zero.lean @@ -0,0 +1,17 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_left_ne_zero + +namespace MyNat + +/- +Multiplication usually makes a number bigger, but multiplication by zero can make +it smaller. Thus many lemmas about inequalities and multiplication need the +hypothesis `a ≠ 0`. Here is a key lemma that enables us to use this hypothesis. +To help us with the proof, we can use the `tauto` tactic. Click on the tactic's name +on the right to see what it does. +-/ +theorem eq_succ_of_ne_zero (a : ℕ) (ha : a ≠ 0) : ∃ n, a = succ n := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvMultiplication/le_mul_right.lean b/courses/nng/exercises/AdvMultiplication/le_mul_right.lean new file mode 100644 index 0000000..31e3f0f --- /dev/null +++ b/courses/nng/exercises/AdvMultiplication/le_mul_right.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.one_le_of_ne_zero + +namespace MyNat + +/- +One day this game will have a Prime Number World, with a final boss +of proving that $2$ is prime. +To do this, we will have to rule out things like $2 = 37 × 42.$ +We will do this by proving that any factor of $2$ is at most $2$, +which we will do using this lemma. The proof I have in mind manipulates the hypothesis +until it becomes the goal, using `mul_left_ne_zero`, `one_le_of_ne_zero` and +`mul_le_mul_right`. +-/ +theorem le_mul_right (a b : ℕ) (h : a * b ≠ 0) : a ≤ a * b := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvMultiplication/mul_eq_zero.lean b/courses/nng/exercises/AdvMultiplication/mul_eq_zero.lean new file mode 100644 index 0000000..2dcbdca --- /dev/null +++ b/courses/nng/exercises/AdvMultiplication/mul_eq_zero.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_ne_zero + +namespace MyNat + +/- +This level proves that if `a * b = 0` then `a = 0` or `b = 0`. It is +logically equivalent to the last level, so there is a very short proof. +-/ +theorem mul_eq_zero (a b : ℕ) (h : a * b = 0) : a = 0 ∨ b = 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvMultiplication/mul_le_mul_right.lean b/courses/nng/exercises/AdvMultiplication/mul_le_mul_right.lean new file mode 100644 index 0000000..cbe4c35 --- /dev/null +++ b/courses/nng/exercises/AdvMultiplication/mul_le_mul_right.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_two + +namespace MyNat + +/- +Let's warm up with an easy one, which works even if `t = 0`. +-/ +theorem mul_le_mul_right (a b t : ℕ) (h : a ≤ b) : a * t ≤ b * t := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvMultiplication/mul_left_cancel.lean b/courses/nng/exercises/AdvMultiplication/mul_left_cancel.lean new file mode 100644 index 0000000..ec3cc7a --- /dev/null +++ b/courses/nng/exercises/AdvMultiplication/mul_left_cancel.lean @@ -0,0 +1,24 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_eq_zero + +namespace MyNat + +/- +In this level we prove that if `a * b = a * c` and `a ≠ 0` then `b = c`. It is tricky, for +several reasons. One of these is that +we need to introduce a new idea: we will need to understand the concept of +mathematical induction a little better. + +Starting with `induction b with d hd` is too naive, because in the inductive step +the hypothesis is `a * d = a * c → d = c` but what we know is `a * succ d = a * c`, +so the induction hypothesis does not apply! + +Assume `a ≠ 0` is fixed. The actual statement we want to prove by induction on `b` is +"for all `c`, if `a * b = a * c` then `b = c`". This *can* be proved by induction, +because we now have the flexibility to change `c`. +-/ +theorem mul_left_cancel (a b c : ℕ) (ha : a ≠ 0) (h : a * b = a * c) : b = c := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvMultiplication/mul_left_ne_zero.lean b/courses/nng/exercises/AdvMultiplication/mul_left_ne_zero.lean new file mode 100644 index 0000000..568f0b3 --- /dev/null +++ b/courses/nng/exercises/AdvMultiplication/mul_left_ne_zero.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_le_mul_right + +namespace MyNat + +/- +If you have completed Algorithm World then you can use the `contrapose!` tactic +here. If not then I'll talk you through a manual approach. +-/ +theorem mul_left_ne_zero (a b : ℕ) (h : a * b ≠ 0) : b ≠ 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvMultiplication/mul_ne_zero.lean b/courses/nng/exercises/AdvMultiplication/mul_ne_zero.lean new file mode 100644 index 0000000..c67b8b5 --- /dev/null +++ b/courses/nng/exercises/AdvMultiplication/mul_ne_zero.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_right_eq_one + +namespace MyNat + +/- +This level proves that if `a ≠ 0` and `b ≠ 0` then `a * b ≠ 0`. One strategy +is to write both `a` and `b` as `succ` of something, deduce that `a * b` is +also `succ` of something, and then `apply zero_ne_succ`. +-/ +theorem mul_ne_zero (a b : ℕ) (ha : a ≠ 0) (hb : b ≠ 0) : a * b ≠ 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvMultiplication/mul_right_eq_one.lean b/courses/nng/exercises/AdvMultiplication/mul_right_eq_one.lean new file mode 100644 index 0000000..7308ccf --- /dev/null +++ b/courses/nng/exercises/AdvMultiplication/mul_right_eq_one.lean @@ -0,0 +1,17 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.le_mul_right + +namespace MyNat + +/- +This level proves `x * y = 1 → x = 1`, the multiplicative analogue of Advanced Addition +World's `x + y = 0 → x = 0`. The strategy is to prove that `x ≤ 1` and then use the +lemma `le_one` from `≤` world. + +We'll prove it using a new and very useful tactic called `have`. +-/ +theorem mul_right_eq_one (x y : ℕ) (h : x * y = 1) : x = 1 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvMultiplication/mul_right_eq_self.lean b/courses/nng/exercises/AdvMultiplication/mul_right_eq_self.lean new file mode 100644 index 0000000..752ce35 --- /dev/null +++ b/courses/nng/exercises/AdvMultiplication/mul_right_eq_self.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_left_cancel + +namespace MyNat + +/- +The lemma proved in the final level of this world will be helpful +in Divisibility World. +-/ +theorem mul_right_eq_self (a b : ℕ) (ha : a ≠ 0) (h : a * b = a) : b = 1 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/AdvMultiplication/one_le_of_ne_zero.lean b/courses/nng/exercises/AdvMultiplication/one_le_of_ne_zero.lean new file mode 100644 index 0000000..d69dd2b --- /dev/null +++ b/courses/nng/exercises/AdvMultiplication/one_le_of_ne_zero.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.eq_succ_of_ne_zero + +namespace MyNat + +/- +The previous lemma can be used to prove this one. +-/ +theorem one_le_of_ne_zero (a : ℕ) (ha : a ≠ 0) : 1 ≤ a := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Algorithm/add_algo1.lean b/courses/nng/exercises/Algorithm/add_algo1.lean new file mode 100644 index 0000000..3dcf3f4 --- /dev/null +++ b/courses/nng/exercises/Algorithm/add_algo1.lean @@ -0,0 +1,24 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.add_left_comm + +namespace MyNat + +/- +In some later worlds, we're going to see some much nastier levels, +like `(a + a + 1) + (b + b + 1) = (a + b + 1) + (a + b + 1)`. +Brackets need to be moved around, and variables need to be swapped. + +In this level, `(a + b) + (c + d) = ((a + c) + d) + b`, +let's forget about the brackets and just think about +the variable order. +To turn `a+b+c+d` into `a+c+d+b` we need to swap `b` and `c`, +and then swap `b` and `d`. But this is easier than you +think with `add_left_comm`. +-/ +/- If $a, b$, $c$ and $d$ are numbers, we have +$(a + b) + (c + d) = ((a + c) + d) + b.$ -/ +example (a b c d : ℕ) : a + b + (c + d) = a + c + d + b := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Algorithm/add_algo2.lean b/courses/nng/exercises/Algorithm/add_algo2.lean new file mode 100644 index 0000000..58cd7a7 --- /dev/null +++ b/courses/nng/exercises/Algorithm/add_algo2.lean @@ -0,0 +1,20 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.add_algo1 + +namespace MyNat + +/- +Lean's simplifier, `simp`, is "`rw` on steroids". It will rewrite every lemma +tagged with `simp` and every lemma fed to it by the user, as much as it can. + +This level is not a level which you want to solve by hand. +Get the simplifier to solve it for you. +-/ +/- If $a, b,\ldots h$ are arbitrary natural numbers, we have +$(d + f) + (h + (a + c)) + (g + e + b) = a + b + c + d + e + f + g + h$. -/ +example (a b c d e f g h : ℕ) : + (d + f) + (h + (a + c)) + (g + e + b) = a + b + c + d + e + f + g + h := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Algorithm/add_algo3.lean b/courses/nng/exercises/Algorithm/add_algo3.lean new file mode 100644 index 0000000..7437ef4 --- /dev/null +++ b/courses/nng/exercises/Algorithm/add_algo3.lean @@ -0,0 +1,24 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.add_algo2 + +namespace MyNat + +/- +You can make your own tactics in Lean. +This code here +``` +macro "simp_add" : tactic => `(tactic|( + simp only [add_assoc, add_left_comm, add_comm])) +``` +was used to create a new tactic `simp_add`, which runs +`simp only [add_assoc, add_left_comm, add_comm]`. +Try running `simp_add` to solve this level! +-/ +/- If $a, b,\ldots h$ are arbitrary natural numbers, we have +$(d + f) + (h + (a + c)) + (g + e + b) = a + b + c + d + e + f + g + h$. -/ +example (a b c d e f g h : ℕ) : + (d + f) + (h + (a + c)) + (g + e + b) = a + b + c + d + e + f + g + h := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Algorithm/add_left_comm.lean b/courses/nng/exercises/Algorithm/add_left_comm.lean new file mode 100644 index 0000000..6cd20c5 --- /dev/null +++ b/courses/nng/exercises/Algorithm/add_left_comm.lean @@ -0,0 +1,21 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_right_eq_self + +namespace MyNat + +/- +Having to rearrange variables manually using commutativity and +associativity is very tedious. We start by reminding you of this. `add_left_comm` +is a key component in the first algorithm which we'll explain, but we need +to prove it manually. + +Remember that you can do precision commutativity rewriting +with things like `rw [add_comm b c]`. And remember that +`a + b + c` means `(a + b) + c`. +-/ +/-- If $a, b, c$ are numbers, then $a+(b+c)=b+(a+c)$. -/ +theorem add_left_comm (a b c : ℕ) : a + (b + c) = b + (a + c) := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Algorithm/decide.lean b/courses/nng/exercises/Algorithm/decide.lean new file mode 100644 index 0000000..b5604f8 --- /dev/null +++ b/courses/nng/exercises/Algorithm/decide.lean @@ -0,0 +1,42 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.succ_ne_succ + +namespace MyNat + +/- +Implementing the algorithm for equality of naturals, and the proof that it is correct, +looks like this: + +``` +instance instDecidableEq : DecidableEq ℕ +| 0, 0 => isTrue <| by + show 0 = 0 + rfl +| succ m, 0 => isFalse <| by + show succ m ≠ 0 + exact succ_ne_zero m +| 0, succ n => isFalse <| by + show 0 ≠ succ n + exact zero_ne_succ n +| succ m, succ n => + match instDecidableEq m n with + | isTrue (h : m = n) => isTrue <| by + show succ m = succ n + rw [h] + rfl + | isFalse (h : m ≠ n) => isFalse <| by + show succ m ≠ succ n + exact succ_ne_succ m n h +``` + +This Lean code is a formally verified algorithm for deciding equality +between two naturals. I've typed it in already, behind the scenes. +Because the algorithm is formally verified to be correct, we can +use it in Lean proofs. You can run the algorithm with the `decide` tactic. +-/ +/- $20+20=40$. -/ +example : (20 : ℕ) + 20 = 40 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Algorithm/decide2.lean b/courses/nng/exercises/Algorithm/decide2.lean new file mode 100644 index 0000000..177f23b --- /dev/null +++ b/courses/nng/exercises/Algorithm/decide2.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.decide + +namespace MyNat + +/- +We gave a pretty unsatisfactory proof of `2 + 2 ≠ 5` earlier on; now give a nicer one. +-/ +/- $2+2 \neq 5.$ -/ +example : (2 : ℕ) + 2 ≠ 5 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Algorithm/pred.lean b/courses/nng/exercises/Algorithm/pred.lean new file mode 100644 index 0000000..823bd27 --- /dev/null +++ b/courses/nng/exercises/Algorithm/pred.lean @@ -0,0 +1,30 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.add_algo3 + +namespace MyNat + +/- +We now start work on an algorithm to do addition more efficiently. Recall that +we defined addition by recursion, saying what it did on `0` and successors. +It is an axiom of Lean that recursion is a valid +way to define functions from types such as the naturals. + +Let's define a new function `pred` from the naturals to the naturals, which +attempts to subtract 1 from the input. The definition is this: + +``` +pred 0 := 37 +pred (succ n) := n +``` + +We cannot subtract one from 0, so we just return a junk value. As well as this +definition, we also create a new lemma `pred_succ`, which says that `pred (succ n) = n`. +Let's use this lemma to prove `succ_inj`, the theorem which +Peano assumed as an axiom and which we have already used extensively without justification. +-/ +/- If $\operatorname{succ}(a)=\operatorname{succ}(b)$ then $a=b$. -/ +example (a b : ℕ) (h : succ a = succ b) : a = b := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Algorithm/succ_ne_succ.lean b/courses/nng/exercises/Algorithm/succ_ne_succ.lean new file mode 100644 index 0000000..e80c96e --- /dev/null +++ b/courses/nng/exercises/Algorithm/succ_ne_succ.lean @@ -0,0 +1,31 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.succ_ne_zero + +namespace MyNat + +/- +Here we begin to +develop an algorithm which, given two naturals `a` and `b`, returns the answer +to "does `a = b`?" + +Here is the algorithm. First note that `a` and `b` are numbers, and hence +are either `0` or successors. + +*) If `a` and `b` are both `0`, return "yes". + +*) If one is `0` and the other is `succ n`, return "no". + +*) If `a = succ m` and `b = succ n`, then return the answer to "does `m = n`?" + +Our job now is to *prove* that this algorithm always gives the correct answer. The proof that +`0 = 0` is `rfl`. The proof that `0 ≠ succ n` is `zero_ne_succ n`, and the proof +that `succ m ≠ 0` is `succ_ne_zero m`. The proof that if `h : m = n` then +`succ m = succ n` is `rw [h]` and then `rfl`. This level is a proof of the one +remaining job we have to do: if `a ≠ b` then `succ a ≠ succ b`. +-/ +/-- If $a \neq b$ then $\operatorname{succ}(a) \neq\operatorname{succ}(b)$. -/ +theorem succ_ne_succ (m n : ℕ) (h : m ≠ n) : succ m ≠ succ n := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Algorithm/succ_ne_zero.lean b/courses/nng/exercises/Algorithm/succ_ne_zero.lean new file mode 100644 index 0000000..6ee910f --- /dev/null +++ b/courses/nng/exercises/Algorithm/succ_ne_zero.lean @@ -0,0 +1,28 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.pred + +namespace MyNat + +/- +We define a function `is_zero` thus: + +``` +is_zero 0 := True +is_zero (succ n) := False +``` + +We also create two lemmas, `is_zero_zero` and `is_zero_succ n`, saying that `is_zero 0 = True` +and `is_zero (succ n) = False`. Let's use these lemmas to prove `succ_ne_zero`, Peano's +Last Axiom. Actually, we have been using `zero_ne_succ` before, but it's handy to have +this opposite version too, which can be proved in the same way. Note: you can +cheat here by using `zero_ne_succ` but the point of this world is to show +you how to *prove* results like that. + +If you can turn your goal into `True`, then the `trivial` tactic will solve it. +-/ +/-- $\operatorname{succ}(a) \neq 0$. -/ +theorem succ_ne_zero (a : ℕ) : succ a ≠ 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Implication/apply.lean b/courses/nng/exercises/Implication/apply.lean new file mode 100644 index 0000000..c3444f7 --- /dev/null +++ b/courses/nng/exercises/Implication/apply.lean @@ -0,0 +1,18 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.exact2 + +namespace MyNat + +/- +In this level, the hypothesis `h2` is an *implication*. It says +that *if* `x = 37` *then* `y = 42`. We can use this +hypothesis with the `apply` tactic. Remember you can click on +`apply` or any other tactic on the right to see a detailed explanation +of what it does, with examples. +-/ +/- If $x=37$ and we know that $x=37\implies y=42$ then we can deduce $y=42$. -/ +example (x y : ℕ) (h1 : x = 37) (h2 : x = 37 → y = 42) : y = 42 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Implication/exact.lean b/courses/nng/exercises/Implication/exact.lean new file mode 100644 index 0000000..3e66b59 --- /dev/null +++ b/courses/nng/exercises/Implication/exact.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.add_sq + +namespace MyNat + +/- +In this world we'll learn how to prove theorems of the form $P\implies Q$. +In other words, how to prove theorems of the form "if $P$ is true, then $Q$ is true." +To do that we need to learn some more tactics. + +The `exact` tactic can be used to close a goal which is exactly one of +the hypotheses. It takes the name of the hypothesis as argument: `exact h`. +-/ +/- Assuming $x+y=37$ and $3x+z=42$, we have $x+y=37$. -/ +example (x y z : ℕ) (h1 : x + y = 37) (h2 : 3 * x + z = 42) : x + y = 37 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Implication/exact2.lean b/courses/nng/exercises/Implication/exact2.lean new file mode 100644 index 0000000..7bba234 --- /dev/null +++ b/courses/nng/exercises/Implication/exact2.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.exact + +namespace MyNat + +/- +If the goal is not *exactly* a hypothesis, we can sometimes +use rewrites to fix things up. +-/ +/- Assuming $0+x=(0+y)+2$, we have $x=y+2$. -/ +example (x y : ℕ) (h : 0 + x = 0 + y + 2) : x = y + 2 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Implication/intro.lean b/courses/nng/exercises/Implication/intro.lean new file mode 100644 index 0000000..089c095 --- /dev/null +++ b/courses/nng/exercises/Implication/intro.lean @@ -0,0 +1,17 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.succ_inj2 + +namespace MyNat + +/- +We have seen how to `apply` theorems and assumptions +of the form `P → Q`. But what if our *goal* is of the form `P → Q`? +To prove this goal, we need to know how to say "let's assume `P` and deduce `Q`" +in Lean. We do this with the `intro` tactic. +-/ +/- $x=37\implies x=37$. -/ +example (x : ℕ) : x = 37 → x = 37 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Implication/intro2.lean b/courses/nng/exercises/Implication/intro2.lean new file mode 100644 index 0000000..12ffbef --- /dev/null +++ b/courses/nng/exercises/Implication/intro2.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.intro + +namespace MyNat + +/- +Let's see if you can use the tactics we've learnt to prove $x+1=y+1\implies x=y$. +Try this one by yourself; if you need help then click on "Show more help!". +-/ +/- $x+1=y+1 \implies x=y$. -/ +example (x y : ℕ) : x + 1 = y + 1 → x = y := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Implication/ne.lean b/courses/nng/exercises/Implication/ne.lean new file mode 100644 index 0000000..4c3fea7 --- /dev/null +++ b/courses/nng/exercises/Implication/ne.lean @@ -0,0 +1,26 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.intro2 + +namespace MyNat + +/- +We still can't prove `2 + 2 ≠ 5` because we have not talked about the +definition of `≠`. In Lean, `a ≠ b` is *notation* for `a = b → False`. +Here `False` is a generic false proposition, and `→` is Lean's notation +for "implies". In logic we learn +that `True → False` is false, but `False → False` is true. Hence +`X → False` is the logical opposite of `X`. + +Even though `a ≠ b` does not look like an implication, +you should treat it as an implication. The next two levels will show you how. + +`False` is a goal which you cannot deduce from a consistent set of assumptions! +So if your goal is `False` then you had better hope that your hypotheses +are contradictory, which they are in this level. +-/ +/- If $x=y$ and $x \neq y$ then we can deduce a contradiction. -/ +example (x y : ℕ) (h1 : x = y) (h2 : x ≠ y) : False := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Implication/one_ne_zero.lean b/courses/nng/exercises/Implication/one_ne_zero.lean new file mode 100644 index 0000000..dc334d9 --- /dev/null +++ b/courses/nng/exercises/Implication/one_ne_zero.lean @@ -0,0 +1,22 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.zero_ne_one + +namespace MyNat + +/- +We know `zero_ne_succ n` is a proof of `0 = succ n → False` -- but what +if we have a hypothesis `succ n = 0`? It's the wrong way around! + +The `symm` tactic changes a goal `x = y` to `y = x`, and a goal `x ≠ y` +to `y ≠ x`. And `symm at h` +does the same for a hypothesis `h`. We've proved $0 \ +eq 1$ and called +the proof `zero_ne_one`; now try proving $1 \ +eq 0$. +-/ +/-- $1\neq0$. -/ +theorem one_ne_zero : (1 : ℕ) ≠ 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Implication/succ_inj.lean b/courses/nng/exercises/Implication/succ_inj.lean new file mode 100644 index 0000000..3a98438 --- /dev/null +++ b/courses/nng/exercises/Implication/succ_inj.lean @@ -0,0 +1,22 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.apply + +namespace MyNat + +/- +If `a` and `b` are numbers, then `succ_inj a b` is a proof +that `succ a = succ b` implies `a = b`. Click on this theorem in the *Peano* +tab for more information. + +Peano had this theorem as an axiom, but in Algorithm World +we will show how to prove it in Lean. Right now let's just assume it, +and let's prove $x+1=4 \implies x=3$ using it. Again, we will proceed +by manipulating our hypothesis until it becomes the goal. I will +walk you through this level. +-/ +/- If $x+1=4$ then $x=3$. -/ +example (x : ℕ) (h : x + 1 = 4) : x = 3 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Implication/succ_inj2.lean b/courses/nng/exercises/Implication/succ_inj2.lean new file mode 100644 index 0000000..6a32559 --- /dev/null +++ b/courses/nng/exercises/Implication/succ_inj2.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.succ_inj + +namespace MyNat + +/- +In the last level, we manipulated the hypothesis `x + 1 = 4` + until it became the goal `x = 3`. In this level we'll manipulate + the goal until it becomes our hypothesis! In other words, we + will "argue backwards". The `apply` tactic can do this too. + Again I will walk you through this one (assuming you're in + command line mode). +-/ +/- If $x+1=4$ then $x=3$. -/ +example (x : ℕ) (h : x + 1 = 4) : x = 3 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Implication/two_add_two_ne_five.lean b/courses/nng/exercises/Implication/two_add_two_ne_five.lean new file mode 100644 index 0000000..dc3a27b --- /dev/null +++ b/courses/nng/exercises/Implication/two_add_two_ne_five.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.one_ne_zero + +namespace MyNat + +/- +2 + 2 ≠ 5 is boring to prove in full, given only the tools we have currently. +To make it a bit less painful, I have unfolded all of the numerals for you. +See if you can use `zero_ne_succ` and `succ_inj` to prove this. +-/ +/- $2+2≠5$. -/ +example : succ (succ 0) + succ (succ 0) ≠ succ (succ (succ (succ (succ 0)))) := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Implication/zero_ne_one.lean b/courses/nng/exercises/Implication/zero_ne_one.lean new file mode 100644 index 0000000..be2d529 --- /dev/null +++ b/courses/nng/exercises/Implication/zero_ne_one.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.ne + +namespace MyNat + +/- +As warm-up for `2 + 2 ≠ 5` let's prove `0 ≠ 1`. To do this we need to +introduce Peano's last axiom `zero_ne_succ n`, a proof that `0 ≠ succ n`. +To learn about this result, click on it in the list of lemmas on the right. +-/ +/-- $0\neq1$. -/ +theorem zero_ne_one : (0 : ℕ) ≠ 1 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/LessOrEqual/le_antisymm.lean b/courses/nng/exercises/LessOrEqual/le_antisymm.lean new file mode 100644 index 0000000..40609dd --- /dev/null +++ b/courses/nng/exercises/LessOrEqual/le_antisymm.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_zero + +namespace MyNat + +/- +This level asks you to prove *antisymmetry* of $\leq$. +In other words, if $x \leq y$ and $y \leq x$ then $x = y$. +It's the trickiest one so far. Good luck! +-/ +/-- If $x \leq y$ and $y \leq x$, then $x = y$. -/ +theorem le_antisymm (x y : ℕ) (hxy : x ≤ y) (hyx : y ≤ x) : x = y := by + sorry + +end MyNat diff --git a/courses/nng/exercises/LessOrEqual/le_one.lean b/courses/nng/exercises/LessOrEqual/le_one.lean new file mode 100644 index 0000000..1cad1ef --- /dev/null +++ b/courses/nng/exercises/LessOrEqual/le_one.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.succ_le_succ + +namespace MyNat + +/- +We've seen `le_zero`, the proof that if `x ≤ 0` then `x = 0`. +Now we'll prove that if `x ≤ 1` then `x = 0` or `x = 1`. +-/ +/-- If $x \leq 1$ then either $x = 0$ or $x = 1$. -/ +theorem le_one (x : ℕ) (hx : x ≤ 1) : x = 0 ∨ x = 1 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/LessOrEqual/le_refl.lean b/courses/nng/exercises/LessOrEqual/le_refl.lean new file mode 100644 index 0000000..14599e5 --- /dev/null +++ b/courses/nng/exercises/LessOrEqual/le_refl.lean @@ -0,0 +1,20 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_left_eq_zero + +namespace MyNat + +/- +`a ≤ b` is *notation* for `∃ c, b = a + c`. This "backwards E" +means "there exists". So `a ≤ b` means that there exists +a number `c` such that `b = a + c`. This definition works +because there are no negative numbers in this game. + +To *prove* an "exists" statement, use the `use` tactic. +Let's see an example. +-/ +/-- If $x$ is a number, then $x \le x$. -/ +theorem le_refl (x : ℕ) : x ≤ x := by + sorry + +end MyNat diff --git a/courses/nng/exercises/LessOrEqual/le_succ_self.lean b/courses/nng/exercises/LessOrEqual/le_succ_self.lean new file mode 100644 index 0000000..58f6866 --- /dev/null +++ b/courses/nng/exercises/LessOrEqual/le_succ_self.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.zero_le + +namespace MyNat + +/- +If you `use` the wrong number, you get stuck with a goal you can't prove. +What number will you `use` here? +-/ +/-- If $x$ is a number, then $x \le \operatorname{succ}(x)$. -/ +theorem le_succ_self (x : ℕ) : x ≤ succ x := by + sorry + +end MyNat diff --git a/courses/nng/exercises/LessOrEqual/le_total.lean b/courses/nng/exercises/LessOrEqual/le_total.lean new file mode 100644 index 0000000..86d0e35 --- /dev/null +++ b/courses/nng/exercises/LessOrEqual/le_total.lean @@ -0,0 +1,20 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.or_symm + +namespace MyNat + +/- +This is I think the toughest level yet. Tips: if `a` is a number +then `cases a with b` will split into cases `a = 0` and `a = succ b`. +And don't go left or right until your hypotheses guarantee that +you can prove the resulting goal! + +I've left hidden hints; if you need them, retry from the beginning +and click on "Show more help!" +-/ +/-- If $x$ and $y$ are numbers, then either $x \leq y$ or $y \leq x$. -/ +theorem le_total (x y : ℕ) : x ≤ y ∨ y ≤ x := by + sorry + +end MyNat diff --git a/courses/nng/exercises/LessOrEqual/le_trans.lean b/courses/nng/exercises/LessOrEqual/le_trans.lean new file mode 100644 index 0000000..75681ba --- /dev/null +++ b/courses/nng/exercises/LessOrEqual/le_trans.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_succ_self + +namespace MyNat + +/- +In this level, we see inequalities as *hypotheses*. We have not seen this before. +The `cases` tactic can be used to take `hxy` apart. +-/ +/-- If $x \leq y$ and $y \leq z$, then $x \leq z$. -/ +theorem le_trans (x y z : ℕ) (hxy : x ≤ y) (hyz : y ≤ z) : x ≤ z := by + sorry + +end MyNat diff --git a/courses/nng/exercises/LessOrEqual/le_two.lean b/courses/nng/exercises/LessOrEqual/le_two.lean new file mode 100644 index 0000000..ec3618d --- /dev/null +++ b/courses/nng/exercises/LessOrEqual/le_two.lean @@ -0,0 +1,18 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_one + +namespace MyNat + +/- +We'll need this lemma to prove that two is prime! + +You'll need to know that `∨` is right associative. This means that +`x = 0 ∨ x = 1 ∨ x = 2` actually means `x = 0 ∨ (x = 1 ∨ x = 2)`. +This affects how `left` and `right` work. +-/ +/-- If $x \leq 2$ then $x = 0$ or $1$ or $2$. -/ +theorem le_two (x : ℕ) (hx : x ≤ 2) : x = 0 ∨ x = 1 ∨ x = 2 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/LessOrEqual/le_zero.lean b/courses/nng/exercises/LessOrEqual/le_zero.lean new file mode 100644 index 0000000..02a7f78 --- /dev/null +++ b/courses/nng/exercises/LessOrEqual/le_zero.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_trans + +namespace MyNat + +/- +It's "intuitively obvious" that there are no numbers less than zero, +but to prove it you will need a result which you showed in advanced +addition world. +-/ +/-- If $x \leq 0$, then $x=0$. -/ +theorem le_zero (x : ℕ) (hx : x ≤ 0) : x = 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/LessOrEqual/or_symm.lean b/courses/nng/exercises/LessOrEqual/or_symm.lean new file mode 100644 index 0000000..7bf6b7a --- /dev/null +++ b/courses/nng/exercises/LessOrEqual/or_symm.lean @@ -0,0 +1,27 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_antisymm + +namespace MyNat + +/- +Totality of `≤` is the boss level of this world, and it's coming up next. It says that +if `a` and `b` are naturals then either `a ≤ b` or `b ≤ a`. +But we haven't talked about `or` at all. Here's a run-through. + +1) The notation for "or" is `∨`. You won't need to type it, but you can +type it with `\or`. + +2) If you have an "or" statement in the *goal*, then two tactics make +progress: `left` and `right`. But don't choose a direction unless your +hypotheses guarantee that it's the correct one. + +3) If you have an "or" statement as a *hypothesis* `h`, then +`cases h with h1 h2` will create two goals, one where you went left, +and the other where you went right. +-/ +/- If $x=37$ or $y=42$, then $y=42$ or $x=37$. -/ +example (x y : ℕ) (h : x = 37 ∨ y = 42) : y = 42 ∨ x = 37 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/LessOrEqual/succ_le_succ.lean b/courses/nng/exercises/LessOrEqual/succ_le_succ.lean new file mode 100644 index 0000000..cfa3cd2 --- /dev/null +++ b/courses/nng/exercises/LessOrEqual/succ_le_succ.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_total + +namespace MyNat + +/- +We've proved that `x ≤ 0` implies `x = 0`. The last two levels +in this world will prove which numbers are `≤ 1` and `≤ 2`. +This lemma will be helpful for them. +-/ +/-- If $\operatorname{succ}(x) \leq \operatorname{succ}(y)$ then $x \leq y$. -/ +theorem succ_le_succ (x y : ℕ) (hx : succ x ≤ succ y) : x ≤ y := by + sorry + +end MyNat diff --git a/courses/nng/exercises/LessOrEqual/zero_le.lean b/courses/nng/exercises/LessOrEqual/zero_le.lean new file mode 100644 index 0000000..391f813 --- /dev/null +++ b/courses/nng/exercises/LessOrEqual/zero_le.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_refl + +namespace MyNat + +/- +To solve this level, you need to `use` a number `c` such that `x = 0 + c`. +-/ +/-- If $x$ is a number, then $0 \le x$. -/ +theorem zero_le (x : ℕ) : 0 ≤ x := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Multiplication/add_mul.lean b/courses/nng/exercises/Multiplication/add_mul.lean new file mode 100644 index 0000000..0213898 --- /dev/null +++ b/courses/nng/exercises/Multiplication/add_mul.lean @@ -0,0 +1,18 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.mul_add + +namespace MyNat + +/- +`add_mul` is just as fiddly to prove by induction; but there's a trick +which avoids it. Can you spot it? +-/ +/-- Addition is distributive over multiplication. +In other words, for all natural numbers $a$, $b$ and $c$, we have +$(a + b) \times c = ac + bc$. -/ +theorem add_mul + (a b c : ℕ) : (a + b) * c = a * c + b * c := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Multiplication/mul_add.lean b/courses/nng/exercises/Multiplication/mul_add.lean new file mode 100644 index 0000000..1782e03 --- /dev/null +++ b/courses/nng/exercises/Multiplication/mul_add.lean @@ -0,0 +1,23 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.two_mul + +namespace MyNat + +/- +Our next goal is "left and right distributivity", +meaning $a(b+c)=ab+ac$ and $(b+c)a=ba+ca$. Rather than +these slightly pompous names, the name of the proofs +in Lean are descriptive. Let's start with +`mul_add a b c`, the proof of `a * (b + c) = a * b + a * c`. +Note that the left hand side contains a multiplication +and then an addition. +-/ +/-- Multiplication is distributive over addition on the left. +In other words, for all natural numbers $a$, $b$ and $c$, we have +$a(b + c) = ab + ac$. -/ +theorem mul_add + (a b c : ℕ) : a * (b + c) = a * b + a * c := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Multiplication/mul_assoc.lean b/courses/nng/exercises/Multiplication/mul_assoc.lean new file mode 100644 index 0000000..6e4e415 --- /dev/null +++ b/courses/nng/exercises/Multiplication/mul_assoc.lean @@ -0,0 +1,18 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.add_mul + +namespace MyNat + +/- +We now have enough to prove that multiplication is associative, +the boss level of multiplication world. Good luck! +-/ +/-- Multiplication is associative. +In other words, for all natural numbers $a$, $b$ and $c$, we have +$(ab)c = a(bc)$. -/ +theorem mul_assoc + (a b c : ℕ) : (a * b) * c = a * (b * c) := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Multiplication/mul_comm.lean b/courses/nng/exercises/Multiplication/mul_comm.lean new file mode 100644 index 0000000..3256cc2 --- /dev/null +++ b/courses/nng/exercises/Multiplication/mul_comm.lean @@ -0,0 +1,20 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.succ_mul + +namespace MyNat + +/- +The first sub-boss of Multiplication World is `mul_comm x y : x * y = y * x`. + +When you've proved this theorem we will have "spare" proofs +such as `zero_mul`, which is now easily deducible from `mul_zero`. +But we'll keep hold of these proofs anyway, because it's convenient +to have exactly the right tool for a job. +-/ +/-- Multiplication is commutative. -/ +theorem mul_comm + (a b : ℕ) : a * b = b * a := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Multiplication/mul_one.lean b/courses/nng/exercises/Multiplication/mul_one.lean new file mode 100644 index 0000000..713929c --- /dev/null +++ b/courses/nng/exercises/Multiplication/mul_one.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Addition.add_right_comm + +namespace MyNat + +/- +See the new "*" tab in your lemmas, containing `mul_zero` and `mul_succ`. +Right now these are the only facts we know about multiplication. +Let's prove nine more. + +Let's start with a warm-up: no induction needed for this one, +because we know `1` is a successor. +-/ +/-- For any natural number $m$, we have $ m \times 1 = m$. -/ +theorem mul_one (m : ℕ) : m * 1 = m := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Multiplication/one_mul.lean b/courses/nng/exercises/Multiplication/one_mul.lean new file mode 100644 index 0000000..3b26770 --- /dev/null +++ b/courses/nng/exercises/Multiplication/one_mul.lean @@ -0,0 +1,17 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.mul_comm + +namespace MyNat + +/- +You can prove $1\times m=m$ in at least three ways. +Either by induction, or by using `succ_mul`, or +by using commutativity. Which do you think is quickest? +-/ +/-- For any natural number $m$, we have $ 1 \times m = m$. -/ +theorem one_mul + (m : ℕ): 1 * m = m := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Multiplication/succ_mul.lean b/courses/nng/exercises/Multiplication/succ_mul.lean new file mode 100644 index 0000000..c3d006b --- /dev/null +++ b/courses/nng/exercises/Multiplication/succ_mul.lean @@ -0,0 +1,23 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.zero_mul + +namespace MyNat + +/- +Similarly we have `mul_succ` +but we're going to need `succ_mul` (guess what it says -- maybe you +are getting the hang of Lean's naming conventions). + +The last level from addition world might help you in this level. +If you can't remember what it is, you can go back to the +home screen by clicking the house icon and then taking a look. +You won't lose any progress. +-/ +/-- For all natural numbers $a$ and $b$, we have +$(\operatorname{succ}\ a) \times b = a\times b + b$. -/ +theorem succ_mul + (a b : ℕ) : succ a * b = a * b + b := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Multiplication/two_mul.lean b/courses/nng/exercises/Multiplication/two_mul.lean new file mode 100644 index 0000000..797f245 --- /dev/null +++ b/courses/nng/exercises/Multiplication/two_mul.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.one_mul + +namespace MyNat + +/- +This level is more important than you think; it plays +a useful role when battling a big boss later on. +-/ +/-- For any natural number $m$, we have $ 2 \times m = m+m$. -/ +theorem two_mul + (m : ℕ): 2 * m = m + m := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Multiplication/zero_mul.lean b/courses/nng/exercises/Multiplication/zero_mul.lean new file mode 100644 index 0000000..2d82c13 --- /dev/null +++ b/courses/nng/exercises/Multiplication/zero_mul.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.mul_one + +namespace MyNat + +/- +Our first challenge is `mul_comm x y : x * y = y * x`, +and we want to prove it by induction. The zero +case will need `mul_zero` (which we have) +and `zero_mul` (which we don't), so let's +start with this. +-/ +/-- For all natural numbers $m$, we have $ 0 \times m = 0$. -/ +theorem zero_mul + (m : ℕ) : 0 * m = 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Power/add_sq.lean b/courses/nng/exercises/Power/add_sq.lean new file mode 100644 index 0000000..cf833c7 --- /dev/null +++ b/courses/nng/exercises/Power/add_sq.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.pow_pow + +namespace MyNat + +/- +[final boss music] +-/ +/-- For all numbers $a$ and $b$, we have +$$(a+b)^2=a^2+b^2+2ab.$$ -/ +theorem add_sq + (a b : ℕ) : (a + b) ^ 2 = a ^ 2 + b ^ 2 + 2 * a * b := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Power/mul_pow.lean b/courses/nng/exercises/Power/mul_pow.lean new file mode 100644 index 0000000..f54dbe8 --- /dev/null +++ b/courses/nng/exercises/Power/mul_pow.lean @@ -0,0 +1,20 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.pow_add + +namespace MyNat + +/- +The music gets ever more dramatic, as we explore +the interplay between exponentiation and multiplication. + +If you're having trouble exchanging the right `a * b` +because `rw [mul_comm]` swaps the wrong multiplication, +then read the documentation of `rw` for tips on how to fix this. +-/ +/-- For all naturals $a$, $b$, $n$, we have $(ab) ^ n = a ^ nb ^ n$. -/ +theorem mul_pow + (a b n : ℕ) : (a * b) ^ n = a ^ n * b ^ n := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Power/one_pow.lean b/courses/nng/exercises/Power/one_pow.lean new file mode 100644 index 0000000..9121e39 --- /dev/null +++ b/courses/nng/exercises/Power/one_pow.lean @@ -0,0 +1,12 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.pow_one + +namespace MyNat + +/-- For all naturals $m$, $1 ^ m = 1$. -/ +theorem one_pow + (m : ℕ) : (1 : ℕ) ^ m = 1 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Power/pow_add.lean b/courses/nng/exercises/Power/pow_add.lean new file mode 100644 index 0000000..6757dca --- /dev/null +++ b/courses/nng/exercises/Power/pow_add.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.pow_two + +namespace MyNat + +/- +Let's now begin our approach to the final boss, +by proving some more subtle facts about powers. +-/ +/-- For all naturals $a$, $m$, $n$, we have $a^{m + n} = a ^ m a ^ n$. -/ +theorem pow_add + (a m n : ℕ) : a ^ (m + n) = a ^ m * a ^ n := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Power/pow_one.lean b/courses/nng/exercises/Power/pow_one.lean new file mode 100644 index 0000000..4ada60c --- /dev/null +++ b/courses/nng/exercises/Power/pow_one.lean @@ -0,0 +1,11 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.zero_pow_succ + +namespace MyNat + +/-- For all naturals $a$, $a ^ 1 = a$. -/ +theorem pow_one (a : ℕ) : a ^ 1 = a := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Power/pow_pow.lean b/courses/nng/exercises/Power/pow_pow.lean new file mode 100644 index 0000000..0b063d7 --- /dev/null +++ b/courses/nng/exercises/Power/pow_pow.lean @@ -0,0 +1,17 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.mul_pow + +namespace MyNat + +/- +One of the best named levels in the game, a savage `pow_pow` +sub-boss appears as the music reaches a frenzy. What +else could there be to prove about powers after this? +-/ +/-- For all naturals $a$, $m$, $n$, we have $(a ^ m) ^ n = a ^ {mn}$. -/ +theorem pow_pow + (a m n : ℕ) : (a ^ m) ^ n = a ^ (m * n) := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Power/pow_two.lean b/courses/nng/exercises/Power/pow_two.lean new file mode 100644 index 0000000..0547ab7 --- /dev/null +++ b/courses/nng/exercises/Power/pow_two.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.one_pow + +namespace MyNat + +/- +Note: this lemma will be useful for the final boss! +-/ +/-- For all naturals $a$, $a ^ 2 = a \times a$. -/ +theorem pow_two + (a : ℕ) : a ^ 2 = a * a := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Power/zero_pow_succ.lean b/courses/nng/exercises/Power/zero_pow_succ.lean new file mode 100644 index 0000000..f456568 --- /dev/null +++ b/courses/nng/exercises/Power/zero_pow_succ.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.zero_pow_zero + +namespace MyNat + +/- +We've just seen that `0 ^ 0 = 1`, but if `n` +is a successor, then `0 ^ n = 0`. We prove that here. +-/ +/-- For all numbers $m$, $0 ^{\operatorname{succ} (m)} = 0$. -/ +theorem zero_pow_succ + (m : ℕ) : (0 : ℕ) ^ (succ m) = 0 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Power/zero_pow_zero.lean b/courses/nng/exercises/Power/zero_pow_zero.lean new file mode 100644 index 0000000..b2eae09 --- /dev/null +++ b/courses/nng/exercises/Power/zero_pow_zero.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.mul_assoc + +namespace MyNat + +/- +Mathematicians sometimes debate what `0 ^ 0` is; +the answer depends, of course, on your definitions. In this +game, `0 ^ 0 = 1`. See if you can prove it. + +Check out the *Pow* tab in your list of theorems +to see the new proofs which are available. +-/ +/-- $0 ^ 0 = 1$ -/ +theorem zero_pow_zero : (0 : ℕ) ^ 0 = 1 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Tutorial/add_zero.lean b/courses/nng/exercises/Tutorial/add_zero.lean new file mode 100644 index 0000000..491182f --- /dev/null +++ b/courses/nng/exercises/Tutorial/add_zero.lean @@ -0,0 +1,35 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.rw_backwards + +namespace MyNat + +/- +We'd like to prove `2 + 2 = 4` but right now +we can't even *state* it +because we haven't yet defined addition. + +## Defining addition. + +How are we going to add $37$ to an arbitrary number $x$? Well, +there are only two ways to make numbers in this game: $0$ +and successors. So to define `37 + x` we will need +to know what `37 + 0` is and what `37 + succ x` is. +Let's start with adding `0`. + +### Adding 0 + +To make addition agree with our intuition, we should *define* `37 + 0` +to be `37`. More generally, we should define `a + 0` to be `a` for +any number `a`. The name of this proof in Lean is `add_zero a`. +For example `add_zero 37` is a proof of `37 + 0 = 37`, +`add_zero x` is a proof of `x + 0 = x`, and `add_zero` is a proof +of `? + 0 = ?`. + +We write `add_zero x : x + 0 = x`, so `proof : statement`. +-/ +/- $a+(b+0)+(c+0)=a+b+c.$ -/ +example (a b c : ℕ) : a + (b + 0) + (c + 0) = a + b + c := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Tutorial/add_zero2.lean b/courses/nng/exercises/Tutorial/add_zero2.lean new file mode 100644 index 0000000..41fad7f --- /dev/null +++ b/courses/nng/exercises/Tutorial/add_zero2.lean @@ -0,0 +1,20 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.add_zero + +namespace MyNat + +/- +## Precision rewriting + +In the last level, there was `b + 0` and `c + 0`, +and `rw [add_zero]` changed the first one it saw, +which was `b + 0`. Let's learn how to tell Lean +to change `c + 0` first by giving `add_zero` an +explicit input. +-/ +/- $a+(b+0)+(c+0)=a+b+c.$ -/ +example (a b c : ℕ) : a + (b + 0) + (c + 0) = a + b + c := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Tutorial/rfl.lean b/courses/nng/exercises/Tutorial/rfl.lean new file mode 100644 index 0000000..404d9ce --- /dev/null +++ b/courses/nng/exercises/Tutorial/rfl.lean @@ -0,0 +1,29 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import MyNatLib.Prelude + +namespace MyNat + +/- +# Read this first + +Each level in this game involves proving a mathematical theorem (the "Goal"). +The goal will be a statement about *numbers*. Some numbers in this game have known values. +Those numbers have names like $37$. Other numbers will be secret. They're called things +like $x$ and $q$. We know $x$ is a number, we just don't know which one. + +In this first level we're going to prove the theorem that $37x + q = 37x + q$. +You can see `x q : ℕ` in the *Objects* below, which means that `x` and `q` +are numbers. + +We solve goals in Lean using *Tactics*, and the first tactic we're +going to learn is called `rfl`, which proves all theorems of the form $X = X$. + +Prove that $37x+q=37x+q$ by executing the `rfl` tactic. +-/ +/- If $x$ and $q$ are arbitrary natural numbers, then $37x+q=37x+q.$ -/ +example + (x q : ℕ) : 37 * x + q = 37 * x + q := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Tutorial/rw.lean b/courses/nng/exercises/Tutorial/rw.lean new file mode 100644 index 0000000..9e89c21 --- /dev/null +++ b/courses/nng/exercises/Tutorial/rw.lean @@ -0,0 +1,22 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.rfl + +namespace MyNat + +/- +In this level the *goal* is $2y=2(x+7)$ but to help us we +have an *assumption* `h` saying that $y = x + 7$. Check that you can see `h` in +your list of assumptions. Lean thinks of `h` as being a secret proof of the +assumption, rather like `x` is a secret number. + +Before we can use `rfl`, we have to "substitute in for $y$". +We do this in Lean by *rewriting* the goal with `h`, +using the `rw` tactic. +-/ +/- If $x$ and $y$ are natural numbers, and $y = x + 7$, then $2y = 2(x + 7)$. -/ +example + (x y : ℕ) (h : y = x + 7) : 2 * y = 2 * (x + 7) := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Tutorial/rw_backwards.lean b/courses/nng/exercises/Tutorial/rw_backwards.lean new file mode 100644 index 0000000..799724f --- /dev/null +++ b/courses/nng/exercises/Tutorial/rw_backwards.lean @@ -0,0 +1,22 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.two_eq_ss0 + +namespace MyNat + +/- +If `h` is a proof of `X = Y` then `rw [h]` will +turn `X`s into `Y`s. But what if we want to +turn `Y`s into `X`s? To tell the `rw` tactic +we want this, we use a left arrow `←`. Type +`\l` and then hit the space bar to get this arrow. + +Let's prove that $2$ is the number after the number +after $0$ again, this time by changing `succ (succ 0)` +into `2`. +-/ +/- $2$ is the number after the number after $0$. -/ +example : 2 = succ (succ 0) := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Tutorial/succ_eq_add_one.lean b/courses/nng/exercises/Tutorial/succ_eq_add_one.lean new file mode 100644 index 0000000..514c4b4 --- /dev/null +++ b/courses/nng/exercises/Tutorial/succ_eq_add_one.lean @@ -0,0 +1,29 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.add_zero2 + +namespace MyNat + +/- +Every number in Lean is either $0$ or a successor. We know how to add $0$, +but we need to figure out how to add successors. Let's say we already know +that `37 + d = q`. What should the answer to `37 + succ d` be? Well, +`succ d` is one bigger than `d`, so `37 + succ d` should be `succ q`, +the number one bigger than `q`. More generally `x + succ d` should +be `succ (x + d)`. Let's add this as a lemma. + +* `add_succ x d : x + succ d = succ (x + d)` + +If you ever see `... + succ ...` in your goal, `rw [add_succ]` is +normally a good idea. + +Let's now prove that `succ n = n + 1`. Figure out how to get `+ succ` into +the picture, and then `rw [add_succ]`. Switch between the `+` (addition) and +`012` (numerals) tabs under "Theorems" on the right to +see which proofs you can rewrite. +-/ +/-- For all natural numbers $a$, we have $\operatorname{succ}(a) = a+1$. -/ +theorem succ_eq_add_one n : succ n = n + 1 := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Tutorial/two_eq_ss0.lean b/courses/nng/exercises/Tutorial/two_eq_ss0.lean new file mode 100644 index 0000000..5fde04e --- /dev/null +++ b/courses/nng/exercises/Tutorial/two_eq_ss0.lean @@ -0,0 +1,35 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.rw + +namespace MyNat + +/- +## The birth of number. + +Numbers in Lean are defined by two rules. + +* `0` is a number. +* If `n` is a number, then the *successor* `succ n` of `n` is a number. + +The successor of `n` means the number after `n`. Let's learn to +count, and name a few small numbers. + +## Counting to four. + +`0` is a number, so `succ 0` is a number. Let's call this new number `1`. +Similarly let's define `2 = succ 1`, `3 = succ 2` and `4 = succ 3`. +This gives us plenty of numbers to be getting along with. + +The *proof* that `2 = succ 1` is called `two_eq_succ_one`. +Check out the "012" tab in the list of lemmas on the right +for this and other proofs. + +Let's prove that $2$ is the number after the number after zero. +-/ +/- $2$ is the number after the number after $0$. -/ +example + : 2 = succ (succ 0) := by + sorry + +end MyNat diff --git a/courses/nng/exercises/Tutorial/twoaddtwo.lean b/courses/nng/exercises/Tutorial/twoaddtwo.lean new file mode 100644 index 0000000..c08379a --- /dev/null +++ b/courses/nng/exercises/Tutorial/twoaddtwo.lean @@ -0,0 +1,18 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.succ_eq_add_one + +namespace MyNat + +/- +Good luck! + + One last hint. If `h : X = Y` then `rw [h]` will change *all* `X`s into `Y`s. + If you only want to change one of them, say the 3rd one, then use + `nth_rewrite 3 [h]`. +-/ +/- $2+2=4$. -/ +example : (2 : ℕ) + 2 = 4 := by + sorry + +end MyNat diff --git a/courses/nng/solutions/Addition/add_assoc.lean b/courses/nng/solutions/Addition/add_assoc.lean new file mode 100644 index 0000000..4373b2e --- /dev/null +++ b/courses/nng/solutions/Addition/add_assoc.lean @@ -0,0 +1,17 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Addition.add_comm + +namespace MyNat + +/-- On the set of natural numbers, addition is associative. +In other words, if $a, b$ and $c$ are arbitrary natural numbers, we have +$ (a + b) + c = a + (b + c). $ -/ +theorem add_assoc (a b c : ℕ) : a + b + c = a + (b + c) := by + induction c with d hd + · rewrite [add_zero, add_zero] + rfl + · rewrite [add_succ, add_succ, hd, add_succ] + rfl + +end MyNat diff --git a/courses/nng/solutions/Addition/add_comm.lean b/courses/nng/solutions/Addition/add_comm.lean new file mode 100644 index 0000000..05e1360 --- /dev/null +++ b/courses/nng/solutions/Addition/add_comm.lean @@ -0,0 +1,17 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Addition.succ_add + +namespace MyNat + +/-- On the set of natural numbers, addition is commutative. +In other words, if `a` and `b` are arbitrary natural numbers, then +$a + b = b + a$. -/ +theorem add_comm (a b : ℕ) : a + b = b + a := by + induction b with d hd + · rewrite [add_zero, zero_add] + rfl + · rewrite [add_succ, succ_add, hd] + rfl + +end MyNat diff --git a/courses/nng/solutions/Addition/add_right_comm.lean b/courses/nng/solutions/Addition/add_right_comm.lean new file mode 100644 index 0000000..eb2053d --- /dev/null +++ b/courses/nng/solutions/Addition/add_right_comm.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Addition.add_assoc + +namespace MyNat + +/-- If $a, b$ and $c$ are arbitrary natural numbers, we have +$(a + b) + c = (a + c) + b$. -/ +theorem add_right_comm (a b c : ℕ) : a + b + c = a + c + b := by + rewrite [add_assoc] + rewrite [add_comm b, add_assoc] + rfl + +end MyNat diff --git a/courses/nng/solutions/Addition/succ_add.lean b/courses/nng/solutions/Addition/succ_add.lean new file mode 100644 index 0000000..e41fa2f --- /dev/null +++ b/courses/nng/solutions/Addition/succ_add.lean @@ -0,0 +1,20 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Addition.zero_add + +namespace MyNat + +/-- +For all natural numbers $a, b$, we have +$ \operatorname{succ}(a) + b = \operatorname{succ}(a + b)$. +-/ +theorem succ_add (a b : ℕ) : succ a + b = succ (a + b) := by + induction b with d hd + · rewrite [add_zero] + rewrite [add_zero] + rfl + · + rewrite [add_succ, add_succ, hd] + rfl + +end MyNat diff --git a/courses/nng/solutions/Addition/zero_add.lean b/courses/nng/solutions/Addition/zero_add.lean new file mode 100644 index 0000000..151d608 --- /dev/null +++ b/courses/nng/solutions/Addition/zero_add.lean @@ -0,0 +1,18 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.twoaddtwo + +namespace MyNat + +/-- For all natural numbers $n$, we have $0 + n = n$. -/ +theorem zero_add (n : ℕ) : 0 + n = n := by + induction n with d hd + · + rewrite [add_zero] + rfl + · + rewrite [add_succ] + rewrite [hd] + rfl + +end MyNat diff --git a/courses/nng/solutions/AdvAddition/add_left_cancel.lean b/courses/nng/solutions/AdvAddition/add_left_cancel.lean new file mode 100644 index 0000000..a9df29b --- /dev/null +++ b/courses/nng/solutions/AdvAddition/add_left_cancel.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_right_cancel + +namespace MyNat + +/-- $n+a=n+b\implies a=b$. -/ +theorem add_left_cancel (a b n : ℕ) : n + a = n + b → a = b := by + repeat rewrite [add_comm n] + intro h + apply add_right_cancel at h + exact h + +end MyNat diff --git a/courses/nng/solutions/AdvAddition/add_left_eq_self.lean b/courses/nng/solutions/AdvAddition/add_left_eq_self.lean new file mode 100644 index 0000000..43c10bf --- /dev/null +++ b/courses/nng/solutions/AdvAddition/add_left_eq_self.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_left_cancel + +namespace MyNat + +/-- $x + y = y\implies x=0.$ -/ +theorem add_left_eq_self (x y : ℕ) : x + y = y → x = 0 := by + intro h + nth_rewrite 2 [← zero_add y] at h + apply add_right_cancel at h + exact h + +end MyNat diff --git a/courses/nng/solutions/AdvAddition/add_left_eq_zero.lean b/courses/nng/solutions/AdvAddition/add_left_eq_zero.lean new file mode 100644 index 0000000..92510e5 --- /dev/null +++ b/courses/nng/solutions/AdvAddition/add_left_eq_zero.lean @@ -0,0 +1,12 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_right_eq_zero + +namespace MyNat + +/-- If $a+b=0$ then $b=0$. -/ +theorem add_left_eq_zero (a b : ℕ) : a + b = 0 → b = 0 := by + rewrite [add_comm] + exact add_right_eq_zero b a + +end MyNat diff --git a/courses/nng/solutions/AdvAddition/add_right_cancel.lean b/courses/nng/solutions/AdvAddition/add_right_cancel.lean new file mode 100644 index 0000000..6621f12 --- /dev/null +++ b/courses/nng/solutions/AdvAddition/add_right_cancel.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.two_add_two_ne_five + +namespace MyNat + +/-- $a+n=b+n\implies a=b$. -/ +theorem add_right_cancel (a b n : ℕ) : a + n = b + n → a = b := by + induction n with d hd + intro h + repeat rewrite [add_zero] at h + exact h + intro h + repeat rewrite [add_succ] at h + apply succ_inj at h + apply hd at h + exact h + +end MyNat diff --git a/courses/nng/solutions/AdvAddition/add_right_eq_self.lean b/courses/nng/solutions/AdvAddition/add_right_eq_self.lean new file mode 100644 index 0000000..918b7e7 --- /dev/null +++ b/courses/nng/solutions/AdvAddition/add_right_eq_self.lean @@ -0,0 +1,12 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_left_eq_self + +namespace MyNat + +/-- $x+y=x\implies y=0$. -/ +theorem add_right_eq_self (x y : ℕ) : x + y = x → y = 0 := by + rewrite [add_comm] + exact add_left_eq_self y x + +end MyNat diff --git a/courses/nng/solutions/AdvAddition/add_right_eq_zero.lean b/courses/nng/solutions/AdvAddition/add_right_eq_zero.lean new file mode 100644 index 0000000..fca6a7a --- /dev/null +++ b/courses/nng/solutions/AdvAddition/add_right_eq_zero.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_right_eq_self + +namespace MyNat + +/-- If $a+b=0$ then $a=0$. -/ +theorem add_right_eq_zero (a b : ℕ) : a + b = 0 → a = 0 := by + cases b with d + intro h + rewrite [add_zero] at h + exact h + intro h + rewrite [add_succ] at h + symm at h + apply zero_ne_succ at h + cases h + +end MyNat diff --git a/courses/nng/solutions/AdvMultiplication/eq_succ_of_ne_zero.lean b/courses/nng/solutions/AdvMultiplication/eq_succ_of_ne_zero.lean new file mode 100644 index 0000000..a516e26 --- /dev/null +++ b/courses/nng/solutions/AdvMultiplication/eq_succ_of_ne_zero.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_left_ne_zero + +namespace MyNat + +theorem eq_succ_of_ne_zero (a : ℕ) (ha : a ≠ 0) : ∃ n, a = succ n := by + cases a with d + · exact absurd rfl ha + · use d + rfl + +end MyNat diff --git a/courses/nng/solutions/AdvMultiplication/le_mul_right.lean b/courses/nng/solutions/AdvMultiplication/le_mul_right.lean new file mode 100644 index 0000000..ce64f68 --- /dev/null +++ b/courses/nng/solutions/AdvMultiplication/le_mul_right.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.one_le_of_ne_zero + +namespace MyNat + +theorem le_mul_right (a b : ℕ) (h : a * b ≠ 0) : a ≤ a * b := by + apply mul_left_ne_zero at h + apply one_le_of_ne_zero at h + apply mul_le_mul_right 1 b a at h + rewrite [one_mul, mul_comm] at h + exact h + +end MyNat diff --git a/courses/nng/solutions/AdvMultiplication/mul_eq_zero.lean b/courses/nng/solutions/AdvMultiplication/mul_eq_zero.lean new file mode 100644 index 0000000..9d97d69 --- /dev/null +++ b/courses/nng/solutions/AdvMultiplication/mul_eq_zero.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_ne_zero + +namespace MyNat + +theorem mul_eq_zero (a b : ℕ) (h : a * b = 0) : a = 0 ∨ b = 0 := by + rcases Classical.em (a = 0) with ha | ha + · left + exact ha + · right + rcases Classical.em (b = 0) with hb | hb + · exact hb + · exact absurd h (mul_ne_zero a b ha hb) + +end MyNat diff --git a/courses/nng/solutions/AdvMultiplication/mul_le_mul_right.lean b/courses/nng/solutions/AdvMultiplication/mul_le_mul_right.lean new file mode 100644 index 0000000..b83a2ff --- /dev/null +++ b/courses/nng/solutions/AdvMultiplication/mul_le_mul_right.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_two + +namespace MyNat + +theorem mul_le_mul_right (a b t : ℕ) (h : a ≤ b) : a * t ≤ b * t := by + cases h with d hd + use d * t + rewrite [hd, add_mul] + rfl + +end MyNat diff --git a/courses/nng/solutions/AdvMultiplication/mul_left_cancel.lean b/courses/nng/solutions/AdvMultiplication/mul_left_cancel.lean new file mode 100644 index 0000000..c2cf6a3 --- /dev/null +++ b/courses/nng/solutions/AdvMultiplication/mul_left_cancel.lean @@ -0,0 +1,28 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_eq_zero + +namespace MyNat + +theorem mul_left_cancel (a b c : ℕ) (ha : a ≠ 0) (h : a * b = a * c) : b = c := by + induction b using MyNat.rec' generalizing c with + | zero => + rewrite [mul_zero] at h + symm at h + apply mul_eq_zero at h + cases h with h1 h2 + · exact absurd h1 ha + · rewrite [h2] + rfl + | succ d hd => + cases c with e + · rewrite [mul_succ, mul_zero] at h + apply add_left_eq_zero at h + exact absurd h ha + · rewrite [mul_succ, mul_succ] at h + apply add_right_cancel at h + apply hd at h + rewrite [h] + rfl + +end MyNat diff --git a/courses/nng/solutions/AdvMultiplication/mul_left_ne_zero.lean b/courses/nng/solutions/AdvMultiplication/mul_left_ne_zero.lean new file mode 100644 index 0000000..b846823 --- /dev/null +++ b/courses/nng/solutions/AdvMultiplication/mul_left_ne_zero.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_le_mul_right + +namespace MyNat + +theorem mul_left_ne_zero (a b : ℕ) (h : a * b ≠ 0) : b ≠ 0 := by + intro hb + apply h + rewrite [hb, mul_zero] + rfl + +end MyNat diff --git a/courses/nng/solutions/AdvMultiplication/mul_ne_zero.lean b/courses/nng/solutions/AdvMultiplication/mul_ne_zero.lean new file mode 100644 index 0000000..09e3a19 --- /dev/null +++ b/courses/nng/solutions/AdvMultiplication/mul_ne_zero.lean @@ -0,0 +1,17 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_right_eq_one + +namespace MyNat + +theorem mul_ne_zero (a b : ℕ) (ha : a ≠ 0) (hb : b ≠ 0) : a * b ≠ 0 := by + apply eq_succ_of_ne_zero at ha + apply eq_succ_of_ne_zero at hb + cases ha with c hc + cases hb with d hd + rewrite [hc, hd] + rewrite [mul_succ, add_succ] + symm + apply zero_ne_succ + +end MyNat diff --git a/courses/nng/solutions/AdvMultiplication/mul_right_eq_one.lean b/courses/nng/solutions/AdvMultiplication/mul_right_eq_one.lean new file mode 100644 index 0000000..711db1c --- /dev/null +++ b/courses/nng/solutions/AdvMultiplication/mul_right_eq_one.lean @@ -0,0 +1,17 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.le_mul_right + +namespace MyNat + +theorem mul_right_eq_one (x y : ℕ) (h : x * y = 1) : x = 1 := by + have h2 : x * y ≠ 0 := by rewrite [h]; exact one_ne_zero + apply le_mul_right at h2 + rewrite [h] at h2 + apply le_one at h2 + cases h2 with h0 h1 + · rewrite [h0, zero_mul] at h + exact absurd h.symm one_ne_zero + · exact h1 + +end MyNat diff --git a/courses/nng/solutions/AdvMultiplication/mul_right_eq_self.lean b/courses/nng/solutions/AdvMultiplication/mul_right_eq_self.lean new file mode 100644 index 0000000..4b4c633 --- /dev/null +++ b/courses/nng/solutions/AdvMultiplication/mul_right_eq_self.lean @@ -0,0 +1,11 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_left_cancel + +namespace MyNat + +theorem mul_right_eq_self (a b : ℕ) (ha : a ≠ 0) (h : a * b = a) : b = 1 := by + nth_rewrite 2 [← mul_one a] at h + exact mul_left_cancel a b 1 ha h + +end MyNat diff --git a/courses/nng/solutions/AdvMultiplication/one_le_of_ne_zero.lean b/courses/nng/solutions/AdvMultiplication/one_le_of_ne_zero.lean new file mode 100644 index 0000000..f3cc674 --- /dev/null +++ b/courses/nng/solutions/AdvMultiplication/one_le_of_ne_zero.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.eq_succ_of_ne_zero + +namespace MyNat + +theorem one_le_of_ne_zero (a : ℕ) (ha : a ≠ 0) : 1 ≤ a := by + apply eq_succ_of_ne_zero at ha + cases ha with n hn + use n + rewrite [hn, succ_eq_add_one, add_comm] + rfl + +end MyNat diff --git a/courses/nng/solutions/Algorithm/add_algo1.lean b/courses/nng/solutions/Algorithm/add_algo1.lean new file mode 100644 index 0000000..3672d8e --- /dev/null +++ b/courses/nng/solutions/Algorithm/add_algo1.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.add_left_comm + +namespace MyNat + +/- If $a, b$, $c$ and $d$ are numbers, we have +$(a + b) + (c + d) = ((a + c) + d) + b.$ -/ +example (a b c d : ℕ) : a + b + (c + d) = a + c + d + b := by + repeat rewrite [add_assoc] + rewrite [add_left_comm b c] + rewrite [add_comm b d] + rfl + +end MyNat diff --git a/courses/nng/solutions/Algorithm/add_algo2.lean b/courses/nng/solutions/Algorithm/add_algo2.lean new file mode 100644 index 0000000..fb20095 --- /dev/null +++ b/courses/nng/solutions/Algorithm/add_algo2.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.add_algo1 + +namespace MyNat + +/- If $a, b,\ldots h$ are arbitrary natural numbers, we have +$(d + f) + (h + (a + c)) + (g + e + b) = a + b + c + d + e + f + g + h$. -/ +example (a b c d e f g h : ℕ) : + (d + f) + (h + (a + c)) + (g + e + b) = a + b + c + d + e + f + g + h := by + simp only [add_left_comm, add_comm] + +end MyNat diff --git a/courses/nng/solutions/Algorithm/add_algo3.lean b/courses/nng/solutions/Algorithm/add_algo3.lean new file mode 100644 index 0000000..9e8bf08 --- /dev/null +++ b/courses/nng/solutions/Algorithm/add_algo3.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.add_algo2 + +namespace MyNat + +/- If $a, b,\ldots h$ are arbitrary natural numbers, we have +$(d + f) + (h + (a + c)) + (g + e + b) = a + b + c + d + e + f + g + h$. -/ +example (a b c d e f g h : ℕ) : + (d + f) + (h + (a + c)) + (g + e + b) = a + b + c + d + e + f + g + h := by + simp only [add_left_comm, add_comm] + +end MyNat diff --git a/courses/nng/solutions/Algorithm/add_left_comm.lean b/courses/nng/solutions/Algorithm/add_left_comm.lean new file mode 100644 index 0000000..c1bfd06 --- /dev/null +++ b/courses/nng/solutions/Algorithm/add_left_comm.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvMultiplication.mul_right_eq_self + +namespace MyNat + +/-- If $a, b, c$ are numbers, then $a+(b+c)=b+(a+c)$. -/ +theorem add_left_comm (a b c : ℕ) : a + (b + c) = b + (a + c) := by + rewrite [← add_assoc] + rewrite [add_comm a b] + rewrite [add_assoc] + rfl + +end MyNat diff --git a/courses/nng/solutions/Algorithm/decide.lean b/courses/nng/solutions/Algorithm/decide.lean new file mode 100644 index 0000000..959b563 --- /dev/null +++ b/courses/nng/solutions/Algorithm/decide.lean @@ -0,0 +1,11 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.succ_ne_succ + +namespace MyNat + +/- $20+20=40$. -/ +example : (20 : ℕ) + 20 = 40 := by + simp only [ofNat_succ, add_succ, add_zero] + +end MyNat diff --git a/courses/nng/solutions/Algorithm/decide2.lean b/courses/nng/solutions/Algorithm/decide2.lean new file mode 100644 index 0000000..b83f38d --- /dev/null +++ b/courses/nng/solutions/Algorithm/decide2.lean @@ -0,0 +1,12 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.decide + +namespace MyNat + +/- $2+2 \neq 5.$ -/ +example : (2 : ℕ) + 2 ≠ 5 := by + simp only [ofNat_succ, add_succ, add_zero] + decide + +end MyNat diff --git a/courses/nng/solutions/Algorithm/pred.lean b/courses/nng/solutions/Algorithm/pred.lean new file mode 100644 index 0000000..9cb4b9f --- /dev/null +++ b/courses/nng/solutions/Algorithm/pred.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.add_algo3 + +namespace MyNat + +/- If $\operatorname{succ}(a)=\operatorname{succ}(b)$ then $a=b$. -/ +example (a b : ℕ) (h : succ a = succ b) : a = b := by + rewrite [← pred_succ a] + rewrite [h] + rewrite [pred_succ] + rfl + +end MyNat diff --git a/courses/nng/solutions/Algorithm/succ_ne_succ.lean b/courses/nng/solutions/Algorithm/succ_ne_succ.lean new file mode 100644 index 0000000..cf517b0 --- /dev/null +++ b/courses/nng/solutions/Algorithm/succ_ne_succ.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.succ_ne_zero + +namespace MyNat + +/-- If $a \neq b$ then $\operatorname{succ}(a) \neq\operatorname{succ}(b)$. -/ +theorem succ_ne_succ (m n : ℕ) (h : m ≠ n) : succ m ≠ succ n := by + intro hs + apply succ_inj at hs + exact h hs + +end MyNat diff --git a/courses/nng/solutions/Algorithm/succ_ne_zero.lean b/courses/nng/solutions/Algorithm/succ_ne_zero.lean new file mode 100644 index 0000000..7132dd5 --- /dev/null +++ b/courses/nng/solutions/Algorithm/succ_ne_zero.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Algorithm.pred + +namespace MyNat + +/-- $\operatorname{succ}(a) \neq 0$. -/ +theorem succ_ne_zero (a : ℕ) : succ a ≠ 0 := by + intro h + rewrite [← is_zero_succ a] + rewrite [h] + rewrite [is_zero_zero] + trivial + +end MyNat diff --git a/courses/nng/solutions/Implication/apply.lean b/courses/nng/solutions/Implication/apply.lean new file mode 100644 index 0000000..a85f105 --- /dev/null +++ b/courses/nng/solutions/Implication/apply.lean @@ -0,0 +1,12 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.exact2 + +namespace MyNat + +/- If $x=37$ and we know that $x=37\implies y=42$ then we can deduce $y=42$. -/ +example (x y : ℕ) (h1 : x = 37) (h2 : x = 37 → y = 42) : y = 42 := by + apply h2 at h1 + exact h1 + +end MyNat diff --git a/courses/nng/solutions/Implication/exact.lean b/courses/nng/solutions/Implication/exact.lean new file mode 100644 index 0000000..c2650a7 --- /dev/null +++ b/courses/nng/solutions/Implication/exact.lean @@ -0,0 +1,11 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.add_sq + +namespace MyNat + +/- Assuming $x+y=37$ and $3x+z=42$, we have $x+y=37$. -/ +example (x y z : ℕ) (h1 : x + y = 37) (h2 : 3 * x + z = 42) : x + y = 37 := by + exact h1 + +end MyNat diff --git a/courses/nng/solutions/Implication/exact2.lean b/courses/nng/solutions/Implication/exact2.lean new file mode 100644 index 0000000..5d4d345 --- /dev/null +++ b/courses/nng/solutions/Implication/exact2.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.exact + +namespace MyNat + +/- Assuming $0+x=(0+y)+2$, we have $x=y+2$. -/ +example (x y : ℕ) (h : 0 + x = 0 + y + 2) : x = y + 2 := by + rewrite [zero_add] at h + rewrite [zero_add] at h + exact h + +end MyNat diff --git a/courses/nng/solutions/Implication/intro.lean b/courses/nng/solutions/Implication/intro.lean new file mode 100644 index 0000000..e00d951 --- /dev/null +++ b/courses/nng/solutions/Implication/intro.lean @@ -0,0 +1,12 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.succ_inj2 + +namespace MyNat + +/- $x=37\implies x=37$. -/ +example (x : ℕ) : x = 37 → x = 37 := by + intro h + exact h + +end MyNat diff --git a/courses/nng/solutions/Implication/intro2.lean b/courses/nng/solutions/Implication/intro2.lean new file mode 100644 index 0000000..7317474 --- /dev/null +++ b/courses/nng/solutions/Implication/intro2.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.intro + +namespace MyNat + +/- $x+1=y+1 \implies x=y$. -/ +example (x y : ℕ) : x + 1 = y + 1 → x = y := by + intro h + repeat rewrite [← succ_eq_add_one] at h + apply succ_inj at h + exact h + +end MyNat diff --git a/courses/nng/solutions/Implication/ne.lean b/courses/nng/solutions/Implication/ne.lean new file mode 100644 index 0000000..bf244fd --- /dev/null +++ b/courses/nng/solutions/Implication/ne.lean @@ -0,0 +1,12 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.intro2 + +namespace MyNat + +/- If $x=y$ and $x \neq y$ then we can deduce a contradiction. -/ +example (x y : ℕ) (h1 : x = y) (h2 : x ≠ y) : False := by + apply h2 at h1 + exact h1 + +end MyNat diff --git a/courses/nng/solutions/Implication/one_ne_zero.lean b/courses/nng/solutions/Implication/one_ne_zero.lean new file mode 100644 index 0000000..4479452 --- /dev/null +++ b/courses/nng/solutions/Implication/one_ne_zero.lean @@ -0,0 +1,12 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.zero_ne_one + +namespace MyNat + +/-- $1\neq0$. -/ +theorem one_ne_zero : (1 : ℕ) ≠ 0 := by + symm + exact zero_ne_one + +end MyNat diff --git a/courses/nng/solutions/Implication/succ_inj.lean b/courses/nng/solutions/Implication/succ_inj.lean new file mode 100644 index 0000000..0e8f2b8 --- /dev/null +++ b/courses/nng/solutions/Implication/succ_inj.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.apply + +namespace MyNat + +/- If $x+1=4$ then $x=3$. -/ +example (x : ℕ) (h : x + 1 = 4) : x = 3 := by + rewrite [four_eq_succ_three] at h + rewrite [←succ_eq_add_one] at h + apply succ_inj at h + exact h + +end MyNat diff --git a/courses/nng/solutions/Implication/succ_inj2.lean b/courses/nng/solutions/Implication/succ_inj2.lean new file mode 100644 index 0000000..bf7e15b --- /dev/null +++ b/courses/nng/solutions/Implication/succ_inj2.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.succ_inj + +namespace MyNat + +/- If $x+1=4$ then $x=3$. -/ +example (x : ℕ) (h : x + 1 = 4) : x = 3 := by + apply succ_inj + rewrite [succ_eq_add_one] + rewrite [← four_eq_succ_three] + exact h + +end MyNat diff --git a/courses/nng/solutions/Implication/two_add_two_ne_five.lean b/courses/nng/solutions/Implication/two_add_two_ne_five.lean new file mode 100644 index 0000000..8a61343 --- /dev/null +++ b/courses/nng/solutions/Implication/two_add_two_ne_five.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.one_ne_zero + +namespace MyNat + +/- $2+2≠5$. -/ +example : succ (succ 0) + succ (succ 0) ≠ succ (succ (succ (succ (succ 0)))) := by + intro h + rewrite [add_succ, add_succ, add_zero] at h + repeat apply succ_inj at h + apply zero_ne_succ at h + exact h + +end MyNat diff --git a/courses/nng/solutions/Implication/zero_ne_one.lean b/courses/nng/solutions/Implication/zero_ne_one.lean new file mode 100644 index 0000000..ef077a5 --- /dev/null +++ b/courses/nng/solutions/Implication/zero_ne_one.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Implication.ne + +namespace MyNat + +/-- $0\neq1$. -/ +theorem zero_ne_one : (0 : ℕ) ≠ 1 := by + intro h + rewrite [one_eq_succ_zero] at h -- **TODO** this line is not needed :-/ + apply zero_ne_succ at h -- **TODO** cripple `apply`. + exact h + +end MyNat diff --git a/courses/nng/solutions/LessOrEqual/le_antisymm.lean b/courses/nng/solutions/LessOrEqual/le_antisymm.lean new file mode 100644 index 0000000..5cc934c --- /dev/null +++ b/courses/nng/solutions/LessOrEqual/le_antisymm.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_zero + +namespace MyNat + +/-- If $x \leq y$ and $y \leq x$, then $x = y$. -/ +theorem le_antisymm (x y : ℕ) (hxy : x ≤ y) (hyx : y ≤ x) : x = y := by + cases hxy with a ha + cases hyx with b hb + rewrite [ha] + rewrite [ha, add_assoc] at hb + symm at hb + apply add_right_eq_self at hb + apply add_right_eq_zero at hb + rewrite [hb, add_zero] + rfl + +end MyNat diff --git a/courses/nng/solutions/LessOrEqual/le_one.lean b/courses/nng/solutions/LessOrEqual/le_one.lean new file mode 100644 index 0000000..03f0841 --- /dev/null +++ b/courses/nng/solutions/LessOrEqual/le_one.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.succ_le_succ + +namespace MyNat + +/-- If $x \leq 1$ then either $x = 0$ or $x = 1$. -/ +theorem le_one (x : ℕ) (hx : x ≤ 1) : x = 0 ∨ x = 1 := by + cases x with y + left + rfl + rewrite [one_eq_succ_zero] at hx ⊢ + apply succ_le_succ at hx + apply le_zero at hx + rewrite [hx] + right + rfl + +end MyNat diff --git a/courses/nng/solutions/LessOrEqual/le_refl.lean b/courses/nng/solutions/LessOrEqual/le_refl.lean new file mode 100644 index 0000000..e0842d6 --- /dev/null +++ b/courses/nng/solutions/LessOrEqual/le_refl.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import AdvAddition.add_left_eq_zero + +namespace MyNat + +/-- If $x$ is a number, then $x \le x$. -/ +theorem le_refl (x : ℕ) : x ≤ x := by + use 0 + rewrite [add_zero] + rfl + +end MyNat diff --git a/courses/nng/solutions/LessOrEqual/le_succ_self.lean b/courses/nng/solutions/LessOrEqual/le_succ_self.lean new file mode 100644 index 0000000..6b7810b --- /dev/null +++ b/courses/nng/solutions/LessOrEqual/le_succ_self.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.zero_le + +namespace MyNat + +/-- If $x$ is a number, then $x \le \operatorname{succ}(x)$. -/ +theorem le_succ_self (x : ℕ) : x ≤ succ x := by + use 1 + rewrite [succ_eq_add_one] + rfl + +end MyNat diff --git a/courses/nng/solutions/LessOrEqual/le_total.lean b/courses/nng/solutions/LessOrEqual/le_total.lean new file mode 100644 index 0000000..95dd812 --- /dev/null +++ b/courses/nng/solutions/LessOrEqual/le_total.lean @@ -0,0 +1,32 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.or_symm + +namespace MyNat + +/-- If $x$ and $y$ are numbers, then either $x \leq y$ or $y \leq x$. -/ +theorem le_total (x y : ℕ) : x ≤ y ∨ y ≤ x := by + induction y with d hd + right + exact zero_le x + cases hd with h1 h2 + left + cases h1 with e h1 + rewrite [h1] + use e + 1 + rewrite [succ_eq_add_one, add_assoc] + rfl + cases h2 with e he + cases e with a + rewrite [he] + left + rewrite [add_zero] + use 1 + exact succ_eq_add_one d + right + use a + rewrite [add_succ] at he + rewrite [succ_add] + exact he + +end MyNat diff --git a/courses/nng/solutions/LessOrEqual/le_trans.lean b/courses/nng/solutions/LessOrEqual/le_trans.lean new file mode 100644 index 0000000..0f882d1 --- /dev/null +++ b/courses/nng/solutions/LessOrEqual/le_trans.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_succ_self + +namespace MyNat + +/-- If $x \leq y$ and $y \leq z$, then $x \leq z$. -/ +theorem le_trans (x y z : ℕ) (hxy : x ≤ y) (hyz : y ≤ z) : x ≤ z := by + cases hxy with a ha + cases hyz with b hb + use a + b + rewrite [hb, ha] + exact add_assoc x a b + +end MyNat diff --git a/courses/nng/solutions/LessOrEqual/le_two.lean b/courses/nng/solutions/LessOrEqual/le_two.lean new file mode 100644 index 0000000..61ff655 --- /dev/null +++ b/courses/nng/solutions/LessOrEqual/le_two.lean @@ -0,0 +1,26 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_one + +namespace MyNat + +/-- If $x \leq 2$ then $x = 0$ or $1$ or $2$. -/ +theorem le_two (x : ℕ) (hx : x ≤ 2) : x = 0 ∨ x = 1 ∨ x = 2 := by + cases x with y + left + rfl + cases y with z + right + left + rewrite [one_eq_succ_zero] + rfl + rewrite [two_eq_succ_one, one_eq_succ_zero] at hx ⊢ + apply succ_le_succ at hx + apply succ_le_succ at hx + apply le_zero at hx + rewrite [hx] + right + right + rfl + +end MyNat diff --git a/courses/nng/solutions/LessOrEqual/le_zero.lean b/courses/nng/solutions/LessOrEqual/le_zero.lean new file mode 100644 index 0000000..b21ec08 --- /dev/null +++ b/courses/nng/solutions/LessOrEqual/le_zero.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_trans + +namespace MyNat + +/-- If $x \leq 0$, then $x=0$. -/ +theorem le_zero (x : ℕ) (hx : x ≤ 0) : x = 0 := by + cases hx with y hy + symm at hy + apply add_right_eq_zero at hy + exact hy + +end MyNat diff --git a/courses/nng/solutions/LessOrEqual/or_symm.lean b/courses/nng/solutions/LessOrEqual/or_symm.lean new file mode 100644 index 0000000..4c842e9 --- /dev/null +++ b/courses/nng/solutions/LessOrEqual/or_symm.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_antisymm + +namespace MyNat + +/- If $x=37$ or $y=42$, then $y=42$ or $x=37$. -/ +example (x y : ℕ) (h : x = 37 ∨ y = 42) : y = 42 ∨ x = 37 := by + cases h with hx hy + right + exact hx + left + exact hy + +end MyNat diff --git a/courses/nng/solutions/LessOrEqual/succ_le_succ.lean b/courses/nng/solutions/LessOrEqual/succ_le_succ.lean new file mode 100644 index 0000000..90d5387 --- /dev/null +++ b/courses/nng/solutions/LessOrEqual/succ_le_succ.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_total + +namespace MyNat + +/-- If $\operatorname{succ}(x) \leq \operatorname{succ}(y)$ then $x \leq y$. -/ +theorem succ_le_succ (x y : ℕ) (hx : succ x ≤ succ y) : x ≤ y := by + cases hx with d hd + use d + rewrite [succ_add] at hd + apply succ_inj at hd + exact hd + +end MyNat diff --git a/courses/nng/solutions/LessOrEqual/zero_le.lean b/courses/nng/solutions/LessOrEqual/zero_le.lean new file mode 100644 index 0000000..843098f --- /dev/null +++ b/courses/nng/solutions/LessOrEqual/zero_le.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import LessOrEqual.le_refl + +namespace MyNat + +/-- If $x$ is a number, then $0 \le x$. -/ +theorem zero_le (x : ℕ) : 0 ≤ x := by + use x + rewrite [zero_add] + rfl + +end MyNat diff --git a/courses/nng/solutions/Multiplication/add_mul.lean b/courses/nng/solutions/Multiplication/add_mul.lean new file mode 100644 index 0000000..d850121 --- /dev/null +++ b/courses/nng/solutions/Multiplication/add_mul.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.mul_add + +namespace MyNat + +/-- Addition is distributive over multiplication. +In other words, for all natural numbers $a$, $b$ and $c$, we have +$(a + b) \times c = ac + bc$. -/ +theorem add_mul + (a b c : ℕ) : (a + b) * c = a * c + b * c := by + rewrite [mul_comm, mul_add] + repeat rewrite [mul_comm c] + rfl + +end MyNat diff --git a/courses/nng/solutions/Multiplication/mul_add.lean b/courses/nng/solutions/Multiplication/mul_add.lean new file mode 100644 index 0000000..29fb9fc --- /dev/null +++ b/courses/nng/solutions/Multiplication/mul_add.lean @@ -0,0 +1,18 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.two_mul + +namespace MyNat + +/-- Multiplication is distributive over addition on the left. +In other words, for all natural numbers $a$, $b$ and $c$, we have +$a(b + c) = ab + ac$. -/ +theorem mul_add + (a b c : ℕ) : a * (b + c) = a * b + a * c := by + induction c with d hd + rewrite [add_zero, mul_zero, add_zero] + rfl + rewrite [add_succ, mul_succ, hd, mul_succ, add_assoc] + rfl + +end MyNat diff --git a/courses/nng/solutions/Multiplication/mul_assoc.lean b/courses/nng/solutions/Multiplication/mul_assoc.lean new file mode 100644 index 0000000..3eeffde --- /dev/null +++ b/courses/nng/solutions/Multiplication/mul_assoc.lean @@ -0,0 +1,21 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.add_mul + +namespace MyNat + +/-- Multiplication is associative. +In other words, for all natural numbers $a$, $b$ and $c$, we have +$(ab)c = a(bc)$. -/ +theorem mul_assoc + (a b c : ℕ) : (a * b) * c = a * (b * c) := by + induction c with d hd + · rewrite [mul_zero, mul_zero, mul_zero] + rfl + · rewrite [mul_succ] + rewrite [mul_succ] + rewrite [hd] + rewrite [mul_add] + rfl + +end MyNat diff --git a/courses/nng/solutions/Multiplication/mul_comm.lean b/courses/nng/solutions/Multiplication/mul_comm.lean new file mode 100644 index 0000000..1904b6e --- /dev/null +++ b/courses/nng/solutions/Multiplication/mul_comm.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.succ_mul + +namespace MyNat + +/-- Multiplication is commutative. -/ +theorem mul_comm + (a b : ℕ) : a * b = b * a := by + induction b with d hd + · rewrite [zero_mul] + rewrite [mul_zero] + rfl + · rewrite [succ_mul] + rewrite [← hd] + rewrite [mul_succ] + rfl + +end MyNat diff --git a/courses/nng/solutions/Multiplication/mul_one.lean b/courses/nng/solutions/Multiplication/mul_one.lean new file mode 100644 index 0000000..1ac5361 --- /dev/null +++ b/courses/nng/solutions/Multiplication/mul_one.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Addition.add_right_comm + +namespace MyNat + +/-- For any natural number $m$, we have $ m \times 1 = m$. -/ +theorem mul_one (m : ℕ) : m * 1 = m := by + rewrite [one_eq_succ_zero] + rewrite [mul_succ] + rewrite [mul_zero] + rewrite [zero_add] + rfl + +end MyNat diff --git a/courses/nng/solutions/Multiplication/one_mul.lean b/courses/nng/solutions/Multiplication/one_mul.lean new file mode 100644 index 0000000..2f199ff --- /dev/null +++ b/courses/nng/solutions/Multiplication/one_mul.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.mul_comm + +namespace MyNat + +/-- For any natural number $m$, we have $ 1 \times m = m$. -/ +theorem one_mul + (m : ℕ): 1 * m = m := by + rewrite [mul_comm, mul_one] + rfl + +end MyNat diff --git a/courses/nng/solutions/Multiplication/succ_mul.lean b/courses/nng/solutions/Multiplication/succ_mul.lean new file mode 100644 index 0000000..ce679c2 --- /dev/null +++ b/courses/nng/solutions/Multiplication/succ_mul.lean @@ -0,0 +1,24 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.zero_mul + +namespace MyNat + +/-- For all natural numbers $a$ and $b$, we have +$(\operatorname{succ}\ a) \times b = a\times b + b$. -/ +theorem succ_mul + (a b : ℕ) : succ a * b = a * b + b := by + induction b with d hd + · rewrite [mul_zero] + rewrite [mul_zero] + rewrite [add_zero] + rfl + · rewrite [mul_succ] + rewrite [mul_succ] + rewrite [hd] + rewrite [add_succ] + rewrite [add_succ] + rewrite [add_right_comm] + rfl + +end MyNat diff --git a/courses/nng/solutions/Multiplication/two_mul.lean b/courses/nng/solutions/Multiplication/two_mul.lean new file mode 100644 index 0000000..100a197 --- /dev/null +++ b/courses/nng/solutions/Multiplication/two_mul.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.one_mul + +namespace MyNat + +/-- For any natural number $m$, we have $ 2 \times m = m+m$. -/ +theorem two_mul + (m : ℕ): 2 * m = m + m := by + rewrite [two_eq_succ_one, succ_mul, one_mul] + rfl + +end MyNat diff --git a/courses/nng/solutions/Multiplication/zero_mul.lean b/courses/nng/solutions/Multiplication/zero_mul.lean new file mode 100644 index 0000000..c8b0020 --- /dev/null +++ b/courses/nng/solutions/Multiplication/zero_mul.lean @@ -0,0 +1,18 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.mul_one + +namespace MyNat + +/-- For all natural numbers $m$, we have $ 0 \times m = 0$. -/ +theorem zero_mul + (m : ℕ) : 0 * m = 0 := by + induction m with d hd + · rewrite [mul_zero] + rfl + · rewrite [mul_succ] + rewrite [hd] + rewrite [add_zero] + rfl + +end MyNat diff --git a/courses/nng/solutions/MyNatLib/Basic.lean b/courses/nng/solutions/MyNatLib/Basic.lean new file mode 100644 index 0000000..2479adb --- /dev/null +++ b/courses/nng/solutions/MyNatLib/Basic.lean @@ -0,0 +1,133 @@ +/- +The `MyNat` foundation: a from-scratch copy of the natural numbers. + +Ported from the Natural Number Game (leanprover-community/NNG4), which is +licensed under Apache-2.0. See `courses/nng/LICENSE` and `courses/nng/NOTICE`. + +Modifications: the upstream files depend on Mathlib and Batteries; this port is +rewritten to use only core Lean 4 so the leanlings `nng` course needs no heavy +dependencies. The mathematics (an axiomatic Peano development) is unchanged. +-/ + +/-- Our copy of the natural numbers, called `MyNat`, with notation `ℕ`. -/ +inductive MyNat where + | zero : MyNat + | succ : MyNat → MyNat + +attribute [pp_nodot] MyNat.succ + +@[inherit_doc] +notation (name := MyNatNotation) (priority := 1000000) "ℕ" => MyNat + +namespace MyNat + +instance : Inhabited MyNat where + default := MyNat.zero + +/-- Convert a core `Nat` numeral into a `MyNat`. Marked `@[reducible]` so that +`(0 : MyNat)` is definitionally `MyNat.zero` at the transparency `rw`/`rfl` use: +this lets the levels use core `induction`/`cases` (which handle dependent +hypotheses correctly) while `rewrite [add_zero]` still fires on the base case. -/ +@[reducible] def ofNat (x : Nat) : MyNat := + match x with + | Nat.zero => MyNat.zero + | Nat.succ b => MyNat.succ (ofNat b) + +/-- Convert a `MyNat` back into a core `Nat` (used for display and `decide`). -/ +def toNat (x : MyNat) : Nat := + match x with + | MyNat.zero => Nat.zero + | MyNat.succ b => Nat.succ (toNat b) + +instance instOfNat {n : Nat} : OfNat MyNat n where + ofNat := ofNat n + +instance : ToString MyNat where + toString p := toString (toNat p) + +theorem zero_eq_0 : MyNat.zero = 0 := rfl + +def one : MyNat := MyNat.succ 0 + +/-! ### Numeral lemmas + +These relate the literals `1`, `2`, `3`, `4` to successors. They hold by +computation (the `OfNat` instance), so they are library givens rather than +exercises. -/ + +theorem one_eq_succ_zero : 1 = succ 0 := rfl +theorem two_eq_succ_one : 2 = succ 1 := rfl +theorem three_eq_succ_two : 3 = succ 2 := rfl +theorem four_eq_succ_three : 4 = succ 3 := rfl + +/-! ### Addition, multiplication and powers + +Following NNG, the operations are `opaque` and characterised by axioms (the +recursion equations) rather than defined by recursion. This keeps the early +levels honest: you can only use `add_zero`/`add_succ`, never definitional +unfolding. -/ + +opaque add : MyNat → MyNat → MyNat +instance instAdd : Add MyNat where add := MyNat.add + +/-- `add_zero a` is a proof of `a + 0 = a`. -/ +axiom add_zero (a : MyNat) : a + 0 = a +/-- `add_succ a d` is a proof of `a + succ d = succ (a + d)`. -/ +axiom add_succ (a d : MyNat) : a + (succ d) = succ (a + d) + +opaque mul : MyNat → MyNat → MyNat +instance instMul : Mul MyNat where mul := MyNat.mul + +/-- `mul_zero a` is a proof of `a * 0 = 0`. -/ +axiom mul_zero (a : MyNat) : a * 0 = 0 +/-- `mul_succ a b` is a proof of `a * succ b = a * b + a`. -/ +axiom mul_succ (a b : MyNat) : a * (succ b) = a * b + a + +opaque pow : ℕ → ℕ → ℕ +instance instPow : Pow ℕ ℕ where pow := pow + +-- ensure `a ^ b` elaborates both sides at type `MyNat` +macro_rules | `($x ^ $y) => `(HPow.hPow ($x : MyNat) ($y : MyNat)) + +/-- `pow_zero m` is a proof of `m ^ 0 = 1`. -/ +axiom pow_zero (m : ℕ) : m ^ 0 = 1 +/-- `pow_succ m n` is a proof of `m ^ succ n = m ^ n * m`. -/ +axiom pow_succ (m n : ℕ) : m ^ (succ n) = m ^ n * m + +/-! ### Order + +`a ≤ b` is *defined* to mean `∃ c, b = a + c`, and `a < b` to mean +`a ≤ b ∧ ¬ b ≤ a`. -/ + +def le (a b : ℕ) := ∃ (c : ℕ), b = a + c +instance instLE : LE MyNat := ⟨MyNat.le⟩ + +theorem le_iff_exists_add (a b : ℕ) : a ≤ b ↔ ∃ (c : ℕ), b = a + c := Iff.rfl + +def lt (a b : ℕ) := a ≤ b ∧ ¬ (b ≤ a) +instance instLT : LT MyNat := ⟨MyNat.lt⟩ + +theorem lt_iff (a b : ℕ) : a < b ↔ a ≤ b ∧ ¬ b ≤ a := Iff.rfl + +/-! ### Custom recursors that display `0` + +These mirror NNG: when you do induction, the base case is shown as `0` (the +`OfNat` form the axioms are stated with) rather than `MyNat.zero`. -/ + +/-- Induction principle whose base case is phrased with `0`. -/ +@[elab_as_elim] +def rec' {P : ℕ → Prop} (zero : P 0) + (succ : (n : ℕ) → (n_ih : P n) → P (MyNat.succ n)) (t : ℕ) : P t := by + induction t with + | zero => exact zero + | succ n ih => exact succ n ih + +/-- Case analysis principle whose zero case is phrased with `0`. -/ +@[elab_as_elim] +def casesOn' {P : ℕ → Sort u} (t : ℕ) (zero : P 0) + (succ : (a : ℕ) → P (MyNat.succ a)) : P t := by + cases t with + | zero => exact zero + | succ n => exact succ n + +end MyNat diff --git a/courses/nng/solutions/MyNatLib/Decide.lean b/courses/nng/solutions/MyNatLib/Decide.lean new file mode 100644 index 0000000..366e7c0 --- /dev/null +++ b/courses/nng/solutions/MyNatLib/Decide.lean @@ -0,0 +1,31 @@ +/- +Decidable equality for `MyNat`, plus the numeral-unfolding lemmas the +Algorithm-world `decide` levels need. Ported from leanprover-community/NNG4 +(Apache-2.0); core Lean only. See `courses/nng/LICENSE`. + +`+`/`*`/`^` are opaque, so `decide` cannot evaluate them directly. The idiom is +`simp only [...]` (unfolding numerals and the recursion equations to a `succ`/`0` +normal form) followed by `decide` (which uses `DecidableEq MyNat`). +-/ +import MyNatLib.Basic +import MyNatLib.Peano + +namespace MyNat + +/-- Unfold a numeral literal one `succ` at a time. -/ +theorem ofNat_succ (n : Nat) : + (OfNat.ofNat (n + 1) : MyNat) = MyNat.succ (OfNat.ofNat n) := rfl + +theorem ofNat_zero : (OfNat.ofNat 0 : MyNat) = MyNat.zero := rfl + +/-- Decidable equality, built from `succ_inj` and `zero_ne_succ`. -/ +instance instDecidableEq : (a b : MyNat) → Decidable (a = b) + | .zero, .zero => isTrue rfl + | .zero, .succ n => isFalse (zero_ne_succ n) + | .succ m, .zero => isFalse (fun h => zero_ne_succ m h.symm) + | .succ m, .succ n => + match instDecidableEq m n with + | isTrue h => isTrue (by rw [h]) + | isFalse h => isFalse (fun hs => h (succ_inj hs)) + +end MyNat diff --git a/courses/nng/solutions/MyNatLib/Peano.lean b/courses/nng/solutions/MyNatLib/Peano.lean new file mode 100644 index 0000000..b246655 --- /dev/null +++ b/courses/nng/solutions/MyNatLib/Peano.lean @@ -0,0 +1,42 @@ +/- +The Peano API NNG provides as givens: predecessor, injectivity of `succ`, and +`0 ≠ succ n`. Ported from leanprover-community/NNG4 (Apache-2.0); upstream pulls +in Mathlib tactics, this uses only core Lean. See `courses/nng/LICENSE`. + +These are part of the foundation, so they are proved with core `rw` (which, in +the library, may close `a = a` automatically). Inside the *levels*, `rw` is +written as `rewrite` so that closing `rfl`s stay explicit. +-/ +import MyNatLib.Basic + +namespace MyNat + +/-- Predecessor. `pred 0` is junk (`37`); `pred (succ n) = n`. -/ +def pred : ℕ → ℕ + | 0 => 37 + | succ n => n + +theorem pred_succ (n : ℕ) : pred (succ n) = n := rfl + +/-- `succ` is injective. (`a`, `b` are implicit so that `apply succ_inj at h` +works the way the levels expect.) -/ +theorem succ_inj {a b : ℕ} (h : succ a = succ b) : a = b := by + rw [← pred_succ a, h, pred_succ] + +/-- `is_zero 0` is `True`; `is_zero (succ n)` is `False`. -/ +def is_zero : ℕ → Prop + | 0 => True + | succ _ => False + +theorem is_zero_zero : is_zero 0 = True := rfl +theorem is_zero_succ (n : ℕ) : is_zero (succ n) = False := rfl + +/-- Zero is not a successor. -/ +theorem zero_ne_succ (a : ℕ) : (0 : ℕ) ≠ succ a := by + intro h + rw [← is_zero_succ a] + rw [← h] + rw [is_zero_zero] + trivial + +end MyNat diff --git a/courses/nng/solutions/MyNatLib/Prelude.lean b/courses/nng/solutions/MyNatLib/Prelude.lean new file mode 100644 index 0000000..863997c --- /dev/null +++ b/courses/nng/solutions/MyNatLib/Prelude.lean @@ -0,0 +1,9 @@ +/- +Everything an `nng` level needs in scope: the `MyNat` foundation plus the +core-only tactic conveniences. Level files import this (transitively, via the +previous level) and add their lemma. +-/ +import MyNatLib.Basic +import MyNatLib.Peano +import MyNatLib.Decide +import MyNatLib.Tactic diff --git a/courses/nng/solutions/MyNatLib/Tactic.lean b/courses/nng/solutions/MyNatLib/Tactic.lean new file mode 100644 index 0000000..bdf95f0 --- /dev/null +++ b/courses/nng/solutions/MyNatLib/Tactic.lean @@ -0,0 +1,108 @@ +/- +The one tactic convenience the `nng` levels need that core Lean lacks: `use`. + +Ported in spirit from leanprover-community/NNG4 (Apache-2.0); the upstream +version wraps Mathlib's `use`, this is a small core-only macro. See +`courses/nng/LICENSE` and `courses/nng/NOTICE`. + +Everything else NNG customises (`induction … with`, `cases … with`, a +rewrite-style `rw`) is expressed directly in core Lean by the solutions: +`induction n using MyNat.rec' with | zero | succ d hd`, and `rewrite` +(which, unlike `rw`, does not auto-close with `rfl`). +-/ +import Lean +import MyNatLib.Basic + +namespace MyNat +open Lean.Parser.Tactic + +/-- For a goal `∃ x, P x`, `use w` supplies the witness `w` and leaves `P w`. +Unlike Mathlib's `use` it does not try to discharge the remaining goal. -/ +macro "use " args:term,+ : tactic => + `(tactic| refine ⟨$args,*, ?_⟩) + +/-- Lean3-style `induction n with d hd`: induct on `n : ℕ`, naming the successor +predecessor `d` and the induction hypothesis `hd`. The base case is shown as `0`. +The two resulting goals are addressed by the following `·` bullets. -/ +syntax (name := lean3Induction) "induction " term " with " ident ppSpace ident : tactic +macro_rules (kind := lean3Induction) + | `(tactic| induction $n:term with $d:ident $hd:ident) => + `(tactic| induction $n:term using MyNat.rec' with + | zero => ?_ + | succ $d $hd => ?_) + +/-- `apply f at h` : given a function `f` and a hypothesis `h : A`, find the +argument of `f` whose type unifies with `A`, apply `f` to `h` (inferring the +other arguments), and replace `h` with the result. (Core Lean's `apply` has no +`at` form; this fills that gap, like Mathlib's `apply … at`.) -/ +syntax (name := applyAt) "apply " term " at " ident : tactic + +open Lean Lean.Meta Lean.Elab Lean.Elab.Tactic in +elab_rules : tactic + | `(tactic| apply $f:term at $hyp:ident) => withMainContext do + let goal ← getMainGoal + let some ldecl := (← getLCtx).findFromUserName? hyp.getId + | throwError "apply at: unknown hypothesis '{hyp.getId}'" + let hType ← instantiateMVars ldecl.type + let fExpr ← Term.elabTermAndSynthesize f none + let (mvars, _, _) ← forallMetaTelescopeReducing (← inferType fExpr) + let mut chosen := none + for i in [0:mvars.size] do + if (← isDefEq (← inferType mvars[i]!) hType) then chosen := some i; break + let some ci := chosen + | throwError "apply at: no argument of the function matches '{hyp.getId} : {hType}'" + mvars[ci]!.mvarId!.assign ldecl.toExpr + -- Apply only up to and including the matched argument; the remaining binders + -- belong to the conclusion (e.g. when the result type is itself `_ → _`). + let result ← instantiateMVars (mkAppN fExpr (mvars.extract 0 (ci + 1))) + let resultType ← instantiateMVars (← inferType result) + let newGoal ← goal.assert ldecl.userName resultType result + let (_, newGoal) ← newGoal.intro1P + replaceMainGoal [← newGoal.tryClear ldecl.fvarId] + +/-- `nth_rewrite n [h]` rewrites only the `n`-th occurrence of the pattern, +using core `rewrite`'s occurrence configuration. -/ +syntax (name := nthRewrite) "nth_rewrite " num rwRuleSeq (location)? : tactic +macro_rules + | `(tactic| nth_rewrite $n:num $rs:rwRuleSeq $[$loc:location]?) => + `(tactic| rewrite (config := { occs := .pos [$n] }) $rs:rwRuleSeq $[$loc:location]?) + +/-- lean3-style `cases e with a b …`. On a natural number, `cases n with d` +splits into the `0` case and the `succ d` case (with `0` shown). On a hypothesis, +the names are distributed across the resulting constructors' fields: for a +one-constructor type (`∃`, `∧`) they bind its fields; for `∨` they name the field +of each goal. (`colGt` stops the name list from swallowing the next tactic.) -/ +syntax (name := lean3Cases) "cases " term " with " (ppSpace colGt ident)+ : tactic + +open Lean Lean.Meta Lean.Elab Lean.Elab.Tactic in +elab_rules (kind := lean3Cases) : tactic + | `(tactic| cases $e:term with $ids:ident*) => withMainContext do + let idents := ids + let scrut ← elabTerm e none + let scrutTy ← instantiateMVars (← inferType scrut) + if (← whnf scrutTy).isAppOf ``MyNat && idents.size == 1 then + let d := idents[0]! + evalTactic (← `(tactic| cases $e:term using MyNat.casesOn' with + | zero => ?_ + | succ $d => ?_)) + else + let names := idents.map (·.getId) + let subgoals ← (← getMainGoal).cases scrut.fvarId! + let mut newGoals := #[] + let mut k := 0 + for sg in subgoals do + let mut mv := sg.mvarId + for f in sg.fields do + if k < names.size then + if f.isFVar then + mv ← mv.rename f.fvarId! names[k]! + k := k + 1 + newGoals := newGoals.push mv + replaceMainGoal newGoals.toList + +/-- `symm at h` turns a hypothesis `h : a = b` into `h : b = a`. -/ +syntax (name := symmAt) "symm " "at " ident : tactic +macro_rules (kind := symmAt) + | `(tactic| symm at $h:ident) => `(tactic| have $h := Eq.symm $h) + +end MyNat diff --git a/courses/nng/solutions/NNG.lean b/courses/nng/solutions/NNG.lean new file mode 100644 index 0000000..2045569 --- /dev/null +++ b/courses/nng/solutions/NNG.lean @@ -0,0 +1,6 @@ +/- +Root of the `nng` course library. Importing this builds the whole course +(foundation + every solution in dependency order), which is how CI verifies all +solutions compile. The tail of the import-chain is updated as worlds are added. +-/ +import MyNatLib.Prelude diff --git a/courses/nng/solutions/Power/add_sq.lean b/courses/nng/solutions/Power/add_sq.lean new file mode 100644 index 0000000..0f10c55 --- /dev/null +++ b/courses/nng/solutions/Power/add_sq.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.pow_pow + +namespace MyNat + +/-- For all numbers $a$ and $b$, we have +$$(a+b)^2=a^2+b^2+2ab.$$ -/ +theorem add_sq + (a b : ℕ) : (a + b) ^ 2 = a ^ 2 + b ^ 2 + 2 * a * b := by + rewrite [pow_two, pow_two, pow_two] + rewrite [add_right_comm] + rewrite [mul_add, add_mul, add_mul] + rewrite [two_mul, add_mul] + rewrite [mul_comm b a] + rewrite [← add_assoc, ← add_assoc] + rfl + +end MyNat diff --git a/courses/nng/solutions/Power/mul_pow.lean b/courses/nng/solutions/Power/mul_pow.lean new file mode 100644 index 0000000..023e392 --- /dev/null +++ b/courses/nng/solutions/Power/mul_pow.lean @@ -0,0 +1,19 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.pow_add + +namespace MyNat + +/-- For all naturals $a$, $b$, $n$, we have $(ab) ^ n = a ^ nb ^ n$. -/ +theorem mul_pow + (a b n : ℕ) : (a * b) ^ n = a ^ n * b ^ n := by + induction n with t Ht + · rewrite [pow_zero, pow_zero, pow_zero, mul_one] + rfl + · rewrite [pow_succ, pow_succ, pow_succ, Ht] + -- simp + repeat rewrite [mul_assoc] + rewrite [mul_comm a (_ * b), mul_assoc, mul_comm b a] + rfl + +end MyNat diff --git a/courses/nng/solutions/Power/one_pow.lean b/courses/nng/solutions/Power/one_pow.lean new file mode 100644 index 0000000..4b49ffb --- /dev/null +++ b/courses/nng/solutions/Power/one_pow.lean @@ -0,0 +1,18 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.pow_one + +namespace MyNat + +/-- For all naturals $m$, $1 ^ m = 1$. -/ +theorem one_pow + (m : ℕ) : (1 : ℕ) ^ m = 1 := by + induction m with t ht + · rewrite [pow_zero] + rfl + · rewrite [pow_succ] + rewrite [ht] + rewrite [mul_one] + rfl + +end MyNat diff --git a/courses/nng/solutions/Power/pow_add.lean b/courses/nng/solutions/Power/pow_add.lean new file mode 100644 index 0000000..865e113 --- /dev/null +++ b/courses/nng/solutions/Power/pow_add.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.pow_two + +namespace MyNat + +/-- For all naturals $a$, $m$, $n$, we have $a^{m + n} = a ^ m a ^ n$. -/ +theorem pow_add + (a m n : ℕ) : a ^ (m + n) = a ^ m * a ^ n := by + induction n with t ht + · rewrite [add_zero, pow_zero, mul_one] + rfl + · rewrite [add_succ, pow_succ, pow_succ, ht, mul_assoc] + rfl + +end MyNat diff --git a/courses/nng/solutions/Power/pow_one.lean b/courses/nng/solutions/Power/pow_one.lean new file mode 100644 index 0000000..7c86675 --- /dev/null +++ b/courses/nng/solutions/Power/pow_one.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.zero_pow_succ + +namespace MyNat + +/-- For all naturals $a$, $a ^ 1 = a$. -/ +theorem pow_one (a : ℕ) : a ^ 1 = a := by + rewrite [one_eq_succ_zero] + rewrite [pow_succ] + rewrite [pow_zero] + rewrite [one_mul] + rfl + +end MyNat diff --git a/courses/nng/solutions/Power/pow_pow.lean b/courses/nng/solutions/Power/pow_pow.lean new file mode 100644 index 0000000..2d60b25 --- /dev/null +++ b/courses/nng/solutions/Power/pow_pow.lean @@ -0,0 +1,16 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.mul_pow + +namespace MyNat + +/-- For all naturals $a$, $m$, $n$, we have $(a ^ m) ^ n = a ^ {mn}$. -/ +theorem pow_pow + (a m n : ℕ) : (a ^ m) ^ n = a ^ (m * n) := by + induction n with t Ht + · rewrite [mul_zero, pow_zero, pow_zero] + rfl + · rewrite [pow_succ, Ht, mul_succ, pow_add] + rfl + +end MyNat diff --git a/courses/nng/solutions/Power/pow_two.lean b/courses/nng/solutions/Power/pow_two.lean new file mode 100644 index 0000000..e564448 --- /dev/null +++ b/courses/nng/solutions/Power/pow_two.lean @@ -0,0 +1,15 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.one_pow + +namespace MyNat + +/-- For all naturals $a$, $a ^ 2 = a \times a$. -/ +theorem pow_two + (a : ℕ) : a ^ 2 = a * a := by + rewrite [two_eq_succ_one] + rewrite [pow_succ] + rewrite [pow_one] + rfl + +end MyNat diff --git a/courses/nng/solutions/Power/zero_pow_succ.lean b/courses/nng/solutions/Power/zero_pow_succ.lean new file mode 100644 index 0000000..216e23d --- /dev/null +++ b/courses/nng/solutions/Power/zero_pow_succ.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Power.zero_pow_zero + +namespace MyNat + +/-- For all numbers $m$, $0 ^{\operatorname{succ} (m)} = 0$. -/ +theorem zero_pow_succ + (m : ℕ) : (0 : ℕ) ^ (succ m) = 0 := by + rewrite [pow_succ] + rewrite [mul_zero] + rfl + +end MyNat diff --git a/courses/nng/solutions/Power/zero_pow_zero.lean b/courses/nng/solutions/Power/zero_pow_zero.lean new file mode 100644 index 0000000..13d2222 --- /dev/null +++ b/courses/nng/solutions/Power/zero_pow_zero.lean @@ -0,0 +1,12 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Multiplication.mul_assoc + +namespace MyNat + +/-- $0 ^ 0 = 1$ -/ +theorem zero_pow_zero : (0 : ℕ) ^ 0 = 1 := by + rewrite [pow_zero] + rfl + +end MyNat diff --git a/courses/nng/solutions/Tutorial/add_zero.lean b/courses/nng/solutions/Tutorial/add_zero.lean new file mode 100644 index 0000000..87caa0a --- /dev/null +++ b/courses/nng/solutions/Tutorial/add_zero.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.rw_backwards + +namespace MyNat + +/- $a+(b+0)+(c+0)=a+b+c.$ -/ +example (a b c : ℕ) : a + (b + 0) + (c + 0) = a + b + c := by + rewrite [add_zero] + rewrite [add_zero] + rfl + +end MyNat diff --git a/courses/nng/solutions/Tutorial/add_zero2.lean b/courses/nng/solutions/Tutorial/add_zero2.lean new file mode 100644 index 0000000..426ef7a --- /dev/null +++ b/courses/nng/solutions/Tutorial/add_zero2.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.add_zero + +namespace MyNat + +/- $a+(b+0)+(c+0)=a+b+c.$ -/ +example (a b c : ℕ) : a + (b + 0) + (c + 0) = a + b + c := by + rewrite [add_zero c] + rewrite [add_zero] + rfl + +end MyNat diff --git a/courses/nng/solutions/Tutorial/rfl.lean b/courses/nng/solutions/Tutorial/rfl.lean new file mode 100644 index 0000000..c557ace --- /dev/null +++ b/courses/nng/solutions/Tutorial/rfl.lean @@ -0,0 +1,12 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import MyNatLib.Prelude + +namespace MyNat + +/- If $x$ and $q$ are arbitrary natural numbers, then $37x+q=37x+q.$ -/ +example + (x q : ℕ) : 37 * x + q = 37 * x + q := by + rfl + +end MyNat diff --git a/courses/nng/solutions/Tutorial/rw.lean b/courses/nng/solutions/Tutorial/rw.lean new file mode 100644 index 0000000..c98619d --- /dev/null +++ b/courses/nng/solutions/Tutorial/rw.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.rfl + +namespace MyNat + +/- If $x$ and $y$ are natural numbers, and $y = x + 7$, then $2y = 2(x + 7)$. -/ +example + (x y : ℕ) (h : y = x + 7) : 2 * y = 2 * (x + 7) := by + rewrite [h] + rfl + +end MyNat diff --git a/courses/nng/solutions/Tutorial/rw_backwards.lean b/courses/nng/solutions/Tutorial/rw_backwards.lean new file mode 100644 index 0000000..4836a08 --- /dev/null +++ b/courses/nng/solutions/Tutorial/rw_backwards.lean @@ -0,0 +1,13 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.two_eq_ss0 + +namespace MyNat + +/- $2$ is the number after the number after $0$. -/ +example : 2 = succ (succ 0) := by + rewrite [← one_eq_succ_zero] + rewrite [← two_eq_succ_one] + rfl + +end MyNat diff --git a/courses/nng/solutions/Tutorial/succ_eq_add_one.lean b/courses/nng/solutions/Tutorial/succ_eq_add_one.lean new file mode 100644 index 0000000..c729f49 --- /dev/null +++ b/courses/nng/solutions/Tutorial/succ_eq_add_one.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.add_zero2 + +namespace MyNat + +/-- For all natural numbers $a$, we have $\operatorname{succ}(a) = a+1$. -/ +theorem succ_eq_add_one n : succ n = n + 1 := by + rewrite [one_eq_succ_zero] + rewrite [add_succ] + rewrite [add_zero] + rfl + +end MyNat diff --git a/courses/nng/solutions/Tutorial/two_eq_ss0.lean b/courses/nng/solutions/Tutorial/two_eq_ss0.lean new file mode 100644 index 0000000..3cf9d25 --- /dev/null +++ b/courses/nng/solutions/Tutorial/two_eq_ss0.lean @@ -0,0 +1,14 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.rw + +namespace MyNat + +/- $2$ is the number after the number after $0$. -/ +example + : 2 = succ (succ 0) := by + rewrite [two_eq_succ_one] + rewrite [one_eq_succ_zero] + rfl + +end MyNat diff --git a/courses/nng/solutions/Tutorial/twoaddtwo.lean b/courses/nng/solutions/Tutorial/twoaddtwo.lean new file mode 100644 index 0000000..dc797ff --- /dev/null +++ b/courses/nng/solutions/Tutorial/twoaddtwo.lean @@ -0,0 +1,18 @@ +-- Adapted from the Natural Number Game (leanprover-community/NNG4), Apache-2.0. +-- Modified for leanlings; see courses/nng/NOTICE. +import Tutorial.succ_eq_add_one + +namespace MyNat + +/- $2+2=4$. -/ +example : (2 : ℕ) + 2 = 4 := by + nth_rewrite 2 [two_eq_succ_one] + rewrite [add_succ] + rewrite [one_eq_succ_zero] + rewrite [add_succ] + rewrite [add_zero] + rewrite [four_eq_succ_three] + rewrite [three_eq_succ_two] + rfl + +end MyNat diff --git a/lakefile.lean b/lakefile.lean index 89d7671..e01bfca 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -9,3 +9,26 @@ lean_lib «Leanlings» @[default_target] lean_exe «leanlings» where root := `Main + +-- The `nng` course (Natural Number Game port) is a self-contained Lean library: +-- its solution files form one growing import-chain, so building it verifies every +-- solution. Exercises live outside the library and are checked on demand via +-- `lake env lean`. See courses/nng/. +@[default_target] +lean_lib «NNG» where + srcDir := "courses/nng/solutions" + -- Modules live under flat namespaces (`MyNatLib.*`, and one per world), so we + -- enumerate them explicitly rather than rely on a single root prefix. + globs := #[ + Glob.one `NNG, + Glob.submodules `MyNatLib, + Glob.submodules `Tutorial, + Glob.submodules `Addition, + Glob.submodules `Multiplication, + Glob.submodules `Power, + Glob.submodules `Implication, + Glob.submodules `AdvAddition, + Glob.submodules `LessOrEqual, + Glob.submodules `AdvMultiplication, + Glob.submodules `Algorithm + ] From eff914ca165603498c3d5baf1d5a943526672667 Mon Sep 17 00:00:00 2001 From: Samuel Schlesinger Date: Mon, 1 Jun 2026 23:31:31 -0400 Subject: [PATCH 4/4] =?UTF-8?q?Address=20intro-course=20feedback=20from=20?= =?UTF-8?q?issues=20#5=E2=80=93#9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #7 Hide the answers: move every exercise's `#guard` correctness checks out of the file the learner edits into a hidden courses/intro/tests/ tree (39 files). The Runner now checks in two phases — compile the exercise alone (so sorry/type errors report against the real file with correct line numbers), then, if clean, compile it together with the hidden checks, reporting a friendly "doesn't satisfy the checks yet" message instead of revealing them. CI now compiles each intro solution together with its hidden checks, so reference answers are verified to actually satisfy the guards (they weren't before — this caught two latent bugs, fixed below). #9 exists1: the instructions told learners to use `use`, which isn't in core Lean (it's a Mathlib tactic). Teach the core anonymous constructor `exact ⟨w, p⟩` / `refine ⟨w, ?_⟩` instead, and note where `use` comes from. #8 tactics3: `rewrite_back` was solvable with a forward `rw [h]`. Redesigned so the goal is about `b` while the extra fact is about `a`, forcing `rw [← h]`. #5 have1: the `double_neg_intro` solution didn't use `have` despite the instructions. It now names the contradiction with `have contra : False := hn hp`, and the exercise gives a step-by-step hint. #6 intro3: the first Bool exercise now tells the learner the Bool values are `true` and `false` (they hadn't been introduced yet). Latent bugs surfaced by verifying solutions against their checks: - 13_quiz/quiz1: solution carried a duplicate copy of the test fixtures; removed (the hidden test file owns them now). - 14_do_notation/do2: the guards compared `Except String Int` with `==` but no `BEq` instance existed (previously masked by `sorry`); the test now provides one. --- .github/workflows/ci.yml | 14 +++-- .gitignore | 1 + Leanlings/Exercise.lean | 5 ++ Leanlings/Runner.lean | 52 ++++++++++++++----- courses/intro/exercises/00_intro/intro1.lean | 3 -- courses/intro/exercises/00_intro/intro2.lean | 3 -- courses/intro/exercises/00_intro/intro3.lean | 7 +-- .../intro/exercises/01_basics/basics1.lean | 4 -- .../intro/exercises/01_basics/basics2.lean | 3 -- .../intro/exercises/01_basics/basics3.lean | 4 -- .../intro/exercises/01_basics/basics4.lean | 5 -- .../intro/exercises/02_definitions/defs1.lean | 5 -- .../intro/exercises/02_definitions/defs2.lean | 5 -- .../intro/exercises/02_definitions/defs3.lean | 3 -- .../exercises/03_functions/functions1.lean | 4 -- .../exercises/03_functions/functions2.lean | 4 -- .../exercises/03_functions/functions3.lean | 8 --- .../exercises/03_functions/functions4.lean | 5 -- .../intro/exercises/04_control_flow/if1.lean | 5 -- .../intro/exercises/04_control_flow/if2.lean | 5 -- .../exercises/04_control_flow/match1.lean | 4 -- .../exercises/05_structures/structs2.lean | 6 --- .../exercises/05_structures/structs3.lean | 6 --- .../exercises/06_inductive/inductive1.lean | 4 -- .../exercises/06_inductive/inductive2.lean | 6 --- .../exercises/06_inductive/inductive3.lean | 4 -- .../exercises/07_recursion/recursion1.lean | 4 -- .../exercises/07_recursion/recursion2.lean | 4 -- .../exercises/07_recursion/recursion3.lean | 4 -- .../exercises/07_recursion/recursion4.lean | 9 ---- .../intro/exercises/10_tactics/tactics3.lean | 6 ++- .../12_typeclasses/typeclasses2.lean | 9 ---- courses/intro/exercises/13_quiz/quiz1.lean | 48 ----------------- .../intro/exercises/14_do_notation/do1.lean | 4 -- .../intro/exercises/14_do_notation/do2.lean | 5 -- .../intro/exercises/14_do_notation/do3.lean | 7 --- .../exercises/16_implicit/implicit1.lean | 5 -- .../exercises/16_implicit/implicit2.lean | 5 -- .../intro/exercises/17_arrays/arrays1.lean | 7 --- .../intro/exercises/17_arrays/arrays2.lean | 5 -- .../intro/exercises/18_namespaces/ns1.lean | 5 -- .../intro/exercises/18_namespaces/ns2.lean | 7 --- courses/intro/exercises/19_quiz2/quiz2.lean | 11 ---- .../intro/exercises/20_exists/exists1.lean | 7 ++- .../intro/exercises/21_cases_have/have1.lean | 6 ++- .../intro/exercises/26_final_quiz/quiz3.lean | 11 ---- .../intro/solutions/10_tactics/tactics3.lean | 3 +- courses/intro/solutions/13_quiz/quiz1.lean | 48 ----------------- .../intro/solutions/21_cases_have/have1.lean | 3 +- courses/intro/tests/00_intro/intro1.lean | 3 ++ courses/intro/tests/00_intro/intro2.lean | 3 ++ courses/intro/tests/00_intro/intro3.lean | 5 ++ courses/intro/tests/01_basics/basics1.lean | 4 ++ courses/intro/tests/01_basics/basics2.lean | 3 ++ courses/intro/tests/01_basics/basics3.lean | 4 ++ courses/intro/tests/01_basics/basics4.lean | 5 ++ courses/intro/tests/02_definitions/defs1.lean | 5 ++ courses/intro/tests/02_definitions/defs2.lean | 5 ++ courses/intro/tests/02_definitions/defs3.lean | 3 ++ .../intro/tests/03_functions/functions1.lean | 4 ++ .../intro/tests/03_functions/functions2.lean | 4 ++ .../intro/tests/03_functions/functions3.lean | 8 +++ .../intro/tests/03_functions/functions4.lean | 5 ++ courses/intro/tests/04_control_flow/if1.lean | 5 ++ courses/intro/tests/04_control_flow/if2.lean | 5 ++ .../intro/tests/04_control_flow/match1.lean | 4 ++ .../intro/tests/05_structures/structs2.lean | 6 +++ .../intro/tests/05_structures/structs3.lean | 6 +++ .../intro/tests/06_inductive/inductive1.lean | 4 ++ .../intro/tests/06_inductive/inductive2.lean | 6 +++ .../intro/tests/06_inductive/inductive3.lean | 4 ++ .../intro/tests/07_recursion/recursion1.lean | 4 ++ .../intro/tests/07_recursion/recursion2.lean | 4 ++ .../intro/tests/07_recursion/recursion3.lean | 4 ++ .../intro/tests/07_recursion/recursion4.lean | 9 ++++ .../tests/12_typeclasses/typeclasses2.lean | 9 ++++ courses/intro/tests/13_quiz/quiz1.lean | 49 +++++++++++++++++ courses/intro/tests/14_do_notation/do1.lean | 4 ++ courses/intro/tests/14_do_notation/do2.lean | 11 ++++ courses/intro/tests/14_do_notation/do3.lean | 7 +++ .../intro/tests/16_implicit/implicit1.lean | 5 ++ .../intro/tests/16_implicit/implicit2.lean | 5 ++ courses/intro/tests/17_arrays/arrays1.lean | 7 +++ courses/intro/tests/17_arrays/arrays2.lean | 5 ++ courses/intro/tests/18_namespaces/ns1.lean | 5 ++ courses/intro/tests/18_namespaces/ns2.lean | 7 +++ courses/intro/tests/19_quiz2/quiz2.lean | 11 ++++ courses/intro/tests/26_final_quiz/quiz3.lean | 12 +++++ 88 files changed, 334 insertions(+), 323 deletions(-) create mode 100644 courses/intro/tests/00_intro/intro1.lean create mode 100644 courses/intro/tests/00_intro/intro2.lean create mode 100644 courses/intro/tests/00_intro/intro3.lean create mode 100644 courses/intro/tests/01_basics/basics1.lean create mode 100644 courses/intro/tests/01_basics/basics2.lean create mode 100644 courses/intro/tests/01_basics/basics3.lean create mode 100644 courses/intro/tests/01_basics/basics4.lean create mode 100644 courses/intro/tests/02_definitions/defs1.lean create mode 100644 courses/intro/tests/02_definitions/defs2.lean create mode 100644 courses/intro/tests/02_definitions/defs3.lean create mode 100644 courses/intro/tests/03_functions/functions1.lean create mode 100644 courses/intro/tests/03_functions/functions2.lean create mode 100644 courses/intro/tests/03_functions/functions3.lean create mode 100644 courses/intro/tests/03_functions/functions4.lean create mode 100644 courses/intro/tests/04_control_flow/if1.lean create mode 100644 courses/intro/tests/04_control_flow/if2.lean create mode 100644 courses/intro/tests/04_control_flow/match1.lean create mode 100644 courses/intro/tests/05_structures/structs2.lean create mode 100644 courses/intro/tests/05_structures/structs3.lean create mode 100644 courses/intro/tests/06_inductive/inductive1.lean create mode 100644 courses/intro/tests/06_inductive/inductive2.lean create mode 100644 courses/intro/tests/06_inductive/inductive3.lean create mode 100644 courses/intro/tests/07_recursion/recursion1.lean create mode 100644 courses/intro/tests/07_recursion/recursion2.lean create mode 100644 courses/intro/tests/07_recursion/recursion3.lean create mode 100644 courses/intro/tests/07_recursion/recursion4.lean create mode 100644 courses/intro/tests/12_typeclasses/typeclasses2.lean create mode 100644 courses/intro/tests/13_quiz/quiz1.lean create mode 100644 courses/intro/tests/14_do_notation/do1.lean create mode 100644 courses/intro/tests/14_do_notation/do2.lean create mode 100644 courses/intro/tests/14_do_notation/do3.lean create mode 100644 courses/intro/tests/16_implicit/implicit1.lean create mode 100644 courses/intro/tests/16_implicit/implicit2.lean create mode 100644 courses/intro/tests/17_arrays/arrays1.lean create mode 100644 courses/intro/tests/17_arrays/arrays2.lean create mode 100644 courses/intro/tests/18_namespaces/ns1.lean create mode 100644 courses/intro/tests/18_namespaces/ns2.lean create mode 100644 courses/intro/tests/19_quiz2/quiz2.lean create mode 100644 courses/intro/tests/26_final_quiz/quiz3.lean diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df7cb18..a37148a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,18 +28,26 @@ jobs: # The `nng` course's solutions form a Lean library, so they are already # compiled (and thus verified) by `lake build` above. Here we check the - # standalone `intro` solutions, which are compiled file-by-file. + # standalone `intro` solutions, compiled file-by-file together with each + # exercise's hidden correctness checks (courses/intro/tests/), so the + # reference answers are verified to actually satisfy the `#guard`s. - name: Verify intro solutions run: | failed=0 for f in $(find courses/intro/solutions -path '*/*.lean' | sort); do - if ! lean "$f" 2>&1; then + rel=${f#courses/intro/solutions/} + test="courses/intro/tests/$rel" + tmp=$(mktemp /tmp/leanlings-XXXXXX.lean) + cat "$f" > "$tmp" + if [ -f "$test" ]; then printf '\n\n' >> "$tmp"; cat "$test" >> "$tmp"; fi + if ! lean "$tmp" 2>&1; then echo "FAIL: $f" failed=$((failed + 1)) fi + rm -f "$tmp" done if [ $failed -gt 0 ]; then echo "$failed solution(s) failed" exit 1 fi - echo "All solutions verified" + echo "All intro solutions verified against their checks" diff --git a/.gitignore b/.gitignore index 44b5c2c..d6843a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .lake/ .leanlings-state +.leanlings-check.lean diff --git a/Leanlings/Exercise.lean b/Leanlings/Exercise.lean index 0e42a03..ec1092e 100644 --- a/Leanlings/Exercise.lean +++ b/Leanlings/Exercise.lean @@ -24,5 +24,10 @@ def path (e : Exercise) : System.FilePath := def solutionPath (e : Exercise) : System.FilePath := s!"courses/{e.course}/solutions/{e.dir}/{e.name}.lean" +/-- Hidden correctness checks (`#guard`s) for an exercise, kept out of the file +the learner edits so the expected answers aren't given away. May not exist. -/ +def testPath (e : Exercise) : System.FilePath := + s!"courses/{e.course}/tests/{e.dir}/{e.name}.lean" + end Exercise end Leanlings diff --git a/Leanlings/Runner.lean b/Leanlings/Runner.lean index 41e1164..dfab0d8 100644 --- a/Leanlings/Runner.lean +++ b/Leanlings/Runner.lean @@ -6,27 +6,53 @@ namespace Leanlings.Runner private def containsSubstr (s sub : String) : Bool := (s.splitOn sub).length > 1 -/-- Check a single exercise by running `lean` on it. - -We invoke it through `lake env lean` so that exercises which `import` a course -library (e.g. the `nng` course's `MyNat` development) resolve against the built -package. Exercises that import nothing (the `intro` course) are unaffected. -Requires the project to have been built (`lake build`). -/ -def checkExercise (exercise : Exercise) : IO ExerciseStatus := do - let output ← IO.Process.output { - cmd := "lake" - args := #["env", "lean", exercise.path.toString] - } +/-- Run `lean` on a file via `lake env lean` (so course-library imports resolve) +and classify the result. -/ +private def runLean (path : System.FilePath) : IO ExerciseStatus := do + let output ← IO.Process.output { cmd := "lake", args := #["env", "lean", path.toString] } -- Check for sorry first — if sorry is present, that's the primary issue - -- (test failures caused by sorry are noise the user doesn't need to see) + -- (test failures caused by sorry are noise the user doesn't need to see). if containsSubstr output.stderr "declaration uses `sorry`" || containsSubstr output.stdout "declaration uses `sorry`" then return .hasSorry - -- No sorry — check for compilation or test errors if output.exitCode != 0 then return .compileError output.stderr return .success +/-- Check a single exercise. + +Compiles via `lake env lean` so exercises that `import` a course library (e.g. +the `nng` course's `MyNat` development) resolve against the built package; the +`intro` course imports nothing and is unaffected. Requires `lake build` first. + +Correctness checks (`#guard`s) are kept in a hidden test file so the expected +answers aren't shown to the learner. We check in two phases: + +1. Compile the exercise alone, so `sorry`/type errors are reported against the + real file with correct line numbers. +2. If that is clean and a hidden test file exists, compile the exercise and the + tests together. A failure here means the code type-checks but doesn't meet + the requirement; we say so without revealing the checks. -/ +def checkExercise (exercise : Exercise) : IO ExerciseStatus := do + match ← runLean exercise.path with + | .success => + let testSrc ← (try some <$> IO.FS.readFile exercise.testPath catch _ => pure none) + match testSrc with + | none => return .success + | some tests => + let exSrc ← IO.FS.readFile exercise.path + let tmp : System.FilePath := ".leanlings-check.lean" + IO.FS.writeFile tmp (exSrc ++ "\n\n" ++ tests) + let result ← runLean tmp + try IO.FS.removeFile tmp catch _ => pure () + match result with + | .success => return .success + | _ => + return .compileError + "Your code compiles, but it doesn't satisfy the exercise's checks yet.\n\ + Re-read the task — the expected behaviour is described there." + | other => return other + /-- Display the result of checking an exercise -/ def displayResult (exercise : Exercise) (status : ExerciseStatus) : IO Unit := do match status with diff --git a/courses/intro/exercises/00_intro/intro1.lean b/courses/intro/exercises/00_intro/intro1.lean index 5fabd13..21c7df2 100644 --- a/courses/intro/exercises/00_intro/intro1.lean +++ b/courses/intro/exercises/00_intro/intro1.lean @@ -12,6 +12,3 @@ -/ def greeting : String := sorry - --- Don't change below this line! -#guard greeting == "Hello, Lean!" diff --git a/courses/intro/exercises/00_intro/intro2.lean b/courses/intro/exercises/00_intro/intro2.lean index 521fd50..4d3477a 100644 --- a/courses/intro/exercises/00_intro/intro2.lean +++ b/courses/intro/exercises/00_intro/intro2.lean @@ -11,6 +11,3 @@ -/ def favoriteNumber : Nat := "seven" - --- Don't change below this line! -#guard favoriteNumber > 0 diff --git a/courses/intro/exercises/00_intro/intro3.lean b/courses/intro/exercises/00_intro/intro3.lean index 30d5ff6..bb1c2b1 100644 --- a/courses/intro/exercises/00_intro/intro3.lean +++ b/courses/intro/exercises/00_intro/intro3.lean @@ -16,7 +16,7 @@ -/ -- Error: "type mismatch" — "yes" is a String, but Bool is expected. --- Fix: change the value to a Bool. +-- Fix: change the value to a Bool. The two Bool values are `true` and `false`. def isReady : Bool := "yes" -- Error: "type mismatch" — true is a Bool, but Nat is expected. @@ -26,8 +26,3 @@ def count : Nat := true -- Error: "type mismatch" — "hello" is a String, but Nat is expected. -- Fix: this time, change the TYPE annotation to match the value. def message : Nat := "hello" - --- Don't change below this line! -#guard isReady == true -#guard count > 0 -#guard message == "hello" diff --git a/courses/intro/exercises/01_basics/basics1.lean b/courses/intro/exercises/01_basics/basics1.lean index 47ca841..efb92c3 100644 --- a/courses/intro/exercises/01_basics/basics1.lean +++ b/courses/intro/exercises/01_basics/basics1.lean @@ -15,7 +15,3 @@ def product : Nat := sorry def power : Nat := sorry - --- Don't change below this line! -#guard product == 42 -#guard power == 1024 diff --git a/courses/intro/exercises/01_basics/basics2.lean b/courses/intro/exercises/01_basics/basics2.lean index 55fef56..7d1d512 100644 --- a/courses/intro/exercises/01_basics/basics2.lean +++ b/courses/intro/exercises/01_basics/basics2.lean @@ -13,6 +13,3 @@ def myNat : Nat := sorry def myString : String := sorry def myBool : Bool := sorry - --- Don't change below this line! -#guard myString.length > 0 diff --git a/courses/intro/exercises/01_basics/basics3.lean b/courses/intro/exercises/01_basics/basics3.lean index 11f70ce..c15c250 100644 --- a/courses/intro/exercises/01_basics/basics3.lean +++ b/courses/intro/exercises/01_basics/basics3.lean @@ -16,7 +16,3 @@ def hello : String := sorry -- Use `s!"..."` with `{name}` to interpolate a variable def name := "Lean" def greeting : String := sorry - --- Don't change below this line! -#guard hello == "Hello, Lean!" -#guard greeting == "Hello, Lean!" diff --git a/courses/intro/exercises/01_basics/basics4.lean b/courses/intro/exercises/01_basics/basics4.lean index 550d5bd..d3268bf 100644 --- a/courses/intro/exercises/01_basics/basics4.lean +++ b/courses/intro/exercises/01_basics/basics4.lean @@ -18,8 +18,3 @@ def or_result : Bool := sorry -- NOT false is true def not_result : Bool := sorry - --- Don't change below this line! -#guard and_result == true -#guard or_result == true -#guard not_result == true diff --git a/courses/intro/exercises/02_definitions/defs1.lean b/courses/intro/exercises/02_definitions/defs1.lean index dd028dc..babb726 100644 --- a/courses/intro/exercises/02_definitions/defs1.lean +++ b/courses/intro/exercises/02_definitions/defs1.lean @@ -24,8 +24,3 @@ def helloLength : Nat := sorry -- Hint: try `#eval (List.range 5).length` -- List.range 5 produces [0, 1, 2, 3, 4] def rangeLength : Nat := sorry - --- Don't change below this line! -#guard powerOfTwo == 1024 -#guard helloLength == 5 -#guard rangeLength == 5 diff --git a/courses/intro/exercises/02_definitions/defs2.lean b/courses/intro/exercises/02_definitions/defs2.lean index d8c73b0..6ea4c60 100644 --- a/courses/intro/exercises/02_definitions/defs2.lean +++ b/courses/intro/exercises/02_definitions/defs2.lean @@ -12,8 +12,3 @@ -/ def square (n : Nat) : Nat := sorry - --- Don't change below this line! -#guard square 0 == 0 -#guard square 5 == 25 -#guard square 3 == 9 diff --git a/courses/intro/exercises/02_definitions/defs3.lean b/courses/intro/exercises/02_definitions/defs3.lean index f30c6f2..5c19972 100644 --- a/courses/intro/exercises/02_definitions/defs3.lean +++ b/courses/intro/exercises/02_definitions/defs3.lean @@ -14,6 +14,3 @@ def difference_of_squares : Nat := sorry - --- Don't change below this line! -#guard difference_of_squares == 91 diff --git a/courses/intro/exercises/03_functions/functions1.lean b/courses/intro/exercises/03_functions/functions1.lean index e404204..8021f37 100644 --- a/courses/intro/exercises/03_functions/functions1.lean +++ b/courses/intro/exercises/03_functions/functions1.lean @@ -9,7 +9,3 @@ -/ def triple (n : Nat) : Nat := sorry - --- Don't change below this line! -#guard triple 0 == 0 -#guard triple 4 == 12 diff --git a/courses/intro/exercises/03_functions/functions2.lean b/courses/intro/exercises/03_functions/functions2.lean index 5c8772f..7db3c2f 100644 --- a/courses/intro/exercises/03_functions/functions2.lean +++ b/courses/intro/exercises/03_functions/functions2.lean @@ -14,7 +14,3 @@ -/ def average (a b : Nat) : Nat := sorry - --- Don't change below this line! -#guard average 4 6 == 5 -#guard average 10 20 == 15 diff --git a/courses/intro/exercises/03_functions/functions3.lean b/courses/intro/exercises/03_functions/functions3.lean index 2b0cc75..751edb3 100644 --- a/courses/intro/exercises/03_functions/functions3.lean +++ b/courses/intro/exercises/03_functions/functions3.lean @@ -24,11 +24,3 @@ def isZero : Nat → Bool := fun n => sorry -- Now use the `·` shorthand: (· + 1) means fun x => x + 1 def tripler : Nat → Nat := sorry - --- Don't change below this line! -#guard doubler 5 == 10 -#guard doubler 0 == 0 -#guard isZero 0 == true -#guard isZero 5 == false -#guard tripler 3 == 9 -#guard tripler 0 == 0 diff --git a/courses/intro/exercises/03_functions/functions4.lean b/courses/intro/exercises/03_functions/functions4.lean index 332320a..afd87b8 100644 --- a/courses/intro/exercises/03_functions/functions4.lean +++ b/courses/intro/exercises/03_functions/functions4.lean @@ -23,8 +23,3 @@ def evens : List Nat := [1, 2, 3, 4, 5, 6].filter sorry -- Sum all elements using foldl def total : Nat := [10, 20, 30].foldl sorry sorry - --- Don't change below this line! -#guard doubled == [2, 4, 6] -#guard evens == [2, 4, 6] -#guard total == 60 diff --git a/courses/intro/exercises/04_control_flow/if1.lean b/courses/intro/exercises/04_control_flow/if1.lean index a474efc..32d7e7f 100644 --- a/courses/intro/exercises/04_control_flow/if1.lean +++ b/courses/intro/exercises/04_control_flow/if1.lean @@ -16,8 +16,3 @@ -/ def abs (n : Int) : Int := sorry - --- Don't change below this line! -#guard abs 5 == 5 -#guard abs (-3) == 3 -#guard abs 0 == 0 diff --git a/courses/intro/exercises/04_control_flow/if2.lean b/courses/intro/exercises/04_control_flow/if2.lean index 5e380e4..94345df 100644 --- a/courses/intro/exercises/04_control_flow/if2.lean +++ b/courses/intro/exercises/04_control_flow/if2.lean @@ -13,8 +13,3 @@ -/ def classify (n : Int) : String := sorry - --- Don't change below this line! -#guard classify 5 == "positive" -#guard classify 0 == "zero" -#guard classify (-3) == "negative" diff --git a/courses/intro/exercises/04_control_flow/match1.lean b/courses/intro/exercises/04_control_flow/match1.lean index ffc2774..c66054c 100644 --- a/courses/intro/exercises/04_control_flow/match1.lean +++ b/courses/intro/exercises/04_control_flow/match1.lean @@ -17,7 +17,3 @@ def getOrDefault (opt : Option Nat) (default : Nat) : Nat := sorry - --- Don't change below this line! -#guard getOrDefault (some 42) 0 == 42 -#guard getOrDefault none 99 == 99 diff --git a/courses/intro/exercises/05_structures/structs2.lean b/courses/intro/exercises/05_structures/structs2.lean index 8b88c72..94a3654 100644 --- a/courses/intro/exercises/05_structures/structs2.lean +++ b/courses/intro/exercises/05_structures/structs2.lean @@ -24,9 +24,3 @@ def isAdult (p : Person) : Bool := sorry -- Return a new Person with age incremented by 1. -- Use `{ p with ... }` to copy all fields except the one you change. def birthday (p : Person) : Person := sorry - --- Don't change below this line! -#guard fullName { firstName := "Jane", lastName := "Doe", age := 25 } == "Jane Doe" -#guard isAdult { firstName := "A", lastName := "B", age := 18 } == true -#guard isAdult { firstName := "A", lastName := "B", age := 17 } == false -#guard birthday { firstName := "A", lastName := "B", age := 25 } == { firstName := "A", lastName := "B", age := 26 } diff --git a/courses/intro/exercises/05_structures/structs3.lean b/courses/intro/exercises/05_structures/structs3.lean index bfa0695..431c8cb 100644 --- a/courses/intro/exercises/05_structures/structs3.lean +++ b/courses/intro/exercises/05_structures/structs3.lean @@ -31,9 +31,3 @@ def white : RGBColor := sorry -- Black: use all defaults def black : RGBColor := sorry - --- Don't change below this line! -#guard pureRed.red == 255 -#guard pureRed.green == 0 -#guard white == { red := 255, green := 255, blue := 255 : RGBColor } -#guard black == ({} : RGBColor) diff --git a/courses/intro/exercises/06_inductive/inductive1.lean b/courses/intro/exercises/06_inductive/inductive1.lean index f5a0b2e..0e444c2 100644 --- a/courses/intro/exercises/06_inductive/inductive1.lean +++ b/courses/intro/exercises/06_inductive/inductive1.lean @@ -27,7 +27,3 @@ inductive Direction where open Direction in def opposite (d : Direction) : Direction := sorry - --- Don't change below this line! -open Direction in -#guard opposite north == south && opposite south == north && opposite east == west && opposite west == east diff --git a/courses/intro/exercises/06_inductive/inductive2.lean b/courses/intro/exercises/06_inductive/inductive2.lean index a94d9c5..36ae11c 100644 --- a/courses/intro/exercises/06_inductive/inductive2.lean +++ b/courses/intro/exercises/06_inductive/inductive2.lean @@ -35,9 +35,3 @@ def getValueOr (r : Result) (default : Nat) : Nat := sorry -- Use `fun | .ok _ => ... | .error _ => ...` def isOk : Result → Bool := sorry - --- Don't change below this line! -#guard getValueOr (.ok 42) 0 == 42 -#guard getValueOr (.error "oops") 99 == 99 -#guard isOk (.ok 5) == true -#guard isOk (.error "nope") == false diff --git a/courses/intro/exercises/06_inductive/inductive3.lean b/courses/intro/exercises/06_inductive/inductive3.lean index 56b54d3..504a632 100644 --- a/courses/intro/exercises/06_inductive/inductive3.lean +++ b/courses/intro/exercises/06_inductive/inductive3.lean @@ -31,7 +31,3 @@ def isNum : Expr → Bool := sorry -- Construct the expression representing (2 + 3) * 4 def sampleExpr : Expr := sorry - --- Don't change below this line! -#guard isNum (.num 5) == true -#guard isNum (.add (.num 1) (.num 2)) == false diff --git a/courses/intro/exercises/07_recursion/recursion1.lean b/courses/intro/exercises/07_recursion/recursion1.lean index 2be55ba..ce65346 100644 --- a/courses/intro/exercises/07_recursion/recursion1.lean +++ b/courses/intro/exercises/07_recursion/recursion1.lean @@ -16,7 +16,3 @@ -/ def factorial : Nat → Nat := sorry - --- Don't change below this line! -#guard factorial 0 == 1 -#guard factorial 5 == 120 diff --git a/courses/intro/exercises/07_recursion/recursion2.lean b/courses/intro/exercises/07_recursion/recursion2.lean index efc8824..0a6abc0 100644 --- a/courses/intro/exercises/07_recursion/recursion2.lean +++ b/courses/intro/exercises/07_recursion/recursion2.lean @@ -13,7 +13,3 @@ -/ def sum : List Nat → Nat := sorry - --- Don't change below this line! -#guard sum [] == 0 -#guard sum [1, 2, 3] == 6 diff --git a/courses/intro/exercises/07_recursion/recursion3.lean b/courses/intro/exercises/07_recursion/recursion3.lean index cb9a30d..3b9b68d 100644 --- a/courses/intro/exercises/07_recursion/recursion3.lean +++ b/courses/intro/exercises/07_recursion/recursion3.lean @@ -18,7 +18,3 @@ def reverse (l : List α) : List α := sorry - --- Don't change below this line! -#guard reverse [1, 2, 3] == [3, 2, 1] -#guard reverse ([] : List Nat) == [] diff --git a/courses/intro/exercises/07_recursion/recursion4.lean b/courses/intro/exercises/07_recursion/recursion4.lean index 0ea91f7..01407f8 100644 --- a/courses/intro/exercises/07_recursion/recursion4.lean +++ b/courses/intro/exercises/07_recursion/recursion4.lean @@ -38,12 +38,3 @@ def eval : Expr → Nat := sorry -- Count how many `num` leaves are in the expression def countNums : Expr → Nat := sorry - --- Don't change below this line! -#guard eval (.num 7) == 7 -#guard eval (.add (.num 2) (.num 3)) == 5 -#guard eval (.mul (.num 4) (.num 5)) == 20 -#guard eval (.add (.mul (.num 2) (.num 3)) (.num 1)) == 7 -#guard countNums (.num 5) == 1 -#guard countNums (.add (.num 1) (.num 2)) == 2 -#guard countNums (.mul (.add (.num 1) (.num 2)) (.num 3)) == 3 diff --git a/courses/intro/exercises/10_tactics/tactics3.lean b/courses/intro/exercises/10_tactics/tactics3.lean index a73b4a0..205da4c 100644 --- a/courses/intro/exercises/10_tactics/tactics3.lean +++ b/courses/intro/exercises/10_tactics/tactics3.lean @@ -19,6 +19,8 @@ theorem rewrite_example (a b : Nat) (h : a = b) : a + 1 = b + 1 := by theorem rewrite_chain (a b c : Nat) (h1 : a = b) (h2 : b = c) : a = c := by sorry --- Use backwards rewriting: replace b with a in the goal -theorem rewrite_back (a b : Nat) (h : a = b) : b + 1 = a + 1 := by +-- Use backwards rewriting. The goal is about `b`, but our extra fact `ha` is +-- about `a`. Forward `rw [h]` looks for `a` in the goal and finds none, so you +-- must rewrite backwards (`rw [← h]`) to turn `b` into `a` first. +theorem rewrite_back (a b : Nat) (h : a = b) (ha : a + 1 = 5) : b + 1 = 5 := by sorry diff --git a/courses/intro/exercises/12_typeclasses/typeclasses2.lean b/courses/intro/exercises/12_typeclasses/typeclasses2.lean index c0461fa..4919477 100644 --- a/courses/intro/exercises/12_typeclasses/typeclasses2.lean +++ b/courses/intro/exercises/12_typeclasses/typeclasses2.lean @@ -31,12 +31,3 @@ instance : BEq Suit where -- TODO: Implement Describable for Suit instance : Describable Suit where describe := sorry - --- Don't change below this line! -#guard (Suit.hearts == Suit.hearts) == true -#guard (Suit.hearts == Suit.diamonds) == false -#guard (Suit.clubs == Suit.spades) == false -open Describable in -#guard (describe Suit.hearts).length > 0 -open Describable in -#guard (describe Suit.diamonds).length > 0 diff --git a/courses/intro/exercises/13_quiz/quiz1.lean b/courses/intro/exercises/13_quiz/quiz1.lean index 068611c..0557757 100644 --- a/courses/intro/exercises/13_quiz/quiz1.lean +++ b/courses/intro/exercises/13_quiz/quiz1.lean @@ -132,51 +132,3 @@ theorem bookTitles_append (l1 l2 : List Book) : sorry -- ============================================= --- Don't change below this line! --- ============================================= - -def catalog : List Book := [ - ⟨"Dune", "Herbert", 412, .science, .stars 5⟩, - ⟨"Sapiens", "Harari", 443, .history, .stars 4⟩, - { title := "Cosmos", author := "Sagan", pages := 365, genre := .science }, - ⟨"The Hobbit", "Tolkien", 310, .fantasy, .stars 5⟩, - ⟨"SPQR", "Beard", 606, .history, .stars 3⟩ -] - -#guard Genre.science == Genre.science -#guard Genre.history == Genre.history -#guard Genre.fantasy == Genre.fantasy -#guard Genre.science != Genre.history -#guard Genre.fantasy != Genre.science -#guard toString (Rating.stars 5) == "5 stars" -#guard toString (Rating.stars 2) == "2 stars" -#guard toString (Rating.stars 1) == "1 star" -#guard toString Rating.unrated == "unrated" -#guard Genre.name .science == "science" -#guard Genre.name .history == "history" -#guard Genre.name .fantasy == "fantasy" -#guard (Rating.stars 5).toNat == 5 -#guard (Rating.stars 0).toNat == 0 -#guard Rating.unrated.toNat == 0 -#guard (⟨"Big", "A", 500, .fantasy, .unrated⟩ : Book).isLong == true -#guard (⟨"Small", "B", 100, .fantasy, .unrated⟩ : Book).isLong == false -#guard (⟨"Edge", "C", 300, .fantasy, .unrated⟩ : Book).isLong == false -#guard ({ title := "A", author := "B", pages := 500, genre := .science } : Book).isWorthReading == true -#guard (⟨"A", "B", 100, .fantasy, .stars 5⟩ : Book).isWorthReading == true -#guard ({ title := "A", author := "B", pages := 100, genre := .fantasy } : Book).isWorthReading == false -#guard (⟨"A", "B", 500, .fantasy, .stars 2⟩ : Book).isWorthReading == false -#guard Book.describe ⟨"Test", "Author", 100, .fantasy, .unrated⟩ - == "Test by Author (100 pages, fantasy)" -#guard ((⟨"X", "Y", 100, .fantasy, .unrated⟩ : Book).withRating (.stars 4)).rating.toNat == 4 -#guard (findBook "Cosmos" catalog).isSome == true -#guard (findBook "Moby Dick" catalog).isSome == false -#guard (findBook "Dune" catalog).map (·.pages) == some 412 -#guard (booksOfGenre .science catalog).length == 2 -#guard (booksOfGenre .fantasy catalog).length == 1 -#guard titles catalog == ["Dune", "Sapiens", "Cosmos", "The Hobbit", "SPQR"] -#guard totalPages [] == 0 -#guard totalPages catalog == 2136 -#guard starBar (.stars 3) == "***" -#guard starBar (.stars 1) == "*" -#guard starBar (.stars 0) == "" -#guard starBar .unrated == "" diff --git a/courses/intro/exercises/14_do_notation/do1.lean b/courses/intro/exercises/14_do_notation/do1.lean index 50fe5e0..003ace6 100644 --- a/courses/intro/exercises/14_do_notation/do1.lean +++ b/courses/intro/exercises/14_do_notation/do1.lean @@ -31,7 +31,3 @@ def chainedDivide : Option Nat := do -- This should return none (division by zero in the chain) def failingDivide : Option Nat := do sorry - --- Don't change below this line! -#guard chainedDivide == some 5 -#guard failingDivide == none diff --git a/courses/intro/exercises/14_do_notation/do2.lean b/courses/intro/exercises/14_do_notation/do2.lean index 2f8da79..3efb07d 100644 --- a/courses/intro/exercises/14_do_notation/do2.lean +++ b/courses/intro/exercises/14_do_notation/do2.lean @@ -20,8 +20,3 @@ def checkSmall (n : Int) : Except String Int := -- Chain the two checks using do notation. def validate (n : Int) : Except String Int := do sorry - --- Don't change below this line! -#guard validate 50 == .ok 50 -#guard validate (-1) == .error "must be positive" -#guard validate 200 == .error "must be less than 100" diff --git a/courses/intro/exercises/14_do_notation/do3.lean b/courses/intro/exercises/14_do_notation/do3.lean index 248bde0..69a82fe 100644 --- a/courses/intro/exercises/14_do_notation/do3.lean +++ b/courses/intro/exercises/14_do_notation/do3.lean @@ -21,10 +21,3 @@ def countWhere (p : Nat → Bool) (l : List Nat) : Nat := Id.run do -- Find the maximum element (return 0 for empty list) def listMax (l : List Nat) : Nat := Id.run do sorry - --- Don't change below this line! -#guard countWhere (· > 3) [1, 2, 3, 4, 5] == 2 -#guard countWhere (· == 0) [0, 1, 0, 2] == 2 -#guard countWhere (· > 0) ([] : List Nat) == 0 -#guard listMax [1, 5, 3, 9, 2] == 9 -#guard listMax ([] : List Nat) == 0 diff --git a/courses/intro/exercises/16_implicit/implicit1.lean b/courses/intro/exercises/16_implicit/implicit1.lean index 86725fd..61ad40f 100644 --- a/courses/intro/exercises/16_implicit/implicit1.lean +++ b/courses/intro/exercises/16_implicit/implicit1.lean @@ -24,8 +24,3 @@ def mySwap {α β : Type} (p : α × β) : β × α := sorry -- Apply a function to both elements of a pair def mapPair {α β γ : Type} (f : α → γ) (g : β → γ) (p : α × β) : γ × γ := sorry - --- Don't change below this line! -#guard myFst (1, "hello") == 1 -#guard mySwap (1, 2) == (2, 1) -#guard mapPair (· + 1) (· * 2) (3, 4) == (4, 8) diff --git a/courses/intro/exercises/16_implicit/implicit2.lean b/courses/intro/exercises/16_implicit/implicit2.lean index 2ad83d0..3fe970d 100644 --- a/courses/intro/exercises/16_implicit/implicit2.lean +++ b/courses/intro/exercises/16_implicit/implicit2.lean @@ -22,8 +22,3 @@ def myContains [BEq α] (x : α) (l : List α) : Bool := sorry -- Remove duplicates from a list (needs BEq) def myDedup [BEq α] (l : List α) : List α := sorry - --- Don't change below this line! -#guard myContains 3 [1, 2, 3] == true -#guard myContains 4 [1, 2, 3] == false -#guard myContains 1 ([] : List Nat) == false diff --git a/courses/intro/exercises/17_arrays/arrays1.lean b/courses/intro/exercises/17_arrays/arrays1.lean index a42d836..68e5419 100644 --- a/courses/intro/exercises/17_arrays/arrays1.lean +++ b/courses/intro/exercises/17_arrays/arrays1.lean @@ -22,10 +22,3 @@ def arraySum (a : Array Nat) : Nat := sorry -- Keep only elements greater than a threshold def filterAbove (a : Array Nat) (threshold : Nat) : Array Nat := sorry - --- Don't change below this line! -#guard doubleArray #[1, 2, 3] == #[2, 4, 6] -#guard doubleArray #[] == #[] -#guard arraySum #[1, 2, 3, 4] == 10 -#guard arraySum #[] == 0 -#guard filterAbove #[1, 5, 3, 8, 2] 3 == #[5, 8] diff --git a/courses/intro/exercises/17_arrays/arrays2.lean b/courses/intro/exercises/17_arrays/arrays2.lean index 30cbde3..4f54846 100644 --- a/courses/intro/exercises/17_arrays/arrays2.lean +++ b/courses/intro/exercises/17_arrays/arrays2.lean @@ -22,8 +22,3 @@ def squares : Array Nat := sorry -- Reverse an array (without using Array.reverse) def myReverse (a : Array Nat) : Array Nat := sorry - --- Don't change below this line! -#guard squares == #[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] -#guard myReverse #[1, 2, 3] == #[3, 2, 1] -#guard myReverse #[] == #[] diff --git a/courses/intro/exercises/18_namespaces/ns1.lean b/courses/intro/exercises/18_namespaces/ns1.lean index 4074fc1..ff5538a 100644 --- a/courses/intro/exercises/18_namespaces/ns1.lean +++ b/courses/intro/exercises/18_namespaces/ns1.lean @@ -34,8 +34,3 @@ end MyMath -- open MyMath in -- ...use square, cube without prefix... def sumOfPowers (n : Nat) : Nat := sorry - --- Don't change below this line! -#guard MyMath.square 5 == 25 -#guard MyMath.cube 3 == 27 -#guard sumOfPowers 2 == 12 diff --git a/courses/intro/exercises/18_namespaces/ns2.lean b/courses/intro/exercises/18_namespaces/ns2.lean index 3ae86ae..c2dbbc4 100644 --- a/courses/intro/exercises/18_namespaces/ns2.lean +++ b/courses/intro/exercises/18_namespaces/ns2.lean @@ -29,10 +29,3 @@ section def myRemoveAll (x : α) : List α → List α := sorry end - --- Don't change below this line! -#guard myElem 3 [1, 2, 3] == true -#guard myElem 4 [1, 2, 3] == false -#guard myElem 1 ([] : List Nat) == false -#guard myRemoveAll 2 [1, 2, 3, 2, 4] == [1, 3, 4] -#guard myRemoveAll 5 [1, 2, 3] == [1, 2, 3] diff --git a/courses/intro/exercises/19_quiz2/quiz2.lean b/courses/intro/exercises/19_quiz2/quiz2.lean index 88e4524..18a1d83 100644 --- a/courses/intro/exercises/19_quiz2/quiz2.lean +++ b/courses/intro/exercises/19_quiz2/quiz2.lean @@ -24,14 +24,3 @@ def sumOfSquares (n : Nat) : Nat := Id.run do -- appears in the list. Needs [BEq α] constraint. def countOccurrences [BEq α] (target : α) (l : List α) : Nat := sorry - --- Don't change below this line! -#guard safeDivTwice 100 5 4 == some 5 -#guard safeDivTwice 100 0 4 == none -#guard safeDivTwice 100 5 0 == none -#guard sumOfSquares 0 == 0 -#guard sumOfSquares 1 == 1 -#guard sumOfSquares 3 == 14 -#guard countOccurrences 3 [1, 3, 2, 3, 3] == 3 -#guard countOccurrences "a" ["b", "a", "c"] == 1 -#guard countOccurrences 7 ([] : List Nat) == 0 diff --git a/courses/intro/exercises/20_exists/exists1.lean b/courses/intro/exercises/20_exists/exists1.lean index 624aedf..381b2b9 100644 --- a/courses/intro/exercises/20_exists/exists1.lean +++ b/courses/intro/exercises/20_exists/exists1.lean @@ -16,8 +16,11 @@ Or in tactic mode: exact ⟨witness, proof⟩ - -- or -- - use witness -- then prove P witness + -- or, to leave the proof of `P witness` as a follow-up goal -- + refine ⟨witness, ?_⟩ -- then prove P witness + + (Mathlib's `use` tactic does the same thing, but it isn't part of + core Lean, so we use the anonymous constructor ⟨_, _⟩ here.) TODO: Prove these existential statements. -/ diff --git a/courses/intro/exercises/21_cases_have/have1.lean b/courses/intro/exercises/21_cases_have/have1.lean index a6c6486..18847b2 100644 --- a/courses/intro/exercises/21_cases_have/have1.lean +++ b/courses/intro/exercises/21_cases_have/have1.lean @@ -19,6 +19,10 @@ theorem chain (P Q R : Prop) (hpq : P → Q) (hqr : Q → R) (hp : P) : R := by sorry --- Use have to establish an intermediate fact +-- Use `have` to establish an intermediate fact. +-- Recall `¬¬P` unfolds to `(¬P) → False`, so start with `intro hn` to get +-- `hn : ¬P`. Then `¬P` is itself `P → False`, so `hn hp : False`. +-- Name that contradiction with `have contra : False := hn hp`, then close +-- the goal with it. theorem double_neg_intro (P : Prop) (hp : P) : ¬¬P := by sorry diff --git a/courses/intro/exercises/26_final_quiz/quiz3.lean b/courses/intro/exercises/26_final_quiz/quiz3.lean index c060342..42381a8 100644 --- a/courses/intro/exercises/26_final_quiz/quiz3.lean +++ b/courses/intro/exercises/26_final_quiz/quiz3.lean @@ -100,14 +100,3 @@ theorem Tree.size_zero_iff_leaf (t : Tree α) : t.size = 0 ↔ t = .leaf := by sorry -- ============================================= --- Don't change below this line! --- ============================================= -#guard sampleTree.size == 3 -#guard (Tree.leaf : Tree Nat).size == 0 -#guard sampleTree.toList == [1, 2, 3] -#guard sampleTree.mirror.size == 3 -#guard sampleTree.mirror.toList == [3, 2, 1] -#guard sampleTree.mirror.mirror == sampleTree -#guard sampleTree.depth == 2 -#guard (Tree.leaf : Tree Nat).depth == 0 -#guard sampleTree.toList.length == sampleTree.size diff --git a/courses/intro/solutions/10_tactics/tactics3.lean b/courses/intro/solutions/10_tactics/tactics3.lean index 8267039..f82bfca 100644 --- a/courses/intro/solutions/10_tactics/tactics3.lean +++ b/courses/intro/solutions/10_tactics/tactics3.lean @@ -4,5 +4,6 @@ theorem rewrite_example (a b : Nat) (h : a = b) : a + 1 = b + 1 := by theorem rewrite_chain (a b c : Nat) (h1 : a = b) (h2 : b = c) : a = c := by rw [h1, h2] -theorem rewrite_back (a b : Nat) (h : a = b) : b + 1 = a + 1 := by +theorem rewrite_back (a b : Nat) (h : a = b) (ha : a + 1 = 5) : b + 1 = 5 := by rw [← h] + exact ha diff --git a/courses/intro/solutions/13_quiz/quiz1.lean b/courses/intro/solutions/13_quiz/quiz1.lean index e46b931..8a8efcd 100644 --- a/courses/intro/solutions/13_quiz/quiz1.lean +++ b/courses/intro/solutions/13_quiz/quiz1.lean @@ -115,51 +115,3 @@ theorem bookTitles_append (l1 l2 : List Book) : induction l1 with | nil => simp [bookTitles] | cons b bs ih => simp [bookTitles, ih] - --- Guards - -def catalog : List Book := [ - ⟨"Dune", "Herbert", 412, .science, .stars 5⟩, - ⟨"Sapiens", "Harari", 443, .history, .stars 4⟩, - { title := "Cosmos", author := "Sagan", pages := 365, genre := .science }, - ⟨"The Hobbit", "Tolkien", 310, .fantasy, .stars 5⟩, - ⟨"SPQR", "Beard", 606, .history, .stars 3⟩ -] - -#guard Genre.science == Genre.science -#guard Genre.history == Genre.history -#guard Genre.fantasy == Genre.fantasy -#guard Genre.science != Genre.history -#guard Genre.fantasy != Genre.science -#guard toString (Rating.stars 5) == "5 stars" -#guard toString (Rating.stars 2) == "2 stars" -#guard toString (Rating.stars 1) == "1 star" -#guard toString Rating.unrated == "unrated" -#guard Genre.name .science == "science" -#guard Genre.name .history == "history" -#guard Genre.name .fantasy == "fantasy" -#guard (Rating.stars 5).toNat == 5 -#guard (Rating.stars 0).toNat == 0 -#guard Rating.unrated.toNat == 0 -#guard (⟨"Big", "A", 500, .fantasy, .unrated⟩ : Book).isLong == true -#guard (⟨"Small", "B", 100, .fantasy, .unrated⟩ : Book).isLong == false -#guard (⟨"Edge", "C", 300, .fantasy, .unrated⟩ : Book).isLong == false -#guard ({ title := "A", author := "B", pages := 500, genre := .science } : Book).isWorthReading == true -#guard (⟨"A", "B", 100, .fantasy, .stars 5⟩ : Book).isWorthReading == true -#guard ({ title := "A", author := "B", pages := 100, genre := .fantasy } : Book).isWorthReading == false -#guard (⟨"A", "B", 500, .fantasy, .stars 2⟩ : Book).isWorthReading == false -#guard Book.describe ⟨"Test", "Author", 100, .fantasy, .unrated⟩ - == "Test by Author (100 pages, fantasy)" -#guard ((⟨"X", "Y", 100, .fantasy, .unrated⟩ : Book).withRating (.stars 4)).rating.toNat == 4 -#guard (findBook "Cosmos" catalog).isSome == true -#guard (findBook "Moby Dick" catalog).isSome == false -#guard (findBook "Dune" catalog).map (·.pages) == some 412 -#guard (booksOfGenre .science catalog).length == 2 -#guard (booksOfGenre .fantasy catalog).length == 1 -#guard titles catalog == ["Dune", "Sapiens", "Cosmos", "The Hobbit", "SPQR"] -#guard totalPages [] == 0 -#guard totalPages catalog == 2136 -#guard starBar (.stars 3) == "***" -#guard starBar (.stars 1) == "*" -#guard starBar (.stars 0) == "" -#guard starBar .unrated == "" diff --git a/courses/intro/solutions/21_cases_have/have1.lean b/courses/intro/solutions/21_cases_have/have1.lean index 1511b92..49d426a 100644 --- a/courses/intro/solutions/21_cases_have/have1.lean +++ b/courses/intro/solutions/21_cases_have/have1.lean @@ -4,4 +4,5 @@ theorem chain (P Q R : Prop) (hpq : P → Q) (hqr : Q → R) (hp : P) : R := by theorem double_neg_intro (P : Prop) (hp : P) : ¬¬P := by intro hn - exact hn hp + have contra : False := hn hp + exact contra diff --git a/courses/intro/tests/00_intro/intro1.lean b/courses/intro/tests/00_intro/intro1.lean new file mode 100644 index 0000000..faff7c3 --- /dev/null +++ b/courses/intro/tests/00_intro/intro1.lean @@ -0,0 +1,3 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard greeting == "Hello, Lean!" + diff --git a/courses/intro/tests/00_intro/intro2.lean b/courses/intro/tests/00_intro/intro2.lean new file mode 100644 index 0000000..33cfc17 --- /dev/null +++ b/courses/intro/tests/00_intro/intro2.lean @@ -0,0 +1,3 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard favoriteNumber > 0 + diff --git a/courses/intro/tests/00_intro/intro3.lean b/courses/intro/tests/00_intro/intro3.lean new file mode 100644 index 0000000..e1f3698 --- /dev/null +++ b/courses/intro/tests/00_intro/intro3.lean @@ -0,0 +1,5 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard isReady == true +#guard count > 0 +#guard message == "hello" + diff --git a/courses/intro/tests/01_basics/basics1.lean b/courses/intro/tests/01_basics/basics1.lean new file mode 100644 index 0000000..f1026b8 --- /dev/null +++ b/courses/intro/tests/01_basics/basics1.lean @@ -0,0 +1,4 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard product == 42 +#guard power == 1024 + diff --git a/courses/intro/tests/01_basics/basics2.lean b/courses/intro/tests/01_basics/basics2.lean new file mode 100644 index 0000000..d27d859 --- /dev/null +++ b/courses/intro/tests/01_basics/basics2.lean @@ -0,0 +1,3 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard myString.length > 0 + diff --git a/courses/intro/tests/01_basics/basics3.lean b/courses/intro/tests/01_basics/basics3.lean new file mode 100644 index 0000000..9a74fdc --- /dev/null +++ b/courses/intro/tests/01_basics/basics3.lean @@ -0,0 +1,4 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard hello == "Hello, Lean!" +#guard greeting == "Hello, Lean!" + diff --git a/courses/intro/tests/01_basics/basics4.lean b/courses/intro/tests/01_basics/basics4.lean new file mode 100644 index 0000000..0e19446 --- /dev/null +++ b/courses/intro/tests/01_basics/basics4.lean @@ -0,0 +1,5 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard and_result == true +#guard or_result == true +#guard not_result == true + diff --git a/courses/intro/tests/02_definitions/defs1.lean b/courses/intro/tests/02_definitions/defs1.lean new file mode 100644 index 0000000..9f063eb --- /dev/null +++ b/courses/intro/tests/02_definitions/defs1.lean @@ -0,0 +1,5 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard powerOfTwo == 1024 +#guard helloLength == 5 +#guard rangeLength == 5 + diff --git a/courses/intro/tests/02_definitions/defs2.lean b/courses/intro/tests/02_definitions/defs2.lean new file mode 100644 index 0000000..6cbb470 --- /dev/null +++ b/courses/intro/tests/02_definitions/defs2.lean @@ -0,0 +1,5 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard square 0 == 0 +#guard square 5 == 25 +#guard square 3 == 9 + diff --git a/courses/intro/tests/02_definitions/defs3.lean b/courses/intro/tests/02_definitions/defs3.lean new file mode 100644 index 0000000..329635a --- /dev/null +++ b/courses/intro/tests/02_definitions/defs3.lean @@ -0,0 +1,3 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard difference_of_squares == 91 + diff --git a/courses/intro/tests/03_functions/functions1.lean b/courses/intro/tests/03_functions/functions1.lean new file mode 100644 index 0000000..33f83fd --- /dev/null +++ b/courses/intro/tests/03_functions/functions1.lean @@ -0,0 +1,4 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard triple 0 == 0 +#guard triple 4 == 12 + diff --git a/courses/intro/tests/03_functions/functions2.lean b/courses/intro/tests/03_functions/functions2.lean new file mode 100644 index 0000000..7770686 --- /dev/null +++ b/courses/intro/tests/03_functions/functions2.lean @@ -0,0 +1,4 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard average 4 6 == 5 +#guard average 10 20 == 15 + diff --git a/courses/intro/tests/03_functions/functions3.lean b/courses/intro/tests/03_functions/functions3.lean new file mode 100644 index 0000000..cc2c411 --- /dev/null +++ b/courses/intro/tests/03_functions/functions3.lean @@ -0,0 +1,8 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard doubler 5 == 10 +#guard doubler 0 == 0 +#guard isZero 0 == true +#guard isZero 5 == false +#guard tripler 3 == 9 +#guard tripler 0 == 0 + diff --git a/courses/intro/tests/03_functions/functions4.lean b/courses/intro/tests/03_functions/functions4.lean new file mode 100644 index 0000000..9b335ea --- /dev/null +++ b/courses/intro/tests/03_functions/functions4.lean @@ -0,0 +1,5 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard doubled == [2, 4, 6] +#guard evens == [2, 4, 6] +#guard total == 60 + diff --git a/courses/intro/tests/04_control_flow/if1.lean b/courses/intro/tests/04_control_flow/if1.lean new file mode 100644 index 0000000..f0eb3f5 --- /dev/null +++ b/courses/intro/tests/04_control_flow/if1.lean @@ -0,0 +1,5 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard abs 5 == 5 +#guard abs (-3) == 3 +#guard abs 0 == 0 + diff --git a/courses/intro/tests/04_control_flow/if2.lean b/courses/intro/tests/04_control_flow/if2.lean new file mode 100644 index 0000000..bd2abe3 --- /dev/null +++ b/courses/intro/tests/04_control_flow/if2.lean @@ -0,0 +1,5 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard classify 5 == "positive" +#guard classify 0 == "zero" +#guard classify (-3) == "negative" + diff --git a/courses/intro/tests/04_control_flow/match1.lean b/courses/intro/tests/04_control_flow/match1.lean new file mode 100644 index 0000000..70c3073 --- /dev/null +++ b/courses/intro/tests/04_control_flow/match1.lean @@ -0,0 +1,4 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard getOrDefault (some 42) 0 == 42 +#guard getOrDefault none 99 == 99 + diff --git a/courses/intro/tests/05_structures/structs2.lean b/courses/intro/tests/05_structures/structs2.lean new file mode 100644 index 0000000..d2a48fd --- /dev/null +++ b/courses/intro/tests/05_structures/structs2.lean @@ -0,0 +1,6 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard fullName { firstName := "Jane", lastName := "Doe", age := 25 } == "Jane Doe" +#guard isAdult { firstName := "A", lastName := "B", age := 18 } == true +#guard isAdult { firstName := "A", lastName := "B", age := 17 } == false +#guard birthday { firstName := "A", lastName := "B", age := 25 } == { firstName := "A", lastName := "B", age := 26 } + diff --git a/courses/intro/tests/05_structures/structs3.lean b/courses/intro/tests/05_structures/structs3.lean new file mode 100644 index 0000000..87ef6b7 --- /dev/null +++ b/courses/intro/tests/05_structures/structs3.lean @@ -0,0 +1,6 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard pureRed.red == 255 +#guard pureRed.green == 0 +#guard white == { red := 255, green := 255, blue := 255 : RGBColor } +#guard black == ({} : RGBColor) + diff --git a/courses/intro/tests/06_inductive/inductive1.lean b/courses/intro/tests/06_inductive/inductive1.lean new file mode 100644 index 0000000..42e475d --- /dev/null +++ b/courses/intro/tests/06_inductive/inductive1.lean @@ -0,0 +1,4 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +open Direction in +#guard opposite north == south && opposite south == north && opposite east == west && opposite west == east + diff --git a/courses/intro/tests/06_inductive/inductive2.lean b/courses/intro/tests/06_inductive/inductive2.lean new file mode 100644 index 0000000..a7f4a26 --- /dev/null +++ b/courses/intro/tests/06_inductive/inductive2.lean @@ -0,0 +1,6 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard getValueOr (.ok 42) 0 == 42 +#guard getValueOr (.error "oops") 99 == 99 +#guard isOk (.ok 5) == true +#guard isOk (.error "nope") == false + diff --git a/courses/intro/tests/06_inductive/inductive3.lean b/courses/intro/tests/06_inductive/inductive3.lean new file mode 100644 index 0000000..19fd299 --- /dev/null +++ b/courses/intro/tests/06_inductive/inductive3.lean @@ -0,0 +1,4 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard isNum (.num 5) == true +#guard isNum (.add (.num 1) (.num 2)) == false + diff --git a/courses/intro/tests/07_recursion/recursion1.lean b/courses/intro/tests/07_recursion/recursion1.lean new file mode 100644 index 0000000..9601bdf --- /dev/null +++ b/courses/intro/tests/07_recursion/recursion1.lean @@ -0,0 +1,4 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard factorial 0 == 1 +#guard factorial 5 == 120 + diff --git a/courses/intro/tests/07_recursion/recursion2.lean b/courses/intro/tests/07_recursion/recursion2.lean new file mode 100644 index 0000000..58323a5 --- /dev/null +++ b/courses/intro/tests/07_recursion/recursion2.lean @@ -0,0 +1,4 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard sum [] == 0 +#guard sum [1, 2, 3] == 6 + diff --git a/courses/intro/tests/07_recursion/recursion3.lean b/courses/intro/tests/07_recursion/recursion3.lean new file mode 100644 index 0000000..879e278 --- /dev/null +++ b/courses/intro/tests/07_recursion/recursion3.lean @@ -0,0 +1,4 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard reverse [1, 2, 3] == [3, 2, 1] +#guard reverse ([] : List Nat) == [] + diff --git a/courses/intro/tests/07_recursion/recursion4.lean b/courses/intro/tests/07_recursion/recursion4.lean new file mode 100644 index 0000000..04daf24 --- /dev/null +++ b/courses/intro/tests/07_recursion/recursion4.lean @@ -0,0 +1,9 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard eval (.num 7) == 7 +#guard eval (.add (.num 2) (.num 3)) == 5 +#guard eval (.mul (.num 4) (.num 5)) == 20 +#guard eval (.add (.mul (.num 2) (.num 3)) (.num 1)) == 7 +#guard countNums (.num 5) == 1 +#guard countNums (.add (.num 1) (.num 2)) == 2 +#guard countNums (.mul (.add (.num 1) (.num 2)) (.num 3)) == 3 + diff --git a/courses/intro/tests/12_typeclasses/typeclasses2.lean b/courses/intro/tests/12_typeclasses/typeclasses2.lean new file mode 100644 index 0000000..cd431d5 --- /dev/null +++ b/courses/intro/tests/12_typeclasses/typeclasses2.lean @@ -0,0 +1,9 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard (Suit.hearts == Suit.hearts) == true +#guard (Suit.hearts == Suit.diamonds) == false +#guard (Suit.clubs == Suit.spades) == false +open Describable in +#guard (describe Suit.hearts).length > 0 +open Describable in +#guard (describe Suit.diamonds).length > 0 + diff --git a/courses/intro/tests/13_quiz/quiz1.lean b/courses/intro/tests/13_quiz/quiz1.lean new file mode 100644 index 0000000..918fe96 --- /dev/null +++ b/courses/intro/tests/13_quiz/quiz1.lean @@ -0,0 +1,49 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +-- ============================================= + +def catalog : List Book := [ + ⟨"Dune", "Herbert", 412, .science, .stars 5⟩, + ⟨"Sapiens", "Harari", 443, .history, .stars 4⟩, + { title := "Cosmos", author := "Sagan", pages := 365, genre := .science }, + ⟨"The Hobbit", "Tolkien", 310, .fantasy, .stars 5⟩, + ⟨"SPQR", "Beard", 606, .history, .stars 3⟩ +] + +#guard Genre.science == Genre.science +#guard Genre.history == Genre.history +#guard Genre.fantasy == Genre.fantasy +#guard Genre.science != Genre.history +#guard Genre.fantasy != Genre.science +#guard toString (Rating.stars 5) == "5 stars" +#guard toString (Rating.stars 2) == "2 stars" +#guard toString (Rating.stars 1) == "1 star" +#guard toString Rating.unrated == "unrated" +#guard Genre.name .science == "science" +#guard Genre.name .history == "history" +#guard Genre.name .fantasy == "fantasy" +#guard (Rating.stars 5).toNat == 5 +#guard (Rating.stars 0).toNat == 0 +#guard Rating.unrated.toNat == 0 +#guard (⟨"Big", "A", 500, .fantasy, .unrated⟩ : Book).isLong == true +#guard (⟨"Small", "B", 100, .fantasy, .unrated⟩ : Book).isLong == false +#guard (⟨"Edge", "C", 300, .fantasy, .unrated⟩ : Book).isLong == false +#guard ({ title := "A", author := "B", pages := 500, genre := .science } : Book).isWorthReading == true +#guard (⟨"A", "B", 100, .fantasy, .stars 5⟩ : Book).isWorthReading == true +#guard ({ title := "A", author := "B", pages := 100, genre := .fantasy } : Book).isWorthReading == false +#guard (⟨"A", "B", 500, .fantasy, .stars 2⟩ : Book).isWorthReading == false +#guard Book.describe ⟨"Test", "Author", 100, .fantasy, .unrated⟩ + == "Test by Author (100 pages, fantasy)" +#guard ((⟨"X", "Y", 100, .fantasy, .unrated⟩ : Book).withRating (.stars 4)).rating.toNat == 4 +#guard (findBook "Cosmos" catalog).isSome == true +#guard (findBook "Moby Dick" catalog).isSome == false +#guard (findBook "Dune" catalog).map (·.pages) == some 412 +#guard (booksOfGenre .science catalog).length == 2 +#guard (booksOfGenre .fantasy catalog).length == 1 +#guard titles catalog == ["Dune", "Sapiens", "Cosmos", "The Hobbit", "SPQR"] +#guard totalPages [] == 0 +#guard totalPages catalog == 2136 +#guard starBar (.stars 3) == "***" +#guard starBar (.stars 1) == "*" +#guard starBar (.stars 0) == "" +#guard starBar .unrated == "" + diff --git a/courses/intro/tests/14_do_notation/do1.lean b/courses/intro/tests/14_do_notation/do1.lean new file mode 100644 index 0000000..2447751 --- /dev/null +++ b/courses/intro/tests/14_do_notation/do1.lean @@ -0,0 +1,4 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard chainedDivide == some 5 +#guard failingDivide == none + diff --git a/courses/intro/tests/14_do_notation/do2.lean b/courses/intro/tests/14_do_notation/do2.lean new file mode 100644 index 0000000..f66f492 --- /dev/null +++ b/courses/intro/tests/14_do_notation/do2.lean @@ -0,0 +1,11 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +private instance : BEq (Except String Int) where + beq a b := match a, b with + | .ok x, .ok y => x == y + | .error x, .error y => x == y + | _, _ => false + +#guard validate 50 == .ok 50 +#guard validate (-1) == .error "must be positive" +#guard validate 200 == .error "must be less than 100" + diff --git a/courses/intro/tests/14_do_notation/do3.lean b/courses/intro/tests/14_do_notation/do3.lean new file mode 100644 index 0000000..d2c6dd0 --- /dev/null +++ b/courses/intro/tests/14_do_notation/do3.lean @@ -0,0 +1,7 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard countWhere (· > 3) [1, 2, 3, 4, 5] == 2 +#guard countWhere (· == 0) [0, 1, 0, 2] == 2 +#guard countWhere (· > 0) ([] : List Nat) == 0 +#guard listMax [1, 5, 3, 9, 2] == 9 +#guard listMax ([] : List Nat) == 0 + diff --git a/courses/intro/tests/16_implicit/implicit1.lean b/courses/intro/tests/16_implicit/implicit1.lean new file mode 100644 index 0000000..038e69a --- /dev/null +++ b/courses/intro/tests/16_implicit/implicit1.lean @@ -0,0 +1,5 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard myFst (1, "hello") == 1 +#guard mySwap (1, 2) == (2, 1) +#guard mapPair (· + 1) (· * 2) (3, 4) == (4, 8) + diff --git a/courses/intro/tests/16_implicit/implicit2.lean b/courses/intro/tests/16_implicit/implicit2.lean new file mode 100644 index 0000000..72063c2 --- /dev/null +++ b/courses/intro/tests/16_implicit/implicit2.lean @@ -0,0 +1,5 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard myContains 3 [1, 2, 3] == true +#guard myContains 4 [1, 2, 3] == false +#guard myContains 1 ([] : List Nat) == false + diff --git a/courses/intro/tests/17_arrays/arrays1.lean b/courses/intro/tests/17_arrays/arrays1.lean new file mode 100644 index 0000000..4da25bf --- /dev/null +++ b/courses/intro/tests/17_arrays/arrays1.lean @@ -0,0 +1,7 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard doubleArray #[1, 2, 3] == #[2, 4, 6] +#guard doubleArray #[] == #[] +#guard arraySum #[1, 2, 3, 4] == 10 +#guard arraySum #[] == 0 +#guard filterAbove #[1, 5, 3, 8, 2] 3 == #[5, 8] + diff --git a/courses/intro/tests/17_arrays/arrays2.lean b/courses/intro/tests/17_arrays/arrays2.lean new file mode 100644 index 0000000..346cb04 --- /dev/null +++ b/courses/intro/tests/17_arrays/arrays2.lean @@ -0,0 +1,5 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard squares == #[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] +#guard myReverse #[1, 2, 3] == #[3, 2, 1] +#guard myReverse #[] == #[] + diff --git a/courses/intro/tests/18_namespaces/ns1.lean b/courses/intro/tests/18_namespaces/ns1.lean new file mode 100644 index 0000000..4610744 --- /dev/null +++ b/courses/intro/tests/18_namespaces/ns1.lean @@ -0,0 +1,5 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard MyMath.square 5 == 25 +#guard MyMath.cube 3 == 27 +#guard sumOfPowers 2 == 12 + diff --git a/courses/intro/tests/18_namespaces/ns2.lean b/courses/intro/tests/18_namespaces/ns2.lean new file mode 100644 index 0000000..063a03e --- /dev/null +++ b/courses/intro/tests/18_namespaces/ns2.lean @@ -0,0 +1,7 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard myElem 3 [1, 2, 3] == true +#guard myElem 4 [1, 2, 3] == false +#guard myElem 1 ([] : List Nat) == false +#guard myRemoveAll 2 [1, 2, 3, 2, 4] == [1, 3, 4] +#guard myRemoveAll 5 [1, 2, 3] == [1, 2, 3] + diff --git a/courses/intro/tests/19_quiz2/quiz2.lean b/courses/intro/tests/19_quiz2/quiz2.lean new file mode 100644 index 0000000..93e9401 --- /dev/null +++ b/courses/intro/tests/19_quiz2/quiz2.lean @@ -0,0 +1,11 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +#guard safeDivTwice 100 5 4 == some 5 +#guard safeDivTwice 100 0 4 == none +#guard safeDivTwice 100 5 0 == none +#guard sumOfSquares 0 == 0 +#guard sumOfSquares 1 == 1 +#guard sumOfSquares 3 == 14 +#guard countOccurrences 3 [1, 3, 2, 3, 3] == 3 +#guard countOccurrences "a" ["b", "a", "c"] == 1 +#guard countOccurrences 7 ([] : List Nat) == 0 + diff --git a/courses/intro/tests/26_final_quiz/quiz3.lean b/courses/intro/tests/26_final_quiz/quiz3.lean new file mode 100644 index 0000000..d703d53 --- /dev/null +++ b/courses/intro/tests/26_final_quiz/quiz3.lean @@ -0,0 +1,12 @@ +-- Hidden correctness checks for this exercise (not shown to the learner). +-- ============================================= +#guard sampleTree.size == 3 +#guard (Tree.leaf : Tree Nat).size == 0 +#guard sampleTree.toList == [1, 2, 3] +#guard sampleTree.mirror.size == 3 +#guard sampleTree.mirror.toList == [3, 2, 1] +#guard sampleTree.mirror.mirror == sampleTree +#guard sampleTree.depth == 2 +#guard (Tree.leaf : Tree Nat).depth == 0 +#guard sampleTree.toList.length == sampleTree.size +