diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 2b1788677..000000000
--- a/.editorconfig
+++ /dev/null
@@ -1,10 +0,0 @@
-root = true
-
-[*]
-indent_style = space
-indent_size = 4
-charset = utf-8
-trim_trailing_whitespace = true
-insert_final_newline = true
-end_of_line = lf
-
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ed8f4a432..234b07e76 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,4 +6,4 @@ updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
- interval: 'daily'
+ interval: 'monthly'
diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml
new file mode 100644
index 000000000..812e91296
--- /dev/null
+++ b/.github/workflows/no-important-files-changed.yml
@@ -0,0 +1,23 @@
+name: No important files changed
+
+on:
+ pull_request_target:
+ types: [opened]
+ branches: [main]
+ paths:
+ - "exercises/concept/**"
+ - "exercises/practice/**"
+ - "!exercises/*/*/.approaches/**"
+ - "!exercises/*/*/.articles/**"
+ - "!exercises/*/*/.docs/**"
+ - "!exercises/*/*/.meta/**"
+
+permissions:
+ pull-requests: write
+
+jobs:
+ check:
+ uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main
+ with:
+ repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }}
+ ref: ${{ github.head_ref }}
diff --git a/.github/workflows/ping-cross-track-maintainers-team.yml b/.github/workflows/ping-cross-track-maintainers-team.yml
new file mode 100644
index 000000000..b6ec9c566
--- /dev/null
+++ b/.github/workflows/ping-cross-track-maintainers-team.yml
@@ -0,0 +1,16 @@
+name: Ping cross-track maintainers team
+
+on:
+ pull_request_target:
+ types:
+ - opened
+
+permissions:
+ pull-requests: write
+
+jobs:
+ ping:
+ if: github.repository_owner == 'exercism' # Stops this job from running on forks
+ uses: exercism/github-actions/.github/workflows/ping-cross-track-maintainers-team.yml@main
+ secrets:
+ github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }}
diff --git a/.github/workflows/run-configlet-sync.yml b/.github/workflows/run-configlet-sync.yml
new file mode 100644
index 000000000..b49cbffe8
--- /dev/null
+++ b/.github/workflows/run-configlet-sync.yml
@@ -0,0 +1,10 @@
+name: Run Configlet Sync
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 0 15 * *'
+
+jobs:
+ call-gha-workflow:
+ uses: exercism/github-actions/.github/workflows/configlet-sync.yml@main
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 91db73990..f916c0158 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -4,7 +4,7 @@ name: CI
on:
push:
branches:
- - main
+ - main
pull_request:
workflow_dispatch:
schedule:
@@ -12,50 +12,13 @@ on:
- cron: "0 0 * * 0"
jobs:
- ensure-conventions:
- name: Ensure conventions are followed
- runs-on: ubuntu-latest
-
- steps:
- # Checks out a copy of your repository on the ubuntu-latest machine
- - name: Checkout code
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
-
- - name: Ensure tool names are snake cased
- run: ./bin/lint_tool_file_names.sh
-
- - name: Ensure src/lib.rs files exist
- run: ./_test/ensure_lib_src_rs_exist.sh
-
- - name: Count ignores
- run: ./_test/count_ignores.sh
-
- - name: Check UUIDs
- run: ./_test/check_uuids.sh
-
- - name: Verify exercise difficulties
- run: ./_test/verify_exercise_difficulties.sh
-
- - name: Check exercises for authors
- run: ./_test/check_exercises_for_authors.sh
-
- - name: Ensure relevant files do not have trailing whitespace
- run: ./bin/lint_trailing_spaces.sh
-
configlet:
name: configlet lint
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- # Checks out default branch locally so that it is available to the scripts.
- - name: Checkout main
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- with:
- ref: main
-
- # Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Fetch configlet
run: ./bin/fetch-configlet
@@ -65,98 +28,94 @@ jobs:
markdownlint:
name: markdown lint
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- - name: Checkout main
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- with:
- ref: main
- name: Checkout code
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Run markdown lint
run: ./bin/lint_markdown.sh
# stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml
shellcheck:
- name: shellcheck internal tooling lint
- runs-on: ubuntu-latest
+ name: Run shellcheck on scripts
+ runs-on: ubuntu-24.04
steps:
- name: Checkout
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Run shellcheck
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0
compilation:
name: Check compilation
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
strategy:
- # Allows running the job multiple times with different configurations
matrix:
rust: ["stable", "beta"]
- deny_warnings: ['', '1']
steps:
- # Checks out main locally so that it is available to the scripts.
- - name: Checkout main
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- with:
- ref: main
-
- # Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+
+ - name: Fetch origin/main
+ run: git fetch --depth=1 origin main
- name: Setup toolchain
- uses: actions-rs/toolchain@v1.0.7
+ uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
with:
toolchain: ${{ matrix.rust }}
- default: true
- # run scripts as steps
- name: Check exercises
env:
- DENYWARNINGS: ${{ matrix.deny_warnings }}
- run: ./_test/check_exercises.sh
- continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }}
+ DENYWARNINGS: "1"
+ run: ./bin/check_exercises.sh
- - name: Cargo clean (to prevent previous compilation from unintentionally interfering with later ones)
- run: ./_test/cargo_clean_all.sh
+ tests:
+ name: Run repository tests
+ runs-on: ubuntu-24.04
- - name: Ensure stubs compile
- env:
- DENYWARNINGS: ${{ matrix.deny_warnings }}
- run: ./_test/ensure_stubs_compile.sh
- continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- - name: Check exercise crate
- env:
- DENYWARNINGS: ${{ matrix.deny_warnings }}
- run: ./_test/check_exercise_crate.sh
- continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }}
+ - name: Setup toolchain
+ uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
+ with:
+ toolchain: stable
+ components: clippy
+
+ - name: Setup compilation cache
+ uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
+ with:
+ workspaces: "rust-tooling -> rust-tooling/target"
+
+ - name: Fetch configlet
+ run: ./bin/fetch-configlet
+
+ - name: Checkout problem-specifications
+ run: ./bin/symlink_problem_specifications.sh
+
+ - name: Run tests
+ run: cd rust-tooling && cargo test
rustformat:
name: Check Rust Formatting
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- name: Checkout code
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup toolchain
- uses: actions-rs/toolchain@v1.0.7
+ uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
with:
toolchain: stable
- default: true
-
- - name: Rust Format Version
- run: rustfmt --version
- name: Format
- run: bin/format_exercises
+ run: ./bin/format_exercises.sh
- name: Diff
run: |
@@ -167,65 +126,38 @@ jobs:
clippy:
name: Clippy
- runs-on: ubuntu-latest
-
- strategy:
- matrix:
- rust: ["stable", "beta"]
+ runs-on: ubuntu-24.04
steps:
- - name: Checkout main
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- with:
- ref: main
-
- name: Checkout code
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup toolchain
- uses: actions-rs/toolchain@v1.0.7
+ uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
with:
- toolchain: ${{ matrix.rust }}
- default: true
-
- # Clippy already installed on Stable, but not Beta.
- # So, we must install here.
- - name: Install Clippy
- run: rustup component add clippy
+ toolchain: stable
+ components: clippy
- name: Clippy tests
env:
CLIPPY: true
- run: ./_test/check_exercises.sh
-
- - name: Clippy stubs
- env:
- CLIPPY: true
- run: ./_test/ensure_stubs_compile.sh
+ run: ./bin/check_exercises.sh
nightly-compilation:
name: Check exercises on nightly (benchmark enabled)
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
continue-on-error: true # It's okay if the nightly job fails
steps:
- # Checks out main locally so that it is available to the scripts.
- - name: Checkout main
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- with:
- ref: main
-
- # Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup nightly toolchain
- uses: actions-rs/toolchain@v1.0.7
+ uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
with:
toolchain: nightly
- default: true
- name: Check exercises
env:
- BENCHMARK: '1'
- run: ./_test/check_exercises.sh
+ BENCHMARK: "1"
+ run: ./bin/check_exercises.sh
diff --git a/.gitignore b/.gitignore
index 6723c90a8..3eea4c1c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,9 +3,9 @@
.DS_Store
**/target
tmp
-bin/configlet
-bin/configlet.exe
-bin/exercise
-bin/exercise.exe
+/bin/configlet
exercises/*/*/Cargo.lock
-exercises/*/*/clippy.log
+clippy.log
+.vscode
+.prob-spec
+problem-specifications
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..e69de29bb
diff --git a/.ignore b/.ignore
new file mode 100644
index 000000000..6a275d68e
--- /dev/null
+++ b/.ignore
@@ -0,0 +1 @@
+!problem-specifications
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index df8e36761..3f7813de1 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -90,4 +90,4 @@ This policy was initially adopted from the Front-end London Slack community and
A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md).
_This policy is a "living" document, and subject to refinement and expansion in the future.
-This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Slack, Twitter, email) and any other Exercism entity or event._
+This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Discord, Forum, Twitter, email) and any other Exercism entity or event._
diff --git a/README.md b/README.md
index 31a9c76eb..6487c01f5 100644
--- a/README.md
+++ b/README.md
@@ -1,106 +1,87 @@
-# Exercism Rust Track
+
-[](https://github.com/exercism/rust/actions?query=workflow%3ACI+branch%3Amain)
+
+
Exercism Rust Track
-Exercism exercises in Rust
+ [](https://forum.exercism.org)
+ [](https://exercism.org/blog/freeing-our-maintainers)
+ [](https://github.com/exercism/rust/actions?query=workflow%3ACI+branch%3Amain)
-## Contributing
+
-Check out our [contributor documentation](docs/CONTRIBUTING.md).
+Hi. 👋🏽 👋 **We are happy you are here.** 🎉 🌟
-## Exercise Tests
+
-At the most basic level, Exercism is all about the tests. You can read more about how we think about test suites in [the Exercism documentation](https://github.com/exercism/legacy-docs/blob/main/language-tracks/exercises/anatomy/test-suites.md).
+**`exercism/rust`** is one of many programming language tracks on [exercism(dot)org][exercism-website].
+This repo holds all the instructions, tests, code, & support files for Rust _exercises_ currently under development or implemented & available for students.
-Test files should use the following format:
+Some Exercism language tracks have a **syllabus** which is meant to teach the language step-by-step.
+The Rust track's syllabus is a work in progress and it's not activated yet.
+All exercises presented to students are **practice exercises**.
+Students are exepcted to learn the language themselves, for example with the [official book][the-rust-programming-language], and practice with our exercises.
-```
-extern crate exercise_name;
+
-use exercise_name::*;
+
+
+
+
+
-#[test]
-fn test_descriptive_name() {
- assert_eq!(exercise_function(1), 1);
-}
+🌟🌟 Please take a moment to read our [Code of Conduct][exercism-code-of-conduct] 🌟🌟
+It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member] & [The words that we use][the-words-that-we-use].
+Some defined roles in our community: [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers] **|** [Admins][exercism-admins]
-#[test]
-#[ignore]
-fn test_second_and_past_tests_ignored() {
- assert_ne!(exercise_function(1), 2);
-}
-```
+
-## Opening an Issue
+
+
-If you plan to make significant or breaking changes, please open an issue so we can discuss it first. If this is a discussion that is relevant to more than just the Rust track, please open an issue in [exercism/discussions](https://github.com/exercism/discussions/issues).
+We 💛 💙 our community.
+**`But our maintainers are not accepting community contributions at this time.`**
+Please read this [community blog post][freeing-maintainers] for details.
-## Submitting a Pull Request
+
+
-Pull requests should be focused on a single exercise, issue, or conceptually cohesive change. Please refer to Exercism's [pull request guidelines](https://github.com/exercism/legacy-docs/blob/main/contributing/pull-request-guidelines.md).
+Here to suggest a new feature or new exercise?? **Hooray!** 🎉
+We'd love if you did that via our [Exercism Community Forum](https://forum.exercism.org/).
+Please read [Suggesting Exercise Improvements][suggesting-improvements] & [Chesterton's Fence][chestertons-fence].
+_Thoughtful suggestions will likely result faster & more enthusiastic responses from volunteers._
-Please follow the coding standards for Rust. [rustfmt](https://github.com/nrc/rustfmt) may help with this
-and can be installed with `cargo install rustfmt`.
+
+
-### Verifying your Change
+✨ 🦄 _**Want to jump directly into Exercism specifications & detail?**_
+ [Structure][exercism-track-structure] **|** [Tasks][exercism-tasks] **|** [Concepts][exercism-concepts] **|** [Concept Exercises][concept-exercises] **|** [Practice Exercises][practice-exercises] **|** [Presentation][exercise-presentation]
+ [Writing Style Guide][exercism-writing-style] **|** [Markdown Specification][exercism-markdown-specification] (_✨ version in [contributing][website-contributing-section] on exercism.org_)
-Before submitting your pull request, you'll want to verify the changes in two ways:
+
+
-* Run all the tests for the Rust exercises
-* Run an Exercism-specific linter to verify the track
+## Exercism Rust Track License
-All the tests for Rust exercises can be run from the top level of the repo with `_test/check_exercises.sh`. If you are on a Windows machine, there are additional [Windows-specific instructions](_test/WINDOWS_README.md) for running this.
+This repository uses the [MIT License](/LICENSE).
-### On modifying the exercises' README
-
-Please note that the README of every exercise is formed using several templates, not all of which are necessarily present on this repo. The most important of these:
-
-- The `description.md` file in the exercise directory from the [problem-specifications repository](https://github.com/exercism/problem-specifications/tree/main/exercises)
-
-- The `.meta/hints.md` file in the exercise directory on this repository
-
-- The [Rust-specific instructions](https://github.com/exercism/rust/blob/main/exercises/shared/.docs/tests.md)
-
-If you are modifying the section of the README that belongs to the template not from this repository, please consider [opening a PR](https://github.com/exercism/problem-specifications/pulls) on the `problem-specifications` repository first.
-
-## Contributing a New Exercise
-
-Please see the documentation about [adding new exercises](https://github.com/exercism/legacy-docs/blob/main/you-can-help/make-up-new-exercises.md).
-
-Note that:
-
-- The simplest way to generate, update or configure an exercise is to use the [exercise](https://github.com/exercism/rust/tree/main/util/exercise) utility provided in this repository. To compile the utility you can use the [bin/build_exercise_crate.sh](https://github.com/exercism/rust/tree/main/bin/build_exercise_crate.sh) script or, if the script does not work for you, use the `cargo build --release` command in the `util/exercise/` directory and then copy the `exercise` binary from the `util/exercise/target/release/` directory into the `bin/` directory. Use `bin/exercise --help` to learn about the existing commands and their possible usage.
-
-- Each exercise must stand on its own. Do not reference files outside the exercise directory. They will not be included when the user fetches the exercise.
-
-- Exercises must conform to the Exercism-wide standards described in [the documentation](https://github.com/exercism/legacy-docs/tree/main/language-tracks/exercises).
-
-- Each exercise should have:
-
- exercises/exercise-name/
- tests/exercise-name.rs <- a test suite
- src/lib.rs <- an empty file or with exercise stubs
- example.rs <- example solution that satisfies tests
- Cargo.toml <- with version equal to exercise definition
- Cargo.lock <- Auto generated
- README.md <- Instructions for the exercise (see notes below)
-
-- The stub file and test suite should use only the Rust core libraries. `Cargo.toml` should not list any external dependencies as we don't want to make the student assume required crates. If an `example.rs` uses external crates, include `Cargo-example.toml` so that `_tests/check_exercises.sh` can compile with these when testing.
-
-- Except in extraordinary circumstances, the stub file should compile under `cargo test --no-run`.
- This allows us to check that the signatures in the stub file match the signatures expected by the tests.
- Use `unimplemented!()` as the body of each function to achieve this.
- If there is a justified reason why this is not possible, instead include a `.custom."allowed-to-not-compile"` key in the exercise's `.meta/config.json` containing the reason.
-
-- If porting an existing exercise from problem-specifications that has a `canonical-data.json` file, use the version in `canonical-data.json` for that exercise as your `Cargo.toml` version. Otherwise, use "0.0.0".
-
-- An exercise may contain `.meta/hints.md`. This is optional and will appear after the normal exercise
- instructions if present. Rust is different in many ways from other languages. This is a place where the differences required for Rust are explained. If it is a large change, you may want to call this out as a comment at the top of `src/lib.rs`, so the user recognizes to read this section before starting.
-
-- If the test suite is appreciably sped up by running in release mode, and there is reason to be confident that the test suite appropriately detects any overflow errors, consider adding a marker to the exercise's `.meta/config.json`: `.custom."test-in-release-mode"` should be `true`. This can particularly impact the online editor experience.
-
-- If your exercise implements macro-based testing (see [#392](https://github.com/exercism/rust/issues/392#issuecomment-343865993) and [`perfect-numbers.rs`](https://github.com/exercism/rust/blob/main/exercises/practice/perfect-numbers/tests/perfect-numbers.rs)), you will likely run afoul of a CI check which counts the `#[ignore]` lines and compares the result to the number of `#[test]` lines. To fix this, add a marker to the exercise's `.meta/config.json`: `.custom."ignore-count-ignores"` should be `true` to disable that check for your exercise.
-
-- `README.md` may be [regenerated](https://github.com/exercism/legacy-docs/blob/main/maintaining-a-track/regenerating-exercise-readmes.md) from Exercism data. The generator will use the `description.md` from the exercise directory in the [problem-specifications repository](https://github.com/exercism/problem-specifications/tree/main/exercises), then any hints in `.meta/hints.md`, then the [Rust-specific instructions](https://github.com/exercism/rust/blob/main/exercises/shared/.docs/tests.md). The `## Source` section comes from the `metadata.yml` in the same directory. Convention is that the description of the source remains text and the link is both name and hyperlink of the markdown link.
-
-- Be sure to add the exercise to an appropriate place in the `config.json` file. The position in the file determines the order exercises are sent. Generate a unique UUID for the exercise. Current difficulty levels in use are 1, 4, 7 and 10.
+[being-a-good-community-member]: https://github.com/exercism/docs/tree/main/community/good-member
+[chestertons-fence]: https://github.com/exercism/docs/blob/main/community/good-member/chestertons-fence.md
+[concept-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md
+[exercise-presentation]: https://github.com/exercism/docs/blob/main/building/tracks/presentation.md
+[exercism-admins]: https://github.com/exercism/docs/blob/main/community/administrators.md
+[exercism-code-of-conduct]: https://exercism.org/docs/using/legal/code-of-conduct
+[exercism-concepts]: https://github.com/exercism/docs/blob/main/building/tracks/concepts.md
+[exercism-contributors]: https://github.com/exercism/docs/blob/main/community/contributors.md
+[exercism-markdown-specification]: https://github.com/exercism/docs/blob/main/building/markdown/markdown.md
+[exercism-mentors]: https://github.com/exercism/docs/tree/main/mentoring
+[exercism-tasks]: https://exercism.org/docs/building/product/tasks
+[exercism-track-maintainers]: https://github.com/exercism/docs/blob/main/community/maintainers.md
+[exercism-track-structure]: https://github.com/exercism/docs/tree/main/building/tracks
+[exercism-website]: https://exercism.org/
+[exercism-writing-style]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md
+[freeing-maintainers]: https://exercism.org/blog/freeing-our-maintainers
+[practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md
+[suggesting-improvements]: https://github.com/exercism/docs/blob/main/community/good-member/suggesting-exercise-improvements.md
+[the-words-that-we-use]: https://github.com/exercism/docs/blob/main/community/good-member/words.md
+[website-contributing-section]: https://exercism.org/docs/building
+[the-rust-programming-language]: https://doc.rust-lang.org/book/
diff --git a/_test/WINDOWS_README.md b/_test/WINDOWS_README.md
deleted file mode 100644
index 79555a8b5..000000000
--- a/_test/WINDOWS_README.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# check_exercises.sh for Windows Rust Developers
-
-It is possible to run `check_exercises.sh` on Windows 10, pointing to the Windows location for your GitHub repository. This is done with the Ubuntu on Windows subsystem.
-
-## Enable Developer Mode
-To run Ubuntu on Windows, you need to be in Developer Mode.
-
- - Open Settings
- - Open Update and Security
- - Select For Developers on Left Side
- - Change to Developer Mode from Sideload Apps
-
-## Install
-
-Start a PowerShell as Administrator.
-
-Run the following:
-
- Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
-
-## Run bash
-
-The `bash` command now gives you a terminal in a Ubuntu Linux instance. You have access to Windows files via /mnt/[drive_letter]
-
-Example: Windows user directory would be
-
- /mnt/c/Users/username
-
-## Installing Rust
-
-Inside bash, you will not have access to Window's Rust. You need to install the Linux version of Rust.
-
- curl -sf -L https://static.rust-lang.org/rustup.sh | sh
-
-You also need to install a cc linker for Rust.
-
- sudo apt-get install build-essential
-
-## Running Tests
-
- cd /mnt/c/[path of github project]
- _test/check_exercises.sh
-
-This will re-download and build any crates needed, as they only existed in your Windows Rust.
diff --git a/_test/cargo_clean_all.sh b/_test/cargo_clean_all.sh
deleted file mode 100755
index c50bb91c2..000000000
--- a/_test/cargo_clean_all.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-
-status=0
-repo="$(cd "$(dirname "$0")/.." && pwd)"
-
-for ex in "$repo"/exercises/*/*/; do
- name=$(grep '^name =' "$ex/Cargo.toml" | cut -d\" -f2)
- if [ -z "$name" ]; then
- echo "don't know name of $ex"
- status=1
- continue
- fi
- cargo clean --manifest-path "$ex/Cargo.toml" --package "$name"
-done
-
-exit $status
diff --git a/_test/check_exercise_crate.sh b/_test/check_exercise_crate.sh
deleted file mode 100755
index 3870db865..000000000
--- a/_test/check_exercise_crate.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env bash
-set -eo pipefail
-
-# A script to ensure that the util/exercise crate builds after it was modified.
-
-EXERCISE_CRATE_PATH="util/exercise"
-
-if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
- # Check the changes on the current branch against main branch
- if ! git diff --name-only remotes/origin/main | grep -q "$EXERCISE_CRATE_PATH"; then
- echo "exercise crate was not modified. The script is aborted."
- exit 0
- fi
-fi
-# If it's not a pull request, just always run it.
-# Two scenarios:
-# 1. It's being run locally,
-# in which case we assume the person running it really does want to run it.
-# 2. It's being run on CI for main,
-# in which case we should check regardless of changes to exercise crate,
-# in case there's a new toolchain release, etc.
-
-
-TRACK_ROOT="$(git rev-parse --show-toplevel)"
-
-if ! (cd "$TRACK_ROOT/$EXERCISE_CRATE_PATH" && cargo check); then
- echo
- echo "An error has occurred while building the exercise crate."
- echo "Please make it compile."
-
- exit 1
-else
- echo
- echo "exercise crate has been successfully built."
-
- exit 0
-fi
diff --git a/_test/check_exercises.sh b/_test/check_exercises.sh
deleted file mode 100755
index 5ec830c38..000000000
--- a/_test/check_exercises.sh
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/usr/bin/env bash
-
-# test for existence and executability of the test-exercise script
-# this depends on that
-if [ ! -f "./bin/test-exercise" ]; then
- echo "bin/test-exercise does not exist"
- exit 1
-fi
-if [ ! -x "./bin/test-exercise" ]; then
- echo "bin/test-exercise does not have its executable bit set"
- exit 1
-fi
-
-# In DENYWARNINGS or CLIPPY mode, do not set -e so that we run all tests.
-# This allows us to see all warnings.
-# If we are in neither DENYWARNINGS nor CLIPPY mode, do set -e.
-if [ -z "$DENYWARNINGS" ] && [ -z "$CLIPPY" ]; then
- set -e
-fi
-
-# can't benchmark with a stable compiler; to bench, use
-# $ BENCHMARK=1 rustup run nightly _test/check_exercises.sh
-if [ -n "$BENCHMARK" ]; then
- target_dir=benches
-else
- target_dir=tests
-fi
-
-repo=$(cd "$(dirname "$0")/.." && pwd)
-
-if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
- files="$(
- git diff --diff-filter=d --name-only remotes/origin/main |
- grep "exercises/" |
- cut -d '/' -f -3 |
- sort -u |
- awk -v repo="$repo" '{print repo"/"$1}'
- )"
-else
- files="$repo/exercises/*/*"
-fi
-
-return_code=0
-# An exercise worth testing is defined here as any top level directory with
-# a 'tests' directory and a .meta/config.json.
-for exercise in $files; do
- exercise="$exercise/$target_dir"
-
- # This assumes that exercises are only one directory deep
- # and that the primary module is named the same as the directory
- directory=$(dirname "${exercise}")
-
- # An exercise must have a .meta/config.json
- metaconf="$directory/.meta/config.json"
- if [ ! -f "$metaconf" ]; then
- continue
- fi
-
- release=""
- if [ -z "$BENCHMARK" ] && jq --exit-status '.custom?."test-in-release-mode"?' "$metaconf"; then
- # release mode is enabled if not benchmarking and the appropriate key is neither `false` nor `null`.
- release="--release"
- fi
-
- if [ -n "$DENYWARNINGS" ]; then
- # Output a progress dot, because we may not otherwise produce output in > 10 mins.
- echo -n '.'
- # No-run mode so we see no test output.
- # Quiet mode so we see no compile output
- # (such as "Compiling"/"Downloading").
- # Compiler errors will still be shown though.
- # Both flags are necessary to keep things quiet.
- ./bin/test-exercise "$directory" --quiet --no-run
- return_code=$((return_code | $?))
- else
- # Run the test and get the status
- ./bin/test-exercise "$directory" $release
- return_code=$((return_code | $?))
- fi
-done
-
-exit $return_code
diff --git a/_test/check_exercises_for_authors.sh b/_test/check_exercises_for_authors.sh
deleted file mode 100755
index d3c3fb10a..000000000
--- a/_test/check_exercises_for_authors.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env bash
-
-repo=$(cd "$(dirname "$0")/.." && pwd)
-
-if grep -rnw "$repo/exercises/" --include="*.toml" -e "authors"; then
- echo "Found 'authors' field in exercises";
- exit 1;
-fi
diff --git a/_test/check_uuids.sh b/_test/check_uuids.sh
deleted file mode 100755
index ae2ae60c1..000000000
--- a/_test/check_uuids.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env bash
-set -eo pipefail
-
-repo=$(cd "$(dirname "$0")/.." && pwd)
-
-# Check for invalid UUIDs.
-# can be removed once `configlet lint` gains this ability.
-# Check issue https://github.com/exercism/configlet/issues/99
-
-bad_uuid=$(jq --raw-output '.exercises | .concept[], .practice[] | .uuid' "$repo"/config.json | grep -vE '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' || test 1)
-if [ -n "$bad_uuid" ]; then
- echo "invalid UUIDs found! please correct these to be valid UUIDs:"
- echo "$bad_uuid"
- exit 1
-fi
diff --git a/_test/count_ignores.sh b/_test/count_ignores.sh
deleted file mode 100755
index 22bbba105..000000000
--- a/_test/count_ignores.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env bash
-
-repo=$(cd "$(dirname "$0")/.." && pwd)
-exitcode=0
-
-for e in "$repo"/exercises/*/*; do
- # An exercise must have a .meta/config.json
- metaconf="$e/.meta/config.json"
- if [ ! -f "$metaconf" ]; then
- continue
- fi
-
- if jq --exit-status '.custom?."ignore-count-ignores"?' "$metaconf"; then
- continue
- fi
- if [ -d "$e/tests" ]; then
- total_tests=0
- total_ignores=0
- for t in "$e"/tests/*.rs; do
- tests=$(grep -c "\#\[test\]" "$t" | tr -d '[:space:]')
- ignores=$(grep -c "\#\[ignore\]" "$t" | tr -d '[:space:]')
-
- total_tests=$((total_tests + tests))
- total_ignores=$((total_ignores + ignores))
- done
- want_ignores=$((total_tests - 1))
- if [ "$total_ignores" != "$want_ignores" ]; then
- # ShellCheck wants us to use printf,
- # but there are no other uses of printf in this repo,
- # so printf hasn't been tested to work yet.
- # (We would not be opposed to using printf and removing this disable;
- # we just haven't tested it to confirm it works yet).
- # shellcheck disable=SC2028
- echo "\033[1;31m$e: Has $total_tests tests and $total_ignores ignores (should be $want_ignores)\033[0m"
- exitcode=1
- fi
- fi
-done
-
-exit $exitcode
diff --git a/_test/ensure_lib_src_rs_exist.sh b/_test/ensure_lib_src_rs_exist.sh
deleted file mode 100755
index 0c0205577..000000000
--- a/_test/ensure_lib_src_rs_exist.sh
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env bash
-
-repo=$(cd "$(dirname "$0")/.." && pwd)
-
-missing=""
-
-empty_stub=""
-
-check_status=0
-
-IGNORED_EXERCISES=(
- "two-fer" #deprecated
- "nucleotide-codons" #deprecated
- "hexadecimal" #deprecated
-)
-
-for dir in "$repo"/exercises/*/*/; do
- exercise=$(basename "$dir")
-
- if [ ! -f "$dir/src/lib.rs" ]; then
- echo "$exercise is missing a src/lib.rs stub file. Please create the missing file with the template, that is necessary for the exercise, present in it."
- missing="$missing\n$exercise"
- else
- #Check if the stub file is empty
- if [ ! -s "$dir/src/lib.rs" ] || [ "$(cat "$dir/src/lib.rs")" == "" ] && [[ " ${IGNORED_EXERCISES[*]} " != *"$exercise"* ]]; then
- echo "$exercise has src/lib.rs stub file, but it is empty."
- empty_stub="$empty_stub\n$exercise"
- fi
- fi
-done
-
-if [ -n "$missing" ]; then
- # extra echo to generate a new line
- echo
- echo "Exercises missing src/lib.rs:$missing"
-
- check_status=1
-fi
-
-if [ -n "$empty_stub" ]; then
- echo
- echo "Exercises with empty src/lib.rs stub file:$empty_stub"
-
- check_status=1
-fi
-
-if [ "$check_status" -ne 0 ]; then
- exit 1
-else
- exit 0
-fi
diff --git a/_test/ensure_stubs_compile.sh b/_test/ensure_stubs_compile.sh
deleted file mode 100755
index 6f154ae31..000000000
--- a/_test/ensure_stubs_compile.sh
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/usr/bin/env bash
-
-repo="$(cd "$(dirname "$0")/.." && pwd)"
-
-if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
- changed_exercises="$(
- git diff --diff-filter=d --name-only remotes/origin/main |
- grep "exercises/" |
- cut -d '/' -f -3 |
- sort -u |
- awk -v repo="$repo" '{print repo"/"$1}'
- )"
-else
- # we want globbing and it does not actually assign locally
- # shellcheck disable=SC2125
- changed_exercises="$repo"/exercises/*/*
-fi
-
-if [ -z "$changed_exercises" ]; then
- echo "No exercise was modified. The script is aborted."
-
- exit 0
-fi
-
-broken=""
-
-for dir in $changed_exercises; do
- exercise=$(basename "$dir")
-
- config_file="$dir/.meta/config.json"
-
- # An exercise must have a .meta/config.json
- if [ ! -f "$config_file" ]; then
- continue
- fi
-
- if jq --exit-status '.custom?."allowed-to-not-compile"?' "$config_file"; then
- echo "$exercise's stub is allowed to not compile"
- continue
- fi
-
- # Backup tests and stub; this script will modify them.
- cp -r "$dir/tests" "$dir/tests.orig"
- cp "$dir/src/lib.rs" "$dir/lib.rs.orig"
-
- # In CI, we may have already compiled using the example solution.
- # So we want to touch the src/lib.rs in some way,
- # so that we surely recompile using the stub.
- # Since we also want to deny warnings, that will also serve as the touch.
- sed -i -e '1i #![deny(warnings)]' "$dir/src/lib.rs"
-
- # Deny warnings in the tests that may result from compiling the stubs.
- # This helps avoid, for example, an overflowing literal warning
- # that could be caused by a stub with a type that is too small.
- sed -i -e '1i #![deny(warnings)]' "$dir"/tests/*.rs
-
- if [ -n "$CLIPPY" ]; then
- echo "clippy $exercise..."
- # We don't find it useful in general to have students implement Default,
- # since we're generally not going to test it.
- if ! (
- cd "$dir" &&
- cargo clippy --lib --tests --color always -- --allow clippy::new_without_default 2>clippy.log
- ); then
- cat "$dir/clippy.log"
- broken="$broken\n$exercise"
- else
- # Just to show progress
- echo "... OK"
- fi
- elif ! (cd "$dir" && cargo test --quiet --no-run); then
- echo "$exercise's stub does not compile; please make it compile"
- broken="$broken\n$exercise"
- fi
-
- # Restore tests and stub.
- mv "$dir/lib.rs.orig" "$dir/src/lib.rs"
- rm -r "$dir/tests"
- mv "$dir/tests.orig" "$dir/tests"
-done
-
-if [ -n "$broken" ]; then
- echo "Exercises that don't compile:$broken"
- exit 1
-fi
diff --git a/_test/verify_exercise_difficulties.sh b/_test/verify_exercise_difficulties.sh
deleted file mode 100755
index e7bb6179f..000000000
--- a/_test/verify_exercise_difficulties.sh
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-repo=$(cd "$(dirname "$0")/.." && pwd)
-config=$repo/config.json
-
-es=0
-
-# ensure every exercise has a difficulty
-no_difficulty=$(
- jq --raw-output '
- .exercises |
- .concept[], .practice[] |
- select((.status != "deprecated") and (has("difficulty") | not)) |
- .slug
- ' "$config"
-)
-if [ -n "$no_difficulty" ]; then
- echo "Exercises without a difficulty in config.json:"
- echo "$no_difficulty"
- es=1
-fi
-
-# ensure that all difficulties are one of 1, 4, 7, 10
-invalid_difficulty=$(
- jq --raw-output '
- .exercises |
- .concept[], .practice[] |
- select(
- (.status != "deprecated") and
- has("difficulty") and
- (
- .difficulty | tostring |
- in({"1":null,"4":null,"7":null,"10":null}) |
- not
- )
- ) |
- "\(.slug) (\(.difficulty))"
- ' "$config"
-)
-if [ -n "$invalid_difficulty" ]; then
- echo "Exercises with invalid difficulty (must be in {1, 4, 7, 10})"
- echo "$invalid_difficulty"
- es=1
-fi
-
-# ensure difficulties are sorted
-#exercise_order=$(jq --raw-output '.exercises[] | select(.deprecated | not) | .slug' $config)
-#sorted_order=$(jq --raw-output '.exercises | sort_by(.difficulty) | .[] | select(.deprecated | not) | .slug' $config)
-#if [ "$exercise_order" != "$sorted_order" ]; then
-# echo "Exercises are not in sorted order in config.json"
-# es=1
-#fi
-
-exit $es
diff --git a/bin/build_exercise_crate.sh b/bin/build_exercise_crate.sh
deleted file mode 100755
index ca92b77f4..000000000
--- a/bin/build_exercise_crate.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-set -e
-# Compile the 'exercise' crate and put it in the 'bin/' folder
-
-TRACK_ROOT="$(git rev-parse --show-toplevel)"
-
-EXERCISE_CRATE_PATH="$TRACK_ROOT/util/exercise"
-
-BIN_DIR_PATH="$TRACK_ROOT/bin"
-
-(
- cd "$EXERCISE_CRATE_PATH"
-
- echo "Building exercise crate"
-
- cargo build --release
-
- RELEASE_PATH="$EXERCISE_CRATE_PATH/target/release/exercise"
-
- if [ -f "$RELEASE_PATH.exe" ]; then
- RELEASE_PATH="$RELEASE_PATH.exe"
- fi
-
- echo "Copying exercise crate from $RELEASE_PATH into $BIN_DIR_PATH"
-
- cp "$RELEASE_PATH" "$BIN_DIR_PATH"
-)
diff --git a/bin/check_exercises.sh b/bin/check_exercises.sh
new file mode 100755
index 000000000..9f188f7f4
--- /dev/null
+++ b/bin/check_exercises.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+set -eo pipefail
+
+repo=$(git rev-parse --show-toplevel)
+
+if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
+ git fetch --depth=1 origin main
+ files="$(
+ git diff --diff-filter=d --name-only remotes/origin/main |
+ grep "exercises/" |
+ cut --delimiter '/' --fields -3 |
+ sort --unique || true
+ )"
+else
+ files="$repo/exercises/*/*"
+fi
+
+# An exercise worth testing is defined here as any top level directory with
+# a 'tests' directory and a .meta/config.json.
+for exercise in $files; do
+ slug=$(basename "$exercise")
+
+ # An exercise must have a .meta/config.json
+ metaconf="$exercise/.meta/config.json"
+ if [ ! -f "$metaconf" ]; then
+ continue
+ fi
+
+ # suppress compilation output
+ cargo_args="--quiet"
+
+ if [ -z "$BENCHMARK" ] && jq --exit-status '.custom?."test-in-release-mode"?' "$metaconf" > /dev/null; then
+ # release mode is enabled if not benchmarking and the appropriate key is neither `false` nor `null`.
+ cargo_args="$cargo_args --release"
+ fi
+
+ if [ -n "$DENYWARNINGS" ] && [ -z "$CLIPPY" ]; then
+ # No-run mode so we see no test output.
+ # clippy does not understand this flag.
+ cargo_args="$cargo_args --no-run"
+ fi
+
+ echo "Checking $slug..."
+ ./bin/test_exercise.sh "$slug" "$cargo_args"
+done
diff --git a/bin/clean_topics_vs_practices.py b/bin/clean_topics_vs_practices.py
deleted file mode 100755
index ed3b55d01..000000000
--- a/bin/clean_topics_vs_practices.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/env python3
-import json
-
-
-def main():
- with open("config.json", encoding="utf-8") as f:
- config = json.load(f)
-
- concepts = {c['slug'] for c in config['concepts']}
-
- for practice_exercise in config['exercises']['practice']:
- if practice_exercise['topics'] is None:
- continue
-
- practice_exercise['practices'].extend((topic for topic in practice_exercise['topics'] if topic in concepts))
- practice_exercise['topics'] = [topic for topic in practice_exercise['topics'] if topic not in concepts]
-
- for concept in concepts:
- count = 0
- for practice_exercise in config['exercises']['practice']:
- if concept in practice_exercise['practices']:
- count += 1
- if count > 10:
- practice_exercise['practices'].remove(concept)
- practice_exercise['topics'].append(concept)
-
- for practice_exercise in config['exercises']['practice']:
- practice_exercise['practices'].sort()
-
- if practice_exercise['topics'] is not None:
- practice_exercise['topics'].sort()
-
-
- with open("config.json", 'w', encoding="utf-8") as f:
- json.dump(config, f, indent=2, ensure_ascii=False)
- f.write('\n')
-
- print("Updated config.json")
-
-
-if __name__ == '__main__':
- main()
diff --git a/bin/fetch-configlet b/bin/fetch-configlet
index 4800e1508..6bef43ab7 100755
--- a/bin/fetch-configlet
+++ b/bin/fetch-configlet
@@ -24,10 +24,11 @@ get_download_url() {
local latest='https://api.github.com/repos/exercism/configlet/releases/latest'
local arch
case "$(uname -m)" in
- x86_64) arch='x86-64' ;;
- *686*) arch='i386' ;;
- *386*) arch='i386' ;;
- *) arch='x86-64' ;;
+ aarch64|arm64) arch='arm64' ;;
+ x86_64) arch='x86-64' ;;
+ *686*) arch='i386' ;;
+ *386*) arch='i386' ;;
+ *) arch='x86-64' ;;
esac
local suffix="${os}_${arch}.${ext}"
curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${latest}" |
@@ -47,7 +48,7 @@ main() {
fi
local os
- case "$(uname)" in
+ case "$(uname -s)" in
Darwin*) os='macos' ;;
Linux*) os='linux' ;;
Windows*) os='windows' ;;
@@ -58,8 +59,8 @@ main() {
local ext
case "${os}" in
- windows*) ext='zip' ;;
- *) ext='tar.gz' ;;
+ windows) ext='zip' ;;
+ *) ext='tar.gz' ;;
esac
echo "Fetching configlet..." >&2
@@ -69,16 +70,16 @@ main() {
curl "${curlopts[@]}" --output "${output_path}" "${download_url}"
case "${ext}" in
- *zip) unzip "${output_path}" -d "${output_dir}" ;;
- *) tar xzf "${output_path}" -C "${output_dir}" ;;
+ zip) unzip "${output_path}" -d "${output_dir}" ;;
+ *) tar xzf "${output_path}" -C "${output_dir}" ;;
esac
rm -f "${output_path}"
local executable_ext
case "${os}" in
- windows*) executable_ext='.exe' ;;
- *) executable_ext='' ;;
+ windows) executable_ext='.exe' ;;
+ *) executable_ext='' ;;
esac
local configlet_path="${output_dir}/configlet${executable_ext}"
diff --git a/bin/format_exercises b/bin/format_exercises
deleted file mode 100755
index 7798baea7..000000000
--- a/bin/format_exercises
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-# Format existing exercises using rustfmt
-set -e
-
-RUST_TRACK_REPO_PATH=$(cd "$(dirname "$0")/.." && pwd)
-
-# traverse either concept or practice exercise
-# directory and format Rust files
-format_exercises() {
- EXERCISES_PATH="${RUST_TRACK_REPO_PATH}/exercises/$1"
- source_file_name="$2"
- for exercise_dir in "${EXERCISES_PATH}"/*; do
- (
- cd "$exercise_dir"
- config_file="$exercise_dir/.meta/config.json"
- if jq --exit-status '.custom?."allowed-to-not-compile"?' "$config_file"; then
- exercise=$(basename "$exercise_dir")
- echo "$exercise's stub is allowed to not compile"
- # exit the subshell successfully to continue
- # to the next exercise directory
- exit 0
- fi
- cargo fmt
- file_name=".meta/$source_file_name.rs"
- if [ -f "$file_name" ]; then
- rustfmt "$file_name"
- fi
- )
- done
-}
-# https://github.com/exercism/docs/blob/main/anatomy/tracks/concept-exercises.md#file-exemplar-implementation
-format_exercises "concept" "exemplar"
-# https://github.com/exercism/docs/blob/main/anatomy/tracks/practice-exercises.md#file-example-implementation
-format_exercises "practice" "example"
diff --git a/bin/format_exercises.sh b/bin/format_exercises.sh
new file mode 100755
index 000000000..ef7b556c3
--- /dev/null
+++ b/bin/format_exercises.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+set -eo pipefail
+
+# Format existing exercises using rustfmt
+
+repo=$(git rev-parse --show-toplevel)
+
+echo "Formatting exercise files..."
+
+format_one_exercise() {
+ exercise_dir="$1"
+ source_file_name="$2"
+
+ cd "$exercise_dir"
+ config_file="$exercise_dir/.meta/config.json"
+ if jq --exit-status '.custom?."allowed-to-not-compile"?' "$config_file" &> /dev/null; then
+ exit 0
+ fi
+ cargo fmt
+ file_name=".meta/$source_file_name.rs"
+ if [ -f "$file_name" ]; then
+ rustfmt --edition 2024 "$file_name"
+ fi
+}
+
+# traverse either concept or practice exercise
+# directory and format Rust files
+format_exercises() {
+ exercises_path="$repo/exercises/$1"
+ source_file_name="$2"
+ for exercise_dir in "$exercises_path"/*; do
+ format_one_exercise "$exercise_dir" "$source_file_name" &
+ done
+ wait
+}
+# https://github.com/exercism/docs/blob/main/anatomy/tracks/concept-exercises.md#file-exemplar-implementation
+format_exercises "concept" "exemplar"
+# https://github.com/exercism/docs/blob/main/anatomy/tracks/practice-exercises.md#file-example-implementation
+format_exercises "practice" "example"
diff --git a/bin/get_problem_specifications_dir.sh b/bin/get_problem_specifications_dir.sh
new file mode 100755
index 000000000..2f0ea1aea
--- /dev/null
+++ b/bin/get_problem_specifications_dir.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ prefix="${XDG_CACHE_HOME:-$HOME/.cache}"
+elif [[ "$OSTYPE" == "darwin"* ]]; then
+ prefix="${XDG_CACHE_HOME:-$HOME/Library/Caches}"
+else
+ echo "Unsupported OS: $OSTYPE" >&2
+ exit 1
+fi
+
+echo -n "$prefix/exercism/configlet/problem-specifications"
diff --git a/bin/lint_markdown.sh b/bin/lint_markdown.sh
index 6f3592c7c..52b75c2ab 100755
--- a/bin/lint_markdown.sh
+++ b/bin/lint_markdown.sh
@@ -1,3 +1,7 @@
#!/usr/bin/env bash
-set -e
-npx markdownlint-cli concepts/**/*.md exercises/**/*.md docs/maintaining.md docs/CONTRIBUTING.md
+set -eo pipefail
+
+npx markdownlint-cli \
+ docs/*.md \
+ concepts/**/*.md \
+ exercises/**/*.md
diff --git a/bin/lint_tool_file_names.sh b/bin/lint_tool_file_names.sh
deleted file mode 100755
index 0c0ef67e7..000000000
--- a/bin/lint_tool_file_names.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/env bash
-
-# Require that internal shell script file names use snake_case
-set -eo pipefail
-
-# find a list of files whose names do not match our convention
-errant_files=$(find bin/ _test/ -iname '*\.sh' -exec basename {} \; | grep '[^a-z_.]' || test 1)
-
-if [ -n "$errant_files" ]; then
- echo "These file names do not follow our snake case convention:"
- # find them again to print the whole relative path
- while IFS= read -r file_name; do
- find . -name "$file_name"
- done <<< "$errant_files"
- echo "Please correct them!"
- exit 1
-fi
diff --git a/bin/lint_trailing_spaces.sh b/bin/lint_trailing_spaces.sh
deleted file mode 100755
index 16f021a14..000000000
--- a/bin/lint_trailing_spaces.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env bash
-
-# Report any .toml files that have trailing white space
-# so user can fix them
-set -eo pipefail
-
-files=$(find . \( -iname '*.toml' -o -iname '*.sh' -o -iname '*.rs' \) -exec grep -l '[[:space:]]$' {} \;)
-
-if [ -n "$files" ]; then
- echo "These files have trailing whitespace:"
- echo "$files"
- echo "Our conventions disallow this so please remove the trailing whitespace."
- exit 1
-fi
diff --git a/bin/symlink_problem_specifications.sh b/bin/symlink_problem_specifications.sh
new file mode 100755
index 000000000..15d698269
--- /dev/null
+++ b/bin/symlink_problem_specifications.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+set -eo pipefail
+
+# This script creates a symlink to the problem-specifications cache used
+# by configlet. When working on an exercise, it is handy to have the
+# problem-specifions files in reach. Symlinking to configlet's cache ensures
+# you're always looking at the up-to-date files.
+
+cd "$(git rev-parse --show-toplevel)"
+
+[ -e "problem-specifications" ] || ln -s "$(./bin/get_problem_specifications_dir.sh)" "problem-specifications"
+
+# ensure populated cache
+[ -f ./bin/configlet ] || ./bin/fetch-configlet
+./bin/configlet info &> /dev/null
+
+for exercise in exercises/practice/*; do
+ name="$(basename "$exercise")"
+ if [ -d "problem-specifications/exercises/$name" ]; then
+ [ -e "$exercise/.prob-spec" ] && rm "$exercise/.prob-spec"
+ ln -s "../../../problem-specifications/exercises/$name" "$exercise/.prob-spec"
+ fi
+done
diff --git a/bin/test-exercise b/bin/test-exercise
deleted file mode 100755
index ee8d3040e..000000000
--- a/bin/test-exercise
+++ /dev/null
@@ -1,112 +0,0 @@
-#!/usr/bin/env bash
-
-set -eo pipefail
-# Test an exercise
-
-# which exercise are we testing right now?
-# if we were passed an argument, that should be the
-# exercise directory. Otherwise, assume we're in
-# it currently.
-if [ $# -ge 1 ]; then
- exercise=$1
- # if this script is called with arguments, it will pass through
- # any beyond the first to cargo. Note that we can only get a
- # free default argument if no arguments at all were passed,
- # so if you are in the exercise directory and want to pass any
- # arguments to cargo, you need to include the local path first.
- # I.e. to test in release mode:
- # $ test-exercise . --release
- shift 1
-else
- exercise='.'
-fi
-
-# what cargo command will we use?
-if [ -n "$BENCHMARK" ]; then
- command="bench"
-else
- command="test"
-fi
-
-declare -a preserve_files=("src/lib.rs" "Cargo.toml" "Cargo.lock")
-declare -a preserve_dirs=("tests")
-
-# reset instructions
-reset () {
- for file in "${preserve_files[@]}"; do
- if [ -f "$exercise/$file.orig" ]; then
- mv -f "$exercise/$file.orig" "$exercise/$file"
- fi
- done
- for dir in "${preserve_dirs[@]}"; do
- if [ -d "$exercise/$dir.orig" ]; then
- rm -rf "${exercise:?}/$dir"
- mv "$exercise/$dir.orig" "$exercise/$dir"
- fi
- done
-}
-
-# cause the reset to execute when the script exits normally or is killed
-trap reset EXIT INT TERM
-
-# preserve the files and directories we care about
-for file in "${preserve_files[@]}"; do
- if [ -f "$exercise/$file" ]; then
- cp "$exercise/$file" "$exercise/$file.orig"
- fi
-done
-for dir in "${preserve_dirs[@]}"; do
- if [ -d "$exercise/$dir" ]; then
- cp -r "$exercise/$dir" "$exercise/$dir.orig"
- fi
-done
-
-# Move example files to where Cargo expects them
-if [ -f "$exercise/.meta/example.rs" ]; then
- example="$exercise/.meta/example.rs"
-elif [ -f "$exercise/.meta/exemplar.rs" ]; then
- example="$exercise/.meta/exemplar.rs"
-else
- echo "Could not locate example implementation for $exercise"
- exit 1
-fi
-
-cp -f "$example" "$exercise/src/lib.rs"
-if [ -f "$exercise/.meta/Cargo-example.toml" ]; then
- cp -f "$exercise/.meta/Cargo-example.toml" "$exercise/Cargo.toml"
-fi
-
-# If deny warnings, insert a deny warnings compiler directive in the header
-if [ -n "$DENYWARNINGS" ]; then
- sed -i -e '1i #![deny(warnings)]' "$exercise/src/lib.rs"
-fi
-
-# eliminate #[ignore] lines from tests
-for test in "$exercise/"tests/*.rs; do
- sed -i -e '/#\[ignore\]/{
- s/#\[ignore\]\s*//
- /^\s*$/d
- }' "$test"
-done
-
-# run tests from within exercise directory
-# (use subshell so we auto-reset to current pwd after)
-(
- cd "$exercise"
- if [ -n "$CLIPPY" ]; then
- # Consider any Clippy to be an error in tests only.
- # For now, not the example solutions since they have many Clippy warnings,
- # and are not shown to students anyway.
- sed -i -e '1i #![deny(clippy::all)]' tests/*.rs
- if ! cargo clippy --tests --color always "$@" 2>clippy.log; then
- cat clippy.log
- exit 1
- else
- # Just to show progress
- echo "clippy $exercise OK"
- fi
- else
- # this is the last command; its exit code is what's passed on
- time cargo $command "$@"
- fi
-)
diff --git a/bin/test_exercise.sh b/bin/test_exercise.sh
new file mode 100755
index 000000000..5d4ea9492
--- /dev/null
+++ b/bin/test_exercise.sh
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+set -eo pipefail
+
+cd "$(git rev-parse --show-toplevel)"
+
+if [ $# -eq 0 ]; then
+ echo "Usage: [cargo args]"
+ exit 1
+fi
+
+slug="$1"
+cargo_args="$2"
+
+# determine the exercise path from the slug
+for p in exercises/{practice,concept}/* ; do
+ current_slug="$(basename "$p")"
+ p="$(dirname "$p")"
+ if [[ "$current_slug" = "$slug" ]]; then
+ exercise_path="$p/$slug"
+ break
+ fi
+done
+if [ -z "$exercise_path" ]; then
+ echo "Could not find exercise path for $slug"
+ exit 1
+fi
+
+# what cargo command will we use?
+if [ -n "$BENCHMARK" ]; then
+ command="+nightly bench"
+ if [ ! -e "$exercise_path/benches" ]; then
+ # no benches, nothing to do
+ exit
+ fi
+else
+ command="test"
+fi
+
+# setup temporary directory for the exercise
+tmp_dir=$(mktemp -d)
+trap 'rm -rf $tmp_dir' EXIT INT TERM
+
+# copy the exercise to the temporary directory
+contents_to_copy=(
+ "src"
+ "tests"
+ "benches"
+ "Cargo.toml"
+)
+for c in "${contents_to_copy[@]}"; do
+ if [ ! -e "$exercise_path/$c" ]; then
+ continue
+ fi
+ cp -r "$exercise_path/$c" "$tmp_dir"
+done
+
+# Move example files to where Cargo expects them
+if [ -f "$exercise_path/.meta/example.rs" ]; then
+ cp -f "$exercise_path/.meta/example.rs" "$tmp_dir/src/lib.rs"
+elif [ -f "$exercise_path/.meta/exemplar.rs" ]; then
+ cp -f "$exercise_path/.meta/exemplar.rs" "$tmp_dir/src/lib.rs"
+else
+ echo "Could not locate example implementation for $exercise_path"
+ exit 1
+fi
+if [ -f "$exercise_path/.meta/Cargo-example.toml" ]; then
+ cp -f "$exercise_path/.meta/Cargo-example.toml" "$tmp_dir/Cargo.toml"
+fi
+
+if [ -n "$DENYWARNINGS" ]; then
+ export RUSTFLAGS="$RUSTFLAGS -D warnings"
+fi
+
+# run tests from within temporary directory
+cd "$tmp_dir"
+if [ -n "$CLIPPY" ]; then
+ export RUSTFLAGS="$RUSTFLAGS -D warnings"
+ # shellcheck disable=SC2086
+ cargo clippy --tests $cargo_args
+else
+ # shellcheck disable=SC2086
+ cargo $command $cargo_args -- --include-ignored
+fi
diff --git a/concepts/integers/about.md b/concepts/integers/about.md
index 03b145017..10449a89b 100644
--- a/concepts/integers/about.md
+++ b/concepts/integers/about.md
@@ -9,7 +9,7 @@ Integers all have a **bit width**, which is just the number of bits making up th
the maximum value which can be represented by that integer type.
For example, one of the most common integer types is a `u8`: an unsigned, 8-bit integer. You may
-recognize this type as a single byte. This has a minimum value of 0 and a maximum value of 256.
+recognize this type as a single byte. This has a minimum value of 0 and a maximum value of 255.
Rust has 12 integer primitive types, broken out by bit width and signedness:
diff --git a/concepts/methods/about.md b/concepts/methods/about.md
index 14eaafdfb..81280e238 100644
--- a/concepts/methods/about.md
+++ b/concepts/methods/about.md
@@ -59,7 +59,7 @@ If we wish to implement an `info` method to display the basic information of the
we could define this method inside an `impl` block for `Wizard`:
```rust
-impl Wizard {
+impl Wizard {
fn info(&self) {
println!(
"A wizard of age {} who studies in House {:?} at Hogwarts",
diff --git a/concepts/references/about.md b/concepts/references/about.md
index 654ff59ad..63c3917ab 100644
--- a/concepts/references/about.md
+++ b/concepts/references/about.md
@@ -65,7 +65,7 @@ fn check_shapes(constant: &[u8], linear: &[u8], superlinear: &[u8]) -> (bool, bo
// understanding the implementations of the following functions is not necessary for this example
// but are provided should you be interested
-fn is_constant(slice: &[u8]) -> bool {
+fn is_constant(slice: &[u8]) -> bool {
slice
.first()
.map(|first| slice.iter().all(|v| v == first))
@@ -146,7 +146,7 @@ pub fn main() {
}
```
-This works, because the compiler knows that the mutable borrows do not overlap
+This works, because the compiler knows that the mutable borrows do not overlap
```rust
fn add_five(counter: &mut i32) {
diff --git a/concepts/string-vs-str/links.json b/concepts/string-vs-str/links.json
index f6fcfc52b..06c5e6ec4 100644
--- a/concepts/string-vs-str/links.json
+++ b/concepts/string-vs-str/links.json
@@ -4,7 +4,7 @@
"description": "String types in RBE"
},
{
- "url": "https://fasterthanli.me/blog/2020/working-with-strings-in-rust/",
+ "url": "https://fasterthanli.me/articles/working-with-strings-in-rust",
"description": "fasterthanli.me's Working with strings in Rust"
},
{
diff --git a/concepts/structs/about.md b/concepts/structs/about.md
index 061ba5889..41f131212 100644
--- a/concepts/structs/about.md
+++ b/concepts/structs/about.md
@@ -32,7 +32,7 @@ Lastly, methods can be defined on structs inside of an `impl` block:
impl Item {
// initializes and returns a new instance of our Item struct
fn new() -> Self {
- unimplemented!()
+ todo!()
}
}
```
diff --git a/concepts/structs/links.json b/concepts/structs/links.json
index 15cb1cccb..9dea0f3e1 100644
--- a/concepts/structs/links.json
+++ b/concepts/structs/links.json
@@ -1,6 +1,6 @@
[
{
- "url": "https://learning-rust.github.io/docs/b2.structs.html#Tuple-structs",
+ "url": "https://learning-rust.github.io/docs/structs/#tuple-structs",
"description": "more information about tuple structs"
},
{
@@ -8,7 +8,7 @@
"description": "examples of tuple and unit structs"
},
{
- "url": "https://learning-rust.github.io/docs/b2.structs.html#C-like-structs",
+ "url": "https://learning-rust.github.io/docs/structs/#c-like-structs",
"description": "good overview of the different types of structs and their syntax"
}
]
diff --git a/config.json b/config.json
index bf3f8b70c..fa7e8283b 100644
--- a/config.json
+++ b/config.json
@@ -3,7 +3,7 @@
"slug": "rust",
"active": true,
"status": {
- "concept_exercises": true,
+ "concept_exercises": false,
"test_runner": true,
"representer": true,
"analyzer": true
@@ -16,7 +16,7 @@
"highlightjs_language": "rust"
},
"test_runner": {
- "average_run_time": 2.0
+ "average_run_time": 2
},
"files": {
"solution": [
@@ -24,7 +24,7 @@
"Cargo.toml"
],
"test": [
- "tests/%{kebab_slug}.rs"
+ "tests/%{snake_slug}.rs"
],
"example": [
".meta/example.rs"
@@ -76,9 +76,9 @@
},
{
"slug": "semi-structured-logs",
+ "uuid": "1924b87a-9246-456f-8fc1-111f922a8cf3",
"name": "Semi Structured Logs",
"difficulty": 1,
- "uuid": "1924b87a-9246-456f-8fc1-111f922a8cf3",
"concepts": [
"enums"
],
@@ -89,8 +89,8 @@
},
{
"slug": "resistor-color",
- "name": "Resistor Color",
"uuid": "51c31e6a-b7ec-469d-8a28-dd821fd857d2",
+ "name": "Resistor Color",
"difficulty": 1,
"concepts": [
"external-crates"
@@ -132,9 +132,9 @@
},
{
"slug": "low-power-embedded-game",
+ "uuid": "7f064e9b-f631-48b1-9ed0-a66e8393ceba",
"name": "Low-Power Embedded Game",
"difficulty": 1,
- "uuid": "7f064e9b-f631-48b1-9ed0-a66e8393ceba",
"concepts": [
"tuples",
"destructuring"
@@ -146,9 +146,9 @@
},
{
"slug": "short-fibonacci",
+ "uuid": "c481e318-ddd7-4f8a-91eb-dadb7315e304",
"name": "A Short Fibonacci Sequence",
"difficulty": 1,
- "uuid": "c481e318-ddd7-4f8a-91eb-dadb7315e304",
"concepts": [
"vec-macro"
],
@@ -160,9 +160,9 @@
},
{
"slug": "rpn-calculator",
+ "uuid": "25cc722b-211d-4271-9381-fdfe16b41301",
"name": "RPN Calculator",
"difficulty": 4,
- "uuid": "25cc722b-211d-4271-9381-fdfe16b41301",
"concepts": [
"vec-stack"
],
@@ -176,9 +176,9 @@
},
{
"slug": "csv-builder",
+ "uuid": "10c9f505-9aef-479f-b689-cb7959572482",
"name": "CSV builder",
"difficulty": 1,
- "uuid": "10c9f505-9aef-479f-b689-cb7959572482",
"concepts": [
"string-vs-str"
],
@@ -284,6 +284,17 @@
"generic_over_type"
]
},
+ {
+ "slug": "flower-field",
+ "name": "Flower Field",
+ "uuid": "117d6a25-960e-4d53-8347-a20490f60f36",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 7,
+ "topics": [
+ "board_state"
+ ]
+ },
{
"slug": "minesweeper",
"name": "Minesweeper",
@@ -291,6 +302,7 @@
"practices": [],
"prerequisites": [],
"difficulty": 7,
+ "status": "deprecated",
"topics": [
"board_state"
]
@@ -309,59 +321,6 @@
"str_to_digits"
]
},
- {
- "slug": "parallel-letter-frequency",
- "name": "Parallel Letter Frequency",
- "uuid": "e114b19f-9a9a-402d-a5cb-1cad8de5088e",
- "practices": [],
- "prerequisites": [],
- "difficulty": 10,
- "topics": [
- "multi_threading"
- ]
- },
- {
- "slug": "macros",
- "name": "Macros",
- "uuid": "29583cc6-d56d-4bee-847d-93d74e5a30e7",
- "practices": [
- "hashmap"
- ],
- "prerequisites": [],
- "difficulty": 10,
- "topics": [
- "macros",
- "macros_by_example"
- ]
- },
- {
- "slug": "poker",
- "name": "Poker",
- "uuid": "0a33f3ac-cedd-4a40-a132-9d044b0e9977",
- "practices": [
- "enums",
- "strings",
- "structs"
- ],
- "prerequisites": [],
- "difficulty": 10,
- "topics": [
- "lifetimes",
- "parsing",
- "traits"
- ]
- },
- {
- "slug": "forth",
- "name": "Forth",
- "uuid": "55976c49-1be5-4170-8aa3-056c2223abbb",
- "practices": [],
- "prerequisites": [],
- "difficulty": 10,
- "topics": [
- "parsing"
- ]
- },
{
"slug": "armstrong-numbers",
"name": "Armstrong Numbers",
@@ -377,16 +336,23 @@
"slug": "beer-song",
"name": "Beer Song",
"uuid": "bb42bc3a-139d-4cab-8b3a-2eac2e1b77b6",
- "practices": [
- "loops",
- "strings"
- ],
+ "practices": [],
"prerequisites": [],
"difficulty": 1,
"topics": [
"case",
"vectors"
- ]
+ ],
+ "status": "deprecated"
+ },
+ {
+ "slug": "bottle-song",
+ "name": "Bottle Song",
+ "uuid": "31e8185b-39d8-48c6-88a0-f302e2864f16",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "topics": []
},
{
"slug": "difference-of-squares",
@@ -527,6 +493,93 @@
"stack_or_recursion"
]
},
+ {
+ "slug": "collatz-conjecture",
+ "name": "Collatz Conjecture",
+ "uuid": "f9afd650-8103-4373-a284-fa4ecfee7207",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "topics": [
+ "math",
+ "option_type"
+ ]
+ },
+ {
+ "slug": "diffie-hellman",
+ "name": "Diffie-Hellman",
+ "uuid": "ff9344b6-b185-4d53-bb03-f1d2bce8c959",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "topics": [
+ "math"
+ ],
+ "status": "deprecated"
+ },
+ {
+ "slug": "series",
+ "name": "Series",
+ "uuid": "9de405e1-3a05-43cb-8eb3-00b81a2968e9",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "topics": [
+ "strings",
+ "vectors"
+ ]
+ },
+ {
+ "slug": "hexadecimal",
+ "name": "Hexadecimal",
+ "uuid": "496fd79f-1678-4aa2-8110-c32c6aaf545e",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "topics": [],
+ "status": "deprecated"
+ },
+ {
+ "slug": "nucleotide-codons",
+ "name": "Nucleotide Codons",
+ "uuid": "8dae8f4d-368d-477d-907e-bf746921bfbf",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "topics": [],
+ "status": "deprecated"
+ },
+ {
+ "slug": "two-fer",
+ "name": "Two Fer",
+ "uuid": "585e963b-366c-48bc-b523-29b6be4175c8",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "topics": [
+ "match",
+ "strings"
+ ],
+ "status": "deprecated"
+ },
+ {
+ "slug": "kindergarten-garden",
+ "name": "Kindergarten Garden",
+ "uuid": "c27e4878-28a4-4637-bde2-2af681a7ff0d",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "topics": []
+ },
+ {
+ "slug": "eliuds-eggs",
+ "name": "Eliud's Eggs",
+ "uuid": "2d738c77-8dde-437a-bf42-ed5542a414d1",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "topics": []
+ },
{
"slug": "acronym",
"name": "Acronym",
@@ -638,7 +691,7 @@
"uuid": "0c8eeef7-4bab-4cf9-9047-c208b5618312",
"practices": [],
"prerequisites": [],
- "difficulty": 4,
+ "difficulty": 1,
"topics": [
"btree"
]
@@ -1007,32 +1060,6 @@
"structs"
]
},
- {
- "slug": "ocr-numbers",
- "name": "OCR Numbers",
- "uuid": "704aab91-b83a-4e64-8c21-fb0be5076289",
- "practices": [],
- "prerequisites": [],
- "difficulty": 10,
- "topics": [
- "chunks",
- "lines",
- "slices"
- ]
- },
- {
- "slug": "react",
- "name": "React",
- "uuid": "8708ccc7-711a-4862-b5a4-ff59fde2241c",
- "practices": [],
- "prerequisites": [],
- "difficulty": 10,
- "topics": [
- "closures",
- "generics",
- "lifetimes"
- ]
- },
{
"slug": "wordy",
"name": "Wordy",
@@ -1061,68 +1088,6 @@
"vectors"
]
},
- {
- "slug": "circular-buffer",
- "name": "Circular Buffer",
- "uuid": "6ff1a539-251b-49d4-81b5-a6b1e9ba66a4",
- "practices": [],
- "prerequisites": [],
- "difficulty": 10,
- "topics": [
- "buffers",
- "generics"
- ]
- },
- {
- "slug": "rectangles",
- "name": "Rectangles",
- "uuid": "cc4ccd99-1c97-4ee7-890c-d629b4e1e46d",
- "practices": [
- "enums"
- ],
- "prerequisites": [],
- "difficulty": 10,
- "topics": [
- "algorithms",
- "structs",
- "traits"
- ]
- },
- {
- "slug": "collatz-conjecture",
- "name": "Collatz Conjecture",
- "uuid": "f9afd650-8103-4373-a284-fa4ecfee7207",
- "practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": [
- "math",
- "option_type"
- ]
- },
- {
- "slug": "diffie-hellman",
- "name": "Diffie-Hellman",
- "uuid": "ff9344b6-b185-4d53-bb03-f1d2bce8c959",
- "practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": [
- "math"
- ]
- },
- {
- "slug": "series",
- "name": "Series",
- "uuid": "9de405e1-3a05-43cb-8eb3-00b81a2968e9",
- "practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": [
- "strings",
- "vectors"
- ]
- },
{
"slug": "accumulate",
"name": "Accumulate",
@@ -1248,6 +1213,19 @@
"traits"
]
},
+ {
+ "slug": "list-ops",
+ "name": "List Ops",
+ "uuid": "4b411941-d272-4b95-8c40-5816fecf27a2",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 4,
+ "topics": [
+ "generics",
+ "iterators",
+ "traits"
+ ]
+ },
{
"slug": "phone-number",
"name": "Phone Number",
@@ -1341,6 +1319,42 @@
"strings"
]
},
+ {
+ "slug": "secret-handshake",
+ "name": "Secret Handshake",
+ "uuid": "8c044530-9deb-4ff7-a638-98d6c05ebbb8",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 4,
+ "topics": []
+ },
+ {
+ "slug": "knapsack",
+ "name": "Knapsack",
+ "uuid": "cbccd0c5-eb15-4705-9a4c-0209861f078c",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 4,
+ "topics": []
+ },
+ {
+ "slug": "yacht",
+ "name": "Yacht",
+ "uuid": "1a0e8e34-f578-4a53-91b0-8a1260446553",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 4,
+ "topics": []
+ },
+ {
+ "slug": "matrix",
+ "name": "Matrix",
+ "uuid": "9fceeb8b-0154-45f0-93a5-7117d4d81449",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 4,
+ "topics": []
+ },
{
"slug": "fizzy",
"name": "Fizzy",
@@ -1365,6 +1379,121 @@
"math"
]
},
+ {
+ "slug": "parallel-letter-frequency",
+ "name": "Parallel Letter Frequency",
+ "uuid": "e114b19f-9a9a-402d-a5cb-1cad8de5088e",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 10,
+ "topics": [
+ "multi_threading"
+ ]
+ },
+ {
+ "slug": "macros",
+ "name": "Macros",
+ "uuid": "29583cc6-d56d-4bee-847d-93d74e5a30e7",
+ "practices": [
+ "hashmap"
+ ],
+ "prerequisites": [],
+ "difficulty": 10,
+ "topics": [
+ "macros",
+ "macros_by_example"
+ ]
+ },
+ {
+ "slug": "pov",
+ "name": "POV",
+ "uuid": "bf7b7309-3d34-4893-a584-f7742502e012",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 10,
+ "topics": []
+ },
+ {
+ "slug": "poker",
+ "name": "Poker",
+ "uuid": "0a33f3ac-cedd-4a40-a132-9d044b0e9977",
+ "practices": [
+ "enums",
+ "strings",
+ "structs"
+ ],
+ "prerequisites": [],
+ "difficulty": 10,
+ "topics": [
+ "lifetimes",
+ "parsing",
+ "traits"
+ ]
+ },
+ {
+ "slug": "forth",
+ "name": "Forth",
+ "uuid": "55976c49-1be5-4170-8aa3-056c2223abbb",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 10,
+ "topics": [
+ "parsing"
+ ]
+ },
+ {
+ "slug": "ocr-numbers",
+ "name": "OCR Numbers",
+ "uuid": "704aab91-b83a-4e64-8c21-fb0be5076289",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 10,
+ "topics": [
+ "chunks",
+ "lines",
+ "slices"
+ ]
+ },
+ {
+ "slug": "react",
+ "name": "React",
+ "uuid": "8708ccc7-711a-4862-b5a4-ff59fde2241c",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 10,
+ "topics": [
+ "closures",
+ "generics",
+ "lifetimes"
+ ]
+ },
+ {
+ "slug": "circular-buffer",
+ "name": "Circular Buffer",
+ "uuid": "6ff1a539-251b-49d4-81b5-a6b1e9ba66a4",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 10,
+ "topics": [
+ "buffers",
+ "generics"
+ ]
+ },
+ {
+ "slug": "rectangles",
+ "name": "Rectangles",
+ "uuid": "cc4ccd99-1c97-4ee7-890c-d629b4e1e46d",
+ "practices": [
+ "enums"
+ ],
+ "prerequisites": [],
+ "difficulty": 10,
+ "topics": [
+ "algorithms",
+ "structs",
+ "traits"
+ ]
+ },
{
"slug": "xorcism",
"name": "Xorcism",
@@ -1429,9 +1558,7 @@
"slug": "scale-generator",
"name": "Scale Generator",
"uuid": "b9436a89-90cb-4fb7-95b0-758f17c309ff",
- "practices": [
- "enums"
- ],
+ "practices": [],
"prerequisites": [],
"difficulty": 7,
"topics": [
@@ -1439,7 +1566,8 @@
"music_theory",
"to_primitive",
"traits"
- ]
+ ],
+ "status": "deprecated"
},
{
"slug": "dominoes",
@@ -1465,39 +1593,6 @@
"lists",
"unsafe"
]
- },
- {
- "slug": "hexadecimal",
- "name": "Hexadecimal",
- "uuid": "496fd79f-1678-4aa2-8110-c32c6aaf545e",
- "practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": null,
- "status": "deprecated"
- },
- {
- "slug": "nucleotide-codons",
- "name": "Nucleotide Codons",
- "uuid": "8dae8f4d-368d-477d-907e-bf746921bfbf",
- "practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": null,
- "status": "deprecated"
- },
- {
- "slug": "two-fer",
- "name": "Two Fer",
- "uuid": "585e963b-366c-48bc-b523-29b6be4175c8",
- "practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": [
- "match",
- "strings"
- ],
- "status": "deprecated"
}
],
"foregone": [
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 94af02384..52af69b34 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -1,39 +1,171 @@
# Contributing to the Rust Exercism Track
-This track is a work in progress.
-Please take all the value you can from it, but if you notice any way to improve it, we are eager for pull requests.
-Fixing a typo in documentation is every bit as valid a contribution as a massive addition of code.
+Issues and pull requests are currently being auto-closed.
+Please make a post on [the Exercism forum] to propose changes.
+Contributions are very welcome if they are coordinated on the forum.
-> A work of art is never finished, merely abandoned.
->
-> -- Paul Valéry
+[the Exercism forum]: https://forum.exercism.org/
-Nonetheless, feel free to peruse what we have written thus far!
+## General policies
-*Contributions welcome :)*
+- [Code of Conduct](https://exercism.org/code-of-conduct)
+- [Exercism's PR guidelines](https://exercism.org/docs/community/being-a-good-community-member/pull-requests).
-As one of the many tracks in Exercism, contributions here should observe Exercism standards like the [Code of Conduct](https://exercism.org/code-of-conduct).
-This document introduces the ways you can help and what maintainers expect of contributors.
+## Tooling
-## Ways to Contribute
+Some tooling is present as bash scripts in `bin/`.
+A lot more is present in `rust-tooling/`,
+which should be preferred for anything non-trivial.
-As with many Open Source projects, work abounds.
-Here are a few categories of welcome contribution:
-- improving existing exercises
-- creating new exercises
-- improving internal tooling
-- updating documentation
-- fixing typos, misspellings, and grammatical errors
+There is also a [`mise`](https://mise.jdx.dev/) configuration
+with a couple useful commands to interact with the repo.
+Feel free to extend it.
-## Merging Philosophy
+If you want to run CI tests locally, `mise run test` will get you quite far.
-A [pull request](https://docs.github.com/en/github/getting-started-with-github/github-glossary#pull-request) should address one logical change.
-This could be small or big.
+## Excluding tests and adding custom ones
-For example, [#1175](https://github.com/exercism/rust/pull/1175) fixed a single typo in a single file after minutes of collaboration.
+While creating or updating an exercise, you may decide to exclude certain tests from being generated.
+You can do so by setting `include = false` in `.meta/tests.toml`.
+Please include a comment about the reason for excluding it.
-It was a small, short pull request with one logical change.
+If you want to add additional track-specific tests, you can do so in a file `.meta/additional-tests.json`.
+Only do this with a good reason for not upstreaming these tests.
+A comment in the additional test should answer these questions:
+- Why is the test no upstreamed to `problem-specifications`, i.e. why is it not generally valuable to other languages?
+- Why is the test valuable for the Rust track anyway?
-[#653](https://github.com/exercism/rust/pull/653) introduced the doubly linked list exercise after months of collaboration.
+## Creating a new exercise
-It was a big, long-running pull request with one logical change.
+Please familiarize yourself with the [Exercism documentation about practice exercises].
+
+[Exercism documentation about practice exercises]: https://exercism.org/docs/building/tracks/practice-exercises
+
+Run `mise run add-exercise` and you'll be prompted for the minimal
+information required to generate the exercise stub for you.
+After that, jump in the generated exercise and fill in any placeholders you find.
+This includes most notably:
+
+- writing the exercise stub in `src/lib.rs`
+- adding an example solution in `.meta/example.rs`
+- Adjusting `.meta/test_template.tera`
+
+The tests are generated using the template engine [Tera].
+The input of the template is the canonical data from [`problem-specifications`].
+
+Find some tips about writing tera templates [here](#tera-templates).
+
+[Tera]: https://keats.github.io/tera/docs/
+[`problem-specifications`]: https://github.com/exercism/problem-specifications/
+
+Many aspects of a correctly implemented exercises are checked in CI.
+I recommend that instead of spending lots of time studying and writing
+documentation about the process, _just do it_.
+If something breaks, fix it and add a test / automation
+so it won't happen anymore.
+
+If you are creating a practice exercise from scratch,
+one that is not present in `problem-specifications`,
+you have to write your tests manually.
+Tests should be sorted by increasing complexity,
+so students can un-ignore them one by one and solve the exercise with TDD.
+See [the Exercism documentation](https://github.com/exercism/legacy-docs/blob/main/language-tracks/exercises/anatomy/test-suites.md)
+for more thoughts on writing good tests.
+
+Except for extraordinary circumstances,
+the stub file should compile under `cargo test --no-run`.
+This allows us to check that the signatures in the stub file
+match the signatures expected by the tests.
+Use `todo!()` as the body of each function to achieve this.
+If there is a justified reason why this is not possible,
+include a `.custom."allowed-to-not-compile"` key
+in the exercise's `.meta/config.json` containing the reason.
+
+## Updating an exercise
+
+Many exercises are derived from [`problem-specifications`].
+This includes their test suite and user-facing documentation.
+Before proposing changes here,
+check if they should be made in `problem-specifications` instead.
+
+Run `mise run update-exercise` to update an exercise.
+This outsources most work to `configlet sync --update`
+and runs the test generator again.
+
+When updating an exercise that doesn't have a tera template yet,
+a new one will be generated for you.
+You will likely have to adjust it to some extent.
+
+Find some tips about writing tera templates [in the next section](#tera-templates).
+
+## Tera templates
+
+The full documentation for tera templates is [here][tera-docs].
+Following are some approaches that have worked for our specific needs.
+
+You will likely want to look at the exercise's `canonical-data.json` to see what your input data looks like.
+You can use `bin/symlink_problem_specifications.sh` to have this data
+symlinked into the actual exercise directory. Handy!
+
+The name of the input property is different for each exercise.
+The default template will be something like this:
+
+```txt
+let input = {{ test.input | json_encode() }};
+```
+
+You will have to add the specific field of input for this exercise, e.g.
+
+```txt
+let input = {{ test.input.integers | json_encode() }};
+```
+
+Some exercises may have error return values.
+You can use an if-else to render something different,
+depending on the structure of the data:
+
+```txt
+let expected = {% if test.expected is object -%}
+ None
+{%- else -%}
+ Some({{ test.expected }})
+{%- endif %};
+```
+
+Some exercises have multiple functions that need to be implemented
+by the student and therefore tested.
+The canonical data contains a field `property` in that case.
+You can construct if-else-chains based on `test.property` and render a different function based on that.
+
+Some exercises have their test cases organized into _test groups_.
+Your template will have to account for that.
+In many cases, you can simply flatten the structure. (example: [`affine-cipher`](/exercises/practice/affine-cipher/.meta/test_template.tera))
+However, tests sometimes have the same description in different groups, which leads to naming conflicts if the structure is flattened.
+In that case, you can use the group description to organize the tests in modules. (example: [`forth`](/exercises/practice/forth/.meta/test_template.tera))
+
+There are some custom tera filters in [`rust-tooling`](/rust-tooling/generate/src/custom_filters.rs).
+Here's the hopefully up-to-date list:
+- `to_hex` formats ints in hexadecimal
+- `make_ident` turns an arbitrary string into a decent Rust identifier.
+ Most useful for generating function names from test descriptions.
+- `fmt_num` format number literals (insert `_` every third digit)
+
+Feel free to add your own in the crate `rust-tooling`.
+Hopefully you'll remember to update the list here as well. 🙂
+
+For a rather complicated example, check out the test template of `triangle`.
+It organizes the test cases in modules and dynamically detects which tests to put behind feature gates.
+That exercise also reimplements some test cases from upstream in `additional-tests.json`, in order to add more information to them necessary for generating good tests.
+
+[tera-docs]: https://keats.github.io/tera/docs/#templates
+
+## Syllabus
+
+The syllabus is currently deactivated due to low quality.
+see [this forum post](https://forum.exercism.org/t/feeling-lost-and-frustrated-in-rust/4882)
+for some background on the desicion.
+
+Creating a better syllabus would be very benefitial,
+but it's a lot of work and requires good communication and coordination.
+Make sure to discuss any plans you have on the forum
+with people who have experience building syllabi.
diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md
index 4e16399e4..5625561c6 100644
--- a/docs/INSTALLATION.md
+++ b/docs/INSTALLATION.md
@@ -1,42 +1,13 @@
# Installation
-Methods for installing Rust change as the language evolves. To get up-to-date installation instructions head to the [Rust install page](https://www.rust-lang.org/tools/install).
+To get the recommended installation instructions for your platform,
+head over to [the official Rust website].
-## Additional utilities
+Having the toolchain installed is not everything!
+IDE support is a huge productivity boost that most developers have come to expect.
+If you are using Visual Studio Code, check out its [documentation for Rust support][vscode].
+We also recommend you whole-heartedly to enable [linting with clippy][vscode-linting]!
-### Rustfmt - Writing well-formatted code
-
-When you are solving the exercise, you are free to choose any coding format you want.
-However when you are writing a real-world application or a library, your source code will
-be read by other people, not just you. To solve a problem when different people choose
-different formats for a single project, the developers set a standard coding format
-for the said project.
-
-In the Rust world there is a tool, that helps developers to bring standard formatting
-to their applications - [rustfmt](https://github.com/rust-lang/rustfmt).
-
-To install `rustfmt` use the following commands:
-
-```bash
-rustup self update
-
-rustup component add rustfmt
-```
-
-### Clippy - Writing effective code
-
-At its core the process of programming consists of two parts: storing and managing
-the resources of your computer. Rust provides a lot of means to accomplish these two
-task. Unfortunately sometimes programmers do not use those means very effectively and
-create programms that work correctly, but require a lot of resources like memory or time.
-
-To catch the most common ineffective usages of the Rust language,
-a tool was created - [clippy](https://github.com/rust-lang/rust-clippy).
-
-To install `clippy` use the following commands:
-
-```bash
-rustup self update
-
-rustup component add clippy
-```
+[the official Rust website]: https://www.rust-lang.org/tools/install
+[vscode]: https://code.visualstudio.com/docs/languages/rust
+[vscode-linting]: https://code.visualstudio.com/docs/languages/rust#_linting
diff --git a/docs/RESOURCES.md b/docs/RESOURCES.md
index cd050b3ec..6d8ee7ae7 100644
--- a/docs/RESOURCES.md
+++ b/docs/RESOURCES.md
@@ -6,4 +6,4 @@
* [Stack Overflow](http://stackoverflow.com/questions/tagged/rust) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.
* The [Rust User Forum](http://users.rust-lang.org) is for general discussion about Rust.
* [/r/rust](http://www.reddit.com/r/rust/) is the official Rust subreddit.
-* [The Rust Programming Language](https://discord.gg/rust-lang) official server on [Discord](https://discordapp.com/) can be used for quick queries and discussions about the language.
+* [The Rust Programming Language](https://discord.gg/rust-lang) official server on [Discord](https://discordapp.com/) can be used for quick queries and discussions about the language.
diff --git a/docs/TESTS.md b/docs/TESTS.md
index 8529aaee1..69a3b7f94 100644
--- a/docs/TESTS.md
+++ b/docs/TESTS.md
@@ -1,8 +1,12 @@
# Writing the Code
-Write your code in `src/lib.rs`. Some exercises come with a stub file in `src/lib.rs` that will show you the signatures of the code you'll need to write. If the exercise does not come with a `src/lib.rs` file, create one.
+Write your code in `src/lib.rs`.
+The exercises come with a stub file in `src/lib.rs` that will show you the signatures of the code you'll need to write.
-The directory must be named `src` and the file must be named `lib.rs` otherwise your code will not compile. For more details, check out the rustlang book [chapter on modules](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html)
+For most exercises, it is best to write all your code in the file `src/lib.rs`.
+If you would like to split your solution into several files, consult the Rust book's [chapter on modules][chapter-modules].
+
+[chapter-modules]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
## Running Tests
@@ -12,8 +16,27 @@ To run the tests, all you need to do is run the following command:
$ cargo test
```
-Only the first test is enabled by default. After you are ready to pass the next test, remove the ignore flag from the next test (`#[ignore]`). You can also remove the flag from all the tests at once if you prefer.
+Only the first test is enabled by default.
+After you are ready to pass the next test, remove the ignore flag from the next test (`#[ignore]`).
+You can also remove the flag from all the tests at once if you prefer.
+
+Feel free to write as little code as possible to get the tests to pass.
+The test failures will guide you to what should be written next.
+
+Because Rust checks all code at compile time, you may find that your tests won't compile until you write the required code.
+Even `ignore`d tests are checked at compile time.
+You can [comment out][comments] tests that won't compile by starting each line with a `//`.
+Then, when you're ready to work on that test, you can un-comment it.
+Rust also has a special macro called `todo!()`, which you can use on unfinished code paths to make your program compile.
+
+[comments]: https://doc.rust-lang.org/book/ch03-04-comments.html
+
+## Using libraries
+
+You should be able to solve most exercises without using any external libraries.
+Nevertheless, you can add libraries (the Rust community calls them **crates**) by running `cargo add crate-name` in the directory of an exercise.
-You should try to write as little code possible to get the tests to pass. Let the test failures guide you to what should be written next.
+The automatic tests on the website only support a predefined set of crates, which can be found [here][local-registry] under the section `[dependencies]`.
+Feel free to open an issue or pull request if you would like support for a specific crate to be added.
-Because Rust checks all code at compile time you may find that your tests won't compile until you write the required code. Even `ignore`d tests are checked at compile time. You can [comment out](https://doc.rust-lang.org/book/ch03-04-comments.html) tests that won't compile by starting each line with a `//`. Then, when you're ready to work on that test, you can un-comment it.
+[local-registry]: https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
diff --git a/docs/archived/exercise-concepts/luhn.md b/docs/archived/exercise-concepts/luhn.md
index 0adece3d2..f8372c582 100644
--- a/docs/archived/exercise-concepts/luhn.md
+++ b/docs/archived/exercise-concepts/luhn.md
@@ -47,8 +47,8 @@ Canonical
```rust
pub fn is_valid(candidate: &str) -> bool {
- if candidate.chars().filter(|c| c.is_digit(10)).take(2).count() <= 1
- || candidate.chars().any(|c| !c.is_digit(10) && c != ' ')
+ if candidate.chars().filter(|c| c.is_ascii_digit()).take(2).count() <= 1
+ || candidate.chars().any(|c| !c.is_ascii_digit() && c != ' ')
{
return false;
}
diff --git a/docs/maintaining.md b/docs/maintaining.md
deleted file mode 100644
index d794de3e9..000000000
--- a/docs/maintaining.md
+++ /dev/null
@@ -1,52 +0,0 @@
-# Maintaining Notes
-
-This document captures informal policies, tips, and topics useful to maintaining the Rust track.
-
-## Internal Tooling
-
-We have a number of scripts for CI tests.
-They live in `bin/` and `_test/`.
-
-## Internal Tooling Style Guide
-
-This is non-exhaustive.
-
-- Adopt a Unix philosophy for tooling
- - prefer using tools that do one thing well
- - prefer using tools that are ubiquitous: `jq` or `sed` instead of `prettier` or `sd`
- - write scripts to do one thing well
- - prefer GNU versions of `sed` and other utilities
-- Prefer Bash for scripting
- - Strive for compatibility. macOS still distributes Bash v3.x by default, despite v5.x being current; this means that the scripts can't depend on certain features like map.
-- Scripts should use `#!/usr/bin/env bash` as their shebang
- - This increases portability on NixOS and macOS because contributors' preferred bash may not be installed in `/bin/bash`.
-- Prefer snake case for script file names
-
- ```sh
- hello_world.sh
- ```
-
- - This simplifies development when upgrading a script into a proper language. *Rusty tooling anyone?*
-- Script file names should include the `.sh` extension
-- Set the executable bit on scripts that should be called directly.
-- Scripts should set the following options at the top
-
- ```bash
- set -eo pipefail
- ```
-
-## Running CI Locally
-
-You can run CI tools locally.
-Scripts expect GNU versions of tooling, so you may see unexpected results on macOS.
-[Here](https://github.com/exercism/rust/issues/1138) is one example.
-Windows users can also run tooling locally using [WSL](https://docs.microsoft.com/en-us/windows/wsl/).
-We recommend WSL 2 with the distribution of your choice.
-
-## Maintainer Tips and Tricks
-
-Exercism tracks follow a specification that has evolved over time.
-Maintainers often need to make ad-hoc migrations to files in this repository.
-If you find yourself scripting such ad-hoc changes, include the source of your script using markdown codeblocks in a commit message.
-
-See [this commit](https://github.com/exercism/rust/commit/45eb8cc113a733636212394dee946ceff5949cc3) for an example.
diff --git a/exercises/concept/assembly-line/.gitignore b/exercises/concept/assembly-line/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/concept/assembly-line/.gitignore
+++ b/exercises/concept/assembly-line/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/concept/assembly-line/.meta/config.json b/exercises/concept/assembly-line/.meta/config.json
index a8a9750a4..bbe7a5f86 100644
--- a/exercises/concept/assembly-line/.meta/config.json
+++ b/exercises/concept/assembly-line/.meta/config.json
@@ -9,7 +9,7 @@
"Cargo.toml"
],
"test": [
- "tests/assembly-line.rs"
+ "tests/assembly_line.rs"
],
"exemplar": [
".meta/exemplar.rs"
diff --git a/exercises/concept/assembly-line/Cargo.toml b/exercises/concept/assembly-line/Cargo.toml
index 5a7f2c4ca..b9ea4ccfa 100644
--- a/exercises/concept/assembly-line/Cargo.toml
+++ b/exercises/concept/assembly-line/Cargo.toml
@@ -1,4 +1,9 @@
[package]
-name = "assembly-line"
+name = "assembly_line"
version = "0.1.0"
-edition = "2021"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/concept/assembly-line/src/lib.rs b/exercises/concept/assembly-line/src/lib.rs
index 96e01eda4..3a2a928f6 100644
--- a/exercises/concept/assembly-line/src/lib.rs
+++ b/exercises/concept/assembly-line/src/lib.rs
@@ -1,7 +1,7 @@
pub fn production_rate_per_hour(speed: u8) -> f64 {
- unimplemented!("calculate hourly production rate at speed: {speed}")
+ todo!("calculate hourly production rate at speed: {speed}")
}
pub fn working_items_per_minute(speed: u8) -> u32 {
- unimplemented!("calculate the amount of working items at speed: {speed}")
+ todo!("calculate the amount of working items at speed: {speed}")
}
diff --git a/exercises/concept/assembly-line/tests/assembly-line.rs b/exercises/concept/assembly-line/tests/assembly_line.rs
similarity index 100%
rename from exercises/concept/assembly-line/tests/assembly-line.rs
rename to exercises/concept/assembly-line/tests/assembly_line.rs
diff --git a/exercises/concept/csv-builder/.gitignore b/exercises/concept/csv-builder/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/concept/csv-builder/.gitignore
+++ b/exercises/concept/csv-builder/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/concept/csv-builder/.meta/config.json b/exercises/concept/csv-builder/.meta/config.json
index 0d8d3df92..5fd7bdc84 100644
--- a/exercises/concept/csv-builder/.meta/config.json
+++ b/exercises/concept/csv-builder/.meta/config.json
@@ -10,7 +10,7 @@
"Cargo.toml"
],
"test": [
- "tests/csv-builder.rs"
+ "tests/csv_builder.rs"
],
"exemplar": [
".meta/exemplar.rs"
diff --git a/exercises/concept/csv-builder/.meta/exemplar.rs b/exercises/concept/csv-builder/.meta/exemplar.rs
index c015adcbe..fa7bb77e0 100644
--- a/exercises/concept/csv-builder/.meta/exemplar.rs
+++ b/exercises/concept/csv-builder/.meta/exemplar.rs
@@ -16,9 +16,9 @@ impl CsvRecordBuilder {
self.content.push(',');
}
- if val.contains(",") || val.contains(r#"""#) || val.contains("\n") {
+ if val.contains(',') || val.contains('"') || val.contains('\n') {
self.content.push('"');
- self.content.push_str(&val.replace(r#"""#, r#""""#));
+ self.content.push_str(&val.replace('"', r#""""#));
self.content.push('"');
} else {
self.content.push_str(val);
@@ -30,3 +30,9 @@ impl CsvRecordBuilder {
self.content
}
}
+
+impl Default for CsvRecordBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/exercises/concept/csv-builder/Cargo.toml b/exercises/concept/csv-builder/Cargo.toml
index 4e0bfad88..cf8a4454f 100644
--- a/exercises/concept/csv-builder/Cargo.toml
+++ b/exercises/concept/csv-builder/Cargo.toml
@@ -1,4 +1,12 @@
[package]
name = "csv_builder"
version = "0.1.0"
-edition = "2021"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
+
+[lints.clippy]
+new_without_default = "allow"
diff --git a/exercises/concept/csv-builder/src/lib.rs b/exercises/concept/csv-builder/src/lib.rs
index ec5887180..4b1f94ff9 100644
--- a/exercises/concept/csv-builder/src/lib.rs
+++ b/exercises/concept/csv-builder/src/lib.rs
@@ -9,16 +9,16 @@ pub struct CsvRecordBuilder {
impl CsvRecordBuilder {
// Create a new builder
pub fn new() -> Self {
- unimplemented!("implement the `CsvRecordBuilder::new` method")
+ todo!("implement the `CsvRecordBuilder::new` method")
}
/// Adds an item to the list separated by a space and a comma.
pub fn add(&mut self, val: &str) {
- unimplemented!("implement the `CsvRecordBuilder::add` method, adding {val}")
+ todo!("implement the `CsvRecordBuilder::add` method, adding {val}")
}
/// Consumes the builder and returns the comma separated list
pub fn build(self) -> String {
- unimplemented!("implement the `CsvRecordBuilder::build` method")
+ todo!("implement the `CsvRecordBuilder::build` method")
}
}
diff --git a/exercises/concept/csv-builder/tests/csv-builder.rs b/exercises/concept/csv-builder/tests/csv_builder.rs
similarity index 91%
rename from exercises/concept/csv-builder/tests/csv-builder.rs
rename to exercises/concept/csv-builder/tests/csv_builder.rs
index ad4b4b79d..69f550cf7 100644
--- a/exercises/concept/csv-builder/tests/csv-builder.rs
+++ b/exercises/concept/csv-builder/tests/csv_builder.rs
@@ -1,7 +1,7 @@
use csv_builder::*;
#[test]
-fn test_no_escaping() {
+fn no_escaping() {
let mut builder = CsvRecordBuilder::new();
builder.add("ant");
@@ -15,7 +15,7 @@ fn test_no_escaping() {
#[test]
#[ignore]
-fn test_quote() {
+fn quote() {
let mut builder = CsvRecordBuilder::new();
builder.add("ant");
@@ -28,7 +28,7 @@ fn test_quote() {
#[test]
#[ignore]
-fn test_new_line() {
+fn new_line() {
let mut builder = CsvRecordBuilder::new();
builder.add("ant");
@@ -39,7 +39,7 @@ fn test_new_line() {
}
#[test]
#[ignore]
-fn test_comma() {
+fn comma() {
let mut builder = CsvRecordBuilder::new();
builder.add("ant");
@@ -51,7 +51,7 @@ fn test_comma() {
#[test]
#[ignore]
-fn test_empty() {
+fn empty() {
let builder = CsvRecordBuilder::new();
let list = builder.build();
assert!(list.is_empty());
diff --git a/exercises/concept/health-statistics/.docs/introduction.md b/exercises/concept/health-statistics/.docs/introduction.md
index 8894da4aa..4ab3579e9 100644
--- a/exercises/concept/health-statistics/.docs/introduction.md
+++ b/exercises/concept/health-statistics/.docs/introduction.md
@@ -32,7 +32,7 @@ Lastly, methods can be defined on structs inside of an `impl` block:
impl Item {
// initializes and returns a new instance of our Item struct
fn new() -> Self {
- unimplemented!()
+ todo!()
}
}
```
diff --git a/exercises/concept/health-statistics/.gitignore b/exercises/concept/health-statistics/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/concept/health-statistics/.gitignore
+++ b/exercises/concept/health-statistics/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/concept/health-statistics/.meta/config.json b/exercises/concept/health-statistics/.meta/config.json
index c8660b9cc..7a860b7b2 100644
--- a/exercises/concept/health-statistics/.meta/config.json
+++ b/exercises/concept/health-statistics/.meta/config.json
@@ -8,7 +8,7 @@
"Cargo.toml"
],
"test": [
- "tests/health-statistics.rs"
+ "tests/health_statistics.rs"
],
"exemplar": [
".meta/exemplar.rs"
diff --git a/exercises/concept/health-statistics/.meta/design.md b/exercises/concept/health-statistics/.meta/design.md
index 1ad4f4b22..d987a10d2 100644
--- a/exercises/concept/health-statistics/.meta/design.md
+++ b/exercises/concept/health-statistics/.meta/design.md
@@ -30,13 +30,13 @@
### Hints
-- [https://learning-rust.github.io/docs/b2.structs.html#C-like-structs](https://learning-rust.github.io/docs/b2.structs.html#C-like-structs)
+- [https://learning-rust.github.io/docs/structs/#c-like-structs](https://learning-rust.github.io/docs/structs/#c-like-structs)
- [https://doc.rust-lang.org/book/ch05-01-defining-structs.html](https://doc.rust-lang.org/book/ch05-01-defining-structs.html)
- [https://doc.rust-lang.org/book/ch05-03-method-syntax.html](https://doc.rust-lang.org/book/ch05-03-method-syntax.html)
### After
-- [https://learning-rust.github.io/docs/b2.structs.html#C-like-structs](https://learning-rust.github.io/docs/b2.structs.html#Tuple-structs)
+- [https://learning-rust.github.io/docs/structs/#tuple-structs](https://learning-rust.github.io/docs/structs/#tuple-structs)
- [https://doc.rust-lang.org/stable/rust-by-example/custom_types/structs.html](https://doc.rust-lang.org/stable/rust-by-example/custom_types/structs.html)
## Representer
diff --git a/exercises/concept/health-statistics/Cargo.toml b/exercises/concept/health-statistics/Cargo.toml
index 66b5fbc15..2374e2e21 100644
--- a/exercises/concept/health-statistics/Cargo.toml
+++ b/exercises/concept/health-statistics/Cargo.toml
@@ -1,4 +1,9 @@
[package]
name = "health_statistics"
version = "0.1.0"
-edition = "2021"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/concept/health-statistics/src/lib.rs b/exercises/concept/health-statistics/src/lib.rs
index f0e2fa0d7..c38c2b705 100644
--- a/exercises/concept/health-statistics/src/lib.rs
+++ b/exercises/concept/health-statistics/src/lib.rs
@@ -10,26 +10,26 @@ pub struct User {
impl User {
pub fn new(name: String, age: u32, weight: f32) -> Self {
- unimplemented!()
+ todo!()
}
pub fn name(&self) -> &str {
- unimplemented!()
+ todo!()
}
pub fn age(&self) -> u32 {
- unimplemented!()
+ todo!()
}
pub fn weight(&self) -> f32 {
- unimplemented!()
+ todo!()
}
pub fn set_age(&mut self, new_age: u32) {
- unimplemented!()
+ todo!()
}
pub fn set_weight(&mut self, new_weight: f32) {
- unimplemented!()
+ todo!()
}
}
diff --git a/exercises/concept/health-statistics/tests/health-statistics.rs b/exercises/concept/health-statistics/tests/health_statistics.rs
similarity index 89%
rename from exercises/concept/health-statistics/tests/health-statistics.rs
rename to exercises/concept/health-statistics/tests/health_statistics.rs
index 26ff2a79b..e933f19a4 100644
--- a/exercises/concept/health-statistics/tests/health-statistics.rs
+++ b/exercises/concept/health-statistics/tests/health_statistics.rs
@@ -5,28 +5,28 @@ const AGE: u32 = 89;
const WEIGHT: f32 = 131.6;
#[test]
-fn test_name() {
+fn name() {
let user = User::new(NAME.into(), AGE, WEIGHT);
assert_eq!(user.name(), NAME);
}
#[test]
#[ignore]
-fn test_age() {
+fn age() {
let user = User::new(NAME.into(), AGE, WEIGHT);
assert_eq!(user.age(), AGE);
}
#[test]
#[ignore]
-fn test_weight() {
+fn weight() {
let user = User::new(NAME.into(), AGE, WEIGHT);
assert!((user.weight() - WEIGHT).abs() < f32::EPSILON);
}
#[test]
#[ignore]
-fn test_set_age() {
+fn set_age() {
let new_age: u32 = 90;
let mut user = User::new(NAME.into(), AGE, WEIGHT);
user.set_age(new_age);
@@ -35,7 +35,7 @@ fn test_set_age() {
#[test]
#[ignore]
-fn test_set_weight() {
+fn set_weight() {
let new_weight: f32 = 129.4;
let mut user = User::new(NAME.into(), AGE, WEIGHT);
user.set_weight(new_weight);
diff --git a/exercises/concept/low-power-embedded-game/.docs/hints.md b/exercises/concept/low-power-embedded-game/.docs/hints.md
index e7317a614..dcf3408c2 100644
--- a/exercises/concept/low-power-embedded-game/.docs/hints.md
+++ b/exercises/concept/low-power-embedded-game/.docs/hints.md
@@ -18,6 +18,6 @@
## 3. Implement a `manhattan` method on a `Position` tuple struct
-- Don't worry about method syntax; just replacing the `unimplemented` portion within the `impl Position` block will do the right thing.
+- Don't worry about method syntax; just replacing the `todo` portion within the `impl Position` block will do the right thing.
- Consider that some values within a `Position` may be negative, but a distance is never negative.
- Calculating the absolute value is [built-in](https://doc.rust-lang.org/std/primitive.i16.html#method.abs)
diff --git a/exercises/concept/low-power-embedded-game/.gitignore b/exercises/concept/low-power-embedded-game/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/concept/low-power-embedded-game/.gitignore
+++ b/exercises/concept/low-power-embedded-game/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/concept/low-power-embedded-game/.meta/config.json b/exercises/concept/low-power-embedded-game/.meta/config.json
index 73bb27fc1..1e96232ea 100644
--- a/exercises/concept/low-power-embedded-game/.meta/config.json
+++ b/exercises/concept/low-power-embedded-game/.meta/config.json
@@ -8,7 +8,7 @@
"Cargo.toml"
],
"test": [
- "tests/low-power-embedded-game.rs"
+ "tests/low_power_embedded_game.rs"
],
"exemplar": [
".meta/exemplar.rs"
diff --git a/exercises/concept/low-power-embedded-game/Cargo.toml b/exercises/concept/low-power-embedded-game/Cargo.toml
index 8cfaf6aae..f589a4f72 100644
--- a/exercises/concept/low-power-embedded-game/Cargo.toml
+++ b/exercises/concept/low-power-embedded-game/Cargo.toml
@@ -1,4 +1,9 @@
[package]
name = "low_power_embedded_game"
version = "0.1.0"
-edition = "2021"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/concept/low-power-embedded-game/src/lib.rs b/exercises/concept/low-power-embedded-game/src/lib.rs
index 48d259d14..917cf7ec8 100644
--- a/exercises/concept/low-power-embedded-game/src/lib.rs
+++ b/exercises/concept/low-power-embedded-game/src/lib.rs
@@ -3,11 +3,11 @@
#![allow(unused)]
pub fn divmod(dividend: i16, divisor: i16) -> (i16, i16) {
- unimplemented!("implement `fn divmod`");
+ todo!("implement `fn divmod`");
}
pub fn evens(iter: impl Iterator) -> impl Iterator {
- unimplemented!("implement `fn evens`");
+ todo!("implement `fn evens`");
// TODO: remove this; it's only necessary to allow this function to compile
// before the student has done any work.
std::iter::empty()
@@ -16,6 +16,6 @@ pub fn evens(iter: impl Iterator) -> impl Iterator {
pub struct Position(pub i16, pub i16);
impl Position {
pub fn manhattan(&self) -> i16 {
- unimplemented!("implement `fn manhattan`")
+ todo!("implement `fn manhattan`")
}
}
diff --git a/exercises/concept/low-power-embedded-game/tests/low-power-embedded-game.rs b/exercises/concept/low-power-embedded-game/tests/low_power_embedded_game.rs
similarity index 100%
rename from exercises/concept/low-power-embedded-game/tests/low-power-embedded-game.rs
rename to exercises/concept/low-power-embedded-game/tests/low_power_embedded_game.rs
index 10d965e45..580c1b285 100644
--- a/exercises/concept/low-power-embedded-game/tests/low-power-embedded-game.rs
+++ b/exercises/concept/low-power-embedded-game/tests/low_power_embedded_game.rs
@@ -38,6 +38,15 @@ mod divmod {
mod evens {
use low_power_embedded_game::evens;
+ #[test]
+ #[ignore]
+ fn strs() {
+ let input = "You really must never be above joking.".split_whitespace();
+ let expected: Vec<_> = "You must be joking.".split_whitespace().collect();
+ let out: Vec<_> = evens(input).collect();
+ assert_eq!(out, expected);
+ }
+
#[test]
#[ignore]
fn simple_i32() {
@@ -58,15 +67,6 @@ mod evens {
let out: Vec = evens(1..).take(5).collect();
assert_eq!(out, &[1, 3, 5, 7, 9]);
}
-
- #[test]
- #[ignore]
- fn strs() {
- let input = "You really must never be above joking.".split_whitespace();
- let expected: Vec<_> = "You must be joking.".split_whitespace().collect();
- let out: Vec<_> = evens(input).collect();
- assert_eq!(out, expected);
- }
}
mod manhattan {
diff --git a/exercises/concept/lucians-luscious-lasagna/.docs/hints.md b/exercises/concept/lucians-luscious-lasagna/.docs/hints.md
index 36a9c10d5..28ee97c87 100644
--- a/exercises/concept/lucians-luscious-lasagna/.docs/hints.md
+++ b/exercises/concept/lucians-luscious-lasagna/.docs/hints.md
@@ -18,7 +18,7 @@
## 3. Calculate the preparation time in minutes
- You need to define a [function][functions] with a single parameter.
-- You can use the [mathematical operator for multiplicaton][operators] to multiply values.
+- You can use the [mathematical operator for multiplication][operators] to multiply values.
## 4. Calculate the elapsed time in minutes
diff --git a/exercises/concept/lucians-luscious-lasagna/.gitignore b/exercises/concept/lucians-luscious-lasagna/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/concept/lucians-luscious-lasagna/.gitignore
+++ b/exercises/concept/lucians-luscious-lasagna/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/concept/lucians-luscious-lasagna/.meta/config.json b/exercises/concept/lucians-luscious-lasagna/.meta/config.json
index 1a3417d76..129d188ee 100644
--- a/exercises/concept/lucians-luscious-lasagna/.meta/config.json
+++ b/exercises/concept/lucians-luscious-lasagna/.meta/config.json
@@ -9,7 +9,7 @@
"Cargo.toml"
],
"test": [
- "tests/lucians-luscious-lasagna.rs"
+ "tests/lucians_luscious_lasagna.rs"
],
"exemplar": [
".meta/exemplar.rs"
diff --git a/exercises/concept/lucians-luscious-lasagna/Cargo.toml b/exercises/concept/lucians-luscious-lasagna/Cargo.toml
index 6f8876654..88240adab 100644
--- a/exercises/concept/lucians-luscious-lasagna/Cargo.toml
+++ b/exercises/concept/lucians-luscious-lasagna/Cargo.toml
@@ -1,4 +1,9 @@
[package]
-name = "lucians-luscious-lasagna"
+name = "lucians_luscious_lasagna"
version = "0.1.0"
-edition = "2021"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/concept/lucians-luscious-lasagna/src/lib.rs b/exercises/concept/lucians-luscious-lasagna/src/lib.rs
index c98729b81..8d2fd2d44 100644
--- a/exercises/concept/lucians-luscious-lasagna/src/lib.rs
+++ b/exercises/concept/lucians-luscious-lasagna/src/lib.rs
@@ -1,19 +1,19 @@
pub fn expected_minutes_in_oven() -> i32 {
- unimplemented!("return expected minutes in the oven")
+ todo!("return expected minutes in the oven")
}
pub fn remaining_minutes_in_oven(actual_minutes_in_oven: i32) -> i32 {
- unimplemented!(
+ todo!(
"calculate remaining minutes in oven given actual minutes in oven: {actual_minutes_in_oven}"
)
}
pub fn preparation_time_in_minutes(number_of_layers: i32) -> i32 {
- unimplemented!("calculate preparation time in minutes for number of layers: {number_of_layers}")
+ todo!("calculate preparation time in minutes for number of layers: {number_of_layers}")
}
pub fn elapsed_time_in_minutes(number_of_layers: i32, actual_minutes_in_oven: i32) -> i32 {
- unimplemented!(
+ todo!(
"calculate elapsed time in minutes for number of layers {number_of_layers} and actual minutes in oven {actual_minutes_in_oven}"
)
}
diff --git a/exercises/concept/lucians-luscious-lasagna/tests/lucians-luscious-lasagna.rs b/exercises/concept/lucians-luscious-lasagna/tests/lucians_luscious_lasagna.rs
similarity index 100%
rename from exercises/concept/lucians-luscious-lasagna/tests/lucians-luscious-lasagna.rs
rename to exercises/concept/lucians-luscious-lasagna/tests/lucians_luscious_lasagna.rs
diff --git a/exercises/concept/magazine-cutout/.docs/instructions.md b/exercises/concept/magazine-cutout/.docs/instructions.md
index 9d47432d9..65433ec4a 100644
--- a/exercises/concept/magazine-cutout/.docs/instructions.md
+++ b/exercises/concept/magazine-cutout/.docs/instructions.md
@@ -13,7 +13,7 @@ You'll start with the following stubbed function signature:
```rust
pub fn can_construct_note(magazine: &[&str], note: &[&str]) -> bool {
- unimplemented!()
+ todo!()
}
```
@@ -27,7 +27,7 @@ assert!(!can_construct_note(&magazine, ¬e));
The function returns `false` since the magazine only contains one instance of `"two"` when the note requires two of them.
-The following input will succeed:
+The following input will succeed:
```rust
let magazine = "Astronomer Amy Mainzer spent hours chatting with Leonardo DiCaprio for Netflix's 'Don't Look Up'".split_whitespace().collect::>();
diff --git a/exercises/concept/magazine-cutout/.gitignore b/exercises/concept/magazine-cutout/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/concept/magazine-cutout/.gitignore
+++ b/exercises/concept/magazine-cutout/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/concept/magazine-cutout/.meta/config.json b/exercises/concept/magazine-cutout/.meta/config.json
index 4afc9ebc0..39e0a7101 100644
--- a/exercises/concept/magazine-cutout/.meta/config.json
+++ b/exercises/concept/magazine-cutout/.meta/config.json
@@ -8,7 +8,7 @@
"Cargo.toml"
],
"test": [
- "tests/magazine-cutout.rs"
+ "tests/magazine_cutout.rs"
],
"exemplar": [
".meta/exemplar.rs"
diff --git a/exercises/concept/magazine-cutout/Cargo.toml b/exercises/concept/magazine-cutout/Cargo.toml
index 2ad12cef8..358fc33b7 100644
--- a/exercises/concept/magazine-cutout/Cargo.toml
+++ b/exercises/concept/magazine-cutout/Cargo.toml
@@ -1,4 +1,9 @@
[package]
name = "magazine_cutout"
version = "0.1.0"
-edition = "2021"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/concept/magazine-cutout/src/lib.rs b/exercises/concept/magazine-cutout/src/lib.rs
index eb1876e28..cbe23f634 100644
--- a/exercises/concept/magazine-cutout/src/lib.rs
+++ b/exercises/concept/magazine-cutout/src/lib.rs
@@ -5,5 +5,5 @@
use std::collections::HashMap;
pub fn can_construct_note(magazine: &[&str], note: &[&str]) -> bool {
- unimplemented!()
+ todo!()
}
diff --git a/exercises/concept/magazine-cutout/tests/magazine-cutout.rs b/exercises/concept/magazine-cutout/tests/magazine_cutout.rs
similarity index 86%
rename from exercises/concept/magazine-cutout/tests/magazine-cutout.rs
rename to exercises/concept/magazine-cutout/tests/magazine_cutout.rs
index 2062d1d92..0b73c7a02 100644
--- a/exercises/concept/magazine-cutout/tests/magazine-cutout.rs
+++ b/exercises/concept/magazine-cutout/tests/magazine_cutout.rs
@@ -1,7 +1,7 @@
use magazine_cutout::*;
#[test]
-fn test_magazine_has_fewer_words_available_than_needed() {
+fn magazine_has_fewer_words_available_than_needed() {
let magazine = "two times three is not four"
.split_whitespace()
.collect::>();
@@ -13,7 +13,7 @@ fn test_magazine_has_fewer_words_available_than_needed() {
#[test]
#[ignore]
-fn test_fn_returns_true_for_good_input() {
+fn fn_returns_true_for_good_input() {
let magazine = "The metro orchestra unveiled its new grand piano today. Its donor paraphrased Nathn Hale: \"I only regret that I have but one to give \"".split_whitespace().collect::>();
let note = "give one grand today."
.split_whitespace()
@@ -23,7 +23,7 @@ fn test_fn_returns_true_for_good_input() {
#[test]
#[ignore]
-fn test_fn_returns_false_for_bad_input() {
+fn fn_returns_false_for_bad_input() {
let magazine = "I've got a lovely bunch of coconuts."
.split_whitespace()
.collect::>();
@@ -35,7 +35,7 @@ fn test_fn_returns_false_for_bad_input() {
#[test]
#[ignore]
-fn test_case_sensitivity() {
+fn case_sensitivity() {
let magazine = "i've got some lovely coconuts"
.split_whitespace()
.collect::>();
@@ -55,7 +55,7 @@ fn test_case_sensitivity() {
#[test]
#[ignore]
-fn test_magazine_has_more_words_available_than_needed() {
+fn magazine_has_more_words_available_than_needed() {
let magazine = "Enough is enough when enough is enough"
.split_whitespace()
.collect::>();
@@ -65,7 +65,7 @@ fn test_magazine_has_more_words_available_than_needed() {
#[test]
#[ignore]
-fn test_magazine_has_one_good_word_many_times_but_still_cant_construct() {
+fn magazine_has_one_good_word_many_times_but_still_cant_construct() {
let magazine = "A A A".split_whitespace().collect::>();
let note = "A nice day".split_whitespace().collect::>();
assert!(!can_construct_note(&magazine, ¬e));
diff --git a/exercises/concept/resistor-color/.gitignore b/exercises/concept/resistor-color/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/concept/resistor-color/.gitignore
+++ b/exercises/concept/resistor-color/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/concept/resistor-color/.meta/config.json b/exercises/concept/resistor-color/.meta/config.json
index 7d5443d1d..2c08f12b0 100644
--- a/exercises/concept/resistor-color/.meta/config.json
+++ b/exercises/concept/resistor-color/.meta/config.json
@@ -11,7 +11,7 @@
"Cargo.toml"
],
"test": [
- "tests/resistor-color.rs"
+ "tests/resistor_color.rs"
],
"exemplar": [
".meta/exemplar.rs"
diff --git a/exercises/concept/resistor-color/.meta/exemplar.rs b/exercises/concept/resistor-color/.meta/exemplar.rs
index 61f9baf05..b8ecbde33 100644
--- a/exercises/concept/resistor-color/.meta/exemplar.rs
+++ b/exercises/concept/resistor-color/.meta/exemplar.rs
@@ -1,4 +1,4 @@
-use enum_iterator::{all, Sequence};
+use enum_iterator::{Sequence, all};
use int_enum::IntEnum;
#[repr(usize)]
@@ -17,11 +17,11 @@ pub enum ResistorColor {
}
pub fn color_to_value(color: ResistorColor) -> usize {
- color.int_value()
+ color.into()
}
pub fn value_to_color_string(value: usize) -> String {
- ResistorColor::from_int(value)
+ ResistorColor::try_from(value)
.map(|color| format!("{color:?}"))
.unwrap_or_else(|_| "value out of range".into())
}
diff --git a/exercises/concept/resistor-color/Cargo.toml b/exercises/concept/resistor-color/Cargo.toml
index 8c7709d35..c6bc363f4 100644
--- a/exercises/concept/resistor-color/Cargo.toml
+++ b/exercises/concept/resistor-color/Cargo.toml
@@ -1,8 +1,11 @@
[package]
-edition = "2021"
-name = "resistor-color"
-version = "1.0.0"
+name = "resistor_color"
+version = "0.1.0"
+edition = "2024"
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
[dependencies]
-int-enum = "0.5.0"
-enum-iterator = "1.2.0"
+enum-iterator = "2.0"
+int-enum = "1.0"
diff --git a/exercises/concept/resistor-color/src/lib.rs b/exercises/concept/resistor-color/src/lib.rs
index 96a5f1033..ff4c92267 100644
--- a/exercises/concept/resistor-color/src/lib.rs
+++ b/exercises/concept/resistor-color/src/lib.rs
@@ -13,13 +13,13 @@ pub enum ResistorColor {
}
pub fn color_to_value(color: ResistorColor) -> u32 {
- unimplemented!("convert color {color:?} into a numerical representation")
+ todo!("convert color {color:?} into a numerical representation")
}
pub fn value_to_color_string(value: u32) -> String {
- unimplemented!("convert the value {value} into a string representation of color")
+ todo!("convert the value {value} into a string representation of color")
}
pub fn colors() -> Vec {
- unimplemented!("return a list of all the colors ordered by resistance")
+ todo!("return a list of all the colors ordered by resistance")
}
diff --git a/exercises/concept/resistor-color/tests/resistor-color.rs b/exercises/concept/resistor-color/tests/resistor_color.rs
similarity index 68%
rename from exercises/concept/resistor-color/tests/resistor-color.rs
rename to exercises/concept/resistor-color/tests/resistor_color.rs
index 1e6523b12..1c20e3217 100644
--- a/exercises/concept/resistor-color/tests/resistor-color.rs
+++ b/exercises/concept/resistor-color/tests/resistor_color.rs
@@ -1,43 +1,43 @@
-use resistor_color::{color_to_value, colors, value_to_color_string, ResistorColor};
+use resistor_color::{ResistorColor, color_to_value, colors, value_to_color_string};
#[test]
-fn test_black() {
+fn black() {
assert_eq!(color_to_value(ResistorColor::Black), 0);
}
#[test]
#[ignore]
-fn test_orange() {
+fn orange() {
assert_eq!(color_to_value(ResistorColor::Orange), 3);
}
#[test]
#[ignore]
-fn test_white() {
+fn white() {
assert_eq!(color_to_value(ResistorColor::White), 9);
}
#[test]
#[ignore]
-fn test_2() {
+fn two() {
assert_eq!(value_to_color_string(2), String::from("Red"));
}
#[test]
#[ignore]
-fn test_6() {
+fn six() {
assert_eq!(value_to_color_string(6), String::from("Blue"));
}
#[test]
#[ignore]
-fn test_8() {
+fn eight() {
assert_eq!(value_to_color_string(8), String::from("Grey"));
}
#[test]
#[ignore]
-fn test_11_out_of_range() {
+fn eleven_out_of_range() {
assert_eq!(
value_to_color_string(11),
String::from("value out of range")
@@ -46,10 +46,12 @@ fn test_11_out_of_range() {
#[test]
#[ignore]
-fn test_all_colors() {
+fn all_colors() {
use ResistorColor::*;
assert_eq!(
colors(),
- vec![Black, Brown, Red, Orange, Yellow, Green, Blue, Violet, Grey, White]
+ vec![
+ Black, Brown, Red, Orange, Yellow, Green, Blue, Violet, Grey, White
+ ]
);
}
diff --git a/exercises/concept/role-playing-game/.docs/introduction.md b/exercises/concept/role-playing-game/.docs/introduction.md
index 3ab3b1cf4..53b3e9f6a 100644
--- a/exercises/concept/role-playing-game/.docs/introduction.md
+++ b/exercises/concept/role-playing-game/.docs/introduction.md
@@ -2,10 +2,10 @@
## Null-References
-If you have ever used another programming language (C/C++, Python, Java, Ruby, Lisp, etc.), it is likely that you have encountered `null` or `nil` before.
-The use of `null` or `nil` is the way that these languages indicate that a particular variable has no value.
-However, this makes accidentally using a variable that points to `null` an easy (and frequent) mistake to make.
-As you might imagine, trying to call a function that isn't there, or access a value that doesn't exist can lead to all sorts of bugs and crashes.
+If you have ever used another programming language (C/C++, Python, Java, Ruby, Lisp, etc.), it is likely that you have encountered `null` or `nil` before.
+The use of `null` or `nil` is the way that these languages indicate that a particular variable has no value.
+However, this makes accidentally using a variable that points to `null` an easy (and frequent) mistake to make.
+As you might imagine, trying to call a function that isn't there, or access a value that doesn't exist can lead to all sorts of bugs and crashes.
The creator of `null` went so far as to call it his ['billion-dollar mistake'.](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/)
## The `Option` Type
diff --git a/exercises/concept/role-playing-game/.gitignore b/exercises/concept/role-playing-game/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/concept/role-playing-game/.gitignore
+++ b/exercises/concept/role-playing-game/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/concept/role-playing-game/.meta/config.json b/exercises/concept/role-playing-game/.meta/config.json
index b7ab9560e..bb8817b10 100644
--- a/exercises/concept/role-playing-game/.meta/config.json
+++ b/exercises/concept/role-playing-game/.meta/config.json
@@ -12,7 +12,7 @@
"Cargo.toml"
],
"test": [
- "tests/role-playing-game.rs"
+ "tests/role_playing_game.rs"
],
"exemplar": [
".meta/exemplar.rs"
diff --git a/exercises/concept/role-playing-game/.meta/design.md b/exercises/concept/role-playing-game/.meta/design.md
index a4d584d8e..c10916351 100644
--- a/exercises/concept/role-playing-game/.meta/design.md
+++ b/exercises/concept/role-playing-game/.meta/design.md
@@ -34,7 +34,7 @@ The concepts this exercise unlocks are:
-
-
--
+-
- [The Billion-Dollar Mistake](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/)
### After
diff --git a/exercises/concept/role-playing-game/Cargo.toml b/exercises/concept/role-playing-game/Cargo.toml
index ab9f24f21..4773e0985 100644
--- a/exercises/concept/role-playing-game/Cargo.toml
+++ b/exercises/concept/role-playing-game/Cargo.toml
@@ -1,4 +1,9 @@
[package]
name = "role_playing_game"
version = "0.1.0"
-edition = "2021"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/concept/role-playing-game/src/lib.rs b/exercises/concept/role-playing-game/src/lib.rs
index effc3d614..1bdc203e1 100644
--- a/exercises/concept/role-playing-game/src/lib.rs
+++ b/exercises/concept/role-playing-game/src/lib.rs
@@ -6,10 +6,10 @@ pub struct Player {
impl Player {
pub fn revive(&self) -> Option {
- unimplemented!("Revive this player")
+ todo!("Revive this player")
}
pub fn cast_spell(&mut self, mana_cost: u32) -> u32 {
- unimplemented!("Cast a spell of cost {mana_cost}")
+ todo!("Cast a spell of cost {mana_cost}")
}
}
diff --git a/exercises/concept/role-playing-game/tests/role-playing-game.rs b/exercises/concept/role-playing-game/tests/role_playing_game.rs
similarity index 90%
rename from exercises/concept/role-playing-game/tests/role-playing-game.rs
rename to exercises/concept/role-playing-game/tests/role_playing_game.rs
index 499320746..ac7ad71dc 100644
--- a/exercises/concept/role-playing-game/tests/role-playing-game.rs
+++ b/exercises/concept/role-playing-game/tests/role_playing_game.rs
@@ -1,7 +1,7 @@
use role_playing_game::*;
#[test]
-fn test_reviving_dead_player() {
+fn reviving_dead_player() {
let dead_player = Player {
health: 0,
mana: Some(0),
@@ -17,7 +17,7 @@ fn test_reviving_dead_player() {
#[test]
#[ignore]
-fn test_reviving_dead_level9_player() {
+fn reviving_dead_level9_player() {
let dead_player = Player {
health: 0,
mana: None,
@@ -33,7 +33,7 @@ fn test_reviving_dead_level9_player() {
#[test]
#[ignore]
-fn test_reviving_dead_level10_player() {
+fn reviving_dead_level10_player() {
let dead_player = Player {
health: 0,
mana: Some(0),
@@ -49,7 +49,7 @@ fn test_reviving_dead_level10_player() {
#[test]
#[ignore]
-fn test_reviving_alive_player() {
+fn reviving_alive_player() {
let alive_player = Player {
health: 1,
mana: None,
@@ -60,7 +60,7 @@ fn test_reviving_alive_player() {
#[test]
#[ignore]
-fn test_cast_spell_with_enough_mana() {
+fn cast_spell_with_enough_mana() {
const HEALTH: u32 = 99;
const MANA: u32 = 100;
const LEVEL: u32 = 100;
@@ -80,7 +80,7 @@ fn test_cast_spell_with_enough_mana() {
#[test]
#[ignore]
-fn test_cast_spell_with_insufficient_mana() {
+fn cast_spell_with_insufficient_mana() {
let mut no_mana_wizard = Player {
health: 56,
mana: Some(2),
@@ -99,7 +99,7 @@ fn test_cast_spell_with_insufficient_mana() {
#[test]
#[ignore]
-fn test_cast_spell_with_no_mana_pool() {
+fn cast_spell_with_no_mana_pool() {
const MANA_COST: u32 = 10;
let mut underleveled_player = Player {
@@ -120,7 +120,7 @@ fn test_cast_spell_with_no_mana_pool() {
#[test]
#[ignore]
-fn test_cast_large_spell_with_no_mana_pool() {
+fn cast_large_spell_with_no_mana_pool() {
const MANA_COST: u32 = 30;
let mut underleveled_player = Player {
diff --git a/exercises/concept/rpn-calculator/.docs/instructions.md b/exercises/concept/rpn-calculator/.docs/instructions.md
index a749c9515..b6c58751d 100644
--- a/exercises/concept/rpn-calculator/.docs/instructions.md
+++ b/exercises/concept/rpn-calculator/.docs/instructions.md
@@ -105,7 +105,7 @@ pub enum CalculatorInput {
}
pub fn evaluate(inputs: &[CalculatorInput]) -> Option {
- unimplemented!(
+ todo!(
"Given the inputs: {inputs:?}, evaluate them as though they were a Reverse Polish notation expression"
);
}
diff --git a/exercises/concept/rpn-calculator/.gitignore b/exercises/concept/rpn-calculator/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/concept/rpn-calculator/.gitignore
+++ b/exercises/concept/rpn-calculator/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/concept/rpn-calculator/.meta/config.json b/exercises/concept/rpn-calculator/.meta/config.json
index 61b4cca78..6f1a89c4f 100644
--- a/exercises/concept/rpn-calculator/.meta/config.json
+++ b/exercises/concept/rpn-calculator/.meta/config.json
@@ -8,7 +8,7 @@
"Cargo.toml"
],
"test": [
- "tests/rpn-calculator.rs"
+ "tests/rpn_calculator.rs"
],
"exemplar": [
".meta/exemplar.rs"
diff --git a/exercises/concept/rpn-calculator/.meta/exemplar.rs b/exercises/concept/rpn-calculator/.meta/exemplar.rs
index 30e4d9a18..2fc63b9ee 100644
--- a/exercises/concept/rpn-calculator/.meta/exemplar.rs
+++ b/exercises/concept/rpn-calculator/.meta/exemplar.rs
@@ -39,9 +39,5 @@ pub fn evaluate(inputs: &[CalculatorInput]) -> Option {
}
let output = stack.pop();
- if stack.is_empty() {
- output
- } else {
- None
- }
+ if stack.is_empty() { output } else { None }
}
diff --git a/exercises/concept/rpn-calculator/Cargo.toml b/exercises/concept/rpn-calculator/Cargo.toml
index 99ae2cf09..c712676eb 100644
--- a/exercises/concept/rpn-calculator/Cargo.toml
+++ b/exercises/concept/rpn-calculator/Cargo.toml
@@ -1,4 +1,9 @@
[package]
name = "rpn_calculator"
version = "0.1.0"
-edition = "2021"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/concept/rpn-calculator/src/lib.rs b/exercises/concept/rpn-calculator/src/lib.rs
index 0e0011f66..f740777eb 100644
--- a/exercises/concept/rpn-calculator/src/lib.rs
+++ b/exercises/concept/rpn-calculator/src/lib.rs
@@ -8,7 +8,7 @@ pub enum CalculatorInput {
}
pub fn evaluate(inputs: &[CalculatorInput]) -> Option {
- unimplemented!(
+ todo!(
"Given the inputs: {inputs:?}, evaluate them as though they were a Reverse Polish notation expression"
);
}
diff --git a/exercises/concept/rpn-calculator/tests/rpn-calculator.rs b/exercises/concept/rpn-calculator/tests/rpn_calculator.rs
similarity index 80%
rename from exercises/concept/rpn-calculator/tests/rpn-calculator.rs
rename to exercises/concept/rpn-calculator/tests/rpn_calculator.rs
index eba9c5f96..e33d058be 100644
--- a/exercises/concept/rpn-calculator/tests/rpn-calculator.rs
+++ b/exercises/concept/rpn-calculator/tests/rpn_calculator.rs
@@ -13,77 +13,77 @@ fn calculator_input(s: &str) -> Vec {
}
#[test]
-fn test_empty_input_returns_none() {
+fn empty_input_returns_none() {
let input = calculator_input("");
assert_eq!(evaluate(&input), None);
}
#[test]
#[ignore]
-fn test_simple_value() {
+fn simple_value() {
let input = calculator_input("10");
assert_eq!(evaluate(&input), Some(10));
}
#[test]
#[ignore]
-fn test_simple_addition() {
+fn simple_addition() {
let input = calculator_input("2 2 +");
assert_eq!(evaluate(&input), Some(4));
}
#[test]
#[ignore]
-fn test_simple_subtraction() {
+fn simple_subtraction() {
let input = calculator_input("7 11 -");
assert_eq!(evaluate(&input), Some(-4));
}
#[test]
#[ignore]
-fn test_simple_multiplication() {
+fn simple_multiplication() {
let input = calculator_input("6 9 *");
assert_eq!(evaluate(&input), Some(54));
}
#[test]
#[ignore]
-fn test_simple_division() {
+fn simple_division() {
let input = calculator_input("57 19 /");
assert_eq!(evaluate(&input), Some(3));
}
#[test]
#[ignore]
-fn test_complex_operation() {
+fn complex_operation() {
let input = calculator_input("4 8 + 7 5 - /");
assert_eq!(evaluate(&input), Some(6));
}
#[test]
#[ignore]
-fn test_too_few_operands_returns_none() {
+fn too_few_operands_returns_none() {
let input = calculator_input("2 +");
assert_eq!(evaluate(&input), None);
}
#[test]
#[ignore]
-fn test_too_many_operands_returns_none() {
+fn too_many_operands_returns_none() {
let input = calculator_input("2 2");
assert_eq!(evaluate(&input), None);
}
#[test]
#[ignore]
-fn test_zero_operands_returns_none() {
+fn zero_operands_returns_none() {
let input = calculator_input("+");
assert_eq!(evaluate(&input), None);
}
#[test]
#[ignore]
-fn test_intermediate_error_returns_none() {
+fn intermediate_error_returns_none() {
let input = calculator_input("+ 2 2 *");
assert_eq!(evaluate(&input), None);
}
diff --git a/exercises/concept/semi-structured-logs/.gitignore b/exercises/concept/semi-structured-logs/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/concept/semi-structured-logs/.gitignore
+++ b/exercises/concept/semi-structured-logs/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/concept/semi-structured-logs/.meta/config.json b/exercises/concept/semi-structured-logs/.meta/config.json
index 6d9300eaa..0df38abeb 100644
--- a/exercises/concept/semi-structured-logs/.meta/config.json
+++ b/exercises/concept/semi-structured-logs/.meta/config.json
@@ -8,7 +8,7 @@
"Cargo.toml"
],
"test": [
- "tests/semi-structured-logs.rs"
+ "tests/semi_structured_logs.rs"
],
"exemplar": [
".meta/exemplar.rs"
diff --git a/exercises/concept/semi-structured-logs/Cargo.toml b/exercises/concept/semi-structured-logs/Cargo.toml
index 166e966a1..a12cc4518 100644
--- a/exercises/concept/semi-structured-logs/Cargo.toml
+++ b/exercises/concept/semi-structured-logs/Cargo.toml
@@ -1,9 +1,12 @@
[package]
name = "semi_structured_logs"
version = "0.1.0"
-edition = "2021"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
[features]
add-a-variant = []
-
-[dependencies]
diff --git a/exercises/concept/semi-structured-logs/src/lib.rs b/exercises/concept/semi-structured-logs/src/lib.rs
index 91b0560f1..07916f96e 100644
--- a/exercises/concept/semi-structured-logs/src/lib.rs
+++ b/exercises/concept/semi-structured-logs/src/lib.rs
@@ -11,14 +11,14 @@ pub enum LogLevel {
}
/// primary function for emitting logs
pub fn log(level: LogLevel, message: &str) -> String {
- unimplemented!("return a message for the given log level")
+ todo!("return a message for the given log level")
}
pub fn info(message: &str) -> String {
- unimplemented!("return a message for info log level")
+ todo!("return a message for info log level")
}
pub fn warn(message: &str) -> String {
- unimplemented!("return a message for warn log level")
+ todo!("return a message for warn log level")
}
pub fn error(message: &str) -> String {
- unimplemented!("return a message for error log level")
+ todo!("return a message for error log level")
}
diff --git a/exercises/concept/semi-structured-logs/tests/semi-structured-logs.rs b/exercises/concept/semi-structured-logs/tests/semi_structured_logs.rs
similarity index 94%
rename from exercises/concept/semi-structured-logs/tests/semi-structured-logs.rs
rename to exercises/concept/semi-structured-logs/tests/semi_structured_logs.rs
index 04168b139..65eb4d5c9 100644
--- a/exercises/concept/semi-structured-logs/tests/semi-structured-logs.rs
+++ b/exercises/concept/semi-structured-logs/tests/semi_structured_logs.rs
@@ -1,4 +1,4 @@
-use semi_structured_logs::{error, info, log, warn, LogLevel};
+use semi_structured_logs::{LogLevel, error, info, log, warn};
#[test]
fn emits_info() {
diff --git a/exercises/concept/short-fibonacci/.docs/instructions.md b/exercises/concept/short-fibonacci/.docs/instructions.md
index 926d5e848..4b3d57af5 100644
--- a/exercises/concept/short-fibonacci/.docs/instructions.md
+++ b/exercises/concept/short-fibonacci/.docs/instructions.md
@@ -7,6 +7,7 @@ The Fibonacci sequence is a set of numbers where the next element is the sum of
## 1. Create a buffer of `count` zeroes.
Create a function that creates a buffer of `count` zeroes.
+
```rust
let my_buffer = create_buffer(5);
// [0, 0, 0, 0, 0]
@@ -14,8 +15,9 @@ let my_buffer = create_buffer(5);
## 2. List the first five elements of the Fibonacci sequence
-Create a function that returns the first five numbers of the Fibonacci sequence.
+Create a function that returns the first five numbers of the Fibonacci sequence.
Its first five elements are `1, 1, 2, 3, 5`
+
```rust
let first_five = fibonacci();
// [1, 1, 2, 3, 5]
diff --git a/exercises/concept/short-fibonacci/.gitignore b/exercises/concept/short-fibonacci/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/concept/short-fibonacci/.gitignore
+++ b/exercises/concept/short-fibonacci/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/concept/short-fibonacci/.meta/config.json b/exercises/concept/short-fibonacci/.meta/config.json
index 9da7ea638..e3b2fc09f 100644
--- a/exercises/concept/short-fibonacci/.meta/config.json
+++ b/exercises/concept/short-fibonacci/.meta/config.json
@@ -9,7 +9,7 @@
"Cargo.toml"
],
"test": [
- "tests/short-fibonacci.rs"
+ "tests/short_fibonacci.rs"
],
"exemplar": [
".meta/exemplar.rs"
diff --git a/exercises/concept/short-fibonacci/Cargo.toml b/exercises/concept/short-fibonacci/Cargo.toml
index d2ee550cc..1740abd20 100644
--- a/exercises/concept/short-fibonacci/Cargo.toml
+++ b/exercises/concept/short-fibonacci/Cargo.toml
@@ -1,4 +1,9 @@
[package]
name = "short_fibonacci"
version = "0.1.0"
-edition = "2021"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/concept/short-fibonacci/src/lib.rs b/exercises/concept/short-fibonacci/src/lib.rs
index 2d5dcbc5c..0db5a8feb 100644
--- a/exercises/concept/short-fibonacci/src/lib.rs
+++ b/exercises/concept/short-fibonacci/src/lib.rs
@@ -1,13 +1,13 @@
/// Create an empty vector
pub fn create_empty() -> Vec {
- unimplemented!()
+ todo!()
}
/// Create a buffer of `count` zeroes.
///
/// Applications often use buffers when serializing data to send over the network.
pub fn create_buffer(count: usize) -> Vec {
- unimplemented!("create a zeroized buffer of {count} bytes")
+ todo!("create a zeroized buffer of {count} bytes")
}
/// Create a vector containing the first five elements of the Fibonacci sequence.
@@ -15,5 +15,5 @@ pub fn create_buffer(count: usize) -> Vec {
/// Fibonacci's sequence is the list of numbers where the next number is a sum of the previous two.
/// Its first five elements are `1, 1, 2, 3, 5`.
pub fn fibonacci() -> Vec {
- unimplemented!()
+ todo!()
}
diff --git a/exercises/concept/short-fibonacci/tests/short-fibonacci.rs b/exercises/concept/short-fibonacci/tests/short_fibonacci.rs
similarity index 89%
rename from exercises/concept/short-fibonacci/tests/short-fibonacci.rs
rename to exercises/concept/short-fibonacci/tests/short_fibonacci.rs
index db6b2eb85..a0fb1a84d 100644
--- a/exercises/concept/short-fibonacci/tests/short-fibonacci.rs
+++ b/exercises/concept/short-fibonacci/tests/short_fibonacci.rs
@@ -1,21 +1,23 @@
use short_fibonacci::*;
#[test]
-fn test_empty() {
+fn empty() {
assert_eq!(create_empty(), Vec::new());
}
+
#[test]
#[ignore]
-fn test_buffer() {
+fn buffer() {
for n in 0..10 {
let zeroized = create_buffer(n);
assert_eq!(zeroized.len(), n);
assert!(zeroized.iter().all(|&v| v == 0));
}
}
+
#[test]
#[ignore]
-fn test_fibonacci() {
+fn first_five_elements() {
let fibb = fibonacci();
assert_eq!(fibb.len(), 5);
assert_eq!(fibb[0], 1);
diff --git a/exercises/practice/accumulate/.docs/hints.md b/exercises/practice/accumulate/.docs/hints.md
new file mode 100644
index 000000000..6a6ecdf29
--- /dev/null
+++ b/exercises/practice/accumulate/.docs/hints.md
@@ -0,0 +1,10 @@
+## General
+
+It may help to look at the Fn\* traits:
+[Fn](https://doc.rust-lang.org/std/ops/trait.Fn.html),
+[FnMut](https://doc.rust-lang.org/std/ops/trait.FnMut.html) and
+[FnOnce](https://doc.rust-lang.org/std/ops/trait.FnOnce.html).
+
+Help with passing a closure into a function may be found in
+the ["closures as input parameters" section](https://doc.rust-lang.org/stable/rust-by-example/fn/closures/input_parameters.html) of
+[Rust by Example](https://doc.rust-lang.org/stable/rust-by-example/).
diff --git a/exercises/practice/accumulate/.docs/instructions.append.md b/exercises/practice/accumulate/.docs/instructions.append.md
index 3d79940ff..3cdb30c2c 100644
--- a/exercises/practice/accumulate/.docs/instructions.append.md
+++ b/exercises/practice/accumulate/.docs/instructions.append.md
@@ -1,14 +1,6 @@
-# Hints
+# Instructions append
-It may help to look at the Fn\* traits:
-[Fn](https://doc.rust-lang.org/std/ops/trait.Fn.html),
-[FnMut](https://doc.rust-lang.org/std/ops/trait.FnMut.html) and
-[FnOnce](https://doc.rust-lang.org/std/ops/trait.FnOnce.html).
+## Workflow
-Help with passing a closure into a function may be found in
-the ["closures as input parameters" section](https://doc.rust-lang.org/stable/rust-by-example/fn/closures/input_parameters.html) of
-[Rust by Example](https://doc.rust-lang.org/stable/rust-by-example/).
-
-The tests for this exercise will cause compile time errors,
-if your function signature does not fit them, even when they're not run.
+The tests for this exercise will cause compile time errors if your function signature does not fit them, even when they're not run.
You may want to comment some tests out and generalize your solution piece by piece.
diff --git a/exercises/practice/accumulate/.docs/instructions.md b/exercises/practice/accumulate/.docs/instructions.md
index 435e0b324..c25a03fab 100644
--- a/exercises/practice/accumulate/.docs/instructions.md
+++ b/exercises/practice/accumulate/.docs/instructions.md
@@ -1,9 +1,6 @@
# Instructions
-Implement the `accumulate` operation, which, given a collection and an
-operation to perform on each element of the collection, returns a new
-collection containing the result of applying that operation to each element of
-the input collection.
+Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection.
Given the collection of numbers:
@@ -21,6 +18,5 @@ Check out the test suite to see the expected function signature.
## Restrictions
-Keep your hands off that collect/map/fmap/whatchamacallit functionality
-provided by your standard library!
+Keep your hands off that collect/map/fmap/whatchamacallit functionality provided by your standard library!
Solve this one yourself using other basic tools instead.
diff --git a/exercises/practice/accumulate/.gitignore b/exercises/practice/accumulate/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/accumulate/.gitignore
+++ b/exercises/practice/accumulate/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/accumulate/.meta/config.json b/exercises/practice/accumulate/.meta/config.json
index 63054a41e..3864fa9c9 100644
--- a/exercises/practice/accumulate/.meta/config.json
+++ b/exercises/practice/accumulate/.meta/config.json
@@ -32,8 +32,8 @@
},
"blurb": "Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection.",
"source": "Conversation with James Edward Gray II",
- "source_url": "https://twitter.com/jeg2",
+ "source_url": "http://graysoftinc.com/",
"custom": {
- "allowed-to-not-compile": "Stub doesn't compile because there is no type specified for _function and _closure arguments. This exercise teaches students about the use of function pointers and closures, therefore specifying the type of arguments for them would reduce learning."
+ "allowed-to-not-compile": "Stub doesn't compile because there is no type specified for the `function` argument. This exercise teaches students about the use of function pointers and closures, therefore specifying the type of arguments for them would reduce learning."
}
}
diff --git a/exercises/practice/accumulate/.meta/tests.toml b/exercises/practice/accumulate/.meta/tests.toml
index be690e975..d7858e07b 100644
--- a/exercises/practice/accumulate/.meta/tests.toml
+++ b/exercises/practice/accumulate/.meta/tests.toml
@@ -1,3 +1,30 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[64d97c14-36dd-44a8-9621-2cecebd6ed23]
+description = "accumulate empty"
+
+[00008ed2-4651-4929-8c08-8b4dbd70872e]
+description = "accumulate squares"
+
+[551016da-4396-4cae-b0ec-4c3a1a264125]
+description = "accumulate upcases"
+
+[cdf95597-b6ec-4eac-a838-3480d13d0d05]
+description = "accumulate reversed strings"
+
+[bee8e9b6-b16f-4cd2-be3b-ccf7457e50bb]
+description = "accumulate recursively"
+include = false
+
+[0b357334-4cad-49e1-a741-425202edfc7c]
+description = "accumulate recursively"
+reimplements = "bee8e9b6-b16f-4cd2-be3b-ccf7457e50bb"
diff --git a/exercises/practice/accumulate/Cargo.toml b/exercises/practice/accumulate/Cargo.toml
index e9add962f..05f075462 100644
--- a/exercises/practice/accumulate/Cargo.toml
+++ b/exercises/practice/accumulate/Cargo.toml
@@ -1,6 +1,9 @@
[package]
-edition = "2021"
name = "accumulate"
-version = "0.0.0"
+version = "0.1.0"
+edition = "2024"
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
[dependencies]
diff --git a/exercises/practice/accumulate/src/lib.rs b/exercises/practice/accumulate/src/lib.rs
index bdacbd272..3c17aa1dd 100644
--- a/exercises/practice/accumulate/src/lib.rs
+++ b/exercises/practice/accumulate/src/lib.rs
@@ -1,4 +1,4 @@
-/// What should the type of _function be?
-pub fn map(input: Vec, _function: ???) -> Vec {
- unimplemented!("Transform input vector {input:?} using passed function");
+/// What should the type of function be?
+pub fn map(input: Vec, function: ???) -> Vec {
+ todo!("Transform input vector {input:?} using passed function");
}
diff --git a/exercises/practice/accumulate/tests/accumulate.rs b/exercises/practice/accumulate/tests/accumulate.rs
index 0cfb6a184..501e6bace 100644
--- a/exercises/practice/accumulate/tests/accumulate.rs
+++ b/exercises/practice/accumulate/tests/accumulate.rs
@@ -5,6 +5,14 @@ fn square(x: i32) -> i32 {
}
#[test]
+fn accumulate_empty() {
+ let input = vec![];
+ let expected = vec![];
+ assert_eq!(map(input, square), expected);
+}
+
+#[test]
+#[ignore]
fn func_single() {
let input = vec![2];
let expected = vec![4];
@@ -13,9 +21,9 @@ fn func_single() {
#[test]
#[ignore]
-fn func_multi() {
- let input = vec![2, 3, 4, 5];
- let expected = vec![4, 9, 16, 25];
+fn accumulate_squares() {
+ let input = vec![1, 2, 3];
+ let expected = vec![1, 4, 9];
assert_eq!(map(input, square), expected);
}
@@ -43,6 +51,38 @@ fn strings() {
assert_eq!(map(input, |s| s.repeat(2)), expected);
}
+#[test]
+#[ignore]
+fn accumulate_upcases() {
+ let input = vec!["Hello", "world"];
+ let expected = vec!["HELLO", "WORLD"];
+ assert_eq!(map(input, str::to_uppercase), expected);
+}
+
+#[test]
+#[ignore]
+fn accumulate_reversed_strings() {
+ let input = vec!["the", "quick", "brown", "fox", "etc"];
+ let expected = vec!["eht", "kciuq", "nworb", "xof", "cte"];
+ let reverse = |s: &str| s.chars().rev().collect::();
+ assert_eq!(map(input, reverse), expected);
+}
+
+#[test]
+#[ignore]
+fn accumulate_recursively() {
+ let input = vec!["a", "b", "c"];
+ let expected = vec![
+ vec!["a1", "a2", "a3"],
+ vec!["b1", "b2", "b3"],
+ vec!["c1", "c2", "c3"],
+ ];
+ assert_eq!(
+ map(input, |x| map(vec!["1", "2", "3"], |y| [x, y].join(""))),
+ expected
+ );
+}
+
#[test]
#[ignore]
fn change_in_type() {
diff --git a/exercises/practice/acronym/.docs/instructions.md b/exercises/practice/acronym/.docs/instructions.md
index e0515b4d1..133bd2cbb 100644
--- a/exercises/practice/acronym/.docs/instructions.md
+++ b/exercises/practice/acronym/.docs/instructions.md
@@ -4,5 +4,14 @@ Convert a phrase to its acronym.
Techies love their TLA (Three Letter Acronyms)!
-Help generate some jargon by writing a program that converts a long name
-like Portable Network Graphics to its acronym (PNG).
+Help generate some jargon by writing a program that converts a long name like Portable Network Graphics to its acronym (PNG).
+
+Punctuation is handled as follows: hyphens are word separators (like whitespace); all other punctuation can be removed from the input.
+
+For example:
+
+| Input | Output |
+| ------------------------- | ------ |
+| As Soon As Possible | ASAP |
+| Liquid-crystal display | LCD |
+| Thank George It's Friday! | TGIF |
diff --git a/exercises/practice/acronym/.gitignore b/exercises/practice/acronym/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/acronym/.gitignore
+++ b/exercises/practice/acronym/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/acronym/.meta/additional-tests.json b/exercises/practice/acronym/.meta/additional-tests.json
new file mode 100644
index 000000000..18985ddab
--- /dev/null
+++ b/exercises/practice/acronym/.meta/additional-tests.json
@@ -0,0 +1,22 @@
+[
+ {
+ "uuid": "d9f6e6d2-79c3-4e53-a22d-66325e8614c6",
+ "description": "camelcase",
+ "comments": [
+ "This test makes the exercise noticeably harder.",
+ "",
+ "Upstreaming this test is not a good option,",
+ "as most other languages would exclude it due to the added difficulty.",
+ "Removing the test from the Rust track is also not a good option,",
+ "because it creates confusion regarding existing community solutions.",
+ "",
+ "While deviations from problem-specifications should generally be avoided,",
+ "it seems like the best choice to stick with it in this case."
+ ],
+ "property": "abbreviate",
+ "input": {
+ "phrase": "HyperText Markup Language"
+ },
+ "expected": "HTML"
+ }
+]
diff --git a/exercises/practice/acronym/.meta/example.rs b/exercises/practice/acronym/.meta/example.rs
index 2bba845ef..80bd35821 100644
--- a/exercises/practice/acronym/.meta/example.rs
+++ b/exercises/practice/acronym/.meta/example.rs
@@ -1,7 +1,7 @@
pub fn abbreviate(phrase: &str) -> String {
phrase
.split(|c: char| c.is_whitespace() || (c != '\'' && !c.is_alphanumeric()))
- .flat_map(|word| split_camel(word))
+ .flat_map(split_camel)
.filter_map(|word| word.chars().next())
.collect::()
.to_uppercase()
diff --git a/exercises/practice/acronym/.meta/test_template.tera b/exercises/practice/acronym/.meta/test_template.tera
new file mode 100644
index 000000000..e20a88d95
--- /dev/null
+++ b/exercises/practice/acronym/.meta/test_template.tera
@@ -0,0 +1,12 @@
+use acronym::*;
+
+{% for test in cases %}
+#[test]
+#[ignore]
+fn {{ test.description | make_ident }}() {
+ let input = {{ test.input.phrase | json_encode() }};
+ let output = abbreviate(input);
+ let expected = {{ test.expected | json_encode() }};
+ assert_eq!(output, expected);
+}
+{% endfor -%}
diff --git a/exercises/practice/acronym/.meta/tests.toml b/exercises/practice/acronym/.meta/tests.toml
index 157cae14e..6e3277c68 100644
--- a/exercises/practice/acronym/.meta/tests.toml
+++ b/exercises/practice/acronym/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
[1e22cceb-c5e4-4562-9afe-aef07ad1eaf4]
description = "basic"
diff --git a/exercises/practice/acronym/Cargo.toml b/exercises/practice/acronym/Cargo.toml
index d1db9db39..c83115be8 100644
--- a/exercises/practice/acronym/Cargo.toml
+++ b/exercises/practice/acronym/Cargo.toml
@@ -1,6 +1,9 @@
[package]
-edition = "2021"
name = "acronym"
-version = "1.7.0"
+version = "0.1.0"
+edition = "2024"
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
[dependencies]
diff --git a/exercises/practice/acronym/src/lib.rs b/exercises/practice/acronym/src/lib.rs
index fd1c026a3..d31dd96c2 100644
--- a/exercises/practice/acronym/src/lib.rs
+++ b/exercises/practice/acronym/src/lib.rs
@@ -1,3 +1,3 @@
pub fn abbreviate(phrase: &str) -> String {
- unimplemented!("Given the phrase '{phrase}', return its acronym");
+ todo!("Given the phrase '{phrase}', return its acronym");
}
diff --git a/exercises/practice/acronym/tests/acronym.rs b/exercises/practice/acronym/tests/acronym.rs
index decf634f9..29e54bd52 100644
--- a/exercises/practice/acronym/tests/acronym.rs
+++ b/exercises/practice/acronym/tests/acronym.rs
@@ -1,84 +1,90 @@
-#[test]
-fn empty() {
- assert_eq!(acronym::abbreviate(""), "");
-}
+use acronym::*;
#[test]
-#[ignore]
fn basic() {
- assert_eq!(acronym::abbreviate("Portable Network Graphics"), "PNG");
+ let input = "Portable Network Graphics";
+ let output = abbreviate(input);
+ let expected = "PNG";
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
fn lowercase_words() {
- assert_eq!(acronym::abbreviate("Ruby on Rails"), "ROR");
-}
-
-#[test]
-#[ignore]
-fn camelcase() {
- assert_eq!(acronym::abbreviate("HyperText Markup Language"), "HTML");
+ let input = "Ruby on Rails";
+ let output = abbreviate(input);
+ let expected = "ROR";
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
fn punctuation() {
- assert_eq!(acronym::abbreviate("First In, First Out"), "FIFO");
+ let input = "First In, First Out";
+ let output = abbreviate(input);
+ let expected = "FIFO";
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
fn all_caps_word() {
- assert_eq!(
- acronym::abbreviate("GNU Image Manipulation Program"),
- "GIMP"
- );
-}
-
-#[test]
-#[ignore]
-fn all_caps_word_with_punctuation() {
- assert_eq!(acronym::abbreviate("PHP: Hypertext Preprocessor"), "PHP");
+ let input = "GNU Image Manipulation Program";
+ let output = abbreviate(input);
+ let expected = "GIMP";
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
fn punctuation_without_whitespace() {
- assert_eq!(
- acronym::abbreviate("Complementary metal-oxide semiconductor"),
- "CMOS"
- );
+ let input = "Complementary metal-oxide semiconductor";
+ let output = abbreviate(input);
+ let expected = "CMOS";
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
fn very_long_abbreviation() {
- assert_eq!(
- acronym::abbreviate(
- "Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me"
- ),
- "ROTFLSHTMDCOALM"
- );
+ let input = "Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me";
+ let output = abbreviate(input);
+ let expected = "ROTFLSHTMDCOALM";
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
fn consecutive_delimiters() {
- assert_eq!(
- acronym::abbreviate("Something - I made up from thin air"),
- "SIMUFTA"
- );
+ let input = "Something - I made up from thin air";
+ let output = abbreviate(input);
+ let expected = "SIMUFTA";
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
fn apostrophes() {
- assert_eq!(acronym::abbreviate("Halley's Comet"), "HC");
+ let input = "Halley's Comet";
+ let output = abbreviate(input);
+ let expected = "HC";
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
fn underscore_emphasis() {
- assert_eq!(acronym::abbreviate("The Road _Not_ Taken"), "TRNT");
+ let input = "The Road _Not_ Taken";
+ let output = abbreviate(input);
+ let expected = "TRNT";
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn camelcase() {
+ let input = "HyperText Markup Language";
+ let output = abbreviate(input);
+ let expected = "HTML";
+ assert_eq!(output, expected);
}
diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md
index b3ebb9c76..1603dbbce 100644
--- a/exercises/practice/affine-cipher/.docs/instructions.md
+++ b/exercises/practice/affine-cipher/.docs/instructions.md
@@ -1,70 +1,74 @@
# Instructions
-Create an implementation of the affine cipher,
-an ancient encryption system created in the Middle East.
+Create an implementation of the affine cipher, an ancient encryption system created in the Middle East.
The affine cipher is a type of monoalphabetic substitution cipher.
-Each character is mapped to its numeric equivalent, encrypted with
-a mathematical function and then converted to the letter relating to
-its new numeric value. Although all monoalphabetic ciphers are weak,
-the affine cypher is much stronger than the atbash cipher,
-because it has many more keys.
+Each character is mapped to its numeric equivalent, encrypted with a mathematical function and then converted to the letter relating to its new numeric value.
+Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the Atbash cipher, because it has many more keys.
+
+[//]: # " monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic "
+
+## Encryption
The encryption function is:
- `E(x) = (ax + b) mod m`
- - where `x` is the letter's index from 0 - length of alphabet - 1
- - `m` is the length of the alphabet. For the roman alphabet `m == 26`.
- - and `a` and `b` make the key
+```text
+E(x) = (ai + b) mod m
+```
-The decryption function is:
+Where:
+
+- `i` is the letter's index from `0` to the length of the alphabet - 1.
+- `m` is the length of the alphabet.
+ For the Latin alphabet `m` is `26`.
+- `a` and `b` are integers which make up the encryption key.
+
+Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]).
+In case `a` is not coprime to `m`, your program should indicate that this is an error.
+Otherwise it should encrypt or decrypt with the provided key.
+
+For the purpose of this exercise, digits are valid input but they are not encrypted.
+Spaces and punctuation characters are excluded.
+Ciphertext is written out in groups of fixed length separated by space, the traditional group size being `5` letters.
+This is to make it harder to guess encrypted text based on word boundaries.
- `D(y) = a^-1(y - b) mod m`
- - where `y` is the numeric value of an encrypted letter, ie. `y = E(x)`
- - it is important to note that `a^-1` is the modular multiplicative inverse
- of `a mod m`
- - the modular multiplicative inverse of `a` only exists if `a` and `m` are
- coprime.
+## Decryption
-To find the MMI of `a`:
+The decryption function is:
+
+```text
+D(y) = (a^-1)(y - b) mod m
+```
- `an mod m = 1`
- - where `n` is the modular multiplicative inverse of `a mod m`
+Where:
-More information regarding how to find a Modular Multiplicative Inverse
-and what it means can be found [here.](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse)
+- `y` is the numeric value of an encrypted letter, i.e., `y = E(x)`
+- it is important to note that `a^-1` is the modular multiplicative inverse (MMI) of `a mod m`
+- the modular multiplicative inverse only exists if `a` and `m` are coprime.
-Because automatic decryption fails if `a` is not coprime to `m` your
-program should return status 1 and `"Error: a and m must be coprime."`
-if they are not. Otherwise it should encode or decode with the
-provided key.
+The MMI of `a` is `x` such that the remainder after dividing `ax` by `m` is `1`:
-The Caesar (shift) cipher is a simple affine cipher where `a` is 1 and
-`b` as the magnitude results in a static displacement of the letters.
-This is much less secure than a full implementation of the affine cipher.
+```text
+ax mod m = 1
+```
-Ciphertext is written out in groups of fixed length, the traditional group
-size being 5 letters, and punctuation is excluded. This is to make it
-harder to guess things based on word boundaries.
+More information regarding how to find a Modular Multiplicative Inverse and what it means can be found in the [related Wikipedia article][mmi].
## General Examples
- - Encoding `test` gives `ybty` with the key a=5 b=7
- - Decoding `ybty` gives `test` with the key a=5 b=7
- - Decoding `ybty` gives `lqul` with the wrong key a=11 b=7
- - Decoding `kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx`
- - gives `thequickbrownfoxjumpsoverthelazydog` with the key a=19 b=13
- - Encoding `test` with the key a=18 b=13
- - gives `Error: a and m must be coprime.`
- - because a and m are not relatively prime
-
-## Examples of finding a Modular Multiplicative Inverse (MMI)
-
- - simple example:
- - `9 mod 26 = 9`
- - `9 * 3 mod 26 = 27 mod 26 = 1`
- - `3` is the MMI of `9 mod 26`
- - a more complicated example:
- - `15 mod 26 = 15`
- - `15 * 7 mod 26 = 105 mod 26 = 1`
- - `7` is the MMI of `15 mod 26`
+- Encrypting `"test"` gives `"ybty"` with the key `a = 5`, `b = 7`
+- Decrypting `"ybty"` gives `"test"` with the key `a = 5`, `b = 7`
+- Decrypting `"ybty"` gives `"lqul"` with the wrong key `a = 11`, `b = 7`
+- Decrypting `"kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx"` gives `"thequickbrownfoxjumpsoverthelazydog"` with the key `a = 19`, `b = 13`
+- Encrypting `"test"` with the key `a = 18`, `b = 13` is an error because `18` and `26` are not coprime
+
+## Example of finding a Modular Multiplicative Inverse (MMI)
+
+Finding MMI for `a = 15`:
+
+- `(15 * x) mod 26 = 1`
+- `(15 * 7) mod 26 = 1`, ie. `105 mod 26 = 1`
+- `7` is the MMI of `15 mod 26`
+
+[mmi]: https://en.wikipedia.org/wiki/Modular_multiplicative_inverse
+[coprime-integers]: https://en.wikipedia.org/wiki/Coprime_integers
diff --git a/exercises/practice/affine-cipher/.gitignore b/exercises/practice/affine-cipher/.gitignore
index e130ceb2d..96ef6c0b9 100644
--- a/exercises/practice/affine-cipher/.gitignore
+++ b/exercises/practice/affine-cipher/.gitignore
@@ -1,8 +1,2 @@
-# Generated by exercism rust track exercise tool
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/affine-cipher/.meta/config.json b/exercises/practice/affine-cipher/.meta/config.json
index 10c809f0a..19e73e177 100644
--- a/exercises/practice/affine-cipher/.meta/config.json
+++ b/exercises/practice/affine-cipher/.meta/config.json
@@ -16,7 +16,7 @@
"Cargo.toml"
],
"test": [
- "tests/affine-cipher.rs"
+ "tests/affine_cipher.rs"
],
"example": [
".meta/example.rs"
@@ -24,5 +24,5 @@
},
"blurb": "Create an implementation of the Affine cipher, an ancient encryption algorithm from the Middle East.",
"source": "Wikipedia",
- "source_url": "http://en.wikipedia.org/wiki/Affine_cipher"
+ "source_url": "https://en.wikipedia.org/wiki/Affine_cipher"
}
diff --git a/exercises/practice/affine-cipher/.meta/example.rs b/exercises/practice/affine-cipher/.meta/example.rs
index 0be1aff26..d3c220daa 100644
--- a/exercises/practice/affine-cipher/.meta/example.rs
+++ b/exercises/practice/affine-cipher/.meta/example.rs
@@ -43,7 +43,7 @@ pub fn decode(ciphertext: &str, a: i32, b: i32) -> Result char {
- if ch.is_digit(10) {
+ if ch.is_ascii_digit() {
ch
} else {
let index = (ch as i32) - ('a' as i32);
@@ -54,7 +54,7 @@ fn encode_char(ch: char, a: i32, b: i32) -> char {
/// Decodes a single char using `inv` (the modular multiplicative inverse of `a`) and `b`.
fn decode_char(ch: char, inv: i32, b: i32) -> char {
- if ch.is_digit(10) {
+ if ch.is_ascii_digit() {
ch
} else {
let index = (ch as i32) - ('a' as i32);
@@ -82,7 +82,7 @@ fn modular_multiplicative_inverse(a: i32) -> Option {
if rs.0 == 1 {
// ts.0 gives a number such that (s * 26 + t * a) % 26 == 1. Since (s * 26) % 26 == 0,
// we can further reduce this to (t * a) % 26 == 1. In other words, t is the MMI of a.
- Some(ts.0 as i32)
+ Some(ts.0)
} else {
None
}
diff --git a/exercises/practice/affine-cipher/.meta/test_template.tera b/exercises/practice/affine-cipher/.meta/test_template.tera
new file mode 100644
index 000000000..bcb4aa736
--- /dev/null
+++ b/exercises/practice/affine-cipher/.meta/test_template.tera
@@ -0,0 +1,21 @@
+use affine_cipher::*;
+
+use affine_cipher::AffineCipherError::NotCoprime;
+
+{% for test_group in cases %}
+{% for test in test_group.cases %}
+#[test]
+#[ignore]
+fn {{ test.description | make_ident }}() {
+ let phrase = {{ test.input.phrase | json_encode() }};
+ let (a, b) = ({{ test.input.key.a }}, {{ test.input.key.b }});
+ let output = {{ test.property }}(phrase, a, b);
+ let expected = {% if test.expected is object -%}
+ Err(NotCoprime({{ test.input.key.a }}))
+ {%- else -%}
+ Ok({{ test.expected | json_encode() }}.into())
+ {%- endif %};
+ assert_eq!(output, expected);
+}
+{% endfor -%}
+{% endfor -%}
diff --git a/exercises/practice/affine-cipher/Cargo.toml b/exercises/practice/affine-cipher/Cargo.toml
index 8f487a07f..979844792 100644
--- a/exercises/practice/affine-cipher/Cargo.toml
+++ b/exercises/practice/affine-cipher/Cargo.toml
@@ -1,4 +1,9 @@
[package]
-edition = "2021"
-name = "affine-cipher"
-version = "2.0.0"
+name = "affine_cipher"
+version = "0.1.0"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/practice/affine-cipher/src/lib.rs b/exercises/practice/affine-cipher/src/lib.rs
index 8e666711f..396aa99d0 100644
--- a/exercises/practice/affine-cipher/src/lib.rs
+++ b/exercises/practice/affine-cipher/src/lib.rs
@@ -8,11 +8,11 @@ pub enum AffineCipherError {
/// Encodes the plaintext using the affine cipher with key (`a`, `b`). Note that, rather than
/// returning a return code, the more common convention in Rust is to return a `Result`.
pub fn encode(plaintext: &str, a: i32, b: i32) -> Result {
- unimplemented!("Encode {plaintext} with the key ({a}, {b})");
+ todo!("Encode {plaintext} with the key ({a}, {b})");
}
/// Decodes the ciphertext using the affine cipher with key (`a`, `b`). Note that, rather than
/// returning a return code, the more common convention in Rust is to return a `Result`.
pub fn decode(ciphertext: &str, a: i32, b: i32) -> Result {
- unimplemented!("Decode {ciphertext} with the key ({a}, {b})");
+ todo!("Decode {ciphertext} with the key ({a}, {b})");
}
diff --git a/exercises/practice/affine-cipher/tests/affine-cipher.rs b/exercises/practice/affine-cipher/tests/affine-cipher.rs
deleted file mode 100644
index 69dbf34ac..000000000
--- a/exercises/practice/affine-cipher/tests/affine-cipher.rs
+++ /dev/null
@@ -1,138 +0,0 @@
-use affine_cipher::*;
-
-#[test]
-fn encode_yes() {
- assert_eq!(encode("yes", 5, 7).unwrap(), "xbt")
-}
-
-#[test]
-#[ignore]
-fn encode_no() {
- assert_eq!(encode("no", 15, 18).unwrap(), "fu")
-}
-
-#[test]
-#[ignore]
-fn encode_omg() {
- assert_eq!(encode("OMG", 21, 3).unwrap(), "lvz")
-}
-
-#[test]
-#[ignore]
-fn encode_o_m_g() {
- assert_eq!(encode("O M G", 25, 47).unwrap(), "hjp")
-}
-
-#[test]
-#[ignore]
-fn encode_mindblowingly() {
- assert_eq!(encode("mindblowingly", 11, 15).unwrap(), "rzcwa gnxzc dgt")
-}
-
-#[test]
-#[ignore]
-fn encode_numbers() {
- assert_eq!(
- encode("Testing,1 2 3, testing.", 3, 4).unwrap(),
- "jqgjc rw123 jqgjc rw"
- )
-}
-
-#[test]
-#[ignore]
-fn encode_deep_thought() {
- assert_eq!(
- encode("Truth is fiction", 5, 17).unwrap(),
- "iynia fdqfb ifje"
- )
-}
-
-#[test]
-#[ignore]
-fn encode_all_the_letters() {
- assert_eq!(
- encode("The quick brown fox jumps over the lazy dog.", 17, 33).unwrap(),
- "swxtj npvyk lruol iejdc blaxk swxmh qzglf"
- )
-}
-
-#[test]
-#[ignore]
-fn encode_with_a_not_coprime_to_m() {
- const EXPECTED_ERROR: AffineCipherError = AffineCipherError::NotCoprime(6);
- match encode("This is a test.", 6, 17) {
- Err(EXPECTED_ERROR) => (),
- Err(err) => panic!("Incorrect error: expected: {EXPECTED_ERROR:?}, actual: {err:?}."),
- Ok(r) => panic!(
- "Cannot encode/decode when a is coprime to m: expected: {EXPECTED_ERROR:?}, actual: {r:?}."
- ),
- }
-}
-
-#[test]
-#[ignore]
-fn decode_exercism() {
- assert_eq!(decode("tytgn fjr", 3, 7).unwrap(), "exercism")
-}
-
-#[test]
-#[ignore]
-fn decode_a_sentence() {
- assert_eq!(
- encode("anobstacleisoftenasteppingstone", 19, 16).unwrap(),
- "qdwju nqcro muwhn odqun oppmd aunwd o"
- );
- assert_eq!(
- decode("qdwju nqcro muwhn odqun oppmd aunwd o", 19, 16).unwrap(),
- "anobstacleisoftenasteppingstone"
- )
-}
-
-#[test]
-#[ignore]
-fn decode_numbers() {
- assert_eq!(
- decode("odpoz ub123 odpoz ub", 25, 7).unwrap(),
- "testing123testing"
- )
-}
-
-#[test]
-#[ignore]
-fn decode_all_the_letters() {
- assert_eq!(
- decode("swxtj npvyk lruol iejdc blaxk swxmh qzglf", 17, 33).unwrap(),
- "thequickbrownfoxjumpsoverthelazydog"
- )
-}
-
-#[test]
-#[ignore]
-fn decode_with_no_spaces_in_input() {
- assert_eq!(
- decode("swxtjnpvyklruoliejdcblaxkswxmhqzglf", 17, 33).unwrap(),
- "thequickbrownfoxjumpsoverthelazydog"
- )
-}
-
-#[test]
-#[ignore]
-fn decode_with_too_many_spaces() {
- assert_eq!(
- decode("vszzm cly yd cg qdp", 15, 16).unwrap(),
- "jollygreengiant"
- )
-}
-
-#[test]
-#[ignore]
-fn decode_with_a_not_coprime_to_m() {
- const EXPECTED_ERROR: AffineCipherError = AffineCipherError::NotCoprime(13);
- match decode("Test", 13, 11) {
- Err(EXPECTED_ERROR) => (),
- Err(e) => panic!("Incorrect error: expected: {EXPECTED_ERROR:?}, actual: {e:?}."),
- Ok(r) => panic!(
- "Cannot encode/decode when a is coprime to m: expected: {EXPECTED_ERROR:?}, actual: {r:?}."
- ),
- }
-}
diff --git a/exercises/practice/affine-cipher/tests/affine_cipher.rs b/exercises/practice/affine-cipher/tests/affine_cipher.rs
new file mode 100644
index 000000000..f11b8665b
--- /dev/null
+++ b/exercises/practice/affine-cipher/tests/affine_cipher.rs
@@ -0,0 +1,162 @@
+use affine_cipher::*;
+
+use affine_cipher::AffineCipherError::NotCoprime;
+
+#[test]
+fn encode_yes() {
+ let phrase = "yes";
+ let (a, b) = (5, 7);
+ let output = encode(phrase, a, b);
+ let expected = Ok("xbt".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn encode_no() {
+ let phrase = "no";
+ let (a, b) = (15, 18);
+ let output = encode(phrase, a, b);
+ let expected = Ok("fu".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn encode_omg() {
+ let phrase = "OMG";
+ let (a, b) = (21, 3);
+ let output = encode(phrase, a, b);
+ let expected = Ok("lvz".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn encode_o_m_g() {
+ let phrase = "O M G";
+ let (a, b) = (25, 47);
+ let output = encode(phrase, a, b);
+ let expected = Ok("hjp".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn encode_mindblowingly() {
+ let phrase = "mindblowingly";
+ let (a, b) = (11, 15);
+ let output = encode(phrase, a, b);
+ let expected = Ok("rzcwa gnxzc dgt".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn encode_numbers() {
+ let phrase = "Testing,1 2 3, testing.";
+ let (a, b) = (3, 4);
+ let output = encode(phrase, a, b);
+ let expected = Ok("jqgjc rw123 jqgjc rw".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn encode_deep_thought() {
+ let phrase = "Truth is fiction.";
+ let (a, b) = (5, 17);
+ let output = encode(phrase, a, b);
+ let expected = Ok("iynia fdqfb ifje".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn encode_all_the_letters() {
+ let phrase = "The quick brown fox jumps over the lazy dog.";
+ let (a, b) = (17, 33);
+ let output = encode(phrase, a, b);
+ let expected = Ok("swxtj npvyk lruol iejdc blaxk swxmh qzglf".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn encode_with_a_not_coprime_to_m() {
+ let phrase = "This is a test.";
+ let (a, b) = (6, 17);
+ let output = encode(phrase, a, b);
+ let expected = Err(NotCoprime(6));
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn decode_exercism() {
+ let phrase = "tytgn fjr";
+ let (a, b) = (3, 7);
+ let output = decode(phrase, a, b);
+ let expected = Ok("exercism".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn decode_a_sentence() {
+ let phrase = "qdwju nqcro muwhn odqun oppmd aunwd o";
+ let (a, b) = (19, 16);
+ let output = decode(phrase, a, b);
+ let expected = Ok("anobstacleisoftenasteppingstone".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn decode_numbers() {
+ let phrase = "odpoz ub123 odpoz ub";
+ let (a, b) = (25, 7);
+ let output = decode(phrase, a, b);
+ let expected = Ok("testing123testing".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn decode_all_the_letters() {
+ let phrase = "swxtj npvyk lruol iejdc blaxk swxmh qzglf";
+ let (a, b) = (17, 33);
+ let output = decode(phrase, a, b);
+ let expected = Ok("thequickbrownfoxjumpsoverthelazydog".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn decode_with_no_spaces_in_input() {
+ let phrase = "swxtjnpvyklruoliejdcblaxkswxmhqzglf";
+ let (a, b) = (17, 33);
+ let output = decode(phrase, a, b);
+ let expected = Ok("thequickbrownfoxjumpsoverthelazydog".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn decode_with_too_many_spaces() {
+ let phrase = "vszzm cly yd cg qdp";
+ let (a, b) = (15, 16);
+ let output = decode(phrase, a, b);
+ let expected = Ok("jollygreengiant".into());
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn decode_with_a_not_coprime_to_m() {
+ let phrase = "Test";
+ let (a, b) = (13, 5);
+ let output = decode(phrase, a, b);
+ let expected = Err(NotCoprime(13));
+ assert_eq!(output, expected);
+}
diff --git a/exercises/practice/all-your-base/.docs/instructions.md b/exercises/practice/all-your-base/.docs/instructions.md
index c39686f28..1b688b691 100644
--- a/exercises/practice/all-your-base/.docs/instructions.md
+++ b/exercises/practice/all-your-base/.docs/instructions.md
@@ -1,32 +1,28 @@
# Instructions
-Convert a number, represented as a sequence of digits in one base, to any other base.
+Convert a sequence of digits in one base, representing a number, into a sequence of digits in another base, representing the same number.
-Implement general base conversion. Given a number in base **a**,
-represented as a sequence of digits, convert it to base **b**.
+~~~~exercism/note
+Try to implement the conversion yourself.
+Do not use something else to perform the conversion for you.
+~~~~
-## Note
+## About [Positional Notation][positional-notation]
-- Try to implement the conversion yourself.
- Do not use something else to perform the conversion for you.
+In positional notation, a number in base **b** can be understood as a linear combination of powers of **b**.
-## About [Positional Notation](https://en.wikipedia.org/wiki/Positional_notation)
+The number 42, _in base 10_, means:
-In positional notation, a number in base **b** can be understood as a linear
-combination of powers of **b**.
+`(4 × 10¹) + (2 × 10⁰)`
-The number 42, *in base 10*, means:
+The number 101010, _in base 2_, means:
-(4 * 10^1) + (2 * 10^0)
+`(1 × 2⁵) + (0 × 2⁴) + (1 × 2³) + (0 × 2²) + (1 × 2¹) + (0 × 2⁰)`
-The number 101010, *in base 2*, means:
+The number 1120, _in base 3_, means:
-(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (0 * 2^0)
+`(1 × 3³) + (1 × 3²) + (2 × 3¹) + (0 × 3⁰)`
-The number 1120, *in base 3*, means:
+_Yes. Those three numbers above are exactly the same. Congratulations!_
-(1 * 3^3) + (1 * 3^2) + (2 * 3^1) + (0 * 3^0)
-
-I think you got the idea!
-
-*Yes. Those three numbers above are exactly the same. Congratulations!*
+[positional-notation]: https://en.wikipedia.org/wiki/Positional_notation
diff --git a/exercises/practice/all-your-base/.docs/introduction.md b/exercises/practice/all-your-base/.docs/introduction.md
new file mode 100644
index 000000000..68aaffbed
--- /dev/null
+++ b/exercises/practice/all-your-base/.docs/introduction.md
@@ -0,0 +1,8 @@
+# Introduction
+
+You've just been hired as professor of mathematics.
+Your first week went well, but something is off in your second week.
+The problem is that every answer given by your students is wrong!
+Luckily, your math skills have allowed you to identify the problem: the student answers _are_ correct, but they're all in base 2 (binary)!
+Amazingly, it turns out that each week, the students use a different base.
+To help you quickly verify the student answers, you'll be building a tool to translate between bases.
diff --git a/exercises/practice/all-your-base/.gitignore b/exercises/practice/all-your-base/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/all-your-base/.gitignore
+++ b/exercises/practice/all-your-base/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/all-your-base/.meta/config.json b/exercises/practice/all-your-base/.meta/config.json
index 7e4b21957..99988fe66 100644
--- a/exercises/practice/all-your-base/.meta/config.json
+++ b/exercises/practice/all-your-base/.meta/config.json
@@ -26,7 +26,7 @@
"Cargo.toml"
],
"test": [
- "tests/all-your-base.rs"
+ "tests/all_your_base.rs"
],
"example": [
".meta/example.rs"
diff --git a/exercises/practice/all-your-base/.meta/example.rs b/exercises/practice/all-your-base/.meta/example.rs
index 1a8521fcd..3aca42c1d 100644
--- a/exercises/practice/all-your-base/.meta/example.rs
+++ b/exercises/practice/all-your-base/.meta/example.rs
@@ -36,7 +36,7 @@ pub enum Error {
/// Notes:
/// * The empty slice ( "[]" ) is equal to the number 0.
/// * Never output leading 0 digits. However, your function must be able to
-/// process input with leading 0 digits.
+/// process input with leading 0 digits.
///
pub fn convert>(
digits: P,
diff --git a/exercises/practice/all-your-base/.meta/test_template.tera b/exercises/practice/all-your-base/.meta/test_template.tera
new file mode 100644
index 000000000..1e8fe4c10
--- /dev/null
+++ b/exercises/practice/all-your-base/.meta/test_template.tera
@@ -0,0 +1,26 @@
+use allyourbase as ayb;
+
+{% for test in cases %}
+#[test]
+#[ignore]
+fn {{ test.description | make_ident }}() {
+ let input_base = {{ test.input.inputBase }};
+ let input_digits = &{{ test.input.digits | json_encode() }};
+ let output_base = {{ test.input.outputBase }};
+ {%- if not test.expected is object %}
+ let output_digits = vec!{{ test.expected | json_encode() }};
+ {%- endif %}
+ assert_eq!(
+ ayb::convert(input_digits, input_base, output_base),
+ {%- if not test.expected is object %}
+ Ok(output_digits)
+ {%- elif test.expected.error == "input base must be >= 2" %}
+ Err(ayb::Error::InvalidInputBase)
+ {%- elif test.expected.error == "all digits must satisfy 0 <= d < input base" %}
+ Err(ayb::Error::InvalidDigit(2))
+ {%- elif test.expected.error == "output base must be >= 2" %}
+ Err(ayb::Error::InvalidOutputBase)
+ {%- endif %}
+ );
+}
+{% endfor -%}
diff --git a/exercises/practice/all-your-base/.meta/tests.toml b/exercises/practice/all-your-base/.meta/tests.toml
index c954370df..628e7d15b 100644
--- a/exercises/practice/all-your-base/.meta/tests.toml
+++ b/exercises/practice/all-your-base/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
[5ce422f9-7a4b-4f44-ad29-49c67cb32d2c]
description = "single bit one to decimal"
@@ -23,6 +30,9 @@ description = "trinary to hexadecimal"
[d3901c80-8190-41b9-bd86-38d988efa956]
description = "hexadecimal to trinary"
+[5d42f85e-21ad-41bd-b9be-a3e8e4258bbf]
+description = "15-bit integer"
+
[d68788f7-66dd-43f8-a543-f15b6d233f83]
description = "empty list"
@@ -41,6 +51,16 @@ description = "input base is one"
[e21a693a-7a69-450b-b393-27415c26a016]
description = "input base is zero"
+[54a23be5-d99e-41cc-88e0-a650ffe5fcc2]
+description = "input base is negative"
+include = false
+comment = "we use unsigned integers"
+
+[9eccf60c-dcc9-407b-95d8-c37b8be56bb6]
+description = "negative digit"
+include = false
+comment = "we use unsigned integers"
+
[232fa4a5-e761-4939-ba0c-ed046cd0676a]
description = "invalid positive digit"
@@ -49,3 +69,13 @@ description = "output base is one"
[73dac367-da5c-4a37-95fe-c87fad0a4047]
description = "output base is zero"
+
+[13f81f42-ff53-4e24-89d9-37603a48ebd9]
+description = "output base is negative"
+include = false
+comment = "we use unsigned integers"
+
+[0e6c895d-8a5d-4868-a345-309d094cfe8d]
+description = "both bases are negative"
+include = false
+comment = "we use unsigned integers"
diff --git a/exercises/practice/all-your-base/Cargo.toml b/exercises/practice/all-your-base/Cargo.toml
index 8ecd11098..7e9f62613 100644
--- a/exercises/practice/all-your-base/Cargo.toml
+++ b/exercises/practice/all-your-base/Cargo.toml
@@ -1,4 +1,9 @@
[package]
-edition = "2021"
name = "allyourbase"
-version = "1.0.0"
+version = "0.1.0"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/practice/all-your-base/src/lib.rs b/exercises/practice/all-your-base/src/lib.rs
index fd9996b9d..2803bcaf9 100644
--- a/exercises/practice/all-your-base/src/lib.rs
+++ b/exercises/practice/all-your-base/src/lib.rs
@@ -12,8 +12,7 @@ pub enum Error {
/// A digit is any unsigned integer (e.g. u8, u16, u32, u64, or usize).
/// Bases are specified as unsigned integers.
///
-/// Return an `Err(.)` if the conversion is impossible.
-/// The tests do not test for specific values inside the `Err(.)`.
+/// Return the corresponding Error enum if the conversion is impossible.
///
///
/// You are allowed to change the function signature as long as all test still pass.
@@ -37,5 +36,5 @@ pub enum Error {
/// However, your function must be able to process input with leading 0 digits.
///
pub fn convert(number: &[u32], from_base: u32, to_base: u32) -> Result, Error> {
- unimplemented!("Convert {number:?} from base {from_base} to base {to_base}")
+ todo!("Convert {number:?} from base {from_base} to base {to_base}")
}
diff --git a/exercises/practice/all-your-base/tests/all-your-base.rs b/exercises/practice/all-your-base/tests/all_your_base.rs
similarity index 98%
rename from exercises/practice/all-your-base/tests/all-your-base.rs
rename to exercises/practice/all-your-base/tests/all_your_base.rs
index 9012a40ae..75cd0d043 100644
--- a/exercises/practice/all-your-base/tests/all-your-base.rs
+++ b/exercises/practice/all-your-base/tests/all_your_base.rs
@@ -92,7 +92,7 @@ fn hexadecimal_to_trinary() {
#[test]
#[ignore]
-fn fifteen_bit_integer() {
+fn test_15_bit_integer() {
let input_base = 97;
let input_digits = &[3, 46, 60];
let output_base = 73;
@@ -157,20 +157,20 @@ fn leading_zeros() {
#[test]
#[ignore]
-fn invalid_positive_digit() {
- let input_base = 2;
- let input_digits = &[1, 2, 1, 0, 1, 0];
+fn input_base_is_one() {
+ let input_base = 1;
+ let input_digits = &[0];
let output_base = 10;
assert_eq!(
ayb::convert(input_digits, input_base, output_base),
- Err(ayb::Error::InvalidDigit(2))
+ Err(ayb::Error::InvalidInputBase)
);
}
#[test]
#[ignore]
-fn input_base_is_one() {
- let input_base = 1;
+fn input_base_is_zero() {
+ let input_base = 0;
let input_digits = &[];
let output_base = 10;
assert_eq!(
@@ -181,25 +181,25 @@ fn input_base_is_one() {
#[test]
#[ignore]
-fn output_base_is_one() {
+fn invalid_positive_digit() {
let input_base = 2;
- let input_digits = &[1, 0, 1, 0, 1, 0];
- let output_base = 1;
+ let input_digits = &[1, 2, 1, 0, 1, 0];
+ let output_base = 10;
assert_eq!(
ayb::convert(input_digits, input_base, output_base),
- Err(ayb::Error::InvalidOutputBase)
+ Err(ayb::Error::InvalidDigit(2))
);
}
#[test]
#[ignore]
-fn input_base_is_zero() {
- let input_base = 0;
- let input_digits = &[];
- let output_base = 10;
+fn output_base_is_one() {
+ let input_base = 2;
+ let input_digits = &[1, 0, 1, 0, 1, 0];
+ let output_base = 1;
assert_eq!(
ayb::convert(input_digits, input_base, output_base),
- Err(ayb::Error::InvalidInputBase)
+ Err(ayb::Error::InvalidOutputBase)
);
}
diff --git a/exercises/practice/allergies/.approaches/introduction.md b/exercises/practice/allergies/.approaches/introduction.md
index fc60fb36a..80a1f732a 100644
--- a/exercises/practice/allergies/.approaches/introduction.md
+++ b/exercises/practice/allergies/.approaches/introduction.md
@@ -8,7 +8,6 @@ Another approach can be to store the `Allergen` values as a [`u32`][u32] and onl
Something to keep in mind is to leverage [bitwise][bitwise] operations to implement the logic.
-
## Approach: Create `Vec` on `new()`
```rust
@@ -104,11 +103,11 @@ impl Allergies {
pub fn new(n: u32) -> Allergies {
Allergies { allergens: n }
}
-
+
pub fn is_allergic_to(&self, allergen: &Allergen) -> bool {
self.allergens & *allergen as u32 != 0
}
-
+
pub fn allergies(&self) -> Vec {
ALLERGENS
.iter()
diff --git a/exercises/practice/allergies/.approaches/vec-when-requested/content.md b/exercises/practice/allergies/.approaches/vec-when-requested/content.md
index 8ea98b250..c3bfce33c 100644
--- a/exercises/practice/allergies/.approaches/vec-when-requested/content.md
+++ b/exercises/practice/allergies/.approaches/vec-when-requested/content.md
@@ -32,11 +32,11 @@ impl Allergies {
pub fn new(n: u32) -> Allergies {
Allergies { allergens: n }
}
-
+
pub fn is_allergic_to(&self, allergen: &Allergen) -> bool {
self.allergens & *allergen as u32 != 0
}
-
+
pub fn allergies(&self) -> Vec {
ALLERGENS
.iter()
@@ -69,7 +69,7 @@ The `new()` method sets its `allergens` field to the `u232` value passed in.
The `is_allergic_to()` method uses the [bitwise AND operator][bitand] (`&`) to compare the `Allergen` passed in with the `allergens` `u32` field.
The dereferenced `Allergen` passed in is [cast][cast] to a `u32` for the purpose of comparison with the `allergens` `u32` value.
-The method returns if the comparison is not `0`.
+The method returns if the comparison is not `0`.
If the comparison is not `0`, then the `allergens` field contains the value of the `Allergen`, and `true` is returned.
For example, if the `allergens` field is decimal `3`, it is binary `11`.
diff --git a/exercises/practice/allergies/.docs/instructions.md b/exercises/practice/allergies/.docs/instructions.md
index b8bbd5a3f..daf8cfde2 100644
--- a/exercises/practice/allergies/.docs/instructions.md
+++ b/exercises/practice/allergies/.docs/instructions.md
@@ -2,20 +2,18 @@
Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.
-An allergy test produces a single numeric score which contains the
-information about all the allergies the person has (that they were
-tested for).
+An allergy test produces a single numeric score which contains the information about all the allergies the person has (that they were tested for).
The list of items (and their value) that were tested are:
-* eggs (1)
-* peanuts (2)
-* shellfish (4)
-* strawberries (8)
-* tomatoes (16)
-* chocolate (32)
-* pollen (64)
-* cats (128)
+- eggs (1)
+- peanuts (2)
+- shellfish (4)
+- strawberries (8)
+- tomatoes (16)
+- chocolate (32)
+- pollen (64)
+- cats (128)
So if Tom is allergic to peanuts and chocolate, he gets a score of 34.
@@ -24,7 +22,6 @@ Now, given just that score of 34, your program should be able to say:
- Whether Tom is allergic to any one of those allergens listed above.
- All the allergens Tom is allergic to.
-Note: a given score may include allergens **not** listed above (i.e.
-allergens that score 256, 512, 1024, etc.). Your program should
-ignore those components of the score. For example, if the allergy
-score is 257, your program should only report the eggs (1) allergy.
+Note: a given score may include allergens **not** listed above (i.e. allergens that score 256, 512, 1024, etc.).
+Your program should ignore those components of the score.
+For example, if the allergy score is 257, your program should only report the eggs (1) allergy.
diff --git a/exercises/practice/allergies/.gitignore b/exercises/practice/allergies/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/allergies/.gitignore
+++ b/exercises/practice/allergies/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/allergies/.meta/config.json b/exercises/practice/allergies/.meta/config.json
index e09f00556..b39243c5e 100644
--- a/exercises/practice/allergies/.meta/config.json
+++ b/exercises/practice/allergies/.meta/config.json
@@ -35,6 +35,6 @@
]
},
"blurb": "Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.",
- "source": "Jumpstart Lab Warm-up",
- "source_url": "http://jumpstartlab.com/"
+ "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.",
+ "source_url": "https://turing.edu/"
}
diff --git a/exercises/practice/allergies/.meta/test_template.tera b/exercises/practice/allergies/.meta/test_template.tera
new file mode 100644
index 000000000..05e5615cd
--- /dev/null
+++ b/exercises/practice/allergies/.meta/test_template.tera
@@ -0,0 +1,28 @@
+use allergies::*;
+
+{% for test_group in cases %}
+{% for test in test_group.cases %}
+#[test]
+#[ignore]
+{%- if test.property == "allergicTo" %}
+{#- canonical data contains multiple cases named "allergic to everything" for different items #}
+fn {{ test.description | make_ident }}_{{ test.input.item }}() {
+ let allergies = Allergies::new({{ test.input.score }});
+{%- if test.expected %}
+ assert!(allergies.is_allergic_to(&Allergen::{{ test.input.item | title }}))
+{% else %}
+ assert!(!allergies.is_allergic_to(&Allergen::{{ test.input.item | title }}))
+{% endif -%}
+{% else %}
+fn {{ test.description | make_ident }}() {
+ let allergies = Allergies::new({{ test.input.score }}).allergies();
+ let expected = &[
+ {% for allergen in test.expected %}
+ Allergen::{{ allergen | title }},
+ {% endfor %}
+ ];
+ assert_eq!(&allergies, expected);
+{% endif -%}
+}
+{% endfor -%}
+{% endfor -%}
diff --git a/exercises/practice/allergies/.meta/tests.toml b/exercises/practice/allergies/.meta/tests.toml
index 63397b988..799ab8563 100644
--- a/exercises/practice/allergies/.meta/tests.toml
+++ b/exercises/practice/allergies/.meta/tests.toml
@@ -1,27 +1,160 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[17fc7296-2440-4ac4-ad7b-d07c321bc5a0]
+description = "testing for eggs allergy -> not allergic to anything"
+
+[07ced27b-1da5-4c2e-8ae2-cb2791437546]
+description = "testing for eggs allergy -> allergic only to eggs"
+
+[5035b954-b6fa-4b9b-a487-dae69d8c5f96]
+description = "testing for eggs allergy -> allergic to eggs and something else"
+
+[64a6a83a-5723-4b5b-a896-663307403310]
+description = "testing for eggs allergy -> allergic to something, but not eggs"
[90c8f484-456b-41c4-82ba-2d08d93231c6]
-description = "allergic to everything"
+description = "testing for eggs allergy -> allergic to everything"
+
+[d266a59a-fccc-413b-ac53-d57cb1f0db9d]
+description = "testing for peanuts allergy -> not allergic to anything"
+
+[ea210a98-860d-46b2-a5bf-50d8995b3f2a]
+description = "testing for peanuts allergy -> allergic only to peanuts"
+
+[eac69ae9-8d14-4291-ac4b-7fd2c73d3a5b]
+description = "testing for peanuts allergy -> allergic to peanuts and something else"
+
+[9152058c-ce39-4b16-9b1d-283ec6d25085]
+description = "testing for peanuts allergy -> allergic to something, but not peanuts"
[d2d71fd8-63d5-40f9-a627-fbdaf88caeab]
-description = "allergic to everything"
+description = "testing for peanuts allergy -> allergic to everything"
+
+[b948b0a1-cbf7-4b28-a244-73ff56687c80]
+description = "testing for shellfish allergy -> not allergic to anything"
+
+[9ce9a6f3-53e9-4923-85e0-73019047c567]
+description = "testing for shellfish allergy -> allergic only to shellfish"
+
+[b272fca5-57ba-4b00-bd0c-43a737ab2131]
+description = "testing for shellfish allergy -> allergic to shellfish and something else"
+
+[21ef8e17-c227-494e-8e78-470a1c59c3d8]
+description = "testing for shellfish allergy -> allergic to something, but not shellfish"
[cc789c19-2b5e-4c67-b146-625dc8cfa34e]
-description = "allergic to everything"
+description = "testing for shellfish allergy -> allergic to everything"
+
+[651bde0a-2a74-46c4-ab55-02a0906ca2f5]
+description = "testing for strawberries allergy -> not allergic to anything"
+
+[b649a750-9703-4f5f-b7f7-91da2c160ece]
+description = "testing for strawberries allergy -> allergic only to strawberries"
+
+[50f5f8f3-3bac-47e6-8dba-2d94470a4bc6]
+description = "testing for strawberries allergy -> allergic to strawberries and something else"
+
+[23dd6952-88c9-48d7-a7d5-5d0343deb18d]
+description = "testing for strawberries allergy -> allergic to something, but not strawberries"
[74afaae2-13b6-43a2-837a-286cd42e7d7e]
-description = "allergic to everything"
+description = "testing for strawberries allergy -> allergic to everything"
+
+[c49a91ef-6252-415e-907e-a9d26ef61723]
+description = "testing for tomatoes allergy -> not allergic to anything"
+
+[b69c5131-b7d0-41ad-a32c-e1b2cc632df8]
+description = "testing for tomatoes allergy -> allergic only to tomatoes"
+
+[1ca50eb1-f042-4ccf-9050-341521b929ec]
+description = "testing for tomatoes allergy -> allergic to tomatoes and something else"
+
+[e9846baa-456b-4eff-8025-034b9f77bd8e]
+description = "testing for tomatoes allergy -> allergic to something, but not tomatoes"
[b2414f01-f3ad-4965-8391-e65f54dad35f]
-description = "allergic to everything"
+description = "testing for tomatoes allergy -> allergic to everything"
+
+[978467ab-bda4-49f7-b004-1d011ead947c]
+description = "testing for chocolate allergy -> not allergic to anything"
+
+[59cf4e49-06ea-4139-a2c1-d7aad28f8cbc]
+description = "testing for chocolate allergy -> allergic only to chocolate"
+
+[b0a7c07b-2db7-4f73-a180-565e07040ef1]
+description = "testing for chocolate allergy -> allergic to chocolate and something else"
+
+[f5506893-f1ae-482a-b516-7532ba5ca9d2]
+description = "testing for chocolate allergy -> allergic to something, but not chocolate"
[02debb3d-d7e2-4376-a26b-3c974b6595c6]
-description = "allergic to everything"
+description = "testing for chocolate allergy -> allergic to everything"
+
+[17f4a42b-c91e-41b8-8a76-4797886c2d96]
+description = "testing for pollen allergy -> not allergic to anything"
+
+[7696eba7-1837-4488-882a-14b7b4e3e399]
+description = "testing for pollen allergy -> allergic only to pollen"
+
+[9a49aec5-fa1f-405d-889e-4dfc420db2b6]
+description = "testing for pollen allergy -> allergic to pollen and something else"
+
+[3cb8e79f-d108-4712-b620-aa146b1954a9]
+description = "testing for pollen allergy -> allergic to something, but not pollen"
[1dc3fe57-7c68-4043-9d51-5457128744b2]
-description = "allergic to everything"
+description = "testing for pollen allergy -> allergic to everything"
+
+[d3f523d6-3d50-419b-a222-d4dfd62ce314]
+description = "testing for cats allergy -> not allergic to anything"
+
+[eba541c3-c886-42d3-baef-c048cb7fcd8f]
+description = "testing for cats allergy -> allergic only to cats"
+
+[ba718376-26e0-40b7-bbbe-060287637ea5]
+description = "testing for cats allergy -> allergic to cats and something else"
+
+[3c6dbf4a-5277-436f-8b88-15a206f2d6c4]
+description = "testing for cats allergy -> allergic to something, but not cats"
[1faabb05-2b98-4995-9046-d83e4a48a7c1]
-description = "allergic to everything"
+description = "testing for cats allergy -> allergic to everything"
+
+[f9c1b8e7-7dc5-4887-aa93-cebdcc29dd8f]
+description = "list when: -> no allergies"
+
+[9e1a4364-09a6-4d94-990f-541a94a4c1e8]
+description = "list when: -> just eggs"
+
+[8851c973-805e-4283-9e01-d0c0da0e4695]
+description = "list when: -> just peanuts"
+
+[2c8943cb-005e-435f-ae11-3e8fb558ea98]
+description = "list when: -> just strawberries"
+
+[6fa95d26-044c-48a9-8a7b-9ee46ec32c5c]
+description = "list when: -> eggs and peanuts"
+
+[19890e22-f63f-4c5c-a9fb-fb6eacddfe8e]
+description = "list when: -> more than eggs but not peanuts"
+
+[4b68f470-067c-44e4-889f-c9fe28917d2f]
+description = "list when: -> lots of stuff"
+
+[0881b7c5-9efa-4530-91bd-68370d054bc7]
+description = "list when: -> everything"
+
+[12ce86de-b347-42a0-ab7c-2e0570f0c65b]
+description = "list when: -> no allergen score parts"
+
+[93c2df3e-4f55-4fed-8116-7513092819cd]
+description = "list when: -> no allergen score parts without highest valid score"
diff --git a/exercises/practice/allergies/Cargo.toml b/exercises/practice/allergies/Cargo.toml
index 7bd71fcf9..41adcdd93 100644
--- a/exercises/practice/allergies/Cargo.toml
+++ b/exercises/practice/allergies/Cargo.toml
@@ -1,4 +1,9 @@
[package]
-edition = "2021"
name = "allergies"
-version = "1.1.0"
+version = "0.1.0"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/practice/allergies/src/lib.rs b/exercises/practice/allergies/src/lib.rs
index a3715f409..6ded730ed 100644
--- a/exercises/practice/allergies/src/lib.rs
+++ b/exercises/practice/allergies/src/lib.rs
@@ -14,14 +14,16 @@ pub enum Allergen {
impl Allergies {
pub fn new(score: u32) -> Self {
- unimplemented!("Given the '{score}' score, construct a new Allergies struct.");
+ todo!("Given the '{score}' score, construct a new Allergies struct.");
}
pub fn is_allergic_to(&self, allergen: &Allergen) -> bool {
- unimplemented!("Determine if the patient is allergic to the '{allergen:?}' allergen.");
+ todo!("Determine if the patient is allergic to the '{allergen:?}' allergen.");
}
pub fn allergies(&self) -> Vec {
- unimplemented!("Return the list of allergens contained within the score with which the Allergies struct was made.");
+ todo!(
+ "Return the list of allergens contained within the score with which the Allergies struct was made."
+ );
}
}
diff --git a/exercises/practice/allergies/tests/allergies.rs b/exercises/practice/allergies/tests/allergies.rs
index da57d7c60..35e31bba4 100644
--- a/exercises/practice/allergies/tests/allergies.rs
+++ b/exercises/practice/allergies/tests/allergies.rs
@@ -1,99 +1,336 @@
use allergies::*;
-fn compare_allergy_vectors(expected: &[Allergen], actual: &[Allergen]) {
- for element in expected {
- if !actual.contains(element) {
- panic!("Allergen missing\n {element:?} should be in {actual:?}");
- }
- }
+#[test]
+fn not_allergic_to_anything_eggs() {
+ let allergies = Allergies::new(0);
+ assert!(!allergies.is_allergic_to(&Allergen::Eggs))
+}
+
+#[test]
+#[ignore]
+fn allergic_only_to_eggs_eggs() {
+ let allergies = Allergies::new(1);
+ assert!(allergies.is_allergic_to(&Allergen::Eggs))
+}
- if actual.len() != expected.len() {
- panic!(
- "Allergy vectors are of different lengths\n expected {expected:?}\n got {actual:?}"
- );
- }
+#[test]
+#[ignore]
+fn allergic_to_eggs_and_something_else_eggs() {
+ let allergies = Allergies::new(3);
+ assert!(allergies.is_allergic_to(&Allergen::Eggs))
}
#[test]
-fn is_not_allergic_to_anything() {
+#[ignore]
+fn allergic_to_something_but_not_eggs_eggs() {
+ let allergies = Allergies::new(2);
+ assert!(!allergies.is_allergic_to(&Allergen::Eggs))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_everything_eggs() {
+ let allergies = Allergies::new(255);
+ assert!(allergies.is_allergic_to(&Allergen::Eggs))
+}
+
+#[test]
+#[ignore]
+fn not_allergic_to_anything_peanuts() {
let allergies = Allergies::new(0);
- assert!(!allergies.is_allergic_to(&Allergen::Peanuts));
- assert!(!allergies.is_allergic_to(&Allergen::Cats));
- assert!(!allergies.is_allergic_to(&Allergen::Strawberries));
+ assert!(!allergies.is_allergic_to(&Allergen::Peanuts))
}
#[test]
#[ignore]
-fn is_allergic_to_eggs() {
- assert!(Allergies::new(1).is_allergic_to(&Allergen::Eggs));
+fn allergic_only_to_peanuts_peanuts() {
+ let allergies = Allergies::new(2);
+ assert!(allergies.is_allergic_to(&Allergen::Peanuts))
}
#[test]
#[ignore]
-fn is_allergic_to_eggs_and_shellfish_but_not_strawberries() {
+fn allergic_to_peanuts_and_something_else_peanuts() {
+ let allergies = Allergies::new(7);
+ assert!(allergies.is_allergic_to(&Allergen::Peanuts))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_something_but_not_peanuts_peanuts() {
let allergies = Allergies::new(5);
- assert!(allergies.is_allergic_to(&Allergen::Eggs));
- assert!(allergies.is_allergic_to(&Allergen::Shellfish));
- assert!(!allergies.is_allergic_to(&Allergen::Strawberries));
+ assert!(!allergies.is_allergic_to(&Allergen::Peanuts))
}
#[test]
#[ignore]
-fn no_allergies_at_all() {
- let expected = &[];
- let allergies = Allergies::new(0).allergies();
+fn allergic_to_everything_peanuts() {
+ let allergies = Allergies::new(255);
+ assert!(allergies.is_allergic_to(&Allergen::Peanuts))
+}
- compare_allergy_vectors(expected, &allergies);
+#[test]
+#[ignore]
+fn not_allergic_to_anything_shellfish() {
+ let allergies = Allergies::new(0);
+ assert!(!allergies.is_allergic_to(&Allergen::Shellfish))
}
#[test]
#[ignore]
-fn allergic_to_just_eggs() {
- let expected = &[Allergen::Eggs];
- let allergies = Allergies::new(1).allergies();
+fn allergic_only_to_shellfish_shellfish() {
+ let allergies = Allergies::new(4);
+ assert!(allergies.is_allergic_to(&Allergen::Shellfish))
+}
- compare_allergy_vectors(expected, &allergies);
+#[test]
+#[ignore]
+fn allergic_to_shellfish_and_something_else_shellfish() {
+ let allergies = Allergies::new(14);
+ assert!(allergies.is_allergic_to(&Allergen::Shellfish))
}
#[test]
#[ignore]
-fn allergic_to_just_peanuts() {
- let expected = &[Allergen::Peanuts];
- let allergies = Allergies::new(2).allergies();
+fn allergic_to_something_but_not_shellfish_shellfish() {
+ let allergies = Allergies::new(10);
+ assert!(!allergies.is_allergic_to(&Allergen::Shellfish))
+}
- compare_allergy_vectors(expected, &allergies);
+#[test]
+#[ignore]
+fn allergic_to_everything_shellfish() {
+ let allergies = Allergies::new(255);
+ assert!(allergies.is_allergic_to(&Allergen::Shellfish))
}
#[test]
#[ignore]
-fn allergic_to_just_strawberries() {
- let expected = &[Allergen::Strawberries];
- let allergies = Allergies::new(8).allergies();
+fn not_allergic_to_anything_strawberries() {
+ let allergies = Allergies::new(0);
+ assert!(!allergies.is_allergic_to(&Allergen::Strawberries))
+}
- compare_allergy_vectors(expected, &allergies);
+#[test]
+#[ignore]
+fn allergic_only_to_strawberries_strawberries() {
+ let allergies = Allergies::new(8);
+ assert!(allergies.is_allergic_to(&Allergen::Strawberries))
}
#[test]
#[ignore]
-fn allergic_to_eggs_and_peanuts() {
- let expected = &[Allergen::Eggs, Allergen::Peanuts];
- let allergies = Allergies::new(3).allergies();
+fn allergic_to_strawberries_and_something_else_strawberries() {
+ let allergies = Allergies::new(28);
+ assert!(allergies.is_allergic_to(&Allergen::Strawberries))
+}
- compare_allergy_vectors(expected, &allergies);
+#[test]
+#[ignore]
+fn allergic_to_something_but_not_strawberries_strawberries() {
+ let allergies = Allergies::new(20);
+ assert!(!allergies.is_allergic_to(&Allergen::Strawberries))
}
#[test]
#[ignore]
-fn allergic_to_eggs_and_shellfish() {
- let expected = &[Allergen::Eggs, Allergen::Shellfish];
- let allergies = Allergies::new(5).allergies();
+fn allergic_to_everything_strawberries() {
+ let allergies = Allergies::new(255);
+ assert!(allergies.is_allergic_to(&Allergen::Strawberries))
+}
+
+#[test]
+#[ignore]
+fn not_allergic_to_anything_tomatoes() {
+ let allergies = Allergies::new(0);
+ assert!(!allergies.is_allergic_to(&Allergen::Tomatoes))
+}
+
+#[test]
+#[ignore]
+fn allergic_only_to_tomatoes_tomatoes() {
+ let allergies = Allergies::new(16);
+ assert!(allergies.is_allergic_to(&Allergen::Tomatoes))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_tomatoes_and_something_else_tomatoes() {
+ let allergies = Allergies::new(56);
+ assert!(allergies.is_allergic_to(&Allergen::Tomatoes))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_something_but_not_tomatoes_tomatoes() {
+ let allergies = Allergies::new(40);
+ assert!(!allergies.is_allergic_to(&Allergen::Tomatoes))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_everything_tomatoes() {
+ let allergies = Allergies::new(255);
+ assert!(allergies.is_allergic_to(&Allergen::Tomatoes))
+}
+
+#[test]
+#[ignore]
+fn not_allergic_to_anything_chocolate() {
+ let allergies = Allergies::new(0);
+ assert!(!allergies.is_allergic_to(&Allergen::Chocolate))
+}
+
+#[test]
+#[ignore]
+fn allergic_only_to_chocolate_chocolate() {
+ let allergies = Allergies::new(32);
+ assert!(allergies.is_allergic_to(&Allergen::Chocolate))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_chocolate_and_something_else_chocolate() {
+ let allergies = Allergies::new(112);
+ assert!(allergies.is_allergic_to(&Allergen::Chocolate))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_something_but_not_chocolate_chocolate() {
+ let allergies = Allergies::new(80);
+ assert!(!allergies.is_allergic_to(&Allergen::Chocolate))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_everything_chocolate() {
+ let allergies = Allergies::new(255);
+ assert!(allergies.is_allergic_to(&Allergen::Chocolate))
+}
+
+#[test]
+#[ignore]
+fn not_allergic_to_anything_pollen() {
+ let allergies = Allergies::new(0);
+ assert!(!allergies.is_allergic_to(&Allergen::Pollen))
+}
+
+#[test]
+#[ignore]
+fn allergic_only_to_pollen_pollen() {
+ let allergies = Allergies::new(64);
+ assert!(allergies.is_allergic_to(&Allergen::Pollen))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_pollen_and_something_else_pollen() {
+ let allergies = Allergies::new(224);
+ assert!(allergies.is_allergic_to(&Allergen::Pollen))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_something_but_not_pollen_pollen() {
+ let allergies = Allergies::new(160);
+ assert!(!allergies.is_allergic_to(&Allergen::Pollen))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_everything_pollen() {
+ let allergies = Allergies::new(255);
+ assert!(allergies.is_allergic_to(&Allergen::Pollen))
+}
+
+#[test]
+#[ignore]
+fn not_allergic_to_anything_cats() {
+ let allergies = Allergies::new(0);
+ assert!(!allergies.is_allergic_to(&Allergen::Cats))
+}
- compare_allergy_vectors(expected, &allergies);
+#[test]
+#[ignore]
+fn allergic_only_to_cats_cats() {
+ let allergies = Allergies::new(128);
+ assert!(allergies.is_allergic_to(&Allergen::Cats))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_cats_and_something_else_cats() {
+ let allergies = Allergies::new(192);
+ assert!(allergies.is_allergic_to(&Allergen::Cats))
}
#[test]
#[ignore]
-fn allergic_to_many_things() {
+fn allergic_to_something_but_not_cats_cats() {
+ let allergies = Allergies::new(64);
+ assert!(!allergies.is_allergic_to(&Allergen::Cats))
+}
+
+#[test]
+#[ignore]
+fn allergic_to_everything_cats() {
+ let allergies = Allergies::new(255);
+ assert!(allergies.is_allergic_to(&Allergen::Cats))
+}
+
+#[test]
+#[ignore]
+fn no_allergies() {
+ let allergies = Allergies::new(0).allergies();
+ let expected = &[];
+ assert_eq!(&allergies, expected);
+}
+
+#[test]
+#[ignore]
+fn just_eggs() {
+ let allergies = Allergies::new(1).allergies();
+ let expected = &[Allergen::Eggs];
+ assert_eq!(&allergies, expected);
+}
+
+#[test]
+#[ignore]
+fn just_peanuts() {
+ let allergies = Allergies::new(2).allergies();
+ let expected = &[Allergen::Peanuts];
+ assert_eq!(&allergies, expected);
+}
+
+#[test]
+#[ignore]
+fn just_strawberries() {
+ let allergies = Allergies::new(8).allergies();
+ let expected = &[Allergen::Strawberries];
+ assert_eq!(&allergies, expected);
+}
+
+#[test]
+#[ignore]
+fn eggs_and_peanuts() {
+ let allergies = Allergies::new(3).allergies();
+ let expected = &[Allergen::Eggs, Allergen::Peanuts];
+ assert_eq!(&allergies, expected);
+}
+
+#[test]
+#[ignore]
+fn more_than_eggs_but_not_peanuts() {
+ let allergies = Allergies::new(5).allergies();
+ let expected = &[Allergen::Eggs, Allergen::Shellfish];
+ assert_eq!(&allergies, expected);
+}
+
+#[test]
+#[ignore]
+fn lots_of_stuff() {
+ let allergies = Allergies::new(248).allergies();
let expected = &[
Allergen::Strawberries,
Allergen::Tomatoes,
@@ -101,14 +338,13 @@ fn allergic_to_many_things() {
Allergen::Pollen,
Allergen::Cats,
];
- let allergies = Allergies::new(248).allergies();
-
- compare_allergy_vectors(expected, &allergies);
+ assert_eq!(&allergies, expected);
}
#[test]
#[ignore]
-fn allergic_to_everything() {
+fn everything() {
+ let allergies = Allergies::new(255).allergies();
let expected = &[
Allergen::Eggs,
Allergen::Peanuts,
@@ -119,14 +355,13 @@ fn allergic_to_everything() {
Allergen::Pollen,
Allergen::Cats,
];
- let allergies = Allergies::new(255).allergies();
-
- compare_allergy_vectors(expected, &allergies);
+ assert_eq!(&allergies, expected);
}
#[test]
#[ignore]
-fn scores_over_255_do_not_trigger_false_positives() {
+fn no_allergen_score_parts() {
+ let allergies = Allergies::new(509).allergies();
let expected = &[
Allergen::Eggs,
Allergen::Shellfish,
@@ -136,7 +371,13 @@ fn scores_over_255_do_not_trigger_false_positives() {
Allergen::Pollen,
Allergen::Cats,
];
- let allergies = Allergies::new(509).allergies();
+ assert_eq!(&allergies, expected);
+}
- compare_allergy_vectors(expected, &allergies);
+#[test]
+#[ignore]
+fn no_allergen_score_parts_without_highest_valid_score() {
+ let allergies = Allergies::new(257).allergies();
+ let expected = &[Allergen::Eggs];
+ assert_eq!(&allergies, expected);
}
diff --git a/exercises/practice/alphametics/.docs/instructions.md b/exercises/practice/alphametics/.docs/instructions.md
index 6936c192d..ef2cbb4a7 100644
--- a/exercises/practice/alphametics/.docs/instructions.md
+++ b/exercises/practice/alphametics/.docs/instructions.md
@@ -1,9 +1,8 @@
# Instructions
-Write a function to solve alphametics puzzles.
+Given an alphametics puzzle, find the correct solution.
-[Alphametics](https://en.wikipedia.org/wiki/Alphametics) is a puzzle where
-letters in words are replaced with numbers.
+[Alphametics][alphametics] is a puzzle where letters in words are replaced with numbers.
For example `SEND + MORE = MONEY`:
@@ -23,10 +22,8 @@ Replacing these with valid numbers gives:
1 0 6 5 2
```
-This is correct because every letter is replaced by a different number and the
-words, translated into numbers, then make a valid sum.
+This is correct because every letter is replaced by a different number and the words, translated into numbers, then make a valid sum.
-Each letter must represent a different digit, and the leading digit of
-a multi-digit number must not be zero.
+Each letter must represent a different digit, and the leading digit of a multi-digit number must not be zero.
-Write a function to solve alphametics puzzles.
+[alphametics]: https://en.wikipedia.org/wiki/Alphametics
diff --git a/exercises/practice/alphametics/.gitignore b/exercises/practice/alphametics/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/alphametics/.gitignore
+++ b/exercises/practice/alphametics/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/alphametics/.meta/Cargo-example.toml b/exercises/practice/alphametics/.meta/Cargo-example.toml
index a83ef229b..1c70f6e2f 100644
--- a/exercises/practice/alphametics/.meta/Cargo-example.toml
+++ b/exercises/practice/alphametics/.meta/Cargo-example.toml
@@ -1,8 +1,11 @@
[package]
-edition = "2021"
name = "alphametics"
-version = "0.0.0"
+version = "0.1.0"
+edition = "2024"
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
[dependencies]
itertools = "0.5"
permutohedron = "0.2"
diff --git a/exercises/practice/alphametics/.meta/config.json b/exercises/practice/alphametics/.meta/config.json
index 79eff93dc..eaa3d5531 100644
--- a/exercises/practice/alphametics/.meta/config.json
+++ b/exercises/practice/alphametics/.meta/config.json
@@ -35,7 +35,7 @@
".meta/example.rs"
]
},
- "blurb": "Write a function to solve alphametics puzzles.",
+ "blurb": "Given an alphametics puzzle, find the correct solution.",
"custom": {
"test-in-release-mode": true
}
diff --git a/exercises/practice/alphametics/.meta/test_template.tera b/exercises/practice/alphametics/.meta/test_template.tera
new file mode 100644
index 000000000..c71e14f23
--- /dev/null
+++ b/exercises/practice/alphametics/.meta/test_template.tera
@@ -0,0 +1,19 @@
+use alphametics::*;
+
+{% for test in cases %}
+#[test]
+#[ignore]
+fn {{ test.description | make_ident }}() {
+ let answer = solve({{ test.input.puzzle | json_encode() }});
+ {%- if test.expected is object %}
+ let expected = [
+ {%- for key, val in test.expected %}
+ ('{{ key }}', {{val}}),
+ {%- endfor %}
+ ].into_iter().collect();
+ assert_eq!(answer, Some(expected));
+ {%- else %}
+ assert_eq!(answer, None);
+ {%- endif %}
+}
+{% endfor -%}
diff --git a/exercises/practice/alphametics/.meta/tests.toml b/exercises/practice/alphametics/.meta/tests.toml
index 8697da6fe..f599b3da6 100644
--- a/exercises/practice/alphametics/.meta/tests.toml
+++ b/exercises/practice/alphametics/.meta/tests.toml
@@ -1,6 +1,40 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[e0c08b07-9028-4d5f-91e1-d178fead8e1a]
+description = "puzzle with three letters"
+
+[a504ee41-cb92-4ec2-9f11-c37e95ab3f25]
+description = "solution must have unique value for each letter"
+
+[4e3b81d2-be7b-4c5c-9a80-cd72bc6d465a]
+description = "leading zero solution is invalid"
[8a3e3168-d1ee-4df7-94c7-b9c54845ac3a]
description = "puzzle with two digits final carry"
+
+[a9630645-15bd-48b6-a61e-d85c4021cc09]
+description = "puzzle with four letters"
+
+[3d905a86-5a52-4e4e-bf80-8951535791bd]
+description = "puzzle with six letters"
+
+[4febca56-e7b7-4789-97b9-530d09ba95f0]
+description = "puzzle with seven letters"
+
+[12125a75-7284-4f9a-a5fa-191471e0d44f]
+description = "puzzle with eight letters"
+
+[fb05955f-38dc-477a-a0b6-5ef78969fffa]
+description = "puzzle with ten letters"
+
+[9a101e81-9216-472b-b458-b513a7adacf7]
+description = "puzzle with ten letters and 199 addends"
diff --git a/exercises/practice/alphametics/Cargo.toml b/exercises/practice/alphametics/Cargo.toml
index bef2c20db..f2bd83000 100644
--- a/exercises/practice/alphametics/Cargo.toml
+++ b/exercises/practice/alphametics/Cargo.toml
@@ -1,4 +1,9 @@
[package]
-edition = "2021"
name = "alphametics"
-version = "1.3.0"
+version = "0.1.0"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/practice/alphametics/src/lib.rs b/exercises/practice/alphametics/src/lib.rs
index d5cb54146..a7eb2c2e2 100644
--- a/exercises/practice/alphametics/src/lib.rs
+++ b/exercises/practice/alphametics/src/lib.rs
@@ -1,5 +1,5 @@
use std::collections::HashMap;
pub fn solve(input: &str) -> Option> {
- unimplemented!("Solve the alphametic {input:?}")
+ todo!("Solve the alphametic {input:?}")
}
diff --git a/exercises/practice/alphametics/tests/alphametics.rs b/exercises/practice/alphametics/tests/alphametics.rs
index 8f9859185..63817df8b 100644
--- a/exercises/practice/alphametics/tests/alphametics.rs
+++ b/exercises/practice/alphametics/tests/alphametics.rs
@@ -1,132 +1,131 @@
-use std::collections::HashMap;
-
-fn assert_alphametic_solution_eq(puzzle: &str, solution: &[(char, u8)]) {
- let answer = alphametics::solve(puzzle);
- let solution: HashMap = solution.iter().cloned().collect();
- assert_eq!(answer, Some(solution));
-}
-
-#[test]
-fn test_with_three_letters() {
- assert_alphametic_solution_eq("I + BB == ILL", &[('I', 1), ('B', 9), ('L', 0)]);
-}
+use alphametics::*;
#[test]
-#[ignore]
-fn test_must_have_unique_value_for_each_letter() {
- let answer = alphametics::solve("A == B");
- assert_eq!(answer, None);
+fn puzzle_with_three_letters() {
+ let answer = solve("I + BB == ILL");
+ let expected = [('I', 1), ('B', 9), ('L', 0)].into_iter().collect();
+ assert_eq!(answer, Some(expected));
}
#[test]
#[ignore]
-fn test_leading_zero_solution_is_invalid() {
- let answer = alphametics::solve("ACA + DD == BD");
+fn solution_must_have_unique_value_for_each_letter() {
+ let answer = solve("A == B");
assert_eq!(answer, None);
}
#[test]
#[ignore]
-fn test_sum_must_be_wide_enough() {
- let answer = alphametics::solve("ABC + DEF == GH");
+fn leading_zero_solution_is_invalid() {
+ let answer = solve("ACA + DD == BD");
assert_eq!(answer, None);
}
#[test]
#[ignore]
fn puzzle_with_two_digits_final_carry() {
- assert_alphametic_solution_eq(
- "A + A + A + A + A + A + A + A + A + A + A + B == BCC",
- &[('A', 9), ('B', 1), ('C', 0)],
- );
+ let answer = solve("A + A + A + A + A + A + A + A + A + A + A + B == BCC");
+ let expected = [('A', 9), ('B', 1), ('C', 0)].into_iter().collect();
+ assert_eq!(answer, Some(expected));
}
#[test]
#[ignore]
-fn test_puzzle_with_four_letters() {
- assert_alphametic_solution_eq("AS + A == MOM", &[('A', 9), ('S', 2), ('M', 1), ('O', 0)]);
+fn puzzle_with_four_letters() {
+ let answer = solve("AS + A == MOM");
+ let expected = [('A', 9), ('S', 2), ('M', 1), ('O', 0)]
+ .into_iter()
+ .collect();
+ assert_eq!(answer, Some(expected));
}
#[test]
#[ignore]
-fn test_puzzle_with_six_letters() {
- assert_alphametic_solution_eq(
- "NO + NO + TOO == LATE",
- &[('N', 7), ('O', 4), ('T', 9), ('L', 1), ('A', 0), ('E', 2)],
- );
+fn puzzle_with_six_letters() {
+ let answer = solve("NO + NO + TOO == LATE");
+ let expected = [('N', 7), ('O', 4), ('T', 9), ('L', 1), ('A', 0), ('E', 2)]
+ .into_iter()
+ .collect();
+ assert_eq!(answer, Some(expected));
}
#[test]
#[ignore]
-fn test_puzzle_with_seven_letters() {
- assert_alphametic_solution_eq(
- "HE + SEES + THE == LIGHT",
- &[
- ('E', 4),
- ('G', 2),
- ('H', 5),
- ('I', 0),
- ('L', 1),
- ('S', 9),
- ('T', 7),
- ],
- );
+fn puzzle_with_seven_letters() {
+ let answer = solve("HE + SEES + THE == LIGHT");
+ let expected = [
+ ('E', 4),
+ ('G', 2),
+ ('H', 5),
+ ('I', 0),
+ ('L', 1),
+ ('S', 9),
+ ('T', 7),
+ ]
+ .into_iter()
+ .collect();
+ assert_eq!(answer, Some(expected));
}
#[test]
#[ignore]
-fn test_puzzle_with_eight_letters() {
- assert_alphametic_solution_eq(
- "SEND + MORE == MONEY",
- &[
- ('S', 9),
- ('E', 5),
- ('N', 6),
- ('D', 7),
- ('M', 1),
- ('O', 0),
- ('R', 8),
- ('Y', 2),
- ],
- );
+fn puzzle_with_eight_letters() {
+ let answer = solve("SEND + MORE == MONEY");
+ let expected = [
+ ('S', 9),
+ ('E', 5),
+ ('N', 6),
+ ('D', 7),
+ ('M', 1),
+ ('O', 0),
+ ('R', 8),
+ ('Y', 2),
+ ]
+ .into_iter()
+ .collect();
+ assert_eq!(answer, Some(expected));
}
#[test]
#[ignore]
-fn test_puzzle_with_ten_letters() {
- assert_alphametic_solution_eq(
- "AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE",
- &[
- ('A', 5),
- ('D', 3),
- ('E', 4),
- ('F', 7),
- ('G', 8),
- ('N', 0),
- ('O', 2),
- ('R', 1),
- ('S', 6),
- ('T', 9),
- ],
- );
+fn puzzle_with_ten_letters() {
+ let answer = solve("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE");
+ let expected = [
+ ('A', 5),
+ ('D', 3),
+ ('E', 4),
+ ('F', 7),
+ ('G', 8),
+ ('N', 0),
+ ('O', 2),
+ ('R', 1),
+ ('S', 6),
+ ('T', 9),
+ ]
+ .into_iter()
+ .collect();
+ assert_eq!(answer, Some(expected));
}
#[test]
#[ignore]
-fn test_puzzle_with_ten_letters_and_199_addends() {
- assert_alphametic_solution_eq(
+fn puzzle_with_ten_letters_and_199_addends() {
+ let answer = solve(
"THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES",
- &[
- ('A', 1),
- ('E', 0),
- ('F', 5),
- ('H', 8),
- ('I', 7),
- ('L', 2),
- ('O', 6),
- ('R', 3),
- ('S', 4),
- ('T', 9),
- ],
);
+ let expected = [
+ ('A', 1),
+ ('E', 0),
+ ('F', 5),
+ ('H', 8),
+ ('I', 7),
+ ('L', 2),
+ ('O', 6),
+ ('R', 3),
+ ('S', 4),
+ ('T', 9),
+ ]
+ .into_iter()
+ .collect();
+ assert_eq!(answer, Some(expected));
}
diff --git a/exercises/practice/anagram/.docs/hints.md b/exercises/practice/anagram/.docs/hints.md
new file mode 100644
index 000000000..4713528cb
--- /dev/null
+++ b/exercises/practice/anagram/.docs/hints.md
@@ -0,0 +1,4 @@
+## General
+
+The solution is case insensitive, which means `"WOrd"` is the same as `"word"` or `"woRd"`.
+It may help to take a peek at the [std library](https://doc.rust-lang.org/std/primitive.char.html) for functions that can convert between them.
diff --git a/exercises/practice/anagram/.docs/instructions.append.md b/exercises/practice/anagram/.docs/instructions.append.md
index a2142d202..85e50cb5f 100644
--- a/exercises/practice/anagram/.docs/instructions.append.md
+++ b/exercises/practice/anagram/.docs/instructions.append.md
@@ -1,11 +1,6 @@
-# Hints
+# Instructions append
-The solution is case insensitive, which means `"WOrd"` is the same as `"word"` or `"woRd"`. It may help to take a peek at the [std library](https://doc.rust-lang.org/std/primitive.char.html) for functions that can convert between them.
+The Rust track extends the possible letters to be any unicode character, not just ASCII alphabetic ones.
-The solution cannot contain the input word. A word is always an anagram of itself, which means it is not an interesting result. Given `"hello"` and the list `["hello", "olleh"]` the answer is `["olleh"]`.
-
-You are going to have to adjust the function signature provided in the stub in order for the lifetimes to work out properly. This is intentional: what's there demonstrates the basics of lifetime syntax, and what's missing teaches how to interpret lifetime-related compiler errors.
-
-Try to limit case changes. Case changes are expensive in terms of time, so it's faster to minimize them.
-
-If sorting, consider [sort_unstable](https://doc.rust-lang.org/std/primitive.slice.html#method.sort_unstable) which is typically faster than stable sorting. When applicable, unstable sorting is preferred because it is generally faster than stable sorting and it doesn't allocate auxiliary memory.
\ No newline at end of file
+You are going to have to adjust the function signature provided in the stub in order for the lifetimes to work out properly.
+This is intentional: what's there demonstrates the basics of lifetime syntax, and what's missing teaches how to interpret lifetime-related compiler errors.
diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md
index 2675b5836..dca24f526 100644
--- a/exercises/practice/anagram/.docs/instructions.md
+++ b/exercises/practice/anagram/.docs/instructions.md
@@ -1,8 +1,12 @@
# Instructions
-An anagram is a rearrangement of letters to form a new word.
-Given a word and a list of candidates, select the sublist of anagrams of the given word.
+Given a target word and one or more candidate words, your task is to find the candidates that are anagrams of the target.
-Given `"listen"` and a list of candidates like `"enlists" "google"
-"inlets" "banana"` the program should return a list containing
-`"inlets"`.
+An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`.
+A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`.
+
+The target word and candidate words are made up of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`).
+Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `"StoP"` is not an anagram of `"sTOp"`.
+The words you need to find should be taken from the candidate words, using the same letter case.
+
+Given the target `"stone"` and the candidate words `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, and `"Seton"`, the anagram words you need to find are `"tones"`, `"notes"`, and `"Seton"`.
diff --git a/exercises/practice/anagram/.docs/introduction.md b/exercises/practice/anagram/.docs/introduction.md
new file mode 100644
index 000000000..1acbdf00b
--- /dev/null
+++ b/exercises/practice/anagram/.docs/introduction.md
@@ -0,0 +1,12 @@
+# Introduction
+
+At a garage sale, you find a lovely vintage typewriter at a bargain price!
+Excitedly, you rush home, insert a sheet of paper, and start typing away.
+However, your excitement wanes when you examine the output: all words are garbled!
+For example, it prints "stop" instead of "post" and "least" instead of "stale."
+Carefully, you try again, but now it prints "spot" and "slate."
+After some experimentation, you find there is a random delay before each letter is printed, which messes up the order.
+You now understand why they sold it for so little money!
+
+You realize this quirk allows you to generate anagrams, which are words formed by rearranging the letters of another word.
+Pleased with your finding, you spend the rest of the day generating hundreds of anagrams.
diff --git a/exercises/practice/anagram/.gitignore b/exercises/practice/anagram/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/anagram/.gitignore
+++ b/exercises/practice/anagram/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/anagram/.meta/test_template.tera b/exercises/practice/anagram/.meta/test_template.tera
new file mode 100644
index 000000000..2fb8bc521
--- /dev/null
+++ b/exercises/practice/anagram/.meta/test_template.tera
@@ -0,0 +1,14 @@
+use anagram::*;
+use std::collections::HashSet;
+
+{% for test in cases %}
+#[test]
+#[ignore]
+fn {{ test.description | make_ident }}() {
+ let word = {{ test.input.subject | json_encode() }};
+ let inputs = &{{ test.input.candidates | json_encode() }};
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter({{ test.expected | json_encode() }});
+ assert_eq!(output, expected);
+}
+{% endfor -%}
diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml
index be690e975..4d9056270 100644
--- a/exercises/practice/anagram/.meta/tests.toml
+++ b/exercises/practice/anagram/.meta/tests.toml
@@ -1,3 +1,86 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[dd40c4d2-3c8b-44e5-992a-f42b393ec373]
+description = "no matches"
+
+[b3cca662-f50a-489e-ae10-ab8290a09bdc]
+description = "detects two anagrams"
+include = false
+
+[03eb9bbe-8906-4ea0-84fa-ffe711b52c8b]
+description = "detects two anagrams"
+reimplements = "b3cca662-f50a-489e-ae10-ab8290a09bdc"
+
+[a27558ee-9ba0-4552-96b1-ecf665b06556]
+description = "does not detect anagram subsets"
+
+[64cd4584-fc15-4781-b633-3d814c4941a4]
+description = "detects anagram"
+
+[99c91beb-838f-4ccd-b123-935139917283]
+description = "detects three anagrams"
+
+[78487770-e258-4e1f-a646-8ece10950d90]
+description = "detects multiple anagrams with different case"
+
+[1d0ab8aa-362f-49b7-9902-3d0c668d557b]
+description = "does not detect non-anagrams with identical checksum"
+
+[9e632c0b-c0b1-4804-8cc1-e295dea6d8a8]
+description = "detects anagrams case-insensitively"
+
+[b248e49f-0905-48d2-9c8d-bd02d8c3e392]
+description = "detects anagrams using case-insensitive subject"
+
+[f367325c-78ec-411c-be76-e79047f4bd54]
+description = "detects anagrams using case-insensitive possible matches"
+
+[7cc195ad-e3c7-44ee-9fd2-d3c344806a2c]
+description = "does not detect an anagram if the original word is repeated"
+include = false
+
+[630abb71-a94e-4715-8395-179ec1df9f91]
+description = "does not detect an anagram if the original word is repeated"
+reimplements = "7cc195ad-e3c7-44ee-9fd2-d3c344806a2c"
+
+[9878a1c9-d6ea-4235-ae51-3ea2befd6842]
+description = "anagrams must use all letters exactly once"
+
+[85757361-4535-45fd-ac0e-3810d40debc1]
+description = "words are not anagrams of themselves (case-insensitive)"
+include = false
+
+[68934ed0-010b-4ef9-857a-20c9012d1ebf]
+description = "words are not anagrams of themselves"
+reimplements = "85757361-4535-45fd-ac0e-3810d40debc1"
+
+[589384f3-4c8a-4e7d-9edc-51c3e5f0c90e]
+description = "words are not anagrams of themselves even if letter case is partially different"
+reimplements = "85757361-4535-45fd-ac0e-3810d40debc1"
+
+[ba53e423-7e02-41ee-9ae2-71f91e6d18e6]
+description = "words are not anagrams of themselves even if letter case is completely different"
+reimplements = "85757361-4535-45fd-ac0e-3810d40debc1"
+
+[a0705568-628c-4b55-9798-82e4acde51ca]
+description = "words other than themselves can be anagrams"
+include = false
+
+[33d3f67e-fbb9-49d3-a90e-0beb00861da7]
+description = "words other than themselves can be anagrams"
+reimplements = "a0705568-628c-4b55-9798-82e4acde51ca"
+
+[a6854f66-eec1-4afd-a137-62ef2870c051]
+description = "handles case of greek letters"
+
+[fd3509e5-e3ba-409d-ac3d-a9ac84d13296]
+description = "different characters may have the same bytes"
diff --git a/exercises/practice/anagram/Cargo.toml b/exercises/practice/anagram/Cargo.toml
index 5e06e4e55..f4381a85d 100644
--- a/exercises/practice/anagram/Cargo.toml
+++ b/exercises/practice/anagram/Cargo.toml
@@ -1,4 +1,9 @@
[package]
-edition = "2021"
name = "anagram"
-version = "0.0.0"
+version = "0.1.0"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/practice/anagram/src/lib.rs b/exercises/practice/anagram/src/lib.rs
index 111521663..f029d0449 100644
--- a/exercises/practice/anagram/src/lib.rs
+++ b/exercises/practice/anagram/src/lib.rs
@@ -1,7 +1,5 @@
use std::collections::HashSet;
pub fn anagrams_for<'a>(word: &str, possible_anagrams: &[&str]) -> HashSet<&'a str> {
- unimplemented!(
- "For the '{word}' word find anagrams among the following words: {possible_anagrams:?}"
- );
+ todo!("For the '{word}' word find anagrams among the following words: {possible_anagrams:?}");
}
diff --git a/exercises/practice/anagram/tests/anagram.rs b/exercises/practice/anagram/tests/anagram.rs
index 6fbb00933..5814f693e 100644
--- a/exercises/practice/anagram/tests/anagram.rs
+++ b/exercises/practice/anagram/tests/anagram.rs
@@ -1,78 +1,50 @@
+use anagram::*;
use std::collections::HashSet;
-fn process_anagram_case(word: &str, inputs: &[&str], expected: &[&str]) {
- let result = anagram::anagrams_for(word, inputs);
-
- let expected: HashSet<&str> = expected.iter().cloned().collect();
-
- assert_eq!(result, expected);
-}
-
#[test]
-fn test_no_matches() {
+fn no_matches() {
let word = "diaper";
-
- let inputs = ["hello", "world", "zombies", "pants"];
-
- let outputs = vec![];
-
- process_anagram_case(word, &inputs, &outputs);
+ let inputs = &["hello", "world", "zombies", "pants"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter([]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_detect_simple_anagram() {
- let word = "ant";
-
- let inputs = ["tan", "stand", "at"];
-
- let outputs = vec!["tan"];
-
- process_anagram_case(word, &inputs, &outputs);
-}
-
-#[test]
-#[ignore]
-fn test_does_not_confuse_different_duplicates() {
- let word = "galea";
-
- let inputs = ["eagle"];
-
- let outputs = vec![];
-
- process_anagram_case(word, &inputs, &outputs);
+fn detects_two_anagrams() {
+ let word = "solemn";
+ let inputs = &["lemons", "cherry", "melons"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter(["lemons", "melons"]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_eliminate_anagram_subsets() {
+fn does_not_detect_anagram_subsets() {
let word = "good";
-
- let inputs = ["dog", "goody"];
-
- let outputs = vec![];
-
- process_anagram_case(word, &inputs, &outputs);
+ let inputs = &["dog", "goody"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter([]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_detect_anagram() {
+fn detects_anagram() {
let word = "listen";
-
- let inputs = ["enlists", "google", "inlets", "banana"];
-
- let outputs = vec!["inlets"];
-
- process_anagram_case(word, &inputs, &outputs);
+ let inputs = &["enlists", "google", "inlets", "banana"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter(["inlets"]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_multiple_anagrams() {
+fn detects_three_anagrams() {
let word = "allergy";
-
- let inputs = [
+ let inputs = &[
"gallery",
"ballerina",
"regally",
@@ -80,107 +52,137 @@ fn test_multiple_anagrams() {
"largely",
"leading",
];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter(["gallery", "regally", "largely"]);
+ assert_eq!(output, expected);
+}
- let outputs = vec!["gallery", "regally", "largely"];
+#[test]
+#[ignore]
+fn detects_multiple_anagrams_with_different_case() {
+ let word = "nose";
+ let inputs = &["Eons", "ONES"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter(["Eons", "ONES"]);
+ assert_eq!(output, expected);
+}
- process_anagram_case(word, &inputs, &outputs);
+#[test]
+#[ignore]
+fn does_not_detect_non_anagrams_with_identical_checksum() {
+ let word = "mass";
+ let inputs = &["last"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter([]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_case_insensitive_anagrams() {
+fn detects_anagrams_case_insensitively() {
let word = "Orchestra";
-
- let inputs = ["cashregister", "Carthorse", "radishes"];
-
- let outputs = vec!["Carthorse"];
-
- process_anagram_case(word, &inputs, &outputs);
+ let inputs = &["cashregister", "Carthorse", "radishes"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter(["Carthorse"]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_unicode_anagrams() {
- let word = "ΑΒΓ";
-
- // These words don't make sense, they're just greek letters cobbled together.
- let inputs = ["ΒΓΑ", "ΒΓΔ", "γβα"];
-
- let outputs = vec!["ΒΓΑ", "γβα"];
-
- process_anagram_case(word, &inputs, &outputs);
+fn detects_anagrams_using_case_insensitive_subject() {
+ let word = "Orchestra";
+ let inputs = &["cashregister", "carthorse", "radishes"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter(["carthorse"]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_misleading_unicode_anagrams() {
- // Despite what a human might think these words contain different letters, the input uses Greek
- // A and B while the list of potential anagrams uses Latin A and B.
- let word = "ΑΒΓ";
-
- let inputs = ["ABΓ"];
-
- let outputs = vec![];
-
- process_anagram_case(word, &inputs, &outputs);
+fn detects_anagrams_using_case_insensitive_possible_matches() {
+ let word = "orchestra";
+ let inputs = &["cashregister", "Carthorse", "radishes"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter(["Carthorse"]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_does_not_detect_a_word_as_its_own_anagram() {
- let word = "banana";
-
- let inputs = ["banana"];
-
- let outputs = vec![];
-
- process_anagram_case(word, &inputs, &outputs);
+fn does_not_detect_an_anagram_if_the_original_word_is_repeated() {
+ let word = "go";
+ let inputs = &["goGoGO"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter([]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_does_not_detect_a_differently_cased_word_as_its_own_anagram() {
- let word = "banana";
-
- let inputs = ["bAnana"];
-
- let outputs = vec![];
-
- process_anagram_case(word, &inputs, &outputs);
+fn anagrams_must_use_all_letters_exactly_once() {
+ let word = "tapper";
+ let inputs = &["patter"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter([]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_does_not_detect_a_differently_cased_unicode_word_as_its_own_anagram() {
- let word = "ΑΒΓ";
-
- let inputs = ["ΑΒγ"];
-
- let outputs = vec![];
-
- process_anagram_case(word, &inputs, &outputs);
+fn words_are_not_anagrams_of_themselves() {
+ let word = "BANANA";
+ let inputs = &["BANANA"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter([]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_same_bytes_different_chars() {
- let word = "a⬂"; // 61 E2 AC 82
-
- let inputs = ["€a"]; // E2 82 AC 61
-
- let outputs = vec![];
-
- process_anagram_case(word, &inputs, &outputs);
+fn words_are_not_anagrams_of_themselves_even_if_letter_case_is_partially_different() {
+ let word = "BANANA";
+ let inputs = &["Banana"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter([]);
+ assert_eq!(output, expected);
}
#[test]
#[ignore]
-fn test_different_words_but_same_ascii_sum() {
- let word = "bc";
+fn words_are_not_anagrams_of_themselves_even_if_letter_case_is_completely_different() {
+ let word = "BANANA";
+ let inputs = &["banana"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter([]);
+ assert_eq!(output, expected);
+}
- let inputs = ["ad"];
+#[test]
+#[ignore]
+fn words_other_than_themselves_can_be_anagrams() {
+ let word = "LISTEN";
+ let inputs = &["LISTEN", "Silent"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter(["Silent"]);
+ assert_eq!(output, expected);
+}
- let outputs = vec![];
+#[test]
+#[ignore]
+fn handles_case_of_greek_letters() {
+ let word = "ΑΒΓ";
+ let inputs = &["ΒΓΑ", "ΒΓΔ", "γβα", "αβγ"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter(["ΒΓΑ", "γβα"]);
+ assert_eq!(output, expected);
+}
- process_anagram_case(word, &inputs, &outputs);
+#[test]
+#[ignore]
+fn different_characters_may_have_the_same_bytes() {
+ let word = "a⬂";
+ let inputs = &["€a"];
+ let output = anagrams_for(word, inputs);
+ let expected = HashSet::from_iter([]);
+ assert_eq!(output, expected);
}
diff --git a/exercises/practice/armstrong-numbers/.docs/instructions.md b/exercises/practice/armstrong-numbers/.docs/instructions.md
index 452a996fb..5e56bbe46 100644
--- a/exercises/practice/armstrong-numbers/.docs/instructions.md
+++ b/exercises/practice/armstrong-numbers/.docs/instructions.md
@@ -1,12 +1,14 @@
# Instructions
-An [Armstrong number](https://en.wikipedia.org/wiki/Narcissistic_number) is a number that is the sum of its own digits each raised to the power of the number of digits.
+An [Armstrong number][armstrong-number] is a number that is the sum of its own digits each raised to the power of the number of digits.
For example:
- 9 is an Armstrong number, because `9 = 9^1 = 9`
-- 10 is *not* an Armstrong number, because `10 != 1^2 + 0^2 = 1`
+- 10 is _not_ an Armstrong number, because `10 != 1^2 + 0^2 = 1`
- 153 is an Armstrong number, because: `153 = 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153`
-- 154 is *not* an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190`
+- 154 is _not_ an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190`
Write some code to determine whether a number is an Armstrong number.
+
+[armstrong-number]: https://en.wikipedia.org/wiki/Narcissistic_number
diff --git a/exercises/practice/armstrong-numbers/.gitignore b/exercises/practice/armstrong-numbers/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/armstrong-numbers/.gitignore
+++ b/exercises/practice/armstrong-numbers/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/armstrong-numbers/.meta/config.json b/exercises/practice/armstrong-numbers/.meta/config.json
index da15665f3..6cc65ea5e 100644
--- a/exercises/practice/armstrong-numbers/.meta/config.json
+++ b/exercises/practice/armstrong-numbers/.meta/config.json
@@ -24,7 +24,7 @@
"Cargo.toml"
],
"test": [
- "tests/armstrong-numbers.rs"
+ "tests/armstrong_numbers.rs"
],
"example": [
".meta/example.rs"
diff --git a/exercises/practice/armstrong-numbers/.meta/example.rs b/exercises/practice/armstrong-numbers/.meta/example.rs
index a14413c63..3760123d6 100644
--- a/exercises/practice/armstrong-numbers/.meta/example.rs
+++ b/exercises/practice/armstrong-numbers/.meta/example.rs
@@ -1,5 +1,5 @@
pub fn is_armstrong_number(num: u32) -> bool {
- let s = format!("{}", num);
+ let s = format!("{num}");
let l = s.len();
s.chars()
.map(|c| c.to_digit(10).unwrap().pow(l as u32))
diff --git a/exercises/practice/armstrong-numbers/.meta/test_template.tera b/exercises/practice/armstrong-numbers/.meta/test_template.tera
new file mode 100644
index 000000000..f60a5d881
--- /dev/null
+++ b/exercises/practice/armstrong-numbers/.meta/test_template.tera
@@ -0,0 +1,9 @@
+use armstrong_numbers::*;
+
+{% for test in cases %}
+#[test]
+#[ignore]
+fn {{ test.description | make_ident }}() {
+ assert!({% if not test.expected %} ! {% endif %}is_armstrong_number({{ test.input.number | fmt_num }}))
+}
+{% endfor -%}
diff --git a/exercises/practice/armstrong-numbers/.meta/tests.toml b/exercises/practice/armstrong-numbers/.meta/tests.toml
index be690e975..15a217c87 100644
--- a/exercises/practice/armstrong-numbers/.meta/tests.toml
+++ b/exercises/practice/armstrong-numbers/.meta/tests.toml
@@ -1,3 +1,53 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[c1ed103c-258d-45b2-be73-d8c6d9580c7b]
+description = "Zero is an Armstrong number"
+
+[579e8f03-9659-4b85-a1a2-d64350f6b17a]
+description = "Single-digit numbers are Armstrong numbers"
+
+[2d6db9dc-5bf8-4976-a90b-b2c2b9feba60]
+description = "There are no two-digit Armstrong numbers"
+
+[509c087f-e327-4113-a7d2-26a4e9d18283]
+description = "Three-digit number that is an Armstrong number"
+
+[7154547d-c2ce-468d-b214-4cb953b870cf]
+description = "Three-digit number that is not an Armstrong number"
+
+[6bac5b7b-42e9-4ecb-a8b0-4832229aa103]
+description = "Four-digit number that is an Armstrong number"
+
+[eed4b331-af80-45b5-a80b-19c9ea444b2e]
+description = "Four-digit number that is not an Armstrong number"
+
+[f971ced7-8d68-4758-aea1-d4194900b864]
+description = "Seven-digit number that is an Armstrong number"
+
+[7ee45d52-5d35-4fbd-b6f1-5c8cd8a67f18]
+description = "Seven-digit number that is not an Armstrong number"
+
+[5ee2fdf8-334e-4a46-bb8d-e5c19c02c148]
+description = "Armstrong number containing seven zeroes"
+include = false
+comment = """
+ The exercise was designed with u32 in the interface,
+ which does not support numbers that big.
+"""
+
+[12ffbf10-307a-434e-b4ad-c925680e1dd4]
+description = "The largest and last Armstrong number"
+include = false
+comment = """
+ The exercise was designed with u32 in the interface,
+ which does not support numbers that big.
+"""
diff --git a/exercises/practice/armstrong-numbers/Cargo.toml b/exercises/practice/armstrong-numbers/Cargo.toml
index 4949ea7fd..cb49ce6dc 100644
--- a/exercises/practice/armstrong-numbers/Cargo.toml
+++ b/exercises/practice/armstrong-numbers/Cargo.toml
@@ -1,4 +1,9 @@
[package]
-edition = "2021"
name = "armstrong_numbers"
-version = "1.1.0"
+version = "0.1.0"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/practice/armstrong-numbers/src/lib.rs b/exercises/practice/armstrong-numbers/src/lib.rs
index 3830a8484..91a62532d 100644
--- a/exercises/practice/armstrong-numbers/src/lib.rs
+++ b/exercises/practice/armstrong-numbers/src/lib.rs
@@ -1,3 +1,3 @@
pub fn is_armstrong_number(num: u32) -> bool {
- unimplemented!("true if {num} is an armstrong number")
+ todo!("true if {num} is an armstrong number")
}
diff --git a/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs b/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs
deleted file mode 100644
index 8e5713b05..000000000
--- a/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-use armstrong_numbers::*;
-
-#[test]
-fn test_zero_is_an_armstrong_number() {
- assert!(is_armstrong_number(0))
-}
-
-#[test]
-#[ignore]
-fn test_single_digit_numbers_are_armstrong_numbers() {
- assert!(is_armstrong_number(5))
-}
-
-#[test]
-#[ignore]
-fn test_there_are_no_2_digit_armstrong_numbers() {
- assert!(!is_armstrong_number(10))
-}
-
-#[test]
-#[ignore]
-fn test_three_digit_armstrong_number() {
- assert!(is_armstrong_number(153))
-}
-
-#[test]
-#[ignore]
-fn test_three_digit_non_armstrong_number() {
- assert!(!is_armstrong_number(100))
-}
-
-#[test]
-#[ignore]
-fn test_four_digit_armstrong_number() {
- assert!(is_armstrong_number(9474))
-}
-
-#[test]
-#[ignore]
-fn test_four_digit_non_armstrong_number() {
- assert!(!is_armstrong_number(9475))
-}
-
-#[test]
-#[ignore]
-fn test_seven_digit_armstrong_number() {
- assert!(is_armstrong_number(9_926_315))
-}
-
-#[test]
-#[ignore]
-fn test_seven_digit_non_armstrong_number() {
- assert!(!is_armstrong_number(9_926_316))
-}
-
-#[test]
-#[ignore]
-fn test_nine_digit_armstrong_number() {
- assert!(is_armstrong_number(912_985_153));
-}
-
-#[test]
-#[ignore]
-fn test_nine_digit_non_armstrong_number() {
- assert!(!is_armstrong_number(999_999_999));
-}
-
-#[test]
-#[ignore]
-fn test_ten_digit_non_armstrong_number() {
- assert!(!is_armstrong_number(3_999_999_999));
-}
-
-// The following number has an Armstrong sum equal to 2^32 plus itself,
-// and therefore will be detected as an Armstrong number if you are
-// incorrectly using wrapping arithmetic.
-#[test]
-#[ignore]
-fn test_properly_handles_overflow() {
- assert!(!is_armstrong_number(4_106_098_957));
-}
diff --git a/exercises/practice/armstrong-numbers/tests/armstrong_numbers.rs b/exercises/practice/armstrong-numbers/tests/armstrong_numbers.rs
new file mode 100644
index 000000000..6e15144cd
--- /dev/null
+++ b/exercises/practice/armstrong-numbers/tests/armstrong_numbers.rs
@@ -0,0 +1,54 @@
+use armstrong_numbers::*;
+
+#[test]
+fn zero_is_an_armstrong_number() {
+ assert!(is_armstrong_number(0))
+}
+
+#[test]
+#[ignore]
+fn single_digit_numbers_are_armstrong_numbers() {
+ assert!(is_armstrong_number(5))
+}
+
+#[test]
+#[ignore]
+fn there_are_no_two_digit_armstrong_numbers() {
+ assert!(!is_armstrong_number(10))
+}
+
+#[test]
+#[ignore]
+fn three_digit_number_that_is_an_armstrong_number() {
+ assert!(is_armstrong_number(153))
+}
+
+#[test]
+#[ignore]
+fn three_digit_number_that_is_not_an_armstrong_number() {
+ assert!(!is_armstrong_number(100))
+}
+
+#[test]
+#[ignore]
+fn four_digit_number_that_is_an_armstrong_number() {
+ assert!(is_armstrong_number(9_474))
+}
+
+#[test]
+#[ignore]
+fn four_digit_number_that_is_not_an_armstrong_number() {
+ assert!(!is_armstrong_number(9_475))
+}
+
+#[test]
+#[ignore]
+fn seven_digit_number_that_is_an_armstrong_number() {
+ assert!(is_armstrong_number(9_926_315))
+}
+
+#[test]
+#[ignore]
+fn seven_digit_number_that_is_not_an_armstrong_number() {
+ assert!(!is_armstrong_number(9_926_314))
+}
diff --git a/exercises/practice/atbash-cipher/.docs/instructions.md b/exercises/practice/atbash-cipher/.docs/instructions.md
index 2f712b159..1e7627b1e 100644
--- a/exercises/practice/atbash-cipher/.docs/instructions.md
+++ b/exercises/practice/atbash-cipher/.docs/instructions.md
@@ -1,11 +1,9 @@
# Instructions
-Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.
+Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East.
-The Atbash cipher is a simple substitution cipher that relies on
-transposing all the letters in the alphabet such that the resulting
-alphabet is backwards. The first letter is replaced with the last
-letter, the second with the second-last, and so on.
+The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards.
+The first letter is replaced with the last letter, the second with the second-last, and so on.
An Atbash cipher for the Latin alphabet would be as follows:
@@ -14,16 +12,16 @@ Plain: abcdefghijklmnopqrstuvwxyz
Cipher: zyxwvutsrqponmlkjihgfedcba
```
-It is a very weak cipher because it only has one possible key, and it is
-a simple monoalphabetic substitution cipher. However, this may not have
-been an issue in the cipher's time.
+It is a very weak cipher because it only has one possible key, and it is a simple mono-alphabetic substitution cipher.
+However, this may not have been an issue in the cipher's time.
-Ciphertext is written out in groups of fixed length, the traditional group size
-being 5 letters, and punctuation is excluded. This is to make it harder to guess
-things based on word boundaries.
+Ciphertext is written out in groups of fixed length, the traditional group size being 5 letters, leaving numbers unchanged, and punctuation is excluded.
+This is to make it harder to guess things based on word boundaries.
+All text will be encoded as lowercase letters.
## Examples
- Encoding `test` gives `gvhg`
+- Encoding `x123 yes` gives `c123b vh`
- Decoding `gvhg` gives `test`
- Decoding `gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt` gives `thequickbrownfoxjumpsoverthelazydog`
diff --git a/exercises/practice/atbash-cipher/.gitignore b/exercises/practice/atbash-cipher/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/atbash-cipher/.gitignore
+++ b/exercises/practice/atbash-cipher/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/atbash-cipher/.meta/config.json b/exercises/practice/atbash-cipher/.meta/config.json
index 9343b43c5..fab26b4bd 100644
--- a/exercises/practice/atbash-cipher/.meta/config.json
+++ b/exercises/practice/atbash-cipher/.meta/config.json
@@ -27,13 +27,13 @@
"Cargo.toml"
],
"test": [
- "tests/atbash-cipher.rs"
+ "tests/atbash_cipher.rs"
],
"example": [
".meta/example.rs"
]
},
- "blurb": "Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.",
+ "blurb": "Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East.",
"source": "Wikipedia",
- "source_url": "http://en.wikipedia.org/wiki/Atbash"
+ "source_url": "https://en.wikipedia.org/wiki/Atbash"
}
diff --git a/exercises/practice/atbash-cipher/.meta/example.rs b/exercises/practice/atbash-cipher/.meta/example.rs
index 565659500..288ae96ac 100644
--- a/exercises/practice/atbash-cipher/.meta/example.rs
+++ b/exercises/practice/atbash-cipher/.meta/example.rs
@@ -3,7 +3,7 @@ fn ascii(ch: char) -> u8 {
}
fn get_transpose(ch: char) -> char {
- if ch.is_digit(10) {
+ if ch.is_ascii_digit() {
ch
} else {
(ascii('z') - ascii(ch) + ascii('a')) as char
diff --git a/exercises/practice/atbash-cipher/.meta/test_template.tera b/exercises/practice/atbash-cipher/.meta/test_template.tera
new file mode 100644
index 000000000..c77c74649
--- /dev/null
+++ b/exercises/practice/atbash-cipher/.meta/test_template.tera
@@ -0,0 +1,14 @@
+use atbash_cipher as cipher;
+
+{% for group in cases %}
+{% for test in group.cases %}
+#[test]
+#[ignore]
+fn {{ test.description | make_ident }}() {
+ assert_eq!(
+ cipher::{{ test.property }}("{{ test.input.phrase }}"),
+ "{{ test.expected }}"
+ );
+}
+{% endfor -%}
+{% endfor -%}
diff --git a/exercises/practice/atbash-cipher/.meta/tests.toml b/exercises/practice/atbash-cipher/.meta/tests.toml
index be690e975..c082d07cc 100644
--- a/exercises/practice/atbash-cipher/.meta/tests.toml
+++ b/exercises/practice/atbash-cipher/.meta/tests.toml
@@ -1,3 +1,52 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[2f47ebe1-eab9-4d6b-b3c6-627562a31c77]
+description = "encode -> encode yes"
+
+[b4ffe781-ea81-4b74-b268-cc58ba21c739]
+description = "encode -> encode no"
+
+[10e48927-24ab-4c4d-9d3f-3067724ace00]
+description = "encode -> encode OMG"
+
+[d59b8bc3-509a-4a9a-834c-6f501b98750b]
+description = "encode -> encode spaces"
+
+[31d44b11-81b7-4a94-8b43-4af6a2449429]
+description = "encode -> encode mindblowingly"
+
+[d503361a-1433-48c0-aae0-d41b5baa33ff]
+description = "encode -> encode numbers"
+
+[79c8a2d5-0772-42d4-b41b-531d0b5da926]
+description = "encode -> encode deep thought"
+
+[9ca13d23-d32a-4967-a1fd-6100b8742bab]
+description = "encode -> encode all the letters"
+
+[bb50e087-7fdf-48e7-9223-284fe7e69851]
+description = "decode -> decode exercism"
+
+[ac021097-cd5d-4717-8907-b0814b9e292c]
+description = "decode -> decode a sentence"
+
+[18729de3-de74-49b8-b68c-025eaf77f851]
+description = "decode -> decode numbers"
+
+[0f30325f-f53b-415d-ad3e-a7a4f63de034]
+description = "decode -> decode all the letters"
+
+[39640287-30c6-4c8c-9bac-9d613d1a5674]
+description = "decode -> decode with too many spaces"
+
+[b34edf13-34c0-49b5-aa21-0768928000d5]
+description = "decode -> decode with no spaces"
diff --git a/exercises/practice/atbash-cipher/Cargo.toml b/exercises/practice/atbash-cipher/Cargo.toml
index 881ddf641..f2f21e973 100644
--- a/exercises/practice/atbash-cipher/Cargo.toml
+++ b/exercises/practice/atbash-cipher/Cargo.toml
@@ -1,4 +1,9 @@
[package]
-edition = "2021"
-name = "atbash-cipher"
-version = "1.2.0"
+name = "atbash_cipher"
+version = "0.1.0"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/practice/atbash-cipher/src/lib.rs b/exercises/practice/atbash-cipher/src/lib.rs
index 17e480ae5..5f63c8b85 100644
--- a/exercises/practice/atbash-cipher/src/lib.rs
+++ b/exercises/practice/atbash-cipher/src/lib.rs
@@ -1,9 +1,9 @@
/// "Encipher" with the Atbash cipher.
pub fn encode(plain: &str) -> String {
- unimplemented!("Encoding of {plain:?} in Atbash cipher.");
+ todo!("Encoding of {plain:?} in Atbash cipher.");
}
/// "Decipher" with the Atbash cipher.
pub fn decode(cipher: &str) -> String {
- unimplemented!("Decoding of {cipher:?} in Atbash cipher.");
+ todo!("Decoding of {cipher:?} in Atbash cipher.");
}
diff --git a/exercises/practice/atbash-cipher/tests/atbash-cipher.rs b/exercises/practice/atbash-cipher/tests/atbash_cipher.rs
similarity index 75%
rename from exercises/practice/atbash-cipher/tests/atbash-cipher.rs
rename to exercises/practice/atbash-cipher/tests/atbash_cipher.rs
index 689426f4f..140a7611e 100644
--- a/exercises/practice/atbash-cipher/tests/atbash-cipher.rs
+++ b/exercises/practice/atbash-cipher/tests/atbash_cipher.rs
@@ -1,37 +1,37 @@
use atbash_cipher as cipher;
#[test]
-fn test_encode_yes() {
+fn encode_yes() {
assert_eq!(cipher::encode("yes"), "bvh");
}
#[test]
#[ignore]
-fn test_encode_no() {
+fn encode_no() {
assert_eq!(cipher::encode("no"), "ml");
}
#[test]
#[ignore]
-fn test_encode_omg() {
+fn encode_omg() {
assert_eq!(cipher::encode("OMG"), "lnt");
}
#[test]
#[ignore]
-fn test_encode_spaces() {
+fn encode_spaces() {
assert_eq!(cipher::encode("O M G"), "lnt");
}
#[test]
#[ignore]
-fn test_encode_mindblowingly() {
+fn encode_mindblowingly() {
assert_eq!(cipher::encode("mindblowingly"), "nrmwy oldrm tob");
}
#[test]
#[ignore]
-fn test_encode_numbers() {
+fn encode_numbers() {
assert_eq!(
cipher::encode("Testing,1 2 3, testing."),
"gvhgr mt123 gvhgr mt"
@@ -40,13 +40,13 @@ fn test_encode_numbers() {
#[test]
#[ignore]
-fn test_encode_deep_thought() {
+fn encode_deep_thought() {
assert_eq!(cipher::encode("Truth is fiction."), "gifgs rhurx grlm");
}
#[test]
#[ignore]
-fn test_encode_all_the_letters() {
+fn encode_all_the_letters() {
assert_eq!(
cipher::encode("The quick brown fox jumps over the lazy dog."),
"gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt"
@@ -55,13 +55,13 @@ fn test_encode_all_the_letters() {
#[test]
#[ignore]
-fn test_decode_exercism() {
+fn decode_exercism() {
assert_eq!(cipher::decode("vcvix rhn"), "exercism");
}
#[test]
#[ignore]
-fn test_decode_a_sentence() {
+fn decode_a_sentence() {
assert_eq!(
cipher::decode("zmlyh gzxov rhlug vmzhg vkkrm thglm v"),
"anobstacleisoftenasteppingstone"
@@ -70,13 +70,13 @@ fn test_decode_a_sentence() {
#[test]
#[ignore]
-fn test_decode_numbers() {
+fn decode_numbers() {
assert_eq!(cipher::decode("gvhgr mt123 gvhgr mt"), "testing123testing");
}
#[test]
#[ignore]
-fn test_decode_all_the_letters() {
+fn decode_all_the_letters() {
assert_eq!(
cipher::decode("gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt"),
"thequickbrownfoxjumpsoverthelazydog"
@@ -85,15 +85,15 @@ fn test_decode_all_the_letters() {
#[test]
#[ignore]
-fn test_decode_with_too_many_spaces() {
+fn decode_with_too_many_spaces() {
assert_eq!(cipher::decode("vc vix r hn"), "exercism");
}
#[test]
#[ignore]
-fn test_decode_with_no_spaces() {
+fn decode_with_no_spaces() {
assert_eq!(
cipher::decode("zmlyhgzxovrhlugvmzhgvkkrmthglmv"),
- "anobstacleisoftenasteppingstone",
+ "anobstacleisoftenasteppingstone"
);
}
diff --git a/exercises/practice/beer-song/.docs/instructions.md b/exercises/practice/beer-song/.docs/instructions.md
index 57429d8ab..e909cfe31 100644
--- a/exercises/practice/beer-song/.docs/instructions.md
+++ b/exercises/practice/beer-song/.docs/instructions.md
@@ -305,17 +305,3 @@ Take it down and pass it around, no more bottles of beer on the wall.
No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.
```
-
-## For bonus points
-
-Did you get the tests passing and the code clean? If you want to, these
-are some additional things you could try:
-
-* Remove as much duplication as you possibly can.
-* Optimize for readability, even if it means introducing duplication.
-* If you've removed all the duplication, do you have a lot of
- conditionals? Try replacing the conditionals with polymorphism, if it
- applies in this language. How readable is it?
-
-Then please share your thoughts in a comment on the submission. Did this
-experiment make the code better? Worse? Did you learn anything from it?
diff --git a/exercises/practice/beer-song/.gitignore b/exercises/practice/beer-song/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/beer-song/.gitignore
+++ b/exercises/practice/beer-song/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/beer-song/.meta/config.json b/exercises/practice/beer-song/.meta/config.json
index 3c0367363..ae57f1965 100644
--- a/exercises/practice/beer-song/.meta/config.json
+++ b/exercises/practice/beer-song/.meta/config.json
@@ -32,7 +32,7 @@
"Cargo.toml"
],
"test": [
- "tests/beer-song.rs"
+ "tests/beer_song.rs"
],
"example": [
".meta/example.rs"
@@ -40,5 +40,5 @@
},
"blurb": "Produce the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall.",
"source": "Learn to Program by Chris Pine",
- "source_url": "http://pine.fm/LearnToProgram/?Chapter=06"
+ "source_url": "https://pine.fm/LearnToProgram/?Chapter=06"
}
diff --git a/exercises/practice/beer-song/.meta/test_template.tera b/exercises/practice/beer-song/.meta/test_template.tera
new file mode 100644
index 000000000..842f5af64
--- /dev/null
+++ b/exercises/practice/beer-song/.meta/test_template.tera
@@ -0,0 +1,24 @@
+{% for stupid_uselessly_nested_test_group in cases -%}
+{% for test_group in stupid_uselessly_nested_test_group.cases -%}
+mod {{ test_group.description | make_ident }} {
+ use beer_song::*;
+
+{% for test in test_group.cases %}
+ #[test]
+ #[ignore]
+ fn {{ test.description | make_ident }}() {
+ assert_eq!(
+ {% if stupid_uselessly_nested_test_group.description == "verse" -%}
+ verse({{ test.input.startBottles }}).trim(),
+ {% else -%}
+ sing({{ test.input.startBottles }}, {{ test.input.startBottles - test.input.takeDown + 1 }}).trim(),
+ {% endif -%}
+ {{ test.expected | join(sep="
+") | json_encode() }},
+ );
+ }
+{% endfor -%}
+}
+
+{% endfor -%}
+{% endfor -%}
diff --git a/exercises/practice/beer-song/.meta/tests.toml b/exercises/practice/beer-song/.meta/tests.toml
index be690e975..306cf88f0 100644
--- a/exercises/practice/beer-song/.meta/tests.toml
+++ b/exercises/practice/beer-song/.meta/tests.toml
@@ -1,3 +1,34 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[5a02fd08-d336-4607-8006-246fe6fa9fb0]
+description = "verse -> single verse -> first generic verse"
+
+[77299ca6-545e-4217-a9cc-606b342e0187]
+description = "verse -> single verse -> last generic verse"
+
+[102cbca0-b197-40fd-b548-e99609b06428]
+description = "verse -> single verse -> verse with 2 bottles"
+
+[b8ef9fce-960e-4d85-a0c9-980a04ec1972]
+description = "verse -> single verse -> verse with 1 bottle"
+
+[c59d4076-f671-4ee3-baaa-d4966801f90d]
+description = "verse -> single verse -> verse with 0 bottles"
+
+[7e17c794-402d-4ca6-8f96-4d8f6ee1ec7e]
+description = "lyrics -> multiple verses -> first two verses"
+
+[949868e7-67e8-43d3-9bb4-69277fe020fb]
+description = "lyrics -> multiple verses -> last three verses"
+
+[bc220626-126c-4e72-8df4-fddfc0c3e458]
+description = "lyrics -> multiple verses -> all verses"
diff --git a/exercises/practice/beer-song/Cargo.toml b/exercises/practice/beer-song/Cargo.toml
index 11c518b4f..848f1827e 100644
--- a/exercises/practice/beer-song/Cargo.toml
+++ b/exercises/practice/beer-song/Cargo.toml
@@ -1,4 +1,9 @@
[package]
-edition = "2021"
-name = "beer-song"
-version = "0.0.0"
+name = "beer_song"
+version = "0.1.0"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/practice/beer-song/src/lib.rs b/exercises/practice/beer-song/src/lib.rs
index c35a613c2..fd6334416 100644
--- a/exercises/practice/beer-song/src/lib.rs
+++ b/exercises/practice/beer-song/src/lib.rs
@@ -1,7 +1,7 @@
pub fn verse(n: u32) -> String {
- unimplemented!("emit verse {n}")
+ todo!("emit verse {n}")
}
pub fn sing(start: u32, end: u32) -> String {
- unimplemented!("sing verses {start} to {end}, inclusive")
+ todo!("sing verses {start} to {end}, inclusive")
}
diff --git a/exercises/practice/beer-song/tests/beer-song.rs b/exercises/practice/beer-song/tests/beer-song.rs
deleted file mode 100644
index d5daaf363..000000000
--- a/exercises/practice/beer-song/tests/beer-song.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-use beer_song as beer;
-
-#[test]
-fn test_verse_0() {
- assert_eq!(beer::verse(0), "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n");
-}
-
-#[test]
-#[ignore]
-fn test_verse_1() {
- assert_eq!(beer::verse(1), "1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n");
-}
-
-#[test]
-#[ignore]
-fn test_verse_2() {
- assert_eq!(beer::verse(2), "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n");
-}
-
-#[test]
-#[ignore]
-fn test_verse_8() {
- assert_eq!(beer::verse(8), "8 bottles of beer on the wall, 8 bottles of beer.\nTake one down and pass it around, 7 bottles of beer on the wall.\n");
-}
-
-#[test]
-#[ignore]
-fn test_song_8_6() {
- assert_eq!(beer::sing(8, 6), "8 bottles of beer on the wall, 8 bottles of beer.\nTake one down and pass it around, 7 bottles of beer on the wall.\n\n7 bottles of beer on the wall, 7 bottles of beer.\nTake one down and pass it around, 6 bottles of beer on the wall.\n\n6 bottles of beer on the wall, 6 bottles of beer.\nTake one down and pass it around, 5 bottles of beer on the wall.\n");
-}
-
-#[test]
-#[ignore]
-fn test_song_3_0() {
- assert_eq!(beer::sing(3, 0), "3 bottles of beer on the wall, 3 bottles of beer.\nTake one down and pass it around, 2 bottles of beer on the wall.\n\n2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n\n1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n\nNo more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n");
-}
diff --git a/exercises/practice/beer-song/tests/beer_song.rs b/exercises/practice/beer-song/tests/beer_song.rs
new file mode 100644
index 000000000..41f54fec2
--- /dev/null
+++ b/exercises/practice/beer-song/tests/beer_song.rs
@@ -0,0 +1,78 @@
+mod single_verse {
+ use beer_song::*;
+
+ #[test]
+ fn first_generic_verse() {
+ assert_eq!(
+ verse(99).trim(),
+ "99 bottles of beer on the wall, 99 bottles of beer.\nTake one down and pass it around, 98 bottles of beer on the wall.",
+ );
+ }
+
+ #[test]
+ #[ignore]
+ fn last_generic_verse() {
+ assert_eq!(
+ verse(3).trim(),
+ "3 bottles of beer on the wall, 3 bottles of beer.\nTake one down and pass it around, 2 bottles of beer on the wall.",
+ );
+ }
+
+ #[test]
+ #[ignore]
+ fn verse_with_2_bottles() {
+ assert_eq!(
+ verse(2).trim(),
+ "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.",
+ );
+ }
+
+ #[test]
+ #[ignore]
+ fn verse_with_1_bottle() {
+ assert_eq!(
+ verse(1).trim(),
+ "1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.",
+ );
+ }
+
+ #[test]
+ #[ignore]
+ fn verse_with_0_bottles() {
+ assert_eq!(
+ verse(0).trim(),
+ "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.",
+ );
+ }
+}
+
+mod multiple_verses {
+ use beer_song::*;
+
+ #[test]
+ #[ignore]
+ fn first_two_verses() {
+ assert_eq!(
+ sing(99, 98).trim(),
+ "99 bottles of beer on the wall, 99 bottles of beer.\nTake one down and pass it around, 98 bottles of beer on the wall.\n\n98 bottles of beer on the wall, 98 bottles of beer.\nTake one down and pass it around, 97 bottles of beer on the wall.",
+ );
+ }
+
+ #[test]
+ #[ignore]
+ fn last_three_verses() {
+ assert_eq!(
+ sing(2, 0).trim(),
+ "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n\n1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n\nNo more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.",
+ );
+ }
+
+ #[test]
+ #[ignore]
+ fn all_verses() {
+ assert_eq!(
+ sing(99, 0).trim(),
+ "99 bottles of beer on the wall, 99 bottles of beer.\nTake one down and pass it around, 98 bottles of beer on the wall.\n\n98 bottles of beer on the wall, 98 bottles of beer.\nTake one down and pass it around, 97 bottles of beer on the wall.\n\n97 bottles of beer on the wall, 97 bottles of beer.\nTake one down and pass it around, 96 bottles of beer on the wall.\n\n96 bottles of beer on the wall, 96 bottles of beer.\nTake one down and pass it around, 95 bottles of beer on the wall.\n\n95 bottles of beer on the wall, 95 bottles of beer.\nTake one down and pass it around, 94 bottles of beer on the wall.\n\n94 bottles of beer on the wall, 94 bottles of beer.\nTake one down and pass it around, 93 bottles of beer on the wall.\n\n93 bottles of beer on the wall, 93 bottles of beer.\nTake one down and pass it around, 92 bottles of beer on the wall.\n\n92 bottles of beer on the wall, 92 bottles of beer.\nTake one down and pass it around, 91 bottles of beer on the wall.\n\n91 bottles of beer on the wall, 91 bottles of beer.\nTake one down and pass it around, 90 bottles of beer on the wall.\n\n90 bottles of beer on the wall, 90 bottles of beer.\nTake one down and pass it around, 89 bottles of beer on the wall.\n\n89 bottles of beer on the wall, 89 bottles of beer.\nTake one down and pass it around, 88 bottles of beer on the wall.\n\n88 bottles of beer on the wall, 88 bottles of beer.\nTake one down and pass it around, 87 bottles of beer on the wall.\n\n87 bottles of beer on the wall, 87 bottles of beer.\nTake one down and pass it around, 86 bottles of beer on the wall.\n\n86 bottles of beer on the wall, 86 bottles of beer.\nTake one down and pass it around, 85 bottles of beer on the wall.\n\n85 bottles of beer on the wall, 85 bottles of beer.\nTake one down and pass it around, 84 bottles of beer on the wall.\n\n84 bottles of beer on the wall, 84 bottles of beer.\nTake one down and pass it around, 83 bottles of beer on the wall.\n\n83 bottles of beer on the wall, 83 bottles of beer.\nTake one down and pass it around, 82 bottles of beer on the wall.\n\n82 bottles of beer on the wall, 82 bottles of beer.\nTake one down and pass it around, 81 bottles of beer on the wall.\n\n81 bottles of beer on the wall, 81 bottles of beer.\nTake one down and pass it around, 80 bottles of beer on the wall.\n\n80 bottles of beer on the wall, 80 bottles of beer.\nTake one down and pass it around, 79 bottles of beer on the wall.\n\n79 bottles of beer on the wall, 79 bottles of beer.\nTake one down and pass it around, 78 bottles of beer on the wall.\n\n78 bottles of beer on the wall, 78 bottles of beer.\nTake one down and pass it around, 77 bottles of beer on the wall.\n\n77 bottles of beer on the wall, 77 bottles of beer.\nTake one down and pass it around, 76 bottles of beer on the wall.\n\n76 bottles of beer on the wall, 76 bottles of beer.\nTake one down and pass it around, 75 bottles of beer on the wall.\n\n75 bottles of beer on the wall, 75 bottles of beer.\nTake one down and pass it around, 74 bottles of beer on the wall.\n\n74 bottles of beer on the wall, 74 bottles of beer.\nTake one down and pass it around, 73 bottles of beer on the wall.\n\n73 bottles of beer on the wall, 73 bottles of beer.\nTake one down and pass it around, 72 bottles of beer on the wall.\n\n72 bottles of beer on the wall, 72 bottles of beer.\nTake one down and pass it around, 71 bottles of beer on the wall.\n\n71 bottles of beer on the wall, 71 bottles of beer.\nTake one down and pass it around, 70 bottles of beer on the wall.\n\n70 bottles of beer on the wall, 70 bottles of beer.\nTake one down and pass it around, 69 bottles of beer on the wall.\n\n69 bottles of beer on the wall, 69 bottles of beer.\nTake one down and pass it around, 68 bottles of beer on the wall.\n\n68 bottles of beer on the wall, 68 bottles of beer.\nTake one down and pass it around, 67 bottles of beer on the wall.\n\n67 bottles of beer on the wall, 67 bottles of beer.\nTake one down and pass it around, 66 bottles of beer on the wall.\n\n66 bottles of beer on the wall, 66 bottles of beer.\nTake one down and pass it around, 65 bottles of beer on the wall.\n\n65 bottles of beer on the wall, 65 bottles of beer.\nTake one down and pass it around, 64 bottles of beer on the wall.\n\n64 bottles of beer on the wall, 64 bottles of beer.\nTake one down and pass it around, 63 bottles of beer on the wall.\n\n63 bottles of beer on the wall, 63 bottles of beer.\nTake one down and pass it around, 62 bottles of beer on the wall.\n\n62 bottles of beer on the wall, 62 bottles of beer.\nTake one down and pass it around, 61 bottles of beer on the wall.\n\n61 bottles of beer on the wall, 61 bottles of beer.\nTake one down and pass it around, 60 bottles of beer on the wall.\n\n60 bottles of beer on the wall, 60 bottles of beer.\nTake one down and pass it around, 59 bottles of beer on the wall.\n\n59 bottles of beer on the wall, 59 bottles of beer.\nTake one down and pass it around, 58 bottles of beer on the wall.\n\n58 bottles of beer on the wall, 58 bottles of beer.\nTake one down and pass it around, 57 bottles of beer on the wall.\n\n57 bottles of beer on the wall, 57 bottles of beer.\nTake one down and pass it around, 56 bottles of beer on the wall.\n\n56 bottles of beer on the wall, 56 bottles of beer.\nTake one down and pass it around, 55 bottles of beer on the wall.\n\n55 bottles of beer on the wall, 55 bottles of beer.\nTake one down and pass it around, 54 bottles of beer on the wall.\n\n54 bottles of beer on the wall, 54 bottles of beer.\nTake one down and pass it around, 53 bottles of beer on the wall.\n\n53 bottles of beer on the wall, 53 bottles of beer.\nTake one down and pass it around, 52 bottles of beer on the wall.\n\n52 bottles of beer on the wall, 52 bottles of beer.\nTake one down and pass it around, 51 bottles of beer on the wall.\n\n51 bottles of beer on the wall, 51 bottles of beer.\nTake one down and pass it around, 50 bottles of beer on the wall.\n\n50 bottles of beer on the wall, 50 bottles of beer.\nTake one down and pass it around, 49 bottles of beer on the wall.\n\n49 bottles of beer on the wall, 49 bottles of beer.\nTake one down and pass it around, 48 bottles of beer on the wall.\n\n48 bottles of beer on the wall, 48 bottles of beer.\nTake one down and pass it around, 47 bottles of beer on the wall.\n\n47 bottles of beer on the wall, 47 bottles of beer.\nTake one down and pass it around, 46 bottles of beer on the wall.\n\n46 bottles of beer on the wall, 46 bottles of beer.\nTake one down and pass it around, 45 bottles of beer on the wall.\n\n45 bottles of beer on the wall, 45 bottles of beer.\nTake one down and pass it around, 44 bottles of beer on the wall.\n\n44 bottles of beer on the wall, 44 bottles of beer.\nTake one down and pass it around, 43 bottles of beer on the wall.\n\n43 bottles of beer on the wall, 43 bottles of beer.\nTake one down and pass it around, 42 bottles of beer on the wall.\n\n42 bottles of beer on the wall, 42 bottles of beer.\nTake one down and pass it around, 41 bottles of beer on the wall.\n\n41 bottles of beer on the wall, 41 bottles of beer.\nTake one down and pass it around, 40 bottles of beer on the wall.\n\n40 bottles of beer on the wall, 40 bottles of beer.\nTake one down and pass it around, 39 bottles of beer on the wall.\n\n39 bottles of beer on the wall, 39 bottles of beer.\nTake one down and pass it around, 38 bottles of beer on the wall.\n\n38 bottles of beer on the wall, 38 bottles of beer.\nTake one down and pass it around, 37 bottles of beer on the wall.\n\n37 bottles of beer on the wall, 37 bottles of beer.\nTake one down and pass it around, 36 bottles of beer on the wall.\n\n36 bottles of beer on the wall, 36 bottles of beer.\nTake one down and pass it around, 35 bottles of beer on the wall.\n\n35 bottles of beer on the wall, 35 bottles of beer.\nTake one down and pass it around, 34 bottles of beer on the wall.\n\n34 bottles of beer on the wall, 34 bottles of beer.\nTake one down and pass it around, 33 bottles of beer on the wall.\n\n33 bottles of beer on the wall, 33 bottles of beer.\nTake one down and pass it around, 32 bottles of beer on the wall.\n\n32 bottles of beer on the wall, 32 bottles of beer.\nTake one down and pass it around, 31 bottles of beer on the wall.\n\n31 bottles of beer on the wall, 31 bottles of beer.\nTake one down and pass it around, 30 bottles of beer on the wall.\n\n30 bottles of beer on the wall, 30 bottles of beer.\nTake one down and pass it around, 29 bottles of beer on the wall.\n\n29 bottles of beer on the wall, 29 bottles of beer.\nTake one down and pass it around, 28 bottles of beer on the wall.\n\n28 bottles of beer on the wall, 28 bottles of beer.\nTake one down and pass it around, 27 bottles of beer on the wall.\n\n27 bottles of beer on the wall, 27 bottles of beer.\nTake one down and pass it around, 26 bottles of beer on the wall.\n\n26 bottles of beer on the wall, 26 bottles of beer.\nTake one down and pass it around, 25 bottles of beer on the wall.\n\n25 bottles of beer on the wall, 25 bottles of beer.\nTake one down and pass it around, 24 bottles of beer on the wall.\n\n24 bottles of beer on the wall, 24 bottles of beer.\nTake one down and pass it around, 23 bottles of beer on the wall.\n\n23 bottles of beer on the wall, 23 bottles of beer.\nTake one down and pass it around, 22 bottles of beer on the wall.\n\n22 bottles of beer on the wall, 22 bottles of beer.\nTake one down and pass it around, 21 bottles of beer on the wall.\n\n21 bottles of beer on the wall, 21 bottles of beer.\nTake one down and pass it around, 20 bottles of beer on the wall.\n\n20 bottles of beer on the wall, 20 bottles of beer.\nTake one down and pass it around, 19 bottles of beer on the wall.\n\n19 bottles of beer on the wall, 19 bottles of beer.\nTake one down and pass it around, 18 bottles of beer on the wall.\n\n18 bottles of beer on the wall, 18 bottles of beer.\nTake one down and pass it around, 17 bottles of beer on the wall.\n\n17 bottles of beer on the wall, 17 bottles of beer.\nTake one down and pass it around, 16 bottles of beer on the wall.\n\n16 bottles of beer on the wall, 16 bottles of beer.\nTake one down and pass it around, 15 bottles of beer on the wall.\n\n15 bottles of beer on the wall, 15 bottles of beer.\nTake one down and pass it around, 14 bottles of beer on the wall.\n\n14 bottles of beer on the wall, 14 bottles of beer.\nTake one down and pass it around, 13 bottles of beer on the wall.\n\n13 bottles of beer on the wall, 13 bottles of beer.\nTake one down and pass it around, 12 bottles of beer on the wall.\n\n12 bottles of beer on the wall, 12 bottles of beer.\nTake one down and pass it around, 11 bottles of beer on the wall.\n\n11 bottles of beer on the wall, 11 bottles of beer.\nTake one down and pass it around, 10 bottles of beer on the wall.\n\n10 bottles of beer on the wall, 10 bottles of beer.\nTake one down and pass it around, 9 bottles of beer on the wall.\n\n9 bottles of beer on the wall, 9 bottles of beer.\nTake one down and pass it around, 8 bottles of beer on the wall.\n\n8 bottles of beer on the wall, 8 bottles of beer.\nTake one down and pass it around, 7 bottles of beer on the wall.\n\n7 bottles of beer on the wall, 7 bottles of beer.\nTake one down and pass it around, 6 bottles of beer on the wall.\n\n6 bottles of beer on the wall, 6 bottles of beer.\nTake one down and pass it around, 5 bottles of beer on the wall.\n\n5 bottles of beer on the wall, 5 bottles of beer.\nTake one down and pass it around, 4 bottles of beer on the wall.\n\n4 bottles of beer on the wall, 4 bottles of beer.\nTake one down and pass it around, 3 bottles of beer on the wall.\n\n3 bottles of beer on the wall, 3 bottles of beer.\nTake one down and pass it around, 2 bottles of beer on the wall.\n\n2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n\n1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n\nNo more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.",
+ );
+ }
+}
diff --git a/exercises/practice/binary-search/.approaches/introduction.md b/exercises/practice/binary-search/.approaches/introduction.md
index 85aa49ae9..d2e2a5092 100644
--- a/exercises/practice/binary-search/.approaches/introduction.md
+++ b/exercises/practice/binary-search/.approaches/introduction.md
@@ -42,23 +42,19 @@ For more information, check the [Looping approach][approach-looping].
```rust
use std::cmp::Ordering;
-fn find_rec, T: Ord>(array: U, key: T, offset: usize) -> Option {
+fn find, T: Ord>(array: U, key: T) -> Option {
let array = array.as_ref();
- if array.len() == 0 {
+ if array.is_empty() {
return None;
}
let mid = array.len() / 2;
match array[mid].cmp(&key) {
- Ordering::Equal => Some(offset + mid),
- Ordering::Less => find_rec(&array[mid + 1..], key, offset + mid + 1),
- Ordering::Greater => find_rec(&array[..mid], key, offset),
+ Ordering::Equal => Some(mid),
+ Ordering::Greater => find(&array[..mid], key),
+ Ordering::Less => find(&array[mid + 1..], key).map(|p| p + mid + 1),
}
}
-
-pub fn find, T: Ord>(array: U, key: T) -> Option {
- find_rec(array, key, 0)
-}
```
For more information, check the [Recursion approach][approach-recursion].
diff --git a/exercises/practice/binary-search/.approaches/looping/content.md b/exercises/practice/binary-search/.approaches/looping/content.md
index 2a31a7bf5..dc598f3b6 100644
--- a/exercises/practice/binary-search/.approaches/looping/content.md
+++ b/exercises/practice/binary-search/.approaches/looping/content.md
@@ -39,7 +39,7 @@ The `T` is constrained to be anything which implements the [`Ord`][ord] trait, w
So, the `key` is of type `T` (orderable), and the `array` is of type `U` (a reference to an indexable container of orderable values
of the same type as the `key`.)
-Although `array` is defined as generic type `U`, which is constrained to be of type `AsRef`,
+Although `array` is defined as generic type `U`, which is constrained to be of type `AsRef`,
the [`as_ref()`][asref] method is used to get the reference to the actual type.
Without it, the compiler would complain that "no method named `len` found for type parameter `U` in the current scope" and
"cannot index into a value of type `U`".
@@ -54,6 +54,7 @@ The [`cmp()`][cmp] method of the `Ord` trait is used to compare that element val
Since the element is a reference, the `key` must also be referenced.
The [`match`][match] arms each use a value from the `Ordering` enum.
+
- If the midpoint element value equals the `key`, then the midpoint is returned from the function wrapped in a [`Some`][some].
- If the midpoint element value is less than the `key`, then the `left` value is adjusted to be one to the right of the midpoint.
- If the midpoint element value is greater than the `key`, then the `right` value is adjusted to be the midpoint.
diff --git a/exercises/practice/binary-search/.approaches/recursion/content.md b/exercises/practice/binary-search/.approaches/recursion/content.md
index f7a5facca..44f0ad692 100644
--- a/exercises/practice/binary-search/.approaches/recursion/content.md
+++ b/exercises/practice/binary-search/.approaches/recursion/content.md
@@ -3,28 +3,24 @@
```rust
use std::cmp::Ordering;
-fn find_rec, T: Ord>(array: U, key: T, offset: usize) -> Option {
+fn find, T: Ord>(array: U, key: T) -> Option {
let array = array.as_ref();
- if array.len() == 0 {
+ if array.is_empty() {
return None;
}
let mid = array.len() / 2;
match array[mid].cmp(&key) {
- Ordering::Equal => Some(offset + mid),
- Ordering::Less => find_rec(&array[mid + 1..], key, offset + mid + 1),
- Ordering::Greater => find_rec(&array[..mid], key, offset),
+ Ordering::Equal => Some(mid),
+ Ordering::Greater => find(&array[..mid], key),
+ Ordering::Less => find(&array[mid + 1..], key).map(|p| p + mid + 1),
}
}
-
-pub fn find, T: Ord>(array: U, key: T) -> Option {
- find_rec(array, key, 0)
-}
```
This approach starts by using the [`Ordering`][ordering-enum] enum.
-The `find_rec()` function has a signature to support the optional generic tests.
+The `find()` function has a signature to support the optional generic tests.
To support slices, arrays and Vecs, which can be of varying lengths and sizes at runtime,
the compiler needs to be given informaton it can know at compile time.
A reference to any of those containers will always be of the same size (essentially the size of a pointer),
@@ -33,16 +29,11 @@ so [`AsRef`][asref] is used to constrain the generic type to be anything that is
The `<[T]>` is used to constrain the reference type to an indexable type `T`.
The `T` is constrained to be anything which implements the [`Ord`][ord] trait, which essentially means the values must be able to be ordered.
-So, the `key` is of type `T` (orderable), and the `array` is of type `U` (a reference to an indexable container of orderable values
-of the same type as the `key`.)
-
-Since slices of the `array` will keep getting shorter with each recursive call to itself, `find_rec()` has an `offset` parameter
-to keep track of the actual midpoint as it relates to the original `array`.
+So, the `key` is of type `T` (orderable), and the `array` is of type `U` (a reference to an indexable container of orderable values of the same type as the `key`.)
-Although `array` is defined as generic type `U`, which is constrained to be of type `AsRef`,
+Although `array` is defined as generic type `U`, which is constrained to be of type `AsRef`,
the [`as_ref()`][asref] method is used to get the reference to the actual type.
-Without it, the compiler would complain that "no method named `len` found for type parameter `U` in the current scope" and
-"cannot index into a value of type `U`".
+Without it, the compiler would complain that "no method named `len` found for type parameter `U` in the current scope" and "cannot index into a value of type `U`".
If the `array` is empty, then [`None`][none] is returned.
@@ -51,19 +42,15 @@ The [`cmp()`][cmp] method of the `Ord` trait is used to compare that element val
Since the element is a reference, the `key` must also be referenced.
The [`match`][match] arms each use a value from the `Ordering` enum.
-- If the midpoint element value equals the `key`, then the midpoint plus the offset is returned from the function wrapped in a [`Some`][some].
-- If the midpoint element value is less than the `key`, then `find_rec()` calls itself,
-passing a slice of the `array` from the element to the right of the midpoint through the end of the `array`.
-The offset is adjusted to be itself plus the midpoint plus `1`.
-- If the midpoint element value is greater than the `key`, then `find_rec()` calls itself,
-passing a slice of the `array` from the beginning up to but not including the midpoint element.
-The offset remains as is.
-While the element value is not equal to the `key`, `find_rec()` keeps calling itself while halving the number of elements being searched,
-until either the `key` is found, or, if it is not in the `array`, the `array` is whittled down to empty.
+- If the midpoint element value equals the `key`, then the midpoint is returned from the function wrapped in a [`Some`][some].
+- If the midpoint element value is greater than the `key`, then `find()` calls itself,
+ passing a slice of the `array` from the beginning up to but not including the midpoint element.
+- If the midpoint element value is less than the `key`, then `find()` calls itself,
+ passing a slice of the `array` from the element to the right of the midpoint through the end of the `array`.
+ The return postion from the recursive call is the midpoint start from the new left(`mid + 1`), so we add `mid + 1` to the return postion.
-The `find()` method returns the final result from calling the `find_rec()` method, passing in the `array`, `key`, and `0` for the initial
-offset value.
+While the element value is not equal to the `key`, `find()` keeps calling itself while halving the number of elements being searched, until either the `key` is found, or, if it is not in the `array`, the `array` is whittled down to empty.
[ordering-enum]: https://doc.rust-lang.org/std/cmp/enum.Ordering.html
[asref]: https://doc.rust-lang.org/std/convert/trait.AsRef.html
diff --git a/exercises/practice/binary-search/.approaches/recursion/snippet.txt b/exercises/practice/binary-search/.approaches/recursion/snippet.txt
index 0c8dd0da7..9d8ef3bd5 100644
--- a/exercises/practice/binary-search/.approaches/recursion/snippet.txt
+++ b/exercises/practice/binary-search/.approaches/recursion/snippet.txt
@@ -1,7 +1,7 @@
let mid = array.len() / 2;
match array[mid].cmp(&key) {
- Ordering::Equal => Some(offset + mid),
- Ordering::Less => find_rec(&array[mid + 1..], key, offset + mid + 1),
- Ordering::Greater => find_rec(&array[..mid], key, offset),
+ Ordering::Equal => Some(mid),
+ Ordering::Greater => find(&array[..mid], key),
+ Ordering::Less => find(&array[mid + 1..], key).map(|p| p + mid + 1),
}
diff --git a/exercises/practice/binary-search/.docs/hints.md b/exercises/practice/binary-search/.docs/hints.md
new file mode 100644
index 000000000..ed2f8ee3c
--- /dev/null
+++ b/exercises/practice/binary-search/.docs/hints.md
@@ -0,0 +1,16 @@
+## General
+
+[Slices](https://doc.rust-lang.org/book/2018-edition/ch04-03-slices.html) have additionally to
+the normal element access via indexing (slice[index]) many useful functions like
+[split_at](https://doc.rust-lang.org/std/primitive.slice.html#method.split_at) or [getting
+subslices](https://doc.rust-lang.org/std/primitive.slice.html#method.get) (slice[start..end]).
+
+You can solve this exercise by just using boring old element access via indexing, but maybe the
+other provided functions can make your code cleaner and safer.
+
+## For Bonus Points
+
+- To get your function working with all kind of elements which can be ordered,
+ have a look at the [Ord Trait](https://doc.rust-lang.org/std/cmp/trait.Ord.html).
+- To get your function working directly on Vec and Array, you can use the
+ [AsRef Trait](https://doc.rust-lang.org/std/convert/trait.AsRef.html)
diff --git a/exercises/practice/binary-search/.docs/instructions.append.md b/exercises/practice/binary-search/.docs/instructions.append.md
index f958a0db5..f2afbf7cb 100644
--- a/exercises/practice/binary-search/.docs/instructions.append.md
+++ b/exercises/practice/binary-search/.docs/instructions.append.md
@@ -6,16 +6,6 @@ Rust provides in its standard library already a
[binary search function](https://doc.rust-lang.org/std/primitive.slice.html#method.binary_search).
For this exercise you should not use this function but just other basic tools instead.
-## Hints
-
-[Slices](https://doc.rust-lang.org/book/2018-edition/ch04-03-slices.html) have additionally to
-the normal element access via indexing (slice[index]) many useful functions like
-[split_at](https://doc.rust-lang.org/std/primitive.slice.html#method.split_at) or [getting
-subslices](https://doc.rust-lang.org/std/primitive.slice.html#method.get) (slice[start..end]).
-
-You can solve this exercise by just using boring old element access via indexing, but maybe the
-other provided functions can make your code cleaner and safer.
-
## For bonus points
Did you get the tests passing and the code clean? If you want to, there
@@ -36,10 +26,3 @@ $ cargo test --features generic
Then please share your thoughts in a comment on the submission. Did this
experiment make the code better? Worse? Did you learn anything from it?
-
-### Hints for Bonus Points
-
-- To get your function working with all kind of elements which can be ordered,
- have a look at the [Ord Trait](https://doc.rust-lang.org/std/cmp/trait.Ord.html).
-- To get your function working directly on Vec and Array, you can use the
- [AsRef Trait](https://doc.rust-lang.org/std/convert/trait.AsRef.html)
diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md
index 4dcaba726..12f4358eb 100644
--- a/exercises/practice/binary-search/.docs/instructions.md
+++ b/exercises/practice/binary-search/.docs/instructions.md
@@ -1,35 +1,29 @@
# Instructions
-Implement a binary search algorithm.
+Your task is to implement a binary search algorithm.
-Searching a sorted collection is a common task. A dictionary is a sorted
-list of word definitions. Given a word, one can find its definition. A
-telephone book is a sorted list of people's names, addresses, and
-telephone numbers. Knowing someone's name allows one to quickly find
-their telephone number and address.
+A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for.
+It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations.
-If the list to be searched contains more than a few items (a dozen, say)
-a binary search will require far fewer comparisons than a linear search,
-but it imposes the requirement that the list be sorted.
+~~~~exercism/caution
+Binary search only works when a list has been sorted.
+~~~~
-In computer science, a binary search or half-interval search algorithm
-finds the position of a specified input value (the search "key") within
-an array sorted by key value.
+The algorithm looks like this:
-In each step, the algorithm compares the search key value with the key
-value of the middle element of the array.
+- Find the middle element of a _sorted_ list and compare it with the item we're looking for.
+- If the middle element is our item, then we're done!
+- If the middle element is greater than our item, we can eliminate that element and all the elements **after** it.
+- If the middle element is less than our item, we can eliminate that element and all the elements **before** it.
+- If every element of the list has been eliminated then the item is not in the list.
+- Otherwise, repeat the process on the part of the list that has not been eliminated.
-If the keys match, then a matching element has been found and its index,
-or position, is returned.
+Here's an example:
-Otherwise, if the search key is less than the middle element's key, then
-the algorithm repeats its action on the sub-array to the left of the
-middle element or, if the search key is greater, on the sub-array to the
-right.
+Let's say we're looking for the number 23 in the following sorted list: `[4, 8, 12, 16, 23, 28, 32]`.
-If the remaining array to be searched is empty, then the key cannot be
-found in the array and a special "not found" indication is returned.
-
-A binary search halves the number of items to check with each iteration,
-so locating an item (or determining its absence) takes logarithmic time.
-A binary search is a dichotomic divide and conquer search algorithm.
+- We start by comparing 23 with the middle element, 16.
+- Since 23 is greater than 16, we can eliminate the left half of the list, leaving us with `[23, 28, 32]`.
+- We then compare 23 with the new middle element, 28.
+- Since 23 is less than 28, we can eliminate the right half of the list: `[23]`.
+- We've found our item.
diff --git a/exercises/practice/binary-search/.docs/introduction.md b/exercises/practice/binary-search/.docs/introduction.md
new file mode 100644
index 000000000..03496599e
--- /dev/null
+++ b/exercises/practice/binary-search/.docs/introduction.md
@@ -0,0 +1,13 @@
+# Introduction
+
+You have stumbled upon a group of mathematicians who are also singer-songwriters.
+They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers (like [0][zero] or [73][seventy-three] or [6174][kaprekars-constant]).
+
+You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while.
+Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about.
+
+You realize that you can use a binary search algorithm to quickly find a song given the title.
+
+[zero]: https://en.wikipedia.org/wiki/0
+[seventy-three]: https://en.wikipedia.org/wiki/73_(number)
+[kaprekars-constant]: https://en.wikipedia.org/wiki/6174_(number)
diff --git a/exercises/practice/binary-search/.gitignore b/exercises/practice/binary-search/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/binary-search/.gitignore
+++ b/exercises/practice/binary-search/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/binary-search/.meta/config.json b/exercises/practice/binary-search/.meta/config.json
index 7937f5db7..cc8c2d21f 100644
--- a/exercises/practice/binary-search/.meta/config.json
+++ b/exercises/practice/binary-search/.meta/config.json
@@ -22,7 +22,7 @@
"Cargo.toml"
],
"test": [
- "tests/binary-search.rs"
+ "tests/binary_search.rs"
],
"example": [
".meta/example.rs"
@@ -30,5 +30,5 @@
},
"blurb": "Implement a binary search algorithm.",
"source": "Wikipedia",
- "source_url": "http://en.wikipedia.org/wiki/Binary_search_algorithm"
+ "source_url": "https://en.wikipedia.org/wiki/Binary_search_algorithm"
}
diff --git a/exercises/practice/binary-search/.meta/test_template.tera b/exercises/practice/binary-search/.meta/test_template.tera
new file mode 100644
index 000000000..34fa5f46e
--- /dev/null
+++ b/exercises/practice/binary-search/.meta/test_template.tera
@@ -0,0 +1,37 @@
+use binary_search::*;
+
+{% for test in cases %}
+#[test]
+#[ignore]
+fn {{ test.description | make_ident }}() {
+ assert_eq!(find(&{{ test.input.array | json_encode() }}, {{ test.input.value }}), {% if test.expected is object -%}
+ None
+ {%- else -%}
+ Some({{ test.expected }})
+ {%- endif -%});
+}
+{% endfor %}
+
+#[test]
+#[ignore]
+#[cfg(feature = "generic")]
+fn works_for_arrays() {
+ assert_eq!(find([6], 6), Some(0));
+}
+
+#[test]
+#[ignore]
+#[cfg(feature = "generic")]
+fn works_for_vec() {
+ let vector = vec![6];
+ assert_eq!(find(&vector, 6), Some(0));
+ assert_eq!(find(vector, 6), Some(0));
+}
+
+#[test]
+#[ignore]
+#[cfg(feature = "generic")]
+fn works_for_str_elements() {
+ assert_eq!(find(["a"], "a"), Some(0));
+ assert_eq!(find(["a", "b"], "b"), Some(1));
+}
diff --git a/exercises/practice/binary-search/.meta/tests.toml b/exercises/practice/binary-search/.meta/tests.toml
index 4bd95d1c4..61e2b0682 100644
--- a/exercises/practice/binary-search/.meta/tests.toml
+++ b/exercises/practice/binary-search/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
[b55c24a9-a98d-4379-a08c-2adcf8ebeee8]
description = "finds a value in an array with one element"
@@ -23,5 +30,14 @@ description = "finds a value in an array of even length"
[da7db20a-354f-49f7-a6a1-650a54998aa6]
description = "identifies that a value is not included in the array"
+[95d869ff-3daf-4c79-b622-6e805c675f97]
+description = "a value smaller than the array's smallest value is not found"
+
+[8b24ef45-6e51-4a94-9eac-c2bf38fdb0ba]
+description = "a value larger than the array's largest value is not found"
+
+[f439a0fa-cf42-4262-8ad1-64bf41ce566a]
+description = "nothing is found in an empty array"
+
[2c353967-b56d-40b8-acff-ce43115eed64]
description = "nothing is found when the left and right bounds cross"
diff --git a/exercises/practice/binary-search/Cargo.toml b/exercises/practice/binary-search/Cargo.toml
index 6cfecd68d..f7721fb90 100644
--- a/exercises/practice/binary-search/Cargo.toml
+++ b/exercises/practice/binary-search/Cargo.toml
@@ -1,9 +1,15 @@
[package]
-edition = "2021"
-name = "binary-search"
-version = "1.3.0"
+name = "binary_search"
+version = "0.1.0"
+edition = "2024"
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
[dependencies]
[features]
generic = []
+
+[lints.clippy]
+needless_borrows_for_generic_args = "allow"
diff --git a/exercises/practice/binary-search/src/lib.rs b/exercises/practice/binary-search/src/lib.rs
index 5b10dadac..fbdd6d6fd 100644
--- a/exercises/practice/binary-search/src/lib.rs
+++ b/exercises/practice/binary-search/src/lib.rs
@@ -1,5 +1,5 @@
pub fn find(array: &[i32], key: i32) -> Option {
- unimplemented!(
+ todo!(
"Using the binary search algorithm, find the element '{key}' in the array '{array:?}' and return its index."
);
}
diff --git a/exercises/practice/binary-search/tests/binary-search.rs b/exercises/practice/binary-search/tests/binary_search.rs
similarity index 73%
rename from exercises/practice/binary-search/tests/binary-search.rs
rename to exercises/practice/binary-search/tests/binary_search.rs
index af0cbae26..1f9ae3bb3 100644
--- a/exercises/practice/binary-search/tests/binary-search.rs
+++ b/exercises/practice/binary-search/tests/binary_search.rs
@@ -1,27 +1,10 @@
-// 1.65.0 says these &[] borrows are needless borrows,
-// but 1.64.0 requires them.
-// The Rust track will reevaluate this after 1.65.0 is released.
-#![allow(clippy::needless_borrow)]
-
-use binary_search::find;
+use binary_search::*;
#[test]
fn finds_a_value_in_an_array_with_one_element() {
assert_eq!(find(&[6], 6), Some(0));
}
-#[test]
-#[ignore]
-fn finds_first_value_in_an_array_with_two_element() {
- assert_eq!(find(&[1, 2], 1), Some(0));
-}
-
-#[test]
-#[ignore]
-fn finds_second_value_in_an_array_with_two_element() {
- assert_eq!(find(&[1, 2], 2), Some(1));
-}
-
#[test]
#[ignore]
fn finds_a_value_in_the_middle_of_an_array() {
@@ -66,19 +49,19 @@ fn identifies_that_a_value_is_not_included_in_the_array() {
#[test]
#[ignore]
-fn a_value_smaller_than_the_arrays_smallest_value_is_not_included() {
+fn a_value_smaller_than_the_array_s_smallest_value_is_not_found() {
assert_eq!(find(&[1, 3, 4, 6, 8, 9, 11], 0), None);
}
#[test]
#[ignore]
-fn a_value_larger_than_the_arrays_largest_value_is_not_included() {
+fn a_value_larger_than_the_array_s_largest_value_is_not_found() {
assert_eq!(find(&[1, 3, 4, 6, 8, 9, 11], 13), None);
}
#[test]
#[ignore]
-fn nothing_is_included_in_an_empty_array() {
+fn nothing_is_found_in_an_empty_array() {
assert_eq!(find(&[], 1), None);
}
diff --git a/exercises/practice/bob/.approaches/answer-array/content.md b/exercises/practice/bob/.approaches/answer-array/content.md
index 20eb68c24..44b877c39 100644
--- a/exercises/practice/bob/.approaches/answer-array/content.md
+++ b/exercises/practice/bob/.approaches/answer-array/content.md
@@ -61,11 +61,11 @@ If the input is a yell, then `is_yelling` is given the value `2`, otherwise it i
The final expression returns the value in the `ANSWERS` array at the index of the combined values for `is_questioning` and `is_yelling`.
-```exercism/note
+~~~~exercism/note
Note that the final line is just `ANSWERS[is_questioning + is_yelling]`
This is because the [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html)
from a function without using `return` and a semicolon.
-```
+~~~~
| is_yelling | is_questioning | Index | Answer |
| ---------- | -------------- | --------- | ------------------------------------- |
diff --git a/exercises/practice/bob/.approaches/if-statements/content.md b/exercises/practice/bob/.approaches/if-statements/content.md
index 38b52a71b..ee5eff8d4 100644
--- a/exercises/practice/bob/.approaches/if-statements/content.md
+++ b/exercises/practice/bob/.approaches/if-statements/content.md
@@ -48,11 +48,11 @@ The uppercasing is done by using the `str` method [`to_uppercase`][to-uppercase]
- If the input is a question, then the function returns the response for that.
- Finally, if the function has not returned by the end, the response for neither a yell nor a question is returned.
-```exercism/note
+~~~~exercism/note
Note that the final line is just `"Whatever."`
This is because the [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html)
from a function without using `return` and a semicolon.
-```
+~~~~
[str]: https://doc.rust-lang.org/std/primitive.str.html
[trim-end]: https://doc.rust-lang.org/std/primitive.str.html#method.trim_end
diff --git a/exercises/practice/bob/.approaches/match-on-tuple/content.md b/exercises/practice/bob/.approaches/match-on-tuple/content.md
index 6a719d21f..94d3fcca1 100644
--- a/exercises/practice/bob/.approaches/match-on-tuple/content.md
+++ b/exercises/practice/bob/.approaches/match-on-tuple/content.md
@@ -43,12 +43,12 @@ Since those values are booleans, each arm of the `match` tests a pattern of bool
- If both the yell value in the tuple is `true` and the question part of the tuple is `true`,
then the response is returned for a yelled question.
-```exercism/note
+~~~~exercism/note
Note that each arm of the `match` is a single-line expression, so `return` is not needed in the responses returned by the `match` arms.
And, since the `match` is the last expression in the function, `return` is not used before `match`.
The [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html)
from a function without using `return` and a semicolon.
-```
+~~~~
- If the tuple is not `(true, true)`, then the next arm of the `match` tests if the yell value in the tuple is `true`.
It uses the [wildcard pattern][wildcard] of `_` to disregard the value for the question part of the tuple.
@@ -59,14 +59,14 @@ If the pattern matches, in other words, if the yell value in the tuple is `true`
This is similar to `default` used in `switch` statements in other languages.
It returns "Whatever." no matter what the values in the tuple are.
-```exercism/note
+~~~~exercism/note
Note that a `match` in Rust must be exhaustive.
This means it must match all conceivable patterns of the value being tested or the code will not compile.
For a boolean value, you can have one arm to match `true` and another to match `false`,
and the compiler will know that the `match` has checked all conceivable patterns for a boolean.
For a value with many possible patterns, such as a `u32`, you can have each arm match whatever patterns you care about,
such as `1` and `2`, and then have one final arm using the wildcard pattern to match on everything else.
-```
+~~~~
[match]: https://doc.rust-lang.org/rust-by-example/flow_control/match.html
[str]: https://doc.rust-lang.org/std/primitive.str.html
diff --git a/exercises/practice/bob/.articles/performance/code/main.rs b/exercises/practice/bob/.articles/performance/code/main.rs
index 1eb8d37bd..ffff8656c 100644
--- a/exercises/practice/bob/.articles/performance/code/main.rs
+++ b/exercises/practice/bob/.articles/performance/code/main.rs
@@ -73,18 +73,18 @@ pub fn reply_array(msg: &str) -> &str {
#[bench]
/// multiple line question for match
-fn test_multiple_line_question_match(b: &mut Bencher) {
+fn multiple_line_question_match(b: &mut Bencher) {
b.iter(|| reply_match("\rDoes this cryogenic chamber make me look fat?\rNo."));
}
#[bench]
/// multiple line question for if statements
-fn test_multiple_line_question_if(b: &mut Bencher) {
+fn multiple_line_question_if(b: &mut Bencher) {
b.iter(|| reply_if_chain("\rDoes this cryogenic chamber make me look fat?\rNo."));
}
#[bench]
/// multiple line question for answer array
-fn test_multiple_line_question_array(b: &mut Bencher) {
+fn multiple_line_question_array(b: &mut Bencher) {
b.iter(|| reply_array("\rDoes this cryogenic chamber make me look fat?\rNo."));
}
diff --git a/exercises/practice/bob/.articles/performance/content.md b/exercises/practice/bob/.articles/performance/content.md
index ec3bbc225..d24be3298 100644
--- a/exercises/practice/bob/.articles/performance/content.md
+++ b/exercises/practice/bob/.articles/performance/content.md
@@ -14,9 +14,9 @@ For our performance investigation, we'll also include a third approach that [use
To benchmark the approaches, we wrote a [small benchmark application][benchmark-application].
```
-test test_multiple_line_question_match ... bench: 96 ns/iter (+/- 17)
-test test_multiple_line_question_if ... bench: 97 ns/iter (+/- 12)
-test test_multiple_line_question_array ... bench: 100 ns/iter (+/- 2)
+test multiple_line_question_match ... bench: 96 ns/iter (+/- 17)
+test multiple_line_question_if ... bench: 97 ns/iter (+/- 12)
+test multiple_line_question_array ... bench: 100 ns/iter (+/- 2)
```
All three approaches are close in performance, but the `if` statements and `match` approaches may be considered to be more idiomatic.
diff --git a/exercises/practice/bob/.articles/performance/snippet.md b/exercises/practice/bob/.articles/performance/snippet.md
index afaf94aa5..f6e718a27 100644
--- a/exercises/practice/bob/.articles/performance/snippet.md
+++ b/exercises/practice/bob/.articles/performance/snippet.md
@@ -1,5 +1,5 @@
```
-test test_multiple_line_question_match ... bench: 96 ns/iter (+/- 17)
-test test_multiple_line_question_if ... bench: 97 ns/iter (+/- 12)
-test test_multiple_line_question_array ... bench: 100 ns/iter (+/- 2)
+test multiple_line_question_match ... bench: 96 ns/iter (+/- 17)
+test multiple_line_question_if ... bench: 97 ns/iter (+/- 12)
+test multiple_line_question_array ... bench: 100 ns/iter (+/- 2)
```
diff --git a/exercises/practice/bob/.docs/instructions.md b/exercises/practice/bob/.docs/instructions.md
index edddb1413..bb702f7bb 100644
--- a/exercises/practice/bob/.docs/instructions.md
+++ b/exercises/practice/bob/.docs/instructions.md
@@ -1,16 +1,19 @@
# Instructions
-Bob is a lackadaisical teenager. In conversation, his responses are very limited.
+Your task is to determine what Bob will reply to someone when they say something to him or ask him a question.
-Bob answers 'Sure.' if you ask him a question, such as "How are you?".
+Bob only ever answers one of five things:
-He answers 'Whoa, chill out!' if you YELL AT HIM (in all capitals).
-
-He answers 'Calm down, I know what I'm doing!' if you yell a question at him.
-
-He says 'Fine. Be that way!' if you address him without actually saying
-anything.
-
-He answers 'Whatever.' to anything else.
-
-Bob's conversational partner is a purist when it comes to written communication and always follows normal rules regarding sentence punctuation in English.
+- **"Sure."**
+ This is his response if you ask him a question, such as "How are you?"
+ The convention used for questions is that it ends with a question mark.
+- **"Whoa, chill out!"**
+ This is his answer if you YELL AT HIM.
+ The convention used for yelling is ALL CAPITAL LETTERS.
+- **"Calm down, I know what I'm doing!"**
+ This is what he says if you yell a question at him.
+- **"Fine. Be that way!"**
+ This is how he responds to silence.
+ The convention used for silence is nothing, or various combinations of whitespace characters.
+- **"Whatever."**
+ This is what he answers to anything else.
diff --git a/exercises/practice/bob/.docs/introduction.md b/exercises/practice/bob/.docs/introduction.md
new file mode 100644
index 000000000..ea4a80776
--- /dev/null
+++ b/exercises/practice/bob/.docs/introduction.md
@@ -0,0 +1,10 @@
+# Introduction
+
+Bob is a [lackadaisical][] teenager.
+He likes to think that he's very cool.
+And he definitely doesn't get excited about things.
+That wouldn't be cool.
+
+When people talk to him, his responses are pretty limited.
+
+[lackadaisical]: https://www.collinsdictionary.com/dictionary/english/lackadaisical
diff --git a/exercises/practice/bob/.gitignore b/exercises/practice/bob/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/bob/.gitignore
+++ b/exercises/practice/bob/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/bob/.meta/config.json b/exercises/practice/bob/.meta/config.json
index 98029fd20..9d8135c40 100644
--- a/exercises/practice/bob/.meta/config.json
+++ b/exercises/practice/bob/.meta/config.json
@@ -46,5 +46,5 @@
},
"blurb": "Bob is a lackadaisical teenager. In conversation, his responses are very limited.",
"source": "Inspired by the 'Deaf Grandma' exercise in Chris Pine's Learn to Program tutorial.",
- "source_url": "http://pine.fm/LearnToProgram/?Chapter=06"
+ "source_url": "https://pine.fm/LearnToProgram/?Chapter=06"
}
diff --git a/exercises/practice/bob/.meta/test_template.tera b/exercises/practice/bob/.meta/test_template.tera
new file mode 100644
index 000000000..20e41953f
--- /dev/null
+++ b/exercises/practice/bob/.meta/test_template.tera
@@ -0,0 +1,9 @@
+use bob::*;
+
+{% for test in cases %}
+#[test]
+#[ignore]
+fn {{ test.description | make_ident }}() {
+ assert_eq!(reply({{ test.input.heyBob | json_encode() }}), "{{ test.expected }}");
+}
+{% endfor -%}
diff --git a/exercises/practice/bob/.meta/tests.toml b/exercises/practice/bob/.meta/tests.toml
index f357dccdb..5299e2895 100644
--- a/exercises/practice/bob/.meta/tests.toml
+++ b/exercises/practice/bob/.meta/tests.toml
@@ -1,9 +1,90 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[e162fead-606f-437a-a166-d051915cea8e]
+description = "stating something"
[73a966dc-8017-47d6-bb32-cf07d1a5fcd9]
description = "shouting"
+[d6c98afd-df35-4806-b55e-2c457c3ab748]
+description = "shouting gibberish"
+
+[8a2e771d-d6f1-4e3f-b6c6-b41495556e37]
+description = "asking a question"
+
+[81080c62-4e4d-4066-b30a-48d8d76920d9]
+description = "asking a numeric question"
+
+[2a02716d-685b-4e2e-a804-2adaf281c01e]
+description = "asking gibberish"
+
+[c02f9179-ab16-4aa7-a8dc-940145c385f7]
+description = "talking forcefully"
+
+[153c0e25-9bb5-4ec5-966e-598463658bcd]
+description = "using acronyms in regular speech"
+
+[a5193c61-4a92-4f68-93e2-f554eb385ec6]
+description = "forceful question"
+
+[a20e0c54-2224-4dde-8b10-bd2cdd4f61bc]
+description = "shouting numbers"
+
+[f7bc4b92-bdff-421e-a238-ae97f230ccac]
+description = "no letters"
+
+[bb0011c5-cd52-4a5b-8bfb-a87b6283b0e2]
+description = "question with no letters"
+
+[496143c8-1c31-4c01-8a08-88427af85c66]
+description = "shouting with special characters"
+
+[e6793c1c-43bd-4b8d-bc11-499aea73925f]
+description = "shouting with no exclamation mark"
+
+[aa8097cc-c548-4951-8856-14a404dd236a]
+description = "statement containing question mark"
+
+[9bfc677d-ea3a-45f2-be44-35bc8fa3753e]
+description = "non-letters with question"
+
+[8608c508-f7de-4b17-985b-811878b3cf45]
+description = "prattling on"
+
[bc39f7c6-f543-41be-9a43-fd1c2f753fc0]
description = "silence"
+
+[d6c47565-372b-4b09-b1dd-c40552b8378b]
+description = "prolonged silence"
+
+[4428f28d-4100-4d85-a902-e5a78cb0ecd3]
+description = "alternate silence"
+
+[66953780-165b-4e7e-8ce3-4bcb80b6385a]
+description = "multiple line question"
+include = false
+
+[5371ef75-d9ea-4103-bcfa-2da973ddec1b]
+description = "starting with whitespace"
+
+[05b304d6-f83b-46e7-81e0-4cd3ca647900]
+description = "ending with whitespace"
+
+[72bd5ad3-9b2f-4931-a988-dce1f5771de2]
+description = "other whitespace"
+
+[12983553-8601-46a8-92fa-fcaa3bc4a2a0]
+description = "non-question ending with whitespace"
+
+[2c7278ac-f955-4eb4-bf8f-e33eb4116a15]
+description = "multiple line question"
+reimplements = "66953780-165b-4e7e-8ce3-4bcb80b6385a"
diff --git a/exercises/practice/bob/Cargo.toml b/exercises/practice/bob/Cargo.toml
index 1ea91729c..f7ab5ddcc 100644
--- a/exercises/practice/bob/Cargo.toml
+++ b/exercises/practice/bob/Cargo.toml
@@ -1,4 +1,9 @@
[package]
-edition = "2021"
name = "bob"
-version = "1.6.0"
+version = "0.1.0"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/practice/bob/src/lib.rs b/exercises/practice/bob/src/lib.rs
index 30b51cbcf..833f2dfca 100644
--- a/exercises/practice/bob/src/lib.rs
+++ b/exercises/practice/bob/src/lib.rs
@@ -1,3 +1,3 @@
pub fn reply(message: &str) -> &str {
- unimplemented!("have Bob reply to the incoming message: {message}")
+ todo!("have Bob reply to the incoming message: {message}")
}
diff --git a/exercises/practice/bob/tests/bob.rs b/exercises/practice/bob/tests/bob.rs
index 330c7ff2b..c94ef8734 100644
--- a/exercises/practice/bob/tests/bob.rs
+++ b/exercises/practice/bob/tests/bob.rs
@@ -1,190 +1,168 @@
-fn process_response_case(phrase: &str, expected_response: &str) {
- assert_eq!(bob::reply(phrase), expected_response);
-}
+use bob::*;
#[test]
-/// stating something
-fn test_stating_something() {
- process_response_case("Tom-ay-to, tom-aaaah-to.", "Whatever.");
+fn stating_something() {
+ assert_eq!(reply("Tom-ay-to, tom-aaaah-to."), "Whatever.");
}
#[test]
#[ignore]
-/// ending with whitespace
-fn test_ending_with_whitespace() {
- process_response_case("Okay if like my spacebar quite a bit? ", "Sure.");
+fn shouting() {
+ assert_eq!(reply("WATCH OUT!"), "Whoa, chill out!");
}
#[test]
#[ignore]
-/// shouting numbers
-fn test_shouting_numbers() {
- process_response_case("1, 2, 3 GO!", "Whoa, chill out!");
+fn shouting_gibberish() {
+ assert_eq!(reply("FCECDFCAAB"), "Whoa, chill out!");
}
#[test]
#[ignore]
-/// other whitespace
-fn test_other_whitespace() {
- process_response_case("\r\r ", "Fine. Be that way!");
+fn asking_a_question() {
+ assert_eq!(
+ reply("Does this cryogenic chamber make me look fat?"),
+ "Sure."
+ );
}
#[test]
#[ignore]
-/// shouting with special characters
-fn test_shouting_with_special_characters() {
- process_response_case(
- "ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!",
- "Whoa, chill out!",
- );
+fn asking_a_numeric_question() {
+ assert_eq!(reply("You are, what, like 15?"), "Sure.");
}
#[test]
#[ignore]
-/// talking forcefully
-fn test_talking_forcefully() {
- process_response_case("Hi there!", "Whatever.");
+fn asking_gibberish() {
+ assert_eq!(reply("fffbbcbeab?"), "Sure.");
}
#[test]
#[ignore]
-/// prattling on
-fn test_prattling_on() {
- process_response_case("Wait! Hang on. Are you going to be OK?", "Sure.");
+fn talking_forcefully() {
+ assert_eq!(reply("Hi there!"), "Whatever.");
}
#[test]
#[ignore]
-/// forceful question
-fn test_forceful_question() {
- process_response_case("WHAT'S GOING ON?", "Calm down, I know what I'm doing!");
+fn using_acronyms_in_regular_speech() {
+ assert_eq!(
+ reply("It's OK if you don't want to go work for NASA."),
+ "Whatever."
+ );
}
#[test]
#[ignore]
-/// shouting with no exclamation mark
-fn test_shouting_with_no_exclamation_mark() {
- process_response_case("I HATE THE DENTIST", "Whoa, chill out!");
+fn forceful_question() {
+ assert_eq!(
+ reply("WHAT'S GOING ON?"),
+ "Calm down, I know what I'm doing!"
+ );
}
#[test]
#[ignore]
-/// asking gibberish
-fn test_asking_gibberish() {
- process_response_case("fffbbcbeab?", "Sure.");
+fn shouting_numbers() {
+ assert_eq!(reply("1, 2, 3 GO!"), "Whoa, chill out!");
}
#[test]
#[ignore]
-/// question with no letters
-fn test_question_with_no_letters() {
- process_response_case("4?", "Sure.");
+fn no_letters() {
+ assert_eq!(reply("1, 2, 3"), "Whatever.");
}
#[test]
#[ignore]
-/// no letters
-fn test_no_letters() {
- process_response_case("1, 2, 3", "Whatever.");
+fn question_with_no_letters() {
+ assert_eq!(reply("4?"), "Sure.");
}
#[test]
#[ignore]
-/// statement containing question mark
-fn test_statement_containing_question_mark() {
- process_response_case("Ending with ? means a question.", "Whatever.");
+fn shouting_with_special_characters() {
+ assert_eq!(
+ reply("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"),
+ "Whoa, chill out!"
+ );
}
-//NEW
#[test]
#[ignore]
-/// multiple line question
-fn test_multiple_line_question() {
- process_response_case(
- "\rDoes this cryogenic chamber make me look fat?\rNo.",
- "Whatever.",
- );
+fn shouting_with_no_exclamation_mark() {
+ assert_eq!(reply("I HATE THE DENTIST"), "Whoa, chill out!");
}
#[test]
#[ignore]
-/// non-question ending with whitespace
-fn test_nonquestion_ending_with_whitespace() {
- process_response_case(
- "This is a statement ending with whitespace ",
- "Whatever.",
- );
+fn statement_containing_question_mark() {
+ assert_eq!(reply("Ending with ? means a question."), "Whatever.");
}
#[test]
#[ignore]
-/// shouting
-fn test_shouting() {
- process_response_case("WATCH OUT!", "Whoa, chill out!");
+fn non_letters_with_question() {
+ assert_eq!(reply(":) ?"), "Sure.");
}
#[test]
#[ignore]
-/// non-letters with question
-fn test_nonletters_with_question() {
- process_response_case(":) ?", "Sure.");
+fn prattling_on() {
+ assert_eq!(reply("Wait! Hang on. Are you going to be OK?"), "Sure.");
}
#[test]
#[ignore]
-/// shouting gibberish
-fn test_shouting_gibberish() {
- process_response_case("FCECDFCAAB", "Whoa, chill out!");
+fn silence() {
+ assert_eq!(reply(""), "Fine. Be that way!");
}
#[test]
#[ignore]
-/// asking a question
-fn test_asking_a_question() {
- process_response_case("Does this cryogenic chamber make me look fat?", "Sure.");
+fn prolonged_silence() {
+ assert_eq!(reply(" "), "Fine. Be that way!");
}
#[test]
#[ignore]
-/// asking a numeric question
-fn test_asking_a_numeric_question() {
- process_response_case("You are, what, like 15?", "Sure.");
+fn alternate_silence() {
+ assert_eq!(reply("\t\t\t\t\t\t\t\t\t\t"), "Fine. Be that way!");
}
#[test]
#[ignore]
-/// silence
-fn test_silence() {
- process_response_case("", "Fine. Be that way!");
+fn starting_with_whitespace() {
+ assert_eq!(reply(" hmmmmmmm..."), "Whatever.");
}
#[test]
#[ignore]
-/// starting with whitespace
-fn test_starting_with_whitespace() {
- process_response_case(" hmmmmmmm...", "Whatever.");
+fn ending_with_whitespace() {
+ assert_eq!(reply("Okay if like my spacebar quite a bit? "), "Sure.");
}
#[test]
#[ignore]
-/// using acronyms in regular speech
-fn test_using_acronyms_in_regular_speech() {
- process_response_case(
- "It's OK if you don't want to go work for NASA.",
- "Whatever.",
- );
+fn other_whitespace() {
+ assert_eq!(reply("\n\r \t"), "Fine. Be that way!");
}
#[test]
#[ignore]
-/// alternate silence
-fn test_alternate_silence() {
- process_response_case(" ", "Fine. Be that way!");
+fn non_question_ending_with_whitespace() {
+ assert_eq!(
+ reply("This is a statement ending with whitespace "),
+ "Whatever."
+ );
}
#[test]
#[ignore]
-/// prolonged silence
-fn test_prolonged_silence() {
- process_response_case(" ", "Fine. Be that way!");
+fn multiple_line_question() {
+ assert_eq!(
+ reply("\nDoes this cryogenic chamber make\n me look fat?"),
+ "Sure."
+ );
}
diff --git a/exercises/practice/book-store/.docs/instructions.md b/exercises/practice/book-store/.docs/instructions.md
index 8ec0a7ba2..54403f17b 100644
--- a/exercises/practice/book-store/.docs/instructions.md
+++ b/exercises/practice/book-store/.docs/instructions.md
@@ -1,12 +1,10 @@
# Instructions
-To try and encourage more sales of different books from a popular 5 book
-series, a bookshop has decided to offer discounts on multiple book purchases.
+To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts on multiple book purchases.
One copy of any of the five books costs $8.
-If, however, you buy two different books, you get a 5%
-discount on those two books.
+If, however, you buy two different books, you get a 5% discount on those two books.
If you buy 3 different books, you get a 10% discount.
@@ -14,14 +12,9 @@ If you buy 4 different books, you get a 20% discount.
If you buy all 5, you get a 25% discount.
-Note: that if you buy four books, of which 3 are
-different titles, you get a 10% discount on the 3 that
-form part of a set, but the fourth book still costs $8.
+Note that if you buy four books, of which 3 are different titles, you get a 10% discount on the 3 that form part of a set, but the fourth book still costs $8.
-Your mission is to write a piece of code to calculate the
-price of any conceivable shopping basket (containing only
-books of the same series), giving as big a discount as
-possible.
+Your mission is to write code to calculate the price of any conceivable shopping basket (containing only books of the same series), giving as big a discount as possible.
For example, how much does this basket of books cost?
@@ -33,36 +26,36 @@ For example, how much does this basket of books cost?
One way of grouping these 8 books is:
-- 1 group of 5 --> 25% discount (1st,2nd,3rd,4th,5th)
-- +1 group of 3 --> 10% discount (1st,2nd,3rd)
+- 1 group of 5 (1st, 2nd,3rd, 4th, 5th)
+- 1 group of 3 (1st, 2nd, 3rd)
This would give a total of:
- 5 books at a 25% discount
-- +3 books at a 10% discount
+- 3 books at a 10% discount
Resulting in:
-- 5 x (8 - 2.00) == 5 x 6.00 == $30.00
-- +3 x (8 - 0.80) == 3 x 7.20 == $21.60
+- 5 × (100% - 25%) × $8 = 5 × $6.00 = $30.00, plus
+- 3 × (100% - 10%) × $8 = 3 × $7.20 = $21.60
-For a total of $51.60
+Which equals $51.60.
However, a different way to group these 8 books is:
-- 1 group of 4 books --> 20% discount (1st,2nd,3rd,4th)
-- +1 group of 4 books --> 20% discount (1st,2nd,3rd,5th)
+- 1 group of 4 books (1st, 2nd, 3rd, 4th)
+- 1 group of 4 books (1st, 2nd, 3rd, 5th)
This would give a total of:
- 4 books at a 20% discount
-- +4 books at a 20% discount
+- 4 books at a 20% discount
Resulting in:
-- 4 x (8 - 1.60) == 4 x 6.40 == $25.60
-- +4 x (8 - 1.60) == 4 x 6.40 == $25.60
+- 4 × (100% - 20%) × $8 = 4 × $6.40 = $25.60, plus
+- 4 × (100% - 20%) × $8 = 4 × $6.40 = $25.60
-For a total of $51.20
+Which equals $51.20.
And $51.20 is the price with the biggest discount.
diff --git a/exercises/practice/book-store/.gitignore b/exercises/practice/book-store/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/book-store/.gitignore
+++ b/exercises/practice/book-store/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/book-store/.meta/Cargo-example.toml b/exercises/practice/book-store/.meta/Cargo-example.toml
index 50711c01a..2178d1a4d 100644
--- a/exercises/practice/book-store/.meta/Cargo-example.toml
+++ b/exercises/practice/book-store/.meta/Cargo-example.toml
@@ -1,6 +1,9 @@
[package]
-edition = "2021"
name = "book_store"
-version = "1.3.0"
+version = "0.1.0"
+edition = "2024"
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
[dependencies]
diff --git a/exercises/practice/book-store/.meta/config.json b/exercises/practice/book-store/.meta/config.json
index 5b7f784ce..2e9f38b6f 100644
--- a/exercises/practice/book-store/.meta/config.json
+++ b/exercises/practice/book-store/.meta/config.json
@@ -20,7 +20,7 @@
"Cargo.toml"
],
"test": [
- "tests/book-store.rs"
+ "tests/book_store.rs"
],
"example": [
".meta/example.rs"
@@ -28,5 +28,5 @@
},
"blurb": "To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts of multiple-book purchases.",
"source": "Inspired by the harry potter kata from Cyber-Dojo.",
- "source_url": "http://cyber-dojo.org/"
+ "source_url": "https://cyber-dojo.org/"
}
diff --git a/exercises/practice/book-store/.meta/example.rs b/exercises/practice/book-store/.meta/example.rs
index dd348f96f..2875827e1 100644
--- a/exercises/practice/book-store/.meta/example.rs
+++ b/exercises/practice/book-store/.meta/example.rs
@@ -3,7 +3,6 @@ use std::cmp::Ordering;
use std::collections::hash_map::DefaultHasher;
use std::collections::{BTreeSet, HashSet};
use std::hash::{Hash, Hasher};
-use std::mem;
type Book = u32;
type Price = u32;
@@ -125,40 +124,40 @@ impl Iterator for DecomposeGroups {
// then move the last item from the most populous group into a new group, alone,
// and return
let return_value = self.next.clone();
- if let Some(groups) = mem::replace(&mut self.next, None) {
- if !(groups.is_empty() || groups.iter().all(|g| g.0.borrow().len() == 1)) {
- let mut hypothetical;
- for mpg_book in groups[0].0.borrow().iter() {
- for (idx, other_group) in groups[1..].iter().enumerate() {
- if !other_group.0.borrow().contains(mpg_book) {
- hypothetical = groups.clone();
- hypothetical[0].0.borrow_mut().remove(mpg_book);
- hypothetical[1 + idx].0.borrow_mut().insert(*mpg_book);
- hypothetical.sort();
- let hypothetical_hash = hash_of(&hypothetical);
- if !self.prev_states.contains(&hypothetical_hash) {
- self.prev_states.insert(hypothetical_hash);
- self.next = Some(hypothetical);
- return return_value;
- }
+ if let Some(groups) = self.next.take()
+ && !(groups.is_empty() || groups.iter().all(|g| g.0.borrow().len() == 1))
+ {
+ let mut hypothetical;
+ for mpg_book in groups[0].0.borrow().iter() {
+ for (idx, other_group) in groups[1..].iter().enumerate() {
+ if !other_group.0.borrow().contains(mpg_book) {
+ hypothetical = groups.clone();
+ hypothetical[0].0.borrow_mut().remove(mpg_book);
+ hypothetical[1 + idx].0.borrow_mut().insert(*mpg_book);
+ hypothetical.sort();
+ let hypothetical_hash = hash_of(&hypothetical);
+ if !self.prev_states.contains(&hypothetical_hash) {
+ self.prev_states.insert(hypothetical_hash);
+ self.next = Some(hypothetical);
+ return return_value;
}
}
}
- // we've gone through all the items of the most populous group,
- // and none of them can be added to any other existing group.
- // We need to create a new group;
- let book = {
- let backing_bt = groups[0].0.borrow();
- let mut book_iter = backing_bt.iter();
- *book_iter.next().unwrap()
- };
- hypothetical = groups;
- hypothetical[0].0.borrow_mut().remove(&book);
- hypothetical.push(Group::new_containing(book));
- hypothetical.sort();
- self.prev_states.insert(hash_of(&hypothetical));
- self.next = Some(hypothetical);
}
+ // we've gone through all the items of the most populous group,
+ // and none of them can be added to any other existing group.
+ // We need to create a new group;
+ let book = {
+ let backing_bt = groups[0].0.borrow();
+ let mut book_iter = backing_bt.iter();
+ *book_iter.next().unwrap()
+ };
+ hypothetical = groups;
+ hypothetical[0].0.borrow_mut().remove(&book);
+ hypothetical.push(Group::new_containing(book));
+ hypothetical.sort();
+ self.prev_states.insert(hash_of(&hypothetical));
+ self.next = Some(hypothetical);
}
return_value
}
@@ -169,7 +168,7 @@ impl DecomposeGroups {
let mut book_groups = Vec::new();
'nextbook: for book in books {
for Group(book_group) in book_groups.iter() {
- if !book_group.borrow().contains(&book) {
+ if !book_group.borrow().contains(book) {
book_group.borrow_mut().insert(*book);
continue 'nextbook;
}
diff --git a/exercises/practice/book-store/.meta/test_template.tera b/exercises/practice/book-store/.meta/test_template.tera
new file mode 100644
index 000000000..702c05b73
--- /dev/null
+++ b/exercises/practice/book-store/.meta/test_template.tera
@@ -0,0 +1,12 @@
+use book_store::*;
+
+{% for test in cases %}
+#[test]
+#[ignore]
+fn {{ test.description | make_ident }}() {
+ let input = &{{ test.input.basket | json_encode() }};
+ let output = lowest_price(input);
+ let expected = {{ test.expected | json_encode() }};
+ assert_eq!(output, expected);
+}
+{% endfor -%}
diff --git a/exercises/practice/book-store/.meta/tests.toml b/exercises/practice/book-store/.meta/tests.toml
index be690e975..4b7ce98be 100644
--- a/exercises/practice/book-store/.meta/tests.toml
+++ b/exercises/practice/book-store/.meta/tests.toml
@@ -1,3 +1,64 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[17146bd5-2e80-4557-ab4c-05632b6b0d01]
+description = "Only a single book"
+
+[cc2de9ac-ff2a-4efd-b7c7-bfe0f43271ce]
+description = "Two of the same book"
+
+[5a86eac0-45d2-46aa-bbf0-266b94393a1a]
+description = "Empty basket"
+
+[158bd19a-3db4-4468-ae85-e0638a688990]
+description = "Two different books"
+
+[f3833f6b-9332-4a1f-ad98-6c3f8e30e163]
+description = "Three different books"
+
+[1951a1db-2fb6-4cd1-a69a-f691b6dd30a2]
+description = "Four different books"
+
+[d70f6682-3019-4c3f-aede-83c6a8c647a3]
+description = "Five different books"
+
+[78cacb57-911a-45f1-be52-2a5bd428c634]
+description = "Two groups of four is cheaper than group of five plus group of three"
+
+[f808b5a4-e01f-4c0d-881f-f7b90d9739da]
+description = "Two groups of four is cheaper than groups of five and three"
+
+[fe96401c-5268-4be2-9d9e-19b76478007c]
+description = "Group of four plus group of two is cheaper than two groups of three"
+
+[68ea9b78-10ad-420e-a766-836a501d3633]
+description = "Two each of first four books and one copy each of rest"
+
+[c0a779d5-a40c-47ae-9828-a340e936b866]
+description = "Two copies of each book"
+
+[18fd86fe-08f1-4b68-969b-392b8af20513]
+description = "Three copies of first book and two each of remaining"
+
+[0b19a24d-e4cf-4ec8-9db2-8899a41af0da]
+description = "Three each of first two books and two each of remaining books"
+
+[bb376344-4fb2-49ab-ab85-e38d8354a58d]
+description = "Four groups of four are cheaper than two groups each of five and three"
+
+[5260ddde-2703-4915-b45a-e54dbbac4303]
+description = "Check that groups of four are created properly even when there are more groups of three than groups of five"
+
+[b0478278-c551-4747-b0fc-7e0be3158b1f]
+description = "One group of one and four is cheaper than one group of two and three"
+
+[cf868453-6484-4ae1-9dfc-f8ee85bbde01]
+description = "One group of one and two plus three groups of four is cheaper than one group of each size"
diff --git a/exercises/practice/book-store/Cargo.toml b/exercises/practice/book-store/Cargo.toml
index 50711c01a..2178d1a4d 100644
--- a/exercises/practice/book-store/Cargo.toml
+++ b/exercises/practice/book-store/Cargo.toml
@@ -1,6 +1,9 @@
[package]
-edition = "2021"
name = "book_store"
-version = "1.3.0"
+version = "0.1.0"
+edition = "2024"
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
[dependencies]
diff --git a/exercises/practice/book-store/src/lib.rs b/exercises/practice/book-store/src/lib.rs
index c8cf0232a..64ee8dd31 100644
--- a/exercises/practice/book-store/src/lib.rs
+++ b/exercises/practice/book-store/src/lib.rs
@@ -1,3 +1,3 @@
pub fn lowest_price(books: &[u32]) -> u32 {
- unimplemented!("Find the lowest price of the bookbasket with books {books:?}")
+ todo!("Find the lowest price of the bookbasket with books {books:?}")
}
diff --git a/exercises/practice/book-store/tests/book-store.rs b/exercises/practice/book-store/tests/book-store.rs
deleted file mode 100644
index e959453b5..000000000
--- a/exercises/practice/book-store/tests/book-store.rs
+++ /dev/null
@@ -1,164 +0,0 @@
-//! Tests for book-store
-//!
-//! Generated by [script][script] using [canonical data][canonical-data]
-//!
-//! [script]: https://github.com/exercism/rust/blob/main/bin/init_exercise.py
-//! [canonical-data]: https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/book-store/canonical_data.json
-
-use book_store::*;
-
-/// Process a single test case for the property `total`
-///
-/// All cases for the `total` property are implemented
-/// in terms of this function.
-///
-/// Expected input format: ('basket', 'targetgrouping')
-fn process_total_case(input: (Vec, Vec>), expected: u32) {
- assert_eq!(lowest_price(&input.0), expected)
-}
-
-// Return the total basket price after applying the best discount.
-// Calculate lowest price for a shopping basket containing books only from
-// a single series. There is no discount advantage for having more than
-// one copy of any single book in a grouping.
-
-#[test]
-/// Only a single book
-fn test_only_a_single_book() {
- process_total_case((vec![1], vec![vec![1]]), 800);
-}
-
-#[test]
-#[ignore]
-/// Two of the same book
-fn test_two_of_the_same_book() {
- process_total_case((vec![2, 2], vec![vec![2], vec![2]]), 1_600);
-}
-
-#[test]
-#[ignore]
-/// Empty basket
-fn test_empty_basket() {
- process_total_case((vec![], vec![]), 0);
-}
-
-#[test]
-#[ignore]
-/// Two different books
-fn test_two_different_books() {
- process_total_case((vec![1, 2], vec![vec![1, 2]]), 1_520);
-}
-
-#[test]
-#[ignore]
-/// Three different books
-fn test_three_different_books() {
- process_total_case((vec![1, 2, 3], vec![vec![1, 2, 3]]), 2_160);
-}
-
-#[test]
-#[ignore]
-/// Four different books
-fn test_four_different_books() {
- process_total_case((vec![1, 2, 3, 4], vec![vec![1, 2, 3, 4]]), 2_560);
-}
-
-#[test]
-#[ignore]
-/// Five different books
-fn test_five_different_books() {
- process_total_case((vec![1, 2, 3, 4, 5], vec![vec![1, 2, 3, 4, 5]]), 3_000);
-}
-
-#[test]
-#[ignore]
-/// Two groups of four is cheaper than group of five plus group of three
-fn test_two_groups_of_four_is_cheaper_than_group_of_five_plus_group_of_three() {
- process_total_case(
- (
- vec![1, 1, 2, 2, 3, 3, 4, 5],
- vec![vec![1, 2, 3, 4], vec![1, 2, 3, 5]],
- ),
- 5_120,
- );
-}
-
-#[test]
-#[ignore]
-/// Group of four plus group of two is cheaper than two groups of three
-fn test_group_of_four_plus_group_of_two_is_cheaper_than_two_groups_of_three() {
- process_total_case(
- (vec![1, 1, 2, 2, 3, 4], vec![vec![1, 2, 3, 4], vec![1, 2]]),
- 4_080,
- );
-}
-
-#[test]
-#[ignore]
-/// Two each of first 4 books and 1 copy each of rest
-fn test_two_each_of_first_4_books_and_1_copy_each_of_rest() {
- process_total_case(
- (
- vec![1, 1, 2, 2, 3, 3, 4, 4, 5],
- vec![vec![1, 2, 3, 4, 5], vec![1, 2, 3, 4]],
- ),
- 5_560,
- );
-}
-
-#[test]
-#[ignore]
-/// Two copies of each book
-fn test_two_copies_of_each_book() {
- process_total_case(
- (
- vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5],
- vec![vec![1, 2, 3, 4, 5], vec![1, 2, 3, 4, 5]],
- ),
- 6_000,
- );
-}
-
-#[test]
-#[ignore]
-/// Three copies of first book and 2 each of remaining
-fn test_three_copies_of_first_book_and_2_each_of_remaining() {
- process_total_case(
- (
- vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1],
- vec![vec![1, 2, 3, 4, 5], vec![1, 2, 3, 4, 5], vec![1]],
- ),
- 6_800,
- );
-}
-
-#[test]
-#[ignore]
-/// Three each of first 2 books and 2 each of remaining books
-fn test_three_each_of_first_2_books_and_2_each_of_remaining_books() {
- process_total_case(
- (
- vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2],
- vec![vec![1, 2, 3, 4, 5], vec![1, 2, 3, 4, 5], vec![1, 2]],
- ),
- 7_520,
- );
-}
-
-#[test]
-#[ignore]
-/// Four groups of four are cheaper than two groups each of five and three
-fn test_four_groups_of_four_are_cheaper_than_two_groups_each_of_five_and_three() {
- process_total_case(
- (
- vec![1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5],
- vec![
- vec![1, 2, 3, 4],
- vec![1, 2, 3, 5],
- vec![1, 2, 3, 4],
- vec![1, 2, 3, 5],
- ],
- ),
- 10_240,
- );
-}
diff --git a/exercises/practice/book-store/tests/book_store.rs b/exercises/practice/book-store/tests/book_store.rs
new file mode 100644
index 000000000..d61a40e53
--- /dev/null
+++ b/exercises/practice/book-store/tests/book_store.rs
@@ -0,0 +1,165 @@
+use book_store::*;
+
+#[test]
+fn only_a_single_book() {
+ let input = &[1];
+ let output = lowest_price(input);
+ let expected = 800;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn two_of_the_same_book() {
+ let input = &[2, 2];
+ let output = lowest_price(input);
+ let expected = 1600;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn empty_basket() {
+ let input = &[];
+ let output = lowest_price(input);
+ let expected = 0;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn two_different_books() {
+ let input = &[1, 2];
+ let output = lowest_price(input);
+ let expected = 1520;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn three_different_books() {
+ let input = &[1, 2, 3];
+ let output = lowest_price(input);
+ let expected = 2160;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn four_different_books() {
+ let input = &[1, 2, 3, 4];
+ let output = lowest_price(input);
+ let expected = 2560;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn five_different_books() {
+ let input = &[1, 2, 3, 4, 5];
+ let output = lowest_price(input);
+ let expected = 3000;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn two_groups_of_four_is_cheaper_than_group_of_five_plus_group_of_three() {
+ let input = &[1, 1, 2, 2, 3, 3, 4, 5];
+ let output = lowest_price(input);
+ let expected = 5120;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn two_groups_of_four_is_cheaper_than_groups_of_five_and_three() {
+ let input = &[1, 1, 2, 3, 4, 4, 5, 5];
+ let output = lowest_price(input);
+ let expected = 5120;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn group_of_four_plus_group_of_two_is_cheaper_than_two_groups_of_three() {
+ let input = &[1, 1, 2, 2, 3, 4];
+ let output = lowest_price(input);
+ let expected = 4080;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn two_each_of_first_four_books_and_one_copy_each_of_rest() {
+ let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5];
+ let output = lowest_price(input);
+ let expected = 5560;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn two_copies_of_each_book() {
+ let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5, 5];
+ let output = lowest_price(input);
+ let expected = 6000;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn three_copies_of_first_book_and_two_each_of_remaining() {
+ let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1];
+ let output = lowest_price(input);
+ let expected = 6800;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn three_each_of_first_two_books_and_two_each_of_remaining_books() {
+ let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2];
+ let output = lowest_price(input);
+ let expected = 7520;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn four_groups_of_four_are_cheaper_than_two_groups_each_of_five_and_three() {
+ let input = &[1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5];
+ let output = lowest_price(input);
+ let expected = 10240;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn check_that_groups_of_four_are_created_properly_even_when_there_are_more_groups_of_three_than_groups_of_five()
+ {
+ let input = &[
+ 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5,
+ ];
+ let output = lowest_price(input);
+ let expected = 14560;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn one_group_of_one_and_four_is_cheaper_than_one_group_of_two_and_three() {
+ let input = &[1, 1, 2, 3, 4];
+ let output = lowest_price(input);
+ let expected = 3360;
+ assert_eq!(output, expected);
+}
+
+#[test]
+#[ignore]
+fn one_group_of_one_and_two_plus_three_groups_of_four_is_cheaper_than_one_group_of_each_size() {
+ let input = &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5];
+ let output = lowest_price(input);
+ let expected = 10000;
+ assert_eq!(output, expected);
+}
diff --git a/exercises/practice/bottle-song/.docs/instructions.md b/exercises/practice/bottle-song/.docs/instructions.md
new file mode 100644
index 000000000..febdfc863
--- /dev/null
+++ b/exercises/practice/bottle-song/.docs/instructions.md
@@ -0,0 +1,57 @@
+# Instructions
+
+Recite the lyrics to that popular children's repetitive song: Ten Green Bottles.
+
+Note that not all verses are identical.
+
+```text
+Ten green bottles hanging on the wall,
+Ten green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be nine green bottles hanging on the wall.
+
+Nine green bottles hanging on the wall,
+Nine green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be eight green bottles hanging on the wall.
+
+Eight green bottles hanging on the wall,
+Eight green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be seven green bottles hanging on the wall.
+
+Seven green bottles hanging on the wall,
+Seven green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be six green bottles hanging on the wall.
+
+Six green bottles hanging on the wall,
+Six green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be five green bottles hanging on the wall.
+
+Five green bottles hanging on the wall,
+Five green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be four green bottles hanging on the wall.
+
+Four green bottles hanging on the wall,
+Four green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be three green bottles hanging on the wall.
+
+Three green bottles hanging on the wall,
+Three green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be two green bottles hanging on the wall.
+
+Two green bottles hanging on the wall,
+Two green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be one green bottle hanging on the wall.
+
+One green bottle hanging on the wall,
+One green bottle hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be no green bottles hanging on the wall.
+```
diff --git a/exercises/practice/bottle-song/.gitignore b/exercises/practice/bottle-song/.gitignore
new file mode 100644
index 000000000..96ef6c0b9
--- /dev/null
+++ b/exercises/practice/bottle-song/.gitignore
@@ -0,0 +1,2 @@
+/target
+Cargo.lock
diff --git a/exercises/practice/bottle-song/.meta/config.json b/exercises/practice/bottle-song/.meta/config.json
new file mode 100644
index 000000000..a0b30d504
--- /dev/null
+++ b/exercises/practice/bottle-song/.meta/config.json
@@ -0,0 +1,20 @@
+{
+ "authors": [
+ "ellnix"
+ ],
+ "files": {
+ "solution": [
+ "src/lib.rs",
+ "Cargo.toml"
+ ],
+ "test": [
+ "tests/bottle_song.rs"
+ ],
+ "example": [
+ ".meta/example.rs"
+ ]
+ },
+ "blurb": "Produce the lyrics to the popular children's repetitive song: Ten Green Bottles.",
+ "source": "Wikipedia",
+ "source_url": "https://en.wikipedia.org/wiki/Ten_Green_Bottles"
+}
diff --git a/exercises/practice/bottle-song/.meta/example.rs b/exercises/practice/bottle-song/.meta/example.rs
new file mode 100644
index 000000000..4dc012617
--- /dev/null
+++ b/exercises/practice/bottle-song/.meta/example.rs
@@ -0,0 +1,39 @@
+pub fn recite(start_bottles: u32, take_down: u32) -> String {
+ (1..=take_down)
+ .map(|taken_down| {
+ let b = start_bottles - taken_down;
+ let b_word = number_to_eng_word(b).to_lowercase();
+ let b_noun = if b == 1 { "bottle" } else { "bottles" };
+ let prev_b = b + 1;
+ let prev_b_word = number_to_eng_word(prev_b);
+ let prev_b_noun = if prev_b == 1 { "bottle" } else { "bottles" };
+ [
+ format!("{prev_b_word} green {prev_b_noun} hanging on the wall"),
+ format!("{prev_b_word} green {prev_b_noun} hanging on the wall"),
+ "And if one green bottle should accidentally fall".into(),
+ format!("There'll be {b_word} green {b_noun} hanging on the wall."),
+ ]
+ .join(",\n")
+ })
+ .collect::>()
+ .join("\n\n")
+ + "\n"
+}
+
+fn number_to_eng_word(digit: u32) -> String {
+ match digit {
+ 0 => "No",
+ 1 => "One",
+ 2 => "Two",
+ 3 => "Three",
+ 4 => "Four",
+ 5 => "Five",
+ 6 => "Six",
+ 7 => "Seven",
+ 8 => "Eight",
+ 9 => "Nine",
+ 10 => "Ten",
+ _ => panic!("Didn't bother adding numbers past 10..."),
+ }
+ .to_string()
+}
diff --git a/exercises/practice/bottle-song/.meta/test_template.tera b/exercises/practice/bottle-song/.meta/test_template.tera
new file mode 100644
index 000000000..f6e2796f3
--- /dev/null
+++ b/exercises/practice/bottle-song/.meta/test_template.tera
@@ -0,0 +1,21 @@
+use bottle_song::*;
+
+{% for group in cases %}
+{% for subgroup in group.cases %}
+{% for test in subgroup.cases %}
+
+#[test]
+#[ignore]
+fn {{ test.description | make_ident }}() {
+ assert_eq!(
+ recite({{ test.input.startBottles }}, {{ test.input.takeDown }}).trim(),
+ concat!(
+ {% for line in test.expected %}
+ "{{line}}{% if not loop.last %}\n{% endif %}",
+ {% endfor %}
+ )
+ );
+}
+{% endfor -%}
+{% endfor -%}
+{% endfor -%}
diff --git a/exercises/practice/bottle-song/.meta/tests.toml b/exercises/practice/bottle-song/.meta/tests.toml
new file mode 100644
index 000000000..1f6e40a37
--- /dev/null
+++ b/exercises/practice/bottle-song/.meta/tests.toml
@@ -0,0 +1,31 @@
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[d4ccf8fc-01dc-48c0-a201-4fbeb30f2d03]
+description = "verse -> single verse -> first generic verse"
+
+[0f0aded3-472a-4c64-b842-18d4f1f5f030]
+description = "verse -> single verse -> last generic verse"
+
+[f61f3c97-131f-459e-b40a-7428f3ed99d9]
+description = "verse -> single verse -> verse with 2 bottles"
+
+[05eadba9-5dbd-401e-a7e8-d17cc9baa8e0]
+description = "verse -> single verse -> verse with 1 bottle"
+
+[a4a28170-83d6-4dc1-bd8b-319b6abb6a80]
+description = "lyrics -> multiple verses -> first two verses"
+
+[3185d438-c5ac-4ce6-bcd3-02c9ff1ed8db]
+description = "lyrics -> multiple verses -> last three verses"
+
+[28c1584a-0e51-4b65-9ae2-fbc0bf4bbb28]
+description = "lyrics -> multiple verses -> all verses"
diff --git a/exercises/practice/bottle-song/Cargo.toml b/exercises/practice/bottle-song/Cargo.toml
new file mode 100644
index 000000000..a133c667a
--- /dev/null
+++ b/exercises/practice/bottle-song/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "bottle_song"
+version = "0.1.0"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
diff --git a/exercises/practice/bottle-song/src/lib.rs b/exercises/practice/bottle-song/src/lib.rs
new file mode 100644
index 000000000..2033677b5
--- /dev/null
+++ b/exercises/practice/bottle-song/src/lib.rs
@@ -0,0 +1,3 @@
+pub fn recite(start_bottles: u32, take_down: u32) -> String {
+ todo!("Return the bottle song starting at {start_bottles} and taking down {take_down} bottles")
+}
diff --git a/exercises/practice/bottle-song/tests/bottle_song.rs b/exercises/practice/bottle-song/tests/bottle_song.rs
new file mode 100644
index 000000000..e2f6d79fc
--- /dev/null
+++ b/exercises/practice/bottle-song/tests/bottle_song.rs
@@ -0,0 +1,158 @@
+use bottle_song::*;
+
+#[test]
+fn first_generic_verse() {
+ assert_eq!(
+ recite(10, 1).trim(),
+ concat!(
+ "Ten green bottles hanging on the wall,\n",
+ "Ten green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be nine green bottles hanging on the wall.",
+ )
+ );
+}
+
+#[test]
+#[ignore]
+fn last_generic_verse() {
+ assert_eq!(
+ recite(3, 1).trim(),
+ concat!(
+ "Three green bottles hanging on the wall,\n",
+ "Three green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be two green bottles hanging on the wall.",
+ )
+ );
+}
+
+#[test]
+#[ignore]
+fn verse_with_2_bottles() {
+ assert_eq!(
+ recite(2, 1).trim(),
+ concat!(
+ "Two green bottles hanging on the wall,\n",
+ "Two green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be one green bottle hanging on the wall.",
+ )
+ );
+}
+
+#[test]
+#[ignore]
+fn verse_with_1_bottle() {
+ assert_eq!(
+ recite(1, 1).trim(),
+ concat!(
+ "One green bottle hanging on the wall,\n",
+ "One green bottle hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be no green bottles hanging on the wall.",
+ )
+ );
+}
+
+#[test]
+#[ignore]
+fn first_two_verses() {
+ assert_eq!(
+ recite(10, 2).trim(),
+ concat!(
+ "Ten green bottles hanging on the wall,\n",
+ "Ten green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be nine green bottles hanging on the wall.\n",
+ "\n",
+ "Nine green bottles hanging on the wall,\n",
+ "Nine green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be eight green bottles hanging on the wall.",
+ )
+ );
+}
+
+#[test]
+#[ignore]
+fn last_three_verses() {
+ assert_eq!(
+ recite(3, 3).trim(),
+ concat!(
+ "Three green bottles hanging on the wall,\n",
+ "Three green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be two green bottles hanging on the wall.\n",
+ "\n",
+ "Two green bottles hanging on the wall,\n",
+ "Two green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be one green bottle hanging on the wall.\n",
+ "\n",
+ "One green bottle hanging on the wall,\n",
+ "One green bottle hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be no green bottles hanging on the wall.",
+ )
+ );
+}
+
+#[test]
+#[ignore]
+fn all_verses() {
+ assert_eq!(
+ recite(10, 10).trim(),
+ concat!(
+ "Ten green bottles hanging on the wall,\n",
+ "Ten green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be nine green bottles hanging on the wall.\n",
+ "\n",
+ "Nine green bottles hanging on the wall,\n",
+ "Nine green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be eight green bottles hanging on the wall.\n",
+ "\n",
+ "Eight green bottles hanging on the wall,\n",
+ "Eight green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be seven green bottles hanging on the wall.\n",
+ "\n",
+ "Seven green bottles hanging on the wall,\n",
+ "Seven green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be six green bottles hanging on the wall.\n",
+ "\n",
+ "Six green bottles hanging on the wall,\n",
+ "Six green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be five green bottles hanging on the wall.\n",
+ "\n",
+ "Five green bottles hanging on the wall,\n",
+ "Five green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be four green bottles hanging on the wall.\n",
+ "\n",
+ "Four green bottles hanging on the wall,\n",
+ "Four green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be three green bottles hanging on the wall.\n",
+ "\n",
+ "Three green bottles hanging on the wall,\n",
+ "Three green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be two green bottles hanging on the wall.\n",
+ "\n",
+ "Two green bottles hanging on the wall,\n",
+ "Two green bottles hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be one green bottle hanging on the wall.\n",
+ "\n",
+ "One green bottle hanging on the wall,\n",
+ "One green bottle hanging on the wall,\n",
+ "And if one green bottle should accidentally fall,\n",
+ "There'll be no green bottles hanging on the wall.",
+ )
+ );
+}
diff --git a/exercises/practice/bowling/.docs/instructions.md b/exercises/practice/bowling/.docs/instructions.md
index be9b27faf..60ccad1b6 100644
--- a/exercises/practice/bowling/.docs/instructions.md
+++ b/exercises/practice/bowling/.docs/instructions.md
@@ -2,35 +2,30 @@
Score a bowling game.
-Bowling is a game where players roll a heavy ball to knock down pins
-arranged in a triangle. Write code to keep track of the score
-of a game of bowling.
+Bowling is a game where players roll a heavy ball to knock down pins arranged in a triangle.
+Write code to keep track of the score of a game of bowling.
## Scoring Bowling
-The game consists of 10 frames. A frame is composed of one or two ball
-throws with 10 pins standing at frame initialization. There are three
-cases for the tabulation of a frame.
+The game consists of 10 frames.
+A frame is composed of one or two ball throws with 10 pins standing at frame initialization.
+There are three cases for the tabulation of a frame.
-* An open frame is where a score of less than 10 is recorded for the
- frame. In this case the score for the frame is the number of pins
- knocked down.
+- An open frame is where a score of less than 10 is recorded for the frame.
+ In this case the score for the frame is the number of pins knocked down.
-* A spare is where all ten pins are knocked down by the second
- throw. The total value of a spare is 10 plus the number of pins
- knocked down in their next throw.
+- A spare is where all ten pins are knocked down by the second throw.
+ The total value of a spare is 10 plus the number of pins knocked down in their next throw.
-* A strike is where all ten pins are knocked down by the first
- throw. The total value of a strike is 10 plus the number of pins
- knocked down in the next two throws. If a strike is immediately
- followed by a second strike, then the value of the first strike
- cannot be determined until the ball is thrown one more time.
+- A strike is where all ten pins are knocked down by the first throw.
+ The total value of a strike is 10 plus the number of pins knocked down in the next two throws.
+ If a strike is immediately followed by a second strike, then the value of the first strike cannot be determined until the ball is thrown one more time.
Here is a three frame example:
-| Frame 1 | Frame 2 | Frame 3 |
-| :-------------: |:-------------:| :---------------------:|
-| X (strike) | 5/ (spare) | 9 0 (open frame) |
+| Frame 1 | Frame 2 | Frame 3 |
+| :--------: | :--------: | :--------------: |
+| X (strike) | 5/ (spare) | 9 0 (open frame) |
Frame 1 is (10 + 5 + 5) = 20
@@ -40,11 +35,11 @@ Frame 3 is (9 + 0) = 9
This means the current running total is 48.
-The tenth frame in the game is a special case. If someone throws a
-strike or a spare then they get a fill ball. Fill balls exist to
-calculate the total of the 10th frame. Scoring a strike or spare on
-the fill ball does not give the player more fill balls. The total
-value of the 10th frame is the total number of pins knocked down.
+The tenth frame in the game is a special case.
+If someone throws a spare or a strike then they get one or two fill balls respectively.
+Fill balls exist to calculate the total of the 10th frame.
+Scoring a strike or spare on the fill ball does not give the player more fill balls.
+The total value of the 10th frame is the total number of pins knocked down.
For a tenth frame of X1/ (strike and a spare), the total value is 20.
@@ -52,10 +47,10 @@ For a tenth frame of XXX (three strikes), the total value is 30.
## Requirements
-Write code to keep track of the score of a game of bowling. It should
-support two operations:
+Write code to keep track of the score of a game of bowling.
+It should support two operations:
-* `roll(pins : int)` is called each time the player rolls a ball. The
- argument is the number of pins knocked down.
-* `score() : int` is called only at the very end of the game. It
- returns the total score for that game.
+- `roll(pins : int)` is called each time the player rolls a ball.
+ The argument is the number of pins knocked down.
+- `score() : int` is called only at the very end of the game.
+ It returns the total score for that game.
diff --git a/exercises/practice/bowling/.gitignore b/exercises/practice/bowling/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/bowling/.gitignore
+++ b/exercises/practice/bowling/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/bowling/.meta/config.json b/exercises/practice/bowling/.meta/config.json
index 478167521..b0787bf90 100644
--- a/exercises/practice/bowling/.meta/config.json
+++ b/exercises/practice/bowling/.meta/config.json
@@ -33,5 +33,5 @@
},
"blurb": "Score a bowling game.",
"source": "The Bowling Game Kata from UncleBob",
- "source_url": "http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata"
+ "source_url": "https://web.archive.org/web/20221001111000/http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata"
}
diff --git a/exercises/practice/bowling/.meta/example.rs b/exercises/practice/bowling/.meta/example.rs
index af7aa50fd..7a879e495 100644
--- a/exercises/practice/bowling/.meta/example.rs
+++ b/exercises/practice/bowling/.meta/example.rs
@@ -43,7 +43,7 @@ impl Frame {
return self.bonus_score() <= 10;
}
- if let Some(first) = self.bonus.get(0) {
+ if let Some(first) = self.bonus.first() {
if *first == 10 {
self.bonus_score() <= 20
} else {
@@ -139,3 +139,9 @@ impl BowlingGame {
self.frames.len() == 10 && self.frames.iter().all(|f| f.is_complete())
}
}
+
+impl Default for BowlingGame {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/exercises/practice/bowling/.meta/tests.toml b/exercises/practice/bowling/.meta/tests.toml
index 7c35295ba..9b1bb4be8 100644
--- a/exercises/practice/bowling/.meta/tests.toml
+++ b/exercises/practice/bowling/.meta/tests.toml
@@ -1,10 +1,32 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[656ae006-25c2-438c-a549-f338e7ec7441]
+description = "should be able to score a game with all zeros"
+
+[f85dcc56-cd6b-4875-81b3-e50921e3597b]
+description = "should be able to score a game with no strikes or spares"
+
+[d1f56305-3ac2-4fe0-8645-0b37e3073e20]
+description = "a spare followed by zeros is worth ten points"
+
+[0b8c8bb7-764a-4287-801a-f9e9012f8be4]
+description = "points scored in the roll after a spare are counted twice"
[4d54d502-1565-4691-84cd-f29a09c65bea]
description = "consecutive spares each get a one roll bonus"
+[e5c9cf3d-abbe-4b74-ad48-34051b2b08c0]
+description = "a spare in the last frame gets a one roll bonus that is counted once"
+
[75269642-2b34-4b72-95a4-9be28ab16902]
description = "a strike earns ten points in a frame with a single roll"
@@ -14,8 +36,69 @@ description = "points scored in the two rolls after a strike are counted twice a
[1635e82b-14ec-4cd1-bce4-4ea14bd13a49]
description = "consecutive strikes each get the two roll bonus"
+[e483e8b6-cb4b-4959-b310-e3982030d766]
+description = "a strike in the last frame gets a two roll bonus that is counted once"
+
+[9d5c87db-84bc-4e01-8e95-53350c8af1f8]
+description = "rolling a spare with the two roll bonus does not get a bonus roll"
+
+[576faac1-7cff-4029-ad72-c16bcada79b5]
+description = "strikes with the two roll bonus do not get bonus rolls"
+
+[efb426ec-7e15-42e6-9b96-b4fca3ec2359]
+description = "last two strikes followed by only last bonus with non strike points"
+
[72e24404-b6c6-46af-b188-875514c0377b]
description = "a strike with the one roll bonus after a spare in the last frame does not get a bonus"
+[62ee4c72-8ee8-4250-b794-234f1fec17b1]
+description = "all strikes is a perfect game"
+
+[1245216b-19c6-422c-b34b-6e4012d7459f]
+description = "rolls cannot score negative points"
+include = false
+
+[5fcbd206-782c-4faa-8f3a-be5c538ba841]
+description = "a roll cannot score more than 10 points"
+
+[fb023c31-d842-422d-ad7e-79ce1db23c21]
+description = "two rolls in a frame cannot score more than 10 points"
+
+[6082d689-d677-4214-80d7-99940189381b]
+description = "bonus roll after a strike in the last frame cannot score more than 10 points"
+
+[e9565fe6-510a-4675-ba6b-733a56767a45]
+description = "two bonus rolls after a strike in the last frame cannot score more than 10 points"
+
+[2f6acf99-448e-4282-8103-0b9c7df99c3d]
+description = "two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike"
+
+[6380495a-8bc4-4cdb-a59f-5f0212dbed01]
+description = "the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike"
+
+[2b2976ea-446c-47a3-9817-42777f09fe7e]
+description = "second bonus roll after a strike in the last frame cannot score more than 10 points"
+
+[29220245-ac8d-463d-bc19-98a94cfada8a]
+description = "an unstarted game cannot be scored"
+
+[4473dc5d-1f86-486f-bf79-426a52ddc955]
+description = "an incomplete game cannot be scored"
+
+[2ccb8980-1b37-4988-b7d1-e5701c317df3]
+description = "cannot roll if game already has ten frames"
+
+[4864f09b-9df3-4b65-9924-c595ed236f1b]
+description = "bonus rolls for a strike in the last frame must be rolled before score can be calculated"
+
+[537f4e37-4b51-4d1c-97e2-986eb37b2ac1]
+description = "both bonus rolls for a strike in the last frame must be rolled before score can be calculated"
+
+[8134e8c1-4201-4197-bf9f-1431afcde4b9]
+description = "bonus roll for a spare in the last frame must be rolled before score can be calculated"
+
[9d4a9a55-134a-4bad-bae8-3babf84bd570]
description = "cannot roll after bonus roll for spare"
+
+[d3e02652-a799-4ae3-b53b-68582cc604be]
+description = "cannot roll after bonus rolls for strike"
diff --git a/exercises/practice/bowling/Cargo.toml b/exercises/practice/bowling/Cargo.toml
index 705fc85f7..81023c878 100644
--- a/exercises/practice/bowling/Cargo.toml
+++ b/exercises/practice/bowling/Cargo.toml
@@ -1,4 +1,12 @@
[package]
-edition = "2021"
name = "bowling"
-version = "1.2.0"
+version = "0.1.0"
+edition = "2024"
+
+# Not all libraries from crates.io are available in Exercism's test runner.
+# The full list of available libraries is here:
+# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
+[dependencies]
+
+[lints.clippy]
+new_without_default = "allow"
diff --git a/exercises/practice/bowling/src/lib.rs b/exercises/practice/bowling/src/lib.rs
index 3b46f51b8..3163e6120 100644
--- a/exercises/practice/bowling/src/lib.rs
+++ b/exercises/practice/bowling/src/lib.rs
@@ -8,14 +8,14 @@ pub struct BowlingGame {}
impl BowlingGame {
pub fn new() -> Self {
- unimplemented!();
+ todo!();
}
pub fn roll(&mut self, pins: u16) -> Result<(), Error> {
- unimplemented!("Record that {pins} pins have been scored");
+ todo!("Record that {pins} pins have been scored");
}
pub fn score(&self) -> Option {
- unimplemented!("Return the score if the game is complete, or None if not.");
+ todo!("Return the score if the game is complete, or None if not.");
}
}
diff --git a/exercises/practice/bowling/tests/bowling.rs b/exercises/practice/bowling/tests/bowling.rs
index 2c46d15df..906568f2f 100644
--- a/exercises/practice/bowling/tests/bowling.rs
+++ b/exercises/practice/bowling/tests/bowling.rs
@@ -8,7 +8,7 @@ fn roll_returns_a_result() {
#[test]
#[ignore]
-fn you_cannot_roll_more_than_ten_pins_in_a_single_roll() {
+fn a_roll_cannot_score_more_than_10_points() {
let mut game = BowlingGame::new();
assert_eq!(game.roll(11), Err(Error::NotEnoughPinsLeft));
@@ -16,7 +16,7 @@ fn you_cannot_roll_more_than_ten_pins_in_a_single_roll() {
#[test]
#[ignore]
-fn a_game_score_is_some_if_ten_frames_have_been_rolled() {
+fn should_be_able_to_score_a_game_with_all_zeros() {
let mut game = BowlingGame::new();
for _ in 0..10 {
@@ -29,7 +29,7 @@ fn a_game_score_is_some_if_ten_frames_have_been_rolled() {
#[test]
#[ignore]
-fn you_cannot_score_a_game_with_no_rolls() {
+fn an_unstarted_game_cannot_be_scored() {
let game = BowlingGame::new();
assert_eq!(game.score(), None);
@@ -37,7 +37,7 @@ fn you_cannot_score_a_game_with_no_rolls() {
#[test]
#[ignore]
-fn a_game_score_is_none_if_fewer_than_ten_frames_have_been_rolled() {
+fn an_incomplete_game_cannot_be_scored() {
let mut game = BowlingGame::new();
for _ in 0..9 {
@@ -50,7 +50,7 @@ fn a_game_score_is_none_if_fewer_than_ten_frames_have_been_rolled() {
#[test]
#[ignore]
-fn a_roll_is_err_if_the_game_is_done() {
+fn cannot_roll_if_game_already_has_ten_frames() {
let mut game = BowlingGame::new();
for _ in 0..10 {
@@ -75,7 +75,7 @@ fn twenty_zero_pin_rolls_scores_zero() {
#[test]
#[ignore]
-fn ten_frames_without_a_strike_or_spare() {
+fn should_be_able_to_score_a_game_with_no_strikes_or_spares() {
let mut game = BowlingGame::new();
for _ in 0..10 {
@@ -88,7 +88,7 @@ fn ten_frames_without_a_strike_or_spare() {
#[test]
#[ignore]
-fn spare_in_the_first_frame_followed_by_zeros() {
+fn a_spare_followed_by_zeros_is_worth_ten_points() {
let mut game = BowlingGame::new();
let _ = game.roll(6);
@@ -103,7 +103,7 @@ fn spare_in_the_first_frame_followed_by_zeros() {
#[test]
#[ignore]
-fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() {
+fn points_scored_in_the_roll_after_a_spare_are_counted_twice() {
let mut game = BowlingGame::new();
let _ = game.roll(6);
@@ -137,7 +137,7 @@ fn consecutive_spares_each_get_a_one_roll_bonus() {
#[test]
#[ignore]
-fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() {
+fn a_spare_in_the_last_frame_gets_a_one_roll_bonus_that_is_counted_once() {
let mut game = BowlingGame::new();
for _ in 0..18 {
@@ -217,7 +217,7 @@ fn a_strike_in_the_last_frame_earns_a_two_roll_bonus_that_is_counted_once() {
#[test]
#[ignore]
-fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() {
+fn rolling_a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() {
let mut game = BowlingGame::new();
for _ in 0..18 {
@@ -265,7 +265,7 @@ fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_frame_does_not_get
#[test]
#[ignore]
-fn all_strikes_is_a_perfect_score_of_300() {
+fn all_strikes_is_a_perfect_game() {
let mut game = BowlingGame::new();
for _ in 0..12 {
@@ -277,7 +277,7 @@ fn all_strikes_is_a_perfect_score_of_300() {
#[test]
#[ignore]
-fn you_cannot_roll_more_than_ten_pins_in_a_single_frame() {
+fn two_rolls_in_a_frame_cannot_score_more_than_10_points() {
let mut game = BowlingGame::new();
assert!(game.roll(5).is_ok());
@@ -286,7 +286,7 @@ fn you_cannot_roll_more_than_ten_pins_in_a_single_frame() {
#[test]
#[ignore]
-fn first_bonus_ball_after_a_final_strike_cannot_score_an_invalid_number_of_pins() {
+fn bonus_roll_after_a_strike_in_the_last_frame_cannot_score_more_than_10_points() {
let mut game = BowlingGame::new();
for _ in 0..18 {
@@ -300,7 +300,7 @@ fn first_bonus_ball_after_a_final_strike_cannot_score_an_invalid_number_of_pins(
#[test]
#[ignore]
-fn the_two_balls_after_a_final_strike_cannot_score_an_invalid_number_of_pins() {
+fn two_bonus_rolls_after_a_strike_in_the_last_frame_cannot_score_more_than_10_points() {
let mut game = BowlingGame::new();
for _ in 0..18 {
@@ -315,7 +315,8 @@ fn the_two_balls_after_a_final_strike_cannot_score_an_invalid_number_of_pins() {
#[test]
#[ignore]
-fn the_two_balls_after_a_final_strike_can_be_a_strike_and_non_strike() {
+fn two_bonus_rolls_after_a_strike_in_the_last_frame_can_score_more_than_10_points_if_one_is_a_strike()
+ {
let mut game = BowlingGame::new();
for _ in 0..18 {
@@ -330,7 +331,8 @@ fn the_two_balls_after_a_final_strike_can_be_a_strike_and_non_strike() {
#[test]
#[ignore]
-fn the_two_balls_after_a_final_strike_cannot_be_a_non_strike_followed_by_a_strike() {
+fn the_second_bonus_rolls_after_a_strike_in_the_last_frame_cannot_be_a_strike_if_the_first_one_is_not_a_strike()
+ {
let mut game = BowlingGame::new();
for _ in 0..18 {
@@ -345,8 +347,7 @@ fn the_two_balls_after_a_final_strike_cannot_be_a_non_strike_followed_by_a_strik
#[test]
#[ignore]
-fn second_bonus_ball_after_a_final_strike_cannot_score_an_invalid_number_of_pins_even_if_first_is_strike(
-) {
+fn second_bonus_roll_after_a_strike_in_the_last_frame_cannot_score_more_than_10_points() {
let mut game = BowlingGame::new();
for _ in 0..18 {
@@ -361,7 +362,7 @@ fn second_bonus_ball_after_a_final_strike_cannot_score_an_invalid_number_of_pins
#[test]
#[ignore]
-fn if_the_last_frame_is_a_strike_you_cannot_score_before_the_extra_rolls_are_taken() {
+fn bonus_rolls_for_a_strike_in_the_last_frame_must_be_rolled_before_score_can_be_calculated() {
let mut game = BowlingGame::new();
for _ in 0..18 {
@@ -371,19 +372,28 @@ fn if_the_last_frame_is_a_strike_you_cannot_score_before_the_extra_rolls_are_tak
let _ = game.roll(10);
assert_eq!(game.score(), None);
+}
- let _ = game.roll(10);
+#[test]
+#[ignore]
+fn both_bonus_rolls_for_a_strike_in_the_last_frame_must_be_rolled_before_score_can_be_calculated() {
+ let mut game = BowlingGame::new();
- assert_eq!(game.score(), None);
+ for _ in 0..18 {
+ let _ = game.roll(0);
+ }
let _ = game.roll(10);
+ let _ = game.roll(10);
+ assert_eq!(game.score(), None);
+ let _ = game.roll(10);
assert!(game.score().is_some());
}
#[test]
#[ignore]
-fn if_the_last_frame_is_a_spare_you_cannot_create_a_score_before_extra_roll_is_taken() {
+fn bonus_roll_for_a_spare_in_the_last_frame_must_be_rolled_before_score_can_be_calculated() {
let mut game = BowlingGame::new();
for _ in 0..18 {
@@ -402,7 +412,7 @@ fn if_the_last_frame_is_a_spare_you_cannot_create_a_score_before_extra_roll_is_t
#[test]
#[ignore]
-fn cannot_roll_after_bonus_roll_for_spare() {
+fn cannot_roll_after_bonus_rolls_for_strike() {
let mut game = BowlingGame::new();
for _ in 0..9 {
diff --git a/exercises/practice/circular-buffer/.docs/instructions.md b/exercises/practice/circular-buffer/.docs/instructions.md
index e9b00b91d..2ba1fda2a 100644
--- a/exercises/practice/circular-buffer/.docs/instructions.md
+++ b/exercises/practice/circular-buffer/.docs/instructions.md
@@ -1,51 +1,58 @@
# Instructions
-A circular buffer, cyclic buffer or ring buffer is a data structure that
-uses a single, fixed-size buffer as if it were connected end-to-end.
-
-A circular buffer first starts empty and of some predefined length. For
-example, this is a 7-element buffer:
-
- [ ][ ][ ][ ][ ][ ][ ]
-
-Assume that a 1 is written into the middle of the buffer (exact starting
-location does not matter in a circular buffer):
-
- [ ][ ][ ][1][ ][ ][ ]
-
-Then assume that two more elements are added — 2 & 3 — which get
-appended after the 1:
-
- [ ][ ][ ][1][2][3][ ]
-
-If two elements are then removed from the buffer, the oldest values
-inside the buffer are removed. The two elements removed, in this case,
-are 1 & 2, leaving the buffer with just a 3:
-
- [ ][ ][ ][ ][ ][3][ ]
+A circular buffer, cyclic buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end.
+
+A circular buffer first starts empty and of some predefined length.
+For example, this is a 7-element buffer:
+
+```text
+[ ][ ][ ][ ][ ][ ][ ]
+```
+
+Assume that a 1 is written into the middle of the buffer (exact starting location does not matter in a circular buffer):
+
+```text
+[ ][ ][ ][1][ ][ ][ ]
+```
+
+Then assume that two more elements are added — 2 & 3 — which get appended after the 1:
+
+```text
+[ ][ ][ ][1][2][3][ ]
+```
+
+If two elements are then removed from the buffer, the oldest values inside the buffer are removed.
+The two elements removed, in this case, are 1 & 2, leaving the buffer with just a 3:
+
+```text
+[ ][ ][ ][ ][ ][3][ ]
+```
If the buffer has 7 elements then it is completely full:
-
- [5][6][7][8][9][3][4]
-
-When the buffer is full an error will be raised, alerting the client
-that further writes are blocked until a slot becomes free.
-
-When the buffer is full, the client can opt to overwrite the oldest
-data with a forced write. In this case, two more elements — A & B —
-are added and they overwrite the 3 & 4:
-
- [5][6][7][8][9][A][B]
-
-3 & 4 have been replaced by A & B making 5 now the oldest data in the
-buffer. Finally, if two elements are removed then what would be
-returned is 5 & 6 yielding the buffer:
-
- [ ][ ][7][8][9][A][B]
-
-Because there is space available, if the client again uses overwrite
-to store C & D then the space where 5 & 6 were stored previously will
-be used not the location of 7 & 8. 7 is still the oldest element and
-the buffer is once again full.
-
- [C][D][7][8][9][A][B]
+
+```text
+[5][6][7][8][9][3][4]
+```
+
+When the buffer is full an error will be raised, alerting the client that further writes are blocked until a slot becomes free.
+
+When the buffer is full, the client can opt to overwrite the oldest data with a forced write.
+In this case, two more elements — A & B — are added and they overwrite the 3 & 4:
+
+```text
+[5][6][7][8][9][A][B]
+```
+
+3 & 4 have been replaced by A & B making 5 now the oldest data in the buffer.
+Finally, if two elements are removed then what would be returned is 5 & 6 yielding the buffer:
+
+```text
+[ ][ ][7][8][9][A][B]
+```
+
+Because there is space available, if the client again uses overwrite to store C & D then the space where 5 & 6 were stored previously will be used not the location of 7 & 8.
+7 is still the oldest element and the buffer is once again full.
+
+```text
+[C][D][7][8][9][A][B]
+```
diff --git a/exercises/practice/circular-buffer/.gitignore b/exercises/practice/circular-buffer/.gitignore
index db7f315c0..96ef6c0b9 100644
--- a/exercises/practice/circular-buffer/.gitignore
+++ b/exercises/practice/circular-buffer/.gitignore
@@ -1,8 +1,2 @@
-# Generated by Cargo
-# will have compiled files and executables
-/target/
-**/*.rs.bk
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+/target
Cargo.lock
diff --git a/exercises/practice/circular-buffer/.meta/additional-tests.json b/exercises/practice/circular-buffer/.meta/additional-tests.json
new file mode 100644
index 000000000..769940038
--- /dev/null
+++ b/exercises/practice/circular-buffer/.meta/additional-tests.json
@@ -0,0 +1,42 @@
+[
+ {
+ "uuid": "8a9c5921-a266-4c11-b392-3b2fbb53517c",
+ "description": "char buffer",
+ "comments": [
+ "The upstream tests intentionally do not cover generics,",
+ "but recommends individual languages may do it."
+ ],
+ "property": "generic",
+ "input": {
+ "capacity": 1,
+ "operations": [
+ {
+ "operation": "write",
+ "item": "'A'",
+ "should_succeed": true
+ }
+ ]
+ },
+ "expected": {}
+ },
+ {
+ "uuid": "8a9c5921-a266-4c11-b392-3b2fbb53517c",
+ "description": "string buffer",
+ "comments": [
+ "The upstream tests intentionally do not cover generics,",
+ "but recommends individual languages may do it."
+ ],
+ "property": "generic",
+ "input": {
+ "capacity": 1,
+ "operations": [
+ {
+ "operation": "write",
+ "item": "\"Testing\".to_string()",
+ "should_succeed": true
+ }
+ ]
+ },
+ "expected": {}
+ }
+]
diff --git a/exercises/practice/circular-buffer/.meta/config.json b/exercises/practice/circular-buffer/.meta/config.json
index 37e850ca4..7a40a5e30 100644
--- a/exercises/practice/circular-buffer/.meta/config.json
+++ b/exercises/practice/circular-buffer/.meta/config.json
@@ -29,7 +29,7 @@
"Cargo.toml"
],
"test": [
- "tests/circular-buffer.rs"
+ "tests/circular_buffer.rs"
],
"example": [
".meta/example.rs"
@@ -37,5 +37,5 @@
},
"blurb": "A data structure that uses a single, fixed-size buffer as if it were connected end-to-end.",
"source": "Wikipedia",
- "source_url": "http://en.wikipedia.org/wiki/Circular_buffer"
+ "source_url": "https://en.wikipedia.org/wiki/Circular_buffer"
}
diff --git a/exercises/practice/circular-buffer/.meta/example.rs b/exercises/practice/circular-buffer/.meta/example.rs
index 489891063..b85bcb558 100644
--- a/exercises/practice/circular-buffer/.meta/example.rs
+++ b/exercises/practice/circular-buffer/.meta/example.rs
@@ -4,20 +4,21 @@ pub enum Error {
FullBuffer,
}
-pub struct CircularBuffer {
- buffer: Vec,
- size: usize,
+pub struct CircularBuffer {
+ /// Using Option leads to less efficient memory layout, but
+ /// it allows us to avoid using `unsafe` to handle uninitialized
+ /// mempory ourselves.
+ data: Vec