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 +
-[![CI](https://github.com/exercism/rust/workflows/CI/badge.svg?branch=main)](https://github.com/exercism/rust/actions?query=workflow%3ACI+branch%3Amain) + +

Exercism Rust Track

-Exercism exercises in Rust +                          [![Discourse topics](https://img.shields.io/discourse/topics?color=8A08E6&label=Connect%20&labelColor=FFDF58&logo=Discourse&logoColor=8A08E6&server=https%3A%2F%2Fforum.exercism.org&style=social)](https://forum.exercism.org) +  [![Exercism_III](https://img.shields.io/badge/PAUSED-C73D4E?labelColor=3D454D&label=Contributions)](https://exercism.org/blog/freeing-our-maintainers) +  [![CI](https://github.com/exercism/rust/workflows/CI/badge.svg?branch=main)](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>, start: usize, end: usize, } -impl CircularBuffer { - // this circular buffer keeps an unallocated slot between the start and the end - // when the buffer is full. - pub fn new(size: usize) -> CircularBuffer { - CircularBuffer { - buffer: vec![T::default(); size + 1], - size: size + 1, +impl CircularBuffer { + pub fn new(capacity: usize) -> Self { + let mut data = Vec::with_capacity(capacity); + data.resize_with(capacity, || None); + Self { + data, start: 0, end: 0, } @@ -27,8 +28,9 @@ impl CircularBuffer { if self.is_empty() { return Err(Error::EmptyBuffer); } - - let v = self.buffer[self.start].clone(); + let v = self.data[self.start] + .take() + .expect("should not read 'uninitialized' memory"); self.advance_start(); Ok(v) } @@ -37,18 +39,16 @@ impl CircularBuffer { if self.is_full() { return Err(Error::FullBuffer); } - - self.buffer[self.end] = byte; + self.data[self.end] = Some(byte); self.advance_end(); Ok(()) } pub fn overwrite(&mut self, byte: T) { - if self.is_full() { + self.data[self.end] = Some(byte); + if self.start == self.end { self.advance_start(); } - - self.buffer[self.end] = byte; self.advance_end(); } @@ -57,24 +57,22 @@ impl CircularBuffer { self.end = 0; // Clear any values in the buffer - for element in self.buffer.iter_mut() { - std::mem::take(element); - } + self.data.fill_with(|| None); } - pub fn is_empty(&self) -> bool { - self.start == self.end + fn is_empty(&self) -> bool { + self.start == self.end && self.data[self.start].is_none() } - pub fn is_full(&self) -> bool { - (self.end + 1) % self.size == self.start + fn is_full(&self) -> bool { + self.start == self.end && self.data[self.start].is_some() } fn advance_start(&mut self) { - self.start = (self.start + 1) % self.size; + self.start = (self.start + 1) % self.data.len(); } fn advance_end(&mut self) { - self.end = (self.end + 1) % self.size; + self.end = (self.end + 1) % self.data.len(); } } diff --git a/exercises/practice/circular-buffer/.meta/test_template.tera b/exercises/practice/circular-buffer/.meta/test_template.tera new file mode 100644 index 000000000..84f5d6ea3 --- /dev/null +++ b/exercises/practice/circular-buffer/.meta/test_template.tera @@ -0,0 +1,61 @@ +use circular_buffer::*; +use std::rc::Rc; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let mut buffer = CircularBuffer{% if loop.index == 1 %}::{% endif %}::new({{ test.input.capacity }}); +{%- for op in test.input.operations %} + {%- if op.operation == "read" %} + {%- if op.should_succeed %} + assert_eq!(buffer.read(), Ok({{ op.expected }})); + {%- else %} + assert_eq!(buffer.read(), Err(Error::EmptyBuffer)); + {% endif -%} + {%- elif op.operation == "write" %} + {%- if op.should_succeed %} + assert!(buffer.write({{ op.item }}).is_ok()); + {%- else %} + assert_eq!(buffer.write({{ op.item }}), Err(Error::FullBuffer)); + {% endif -%} + {%- elif op.operation == "overwrite" %} + buffer.overwrite({{ op.item }}); + {%- elif op.operation == "clear" %} + buffer.clear(); + {%- else %} + panic!("error in test template: unknown operation"); + {% endif -%} +{% endfor %} +} +{% endfor %} + +{#- + We usually do not add additional tests directly in the template. + In this case however, the structure of the tests is different from the + regular ones. Accommodating that in the template would be complicated + and unnecessary. +#} + +#[test] +#[ignore] +fn clear_actually_frees_up_its_elements() { + let mut buffer = CircularBuffer::new(1); + let element = Rc::new(()); + assert!(buffer.write(Rc::clone(&element)).is_ok()); + assert_eq!(Rc::strong_count(&element), 2); + buffer.clear(); + assert_eq!(Rc::strong_count(&element), 1); +} + +#[test] +#[ignore] +fn dropping_the_buffer_drops_its_elements() { + let element = Rc::new(()); + { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write(Rc::clone(&element)).is_ok()); + assert_eq!(Rc::strong_count(&element), 2); + } + assert_eq!(Rc::strong_count(&element), 1); +} diff --git a/exercises/practice/circular-buffer/.meta/tests.toml b/exercises/practice/circular-buffer/.meta/tests.toml index cfdc1544c..0fb3143dd 100644 --- a/exercises/practice/circular-buffer/.meta/tests.toml +++ b/exercises/practice/circular-buffer/.meta/tests.toml @@ -1,6 +1,19 @@ -# 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. + +[28268ed4-4ff3-45f3-820e-895b44d53dfa] +description = "reading empty buffer should fail" + +[2e6db04a-58a1-425d-ade8-ac30b5f318f3] +description = "can read an item just written" [90741fe8-a448-45ce-be2b-de009a24c144] description = "each item may only be read once" @@ -11,6 +24,9 @@ description = "items are read in the order they are written" [2af22046-3e44-4235-bfe6-05ba60439d38] description = "full buffer can't be written to" +[547d192c-bbf0-4369-b8fa-fc37e71f2393] +description = "a read frees up capacity for another write" + [04a56659-3a81-4113-816b-6ecb659b4471] description = "read position is maintained even across multiple writes" @@ -23,8 +39,14 @@ description = "clear frees up capacity for another write" [e1ac5170-a026-4725-bfbe-0cf332eddecd] description = "clear does nothing on empty buffer" +[9c2d4f26-3ec7-453f-a895-7e7ff8ae7b5b] +description = "overwrite acts like write on non-full buffer" + [880f916b-5039-475c-bd5c-83463c36a147] description = "overwrite replaces the oldest item on full buffer" [bfecab5b-aca1-4fab-a2b0-cd4af2b053c3] description = "overwrite replaces the oldest item remaining in buffer following a read" + +[9cebe63a-c405-437b-8b62-e3fdc1ecec5a] +description = "initial clear does not affect wrapping around" diff --git a/exercises/practice/circular-buffer/Cargo.toml b/exercises/practice/circular-buffer/Cargo.toml index 63c8be965..89de6cb01 100644 --- a/exercises/practice/circular-buffer/Cargo.toml +++ b/exercises/practice/circular-buffer/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "circular-buffer" -version = "1.1.0" +name = "circular_buffer" +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/circular-buffer/src/lib.rs b/exercises/practice/circular-buffer/src/lib.rs index 426fba8a3..93b54abf7 100644 --- a/exercises/practice/circular-buffer/src/lib.rs +++ b/exercises/practice/circular-buffer/src/lib.rs @@ -12,7 +12,7 @@ pub enum Error { impl CircularBuffer { pub fn new(capacity: usize) -> Self { - unimplemented!( + todo!( "Construct a new CircularBuffer with the capacity to hold {}.", match capacity { 1 => "1 element".to_string(), @@ -22,18 +22,24 @@ impl CircularBuffer { } pub fn write(&mut self, _element: T) -> Result<(), Error> { - unimplemented!("Write the passed element to the CircularBuffer or return FullBuffer error if CircularBuffer is full."); + todo!( + "Write the passed element to the CircularBuffer or return FullBuffer error if CircularBuffer is full." + ); } pub fn read(&mut self) -> Result { - unimplemented!("Read the oldest element from the CircularBuffer or return EmptyBuffer error if CircularBuffer is empty."); + todo!( + "Read the oldest element from the CircularBuffer or return EmptyBuffer error if CircularBuffer is empty." + ); } pub fn clear(&mut self) { - unimplemented!("Clear the CircularBuffer."); + todo!("Clear the CircularBuffer."); } pub fn overwrite(&mut self, _element: T) { - unimplemented!("Write the passed element to the CircularBuffer, overwriting the existing elements if CircularBuffer is full."); + todo!( + "Write the passed element to the CircularBuffer, overwriting the existing elements if CircularBuffer is full." + ); } } diff --git a/exercises/practice/circular-buffer/tests/circular-buffer.rs b/exercises/practice/circular-buffer/tests/circular-buffer.rs deleted file mode 100644 index 36f1b2b16..000000000 --- a/exercises/practice/circular-buffer/tests/circular-buffer.rs +++ /dev/null @@ -1,165 +0,0 @@ -use circular_buffer::{CircularBuffer, Error}; -use std::rc::Rc; - -#[test] -fn error_on_read_empty_buffer() { - let mut buffer = CircularBuffer::::new(1); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); -} - -#[test] -#[ignore] -fn can_read_item_just_written() { - let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); - assert_eq!(Ok('1'), buffer.read()); -} - -#[test] -#[ignore] -fn each_item_may_only_be_read_once() { - let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); - assert_eq!(Ok('1'), buffer.read()); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); -} - -#[test] -#[ignore] -fn items_are_read_in_the_order_they_are_written() { - let mut buffer = CircularBuffer::new(2); - assert!(buffer.write('1').is_ok()); - assert!(buffer.write('2').is_ok()); - assert_eq!(Ok('1'), buffer.read()); - assert_eq!(Ok('2'), buffer.read()); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); -} - -#[test] -#[ignore] -fn full_buffer_cant_be_written_to() { - let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); - assert_eq!(Err(Error::FullBuffer), buffer.write('2')); -} - -#[test] -#[ignore] -fn read_frees_up_capacity_for_another_write() { - let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); - assert_eq!(Ok('1'), buffer.read()); - assert!(buffer.write('2').is_ok()); - assert_eq!(Ok('2'), buffer.read()); -} - -#[test] -#[ignore] -fn read_position_is_maintained_even_across_multiple_writes() { - let mut buffer = CircularBuffer::new(3); - assert!(buffer.write('1').is_ok()); - assert!(buffer.write('2').is_ok()); - assert_eq!(Ok('1'), buffer.read()); - assert!(buffer.write('3').is_ok()); - assert_eq!(Ok('2'), buffer.read()); - assert_eq!(Ok('3'), buffer.read()); -} - -#[test] -#[ignore] -fn items_cleared_out_of_buffer_cant_be_read() { - let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); - buffer.clear(); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); -} - -#[test] -#[ignore] -fn clear_frees_up_capacity_for_another_write() { - let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); - buffer.clear(); - assert!(buffer.write('2').is_ok()); - assert_eq!(Ok('2'), buffer.read()); -} - -#[test] -#[ignore] -fn clear_does_nothing_on_empty_buffer() { - let mut buffer = CircularBuffer::new(1); - buffer.clear(); - assert!(buffer.write('1').is_ok()); - assert_eq!(Ok('1'), buffer.read()); -} - -#[test] -#[ignore] -fn clear_actually_frees_up_its_elements() { - let mut buffer = CircularBuffer::new(1); - let element = Rc::new(()); - assert!(buffer.write(Rc::clone(&element)).is_ok()); - assert_eq!(Rc::strong_count(&element), 2); - buffer.clear(); - assert_eq!(Rc::strong_count(&element), 1); -} - -#[test] -#[ignore] -fn overwrite_acts_like_write_on_non_full_buffer() { - let mut buffer = CircularBuffer::new(2); - assert!(buffer.write('1').is_ok()); - buffer.overwrite('2'); - assert_eq!(Ok('1'), buffer.read()); - assert_eq!(Ok('2'), buffer.read()); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); -} - -#[test] -#[ignore] -fn overwrite_replaces_the_oldest_item_on_full_buffer() { - let mut buffer = CircularBuffer::new(2); - assert!(buffer.write('1').is_ok()); - assert!(buffer.write('2').is_ok()); - buffer.overwrite('A'); - assert_eq!(Ok('2'), buffer.read()); - assert_eq!(Ok('A'), buffer.read()); -} - -#[test] -#[ignore] -fn overwrite_replaces_the_oldest_item_remaining_in_buffer_following_a_read() { - let mut buffer = CircularBuffer::new(3); - assert!(buffer.write('1').is_ok()); - assert!(buffer.write('2').is_ok()); - assert!(buffer.write('3').is_ok()); - assert_eq!(Ok('1'), buffer.read()); - assert!(buffer.write('4').is_ok()); - buffer.overwrite('5'); - assert_eq!(Ok('3'), buffer.read()); - assert_eq!(Ok('4'), buffer.read()); - assert_eq!(Ok('5'), buffer.read()); -} - -#[test] -#[ignore] -fn integer_buffer() { - let mut buffer = CircularBuffer::new(2); - assert!(buffer.write(1).is_ok()); - assert!(buffer.write(2).is_ok()); - assert_eq!(Ok(1), buffer.read()); - assert!(buffer.write(-1).is_ok()); - assert_eq!(Ok(2), buffer.read()); - assert_eq!(Ok(-1), buffer.read()); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); -} - -#[test] -#[ignore] -fn string_buffer() { - let mut buffer = CircularBuffer::new(2); - buffer.write("".to_string()).unwrap(); - buffer.write("Testing".to_string()).unwrap(); - assert_eq!(0, buffer.read().unwrap().len()); - assert_eq!(Ok("Testing".to_string()), buffer.read()); -} diff --git a/exercises/practice/circular-buffer/tests/circular_buffer.rs b/exercises/practice/circular-buffer/tests/circular_buffer.rs new file mode 100644 index 000000000..2f4e0712f --- /dev/null +++ b/exercises/practice/circular-buffer/tests/circular_buffer.rs @@ -0,0 +1,180 @@ +use circular_buffer::*; +use std::rc::Rc; + +#[test] +fn reading_empty_buffer_should_fail() { + let mut buffer = CircularBuffer::::new(1); + assert_eq!(buffer.read(), Err(Error::EmptyBuffer)); +} + +#[test] +#[ignore] +fn can_read_an_item_just_written() { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write(1).is_ok()); + assert_eq!(buffer.read(), Ok(1)); +} + +#[test] +#[ignore] +fn each_item_may_only_be_read_once() { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write(1).is_ok()); + assert_eq!(buffer.read(), Ok(1)); + assert_eq!(buffer.read(), Err(Error::EmptyBuffer)); +} + +#[test] +#[ignore] +fn items_are_read_in_the_order_they_are_written() { + let mut buffer = CircularBuffer::new(2); + assert!(buffer.write(1).is_ok()); + assert!(buffer.write(2).is_ok()); + assert_eq!(buffer.read(), Ok(1)); + assert_eq!(buffer.read(), Ok(2)); +} + +#[test] +#[ignore] +fn full_buffer_can_t_be_written_to() { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write(1).is_ok()); + assert_eq!(buffer.write(2), Err(Error::FullBuffer)); +} + +#[test] +#[ignore] +fn a_read_frees_up_capacity_for_another_write() { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write(1).is_ok()); + assert_eq!(buffer.read(), Ok(1)); + assert!(buffer.write(2).is_ok()); + assert_eq!(buffer.read(), Ok(2)); +} + +#[test] +#[ignore] +fn read_position_is_maintained_even_across_multiple_writes() { + let mut buffer = CircularBuffer::new(3); + assert!(buffer.write(1).is_ok()); + assert!(buffer.write(2).is_ok()); + assert_eq!(buffer.read(), Ok(1)); + assert!(buffer.write(3).is_ok()); + assert_eq!(buffer.read(), Ok(2)); + assert_eq!(buffer.read(), Ok(3)); +} + +#[test] +#[ignore] +fn items_cleared_out_of_buffer_can_t_be_read() { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write(1).is_ok()); + buffer.clear(); + assert_eq!(buffer.read(), Err(Error::EmptyBuffer)); +} + +#[test] +#[ignore] +fn clear_frees_up_capacity_for_another_write() { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write(1).is_ok()); + buffer.clear(); + assert!(buffer.write(2).is_ok()); + assert_eq!(buffer.read(), Ok(2)); +} + +#[test] +#[ignore] +fn clear_does_nothing_on_empty_buffer() { + let mut buffer = CircularBuffer::new(1); + buffer.clear(); + assert!(buffer.write(1).is_ok()); + assert_eq!(buffer.read(), Ok(1)); +} + +#[test] +#[ignore] +fn overwrite_acts_like_write_on_non_full_buffer() { + let mut buffer = CircularBuffer::new(2); + assert!(buffer.write(1).is_ok()); + buffer.overwrite(2); + assert_eq!(buffer.read(), Ok(1)); + assert_eq!(buffer.read(), Ok(2)); +} + +#[test] +#[ignore] +fn overwrite_replaces_the_oldest_item_on_full_buffer() { + let mut buffer = CircularBuffer::new(2); + assert!(buffer.write(1).is_ok()); + assert!(buffer.write(2).is_ok()); + buffer.overwrite(3); + assert_eq!(buffer.read(), Ok(2)); + assert_eq!(buffer.read(), Ok(3)); +} + +#[test] +#[ignore] +fn overwrite_replaces_the_oldest_item_remaining_in_buffer_following_a_read() { + let mut buffer = CircularBuffer::new(3); + assert!(buffer.write(1).is_ok()); + assert!(buffer.write(2).is_ok()); + assert!(buffer.write(3).is_ok()); + assert_eq!(buffer.read(), Ok(1)); + assert!(buffer.write(4).is_ok()); + buffer.overwrite(5); + assert_eq!(buffer.read(), Ok(3)); + assert_eq!(buffer.read(), Ok(4)); + assert_eq!(buffer.read(), Ok(5)); +} + +#[test] +#[ignore] +fn initial_clear_does_not_affect_wrapping_around() { + let mut buffer = CircularBuffer::new(2); + buffer.clear(); + assert!(buffer.write(1).is_ok()); + assert!(buffer.write(2).is_ok()); + buffer.overwrite(3); + buffer.overwrite(4); + assert_eq!(buffer.read(), Ok(3)); + assert_eq!(buffer.read(), Ok(4)); + assert_eq!(buffer.read(), Err(Error::EmptyBuffer)); +} + +#[test] +#[ignore] +fn char_buffer() { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write('A').is_ok()); +} + +#[test] +#[ignore] +fn string_buffer() { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write("Testing".to_string()).is_ok()); +} + +#[test] +#[ignore] +fn clear_actually_frees_up_its_elements() { + let mut buffer = CircularBuffer::new(1); + let element = Rc::new(()); + assert!(buffer.write(Rc::clone(&element)).is_ok()); + assert_eq!(Rc::strong_count(&element), 2); + buffer.clear(); + assert_eq!(Rc::strong_count(&element), 1); +} + +#[test] +#[ignore] +fn dropping_the_buffer_drops_its_elements() { + let element = Rc::new(()); + { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write(Rc::clone(&element)).is_ok()); + assert_eq!(Rc::strong_count(&element), 2); + } + assert_eq!(Rc::strong_count(&element), 1); +} diff --git a/exercises/practice/clock/.docs/instructions.append.md b/exercises/practice/clock/.docs/instructions.append.md index 75c732f08..1e3a35101 100644 --- a/exercises/practice/clock/.docs/instructions.append.md +++ b/exercises/practice/clock/.docs/instructions.append.md @@ -1,9 +1,12 @@ -# Rust Traits for `.to_string()` +# Instructions append -Did you implement `.to_string()` for the `Clock` struct? +## Rust Traits for `.to_string()` -If so, try implementing the -[Display trait](https://doc.rust-lang.org/std/fmt/trait.Display.html) for `Clock` instead. +You will also need to implement `.to_string()` for the `Clock` struct. +We will be using this to display the Clock's state. +You can either do it via implementing it directly or using the [Display trait][display-trait]. + +If so, try implementing the [Display trait][display-trait] for `Clock` instead. Traits allow for a common way to implement functionality for various types. @@ -11,3 +14,5 @@ For additional learning, consider how you might implement `String::from` for the You don't have to actually implement this—it's redundant with `Display`, which is generally the better choice when the destination type is `String`—but it's useful to have a few type-conversion traits in your toolkit. + +[display-trait]: https://doc.rust-lang.org/std/fmt/trait.Display.html diff --git a/exercises/practice/clock/.docs/instructions.md b/exercises/practice/clock/.docs/instructions.md index ebe272526..a1efc7890 100644 --- a/exercises/practice/clock/.docs/instructions.md +++ b/exercises/practice/clock/.docs/instructions.md @@ -5,5 +5,3 @@ Implement a clock that handles times without dates. You should be able to add and subtract minutes to it. Two clocks that represent the same time should be equal to each other. - -You will also need to implement `.to_string()` for the `Clock` struct. We will be using this to display the Clock's state. You can either do it via implementing it directly or using the [Display trait](https://doc.rust-lang.org/std/fmt/trait.Display.html). diff --git a/exercises/practice/clock/.gitignore b/exercises/practice/clock/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/clock/.gitignore +++ b/exercises/practice/clock/.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/clock/.meta/config.json b/exercises/practice/clock/.meta/config.json index 21c603c13..767284df7 100644 --- a/exercises/practice/clock/.meta/config.json +++ b/exercises/practice/clock/.meta/config.json @@ -36,7 +36,6 @@ }, "blurb": "Implement a clock that handles times without dates.", "source": "Pairing session with Erin Drummond", - "source_url": "https://twitter.com/ebdrummond", "custom": { "allowed-to-not-compile": "Stub doesn't compile because there is no to_string() implementation. This exercise is an introduction to derived and self-implemented traits, therefore adding template for a trait would reduce student learning." } diff --git a/exercises/practice/clock/.meta/example.rs b/exercises/practice/clock/.meta/example.rs index af1ccfe1d..d3adbb350 100644 --- a/exercises/practice/clock/.meta/example.rs +++ b/exercises/practice/clock/.meta/example.rs @@ -9,7 +9,7 @@ impl fmt::Display for Clock { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let hours = self.minutes / 60; let mins = self.minutes % 60; - write!(f, "{:02}:{:02}", hours, mins) + write!(f, "{hours:02}:{mins:02}") } } diff --git a/exercises/practice/clock/.meta/test_template.tera b/exercises/practice/clock/.meta/test_template.tera new file mode 100644 index 000000000..8d7fd0521 --- /dev/null +++ b/exercises/practice/clock/.meta/test_template.tera @@ -0,0 +1,53 @@ +use clock::*; + +// +// Clock Creation +// + +{% for test in cases.0.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert_eq!(Clock::new({{ test.input.hour }}, {{ test.input.minute }}).to_string(), {{ test.expected | json_encode() }}); +} +{% endfor %} + +// +// Clock Math +// + +{% for test in cases.1.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let clock = Clock::new({{ test.input.hour }}, {{ test.input.minute }}).add_minutes({{ test.input.value }}); + assert_eq!(clock.to_string(), {{ test.expected | json_encode() }}); +} +{% endfor %} + +{% for test in cases.2.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let clock = Clock::new({{ test.input.hour }}, {{ test.input.minute }}).add_minutes(-{{ test.input.value }}); + assert_eq!(clock.to_string(), {{ test.expected | json_encode() }}); +} +{% endfor %} + +// +// Test Equality +// + +{% for test in cases.3.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% set c1 = test.input.clock1 %} + {% set c2 = test.input.clock2 %} + {% if test.expected -%} + assert_eq!(Clock::new({{ c1.hour }}, {{ c1.minute }}), Clock::new({{ c2.hour }}, {{ c2.minute }})); + {% else -%} + assert_ne!(Clock::new({{ c1.hour }}, {{ c1.minute }}), Clock::new({{ c2.hour }}, {{ c2.minute }})); + {% endif -%} +} +{% endfor %} diff --git a/exercises/practice/clock/.meta/tests.toml b/exercises/practice/clock/.meta/tests.toml index be690e975..712c87bca 100644 --- a/exercises/practice/clock/.meta/tests.toml +++ b/exercises/practice/clock/.meta/tests.toml @@ -1,3 +1,166 @@ -# 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. + +[a577bacc-106b-496e-9792-b3083ea8705e] +description = "Create a new clock with an initial time -> on the hour" + +[b5d0c360-3b88-489b-8e84-68a1c7a4fa23] +description = "Create a new clock with an initial time -> past the hour" + +[473223f4-65f3-46ff-a9f7-7663c7e59440] +description = "Create a new clock with an initial time -> midnight is zero hours" + +[ca95d24a-5924-447d-9a96-b91c8334725c] +description = "Create a new clock with an initial time -> hour rolls over" + +[f3826de0-0925-4d69-8ac8-89aea7e52b78] +description = "Create a new clock with an initial time -> hour rolls over continuously" + +[a02f7edf-dfd4-4b11-b21a-86de3cc6a95c] +description = "Create a new clock with an initial time -> sixty minutes is next hour" + +[8f520df6-b816-444d-b90f-8a477789beb5] +description = "Create a new clock with an initial time -> minutes roll over" + +[c75c091b-47ac-4655-8d40-643767fc4eed] +description = "Create a new clock with an initial time -> minutes roll over continuously" + +[06343ecb-cf39-419d-a3f5-dcbae0cc4c57] +description = "Create a new clock with an initial time -> hour and minutes roll over" + +[be60810e-f5d9-4b58-9351-a9d1e90e660c] +description = "Create a new clock with an initial time -> hour and minutes roll over continuously" + +[1689107b-0b5c-4bea-aad3-65ec9859368a] +description = "Create a new clock with an initial time -> hour and minutes roll over to exactly midnight" + +[d3088ee8-91b7-4446-9e9d-5e2ad6219d91] +description = "Create a new clock with an initial time -> negative hour" + +[77ef6921-f120-4d29-bade-80d54aa43b54] +description = "Create a new clock with an initial time -> negative hour rolls over" + +[359294b5-972f-4546-bb9a-a85559065234] +description = "Create a new clock with an initial time -> negative hour rolls over continuously" + +[509db8b7-ac19-47cc-bd3a-a9d2f30b03c0] +description = "Create a new clock with an initial time -> negative minutes" + +[5d6bb225-130f-4084-84fd-9e0df8996f2a] +description = "Create a new clock with an initial time -> negative minutes roll over" + +[d483ceef-b520-4f0c-b94a-8d2d58cf0484] +description = "Create a new clock with an initial time -> negative minutes roll over continuously" + +[1cd19447-19c6-44bf-9d04-9f8305ccb9ea] +description = "Create a new clock with an initial time -> negative sixty minutes is previous hour" + +[9d3053aa-4f47-4afc-bd45-d67a72cef4dc] +description = "Create a new clock with an initial time -> negative hour and minutes both roll over" + +[51d41fcf-491e-4ca0-9cae-2aa4f0163ad4] +description = "Create a new clock with an initial time -> negative hour and minutes both roll over continuously" + +[d098e723-ad29-4ef9-997a-2693c4c9d89a] +description = "Add minutes -> add minutes" + +[b6ec8f38-e53e-4b22-92a7-60dab1f485f4] +description = "Add minutes -> add no minutes" + +[efd349dd-0785-453e-9ff8-d7452a8e7269] +description = "Add minutes -> add to next hour" + +[749890f7-aba9-4702-acce-87becf4ef9fe] +description = "Add minutes -> add more than one hour" + +[da63e4c1-1584-46e3-8d18-c9dc802c1713] +description = "Add minutes -> add more than two hours with carry" + +[be167a32-3d33-4cec-a8bc-accd47ddbb71] +description = "Add minutes -> add across midnight" + +[6672541e-cdae-46e4-8be7-a820cc3be2a8] +description = "Add minutes -> add more than one day (1500 min = 25 hrs)" + +[1918050d-c79b-4cb7-b707-b607e2745c7e] +description = "Add minutes -> add more than two days" + +[37336cac-5ede-43a5-9026-d426cbe40354] +description = "Subtract minutes -> subtract minutes" + +[0aafa4d0-3b5f-4b12-b3af-e3a9e09c047b] +description = "Subtract minutes -> subtract to previous hour" + +[9b4e809c-612f-4b15-aae0-1df0acb801b9] +description = "Subtract minutes -> subtract more than an hour" + +[8b04bb6a-3d33-4e6c-8de9-f5de6d2c70d6] +description = "Subtract minutes -> subtract across midnight" + +[07c3bbf7-ce4d-4658-86e8-4a77b7a5ccd9] +description = "Subtract minutes -> subtract more than two hours" + +[90ac8a1b-761c-4342-9c9c-cdc3ed5db097] +description = "Subtract minutes -> subtract more than two hours with borrow" + +[2149f985-7136-44ad-9b29-ec023a97a2b7] +description = "Subtract minutes -> subtract more than one day (1500 min = 25 hrs)" + +[ba11dbf0-ac27-4acb-ada9-3b853ec08c97] +description = "Subtract minutes -> subtract more than two days" + +[f2fdad51-499f-4c9b-a791-b28c9282e311] +description = "Compare two clocks for equality -> clocks with same time" + +[5d409d4b-f862-4960-901e-ec430160b768] +description = "Compare two clocks for equality -> clocks a minute apart" + +[a6045fcf-2b52-4a47-8bb2-ef10a064cba5] +description = "Compare two clocks for equality -> clocks an hour apart" + +[66b12758-0be5-448b-a13c-6a44bce83527] +description = "Compare two clocks for equality -> clocks with hour overflow" + +[2b19960c-212e-4a71-9aac-c581592f8111] +description = "Compare two clocks for equality -> clocks with hour overflow by several days" + +[6f8c6541-afac-4a92-b0c2-b10d4e50269f] +description = "Compare two clocks for equality -> clocks with negative hour" + +[bb9d5a68-e324-4bf5-a75e-0e9b1f97a90d] +description = "Compare two clocks for equality -> clocks with negative hour that wraps" + +[56c0326d-565b-4d19-a26f-63b3205778b7] +description = "Compare two clocks for equality -> clocks with negative hour that wraps multiple times" + +[c90b9de8-ddff-4ffe-9858-da44a40fdbc2] +description = "Compare two clocks for equality -> clocks with minute overflow" + +[533a3dc5-59a7-491b-b728-a7a34fe325de] +description = "Compare two clocks for equality -> clocks with minute overflow by several days" + +[fff49e15-f7b7-4692-a204-0f6052d62636] +description = "Compare two clocks for equality -> clocks with negative minute" + +[605c65bb-21bd-43eb-8f04-878edf508366] +description = "Compare two clocks for equality -> clocks with negative minute that wraps" + +[b87e64ed-212a-4335-91fd-56da8421d077] +description = "Compare two clocks for equality -> clocks with negative minute that wraps multiple times" + +[822fbf26-1f3b-4b13-b9bf-c914816b53dd] +description = "Compare two clocks for equality -> clocks with negative hours and minutes" + +[e787bccd-cf58-4a1d-841c-ff80eaaccfaa] +description = "Compare two clocks for equality -> clocks with negative hours and minutes that wrap" + +[96969ca8-875a-48a1-86ae-257a528c44f5] +description = "Compare two clocks for equality -> full clock and zeroed clock" diff --git a/exercises/practice/clock/Cargo.toml b/exercises/practice/clock/Cargo.toml index ca943d67c..79c6ff8e7 100644 --- a/exercises/practice/clock/Cargo.toml +++ b/exercises/practice/clock/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "clock" -version = "2.4.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/clock/src/lib.rs b/exercises/practice/clock/src/lib.rs index 8627e2296..c625f73de 100644 --- a/exercises/practice/clock/src/lib.rs +++ b/exercises/practice/clock/src/lib.rs @@ -2,10 +2,10 @@ pub struct Clock; impl Clock { pub fn new(hours: i32, minutes: i32) -> Self { - unimplemented!("Construct a new Clock from {hours} hours and {minutes} minutes"); + todo!("Construct a new Clock from {hours} hours and {minutes} minutes"); } pub fn add_minutes(&self, minutes: i32) -> Self { - unimplemented!("Add {minutes} minutes to existing Clock time"); + todo!("Add {minutes} minutes to existing Clock time"); } } diff --git a/exercises/practice/clock/tests/clock.rs b/exercises/practice/clock/tests/clock.rs index 6d1ffdf54..fbc4946dc 100644 --- a/exercises/practice/clock/tests/clock.rs +++ b/exercises/practice/clock/tests/clock.rs @@ -1,252 +1,240 @@ -use clock::Clock; +use clock::*; // // Clock Creation // #[test] -fn test_on_the_hour() { +fn on_the_hour() { assert_eq!(Clock::new(8, 0).to_string(), "08:00"); } #[test] #[ignore] -fn test_past_the_hour() { +fn past_the_hour() { assert_eq!(Clock::new(11, 9).to_string(), "11:09"); } #[test] #[ignore] -fn test_midnight_is_zero_hours() { +fn midnight_is_zero_hours() { assert_eq!(Clock::new(24, 0).to_string(), "00:00"); } #[test] #[ignore] -fn test_hour_rolls_over() { +fn hour_rolls_over() { assert_eq!(Clock::new(25, 0).to_string(), "01:00"); } #[test] #[ignore] -fn test_hour_rolls_over_continuously() { +fn hour_rolls_over_continuously() { assert_eq!(Clock::new(100, 0).to_string(), "04:00"); } #[test] #[ignore] -fn test_sixty_minutes_is_next_hour() { +fn sixty_minutes_is_next_hour() { assert_eq!(Clock::new(1, 60).to_string(), "02:00"); } #[test] #[ignore] -fn test_minutes_roll_over() { +fn minutes_roll_over() { assert_eq!(Clock::new(0, 160).to_string(), "02:40"); } #[test] #[ignore] -fn test_minutes_roll_over_continuously() { +fn minutes_roll_over_continuously() { assert_eq!(Clock::new(0, 1723).to_string(), "04:43"); } #[test] #[ignore] -fn test_hours_and_minutes_roll_over() { +fn hour_and_minutes_roll_over() { assert_eq!(Clock::new(25, 160).to_string(), "03:40"); } #[test] #[ignore] -fn test_hours_and_minutes_roll_over_continuously() { +fn hour_and_minutes_roll_over_continuously() { assert_eq!(Clock::new(201, 3001).to_string(), "11:01"); } #[test] #[ignore] -fn test_hours_and_minutes_roll_over_to_exactly_midnight() { +fn hour_and_minutes_roll_over_to_exactly_midnight() { assert_eq!(Clock::new(72, 8640).to_string(), "00:00"); } #[test] #[ignore] -fn test_negative_hour() { +fn negative_hour() { assert_eq!(Clock::new(-1, 15).to_string(), "23:15"); } #[test] #[ignore] -fn test_negative_hour_roll_over() { - assert_eq!(Clock::new(-25, 00).to_string(), "23:00"); +fn negative_hour_rolls_over() { + assert_eq!(Clock::new(-25, 0).to_string(), "23:00"); } #[test] #[ignore] -fn test_negative_hour_roll_over_continuously() { - assert_eq!(Clock::new(-91, 00).to_string(), "05:00"); +fn negative_hour_rolls_over_continuously() { + assert_eq!(Clock::new(-91, 0).to_string(), "05:00"); } #[test] #[ignore] -fn test_negative_minutes() { +fn negative_minutes() { assert_eq!(Clock::new(1, -40).to_string(), "00:20"); } #[test] #[ignore] -fn test_negative_minutes_roll_over() { +fn negative_minutes_roll_over() { assert_eq!(Clock::new(1, -160).to_string(), "22:20"); } #[test] #[ignore] -fn test_negative_minutes_roll_over_continuously() { +fn negative_minutes_roll_over_continuously() { assert_eq!(Clock::new(1, -4820).to_string(), "16:40"); } #[test] #[ignore] -fn test_negative_sixty_minutes_is_prev_hour() { +fn negative_sixty_minutes_is_previous_hour() { assert_eq!(Clock::new(2, -60).to_string(), "01:00"); } #[test] #[ignore] -fn test_negative_one_twenty_minutes_is_two_prev_hours() { - assert_eq!(Clock::new(1, -120).to_string(), "23:00"); -} - -#[test] -#[ignore] -fn test_negative_hour_and_minutes_both_roll_over() { +fn negative_hour_and_minutes_both_roll_over() { assert_eq!(Clock::new(-25, -160).to_string(), "20:20"); } #[test] #[ignore] -fn test_negative_hour_and_minutes_both_roll_over_continuously() { +fn negative_hour_and_minutes_both_roll_over_continuously() { assert_eq!(Clock::new(-121, -5810).to_string(), "22:10"); } -#[test] -#[ignore] -fn test_zero_hour_and_negative_minutes() { - assert_eq!(Clock::new(0, -22).to_string(), "23:38"); -} - // // Clock Math // #[test] #[ignore] -fn test_add_minutes() { +fn add_minutes() { let clock = Clock::new(10, 0).add_minutes(3); assert_eq!(clock.to_string(), "10:03"); } #[test] #[ignore] -fn test_add_no_minutes() { +fn add_no_minutes() { let clock = Clock::new(6, 41).add_minutes(0); assert_eq!(clock.to_string(), "06:41"); } #[test] #[ignore] -fn test_add_to_next_hour() { +fn add_to_next_hour() { let clock = Clock::new(0, 45).add_minutes(40); assert_eq!(clock.to_string(), "01:25"); } #[test] #[ignore] -fn test_add_more_than_one_hour() { +fn add_more_than_one_hour() { let clock = Clock::new(10, 0).add_minutes(61); assert_eq!(clock.to_string(), "11:01"); } #[test] #[ignore] -fn test_add_more_than_two_hours_with_carry() { +fn add_more_than_two_hours_with_carry() { let clock = Clock::new(0, 45).add_minutes(160); assert_eq!(clock.to_string(), "03:25"); } #[test] #[ignore] -fn test_add_across_midnight() { +fn add_across_midnight() { let clock = Clock::new(23, 59).add_minutes(2); assert_eq!(clock.to_string(), "00:01"); } #[test] #[ignore] -fn test_add_more_than_one_day() { +fn add_more_than_one_day_1500_min_25_hrs() { let clock = Clock::new(5, 32).add_minutes(1500); assert_eq!(clock.to_string(), "06:32"); } #[test] #[ignore] -fn test_add_more_than_two_days() { +fn add_more_than_two_days() { let clock = Clock::new(1, 1).add_minutes(3500); assert_eq!(clock.to_string(), "11:21"); } #[test] #[ignore] -fn test_subtract_minutes() { +fn subtract_minutes() { let clock = Clock::new(10, 3).add_minutes(-3); assert_eq!(clock.to_string(), "10:00"); } #[test] #[ignore] -fn test_subtract_to_previous_hour() { +fn subtract_to_previous_hour() { let clock = Clock::new(10, 3).add_minutes(-30); assert_eq!(clock.to_string(), "09:33"); } #[test] #[ignore] -fn test_subtract_more_than_an_hour() { +fn subtract_more_than_an_hour() { let clock = Clock::new(10, 3).add_minutes(-70); assert_eq!(clock.to_string(), "08:53"); } #[test] #[ignore] -fn test_subtract_across_midnight() { +fn subtract_across_midnight() { let clock = Clock::new(0, 3).add_minutes(-4); assert_eq!(clock.to_string(), "23:59"); } #[test] #[ignore] -fn test_subtract_more_than_two_hours() { +fn subtract_more_than_two_hours() { let clock = Clock::new(0, 0).add_minutes(-160); assert_eq!(clock.to_string(), "21:20"); } #[test] #[ignore] -fn test_subtract_more_than_two_hours_with_borrow() { +fn subtract_more_than_two_hours_with_borrow() { let clock = Clock::new(6, 15).add_minutes(-160); assert_eq!(clock.to_string(), "03:35"); } #[test] #[ignore] -fn test_subtract_more_than_one_day() { +fn subtract_more_than_one_day_1500_min_25_hrs() { let clock = Clock::new(5, 32).add_minutes(-1500); assert_eq!(clock.to_string(), "04:32"); } #[test] #[ignore] -fn test_subtract_more_than_two_days() { +fn subtract_more_than_two_days() { let clock = Clock::new(2, 20).add_minutes(-3000); assert_eq!(clock.to_string(), "00:20"); } @@ -257,96 +245,96 @@ fn test_subtract_more_than_two_days() { #[test] #[ignore] -fn test_compare_clocks_for_equality() { +fn clocks_with_same_time() { assert_eq!(Clock::new(15, 37), Clock::new(15, 37)); } #[test] #[ignore] -fn test_compare_clocks_a_minute_apart() { +fn clocks_a_minute_apart() { assert_ne!(Clock::new(15, 36), Clock::new(15, 37)); } #[test] #[ignore] -fn test_compare_clocks_an_hour_apart() { +fn clocks_an_hour_apart() { assert_ne!(Clock::new(14, 37), Clock::new(15, 37)); } #[test] #[ignore] -fn test_compare_clocks_with_hour_overflow() { +fn clocks_with_hour_overflow() { assert_eq!(Clock::new(10, 37), Clock::new(34, 37)); } #[test] #[ignore] -fn test_compare_clocks_with_hour_overflow_by_several_days() { - assert_eq!(Clock::new(99, 11), Clock::new(3, 11)); +fn clocks_with_hour_overflow_by_several_days() { + assert_eq!(Clock::new(3, 11), Clock::new(99, 11)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_hour() { - assert_eq!(Clock::new(-2, 40), Clock::new(22, 40)); +fn clocks_with_negative_hour() { + assert_eq!(Clock::new(22, 40), Clock::new(-2, 40)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_hour_that_wraps() { - assert_eq!(Clock::new(-31, 3), Clock::new(17, 3)); +fn clocks_with_negative_hour_that_wraps() { + assert_eq!(Clock::new(17, 3), Clock::new(-31, 3)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_hour_that_wraps_multiple_times() { - assert_eq!(Clock::new(-83, 49), Clock::new(13, 49)); +fn clocks_with_negative_hour_that_wraps_multiple_times() { + assert_eq!(Clock::new(13, 49), Clock::new(-83, 49)); } #[test] #[ignore] -fn test_compare_clocks_with_minutes_overflow() { - assert_eq!(Clock::new(0, 1441), Clock::new(0, 1)); +fn clocks_with_minute_overflow() { + assert_eq!(Clock::new(0, 1), Clock::new(0, 1441)); } #[test] #[ignore] -fn test_compare_clocks_with_minutes_overflow_by_several_days() { - assert_eq!(Clock::new(2, 4322), Clock::new(2, 2)); +fn clocks_with_minute_overflow_by_several_days() { + assert_eq!(Clock::new(2, 2), Clock::new(2, 4322)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_minute() { - assert_eq!(Clock::new(3, -20), Clock::new(2, 40)); +fn clocks_with_negative_minute() { + assert_eq!(Clock::new(2, 40), Clock::new(3, -20)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_minute_that_wraps() { - assert_eq!(Clock::new(5, -1490), Clock::new(4, 10)); +fn clocks_with_negative_minute_that_wraps() { + assert_eq!(Clock::new(4, 10), Clock::new(5, -1490)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_minute_that_wraps_multiple() { - assert_eq!(Clock::new(6, -4305), Clock::new(6, 15)); +fn clocks_with_negative_minute_that_wraps_multiple_times() { + assert_eq!(Clock::new(6, 15), Clock::new(6, -4305)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_hours_and_minutes() { - assert_eq!(Clock::new(-12, -268), Clock::new(7, 32)); +fn clocks_with_negative_hours_and_minutes() { + assert_eq!(Clock::new(7, 32), Clock::new(-12, -268)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_hours_and_minutes_that_wrap() { - assert_eq!(Clock::new(-54, -11_513), Clock::new(18, 7)); +fn clocks_with_negative_hours_and_minutes_that_wrap() { + assert_eq!(Clock::new(18, 7), Clock::new(-54, -11513)); } #[test] #[ignore] -fn test_compare_full_clock_and_zeroed_clock() { +fn full_clock_and_zeroed_clock() { assert_eq!(Clock::new(24, 0), Clock::new(0, 0)); } diff --git a/exercises/practice/collatz-conjecture/.docs/instructions.md b/exercises/practice/collatz-conjecture/.docs/instructions.md index 9a6de68c5..af332a810 100644 --- a/exercises/practice/collatz-conjecture/.docs/instructions.md +++ b/exercises/practice/collatz-conjecture/.docs/instructions.md @@ -1,31 +1,3 @@ # Instructions -The Collatz Conjecture or 3x+1 problem can be summarized as follows: - -Take any positive integer n. If n is even, divide n by 2 to get n / 2. If n is -odd, multiply n by 3 and add 1 to get 3n + 1. Repeat the process indefinitely. -The conjecture states that no matter which number you start with, you will -always reach 1 eventually. - -But sometimes the number grow significantly before it reaches 1. -This can lead to an integer overflow and makes the algorithm unsolvable -within the range of a number in u64. - -Given a number n, return the number of steps required to reach 1. - -## Examples - -Starting with n = 12, the steps would be as follows: - -0. 12 -1. 6 -2. 3 -3. 10 -4. 5 -5. 16 -6. 8 -7. 4 -8. 2 -9. 1 - -Resulting in 9 steps. So for input n = 12, the return value would be 9. +Given a positive integer, return the number of steps it takes to reach 1 according to the rules of the Collatz Conjecture. diff --git a/exercises/practice/collatz-conjecture/.docs/introduction.md b/exercises/practice/collatz-conjecture/.docs/introduction.md new file mode 100644 index 000000000..c35bdeb67 --- /dev/null +++ b/exercises/practice/collatz-conjecture/.docs/introduction.md @@ -0,0 +1,28 @@ +# Introduction + +One evening, you stumbled upon an old notebook filled with cryptic scribbles, as though someone had been obsessively chasing an idea. +On one page, a single question stood out: **Can every number find its way to 1?** +It was tied to something called the **Collatz Conjecture**, a puzzle that has baffled thinkers for decades. + +The rules were deceptively simple. +Pick any positive integer. + +- If it's even, divide it by 2. +- If it's odd, multiply it by 3 and add 1. + +Then, repeat these steps with the result, continuing indefinitely. + +Curious, you picked number 12 to test and began the journey: + +12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1 + +Counting from the second number (6), it took 9 steps to reach 1, and each time the rules repeated, the number kept changing. +At first, the sequence seemed unpredictable — jumping up, down, and all over. +Yet, the conjecture claims that no matter the starting number, we'll always end at 1. + +It was fascinating, but also puzzling. +Why does this always seem to work? +Could there be a number where the process breaks down, looping forever or escaping into infinity? +The notebook suggested solving this could reveal something profound — and with it, fame, [fortune][collatz-prize], and a place in history awaits whoever could unlock its secrets. + +[collatz-prize]: https://mathprize.net/posts/collatz-conjecture/ diff --git a/exercises/practice/collatz-conjecture/.gitignore b/exercises/practice/collatz-conjecture/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/collatz-conjecture/.gitignore +++ b/exercises/practice/collatz-conjecture/.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/collatz-conjecture/.meta/config.json b/exercises/practice/collatz-conjecture/.meta/config.json index f53fbab83..6972ca734 100644 --- a/exercises/practice/collatz-conjecture/.meta/config.json +++ b/exercises/practice/collatz-conjecture/.meta/config.json @@ -22,13 +22,13 @@ "Cargo.toml" ], "test": [ - "tests/collatz-conjecture.rs" + "tests/collatz_conjecture.rs" ], "example": [ ".meta/example.rs" ] }, "blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture.", - "source": "An unsolved problem in mathematics named after mathematician Lothar Collatz", - "source_url": "https://en.wikipedia.org/wiki/3x_%2B_1_problem" + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Collatz_conjecture" } diff --git a/exercises/practice/collatz-conjecture/.meta/test_template.tera b/exercises/practice/collatz-conjecture/.meta/test_template.tera new file mode 100644 index 000000000..1f2720a26 --- /dev/null +++ b/exercises/practice/collatz-conjecture/.meta/test_template.tera @@ -0,0 +1,15 @@ +use collatz_conjecture::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let output = collatz({{ test.input.number | fmt_num }}); + let expected = {% if test.expected is object %} + None + {% else %} + Some({{ test.expected | json_encode() }}) + {% endif %}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/collatz-conjecture/.meta/tests.toml b/exercises/practice/collatz-conjecture/.meta/tests.toml index be690e975..0bd122074 100644 --- a/exercises/practice/collatz-conjecture/.meta/tests.toml +++ b/exercises/practice/collatz-conjecture/.meta/tests.toml @@ -1,3 +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. + +[540a3d51-e7a6-47a5-92a3-4ad1838f0bfd] +description = "zero steps for one" + +[3d76a0a6-ea84-444a-821a-f7857c2c1859] +description = "divide if even" + +[754dea81-123c-429e-b8bc-db20b05a87b9] +description = "even and odd steps" + +[ecfd0210-6f85-44f6-8280-f65534892ff6] +description = "large number of even and odd steps" + +[7d4750e6-def9-4b86-aec7-9f7eb44f95a3] +description = "zero is an error" +include = false + +[2187673d-77d6-4543-975e-66df6c50e2da] +description = "zero is an error" +reimplements = "7d4750e6-def9-4b86-aec7-9f7eb44f95a3" + +[c6c795bf-a288-45e9-86a1-841359ad426d] +description = "negative value is an error" +include = false + +[ec11f479-56bc-47fd-a434-bcd7a31a7a2e] +description = "negative value is an error" +reimplements = "c6c795bf-a288-45e9-86a1-841359ad426d" +include = false +comment = "The exercise uses u64, which doesn't allow negative numbers." diff --git a/exercises/practice/collatz-conjecture/Cargo.toml b/exercises/practice/collatz-conjecture/Cargo.toml index dd64fa792..4463d57e8 100644 --- a/exercises/practice/collatz-conjecture/Cargo.toml +++ b/exercises/practice/collatz-conjecture/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "collatz_conjecture" -version = "1.2.1" +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/collatz-conjecture/src/lib.rs b/exercises/practice/collatz-conjecture/src/lib.rs index 5bf41a567..84f178225 100644 --- a/exercises/practice/collatz-conjecture/src/lib.rs +++ b/exercises/practice/collatz-conjecture/src/lib.rs @@ -1,5 +1,3 @@ pub fn collatz(n: u64) -> Option { - unimplemented!( - "return Some(x) where x is the number of steps required to reach 1 starting with {n}" - ) + todo!("return Some(x) where x is the number of steps required to reach 1 starting with {n}") } diff --git a/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs b/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs deleted file mode 100644 index b78442270..000000000 --- a/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs +++ /dev/null @@ -1,51 +0,0 @@ -use collatz_conjecture::*; - -#[test] -fn test_1() { - assert_eq!(Some(0), collatz(1)); -} - -#[test] -#[ignore] -fn test_16() { - assert_eq!(Some(4), collatz(16)); -} - -#[test] -#[ignore] -fn test_12() { - assert_eq!(Some(9), collatz(12)); -} - -#[test] -#[ignore] -fn test_1000000() { - assert_eq!(Some(152), collatz(1_000_000)); -} - -#[test] -#[ignore] -fn test_0() { - assert_eq!(None, collatz(0)); -} - -#[test] -#[ignore] -fn test_110243094271() { - let val = 110243094271; - assert_eq!(None, collatz(val)); -} - -#[test] -#[ignore] -fn test_max_div_3() { - let max = u64::MAX / 3; - assert_eq!(None, collatz(max)); -} - -#[test] -#[ignore] -fn test_max_minus_1() { - let max = u64::MAX - 1; - assert_eq!(None, collatz(max)); -} diff --git a/exercises/practice/collatz-conjecture/tests/collatz_conjecture.rs b/exercises/practice/collatz-conjecture/tests/collatz_conjecture.rs new file mode 100644 index 000000000..8ccceefc2 --- /dev/null +++ b/exercises/practice/collatz-conjecture/tests/collatz_conjecture.rs @@ -0,0 +1,40 @@ +use collatz_conjecture::*; + +#[test] +fn zero_steps_for_one() { + let output = collatz(1); + let expected = Some(0); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn divide_if_even() { + let output = collatz(16); + let expected = Some(4); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn even_and_odd_steps() { + let output = collatz(12); + let expected = Some(9); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn large_number_of_even_and_odd_steps() { + let output = collatz(1_000_000); + let expected = Some(152); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn zero_is_an_error() { + let output = collatz(0); + let expected = None; + assert_eq!(output, expected); +} diff --git a/exercises/practice/crypto-square/.docs/instructions.md b/exercises/practice/crypto-square/.docs/instructions.md index 41615f819..6c3826ee5 100644 --- a/exercises/practice/crypto-square/.docs/instructions.md +++ b/exercises/practice/crypto-square/.docs/instructions.md @@ -4,11 +4,10 @@ Implement the classic method for composing secret messages called a square code. Given an English text, output the encoded version of that text. -First, the input is normalized: the spaces and punctuation are removed -from the English text and the message is downcased. +First, the input is normalized: the spaces and punctuation are removed from the English text and the message is down-cased. -Then, the normalized characters are broken into rows. These rows can be -regarded as forming a rectangle when printed with intervening newlines. +Then, the normalized characters are broken into rows. +These rows can be regarded as forming a rectangle when printed with intervening newlines. For example, the sentence @@ -22,13 +21,16 @@ is normalized to: "ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots" ``` -The plaintext should be organized in to a rectangle. The size of the -rectangle (`r x c`) should be decided by the length of the message, -such that `c >= r` and `c - r <= 1`, where `c` is the number of columns -and `r` is the number of rows. +The plaintext should be organized into a rectangle as square as possible. +The size of the rectangle should be decided by the length of the message. -Our normalized text is 54 characters long, dictating a rectangle with -`c = 8` and `r = 7`: +If `c` is the number of columns and `r` is the number of rows, then for the rectangle `r` x `c` find the smallest possible integer `c` such that: + +- `r * c >= length of message`, +- and `c >= r`, +- and `c - r <= 1`. + +Our normalized text is 54 characters long, dictating a rectangle with `c = 8` and `r = 7`: ```text "ifmanwas" @@ -40,8 +42,7 @@ Our normalized text is 54 characters long, dictating a rectangle with "sroots " ``` -The coded message is obtained by reading down the columns going left to -right. +The coded message is obtained by reading down the columns going left to right. The message above is coded as: @@ -49,17 +50,14 @@ The message above is coded as: "imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau" ``` -Output the encoded text in chunks that fill perfect rectangles `(r X c)`, -with `c` chunks of `r` length, separated by spaces. For phrases that are -`n` characters short of the perfect rectangle, pad each of the last `n` -chunks with a single trailing space. +Output the encoded text in chunks that fill perfect rectangles `(r X c)`, with `c` chunks of `r` length, separated by spaces. +For phrases that are `n` characters short of the perfect rectangle, pad each of the last `n` chunks with a single trailing space. ```text "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau " ``` -Notice that were we to stack these, we could visually decode the -ciphertext back in to the original message: +Notice that were we to stack these, we could visually decode the ciphertext back in to the original message: ```text "imtgdvs" diff --git a/exercises/practice/crypto-square/.gitignore b/exercises/practice/crypto-square/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/crypto-square/.gitignore +++ b/exercises/practice/crypto-square/.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/crypto-square/.meta/Cargo-example.toml b/exercises/practice/crypto-square/.meta/Cargo-example.toml deleted file mode 100644 index 5c7303093..000000000 --- a/exercises/practice/crypto-square/.meta/Cargo-example.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -edition = "2021" -name = "crypto-square" -version = "0.1.0" - -[dependencies] -itertools = "0.6.1" diff --git a/exercises/practice/crypto-square/.meta/config.json b/exercises/practice/crypto-square/.meta/config.json index b8c261f2f..27e42b68b 100644 --- a/exercises/practice/crypto-square/.meta/config.json +++ b/exercises/practice/crypto-square/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/crypto-square.rs" + "tests/crypto_square.rs" ], "example": [ ".meta/example.rs" @@ -32,5 +32,5 @@ }, "blurb": "Implement the classic method for composing secret messages called a square code.", "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/crypto-square/.meta/example.rs b/exercises/practice/crypto-square/.meta/example.rs index 5a7619006..5d5feb4b0 100644 --- a/exercises/practice/crypto-square/.meta/example.rs +++ b/exercises/practice/crypto-square/.meta/example.rs @@ -1,117 +1,40 @@ -extern crate itertools; -use itertools::Itertools; - -/// Encrypt the input string using square cryptography pub fn encrypt(input: &str) -> String { - let prepared = prepare(input); - if prepared.is_empty() { + let mut input: Vec<_> = input + .to_ascii_lowercase() + .chars() + .filter(char::is_ascii_alphanumeric) + .collect(); + if input.is_empty() { return String::new(); } - let (cols, rows) = dimensions(prepared.len()); - - let mut output = String::with_capacity(input.len()); - for chunk_iterator in SquareIndexer::new(rows, cols).chunks(cols).into_iter() { - for ch_idx in chunk_iterator { - if ch_idx < prepared.len() { - output.push(prepared[ch_idx]); - } - } - output.push(' '); - } - - // we know there's one extra space at the end - output.pop(); - - output -} - -/// Construct a vector of characters from the given input. -/// -/// Constrain it to the allowed chars: lowercase ascii letters. -/// We construct a vector here because the length of the input -/// matters when constructing the output, so we need to know -/// how many input chars there are. We could treat it as a stream -/// and just stream twice, but collecting it into a vector works -/// equally well and might be a bit faster. -fn prepare(input: &str) -> Vec { - let mut output = Vec::with_capacity(input.len()); - - output.extend( - input - .chars() - .filter(|&c| c.is_ascii() && !c.is_whitespace() && !c.is_ascii_punctuation()) - .map(|c| c.to_ascii_lowercase()), - ); - - // add space padding to the end such that the actual string returned - // forms a perfect rectangle - let (r, c) = dimensions(output.len()); - output.resize(r * c, ' '); + let width = (input.len() as f64).sqrt().ceil() as usize; + let size = width * width; - output.shrink_to_fit(); - - output -} - -/// Get the dimensions of the appropriate bounding rectangle for this encryption -/// -/// To find `(rows, cols)` such that `cols >= rows && cols - rows <= 1`, we find -/// the least square greater than or equal to the message length. Its square root -/// is the cols. If the message length is a perfect square, `rows` is the same. -/// Otherwise, it is one less. -fn dimensions(length: usize) -> (usize, usize) { - let cols = (length as f64).sqrt().ceil() as usize; - let rows = if cols * cols == length { - cols + // skip last row if already empty + let last_row = if input.len() + width > size { + width } else { - cols - 1 + width - 1 }; - (rows, cols) -} -/// Iterator over the indices of the appropriate chars of the output. -/// -/// For a (2, 3) (r, c) grid, yields (0, 3, 1, 4, 2, 5). -/// Does no bounds checking or space insertion: that's handled elsewhere. -#[derive(Debug)] -struct SquareIndexer { - rows: usize, - cols: usize, - cur_row: usize, - cur_col: usize, - max_value: usize, -} + // padding + input.resize(size, ' '); -impl SquareIndexer { - fn new(rows: usize, cols: usize) -> SquareIndexer { - SquareIndexer { - rows, - cols, - cur_row: 0, - cur_col: 0, - max_value: rows * cols, - } - } -} + // prevent input from being moved into closure below + let input = &input; -impl Iterator for SquareIndexer { - type Item = usize; - fn next(&mut self) -> Option { - let value = self.cur_row + (self.cur_col * self.rows); - let output = if value < self.max_value && self.cur_row < self.rows { - Some(value) - } else { - None - }; + // transpose + let mut res: String = (0..width) + .flat_map(|col| { + (0..last_row) + .map(move |row| input[row * width + col]) + .chain(std::iter::once(' ')) + }) + .collect(); - // now increment internal state to next value - self.cur_col += 1; - if self.cur_col >= self.cols { - self.cur_col = 0; - self.cur_row += 1; - } + // trailing space separator + res.pop(); - output - } + res } diff --git a/exercises/practice/crypto-square/.meta/test_template.tera b/exercises/practice/crypto-square/.meta/test_template.tera new file mode 100644 index 000000000..9a2468871 --- /dev/null +++ b/exercises/practice/crypto-square/.meta/test_template.tera @@ -0,0 +1,11 @@ +use crypto_square::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let actual = encrypt({{ test.input.plaintext | json_encode() }}); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(&actual, expected); +} +{% endfor -%} diff --git a/exercises/practice/crypto-square/.meta/tests.toml b/exercises/practice/crypto-square/.meta/tests.toml index be690e975..94ef0819f 100644 --- a/exercises/practice/crypto-square/.meta/tests.toml +++ b/exercises/practice/crypto-square/.meta/tests.toml @@ -1,3 +1,39 @@ -# 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. + +[407c3837-9aa7-4111-ab63-ec54b58e8e9f] +description = "empty plaintext results in an empty ciphertext" + +[aad04a25-b8bb-4304-888b-581bea8e0040] +description = "normalization results in empty plaintext" + +[64131d65-6fd9-4f58-bdd8-4a2370fb481d] +description = "Lowercase" + +[63a4b0ed-1e3c-41ea-a999-f6f26ba447d6] +description = "Remove spaces" + +[1b5348a1-7893-44c1-8197-42d48d18756c] +description = "Remove punctuation" + +[8574a1d3-4a08-4cec-a7c7-de93a164f41a] +description = "9 character plaintext results in 3 chunks of 3 characters" + +[a65d3fa1-9e09-43f9-bcec-7a672aec3eae] +description = "8 character plaintext results in 3 chunks, the last one with a trailing space" + +[fbcb0c6d-4c39-4a31-83f6-c473baa6af80] +description = "54 character plaintext results in 7 chunks, the last two with trailing spaces" +include = false + +[33fd914e-fa44-445b-8f38-ff8fbc9fe6e6] +description = "54 character plaintext results in 8 chunks, the last two with trailing spaces" +reimplements = "fbcb0c6d-4c39-4a31-83f6-c473baa6af80" diff --git a/exercises/practice/crypto-square/Cargo.toml b/exercises/practice/crypto-square/Cargo.toml index 0363ae0c8..b8e23929a 100644 --- a/exercises/practice/crypto-square/Cargo.toml +++ b/exercises/practice/crypto-square/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" -name = "crypto-square" +name = "crypto_square" 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/crypto-square/src/lib.rs b/exercises/practice/crypto-square/src/lib.rs index d9700db5c..c22623ef2 100644 --- a/exercises/practice/crypto-square/src/lib.rs +++ b/exercises/practice/crypto-square/src/lib.rs @@ -1,3 +1,3 @@ pub fn encrypt(input: &str) -> String { - unimplemented!("Encrypt {input:?} using a square code") + todo!("Encrypt {input:?} using a square code") } diff --git a/exercises/practice/crypto-square/tests/crypto-square.rs b/exercises/practice/crypto-square/tests/crypto-square.rs deleted file mode 100644 index ba65c2e43..000000000 --- a/exercises/practice/crypto-square/tests/crypto-square.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crypto_square::encrypt; - -fn test(input: &str, output: &str) { - assert_eq!(&encrypt(input), output); -} - -#[test] -fn test_empty_input() { - test("", "") -} - -#[test] -#[ignore] -fn test_encrypt_also_decrypts_square() { - // note that you only get the exact input back if: - // 1. no punctuation - // 2. even spacing - // 3. all lowercase - // 4. square input - let example = "lime anda coco anut"; - assert_eq!(example, &encrypt(&encrypt(example))); -} - -#[test] -#[ignore] -fn test_example() { - test( - "If man was meant to stay on the ground, god would have given us roots.", - "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ", - ) -} - -#[test] -#[ignore] -fn test_empty_last_line() { - test("congratulate", "crl oaa ntt gue") -} - -#[test] -#[ignore] -fn test_spaces_are_reorganized() { - test("abet", "ae bt"); - test("a bet", "ae bt"); - test(" a b e t ", "ae bt"); -} - -#[test] -#[ignore] -fn test_everything_becomes_lowercase() { - test("caSe", "cs ae"); - test("cAsE", "cs ae"); - test("CASE", "cs ae"); -} - -#[test] -#[ignore] -fn test_long() { - test( - r#" -We choose to go to the moon. - -We choose to go to the moon in this decade and do the other things, -not because they are easy, but because they are hard, because that -goal will serve to organize and measure the best of our energies and -skills, because that challenge is one that we are willing to accept, -one we are unwilling to postpone, and one which we intend to win, -and the others, too. - --- John F. Kennedy, 12 September 1962 - "#, - &(String::from("womdbudlmecsgwdwob enooetbsenaotioihe ") - + "cwotcbeeaeunolnnnr henhaecrsrsealeaf1 ocieucavugetciwnk9 " - + "ohnosauerithcnhde6 sotteusteehaegitn2 eohhtseotsatptchn " - + "tsiehetohatwtohee oesrethrenceopwod gtdtyhagbdhanoety " - + "ooehaetaesaresih1 tgcirygnsklewtne2 ooaneaoitilweptrs " - + "ttdgerazoleiaoese hoesaeleflnlrnntp etanshwaosgleedot " - + "mhnoyainubeiuatoe oedtbrldreinnnojm "), - ) -} diff --git a/exercises/practice/crypto-square/tests/crypto_square.rs b/exercises/practice/crypto-square/tests/crypto_square.rs new file mode 100644 index 000000000..dd12dc922 --- /dev/null +++ b/exercises/practice/crypto-square/tests/crypto_square.rs @@ -0,0 +1,64 @@ +use crypto_square::*; + +#[test] +fn empty_plaintext_results_in_an_empty_ciphertext() { + let actual = encrypt(""); + let expected = ""; + assert_eq!(&actual, expected); +} + +#[test] +#[ignore] +fn normalization_results_in_empty_plaintext() { + let actual = encrypt("... --- ..."); + let expected = ""; + assert_eq!(&actual, expected); +} + +#[test] +#[ignore] +fn lowercase() { + let actual = encrypt("A"); + let expected = "a"; + assert_eq!(&actual, expected); +} + +#[test] +#[ignore] +fn remove_spaces() { + let actual = encrypt(" b "); + let expected = "b"; + assert_eq!(&actual, expected); +} + +#[test] +#[ignore] +fn remove_punctuation() { + let actual = encrypt("@1,%!"); + let expected = "1"; + assert_eq!(&actual, expected); +} + +#[test] +#[ignore] +fn test_9_character_plaintext_results_in_3_chunks_of_3_characters() { + let actual = encrypt("This is fun!"); + let expected = "tsf hiu isn"; + assert_eq!(&actual, expected); +} + +#[test] +#[ignore] +fn test_8_character_plaintext_results_in_3_chunks_the_last_one_with_a_trailing_space() { + let actual = encrypt("Chill out."); + let expected = "clu hlt io "; + assert_eq!(&actual, expected); +} + +#[test] +#[ignore] +fn test_54_character_plaintext_results_in_8_chunks_the_last_two_with_trailing_spaces() { + let actual = encrypt("If man was meant to stay on the ground, god would have given us roots."); + let expected = "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau "; + assert_eq!(&actual, expected); +} diff --git a/exercises/practice/custom-set/.docs/instructions.md b/exercises/practice/custom-set/.docs/instructions.md index e4931b058..33b90e28d 100644 --- a/exercises/practice/custom-set/.docs/instructions.md +++ b/exercises/practice/custom-set/.docs/instructions.md @@ -2,7 +2,6 @@ Create a custom set type. -Sometimes it is necessary to define a custom data structure of some -type, like a set. In this exercise you will define your own set. How it -works internally doesn't matter, as long as it behaves like a set of -unique elements. +Sometimes it is necessary to define a custom data structure of some type, like a set. +In this exercise you will define your own set. +How it works internally doesn't matter, as long as it behaves like a set of unique elements. diff --git a/exercises/practice/custom-set/.gitignore b/exercises/practice/custom-set/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/custom-set/.gitignore +++ b/exercises/practice/custom-set/.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/custom-set/.meta/config.json b/exercises/practice/custom-set/.meta/config.json index 40567345a..db301405f 100644 --- a/exercises/practice/custom-set/.meta/config.json +++ b/exercises/practice/custom-set/.meta/config.json @@ -28,7 +28,7 @@ "Cargo.toml" ], "test": [ - "tests/custom-set.rs" + "tests/custom_set.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/custom-set/.meta/example.rs b/exercises/practice/custom-set/.meta/example.rs index 8467e519f..9a2c918e5 100644 --- a/exercises/practice/custom-set/.meta/example.rs +++ b/exercises/practice/custom-set/.meta/example.rs @@ -5,8 +5,8 @@ pub struct CustomSet { impl PartialEq for CustomSet { fn eq(&self, other: &Self) -> bool { - self.collection.iter().all(|x| other.contains(&x)) - && other.collection.iter().all(|x| self.contains(&x)) + self.collection.iter().all(|x| other.contains(x)) + && other.collection.iter().all(|x| self.contains(x)) } } @@ -49,8 +49,8 @@ impl CustomSet { &self .collection .iter() - .cloned() .filter(|c| other.contains(c)) + .cloned() .collect::>(), ) } @@ -61,8 +61,8 @@ impl CustomSet { &self .collection .iter() + .chain(other.collection.iter()) .cloned() - .chain(other.collection.iter().cloned()) .collect::>(), ) } @@ -73,8 +73,8 @@ impl CustomSet { &self .collection .iter() - .cloned() .filter(|c| !other.contains(c)) + .cloned() .collect::>(), ) } diff --git a/exercises/practice/custom-set/.meta/test_template.tera b/exercises/practice/custom-set/.meta/test_template.tera new file mode 100644 index 000000000..e2aac4f24 --- /dev/null +++ b/exercises/practice/custom-set/.meta/test_template.tera @@ -0,0 +1,61 @@ +use custom_set::CustomSet; + +{% for test_group in cases %} +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { +{%- if test.property == "empty" %} + let set = CustomSet::::new(&{{ test.input.set | json_encode() }}); + assert!( + {%- if not test.expected -%} ! {%- endif -%} + set.is_empty()); +{%- elif test.property == "contains" %} + let set = CustomSet::::new(&{{ test.input.set | json_encode() }}); + assert!( + {%- if not test.expected -%} ! {%- endif -%} + set.contains(&{{ test.input.element }})); +{%- elif test.property == "subset" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + assert!( + {%- if not test.expected -%} ! {%- endif -%} + set_1.is_subset(&set_2)); +{%- elif test.property == "disjoint" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + assert!( + {%- if not test.expected -%} ! {%- endif -%} + set_1.is_disjoint(&set_2)); +{%- elif test.property == "equal" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + {% if test.expected -%} + assert_eq!(set_1, set_2); + {%- else -%} + assert_ne!(set_1, set_2); + {%- endif %} +{%- elif test.property == "add" %} + let mut set = CustomSet::::new(&{{ test.input.set | json_encode() }}); + set.add({{ test.input.element }}); + let expected = CustomSet::::new(&{{ test.expected | json_encode() }}); + assert_eq!(set, expected); +{%- elif test.property == "intersection" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + let expected = CustomSet::::new(&{{ test.expected | json_encode() }}); + assert_eq!(set_1.intersection(&set_2), expected); +{%- elif test.property == "difference" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + let expected = CustomSet::::new(&{{ test.expected | json_encode() }}); + assert_eq!(set_1.difference(&set_2), expected); +{%- elif test.property == "union" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + let expected = CustomSet::::new(&{{ test.expected | json_encode() }}); + assert_eq!(set_1.union(&set_2), expected); +{%- endif %} +} +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/custom-set/.meta/tests.toml b/exercises/practice/custom-set/.meta/tests.toml index 1f3929e49..430c139e6 100644 --- a/exercises/practice/custom-set/.meta/tests.toml +++ b/exercises/practice/custom-set/.meta/tests.toml @@ -1,30 +1,130 @@ -# 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. [20c5f855-f83a-44a7-abdd-fe75c6cf022b] -description = "sets with no elements are empty" +description = "Returns true if the set contains no elements -> sets with no elements are empty" [d506485d-5706-40db-b7d8-5ceb5acf88d2] -description = "sets with elements are not empty" +description = "Returns true if the set contains no elements -> sets with elements are not empty" [759b9740-3417-44c3-8ca3-262b3c281043] -description = "nothing is contained in an empty set" +description = "Sets can report if they contain an element -> nothing is contained in an empty set" + +[f83cd2d1-2a85-41bc-b6be-80adbff4be49] +description = "Sets can report if they contain an element -> when the element is in the set" + +[93423fc0-44d0-4bc0-a2ac-376de8d7af34] +description = "Sets can report if they contain an element -> when the element is not in the set" + +[c392923a-637b-4495-b28e-34742cd6157a] +description = "A set is a subset if all of its elements are contained in the other set -> empty set is a subset of another empty set" + +[5635b113-be8c-4c6f-b9a9-23c485193917] +description = "A set is a subset if all of its elements are contained in the other set -> empty set is a subset of non-empty set" + +[832eda58-6d6e-44e2-92c2-be8cf0173cee] +description = "A set is a subset if all of its elements are contained in the other set -> non-empty set is not a subset of empty set" + +[c830c578-8f97-4036-b082-89feda876131] +description = "A set is a subset if all of its elements are contained in the other set -> set is a subset of set with exact same elements" + +[476a4a1c-0fd1-430f-aa65-5b70cbc810c5] +description = "A set is a subset if all of its elements are contained in the other set -> set is a subset of larger set with same elements" + +[d2498999-3e46-48e4-9660-1e20c3329d3d] +description = "A set is a subset if all of its elements are contained in the other set -> set is not a subset of set that does not contain its elements" + +[7d38155e-f472-4a7e-9ad8-5c1f8f95e4cc] +description = "Sets are disjoint if they share no elements -> the empty set is disjoint with itself" + +[7a2b3938-64b6-4b32-901a-fe16891998a6] +description = "Sets are disjoint if they share no elements -> empty set is disjoint with non-empty set" + +[589574a0-8b48-48ea-88b0-b652c5fe476f] +description = "Sets are disjoint if they share no elements -> non-empty set is disjoint with empty set" + +[febeaf4f-f180-4499-91fa-59165955a523] +description = "Sets are disjoint if they share no elements -> sets are not disjoint if they share an element" + +[0de20d2f-c952-468a-88c8-5e056740f020] +description = "Sets are disjoint if they share no elements -> sets are disjoint if they share no elements" [4bd24adb-45da-4320-9ff6-38c044e9dff8] -description = "empty sets are equal" +description = "Sets with the same elements are equal -> empty sets are equal" + +[f65c0a0e-6632-4b2d-b82c-b7c6da2ec224] +description = "Sets with the same elements are equal -> empty set is not equal to non-empty set" + +[81e53307-7683-4b1e-a30c-7e49155fe3ca] +description = "Sets with the same elements are equal -> non-empty set is not equal to empty set" [d57c5d7c-a7f3-48cc-a162-6b488c0fbbd0] -description = "sets with the same elements are equal" +description = "Sets with the same elements are equal -> sets with the same elements are equal" [dd61bafc-6653-42cc-961a-ab071ee0ee85] -description = "sets with different elements are not equal" +description = "Sets with the same elements are equal -> sets with different elements are not equal" + +[06059caf-9bf4-425e-aaff-88966cb3ea14] +description = "Sets with the same elements are equal -> set is not equal to larger set with same elements" + +[d4a1142f-09aa-4df9-8b83-4437dcf7ec24] +description = "Sets with the same elements are equal -> set is equal to a set constructed from an array with duplicates" [8a677c3c-a658-4d39-bb88-5b5b1a9659f4] -description = "add to empty set" +description = "Unique elements can be added to a set -> add to empty set" + +[0903dd45-904d-4cf2-bddd-0905e1a8d125] +description = "Unique elements can be added to a set -> add to non-empty set" + +[b0eb7bb7-5e5d-4733-b582-af771476cb99] +description = "Unique elements can be added to a set -> adding an existing element does not change the set" + +[893d5333-33b8-4151-a3d4-8f273358208a] +description = "Intersection returns a set of all shared elements -> intersection of two empty sets is an empty set" + +[d739940e-def2-41ab-a7bb-aaf60f7d782c] +description = "Intersection returns a set of all shared elements -> intersection of an empty set and non-empty set is an empty set" + +[3607d9d8-c895-4d6f-ac16-a14956e0a4b7] +description = "Intersection returns a set of all shared elements -> intersection of a non-empty set and an empty set is an empty set" [b5120abf-5b5e-41ab-aede-4de2ad85c34e] -description = "intersection of two sets with no shared elements is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of two sets with no shared elements is an empty set" [af21ca1b-fac9-499c-81c0-92a591653d49] -description = "intersection of two sets with shared elements is a set of the shared elements" +description = "Intersection returns a set of all shared elements -> intersection of two sets with shared elements is a set of the shared elements" + +[c5e6e2e4-50e9-4bc2-b89f-c518f015b57e] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two empty sets is an empty set" + +[2024cc92-5c26-44ed-aafd-e6ca27d6fcd2] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of empty set and non-empty set is an empty set" + +[e79edee7-08aa-4c19-9382-f6820974b43e] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of a non-empty set and an empty set is the non-empty set" + +[c5ac673e-d707-4db5-8d69-7082c3a5437e] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two non-empty sets is a set of elements that are only in the first set" + +[20d0a38f-7bb7-4c4a-ac15-90c7392ecf2b] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference removes all duplicates in the first set" + +[c45aed16-5494-455a-9033-5d4c93589dc6] +description = "Union returns a set of all elements in either set -> union of empty sets is an empty set" + +[9d258545-33c2-4fcb-a340-9f8aa69e7a41] +description = "Union returns a set of all elements in either set -> union of an empty set and non-empty set is the non-empty set" + +[3aade50c-80c7-4db8-853d-75bac5818b83] +description = "Union returns a set of all elements in either set -> union of a non-empty set and empty set is the non-empty set" + +[a00bb91f-c4b4-4844-8f77-c73e2e9df77c] +description = "Union returns a set of all elements in either set -> union of non-empty sets contains all unique elements" diff --git a/exercises/practice/custom-set/Cargo.toml b/exercises/practice/custom-set/Cargo.toml index d595327c1..e56e5dd82 100644 --- a/exercises/practice/custom-set/Cargo.toml +++ b/exercises/practice/custom-set/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "custom-set" -version = "1.0.1" +name = "custom_set" +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/custom-set/src/lib.rs b/exercises/practice/custom-set/src/lib.rs index f5ff8f44c..3cde4569c 100644 --- a/exercises/practice/custom-set/src/lib.rs +++ b/exercises/practice/custom-set/src/lib.rs @@ -7,41 +7,41 @@ pub struct CustomSet { impl CustomSet { pub fn new(_input: &[T]) -> Self { - unimplemented!(); + todo!(); } pub fn contains(&self, _element: &T) -> bool { - unimplemented!(); + todo!(); } pub fn add(&mut self, _element: T) { - unimplemented!(); + todo!(); } pub fn is_subset(&self, _other: &Self) -> bool { - unimplemented!(); + todo!(); } pub fn is_empty(&self) -> bool { - unimplemented!(); + todo!(); } pub fn is_disjoint(&self, _other: &Self) -> bool { - unimplemented!(); + todo!(); } #[must_use] pub fn intersection(&self, _other: &Self) -> Self { - unimplemented!(); + todo!(); } #[must_use] pub fn difference(&self, _other: &Self) -> Self { - unimplemented!(); + todo!(); } #[must_use] pub fn union(&self, _other: &Self) -> Self { - unimplemented!(); + todo!(); } } diff --git a/exercises/practice/custom-set/tests/custom-set.rs b/exercises/practice/custom-set/tests/custom-set.rs deleted file mode 100644 index 10d384f22..000000000 --- a/exercises/practice/custom-set/tests/custom-set.rs +++ /dev/null @@ -1,298 +0,0 @@ -use custom_set::*; - -#[test] -fn sets_with_no_elements_are_empty() { - let set: CustomSet<()> = CustomSet::new(&[]); - assert!(set.is_empty()); -} - -#[test] -#[ignore] -fn sets_with_elements_are_not_empty() { - let set = CustomSet::new(&[1]); - assert!(!set.is_empty()); -} - -#[test] -#[ignore] -fn nothing_is_contained_in_an_empty_set() { - let set = CustomSet::new(&[]); - assert!(!set.contains(&1)); -} - -#[test] -#[ignore] -fn true_when_the_element_is_in_the_set() { - let set = CustomSet::new(&[1, 2, 3]); - assert!(set.contains(&1)); -} - -#[test] -#[ignore] -fn false_when_the_element_is_not_in_the_set() { - let set = CustomSet::new(&[1, 2, 3]); - assert!(!set.contains(&4)); -} - -#[test] -#[ignore] -fn empty_sets_are_subsets_of_each_other() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert!(set1.is_subset(&set2)); - assert!(set2.is_subset(&set1)); -} - -#[test] -#[ignore] -fn empty_set_is_subset_of_non_empty_set() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[1]); - assert!(set1.is_subset(&set2)); -} - -#[test] -#[ignore] -fn non_empty_set_is_not_subset_of_empty_set() { - let set1 = CustomSet::new(&[1]); - let set2 = CustomSet::new(&[]); - assert!(!set1.is_subset(&set2)); -} - -#[test] -#[ignore] -fn sets_with_same_elements_are_subsets() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[1, 2, 3]); - assert!(set1.is_subset(&set2)); - assert!(set2.is_subset(&set1)); -} - -#[test] -#[ignore] -fn set_contained_in_other_set_is_a_subset() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[4, 1, 2, 3]); - assert!(set1.is_subset(&set2)); -} - -#[test] -#[ignore] -fn set_not_contained_in_other_set_is_not_a_subset_one() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[4, 1, 3]); - assert!(!set1.is_subset(&set2)); -} - -#[test] -#[ignore] -fn empty_sets_are_disjoint_with_each_other() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert!(set1.is_disjoint(&set2)); - assert!(set2.is_disjoint(&set1)); -} - -#[test] -#[ignore] -fn empty_set_disjoint_with_non_empty_set() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[1]); - assert!(set1.is_disjoint(&set2)); -} - -#[test] -#[ignore] -fn non_empty_set_disjoint_with_empty_set() { - let set1 = CustomSet::new(&[1]); - let set2 = CustomSet::new(&[]); - assert!(set1.is_disjoint(&set2)); -} - -#[test] -#[ignore] -fn sets_with_one_element_in_common_are_not_disjoint() { - let set1 = CustomSet::new(&[1, 2]); - let set2 = CustomSet::new(&[2, 3]); - assert!(!set1.is_disjoint(&set2)); - assert!(!set2.is_disjoint(&set1)); -} - -#[test] -#[ignore] -fn sets_with_no_elements_in_common_are_disjoint() { - let set1 = CustomSet::new(&[1, 2]); - let set2 = CustomSet::new(&[3, 4]); - assert!(set1.is_disjoint(&set2)); - assert!(set2.is_disjoint(&set1)); -} - -#[test] -#[ignore] -fn empty_sets_are_equal() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert_eq!(set1, set2); -} - -#[test] -#[ignore] -fn empty_set_is_not_equal_to_a_non_empty_set() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[1, 2, 3]); - assert_ne!(set1, set2); -} - -#[test] -#[ignore] -fn non_empty_set_is_not_equal_to_an_empty_set() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[]); - assert_ne!(set1, set2); -} - -#[test] -#[ignore] -fn sets_with_the_same_elements_are_equal() { - let set1 = CustomSet::new(&[1, 2]); - let set2 = CustomSet::new(&[2, 1]); - assert_eq!(set1, set2); -} - -#[test] -#[ignore] -fn sets_with_different_elements_are_not_equal() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[2, 1, 4]); - assert_ne!(set1, set2); -} - -#[test] -#[ignore] -fn add_to_empty_set() { - let mut set = CustomSet::new(&[]); - set.add(3); - assert_eq!(set, CustomSet::new(&[3])); -} - -#[test] -#[ignore] -fn add_to_non_empty_set() { - let mut set = CustomSet::new(&[1, 2, 4]); - set.add(3); - assert_eq!(set, CustomSet::new(&[1, 2, 3, 4])); -} - -#[test] -#[ignore] -fn add_existing_element() { - let mut set = CustomSet::new(&[1, 2, 3]); - set.add(3); - assert_eq!(set, CustomSet::new(&[1, 2, 3])); -} - -#[test] -#[ignore] -fn intersecting_empty_sets_return_empty_set() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert_eq!(set1.intersection(&set2), CustomSet::new(&[])); -} - -#[test] -#[ignore] -fn intersecting_empty_set_with_non_empty_returns_empty_set() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[3, 2, 5]); - assert_eq!(set1.intersection(&set2), CustomSet::new(&[])); -} - -#[test] -#[ignore] -fn intersecting_non_empty_set_with_empty_returns_empty_set() { - let set1 = CustomSet::new(&[1, 2, 3, 4]); - let set2 = CustomSet::new(&[]); - assert_eq!(set1.intersection(&set2), CustomSet::new(&[])); -} - -#[test] -#[ignore] -fn intersection_of_two_sets_with_no_shared_elements_is_an_empty_set() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[4, 5, 6]); - assert_eq!(set1.intersection(&set2), CustomSet::new(&[])); - assert_eq!(set2.intersection(&set1), CustomSet::new(&[])); -} - -#[test] -#[ignore] -fn intersection_of_two_sets_with_shared_elements_is_a_set_of_the_shared_elements() { - let set1 = CustomSet::new(&[1, 2, 3, 4]); - let set2 = CustomSet::new(&[3, 2, 5]); - assert_eq!(set1.intersection(&set2), CustomSet::new(&[2, 3])); - assert_eq!(set2.intersection(&set1), CustomSet::new(&[2, 3])); -} - -#[test] -#[ignore] -fn difference_of_two_empty_sets_is_empty_set() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert_eq!(set1.difference(&set2), CustomSet::new(&[])); -} - -#[test] -#[ignore] -fn difference_of_an_empty_and_non_empty_set_is_an_empty_set() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[3, 2, 5]); - assert_eq!(set1.difference(&set2), CustomSet::new(&[])); -} - -#[test] -#[ignore] -fn difference_of_a_non_empty_set_and_empty_set_is_the_non_empty_set() { - let set1 = CustomSet::new(&[1, 2, 3, 4]); - let set2 = CustomSet::new(&[]); - assert_eq!(set1.difference(&set2), CustomSet::new(&[1, 2, 3, 4])); -} - -#[test] -#[ignore] -fn difference_of_two_non_empty_sets_is_elements_only_in_first_set_one() { - let set1 = CustomSet::new(&[3, 2, 1]); - let set2 = CustomSet::new(&[2, 4]); - assert_eq!(set1.difference(&set2), CustomSet::new(&[1, 3])); -} - -#[test] -#[ignore] -fn union_of_two_empty_sets_is_empty_set() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert_eq!(set1.union(&set2), CustomSet::new(&[])); -} - -#[test] -#[ignore] -fn union_of_empty_set_and_non_empty_set_is_all_elements() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[2]); - assert_eq!(set1.union(&set2), CustomSet::new(&[2])); -} - -#[test] -#[ignore] -fn union_of_non_empty_set_and_empty_set_is_the_non_empty_set() { - let set1 = CustomSet::new(&[1, 3]); - let set2 = CustomSet::new(&[]); - assert_eq!(set1.union(&set2), CustomSet::new(&[1, 3])); -} - -#[test] -#[ignore] -fn union_of_non_empty_sets_contains_all_unique_elements() { - let set1 = CustomSet::new(&[1, 3]); - let set2 = CustomSet::new(&[2, 3]); - assert_eq!(set1.union(&set2), CustomSet::new(&[3, 2, 1])); -} diff --git a/exercises/practice/custom-set/tests/custom_set.rs b/exercises/practice/custom-set/tests/custom_set.rs new file mode 100644 index 000000000..2d47870dd --- /dev/null +++ b/exercises/practice/custom-set/tests/custom_set.rs @@ -0,0 +1,332 @@ +use custom_set::CustomSet; + +#[test] +fn sets_with_no_elements_are_empty() { + let set = CustomSet::::new(&[]); + assert!(set.is_empty()); +} + +#[test] +#[ignore] +fn sets_with_elements_are_not_empty() { + let set = CustomSet::::new(&[1]); + assert!(!set.is_empty()); +} + +#[test] +#[ignore] +fn nothing_is_contained_in_an_empty_set() { + let set = CustomSet::::new(&[]); + assert!(!set.contains(&1)); +} + +#[test] +#[ignore] +fn when_the_element_is_in_the_set() { + let set = CustomSet::::new(&[1, 2, 3]); + assert!(set.contains(&1)); +} + +#[test] +#[ignore] +fn when_the_element_is_not_in_the_set() { + let set = CustomSet::::new(&[1, 2, 3]); + assert!(!set.contains(&4)); +} + +#[test] +#[ignore] +fn empty_set_is_a_subset_of_another_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + assert!(set_1.is_subset(&set_2)); +} + +#[test] +#[ignore] +fn empty_set_is_a_subset_of_non_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[1]); + assert!(set_1.is_subset(&set_2)); +} + +#[test] +#[ignore] +fn non_empty_set_is_not_a_subset_of_empty_set() { + let set_1 = CustomSet::::new(&[1]); + let set_2 = CustomSet::::new(&[]); + assert!(!set_1.is_subset(&set_2)); +} + +#[test] +#[ignore] +fn set_is_a_subset_of_set_with_exact_same_elements() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[1, 2, 3]); + assert!(set_1.is_subset(&set_2)); +} + +#[test] +#[ignore] +fn set_is_a_subset_of_larger_set_with_same_elements() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[4, 1, 2, 3]); + assert!(set_1.is_subset(&set_2)); +} + +#[test] +#[ignore] +fn set_is_not_a_subset_of_set_that_does_not_contain_its_elements() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[4, 1, 3]); + assert!(!set_1.is_subset(&set_2)); +} + +#[test] +#[ignore] +fn the_empty_set_is_disjoint_with_itself() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + assert!(set_1.is_disjoint(&set_2)); +} + +#[test] +#[ignore] +fn empty_set_is_disjoint_with_non_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[1]); + assert!(set_1.is_disjoint(&set_2)); +} + +#[test] +#[ignore] +fn non_empty_set_is_disjoint_with_empty_set() { + let set_1 = CustomSet::::new(&[1]); + let set_2 = CustomSet::::new(&[]); + assert!(set_1.is_disjoint(&set_2)); +} + +#[test] +#[ignore] +fn sets_are_not_disjoint_if_they_share_an_element() { + let set_1 = CustomSet::::new(&[1, 2]); + let set_2 = CustomSet::::new(&[2, 3]); + assert!(!set_1.is_disjoint(&set_2)); +} + +#[test] +#[ignore] +fn sets_are_disjoint_if_they_share_no_elements() { + let set_1 = CustomSet::::new(&[1, 2]); + let set_2 = CustomSet::::new(&[3, 4]); + assert!(set_1.is_disjoint(&set_2)); +} + +#[test] +#[ignore] +fn empty_sets_are_equal() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + assert_eq!(set_1, set_2); +} + +#[test] +#[ignore] +fn empty_set_is_not_equal_to_non_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[1, 2, 3]); + assert_ne!(set_1, set_2); +} + +#[test] +#[ignore] +fn non_empty_set_is_not_equal_to_empty_set() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[]); + assert_ne!(set_1, set_2); +} + +#[test] +#[ignore] +fn sets_with_the_same_elements_are_equal() { + let set_1 = CustomSet::::new(&[1, 2]); + let set_2 = CustomSet::::new(&[2, 1]); + assert_eq!(set_1, set_2); +} + +#[test] +#[ignore] +fn sets_with_different_elements_are_not_equal() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[1, 2, 4]); + assert_ne!(set_1, set_2); +} + +#[test] +#[ignore] +fn set_is_not_equal_to_larger_set_with_same_elements() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[1, 2, 3, 4]); + assert_ne!(set_1, set_2); +} + +#[test] +#[ignore] +fn set_is_equal_to_a_set_constructed_from_an_array_with_duplicates() { + let set_1 = CustomSet::::new(&[1]); + let set_2 = CustomSet::::new(&[1, 1]); + assert_eq!(set_1, set_2); +} + +#[test] +#[ignore] +fn add_to_empty_set() { + let mut set = CustomSet::::new(&[]); + set.add(3); + let expected = CustomSet::::new(&[3]); + assert_eq!(set, expected); +} + +#[test] +#[ignore] +fn add_to_non_empty_set() { + let mut set = CustomSet::::new(&[1, 2, 4]); + set.add(3); + let expected = CustomSet::::new(&[1, 2, 3, 4]); + assert_eq!(set, expected); +} + +#[test] +#[ignore] +fn adding_an_existing_element_does_not_change_the_set() { + let mut set = CustomSet::::new(&[1, 2, 3]); + set.add(3); + let expected = CustomSet::::new(&[1, 2, 3]); + assert_eq!(set, expected); +} + +#[test] +#[ignore] +fn intersection_of_two_empty_sets_is_an_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.intersection(&set_2), expected); +} + +#[test] +#[ignore] +fn intersection_of_an_empty_set_and_non_empty_set_is_an_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[3, 2, 5]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.intersection(&set_2), expected); +} + +#[test] +#[ignore] +fn intersection_of_a_non_empty_set_and_an_empty_set_is_an_empty_set() { + let set_1 = CustomSet::::new(&[1, 2, 3, 4]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.intersection(&set_2), expected); +} + +#[test] +#[ignore] +fn intersection_of_two_sets_with_no_shared_elements_is_an_empty_set() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[4, 5, 6]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.intersection(&set_2), expected); +} + +#[test] +#[ignore] +fn intersection_of_two_sets_with_shared_elements_is_a_set_of_the_shared_elements() { + let set_1 = CustomSet::::new(&[1, 2, 3, 4]); + let set_2 = CustomSet::::new(&[3, 2, 5]); + let expected = CustomSet::::new(&[2, 3]); + assert_eq!(set_1.intersection(&set_2), expected); +} + +#[test] +#[ignore] +fn difference_of_two_empty_sets_is_an_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.difference(&set_2), expected); +} + +#[test] +#[ignore] +fn difference_of_empty_set_and_non_empty_set_is_an_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[3, 2, 5]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.difference(&set_2), expected); +} + +#[test] +#[ignore] +fn difference_of_a_non_empty_set_and_an_empty_set_is_the_non_empty_set() { + let set_1 = CustomSet::::new(&[1, 2, 3, 4]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[1, 2, 3, 4]); + assert_eq!(set_1.difference(&set_2), expected); +} + +#[test] +#[ignore] +fn difference_of_two_non_empty_sets_is_a_set_of_elements_that_are_only_in_the_first_set() { + let set_1 = CustomSet::::new(&[3, 2, 1]); + let set_2 = CustomSet::::new(&[2, 4]); + let expected = CustomSet::::new(&[1, 3]); + assert_eq!(set_1.difference(&set_2), expected); +} + +#[test] +#[ignore] +fn difference_removes_all_duplicates_in_the_first_set() { + let set_1 = CustomSet::::new(&[1, 1]); + let set_2 = CustomSet::::new(&[1]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.difference(&set_2), expected); +} + +#[test] +#[ignore] +fn union_of_empty_sets_is_an_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.union(&set_2), expected); +} + +#[test] +#[ignore] +fn union_of_an_empty_set_and_non_empty_set_is_the_non_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[2]); + let expected = CustomSet::::new(&[2]); + assert_eq!(set_1.union(&set_2), expected); +} + +#[test] +#[ignore] +fn union_of_a_non_empty_set_and_empty_set_is_the_non_empty_set() { + let set_1 = CustomSet::::new(&[1, 3]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[1, 3]); + assert_eq!(set_1.union(&set_2), expected); +} + +#[test] +#[ignore] +fn union_of_non_empty_sets_contains_all_unique_elements() { + let set_1 = CustomSet::::new(&[1, 3]); + let set_2 = CustomSet::::new(&[2, 3]); + let expected = CustomSet::::new(&[3, 2, 1]); + assert_eq!(set_1.union(&set_2), expected); +} diff --git a/exercises/practice/decimal/.docs/hints.md b/exercises/practice/decimal/.docs/hints.md new file mode 100644 index 000000000..40831648e --- /dev/null +++ b/exercises/practice/decimal/.docs/hints.md @@ -0,0 +1,5 @@ +## General + +- Instead of implementing arbitrary-precision arithmetic from scratch, consider building your type on top of the [num_bigint](https://crates.io/crates/num-bigint) crate. +- You might be able to [derive](https://doc.rust-lang.org/book/2018-edition/appendix-03-derivable-traits.html) some of the required traits. +- `Decimal` is assumed to be a signed type. You do not have to create a separate unsigned type, though you may do so as an implementation detail if you so choose. diff --git a/exercises/practice/decimal/.docs/instructions.md b/exercises/practice/decimal/.docs/instructions.md index 2ec586381..d954595df 100644 --- a/exercises/practice/decimal/.docs/instructions.md +++ b/exercises/practice/decimal/.docs/instructions.md @@ -13,9 +13,3 @@ In Rust, the way to get these operations on custom types is to implement the rel # Note It would be very easy to implement this exercise by using the [bigdecimal](https://crates.io/crates/bigdecimal) crate. Don't do that; implement this yourself. - -# Hints - -- Instead of implementing arbitrary-precision arithmetic from scratch, consider building your type on top of the [num_bigint](https://crates.io/crates/num-bigint) crate. -- You might be able to [derive](https://doc.rust-lang.org/book/2018-edition/appendix-03-derivable-traits.html) some of the required traits. -- `Decimal` is assumed to be a signed type. You do not have to create a separate unsigned type, though you may do so as an implementation detail if you so choose. diff --git a/exercises/practice/decimal/.gitignore b/exercises/practice/decimal/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/decimal/.gitignore +++ b/exercises/practice/decimal/.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/decimal/.meta/Cargo-example.toml b/exercises/practice/decimal/.meta/Cargo-example.toml index 5210e3217..e2a4c289f 100644 --- a/exercises/practice/decimal/.meta/Cargo-example.toml +++ b/exercises/practice/decimal/.meta/Cargo-example.toml @@ -1,8 +1,11 @@ [package] -edition = "2021" name = "decimal" 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] -num-bigint = "0.1.40" -num-traits = "0.1.40" +num-bigint = "0.4.4" +num-traits = "0.2.16" diff --git a/exercises/practice/decimal/.meta/example.rs b/exercises/practice/decimal/.meta/example.rs index 7f8c3faf7..a11f60f8f 100644 --- a/exercises/practice/decimal/.meta/example.rs +++ b/exercises/practice/decimal/.meta/example.rs @@ -63,7 +63,7 @@ impl Decimal { fn equalize_precision(one: &mut Decimal, two: &mut Decimal) { fn expand(lower_precision: &mut Decimal, higher_precision: &Decimal) { let precision_difference = - (higher_precision.decimal_index - lower_precision.decimal_index) as usize; + higher_precision.decimal_index - lower_precision.decimal_index; lower_precision.digits = &lower_precision.digits * pow(BigInt::from(10_usize), precision_difference); @@ -102,7 +102,9 @@ macro_rules! auto_impl_decimal_ops { fn $func_name(mut self, mut rhs: Self) -> Self { Decimal::equalize_precision(&mut self, &mut rhs); Decimal::new( + #[allow(clippy::redundant_closure_call)] $digits_operation(self.digits, rhs.digits), + #[allow(clippy::redundant_closure_call)] $index_operation(self.decimal_index, rhs.decimal_index), ) } @@ -125,6 +127,7 @@ macro_rules! auto_impl_decimal_cow { impl $trait for Decimal { fn $func_name(&self, other: &Self) -> $return_type { if self.decimal_index == other.decimal_index { + #[allow(clippy::redundant_closure_call)] $digits_operation(&self.digits, &other.digits) } else { // if we're here, the decimal indexes are unmatched. @@ -156,12 +159,12 @@ impl fmt::Display for Decimal { // left-padded with zeroes let digits = format!("{:0>width$}", self.digits, width = self.decimal_index); if self.decimal_index == digits.len() { - write!(f, "0.{}", digits) + write!(f, "0.{digits}") } else if self.decimal_index == 0 { - write!(f, "{}", digits) + write!(f, "{digits}") } else { let (before_index, after_index) = digits.split_at(digits.len() - self.decimal_index); - write!(f, "{}.{}", before_index, after_index) + write!(f, "{before_index}.{after_index}") } } } @@ -171,7 +174,7 @@ mod tests { use super::*; #[test] - fn test_display_temp() { + fn display_temp() { for &test_str in &["0", "1", "20", "0.3", "0.04", "50.05", "66.0006", "0.007"] { println!( "Decimal representation of \"{}\": {}", diff --git a/exercises/practice/decimal/Cargo.toml b/exercises/practice/decimal/Cargo.toml index 6ddb62742..3a34b5801 100644 --- a/exercises/practice/decimal/Cargo.toml +++ b/exercises/practice/decimal/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "decimal" 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/decimal/src/lib.rs b/exercises/practice/decimal/src/lib.rs index e61d55bb5..c9ad7098f 100644 --- a/exercises/practice/decimal/src/lib.rs +++ b/exercises/practice/decimal/src/lib.rs @@ -5,6 +5,6 @@ pub struct Decimal { impl Decimal { pub fn try_from(input: &str) -> Option { - unimplemented!("Create a new decimal with a value of {input}") + todo!("Create a new decimal with a value of {input}") } } diff --git a/exercises/practice/decimal/tests/decimal.rs b/exercises/practice/decimal/tests/decimal.rs index 02823fc3a..ebdf9d7d5 100644 --- a/exercises/practice/decimal/tests/decimal.rs +++ b/exercises/practice/decimal/tests/decimal.rs @@ -16,7 +16,7 @@ const BIGS: [&str; 3] = [ // test simple properties of required operations #[test] -fn test_eq() { +fn eq() { assert!(decimal("0.0") == decimal("0.0")); assert!(decimal("1.0") == decimal("1.0")); for big in BIGS.iter() { @@ -26,14 +26,14 @@ fn test_eq() { #[test] #[ignore] -fn test_ne() { +fn ne() { assert!(decimal("0.0") != decimal("1.0")); assert!(decimal(BIGS[0]) != decimal(BIGS[1])); } #[test] #[ignore] -fn test_gt() { +fn gt() { for slice_2 in BIGS.windows(2) { assert!(decimal(slice_2[1]) > decimal(slice_2[0])); } @@ -41,7 +41,7 @@ fn test_gt() { #[test] #[ignore] -fn test_lt() { +fn lt() { for slice_2 in BIGS.windows(2) { assert!(decimal(slice_2[0]) < decimal(slice_2[1])); } @@ -49,7 +49,7 @@ fn test_lt() { #[test] #[ignore] -fn test_add() { +fn add() { assert_eq!(decimal("0.1") + decimal("0.2"), decimal("0.3")); assert_eq!(decimal(BIGS[0]) + decimal(BIGS[1]), decimal(BIGS[2])); assert_eq!(decimal(BIGS[1]) + decimal(BIGS[0]), decimal(BIGS[2])); @@ -57,14 +57,14 @@ fn test_add() { #[test] #[ignore] -fn test_sub() { +fn sub() { assert_eq!(decimal(BIGS[2]) - decimal(BIGS[1]), decimal(BIGS[0])); assert_eq!(decimal(BIGS[2]) - decimal(BIGS[0]), decimal(BIGS[1])); } #[test] #[ignore] -fn test_mul() { +fn mul() { for big in BIGS.iter() { assert_eq!(decimal(big) * decimal("2"), decimal(big) + decimal(big)); } @@ -73,7 +73,7 @@ fn test_mul() { // test identities #[test] #[ignore] -fn test_add_id() { +fn add_id() { assert_eq!(decimal("1.0") + decimal("0.0"), decimal("1.0")); assert_eq!(decimal("0.1") + decimal("0.0"), decimal("0.1")); assert_eq!(decimal("0.0") + decimal("1.0"), decimal("1.0")); @@ -82,49 +82,60 @@ fn test_add_id() { #[test] #[ignore] -fn test_sub_id() { +fn sub_id() { assert_eq!(decimal("1.0") - decimal("0.0"), decimal("1.0")); assert_eq!(decimal("0.1") - decimal("0.0"), decimal("0.1")); } #[test] #[ignore] -fn test_mul_id() { +fn mul_id() { assert_eq!(decimal("2.1") * decimal("1.0"), decimal("2.1")); assert_eq!(decimal("1.0") * decimal("2.1"), decimal("2.1")); } #[test] #[ignore] -fn test_gt_positive_and_zero() { +fn gt_positive_and_zero() { assert!(decimal("1.0") > decimal("0.0")); assert!(decimal("0.1") > decimal("0.0")); } #[test] #[ignore] -fn test_gt_negative_and_zero() { +fn gt_negative_and_zero() { assert!(decimal("0.0") > decimal("-0.1")); assert!(decimal("0.0") > decimal("-1.0")); } +#[test] +#[ignore] +fn unequal_number_of_decimal_places() { + assert!(decimal("3.14") > decimal("3.13")); + assert!(decimal("3.14") > decimal("3.131")); + assert!(decimal("3.14") > decimal("3.1")); + assert!(decimal("3.13") < decimal("3.14")); + assert!(decimal("3.131") < decimal("3.14")); + assert!(decimal("3.1") < decimal("3.14")); +} + // tests of arbitrary precision behavior #[test] #[ignore] -fn test_add_uneven_position() { +fn add_uneven_position() { assert_eq!(decimal("0.1") + decimal("0.02"), decimal("0.12")); } #[test] #[ignore] -fn test_eq_vary_sig_digits() { +fn eq_vary_sig_digits() { assert!(decimal("0") == decimal("0000000000000.0000000000000000000000")); assert!(decimal("1") == decimal("00000000000000001.000000000000000000")); } #[test] #[ignore] -fn test_add_vary_precision() { +fn add_vary_precision() { assert_eq!( decimal("100000000000000000000000000000000000000000000") + decimal("0.00000000000000000000000000000000000000001"), @@ -134,7 +145,7 @@ fn test_add_vary_precision() { #[test] #[ignore] -fn test_cleanup_precision() { +fn cleanup_precision() { assert_eq!( decimal("10000000000000000000000000000000000000000000000.999999999999999999999999998",) + decimal( @@ -146,7 +157,7 @@ fn test_cleanup_precision() { #[test] #[ignore] -fn test_gt_varying_positive_precisions() { +fn gt_varying_positive_precisions() { assert!(decimal("1.1") > decimal("1.01")); assert!(decimal("1.01") > decimal("1.0")); assert!(decimal("1.0") > decimal("0.1")); @@ -155,7 +166,7 @@ fn test_gt_varying_positive_precisions() { #[test] #[ignore] -fn test_gt_positive_and_negative() { +fn gt_positive_and_negative() { assert!(decimal("1.0") > decimal("-1.0")); assert!(decimal("1.1") > decimal("-1.1")); assert!(decimal("0.1") > decimal("-0.1")); @@ -163,7 +174,7 @@ fn test_gt_positive_and_negative() { #[test] #[ignore] -fn test_gt_varying_negative_precisions() { +fn gt_varying_negative_precisions() { assert!(decimal("-0.01") > decimal("-0.1")); assert!(decimal("-0.1") > decimal("-1.0")); assert!(decimal("-1.0") > decimal("-1.01")); @@ -173,7 +184,7 @@ fn test_gt_varying_negative_precisions() { // test signed properties #[test] #[ignore] -fn test_negatives() { +fn negatives() { assert!(Decimal::try_from("-1").is_some()); assert_eq!(decimal("0") - decimal("1"), decimal("-1")); assert_eq!(decimal("5.5") + decimal("-6.5"), decimal("-1")); @@ -181,21 +192,21 @@ fn test_negatives() { #[test] #[ignore] -fn test_explicit_positive() { +fn explicit_positive() { assert_eq!(decimal("+1"), decimal("1")); assert_eq!(decimal("+2.0") - decimal("-0002.0"), decimal("4")); } #[test] #[ignore] -fn test_multiply_by_negative() { +fn multiply_by_negative() { assert_eq!(decimal("5") * decimal("-0.2"), decimal("-1")); assert_eq!(decimal("-20") * decimal("-0.2"), decimal("4")); } #[test] #[ignore] -fn test_simple_partial_cmp() { +fn simple_partial_cmp() { assert!(decimal("1.0") < decimal("1.1")); assert!(decimal("0.00000000000000000000001") > decimal("-20000000000000000000000000000")); } @@ -205,122 +216,122 @@ fn test_simple_partial_cmp() { // integer and fractional parts of the number are stored separately #[test] #[ignore] -fn test_carry_into_integer() { +fn carry_into_integer() { assert_eq!(decimal("0.901") + decimal("0.1"), decimal("1.001")) } #[test] #[ignore] -fn test_carry_into_fractional_with_digits_to_right() { +fn carry_into_fractional_with_digits_to_right() { assert_eq!(decimal("0.0901") + decimal("0.01"), decimal("0.1001")) } #[test] #[ignore] -fn test_add_carry_over_negative() { +fn add_carry_over_negative() { assert_eq!(decimal("-1.99") + decimal("-0.01"), decimal("-2.0")) } #[test] #[ignore] -fn test_sub_carry_over_negative() { +fn sub_carry_over_negative() { assert_eq!(decimal("-1.99") - decimal("0.01"), decimal("-2.0")) } #[test] #[ignore] -fn test_add_carry_over_negative_with_fractional() { +fn add_carry_over_negative_with_fractional() { assert_eq!(decimal("-1.99") + decimal("-0.02"), decimal("-2.01")) } #[test] #[ignore] -fn test_sub_carry_over_negative_with_fractional() { +fn sub_carry_over_negative_with_fractional() { assert_eq!(decimal("-1.99") - decimal("0.02"), decimal("-2.01")) } #[test] #[ignore] -fn test_carry_from_rightmost_one() { +fn carry_from_rightmost_one() { assert_eq!(decimal("0.09") + decimal("0.01"), decimal("0.1")) } #[test] #[ignore] -fn test_carry_from_rightmost_more() { +fn carry_from_rightmost_more() { assert_eq!(decimal("0.099") + decimal("0.001"), decimal("0.1")) } #[test] #[ignore] -fn test_carry_from_rightmost_into_integer() { +fn carry_from_rightmost_into_integer() { assert_eq!(decimal("0.999") + decimal("0.001"), decimal("1.0")) } // test arithmetic borrow rules #[test] #[ignore] -fn test_add_borrow() { +fn add_borrow() { assert_eq!(decimal("0.01") + decimal("-0.0001"), decimal("0.0099")) } #[test] #[ignore] -fn test_sub_borrow() { +fn sub_borrow() { assert_eq!(decimal("0.01") - decimal("0.0001"), decimal("0.0099")) } #[test] #[ignore] -fn test_add_borrow_integral() { +fn add_borrow_integral() { assert_eq!(decimal("1.0") + decimal("-0.01"), decimal("0.99")) } #[test] #[ignore] -fn test_sub_borrow_integral() { +fn sub_borrow_integral() { assert_eq!(decimal("1.0") - decimal("0.01"), decimal("0.99")) } #[test] #[ignore] -fn test_add_borrow_integral_zeroes() { +fn add_borrow_integral_zeroes() { assert_eq!(decimal("1.0") + decimal("-0.99"), decimal("0.01")) } #[test] #[ignore] -fn test_sub_borrow_integral_zeroes() { +fn sub_borrow_integral_zeroes() { assert_eq!(decimal("1.0") - decimal("0.99"), decimal("0.01")) } #[test] #[ignore] -fn test_borrow_from_negative() { +fn borrow_from_negative() { assert_eq!(decimal("-1.0") + decimal("0.01"), decimal("-0.99")) } #[test] #[ignore] -fn test_add_into_fewer_digits() { +fn add_into_fewer_digits() { assert_eq!(decimal("0.011") + decimal("-0.001"), decimal("0.01")) } // misc tests of arithmetic properties #[test] #[ignore] -fn test_sub_into_fewer_digits() { +fn sub_into_fewer_digits() { assert_eq!(decimal("0.011") - decimal("0.001"), decimal("0.01")) } #[test] #[ignore] -fn test_add_away_decimal() { +fn add_away_decimal() { assert_eq!(decimal("1.1") + decimal("-0.1"), decimal("1.0")) } #[test] #[ignore] -fn test_sub_away_decimal() { +fn sub_away_decimal() { assert_eq!(decimal("1.1") - decimal("0.1"), decimal("1.0")) } diff --git a/exercises/practice/diamond/.docs/instructions.md b/exercises/practice/diamond/.docs/instructions.md index 1de7016f0..3034802fe 100644 --- a/exercises/practice/diamond/.docs/instructions.md +++ b/exercises/practice/diamond/.docs/instructions.md @@ -1,22 +1,21 @@ # Instructions -The diamond kata takes as its input a letter, and outputs it in a diamond -shape. Given a letter, it prints a diamond starting with 'A', with the -supplied letter at the widest point. +The diamond kata takes as its input a letter, and outputs it in a diamond shape. +Given a letter, it prints a diamond starting with 'A', with the supplied letter at the widest point. ## Requirements -* The first row contains one 'A'. -* The last row contains one 'A'. -* All rows, except the first and last, have exactly two identical letters. -* All rows have as many trailing spaces as leading spaces. (This might be 0). -* The diamond is horizontally symmetric. -* The diamond is vertically symmetric. -* The diamond has a square shape (width equals height). -* The letters form a diamond shape. -* The top half has the letters in ascending order. -* The bottom half has the letters in descending order. -* The four corners (containing the spaces) are triangles. +- The first row contains one 'A'. +- The last row contains one 'A'. +- All rows, except the first and last, have exactly two identical letters. +- All rows have as many trailing spaces as leading spaces. (This might be 0). +- The diamond is horizontally symmetric. +- The diamond is vertically symmetric. +- The diamond has a square shape (width equals height). +- The letters form a diamond shape. +- The top half has the letters in ascending order. +- The bottom half has the letters in descending order. +- The four corners (containing the spaces) are triangles. ## Examples diff --git a/exercises/practice/diamond/.gitignore b/exercises/practice/diamond/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/diamond/.gitignore +++ b/exercises/practice/diamond/.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/diamond/.meta/config.json b/exercises/practice/diamond/.meta/config.json index 84e769b1b..b6ac35302 100644 --- a/exercises/practice/diamond/.meta/config.json +++ b/exercises/practice/diamond/.meta/config.json @@ -29,5 +29,5 @@ }, "blurb": "Given a letter, print a diamond starting with 'A' with the supplied letter at the widest point.", "source": "Seb Rose", - "source_url": "http://claysnow.co.uk/recycling-tests-in-tdd/" + "source_url": "https://web.archive.org/web/20220807163751/http://claysnow.co.uk/recycling-tests-in-tdd/" } diff --git a/exercises/practice/diamond/.meta/test_template.tera b/exercises/practice/diamond/.meta/test_template.tera new file mode 100644 index 000000000..1b2188ede --- /dev/null +++ b/exercises/practice/diamond/.meta/test_template.tera @@ -0,0 +1,25 @@ +use diamond::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {# + rustfmt will inline array literals under a minimum length + For us that means up to letter E + We also exclude A to keep it inlined + #} + {%- if test.input.letter is matching("[B-D]") %} + #[rustfmt::skip] + {%- endif %} + assert_eq!( + get_diamond('{{ test.input.letter }}'), + vec![ + {%- for line in test.expected %} + "{{ line }}"{% if test.expected | length > 1 %},{% endif %} + {%- endfor %} + ] + ); +} +{% endfor %} + diff --git a/exercises/practice/diamond/.meta/tests.toml b/exercises/practice/diamond/.meta/tests.toml index be690e975..4e7802ec8 100644 --- a/exercises/practice/diamond/.meta/tests.toml +++ b/exercises/practice/diamond/.meta/tests.toml @@ -1,3 +1,25 @@ -# 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. + +[202fb4cc-6a38-4883-9193-a29d5cb92076] +description = "Degenerate case with a single 'A' row" + +[bd6a6d78-9302-42e9-8f60-ac1461e9abae] +description = "Degenerate case with no row containing 3 distinct groups of spaces" + +[af8efb49-14ed-447f-8944-4cc59ce3fd76] +description = "Smallest non-degenerate case with odd diamond side length" + +[e0c19a95-9888-4d05-86a0-fa81b9e70d1d] +description = "Smallest non-degenerate case with even diamond side length" + +[82ea9aa9-4c0e-442a-b07e-40204e925944] +description = "Largest possible diamond" diff --git a/exercises/practice/diamond/Cargo.toml b/exercises/practice/diamond/Cargo.toml index f5357766f..436ce7a0b 100644 --- a/exercises/practice/diamond/Cargo.toml +++ b/exercises/practice/diamond/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "diamond" -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/diamond/src/lib.rs b/exercises/practice/diamond/src/lib.rs index 0bcc23a32..db78003f8 100644 --- a/exercises/practice/diamond/src/lib.rs +++ b/exercises/practice/diamond/src/lib.rs @@ -1,5 +1,3 @@ pub fn get_diamond(c: char) -> Vec { - unimplemented!( - "Return the vector of strings which represent the diamond with particular char {c}" - ); + todo!("Return the vector of strings which represent the diamond with particular char {c}"); } diff --git a/exercises/practice/diamond/tests/diamond.rs b/exercises/practice/diamond/tests/diamond.rs index cb1c09005..ce1bb2ade 100644 --- a/exercises/practice/diamond/tests/diamond.rs +++ b/exercises/practice/diamond/tests/diamond.rs @@ -1,13 +1,13 @@ use diamond::*; #[test] -fn test_a() { +fn degenerate_case_with_a_single_a_row() { assert_eq!(get_diamond('A'), vec!["A"]); } #[test] #[ignore] -fn test_b() { +fn degenerate_case_with_no_row_containing_3_distinct_groups_of_spaces() { #[rustfmt::skip] assert_eq!( get_diamond('B'), @@ -21,7 +21,7 @@ fn test_b() { #[test] #[ignore] -fn test_c() { +fn smallest_non_degenerate_case_with_odd_diamond_side_length() { #[rustfmt::skip] assert_eq!( get_diamond('C'), @@ -37,7 +37,7 @@ fn test_c() { #[test] #[ignore] -fn test_d() { +fn smallest_non_degenerate_case_with_even_diamond_side_length() { #[rustfmt::skip] assert_eq!( get_diamond('D'), @@ -55,7 +55,7 @@ fn test_d() { #[test] #[ignore] -fn test_e() { +fn largest_possible_diamond() { assert_eq!( get_diamond('Z'), vec![ diff --git a/exercises/practice/difference-of-squares/.docs/instructions.md b/exercises/practice/difference-of-squares/.docs/instructions.md index c3999e86a..39c38b509 100644 --- a/exercises/practice/difference-of-squares/.docs/instructions.md +++ b/exercises/practice/difference-of-squares/.docs/instructions.md @@ -8,10 +8,7 @@ The square of the sum of the first ten natural numbers is The sum of the squares of the first ten natural numbers is 1² + 2² + ... + 10² = 385. -Hence the difference between the square of the sum of the first -ten natural numbers and the sum of the squares of the first ten -natural numbers is 3025 - 385 = 2640. +Hence the difference between the square of the sum of the first ten natural numbers and the sum of the squares of the first ten natural numbers is 3025 - 385 = 2640. -You are not expected to discover an efficient solution to this yourself from -first principles; research is allowed, indeed, encouraged. Finding the best -algorithm for the problem is a key skill in software engineering. +You are not expected to discover an efficient solution to this yourself from first principles; research is allowed, indeed, encouraged. +Finding the best algorithm for the problem is a key skill in software engineering. diff --git a/exercises/practice/difference-of-squares/.gitignore b/exercises/practice/difference-of-squares/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/difference-of-squares/.gitignore +++ b/exercises/practice/difference-of-squares/.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/difference-of-squares/.meta/config.json b/exercises/practice/difference-of-squares/.meta/config.json index 25c70ef56..de21882e8 100644 --- a/exercises/practice/difference-of-squares/.meta/config.json +++ b/exercises/practice/difference-of-squares/.meta/config.json @@ -29,7 +29,7 @@ "Cargo.toml" ], "test": [ - "tests/difference-of-squares.rs" + "tests/difference_of_squares.rs" ], "example": [ ".meta/example.rs" @@ -37,5 +37,5 @@ }, "blurb": "Find the difference between the square of the sum and the sum of the squares of the first N natural numbers.", "source": "Problem 6 at Project Euler", - "source_url": "http://projecteuler.net/problem=6" + "source_url": "https://projecteuler.net/problem=6" } diff --git a/exercises/practice/difference-of-squares/.meta/test_template.tera b/exercises/practice/difference-of-squares/.meta/test_template.tera new file mode 100644 index 000000000..7aa683dfd --- /dev/null +++ b/exercises/practice/difference-of-squares/.meta/test_template.tera @@ -0,0 +1,26 @@ +use difference_of_squares as squares; + +{% for group in cases %} +{% for test in group.cases %} + +{% if test.property == "squareOfSum" %} + {% set function = "square_of_sum" %} +{% elif test.property == "sumOfSquares" %} + {% set function = "sum_of_squares" %} +{% elif test.property == "differenceOfSquares" %} + {% set function = "difference" %} +{% endif %} + +{% filter replace(from="difference_of_squares", to="difference") %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert_eq!( + {{ test.expected | fmt_num }}, + squares::{{ function }}({{ test.input.number }}) + ); +} +{% endfilter %} + +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/difference-of-squares/.meta/tests.toml b/exercises/practice/difference-of-squares/.meta/tests.toml index be690e975..e54414c0a 100644 --- a/exercises/practice/difference-of-squares/.meta/tests.toml +++ b/exercises/practice/difference-of-squares/.meta/tests.toml @@ -1,3 +1,37 @@ -# 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. + +[e46c542b-31fc-4506-bcae-6b62b3268537] +description = "Square the sum of the numbers up to the given number -> square of sum 1" + +[9b3f96cb-638d-41ee-99b7-b4f9c0622948] +description = "Square the sum of the numbers up to the given number -> square of sum 5" + +[54ba043f-3c35-4d43-86ff-3a41625d5e86] +description = "Square the sum of the numbers up to the given number -> square of sum 100" + +[01d84507-b03e-4238-9395-dd61d03074b5] +description = "Sum the squares of the numbers up to the given number -> sum of squares 1" + +[c93900cd-8cc2-4ca4-917b-dd3027023499] +description = "Sum the squares of the numbers up to the given number -> sum of squares 5" + +[94807386-73e4-4d9e-8dec-69eb135b19e4] +description = "Sum the squares of the numbers up to the given number -> sum of squares 100" + +[44f72ae6-31a7-437f-858d-2c0837adabb6] +description = "Subtract sum of squares from square of sums -> difference of squares 1" + +[005cb2bf-a0c8-46f3-ae25-924029f8b00b] +description = "Subtract sum of squares from square of sums -> difference of squares 5" + +[b1bf19de-9a16-41c0-a62b-1f02ecc0b036] +description = "Subtract sum of squares from square of sums -> difference of squares 100" diff --git a/exercises/practice/difference-of-squares/Cargo.toml b/exercises/practice/difference-of-squares/Cargo.toml index 71d56f826..6b0bc15cd 100644 --- a/exercises/practice/difference-of-squares/Cargo.toml +++ b/exercises/practice/difference-of-squares/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "difference-of-squares" -version = "1.2.0" +name = "difference_of_squares" +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/difference-of-squares/src/lib.rs b/exercises/practice/difference-of-squares/src/lib.rs index 402286a6c..fdf2e93d8 100644 --- a/exercises/practice/difference-of-squares/src/lib.rs +++ b/exercises/practice/difference-of-squares/src/lib.rs @@ -1,11 +1,11 @@ pub fn square_of_sum(n: u32) -> u32 { - unimplemented!("square of sum of 1...{n}") + todo!("square of sum of 1...{n}") } pub fn sum_of_squares(n: u32) -> u32 { - unimplemented!("sum of squares of 1...{n}") + todo!("sum of squares of 1...{n}") } pub fn difference(n: u32) -> u32 { - unimplemented!("difference between square of sum of 1...{n} and sum of squares of 1...{n}") + todo!("difference between square of sum of 1...{n} and sum of squares of 1...{n}") } diff --git a/exercises/practice/difference-of-squares/tests/difference-of-squares.rs b/exercises/practice/difference-of-squares/tests/difference_of_squares.rs similarity index 72% rename from exercises/practice/difference-of-squares/tests/difference-of-squares.rs rename to exercises/practice/difference-of-squares/tests/difference_of_squares.rs index 552b9c4de..672faaee0 100644 --- a/exercises/practice/difference-of-squares/tests/difference-of-squares.rs +++ b/exercises/practice/difference-of-squares/tests/difference_of_squares.rs @@ -1,54 +1,54 @@ use difference_of_squares as squares; #[test] -fn test_square_of_sum_1() { +fn square_of_sum_1() { assert_eq!(1, squares::square_of_sum(1)); } #[test] #[ignore] -fn test_square_of_sum_5() { +fn square_of_sum_5() { assert_eq!(225, squares::square_of_sum(5)); } #[test] #[ignore] -fn test_square_of_sum_100() { +fn square_of_sum_100() { assert_eq!(25_502_500, squares::square_of_sum(100)); } #[test] #[ignore] -fn test_sum_of_squares_1() { +fn sum_of_squares_1() { assert_eq!(1, squares::sum_of_squares(1)); } #[test] #[ignore] -fn test_sum_of_squares_5() { +fn sum_of_squares_5() { assert_eq!(55, squares::sum_of_squares(5)); } #[test] #[ignore] -fn test_sum_of_squares_100() { +fn sum_of_squares_100() { assert_eq!(338_350, squares::sum_of_squares(100)); } #[test] #[ignore] -fn test_difference_1() { +fn difference_1() { assert_eq!(0, squares::difference(1)); } #[test] #[ignore] -fn test_difference_5() { +fn difference_5() { assert_eq!(170, squares::difference(5)); } #[test] #[ignore] -fn test_difference_100() { +fn difference_100() { assert_eq!(25_164_150, squares::difference(100)); } diff --git a/exercises/practice/diffie-hellman/.docs/instructions.md b/exercises/practice/diffie-hellman/.docs/instructions.md index 589cbd19f..9f1c85e31 100644 --- a/exercises/practice/diffie-hellman/.docs/instructions.md +++ b/exercises/practice/diffie-hellman/.docs/instructions.md @@ -2,9 +2,8 @@ Diffie-Hellman key exchange. -Alice and Bob use Diffie-Hellman key exchange to share secrets. They -start with prime numbers, pick private keys, generate and share public -keys, and then generate a shared secret key. +Alice and Bob use Diffie-Hellman key exchange to share secrets. +They start with prime numbers, pick private keys, generate and share public keys, and then generate a shared secret key. ## Step 0 @@ -12,27 +11,27 @@ The test program supplies prime numbers p and g. ## Step 1 -Alice picks a private key, a, greater than 1 and less than p. Bob does -the same to pick a private key b. +Alice picks a private key, a, greater than 1 and less than p. +Bob does the same to pick a private key b. ## Step 2 Alice calculates a public key A. - A = g**a mod p + A = gᵃ mod p -Using the same p and g, Bob similarly calculates a public key B from his -private key b. +Using the same p and g, Bob similarly calculates a public key B from his private key b. ## Step 3 -Alice and Bob exchange public keys. Alice calculates secret key s. +Alice and Bob exchange public keys. +Alice calculates secret key s. - s = B**a mod p + s = Bᵃ mod p Bob calculates - s = A**b mod p + s = Aᵇ mod p -The calculations produce the same result! Alice and Bob now share -secret s. +The calculations produce the same result! +Alice and Bob now share secret s. diff --git a/exercises/practice/diffie-hellman/.gitignore b/exercises/practice/diffie-hellman/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/diffie-hellman/.gitignore +++ b/exercises/practice/diffie-hellman/.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/diffie-hellman/.meta/Cargo-example.toml b/exercises/practice/diffie-hellman/.meta/Cargo-example.toml index 49b664725..54647854c 100644 --- a/exercises/practice/diffie-hellman/.meta/Cargo-example.toml +++ b/exercises/practice/diffie-hellman/.meta/Cargo-example.toml @@ -1,8 +1,11 @@ [package] -edition = "2021" -name = "diffie-hellman" +name = "diffie_hellman" 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] rand = "0.3.18" diff --git a/exercises/practice/diffie-hellman/.meta/config.json b/exercises/practice/diffie-hellman/.meta/config.json index 94b35bae0..327e6ab3c 100644 --- a/exercises/practice/diffie-hellman/.meta/config.json +++ b/exercises/practice/diffie-hellman/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/diffie-hellman.rs" + "tests/diffie_hellman.rs" ], "example": [ ".meta/example.rs" @@ -31,5 +31,5 @@ }, "blurb": "Diffie-Hellman key exchange.", "source": "Wikipedia, 1024 bit key from www.cryptopp.com/wiki.", - "source_url": "http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange" + "source_url": "https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange" } diff --git a/exercises/practice/diffie-hellman/Cargo.toml b/exercises/practice/diffie-hellman/Cargo.toml index c76e7ab34..82e5d871d 100644 --- a/exercises/practice/diffie-hellman/Cargo.toml +++ b/exercises/practice/diffie-hellman/Cargo.toml @@ -1,8 +1,11 @@ [package] -edition = "2021" -name = "diffie-hellman" +name = "diffie_hellman" 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] diff --git a/exercises/practice/diffie-hellman/src/lib.rs b/exercises/practice/diffie-hellman/src/lib.rs index a38954435..157e540d7 100644 --- a/exercises/practice/diffie-hellman/src/lib.rs +++ b/exercises/practice/diffie-hellman/src/lib.rs @@ -1,13 +1,11 @@ pub fn private_key(p: u64) -> u64 { - unimplemented!("Pick a private key greater than 1 and less than {p}") + todo!("Pick a private key greater than 1 and less than {p}") } pub fn public_key(p: u64, g: u64, a: u64) -> u64 { - unimplemented!("Calculate public key using prime numbers {p} and {g}, and private key {a}") + todo!("Calculate public key using prime numbers {p} and {g}, and private key {a}") } pub fn secret(p: u64, b_pub: u64, a: u64) -> u64 { - unimplemented!( - "Calculate secret key using prime number {p}, public key {b_pub}, and private key {a}" - ) + todo!("Calculate secret key using prime number {p}, public key {b_pub}, and private key {a}") } diff --git a/exercises/practice/diffie-hellman/tests/diffie-hellman.rs b/exercises/practice/diffie-hellman/tests/diffie_hellman.rs similarity index 89% rename from exercises/practice/diffie-hellman/tests/diffie-hellman.rs rename to exercises/practice/diffie-hellman/tests/diffie_hellman.rs index 5c7504304..da338d9a0 100644 --- a/exercises/practice/diffie-hellman/tests/diffie-hellman.rs +++ b/exercises/practice/diffie-hellman/tests/diffie_hellman.rs @@ -1,7 +1,7 @@ use diffie_hellman::*; #[test] -fn test_private_key_in_range_key() { +fn private_key_in_range_key() { let primes: Vec = vec![ 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 773, 967, 3461, 6131, ]; @@ -14,7 +14,7 @@ fn test_private_key_in_range_key() { #[test] #[ignore] -fn test_public_key_correct() { +fn public_key_correct() { let p: u64 = 23; let g: u64 = 5; @@ -26,7 +26,7 @@ fn test_public_key_correct() { #[test] #[ignore] -fn test_secret_key_correct() { +fn secret_key_correct() { let p: u64 = 11; let private_key_a = 7; @@ -39,7 +39,7 @@ fn test_secret_key_correct() { #[test] #[ignore] -fn test_public_key_correct_big_numbers() { +fn public_key_correct_big_numbers() { let p: u64 = 4_294_967_299; let g: u64 = 8; @@ -53,7 +53,7 @@ fn test_public_key_correct_big_numbers() { #[test] #[ignore] -fn test_secret_key_correct_big_numbers() { +fn secret_key_correct_big_numbers() { let p: u64 = 4_294_967_927; let private_key_a = 4_294_967_300; @@ -80,7 +80,7 @@ const PUBLIC_KEY_64BIT: u64 = 0xB851_EB85_1EB8_51C1; #[test] #[ignore] #[cfg(feature = "big-primes")] -fn test_public_key_correct_biggest_numbers() { +fn public_key_correct_biggest_numbers() { assert_eq!( public_key(PRIME_64BIT_1, PRIME_64BIT_2, PRIVATE_KEY_64BIT), PUBLIC_KEY_64BIT @@ -90,7 +90,7 @@ fn test_public_key_correct_biggest_numbers() { #[test] #[ignore] #[cfg(feature = "big-primes")] -fn test_secret_key_correct_biggest_numbers() { +fn secret_key_correct_biggest_numbers() { let private_key_b = 0xEFFF_FFFF_FFFF_FFC0; let public_key_b = public_key(PRIME_64BIT_1, PRIME_64BIT_2, private_key_b); @@ -111,7 +111,7 @@ fn test_secret_key_correct_biggest_numbers() { #[test] #[ignore] #[cfg(feature = "big-primes")] -fn test_changed_secret_key_biggest_numbers() { +fn changed_secret_key_biggest_numbers() { let private_key_a = private_key(PRIME_64BIT_1); let public_key_a = public_key(PRIME_64BIT_1, PRIME_64BIT_2, private_key_a); @@ -126,7 +126,7 @@ fn test_changed_secret_key_biggest_numbers() { #[test] #[ignore] -fn test_changed_secret_key() { +fn changed_secret_key() { let p: u64 = 13; let g: u64 = 11; diff --git a/exercises/practice/dominoes/.docs/instructions.md b/exercises/practice/dominoes/.docs/instructions.md index 47f05a60d..75055b9e8 100644 --- a/exercises/practice/dominoes/.docs/instructions.md +++ b/exercises/practice/dominoes/.docs/instructions.md @@ -2,14 +2,14 @@ Make a chain of dominoes. -Compute a way to order a given set of dominoes in such a way that they form a -correct domino chain (the dots on one half of a stone match the dots on the -neighbouring half of an adjacent stone) and that dots on the halves of the -stones which don't have a neighbour (the first and last stone) match each other. +Compute a way to order a given set of domino stones so that they form a correct domino chain. +In the chain, the dots on one half of a stone must match the dots on the neighboring half of an adjacent stone. +Additionally, the dots on the halves of the stones without neighbors (the first and last stone) must match each other. For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something like `[1|2] [2|3] [3|1]` or `[3|2] [2|1] [1|3]` or `[1|3] [3|2] [2|1]` etc, where the first and last numbers are the same. -For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same. 4 != 3 +For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same. +4 != 3 Some test cases may use duplicate stones in a chain solution, assume that multiple Domino sets are being used. diff --git a/exercises/practice/dominoes/.docs/introduction.md b/exercises/practice/dominoes/.docs/introduction.md new file mode 100644 index 000000000..df248c211 --- /dev/null +++ b/exercises/practice/dominoes/.docs/introduction.md @@ -0,0 +1,13 @@ +# Introduction + +In Toyland, the trains are always busy delivering treasures across the city, from shiny marbles to rare building blocks. +The tracks they run on are made of colorful domino-shaped pieces, each marked with two numbers. +For the trains to move, the dominoes must form a perfect chain where the numbers match. + +Today, an urgent delivery of rare toys is on hold. +You've been handed a set of track pieces to inspect. +If they can form a continuous chain, the train will be on its way, bringing smiles across Toyland. +If not, the set will be discarded, and another will be tried. + +The toys are counting on you to solve this puzzle. +Will the dominoes connect the tracks and send the train rolling, or will the set be left behind? diff --git a/exercises/practice/dominoes/.gitignore b/exercises/practice/dominoes/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/dominoes/.gitignore +++ b/exercises/practice/dominoes/.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/dominoes/.meta/example.rs b/exercises/practice/dominoes/.meta/example.rs index bf166e810..14ed1ef24 100644 --- a/exercises/practice/dominoes/.meta/example.rs +++ b/exercises/practice/dominoes/.meta/example.rs @@ -1,5 +1,3 @@ -use std::iter; - pub type Domino = (u8, u8); /// A table keeping track of available dominoes. @@ -14,7 +12,7 @@ struct AvailabilityTable { impl AvailabilityTable { fn new() -> AvailabilityTable { AvailabilityTable { - m: iter::repeat(0).take(6 * 6).collect(), + m: std::iter::repeat_n(0, 6 * 6).collect(), } } @@ -52,7 +50,7 @@ impl AvailabilityTable { } } else { // For this toy code hard explicit fail is best - panic!("remove for 0 stones: ({:?}, {:?})", x, y) + panic!("remove for 0 stones: ({x:?}, {y:?})") } } diff --git a/exercises/practice/dominoes/.meta/test_template.tera b/exercises/practice/dominoes/.meta/test_template.tera new file mode 100644 index 000000000..22335e994 --- /dev/null +++ b/exercises/practice/dominoes/.meta/test_template.tera @@ -0,0 +1,61 @@ +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = &[ + {% for domino in test.input.dominoes %} + ({{ domino.0 }}, {{domino.1 }}), + {% endfor %} + ]; + {%- if test.expected %} + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); + {%- else %} + assert!(dominoes::chain(input).is_none()); + {%- endif %} +} +{% endfor -%} + +type Domino = (u8, u8); + +fn assert_correct(input: &[Domino], output: Vec) { + if input.len() != output.len() { + panic!("Length mismatch for input {input:?}, output {output:?}"); + } else if input.is_empty() { + // and thus output.is_empty() + return; + } + + let mut output_sorted = output + .iter() + .map(|&d| normalize(d)) + .collect::>(); + output_sorted.sort_unstable(); + let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::>(); + input_sorted.sort_unstable(); + if input_sorted != output_sorted { + panic!("Domino mismatch for input {input:?}, output {output:?}"); + } + + // both input and output have at least 1 element + // This essentially puts the first element after the last one, thereby making it + // easy to check whether the domino chains "wraps around". + { + let mut n = output[0].1; + let iter = output.iter().skip(1).chain(output.iter().take(1)); + for &(first, second) in iter { + if n != first { + panic!("Chaining failure for input {input:?}, output {output:?}") + } + n = second + } + } +} + +fn normalize(d: Domino) -> Domino { + match d { + (m, n) if m > n => (n, m), + (m, n) => (m, n), + } +} diff --git a/exercises/practice/dominoes/.meta/tests.toml b/exercises/practice/dominoes/.meta/tests.toml index ff6932826..08c8e08d0 100644 --- a/exercises/practice/dominoes/.meta/tests.toml +++ b/exercises/practice/dominoes/.meta/tests.toml @@ -1,13 +1,41 @@ -# 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. + +[31a673f2-5e54-49fe-bd79-1c1dae476c9c] +description = "empty input = empty output" + +[4f99b933-367b-404b-8c6d-36d5923ee476] +description = "singleton input = singleton output" [91122d10-5ec7-47cb-b759-033756375869] description = "singleton that can't be chained" +[be8bc26b-fd3d-440b-8e9f-d698a0623be3] +description = "three elements" + [99e615c6-c059-401c-9e87-ad7af11fea5c] description = "can reverse dominoes" +[51f0c291-5d43-40c5-b316-0429069528c9] +description = "can't be chained" + +[9a75e078-a025-4c23-8c3a-238553657f39] +description = "disconnected - simple" + +[0da0c7fe-d492-445d-b9ef-1f111f07a301] +description = "disconnected - double loop" + +[b6087ff0-f555-4ea0-a71c-f9d707c5994a] +description = "disconnected - single isolated" + [2174fbdc-8b48-4bac-9914-8090d06ef978] description = "need backtrack" @@ -16,3 +44,6 @@ description = "separate loops" [cd061538-6046-45a7-ace9-6708fe8f6504] description = "nine elements" + +[44704c7c-3adb-4d98-bd30-f45527cf8b49] +description = "separate three-domino loops" diff --git a/exercises/practice/dominoes/Cargo.toml b/exercises/practice/dominoes/Cargo.toml index 1ecfccf0f..38fa62836 100644 --- a/exercises/practice/dominoes/Cargo.toml +++ b/exercises/practice/dominoes/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "dominoes" -version = "2.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/dominoes/src/lib.rs b/exercises/practice/dominoes/src/lib.rs index 7591c7ab9..e1419acd4 100644 --- a/exercises/practice/dominoes/src/lib.rs +++ b/exercises/practice/dominoes/src/lib.rs @@ -1,3 +1,5 @@ pub fn chain(input: &[(u8, u8)]) -> Option> { - unimplemented!("From the given input '{input:?}' construct a proper dominoes chain or return None if it is not possible."); + todo!( + "From the given input '{input:?}' construct a proper dominoes chain or return None if it is not possible." + ); } diff --git a/exercises/practice/dominoes/tests/dominoes.rs b/exercises/practice/dominoes/tests/dominoes.rs index 00bec57b2..1153a3b24 100644 --- a/exercises/practice/dominoes/tests/dominoes.rs +++ b/exercises/practice/dominoes/tests/dominoes.rs @@ -1,166 +1,89 @@ -use crate::CheckResult::*; - -type Domino = (u8, u8); - -#[derive(Debug)] -enum CheckResult { - GotInvalid, // chain returned None - Correct, - ChainingFailure(Vec), // failure to match the dots at the right side of one domino with - // the one on the left side of the next - LengthMismatch(Vec), - DominoMismatch(Vec), // different dominoes are used in input and output -} - -fn normalize(d: Domino) -> Domino { - match d { - (m, n) if m > n => (n, m), - (m, n) => (m, n), - } -} - -fn check(input: &[Domino]) -> CheckResult { - let output = match dominoes::chain(input) { - None => return GotInvalid, - Some(o) => o, - }; - if input.len() != output.len() { - return LengthMismatch(output); - } else if input.is_empty() { - // and thus output.is_empty() - return Correct; - } - - let mut output_sorted = output - .iter() - .map(|&d| normalize(d)) - .collect::>(); - output_sorted.sort_unstable(); - let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::>(); - input_sorted.sort_unstable(); - if input_sorted != output_sorted { - return DominoMismatch(output); - } - - // both input and output have at least 1 element - // This essentially puts the first element after the last one, thereby making it - // easy to check whether the domino chains "wraps around". - let mut fail = false; - { - let mut n = output[0].1; - let iter = output.iter().skip(1).chain(output.iter().take(1)); - for &(first, second) in iter { - if n != first { - fail = true; - break; - } - n = second - } - } - if fail { - ChainingFailure(output) - } else { - Correct - } -} - -fn assert_correct(input: &[Domino]) { - match check(input) { - Correct => (), - GotInvalid => panic!("Unexpectedly got invalid on input {input:?}"), - ChainingFailure(output) => { - panic!("Chaining failure for input {input:?}, output {output:?}") - } - LengthMismatch(output) => { - panic!("Length mismatch for input {input:?}, output {output:?}") - } - DominoMismatch(output) => { - panic!("Domino mismatch for input {input:?}, output {output:?}") - } - } -} - #[test] fn empty_input_empty_output() { let input = &[]; - assert_eq!(dominoes::chain(input), Some(vec![])); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] fn singleton_input_singleton_output() { let input = &[(1, 1)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] -fn singleton_that_cant_be_chained() { +fn singleton_that_can_t_be_chained() { let input = &[(1, 2)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] -fn no_repeat_numbers() { +fn three_elements() { let input = &[(1, 2), (3, 1), (2, 3)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] fn can_reverse_dominoes() { let input = &[(1, 2), (1, 3), (2, 3)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] -fn no_chains() { +fn can_t_be_chained() { let input = &[(1, 2), (4, 1), (2, 3)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn disconnected_simple() { let input = &[(1, 1), (2, 2)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn disconnected_double_loop() { let input = &[(1, 2), (2, 1), (3, 4), (4, 3)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn disconnected_single_isolated() { let input = &[(1, 2), (2, 3), (3, 1), (4, 4)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn need_backtrack() { let input = &[(1, 2), (2, 3), (3, 1), (2, 4), (2, 4)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] fn separate_loops() { let input = &[(1, 2), (2, 3), (3, 1), (1, 1), (2, 2), (3, 3)]; - assert_correct(input); -} - -#[test] -#[ignore] -fn pop_same_value_first() { - let input = &[(2, 3), (3, 1), (1, 1), (2, 2), (3, 3), (2, 1)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] @@ -177,5 +100,56 @@ fn nine_elements() { (3, 4), (5, 6), ]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); +} + +#[test] +#[ignore] +fn separate_three_domino_loops() { + let input = &[(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]; + assert!(dominoes::chain(input).is_none()); +} +type Domino = (u8, u8); + +fn assert_correct(input: &[Domino], output: Vec) { + if input.len() != output.len() { + panic!("Length mismatch for input {input:?}, output {output:?}"); + } else if input.is_empty() { + // and thus output.is_empty() + return; + } + + let mut output_sorted = output + .iter() + .map(|&d| normalize(d)) + .collect::>(); + output_sorted.sort_unstable(); + let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::>(); + input_sorted.sort_unstable(); + if input_sorted != output_sorted { + panic!("Domino mismatch for input {input:?}, output {output:?}"); + } + + // both input and output have at least 1 element + // This essentially puts the first element after the last one, thereby making it + // easy to check whether the domino chains "wraps around". + { + let mut n = output[0].1; + let iter = output.iter().skip(1).chain(output.iter().take(1)); + for &(first, second) in iter { + if n != first { + panic!("Chaining failure for input {input:?}, output {output:?}") + } + n = second + } + } +} + +fn normalize(d: Domino) -> Domino { + match d { + (m, n) if m > n => (n, m), + (m, n) => (m, n), + } } diff --git a/exercises/practice/dot-dsl/.docs/instructions.append.md b/exercises/practice/dot-dsl/.docs/instructions.append.md index f2bb96c02..c9aabe67c 100644 --- a/exercises/practice/dot-dsl/.docs/instructions.append.md +++ b/exercises/practice/dot-dsl/.docs/instructions.append.md @@ -1,7 +1,9 @@ -# Builder pattern +# Instructions append + +## Builder pattern This exercise expects you to build several structs using `builder pattern`. -In short, this pattern allows you to split the construction function of your struct, that contains a lot of arguments, into +In short, this pattern allows you to split the construction function of your struct, that contains a lot of arguments, into several separate functions. This approach gives you the means to make compact but highly-flexible struct construction and configuration. You can read more about it on the [following page](https://doc.rust-lang.org/1.0.0/style/ownership/builders.html). diff --git a/exercises/practice/dot-dsl/.docs/instructions.md b/exercises/practice/dot-dsl/.docs/instructions.md index f61f3f0b4..5e65ebef9 100644 --- a/exercises/practice/dot-dsl/.docs/instructions.md +++ b/exercises/practice/dot-dsl/.docs/instructions.md @@ -1,16 +1,12 @@ # Instructions -A [Domain Specific Language -(DSL)](https://en.wikipedia.org/wiki/Domain-specific_language) is a -small language optimized for a specific domain. Since a DSL is -targeted, it can greatly impact productivity/understanding by allowing the -writer to declare *what* they want rather than *how*. +A [Domain Specific Language (DSL)][dsl] is a small language optimized for a specific domain. +Since a DSL is targeted, it can greatly impact productivity/understanding by allowing the writer to declare _what_ they want rather than _how_. One problem area where they are applied are complex customizations/configurations. -For example the [DOT language](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) allows -you to write a textual description of a graph which is then transformed into a picture by one of -the [Graphviz](http://graphviz.org/) tools (such as `dot`). A simple graph looks like this: +For example the [DOT language][dot-language] allows you to write a textual description of a graph which is then transformed into a picture by one of the [Graphviz][graphviz] tools (such as `dot`). +A simple graph looks like this: graph { graph [bgcolor="yellow"] @@ -19,15 +15,16 @@ the [Graphviz](http://graphviz.org/) tools (such as `dot`). A simple graph looks a -- b [color="green"] } -Putting this in a file `example.dot` and running `dot example.dot -T png --o example.png` creates an image `example.png` with red and blue circle -connected by a green line on a yellow background. +Putting this in a file `example.dot` and running `dot example.dot -T png -o example.png` creates an image `example.png` with red and blue circle connected by a green line on a yellow background. Write a Domain Specific Language similar to the Graphviz dot language. -Our DSL is similar to the Graphviz dot language in that our DSL will be used -to create graph data structures. However, unlike the DOT Language, our DSL will -be an internal DSL for use only in our language. +Our DSL is similar to the Graphviz dot language in that our DSL will be used to create graph data structures. +However, unlike the DOT Language, our DSL will be an internal DSL for use only in our language. -More information about the difference between internal and external DSLs can be -found [here](https://martinfowler.com/bliki/DomainSpecificLanguage.html). +[Learn more about the difference between internal and external DSLs][fowler-dsl]. + +[dsl]: https://en.wikipedia.org/wiki/Domain-specific_language +[dot-language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language) +[graphviz]: https://graphviz.org/ +[fowler-dsl]: https://martinfowler.com/bliki/DomainSpecificLanguage.html diff --git a/exercises/practice/dot-dsl/.gitignore b/exercises/practice/dot-dsl/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/dot-dsl/.gitignore +++ b/exercises/practice/dot-dsl/.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/dot-dsl/.meta/config.json b/exercises/practice/dot-dsl/.meta/config.json index b01e83179..b46b71528 100644 --- a/exercises/practice/dot-dsl/.meta/config.json +++ b/exercises/practice/dot-dsl/.meta/config.json @@ -20,7 +20,7 @@ "Cargo.toml" ], "test": [ - "tests/dot-dsl.rs" + "tests/dot_dsl.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/dot-dsl/.meta/example.rs b/exercises/practice/dot-dsl/.meta/example.rs index 698088670..e957d61c2 100644 --- a/exercises/practice/dot-dsl/.meta/example.rs +++ b/exercises/practice/dot-dsl/.meta/example.rs @@ -44,6 +44,12 @@ pub mod graph { } } + impl Default for Graph { + fn default() -> Self { + Self::new() + } + } + pub mod graph_items { pub mod edge { use std::collections::HashMap; diff --git a/exercises/practice/dot-dsl/Cargo.toml b/exercises/practice/dot-dsl/Cargo.toml index 81acbd4b2..19072537d 100644 --- a/exercises/practice/dot-dsl/Cargo.toml +++ b/exercises/practice/dot-dsl/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" -name = "dot-dsl" +name = "dot_dsl" 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] -maplit = "1.0.1" + +[lints.clippy] +new_without_default = "allow" diff --git a/exercises/practice/dot-dsl/src/lib.rs b/exercises/practice/dot-dsl/src/lib.rs index ace9a0e88..3f2514dc6 100644 --- a/exercises/practice/dot-dsl/src/lib.rs +++ b/exercises/practice/dot-dsl/src/lib.rs @@ -3,7 +3,7 @@ pub mod graph { impl Graph { pub fn new() -> Self { - unimplemented!("Construct a new Graph struct."); + todo!("Construct a new Graph struct."); } } } diff --git a/exercises/practice/dot-dsl/tests/dot-dsl.rs b/exercises/practice/dot-dsl/tests/dot_dsl.rs similarity index 86% rename from exercises/practice/dot-dsl/tests/dot-dsl.rs rename to exercises/practice/dot-dsl/tests/dot_dsl.rs index cadd478e8..ad9a52fa1 100644 --- a/exercises/practice/dot-dsl/tests/dot-dsl.rs +++ b/exercises/practice/dot-dsl/tests/dot_dsl.rs @@ -1,10 +1,11 @@ +use std::collections::HashMap; + use dot_dsl::graph::graph_items::edge::Edge; use dot_dsl::graph::graph_items::node::Node; use dot_dsl::graph::Graph; -use maplit::hashmap; #[test] -fn test_empty_graph() { +fn empty_graph() { let graph = Graph::new(); assert!(graph.nodes.is_empty()); @@ -16,7 +17,7 @@ fn test_empty_graph() { #[test] #[ignore] -fn test_graph_with_one_node() { +fn graph_with_one_node() { let nodes = vec![Node::new("a")]; let graph = Graph::new().with_nodes(&nodes); @@ -30,7 +31,7 @@ fn test_graph_with_one_node() { #[test] #[ignore] -fn test_graph_with_one_node_with_keywords() { +fn graph_with_one_node_with_keywords() { let nodes = vec![Node::new("a").with_attrs(&[("color", "green")])]; let graph = Graph::new().with_nodes(&nodes); @@ -47,7 +48,7 @@ fn test_graph_with_one_node_with_keywords() { #[test] #[ignore] -fn test_graph_with_one_edge() { +fn graph_with_one_edge() { let edges = vec![Edge::new("a", "b")]; let graph = Graph::new().with_edges(&edges); @@ -61,7 +62,7 @@ fn test_graph_with_one_edge() { #[test] #[ignore] -fn test_graph_with_one_edge_with_keywords() { +fn graph_with_one_edge_with_keywords() { let edges = vec![Edge::new("a", "b").with_attrs(&[("color", "blue")])]; let graph = Graph::new().with_edges(&edges); @@ -78,12 +79,11 @@ fn test_graph_with_one_edge_with_keywords() { #[test] #[ignore] -fn test_graph_with_one_attribute() { +fn graph_with_one_attribute() { let graph = Graph::new().with_attrs(&[("foo", "1")]); - let expected_attrs = hashmap! { - "foo".to_string() => "1".to_string(), - }; + #[allow(clippy::useless_conversion, reason = "allow String and &str")] + let expected_attrs = HashMap::from([("foo".into(), "1".into())]); assert!(graph.nodes.is_empty()); @@ -94,7 +94,7 @@ fn test_graph_with_one_attribute() { #[test] #[ignore] -fn test_graph_with_attributes() { +fn graph_with_attributes() { let nodes = vec![ Node::new("a").with_attrs(&[("color", "green")]), Node::new("c"), @@ -108,11 +108,12 @@ fn test_graph_with_attributes() { let attrs = vec![("foo", "1"), ("title", "Testing Attrs"), ("bar", "true")]; - let expected_attrs = hashmap! { - "foo".to_string() => "1".to_string(), - "title".to_string() => "Testing Attrs".to_string(), - "bar".to_string() => "true".to_string(), - }; + #[allow(clippy::useless_conversion, reason = "allow String and &str")] + let expected_attrs = HashMap::from([ + ("foo".into(), "1".into()), + ("title".into(), "Testing Attrs".into()), + ("bar".into(), "true".into()), + ]); let graph = Graph::new() .with_nodes(&nodes) @@ -141,7 +142,7 @@ fn test_graph_with_attributes() { #[test] #[ignore] -fn test_edges_store_attributes() { +fn edges_store_attributes() { let nodes = vec![ Node::new("a").with_attrs(&[("color", "green")]), Node::new("c"), @@ -178,7 +179,7 @@ fn test_edges_store_attributes() { #[test] #[ignore] -fn test_graph_nodes_store_attributes() { +fn graph_nodes_store_attributes() { let attributes = [("foo", "bar"), ("bat", "baz"), ("bim", "bef")]; let graph = Graph::new().with_nodes( &["a", "b", "c"] diff --git a/exercises/practice/doubly-linked-list/.docs/instructions.append.md b/exercises/practice/doubly-linked-list/.docs/hints.md similarity index 97% rename from exercises/practice/doubly-linked-list/.docs/instructions.append.md rename to exercises/practice/doubly-linked-list/.docs/hints.md index d2a6ef54e..afe765081 100644 --- a/exercises/practice/doubly-linked-list/.docs/instructions.append.md +++ b/exercises/practice/doubly-linked-list/.docs/hints.md @@ -1,4 +1,4 @@ -# Hints +## General * A doubly linked does not have a clear ownership hierarchy, which is why it requires either the use of unsafe or abstractions for shared ownership like `Rc`. The latter has some overhead that is unnecessary diff --git a/exercises/practice/doubly-linked-list/.docs/instructions.md b/exercises/practice/doubly-linked-list/.docs/instructions.md index 4132785db..c8cf7898f 100644 --- a/exercises/practice/doubly-linked-list/.docs/instructions.md +++ b/exercises/practice/doubly-linked-list/.docs/instructions.md @@ -3,11 +3,7 @@ Write a doubly linked list using unsafe Rust, including an iterator over the list and a cursor for efficient mutation. -The doubly linked list is a fundamental data structure in computer science, -often used in the implementation of other data structures. They're -pervasive in functional programming languages, such as Clojure, Erlang, -or Haskell, but far less common in imperative languages such as Ruby or -Python. +The doubly linked list is a fundamental data structure in computer science. Each node in a doubly linked list contains data and pointers to the next and previous node, if they exist. diff --git a/exercises/practice/doubly-linked-list/.gitignore b/exercises/practice/doubly-linked-list/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/doubly-linked-list/.gitignore +++ b/exercises/practice/doubly-linked-list/.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/doubly-linked-list/.meta/config.json b/exercises/practice/doubly-linked-list/.meta/config.json index 5d7dcc88b..c0f7cdea0 100644 --- a/exercises/practice/doubly-linked-list/.meta/config.json +++ b/exercises/practice/doubly-linked-list/.meta/config.json @@ -16,14 +16,11 @@ "Cargo.toml" ], "test": [ - "tests/doubly-linked-list.rs" + "tests/doubly_linked_list.rs" ], "example": [ ".meta/example.rs" ] }, - "blurb": "linked-list", - "custom": { - "allowed-to-not-compile": "Stub allowed to not compile because pre_implemented module cannot be resolved by rustfmt in exercism v3 track structure" - } + "blurb": "Write a more advanced doubly linked list implementation with cursors and iteration." } diff --git a/exercises/practice/doubly-linked-list/.meta/example.rs b/exercises/practice/doubly-linked-list/.meta/example.rs index 8d614af3a..f5c067fb5 100644 --- a/exercises/practice/doubly-linked-list/.meta/example.rs +++ b/exercises/practice/doubly-linked-list/.meta/example.rs @@ -62,8 +62,16 @@ impl Node { // `left` and `right` must point to adjacent nodes unsafe fn link(mut left: NodePtr, mut right: NodePtr) { - left.as_mut().next = Some(right); - right.as_mut().prev = Some(left); + unsafe { + left.as_mut().next = Some(right); + right.as_mut().prev = Some(left); + } + } +} + +impl Default for LinkedList { + fn default() -> Self { + Self::new() } } @@ -158,11 +166,13 @@ impl Cursor<'_, T> { // a mutable reference to its element. // `get_next` must return None or a pointer owned by the linked list unsafe fn _step(&mut self, get_next: impl Fn(&Node) -> OptNodePtr) -> Option<&mut T> { - // safe due to L1: All NodePtrs are valid - let new_pos = get_next(self.node?.as_ref())?; - self.node = Some(new_pos); - // returning a mutable reference is safe for the same reason peek_mut() is safe - Some(&mut (*new_pos.as_ptr()).element) + unsafe { + // safe due to L1: All NodePtrs are valid + let new_pos = get_next(self.node?.as_ref())?; + self.node = Some(new_pos); + // returning a mutable reference is safe for the same reason peek_mut() is safe + Some(&mut (*new_pos.as_ptr()).element) + } } pub fn take(&mut self) -> Option { @@ -253,7 +263,7 @@ impl Cursor<'_, T> { } }; link_new_node(cursor_node, new_node); - let end_node = end_node(&mut self.list); + let end_node = end_node(self.list); if *end_node == Some(cursor_node) { *end_node = Some(new_node); } diff --git a/exercises/practice/doubly-linked-list/Cargo.toml b/exercises/practice/doubly-linked-list/Cargo.toml index 87094f3ae..4b3ee76bc 100644 --- a/exercises/practice/doubly-linked-list/Cargo.toml +++ b/exercises/practice/doubly-linked-list/Cargo.toml @@ -1,8 +1,17 @@ [package] -name = "doubly-linked-list" -version = "0.0.0" -edition = "2021" +name = "doubly_linked_list" +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] # check correct covariance and Send, Sync advanced = [] + +[lints.clippy] +drop_non_drop = "allow" # tests call drop before students have implemented it +new_without_default = "allow" diff --git a/exercises/practice/doubly-linked-list/src/lib.rs b/exercises/practice/doubly-linked-list/src/lib.rs index d6391c6f3..a02fdb3f9 100644 --- a/exercises/practice/doubly-linked-list/src/lib.rs +++ b/exercises/practice/doubly-linked-list/src/lib.rs @@ -11,7 +11,7 @@ pub struct Iter<'a, T>(std::marker::PhantomData<&'a T>); impl LinkedList { pub fn new() -> Self { - unimplemented!() + todo!() } // You may be wondering why it's necessary to have is_empty() @@ -20,26 +20,26 @@ impl LinkedList { // whereas is_empty() is almost always cheap. // (Also ask yourself whether len() is expensive for LinkedList) pub fn is_empty(&self) -> bool { - unimplemented!() + todo!() } pub fn len(&self) -> usize { - unimplemented!() + todo!() } /// Return a cursor positioned on the front element pub fn cursor_front(&mut self) -> Cursor<'_, T> { - unimplemented!() + todo!() } /// Return a cursor positioned on the back element pub fn cursor_back(&mut self) -> Cursor<'_, T> { - unimplemented!() + todo!() } /// Return an iterator that moves from front to back pub fn iter(&self) -> Iter<'_, T> { - unimplemented!() + todo!() } } @@ -48,35 +48,35 @@ impl LinkedList { impl Cursor<'_, T> { /// Take a mutable reference to the current element pub fn peek_mut(&mut self) -> Option<&mut T> { - unimplemented!() + todo!() } /// Move one position forward (towards the back) and /// return a reference to the new position #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Option<&mut T> { - unimplemented!() + todo!() } /// Move one position backward (towards the front) and /// return a reference to the new position pub fn prev(&mut self) -> Option<&mut T> { - unimplemented!() + todo!() } /// Remove and return the element at the current position and move the cursor /// to the neighboring element that's closest to the back. This can be /// either the next or previous position. pub fn take(&mut self) -> Option { - unimplemented!() + todo!() } pub fn insert_after(&mut self, _element: T) { - unimplemented!() + todo!() } pub fn insert_before(&mut self, _element: T) { - unimplemented!() + todo!() } } @@ -84,6 +84,6 @@ impl<'a, T> Iterator for Iter<'a, T> { type Item = &'a T; fn next(&mut self) -> Option<&'a T> { - unimplemented!() + todo!() } } diff --git a/exercises/practice/doubly-linked-list/src/pre_implemented.rs b/exercises/practice/doubly-linked-list/src/pre_implemented.rs index 8e2d09070..03d145587 100644 --- a/exercises/practice/doubly-linked-list/src/pre_implemented.rs +++ b/exercises/practice/doubly-linked-list/src/pre_implemented.rs @@ -1,4 +1,4 @@ -//! Everything in thes file is implemented in terms of required functionality. +//! Everything in this file is implemented in terms of required functionality. //! You are free to use anything, if it suits you. //! They are useful for the test framework, but the implementation is trivial. //! We supply them to reduce work both for you and the mentors. diff --git a/exercises/practice/doubly-linked-list/tests/doubly-linked-list.rs b/exercises/practice/doubly-linked-list/tests/doubly_linked_list.rs similarity index 99% rename from exercises/practice/doubly-linked-list/tests/doubly-linked-list.rs rename to exercises/practice/doubly-linked-list/tests/doubly_linked_list.rs index 101ef6c21..c3985d23c 100644 --- a/exercises/practice/doubly-linked-list/tests/doubly-linked-list.rs +++ b/exercises/practice/doubly-linked-list/tests/doubly_linked_list.rs @@ -259,7 +259,7 @@ fn drop_no_double_frees() { use std::cell::Cell; struct DropCounter<'a>(&'a Cell); - impl<'a> Drop for DropCounter<'a> { + impl Drop for DropCounter<'_> { fn drop(&mut self) { let num = self.0.get(); self.0.set(num + 1); diff --git a/exercises/practice/doubly-linked-list/tests/step_4_leak_test_1.rs b/exercises/practice/doubly-linked-list/tests/step_4_leak_test_1.rs index 34df209a8..ea84c886a 100644 --- a/exercises/practice/doubly-linked-list/tests/step_4_leak_test_1.rs +++ b/exercises/practice/doubly-linked-list/tests/step_4_leak_test_1.rs @@ -34,7 +34,7 @@ struct Counter; unsafe impl GlobalAlloc for Counter { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - let ret = System.alloc(layout); + let ret = unsafe { System.alloc(layout) }; if !ret.is_null() { ALLOCATED.fetch_add(layout.size(), SeqCst); } @@ -42,7 +42,7 @@ unsafe impl GlobalAlloc for Counter { } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - System.dealloc(ptr, layout); + unsafe { System.dealloc(ptr, layout) }; ALLOCATED.fetch_sub(layout.size(), SeqCst); } } diff --git a/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs b/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs index c55b6b64c..46687d8b7 100644 --- a/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs +++ b/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs @@ -19,8 +19,7 @@ fn drop_no_leaks() { drop(list); let allocated_after = ALLOCATED.load(SeqCst); - let leaked_bytes = allocated_before - allocated_after; - assert!(leaked_bytes == 0); + assert_eq!(allocated_before, allocated_after); } // Defines a wrapper around the global allocator that counts allocations @@ -34,7 +33,7 @@ struct Counter; unsafe impl GlobalAlloc for Counter { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - let ret = System.alloc(layout); + let ret = unsafe { System.alloc(layout) }; if !ret.is_null() { ALLOCATED.fetch_add(layout.size(), SeqCst); } @@ -42,7 +41,7 @@ unsafe impl GlobalAlloc for Counter { } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - System.dealloc(ptr, layout); + unsafe { System.dealloc(ptr, layout) }; ALLOCATED.fetch_sub(layout.size(), SeqCst); } } diff --git a/exercises/practice/eliuds-eggs/.docs/instructions.md b/exercises/practice/eliuds-eggs/.docs/instructions.md new file mode 100644 index 000000000..b0c2df593 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.docs/instructions.md @@ -0,0 +1,8 @@ +# Instructions + +Your task is to count the number of 1 bits in the binary representation of a number. + +## Restrictions + +Keep your hands off that bit-count functionality provided by your standard library! +Solve this one yourself using other basic tools instead. diff --git a/exercises/practice/eliuds-eggs/.docs/introduction.md b/exercises/practice/eliuds-eggs/.docs/introduction.md new file mode 100644 index 000000000..2b2e5c43d --- /dev/null +++ b/exercises/practice/eliuds-eggs/.docs/introduction.md @@ -0,0 +1,65 @@ +# Introduction + +Your friend Eliud inherited a farm from her grandma Tigist. +Her granny was an inventor and had a tendency to build things in an overly complicated manner. +The chicken coop has a digital display showing an encoded number representing the positions of all eggs that could be picked up. + +Eliud is asking you to write a program that shows the actual number of eggs in the coop. + +The position information encoding is calculated as follows: + +1. Scan the potential egg-laying spots and mark down a `1` for an existing egg or a `0` for an empty spot. +2. Convert the number from binary to decimal. +3. Show the result on the display. + +## Example 1 + +![Seven individual nest boxes arranged in a row whose first, third, fourth and seventh nests each have a single egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-coop.svg) + +```text + _ _ _ _ _ _ _ +|E| |E|E| | |E| +``` + +### Resulting Binary + +![1011001](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-binary.svg) + +```text + _ _ _ _ _ _ _ +|1|0|1|1|0|0|1| +``` + +### Decimal number on the display + +89 + +### Actual eggs in the coop + +4 + +## Example 2 + +![Seven individual nest boxes arranged in a row where only the fourth nest has an egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-coop.svg) + +```text + _ _ _ _ _ _ _ +| | | |E| | | | +``` + +### Resulting Binary + +![0001000](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-binary.svg) + +```text + _ _ _ _ _ _ _ +|0|0|0|1|0|0|0| +``` + +### Decimal number on the display + +8 + +### Actual eggs in the coop + +1 diff --git a/exercises/practice/eliuds-eggs/.gitignore b/exercises/practice/eliuds-eggs/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/eliuds-eggs/.meta/config.json b/exercises/practice/eliuds-eggs/.meta/config.json new file mode 100644 index 000000000..dd2fdf2b5 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": ["senekor"], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/eliuds_eggs.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Help Eliud count the number of eggs in her chicken coop by counting the number of 1 bits in a binary representation.", + "source": "Christian Willner, Eric Willigers", + "source_url": "https://forum.exercism.org/t/new-exercise-suggestion-pop-count/7632/5" +} diff --git a/exercises/practice/eliuds-eggs/.meta/example.rs b/exercises/practice/eliuds-eggs/.meta/example.rs new file mode 100644 index 000000000..c4ff172a9 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/example.rs @@ -0,0 +1,3 @@ +pub fn egg_count(display_value: u32) -> usize { + (0..32).filter(|i| display_value & (1 << i) != 0).count() +} diff --git a/exercises/practice/eliuds-eggs/.meta/test_template.tera b/exercises/practice/eliuds-eggs/.meta/test_template.tera new file mode 100644 index 000000000..1687e0108 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/test_template.tera @@ -0,0 +1,12 @@ +use eliuds_eggs::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.number | fmt_num }}; + let output = egg_count(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/eliuds-eggs/.meta/tests.toml b/exercises/practice/eliuds-eggs/.meta/tests.toml new file mode 100644 index 000000000..e11683c2e --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/tests.toml @@ -0,0 +1,22 @@ +# 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. + +[559e789d-07d1-4422-9004-3b699f83bca3] +description = "0 eggs" + +[97223282-f71e-490c-92f0-b3ec9e275aba] +description = "1 egg" + +[1f8fd18f-26e9-4144-9a0e-57cdfc4f4ff5] +description = "4 eggs" + +[0c18be92-a498-4ef2-bcbb-28ac4b06cb81] +description = "13 eggs" diff --git a/exercises/practice/eliuds-eggs/Cargo.toml b/exercises/practice/eliuds-eggs/Cargo.toml new file mode 100644 index 000000000..ae51b9c42 --- /dev/null +++ b/exercises/practice/eliuds-eggs/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "eliuds_eggs" +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/eliuds-eggs/src/lib.rs b/exercises/practice/eliuds-eggs/src/lib.rs new file mode 100644 index 000000000..0565f7348 --- /dev/null +++ b/exercises/practice/eliuds-eggs/src/lib.rs @@ -0,0 +1,3 @@ +pub fn egg_count(display_value: u32) -> usize { + todo!("count the eggs in {display_value}") +} diff --git a/exercises/practice/eliuds-eggs/tests/eliuds_eggs.rs b/exercises/practice/eliuds-eggs/tests/eliuds_eggs.rs new file mode 100644 index 000000000..d34e79d1f --- /dev/null +++ b/exercises/practice/eliuds-eggs/tests/eliuds_eggs.rs @@ -0,0 +1,36 @@ +use eliuds_eggs::*; + +#[test] +fn test_0_eggs() { + let input = 0; + let output = egg_count(input); + let expected = 0; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_1_egg() { + let input = 16; + let output = egg_count(input); + let expected = 1; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_4_eggs() { + let input = 89; + let output = egg_count(input); + let expected = 4; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_13_eggs() { + let input = 2_000_000_000; + let output = egg_count(input); + let expected = 13; + assert_eq!(output, expected); +} diff --git a/exercises/practice/etl/.docs/instructions.md b/exercises/practice/etl/.docs/instructions.md index ff96906c6..802863b54 100644 --- a/exercises/practice/etl/.docs/instructions.md +++ b/exercises/practice/etl/.docs/instructions.md @@ -1,21 +1,8 @@ # Instructions -We are going to do the `Transform` step of an Extract-Transform-Load. +Your task is to change the data format of letters and their point values in the game. -## ETL - -Extract-Transform-Load (ETL) is a fancy way of saying, "We have some crufty, legacy data over in this system, and now we need it in this shiny new system over here, so -we're going to migrate this." - -(Typically, this is followed by, "We're only going to need to run this -once." That's then typically followed by much forehead slapping and -moaning about how stupid we could possibly be.) - -## The goal - -We're going to extract some Scrabble scores from a legacy system. - -The old system stored a list of letters per score: +Currently, letters are stored in groups based on their score, in a one-to-many mapping. - 1 point: "A", "E", "I", "O", "U", "L", "N", "R", "S", "T", - 2 points: "D", "G", @@ -25,23 +12,16 @@ The old system stored a list of letters per score: - 8 points: "J", "X", - 10 points: "Q", "Z", -The shiny new Scrabble system instead stores the score per letter, which -makes it much faster and easier to calculate the score for a word. It -also stores the letters in lower-case regardless of the case of the -input letters: +This needs to be changed to store each individual letter with its score in a one-to-one mapping. - "a" is worth 1 point. - "b" is worth 3 points. - "c" is worth 3 points. - "d" is worth 2 points. -- Etc. - -Your mission, should you choose to accept it, is to transform the legacy data -format to the shiny new format. +- etc. -## Notes +As part of this change, the team has also decided to change the letters to be lower-case rather than upper-case. -A final note about scoring, Scrabble is played around the world in a -variety of languages, each with its own unique scoring table. For -example, an "E" is scored at 2 in the Māori-language version of the -game while being scored at 4 in the Hawaiian-language version. +~~~~exercism/note +If you want to look at how the data was previously structured and how it needs to change, take a look at the examples in the test suite. +~~~~ diff --git a/exercises/practice/etl/.docs/introduction.md b/exercises/practice/etl/.docs/introduction.md new file mode 100644 index 000000000..5be65147d --- /dev/null +++ b/exercises/practice/etl/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a company that makes an online multiplayer game called Lexiconia. + +To play the game, each player is given 13 letters, which they must rearrange to create words. +Different letters have different point values, since it's easier to create words with some letters than others. + +The game was originally launched in English, but it is very popular, and now the company wants to expand to other languages as well. + +Different languages need to support different point values for letters. +The point values are determined by how often letters are used, compared to other letters in that language. + +For example, the letter 'C' is quite common in English, and is only worth 3 points. +But in Norwegian it's a very rare letter, and is worth 10 points. + +To make it easier to add new languages, your team needs to change the way letters and their point values are stored in the game. diff --git a/exercises/practice/etl/.gitignore b/exercises/practice/etl/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/etl/.gitignore +++ b/exercises/practice/etl/.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/etl/.meta/config.json b/exercises/practice/etl/.meta/config.json index af4b8d315..c5cb84379 100644 --- a/exercises/practice/etl/.meta/config.json +++ b/exercises/practice/etl/.meta/config.json @@ -36,7 +36,7 @@ ".meta/example.rs" ] }, - "blurb": "We are going to do the `Transform` step of an Extract-Transform-Load.", - "source": "The Jumpstart Lab team", - "source_url": "http://jumpstartlab.com/" + "blurb": "Change the data format for scoring a game to more easily add other languages.", + "source": "Based on an 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/etl/.meta/test_template.tera b/exercises/practice/etl/.meta/test_template.tera new file mode 100644 index 000000000..40e64e518 --- /dev/null +++ b/exercises/practice/etl/.meta/test_template.tera @@ -0,0 +1,29 @@ +use std::collections::BTreeMap; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = BTreeMap::from( + [ + {% for value, letters in test.input.legacy %} + ({{value}}, vec![ + {% for letter in letters %} + '{{letter}}', + {% endfor %} + ]), + {% endfor %} + ] + ); + + let expected = BTreeMap::from( + [ + {% for letter, value in test.expected %} + ('{{letter}}', {{ value }}), + {% endfor %} + ] + ); + + assert_eq!(expected, etl::transform(&input)); +} +{% endfor %} diff --git a/exercises/practice/etl/.meta/tests.toml b/exercises/practice/etl/.meta/tests.toml index be690e975..e9371078c 100644 --- a/exercises/practice/etl/.meta/tests.toml +++ b/exercises/practice/etl/.meta/tests.toml @@ -1,3 +1,22 @@ -# 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. + +[78a7a9f9-4490-4a47-8ee9-5a38bb47d28f] +description = "single letter" + +[60dbd000-451d-44c7-bdbb-97c73ac1f497] +description = "single score with multiple letters" + +[f5c5de0c-301f-4fdd-a0e5-df97d4214f54] +description = "multiple scores with multiple letters" + +[5db8ea89-ecb4-4dcd-902f-2b418cc87b9d] +description = "multiple scores with differing numbers of letters" diff --git a/exercises/practice/etl/Cargo.toml b/exercises/practice/etl/Cargo.toml index 67624abea..a63ea7951 100644 --- a/exercises/practice/etl/Cargo.toml +++ b/exercises/practice/etl/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "etl" -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/etl/src/lib.rs b/exercises/practice/etl/src/lib.rs index 3b451f1c1..25e941f27 100644 --- a/exercises/practice/etl/src/lib.rs +++ b/exercises/practice/etl/src/lib.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; pub fn transform(h: &BTreeMap>) -> BTreeMap { - unimplemented!("How will you transform the tree {h:?}?") + todo!("How will you transform the tree {h:?}?") } diff --git a/exercises/practice/etl/tests/etl.rs b/exercises/practice/etl/tests/etl.rs index 37241247e..7e973d5a9 100644 --- a/exercises/practice/etl/tests/etl.rs +++ b/exercises/practice/etl/tests/etl.rs @@ -1,38 +1,38 @@ use std::collections::BTreeMap; #[test] -fn test_transform_one_value() { - let input = input_from(&[(1, vec!['A'])]); +fn single_letter() { + let input = BTreeMap::from([(1, vec!['A'])]); - let expected = expected_from(&[('a', 1)]); + let expected = BTreeMap::from([('a', 1)]); assert_eq!(expected, etl::transform(&input)); } #[test] #[ignore] -fn test_transform_more_values() { - let input = input_from(&[(1, vec!['A', 'E', 'I', 'O', 'U'])]); +fn single_score_with_multiple_letters() { + let input = BTreeMap::from([(1, vec!['A', 'E', 'I', 'O', 'U'])]); - let expected = expected_from(&[('a', 1), ('e', 1), ('i', 1), ('o', 1), ('u', 1)]); + let expected = BTreeMap::from([('a', 1), ('e', 1), ('i', 1), ('o', 1), ('u', 1)]); assert_eq!(expected, etl::transform(&input)); } #[test] #[ignore] -fn test_more_keys() { - let input = input_from(&[(1, vec!['A', 'E']), (2, vec!['D', 'G'])]); +fn multiple_scores_with_multiple_letters() { + let input = BTreeMap::from([(1, vec!['A', 'E']), (2, vec!['D', 'G'])]); - let expected = expected_from(&[('a', 1), ('e', 1), ('d', 2), ('g', 2)]); + let expected = BTreeMap::from([('a', 1), ('d', 2), ('e', 1), ('g', 2)]); assert_eq!(expected, etl::transform(&input)); } #[test] #[ignore] -fn test_full_dataset() { - let input = input_from(&[ +fn multiple_scores_with_differing_numbers_of_letters() { + let input = BTreeMap::from([ (1, vec!['A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T']), (2, vec!['D', 'G']), (3, vec!['B', 'C', 'M', 'P']), @@ -42,7 +42,7 @@ fn test_full_dataset() { (10, vec!['Q', 'Z']), ]); - let expected = expected_from(&[ + let expected = BTreeMap::from([ ('a', 1), ('b', 3), ('c', 3), @@ -73,11 +73,3 @@ fn test_full_dataset() { assert_eq!(expected, etl::transform(&input)); } - -fn input_from(v: &[(i32, Vec)]) -> BTreeMap> { - v.iter().cloned().collect() -} - -fn expected_from(v: &[(char, i32)]) -> BTreeMap { - v.iter().cloned().collect() -} diff --git a/exercises/practice/fizzy/.gitignore b/exercises/practice/fizzy/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/fizzy/.gitignore +++ b/exercises/practice/fizzy/.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/fizzy/.meta/example.rs b/exercises/practice/fizzy/.meta/example.rs index 0d11f9411..3543dcb12 100644 --- a/exercises/practice/fizzy/.meta/example.rs +++ b/exercises/practice/fizzy/.meta/example.rs @@ -39,7 +39,7 @@ where } pub fn apply_to(&self, item: T) -> String { - let Fizzy(ref matchers) = self; + let Fizzy(matchers) = self; let mut out = String::new(); for matcher in matchers { if (matcher.matcher)(item) { @@ -85,7 +85,7 @@ mod test { use super::*; #[test] - fn test_fizz_buzz() { + fn fizz_buzz_i32() { let expect = vec![ "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", "fizzbuzz", "16", @@ -95,7 +95,7 @@ mod test { } #[test] - fn test_fizz_buzz_u8() { + fn fizz_buzz_u8() { let expect = vec![ "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", "fizzbuzz", "16", @@ -105,7 +105,7 @@ mod test { } #[test] - fn test_fizz_buzz_u64() { + fn fizz_buzz_u64() { let expect = vec![ "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", "fizzbuzz", "16", @@ -115,7 +115,7 @@ mod test { } #[test] - fn test_fizz_buzz_nonsequential() { + fn fizz_buzz_nonsequential() { let collatz_12 = &[12, 6, 3, 10, 5, 16, 8, 4, 2, 1]; let expect = vec![ "fizz", "fizz", "fizz", "buzz", "buzz", "16", "8", "4", "2", "1", @@ -127,7 +127,7 @@ mod test { } #[test] - fn test_fizz_buzz_custom() { + fn fizz_buzz_custom() { let expect = vec![ "1", "2", "Fizz", "4", "Buzz", "Fizz", "Bam", "8", "Fizz", "Buzz", "11", "Fizz", "13", "Bam", "BuzzFizz", "16", @@ -142,7 +142,7 @@ mod test { } #[test] - fn test_map() { + fn map() { let expect = vec![ "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", "fizzbuzz", "16", @@ -155,7 +155,7 @@ mod test { } #[test] - fn test_fizz_buzz_f64() { + fn fizz_buzz_f64() { let expect = vec![ "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", "fizzbuzz", "16", diff --git a/exercises/practice/fizzy/Cargo.toml b/exercises/practice/fizzy/Cargo.toml index 005dcd594..0a1c3df13 100644 --- a/exercises/practice/fizzy/Cargo.toml +++ b/exercises/practice/fizzy/Cargo.toml @@ -1,6 +1,12 @@ [package] name = "fizzy" -version = "0.0.0" -edition = "2021" +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/fizzy/src/lib.rs b/exercises/practice/fizzy/src/lib.rs index 89d0e802b..03b50ae73 100644 --- a/exercises/practice/fizzy/src/lib.rs +++ b/exercises/practice/fizzy/src/lib.rs @@ -7,7 +7,7 @@ pub struct Matcher(std::marker::PhantomData); impl Matcher { pub fn new(_matcher: F, _subs: S) -> Matcher { - unimplemented!() + todo!() } } @@ -24,18 +24,18 @@ pub struct Fizzy(std::marker::PhantomData); impl Fizzy { pub fn new() -> Self { - unimplemented!() + todo!() } // feel free to change the signature to `mut self` if you like #[must_use] pub fn add_matcher(self, _matcher: Matcher) -> Self { - unimplemented!() + todo!() } /// map this fizzy onto every element of an iterator, returning a new iterator pub fn apply(self, _iter: I) -> impl Iterator { - // unimplemented!() doesn't actually work, here; () is not an Iterator + // todo!() doesn't actually work, here; () is not an Iterator // that said, this is probably not the actual implementation you desire Vec::new().into_iter() } @@ -43,5 +43,5 @@ impl Fizzy { /// convenience function: return a Fizzy which applies the standard fizz-buzz rules pub fn fizz_buzz() -> Fizzy { - unimplemented!() + todo!() } diff --git a/exercises/practice/fizzy/tests/fizzy.rs b/exercises/practice/fizzy/tests/fizzy.rs index 85a7f8adb..af84d6f91 100644 --- a/exercises/practice/fizzy/tests/fizzy.rs +++ b/exercises/practice/fizzy/tests/fizzy.rs @@ -1,51 +1,54 @@ use fizzy::*; -macro_rules! expect { - () => { - vec![ - "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", - "14", "fizzbuzz", "16", - ] - }; -} - #[test] -fn test_simple() { - let got = fizz_buzz::().apply(1..=16).collect::>(); - assert_eq!(expect!(), got); +fn simple() { + let actual = fizz_buzz::().apply(1..=16).collect::>(); + let expected = [ + "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", + "fizzbuzz", "16", + ]; + assert_eq!(actual, expected); } #[test] #[ignore] -fn test_u8() { - let got = fizz_buzz::().apply(1_u8..=16).collect::>(); - assert_eq!(expect!(), got); +fn u8() { + let actual = fizz_buzz::().apply(1_u8..=16).collect::>(); + let expected = [ + "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", + "fizzbuzz", "16", + ]; + assert_eq!(actual, expected); } #[test] #[ignore] -fn test_u64() { - let got = fizz_buzz::().apply(1_u64..=16).collect::>(); - assert_eq!(expect!(), got); +fn u64() { + let actual = fizz_buzz::().apply(1_u64..=16).collect::>(); + let expected = [ + "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", + "fizzbuzz", "16", + ]; + assert_eq!(actual, expected); } #[test] #[ignore] -fn test_nonsequential() { +fn nonsequential() { let collatz_12 = &[12, 6, 3, 10, 5, 16, 8, 4, 2, 1]; - let expect = vec![ - "fizz", "fizz", "fizz", "buzz", "buzz", "16", "8", "4", "2", "1", - ]; - let got = fizz_buzz::() + let actual = fizz_buzz::() .apply(collatz_12.iter().cloned()) .collect::>(); - assert_eq!(expect, got); + let expected = vec![ + "fizz", "fizz", "fizz", "buzz", "buzz", "16", "8", "4", "2", "1", + ]; + assert_eq!(actual, expected); } #[test] #[ignore] -fn test_custom() { - let expect = vec![ +fn custom() { + let expected = vec![ "1", "2", "Fizz", "4", "Buzz", "Fizz", "Bam", "8", "Fizz", "Buzz", "11", "Fizz", "13", "Bam", "BuzzFizz", "16", ]; @@ -53,30 +56,28 @@ fn test_custom() { .add_matcher(Matcher::new(|n: i32| n % 5 == 0, "Buzz")) .add_matcher(Matcher::new(|n: i32| n % 3 == 0, "Fizz")) .add_matcher(Matcher::new(|n: i32| n % 7 == 0, "Bam")); - let got = fizzer.apply(1..=16).collect::>(); - assert_eq!(expect, got); + let actual = fizzer.apply(1..=16).collect::>(); + assert_eq!(actual, expected); } #[test] #[ignore] -fn test_f64() { +fn f64() { // a tiny bit more complicated becuase range isn't natively implemented on floats - // NOTE: this test depends on a language feature introduced in Rust 1.34. If you - // have an older compiler, upgrade. If you have an older compiler and cannot upgrade, - // feel free to ignore this test. - let got = fizz_buzz::() + let actual = fizz_buzz::() .apply(std::iter::successors(Some(1.0), |prev| Some(prev + 1.0))) .take(16) .collect::>(); - assert_eq!(expect!(), got); + let expected = [ + "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", + "fizzbuzz", "16", + ]; + assert_eq!(actual, expected); } #[test] #[ignore] -fn test_minimal_generic_bounds() { - // NOTE: this test depends on a language feature introduced in Rust 1.34. If you - // have an older compiler, upgrade. If you have an older compiler and cannot upgrade, - // feel free to ignore this test. +fn minimal_generic_bounds() { use std::fmt; use std::ops::{Add, Rem}; @@ -91,7 +92,7 @@ fn test_minimal_generic_bounds() { impl fmt::Display for Fizzable { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let Fizzable(ref n) = self; + let Fizzable(n) = self; write!(f, "{n}") } } @@ -114,11 +115,15 @@ fn test_minimal_generic_bounds() { } } - let got = fizz_buzz::() + let actual = fizz_buzz::() .apply(std::iter::successors(Some(Fizzable(1)), |prev| { Some(*prev + 1.into()) })) .take(16) .collect::>(); - assert_eq!(expect!(), got); + let expected = [ + "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", + "fizzbuzz", "16", + ]; + assert_eq!(actual, expected); } diff --git a/exercises/practice/flower-field/.docs/instructions.append.md b/exercises/practice/flower-field/.docs/instructions.append.md new file mode 100644 index 000000000..51d0953a4 --- /dev/null +++ b/exercises/practice/flower-field/.docs/instructions.append.md @@ -0,0 +1,10 @@ +# Instructions append + +## Performance Hint + +All the inputs and outputs are in ASCII. +Rust `String`s and `&str` are utf8, so while one might expect `"Hello".chars()` to be simple, it actually has to check each char to see if it's 1, 2, 3 or 4 `u8`s long. +If we know a `&str` is ASCII then we can call `.as_bytes()` and refer to the underlying data as a `&[u8]` (byte slice). +Iterating over a slice of ASCII bytes is much quicker as there are no codepoints involved - every ASCII byte is one `u8` long. + +Can you complete the challenge without cloning the input? diff --git a/exercises/practice/flower-field/.docs/instructions.md b/exercises/practice/flower-field/.docs/instructions.md new file mode 100644 index 000000000..bbdae0c2c --- /dev/null +++ b/exercises/practice/flower-field/.docs/instructions.md @@ -0,0 +1,26 @@ +# Instructions + +Your task is to add flower counts to empty squares in a completed Flower Field garden. +The garden itself is a rectangle board composed of squares that are either empty (`' '`) or a flower (`'*'`). + +For each empty square, count the number of flowers adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent flowers, leave it empty. +Otherwise replace it with the count of adjacent flowers. + +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): + +```text +·*·*· +··*·· +··*·· +····· +``` + +Which your code should transform into this: + +```text +1*3*1 +13*31 +·2*2· +·111· +``` diff --git a/exercises/practice/flower-field/.docs/introduction.md b/exercises/practice/flower-field/.docs/introduction.md new file mode 100644 index 000000000..af9b61536 --- /dev/null +++ b/exercises/practice/flower-field/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Flower Field][history] is a compassionate reimagining of the popular game Minesweeper. +The object of the game is to find all the flowers in the garden using numeric hints that indicate how many flowers are directly adjacent (horizontally, vertically, diagonally) to a square. +"Flower Field" shipped in regional versions of Microsoft Windows in Italy, Germany, South Korea, Japan and Taiwan. + +[history]: https://web.archive.org/web/20020409051321fw_/http://rcm.usr.dsi.unimi.it/rcmweb/fnm/ diff --git a/exercises/practice/flower-field/.gitignore b/exercises/practice/flower-field/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/flower-field/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/flower-field/.meta/config.json b/exercises/practice/flower-field/.meta/config.json new file mode 100644 index 000000000..aa8cb9c60 --- /dev/null +++ b/exercises/practice/flower-field/.meta/config.json @@ -0,0 +1,39 @@ +{ + "authors": [ + "EduardoBautista" + ], + "contributors": [ + "ashleygwilliams", + "coriolinus", + "cwhakes", + "EduardoBautista", + "efx", + "ErikSchierboom", + "ffflorian", + "IanWhitney", + "keiravillekode", + "kytrinyx", + "lutostag", + "mkantor", + "nfiles", + "petertseng", + "rofrol", + "stringparser", + "workingjubilee", + "xakon", + "ZapAnton" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/flower_field.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Mark all the flowers in a garden." +} diff --git a/exercises/practice/flower-field/.meta/example.rs b/exercises/practice/flower-field/.meta/example.rs new file mode 100644 index 000000000..830ecf5f9 --- /dev/null +++ b/exercises/practice/flower-field/.meta/example.rs @@ -0,0 +1,66 @@ +struct Board { + pieces: Vec>, + num_rows: usize, + num_cols: usize, +} + +impl Board { + fn annotated(&self) -> Vec { + (0..self.num_rows).map(|y| self.annotated_row(y)).collect() + } + + fn annotated_row(&self, y: usize) -> String { + self.pieces[y] + .iter() + .enumerate() + .map(|(x, &c)| { + if c == ' ' { + self.count_neighbouring_flowers_char(x, y) + } else { + c + } + }) + .collect::() + } + + fn count_neighbouring_flowers_char(&self, x: usize, y: usize) -> char { + let mut count = 0; + for x1 in neighbouring_points(x, self.num_cols) { + for y1 in neighbouring_points(y, self.num_rows) { + let piece = self.pieces[y1][x1]; + if piece == '*' { + count += 1; + } + } + } + if count == 0 { + ' ' + } else { + (b'0' + count) as char + } + } +} + +pub fn annotate(pieces: &[&str]) -> Vec { + if pieces.is_empty() { + return Vec::new(); + } + let pieces_vec = pieces.iter().map(|&r| r.chars().collect()).collect(); + Board { + pieces: pieces_vec, + num_rows: pieces.len(), + num_cols: pieces[0].len(), + } + .annotated() +} + +fn neighbouring_points(x: usize, limit: usize) -> Vec { + let mut offsets = vec![x]; + if x >= 1 { + offsets.push(x - 1); + } + if x + 2 <= limit { + offsets.push(x + 1); + } + offsets +} diff --git a/exercises/practice/flower-field/.meta/test_template.tera b/exercises/practice/flower-field/.meta/test_template.tera new file mode 100644 index 000000000..3d6298899 --- /dev/null +++ b/exercises/practice/flower-field/.meta/test_template.tera @@ -0,0 +1,33 @@ +use flower_field::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% if test.input.garden | length < 2 -%} + let input = &[ + {%- for line in test.input.garden %} + {{ line | json_encode() }}, + {%- endfor %} + ]; + let expected{% if test.expected | length == 0 %}: &[&str]{% endif %} = &[ + {%- for line in test.expected %} + {{ line | json_encode() }}, + {%- endfor %} + ]; + {% else -%} + #[rustfmt::skip] + let (input, expected) = (&[ + {%- for line in test.input.garden %} + {{ line | json_encode() }}, + {%- endfor %} + ], &[ + {%- for line in test.expected %} + {{ line | json_encode() }}, + {%- endfor %} + ]); + {% endif -%} + let actual = annotate(input); + assert_eq!(actual, expected); +} +{% endfor -%} diff --git a/exercises/practice/flower-field/.meta/tests.toml b/exercises/practice/flower-field/.meta/tests.toml new file mode 100644 index 000000000..c2b24fdaf --- /dev/null +++ b/exercises/practice/flower-field/.meta/tests.toml @@ -0,0 +1,46 @@ +# 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. + +[237ff487-467a-47e1-9b01-8a891844f86c] +description = "no rows" + +[4b4134ec-e20f-439c-a295-664c38950ba1] +description = "no columns" + +[d774d054-bbad-4867-88ae-069cbd1c4f92] +description = "no flowers" + +[225176a0-725e-43cd-aa13-9dced501f16e] +description = "garden full of flowers" + +[3f345495-f1a5-4132-8411-74bd7ca08c49] +description = "flower surrounded by spaces" + +[6cb04070-4199-4ef7-a6fa-92f68c660fca] +description = "space surrounded by flowers" + +[272d2306-9f62-44fe-8ab5-6b0f43a26338] +description = "horizontal line" + +[c6f0a4b2-58d0-4bf6-ad8d-ccf4144f1f8e] +description = "horizontal line, flowers at edges" + +[a54e84b7-3b25-44a8-b8cf-1753c8bb4cf5] +description = "vertical line" + +[b40f42f5-dec5-4abc-b167-3f08195189c1] +description = "vertical line, flowers at edges" + +[58674965-7b42-4818-b930-0215062d543c] +description = "cross" + +[dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8] +description = "large garden" diff --git a/exercises/practice/flower-field/Cargo.toml b/exercises/practice/flower-field/Cargo.toml new file mode 100644 index 000000000..fc11c06f1 --- /dev/null +++ b/exercises/practice/flower-field/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "flower_field" +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/flower-field/src/lib.rs b/exercises/practice/flower-field/src/lib.rs new file mode 100644 index 000000000..965fb2b65 --- /dev/null +++ b/exercises/practice/flower-field/src/lib.rs @@ -0,0 +1,5 @@ +pub fn annotate(garden: &[&str]) -> Vec { + todo!( + "\nAnnotate each square of the given garden with the number of flowers that surround said square (blank if there are no surrounding flowers):\n{garden:#?}\n" + ); +} diff --git a/exercises/practice/flower-field/tests/flower_field.rs b/exercises/practice/flower-field/tests/flower_field.rs new file mode 100644 index 000000000..05cd8e66a --- /dev/null +++ b/exercises/practice/flower-field/tests/flower_field.rs @@ -0,0 +1,190 @@ +use flower_field::*; + +#[test] +fn no_rows() { + let input = &[]; + let expected: &[&str] = &[]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn no_columns() { + let input = &[""]; + let expected = &[""]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn no_flowers() { + #[rustfmt::skip] + let (input, expected) = (&[ + " ", + " ", + " ", + ], &[ + " ", + " ", + " ", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn garden_full_of_flowers() { + #[rustfmt::skip] + let (input, expected) = (&[ + "***", + "***", + "***", + ], &[ + "***", + "***", + "***", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn flower_surrounded_by_spaces() { + #[rustfmt::skip] + let (input, expected) = (&[ + " ", + " * ", + " ", + ], &[ + "111", + "1*1", + "111", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn space_surrounded_by_flowers() { + #[rustfmt::skip] + let (input, expected) = (&[ + "***", + "* *", + "***", + ], &[ + "***", + "*8*", + "***", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn horizontal_line() { + let input = &[" * * "]; + let expected = &["1*2*1"]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn horizontal_line_flowers_at_edges() { + let input = &["* *"]; + let expected = &["*1 1*"]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn vertical_line() { + #[rustfmt::skip] + let (input, expected) = (&[ + " ", + "*", + " ", + "*", + " ", + ], &[ + "1", + "*", + "2", + "*", + "1", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn vertical_line_flowers_at_edges() { + #[rustfmt::skip] + let (input, expected) = (&[ + "*", + " ", + " ", + " ", + "*", + ], &[ + "*", + "1", + " ", + "1", + "*", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn cross() { + #[rustfmt::skip] + let (input, expected) = (&[ + " * ", + " * ", + "*****", + " * ", + " * ", + ], &[ + " 2*2 ", + "25*52", + "*****", + "25*52", + " 2*2 ", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn large_garden() { + #[rustfmt::skip] + let (input, expected) = (&[ + " * * ", + " * ", + " * ", + " * *", + " * * ", + " ", + ], &[ + "1*22*1", + "12*322", + " 123*2", + "112*4*", + "1*22*2", + "111111", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} diff --git a/exercises/practice/forth/.docs/instructions.append.md b/exercises/practice/forth/.docs/instructions.append.md new file mode 100644 index 000000000..947eed8e1 --- /dev/null +++ b/exercises/practice/forth/.docs/instructions.append.md @@ -0,0 +1,5 @@ +# Instructions append + +Note the additional test case in `tests/alloc-attack.rs`. It tests against +algorithmically inefficient implementations. Because of that, it usually times +out online instead of outright failing, leading to a less helpful error message. diff --git a/exercises/practice/forth/.docs/instructions.md b/exercises/practice/forth/.docs/instructions.md index f481b725a..91ad26e6e 100644 --- a/exercises/practice/forth/.docs/instructions.md +++ b/exercises/practice/forth/.docs/instructions.md @@ -2,25 +2,22 @@ Implement an evaluator for a very simple subset of Forth. -[Forth](https://en.wikipedia.org/wiki/Forth_%28programming_language%29) -is a stack-based programming language. Implement a very basic evaluator -for a small subset of Forth. +[Forth][forth] +is a stack-based programming language. +Implement a very basic evaluator for a small subset of Forth. Your evaluator has to support the following words: - `+`, `-`, `*`, `/` (integer arithmetic) - `DUP`, `DROP`, `SWAP`, `OVER` (stack manipulation) -Your evaluator also has to support defining new words using the -customary syntax: `: word-name definition ;`. +Your evaluator also has to support defining new words using the customary syntax: `: word-name definition ;`. -To keep things simple the only data type you need to support is signed -integers of at least 16 bits size. +To keep things simple the only data type you need to support is signed integers of at least 16 bits size. -You should use the following rules for the syntax: a number is a -sequence of one or more (ASCII) digits, a word is a sequence of one or -more letters, digits, symbols or punctuation that is not a number. -(Forth probably uses slightly different rules, but this is close -enough.) +You should use the following rules for the syntax: a number is a sequence of one or more (ASCII) digits, a word is a sequence of one or more letters, digits, symbols or punctuation that is not a number. +(Forth probably uses slightly different rules, but this is close enough.) Words are case-insensitive. + +[forth]: https://en.wikipedia.org/wiki/Forth_%28programming_language%29 diff --git a/exercises/practice/forth/.gitignore b/exercises/practice/forth/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/forth/.gitignore +++ b/exercises/practice/forth/.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/forth/.meta/config.json b/exercises/practice/forth/.meta/config.json index 6abe60190..ec4175d59 100644 --- a/exercises/practice/forth/.meta/config.json +++ b/exercises/practice/forth/.meta/config.json @@ -34,7 +34,7 @@ ], "test": [ "tests/forth.rs", - "tests/alloc-attack.rs" + "tests/alloc_attack.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/forth/.meta/example.rs b/exercises/practice/forth/.meta/example.rs index 66ff0e69e..3eb990209 100644 --- a/exercises/practice/forth/.meta/example.rs +++ b/exercises/practice/forth/.meta/example.rs @@ -67,7 +67,6 @@ impl Forth { eval_op( parse_op(token, &self.words)?, &mut self.stack, - &self.words, &self.definitions, )?; } @@ -96,7 +95,7 @@ impl Forth { } (mode == Mode::Execution) - .then(|| ()) + .then_some(()) .ok_or(Error::InvalidWord) } } @@ -119,12 +118,7 @@ fn parse_op(token: &str, words: &HashMap) -> Result { }) } -fn eval_op( - op: Op, - stack: &mut Vec, - words: &HashMap, - definitions: &Vec>, -) -> ForthResult { +fn eval_op(op: Op, stack: &mut Vec, definitions: &Vec>) -> ForthResult { let mut pop = || stack.pop().ok_or(Error::StackUnderflow); match op { Op::Add => { @@ -165,7 +159,7 @@ fn eval_op( } Op::Call(fn_id) => { for op in &definitions[fn_id as usize] { - eval_op(*op, stack, words, definitions)?; + eval_op(*op, stack, definitions)?; } } } diff --git a/exercises/practice/forth/.meta/test_template.tera b/exercises/practice/forth/.meta/test_template.tera new file mode 100644 index 000000000..dc7152268 --- /dev/null +++ b/exercises/practice/forth/.meta/test_template.tera @@ -0,0 +1,44 @@ +{% for test_group in cases %} +mod {{ test_group.description | make_ident }} { + use forth::*; + +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let mut f = Forth::new(); + {% if test.property == "evaluateBoth" -%} + {% for instr in test.input.instructionsFirst -%} + assert!(f.eval("{{ instr }}").is_ok()); + {% endfor -%} + assert_eq!(f.stack(), {{ test.expected[0] | json_encode() }}); + let mut f = Forth::new(); + {% for instr in test.input.instructionsSecond -%} + assert!(f.eval("{{ instr }}").is_ok()); + {% endfor -%} + assert_eq!(f.stack(), {{ test.expected[1] | json_encode() }}); + } + {% continue %} + {% endif -%} + + {% if test.expected is object -%} + {% if test.expected.error == "empty stack" or test.expected.error == "only one value on the stack" -%} + assert_eq!(f.eval("{{ test.input.instructions[0] }}"), Err(Error::StackUnderflow)); + {% elif test.expected.error == "divide by zero" -%} + assert_eq!(f.eval("{{ test.input.instructions[0] }}"), Err(Error::DivisionByZero)); + {% elif test.expected.error == "illegal operation" -%} + assert_eq!(f.eval("{{ test.input.instructions[0] }}"), Err(Error::InvalidWord)); + {% elif test.expected.error == "undefined operation" -%} + assert_eq!(f.eval("{{ test.input.instructions[0] }}"), Err(Error::UnknownWord)); + {% endif -%} + {% else -%} + {% for instr in test.input.instructions -%} + assert!(f.eval("{{ instr }}").is_ok()); + {% endfor -%} + assert_eq!(f.stack(), {{ test.expected | json_encode() }}); + {% endif -%} +} +{% endfor %} + +} +{% endfor %} diff --git a/exercises/practice/forth/.meta/tests.toml b/exercises/practice/forth/.meta/tests.toml index ed1f74b2c..d1e146a1e 100644 --- a/exercises/practice/forth/.meta/tests.toml +++ b/exercises/practice/forth/.meta/tests.toml @@ -1,39 +1,175 @@ -# 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. [9962203f-f00a-4a85-b404-8a8ecbcec09d] -description = "numbers just get pushed onto the stack" +description = "parsing and numbers -> numbers just get pushed onto the stack" + +[fd7a8da2-6818-4203-a866-fed0714e7aa0] +description = "parsing and numbers -> pushes negative numbers onto the stack" [9e69588e-a3d8-41a3-a371-ea02206c1e6e] -description = "can add two numbers" +description = "addition -> can add two numbers" + +[52336dd3-30da-4e5c-8523-bdf9a3427657] +description = "addition -> errors if there is nothing on the stack" + +[06efb9a4-817a-435e-b509-06166993c1b8] +description = "addition -> errors if there is only one value on the stack" + +[1e07a098-c5fa-4c66-97b2-3c81205dbc2f] +description = "addition -> more than two values on the stack" [09687c99-7bbc-44af-8526-e402f997ccbf] -description = "can subtract two numbers" +description = "subtraction -> can subtract two numbers" + +[5d63eee2-1f7d-4538-b475-e27682ab8032] +description = "subtraction -> errors if there is nothing on the stack" + +[b3cee1b2-9159-418a-b00d-a1bb3765c23b] +description = "subtraction -> errors if there is only one value on the stack" + +[2c8cc5ed-da97-4cb1-8b98-fa7b526644f4] +description = "subtraction -> more than two values on the stack" [5df0ceb5-922e-401f-974d-8287427dbf21] -description = "can multiply two numbers" +description = "multiplication -> can multiply two numbers" + +[9e004339-15ac-4063-8ec1-5720f4e75046] +description = "multiplication -> errors if there is nothing on the stack" + +[8ba4b432-9f94-41e0-8fae-3b3712bd51b3] +description = "multiplication -> errors if there is only one value on the stack" + +[5cd085b5-deb1-43cc-9c17-6b1c38bc9970] +description = "multiplication -> more than two values on the stack" [e74c2204-b057-4cff-9aa9-31c7c97a93f5] -description = "can divide two numbers" +description = "division -> can divide two numbers" [54f6711c-4b14-4bb0-98ad-d974a22c4620] -description = "performs integer division" +description = "division -> performs integer division" [a5df3219-29b4-4d2f-b427-81f82f42a3f1] -description = "errors if dividing by zero" +description = "division -> errors if dividing by zero" + +[1d5bb6b3-6749-4e02-8a79-b5d4d334cb8a] +description = "division -> errors if there is nothing on the stack" + +[d5547f43-c2ff-4d5c-9cb0-2a4f6684c20d] +description = "division -> errors if there is only one value on the stack" + +[f224f3e0-b6b6-4864-81de-9769ecefa03f] +description = "division -> more than two values on the stack" [ee28d729-6692-4a30-b9be-0d830c52a68c] -description = "addition and subtraction" +description = "combined arithmetic -> addition and subtraction" [40b197da-fa4b-4aca-a50b-f000d19422c1] -description = "multiplication and division" +description = "combined arithmetic -> multiplication and division" + +[f749b540-53aa-458e-87ec-a70797eddbcb] +description = "combined arithmetic -> multiplication and addition" + +[c8e5a4c2-f9bf-4805-9a35-3c3314e4989a] +description = "combined arithmetic -> addition and multiplication" + +[c5758235-6eef-4bf6-ab62-c878e50b9957] +description = "dup -> copies a value on the stack" + +[f6889006-5a40-41e7-beb3-43b09e5a22f4] +description = "dup -> copies the top value on the stack" + +[40b7569c-8401-4bd4-a30d-9adf70d11bc4] +description = "dup -> errors if there is nothing on the stack" + +[1971da68-1df2-4569-927a-72bf5bb7263c] +description = "drop -> removes the top value on the stack if it is the only one" + +[8929d9f2-4a78-4e0f-90ad-be1a0f313fd9] +description = "drop -> removes the top value on the stack if it is not the only one" + +[6dd31873-6dd7-4cb8-9e90-7daa33ba045c] +description = "drop -> errors if there is nothing on the stack" + +[3ee68e62-f98a-4cce-9e6c-8aae6c65a4e3] +description = "swap -> swaps the top two values on the stack if they are the only ones" + +[8ce869d5-a503-44e4-ab55-1da36816ff1c] +description = "swap -> swaps the top two values on the stack if they are not the only ones" + +[74ba5b2a-b028-4759-9176-c5c0e7b2b154] +description = "swap -> errors if there is nothing on the stack" + +[dd52e154-5d0d-4a5c-9e5d-73eb36052bc8] +description = "swap -> errors if there is only one value on the stack" + +[a2654074-ba68-4f93-b014-6b12693a8b50] +description = "over -> copies the second element if there are only two" + +[c5b51097-741a-4da7-8736-5c93fa856339] +description = "over -> copies the second element if there are more than two" + +[6e1703a6-5963-4a03-abba-02e77e3181fd] +description = "over -> errors if there is nothing on the stack" + +[ee574dc4-ef71-46f6-8c6a-b4af3a10c45f] +description = "over -> errors if there is only one value on the stack" + +[ed45cbbf-4dbf-4901-825b-54b20dbee53b] +description = "user-defined words -> can consist of built-in words" [2726ea44-73e4-436b-bc2b-5ff0c6aa014b] -description = "execute in the right order" +description = "user-defined words -> execute in the right order" + +[9e53c2d0-b8ef-4ad8-b2c9-a559b421eb33] +description = "user-defined words -> can override other user-defined words" + +[669db3f3-5bd6-4be0-83d1-618cd6e4984b] +description = "user-defined words -> can override built-in words" + +[588de2f0-c56e-4c68-be0b-0bb1e603c500] +description = "user-defined words -> can override built-in operators" [ac12aaaf-26c6-4a10-8b3c-1c958fa2914c] -description = "can use different words with the same name" +description = "user-defined words -> can use different words with the same name" [53f82ef0-2750-4ccb-ac04-5d8c1aefabb1] -description = "can define word that uses word with the same name" +description = "user-defined words -> can define word that uses word with the same name" + +[35958cee-a976-4a0f-9378-f678518fa322] +description = "user-defined words -> cannot redefine non-negative numbers" + +[df5b2815-3843-4f55-b16c-c3ed507292a7] +description = "user-defined words -> cannot redefine negative numbers" + +[5180f261-89dd-491e-b230-62737e09806f] +description = "user-defined words -> errors if executing a non-existent word" + +[3c8bfef3-edbb-49c1-9993-21d4030043cb] +description = "user-defined words -> only defines locally" + +[7b83bb2e-b0e8-461f-ad3b-96ee2e111ed6] +description = "case-insensitivity -> DUP is case-insensitive" + +[339ed30b-f5b4-47ff-ab1c-67591a9cd336] +description = "case-insensitivity -> DROP is case-insensitive" + +[ee1af31e-1355-4b1b-bb95-f9d0b2961b87] +description = "case-insensitivity -> SWAP is case-insensitive" + +[acdc3a49-14c8-4cc2-945d-11edee6408fa] +description = "case-insensitivity -> OVER is case-insensitive" + +[5934454f-a24f-4efc-9fdd-5794e5f0c23c] +description = "case-insensitivity -> user-defined words are case-insensitive" + +[037d4299-195f-4be7-a46d-f07ca6280a06] +description = "case-insensitivity -> definitions are case-insensitive" diff --git a/exercises/practice/forth/Cargo.toml b/exercises/practice/forth/Cargo.toml index 9a51e6e8a..797644054 100644 --- a/exercises/practice/forth/Cargo.toml +++ b/exercises/practice/forth/Cargo.toml @@ -1,4 +1,12 @@ [package] -edition = "2021" name = "forth" -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] + +[lints.clippy] +new_without_default = "allow" diff --git a/exercises/practice/forth/src/lib.rs b/exercises/practice/forth/src/lib.rs index 16947dd9c..e0e06c73f 100644 --- a/exercises/practice/forth/src/lib.rs +++ b/exercises/practice/forth/src/lib.rs @@ -13,14 +13,14 @@ pub enum Error { impl Forth { pub fn new() -> Forth { - unimplemented!() + todo!() } pub fn stack(&self) -> &[Value] { - unimplemented!() + todo!() } pub fn eval(&mut self, input: &str) -> Result { - unimplemented!("result of evaluating '{input}'") + todo!("result of evaluating '{input}'") } } diff --git a/exercises/practice/forth/tests/alloc-attack.rs b/exercises/practice/forth/tests/alloc_attack.rs similarity index 97% rename from exercises/practice/forth/tests/alloc-attack.rs rename to exercises/practice/forth/tests/alloc_attack.rs index 861dfe733..0ea591a93 100644 --- a/exercises/practice/forth/tests/alloc-attack.rs +++ b/exercises/practice/forth/tests/alloc_attack.rs @@ -76,11 +76,11 @@ struct TrackingAllocator(A, AtomicU64); unsafe impl GlobalAlloc for TrackingAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { self.1.fetch_add(layout.size() as u64, Ordering::SeqCst); - self.0.alloc(layout) + unsafe { self.0.alloc(layout) } } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - self.0.dealloc(ptr, layout); + unsafe { self.0.dealloc(ptr, layout) }; self.1.fetch_sub(layout.size() as u64, Ordering::SeqCst); } } diff --git a/exercises/practice/forth/tests/forth.rs b/exercises/practice/forth/tests/forth.rs index a2526e98e..887d381df 100644 --- a/exercises/practice/forth/tests/forth.rs +++ b/exercises/practice/forth/tests/forth.rs @@ -1,373 +1,485 @@ -use forth::{Error, Forth, Value}; - -#[test] -fn no_input_no_stack() { - assert_eq!(Vec::::new(), Forth::new().stack()); -} - -#[test] -#[ignore] -fn numbers_just_get_pushed_onto_the_stack() { - let mut f = Forth::new(); - assert!(f.eval("1 2 3 4 5").is_ok()); - assert_eq!(vec![1, 2, 3, 4, 5], f.stack()); -} - -#[test] -#[ignore] -fn can_add_two_numbers() { - let mut f = Forth::new(); - assert!(f.eval("1 2 +").is_ok()); - assert_eq!(vec![3], f.stack()); -} - -#[test] -#[ignore] -fn addition_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 +")); - assert_eq!(Err(Error::StackUnderflow), f.eval("+")); -} - -#[test] -#[ignore] -fn can_subtract_two_numbers() { - let mut f = Forth::new(); - assert!(f.eval("3 4 -").is_ok()); - assert_eq!(vec![-1], f.stack()); -} - -#[test] -#[ignore] -fn subtraction_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 -")); - assert_eq!(Err(Error::StackUnderflow), f.eval("-")); -} - -#[test] -#[ignore] -fn can_multiply_two_numbers() { - let mut f = Forth::new(); - assert!(f.eval("2 4 *").is_ok()); - assert_eq!(vec![8], f.stack()); -} - -#[test] -#[ignore] -fn multiplication_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 *")); - assert_eq!(Err(Error::StackUnderflow), f.eval("*")); -} - -#[test] -#[ignore] -fn can_divide_two_numbers() { - let mut f = Forth::new(); - assert!(f.eval("12 3 /").is_ok()); - assert_eq!(vec![4], f.stack()); -} - -#[test] -#[ignore] -fn performs_integer_division() { - let mut f = Forth::new(); - assert!(f.eval("8 3 /").is_ok()); - assert_eq!(vec![2], f.stack()); -} - -#[test] -#[ignore] -fn division_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 /")); - assert_eq!(Err(Error::StackUnderflow), f.eval("/")); -} - -#[test] -#[ignore] -fn errors_if_dividing_by_zero() { - let mut f = Forth::new(); - assert_eq!(Err(Error::DivisionByZero), f.eval("4 0 /")); -} - -#[test] -#[ignore] -fn addition_and_subtraction() { - let mut f = Forth::new(); - assert!(f.eval("1 2 + 4 -").is_ok()); - assert_eq!(vec![-1], f.stack()); -} - -#[test] -#[ignore] -fn multiplication_and_division() { - let mut f = Forth::new(); - assert!(f.eval("2 4 * 3 /").is_ok()); - assert_eq!(vec![2], f.stack()); -} - -#[test] -#[ignore] -fn dup() { - let mut f = Forth::new(); - assert!(f.eval("1 dup").is_ok()); - assert_eq!(vec![1, 1], f.stack()); -} - -#[test] -#[ignore] -fn dup_top_value_only() { - let mut f = Forth::new(); - assert!(f.eval("1 2 dup").is_ok()); - assert_eq!(vec![1, 2, 2], f.stack()); -} - -#[test] -#[ignore] -fn dup_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval("1 DUP Dup dup").is_ok()); - assert_eq!(vec![1, 1, 1, 1], f.stack()); -} - -#[test] -#[ignore] -fn dup_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("dup")); -} - -#[test] -#[ignore] -fn drop() { - let mut f = Forth::new(); - assert!(f.eval("1 drop").is_ok()); - assert_eq!(Vec::::new(), f.stack()); -} - -#[test] -#[ignore] -fn drop_with_two() { - let mut f = Forth::new(); - assert!(f.eval("1 2 drop").is_ok()); - assert_eq!(vec![1], f.stack()); -} - -#[test] -#[ignore] -fn drop_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval("1 2 3 4 DROP Drop drop").is_ok()); - assert_eq!(vec![1], f.stack()); -} - -#[test] -#[ignore] -fn drop_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("drop")); -} - -#[test] -#[ignore] -fn swap() { - let mut f = Forth::new(); - assert!(f.eval("1 2 swap").is_ok()); - assert_eq!(vec![2, 1], f.stack()); -} - -#[test] -#[ignore] -fn swap_with_three() { - let mut f = Forth::new(); - assert!(f.eval("1 2 3 swap").is_ok()); - assert_eq!(vec![1, 3, 2], f.stack()); -} - -#[test] -#[ignore] -fn swap_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval("1 2 SWAP 3 Swap 4 swap").is_ok()); - assert_eq!(vec![2, 3, 4, 1], f.stack()); -} - -#[test] -#[ignore] -fn swap_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 swap")); - assert_eq!(Err(Error::StackUnderflow), f.eval("swap")); -} - -#[test] -#[ignore] -fn over() { - let mut f = Forth::new(); - assert!(f.eval("1 2 over").is_ok()); - assert_eq!(vec![1, 2, 1], f.stack()); -} - -#[test] -#[ignore] -fn over_with_three() { - let mut f = Forth::new(); - assert!(f.eval("1 2 3 over").is_ok()); - assert_eq!(vec![1, 2, 3, 2], f.stack()); -} - -#[test] -#[ignore] -fn over_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval("1 2 OVER Over over").is_ok()); - assert_eq!(vec![1, 2, 1, 2, 1], f.stack()); -} - -#[test] -#[ignore] -fn over_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 over")); - assert_eq!(Err(Error::StackUnderflow), f.eval("over")); -} - -// User-defined words - -#[test] -#[ignore] -fn can_consist_of_built_in_words() { - let mut f = Forth::new(); - assert!(f.eval(": dup-twice dup dup ;").is_ok()); - assert!(f.eval("1 dup-twice").is_ok()); - assert_eq!(vec![1, 1, 1], f.stack()); -} - -#[test] -#[ignore] -fn execute_in_the_right_order() { - let mut f = Forth::new(); - assert!(f.eval(": countup 1 2 3 ;").is_ok()); - assert!(f.eval("countup").is_ok()); - assert_eq!(vec![1, 2, 3], f.stack()); -} - -#[test] -#[ignore] -fn redefining_an_existing_word() { - let mut f = Forth::new(); - assert!(f.eval(": foo dup ;").is_ok()); - assert!(f.eval(": foo dup dup ;").is_ok()); - assert!(f.eval("1 foo").is_ok()); - assert_eq!(vec![1, 1, 1], f.stack()); -} - -#[test] -#[ignore] -fn redefining_an_existing_built_in_word() { - let mut f = Forth::new(); - assert!(f.eval(": swap dup ;").is_ok()); - assert!(f.eval("1 swap").is_ok()); - assert_eq!(vec![1, 1], f.stack()); -} - -#[test] -#[ignore] -fn user_defined_words_are_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval(": foo dup ;").is_ok()); - assert!(f.eval("1 FOO Foo foo").is_ok()); - assert_eq!(vec![1, 1, 1, 1], f.stack()); -} - -#[test] -#[ignore] -fn definitions_are_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval(": SWAP DUP Dup dup ;").is_ok()); - assert!(f.eval("1 swap").is_ok()); - assert_eq!(vec![1, 1, 1, 1], f.stack()); -} - -#[test] -#[ignore] -fn redefining_a_built_in_operator() { - let mut f = Forth::new(); - assert!(f.eval(": + * ;").is_ok()); - assert!(f.eval("3 4 +").is_ok()); - assert_eq!(vec![12], f.stack()); -} - -#[test] -#[ignore] -fn can_use_different_words_with_the_same_name() { - let mut f = Forth::new(); - assert!(f.eval(": foo 5 ;").is_ok()); - assert!(f.eval(": bar foo ;").is_ok()); - assert!(f.eval(": foo 6 ;").is_ok()); - assert!(f.eval("bar foo").is_ok()); - assert_eq!(vec![5, 6], f.stack()); -} - -#[test] -#[ignore] -fn can_define_word_that_uses_word_with_the_same_name() { - let mut f = Forth::new(); - assert!(f.eval(": foo 10 ;").is_ok()); - assert!(f.eval(": foo foo 1 + ;").is_ok()); - assert!(f.eval("foo").is_ok()); - assert_eq!(vec![11], f.stack()); -} - -#[test] -#[ignore] -fn defining_a_number() { - let mut f = Forth::new(); - assert_eq!(Err(Error::InvalidWord), f.eval(": 1 2 ;")); -} - -#[test] -#[ignore] -fn malformed_word_definition() { - let mut f = Forth::new(); - assert_eq!(Err(Error::InvalidWord), f.eval(":")); - assert_eq!(Err(Error::InvalidWord), f.eval(": foo")); - assert_eq!(Err(Error::InvalidWord), f.eval(": foo 1")); -} - -#[test] -#[ignore] -fn calling_non_existing_word() { - let mut f = Forth::new(); - assert_eq!(Err(Error::UnknownWord), f.eval("1 foo")); -} - -#[test] -#[ignore] -fn multiple_definitions() { - let mut f = Forth::new(); - assert!(f.eval(": one 1 ; : two 2 ; one two +").is_ok()); - assert_eq!(vec![3], f.stack()); -} - -#[test] -#[ignore] -fn definitions_after_ops() { - let mut f = Forth::new(); - assert!(f.eval("1 2 + : addone 1 + ; addone").is_ok()); - assert_eq!(vec![4], f.stack()); -} - -#[test] -#[ignore] -fn redefine_an_existing_word_with_another_existing_word() { - let mut f = Forth::new(); - assert!(f.eval(": foo 5 ;").is_ok()); - assert!(f.eval(": bar foo ;").is_ok()); - assert!(f.eval(": foo 6 ;").is_ok()); - assert!(f.eval(": bar foo ;").is_ok()); - assert!(f.eval("bar foo").is_ok()); - assert_eq!(vec![6, 6], f.stack()); +mod parsing_and_numbers { + use forth::*; + + #[test] + fn numbers_just_get_pushed_onto_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 4 5").is_ok()); + assert_eq!(f.stack(), [1, 2, 3, 4, 5]); + } + + #[test] + #[ignore] + fn pushes_negative_numbers_onto_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("-1 -2 -3 -4 -5").is_ok()); + assert_eq!(f.stack(), [-1, -2, -3, -4, -5]); + } +} + +mod addition { + use forth::*; + + #[test] + #[ignore] + fn can_add_two_numbers() { + let mut f = Forth::new(); + assert!(f.eval("1 2 +").is_ok()); + assert_eq!(f.stack(), [3]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("+"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 +"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn more_than_two_values_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 +").is_ok()); + assert_eq!(f.stack(), [1, 5]); + } +} + +mod subtraction { + use forth::*; + + #[test] + #[ignore] + fn can_subtract_two_numbers() { + let mut f = Forth::new(); + assert!(f.eval("3 4 -").is_ok()); + assert_eq!(f.stack(), [-1]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("-"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 -"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn more_than_two_values_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 12 3 -").is_ok()); + assert_eq!(f.stack(), [1, 9]); + } +} + +mod multiplication { + use forth::*; + + #[test] + #[ignore] + fn can_multiply_two_numbers() { + let mut f = Forth::new(); + assert!(f.eval("2 4 *").is_ok()); + assert_eq!(f.stack(), [8]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("*"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 *"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn more_than_two_values_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 *").is_ok()); + assert_eq!(f.stack(), [1, 6]); + } +} + +mod division { + use forth::*; + + #[test] + #[ignore] + fn can_divide_two_numbers() { + let mut f = Forth::new(); + assert!(f.eval("12 3 /").is_ok()); + assert_eq!(f.stack(), [4]); + } + + #[test] + #[ignore] + fn performs_integer_division() { + let mut f = Forth::new(); + assert!(f.eval("8 3 /").is_ok()); + assert_eq!(f.stack(), [2]); + } + + #[test] + #[ignore] + fn errors_if_dividing_by_zero() { + let mut f = Forth::new(); + assert_eq!(f.eval("4 0 /"), Err(Error::DivisionByZero)); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("/"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 /"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn more_than_two_values_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 12 3 /").is_ok()); + assert_eq!(f.stack(), [1, 4]); + } +} + +mod combined_arithmetic { + use forth::*; + + #[test] + #[ignore] + fn addition_and_subtraction() { + let mut f = Forth::new(); + assert!(f.eval("1 2 + 4 -").is_ok()); + assert_eq!(f.stack(), [-1]); + } + + #[test] + #[ignore] + fn multiplication_and_division() { + let mut f = Forth::new(); + assert!(f.eval("2 4 * 3 /").is_ok()); + assert_eq!(f.stack(), [2]); + } + + #[test] + #[ignore] + fn multiplication_and_addition() { + let mut f = Forth::new(); + assert!(f.eval("1 3 4 * +").is_ok()); + assert_eq!(f.stack(), [13]); + } + + #[test] + #[ignore] + fn addition_and_multiplication() { + let mut f = Forth::new(); + assert!(f.eval("1 3 4 + *").is_ok()); + assert_eq!(f.stack(), [7]); + } +} + +mod dup { + use forth::*; + + #[test] + #[ignore] + fn copies_a_value_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 dup").is_ok()); + assert_eq!(f.stack(), [1, 1]); + } + + #[test] + #[ignore] + fn copies_the_top_value_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 2 dup").is_ok()); + assert_eq!(f.stack(), [1, 2, 2]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("dup"), Err(Error::StackUnderflow)); + } +} + +mod drop { + use forth::*; + + #[test] + #[ignore] + fn removes_the_top_value_on_the_stack_if_it_is_the_only_one() { + let mut f = Forth::new(); + assert!(f.eval("1 drop").is_ok()); + assert_eq!(f.stack(), []); + } + + #[test] + #[ignore] + fn removes_the_top_value_on_the_stack_if_it_is_not_the_only_one() { + let mut f = Forth::new(); + assert!(f.eval("1 2 drop").is_ok()); + assert_eq!(f.stack(), [1]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("drop"), Err(Error::StackUnderflow)); + } +} + +mod swap { + use forth::*; + + #[test] + #[ignore] + fn swaps_the_top_two_values_on_the_stack_if_they_are_the_only_ones() { + let mut f = Forth::new(); + assert!(f.eval("1 2 swap").is_ok()); + assert_eq!(f.stack(), [2, 1]); + } + + #[test] + #[ignore] + fn swaps_the_top_two_values_on_the_stack_if_they_are_not_the_only_ones() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 swap").is_ok()); + assert_eq!(f.stack(), [1, 3, 2]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("swap"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 swap"), Err(Error::StackUnderflow)); + } +} + +mod over { + use forth::*; + + #[test] + #[ignore] + fn copies_the_second_element_if_there_are_only_two() { + let mut f = Forth::new(); + assert!(f.eval("1 2 over").is_ok()); + assert_eq!(f.stack(), [1, 2, 1]); + } + + #[test] + #[ignore] + fn copies_the_second_element_if_there_are_more_than_two() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 over").is_ok()); + assert_eq!(f.stack(), [1, 2, 3, 2]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("over"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 over"), Err(Error::StackUnderflow)); + } +} + +mod user_defined_words { + use forth::*; + + #[test] + #[ignore] + fn can_consist_of_built_in_words() { + let mut f = Forth::new(); + assert!(f.eval(": dup-twice dup dup ;").is_ok()); + assert!(f.eval("1 dup-twice").is_ok()); + assert_eq!(f.stack(), [1, 1, 1]); + } + + #[test] + #[ignore] + fn execute_in_the_right_order() { + let mut f = Forth::new(); + assert!(f.eval(": countup 1 2 3 ;").is_ok()); + assert!(f.eval("countup").is_ok()); + assert_eq!(f.stack(), [1, 2, 3]); + } + + #[test] + #[ignore] + fn can_override_other_user_defined_words() { + let mut f = Forth::new(); + assert!(f.eval(": foo dup ;").is_ok()); + assert!(f.eval(": foo dup dup ;").is_ok()); + assert!(f.eval("1 foo").is_ok()); + assert_eq!(f.stack(), [1, 1, 1]); + } + + #[test] + #[ignore] + fn can_override_built_in_words() { + let mut f = Forth::new(); + assert!(f.eval(": swap dup ;").is_ok()); + assert!(f.eval("1 swap").is_ok()); + assert_eq!(f.stack(), [1, 1]); + } + + #[test] + #[ignore] + fn can_override_built_in_operators() { + let mut f = Forth::new(); + assert!(f.eval(": + * ;").is_ok()); + assert!(f.eval("3 4 +").is_ok()); + assert_eq!(f.stack(), [12]); + } + + #[test] + #[ignore] + fn can_use_different_words_with_the_same_name() { + let mut f = Forth::new(); + assert!(f.eval(": foo 5 ;").is_ok()); + assert!(f.eval(": bar foo ;").is_ok()); + assert!(f.eval(": foo 6 ;").is_ok()); + assert!(f.eval("bar foo").is_ok()); + assert_eq!(f.stack(), [5, 6]); + } + + #[test] + #[ignore] + fn can_define_word_that_uses_word_with_the_same_name() { + let mut f = Forth::new(); + assert!(f.eval(": foo 10 ;").is_ok()); + assert!(f.eval(": foo foo 1 + ;").is_ok()); + assert!(f.eval("foo").is_ok()); + assert_eq!(f.stack(), [11]); + } + + #[test] + #[ignore] + fn cannot_redefine_non_negative_numbers() { + let mut f = Forth::new(); + assert_eq!(f.eval(": 1 2 ;"), Err(Error::InvalidWord)); + } + + #[test] + #[ignore] + fn cannot_redefine_negative_numbers() { + let mut f = Forth::new(); + assert_eq!(f.eval(": -1 2 ;"), Err(Error::InvalidWord)); + } + + #[test] + #[ignore] + fn errors_if_executing_a_non_existent_word() { + let mut f = Forth::new(); + assert_eq!(f.eval("foo"), Err(Error::UnknownWord)); + } + + #[test] + #[ignore] + fn only_defines_locally() { + let mut f = Forth::new(); + assert!(f.eval(": + - ;").is_ok()); + assert!(f.eval("1 1 +").is_ok()); + assert_eq!(f.stack(), [0]); + let mut f = Forth::new(); + assert!(f.eval("1 1 +").is_ok()); + assert_eq!(f.stack(), [2]); + } +} + +mod case_insensitivity { + use forth::*; + + #[test] + #[ignore] + fn dup_is_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval("1 DUP Dup dup").is_ok()); + assert_eq!(f.stack(), [1, 1, 1, 1]); + } + + #[test] + #[ignore] + fn drop_is_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 4 DROP Drop drop").is_ok()); + assert_eq!(f.stack(), [1]); + } + + #[test] + #[ignore] + fn swap_is_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval("1 2 SWAP 3 Swap 4 swap").is_ok()); + assert_eq!(f.stack(), [2, 3, 4, 1]); + } + + #[test] + #[ignore] + fn over_is_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval("1 2 OVER Over over").is_ok()); + assert_eq!(f.stack(), [1, 2, 1, 2, 1]); + } + + #[test] + #[ignore] + fn user_defined_words_are_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval(": foo dup ;").is_ok()); + assert!(f.eval("1 FOO Foo foo").is_ok()); + assert_eq!(f.stack(), [1, 1, 1, 1]); + } + + #[test] + #[ignore] + fn definitions_are_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval(": SWAP DUP Dup dup ;").is_ok()); + assert!(f.eval("1 swap").is_ok()); + assert_eq!(f.stack(), [1, 1, 1, 1]); + } } diff --git a/exercises/practice/gigasecond/.docs/instructions.md b/exercises/practice/gigasecond/.docs/instructions.md index 680870f3a..1e20f0022 100644 --- a/exercises/practice/gigasecond/.docs/instructions.md +++ b/exercises/practice/gigasecond/.docs/instructions.md @@ -1,6 +1,8 @@ # Instructions -Given a moment, determine the moment that would be after a gigasecond -has passed. +Your task is to determine the date and time one gigasecond after a certain date. -A gigasecond is 10^9 (1,000,000,000) seconds. +A gigasecond is one thousand million seconds. +That is a one with nine zeros after it. + +If you were born on _January 24th, 2015 at 22:00 (10:00:00pm)_, then you would be a gigasecond old on _October 2nd, 2046 at 23:46:40 (11:46:40pm)_. diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md new file mode 100644 index 000000000..18a3dc200 --- /dev/null +++ b/exercises/practice/gigasecond/.docs/introduction.md @@ -0,0 +1,24 @@ +# Introduction + +The way we measure time is kind of messy. +We have 60 seconds in a minute, and 60 minutes in an hour. +This comes from ancient Babylon, where they used 60 as the basis for their number system. +We have 24 hours in a day, 7 days in a week, and how many days in a month? +Well, for days in a month it depends not only on which month it is, but also on what type of calendar is used in the country you live in. + +What if, instead, we only use seconds to express time intervals? +Then we can use metric system prefixes for writing large numbers of seconds in more easily comprehensible quantities. + +- A food recipe might explain that you need to let the brownies cook in the oven for two kiloseconds (that's two thousand seconds). +- Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). +- And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. + +~~~~exercism/note +If we ever colonize Mars or some other planet, measuring time is going to get even messier. +If someone says "year" do they mean a year on Earth or a year on Mars? + +The idea for this exercise came from the science fiction novel ["A Deepness in the Sky"][vinge-novel] by author Vernor Vinge. +In it the author uses the metric system as the basis for time measurements. + +[vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ +~~~~ diff --git a/exercises/practice/gigasecond/.gitignore b/exercises/practice/gigasecond/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/gigasecond/.gitignore +++ b/exercises/practice/gigasecond/.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/gigasecond/.meta/config.json b/exercises/practice/gigasecond/.meta/config.json index 43bf4127e..d3e038dbb 100644 --- a/exercises/practice/gigasecond/.meta/config.json +++ b/exercises/practice/gigasecond/.meta/config.json @@ -41,5 +41,5 @@ }, "blurb": "Given a moment, determine the moment that would be after a gigasecond has passed.", "source": "Chapter 9 in Chris Pine's online Learn to Program tutorial.", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=09" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=09" } diff --git a/exercises/practice/gigasecond/.meta/test_template.tera b/exercises/practice/gigasecond/.meta/test_template.tera new file mode 100644 index 000000000..e429b5276 --- /dev/null +++ b/exercises/practice/gigasecond/.meta/test_template.tera @@ -0,0 +1,53 @@ +{% for test in cases %} + +{# + Parsing datetime strings in a templating language + really makes you question your life choices. +#} +{% set date_and_time = test.input.moment | split(pat="T") %} +{% set date = date_and_time.0 | split(pat="-") %} +{% set year = date.0 | int %} +{% set month = date.1 | int %} +{% set day = date.2 | int %} +{% if date_and_time | length >= 2 %} + {% set time = date_and_time.1 | split(pat=":") %} + {% set hour = time.0 | int %} + {% set minute = time.1 | int %} + {% set second = time.2 | int %} +{% else %} + {% set hour = 0 %} + {% set minute = 0 %} + {% set second = 0 %} +{% endif %} + +{# + again for the expected datetime +#} +{% set date_and_time = test.expected | split(pat="T") %} +{% set date = date_and_time.0 | split(pat="-") %} +{% set e_year = date.0 | int %} +{% set e_month = date.1 | int %} +{% set e_day = date.2 | int %} +{% set time = date_and_time.1 | split(pat=":") %} +{% set e_hour = time.0 | int %} +{% set e_minute = time.1 | int %} +{% set e_second = time.2 | int %} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let start = datetime({{ year }},{{ month }},{{ day }},{{ hour }},{{ minute }},{{ second }}); + let actual = gigasecond::after(start); + let expected = datetime({{ e_year }},{{ e_month }},{{ e_day }},{{ e_hour }},{{ e_minute }},{{ e_second }}); + assert_eq!(actual, expected); +} +{% endfor %} + +fn datetime(year: i32, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> time::PrimitiveDateTime { + use time::{Date, PrimitiveDateTime, Time}; + + PrimitiveDateTime::new( + Date::from_calendar_date(year, month.try_into().unwrap(), day).unwrap(), + Time::from_hms(hour, minute, second).unwrap(), + ) +} diff --git a/exercises/practice/gigasecond/.meta/tests.toml b/exercises/practice/gigasecond/.meta/tests.toml index be690e975..91a3c8e53 100644 --- a/exercises/practice/gigasecond/.meta/tests.toml +++ b/exercises/practice/gigasecond/.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. + +[92fbe71c-ea52-4fac-bd77-be38023cacf7] +description = "date only specification of time" + +[6d86dd16-6f7a-47be-9e58-bb9fb2ae1433] +description = "second test for date only specification of time" + +[77eb8502-2bca-4d92-89d9-7b39ace28dd5] +description = "third test for date only specification of time" + +[c9d89a7d-06f8-4e28-a305-64f1b2abc693] +description = "full time specified" + +[09d4e30e-728a-4b52-9005-be44a58d9eba] +description = "full time with day roll-over" + +[fcec307c-7529-49ab-b0fe-20309197618a] +description = "does not mutate the input" +include = false +comments = "Rust has immutability by default" diff --git a/exercises/practice/gigasecond/Cargo.toml b/exercises/practice/gigasecond/Cargo.toml index da110b7b2..826b09466 100644 --- a/exercises/practice/gigasecond/Cargo.toml +++ b/exercises/practice/gigasecond/Cargo.toml @@ -1,7 +1,10 @@ [package] -edition = "2021" name = "gigasecond" -version = "2.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] time = "0.3" diff --git a/exercises/practice/gigasecond/src/lib.rs b/exercises/practice/gigasecond/src/lib.rs index 91daf88f5..2dbb9ab9a 100644 --- a/exercises/practice/gigasecond/src/lib.rs +++ b/exercises/practice/gigasecond/src/lib.rs @@ -2,5 +2,5 @@ use time::PrimitiveDateTime as DateTime; // Returns a DateTime one billion seconds after start. pub fn after(start: DateTime) -> DateTime { - unimplemented!("What time is a gigasecond later than {start}"); + todo!("What time is a gigasecond later than {start}"); } diff --git a/exercises/practice/gigasecond/tests/gigasecond.rs b/exercises/practice/gigasecond/tests/gigasecond.rs index abd53a882..e29f5c123 100644 --- a/exercises/practice/gigasecond/tests/gigasecond.rs +++ b/exercises/practice/gigasecond/tests/gigasecond.rs @@ -1,52 +1,59 @@ -use time::PrimitiveDateTime as DateTime; - -/// Create a datetime from the given numeric point in time. -/// -/// Panics if any field is invalid. -fn dt(year: i32, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> DateTime { - use time::{Date, Time}; - - DateTime::new( - Date::from_calendar_date(year, month.try_into().unwrap(), day).unwrap(), - Time::from_hms(hour, minute, second).unwrap(), - ) -} - #[test] -fn test_date() { - let start_date = dt(2011, 4, 25, 0, 0, 0); - - assert_eq!(gigasecond::after(start_date), dt(2043, 1, 1, 1, 46, 40)); +fn date_only_specification_of_time() { + let start = datetime(2011, 4, 25, 0, 0, 0); + let actual = gigasecond::after(start); + let expected = datetime(2043, 1, 1, 1, 46, 40); + assert_eq!(actual, expected); } #[test] #[ignore] -fn test_another_date() { - let start_date = dt(1977, 6, 13, 0, 0, 0); - - assert_eq!(gigasecond::after(start_date), dt(2009, 2, 19, 1, 46, 40)); +fn second_test_for_date_only_specification_of_time() { + let start = datetime(1977, 6, 13, 0, 0, 0); + let actual = gigasecond::after(start); + let expected = datetime(2009, 2, 19, 1, 46, 40); + assert_eq!(actual, expected); } #[test] #[ignore] -fn test_third_date() { - let start_date = dt(1959, 7, 19, 0, 0, 0); - - assert_eq!(gigasecond::after(start_date), dt(1991, 3, 27, 1, 46, 40)); +fn third_test_for_date_only_specification_of_time() { + let start = datetime(1959, 7, 19, 0, 0, 0); + let actual = gigasecond::after(start); + let expected = datetime(1991, 3, 27, 1, 46, 40); + assert_eq!(actual, expected); } #[test] #[ignore] -fn test_datetime() { - let start_date = dt(2015, 1, 24, 22, 0, 0); - - assert_eq!(gigasecond::after(start_date), dt(2046, 10, 2, 23, 46, 40)); +fn full_time_specified() { + let start = datetime(2015, 1, 24, 22, 0, 0); + let actual = gigasecond::after(start); + let expected = datetime(2046, 10, 2, 23, 46, 40); + assert_eq!(actual, expected); } #[test] #[ignore] -fn test_another_datetime() { - let start_date = dt(2015, 1, 24, 23, 59, 59); +fn full_time_with_day_roll_over() { + let start = datetime(2015, 1, 24, 23, 59, 59); + let actual = gigasecond::after(start); + let expected = datetime(2046, 10, 3, 1, 46, 39); + assert_eq!(actual, expected); +} - assert_eq!(gigasecond::after(start_date), dt(2046, 10, 3, 1, 46, 39)); +fn datetime( + year: i32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, +) -> time::PrimitiveDateTime { + use time::{Date, PrimitiveDateTime, Time}; + + PrimitiveDateTime::new( + Date::from_calendar_date(year, month.try_into().unwrap(), day).unwrap(), + Time::from_hms(hour, minute, second).unwrap(), + ) } diff --git a/exercises/practice/grade-school/.docs/instructions.md b/exercises/practice/grade-school/.docs/instructions.md index 8bbbf6446..3cb1b5d5f 100644 --- a/exercises/practice/grade-school/.docs/instructions.md +++ b/exercises/practice/grade-school/.docs/instructions.md @@ -1,38 +1,21 @@ # Instructions -Given students' names along with the grade that they are in, create a roster -for the school. +Given students' names along with the grade they are in, create a roster for the school. In the end, you should be able to: -- Add a student's name to the roster for a grade +- Add a student's name to the roster for a grade: - "Add Jim to grade 2." - "OK." -- Get a list of all students enrolled in a grade +- Get a list of all students enrolled in a grade: - "Which students are in grade 2?" - - "We've only got Jim just now." -- Get a sorted list of all students in all grades. Grades should sort - as 1, 2, 3, etc., and students within a grade should be sorted - alphabetically by name. - - "Who all is enrolled in school right now?" - - "Let me think. We have - Anna, Barb, and Charlie in grade 1, - Alex, Peter, and Zoe in grade 2 - and Jim in grade 5. - So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim" - -Note that all our students only have one name. (It's a small town, what -do you want?) - -## 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: - -- If you're working in a language with mutable data structures and your - implementation allows outside code to mutate the school's internal DB - directly, see if you can prevent this. Feel free to introduce additional - tests. - -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? + - "We've only got Jim right now." +- Get a sorted list of all students in all grades. + Grades should be sorted as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. + - "Who is enrolled in school right now?" + - "Let me think. + We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2, and Jim in grade 5. + So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe, and Jim." + +Note that all our students only have one name (it's a small town, what do you want?), and each student cannot be added more than once to a grade or the roster. +If a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. diff --git a/exercises/practice/grade-school/.gitignore b/exercises/practice/grade-school/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/grade-school/.gitignore +++ b/exercises/practice/grade-school/.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/grade-school/.meta/additional-tests.json b/exercises/practice/grade-school/.meta/additional-tests.json new file mode 100644 index 000000000..c4c2c3ace --- /dev/null +++ b/exercises/practice/grade-school/.meta/additional-tests.json @@ -0,0 +1,62 @@ +[ + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "grades_for_empty_school", + "comments": [ + "The original exercise design contains a `grades` method", + "which should not go untested." + ], + "property": "grades", + "input": { + "students": [] + }, + "expected": [] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "grades_for_one_student", + "comments": [ + "The original exercise design contains a `grades` method", + "which should not go untested." + ], + "property": "grades", + "input": { + "students": [["Aimee", 2]] + }, + "expected": [2] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "grades_for_several_students_are_sorted", + "comments": [ + "The original exercise design contains a `grades` method", + "which should not go untested." + ], + "property": "grades", + "input": { + "students": [ + ["Aimee", 2], + ["Logan", 7], + ["Blair", 4] + ] + }, + "expected": [2, 4, 7] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "grades_when_several_students_have_the_same_grade", + "comments": [ + "The original exercise design contains a `grades` method", + "which should not go untested." + ], + "property": "grades", + "input": { + "students": [ + ["Aimee", 2], + ["Logan", 2], + ["Blair", 2] + ] + }, + "expected": [2] + } +] diff --git a/exercises/practice/grade-school/.meta/config.json b/exercises/practice/grade-school/.meta/config.json index a097058bb..e08829488 100644 --- a/exercises/practice/grade-school/.meta/config.json +++ b/exercises/practice/grade-school/.meta/config.json @@ -31,7 +31,7 @@ "Cargo.toml" ], "test": [ - "tests/grade-school.rs" + "tests/grade_school.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/grade-school/.meta/example.rs b/exercises/practice/grade-school/.meta/example.rs index 6e1e470f3..cacd65fa3 100644 --- a/exercises/practice/grade-school/.meta/example.rs +++ b/exercises/practice/grade-school/.meta/example.rs @@ -12,7 +12,14 @@ impl School { } pub fn add(&mut self, grade: u32, student: &str) { - let entry = self.grades.entry(grade).or_insert_with(Vec::new); + if self + .grades + .iter() + .any(|(_, students)| students.iter().any(|s| s == student)) + { + return; // don't add duplicate student + } + let entry = self.grades.entry(grade).or_default(); entry.push(student.to_string()); entry.sort_unstable(); } @@ -30,3 +37,9 @@ impl School { .unwrap_or_default() } } + +impl Default for School { + fn default() -> Self { + Self::new() + } +} diff --git a/exercises/practice/grade-school/.meta/test_template.tera b/exercises/practice/grade-school/.meta/test_template.tera new file mode 100644 index 000000000..c05d12f59 --- /dev/null +++ b/exercises/practice/grade-school/.meta/test_template.tera @@ -0,0 +1,46 @@ +use grade_school::*; + +{% for test in cases %} + +{% if test.property == "roster" %} + {# + The original exercise design does not include a method for getting + the roster. Excluding these tests doesn't seem too bad, it would be + difficult to implement this property incorrectly while getting the other + ones right. + #} + {% continue %} +{% endif%} +{% if test.property == "add" %} + {# + The original exercise design doesn't define the add method as fallible. + #} + {% continue %} +{% endif%} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let {% if test.input.students | length > 0 %} + mut + {% endif %} s = School::new(); + {% for student in test.input.students -%} + s.add({{ student.1 }}, {{ student.0 | json_encode() }}); + {% endfor -%} + {% if test.property == "grade" -%} + assert_eq!( + s.grade({{ test.input.desiredGrade }}), + {% if test.expected | length == 0 -%} + Vec::::new() + {% else -%} + vec!{{ test.expected | json_encode() }} + {% endif -%} + ) + {% elif test.property == "grades" -%} + assert_eq!( + s.grades(), + vec!{{ test.expected | json_encode() }} + ) + {% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/grade-school/.meta/tests.toml b/exercises/practice/grade-school/.meta/tests.toml index be690e975..50c9e2e59 100644 --- a/exercises/practice/grade-school/.meta/tests.toml +++ b/exercises/practice/grade-school/.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. + +[a3f0fb58-f240-4723-8ddc-e644666b85cc] +description = "Roster is empty when no student is added" + +[9337267f-7793-4b90-9b4a-8e3978408824] +description = "Add a student" + +[6d0a30e4-1b4e-472e-8e20-c41702125667] +description = "Student is added to the roster" + +[73c3ca75-0c16-40d7-82f5-ed8fe17a8e4a] +description = "Adding multiple students in the same grade in the roster" + +[233be705-dd58-4968-889d-fb3c7954c9cc] +description = "Multiple students in the same grade are added to the roster" + +[87c871c1-6bde-4413-9c44-73d59a259d83] +description = "Cannot add student to same grade in the roster more than once" + +[c125dab7-2a53-492f-a99a-56ad511940d8] +description = "A student can't be in two different grades" +include = false + +[a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1] +description = "A student can only be added to the same grade in the roster once" +include = false +reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8" + +[d7982c4f-1602-49f6-a651-620f2614243a] +description = "Student not added to same grade in the roster more than once" +reimplements = "a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1" + +[e70d5d8f-43a9-41fd-94a4-1ea0fa338056] +description = "Adding students in multiple grades" + +[75a51579-d1d7-407c-a2f8-2166e984e8ab] +description = "Students in multiple grades are added to the roster" + +[7df542f1-57ce-433c-b249-ff77028ec479] +description = "Cannot add same student to multiple grades in the roster" + +[6a03b61e-1211-4783-a3cc-fc7f773fba3f] +description = "A student cannot be added to more than one grade in the sorted roster" +include = false +reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8" + +[c7ec1c5e-9ab7-4d3b-be5c-29f2f7a237c5] +description = "Student not added to multiple grades in the roster" +reimplements = "6a03b61e-1211-4783-a3cc-fc7f773fba3f" + +[d9af4f19-1ba1-48e7-94d0-dabda4e5aba6] +description = "Students are sorted by grades in the roster" + +[d9fb5bea-f5aa-4524-9d61-c158d8906807] +description = "Students are sorted by name in the roster" + +[180a8ff9-5b94-43fc-9db1-d46b4a8c93b6] +description = "Students are sorted by grades and then by name in the roster" + +[5e67aa3c-a3c6-4407-a183-d8fe59cd1630] +description = "Grade is empty if no students in the roster" + +[1e0cf06b-26e0-4526-af2d-a2e2df6a51d6] +description = "Grade is empty if no students in that grade" + +[2bfc697c-adf2-4b65-8d0f-c46e085f796e] +description = "Student not added to same grade more than once" + +[66c8e141-68ab-4a04-a15a-c28bc07fe6b9] +description = "Student not added to multiple grades" + +[c9c1fc2f-42e0-4d2c-b361-99271f03eda7] +description = "Student not added to other grade for multiple grades" + +[1bfbcef1-e4a3-49e8-8d22-f6f9f386187e] +description = "Students are sorted by name in a grade" diff --git a/exercises/practice/grade-school/Cargo.toml b/exercises/practice/grade-school/Cargo.toml index d8851568d..b31ba7356 100644 --- a/exercises/practice/grade-school/Cargo.toml +++ b/exercises/practice/grade-school/Cargo.toml @@ -1,4 +1,12 @@ [package] -edition = "2021" -name = "grade-school" -version = "0.0.0" +name = "grade_school" +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/grade-school/src/lib.rs b/exercises/practice/grade-school/src/lib.rs index b81e9a1ab..da3f53603 100644 --- a/exercises/practice/grade-school/src/lib.rs +++ b/exercises/practice/grade-school/src/lib.rs @@ -1,23 +1,16 @@ -// This annotation prevents Clippy from warning us that `School` has a -// `fn new()` with no arguments, but doesn't implement the `Default` trait. -// -// Normally, it's good practice to just do what Clippy tells you, but in this -// case, we want to keep things relatively simple. The `Default` trait is not the point -// of this exercise. -#[allow(clippy::new_without_default)] pub struct School {} impl School { pub fn new() -> School { - unimplemented!() + todo!() } pub fn add(&mut self, grade: u32, student: &str) { - unimplemented!("Add {student} to the roster for {grade}") + todo!("Add {student} to the roster for {grade}") } pub fn grades(&self) -> Vec { - unimplemented!() + todo!() } // If `grade` returned a reference, `School` would be forced to keep a `Vec` @@ -25,6 +18,6 @@ impl School { // the internal structure can be completely arbitrary. The tradeoff is that some data // must be copied each time `grade` is called. pub fn grade(&self, grade: u32) -> Vec { - unimplemented!("Return the list of students in {grade}") + todo!("Return the list of students in {grade}") } } diff --git a/exercises/practice/grade-school/tests/grade-school.rs b/exercises/practice/grade-school/tests/grade-school.rs deleted file mode 100644 index eb41d0789..000000000 --- a/exercises/practice/grade-school/tests/grade-school.rs +++ /dev/null @@ -1,83 +0,0 @@ -use grade_school as school; - -fn to_owned(v: &[&str]) -> Vec { - v.iter().map(|s| s.to_string()).collect() -} - -#[test] -fn test_grades_for_empty_school() { - let s = school::School::new(); - assert_eq!(s.grades(), vec![]); -} - -#[test] -#[ignore] -fn test_grades_for_one_student() { - let mut s = school::School::new(); - s.add(2, "Aimee"); - assert_eq!(s.grades(), vec![2]); -} - -#[test] -#[ignore] -fn test_grades_for_several_students_are_sorted() { - let mut s = school::School::new(); - s.add(2, "Aimee"); - s.add(7, "Logan"); - s.add(4, "Blair"); - assert_eq!(s.grades(), vec![2, 4, 7]); -} - -#[test] -#[ignore] -fn test_grades_when_several_students_have_the_same_grade() { - let mut s = school::School::new(); - s.add(2, "Aimee"); - s.add(2, "Logan"); - s.add(2, "Blair"); - assert_eq!(s.grades(), vec![2]); -} - -#[test] -#[ignore] -fn test_grade_for_empty_school() { - let s = school::School::new(); - assert_eq!(s.grade(1), Vec::::new()); -} - -#[test] -#[ignore] -fn test_grade_when_no_students_have_that_grade() { - let mut s = school::School::new(); - s.add(7, "Logan"); - assert_eq!(s.grade(1), Vec::::new()); -} - -#[test] -#[ignore] -fn test_grade_for_one_student() { - let mut s = school::School::new(); - s.add(2, "Aimee"); - assert_eq!(s.grade(2), to_owned(&["Aimee"])); -} - -#[test] -#[ignore] -fn test_grade_returns_students_sorted_by_name() { - let mut s = school::School::new(); - s.add(2, "James"); - s.add(2, "Blair"); - s.add(2, "Paul"); - assert_eq!(s.grade(2), to_owned(&["Blair", "James", "Paul"])); -} - -#[test] -#[ignore] -fn test_add_students_to_different_grades() { - let mut s = school::School::new(); - s.add(3, "Chelsea"); - s.add(7, "Logan"); - assert_eq!(s.grades(), vec![3, 7]); - assert_eq!(s.grade(3), to_owned(&["Chelsea"])); - assert_eq!(s.grade(7), to_owned(&["Logan"])); -} diff --git a/exercises/practice/grade-school/tests/grade_school.rs b/exercises/practice/grade-school/tests/grade_school.rs new file mode 100644 index 000000000..d2b1dde81 --- /dev/null +++ b/exercises/practice/grade-school/tests/grade_school.rs @@ -0,0 +1,96 @@ +use grade_school::*; + +#[test] +fn grade_is_empty_if_no_students_in_the_roster() { + let s = School::new(); + assert_eq!(s.grade(1), Vec::::new()) +} + +#[test] +#[ignore] +fn grade_is_empty_if_no_students_in_that_grade() { + let mut s = School::new(); + s.add(2, "Peter"); + s.add(2, "Zoe"); + s.add(2, "Alex"); + s.add(3, "Jim"); + assert_eq!(s.grade(1), Vec::::new()) +} + +#[test] +#[ignore] +fn student_not_added_to_same_grade_more_than_once() { + let mut s = School::new(); + s.add(2, "Blair"); + s.add(2, "James"); + s.add(2, "James"); + s.add(2, "Paul"); + assert_eq!(s.grade(2), vec!["Blair", "James", "Paul"]) +} + +#[test] +#[ignore] +fn student_not_added_to_multiple_grades() { + let mut s = School::new(); + s.add(2, "Blair"); + s.add(2, "James"); + s.add(3, "James"); + s.add(3, "Paul"); + assert_eq!(s.grade(2), vec!["Blair", "James"]) +} + +#[test] +#[ignore] +fn student_not_added_to_other_grade_for_multiple_grades() { + let mut s = School::new(); + s.add(2, "Blair"); + s.add(2, "James"); + s.add(3, "James"); + s.add(3, "Paul"); + assert_eq!(s.grade(3), vec!["Paul"]) +} + +#[test] +#[ignore] +fn students_are_sorted_by_name_in_a_grade() { + let mut s = School::new(); + s.add(5, "Franklin"); + s.add(5, "Bradley"); + s.add(1, "Jeff"); + assert_eq!(s.grade(5), vec!["Bradley", "Franklin"]) +} + +#[test] +#[ignore] +fn grades_for_empty_school() { + let s = School::new(); + assert_eq!(s.grades(), vec![]) +} + +#[test] +#[ignore] +fn grades_for_one_student() { + let mut s = School::new(); + s.add(2, "Aimee"); + assert_eq!(s.grades(), vec![2]) +} + +#[test] +#[ignore] +fn grades_for_several_students_are_sorted() { + let mut s = School::new(); + s.add(2, "Aimee"); + s.add(7, "Logan"); + s.add(4, "Blair"); + assert_eq!(s.grades(), vec![2, 4, 7]) +} + +#[test] +#[ignore] +fn grades_when_several_students_have_the_same_grade() { + let mut s = School::new(); + s.add(2, "Aimee"); + s.add(2, "Logan"); + s.add(2, "Blair"); + assert_eq!(s.grades(), vec![2]) +} diff --git a/exercises/practice/grains/.approaches/bit-shifting/content.md b/exercises/practice/grains/.approaches/bit-shifting/content.md index a4d37f537..c0be0804c 100644 --- a/exercises/practice/grains/.approaches/bit-shifting/content.md +++ b/exercises/practice/grains/.approaches/bit-shifting/content.md @@ -38,12 +38,12 @@ However, we can't do this with a `u64` which has only `64` bits, so we need to u To go back to our two-square example, if we can grow to three squares, then we can shift `1_u128` two positions to the left for binary `100`, which is decimal `4`. -```exercism/note +~~~~exercism/note Note that the type of a binding can be defined by appending it to the binding name. It can optionally be appended using one or more `_` separators between the value and the type. More info can be found in the [Literals](https://doc.rust-lang.org/rust-by-example/types/literals.html) section of [Rust by Example](https://doc.rust-lang.org/rust-by-example/index.html) -``` +~~~~ By subtracting `1` we get `3`, which is the total amount of grains on the two squares. | Square | Binary Value | Decimal Value | diff --git a/exercises/practice/grains/.approaches/pow/content.md b/exercises/practice/grains/.approaches/pow/content.md index f3671fe31..14abea542 100644 --- a/exercises/practice/grains/.approaches/pow/content.md +++ b/exercises/practice/grains/.approaches/pow/content.md @@ -19,12 +19,12 @@ Rust does not have an exponential operator, but uses the [`pow`][pow-u64] method `pow` is nicely suited to the problem, since we start with one grain and keep doubling the number of grains on each successive square. `1` grain is `2u64.pow(0)`, `2` grains is `2u64.pow(1)`, `4` is `2u64.pow(2)`, and so on. -```exercism/note +~~~~exercism/note Note that the type of a binding can be defined by appending it to the binding name. It can optionally be appended using one or more `_` separators between the value and the type. More info can be found in the [Literals](https://doc.rust-lang.org/rust-by-example/types/literals.html) section of [Rust by Example](https://doc.rust-lang.org/rust-by-example/index.html) -``` +~~~~ So, to get the right exponent, we subtract `1` from the square number `s`. diff --git a/exercises/practice/grains/.articles/performance/code/main.rs b/exercises/practice/grains/.articles/performance/code/main.rs index 3da5fcdcd..f62261bb0 100644 --- a/exercises/practice/grains/.articles/performance/code/main.rs +++ b/exercises/practice/grains/.articles/performance/code/main.rs @@ -41,31 +41,31 @@ pub fn total_pow_for() -> u64 { } #[bench] -fn test_square_bit(b: &mut Bencher) { +fn square_bit(b: &mut Bencher) { b.iter(|| square_bit(64)); } #[bench] -fn test_total_bit(b: &mut Bencher) { +fn total_bit(b: &mut Bencher) { b.iter(|| total_bit()); } #[bench] -fn test_square_pow(b: &mut Bencher) { +fn square_pow(b: &mut Bencher) { b.iter(|| square_pow(64)); } #[bench] -fn test_total_pow_u128(b: &mut Bencher) { +fn total_pow_u128(b: &mut Bencher) { b.iter(|| total_pow_u128()); } #[bench] -fn test_total_pow_fold(b: &mut Bencher) { +fn total_pow_fold(b: &mut Bencher) { b.iter(|| total_pow_fold()); } #[bench] -fn test_total_pow_for(b: &mut Bencher) { +fn total_pow_for(b: &mut Bencher) { b.iter(|| total_pow_for()); } diff --git a/exercises/practice/grains/.articles/performance/content.md b/exercises/practice/grains/.articles/performance/content.md index a337edcdd..a29997f72 100644 --- a/exercises/practice/grains/.articles/performance/content.md +++ b/exercises/practice/grains/.articles/performance/content.md @@ -14,12 +14,12 @@ Varieties of `pow` that iterate and call `square` were also benchmarked. To benchmark the approaches, we wrote a [small benchmark application][benchmark-application]. ``` -test test_square_bit ... bench: 0 ns/iter (+/- 0) -test test_total_bit ... bench: 0 ns/iter (+/- 0) -test test_square_pow ... bench: 0 ns/iter (+/- 0) -test test_total_pow_u128 ... bench: 0 ns/iter (+/- 0) -test test_total_pow_fold ... bench: 215 ns/iter (+/- 7) -test test_total_pow_for ... bench: 263 ns/iter (+/- 46) +test square_bit ... bench: 0 ns/iter (+/- 0) +test total_bit ... bench: 0 ns/iter (+/- 0) +test square_pow ... bench: 0 ns/iter (+/- 0) +test total_pow_u128 ... bench: 0 ns/iter (+/- 0) +test total_pow_fold ... bench: 215 ns/iter (+/- 7) +test total_pow_for ... bench: 263 ns/iter (+/- 46) ``` `pow` and bit shifting have the same measurable performance. diff --git a/exercises/practice/grains/.articles/performance/snippet.md b/exercises/practice/grains/.articles/performance/snippet.md index 5dac649d6..0c860e75d 100644 --- a/exercises/practice/grains/.articles/performance/snippet.md +++ b/exercises/practice/grains/.articles/performance/snippet.md @@ -1,8 +1,8 @@ ``` -test test_square_bit ... bench: 0 ns/iter (+/- 0) -test test_total_bit ... bench: 0 ns/iter (+/- 0) -test test_square_pow ... bench: 0 ns/iter (+/- 0) -test test_total_pow_u128 ... bench: 0 ns/iter (+/- 0) -test test_total_pow_fold ... bench: 215 ns/iter (+/- 7) -test test_total_pow_for ... bench: 263 ns/iter (+/- 46) +test square_bit ... bench: 0 ns/iter (+/- 0) +test total_bit ... bench: 0 ns/iter (+/- 0) +test square_pow ... bench: 0 ns/iter (+/- 0) +test total_pow_u128 ... bench: 0 ns/iter (+/- 0) +test total_pow_fold ... bench: 215 ns/iter (+/- 7) +test total_pow_for ... bench: 263 ns/iter (+/- 46) ``` diff --git a/exercises/practice/grains/.docs/instructions.md b/exercises/practice/grains/.docs/instructions.md index 6f235bfc2..f5b752a81 100644 --- a/exercises/practice/grains/.docs/instructions.md +++ b/exercises/practice/grains/.docs/instructions.md @@ -1,28 +1,11 @@ # Instructions -Calculate the number of grains of wheat on a chessboard given that the number -on each square doubles. +Calculate the number of grains of wheat on a chessboard. -There once was a wise servant who saved the life of a prince. The king -promised to pay whatever the servant could dream up. Knowing that the -king loved chess, the servant told the king he would like to have grains -of wheat. One grain on the first square of a chess board, with the number -of grains doubling on each successive square. +A chessboard has 64 squares. +Square 1 has one grain, square 2 has two grains, square 3 has four grains, and so on, doubling each time. -There are 64 squares on a chessboard (where square 1 has one grain, square 2 has two grains, and so on). +Write code that calculates: -Write code that shows: -- how many grains were on a given square, -- the total number of grains on the chessboard, and -- panics with a message of "Square must be between 1 and 64" if the value is not valid - -## 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: - -- Optimize for speed. -- Optimize for readability. - -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? +- the number of grains on a given square +- the total number of grains on the chessboard diff --git a/exercises/practice/grains/.docs/introduction.md b/exercises/practice/grains/.docs/introduction.md new file mode 100644 index 000000000..0df4f46f7 --- /dev/null +++ b/exercises/practice/grains/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +There once was a wise servant who saved the life of a prince. +The king promised to pay whatever the servant could dream up. +Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. +One grain on the first square of a chessboard, with the number of grains doubling on each successive square. diff --git a/exercises/practice/grains/.gitignore b/exercises/practice/grains/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/grains/.gitignore +++ b/exercises/practice/grains/.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/grains/.meta/config.json b/exercises/practice/grains/.meta/config.json index ec7b1e311..1488506a3 100644 --- a/exercises/practice/grains/.meta/config.json +++ b/exercises/practice/grains/.meta/config.json @@ -32,6 +32,6 @@ ] }, "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", - "source": "JavaRanch Cattle Drive, exercise 6", - "source_url": "http://www.javaranch.com/grains.jsp" + "source": "The CodeRanch Cattle Drive, Assignment 6", + "source_url": "https://web.archive.org/web/20240908084142/https://coderanch.com/wiki/718824/Grains" } diff --git a/exercises/practice/grains/.meta/test_template.tera b/exercises/practice/grains/.meta/test_template.tera new file mode 100644 index 000000000..23590743b --- /dev/null +++ b/exercises/practice/grains/.meta/test_template.tera @@ -0,0 +1,22 @@ +use grains::*; + +{% for test in cases.0.cases %} +#[test] +#[ignore] +{% if test.expected is object -%} +#[should_panic] +{% endif -%} +fn {{ test.description | make_ident }}() { + {% if test.expected is number -%} + assert_eq!(square({{ test.input.square }}), {{ test.expected | fmt_num }}); + {% else %} + square({{ test.input.square }}); + {% endif -%} +} +{% endfor -%} + +#[test] +#[ignore] +fn returns_the_total_number_of_grains_on_the_board() { + assert_eq!(grains::total(), 18_446_744_073_709_551_615); +} diff --git a/exercises/practice/grains/.meta/tests.toml b/exercises/practice/grains/.meta/tests.toml index 442dbacd9..f1ef18b9b 100644 --- a/exercises/practice/grains/.meta/tests.toml +++ b/exercises/practice/grains/.meta/tests.toml @@ -28,6 +28,8 @@ description = "square 0 raises an exception" [61974483-eeb2-465e-be54-ca5dde366453] description = "negative square raises an exception" +include = false +comment = "u64 cannot be negative" [a95e4374-f32c-45a7-a10d-ffec475c012f] description = "square greater than 64 raises an exception" diff --git a/exercises/practice/grains/Cargo.toml b/exercises/practice/grains/Cargo.toml index 077822d61..d63ba538f 100644 --- a/exercises/practice/grains/Cargo.toml +++ b/exercises/practice/grains/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "grains" -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] diff --git a/exercises/practice/grains/src/lib.rs b/exercises/practice/grains/src/lib.rs index a4e1da66d..8eeccb68b 100644 --- a/exercises/practice/grains/src/lib.rs +++ b/exercises/practice/grains/src/lib.rs @@ -1,7 +1,7 @@ pub fn square(s: u32) -> u64 { - unimplemented!("grains of rice on square {s}"); + todo!("grains of rice on square {s}"); } pub fn total() -> u64 { - unimplemented!(); + todo!(); } diff --git a/exercises/practice/grains/tests/grains.rs b/exercises/practice/grains/tests/grains.rs index d7eb53a2b..ff7b18b09 100644 --- a/exercises/practice/grains/tests/grains.rs +++ b/exercises/practice/grains/tests/grains.rs @@ -1,72 +1,61 @@ -fn process_square_case(input: u32, expected: u64) { - assert_eq!(grains::square(input), expected); -} +use grains::*; #[test] -/// 1 -fn test_1() { - process_square_case(1, 1); +fn grains_on_square_1() { + assert_eq!(square(1), 1); } #[test] #[ignore] -/// 2 -fn test_2() { - process_square_case(2, 2); +fn grains_on_square_2() { + assert_eq!(square(2), 2); } #[test] #[ignore] -/// 3 -fn test_3() { - process_square_case(3, 4); +fn grains_on_square_3() { + assert_eq!(square(3), 4); } #[test] #[ignore] -/// 4 -fn test_4() { - process_square_case(4, 8); +fn grains_on_square_4() { + assert_eq!(square(4), 8); } -//NEW #[test] #[ignore] -/// 16 -fn test_16() { - process_square_case(16, 32_768); +fn grains_on_square_16() { + assert_eq!(square(16), 32_768); } #[test] #[ignore] -/// 32 -fn test_32() { - process_square_case(32, 2_147_483_648); +fn grains_on_square_32() { + assert_eq!(square(32), 2_147_483_648); } #[test] #[ignore] -/// 64 -fn test_64() { - process_square_case(64, 9_223_372_036_854_775_808); +fn grains_on_square_64() { + assert_eq!(square(64), 9_223_372_036_854_775_808); } #[test] #[ignore] -#[should_panic(expected = "Square must be between 1 and 64")] -fn test_square_0_raises_an_exception() { - grains::square(0); +#[should_panic] +fn square_0_is_invalid() { + square(0); } #[test] #[ignore] -#[should_panic(expected = "Square must be between 1 and 64")] -fn test_square_greater_than_64_raises_an_exception() { - grains::square(65); +#[should_panic] +fn square_greater_than_64_is_invalid() { + square(65); } - #[test] #[ignore] -fn test_returns_the_total_number_of_grains_on_the_board() { +fn returns_the_total_number_of_grains_on_the_board() { assert_eq!(grains::total(), 18_446_744_073_709_551_615); } diff --git a/exercises/practice/grep/.docs/instructions.md b/exercises/practice/grep/.docs/instructions.md index 6c072e66b..004f28acd 100644 --- a/exercises/practice/grep/.docs/instructions.md +++ b/exercises/practice/grep/.docs/instructions.md @@ -1,65 +1,27 @@ # Instructions -Search a file for lines matching a regular expression pattern. Return the line -number and contents of each matching line. +Search files for lines matching a search string and return all matching lines. -The Unix [`grep`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html) command can be used to search for lines in one or more files -that match a user-provided search query (known as the *pattern*). +The Unix [`grep`][grep] command searches files for lines that match a regular expression. +Your task is to implement a simplified `grep` command, which supports searching for fixed strings. The `grep` command takes three arguments: -1. The pattern used to match lines in a file. -2. Zero or more flags to customize the matching behavior. -3. One or more files in which to search for matching lines. +1. The string to search for. +2. Zero or more flags for customizing the command's behavior. +3. One or more files to search in. -Your task is to implement the `grep` function, which should read the contents -of the specified files, find the lines that match the specified pattern -and then output those lines as a single string. Note that the lines should -be output in the order in which they were found, with the first matching line -in the first file being output first. - -As an example, suppose there is a file named "input.txt" with the following contents: - -```text -hello -world -hello again -``` - -If we were to call `grep "hello" input.txt`, the returned string should be: - -```text -hello -hello again -``` +It then reads the contents of the specified files (in the order specified), finds the lines that contain the search string, and finally returns those lines in the order in which they were found. +When searching in multiple files, each matching line is prepended by the file name and a colon (':'). ## Flags -As said earlier, the `grep` command should also support the following flags: - -- `-n` Print the line numbers of each matching line. -- `-l` Print only the names of files that contain at least one matching line. -- `-i` Match line using a case-insensitive comparison. -- `-v` Invert the program -- collect all lines that fail to match the pattern. -- `-x` Only match entire lines, instead of lines that contain a match. - -If we run `grep -n "hello" input.txt`, the `-n` flag will require the matching -lines to be prefixed with its line number: - -```text -1:hello -3:hello again -``` - -And if we run `grep -i "HELLO" input.txt`, we'll do a case-insensitive match, -and the output will be: - -```text -hello -hello again -``` +The `grep` command supports the following flags: -The `grep` command should support multiple flags at once. +- `-n` Prepend the line number and a colon (':') to each line in the output, placing the number after the filename (if present). +- `-l` Output only the names of the files that contain at least one matching line. +- `-i` Match using a case-insensitive comparison. +- `-v` Invert the program -- collect all lines that fail to match. +- `-x` Search only for lines where the search string matches the entire line. -For example, running `grep -l -v "hello" file1.txt file2.txt` should -print the names of files that do not contain the string "hello". +[grep]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html diff --git a/exercises/practice/grep/.gitignore b/exercises/practice/grep/.gitignore index dfd29ee5b..96ef6c0b9 100644 --- a/exercises/practice/grep/.gitignore +++ b/exercises/practice/grep/.gitignore @@ -1,12 +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 - -# Exercise generates some text files, and while it is meant to clean up after itself, -# it might not in the event of panics -*.txt diff --git a/exercises/practice/grep/.meta/Cargo-example.toml b/exercises/practice/grep/.meta/Cargo-example.toml index 367d28094..c11e06f02 100644 --- a/exercises/practice/grep/.meta/Cargo-example.toml +++ b/exercises/practice/grep/.meta/Cargo-example.toml @@ -1,8 +1,11 @@ +[package] +name = "grep" +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] anyhow = "1.0" thiserror = "1.0" - -[package] -edition = "2021" -name = "grep" -version = "1.3.0" diff --git a/exercises/practice/grep/.meta/config.json b/exercises/practice/grep/.meta/config.json index ef0541fc6..76ccc4dfc 100644 --- a/exercises/practice/grep/.meta/config.json +++ b/exercises/practice/grep/.meta/config.json @@ -28,5 +28,5 @@ }, "blurb": "Search a file for lines matching a regular expression pattern. Return the line number and contents of each matching line.", "source": "Conversation with Nate Foster.", - "source_url": "http://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf" + "source_url": "https://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf" } diff --git a/exercises/practice/grep/.meta/example.rs b/exercises/practice/grep/.meta/example.rs index 063d77318..917f39ce6 100644 --- a/exercises/practice/grep/.meta/example.rs +++ b/exercises/practice/grep/.meta/example.rs @@ -88,11 +88,11 @@ pub fn grep(pattern: &str, flags: &Flags, files: &[&str]) -> Result, } if is_multiple_file_search { - result.insert_str(0, &format!("{}:", file_name)) + result.insert_str(0, &format!("{file_name}:")) } if flags.print_file_name { - result = file_name.to_owned().to_owned(); + file_name.to_owned().clone_into(&mut result); } result diff --git a/exercises/practice/grep/.meta/test_template.tera b/exercises/practice/grep/.meta/test_template.tera new file mode 100644 index 000000000..2fce36eeb --- /dev/null +++ b/exercises/practice/grep/.meta/test_template.tera @@ -0,0 +1,148 @@ +use grep::*; + +#[test] +#[ignore] +fn nonexistent_file_returns_error() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = ["0-1-nonexistent-file-returns-error-iliad.txt"]; + assert!(grep(pattern, &flags, &files).is_err()); +} + +#[test] +#[ignore] +fn grep_returns_result() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = Files::new(&["0-2-grep-returns-result-iliad.txt"]); + assert!(grep(pattern, &flags, files.as_ref()).is_ok()); +} + +{% for test_group in cases %} +{% set group_idx = loop.index %} +{% for test in test_group.cases %} +{% set test_idx = loop.index %} + +{# + This prefix is added to the file names to ensure each test case has its own + set of files. This is necessary because every test case creates and deletes + its own files, so the names must be unique to prevent conflicts. +#} +{% set prefix = group_idx ~ "-" ~ test_idx %} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let pattern = {{ test.input.pattern | json_encode() }}; + let flags = Flags::new(&{{ test.input.flags | json_encode() }}); + let files = Files::new(&[ + {% for file in test.input.files -%} + "{{ prefix }}-{{ file }}", + {%- endfor %} + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + {% if test.input.files | length > 1 or test.input.flags is containing("-l") %} + {% for line in test.expected %} + "{{ prefix }}-{{ line }}", + {% endfor %} + {% else %} + {% for line in test.expected %} + "{{ line }}", + {% endfor %} + {% endif %} + ]; + assert_eq!(actual, expected); +} +{% endfor %} +{% endfor %} + +static ILIAD_CONTENT: &str = "\ +Achilles sing, O Goddess! Peleus' son; +His wrath pernicious, who ten thousand woes +Caused to Achaia's host, sent many a soul +Illustrious into Ades premature, +And Heroes gave (so stood the will of Jove) +To dogs and to all ravening fowls a prey, +When fierce dispute had separated once +The noble Chief Achilles from the son +Of Atreus, Agamemnon, King of men. +"; + +static MIDSUMMER_NIGHT_CONTENT: &str = "\ +I do entreat your grace to pardon me. +I know not by what power I am made bold, +Nor how it may concern my modesty, +In such a presence here to plead my thoughts; +But I beseech your grace that I may know +The worst that may befall me in this case, +If I refuse to wed Demetrius. +"; + +static PARADISE_LOST_CONTENT: &str = "\ +Of Mans First Disobedience, and the Fruit +Of that Forbidden Tree, whose mortal tast +Brought Death into the World, and all our woe, +With loss of Eden, till one greater Man +Restore us, and regain the blissful Seat, +Sing Heav'nly Muse, that on the secret top +Of Oreb, or of Sinai, didst inspire +That Shepherd, who first taught the chosen Seed +"; + +/// In The White Night +/// A poem by Alexander Blok(https://en.wikipedia.org/wiki/Alexander_Blok) +/// a Russian poet who is regarded as one of the most important figures of the Silver Age of Russian Poetry +/// You can read the translation here: https://lyricstranslate.com/ru/белой-ночью-месяц-красный-white-night-crimson-crescent.html +static IN_THE_WHITE_NIGHT_CONTENT: &str = " +Белой ночью месяц красный +Выплывает в синеве. +Бродит призрачно-прекрасный, +Отражается в Неве. +Мне провидится и снится +Исполненье тайных дум. +В вас ли доброе таится, +Красный месяц, тихий шум?.. +"; + +struct Files<'a> { + file_names: &'a [&'a str], +} + +impl<'a> Files<'a> { + fn new(file_names: &'a [&'a str]) -> Self { + for file_name in file_names { + let content = if file_name.ends_with("iliad.txt") { + ILIAD_CONTENT + } else if file_name.ends_with("midsummer-night.txt") { + MIDSUMMER_NIGHT_CONTENT + } else if file_name.ends_with("paradise-lost.txt") { + PARADISE_LOST_CONTENT + } else { + IN_THE_WHITE_NIGHT_CONTENT + }; + std::fs::write(file_name, content).unwrap_or_else(|_| { + panic!( + "Error setting up file '{file_name}' with the following content:\n{content}" + ) + }); + } + + Self { file_names } + } +} + +impl Drop for Files<'_> { + fn drop(&mut self) { + for file_name in self.file_names { + std::fs::remove_file(file_name) + .unwrap_or_else(|e| panic!("Could not delete file '{file_name}': {e}")); + } + } +} + +impl<'a> AsRef<[&'a str]> for Files<'a> { + fn as_ref(&self) -> &[&'a str] { + self.file_names + } +} diff --git a/exercises/practice/grep/.meta/tests.toml b/exercises/practice/grep/.meta/tests.toml index be690e975..04c51e71b 100644 --- a/exercises/practice/grep/.meta/tests.toml +++ b/exercises/practice/grep/.meta/tests.toml @@ -1,3 +1,85 @@ -# 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. + +[9049fdfd-53a7-4480-a390-375203837d09] +description = "Test grepping a single file -> One file, one match, no flags" + +[76519cce-98e3-46cd-b287-aac31b1d77d6] +description = "Test grepping a single file -> One file, one match, print line numbers flag" + +[af0b6d3c-e0e8-475e-a112-c0fc10a1eb30] +description = "Test grepping a single file -> One file, one match, case-insensitive flag" + +[ff7af839-d1b8-4856-a53e-99283579b672] +description = "Test grepping a single file -> One file, one match, print file names flag" + +[8625238a-720c-4a16-81f2-924ec8e222cb] +description = "Test grepping a single file -> One file, one match, match entire lines flag" + +[2a6266b3-a60f-475c-a5f5-f5008a717d3e] +description = "Test grepping a single file -> One file, one match, multiple flags" + +[842222da-32e8-4646-89df-0d38220f77a1] +description = "Test grepping a single file -> One file, several matches, no flags" + +[4d84f45f-a1d8-4c2e-a00e-0b292233828c] +description = "Test grepping a single file -> One file, several matches, print line numbers flag" + +[0a483b66-315b-45f5-bc85-3ce353a22539] +description = "Test grepping a single file -> One file, several matches, match entire lines flag" + +[3d2ca86a-edd7-494c-8938-8eeed1c61cfa] +description = "Test grepping a single file -> One file, several matches, case-insensitive flag" + +[1f52001f-f224-4521-9456-11120cad4432] +description = "Test grepping a single file -> One file, several matches, inverted flag" + +[7a6ede7f-7dd5-4364-8bf8-0697c53a09fe] +description = "Test grepping a single file -> One file, no matches, various flags" + +[3d3dfc23-8f2a-4e34-abd6-7b7d140291dc] +description = "Test grepping a single file -> One file, one match, file flag takes precedence over line flag" + +[87b21b24-b788-4d6e-a68b-7afe9ca141fe] +description = "Test grepping a single file -> One file, several matches, inverted and match entire lines flags" + +[ba496a23-6149-41c6-a027-28064ed533e5] +description = "Test grepping multiples files at once -> Multiple files, one match, no flags" + +[4539bd36-6daa-4bc3-8e45-051f69f5aa95] +description = "Test grepping multiples files at once -> Multiple files, several matches, no flags" + +[9fb4cc67-78e2-4761-8e6b-a4b57aba1938] +description = "Test grepping multiples files at once -> Multiple files, several matches, print line numbers flag" + +[aeee1ef3-93c7-4cd5-af10-876f8c9ccc73] +description = "Test grepping multiples files at once -> Multiple files, one match, print file names flag" + +[d69f3606-7d15-4ddf-89ae-01df198e6b6c] +description = "Test grepping multiples files at once -> Multiple files, several matches, case-insensitive flag" + +[82ef739d-6701-4086-b911-007d1a3deb21] +description = "Test grepping multiples files at once -> Multiple files, several matches, inverted flag" + +[77b2eb07-2921-4ea0-8971-7636b44f5d29] +description = "Test grepping multiples files at once -> Multiple files, one match, match entire lines flag" + +[e53a2842-55bb-4078-9bb5-04ac38929989] +description = "Test grepping multiples files at once -> Multiple files, one match, multiple flags" + +[9c4f7f9a-a555-4e32-bb06-4b8f8869b2cb] +description = "Test grepping multiples files at once -> Multiple files, no matches, various flags" + +[ba5a540d-bffd-481b-bd0c-d9a30f225e01] +description = "Test grepping multiples files at once -> Multiple files, several matches, file flag takes precedence over line number flag" + +[ff406330-2f0b-4b17-9ee4-4b71c31dd6d2] +description = "Test grepping multiples files at once -> Multiple files, several matches, inverted and match entire lines flags" diff --git a/exercises/practice/grep/Cargo.toml b/exercises/practice/grep/Cargo.toml index fd0a6b754..90779a2c0 100644 --- a/exercises/practice/grep/Cargo.toml +++ b/exercises/practice/grep/Cargo.toml @@ -1,7 +1,10 @@ -[dependencies] -anyhow = "1.0" - [package] -edition = "2021" name = "grep" -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] +anyhow = "1.0" diff --git a/exercises/practice/grep/src/lib.rs b/exercises/practice/grep/src/lib.rs index 25b636a1e..f64031266 100644 --- a/exercises/practice/grep/src/lib.rs +++ b/exercises/practice/grep/src/lib.rs @@ -5,26 +5,20 @@ use anyhow::Error; /// both more convenient and more idiomatic to contain runtime configuration in /// a dedicated struct. Therefore, we suggest that you do so in this exercise. /// -/// In the real world, it's common to use crates such as [`clap`] or -/// [`structopt`] to handle argument parsing, and of course doing so is -/// permitted in this exercise as well, though it may be somewhat overkill. -/// -/// [`clap`]: https://crates.io/crates/clap /// [`std::env::args`]: https://doc.rust-lang.org/std/env/fn.args.html -/// [`structopt`]: https://crates.io/crates/structopt #[derive(Debug)] pub struct Flags; impl Flags { pub fn new(flags: &[&str]) -> Self { - unimplemented!( + todo!( "Given the flags {flags:?} implement your own 'Flags' struct to handle flags-related logic" ); } } pub fn grep(pattern: &str, flags: &Flags, files: &[&str]) -> Result, Error> { - unimplemented!( + todo!( "Search the files '{files:?}' for '{pattern}' pattern and save the matches in a vector. Your search logic should be aware of the given flags '{flags:?}'" ); } diff --git a/exercises/practice/grep/tests/grep.rs b/exercises/practice/grep/tests/grep.rs index 398c2d8a5..1154d2d69 100644 --- a/exercises/practice/grep/tests/grep.rs +++ b/exercises/practice/grep/tests/grep.rs @@ -1,8 +1,417 @@ -use grep::{grep, Flags}; +use grep::*; -use std::fs; +#[test] +fn nonexistent_file_returns_error() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = ["0-1-nonexistent-file-returns-error-iliad.txt"]; + assert!(grep(pattern, &flags, &files).is_err()); +} + +#[test] +#[ignore] +fn grep_returns_result() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = Files::new(&["0-2-grep-returns-result-iliad.txt"]); + assert!(grep(pattern, &flags, files.as_ref()).is_ok()); +} + +#[test] +#[ignore] +fn one_file_one_match_no_flags() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = Files::new(&["1-1-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["Of Atreus, Agamemnon, King of men."]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_print_line_numbers_flag() { + let pattern = "Forbidden"; + let flags = Flags::new(&["-n"]); + let files = Files::new(&["1-2-paradise-lost.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2:Of that Forbidden Tree, whose mortal tast"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_case_insensitive_flag() { + let pattern = "FORBIDDEN"; + let flags = Flags::new(&["-i"]); + let files = Files::new(&["1-3-paradise-lost.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["Of that Forbidden Tree, whose mortal tast"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_print_file_names_flag() { + let pattern = "Forbidden"; + let flags = Flags::new(&["-l"]); + let files = Files::new(&["1-4-paradise-lost.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["1-4-paradise-lost.txt"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_match_entire_lines_flag() { + let pattern = "With loss of Eden, till one greater Man"; + let flags = Flags::new(&["-x"]); + let files = Files::new(&["1-5-paradise-lost.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["With loss of Eden, till one greater Man"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_multiple_flags() { + let pattern = "OF ATREUS, Agamemnon, KIng of MEN."; + let flags = Flags::new(&["-n", "-i", "-x"]); + let files = Files::new(&["1-6-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["9:Of Atreus, Agamemnon, King of men."]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_no_flags() { + let pattern = "may"; + let flags = Flags::new(&[]); + let files = Files::new(&["1-7-midsummer-night.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "Nor how it may concern my modesty,", + "But I beseech your grace that I may know", + "The worst that may befall me in this case,", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_print_line_numbers_flag() { + let pattern = "may"; + let flags = Flags::new(&["-n"]); + let files = Files::new(&["1-8-midsummer-night.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "3:Nor how it may concern my modesty,", + "5:But I beseech your grace that I may know", + "6:The worst that may befall me in this case,", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_match_entire_lines_flag() { + let pattern = "may"; + let flags = Flags::new(&["-x"]); + let files = Files::new(&["1-9-midsummer-night.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_case_insensitive_flag() { + let pattern = "ACHILLES"; + let flags = Flags::new(&["-i"]); + let files = Files::new(&["1-10-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "Achilles sing, O Goddess! Peleus' son;", + "The noble Chief Achilles from the son", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_inverted_flag() { + let pattern = "Of"; + let flags = Flags::new(&["-v"]); + let files = Files::new(&["1-11-paradise-lost.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "Brought Death into the World, and all our woe,", + "With loss of Eden, till one greater Man", + "Restore us, and regain the blissful Seat,", + "Sing Heav'nly Muse, that on the secret top", + "That Shepherd, who first taught the chosen Seed", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_no_matches_various_flags() { + let pattern = "Gandalf"; + let flags = Flags::new(&["-n", "-l", "-x", "-i"]); + let files = Files::new(&["1-12-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_file_flag_takes_precedence_over_line_flag() { + let pattern = "ten"; + let flags = Flags::new(&["-n", "-l"]); + let files = Files::new(&["1-13-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["1-13-iliad.txt"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_inverted_and_match_entire_lines_flags() { + let pattern = "Illustrious into Ades premature,"; + let flags = Flags::new(&["-x", "-v"]); + let files = Files::new(&["1-14-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "Achilles sing, O Goddess! Peleus' son;", + "His wrath pernicious, who ten thousand woes", + "Caused to Achaia's host, sent many a soul", + "And Heroes gave (so stood the will of Jove)", + "To dogs and to all ravening fowls a prey,", + "When fierce dispute had separated once", + "The noble Chief Achilles from the son", + "Of Atreus, Agamemnon, King of men.", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_one_match_no_flags() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = Files::new(&[ + "2-1-iliad.txt", + "2-1-midsummer-night.txt", + "2-1-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2-1-iliad.txt:Of Atreus, Agamemnon, King of men."]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_several_matches_no_flags() { + let pattern = "may"; + let flags = Flags::new(&[]); + let files = Files::new(&[ + "2-2-iliad.txt", + "2-2-midsummer-night.txt", + "2-2-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "2-2-midsummer-night.txt:Nor how it may concern my modesty,", + "2-2-midsummer-night.txt:But I beseech your grace that I may know", + "2-2-midsummer-night.txt:The worst that may befall me in this case,", + ]; + assert_eq!(actual, expected); +} -static ILIAD_CONTENT: &str = "Achilles sing, O Goddess! Peleus' son; +#[test] +#[ignore] +fn multiple_files_several_matches_print_line_numbers_flag() { + let pattern = "that"; + let flags = Flags::new(&["-n"]); + let files = Files::new(&[ + "2-3-iliad.txt", + "2-3-midsummer-night.txt", + "2-3-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "2-3-midsummer-night.txt:5:But I beseech your grace that I may know", + "2-3-midsummer-night.txt:6:The worst that may befall me in this case,", + "2-3-paradise-lost.txt:2:Of that Forbidden Tree, whose mortal tast", + "2-3-paradise-lost.txt:6:Sing Heav'nly Muse, that on the secret top", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_one_match_print_file_names_flag() { + let pattern = "who"; + let flags = Flags::new(&["-l"]); + let files = Files::new(&[ + "2-4-iliad.txt", + "2-4-midsummer-night.txt", + "2-4-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2-4-iliad.txt", "2-4-paradise-lost.txt"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_several_matches_case_insensitive_flag() { + let pattern = "TO"; + let flags = Flags::new(&["-i"]); + let files = Files::new(&[ + "2-5-iliad.txt", + "2-5-midsummer-night.txt", + "2-5-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "2-5-iliad.txt:Caused to Achaia's host, sent many a soul", + "2-5-iliad.txt:Illustrious into Ades premature,", + "2-5-iliad.txt:And Heroes gave (so stood the will of Jove)", + "2-5-iliad.txt:To dogs and to all ravening fowls a prey,", + "2-5-midsummer-night.txt:I do entreat your grace to pardon me.", + "2-5-midsummer-night.txt:In such a presence here to plead my thoughts;", + "2-5-midsummer-night.txt:If I refuse to wed Demetrius.", + "2-5-paradise-lost.txt:Brought Death into the World, and all our woe,", + "2-5-paradise-lost.txt:Restore us, and regain the blissful Seat,", + "2-5-paradise-lost.txt:Sing Heav'nly Muse, that on the secret top", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_several_matches_inverted_flag() { + let pattern = "a"; + let flags = Flags::new(&["-v"]); + let files = Files::new(&[ + "2-6-iliad.txt", + "2-6-midsummer-night.txt", + "2-6-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "2-6-iliad.txt:Achilles sing, O Goddess! Peleus' son;", + "2-6-iliad.txt:The noble Chief Achilles from the son", + "2-6-midsummer-night.txt:If I refuse to wed Demetrius.", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_one_match_match_entire_lines_flag() { + let pattern = "But I beseech your grace that I may know"; + let flags = Flags::new(&["-x"]); + let files = Files::new(&[ + "2-7-iliad.txt", + "2-7-midsummer-night.txt", + "2-7-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2-7-midsummer-night.txt:But I beseech your grace that I may know"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_one_match_multiple_flags() { + let pattern = "WITH LOSS OF EDEN, TILL ONE GREATER MAN"; + let flags = Flags::new(&["-n", "-i", "-x"]); + let files = Files::new(&[ + "2-8-iliad.txt", + "2-8-midsummer-night.txt", + "2-8-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2-8-paradise-lost.txt:4:With loss of Eden, till one greater Man"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_no_matches_various_flags() { + let pattern = "Frodo"; + let flags = Flags::new(&["-n", "-l", "-x", "-i"]); + let files = Files::new(&[ + "2-9-iliad.txt", + "2-9-midsummer-night.txt", + "2-9-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_several_matches_file_flag_takes_precedence_over_line_number_flag() { + let pattern = "who"; + let flags = Flags::new(&["-n", "-l"]); + let files = Files::new(&[ + "2-10-iliad.txt", + "2-10-midsummer-night.txt", + "2-10-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2-10-iliad.txt", "2-10-paradise-lost.txt"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_several_matches_inverted_and_match_entire_lines_flags() { + let pattern = "Illustrious into Ades premature,"; + let flags = Flags::new(&["-x", "-v"]); + let files = Files::new(&[ + "2-11-iliad.txt", + "2-11-midsummer-night.txt", + "2-11-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "2-11-iliad.txt:Achilles sing, O Goddess! Peleus' son;", + "2-11-iliad.txt:His wrath pernicious, who ten thousand woes", + "2-11-iliad.txt:Caused to Achaia's host, sent many a soul", + "2-11-iliad.txt:And Heroes gave (so stood the will of Jove)", + "2-11-iliad.txt:To dogs and to all ravening fowls a prey,", + "2-11-iliad.txt:When fierce dispute had separated once", + "2-11-iliad.txt:The noble Chief Achilles from the son", + "2-11-iliad.txt:Of Atreus, Agamemnon, King of men.", + "2-11-midsummer-night.txt:I do entreat your grace to pardon me.", + "2-11-midsummer-night.txt:I know not by what power I am made bold,", + "2-11-midsummer-night.txt:Nor how it may concern my modesty,", + "2-11-midsummer-night.txt:In such a presence here to plead my thoughts;", + "2-11-midsummer-night.txt:But I beseech your grace that I may know", + "2-11-midsummer-night.txt:The worst that may befall me in this case,", + "2-11-midsummer-night.txt:If I refuse to wed Demetrius.", + "2-11-paradise-lost.txt:Of Mans First Disobedience, and the Fruit", + "2-11-paradise-lost.txt:Of that Forbidden Tree, whose mortal tast", + "2-11-paradise-lost.txt:Brought Death into the World, and all our woe,", + "2-11-paradise-lost.txt:With loss of Eden, till one greater Man", + "2-11-paradise-lost.txt:Restore us, and regain the blissful Seat,", + "2-11-paradise-lost.txt:Sing Heav'nly Muse, that on the secret top", + "2-11-paradise-lost.txt:Of Oreb, or of Sinai, didst inspire", + "2-11-paradise-lost.txt:That Shepherd, who first taught the chosen Seed", + ]; + assert_eq!(actual, expected); +} + +static ILIAD_CONTENT: &str = "\ +Achilles sing, O Goddess! Peleus' son; His wrath pernicious, who ten thousand woes Caused to Achaia's host, sent many a soul Illustrious into Ades premature, @@ -13,7 +422,8 @@ The noble Chief Achilles from the son Of Atreus, Agamemnon, King of men. "; -static MIDSUMMER_NIGHT_CONTENT: &str = "I do entreat your grace to pardon me. +static MIDSUMMER_NIGHT_CONTENT: &str = "\ +I do entreat your grace to pardon me. I know not by what power I am made bold, Nor how it may concern my modesty, In such a presence here to plead my thoughts; @@ -22,7 +432,8 @@ The worst that may befall me in this case, If I refuse to wed Demetrius. "; -static PARADISE_LOST_CONTENT: &str = "Of Mans First Disobedience, and the Fruit +static PARADISE_LOST_CONTENT: &str = "\ +Of Mans First Disobedience, and the Fruit Of that Forbidden Tree, whose mortal tast Brought Death into the World, and all our woe, With loss of Eden, till one greater Man @@ -36,7 +447,8 @@ That Shepherd, who first taught the chosen Seed /// A poem by Alexander Blok(https://en.wikipedia.org/wiki/Alexander_Blok) /// a Russian poet who is regarded as one of the most important figures of the Silver Age of Russian Poetry /// You can read the translation here: https://lyricstranslate.com/ru/белой-ночью-месяц-красный-white-night-crimson-crescent.html -static IN_THE_WHITE_NIGHT_CONTENT: &str = "Белой ночью месяц красный +static IN_THE_WHITE_NIGHT_CONTENT: &str = " +Белой ночью месяц красный Выплывает в синеве. Бродит призрачно-прекрасный, Отражается в Неве. @@ -46,512 +458,42 @@ static IN_THE_WHITE_NIGHT_CONTENT: &str = "Белой ночью месяц кр Красный месяц, тихий шум?.. "; -struct Fixture<'a> { +struct Files<'a> { file_names: &'a [&'a str], } -impl<'a> Fixture<'a> { +impl<'a> Files<'a> { fn new(file_names: &'a [&'a str]) -> Self { - Fixture { file_names } - } + for file_name in file_names { + let content = if file_name.ends_with("iliad.txt") { + ILIAD_CONTENT + } else if file_name.ends_with("midsummer-night.txt") { + MIDSUMMER_NIGHT_CONTENT + } else if file_name.ends_with("paradise-lost.txt") { + PARADISE_LOST_CONTENT + } else { + IN_THE_WHITE_NIGHT_CONTENT + }; + std::fs::write(file_name, content).unwrap_or_else(|_| { + panic!("Error setting up file '{file_name}' with the following content:\n{content}") + }); + } - fn set_up(&self) { - let file_name_content_pairs = self - .file_names - .iter() - .cloned() - .map(|file_name| { - if file_name.ends_with("iliad.txt") { - (file_name, ILIAD_CONTENT) - } else if file_name.ends_with("midsummer_night.txt") { - (file_name, MIDSUMMER_NIGHT_CONTENT) - } else if file_name.ends_with("paradise_lost.txt") { - (file_name, PARADISE_LOST_CONTENT) - } else { - (file_name, IN_THE_WHITE_NIGHT_CONTENT) - } - }) - .collect::>(); - - set_up_files(&file_name_content_pairs); + Self { file_names } } } -impl<'a> Drop for Fixture<'a> { +impl Drop for Files<'_> { fn drop(&mut self) { - tear_down_files(self.file_names); - } -} - -fn set_up_files(files: &[(&str, &str)]) { - for (file_name, file_content) in files { - fs::write(file_name, file_content).unwrap_or_else(|_| { - panic!( - "Error setting up file '{file_name}' with the following content:\n{file_content}" - ) - }); - } -} - -fn tear_down_files(files: &[&str]) { - for file_name in files { - fs::remove_file(file_name) - .unwrap_or_else(|_| panic!("Could not delete file '{file_name}'")); - } -} - -/// This macro is here so that every test case had its own set of files to be used in test. -/// The approach is to create required files for every test case and to append test name to the -/// file names (so for test with a name 'test_one_file_one_match_no_flags' and a required file -/// 'iliad.txt' there would be created a file with a name -/// 'test_one_file_one_match_no_flags_iliad.txt'). -/// This allows us to create files for every test case with no intersection between them. -/// -/// A better way would be to create required set of files at the start of tests run and to -/// delete them after every test is finished, but there is no trivial way to create such -/// a test fixture in standard Rust, and Exercism restricts the usage of external dependencies -/// in test files. Therefore the above approach is chosen. -/// -/// If you have an idea about a better way to implement test fixture for this exercise, -/// please submit PR to the Rust Exercism track: https://github.com/exercism/rust -macro_rules! set_up_test_case { - ($(#[$flag:meta])+ $test_case_name:ident(pattern=$pattern:expr, flags=[$($grep_flag:expr),*], files=[$($file:expr),+], expected=[$($expected:expr),*])) => { - $(#[$flag])+ - fn $test_case_name() { - let pattern = $pattern; - - let flags = vec![$($grep_flag),*]; - - let files = vec![$(concat!(stringify!($test_case_name), "_" , $file)),+]; - - let expected = vec![$($expected),*]; - - process_grep_case(pattern, &flags, &files, &expected); - } - }; - ($(#[$flag:meta])+ $test_case_name:ident(pattern=$pattern:expr, flags=[$($grep_flag:expr),*], files=[$($file:expr),+], prefix_expected=[$($expected:expr),*])) => { - $(#[$flag])+ - fn $test_case_name() { - let pattern = $pattern; - - let flags = vec![$($grep_flag),*]; - - let files = vec![$(concat!(stringify!($test_case_name), "_" , $file)),+]; - - let expected = vec![$(concat!(stringify!($test_case_name), "_", $expected)),*]; - - process_grep_case(pattern, &flags, &files, &expected); + for file_name in self.file_names { + std::fs::remove_file(file_name) + .unwrap_or_else(|e| panic!("Could not delete file '{file_name}': {e}")); } - } } -fn process_grep_case(pattern: &str, flags: &[&str], files: &[&str], expected: &[&str]) { - let test_fixture = Fixture::new(files); - - test_fixture.set_up(); - - let flags = Flags::new(flags); - - let grep_result = grep(pattern, &flags, files).unwrap(); - - assert_eq!(grep_result, expected); -} - -// Test returning a Result - -#[test] -fn test_nonexistent_file_returns_error() { - let pattern = "Agamemnon"; - - let flags = Flags::new(&[]); - - let files = vec!["test_nonexistent_file_returns_error_iliad.txt"]; - - assert!(grep(pattern, &flags, &files).is_err()); -} - -#[test] -#[ignore] -fn test_grep_returns_result() { - let pattern = "Agamemnon"; - - let flags = Flags::new(&[]); - - let files = vec!["test_grep_returns_result_iliad.txt"]; - - let test_fixture = Fixture::new(&files); - - test_fixture.set_up(); - - assert!(grep(pattern, &flags, &files).is_ok()); +impl<'a> AsRef<[&'a str]> for Files<'a> { + fn as_ref(&self) -> &[&'a str] { + self.file_names + } } - -// Test grepping a single file - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_one_match_no_flags( - pattern = "Agamemnon", - flags = [], - files = ["iliad.txt"], - expected = ["Of Atreus, Agamemnon, King of men."] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_one_match_print_line_numbers_flag( - pattern = "Forbidden", - flags = ["-n"], - files = ["paradise_lost.txt"], - expected = ["2:Of that Forbidden Tree, whose mortal tast"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_one_match_caseinsensitive_flag( - pattern = "FORBIDDEN", - flags = ["-i"], - files = ["paradise_lost.txt"], - expected = ["Of that Forbidden Tree, whose mortal tast"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_one_match_print_file_names_flag( - pattern = "Forbidden", - flags = ["-l"], - files = ["paradise_lost.txt"], - prefix_expected = ["paradise_lost.txt"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_one_match_match_entire_lines_flag( - pattern = "With loss of Eden, till one greater Man", - flags = ["-x"], - files = ["paradise_lost.txt"], - expected = ["With loss of Eden, till one greater Man"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_one_match_multiple_flags( - pattern = "OF ATREUS, Agamemnon, KIng of MEN.", - flags = ["-x", "-i", "-n"], - files = ["iliad.txt"], - expected = ["9:Of Atreus, Agamemnon, King of men."] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_several_matches_no_flags( - pattern = "may", - flags = [], - files = ["midsummer_night.txt"], - expected = [ - "Nor how it may concern my modesty,", - "But I beseech your grace that I may know", - "The worst that may befall me in this case," - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_several_matches_print_line_numbers_flag( - pattern = "may", - flags = ["-n"], - files = ["midsummer_night.txt"], - expected = [ - "3:Nor how it may concern my modesty,", - "5:But I beseech your grace that I may know", - "6:The worst that may befall me in this case," - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_several_matches_match_entire_lines_flag( - pattern = "may", - flags = ["-x"], - files = ["midsummer_night.txt"], - expected = [] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_several_matches_caseinsensitive_flag( - pattern = "ACHILLES", - flags = ["-i"], - files = ["iliad.txt"], - expected = [ - "Achilles sing, O Goddess! Peleus' son;", - "The noble Chief Achilles from the son" - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_several_matches_inverted_flag( - pattern = "Of", - flags = ["-v"], - files = ["paradise_lost.txt"], - expected = [ - "Brought Death into the World, and all our woe,", - "With loss of Eden, till one greater Man", - "Restore us, and regain the blissful Seat,", - "Sing Heav'nly Muse, that on the secret top", - "That Shepherd, who first taught the chosen Seed" - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_no_matches_various_flags( - pattern = "Gandalf", - flags = ["-n", "-l", "-x", "-i"], - files = ["iliad.txt"], - expected = [] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_several_matches_inverted_and_match_entire_lines_flags( - pattern = "Illustrious into Ades premature,", - flags = ["-x", "-v"], - files = ["iliad.txt"], - expected = [ - "Achilles sing, O Goddess! Peleus' son;", - "His wrath pernicious, who ten thousand woes", - "Caused to Achaia's host, sent many a soul", - "And Heroes gave (so stood the will of Jove)", - "To dogs and to all ravening fowls a prey,", - "When fierce dispute had separated once", - "The noble Chief Achilles from the son", - "Of Atreus, Agamemnon, King of men." - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_one_file_one_match_file_flag_takes_precedence_over_line_flag( - pattern = "ten", - flags = ["-n", "-l"], - files = ["iliad.txt"], - prefix_expected = ["iliad.txt"] - ) -); - -// Test grepping multiples files at once - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_one_match_no_flags( - pattern = "Agamemnon", - flags = [], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = ["iliad.txt:Of Atreus, Agamemnon, King of men."] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_several_matches_no_flags( - pattern = "may", - flags = [], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = [ - "midsummer_night.txt:Nor how it may concern my modesty,", - "midsummer_night.txt:But I beseech your grace that I may know", - "midsummer_night.txt:The worst that may befall me in this case," - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_several_matches_print_line_numbers_flag( - pattern = "that", - flags = ["-n"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = [ - "midsummer_night.txt:5:But I beseech your grace that I may know", - "midsummer_night.txt:6:The worst that may befall me in this case,", - "paradise_lost.txt:2:Of that Forbidden Tree, whose mortal tast", - "paradise_lost.txt:6:Sing Heav'nly Muse, that on the secret top" - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_one_match_print_file_names_flag( - pattern = "who", - flags = ["-l"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = ["iliad.txt", "paradise_lost.txt"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_several_matches_caseinsensitive_flag( - pattern = "TO", - flags = ["-i"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = [ - "iliad.txt:Caused to Achaia's host, sent many a soul", - "iliad.txt:Illustrious into Ades premature,", - "iliad.txt:And Heroes gave (so stood the will of Jove)", - "iliad.txt:To dogs and to all ravening fowls a prey,", - "midsummer_night.txt:I do entreat your grace to pardon me.", - "midsummer_night.txt:In such a presence here to plead my thoughts;", - "midsummer_night.txt:If I refuse to wed Demetrius.", - "paradise_lost.txt:Brought Death into the World, and all our woe,", - "paradise_lost.txt:Restore us, and regain the blissful Seat,", - "paradise_lost.txt:Sing Heav'nly Muse, that on the secret top" - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_several_matches_caseinsensitive_flag_utf8( - pattern = "В", // This letter stands for cyrillic 'Ve' and not latin 'B'. Therefore there should be no matches from paradise_lost.txt - flags = ["-i"], - files = ["paradise_lost.txt", "in_the_white_night.txt"], - prefix_expected = [ - "in_the_white_night.txt:Выплывает в синеве.", - "in_the_white_night.txt:Отражается в Неве.", - "in_the_white_night.txt:Мне провидится и снится", - "in_the_white_night.txt:В вас ли доброе таится," - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_several_matches_inverted_flag( - pattern = "a", - flags = ["-v"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = [ - "iliad.txt:Achilles sing, O Goddess! Peleus' son;", - "iliad.txt:The noble Chief Achilles from the son", - "midsummer_night.txt:If I refuse to wed Demetrius." - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_one_match_match_entire_lines_flag( - pattern = "But I beseech your grace that I may know", - flags = ["-x"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = ["midsummer_night.txt:But I beseech your grace that I may know"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_one_match_multiple_flags( - pattern = "WITH LOSS OF EDEN, TILL ONE GREATER MAN", - flags = ["-n", "-i", "-x"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = ["paradise_lost.txt:4:With loss of Eden, till one greater Man"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_no_matches_various_flags( - pattern = "Frodo", - flags = ["-n", "-i", "-x", "-l"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - expected = [] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_several_matches_file_flag_takes_precedence_over_line_number_flag( - pattern = "who", - flags = ["-n", "-l"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = ["iliad.txt", "paradise_lost.txt"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - test_multiple_files_several_matches_inverted_and_match_entire_lines_flags( - pattern = "Illustrious into Ades premature,", - flags = ["-x", "-v"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = [ - "iliad.txt:Achilles sing, O Goddess! Peleus' son;", - "iliad.txt:His wrath pernicious, who ten thousand woes", - "iliad.txt:Caused to Achaia's host, sent many a soul", - "iliad.txt:And Heroes gave (so stood the will of Jove)", - "iliad.txt:To dogs and to all ravening fowls a prey,", - "iliad.txt:When fierce dispute had separated once", - "iliad.txt:The noble Chief Achilles from the son", - "iliad.txt:Of Atreus, Agamemnon, King of men.", - "midsummer_night.txt:I do entreat your grace to pardon me.", - "midsummer_night.txt:I know not by what power I am made bold,", - "midsummer_night.txt:Nor how it may concern my modesty,", - "midsummer_night.txt:In such a presence here to plead my thoughts;", - "midsummer_night.txt:But I beseech your grace that I may know", - "midsummer_night.txt:The worst that may befall me in this case,", - "midsummer_night.txt:If I refuse to wed Demetrius.", - "paradise_lost.txt:Of Mans First Disobedience, and the Fruit", - "paradise_lost.txt:Of that Forbidden Tree, whose mortal tast", - "paradise_lost.txt:Brought Death into the World, and all our woe,", - "paradise_lost.txt:With loss of Eden, till one greater Man", - "paradise_lost.txt:Restore us, and regain the blissful Seat,", - "paradise_lost.txt:Sing Heav'nly Muse, that on the secret top", - "paradise_lost.txt:Of Oreb, or of Sinai, didst inspire", - "paradise_lost.txt:That Shepherd, who first taught the chosen Seed" - ] - ) -); diff --git a/exercises/practice/hamming/.docs/instructions.md b/exercises/practice/hamming/.docs/instructions.md index 56c5696de..8f47a179e 100644 --- a/exercises/practice/hamming/.docs/instructions.md +++ b/exercises/practice/hamming/.docs/instructions.md @@ -1,24 +1,16 @@ # Instructions -Calculate the Hamming Distance between two DNA strands. +Calculate the Hamming distance between two DNA strands. -Your body is made up of cells that contain DNA. Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! - -When cells divide, their DNA replicates too. Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. This is known as the "Hamming Distance". - -We read DNA using the letters C,A,G and T. Two strands might look like this: +We read DNA using the letters C, A, G and T. +Two strands might look like this: GAGCCTACTAACGGGAT CATCGTAATGACGGCCT ^ ^ ^ ^ ^ ^^ -They have 7 differences, and therefore the Hamming Distance is 7. - -The Hamming Distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :) +They have 7 differences, and therefore the Hamming distance is 7. -# Implementation notes +## Implementation notes -The Hamming distance is only defined for sequences of equal length, so -an attempt to calculate it between sequences of different lengths should -not work. The general handling of this situation (e.g., raising an -exception vs returning a special value) may differ between languages. +The Hamming distance is only defined for sequences of equal length, so an attempt to calculate it between sequences of different lengths should not work. diff --git a/exercises/practice/hamming/.docs/introduction.md b/exercises/practice/hamming/.docs/introduction.md new file mode 100644 index 000000000..8419bf479 --- /dev/null +++ b/exercises/practice/hamming/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +Your body is made up of cells that contain DNA. +Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. +In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! + +When cells divide, their DNA replicates too. +Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. +If we compare two strands of DNA and count the differences between them, we can see how many mistakes occurred. +This is known as the "Hamming distance". + +The Hamming distance is useful in many areas of science, not just biology, so it's a nice phrase to be familiar with :) diff --git a/exercises/practice/hamming/.gitignore b/exercises/practice/hamming/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/hamming/.gitignore +++ b/exercises/practice/hamming/.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/hamming/.meta/config.json b/exercises/practice/hamming/.meta/config.json index 06811aa5e..4d4f72e47 100644 --- a/exercises/practice/hamming/.meta/config.json +++ b/exercises/practice/hamming/.meta/config.json @@ -35,7 +35,7 @@ ".meta/example.rs" ] }, - "blurb": "Calculate the Hamming difference between two DNA strands.", + "blurb": "Calculate the Hamming distance between two DNA strands.", "source": "The Calculating Point Mutations problem at Rosalind", - "source_url": "http://rosalind.info/problems/hamm/" + "source_url": "https://rosalind.info/problems/hamm/" } diff --git a/exercises/practice/hamming/.meta/test_template.tera b/exercises/practice/hamming/.meta/test_template.tera new file mode 100644 index 000000000..3bb6cc252 --- /dev/null +++ b/exercises/practice/hamming/.meta/test_template.tera @@ -0,0 +1,16 @@ +use hamming::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert_eq!( + hamming_distance({{ test.input.strand1 | json_encode() }}, {{ test.input.strand2 | json_encode() }}), + {% if test.expected is object -%} + None + {% else -%} + Some({{ test.expected }}) + {% endif -%} + ); +} +{% endfor -%} diff --git a/exercises/practice/hamming/.meta/tests.toml b/exercises/practice/hamming/.meta/tests.toml index be690e975..5dc17ed4e 100644 --- a/exercises/practice/hamming/.meta/tests.toml +++ b/exercises/practice/hamming/.meta/tests.toml @@ -1,3 +1,67 @@ -# 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. + +[f6dcb64f-03b0-4b60-81b1-3c9dbf47e887] +description = "empty strands" + +[54681314-eee2-439a-9db0-b0636c656156] +description = "single letter identical strands" + +[294479a3-a4c8-478f-8d63-6209815a827b] +description = "single letter different strands" + +[9aed5f34-5693-4344-9b31-40c692fb5592] +description = "long identical strands" + +[cd2273a5-c576-46c8-a52b-dee251c3e6e5] +description = "long different strands" + +[919f8ef0-b767-4d1b-8516-6379d07fcb28] +description = "disallow first strand longer" +include = false + +[b9228bb1-465f-4141-b40f-1f99812de5a8] +description = "disallow first strand longer" +reimplements = "919f8ef0-b767-4d1b-8516-6379d07fcb28" + +[8a2d4ed0-ead5-4fdd-924d-27c4cf56e60e] +description = "disallow second strand longer" +include = false + +[dab38838-26bb-4fff-acbe-3b0a9bfeba2d] +description = "disallow second strand longer" +reimplements = "8a2d4ed0-ead5-4fdd-924d-27c4cf56e60e" + +[5dce058b-28d4-4ca7-aa64-adfe4e17784c] +description = "disallow left empty strand" +include = false + +[db92e77e-7c72-499d-8fe6-9354d2bfd504] +description = "disallow left empty strand" +include = false +reimplements = "5dce058b-28d4-4ca7-aa64-adfe4e17784c" + +[b764d47c-83ff-4de2-ab10-6cfe4b15c0f3] +description = "disallow empty first strand" +reimplements = "db92e77e-7c72-499d-8fe6-9354d2bfd504" + +[38826d4b-16fb-4639-ac3e-ba027dec8b5f] +description = "disallow right empty strand" +include = false + +[920cd6e3-18f4-4143-b6b8-74270bb8f8a3] +description = "disallow right empty strand" +include = false +reimplements = "38826d4b-16fb-4639-ac3e-ba027dec8b5f" + +[9ab9262f-3521-4191-81f5-0ed184a5aa89] +description = "disallow empty second strand" +reimplements = "920cd6e3-18f4-4143-b6b8-74270bb8f8a3" diff --git a/exercises/practice/hamming/Cargo.toml b/exercises/practice/hamming/Cargo.toml index 0217a7a93..085746843 100644 --- a/exercises/practice/hamming/Cargo.toml +++ b/exercises/practice/hamming/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "hamming" -version = "2.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] diff --git a/exercises/practice/hamming/src/lib.rs b/exercises/practice/hamming/src/lib.rs index 021efd9bc..391af3c52 100644 --- a/exercises/practice/hamming/src/lib.rs +++ b/exercises/practice/hamming/src/lib.rs @@ -1,5 +1,5 @@ /// Return the Hamming distance between the strings, /// or None if the lengths are mismatched. pub fn hamming_distance(s1: &str, s2: &str) -> Option { - unimplemented!("What is the Hamming Distance between {s1} and {s2}"); + todo!("What is the Hamming Distance between {s1} and {s2}"); } diff --git a/exercises/practice/hamming/tests/hamming.rs b/exercises/practice/hamming/tests/hamming.rs index 3e744ccd2..b5fb7b437 100644 --- a/exercises/practice/hamming/tests/hamming.rs +++ b/exercises/practice/hamming/tests/hamming.rs @@ -1,89 +1,54 @@ -fn process_distance_case(strand_pair: [&str; 2], expected_distance: Option) { - assert_eq!( - hamming::hamming_distance(strand_pair[0], strand_pair[1]), - expected_distance - ); -} +use hamming::*; #[test] -fn test_empty_strands() { - process_distance_case(["", ""], Some(0)); -} - -#[test] -#[ignore] -/// disallow first strand longer -fn test_disallow_first_strand_longer() { - process_distance_case(["AATG", "AAA"], None); -} - -#[test] -#[ignore] -/// disallow second strand longer -fn test_disallow_second_strand_longer() { - process_distance_case(["ATA", "AGTG"], None); -} - -#[test] -#[ignore] -fn test_first_string_is_longer() { - process_distance_case(["AAA", "AA"], None); -} - -#[test] -#[ignore] -fn test_second_string_is_longer() { - process_distance_case(["A", "AA"], None); +fn empty_strands() { + assert_eq!(hamming_distance("", ""), Some(0)); } #[test] #[ignore] -/// single letter identical strands -fn test_single_letter_identical_strands() { - process_distance_case(["A", "A"], Some(0)); +fn single_letter_identical_strands() { + assert_eq!(hamming_distance("A", "A"), Some(0)); } #[test] #[ignore] -/// small distance -fn test_single_letter_different_strands() { - process_distance_case(["G", "T"], Some(1)); +fn single_letter_different_strands() { + assert_eq!(hamming_distance("G", "T"), Some(1)); } #[test] #[ignore] -/// long identical strands -fn test_long_identical_strands() { - process_distance_case(["GGACTGAAATCTG", "GGACTGAAATCTG"], Some(0)); +fn long_identical_strands() { + assert_eq!(hamming_distance("GGACTGAAATCTG", "GGACTGAAATCTG"), Some(0)); } #[test] #[ignore] -fn test_no_difference_between_identical_strands() { - process_distance_case(["GGACTGA", "GGACTGA"], Some(0)); +fn long_different_strands() { + assert_eq!(hamming_distance("GGACGGATTCTG", "AGGACGGATTCT"), Some(9)); } #[test] #[ignore] -fn test_complete_hamming_distance_in_small_strand() { - process_distance_case(["ACT", "GGA"], Some(3)); +fn disallow_first_strand_longer() { + assert_eq!(hamming_distance("AATG", "AAA"), None); } #[test] #[ignore] -fn test_small_hamming_distance_in_the_middle_somewhere() { - process_distance_case(["GGACG", "GGTCG"], Some(1)); +fn disallow_second_strand_longer() { + assert_eq!(hamming_distance("ATA", "AGTG"), None); } #[test] #[ignore] -fn test_larger_distance() { - process_distance_case(["ACCAGGG", "ACTATGG"], Some(2)); +fn disallow_empty_first_strand() { + assert_eq!(hamming_distance("", "G"), None); } #[test] #[ignore] -/// large distance in off-by-one strand -fn test_long_different_strands() { - process_distance_case(["GGACGGATTCTG", "AGGACGGATTCT"], Some(9)); +fn disallow_empty_second_strand() { + assert_eq!(hamming_distance("G", ""), None); } diff --git a/exercises/practice/hello-world/.docs/instructions.append.md b/exercises/practice/hello-world/.docs/instructions.append.md index 9c894cfaa..d14290126 100644 --- a/exercises/practice/hello-world/.docs/instructions.append.md +++ b/exercises/practice/hello-world/.docs/instructions.append.md @@ -1 +1,3 @@ +# Instructions append + In the Rust track, tests are run using the command `cargo test`. diff --git a/exercises/practice/hello-world/.docs/instructions.md b/exercises/practice/hello-world/.docs/instructions.md index 6e08ebba5..c9570e48a 100644 --- a/exercises/practice/hello-world/.docs/instructions.md +++ b/exercises/practice/hello-world/.docs/instructions.md @@ -1,15 +1,16 @@ # Instructions -The classical introductory exercise. Just say "Hello, World!". +The classical introductory exercise. +Just say "Hello, World!". -["Hello, World!"](http://en.wikipedia.org/wiki/%22Hello,_world!%22_program) is -the traditional first program for beginning programming in a new language -or environment. +["Hello, World!"][hello-world] is the traditional first program for beginning programming in a new language or environment. The objectives are simple: -- Write a function that returns the string "Hello, World!". +- Modify the provided code so that it produces the string "Hello, World!". - Run the test suite and make sure that it succeeds. - Submit your solution and check it at the website. If everything goes well, you will be ready to fetch your first real exercise. + +[hello-world]: https://en.wikipedia.org/wiki/%22Hello,_world!%22_program diff --git a/exercises/practice/hello-world/.gitignore b/exercises/practice/hello-world/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/hello-world/.gitignore +++ b/exercises/practice/hello-world/.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/hello-world/.meta/config.json b/exercises/practice/hello-world/.meta/config.json index 93473d3d8..157c4f41b 100644 --- a/exercises/practice/hello-world/.meta/config.json +++ b/exercises/practice/hello-world/.meta/config.json @@ -30,13 +30,13 @@ "Cargo.toml" ], "test": [ - "tests/hello-world.rs" + "tests/hello_world.rs" ], "example": [ ".meta/example.rs" ] }, - "blurb": "The classical introductory exercise. Just say \"Hello, World!\".", + "blurb": "Exercism's classic introductory exercise. Just say \"Hello, World!\".", "source": "This is an exercise to introduce users to using Exercism", - "source_url": "http://en.wikipedia.org/wiki/%22Hello,_world!%22_program" + "source_url": "https://en.wikipedia.org/wiki/%22Hello,_world!%22_program" } diff --git a/exercises/practice/hello-world/.meta/tests.toml b/exercises/practice/hello-world/.meta/tests.toml index be690e975..73466d677 100644 --- a/exercises/practice/hello-world/.meta/tests.toml +++ b/exercises/practice/hello-world/.meta/tests.toml @@ -1,3 +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. + +[af9ffe10-dc13-42d8-a742-e7bdafac449d] +description = "Say Hi!" diff --git a/exercises/practice/hello-world/Cargo.toml b/exercises/practice/hello-world/Cargo.toml index face5dfd5..e780cd474 100644 --- a/exercises/practice/hello-world/Cargo.toml +++ b/exercises/practice/hello-world/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "hello-world" -version = "1.1.0" +name = "hello_world" +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/hello-world/GETTING_STARTED.md b/exercises/practice/hello-world/GETTING_STARTED.md index 164ec615c..7ae584ca5 100644 --- a/exercises/practice/hello-world/GETTING_STARTED.md +++ b/exercises/practice/hello-world/GETTING_STARTED.md @@ -22,29 +22,29 @@ This will compile the `hello-world` crate and run the test, which fails. ```sh running 1 test -test test_hello_world ... FAILED +test hello_world ... FAILED failures: ----- test_hello_world stdout ---- -thread 'test_hello_world' panicked at 'assertion failed: `(left == right)` +---- hello_world stdout ---- +thread 'hello_world' panicked at 'assertion failed: `(left == right)` (left: `"Hello, World!"`, right: `"Goodbye, Mars!"`)', tests/hello-world.rs:5 failures: - test_hello_world + hello_world test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured ``` ### Understanding Test Failures -The `test_hello_world` failure states that it is expecting the value, +The `hello_world` failure states that it is expecting the value, `"Hello, World!"`, to be returned from `hello()`. The left side of the assertion (at line 5) should be equal to the right side. ```sh ----- test_hello_world stdout ---- -thread 'test_hello_world' panicked at 'assertion failed: `(left == right)` +---- hello_world stdout ---- +thread 'hello_world' panicked at 'assertion failed: `(left == right)` (left: `"Hello, World!"`, right: `"Goodbye, Mars!"`)', tests/hello-world.rs:5 ``` @@ -71,7 +71,7 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured Running target/debug/deps/hello_world-bd1f06dc726ef14f running 1 test -test test_hello_world ... ok +test hello_world ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured @@ -88,5 +88,5 @@ Once the test is passing, you can submit your code with the following command: ```sh -$ exercism submit src/lib.rs +$ exercism submit ``` diff --git a/exercises/practice/hello-world/tests/hello-world.rs b/exercises/practice/hello-world/tests/hello_world.rs similarity index 73% rename from exercises/practice/hello-world/tests/hello-world.rs rename to exercises/practice/hello-world/tests/hello_world.rs index b29f95388..7c7bfbaa4 100644 --- a/exercises/practice/hello-world/tests/hello-world.rs +++ b/exercises/practice/hello-world/tests/hello_world.rs @@ -1,4 +1,4 @@ #[test] -fn test_hello_world() { +fn hello_world() { assert_eq!("Hello, World!", hello_world::hello()); } diff --git a/exercises/practice/hexadecimal/.gitignore b/exercises/practice/hexadecimal/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/hexadecimal/.gitignore +++ b/exercises/practice/hexadecimal/.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/hexadecimal/.meta/example.rs b/exercises/practice/hexadecimal/.meta/example.rs index f539b3c9c..43fa3abab 100644 --- a/exercises/practice/hexadecimal/.meta/example.rs +++ b/exercises/practice/hexadecimal/.meta/example.rs @@ -27,7 +27,7 @@ pub fn hex_to_int(string: &str) -> Option { .chars() .rev() .enumerate() - .fold(Some(0), |acc, (pos, c)| { - parse_hex_digit(c).and_then(|n| acc.map(|acc| acc + n * base.pow(pos as u32))) + .try_fold(0, |acc, (pos, c)| { + parse_hex_digit(c).map(|n| acc + n * base.pow(pos as u32)) }) } diff --git a/exercises/practice/hexadecimal/Cargo.toml b/exercises/practice/hexadecimal/Cargo.toml index 313084bc0..1840732aa 100644 --- a/exercises/practice/hexadecimal/Cargo.toml +++ b/exercises/practice/hexadecimal/Cargo.toml @@ -1,3 +1,9 @@ [package] name = "hexadecimal" -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/hexadecimal/src/lib.rs b/exercises/practice/hexadecimal/src/lib.rs index f539b3c9c..870cc6e56 100644 --- a/exercises/practice/hexadecimal/src/lib.rs +++ b/exercises/practice/hexadecimal/src/lib.rs @@ -1,33 +1,9 @@ -fn parse_hex_digit(c: char) -> Option { - match c { - '0' => Some(0), - '1' => Some(1), - '2' => Some(2), - '3' => Some(3), - '4' => Some(4), - '5' => Some(5), - '6' => Some(6), - '7' => Some(7), - '8' => Some(8), - '9' => Some(9), - 'a' => Some(10), - 'b' => Some(11), - 'c' => Some(12), - 'd' => Some(13), - 'e' => Some(14), - 'f' => Some(15), - _ => None, - } -} +// This exercise is deprecated. +// Consider working on all-your-base instead. pub fn hex_to_int(string: &str) -> Option { - let base: i64 = 16; - - string - .chars() - .rev() - .enumerate() - .fold(Some(0), |acc, (pos, c)| { - parse_hex_digit(c).and_then(|n| acc.map(|acc| acc + n * base.pow(pos as u32))) - }) + todo!( + "what integer is represented by the base-16 digits {}?", + string + ); } diff --git a/exercises/practice/hexadecimal/tests/hexadecimal.rs b/exercises/practice/hexadecimal/tests/hexadecimal.rs index 9385391a5..376261055 100644 --- a/exercises/practice/hexadecimal/tests/hexadecimal.rs +++ b/exercises/practice/hexadecimal/tests/hexadecimal.rs @@ -1,58 +1,58 @@ #[test] -fn test_hex_1_is_decimal_1() { +fn hex_1_is_decimal_1() { assert_eq!(Some(1), hexadecimal::hex_to_int("1")); } #[test] #[ignore] -fn test_hex_c_is_decimal_12() { +fn hex_c_is_decimal_12() { assert_eq!(Some(12), hexadecimal::hex_to_int("c")); } #[test] #[ignore] -fn test_hex_10_is_decimal_16() { +fn hex_10_is_decimal_16() { assert_eq!(Some(16), hexadecimal::hex_to_int("10")); } #[test] #[ignore] -fn test_hex_af_is_decimal_175() { +fn hex_af_is_decimal_175() { assert_eq!(Some(175), hexadecimal::hex_to_int("af")); } #[test] #[ignore] -fn test_hex_100_is_decimal_256() { +fn hex_100_is_decimal_256() { assert_eq!(Some(256), hexadecimal::hex_to_int("100")); } #[test] #[ignore] -fn test_hex_19ace_is_decimal_105166() { +fn hex_19ace_is_decimal_105166() { assert_eq!(Some(105_166), hexadecimal::hex_to_int("19ace")); } #[test] #[ignore] -fn test_invalid_hex_is_none() { +fn invalid_hex_is_none() { assert_eq!(None, hexadecimal::hex_to_int("carrot")); } #[test] #[ignore] -fn test_black() { +fn black() { assert_eq!(Some(0), hexadecimal::hex_to_int("0000000")); } #[test] #[ignore] -fn test_white() { +fn white() { assert_eq!(Some(16_777_215), hexadecimal::hex_to_int("ffffff")); } #[test] #[ignore] -fn test_yellow() { +fn yellow() { assert_eq!(Some(16_776_960), hexadecimal::hex_to_int("ffff00")); } diff --git a/exercises/practice/high-scores/.docs/instructions.append.md b/exercises/practice/high-scores/.docs/hints.md similarity index 92% rename from exercises/practice/high-scores/.docs/instructions.append.md rename to exercises/practice/high-scores/.docs/hints.md index 20863a371..228e4850d 100644 --- a/exercises/practice/high-scores/.docs/instructions.append.md +++ b/exercises/practice/high-scores/.docs/hints.md @@ -1,4 +1,4 @@ -# Hints +## General Consider retaining a reference to `scores` in the struct - copying is not necessary. You will require some lifetime annotations, though. diff --git a/exercises/practice/high-scores/.docs/instructions.md b/exercises/practice/high-scores/.docs/instructions.md index 1f8154d5f..55802488c 100644 --- a/exercises/practice/high-scores/.docs/instructions.md +++ b/exercises/practice/high-scores/.docs/instructions.md @@ -2,4 +2,5 @@ Manage a game player's High Score list. -Your task is to build a high-score component of the classic Frogger game, one of the highest selling and addictive games of all time, and a classic of the arcade era. Your task is to write methods that return the highest score from the list, the last added score and the three highest scores. +Your task is to build a high-score component of the classic Frogger game, one of the highest selling and most addictive games of all time, and a classic of the arcade era. +Your task is to write methods that return the highest score from the list, the last added score and the three highest scores. diff --git a/exercises/practice/high-scores/.gitignore b/exercises/practice/high-scores/.gitignore index e130ceb2d..96ef6c0b9 100644 --- a/exercises/practice/high-scores/.gitignore +++ b/exercises/practice/high-scores/.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/high-scores/.meta/config.json b/exercises/practice/high-scores/.meta/config.json index f36a7c36e..970c70d4e 100644 --- a/exercises/practice/high-scores/.meta/config.json +++ b/exercises/practice/high-scores/.meta/config.json @@ -14,7 +14,7 @@ "Cargo.toml" ], "test": [ - "tests/high-scores.rs" + "tests/high_scores.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/high-scores/.meta/example.rs b/exercises/practice/high-scores/.meta/example.rs index 5a7e74fd7..9e855ddb3 100644 --- a/exercises/practice/high-scores/.meta/example.rs +++ b/exercises/practice/high-scores/.meta/example.rs @@ -9,7 +9,7 @@ impl<'a> HighScores<'a> { } pub fn scores(&self) -> &'a [u32] { - &self.scores + self.scores } pub fn latest(&self) -> Option { diff --git a/exercises/practice/high-scores/.meta/test_template.tera b/exercises/practice/high-scores/.meta/test_template.tera new file mode 100644 index 000000000..3d93408a6 --- /dev/null +++ b/exercises/practice/high-scores/.meta/test_template.tera @@ -0,0 +1,80 @@ +use high_scores::HighScores; + +{% for test_or_group in cases %} + +{# canonical_data for this exercise is cursed #} +{% if test_or_group.cases is defined %} + {% set group = test_or_group.cases %} +{% else %} + {% set group = [test_or_group] %} +{% endif %} + +{% for test in group %} + +{% set test_name = test.description | make_ident %} + +{# cryptic test names #} +{% if test.scenarios is iterable and "immutable" in test.scenarios %} + {% set test_name = test_name | replace(from="_after", to="_unchanged_after") %} +{% endif %} + +#[test] +#[ignore] +fn {{ test_name }}() { + {% if test.property is starting_with("score") %} + let expected = {{ test.input.scores }}; + let high_scores = HighScores::new(&expected); + {% else %} + let high_scores = HighScores::new(&{{ test.input.scores }}); + {% endif %} + + {% if test.property is ending_with("AfterTopThree") %} + let _ = high_scores.personal_top_three(); + {% elif test.property is ending_with("AfterBest") %} + let _ = high_scores.personal_best(); + {% endif %} + + + {% if test.property is starting_with("score") %} + assert_eq!(high_scores.scores(), &expected); + {% elif test.property is starting_with("latest") %} + assert_eq!(high_scores.latest(), Some({{ test.expected }})); + {% elif test.property == "personalBest" %} + assert_eq!(high_scores.personal_best(), Some({{ test.expected }})); + {% elif test.property == "personalTopThree" %} + assert_eq!(high_scores.personal_top_three(), vec!{{ test.expected }}); + {% else %} + // Causing a compiler error to bring test to attention + Unexpected property in high-scores test + {% endif %} +} +{% endfor %} +{% endfor %} + +{# + Unique rust tests using None and is_empty. + Don't make sense to have additional tests json file + since each of these would be an exception in the template + and it would be no simpler +#} + +#[test] +#[ignore] +fn latest_score_empty() { + let high_scores = HighScores::new(&[]); + assert_eq!(high_scores.latest(), None); +} + +#[test] +#[ignore] +fn personal_best_empty() { + let high_scores = HighScores::new(&[]); + assert_eq!(high_scores.personal_best(), None); +} + +#[test] +#[ignore] +fn personal_top_three_empty() { + let high_scores = HighScores::new(&[]); + assert!(high_scores.personal_top_three().is_empty()); +} diff --git a/exercises/practice/high-scores/.meta/tests.toml b/exercises/practice/high-scores/.meta/tests.toml index be690e975..7c9463380 100644 --- a/exercises/practice/high-scores/.meta/tests.toml +++ b/exercises/practice/high-scores/.meta/tests.toml @@ -1,3 +1,46 @@ -# 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. + +[1035eb93-2208-4c22-bab8-fef06769a73c] +description = "List of scores" + +[6aa5dbf5-78fa-4375-b22c-ffaa989732d2] +description = "Latest score" + +[b661a2e1-aebf-4f50-9139-0fb817dd12c6] +description = "Personal best" + +[3d996a97-c81c-4642-9afc-80b80dc14015] +description = "Top 3 scores -> Personal top three from a list of scores" + +[1084ecb5-3eb4-46fe-a816-e40331a4e83a] +description = "Top 3 scores -> Personal top highest to lowest" + +[e6465b6b-5a11-4936-bfe3-35241c4f4f16] +description = "Top 3 scores -> Personal top when there is a tie" + +[f73b02af-c8fd-41c9-91b9-c86eaa86bce2] +description = "Top 3 scores -> Personal top when there are less than 3" + +[16608eae-f60f-4a88-800e-aabce5df2865] +description = "Top 3 scores -> Personal top when there is only one" + +[2df075f9-fec9-4756-8f40-98c52a11504f] +description = "Top 3 scores -> Latest score after personal top scores" + +[809c4058-7eb1-4206-b01e-79238b9b71bc] +description = "Top 3 scores -> Scores after personal top scores" + +[ddb0efc0-9a86-4f82-bc30-21ae0bdc6418] +description = "Top 3 scores -> Latest score after personal best" + +[6a0fd2d1-4cc4-46b9-a5bb-2fb667ca2364] +description = "Top 3 scores -> Scores after personal best" diff --git a/exercises/practice/high-scores/Cargo.toml b/exercises/practice/high-scores/Cargo.toml index 6e7360a66..1e3a7727f 100644 --- a/exercises/practice/high-scores/Cargo.toml +++ b/exercises/practice/high-scores/Cargo.toml @@ -1,6 +1,9 @@ -[dependencies] - [package] -edition = "2021" -name = "high-scores" -version = "4.0.0" +name = "high_scores" +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/high-scores/src/lib.rs b/exercises/practice/high-scores/src/lib.rs index ccb33d324..07f513ef6 100644 --- a/exercises/practice/high-scores/src/lib.rs +++ b/exercises/practice/high-scores/src/lib.rs @@ -3,22 +3,22 @@ pub struct HighScores; impl HighScores { pub fn new(scores: &[u32]) -> Self { - unimplemented!("Construct a HighScores struct, given the scores: {scores:?}") + todo!("Construct a HighScores struct, given the scores: {scores:?}") } pub fn scores(&self) -> &[u32] { - unimplemented!("Return all the scores as a slice") + todo!("Return all the scores as a slice") } pub fn latest(&self) -> Option { - unimplemented!("Return the latest (last) score") + todo!("Return the latest (last) score") } pub fn personal_best(&self) -> Option { - unimplemented!("Return the highest score") + todo!("Return the highest score") } pub fn personal_top_three(&self) -> Vec { - unimplemented!("Return 3 highest scores") + todo!("Return 3 highest scores") } } diff --git a/exercises/practice/high-scores/tests/high-scores.rs b/exercises/practice/high-scores/tests/high_scores.rs similarity index 53% rename from exercises/practice/high-scores/tests/high-scores.rs rename to exercises/practice/high-scores/tests/high_scores.rs index f47634cb2..93471a2a7 100644 --- a/exercises/practice/high-scores/tests/high-scores.rs +++ b/exercises/practice/high-scores/tests/high_scores.rs @@ -1,78 +1,128 @@ use high_scores::HighScores; #[test] -fn test_list_of_scores() { +fn list_of_scores() { let expected = [30, 50, 20, 70]; let high_scores = HighScores::new(&expected); + assert_eq!(high_scores.scores(), &expected); } #[test] #[ignore] -fn test_latest_score() { +fn latest_score() { let high_scores = HighScores::new(&[100, 0, 90, 30]); - assert_eq!(high_scores.latest(), Some(30)); -} -#[test] -#[ignore] -fn test_latest_score_empty() { - let high_scores = HighScores::new(&[]); - assert_eq!(high_scores.latest(), None); + assert_eq!(high_scores.latest(), Some(30)); } #[test] #[ignore] -fn test_personal_best() { +fn personal_best() { let high_scores = HighScores::new(&[40, 100, 70]); - assert_eq!(high_scores.personal_best(), Some(100)); -} -#[test] -#[ignore] -fn test_personal_best_empty() { - let high_scores = HighScores::new(&[]); - assert_eq!(high_scores.personal_best(), None); + assert_eq!(high_scores.personal_best(), Some(100)); } #[test] #[ignore] -fn test_personal_top_three() { +fn personal_top_three_from_a_list_of_scores() { let high_scores = HighScores::new(&[10, 30, 90, 30, 100, 20, 10, 0, 30, 40, 40, 70, 70]); + assert_eq!(high_scores.personal_top_three(), vec![100, 90, 70]); } #[test] #[ignore] -fn test_personal_top_three_highest_to_lowest() { +fn personal_top_highest_to_lowest() { let high_scores = HighScores::new(&[20, 10, 30]); + assert_eq!(high_scores.personal_top_three(), vec![30, 20, 10]); } #[test] #[ignore] -fn test_personal_top_three_with_tie() { +fn personal_top_when_there_is_a_tie() { let high_scores = HighScores::new(&[40, 20, 40, 30]); + assert_eq!(high_scores.personal_top_three(), vec![40, 40, 30]); } #[test] #[ignore] -fn test_personal_top_three_with_less_than_three_scores() { +fn personal_top_when_there_are_less_than_3() { let high_scores = HighScores::new(&[30, 70]); + assert_eq!(high_scores.personal_top_three(), vec![70, 30]); } #[test] #[ignore] -fn test_personal_top_three_only_one_score() { +fn personal_top_when_there_is_only_one() { let high_scores = HighScores::new(&[40]); + assert_eq!(high_scores.personal_top_three(), vec![40]); } #[test] #[ignore] -fn test_personal_top_three_empty() { +fn latest_score_unchanged_after_personal_top_scores() { + let high_scores = HighScores::new(&[70, 50, 20, 30]); + + let _ = high_scores.personal_top_three(); + + assert_eq!(high_scores.latest(), Some(30)); +} + +#[test] +#[ignore] +fn scores_unchanged_after_personal_top_scores() { + let expected = [30, 50, 20, 70]; + let high_scores = HighScores::new(&expected); + + let _ = high_scores.personal_top_three(); + + assert_eq!(high_scores.scores(), &expected); +} + +#[test] +#[ignore] +fn latest_score_unchanged_after_personal_best() { + let high_scores = HighScores::new(&[20, 70, 15, 25, 30]); + + let _ = high_scores.personal_best(); + + assert_eq!(high_scores.latest(), Some(30)); +} + +#[test] +#[ignore] +fn scores_unchanged_after_personal_best() { + let expected = [20, 70, 15, 25, 30]; + let high_scores = HighScores::new(&expected); + + let _ = high_scores.personal_best(); + + assert_eq!(high_scores.scores(), &expected); +} + +#[test] +#[ignore] +fn latest_score_empty() { + let high_scores = HighScores::new(&[]); + assert_eq!(high_scores.latest(), None); +} + +#[test] +#[ignore] +fn personal_best_empty() { + let high_scores = HighScores::new(&[]); + assert_eq!(high_scores.personal_best(), None); +} + +#[test] +#[ignore] +fn personal_top_three_empty() { let high_scores = HighScores::new(&[]); assert!(high_scores.personal_top_three().is_empty()); } diff --git a/exercises/practice/isbn-verifier/.docs/instructions.md b/exercises/practice/isbn-verifier/.docs/instructions.md index 7d6635edc..4a0244e55 100644 --- a/exercises/practice/isbn-verifier/.docs/instructions.md +++ b/exercises/practice/isbn-verifier/.docs/instructions.md @@ -1,22 +1,26 @@ # Instructions -The [ISBN-10 verification process](https://en.wikipedia.org/wiki/International_Standard_Book_Number) is used to validate book identification -numbers. These normally contain dashes and look like: `3-598-21508-8` +The [ISBN-10 verification process][isbn-verification] is used to validate book identification numbers. +These normally contain dashes and look like: `3-598-21508-8` ## ISBN -The ISBN-10 format is 9 digits (0 to 9) plus one check character (either a digit or an X only). In the case the check character is an X, this represents the value '10'. These may be communicated with or without hyphens, and can be checked for their validity by the following formula: +The ISBN-10 format is 9 digits (0 to 9) plus one check character (either a digit or an X only). +In the case the check character is an X, this represents the value '10'. +These may be communicated with or without hyphens, and can be checked for their validity by the following formula: -``` -(x1 * 10 + x2 * 9 + x3 * 8 + x4 * 7 + x5 * 6 + x6 * 5 + x7 * 4 + x8 * 3 + x9 * 2 + x10 * 1) mod 11 == 0 +```text +(d₁ * 10 + d₂ * 9 + d₃ * 8 + d₄ * 7 + d₅ * 6 + d₆ * 5 + d₇ * 4 + d₈ * 3 + d₉ * 2 + d₁₀ * 1) mod 11 == 0 ``` If the result is 0, then it is a valid ISBN-10, otherwise it is invalid. ## Example -Let's take the ISBN-10 `3-598-21508-8`. We plug it in to the formula, and get: -``` +Let's take the ISBN-10 `3-598-21508-8`. +We plug it in to the formula, and get: + +```text (3 * 10 + 5 * 9 + 9 * 8 + 8 * 7 + 2 * 6 + 1 * 5 + 5 * 4 + 0 * 3 + 8 * 2 + 8 * 1) mod 11 == 0 ``` @@ -29,14 +33,10 @@ Putting this into place requires some thinking about preprocessing/parsing of th The program should be able to verify ISBN-10 both with and without separating dashes. - ## Caveats Converting from strings to numbers can be tricky in certain languages. -Now, it's even trickier since the check digit of an ISBN-10 may be 'X' (representing '10'). For instance `3-598-21507-X` is a valid ISBN-10. - -## Bonus tasks - -* Generate a valid ISBN-13 from the input ISBN-10 (and maybe verify it again with a derived verifier). +Now, it's even trickier since the check digit of an ISBN-10 may be 'X' (representing '10'). +For instance `3-598-21507-X` is a valid ISBN-10. -* Generate valid ISBN, maybe even from a given starting ISBN. +[isbn-verification]: https://en.wikipedia.org/wiki/International_Standard_Book_Number diff --git a/exercises/practice/isbn-verifier/.gitignore b/exercises/practice/isbn-verifier/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/isbn-verifier/.gitignore +++ b/exercises/practice/isbn-verifier/.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/isbn-verifier/.meta/config.json b/exercises/practice/isbn-verifier/.meta/config.json index 7f3d40f6c..52d9afa03 100644 --- a/exercises/practice/isbn-verifier/.meta/config.json +++ b/exercises/practice/isbn-verifier/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/isbn-verifier.rs" + "tests/isbn_verifier.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/isbn-verifier/.meta/test_template.tera b/exercises/practice/isbn-verifier/.meta/test_template.tera new file mode 100644 index 000000000..3a3dc7233 --- /dev/null +++ b/exercises/practice/isbn-verifier/.meta/test_template.tera @@ -0,0 +1,13 @@ +use isbn_verifier::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% if test.expected %} + assert!(is_valid_isbn("{{ test.input.isbn }}")); + {% else %} + assert!(!is_valid_isbn("{{ test.input.isbn }}")); + {% endif %} +} +{% endfor -%} diff --git a/exercises/practice/isbn-verifier/.meta/tests.toml b/exercises/practice/isbn-verifier/.meta/tests.toml index 1519d218d..6d5a84599 100644 --- a/exercises/practice/isbn-verifier/.meta/tests.toml +++ b/exercises/practice/isbn-verifier/.meta/tests.toml @@ -1,10 +1,56 @@ -# 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. + +[0caa3eac-d2e3-4c29-8df8-b188bc8c9292] +description = "valid isbn" + +[19f76b53-7c24-45f8-87b8-4604d0ccd248] +description = "invalid isbn check digit" + +[4164bfee-fb0a-4a1c-9f70-64c6a1903dcd] +description = "valid isbn with a check digit of 10" + +[3ed50db1-8982-4423-a993-93174a20825c] +description = "check digit is a character other than X" + +[9416f4a5-fe01-4b61-a07b-eb75892ef562] +description = "invalid check digit in isbn is not treated as zero" + +[c19ba0c4-014f-4dc3-a63f-ff9aefc9b5ec] +description = "invalid character in isbn is not treated as zero" + +[28025280-2c39-4092-9719-f3234b89c627] +description = "X is only valid as a check digit" + +[f6294e61-7e79-46b3-977b-f48789a4945b] +description = "valid isbn without separating dashes" + +[185ab99b-3a1b-45f3-aeec-b80d80b07f0b] +description = "isbn without separating dashes and X as check digit" + +[7725a837-ec8e-4528-a92a-d981dd8cf3e2] +description = "isbn without check digit and dashes" + +[47e4dfba-9c20-46ed-9958-4d3190630bdf] +description = "too long isbn and no dashes" [737f4e91-cbba-4175-95bf-ae630b41fb60] description = "too short isbn" +[5458a128-a9b6-4ff8-8afb-674e74567cef] +description = "isbn without check digit" + +[70b6ad83-d0a2-4ca7-a4d5-a9ab731800f7] +description = "check digit of X should not be used for 0" + [94610459-55ab-4c35-9b93-ff6ea1a8e562] description = "empty isbn" @@ -12,4 +58,10 @@ description = "empty isbn" description = "input is 9 characters" [ed6e8d1b-382c-4081-8326-8b772c581fec] -description = "invalid characters are not ignored" +description = "invalid characters are not ignored after checking length" + +[daad3e58-ce00-4395-8a8e-e3eded1cdc86] +description = "invalid characters are not ignored before checking length" + +[fb5e48d8-7c03-4bfb-a088-b101df16fdc3] +description = "input is too long but contains a valid isbn" diff --git a/exercises/practice/isbn-verifier/Cargo.toml b/exercises/practice/isbn-verifier/Cargo.toml index b355e387a..af7053701 100644 --- a/exercises/practice/isbn-verifier/Cargo.toml +++ b/exercises/practice/isbn-verifier/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "isbn-verifier" -version = "2.7.0" +name = "isbn_verifier" +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/isbn-verifier/src/lib.rs b/exercises/practice/isbn-verifier/src/lib.rs index e96ca32f4..c500b0bf5 100644 --- a/exercises/practice/isbn-verifier/src/lib.rs +++ b/exercises/practice/isbn-verifier/src/lib.rs @@ -1,4 +1,4 @@ /// Determines whether the supplied string is a valid ISBN number pub fn is_valid_isbn(isbn: &str) -> bool { - unimplemented!("Is {isbn:?} a valid ISBN number?"); + todo!("Is {isbn:?} a valid ISBN number?"); } diff --git a/exercises/practice/isbn-verifier/tests/isbn-verifier.rs b/exercises/practice/isbn-verifier/tests/isbn_verifier.rs similarity index 58% rename from exercises/practice/isbn-verifier/tests/isbn-verifier.rs rename to exercises/practice/isbn-verifier/tests/isbn_verifier.rs index ea381c0b9..73f27f406 100644 --- a/exercises/practice/isbn-verifier/tests/isbn-verifier.rs +++ b/exercises/practice/isbn-verifier/tests/isbn_verifier.rs @@ -1,63 +1,67 @@ -use isbn_verifier::is_valid_isbn; +use isbn_verifier::*; #[test] -fn test_valid() { +fn valid_isbn() { assert!(is_valid_isbn("3-598-21508-8")); } #[test] #[ignore] -fn test_invalid_check_digit() { +fn invalid_isbn_check_digit() { assert!(!is_valid_isbn("3-598-21508-9")); } #[test] #[ignore] -fn test_valid_check_digit_of_10() { +fn valid_isbn_with_a_check_digit_of_10() { assert!(is_valid_isbn("3-598-21507-X")); } #[test] #[ignore] -fn test_invalid_character_as_check_digit() { +fn check_digit_is_a_character_other_than_x() { assert!(!is_valid_isbn("3-598-21507-A")); } #[test] #[ignore] -fn test_invalid_character_in_isbn() { +fn invalid_check_digit_in_isbn_is_not_treated_as_zero() { + assert!(!is_valid_isbn("4-598-21507-B")); +} + +#[test] +#[ignore] +fn invalid_character_in_isbn_is_not_treated_as_zero() { assert!(!is_valid_isbn("3-598-P1581-X")); } #[test] #[ignore] -#[allow(non_snake_case)] -fn test_invalid_isbn_with_invalid_X() { +fn x_is_only_valid_as_a_check_digit() { assert!(!is_valid_isbn("3-598-2X507-9")); } #[test] #[ignore] -fn test_valid_isbn_without_dashes() { +fn valid_isbn_without_separating_dashes() { assert!(is_valid_isbn("3598215088")); } #[test] #[ignore] -#[allow(non_snake_case)] -fn test_valid_isbn_without_dashes_and_X_as_check() { +fn isbn_without_separating_dashes_and_x_as_check_digit() { assert!(is_valid_isbn("359821507X")); } #[test] #[ignore] -fn test_invalid_isbn_without_dashes_and_no_check_digit() { +fn isbn_without_check_digit_and_dashes() { assert!(!is_valid_isbn("359821507")); } #[test] #[ignore] -fn test_invalid_isbn_without_dashes_and_too_long() { +fn too_long_isbn_and_no_dashes() { assert!(!is_valid_isbn("3598215078X")); } @@ -69,26 +73,13 @@ fn too_short_isbn() { #[test] #[ignore] -fn test_invalid_isbn_without_check_digit() { +fn isbn_without_check_digit() { assert!(!is_valid_isbn("3-598-21507")); } #[test] #[ignore] -fn test_valid_digits_invalid_length() { - assert!(!is_valid_isbn("35982150881")); -} - -#[test] -#[ignore] -fn test_special_characters() { - assert!(!is_valid_isbn("!@#%!@")); -} - -#[test] -#[ignore] -#[allow(non_snake_case)] -fn test_invalid_isbn_with_check_digit_X_instead_of_0() { +fn check_digit_of_x_should_not_be_used_for_0() { assert!(!is_valid_isbn("3-598-21515-X")); } @@ -106,12 +97,18 @@ fn input_is_9_characters() { #[test] #[ignore] -fn invalid_characters_are_not_ignored() { +fn invalid_characters_are_not_ignored_after_checking_length() { assert!(!is_valid_isbn("3132P34035")); } #[test] #[ignore] -fn too_long_but_contains_a_valid_isbn() { +fn invalid_characters_are_not_ignored_before_checking_length() { + assert!(!is_valid_isbn("3598P215088")); +} + +#[test] +#[ignore] +fn input_is_too_long_but_contains_a_valid_isbn() { assert!(!is_valid_isbn("98245726788")); } diff --git a/exercises/practice/isogram/.approaches/filter-all/content.md b/exercises/practice/isogram/.approaches/filter-all/content.md index 5df0b0429..43e73fd5e 100644 --- a/exercises/practice/isogram/.approaches/filter-all/content.md +++ b/exercises/practice/isogram/.approaches/filter-all/content.md @@ -23,18 +23,19 @@ let mut hs = std::collections::HashSet::new(); ``` After the `HashSet` is instantiated, a series of functions are chained from the `candidate` `&str`. + - Since all of the characters are [ASCII][ascii], they can be iterated with the [`bytes`][bytes] method. -Each byte is iterated as a [`u8`][u8], which is an unsigned 8-bit integer. + Each byte is iterated as a [`u8`][u8], which is an unsigned 8-bit integer. - The [`filter`][filter] method [borrows][borrow] each byte as a [reference][reference] to a `u8` (`&u8`). -Inside of its [closure][closure] it tests each byte to see if it [`is_ascii_alphabetic`][is-ascii-alphabetic]. -Only bytes which are ASCII letters will survive the `filter` to be passed on to the [`map`][map] method. + Inside of its [closure][closure] it tests each byte to see if it [`is_ascii_alphabetic`][is-ascii-alphabetic]. + Only bytes which are ASCII letters will survive the `filter` to be passed on to the [`map`][map] method. - The `map` method calls [`to_ascii_lowercase`][to-ascii-lowercase] on each byte. - Each lowercased byte is then tested by the [`all`][all] method by using the [`insert`][insert] method of `HashSet`. -`all` will return `true` if every call to `insert` returns true. -If a call to `insert` returns `false` then `all` will "short-circuit" and immediately return `false`. -The `insert` method returns whether the value is _newly_ inserted. -So, for the word `"alpha"`, `insert` will return `true` when the first `a` is inserted, -but will return `false` when the second `a` is inserted. + `all` will return `true` if every call to `insert` returns true. + If a call to `insert` returns `false` then `all` will "short-circuit" and immediately return `false`. + The `insert` method returns whether the value is _newly_ inserted. + So, for the word `"alpha"`, `insert` will return `true` when the first `a` is inserted, + but will return `false` when the second `a` is inserted. ## Refactoring @@ -53,7 +54,7 @@ candidate However, changing the case of all characters in a `str` raised the average benchmark a few nanoseconds. It is a bit faster to `filter` out non-ASCII letters and to change the case of each surviving byte. -Since the performance is fairly close, either may be prefered. +Since the performance is fairly close, either may be prefered. ### using `filter_map` diff --git a/exercises/practice/isogram/.articles/performance/code/main.rs b/exercises/practice/isogram/.articles/performance/code/main.rs index 1acc59f90..f903d87b6 100644 --- a/exercises/practice/isogram/.articles/performance/code/main.rs +++ b/exercises/practice/isogram/.articles/performance/code/main.rs @@ -85,31 +85,31 @@ pub fn check_hash_unicode(candidate: &str) -> bool { } #[bench] -fn test_check_hash(b: &mut Bencher) { +fn check_hash(b: &mut Bencher) { b.iter(|| check_hash("thumbscrew-japingly")); } #[bench] -fn test_check_bits(b: &mut Bencher) { +fn check_bits(b: &mut Bencher) { b.iter(|| check_bits("thumbscrew-japingly")); } #[bench] -fn test_check_bits_func(b: &mut Bencher) { +fn check_bits_func(b: &mut Bencher) { b.iter(|| check_bits_func("thumbscrew-japingly")); } #[bench] -fn test_check_hash_filtermap(b: &mut Bencher) { +fn check_hash_filtermap(b: &mut Bencher) { b.iter(|| check_hash_filtermap("thumbscrew-japingly")); } #[bench] -fn test_check_bits_func_filter_map(b: &mut Bencher) { +fn check_bits_func_filter_map(b: &mut Bencher) { b.iter(|| check_bits_func_filter_map("thumbscrew-japingly")); } #[bench] -fn test_check_hash_unicode(b: &mut Bencher) { +fn check_hash_unicode(b: &mut Bencher) { b.iter(|| check_hash_unicode("thumbscrew-japingly")); } diff --git a/exercises/practice/isogram/.articles/performance/content.md b/exercises/practice/isogram/.articles/performance/content.md index ed5f0c135..6a780051b 100644 --- a/exercises/practice/isogram/.articles/performance/content.md +++ b/exercises/practice/isogram/.articles/performance/content.md @@ -17,12 +17,12 @@ At the time of this writing, all tests use [ASCII][ascii] characters, so the let To benchmark the approaches, we wrote a [small benchmark application][benchmark-application]. ``` -test test_check_hash ... bench: 952 ns/iter (+/- 176) // using filter, then map -test test_check_bits ... bench: 17 ns/iter (+/- 3) -test test_check_bits_func ... bench: 22 ns/iter (+/- 4) // using filter_map -test test_check_hash_filtermap ... bench: 970 ns/iter (+/- 216) // using filter_map -test test_check_bits_func_filter_map ... bench: 37 ns/iter (+/- 7) // using filter, then map -test test_check_hash_unicode ... bench: 1,052 ns/iter (+/- 327) +test check_hash ... bench: 952 ns/iter (+/- 176) // using filter, then map +test check_bits ... bench: 17 ns/iter (+/- 3) +test check_bits_func ... bench: 22 ns/iter (+/- 4) // using filter_map +test check_hash_filtermap ... bench: 970 ns/iter (+/- 216) // using filter_map +test check_bits_func_filter_map ... bench: 37 ns/iter (+/- 7) // using filter, then map +test check_hash_unicode ... bench: 1,052 ns/iter (+/- 327) ``` The `HashSet` approach was also benchmarked using [`filter_map`][filter-map] instead of `filter` and `map`. diff --git a/exercises/practice/isogram/.articles/performance/snippet.md b/exercises/practice/isogram/.articles/performance/snippet.md index 818e88b2d..397764ef9 100644 --- a/exercises/practice/isogram/.articles/performance/snippet.md +++ b/exercises/practice/isogram/.articles/performance/snippet.md @@ -1,8 +1,8 @@ ``` -test test_check_hash ... bench: 952 ns/iter (+/- 176) -test test_check_bits ... bench: 17 ns/iter (+/- 3) -test test_check_bits_func ... bench: 22 ns/iter (+/- 4) -test test_check_hash_filtermap ... bench: 970 ns/iter (+/- 216) -test test_check_bits_func_filter_map ... bench: 37 ns/iter (+/- 7) -test test_check_hash_unicode ... bench: 1,052 ns/iter (+/- 327) +test check_hash ... bench: 952 ns/iter (+/- 176) +test check_bits ... bench: 17 ns/iter (+/- 3) +test check_bits_func ... bench: 22 ns/iter (+/- 4) +test check_hash_filtermap ... bench: 970 ns/iter (+/- 216) +test check_bits_func_filter_map ... bench: 37 ns/iter (+/- 7) +test check_hash_unicode ... bench: 1,052 ns/iter (+/- 327) ``` diff --git a/exercises/practice/isogram/.docs/instructions.md b/exercises/practice/isogram/.docs/instructions.md index 9cc5350b6..2e8df851a 100644 --- a/exercises/practice/isogram/.docs/instructions.md +++ b/exercises/practice/isogram/.docs/instructions.md @@ -2,7 +2,7 @@ Determine if a word or phrase is an isogram. -An isogram (also known as a "nonpattern word") is a word or phrase without a repeating letter, however spaces and hyphens are allowed to appear multiple times. +An isogram (also known as a "non-pattern word") is a word or phrase without a repeating letter, however spaces and hyphens are allowed to appear multiple times. Examples of isograms: @@ -11,4 +11,4 @@ Examples of isograms: - downstream - six-year-old -The word *isograms*, however, is not an isogram, because the s repeats. +The word _isograms_, however, is not an isogram, because the s repeats. diff --git a/exercises/practice/isogram/.gitignore b/exercises/practice/isogram/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/isogram/.gitignore +++ b/exercises/practice/isogram/.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/isogram/.meta/test_template.tera b/exercises/practice/isogram/.meta/test_template.tera new file mode 100644 index 000000000..70e9382ae --- /dev/null +++ b/exercises/practice/isogram/.meta/test_template.tera @@ -0,0 +1,13 @@ +use isogram::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% if test.expected %} + assert!(check({{ test.input.phrase | json_encode() }})); + {% else %} + assert!(!check({{ test.input.phrase | json_encode() }})); + {% endif %} +} +{% endfor -%} diff --git a/exercises/practice/isogram/.meta/tests.toml b/exercises/practice/isogram/.meta/tests.toml index cf1f7ea7d..ba04c6645 100644 --- a/exercises/practice/isogram/.meta/tests.toml +++ b/exercises/practice/isogram/.meta/tests.toml @@ -1,15 +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. [a0e97d2d-669e-47c7-8134-518a1e2c4555] description = "empty string" +[9a001b50-f194-4143-bc29-2af5ec1ef652] +description = "isogram with only lower case characters" + +[8ddb0ca3-276e-4f8b-89da-d95d5bae78a4] +description = "word with one duplicated character" + +[6450b333-cbc2-4b24-a723-0b459b34fe18] +description = "word with one duplicated character from the end of the alphabet" + [a15ff557-dd04-4764-99e7-02cc1a385863] description = "longest reported english isogram" +[f1a7f6c7-a42f-4915-91d7-35b2ea11c92e] +description = "word with duplicated character in mixed case" + +[14a4f3c1-3b47-4695-b645-53d328298942] +description = "word with duplicated character in mixed case, lowercase first" + +[423b850c-7090-4a8a-b057-97f1cadd7c42] +description = "hypothetical isogrammic word with hyphen" + +[93dbeaa0-3c5a-45c2-8b25-428b8eacd4f2] +description = "hypothetical word with duplicated character following hyphen" + [36b30e5c-173f-49c6-a515-93a3e825553f] description = "isogram with duplicated hyphen" +[cdabafa0-c9f4-4c1f-b142-689c6ee17d93] +description = "made-up name that is an isogram" + [5fc61048-d74e-48fd-bc34-abfc21552d4d] description = "duplicated character in the middle" + +[310ac53d-8932-47bc-bbb4-b2b94f25a83e] +description = "same first and last characters" + +[0d0b8644-0a1e-4a31-a432-2b3ee270d847] +description = "word with duplicated character and with two hyphens" diff --git a/exercises/practice/isogram/Cargo.toml b/exercises/practice/isogram/Cargo.toml index 671cd99d7..4af75dc7a 100644 --- a/exercises/practice/isogram/Cargo.toml +++ b/exercises/practice/isogram/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "isogram" -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/isogram/src/lib.rs b/exercises/practice/isogram/src/lib.rs index 7fd132af7..620281037 100644 --- a/exercises/practice/isogram/src/lib.rs +++ b/exercises/practice/isogram/src/lib.rs @@ -1,3 +1,3 @@ pub fn check(candidate: &str) -> bool { - unimplemented!("Is {candidate} an isogram?"); + todo!("Is {candidate} an isogram?"); } diff --git a/exercises/practice/isogram/tests/isogram.rs b/exercises/practice/isogram/tests/isogram.rs index b0c6a2d21..29ee0e0b3 100644 --- a/exercises/practice/isogram/tests/isogram.rs +++ b/exercises/practice/isogram/tests/isogram.rs @@ -1,75 +1,84 @@ -use isogram::check; +use isogram::*; #[test] fn empty_string() { - assert!(check(""), "An empty string should be an isogram.") + assert!(check("")); } #[test] #[ignore] -fn only_lower_case_characters() { - assert!(check("isogram"), "\"isogram\" should be an isogram.") +fn isogram_with_only_lower_case_characters() { + assert!(check("isogram")); } #[test] #[ignore] -fn one_duplicated_character() { - assert!( - !check("eleven"), - "\"eleven\" has more than one \'e\', therefore it is no isogram." - ) +fn word_with_one_duplicated_character() { + assert!(!check("eleven")); +} + +#[test] +#[ignore] +fn word_with_one_duplicated_character_from_the_end_of_the_alphabet() { + assert!(!check("zzyzx")); } #[test] #[ignore] fn longest_reported_english_isogram() { - assert!( - check("subdermatoglyphic"), - "\"subdermatoglyphic\" should be an isogram." - ) + assert!(check("subdermatoglyphic")); +} + +#[test] +#[ignore] +fn word_with_duplicated_character_in_mixed_case() { + assert!(!check("Alphabet")); +} + +#[test] +#[ignore] +fn word_with_duplicated_character_in_mixed_case_lowercase_first() { + assert!(!check("alphAbet")); } #[test] #[ignore] -fn one_duplicated_character_mixed_case() { - assert!( - !check("Alphabet"), - "\"Alphabet\" has more than one \'a\', therefore it is no isogram." - ) +fn hypothetical_isogrammic_word_with_hyphen() { + assert!(check("thumbscrew-japingly")); } #[test] #[ignore] -fn hypothetical_isogramic_word_with_hyphen() { - assert!( - check("thumbscrew-japingly"), - "\"thumbscrew-japingly\" should be an isogram." - ) +fn hypothetical_word_with_duplicated_character_following_hyphen() { + assert!(!check("thumbscrew-jappingly")); } #[test] #[ignore] fn isogram_with_duplicated_hyphen() { - assert!( - check("six-year-old"), - "\"six-year-old\" should be an isogram." - ) + assert!(check("six-year-old")); } #[test] #[ignore] fn made_up_name_that_is_an_isogram() { - assert!( - check("Emily Jung Schwartzkopf"), - "\"Emily Jung Schwartzkopf\" should be an isogram." - ) + assert!(check("Emily Jung Schwartzkopf")); } #[test] #[ignore] fn duplicated_character_in_the_middle() { - assert!( - !check("accentor"), - "\"accentor\" has more than one \'c\', therefore it is no isogram." - ) + assert!(!check("accentor")); +} + +#[test] +#[ignore] +fn same_first_and_last_characters() { + assert!(!check("angola")); +} + +#[test] +#[ignore] +fn word_with_duplicated_character_and_with_two_hyphens() { + assert!(!check("up-to-date")); } diff --git a/exercises/practice/kindergarten-garden/.docs/instructions.md b/exercises/practice/kindergarten-garden/.docs/instructions.md new file mode 100644 index 000000000..6fe11a58c --- /dev/null +++ b/exercises/practice/kindergarten-garden/.docs/instructions.md @@ -0,0 +1,56 @@ +# Instructions + +Your task is to, given a diagram, determine which plants each child in the kindergarten class is responsible for. + +There are 12 children in the class: + +- Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, and Larry. + +Four different types of seeds are planted: + +| Plant | Diagram encoding | +| ------ | ---------------- | +| Grass | G | +| Clover | C | +| Radish | R | +| Violet | V | + +Each child gets four cups, two on each row: + +```text +[window][window][window] +........................ # each dot represents a cup +........................ +``` + +Their teacher assigns cups to the children alphabetically by their names, which means that Alice comes first and Larry comes last. + +Here is an example diagram representing Alice's plants: + +```text +[window][window][window] +VR...................... +RG...................... +``` + +In the first row, nearest the windows, she has a violet and a radish. +In the second row she has a radish and some grass. + +Your program will be given the plants from left-to-right starting with the row nearest the windows. +From this, it should be able to determine which plants belong to each student. + +For example, if it's told that the garden looks like so: + +```text +[window][window][window] +VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV +``` + +Then if asked for Alice's plants, it should provide: + +- Violets, radishes, violets, radishes + +While asking for Bob's plants would yield: + +- Clover, grass, clover, clover diff --git a/exercises/practice/kindergarten-garden/.docs/introduction.md b/exercises/practice/kindergarten-garden/.docs/introduction.md new file mode 100644 index 000000000..5ad97d23e --- /dev/null +++ b/exercises/practice/kindergarten-garden/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +The kindergarten class is learning about growing plants. +The teacher thought it would be a good idea to give the class seeds to plant and grow in the dirt. +To this end, the children have put little cups along the window sills and planted one type of plant in each cup. +The children got to pick their favorites from four available types of seeds: grass, clover, radishes, and violets. diff --git a/exercises/practice/kindergarten-garden/.gitignore b/exercises/practice/kindergarten-garden/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/kindergarten-garden/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/kindergarten-garden/.meta/config.json b/exercises/practice/kindergarten-garden/.meta/config.json new file mode 100644 index 000000000..7542c928d --- /dev/null +++ b/exercises/practice/kindergarten-garden/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "dem4ron" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/kindergarten_garden.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Given a diagram, determine which plants each child in the kindergarten class is responsible for.", + "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/kindergarten-garden/.meta/example.rs b/exercises/practice/kindergarten-garden/.meta/example.rs new file mode 100644 index 000000000..96c753372 --- /dev/null +++ b/exercises/practice/kindergarten-garden/.meta/example.rs @@ -0,0 +1,35 @@ +pub fn plants(diagram: &str, student: &str) -> Vec<&'static str> { + let student_names = [ + "Alice", "Bob", "Charlie", "David", "Eve", "Fred", "Ginny", "Harriet", "Ileana", "Joseph", + "Kincaid", "Larry", + ]; + let index = student_names + .iter() + .position(|&name| name == student) + .unwrap(); + + let mut lines = diagram + .lines() + .map(|line| line.chars().map(plant_from_char)); + + let start = index * 2; + let mut first_row = lines.next().unwrap(); + let mut second_row = lines.next().unwrap(); + + vec![ + first_row.nth(start).unwrap(), + first_row.next().unwrap(), + second_row.nth(start).unwrap(), + second_row.next().unwrap(), + ] +} + +fn plant_from_char(c: char) -> &'static str { + match c { + 'R' => "radishes", + 'C' => "clover", + 'G' => "grass", + 'V' => "violets", + _ => "No such plant", + } +} diff --git a/exercises/practice/kindergarten-garden/.meta/tests.toml b/exercises/practice/kindergarten-garden/.meta/tests.toml new file mode 100644 index 000000000..0cdd9ad64 --- /dev/null +++ b/exercises/practice/kindergarten-garden/.meta/tests.toml @@ -0,0 +1,61 @@ +# 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. + +[1fc316ed-17ab-4fba-88ef-3ae78296b692] +description = "partial garden -> garden with single student" + +[acd19dc1-2200-4317-bc2a-08f021276b40] +description = "partial garden -> different garden with single student" + +[c376fcc8-349c-446c-94b0-903947315757] +description = "partial garden -> garden with two students" + +[2d620f45-9617-4924-9d27-751c80d17db9] +description = "partial garden -> multiple students for the same garden with three students -> second student's garden" + +[57712331-4896-4364-89f8-576421d69c44] +description = "partial garden -> multiple students for the same garden with three students -> third student's garden" + +[149b4290-58e1-40f2-8ae4-8b87c46e765b] +description = "full garden -> for Alice, first student's garden" + +[ba25dbbc-10bd-4a37-b18e-f89ecd098a5e] +description = "full garden -> for Bob, second student's garden" + +[566b621b-f18e-4c5f-873e-be30544b838c] +description = "full garden -> for Charlie" + +[3ad3df57-dd98-46fc-9269-1877abf612aa] +description = "full garden -> for David" + +[0f0a55d1-9710-46ed-a0eb-399ba8c72db2] +description = "full garden -> for Eve" + +[a7e80c90-b140-4ea1-aee3-f4625365c9a4] +description = "full garden -> for Fred" + +[9d94b273-2933-471b-86e8-dba68694c615] +description = "full garden -> for Ginny" + +[f55bc6c2-ade8-4844-87c4-87196f1b7258] +description = "full garden -> for Harriet" + +[759070a3-1bb1-4dd4-be2c-7cce1d7679ae] +description = "full garden -> for Ileana" + +[78578123-2755-4d4a-9c7d-e985b8dda1c6] +description = "full garden -> for Joseph" + +[6bb66df7-f433-41ab-aec2-3ead6e99f65b] +description = "full garden -> for Kincaid, second to last student's garden" + +[d7edec11-6488-418a-94e6-ed509e0fa7eb] +description = "full garden -> for Larry, last student's garden" diff --git a/exercises/practice/kindergarten-garden/Cargo.toml b/exercises/practice/kindergarten-garden/Cargo.toml new file mode 100644 index 000000000..b5edfd55e --- /dev/null +++ b/exercises/practice/kindergarten-garden/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "kindergarten_garden" +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/kindergarten-garden/src/lib.rs b/exercises/practice/kindergarten-garden/src/lib.rs new file mode 100644 index 000000000..45106de9b --- /dev/null +++ b/exercises/practice/kindergarten-garden/src/lib.rs @@ -0,0 +1,3 @@ +pub fn plants(diagram: &str, student: &str) -> Vec<&'static str> { + todo!("based on the {diagram}, determine the plants the {student} is responsible for"); +} diff --git a/exercises/practice/kindergarten-garden/tests/kindergarten_garden.rs b/exercises/practice/kindergarten-garden/tests/kindergarten_garden.rs new file mode 100644 index 000000000..644b29cd7 --- /dev/null +++ b/exercises/practice/kindergarten-garden/tests/kindergarten_garden.rs @@ -0,0 +1,170 @@ +use kindergarten_garden::*; + +#[test] +fn garden_with_single_student() { + let diagram = "RC +GG"; + let student = "Alice"; + let expected = vec!["radishes", "clover", "grass", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn different_garden_with_single_student() { + let diagram = "VC +RC"; + let student = "Alice"; + let expected = vec!["violets", "clover", "radishes", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn garden_with_two_students() { + let diagram = "VVCG +VVRC"; + let student = "Bob"; + let expected = vec!["clover", "grass", "radishes", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn second_students_garden() { + let diagram = "VVCCGG +VVCCGG"; + let student = "Bob"; + let expected = vec!["clover", "clover", "clover", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn third_students_garden() { + let diagram = "VVCCGG +VVCCGG"; + let student = "Charlie"; + let expected = vec!["grass", "grass", "grass", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_alice_first_students_garden() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Alice"; + let expected = vec!["violets", "radishes", "violets", "radishes"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_bob_second_students_garden() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Bob"; + let expected = vec!["clover", "grass", "clover", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_charlie() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Charlie"; + let expected = vec!["violets", "violets", "clover", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_david() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "David"; + let expected = vec!["radishes", "violets", "clover", "radishes"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_eve() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Eve"; + let expected = vec!["clover", "grass", "radishes", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_fred() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Fred"; + let expected = vec!["grass", "clover", "violets", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_ginny() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Ginny"; + let expected = vec!["clover", "grass", "grass", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_harriet() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Harriet"; + let expected = vec!["violets", "radishes", "radishes", "violets"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_ileana() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Ileana"; + let expected = vec!["grass", "clover", "violets", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_joseph() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Joseph"; + let expected = vec!["violets", "clover", "violets", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_kincaid_second_to_last_students_garden() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Kincaid"; + let expected = vec!["grass", "clover", "clover", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn for_larry_last_students_garden() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Larry"; + let expected = vec!["grass", "violets", "clover", "violets"]; + assert_eq!(plants(diagram, student), expected); +} diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md new file mode 100644 index 000000000..0ebf7914c --- /dev/null +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -0,0 +1,25 @@ +# Instructions + +Your task is to determine which items to take so that the total value of her selection is maximized, taking into account the knapsack's carrying capacity. + +Items will be represented as a list of items. +Each item will have a weight and value. +All values given will be strictly positive. +Lhakpa can take only one of each item. + +For example: + +```text +Items: [ + { "weight": 5, "value": 10 }, + { "weight": 4, "value": 40 }, + { "weight": 6, "value": 30 }, + { "weight": 4, "value": 50 } +] + +Knapsack Maximum Weight: 10 +``` + +For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. +In this example, Lhakpa should take the second and fourth item to maximize her value, which, in this case, is 90. +She cannot get more than 90 as her knapsack has a weight limit of 10. diff --git a/exercises/practice/knapsack/.docs/introduction.md b/exercises/practice/knapsack/.docs/introduction.md new file mode 100644 index 000000000..9ac9df596 --- /dev/null +++ b/exercises/practice/knapsack/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +Lhakpa is a [Sherpa][sherpa] mountain guide and porter. +After months of careful planning, the expedition Lhakpa works for is about to leave. +She will be paid the value she carried to the base camp. + +In front of her are many items, each with a value and weight. +Lhakpa would gladly take all of the items, but her knapsack can only hold so much weight. + +[sherpa]: https://en.wikipedia.org/wiki/Sherpa_people#Mountaineering diff --git a/exercises/practice/knapsack/.gitignore b/exercises/practice/knapsack/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/knapsack/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/knapsack/.meta/config.json b/exercises/practice/knapsack/.meta/config.json new file mode 100644 index 000000000..8560394a0 --- /dev/null +++ b/exercises/practice/knapsack/.meta/config.json @@ -0,0 +1,23 @@ +{ + "authors": [ + "dem4ron" + ], + "contributors": [ + "victor-prokhorov" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/knapsack.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Given a knapsack that can only carry a certain weight, determine which items to put in the knapsack in order to maximize their combined value.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Knapsack_problem" +} diff --git a/exercises/practice/knapsack/.meta/example.rs b/exercises/practice/knapsack/.meta/example.rs new file mode 100644 index 000000000..085f21358 --- /dev/null +++ b/exercises/practice/knapsack/.meta/example.rs @@ -0,0 +1,26 @@ +pub struct Item { + pub weight: u32, + pub value: u32, +} + +pub fn maximum_value(max_weight: u32, items: &[Item]) -> u32 { + let mut max_values = vec![vec![0; (max_weight + 1) as usize]; items.len() + 1]; + + for i in 1..=items.len() { + let item_weight = items[i - 1].weight as usize; + let item_value = items[i - 1].value; + + for w in 0..=(max_weight as usize) { + if item_weight <= w { + max_values[i][w] = std::cmp::max( + max_values[i - 1][w], + max_values[i - 1][w - item_weight] + item_value, + ); + } else { + max_values[i][w] = max_values[i - 1][w]; + } + } + } + + max_values[items.len()][max_weight as usize] +} diff --git a/exercises/practice/knapsack/.meta/test_template.tera b/exercises/practice/knapsack/.meta/test_template.tera new file mode 100644 index 000000000..1a6a7ad53 --- /dev/null +++ b/exercises/practice/knapsack/.meta/test_template.tera @@ -0,0 +1,20 @@ +use knapsack::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let max_weight = {{ test.input.maximumWeight }}; + let items = [ + {% for item in test.input.items -%} + Item { + weight: {{ item.weight }}, + value: {{ item.value }}, + }, + {% endfor -%} + ]; + let output = maximum_value(max_weight, &items); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/knapsack/.meta/tests.toml b/exercises/practice/knapsack/.meta/tests.toml new file mode 100644 index 000000000..8e013ef19 --- /dev/null +++ b/exercises/practice/knapsack/.meta/tests.toml @@ -0,0 +1,36 @@ +# 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. + +[a4d7d2f0-ad8a-460c-86f3-88ba709d41a7] +description = "no items" +include = false + +[3993a824-c20e-493d-b3c9-ee8a7753ee59] +description = "no items" +reimplements = "a4d7d2f0-ad8a-460c-86f3-88ba709d41a7" + +[1d39e98c-6249-4a8b-912f-87cb12e506b0] +description = "one item, too heavy" + +[833ea310-6323-44f2-9d27-a278740ffbd8] +description = "five items (cannot be greedy by weight)" + +[277cdc52-f835-4c7d-872b-bff17bab2456] +description = "five items (cannot be greedy by value)" + +[81d8e679-442b-4f7a-8a59-7278083916c9] +description = "example knapsack" + +[f23a2449-d67c-4c26-bf3e-cde020f27ecc] +description = "8 items" + +[7c682ae9-c385-4241-a197-d2fa02c81a11] +description = "15 items" diff --git a/exercises/practice/knapsack/Cargo.toml b/exercises/practice/knapsack/Cargo.toml new file mode 100644 index 000000000..71cc438ba --- /dev/null +++ b/exercises/practice/knapsack/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "knapsack" +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/knapsack/src/lib.rs b/exercises/practice/knapsack/src/lib.rs new file mode 100644 index 000000000..9c505ce5c --- /dev/null +++ b/exercises/practice/knapsack/src/lib.rs @@ -0,0 +1,9 @@ +#[derive(Debug)] +pub struct Item { + pub weight: u32, + pub value: u32, +} + +pub fn maximum_value(max_weight: u32, items: &[Item]) -> u32 { + todo!("calculate the maximum value achievable with the given {items:?} and {max_weight}"); +} diff --git a/exercises/practice/knapsack/tests/knapsack.rs b/exercises/practice/knapsack/tests/knapsack.rs new file mode 100644 index 000000000..f601dac8c --- /dev/null +++ b/exercises/practice/knapsack/tests/knapsack.rs @@ -0,0 +1,226 @@ +use knapsack::*; + +#[test] +fn no_items() { + let max_weight = 100; + let items = []; + let output = maximum_value(max_weight, &items); + let expected = 0; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn one_item_too_heavy() { + let max_weight = 10; + let items = [Item { + weight: 100, + value: 1, + }]; + let output = maximum_value(max_weight, &items); + let expected = 0; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn five_items_cannot_be_greedy_by_weight() { + let max_weight = 10; + let items = [ + Item { + weight: 2, + value: 5, + }, + Item { + weight: 2, + value: 5, + }, + Item { + weight: 2, + value: 5, + }, + Item { + weight: 2, + value: 5, + }, + Item { + weight: 10, + value: 21, + }, + ]; + let output = maximum_value(max_weight, &items); + let expected = 21; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn five_items_cannot_be_greedy_by_value() { + let max_weight = 10; + let items = [ + Item { + weight: 2, + value: 20, + }, + Item { + weight: 2, + value: 20, + }, + Item { + weight: 2, + value: 20, + }, + Item { + weight: 2, + value: 20, + }, + Item { + weight: 10, + value: 50, + }, + ]; + let output = maximum_value(max_weight, &items); + let expected = 80; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn example_knapsack() { + let max_weight = 10; + let items = [ + Item { + weight: 5, + value: 10, + }, + Item { + weight: 4, + value: 40, + }, + Item { + weight: 6, + value: 30, + }, + Item { + weight: 4, + value: 50, + }, + ]; + let output = maximum_value(max_weight, &items); + let expected = 90; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_8_items() { + let max_weight = 104; + let items = [ + Item { + weight: 25, + value: 350, + }, + Item { + weight: 35, + value: 400, + }, + Item { + weight: 45, + value: 450, + }, + Item { + weight: 5, + value: 20, + }, + Item { + weight: 25, + value: 70, + }, + Item { + weight: 3, + value: 8, + }, + Item { + weight: 2, + value: 5, + }, + Item { + weight: 2, + value: 5, + }, + ]; + let output = maximum_value(max_weight, &items); + let expected = 900; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_15_items() { + let max_weight = 750; + let items = [ + Item { + weight: 70, + value: 135, + }, + Item { + weight: 73, + value: 139, + }, + Item { + weight: 77, + value: 149, + }, + Item { + weight: 80, + value: 150, + }, + Item { + weight: 82, + value: 156, + }, + Item { + weight: 87, + value: 163, + }, + Item { + weight: 90, + value: 173, + }, + Item { + weight: 94, + value: 184, + }, + Item { + weight: 98, + value: 192, + }, + Item { + weight: 106, + value: 201, + }, + Item { + weight: 110, + value: 210, + }, + Item { + weight: 113, + value: 214, + }, + Item { + weight: 115, + value: 221, + }, + Item { + weight: 118, + value: 229, + }, + Item { + weight: 120, + value: 240, + }, + ]; + let output = maximum_value(max_weight, &items); + let expected = 1458; + assert_eq!(output, expected); +} diff --git a/exercises/practice/largest-series-product/.docs/instructions.append.md b/exercises/practice/largest-series-product/.docs/hints.md similarity index 87% rename from exercises/practice/largest-series-product/.docs/instructions.append.md rename to exercises/practice/largest-series-product/.docs/hints.md index f91157776..7344c2e28 100644 --- a/exercises/practice/largest-series-product/.docs/instructions.append.md +++ b/exercises/practice/largest-series-product/.docs/hints.md @@ -1,4 +1,4 @@ -# Largest Series Product in Rust +## General These iterators may be useful, depending on your approach diff --git a/exercises/practice/largest-series-product/.docs/instructions.md b/exercises/practice/largest-series-product/.docs/instructions.md index 8ddbc6024..f297b57f7 100644 --- a/exercises/practice/largest-series-product/.docs/instructions.md +++ b/exercises/practice/largest-series-product/.docs/instructions.md @@ -1,14 +1,26 @@ # Instructions -Given a string of digits, calculate the largest product for a contiguous -substring of digits of length n. +Your task is to look for patterns in the long sequence of digits in the encrypted signal. -For example, for the input `'1027839564'`, the largest product for a -series of 3 digits is 270 (9 * 5 * 6), and the largest product for a -series of 5 digits is 7560 (7 * 8 * 3 * 9 * 5). +The technique you're going to use here is called the largest series product. -Note that these series are only required to occupy *adjacent positions* -in the input; the digits need not be *numerically consecutive*. +Let's define a few terms, first. -For the input `'73167176531330624919225119674426574742355349194934'`, -the largest product for a series of 6 digits is 23520. +- **input**: the sequence of digits that you need to analyze +- **series**: a sequence of adjacent digits (those that are next to each other) that is contained within the input +- **span**: how many digits long each series is +- **product**: what you get when you multiply numbers together + +Let's work through an example, with the input `"63915"`. + +- To form a series, take adjacent digits in the original input. +- If you are working with a span of `3`, there will be three possible series: + - `"639"` + - `"391"` + - `"915"` +- Then we need to calculate the product of each series: + - The product of the series `"639"` is 162 (`6 × 3 × 9 = 162`) + - The product of the series `"391"` is 27 (`3 × 9 × 1 = 27`) + - The product of the series `"915"` is 45 (`9 × 1 × 5 = 45`) +- 162 is bigger than both 27 and 45, so the largest series product of `"63915"` is from the series `"639"`. + So the answer is **162**. diff --git a/exercises/practice/largest-series-product/.docs/introduction.md b/exercises/practice/largest-series-product/.docs/introduction.md new file mode 100644 index 000000000..597bb5fa1 --- /dev/null +++ b/exercises/practice/largest-series-product/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +You work for a government agency that has intercepted a series of encrypted communication signals from a group of bank robbers. +The signals contain a long sequence of digits. +Your team needs to use various digital signal processing techniques to analyze the signals and identify any patterns that may indicate the planning of a heist. diff --git a/exercises/practice/largest-series-product/.gitignore b/exercises/practice/largest-series-product/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/largest-series-product/.gitignore +++ b/exercises/practice/largest-series-product/.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/largest-series-product/.meta/config.json b/exercises/practice/largest-series-product/.meta/config.json index 6a457ceeb..7dc3f10e5 100644 --- a/exercises/practice/largest-series-product/.meta/config.json +++ b/exercises/practice/largest-series-product/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/largest-series-product.rs" + "tests/largest_series_product.rs" ], "example": [ ".meta/example.rs" @@ -31,5 +31,5 @@ }, "blurb": "Given a string of digits, calculate the largest product for a contiguous substring of digits of length n.", "source": "A variation on Problem 8 at Project Euler", - "source_url": "http://projecteuler.net/problem=8" + "source_url": "https://projecteuler.net/problem=8" } diff --git a/exercises/practice/largest-series-product/.meta/example.rs b/exercises/practice/largest-series-product/.meta/example.rs index e89c42247..1f9bfbff6 100644 --- a/exercises/practice/largest-series-product/.meta/example.rs +++ b/exercises/practice/largest-series-product/.meta/example.rs @@ -9,7 +9,7 @@ pub fn lsp(string_digits: &str, span: usize) -> Result { return Ok(1); } - if let Some(invalid) = string_digits.chars().find(|c| !c.is_digit(10)) { + if let Some(invalid) = string_digits.chars().find(|c| !c.is_ascii_digit()) { return Err(Error::InvalidDigit(invalid)); } diff --git a/exercises/practice/largest-series-product/.meta/test_template.tera b/exercises/practice/largest-series-product/.meta/test_template.tera new file mode 100644 index 000000000..46a74e3d4 --- /dev/null +++ b/exercises/practice/largest-series-product/.meta/test_template.tera @@ -0,0 +1,28 @@ +use largest_series_product::*; + +#[test] +#[ignore] +fn return_is_a_result() { + assert!(lsp("29", 2).is_ok()); +} + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% if test.expected.error is defined %} + {% if test.expected.error == "span must not exceed string length" %} + {% set error_type = "SpanTooLong" %} + {% elif test.expected.error == "digits input must only contain digits" %} + {% set invalid_char = test.input.digits | split(pat="") | sort | last %} + {% set error_type = "InvalidDigit('" ~ invalid_char ~ "')" %} + {% else %} + {{ throw(message="unknown error: " ~ test.expected.error) }} + {% endif %} + + assert_eq!(Err(Error::{{error_type}}), lsp("{{test.input.digits}}", {{test.input.span}})); + {% else %} + assert_eq!(Ok({{test.expected | fmt_num}}), lsp("{{test.input.digits}}", {{test.input.span}})); + {% endif %} +} +{% endfor -%} diff --git a/exercises/practice/largest-series-product/.meta/tests.toml b/exercises/practice/largest-series-product/.meta/tests.toml index be690e975..b6416b5a0 100644 --- a/exercises/practice/largest-series-product/.meta/tests.toml +++ b/exercises/practice/largest-series-product/.meta/tests.toml @@ -1,3 +1,73 @@ -# 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. + +[7c82f8b7-e347-48ee-8a22-f672323324d4] +description = "finds the largest product if span equals length" + +[88523f65-21ba-4458-a76a-b4aaf6e4cb5e] +description = "can find the largest product of 2 with numbers in order" + +[f1376b48-1157-419d-92c2-1d7e36a70b8a] +description = "can find the largest product of 2" + +[46356a67-7e02-489e-8fea-321c2fa7b4a4] +description = "can find the largest product of 3 with numbers in order" + +[a2dcb54b-2b8f-4993-92dd-5ce56dece64a] +description = "can find the largest product of 3" + +[673210a3-33cd-4708-940b-c482d7a88f9d] +description = "can find the largest product of 5 with numbers in order" + +[02acd5a6-3bbf-46df-8282-8b313a80a7c9] +description = "can get the largest product of a big number" + +[76dcc407-21e9-424c-a98e-609f269622b5] +description = "reports zero if the only digits are zero" + +[6ef0df9f-52d4-4a5d-b210-f6fae5f20e19] +description = "reports zero if all spans include zero" + +[5d81aaf7-4f67-4125-bf33-11493cc7eab7] +description = "rejects span longer than string length" +include = false + +[0ae1ce53-d9ba-41bb-827f-2fceb64f058b] +description = "rejects span longer than string length" +reimplements = "5d81aaf7-4f67-4125-bf33-11493cc7eab7" + +[06bc8b90-0c51-4c54-ac22-3ec3893a079e] +description = "reports 1 for empty string and empty product (0 span)" + +[3ec0d92e-f2e2-4090-a380-70afee02f4c0] +description = "reports 1 for nonempty string and empty product (0 span)" + +[6d96c691-4374-4404-80ee-2ea8f3613dd4] +description = "rejects empty string and nonzero span" +include = false + +[6cf66098-a6af-4223-aab1-26aeeefc7402] +description = "rejects empty string and nonzero span" +reimplements = "6d96c691-4374-4404-80ee-2ea8f3613dd4" + +[7a38f2d6-3c35-45f6-8d6f-12e6e32d4d74] +description = "rejects invalid character in digits" + +[5fe3c0e5-a945-49f2-b584-f0814b4dd1ef] +description = "rejects negative span" +include = false +comment = "the function signature already prevents this (usize)" + +[c859f34a-9bfe-4897-9c2f-6d7f8598e7f0] +description = "rejects negative span" +include = false +reimplements = "5fe3c0e5-a945-49f2-b584-f0814b4dd1ef" +comment = "the function signature already prevents this (usize)" diff --git a/exercises/practice/largest-series-product/Cargo.toml b/exercises/practice/largest-series-product/Cargo.toml index 16e2defe5..ee231d49d 100644 --- a/exercises/practice/largest-series-product/Cargo.toml +++ b/exercises/practice/largest-series-product/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "largest-series-product" -version = "1.2.0" +name = "largest_series_product" +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/largest-series-product/src/lib.rs b/exercises/practice/largest-series-product/src/lib.rs index 2741a299b..8b4ce6d73 100644 --- a/exercises/practice/largest-series-product/src/lib.rs +++ b/exercises/practice/largest-series-product/src/lib.rs @@ -5,5 +5,5 @@ pub enum Error { } pub fn lsp(string_digits: &str, span: usize) -> Result { - unimplemented!("largest series product of a span of {span} digits in {string_digits}"); + todo!("largest series product of a span of {span} digits in {string_digits}"); } diff --git a/exercises/practice/largest-series-product/tests/largest-series-product.rs b/exercises/practice/largest-series-product/tests/largest-series-product.rs deleted file mode 100644 index 4811071d0..000000000 --- a/exercises/practice/largest-series-product/tests/largest-series-product.rs +++ /dev/null @@ -1,105 +0,0 @@ -use largest_series_product::*; - -#[test] -fn return_is_a_result() { - assert!(lsp("29", 2).is_ok()); -} - -#[test] -#[ignore] -fn find_the_largest_product_when_span_equals_length() { - assert_eq!(Ok(18), lsp("29", 2)); -} - -#[test] -#[ignore] -fn find_the_largest_product_of_two_with_numbers_in_order() { - assert_eq!(Ok(72), lsp("0123456789", 2)); -} - -#[test] -#[ignore] -fn find_the_largest_product_of_two_with_numbers_not_in_order() { - assert_eq!(Ok(48), lsp("576802143", 2)); -} - -#[test] -#[ignore] -fn find_the_largest_product_of_three_with_numbers_in_order() { - assert_eq!(Ok(504), lsp("0123456789", 3)); -} - -#[test] -#[ignore] -fn find_the_largest_product_of_three_with_numbers_not_in_order() { - assert_eq!(Ok(270), lsp("1027839564", 3)); -} - -#[test] -#[ignore] -fn find_the_largest_product_of_five_with_numbers_in_order() { - assert_eq!(Ok(15_120), lsp("0123456789", 5)); -} - -#[test] -#[ignore] -fn span_of_six_in_a_large_number() { - assert_eq!( - Ok(23_520), - lsp("73167176531330624919225119674426574742355349194934", 6) - ); -} - -#[test] -#[ignore] -fn returns_zero_if_number_is_zeros() { - assert_eq!(Ok(0), lsp("0000", 2)); -} - -#[test] -#[ignore] -fn returns_zero_if_all_products_are_zero() { - assert_eq!(Ok(0), lsp("99099", 3)); -} - -#[test] -#[ignore] -fn a_span_is_longer_than_number_is_an_error() { - assert_eq!(Err(Error::SpanTooLong), lsp("123", 4)); -} - -// There may be some confusion about whether this should be 1 or error. -// The reasoning for it being 1 is this: -// There is one 0-character string contained in the empty string. -// That's the empty string itself. -// The empty product is 1 (the identity for multiplication). -// Therefore LSP('', 0) is 1. -// It's NOT the case that LSP('', 0) takes max of an empty list. -// So there is no error. -// Compare against LSP('123', 4): -// There are zero 4-character strings in '123'. -// So LSP('123', 4) really DOES take the max of an empty list. -// So LSP('123', 4) errors and LSP('', 0) does NOT. -#[test] -#[ignore] -fn an_empty_string_and_no_span_returns_one() { - assert_eq!(Ok(1), lsp("", 0)); -} - -#[test] -#[ignore] -fn a_non_empty_string_and_no_span_returns_one() { - assert_eq!(Ok(1), lsp("123", 0)); -} - -#[test] -#[ignore] -fn empty_string_and_non_zero_span_is_an_error() { - assert_eq!(Err(Error::SpanTooLong), lsp("", 1)); -} - -#[test] -#[ignore] -fn a_string_with_non_digits_is_an_error() { - assert_eq!(Err(Error::InvalidDigit('a')), lsp("1234a5", 2)); -} diff --git a/exercises/practice/largest-series-product/tests/largest_series_product.rs b/exercises/practice/largest-series-product/tests/largest_series_product.rs new file mode 100644 index 000000000..a96ef5694 --- /dev/null +++ b/exercises/practice/largest-series-product/tests/largest_series_product.rs @@ -0,0 +1,93 @@ +use largest_series_product::*; + +#[test] +fn return_is_a_result() { + assert!(lsp("29", 2).is_ok()); +} + +#[test] +#[ignore] +fn finds_the_largest_product_if_span_equals_length() { + assert_eq!(Ok(18), lsp("29", 2)); +} + +#[test] +#[ignore] +fn can_find_the_largest_product_of_2_with_numbers_in_order() { + assert_eq!(Ok(72), lsp("0123456789", 2)); +} + +#[test] +#[ignore] +fn can_find_the_largest_product_of_2() { + assert_eq!(Ok(48), lsp("576802143", 2)); +} + +#[test] +#[ignore] +fn can_find_the_largest_product_of_3_with_numbers_in_order() { + assert_eq!(Ok(504), lsp("0123456789", 3)); +} + +#[test] +#[ignore] +fn can_find_the_largest_product_of_3() { + assert_eq!(Ok(270), lsp("1027839564", 3)); +} + +#[test] +#[ignore] +fn can_find_the_largest_product_of_5_with_numbers_in_order() { + assert_eq!(Ok(15_120), lsp("0123456789", 5)); +} + +#[test] +#[ignore] +fn can_get_the_largest_product_of_a_big_number() { + assert_eq!( + Ok(23_520), + lsp("73167176531330624919225119674426574742355349194934", 6) + ); +} + +#[test] +#[ignore] +fn reports_zero_if_the_only_digits_are_zero() { + assert_eq!(Ok(0), lsp("0000", 2)); +} + +#[test] +#[ignore] +fn reports_zero_if_all_spans_include_zero() { + assert_eq!(Ok(0), lsp("99099", 3)); +} + +#[test] +#[ignore] +fn rejects_span_longer_than_string_length() { + assert_eq!(Err(Error::SpanTooLong), lsp("123", 4)); +} + +#[test] +#[ignore] +fn reports_1_for_empty_string_and_empty_product_0_span() { + assert_eq!(Ok(1), lsp("", 0)); +} + +#[test] +#[ignore] +fn reports_1_for_nonempty_string_and_empty_product_0_span() { + assert_eq!(Ok(1), lsp("123", 0)); +} + +#[test] +#[ignore] +fn rejects_empty_string_and_nonzero_span() { + assert_eq!(Err(Error::SpanTooLong), lsp("", 1)); +} + +#[test] +#[ignore] +fn rejects_invalid_character_in_digits() { + assert_eq!(Err(Error::InvalidDigit('a')), lsp("1234a5", 2)); +} diff --git a/exercises/practice/leap/.approaches/date-addition-chrono/content.md b/exercises/practice/leap/.approaches/date-addition-chrono/content.md index 63081f66b..04a1be05d 100644 --- a/exercises/practice/leap/.approaches/date-addition-chrono/content.md +++ b/exercises/practice/leap/.approaches/date-addition-chrono/content.md @@ -8,9 +8,9 @@ pub fn is_leap_year(year: u64) -> bool { } ``` -```exercism/caution +~~~~exercism/caution This approach may be considered a "cheat" for this exercise. -``` +~~~~ By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st. If it is the 29th, then the year is a leap year. This is done by using the [`Duration::days(1)`][day-duration] method to add a day to a [`chrono::Date`][chrono-date] `struct` and comparing it to `29` with the [`day`][day-method] method. diff --git a/exercises/practice/leap/.approaches/date-addition-time/content.md b/exercises/practice/leap/.approaches/date-addition-time/content.md index 779a2931d..02fe8058f 100644 --- a/exercises/practice/leap/.approaches/date-addition-time/content.md +++ b/exercises/practice/leap/.approaches/date-addition-time/content.md @@ -9,9 +9,9 @@ pub fn is_leap_year(year: u64) -> bool { } ``` -```exercism/caution +~~~~exercism/caution This approach may be considered a "cheat" for this exercise. -``` +~~~~ By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st. If it is the 29th, then the year is a leap year. This is done by adding a [`Duration::DAY`][day-duration] to a [`time::Date`][time-date] `struct` and comparing it to `29` with the [`day`][day-method] method. diff --git a/exercises/practice/leap/.approaches/introduction.md b/exercises/practice/leap/.approaches/introduction.md index 081254f59..da1fefae2 100644 --- a/exercises/practice/leap/.approaches/introduction.md +++ b/exercises/practice/leap/.approaches/introduction.md @@ -75,7 +75,7 @@ For more information, check the [Performance article][article-performance]. [remainder-operator]: https://doc.rust-lang.org/std/ops/trait.Rem.html [match]: https://doc.rust-lang.org/rust-by-example/flow_control/match.html [tuple]: https://doc.rust-lang.org/rust-by-example/primitives/tuples.html -[ternary-expression]: (https://doc.rust-lang.org/reference/expressions/if-expr.html) +[ternary-expression]: https://doc.rust-lang.org/reference/expressions/if-expr.html [approach-boolean-line]: https://exercism.org/tracks/rust/exercises/leap/approaches/boolean-one-line [approach-ternary-expression]: https://exercism.org/tracks/rust/exercises/leap/approaches/ternary-expression [approach-match-on-a-tuple]: https://exercism.org/tracks/rust/exercises/leap/approaches/match-on-a-tuple diff --git a/exercises/practice/leap/.approaches/ternary-expression/content.md b/exercises/practice/leap/.approaches/ternary-expression/content.md index 5e4729f62..100d58fd0 100644 --- a/exercises/practice/leap/.approaches/ternary-expression/content.md +++ b/exercises/practice/leap/.approaches/ternary-expression/content.md @@ -33,11 +33,11 @@ if year % 100 == 0 { `year` is tested to be evenly divislbe by `100` by using the [remainder operator][remainder-operator]. If so, it returns if `year` is evenly divisible by `400`, as the last expression evaluated in the function. -```exercism/note +~~~~exercism/note Note that the line is just `year % 400 == 0`. 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. -``` +~~~~ If `year` is _not_ evenly divisible by `100`, then `else` is the last expression evaluated in the function diff --git a/exercises/practice/leap/.articles/performance/code/main.rs b/exercises/practice/leap/.articles/performance/code/main.rs index c1628cf4f..4ed266f5a 100644 --- a/exercises/practice/leap/.articles/performance/code/main.rs +++ b/exercises/practice/leap/.articles/performance/code/main.rs @@ -46,31 +46,31 @@ pub fn is_leap_year_naive(year: u64) -> bool { } #[bench] -fn test_ternary(b: &mut Bencher) { +fn ternary(b: &mut Bencher) { b.iter(|| is_leap_year(2000)); } #[bench] -fn test_one_line(b: &mut Bencher) { +fn one_line(b: &mut Bencher) { b.iter(|| is_leap_year_one_line(2000)); } #[bench] -fn test_match(b: &mut Bencher) { +fn match(b: &mut Bencher) { b.iter(|| is_leap_year_match(2000)); } #[bench] -fn test_time(b: &mut Bencher) { +fn time(b: &mut Bencher) { b.iter(|| is_leap_year_time(2000)); } #[bench] -fn test_chrono(b: &mut Bencher) { +fn chrono(b: &mut Bencher) { b.iter(|| is_leap_year_chrono(2000)); } #[bench] -fn test_naive(b: &mut Bencher) { +fn naive(b: &mut Bencher) { b.iter(|| is_leap_year_naive(2000)); } diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index 2edb23c34..53b79f4ff 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -18,12 +18,12 @@ For our performance investigation, we'll also include two other approaches: To benchmark the approaches, we wrote a [small benchmark application][benchmark-application]. ``` -test test_ternary ... bench: 0 ns/iter (+/- 0) -test test_one_line ... bench: 0 ns/iter (+/- 0) -test test_match ... bench: 0 ns/iter (+/- 0) -test test_time ... bench: 30 ns/iter (+/- 9) -test test_chrono ... bench: 18 ns/iter (+/- 3) -test test_naive ... bench: 18 ns/iter (+/- 1) +test ternary ... bench: 0 ns/iter (+/- 0) +test one_line ... bench: 0 ns/iter (+/- 0) +test match ... bench: 0 ns/iter (+/- 0) +test time ... bench: 30 ns/iter (+/- 9) +test chrono ... bench: 18 ns/iter (+/- 3) +test naive ... bench: 18 ns/iter (+/- 1) ``` The three main approaches were identical in measurable performance. diff --git a/exercises/practice/leap/.articles/performance/snippet.md b/exercises/practice/leap/.articles/performance/snippet.md index de3e4d3c8..ddb5f0085 100644 --- a/exercises/practice/leap/.articles/performance/snippet.md +++ b/exercises/practice/leap/.articles/performance/snippet.md @@ -1,7 +1,7 @@ ``` -test test_ternary ... bench: 0 ns/iter (+/- 0) -test test_one_line ... bench: 0 ns/iter (+/- 0) -test test_match ... bench: 0 ns/iter (+/- 0) -test test_time ... bench: 30 ns/iter (+/- 9) -test test_chrono ... bench: 19 ns/iter (+/- 1) +test ternary ... bench: 0 ns/iter (+/- 0) +test one_line ... bench: 0 ns/iter (+/- 0) +test match ... bench: 0 ns/iter (+/- 0) +test time ... bench: 30 ns/iter (+/- 9) +test chrono ... bench: 19 ns/iter (+/- 1) ``` diff --git a/exercises/practice/leap/.docs/instructions.md b/exercises/practice/leap/.docs/instructions.md index dc7b4e816..b14f8565d 100644 --- a/exercises/practice/leap/.docs/instructions.md +++ b/exercises/practice/leap/.docs/instructions.md @@ -1,24 +1,3 @@ # Instructions -Given a year, report if it is a leap year. - -The tricky thing here is that a leap year in the Gregorian calendar occurs: - -```text -on every year that is evenly divisible by 4 - except every year that is evenly divisible by 100 - unless the year is also evenly divisible by 400 -``` - -For example, 1997 is not a leap year, but 1996 is. 1900 is not a leap -year, but 2000 is. - -## Notes - -Though our exercise adopts some very simple rules, there is more to -learn! - -For a delightful, four minute explanation of the whole leap year -phenomenon, go watch [this youtube video][video]. - -[video]: http://www.youtube.com/watch?v=xX96xng7sAE +Your task is to determine whether a given year is a leap year. diff --git a/exercises/practice/leap/.docs/introduction.md b/exercises/practice/leap/.docs/introduction.md new file mode 100644 index 000000000..4ffd2da59 --- /dev/null +++ b/exercises/practice/leap/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +A leap year (in the Gregorian calendar) occurs: + +- In every year that is evenly divisible by 4. +- Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400. + +Some examples: + +- 1997 was not a leap year as it's not divisible by 4. +- 1900 was not a leap year as it's not divisible by 400. +- 2000 was a leap year! + +~~~~exercism/note +For a delightful, four-minute explanation of the whole phenomenon of leap years, check out [this YouTube video](https://www.youtube.com/watch?v=xX96xng7sAE). +~~~~ diff --git a/exercises/practice/leap/.gitignore b/exercises/practice/leap/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/leap/.gitignore +++ b/exercises/practice/leap/.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/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index 1f3b24293..95d0448e8 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -44,7 +44,7 @@ ".meta/example.rs" ] }, - "blurb": "Given a year, report if it is a leap year.", - "source": "JavaRanch Cattle Drive, exercise 3", - "source_url": "http://www.javaranch.com/leap.jsp" + "blurb": "Determine whether a given year is a leap year.", + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://web.archive.org/web/20240907033714/https://coderanch.com/t/718816/Leap" } diff --git a/exercises/practice/leap/.meta/example.rs b/exercises/practice/leap/.meta/example.rs index 8549b184b..b4f5a3cb3 100644 --- a/exercises/practice/leap/.meta/example.rs +++ b/exercises/practice/leap/.meta/example.rs @@ -1,4 +1,3 @@ pub fn is_leap_year(year: u64) -> bool { - let has_factor = |n| year % n == 0; - has_factor(4) && (!has_factor(100) || has_factor(400)) + year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400)) } diff --git a/exercises/practice/leap/.meta/test_template.tera b/exercises/practice/leap/.meta/test_template.tera new file mode 100644 index 000000000..bed0ba0a6 --- /dev/null +++ b/exercises/practice/leap/.meta/test_template.tera @@ -0,0 +1,13 @@ +use leap::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { +{%- if test.expected %} + assert!(is_leap_year({{ test.input.year }})); +{% else %} + assert!(!is_leap_year({{ test.input.year }})); +{% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/leap/.meta/tests.toml b/exercises/practice/leap/.meta/tests.toml index be690e975..ce6ba325e 100644 --- a/exercises/practice/leap/.meta/tests.toml +++ b/exercises/practice/leap/.meta/tests.toml @@ -1,3 +1,37 @@ -# 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. + +[6466b30d-519c-438e-935d-388224ab5223] +description = "year not divisible by 4 in common year" + +[ac227e82-ee82-4a09-9eb6-4f84331ffdb0] +description = "year divisible by 2, not divisible by 4 in common year" + +[4fe9b84c-8e65-489e-970b-856d60b8b78e] +description = "year divisible by 4, not divisible by 100 in leap year" + +[7fc6aed7-e63c-48f5-ae05-5fe182f60a5d] +description = "year divisible by 4 and 5 is still a leap year" + +[78a7848f-9667-4192-ae53-87b30c9a02dd] +description = "year divisible by 100, not divisible by 400 in common year" + +[9d70f938-537c-40a6-ba19-f50739ce8bac] +description = "year divisible by 100 but not by 3 is still not a leap year" + +[42ee56ad-d3e6-48f1-8e3f-c84078d916fc] +description = "year divisible by 400 is leap year" + +[57902c77-6fe9-40de-8302-587b5c27121e] +description = "year divisible by 400 but not by 125 is still a leap year" + +[c30331f6-f9f6-4881-ad38-8ca8c12520c1] +description = "year divisible by 200, not divisible by 400 in common year" diff --git a/exercises/practice/leap/Cargo.toml b/exercises/practice/leap/Cargo.toml index af7835945..fa2e07b33 100644 --- a/exercises/practice/leap/Cargo.toml +++ b/exercises/practice/leap/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "leap" -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/leap/src/lib.rs b/exercises/practice/leap/src/lib.rs index 78a67219a..edeefea4d 100644 --- a/exercises/practice/leap/src/lib.rs +++ b/exercises/practice/leap/src/lib.rs @@ -1,3 +1,3 @@ pub fn is_leap_year(year: u64) -> bool { - unimplemented!("true if {year} is a leap year") + todo!("true if {year} is a leap year") } diff --git a/exercises/practice/leap/tests/leap.rs b/exercises/practice/leap/tests/leap.rs index 4c7ee2ce6..371f27e32 100644 --- a/exercises/practice/leap/tests/leap.rs +++ b/exercises/practice/leap/tests/leap.rs @@ -1,100 +1,54 @@ -fn process_leapyear_case(year: u64, expected: bool) { - assert_eq!(leap::is_leap_year(year), expected); -} - -#[test] -fn test_year_not_divisible_by_4_common_year() { - process_leapyear_case(2015, false); -} - -#[test] -#[ignore] -fn test_year_divisible_by_2_not_divisible_by_4_in_common_year() { - process_leapyear_case(1970, false); -} - -#[test] -#[ignore] -fn test_year_divisible_by_4_not_divisible_by_100_leap_year() { - process_leapyear_case(1996, true); -} - -#[test] -#[ignore] -fn test_year_divisible_by_4_and_5_is_still_a_leap_year() { - process_leapyear_case(1960, true); -} +use leap::*; #[test] -#[ignore] -fn test_year_divisible_by_100_not_divisible_by_400_common_year() { - process_leapyear_case(2100, false); +fn year_not_divisible_by_4_in_common_year() { + assert!(!is_leap_year(2015)); } #[test] #[ignore] -fn test_year_divisible_by_100_but_not_by_3_is_still_not_a_leap_year() { - process_leapyear_case(1900, false); +fn year_divisible_by_2_not_divisible_by_4_in_common_year() { + assert!(!is_leap_year(1970)); } #[test] #[ignore] -fn test_year_divisible_by_400_leap_year() { - process_leapyear_case(2000, true); +fn year_divisible_by_4_not_divisible_by_100_in_leap_year() { + assert!(is_leap_year(1996)); } #[test] #[ignore] -fn test_year_divisible_by_400_but_not_by_125_is_still_a_leap_year() { - process_leapyear_case(2400, true); +fn year_divisible_by_4_and_5_is_still_a_leap_year() { + assert!(is_leap_year(1960)); } #[test] #[ignore] -fn test_year_divisible_by_200_not_divisible_by_400_common_year() { - process_leapyear_case(1800, false); +fn year_divisible_by_100_not_divisible_by_400_in_common_year() { + assert!(!is_leap_year(2100)); } #[test] #[ignore] -fn test_any_old_year() { - process_leapyear_case(1997, false); +fn year_divisible_by_100_but_not_by_3_is_still_not_a_leap_year() { + assert!(!is_leap_year(1900)); } #[test] #[ignore] -fn test_early_years() { - process_leapyear_case(1, false); - process_leapyear_case(4, true); - process_leapyear_case(100, false); - process_leapyear_case(400, true); - process_leapyear_case(900, false); +fn year_divisible_by_400_is_leap_year() { + assert!(is_leap_year(2000)); } #[test] #[ignore] -fn test_century() { - process_leapyear_case(1700, false); - process_leapyear_case(1800, false); - process_leapyear_case(1900, false); +fn year_divisible_by_400_but_not_by_125_is_still_a_leap_year() { + assert!(is_leap_year(2400)); } #[test] #[ignore] -fn test_exceptional_centuries() { - process_leapyear_case(1600, true); - process_leapyear_case(2000, true); - process_leapyear_case(2400, true); -} - -#[test] -#[ignore] -fn test_years_1600_to_1699() { - let incorrect_years = (1600..1700) - .filter(|&year| leap::is_leap_year(year) != (year % 4 == 0)) - .collect::>(); - - if !incorrect_years.is_empty() { - panic!("incorrect result for years: {incorrect_years:?}"); - } +fn year_divisible_by_200_not_divisible_by_400_in_common_year() { + assert!(!is_leap_year(1800)); } diff --git a/exercises/practice/list-ops/.docs/instructions.append.md b/exercises/practice/list-ops/.docs/instructions.append.md new file mode 100644 index 000000000..cb2de8100 --- /dev/null +++ b/exercises/practice/list-ops/.docs/instructions.append.md @@ -0,0 +1,13 @@ +# Instructions append + +## Implementing your own list operations + +Rust has a rich iterator system, using them to solve this exercise is trivial. +Please attempt to solve the exercise without reaching for iterator methods like +`map()`, `flatten()`, `filter()`, `count()`, `fold()`, `chain()`, `rev()` and similar. +The `next()` and `next_back()` methods do not count. + +## Avoiding allocations + +The exercise is solvable without introducing any additional memory allocations in the solution. +See if you can create your own iterator type instead of creating an iterator from a collection. diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md new file mode 100644 index 000000000..ebc5dffed --- /dev/null +++ b/exercises/practice/list-ops/.docs/instructions.md @@ -0,0 +1,19 @@ +# Instructions + +Implement basic list operations. + +In functional languages list operations like `length`, `map`, and `reduce` are very common. +Implement a series of basic list operations, without using existing functions. + +The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include: + +- `append` (_given two lists, add all items in the second list to the end of the first list_); +- `concatenate` (_given a series of lists, combine all items in all lists into one flattened list_); +- `filter` (_given a predicate and a list, return the list of all items for which `predicate(item)` is True_); +- `length` (_given a list, return the total number of items within it_); +- `map` (_given a function and a list, return the list of the results of applying `function(item)` on all items_); +- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left_); +- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right_); +- `reverse` (_given a list, return a list with all the original items, but in reversed order_). + +Note, the ordering in which arguments are passed to the fold functions (`foldl`, `foldr`) is significant. diff --git a/exercises/practice/list-ops/.gitignore b/exercises/practice/list-ops/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/list-ops/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/list-ops/.meta/config.json b/exercises/practice/list-ops/.meta/config.json new file mode 100644 index 000000000..5590d9eb2 --- /dev/null +++ b/exercises/practice/list-ops/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "ellnix", + "senekor", + "Morgane55440" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/list_ops.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Implement basic list operations." +} diff --git a/exercises/practice/list-ops/.meta/example.rs b/exercises/practice/list-ops/.meta/example.rs new file mode 100644 index 000000000..157b04dbe --- /dev/null +++ b/exercises/practice/list-ops/.meta/example.rs @@ -0,0 +1,154 @@ +pub fn append(a: I, b: I) -> impl Iterator +where + I: Iterator, +{ + struct Append> { + a: I, + b: I, + } + + impl> Iterator for Append { + type Item = T; + + fn next(&mut self) -> Option { + self.a.next().or_else(|| self.b.next()) + } + } + + Append { a, b } +} + +pub fn concat(list: I) -> impl Iterator +where + NI: Iterator, + I: Iterator, +{ + struct Concat, NI: Iterator, T> { + nested_list: I, + cur: Option, + } + + impl, NI: Iterator, T> Iterator for Concat { + type Item = T; + + fn next(&mut self) -> Option { + if let Some(nested_iterator) = self.cur.as_mut() + && let Some(val) = nested_iterator.next() + { + return Some(val); + } + + if let Some(next_nested) = self.nested_list.next() { + self.cur = Some(next_nested); + self.next() + } else { + None + } + } + } + + Concat { + nested_list: list, + cur: None, + } +} + +pub fn filter(list: I, predicate: F) -> impl Iterator +where + I: Iterator, + F: Fn(&T) -> bool, +{ + struct Filter, T, F: Fn(&T) -> bool> { + list: I, + predicate: F, + } + + impl Iterator for Filter + where + I: Iterator, + F: Fn(&T) -> bool, + { + type Item = T; + + fn next(&mut self) -> Option { + self.list.find(|val| (self.predicate)(val)) + } + } + + Filter { list, predicate } +} + +pub fn length, T>(list: I) -> usize { + let mut len = 0; + + for _ in list { + len += 1; + } + + len +} + +pub fn map(list: I, function: F) -> impl Iterator +where + I: Iterator, + F: Fn(T) -> U, +{ + struct Map, F: Fn(T) -> U, T, U> { + list: I, + function: F, + } + + impl, F: Fn(T) -> U, T, U> Iterator for Map { + type Item = U; + + fn next(&mut self) -> Option { + self.list.next().map(&self.function) + } + } + + Map { list, function } +} + +pub fn foldl(list: I, initial: U, function: F) -> U +where + I: Iterator, + F: Fn(U, T) -> U, +{ + let mut result = initial; + + for item in list { + result = (function)(result, item) + } + + result +} + +pub fn foldr(mut list: I, initial: U, function: F) -> U +where + I: DoubleEndedIterator, + F: Fn(U, T) -> U, +{ + let mut result = initial; + + while let Some(item) = list.next_back() { + result = (function)(result, item) + } + + result +} + +pub fn reverse, T>(list: I) -> impl Iterator { + struct Reverse, T> { + list: I, + } + + impl, T> Iterator for Reverse { + type Item = T; + + fn next(&mut self) -> Option { + self.list.next_back() + } + } + + Reverse { list } +} diff --git a/exercises/practice/list-ops/.meta/test_template.tera b/exercises/practice/list-ops/.meta/test_template.tera new file mode 100644 index 000000000..9250e335c --- /dev/null +++ b/exercises/practice/list-ops/.meta/test_template.tera @@ -0,0 +1,78 @@ +use list_ops::*; + +{% for group in cases %} + +{% set first_exercise = group.cases | first %} +{% if first_exercise.property == "concat" %} +#[allow(clippy::zero_repeat_side_effects)] +{%- endif %} +mod {{ first_exercise.property | make_ident }} { + use super::*; + +{% for test in group.cases %} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { +{% filter replace(from="[", to="vec![") %} + + {%- for arg, value in test.input -%} + {% if arg == "function" %}{% continue %}{% endif %} + {%- set is_empty = value is iterable and value | length == 0 -%} + + let {{arg}} = + {% if is_empty %} + {% if test.property is starting_with("fold") %} + [0.0f64; 0] + {% elif test.property == "concat" %} + [[0i32; 0]; 0] + {% else %} + [0i32; 0] + {% endif %} + {% elif test.property is starting_with("fold") %} + {% if value is number %} + {{ test.input[arg] }}.0 + {% else %} + {{ test.input[arg] | as_str | replace(from=",", to=".0,") | replace(from="]", to=".0]") | replace(from="[.0]", to="[]") }} + {% endif %} + {% else %} + {{ test.input[arg] }} + {% endif -%} + {% if value is iterable %} + .into_iter() + {% set first_item = value | first %} + {% if test.property == "concat" or first_item is iterable %} + .map(Vec::into_iter) + {% endif %} + {% endif %}; + {%- endfor -%} + + let output = {{ test.property }}( + {% for arg, value in test.input %} + {% if arg == "function" %} + {{ value | replace(from="(", to="|") | replace(from=")", to="|") | replace(from=" ->", to="") | replace(from="modulo", to="%")}}, + {% else %} + {{arg}}, + {% endif %} + {% endfor %} + ); + + let expected = {{ test.expected }}{% if test.property is starting_with("fold") %}.0{% endif %}; + + assert_eq!( + output + {% if test.expected is iterable %} + {% set first_item = test.expected | first %} + {% if test.property != "concat" and first_item is iterable %} + .map(|subiter| subiter.collect::>()) + {% endif %} + .collect::>() + {% endif %}, + expected + ); + +{% endfilter %} + } +{% endfor -%} +} +{% endfor -%} diff --git a/exercises/practice/list-ops/.meta/tests.toml b/exercises/practice/list-ops/.meta/tests.toml new file mode 100644 index 000000000..08b1edc04 --- /dev/null +++ b/exercises/practice/list-ops/.meta/tests.toml @@ -0,0 +1,106 @@ +# 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. + +[485b9452-bf94-40f7-a3db-c3cf4850066a] +description = "append entries to a list and return the new list -> empty lists" + +[2c894696-b609-4569-b149-8672134d340a] +description = "append entries to a list and return the new list -> list to empty list" + +[e842efed-3bf6-4295-b371-4d67a4fdf19c] +description = "append entries to a list and return the new list -> empty list to list" + +[71dcf5eb-73ae-4a0e-b744-a52ee387922f] +description = "append entries to a list and return the new list -> non-empty lists" + +[28444355-201b-4af2-a2f6-5550227bde21] +description = "concatenate a list of lists -> empty list" + +[331451c1-9573-42a1-9869-2d06e3b389a9] +description = "concatenate a list of lists -> list of lists" + +[d6ecd72c-197f-40c3-89a4-aa1f45827e09] +description = "concatenate a list of lists -> list of nested lists" + +[0524fba8-3e0f-4531-ad2b-f7a43da86a16] +description = "filter list returning only values that satisfy the filter function -> empty list" + +[88494bd5-f520-4edb-8631-88e415b62d24] +description = "filter list returning only values that satisfy the filter function -> non-empty list" + +[1cf0b92d-8d96-41d5-9c21-7b3c37cb6aad] +description = "returns the length of a list -> empty list" + +[d7b8d2d9-2d16-44c4-9a19-6e5f237cb71e] +description = "returns the length of a list -> non-empty list" + +[c0bc8962-30e2-4bec-9ae4-668b8ecd75aa] +description = "return a list of elements whose values equal the list value transformed by the mapping function -> empty list" + +[11e71a95-e78b-4909-b8e4-60cdcaec0e91] +description = "return a list of elements whose values equal the list value transformed by the mapping function -> non-empty list" + +[613b20b7-1873-4070-a3a6-70ae5f50d7cc] +description = "folds (reduces) the given list from the left with a function -> empty list" +include = false + +[e56df3eb-9405-416a-b13a-aabb4c3b5194] +description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" +include = false + +[d2cf5644-aee1-4dfc-9b88-06896676fe27] +description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" +include = false + +[36549237-f765-4a4c-bfd9-5d3a8f7b07d2] +description = "folds (reduces) the given list from the left with a function -> empty list" +reimplements = "613b20b7-1873-4070-a3a6-70ae5f50d7cc" + +[7a626a3c-03ec-42bc-9840-53f280e13067] +description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" +reimplements = "e56df3eb-9405-416a-b13a-aabb4c3b5194" + +[d7fcad99-e88e-40e1-a539-4c519681f390] +description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" +reimplements = "d2cf5644-aee1-4dfc-9b88-06896676fe27" + +[aeb576b9-118e-4a57-a451-db49fac20fdc] +description = "folds (reduces) the given list from the right with a function -> empty list" +include = false + +[c4b64e58-313e-4c47-9c68-7764964efb8e] +description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" +include = false + +[be396a53-c074-4db3-8dd6-f7ed003cce7c] +description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" +include = false + +[17214edb-20ba-42fc-bda8-000a5ab525b0] +description = "folds (reduces) the given list from the right with a function -> empty list" +reimplements = "aeb576b9-118e-4a57-a451-db49fac20fdc" + +[e1c64db7-9253-4a3d-a7c4-5273b9e2a1bd] +description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" +reimplements = "c4b64e58-313e-4c47-9c68-7764964efb8e" + +[8066003b-f2ff-437e-9103-66e6df474844] +description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" +reimplements = "be396a53-c074-4db3-8dd6-f7ed003cce7c" + +[94231515-050e-4841-943d-d4488ab4ee30] +description = "reverse the elements of the list -> empty list" + +[fcc03d1e-42e0-4712-b689-d54ad761f360] +description = "reverse the elements of the list -> non-empty list" + +[40872990-b5b8-4cb8-9085-d91fc0d05d26] +description = "reverse the elements of the list -> list of lists is not flattened" diff --git a/exercises/practice/list-ops/Cargo.toml b/exercises/practice/list-ops/Cargo.toml new file mode 100644 index 000000000..2637d4f1c --- /dev/null +++ b/exercises/practice/list-ops/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "list_ops" +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/list-ops/src/lib.rs b/exercises/practice/list-ops/src/lib.rs new file mode 100644 index 000000000..a67c02ac1 --- /dev/null +++ b/exercises/practice/list-ops/src/lib.rs @@ -0,0 +1,70 @@ +/// Yields each item of a and then each item of b +pub fn append(_a: I, _b: J) -> impl Iterator +where + I: Iterator, + J: Iterator, +{ + // this empty iterator silences a compiler complaint that + // () doesn't implement Iterator + std::iter::from_fn(|| todo!()) +} + +/// Combines all items in all nested iterators inside into one flattened iterator +pub fn concat(_nested_iter: I) -> impl Iterator::Item> +where + I: Iterator, + I::Item: Iterator, +{ + // this empty iterator silences a compiler complaint that + // () doesn't implement Iterator + std::iter::from_fn(|| todo!()) +} + +/// Returns an iterator of all items in iter for which `predicate(item)` is true +pub fn filter(_iter: I, _predicate: F) -> impl Iterator +where + I: Iterator, + F: Fn(&I::Item) -> bool, +{ + // this empty iterator silences a compiler complaint that + // () doesn't implement Iterator + std::iter::from_fn(|| todo!()) +} + +pub fn length(_iter: I) -> usize { + todo!("return the total number of items within iter") +} + +/// Returns an iterator of the results of applying `function(item)` on all iter items +pub fn map(_iter: I, _function: F) -> impl Iterator +where + I: Iterator, + F: Fn(I::Item) -> U, +{ + // this empty iterator silences a compiler complaint that + // () doesn't implement Iterator + std::iter::from_fn(|| todo!()) +} + +pub fn foldl(mut _iter: I, _initial: U, _function: F) -> U +where + I: Iterator, + F: Fn(U, I::Item) -> U, +{ + todo!("starting with initial, fold (reduce) each iter item into the accumulator from the left") +} + +pub fn foldr(mut _iter: I, _initial: U, _function: F) -> U +where + I: DoubleEndedIterator, + F: Fn(U, I::Item) -> U, +{ + todo!("starting with initial, fold (reduce) each iter item into the accumulator from the right") +} + +/// Returns an iterator with all the original items, but in reverse order +pub fn reverse(_iter: I) -> impl Iterator { + // this empty iterator silences a compiler complaint that + // () doesn't implement Iterator + std::iter::from_fn(|| todo!()) +} diff --git a/exercises/practice/list-ops/tests/list_ops.rs b/exercises/practice/list-ops/tests/list_ops.rs new file mode 100644 index 000000000..96c1c654d --- /dev/null +++ b/exercises/practice/list-ops/tests/list_ops.rs @@ -0,0 +1,301 @@ +use list_ops::*; + +mod append { + use super::*; + + #[test] + fn empty_lists() { + let list1 = vec![0i32; 0].into_iter(); + let list2 = vec![0i32; 0].into_iter(); + let output = append(list1, list2); + + let expected = vec![]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn list_to_empty_list() { + let list1 = vec![0i32; 0].into_iter(); + let list2 = vec![1, 2, 3, 4].into_iter(); + let output = append(list1, list2); + + let expected = vec![1, 2, 3, 4]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn empty_list_to_list() { + let list1 = vec![1, 2, 3, 4].into_iter(); + let list2 = vec![0i32; 0].into_iter(); + let output = append(list1, list2); + + let expected = vec![1, 2, 3, 4]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn non_empty_lists() { + let list1 = vec![1, 2].into_iter(); + let list2 = vec![2, 3, 4, 5].into_iter(); + let output = append(list1, list2); + + let expected = vec![1, 2, 2, 3, 4, 5]; + + assert_eq!(output.collect::>(), expected); + } +} + +#[allow(clippy::zero_repeat_side_effects)] +mod concat { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let lists = vec![vec![0i32; 0]; 0].into_iter().map(Vec::into_iter); + let output = concat(lists); + + let expected = vec![]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn list_of_lists() { + let lists = vec![vec![1, 2], vec![3], vec![], vec![4, 5, 6]] + .into_iter() + .map(Vec::into_iter); + let output = concat(lists); + + let expected = vec![1, 2, 3, 4, 5, 6]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn list_of_nested_lists() { + let lists = vec![ + vec![vec![1], vec![2]], + vec![vec![3]], + vec![vec![]], + vec![vec![4, 5, 6]], + ] + .into_iter() + .map(Vec::into_iter); + let output = concat(lists); + + let expected = vec![vec![1], vec![2], vec![3], vec![], vec![4, 5, 6]]; + + assert_eq!(output.collect::>(), expected); + } +} + +mod filter { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0i32; 0].into_iter(); + let output = filter(list, |x| x % 2 == 1); + + let expected = vec![]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn non_empty_list() { + let list = vec![1, 2, 3, 5].into_iter(); + let output = filter(list, |x| x % 2 == 1); + + let expected = vec![1, 3, 5]; + + assert_eq!(output.collect::>(), expected); + } +} + +mod length { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0i32; 0].into_iter(); + let output = length(list); + + let expected = 0; + + assert_eq!(output, expected); + } + + #[test] + #[ignore] + fn non_empty_list() { + let list = vec![1, 2, 3, 4].into_iter(); + let output = length(list); + + let expected = 4; + + assert_eq!(output, expected); + } +} + +mod map { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0i32; 0].into_iter(); + let output = map(list, |x| x + 1); + + let expected = vec![]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn non_empty_list() { + let list = vec![1, 3, 5, 7].into_iter(); + let output = map(list, |x| x + 1); + + let expected = vec![2, 4, 6, 8]; + + assert_eq!(output.collect::>(), expected); + } +} + +mod foldl { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0.0f64; 0].into_iter(); + let initial = 2.0; + let output = foldl(list, initial, |acc, el| el * acc); + + let expected = 2.0; + + assert_eq!(output, expected); + } + + #[test] + #[ignore] + fn direction_independent_function_applied_to_non_empty_list() { + let list = vec![1.0, 2.0, 3.0, 4.0].into_iter(); + let initial = 5.0; + let output = foldl(list, initial, |acc, el| el + acc); + + let expected = 15.0; + + assert_eq!(output, expected); + } + + #[test] + #[ignore] + fn direction_dependent_function_applied_to_non_empty_list() { + let list = vec![1.0, 2.0, 3.0, 4.0].into_iter(); + let initial = 24.0; + let output = foldl(list, initial, |acc, el| el / acc); + + let expected = 64.0; + + assert_eq!(output, expected); + } +} + +mod foldr { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0.0f64; 0].into_iter(); + let initial = 2.0; + let output = foldr(list, initial, |acc, el| el * acc); + + let expected = 2.0; + + assert_eq!(output, expected); + } + + #[test] + #[ignore] + fn direction_independent_function_applied_to_non_empty_list() { + let list = vec![1.0, 2.0, 3.0, 4.0].into_iter(); + let initial = 5.0; + let output = foldr(list, initial, |acc, el| el + acc); + + let expected = 15.0; + + assert_eq!(output, expected); + } + + #[test] + #[ignore] + fn direction_dependent_function_applied_to_non_empty_list() { + let list = vec![1.0, 2.0, 3.0, 4.0].into_iter(); + let initial = 24.0; + let output = foldr(list, initial, |acc, el| el / acc); + + let expected = 9.0; + + assert_eq!(output, expected); + } +} + +mod reverse { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0i32; 0].into_iter(); + let output = reverse(list); + + let expected = vec![]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn non_empty_list() { + let list = vec![1, 3, 5, 7].into_iter(); + let output = reverse(list); + + let expected = vec![7, 5, 3, 1]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn list_of_lists_is_not_flattened() { + let list = vec![vec![1, 2], vec![3], vec![], vec![4, 5, 6]] + .into_iter() + .map(Vec::into_iter); + let output = reverse(list); + + let expected = vec![vec![4, 5, 6], vec![], vec![3], vec![1, 2]]; + + assert_eq!( + output + .map(|subiter| subiter.collect::>()) + .collect::>(), + expected + ); + } +} diff --git a/exercises/practice/luhn-from/.gitignore b/exercises/practice/luhn-from/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/luhn-from/.gitignore +++ b/exercises/practice/luhn-from/.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/luhn-from/.meta/config.json b/exercises/practice/luhn-from/.meta/config.json index b1e6dff08..ab8da1029 100644 --- a/exercises/practice/luhn-from/.meta/config.json +++ b/exercises/practice/luhn-from/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/luhn-from.rs" + "tests/luhn_from.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/luhn-from/.meta/example.rs b/exercises/practice/luhn-from/.meta/example.rs index 1cfd286a7..49f6436ef 100644 --- a/exercises/practice/luhn-from/.meta/example.rs +++ b/exercises/practice/luhn-from/.meta/example.rs @@ -4,7 +4,7 @@ pub struct Luhn { impl Luhn { pub fn is_valid(&self) -> bool { - if self.digits.iter().any(|c| c.is_alphabetic()) || self.digits.iter().count() == 1 { + if self.digits.iter().any(|c| c.is_alphabetic()) || self.digits.len() == 1 { return false; } diff --git a/exercises/practice/luhn-from/Cargo.toml b/exercises/practice/luhn-from/Cargo.toml index 2254dfa38..d5437b4df 100644 --- a/exercises/practice/luhn-from/Cargo.toml +++ b/exercises/practice/luhn-from/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "luhn-from" -version = "0.0.0" +name = "luhn_from" +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/luhn-from/src/lib.rs b/exercises/practice/luhn-from/src/lib.rs index c0ca85945..169f7e666 100644 --- a/exercises/practice/luhn-from/src/lib.rs +++ b/exercises/practice/luhn-from/src/lib.rs @@ -2,17 +2,17 @@ pub struct Luhn; impl Luhn { pub fn is_valid(&self) -> bool { - unimplemented!("Determine if the current Luhn struct contains a valid credit card number."); + todo!("Determine if the current Luhn struct contains a valid credit card number."); } } /// Here is the example of how the From trait could be implemented /// for the &str type. Naturally, you can implement this trait -/// by hand for the every other type presented in the test suite, +/// by hand for every other type presented in the test suite, /// but your solution will fail if a new type is presented. /// Perhaps there exists a better solution for this problem? -impl<'a> From<&'a str> for Luhn { - fn from(input: &'a str) -> Self { - unimplemented!("From the given input '{input}' create a new Luhn struct."); +impl From<&str> for Luhn { + fn from(input: &str) -> Self { + todo!("From the given input '{input}' create a new Luhn struct."); } } diff --git a/exercises/practice/luhn-from/tests/luhn-from.rs b/exercises/practice/luhn-from/tests/luhn_from.rs similarity index 94% rename from exercises/practice/luhn-from/tests/luhn-from.rs rename to exercises/practice/luhn-from/tests/luhn_from.rs index a6224b646..b5f42b3d9 100644 --- a/exercises/practice/luhn-from/tests/luhn-from.rs +++ b/exercises/practice/luhn-from/tests/luhn_from.rs @@ -97,3 +97,9 @@ fn invalid_credit_card_is_invalid() { fn strings_that_contain_non_digits_are_invalid() { assert!(!Luhn::from("046a 454 286").is_valid()); } + +#[test] +#[ignore] +fn input_digit_9_is_still_correctly_converted_to_output_digit_9() { + assert!(Luhn::from("091").is_valid()); +} diff --git a/exercises/practice/luhn-trait/.docs/instructions.md b/exercises/practice/luhn-trait/.docs/instructions.md index 7e203cbcb..cbbd4f98e 100644 --- a/exercises/practice/luhn-trait/.docs/instructions.md +++ b/exercises/practice/luhn-trait/.docs/instructions.md @@ -20,4 +20,4 @@ In "Luhn: Using the From Trait" you implemented a From trait, which also require Instead of creating a Struct just to perform the validation, what if you validated the primitives (i.e, String, u8, etc.) themselves? -In this exercise you'll create and implement a custom [trait](https://doc.rust-lang.org/book/2018-edition/ch10-02-traits.html) that performs the validation. +In this exercise you'll create and implement a custom [trait](https://doc.rust-lang.org/book/ch10-02-traits.html) that performs the validation. diff --git a/exercises/practice/luhn-trait/.gitignore b/exercises/practice/luhn-trait/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/luhn-trait/.gitignore +++ b/exercises/practice/luhn-trait/.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/luhn-trait/.meta/config.json b/exercises/practice/luhn-trait/.meta/config.json index a8b53df2e..9d4db11fa 100644 --- a/exercises/practice/luhn-trait/.meta/config.json +++ b/exercises/practice/luhn-trait/.meta/config.json @@ -25,7 +25,7 @@ "Cargo.toml" ], "test": [ - "tests/luhn-trait.rs" + "tests/luhn_trait.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/luhn-trait/.meta/example.rs b/exercises/practice/luhn-trait/.meta/example.rs index ed71406fd..d5ab8c444 100644 --- a/exercises/practice/luhn-trait/.meta/example.rs +++ b/exercises/practice/luhn-trait/.meta/example.rs @@ -20,7 +20,7 @@ impl Luhn for String { } } -impl<'a> Luhn for &'a str { +impl Luhn for &str { fn valid_luhn(&self) -> bool { String::from(*self).valid_luhn() } diff --git a/exercises/practice/luhn-trait/Cargo.toml b/exercises/practice/luhn-trait/Cargo.toml index 75fa75d93..ff34e58ee 100644 --- a/exercises/practice/luhn-trait/Cargo.toml +++ b/exercises/practice/luhn-trait/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "luhn-trait" -version = "0.0.0" +name = "luhn_trait" +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/luhn-trait/src/lib.rs b/exercises/practice/luhn-trait/src/lib.rs index a7aa54218..ccf07bbf8 100644 --- a/exercises/practice/luhn-trait/src/lib.rs +++ b/exercises/practice/luhn-trait/src/lib.rs @@ -4,11 +4,11 @@ pub trait Luhn { /// Here is the example of how to implement custom Luhn trait /// for the &str type. Naturally, you can implement this trait -/// by hand for the every other type presented in the test suite, +/// by hand for every other type presented in the test suite, /// but your solution will fail if a new type is presented. /// Perhaps there exists a better solution for this problem? -impl<'a> Luhn for &'a str { +impl Luhn for &str { fn valid_luhn(&self) -> bool { - unimplemented!("Determine if '{self}' is a valid credit card number."); + todo!("Determine if '{self}' is a valid credit card number."); } } diff --git a/exercises/practice/luhn-trait/tests/luhn-trait.rs b/exercises/practice/luhn-trait/tests/luhn_trait.rs similarity index 90% rename from exercises/practice/luhn-trait/tests/luhn-trait.rs rename to exercises/practice/luhn-trait/tests/luhn_trait.rs index 1fbc60fad..dd0454c17 100644 --- a/exercises/practice/luhn-trait/tests/luhn-trait.rs +++ b/exercises/practice/luhn-trait/tests/luhn_trait.rs @@ -55,3 +55,9 @@ fn you_can_validate_from_a_usize() { assert!(valid.valid_luhn()); assert!(!invalid.valid_luhn()); } + +#[test] +#[ignore] +fn input_digit_9_is_still_correctly_converted_to_output_digit_9() { + assert!("091".valid_luhn()); +} diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index c7c7d3e0f..7702c6bbb 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -1,65 +1,68 @@ # Instructions -Given a number determine whether or not it is valid per the Luhn formula. +Determine whether a number is valid according to the [Luhn formula][luhn]. -The [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) is -a simple checksum formula used to validate a variety of identification -numbers, such as credit card numbers and Canadian Social Insurance -Numbers. +The number will be provided as a string. -The task is to check if a given string is valid. +## Validating a number -Validating a Number ------- +Strings of length 1 or less are not valid. +Spaces are allowed in the input, but they should be stripped before checking. +All other non-digit characters are disallowed. -Strings of length 1 or less are not valid. Spaces are allowed in the input, -but they should be stripped before checking. All other non-digit characters -are disallowed. +## Examples -## Example 1: valid credit card number +### Valid credit card number -```text -4539 3195 0343 6467 -``` +The number to be checked is `4539 3195 0343 6467`. -The first step of the Luhn algorithm is to double every second digit, -starting from the right. We will be doubling +The first step of the Luhn algorithm is to start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text -4_3_ 3_9_ 0_4_ 6_6_ +4539 3195 0343 6467 +↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ (double these) ``` -If doubling the number results in a number greater than 9 then subtract 9 -from the product. The results of our doubling: +If the result of doubling a digit is greater than 9, we subtract 9 from that result. +We end up with: ```text 8569 6195 0383 3437 ``` -Then sum all of the digits: +Finally, we sum all digits. +If the sum is evenly divisible by 10, the original number is valid. ```text -8+5+6+9+6+1+9+5+0+3+8+3+3+4+3+7 = 80 +8 + 5 + 6 + 9 + 6 + 1 + 9 + 5 + 0 + 3 + 8 + 3 + 3 + 4 + 3 + 7 = 80 ``` -If the sum is evenly divisible by 10, then the number is valid. This number is valid! +80 is evenly divisible by 10, so number `4539 3195 0343 6467` is valid! -## Example 2: invalid credit card number +### Invalid Canadian SIN + +The number to be checked is `066 123 478`. + +We start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text -8273 1232 7352 0569 +066 123 478 + ↑ ↑ ↑ ↑ (double these) ``` -Double the second digits, starting from the right +If the result of doubling a digit is greater than 9, we subtract 9 from that result. +We end up with: ```text -7253 2262 5312 0539 +036 226 458 ``` -Sum the digits +We sum the digits: ```text -7+2+5+3+2+2+6+2+5+3+1+2+0+5+3+9 = 57 +0 + 3 + 6 + 2 + 2 + 6 + 4 + 5 + 8 = 36 ``` -57 is not evenly divisible by 10, so this number is not valid. +36 is not evenly divisible by 10, so number `066 123 478` is not valid! + +[luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm diff --git a/exercises/practice/luhn/.docs/introduction.md b/exercises/practice/luhn/.docs/introduction.md new file mode 100644 index 000000000..dee48006e --- /dev/null +++ b/exercises/practice/luhn/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +At the Global Verification Authority, you've just been entrusted with a critical assignment. +Across the city, from online purchases to secure logins, countless operations rely on the accuracy of numerical identifiers like credit card numbers, bank account numbers, transaction codes, and tracking IDs. +The Luhn algorithm is a simple checksum formula used to help identify mistyped numbers. + +A batch of identifiers has just arrived on your desk. +All of them must pass the Luhn test to ensure they're legitimate. +If any fail, they'll be flagged as invalid, preventing mistakes such as incorrect transactions or failed account verifications. + +Can you ensure this is done right? The integrity of many services depends on you. diff --git a/exercises/practice/luhn/.gitignore b/exercises/practice/luhn/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/luhn/.gitignore +++ b/exercises/practice/luhn/.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/luhn/.meta/config.json b/exercises/practice/luhn/.meta/config.json index 01a41e8e3..f8b02e36c 100644 --- a/exercises/practice/luhn/.meta/config.json +++ b/exercises/practice/luhn/.meta/config.json @@ -37,5 +37,5 @@ }, "blurb": "Given a number determine whether or not it is valid per the Luhn formula.", "source": "The Luhn Algorithm on Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Luhn_algorithm" + "source_url": "https://en.wikipedia.org/wiki/Luhn_algorithm" } diff --git a/exercises/practice/luhn/.meta/example.rs b/exercises/practice/luhn/.meta/example.rs index 4308887d6..d61897eff 100644 --- a/exercises/practice/luhn/.meta/example.rs +++ b/exercises/practice/luhn/.meta/example.rs @@ -1,6 +1,11 @@ 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/exercises/practice/luhn/.meta/test_template.tera b/exercises/practice/luhn/.meta/test_template.tera new file mode 100644 index 000000000..d726197ed --- /dev/null +++ b/exercises/practice/luhn/.meta/test_template.tera @@ -0,0 +1,9 @@ +use luhn::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert!({% if not test.expected %} ! {% endif %}is_valid({{ test.input.value | json_encode() }})); +} +{% endfor -%} diff --git a/exercises/practice/luhn/.meta/tests.toml b/exercises/practice/luhn/.meta/tests.toml index be690e975..c0be0c4d9 100644 --- a/exercises/practice/luhn/.meta/tests.toml +++ b/exercises/practice/luhn/.meta/tests.toml @@ -1,3 +1,76 @@ -# 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. + +[792a7082-feb7-48c7-b88b-bbfec160865e] +description = "single digit strings can not be valid" + +[698a7924-64d4-4d89-8daa-32e1aadc271e] +description = "a single zero is invalid" + +[73c2f62b-9b10-4c9f-9a04-83cee7367965] +description = "a simple valid SIN that remains valid if reversed" + +[9369092e-b095-439f-948d-498bd076be11] +description = "a simple valid SIN that becomes invalid if reversed" + +[8f9f2350-1faf-4008-ba84-85cbb93ffeca] +description = "a valid Canadian SIN" + +[1cdcf269-6560-44fc-91f6-5819a7548737] +description = "invalid Canadian SIN" + +[656c48c1-34e8-4e60-9a5a-aad8a367810a] +description = "invalid credit card" + +[20e67fad-2121-43ed-99a8-14b5b856adb9] +description = "invalid long number with an even remainder" + +[7e7c9fc1-d994-457c-811e-d390d52fba5e] +description = "invalid long number with a remainder divisible by 5" + +[ad2a0c5f-84ed-4e5b-95da-6011d6f4f0aa] +description = "valid number with an even number of digits" + +[ef081c06-a41f-4761-8492-385e13c8202d] +description = "valid number with an odd number of spaces" + +[bef66f64-6100-4cbb-8f94-4c9713c5e5b2] +description = "valid strings with a non-digit added at the end become invalid" + +[2177e225-9ce7-40f6-b55d-fa420e62938e] +description = "valid strings with punctuation included become invalid" + +[ebf04f27-9698-45e1-9afe-7e0851d0fe8d] +description = "valid strings with symbols included become invalid" + +[08195c5e-ce7f-422c-a5eb-3e45fece68ba] +description = "single zero with space is invalid" + +[12e63a3c-f866-4a79-8c14-b359fc386091] +description = "more than a single zero is valid" + +[ab56fa80-5de8-4735-8a4a-14dae588663e] +description = "input digit 9 is correctly converted to output digit 9" + +[b9887ee8-8337-46c5-bc45-3bcab51bc36f] +description = "very long input is valid" + +[8a7c0e24-85ea-4154-9cf1-c2db90eabc08] +description = "valid luhn with an odd number of digits and non zero first digit" + +[39a06a5a-5bad-4e0f-b215-b042d46209b1] +description = "using ascii value for non-doubled non-digit isn't allowed" + +[f94cf191-a62f-4868-bc72-7253114aa157] +description = "using ascii value for doubled non-digit isn't allowed" + +[8b72ad26-c8be-49a2-b99c-bcc3bf631b33] +description = "non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed" diff --git a/exercises/practice/luhn/Cargo.toml b/exercises/practice/luhn/Cargo.toml index 2c4011487..4a36628c1 100644 --- a/exercises/practice/luhn/Cargo.toml +++ b/exercises/practice/luhn/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "luhn" -version = "1.6.1" +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/luhn/src/lib.rs b/exercises/practice/luhn/src/lib.rs index 858a086e6..38e363f13 100644 --- a/exercises/practice/luhn/src/lib.rs +++ b/exercises/practice/luhn/src/lib.rs @@ -1,4 +1,4 @@ /// Check a Luhn checksum. pub fn is_valid(code: &str) -> bool { - unimplemented!("Is the Luhn checksum for {code} valid?"); + todo!("Is the Luhn checksum for {code} valid?"); } diff --git a/exercises/practice/luhn/tests/luhn.rs b/exercises/practice/luhn/tests/luhn.rs index 1eab9c40f..15fb8e8e6 100644 --- a/exercises/practice/luhn/tests/luhn.rs +++ b/exercises/practice/luhn/tests/luhn.rs @@ -1,141 +1,132 @@ use luhn::*; -fn process_valid_case(number: &str, is_luhn_expected: bool) { - assert_eq!(is_valid(number), is_luhn_expected); +#[test] +fn single_digit_strings_can_not_be_valid() { + assert!(!is_valid("1")); } #[test] -fn test_single_digit_strings_can_not_be_valid() { - process_valid_case("1", false); +#[ignore] +fn a_single_zero_is_invalid() { + assert!(!is_valid("0")); } #[test] #[ignore] -fn test_a_single_zero_is_invalid() { - process_valid_case("0", false); +fn a_simple_valid_sin_that_remains_valid_if_reversed() { + assert!(is_valid("059")); } #[test] #[ignore] -fn test_a_simple_valid_sin_that_remains_valid_if_reversed() { - process_valid_case("059", true); +fn a_simple_valid_sin_that_becomes_invalid_if_reversed() { + assert!(is_valid("59")); } #[test] #[ignore] -fn test_a_simple_valid_sin_that_becomes_invalid_if_reversed() { - process_valid_case("59", true); +fn a_valid_canadian_sin() { + assert!(is_valid("055 444 285")); } #[test] #[ignore] -fn test_a_valid_canadian_sin() { - process_valid_case("055 444 285", true); +fn invalid_canadian_sin() { + assert!(!is_valid("055 444 286")); } #[test] #[ignore] -fn test_invalid_canadian_sin() { - process_valid_case("055 444 286", false); +fn invalid_credit_card() { + assert!(!is_valid("8273 1232 7352 0569")); } #[test] #[ignore] -fn test_invalid_credit_card() { - process_valid_case("8273 1232 7352 0569", false); +fn invalid_long_number_with_an_even_remainder() { + assert!(!is_valid("1 2345 6789 1234 5678 9012")); } #[test] #[ignore] -fn test_valid_number_with_an_even_number_of_digits() { - process_valid_case("095 245 88", true); +fn invalid_long_number_with_a_remainder_divisible_by_5() { + assert!(!is_valid("1 2345 6789 1234 5678 9013")); } #[test] #[ignore] -fn strings_that_contain_non_digits_are_invalid() { - process_valid_case("055a 444 285", false); +fn valid_number_with_an_even_number_of_digits() { + assert!(is_valid("095 245 88")); } #[test] #[ignore] -fn test_valid_strings_with_punctuation_included_become_invalid() { - process_valid_case("055-444-285", false); +fn valid_number_with_an_odd_number_of_spaces() { + assert!(is_valid("234 567 891 234")); } #[test] #[ignore] -fn symbols_are_invalid() { - process_valid_case("055£ 444$ 285", false); +fn valid_strings_with_a_non_digit_added_at_the_end_become_invalid() { + assert!(!is_valid("059a")); } #[test] #[ignore] -fn test_single_zero_with_space_is_invalid() { - process_valid_case(" 0", false); +fn valid_strings_with_punctuation_included_become_invalid() { + assert!(!is_valid("055-444-285")); } #[test] #[ignore] -fn test_more_than_a_single_zero_is_valid() { - process_valid_case("0000 0", true); +fn valid_strings_with_symbols_included_become_invalid() { + assert!(!is_valid("055# 444$ 285")); } #[test] #[ignore] -fn test_input_digit_9_is_correctly_converted_to_output_digit_9() { - process_valid_case("091", true); +fn single_zero_with_space_is_invalid() { + assert!(!is_valid(" 0")); } #[test] #[ignore] -/// using ASCII value for doubled non-digit isn't allowed -/// Convert non-digits to their ASCII values and then offset them by 48 sometimes accidentally declare an invalid string to be valid. -/// This test is designed to avoid that solution. -fn test_using_ascii_value_for_doubled_nondigit_isnt_allowed() { - process_valid_case(":9", false); +fn more_than_a_single_zero_is_valid() { + assert!(is_valid("0000 0")); } #[test] #[ignore] -/// valid strings with a non-digit added at the end become invalid -fn test_valid_strings_with_a_nondigit_added_at_the_end_become_invalid() { - process_valid_case("059a", false); +fn input_digit_9_is_correctly_converted_to_output_digit_9() { + assert!(is_valid("091")); } #[test] #[ignore] -/// valid strings with symbols included become invalid -fn test_valid_strings_with_symbols_included_become_invalid() { - process_valid_case("055# 444$ 285", false); +fn very_long_input_is_valid() { + assert!(is_valid("9999999999 9999999999 9999999999 9999999999")); } #[test] #[ignore] -/// using ASCII value for non-doubled non-digit isn't allowed -/// Convert non-digits to their ASCII values and then offset them by 48 sometimes accidentally declare an invalid string to be valid. -/// This test is designed to avoid that solution. -fn test_using_ascii_value_for_nondoubled_nondigit_isnt_allowed() { - process_valid_case("055b 444 285", false); +fn valid_luhn_with_an_odd_number_of_digits_and_non_zero_first_digit() { + assert!(is_valid("109")); } #[test] #[ignore] -/// valid number with an odd number of spaces -fn test_valid_number_with_an_odd_number_of_spaces() { - process_valid_case("234 567 891 234", true); +fn using_ascii_value_for_non_doubled_non_digit_isn_t_allowed() { + assert!(!is_valid("055b 444 285")); } #[test] #[ignore] -/// non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed -fn test_invalid_char_in_middle_with_sum_divisible_by_10_isnt_allowed() { - process_valid_case("59%59", false); +fn using_ascii_value_for_doubled_non_digit_isn_t_allowed() { + assert!(!is_valid(":9")); } #[test] #[ignore] -/// unicode numeric characters are not allowed in a otherwise valid number -fn test_valid_strings_with_numeric_unicode_characters_become_invalid() { - process_valid_case("1249①", false); +fn non_numeric_non_space_char_in_the_middle_with_a_sum_that_s_divisible_by_10_isn_t_allowed() { + assert!(!is_valid("59%59")); } diff --git a/exercises/practice/macros/.docs/instructions.append.md b/exercises/practice/macros/.docs/instructions.append.md deleted file mode 100644 index 4c02cfac6..000000000 --- a/exercises/practice/macros/.docs/instructions.append.md +++ /dev/null @@ -1,3 +0,0 @@ -# Compatibility - -Note that this exercise requires Rust 1.36 or later. diff --git a/exercises/practice/macros/.gitignore b/exercises/practice/macros/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/macros/.gitignore +++ b/exercises/practice/macros/.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/macros/Cargo.toml b/exercises/practice/macros/Cargo.toml index 8c82d2e60..8161a0872 100644 --- a/exercises/practice/macros/Cargo.toml +++ b/exercises/practice/macros/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "macros" 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/macros/src/lib.rs b/exercises/practice/macros/src/lib.rs index bf7d88812..a4c8c16dc 100644 --- a/exercises/practice/macros/src/lib.rs +++ b/exercises/practice/macros/src/lib.rs @@ -1,6 +1,6 @@ #[macro_export] macro_rules! hashmap { () => { - unimplemented!() + todo!() }; } diff --git a/exercises/practice/macros/tests/invalid/Cargo.toml b/exercises/practice/macros/tests/invalid/Cargo.toml index 824de5628..ca30c0bae 100644 --- a/exercises/practice/macros/tests/invalid/Cargo.toml +++ b/exercises/practice/macros/tests/invalid/Cargo.toml @@ -15,41 +15,41 @@ path = "../../" default-features = false [[bin]] -name = "comma-sep-rs" -path = "comma-sep.rs" +name = "comma_sep" +path = "comma_sep.rs" [[bin]] -name = "double-commas-rs" -path = "double-commas.rs" +name = "double_commas" +path = "double_commas.rs" [[bin]] -name = "only-arrow-rs" -path = "only-arrow.rs" +name = "only_arrow" +path = "only_arrow.rs" [[bin]] -name = "only-comma-rs" -path = "only-comma.rs" +name = "only_comma" +path = "only_comma.rs" [[bin]] -name = "single-argument-rs" -path = "single-argument.rs" +name = "single_argument" +path = "single_argument.rs" [[bin]] -name = "triple-arguments-rs" -path = "triple-arguments.rs" +name = "triple_arguments" +path = "triple_arguments.rs" [[bin]] -name = "two-arrows-rs" -path = "two-arrows.rs" +name = "two_arrows" +path = "two_arrows.rs" [[bin]] -name = "leading-comma-rs" -path = "leading-comma.rs" +name = "leading_comma" +path = "leading_comma.rs" [[bin]] -name = "no-comma-rs" -path = "no-comma.rs" +name = "no_comma" +path = "no_comma.rs" [[bin]] -name = "missing-argument-rs" -path = "missing-argument.rs" +name = "missing_argument" +path = "missing_argument.rs" diff --git a/exercises/practice/macros/tests/invalid/comma-sep.rs b/exercises/practice/macros/tests/invalid/comma_sep.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/comma-sep.rs rename to exercises/practice/macros/tests/invalid/comma_sep.rs diff --git a/exercises/practice/macros/tests/invalid/double-commas.rs b/exercises/practice/macros/tests/invalid/double_commas.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/double-commas.rs rename to exercises/practice/macros/tests/invalid/double_commas.rs diff --git a/exercises/practice/macros/tests/invalid/leading-comma.rs b/exercises/practice/macros/tests/invalid/leading_comma.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/leading-comma.rs rename to exercises/practice/macros/tests/invalid/leading_comma.rs diff --git a/exercises/practice/macros/tests/invalid/missing-argument.rs b/exercises/practice/macros/tests/invalid/missing_argument.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/missing-argument.rs rename to exercises/practice/macros/tests/invalid/missing_argument.rs diff --git a/exercises/practice/macros/tests/invalid/no-comma.rs b/exercises/practice/macros/tests/invalid/no_comma.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/no-comma.rs rename to exercises/practice/macros/tests/invalid/no_comma.rs diff --git a/exercises/practice/macros/tests/invalid/only-arrow.rs b/exercises/practice/macros/tests/invalid/only_arrow.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/only-arrow.rs rename to exercises/practice/macros/tests/invalid/only_arrow.rs diff --git a/exercises/practice/macros/tests/invalid/only-comma.rs b/exercises/practice/macros/tests/invalid/only_comma.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/only-comma.rs rename to exercises/practice/macros/tests/invalid/only_comma.rs diff --git a/exercises/practice/macros/tests/invalid/single-argument.rs b/exercises/practice/macros/tests/invalid/single_argument.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/single-argument.rs rename to exercises/practice/macros/tests/invalid/single_argument.rs diff --git a/exercises/practice/macros/tests/invalid/triple-arguments.rs b/exercises/practice/macros/tests/invalid/triple_arguments.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/triple-arguments.rs rename to exercises/practice/macros/tests/invalid/triple_arguments.rs diff --git a/exercises/practice/macros/tests/invalid/two-arrows.rs b/exercises/practice/macros/tests/invalid/two_arrows.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/two-arrows.rs rename to exercises/practice/macros/tests/invalid/two_arrows.rs diff --git a/exercises/practice/macros/tests/macros.rs b/exercises/practice/macros/tests/macros.rs index b546174f2..10c91283a 100644 --- a/exercises/practice/macros/tests/macros.rs +++ b/exercises/practice/macros/tests/macros.rs @@ -2,7 +2,7 @@ use macros::hashmap; use std::collections::HashMap; #[test] -fn test_empty() { +fn empty() { let expected: HashMap = HashMap::new(); let computed: HashMap = hashmap!(); assert_eq!(computed, expected); @@ -10,7 +10,7 @@ fn test_empty() { #[test] #[ignore] -fn test_single() { +fn single() { let mut expected = HashMap::new(); expected.insert(1, "one"); assert_eq!(hashmap!(1 => "one"), expected); @@ -18,7 +18,7 @@ fn test_single() { #[test] #[ignore] -fn test_no_trailing_comma() { +fn no_trailing_comma() { let mut expected = HashMap::new(); expected.insert(1, "one"); expected.insert(2, "two"); @@ -27,7 +27,7 @@ fn test_no_trailing_comma() { #[test] #[ignore] -fn test_trailing_comma() { +fn trailing_comma() { let mut expected = HashMap::new(); expected.insert('h', 89); expected.insert('a', 1); @@ -46,7 +46,7 @@ fn test_trailing_comma() { #[test] #[ignore] -fn test_nested() { +fn nested() { let mut expected = HashMap::new(); expected.insert("non-empty", { let mut subhashmap = HashMap::new(); @@ -80,7 +80,7 @@ mod test { #[test] #[ignore] - fn test_macro_out_of_scope() { + fn macro_out_of_scope() { let _empty: ::std::collections::HashMap<(), ()> = macros::hashmap!(); let _without_comma = macros::hashmap!(23=> 623, 34 => 21); let _with_trailing = macros::hashmap!(23 => 623, 34 => 21,); @@ -89,7 +89,7 @@ mod test { #[test] #[ignore] -fn test_type_override() { +fn type_override() { // The macro should always use std::collections::HashMap and ignore crate::std::collections::HashMap mod std { pub mod collections { @@ -116,62 +116,62 @@ fn test_type_override() { #[test] #[ignore] -fn test_compile_fails_comma_sep() { - simple_trybuild::compile_fail("comma-sep.rs"); +fn compile_fails_comma_sep() { + simple_trybuild::compile_fail("comma_sep.rs"); } #[test] #[ignore] -fn test_compile_fails_double_commas() { - simple_trybuild::compile_fail("double-commas.rs"); +fn compile_fails_double_commas() { + simple_trybuild::compile_fail("double_commas.rs"); } #[test] #[ignore] -fn test_compile_fails_only_comma() { - simple_trybuild::compile_fail("only-comma.rs"); +fn compile_fails_only_comma() { + simple_trybuild::compile_fail("only_comma.rs"); } #[test] #[ignore] -fn test_compile_fails_single_argument() { - simple_trybuild::compile_fail("single-argument.rs"); +fn compile_fails_single_argument() { + simple_trybuild::compile_fail("single_argument.rs"); } #[test] #[ignore] -fn test_compile_fails_triple_arguments() { - simple_trybuild::compile_fail("triple-arguments.rs"); +fn compile_fails_triple_arguments() { + simple_trybuild::compile_fail("triple_arguments.rs"); } #[test] #[ignore] -fn test_compile_fails_only_arrow() { - simple_trybuild::compile_fail("only-arrow.rs"); +fn compile_fails_only_arrow() { + simple_trybuild::compile_fail("only_arrow.rs"); } #[test] #[ignore] -fn test_compile_fails_two_arrows() { - simple_trybuild::compile_fail("two-arrows.rs"); +fn compile_fails_two_arrows() { + simple_trybuild::compile_fail("two_arrows.rs"); } #[test] #[ignore] -fn test_compile_fails_leading_comma() { - simple_trybuild::compile_fail("leading-comma.rs"); +fn compile_fails_leading_comma() { + simple_trybuild::compile_fail("leading_comma.rs"); } #[test] #[ignore] -fn test_compile_fails_no_comma() { - simple_trybuild::compile_fail("no-comma.rs"); +fn compile_fails_no_comma() { + simple_trybuild::compile_fail("no_comma.rs"); } #[test] #[ignore] -fn test_compile_fails_missing_argument() { - simple_trybuild::compile_fail("missing-argument.rs"); +fn compile_fails_missing_argument() { + simple_trybuild::compile_fail("missing_argument.rs"); } mod simple_trybuild { @@ -189,7 +189,7 @@ mod simple_trybuild { file_path.into_os_string() ); - let test_name = file_name.replace('.', "-"); + let test_name = file_name.strip_suffix(".rs").unwrap(); let macros_dir = ["..", "..", "target", "tests", "macros"] .iter() .collect::(); diff --git a/exercises/practice/matching-brackets/.docs/instructions.md b/exercises/practice/matching-brackets/.docs/instructions.md index 364ecad21..ea1708423 100644 --- a/exercises/practice/matching-brackets/.docs/instructions.md +++ b/exercises/practice/matching-brackets/.docs/instructions.md @@ -1,5 +1,5 @@ # Instructions -Given a string containing brackets `[]`, braces `{}`, parentheses `()`, -or any combination thereof, verify that any and all pairs are matched -and nested correctly. +Given a string containing brackets `[]`, braces `{}`, parentheses `()`, or any combination thereof, verify that any and all pairs are matched and nested correctly. +Any other characters should be ignored. +For example, `"{what is (42)}?"` is balanced and `"[text}"` is not. diff --git a/exercises/practice/matching-brackets/.docs/introduction.md b/exercises/practice/matching-brackets/.docs/introduction.md new file mode 100644 index 000000000..0618221b2 --- /dev/null +++ b/exercises/practice/matching-brackets/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You're given the opportunity to write software for the Bracketeer™, an ancient but powerful mainframe. +The software that runs on it is written in a proprietary language. +Much of its syntax is familiar, but you notice _lots_ of brackets, braces and parentheses. +Despite the Bracketeer™ being powerful, it lacks flexibility. +If the source code has any unbalanced brackets, braces or parentheses, the Bracketeer™ crashes and must be rebooted. +To avoid such a scenario, you start writing code that can verify that brackets, braces, and parentheses are balanced before attempting to run it on the Bracketeer™. diff --git a/exercises/practice/matching-brackets/.gitignore b/exercises/practice/matching-brackets/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/matching-brackets/.gitignore +++ b/exercises/practice/matching-brackets/.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/matching-brackets/.meta/config.json b/exercises/practice/matching-brackets/.meta/config.json index 632203027..5ef329c30 100644 --- a/exercises/practice/matching-brackets/.meta/config.json +++ b/exercises/practice/matching-brackets/.meta/config.json @@ -27,7 +27,7 @@ "Cargo.toml" ], "test": [ - "tests/matching-brackets.rs" + "tests/matching_brackets.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/matching-brackets/.meta/example.rs b/exercises/practice/matching-brackets/.meta/example.rs index 8d3e46858..4a139dae9 100644 --- a/exercises/practice/matching-brackets/.meta/example.rs +++ b/exercises/practice/matching-brackets/.meta/example.rs @@ -9,7 +9,7 @@ struct Brackets { pairs: MatchingBrackets, } -impl<'a> From<&'a str> for Brackets { +impl From<&str> for Brackets { fn from(i: &str) -> Self { Brackets::new(i, None) } @@ -23,7 +23,7 @@ impl Brackets { }; Brackets { - raw_brackets: s.chars().filter(|c| p.contains(&c)).collect::>(), + raw_brackets: s.chars().filter(|c| p.contains(c)).collect::>(), pairs: p, } } diff --git a/exercises/practice/matching-brackets/.meta/test_template.tera b/exercises/practice/matching-brackets/.meta/test_template.tera new file mode 100644 index 000000000..25bc9bb9b --- /dev/null +++ b/exercises/practice/matching-brackets/.meta/test_template.tera @@ -0,0 +1,12 @@ +use matching_brackets::brackets_are_balanced; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert!( + {% if test.expected == false %}!{% endif %} + brackets_are_balanced("{{ test.input.value | replace(from="\", to="\\") }}") + ); +} +{% endfor -%} diff --git a/exercises/practice/matching-brackets/.meta/tests.toml b/exercises/practice/matching-brackets/.meta/tests.toml index cc9e471a4..35a98a042 100644 --- a/exercises/practice/matching-brackets/.meta/tests.toml +++ b/exercises/practice/matching-brackets/.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. [81ec11da-38dd-442a-bcf9-3de7754609a5] description = "paired square brackets" @@ -41,12 +48,21 @@ description = "unpaired and nested brackets" [a0205e34-c2ac-49e6-a88a-899508d7d68e] description = "paired and wrong nested brackets" +[1d5c093f-fc84-41fb-8c2a-e052f9581602] +description = "paired and wrong nested brackets but innermost are correct" + [ef47c21b-bcfd-4998-844c-7ad5daad90a8] description = "paired and incomplete brackets" [a4675a40-a8be-4fc2-bc47-2a282ce6edbe] description = "too many closing brackets" +[a345a753-d889-4b7e-99ae-34ac85910d1a] +description = "early unexpected brackets" + +[21f81d61-1608-465a-b850-baa44c5def83] +description = "early mismatched brackets" + [99255f93-261b-4435-a352-02bdecc9bdf2] description = "math expression" diff --git a/exercises/practice/matching-brackets/Cargo.toml b/exercises/practice/matching-brackets/Cargo.toml index 55b42afb7..3c9770b69 100644 --- a/exercises/practice/matching-brackets/Cargo.toml +++ b/exercises/practice/matching-brackets/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "matching-brackets" -version = "2.0.0" +name = "matching_brackets" +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/matching-brackets/src/lib.rs b/exercises/practice/matching-brackets/src/lib.rs index a780fad56..2adc73149 100644 --- a/exercises/practice/matching-brackets/src/lib.rs +++ b/exercises/practice/matching-brackets/src/lib.rs @@ -1,3 +1,3 @@ pub fn brackets_are_balanced(string: &str) -> bool { - unimplemented!("Check if the string \"{string}\" contains balanced brackets"); + todo!("Check if the string \"{string}\" contains balanced brackets"); } diff --git a/exercises/practice/matching-brackets/tests/matching-brackets.rs b/exercises/practice/matching-brackets/tests/matching_brackets.rs similarity index 85% rename from exercises/practice/matching-brackets/tests/matching-brackets.rs rename to exercises/practice/matching-brackets/tests/matching_brackets.rs index 6771e9572..4e95c0c6d 100644 --- a/exercises/practice/matching-brackets/tests/matching-brackets.rs +++ b/exercises/practice/matching-brackets/tests/matching_brackets.rs @@ -77,6 +77,12 @@ fn paired_and_wrong_nested_brackets() { assert!(!brackets_are_balanced("[({]})")); } +#[test] +#[ignore] +fn paired_and_wrong_nested_brackets_but_innermost_are_correct() { + assert!(!brackets_are_balanced("[({}])")); +} + #[test] #[ignore] fn paired_and_incomplete_brackets() { @@ -91,7 +97,7 @@ fn too_many_closing_brackets() { #[test] #[ignore] -fn early_incomplete_brackets() { +fn early_unexpected_brackets() { assert!(!brackets_are_balanced(")()")); } @@ -110,7 +116,7 @@ fn math_expression() { #[test] #[ignore] fn complex_latex_expression() { - let input = "\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \ - \\end{array}\\right)"; - assert!(brackets_are_balanced(input)); + assert!(brackets_are_balanced( + "\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)" + )); } diff --git a/exercises/practice/matrix/.docs/instructions.append.md b/exercises/practice/matrix/.docs/instructions.append.md new file mode 100644 index 000000000..e96864709 --- /dev/null +++ b/exercises/practice/matrix/.docs/instructions.append.md @@ -0,0 +1,20 @@ +# Instructions append + +## Challenges + +- Can you implement this solution without using any `for` loops? (i.e. using [iterators][iterators] instead) +- Although it would be relatively straight-forward to have two data structures in `Matrix` (one representing rows and another for columns), can you implement this solution without having to hold two separate copies of the input in your struct? + +## Helpful Methods +- [`map()`][map] +- [`filter()`][filter] +- [`lines()`][lines] +- [`split()`][split] +- [`parse()`][parse] + +[iterators]: https://doc.rust-lang.org/book/ch13-02-iterators.html +[map]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map +[filter]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter +[lines]: https://doc.rust-lang.org/std/primitive.str.html#method.lines +[split]: https://doc.rust-lang.org/std/primitive.str.html#method.split +[parse]: https://doc.rust-lang.org/std/primitive.str.html#method.parse \ No newline at end of file diff --git a/exercises/practice/matrix/.docs/instructions.md b/exercises/practice/matrix/.docs/instructions.md new file mode 100644 index 000000000..dadea8acb --- /dev/null +++ b/exercises/practice/matrix/.docs/instructions.md @@ -0,0 +1,38 @@ +# Instructions + +Given a string representing a matrix of numbers, return the rows and columns of that matrix. + +So given a string with embedded newlines like: + +```text +9 8 7 +5 3 2 +6 6 7 +``` + +representing this matrix: + +```text + 1 2 3 + |--------- +1 | 9 8 7 +2 | 5 3 2 +3 | 6 6 7 +``` + +your code should be able to spit out: + +- A list of the rows, reading each row left-to-right while moving top-to-bottom across the rows, +- A list of the columns, reading each column top-to-bottom while moving from left-to-right. + +The rows for our example matrix: + +- 9, 8, 7 +- 5, 3, 2 +- 6, 6, 7 + +And its columns: + +- 9, 5, 6 +- 8, 3, 6 +- 7, 2, 7 diff --git a/exercises/practice/matrix/.gitignore b/exercises/practice/matrix/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/matrix/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/matrix/.meta/additional-tests.json b/exercises/practice/matrix/.meta/additional-tests.json new file mode 100644 index 000000000..2bb9cd369 --- /dev/null +++ b/exercises/practice/matrix/.meta/additional-tests.json @@ -0,0 +1,34 @@ +[ + { + "uuid": "9ca9c869-7220-4783-93ba-3e4f96b81732", + "description": "cannot extract row with no corresponding row in matrix", + "property": "row", + "input": { + "string": "1 2 3\n4 5 6\n7 8 9", + "index": 4 + }, + "expected": null, + "comments": [ + "Additional test to ensure the method can handle invalid input", + "Upstream does not want to add this test to avoid every exercise being about input validation.", + "Rust puts a lot of emphasis on error handling, hence the idiomatic Option return type.", + "With Option in the API, it makes sense to test for None at least once." + ] + }, + { + "uuid": "304d71d4-cf26-41d4-9b74-cc04441fec8c", + "description": "cannot extract column with no corresponding column in matrix", + "property": "column", + "input": { + "string": "1 2 3\n4 5 6\n7 8 9", + "index": 4 + }, + "expected": null, + "comments": [ + "Additional test to ensure the method can handle invalid input", + "Upstream does not want to add this test to avoid every exercise being about input validation.", + "Rust puts a lot of emphasis on error handling, hence the idiomatic Option return type.", + "With Option in the API, it makes sense to test for None at least once." + ] + } +] diff --git a/exercises/practice/matrix/.meta/config.json b/exercises/practice/matrix/.meta/config.json new file mode 100644 index 000000000..cc6fedb59 --- /dev/null +++ b/exercises/practice/matrix/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": ["jpal91"], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/matrix.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Given a string representing a matrix of numbers, return the rows and columns of that matrix.", + "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/matrix/.meta/example.rs b/exercises/practice/matrix/.meta/example.rs new file mode 100644 index 000000000..024642824 --- /dev/null +++ b/exercises/practice/matrix/.meta/example.rs @@ -0,0 +1,26 @@ +pub struct Matrix { + grid: Vec>, +} + +impl Matrix { + pub fn new(input: &str) -> Self { + let grid = input + .lines() + .map(|l| l.split(' ').map(|n| n.parse::().unwrap()).collect()) + .collect(); + + Self { grid } + } + + pub fn row(&self, row_no: usize) -> Option> { + self.grid.get(row_no - 1).map(|row| row.to_owned()) + } + + pub fn column(&self, col_no: usize) -> Option> { + if col_no > self.grid[0].len() { + return None; + }; + + Some(self.grid.iter().map(|row| row[col_no - 1]).collect()) + } +} diff --git a/exercises/practice/matrix/.meta/test_template.tera b/exercises/practice/matrix/.meta/test_template.tera new file mode 100644 index 000000000..c857c4ca4 --- /dev/null +++ b/exercises/practice/matrix/.meta/test_template.tera @@ -0,0 +1,14 @@ +use matrix::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let matrix = Matrix::new({{ test.input.string | json_encode() }}); + {% if test.expected -%} + assert_eq!(matrix.{{ test.property }}({{ test.input.index }}), Some(vec!{{ test.expected | json_encode() }})); + {% else -%} + assert_eq!(matrix.{{ test.property }}({{ test.input.index }}), None); + {% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/matrix/.meta/tests.toml b/exercises/practice/matrix/.meta/tests.toml new file mode 100644 index 000000000..90b509c44 --- /dev/null +++ b/exercises/practice/matrix/.meta/tests.toml @@ -0,0 +1,34 @@ +# 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. + +[ca733dab-9d85-4065-9ef6-a880a951dafd] +description = "extract row from one number matrix" + +[5c93ec93-80e1-4268-9fc2-63bc7d23385c] +description = "can extract row" + +[2f1aad89-ad0f-4bd2-9919-99a8bff0305a] +description = "extract row where numbers have different widths" + +[68f7f6ba-57e2-4e87-82d0-ad09889b5204] +description = "can extract row from non-square matrix with no corresponding column" + +[e8c74391-c93b-4aed-8bfe-f3c9beb89ebb] +description = "extract column from one number matrix" + +[7136bdbd-b3dc-48c4-a10c-8230976d3727] +description = "can extract column" + +[ad64f8d7-bba6-4182-8adf-0c14de3d0eca] +description = "can extract column from non-square matrix with no corresponding row" + +[9eddfa5c-8474-440e-ae0a-f018c2a0dd89] +description = "extract column where numbers have different widths" diff --git a/exercises/practice/matrix/Cargo.toml b/exercises/practice/matrix/Cargo.toml new file mode 100644 index 000000000..1247a58c2 --- /dev/null +++ b/exercises/practice/matrix/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "matrix" +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/matrix/src/lib.rs b/exercises/practice/matrix/src/lib.rs new file mode 100644 index 000000000..119c38f83 --- /dev/null +++ b/exercises/practice/matrix/src/lib.rs @@ -0,0 +1,17 @@ +pub struct Matrix { + // Implement your Matrix struct +} + +impl Matrix { + pub fn new(input: &str) -> Self { + todo!("Create new method to store the {input}") + } + + pub fn row(&self, row_no: usize) -> Option> { + todo!("Return the row at {row_no} (1-indexed) or None if the number is invalid") + } + + pub fn column(&self, col_no: usize) -> Option> { + todo!("Return the column at {col_no} (1-indexed) or None if the number is invalid") + } +} diff --git a/exercises/practice/matrix/tests/matrix.rs b/exercises/practice/matrix/tests/matrix.rs new file mode 100644 index 000000000..257a48272 --- /dev/null +++ b/exercises/practice/matrix/tests/matrix.rs @@ -0,0 +1,70 @@ +use matrix::*; + +#[test] +fn extract_row_from_one_number_matrix() { + let matrix = Matrix::new("1"); + assert_eq!(matrix.row(1), Some(vec![1])); +} + +#[test] +#[ignore] +fn can_extract_row() { + let matrix = Matrix::new("1 2\n3 4"); + assert_eq!(matrix.row(2), Some(vec![3, 4])); +} + +#[test] +#[ignore] +fn extract_row_where_numbers_have_different_widths() { + let matrix = Matrix::new("1 2\n10 20"); + assert_eq!(matrix.row(2), Some(vec![10, 20])); +} + +#[test] +#[ignore] +fn can_extract_row_from_non_square_matrix_with_no_corresponding_column() { + let matrix = Matrix::new("1 2 3\n4 5 6\n7 8 9\n8 7 6"); + assert_eq!(matrix.row(4), Some(vec![8, 7, 6])); +} + +#[test] +#[ignore] +fn extract_column_from_one_number_matrix() { + let matrix = Matrix::new("1"); + assert_eq!(matrix.column(1), Some(vec![1])); +} + +#[test] +#[ignore] +fn can_extract_column() { + let matrix = Matrix::new("1 2 3\n4 5 6\n7 8 9"); + assert_eq!(matrix.column(3), Some(vec![3, 6, 9])); +} + +#[test] +#[ignore] +fn can_extract_column_from_non_square_matrix_with_no_corresponding_row() { + let matrix = Matrix::new("1 2 3 4\n5 6 7 8\n9 8 7 6"); + assert_eq!(matrix.column(4), Some(vec![4, 8, 6])); +} + +#[test] +#[ignore] +fn extract_column_where_numbers_have_different_widths() { + let matrix = Matrix::new("89 1903 3\n18 3 1\n9 4 800"); + assert_eq!(matrix.column(2), Some(vec![1903, 3, 4])); +} + +#[test] +#[ignore] +fn cannot_extract_row_with_no_corresponding_row_in_matrix() { + let matrix = Matrix::new("1 2 3\n4 5 6\n7 8 9"); + assert_eq!(matrix.row(4), None); +} + +#[test] +#[ignore] +fn cannot_extract_column_with_no_corresponding_column_in_matrix() { + let matrix = Matrix::new("1 2 3\n4 5 6\n7 8 9"); + assert_eq!(matrix.column(4), None); +} diff --git a/exercises/practice/minesweeper/.docs/instructions.append.md b/exercises/practice/minesweeper/.docs/instructions.append.md new file mode 100644 index 000000000..51d0953a4 --- /dev/null +++ b/exercises/practice/minesweeper/.docs/instructions.append.md @@ -0,0 +1,10 @@ +# Instructions append + +## Performance Hint + +All the inputs and outputs are in ASCII. +Rust `String`s and `&str` are utf8, so while one might expect `"Hello".chars()` to be simple, it actually has to check each char to see if it's 1, 2, 3 or 4 `u8`s long. +If we know a `&str` is ASCII then we can call `.as_bytes()` and refer to the underlying data as a `&[u8]` (byte slice). +Iterating over a slice of ASCII bytes is much quicker as there are no codepoints involved - every ASCII byte is one `u8` long. + +Can you complete the challenge without cloning the input? diff --git a/exercises/practice/minesweeper/.docs/instructions.md b/exercises/practice/minesweeper/.docs/instructions.md index 72ffc65c5..7c1df2e4b 100644 --- a/exercises/practice/minesweeper/.docs/instructions.md +++ b/exercises/practice/minesweeper/.docs/instructions.md @@ -1,48 +1,26 @@ # Instructions -Add the mine counts to a completed Minesweeper board. +Your task is to add the mine counts to empty squares in a completed Minesweeper board. +The board itself is a rectangle composed of squares that are either empty (`' '`) or a mine (`'*'`). -Minesweeper is a popular game where the user has to find the mines using -numeric hints that indicate how many mines are directly adjacent -(horizontally, vertically, diagonally) to a square. +For each empty square, count the number of mines adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent mines, leave it empty. +Otherwise replace it with the adjacent mines count. -In this exercise you have to create some code that counts the number of -mines adjacent to a given empty square and replaces that square with the -count. +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): -The board is a rectangle composed of blank space (' ') characters. A mine -is represented by an asterisk ('\*') character. - -If a given space has no adjacent mines at all, leave that square blank. - -## Examples - -For example you may receive a 5 x 4 board like this (empty spaces are -represented here with the '·' character for display on screen): - -``` +```text ·*·*· ··*·· ··*·· ····· ``` -And your code will transform it into this: +Which your code should transform into this: -``` +```text 1*3*1 13*31 ·2*2· ·111· ``` - -## Performance Hint - -All the inputs and outputs are in ASCII. Rust `String`s and `&str` are utf8, -so while one might expect "Hello".chars() to be simple, it actually has to -check each char to see if it's 1, 2, 3 or 4 `u8`s long. If we know a `&str` -is ASCII then we can call `.as_bytes()` and refer to the underlying data via a `&[u8]` slice. -Iterating over a u8 slice of ASCII is much quicker as there are no codepoints -involved - every ASCII char is one u8 long. - -Can you complete the challenge without cloning the input? diff --git a/exercises/practice/minesweeper/.docs/introduction.md b/exercises/practice/minesweeper/.docs/introduction.md new file mode 100644 index 000000000..5f74a742b --- /dev/null +++ b/exercises/practice/minesweeper/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +[Minesweeper][wikipedia] is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. + +[wikipedia]: https://en.wikipedia.org/wiki/Minesweeper_(video_game) diff --git a/exercises/practice/minesweeper/.gitignore b/exercises/practice/minesweeper/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/minesweeper/.gitignore +++ b/exercises/practice/minesweeper/.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/minesweeper/.meta/test_template.tera b/exercises/practice/minesweeper/.meta/test_template.tera new file mode 100644 index 000000000..451fbf988 --- /dev/null +++ b/exercises/practice/minesweeper/.meta/test_template.tera @@ -0,0 +1,33 @@ +use minesweeper::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% if test.input.minefield | length < 2 -%} + let input = &[ + {%- for line in test.input.minefield %} + {{ line | json_encode() }}, + {%- endfor %} + ]; + let expected{% if test.expected | length == 0 %}: &[&str]{% endif %} = &[ + {%- for line in test.expected %} + {{ line | json_encode() }}, + {%- endfor %} + ]; + {% else -%} + #[rustfmt::skip] + let (input, expected) = (&[ + {%- for line in test.input.minefield %} + {{ line | json_encode() }}, + {%- endfor %} + ], &[ + {%- for line in test.expected %} + {{ line | json_encode() }}, + {%- endfor %} + ]); + {% endif -%} + let actual = annotate(input); + assert_eq!(actual, expected); +} +{% endfor -%} diff --git a/exercises/practice/minesweeper/.meta/tests.toml b/exercises/practice/minesweeper/.meta/tests.toml index 83e6859da..2a1422224 100644 --- a/exercises/practice/minesweeper/.meta/tests.toml +++ b/exercises/practice/minesweeper/.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. [0c5ec4bd-dea7-4138-8651-1203e1cb9f44] description = "no rows" @@ -11,6 +18,9 @@ description = "no columns" [6fbf8f6d-a03b-42c9-9a58-b489e9235478] description = "no mines" +[61aff1c4-fb31-4078-acad-cd5f1e635655] +description = "minefield with only mines" + [84167147-c504-4896-85d7-246b01dea7c5] description = "mine surrounded by spaces" @@ -31,3 +41,6 @@ description = "vertical line, mines at edges" [4b098563-b7f3-401c-97c6-79dd1b708f34] description = "cross" + +[04a260f1-b40a-4e89-839e-8dd8525abe0e] +description = "large minefield" diff --git a/exercises/practice/minesweeper/Cargo.toml b/exercises/practice/minesweeper/Cargo.toml index 5b53e2644..e64ff1b08 100644 --- a/exercises/practice/minesweeper/Cargo.toml +++ b/exercises/practice/minesweeper/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "minesweeper" -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/minesweeper/src/lib.rs b/exercises/practice/minesweeper/src/lib.rs index 3226570b0..c19457694 100644 --- a/exercises/practice/minesweeper/src/lib.rs +++ b/exercises/practice/minesweeper/src/lib.rs @@ -1,3 +1,5 @@ pub fn annotate(minefield: &[&str]) -> Vec { - unimplemented!("\nAnnotate each square of the given minefield with the number of mines that surround said square (blank if there are no surrounding mines):\n{minefield:#?}\n"); + todo!( + "\nAnnotate each square of the given minefield with the number of mines that surround said square (blank if there are no surrounding mines):\n{minefield:#?}\n" + ); } diff --git a/exercises/practice/minesweeper/tests/minesweeper.rs b/exercises/practice/minesweeper/tests/minesweeper.rs index f05255e5c..dfa647dda 100644 --- a/exercises/practice/minesweeper/tests/minesweeper.rs +++ b/exercises/practice/minesweeper/tests/minesweeper.rs @@ -1,147 +1,183 @@ -use minesweeper::annotate; - -fn remove_annotations(board: &[&str]) -> Vec { - board.iter().map(|r| remove_annotations_in_row(r)).collect() -} - -fn remove_annotations_in_row(row: &str) -> String { - row.chars() - .map(|ch| match ch { - '*' => '*', - _ => ' ', - }) - .collect() -} - -fn run_test(test_case: &[&str]) { - let cleaned = remove_annotations(test_case); - let cleaned_strs = cleaned.iter().map(|r| &r[..]).collect::>(); - let expected = test_case.iter().map(|&r| r.to_string()).collect::>(); - assert_eq!(expected, annotate(&cleaned_strs)); -} +use minesweeper::*; #[test] fn no_rows() { - #[rustfmt::skip] - run_test(&[ - ]); + let input = &[]; + let expected: &[&str] = &[]; + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn no_columns() { - #[rustfmt::skip] - run_test(&[ - "", - ]); + let input = &[""]; + let expected = &[""]; + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn no_mines() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + " ", + " ", + " ", + ], &[ " ", " ", " ", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] -fn board_with_only_mines() { +fn minefield_with_only_mines() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + "***", + "***", + "***", + ], &[ "***", "***", "***", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn mine_surrounded_by_spaces() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + " ", + " * ", + " ", + ], &[ "111", "1*1", "111", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn space_surrounded_by_mines() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + "***", + "* *", + "***", + ], &[ "***", "*8*", "***", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn horizontal_line() { - #[rustfmt::skip] - run_test(&[ - "1*2*1", - ]); + let input = &[" * * "]; + let expected = &["1*2*1"]; + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn horizontal_line_mines_at_edges() { - #[rustfmt::skip] - run_test(&[ - "*1 1*", - ]); + let input = &["* *"]; + let expected = &["*1 1*"]; + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn vertical_line() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + " ", + "*", + " ", + "*", + " ", + ], &[ "1", "*", "2", "*", "1", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn vertical_line_mines_at_edges() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + "*", + " ", + " ", + " ", + "*", + ], &[ "*", "1", " ", "1", "*", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn cross() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + " * ", + " * ", + "*****", + " * ", + " * ", + ], &[ " 2*2 ", "25*52", "*****", "25*52", " 2*2 ", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] -fn large_board() { +fn large_minefield() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + " * * ", + " * ", + " * ", + " * *", + " * * ", + " ", + ], &[ "1*22*1", "12*322", " 123*2", @@ -149,4 +185,6 @@ fn large_board() { "1*22*2", "111111", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } diff --git a/exercises/practice/nth-prime/.docs/instructions.md b/exercises/practice/nth-prime/.docs/instructions.md index 30a75216f..065e323ab 100644 --- a/exercises/practice/nth-prime/.docs/instructions.md +++ b/exercises/practice/nth-prime/.docs/instructions.md @@ -2,8 +2,6 @@ Given a number n, determine what the nth prime is. -By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that -the 6th prime is 13. +By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. -If your language provides methods in the standard library to deal with prime -numbers, pretend they don't exist and implement them yourself. +If your language provides methods in the standard library to deal with prime numbers, pretend they don't exist and implement them yourself. diff --git a/exercises/practice/nth-prime/.gitignore b/exercises/practice/nth-prime/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/nth-prime/.gitignore +++ b/exercises/practice/nth-prime/.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/nth-prime/.meta/config.json b/exercises/practice/nth-prime/.meta/config.json index 42f584310..57d36cef3 100644 --- a/exercises/practice/nth-prime/.meta/config.json +++ b/exercises/practice/nth-prime/.meta/config.json @@ -25,7 +25,7 @@ "Cargo.toml" ], "test": [ - "tests/nth-prime.rs" + "tests/nth_prime.rs" ], "example": [ ".meta/example.rs" @@ -33,5 +33,5 @@ }, "blurb": "Given a number n, determine what the nth prime is.", "source": "A variation on Problem 7 at Project Euler", - "source_url": "http://projecteuler.net/problem=7" + "source_url": "https://projecteuler.net/problem=7" } diff --git a/exercises/practice/nth-prime/.meta/example.rs b/exercises/practice/nth-prime/.meta/example.rs index ce9b07d6a..ba0986c29 100644 --- a/exercises/practice/nth-prime/.meta/example.rs +++ b/exercises/practice/nth-prime/.meta/example.rs @@ -1,7 +1,7 @@ fn is_prime(n: u32) -> bool { let mut i = 3; while (i * i) < (n + 1) { - if n % i == 0 { + if n.is_multiple_of(i) { return false; } i += 1; diff --git a/exercises/practice/nth-prime/.meta/test_template.tera b/exercises/practice/nth-prime/.meta/test_template.tera new file mode 100644 index 000000000..a69cc5c50 --- /dev/null +++ b/exercises/practice/nth-prime/.meta/test_template.tera @@ -0,0 +1,11 @@ +use nth_prime::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let output = nth({{ test.input.number - 1 | fmt_num }}); + let expected = {{ test.expected | fmt_num }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/nth-prime/.meta/tests.toml b/exercises/practice/nth-prime/.meta/tests.toml index be690e975..8655ede29 100644 --- a/exercises/practice/nth-prime/.meta/tests.toml +++ b/exercises/practice/nth-prime/.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. + +[75c65189-8aef-471a-81de-0a90c728160c] +description = "first prime" + +[2c38804c-295f-4701-b728-56dea34fd1a0] +description = "second prime" + +[56692534-781e-4e8c-b1f9-3e82c1640259] +description = "sixth prime" + +[fce1e979-0edb-412d-93aa-2c744e8f50ff] +description = "big prime" + +[bd0a9eae-6df7-485b-a144-80e13c7d55b2] +description = "there is no zeroth prime" +include = false +comment = """ + The zeroth prime was defined to be 2 (zero-based indexing). + Changing it now would be an unnecessary breaking change. +""" diff --git a/exercises/practice/nth-prime/Cargo.toml b/exercises/practice/nth-prime/Cargo.toml index 11a62d103..ce719bcd8 100644 --- a/exercises/practice/nth-prime/Cargo.toml +++ b/exercises/practice/nth-prime/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "nth_prime" -version = "2.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/nth-prime/src/lib.rs b/exercises/practice/nth-prime/src/lib.rs index f4726d180..563e947cf 100644 --- a/exercises/practice/nth-prime/src/lib.rs +++ b/exercises/practice/nth-prime/src/lib.rs @@ -1,3 +1,3 @@ pub fn nth(n: u32) -> u32 { - unimplemented!("What is the 0-indexed {n}th prime number?") + todo!("What is the 0-indexed {n}th prime number?") } diff --git a/exercises/practice/nth-prime/tests/nth-prime.rs b/exercises/practice/nth-prime/tests/nth-prime.rs deleted file mode 100644 index 7b8c17e91..000000000 --- a/exercises/practice/nth-prime/tests/nth-prime.rs +++ /dev/null @@ -1,24 +0,0 @@ -use nth_prime as np; - -#[test] -fn test_first_prime() { - assert_eq!(np::nth(0), 2); -} - -#[test] -#[ignore] -fn test_second_prime() { - assert_eq!(np::nth(1), 3); -} - -#[test] -#[ignore] -fn test_sixth_prime() { - assert_eq!(np::nth(5), 13); -} - -#[test] -#[ignore] -fn test_big_prime() { - assert_eq!(np::nth(10_000), 104_743); -} diff --git a/exercises/practice/nth-prime/tests/nth_prime.rs b/exercises/practice/nth-prime/tests/nth_prime.rs new file mode 100644 index 000000000..3a496849a --- /dev/null +++ b/exercises/practice/nth-prime/tests/nth_prime.rs @@ -0,0 +1,32 @@ +use nth_prime::*; + +#[test] +fn first_prime() { + let output = nth(0); + let expected = 2; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn second_prime() { + let output = nth(1); + let expected = 3; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn sixth_prime() { + let output = nth(5); + let expected = 13; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn big_prime() { + let output = nth(10_000); + let expected = 104_743; + assert_eq!(output, expected); +} diff --git a/exercises/practice/nucleotide-codons/.gitignore b/exercises/practice/nucleotide-codons/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/nucleotide-codons/.gitignore +++ b/exercises/practice/nucleotide-codons/.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/nucleotide-codons/.meta/config.json b/exercises/practice/nucleotide-codons/.meta/config.json index 0d5d94be2..ca5356af3 100644 --- a/exercises/practice/nucleotide-codons/.meta/config.json +++ b/exercises/practice/nucleotide-codons/.meta/config.json @@ -28,7 +28,7 @@ "Cargo.toml" ], "test": [ - "tests/nucleotide-codons.rs" + "tests/nucleotide_codons.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/nucleotide-codons/Cargo.toml b/exercises/practice/nucleotide-codons/Cargo.toml index ff77d8534..a39d46657 100644 --- a/exercises/practice/nucleotide-codons/Cargo.toml +++ b/exercises/practice/nucleotide-codons/Cargo.toml @@ -1,3 +1,9 @@ [package] name = "nucleotide_codons" 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/nucleotide-codons/src/lib.rs b/exercises/practice/nucleotide-codons/src/lib.rs index 0577d8166..29460af9b 100644 --- a/exercises/practice/nucleotide-codons/src/lib.rs +++ b/exercises/practice/nucleotide-codons/src/lib.rs @@ -1,46 +1,38 @@ -use std::collections::HashMap; +// This exercise is deprecated. +// Consider working on protein-translation instead. -pub struct CodonInfo<'a> { - actual_codons: HashMap<&'a str, &'a str>, -} +use std::marker::PhantomData; -pub fn parse<'a>(pairs: Vec<(&'a str, &'a str)>) -> CodonInfo<'a> { - CodonInfo { - actual_codons: pairs.into_iter().collect(), - } +pub struct CodonsInfo<'a> { + // This field is here to make the template compile and not to + // complain about unused type lifetime parameter "'a". Once you start + // solving the exercise, delete this field and the 'std::marker::PhantomData' + // import. + phantom: PhantomData<&'a ()>, } -impl<'a> CodonInfo<'a> { - pub fn name_for(&self, codon: &str) -> Result<&'a str, &'static str> { - if codon.len() != 3 { - return Err("invalid length"); - } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Error; - let mut valid = true; - let lookup: String = codon - .chars() - .map(|l| { - // Get an example of a "letter" represented by the possibly encoded letter. - // Since every codon represented by the compressed notation has to be of - // the desired amino acid just picking one at random will do. - match l { - 'A' | 'W' | 'M' | 'R' | 'D' | 'H' | 'V' | 'N' => 'A', - 'C' | 'S' | 'Y' | 'B' => 'C', - 'G' | 'K' => 'G', - 'T' => 'T', - _ => { - valid = false; - ' ' - } - } - }) - .collect(); - if !valid { - return Err("invalid char"); - } +impl<'a> CodonsInfo<'a> { + pub fn name_for(&self, codon: &str) -> Result<&'a str, Error> { + todo!( + "Return the protein name for a '{}' codon or Err, if codon string is invalid", + codon + ); + } - // If the input table is correct (which it is) every valid codon is in it - // so unwrap() shouldn't panic. - Ok(self.actual_codons.get(&lookup.as_ref()).unwrap()) + pub fn of_rna(&self, rna: &str) -> Result, Error> { + todo!( + "Return a list of protein names that correspond to the '{}' RNA string or Err if the RNA string is invalid", + rna + ); } } + +pub fn parse<'a>(pairs: Vec<(&'a str, &'a str)>) -> CodonsInfo<'a> { + todo!( + "Construct a new CodonsInfo struct from given pairs: {:?}", + pairs + ); +} diff --git a/exercises/practice/nucleotide-codons/tests/nucleotide-codons.rs b/exercises/practice/nucleotide-codons/tests/nucleotide_codons.rs similarity index 95% rename from exercises/practice/nucleotide-codons/tests/nucleotide-codons.rs rename to exercises/practice/nucleotide-codons/tests/nucleotide_codons.rs index f585d57e2..bee25bb69 100644 --- a/exercises/practice/nucleotide-codons/tests/nucleotide-codons.rs +++ b/exercises/practice/nucleotide-codons/tests/nucleotide_codons.rs @@ -1,19 +1,19 @@ #[test] -fn test_methionine() { +fn methionine() { let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("ATG"), Ok("methionine")); } #[test] #[ignore] -fn test_cysteine_tgt() { +fn cysteine_tgt() { let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("TGT"), Ok("cysteine")); } #[test] #[ignore] -fn test_cysteine_tgy() { +fn cysteine_tgy() { // "compressed" name for TGT and TGC let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("TGT"), info.name_for("TGY")); @@ -22,28 +22,28 @@ fn test_cysteine_tgy() { #[test] #[ignore] -fn test_stop() { +fn stop() { let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("TAA"), Ok("stop codon")); } #[test] #[ignore] -fn test_valine() { +fn valine() { let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("GTN"), Ok("valine")); } #[test] #[ignore] -fn test_isoleucine() { +fn isoleucine() { let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("ATH"), Ok("isoleucine")); } #[test] #[ignore] -fn test_arginine_name() { +fn arginine_name() { // In arginine CGA can be "compressed" both as CGN and as MGR let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("CGA"), Ok("arginine")); diff --git a/exercises/practice/nucleotide-count/.docs/instructions.md b/exercises/practice/nucleotide-count/.docs/instructions.md index cd0875894..548d9ba5a 100644 --- a/exercises/practice/nucleotide-count/.docs/instructions.md +++ b/exercises/practice/nucleotide-count/.docs/instructions.md @@ -1,10 +1,12 @@ # Instructions -Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. All known life depends on DNA! +Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. +All known life depends on DNA! > Note: You do not need to understand anything about nucleotides or DNA to complete this exercise. -DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! +DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. +A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! We call the order of these nucleotides in a bit of DNA a "DNA sequence". We represent a DNA sequence as an ordered collection of these four nucleotides and a common way to do that is with a string of characters such as "ATTACG" for a DNA sequence of 6 nucleotides. @@ -15,7 +17,7 @@ If the string contains characters that aren't A, C, G, or T then it is invalid a For example: -``` +```text "GATTACA" -> 'A': 3, 'C': 1, 'G': 1, 'T': 2 "INVALID" -> error ``` diff --git a/exercises/practice/nucleotide-count/.gitignore b/exercises/practice/nucleotide-count/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/nucleotide-count/.gitignore +++ b/exercises/practice/nucleotide-count/.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/nucleotide-count/.meta/additional-tests.json b/exercises/practice/nucleotide-count/.meta/additional-tests.json new file mode 100644 index 000000000..bad9e678a --- /dev/null +++ b/exercises/practice/nucleotide-count/.meta/additional-tests.json @@ -0,0 +1,72 @@ +[ + { + "uuid": "3e5c30a8-87e2-4845-a815-a49671ade970", + "description": "count_empty", + "comments": [ + "The exercise was designed with a `count` method", + "which should be tested as well." + ], + "property": "count", + "input": { + "nucleotide": "A", + "strand": "" + }, + "expected": "Ok(0)" + }, + { + "uuid": "3e5c30a8-87e2-4845-a815-a49671ade970", + "description": "count_invalid_nucleotide", + "comments": [ + "The exercise was designed with a `count` method", + "which should be tested as well." + ], + "property": "count", + "input": { + "nucleotide": "X", + "strand": "A" + }, + "expected": "Err('X')" + }, + { + "uuid": "3e5c30a8-87e2-4845-a815-a49671ade970", + "description": "count_invalid_dna", + "comments": [ + "The exercise was designed with a `count` method", + "which should be tested as well." + ], + "property": "count", + "input": { + "nucleotide": "A", + "strand": "AX" + }, + "expected": "Err('X')" + }, + { + "uuid": "3e5c30a8-87e2-4845-a815-a49671ade970", + "description": "count_repetitive_cytosine", + "comments": [ + "The exercise was designed with a `count` method", + "which should be tested as well." + ], + "property": "count", + "input": { + "nucleotide": "C", + "strand": "CCCCC" + }, + "expected": "Ok(5)" + }, + { + "uuid": "3e5c30a8-87e2-4845-a815-a49671ade970", + "description": "count_only_thymine", + "comments": [ + "The exercise was designed with a `count` method", + "which should be tested as well." + ], + "property": "count", + "input": { + "nucleotide": "T", + "strand": "GGGGGTAACCCGG" + }, + "expected": "Ok(1)" + } +] diff --git a/exercises/practice/nucleotide-count/.meta/config.json b/exercises/practice/nucleotide-count/.meta/config.json index 6d0ff3e61..bae572c6c 100644 --- a/exercises/practice/nucleotide-count/.meta/config.json +++ b/exercises/practice/nucleotide-count/.meta/config.json @@ -30,7 +30,7 @@ "Cargo.toml" ], "test": [ - "tests/nucleotide-count.rs" + "tests/nucleotide_count.rs" ], "example": [ ".meta/example.rs" @@ -38,5 +38,5 @@ }, "blurb": "Given a DNA string, compute how many times each nucleotide occurs in the string.", "source": "The Calculating DNA Nucleotides_problem at Rosalind", - "source_url": "http://rosalind.info/problems/dna/" + "source_url": "https://rosalind.info/problems/dna/" } diff --git a/exercises/practice/nucleotide-count/.meta/test_template.tera b/exercises/practice/nucleotide-count/.meta/test_template.tera new file mode 100644 index 000000000..bd1d4baa1 --- /dev/null +++ b/exercises/practice/nucleotide-count/.meta/test_template.tera @@ -0,0 +1,39 @@ +use std::collections::HashMap; + +use nucleotide_count::*; + +{% for test in cases %} +{# + Additional tests are appended, but we want to test the `count` API first. +#} +{% if test.property != "count" %} + {% continue %} +{% endif %} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert_eq!(count('{{ test.input.nucleotide }}', {{ test.input.strand | json_encode() }}), {{ test.expected }}); +} +{% endfor %} + +{% for test in cases %} +{% if test.property == "count" %} + {% continue %} +{% endif %} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let output = nucleotide_counts({{ test.input.strand | json_encode() }}); + {% if "error" in test.expected -%} + assert!(output.is_err()); + {% else -%} + let mut expected = HashMap::new(); + {% for key, val in test.expected -%} + expected.insert('{{ key }}', {{ val }}); + {% endfor -%} + assert_eq!(output, Ok(expected)); + {% endif -%} +} +{% endfor %} diff --git a/exercises/practice/nucleotide-count/.meta/tests.toml b/exercises/practice/nucleotide-count/.meta/tests.toml index be690e975..7c55e53f2 100644 --- a/exercises/practice/nucleotide-count/.meta/tests.toml +++ b/exercises/practice/nucleotide-count/.meta/tests.toml @@ -1,3 +1,25 @@ -# 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. + +[3e5c30a8-87e2-4845-a815-a49671ade970] +description = "empty strand" + +[a0ea42a6-06d9-4ac6-828c-7ccaccf98fec] +description = "can count one nucleotide in single-character input" + +[eca0d565-ed8c-43e7-9033-6cefbf5115b5] +description = "strand with repeated nucleotide" + +[40a45eac-c83f-4740-901a-20b22d15a39f] +description = "strand with multiple nucleotides" + +[b4c47851-ee9e-4b0a-be70-a86e343bd851] +description = "strand with invalid nucleotides" diff --git a/exercises/practice/nucleotide-count/Cargo.toml b/exercises/practice/nucleotide-count/Cargo.toml index 97d8007e1..2a1ef9ab3 100644 --- a/exercises/practice/nucleotide-count/Cargo.toml +++ b/exercises/practice/nucleotide-count/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "nucleotide-count" -version = "1.3.0" +name = "nucleotide_count" +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/nucleotide-count/src/lib.rs b/exercises/practice/nucleotide-count/src/lib.rs index 40ac5abe3..c91615c21 100644 --- a/exercises/practice/nucleotide-count/src/lib.rs +++ b/exercises/practice/nucleotide-count/src/lib.rs @@ -1,11 +1,9 @@ use std::collections::HashMap; pub fn count(nucleotide: char, dna: &str) -> Result { - unimplemented!( - "How much of nucleotide type '{nucleotide}' is contained inside DNA string '{dna}'?" - ); + todo!("How much of nucleotide type '{nucleotide}' is contained inside DNA string '{dna}'?"); } pub fn nucleotide_counts(dna: &str) -> Result, char> { - unimplemented!("How much of every nucleotide type is contained inside DNA string '{dna}'?"); + todo!("How much of every nucleotide type is contained inside DNA string '{dna}'?"); } diff --git a/exercises/practice/nucleotide-count/tests/nucleotide-count.rs b/exercises/practice/nucleotide-count/tests/nucleotide-count.rs deleted file mode 100644 index e910798ad..000000000 --- a/exercises/practice/nucleotide-count/tests/nucleotide-count.rs +++ /dev/null @@ -1,100 +0,0 @@ -use nucleotide_count as dna; - -use std::collections::HashMap; - -fn process_nucleotidecounts_case(s: &str, pairs: &[(char, usize)]) { - // The reason for the awkward code in here is to ensure that the failure - // message for assert_eq! is as informative as possible. A simpler - // solution would simply check the length of the map, and then - // check for the presence and value of each key in the given pairs vector. - let mut m: HashMap = dna::nucleotide_counts(s).unwrap(); - for &(k, v) in pairs.iter() { - assert_eq!((k, m.remove(&k)), (k, Some(v))); - } - - // may fail with a message that clearly shows all extra pairs in the map - assert_eq!(m.iter().collect::>(), vec![]); -} - -#[test] -fn count_returns_result() { - assert!(dna::count('A', "").is_ok()); -} - -#[test] -#[ignore] -fn test_count_empty() { - assert_eq!(dna::count('A', ""), Ok(0)); -} - -#[test] -#[ignore] -fn count_invalid_nucleotide() { - assert_eq!(dna::count('X', "A"), Err('X')); -} - -#[test] -#[ignore] -fn count_invalid_dna() { - assert_eq!(dna::count('A', "AX"), Err('X')); -} - -#[test] -#[ignore] -fn test_count_repetitive_cytosine() { - assert_eq!(dna::count('C', "CCCCC"), Ok(5)); -} - -#[test] -#[ignore] -fn test_count_only_thymine() { - assert_eq!(dna::count('T', "GGGGGTAACCCGG"), Ok(1)); -} - -#[test] -#[ignore] -fn counts_returns_result() { - assert!(dna::nucleotide_counts("ACGT").is_ok()); -} - -#[test] -#[ignore] -fn test_empty_strand() { - process_nucleotidecounts_case("", &[('A', 0), ('T', 0), ('C', 0), ('G', 0)]); -} - -#[test] -#[ignore] -/// can count one nucleotide in single-character input -fn test_can_count_one_nucleotide_in_singlecharacter_input() { - process_nucleotidecounts_case("G", &[('A', 0), ('C', 0), ('G', 1), ('T', 0)]); -} - -#[test] -#[ignore] -fn test_strand_with_repeated_nucleotide() { - process_nucleotidecounts_case("GGGGGGG", &[('A', 0), ('T', 0), ('C', 0), ('G', 7)]); -} - -#[test] -#[ignore] -/// strand with multiple nucleotides -fn test_strand_with_multiple_nucleotides() { - process_nucleotidecounts_case( - "AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC", - &[('A', 20), ('T', 21), ('C', 12), ('G', 17)], - ); -} - -#[test] -#[ignore] -fn counts_invalid_nucleotide_results_in_err() { - assert_eq!(dna::nucleotide_counts("GGXXX"), Err('X')); -} - -#[test] -#[ignore] -/// strand with invalid nucleotides -fn test_strand_with_invalid_nucleotides() { - assert_eq!(dna::nucleotide_counts("AGXXACT"), Err('X'),); -} diff --git a/exercises/practice/nucleotide-count/tests/nucleotide_count.rs b/exercises/practice/nucleotide-count/tests/nucleotide_count.rs new file mode 100644 index 000000000..e820b0d9e --- /dev/null +++ b/exercises/practice/nucleotide-count/tests/nucleotide_count.rs @@ -0,0 +1,88 @@ +use std::collections::HashMap; + +use nucleotide_count::*; + +#[test] +fn count_empty() { + assert_eq!(count('A', ""), Ok(0)); +} + +#[test] +#[ignore] +fn count_invalid_nucleotide() { + assert_eq!(count('X', "A"), Err('X')); +} + +#[test] +#[ignore] +fn count_invalid_dna() { + assert_eq!(count('A', "AX"), Err('X')); +} + +#[test] +#[ignore] +fn count_repetitive_cytosine() { + assert_eq!(count('C', "CCCCC"), Ok(5)); +} + +#[test] +#[ignore] +fn count_only_thymine() { + assert_eq!(count('T', "GGGGGTAACCCGG"), Ok(1)); +} + +#[test] +#[ignore] +fn empty_strand() { + let output = nucleotide_counts(""); + let mut expected = HashMap::new(); + expected.insert('A', 0); + expected.insert('C', 0); + expected.insert('G', 0); + expected.insert('T', 0); + assert_eq!(output, Ok(expected)); +} + +#[test] +#[ignore] +fn can_count_one_nucleotide_in_single_character_input() { + let output = nucleotide_counts("G"); + let mut expected = HashMap::new(); + expected.insert('A', 0); + expected.insert('C', 0); + expected.insert('G', 1); + expected.insert('T', 0); + assert_eq!(output, Ok(expected)); +} + +#[test] +#[ignore] +fn strand_with_repeated_nucleotide() { + let output = nucleotide_counts("GGGGGGG"); + let mut expected = HashMap::new(); + expected.insert('A', 0); + expected.insert('C', 0); + expected.insert('G', 7); + expected.insert('T', 0); + assert_eq!(output, Ok(expected)); +} + +#[test] +#[ignore] +fn strand_with_multiple_nucleotides() { + let output = + nucleotide_counts("AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC"); + let mut expected = HashMap::new(); + expected.insert('A', 20); + expected.insert('C', 12); + expected.insert('G', 17); + expected.insert('T', 21); + assert_eq!(output, Ok(expected)); +} + +#[test] +#[ignore] +fn strand_with_invalid_nucleotides() { + let output = nucleotide_counts("AGXXACT"); + assert!(output.is_err()); +} diff --git a/exercises/practice/ocr-numbers/.docs/instructions.md b/exercises/practice/ocr-numbers/.docs/instructions.md index 4086329bd..7beb25779 100644 --- a/exercises/practice/ocr-numbers/.docs/instructions.md +++ b/exercises/practice/ocr-numbers/.docs/instructions.md @@ -1,9 +1,8 @@ # Instructions -Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is -represented, or whether it is garbled. +Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled. -# Step One +## Step One To begin with, convert a simple binary font to a string containing 0 or 1. @@ -31,19 +30,19 @@ If the input is the correct size, but not recognizable, your program should retu If the input is the incorrect size, your program should return an error. -# Step Two +## Step Two Update your program to recognize multi-character binary strings, replacing garbled numbers with ? -# Step Three +## Step Three Update your program to recognize all numbers 0 through 9, both individually and as part of a larger string. ```text - _ + _ _| -|_ - +|_ + ``` Is converted to "2" @@ -57,23 +56,24 @@ Is converted to "2" Is converted to "1234567890" -# Step Four +## Step Four -Update your program to handle multiple numbers, one per line. When converting several lines, join the lines with commas. +Update your program to handle multiple numbers, one per line. +When converting several lines, join the lines with commas. ```text - _ _ + _ _ | _| _| ||_ _| - - _ _ -|_||_ |_ + + _ _ +|_||_ |_ | _||_| - - _ _ _ + + _ _ _ ||_||_| ||_| _| - + ``` -Is converted to "123,456,789" +Is converted to "123,456,789". diff --git a/exercises/practice/ocr-numbers/.gitignore b/exercises/practice/ocr-numbers/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/ocr-numbers/.gitignore +++ b/exercises/practice/ocr-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/ocr-numbers/.meta/config.json b/exercises/practice/ocr-numbers/.meta/config.json index 239448a21..13569f9d5 100644 --- a/exercises/practice/ocr-numbers/.meta/config.json +++ b/exercises/practice/ocr-numbers/.meta/config.json @@ -25,7 +25,7 @@ "Cargo.toml" ], "test": [ - "tests/ocr-numbers.rs" + "tests/ocr_numbers.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/ocr-numbers/.meta/example.rs b/exercises/practice/ocr-numbers/.meta/example.rs index 98153936f..5aea684df 100644 --- a/exercises/practice/ocr-numbers/.meta/example.rs +++ b/exercises/practice/ocr-numbers/.meta/example.rs @@ -8,12 +8,12 @@ pub enum Error { pub fn convert(input: &str) -> Result { let line_count = input.lines().count(); - if line_count % 4 != 0 { + if !line_count.is_multiple_of(4) { return Err(Error::InvalidRowCount(line_count)); } for line in input.lines() { let char_count = line.chars().count(); - if char_count % 3 != 0 { + if !char_count.is_multiple_of(3) { return Err(Error::InvalidColumnCount(char_count)); } } diff --git a/exercises/practice/ocr-numbers/.meta/tests.toml b/exercises/practice/ocr-numbers/.meta/tests.toml index 7f40437ff..0d7a5b770 100644 --- a/exercises/practice/ocr-numbers/.meta/tests.toml +++ b/exercises/practice/ocr-numbers/.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. [5ee54e1a-b554-4bf3-a056-9a7976c3f7e8] description = "Recognizes 0" @@ -8,9 +15,21 @@ description = "Recognizes 0" [027ada25-17fd-4d78-aee6-35a19623639d] description = "Recognizes 1" +[3cce2dbd-01d9-4f94-8fae-419a822e89bb] +description = "Unreadable but correctly sized inputs return ?" + +[cb19b733-4e36-4cf9-a4a1-6e6aac808b9a] +description = "Input with a number of lines that is not a multiple of four raises an error" + +[235f7bd1-991b-4587-98d4-84206eec4cc6] +description = "Input with a number of columns that is not a multiple of three raises an error" + [4a841794-73c9-4da9-a779-1f9837faff66] description = "Recognizes 110101100" +[70c338f9-85b1-4296-a3a8-122901cdfde8] +description = "Garbled numbers in a string are replaced with ?" + [ea494ff4-3610-44d7-ab7e-72fdef0e0802] description = "Recognizes 2" @@ -37,3 +56,6 @@ description = "Recognizes 9" [f60cb04a-42be-494e-a535-3451c8e097a4] description = "Recognizes string of decimal numbers" + +[b73ecf8b-4423-4b36-860d-3710bdb8a491] +description = "Numbers separated by empty lines are recognized. Lines are joined by commas." diff --git a/exercises/practice/ocr-numbers/Cargo.toml b/exercises/practice/ocr-numbers/Cargo.toml index 2f0af9aea..e99b7d21b 100644 --- a/exercises/practice/ocr-numbers/Cargo.toml +++ b/exercises/practice/ocr-numbers/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "ocr-numbers" -version = "0.0.0" +name = "ocr_numbers" +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/ocr-numbers/src/lib.rs b/exercises/practice/ocr-numbers/src/lib.rs index baea39693..fd08adad5 100644 --- a/exercises/practice/ocr-numbers/src/lib.rs +++ b/exercises/practice/ocr-numbers/src/lib.rs @@ -8,5 +8,5 @@ pub enum Error { } pub fn convert(input: &str) -> Result { - unimplemented!("Convert the input '{input}' to a string"); + todo!("Convert the input '{input}' to a string"); } diff --git a/exercises/practice/ocr-numbers/tests/ocr-numbers.rs b/exercises/practice/ocr-numbers/tests/ocr_numbers.rs similarity index 100% rename from exercises/practice/ocr-numbers/tests/ocr-numbers.rs rename to exercises/practice/ocr-numbers/tests/ocr_numbers.rs diff --git a/exercises/practice/paasio/.docs/instructions.append.md b/exercises/practice/paasio/.docs/instructions.append.md index 8fc197892..10428370d 100644 --- a/exercises/practice/paasio/.docs/instructions.append.md +++ b/exercises/practice/paasio/.docs/instructions.append.md @@ -1,4 +1,6 @@ -# Abstraction over Networks and Files +# Instructions append + +## Abstraction over Networks and Files Network and file operations are implemented in terms of the [`io::Read`][read] and [`io::Write`][write] traits. It will therefore be necessary to implement those traits for your types. diff --git a/exercises/practice/paasio/.docs/instructions.md b/exercises/practice/paasio/.docs/instructions.md index 67aaefaaa..595644748 100644 --- a/exercises/practice/paasio/.docs/instructions.md +++ b/exercises/practice/paasio/.docs/instructions.md @@ -2,13 +2,12 @@ Report network IO statistics. -You are writing a [PaaS][], and you need a way to bill customers based -on network and filesystem usage. +You are writing a [PaaS][paas], and you need a way to bill customers based on network and filesystem usage. -Create a wrapper for network connections and files that can report IO -statistics. The wrapper must report: +Create a wrapper for network connections and files that can report IO statistics. +The wrapper must report: - The total number of bytes read/written. - The total number of read/write operations. -[PaaS]: http://en.wikipedia.org/wiki/Platform_as_a_service +[paas]: https://en.wikipedia.org/wiki/Platform_as_a_service diff --git a/exercises/practice/paasio/.gitignore b/exercises/practice/paasio/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/paasio/.gitignore +++ b/exercises/practice/paasio/.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/paasio/.meta/config.json b/exercises/practice/paasio/.meta/config.json index 7feeed06a..04506cf73 100644 --- a/exercises/practice/paasio/.meta/config.json +++ b/exercises/practice/paasio/.meta/config.json @@ -29,8 +29,5 @@ }, "blurb": "Report network IO statistics.", "source": "Brian Matsuo", - "source_url": "https://github.com/bmatsuo", - "custom": { - "ignore-count-ignores": true - } + "source_url": "https://github.com/bmatsuo" } diff --git a/exercises/practice/paasio/Cargo.toml b/exercises/practice/paasio/Cargo.toml index 91ee30182..a6924c751 100644 --- a/exercises/practice/paasio/Cargo.toml +++ b/exercises/practice/paasio/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "paasio" -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/paasio/src/lib.rs b/exercises/practice/paasio/src/lib.rs index 8196a6c96..06666d172 100644 --- a/exercises/practice/paasio/src/lib.rs +++ b/exercises/practice/paasio/src/lib.rs @@ -1,5 +1,8 @@ use std::io::{Read, Result, Write}; +// the PhantomData instances in this file are just to stop compiler complaints +// about missing generics; feel free to remove them + pub struct ReadStats(::std::marker::PhantomData); impl ReadStats { @@ -7,25 +10,25 @@ impl ReadStats { // can't be passed through format!(). For actual implementation you will likely // wish to remove the leading underscore so the variable is not ignored. pub fn new(_wrapped: R) -> ReadStats { - unimplemented!() + todo!() } pub fn get_ref(&self) -> &R { - unimplemented!() + todo!() } pub fn bytes_through(&self) -> usize { - unimplemented!() + todo!() } pub fn reads(&self) -> usize { - unimplemented!() + todo!() } } impl Read for ReadStats { fn read(&mut self, buf: &mut [u8]) -> Result { - unimplemented!("Collect statistics about this call reading {buf:?}") + todo!("Collect statistics about this call reading {buf:?}") } } @@ -36,28 +39,28 @@ impl WriteStats { // can't be passed through format!(). For actual implementation you will likely // wish to remove the leading underscore so the variable is not ignored. pub fn new(_wrapped: W) -> WriteStats { - unimplemented!() + todo!() } pub fn get_ref(&self) -> &W { - unimplemented!() + todo!() } pub fn bytes_through(&self) -> usize { - unimplemented!() + todo!() } pub fn writes(&self) -> usize { - unimplemented!() + todo!() } } impl Write for WriteStats { fn write(&mut self, buf: &[u8]) -> Result { - unimplemented!("Collect statistics about this call writing {buf:?}") + todo!("Collect statistics about this call writing {buf:?}") } fn flush(&mut self) -> Result<()> { - unimplemented!() + todo!() } } diff --git a/exercises/practice/paasio/tests/paasio.rs b/exercises/practice/paasio/tests/paasio.rs index d13c0d2df..7a8c0acc4 100644 --- a/exercises/practice/paasio/tests/paasio.rs +++ b/exercises/practice/paasio/tests/paasio.rs @@ -1,194 +1,408 @@ -/// test a few read scenarios -macro_rules! test_read { - ($(#[$attr:meta])* $modname:ident ($input:expr, $len:expr)) => { - mod $modname { - use std::io::{Read, BufReader}; - use paasio::*; - - const CHUNK_SIZE: usize = 2; - - $(#[$attr])* - #[test] - fn test_read_passthrough() { - let data = $input; - let size = $len(&data); - let mut reader = ReadStats::new(data); - - let mut buffer = Vec::with_capacity(size); - let qty_read = reader.read_to_end(&mut buffer); - - assert!(qty_read.is_ok()); - assert_eq!(size, qty_read.unwrap()); - assert_eq!(size, buffer.len()); - // 2: first to read all the data, second to check that - // there wasn't any more pending data which simply didn't - // fit into the existing buffer - assert_eq!(2, reader.reads()); - assert_eq!(size, reader.bytes_through()); - } - - $(#[$attr])* - #[test] - fn test_read_chunks() { - let data = $input; - let size = $len(&data); - let mut reader = ReadStats::new(data); - - let mut buffer = [0_u8; CHUNK_SIZE]; - let mut chunks_read = 0; - while reader.read(&mut buffer[..]).unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read+1)) > 0 { - chunks_read += 1; - } - - assert_eq!(size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), chunks_read); - // we read once more than the number of chunks, because the final - // read returns 0 new bytes - assert_eq!(1+chunks_read, reader.reads()); - assert_eq!(size, reader.bytes_through()); - } - - $(#[$attr])* - #[test] - fn test_read_buffered_chunks() { - let data = $input; - let size = $len(&data); - let mut reader = BufReader::new(ReadStats::new(data)); - - let mut buffer = [0_u8; CHUNK_SIZE]; - let mut chunks_read = 0; - while reader.read(&mut buffer[..]).unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read+1)) > 0 { - chunks_read += 1; - } - - assert_eq!(size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), chunks_read); - // the BufReader should smooth out the reads, collecting into - // a buffer and performing only two read operations: - // the first collects everything into the buffer, - // and the second ensures that no data remains - assert_eq!(2, reader.get_ref().reads()); - assert_eq!(size, reader.get_ref().bytes_through()); - } +use std::io::{Error, ErrorKind, Read, Result, Write}; + +#[test] +fn create_stats() { + let mut data: Vec = Vec::new(); + let _ = paasio::ReadStats::new(data.as_slice()); + let _ = paasio::WriteStats::new(data.as_mut_slice()); +} + +mod read_string { + use paasio::*; + use std::io::{BufReader, Read}; + + const CHUNK_SIZE: usize = 2; + + static INPUT: &[u8] = b"Twas brillig, and the slithy toves/Did gyre and gimble in the wabe:/All mimsy were the borogoves,/And the mome raths outgrabe."; + + #[test] + #[ignore] + fn read_passthrough() { + let data = INPUT; + let size = data.len(); + let mut reader = ReadStats::new(data); + + let mut buffer = Vec::with_capacity(size); + let qty_read = reader.read_to_end(&mut buffer); + + assert!(qty_read.is_ok()); + assert_eq!(size, qty_read.unwrap()); + assert_eq!(size, buffer.len()); + // 2: first to read all the data, second to check that + // there wasn't any more pending data which simply didn't + // fit into the existing buffer + assert_eq!(2, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_chunks() { + let data = INPUT; + let size = data.len(); + let mut reader = ReadStats::new(data); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader.read(&mut buffer[..]).unwrap() > 0 { + chunks_read += 1; } - }; + + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); + // we read once more than the number of chunks, because the final + // read returns 0 new bytes + assert_eq!(1 + chunks_read, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_buffered_chunks() { + let data = INPUT; + let size = data.len(); + let mut reader = BufReader::new(ReadStats::new(data)); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader.read(&mut buffer[..]).unwrap() > 0 { + chunks_read += 1; + } + + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); + // the BufReader should smooth out the reads, collecting into + // a buffer and performing only two read operations: + // the first collects everything into the buffer, + // and the second ensures that no data remains + assert_eq!(2, reader.get_ref().reads()); + assert_eq!(size, reader.get_ref().bytes_through()); + } } -/// test a few write scenarios -macro_rules! test_write { - ($(#[$attr:meta])* $modname:ident ($input:expr, $len:expr)) => { - mod $modname { - use std::io::{self, Write, BufWriter}; - use paasio::*; - - const CHUNK_SIZE: usize = 2; - $(#[$attr])* - #[test] - fn test_write_passthrough() { - let data = $input; - let size = $len(&data); - let mut writer = WriteStats::new(Vec::with_capacity(size)); - let written = writer.write(data); - assert!(written.is_ok()); - assert_eq!(size, written.unwrap()); - assert_eq!(size, writer.bytes_through()); - assert_eq!(1, writer.writes()); - assert_eq!(data, writer.get_ref().as_slice()); - } - - $(#[$attr])* - #[test] - fn test_sink_oneshot() { - let data = $input; - let size = $len(&data); - let mut writer = WriteStats::new(io::sink()); - let written = writer.write(data); - assert!(written.is_ok()); - assert_eq!(size, written.unwrap()); - assert_eq!(size, writer.bytes_through()); - assert_eq!(1, writer.writes()); - } - - $(#[$attr])* - #[test] - fn test_sink_windowed() { - let data = $input; - let size = $len(&data); - let mut writer = WriteStats::new(io::sink()); - - let mut chunk_count = 0; - for chunk in data.chunks(CHUNK_SIZE) { - chunk_count += 1; - let written = writer.write(chunk); - assert!(written.is_ok()); - assert_eq!(CHUNK_SIZE, written.unwrap()); - } - assert_eq!(size, writer.bytes_through()); - assert_eq!(chunk_count, writer.writes()); - } - - $(#[$attr])* - #[test] - fn test_sink_buffered_windowed() { - let data = $input; - let size = $len(&data); - let mut writer = BufWriter::new(WriteStats::new(io::sink())); - - for chunk in data.chunks(CHUNK_SIZE) { - let written = writer.write(chunk); - assert!(written.is_ok()); - assert_eq!(CHUNK_SIZE, written.unwrap()); - } - // at this point, nothing should have yet been passed through to - // our writer - assert_eq!(0, writer.get_ref().bytes_through()); - assert_eq!(0, writer.get_ref().writes()); - - // after flushing, everything should pass through in one go - assert!(writer.flush().is_ok()); - assert_eq!(size, writer.get_ref().bytes_through()); - assert_eq!(1, writer.get_ref().writes()); - } +mod write_string { + use paasio::*; + use std::io::{self, BufWriter, Write}; + + const CHUNK_SIZE: usize = 2; + + static INPUT: &[u8] = b"Beware the Jabberwock, my son!/The jaws that bite, the claws that catch!/Beware the Jubjub bird, and shun/The frumious Bandersnatch!"; + + #[test] + #[ignore] + fn write_passthrough() { + let data = INPUT; + let size = data.len(); + let mut writer = WriteStats::new(Vec::with_capacity(size)); + let written = writer.write(data); + assert!(written.is_ok()); + assert_eq!(size, written.unwrap()); + assert_eq!(size, writer.bytes_through()); + assert_eq!(1, writer.writes()); + assert_eq!(data, writer.get_ref().as_slice()); + } + + #[test] + #[ignore] + fn sink_oneshot() { + let data = INPUT; + let size = data.len(); + let mut writer = WriteStats::new(io::sink()); + let written = writer.write(data); + assert!(written.is_ok()); + assert_eq!(size, written.unwrap()); + assert_eq!(size, writer.bytes_through()); + assert_eq!(1, writer.writes()); + } + + #[test] + #[ignore] + fn sink_windowed() { + let data = INPUT; + let size = data.len(); + let mut writer = WriteStats::new(io::sink()); + + let mut chunk_count = 0; + for chunk in data.chunks(CHUNK_SIZE) { + chunk_count += 1; + let written = writer.write(chunk); + assert!(written.is_ok()); + assert_eq!(CHUNK_SIZE, written.unwrap()); } - }; + assert_eq!(size, writer.bytes_through()); + assert_eq!(chunk_count, writer.writes()); + } + + #[test] + #[ignore] + fn sink_buffered_windowed() { + let data = INPUT; + let size = data.len(); + + // We store the inner writer in a separate variable so its destructor + // is called correctly. We then wrap a mutable reference to it in a + // buffered writer. The destructor of the buffered writer is suppressed, + // because it tries to flush the inner writer. (It doesn't do anything + // else, so it's fine to skip it.) This would cause a non-unwinding + // panic if the inner writer hasn't implemented `write` yet. The + // standard library implementation of `BufWriter` does try to keep track + // of a panic by the inner writer, but it's not perfect. We access the + // inner writer later with `.get_ref()`. If there is a panic in that + // situation, the buffered writer cannot observe and track it. + // + // Related forum discussion: + // https://forum.exercism.org/t/test-runner-fail-on-paas/19426 + let mut inner_writer = WriteStats::new(io::sink()); + let mut writer = std::mem::ManuallyDrop::new(BufWriter::new(&mut inner_writer)); + + for chunk in data.chunks(CHUNK_SIZE) { + let written = writer.write(chunk); + assert!(written.is_ok()); + assert_eq!(CHUNK_SIZE, written.unwrap()); + } + // at this point, nothing should have yet been passed through to + // our writer + assert_eq!(0, writer.get_ref().bytes_through()); + assert_eq!(0, writer.get_ref().writes()); + + // after flushing, everything should pass through in one go + assert!(writer.flush().is_ok()); + assert_eq!(size, writer.get_ref().bytes_through()); + assert_eq!(1, writer.get_ref().writes()); + } } -#[test] -fn test_create_stats() { - let mut data: Vec = Vec::new(); - let _ = paasio::ReadStats::new(data.as_slice()); - let _ = paasio::WriteStats::new(data.as_mut_slice()); +mod read_byte_literal { + use paasio::*; + use std::io::{BufReader, Read}; + + const CHUNK_SIZE: usize = 2; + + static INPUT: &[u8] = &[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]; + + #[test] + #[ignore] + fn read_passthrough() { + let data = INPUT; + let size = data.len(); + let mut reader = ReadStats::new(data); + + let mut buffer = Vec::with_capacity(size); + let qty_read = reader.read_to_end(&mut buffer); + + assert!(qty_read.is_ok()); + assert_eq!(size, qty_read.unwrap()); + assert_eq!(size, buffer.len()); + // 2: first to read all the data, second to check that + // there wasn't any more pending data which simply didn't + // fit into the existing buffer + assert_eq!(2, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_chunks() { + let data = INPUT; + let size = data.len(); + let mut reader = ReadStats::new(data); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader.read(&mut buffer[..]).unwrap() > 0 { + chunks_read += 1; + } + + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); + // we read once more than the number of chunks, because the final + // read returns 0 new bytes + assert_eq!(1 + chunks_read, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_buffered_chunks() { + let data = INPUT; + let size = data.len(); + let mut reader = BufReader::new(ReadStats::new(data)); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader.read(&mut buffer[..]).unwrap() > 0 { + chunks_read += 1; + } + + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); + // the BufReader should smooth out the reads, collecting into + // a buffer and performing only two read operations: + // the first collects everything into the buffer, + // and the second ensures that no data remains + assert_eq!(2, reader.get_ref().reads()); + assert_eq!(size, reader.get_ref().bytes_through()); + } } -test_read!(#[ignore] read_string ( - "Twas brillig, and the slithy toves/Did gyre and gimble in the wabe:/All mimsy were the borogoves,/And the mome raths outgrabe.".as_bytes(), - |d: &[u8]| d.len() -)); -test_write!(#[ignore] write_string ( - "Beware the Jabberwock, my son!/The jaws that bite, the claws that catch!/Beware the Jubjub bird, and shun/The frumious Bandersnatch!".as_bytes(), - |d: &[u8]| d.len() -)); - -test_read!( - #[ignore] - read_byte_literal( - &[1_u8, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144][..], - |d: &[u8]| d.len() - ) -); -test_write!( - #[ignore] - write_byte_literal( - &[2_u8, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61,][..], - |d: &[u8]| d.len() - ) -); - -test_read!( - #[ignore] - read_file( - ::std::fs::File::open("Cargo.toml").expect("Cargo.toml must be present"), - |f: &::std::fs::File| f.metadata().expect("metadata must be present").len() as usize - ) -); +mod write_byte_literal { + use paasio::*; + use std::io::{self, BufWriter, Write}; + + const CHUNK_SIZE: usize = 2; + + static INPUT: &[u8] = &[ + 2_u8, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, + ]; + + #[test] + #[ignore] + fn write_passthrough() { + let data = INPUT; + let size = data.len(); + let mut writer = WriteStats::new(Vec::with_capacity(size)); + let written = writer.write(data); + assert!(written.is_ok()); + assert_eq!(size, written.unwrap()); + assert_eq!(size, writer.bytes_through()); + assert_eq!(1, writer.writes()); + assert_eq!(data, writer.get_ref().as_slice()); + } + + #[test] + #[ignore] + fn sink_oneshot() { + let data = INPUT; + let size = data.len(); + let mut writer = WriteStats::new(io::sink()); + let written = writer.write(data); + assert!(written.is_ok()); + assert_eq!(size, written.unwrap()); + assert_eq!(size, writer.bytes_through()); + assert_eq!(1, writer.writes()); + } + + #[test] + #[ignore] + fn sink_windowed() { + let data = INPUT; + let size = data.len(); + let mut writer = WriteStats::new(io::sink()); + + let mut chunk_count = 0; + for chunk in data.chunks(CHUNK_SIZE) { + chunk_count += 1; + let written = writer.write(chunk); + assert!(written.is_ok()); + assert_eq!(CHUNK_SIZE, written.unwrap()); + } + assert_eq!(size, writer.bytes_through()); + assert_eq!(chunk_count, writer.writes()); + } + + #[test] + #[ignore] + fn sink_buffered_windowed() { + let data = INPUT; + let size = data.len(); + + // We store the inner writer in a separate variable so its destructor + // is called correctly. We then wrap a mutable reference to it in a + // buffered writer. The destructor of the buffered writer is suppressed, + // because it tries to flush the inner writer. (It doesn't do anything + // else, so it's fine to skip it.) This would cause a non-unwinding + // panic if the inner writer hasn't implemented `write` yet. The + // standard library implementation of `BufWriter` does try to keep track + // of a panic by the inner writer, but it's not perfect. We access the + // inner writer later with `.get_ref()`. If there is a panic in that + // situation, the buffered writer cannot observe and track it. + // + // Related forum discussion: + // https://forum.exercism.org/t/test-runner-fail-on-paas/19426 + let mut inner_writer = WriteStats::new(io::sink()); + let mut writer = std::mem::ManuallyDrop::new(BufWriter::new(&mut inner_writer)); + + for chunk in data.chunks(CHUNK_SIZE) { + let written = writer.write(chunk); + assert!(written.is_ok()); + assert_eq!(CHUNK_SIZE, written.unwrap()); + } + // at this point, nothing should have yet been passed through to + // our writer + assert_eq!(0, writer.get_ref().bytes_through()); + assert_eq!(0, writer.get_ref().writes()); + + // after flushing, everything should pass through in one go + assert!(writer.flush().is_ok()); + assert_eq!(size, writer.get_ref().bytes_through()); + assert_eq!(1, writer.get_ref().writes()); + } +} + +mod read_file { + use paasio::*; + use std::io::{BufReader, Read}; + + const CHUNK_SIZE: usize = 2; + + #[test] + #[ignore] + fn read_passthrough() { + let data = std::fs::File::open("Cargo.toml").expect("Cargo.toml must be present"); + let size = data.metadata().expect("metadata must be present").len() as usize; + let mut reader = ReadStats::new(data); + + let mut buffer = Vec::with_capacity(size); + let qty_read = reader.read_to_end(&mut buffer); + + assert!(qty_read.is_ok()); + assert_eq!(size, qty_read.unwrap()); + assert_eq!(size, buffer.len()); + // 2: first to read all the data, second to check that + // there wasn't any more pending data which simply didn't + // fit into the existing buffer + assert_eq!(2, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_chunks() { + let data = std::fs::File::open("Cargo.toml").expect("Cargo.toml must be present"); + let size = data.metadata().expect("metadata must be present").len() as usize; + let mut reader = ReadStats::new(data); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader.read(&mut buffer[..]).unwrap() > 0 { + chunks_read += 1; + } + + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); + // we read once more than the number of chunks, because the final + // read returns 0 new bytes + assert_eq!(1 + chunks_read, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_buffered_chunks() { + let data = std::fs::File::open("Cargo.toml").expect("Cargo.toml must be present"); + let size = data.metadata().expect("metadata must be present").len() as usize; + let mut reader = BufReader::new(ReadStats::new(data)); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader.read(&mut buffer[..]).unwrap() > 0 { + chunks_read += 1; + } + + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); + // the BufReader should smooth out the reads, collecting into + // a buffer and performing only two read operations: + // the first collects everything into the buffer, + // and the second ensures that no data remains + assert_eq!(2, reader.get_ref().reads()); + assert_eq!(size, reader.get_ref().bytes_through()); + } +} #[test] #[ignore] @@ -200,3 +414,67 @@ fn read_stats_by_ref_returns_wrapped_reader() { let reader = ReadStats::new(input); assert_eq!(reader.get_ref(), &input); } + +/// a Read type that always errors +struct ReadFails; + +impl ReadFails { + const MESSAGE: &'static str = "this reader always fails"; +} + +impl Read for ReadFails { + fn read(&mut self, _buf: &mut [u8]) -> Result { + Err(Error::other(Self::MESSAGE)) + } +} + +/// a Write type that always errors +struct WriteFails; + +impl WriteFails { + const MESSAGE: &'static str = "this writer always fails"; +} + +impl Write for WriteFails { + fn write(&mut self, _buf: &[u8]) -> Result { + Err(Error::other(Self::MESSAGE)) + } + + fn flush(&mut self) -> Result<()> { + Err(Error::other(Self::MESSAGE)) + } +} + +#[test] +#[ignore] +fn read_propagates_errors() { + use paasio::ReadStats; + + let mut reader = ReadStats::new(ReadFails); + let mut buffer = Vec::new(); + + let Err(e) = reader.read(&mut buffer) else { + panic!("read error not propagated") + }; + + // check that the correct error was returned + assert_eq!(e.kind(), ErrorKind::Other); + assert_eq!(e.get_ref().unwrap().to_string(), ReadFails::MESSAGE); +} + +#[test] +#[ignore] +fn write_propagates_errors() { + use paasio::WriteStats; + + let mut writer = WriteStats::new(WriteFails); + let buffer = "This text won't be written"; + + let Err(e) = writer.write(buffer.as_bytes()) else { + panic!("write error not propagated") + }; + + // check that the correct error is returned + assert_eq!(e.kind(), ErrorKind::Other); + assert_eq!(e.get_ref().unwrap().to_string(), WriteFails::MESSAGE); +} diff --git a/exercises/practice/palindrome-products/.docs/instructions.md b/exercises/practice/palindrome-products/.docs/instructions.md index fd9a44124..aac66521c 100644 --- a/exercises/practice/palindrome-products/.docs/instructions.md +++ b/exercises/practice/palindrome-products/.docs/instructions.md @@ -2,15 +2,14 @@ Detect palindrome products in a given range. -A palindromic number is a number that remains the same when its digits are -reversed. For example, `121` is a palindromic number but `112` is not. +A palindromic number is a number that remains the same when its digits are reversed. +For example, `121` is a palindromic number but `112` is not. Given a range of numbers, find the largest and smallest palindromes which are products of two numbers within that range. -Your solution should return the largest and smallest palindromes, along with the -factors of each within the range. If the largest or smallest palindrome has more -than one pair of factors within the range, then return all the pairs. +Your solution should return the largest and smallest palindromes, along with the factors of each within the range. +If the largest or smallest palindrome has more than one pair of factors within the range, then return all the pairs. ## Example 1 @@ -22,12 +21,16 @@ And given the list of all possible products within this range: The palindrome products are all single digit numbers (in this case): `[1, 2, 3, 4, 5, 6, 7, 8, 9]` -The smallest palindrome product is `1`. Its factors are `(1, 1)`. -The largest palindrome product is `9`. Its factors are `(1, 9)` and `(3, 3)`. +The smallest palindrome product is `1`. +Its factors are `(1, 1)`. +The largest palindrome product is `9`. +Its factors are `(1, 9)` and `(3, 3)`. ## Example 2 Given the range `[10, 99]` (both inclusive)... -The smallest palindrome product is `121`. Its factors are `(11, 11)`. -The largest palindrome product is `9009`. Its factors are `(91, 99)`. +The smallest palindrome product is `121`. +Its factors are `(11, 11)`. +The largest palindrome product is `9009`. +Its factors are `(91, 99)`. diff --git a/exercises/practice/palindrome-products/.gitignore b/exercises/practice/palindrome-products/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/palindrome-products/.gitignore +++ b/exercises/practice/palindrome-products/.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/palindrome-products/.meta/config.json b/exercises/practice/palindrome-products/.meta/config.json index ffcc8fe67..cb48e3db2 100644 --- a/exercises/practice/palindrome-products/.meta/config.json +++ b/exercises/practice/palindrome-products/.meta/config.json @@ -22,7 +22,7 @@ "Cargo.toml" ], "test": [ - "tests/palindrome-products.rs" + "tests/palindrome_products.rs" ], "example": [ ".meta/example.rs" @@ -30,7 +30,7 @@ }, "blurb": "Detect palindrome products in a given range.", "source": "Problem 4 at Project Euler", - "source_url": "http://projecteuler.net/problem=4", + "source_url": "https://projecteuler.net/problem=4", "custom": { "test-in-release-mode": true } diff --git a/exercises/practice/palindrome-products/.meta/example.rs b/exercises/practice/palindrome-products/.meta/example.rs index a2e3ce7c9..43799fd5c 100644 --- a/exercises/practice/palindrome-products/.meta/example.rs +++ b/exercises/practice/palindrome-products/.meta/example.rs @@ -1,19 +1,30 @@ -/// `Palindrome` is a newtype which only exists when the contained value is a palindrome number in base ten. -/// -/// A struct with a single field which is used to constrain behavior like this is called a "newtype", and its use is -/// often referred to as the "newtype pattern". This is a fairly common pattern in Rust. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] -pub struct Palindrome(u64); +use std::cmp::Ordering; +use std::collections::HashSet; + +#[derive(Debug, Clone)] +pub struct Palindrome { + value: u64, + factors: HashSet<(u64, u64)>, +} impl Palindrome { - /// Create a `Palindrome` only if `value` is in fact a palindrome when represented in base ten. Otherwise, `None`. - pub fn new(value: u64) -> Option { - is_palindrome(value).then(move || Palindrome(value)) + pub fn new(value: u64, first_factors: (u64, u64)) -> Palindrome { + Self { + value, + factors: HashSet::from([first_factors]), + } + } + + pub fn add_factor(&mut self, factor: (u64, u64)) -> bool { + self.factors.insert(factor) + } + + pub fn value(&self) -> u64 { + self.value } - /// Get the value of this palindrome. - pub fn into_inner(self) -> u64 { - self.0 + pub fn into_factors(self) -> HashSet<(u64, u64)> { + self.factors } } @@ -22,14 +33,28 @@ pub fn palindrome_products(min: u64, max: u64) -> Option<(Palindrome, Palindrome let mut pmax: Option = None; for i in min..=max { for j in i..=max { - if let Some(palindrome) = Palindrome::new(i * j) { - pmin = match pmin { - None => Some(palindrome), - Some(prev) => Some(prev.min(palindrome)), + let p = i * j; + if is_palindrome(p) { + pmin = match pmin.as_ref().map(|prev| prev.value.cmp(&p)) { + Some(Ordering::Less) => pmin, + Some(Ordering::Equal) => { + if i <= j { + pmin.as_mut().unwrap().add_factor((i, j)); + } + pmin + } + Some(Ordering::Greater) | None => Some(Palindrome::new(p, (i, j))), }; - pmax = match pmax { - None => Some(palindrome), - Some(prev) => Some(prev.max(palindrome)), + + pmax = match pmax.as_ref().map(|prev| prev.value.cmp(&p)) { + Some(Ordering::Greater) => pmax, + Some(Ordering::Equal) => { + if i <= j { + pmax.as_mut().unwrap().add_factor((i, j)); + } + pmax + } + Some(Ordering::Less) | None => Some(Palindrome::new(p, (i, j))), }; } } diff --git a/exercises/practice/palindrome-products/.meta/test_template.tera b/exercises/practice/palindrome-products/.meta/test_template.tera new file mode 100644 index 000000000..626868776 --- /dev/null +++ b/exercises/practice/palindrome-products/.meta/test_template.tera @@ -0,0 +1,26 @@ +use palindrome_products::*; +use std::collections::HashSet; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let output = palindrome_products({{ test.input.min }}, {{ test.input.max }}); + + {%- if test.expected.error is defined or not test.expected.value %} + assert!(output.is_none()); + {%- else %} + assert!(output.is_some()); + + {% if test.property == "smallest" %} + let (pal, _) = output.unwrap(); + {%- else %} + let (_, pal) = output.unwrap(); + {%- endif%} + assert_eq!(pal.value(), {{ test.expected.value }}); + assert_eq!(pal.into_factors(), HashSet::from([ + {{- test.expected.factors | join(sep=", ") | replace(from="[", to="(") | replace(from="]", to=")") -}} + ])); + {%- endif%} +} +{% endfor -%} diff --git a/exercises/practice/palindrome-products/.meta/tests.toml b/exercises/practice/palindrome-products/.meta/tests.toml index be690e975..a3bc41750 100644 --- a/exercises/practice/palindrome-products/.meta/tests.toml +++ b/exercises/practice/palindrome-products/.meta/tests.toml @@ -1,3 +1,49 @@ -# 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. + +[5cff78fe-cf02-459d-85c2-ce584679f887] +description = "find the smallest palindrome from single digit factors" + +[0853f82c-5fc4-44ae-be38-fadb2cced92d] +description = "find the largest palindrome from single digit factors" + +[66c3b496-bdec-4103-9129-3fcb5a9063e1] +description = "find the smallest palindrome from double digit factors" + +[a10682ae-530a-4e56-b89d-69664feafe53] +description = "find the largest palindrome from double digit factors" + +[cecb5a35-46d1-4666-9719-fa2c3af7499d] +description = "find the smallest palindrome from triple digit factors" + +[edab43e1-c35f-4ea3-8c55-2f31dddd92e5] +description = "find the largest palindrome from triple digit factors" + +[4f802b5a-9d74-4026-a70f-b53ff9234e4e] +description = "find the smallest palindrome from four digit factors" + +[787525e0-a5f9-40f3-8cb2-23b52cf5d0be] +description = "find the largest palindrome from four digit factors" + +[58fb1d63-fddb-4409-ab84-a7a8e58d9ea0] +description = "empty result for smallest if no palindrome in the range" + +[9de9e9da-f1d9-49a5-8bfc-3d322efbdd02] +description = "empty result for largest if no palindrome in the range" + +[12e73aac-d7ee-4877-b8aa-2aa3dcdb9f8a] +description = "error result for smallest if min is more than max" + +[eeeb5bff-3f47-4b1e-892f-05829277bd74] +description = "error result for largest if min is more than max" + +[16481711-26c4-42e0-9180-e2e4e8b29c23] +description = "smallest product does not use the smallest factor" diff --git a/exercises/practice/palindrome-products/Cargo.toml b/exercises/practice/palindrome-products/Cargo.toml index a082817c1..6629a3933 100644 --- a/exercises/practice/palindrome-products/Cargo.toml +++ b/exercises/practice/palindrome-products/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" -name = "palindrome-products" -version = "1.2.0" +name = "palindrome_products" +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/palindrome-products/src/lib.rs b/exercises/practice/palindrome-products/src/lib.rs index c90fffdf9..5b46d9c21 100644 --- a/exercises/practice/palindrome-products/src/lib.rs +++ b/exercises/practice/palindrome-products/src/lib.rs @@ -1,24 +1,22 @@ -/// `Palindrome` is a newtype which only exists when the contained value is a palindrome number in base ten. -/// -/// A struct with a single field which is used to constrain behavior like this is called a "newtype", and its use is -/// often referred to as the "newtype pattern". This is a fairly common pattern in Rust. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Palindrome(u64); +use std::collections::HashSet; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Palindrome { + // TODO +} impl Palindrome { - /// Create a `Palindrome` only if `value` is in fact a palindrome when represented in base ten. Otherwise, `None`. - pub fn new(value: u64) -> Option { - unimplemented!("if the value {value} is a palindrome return Some, otherwise return None"); + pub fn value(&self) -> u64 { + todo!("return the value of the palindrome") } - /// Get the value of this palindrome. - pub fn into_inner(self) -> u64 { - unimplemented!("return inner value of a Palindrome"); + pub fn into_factors(self) -> HashSet<(u64, u64)> { + todo!("return the set of factors of the palindrome") } } pub fn palindrome_products(min: u64, max: u64) -> Option<(Palindrome, Palindrome)> { - unimplemented!( - "returns the minimum and maximum number of palindromes of the products of two factors in the range {min} to {max}" + todo!( + "returns the minimum palindrome and maximum palindrome of the products of two factors in the range {min} to {max}" ); } diff --git a/exercises/practice/palindrome-products/tests/palindrome-products.rs b/exercises/practice/palindrome-products/tests/palindrome-products.rs deleted file mode 100644 index b3a32f0ad..000000000 --- a/exercises/practice/palindrome-products/tests/palindrome-products.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! This test suite was generated by the rust exercise tool, which can be found at -//! https://github.com/exercism/rust/tree/main/util/exercise - -use palindrome_products::{palindrome_products, Palindrome}; - -/// Process a single test case for the property `smallest` -/// -/// All cases for the `smallest` property are implemented -/// in terms of this function. -fn process_smallest_case((from, to): (u64, u64), expected: Option) { - let min = palindrome_products(from, to).map(|(min, _)| min); - assert_eq!(min.map(|newtype| newtype.into_inner()), expected); -} - -/// Process a single test case for the property `largest` -/// -/// All cases for the `largest` property are implemented -/// in terms of this function. -fn process_largest_case((from, to): (u64, u64), expected: Option) { - let max = palindrome_products(from, to).map(|(_, max)| max); - assert_eq!(max.map(|newtype| newtype.into_inner()), expected); -} - -#[test] -/// test `Palindrome::new` with valid input -fn test_palindrome_new_return_some() { - for v in [1, 11, 121, 12321, 1234321, 123454321, 543212345] { - assert_eq!(Palindrome::new(v).expect("is a palindrome").into_inner(), v); - } -} - -#[test] -#[ignore] -/// test `Palindrome::new` with invalid input -fn test_palindrome_new_return_none() { - for v in [12, 2322, 23443, 1233211, 8932343] { - assert_eq!(Palindrome::new(v), None); - } -} - -#[test] -#[ignore] -/// finds the smallest palindrome from single digit factors -fn test_finds_the_smallest_palindrome_from_single_digit_factors() { - process_smallest_case((1, 9), Some(1)); -} - -#[test] -#[ignore] -/// finds the largest palindrome from single digit factors -fn test_finds_the_largest_palindrome_from_single_digit_factors() { - process_largest_case((1, 9), Some(9)); -} - -#[test] -#[ignore] -/// find the smallest palindrome from double digit factors -fn test_find_the_smallest_palindrome_from_double_digit_factors() { - process_smallest_case((10, 99), Some(121)); -} - -#[test] -#[ignore] -/// find the largest palindrome from double digit factors -fn test_find_the_largest_palindrome_from_double_digit_factors() { - process_largest_case((10, 99), Some(9009)); -} - -#[test] -#[ignore] -/// find smallest palindrome from triple digit factors -fn test_find_smallest_palindrome_from_triple_digit_factors() { - process_smallest_case((100, 999), Some(10201)); -} - -#[test] -#[ignore] -/// find the largest palindrome from triple digit factors -fn test_find_the_largest_palindrome_from_triple_digit_factors() { - process_largest_case((100, 999), Some(906609)); -} - -#[test] -#[ignore] -/// find smallest palindrome from four digit factors -fn test_find_smallest_palindrome_from_four_digit_factors() { - process_smallest_case((1000, 9999), Some(1002001)); -} - -#[test] -#[ignore] -/// find the largest palindrome from four digit factors -fn test_find_the_largest_palindrome_from_four_digit_factors() { - process_largest_case((1000, 9999), Some(99000099)); -} - -#[test] -#[ignore] -/// empty result for smallest if no palindrome in the range -fn test_empty_result_for_smallest_if_no_palindrome_in_the_range() { - process_smallest_case((1002, 1003), None); -} - -#[test] -#[ignore] -/// empty result for largest if no palindrome in the range -fn test_empty_result_for_largest_if_no_palindrome_in_the_range() { - process_largest_case((15, 15), None); -} - -#[test] -#[ignore] -/// error result for smallest if min is more than max -fn test_error_result_for_smallest_if_min_is_more_than_max() { - process_smallest_case((10000, 1), None); -} - -#[test] -#[ignore] -/// error result for largest if min is more than max -fn test_error_result_for_largest_if_min_is_more_than_max() { - process_largest_case((2, 1), None); -} diff --git a/exercises/practice/palindrome-products/tests/palindrome_products.rs b/exercises/practice/palindrome-products/tests/palindrome_products.rs new file mode 100644 index 000000000..d5c230058 --- /dev/null +++ b/exercises/practice/palindrome-products/tests/palindrome_products.rs @@ -0,0 +1,128 @@ +use palindrome_products::*; +use std::collections::HashSet; + +#[test] +fn find_the_smallest_palindrome_from_single_digit_factors() { + let output = palindrome_products(1, 9); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 1); + assert_eq!(pal.into_factors(), HashSet::from([(1, 1)])); +} + +#[test] +#[ignore] +fn find_the_largest_palindrome_from_single_digit_factors() { + let output = palindrome_products(1, 9); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 9); + assert_eq!(pal.into_factors(), HashSet::from([(1, 9), (3, 3)])); +} + +#[test] +#[ignore] +fn find_the_smallest_palindrome_from_double_digit_factors() { + let output = palindrome_products(10, 99); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 121); + assert_eq!(pal.into_factors(), HashSet::from([(11, 11)])); +} + +#[test] +#[ignore] +fn find_the_largest_palindrome_from_double_digit_factors() { + let output = palindrome_products(10, 99); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 9009); + assert_eq!(pal.into_factors(), HashSet::from([(91, 99)])); +} + +#[test] +#[ignore] +fn find_the_smallest_palindrome_from_triple_digit_factors() { + let output = palindrome_products(100, 999); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 10201); + assert_eq!(pal.into_factors(), HashSet::from([(101, 101)])); +} + +#[test] +#[ignore] +fn find_the_largest_palindrome_from_triple_digit_factors() { + let output = palindrome_products(100, 999); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 906609); + assert_eq!(pal.into_factors(), HashSet::from([(913, 993)])); +} + +#[test] +#[ignore] +fn find_the_smallest_palindrome_from_four_digit_factors() { + let output = palindrome_products(1000, 9999); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 1002001); + assert_eq!(pal.into_factors(), HashSet::from([(1001, 1001)])); +} + +#[test] +#[ignore] +fn find_the_largest_palindrome_from_four_digit_factors() { + let output = palindrome_products(1000, 9999); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 99000099); + assert_eq!(pal.into_factors(), HashSet::from([(9901, 9999)])); +} + +#[test] +#[ignore] +fn empty_result_for_smallest_if_no_palindrome_in_the_range() { + let output = palindrome_products(1002, 1003); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn empty_result_for_largest_if_no_palindrome_in_the_range() { + let output = palindrome_products(15, 15); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn error_result_for_smallest_if_min_is_more_than_max() { + let output = palindrome_products(10000, 1); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn error_result_for_largest_if_min_is_more_than_max() { + let output = palindrome_products(2, 1); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn smallest_product_does_not_use_the_smallest_factor() { + let output = palindrome_products(3215, 4000); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 10988901); + assert_eq!(pal.into_factors(), HashSet::from([(3297, 3333)])); +} diff --git a/exercises/practice/pangram/.articles/performance/code/main.rs b/exercises/practice/pangram/.articles/performance/code/main.rs index f56c3ce21..fadcc667d 100644 --- a/exercises/practice/pangram/.articles/performance/code/main.rs +++ b/exercises/practice/pangram/.articles/performance/code/main.rs @@ -47,7 +47,7 @@ pub fn is_pangram_bitfield(sentence: &str) -> bool { } #[bench] -fn test_is_pangram_all_contains(b: &mut Bencher) { +fn is_pangram_all_contains(b: &mut Bencher) { b.iter(|| { is_pangram_all_contains( "Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.", @@ -56,7 +56,7 @@ fn test_is_pangram_all_contains(b: &mut Bencher) { } #[bench] -fn test_is_pangram_hash_is_subset(b: &mut Bencher) { +fn is_pangram_hash_is_subset(b: &mut Bencher) { b.iter(|| { is_pangram_hash_is_subset( "Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.", @@ -65,7 +65,7 @@ fn test_is_pangram_hash_is_subset(b: &mut Bencher) { } #[bench] -fn test_is_pangram_hashset_len(b: &mut Bencher) { +fn is_pangram_hashset_len(b: &mut Bencher) { b.iter(|| { is_pangram_hashset_len( "Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.", @@ -74,7 +74,7 @@ fn test_is_pangram_hashset_len(b: &mut Bencher) { } #[bench] -fn test_is_pangram_bitfield(b: &mut Bencher) { +fn is_pangram_bitfield(b: &mut Bencher) { b.iter(|| { is_pangram_bitfield("Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.") }); diff --git a/exercises/practice/pangram/.articles/performance/content.md b/exercises/practice/pangram/.articles/performance/content.md index a13b56d55..e1ba60bbe 100644 --- a/exercises/practice/pangram/.articles/performance/content.md +++ b/exercises/practice/pangram/.articles/performance/content.md @@ -14,10 +14,10 @@ The [approaches page][approaches] lists four idiomatic approaches to this exerc To benchmark the approaches, we wrote a [small benchmark application][benchmark-application]. ``` -test test_is_pangram_all_contains ... bench: 850 ns/iter (+/- 525) -test test_is_pangram_hash_is_subset ... bench: 3,113 ns/iter (+/- 188) -test test_is_pangram_hashset_len ... bench: 1,737 ns/iter (+/- 331) -test test_is_pangram_bitfield ... bench: 76 ns/iter (+/- 12) +test is_pangram_all_contains ... bench: 850 ns/iter (+/- 525) +test is_pangram_hash_is_subset ... bench: 3,113 ns/iter (+/- 188) +test is_pangram_hashset_len ... bench: 1,737 ns/iter (+/- 331) +test is_pangram_bitfield ... bench: 76 ns/iter (+/- 12) ``` - The `HashSet` `len` approach is not quite twice as fast as the `HashSet` `is_subset` approach. diff --git a/exercises/practice/pangram/.articles/performance/snippet.md b/exercises/practice/pangram/.articles/performance/snippet.md index c160dc70b..dc80ecfd8 100644 --- a/exercises/practice/pangram/.articles/performance/snippet.md +++ b/exercises/practice/pangram/.articles/performance/snippet.md @@ -1,6 +1,6 @@ ``` -test test_is_pangram_all_contains ... bench: 850 ns/iter (+/- 525) -test test_is_pangram_hash_is_subset ... bench: 3,113 ns/iter (+/- 188) -test test_is_pangram_hashset_len ... bench: 1,737 ns/iter (+/- 331) -test test_is_pangram_bitfield ... bench: 76 ns/iter (+/- 12) +test is_pangram_all_contains ... bench: 850 ns/iter (+/- 525) +test is_pangram_hash_is_subset ... bench: 3,113 ns/iter (+/- 188) +test is_pangram_hashset_len ... bench: 1,737 ns/iter (+/- 331) +test is_pangram_bitfield ... bench: 76 ns/iter (+/- 12) ``` diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md index 5c3bbde35..817c872d9 100644 --- a/exercises/practice/pangram/.docs/instructions.md +++ b/exercises/practice/pangram/.docs/instructions.md @@ -1,9 +1,8 @@ # Instructions -Determine if a sentence is a pangram. A pangram (Greek: παν γράμμα, pan gramma, -"every letter") is a sentence using every letter of the alphabet at least once. -The best known English pangram is: -> The quick brown fox jumps over the lazy dog. +Your task is to figure out if a sentence is a pangram. -The alphabet used consists of ASCII letters `a` to `z`, inclusive, and is case -insensitive. Any characters which are not an ASCII letter should be ignored. +A pangram is a sentence using every letter of the alphabet at least once. +It is case insensitive, so it doesn't matter if a letter is lower-case (e.g. `k`) or upper-case (e.g. `K`). + +For this exercise, a sentence is a pangram if it contains each of the 26 letters in the English alphabet. diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md new file mode 100644 index 000000000..32b6f1fc3 --- /dev/null +++ b/exercises/practice/pangram/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a company that sells fonts through their website. +They'd like to show a different sentence each time someone views a font on their website. +To give a comprehensive sense of the font, the random sentences should use **all** the letters in the English alphabet. + +They're running a competition to get suggestions for sentences that they can use. +You're in charge of checking the submissions to see if they are valid. + +~~~~exercism/note +Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". + +The best known English pangram is: + +> The quick brown fox jumps over the lazy dog. +~~~~ diff --git a/exercises/practice/pangram/.gitignore b/exercises/practice/pangram/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/pangram/.gitignore +++ b/exercises/practice/pangram/.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/pangram/.meta/test_template.tera b/exercises/practice/pangram/.meta/test_template.tera new file mode 100644 index 000000000..8d9eb5cd4 --- /dev/null +++ b/exercises/practice/pangram/.meta/test_template.tera @@ -0,0 +1,14 @@ +use pangram::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let sentence = {{ test.input.sentence | json_encode() }}; + {% if test.expected -%} + assert!(is_pangram(sentence)); + {% else -%} + assert!(!is_pangram(sentence)); + {% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/pangram/.meta/tests.toml b/exercises/practice/pangram/.meta/tests.toml index be690e975..10b5a335a 100644 --- a/exercises/practice/pangram/.meta/tests.toml +++ b/exercises/practice/pangram/.meta/tests.toml @@ -1,3 +1,45 @@ -# 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. + +[64f61791-508e-4f5c-83ab-05de042b0149] +description = "empty sentence" + +[74858f80-4a4d-478b-8a5e-c6477e4e4e84] +description = "perfect lower case" + +[61288860-35ca-4abe-ba08-f5df76ecbdcd] +description = "only lower case" + +[6564267d-8ac5-4d29-baf2-e7d2e304a743] +description = "missing the letter 'x'" + +[c79af1be-d715-4cdb-a5f2-b2fa3e7e0de0] +description = "missing the letter 'h'" + +[d835ec38-bc8f-48e4-9e36-eb232427b1df] +description = "with underscores" + +[8cc1e080-a178-4494-b4b3-06982c9be2a8] +description = "with numbers" + +[bed96b1c-ff95-45b8-9731-fdbdcb6ede9a] +description = "missing letters replaced by numbers" + +[938bd5d8-ade5-40e2-a2d9-55a338a01030] +description = "mixed case and punctuation" + +[2577bf54-83c8-402d-a64b-a2c0f7bb213a] +description = "case insensitive" +include = false + +[7138e389-83e4-4c6e-8413-1e40a0076951] +description = "a-m and A-M are 26 different characters but not a pangram" +reimplements = "2577bf54-83c8-402d-a64b-a2c0f7bb213a" diff --git a/exercises/practice/pangram/Cargo.toml b/exercises/practice/pangram/Cargo.toml index 1d64635aa..6a9e01b8f 100644 --- a/exercises/practice/pangram/Cargo.toml +++ b/exercises/practice/pangram/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "pangram" -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/pangram/src/lib.rs b/exercises/practice/pangram/src/lib.rs index f9d959d85..685588e2f 100644 --- a/exercises/practice/pangram/src/lib.rs +++ b/exercises/practice/pangram/src/lib.rs @@ -1,4 +1,4 @@ /// Determine whether a sentence is a pangram. pub fn is_pangram(sentence: &str) -> bool { - unimplemented!("Is {sentence} a pangram?"); + todo!("Is {sentence} a pangram?"); } diff --git a/exercises/practice/pangram/tests/pangram.rs b/exercises/practice/pangram/tests/pangram.rs index 27edf4cdf..2471315ef 100644 --- a/exercises/practice/pangram/tests/pangram.rs +++ b/exercises/practice/pangram/tests/pangram.rs @@ -1,70 +1,70 @@ use pangram::*; #[test] -fn empty_strings_are_not_pangrams() { +fn empty_sentence() { let sentence = ""; assert!(!is_pangram(sentence)); } #[test] #[ignore] -fn classic_pangram_is_a_pangram() { - let sentence = "the quick brown fox jumps over the lazy dog"; +fn perfect_lower_case() { + let sentence = "abcdefghijklmnopqrstuvwxyz"; assert!(is_pangram(sentence)); } #[test] #[ignore] -fn pangrams_must_have_all_letters() { - let sentence = "a quick movement of the enemy will jeopardize five gunboats"; - assert!(!is_pangram(sentence)); +fn only_lower_case() { + let sentence = "the quick brown fox jumps over the lazy dog"; + assert!(is_pangram(sentence)); } #[test] #[ignore] -fn pangrams_must_have_all_letters_two() { - let sentence = "the quick brown fish jumps over the lazy dog"; +fn missing_the_letter_x() { + let sentence = "a quick movement of the enemy will jeopardize five gunboats"; assert!(!is_pangram(sentence)); } #[test] #[ignore] -fn pangrams_must_include_z() { - let sentence = "the quick brown fox jumps over the lay dog"; +fn missing_the_letter_h() { + let sentence = "five boxing wizards jump quickly at it"; assert!(!is_pangram(sentence)); } #[test] #[ignore] -fn underscores_do_not_affect_pangrams() { +fn with_underscores() { let sentence = "the_quick_brown_fox_jumps_over_the_lazy_dog"; assert!(is_pangram(sentence)); } #[test] #[ignore] -fn numbers_do_not_affect_pangrams() { +fn with_numbers() { let sentence = "the 1 quick brown fox jumps over the 2 lazy dogs"; assert!(is_pangram(sentence)); } #[test] #[ignore] -fn numbers_can_not_replace_letters() { +fn missing_letters_replaced_by_numbers() { let sentence = "7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog"; assert!(!is_pangram(sentence)); } #[test] #[ignore] -fn capitals_and_punctuation_can_be_in_pangrams() { +fn mixed_case_and_punctuation() { let sentence = "\"Five quacking Zephyrs jolt my wax bed.\""; assert!(is_pangram(sentence)); } #[test] #[ignore] -fn non_ascii_characters_can_be_in_pangrams() { - let sentence = "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich."; - assert!(is_pangram(sentence)); +fn a_m_and_a_m_are_26_different_characters_but_not_a_pangram() { + let sentence = "abcdefghijklm ABCDEFGHIJKLM"; + assert!(!is_pangram(sentence)); } diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.append.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.append.md index b858dd6c3..309eaba05 100644 --- a/exercises/practice/parallel-letter-frequency/.docs/instructions.append.md +++ b/exercises/practice/parallel-letter-frequency/.docs/instructions.append.md @@ -1,4 +1,6 @@ -# Parallel Letter Frequency in Rust +# Instructions append + +## Parallel Letter Frequency in Rust Learn more about concurrency in Rust here: diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.md index a5b936c5e..6147b90af 100644 --- a/exercises/practice/parallel-letter-frequency/.docs/instructions.md +++ b/exercises/practice/parallel-letter-frequency/.docs/instructions.md @@ -2,7 +2,6 @@ Count the frequency of letters in texts using parallel computation. -Parallelism is about doing things in parallel that can also be done -sequentially. A common example is counting the frequency of letters. -Create a function that returns the total frequency of each letter in a -list of texts and that employs parallelism. +Parallelism is about doing things in parallel that can also be done sequentially. +A common example is counting the frequency of letters. +Employ parallelism to calculate the total frequency of each letter in a list of texts. diff --git a/exercises/practice/parallel-letter-frequency/.gitignore b/exercises/practice/parallel-letter-frequency/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/parallel-letter-frequency/.gitignore +++ b/exercises/practice/parallel-letter-frequency/.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/parallel-letter-frequency/.meta/config.json b/exercises/practice/parallel-letter-frequency/.meta/config.json index 8a69050b7..d94e74892 100644 --- a/exercises/practice/parallel-letter-frequency/.meta/config.json +++ b/exercises/practice/parallel-letter-frequency/.meta/config.json @@ -32,7 +32,7 @@ "Cargo.toml" ], "test": [ - "tests/parallel-letter-frequency.rs" + "tests/parallel_letter_frequency.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/parallel-letter-frequency/.meta/tests.toml b/exercises/practice/parallel-letter-frequency/.meta/tests.toml new file mode 100644 index 000000000..094b1dad2 --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/.meta/tests.toml @@ -0,0 +1,54 @@ +# 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. + +[c054d642-c1fa-4234-8007-9339f2337886] +description = "no texts" + +[818031be-49dc-4675-b2f9-c4047f638a2a] +description = "one text with one letter" + +[c0b81d1b-940d-4cea-9f49-8445c69c17ae] +description = "one text with multiple letters" + +[708ff1e0-f14a-43fd-adb5-e76750dcf108] +description = "two texts with one letter" + +[1b5c28bb-4619-4c9d-8db9-a4bb9c3bdca0] +description = "two texts with multiple letters" + +[6366e2b8-b84c-4334-a047-03a00a656d63] +description = "ignore letter casing" +rust_fn = "case_insensitivity" + +[92ebcbb0-9181-4421-a784-f6f5aa79f75b] +description = "ignore whitespace" + +[bc5f4203-00ce-4acc-a5fa-f7b865376fd9] +description = "ignore punctuation" +rust_fn = "punctuation_doesnt_count" + +[68032b8b-346b-4389-a380-e397618f6831] +description = "ignore numbers" +rust_fn = "numbers_dont_count" + +[aa9f97ac-3961-4af1-88e7-6efed1bfddfd] +description = "Unicode letters" + +[7b1da046-701b-41fc-813e-dcfb5ee51813] +description = "combination of lower- and uppercase letters, punctuation and white space" +rust_fn = "all_three_anthems_1_worker" + +[4727f020-df62-4dcf-99b2-a6e58319cb4f] +description = "large texts" + +[adf8e57b-8e54-4483-b6b8-8b32c115884c] +description = "many small texts" +rust_fn = "many_times_same_text" diff --git a/exercises/practice/parallel-letter-frequency/Cargo.toml b/exercises/practice/parallel-letter-frequency/Cargo.toml index 8caaa081b..9885e321c 100644 --- a/exercises/practice/parallel-letter-frequency/Cargo.toml +++ b/exercises/practice/parallel-letter-frequency/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "parallel-letter-frequency" -version = "0.0.0" +name = "parallel_letter_frequency" +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/parallel-letter-frequency/src/lib.rs b/exercises/practice/parallel-letter-frequency/src/lib.rs index 55933fa5a..43b349c9e 100644 --- a/exercises/practice/parallel-letter-frequency/src/lib.rs +++ b/exercises/practice/parallel-letter-frequency/src/lib.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; pub fn frequency(input: &[&str], worker_count: usize) -> HashMap { - unimplemented!( + todo!( "Count the frequency of letters in the given input '{input:?}'. Ensure that you are using {} to process the input.", match worker_count { 1 => "1 worker".to_string(), diff --git a/exercises/practice/parallel-letter-frequency/tests/parallel-letter-frequency.rs b/exercises/practice/parallel-letter-frequency/tests/parallel-letter-frequency.rs deleted file mode 100644 index 8e79feab6..000000000 --- a/exercises/practice/parallel-letter-frequency/tests/parallel-letter-frequency.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::collections::HashMap; - -use parallel_letter_frequency as frequency; - -// Poem by Friedrich Schiller. The corresponding music is the European Anthem. -const ODE_AN_DIE_FREUDE: [&str; 8] = [ - "Freude schöner Götterfunken", - "Tochter aus Elysium,", - "Wir betreten feuertrunken,", - "Himmlische, dein Heiligtum!", - "Deine Zauber binden wieder", - "Was die Mode streng geteilt;", - "Alle Menschen werden Brüder,", - "Wo dein sanfter Flügel weilt.", -]; - -// Dutch national anthem -const WILHELMUS: [&str; 8] = [ - "Wilhelmus van Nassouwe", - "ben ik, van Duitsen bloed,", - "den vaderland getrouwe", - "blijf ik tot in den dood.", - "Een Prinse van Oranje", - "ben ik, vrij, onverveerd,", - "den Koning van Hispanje", - "heb ik altijd geëerd.", -]; - -// American national anthem -const STAR_SPANGLED_BANNER: [&str; 8] = [ - "O say can you see by the dawn's early light,", - "What so proudly we hailed at the twilight's last gleaming,", - "Whose broad stripes and bright stars through the perilous fight,", - "O'er the ramparts we watched, were so gallantly streaming?", - "And the rockets' red glare, the bombs bursting in air,", - "Gave proof through the night that our flag was still there;", - "O say does that star-spangled banner yet wave,", - "O'er the land of the free and the home of the brave?", -]; - -#[test] -fn test_no_texts() { - assert_eq!(frequency::frequency(&[], 4), HashMap::new()); -} - -#[test] -#[ignore] -fn test_one_letter() { - let mut hm = HashMap::new(); - hm.insert('a', 1); - assert_eq!(frequency::frequency(&["a"], 4), hm); -} - -#[test] -#[ignore] -fn test_case_insensitivity() { - let mut hm = HashMap::new(); - hm.insert('a', 2); - assert_eq!(frequency::frequency(&["aA"], 4), hm); -} - -#[test] -#[ignore] -fn test_many_empty_lines() { - let v = vec![""; 1000]; - assert_eq!(frequency::frequency(&v[..], 4), HashMap::new()); -} - -#[test] -#[ignore] -fn test_many_times_same_text() { - let v = vec!["abc"; 1000]; - let mut hm = HashMap::new(); - hm.insert('a', 1000); - hm.insert('b', 1000); - hm.insert('c', 1000); - assert_eq!(frequency::frequency(&v[..], 4), hm); -} - -#[test] -#[ignore] -fn test_punctuation_doesnt_count() { - assert!(!frequency::frequency(&WILHELMUS, 4).contains_key(&',')); -} - -#[test] -#[ignore] -fn test_numbers_dont_count() { - assert!(!frequency::frequency(&["Testing, 1, 2, 3"], 4).contains_key(&'1')); -} - -#[test] -#[ignore] -fn test_all_three_anthems_1_worker() { - let mut v = Vec::new(); - for anthem in [ODE_AN_DIE_FREUDE, WILHELMUS, STAR_SPANGLED_BANNER].iter() { - for line in anthem.iter() { - v.push(*line); - } - } - let freqs = frequency::frequency(&v[..], 1); - assert_eq!(freqs.get(&'a'), Some(&49)); - assert_eq!(freqs.get(&'t'), Some(&56)); - assert_eq!(freqs.get(&'ü'), Some(&2)); -} - -#[test] -#[ignore] -fn test_all_three_anthems_3_workers() { - let mut v = Vec::new(); - for anthem in [ODE_AN_DIE_FREUDE, WILHELMUS, STAR_SPANGLED_BANNER].iter() { - for line in anthem.iter() { - v.push(*line); - } - } - let freqs = frequency::frequency(&v[..], 3); - assert_eq!(freqs.get(&'a'), Some(&49)); - assert_eq!(freqs.get(&'t'), Some(&56)); - assert_eq!(freqs.get(&'ü'), Some(&2)); -} - -#[test] -#[ignore] -fn test_non_integer_multiple_of_threads() { - let v = vec!["abc"; 999]; - let mut hm = HashMap::new(); - hm.insert('a', 999); - hm.insert('b', 999); - hm.insert('c', 999); - assert_eq!(frequency::frequency(&v[..], 4), hm); -} diff --git a/exercises/practice/parallel-letter-frequency/tests/parallel_letter_frequency.rs b/exercises/practice/parallel-letter-frequency/tests/parallel_letter_frequency.rs new file mode 100644 index 000000000..5ed2c1006 --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/tests/parallel_letter_frequency.rs @@ -0,0 +1,362 @@ +use std::collections::HashMap; + +use parallel_letter_frequency as frequency; + +#[test] +fn no_texts() { + assert_eq!(frequency::frequency(&[], 4), HashMap::new()); +} + +#[test] +#[ignore] +fn one_text_with_one_letter() { + let mut hm = HashMap::new(); + hm.insert('a', 1); + assert_eq!(frequency::frequency(&["a"], 4), hm); +} + +#[test] +#[ignore] +fn one_text_with_multiple_letters() { + let mut hm = HashMap::new(); + hm.insert('b', 2); + hm.insert('c', 3); + hm.insert('d', 1); + assert_eq!(frequency::frequency(&["bbcccd"], 4), hm); +} + +#[test] +#[ignore] +fn two_texts_with_one_letter() { + let mut hm = HashMap::new(); + hm.insert('e', 1); + hm.insert('f', 1); + assert_eq!(frequency::frequency(&["e", "f"], 4), hm); +} + +#[test] +#[ignore] +fn two_texts_with_multiple_letters() { + let mut hm = HashMap::new(); + hm.insert('g', 2); + hm.insert('h', 3); + hm.insert('i', 1); + assert_eq!(frequency::frequency(&["ggh", "hhi"], 4), hm); +} + +#[test] +#[ignore] +fn case_insensitivity() { + let mut hm = HashMap::new(); + hm.insert('a', 2); + assert_eq!(frequency::frequency(&["aA"], 4), hm); +} + +#[test] +#[ignore] +fn many_empty_lines() { + let v = vec![""; 1000]; + assert_eq!(frequency::frequency(&v[..], 4), HashMap::new()); +} + +#[test] +#[ignore] +fn ignore_whitespace() { + let v = [" ", "\t", "\r\n"]; + assert_eq!(frequency::frequency(&v[..], 4), HashMap::new()); +} + +#[test] +#[ignore] +fn many_times_same_text() { + let v = vec!["abc"; 1000]; + let mut hm = HashMap::new(); + hm.insert('a', 1000); + hm.insert('b', 1000); + hm.insert('c', 1000); + assert_eq!(frequency::frequency(&v[..], 4), hm); +} + +#[test] +#[ignore] +fn punctuation_doesnt_count() { + assert!(!frequency::frequency(&WILHELMUS, 4).contains_key(&',')); +} + +#[test] +#[ignore] +fn numbers_dont_count() { + assert!(!frequency::frequency(&["Testing, 1, 2, 3"], 4).contains_key(&'1')); +} + +#[test] +#[ignore] +fn unicode_letters() { + let mut hm = HashMap::new(); + hm.insert('本', 1); + hm.insert('φ', 1); + hm.insert('ほ', 1); + hm.insert('ø', 1); + let v = ["本", "φ", "ほ", "ø"]; + assert_eq!(frequency::frequency(&v, 4), hm); +} + +#[test] +#[ignore] +fn all_three_anthems_1_worker() { + let mut v = Vec::new(); + // These constants can be found under the last test if you wish to see them + for anthem in [ODE_AN_DIE_FREUDE, WILHELMUS, STAR_SPANGLED_BANNER].iter() { + for line in anthem.iter() { + v.push(*line); + } + } + let freqs = frequency::frequency(&v[..], 1); + assert_eq!(freqs.get(&'a'), Some(&49)); + assert_eq!(freqs.get(&'t'), Some(&56)); + assert_eq!(freqs.get(&'ü'), Some(&2)); +} + +#[test] +#[ignore] +fn all_three_anthems_3_workers() { + let mut v = Vec::new(); + for anthem in [ODE_AN_DIE_FREUDE, WILHELMUS, STAR_SPANGLED_BANNER].iter() { + for line in anthem.iter() { + v.push(*line); + } + } + let freqs = frequency::frequency(&v[..], 3); + assert_eq!(freqs.get(&'a'), Some(&49)); + assert_eq!(freqs.get(&'t'), Some(&56)); + assert_eq!(freqs.get(&'ü'), Some(&2)); +} + +#[test] +#[ignore] +fn non_integer_multiple_of_threads() { + let v = vec!["abc"; 999]; + let mut hm = HashMap::new(); + hm.insert('a', 999); + hm.insert('b', 999); + hm.insert('c', 999); + assert_eq!(frequency::frequency(&v[..], 4), hm); +} + +#[test] +#[ignore] +fn large_texts() { + let expected: HashMap = [ + ('a', 845), + ('b', 155), + ('c', 278), + ('d', 359), + ('e', 1143), + ('f', 222), + ('g', 187), + ('h', 507), + ('i', 791), + ('j', 12), + ('k', 67), + ('l', 423), + ('m', 288), + ('n', 833), + ('o', 791), + ('p', 197), + ('q', 8), + ('r', 432), + ('s', 700), + ('t', 1043), + ('u', 325), + ('v', 111), + ('w', 223), + ('x', 7), + ('y', 251), + ] + .into_iter() + .collect(); + + assert_eq!(frequency::frequency(&DOSTOEVSKY, 4), expected); +} + +// Poem by Friedrich Schiller. The corresponding music is the European Anthem. +const ODE_AN_DIE_FREUDE: [&str; 8] = [ + "Freude schöner Götterfunken", + "Tochter aus Elysium,", + "Wir betreten feuertrunken,", + "Himmlische, dein Heiligtum!", + "Deine Zauber binden wieder", + "Was die Mode streng geteilt;", + "Alle Menschen werden Brüder,", + "Wo dein sanfter Flügel weilt.", +]; + +// Dutch national anthem +const WILHELMUS: [&str; 8] = [ + "Wilhelmus van Nassouwe", + "ben ik, van Duitsen bloed,", + "den vaderland getrouwe", + "blijf ik tot in den dood.", + "Een Prinse van Oranje", + "ben ik, vrij, onverveerd,", + "den Koning van Hispanje", + "heb ik altijd geëerd.", +]; + +// American national anthem +const STAR_SPANGLED_BANNER: [&str; 8] = [ + "O say can you see by the dawn's early light,", + "What so proudly we hailed at the twilight's last gleaming,", + "Whose broad stripes and bright stars through the perilous fight,", + "O'er the ramparts we watched, were so gallantly streaming?", + "And the rockets' red glare, the bombs bursting in air,", + "Gave proof through the night that our flag was still there;", + "O say does that star-spangled banner yet wave,", + "O'er the land of the free and the home of the brave?", +]; + +const DOSTOEVSKY: [&str; 4] = [ + r#" + I am a sick man.... I am a spiteful man. I am an unattractive man. + I believe my liver is diseased. However, I know nothing at all about my disease, and do not + know for certain what ails me. I don't consult a doctor for it, + and never have, though I have a respect for medicine and doctors. + Besides, I am extremely superstitious, sufficiently so to respect medicine, + anyway (I am well-educated enough not to be superstitious, but I am superstitious). + No, I refuse to consult a doctor from spite. + That you probably will not understand. Well, I understand it, though. + Of course, I can't explain who it is precisely that I am mortifying in this case by my spite: + I am perfectly well aware that I cannot "pay out" the doctors by not consulting them; + I know better than anyone that by all this I am only injuring myself and no one else. + But still, if I don't consult a doctor it is from spite. + My liver is bad, well - let it get worse! + I have been going on like that for a long time - twenty years. Now I am forty. + I used to be in the government service, but am no longer. + I was a spiteful official. I was rude and took pleasure in being so. + I did not take bribes, you see, so I was bound to find a recompense in that, at least. + (A poor jest, but I will not scratch it out. I wrote it thinking it would sound very witty; + but now that I have seen myself that I only wanted to show off in a despicable way - + I will not scratch it out on purpose!) When petitioners used to come for + information to the table at which I sat, I used to grind my teeth at them, + and felt intense enjoyment when I succeeded in making anybody unhappy. + I almost did succeed. For the most part they were all timid people - of course, + they were petitioners. But of the uppish ones there was one officer in particular + I could not endure. He simply would not be humble, and clanked his sword in a disgusting way. + I carried on a feud with him for eighteen months over that sword. At last I got the better of him. + He left off clanking it. That happened in my youth, though. But do you know, + gentlemen, what was the chief point about my spite? Why, the whole point, + the real sting of it lay in the fact that continually, even in the moment of the acutest spleen, + I was inwardly conscious with shame that I was not only not a spiteful but not even an embittered man, + that I was simply scaring sparrows at random and amusing myself by it. + I might foam at the mouth, but bring me a doll to play with, give me a cup of tea with sugar in it, + and maybe I should be appeased. I might even be genuinely touched, + though probably I should grind my teeth at myself afterwards and lie awake at night with shame for + months after. That was my way. I was lying when I said just now that I was a spiteful official. + I was lying from spite. I was simply amusing myself with the petitioners and with the officer, + and in reality I never could become spiteful. I was conscious every moment in myself of many, + very many elements absolutely opposite to that. I felt them positively swarming in me, + these opposite elements. I knew that they had been swarming in me all my life and craving some outlet from me, + but I would not let them, would not let them, purposely would not let them come out. + They tormented me till I was ashamed: they drove me to convulsions and - sickened me, at last, + how they sickened me! + "#, + r#" + Gentlemen, I am joking, and I know myself that my jokes are not brilliant, + but you know one can take everything as a joke. I am, perhaps, jesting against the grain. + Gentlemen, I am tormented by questions; answer them for me. You, for instance, want to cure men of their + old habits and reform their will in accordance with science and good sense. + But how do you know, not only that it is possible, but also that it is + desirable to reform man in that way? And what leads you to the conclusion that man's + inclinations need reforming? In short, how do you know that such a reformation will be a benefit to man? + And to go to the root of the matter, why are you so positively convinced that not to act against + his real normal interests guaranteed by the conclusions of reason and arithmetic is certainly always + advantageous for man and must always be a law for mankind? So far, you know, + this is only your supposition. It may be the law of logic, but not the law of humanity. + You think, gentlemen, perhaps that I am mad? Allow me to defend myself. I agree that man + is pre-eminently a creative animal, predestined to strive consciously for an object and to engage in engineering - + that is, incessantly and eternally to make new roads, wherever + they may lead. But the reason why he wants sometimes to go off at a tangent may just be that he is + predestined to make the road, and perhaps, too, that however stupid the "direct" + practical man may be, the thought sometimes will occur to him that the road almost always does lead + somewhere, and that the destination it leads to is less important than the process + of making it, and that the chief thing is to save the well-conducted child from despising engineering, + and so giving way to the fatal idleness, which, as we all know, + is the mother of all the vices. Man likes to make roads and to create, that is a fact beyond dispute. + But why has he such a passionate love for destruction and chaos also? + Tell me that! But on that point I want to say a couple of words myself. May it not be that he loves + chaos and destruction (there can be no disputing that he does sometimes love it) + because he is instinctively afraid of attaining his object and completing the edifice he is constructing? + Who knows, perhaps he only loves that edifice from a distance, and is by no means + in love with it at close quarters; perhaps he only loves building it and does not want to live in it, + but will leave it, when completed, for the use of les animaux domestiques - + such as the ants, the sheep, and so on. Now the ants have quite a different taste. + They have a marvellous edifice of that pattern which endures for ever - the ant-heap. + With the ant-heap the respectable race of ants began and with the ant-heap they will probably end, + which does the greatest credit to their perseverance and good sense. But man is a frivolous and + incongruous creature, and perhaps, like a chess player, loves the process of the game, not the end of it. + And who knows (there is no saying with certainty), perhaps the only goal on earth + to which mankind is striving lies in this incessant process of attaining, in other words, + in life itself, and not in the thing to be attained, which must always be expressed as a formula, + as positive as twice two makes four, and such positiveness is not life, gentlemen, + but is the beginning of death. + "#, + r#" + But these are all golden dreams. Oh, tell me, who was it first announced, + who was it first proclaimed, that man only does nasty things because he does not know his own interests; + and that if he were enlightened, if his eyes were opened to his real normal interests, + man would at once cease to do nasty things, would at once become good and noble because, + being enlightened and understanding his real advantage, he would see his own advantage in the + good and nothing else, and we all know that not one man can, consciously, act against his own interests, + consequently, so to say, through necessity, he would begin doing good? Oh, the babe! Oh, the pure, + innocent child! Why, in the first place, when in all these thousands of years has there been a time + when man has acted only from his own interest? What is to be done with the millions of facts that bear + witness that men, consciously, that is fully understanding their real interests, have left them in the + background and have rushed headlong on another path, to meet peril and danger, + compelled to this course by nobody and by nothing, but, as it were, simply disliking the beaten track, + and have obstinately, wilfully, struck out another difficult, absurd way, seeking it almost in the darkness. + So, I suppose, this obstinacy and perversity were pleasanter to them than any advantage.... + Advantage! What is advantage? And will you take it upon yourself to define with perfect accuracy in what the + advantage of man consists? And what if it so happens that a man's advantage, sometimes, not only may, + but even must, consist in his desiring in certain cases what is harmful to himself and not advantageous. + And if so, if there can be such a case, the whole principle falls into dust. What do you think - + are there such cases? You laugh; laugh away, gentlemen, but only answer me: have man's advantages been + reckoned up with perfect certainty? Are there not some which not only have not been included but cannot + possibly be included under any classification? You see, you gentlemen have, to the best of my knowledge, + taken your whole register of human advantages from the averages of statistical figures and + politico-economical formulas. Your advantages are prosperity, wealth, freedom, peace - and so on, and so on. + So that the man who should, for instance, go openly and knowingly in opposition to all that list would to your thinking, + and indeed mine, too, of course, be an obscurantist or an absolute madman: would not he? But, you know, this is + what is surprising: why does it so happen that all these statisticians, sages and lovers of humanity, + when they reckon up human advantages invariably leave out one? They don't even take it into their reckoning + in the form in which it should be taken, and the whole reckoning depends upon that. It would be no greater matter, + they would simply have to take it, this advantage, and add it to the list. But the trouble is, that this strange + advantage does not fall under any classification and is not in place in any list. I have a friend for instance ... + Ech! gentlemen, but of course he is your friend, too; and indeed there is no one, no one to whom he is not a friend! + "#, + r#" + Yes, but here I come to a stop! Gentlemen, you must excuse me for being over-philosophical; + it's the result of forty years underground! Allow me to indulge my fancy. You see, gentlemen, reason is an excellent thing, + there's no disputing that, but reason is nothing but reason and satisfies only the rational side of man's nature, + while will is a manifestation of the whole life, that is, of the whole human life including reason and all the impulses. + And although our life, in this manifestation of it, is often worthless, yet it is life and not simply extracting square roots. + Here I, for instance, quite naturally want to live, in order to satisfy all my capacities for life, and not simply my capacity + for reasoning, that is, not simply one twentieth of my capacity for life. What does reason know? Reason only knows what it has + succeeded in learning (some things, perhaps, it will never learn; this is a poor comfort, but why not say so frankly?) + and human nature acts as a whole, with everything that is in it, consciously or unconsciously, and, even it if goes wrong, it lives. + I suspect, gentlemen, that you are looking at me with compassion; you tell me again that an enlightened and developed man, + such, in short, as the future man will be, cannot consciously desire anything disadvantageous to himself, that that can be proved mathematically. + I thoroughly agree, it can - by mathematics. But I repeat for the hundredth time, there is one case, one only, when man may consciously, purposely, + desire what is injurious to himself, what is stupid, very stupid - simply in order to have the right to desire for himself even what is very stupid + and not to be bound by an obligation to desire only what is sensible. Of course, this very stupid thing, this caprice of ours, may be in reality, + gentlemen, more advantageous for us than anything else on earth, especially in certain cases. And in particular it may be more advantageous than + any advantage even when it does us obvious harm, and contradicts the soundest conclusions of our reason concerning our advantage - + for in any circumstances it preserves for us what is most precious and most important - that is, our personality, our individuality. + Some, you see, maintain that this really is the most precious thing for mankind; choice can, of course, if it chooses, be in agreement + with reason; and especially if this be not abused but kept within bounds. It is profitable and some- times even praiseworthy. + But very often, and even most often, choice is utterly and stubbornly opposed to reason ... and ... and ... do you know that that, + too, is profitable, sometimes even praiseworthy? Gentlemen, let us suppose that man is not stupid. (Indeed one cannot refuse to suppose that, + if only from the one consideration, that, if man is stupid, then who is wise?) But if he is not stupid, he is monstrously ungrateful! + Phenomenally ungrateful. In fact, I believe that the best definition of man is the ungrateful biped. But that is not all, that is not his worst defect; + his worst defect is his perpetual moral obliquity, perpetual - from the days of the Flood to the Schleswig-Holstein period. + "#, +]; diff --git a/exercises/practice/pascals-triangle/.docs/instructions.md b/exercises/practice/pascals-triangle/.docs/instructions.md index 7109334fb..0f58f0069 100644 --- a/exercises/practice/pascals-triangle/.docs/instructions.md +++ b/exercises/practice/pascals-triangle/.docs/instructions.md @@ -1,9 +1,20 @@ # Instructions -Compute Pascal's triangle up to a given number of rows. +Your task is to output the first N rows of Pascal's triangle. -In Pascal's Triangle each number is computed by adding the numbers to -the right and left of the current position in the previous row. +[Pascal's triangle][wikipedia] is a triangular array of positive integers. + +In Pascal's triangle, the number of values in a row is equal to its row number (which starts at one). +Therefore, the first row has one value, the second row has two values, and so on. + +The first (topmost) row has a single value: `1`. +Subsequent rows' values are computed by adding the numbers directly to the right and left of the current position in the previous row. + +If the previous row does _not_ have a value to the left or right of the current position (which only happens for the leftmost and rightmost positions), treat that position's value as zero (effectively "ignoring" it in the summation). + +## Example + +Let's look at the first 5 rows of Pascal's Triangle: ```text 1 @@ -11,5 +22,14 @@ the right and left of the current position in the previous row. 1 2 1 1 3 3 1 1 4 6 4 1 -# ... etc ``` + +The topmost row has one value, which is `1`. + +The leftmost and rightmost values have only one preceding position to consider, which is the position to its right respectively to its left. +With the topmost value being `1`, it follows from this that all the leftmost and rightmost values are also `1`. + +The other values all have two positions to consider. +For example, the fifth row's (`1 4 6 4 1`) middle value is `6`, as the values to its left and right in the preceding row are `3` and `3`: + +[wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle diff --git a/exercises/practice/pascals-triangle/.docs/introduction.md b/exercises/practice/pascals-triangle/.docs/introduction.md new file mode 100644 index 000000000..eab454e5a --- /dev/null +++ b/exercises/practice/pascals-triangle/.docs/introduction.md @@ -0,0 +1,22 @@ +# Introduction + +With the weather being great, you're not looking forward to spending an hour in a classroom. +Annoyed, you enter the class room, where you notice a strangely satisfying triangle shape on the blackboard. +Whilst waiting for your math teacher to arrive, you can't help but notice some patterns in the triangle: the outer values are all ones, each subsequent row has one more value than its previous row and the triangle is symmetrical. +Weird! + +Not long after you sit down, your teacher enters the room and explains that this triangle is the famous [Pascal's triangle][wikipedia]. + +Over the next hour, your teacher reveals some amazing things hidden in this triangle: + +- It can be used to compute how many ways you can pick K elements from N values. +- It contains the Fibonacci sequence. +- If you color odd and even numbers differently, you get a beautiful pattern called the [Sierpiński triangle][wikipedia-sierpinski-triangle]. + +The teacher implores you and your classmates to look up other uses, and assures you that there are lots more! +At that moment, the school bell rings. +You realize that for the past hour, you were completely absorbed in learning about Pascal's triangle. +You quickly grab your laptop from your bag and go outside, ready to enjoy both the sunshine _and_ the wonders of Pascal's triangle. + +[wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle +[wikipedia-sierpinski-triangle]: https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle diff --git a/exercises/practice/pascals-triangle/.gitignore b/exercises/practice/pascals-triangle/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/pascals-triangle/.gitignore +++ b/exercises/practice/pascals-triangle/.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/pascals-triangle/.meta/config.json b/exercises/practice/pascals-triangle/.meta/config.json index f8d75e1de..2b272e4e7 100644 --- a/exercises/practice/pascals-triangle/.meta/config.json +++ b/exercises/practice/pascals-triangle/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/pascals-triangle.rs" + "tests/pascals_triangle.rs" ], "example": [ ".meta/example.rs" @@ -31,5 +31,5 @@ }, "blurb": "Compute Pascal's triangle up to a given number of rows.", "source": "Pascal's Triangle at Wolfram Math World", - "source_url": "http://mathworld.wolfram.com/PascalsTriangle.html" + "source_url": "https://www.wolframalpha.com/input/?i=Pascal%27s+triangle" } diff --git a/exercises/practice/pascals-triangle/.meta/test_template.tera b/exercises/practice/pascals-triangle/.meta/test_template.tera new file mode 100644 index 000000000..60e67468c --- /dev/null +++ b/exercises/practice/pascals-triangle/.meta/test_template.tera @@ -0,0 +1,12 @@ +use pascals_triangle::PascalsTriangle; +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let pt = PascalsTriangle::new({{ test.input.count }}); + let expected: Vec> = vec![{% for row in test.expected -%} + vec!{{ row | json_encode() }}, + {%- endfor %}]; + assert_eq!(pt.rows(), expected); +} +{% endfor -%} diff --git a/exercises/practice/pascals-triangle/.meta/tests.toml b/exercises/practice/pascals-triangle/.meta/tests.toml index b0c8be28c..2db0ee523 100644 --- a/exercises/practice/pascals-triangle/.meta/tests.toml +++ b/exercises/practice/pascals-triangle/.meta/tests.toml @@ -1,6 +1,19 @@ -# 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. + +[9920ce55-9629-46d5-85d6-4201f4a4234d] +description = "zero rows" + +[70d643ce-a46d-4e93-af58-12d88dd01f21] +description = "single row" [a6e5a2a2-fc9a-4b47-9f4f-ed9ad9fbe4bd] description = "two rows" @@ -8,6 +21,9 @@ description = "two rows" [97206a99-79ba-4b04-b1c5-3c0fa1e16925] description = "three rows" +[565a0431-c797-417c-a2c8-2935e01ce306] +description = "four rows" + [06f9ea50-9f51-4eb2-b9a9-c00975686c27] description = "five rows" diff --git a/exercises/practice/pascals-triangle/Cargo.toml b/exercises/practice/pascals-triangle/Cargo.toml index 40e039fa8..c703118e0 100644 --- a/exercises/practice/pascals-triangle/Cargo.toml +++ b/exercises/practice/pascals-triangle/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "pascals-triangle" -version = "1.5.0" +name = "pascals_triangle" +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/pascals-triangle/src/lib.rs b/exercises/practice/pascals-triangle/src/lib.rs index d7b800e6c..7e7bf3bbf 100644 --- a/exercises/practice/pascals-triangle/src/lib.rs +++ b/exercises/practice/pascals-triangle/src/lib.rs @@ -2,10 +2,10 @@ pub struct PascalsTriangle; impl PascalsTriangle { pub fn new(row_count: u32) -> Self { - unimplemented!("create Pascal's triangle with {row_count} rows"); + todo!("create Pascal's triangle with {row_count} rows"); } pub fn rows(&self) -> Vec> { - unimplemented!(); + todo!(); } } diff --git a/exercises/practice/pascals-triangle/tests/pascals-triangle.rs b/exercises/practice/pascals-triangle/tests/pascals_triangle.rs similarity index 63% rename from exercises/practice/pascals-triangle/tests/pascals-triangle.rs rename to exercises/practice/pascals-triangle/tests/pascals_triangle.rs index fe2042bdc..24ba514fe 100644 --- a/exercises/practice/pascals-triangle/tests/pascals-triangle.rs +++ b/exercises/practice/pascals-triangle/tests/pascals_triangle.rs @@ -1,18 +1,18 @@ -use pascals_triangle::*; +use pascals_triangle::PascalsTriangle; #[test] -fn no_rows() { +fn zero_rows() { let pt = PascalsTriangle::new(0); - let expected: Vec> = Vec::new(); - assert_eq!(expected, pt.rows()); + let expected: Vec> = vec![]; + assert_eq!(pt.rows(), expected); } #[test] #[ignore] -fn one_row() { +fn single_row() { let pt = PascalsTriangle::new(1); let expected: Vec> = vec![vec![1]]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } #[test] @@ -20,7 +20,7 @@ fn one_row() { fn two_rows() { let pt = PascalsTriangle::new(2); let expected: Vec> = vec![vec![1], vec![1, 1]]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } #[test] @@ -28,15 +28,15 @@ fn two_rows() { fn three_rows() { let pt = PascalsTriangle::new(3); let expected: Vec> = vec![vec![1], vec![1, 1], vec![1, 2, 1]]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } #[test] #[ignore] -fn last_of_four_rows() { +fn four_rows() { let pt = PascalsTriangle::new(4); - let expected: Vec = vec![1, 3, 3, 1]; - assert_eq!(Some(expected), pt.rows().pop()); + let expected: Vec> = vec![vec![1], vec![1, 1], vec![1, 2, 1], vec![1, 3, 3, 1]]; + assert_eq!(pt.rows(), expected); } #[test] @@ -50,7 +50,7 @@ fn five_rows() { vec![1, 3, 3, 1], vec![1, 4, 6, 4, 1], ]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } #[test] @@ -65,23 +65,7 @@ fn six_rows() { vec![1, 4, 6, 4, 1], vec![1, 5, 10, 10, 5, 1], ]; - assert_eq!(expected, pt.rows()); -} - -#[test] -#[ignore] -fn seven_rows() { - let pt = PascalsTriangle::new(7); - let expected: Vec> = vec![ - vec![1], - vec![1, 1], - vec![1, 2, 1], - vec![1, 3, 3, 1], - vec![1, 4, 6, 4, 1], - vec![1, 5, 10, 10, 5, 1], - vec![1, 6, 15, 20, 15, 6, 1], - ]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } #[test] @@ -100,5 +84,5 @@ fn ten_rows() { vec![1, 8, 28, 56, 70, 56, 28, 8, 1], vec![1, 9, 36, 84, 126, 126, 84, 36, 9, 1], ]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } diff --git a/exercises/practice/perfect-numbers/.docs/instructions.md b/exercises/practice/perfect-numbers/.docs/instructions.md index 144c9133e..b2bc82ca3 100644 --- a/exercises/practice/perfect-numbers/.docs/instructions.md +++ b/exercises/practice/perfect-numbers/.docs/instructions.md @@ -1,18 +1,39 @@ # Instructions -Determine if a number is perfect, abundant, or deficient based on -Nicomachus' (60 - 120 CE) classification scheme for positive integers. - -The Greek mathematician [Nicomachus](https://en.wikipedia.org/wiki/Nicomachus) devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of **perfect**, **abundant**, or **deficient** based on their [aliquot sum](https://en.wikipedia.org/wiki/Aliquot_sum). The aliquot sum is defined as the sum of the factors of a number not including the number itself. For example, the aliquot sum of 15 is (1 + 3 + 5) = 9 - -- **Perfect**: aliquot sum = number - - 6 is a perfect number because (1 + 2 + 3) = 6 - - 28 is a perfect number because (1 + 2 + 4 + 7 + 14) = 28 -- **Abundant**: aliquot sum > number - - 12 is an abundant number because (1 + 2 + 3 + 4 + 6) = 16 - - 24 is an abundant number because (1 + 2 + 3 + 4 + 6 + 8 + 12) = 36 -- **Deficient**: aliquot sum < number - - 8 is a deficient number because (1 + 2 + 4) = 7 - - Prime numbers are deficient - -Implement a way to determine whether a given number is **perfect**. Depending on your language track, you may also need to implement a way to determine whether a given number is **abundant** or **deficient**. +Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers. + +The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of [perfect](#perfect), [abundant](#abundant), or [deficient](#deficient) based on their [aliquot sum][aliquot-sum]. +The _aliquot sum_ is defined as the sum of the factors of a number not including the number itself. +For example, the aliquot sum of `15` is `1 + 3 + 5 = 9`. + +## Perfect + +A number is perfect when it equals its aliquot sum. +For example: + +- `6` is a perfect number because `1 + 2 + 3 = 6` +- `28` is a perfect number because `1 + 2 + 4 + 7 + 14 = 28` + +## Abundant + +A number is abundant when it is less than its aliquot sum. +For example: + +- `12` is an abundant number because `1 + 2 + 3 + 4 + 6 = 16` +- `24` is an abundant number because `1 + 2 + 3 + 4 + 6 + 8 + 12 = 36` + +## Deficient + +A number is deficient when it is greater than its aliquot sum. +For example: + +- `8` is a deficient number because `1 + 2 + 4 = 7` +- Prime numbers are deficient + +## Task + +Implement a way to determine whether a given number is [perfect](#perfect). +Depending on your language track, you may also need to implement a way to determine whether a given number is [abundant](#abundant) or [deficient](#deficient). + +[nicomachus]: https://en.wikipedia.org/wiki/Nicomachus +[aliquot-sum]: https://en.wikipedia.org/wiki/Aliquot_sum diff --git a/exercises/practice/perfect-numbers/.gitignore b/exercises/practice/perfect-numbers/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/perfect-numbers/.gitignore +++ b/exercises/practice/perfect-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/perfect-numbers/.meta/config.json b/exercises/practice/perfect-numbers/.meta/config.json index 50d15d8d8..c6db2e942 100644 --- a/exercises/practice/perfect-numbers/.meta/config.json +++ b/exercises/practice/perfect-numbers/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/perfect-numbers.rs" + "tests/perfect_numbers.rs" ], "example": [ ".meta/example.rs" @@ -32,8 +32,5 @@ }, "blurb": "Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers.", "source": "Taken from Chapter 2 of Functional Thinking by Neal Ford.", - "source_url": "http://shop.oreilly.com/product/0636920029687.do", - "custom": { - "ignore-count-ignores": true - } + "source_url": "https://www.oreilly.com/library/view/functional-thinking/9781449365509/" } diff --git a/exercises/practice/perfect-numbers/.meta/example.rs b/exercises/practice/perfect-numbers/.meta/example.rs index 7fc827309..a2c6b3b3c 100644 --- a/exercises/practice/perfect-numbers/.meta/example.rs +++ b/exercises/practice/perfect-numbers/.meta/example.rs @@ -2,7 +2,7 @@ pub fn classify(num: u64) -> Option { if num == 0 { return None; } - let sum: u64 = (1..num).filter(|i| num % i == 0).sum(); + let sum: u64 = (1..num).filter(|i| num.is_multiple_of(*i)).sum(); Some(match sum.cmp(&num) { std::cmp::Ordering::Equal => Classification::Perfect, std::cmp::Ordering::Less => Classification::Deficient, diff --git a/exercises/practice/perfect-numbers/.meta/test_template.tera b/exercises/practice/perfect-numbers/.meta/test_template.tera new file mode 100644 index 000000000..82be1fa7b --- /dev/null +++ b/exercises/practice/perfect-numbers/.meta/test_template.tera @@ -0,0 +1,18 @@ +use perfect_numbers::*; + +{% for test_group in cases %} +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.number | fmt_num }}; + let output = classify(input); +{%- if test.expected is object %} + assert!(output.is_none()); +{% else %} + let expected = Some(Classification::{{ test.expected | title }}); + assert_eq!(output, expected); +{% endif -%} +} +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/perfect-numbers/.meta/tests.toml b/exercises/practice/perfect-numbers/.meta/tests.toml index be690e975..90976e329 100644 --- a/exercises/practice/perfect-numbers/.meta/tests.toml +++ b/exercises/practice/perfect-numbers/.meta/tests.toml @@ -1,3 +1,51 @@ -# 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. + +[163e8e86-7bfd-4ee2-bd68-d083dc3381a3] +description = "Perfect numbers -> Smallest perfect number is classified correctly" + +[169a7854-0431-4ae0-9815-c3b6d967436d] +description = "Perfect numbers -> Medium perfect number is classified correctly" + +[ee3627c4-7b36-4245-ba7c-8727d585f402] +description = "Perfect numbers -> Large perfect number is classified correctly" + +[80ef7cf8-9ea8-49b9-8b2d-d9cb3db3ed7e] +description = "Abundant numbers -> Smallest abundant number is classified correctly" + +[3e300e0d-1a12-4f11-8c48-d1027165ab60] +description = "Abundant numbers -> Medium abundant number is classified correctly" + +[ec7792e6-8786-449c-b005-ce6dd89a772b] +description = "Abundant numbers -> Large abundant number is classified correctly" + +[e610fdc7-2b6e-43c3-a51c-b70fb37413ba] +description = "Deficient numbers -> Smallest prime deficient number is classified correctly" + +[0beb7f66-753a-443f-8075-ad7fbd9018f3] +description = "Deficient numbers -> Smallest non-prime deficient number is classified correctly" + +[1c802e45-b4c6-4962-93d7-1cad245821ef] +description = "Deficient numbers -> Medium deficient number is classified correctly" + +[47dd569f-9e5a-4a11-9a47-a4e91c8c28aa] +description = "Deficient numbers -> Large deficient number is classified correctly" + +[a696dec8-6147-4d68-afad-d38de5476a56] +description = "Deficient numbers -> Edge case (no factors other than itself) is classified correctly" + +[72445cee-660c-4d75-8506-6c40089dc302] +description = "Invalid inputs -> Zero is rejected (as it is not a positive integer)" + +[2d72ce2c-6802-49ac-8ece-c790ba3dae13] +description = "Invalid inputs -> Negative integer is rejected (as it is not a positive integer)" +include = false +comment = "the input type is u64, which prevents negative numbers at compile time" diff --git a/exercises/practice/perfect-numbers/Cargo.toml b/exercises/practice/perfect-numbers/Cargo.toml index d24b1e132..a096fd0dd 100644 --- a/exercises/practice/perfect-numbers/Cargo.toml +++ b/exercises/practice/perfect-numbers/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "perfect_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/perfect-numbers/src/lib.rs b/exercises/practice/perfect-numbers/src/lib.rs index 3448be300..b2257e1b0 100644 --- a/exercises/practice/perfect-numbers/src/lib.rs +++ b/exercises/practice/perfect-numbers/src/lib.rs @@ -6,5 +6,5 @@ pub enum Classification { } pub fn classify(num: u64) -> Option { - unimplemented!("classify {num}"); + todo!("classify {num}"); } diff --git a/exercises/practice/perfect-numbers/tests/perfect-numbers.rs b/exercises/practice/perfect-numbers/tests/perfect-numbers.rs deleted file mode 100644 index 3c6590a08..000000000 --- a/exercises/practice/perfect-numbers/tests/perfect-numbers.rs +++ /dev/null @@ -1,40 +0,0 @@ -use perfect_numbers::{classify, Classification}; - -macro_rules! tests { - ($property_test_func:ident { - $( $(#[$attr:meta])* $test_name:ident( $( $param:expr ),* ); )+ - }) => { - $( - $(#[$attr])* - #[test] - fn $test_name() { - $property_test_func($( $param ),* ) - } - )+ - } -} - -fn test_classification(num: u64, result: Classification) { - assert_eq!(classify(num), Some(result)); -} - -#[test] -fn basic() { - assert_eq!(classify(0), None); -} - -tests! { - test_classification { - #[ignore] test_1(1, Classification::Deficient); - #[ignore] test_2(2, Classification::Deficient); - #[ignore] test_4(4, Classification::Deficient); - #[ignore] test_6(6, Classification::Perfect); - #[ignore] test_12(12, Classification::Abundant); - #[ignore] test_28(28, Classification::Perfect); - #[ignore] test_30(30, Classification::Abundant); - #[ignore] test_32(32, Classification::Deficient); - #[ignore] test_33550335(33_550_335, Classification::Abundant); - #[ignore] test_33550336(33_550_336, Classification::Perfect); - #[ignore] test_33550337(33_550_337, Classification::Deficient); - } -} diff --git a/exercises/practice/perfect-numbers/tests/perfect_numbers.rs b/exercises/practice/perfect-numbers/tests/perfect_numbers.rs new file mode 100644 index 000000000..582862278 --- /dev/null +++ b/exercises/practice/perfect-numbers/tests/perfect_numbers.rs @@ -0,0 +1,107 @@ +use perfect_numbers::*; + +#[test] +fn smallest_perfect_number_is_classified_correctly() { + let input = 6; + let output = classify(input); + let expected = Some(Classification::Perfect); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn medium_perfect_number_is_classified_correctly() { + let input = 28; + let output = classify(input); + let expected = Some(Classification::Perfect); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn large_perfect_number_is_classified_correctly() { + let input = 33_550_336; + let output = classify(input); + let expected = Some(Classification::Perfect); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_abundant_number_is_classified_correctly() { + let input = 12; + let output = classify(input); + let expected = Some(Classification::Abundant); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn medium_abundant_number_is_classified_correctly() { + let input = 30; + let output = classify(input); + let expected = Some(Classification::Abundant); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn large_abundant_number_is_classified_correctly() { + let input = 33_550_335; + let output = classify(input); + let expected = Some(Classification::Abundant); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_prime_deficient_number_is_classified_correctly() { + let input = 2; + let output = classify(input); + let expected = Some(Classification::Deficient); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_non_prime_deficient_number_is_classified_correctly() { + let input = 4; + let output = classify(input); + let expected = Some(Classification::Deficient); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn medium_deficient_number_is_classified_correctly() { + let input = 32; + let output = classify(input); + let expected = Some(Classification::Deficient); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn large_deficient_number_is_classified_correctly() { + let input = 33_550_337; + let output = classify(input); + let expected = Some(Classification::Deficient); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn edge_case_no_factors_other_than_itself_is_classified_correctly() { + let input = 1; + let output = classify(input); + let expected = Some(Classification::Deficient); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn zero_is_rejected_as_it_is_not_a_positive_integer() { + let input = 0; + let output = classify(input); + assert!(output.is_none()); +} diff --git a/exercises/practice/phone-number/.docs/instructions.md b/exercises/practice/phone-number/.docs/instructions.md index 6e36daefe..5d4d3739f 100644 --- a/exercises/practice/phone-number/.docs/instructions.md +++ b/exercises/practice/phone-number/.docs/instructions.md @@ -1,22 +1,27 @@ # Instructions -Clean up user-entered phone numbers so that they can be sent SMS messages. +Clean up phone numbers so that they can be sent SMS messages. -The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: `1`. +The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. +All NANP-countries share the same international country code: `1`. -NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as *area code*, followed by a seven-digit local number. The first three digits of the local number represent the *exchange code*, followed by the unique four-digit number which is the *subscriber number*. +NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as _area code_, followed by a seven-digit local number. +The first three digits of the local number represent the _exchange code_, followed by the unique four-digit number which is the _subscriber number_. The format is usually represented as ```text -(NXX)-NXX-XXXX +NXX NXX-XXXX ``` where `N` is any digit from 2 through 9 and `X` is any digit from 0 through 9. -Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code (1) if present. +Sometimes they also have the country code (represented as `1` or `+1`) prefixed. + +Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code if present. For example, the inputs + - `+1 (613)-995-0253` - `613-995-0253` - `1 613 995 0253` diff --git a/exercises/practice/phone-number/.docs/introduction.md b/exercises/practice/phone-number/.docs/introduction.md new file mode 100644 index 000000000..c4142c5af --- /dev/null +++ b/exercises/practice/phone-number/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +You've joined LinkLine, a leading communications company working to ensure reliable connections for everyone. +The team faces a big challenge: users submit phone numbers in all sorts of formats — dashes, spaces, dots, parentheses, and even prefixes. +Some numbers are valid, while others are impossible to use. + +Your mission is to turn this chaos into order. +You'll clean up valid numbers, formatting them appropriately for use in the system. +At the same time, you'll identify and filter out any invalid entries. + +The success of LinkLine's operations depends on your ability to separate the useful from the unusable. +Are you ready to take on the challenge and keep the connections running smoothly? diff --git a/exercises/practice/phone-number/.gitignore b/exercises/practice/phone-number/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/phone-number/.gitignore +++ b/exercises/practice/phone-number/.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/phone-number/.meta/config.json b/exercises/practice/phone-number/.meta/config.json index e85aab65b..8bea16007 100644 --- a/exercises/practice/phone-number/.meta/config.json +++ b/exercises/practice/phone-number/.meta/config.json @@ -33,13 +33,13 @@ "Cargo.toml" ], "test": [ - "tests/phone-number.rs" + "tests/phone_number.rs" ], "example": [ ".meta/example.rs" ] }, "blurb": "Clean up user-entered phone numbers so that they can be sent SMS messages.", - "source": "Event Manager by JumpstartLab", - "source_url": "http://tutorials.jumpstartlab.com/projects/eventmanager.html" + "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/phone-number/.meta/example.rs b/exercises/practice/phone-number/.meta/example.rs index 88896c988..b8abacfb9 100644 --- a/exercises/practice/phone-number/.meta/example.rs +++ b/exercises/practice/phone-number/.meta/example.rs @@ -1,5 +1,8 @@ pub fn number(user_number: &str) -> Option { - let mut filtered_number: String = user_number.chars().filter(|ch| ch.is_digit(10)).collect(); + let mut filtered_number: String = user_number + .chars() + .filter(|ch| ch.is_ascii_digit()) + .collect(); let number_len = filtered_number.len(); diff --git a/exercises/practice/phone-number/.meta/test_template.tera b/exercises/practice/phone-number/.meta/test_template.tera new file mode 100644 index 000000000..3f50cc8f6 --- /dev/null +++ b/exercises/practice/phone-number/.meta/test_template.tera @@ -0,0 +1,16 @@ +use phone_number::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.phrase | json_encode() }}; + let output = number(input); +{%- if test.expected is object %} + assert!(output.is_none()); +{% else %} + let expected = Some({{ test.expected | json_encode() }}.into()); + assert_eq!(output, expected); +{% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/phone-number/.meta/tests.toml b/exercises/practice/phone-number/.meta/tests.toml index be690e975..24dbf07a7 100644 --- a/exercises/practice/phone-number/.meta/tests.toml +++ b/exercises/practice/phone-number/.meta/tests.toml @@ -1,3 +1,84 @@ -# 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. + +[79666dce-e0f1-46de-95a1-563802913c35] +description = "cleans the number" + +[c360451f-549f-43e4-8aba-fdf6cb0bf83f] +description = "cleans numbers with dots" + +[08f94c34-9a37-46a2-a123-2a8e9727395d] +description = "cleans numbers with multiple spaces" + +[598d8432-0659-4019-a78b-1c6a73691d21] +description = "invalid when 9 digits" +include = false + +[2de74156-f646-42b5-8638-0ef1d8b58bc2] +description = "invalid when 9 digits" +reimplements = "598d8432-0659-4019-a78b-1c6a73691d21" + +[57061c72-07b5-431f-9766-d97da7c4399d] +description = "invalid when 11 digits does not start with a 1" + +[9962cbf3-97bb-4118-ba9b-38ff49c64430] +description = "valid when 11 digits and starting with 1" + +[fa724fbf-054c-4d91-95da-f65ab5b6dbca] +description = "valid when 11 digits and starting with 1 even with punctuation" + +[c6a5f007-895a-4fc5-90bc-a7e70f9b5cad] +description = "invalid when more than 11 digits" +include = false + +[4a1509b7-8953-4eec-981b-c483358ff531] +description = "invalid when more than 11 digits" +reimplements = "c6a5f007-895a-4fc5-90bc-a7e70f9b5cad" + +[63f38f37-53f6-4a5f-bd86-e9b404f10a60] +description = "invalid with letters" +include = false + +[eb8a1fc0-64e5-46d3-b0c6-33184208e28a] +description = "invalid with letters" +reimplements = "63f38f37-53f6-4a5f-bd86-e9b404f10a60" + +[4bd97d90-52fd-45d3-b0db-06ab95b1244e] +description = "invalid with punctuations" +include = false + +[065f6363-8394-4759-b080-e6c8c351dd1f] +description = "invalid with punctuations" +reimplements = "4bd97d90-52fd-45d3-b0db-06ab95b1244e" + +[d77d07f8-873c-4b17-8978-5f66139bf7d7] +description = "invalid if area code starts with 0" + +[c7485cfb-1e7b-4081-8e96-8cdb3b77f15e] +description = "invalid if area code starts with 1" + +[4d622293-6976-413d-b8bf-dd8a94d4e2ac] +description = "invalid if exchange code starts with 0" + +[4cef57b4-7d8e-43aa-8328-1e1b89001262] +description = "invalid if exchange code starts with 1" + +[9925b09c-1a0d-4960-a197-5d163cbe308c] +description = "invalid if area code starts with 0 on valid 11-digit number" + +[3f809d37-40f3-44b5-ad90-535838b1a816] +description = "invalid if area code starts with 1 on valid 11-digit number" + +[e08e5532-d621-40d4-b0cc-96c159276b65] +description = "invalid if exchange code starts with 0 on valid 11-digit number" + +[57b32f3d-696a-455c-8bf1-137b6d171cdf] +description = "invalid if exchange code starts with 1 on valid 11-digit number" diff --git a/exercises/practice/phone-number/Cargo.toml b/exercises/practice/phone-number/Cargo.toml index a8fb61d48..e7dd2918d 100644 --- a/exercises/practice/phone-number/Cargo.toml +++ b/exercises/practice/phone-number/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "phone-number" -version = "1.6.1" +name = "phone_number" +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/phone-number/src/lib.rs b/exercises/practice/phone-number/src/lib.rs index 59a1747c1..af53e11c8 100644 --- a/exercises/practice/phone-number/src/lib.rs +++ b/exercises/practice/phone-number/src/lib.rs @@ -1,5 +1,5 @@ pub fn number(user_number: &str) -> Option { - unimplemented!( + todo!( "Given the number entered by user '{user_number}', convert it into SMS-friendly format. If the entered number is not a valid NANP number, return None." ); } diff --git a/exercises/practice/phone-number/tests/phone-number.rs b/exercises/practice/phone-number/tests/phone-number.rs deleted file mode 100644 index 2bde594e2..000000000 --- a/exercises/practice/phone-number/tests/phone-number.rs +++ /dev/null @@ -1,112 +0,0 @@ -use phone_number as phone; - -fn process_clean_case(number: &str, expected: Option<&str>) { - assert_eq!(phone::number(number), expected.map(|x| x.to_string())); -} - -#[test] -fn test_cleans_the_number() { - process_clean_case("(223) 456-7890", Some("2234567890")); -} - -#[test] -#[ignore] -fn test_cleans_numbers_with_dots() { - process_clean_case("223.456.7890", Some("2234567890")); -} - -#[test] -#[ignore] -fn test_cleans_numbers_with_multiple_spaces() { - process_clean_case("223 456 7890 ", Some("2234567890")); -} - -#[test] -#[ignore] -fn test_invalid_when_9_digits() { - process_clean_case("123456789", None); -} - -#[test] -#[ignore] -fn test_invalid_when_11_digits_does_not_start_with_a_1() { - process_clean_case("22234567890", None); -} - -#[test] -#[ignore] -fn test_valid_when_11_digits_and_starting_with_1() { - process_clean_case("12234567890", Some("2234567890")); -} - -#[test] -#[ignore] -fn test_valid_when_11_digits_and_starting_with_1_even_with_punctuation() { - process_clean_case("+1 (223) 456-7890", Some("2234567890")); -} - -#[test] -#[ignore] -fn test_invalid_when_more_than_11_digits() { - process_clean_case("321234567890", None); -} - -#[test] -#[ignore] -fn test_invalid_with_letters() { - process_clean_case("123-abc-7890", None); -} - -#[test] -#[ignore] -fn test_invalid_with_punctuations() { - process_clean_case("123-@:!-7890", None); -} - -#[test] -#[ignore] -fn test_invalid_if_area_code_starts_with_1_on_valid_11digit_number() { - process_clean_case("1 (123) 456-7890", None); -} - -#[test] -#[ignore] -fn test_invalid_if_area_code_starts_with_0_on_valid_11digit_number() { - process_clean_case("1 (023) 456-7890", None); -} - -#[test] -#[ignore] -fn test_invalid_if_area_code_starts_with_1() { - process_clean_case("(123) 456-7890", None); -} - -#[test] -#[ignore] -fn test_invalid_if_exchange_code_starts_with_1() { - process_clean_case("(223) 156-7890", None); -} - -#[test] -#[ignore] -fn test_invalid_if_exchange_code_starts_with_0() { - process_clean_case("(223) 056-7890", None); -} - -#[test] -#[ignore] -fn test_invalid_if_exchange_code_starts_with_1_on_valid_11digit_number() { - process_clean_case("1 (223) 156-7890", None); -} - -#[test] -#[ignore] -fn test_invalid_if_exchange_code_starts_with_0_on_valid_11digit_number() { - process_clean_case("1 (223) 056-7890", None); -} - -#[test] -#[ignore] -fn test_invalid_if_area_code_starts_with_0() { - process_clean_case("(023) 456-7890", None); -} diff --git a/exercises/practice/phone-number/tests/phone_number.rs b/exercises/practice/phone-number/tests/phone_number.rs new file mode 100644 index 000000000..4ffc59931 --- /dev/null +++ b/exercises/practice/phone-number/tests/phone_number.rs @@ -0,0 +1,149 @@ +use phone_number::*; + +#[test] +fn cleans_the_number() { + let input = "(223) 456-7890"; + let output = number(input); + let expected = Some("2234567890".into()); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn cleans_numbers_with_dots() { + let input = "223.456.7890"; + let output = number(input); + let expected = Some("2234567890".into()); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn cleans_numbers_with_multiple_spaces() { + let input = "223 456 7890 "; + let output = number(input); + let expected = Some("2234567890".into()); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn invalid_when_9_digits() { + let input = "123456789"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn invalid_when_11_digits_does_not_start_with_a_1() { + let input = "22234567890"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn valid_when_11_digits_and_starting_with_1() { + let input = "12234567890"; + let output = number(input); + let expected = Some("2234567890".into()); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn valid_when_11_digits_and_starting_with_1_even_with_punctuation() { + let input = "+1 (223) 456-7890"; + let output = number(input); + let expected = Some("2234567890".into()); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn invalid_when_more_than_11_digits() { + let input = "321234567890"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn invalid_with_letters() { + let input = "523-abc-7890"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn invalid_with_punctuations() { + let input = "523-@:!-7890"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn invalid_if_area_code_starts_with_0() { + let input = "(023) 456-7890"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn invalid_if_area_code_starts_with_1() { + let input = "(123) 456-7890"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn invalid_if_exchange_code_starts_with_0() { + let input = "(223) 056-7890"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn invalid_if_exchange_code_starts_with_1() { + let input = "(223) 156-7890"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn invalid_if_area_code_starts_with_0_on_valid_11_digit_number() { + let input = "1 (023) 456-7890"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn invalid_if_area_code_starts_with_1_on_valid_11_digit_number() { + let input = "1 (123) 456-7890"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn invalid_if_exchange_code_starts_with_0_on_valid_11_digit_number() { + let input = "1 (223) 056-7890"; + let output = number(input); + assert!(output.is_none()); +} + +#[test] +#[ignore] +fn invalid_if_exchange_code_starts_with_1_on_valid_11_digit_number() { + let input = "1 (223) 156-7890"; + let output = number(input); + assert!(output.is_none()); +} diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index bcb125117..a9645ac23 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -1,18 +1,46 @@ # Instructions -Implement a program that translates from English to Pig Latin. +Your task is to translate text from English to Pig Latin. +The translation is defined using four rules, which look at the pattern of vowels and consonants at the beginning of a word. +These rules look at each word's use of vowels and consonants: -Pig Latin is a made-up children's language that's intended to be -confusing. It obeys a few simple rules (below), but when it's spoken -quickly it's really difficult for non-children (and non-native speakers) -to understand. +- vowels: the letters `a`, `e`, `i`, `o`, and `u` +- consonants: the other 21 letters of the English alphabet -- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word. Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). -- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. Consonant sounds can be made up of multiple consonants, a.k.a. a consonant cluster (e.g. "chair" -> "airchay"). -- **Rule 3**: If a word starts with a consonant sound followed by "qu", move it to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). -- **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay"). +## Rule 1 -There are a few more rules for edge cases, and there are regional -variants too. +If a word begins with a vowel, or starts with `"xr"` or `"yt"`, add an `"ay"` sound to the end of the word. -See for more details. +For example: + +- `"apple"` -> `"appleay"` (starts with vowel) +- `"xray"` -> `"xrayay"` (starts with `"xr"`) +- `"yttria"` -> `"yttriaay"` (starts with `"yt"`) + +## Rule 2 + +If a word begins with one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. + +For example: + +- `"pig"` -> `"igp"` -> `"igpay"` (starts with single consonant) +- `"chair"` -> `"airch"` -> `"airchay"` (starts with multiple consonants) +- `"thrush"` -> `"ushthr"` -> `"ushthray"` (starts with multiple consonants) + +## Rule 3 + +If a word starts with zero or more consonants followed by `"qu"`, first move those consonants (if any) and the `"qu"` part to the end of the word, and then add an `"ay"` sound to the end of the word. + +For example: + +- `"quick"` -> `"ickqu"` -> `"ickquay"` (starts with `"qu"`, no preceding consonants) +- `"square"` -> `"aresqu"` -> `"aresquay"` (starts with one consonant followed by `"qu`") + +## Rule 4 + +If a word starts with one or more consonants followed by `"y"`, first move the consonants preceding the `"y"`to the end of the word, and then add an `"ay"` sound to the end of the word. + +Some examples: + +- `"my"` -> `"ym"` -> `"ymay"` (starts with single consonant followed by `"y"`) +- `"rhythm"` -> `"ythmrh"` -> `"ythmrhay"` (starts with multiple consonants followed by `"y"`) diff --git a/exercises/practice/pig-latin/.docs/introduction.md b/exercises/practice/pig-latin/.docs/introduction.md new file mode 100644 index 000000000..04baa4758 --- /dev/null +++ b/exercises/practice/pig-latin/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +Your parents have challenged you and your sibling to a game of two-on-two basketball. +Confident they'll win, they let you score the first couple of points, but then start taking over the game. +Needing a little boost, you start speaking in [Pig Latin][pig-latin], which is a made-up children's language that's difficult for non-children to understand. +This will give you the edge to prevail over your parents! + +[pig-latin]: https://en.wikipedia.org/wiki/Pig_latin diff --git a/exercises/practice/pig-latin/.gitignore b/exercises/practice/pig-latin/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/pig-latin/.gitignore +++ b/exercises/practice/pig-latin/.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/pig-latin/.meta/Cargo-example.toml b/exercises/practice/pig-latin/.meta/Cargo-example.toml index 0d0b60aa4..3a976347f 100644 --- a/exercises/practice/pig-latin/.meta/Cargo-example.toml +++ b/exercises/practice/pig-latin/.meta/Cargo-example.toml @@ -1,8 +1,10 @@ [package] -edition = "2021" -name = "pig-latin" -version = "1.0.0" +name = "pig_latin" +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] regex = "0.2" -lazy_static = "1.4.0" diff --git a/exercises/practice/pig-latin/.meta/config.json b/exercises/practice/pig-latin/.meta/config.json index 14dfe5f40..1d6631e0f 100644 --- a/exercises/practice/pig-latin/.meta/config.json +++ b/exercises/practice/pig-latin/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/pig-latin.rs" + "tests/pig_latin.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/pig-latin/.meta/example.rs b/exercises/practice/pig-latin/.meta/example.rs index 6218f719f..f6daf2cd0 100644 --- a/exercises/practice/pig-latin/.meta/example.rs +++ b/exercises/practice/pig-latin/.meta/example.rs @@ -1,6 +1,4 @@ -#[macro_use] -extern crate lazy_static; -extern crate regex; +use std::sync::LazyLock; use regex::Regex; @@ -9,12 +7,13 @@ use regex::Regex; pub fn translate_word(word: &str) -> String { // Prevent creation and compilation at every call. // These are compiled exactly once - lazy_static! { - // Detects if it starts with a vowel - static ref VOWEL: Regex = Regex::new(r"^([aeiou]|y[^aeiou]|xr)[a-z]*").unwrap(); - // Detects splits for initial consonants - static ref CONSONANTS: Regex = Regex::new(r"^([^aeiou]?qu|[^aeiou][^aeiouy]*)([a-z]*)").unwrap(); - } + + // Detects if it starts with a vowel + static VOWEL: LazyLock = + LazyLock::new(|| Regex::new(r"^([aeiou]|y[^aeiou]|xr)[a-z]*").unwrap()); + // Detects splits for initial consonants + static CONSONANTS: LazyLock = + LazyLock::new(|| Regex::new(r"^([^aeiou]?qu|[^aeiou][^aeiouy]*)([a-z]*)").unwrap()); if VOWEL.is_match(word) { String::from(word) + "ay" @@ -26,7 +25,7 @@ pub fn translate_word(word: &str) -> String { pub fn translate(text: &str) -> String { text.split(' ') - .map(|w| translate_word(w)) + .map(translate_word) .collect::>() .join(" ") } diff --git a/exercises/practice/pig-latin/.meta/test_template.tera b/exercises/practice/pig-latin/.meta/test_template.tera new file mode 100644 index 000000000..a6e1b9dff --- /dev/null +++ b/exercises/practice/pig-latin/.meta/test_template.tera @@ -0,0 +1,14 @@ +use pig_latin::*; + +{% for test_group in cases %} +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.phrase | json_encode() }}; + let output = translate(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/pig-latin/.meta/tests.toml b/exercises/practice/pig-latin/.meta/tests.toml index be690e975..d524305b4 100644 --- a/exercises/practice/pig-latin/.meta/tests.toml +++ b/exercises/practice/pig-latin/.meta/tests.toml @@ -1,3 +1,79 @@ -# 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. + +[11567f84-e8c6-4918-aedb-435f0b73db57] +description = "ay is added to words that start with vowels -> word beginning with a" + +[f623f581-bc59-4f45-9032-90c3ca9d2d90] +description = "ay is added to words that start with vowels -> word beginning with e" + +[7dcb08b3-23a6-4e8a-b9aa-d4e859450d58] +description = "ay is added to words that start with vowels -> word beginning with i" + +[0e5c3bff-266d-41c8-909f-364e4d16e09c] +description = "ay is added to words that start with vowels -> word beginning with o" + +[614ba363-ca3c-4e96-ab09-c7320799723c] +description = "ay is added to words that start with vowels -> word beginning with u" + +[bf2538c6-69eb-4fa7-a494-5a3fec911326] +description = "ay is added to words that start with vowels -> word beginning with a vowel and followed by a qu" + +[e5be8a01-2d8a-45eb-abb4-3fcc9582a303] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with p" + +[d36d1e13-a7ed-464d-a282-8820cb2261ce] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with k" + +[d838b56f-0a89-4c90-b326-f16ff4e1dddc] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with x" + +[bce94a7a-a94e-4e2b-80f4-b2bb02e40f71] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with q without a following u" + +[e59dbbe8-ccee-4619-a8e9-ce017489bfc0] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with consonant and vowel containing qu" + +[c01e049a-e3e2-451c-bf8e-e2abb7e438b8] +description = "some letter clusters are treated like a single consonant -> word beginning with ch" + +[9ba1669e-c43f-4b93-837a-cfc731fd1425] +description = "some letter clusters are treated like a single consonant -> word beginning with qu" + +[92e82277-d5e4-43d7-8dd3-3a3b316c41f7] +description = "some letter clusters are treated like a single consonant -> word beginning with qu and a preceding consonant" + +[79ae4248-3499-4d5b-af46-5cb05fa073ac] +description = "some letter clusters are treated like a single consonant -> word beginning with th" + +[e0b3ae65-f508-4de3-8999-19c2f8e243e1] +description = "some letter clusters are treated like a single consonant -> word beginning with thr" + +[20bc19f9-5a35-4341-9d69-1627d6ee6b43] +description = "some letter clusters are treated like a single consonant -> word beginning with sch" + +[54b796cb-613d-4509-8c82-8fbf8fc0af9e] +description = "some letter clusters are treated like a single vowel -> word beginning with yt" + +[8c37c5e1-872e-4630-ba6e-d20a959b67f6] +description = "some letter clusters are treated like a single vowel -> word beginning with xr" + +[a4a36d33-96f3-422c-a233-d4021460ff00] +description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a consonant at the beginning of a word" + +[adc90017-1a12-4100-b595-e346105042c7] +description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a vowel at the end of a consonant cluster" + +[29b4ca3d-efe5-4a95-9a54-8467f2e5e59a] +description = "position of y in a word determines if it is a consonant or a vowel -> y as second letter in two letter word" + +[44616581-5ce3-4a81-82d0-40c7ab13d2cf] +description = "phrases are translated -> a whole phrase" diff --git a/exercises/practice/pig-latin/Cargo.toml b/exercises/practice/pig-latin/Cargo.toml index 7ae4cf403..4dc02d93f 100644 --- a/exercises/practice/pig-latin/Cargo.toml +++ b/exercises/practice/pig-latin/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" -name = "pig-latin" -version = "1.0.0" +name = "pig_latin" +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/pig-latin/src/lib.rs b/exercises/practice/pig-latin/src/lib.rs index 928972389..f395af0b8 100644 --- a/exercises/practice/pig-latin/src/lib.rs +++ b/exercises/practice/pig-latin/src/lib.rs @@ -1,5 +1,3 @@ pub fn translate(input: &str) -> String { - unimplemented!( - "Using the Pig Latin text transformation rules, convert the given input '{input}'" - ); + todo!("Using the Pig Latin text transformation rules, convert the given input '{input}'"); } diff --git a/exercises/practice/pig-latin/tests/pig-latin.rs b/exercises/practice/pig-latin/tests/pig-latin.rs deleted file mode 100644 index 6e3812744..000000000 --- a/exercises/practice/pig-latin/tests/pig-latin.rs +++ /dev/null @@ -1,126 +0,0 @@ -use pig_latin as pl; - -#[test] -fn test_word_beginning_with_a() { - assert_eq!(pl::translate("apple"), "appleay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_e() { - assert_eq!(pl::translate("ear"), "earay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_i() { - assert_eq!(pl::translate("igloo"), "iglooay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_o() { - assert_eq!(pl::translate("object"), "objectay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_u() { - assert_eq!(pl::translate("under"), "underay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_a_vowel_and_followed_by_a_qu() { - assert_eq!(pl::translate("equal"), "equalay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_p() { - assert_eq!(pl::translate("pig"), "igpay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_k() { - assert_eq!(pl::translate("koala"), "oalakay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_y() { - assert_eq!(pl::translate("yellow"), "ellowyay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_x() { - assert_eq!(pl::translate("xenon"), "enonxay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_q_without_a_following_u() { - assert_eq!(pl::translate("qat"), "atqay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_ch() { - assert_eq!(pl::translate("chair"), "airchay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_qu() { - assert_eq!(pl::translate("queen"), "eenquay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_qu_and_a_preceding_consonant() { - assert_eq!(pl::translate("square"), "aresquay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_th() { - assert_eq!(pl::translate("therapy"), "erapythay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_thr() { - assert_eq!(pl::translate("thrush"), "ushthray"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_sch() { - assert_eq!(pl::translate("school"), "oolschay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_yt() { - assert_eq!(pl::translate("yttria"), "yttriaay"); -} - -#[test] -#[ignore] -fn test_word_beginning_with_xr() { - assert_eq!(pl::translate("xray"), "xrayay"); -} - -#[test] -#[ignore] -fn test_y_is_treated_like_a_vowel_at_the_end_of_a_consonant_cluster() { - assert_eq!(pl::translate("rhythm"), "ythmrhay"); -} - -#[test] -#[ignore] -fn test_a_whole_phrase() { - assert_eq!(pl::translate("quick fast run"), "ickquay astfay unray"); -} diff --git a/exercises/practice/pig-latin/tests/pig_latin.rs b/exercises/practice/pig-latin/tests/pig_latin.rs new file mode 100644 index 000000000..55eb82ca3 --- /dev/null +++ b/exercises/practice/pig-latin/tests/pig_latin.rs @@ -0,0 +1,207 @@ +use pig_latin::*; + +#[test] +fn word_beginning_with_a() { + let input = "apple"; + let output = translate(input); + let expected = "appleay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_e() { + let input = "ear"; + let output = translate(input); + let expected = "earay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_i() { + let input = "igloo"; + let output = translate(input); + let expected = "iglooay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_o() { + let input = "object"; + let output = translate(input); + let expected = "objectay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_u() { + let input = "under"; + let output = translate(input); + let expected = "underay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_a_vowel_and_followed_by_a_qu() { + let input = "equal"; + let output = translate(input); + let expected = "equalay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_p() { + let input = "pig"; + let output = translate(input); + let expected = "igpay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_k() { + let input = "koala"; + let output = translate(input); + let expected = "oalakay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_x() { + let input = "xenon"; + let output = translate(input); + let expected = "enonxay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_q_without_a_following_u() { + let input = "qat"; + let output = translate(input); + let expected = "atqay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_consonant_and_vowel_containing_qu() { + let input = "liquid"; + let output = translate(input); + let expected = "iquidlay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_ch() { + let input = "chair"; + let output = translate(input); + let expected = "airchay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_qu() { + let input = "queen"; + let output = translate(input); + let expected = "eenquay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_qu_and_a_preceding_consonant() { + let input = "square"; + let output = translate(input); + let expected = "aresquay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_th() { + let input = "therapy"; + let output = translate(input); + let expected = "erapythay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_thr() { + let input = "thrush"; + let output = translate(input); + let expected = "ushthray"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_sch() { + let input = "school"; + let output = translate(input); + let expected = "oolschay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_yt() { + let input = "yttria"; + let output = translate(input); + let expected = "yttriaay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn word_beginning_with_xr() { + let input = "xray"; + let output = translate(input); + let expected = "xrayay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn y_is_treated_like_a_consonant_at_the_beginning_of_a_word() { + let input = "yellow"; + let output = translate(input); + let expected = "ellowyay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn y_is_treated_like_a_vowel_at_the_end_of_a_consonant_cluster() { + let input = "rhythm"; + let output = translate(input); + let expected = "ythmrhay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn y_as_second_letter_in_two_letter_word() { + let input = "my"; + let output = translate(input); + let expected = "ymay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn a_whole_phrase() { + let input = "quick fast run"; + let output = translate(input); + let expected = "ickquay astfay unray"; + assert_eq!(output, expected); +} diff --git a/exercises/practice/poker/.docs/instructions.append.md b/exercises/practice/poker/.docs/hints.md similarity index 98% rename from exercises/practice/poker/.docs/instructions.append.md rename to exercises/practice/poker/.docs/hints.md index fb531d554..0312531cc 100644 --- a/exercises/practice/poker/.docs/instructions.append.md +++ b/exercises/practice/poker/.docs/hints.md @@ -1,4 +1,4 @@ -# Hints +## General - Ranking a list of poker hands can be considered a sorting problem. - Rust provides the [sort](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort) method for `Vec where T: Ord`. diff --git a/exercises/practice/poker/.docs/instructions.md b/exercises/practice/poker/.docs/instructions.md index 6a38cf4bc..107cd49d6 100644 --- a/exercises/practice/poker/.docs/instructions.md +++ b/exercises/practice/poker/.docs/instructions.md @@ -2,5 +2,6 @@ Pick the best hand(s) from a list of poker hands. -See [wikipedia](https://en.wikipedia.org/wiki/List_of_poker_hands) for an -overview of poker hands. +See [Wikipedia][poker-hands] for an overview of poker hands. + +[poker-hands]: https://en.wikipedia.org/wiki/List_of_poker_hands diff --git a/exercises/practice/poker/.gitignore b/exercises/practice/poker/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/poker/.gitignore +++ b/exercises/practice/poker/.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/poker/.meta/Cargo-example.toml b/exercises/practice/poker/.meta/Cargo-example.toml index 5b66e0ebf..23533088f 100644 --- a/exercises/practice/poker/.meta/Cargo-example.toml +++ b/exercises/practice/poker/.meta/Cargo-example.toml @@ -1,7 +1,10 @@ [package] -edition = "2021" name = "poker" -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] counter = "0.5.2" diff --git a/exercises/practice/poker/.meta/example.rs b/exercises/practice/poker/.meta/example.rs index 740cb2489..f563ae0a1 100644 --- a/exercises/practice/poker/.meta/example.rs +++ b/exercises/practice/poker/.meta/example.rs @@ -1,5 +1,5 @@ +use std::cmp::Ordering; use std::fmt; -use std::{cmp::Ordering, convert::TryFrom}; use counter::Counter; @@ -51,7 +51,7 @@ impl fmt::Display for Suit { } } -impl<'a> TryFrom<&'a str> for Suit { +impl TryFrom<&str> for Suit { type Error = &'static str; fn try_from(source: &str) -> Result { @@ -115,7 +115,7 @@ impl PartialOrd for Rank { } } -impl<'a> TryFrom<&'a str> for Rank { +impl TryFrom<&str> for Rank { type Error = &'static str; fn try_from(source: &str) -> Result { @@ -160,7 +160,7 @@ impl fmt::Display for Card { } } -impl<'a> TryFrom<&'a str> for Card { +impl TryFrom<&str> for Card { type Error = &'static str; fn try_from(source: &str) -> Result { @@ -262,7 +262,7 @@ struct Hand<'a> { hand_type: PokerHand, } -impl<'a> Hand<'a> { +impl Hand<'_> { fn cmp_high_card(&self, other: &Hand, card: usize) -> Ordering { let mut ordering = self.cards[card] .rank @@ -312,13 +312,13 @@ impl<'a> Hand<'a> { } } -impl<'a> fmt::Display for Hand<'a> { +impl fmt::Display for Hand<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.source) } } -impl<'a> PartialOrd for Hand<'a> { +impl PartialOrd for Hand<'_> { fn partial_cmp(&self, other: &Hand) -> Option { Some(self.hand_type.cmp(&other.hand_type).then_with(|| { use crate::PokerHand::*; diff --git a/exercises/practice/poker/.meta/test_template.tera b/exercises/practice/poker/.meta/test_template.tera new file mode 100644 index 000000000..546fe4850 --- /dev/null +++ b/exercises/practice/poker/.meta/test_template.tera @@ -0,0 +1,13 @@ +use poker::*; +use std::collections::HashSet; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = &{{ test.input.hands | json_encode() }}; + let output = winning_hands(input).into_iter().collect::>(); + let expected = {{ test.expected | json_encode() }}.into_iter().collect::>(); + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/poker/.meta/tests.toml b/exercises/practice/poker/.meta/tests.toml index be690e975..2e654ef63 100644 --- a/exercises/practice/poker/.meta/tests.toml +++ b/exercises/practice/poker/.meta/tests.toml @@ -1,3 +1,131 @@ -# 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. + +[161f485e-39c2-4012-84cf-bec0c755b66c] +description = "single hand always wins" + +[370ac23a-a00f-48a9-9965-6f3fb595cf45] +description = "highest card out of all hands wins" + +[d94ad5a7-17df-484b-9932-c64fc26cff52] +description = "a tie has multiple winners" + +[61ed83a9-cfaa-40a5-942a-51f52f0a8725] +description = "multiple hands with the same high cards, tie compares next highest ranked, down to last card" + +[da01becd-f5b0-4342-b7f3-1318191d0580] +description = "winning high card hand also has the lowest card" + +[f7175a89-34ff-44de-b3d7-f6fd97d1fca4] +description = "one pair beats high card" + +[e114fd41-a301-4111-a9e7-5a7f72a76561] +description = "highest pair wins" + +[b3acd3a7-f9fa-4647-85ab-e0a9e07d1365] +description = "both hands have the same pair, high card wins" + +[935bb4dc-a622-4400-97fa-86e7d06b1f76] +description = "two pairs beats one pair" + +[c8aeafe1-6e3d-4711-a6de-5161deca91fd] +description = "both hands have two pairs, highest ranked pair wins" + +[88abe1ba-7ad7-40f3-847e-0a26f8e46a60] +description = "both hands have two pairs, with the same highest ranked pair, tie goes to low pair" + +[15a7a315-0577-47a3-9981-d6cf8e6f387b] +description = "both hands have two identically ranked pairs, tie goes to remaining card (kicker)" + +[f761e21b-2560-4774-a02a-b3e9366a51ce] +description = "both hands have two pairs that add to the same value, win goes to highest pair" + +[fc6277ac-94ac-4078-8d39-9d441bc7a79e] +description = "two pairs first ranked by largest pair" + +[21e9f1e6-2d72-49a1-a930-228e5e0195dc] +description = "three of a kind beats two pair" + +[c2fffd1f-c287-480f-bf2d-9628e63bbcc3] +description = "both hands have three of a kind, tie goes to highest ranked triplet" + +[eb856cc2-481c-4b0d-9835-4d75d07a5d9d] +description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" +include = false + +[26a4a7d4-34a2-4f18-90b4-4a8dd35d2bb1] +description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" +reimplements = "eb856cc2-481c-4b0d-9835-4d75d07a5d9d" + +[a858c5d9-2f28-48e7-9980-b7fa04060a60] +description = "a straight beats three of a kind" + +[73c9c756-e63e-4b01-a88d-0d4491a7a0e3] +description = "aces can end a straight (10 J Q K A)" + +[76856b0d-35cd-49ce-a492-fe5db53abc02] +description = "aces can start a straight (A 2 3 4 5)" + +[e214b7df-dcba-45d3-a2e5-342d8c46c286] +description = "aces cannot be in the middle of a straight (Q K A 2 3)" + +[6980c612-bbff-4914-b17a-b044e4e69ea1] +description = "both hands with a straight, tie goes to highest ranked card" + +[5135675c-c2fc-4e21-9ba3-af77a32e9ba4] +description = "even though an ace is usually high, a 5-high straight is the lowest-scoring straight" + +[c601b5e6-e1df-4ade-b444-b60ce13b2571] +description = "flush beats a straight" + +[4d90261d-251c-49bd-a468-896bf10133de] +description = "both hands have a flush, tie goes to high card, down to the last one if necessary" +include = false + +[e04137c5-c19a-4dfc-97a1-9dfe9baaa2ff] +description = "both hands have a flush, tie goes to high card, down to the last one if necessary" +reimplements = "4d90261d-251c-49bd-a468-896bf10133de" + +[3a19361d-8974-455c-82e5-f7152f5dba7c] +description = "full house beats a flush" + +[eb73d0e6-b66c-4f0f-b8ba-bf96bc0a67f0] +description = "both hands have a full house, tie goes to highest-ranked triplet" + +[34b51168-1e43-4c0d-9b32-e356159b4d5d] +description = "with multiple decks, both hands have a full house with the same triplet, tie goes to the pair" + +[d61e9e99-883b-4f99-b021-18f0ae50c5f4] +description = "four of a kind beats a full house" + +[2e1c8c63-e0cb-4214-a01b-91954490d2fe] +description = "both hands have four of a kind, tie goes to high quad" + +[892ca75d-5474-495d-9f64-a6ce2dcdb7e1] +description = "with multiple decks, both hands with identical four of a kind, tie determined by kicker" + +[923bd910-dc7b-4f7d-a330-8b42ec10a3ac] +description = "straight flush beats four of a kind" + +[d9629e22-c943-460b-a951-2134d1b43346] +description = "aces can end a straight flush (10 J Q K A)" + +[05d5ede9-64a5-4678-b8ae-cf4c595dc824] +description = "aces can start a straight flush (A 2 3 4 5)" + +[ad655466-6d04-49e8-a50c-0043c3ac18ff] +description = "aces cannot be in the middle of a straight flush (Q K A 2 3)" + +[d0927f70-5aec-43db-aed8-1cbd1b6ee9ad] +description = "both hands have a straight flush, tie goes to highest-ranked card" + +[be620e09-0397-497b-ac37-d1d7a4464cfc] +description = "even though an ace is usually high, a 5-high straight flush is the lowest-scoring straight flush" diff --git a/exercises/practice/poker/Cargo.toml b/exercises/practice/poker/Cargo.toml index d0c6bdcb6..539ae7925 100644 --- a/exercises/practice/poker/Cargo.toml +++ b/exercises/practice/poker/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "poker" -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/poker/src/lib.rs b/exercises/practice/poker/src/lib.rs index 802f080ef..a6f117e30 100644 --- a/exercises/practice/poker/src/lib.rs +++ b/exercises/practice/poker/src/lib.rs @@ -3,5 +3,5 @@ /// Note the type signature: this function should return _the same_ reference to /// the winning hand(s) as were passed in, not reconstructed strings which happen to be equal. pub fn winning_hands<'a>(hands: &[&'a str]) -> Vec<&'a str> { - unimplemented!("Out of {hands:?}, which hand wins?") + todo!("Out of {hands:?}, which hand wins?") } diff --git a/exercises/practice/poker/tests/poker.rs b/exercises/practice/poker/tests/poker.rs index cd16325f3..484451145 100644 --- a/exercises/practice/poker/tests/poker.rs +++ b/exercises/practice/poker/tests/poker.rs @@ -1,265 +1,343 @@ -use poker::winning_hands; +use poker::*; use std::collections::HashSet; -fn hs_from<'a>(input: &[&'a str]) -> HashSet<&'a str> { - let mut hs = HashSet::new(); - for item in input.iter() { - hs.insert(*item); - } - hs +#[test] +fn single_hand_always_wins() { + let input = &["4S 5S 7H 8D JC"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S 5S 7H 8D JC"].into_iter().collect::>(); + assert_eq!(output, expected); } -/// Test that the expected output is produced from the given input -/// using the `winning_hands` function. -/// -/// Note that the output can be in any order. Here, we use a HashSet to -/// abstract away the order of outputs. -fn test(input: &[&str], expected: &[&str]) { - assert_eq!(hs_from(&winning_hands(input)), hs_from(expected)) +#[test] +#[ignore] +fn highest_card_out_of_all_hands_wins() { + let input = &["4D 5S 6S 8D 3C", "2S 4C 7S 9H 10H", "3S 4S 5D 6H JH"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 4S 5D 6H JH"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] -fn test_single_hand_always_wins() { - test(&["4S 5S 7H 8D JC"], &["4S 5S 7H 8D JC"]) +#[ignore] +fn a_tie_has_multiple_winners() { + let input = &[ + "4D 5S 6S 8D 3C", + "2S 4C 7S 9H 10H", + "3S 4S 5D 6H JH", + "3H 4H 5C 6C JD", + ]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 4S 5D 6H JH", "3H 4H 5C 6C JD"] + .into_iter() + .collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_duplicate_hands_always_tie() { - let input = &["3S 4S 5D 6H JH", "3S 4S 5D 6H JH", "3S 4S 5D 6H JH"]; - assert_eq!(&winning_hands(input), input) +fn multiple_hands_with_the_same_high_cards_tie_compares_next_highest_ranked_down_to_last_card() { + let input = &["3S 5H 6S 8D 7H", "2S 5D 6D 8C 7S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 5H 6S 8D 7H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_highest_card_of_all_hands_wins() { - test( - &["4D 5S 6S 8D 3C", "2S 4C 7S 9H 10H", "3S 4S 5D 6H JH"], - &["3S 4S 5D 6H JH"], - ) +fn winning_high_card_hand_also_has_the_lowest_card() { + let input = &["2S 5H 6S 8D 7H", "3S 4D 6D 8C 7S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2S 5H 6S 8D 7H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_a_tie_has_multiple_winners() { - test( - &[ - "4D 5S 6S 8D 3C", - "2S 4C 7S 9H 10H", - "3S 4S 5D 6H JH", - "3H 4H 5C 6C JD", - ], - &["3S 4S 5D 6H JH", "3H 4H 5C 6C JD"], - ) +fn one_pair_beats_high_card() { + let input = &["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2S 4H 6S 4D JH"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_high_card_can_be_low_card_in_an_otherwise_tie() { - // multiple hands with the same high cards, tie compares next highest ranked, - // down to last card - test(&["3S 5H 6S 8D 7H", "2S 5D 6D 8C 7S"], &["3S 5H 6S 8D 7H"]) +fn highest_pair_wins() { + let input = &["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2S 4H 6C 4D JD"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_one_pair_beats_high_card() { - test(&["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"], &["2S 4H 6S 4D JH"]) +fn both_hands_have_the_same_pair_high_card_wins() { + let input = &["4H 4S AH JC 3D", "4C 4D AS 5D 6C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4H 4S AH JC 3D"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_highest_pair_wins() { - test(&["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"], &["2S 4H 6C 4D JD"]) +fn two_pairs_beats_one_pair() { + let input = &["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S 5H 4C 8C 5C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_two_pairs_beats_one_pair() { - test(&["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"], &["4S 5H 4C 8C 5C"]) +fn both_hands_have_two_pairs_highest_ranked_pair_wins() { + let input = &["2S 8H 2D 8D 3H", "4S 5H 4C 8S 5D"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2S 8H 2D 8D 3H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_two_pair_ranks() { - // both hands have two pairs, highest ranked pair wins - test(&["2S 8H 2D 8D 3H", "4S 5H 4C 8S 5D"], &["2S 8H 2D 8D 3H"]) +fn both_hands_have_two_pairs_with_the_same_highest_ranked_pair_tie_goes_to_low_pair() { + let input = &["2S QS 2C QD JH", "JD QH JS 8D QC"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["JD QH JS 8D QC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_two_pairs_second_pair_cascade() { - // both hands have two pairs, with the same highest ranked pair, - // tie goes to low pair - test(&["2S QS 2C QD JH", "JD QH JS 8D QC"], &["JD QH JS 8D QC"]) +fn both_hands_have_two_identically_ranked_pairs_tie_goes_to_remaining_card_kicker() { + let input = &["JD QH JS 8D QC", "JS QS JC 2D QD"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["JD QH JS 8D QC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_two_pairs_last_card_cascade() { - // both hands have two identically ranked pairs, - // tie goes to remaining card (kicker) - test(&["JD QH JS 8D QC", "JS QS JC 2D QD"], &["JD QH JS 8D QC"]) +fn both_hands_have_two_pairs_that_add_to_the_same_value_win_goes_to_highest_pair() { + let input = &["6S 6H 3S 3H AS", "7H 7S 2H 2S AC"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["7H 7S 2H 2S AC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_three_of_a_kind_beats_two_pair() { - test(&["2S 8H 2H 8D JH", "4S 5H 4C 8S 4H"], &["4S 5H 4C 8S 4H"]) +fn two_pairs_first_ranked_by_largest_pair() { + let input = &["5C 2S 5S 4H 4C", "6S 2S 6H 7C 2C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["6S 2S 6H 7C 2C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_three_of_a_kind_ranks() { - //both hands have three of a kind, tie goes to highest ranked triplet - test(&["2S 2H 2C 8D JH", "4S AH AS 8C AD"], &["4S AH AS 8C AD"]) +fn three_of_a_kind_beats_two_pair() { + let input = &["2S 8H 2H 8D JH", "4S 5H 4C 8S 4H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S 5H 4C 8S 4H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_low_three_of_a_kind_beats_high_two_pair() { - test(&["2H 2D 2C 8H 5H", "AS AC KS KC 6S"], &["2H 2D 2C 8H 5H"]) +fn both_hands_have_three_of_a_kind_tie_goes_to_highest_ranked_triplet() { + let input = &["2S 2H 2C 8D JH", "4S AH AS 8C AD"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S AH AS 8C AD"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_three_of_a_kind_cascade_ranks() { - // with multiple decks, two players can have same three of a kind, - // ties go to highest remaining cards - test(&["4S AH AS 7C AD", "4S AH AS 8C AD"], &["4S AH AS 8C AD"]) +fn with_multiple_decks_two_players_can_have_same_three_of_a_kind_ties_go_to_highest_remaining_cards() + { + let input = &["5S AH AS 7C AD", "4S AH AS 8C AD"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S AH AS 8C AD"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_straight_beats_three_of_a_kind() { - test(&["4S 5H 4C 8D 4H", "3S 4D 2S 6D 5C"], &["3S 4D 2S 6D 5C"]) +fn a_straight_beats_three_of_a_kind() { + let input = &["4S 5H 4C 8D 4H", "3S 4D 2S 6D 5C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 4D 2S 6D 5C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_aces_can_end_a_straight_high() { - // aces can end a straight (10 J Q K A) - test(&["4S 5H 4C 8D 4H", "10D JH QS KD AC"], &["10D JH QS KD AC"]) +fn aces_can_end_a_straight_10_j_q_k_a() { + let input = &["4S 5H 4C 8D 4H", "10D JH QS KD AC"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["10D JH QS KD AC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_aces_can_start_a_straight_low() { - // aces can start a straight (A 2 3 4 5) - test(&["4S 5H 4C 8D 4H", "4D AH 3S 2D 5C"], &["4D AH 3S 2D 5C"]) +fn aces_can_start_a_straight_a_2_3_4_5() { + let input = &["4S 5H 4C 8D 4H", "4D AH 3S 2D 5C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4D AH 3S 2D 5C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_no_ace_in_middle_of_straight() { - // aces cannot be in the middle of a straight (Q K A 2 3) - test(&["2C 3D 7H 5H 2S", "QS KH AC 2D 3S"], &["2C 3D 7H 5H 2S"]) +fn aces_cannot_be_in_the_middle_of_a_straight_q_k_a_2_3() { + let input = &["2C 3D 7H 5H 2S", "QS KH AC 2D 3S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2C 3D 7H 5H 2S"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_straight_ranks() { - // both hands with a straight, tie goes to highest ranked card - test(&["4S 6C 7S 8D 5H", "5S 7H 8S 9D 6H"], &["5S 7H 8S 9D 6H"]) +fn both_hands_with_a_straight_tie_goes_to_highest_ranked_card() { + let input = &["4S 6C 7S 8D 5H", "5S 7H 8S 9D 6H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["5S 7H 8S 9D 6H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_straight_scoring() { - // even though an ace is usually high, a 5-high straight is the lowest-scoring straight - test(&["2H 3C 4D 5D 6H", "4S AH 3S 2D 5H"], &["2H 3C 4D 5D 6H"]) +fn even_though_an_ace_is_usually_high_a_5_high_straight_is_the_lowest_scoring_straight() { + let input = &["2H 3C 4D 5D 6H", "4S AH 3S 2D 5H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2H 3C 4D 5D 6H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_flush_beats_a_straight() { - test(&["4C 6H 7D 8D 5H", "2S 4S 5S 6S 7S"], &["2S 4S 5S 6S 7S"]) +fn flush_beats_a_straight() { + let input = &["4C 6H 7D 8D 5H", "2S 4S 5S 6S 7S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2S 4S 5S 6S 7S"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_flush_cascade() { - // both hands have a flush, tie goes to high card, down to the last one if necessary - test(&["4H 7H 8H 9H 6H", "2S 4S 5S 6S 7S"], &["4H 7H 8H 9H 6H"]) +fn both_hands_have_a_flush_tie_goes_to_high_card_down_to_the_last_one_if_necessary() { + let input = &["2H 7H 8H 9H 6H", "3S 5S 6S 7S 8S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2H 7H 8H 9H 6H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_full_house_beats_a_flush() { - test(&["3H 6H 7H 8H 5H", "4S 5C 4C 5D 4H"], &["4S 5C 4C 5D 4H"]) +fn full_house_beats_a_flush() { + let input = &["3H 6H 7H 8H 5H", "4S 5H 4C 5D 4H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S 5H 4C 5D 4H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_full_house_ranks() { - // both hands have a full house, tie goes to highest-ranked triplet - test(&["4H 4S 4D 9S 9D", "5H 5S 5D 8S 8D"], &["5H 5S 5D 8S 8D"]) +fn both_hands_have_a_full_house_tie_goes_to_highest_ranked_triplet() { + let input = &["4H 4S 4D 9S 9D", "5H 5S 5D 8S 8D"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["5H 5S 5D 8S 8D"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_full_house_cascade() { - // with multiple decks, both hands have a full house with the same triplet, tie goes to the pair - test(&["5H 5S 5D 9S 9D", "5H 5S 5D 8S 8D"], &["5H 5S 5D 9S 9D"]) +fn with_multiple_decks_both_hands_have_a_full_house_with_the_same_triplet_tie_goes_to_the_pair() { + let input = &["5H 5S 5D 9S 9D", "5H 5S 5D 8S 8D"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["5H 5S 5D 9S 9D"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_four_of_a_kind_beats_full_house() { - test(&["4S 5H 4D 5D 4H", "3S 3H 2S 3D 3C"], &["3S 3H 2S 3D 3C"]) +fn four_of_a_kind_beats_a_full_house() { + let input = &["4S 5H 4D 5D 4H", "3S 3H 2S 3D 3C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 3H 2S 3D 3C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_four_of_a_kind_ranks() { - // both hands have four of a kind, tie goes to high quad - test(&["2S 2H 2C 8D 2D", "4S 5H 5S 5D 5C"], &["4S 5H 5S 5D 5C"]) +fn both_hands_have_four_of_a_kind_tie_goes_to_high_quad() { + let input = &["2S 2H 2C 8D 2D", "4S 5H 5S 5D 5C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S 5H 5S 5D 5C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_four_of_a_kind_cascade() { - // with multiple decks, both hands with identical four of a kind, tie determined by kicker - test(&["3S 3H 2S 3D 3C", "3S 3H 4S 3D 3C"], &["3S 3H 4S 3D 3C"]) +fn with_multiple_decks_both_hands_with_identical_four_of_a_kind_tie_determined_by_kicker() { + let input = &["3S 3H 2S 3D 3C", "3S 3H 4S 3D 3C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 3H 4S 3D 3C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_straight_flush_beats_four_of_a_kind() { - test(&["4S 5H 5S 5D 5C", "7S 8S 9S 6S 10S"], &["7S 8S 9S 6S 10S"]) +fn straight_flush_beats_four_of_a_kind() { + let input = &["4S 5H 5S 5D 5C", "7S 8S 9S 6S 10S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["7S 8S 9S 6S 10S"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_aces_can_end_a_straight_flush_high() { - // aces can end a straight flush (10 J Q K A) - test(&["KC AH AS AD AC", "10C JC QC KC AC"], &["10C JC QC KC AC"]) +fn aces_can_end_a_straight_flush_10_j_q_k_a() { + let input = &["KC AH AS AD AC", "10C JC QC KC AC"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["10C JC QC KC AC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_aces_can_start_a_straight_flush_low() { - // aces can start a straight flush (A 2 3 4 5) - test(&["KS AH AS AD AC", "4H AH 3H 2H 5H"], &["4H AH 3H 2H 5H"]) +fn aces_can_start_a_straight_flush_a_2_3_4_5() { + let input = &["KS AH AS AD AC", "4H AH 3H 2H 5H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4H AH 3H 2H 5H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_no_ace_in_middle_of_straight_flush() { - // aces cannot be in the middle of a straight flush (Q K A 2 3) - test(&["2C AC QC 10C KC", "QH KH AH 2H 3H"], &["2C AC QC 10C KC"]) +fn aces_cannot_be_in_the_middle_of_a_straight_flush_q_k_a_2_3() { + let input = &["2C AC QC 10C KC", "QH KH AH 2H 3H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2C AC QC 10C KC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_straight_flush_ranks() { - // both hands have a straight flush, tie goes to highest-ranked card - test(&["4H 6H 7H 8H 5H", "5S 7S 8S 9S 6S"], &["5S 7S 8S 9S 6S"]) +fn both_hands_have_a_straight_flush_tie_goes_to_highest_ranked_card() { + let input = &["4H 6H 7H 8H 5H", "5S 7S 8S 9S 6S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["5S 7S 8S 9S 6S"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_straight_flush_scoring() { - // even though an ace is usually high, a 5-high straight flush is the lowest-scoring straight flush - test(&["2H 3H 4H 5H 6H", "4D AD 3D 2D 5D"], &["2H 3H 4H 5H 6H"]) +fn even_though_an_ace_is_usually_high_a_5_high_straight_flush_is_the_lowest_scoring_straight_flush() +{ + let input = &["2H 3H 4H 5H 6H", "4D AD 3D 2D 5D"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2H 3H 4H 5H 6H"].into_iter().collect::>(); + assert_eq!(output, expected); } diff --git a/exercises/practice/pov/.docs/instructions.append.md b/exercises/practice/pov/.docs/instructions.append.md new file mode 100644 index 000000000..db8425a81 --- /dev/null +++ b/exercises/practice/pov/.docs/instructions.append.md @@ -0,0 +1,7 @@ +# Instructions append + +## Rust Specific Exercise Notes + +You are free to choose the internal representation of your tree. +However, watch out if you store the children in an ordered container. +Two trees must compare as equal independent of the children's order. diff --git a/exercises/practice/pov/.docs/instructions.md b/exercises/practice/pov/.docs/instructions.md new file mode 100644 index 000000000..0fdeed225 --- /dev/null +++ b/exercises/practice/pov/.docs/instructions.md @@ -0,0 +1,41 @@ +# Instructions + +Reparent a tree on a selected node. + +A [tree][wiki-tree] is a special type of [graph][wiki-graph] where all nodes are connected but there are no cycles. +That means, there is exactly one path to get from one node to another for any pair of nodes. + +This exercise is all about re-orientating a tree to see things from a different point of view. +For example family trees are usually presented from the ancestor's perspective: + +```text + +------0------+ + | | | + +-1-+ +-2-+ +-3-+ + | | | | | | + 4 5 6 7 8 9 +``` + +But there is no inherent direction in a tree. +The same information can be presented from the perspective of any other node in the tree, by pulling it up to the root and dragging its relationships along with it. +So the same tree from 6's perspective would look like: + +```text + 6 + | + +-----2-----+ + | | + 7 +-----0-----+ + | | + +-1-+ +-3-+ + | | | | + 4 5 8 9 +``` + +This lets us more simply describe the paths between two nodes. +So for example the path from 6-9 (which in the first tree goes up to the root and then down to a different leaf node) can be seen to follow the path 6-2-0-3-9. + +This exercise involves taking an input tree and re-orientating it from the point of view of one of the nodes. + +[wiki-graph]: https://en.wikipedia.org/wiki/Tree_(graph_theory) +[wiki-tree]: https://en.wikipedia.org/wiki/Graph_(discrete_mathematics) diff --git a/exercises/practice/pov/.gitignore b/exercises/practice/pov/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/pov/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/pov/.meta/config.json b/exercises/practice/pov/.meta/config.json new file mode 100644 index 000000000..dfab027d4 --- /dev/null +++ b/exercises/practice/pov/.meta/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "devcyjung", + "senekor" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/pov.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Reparent a graph on a selected node.", + "source": "Adaptation of exercise from 4clojure", + "source_url": "https://github.com/oxalorg/4ever-clojure" +} diff --git a/exercises/practice/pov/.meta/example.rs b/exercises/practice/pov/.meta/example.rs new file mode 100644 index 000000000..bacef69f2 --- /dev/null +++ b/exercises/practice/pov/.meta/example.rs @@ -0,0 +1,84 @@ +use std::fmt::Debug; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Tree { + label: T, + children: Vec>>, +} + +impl Tree { + pub fn new(label: T) -> Self { + Self { + label, + children: Default::default(), + } + } + + pub fn with_child(mut self, child: Self) -> Self { + self.children.insert( + self.children + .binary_search_by(|c| c.label.cmp(&child.label)) + .unwrap_err(), + Box::new(child), + ); + self + } + + pub fn pov_from(&mut self, from: &T) -> bool { + self.pov_from_rec(from).is_some() + } + + fn pov_from_rec(&mut self, from: &T) -> Option> { + if &self.label == from { + return Some(Vec::new()); + } + + // Run `pov_from_rec` over all children, until finding the one where it + // worked. That also returns the list of indexes to traverse to find the + // insertion point for the old POV. + let (pos, mut index_list) = self + .children + .iter_mut() + .enumerate() + .find_map(|(i, child)| child.pov_from_rec(from).map(|index_list| (i, index_list)))?; + + // swap old and new POV + let mut old_pov = self.children.remove(pos); + std::mem::swap(self, &mut old_pov); + + // find parent of old POV + let mut parent_of_old_pov = self; + for i in index_list.iter().rev() { + parent_of_old_pov = &mut parent_of_old_pov.children[*i]; + } + + // put old POV into its new place + let new_idx = parent_of_old_pov + .children + .binary_search_by(|c| c.label.cmp(&old_pov.label)) + .unwrap_err(); + parent_of_old_pov.children.insert(new_idx, old_pov); + + // Record index of old POV such that other recursive calls can insert + // their old POV as the child of ours. + index_list.push(new_idx); + + Some(index_list) + } + + pub fn path_between<'a>(&'a mut self, from: &'a T, to: &'a T) -> Option> { + if !self.pov_from(to) { + return None; + } + self.path_from(from) + } + + fn path_from<'a>(&'a self, from: &'a T) -> Option> { + if &self.label == from { + return Some(vec![from]); + } + let mut path = self.children.iter().find_map(|c| c.path_from(from))?; + path.push(&self.label); + Some(path) + } +} diff --git a/exercises/practice/pov/.meta/test_template.tera b/exercises/practice/pov/.meta/test_template.tera new file mode 100644 index 000000000..a7b1afca8 --- /dev/null +++ b/exercises/practice/pov/.meta/test_template.tera @@ -0,0 +1,78 @@ +/// Forbid implementations that rely on Clone. +mod no_clone { + use pov::*; + + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] + struct NotClone; + + #[test] + #[ignore] + fn doesnt_rely_on_clone() { + let mut tree = Tree::new(NotClone); + assert!(tree.pov_from(&NotClone)); + } +} + +/// Equality on trees must be independent of the order of children. +mod equality { + use pov::*; + use pretty_assertions::assert_eq; + + #[test] + #[ignore] + fn equality_is_order_independent() { + let a = Tree::new("root").with_child(Tree::new("left")).with_child(Tree::new("right")); + let b = Tree::new("root").with_child(Tree::new("right")).with_child(Tree::new("left")); + assert_eq!(a, b); + } +} + +{% macro render_tree(tree) -%} + Tree::new("{{ tree.label }}") + {%- if tree.children -%}{%- for child in tree.children -%} + .with_child({{ self::render_tree(tree=child) }}) + {%- endfor -%}{%- endif -%} +{%- endmacro -%} + +{%- macro render_vec(values) -%} +vec![ + {%- for value in values -%} + &"{{ value }}", + {%- endfor -%} +] +{%- endmacro -%} + +{% for test_group in cases %} +/// {{ test_group.description }} +mod {{ test_group.cases[0].property | make_ident }} { + use pov::*; + use pretty_assertions::assert_eq; + +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let mut tree = {{ self::render_tree(tree=test.input.tree) }}; + {%- if test.property == "fromPov" -%} + {%- if not test.expected -%} + assert!(!tree.pov_from(&"{{ test.input.from }}")); + {%- else -%} + assert!(tree.pov_from(&"{{ test.input.from }}")); + let expected = {{ self::render_tree(tree=test.expected) }}; + assert_eq!(tree, expected); + {%- endif -%} + {%- elif test.property == "pathTo" -%} + let result = tree.path_between(&"{{ test.input.from }}", &"{{ test.input.to }}"); + {%- if not test.expected -%} + let expected: Option> = None; + {%- else -%} + let expected = Some({{ self::render_vec(values=test.expected) }}); + {%- endif -%} + assert_eq!(result, expected); + {%- else -%} + Invalid property: {{ test.property }} + {%- endif -%} +} +{% endfor %} +} +{% endfor %} diff --git a/exercises/practice/pov/.meta/tests.toml b/exercises/practice/pov/.meta/tests.toml new file mode 100644 index 000000000..bfa0bb630 --- /dev/null +++ b/exercises/practice/pov/.meta/tests.toml @@ -0,0 +1,55 @@ +# 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. + +[1b3cd134-49ad-4a7d-8376-7087b7e70792] +description = "Reroot a tree so that its root is the specified node. -> Results in the same tree if the input tree is a singleton" + +[0778c745-0636-40de-9edd-25a8f40426f6] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with a parent and one sibling" + +[fdfdef0a-4472-4248-8bcf-19cf33f9c06e] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with a parent and many siblings" + +[cbcf52db-8667-43d8-a766-5d80cb41b4bb] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with new root deeply nested in tree" + +[e27fa4fa-648d-44cd-90af-d64a13d95e06] +description = "Reroot a tree so that its root is the specified node. -> Moves children of the new root to same level as former parent" + +[09236c7f-7c83-42cc-87a1-25afa60454a3] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a complex tree with cousins" + +[f41d5eeb-8973-448f-a3b0-cc1e019a4193] +description = "Reroot a tree so that its root is the specified node. -> Errors if target does not exist in a singleton tree" + +[9dc0a8b3-df02-4267-9a41-693b6aff75e7] +description = "Reroot a tree so that its root is the specified node. -> Errors if target does not exist in a large tree" + +[02d1f1d9-428d-4395-b026-2db35ffa8f0a] +description = "Given two nodes, find the path between them -> Can find path to parent" + +[d0002674-fcfb-4cdc-9efa-bfc54e3c31b5] +description = "Given two nodes, find the path between them -> Can find path to sibling" + +[c9877cd1-0a69-40d4-b362-725763a5c38f] +description = "Given two nodes, find the path between them -> Can find path to cousin" + +[9fb17a82-2c14-4261-baa3-2f3f234ffa03] +description = "Given two nodes, find the path between them -> Can find path not involving root" + +[5124ed49-7845-46ad-bc32-97d5ac7451b2] +description = "Given two nodes, find the path between them -> Can find path from nodes other than x" + +[f52a183c-25cc-4c87-9fc9-0e7f81a5725c] +description = "Given two nodes, find the path between them -> Errors if destination does not exist" + +[f4fe18b9-b4a2-4bd5-a694-e179155c2149] +description = "Given two nodes, find the path between them -> Errors if source does not exist" diff --git a/exercises/practice/pov/Cargo.toml b/exercises/practice/pov/Cargo.toml new file mode 100644 index 000000000..bec58b6eb --- /dev/null +++ b/exercises/practice/pov/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pov" +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] + +[dev-dependencies] +pretty_assertions = "*" diff --git a/exercises/practice/pov/src/lib.rs b/exercises/practice/pov/src/lib.rs new file mode 100644 index 000000000..9efef18cd --- /dev/null +++ b/exercises/practice/pov/src/lib.rs @@ -0,0 +1,25 @@ +use std::fmt::Debug; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Tree { + remove_this: std::marker::PhantomData, +} + +impl Tree { + pub fn new(label: T) -> Self { + todo!("Create a new tree with the given {label:?}"); + } + + /// Builder-method for constructing a tree with children + pub fn with_child(self, child: Self) -> Self { + todo!("Add {child:?} to the tree and return the tree"); + } + + pub fn pov_from(&mut self, from: &T) -> bool { + todo!("Reparent the tree with the label {from:?} as root"); + } + + pub fn path_between<'a>(&'a mut self, from: &'a T, to: &'a T) -> Option> { + todo!("Return the shortest path between {from:?} and {to:?}"); + } +} diff --git a/exercises/practice/pov/tests/pov.rs b/exercises/practice/pov/tests/pov.rs new file mode 100644 index 000000000..ab4d6577b --- /dev/null +++ b/exercises/practice/pov/tests/pov.rs @@ -0,0 +1,277 @@ +/// Forbid implementations that rely on Clone. +mod no_clone { + use pov::*; + + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] + struct NotClone; + + #[test] + fn doesnt_rely_on_clone() { + let mut tree = Tree::new(NotClone); + assert!(tree.pov_from(&NotClone)); + } +} + +/// Equality on trees must be independent of the order of children. +mod equality { + use pov::*; + use pretty_assertions::assert_eq; + + #[test] + #[ignore] + fn equality_is_order_independent() { + let a = Tree::new("root") + .with_child(Tree::new("left")) + .with_child(Tree::new("right")); + let b = Tree::new("root") + .with_child(Tree::new("right")) + .with_child(Tree::new("left")); + assert_eq!(a, b); + } +} + +/// Reroot a tree so that its root is the specified node. +mod from_pov { + use pov::*; + use pretty_assertions::assert_eq; + + #[test] + #[ignore] + fn results_in_the_same_tree_if_the_input_tree_is_a_singleton() { + let mut tree = Tree::new("x"); + assert!(tree.pov_from(&"x")); + let expected = Tree::new("x"); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn can_reroot_a_tree_with_a_parent_and_one_sibling() { + let mut tree = Tree::new("parent") + .with_child(Tree::new("x")) + .with_child(Tree::new("sibling")); + assert!(tree.pov_from(&"x")); + let expected = + Tree::new("x").with_child(Tree::new("parent").with_child(Tree::new("sibling"))); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn can_reroot_a_tree_with_a_parent_and_many_siblings() { + let mut tree = Tree::new("parent") + .with_child(Tree::new("a")) + .with_child(Tree::new("x")) + .with_child(Tree::new("b")) + .with_child(Tree::new("c")); + assert!(tree.pov_from(&"x")); + let expected = Tree::new("x").with_child( + Tree::new("parent") + .with_child(Tree::new("a")) + .with_child(Tree::new("b")) + .with_child(Tree::new("c")), + ); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn can_reroot_a_tree_with_new_root_deeply_nested_in_tree() { + let mut tree = Tree::new("level-0").with_child(Tree::new("level-1").with_child( + Tree::new("level-2").with_child(Tree::new("level-3").with_child(Tree::new("x"))), + )); + assert!(tree.pov_from(&"x")); + let expected = Tree::new("x").with_child(Tree::new("level-3").with_child( + Tree::new("level-2").with_child(Tree::new("level-1").with_child(Tree::new("level-0"))), + )); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn moves_children_of_the_new_root_to_same_level_as_former_parent() { + let mut tree = Tree::new("parent").with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ); + assert!(tree.pov_from(&"x")); + let expected = Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")) + .with_child(Tree::new("parent")); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn can_reroot_a_complex_tree_with_cousins() { + let mut tree = Tree::new("grandparent") + .with_child( + Tree::new("parent") + .with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")), + ) + .with_child( + Tree::new("uncle") + .with_child(Tree::new("cousin-0")) + .with_child(Tree::new("cousin-1")), + ); + assert!(tree.pov_from(&"x")); + let expected = Tree::new("x") + .with_child(Tree::new("kid-1")) + .with_child(Tree::new("kid-0")) + .with_child( + Tree::new("parent") + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")) + .with_child( + Tree::new("grandparent").with_child( + Tree::new("uncle") + .with_child(Tree::new("cousin-0")) + .with_child(Tree::new("cousin-1")), + ), + ), + ); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn errors_if_target_does_not_exist_in_a_singleton_tree() { + let mut tree = Tree::new("x"); + assert!(!tree.pov_from(&"nonexistent")); + } + + #[test] + #[ignore] + fn errors_if_target_does_not_exist_in_a_large_tree() { + let mut tree = Tree::new("parent") + .with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")); + assert!(!tree.pov_from(&"nonexistent")); + } +} + +/// Given two nodes, find the path between them +mod path_to { + use pov::*; + use pretty_assertions::assert_eq; + + #[test] + #[ignore] + fn can_find_path_to_parent() { + let mut tree = Tree::new("parent") + .with_child(Tree::new("x")) + .with_child(Tree::new("sibling")); + let result = tree.path_between(&"x", &"parent"); + let expected = Some(vec![&"x", &"parent"]); + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn can_find_path_to_sibling() { + let mut tree = Tree::new("parent") + .with_child(Tree::new("a")) + .with_child(Tree::new("x")) + .with_child(Tree::new("b")) + .with_child(Tree::new("c")); + let result = tree.path_between(&"x", &"b"); + let expected = Some(vec![&"x", &"parent", &"b"]); + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn can_find_path_to_cousin() { + let mut tree = Tree::new("grandparent") + .with_child( + Tree::new("parent") + .with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")), + ) + .with_child( + Tree::new("uncle") + .with_child(Tree::new("cousin-0")) + .with_child(Tree::new("cousin-1")), + ); + let result = tree.path_between(&"x", &"cousin-1"); + let expected = Some(vec![&"x", &"parent", &"grandparent", &"uncle", &"cousin-1"]); + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn can_find_path_not_involving_root() { + let mut tree = Tree::new("grandparent").with_child( + Tree::new("parent") + .with_child(Tree::new("x")) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")), + ); + let result = tree.path_between(&"x", &"sibling-1"); + let expected = Some(vec![&"x", &"parent", &"sibling-1"]); + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn can_find_path_from_nodes_other_than_x() { + let mut tree = Tree::new("parent") + .with_child(Tree::new("a")) + .with_child(Tree::new("x")) + .with_child(Tree::new("b")) + .with_child(Tree::new("c")); + let result = tree.path_between(&"a", &"c"); + let expected = Some(vec![&"a", &"parent", &"c"]); + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn errors_if_destination_does_not_exist() { + let mut tree = Tree::new("parent") + .with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")); + let result = tree.path_between(&"x", &"nonexistent"); + let expected: Option> = None; + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn errors_if_source_does_not_exist() { + let mut tree = Tree::new("parent") + .with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")); + let result = tree.path_between(&"nonexistent", &"x"); + let expected: Option> = None; + assert_eq!(result, expected); + } +} diff --git a/exercises/practice/prime-factors/.docs/instructions.md b/exercises/practice/prime-factors/.docs/instructions.md index b5cb1657e..252cc8ee1 100644 --- a/exercises/practice/prime-factors/.docs/instructions.md +++ b/exercises/practice/prime-factors/.docs/instructions.md @@ -10,21 +10,27 @@ Note that 1 is not a prime number. What are the prime factors of 60? -- Our first divisor is 2. 2 goes into 60, leaving 30. +- Our first divisor is 2. + 2 goes into 60, leaving 30. - 2 goes into 30, leaving 15. - - 2 doesn't go cleanly into 15. So let's move on to our next divisor, 3. + - 2 doesn't go cleanly into 15. + So let's move on to our next divisor, 3. - 3 goes cleanly into 15, leaving 5. - - 3 does not go cleanly into 5. The next possible factor is 4. - - 4 does not go cleanly into 5. The next possible factor is 5. + - 3 does not go cleanly into 5. + The next possible factor is 4. + - 4 does not go cleanly into 5. + The next possible factor is 5. - 5 does go cleanly into 5. - We're left only with 1, so now, we're done. -Our successful divisors in that computation represent the list of prime -factors of 60: 2, 2, 3, and 5. +Our successful divisors in that computation represent the list of prime factors of 60: 2, 2, 3, and 5. You can check this yourself: -- 2 * 2 * 3 * 5 -- = 4 * 15 -- = 60 -- Success! +```text +2 * 2 * 3 * 5 += 4 * 15 += 60 +``` + +Success! diff --git a/exercises/practice/prime-factors/.gitignore b/exercises/practice/prime-factors/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/prime-factors/.gitignore +++ b/exercises/practice/prime-factors/.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/prime-factors/.meta/config.json b/exercises/practice/prime-factors/.meta/config.json index 79d265ace..d8ab1c7f5 100644 --- a/exercises/practice/prime-factors/.meta/config.json +++ b/exercises/practice/prime-factors/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/prime-factors.rs" + "tests/prime_factors.rs" ], "example": [ ".meta/example.rs" @@ -32,5 +32,5 @@ }, "blurb": "Compute the prime factors of a given natural number.", "source": "The Prime Factors Kata by Uncle Bob", - "source_url": "http://butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata" + "source_url": "https://web.archive.org/web/20221026171801/http://butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata" } diff --git a/exercises/practice/prime-factors/.meta/example.rs b/exercises/practice/prime-factors/.meta/example.rs index e8c6245da..52ab60979 100644 --- a/exercises/practice/prime-factors/.meta/example.rs +++ b/exercises/practice/prime-factors/.meta/example.rs @@ -3,7 +3,7 @@ pub fn factors(n: u64) -> Vec { let mut out: Vec = vec![]; let mut possible: u64 = 2; while val > 1 { - while val % possible == 0 { + while val.is_multiple_of(possible) { out.push(possible); val /= possible; } diff --git a/exercises/practice/prime-factors/.meta/test_template.tera b/exercises/practice/prime-factors/.meta/test_template.tera new file mode 100644 index 000000000..0c80353ba --- /dev/null +++ b/exercises/practice/prime-factors/.meta/test_template.tera @@ -0,0 +1,13 @@ +use prime_factors::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let factors = factors({{ test.input.value | fmt_num }}); + let expected = [{% for factor in test.expected -%} + {{ factor | fmt_num }}, + {%- endfor %}]; + assert_eq!(factors, expected); +} +{% endfor -%} diff --git a/exercises/practice/prime-factors/.meta/tests.toml b/exercises/practice/prime-factors/.meta/tests.toml index be690e975..6f9cc8ced 100644 --- a/exercises/practice/prime-factors/.meta/tests.toml +++ b/exercises/practice/prime-factors/.meta/tests.toml @@ -1,3 +1,46 @@ -# 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. + +[924fc966-a8f5-4288-82f2-6b9224819ccd] +description = "no factors" + +[17e30670-b105-4305-af53-ddde182cb6ad] +description = "prime number" + +[238d57c8-4c12-42ef-af34-ae4929f94789] +description = "another prime number" + +[f59b8350-a180-495a-8fb1-1712fbee1158] +description = "square of a prime" + +[756949d3-3158-4e3d-91f2-c4f9f043ee70] +description = "product of first prime" + +[bc8c113f-9580-4516-8669-c5fc29512ceb] +description = "cube of a prime" + +[7d6a3300-a4cb-4065-bd33-0ced1de6cb44] +description = "product of second prime" + +[073ac0b2-c915-4362-929d-fc45f7b9a9e4] +description = "product of third prime" + +[6e0e4912-7fb6-47f3-a9ad-dbcd79340c75] +description = "product of first and second prime" + +[00485cd3-a3fe-4fbe-a64a-a4308fc1f870] +description = "product of primes and non-primes" + +[02251d54-3ca1-4a9b-85e1-b38f4b0ccb91] +description = "product of primes" + +[070cf8dc-e202-4285-aa37-8d775c9cd473] +description = "factors include a large prime" diff --git a/exercises/practice/prime-factors/Cargo.toml b/exercises/practice/prime-factors/Cargo.toml index 18940fcd9..0c8df82f4 100644 --- a/exercises/practice/prime-factors/Cargo.toml +++ b/exercises/practice/prime-factors/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "prime_factors" -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/prime-factors/src/lib.rs b/exercises/practice/prime-factors/src/lib.rs index a2ce597a3..30d06a2fc 100644 --- a/exercises/practice/prime-factors/src/lib.rs +++ b/exercises/practice/prime-factors/src/lib.rs @@ -1,3 +1,3 @@ pub fn factors(n: u64) -> Vec { - unimplemented!("This should calculate the prime factors of {n}") + todo!("This should calculate the prime factors of {n}") } diff --git a/exercises/practice/prime-factors/tests/prime-factors.rs b/exercises/practice/prime-factors/tests/prime-factors.rs deleted file mode 100644 index 3c17ec50a..000000000 --- a/exercises/practice/prime-factors/tests/prime-factors.rs +++ /dev/null @@ -1,42 +0,0 @@ -use prime_factors::factors; - -#[test] -fn test_no_factors() { - assert_eq!(factors(1), vec![]); -} - -#[test] -#[ignore] -fn test_prime_number() { - assert_eq!(factors(2), vec![2]); -} - -#[test] -#[ignore] -fn test_square_of_a_prime() { - assert_eq!(factors(9), vec![3, 3]); -} - -#[test] -#[ignore] -fn test_cube_of_a_prime() { - assert_eq!(factors(8), vec![2, 2, 2]); -} - -#[test] -#[ignore] -fn test_product_of_primes_and_non_primes() { - assert_eq!(factors(12), vec![2, 2, 3]); -} - -#[test] -#[ignore] -fn test_product_of_primes() { - assert_eq!(factors(901_255), vec![5, 17, 23, 461]); -} - -#[test] -#[ignore] -fn test_factors_include_large_prime() { - assert_eq!(factors(93_819_012_551), vec![11, 9539, 894_119]); -} diff --git a/exercises/practice/prime-factors/tests/prime_factors.rs b/exercises/practice/prime-factors/tests/prime_factors.rs new file mode 100644 index 000000000..119c9c8a9 --- /dev/null +++ b/exercises/practice/prime-factors/tests/prime_factors.rs @@ -0,0 +1,96 @@ +use prime_factors::*; + +#[test] +fn no_factors() { + let factors = factors(1); + let expected = []; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn prime_number() { + let factors = factors(2); + let expected = [2]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn another_prime_number() { + let factors = factors(3); + let expected = [3]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn square_of_a_prime() { + let factors = factors(9); + let expected = [3, 3]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn product_of_first_prime() { + let factors = factors(4); + let expected = [2, 2]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn cube_of_a_prime() { + let factors = factors(8); + let expected = [2, 2, 2]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn product_of_second_prime() { + let factors = factors(27); + let expected = [3, 3, 3]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn product_of_third_prime() { + let factors = factors(625); + let expected = [5, 5, 5, 5]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn product_of_first_and_second_prime() { + let factors = factors(6); + let expected = [2, 3]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn product_of_primes_and_non_primes() { + let factors = factors(12); + let expected = [2, 2, 3]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn product_of_primes() { + let factors = factors(901_255); + let expected = [5, 17, 23, 461]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn factors_include_a_large_prime() { + let factors = factors(93_819_012_551); + let expected = [11, 9_539, 894_119]; + assert_eq!(factors, expected); +} diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md index c211345ed..35c953b11 100644 --- a/exercises/practice/protein-translation/.docs/instructions.md +++ b/exercises/practice/protein-translation/.docs/instructions.md @@ -1,42 +1,38 @@ # Instructions -Translate RNA sequences into proteins. +Your job is to translate RNA sequences into proteins. -RNA can be broken into three nucleotide sequences called codons, and then translated to a polypeptide like so: +RNA strands are made up of three-nucleotide sequences called **codons**. +Each codon translates to an **amino acid**. +When joined together, those amino acids make a protein. -RNA: `"AUGUUUUCU"` => translates to +In the real world, there are 64 codons, which in turn correspond to 20 amino acids. +However, for this exercise, you’ll only use a few of the possible 64. +They are listed below: -Codons: `"AUG", "UUU", "UCU"` -=> which become a polypeptide with the following sequence => +| Codon | Amino Acid | +| ------------------ | ------------- | +| AUG | Methionine | +| UUU, UUC | Phenylalanine | +| UUA, UUG | Leucine | +| UCU, UCC, UCA, UCG | Serine | +| UAU, UAC | Tyrosine | +| UGU, UGC | Cysteine | +| UGG | Tryptophan | +| UAA, UAG, UGA | STOP | -Protein: `"Methionine", "Phenylalanine", "Serine"` +For example, the RNA string “AUGUUUUCU” has three codons: “AUG”, “UUU” and “UCU”. +These map to Methionine, Phenylalanine, and Serine. -There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise. If it works for one codon, the program should work for all of them. -However, feel free to expand the list in the test suite to include them all. +## “STOP” Codons -There are also three terminating codons (also known as 'STOP' codons); if any of these codons are encountered (by the ribosome), all translation ends and the protein is terminated. +You’ll note from the table above that there are three **“STOP” codons**. +If you encounter any of these codons, ignore the rest of the sequence — the protein is complete. -All subsequent codons after are ignored, like this: +For example, “AUGUUUUCUUAAAUG” contains a STOP codon (“UAA”). +Once we reach that point, we stop processing. +We therefore only consider the part before it (i.e. “AUGUUUUCU”), not any further codons after it (i.e. “AUG”). -RNA: `"AUGUUUUCUUAAAUG"` => +Learn more about [protein translation on Wikipedia][protein-translation]. -Codons: `"AUG", "UUU", "UCU", "UAA", "AUG"` => - -Protein: `"Methionine", "Phenylalanine", "Serine"` - -Note the stop codon `"UAA"` terminates the translation and the final methionine is not translated into the protein sequence. - -Below are the codons and resulting Amino Acids needed for the exercise. - -Codon | Protein -:--- | :--- -AUG | Methionine -UUU, UUC | Phenylalanine -UUA, UUG | Leucine -UCU, UCC, UCA, UCG | Serine -UAU, UAC | Tyrosine -UGU, UGC | Cysteine -UGG | Tryptophan -UAA, UAG, UGA | STOP - -Learn more about [protein translation on Wikipedia](http://en.wikipedia.org/wiki/Translation_(biology)) +[protein-translation]: https://en.wikipedia.org/wiki/Translation_(biology) diff --git a/exercises/practice/protein-translation/.gitignore b/exercises/practice/protein-translation/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/protein-translation/.gitignore +++ b/exercises/practice/protein-translation/.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/protein-translation/.meta/config.json b/exercises/practice/protein-translation/.meta/config.json index ca68e8386..e89173fd0 100644 --- a/exercises/practice/protein-translation/.meta/config.json +++ b/exercises/practice/protein-translation/.meta/config.json @@ -26,7 +26,7 @@ "Cargo.toml" ], "test": [ - "tests/protein-translation.rs" + "tests/protein_translation.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/protein-translation/.meta/example.rs b/exercises/practice/protein-translation/.meta/example.rs index 38d7da580..c64ef5ff4 100644 --- a/exercises/practice/protein-translation/.meta/example.rs +++ b/exercises/practice/protein-translation/.meta/example.rs @@ -1,27 +1,17 @@ -use std::collections::HashMap; - -pub struct CodonsInfo<'a> { - actual_codons: HashMap<&'a str, &'a str>, -} - -pub fn parse<'a>(pairs: Vec<(&'a str, &'a str)>) -> CodonsInfo<'a> { - CodonsInfo { - actual_codons: pairs.into_iter().collect(), - } -} - -impl<'a> CodonsInfo<'a> { - pub fn name_for(&self, codon: &str) -> Option<&'a str> { - self.actual_codons.get(&codon).cloned() - } - - pub fn of_rna(&self, strand: &str) -> Option> { - strand - .chars() - .collect::>() - .chunks(3) - .map(|chars| self.name_for(&chars.iter().collect::())) - .take_while(|result| result.is_none() || result.unwrap() != "stop codon") - .collect() - } +pub fn translate(rna: &str) -> Option> { + rna.as_bytes() + .chunks(3) + .map(|codon| match codon { + b"AUG" => Some("Methionine"), + b"UUU" | b"UUC" => Some("Phenylalanine"), + b"UUA" | b"UUG" => Some("Leucine"), + b"UCU" | b"UCC" | b"UCA" | b"UCG" => Some("Serine"), + b"UAU" | b"UAC" => Some("Tyrosine"), + b"UGU" | b"UGC" => Some("Cysteine"), + b"UGG" => Some("Tryptophan"), + b"UAA" | b"UAG" | b"UGA" => Some("STOP"), + _ => None, + }) + .take_while(|result| result.is_none() || result.unwrap() != "STOP") + .collect() } diff --git a/exercises/practice/protein-translation/.meta/test_template.tera b/exercises/practice/protein-translation/.meta/test_template.tera new file mode 100644 index 000000000..5b10a3706 --- /dev/null +++ b/exercises/practice/protein-translation/.meta/test_template.tera @@ -0,0 +1,20 @@ +use protein_translation::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert_eq!( + translate({{ test.input.strand | json_encode() }}), + {% if test.expected is object %} + None + {% else %} + Some(vec![ + {% for s in test.expected %} + "{{ s }}" {% if not loop.last %} , {% endif %} + {% endfor %} + ]) + {% endif %}, + ); +} +{% endfor -%} diff --git a/exercises/practice/protein-translation/.meta/tests.toml b/exercises/practice/protein-translation/.meta/tests.toml index be690e975..de680e39e 100644 --- a/exercises/practice/protein-translation/.meta/tests.toml +++ b/exercises/practice/protein-translation/.meta/tests.toml @@ -1,3 +1,105 @@ -# 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. + +[2c44f7bf-ba20-43f7-a3bf-f2219c0c3f98] +description = "Empty RNA sequence results in no proteins" + +[96d3d44f-34a2-4db4-84cd-fff523e069be] +description = "Methionine RNA sequence" + +[1b4c56d8-d69f-44eb-be0e-7b17546143d9] +description = "Phenylalanine RNA sequence 1" + +[81b53646-bd57-4732-b2cb-6b1880e36d11] +description = "Phenylalanine RNA sequence 2" + +[42f69d4f-19d2-4d2c-a8b0-f0ae9ee1b6b4] +description = "Leucine RNA sequence 1" + +[ac5edadd-08ed-40a3-b2b9-d82bb50424c4] +description = "Leucine RNA sequence 2" + +[8bc36e22-f984-44c3-9f6b-ee5d4e73f120] +description = "Serine RNA sequence 1" + +[5c3fa5da-4268-44e5-9f4b-f016ccf90131] +description = "Serine RNA sequence 2" + +[00579891-b594-42b4-96dc-7ff8bf519606] +description = "Serine RNA sequence 3" + +[08c61c3b-fa34-4950-8c4a-133945570ef6] +description = "Serine RNA sequence 4" + +[54e1e7d8-63c0-456d-91d2-062c72f8eef5] +description = "Tyrosine RNA sequence 1" + +[47bcfba2-9d72-46ad-bbce-22f7666b7eb1] +description = "Tyrosine RNA sequence 2" + +[3a691829-fe72-43a7-8c8e-1bd083163f72] +description = "Cysteine RNA sequence 1" + +[1b6f8a26-ca2f-43b8-8262-3ee446021767] +description = "Cysteine RNA sequence 2" + +[1e91c1eb-02c0-48a0-9e35-168ad0cb5f39] +description = "Tryptophan RNA sequence" + +[e547af0b-aeab-49c7-9f13-801773a73557] +description = "STOP codon RNA sequence 1" + +[67640947-ff02-4f23-a2ef-816f8a2ba72e] +description = "STOP codon RNA sequence 2" + +[9c2ad527-ebc9-4ace-808b-2b6447cb54cb] +description = "STOP codon RNA sequence 3" + +[f4d9d8ee-00a8-47bf-a1e3-1641d4428e54] +description = "Sequence of two protein codons translates into proteins" + +[dd22eef3-b4f1-4ad6-bb0b-27093c090a9d] +description = "Sequence of two different protein codons translates into proteins" + +[d0f295df-fb70-425c-946c-ec2ec185388e] +description = "Translate RNA strand into correct protein list" + +[e30e8505-97ec-4e5f-a73e-5726a1faa1f4] +description = "Translation stops if STOP codon at beginning of sequence" + +[5358a20b-6f4c-4893-bce4-f929001710f3] +description = "Translation stops if STOP codon at end of two-codon sequence" + +[ba16703a-1a55-482f-bb07-b21eef5093a3] +description = "Translation stops if STOP codon at end of three-codon sequence" + +[4089bb5a-d5b4-4e71-b79e-b8d1f14a2911] +description = "Translation stops if STOP codon in middle of three-codon sequence" + +[2c2a2a60-401f-4a80-b977-e0715b23b93d] +description = "Translation stops if STOP codon in middle of six-codon sequence" + +[f6f92714-769f-4187-9524-e353e8a41a80] +description = "Sequence of two non-STOP codons does not translate to a STOP codon" + +[1e75ea2a-f907-4994-ae5c-118632a1cb0f] +description = "Non-existing codon can't translate" +include = false + +[9eac93f3-627a-4c90-8653-6d0a0595bc6f] +description = "Unknown amino acids, not part of a codon, can't translate" +reimplements = "1e75ea2a-f907-4994-ae5c-118632a1cb0f" + +[9d73899f-e68e-4291-b1e2-7bf87c00f024] +description = "Incomplete RNA sequence can't translate" + +[43945cf7-9968-402d-ab9f-b8a28750b050] +description = "Incomplete RNA sequence can translate if valid until a STOP codon" diff --git a/exercises/practice/protein-translation/Cargo.toml b/exercises/practice/protein-translation/Cargo.toml index ce59d44a8..82be0604b 100644 --- a/exercises/practice/protein-translation/Cargo.toml +++ b/exercises/practice/protein-translation/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "protein-translation" +name = "protein_translation" 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/protein-translation/src/lib.rs b/exercises/practice/protein-translation/src/lib.rs index 10fac37a7..b5ba6a902 100644 --- a/exercises/practice/protein-translation/src/lib.rs +++ b/exercises/practice/protein-translation/src/lib.rs @@ -1,21 +1,5 @@ -pub struct CodonsInfo<'a> { - // We fake using 'a here, so the compiler does not complain that - // "parameter `'a` is never used". Delete when no longer needed. - phantom: std::marker::PhantomData<&'a ()>, -} - -impl<'a> CodonsInfo<'a> { - pub fn name_for(&self, codon: &str) -> Option<&'a str> { - unimplemented!( - "Return the protein name for a '{codon}' codon or None, if codon string is invalid" - ); - } - - pub fn of_rna(&self, rna: &str) -> Option> { - unimplemented!("Return a list of protein names that correspond to the '{rna}' RNA string or None if the RNA string is invalid"); - } -} - -pub fn parse<'a>(pairs: Vec<(&'a str, &'a str)>) -> CodonsInfo<'a> { - unimplemented!("Construct a new CodonsInfo struct from given pairs: {pairs:?}"); +pub fn translate(rna: &str) -> Option> { + todo!( + "Return a list of protein names that correspond to the '{rna}' RNA string or None if the RNA string is invalid" + ); } diff --git a/exercises/practice/protein-translation/tests/protein-translation.rs b/exercises/practice/protein-translation/tests/protein-translation.rs deleted file mode 100644 index 828efab00..000000000 --- a/exercises/practice/protein-translation/tests/protein-translation.rs +++ /dev/null @@ -1,157 +0,0 @@ -use protein_translation as proteins; - -#[test] -fn test_methionine() { - let info = proteins::parse(make_pairs()); - assert_eq!(info.name_for("AUG"), Some("methionine")); -} - -#[test] -#[ignore] -fn test_cysteine_tgt() { - let info = proteins::parse(make_pairs()); - assert_eq!(info.name_for("UGU"), Some("cysteine")); -} - -#[test] -#[ignore] -fn test_stop() { - let info = proteins::parse(make_pairs()); - assert_eq!(info.name_for("UAA"), Some("stop codon")); -} - -#[test] -#[ignore] -fn test_valine() { - let info = proteins::parse(make_pairs()); - assert_eq!(info.name_for("GUU"), Some("valine")); -} - -#[test] -#[ignore] -fn test_isoleucine() { - let info = proteins::parse(make_pairs()); - assert_eq!(info.name_for("AUU"), Some("isoleucine")); -} - -#[test] -#[ignore] -fn test_arginine_name() { - let info = proteins::parse(make_pairs()); - assert_eq!(info.name_for("CGA"), Some("arginine")); - assert_eq!(info.name_for("AGA"), Some("arginine")); - assert_eq!(info.name_for("AGG"), Some("arginine")); -} - -#[test] -#[ignore] -fn empty_is_invalid() { - let info = proteins::parse(make_pairs()); - assert!(info.name_for("").is_none()); -} - -#[test] -#[ignore] -fn x_is_not_shorthand_so_is_invalid() { - let info = proteins::parse(make_pairs()); - assert!(info.name_for("VWX").is_none()); -} - -#[test] -#[ignore] -fn too_short_is_invalid() { - let info = proteins::parse(make_pairs()); - assert!(info.name_for("AU").is_none()); -} - -#[test] -#[ignore] -fn too_long_is_invalid() { - let info = proteins::parse(make_pairs()); - assert!(info.name_for("ATTA").is_none()); -} - -#[test] -#[ignore] -fn test_translates_rna_strand_into_correct_protein() { - let info = proteins::parse(make_pairs()); - assert_eq!( - info.of_rna("AUGUUUUGG"), - Some(vec!["methionine", "phenylalanine", "tryptophan"]) - ); -} - -#[test] -#[ignore] -fn test_stops_translation_if_stop_codon_present() { - let info = proteins::parse(make_pairs()); - assert_eq!( - info.of_rna("AUGUUUUAA"), - Some(vec!["methionine", "phenylalanine"]) - ); -} - -#[test] -#[ignore] -fn test_stops_translation_of_longer_strand() { - let info = proteins::parse(make_pairs()); - assert_eq!( - info.of_rna("UGGUGUUAUUAAUGGUUU"), - Some(vec!["tryptophan", "cysteine", "tyrosine"]) - ); -} - -#[test] -#[ignore] -fn test_invalid_codons() { - let info = proteins::parse(make_pairs()); - assert!(info.of_rna("CARROT").is_none()); -} - -#[test] -#[ignore] -fn test_invalid_length() { - let info = proteins::parse(make_pairs()); - assert!(info.of_rna("AUGUA").is_none()); -} - -#[test] -#[ignore] -fn test_valid_stopped_rna() { - let info = proteins::parse(make_pairs()); - assert_eq!(info.of_rna("AUGUAAASDF"), Some(vec!["methionine"])); -} - -// The input data constructor. Returns a list of codon, name pairs. -fn make_pairs() -> Vec<(&'static str, &'static str)> { - let grouped = vec![ - ("isoleucine", vec!["AUU", "AUC", "AUA"]), - ("valine", vec!["GUU", "GUC", "GUA", "GUG"]), - ("phenylalanine", vec!["UUU", "UUC"]), - ("methionine", vec!["AUG"]), - ("cysteine", vec!["UGU", "UGC"]), - ("alanine", vec!["GCU", "GCC", "GCA", "GCG"]), - ("glycine", vec!["GGU", "GGC", "GGA", "GGG"]), - ("proline", vec!["CCU", "CCC", "CCA", "CCG"]), - ("threonine", vec!["ACU", "ACC", "ACA", "ACG"]), - ("serine", vec!["AGU", "AGC"]), - ("tyrosine", vec!["UAU", "UAC"]), - ("tryptophan", vec!["UGG"]), - ("glutamine", vec!["CAA", "CAG"]), - ("asparagine", vec!["AAU", "AAC"]), - ("histidine", vec!["CAU", "CAC"]), - ("glutamic acid", vec!["GAA", "GAG"]), - ("aspartic acid", vec!["GAU", "GAC"]), - ("lysine", vec!["AAA", "AAG"]), - ("arginine", vec!["CGU", "CGC", "CGA", "CGG", "AGA", "AGG"]), - ("stop codon", vec!["UAA", "UAG", "UGA"]), - ]; - let mut pairs = Vec::<(&'static str, &'static str)>::new(); - for (name, codons) in grouped.into_iter() { - for codon in codons { - pairs.push((codon, name)); - } - } - pairs.sort_by(|&(_, a), &(_, b)| a.cmp(b)); - pairs -} diff --git a/exercises/practice/protein-translation/tests/protein_translation.rs b/exercises/practice/protein-translation/tests/protein_translation.rs new file mode 100644 index 000000000..7ac612ba1 --- /dev/null +++ b/exercises/practice/protein-translation/tests/protein_translation.rs @@ -0,0 +1,195 @@ +use protein_translation::*; + +#[test] +fn empty_rna_sequence_results_in_no_proteins() { + assert_eq!(translate(""), Some(vec![]),); +} + +#[test] +#[ignore] +fn methionine_rna_sequence() { + assert_eq!(translate("AUG"), Some(vec!["Methionine"]),); +} + +#[test] +#[ignore] +fn phenylalanine_rna_sequence_1() { + assert_eq!(translate("UUU"), Some(vec!["Phenylalanine"]),); +} + +#[test] +#[ignore] +fn phenylalanine_rna_sequence_2() { + assert_eq!(translate("UUC"), Some(vec!["Phenylalanine"]),); +} + +#[test] +#[ignore] +fn leucine_rna_sequence_1() { + assert_eq!(translate("UUA"), Some(vec!["Leucine"]),); +} + +#[test] +#[ignore] +fn leucine_rna_sequence_2() { + assert_eq!(translate("UUG"), Some(vec!["Leucine"]),); +} + +#[test] +#[ignore] +fn serine_rna_sequence_1() { + assert_eq!(translate("UCU"), Some(vec!["Serine"]),); +} + +#[test] +#[ignore] +fn serine_rna_sequence_2() { + assert_eq!(translate("UCC"), Some(vec!["Serine"]),); +} + +#[test] +#[ignore] +fn serine_rna_sequence_3() { + assert_eq!(translate("UCA"), Some(vec!["Serine"]),); +} + +#[test] +#[ignore] +fn serine_rna_sequence_4() { + assert_eq!(translate("UCG"), Some(vec!["Serine"]),); +} + +#[test] +#[ignore] +fn tyrosine_rna_sequence_1() { + assert_eq!(translate("UAU"), Some(vec!["Tyrosine"]),); +} + +#[test] +#[ignore] +fn tyrosine_rna_sequence_2() { + assert_eq!(translate("UAC"), Some(vec!["Tyrosine"]),); +} + +#[test] +#[ignore] +fn cysteine_rna_sequence_1() { + assert_eq!(translate("UGU"), Some(vec!["Cysteine"]),); +} + +#[test] +#[ignore] +fn cysteine_rna_sequence_2() { + assert_eq!(translate("UGC"), Some(vec!["Cysteine"]),); +} + +#[test] +#[ignore] +fn tryptophan_rna_sequence() { + assert_eq!(translate("UGG"), Some(vec!["Tryptophan"]),); +} + +#[test] +#[ignore] +fn stop_codon_rna_sequence_1() { + assert_eq!(translate("UAA"), Some(vec![]),); +} + +#[test] +#[ignore] +fn stop_codon_rna_sequence_2() { + assert_eq!(translate("UAG"), Some(vec![]),); +} + +#[test] +#[ignore] +fn stop_codon_rna_sequence_3() { + assert_eq!(translate("UGA"), Some(vec![]),); +} + +#[test] +#[ignore] +fn sequence_of_two_protein_codons_translates_into_proteins() { + assert_eq!( + translate("UUUUUU"), + Some(vec!["Phenylalanine", "Phenylalanine"]), + ); +} + +#[test] +#[ignore] +fn sequence_of_two_different_protein_codons_translates_into_proteins() { + assert_eq!(translate("UUAUUG"), Some(vec!["Leucine", "Leucine"]),); +} + +#[test] +#[ignore] +fn translate_rna_strand_into_correct_protein_list() { + assert_eq!( + translate("AUGUUUUGG"), + Some(vec!["Methionine", "Phenylalanine", "Tryptophan"]), + ); +} + +#[test] +#[ignore] +fn translation_stops_if_stop_codon_at_beginning_of_sequence() { + assert_eq!(translate("UAGUGG"), Some(vec![]),); +} + +#[test] +#[ignore] +fn translation_stops_if_stop_codon_at_end_of_two_codon_sequence() { + assert_eq!(translate("UGGUAG"), Some(vec!["Tryptophan"]),); +} + +#[test] +#[ignore] +fn translation_stops_if_stop_codon_at_end_of_three_codon_sequence() { + assert_eq!( + translate("AUGUUUUAA"), + Some(vec!["Methionine", "Phenylalanine"]), + ); +} + +#[test] +#[ignore] +fn translation_stops_if_stop_codon_in_middle_of_three_codon_sequence() { + assert_eq!(translate("UGGUAGUGG"), Some(vec!["Tryptophan"]),); +} + +#[test] +#[ignore] +fn translation_stops_if_stop_codon_in_middle_of_six_codon_sequence() { + assert_eq!( + translate("UGGUGUUAUUAAUGGUUU"), + Some(vec!["Tryptophan", "Cysteine", "Tyrosine"]), + ); +} + +#[test] +#[ignore] +fn sequence_of_two_non_stop_codons_does_not_translate_to_a_stop_codon() { + assert_eq!(translate("AUGAUG"), Some(vec!["Methionine", "Methionine"]),); +} + +#[test] +#[ignore] +fn unknown_amino_acids_not_part_of_a_codon_can_t_translate() { + assert_eq!(translate("XYZ"), None,); +} + +#[test] +#[ignore] +fn incomplete_rna_sequence_can_t_translate() { + assert_eq!(translate("AUGU"), None,); +} + +#[test] +#[ignore] +fn incomplete_rna_sequence_can_translate_if_valid_until_a_stop_codon() { + assert_eq!( + translate("UUCUUCUAAUGGU"), + Some(vec!["Phenylalanine", "Phenylalanine"]), + ); +} diff --git a/exercises/practice/proverb/.docs/instructions.md b/exercises/practice/proverb/.docs/instructions.md index 0da9da2cb..f6fb85932 100644 --- a/exercises/practice/proverb/.docs/instructions.md +++ b/exercises/practice/proverb/.docs/instructions.md @@ -2,7 +2,8 @@ For want of a horseshoe nail, a kingdom was lost, or so the saying goes. -Given a list of inputs, generate the relevant proverb. For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme: +Given a list of inputs, generate the relevant proverb. +For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme: ```text For want of a nail the shoe was lost. @@ -14,4 +15,5 @@ For want of a battle the kingdom was lost. And all for the want of a nail. ``` -Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length (including zero elements, generating empty output) and content. No line of the output text should be a static, unchanging string; all should vary according to the input given. +Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length and content. +No line of the output text should be a static, unchanging string; all should vary according to the input given. diff --git a/exercises/practice/proverb/.gitignore b/exercises/practice/proverb/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/proverb/.gitignore +++ b/exercises/practice/proverb/.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/proverb/.meta/config.json b/exercises/practice/proverb/.meta/config.json index d3b955805..a2346dfa0 100644 --- a/exercises/practice/proverb/.meta/config.json +++ b/exercises/practice/proverb/.meta/config.json @@ -32,5 +32,5 @@ }, "blurb": "For want of a horseshoe nail, a kingdom was lost, or so the saying goes. Output the full text of this proverbial rhyme.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/For_Want_of_a_Nail" + "source_url": "https://en.wikipedia.org/wiki/For_Want_of_a_Nail" } diff --git a/exercises/practice/proverb/.meta/test_template.tera b/exercises/practice/proverb/.meta/test_template.tera new file mode 100644 index 000000000..3dbfb203d --- /dev/null +++ b/exercises/practice/proverb/.meta/test_template.tera @@ -0,0 +1,16 @@ +use proverb::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = &{{ test.input.strings | json_encode() }}; + let output = build_proverb(input); + {% if test.expected | length == 0 -%} + let expected = String::new(); + {%- else -%} + let expected: String = {{ test.expected | json_encode() }}.join("\n"); + {%- endif %} + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/proverb/.meta/tests.toml b/exercises/practice/proverb/.meta/tests.toml index be690e975..dc92a0c96 100644 --- a/exercises/practice/proverb/.meta/tests.toml +++ b/exercises/practice/proverb/.meta/tests.toml @@ -1,3 +1,28 @@ -# 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. + +[e974b73e-7851-484f-8d6d-92e07fe742fc] +description = "zero pieces" + +[2fcd5f5e-8b82-4e74-b51d-df28a5e0faa4] +description = "one piece" + +[d9d0a8a1-d933-46e2-aa94-eecf679f4b0e] +description = "two pieces" + +[c95ef757-5e94-4f0d-a6cb-d2083f5e5a83] +description = "three pieces" + +[433fb91c-35a2-4d41-aeab-4de1e82b2126] +description = "full proverb" + +[c1eefa5a-e8d9-41c7-91d4-99fab6d6b9f7] +description = "four pieces modernized" diff --git a/exercises/practice/proverb/Cargo.toml b/exercises/practice/proverb/Cargo.toml index eb3977050..3ea43d7a3 100644 --- a/exercises/practice/proverb/Cargo.toml +++ b/exercises/practice/proverb/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "proverb" -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/proverb/src/lib.rs b/exercises/practice/proverb/src/lib.rs index ec19e2686..56705813e 100644 --- a/exercises/practice/proverb/src/lib.rs +++ b/exercises/practice/proverb/src/lib.rs @@ -1,3 +1,3 @@ pub fn build_proverb(list: &[&str]) -> String { - unimplemented!("build a proverb from this list of items: {list:?}") + todo!("build a proverb from this list of items: {list:?}") } diff --git a/exercises/practice/proverb/tests/proverb.rs b/exercises/practice/proverb/tests/proverb.rs index fbf8e8cea..becf70be3 100644 --- a/exercises/practice/proverb/tests/proverb.rs +++ b/exercises/practice/proverb/tests/proverb.rs @@ -1,53 +1,57 @@ -use proverb::build_proverb; +use proverb::*; #[test] -fn test_two_pieces() { - let input = vec!["nail", "shoe"]; - let expected = vec![ +fn zero_pieces() { + let input = &[]; + let output = build_proverb(input); + let expected = String::new(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn one_piece() { + let input = &["nail"]; + let output = build_proverb(input); + let expected: String = ["And all for the want of a nail."].join("\n"); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn two_pieces() { + let input = &["nail", "shoe"]; + let output = build_proverb(input); + let expected: String = [ "For want of a nail the shoe was lost.", "And all for the want of a nail.", ] .join("\n"); - assert_eq!(build_proverb(&input), expected); + assert_eq!(output, expected); } -// Notice the change in the last line at three pieces. #[test] #[ignore] -fn test_three_pieces() { - let input = vec!["nail", "shoe", "horse"]; - let expected = vec![ +fn three_pieces() { + let input = &["nail", "shoe", "horse"]; + let output = build_proverb(input); + let expected: String = [ "For want of a nail the shoe was lost.", "For want of a shoe the horse was lost.", "And all for the want of a nail.", ] .join("\n"); - assert_eq!(build_proverb(&input), expected); -} - -#[test] -#[ignore] -fn test_one_piece() { - let input = vec!["nail"]; - let expected = String::from("And all for the want of a nail."); - assert_eq!(build_proverb(&input), expected); -} - -#[test] -#[ignore] -fn test_zero_pieces() { - let input: Vec<&str> = vec![]; - let expected = String::new(); - assert_eq!(build_proverb(&input), expected); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_full() { - let input = vec![ +fn full_proverb() { + let input = &[ "nail", "shoe", "horse", "rider", "message", "battle", "kingdom", ]; - let expected = vec![ + let output = build_proverb(input); + let expected: String = [ "For want of a nail the shoe was lost.", "For want of a shoe the horse was lost.", "For want of a horse the rider was lost.", @@ -57,19 +61,20 @@ fn test_full() { "And all for the want of a nail.", ] .join("\n"); - assert_eq!(build_proverb(&input), expected); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_three_pieces_modernized() { - let input = vec!["pin", "gun", "soldier", "battle"]; - let expected = vec![ +fn four_pieces_modernized() { + let input = &["pin", "gun", "soldier", "battle"]; + let output = build_proverb(input); + let expected: String = [ "For want of a pin the gun was lost.", "For want of a gun the soldier was lost.", "For want of a soldier the battle was lost.", "And all for the want of a pin.", ] .join("\n"); - assert_eq!(build_proverb(&input), expected); + assert_eq!(output, expected); } diff --git a/exercises/practice/pythagorean-triplet/.docs/instructions.md b/exercises/practice/pythagorean-triplet/.docs/instructions.md index 395ff6a55..ced833d7a 100644 --- a/exercises/practice/pythagorean-triplet/.docs/instructions.md +++ b/exercises/practice/pythagorean-triplet/.docs/instructions.md @@ -1,10 +1,9 @@ -# Instructions +# Description -A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for -which, +A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for which, ```text -a**2 + b**2 = c**2 +a² + b² = c² ``` and such that, @@ -16,7 +15,7 @@ a < b < c For example, ```text -3**2 + 4**2 = 9 + 16 = 25 = 5**2. +3² + 4² = 5². ``` Given an input integer N, find all Pythagorean triplets for which `a + b + c = N`. diff --git a/exercises/practice/pythagorean-triplet/.docs/introduction.md b/exercises/practice/pythagorean-triplet/.docs/introduction.md new file mode 100644 index 000000000..3453c6ed4 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.docs/introduction.md @@ -0,0 +1,19 @@ +# Introduction + +You are an accomplished problem-solver, known for your ability to tackle the most challenging mathematical puzzles. +One evening, you receive an urgent letter from an inventor called the Triangle Tinkerer, who is working on a groundbreaking new project. +The letter reads: + +> Dear Mathematician, +> +> I need your help. +> I am designing a device that relies on the unique properties of Pythagorean triplets — sets of three integers that satisfy the equation a² + b² = c². +> This device will revolutionize navigation, but for it to work, I must program it with every possible triplet where the sum of a, b, and c equals a specific number, N. +> Calculating these triplets by hand would take me years, but I hear you are more than up to the task. +> +> Time is of the essence. +> The future of my invention — and perhaps even the future of mathematical innovation — rests on your ability to solve this problem. + +Motivated by the importance of the task, you set out to find all Pythagorean triplets that satisfy the condition. +Your work could have far-reaching implications, unlocking new possibilities in science and engineering. +Can you rise to the challenge and make history? diff --git a/exercises/practice/pythagorean-triplet/.gitignore b/exercises/practice/pythagorean-triplet/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/pythagorean-triplet/.gitignore +++ b/exercises/practice/pythagorean-triplet/.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/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index 6adbaf21a..d73e02617 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -27,13 +27,13 @@ "Cargo.toml" ], "test": [ - "tests/pythagorean-triplet.rs" + "tests/pythagorean_triplet.rs" ], "example": [ ".meta/example.rs" ] }, - "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product a * b * c.", - "source": "Problem 9 at Project Euler", - "source_url": "http://projecteuler.net/problem=9" + "blurb": "Given an integer N, find all Pythagorean triplets for which a + b + c = N.", + "source": "A variation of Problem 9 from Project Euler", + "source_url": "https://projecteuler.net/problem=9" } diff --git a/exercises/practice/pythagorean-triplet/.meta/test_template.tera b/exercises/practice/pythagorean-triplet/.meta/test_template.tera new file mode 100644 index 000000000..904903c2c --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.meta/test_template.tera @@ -0,0 +1,18 @@ +use pythagorean_triplet::*; + +use std::collections::HashSet; +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.n | fmt_num }}; + let output = find(input); + let expected = [{% for triple in test.expected -%} + [{% for side in triple -%} + {{ side | fmt_num }}, + {%- endfor %}], + {%- endfor %}]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/pythagorean-triplet/.meta/tests.toml b/exercises/practice/pythagorean-triplet/.meta/tests.toml index be690e975..719620a97 100644 --- a/exercises/practice/pythagorean-triplet/.meta/tests.toml +++ b/exercises/practice/pythagorean-triplet/.meta/tests.toml @@ -1,3 +1,31 @@ -# 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. + +[a19de65d-35b8-4480-b1af-371d9541e706] +description = "triplets whose sum is 12" + +[48b21332-0a3d-43b2-9a52-90b2a6e5c9f5] +description = "triplets whose sum is 108" + +[dffc1266-418e-4daa-81af-54c3e95c3bb5] +description = "triplets whose sum is 1000" + +[5f86a2d4-6383-4cce-93a5-e4489e79b186] +description = "no matching triplets for 1001" + +[bf17ba80-1596-409a-bb13-343bdb3b2904] +description = "returns all matching triplets" + +[9d8fb5d5-6c6f-42df-9f95-d3165963ac57] +description = "several matching triplets" + +[f5be5734-8aa0-4bd1-99a2-02adcc4402b4] +description = "triplets for large number" diff --git a/exercises/practice/pythagorean-triplet/Cargo.toml b/exercises/practice/pythagorean-triplet/Cargo.toml index 517618ef1..12d11014a 100644 --- a/exercises/practice/pythagorean-triplet/Cargo.toml +++ b/exercises/practice/pythagorean-triplet/Cargo.toml @@ -1,6 +1,9 @@ -[dependencies] - [package] -edition = "2021" name = "pythagorean_triplet" -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/pythagorean-triplet/src/lib.rs b/exercises/practice/pythagorean-triplet/src/lib.rs index 340d15571..e645ab1f3 100644 --- a/exercises/practice/pythagorean-triplet/src/lib.rs +++ b/exercises/practice/pythagorean-triplet/src/lib.rs @@ -1,5 +1,7 @@ use std::collections::HashSet; pub fn find(sum: u32) -> HashSet<[u32; 3]> { - unimplemented!("Given the sum {sum}, return all possible Pythagorean triplets, which produce the said sum, or an empty HashSet if there are no such triplets. Note that you are expected to return triplets in [a, b, c] order, where a < b < c"); + todo!( + "Given the sum {sum}, return all possible Pythagorean triplets, which produce the said sum, or an empty HashSet if there are no such triplets. Note that you are expected to return triplets in [a, b, c] order, where a < b < c" + ); } diff --git a/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs b/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs deleted file mode 100644 index 3ca5c3728..000000000 --- a/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs +++ /dev/null @@ -1,76 +0,0 @@ -use pythagorean_triplet::find; -use std::collections::HashSet; - -fn process_tripletswithsum_case(sum: u32, expected: &[[u32; 3]]) { - let triplets = find(sum); - - if !expected.is_empty() { - let expected: HashSet<_> = expected.iter().cloned().collect(); - - assert_eq!(expected, triplets); - } else { - assert!(triplets.is_empty()); - } -} - -#[test] -fn test_triplets_whose_sum_is_12() { - process_tripletswithsum_case(12, &[[3, 4, 5]]); -} - -#[test] -#[ignore] -fn test_triplets_whose_sum_is_108() { - process_tripletswithsum_case(108, &[[27, 36, 45]]); -} - -#[test] -#[ignore] -fn test_triplets_whose_sum_is_1000() { - process_tripletswithsum_case(1000, &[[200, 375, 425]]); -} - -#[test] -#[ignore] -fn test_no_matching_triplets_for_1001() { - process_tripletswithsum_case(1001, &[]); -} - -#[test] -#[ignore] -fn test_returns_all_matching_triplets() { - process_tripletswithsum_case(90, &[[9, 40, 41], [15, 36, 39]]); -} - -#[test] -#[ignore] -fn test_several_matching_triplets() { - process_tripletswithsum_case( - 840, - &[ - [40, 399, 401], - [56, 390, 394], - [105, 360, 375], - [120, 350, 370], - [140, 336, 364], - [168, 315, 357], - [210, 280, 350], - [240, 252, 348], - ], - ); -} - -#[test] -#[ignore] -fn test_triplets_for_large_number() { - process_tripletswithsum_case( - 30_000, - &[ - [1200, 14_375, 14_425], - [1875, 14_000, 14_125], - [5000, 12_000, 13_000], - [6000, 11_250, 12_750], - [7500, 10_000, 12_500], - ], - ); -} diff --git a/exercises/practice/pythagorean-triplet/tests/pythagorean_triplet.rs b/exercises/practice/pythagorean-triplet/tests/pythagorean_triplet.rs new file mode 100644 index 000000000..9568f9095 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/tests/pythagorean_triplet.rs @@ -0,0 +1,87 @@ +use pythagorean_triplet::*; + +use std::collections::HashSet; + +#[test] +fn triplets_whose_sum_is_12() { + let input = 12; + let output = find(input); + let expected = [[3, 4, 5]]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn triplets_whose_sum_is_108() { + let input = 108; + let output = find(input); + let expected = [[27, 36, 45]]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn triplets_whose_sum_is_1000() { + let input = 1_000; + let output = find(input); + let expected = [[200, 375, 425]]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn no_matching_triplets_for_1001() { + let input = 1_001; + let output = find(input); + let expected = []; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn returns_all_matching_triplets() { + let input = 90; + let output = find(input); + let expected = [[9, 40, 41], [15, 36, 39]]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn several_matching_triplets() { + let input = 840; + let output = find(input); + let expected = [ + [40, 399, 401], + [56, 390, 394], + [105, 360, 375], + [120, 350, 370], + [140, 336, 364], + [168, 315, 357], + [210, 280, 350], + [240, 252, 348], + ]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn triplets_for_large_number() { + let input = 30_000; + let output = find(input); + let expected = [ + [1_200, 14_375, 14_425], + [1_875, 14_000, 14_125], + [5_000, 12_000, 13_000], + [6_000, 11_250, 12_750], + [7_500, 10_000, 12_500], + ]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); +} diff --git a/exercises/practice/queen-attack/.docs/instructions.md b/exercises/practice/queen-attack/.docs/instructions.md index 42528d5fd..97f22a0ae 100644 --- a/exercises/practice/queen-attack/.docs/instructions.md +++ b/exercises/practice/queen-attack/.docs/instructions.md @@ -1,58 +1,21 @@ # Instructions -Given the position of two queens on a chess board, indicate whether or not they -are positioned so that they can attack each other. - -In the game of chess, a queen can attack pieces which are on the same -row, column, or diagonal. - -A chessboard can be represented by an 8 by 8 array. The rows of a chessboard are known as ranks and columns are known as files. - -So if you're told the white queen is at (2, 3) and the black queen at -(5, 6), then you'd know you've got a set-up like so: - -```text -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ -_ _ _ W _ _ _ _ -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ B _ -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ -``` - -You'd also be able to answer whether the queens can attack each other. -In this case, that answer would be yes, they can, because both pieces -share a diagonal. - -### Examples of queens attacking: - -```text -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ W _ _ _ _ -_ _ _ W _ _ _ _ _ B _ _ _ W _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ B _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ B _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -``` - -### Examples of queens not interacting: - -```text - -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ W _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ W _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ W _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ B _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ B _ _ _ _ _ _ _ _ _ _ _ B _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -``` - -B and W stand for **Black** and **White**, the two sides competing -against each other in a game of chess. For this exercise you do not need to know which side -the queens are on. +Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other. + +In the game of chess, a queen can attack pieces which are on the same row, column, or diagonal. + +A chessboard can be represented by an 8 by 8 array. + +So if you are told the white queen is at `c5` (zero-indexed at column 2, row 3) and the black queen at `f2` (zero-indexed at column 5, row 6), then you know that the set-up is like so: + +![A chess board with two queens. Arrows emanating from the queen at c5 indicate possible directions of capture along file, rank and diagonal.](https://assets.exercism.org/images/exercises/queen-attack/queen-capture.svg) + +You are also able to answer whether the queens can attack each other. +In this case, that answer would be yes, they can, because both pieces share a diagonal. + +## Credit + +The chessboard image was made by [habere-et-dispertire][habere-et-dispertire] using LaTeX and the [chessboard package][chessboard-package] by Ulrike Fischer. + +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[chessboard-package]: https://github.com/u-fischer/chessboard diff --git a/exercises/practice/queen-attack/.gitignore b/exercises/practice/queen-attack/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/queen-attack/.gitignore +++ b/exercises/practice/queen-attack/.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/queen-attack/.meta/config.json b/exercises/practice/queen-attack/.meta/config.json index 7359547ef..23eb7d588 100644 --- a/exercises/practice/queen-attack/.meta/config.json +++ b/exercises/practice/queen-attack/.meta/config.json @@ -25,7 +25,7 @@ "Cargo.toml" ], "test": [ - "tests/queen-attack.rs" + "tests/queen_attack.rs" ], "example": [ ".meta/example.rs" @@ -33,5 +33,5 @@ }, "blurb": "Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other.", "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/queen-attack/.meta/example.rs b/exercises/practice/queen-attack/.meta/example.rs index 76808ae09..e14c9bce4 100644 --- a/exercises/practice/queen-attack/.meta/example.rs +++ b/exercises/practice/queen-attack/.meta/example.rs @@ -14,9 +14,9 @@ impl ChessPiece for Queen { } fn can_attack(&self, piece: &T) -> bool { - self.position.horizontal_from(&piece.position()) - || self.position.vertical_from(&piece.position()) - || self.position.diagonal_from(&piece.position()) + self.position.horizontal_from(piece.position()) + || self.position.vertical_from(piece.position()) + || self.position.diagonal_from(piece.position()) } } diff --git a/exercises/practice/queen-attack/.meta/test_template.tera b/exercises/practice/queen-attack/.meta/test_template.tera new file mode 100644 index 000000000..941f42d33 --- /dev/null +++ b/exercises/practice/queen-attack/.meta/test_template.tera @@ -0,0 +1,26 @@ +use queen_attack::*; + +{% for test_group in cases %} +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { +{% if test.property == "create" %} + let chess_position = ChessPosition::new({{ test.input.queen.position.row }}, {{ test.input.queen.position.column }}); +{%- if test.expected is object %} + assert!(chess_position.is_none()); +{% else %} + assert!(chess_position.is_some()); +{% endif -%} +{% else %} + let white_queen = Queen::new(ChessPosition::new({{ test.input.white_queen.position.row }}, {{ test.input.white_queen.position.column }}).unwrap()); + let black_queen = Queen::new(ChessPosition::new({{ test.input.black_queen.position.row }}, {{ test.input.black_queen.position.column }}).unwrap()); +{%- if test.expected %} + assert!(white_queen.can_attack(&black_queen)); +{% else %} + assert!(!white_queen.can_attack(&black_queen)); +{% endif -%} +{% endif %} +} +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/queen-attack/.meta/tests.toml b/exercises/practice/queen-attack/.meta/tests.toml index be690e975..e0624123d 100644 --- a/exercises/practice/queen-attack/.meta/tests.toml +++ b/exercises/practice/queen-attack/.meta/tests.toml @@ -1,3 +1,49 @@ -# 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. + +[3ac4f735-d36c-44c4-a3e2-316f79704203] +description = "Test creation of Queens with valid and invalid positions -> queen with a valid position" + +[4e812d5d-b974-4e38-9a6b-8e0492bfa7be] +description = "Test creation of Queens with valid and invalid positions -> queen must have positive row" + +[f07b7536-b66b-4f08-beb9-4d70d891d5c8] +description = "Test creation of Queens with valid and invalid positions -> queen must have row on board" + +[15a10794-36d9-4907-ae6b-e5a0d4c54ebe] +description = "Test creation of Queens with valid and invalid positions -> queen must have positive column" + +[6907762d-0e8a-4c38-87fb-12f2f65f0ce4] +description = "Test creation of Queens with valid and invalid positions -> queen must have column on board" + +[33ae4113-d237-42ee-bac1-e1e699c0c007] +description = "Test the ability of one queen to attack another -> cannot attack" + +[eaa65540-ea7c-4152-8c21-003c7a68c914] +description = "Test the ability of one queen to attack another -> can attack on same row" + +[bae6f609-2c0e-4154-af71-af82b7c31cea] +description = "Test the ability of one queen to attack another -> can attack on same column" + +[0e1b4139-b90d-4562-bd58-dfa04f1746c7] +description = "Test the ability of one queen to attack another -> can attack on first diagonal" + +[ff9b7ed4-e4b6-401b-8d16-bc894d6d3dcd] +description = "Test the ability of one queen to attack another -> can attack on second diagonal" + +[0a71e605-6e28-4cc2-aa47-d20a2e71037a] +description = "Test the ability of one queen to attack another -> can attack on third diagonal" + +[0790b588-ae73-4f1f-a968-dd0b34f45f86] +description = "Test the ability of one queen to attack another -> can attack on fourth diagonal" + +[543f8fd4-2597-4aad-8d77-cbdab63619f8] +description = "Test the ability of one queen to attack another -> cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal" diff --git a/exercises/practice/queen-attack/Cargo.toml b/exercises/practice/queen-attack/Cargo.toml index e642e0ae7..a2c251524 100644 --- a/exercises/practice/queen-attack/Cargo.toml +++ b/exercises/practice/queen-attack/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "queen-attack" -version = "2.2.0" +name = "queen_attack" +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/queen-attack/src/lib.rs b/exercises/practice/queen-attack/src/lib.rs index b2139bff0..2c6840034 100644 --- a/exercises/practice/queen-attack/src/lib.rs +++ b/exercises/practice/queen-attack/src/lib.rs @@ -6,7 +6,7 @@ pub struct Queen; impl ChessPosition { pub fn new(rank: i32, file: i32) -> Option { - unimplemented!( + todo!( "Construct a ChessPosition struct, given the following rank, file: ({rank}, {file}). If the position is invalid return None." ); } @@ -14,10 +14,10 @@ impl ChessPosition { impl Queen { pub fn new(position: ChessPosition) -> Self { - unimplemented!("Given the chess position {position:?}, construct a Queen struct."); + todo!("Given the chess position {position:?}, construct a Queen struct."); } pub fn can_attack(&self, other: &Queen) -> bool { - unimplemented!("Determine if this Queen can attack the other Queen {other:?}"); + todo!("Determine if this Queen can attack the other Queen {other:?}"); } } diff --git a/exercises/practice/queen-attack/tests/queen-attack.rs b/exercises/practice/queen-attack/tests/queen_attack.rs similarity index 56% rename from exercises/practice/queen-attack/tests/queen-attack.rs rename to exercises/practice/queen-attack/tests/queen_attack.rs index b1f17d20c..cb37dfcd5 100644 --- a/exercises/practice/queen-attack/tests/queen-attack.rs +++ b/exercises/practice/queen-attack/tests/queen_attack.rs @@ -1,96 +1,100 @@ use queen_attack::*; #[test] -fn chess_position_on_the_board_is_some() { - assert!(ChessPosition::new(2, 4).is_some()); +fn queen_with_a_valid_position() { + let chess_position = ChessPosition::new(2, 2); + assert!(chess_position.is_some()); } #[test] #[ignore] -fn chess_position_off_the_board_is_none() { - assert!(ChessPosition::new(-1, 2).is_none()); - - assert!(ChessPosition::new(8, 2).is_none()); +fn queen_must_have_positive_row() { + let chess_position = ChessPosition::new(-2, 2); + assert!(chess_position.is_none()); +} - assert!(ChessPosition::new(5, -1).is_none()); +#[test] +#[ignore] +fn queen_must_have_row_on_board() { + let chess_position = ChessPosition::new(8, 4); + assert!(chess_position.is_none()); +} - assert!(ChessPosition::new(5, 8).is_none()); +#[test] +#[ignore] +fn queen_must_have_positive_column() { + let chess_position = ChessPosition::new(2, -2); + assert!(chess_position.is_none()); } #[test] #[ignore] -fn queen_is_created_with_a_valid_position() { - Queen::new(ChessPosition::new(2, 4).unwrap()); +fn queen_must_have_column_on_board() { + let chess_position = ChessPosition::new(4, 8); + assert!(chess_position.is_none()); } #[test] #[ignore] -fn queens_that_can_not_attack() { +fn cannot_attack() { let white_queen = Queen::new(ChessPosition::new(2, 4).unwrap()); let black_queen = Queen::new(ChessPosition::new(6, 6).unwrap()); - assert!(!white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_rank_can_attack() { +fn can_attack_on_same_row() { let white_queen = Queen::new(ChessPosition::new(2, 4).unwrap()); let black_queen = Queen::new(ChessPosition::new(2, 6).unwrap()); - assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_file_can_attack() { +fn can_attack_on_same_column() { let white_queen = Queen::new(ChessPosition::new(4, 5).unwrap()); - let black_queen = Queen::new(ChessPosition::new(3, 5).unwrap()); - + let black_queen = Queen::new(ChessPosition::new(2, 5).unwrap()); assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_diagonal_can_attack_one() { +fn can_attack_on_first_diagonal() { let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap()); let black_queen = Queen::new(ChessPosition::new(0, 4).unwrap()); - assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_diagonal_can_attack_two() { +fn can_attack_on_second_diagonal() { let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap()); let black_queen = Queen::new(ChessPosition::new(3, 1).unwrap()); - assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_diagonal_can_attack_three() { +fn can_attack_on_third_diagonal() { let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap()); let black_queen = Queen::new(ChessPosition::new(1, 1).unwrap()); - assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_diagonal_can_attack_four() { - let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap()); - let black_queen = Queen::new(ChessPosition::new(5, 5).unwrap()); - +fn can_attack_on_fourth_diagonal() { + let white_queen = Queen::new(ChessPosition::new(1, 7).unwrap()); + let black_queen = Queen::new(ChessPosition::new(0, 6).unwrap()); assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_that_cannot_attack_with_equal_difference() { +fn cannot_attack_if_falling_diagonals_are_only_the_same_when_reflected_across_the_longest_falling_diagonal() + { let white_queen = Queen::new(ChessPosition::new(4, 1).unwrap()); let black_queen = Queen::new(ChessPosition::new(2, 5).unwrap()); - assert!(!white_queen.can_attack(&black_queen)); } diff --git a/exercises/practice/rail-fence-cipher/.docs/instructions.md b/exercises/practice/rail-fence-cipher/.docs/instructions.md index 0e75a2bf7..e311de6cd 100644 --- a/exercises/practice/rail-fence-cipher/.docs/instructions.md +++ b/exercises/practice/rail-fence-cipher/.docs/instructions.md @@ -2,15 +2,13 @@ Implement encoding and decoding for the rail fence cipher. -The Rail Fence cipher is a form of transposition cipher that gets its name from -the way in which it's encoded. It was already used by the ancient Greeks. +The Rail Fence cipher is a form of transposition cipher that gets its name from the way in which it's encoded. +It was already used by the ancient Greeks. -In the Rail Fence cipher, the message is written downwards on successive "rails" -of an imaginary fence, then moving up when we get to the bottom (like a zig-zag). +In the Rail Fence cipher, the message is written downwards on successive "rails" of an imaginary fence, then moving up when we get to the bottom (like a zig-zag). Finally the message is then read off in rows. -For example, using three "rails" and the message "WE ARE DISCOVERED FLEE AT ONCE", -the cipherer writes out: +For example, using three "rails" and the message "WE ARE DISCOVERED FLEE AT ONCE", the cipherer writes out: ```text W . . . E . . . C . . . R . . . L . . . T . . . E diff --git a/exercises/practice/rail-fence-cipher/.gitignore b/exercises/practice/rail-fence-cipher/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/rail-fence-cipher/.gitignore +++ b/exercises/practice/rail-fence-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/rail-fence-cipher/.meta/additional-tests.json b/exercises/practice/rail-fence-cipher/.meta/additional-tests.json new file mode 100644 index 000000000..ef3a0f2fb --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.meta/additional-tests.json @@ -0,0 +1,21 @@ +[ + { + "description": "unicode", + "cases": [ + { + "uuid": "46dc5c50-5538-401d-93a5-41102680d068", + "description": "encode wide characters", + "comments": [ + "Unicode tests are not suitable to be upstreamed.", + "Handling unicode is tedious in many languages." + ], + "property": "encode", + "input": { + "msg": "古池蛙飛び込む水の音", + "rails": 3 + }, + "expected": "古びの池飛込水音蛙む" + } + ] + } +] diff --git a/exercises/practice/rail-fence-cipher/.meta/config.json b/exercises/practice/rail-fence-cipher/.meta/config.json index 8807bd52e..4a060ab0f 100644 --- a/exercises/practice/rail-fence-cipher/.meta/config.json +++ b/exercises/practice/rail-fence-cipher/.meta/config.json @@ -17,7 +17,7 @@ "Cargo.toml" ], "test": [ - "tests/rail-fence-cipher.rs" + "tests/rail_fence_cipher.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/rail-fence-cipher/.meta/test_template.tera b/exercises/practice/rail-fence-cipher/.meta/test_template.tera new file mode 100644 index 000000000..c9ada5303 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.meta/test_template.tera @@ -0,0 +1,20 @@ +use rail_fence_cipher::RailFence; + +{% for test_group in cases %} +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.msg | json_encode() }}; + let rails = {{ test.input.rails | json_encode() }}; + let rail_fence = RailFence::new(rails); + {% if test.property == "encode" -%} + let output = rail_fence.encode(input); + {%- else -%} + let output = rail_fence.decode(input); + {%- endif %} + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/rail-fence-cipher/.meta/tests.toml b/exercises/practice/rail-fence-cipher/.meta/tests.toml index be690e975..dfc5e16ba 100644 --- a/exercises/practice/rail-fence-cipher/.meta/tests.toml +++ b/exercises/practice/rail-fence-cipher/.meta/tests.toml @@ -1,3 +1,28 @@ -# 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. + +[46dc5c50-5538-401d-93a5-41102680d068] +description = "encode -> encode with two rails" + +[25691697-fbd8-4278-8c38-b84068b7bc29] +description = "encode -> encode with three rails" + +[384f0fea-1442-4f1a-a7c4-5cbc2044002c] +description = "encode -> encode with ending in the middle" + +[cd525b17-ec34-45ef-8f0e-4f27c24a7127] +description = "decode -> decode with three rails" + +[dd7b4a98-1a52-4e5c-9499-cbb117833507] +description = "decode -> decode with five rails" + +[93e1ecf4-fac9-45d9-9cd2-591f47d3b8d3] +description = "decode -> decode with six rails" diff --git a/exercises/practice/rail-fence-cipher/Cargo.toml b/exercises/practice/rail-fence-cipher/Cargo.toml index 82030937b..1e9644ee8 100644 --- a/exercises/practice/rail-fence-cipher/Cargo.toml +++ b/exercises/practice/rail-fence-cipher/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "rail_fence_cipher" -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/rail-fence-cipher/src/lib.rs b/exercises/practice/rail-fence-cipher/src/lib.rs index 498d827bb..4b72261ce 100644 --- a/exercises/practice/rail-fence-cipher/src/lib.rs +++ b/exercises/practice/rail-fence-cipher/src/lib.rs @@ -2,14 +2,14 @@ pub struct RailFence; impl RailFence { pub fn new(rails: u32) -> RailFence { - unimplemented!("Construct a new fence with {rails} rails") + todo!("Construct a new fence with {rails} rails") } pub fn encode(&self, text: &str) -> String { - unimplemented!("Encode this text: {text}") + todo!("Encode this text: {text}") } pub fn decode(&self, cipher: &str) -> String { - unimplemented!("Decode this ciphertext: {cipher}") + todo!("Decode this ciphertext: {cipher}") } } diff --git a/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs b/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs deleted file mode 100644 index 399f0c6da..000000000 --- a/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! Tests for rail-fence-cipher -//! -//! 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/rail-fence-cipher/canonical_data.json -//! -//! The tests do not expect any normalization or cleaning. -//! That trade is tested in enough other exercises. - -use rail_fence_cipher::*; - -/// Process a single test case for the property `encode` -/// -/// All cases for the `encode` property are implemented -/// in terms of this function. -fn process_encode_case(input: &str, rails: u32, expected: &str) { - let rail_fence = RailFence::new(rails); - assert_eq!(rail_fence.encode(input), expected); -} - -/// Process a single test case for the property `decode` -/// -/// All cases for the `decode` property are implemented -/// in terms of this function. -fn process_decode_case(input: &str, rails: u32, expected: &str) { - let rail_fence = RailFence::new(rails); - assert_eq!(rail_fence.decode(input), expected); -} - -// encode - -#[test] -/// encode with two rails -fn test_encode_with_two_rails() { - process_encode_case("XOXOXOXOXOXOXOXOXO", 2, "XXXXXXXXXOOOOOOOOO"); -} - -#[test] -#[ignore] -/// encode with three rails -fn test_encode_with_three_rails() { - process_encode_case("WEAREDISCOVEREDFLEEATONCE", 3, "WECRLTEERDSOEEFEAOCAIVDEN"); -} - -#[test] -#[ignore] -/// encode with ending in the middle -fn test_encode_with_ending_in_the_middle() { - process_encode_case("EXERCISES", 4, "ESXIEECSR"); -} - -// decode - -#[test] -#[ignore] -/// decode with three rails -fn test_decode_with_three_rails() { - process_decode_case("TEITELHDVLSNHDTISEIIEA", 3, "THEDEVILISINTHEDETAILS"); -} - -#[test] -#[ignore] -/// decode with five rails -fn test_decode_with_five_rails() { - process_decode_case("EIEXMSMESAORIWSCE", 5, "EXERCISMISAWESOME"); -} - -#[test] -#[ignore] -/// decode with six rails -fn test_decode_with_six_rails() { - process_decode_case( - "133714114238148966225439541018335470986172518171757571896261", - 6, - "112358132134558914423337761098715972584418167651094617711286", - ); -} - -#[test] -#[ignore] -/// encode wide characters -/// -/// normally unicode is not part of exercism exercises, but in an exercise -/// specifically oriented around shuffling characters, it seems worth ensuring -/// that wide characters are handled properly -/// -/// this text is possibly one of the most famous haiku of all time, by -/// Matsuo Bashō (松尾芭蕉) -fn test_encode_wide_characters() { - process_encode_case("古池蛙飛び込む水の音", 3, "古びの池飛込水音蛙む"); -} diff --git a/exercises/practice/rail-fence-cipher/tests/rail_fence_cipher.rs b/exercises/practice/rail-fence-cipher/tests/rail_fence_cipher.rs new file mode 100644 index 000000000..6fb28d611 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/tests/rail_fence_cipher.rs @@ -0,0 +1,77 @@ +use rail_fence_cipher::RailFence; + +#[test] +fn encode_with_two_rails() { + let input = "XOXOXOXOXOXOXOXOXO"; + let rails = 2; + let rail_fence = RailFence::new(rails); + let output = rail_fence.encode(input); + let expected = "XXXXXXXXXOOOOOOOOO"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn encode_with_three_rails() { + let input = "WEAREDISCOVEREDFLEEATONCE"; + let rails = 3; + let rail_fence = RailFence::new(rails); + let output = rail_fence.encode(input); + let expected = "WECRLTEERDSOEEFEAOCAIVDEN"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn encode_with_ending_in_the_middle() { + let input = "EXERCISES"; + let rails = 4; + let rail_fence = RailFence::new(rails); + let output = rail_fence.encode(input); + let expected = "ESXIEECSR"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn decode_with_three_rails() { + let input = "TEITELHDVLSNHDTISEIIEA"; + let rails = 3; + let rail_fence = RailFence::new(rails); + let output = rail_fence.decode(input); + let expected = "THEDEVILISINTHEDETAILS"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn decode_with_five_rails() { + let input = "EIEXMSMESAORIWSCE"; + let rails = 5; + let rail_fence = RailFence::new(rails); + let output = rail_fence.decode(input); + let expected = "EXERCISMISAWESOME"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn decode_with_six_rails() { + let input = "133714114238148966225439541018335470986172518171757571896261"; + let rails = 6; + let rail_fence = RailFence::new(rails); + let output = rail_fence.decode(input); + let expected = "112358132134558914423337761098715972584418167651094617711286"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn encode_wide_characters() { + let input = "古池蛙飛び込む水の音"; + let rails = 3; + let rail_fence = RailFence::new(rails); + let output = rail_fence.encode(input); + let expected = "古びの池飛込水音蛙む"; + assert_eq!(output, expected); +} diff --git a/exercises/practice/raindrops/.docs/instructions.md b/exercises/practice/raindrops/.docs/instructions.md index a78585df2..df6441075 100644 --- a/exercises/practice/raindrops/.docs/instructions.md +++ b/exercises/practice/raindrops/.docs/instructions.md @@ -1,16 +1,24 @@ # Instructions -Your task is to convert a number into a string that contains raindrop sounds corresponding to certain potential factors. A factor is a number that evenly divides into another number, leaving no remainder. The simplest way to test if a one number is a factor of another is to use the [modulo operation](https://en.wikipedia.org/wiki/Modulo_operation). +Your task is to convert a number into its corresponding raindrop sounds. -The rules of `raindrops` are that if a given number: +If a given number: -- has 3 as a factor, add 'Pling' to the result. -- has 5 as a factor, add 'Plang' to the result. -- has 7 as a factor, add 'Plong' to the result. -- _does not_ have any of 3, 5, or 7 as a factor, the result should be the digits of the number. +- is divisible by 3, add "Pling" to the result. +- is divisible by 5, add "Plang" to the result. +- is divisible by 7, add "Plong" to the result. +- **is not** divisible by 3, 5, or 7, the result should be the number as a string. ## Examples -- 28 has 7 as a factor, but not 3 or 5, so the result would be "Plong". -- 30 has both 3 and 5 as factors, but not 7, so the result would be "PlingPlang". -- 34 is not factored by 3, 5, or 7, so the result would be "34". +- 28 is divisible by 7, but not 3 or 5, so the result would be `"Plong"`. +- 30 is divisible by 3 and 5, but not 7, so the result would be `"PlingPlang"`. +- 34 is not divisible by 3, 5, or 7, so the result would be `"34"`. + +~~~~exercism/note +A common way to test if one number is evenly divisible by another is to compare the [remainder][remainder] or [modulus][modulo] to zero. +Most languages provide operators or functions for one (or both) of these. + +[remainder]: https://exercism.org/docs/programming/operators/remainder +[modulo]: https://en.wikipedia.org/wiki/Modulo_operation +~~~~ diff --git a/exercises/practice/raindrops/.docs/introduction.md b/exercises/practice/raindrops/.docs/introduction.md new file mode 100644 index 000000000..ba12100f3 --- /dev/null +++ b/exercises/practice/raindrops/.docs/introduction.md @@ -0,0 +1,3 @@ +# Introduction + +Raindrops is a slightly more complex version of the FizzBuzz challenge, a classic interview question. diff --git a/exercises/practice/raindrops/.gitignore b/exercises/practice/raindrops/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/raindrops/.gitignore +++ b/exercises/practice/raindrops/.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/raindrops/.meta/config.json b/exercises/practice/raindrops/.meta/config.json index 6c6974228..fee65d4bb 100644 --- a/exercises/practice/raindrops/.meta/config.json +++ b/exercises/practice/raindrops/.meta/config.json @@ -36,7 +36,7 @@ ".meta/example.rs" ] }, - "blurb": "Convert a number to a string, the content of which depends on the number's factors.", + "blurb": "Convert a number into its corresponding raindrop sounds - Pling, Plang and Plong.", "source": "A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division.", "source_url": "https://en.wikipedia.org/wiki/Fizz_buzz" } diff --git a/exercises/practice/raindrops/.meta/example.rs b/exercises/practice/raindrops/.meta/example.rs index 2ff3d312b..b580bd073 100644 --- a/exercises/practice/raindrops/.meta/example.rs +++ b/exercises/practice/raindrops/.meta/example.rs @@ -13,7 +13,7 @@ pub fn raindrops(n: u32) -> String { drops.push_str("Plong"); } if drops.is_empty() { - let s = format!("{}", n); + let s = format!("{n}"); drops.push_str(&s); } drops diff --git a/exercises/practice/raindrops/.meta/test_template.tera b/exercises/practice/raindrops/.meta/test_template.tera new file mode 100644 index 000000000..8e97f5142 --- /dev/null +++ b/exercises/practice/raindrops/.meta/test_template.tera @@ -0,0 +1,12 @@ +use raindrops::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.number | json_encode() }}; + let output = raindrops(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/raindrops/.meta/tests.toml b/exercises/practice/raindrops/.meta/tests.toml index be690e975..756d16ca1 100644 --- a/exercises/practice/raindrops/.meta/tests.toml +++ b/exercises/practice/raindrops/.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. + +[1575d549-e502-46d4-a8e1-6b7bec6123d8] +description = "the sound for 1 is 1" + +[1f51a9f9-4895-4539-b182-d7b0a5ab2913] +description = "the sound for 3 is Pling" + +[2d9bfae5-2b21-4bcd-9629-c8c0e388f3e0] +description = "the sound for 5 is Plang" + +[d7e60daa-32ef-4c23-b688-2abff46c4806] +description = "the sound for 7 is Plong" + +[6bb4947b-a724-430c-923f-f0dc3d62e56a] +description = "the sound for 6 is Pling as it has a factor 3" + +[ce51e0e8-d9d4-446d-9949-96eac4458c2d] +description = "2 to the power 3 does not make a raindrop sound as 3 is the exponent not the base" + +[0dd66175-e3e2-47fc-8750-d01739856671] +description = "the sound for 9 is Pling as it has a factor 3" + +[022c44d3-2182-4471-95d7-c575af225c96] +description = "the sound for 10 is Plang as it has a factor 5" + +[37ab74db-fed3-40ff-b7b9-04acdfea8edf] +description = "the sound for 14 is Plong as it has a factor of 7" + +[31f92999-6afb-40ee-9aa4-6d15e3334d0f] +description = "the sound for 15 is PlingPlang as it has factors 3 and 5" + +[ff9bb95d-6361-4602-be2c-653fe5239b54] +description = "the sound for 21 is PlingPlong as it has factors 3 and 7" + +[d2e75317-b72e-40ab-8a64-6734a21dece1] +description = "the sound for 25 is Plang as it has a factor 5" + +[a09c4c58-c662-4e32-97fe-f1501ef7125c] +description = "the sound for 27 is Pling as it has a factor 3" + +[bdf061de-8564-4899-a843-14b48b722789] +description = "the sound for 35 is PlangPlong as it has factors 5 and 7" + +[c4680bee-69ba-439d-99b5-70c5fd1a7a83] +description = "the sound for 49 is Plong as it has a factor 7" + +[17f2bc9a-b65a-4d23-8ccd-266e8c271444] +description = "the sound for 52 is 52" + +[e46677ed-ff1a-419f-a740-5c713d2830e4] +description = "the sound for 105 is PlingPlangPlong as it has factors 3, 5 and 7" + +[13c6837a-0fcd-4b86-a0eb-20572f7deb0b] +description = "the sound for 3125 is Plang as it has a factor 5" diff --git a/exercises/practice/raindrops/Cargo.toml b/exercises/practice/raindrops/Cargo.toml index 41c138c6b..a297d155b 100644 --- a/exercises/practice/raindrops/Cargo.toml +++ b/exercises/practice/raindrops/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "raindrops" -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/raindrops/src/lib.rs b/exercises/practice/raindrops/src/lib.rs index 6bcb31cdd..c16e39c18 100644 --- a/exercises/practice/raindrops/src/lib.rs +++ b/exercises/practice/raindrops/src/lib.rs @@ -1,3 +1,3 @@ pub fn raindrops(n: u32) -> String { - unimplemented!("what sound does Raindrop #{n} make?") + todo!("what sound does Raindrop #{n} make?") } diff --git a/exercises/practice/raindrops/tests/raindrops.rs b/exercises/practice/raindrops/tests/raindrops.rs index c7fa1ecb8..ea34ac57e 100644 --- a/exercises/practice/raindrops/tests/raindrops.rs +++ b/exercises/practice/raindrops/tests/raindrops.rs @@ -1,112 +1,162 @@ -#[test] -fn test_1() { - assert_eq!("1", raindrops::raindrops(1)); -} +use raindrops::*; #[test] -#[ignore] -fn test_3() { - assert_eq!("Pling", raindrops::raindrops(3)); +fn the_sound_for_1_is_1() { + let input = 1; + let output = raindrops(input); + let expected = "1"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_5() { - assert_eq!("Plang", raindrops::raindrops(5)); +fn the_sound_for_3_is_pling() { + let input = 3; + let output = raindrops(input); + let expected = "Pling"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_7() { - assert_eq!("Plong", raindrops::raindrops(7)); +fn the_sound_for_5_is_plang() { + let input = 5; + let output = raindrops(input); + let expected = "Plang"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_6() { - assert_eq!("Pling", raindrops::raindrops(6)); +fn the_sound_for_7_is_plong() { + let input = 7; + let output = raindrops(input); + let expected = "Plong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_8() { - assert_eq!("8", raindrops::raindrops(8)); +fn the_sound_for_6_is_pling_as_it_has_a_factor_3() { + let input = 6; + let output = raindrops(input); + let expected = "Pling"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_9() { - assert_eq!("Pling", raindrops::raindrops(9)); +fn test_2_to_the_power_3_does_not_make_a_raindrop_sound_as_3_is_the_exponent_not_the_base() { + let input = 8; + let output = raindrops(input); + let expected = "8"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_10() { - assert_eq!("Plang", raindrops::raindrops(10)); +fn the_sound_for_9_is_pling_as_it_has_a_factor_3() { + let input = 9; + let output = raindrops(input); + let expected = "Pling"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_14() { - assert_eq!("Plong", raindrops::raindrops(14)); +fn the_sound_for_10_is_plang_as_it_has_a_factor_5() { + let input = 10; + let output = raindrops(input); + let expected = "Plang"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_15() { - assert_eq!("PlingPlang", raindrops::raindrops(15)); +fn the_sound_for_14_is_plong_as_it_has_a_factor_of_7() { + let input = 14; + let output = raindrops(input); + let expected = "Plong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_21() { - assert_eq!("PlingPlong", raindrops::raindrops(21)); +fn the_sound_for_15_is_plingplang_as_it_has_factors_3_and_5() { + let input = 15; + let output = raindrops(input); + let expected = "PlingPlang"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_25() { - assert_eq!("Plang", raindrops::raindrops(25)); +fn the_sound_for_21_is_plingplong_as_it_has_factors_3_and_7() { + let input = 21; + let output = raindrops(input); + let expected = "PlingPlong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_27() { - assert_eq!("Pling", raindrops::raindrops(27)); +fn the_sound_for_25_is_plang_as_it_has_a_factor_5() { + let input = 25; + let output = raindrops(input); + let expected = "Plang"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_35() { - assert_eq!("PlangPlong", raindrops::raindrops(35)); +fn the_sound_for_27_is_pling_as_it_has_a_factor_3() { + let input = 27; + let output = raindrops(input); + let expected = "Pling"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_49() { - assert_eq!("Plong", raindrops::raindrops(49)); +fn the_sound_for_35_is_plangplong_as_it_has_factors_5_and_7() { + let input = 35; + let output = raindrops(input); + let expected = "PlangPlong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_52() { - assert_eq!("52", raindrops::raindrops(52)); +fn the_sound_for_49_is_plong_as_it_has_a_factor_7() { + let input = 49; + let output = raindrops(input); + let expected = "Plong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_105() { - assert_eq!("PlingPlangPlong", raindrops::raindrops(105)); +fn the_sound_for_52_is_52() { + let input = 52; + let output = raindrops(input); + let expected = "52"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_3125() { - assert_eq!("Plang", raindrops::raindrops(3125)); +fn the_sound_for_105_is_plingplangplong_as_it_has_factors_3_5_and_7() { + let input = 105; + let output = raindrops(input); + let expected = "PlingPlangPlong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_12121() { - assert_eq!("12121", raindrops::raindrops(12_121)); +fn the_sound_for_3125_is_plang_as_it_has_a_factor_5() { + let input = 3125; + let output = raindrops(input); + let expected = "Plang"; + assert_eq!(output, expected); } diff --git a/exercises/practice/react/.docs/instructions.md b/exercises/practice/react/.docs/instructions.md index 5cad8825f..1b9a175d0 100644 --- a/exercises/practice/react/.docs/instructions.md +++ b/exercises/practice/react/.docs/instructions.md @@ -2,15 +2,10 @@ Implement a basic reactive system. -Reactive programming is a programming paradigm that focuses on how values -are computed in terms of each other to allow a change to one value to -automatically propagate to other values, like in a spreadsheet. +Reactive programming is a programming paradigm that focuses on how values are computed in terms of each other to allow a change to one value to automatically propagate to other values, like in a spreadsheet. -Implement a basic reactive system with cells with settable values ("input" -cells) and cells with values computed in terms of other cells ("compute" -cells). Implement updates so that when an input value is changed, values -propagate to reach a new stable system state. +Implement a basic reactive system with cells with settable values ("input" cells) and cells with values computed in terms of other cells ("compute" cells). +Implement updates so that when an input value is changed, values propagate to reach a new stable system state. -In addition, compute cells should allow for registering change notification -callbacks. Call a cell’s callbacks when the cell’s value in a new stable -state has changed from the previous stable state. +In addition, compute cells should allow for registering change notification callbacks. +Call a cell’s callbacks when the cell’s value in a new stable state has changed from the previous stable state. diff --git a/exercises/practice/react/.gitignore b/exercises/practice/react/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/react/.gitignore +++ b/exercises/practice/react/.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/react/.meta/example.rs b/exercises/practice/react/.meta/example.rs index 86a25a405..03dac050b 100644 --- a/exercises/practice/react/.meta/example.rs +++ b/exercises/practice/react/.meta/example.rs @@ -44,6 +44,7 @@ struct ComputeCell<'a, T: Copy> { cell: Cell, dependencies: Vec, + #[allow(clippy::type_complexity)] f: Box T>, callbacks_issued: usize, callbacks: HashMap>, @@ -247,3 +248,9 @@ impl<'a, T: Copy + PartialEq> Reactor<'a, T> { } } } + +impl Default for Reactor<'_, T> { + fn default() -> Self { + Self::new() + } +} diff --git a/exercises/practice/react/.meta/tests.toml b/exercises/practice/react/.meta/tests.toml index 8d443115a..9b29a0a0a 100644 --- a/exercises/practice/react/.meta/tests.toml +++ b/exercises/practice/react/.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. [c51ee736-d001-4f30-88d1-0c8e8b43cd07] description = "input cells have a value" @@ -23,6 +30,15 @@ description = "compute cells can depend on other compute cells" [abe33eaf-68ad-42a5-b728-05519ca88d2d] description = "compute cells fire callbacks" +[9e5cb3a4-78e5-4290-80f8-a78612c52db2] +description = "callback cells only fire on change" + +[ada17cb6-7332-448a-b934-e3d7495c13d3] +description = "callbacks do not report already reported values" + +[ac271900-ea5c-461c-9add-eeebcb8c03e5] +description = "callbacks can fire from multiple cells" + [95a82dcc-8280-4de3-a4cd-4f19a84e3d6f] description = "callbacks can be added and removed" diff --git a/exercises/practice/react/Cargo.toml b/exercises/practice/react/Cargo.toml index 58e208248..171fe4d01 100644 --- a/exercises/practice/react/Cargo.toml +++ b/exercises/practice/react/Cargo.toml @@ -1,4 +1,12 @@ [package] -edition = "2021" name = "react" -version = "2.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] + +[lints.clippy] +new_without_default = "allow" diff --git a/exercises/practice/react/src/lib.rs b/exercises/practice/react/src/lib.rs index f87356c4d..0885ae2d3 100644 --- a/exercises/practice/react/src/lib.rs +++ b/exercises/practice/react/src/lib.rs @@ -41,12 +41,12 @@ pub struct Reactor { // You are guaranteed that Reactor will only be tested against types that are Copy + PartialEq. impl Reactor { pub fn new() -> Self { - unimplemented!() + todo!() } // Creates an input cell with the specified initial value, returning its ID. pub fn create_input(&mut self, _initial: T) -> InputCellId { - unimplemented!() + todo!() } // Creates a compute cell with the specified dependencies and compute function. @@ -67,7 +67,7 @@ impl Reactor { _dependencies: &[CellId], _compute_func: F, ) -> Result { - unimplemented!() + todo!() } // Retrieves the current value of the cell, or None if the cell does not exist. @@ -78,7 +78,7 @@ impl Reactor { // It turns out this introduces a significant amount of extra complexity to this exercise. // We chose not to cover this here, since this exercise is probably enough work as-is. pub fn value(&self, id: CellId) -> Option { - unimplemented!("Get the value of the cell whose id is {id:?}") + todo!("Get the value of the cell whose id is {id:?}") } // Sets the value of the specified input cell. @@ -90,7 +90,7 @@ impl Reactor { // // As before, that turned out to add too much extra complexity. pub fn set_value(&mut self, _id: InputCellId, _new_value: T) -> bool { - unimplemented!() + todo!() } // Adds a callback to the specified compute cell. @@ -110,7 +110,7 @@ impl Reactor { _id: ComputeCellId, _callback: F, ) -> Option { - unimplemented!() + todo!() } // Removes the specified callback, using an ID returned from add_callback. @@ -123,7 +123,7 @@ impl Reactor { cell: ComputeCellId, callback: CallbackId, ) -> Result<(), RemoveCallbackError> { - unimplemented!( + todo!( "Remove the callback identified by the CallbackId {callback:?} from the cell {cell:?}" ) } diff --git a/exercises/practice/react/tests/react.rs b/exercises/practice/react/tests/react.rs index af053b2d2..3683a98f3 100644 --- a/exercises/practice/react/tests/react.rs +++ b/exercises/practice/react/tests/react.rs @@ -169,9 +169,11 @@ fn compute_cells_fire_callbacks() { let output = reactor .create_compute(&[CellId::Input(input)], |v| v[0] + 1) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 3)); cb.expect_to_have_been_called_with(4); } @@ -226,9 +228,11 @@ fn callbacks_only_fire_on_change() { |v| if v[0] < 3 { 111 } else { 222 }, ) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 2)); cb.expect_not_to_have_been_called(); @@ -245,9 +249,11 @@ fn callbacks_can_be_called_multiple_times() { let output = reactor .create_compute(&[CellId::Input(input)], |v| v[0] + 1) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 2)); cb.expect_to_have_been_called_with(3); @@ -268,12 +274,16 @@ fn callbacks_can_be_called_from_multiple_cells() { let minus_one = reactor .create_compute(&[CellId::Input(input)], |v| v[0] - 1) .unwrap(); - assert!(reactor - .add_callback(plus_one, |v| cb1.callback_called(v)) - .is_some()); - assert!(reactor - .add_callback(minus_one, |v| cb2.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(plus_one, |v| cb1.callback_called(v)) + .is_some() + ); + assert!( + reactor + .add_callback(minus_one, |v| cb2.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 10)); cb1.expect_to_have_been_called_with(11); @@ -296,18 +306,22 @@ fn callbacks_can_be_added_and_removed() { let callback = reactor .add_callback(output, |v| cb1.callback_called(v)) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb2.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb2.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 31)); cb1.expect_to_have_been_called_with(32); cb2.expect_to_have_been_called_with(32); assert!(reactor.remove_callback(output, callback).is_ok()); - assert!(reactor - .add_callback(output, |v| cb3.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb3.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 41)); cb1.expect_not_to_have_been_called(); @@ -329,9 +343,11 @@ fn removing_a_callback_multiple_times_doesnt_interfere_with_other_callbacks() { let callback = reactor .add_callback(output, |v| cb1.callback_called(v)) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb2.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb2.callback_called(v)) + .is_some() + ); // We want the first remove to be Ok, but the others should be errors. assert!(reactor.remove_callback(output, callback).is_ok()); for _ in 1..5 { @@ -367,9 +383,11 @@ fn callbacks_should_only_be_called_once_even_if_multiple_dependencies_change() { |v| v[0] * v[1], ) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 4)); cb.expect_to_have_been_called_with(10); } @@ -392,9 +410,11 @@ fn callbacks_should_not_be_called_if_dependencies_change_but_output_value_doesnt |v| v[0] - v[1], ) .unwrap(); - assert!(reactor - .add_callback(always_two, |v| cb.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(always_two, |v| cb.callback_called(v)) + .is_some() + ); for i in 2..5 { assert!(reactor.set_value(input, i)); cb.expect_not_to_have_been_called(); @@ -403,7 +423,7 @@ fn callbacks_should_not_be_called_if_dependencies_change_but_output_value_doesnt #[test] #[ignore] -fn test_adder_with_boolean_values() { +fn adder_with_boolean_values() { // This is a digital logic circuit called an adder: // https://en.wikipedia.org/wiki/Adder_(electronics) let mut reactor = Reactor::new(); diff --git a/exercises/practice/rectangles/.docs/instructions.md b/exercises/practice/rectangles/.docs/instructions.md index e1efd7473..8eb4ed470 100644 --- a/exercises/practice/rectangles/.docs/instructions.md +++ b/exercises/practice/rectangles/.docs/instructions.md @@ -10,7 +10,7 @@ Count the rectangles in an ASCII diagram like the one below. +--+--+ ``` -The above diagram contains 6 rectangles: +The above diagram contains these 6 rectangles: ```text @@ -60,5 +60,4 @@ The above diagram contains 6 rectangles: ``` -You may assume that the input is always a proper rectangle (i.e. the length of -every line equals the length of the first line). +You may assume that the input is always a proper rectangle (i.e. the length of every line equals the length of the first line). diff --git a/exercises/practice/rectangles/.gitignore b/exercises/practice/rectangles/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/rectangles/.gitignore +++ b/exercises/practice/rectangles/.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/rectangles/.meta/example.rs b/exercises/practice/rectangles/.meta/example.rs index 001455c5c..38673622c 100644 --- a/exercises/practice/rectangles/.meta/example.rs +++ b/exercises/practice/rectangles/.meta/example.rs @@ -70,10 +70,12 @@ impl Area for RealArea { struct TransposedArea<'a>(&'a RealArea); // For vertical scanning -impl<'a> Area for TransposedArea<'a> { +impl Area for TransposedArea<'_> { + #[allow(clippy::misnamed_getters)] fn width(&self) -> usize { self.0.height } + #[allow(clippy::misnamed_getters)] fn height(&self) -> usize { self.0.width } diff --git a/exercises/practice/rectangles/.meta/test_template.tera b/exercises/practice/rectangles/.meta/test_template.tera new file mode 100644 index 000000000..fed71856c --- /dev/null +++ b/exercises/practice/rectangles/.meta/test_template.tera @@ -0,0 +1,19 @@ +use rectangles::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% if test.input.strings | length > 1 -%} + #[rustfmt::skip] + {%- endif %} + let input = &[ + {%- for row in test.input.strings %} + {{ row | json_encode }}, + {%- endfor %} + ]; + let output = count(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/rectangles/.meta/tests.toml b/exercises/practice/rectangles/.meta/tests.toml index be690e975..282015033 100644 --- a/exercises/practice/rectangles/.meta/tests.toml +++ b/exercises/practice/rectangles/.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. + +[485b7bab-4150-40aa-a8db-73013427d08c] +description = "no rows" + +[076929ed-27e8-45dc-b14b-08279944dc49] +description = "no columns" + +[0a8abbd1-a0a4-4180-aa4e-65c1b1a073fa] +description = "no rectangles" + +[a4ba42e9-4e7f-4973-b7c7-4ce0760ac6cd] +description = "one rectangle" + +[ced06550-83da-4d23-98b7-d24152e0db93] +description = "two rectangles without shared parts" + +[5942d69a-a07c-41c8-8b93-2d13877c706a] +description = "five rectangles with shared parts" + +[82d70be4-ab37-4bf2-a433-e33778d3bbf1] +description = "rectangle of height 1 is counted" + +[57f1bc0e-2782-401e-ab12-7c01d8bfc2e0] +description = "rectangle of width 1 is counted" + +[ef0bb65c-bd80-4561-9535-efc4067054f9] +description = "1x1 square is counted" + +[e1e1d444-e926-4d30-9bf3-7d8ec9a9e330] +description = "only complete rectangles are counted" + +[ca021a84-1281-4a56-9b9b-af14113933a4] +description = "rectangles can be of different sizes" + +[51f689a7-ef3f-41ae-aa2f-5ea09ad897ff] +description = "corner is required for a rectangle to be complete" + +[d78fe379-8c1b-4d3c-bdf7-29bfb6f6dc66] +description = "large input with many rectangles" + +[6ef24e0f-d191-46da-b929-4faca24b4cd2] +description = "rectangles must have four sides" diff --git a/exercises/practice/rectangles/Cargo.toml b/exercises/practice/rectangles/Cargo.toml index 320d33678..27b83075f 100644 --- a/exercises/practice/rectangles/Cargo.toml +++ b/exercises/practice/rectangles/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "rectangles" -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/rectangles/src/lib.rs b/exercises/practice/rectangles/src/lib.rs index 3644efa33..90fe2c037 100644 --- a/exercises/practice/rectangles/src/lib.rs +++ b/exercises/practice/rectangles/src/lib.rs @@ -1,3 +1,5 @@ pub fn count(lines: &[&str]) -> u32 { - unimplemented!("\nDetermine the count of rectangles in the ASCII diagram represented by the following lines:\n{lines:#?}\n."); + todo!( + "\nDetermine the count of rectangles in the ASCII diagram represented by the following lines:\n{lines:#?}\n." + ); } diff --git a/exercises/practice/rectangles/tests/rectangles.rs b/exercises/practice/rectangles/tests/rectangles.rs index e8863eb8b..954268284 100644 --- a/exercises/practice/rectangles/tests/rectangles.rs +++ b/exercises/practice/rectangles/tests/rectangles.rs @@ -1,143 +1,170 @@ -use rectangles::count; +use rectangles::*; #[test] -fn test_zero_area_1() { - let lines = &[]; - assert_eq!(0, count(lines)) +fn no_rows() { + let input = &[]; + let output = count(input); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_zero_area_2() { - let lines = &[""]; - assert_eq!(0, count(lines)) +fn no_columns() { + let input = &[""]; + let output = count(input); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_empty_area() { - let lines = &[" "]; - assert_eq!(0, count(lines)) +fn no_rectangles() { + let input = &[" "]; + let output = count(input); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_one_rectangle() { +fn one_rectangle() { #[rustfmt::skip] - let lines = &[ + let input = &[ "+-+", "| |", "+-+", ]; - assert_eq!(1, count(lines)) + let output = count(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_two_rectangles_no_shared_parts() { +fn two_rectangles_without_shared_parts() { #[rustfmt::skip] - let lines = &[ + let input = &[ " +-+", " | |", "+-+-+", "| | ", "+-+ ", ]; - assert_eq!(2, count(lines)) + let output = count(input); + let expected = 2; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_five_rectangles_three_regions() { +fn five_rectangles_with_shared_parts() { #[rustfmt::skip] - let lines = &[ + let input = &[ " +-+", " | |", "+-+-+", "| | |", "+-+-+", ]; - assert_eq!(5, count(lines)) + let output = count(input); + let expected = 5; + assert_eq!(output, expected); } #[test] #[ignore] -fn rectangle_of_height_1() { +fn rectangle_of_height_1_is_counted() { #[rustfmt::skip] - let lines = &[ + let input = &[ "+--+", "+--+", ]; - assert_eq!(1, count(lines)) + let output = count(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn rectangle_of_width_1() { +fn rectangle_of_width_1_is_counted() { #[rustfmt::skip] - let lines = &[ + let input = &[ "++", "||", "++", ]; - assert_eq!(1, count(lines)) + let output = count(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn unit_square() { +fn test_1x1_square_is_counted() { #[rustfmt::skip] - let lines = &[ + let input = &[ "++", "++", ]; - assert_eq!(1, count(lines)) + let output = count(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_incomplete_rectangles() { +fn only_complete_rectangles_are_counted() { #[rustfmt::skip] - let lines = &[ + let input = &[ " +-+", " |", "+-+-+", "| | -", "+-+-+", ]; - assert_eq!(1, count(lines)) + let output = count(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_complicated() { - let lines = &[ +fn rectangles_can_be_of_different_sizes() { + #[rustfmt::skip] + let input = &[ "+------+----+", "| | |", "+---+--+ |", "| | |", "+---+-------+", ]; - assert_eq!(3, count(lines)) + let output = count(input); + let expected = 3; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_not_so_complicated() { - let lines = &[ +fn corner_is_required_for_a_rectangle_to_be_complete() { + #[rustfmt::skip] + let input = &[ "+------+----+", "| | |", "+------+ |", "| | |", "+---+-------+", ]; - assert_eq!(2, count(lines)) + let output = count(input); + let expected = 2; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_large_input_with_many_rectangles() { - let lines = &[ +fn large_input_with_many_rectangles() { + #[rustfmt::skip] + let input = &[ "+---+--+----+", "| +--+----+", "+---+--+ |", @@ -147,19 +174,25 @@ fn test_large_input_with_many_rectangles() { "+------+ | |", " +-+", ]; - assert_eq!(60, count(lines)) + let output = count(input); + let expected = 60; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_three_rectangles_no_shared_parts() { +fn rectangles_must_have_four_sides() { #[rustfmt::skip] - let lines = &[ - " +-+ ", + let input = &[ + "+-+ +-+", + "| | | |", + "+-+-+-+", " | | ", "+-+-+-+", "| | | |", "+-+ +-+", ]; - assert_eq!(3, count(lines)) + let output = count(input); + let expected = 5; + assert_eq!(output, expected); } diff --git a/exercises/practice/reverse-string/.docs/instructions.append.md b/exercises/practice/reverse-string/.docs/instructions.append.md index e13803cd4..1c877a155 100644 --- a/exercises/practice/reverse-string/.docs/instructions.append.md +++ b/exercises/practice/reverse-string/.docs/instructions.append.md @@ -1,14 +1,20 @@ -# Bonus -Test your function on this string: `uüu` and see what happens. Try to write a function that properly -reverses this string. Hint: grapheme clusters +# Instructions append -To get the bonus test to run, remove the ignore flag (`#[ignore]`) from the -last test, and execute the tests with: +## Bonus + +Test your function on this string: `uüu` and see what happens. +Try to write a function that properly reverses this string. +Hint: grapheme clusters + +To get the bonus test to run, remove the ignore flag (`#[ignore]`) from the last test, and execute the tests with: ```bash -$ cargo test --features grapheme +cargo test --features grapheme ``` -You will need to use external libraries (a `crate` in rust lingo) for the bonus task. A good place to look for those is [crates.io](https://crates.io/), the official repository of crates. +You will need to use external libraries (a `crate` in rust lingo) for the bonus task. +A good place to look for those is [crates.io](https://crates.io/), the official repository of crates. +Please remember that only a limited set of crates is supported by our test runner. +The full list is in [this file](https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml) under the section `[dependencies]`. [Check the documentation](https://doc.rust-lang.org/cargo/guide/dependencies.html) for instructions on how to use external crates in your projects. diff --git a/exercises/practice/reverse-string/.docs/instructions.md b/exercises/practice/reverse-string/.docs/instructions.md index 039ee33ae..0ff4198e4 100644 --- a/exercises/practice/reverse-string/.docs/instructions.md +++ b/exercises/practice/reverse-string/.docs/instructions.md @@ -1,7 +1,9 @@ # Instructions -Reverse a string +Your task is to reverse a given string. -For example: -input: "cool" -output: "looc" +Some examples: + +- Turn `"stressed"` into `"desserts"`. +- Turn `"strops"` into `"sports"`. +- Turn `"racecar"` into `"racecar"`. diff --git a/exercises/practice/reverse-string/.docs/introduction.md b/exercises/practice/reverse-string/.docs/introduction.md new file mode 100644 index 000000000..02233e075 --- /dev/null +++ b/exercises/practice/reverse-string/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +Reversing strings (reading them from right to left, rather than from left to right) is a surprisingly common task in programming. + +For example, in bioinformatics, reversing the sequence of DNA or RNA strings is often important for various analyses, such as finding complementary strands or identifying palindromic sequences that have biological significance. diff --git a/exercises/practice/reverse-string/.gitignore b/exercises/practice/reverse-string/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/reverse-string/.gitignore +++ b/exercises/practice/reverse-string/.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/reverse-string/.meta/config.json b/exercises/practice/reverse-string/.meta/config.json index 054b915c3..dc879630e 100644 --- a/exercises/practice/reverse-string/.meta/config.json +++ b/exercises/practice/reverse-string/.meta/config.json @@ -26,13 +26,13 @@ "Cargo.toml" ], "test": [ - "tests/reverse-string.rs" + "tests/reverse_string.rs" ], "example": [ ".meta/example.rs" ] }, - "blurb": "Reverse a string.", + "blurb": "Reverse a given string.", "source": "Introductory challenge to reverse an input string", "source_url": "https://medium.freecodecamp.org/how-to-reverse-a-string-in-javascript-in-3-different-ways-75e4763c68cb" } diff --git a/exercises/practice/reverse-string/.meta/test_template.tera b/exercises/practice/reverse-string/.meta/test_template.tera new file mode 100644 index 000000000..1ddf9e050 --- /dev/null +++ b/exercises/practice/reverse-string/.meta/test_template.tera @@ -0,0 +1,15 @@ +use reverse_string::*; + +{% for test in cases %} +#[test] +#[ignore] +{% if test.description is starting_with("grapheme cluster") -%} +#[cfg(feature = "grapheme")] +{% endif -%} +fn {{ test.description | make_ident }}() { + let input = {{ test.input.value | json_encode() }}; + let output = reverse(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/reverse-string/.meta/tests.toml b/exercises/practice/reverse-string/.meta/tests.toml index be690e975..0c313cc53 100644 --- a/exercises/practice/reverse-string/.meta/tests.toml +++ b/exercises/practice/reverse-string/.meta/tests.toml @@ -1,3 +1,37 @@ -# 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. + +[c3b7d806-dced-49ee-8543-933fd1719b1c] +description = "an empty string" + +[01ebf55b-bebb-414e-9dec-06f7bb0bee3c] +description = "a word" + +[0f7c07e4-efd1-4aaa-a07a-90b49ce0b746] +description = "a capitalized word" + +[71854b9c-f200-4469-9f5c-1e8e5eff5614] +description = "a sentence with punctuation" + +[1f8ed2f3-56f3-459b-8f3e-6d8d654a1f6c] +description = "a palindrome" + +[b9e7dec1-c6df-40bd-9fa3-cd7ded010c4c] +description = "an even-sized word" + +[1bed0f8a-13b0-4bd3-9d59-3d0593326fa2] +description = "wide characters" + +[93d7e1b8-f60f-4f3c-9559-4056e10d2ead] +description = "grapheme cluster with pre-combined form" + +[1028b2c1-6763-4459-8540-2da47ca512d9] +description = "grapheme clusters" diff --git a/exercises/practice/reverse-string/Cargo.toml b/exercises/practice/reverse-string/Cargo.toml index 52b902c82..30529e8cb 100644 --- a/exercises/practice/reverse-string/Cargo.toml +++ b/exercises/practice/reverse-string/Cargo.toml @@ -1,9 +1,12 @@ +[package] +name = "reverse_string" +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] grapheme = [] - -[package] -edition = "2021" -name = "reverse_string" -version = "1.2.0" diff --git a/exercises/practice/reverse-string/src/lib.rs b/exercises/practice/reverse-string/src/lib.rs index bdfce8c1f..01e0d4dc8 100644 --- a/exercises/practice/reverse-string/src/lib.rs +++ b/exercises/practice/reverse-string/src/lib.rs @@ -1,3 +1,3 @@ pub fn reverse(input: &str) -> String { - unimplemented!("Write a function to reverse {input}"); + todo!("Write a function to reverse {input}"); } diff --git a/exercises/practice/reverse-string/tests/reverse-string.rs b/exercises/practice/reverse-string/tests/reverse-string.rs deleted file mode 100644 index b6f5b85e3..000000000 --- a/exercises/practice/reverse-string/tests/reverse-string.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Tests for reverse-string -//! -//! Generated by [script][script] using [canonical data][canonical-data] -//! -//! [script]: https://github.com/exercism/rust/blob/b829ce2/bin/init_exercise.py -//! [canonical-data]: https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/reverse-string/canonical_data.json - -use reverse_string::*; - -/// Process a single test case for the property `reverse` -fn process_reverse_case(input: &str, expected: &str) { - assert_eq!(&reverse(input), expected) -} - -#[test] -/// empty string -fn test_an_empty_string() { - process_reverse_case("", ""); -} - -#[test] -#[ignore] -/// a word -fn test_a_word() { - process_reverse_case("robot", "tobor"); -} - -#[test] -#[ignore] -/// a capitalized word -fn test_a_capitalized_word() { - process_reverse_case("Ramen", "nemaR"); -} - -#[test] -#[ignore] -/// a sentence with punctuation -fn test_a_sentence_with_punctuation() { - process_reverse_case("I'm hungry!", "!yrgnuh m'I"); -} - -#[test] -#[ignore] -/// a palindrome -fn test_a_palindrome() { - process_reverse_case("racecar", "racecar"); -} - -#[test] -#[ignore] -/// an even-sized word -fn test_an_even_sized_word() { - process_reverse_case("drawer", "reward"); -} - -#[test] -#[ignore] -/// wide characters -fn test_wide_characters() { - process_reverse_case("子猫", "猫子"); -} - -#[test] -#[ignore] -#[cfg(feature = "grapheme")] -/// grapheme clusters -fn test_grapheme_clusters() { - process_reverse_case("uüu", "uüu"); -} diff --git a/exercises/practice/reverse-string/tests/reverse_string.rs b/exercises/practice/reverse-string/tests/reverse_string.rs new file mode 100644 index 000000000..975204573 --- /dev/null +++ b/exercises/practice/reverse-string/tests/reverse_string.rs @@ -0,0 +1,83 @@ +use reverse_string::*; + +#[test] +fn an_empty_string() { + let input = ""; + let output = reverse(input); + let expected = ""; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn a_word() { + let input = "robot"; + let output = reverse(input); + let expected = "tobor"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn a_capitalized_word() { + let input = "Ramen"; + let output = reverse(input); + let expected = "nemaR"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn a_sentence_with_punctuation() { + let input = "I'm hungry!"; + let output = reverse(input); + let expected = "!yrgnuh m'I"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn a_palindrome() { + let input = "racecar"; + let output = reverse(input); + let expected = "racecar"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn an_even_sized_word() { + let input = "drawer"; + let output = reverse(input); + let expected = "reward"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn wide_characters() { + let input = "子猫"; + let output = reverse(input); + let expected = "猫子"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +#[cfg(feature = "grapheme")] +fn grapheme_cluster_with_pre_combined_form() { + let input = "Würstchenstand"; + let output = reverse(input); + let expected = "dnatsnehctsrüW"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +#[cfg(feature = "grapheme")] +fn grapheme_clusters() { + let input = "ผู้เขียนโปรแกรม"; + let output = reverse(input); + let expected = "มรกแรปโนยขีเผู้"; + assert_eq!(output, expected); +} diff --git a/exercises/practice/rna-transcription/.docs/instructions.append.md b/exercises/practice/rna-transcription/.docs/instructions.append.md index 05c469325..1273336ec 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.append.md +++ b/exercises/practice/rna-transcription/.docs/instructions.append.md @@ -1,4 +1,6 @@ -# Notes on Rust implementation +# Instructions append + +## Notes on Rust implementation By using private fields in structs with public `new` functions returning `Option` or `Result` (as here with `DNA::new` & `RNA::new`), we can guarantee @@ -7,3 +9,7 @@ string has a valid RNA string, we don't need to return a `Result`/`Option` from `into_rna`. This explains the type signatures you will see in the tests. + +The return types of both `DNA::new()` and `RNA::new()` are `Result`, +where the error type `usize` represents the index of the first invalid character +(char index, not utf8). diff --git a/exercises/practice/rna-transcription/.docs/instructions.md b/exercises/practice/rna-transcription/.docs/instructions.md index 9e86efea9..4dbfd3a27 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.md +++ b/exercises/practice/rna-transcription/.docs/instructions.md @@ -1,19 +1,20 @@ # Instructions -Given a DNA strand, return its RNA complement (per RNA transcription). +Your task is to determine the RNA complement of a given DNA sequence. Both DNA and RNA strands are a sequence of nucleotides. -The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), -guanine (**G**) and thymine (**T**). +The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), guanine (**G**), and thymine (**T**). -The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), -guanine (**G**) and uracil (**U**). +The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), guanine (**G**), and uracil (**U**). -Given a DNA strand, its transcribed RNA strand is formed by replacing -each nucleotide with its complement: +Given a DNA strand, its transcribed RNA strand is formed by replacing each nucleotide with its complement: -* `G` -> `C` -* `C` -> `G` -* `T` -> `A` -* `A` -> `U` +- `G` -> `C` +- `C` -> `G` +- `T` -> `A` +- `A` -> `U` + +~~~~exercism/note +If you want to look at how the inputs and outputs are structured, take a look at the examples in the test suite. +~~~~ diff --git a/exercises/practice/rna-transcription/.docs/introduction.md b/exercises/practice/rna-transcription/.docs/introduction.md new file mode 100644 index 000000000..6b3f44b53 --- /dev/null +++ b/exercises/practice/rna-transcription/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a bioengineering company that specializes in developing therapeutic solutions. + +Your team has just been given a new project to develop a targeted therapy for a rare type of cancer. + +~~~~exercism/note +It's all very complicated, but the basic idea is that sometimes people's bodies produce too much of a given protein. +That can cause all sorts of havoc. + +But if you can create a very specific molecule (called a micro-RNA), it can prevent the protein from being produced. + +This technique is called [RNA Interference][rnai]. + +[rnai]: https://admin.acceleratingscience.com/ask-a-scientist/what-is-rnai/ +~~~~ diff --git a/exercises/practice/rna-transcription/.gitignore b/exercises/practice/rna-transcription/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/rna-transcription/.gitignore +++ b/exercises/practice/rna-transcription/.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/rna-transcription/.meta/additional-tests.json b/exercises/practice/rna-transcription/.meta/additional-tests.json new file mode 100644 index 000000000..f1e449cb2 --- /dev/null +++ b/exercises/practice/rna-transcription/.meta/additional-tests.json @@ -0,0 +1,54 @@ +[ + { + "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", + "description": "Invalid DNA input", + "comments": [ + "Upstream does not focus on error handling,", + "but this is a big focus for Rust." + ], + "property": "invalidDna", + "input": { + "dna": "U" + }, + "expected": 0 + }, + { + "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", + "description": "Invalid DNA input at offset", + "comments": [ + "Upstream does not focus on error handling,", + "but this is a big focus for Rust." + ], + "property": "invalidDna", + "input": { + "dna": "ACGTUXXCTTAA" + }, + "expected": 4 + }, + { + "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", + "description": "Invalid RNA input", + "comments": [ + "Upstream does not focus on error handling,", + "but this is a big focus for Rust." + ], + "property": "invalidRna", + "input": { + "dna": "T" + }, + "expected": 0 + }, + { + "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", + "description": "Invalid RNA input at offset", + "comments": [ + "Upstream does not focus on error handling,", + "but this is a big focus for Rust." + ], + "property": "invalidRna", + "input": { + "dna": "ACGTUXXCTTAA" + }, + "expected": 3 + } +] diff --git a/exercises/practice/rna-transcription/.meta/config.json b/exercises/practice/rna-transcription/.meta/config.json index 4d3b4e329..f22ab44cc 100644 --- a/exercises/practice/rna-transcription/.meta/config.json +++ b/exercises/practice/rna-transcription/.meta/config.json @@ -35,13 +35,13 @@ "Cargo.toml" ], "test": [ - "tests/rna-transcription.rs" + "tests/rna_transcription.rs" ], "example": [ ".meta/example.rs" ] }, - "blurb": "Given a DNA strand, return its RNA Complement Transcription.", + "blurb": "Given a DNA strand, return its RNA complement.", "source": "Hyperphysics", - "source_url": "http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" + "source_url": "https://web.archive.org/web/20220408112140/http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" } diff --git a/exercises/practice/rna-transcription/.meta/test_template.tera b/exercises/practice/rna-transcription/.meta/test_template.tera new file mode 100644 index 000000000..8f3f2b8ce --- /dev/null +++ b/exercises/practice/rna-transcription/.meta/test_template.tera @@ -0,0 +1,19 @@ +use rna_transcription::{Dna, Rna}; +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.dna | json_encode() }}; +{% if test.property == "invalidDna" -%} + let output = Dna::new(input); + let expected = Err({{ test.expected }}); +{%- elif test.property == "invalidRna" -%} + let output = Rna::new(input); + let expected = Err({{ test.expected }}); +{%- else -%} + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new({{ test.expected | json_encode() }}).unwrap(); +{%- endif %} + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/rna-transcription/.meta/tests.toml b/exercises/practice/rna-transcription/.meta/tests.toml index be690e975..680051407 100644 --- a/exercises/practice/rna-transcription/.meta/tests.toml +++ b/exercises/practice/rna-transcription/.meta/tests.toml @@ -1,3 +1,28 @@ -# 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. + +[b4631f82-c98c-4a2f-90b3-c5c2b6c6f661] +description = "Empty RNA sequence" + +[a9558a3c-318c-4240-9256-5d5ed47005a6] +description = "RNA complement of cytosine is guanine" + +[6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7] +description = "RNA complement of guanine is cytosine" + +[870bd3ec-8487-471d-8d9a-a25046488d3e] +description = "RNA complement of thymine is adenine" + +[aade8964-02e1-4073-872f-42d3ffd74c5f] +description = "RNA complement of adenine is uracil" + +[79ed2757-f018-4f47-a1d7-34a559392dbf] +description = "RNA complement" diff --git a/exercises/practice/rna-transcription/Cargo.toml b/exercises/practice/rna-transcription/Cargo.toml index 6307cb6e1..acb68cde0 100644 --- a/exercises/practice/rna-transcription/Cargo.toml +++ b/exercises/practice/rna-transcription/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "rna-transcription" -version = "1.0.0" +name = "rna_transcription" +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/rna-transcription/src/lib.rs b/exercises/practice/rna-transcription/src/lib.rs index ec9c7bfc7..c235cfad3 100644 --- a/exercises/practice/rna-transcription/src/lib.rs +++ b/exercises/practice/rna-transcription/src/lib.rs @@ -6,16 +6,20 @@ pub struct Rna; impl Dna { pub fn new(dna: &str) -> Result { - unimplemented!("Construct new Dna from '{dna}' string. If string contains invalid nucleotides return index of first invalid nucleotide"); + todo!( + "Construct new Dna from '{dna}' string. If string contains invalid nucleotides return index of first invalid nucleotide" + ); } pub fn into_rna(self) -> Rna { - unimplemented!("Transform Dna {self:?} into corresponding Rna"); + todo!("Transform Dna {self:?} into corresponding Rna"); } } impl Rna { pub fn new(rna: &str) -> Result { - unimplemented!("Construct new Rna from '{rna}' string. If string contains invalid nucleotides return index of first invalid nucleotide"); + todo!( + "Construct new Rna from '{rna}' string. If string contains invalid nucleotides return index of first invalid nucleotide" + ); } } diff --git a/exercises/practice/rna-transcription/tests/rna-transcription.rs b/exercises/practice/rna-transcription/tests/rna-transcription.rs deleted file mode 100644 index 11c727f38..000000000 --- a/exercises/practice/rna-transcription/tests/rna-transcription.rs +++ /dev/null @@ -1,88 +0,0 @@ -use rna_transcription as dna; - -#[test] -fn test_valid_dna_input() { - assert!(dna::Dna::new("GCTA").is_ok()); -} - -#[test] -#[ignore] -fn test_valid_rna_input() { - assert!(dna::Rna::new("CGAU").is_ok()); -} - -#[test] -#[ignore] -fn test_invalid_dna_input() { - // Invalid character - assert_eq!(dna::Dna::new("X").err(), Some(0)); - // Valid nucleotide, but invalid in context - assert_eq!(dna::Dna::new("U").err(), Some(0)); - // Longer string with contained errors - assert_eq!(dna::Dna::new("ACGTUXXCTTAA").err(), Some(4)); -} - -#[test] -#[ignore] -fn test_invalid_rna_input() { - // Invalid character - assert_eq!(dna::Rna::new("X").unwrap_err(), 0); - // Valid nucleotide, but invalid in context - assert_eq!(dna::Rna::new("T").unwrap_err(), 0); - // Longer string with contained errors - assert_eq!(dna::Rna::new("ACGUTTXCUUAA").unwrap_err(), 4); -} - -#[test] -#[ignore] -fn test_acid_equals_acid() { - assert_eq!(dna::Dna::new("CGA").unwrap(), dna::Dna::new("CGA").unwrap()); - assert_ne!(dna::Dna::new("CGA").unwrap(), dna::Dna::new("AGC").unwrap()); - assert_eq!(dna::Rna::new("CGA").unwrap(), dna::Rna::new("CGA").unwrap()); - assert_ne!(dna::Rna::new("CGA").unwrap(), dna::Rna::new("AGC").unwrap()); -} - -#[test] -#[ignore] -fn test_transcribes_cytosine_guanine() { - assert_eq!( - dna::Rna::new("G").unwrap(), - dna::Dna::new("C").unwrap().into_rna() - ); -} - -#[test] -#[ignore] -fn test_transcribes_guanine_cytosine() { - assert_eq!( - dna::Rna::new("C").unwrap(), - dna::Dna::new("G").unwrap().into_rna() - ); -} - -#[test] -#[ignore] -fn test_transcribes_adenine_uracil() { - assert_eq!( - dna::Rna::new("U").unwrap(), - dna::Dna::new("A").unwrap().into_rna() - ); -} - -#[test] -#[ignore] -fn test_transcribes_thymine_to_adenine() { - assert_eq!( - dna::Rna::new("A").unwrap(), - dna::Dna::new("T").unwrap().into_rna() - ); -} - -#[test] -#[ignore] -fn test_transcribes_all_dna_to_rna() { - assert_eq!( - dna::Rna::new("UGCACCAGAAUU").unwrap(), - dna::Dna::new("ACGTGGTCTTAA").unwrap().into_rna() - ) -} diff --git a/exercises/practice/rna-transcription/tests/rna_transcription.rs b/exercises/practice/rna-transcription/tests/rna_transcription.rs new file mode 100644 index 000000000..4d16cf279 --- /dev/null +++ b/exercises/practice/rna-transcription/tests/rna_transcription.rs @@ -0,0 +1,90 @@ +use rna_transcription::{Dna, Rna}; + +#[test] +fn empty_rna_sequence() { + let input = ""; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("").unwrap(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rna_complement_of_cytosine_is_guanine() { + let input = "C"; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("G").unwrap(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rna_complement_of_guanine_is_cytosine() { + let input = "G"; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("C").unwrap(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rna_complement_of_thymine_is_adenine() { + let input = "T"; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("A").unwrap(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rna_complement_of_adenine_is_uracil() { + let input = "A"; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("U").unwrap(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rna_complement() { + let input = "ACGTGGTCTTAA"; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("UGCACCAGAAUU").unwrap(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn invalid_dna_input() { + let input = "U"; + let output = Dna::new(input); + let expected = Err(0); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn invalid_dna_input_at_offset() { + let input = "ACGTUXXCTTAA"; + let output = Dna::new(input); + let expected = Err(4); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn invalid_rna_input() { + let input = "T"; + let output = Rna::new(input); + let expected = Err(0); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn invalid_rna_input_at_offset() { + let input = "ACGTUXXCTTAA"; + let output = Rna::new(input); + let expected = Err(3); + assert_eq!(output, expected); +} diff --git a/exercises/practice/robot-name/.docs/instructions.append.md b/exercises/practice/robot-name/.docs/instructions.append.md new file mode 100644 index 000000000..48930b13d --- /dev/null +++ b/exercises/practice/robot-name/.docs/instructions.append.md @@ -0,0 +1,9 @@ +# Instructions append + +## Global Mutable State + +The way the tests are setup, you will be forced to use global mutable state. +This is generally frowned-upon and considered unidiomatic in Rust. +However, it's a delibarate choice for the purpose of learning here. + +Can you think of a better API design that doesn't use global mutable state? diff --git a/exercises/practice/robot-name/.docs/instructions.md b/exercises/practice/robot-name/.docs/instructions.md index a0079a341..fca3a41ae 100644 --- a/exercises/practice/robot-name/.docs/instructions.md +++ b/exercises/practice/robot-name/.docs/instructions.md @@ -4,13 +4,11 @@ Manage robot factory settings. When a robot comes off the factory floor, it has no name. -The first time you turn on a robot, a random name is generated in the format -of two uppercase letters followed by three digits, such as RX837 or BC811. +The first time you turn on a robot, a random name is generated in the format of two uppercase letters followed by three digits, such as RX837 or BC811. -Every once in a while we need to reset a robot to its factory settings, -which means that its name gets wiped. The next time you ask, that robot will -respond with a new random name. +Every once in a while we need to reset a robot to its factory settings, which means that its name gets wiped. +The next time you ask, that robot will respond with a new random name. The names must be random: they should not follow a predictable sequence. -Using random names means a risk of collisions. Your solution must ensure that -every existing robot has a unique name. +Using random names means a risk of collisions. +Your solution must ensure that every existing robot has a unique name. diff --git a/exercises/practice/robot-name/.gitignore b/exercises/practice/robot-name/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/robot-name/.gitignore +++ b/exercises/practice/robot-name/.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/robot-name/.meta/Cargo-example.toml b/exercises/practice/robot-name/.meta/Cargo-example.toml index be60f1e17..bceec03b5 100644 --- a/exercises/practice/robot-name/.meta/Cargo-example.toml +++ b/exercises/practice/robot-name/.meta/Cargo-example.toml @@ -1,8 +1,10 @@ [package] -edition = "2021" -name = "robot-name" -version = "0.0.0" +name = "robot_name" +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] -lazy_static = "1.4.0" rand = "0.3.12" diff --git a/exercises/practice/robot-name/.meta/config.json b/exercises/practice/robot-name/.meta/config.json index 41a1906b8..c5d2fa26d 100644 --- a/exercises/practice/robot-name/.meta/config.json +++ b/exercises/practice/robot-name/.meta/config.json @@ -34,7 +34,7 @@ "Cargo.toml" ], "test": [ - "tests/robot-name.rs" + "tests/robot_name.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/robot-name/.meta/example.rs b/exercises/practice/robot-name/.meta/example.rs index efed25d51..487312a77 100644 --- a/exercises/practice/robot-name/.meta/example.rs +++ b/exercises/practice/robot-name/.meta/example.rs @@ -1,11 +1,11 @@ -use lazy_static::lazy_static; -use rand::{thread_rng, Rng}; -use std::collections::HashSet; -use std::sync::Mutex; +use std::{ + collections::HashSet, + sync::{LazyLock, Mutex}, +}; -lazy_static! { - static ref NAMES: Mutex> = Mutex::new(HashSet::new()); -} +use rand::{Rng, thread_rng}; + +static NAMES: LazyLock>> = LazyLock::new(|| Mutex::new(HashSet::new())); pub struct Robot { name: String, @@ -44,3 +44,9 @@ impl Robot { self.name = generate_name(); } } + +impl Default for Robot { + fn default() -> Self { + Self::new() + } +} diff --git a/exercises/practice/robot-name/Cargo.toml b/exercises/practice/robot-name/Cargo.toml index 97c58d658..d584df2f0 100644 --- a/exercises/practice/robot-name/Cargo.toml +++ b/exercises/practice/robot-name/Cargo.toml @@ -1,6 +1,12 @@ [package] -edition = "2021" -name = "robot-name" -version = "0.0.0" +name = "robot_name" +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/robot-name/src/lib.rs b/exercises/practice/robot-name/src/lib.rs index a2e05a17b..37bf158ed 100644 --- a/exercises/practice/robot-name/src/lib.rs +++ b/exercises/practice/robot-name/src/lib.rs @@ -2,14 +2,14 @@ pub struct Robot; impl Robot { pub fn new() -> Self { - unimplemented!("Construct a new Robot struct."); + todo!("Construct a new Robot struct."); } pub fn name(&self) -> &str { - unimplemented!("Return the reference to the robot's name."); + todo!("Return the reference to the robot's name."); } pub fn reset_name(&mut self) { - unimplemented!("Assign a new unique name to the robot."); + todo!("Assign a new unique name to the robot."); } } diff --git a/exercises/practice/robot-name/tests/robot-name.rs b/exercises/practice/robot-name/tests/robot_name.rs similarity index 65% rename from exercises/practice/robot-name/tests/robot-name.rs rename to exercises/practice/robot-name/tests/robot_name.rs index ab848297d..abfeadd3f 100644 --- a/exercises/practice/robot-name/tests/robot-name.rs +++ b/exercises/practice/robot-name/tests/robot_name.rs @@ -12,30 +12,15 @@ fn assert_name_matches_pattern(n: &str) { ); } -fn assert_name_is_persistent(r: &robot::Robot) { - // The type system already proves this, but why not. - let n1 = r.name(); - let n2 = r.name(); - let n3 = r.name(); - assert_eq!(n1, n2); - assert_eq!(n2, n3); -} - #[test] -fn test_name_should_match_expected_pattern() { +fn name_should_match_expected_pattern() { let r = robot::Robot::new(); assert_name_matches_pattern(r.name()); } #[test] #[ignore] -fn test_name_is_persistent() { - assert_name_is_persistent(&robot::Robot::new()); -} - -#[test] -#[ignore] -fn test_different_robots_have_different_names() { +fn different_robots_have_different_names() { let r1 = robot::Robot::new(); let r2 = robot::Robot::new(); assert_ne!(r1.name(), r2.name(), "Robot names should be different"); @@ -43,7 +28,7 @@ fn test_different_robots_have_different_names() { #[test] #[ignore] -fn test_many_different_robots_have_different_names() { +fn many_different_robots_have_different_names() { use std::collections::HashSet; // In 3,529 random robot names, there is ~99.99% chance of a name collision @@ -56,7 +41,7 @@ fn test_many_different_robots_have_different_names() { #[test] #[ignore] -fn test_new_name_should_match_expected_pattern() { +fn new_name_should_match_expected_pattern() { let mut r = robot::Robot::new(); assert_name_matches_pattern(r.name()); r.reset_name(); @@ -65,15 +50,7 @@ fn test_new_name_should_match_expected_pattern() { #[test] #[ignore] -fn test_new_name_is_persistent() { - let mut r = robot::Robot::new(); - r.reset_name(); - assert_name_is_persistent(&r); -} - -#[test] -#[ignore] -fn test_new_name_is_different_from_old_name() { +fn new_name_is_different_from_old_name() { let mut r = robot::Robot::new(); let n1 = r.name().to_string(); r.reset_name(); diff --git a/exercises/practice/robot-simulator/.docs/instructions.md b/exercises/practice/robot-simulator/.docs/instructions.md index 83be50ccc..0ac96ce0b 100644 --- a/exercises/practice/robot-simulator/.docs/instructions.md +++ b/exercises/practice/robot-simulator/.docs/instructions.md @@ -10,13 +10,10 @@ The robots have three possible movements: - turn left - advance -Robots are placed on a hypothetical infinite grid, facing a particular -direction (north, east, south, or west) at a set of {x,y} coordinates, +Robots are placed on a hypothetical infinite grid, facing a particular direction (north, east, south, or west) at a set of {x,y} coordinates, e.g., {3,8}, with coordinates increasing to the north and east. -The robot then receives a number of instructions, at which point the -testing facility verifies the robot's new position, and in which -direction it is pointing. +The robot then receives a number of instructions, at which point the testing facility verifies the robot's new position, and in which direction it is pointing. - The letter-string "RAALAL" means: - Turn right @@ -24,5 +21,5 @@ direction it is pointing. - Turn left - Advance once - Turn left yet again -- Say a robot starts at {7, 3} facing north. Then running this stream - of instructions should leave it at {9, 4} facing west. +- Say a robot starts at {7, 3} facing north. + Then running this stream of instructions should leave it at {9, 4} facing west. diff --git a/exercises/practice/robot-simulator/.gitignore b/exercises/practice/robot-simulator/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/robot-simulator/.gitignore +++ b/exercises/practice/robot-simulator/.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/robot-simulator/.meta/config.json b/exercises/practice/robot-simulator/.meta/config.json index 4439c09e8..4631702e8 100644 --- a/exercises/practice/robot-simulator/.meta/config.json +++ b/exercises/practice/robot-simulator/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/robot-simulator.rs" + "tests/robot_simulator.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/robot-simulator/.meta/test_template.tera b/exercises/practice/robot-simulator/.meta/test_template.tera new file mode 100644 index 000000000..1b01e73a0 --- /dev/null +++ b/exercises/practice/robot-simulator/.meta/test_template.tera @@ -0,0 +1,30 @@ +use robot_simulator::*; + +{% for test_group in cases %} +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { +{%- if test.property == "create" %} + let robot = Robot::new({{ test.input.position.x }}, {{ test.input.position.y }}, Direction::{{ test.input.direction | title }}); + assert_eq!(robot.position(), ({{ test.expected.position.x }}, {{ test.expected.position.y }})); + assert_eq!(robot.direction(), &Direction::{{ test.expected.direction | title }}); +} + {% continue %} +{% endif -%} + + let robot_start = Robot::new({{ test.input.position.x }}, {{ test.input.position.y }}, Direction::{{ test.input.direction | title }}); +{%- if test.input.instructions == "R" %} + let robot_end = robot_start.turn_right(); +{%- elif test.input.instructions == "L" %} + let robot_end = robot_start.turn_left(); +{%- elif test.input.instructions == "A" %} + let robot_end = robot_start.advance(); +{%- else %} + let robot_end = robot_start.instructions({{ test.input.instructions | json_encode() }}); +{% endif -%} + assert_eq!(robot_end.position(), ({{ test.expected.position.x }}, {{ test.expected.position.y }})); + assert_eq!(robot_end.direction(), &Direction::{{ test.expected.direction | title }}); +} +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/robot-simulator/.meta/tests.toml b/exercises/practice/robot-simulator/.meta/tests.toml index be690e975..16da03d4b 100644 --- a/exercises/practice/robot-simulator/.meta/tests.toml +++ b/exercises/practice/robot-simulator/.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. + +[c557c16d-26c1-4e06-827c-f6602cd0785c] +description = "Create robot -> at origin facing north" + +[bf0dffce-f11c-4cdb-8a5e-2c89d8a5a67d] +description = "Create robot -> at negative position facing south" + +[8cbd0086-6392-4680-b9b9-73cf491e67e5] +description = "Rotating clockwise -> changes north to east" + +[8abc87fc-eab2-4276-93b7-9c009e866ba1] +description = "Rotating clockwise -> changes east to south" + +[3cfe1b85-bbf2-4bae-b54d-d73e7e93617a] +description = "Rotating clockwise -> changes south to west" + +[5ea9fb99-3f2c-47bd-86f7-46b7d8c3c716] +description = "Rotating clockwise -> changes west to north" + +[fa0c40f5-6ba3-443d-a4b3-58cbd6cb8d63] +description = "Rotating counter-clockwise -> changes north to west" + +[da33d734-831f-445c-9907-d66d7d2a92e2] +description = "Rotating counter-clockwise -> changes west to south" + +[bd1ca4b9-4548-45f4-b32e-900fc7c19389] +description = "Rotating counter-clockwise -> changes south to east" + +[2de27b67-a25c-4b59-9883-bc03b1b55bba] +description = "Rotating counter-clockwise -> changes east to north" + +[f0dc2388-cddc-4f83-9bed-bcf46b8fc7b8] +description = "Moving forward one -> facing north increments Y" + +[2786cf80-5bbf-44b0-9503-a89a9c5789da] +description = "Moving forward one -> facing south decrements Y" + +[84bf3c8c-241f-434d-883d-69817dbd6a48] +description = "Moving forward one -> facing east increments X" + +[bb69c4a7-3bbf-4f64-b415-666fa72d7b04] +description = "Moving forward one -> facing west decrements X" + +[e34ac672-4ed4-4be3-a0b8-d9af259cbaa1] +description = "Follow series of instructions -> moving east and north from README" + +[f30e4955-4b47-4aa3-8b39-ae98cfbd515b] +description = "Follow series of instructions -> moving west and north" + +[3e466bf6-20ab-4d79-8b51-264165182fca] +description = "Follow series of instructions -> moving west and south" + +[41f0bb96-c617-4e6b-acff-a4b279d44514] +description = "Follow series of instructions -> moving east and north" diff --git a/exercises/practice/robot-simulator/Cargo.toml b/exercises/practice/robot-simulator/Cargo.toml index c82b015f6..996ffc36c 100644 --- a/exercises/practice/robot-simulator/Cargo.toml +++ b/exercises/practice/robot-simulator/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "robot-simulator" -version = "2.2.0" +name = "robot_simulator" +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/robot-simulator/src/lib.rs b/exercises/practice/robot-simulator/src/lib.rs index f9f605fbf..ff3fab8ca 100644 --- a/exercises/practice/robot-simulator/src/lib.rs +++ b/exercises/practice/robot-simulator/src/lib.rs @@ -13,34 +13,34 @@ pub struct Robot; impl Robot { pub fn new(x: i32, y: i32, d: Direction) -> Self { - unimplemented!("Create a robot at (x, y) ({x}, {y}) facing {d:?}") + todo!("Create a robot at (x, y) ({x}, {y}) facing {d:?}") } #[must_use] pub fn turn_right(self) -> Self { - unimplemented!() + todo!() } #[must_use] pub fn turn_left(self) -> Self { - unimplemented!() + todo!() } #[must_use] pub fn advance(self) -> Self { - unimplemented!() + todo!() } #[must_use] pub fn instructions(self, instructions: &str) -> Self { - unimplemented!("Follow the given sequence of instructions: {instructions}") + todo!("Follow the given sequence of instructions: {instructions}") } pub fn position(&self) -> (i32, i32) { - unimplemented!() + todo!() } pub fn direction(&self) -> &Direction { - unimplemented!() + todo!() } } diff --git a/exercises/practice/robot-simulator/tests/robot-simulator.rs b/exercises/practice/robot-simulator/tests/robot-simulator.rs deleted file mode 100644 index 1c33c2f18..000000000 --- a/exercises/practice/robot-simulator/tests/robot-simulator.rs +++ /dev/null @@ -1,145 +0,0 @@ -use robot_simulator::*; - -#[test] -fn robots_are_created_with_position_and_direction() { - let robot = Robot::new(0, 0, Direction::North); - assert_eq!((0, 0), robot.position()); - assert_eq!(&Direction::North, robot.direction()); -} - -#[test] -#[ignore] -fn positions_can_be_negative() { - let robot = Robot::new(-1, -1, Direction::South); - assert_eq!((-1, -1), robot.position()); - assert_eq!(&Direction::South, robot.direction()); -} - -#[test] -#[ignore] -fn turning_right_does_not_change_position() { - let robot = Robot::new(0, 0, Direction::North).turn_right(); - assert_eq!((0, 0), robot.position()); -} - -#[test] -#[ignore] -fn turning_right_from_north_points_the_robot_east() { - let robot = Robot::new(0, 0, Direction::North).turn_right(); - assert_eq!(&Direction::East, robot.direction()); -} - -#[test] -#[ignore] -fn turning_right_from_east_points_the_robot_south() { - let robot = Robot::new(0, 0, Direction::East).turn_right(); - assert_eq!(&Direction::South, robot.direction()); -} - -#[test] -#[ignore] -fn turning_right_from_south_points_the_robot_west() { - let robot = Robot::new(0, 0, Direction::South).turn_right(); - assert_eq!(&Direction::West, robot.direction()); -} - -#[test] -#[ignore] -fn turning_right_from_west_points_the_robot_north() { - let robot = Robot::new(0, 0, Direction::West).turn_right(); - assert_eq!(&Direction::North, robot.direction()); -} - -#[test] -#[ignore] -fn turning_left_does_not_change_position() { - let robot = Robot::new(0, 0, Direction::North).turn_left(); - assert_eq!((0, 0), robot.position()); -} - -#[test] -#[ignore] -fn turning_left_from_north_points_the_robot_west() { - let robot = Robot::new(0, 0, Direction::North).turn_left(); - assert_eq!(&Direction::West, robot.direction()); -} - -#[test] -#[ignore] -fn turning_left_from_west_points_the_robot_south() { - let robot = Robot::new(0, 0, Direction::West).turn_left(); - assert_eq!(&Direction::South, robot.direction()); -} - -#[test] -#[ignore] -fn turning_left_from_south_points_the_robot_east() { - let robot = Robot::new(0, 0, Direction::South).turn_left(); - assert_eq!(&Direction::East, robot.direction()); -} - -#[test] -#[ignore] -fn turning_left_from_east_points_the_robot_north() { - let robot = Robot::new(0, 0, Direction::East).turn_left(); - assert_eq!(&Direction::North, robot.direction()); -} - -#[test] -#[ignore] -fn advance_does_not_change_the_direction() { - let robot = Robot::new(0, 0, Direction::North).advance(); - assert_eq!(&Direction::North, robot.direction()); -} - -#[test] -#[ignore] -fn advance_increases_the_y_coordinate_by_one_when_facing_north() { - let robot = Robot::new(0, 0, Direction::North).advance(); - assert_eq!((0, 1), robot.position()); -} - -#[test] -#[ignore] -fn advance_decreases_the_y_coordinate_by_one_when_facing_south() { - let robot = Robot::new(0, 0, Direction::South).advance(); - assert_eq!((0, -1), robot.position()); -} - -#[test] -#[ignore] -fn advance_increases_the_x_coordinate_by_one_when_facing_east() { - let robot = Robot::new(0, 0, Direction::East).advance(); - assert_eq!((1, 0), robot.position()); -} - -#[test] -#[ignore] -fn advance_decreases_the_x_coordinate_by_one_when_facing_west() { - let robot = Robot::new(0, 0, Direction::West).advance(); - assert_eq!((-1, 0), robot.position()); -} - -#[test] -#[ignore] -fn follow_instructions_to_move_west_and_north() { - let robot = Robot::new(0, 0, Direction::North).instructions("LAAARALA"); - assert_eq!((-4, 1), robot.position()); - assert_eq!(&Direction::West, robot.direction()); -} - -#[test] -#[ignore] -fn follow_instructions_to_move_west_and_south() { - let robot = Robot::new(2, -7, Direction::East).instructions("RRAAAAALA"); - assert_eq!((-3, -8), robot.position()); - assert_eq!(&Direction::South, robot.direction()); -} - -#[test] -#[ignore] -fn follow_instructions_to_move_east_and_north() { - let robot = Robot::new(8, 4, Direction::South).instructions("LAAARRRALLLL"); - assert_eq!((11, 5), robot.position()); - assert_eq!(&Direction::North, robot.direction()); -} diff --git a/exercises/practice/robot-simulator/tests/robot_simulator.rs b/exercises/practice/robot-simulator/tests/robot_simulator.rs new file mode 100644 index 000000000..45ff1160a --- /dev/null +++ b/exercises/practice/robot-simulator/tests/robot_simulator.rs @@ -0,0 +1,160 @@ +use robot_simulator::*; + +#[test] +fn at_origin_facing_north() { + let robot = Robot::new(0, 0, Direction::North); + assert_eq!(robot.position(), (0, 0)); + assert_eq!(robot.direction(), &Direction::North); +} + +#[test] +#[ignore] +fn at_negative_position_facing_south() { + let robot = Robot::new(-1, -1, Direction::South); + assert_eq!(robot.position(), (-1, -1)); + assert_eq!(robot.direction(), &Direction::South); +} + +#[test] +#[ignore] +fn changes_north_to_east() { + let robot_start = Robot::new(0, 0, Direction::North); + let robot_end = robot_start.turn_right(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::East); +} + +#[test] +#[ignore] +fn changes_east_to_south() { + let robot_start = Robot::new(0, 0, Direction::East); + let robot_end = robot_start.turn_right(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::South); +} + +#[test] +#[ignore] +fn changes_south_to_west() { + let robot_start = Robot::new(0, 0, Direction::South); + let robot_end = robot_start.turn_right(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::West); +} + +#[test] +#[ignore] +fn changes_west_to_north() { + let robot_start = Robot::new(0, 0, Direction::West); + let robot_end = robot_start.turn_right(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::North); +} + +#[test] +#[ignore] +fn changes_north_to_west() { + let robot_start = Robot::new(0, 0, Direction::North); + let robot_end = robot_start.turn_left(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::West); +} + +#[test] +#[ignore] +fn changes_west_to_south() { + let robot_start = Robot::new(0, 0, Direction::West); + let robot_end = robot_start.turn_left(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::South); +} + +#[test] +#[ignore] +fn changes_south_to_east() { + let robot_start = Robot::new(0, 0, Direction::South); + let robot_end = robot_start.turn_left(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::East); +} + +#[test] +#[ignore] +fn changes_east_to_north() { + let robot_start = Robot::new(0, 0, Direction::East); + let robot_end = robot_start.turn_left(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::North); +} + +#[test] +#[ignore] +fn facing_north_increments_y() { + let robot_start = Robot::new(0, 0, Direction::North); + let robot_end = robot_start.advance(); + assert_eq!(robot_end.position(), (0, 1)); + assert_eq!(robot_end.direction(), &Direction::North); +} + +#[test] +#[ignore] +fn facing_south_decrements_y() { + let robot_start = Robot::new(0, 0, Direction::South); + let robot_end = robot_start.advance(); + assert_eq!(robot_end.position(), (0, -1)); + assert_eq!(robot_end.direction(), &Direction::South); +} + +#[test] +#[ignore] +fn facing_east_increments_x() { + let robot_start = Robot::new(0, 0, Direction::East); + let robot_end = robot_start.advance(); + assert_eq!(robot_end.position(), (1, 0)); + assert_eq!(robot_end.direction(), &Direction::East); +} + +#[test] +#[ignore] +fn facing_west_decrements_x() { + let robot_start = Robot::new(0, 0, Direction::West); + let robot_end = robot_start.advance(); + assert_eq!(robot_end.position(), (-1, 0)); + assert_eq!(robot_end.direction(), &Direction::West); +} + +#[test] +#[ignore] +fn moving_east_and_north_from_readme() { + let robot_start = Robot::new(7, 3, Direction::North); + let robot_end = robot_start.instructions("RAALAL"); + assert_eq!(robot_end.position(), (9, 4)); + assert_eq!(robot_end.direction(), &Direction::West); +} + +#[test] +#[ignore] +fn moving_west_and_north() { + let robot_start = Robot::new(0, 0, Direction::North); + let robot_end = robot_start.instructions("LAAARALA"); + assert_eq!(robot_end.position(), (-4, 1)); + assert_eq!(robot_end.direction(), &Direction::West); +} + +#[test] +#[ignore] +fn moving_west_and_south() { + let robot_start = Robot::new(2, -7, Direction::East); + let robot_end = robot_start.instructions("RRAAAAALA"); + assert_eq!(robot_end.position(), (-3, -8)); + assert_eq!(robot_end.direction(), &Direction::South); +} + +#[test] +#[ignore] +fn moving_east_and_north() { + let robot_start = Robot::new(8, 4, Direction::South); + let robot_end = robot_start.instructions("LAAARRRALLLL"); + assert_eq!(robot_end.position(), (11, 5)); + assert_eq!(robot_end.direction(), &Direction::North); +} diff --git a/exercises/practice/roman-numerals/.docs/instructions.md b/exercises/practice/roman-numerals/.docs/instructions.md index ce25f205e..50e2f5bf1 100644 --- a/exercises/practice/roman-numerals/.docs/instructions.md +++ b/exercises/practice/roman-numerals/.docs/instructions.md @@ -1,43 +1,12 @@ -# Instructions +# Introduction -Write a function to convert from normal numbers to Roman Numerals. +Your task is to convert a number from Arabic numerals to Roman numerals. -The Romans were a clever bunch. They conquered most of Europe and ruled -it for hundreds of years. They invented concrete and straight roads and -even bikinis. One thing they never discovered though was the number -zero. This made writing and dating extensive histories of their exploits -slightly more challenging, but the system of numbers they came up with -is still in use today. For example the BBC uses Roman numerals to date -their programmes. +For this exercise, we are only concerned about traditional Roman numerals, in which the largest number is MMMCMXCIX (or 3,999). -The Romans wrote numbers using letters - I, V, X, L, C, D, M. (notice -these letters have lots of straight lines and are hence easy to hack -into stone tablets). +~~~~exercism/note +There are lots of different ways to convert between Arabic and Roman numerals. +We recommend taking a naive approach first to familiarise yourself with the concept of Roman numerals and then search for more efficient methods. -```text - 1 => I -10 => X - 7 => VII -``` - -There is no need to be able to convert numbers larger than about 3000. -(The Romans themselves didn't tend to go any higher) - -Wikipedia says: Modern Roman numerals ... are written by expressing each -digit separately starting with the left most digit and skipping any -digit with a value of zero. - -To see this in practice, consider the example of 1990. - -In Roman numerals 1990 is MCMXC: - -1000=M -900=CM -90=XC - -2008 is written as MMVIII: - -2000=MM -8=VIII - -See also: http://www.novaroma.org/via_romana/numbers.html +Make sure to check out our Deep Dive video at the end to explore the different approaches you can take! +~~~~ diff --git a/exercises/practice/roman-numerals/.docs/introduction.md b/exercises/practice/roman-numerals/.docs/introduction.md new file mode 100644 index 000000000..6fd942fef --- /dev/null +++ b/exercises/practice/roman-numerals/.docs/introduction.md @@ -0,0 +1,59 @@ +# Description + +Today, most people in the world use Arabic numerals (0–9). +But if you travelled back two thousand years, you'd find that most Europeans were using Roman numerals instead. + +To write a Roman numeral we use the following Latin letters, each of which has a value: + +| M | D | C | L | X | V | I | +| ---- | --- | --- | --- | --- | --- | --- | +| 1000 | 500 | 100 | 50 | 10 | 5 | 1 | + +A Roman numeral is a sequence of these letters, and its value is the sum of the letters' values. +For example, `XVIII` has the value 18 (`10 + 5 + 1 + 1 + 1 = 18`). + +There's one rule that makes things trickier though, and that's that **the same letter cannot be used more than three times in succession**. +That means that we can't express numbers such as 4 with the seemingly natural `IIII`. +Instead, for those numbers, we use a subtraction method between two letters. +So we think of `4` not as `1 + 1 + 1 + 1` but instead as `5 - 1`. +And slightly confusingly to our modern thinking, we write the smaller number first. +This applies only in the following cases: 4 (`IV`), 9 (`IX`), 40 (`XL`), 90 (`XC`), 400 (`CD`) and 900 (`CM`). + +Order matters in Roman numerals! +Letters (and the special compounds above) must be ordered by decreasing value from left to right. + +Here are some examples: + +```text + 105 => CV +---- => -- + 100 => C ++ 5 => V +``` + +```text + 106 => CVI +---- => -- + 100 => C ++ 5 => V ++ 1 => I +``` + +```text + 104 => CIV +---- => --- + 100 => C ++ 4 => IV +``` + +And a final more complex example: + +```text + 1996 => MCMXCVI +----- => ------- + 1000 => M ++ 900 => CM ++ 90 => XC ++ 5 => V ++ 1 => I +``` diff --git a/exercises/practice/roman-numerals/.gitignore b/exercises/practice/roman-numerals/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/roman-numerals/.gitignore +++ b/exercises/practice/roman-numerals/.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/roman-numerals/.meta/config.json b/exercises/practice/roman-numerals/.meta/config.json index 1042d9e94..8be87b7bb 100644 --- a/exercises/practice/roman-numerals/.meta/config.json +++ b/exercises/practice/roman-numerals/.meta/config.json @@ -26,13 +26,13 @@ "Cargo.toml" ], "test": [ - "tests/roman-numerals.rs" + "tests/roman_numerals.rs" ], "example": [ ".meta/example.rs" ] }, - "blurb": "Write a function to convert from normal numbers to Roman Numerals.", + "blurb": "Convert modern Arabic numbers into Roman numerals.", "source": "The Roman Numeral Kata", - "source_url": "http://codingdojo.org/cgi-bin/index.pl?KataRomanNumerals" + "source_url": "https://codingdojo.org/kata/RomanNumerals/" } diff --git a/exercises/practice/roman-numerals/.meta/example.rs b/exercises/practice/roman-numerals/.meta/example.rs index 371974a4b..a1b91521b 100644 --- a/exercises/practice/roman-numerals/.meta/example.rs +++ b/exercises/practice/roman-numerals/.meta/example.rs @@ -36,7 +36,7 @@ impl fmt::Display for Roman { start -= numeric; } } - write!(f, "{}", result) + write!(f, "{result}") } } diff --git a/exercises/practice/roman-numerals/.meta/test_template.tera b/exercises/practice/roman-numerals/.meta/test_template.tera new file mode 100644 index 000000000..308f567d6 --- /dev/null +++ b/exercises/practice/roman-numerals/.meta/test_template.tera @@ -0,0 +1,12 @@ +use roman_numerals::Roman; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.number | json_encode() }}; + let output = Roman::from(input).to_string(); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/roman-numerals/.meta/tests.toml b/exercises/practice/roman-numerals/.meta/tests.toml index be690e975..709011b55 100644 --- a/exercises/practice/roman-numerals/.meta/tests.toml +++ b/exercises/practice/roman-numerals/.meta/tests.toml @@ -1,3 +1,91 @@ -# 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. + +[19828a3a-fbf7-4661-8ddd-cbaeee0e2178] +description = "1 is I" + +[f088f064-2d35-4476-9a41-f576da3f7b03] +description = "2 is II" + +[b374a79c-3bea-43e6-8db8-1286f79c7106] +description = "3 is III" + +[05a0a1d4-a140-4db1-82e8-fcc21fdb49bb] +description = "4 is IV" + +[57c0f9ad-5024-46ab-975d-de18c430b290] +description = "5 is V" + +[20a2b47f-e57f-4797-a541-0b3825d7f249] +description = "6 is VI" + +[ff3fb08c-4917-4aab-9f4e-d663491d083d] +description = "9 is IX" + +[6d1d82d5-bf3e-48af-9139-87d7165ed509] +description = "16 is XVI" + +[2bda64ca-7d28-4c56-b08d-16ce65716cf6] +description = "27 is XXVII" + +[a1f812ef-84da-4e02-b4f0-89c907d0962c] +description = "48 is XLVIII" + +[607ead62-23d6-4c11-a396-ef821e2e5f75] +description = "49 is XLIX" + +[d5b283d4-455d-4e68-aacf-add6c4b51915] +description = "59 is LIX" + +[4465ffd5-34dc-44f3-ada5-56f5007b6dad] +description = "66 is LXVI" + +[46b46e5b-24da-4180-bfe2-2ef30b39d0d0] +description = "93 is XCIII" + +[30494be1-9afb-4f84-9d71-db9df18b55e3] +description = "141 is CXLI" + +[267f0207-3c55-459a-b81d-67cec7a46ed9] +description = "163 is CLXIII" + +[902ad132-0b4d-40e3-8597-ba5ed611dd8d] +description = "166 is CLXVI" + +[cdb06885-4485-4d71-8bfb-c9d0f496b404] +description = "402 is CDII" + +[6b71841d-13b2-46b4-ba97-dec28133ea80] +description = "575 is DLXXV" + +[dacb84b9-ea1c-4a61-acbb-ce6b36674906] +description = "666 is DCLXVI" + +[432de891-7fd6-4748-a7f6-156082eeca2f] +description = "911 is CMXI" + +[e6de6d24-f668-41c0-88d7-889c0254d173] +description = "1024 is MXXIV" + +[efbe1d6a-9f98-4eb5-82bc-72753e3ac328] +description = "1666 is MDCLXVI" + +[bb550038-d4eb-4be2-a9ce-f21961ac3bc6] +description = "3000 is MMM" + +[3bc4b41c-c2e6-49d9-9142-420691504336] +description = "3001 is MMMI" + +[2f89cad7-73f6-4d1b-857b-0ef531f68b7e] +description = "3888 is MMMDCCCLXXXVIII" + +[4e18e96b-5fbb-43df-a91b-9cb511fe0856] +description = "3999 is MMMCMXCIX" diff --git a/exercises/practice/roman-numerals/Cargo.toml b/exercises/practice/roman-numerals/Cargo.toml index e474f49cb..7d0f36fa7 100644 --- a/exercises/practice/roman-numerals/Cargo.toml +++ b/exercises/practice/roman-numerals/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "roman-numerals" -version = "1.0.0" +name = "roman_numerals" +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/roman-numerals/src/lib.rs b/exercises/practice/roman-numerals/src/lib.rs index 5c9d1b600..97ae5cf73 100644 --- a/exercises/practice/roman-numerals/src/lib.rs +++ b/exercises/practice/roman-numerals/src/lib.rs @@ -4,12 +4,12 @@ pub struct Roman; impl Display for Roman { fn fmt(&self, _f: &mut Formatter<'_>) -> Result { - unimplemented!("Return a roman-numeral string representation of the Roman object"); + todo!("Return a roman-numeral string representation of the Roman object"); } } impl From for Roman { fn from(num: u32) -> Self { - unimplemented!("Construct a Roman object from the '{num}' number"); + todo!("Construct a Roman object from the '{num}' number"); } } diff --git a/exercises/practice/roman-numerals/tests/roman-numerals.rs b/exercises/practice/roman-numerals/tests/roman-numerals.rs deleted file mode 100644 index cb175fec2..000000000 --- a/exercises/practice/roman-numerals/tests/roman-numerals.rs +++ /dev/null @@ -1,108 +0,0 @@ -use roman_numerals::*; - -#[test] -fn test_one() { - assert_eq!("I", Roman::from(1).to_string()); -} - -#[test] -#[ignore] -fn test_two() { - assert_eq!("II", Roman::from(2).to_string()); -} - -#[test] -#[ignore] -fn test_three() { - assert_eq!("III", Roman::from(3).to_string()); -} - -#[test] -#[ignore] -fn test_four() { - assert_eq!("IV", Roman::from(4).to_string()); -} - -#[test] -#[ignore] -fn test_five() { - assert_eq!("V", Roman::from(5).to_string()); -} - -#[test] -#[ignore] -fn test_six() { - assert_eq!("VI", Roman::from(6).to_string()); -} - -#[test] -#[ignore] -fn test_nine() { - assert_eq!("IX", Roman::from(9).to_string()); -} - -#[test] -#[ignore] -fn test_twenty_seven() { - assert_eq!("XXVII", Roman::from(27).to_string()); -} - -#[test] -#[ignore] -fn test_forty_eight() { - assert_eq!("XLVIII", Roman::from(48).to_string()); -} - -#[test] -#[ignore] -fn test_fifty_nine() { - assert_eq!("LIX", Roman::from(59).to_string()); -} - -#[test] -#[ignore] -fn test_ninety_three() { - assert_eq!("XCIII", Roman::from(93).to_string()); -} - -#[test] -#[ignore] -fn test_141() { - assert_eq!("CXLI", Roman::from(141).to_string()); -} - -#[test] -#[ignore] -fn test_163() { - assert_eq!("CLXIII", Roman::from(163).to_string()); -} - -#[test] -#[ignore] -fn test_402() { - assert_eq!("CDII", Roman::from(402).to_string()); -} - -#[test] -#[ignore] -fn test_575() { - assert_eq!("DLXXV", Roman::from(575).to_string()); -} - -#[test] -#[ignore] -fn test_911() { - assert_eq!("CMXI", Roman::from(911).to_string()); -} - -#[test] -#[ignore] -fn test_1024() { - assert_eq!("MXXIV", Roman::from(1024).to_string()); -} - -#[test] -#[ignore] -fn test_3000() { - assert_eq!("MMM", Roman::from(3000).to_string()); -} diff --git a/exercises/practice/roman-numerals/tests/roman_numerals.rs b/exercises/practice/roman-numerals/tests/roman_numerals.rs new file mode 100644 index 000000000..336b42e9d --- /dev/null +++ b/exercises/practice/roman-numerals/tests/roman_numerals.rs @@ -0,0 +1,243 @@ +use roman_numerals::Roman; + +#[test] +fn test_1_is_i() { + let input = 1; + let output = Roman::from(input).to_string(); + let expected = "I"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_2_is_ii() { + let input = 2; + let output = Roman::from(input).to_string(); + let expected = "II"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_3_is_iii() { + let input = 3; + let output = Roman::from(input).to_string(); + let expected = "III"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_4_is_iv() { + let input = 4; + let output = Roman::from(input).to_string(); + let expected = "IV"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_5_is_v() { + let input = 5; + let output = Roman::from(input).to_string(); + let expected = "V"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_6_is_vi() { + let input = 6; + let output = Roman::from(input).to_string(); + let expected = "VI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_9_is_ix() { + let input = 9; + let output = Roman::from(input).to_string(); + let expected = "IX"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_16_is_xvi() { + let input = 16; + let output = Roman::from(input).to_string(); + let expected = "XVI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_27_is_xxvii() { + let input = 27; + let output = Roman::from(input).to_string(); + let expected = "XXVII"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_48_is_xlviii() { + let input = 48; + let output = Roman::from(input).to_string(); + let expected = "XLVIII"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_49_is_xlix() { + let input = 49; + let output = Roman::from(input).to_string(); + let expected = "XLIX"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_59_is_lix() { + let input = 59; + let output = Roman::from(input).to_string(); + let expected = "LIX"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_66_is_lxvi() { + let input = 66; + let output = Roman::from(input).to_string(); + let expected = "LXVI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_93_is_xciii() { + let input = 93; + let output = Roman::from(input).to_string(); + let expected = "XCIII"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_141_is_cxli() { + let input = 141; + let output = Roman::from(input).to_string(); + let expected = "CXLI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_163_is_clxiii() { + let input = 163; + let output = Roman::from(input).to_string(); + let expected = "CLXIII"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_166_is_clxvi() { + let input = 166; + let output = Roman::from(input).to_string(); + let expected = "CLXVI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_402_is_cdii() { + let input = 402; + let output = Roman::from(input).to_string(); + let expected = "CDII"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_575_is_dlxxv() { + let input = 575; + let output = Roman::from(input).to_string(); + let expected = "DLXXV"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_666_is_dclxvi() { + let input = 666; + let output = Roman::from(input).to_string(); + let expected = "DCLXVI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_911_is_cmxi() { + let input = 911; + let output = Roman::from(input).to_string(); + let expected = "CMXI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_1024_is_mxxiv() { + let input = 1024; + let output = Roman::from(input).to_string(); + let expected = "MXXIV"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_1666_is_mdclxvi() { + let input = 1666; + let output = Roman::from(input).to_string(); + let expected = "MDCLXVI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_3000_is_mmm() { + let input = 3000; + let output = Roman::from(input).to_string(); + let expected = "MMM"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_3001_is_mmmi() { + let input = 3001; + let output = Roman::from(input).to_string(); + let expected = "MMMI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_3888_is_mmmdccclxxxviii() { + let input = 3888; + let output = Roman::from(input).to_string(); + let expected = "MMMDCCCLXXXVIII"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_3999_is_mmmcmxcix() { + let input = 3999; + let output = Roman::from(input).to_string(); + let expected = "MMMCMXCIX"; + assert_eq!(output, expected); +} diff --git a/exercises/practice/rotational-cipher/.docs/instructions.md b/exercises/practice/rotational-cipher/.docs/instructions.md index dbf6276f3..4bf64ca1d 100644 --- a/exercises/practice/rotational-cipher/.docs/instructions.md +++ b/exercises/practice/rotational-cipher/.docs/instructions.md @@ -2,11 +2,9 @@ Create an implementation of the rotational cipher, also sometimes called the Caesar cipher. -The Caesar cipher is a simple shift cipher that relies on -transposing all the letters in the alphabet using an integer key -between `0` and `26`. Using a key of `0` or `26` will always yield -the same output due to modular arithmetic. The letter is shifted -for as many values as the value of the key. +The Caesar cipher is a simple shift cipher that relies on transposing all the letters in the alphabet using an integer key between `0` and `26`. +Using a key of `0` or `26` will always yield the same output due to modular arithmetic. +The letter is shifted for as many values as the value of the key. The general notation for rotational ciphers is `ROT + `. The most commonly used rotational cipher is `ROT13`. @@ -24,8 +22,8 @@ Ciphertext is written out in the same formatting as the input including spaces a ## Examples -- ROT5 `omg` gives `trl` -- ROT0 `c` gives `c` +- ROT5 `omg` gives `trl` +- ROT0 `c` gives `c` - ROT26 `Cool` gives `Cool` - ROT13 `The quick brown fox jumps over the lazy dog.` gives `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.` - ROT13 `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.` gives `The quick brown fox jumps over the lazy dog.` diff --git a/exercises/practice/rotational-cipher/.gitignore b/exercises/practice/rotational-cipher/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/rotational-cipher/.gitignore +++ b/exercises/practice/rotational-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/rotational-cipher/.meta/config.json b/exercises/practice/rotational-cipher/.meta/config.json index 8a3fa4382..48517941d 100644 --- a/exercises/practice/rotational-cipher/.meta/config.json +++ b/exercises/practice/rotational-cipher/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/rotational-cipher.rs" + "tests/rotational_cipher.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/rotational-cipher/.meta/example.rs b/exercises/practice/rotational-cipher/.meta/example.rs index 44359ab95..c19c05688 100644 --- a/exercises/practice/rotational-cipher/.meta/example.rs +++ b/exercises/practice/rotational-cipher/.meta/example.rs @@ -1,6 +1,4 @@ -pub fn rotate(text: &str, key: i8) -> String { - let key = if key < 0 { (26 + key) as u8 } else { key as u8 }; - +pub fn rotate(text: &str, key: u8) -> String { text.chars() .map(|c| { if c.is_alphabetic() { diff --git a/exercises/practice/rotational-cipher/.meta/test_template.tera b/exercises/practice/rotational-cipher/.meta/test_template.tera new file mode 100644 index 000000000..68c693cd3 --- /dev/null +++ b/exercises/practice/rotational-cipher/.meta/test_template.tera @@ -0,0 +1,12 @@ +use rotational_cipher as cipher; +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let text = {{ test.input.text | json_encode() }}; + let shift_key = {{ test.input.shiftKey | json_encode() }}; + let output = cipher::rotate(text, shift_key); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/rotational-cipher/.meta/tests.toml b/exercises/practice/rotational-cipher/.meta/tests.toml index 12d669de4..53441ed22 100644 --- a/exercises/practice/rotational-cipher/.meta/tests.toml +++ b/exercises/practice/rotational-cipher/.meta/tests.toml @@ -1,6 +1,31 @@ -# 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. + +[74e58a38-e484-43f1-9466-877a7515e10f] +description = "rotate a by 0, same output as input" + +[7ee352c6-e6b0-4930-b903-d09943ecb8f5] +description = "rotate a by 1" + +[edf0a733-4231-4594-a5ee-46a4009ad764] +description = "rotate a by 26, same output as input" + +[e3e82cb9-2a5b-403f-9931-e43213879300] +description = "rotate m by 13" + +[19f9eb78-e2ad-4da4-8fe3-9291d47c1709] +description = "rotate n by 13 with wrap around alphabet" + +[a116aef4-225b-4da9-884f-e8023ca6408a] +description = "rotate capital letters" [71b541bb-819c-4dc6-a9c3-132ef9bb737b] description = "rotate spaces" @@ -10,3 +35,6 @@ description = "rotate numbers" [32dd74f6-db2b-41a6-b02c-82eb4f93e549] description = "rotate punctuation" + +[9fb93fe6-42b0-46e6-9ec1-0bf0a062d8c9] +description = "rotate all letters" diff --git a/exercises/practice/rotational-cipher/Cargo.toml b/exercises/practice/rotational-cipher/Cargo.toml index 2b00cbaeb..61e6d3c41 100644 --- a/exercises/practice/rotational-cipher/Cargo.toml +++ b/exercises/practice/rotational-cipher/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "rotational-cipher" -version = "1.0.0" +name = "rotational_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/rotational-cipher/src/lib.rs b/exercises/practice/rotational-cipher/src/lib.rs index 9f59d656e..a1d0b53e5 100644 --- a/exercises/practice/rotational-cipher/src/lib.rs +++ b/exercises/practice/rotational-cipher/src/lib.rs @@ -1,5 +1,5 @@ -pub fn rotate(input: &str, key: i8) -> String { - unimplemented!( +pub fn rotate(input: &str, key: u8) -> String { + todo!( "How would input text '{input}' transform when every letter is shifted using key '{key}'?" ); } diff --git a/exercises/practice/rotational-cipher/tests/rotational-cipher.rs b/exercises/practice/rotational-cipher/tests/rotational-cipher.rs deleted file mode 100644 index f447495d0..000000000 --- a/exercises/practice/rotational-cipher/tests/rotational-cipher.rs +++ /dev/null @@ -1,81 +0,0 @@ -use rotational_cipher as cipher; - -#[test] -fn rotate_a_1() { - assert_eq!("b", cipher::rotate("a", 1)); -} - -#[test] -#[ignore] -fn rotate_a_26() { - assert_eq!("a", cipher::rotate("a", 26)); -} - -#[test] -#[ignore] -fn rotate_a_0() { - assert_eq!("a", cipher::rotate("a", 0)); -} - -#[test] -#[ignore] -fn rotate_m_13() { - assert_eq!("z", cipher::rotate("m", 13)); -} - -#[test] -#[ignore] -fn rotate_n_13_with_wrap() { - assert_eq!("a", cipher::rotate("n", 13)); -} - -#[test] -#[ignore] -fn rotate_caps() { - assert_eq!("TRL", cipher::rotate("OMG", 5)); -} - -#[test] -#[ignore] -fn rotate_spaces() { - assert_eq!("T R L", cipher::rotate("O M G", 5)); -} - -#[test] -#[ignore] -fn rotate_numbers() { - assert_eq!( - "Xiwxmrk 1 2 3 xiwxmrk", - cipher::rotate("Testing 1 2 3 testing", 4) - ); -} - -#[test] -#[ignore] -fn rotate_punctuation() { - assert_eq!( - "Gzo\'n zvo, Bmviyhv!", - cipher::rotate("Let\'s eat, Grandma!", 21) - ); -} - -#[test] -#[ignore] -fn rotate_all_the_letters() { - assert_eq!( - "Gur dhvpx oebja sbk whzcf bire gur ynml qbt.", - cipher::rotate("The quick brown fox jumps over the lazy dog.", 13) - ); -} - -#[test] -#[ignore] -fn rotate_m_negative_1() { - assert_eq!("l", cipher::rotate("m", -1)); -} - -#[test] -#[ignore] -fn rotate_letters_negative_26() { - assert_eq!("omg", cipher::rotate("omg", -26)); -} diff --git a/exercises/practice/rotational-cipher/tests/rotational_cipher.rs b/exercises/practice/rotational-cipher/tests/rotational_cipher.rs new file mode 100644 index 000000000..d5bf7579d --- /dev/null +++ b/exercises/practice/rotational-cipher/tests/rotational_cipher.rs @@ -0,0 +1,100 @@ +use rotational_cipher as cipher; + +#[test] +fn rotate_a_by_0_same_output_as_input() { + let text = "a"; + let shift_key = 0; + let output = cipher::rotate(text, shift_key); + let expected = "a"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rotate_a_by_1() { + let text = "a"; + let shift_key = 1; + let output = cipher::rotate(text, shift_key); + let expected = "b"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rotate_a_by_26_same_output_as_input() { + let text = "a"; + let shift_key = 26; + let output = cipher::rotate(text, shift_key); + let expected = "a"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rotate_m_by_13() { + let text = "m"; + let shift_key = 13; + let output = cipher::rotate(text, shift_key); + let expected = "z"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rotate_n_by_13_with_wrap_around_alphabet() { + let text = "n"; + let shift_key = 13; + let output = cipher::rotate(text, shift_key); + let expected = "a"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rotate_capital_letters() { + let text = "OMG"; + let shift_key = 5; + let output = cipher::rotate(text, shift_key); + let expected = "TRL"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rotate_spaces() { + let text = "O M G"; + let shift_key = 5; + let output = cipher::rotate(text, shift_key); + let expected = "T R L"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rotate_numbers() { + let text = "Testing 1 2 3 testing"; + let shift_key = 4; + let output = cipher::rotate(text, shift_key); + let expected = "Xiwxmrk 1 2 3 xiwxmrk"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rotate_punctuation() { + let text = "Let's eat, Grandma!"; + let shift_key = 21; + let output = cipher::rotate(text, shift_key); + let expected = "Gzo'n zvo, Bmviyhv!"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn rotate_all_letters() { + let text = "The quick brown fox jumps over the lazy dog."; + let shift_key = 13; + let output = cipher::rotate(text, shift_key); + let expected = "Gur dhvpx oebja sbk whzcf bire gur ynml qbt."; + assert_eq!(output, expected); +} diff --git a/exercises/practice/run-length-encoding/.docs/instructions.md b/exercises/practice/run-length-encoding/.docs/instructions.md index 95f7a9d69..fc8ce0569 100644 --- a/exercises/practice/run-length-encoding/.docs/instructions.md +++ b/exercises/practice/run-length-encoding/.docs/instructions.md @@ -2,8 +2,7 @@ Implement run-length encoding and decoding. -Run-length encoding (RLE) is a simple form of data compression, where runs -(consecutive data elements) are replaced by just one data value and count. +Run-length encoding (RLE) is a simple form of data compression, where runs (consecutive data elements) are replaced by just one data value and count. For example we can represent the original 53 characters with only 13. @@ -11,14 +10,11 @@ For example we can represent the original 53 characters with only 13. "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" -> "12WB12W3B24WB" ``` -RLE allows the original data to be perfectly reconstructed from -the compressed data, which makes it a lossless data compression. +RLE allows the original data to be perfectly reconstructed from the compressed data, which makes it a lossless data compression. ```text "AABCCCDEEEE" -> "2AB3CD4E" -> "AABCCCDEEEE" ``` -For simplicity, you can assume that the unencoded string will only contain -the letters A through Z (either lower or upper case) and whitespace. This way -data to be encoded will never contain any numbers and numbers inside data to -be decoded always represent the count for the following character. +For simplicity, you can assume that the unencoded string will only contain the letters A through Z (either lower or upper case) and whitespace. +This way data to be encoded will never contain any numbers and numbers inside data to be decoded always represent the count for the following character. diff --git a/exercises/practice/run-length-encoding/.gitignore b/exercises/practice/run-length-encoding/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/run-length-encoding/.gitignore +++ b/exercises/practice/run-length-encoding/.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/run-length-encoding/.meta/config.json b/exercises/practice/run-length-encoding/.meta/config.json index 51fa925e4..50cb9baf2 100644 --- a/exercises/practice/run-length-encoding/.meta/config.json +++ b/exercises/practice/run-length-encoding/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/run-length-encoding.rs" + "tests/run_length_encoding.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/run-length-encoding/.meta/test_template.tera b/exercises/practice/run-length-encoding/.meta/test_template.tera new file mode 100644 index 000000000..8dbfee2e2 --- /dev/null +++ b/exercises/practice/run-length-encoding/.meta/test_template.tera @@ -0,0 +1,18 @@ +use run_length_encoding as rle; + +{% for test_group in cases %} +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.property }}_{{ test.description | make_ident }}() { + let input = {{ test.input.string | json_encode() }}; + {% if test.property == "consistency" -%} + let output = rle::decode(&rle::encode(input)); + {%- else -%} + let output = rle::{{ test.property }}(input); + {%- endif %} + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/run-length-encoding/.meta/tests.toml b/exercises/practice/run-length-encoding/.meta/tests.toml index be690e975..7bdb80867 100644 --- a/exercises/practice/run-length-encoding/.meta/tests.toml +++ b/exercises/practice/run-length-encoding/.meta/tests.toml @@ -1,3 +1,49 @@ -# 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. + +[ad53b61b-6ffc-422f-81a6-61f7df92a231] +description = "run-length encode a string -> empty string" + +[52012823-b7e6-4277-893c-5b96d42f82de] +description = "run-length encode a string -> single characters only are encoded without count" + +[b7868492-7e3a-415f-8da3-d88f51f80409] +description = "run-length encode a string -> string with no single characters" + +[859b822b-6e9f-44d6-9c46-6091ee6ae358] +description = "run-length encode a string -> single characters mixed with repeated characters" + +[1b34de62-e152-47be-bc88-469746df63b3] +description = "run-length encode a string -> multiple whitespace mixed in string" + +[abf176e2-3fbd-40ad-bb2f-2dd6d4df721a] +description = "run-length encode a string -> lowercase characters" + +[7ec5c390-f03c-4acf-ac29-5f65861cdeb5] +description = "run-length decode a string -> empty string" + +[ad23f455-1ac2-4b0e-87d0-b85b10696098] +description = "run-length decode a string -> single characters only" + +[21e37583-5a20-4a0e-826c-3dee2c375f54] +description = "run-length decode a string -> string with no single characters" + +[1389ad09-c3a8-4813-9324-99363fba429c] +description = "run-length decode a string -> single characters with repeated characters" + +[3f8e3c51-6aca-4670-b86c-a213bf4706b0] +description = "run-length decode a string -> multiple whitespace mixed in string" + +[29f721de-9aad-435f-ba37-7662df4fb551] +description = "run-length decode a string -> lowercase string" + +[2a762efd-8695-4e04-b0d6-9736899fbc16] +description = "encode and then decode -> encode followed by decode gives original string" diff --git a/exercises/practice/run-length-encoding/Cargo.toml b/exercises/practice/run-length-encoding/Cargo.toml index 6af6432b9..838424e16 100644 --- a/exercises/practice/run-length-encoding/Cargo.toml +++ b/exercises/practice/run-length-encoding/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" -name = "run-length-encoding" -version = "1.1.0" +name = "run_length_encoding" +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/run-length-encoding/src/lib.rs b/exercises/practice/run-length-encoding/src/lib.rs index 1860bed48..9b830aec9 100644 --- a/exercises/practice/run-length-encoding/src/lib.rs +++ b/exercises/practice/run-length-encoding/src/lib.rs @@ -1,7 +1,7 @@ pub fn encode(source: &str) -> String { - unimplemented!("Return the run-length encoding of {source}."); + todo!("Return the run-length encoding of {source}."); } pub fn decode(source: &str) -> String { - unimplemented!("Return the run-length decoding of {source}."); + todo!("Return the run-length decoding of {source}."); } diff --git a/exercises/practice/run-length-encoding/tests/run-length-encoding.rs b/exercises/practice/run-length-encoding/tests/run-length-encoding.rs deleted file mode 100644 index bcb208e36..000000000 --- a/exercises/practice/run-length-encoding/tests/run-length-encoding.rs +++ /dev/null @@ -1,93 +0,0 @@ -use run_length_encoding as rle; - -// encoding tests - -#[test] -fn test_encode_empty_string() { - assert_eq!("", rle::encode("")); -} - -#[test] -#[ignore] -fn test_encode_single_characters() { - assert_eq!("XYZ", rle::encode("XYZ")); -} - -#[test] -#[ignore] -fn test_encode_string_with_no_single_characters() { - assert_eq!("2A3B4C", rle::encode("AABBBCCCC")); -} - -#[test] -#[ignore] -fn test_encode_single_characters_mixed_with_repeated_characters() { - assert_eq!( - "12WB12W3B24WB", - rle::encode("WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB") - ); -} - -#[test] -#[ignore] -fn test_encode_multiple_whitespace_mixed_in_string() { - assert_eq!("2 hs2q q2w2 ", rle::encode(" hsqq qww ")); -} - -#[test] -#[ignore] -fn test_encode_lowercase_characters() { - assert_eq!("2a3b4c", rle::encode("aabbbcccc")); -} - -// decoding tests - -#[test] -#[ignore] -fn test_decode_empty_string() { - assert_eq!("", rle::decode("")); -} - -#[test] -#[ignore] -fn test_decode_single_characters_only() { - assert_eq!("XYZ", rle::decode("XYZ")); -} - -#[test] -#[ignore] -fn test_decode_string_with_no_single_characters() { - assert_eq!("AABBBCCCC", rle::decode("2A3B4C")); -} - -#[test] -#[ignore] -fn test_decode_single_characters_with_repeated_characters() { - assert_eq!( - "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB", - rle::decode("12WB12W3B24WB") - ); -} - -#[test] -#[ignore] -fn test_decode_multiple_whitespace_mixed_in_string() { - assert_eq!(" hsqq qww ", rle::decode("2 hs2q q2w2 ")); -} - -#[test] -#[ignore] -fn test_decode_lower_case_string() { - assert_eq!("aabbbcccc", rle::decode("2a3b4c")); -} - -// consistency test - -#[test] -#[ignore] -fn test_consistency() { - assert_eq!( - "zzz ZZ zZ", - rle::decode(rle::encode("zzz ZZ zZ").as_str()) - ); -} diff --git a/exercises/practice/run-length-encoding/tests/run_length_encoding.rs b/exercises/practice/run-length-encoding/tests/run_length_encoding.rs new file mode 100644 index 000000000..4221b3a47 --- /dev/null +++ b/exercises/practice/run-length-encoding/tests/run_length_encoding.rs @@ -0,0 +1,117 @@ +use run_length_encoding as rle; + +#[test] +fn encode_empty_string() { + let input = ""; + let output = rle::encode(input); + let expected = ""; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn encode_single_characters_only_are_encoded_without_count() { + let input = "XYZ"; + let output = rle::encode(input); + let expected = "XYZ"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn encode_string_with_no_single_characters() { + let input = "AABBBCCCC"; + let output = rle::encode(input); + let expected = "2A3B4C"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn encode_single_characters_mixed_with_repeated_characters() { + let input = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"; + let output = rle::encode(input); + let expected = "12WB12W3B24WB"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn encode_multiple_whitespace_mixed_in_string() { + let input = " hsqq qww "; + let output = rle::encode(input); + let expected = "2 hs2q q2w2 "; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn encode_lowercase_characters() { + let input = "aabbbcccc"; + let output = rle::encode(input); + let expected = "2a3b4c"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn decode_empty_string() { + let input = ""; + let output = rle::decode(input); + let expected = ""; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn decode_single_characters_only() { + let input = "XYZ"; + let output = rle::decode(input); + let expected = "XYZ"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn decode_string_with_no_single_characters() { + let input = "2A3B4C"; + let output = rle::decode(input); + let expected = "AABBBCCCC"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn decode_single_characters_with_repeated_characters() { + let input = "12WB12W3B24WB"; + let output = rle::decode(input); + let expected = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn decode_multiple_whitespace_mixed_in_string() { + let input = "2 hs2q q2w2 "; + let output = rle::decode(input); + let expected = " hsqq qww "; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn decode_lowercase_string() { + let input = "2a3b4c"; + let output = rle::decode(input); + let expected = "aabbbcccc"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn consistency_encode_followed_by_decode_gives_original_string() { + let input = "zzz ZZ zZ"; + let output = rle::decode(&rle::encode(input)); + let expected = "zzz ZZ zZ"; + assert_eq!(output, expected); +} diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md index aa11e0571..f69cdab95 100644 --- a/exercises/practice/saddle-points/.docs/instructions.md +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -1,29 +1,27 @@ # Instructions -Detect saddle points in a matrix. +Your task is to find the potential trees where you could build your tree house. -So say you have a matrix like so: +The data company provides the data as grids that show the heights of the trees. +The rows of the grid represent the east-west direction, and the columns represent the north-south direction. -```text - 1 2 3 - |--------- -1 | 9 8 7 -2 | 5 3 2 <--- saddle point at column 1, row 2, with value 5 -3 | 6 6 7 -``` - -It has a saddle point at column 1, row 2. +An acceptable tree will be the largest in its row, while being the smallest in its column. -It's called a "saddle point" because it is greater than or equal to -every element in its row and less than or equal to every element in -its column. +A grid might not have any good trees at all. +Or it might have one, or even several. -A matrix may have zero or more saddle points. +Here is a grid that has exactly one candidate tree. -Your code should be able to provide the (possibly empty) list of all the -saddle points for any given matrix. +```text + ↓ + 1 2 3 4 + |----------- + 1 | 9 8 7 8 +→ 2 |[5] 3 2 4 + 3 | 6 6 7 1 +``` -The matrix can have a different number of rows and columns (Non square). +- Row 2 has values 5, 3, 2, and 4. The largest value is 5. +- Column 1 has values 9, 5, and 6. The smallest value is 5. -Note that you may find other definitions of matrix saddle points online, -but the tests for this exercise follow the above unambiguous definition. +So the point at `[2, 1]` (row: 2, column: 1) is a great spot for a tree house. diff --git a/exercises/practice/saddle-points/.docs/introduction.md b/exercises/practice/saddle-points/.docs/introduction.md new file mode 100644 index 000000000..34b2c77e0 --- /dev/null +++ b/exercises/practice/saddle-points/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +You plan to build a tree house in the woods near your house so that you can watch the sun rise and set. + +You've obtained data from a local survey company that show the height of every tree in each rectangular section of the map. +You need to analyze each grid on the map to find good trees for your tree house. + +A good tree is both: + +- taller than every tree to the east and west, so that you have the best possible view of the sunrises and sunsets. +- shorter than every tree to the north and south, to minimize the amount of tree climbing. diff --git a/exercises/practice/saddle-points/.gitignore b/exercises/practice/saddle-points/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/saddle-points/.gitignore +++ b/exercises/practice/saddle-points/.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/saddle-points/.meta/config.json b/exercises/practice/saddle-points/.meta/config.json index 20499074a..4cfbaf457 100644 --- a/exercises/practice/saddle-points/.meta/config.json +++ b/exercises/practice/saddle-points/.meta/config.json @@ -28,7 +28,7 @@ "Cargo.toml" ], "test": [ - "tests/saddle-points.rs" + "tests/saddle_points.rs" ], "example": [ ".meta/example.rs" @@ -36,5 +36,5 @@ }, "blurb": "Detect saddle points in a matrix.", "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/saddle-points/.meta/test_template.tera b/exercises/practice/saddle-points/.meta/test_template.tera new file mode 100644 index 000000000..69fb2559c --- /dev/null +++ b/exercises/practice/saddle-points/.meta/test_template.tera @@ -0,0 +1,19 @@ +use saddle_points::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = &[{% for row in test.input.matrix %} + vec!{{ row }}, + {% endfor %}]; + let mut output = find_saddle_points(input); + output.sort_unstable(); + let expected = &[ + {% for p in test.expected | sort(attribute = "column") | sort(attribute = "row") %} + ({{ p.row - 1 }}, {{ p.column - 1 }}), + {% endfor %} + ]; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/saddle-points/.meta/tests.toml b/exercises/practice/saddle-points/.meta/tests.toml index be690e975..ca0085202 100644 --- a/exercises/practice/saddle-points/.meta/tests.toml +++ b/exercises/practice/saddle-points/.meta/tests.toml @@ -1,3 +1,37 @@ -# 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. + +[3e374e63-a2e0-4530-a39a-d53c560382bd] +description = "Can identify single saddle point" + +[6b501e2b-6c1f-491f-b1bb-7f278f760534] +description = "Can identify that empty matrix has no saddle points" + +[8c27cc64-e573-4fcb-a099-f0ae863fb02f] +description = "Can identify lack of saddle points when there are none" + +[6d1399bd-e105-40fd-a2c9-c6609507d7a3] +description = "Can identify multiple saddle points in a column" + +[3e81dce9-53b3-44e6-bf26-e328885fd5d1] +description = "Can identify multiple saddle points in a row" + +[88868621-b6f4-4837-bb8b-3fad8b25d46b] +description = "Can identify saddle point in bottom right corner" + +[5b9499ca-fcea-4195-830a-9c4584a0ee79] +description = "Can identify saddle points in a non square matrix" + +[ee99ccd2-a1f1-4283-ad39-f8c70f0cf594] +description = "Can identify that saddle points in a single column matrix are those with the minimum value" + +[63abf709-a84b-407f-a1b3-456638689713] +description = "Can identify that saddle points in a single row matrix are those with the maximum value" diff --git a/exercises/practice/saddle-points/Cargo.toml b/exercises/practice/saddle-points/Cargo.toml index 9507ab2ad..afe9ec54a 100644 --- a/exercises/practice/saddle-points/Cargo.toml +++ b/exercises/practice/saddle-points/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" -name = "saddle-points" -version = "1.3.0" +name = "saddle_points" +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/saddle-points/src/lib.rs b/exercises/practice/saddle-points/src/lib.rs index b9126326e..5e3a95bcd 100644 --- a/exercises/practice/saddle-points/src/lib.rs +++ b/exercises/practice/saddle-points/src/lib.rs @@ -1,3 +1,3 @@ pub fn find_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { - unimplemented!("find the saddle points of the following matrix: {input:?}") + todo!("find the saddle points of the following matrix: {input:?}") } diff --git a/exercises/practice/saddle-points/tests/saddle-points.rs b/exercises/practice/saddle-points/tests/saddle-points.rs deleted file mode 100644 index 3bed9af77..000000000 --- a/exercises/practice/saddle-points/tests/saddle-points.rs +++ /dev/null @@ -1,106 +0,0 @@ -use saddle_points::find_saddle_points; - -// We don't care about order -fn find_sorted_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { - let mut result = saddle_points::find_saddle_points(input); - result.sort_unstable(); - result -} - -#[test] -fn identify_single_saddle_point() { - let input = vec![vec![9, 8, 7], vec![5, 3, 2], vec![6, 6, 7]]; - assert_eq!(vec![(1, 0)], find_saddle_points(&input)); -} - -#[test] -#[ignore] -fn identify_empty_matrix() { - let input = vec![vec![], vec![], vec![]]; - let expected: Vec<(usize, usize)> = Vec::new(); - assert_eq!(expected, find_saddle_points(&input)); -} - -#[test] -#[ignore] -fn identify_lack_of_saddle_point() { - let input = vec![vec![1, 2, 3], vec![3, 1, 2], vec![2, 3, 1]]; - let expected: Vec<(usize, usize)> = Vec::new(); - assert_eq!(expected, find_saddle_points(&input)); -} - -#[test] -#[ignore] -fn multiple_saddle_points_in_col() { - let input = vec![vec![4, 5, 4], vec![3, 5, 5], vec![1, 5, 4]]; - assert_eq!( - vec![(0, 1), (1, 1), (2, 1)], - find_sorted_saddle_points(&input) - ); -} - -#[test] -#[ignore] -fn multiple_saddle_points_in_row() { - let input = vec![vec![6, 7, 8], vec![5, 5, 5], vec![7, 5, 6]]; - assert_eq!( - vec![(1, 0), (1, 1), (1, 2)], - find_sorted_saddle_points(&input) - ); -} - -#[test] -#[ignore] -fn identify_bottom_right_saddle_point() { - let input = vec![vec![8, 7, 9], vec![6, 7, 6], vec![3, 2, 5]]; - assert_eq!(vec![(2, 2)], find_saddle_points(&input)); -} - -// track specific as of v1.3 -#[test] -#[ignore] -fn non_square_matrix_high() { - let input = vec![vec![1, 5], vec![3, 6], vec![2, 7], vec![3, 8]]; - assert_eq!(vec![(0, 1)], find_saddle_points(&input)); -} - -#[test] -#[ignore] -fn non_square_matrix_wide() { - let input = vec![vec![3, 1, 3], vec![3, 2, 4]]; - assert_eq!(vec![(0, 0), (0, 2)], find_sorted_saddle_points(&input)); -} - -#[test] -#[ignore] -fn single_column_matrix() { - let input = vec![vec![2], vec![1], vec![4], vec![1]]; - assert_eq!(vec![(1, 0), (3, 0)], find_sorted_saddle_points(&input)); -} - -#[test] -#[ignore] -fn single_row_matrix() { - let input = vec![vec![2, 5, 3, 5]]; - assert_eq!(vec![(0, 1), (0, 3)], find_sorted_saddle_points(&input)); -} - -#[test] -#[ignore] -fn identify_all_saddle_points() { - let input = vec![vec![5, 5, 5], vec![5, 5, 5], vec![5, 5, 5]]; - assert_eq!( - vec![ - (0, 0), - (0, 1), - (0, 2), - (1, 0), - (1, 1), - (1, 2), - (2, 0), - (2, 1), - (2, 2) - ], - find_sorted_saddle_points(&input) - ); -} diff --git a/exercises/practice/saddle-points/tests/saddle_points.rs b/exercises/practice/saddle-points/tests/saddle_points.rs new file mode 100644 index 000000000..351eb7b3c --- /dev/null +++ b/exercises/practice/saddle-points/tests/saddle_points.rs @@ -0,0 +1,90 @@ +use saddle_points::*; + +#[test] +fn can_identify_single_saddle_point() { + let input = &[vec![9, 8, 7], vec![5, 3, 2], vec![6, 6, 7]]; + let mut output = find_saddle_points(input); + output.sort_unstable(); + let expected = &[(1, 0)]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn can_identify_that_empty_matrix_has_no_saddle_points() { + let input = &[vec![]]; + let mut output = find_saddle_points(input); + output.sort_unstable(); + let expected = &[]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn can_identify_lack_of_saddle_points_when_there_are_none() { + let input = &[vec![1, 2, 3], vec![3, 1, 2], vec![2, 3, 1]]; + let mut output = find_saddle_points(input); + output.sort_unstable(); + let expected = &[]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn can_identify_multiple_saddle_points_in_a_column() { + let input = &[vec![4, 5, 4], vec![3, 5, 5], vec![1, 5, 4]]; + let mut output = find_saddle_points(input); + output.sort_unstable(); + let expected = &[(0, 1), (1, 1), (2, 1)]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn can_identify_multiple_saddle_points_in_a_row() { + let input = &[vec![6, 7, 8], vec![5, 5, 5], vec![7, 5, 6]]; + let mut output = find_saddle_points(input); + output.sort_unstable(); + let expected = &[(1, 0), (1, 1), (1, 2)]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn can_identify_saddle_point_in_bottom_right_corner() { + let input = &[vec![8, 7, 9], vec![6, 7, 6], vec![3, 2, 5]]; + let mut output = find_saddle_points(input); + output.sort_unstable(); + let expected = &[(2, 2)]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn can_identify_saddle_points_in_a_non_square_matrix() { + let input = &[vec![3, 1, 3], vec![3, 2, 4]]; + let mut output = find_saddle_points(input); + output.sort_unstable(); + let expected = &[(0, 0), (0, 2)]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn can_identify_that_saddle_points_in_a_single_column_matrix_are_those_with_the_minimum_value() { + let input = &[vec![2], vec![1], vec![4], vec![1]]; + let mut output = find_saddle_points(input); + output.sort_unstable(); + let expected = &[(1, 0), (3, 0)]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn can_identify_that_saddle_points_in_a_single_row_matrix_are_those_with_the_maximum_value() { + let input = &[vec![2, 5, 3, 5]]; + let mut output = find_saddle_points(input); + output.sort_unstable(); + let expected = &[(0, 1), (0, 3)]; + assert_eq!(output, expected); +} diff --git a/exercises/practice/say/.docs/instructions.append.md b/exercises/practice/say/.docs/instructions.append.md index 37260b9bf..ce243386b 100644 --- a/exercises/practice/say/.docs/instructions.append.md +++ b/exercises/practice/say/.docs/instructions.append.md @@ -1,3 +1,4 @@ +# Instructions append ## Rust Specific Exercise Notes @@ -7,13 +8,8 @@ errors for out of range, we are using Rust's strong type system to limit input. It is much easier to make a function deal with all valid inputs, rather than requiring the user of your module to handle errors. -There is a -1 version of a test case, but it is commented out. -If your function is implemented properly, the -1 test case should not compile. - -Adding 'and' into number text has not been implemented in test cases. - ### Extension -Add capability of converting up to the max value for u64: 18,446,744,073,709,551,615. +Add capability of converting up to the max value for u64: `18_446_744_073_709_551_615`. For hints at the output this should have, look at the last test case. diff --git a/exercises/practice/say/.docs/instructions.md b/exercises/practice/say/.docs/instructions.md index 727b0186d..3251c519a 100644 --- a/exercises/practice/say/.docs/instructions.md +++ b/exercises/practice/say/.docs/instructions.md @@ -1,63 +1,12 @@ # Instructions -Given a number from 0 to 999,999,999,999, spell out that number in English. +Given a number, your task is to express it in English words exactly as your friend should say it out loud. +Yaʻqūb expects to use numbers from 0 up to 999,999,999,999. -## Step 1 +Examples: -Handle the basic case of 0 through 99. - -If the input to the program is `22`, then the output should be -`'twenty-two'`. - -Your program should complain loudly if given a number outside the -blessed range. - -Some good test cases for this program are: - -- 0 -- 14 -- 50 -- 98 -- -1 -- 100 - -### Extension - -If you're on a Mac, shell out to Mac OS X's `say` program to talk out -loud. If you're on Linux or Windows, eSpeakNG may be available with the command `espeak`. - -## Step 2 - -Implement breaking a number up into chunks of thousands. - -So `1234567890` should yield a list like 1, 234, 567, and 890, while the -far simpler `1000` should yield just 1 and 0. - -The program must also report any values that are out of range. - -## Step 3 - -Now handle inserting the appropriate scale word between those chunks. - -So `1234567890` should yield `'1 billion 234 million 567 thousand 890'` - -The program must also report any values that are out of range. It's -fine to stop at "trillion". - -## Step 4 - -Put it all together to get nothing but plain English. - -`12345` should give `twelve thousand three hundred forty-five`. - -The program must also report any values that are out of range. - -### Extensions - -Use _and_ (correctly) when spelling out the number in English: - -- 14 becomes "fourteen". -- 100 becomes "one hundred". -- 120 becomes "one hundred and twenty". -- 1002 becomes "one thousand and two". -- 1323 becomes "one thousand three hundred and twenty-three". +- 0 → zero +- 1 → one +- 12 → twelve +- 123 → one hundred twenty-three +- 1,234 → one thousand two hundred thirty-four diff --git a/exercises/practice/say/.docs/introduction.md b/exercises/practice/say/.docs/introduction.md new file mode 100644 index 000000000..abd22851e --- /dev/null +++ b/exercises/practice/say/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +Your friend Yaʻqūb works the counter at the busiest deli in town, slicing, weighing, and wrapping orders for a never-ending line of hungry customers. +To keep things moving, each customer takes a numbered ticket when they arrive. + +When it’s time to call the next person, Yaʻqūb reads their number out loud, always in full English words to make sure everyone hears it clearly. diff --git a/exercises/practice/say/.gitignore b/exercises/practice/say/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/say/.gitignore +++ b/exercises/practice/say/.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/say/.meta/additional-tests.json b/exercises/practice/say/.meta/additional-tests.json new file mode 100644 index 000000000..12d4a7906 --- /dev/null +++ b/exercises/practice/say/.meta/additional-tests.json @@ -0,0 +1,30 @@ +[ + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "max i64", + "comments": [ + "Idiomatic Rust emphasizes the principle", + "'Make invalid states impossible to represent'.", + "So it is appropriate to test the entire range of possible inputs." + ], + "property": "say", + "input": { + "number": 9223372036854775807 + }, + "expected": "nine quintillion two hundred twenty-three quadrillion three hundred seventy-two trillion thirty-six billion eight hundred fifty-four million seven hundred seventy-five thousand eight hundred seven" + }, + { + "uuid": "88808dac-dffb-46a6-95d4-68d5271a9c38", + "description": "max u64", + "comments": [ + "Idiomatic Rust emphasizes the principle", + "'Make invalid states impossible to represent'.", + "So it is appropriate to test the entire range of possible inputs." + ], + "property": "say", + "input": { + "number": 18446744073709551615 + }, + "expected": "eighteen quintillion four hundred forty-six quadrillion seven hundred forty-four trillion seventy-three billion seven hundred nine million five hundred fifty-one thousand six hundred fifteen" + } +] \ No newline at end of file diff --git a/exercises/practice/say/.meta/config.json b/exercises/practice/say/.meta/config.json index 42c063967..28df68200 100644 --- a/exercises/practice/say/.meta/config.json +++ b/exercises/practice/say/.meta/config.json @@ -33,6 +33,6 @@ ] }, "blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.", - "source": "A variation on JavaRanch CattleDrive, exercise 4a", - "source_url": "http://www.javaranch.com/say.jsp" + "source": "A variation on the JavaRanch CattleDrive, Assignment 4", + "source_url": "https://web.archive.org/web/20240907035912/https://coderanch.com/wiki/718804" } diff --git a/exercises/practice/say/.meta/test_template.tera b/exercises/practice/say/.meta/test_template.tera new file mode 100644 index 000000000..477939969 --- /dev/null +++ b/exercises/practice/say/.meta/test_template.tera @@ -0,0 +1,12 @@ +use say::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.number | fmt_num }}; + let output = encode(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/say/.meta/tests.toml b/exercises/practice/say/.meta/tests.toml index 6ef482ff1..1d2c4d469 100644 --- a/exercises/practice/say/.meta/tests.toml +++ b/exercises/practice/say/.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. [5d22a120-ba0c-428c-bd25-8682235d83e8] description = "zero" @@ -13,3 +20,52 @@ description = "fourteen" [f541dd8e-f070-4329-92b4-b7ce2fcf06b4] description = "twenty" + +[d78601eb-4a84-4bfa-bf0e-665aeb8abe94] +description = "twenty-two" + +[f010d4ca-12c9-44e9-803a-27789841adb1] +description = "thirty" + +[738ce12d-ee5c-4dfb-ad26-534753a98327] +description = "ninety-nine" + +[e417d452-129e-4056-bd5b-6eb1df334dce] +description = "one hundred" + +[d6924f30-80ba-4597-acf6-ea3f16269da8] +description = "one hundred twenty-three" + +[2f061132-54bc-4fd4-b5df-0a3b778959b9] +description = "two hundred" + +[feed6627-5387-4d38-9692-87c0dbc55c33] +description = "nine hundred ninety-nine" + +[3d83da89-a372-46d3-b10d-de0c792432b3] +description = "one thousand" + +[865af898-1d5b-495f-8ff0-2f06d3c73709] +description = "one thousand two hundred thirty-four" + +[b6a3f442-266e-47a3-835d-7f8a35f6cf7f] +description = "one million" + +[2cea9303-e77e-4212-b8ff-c39f1978fc70] +description = "one million two thousand three hundred forty-five" + +[3e240eeb-f564-4b80-9421-db123f66a38f] +description = "one billion" + +[9a43fed1-c875-4710-8286-5065d73b8a9e] +description = "a big number" + +[49a6a17b-084e-423e-994d-a87c0ecc05ef] +description = "numbers below zero are out of range" +include = false +comment = "explanation in .docs/instructions.append.md" + +[4d6492eb-5853-4d16-9d34-b0f61b261fd9] +description = "numbers above 999,999,999,999 are out of range" +include = false +comment = "explanation in .docs/instructions.append.md" diff --git a/exercises/practice/say/Cargo.toml b/exercises/practice/say/Cargo.toml index 9338eb9cc..3a1bb171b 100644 --- a/exercises/practice/say/Cargo.toml +++ b/exercises/practice/say/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "say" -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] diff --git a/exercises/practice/say/src/lib.rs b/exercises/practice/say/src/lib.rs index e10f3f3f1..38ddc45ed 100644 --- a/exercises/practice/say/src/lib.rs +++ b/exercises/practice/say/src/lib.rs @@ -1,3 +1,3 @@ pub fn encode(n: u64) -> String { - unimplemented!("Say {n} in English."); + todo!("Say {n} in English."); } diff --git a/exercises/practice/say/tests/say.rs b/exercises/practice/say/tests/say.rs index dc320dd96..6d902f4fb 100644 --- a/exercises/practice/say/tests/say.rs +++ b/exercises/practice/say/tests/say.rs @@ -1,160 +1,171 @@ -// Note: No tests created using 'and' with numbers. -// Apparently Most American English does not use the 'and' with numbers, -// where it is common in British English to use the 'and'. +use say::*; #[test] -fn test_zero() { - assert_eq!(say::encode(0), String::from("zero")); +fn zero() { + let input = 0; + let output = encode(input); + let expected = "zero"; + assert_eq!(output, expected); } -// -// If the below test is uncommented, it should not compile. -// -/* #[test] #[ignore] -fn test_negative() { - assert_eq!(say::encode(-1), String::from("won't compile")); +fn one() { + let input = 1; + let output = encode(input); + let expected = "one"; + assert_eq!(output, expected); } -*/ #[test] #[ignore] -fn test_one() { - assert_eq!(say::encode(1), String::from("one")); +fn fourteen() { + let input = 14; + let output = encode(input); + let expected = "fourteen"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_fourteen() { - assert_eq!(say::encode(14), String::from("fourteen")); +fn twenty() { + let input = 20; + let output = encode(input); + let expected = "twenty"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_twenty() { - assert_eq!(say::encode(20), String::from("twenty")); +fn twenty_two() { + let input = 22; + let output = encode(input); + let expected = "twenty-two"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_twenty_two() { - assert_eq!(say::encode(22), String::from("twenty-two")); +fn thirty() { + let input = 30; + let output = encode(input); + let expected = "thirty"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_one_hundred() { - assert_eq!(say::encode(100), String::from("one hundred")); +fn ninety_nine() { + let input = 99; + let output = encode(input); + let expected = "ninety-nine"; + assert_eq!(output, expected); } -// note, using American style with no and #[test] #[ignore] -fn test_one_hundred_twenty() { - assert_eq!(say::encode(120), String::from("one hundred twenty")); +fn one_hundred() { + let input = 100; + let output = encode(input); + let expected = "one hundred"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_one_hundred_twenty_three() { - assert_eq!(say::encode(123), String::from("one hundred twenty-three")); +fn one_hundred_twenty_three() { + let input = 123; + let output = encode(input); + let expected = "one hundred twenty-three"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_one_thousand() { - assert_eq!(say::encode(1000), String::from("one thousand")); +fn two_hundred() { + let input = 200; + let output = encode(input); + let expected = "two hundred"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_one_thousand_two_hundred_thirty_four() { - assert_eq!( - say::encode(1234), - String::from("one thousand two hundred thirty-four") - ); +fn nine_hundred_ninety_nine() { + let input = 999; + let output = encode(input); + let expected = "nine hundred ninety-nine"; + assert_eq!(output, expected); } -// note, using American style with no and #[test] #[ignore] -fn test_eight_hundred_and_ten_thousand() { - assert_eq!( - say::encode(810_000), - String::from("eight hundred ten thousand") - ); +fn one_thousand() { + let input = 1_000; + let output = encode(input); + let expected = "one thousand"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_one_million() { - assert_eq!(say::encode(1_000_000), String::from("one million")); +fn one_thousand_two_hundred_thirty_four() { + let input = 1_234; + let output = encode(input); + let expected = "one thousand two hundred thirty-four"; + assert_eq!(output, expected); } -// note, using American style with no and #[test] #[ignore] -fn test_one_million_two() { - assert_eq!(say::encode(1_000_002), String::from("one million two")); +fn one_million() { + let input = 1_000_000; + let output = encode(input); + let expected = "one million"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_1002345() { - assert_eq!( - say::encode(1_002_345), - String::from("one million two thousand three hundred forty-five") - ); +fn one_million_two_thousand_three_hundred_forty_five() { + let input = 1_002_345; + let output = encode(input); + let expected = "one million two thousand three hundred forty-five"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_one_billion() { - assert_eq!(say::encode(1_000_000_000), String::from("one billion")); +fn one_billion() { + let input = 1_000_000_000; + let output = encode(input); + let expected = "one billion"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_987654321123() { - assert_eq!( - say::encode(987_654_321_123), - String::from( - "nine hundred eighty-seven billion \ - six hundred fifty-four million \ - three hundred twenty-one thousand \ - one hundred twenty-three" - ) - ); +fn a_big_number() { + let input = 987_654_321_123; + let output = encode(input); + let expected = "nine hundred eighty-seven billion six hundred fifty-four million three hundred twenty-one thousand one hundred twenty-three"; + assert_eq!(output, expected); } -/* - These tests are only if you implemented full parsing for u64 type. -*/ #[test] #[ignore] -fn test_max_i64() { - assert_eq!( - say::encode(9_223_372_036_854_775_807), - String::from( - "nine quintillion two hundred twenty-three \ - quadrillion three hundred seventy-two trillion \ - thirty-six billion eight hundred fifty-four million \ - seven hundred seventy-five thousand eight hundred seven" - ) - ); +fn max_i64() { + let input = 9_223_372_036_854_775_807; + let output = encode(input); + let expected = "nine quintillion two hundred twenty-three quadrillion three hundred seventy-two trillion thirty-six billion eight hundred fifty-four million seven hundred seventy-five thousand eight hundred seven"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_max_u64() { - assert_eq!( - say::encode(18_446_744_073_709_551_615), - String::from( - "eighteen quintillion four hundred forty-six \ - quadrillion seven hundred forty-four trillion \ - seventy-three billion seven hundred nine million \ - five hundred fifty-one thousand six hundred fifteen" - ) - ); +fn max_u64() { + let input = 18_446_744_073_709_551_615; + let output = encode(input); + let expected = "eighteen quintillion four hundred forty-six quadrillion seven hundred forty-four trillion seventy-three billion seven hundred nine million five hundred fifty-one thousand six hundred fifteen"; + assert_eq!(output, expected); } diff --git a/exercises/practice/scale-generator/.gitignore b/exercises/practice/scale-generator/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/scale-generator/.gitignore +++ b/exercises/practice/scale-generator/.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/scale-generator/.meta/Cargo-example.toml b/exercises/practice/scale-generator/.meta/Cargo-example.toml deleted file mode 100644 index 1e740af67..000000000 --- a/exercises/practice/scale-generator/.meta/Cargo-example.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -edition = "2021" -name = "scale_generator" -version = "1.0.0" - -[dependencies] -enum-primitive-derive = "^0.1" -failure = "0.1" -failure_derive = "0.1" -itertools = "0.7" -num-traits = "^0.1" diff --git a/exercises/practice/scale-generator/.meta/config.json b/exercises/practice/scale-generator/.meta/config.json index ef0c54851..309f3cce7 100644 --- a/exercises/practice/scale-generator/.meta/config.json +++ b/exercises/practice/scale-generator/.meta/config.json @@ -18,7 +18,7 @@ "Cargo.toml" ], "test": [ - "tests/scale-generator.rs" + "tests/scale_generator.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/scale-generator/.meta/example.rs b/exercises/practice/scale-generator/.meta/example.rs index ceddf50f4..afa7631d6 100644 --- a/exercises/practice/scale-generator/.meta/example.rs +++ b/exercises/practice/scale-generator/.meta/example.rs @@ -1,357 +1,68 @@ -#[macro_use] -extern crate enum_primitive_derive; -extern crate failure; -#[macro_use] -extern crate failure_derive; -extern crate itertools; -extern crate num_traits; - -pub use self::interval::{Interval, Intervals}; -use self::note::Accidental; -pub use self::note::Note; -use failure::Error; -use std::str::FromStr; - -pub mod interval { - use itertools::Itertools; - use std::fmt; - use std::ops::Deref; - use std::str::FromStr; - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Fail)] - pub enum ParseErr { - #[fail(display = "invalid interval")] - InvalidInterval, - #[fail(display = "wrong number of semitones")] - WrongNumberOfSemitones, - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Primitive)] - pub enum Interval { - HalfStep = 1, - WholeStep = 2, - AugmentedFirst = 3, - } - - impl fmt::Display for Interval { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::Interval::*; - write!( - f, - "{}", - match self { - HalfStep => "m", - WholeStep => "M", - AugmentedFirst => "A", - } - ) - } - } - - impl FromStr for Interval { - type Err = ParseErr; - - fn from_str(s: &str) -> Result { - use self::Interval::*; - match s { - "m" => Ok(HalfStep), - "M" => Ok(WholeStep), - "A" => Ok(AugmentedFirst), - _ => Err(ParseErr::InvalidInterval), - } - } - } - - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct Intervals(Vec); - - impl fmt::Display for Intervals { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0.iter().join("")) - } - } - - impl FromStr for Intervals { - type Err = ParseErr; +#[derive(Debug)] +pub struct Error; - fn from_str(s: &str) -> Result { - let mut semitones = Vec::with_capacity(s.len()); +pub struct Scale(Vec); - for (i, c) in s.char_indices() { - semitones.push(Interval::from_str(&s[i..i + c.len_utf8()])?); - } - - if semitones.iter().take(12).map(|&i| i as u8).sum::() == 12 { - Ok(Intervals(semitones)) - } else { - Err(ParseErr::WrongNumberOfSemitones) - } - } - } +const NUM_TONICS: usize = 12; - impl Deref for Intervals { - type Target = Vec; +const SCALE_WITH_SHARPS: [&str; NUM_TONICS] = [ + "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", +]; - fn deref(&self) -> &Self::Target { - &self.0 - } - } +const SCALE_WITH_FLATS: [&str; NUM_TONICS] = [ + "A", "Bb", "B", "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", +]; - #[cfg(test)] - mod test { - use super::*; +const USING_FLATS: [&str; 12] = [ + "F", "Bb", "Eb", "Ab", "Db", "Gb", "d", "g", "c", "f", "bb", "eb", +]; - #[test] - fn test_parse_chromatic() { - assert!("mmmmmmmmmmmm".parse::().is_ok()); - } +fn get_uppercase_tonic(tonic: &str) -> String { + let mut iter = tonic.chars(); + let mut s = String::new(); - #[test] - fn test_parse_major() { - assert!("MMmMMMm".parse::().is_ok()); - } + let first = iter.next().unwrap().to_uppercase().next().unwrap(); - #[test] - fn test_parse_minor() { - assert!("MmMMmMM".parse::().is_ok()); - } - } + s.push(first); + s.extend(iter); + s } -pub mod note { - use crate::Interval; - use num_traits::{FromPrimitive, ToPrimitive}; - use std::fmt; - use std::ops::AddAssign; - use std::str::FromStr; - - pub const SEMITONES: i8 = 12; - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Primitive)] - pub enum Semitone { - A = 0, - ASharp = 1, - B = 2, - C = 3, - CSharp = 4, - D = 5, - DSharp = 6, - E = 7, - F = 8, - FSharp = 9, - G = 10, - GSharp = 11, - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Primitive)] - pub enum Root { - A = 0, - B = 2, - C = 3, - D = 5, - E = 7, - F = 8, - G = 10, - } - - impl fmt::Display for Root { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum Accidental { - Sharp, - Flat, - } - - impl Accidental { - fn to_i8(&self) -> i8 { - match *self { - Accidental::Sharp => 1, - Accidental::Flat => -1, - } - } - - pub fn from_tonic(tonic: &str) -> Accidental { - match tonic { - "C" | "a" | "G" | "D" | "A" | "E" | "B" | "F#" | "e" | "b" | "f#" | "c#" | "g#" - | "d#" => Accidental::Sharp, - _ => Accidental::Flat, - } - } - } - - impl fmt::Display for Accidental { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match &self { - Accidental::Sharp => '#', - Accidental::Flat => 'b', - } - ) - } - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub struct Note { - tonic: Root, - accidental: Option, - } - - impl Note { - pub fn canonicalize(&self, lean: Accidental) -> Note { - let mut n: Note = Semitone::from(*self).into(); - if let Some(accidental) = n.accidental { - if accidental != lean && lean == Accidental::Flat { - n += Interval::HalfStep; - n.accidental = Some(Accidental::Flat); - } - } - n - } - } - - impl fmt::Display for Note { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}{}", - self.tonic, - self.accidental.map_or(String::new(), |a| a.to_string()), - ) - } - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Fail)] - pub enum ParseErr { - #[fail(display = "invalid length")] - InvalidLength, - #[fail(display = "invalid tonic")] - InvalidTonic, - #[fail(display = "invalid accidental")] - InvalidAccidental, - } - - impl FromStr for Note { - type Err = ParseErr; - - fn from_str(s: &str) -> Result { - let lc = s.to_lowercase(); - let mut iter = lc.chars(); - - let mut note = match iter.next() { - Some(c) if ('a'..='g').contains(&c) => Note { - tonic: match c { - 'a' => Root::A, - 'b' => Root::B, - 'c' => Root::C, - 'd' => Root::D, - 'e' => Root::E, - 'f' => Root::F, - 'g' => Root::G, - _ => return Err(ParseErr::InvalidTonic), - }, - accidental: None, - }, - Some(_) => return Err(ParseErr::InvalidTonic), - None => return Err(ParseErr::InvalidLength), - }; - - match iter.next() { - Some('b') => note.accidental = Some(Accidental::Flat), - Some('#') => note.accidental = Some(Accidental::Sharp), - Some(_) => return Err(ParseErr::InvalidAccidental), - None => {} - } - - if iter.next().is_some() { - return Err(ParseErr::InvalidLength); - } - - Ok(note) - } - } - - impl From for Note { - fn from(s: Semitone) -> Self { - Note { - tonic: match s { - Semitone::A | Semitone::ASharp => Root::A, - Semitone::B => Root::B, - Semitone::C | Semitone::CSharp => Root::C, - Semitone::D | Semitone::DSharp => Root::D, - Semitone::E => Root::E, - Semitone::F | Semitone::FSharp => Root::F, - Semitone::G | Semitone::GSharp => Root::G, - }, - accidental: match s { - Semitone::ASharp - | Semitone::CSharp - | Semitone::DSharp - | Semitone::FSharp - | Semitone::GSharp => Some(Accidental::Sharp), - _ => None, - }, +impl Scale { + pub fn new(tonic: &str, intervals: &str) -> Result { + let scale = if USING_FLATS.contains(&tonic) { + &SCALE_WITH_FLATS + } else { + &SCALE_WITH_SHARPS + }; + + let tonic = get_uppercase_tonic(tonic); + + let mut tonics_iter = scale + .iter() + .cycle() + .skip_while(|&&t| t != tonic) + .map(|&t| t.to_owned()); + + let mut v = vec![tonics_iter.next().unwrap()]; + + for interv in intervals.bytes() { + match interv { + b'm' => v.push(tonics_iter.next().unwrap()), + b'M' => v.push(tonics_iter.nth(1).unwrap()), + b'A' => v.push(tonics_iter.nth(2).unwrap()), + _ => panic!("unknown interval"), } } - } - - impl From for Semitone { - fn from(n: Note) -> Self { - Semitone::from_i8( - (SEMITONES + n.tonic.to_i8().unwrap() + n.accidental.map_or(0, |a| a.to_i8())) - % SEMITONES, - ) - .expect("must have valid semitone") - } - } - - impl AddAssign for Note { - fn add_assign(&mut self, rhs: Interval) { - *self = Semitone::from_i8( - (SEMITONES + Semitone::from(*self).to_i8().unwrap() + rhs.to_i8().unwrap()) - % SEMITONES, - ) - .unwrap() - .into(); - } - } -} - -#[derive(Debug)] -pub struct Scale { - tonic: Note, - lean: Accidental, - intervals: Intervals, -} -impl Scale { - pub fn new(tonic: &str, intervals: &str) -> Result { - Ok(Scale { - tonic: Note::from_str(tonic)?, - lean: Accidental::from_tonic(tonic), - intervals: Intervals::from_str(intervals)?, - }) + Ok(Scale(v)) } pub fn chromatic(tonic: &str) -> Result { - Scale::new(tonic, "mmmmmmmmmmmm") + Self::new(tonic, "mmmmmmmmmmmm") } pub fn enumerate(&self) -> Vec { - let mut out = Vec::with_capacity(self.intervals.len()); - - let mut note = self.tonic; - out.push(note.canonicalize(self.lean).to_string()); - for &interval in self.intervals.iter() { - note += interval; - out.push(note.canonicalize(self.lean).to_string()); - } - - out + self.0.clone() } } diff --git a/exercises/practice/scale-generator/Cargo.toml b/exercises/practice/scale-generator/Cargo.toml index 65ed9ae27..254b870d1 100644 --- a/exercises/practice/scale-generator/Cargo.toml +++ b/exercises/practice/scale-generator/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "scale_generator" -version = "2.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/scale-generator/src/lib.rs b/exercises/practice/scale-generator/src/lib.rs index 74d8a0201..3cf6503c5 100644 --- a/exercises/practice/scale-generator/src/lib.rs +++ b/exercises/practice/scale-generator/src/lib.rs @@ -16,14 +16,14 @@ pub struct Scale; impl Scale { pub fn new(tonic: &str, intervals: &str) -> Result { - unimplemented!("Construct a new scale with tonic {tonic} and intervals {intervals}") + todo!("Construct a new scale with tonic {tonic} and intervals {intervals}") } pub fn chromatic(tonic: &str) -> Result { - unimplemented!("Construct a new chromatic scale with tonic {tonic}") + todo!("Construct a new chromatic scale with tonic {tonic}") } pub fn enumerate(&self) -> Vec { - unimplemented!() + todo!() } } diff --git a/exercises/practice/scale-generator/tests/scale-generator.rs b/exercises/practice/scale-generator/tests/scale_generator.rs similarity index 88% rename from exercises/practice/scale-generator/tests/scale-generator.rs rename to exercises/practice/scale-generator/tests/scale_generator.rs index 8d2bcd92a..2119a91ea 100644 --- a/exercises/practice/scale-generator/tests/scale-generator.rs +++ b/exercises/practice/scale-generator/tests/scale_generator.rs @@ -31,7 +31,7 @@ fn process_interval_case(tonic: &str, intervals: &str, expected: &[&str]) { #[test] /// Chromatic scale with sharps -fn test_chromatic_scale_with_sharps() { +fn chromatic_scale_with_sharps() { process_chromatic_case( "C", &[ @@ -43,7 +43,7 @@ fn test_chromatic_scale_with_sharps() { #[test] #[ignore] /// Chromatic scale with flats -fn test_chromatic_scale_with_flats() { +fn chromatic_scale_with_flats() { process_chromatic_case( "F", &[ @@ -61,28 +61,28 @@ fn test_chromatic_scale_with_flats() { /// Simple major scale /// /// The simplest major scale, with no sharps or flats. -fn test_simple_major_scale() { +fn simple_major_scale() { process_interval_case("C", "MMmMMMm", &["C", "D", "E", "F", "G", "A", "B", "C"]); } #[test] #[ignore] /// Major scale with sharps -fn test_major_scale_with_sharps() { +fn major_scale_with_sharps() { process_interval_case("G", "MMmMMMm", &["G", "A", "B", "C", "D", "E", "F#", "G"]); } #[test] #[ignore] /// Major scale with flats -fn test_major_scale_with_flats() { +fn major_scale_with_flats() { process_interval_case("F", "MMmMMMm", &["F", "G", "A", "Bb", "C", "D", "E", "F"]); } #[test] #[ignore] /// Minor scale with sharps -fn test_minor_scale_with_sharps() { +fn minor_scale_with_sharps() { process_interval_case( "f#", "MmMMmMM", @@ -93,7 +93,7 @@ fn test_minor_scale_with_sharps() { #[test] #[ignore] /// Minor scale with flats -fn test_minor_scale_with_flats() { +fn minor_scale_with_flats() { process_interval_case( "bb", "MmMMmMM", @@ -104,14 +104,14 @@ fn test_minor_scale_with_flats() { #[test] #[ignore] /// Dorian mode -fn test_dorian_mode() { +fn dorian_mode() { process_interval_case("d", "MmMMMmM", &["D", "E", "F", "G", "A", "B", "C", "D"]); } #[test] #[ignore] /// Mixolydian mode -fn test_mixolydian_mode() { +fn mixolydian_mode() { process_interval_case( "Eb", "MMmMMmM", @@ -122,7 +122,7 @@ fn test_mixolydian_mode() { #[test] #[ignore] /// Lydian mode -fn test_lydian_mode() { +fn lydian_mode() { process_interval_case( "a", "MMMmMMm", @@ -133,14 +133,14 @@ fn test_lydian_mode() { #[test] #[ignore] /// Phrygian mode -fn test_phrygian_mode() { +fn phrygian_mode() { process_interval_case("e", "mMMMmMM", &["E", "F", "G", "A", "B", "C", "D", "E"]); } #[test] #[ignore] /// Locrian mode -fn test_locrian_mode() { +fn locrian_mode() { process_interval_case( "g", "mMMmMMM", @@ -153,14 +153,14 @@ fn test_locrian_mode() { /// Harmonic minor /// /// Note that this case introduces the augmented second interval (A) -fn test_harmonic_minor() { +fn harmonic_minor() { process_interval_case("d", "MmMMmAm", &["D", "E", "F", "G", "A", "Bb", "Db", "D"]); } #[test] #[ignore] /// Octatonic -fn test_octatonic() { +fn octatonic() { process_interval_case( "C", "MmMmMmMm", @@ -171,21 +171,21 @@ fn test_octatonic() { #[test] #[ignore] /// Hexatonic -fn test_hexatonic() { +fn hexatonic() { process_interval_case("Db", "MMMMMM", &["Db", "Eb", "F", "G", "A", "B", "Db"]); } #[test] #[ignore] /// Pentatonic -fn test_pentatonic() { +fn pentatonic() { process_interval_case("A", "MMAMA", &["A", "B", "C#", "E", "F#", "A"]); } #[test] #[ignore] /// Enigmatic -fn test_enigmatic() { +fn enigmatic() { process_interval_case( "G", "mAMMMmm", diff --git a/exercises/practice/scrabble-score/.docs/instructions.md b/exercises/practice/scrabble-score/.docs/instructions.md index 3f986c947..738f928c5 100644 --- a/exercises/practice/scrabble-score/.docs/instructions.md +++ b/exercises/practice/scrabble-score/.docs/instructions.md @@ -1,40 +1,25 @@ # Instructions -Given a word, compute the Scrabble score for that word. +Your task is to compute a word's Scrabble score by summing the values of its letters. -## Letter Values +The letters are valued as follows: -You'll need these: +| Letter | Value | +| ---------------------------- | ----- | +| A, E, I, O, U, L, N, R, S, T | 1 | +| D, G | 2 | +| B, C, M, P | 3 | +| F, H, V, W, Y | 4 | +| K | 5 | +| J, X | 8 | +| Q, Z | 10 | -```text -Letter Value -A, E, I, O, U, L, N, R, S, T 1 -D, G 2 -B, C, M, P 3 -F, H, V, W, Y 4 -K 5 -J, X 8 -Q, Z 10 -``` - -## Examples - -"cabbage" should be scored as worth 14 points: +For example, the word "cabbage" is worth 14 points: - 3 points for C -- 1 point for A, twice -- 3 points for B, twice +- 1 point for A +- 3 points for B +- 3 points for B +- 1 point for A - 2 points for G - 1 point for E - -And to total: - -- `3 + 2*1 + 2*3 + 2 + 1` -- = `3 + 2 + 6 + 3` -- = `5 + 9` -- = 14 - -## Extensions - -- You can play a double or a triple letter. -- You can play a double or a triple word. diff --git a/exercises/practice/scrabble-score/.docs/introduction.md b/exercises/practice/scrabble-score/.docs/introduction.md new file mode 100644 index 000000000..8821f240b --- /dev/null +++ b/exercises/practice/scrabble-score/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Scrabble][wikipedia] is a word game where players place letter tiles on a board to form words. +Each letter has a value. +A word's score is the sum of its letters' values. + +[wikipedia]: https://en.wikipedia.org/wiki/Scrabble diff --git a/exercises/practice/scrabble-score/.gitignore b/exercises/practice/scrabble-score/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/scrabble-score/.gitignore +++ b/exercises/practice/scrabble-score/.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/scrabble-score/.meta/additional-tests.json b/exercises/practice/scrabble-score/.meta/additional-tests.json new file mode 100644 index 000000000..ad0cf25e6 --- /dev/null +++ b/exercises/practice/scrabble-score/.meta/additional-tests.json @@ -0,0 +1,28 @@ +[ + { + "uuid": "9e0faee7-dc23-460b-afec-17c7145ae564", + "description": "non english scrabble letters do not score", + "comments": [ + "Unicode tests are not suitable to be upstreamed.", + "Handling unicode is tedious in many languages." + ], + "property": "score", + "input": { + "word": "piñata" + }, + "expected": 7 + }, + { + "uuid": "3099e8fd-0077-4520-be33-54bb9a1c0dc7", + "description": "german letters do not score", + "comments": [ + "Unicode tests are not suitable to be upstreamed.", + "Handling unicode is tedious in many languages." + ], + "property": "score", + "input": { + "word": "STRAßE" + }, + "expected": 5 + } +] diff --git a/exercises/practice/scrabble-score/.meta/config.json b/exercises/practice/scrabble-score/.meta/config.json index 4050334a9..9ae2fca18 100644 --- a/exercises/practice/scrabble-score/.meta/config.json +++ b/exercises/practice/scrabble-score/.meta/config.json @@ -28,7 +28,7 @@ "Cargo.toml" ], "test": [ - "tests/scrabble-score.rs" + "tests/scrabble_score.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/scrabble-score/.meta/test_template.tera b/exercises/practice/scrabble-score/.meta/test_template.tera new file mode 100644 index 000000000..d73cb83fb --- /dev/null +++ b/exercises/practice/scrabble-score/.meta/test_template.tera @@ -0,0 +1,12 @@ +use scrabble_score::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.word | json_encode() }}; + let output = score(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/scrabble-score/.meta/tests.toml b/exercises/practice/scrabble-score/.meta/tests.toml index aa9692e2a..33a873c05 100644 --- a/exercises/practice/scrabble-score/.meta/tests.toml +++ b/exercises/practice/scrabble-score/.meta/tests.toml @@ -1,6 +1,43 @@ -# 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. + +[f46cda29-1ca5-4ef2-bd45-388a767e3db2] +description = "lowercase letter" + +[f7794b49-f13e-45d1-a933-4e48459b2201] +description = "uppercase letter" + +[eaba9c76-f9fa-49c9-a1b0-d1ba3a5b31fa] +description = "valuable letter" + +[f3c8c94e-bb48-4da2-b09f-e832e103151e] +description = "short word" + +[71e3d8fa-900d-4548-930e-68e7067c4615] +description = "short, valuable word" [d3088ad9-570c-4b51-8764-c75d5a430e99] description = "medium word" + +[fa20c572-ad86-400a-8511-64512daac352] +description = "medium, valuable word" + +[9336f0ba-9c2b-4fa0-bd1c-2e2d328cf967] +description = "long, mixed-case word" + +[1e34e2c3-e444-4ea7-b598-3c2b46fd2c10] +description = "english-like word" + +[4efe3169-b3b6-4334-8bae-ff4ef24a7e4f] +description = "empty input" + +[3b305c1c-f260-4e15-a5b5-cb7d3ea7c3d7] +description = "entire alphabet available" diff --git a/exercises/practice/scrabble-score/Cargo.toml b/exercises/practice/scrabble-score/Cargo.toml index 7de526ac3..eacff3dd8 100644 --- a/exercises/practice/scrabble-score/Cargo.toml +++ b/exercises/practice/scrabble-score/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "scrabble-score" -version = "1.1.0" +name = "scrabble_score" +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/scrabble-score/src/lib.rs b/exercises/practice/scrabble-score/src/lib.rs index bd9a9d35d..d875e1977 100644 --- a/exercises/practice/scrabble-score/src/lib.rs +++ b/exercises/practice/scrabble-score/src/lib.rs @@ -1,4 +1,4 @@ /// Compute the Scrabble score for a word. pub fn score(word: &str) -> u64 { - unimplemented!("Score {word} in Scrabble."); + todo!("Score {word} in Scrabble."); } diff --git a/exercises/practice/scrabble-score/tests/scrabble-score.rs b/exercises/practice/scrabble-score/tests/scrabble-score.rs deleted file mode 100644 index ab7439a92..000000000 --- a/exercises/practice/scrabble-score/tests/scrabble-score.rs +++ /dev/null @@ -1,74 +0,0 @@ -use scrabble_score::*; - -#[test] -fn a_is_worth_one_point() { - assert_eq!(score("a"), 1); -} - -#[test] -#[ignore] -fn scoring_is_case_insensitive() { - assert_eq!(score("A"), 1); -} - -#[test] -#[ignore] -fn f_is_worth_four() { - assert_eq!(score("f"), 4); -} - -#[test] -#[ignore] -fn two_one_point_letters_make_a_two_point_word() { - assert_eq!(score("at"), 2); -} - -#[test] -#[ignore] -fn three_letter_word() { - assert_eq!(score("zoo"), 12); -} - -#[test] -#[ignore] -fn medium_word() { - assert_eq!(score("street"), 6); -} - -#[test] -#[ignore] -fn longer_words_with_valuable_letters() { - assert_eq!(score("quirky"), 22); -} - -#[test] -#[ignore] -fn long_mixed_case_word() { - assert_eq!(score("OxyphenButazone"), 41); -} - -#[test] -#[ignore] -fn non_english_scrabble_letters_do_not_score() { - assert_eq!(score("pinata"), 8, "'n' should score 1"); - assert_eq!(score("piñata"), 7, "'ñ' should score 0"); -} - -#[test] -#[ignore] -fn empty_words_are_worth_zero() { - assert_eq!(score(""), 0); -} - -#[test] -#[ignore] -fn all_letters_work() { - assert_eq!(score("abcdefghijklmnopqrstuvwxyz"), 87); -} - -#[test] -#[ignore] -fn german_letters_do_not_score() { - assert_eq!(score("STRASSE"), 7, "\"SS\" should score 2"); - assert_eq!(score("STRAßE"), 5, "'ß' should score 0"); -} diff --git a/exercises/practice/scrabble-score/tests/scrabble_score.rs b/exercises/practice/scrabble-score/tests/scrabble_score.rs new file mode 100644 index 000000000..a864b899a --- /dev/null +++ b/exercises/practice/scrabble-score/tests/scrabble_score.rs @@ -0,0 +1,117 @@ +use scrabble_score::*; + +#[test] +fn lowercase_letter() { + let input = "a"; + let output = score(input); + let expected = 1; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn uppercase_letter() { + let input = "A"; + let output = score(input); + let expected = 1; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn valuable_letter() { + let input = "f"; + let output = score(input); + let expected = 4; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn short_word() { + let input = "at"; + let output = score(input); + let expected = 2; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn short_valuable_word() { + let input = "zoo"; + let output = score(input); + let expected = 12; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn medium_word() { + let input = "street"; + let output = score(input); + let expected = 6; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn medium_valuable_word() { + let input = "quirky"; + let output = score(input); + let expected = 22; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn long_mixed_case_word() { + let input = "OxyphenButazone"; + let output = score(input); + let expected = 41; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn english_like_word() { + let input = "pinata"; + let output = score(input); + let expected = 8; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn empty_input() { + let input = ""; + let output = score(input); + let expected = 0; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn entire_alphabet_available() { + let input = "abcdefghijklmnopqrstuvwxyz"; + let output = score(input); + let expected = 87; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn non_english_scrabble_letters_do_not_score() { + let input = "piñata"; + let output = score(input); + let expected = 7; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn german_letters_do_not_score() { + let input = "STRAßE"; + let output = score(input); + let expected = 5; + assert_eq!(output, expected); +} diff --git a/exercises/practice/secret-handshake/.approaches/config.json b/exercises/practice/secret-handshake/.approaches/config.json new file mode 100644 index 000000000..484a72695 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/config.json @@ -0,0 +1,15 @@ +{ + "introduction": { + "authors": ["bobahop"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "cd0c0a31-0905-47f0-815c-02597a7cdf40", + "slug": "iterate-once", + "title": "Iterate once", + "blurb": "Iterate once even when reversed.", + "authors": ["bobahop"] + } + ] +} diff --git a/exercises/practice/secret-handshake/.approaches/introduction.md b/exercises/practice/secret-handshake/.approaches/introduction.md new file mode 100644 index 000000000..ff9c1007e --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/introduction.md @@ -0,0 +1,39 @@ +# Introduction + +There are many ways to solve Secret Handshake. +One approach is to iterate only once, even when the signs are to be reversed. + +## General guidance + +Something to consider is to keep the number of iterations at a minimum to get the best performance. +However, if that is felt to adversely impact readability, then to use a series of `if` statements and then reverse is also valid. + +## Approach: Iterate once + +```rust +const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"]; +const REVERSE_SIGNS: u8 = 16; + +pub fn actions(n: u8) -> Vec<&'static str> { + let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), + }; + let mut output: Vec<&'static str> = Vec::new(); + + loop { + if action == end { + break; + } + if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) + } + action += action_incr + } + output +} +``` + +For more information, check the [Iterate once approach][approach-iterate-once]. + +[approach-iterate-once]: https://exercism.org/tracks/rust/exercises/secret-handshake/approaches/iterate-once diff --git a/exercises/practice/secret-handshake/.approaches/iterate-once/content.md b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md new file mode 100644 index 000000000..e4c686646 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md @@ -0,0 +1,99 @@ +# Iterate once + +```rust +const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"]; +const REVERSE_SIGNS: u8 = 16; + +pub fn actions(n: u8) -> Vec<&'static str> { + let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), + }; + let mut output: Vec<&'static str> = Vec::new(); + + loop { + if action == end { + break; + } + if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) + } + action += action_incr + } + output +} +``` + +This approach starts by defining a fixed-size [array][array] to hold the signal values in normal order. + +The `[&'static str; 4]` is used to give the type and length of the array. +To be a [`const`][const], the size of the array must be known at compile time, so setting the type and length must be done explicitly, +so the size in bytes of the fixed array can be deduced by the compiler from that and not by inspecting the element types and counting +the elements itself. + +The value of `16` is defined as a `const` with a meaningful name so it won't be used as a [magic number][magic-number]. + +The `actions` function uses multiple assignment with a `match` expression to define the variables that control iterating through the signals array, +setting their values to iterate in either the normal or reverse order. + +The [bitwise AND operator][bitand] is used to check if the input number contains the signal for reversing the order of the other signals. + +For example, if the number passed in is `19`, which is `10011` in binary, then it is ANDed with `16`, which is `10000` in binary. +The `1` in `10000` is also at the same position in `10011`, so the two values ANDed will not be `0`. + +- `10011` AND +- `10000` = +- `10000` + +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `16`, which is `10000` in binary. +The `1` in `10000` is not at the same position in `00011`, so the two values ANDed will be `0`. + +- `00011` AND +- `10000` = +- `00000` + +If the number passed in does not contain the signal for reverse, then the iteration variables are set to iterate through the array of signals +in their normal order, otherwise they are set to iterate through the arrray backwards.. + +The output `vector` is defined, and then the [`loop`][loop] begins. + +Normal iteration will start at index `0`. +Reverse iteration will start at index `3`. + +Normal iteration will terminate when the index equals `4`. +Reverse iteration will terminate when the index equals `-1`. + +Normal iteration will increase the index by `1` for each iteration. +Reverse iteration will decrease the index by `1` for each iteration. + +For each iteration of the `loop`, the AND operator is used to check if the number passed in contains `1` [shifted left][shl] (`<<`) for the number of positions +as the value being iterated. + +```rust +if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) +} +``` + +For example, if the number being iterated is `0`, then `1` is shifted left `0` times (so not shifted at all), and the number passed in is ANDed with `00001`. +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00001`. +`00011` ANDed with `00001` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`. +The index used is the number being iterated, which is `0`, so the element at index `0` (`"wink"`) would be added to the output `vector` +using the [push][push] function. + +If the number being iterated is `1`, then `1` is shifted left `1` time, and the number passed in is ANDed with `00010`. +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00010`. +`00011` ANDed with `00010` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`. +The index used is the number being iterated, which is `1`, so the element at index `1` (`"double blink"`) would be added to the output `vector`. + +If the number passed in ANDed with the number being iterated is equal to `0`, then the signal in the array for that index is not added to the output `vector`. + +After iterating through the array of signals is done, the output `vector` is returned from the function. + +[array]: https://doc.rust-lang.org/std/primitive.array.html +[const]: https://doc.rust-lang.org/std/keyword.const.html +[magic-number]: https://en.wikipedia.org/wiki/Magic_number_(programming) +[bitand]: https://doc.rust-lang.org/std/ops/trait.BitAnd.html +[shl]: https://doc.rust-lang.org/std/ops/trait.Shl.html +[loop]: https://doc.rust-lang.org/rust-by-example/flow_control/loop.html +[push]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.push diff --git a/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt b/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt new file mode 100644 index 000000000..e42302ba8 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt @@ -0,0 +1,4 @@ +let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), +}; diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md new file mode 100644 index 000000000..d2120b9bf --- /dev/null +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -0,0 +1,48 @@ +# Instructions + +Your task is to convert a number between 1 and 31 to a sequence of actions in the secret handshake. + +The sequence of actions is chosen by looking at the rightmost five digits of the number once it's been converted to binary. +Start at the right-most digit and move left. + +The actions for each number place are: + +```plaintext +00001 = wink +00010 = double blink +00100 = close your eyes +01000 = jump +10000 = Reverse the order of the operations in the secret handshake. +``` + +Let's use the number `9` as an example: + +- 9 in binary is `1001`. +- The digit that is farthest to the right is 1, so the first action is `wink`. +- Going left, the next digit is 0, so there is no double-blink. +- Going left again, the next digit is 0, so you leave your eyes open. +- Going left again, the next digit is 1, so you jump. + +That was the last digit, so the final code is: + +```plaintext +wink, jump +``` + +Given the number 26, which is `11010` in binary, we get the following actions: + +- double blink +- jump +- reverse actions + +The secret handshake for 26 is therefore: + +```plaintext +jump, double blink +``` + +~~~~exercism/note +If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. + +[intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa +~~~~ diff --git a/exercises/practice/secret-handshake/.docs/introduction.md b/exercises/practice/secret-handshake/.docs/introduction.md new file mode 100644 index 000000000..176b92e8c --- /dev/null +++ b/exercises/practice/secret-handshake/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You are starting a secret coding club with some friends and friends-of-friends. +Not everyone knows each other, so you and your friends have decided to create a secret handshake that you can use to recognize that someone is a member. +You don't want anyone who isn't in the know to be able to crack the code. + +You've designed the code so that one person says a number between 1 and 31, and the other person turns it into a series of actions. diff --git a/exercises/practice/secret-handshake/.gitignore b/exercises/practice/secret-handshake/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/secret-handshake/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/secret-handshake/.meta/config.json b/exercises/practice/secret-handshake/.meta/config.json new file mode 100644 index 000000000..88501c1fe --- /dev/null +++ b/exercises/practice/secret-handshake/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "dem4ron" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/secret_handshake.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.", + "source": "Bert, in Mary Poppins", + "source_url": "https://www.imdb.com/title/tt0058331/quotes/?item=qt0437047" +} diff --git a/exercises/practice/secret-handshake/.meta/example.rs b/exercises/practice/secret-handshake/.meta/example.rs new file mode 100644 index 000000000..88afcfad5 --- /dev/null +++ b/exercises/practice/secret-handshake/.meta/example.rs @@ -0,0 +1,16 @@ +const ACTIONS: [&str; 4] = ["wink", "double blink", "close your eyes", "jump"]; + +pub fn actions(n: u8) -> Vec<&'static str> { + let result: Vec<&str> = ACTIONS + .iter() + .enumerate() + .filter(|(i, _)| (1u8 << *i) & n != 0) + .map(|(_, &c)| c) + .collect(); + + if n & 16 != 0 { + result.into_iter().rev().collect() + } else { + result + } +} diff --git a/exercises/practice/secret-handshake/.meta/tests.toml b/exercises/practice/secret-handshake/.meta/tests.toml new file mode 100644 index 000000000..f318e5283 --- /dev/null +++ b/exercises/practice/secret-handshake/.meta/tests.toml @@ -0,0 +1,43 @@ +# 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. + +[b8496fbd-6778-468c-8054-648d03c4bb23] +description = "wink for 1" + +[83ec6c58-81a9-4fd1-bfaf-0160514fc0e3] +description = "double blink for 10" + +[0e20e466-3519-4134-8082-5639d85fef71] +description = "close your eyes for 100" + +[b339ddbb-88b7-4b7d-9b19-4134030d9ac0] +description = "jump for 1000" + +[40499fb4-e60c-43d7-8b98-0de3ca44e0eb] +description = "combine two actions" + +[9730cdd5-ef27-494b-afd3-5c91ad6c3d9d] +description = "reverse two actions" + +[0b828205-51ca-45cd-90d5-f2506013f25f] +description = "reversing one action gives the same action" + +[9949e2ac-6c9c-4330-b685-2089ab28b05f] +description = "reversing no actions still gives no actions" + +[23fdca98-676b-4848-970d-cfed7be39f81] +description = "all possible actions" + +[ae8fe006-d910-4d6f-be00-54b7c3799e79] +description = "reverse all possible actions" + +[3d36da37-b31f-4cdb-a396-d93a2ee1c4a5] +description = "do nothing for zero" diff --git a/exercises/practice/secret-handshake/Cargo.toml b/exercises/practice/secret-handshake/Cargo.toml new file mode 100644 index 000000000..52f429426 --- /dev/null +++ b/exercises/practice/secret-handshake/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "secret_handshake" +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/secret-handshake/src/lib.rs b/exercises/practice/secret-handshake/src/lib.rs new file mode 100644 index 000000000..98f52b19e --- /dev/null +++ b/exercises/practice/secret-handshake/src/lib.rs @@ -0,0 +1,3 @@ +pub fn actions(n: u8) -> Vec<&'static str> { + todo!("What is the secret handshake for {n}?") +} diff --git a/exercises/practice/secret-handshake/tests/secret_handshake.rs b/exercises/practice/secret-handshake/tests/secret_handshake.rs new file mode 100644 index 000000000..4fb6b59c5 --- /dev/null +++ b/exercises/practice/secret-handshake/tests/secret_handshake.rs @@ -0,0 +1,72 @@ +use secret_handshake::*; + +#[test] +fn wink_for_1() { + assert_eq!(actions(1), vec!["wink"]) +} + +#[test] +#[ignore] +fn double_blink_for_10() { + assert_eq!(actions(2), vec!["double blink"]) +} + +#[test] +#[ignore] +fn close_your_eyes_for_100() { + assert_eq!(actions(4), vec!["close your eyes"]) +} + +#[test] +#[ignore] +fn jump_for_1000() { + assert_eq!(actions(8), vec!["jump"]) +} + +#[test] +#[ignore] +fn combine_two_actions() { + assert_eq!(actions(3), vec!["wink", "double blink"]) +} + +#[test] +#[ignore] +fn reverse_two_actions() { + assert_eq!(actions(19), vec!["double blink", "wink"]) +} + +#[test] +#[ignore] +fn reversing_one_action_gives_the_same_action() { + assert_eq!(actions(24), vec!["jump"]) +} + +#[test] +#[ignore] +fn reversing_no_actions_still_gives_no_actions() { + assert_eq!(actions(16), Vec::<&'static str>::new()) +} + +#[test] +#[ignore] +fn all_possible_actions() { + assert_eq!( + actions(15), + vec!["wink", "double blink", "close your eyes", "jump"] + ) +} + +#[test] +#[ignore] +fn reverse_all_possible_actions() { + assert_eq!( + actions(31), + vec!["jump", "close your eyes", "double blink", "wink"] + ) +} + +#[test] +#[ignore] +fn do_nothing_for_zero() { + assert_eq!(actions(0), Vec::<&'static str>::new()) +} diff --git a/exercises/practice/series/.docs/instructions.append.md b/exercises/practice/series/.docs/instructions.append.md new file mode 100644 index 000000000..a45087895 --- /dev/null +++ b/exercises/practice/series/.docs/instructions.append.md @@ -0,0 +1,10 @@ +# Instructions append + +Different languages on Exercism have different expectations about what the result should be if the length of the substrings is zero. +On the Rust track, we don't have a test for that case, so you are free to do what you feel is most appropriate. + +Consider the advantages and disadvantages of the following possibilities: +- Crash the program with `panic!`. +- Return a `Result::Err`. (not possible here, because the function signature is given) +- Return an empty vector. +- Return a vector containing as many empty strings as the length of the string "digits" **plus one**. (this has some nice mathematical properties!) diff --git a/exercises/practice/series/.docs/instructions.md b/exercises/practice/series/.docs/instructions.md index 3f9d371fa..fd97a6706 100644 --- a/exercises/practice/series/.docs/instructions.md +++ b/exercises/practice/series/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -Given a string of digits, output all the contiguous substrings of length `n` in -that string in the order that they appear. +Given a string of digits, output all the contiguous substrings of length `n` in that string in the order that they appear. For example, the string "49142" has the following 3-digit series: @@ -14,8 +13,7 @@ And the following 4-digit series: - "4914" - "9142" -And if you ask for a 6-digit series from a 5-digit string, you deserve -whatever you get. +And if you ask for a 6-digit series from a 5-digit string, you deserve whatever you get. -Note that these series are only required to occupy *adjacent positions* -in the input; the digits need not be *numerically consecutive*. +Note that these series are only required to occupy _adjacent positions_ in the input; +the digits need not be _numerically consecutive_. diff --git a/exercises/practice/series/.gitignore b/exercises/practice/series/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/series/.gitignore +++ b/exercises/practice/series/.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/series/.meta/config.json b/exercises/practice/series/.meta/config.json index 677992183..0960722a3 100644 --- a/exercises/practice/series/.meta/config.json +++ b/exercises/practice/series/.meta/config.json @@ -29,5 +29,5 @@ }, "blurb": "Given a string of digits, output all the contiguous substrings of length `n` in that string.", "source": "A subset of the Problem 8 at Project Euler", - "source_url": "http://projecteuler.net/problem=8" + "source_url": "https://projecteuler.net/problem=8" } diff --git a/exercises/practice/series/.meta/test_template.tera b/exercises/practice/series/.meta/test_template.tera new file mode 100644 index 000000000..a093d0e2f --- /dev/null +++ b/exercises/practice/series/.meta/test_template.tera @@ -0,0 +1,22 @@ +use series::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.series | json_encode() }}; + let length = {{ test.input.sliceLength | json_encode() }}; + let output = series(input, length); +{%- if test.expected is object -%} + {# + The author of the exercise chose to define the semantics such that + "invalid" series return empty vectors. Adding error handling to the + exercise later would be a breaking change. + #} + let expected: &[&str] = &[]; +{% else %} + let expected = &{{ test.expected | json_encode() }}; +{% endif -%} + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/series/.meta/tests.toml b/exercises/practice/series/.meta/tests.toml index be690e975..778d6b794 100644 --- a/exercises/practice/series/.meta/tests.toml +++ b/exercises/practice/series/.meta/tests.toml @@ -1,3 +1,70 @@ -# 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. + +[7ae7a46a-d992-4c2a-9c15-a112d125ebad] +description = "slices of one from one" + +[3143b71d-f6a5-4221-aeae-619f906244d2] +description = "slices of one from two" + +[dbb68ff5-76c5-4ccd-895a-93dbec6d5805] +description = "slices of two" + +[19bbea47-c987-4e11-a7d1-e103442adf86] +description = "slices of two overlap" + +[8e17148d-ba0a-4007-a07f-d7f87015d84c] +description = "slices can include duplicates" + +[bd5b085e-f612-4f81-97a8-6314258278b0] +description = "slices of a long series" + +[6d235d85-46cf-4fae-9955-14b6efef27cd] +description = "slice length is too large" + +[d7957455-346d-4e47-8e4b-87ed1564c6d7] +description = "slice length is way too large" + +[d34004ad-8765-4c09-8ba1-ada8ce776806] +description = "slice length cannot be zero" +include = false +comment = """ + This test case has caused a certain amount of confusion. + In the original implementation of the exercise, the test expected: + + series("12345", 0) -> ["", "", "", "", "", ""] + + Reasonable arguments have been made on both sides: + https://github.com/exercism/rust/pull/402 + https://github.com/exercism/rust/issues/671 + + The problem-specifications repository defines this case to return an error. + But the existing interface of the exercise doesn't allow error handling, + so changing it now would be a breaking change. + + It was decided to simply exclude this test case. + Handling this specific edge case in the very specific way it was designed + in the original implementation doesn't further the student's understanding + of Rust as a language. + + In order to avoid breaking existing solutions, there should not be any + tests added in the future where the length of the slice is exactly one more + than the length of the input string. If such tests are added in + problem-specifications, they should be excluded as well. +""" + +[10ab822d-8410-470a-a85d-23fbeb549e54] +description = "slice length cannot be negative" +include = false +comment = "the function signature already prevents this (usize)" + +[c7ed0812-0e4b-4bf3-99c4-28cbbfc246a2] +description = "empty series is invalid" diff --git a/exercises/practice/series/Cargo.toml b/exercises/practice/series/Cargo.toml index 4588185aa..a7aca7910 100644 --- a/exercises/practice/series/Cargo.toml +++ b/exercises/practice/series/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "series" 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/series/src/lib.rs b/exercises/practice/series/src/lib.rs index 9337bb7af..e73729f6c 100644 --- a/exercises/practice/series/src/lib.rs +++ b/exercises/practice/series/src/lib.rs @@ -1,3 +1,3 @@ pub fn series(digits: &str, len: usize) -> Vec { - unimplemented!("What are the series of length {len} in string {digits:?}") + todo!("What are the series of length {len} in string {digits:?}") } diff --git a/exercises/practice/series/tests/series.rs b/exercises/practice/series/tests/series.rs index ddfb6e2da..dc73172ee 100644 --- a/exercises/practice/series/tests/series.rs +++ b/exercises/practice/series/tests/series.rs @@ -1,40 +1,92 @@ use series::*; #[test] -fn test_with_zero_length() { - let expected = vec!["".to_string(); 6]; - assert_eq!(series("92017", 0), expected); +fn slices_of_one_from_one() { + let input = "1"; + let length = 1; + let output = series(input, length); + let expected = &["1"]; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_with_length_2() { - let expected = vec![ - "92".to_string(), - "20".to_string(), - "01".to_string(), - "17".to_string(), +fn slices_of_one_from_two() { + let input = "12"; + let length = 1; + let output = series(input, length); + let expected = &["1", "2"]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn slices_of_two() { + let input = "35"; + let length = 2; + let output = series(input, length); + let expected = &["35"]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn slices_of_two_overlap() { + let input = "9142"; + let length = 2; + let output = series(input, length); + let expected = &["91", "14", "42"]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn slices_can_include_duplicates() { + let input = "777777"; + let length = 3; + let output = series(input, length); + let expected = &["777", "777", "777", "777"]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn slices_of_a_long_series() { + let input = "918493904243"; + let length = 5; + let output = series(input, length); + let expected = &[ + "91849", "18493", "84939", "49390", "93904", "39042", "90424", "04243", ]; - assert_eq!(series("92017", 2), expected); + assert_eq!(output, expected); } #[test] #[ignore] -fn test_with_numbers_length() { - let expected = vec!["92017".to_string()]; - assert_eq!(series("92017", 5), expected); +fn slice_length_is_too_large() { + let input = "12345"; + let length = 6; + let output = series(input, length); + let expected: &[&str] = &[]; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_too_long() { - let expected: Vec = vec![]; - assert_eq!(series("92017", 6), expected); +fn slice_length_is_way_too_large() { + let input = "12345"; + let length = 42; + let output = series(input, length); + let expected: &[&str] = &[]; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_way_too_long() { - let expected: Vec = vec![]; - assert_eq!(series("92017", 42), expected); +fn empty_series_is_invalid() { + let input = ""; + let length = 1; + let output = series(input, length); + let expected: &[&str] = &[]; + assert_eq!(output, expected); } diff --git a/exercises/practice/sieve/.approaches/introduction.md b/exercises/practice/sieve/.approaches/introduction.md index 7291c2883..a247dc0e8 100644 --- a/exercises/practice/sieve/.approaches/introduction.md +++ b/exercises/practice/sieve/.approaches/introduction.md @@ -20,7 +20,7 @@ pub fn primes_up_to(upper_bound: u64) -> Vec { (2..numbers.len()) .filter_map(|i| { let prime = numbers[i].take()? as usize; - (prime + prime..=upper_bound) + (prime * prime..=upper_bound) .step_by(prime) .for_each(|j| numbers[j] = None); Some(prime as u64) diff --git a/exercises/practice/sieve/.approaches/ranges-and-filtermap/content.md b/exercises/practice/sieve/.approaches/ranges-and-filtermap/content.md index cba7635de..d9fc134ad 100644 --- a/exercises/practice/sieve/.approaches/ranges-and-filtermap/content.md +++ b/exercises/practice/sieve/.approaches/ranges-and-filtermap/content.md @@ -7,7 +7,7 @@ pub fn primes_up_to(upper_bound: u64) -> Vec { (2..numbers.len()) .filter_map(|i| { let prime = numbers[i].take()? as usize; - (prime + prime..=upper_bound) + (prime * prime..=upper_bound) .step_by(prime) .for_each(|j| numbers[j] = None); Some(prime as u64) @@ -25,7 +25,7 @@ Each number from the range is passed to the [`filter_map()`][filtermap]. The [closure][closure] (also known as a lambda) in the body of the `filter_map()` uses the [`take()`][take] method, combined with the unwrap operator (`?`), to get the element value in the `Vec` at the index of the number passed in from the range. If the element value is `None`, then no further processing happens in the lambda for that iteration. -If the element value is `Some` number, then an inner range is defined, starting from the element value plus itself and going through the upper bound. +If the element value is `Some` number, then an inner range is defined, starting from the element value times itself and going through the upper bound. The [`step_by()`][stepby] method is used to traverse the range in steps the size of the element value. diff --git a/exercises/practice/sieve/.approaches/ranges-and-filtermap/snippet.txt b/exercises/practice/sieve/.approaches/ranges-and-filtermap/snippet.txt index f098c28c6..a331efc85 100644 --- a/exercises/practice/sieve/.approaches/ranges-and-filtermap/snippet.txt +++ b/exercises/practice/sieve/.approaches/ranges-and-filtermap/snippet.txt @@ -1,7 +1,7 @@ (2..numbers.len()) .filter_map(|i| { let prime = numbers[i].take()? as usize; - (prime + prime..=upper_bound) + (prime * prime..=upper_bound) .step_by(prime) .for_each(|j| numbers[j] = None); Some(prime as u64) diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 7228737a2..71292e178 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -1,30 +1,101 @@ # Instructions -Use the Sieve of Eratosthenes to find all the primes from 2 up to a given -number. +Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find all prime numbers less than or equal to a given number. -The Sieve of Eratosthenes is a simple, ancient algorithm for finding all -prime numbers up to any given limit. It does so by iteratively marking as -composite (i.e. not prime) the multiples of each prime, starting with the -multiples of 2. It does not use any division or remainder operation. +A prime number is a number larger than 1 that is only divisible by 1 and itself. +For example, 2, 3, 5, 7, 11, and 13 are prime numbers. +By contrast, 6 is _not_ a prime number as it not only divisible by 1 and itself, but also by 2 and 3. -Create your range, starting at two and continuing up to and including the given limit. (i.e. [2, limit]) +To use the Sieve of Eratosthenes, first, write out all the numbers from 2 up to and including your given number. +Then, follow these steps: -The algorithm consists of repeating the following over and over: +1. Find the next unmarked number (skipping over marked numbers). + This is a prime number. +2. Mark all the multiples of that prime number as **not** prime. -- take the next available unmarked number in your list (it is prime) -- mark all the multiples of that number (they are not prime) +Repeat the steps until you've gone through every number. +At the end, all the unmarked numbers are prime. -Repeat until you have processed each number in your range. +~~~~exercism/note +The Sieve of Eratosthenes marks off multiples of each prime using addition (repeatedly adding the prime) or multiplication (directly computing its multiples), rather than checking each number for divisibility. -When the algorithm terminates, all the numbers in the list that have not -been marked are prime. +The tests don't check that you've implemented the algorithm, only that you've come up with the correct primes. +~~~~ -The wikipedia article has a useful graphic that explains the algorithm: -https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +## Example -Notice that this is a very specific algorithm, and the tests don't check -that you've implemented the algorithm, only that you've come up with the -correct list of primes. A good first test is to check that you do not use -division or remainder operations (div, /, mod or % depending on the -language). +Let's say you're finding the primes less than or equal to 10. + +- Write out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. + + ```text + 2 3 4 5 6 7 8 9 10 + ``` + +- 2 is unmarked and is therefore a prime. + Mark 4, 6, 8 and 10 as "not prime". + + ```text + 2 3 [4] 5 [6] 7 [8] 9 [10] + ↑ + ``` + +- 3 is unmarked and is therefore a prime. + Mark 6 and 9 as not prime _(marking 6 is optional - as it's already been marked)_. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 4 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 5 is unmarked and is therefore a prime. + Mark 10 as not prime _(optional - as it's already been marked)_. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 6 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 7 is unmarked and is therefore a prime. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 8 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 9 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 10 is marked as "not prime", so we stop as there are no more numbers to check. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +You've examined all the numbers and found that 2, 3, 5, and 7 are still unmarked, meaning they're the primes less than or equal to 10. diff --git a/exercises/practice/sieve/.docs/introduction.md b/exercises/practice/sieve/.docs/introduction.md new file mode 100644 index 000000000..f6c1cf79a --- /dev/null +++ b/exercises/practice/sieve/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You bought a big box of random computer parts at a garage sale. +You've started putting the parts together to build custom computers. + +You want to test the performance of different combinations of parts, and decide to create your own benchmarking program to see how your computers compare. +You choose the famous "Sieve of Eratosthenes" algorithm, an ancient algorithm, but one that should push your computers to the limits. diff --git a/exercises/practice/sieve/.gitignore b/exercises/practice/sieve/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/sieve/.gitignore +++ b/exercises/practice/sieve/.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/sieve/.meta/config.json b/exercises/practice/sieve/.meta/config.json index 40fe741f2..caeb2186c 100644 --- a/exercises/practice/sieve/.meta/config.json +++ b/exercises/practice/sieve/.meta/config.json @@ -33,5 +33,5 @@ }, "blurb": "Use the Sieve of Eratosthenes to find all the primes from 2 up to a given number.", "source": "Sieve of Eratosthenes at Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes" + "source_url": "https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes" } diff --git a/exercises/practice/sieve/.meta/test_template.tera b/exercises/practice/sieve/.meta/test_template.tera new file mode 100644 index 000000000..fcd7aa4c5 --- /dev/null +++ b/exercises/practice/sieve/.meta/test_template.tera @@ -0,0 +1,12 @@ +use sieve::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.limit | json_encode() }}; + let output = primes_up_to(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/sieve/.meta/tests.toml b/exercises/practice/sieve/.meta/tests.toml index 69b57ccf2..fec5e1a1a 100644 --- a/exercises/practice/sieve/.meta/tests.toml +++ b/exercises/practice/sieve/.meta/tests.toml @@ -1,6 +1,25 @@ -# 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. + +[88529125-c4ce-43cc-bb36-1eb4ddd7b44f] +description = "no primes under two" + +[4afe9474-c705-4477-9923-840e1024cc2b] +description = "find first prime" + +[974945d8-8cd9-4f00-9463-7d813c7f17b7] +description = "find primes up to 10" [2e2417b7-3f3a-452a-8594-b9af08af6d82] description = "limit is prime" + +[92102a05-4c7c-47de-9ed0-b7d5fcd00f21] +description = "find primes up to 1000" diff --git a/exercises/practice/sieve/Cargo.toml b/exercises/practice/sieve/Cargo.toml index 7c35c45e1..bb52d8f89 100644 --- a/exercises/practice/sieve/Cargo.toml +++ b/exercises/practice/sieve/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "sieve" -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/sieve/src/lib.rs b/exercises/practice/sieve/src/lib.rs index 7f5d35f30..23a75707c 100644 --- a/exercises/practice/sieve/src/lib.rs +++ b/exercises/practice/sieve/src/lib.rs @@ -1,3 +1,3 @@ pub fn primes_up_to(upper_bound: u64) -> Vec { - unimplemented!("Construct a vector of all primes up to {upper_bound}"); + todo!("Construct a vector of all primes up to {upper_bound}"); } diff --git a/exercises/practice/sieve/tests/sieve.rs b/exercises/practice/sieve/tests/sieve.rs index 98e521e77..312458825 100644 --- a/exercises/practice/sieve/tests/sieve.rs +++ b/exercises/practice/sieve/tests/sieve.rs @@ -1,30 +1,46 @@ +use sieve::*; + #[test] -fn limit_lower_than_the_first_prime() { - assert_eq!(sieve::primes_up_to(1), []); +fn no_primes_under_two() { + let input = 1; + let output = primes_up_to(input); + let expected = []; + assert_eq!(output, expected); } #[test] #[ignore] -fn limit_is_the_first_prime() { - assert_eq!(sieve::primes_up_to(2), [2]); +fn find_first_prime() { + let input = 2; + let output = primes_up_to(input); + let expected = [2]; + assert_eq!(output, expected); } #[test] #[ignore] -fn primes_up_to_10() { - assert_eq!(sieve::primes_up_to(10), [2, 3, 5, 7]); +fn find_primes_up_to_10() { + let input = 10; + let output = primes_up_to(input); + let expected = [2, 3, 5, 7]; + assert_eq!(output, expected); } #[test] #[ignore] fn limit_is_prime() { - assert_eq!(sieve::primes_up_to(13), [2, 3, 5, 7, 11, 13]); + let input = 13; + let output = primes_up_to(input); + let expected = [2, 3, 5, 7, 11, 13]; + assert_eq!(output, expected); } #[test] #[ignore] -fn limit_of_1000() { - let expected = vec![ +fn find_primes_up_to_1000() { + let input = 1000; + let output = primes_up_to(input); + let expected = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, @@ -35,5 +51,5 @@ fn limit_of_1000() { 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, ]; - assert_eq!(sieve::primes_up_to(1000), expected); + assert_eq!(output, expected); } diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md index 22a7e4d4b..afd0b57da 100644 --- a/exercises/practice/simple-cipher/.docs/instructions.md +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -1,79 +1,40 @@ # Instructions -Implement a simple shift cipher like Caesar and a more secure substitution cipher. +Create an implementation of the [Vigenère cipher][wiki]. +The Vigenère cipher is a simple substitution cipher. -## Step 1 +## Cipher terminology -"If he had anything confidential to say, he wrote it in cipher, that is, -by so changing the order of the letters of the alphabet, that not a word -could be made out. If anyone wishes to decipher these, and get at their -meaning, he must substitute the fourth letter of the alphabet, namely D, -for A, and so with the others." -—Suetonius, Life of Julius Caesar +A cipher is an algorithm used to encrypt, or encode, a string. +The unencrypted string is called the _plaintext_ and the encrypted string is called the _ciphertext_. +Converting plaintext to ciphertext is called _encoding_ while the reverse is called _decoding_. -Ciphers are very straight-forward algorithms that allow us to render -text less readable while still allowing easy deciphering. They are -vulnerable to many forms of cryptanalysis, but we are lucky that -generally our little sisters are not cryptanalysts. +In a _substitution cipher_, each plaintext letter is replaced with a ciphertext letter which is computed with the help of a _key_. +(Note, it is possible for replacement letter to be the same as the original letter.) -The Caesar Cipher was used for some messages from Julius Caesar that -were sent afield. Now Caesar knew that the cipher wasn't very good, but -he had one ally in that respect: almost nobody could read well. So even -being a couple letters off was sufficient so that people couldn't -recognize the few words that they did know. +## Encoding details -Your task is to create a simple shift cipher like the Caesar Cipher. -This image is a great example of the Caesar Cipher: +In this cipher, the key is a series of lowercase letters, such as `"abcd"`. +Each letter of the plaintext is _shifted_ or _rotated_ by a distance based on a corresponding letter in the key. +An `"a"` in the key means a shift of 0 (that is, no shift). +A `"b"` in the key means a shift of 1. +A `"c"` in the key means a shift of 2, and so on. -![Caesar Cipher][1] +The first letter of the plaintext uses the first letter of the key, the second letter of the plaintext uses the second letter of the key and so on. +If you run out of letters in the key before you run out of letters in the plaintext, start over from the start of the key again. -For example: +If the key only contains one letter, such as `"dddddd"`, then all letters of the plaintext are shifted by the same amount (three in this example), which would make this the same as a rotational cipher or shift cipher (sometimes called a Caesar cipher). +For example, the plaintext `"iamapandabear"` would become `"ldpdsdqgdehdu"`. -Giving "iamapandabear" as input to the encode function returns the cipher "ldpdsdqgdehdu". Obscure enough to keep our message secret in transit. +If the key only contains the letter `"a"` (one or more times), the shift distance is zero and the ciphertext is the same as the plaintext. -When "ldpdsdqgdehdu" is put into the decode function it would return -the original "iamapandabear" letting your friend read your original -message. +Usually the key is more complicated than that, though! +If the key is `"abcd"` then letters of the plaintext would be shifted by a distance of 0, 1, 2, and 3. +If the plaintext is `"hello"`, we need 5 shifts so the key would wrap around, giving shift distances of 0, 1, 2, 3, and 0. +Applying those shifts to the letters of `"hello"` we get `"hfnoo"`. -## Step 2 +## Random keys -Shift ciphers are no fun though when your kid sister figures it out. Try -amending the code to allow us to specify a key and use that for the -shift distance. This is called a substitution cipher. +If no key is provided, generate a key which consists of at least 100 random lowercase letters from the Latin alphabet. -Here's an example: - -Given the key "aaaaaaaaaaaaaaaaaa", encoding the string "iamapandabear" -would return the original "iamapandabear". - -Given the key "ddddddddddddddddd", encoding our string "iamapandabear" -would return the obscured "ldpdsdqgdehdu" - -In the example above, we've set a = 0 for the key value. So when the -plaintext is added to the key, we end up with the same message coming -out. So "aaaa" is not an ideal key. But if we set the key to "dddd", we -would get the same thing as the Caesar Cipher. - -## Step 3 - -The weakest link in any cipher is the human being. Let's make your -substitution cipher a little more fault tolerant by providing a source -of randomness and ensuring that the key contains only lowercase letters. - -If someone doesn't submit a key at all, generate a truly random key of -at least 100 lowercase characters in length. - -## Extensions - -Shift ciphers work by making the text slightly odd, but are vulnerable -to frequency analysis. Substitution ciphers help that, but are still -very vulnerable when the key is short or if spaces are preserved. Later -on you'll see one solution to this problem in the exercise -"crypto-square". - -If you want to go farther in this field, the questions begin to be about -how we can exchange keys in a secure way. Take a look at [Diffie-Hellman -on Wikipedia][dh] for one of the first implementations of this scheme. - -[1]: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Caesar_cipher_left_shift_of_3.svg/320px-Caesar_cipher_left_shift_of_3.svg.png -[dh]: http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange +[wiki]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher diff --git a/exercises/practice/simple-cipher/.gitignore b/exercises/practice/simple-cipher/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/simple-cipher/.gitignore +++ b/exercises/practice/simple-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/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json index 34cd79628..66439abf1 100644 --- a/exercises/practice/simple-cipher/.meta/config.json +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -23,13 +23,13 @@ "Cargo.toml" ], "test": [ - "tests/simple-cipher.rs" + "tests/simple_cipher.rs" ], "example": [ ".meta/example.rs" ] }, - "blurb": "Implement a simple shift cipher like Caesar and a more secure substitution cipher.", + "blurb": "Implement the Vigenère cipher, a simple substitution cipher.", "source": "Substitution Cipher at Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Substitution_cipher" + "source_url": "https://en.wikipedia.org/wiki/Substitution_cipher" } diff --git a/exercises/practice/simple-cipher/.meta/example.rs b/exercises/practice/simple-cipher/.meta/example.rs index fb18e9f9f..33a49501c 100644 --- a/exercises/practice/simple-cipher/.meta/example.rs +++ b/exercises/practice/simple-cipher/.meta/example.rs @@ -1,10 +1,10 @@ use rand::Rng; pub fn encode_random(s: &str) -> (String, String) { - let mut r = rand::thread_rng(); + let mut r = rand::rng(); let mut key = String::new(); for _ in 0..100 { - key.push(char::from(b'a' + r.gen_range(0..26))); + key.push(char::from(b'a' + r.random_range(0..26))); } let encoded = encode(&key, s); (key, encoded.unwrap()) diff --git a/exercises/practice/simple-cipher/.meta/tests.toml b/exercises/practice/simple-cipher/.meta/tests.toml index be690e975..77e6571e4 100644 --- a/exercises/practice/simple-cipher/.meta/tests.toml +++ b/exercises/practice/simple-cipher/.meta/tests.toml @@ -1,3 +1,46 @@ -# 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. + +[b8bdfbe1-bea3-41bb-a999-b41403f2b15d] +description = "Random key cipher -> Can encode" + +[3dff7f36-75db-46b4-ab70-644b3f38b81c] +description = "Random key cipher -> Can decode" + +[8143c684-6df6-46ba-bd1f-dea8fcb5d265] +description = "Random key cipher -> Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method" + +[defc0050-e87d-4840-85e4-51a1ab9dd6aa] +description = "Random key cipher -> Key is made only of lowercase letters" + +[565e5158-5b3b-41dd-b99d-33b9f413c39f] +description = "Substitution cipher -> Can encode" + +[d44e4f6a-b8af-4e90-9d08-fd407e31e67b] +description = "Substitution cipher -> Can decode" + +[70a16473-7339-43df-902d-93408c69e9d1] +description = "Substitution cipher -> Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method" + +[69a1458b-92a6-433a-a02d-7beac3ea91f9] +description = "Substitution cipher -> Can double shift encode" + +[21d207c1-98de-40aa-994f-86197ae230fb] +description = "Substitution cipher -> Can wrap on encode" + +[a3d7a4d7-24a9-4de6-bdc4-a6614ced0cb3] +description = "Substitution cipher -> Can wrap on decode" + +[e31c9b8c-8eb6-45c9-a4b5-8344a36b9641] +description = "Substitution cipher -> Can encode messages longer than the key" + +[93cfaae0-17da-4627-9a04-d6d1e1be52e3] +description = "Substitution cipher -> Can decode messages longer than the key" diff --git a/exercises/practice/simple-cipher/Cargo.toml b/exercises/practice/simple-cipher/Cargo.toml index 5a2dcf498..718fbe537 100644 --- a/exercises/practice/simple-cipher/Cargo.toml +++ b/exercises/practice/simple-cipher/Cargo.toml @@ -1,7 +1,10 @@ [package] -edition = "2021" -name = "simple-cipher" -version = "0.0.0" +name = "simple_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] -rand = "0.8" +rand = "0.9" diff --git a/exercises/practice/simple-cipher/src/lib.rs b/exercises/practice/simple-cipher/src/lib.rs index 847d76533..b3ab8b141 100644 --- a/exercises/practice/simple-cipher/src/lib.rs +++ b/exercises/practice/simple-cipher/src/lib.rs @@ -1,13 +1,11 @@ pub fn encode(key: &str, s: &str) -> Option { - unimplemented!("Use {key} to encode {s} using shift cipher") + todo!("Use {key} to encode {s} using shift cipher") } pub fn decode(key: &str, s: &str) -> Option { - unimplemented!("Use {key} to decode {s} using shift cipher") + todo!("Use {key} to decode {s} using shift cipher") } pub fn encode_random(s: &str) -> (String, String) { - unimplemented!( - "Generate random key with only a-z chars and encode {s}. Return tuple (key, encoded s)" - ) + todo!("Generate random key with only a-z chars and encode {s}. Return tuple (key, encoded s)") } diff --git a/exercises/practice/simple-cipher/tests/simple-cipher.rs b/exercises/practice/simple-cipher/tests/simple_cipher.rs similarity index 100% rename from exercises/practice/simple-cipher/tests/simple-cipher.rs rename to exercises/practice/simple-cipher/tests/simple_cipher.rs diff --git a/exercises/practice/simple-linked-list/.approaches/do-not-keep-track-of-length/content.md b/exercises/practice/simple-linked-list/.approaches/do-not-keep-track-of-length/content.md index 24baa3232..55faa7807 100644 --- a/exercises/practice/simple-linked-list/.approaches/do-not-keep-track-of-length/content.md +++ b/exercises/practice/simple-linked-list/.approaches/do-not-keep-track-of-length/content.md @@ -23,11 +23,11 @@ impl SimpleLinkedList { pub fn new() -> Self { Self { head: None } } - + pub fn is_empty(&self) -> bool { self.head.is_none() } - + pub fn len(&self) -> usize { let mut current_node = &self.head; let mut size = 0; @@ -37,12 +37,12 @@ impl SimpleLinkedList { } size } - + pub fn push(&mut self, element: T) { let node = Box::new(Node::new(element, self.head.take())); self.head = Some(node); } - + pub fn pop(&mut self) -> Option { if self.head.is_some() { let head_node = self.head.take().unwrap(); @@ -52,11 +52,11 @@ impl SimpleLinkedList { None } } - + pub fn peek(&self) -> Option<&T> { self.head.as_ref().map(|head| &(head.data)) } - + pub fn rev(self) -> SimpleLinkedList { let mut list = SimpleLinkedList::new(); let mut cur_node = self.head; diff --git a/exercises/practice/simple-linked-list/.approaches/introduction.md b/exercises/practice/simple-linked-list/.approaches/introduction.md index 345176cbf..ea38f326f 100644 --- a/exercises/practice/simple-linked-list/.approaches/introduction.md +++ b/exercises/practice/simple-linked-list/.approaches/introduction.md @@ -7,7 +7,7 @@ Another approach is to calculate the length every time it is asked for. ## General guidance One thing to keep in mind is to not mutate the list when it is not necessary. -For instance, if you find yourself using `mut self` for `rev()` or `into()`, that is an indication that the list is being mutated when it is not necessary. +For instance, if you find yourself using `mut self` for `rev()` or `into()`, that is an indication that the list is being mutated when it is not necessary. A well-known treatment of writing linked lists in Rust is [`Learn Rust With Entirely Too Many Linked Lists`][too-many-lists]. @@ -132,11 +132,11 @@ impl SimpleLinkedList { pub fn new() -> Self { Self { head: None } } - + pub fn is_empty(&self) -> bool { self.head.is_none() } - + pub fn len(&self) -> usize { let mut current_node = &self.head; let mut size = 0; @@ -146,12 +146,12 @@ impl SimpleLinkedList { } size } - + pub fn push(&mut self, element: T) { let node = Box::new(Node::new(element, self.head.take())); self.head = Some(node); } - + pub fn pop(&mut self) -> Option { if self.head.is_some() { let head_node = self.head.take().unwrap(); @@ -161,11 +161,11 @@ impl SimpleLinkedList { None } } - + pub fn peek(&self) -> Option<&T> { self.head.as_ref().map(|head| &(head.data)) } - + pub fn rev(self) -> SimpleLinkedList { let mut list = SimpleLinkedList::new(); let mut cur_node = self.head; diff --git a/exercises/practice/simple-linked-list/.docs/instructions.append.md b/exercises/practice/simple-linked-list/.docs/hints.md similarity index 63% rename from exercises/practice/simple-linked-list/.docs/instructions.append.md rename to exercises/practice/simple-linked-list/.docs/hints.md index eabb3266c..b9520d6d2 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.append.md +++ b/exercises/practice/simple-linked-list/.docs/hints.md @@ -1,23 +1,30 @@ -# Implementation Hints +## General + +Do not implement the struct `SimpleLinkedList` as a wrapper around a `Vec`. Instead, allocate nodes on the heap. -Do not implement the struct `SimpleLinkedList` as a wrapper around a `Vec`. Instead, allocate nodes on the heap. This might be implemented as: + ``` pub struct SimpleLinkedList { head: Option>>, } ``` -The `head` field points to the first element (Node) of this linked list. + +The `head` field points to the first element (Node) of this linked list. + This implementation also requires a struct `Node` with the following fields: + ``` struct Node { data: T, next: Option>>, } ``` -`data` contains the stored data, and `next` points to the following node (if available) or None. + +`data` contains the stored data, and `next` points to the following node (if available) or None. ## Why `Option>>` and not just `Option>`? + Try it on your own. You will get the following error. ``` @@ -26,8 +33,8 @@ Try it on your own. You will get the following error. ... | next: Option>, | --------------------- recursive without indirection - ``` +``` - The problem is that at compile time the size of next must be known. - Since `next` is recursive ("a node has a node has a node..."), the compiler does not know how much memory is to be allocated. - In contrast, [Box](https://doc.rust-lang.org/std/boxed/) is a heap pointer with a defined size. +The problem is that at compile time the size of next must be known. +Since `next` is recursive ("a node has a node has a node..."), the compiler does not know how much memory is to be allocated. +In contrast, [Box](https://doc.rust-lang.org/std/boxed/) is a heap pointer with a defined size. diff --git a/exercises/practice/simple-linked-list/.docs/instructions.md b/exercises/practice/simple-linked-list/.docs/instructions.md index 1c9d0b3de..04640b1fb 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.md @@ -1,22 +1,19 @@ # Instructions -Write a simple linked list implementation that uses Elements and a List. +Write a prototype of the music player application. -The linked list is a fundamental data structure in computer science, -often used in the implementation of other data structures. They're -pervasive in functional programming languages, such as Clojure, Erlang, -or Haskell, but far less common in imperative languages such as Ruby or -Python. +For the prototype, each song will simply be represented by a number. +Given a range of numbers (the song IDs), create a singly linked list. -The simplest kind of linked list is a singly linked list. Each element in the -list contains data and a "next" field pointing to the next element in the list -of elements. +Given a singly linked list, you should be able to reverse the list to play the songs in the opposite order. -This variant of linked lists is often used to represent sequences or -push-down stacks (also called a LIFO stack; Last In, First Out). +~~~~exercism/note +The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. -As a first take, lets create a singly linked list to contain the range (1..10), -and provide functions to reverse a linked list and convert to and from arrays. +The simplest kind of linked list is a **singly** linked list. +That means that each element (or "node") contains data, along with something that points to the next node in the list. -When implementing this in a language with built-in linked lists, -implement your own abstract data type. +If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. + +[intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d +~~~~ diff --git a/exercises/practice/simple-linked-list/.docs/introduction.md b/exercises/practice/simple-linked-list/.docs/introduction.md new file mode 100644 index 000000000..0e1df72f9 --- /dev/null +++ b/exercises/practice/simple-linked-list/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +You work for a music streaming company. + +You've been tasked with creating a playlist feature for your music player application. diff --git a/exercises/practice/simple-linked-list/.gitignore b/exercises/practice/simple-linked-list/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/simple-linked-list/.gitignore +++ b/exercises/practice/simple-linked-list/.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/simple-linked-list/.meta/config.json b/exercises/practice/simple-linked-list/.meta/config.json index 9f1cd2d46..ad25f43fc 100644 --- a/exercises/practice/simple-linked-list/.meta/config.json +++ b/exercises/practice/simple-linked-list/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/simple-linked-list.rs" + "tests/simple_linked_list.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/simple-linked-list/.meta/example.rs b/exercises/practice/simple-linked-list/.meta/example.rs index e64a55b01..7061edb76 100644 --- a/exercises/practice/simple-linked-list/.meta/example.rs +++ b/exercises/practice/simple-linked-list/.meta/example.rs @@ -1,5 +1,3 @@ -use std::iter::FromIterator; - pub struct SimpleLinkedList { head: Option>>, len: usize, @@ -10,6 +8,12 @@ struct Node { next: Option>>, } +impl Default for SimpleLinkedList { + fn default() -> Self { + Self::new() + } +} + impl SimpleLinkedList { pub fn new() -> Self { SimpleLinkedList { head: None, len: 0 } diff --git a/exercises/practice/simple-linked-list/Cargo.toml b/exercises/practice/simple-linked-list/Cargo.toml index 81a5a00d3..56df69ec3 100644 --- a/exercises/practice/simple-linked-list/Cargo.toml +++ b/exercises/practice/simple-linked-list/Cargo.toml @@ -1,6 +1,12 @@ [package] -edition = "2021" name = "simple_linked_list" 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/simple-linked-list/src/lib.rs b/exercises/practice/simple-linked-list/src/lib.rs index 8d7d5cc44..75a931155 100644 --- a/exercises/practice/simple-linked-list/src/lib.rs +++ b/exercises/practice/simple-linked-list/src/lib.rs @@ -1,5 +1,3 @@ -use std::iter::FromIterator; - pub struct SimpleLinkedList { // Delete this field // dummy is needed to avoid unused parameter error during compilation @@ -8,7 +6,7 @@ pub struct SimpleLinkedList { impl SimpleLinkedList { pub fn new() -> Self { - unimplemented!() + todo!() } // You may be wondering why it's necessary to have is_empty() @@ -17,50 +15,50 @@ impl SimpleLinkedList { // whereas is_empty() is almost always cheap. // (Also ask yourself whether len() is expensive for SimpleLinkedList) pub fn is_empty(&self) -> bool { - unimplemented!() + todo!() } pub fn len(&self) -> usize { - unimplemented!() + todo!() } pub fn push(&mut self, _element: T) { - unimplemented!() + todo!() } pub fn pop(&mut self) -> Option { - unimplemented!() + todo!() } pub fn peek(&self) -> Option<&T> { - unimplemented!() + todo!() } #[must_use] pub fn rev(self) -> SimpleLinkedList { - unimplemented!() + todo!() } } impl FromIterator for SimpleLinkedList { fn from_iter>(_iter: I) -> Self { - unimplemented!() + todo!() } } // In general, it would be preferable to implement IntoIterator for SimpleLinkedList // instead of implementing an explicit conversion to a vector. This is because, together, // FromIterator and IntoIterator enable conversion between arbitrary collections. -// Given that implementation, converting to a vector is trivial: -// -// let vec: Vec<_> = simple_linked_list.into_iter().collect(); // // The reason this exercise's API includes an explicit conversion to Vec instead // of IntoIterator is that implementing that interface is fairly complicated, and // demands more of the student than we expect at this point in the track. +// +// Please note that the "front" of the linked list should correspond to the "back" +// of the vector as far as the tests are concerned. impl From> for Vec { fn from(mut _linked_list: SimpleLinkedList) -> Vec { - unimplemented!() + todo!() } } diff --git a/exercises/practice/simple-linked-list/tests/simple-linked-list.rs b/exercises/practice/simple-linked-list/tests/simple_linked_list.rs similarity index 91% rename from exercises/practice/simple-linked-list/tests/simple-linked-list.rs rename to exercises/practice/simple-linked-list/tests/simple_linked_list.rs index a95a66665..896528666 100644 --- a/exercises/practice/simple-linked-list/tests/simple-linked-list.rs +++ b/exercises/practice/simple-linked-list/tests/simple_linked_list.rs @@ -1,14 +1,14 @@ use simple_linked_list::SimpleLinkedList; #[test] -fn test_new_list_is_empty() { +fn new_list_is_empty() { let list: SimpleLinkedList = SimpleLinkedList::new(); assert_eq!(list.len(), 0, "list's length must be 0"); } #[test] #[ignore] -fn test_push_increments_length() { +fn push_increments_length() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); list.push(1); assert_eq!(list.len(), 1, "list's length must be 1"); @@ -18,7 +18,7 @@ fn test_push_increments_length() { #[test] #[ignore] -fn test_pop_decrements_length() { +fn pop_decrements_length() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); list.push(1); list.push(2); @@ -30,7 +30,7 @@ fn test_pop_decrements_length() { #[test] #[ignore] -fn test_is_empty() { +fn is_empty() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); assert!(list.is_empty(), "List wasn't empty on creation"); for inserts in 0..100 { @@ -57,7 +57,7 @@ fn test_is_empty() { #[test] #[ignore] -fn test_pop_returns_head_element_and_removes_it() { +fn pop_returns_head_element_and_removes_it() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); list.push(1); list.push(2); @@ -68,7 +68,7 @@ fn test_pop_returns_head_element_and_removes_it() { #[test] #[ignore] -fn test_peek_returns_reference_to_head_element_but_does_not_remove_it() { +fn peek_returns_reference_to_head_element_but_does_not_remove_it() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); assert_eq!(list.peek(), None, "No element should be contained in list"); list.push(2); @@ -84,7 +84,7 @@ fn test_peek_returns_reference_to_head_element_but_does_not_remove_it() { #[test] #[ignore] -fn test_from_slice() { +fn from_slice() { let mut array = vec!["1", "2", "3", "4"]; let mut list: SimpleLinkedList<_> = array.drain(..).collect(); assert_eq!(list.pop(), Some("4")); @@ -95,7 +95,7 @@ fn test_from_slice() { #[test] #[ignore] -fn test_reverse() { +fn reverse() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); list.push(1); list.push(2); @@ -109,7 +109,7 @@ fn test_reverse() { #[test] #[ignore] -fn test_into_vector() { +fn into_vector() { let mut v = Vec::new(); let mut s = SimpleLinkedList::new(); for i in 1..4 { diff --git a/exercises/practice/space-age/.docs/instructions.append.md b/exercises/practice/space-age/.docs/instructions.append.md index 977d1b6b4..1443ddea2 100644 --- a/exercises/practice/space-age/.docs/instructions.append.md +++ b/exercises/practice/space-age/.docs/instructions.append.md @@ -1,17 +1,18 @@ -# Topics +# Instructions append + +## Topics Some Rust topics you may want to read about while solving this problem: -- Traits, both the From trait and implementing your own traits -- Default method implementations for traits -- Macros, the use of a macro could reduce boilerplate and increase readability - for this exercise. For instance, +- Traits, both the From trait and [implementing your own traits](https://doc.rust-lang.org/book/ch10-02-traits.html) +- [Default method implementations](https://doc.rust-lang.org/book/ch10-02-traits.html#default-implementations) for traits +- Macros, the use of a macro could reduce boilerplate and increase readability + for this exercise. For instance, [a macro can implement a trait for multiple types at once](https://stackoverflow.com/questions/39150216/implementing-a-trait-for-multiple-types-at-once), though it is fine to implement `years_during` in the Planet trait itself. A macro could - define both the structs and their implementations. Info to get started with macros can + define both the structs and their implementations. Info to get started with macros can be found at: - + - [The Macros chapter in The Rust Programming Language](https://doc.rust-lang.org/stable/book/ch19-06-macros.html) - [an older version of the Macros chapter with helpful detail](https://doc.rust-lang.org/1.30.0/book/first-edition/macros.html) - [Rust By Example](https://doc.rust-lang.org/stable/rust-by-example/macros.html) - diff --git a/exercises/practice/space-age/.docs/instructions.md b/exercises/practice/space-age/.docs/instructions.md index 19cca8bf9..f23b5e2c1 100644 --- a/exercises/practice/space-age/.docs/instructions.md +++ b/exercises/practice/space-age/.docs/instructions.md @@ -1,18 +1,28 @@ # Instructions -Given an age in seconds, calculate how old someone would be on: +Given an age in seconds, calculate how old someone would be on a planet in our Solar System. - - Mercury: orbital period 0.2408467 Earth years - - Venus: orbital period 0.61519726 Earth years - - Earth: orbital period 1.0 Earth years, 365.25 Earth days, or 31557600 seconds - - Mars: orbital period 1.8808158 Earth years - - Jupiter: orbital period 11.862615 Earth years - - Saturn: orbital period 29.447498 Earth years - - Uranus: orbital period 84.016846 Earth years - - Neptune: orbital period 164.79132 Earth years +One Earth year equals 365.25 Earth days, or 31,557,600 seconds. +If you were told someone was 1,000,000,000 seconds old, their age would be 31.69 Earth-years. -So if you were told someone were 1,000,000,000 seconds old, you should -be able to say that they're 31.69 Earth-years old. +For the other planets, you have to account for their orbital period in Earth Years: -If you're wondering why Pluto didn't make the cut, go watch [this -youtube video](http://www.youtube.com/watch?v=Z_2gbGXzFbs). +| Planet | Orbital period in Earth Years | +| ------- | ----------------------------- | +| Mercury | 0.2408467 | +| Venus | 0.61519726 | +| Earth | 1.0 | +| Mars | 1.8808158 | +| Jupiter | 11.862615 | +| Saturn | 29.447498 | +| Uranus | 84.016846 | +| Neptune | 164.79132 | + +~~~~exercism/note +The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). +The Gregorian calendar has, on average, 365.2425 days. +While not entirely accurate, 365.25 is the value used in this exercise. +See [Year on Wikipedia][year] for more ways to measure a year. + +[year]: https://en.wikipedia.org/wiki/Year#Summary +~~~~ diff --git a/exercises/practice/space-age/.docs/introduction.md b/exercises/practice/space-age/.docs/introduction.md new file mode 100644 index 000000000..014d78857 --- /dev/null +++ b/exercises/practice/space-age/.docs/introduction.md @@ -0,0 +1,20 @@ +# Introduction + +The year is 2525 and you've just embarked on a journey to visit all planets in the Solar System (Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus and Neptune). +The first stop is Mercury, where customs require you to fill out a form (bureaucracy is apparently _not_ Earth-specific). +As you hand over the form to the customs officer, they scrutinize it and frown. +"Do you _really_ expect me to believe you're just 50 years old? +You must be closer to 200 years old!" + +Amused, you wait for the customs officer to start laughing, but they appear to be dead serious. +You realize that you've entered your age in _Earth years_, but the officer expected it in _Mercury years_! +As Mercury's orbital period around the sun is significantly shorter than Earth, you're actually a lot older in Mercury years. +After some quick calculations, you're able to provide your age in Mercury Years. +The customs officer smiles, satisfied, and waves you through. +You make a mental note to pre-calculate your planet-specific age _before_ future customs checks, to avoid such mix-ups. + +~~~~exercism/note +If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. + +[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs +~~~~ diff --git a/exercises/practice/space-age/.gitignore b/exercises/practice/space-age/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/space-age/.gitignore +++ b/exercises/practice/space-age/.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/space-age/.meta/config.json b/exercises/practice/space-age/.meta/config.json index 93de819e2..193c33e3a 100644 --- a/exercises/practice/space-age/.meta/config.json +++ b/exercises/practice/space-age/.meta/config.json @@ -28,7 +28,7 @@ "Cargo.toml" ], "test": [ - "tests/space-age.rs" + "tests/space_age.rs" ], "example": [ ".meta/example.rs" @@ -36,5 +36,5 @@ }, "blurb": "Given an age in seconds, calculate how old someone is in terms of a given planet's solar years.", "source": "Partially inspired by Chapter 1 in Chris Pine's online Learn to Program tutorial.", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=01" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=01" } diff --git a/exercises/practice/space-age/.meta/test_template.tera b/exercises/practice/space-age/.meta/test_template.tera new file mode 100644 index 000000000..389c6dd1e --- /dev/null +++ b/exercises/practice/space-age/.meta/test_template.tera @@ -0,0 +1,20 @@ +use space_age::*; + +fn assert_in_delta(expected: f64, actual: f64) { + let diff: f64 = (expected - actual).abs(); + let delta: f64 = 0.01; + if diff > delta { + panic!("Your result of {actual} should be within {delta} of the expected result {expected}") + } +} +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let seconds = {{ test.input.seconds | fmt_num }}; + let duration = Duration::from(seconds); + let output = {{ test.input.planet }}::years_during(&duration); + let expected = {{ test.expected | json_encode() }}; + assert_in_delta(expected, output); +} +{% endfor -%} diff --git a/exercises/practice/space-age/.meta/tests.toml b/exercises/practice/space-age/.meta/tests.toml index be690e975..93f2ffb6f 100644 --- a/exercises/practice/space-age/.meta/tests.toml +++ b/exercises/practice/space-age/.meta/tests.toml @@ -1,3 +1,42 @@ -# 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. + +[84f609af-5a91-4d68-90a3-9e32d8a5cd34] +description = "age on Earth" + +[ca20c4e9-6054-458c-9312-79679ffab40b] +description = "age on Mercury" + +[502c6529-fd1b-41d3-8fab-65e03082b024] +description = "age on Venus" + +[9ceadf5e-a0d5-4388-9d40-2c459227ceb8] +description = "age on Mars" + +[42927dc3-fe5e-4f76-a5b5-f737fc19bcde] +description = "age on Jupiter" + +[8469b332-7837-4ada-b27c-00ee043ebcad] +description = "age on Saturn" + +[999354c1-76f8-4bb5-a672-f317b6436743] +description = "age on Uranus" + +[80096d30-a0d4-4449-903e-a381178355d8] +description = "age on Neptune" + +[57b96e2a-1178-40b7-b34d-f3c9c34e4bf4] +description = "invalid planet causes error" +include = false +comment = """ + Our implementation of this exercise defines the Planets at compile time. + Therefore, there cannot be any runtime errors about invalid planets. +""" diff --git a/exercises/practice/space-age/Cargo.toml b/exercises/practice/space-age/Cargo.toml index 9640244bd..bbe6044c7 100644 --- a/exercises/practice/space-age/Cargo.toml +++ b/exercises/practice/space-age/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "space-age" -version = "1.2.0" +name = "space_age" +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/space-age/src/lib.rs b/exercises/practice/space-age/src/lib.rs index 59d904771..fd180d47c 100644 --- a/exercises/practice/space-age/src/lib.rs +++ b/exercises/practice/space-age/src/lib.rs @@ -6,15 +6,13 @@ pub struct Duration; impl From for Duration { fn from(s: u64) -> Self { - unimplemented!("s, measured in seconds: {s}") + todo!("s, measured in seconds: {s}") } } pub trait Planet { fn years_during(d: &Duration) -> f64 { - unimplemented!( - "convert a duration ({d:?}) to the number of years on this planet for that duration" - ); + todo!("convert a duration ({d:?}) to the number of years on this planet for that duration"); } } diff --git a/exercises/practice/space-age/tests/space-age.rs b/exercises/practice/space-age/tests/space-age.rs deleted file mode 100644 index a36c42bf7..000000000 --- a/exercises/practice/space-age/tests/space-age.rs +++ /dev/null @@ -1,64 +0,0 @@ -use space_age::*; - -fn assert_in_delta(expected: f64, actual: f64) { - let diff: f64 = (expected - actual).abs(); - let delta: f64 = 0.01; - if diff > delta { - panic!("Your result of {actual} should be within {delta} of the expected result {expected}") - } -} - -#[test] -fn earth_age() { - let duration = Duration::from(1_000_000_000); - assert_in_delta(31.69, Earth::years_during(&duration)); -} - -#[test] -#[ignore] -fn mercury_age() { - let duration = Duration::from(2_134_835_688); - assert_in_delta(280.88, Mercury::years_during(&duration)); -} - -#[test] -#[ignore] -fn venus_age() { - let duration = Duration::from(189_839_836); - assert_in_delta(9.78, Venus::years_during(&duration)); -} - -#[test] -#[ignore] -fn mars_age() { - let duration = Duration::from(2_129_871_239); - assert_in_delta(35.88, Mars::years_during(&duration)); -} - -#[test] -#[ignore] -fn jupiter_age() { - let duration = Duration::from(901_876_382); - assert_in_delta(2.41, Jupiter::years_during(&duration)); -} - -#[test] -#[ignore] -fn saturn_age() { - let duration = Duration::from(2_000_000_000); - assert_in_delta(2.15, Saturn::years_during(&duration)); -} - -#[test] -#[ignore] -fn uranus_age() { - let duration = Duration::from(1_210_123_456); - assert_in_delta(0.46, Uranus::years_during(&duration)); -} - -#[test] -#[ignore] -fn neptune_age() { - let duration = Duration::from(1_821_023_456); - assert_in_delta(0.35, Neptune::years_during(&duration)); -} diff --git a/exercises/practice/space-age/tests/space_age.rs b/exercises/practice/space-age/tests/space_age.rs new file mode 100644 index 000000000..995feedb1 --- /dev/null +++ b/exercises/practice/space-age/tests/space_age.rs @@ -0,0 +1,88 @@ +use space_age::*; + +fn assert_in_delta(expected: f64, actual: f64) { + let diff: f64 = (expected - actual).abs(); + let delta: f64 = 0.01; + if diff > delta { + panic!("Your result of {actual} should be within {delta} of the expected result {expected}") + } +} + +#[test] +fn age_on_earth() { + let seconds = 1_000_000_000; + let duration = Duration::from(seconds); + let output = Earth::years_during(&duration); + let expected = 31.69; + assert_in_delta(expected, output); +} + +#[test] +#[ignore] +fn age_on_mercury() { + let seconds = 2_134_835_688; + let duration = Duration::from(seconds); + let output = Mercury::years_during(&duration); + let expected = 280.88; + assert_in_delta(expected, output); +} + +#[test] +#[ignore] +fn age_on_venus() { + let seconds = 189_839_836; + let duration = Duration::from(seconds); + let output = Venus::years_during(&duration); + let expected = 9.78; + assert_in_delta(expected, output); +} + +#[test] +#[ignore] +fn age_on_mars() { + let seconds = 2_129_871_239; + let duration = Duration::from(seconds); + let output = Mars::years_during(&duration); + let expected = 35.88; + assert_in_delta(expected, output); +} + +#[test] +#[ignore] +fn age_on_jupiter() { + let seconds = 901_876_382; + let duration = Duration::from(seconds); + let output = Jupiter::years_during(&duration); + let expected = 2.41; + assert_in_delta(expected, output); +} + +#[test] +#[ignore] +fn age_on_saturn() { + let seconds = 2_000_000_000; + let duration = Duration::from(seconds); + let output = Saturn::years_during(&duration); + let expected = 2.15; + assert_in_delta(expected, output); +} + +#[test] +#[ignore] +fn age_on_uranus() { + let seconds = 1_210_123_456; + let duration = Duration::from(seconds); + let output = Uranus::years_during(&duration); + let expected = 0.46; + assert_in_delta(expected, output); +} + +#[test] +#[ignore] +fn age_on_neptune() { + let seconds = 1_821_023_456; + let duration = Duration::from(seconds); + let output = Neptune::years_during(&duration); + let expected = 0.35; + assert_in_delta(expected, output); +} diff --git a/exercises/practice/spiral-matrix/.docs/instructions.md b/exercises/practice/spiral-matrix/.docs/instructions.md index af412dd83..01e8a77f8 100644 --- a/exercises/practice/spiral-matrix/.docs/instructions.md +++ b/exercises/practice/spiral-matrix/.docs/instructions.md @@ -1,12 +1,11 @@ # Instructions -Given the size, return a square matrix of numbers in spiral order. +Your task is to return a square matrix of a given size. -The matrix should be filled with natural numbers, starting from 1 -in the top-left corner, increasing in an inward, clockwise spiral order, -like these examples: +The matrix should be filled with natural numbers, starting from 1 in the top-left corner, increasing in an inward, clockwise spiral order, like these examples: ## Examples + ### Spiral matrix of size 3 ```text diff --git a/exercises/practice/spiral-matrix/.docs/introduction.md b/exercises/practice/spiral-matrix/.docs/introduction.md new file mode 100644 index 000000000..25c7eb595 --- /dev/null +++ b/exercises/practice/spiral-matrix/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +In a small village near an ancient forest, there was a legend of a hidden treasure buried deep within the woods. +Despite numerous attempts, no one had ever succeeded in finding it. +This was about to change, however, thanks to a young explorer named Elara. +She had discovered an old document containing instructions on how to locate the treasure. +Using these instructions, Elara was able to draw a map that revealed the path to the treasure. + +To her surprise, the path followed a peculiar clockwise spiral. +It was no wonder no one had been able to find the treasure before! +With the map in hand, Elara embarks on her journey to uncover the hidden treasure. diff --git a/exercises/practice/spiral-matrix/.gitignore b/exercises/practice/spiral-matrix/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/spiral-matrix/.gitignore +++ b/exercises/practice/spiral-matrix/.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/spiral-matrix/.meta/config.json b/exercises/practice/spiral-matrix/.meta/config.json index 99b94dacf..dd4ce7a40 100644 --- a/exercises/practice/spiral-matrix/.meta/config.json +++ b/exercises/practice/spiral-matrix/.meta/config.json @@ -20,13 +20,13 @@ "Cargo.toml" ], "test": [ - "tests/spiral-matrix.rs" + "tests/spiral_matrix.rs" ], "example": [ ".meta/example.rs" ] }, - "blurb": " Given the size, return a square matrix of numbers in spiral order.", + "blurb": "Given the size, return a square matrix of numbers in spiral order.", "source": "Reddit r/dailyprogrammer challenge #320 [Easy] Spiral Ascension.", - "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" + "source_url": "https://web.archive.org/web/20230607064729/https://old.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" } diff --git a/exercises/practice/spiral-matrix/.meta/test_template.tera b/exercises/practice/spiral-matrix/.meta/test_template.tera new file mode 100644 index 000000000..7db053341 --- /dev/null +++ b/exercises/practice/spiral-matrix/.meta/test_template.tera @@ -0,0 +1,16 @@ +use spiral_matrix::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.size | json_encode() }}; + let output = spiral_matrix(input); + let expected: [[u32; {{ test.input.size }}]; {{ test.input.size }}] = [ + {% for i in test.expected %} + {{ i | json_encode }}, + {% endfor %} + ]; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/spiral-matrix/.meta/tests.toml b/exercises/practice/spiral-matrix/.meta/tests.toml index cb988949d..9ac5bacaa 100644 --- a/exercises/practice/spiral-matrix/.meta/tests.toml +++ b/exercises/practice/spiral-matrix/.meta/tests.toml @@ -1,6 +1,28 @@ -# 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. [8f584201-b446-4bc9-b132-811c8edd9040] description = "empty spiral" + +[e40ae5f3-e2c9-4639-8116-8a119d632ab2] +description = "trivial spiral" + +[cf05e42d-eb78-4098-a36e-cdaf0991bc48] +description = "spiral of size 2" + +[1c475667-c896-4c23-82e2-e033929de939] +description = "spiral of size 3" + +[05ccbc48-d891-44f5-9137-f4ce462a759d] +description = "spiral of size 4" + +[f4d2165b-1738-4e0c-bed0-c459045ae50d] +description = "spiral of size 5" diff --git a/exercises/practice/spiral-matrix/Cargo.toml b/exercises/practice/spiral-matrix/Cargo.toml index f86f78135..49fc6c78a 100644 --- a/exercises/practice/spiral-matrix/Cargo.toml +++ b/exercises/practice/spiral-matrix/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "spiral-matrix" -version = "1.1.0" +name = "spiral_matrix" +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/spiral-matrix/src/lib.rs b/exercises/practice/spiral-matrix/src/lib.rs index 79037e487..16d2d2ccd 100644 --- a/exercises/practice/spiral-matrix/src/lib.rs +++ b/exercises/practice/spiral-matrix/src/lib.rs @@ -1,3 +1,3 @@ pub fn spiral_matrix(size: u32) -> Vec> { - unimplemented!("Function that returns the spiral matrix of square size {size}"); + todo!("Function that returns the spiral matrix of square size {size}"); } diff --git a/exercises/practice/spiral-matrix/tests/spiral-matrix.rs b/exercises/practice/spiral-matrix/tests/spiral-matrix.rs deleted file mode 100644 index 0c957b9b5..000000000 --- a/exercises/practice/spiral-matrix/tests/spiral-matrix.rs +++ /dev/null @@ -1,55 +0,0 @@ -use spiral_matrix::*; - -#[test] -fn empty_spiral() { - let expected: Vec> = Vec::new(); - assert_eq!(spiral_matrix(0), expected); -} - -#[test] -#[ignore] -fn size_one_spiral() { - let expected: Vec> = vec![vec![1]]; - assert_eq!(spiral_matrix(1), expected); -} -#[test] -#[ignore] -fn size_two_spiral() { - let expected: Vec> = vec![vec![1, 2], vec![4, 3]]; - assert_eq!(spiral_matrix(2), expected); -} - -#[test] -#[ignore] -fn size_three_spiral() { - #[rustfmt::skip] - let expected: Vec> = vec![ - vec![1, 2, 3], - vec![8, 9, 4], - vec![7, 6, 5], - ]; - assert_eq!(spiral_matrix(3), expected); -} -#[test] -#[ignore] -fn size_four_spiral() { - let expected: Vec> = vec![ - vec![1, 2, 3, 4], - vec![12, 13, 14, 5], - vec![11, 16, 15, 6], - vec![10, 9, 8, 7], - ]; - assert_eq!(spiral_matrix(4), expected); -} -#[test] -#[ignore] -fn size_five_spiral() { - let expected: Vec> = vec![ - vec![1, 2, 3, 4, 5], - vec![16, 17, 18, 19, 6], - vec![15, 24, 25, 20, 7], - vec![14, 23, 22, 21, 8], - vec![13, 12, 11, 10, 9], - ]; - assert_eq!(spiral_matrix(5), expected); -} diff --git a/exercises/practice/spiral-matrix/tests/spiral_matrix.rs b/exercises/practice/spiral-matrix/tests/spiral_matrix.rs new file mode 100644 index 000000000..ef3493958 --- /dev/null +++ b/exercises/practice/spiral-matrix/tests/spiral_matrix.rs @@ -0,0 +1,65 @@ +use spiral_matrix::*; + +#[test] +fn empty_spiral() { + let input = 0; + let output = spiral_matrix(input); + let expected: [[u32; 0]; 0] = []; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn trivial_spiral() { + let input = 1; + let output = spiral_matrix(input); + let expected: [[u32; 1]; 1] = [[1]]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn spiral_of_size_2() { + let input = 2; + let output = spiral_matrix(input); + let expected: [[u32; 2]; 2] = [[1, 2], [4, 3]]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn spiral_of_size_3() { + let input = 3; + let output = spiral_matrix(input); + let expected: [[u32; 3]; 3] = [[1, 2, 3], [8, 9, 4], [7, 6, 5]]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn spiral_of_size_4() { + let input = 4; + let output = spiral_matrix(input); + let expected: [[u32; 4]; 4] = [ + [1, 2, 3, 4], + [12, 13, 14, 5], + [11, 16, 15, 6], + [10, 9, 8, 7], + ]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn spiral_of_size_5() { + let input = 5; + let output = spiral_matrix(input); + let expected: [[u32; 5]; 5] = [ + [1, 2, 3, 4, 5], + [16, 17, 18, 19, 6], + [15, 24, 25, 20, 7], + [14, 23, 22, 21, 8], + [13, 12, 11, 10, 9], + ]; + assert_eq!(output, expected); +} diff --git a/exercises/practice/sublist/.docs/instructions.md b/exercises/practice/sublist/.docs/instructions.md index 45c3b9648..8228edc6c 100644 --- a/exercises/practice/sublist/.docs/instructions.md +++ b/exercises/practice/sublist/.docs/instructions.md @@ -1,18 +1,25 @@ # Instructions -Given two lists determine if the first list is contained within the second -list, if the second list is contained within the first list, if both lists are -contained within each other or if none of these are true. +Given any two lists `A` and `B`, determine if: -Specifically, a list A is a sublist of list B if by dropping 0 or more elements -from the front of B and 0 or more elements from the back of B you get a list -that's completely equal to A. +- List `A` is equal to list `B`; or +- List `A` contains list `B` (`A` is a superlist of `B`); or +- List `A` is contained by list `B` (`A` is a sublist of `B`); or +- None of the above is true, thus lists `A` and `B` are unequal + +Specifically, list `A` is equal to list `B` if both lists have the same values in the same order. +List `A` is a superlist of `B` if `A` contains a contiguous sub-sequence of values equal to `B`. +List `A` is a sublist of `B` if `B` contains a contiguous sub-sequence of values equal to `A`. Examples: - * A = [1, 2, 3], B = [1, 2, 3, 4, 5], A is a sublist of B - * A = [3, 4, 5], B = [1, 2, 3, 4, 5], A is a sublist of B - * A = [3, 4], B = [1, 2, 3, 4, 5], A is a sublist of B - * A = [1, 2, 3], B = [1, 2, 3], A is equal to B - * A = [1, 2, 3, 4, 5], B = [2, 3, 4], A is a superlist of B - * A = [1, 2, 4], B = [1, 2, 3, 4, 5], A is not a superlist of, sublist of or equal to B +- If `A = []` and `B = []` (both lists are empty), then `A` and `B` are equal +- If `A = [1, 2, 3]` and `B = []`, then `A` is a superlist of `B` +- If `A = []` and `B = [1, 2, 3]`, then `A` is a sublist of `B` +- If `A = [1, 2, 3]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [3, 4, 5]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [3, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [1, 2, 3]` and `B = [1, 2, 3]`, then `A` and `B` are equal +- If `A = [1, 2, 3, 4, 5]` and `B = [2, 3, 4]`, then `A` is a superlist of `B` +- If `A = [1, 2, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` and `B` are unequal +- If `A = [1, 2, 3]` and `B = [1, 3, 2]`, then `A` and `B` are unequal diff --git a/exercises/practice/sublist/.gitignore b/exercises/practice/sublist/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/sublist/.gitignore +++ b/exercises/practice/sublist/.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/sublist/.meta/test_template.tera b/exercises/practice/sublist/.meta/test_template.tera new file mode 100644 index 000000000..b4f3540f5 --- /dev/null +++ b/exercises/practice/sublist/.meta/test_template.tera @@ -0,0 +1,21 @@ +use sublist::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let list_one: &[i32] = &{{ test.input.listOne | json_encode() }}; + let list_two: &[i32] = &{{ test.input.listTwo | json_encode() }}; + let output = sublist(list_one, list_two); + let expected = {% if test.expected == "equal" -%} + Comparison::Equal + {%- elif test.expected == "sublist" -%} + Comparison::Sublist + {%- elif test.expected == "superlist" -%} + Comparison::Superlist + {%- else -%} + Comparison::Unequal + {%- endif %}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/sublist/.meta/tests.toml b/exercises/practice/sublist/.meta/tests.toml index de63ebcc8..de5020a9d 100644 --- a/exercises/practice/sublist/.meta/tests.toml +++ b/exercises/practice/sublist/.meta/tests.toml @@ -1,9 +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. + +[97319c93-ebc5-47ab-a022-02a1980e1d29] +description = "empty lists" + +[de27dbd4-df52-46fe-a336-30be58457382] +description = "empty list within non empty list" + +[5487cfd1-bc7d-429f-ac6f-1177b857d4fb] +description = "non empty list contains empty list" + +[1f390b47-f6b2-4a93-bc23-858ba5dda9a6] +description = "list equals itself" + +[7ed2bfb2-922b-4363-ae75-f3a05e8274f5] +description = "different lists" + +[3b8a2568-6144-4f06-b0a1-9d266b365341] +description = "false start" + +[dc39ed58-6311-4814-be30-05a64bc8d9b1] +description = "consecutive" + +[d1270dab-a1ce-41aa-b29d-b3257241ac26] +description = "sublist at start" [81f3d3f7-4f25-4ada-bcdc-897c403de1b6] description = "sublist in middle" [43bcae1e-a9cf-470e-923e-0946e04d8fdd] description = "sublist at end" + +[76cf99ed-0ff0-4b00-94af-4dfb43fe5caa] +description = "at start of superlist" + +[b83989ec-8bdf-4655-95aa-9f38f3e357fd] +description = "in middle of superlist" + +[26f9f7c3-6cf6-4610-984a-662f71f8689b] +description = "at end of superlist" + +[0a6db763-3588-416a-8f47-76b1cedde31e] +description = "first list missing element from second list" + +[83ffe6d8-a445-4a3c-8795-1e51a95e65c3] +description = "second list missing element from first list" + +[7bc76cb8-5003-49ca-bc47-cdfbe6c2bb89] +description = "first list missing additional digits from second list" + +[0d7ee7c1-0347-45c8-9ef5-b88db152b30b] +description = "order matters to a list" + +[5f47ce86-944e-40f9-9f31-6368aad70aa6] +description = "same digits but different numbers" diff --git a/exercises/practice/sublist/Cargo.toml b/exercises/practice/sublist/Cargo.toml index 6493981cf..c900a2e79 100644 --- a/exercises/practice/sublist/Cargo.toml +++ b/exercises/practice/sublist/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "sublist" -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/sublist/src/lib.rs b/exercises/practice/sublist/src/lib.rs index 95205e5aa..ac50a59cf 100644 --- a/exercises/practice/sublist/src/lib.rs +++ b/exercises/practice/sublist/src/lib.rs @@ -6,6 +6,8 @@ pub enum Comparison { Unequal, } -pub fn sublist(_first_list: &[T], _second_list: &[T]) -> Comparison { - unimplemented!("Determine if the first list is equal to, sublist of, superlist of or unequal to the second list."); +pub fn sublist(first_list: &[i32], second_list: &[i32]) -> Comparison { + todo!( + "Determine if the {first_list:?} is equal to, sublist of, superlist of or unequal to {second_list:?}." + ); } diff --git a/exercises/practice/sublist/tests/sublist.rs b/exercises/practice/sublist/tests/sublist.rs index 9653ab157..7b9327f41 100644 --- a/exercises/practice/sublist/tests/sublist.rs +++ b/exercises/practice/sublist/tests/sublist.rs @@ -1,127 +1,180 @@ -use sublist::{sublist, Comparison}; +use sublist::*; #[test] -fn empty_equals_empty() { - let v: &[u32] = &[]; - - assert_eq!(Comparison::Equal, sublist(v, v)); +fn empty_lists() { + let list_one: &[i32] = &[]; + let list_two: &[i32] = &[]; + let output = sublist(list_one, list_two); + let expected = Comparison::Equal; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_empty_is_a_sublist_of_anything() { - assert_eq!(Comparison::Sublist, sublist(&[], &['a', 's', 'd', 'f'])); +fn empty_list_within_non_empty_list() { + let list_one: &[i32] = &[]; + let list_two: &[i32] = &[1, 2, 3]; + let output = sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_anything_is_a_superlist_of_empty() { - assert_eq!(Comparison::Superlist, sublist(&['a', 's', 'd', 'f'], &[])); +fn non_empty_list_contains_empty_list() { + let list_one: &[i32] = &[1, 2, 3]; + let list_two: &[i32] = &[]; + let output = sublist(list_one, list_two); + let expected = Comparison::Superlist; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_1_is_not_2() { - assert_eq!(Comparison::Unequal, sublist(&[1], &[2])); +fn list_equals_itself() { + let list_one: &[i32] = &[1, 2, 3]; + let list_two: &[i32] = &[1, 2, 3]; + let output = sublist(list_one, list_two); + let expected = Comparison::Equal; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_compare_larger_equal_lists() { - use std::iter::repeat; - - let v: Vec = repeat('x').take(1000).collect(); - - assert_eq!(Comparison::Equal, sublist(&v, &v)); +fn different_lists() { + let list_one: &[i32] = &[1, 2, 3]; + let list_two: &[i32] = &[2, 3, 4]; + let output = sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_sublist_at_start() { - assert_eq!(Comparison::Sublist, sublist(&[1, 2, 3], &[1, 2, 3, 4, 5])); +fn false_start() { + let list_one: &[i32] = &[1, 2, 5]; + let list_two: &[i32] = &[0, 1, 2, 3, 1, 2, 5, 6]; + let output = sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn sublist_in_middle() { - assert_eq!(Comparison::Sublist, sublist(&[4, 3, 2], &[5, 4, 3, 2, 1])); +fn consecutive() { + let list_one: &[i32] = &[1, 1, 2]; + let list_two: &[i32] = &[0, 1, 1, 1, 2, 1, 2]; + let output = sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn sublist_at_end() { - assert_eq!(Comparison::Sublist, sublist(&[3, 4, 5], &[1, 2, 3, 4, 5])); +fn sublist_at_start() { + let list_one: &[i32] = &[0, 1, 2]; + let list_two: &[i32] = &[0, 1, 2, 3, 4, 5]; + let output = sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn partially_matching_sublist_at_start() { - assert_eq!(Comparison::Sublist, sublist(&[1, 1, 2], &[1, 1, 1, 2])); +fn sublist_in_middle() { + let list_one: &[i32] = &[2, 3, 4]; + let list_two: &[i32] = &[0, 1, 2, 3, 4, 5]; + let output = sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn sublist_early_in_huge_list() { - let huge: Vec = (1..1_000_000).collect(); - - assert_eq!(Comparison::Sublist, sublist(&[3, 4, 5], &huge)); +fn sublist_at_end() { + let list_one: &[i32] = &[3, 4, 5]; + let list_two: &[i32] = &[0, 1, 2, 3, 4, 5]; + let output = sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn huge_sublist_not_in_huge_list() { - let v1: Vec = (10..1_000_001).collect(); - let v2: Vec = (1..1_000_000).collect(); - - assert_eq!(Comparison::Unequal, sublist(&v1, &v2)); +fn at_start_of_superlist() { + let list_one: &[i32] = &[0, 1, 2, 3, 4, 5]; + let list_two: &[i32] = &[0, 1, 2]; + let output = sublist(list_one, list_two); + let expected = Comparison::Superlist; + assert_eq!(output, expected); } #[test] #[ignore] -fn superlist_at_start() { - assert_eq!(Comparison::Superlist, sublist(&[1, 2, 3, 4, 5], &[1, 2, 3])); +fn in_middle_of_superlist() { + let list_one: &[i32] = &[0, 1, 2, 3, 4, 5]; + let list_two: &[i32] = &[2, 3]; + let output = sublist(list_one, list_two); + let expected = Comparison::Superlist; + assert_eq!(output, expected); } #[test] #[ignore] -fn superlist_in_middle() { - assert_eq!(Comparison::Superlist, sublist(&[5, 4, 3, 2, 1], &[4, 3, 2])); +fn at_end_of_superlist() { + let list_one: &[i32] = &[0, 1, 2, 3, 4, 5]; + let list_two: &[i32] = &[3, 4, 5]; + let output = sublist(list_one, list_two); + let expected = Comparison::Superlist; + assert_eq!(output, expected); } #[test] #[ignore] -fn superlist_at_end() { - assert_eq!(Comparison::Superlist, sublist(&[1, 2, 3, 4, 5], &[3, 4, 5])); +fn first_list_missing_element_from_second_list() { + let list_one: &[i32] = &[1, 3]; + let list_two: &[i32] = &[1, 2, 3]; + let output = sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } #[test] #[ignore] fn second_list_missing_element_from_first_list() { - assert_eq!(Comparison::Unequal, sublist(&[1, 2, 3], &[1, 3])); + let list_one: &[i32] = &[1, 2, 3]; + let list_two: &[i32] = &[1, 3]; + let output = sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } #[test] #[ignore] -fn superlist_early_in_huge_list() { - let huge: Vec = (1..1_000_000).collect(); - - assert_eq!(Comparison::Superlist, sublist(&huge, &[3, 4, 5])); +fn first_list_missing_additional_digits_from_second_list() { + let list_one: &[i32] = &[1, 2]; + let list_two: &[i32] = &[1, 22]; + let output = sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } #[test] #[ignore] -fn recurring_values_sublist() { - assert_eq!( - Comparison::Sublist, - sublist(&[1, 2, 1, 2, 3], &[1, 2, 3, 1, 2, 1, 2, 3, 2, 1]) - ); +fn order_matters_to_a_list() { + let list_one: &[i32] = &[1, 2, 3]; + let list_two: &[i32] = &[3, 2, 1]; + let output = sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } #[test] #[ignore] -fn recurring_values_unequal() { - assert_eq!( - Comparison::Unequal, - sublist(&[1, 2, 1, 2, 3], &[1, 2, 3, 1, 2, 3, 2, 3, 2, 1]) - ); +fn same_digits_but_different_numbers() { + let list_one: &[i32] = &[1, 0, 1]; + let list_two: &[i32] = &[10, 1]; + let output = sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } diff --git a/exercises/practice/sum-of-multiples/.approaches/config.json b/exercises/practice/sum-of-multiples/.approaches/config.json new file mode 100644 index 000000000..4812f03b7 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/config.json @@ -0,0 +1,27 @@ +{ + "introduction": { + "authors": [ + "clechasseur" + ] + }, + "approaches": [ + { + "uuid": "c0e599bf-a0b4-4eb3-af73-ab6c5b04dec8", + "slug": "from-factors", + "title": "Calculating sum from factors", + "blurb": "Calculate the sum by scanning the factors and computing their multiples.", + "authors": [ + "clechasseur" + ] + }, + { + "uuid": "305246ad-c36a-48ae-8047-cd00a4e7a3e4", + "slug": "from-range", + "title": "Calculating sum by iterating the whole range", + "blurb": "Calculate the sum by scanning the whole range and identifying any multiple via factors.", + "authors": [ + "clechasseur" + ] + } + ] + } diff --git a/exercises/practice/sum-of-multiples/.approaches/from-factors/content.md b/exercises/practice/sum-of-multiples/.approaches/from-factors/content.md new file mode 100644 index 000000000..0300b5fa0 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/from-factors/content.md @@ -0,0 +1,44 @@ +# Calculating sum from factors + +```rust +pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { + factors + .iter() + .filter(|&&factor| factor != 0) + .flat_map(|&factor| (factor..limit).step_by(factor as usize)) + .collect::>() + .iter() + .sum() +} +``` + +This approach implements the exact steps outlined in the exercise description: + +1. For each non-zero factor, find all multiples of that factor that are less than the `limit` +2. Collect all multiples in a [`HashSet`][hash_set] +3. Calculate the sum of all unique multiples + +In order to compute the list of multiples for a factor, we create a [`Range`][range] from the factor (inclusive) to the `limit` (exclusive), then use [`step_by`][iterator-step_by] with the same factor. + +To combine the multiples of all factors, we iterate the list of factors and use [`flat_map`][iterator-flat_map] on each factor's multiples. +[`flat_map`][iterator-flat_map] is a combination of [`map`][iterator-map] and [`flatten`][iterator-flatten]; it maps each factor into its multiples, then flattens them all in a single sequence. + +Since we need to have unique multiples to compute the proper sum, we [`collect`][iterator-collect] the multiples into a [`HashSet`][hash_set], which only keeps one of each of its entries, thus removing duplicates. +[`collect`][iterator-collect] is a powerful function that can collect the data in a sequence and store it in any kind of collection - however, because of this, the compiler in this case is not able to infer the type of collection you want as the output. +To solve this problem, we specify the type `HashSet<_>` explicitly. + +Finally, calculating the sum of the remaining unique multiples in the set is easy: we can simply get an [Iterator][iterator] and call [`sum`][iterator-sum]. + +[vec]: https://doc.rust-lang.org/std/vec/struct.Vec.html +[hash_set]: https://doc.rust-lang.org/std/collections/struct.HashSet.html +[range]: https://doc.rust-lang.org/std/ops/struct.Range.html +[iterator]: https://doc.rust-lang.org/std/iter/trait.Iterator.html +[iterator-step_by]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.step_by +[iterator-flat_map]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flat_map +[iterator-map]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map +[iterator-flatten]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flatten +[iterator-collect]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect +[slice-sort]: https://doc.rust-lang.org/std/primitive.slice.html#method.sort +[vec-dedup]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.dedup +[iterator-sum]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.sum +[slice-sort_unstable]: https://doc.rust-lang.org/std/primitive.slice.html#method.sort_unstable diff --git a/exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt b/exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt new file mode 100644 index 000000000..105d1bd45 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt @@ -0,0 +1,8 @@ +pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { + factors + .iter() + .filter(|&&factor| factor != 0) + .flat_map(|&factor| (factor..limit).step_by(factor as usize)) + .collect::>() + .iter() + .sum() \ No newline at end of file diff --git a/exercises/practice/sum-of-multiples/.approaches/from-range/content.md b/exercises/practice/sum-of-multiples/.approaches/from-range/content.md new file mode 100644 index 000000000..24afd0a40 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/from-range/content.md @@ -0,0 +1,25 @@ +# Calculating sum by iterating the whole range + +```rust +pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { + (1..limit) + .filter(|&n| factors.iter().any(|&factor| factor != 0 && n % factor == 0)) + .sum() +} +``` + +Instead of implementing the steps in the exercise description, this approach uses another angle: + +1. Iterate all numbers between 1 (inclusive) and `limit` (exclusive) +2. Keep only numbers which have at least one factor in `factors` (automatically avoiding any duplicates) +3. Calculate the sum of all numbers kept + +After creating our range, we use [`filter`][iterator-filter] to keep only matching multiples. +To detect the multiples, we scan the `factors` and use [`any`][iterator-any] to check if at least one is a factor of the number we're checking. +([`any`][iterator-any] is short-circuiting: it stops as soon as it finds one compatible factor.) + +Finally, once we have the multiples, we can compute the sum easily using [`sum`][iterator-sum]. + +[iterator-filter]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter +[iterator-any]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any +[iterator-sum]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.sum diff --git a/exercises/practice/sum-of-multiples/.approaches/from-range/snippet.txt b/exercises/practice/sum-of-multiples/.approaches/from-range/snippet.txt new file mode 100644 index 000000000..239db6b23 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/from-range/snippet.txt @@ -0,0 +1,5 @@ +pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { + (1..limit) + .filter(|&n| factors.iter().any(|&factor| factor != 0 && n % factor == 0)) + .sum() +} \ No newline at end of file diff --git a/exercises/practice/sum-of-multiples/.approaches/introduction.md b/exercises/practice/sum-of-multiples/.approaches/introduction.md new file mode 100644 index 000000000..dd5bf42d8 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/introduction.md @@ -0,0 +1,54 @@ +# Introduction + +There are a couple of different approaches available to solve Sum of Multiples. +One is to follow the algorithm [outlined in the exercise description][approach-from-factors], +but there are other ways, including [scanning the entire range][approach-from-range]. + +## General guidance + +The key to solving Sum of Multiples is to find the unique multiples of all provided factors. +To determine if `f` is a factor of a given number `n`, we can use the [remainder operator][rem]. +It is also possible to find the multiples by simple addition, starting from the factor. + +## Approach: Calculating sum from factors + +```rust +pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { + factors + .iter() + .filter(|&&factor| factor != 0) + .flat_map(|&factor| (factor..limit).step_by(factor as usize)) + .collect::>() + .iter() + .sum() +} +``` + +For more information, check the [Sum from factors approach][approach-from-factors]. + +## Approach: Calculating sum by iterating the whole range + +```rust +pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { + (1..limit) + .filter(|&n| factors.iter().any(|&factor| factor != 0 && n % factor == 0)) + .sum() +} +``` + +For more information, check the [Sum by iterating the whole range approach][approach-from-range]. + +## Which approach to use? + +- Computing the sum from factors can be efficient if we have a small number of `factors` and/or if they are large compared to the `limit`, because this will result in a small number of hashes to compute "in vain". + However, as the number of multiples grows, this approach can result in a lot of effort updating the `HashMap` to eliminate duplicates. +- Computing the sum by iterating the whole range can be efficient if we have a small range (low `limit`) and a comparatively large amount of `factors`. + Additionally, this approach has the advantage of having stable complexity that is only dependent on the limit and the number of factors, since there is no deduplication involved. + It also avoids any additional memory allocation. + +Without proper benchmarks, the second approach may be preferred since it offers a more stable level of complexity (e.g. its performances varies less when the size of the input changes). +However, if you have some knowledge of the size and shape of the input, then benchmarking might reveal that one approach is better than the other for your specific use case. + +[approach-from-factors]: https://exercism.org/tracks/rust/exercises/sum-of-multiples/approaches/from-factors +[approach-from-range]: https://exercism.org/tracks/rust/exercises/sum-of-multiples/approaches/from-range +[rem]: https://doc.rust-lang.org/core/ops/trait.Rem.html diff --git a/exercises/practice/sum-of-multiples/.docs/instructions.md b/exercises/practice/sum-of-multiples/.docs/instructions.md index bb512396a..d69f890e9 100644 --- a/exercises/practice/sum-of-multiples/.docs/instructions.md +++ b/exercises/practice/sum-of-multiples/.docs/instructions.md @@ -1,9 +1,27 @@ # Instructions -Given a number, find the sum of all the unique multiples of particular numbers up to -but not including that number. +Your task is to write the code that calculates the energy points that get awarded to players when they complete a level. -If we list all the natural numbers below 20 that are multiples of 3 or 5, -we get 3, 5, 6, 9, 10, 12, 15, and 18. +The points awarded depend on two things: -The sum of these multiples is 78. +- The level (a number) that the player completed. +- The base value of each magical item collected by the player during that level. + +The energy points are awarded according to the following rules: + +1. For each magical item, take the base value and find all the multiples of that value that are less than the level number. +2. Combine the sets of numbers. +3. Remove any duplicates. +4. Calculate the sum of all the numbers that are left. + +Let's look at an example: + +**The player completed level 20 and found two magical items with base values of 3 and 5.** + +To calculate the energy points earned by the player, we need to find all the unique multiples of these base values that are less than level 20. + +- Multiples of 3 less than 20: `{3, 6, 9, 12, 15, 18}` +- Multiples of 5 less than 20: `{5, 10, 15}` +- Combine the sets and remove duplicates: `{3, 5, 6, 9, 10, 12, 15, 18}` +- Sum the unique multiples: `3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 = 78` +- Therefore, the player earns **78** energy points for completing level 20 and finding the two magical items with base values of 3 and 5. diff --git a/exercises/practice/sum-of-multiples/.docs/introduction.md b/exercises/practice/sum-of-multiples/.docs/introduction.md new file mode 100644 index 000000000..69cabeed5 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +You work for a company that makes an online, fantasy-survival game. + +When a player finishes a level, they are awarded energy points. +The amount of energy awarded depends on which magical items the player found while exploring that level. diff --git a/exercises/practice/sum-of-multiples/.gitignore b/exercises/practice/sum-of-multiples/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/sum-of-multiples/.gitignore +++ b/exercises/practice/sum-of-multiples/.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/sum-of-multiples/.meta/config.json b/exercises/practice/sum-of-multiples/.meta/config.json index e020b0c58..c5c6c9ad6 100644 --- a/exercises/practice/sum-of-multiples/.meta/config.json +++ b/exercises/practice/sum-of-multiples/.meta/config.json @@ -27,7 +27,7 @@ "Cargo.toml" ], "test": [ - "tests/sum-of-multiples.rs" + "tests/sum_of_multiples.rs" ], "example": [ ".meta/example.rs" @@ -35,5 +35,5 @@ }, "blurb": "Given a number, find the sum of all the multiples of particular numbers up to but not including that number.", "source": "A variation on Problem 1 at Project Euler", - "source_url": "http://projecteuler.net/problem=1" + "source_url": "https://projecteuler.net/problem=1" } diff --git a/exercises/practice/sum-of-multiples/.meta/test_template.tera b/exercises/practice/sum-of-multiples/.meta/test_template.tera new file mode 100644 index 000000000..50776e114 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.meta/test_template.tera @@ -0,0 +1,13 @@ +use sum_of_multiples::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let factors = &{{ test.input.factors | json_encode() }}; + let limit = {{ test.input.limit | fmt_num }}; + let output = sum_of_multiples(limit, factors); + let expected = {{ test.expected | fmt_num }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/sum-of-multiples/.meta/tests.toml b/exercises/practice/sum-of-multiples/.meta/tests.toml index 59bd7ca12..1e9b1241d 100644 --- a/exercises/practice/sum-of-multiples/.meta/tests.toml +++ b/exercises/practice/sum-of-multiples/.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. [54aaab5a-ce86-4edc-8b40-d3ab2400a279] description = "no multiples within limit" @@ -46,3 +53,6 @@ description = "the only multiple of 0 is 0" [c423ae21-a0cb-4ec7-aeb1-32971af5b510] description = "the factor 0 does not affect the sum of multiples of other factors" + +[17053ba9-112f-4ac0-aadb-0519dd836342] +description = "solutions using include-exclude must extend to cardinality greater than 3" diff --git a/exercises/practice/sum-of-multiples/Cargo.toml b/exercises/practice/sum-of-multiples/Cargo.toml index c87994430..be310bd03 100644 --- a/exercises/practice/sum-of-multiples/Cargo.toml +++ b/exercises/practice/sum-of-multiples/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "sum-of-multiples" -version = "1.5.0" +name = "sum_of_multiples" +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/sum-of-multiples/src/lib.rs b/exercises/practice/sum-of-multiples/src/lib.rs index 09a5c5be5..c5e1645e6 100644 --- a/exercises/practice/sum-of-multiples/src/lib.rs +++ b/exercises/practice/sum-of-multiples/src/lib.rs @@ -1,3 +1,3 @@ pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { - unimplemented!("Sum the multiples of all of {factors:?} which are less than {limit}") + todo!("Sum the multiples of all of {factors:?} which are less than {limit}") } diff --git a/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs b/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs deleted file mode 100644 index bdcd30d77..000000000 --- a/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs +++ /dev/null @@ -1,96 +0,0 @@ -use sum_of_multiples::*; - -#[test] -fn no_multiples_within_limit() { - assert_eq!(0, sum_of_multiples(1, &[3, 5])) -} - -#[test] -#[ignore] -fn one_factor_has_multiples_within_limit() { - assert_eq!(3, sum_of_multiples(4, &[3, 5])) -} - -#[test] -#[ignore] -fn more_than_one_multiple_within_limit() { - assert_eq!(9, sum_of_multiples(7, &[3])) -} - -#[test] -#[ignore] -fn more_than_one_factor_with_multiples_within_limit() { - assert_eq!(23, sum_of_multiples(10, &[3, 5])) -} - -#[test] -#[ignore] -fn each_multiple_is_only_counted_once() { - assert_eq!(2318, sum_of_multiples(100, &[3, 5])) -} - -#[test] -#[ignore] -fn a_much_larger_limit() { - assert_eq!(233_168, sum_of_multiples(1000, &[3, 5])) -} - -#[test] -#[ignore] -fn three_factors() { - assert_eq!(51, sum_of_multiples(20, &[7, 13, 17])) -} - -#[test] -#[ignore] -fn factors_not_relatively_prime() { - assert_eq!(30, sum_of_multiples(15, &[4, 6])) -} - -#[test] -#[ignore] -fn some_pairs_of_factors_relatively_prime_and_some_not() { - assert_eq!(4419, sum_of_multiples(150, &[5, 6, 8])) -} - -#[test] -#[ignore] -fn one_factor_is_a_multiple_of_another() { - assert_eq!(275, sum_of_multiples(51, &[5, 25])) -} - -#[test] -#[ignore] -fn much_larger_factors() { - assert_eq!(2_203_160, sum_of_multiples(10_000, &[43, 47])) -} - -#[test] -#[ignore] -fn all_numbers_are_multiples_of_1() { - assert_eq!(4950, sum_of_multiples(100, &[1])) -} - -#[test] -#[ignore] -fn no_factors_means_an_empty_sum() { - assert_eq!(0, sum_of_multiples(10_000, &[])) -} - -#[test] -#[ignore] -fn the_only_multiple_of_0_is_0() { - assert_eq!(0, sum_of_multiples(1, &[0])) -} - -#[test] -#[ignore] -fn the_factor_0_does_not_affect_the_sum_of_multiples_of_other_factors() { - assert_eq!(3, sum_of_multiples(4, &[3, 0])) -} - -#[test] -#[ignore] -fn solutions_using_include_exclude_must_extend_to_cardinality_greater_than_3() { - assert_eq!(39_614_537, sum_of_multiples(10_000, &[2, 3, 5, 7, 11])) -} diff --git a/exercises/practice/sum-of-multiples/tests/sum_of_multiples.rs b/exercises/practice/sum-of-multiples/tests/sum_of_multiples.rs new file mode 100644 index 000000000..9864598ce --- /dev/null +++ b/exercises/practice/sum-of-multiples/tests/sum_of_multiples.rs @@ -0,0 +1,160 @@ +use sum_of_multiples::*; + +#[test] +fn no_multiples_within_limit() { + let factors = &[3, 5]; + let limit = 1; + let output = sum_of_multiples(limit, factors); + let expected = 0; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn one_factor_has_multiples_within_limit() { + let factors = &[3, 5]; + let limit = 4; + let output = sum_of_multiples(limit, factors); + let expected = 3; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn more_than_one_multiple_within_limit() { + let factors = &[3]; + let limit = 7; + let output = sum_of_multiples(limit, factors); + let expected = 9; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn more_than_one_factor_with_multiples_within_limit() { + let factors = &[3, 5]; + let limit = 10; + let output = sum_of_multiples(limit, factors); + let expected = 23; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn each_multiple_is_only_counted_once() { + let factors = &[3, 5]; + let limit = 100; + let output = sum_of_multiples(limit, factors); + let expected = 2_318; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn a_much_larger_limit() { + let factors = &[3, 5]; + let limit = 1_000; + let output = sum_of_multiples(limit, factors); + let expected = 233_168; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn three_factors() { + let factors = &[7, 13, 17]; + let limit = 20; + let output = sum_of_multiples(limit, factors); + let expected = 51; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn factors_not_relatively_prime() { + let factors = &[4, 6]; + let limit = 15; + let output = sum_of_multiples(limit, factors); + let expected = 30; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn some_pairs_of_factors_relatively_prime_and_some_not() { + let factors = &[5, 6, 8]; + let limit = 150; + let output = sum_of_multiples(limit, factors); + let expected = 4_419; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn one_factor_is_a_multiple_of_another() { + let factors = &[5, 25]; + let limit = 51; + let output = sum_of_multiples(limit, factors); + let expected = 275; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn much_larger_factors() { + let factors = &[43, 47]; + let limit = 10_000; + let output = sum_of_multiples(limit, factors); + let expected = 2_203_160; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn all_numbers_are_multiples_of_1() { + let factors = &[1]; + let limit = 100; + let output = sum_of_multiples(limit, factors); + let expected = 4_950; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn no_factors_means_an_empty_sum() { + let factors = &[]; + let limit = 10_000; + let output = sum_of_multiples(limit, factors); + let expected = 0; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn the_only_multiple_of_0_is_0() { + let factors = &[0]; + let limit = 1; + let output = sum_of_multiples(limit, factors); + let expected = 0; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn the_factor_0_does_not_affect_the_sum_of_multiples_of_other_factors() { + let factors = &[3, 0]; + let limit = 4; + let output = sum_of_multiples(limit, factors); + let expected = 3; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn solutions_using_include_exclude_must_extend_to_cardinality_greater_than_3() { + let factors = &[2, 3, 5, 7, 11]; + let limit = 10_000; + let output = sum_of_multiples(limit, factors); + let expected = 39_614_537; + assert_eq!(output, expected); +} diff --git a/exercises/practice/tournament/.docs/instructions.md b/exercises/practice/tournament/.docs/instructions.md index 8831dd195..e5ca23738 100644 --- a/exercises/practice/tournament/.docs/instructions.md +++ b/exercises/practice/tournament/.docs/instructions.md @@ -2,8 +2,7 @@ Tally the results of a small football competition. -Based on an input file containing which team played against which and what the -outcome was, create a file with a table like this: +Based on an input file containing which team played against which and what the outcome was, create a file with a table like this: ```text Team | MP | W | D | L | P @@ -21,9 +20,12 @@ What do those abbreviations mean? - L: Matches Lost - P: Points -A win earns a team 3 points. A draw earns 1. A loss earns 0. +A win earns a team 3 points. +A draw earns 1. +A loss earns 0. -The outcome should be ordered by points, descending. In case of a tie, teams are ordered alphabetically. +The outcome is ordered by points, descending. +In case of a tie, teams are ordered alphabetically. ## Input @@ -38,7 +40,8 @@ Blithering Badgers;Devastating Donkeys;loss Allegoric Alaskans;Courageous Californians;win ``` -The result of the match refers to the first team listed. So this line: +The result of the match refers to the first team listed. +So this line: ```text Allegoric Alaskans;Blithering Badgers;win diff --git a/exercises/practice/tournament/.gitignore b/exercises/practice/tournament/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/tournament/.gitignore +++ b/exercises/practice/tournament/.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/tournament/.meta/example.rs b/exercises/practice/tournament/.meta/example.rs index e7767a057..9221684a9 100644 --- a/exercises/practice/tournament/.meta/example.rs +++ b/exercises/practice/tournament/.meta/example.rs @@ -70,7 +70,7 @@ fn write_tally(results: &HashMap) -> String { .collect(); // Sort by points descending, then name A-Z. v.sort_by(|a, b| match a.3.cmp(&(b.3)).reverse() { - Equal => a.0.cmp(&(b.0)), + Equal => a.0.cmp(b.0), other => other, }); let mut lines = vec![format!("{:30} | MP | W | D | L | P", "Team")]; diff --git a/exercises/practice/tournament/.meta/test_template.tera b/exercises/practice/tournament/.meta/test_template.tera new file mode 100644 index 000000000..acc85890a --- /dev/null +++ b/exercises/practice/tournament/.meta/test_template.tera @@ -0,0 +1,13 @@ +use tournament::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input: &[&str] = &{{ test.input.rows | json_encode() }}; + let input = input.join("\n"); + let output = tally(&input); + let expected = {{ test.expected | json_encode() }}.join("\n"); + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/tournament/.meta/tests.toml b/exercises/practice/tournament/.meta/tests.toml index 598677086..0e33c87fc 100644 --- a/exercises/practice/tournament/.meta/tests.toml +++ b/exercises/practice/tournament/.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. [67e9fab1-07c1-49cf-9159-bc8671cc7c9c] description = "just the header if no input" @@ -34,3 +41,6 @@ description = "incomplete competition (not all pairs have played)" [3aa0386f-150b-4f99-90bb-5195e7b7d3b8] description = "ties broken alphabetically" + +[f9e20931-8a65-442a-81f6-503c0205b17a] +description = "ensure points sorted numerically" diff --git a/exercises/practice/tournament/Cargo.toml b/exercises/practice/tournament/Cargo.toml index e9209b33a..49744661b 100644 --- a/exercises/practice/tournament/Cargo.toml +++ b/exercises/practice/tournament/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "tournament" -version = "1.4.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/tournament/src/lib.rs b/exercises/practice/tournament/src/lib.rs index 47b3d36de..780d3acf8 100644 --- a/exercises/practice/tournament/src/lib.rs +++ b/exercises/practice/tournament/src/lib.rs @@ -1,5 +1,5 @@ pub fn tally(match_results: &str) -> String { - unimplemented!( + todo!( "Given the result of the played matches '{match_results}' return a properly formatted tally table string." ); } diff --git a/exercises/practice/tournament/tests/tournament.rs b/exercises/practice/tournament/tests/tournament.rs index 398e91807..4d3572585 100644 --- a/exercises/practice/tournament/tests/tournament.rs +++ b/exercises/practice/tournament/tests/tournament.rs @@ -1,152 +1,217 @@ +use tournament::*; + #[test] fn just_the_header_if_no_input() { - let input = ""; - let expected = "Team | MP | W | D | L | P"; - - assert_eq!(tournament::tally(input), expected); + let input: &[&str] = &[]; + let input = input.join("\n"); + let output = tally(&input); + let expected = ["Team | MP | W | D | L | P"].join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn a_win_is_three_points_a_loss_is_zero_points() { - let input = "Allegoric Alaskans;Blithering Badgers;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 1 | 1 | 0 | 0 | 3\n" - + "Blithering Badgers | 1 | 0 | 0 | 1 | 0"; - - assert_eq!(tournament::tally(input), expected); + let input: &[&str] = &["Allegoric Alaskans;Blithering Badgers;win"]; + let input = input.join("\n"); + let output = tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 1 | 1 | 0 | 0 | 3", + "Blithering Badgers | 1 | 0 | 0 | 1 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn a_win_can_also_be_expressed_as_a_loss() { - let input = "Blithering Badgers;Allegoric Alaskans;loss"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 1 | 1 | 0 | 0 | 3\n" - + "Blithering Badgers | 1 | 0 | 0 | 1 | 0"; - - assert_eq!(tournament::tally(input), expected); + let input: &[&str] = &["Blithering Badgers;Allegoric Alaskans;loss"]; + let input = input.join("\n"); + let output = tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 1 | 1 | 0 | 0 | 3", + "Blithering Badgers | 1 | 0 | 0 | 1 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn a_different_team_can_win() { - let input = "Blithering Badgers;Allegoric Alaskans;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Blithering Badgers | 1 | 1 | 0 | 0 | 3\n" - + "Allegoric Alaskans | 1 | 0 | 0 | 1 | 0"; - - assert_eq!(tournament::tally(input), expected); + let input: &[&str] = &["Blithering Badgers;Allegoric Alaskans;win"]; + let input = input.join("\n"); + let output = tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Blithering Badgers | 1 | 1 | 0 | 0 | 3", + "Allegoric Alaskans | 1 | 0 | 0 | 1 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] -fn there_can_be_more_than_one_match() { - let input = "Allegoric Alaskans;Blithering Badgers;win\n".to_string() - + "Allegoric Alaskans;Blithering Badgers;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 2 | 2 | 0 | 0 | 6\n" - + "Blithering Badgers | 2 | 0 | 0 | 2 | 0"; - - assert_eq!(tournament::tally(&input), expected); +fn a_draw_is_one_point_each() { + let input: &[&str] = &["Allegoric Alaskans;Blithering Badgers;draw"]; + let input = input.join("\n"); + let output = tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 1 | 0 | 1 | 0 | 1", + "Blithering Badgers | 1 | 0 | 1 | 0 | 1", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] -fn a_draw_is_one_point_each() { - let input = "Allegoric Alaskans;Blithering Badgers;draw\n".to_string() - + "Allegoric Alaskans;Blithering Badgers;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 2 | 1 | 1 | 0 | 4\n" - + "Blithering Badgers | 2 | 0 | 1 | 1 | 1"; - - assert_eq!(tournament::tally(&input), expected); +fn there_can_be_more_than_one_match() { + let input: &[&str] = &[ + "Allegoric Alaskans;Blithering Badgers;win", + "Allegoric Alaskans;Blithering Badgers;win", + ]; + let input = input.join("\n"); + let output = tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 2 | 2 | 0 | 0 | 6", + "Blithering Badgers | 2 | 0 | 0 | 2 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn there_can_be_more_than_one_winner() { - let input = "Allegoric Alaskans;Blithering Badgers;loss\n".to_string() - + "Allegoric Alaskans;Blithering Badgers;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 2 | 1 | 0 | 1 | 3\n" - + "Blithering Badgers | 2 | 1 | 0 | 1 | 3"; - - assert_eq!(tournament::tally(&input), expected); + let input: &[&str] = &[ + "Allegoric Alaskans;Blithering Badgers;loss", + "Allegoric Alaskans;Blithering Badgers;win", + ]; + let input = input.join("\n"); + let output = tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 2 | 1 | 0 | 1 | 3", + "Blithering Badgers | 2 | 1 | 0 | 1 | 3", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn there_can_be_more_than_two_teams() { - let input = "Allegoric Alaskans;Blithering Badgers;win\n".to_string() - + "Blithering Badgers;Courageous Californians;win\n" - + "Courageous Californians;Allegoric Alaskans;loss"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 2 | 2 | 0 | 0 | 6\n" - + "Blithering Badgers | 2 | 1 | 0 | 1 | 3\n" - + "Courageous Californians | 2 | 0 | 0 | 2 | 0"; - - assert_eq!(tournament::tally(&input), expected); + let input: &[&str] = &[ + "Allegoric Alaskans;Blithering Badgers;win", + "Blithering Badgers;Courageous Californians;win", + "Courageous Californians;Allegoric Alaskans;loss", + ]; + let input = input.join("\n"); + let output = tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 2 | 2 | 0 | 0 | 6", + "Blithering Badgers | 2 | 1 | 0 | 1 | 3", + "Courageous Californians | 2 | 0 | 0 | 2 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn typical_input() { - let input = "Allegoric Alaskans;Blithering Badgers;win\n".to_string() - + "Devastating Donkeys;Courageous Californians;draw\n" - + "Devastating Donkeys;Allegoric Alaskans;win\n" - + "Courageous Californians;Blithering Badgers;loss\n" - + "Blithering Badgers;Devastating Donkeys;loss\n" - + "Allegoric Alaskans;Courageous Californians;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Devastating Donkeys | 3 | 2 | 1 | 0 | 7\n" - + "Allegoric Alaskans | 3 | 2 | 0 | 1 | 6\n" - + "Blithering Badgers | 3 | 1 | 0 | 2 | 3\n" - + "Courageous Californians | 3 | 0 | 1 | 2 | 1"; - - assert_eq!(tournament::tally(&input), expected); + let input: &[&str] = &[ + "Allegoric Alaskans;Blithering Badgers;win", + "Devastating Donkeys;Courageous Californians;draw", + "Devastating Donkeys;Allegoric Alaskans;win", + "Courageous Californians;Blithering Badgers;loss", + "Blithering Badgers;Devastating Donkeys;loss", + "Allegoric Alaskans;Courageous Californians;win", + ]; + let input = input.join("\n"); + let output = tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Devastating Donkeys | 3 | 2 | 1 | 0 | 7", + "Allegoric Alaskans | 3 | 2 | 0 | 1 | 6", + "Blithering Badgers | 3 | 1 | 0 | 2 | 3", + "Courageous Californians | 3 | 0 | 1 | 2 | 1", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn incomplete_competition_not_all_pairs_have_played() { - let input = "Allegoric Alaskans;Blithering Badgers;loss\n".to_string() - + "Devastating Donkeys;Allegoric Alaskans;loss\n" - + "Courageous Californians;Blithering Badgers;draw\n" - + "Allegoric Alaskans;Courageous Californians;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 3 | 2 | 0 | 1 | 6\n" - + "Blithering Badgers | 2 | 1 | 1 | 0 | 4\n" - + "Courageous Californians | 2 | 0 | 1 | 1 | 1\n" - + "Devastating Donkeys | 1 | 0 | 0 | 1 | 0"; - - assert_eq!(tournament::tally(&input), expected); + let input: &[&str] = &[ + "Allegoric Alaskans;Blithering Badgers;loss", + "Devastating Donkeys;Allegoric Alaskans;loss", + "Courageous Californians;Blithering Badgers;draw", + "Allegoric Alaskans;Courageous Californians;win", + ]; + let input = input.join("\n"); + let output = tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 3 | 2 | 0 | 1 | 6", + "Blithering Badgers | 2 | 1 | 1 | 0 | 4", + "Courageous Californians | 2 | 0 | 1 | 1 | 1", + "Devastating Donkeys | 1 | 0 | 0 | 1 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn ties_broken_alphabetically() { - let input = "Courageous Californians;Devastating Donkeys;win\n".to_string() - + "Allegoric Alaskans;Blithering Badgers;win\n" - + "Devastating Donkeys;Allegoric Alaskans;loss\n" - + "Courageous Californians;Blithering Badgers;win\n" - + "Blithering Badgers;Devastating Donkeys;draw\n" - + "Allegoric Alaskans;Courageous Californians;draw"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 3 | 2 | 1 | 0 | 7\n" - + "Courageous Californians | 3 | 2 | 1 | 0 | 7\n" - + "Blithering Badgers | 3 | 0 | 1 | 2 | 1\n" - + "Devastating Donkeys | 3 | 0 | 1 | 2 | 1"; + let input: &[&str] = &[ + "Courageous Californians;Devastating Donkeys;win", + "Allegoric Alaskans;Blithering Badgers;win", + "Devastating Donkeys;Allegoric Alaskans;loss", + "Courageous Californians;Blithering Badgers;win", + "Blithering Badgers;Devastating Donkeys;draw", + "Allegoric Alaskans;Courageous Californians;draw", + ]; + let input = input.join("\n"); + let output = tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 3 | 2 | 1 | 0 | 7", + "Courageous Californians | 3 | 2 | 1 | 0 | 7", + "Blithering Badgers | 3 | 0 | 1 | 2 | 1", + "Devastating Donkeys | 3 | 0 | 1 | 2 | 1", + ] + .join("\n"); + assert_eq!(output, expected); +} - assert_eq!(tournament::tally(&input), expected); +#[test] +#[ignore] +fn ensure_points_sorted_numerically() { + let input: &[&str] = &[ + "Devastating Donkeys;Blithering Badgers;win", + "Devastating Donkeys;Blithering Badgers;win", + "Devastating Donkeys;Blithering Badgers;win", + "Devastating Donkeys;Blithering Badgers;win", + "Blithering Badgers;Devastating Donkeys;win", + ]; + let input = input.join("\n"); + let output = tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Devastating Donkeys | 5 | 4 | 0 | 1 | 12", + "Blithering Badgers | 5 | 1 | 0 | 4 | 3", + ] + .join("\n"); + assert_eq!(output, expected); } diff --git a/exercises/practice/triangle/.docs/instructions.append.md b/exercises/practice/triangle/.docs/instructions.append.md index deb679100..a5eb5f15e 100644 --- a/exercises/practice/triangle/.docs/instructions.append.md +++ b/exercises/practice/triangle/.docs/instructions.append.md @@ -1,11 +1,11 @@ -# Triangle in Rust +# Instructions append - [Result](https://doc.rust-lang.org/std/result/index.html) Implementation of this can take many forms. Here are some topics that may help you, depending on the approach you take. -- [Enums](https://doc.rust-lang.org/book/2018-edition/ch06-00-enums.html) -- [Traits](https://doc.rust-lang.org/book/2018-edition/ch10-02-traits.html) +- [Enums](https://doc.rust-lang.org/book/ch06-00-enums.html) +- [Traits](https://doc.rust-lang.org/book/ch10-02-traits.html) - [BTreeSet](https://doc.rust-lang.org/std/collections/btree_set/struct.BTreeSet.html) Or maybe you will come up with an approach that uses none of those! @@ -13,11 +13,11 @@ Or maybe you will come up with an approach that uses none of those! ## Non-integer lengths The base exercise tests identification of triangles whose sides are all -integers. However, some triangles cannot be represented by pure integers. A simple example is a right triangle (an isosceles triangle whose equal sides are separated by 90 degrees) whose equal sides both have length of 1. Its hypotenuse is the square root of 2, which is an irrational number: no simple multiplication can represent this number as an integer. +integers. However, some triangles cannot be represented by pure integers. A simple example is a triangle with a 90 degree angle between two equal sides of length 1. Its third side has the length square root of 2, which is an irrational number (meaning it cannot be written as an integer or a fraction). It would be tedious to rewrite the analysis functions to handle both integer and floating-point cases, and particularly tedious to do so for all potential integer and floating point types: given signed and unsigned variants of bitwidths 8, 16, 32, 64, and 128, that would be 10 reimplementations of fundamentally the same code even before considering floats! -There's a better way: [generics](https://doc.rust-lang.org/stable/book/2018-edition/ch10-00-generics.html). By rewriting your Triangle as a `Triangle`, you can write your code once, and hand off the work of generating all those specializations to the compiler. Note that in order to use mathematical operations, you'll need to constrain your generic type to types which support those operations using traits. +There's a better way: [generics](https://doc.rust-lang.org/stable/book/ch10-00-generics.html). By rewriting your Triangle as a `Triangle`, you can write your code once, and hand off the work of generating all those specializations to the compiler. Note that in order to use mathematical operations, you'll need to constrain your generic type to types which support those operations using traits. There are some bonus tests you can run which test your implementation on floating-point numbers. To enable them, run your tests with the `generic` feature flag, like this: diff --git a/exercises/practice/triangle/.docs/instructions.md b/exercises/practice/triangle/.docs/instructions.md index 0a9c68e3b..e9b053dcd 100644 --- a/exercises/practice/triangle/.docs/instructions.md +++ b/exercises/practice/triangle/.docs/instructions.md @@ -4,20 +4,32 @@ Determine if a triangle is equilateral, isosceles, or scalene. An _equilateral_ triangle has all three sides the same length. -An _isosceles_ triangle has at least two sides the same length. (It is sometimes -specified as having exactly two sides the same length, but for the purposes of -this exercise we'll say at least two.) +An _isosceles_ triangle has at least two sides the same length. +(It is sometimes specified as having exactly two sides the same length, but for the purposes of this exercise we'll say at least two.) A _scalene_ triangle has all sides of different lengths. ## Note -For a shape to be a triangle at all, all sides have to be of length > 0, and -the sum of the lengths of any two sides must be greater than or equal to the -length of the third side. See [Triangle Inequality](https://en.wikipedia.org/wiki/Triangle_inequality). +For a shape to be a triangle at all, all sides have to be of length > 0, and the sum of the lengths of any two sides must be greater than or equal to the length of the third side. -## Dig Deeper +~~~~exercism/note +_Degenerate triangles_ are triangles where the sum of the length of two sides is **equal** to the length of the third side, e.g. `1, 1, 2`. +We opted to not include tests for degenerate triangles in this exercise. +You may handle those situations if you wish to do so, or safely ignore them. +~~~~ -The case where the sum of the lengths of two sides _equals_ that of the -third is known as a _degenerate_ triangle - it has zero area and looks like -a single line. Feel free to add your own code/tests to check for degenerate triangles. +In equations: + +Let `a`, `b`, and `c` be sides of the triangle. +Then all three of the following expressions must be true: + +```text +a + b ≥ c +b + c ≥ a +a + c ≥ b +``` + +See [Triangle Inequality][triangle-inequality] + +[triangle-inequality]: https://en.wikipedia.org/wiki/Triangle_inequality diff --git a/exercises/practice/triangle/.gitignore b/exercises/practice/triangle/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/triangle/.gitignore +++ b/exercises/practice/triangle/.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/triangle/.meta/additional-tests.json b/exercises/practice/triangle/.meta/additional-tests.json new file mode 100644 index 000000000..617a06a15 --- /dev/null +++ b/exercises/practice/triangle/.meta/additional-tests.json @@ -0,0 +1,57 @@ +[ + { + "description": "invalid triangle", + "cases": [ + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "all zero sides is not a triangle", + "comments": ["reimplements 16e8ceb0-eadb-46d1-b892-c50327479251"], + "property": "invalid", + "input": { + "sides": [0, 0, 0] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "first triangle inequality violation", + "comments": ["reimplements 2eba0cfb-6c65-4c40-8146-30b608905eae"], + "property": "invalid", + "input": { + "sides": [1, 1, 3] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "second triangle inequality violation", + "comments": ["reimplements 278469cb-ac6b-41f0-81d4-66d9b828f8ac"], + "property": "invalid", + "input": { + "sides": [1, 3, 1] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "third triangle inequality violation", + "comments": ["reimplements 90efb0c7-72bb-4514-b320-3a3892e278ff"], + "property": "invalid", + "input": { + "sides": [3, 1, 1] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "may not violate triangle inequality", + "comments": ["reimplements 70ad5154-0033-48b7-af2c-b8d739cd9fdc"], + "property": "invalid", + "input": { + "sides": [7, 3, 2] + }, + "expected": false + } + ] + } +] diff --git a/exercises/practice/triangle/.meta/config.json b/exercises/practice/triangle/.meta/config.json index 2994bb66a..0f26a1103 100644 --- a/exercises/practice/triangle/.meta/config.json +++ b/exercises/practice/triangle/.meta/config.json @@ -36,5 +36,5 @@ }, "blurb": "Determine if a triangle is equilateral, isosceles, or scalene.", "source": "The Ruby Koans triangle project, parts 1 & 2", - "source_url": "http://rubykoans.com/" + "source_url": "https://web.archive.org/web/20220831105330/http://rubykoans.com" } diff --git a/exercises/practice/triangle/.meta/example.rs b/exercises/practice/triangle/.meta/example.rs index 213cfb788..ea4c1d859 100644 --- a/exercises/practice/triangle/.meta/example.rs +++ b/exercises/practice/triangle/.meta/example.rs @@ -25,11 +25,7 @@ where pub fn build(sides: [T; 3]) -> Option> { let t = Triangle { sides }; - if t.valid_sides() { - Some(t) - } else { - None - } + if t.valid_sides() { Some(t) } else { None } } pub fn is_equilateral(&self) -> bool { @@ -37,7 +33,7 @@ where } pub fn is_isosceles(&self) -> bool { - self.count_distinct_pairs() == 2 + self.count_distinct_pairs() <= 2 } pub fn is_scalene(&self) -> bool { diff --git a/exercises/practice/triangle/.meta/test_template.tera b/exercises/practice/triangle/.meta/test_template.tera new file mode 100644 index 000000000..cd13aa6c0 --- /dev/null +++ b/exercises/practice/triangle/.meta/test_template.tera @@ -0,0 +1,70 @@ +{% for test_group in cases %} +mod {{ test_group.description | split(pat=" ") | first }} { + use triangle::Triangle; + +{% for test in test_group.cases %} + +{% if test.property == "equilateral" %} +#[test] +#[ignore] +{% if test.description is containing("float") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | make_ident }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input).unwrap(); + {%- if test.expected %} + assert!(output.is_equilateral()); + {% else %} + assert!(!output.is_equilateral()); + {% endif -%} +} + +{% elif test.property == "isosceles" %} +#[test] +#[ignore] +{% if test.scenarios and test.scenarios is containing("floating-point") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | make_ident }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input).unwrap(); + {%- if test.expected %} + assert!(output.is_isosceles()); + {% else %} + assert!(!output.is_isosceles()); + {% endif -%} +} + +{% elif test.property == "scalene" %} +#[test] +#[ignore] +{% if test.scenarios and test.scenarios is containing("floating-point") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | make_ident }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input).unwrap(); + {%- if test.expected %} + assert!(output.is_scalene()); + {% else %} + assert!(!output.is_scalene()); + {% endif -%} +} + +{% elif test.property == "invalid" %} +#[test] +#[ignore] +{% if test.scenarios and test.scenarios is containing("floating-point") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | make_ident }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input); + assert!(output.is_none()); +} + +{% endif %} +{% endfor %} +} +{% endfor %} diff --git a/exercises/practice/triangle/.meta/tests.toml b/exercises/practice/triangle/.meta/tests.toml index be690e975..2590d6ff4 100644 --- a/exercises/practice/triangle/.meta/tests.toml +++ b/exercises/practice/triangle/.meta/tests.toml @@ -1,3 +1,108 @@ -# 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. + +[8b2c43ac-7257-43f9-b552-7631a91988af] +description = "equilateral triangle -> all sides are equal" + +[33eb6f87-0498-4ccf-9573-7f8c3ce92b7b] +description = "equilateral triangle -> any side is unequal" + +[c6585b7d-a8c0-4ad8-8a34-e21d36f7ad87] +description = "equilateral triangle -> no sides are equal" + +[16e8ceb0-eadb-46d1-b892-c50327479251] +description = "equilateral triangle -> all zero sides is not a triangle" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[3022f537-b8e5-4cc1-8f12-fd775827a00c] +description = "equilateral triangle -> sides may be floats" + +[cbc612dc-d75a-4c1c-87fc-e2d5edd70b71] +description = "isosceles triangle -> last two sides are equal" + +[e388ce93-f25e-4daf-b977-4b7ede992217] +description = "isosceles triangle -> first two sides are equal" + +[d2080b79-4523-4c3f-9d42-2da6e81ab30f] +description = "isosceles triangle -> first and last sides are equal" + +[8d71e185-2bd7-4841-b7e1-71689a5491d8] +description = "isosceles triangle -> equilateral triangles are also isosceles" + +[840ed5f8-366f-43c5-ac69-8f05e6f10bbb] +description = "isosceles triangle -> no sides are equal" + +[2eba0cfb-6c65-4c40-8146-30b608905eae] +description = "isosceles triangle -> first triangle inequality violation" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[278469cb-ac6b-41f0-81d4-66d9b828f8ac] +description = "isosceles triangle -> second triangle inequality violation" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[90efb0c7-72bb-4514-b320-3a3892e278ff] +description = "isosceles triangle -> third triangle inequality violation" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[adb4ee20-532f-43dc-8d31-e9271b7ef2bc] +description = "isosceles triangle -> sides may be floats" + +[e8b5f09c-ec2e-47c1-abec-f35095733afb] +description = "scalene triangle -> no sides are equal" + +[2510001f-b44d-4d18-9872-2303e7977dc1] +description = "scalene triangle -> all sides are equal" + +[c6e15a92-90d9-4fb3-90a2-eef64f8d3e1e] +description = "scalene triangle -> first and second sides are equal" + +[3da23a91-a166-419a-9abf-baf4868fd985] +description = "scalene triangle -> first and third sides are equal" + +[b6a75d98-1fef-4c42-8e9a-9db854ba0a4d] +description = "scalene triangle -> second and third sides are equal" + +[70ad5154-0033-48b7-af2c-b8d739cd9fdc] +description = "scalene triangle -> may not violate triangle inequality" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[26d9d59d-f8f1-40d3-ad58-ae4d54123d7d] +description = "scalene triangle -> sides may be floats" diff --git a/exercises/practice/triangle/Cargo.toml b/exercises/practice/triangle/Cargo.toml index b7f441c75..9bb92a6ca 100644 --- a/exercises/practice/triangle/Cargo.toml +++ b/exercises/practice/triangle/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" name = "triangle" -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] [features] generic = [] diff --git a/exercises/practice/triangle/src/lib.rs b/exercises/practice/triangle/src/lib.rs index f7f365798..020b2a4cf 100644 --- a/exercises/practice/triangle/src/lib.rs +++ b/exercises/practice/triangle/src/lib.rs @@ -2,18 +2,20 @@ pub struct Triangle; impl Triangle { pub fn build(sides: [u64; 3]) -> Option { - unimplemented!("Construct new Triangle from following sides: {sides:?}. Return None if the sides are invalid."); + todo!( + "Construct new Triangle from following sides: {sides:?}. Return None if the sides are invalid." + ); } pub fn is_equilateral(&self) -> bool { - unimplemented!("Determine if the Triangle is equilateral."); + todo!("Determine if the Triangle is equilateral."); } pub fn is_scalene(&self) -> bool { - unimplemented!("Determine if the Triangle is scalene."); + todo!("Determine if the Triangle is scalene."); } pub fn is_isosceles(&self) -> bool { - unimplemented!("Determine if the Triangle is isosceles."); + todo!("Determine if the Triangle is isosceles."); } } diff --git a/exercises/practice/triangle/tests/triangle.rs b/exercises/practice/triangle/tests/triangle.rs index c93221df4..4992a3df9 100644 --- a/exercises/practice/triangle/tests/triangle.rs +++ b/exercises/practice/triangle/tests/triangle.rs @@ -1,204 +1,185 @@ -use triangle::*; - -#[test] -fn positive_length_sides_are_ok() { - let sides = [2, 2, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_some()); -} - -#[test] -#[ignore] -fn zero_length_sides_are_illegal() { - let sides = [0, 0, 0]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn one_length_zero_side_first() { - let sides = [0, 2, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn one_length_zero_side_second() { - let sides = [2, 0, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn one_length_zero_side_third() { - let sides = [2, 2, 0]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn equilateral_triangles_have_equal_sides() { - let sides = [2, 2, 2]; - let triangle = Triangle::build(sides).unwrap(); - assert!(triangle.is_equilateral()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn larger_equilateral_triangles_have_equal_sides() { - let sides = [10, 10, 10]; - let triangle = Triangle::build(sides).unwrap(); - assert!(triangle.is_equilateral()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_one() { - let sides = [3, 4, 4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_two() { - let sides = [4, 4, 3]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_three() { - let sides = [4, 3, 4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_four() { - let sides = [4, 7, 4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_one() { - let sides = [3, 4, 5]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_two() { - let sides = [5, 4, 6]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_three() { - let sides = [10, 11, 12]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_four() { - let sides = [5, 4, 2]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn sum_of_two_sides_must_equal_or_exceed_the_remaining_side_one() { - let sides = [7, 3, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn sum_of_two_sides_must_equal_or_exceed_the_remaining_side_two() { - let sides = [1, 1, 3]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn scalene_triangle_with_floating_point_sides() { - let sides = [0.4, 0.6, 0.3]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn equilateral_triangles_with_floating_point_sides() { - let sides = [0.2, 0.2, 0.2]; - let triangle = Triangle::build(sides).unwrap(); - assert!(triangle.is_equilateral()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn isosceles_triangle_with_floating_point_sides() { - let sides = [0.3, 0.4, 0.4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn invalid_triangle_with_floating_point_sides_one() { - let sides = [0.0, 0.4, 0.3]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn invalid_triangle_with_floating_point_sides_two() { - let sides = [0.1, 0.3, 0.5]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); +mod equilateral { + use triangle::Triangle; + + #[test] + fn all_sides_are_equal() { + let input = [2, 2, 2]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_equilateral()); + } + + #[test] + #[ignore] + fn any_side_is_unequal() { + let input = [2, 3, 2]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_equilateral()); + } + + #[test] + #[ignore] + fn no_sides_are_equal() { + let input = [5, 4, 6]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_equilateral()); + } + + #[test] + #[ignore] + #[cfg(feature = "generic")] + fn sides_may_be_floats() { + let input = [0.5, 0.5, 0.5]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_equilateral()); + } +} + +mod isosceles { + use triangle::Triangle; + + #[test] + #[ignore] + fn last_two_sides_are_equal() { + let input = [3, 4, 4]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn first_two_sides_are_equal() { + let input = [4, 4, 3]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn first_and_last_sides_are_equal() { + let input = [4, 3, 4]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn equilateral_triangles_are_also_isosceles() { + let input = [4, 4, 4]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn no_sides_are_equal() { + let input = [2, 3, 4]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_isosceles()); + } + + #[test] + #[ignore] + #[cfg(feature = "generic")] + fn sides_may_be_floats() { + let input = [0.5, 0.4, 0.5]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } +} + +mod scalene { + use triangle::Triangle; + + #[test] + #[ignore] + fn no_sides_are_equal() { + let input = [5, 4, 6]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_scalene()); + } + + #[test] + #[ignore] + fn all_sides_are_equal() { + let input = [4, 4, 4]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + fn first_and_second_sides_are_equal() { + let input = [4, 4, 3]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + fn first_and_third_sides_are_equal() { + let input = [3, 4, 3]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + fn second_and_third_sides_are_equal() { + let input = [4, 3, 3]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + #[cfg(feature = "generic")] + fn sides_may_be_floats() { + let input = [0.5, 0.4, 0.6]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_scalene()); + } +} + +mod invalid { + use triangle::Triangle; + + #[test] + #[ignore] + fn all_zero_sides_is_not_a_triangle() { + let input = [0, 0, 0]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn first_triangle_inequality_violation() { + let input = [1, 1, 3]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn second_triangle_inequality_violation() { + let input = [1, 3, 1]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn third_triangle_inequality_violation() { + let input = [3, 1, 1]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn may_not_violate_triangle_inequality() { + let input = [7, 3, 2]; + let output = Triangle::build(input); + assert!(output.is_none()); + } } diff --git a/exercises/practice/two-bucket/.docs/instructions.md b/exercises/practice/two-bucket/.docs/instructions.md index 73eb450ce..30d779aa9 100644 --- a/exercises/practice/two-bucket/.docs/instructions.md +++ b/exercises/practice/two-bucket/.docs/instructions.md @@ -1,30 +1,46 @@ # Instructions -Given two buckets of different size, demonstrate how to measure an exact number of liters by strategically transferring liters of fluid between the buckets. +Given two buckets of different size and which bucket to fill first, determine how many actions are required to measure an exact number of liters by strategically transferring fluid between the buckets. -Since this mathematical problem is fairly subject to interpretation / individual approach, the tests have been written specifically to expect one overarching solution. +There are some rules that your solution must follow: -To help, the tests provide you with which bucket to fill first. That means, when starting with the larger bucket full, you are NOT allowed at any point to have the smaller bucket full and the larger bucket empty (aka, the opposite starting point); that would defeat the purpose of comparing both approaches! +- You can only do one action at a time. +- There are only 3 possible actions: + 1. Pouring one bucket into the other bucket until either: + a) the first bucket is empty + b) the second bucket is full + 2. Emptying a bucket and doing nothing to the other. + 3. Filling a bucket and doing nothing to the other. +- After an action, you may not arrive at a state where the initial starting bucket is empty and the other bucket is full. Your program will take as input: + - the size of bucket one - the size of bucket two - the desired number of liters to reach - which bucket to fill first, either bucket one or bucket two Your program should determine: -- the total number of "moves" it should take to reach the desired number of liters, including the first fill -- which bucket should end up with the desired number of liters (let's say this is bucket A) - either bucket one or bucket two -- how many liters are left in the other bucket (bucket B) -Note: any time a change is made to either or both buckets counts as one (1) move. +- the total number of actions it should take to reach the desired number of liters, including the first fill of the starting bucket +- which bucket should end up with the desired number of liters - either bucket one or bucket two +- how many liters are left in the other bucket + +Note: any time a change is made to either or both buckets counts as one (1) action. Example: -Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters. Let's say bucket one, at a given step, is holding 7 liters, and bucket two is holding 8 liters (7,8). If you empty bucket one and make no change to bucket two, leaving you with 0 liters and 8 liters respectively (0,8), that counts as one "move". Instead, if you had poured from bucket one into bucket two until bucket two was full, leaving you with 4 liters in bucket one and 11 liters in bucket two (4,11), that would count as only one "move" as well. +Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters. +Let's say at a given step, bucket one is holding 7 liters and bucket two is holding 8 liters (7,8). +If you empty bucket one and make no change to bucket two, leaving you with 0 liters and 8 liters respectively (0,8), that counts as one action. +Instead, if you had poured from bucket one into bucket two until bucket two was full, resulting in 4 liters in bucket one and 11 liters in bucket two (4,11), that would also only count as one action. + +Another Example: +Bucket one can hold 3 liters, and bucket two can hold up to 5 liters. +You are told you must start with bucket one. +So your first action is to fill bucket one. +You choose to empty bucket one for your second action. +For your third action, you may not fill bucket two, because this violates the third rule -- you may not end up in a state after any action where the starting bucket is empty and the other bucket is full. -To conclude, the only valid moves are: -- pouring from either bucket to another -- emptying either bucket and doing nothing to the other -- filling either bucket and doing nothing to the other +Written with <3 at [Fullstack Academy][fullstack] by Lindsay Levine. -Written with <3 at [Fullstack Academy](http://www.fullstackacademy.com/) by Lindsay Levine. +[fullstack]: https://www.fullstackacademy.com/ diff --git a/exercises/practice/two-bucket/.gitignore b/exercises/practice/two-bucket/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/two-bucket/.gitignore +++ b/exercises/practice/two-bucket/.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/two-bucket/.meta/config.json b/exercises/practice/two-bucket/.meta/config.json index aa046009b..8097c543f 100644 --- a/exercises/practice/two-bucket/.meta/config.json +++ b/exercises/practice/two-bucket/.meta/config.json @@ -22,7 +22,7 @@ "Cargo.toml" ], "test": [ - "tests/two-bucket.rs" + "tests/two_bucket.rs" ], "example": [ ".meta/example.rs" @@ -30,5 +30,5 @@ }, "blurb": "Given two buckets of different size, demonstrate how to measure an exact number of liters.", "source": "Water Pouring Problem", - "source_url": "http://demonstrations.wolfram.com/WaterPouringProblem/" + "source_url": "https://demonstrations.wolfram.com/WaterPouringProblem/" } diff --git a/exercises/practice/two-bucket/.meta/test_template.tera b/exercises/practice/two-bucket/.meta/test_template.tera new file mode 100644 index 000000000..959791f8b --- /dev/null +++ b/exercises/practice/two-bucket/.meta/test_template.tera @@ -0,0 +1,30 @@ +use two_bucket::*; + +{% macro bucket(label) -%} + {% if label == 'one' -%} + Bucket::One + {%- else -%} + Bucket::Two + {%- endif %} +{%- endmacro bucket -%} + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let output = solve( + {{ test.input.bucketOne }}, {{ test.input.bucketTwo }}, {{ test.input.goal }}, + &{{ self::bucket(label=test.input.startBucket) }}, + ); + let expected = {% if 'error' in test.expected -%} + None + {%- else -%} + Some(BucketStats { + moves: {{ test.expected.moves }}, + goal_bucket: {{ self::bucket(label=test.expected.goalBucket) }}, + other_bucket: {{ test.expected.otherBucket }}, + }) + {%- endif %}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/two-bucket/.meta/tests.toml b/exercises/practice/two-bucket/.meta/tests.toml index be690e975..a3fe533ec 100644 --- a/exercises/practice/two-bucket/.meta/tests.toml +++ b/exercises/practice/two-bucket/.meta/tests.toml @@ -1,3 +1,43 @@ -# 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. + +[a6f2b4ba-065f-4dca-b6f0-e3eee51cb661] +description = "Measure using bucket one of size 3 and bucket two of size 5 - start with bucket one" + +[6c4ea451-9678-4926-b9b3-68364e066d40] +description = "Measure using bucket one of size 3 and bucket two of size 5 - start with bucket two" + +[3389f45e-6a56-46d5-9607-75aa930502ff] +description = "Measure using bucket one of size 7 and bucket two of size 11 - start with bucket one" + +[fe0ff9a0-3ea5-4bf7-b17d-6d4243961aa1] +description = "Measure using bucket one of size 7 and bucket two of size 11 - start with bucket two" + +[0ee1f57e-da84-44f7-ac91-38b878691602] +description = "Measure one step using bucket one of size 1 and bucket two of size 3 - start with bucket two" + +[eb329c63-5540-4735-b30b-97f7f4df0f84] +description = "Measure using bucket one of size 2 and bucket two of size 3 - start with bucket one and end with bucket two" + +[58d70152-bf2b-46bb-ad54-be58ebe94c03] +description = "Measure using bucket one much bigger than bucket two" + +[9dbe6499-caa5-4a58-b5ce-c988d71b8981] +description = "Measure using bucket one much smaller than bucket two" + +[449be72d-b10a-4f4b-a959-ca741e333b72] +description = "Not possible to reach the goal" + +[aac38b7a-77f4-4d62-9b91-8846d533b054] +description = "With the same buckets but a different goal, then it is possible" + +[74633132-0ccf-49de-8450-af4ab2e3b299] +description = "Goal larger than both buckets is impossible" diff --git a/exercises/practice/two-bucket/Cargo.toml b/exercises/practice/two-bucket/Cargo.toml index 9f578e2aa..4feb06478 100644 --- a/exercises/practice/two-bucket/Cargo.toml +++ b/exercises/practice/two-bucket/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "two-bucket" -version = "1.4.0" +name = "two_bucket" +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/two-bucket/src/lib.rs b/exercises/practice/two-bucket/src/lib.rs index 801d4c382..ae20764d7 100644 --- a/exercises/practice/two-bucket/src/lib.rs +++ b/exercises/practice/two-bucket/src/lib.rs @@ -23,7 +23,7 @@ pub fn solve( goal: u8, start_bucket: &Bucket, ) -> Option { - unimplemented!( + todo!( "Given one bucket of capacity {capacity_1}, another of capacity {capacity_2}, starting with {start_bucket:?}, find pours to reach {goal}, or None if impossible" ); } diff --git a/exercises/practice/two-bucket/tests/two-bucket.rs b/exercises/practice/two-bucket/tests/two-bucket.rs deleted file mode 100644 index 397195aed..000000000 --- a/exercises/practice/two-bucket/tests/two-bucket.rs +++ /dev/null @@ -1,97 +0,0 @@ -use two_bucket::{solve, Bucket, BucketStats}; - -#[test] -fn test_case_1() { - assert_eq!( - solve(3, 5, 1, &Bucket::One), - Some(BucketStats { - moves: 4, - goal_bucket: Bucket::One, - other_bucket: 5, - }) - ); -} - -#[test] -#[ignore] -fn test_case_2() { - assert_eq!( - solve(3, 5, 1, &Bucket::Two), - Some(BucketStats { - moves: 8, - goal_bucket: Bucket::Two, - other_bucket: 3, - }) - ); -} - -#[test] -#[ignore] -fn test_case_3() { - assert_eq!( - solve(7, 11, 2, &Bucket::One), - Some(BucketStats { - moves: 14, - goal_bucket: Bucket::One, - other_bucket: 11, - }) - ); -} - -#[test] -#[ignore] -fn test_case_4() { - assert_eq!( - solve(7, 11, 2, &Bucket::Two), - Some(BucketStats { - moves: 18, - goal_bucket: Bucket::Two, - other_bucket: 7, - }) - ); -} - -#[test] -#[ignore] -fn goal_equal_to_start_bucket() { - assert_eq!( - solve(1, 3, 3, &Bucket::Two), - Some(BucketStats { - moves: 1, - goal_bucket: Bucket::Two, - other_bucket: 0, - }) - ); -} - -#[test] -#[ignore] -fn goal_equal_to_other_bucket() { - assert_eq!( - solve(2, 3, 3, &Bucket::One), - Some(BucketStats { - moves: 2, - goal_bucket: Bucket::Two, - other_bucket: 2, - }) - ); -} - -#[test] -#[ignore] -fn not_possible_to_reach_the_goal() { - assert_eq!(solve(6, 15, 5, &Bucket::One), None); -} - -#[test] -#[ignore] -fn with_same_buckets_but_different_goal_then_it_is_possible() { - assert_eq!( - solve(6, 15, 9, &Bucket::One), - Some(BucketStats { - moves: 10, - goal_bucket: Bucket::Two, - other_bucket: 0, - }) - ); -} diff --git a/exercises/practice/two-bucket/tests/two_bucket.rs b/exercises/practice/two-bucket/tests/two_bucket.rs new file mode 100644 index 000000000..f5ae69108 --- /dev/null +++ b/exercises/practice/two-bucket/tests/two_bucket.rs @@ -0,0 +1,125 @@ +use two_bucket::*; + +#[test] +fn measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_one() { + let output = solve(3, 5, 1, &Bucket::One); + let expected = Some(BucketStats { + moves: 4, + goal_bucket: Bucket::One, + other_bucket: 5, + }); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_two() { + let output = solve(3, 5, 1, &Bucket::Two); + let expected = Some(BucketStats { + moves: 8, + goal_bucket: Bucket::Two, + other_bucket: 3, + }); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucket_one() { + let output = solve(7, 11, 2, &Bucket::One); + let expected = Some(BucketStats { + moves: 14, + goal_bucket: Bucket::One, + other_bucket: 11, + }); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucket_two() { + let output = solve(7, 11, 2, &Bucket::Two); + let expected = Some(BucketStats { + moves: 18, + goal_bucket: Bucket::Two, + other_bucket: 7, + }); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn measure_one_step_using_bucket_one_of_size_1_and_bucket_two_of_size_3_start_with_bucket_two() { + let output = solve(1, 3, 3, &Bucket::Two); + let expected = Some(BucketStats { + moves: 1, + goal_bucket: Bucket::Two, + other_bucket: 0, + }); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn measure_using_bucket_one_of_size_2_and_bucket_two_of_size_3_start_with_bucket_one_and_end_with_bucket_two() + { + let output = solve(2, 3, 3, &Bucket::One); + let expected = Some(BucketStats { + moves: 2, + goal_bucket: Bucket::Two, + other_bucket: 2, + }); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn measure_using_bucket_one_much_bigger_than_bucket_two() { + let output = solve(5, 1, 2, &Bucket::One); + let expected = Some(BucketStats { + moves: 6, + goal_bucket: Bucket::One, + other_bucket: 1, + }); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn measure_using_bucket_one_much_smaller_than_bucket_two() { + let output = solve(3, 15, 9, &Bucket::One); + let expected = Some(BucketStats { + moves: 6, + goal_bucket: Bucket::Two, + other_bucket: 0, + }); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn not_possible_to_reach_the_goal() { + let output = solve(6, 15, 5, &Bucket::One); + let expected = None; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn with_the_same_buckets_but_a_different_goal_then_it_is_possible() { + let output = solve(6, 15, 9, &Bucket::One); + let expected = Some(BucketStats { + moves: 10, + goal_bucket: Bucket::Two, + other_bucket: 0, + }); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn goal_larger_than_both_buckets_is_impossible() { + let output = solve(5, 7, 8, &Bucket::One); + let expected = None; + assert_eq!(output, expected); +} diff --git a/exercises/practice/two-fer/.docs/instructions.md b/exercises/practice/two-fer/.docs/instructions.md index f4853c54d..adc534879 100644 --- a/exercises/practice/two-fer/.docs/instructions.md +++ b/exercises/practice/two-fer/.docs/instructions.md @@ -1,16 +1,14 @@ # Instructions -`Two-fer` or `2-fer` is short for two for one. One for you and one for me. +Your task is to determine what you will say as you give away the extra cookie. -Given a name, return a string with the message: +If you know the person's name (e.g. if they're named Do-yun), then you will say: ```text -One for name, one for me. +One for Do-yun, one for me. ``` -Where "name" is the given name. - -However, if the name is missing, return the string: +If you don't know the person's name, you will say _you_ instead. ```text One for you, one for me. @@ -18,9 +16,9 @@ One for you, one for me. Here are some examples: -|Name |String to return -|:-------|:------------------ -|Alice |One for Alice, one for me. -|Bob |One for Bob, one for me. -| |One for you, one for me. -|Zaphod |One for Zaphod, one for me. +| Name | Dialogue | +| :----- | :-------------------------- | +| Alice | One for Alice, one for me. | +| Bohdan | One for Bohdan, one for me. | +| | One for you, one for me. | +| Zaphod | One for Zaphod, one for me. | diff --git a/exercises/practice/two-fer/.docs/introduction.md b/exercises/practice/two-fer/.docs/introduction.md new file mode 100644 index 000000000..5947a2230 --- /dev/null +++ b/exercises/practice/two-fer/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +In some English accents, when you say "two for" quickly, it sounds like "two fer". +Two-for-one is a way of saying that if you buy one, you also get one for free. +So the phrase "two-fer" often implies a two-for-one offer. + +Imagine a bakery that has a holiday offer where you can buy two cookies for the price of one ("two-fer one!"). +You take the offer and (very generously) decide to give the extra cookie to someone else in the queue. diff --git a/exercises/practice/two-fer/.gitignore b/exercises/practice/two-fer/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/two-fer/.gitignore +++ b/exercises/practice/two-fer/.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/two-fer/.meta/config.json b/exercises/practice/two-fer/.meta/config.json index fb32da3a8..7b8e0d7de 100644 --- a/exercises/practice/two-fer/.meta/config.json +++ b/exercises/practice/two-fer/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/two-fer.rs" + "tests/two_fer.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/two-fer/.meta/example.rs b/exercises/practice/two-fer/.meta/example.rs index 7b4221dd6..145b4ad15 100644 --- a/exercises/practice/two-fer/.meta/example.rs +++ b/exercises/practice/two-fer/.meta/example.rs @@ -1,6 +1,6 @@ pub fn twofer(name: &str) -> String { match name { "" => "One for you, one for me.".to_string(), - _ => format!("One for {}, one for me.", name), + _ => format!("One for {name}, one for me."), } } diff --git a/exercises/practice/two-fer/Cargo.toml b/exercises/practice/two-fer/Cargo.toml index c8075f3e9..31941433a 100644 --- a/exercises/practice/two-fer/Cargo.toml +++ b/exercises/practice/two-fer/Cargo.toml @@ -1,6 +1,9 @@ [package] -edition = "2021" name = "twofer" -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] diff --git a/exercises/practice/two-fer/src/lib.rs b/exercises/practice/two-fer/src/lib.rs index 1c8494223..f7993705b 100644 --- a/exercises/practice/two-fer/src/lib.rs +++ b/exercises/practice/two-fer/src/lib.rs @@ -1,3 +1,3 @@ pub fn twofer(name: &str) -> String { - unimplemented!("how many for {name}") + todo!("how many for {name}") } diff --git a/exercises/practice/two-fer/tests/two-fer.rs b/exercises/practice/two-fer/tests/two_fer.rs similarity index 100% rename from exercises/practice/two-fer/tests/two-fer.rs rename to exercises/practice/two-fer/tests/two_fer.rs diff --git a/exercises/practice/variable-length-quantity/.docs/instructions.md b/exercises/practice/variable-length-quantity/.docs/instructions.md index eadce28d0..501254826 100644 --- a/exercises/practice/variable-length-quantity/.docs/instructions.md +++ b/exercises/practice/variable-length-quantity/.docs/instructions.md @@ -2,10 +2,10 @@ Implement variable length quantity encoding and decoding. -The goal of this exercise is to implement [VLQ](https://en.wikipedia.org/wiki/Variable-length_quantity) encoding/decoding. +The goal of this exercise is to implement [VLQ][vlq] encoding/decoding. In short, the goal of this encoding is to encode integer values in a way that would save bytes. -Only the first 7 bits of each byte is significant (right-justified; sort of like an ASCII byte). +Only the first 7 bits of each byte are significant (right-justified; sort of like an ASCII byte). So, if you have a 32-bit value, you have to unpack it into a series of 7-bit bytes. Of course, you will have a variable number of bytes depending upon your integer. To indicate which is the last byte of the series, you leave bit #7 clear. @@ -30,3 +30,5 @@ Here are examples of integers as 32-bit values, and the variable length quantiti 08000000 C0 80 80 00 0FFFFFFF FF FF FF 7F ``` + +[vlq]: https://en.wikipedia.org/wiki/Variable-length_quantity diff --git a/exercises/practice/variable-length-quantity/.gitignore b/exercises/practice/variable-length-quantity/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/variable-length-quantity/.gitignore +++ b/exercises/practice/variable-length-quantity/.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/variable-length-quantity/.meta/config.json b/exercises/practice/variable-length-quantity/.meta/config.json index 3051dc7a9..fcaf18202 100644 --- a/exercises/practice/variable-length-quantity/.meta/config.json +++ b/exercises/practice/variable-length-quantity/.meta/config.json @@ -26,7 +26,7 @@ "Cargo.toml" ], "test": [ - "tests/variable-length-quantity.rs" + "tests/variable_length_quantity.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/variable-length-quantity/.meta/test_template.tera b/exercises/practice/variable-length-quantity/.meta/test_template.tera new file mode 100644 index 000000000..03131daca --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/test_template.tera @@ -0,0 +1,38 @@ +use variable_length_quantity as vlq; + +{% for test_group in cases %} +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { +{%- if test.property == "encode" %} + let input = &[{% for integer in test.input.integers -%} + {{ integer | fmt_num }}, + {%- endfor %}]; + let output = vlq::to_bytes(input); + let expected = vec![ + {%- for byte in test.expected -%} + 0x{{ byte | to_hex }}, + {%- endfor -%} + ]; +{%- elif test.property == "decode" %} + let input = &[ + {%- for byte in test.input.integers -%} + 0x{{ byte | to_hex }}, + {%- endfor -%} + ]; + let output = vlq::from_bytes(input); + let expected = {% if test.expected is object -%} + Err(vlq::Error::IncompleteNumber) + {%- else -%} + Ok(vec![{% for integer in test.expected -%} + {{ integer | fmt_num }}, + {%- endfor %}]) + {%- endif %}; +{%- else %} + panic!("unknown property: {{ test.property }}"); +{%- endif %} + assert_eq!(output, expected); +} +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/variable-length-quantity/.meta/tests.toml b/exercises/practice/variable-length-quantity/.meta/tests.toml index be690e975..53be789a3 100644 --- a/exercises/practice/variable-length-quantity/.meta/tests.toml +++ b/exercises/practice/variable-length-quantity/.meta/tests.toml @@ -1,3 +1,103 @@ -# 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. + +[35c9db2e-f781-4c52-b73b-8e76427defd0] +description = "Encode a series of integers, producing a series of bytes. -> zero" + +[be44d299-a151-4604-a10e-d4b867f41540] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary single byte" + +[890bc344-cb80-45af-b316-6806a6971e81] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric single byte" + +[ea399615-d274-4af6-bbef-a1c23c9e1346] +description = "Encode a series of integers, producing a series of bytes. -> largest single byte" + +[77b07086-bd3f-4882-8476-8dcafee79b1c] +description = "Encode a series of integers, producing a series of bytes. -> smallest double byte" + +[63955a49-2690-4e22-a556-0040648d6b2d] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary double byte" + +[4977d113-251b-4d10-a3ad-2f5a7756bb58] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric double byte" + +[29da7031-0067-43d3-83a7-4f14b29ed97a] +description = "Encode a series of integers, producing a series of bytes. -> largest double byte" + +[3345d2e3-79a9-4999-869e-d4856e3a8e01] +description = "Encode a series of integers, producing a series of bytes. -> smallest triple byte" + +[5df0bc2d-2a57-4300-a653-a75ee4bd0bee] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary triple byte" + +[6731045f-1e00-4192-b5ae-98b22e17e9f7] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric triple byte" + +[f51d8539-312d-4db1-945c-250222c6aa22] +description = "Encode a series of integers, producing a series of bytes. -> largest triple byte" + +[da78228b-544f-47b7-8bfe-d16b35bbe570] +description = "Encode a series of integers, producing a series of bytes. -> smallest quadruple byte" + +[11ed3469-a933-46f1-996f-2231e05d7bb6] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary quadruple byte" + +[b45ef770-cbba-48c2-bd3c-c6362679516e] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric quadruple byte" + +[d5f3f3c3-e0f1-4e7f-aad0-18a44f223d1c] +description = "Encode a series of integers, producing a series of bytes. -> largest quadruple byte" + +[91a18b33-24e7-4bfb-bbca-eca78ff4fc47] +description = "Encode a series of integers, producing a series of bytes. -> smallest quintuple byte" + +[5f34ff12-2952-4669-95fe-2d11b693d331] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary quintuple byte" + +[9be46731-7cd5-415c-b960-48061cbc1154] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric quintuple byte" + +[7489694b-88c3-4078-9864-6fe802411009] +description = "Encode a series of integers, producing a series of bytes. -> maximum 32-bit integer input" + +[f9b91821-cada-4a73-9421-3c81d6ff3661] +description = "Encode a series of integers, producing a series of bytes. -> two single-byte values" + +[68694449-25d2-4974-ba75-fa7bb36db212] +description = "Encode a series of integers, producing a series of bytes. -> two multi-byte values" + +[51a06b5c-de1b-4487-9a50-9db1b8930d85] +description = "Encode a series of integers, producing a series of bytes. -> many multi-byte values" + +[baa73993-4514-4915-bac0-f7f585e0e59a] +description = "Decode a series of bytes, producing a series of integers. -> one byte" + +[72e94369-29f9-46f2-8c95-6c5b7a595aee] +description = "Decode a series of bytes, producing a series of integers. -> two bytes" + +[df5a44c4-56f7-464e-a997-1db5f63ce691] +description = "Decode a series of bytes, producing a series of integers. -> three bytes" + +[1bb58684-f2dc-450a-8406-1f3452aa1947] +description = "Decode a series of bytes, producing a series of integers. -> four bytes" + +[cecd5233-49f1-4dd1-a41a-9840a40f09cd] +description = "Decode a series of bytes, producing a series of integers. -> maximum 32-bit integer" + +[e7d74ba3-8b8e-4bcb-858d-d08302e15695] +description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error" + +[aa378291-9043-4724-bc53-aca1b4a3fcb6] +description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error, even if value is zero" + +[a91e6f5a-c64a-48e3-8a75-ce1a81e0ebee] +description = "Decode a series of bytes, producing a series of integers. -> multiple values" diff --git a/exercises/practice/variable-length-quantity/Cargo.toml b/exercises/practice/variable-length-quantity/Cargo.toml index 6986c0c89..0b8caf3f8 100644 --- a/exercises/practice/variable-length-quantity/Cargo.toml +++ b/exercises/practice/variable-length-quantity/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "variable-length-quantity" -version = "1.2.0" +name = "variable_length_quantity" +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/variable-length-quantity/src/lib.rs b/exercises/practice/variable-length-quantity/src/lib.rs index f60834668..15f55c68c 100644 --- a/exercises/practice/variable-length-quantity/src/lib.rs +++ b/exercises/practice/variable-length-quantity/src/lib.rs @@ -1,15 +1,14 @@ #[derive(Debug, PartialEq, Eq)] pub enum Error { IncompleteNumber, - Overflow, } /// Convert a list of numbers to a stream of bytes encoded with variable length encoding. pub fn to_bytes(values: &[u32]) -> Vec { - unimplemented!("Convert the values {values:?} to a list of bytes") + todo!("Convert the values {values:?} to a list of bytes") } /// Given a stream of bytes, extract all numbers which are encoded in there. pub fn from_bytes(bytes: &[u8]) -> Result, Error> { - unimplemented!("Convert the list of bytes {bytes:?} to a list of numbers") + todo!("Convert the list of bytes {bytes:?} to a list of numbers") } diff --git a/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs b/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs deleted file mode 100644 index b2e3e7e29..000000000 --- a/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs +++ /dev/null @@ -1,134 +0,0 @@ -use variable_length_quantity as vlq; - -#[test] -fn to_single_byte() { - assert_eq!(&[0x00], vlq::to_bytes(&[0x00]).as_slice()); - assert_eq!(&[0x40], vlq::to_bytes(&[0x40]).as_slice()); - assert_eq!(&[0x7f], vlq::to_bytes(&[0x7f]).as_slice()); -} - -#[test] -#[ignore] -fn to_double_byte() { - assert_eq!(&[0x81, 0x00], vlq::to_bytes(&[0x80]).as_slice()); - assert_eq!(&[0xc0, 0x00], vlq::to_bytes(&[0x2000]).as_slice()); - assert_eq!(&[0xff, 0x7f], vlq::to_bytes(&[0x3fff]).as_slice()); -} - -#[test] -#[ignore] -fn to_triple_byte() { - assert_eq!(&[0x81, 0x80, 0x00], vlq::to_bytes(&[0x4000]).as_slice()); - assert_eq!(&[0xc0, 0x80, 0x00], vlq::to_bytes(&[0x10_0000]).as_slice()); - assert_eq!(&[0xff, 0xff, 0x7f], vlq::to_bytes(&[0x1f_ffff]).as_slice()); -} - -#[test] -#[ignore] -fn to_quadruple_byte() { - assert_eq!( - &[0x81, 0x80, 0x80, 0x00], - vlq::to_bytes(&[0x20_0000]).as_slice() - ); - assert_eq!( - &[0xc0, 0x80, 0x80, 0x00], - vlq::to_bytes(&[0x0800_0000]).as_slice() - ); - assert_eq!( - &[0xff, 0xff, 0xff, 0x7f], - vlq::to_bytes(&[0x0fff_ffff]).as_slice() - ); -} - -#[test] -#[ignore] -fn to_quintuple_byte() { - assert_eq!( - &[0x81, 0x80, 0x80, 0x80, 0x00], - vlq::to_bytes(&[0x1000_0000]).as_slice() - ); - assert_eq!( - &[0x8f, 0xf8, 0x80, 0x80, 0x00], - vlq::to_bytes(&[0xff00_0000]).as_slice() - ); - assert_eq!( - &[0x8f, 0xff, 0xff, 0xff, 0x7f], - vlq::to_bytes(&[0xffff_ffff]).as_slice() - ); -} - -#[test] -#[ignore] -fn from_bytes() { - assert_eq!(Ok(vec![0x7f]), vlq::from_bytes(&[0x7f])); - assert_eq!(Ok(vec![0x2000]), vlq::from_bytes(&[0xc0, 0x00])); - assert_eq!(Ok(vec![0x1f_ffff]), vlq::from_bytes(&[0xff, 0xff, 0x7f])); - assert_eq!( - Ok(vec![0x20_0000]), - vlq::from_bytes(&[0x81, 0x80, 0x80, 0x00]) - ); - assert_eq!( - Ok(vec![0xffff_ffff]), - vlq::from_bytes(&[0x8f, 0xff, 0xff, 0xff, 0x7f]) - ); -} - -#[test] -#[ignore] -fn to_bytes_multiple_values() { - assert_eq!(&[0x40, 0x7f], vlq::to_bytes(&[0x40, 0x7f]).as_slice()); - assert_eq!( - &[0x81, 0x80, 0x00, 0xc8, 0xe8, 0x56], - vlq::to_bytes(&[0x4000, 0x12_3456]).as_slice() - ); - assert_eq!( - &[ - 0xc0, 0x00, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xff, 0x7f, 0x81, 0x80, - 0x00, - ], - vlq::to_bytes(&[0x2000, 0x12_3456, 0x0fff_ffff, 0x00, 0x3fff, 0x4000]).as_slice() - ); -} - -#[test] -#[ignore] -fn from_bytes_multiple_values() { - assert_eq!( - Ok(vec![0x2000, 0x12_3456, 0x0fff_ffff, 0x00, 0x3fff, 0x4000]), - vlq::from_bytes(&[ - 0xc0, 0x00, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xff, 0x7f, 0x81, 0x80, - 0x00, - ]) - ); -} - -#[test] -#[ignore] -fn incomplete_byte_sequence() { - assert_eq!(Err(vlq::Error::IncompleteNumber), vlq::from_bytes(&[0xff])); -} - -#[test] -#[ignore] -fn zero_incomplete_byte_sequence() { - assert_eq!(Err(vlq::Error::IncompleteNumber), vlq::from_bytes(&[0x80])); -} - -#[test] -#[ignore] -fn overflow_u32() { - assert_eq!( - Err(vlq::Error::Overflow), - vlq::from_bytes(&[0xff, 0xff, 0xff, 0xff, 0x7f]) - ); -} - -#[test] -#[ignore] -fn chained_execution_is_identity() { - let test = &[0xf2, 0xf6, 0x96, 0x9c, 0x3b, 0x39, 0x2e, 0x30, 0xb3, 0x24]; - assert_eq!( - Ok(Vec::from(&test[..])), - vlq::from_bytes(&vlq::to_bytes(test)) - ); -} diff --git a/exercises/practice/variable-length-quantity/tests/variable_length_quantity.rs b/exercises/practice/variable-length-quantity/tests/variable_length_quantity.rs new file mode 100644 index 000000000..b1b4e762d --- /dev/null +++ b/exercises/practice/variable-length-quantity/tests/variable_length_quantity.rs @@ -0,0 +1,283 @@ +use variable_length_quantity as vlq; + +#[test] +fn zero() { + let input = &[0]; + let output = vlq::to_bytes(input); + let expected = vec![0x0]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn arbitrary_single_byte() { + let input = &[64]; + let output = vlq::to_bytes(input); + let expected = vec![0x40]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn asymmetric_single_byte() { + let input = &[83]; + let output = vlq::to_bytes(input); + let expected = vec![0x53]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn largest_single_byte() { + let input = &[127]; + let output = vlq::to_bytes(input); + let expected = vec![0x7f]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_double_byte() { + let input = &[128]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x0]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn arbitrary_double_byte() { + let input = &[8_192]; + let output = vlq::to_bytes(input); + let expected = vec![0xc0, 0x0]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn asymmetric_double_byte() { + let input = &[173]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x2d]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn largest_double_byte() { + let input = &[16_383]; + let output = vlq::to_bytes(input); + let expected = vec![0xff, 0x7f]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_triple_byte() { + let input = &[16_384]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x80, 0x0]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn arbitrary_triple_byte() { + let input = &[1_048_576]; + let output = vlq::to_bytes(input); + let expected = vec![0xc0, 0x80, 0x0]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn asymmetric_triple_byte() { + let input = &[120_220]; + let output = vlq::to_bytes(input); + let expected = vec![0x87, 0xab, 0x1c]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn largest_triple_byte() { + let input = &[2_097_151]; + let output = vlq::to_bytes(input); + let expected = vec![0xff, 0xff, 0x7f]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_quadruple_byte() { + let input = &[2_097_152]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x80, 0x80, 0x0]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn arbitrary_quadruple_byte() { + let input = &[134_217_728]; + let output = vlq::to_bytes(input); + let expected = vec![0xc0, 0x80, 0x80, 0x0]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn asymmetric_quadruple_byte() { + let input = &[3_503_876]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0xd5, 0xee, 0x4]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn largest_quadruple_byte() { + let input = &[268_435_455]; + let output = vlq::to_bytes(input); + let expected = vec![0xff, 0xff, 0xff, 0x7f]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_quintuple_byte() { + let input = &[268_435_456]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x80, 0x80, 0x80, 0x0]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn arbitrary_quintuple_byte() { + let input = &[4_278_190_080]; + let output = vlq::to_bytes(input); + let expected = vec![0x8f, 0xf8, 0x80, 0x80, 0x0]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn asymmetric_quintuple_byte() { + let input = &[2_254_790_917]; + let output = vlq::to_bytes(input); + let expected = vec![0x88, 0xb3, 0x95, 0xc2, 0x5]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn maximum_32_bit_integer_input() { + let input = &[4_294_967_295]; + let output = vlq::to_bytes(input); + let expected = vec![0x8f, 0xff, 0xff, 0xff, 0x7f]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn two_single_byte_values() { + let input = &[64, 127]; + let output = vlq::to_bytes(input); + let expected = vec![0x40, 0x7f]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn two_multi_byte_values() { + let input = &[16_384, 1_193_046]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x80, 0x0, 0xc8, 0xe8, 0x56]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn many_multi_byte_values() { + let input = &[8_192, 1_193_046, 268_435_455, 0, 16_383, 16_384]; + let output = vlq::to_bytes(input); + let expected = vec![ + 0xc0, 0x0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x0, 0xff, 0x7f, 0x81, 0x80, 0x0, + ]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn one_byte() { + let input = &[0x7f]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![127]); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn two_bytes() { + let input = &[0xc0, 0x0]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![8_192]); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn three_bytes() { + let input = &[0xff, 0xff, 0x7f]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![2_097_151]); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn four_bytes() { + let input = &[0x81, 0x80, 0x80, 0x0]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![2_097_152]); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn maximum_32_bit_integer() { + let input = &[0x8f, 0xff, 0xff, 0xff, 0x7f]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![4_294_967_295]); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn incomplete_sequence_causes_error() { + let input = &[0xff]; + let output = vlq::from_bytes(input); + let expected = Err(vlq::Error::IncompleteNumber); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn incomplete_sequence_causes_error_even_if_value_is_zero() { + let input = &[0x80]; + let output = vlq::from_bytes(input); + let expected = Err(vlq::Error::IncompleteNumber); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn multiple_values() { + let input = &[ + 0xc0, 0x0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x0, 0xff, 0x7f, 0x81, 0x80, 0x0, + ]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![8_192, 1_193_046, 268_435_455, 0, 16_383, 16_384]); + assert_eq!(output, expected); +} diff --git a/exercises/practice/word-count/.docs/instructions.md b/exercises/practice/word-count/.docs/instructions.md index 1221efd38..064393c8a 100644 --- a/exercises/practice/word-count/.docs/instructions.md +++ b/exercises/practice/word-count/.docs/instructions.md @@ -1,40 +1,47 @@ # Instructions -Given a phrase, count the occurrences of each _word_ in that phrase. +Your task is to count how many times each word occurs in a subtitle of a drama. -For the purposes of this exercise you can expect that a _word_ will always be one of: +The subtitles from these dramas use only ASCII characters. -1. A _number_ composed of one or more ASCII digits (i.e. "0" or "1234") OR -2. A _simple word_ composed of one or more ASCII letters (i.e. "a" or "they") OR -3. A _contraction_ of two _simple words_ joined by a single apostrophe (i.e. "it's" or "they're") +The characters often speak in casual English, using contractions like _they're_ or _it's_. +Though these contractions come from two words (e.g. _we are_), the contraction (_we're_) is considered a single word. -When counting words you can assume the following rules: +Words can be separated by any form of punctuation (e.g. ":", "!", or "?") or whitespace (e.g. "\t", "\n", or " "). +The only punctuation that does not separate words is the apostrophe in contractions. -1. The count is _case insensitive_ (i.e. "You", "you", and "YOU" are 3 uses of the same word) -2. The count is _unordered_; the tests will ignore how words and counts are ordered -3. Other than the apostrophe in a _contraction_ all forms of _punctuation_ are ignored -4. The words can be separated by _any_ form of whitespace (i.e. "\t", "\n", " "), or - external punctuation. +Numbers are considered words. +If the subtitles say _It costs 100 dollars._ then _100_ will be its own word. -For example, for the phrase `"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` the count would be: +Words are case insensitive. +For example, the word _you_ occurs three times in the following sentence: + +> You come back, you hear me? DO YOU HEAR ME? + +The ordering of the word counts in the results doesn't matter. + +Here's an example that incorporates several of the elements discussed above: + +- simple words +- contractions +- numbers +- case insensitive words +- punctuation (including apostrophes) to separate words +- different forms of whitespace to separate words + +`"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` + +The mapping for this subtitle would be: ```text -that's: 1 -the: 2 -password: 2 123: 1 -cried: 1 -special: 1 agent: 1 -so: 1 -i: 1 +cried: 1 fled: 1 -``` - -For the phrase `"one,two,three"` the count would be: - -```text -one: 1 -two: 1 -three: 1 +i: 1 +password: 2 +so: 1 +special: 1 +that's: 1 +the: 2 ``` diff --git a/exercises/practice/word-count/.docs/introduction.md b/exercises/practice/word-count/.docs/introduction.md new file mode 100644 index 000000000..1654508e7 --- /dev/null +++ b/exercises/practice/word-count/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You teach English as a foreign language to high school students. + +You've decided to base your entire curriculum on TV shows. +You need to analyze which words are used, and how often they're repeated. + +This will let you choose the simplest shows to start with, and to gradually increase the difficulty as time passes. diff --git a/exercises/practice/word-count/.gitignore b/exercises/practice/word-count/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/word-count/.gitignore +++ b/exercises/practice/word-count/.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/word-count/.meta/config.json b/exercises/practice/word-count/.meta/config.json index 025f8d001..61b4d1903 100644 --- a/exercises/practice/word-count/.meta/config.json +++ b/exercises/practice/word-count/.meta/config.json @@ -34,7 +34,7 @@ "Cargo.toml" ], "test": [ - "tests/word-count.rs" + "tests/word_count.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/word-count/.meta/example.rs b/exercises/practice/word-count/.meta/example.rs index 7e78fa1fd..eb1d80942 100644 --- a/exercises/practice/word-count/.meta/example.rs +++ b/exercises/practice/word-count/.meta/example.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; -pub fn word_count(input: &str) -> HashMap { - let mut map: HashMap = HashMap::new(); +pub fn word_count(input: &str) -> HashMap { + let mut map = HashMap::new(); let lower = input.to_lowercase(); let slice: &str = lower.as_ref(); for word in slice .split(|c: char| !c.is_alphanumeric() && c != '\'') - .filter(|s| !s.is_empty()) .map(|s| s.trim_matches('\'')) + .filter(|s| !s.is_empty()) { *map.entry(word.to_string()).or_insert(0) += 1; } diff --git a/exercises/practice/word-count/.meta/test_template.tera b/exercises/practice/word-count/.meta/test_template.tera new file mode 100644 index 000000000..931086036 --- /dev/null +++ b/exercises/practice/word-count/.meta/test_template.tera @@ -0,0 +1,24 @@ +use word_count::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input.sentence | json_encode() }}; + let mut output = word_count(input); + let expected = [{% for key, value in test.expected -%} + ({{ key | json_encode() }}, {{ value }}), + {%- endfor %}]; + {#- + The reason for the awkward code in here is to ensure that the failure + message for assert_eq! is as informative as possible. A simpler + solution would simply check the length of the map, and then + check for the presence and value of each key in the given pairs vector. + #} + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + {#- may fail with a message that clearly shows all extra pairs in the map #} + assert_eq!(output.into_iter().collect::>(), vec![]); +} +{% endfor -%} diff --git a/exercises/practice/word-count/.meta/tests.toml b/exercises/practice/word-count/.meta/tests.toml index 46d1b6fa5..1be425b33 100644 --- a/exercises/practice/word-count/.meta/tests.toml +++ b/exercises/practice/word-count/.meta/tests.toml @@ -1,12 +1,57 @@ -# 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. + +[61559d5f-2cad-48fb-af53-d3973a9ee9ef] +description = "count one word" + +[5abd53a3-1aed-43a4-a15a-29f88c09cbbd] +description = "count one of each word" + +[2a3091e5-952e-4099-9fac-8f85d9655c0e] +description = "multiple occurrences of a word" + +[e81877ae-d4da-4af4-931c-d923cd621ca6] +description = "handles cramped lists" + +[7349f682-9707-47c0-a9af-be56e1e7ff30] +description = "handles expanded lists" + +[a514a0f2-8589-4279-8892-887f76a14c82] +description = "ignore punctuation" + +[d2e5cee6-d2ec-497b-bdc9-3ebe092ce55e] +description = "include numbers" + +[dac6bc6a-21ae-4954-945d-d7f716392dbf] +description = "normalize case" [4185a902-bdb0-4074-864c-f416e42a0f19] description = "with apostrophes" +include = false + +[4ff6c7d7-fcfc-43ef-b8e7-34ff1837a2d3] +description = "with apostrophes" +reimplements = "4185a902-bdb0-4074-864c-f416e42a0f19" [be72af2b-8afe-4337-b151-b297202e4a7b] description = "with quotations" +[8d6815fe-8a51-4a65-96f9-2fb3f6dc6ed6] +description = "substrings from the beginning" + [c5f4ef26-f3f7-4725-b314-855c04fb4c13] description = "multiple spaces not detected as a word" + +[50176e8a-fe8e-4f4c-b6b6-aa9cf8f20360] +description = "alternating word separators not detected as a word" + +[6d00f1db-901c-4bec-9829-d20eb3044557] +description = "quotation for word with apostrophe" diff --git a/exercises/practice/word-count/Cargo.toml b/exercises/practice/word-count/Cargo.toml index c2fab6f70..7ac73abec 100644 --- a/exercises/practice/word-count/Cargo.toml +++ b/exercises/practice/word-count/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "word-count" -version = "1.2.0" +name = "word_count" +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/word-count/src/lib.rs b/exercises/practice/word-count/src/lib.rs index 62d0cad60..a0e9034ef 100644 --- a/exercises/practice/word-count/src/lib.rs +++ b/exercises/practice/word-count/src/lib.rs @@ -2,5 +2,5 @@ use std::collections::HashMap; /// Count occurrences of words. pub fn word_count(words: &str) -> HashMap { - unimplemented!("Count of occurrences of words in {words:?}"); + todo!("Count of occurrences of words in {words:?}"); } diff --git a/exercises/practice/word-count/tests/word-count.rs b/exercises/practice/word-count/tests/word-count.rs deleted file mode 100644 index 58f1eb6ee..000000000 --- a/exercises/practice/word-count/tests/word-count.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::collections::HashMap; - -fn check_word_count(s: &str, pairs: &[(&str, u32)]) { - // The reason for the awkward code in here is to ensure that the failure - // message for assert_eq! is as informative as possible. A simpler - // solution would simply check the length of the map, and then - // check for the presence and value of each key in the given pairs vector. - let mut m: HashMap = word_count::word_count(s); - for &(k, v) in pairs.iter() { - assert_eq!((k, m.remove(&k.to_string()).unwrap_or(0)), (k, v)); - } - // may fail with a message that clearly shows all extra pairs in the map - assert_eq!(m.iter().collect::>(), vec![]); -} - -#[test] -fn test_count_one_word() { - check_word_count("word", &[("word", 1)]); -} - -#[test] -#[ignore] -fn test_count_one_of_each() { - check_word_count("one of each", &[("one", 1), ("of", 1), ("each", 1)]); -} - -#[test] -#[ignore] -fn test_count_multiple_occurrences() { - check_word_count( - "one fish two fish red fish blue fish", - &[("one", 1), ("fish", 4), ("two", 1), ("red", 1), ("blue", 1)], - ); -} - -#[test] -#[ignore] -fn cramped_lists() { - check_word_count("one,two,three", &[("one", 1), ("two", 1), ("three", 1)]); -} - -#[test] -#[ignore] -fn expanded_lists() { - check_word_count("one\ntwo\nthree", &[("one", 1), ("two", 1), ("three", 1)]); -} - -#[test] -#[ignore] -fn test_ignore_punctuation() { - check_word_count( - "car : carpet as java : javascript!!&@$%^&", - &[ - ("car", 1), - ("carpet", 1), - ("as", 1), - ("java", 1), - ("javascript", 1), - ], - ); -} - -#[test] -#[ignore] -fn test_include_numbers() { - check_word_count( - "testing, 1, 2 testing", - &[("testing", 2), ("1", 1), ("2", 1)], - ); -} - -#[test] -#[ignore] -fn test_normalize_case() { - check_word_count("go Go GO Stop stop", &[("go", 3), ("stop", 2)]); -} - -#[test] -#[ignore] -fn with_apostrophes() { - check_word_count( - "First: don't laugh. Then: don't cry.", - &[ - ("first", 1), - ("don't", 2), - ("laugh", 1), - ("then", 1), - ("cry", 1), - ], - ); -} - -#[test] -#[ignore] -fn with_quotations() { - check_word_count( - "Joe can't tell between 'large' and large.", - &[ - ("joe", 1), - ("can't", 1), - ("tell", 1), - ("between", 1), - ("large", 2), - ("and", 1), - ], - ); -} - -#[test] -#[ignore] -fn multiple_spaces_not_detected_as_a_word() { - check_word_count( - " multiple whitespaces", - &[("multiple", 1), ("whitespaces", 1)], - ); -} diff --git a/exercises/practice/word-count/tests/word_count.rs b/exercises/practice/word-count/tests/word_count.rs new file mode 100644 index 000000000..40c92bd2c --- /dev/null +++ b/exercises/practice/word-count/tests/word_count.rs @@ -0,0 +1,199 @@ +use word_count::*; + +#[test] +fn count_one_word() { + let input = "word"; + let mut output = word_count(input); + let expected = [("word", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn count_one_of_each_word() { + let input = "one of each"; + let mut output = word_count(input); + let expected = [("one", 1), ("of", 1), ("each", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn multiple_occurrences_of_a_word() { + let input = "one fish two fish red fish blue fish"; + let mut output = word_count(input); + let expected = [("one", 1), ("fish", 4), ("two", 1), ("red", 1), ("blue", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn handles_cramped_lists() { + let input = "one,two,three"; + let mut output = word_count(input); + let expected = [("one", 1), ("two", 1), ("three", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn handles_expanded_lists() { + let input = "one,\ntwo,\nthree"; + let mut output = word_count(input); + let expected = [("one", 1), ("two", 1), ("three", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn ignore_punctuation() { + let input = "car: carpet as java: javascript!!&@$%^&"; + let mut output = word_count(input); + let expected = [ + ("car", 1), + ("carpet", 1), + ("as", 1), + ("java", 1), + ("javascript", 1), + ]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn include_numbers() { + let input = "testing, 1, 2 testing"; + let mut output = word_count(input); + let expected = [("testing", 2), ("1", 1), ("2", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn normalize_case() { + let input = "go Go GO Stop stop"; + let mut output = word_count(input); + let expected = [("go", 3), ("stop", 2)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn with_apostrophes() { + let input = "'First: don't laugh. Then: don't cry. You're getting it.'"; + let mut output = word_count(input); + let expected = [ + ("first", 1), + ("don't", 2), + ("laugh", 1), + ("then", 1), + ("cry", 1), + ("you're", 1), + ("getting", 1), + ("it", 1), + ]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn with_quotations() { + let input = "Joe can't tell between 'large' and large."; + let mut output = word_count(input); + let expected = [ + ("joe", 1), + ("can't", 1), + ("tell", 1), + ("between", 1), + ("large", 2), + ("and", 1), + ]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn substrings_from_the_beginning() { + let input = "Joe can't tell between app, apple and a."; + let mut output = word_count(input); + let expected = [ + ("joe", 1), + ("can't", 1), + ("tell", 1), + ("between", 1), + ("app", 1), + ("apple", 1), + ("and", 1), + ("a", 1), + ]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn multiple_spaces_not_detected_as_a_word() { + let input = " multiple whitespaces"; + let mut output = word_count(input); + let expected = [("multiple", 1), ("whitespaces", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn alternating_word_separators_not_detected_as_a_word() { + let input = ",\n,one,\n ,two \n 'three'"; + let mut output = word_count(input); + let expected = [("one", 1), ("two", 1), ("three", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} + +#[test] +#[ignore] +fn quotation_for_word_with_apostrophe() { + let input = "can, can't, 'can't'"; + let mut output = word_count(input); + let expected = [("can", 1), ("can't", 2)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); +} diff --git a/exercises/practice/wordy/.docs/instructions.md b/exercises/practice/wordy/.docs/instructions.md index f65b05acf..aafb9ee54 100644 --- a/exercises/practice/wordy/.docs/instructions.md +++ b/exercises/practice/wordy/.docs/instructions.md @@ -40,8 +40,7 @@ Now, perform the other three operations. Handle a set of operations, in sequence. -Since these are verbal word problems, evaluate the expression from -left-to-right, _ignoring the typical order of operations._ +Since these are verbal word problems, evaluate the expression from left-to-right, _ignoring the typical order of operations._ > What is 5 plus 13 plus 6? @@ -49,20 +48,12 @@ left-to-right, _ignoring the typical order of operations._ > What is 3 plus 2 multiplied by 3? -15 (i.e. not 9) +15 (i.e. not 9) ## Iteration 4 — Errors The parser should reject: -* Unsupported operations ("What is 52 cubed?") -* Non-math questions ("Who is the President of the United States") -* Word problems with invalid syntax ("What is 1 plus plus 2?") - -## Bonus — Exponentials - -If you'd like, handle exponentials. - -> What is 2 raised to the 5th power? - -32 +- Unsupported operations ("What is 52 cubed?") +- Non-math questions ("Who is the President of the United States") +- Word problems with invalid syntax ("What is 1 plus plus 2?") diff --git a/exercises/practice/wordy/.gitignore b/exercises/practice/wordy/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/wordy/.gitignore +++ b/exercises/practice/wordy/.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/wordy/.meta/additional-tests.json b/exercises/practice/wordy/.meta/additional-tests.json new file mode 100644 index 000000000..592290e2a --- /dev/null +++ b/exercises/practice/wordy/.meta/additional-tests.json @@ -0,0 +1,28 @@ +[ + { + "uuid": "88bf4b28-0de3-4883-93c7-db1b14aa806e", + "description": "exponential", + "comments": [ + "This test case was added a long time ago.", + "Upstreaming it would make the exercise more difficult." + ], + "property": "exponentials", + "input": { + "question": "What is 2 raised to the 5th power?" + }, + "expected": 32 + }, + { + "uuid": "bb8c655c-cf42-4dfc-90e0-152fcfd8d4e0", + "description": "addition and exponential", + "comments": [ + "This test case was added a long time ago.", + "Upstreaming it would make the exercise more difficult." + ], + "property": "exponentials", + "input": { + "question": "What is 1 plus 2 raised to the 2nd power?" + }, + "expected": 9 + } +] diff --git a/exercises/practice/wordy/.meta/example.rs b/exercises/practice/wordy/.meta/example.rs index 6ceceeed4..44052ffef 100644 --- a/exercises/practice/wordy/.meta/example.rs +++ b/exercises/practice/wordy/.meta/example.rs @@ -22,8 +22,12 @@ fn apply_op<'a, 'b>(num1: i32, words: &'a [Token<'b>]) -> Option<(i32, &'a [Toke [Token::NonNumber("minus")] => Some(num1 - num2), [Token::NonNumber("multiplied"), Token::NonNumber("by")] => Some(num1 * num2), [Token::NonNumber("divided"), Token::NonNumber("by")] => Some(num1 / num2), - [Token::NonNumber("raised"), Token::NonNumber("to"), Token::NonNumber("the")] => { - if Some(&Token::NonNumber("power")) == remainder.get(0) { + [ + Token::NonNumber("raised"), + Token::NonNumber("to"), + Token::NonNumber("the"), + ] => { + if Some(&Token::NonNumber("power")) == remainder.first() { remainder = remainder.get(1..)?; Some(num1.pow(num2 as u32)) } else { @@ -56,7 +60,11 @@ pub fn answer(c: &str) -> Option { return None; } let mut result: i32 = match words[0..3] { - [Token::NonNumber("What"), Token::NonNumber("is"), Token::Number(i)] => i, + [ + Token::NonNumber("What"), + Token::NonNumber("is"), + Token::Number(i), + ] => i, _ => return None, }; let mut words = words.split_at(3).1; diff --git a/exercises/practice/wordy/.meta/test_template.tera b/exercises/practice/wordy/.meta/test_template.tera new file mode 100644 index 000000000..33cebd2ae --- /dev/null +++ b/exercises/practice/wordy/.meta/test_template.tera @@ -0,0 +1,19 @@ +use wordy::*; + +{% for test in cases %} +#[test] +#[ignore] +{% if test.property == "exponentials" -%} +#[cfg(feature = "exponentials")] +{% endif -%} +fn {{ test.description | make_ident }}() { + let input = {{ test.input.question | json_encode() }}; + let output = answer(input); + let expected = {% if test.expected is object -%} + None + {%- else -%} + Some({{ test.expected }}) + {%- endif %}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/wordy/.meta/tests.toml b/exercises/practice/wordy/.meta/tests.toml index b6a29d500..a0a83ed0b 100644 --- a/exercises/practice/wordy/.meta/tests.toml +++ b/exercises/practice/wordy/.meta/tests.toml @@ -1,13 +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. [88bf4b28-0de3-4883-93c7-db1b14aa806e] description = "just a number" +[18983214-1dfc-4ebd-ac77-c110dde699ce] +description = "just a zero" + +[607c08ee-2241-4288-916d-dae5455c87e6] +description = "just a negative number" + [bb8c655c-cf42-4dfc-90e0-152fcfd8d4e0] description = "addition" +[bb9f2082-171c-46ad-ad4e-c3f72087c1b5] +description = "addition with a left hand zero" + +[6fa05f17-405a-4742-80ae-5d1a8edb0d5d] +description = "addition with a right hand zero" + [79e49e06-c5ae-40aa-a352-7a3a01f70015] description = "more addition" @@ -38,9 +57,15 @@ description = "multiple subtraction" [4f4a5749-ef0c-4f60-841f-abcfaf05d2ae] description = "subtraction then addition" +[312d908c-f68f-42c9-aa75-961623cc033f] +description = "multiple multiplication" + [38e33587-8940-4cc1-bc28-bfd7e3966276] description = "addition and multiplication" +[3c854f97-9311-46e8-b574-92b60d17d394] +description = "multiple division" + [3ad3e433-8af7-41ec-aa9b-97b42ab49357] description = "unknown operation" diff --git a/exercises/practice/wordy/Cargo.toml b/exercises/practice/wordy/Cargo.toml index 92e0bbcf2..aad29393a 100644 --- a/exercises/practice/wordy/Cargo.toml +++ b/exercises/practice/wordy/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" name = "wordy" -version = "1.5.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] [features] exponentials = [] diff --git a/exercises/practice/wordy/src/lib.rs b/exercises/practice/wordy/src/lib.rs index 229739568..dff74fdac 100644 --- a/exercises/practice/wordy/src/lib.rs +++ b/exercises/practice/wordy/src/lib.rs @@ -1,5 +1,3 @@ pub fn answer(command: &str) -> Option { - unimplemented!( - "Return the result of the command '{command}' or None, if the command is invalid." - ); + todo!("Return the result of the command '{command}' or None, if the command is invalid."); } diff --git a/exercises/practice/wordy/tests/wordy.rs b/exercises/practice/wordy/tests/wordy.rs index 6550af76f..86b565c22 100644 --- a/exercises/practice/wordy/tests/wordy.rs +++ b/exercises/practice/wordy/tests/wordy.rs @@ -1,177 +1,263 @@ -use wordy::answer; +use wordy::*; #[test] fn just_a_number() { - let command = "What is 5?"; - assert_eq!(Some(5), answer(command)); + let input = "What is 5?"; + let output = answer(input); + let expected = Some(5); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn just_a_zero() { + let input = "What is 0?"; + let output = answer(input); + let expected = Some(0); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn just_a_negative_number() { + let input = "What is -123?"; + let output = answer(input); + let expected = Some(-123); + assert_eq!(output, expected); } #[test] #[ignore] fn addition() { - let command = "What is 1 plus 1?"; - assert_eq!(Some(2), answer(command)); + let input = "What is 1 plus 1?"; + let output = answer(input); + let expected = Some(2); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn addition_with_a_left_hand_zero() { + let input = "What is 0 plus 2?"; + let output = answer(input); + let expected = Some(2); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn addition_with_a_right_hand_zero() { + let input = "What is 3 plus 0?"; + let output = answer(input); + let expected = Some(3); + assert_eq!(output, expected); } #[test] #[ignore] fn more_addition() { - let command = "What is 53 plus 2?"; - assert_eq!(Some(55), answer(command)); + let input = "What is 53 plus 2?"; + let output = answer(input); + let expected = Some(55); + assert_eq!(output, expected); } #[test] #[ignore] fn addition_with_negative_numbers() { - let command = "What is -1 plus -10?"; - assert_eq!(Some(-11), answer(command)); + let input = "What is -1 plus -10?"; + let output = answer(input); + let expected = Some(-11); + assert_eq!(output, expected); } #[test] #[ignore] fn large_addition() { - let command = "What is 123 plus 45678?"; - assert_eq!(Some(45_801), answer(command)); + let input = "What is 123 plus 45678?"; + let output = answer(input); + let expected = Some(45801); + assert_eq!(output, expected); } #[test] #[ignore] fn subtraction() { - let command = "What is 4 minus -12?"; - assert_eq!(Some(16), answer(command)); + let input = "What is 4 minus -12?"; + let output = answer(input); + let expected = Some(16); + assert_eq!(output, expected); } #[test] #[ignore] fn multiplication() { - let command = "What is -3 multiplied by 25?"; - assert_eq!(Some(-75), answer(command)); + let input = "What is -3 multiplied by 25?"; + let output = answer(input); + let expected = Some(-75); + assert_eq!(output, expected); } #[test] #[ignore] fn division() { - let command = "What is 33 divided by -3?"; - assert_eq!(Some(-11), answer(command)); + let input = "What is 33 divided by -3?"; + let output = answer(input); + let expected = Some(-11); + assert_eq!(output, expected); } #[test] #[ignore] fn multiple_additions() { - let command = "What is 1 plus 1 plus 1?"; - assert_eq!(Some(3), answer(command)); + let input = "What is 1 plus 1 plus 1?"; + let output = answer(input); + let expected = Some(3); + assert_eq!(output, expected); } #[test] #[ignore] fn addition_and_subtraction() { - let command = "What is 1 plus 5 minus -2?"; - assert_eq!(Some(8), answer(command)); + let input = "What is 1 plus 5 minus -2?"; + let output = answer(input); + let expected = Some(8); + assert_eq!(output, expected); } #[test] #[ignore] fn multiple_subtraction() { - let command = "What is 20 minus 4 minus 13?"; - assert_eq!(Some(3), answer(command)); + let input = "What is 20 minus 4 minus 13?"; + let output = answer(input); + let expected = Some(3); + assert_eq!(output, expected); } #[test] #[ignore] fn subtraction_then_addition() { - let command = "What is 17 minus 6 plus 3?"; - assert_eq!(Some(14), answer(command)); + let input = "What is 17 minus 6 plus 3?"; + let output = answer(input); + let expected = Some(14); + assert_eq!(output, expected); } #[test] #[ignore] -fn multiple_multiplications() { - let command = "What is 2 multiplied by -2 multiplied by 3?"; - assert_eq!(Some(-12), answer(command)); +fn multiple_multiplication() { + let input = "What is 2 multiplied by -2 multiplied by 3?"; + let output = answer(input); + let expected = Some(-12); + assert_eq!(output, expected); } #[test] #[ignore] fn addition_and_multiplication() { - let command = "What is -3 plus 7 multiplied by -2?"; - assert_eq!(Some(-8), answer(command)); + let input = "What is -3 plus 7 multiplied by -2?"; + let output = answer(input); + let expected = Some(-8); + assert_eq!(output, expected); } #[test] #[ignore] -fn multiple_divisions() { - let command = "What is -12 divided by 2 divided by -3?"; - assert_eq!(Some(2), answer(command)); +fn multiple_division() { + let input = "What is -12 divided by 2 divided by -3?"; + let output = answer(input); + let expected = Some(2); + assert_eq!(output, expected); } #[test] #[ignore] fn unknown_operation() { - let command = "What is 52 cubed?"; - assert_eq!(None, answer(command)); + let input = "What is 52 cubed?"; + let output = answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn non_math_question() { - let command = "Who is the President of the United States?"; - assert_eq!(None, answer(command)); + let input = "Who is the President of the United States?"; + let output = answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_problem_missing_an_operand() { - let command = "What is 1 plus?"; - assert_eq!(None, answer(command)); + let input = "What is 1 plus?"; + let output = answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_problem_with_no_operands_or_operators() { - let command = "What is?"; - assert_eq!(None, answer(command)); + let input = "What is?"; + let output = answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_two_operations_in_a_row() { - let command = "What is 1 plus plus 2?"; - assert_eq!(None, answer(command)); + let input = "What is 1 plus plus 2?"; + let output = answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_two_numbers_in_a_row() { - let command = "What is 1 plus 2 1?"; - assert_eq!(None, answer(command)); + let input = "What is 1 plus 2 1?"; + let output = answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_postfix_notation() { - let command = "What is 1 2 plus?"; - assert_eq!(None, answer(command)); + let input = "What is 1 2 plus?"; + let output = answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_prefix_notation() { - let command = "What is plus 1 2?"; - assert_eq!(None, answer(command)); + let input = "What is plus 1 2?"; + let output = answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] #[cfg(feature = "exponentials")] fn exponential() { - let command = "What is 2 raised to the 5th power?"; - assert_eq!(Some(32), answer(command)); + let input = "What is 2 raised to the 5th power?"; + let output = answer(input); + let expected = Some(32); + assert_eq!(output, expected); } #[test] #[ignore] #[cfg(feature = "exponentials")] fn addition_and_exponential() { - let command = "What is 1 plus 2 raised to the 2nd power?"; - assert_eq!(Some(9), answer(command)); + let input = "What is 1 plus 2 raised to the 2nd power?"; + let output = answer(input); + let expected = Some(9); + assert_eq!(output, expected); } diff --git a/exercises/practice/xorcism/.docs/instructions.append.md b/exercises/practice/xorcism/.docs/instructions.append.md index c7bd1f3bf..7b90b0f4e 100644 --- a/exercises/practice/xorcism/.docs/instructions.append.md +++ b/exercises/practice/xorcism/.docs/instructions.append.md @@ -1,4 +1,6 @@ -# Lifetime of `munge` return value +# Instructions append + +## Lifetime of `munge` return value Due to the usage of the `impl Trait` feature, lifetime management may be a bit tricky when implementing the `munge` method. You may find it easier to write diff --git a/exercises/practice/xorcism/.gitignore b/exercises/practice/xorcism/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/xorcism/.gitignore +++ b/exercises/practice/xorcism/.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/xorcism/.meta/additional-tests.json b/exercises/practice/xorcism/.meta/additional-tests.json new file mode 100644 index 000000000..2799c2bb8 --- /dev/null +++ b/exercises/practice/xorcism/.meta/additional-tests.json @@ -0,0 +1,90 @@ +[ + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "key_shorter_than_data", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "abcde", + "input": "123455" + }, + "expected": [80,80,80,80,80,84] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "key_len_equal_to_data", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "The quick brown fox jumped over the lazy dog.", + "input": "Wait, oops, this is not the pangram exercise!" + }, + "expected": [3,9,12,84,93,85,6,12,27,83,78,82,27,31,7,83,70,6,11,0,4,26,25,80,17,12,69,79,6,4,28,71,6,9,8,0,9,25,31,11,67,13,28,2,15] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "key_longer_than_data", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "A properly cryptographically random key longer than the data can actually be fairly secure.", + "input": "Text is not cryptographically random." + }, + "expected": [21,69,8,6,79,25,22,82,2,22,84,67,17,11,9,4,27,8,21,19,17,24,1,10,2,13,0,21,89,82,19,15,10,11,2,77,69] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "shakespearean", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "Forsooth, let us never break our trust!", + "input": "The sacred brothership in which we share shall never from our hearts be lost." + }, + "expected": [18,7,23,83,28,14,23,26,73,68,76,7,6,79,1,27,69,28,22,30,12,2,0,11,28,69,22,3,73,12,29,82,87,17,82,6,27,21,83,35,79,1,27,14,3,24,72,66,69,26,0,6,0,19,1,79,3,69,25,16,0,0,10,23,4,19,31,83,79,23,23,0,24,29,6,7,90] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "comics", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "Who knows what evil lurks in the hearts of men?", + "input": "Spiderman! It's spiderman! Not a bird, or a plane, or a fireman! Just spiderman!" + }, + "expected": [4,24,6,68,14,28,2,22,29,1,87,33,21,83,83,69,5,25,5,68,9,7,31,10,29,1,73,32,79,0,72,4,0,10,12,19,22,88,83,79,29,70,65,77,21,2,94,57,13,67,0,4,28,79,22,83,70,30,26,4,25,65,11,87,73,38,85,31,1,82,24,3,73,13,11,82,25,9,11,1] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "mad_science", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "TRANSMUTATION_NOTES_1", + "input": "If wishes were horses, beggars would ride." + }, + "expected": [29,52,97,57,58,62,61,49,50,116,62,42,60,58,110,39,59,55,32,58,66,120,114,35,43,52,42,52,38,50,116,62,32,59,51,42,111,38,44,55,58,31] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "metaphor", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "Contextualism", + "input": "The globe is text, its people prose; all the world's a page." + }, + "expected": [23,7,11,84,2,20,27,23,4,76,0,0,77,55,10,22,0,73,88,29,1,18,76,25,22,2,51,3,11,84,21,10,27,6,4,87,73,18,1,47,79,26,28,0,88,3,26,19,0,13,84,30,99,14,78,4,4,31,17,91] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "emoji", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "🔑🗝️… 🎹?", + "input": "⌨️! 🔒+💻+🧠=🔓" + }, + "expected": [18,19,60,126,72,16,182,189,31,39,27,112,171,86,191,98,36,165,73,160,87,63,169,97,111,11,4] + } +] diff --git a/exercises/practice/xorcism/.meta/config.json b/exercises/practice/xorcism/.meta/config.json index d32911474..1735a26e9 100644 --- a/exercises/practice/xorcism/.meta/config.json +++ b/exercises/practice/xorcism/.meta/config.json @@ -24,7 +24,6 @@ "blurb": "Implement zero-copy streaming adaptors", "source": "Peter Goodspeed-Niklaus", "custom": { - "allowed-to-not-compile": "The point of this exercise is for students to figure out the appropriate function signatures and generic bounds for their implementations, so we cannot provide those.", - "ignore-count-ignores": true + "allowed-to-not-compile": "The point of this exercise is for students to figure out the appropriate function signatures and generic bounds for their implementations, so we cannot provide those." } } diff --git a/exercises/practice/xorcism/.meta/example.rs b/exercises/practice/xorcism/.meta/example.rs index ee5d6e0a0..6c2f7190b 100644 --- a/exercises/practice/xorcism/.meta/example.rs +++ b/exercises/practice/xorcism/.meta/example.rs @@ -110,7 +110,7 @@ where } } -impl<'a, W> Write for Writer<'a, W> +impl Write for Writer<'_, W> where W: Write, { @@ -149,7 +149,7 @@ where } } -impl<'a, R> Read for Reader<'a, R> +impl Read for Reader<'_, R> where R: Read, { diff --git a/exercises/practice/xorcism/.meta/test_template.tera b/exercises/practice/xorcism/.meta/test_template.tera new file mode 100644 index 000000000..746d7f093 --- /dev/null +++ b/exercises/practice/xorcism/.meta/test_template.tera @@ -0,0 +1,290 @@ +#[cfg(feature = "io")] +use std::io::{Read, Write}; +use xorcism::Xorcism; + +#[test] +#[ignore] +fn munge_in_place_identity() { + let mut xs = Xorcism::new(&[0]); + let input = "This is super-secret, cutting edge encryption, folks.".as_bytes(); + let mut output = input.to_owned(); + xs.munge_in_place(&mut output); + + assert_eq!(&input, &output); +} + +#[test] +#[ignore] +fn munge_in_place_roundtrip() { + let mut xs1 = Xorcism::new(&[1, 2, 3, 4, 5]); + let mut xs2 = Xorcism::new(&[1, 2, 3, 4, 5]); + let input = "This is super-secret, cutting edge encryption, folks.".as_bytes(); + let mut cipher = input.to_owned(); + xs1.munge_in_place(&mut cipher); + assert_ne!(&input, &cipher); + let mut output = cipher; + xs2.munge_in_place(&mut output); + assert_eq!(&input, &output); +} + +#[test] +#[ignore] +fn munge_in_place_stateful() { + let mut xs = Xorcism::new(&[1, 2, 3, 4, 5]); + let input = "This is super-secret, cutting edge encryption, folks.".as_bytes(); + + let mut cipher1 = input.to_owned(); + let mut cipher2 = input.to_owned(); + xs.munge_in_place(&mut cipher1); + xs.munge_in_place(&mut cipher2); + + assert_ne!(&input, &cipher1); + assert_ne!(&input, &cipher2); + assert_ne!(&cipher1, &cipher2); +} + +#[test] +#[ignore] +fn munge_identity() { + let mut xs = Xorcism::new(&[0]); + let data = "This is super-secret, cutting edge encryption, folks."; + + assert_eq!( + xs.munge(data.as_bytes()).collect::>(), + data.as_bytes() + ); +} + +#[test] +#[ignore] +fn statefulness() { + // we expect Xorcism to be stateful: at the end of a munging run, the key has rotated. + // this means that until the key has completely rotated around, equal inputs will produce + // unequal outputs. + let key = &[0, 1, 2, 3, 4, 5, 6, 7]; + let input = &[0b1010_1010, 0b0101_0101]; + + let mut xs = Xorcism::new(&key); + let out1: Vec<_> = xs.munge(input).collect(); + let out2: Vec<_> = xs.munge(input).collect(); + let out3: Vec<_> = xs.munge(input).collect(); + let out4: Vec<_> = xs.munge(input).collect(); + let out5: Vec<_> = xs.munge(input).collect(); + + assert_ne!(out1, out2); + assert_ne!(out2, out3); + assert_ne!(out3, out4); + assert_ne!(out4, out5); + assert_eq!(out1, out5); +} + +{% for test in cases %} + +mod {{ test.description | make_ident }} { + + use super::*; + + const KEY: &str = {{ test.input.key | json_encode() }}; + const INPUT: &str = {{ test.input.input | json_encode() }}; + const EXPECT: &[u8] = &{{ test.expected | json_encode() }}; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} +{% endfor -%} diff --git a/exercises/practice/xorcism/Cargo.toml b/exercises/practice/xorcism/Cargo.toml index 268e17ce2..d1cacc9a7 100644 --- a/exercises/practice/xorcism/Cargo.toml +++ b/exercises/practice/xorcism/Cargo.toml @@ -1,13 +1,12 @@ [package] name = "xorcism" 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] io = [] - -[dev-dependencies] -hexlit = "0.5.0" diff --git a/exercises/practice/xorcism/src/lib.rs b/exercises/practice/xorcism/src/lib.rs index 59adc045a..a462fefbb 100644 --- a/exercises/practice/xorcism/src/lib.rs +++ b/exercises/practice/xorcism/src/lib.rs @@ -3,7 +3,7 @@ pub struct Xorcism<'a> { // This field is just to suppress compiler complaints; // feel free to delete it at any point. - _phantom: std::marker::PhantomData<&'a u8>, + phantom: std::marker::PhantomData<&'a u8>, } impl<'a> Xorcism<'a> { @@ -11,7 +11,7 @@ impl<'a> Xorcism<'a> { /// /// Should accept anything which has a cheap conversion to a byte slice. pub fn new(key: &Key) -> Xorcism<'a> { - unimplemented!() + todo!() } /// XOR each byte of the input buffer with a byte from the key. @@ -19,7 +19,7 @@ impl<'a> Xorcism<'a> { /// Note that this is stateful: repeated calls are likely to produce different results, /// even with identical inputs. pub fn munge_in_place(&mut self, data: &mut [u8]) { - unimplemented!() + todo!() } /// XOR each byte of the data with a byte from the key. @@ -30,7 +30,7 @@ impl<'a> Xorcism<'a> { /// Should accept anything which has a cheap conversion to a byte iterator. /// Shouldn't matter whether the byte iterator's values are owned or borrowed. pub fn munge(&mut self, data: Data) -> impl Iterator { - unimplemented!(); + todo!(); // this empty iterator silences a compiler complaint that // () doesn't implement ExactSizeIterator std::iter::empty() diff --git a/exercises/practice/xorcism/tests/xorcism.rs b/exercises/practice/xorcism/tests/xorcism.rs index df5580249..fe237c38c 100644 --- a/exercises/practice/xorcism/tests/xorcism.rs +++ b/exercises/practice/xorcism/tests/xorcism.rs @@ -1,4 +1,3 @@ -use hexlit::hex; #[cfg(feature = "io")] use std::io::{Read, Write}; use xorcism::Xorcism; @@ -78,255 +77,1694 @@ fn statefulness() { assert_eq!(out1, out5); } -macro_rules! test_cases { - ($($name:ident, $key:literal, $input:literal, $expect:literal);+) => { - $(mod $name { - use super::*; - - const KEY: &str = $key; - const INPUT: &str = $input; - const EXPECT: &[u8] = &hex!($expect); - - /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` - mod str_slice { - use super::*; - - #[test] - #[ignore] - fn munge_in_place() { - // we transform the input into a `Vec` despite its presence in this - // module because of the more restricted syntax that this function accepts - let mut input = INPUT.as_bytes().to_vec(); - let original = input.clone(); - - // in-place munging is stateful on Xorcism, so clone it - // to ensure the keys positions stay synchronized - let mut xorcism1 = Xorcism::new(KEY); - let mut xorcism2 = xorcism1.clone(); - - xorcism1.munge_in_place(&mut input); - assert_eq!(input.len(), original.len()); - assert_ne!(input, original); - assert_eq!(input, EXPECT); - xorcism2.munge_in_place(&mut input); - assert_eq!(input, original); - } - - #[test] - #[ignore] - fn munges() { - let mut xorcism = Xorcism::new(KEY); - let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); - assert_eq!(INPUT.len(), result.len()); - assert_ne!(INPUT.as_bytes(), result); - assert_eq!(result, EXPECT); - } - - #[test] - #[ignore] - fn round_trip() { - let mut xorcism1 = Xorcism::new(KEY); - let mut xorcism2 = xorcism1.clone(); - let munge_iter = xorcism1.munge(INPUT.as_bytes()); - let result: Vec = xorcism2.munge(munge_iter).collect(); - assert_eq!(INPUT.as_bytes(), result); - } +mod key_shorter_than_data { + + use super::*; + + const KEY: &str = "abcde"; + const INPUT: &str = "123455"; + const EXPECT: &[u8] = &[80, 80, 80, 80, 80, 84]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod key_len_equal_to_data { + + use super::*; + + const KEY: &str = "The quick brown fox jumped over the lazy dog."; + const INPUT: &str = "Wait, oops, this is not the pangram exercise!"; + const EXPECT: &[u8] = &[ + 3, 9, 12, 84, 93, 85, 6, 12, 27, 83, 78, 82, 27, 31, 7, 83, 70, 6, 11, 0, 4, 26, 25, 80, + 17, 12, 69, 79, 6, 4, 28, 71, 6, 9, 8, 0, 9, 25, 31, 11, 67, 13, 28, 2, 15, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod key_longer_than_data { + + use super::*; + + const KEY: &str = "A properly cryptographically random key longer than the data can actually be fairly secure."; + const INPUT: &str = "Text is not cryptographically random."; + const EXPECT: &[u8] = &[ + 21, 69, 8, 6, 79, 25, 22, 82, 2, 22, 84, 67, 17, 11, 9, 4, 27, 8, 21, 19, 17, 24, 1, 10, 2, + 13, 0, 21, 89, 82, 19, 15, 10, 11, 2, 77, 69, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod shakespearean { + + use super::*; + + const KEY: &str = "Forsooth, let us never break our trust!"; + const INPUT: &str = + "The sacred brothership in which we share shall never from our hearts be lost."; + const EXPECT: &[u8] = &[ + 18, 7, 23, 83, 28, 14, 23, 26, 73, 68, 76, 7, 6, 79, 1, 27, 69, 28, 22, 30, 12, 2, 0, 11, + 28, 69, 22, 3, 73, 12, 29, 82, 87, 17, 82, 6, 27, 21, 83, 35, 79, 1, 27, 14, 3, 24, 72, 66, + 69, 26, 0, 6, 0, 19, 1, 79, 3, 69, 25, 16, 0, 0, 10, 23, 4, 19, 31, 83, 79, 23, 23, 0, 24, + 29, 6, 7, 90, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); } + assert_eq!(writer_dest, EXPECT); + } - /// tests where the key and input are both expressed as `&[u8]` - mod slice_slice { - use super::*; - - #[test] - #[ignore] - fn munge_in_place() { - let key = KEY.as_bytes(); - - // we transform the input into a `Vec` despite its presence in this - // module because of the more restricted syntax that this function accepts - let mut input = INPUT.as_bytes().to_vec(); - let original = input.clone(); - - // in-place munging is stateful on Xorcism, so clone it - // to ensure the keys positions stay synchronized - let mut xorcism1 = Xorcism::new(key); - let mut xorcism2 = xorcism1.clone(); - - xorcism1.munge_in_place(&mut input); - assert_eq!(input.len(), original.len()); - assert_ne!(input, original); - assert_eq!(input, EXPECT); - xorcism2.munge_in_place(&mut input); - assert_eq!(input, original); - } - - #[test] - #[ignore] - fn munges() { - let key = KEY.as_bytes(); - let input = INPUT.as_bytes(); - - let mut xorcism = Xorcism::new(key); - let result: Vec = xorcism.munge(input).collect(); - assert_eq!(input.len(), result.len()); - assert_ne!(input, result); - assert_eq!(result, EXPECT); - } - - #[test] - #[ignore] - fn round_trip() { - let key = KEY.as_bytes(); - let input = INPUT.as_bytes(); - - let mut xorcism1 = Xorcism::new(key); - let mut xorcism2 = xorcism1.clone(); - let munge_iter = xorcism1.munge(input); - let result: Vec = xorcism2.munge(munge_iter).collect(); - assert_eq!(input, result); - } + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod comics { + + use super::*; + + const KEY: &str = "Who knows what evil lurks in the hearts of men?"; + const INPUT: &str = + "Spiderman! It's spiderman! Not a bird, or a plane, or a fireman! Just spiderman!"; + const EXPECT: &[u8] = &[ + 4, 24, 6, 68, 14, 28, 2, 22, 29, 1, 87, 33, 21, 83, 83, 69, 5, 25, 5, 68, 9, 7, 31, 10, 29, + 1, 73, 32, 79, 0, 72, 4, 0, 10, 12, 19, 22, 88, 83, 79, 29, 70, 65, 77, 21, 2, 94, 57, 13, + 67, 0, 4, 28, 79, 22, 83, 70, 30, 26, 4, 25, 65, 11, 87, 73, 38, 85, 31, 1, 82, 24, 3, 73, + 13, 11, 82, 25, 9, 11, 1, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod mad_science { + + use super::*; + + const KEY: &str = "TRANSMUTATION_NOTES_1"; + const INPUT: &str = "If wishes were horses, beggars would ride."; + const EXPECT: &[u8] = &[ + 29, 52, 97, 57, 58, 62, 61, 49, 50, 116, 62, 42, 60, 58, 110, 39, 59, 55, 32, 58, 66, 120, + 114, 35, 43, 52, 42, 52, 38, 50, 116, 62, 32, 59, 51, 42, 111, 38, 44, 55, 58, 31, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); - /// tests where the key is expressed as `&str` and input is expressed as `Vec` - mod vec_vec { - use super::*; - - #[test] - #[ignore] - fn munge_in_place() { - let mut input = INPUT.as_bytes().to_vec(); - let original = input.clone(); - - // in-place munging is stateful on Xorcism, so clone it - // to ensure the keys positions stay synchronized - let mut xorcism1 = Xorcism::new(KEY); - let mut xorcism2 = xorcism1.clone(); - - xorcism1.munge_in_place(&mut input); - assert_eq!(input.len(), original.len()); - assert_ne!(input, original); - assert_eq!(input, EXPECT); - xorcism2.munge_in_place(&mut input); - assert_eq!(input, original); - } - - #[test] - #[ignore] - fn munges() { - let owned_input = INPUT.as_bytes().to_vec(); - - let mut xorcism = Xorcism::new(KEY); - let result: Vec = xorcism.munge(owned_input).collect(); - assert_eq!(INPUT.len(), result.len()); - assert_ne!(INPUT.as_bytes(), result); - assert_eq!(result, EXPECT); - } - - #[test] - #[ignore] - fn round_trip() { - let owned_input = INPUT.as_bytes().to_vec(); - - let mut xorcism1 = Xorcism::new(KEY); - let mut xorcism2 = xorcism1.clone(); - let munge_iter = xorcism1.munge(owned_input); - let result: Vec = xorcism2.munge(munge_iter).collect(); - assert_eq!(INPUT.as_bytes(), result); - } + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); } + assert_eq!(writer_dest, EXPECT); + } - #[cfg(feature = "io")] - mod io { - use super::*; - - #[test] - #[ignore] - fn reader_munges() { - let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); - let mut buf = Vec::with_capacity(INPUT.len()); - let bytes_read = reader.read_to_end(&mut buf).unwrap(); - assert_eq!(bytes_read, INPUT.len()); - assert_eq!(buf, EXPECT); - } - - #[test] - #[ignore] - fn reader_roundtrip() { - let xs = Xorcism::new(KEY); - let reader1 = xs.clone().reader(INPUT.as_bytes()); - let mut reader2 = xs.clone().reader(reader1); - let mut buf = Vec::with_capacity(INPUT.len()); - let bytes_read = reader2.read_to_end(&mut buf).unwrap(); - assert_eq!(bytes_read, INPUT.len()); - assert_eq!(buf, INPUT.as_bytes()); - } - - #[test] - #[ignore] - fn writer_munges() { - let mut writer_dest = Vec::new(); - { - let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); - assert!(writer.write_all(INPUT.as_bytes()).is_ok()); - } - assert_eq!(writer_dest, EXPECT); - } - - #[test] - #[ignore] - fn writer_roundtrip() { - let mut writer_dest = Vec::new(); - let xs = Xorcism::new(KEY); - { - let writer1 = xs.clone().writer(&mut writer_dest); - let mut writer2 = xs.writer(writer1); - assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); - } - assert_eq!(writer_dest, INPUT.as_bytes()); - } + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); } - })+ - }; + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } } -test_cases!( - key_shorter_than_data, "abcde", "123455", "5050 5050 5054" - ; - key_len_equal_to_data, - "The quick brown fox jumped over the lazy dog.", - "Wait, oops, this is not the pangram exercise!", - "0309 0c54 5d55 060c 1b53 4e52 1b1f 0753 4606 0b00 041a 1950 110c 454f 0604 1c47 0609 0800 0919 1f0b 430d 1c02 0f" - ; - key_longer_than_data, - "A properly cryptographically random key longer than the data can actually be fairly secure.", - "Text is not cryptographically random.", - "1545 0806 4f19 1652 0216 5443 110b 0904 1b08 1513 1118 010a 020d 0015 5952 130f 0a0b 024d 45" - ; - shakespearean, - "Forsooth, let us never break our trust!", - "The sacred brothership in which we share shall never from our hearts be lost.", - "1207 1753 1c0e 171a 4944 4c07 064f 011b 451c 161e 0c02 000b 1c45 1603 490c 1d52 5711 5206 1b15 5323 4f01 1b0e 0318 4842 451a 0006 0013 014f 0345 1910 0000 0a17 0413 1f53 4f17 1700 181d 0607 5a" - ; - comics, - "Who knows what evil lurks in the hearts of men?", - "Spiderman! It's spiderman! Not a bird, or a plane, or a fireman! Just spiderman!", - "0418 0644 0e1c 0216 1d01 5721 1553 5345 0519 0544 0907 1f0a 1d01 4920 4f00 4804 000a 0c13 1658 534f 1d46 414d 1502 5e39 0d43 0004 1c4f 1653 461e 1a04 1941 0b57 4926 551f 0152 1803 490d 0b52 1909 0b01" - ; - mad_science, - "TRANSMUTATION_NOTES_1", - "If wishes were horses, beggars would ride.", - "1d34 6139 3a3e 3d31 3274 3e2a 3c3a 6e27 3b37 203a 4278 7223 2b34 2a34 2632 743e 203b 332a 6f26 2c37 3a1f" - ; - metaphor, - "Contextualism", - "The globe is text, its people prose; all the world's a page.", - "1707 0b54 0214 1b17 044c 0000 4d37 0a16 0049 581d 0112 4c19 1602 3303 0b54 150a 1b06 0457 4912 012f 4f1a 1c00 5803 1a13 000d 541e 630e 4e04 041f 115b" - ; - emoji, - "🔑🗝️… 🎹?", - "⌨️! 🔒+💻+🧠=🔓", - "1213 3c7e 4810 b6bd 1f27 1b70 ab56 bf62 24a5 49a0 573f a961 6f0b 04" -); -// note the emoji case above. Most exercism tests don't test emoji, because we like to use strings -// as input, but in this case, they're fine: they're arbitrary binary data that _just happen_ to -// represent emoji when construed as a string, in this case. +mod metaphor { + + use super::*; + + const KEY: &str = "Contextualism"; + const INPUT: &str = "The globe is text, its people prose; all the world's a page."; + const EXPECT: &[u8] = &[ + 23, 7, 11, 84, 2, 20, 27, 23, 4, 76, 0, 0, 77, 55, 10, 22, 0, 73, 88, 29, 1, 18, 76, 25, + 22, 2, 51, 3, 11, 84, 21, 10, 27, 6, 4, 87, 73, 18, 1, 47, 79, 26, 28, 0, 88, 3, 26, 19, 0, + 13, 84, 30, 99, 14, 78, 4, 4, 31, 17, 91, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod emoji { + + use super::*; + + const KEY: &str = "🔑🗝️… 🎹?"; + const INPUT: &str = "⌨️! 🔒+💻+🧠=🔓"; + const EXPECT: &[u8] = &[ + 18, 19, 60, 126, 72, 16, 182, 189, 31, 39, 27, 112, 171, 86, 191, 98, 36, 165, 73, 160, 87, + 63, 169, 97, 111, 11, 4, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} diff --git a/exercises/practice/yacht/.docs/instructions.md b/exercises/practice/yacht/.docs/instructions.md new file mode 100644 index 000000000..519b7a68b --- /dev/null +++ b/exercises/practice/yacht/.docs/instructions.md @@ -0,0 +1,30 @@ +# Instructions + +Given five dice and a category, calculate the score of the dice for that category. + +~~~~exercism/note +You'll always be presented with five dice. +Each dice's value will be between one and six inclusively. +The dice may be unordered. +~~~~ + +## Scores in Yacht + +| Category | Score | Description | Example | +| --------------- | ---------------------- | ---------------------------------------- | ------------------- | +| Ones | 1 × number of ones | Any combination | 1 1 1 4 5 scores 3 | +| Twos | 2 × number of twos | Any combination | 2 2 3 4 5 scores 4 | +| Threes | 3 × number of threes | Any combination | 3 3 3 3 3 scores 15 | +| Fours | 4 × number of fours | Any combination | 1 2 3 3 5 scores 0 | +| Fives | 5 × number of fives | Any combination | 5 1 5 2 5 scores 15 | +| Sixes | 6 × number of sixes | Any combination | 2 3 4 5 6 scores 6 | +| Full House | Total of the dice | Three of one number and two of another | 3 3 3 5 5 scores 19 | +| Four of a Kind | Total of the four dice | At least four dice showing the same face | 4 4 4 4 6 scores 16 | +| Little Straight | 30 points | 1-2-3-4-5 | 1 2 3 4 5 scores 30 | +| Big Straight | 30 points | 2-3-4-5-6 | 2 3 4 5 6 scores 30 | +| Choice | Sum of the dice | Any combination | 2 3 3 4 6 scores 18 | +| Yacht | 50 points | All five dice showing the same face | 4 4 4 4 4 scores 50 | + +If the dice do **not** satisfy the requirements of a category, the score is zero. +If, for example, _Four Of A Kind_ is entered in the _Yacht_ category, zero points are scored. +A _Yacht_ scores zero if entered in the _Full House_ category. diff --git a/exercises/practice/yacht/.docs/introduction.md b/exercises/practice/yacht/.docs/introduction.md new file mode 100644 index 000000000..5b541f562 --- /dev/null +++ b/exercises/practice/yacht/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +Each year, something new is "all the rage" in your high school. +This year it is a dice game: [Yacht][yacht]. + +The game of Yacht is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor. +The game consists of twelve rounds. +In each, five dice are rolled and the player chooses one of twelve categories. +The chosen category is then used to score the throw of the dice. + +[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game) diff --git a/exercises/practice/yacht/.gitignore b/exercises/practice/yacht/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/yacht/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/yacht/.meta/config.json b/exercises/practice/yacht/.meta/config.json new file mode 100644 index 000000000..9478b2f43 --- /dev/null +++ b/exercises/practice/yacht/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "dem4ron" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/yacht.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Score a single throw of dice in the game Yacht.", + "source": "James Kilfiger, using Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Yacht_(dice_game)" +} diff --git a/exercises/practice/yacht/.meta/example.rs b/exercises/practice/yacht/.meta/example.rs new file mode 100644 index 000000000..03fa51fd4 --- /dev/null +++ b/exercises/practice/yacht/.meta/example.rs @@ -0,0 +1,82 @@ +pub enum Category { + Ones, + Twos, + Threes, + Fours, + Fives, + Sixes, + FullHouse, + FourOfAKind, + LittleStraight, + BigStraight, + Choice, + Yacht, +} + +type Dice = [u8; 5]; +pub fn score(dice: Dice, category: Category) -> u8 { + match category { + Category::Ones => get_score(dice, 1), + Category::Twos => get_score(dice, 2), + Category::Threes => get_score(dice, 3), + Category::Fours => get_score(dice, 4), + Category::Fives => get_score(dice, 5), + Category::Sixes => get_score(dice, 6), + Category::FullHouse => get_full_house(dice), + Category::FourOfAKind => get_poker(dice), + Category::LittleStraight => get_straight(dice, 'l'), + Category::BigStraight => get_straight(dice, 'b'), + Category::Choice => dice.iter().sum(), + Category::Yacht => get_yacht(dice), + } +} + +fn collect_scores(dice: Dice) -> [u8; 6] { + dice.iter().fold([0; 6], |mut scores, num| { + scores[(*num - 1) as usize] += 1; + scores + }) +} + +fn get_score(dice: Dice, num: usize) -> u8 { + collect_scores(dice)[num - 1] * num as u8 +} + +fn get_full_house(dice: Dice) -> u8 { + let scores = collect_scores(dice); + let two_of_n = scores.iter().position(|&n| n == 2); + let three_of_n = scores.iter().position(|&n| n == 3); + match (two_of_n, three_of_n) { + (Some(two_index), Some(three_index)) => { + scores[two_index] * (two_index as u8 + 1) + + scores[three_index] * (three_index as u8 + 1) + } + _ => 0, + } +} + +fn get_poker(dice: Dice) -> u8 { + let scores = collect_scores(dice); + match scores.iter().max() { + Some(5) => (scores.iter().position(|&n| n == 5).unwrap() as u8 + 1) * 4, + Some(4) => (scores.iter().position(|&n| n == 4).unwrap() as u8 + 1) * 4, + _ => 0, + } +} + +fn get_straight(dice: Dice, straight_type: char) -> u8 { + let scores = collect_scores(dice); + if !scores.iter().all(|&n| n == 0 || n == 1) { + return 0; + } + let index: usize = if straight_type == 'l' { 5 } else { 0 }; + + if scores[index] == 0 { 30 } else { 0 } +} + +fn get_yacht(dice: Dice) -> u8 { + match collect_scores(dice).iter().max() { + Some(5) => 50, + _ => 0, + } +} diff --git a/exercises/practice/yacht/.meta/tests.toml b/exercises/practice/yacht/.meta/tests.toml new file mode 100644 index 000000000..b9d920379 --- /dev/null +++ b/exercises/practice/yacht/.meta/tests.toml @@ -0,0 +1,97 @@ +# 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. + +[3060e4a5-4063-4deb-a380-a630b43a84b6] +description = "Yacht" + +[15026df2-f567-482f-b4d5-5297d57769d9] +description = "Not Yacht" + +[36b6af0c-ca06-4666-97de-5d31213957a4] +description = "Ones" + +[023a07c8-6c6e-44d0-bc17-efc5e1b8205a] +description = "Ones, out of order" + +[7189afac-cccd-4a74-8182-1cb1f374e496] +description = "No ones" + +[793c4292-dd14-49c4-9707-6d9c56cee725] +description = "Twos" + +[dc41bceb-d0c5-4634-a734-c01b4233a0c6] +description = "Fours" + +[f6125417-5c8a-4bca-bc5b-b4b76d0d28c8] +description = "Yacht counted as threes" + +[464fc809-96ed-46e4-acb8-d44e302e9726] +description = "Yacht of 3s counted as fives" + +[d054227f-3a71-4565-a684-5c7e621ec1e9] +description = "Fives" + +[e8a036e0-9d21-443a-8b5f-e15a9e19a761] +description = "Sixes" + +[51cb26db-6b24-49af-a9ff-12f53b252eea] +description = "Full house two small, three big" + +[1822ca9d-f235-4447-b430-2e8cfc448f0c] +description = "Full house three small, two big" + +[b208a3fc-db2e-4363-a936-9e9a71e69c07] +description = "Two pair is not a full house" + +[b90209c3-5956-445b-8a0b-0ac8b906b1c2] +description = "Four of a kind is not a full house" + +[32a3f4ee-9142-4edf-ba70-6c0f96eb4b0c] +description = "Yacht is not a full house" + +[b286084d-0568-4460-844a-ba79d71d79c6] +description = "Four of a Kind" + +[f25c0c90-5397-4732-9779-b1e9b5f612ca] +description = "Yacht can be scored as Four of a Kind" + +[9f8ef4f0-72bb-401a-a871-cbad39c9cb08] +description = "Full house is not Four of a Kind" + +[b4743c82-1eb8-4a65-98f7-33ad126905cd] +description = "Little Straight" + +[7ac08422-41bf-459c-8187-a38a12d080bc] +description = "Little Straight as Big Straight" + +[97bde8f7-9058-43ea-9de7-0bc3ed6d3002] +description = "Four in order but not a little straight" + +[cef35ff9-9c5e-4fd2-ae95-6e4af5e95a99] +description = "No pairs but not a little straight" + +[fd785ad2-c060-4e45-81c6-ea2bbb781b9d] +description = "Minimum is 1, maximum is 5, but not a little straight" + +[35bd74a6-5cf6-431a-97a3-4f713663f467] +description = "Big Straight" + +[87c67e1e-3e87-4f3a-a9b1-62927822b250] +description = "Big Straight as little straight" + +[c1fa0a3a-40ba-4153-a42d-32bc34d2521e] +description = "No pairs but not a big straight" + +[207e7300-5d10-43e5-afdd-213e3ac8827d] +description = "Choice" + +[b524c0cf-32d2-4b40-8fb3-be3500f3f135] +description = "Yacht as choice" diff --git a/exercises/practice/yacht/Cargo.toml b/exercises/practice/yacht/Cargo.toml new file mode 100644 index 000000000..e28004466 --- /dev/null +++ b/exercises/practice/yacht/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "yacht" +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/yacht/src/lib.rs b/exercises/practice/yacht/src/lib.rs new file mode 100644 index 000000000..32121a6f1 --- /dev/null +++ b/exercises/practice/yacht/src/lib.rs @@ -0,0 +1,21 @@ +#[derive(Debug)] +pub enum Category { + Ones, + Twos, + Threes, + Fours, + Fives, + Sixes, + FullHouse, + FourOfAKind, + LittleStraight, + BigStraight, + Choice, + Yacht, +} + +type Dice = [u8; 5]; + +pub fn score(dice: Dice, category: Category) -> u8 { + todo!("determine the score for {dice:?} in the {category:?}"); +} diff --git a/exercises/practice/yacht/tests/yacht.rs b/exercises/practice/yacht/tests/yacht.rs new file mode 100644 index 000000000..9d8c6cffd --- /dev/null +++ b/exercises/practice/yacht/tests/yacht.rs @@ -0,0 +1,203 @@ +use yacht::*; + +#[test] +fn yacht() { + let expected = 50; + assert_eq!(score([5, 5, 5, 5, 5], Category::Yacht), expected); +} + +#[test] +#[ignore] +fn not_yacht() { + let expected = 0; + assert_eq!(score([1, 3, 3, 2, 5], Category::Yacht), expected); +} + +#[test] +#[ignore] +fn ones() { + let expected = 3; + assert_eq!(score([1, 1, 1, 3, 5], Category::Ones), expected); +} + +#[test] +#[ignore] +fn ones_out_of_order() { + let expected = 3; + assert_eq!(score([3, 1, 1, 5, 1], Category::Ones), expected); +} + +#[test] +#[ignore] +fn no_ones() { + let expected = 0; + assert_eq!(score([4, 3, 6, 5, 5], Category::Ones), expected); +} + +#[test] +#[ignore] +fn twos() { + let expected = 2; + assert_eq!(score([2, 3, 4, 5, 6], Category::Twos), expected); +} + +#[test] +#[ignore] +fn fours() { + let expected = 8; + assert_eq!(score([1, 4, 1, 4, 1], Category::Fours), expected); +} + +#[test] +#[ignore] +fn yacht_counted_as_threes() { + let expected = 15; + assert_eq!(score([3, 3, 3, 3, 3], Category::Threes), expected); +} + +#[test] +#[ignore] +fn yacht_of_3s_counted_as_fives() { + let expected = 0; + assert_eq!(score([3, 3, 3, 3, 3], Category::Fives), expected); +} + +#[test] +#[ignore] +fn fives() { + let expected = 10; + assert_eq!(score([1, 5, 3, 5, 3], Category::Fives), expected); +} + +#[test] +#[ignore] +fn sixes() { + let expected = 6; + assert_eq!(score([2, 3, 4, 5, 6], Category::Sixes), expected); +} + +#[test] +#[ignore] +fn full_house_two_small_three_big() { + let expected = 16; + assert_eq!(score([2, 2, 4, 4, 4], Category::FullHouse), expected); +} + +#[test] +#[ignore] +fn full_house_three_small_two_big() { + let expected = 19; + assert_eq!(score([5, 3, 3, 5, 3], Category::FullHouse), expected); +} + +#[test] +#[ignore] +fn two_pair_is_not_a_full_house() { + let expected = 0; + assert_eq!(score([2, 2, 4, 4, 5], Category::FullHouse), expected); +} + +#[test] +#[ignore] +fn four_of_a_kind_is_not_a_full_house() { + let expected = 0; + assert_eq!(score([1, 4, 4, 4, 4], Category::FullHouse), expected); +} + +#[test] +#[ignore] +fn yacht_is_not_a_full_house() { + let expected = 0; + assert_eq!(score([2, 2, 2, 2, 2], Category::FullHouse), expected); +} + +#[test] +#[ignore] +fn four_of_a_kind() { + let expected = 24; + assert_eq!(score([6, 6, 4, 6, 6], Category::FourOfAKind), expected); +} + +#[test] +#[ignore] +fn yacht_can_be_scored_as_four_of_a_kind() { + let expected = 12; + assert_eq!(score([3, 3, 3, 3, 3], Category::FourOfAKind), expected); +} + +#[test] +#[ignore] +fn full_house_is_not_four_of_a_kind() { + let expected = 0; + assert_eq!(score([3, 3, 3, 5, 5], Category::FourOfAKind), expected); +} + +#[test] +#[ignore] +fn little_straight() { + let expected = 30; + assert_eq!(score([3, 5, 4, 1, 2], Category::LittleStraight), expected); +} + +#[test] +#[ignore] +fn little_straight_as_big_straight() { + let expected = 0; + assert_eq!(score([1, 2, 3, 4, 5], Category::BigStraight), expected); +} + +#[test] +#[ignore] +fn four_in_order_but_not_a_little_straight() { + let expected = 0; + assert_eq!(score([1, 1, 2, 3, 4], Category::LittleStraight), expected); +} + +#[test] +#[ignore] +fn no_pairs_but_not_a_little_straight() { + let expected = 0; + assert_eq!(score([1, 2, 3, 4, 6], Category::LittleStraight), expected); +} + +#[test] +#[ignore] +fn minimum_is_1_maximum_is_5_but_not_a_little_straight() { + let expected = 0; + assert_eq!(score([1, 1, 3, 4, 5], Category::LittleStraight), expected); +} + +#[test] +#[ignore] +fn big_straight() { + let expected = 30; + assert_eq!(score([4, 6, 2, 5, 3], Category::BigStraight), expected); +} + +#[test] +#[ignore] +fn big_straight_as_little_straight() { + let expected = 0; + assert_eq!(score([6, 5, 4, 3, 2], Category::LittleStraight), expected); +} + +#[test] +#[ignore] +fn no_pairs_but_not_a_big_straight() { + let expected = 0; + assert_eq!(score([6, 5, 4, 3, 1], Category::BigStraight), expected); +} + +#[test] +#[ignore] +fn choice() { + let expected = 23; + assert_eq!(score([3, 3, 5, 6, 6], Category::Choice), expected); +} + +#[test] +#[ignore] +fn yacht_as_choice() { + let expected = 10; + assert_eq!(score([2, 2, 2, 2, 2], Category::Choice), expected); +} diff --git a/exercises/shared/.docs/debug.md b/exercises/shared/.docs/debug.md new file mode 100644 index 000000000..cdd663089 --- /dev/null +++ b/exercises/shared/.docs/debug.md @@ -0,0 +1,60 @@ +# Debug + +When a test fails, a message is displayed describing what went wrong and for which input. +You can also use the fact that anything printed to "standard out" will be shown too. +You can write to standard out using `println!`: + +```rust +println!("Debug message"); +``` + +Use braces `{}` to print the value of a variable or expression: + +```rust +let secret_number = 4321; +println!("My banking password is {}, which is better than {}.", secret_number, 1200 + 34); +``` + +Note that not all types can be printed this way, only the ones that can be presented to users unambiguously. +For example, vectors (lists) cannot be printed like this, because it's not clear how a vector should be displayed to users! +For these other types, you can tell the `println!` macro to use _debug-formatting_ with: `{:?}`. +This representation is only intended to be seen by developers. +An example: + +```rust +let words = vec!["hello", "world"]; +println!("words that are often combined: {:?}", words); +``` + +There is even the handy `dbg!` macro, which can be used to debug large expressions inline, without having to pick it apart into separate variable declarations. + +Assume we have the following expression: + +```rust +"hello world".split(' ').collect::>().join("-") +``` + +Now we would like to see what the call to `collect` returns. +We could do the following: + +```rust +let my_temporary_debug_variable = "hello world".split(' ').collect::>(); +println!("{:?}", my_temporary_debug_variable); +my_temporary_debug_variable.join("-") +``` + +But that's tedious! +We don't wan't to rewrite our nice expressions just to be able to debug them. +Please welcome to the stage: `dbg!` + +```rust +dbg!( + "hello world".split(' ').collect::>() +).join("-") +``` + +The whitespace is only for clarity. +You can wrap any expression in `dbg!()`, which will print that value (with debug-formatting). +Afterwards you can keep using the value in a larger expression, as if the `dbg!` wasn't even there. + +Happy debugging! diff --git a/exercises/shared/.docs/help.md b/exercises/shared/.docs/help.md index d4f8c3f5d..471913f3c 100644 --- a/exercises/shared/.docs/help.md +++ b/exercises/shared/.docs/help.md @@ -11,7 +11,10 @@ Generally you should submit all files in which you implemented your solution (`s ## Feedback, Issues, Pull Requests -The GitHub [track repository][github] is the home for all of the Rust exercises. If you have feedback about an exercise, or want to help implement new exercises, head over there and create an issue. Members of the rust track team are happy to help! +Head to [the forum](https://forum.exercism.org/c/programming/rust/) and create a post to provide feedback about an exercise or if you want to help implement new exercises. +Members of the rust track team are happy to help! + +The GitHub [track repository][github] is the home for all of the Rust exercises. If you want to know more about Exercism, take a look at the [contribution guide]. diff --git a/mise.toml b/mise.toml new file mode 100644 index 000000000..0bb095597 --- /dev/null +++ b/mise.toml @@ -0,0 +1,34 @@ +[tasks.test] +description = "simulate CI locally (WIP)" +run = [ + "mise run configlet lint", + "bin/lint_markdown.sh", + "shellcheck bin/*.sh", + "bin/check_exercises.sh", + "CLIPPY=true bin/check_exercises.sh", + "cd rust-tooling && cargo test", + "bin/format_exercises.sh ; git diff --exit-code", +] + +[tasks.add-exercise] +depends = "symlink-problem-specifications" +run = "cd rust-tooling/generate; cargo run --quiet --release -- add" + +[tasks.update-exercise] +depends = "symlink-problem-specifications" +run = "cd rust-tooling/generate; cargo run --quiet --release -- update" + +[tasks.configlet] +depends = "fetch-configlet" +quiet = true +run = "bin/configlet" + +[tasks.fetch-configlet] +hide = true +silent = true +run = "[ -f bin/configlet ] || bin/fetch-configlet" + +[tasks.symlink-problem-specifications] +hide = true +silent = true +run = "[ -L problem-specifications ] || bin/symlink_problem_specifications.sh" diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock new file mode 100644 index 000000000..2e98226bc --- /dev/null +++ b/rust-tooling/Cargo.lock @@ -0,0 +1,1403 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets 0.48.5", +] + +[[package]] +name = "chrono-tz" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d7b79e99bfaa0d47da0687c43aa3b7381938a62ad3a6498599039321f660b7" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + +[[package]] +name = "ci-tests" +version = "0.1.0" +dependencies = [ + "convert_case", + "glob", + "ignore", + "models", + "serde_json", + "tempfile", + "utils", +] + +[[package]] +name = "clap" +version = "4.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deunicode" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae2a35373c5c74340b79ae6780b498b2b183915ec5dacf263aac5a099bf485a" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dyn-clone" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "generate" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "convert_case", + "glob", + "inquire", + "models", + "serde_json", + "slug", + "tera", + "utils", + "uuid", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inquire" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33e7c1ddeb15c9abcbfef6029d8e29f69b52b6d6c891031b88ed91b5065803b" +dependencies = [ + "bitflags 1.3.2", + "crossterm", + "dyn-clone", + "lazy_static", + "newline-converter", + "thiserror", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "models" +version = "0.1.0" +dependencies = [ + "ignore", + "once_cell", + "serde", + "serde_json", + "serde_repr", + "utils", + "uuid", +] + +[[package]] +name = "newline-converter" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slug" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "tera" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "utils" +version = "0.1.0" + +[[package]] +name = "uuid" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +dependencies = [ + "getrandom", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/rust-tooling/Cargo.toml b/rust-tooling/Cargo.toml new file mode 100644 index 000000000..47ea1e1ca --- /dev/null +++ b/rust-tooling/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["generate", "ci-tests", "utils", "models"] +resolver = "2" diff --git a/rust-tooling/ci-tests/Cargo.toml b/rust-tooling/ci-tests/Cargo.toml new file mode 100644 index 000000000..b0ee68b51 --- /dev/null +++ b/rust-tooling/ci-tests/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ci-tests" +version = "0.1.0" +edition = "2024" +description = "Tests to be run in CI to make sure the repo is in good shape" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +# This crate is built in CI to run the tests. +# Be considerate when adding dependencies to keep compile times reasonable. +[dependencies] +convert_case = "0.6.0" +glob = "0.3.1" +ignore = "0.4.20" +models = { version = "0.1.0", path = "../models" } +serde_json = "1.0.108" +tempfile = "3.9.0" +utils = { version = "0.1.0", path = "../utils" } diff --git a/rust-tooling/ci-tests/src/lib.rs b/rust-tooling/ci-tests/src/lib.rs new file mode 100644 index 000000000..9255357f4 --- /dev/null +++ b/rust-tooling/ci-tests/src/lib.rs @@ -0,0 +1,5 @@ +//! dummy lib.rs +//! +//! This crate only exists for the tests. +//! The lib.rs may be used for shared code amoung the tests, +//! if that ever turns out to be useful. diff --git a/rust-tooling/ci-tests/tests/additional_tests_are_documented.rs b/rust-tooling/ci-tests/tests/additional_tests_are_documented.rs new file mode 100644 index 000000000..685a87984 --- /dev/null +++ b/rust-tooling/ci-tests/tests/additional_tests_are_documented.rs @@ -0,0 +1,30 @@ +use std::path::Path; + +use glob::glob; +use models::problem_spec::TestCase; +use utils::fs::cd_into_repo_root; + +#[test] +fn additional_tests_are_documented() { + cd_into_repo_root(); + for entry in glob("exercises/*/*/.meta/additional-tests.json").unwrap() { + let path = entry.unwrap(); + let f = std::fs::File::open(&path).unwrap(); + let reader = std::io::BufReader::new(f); + let test_cases: Vec = serde_json::from_reader(reader).unwrap(); + + fn rec(case: TestCase, path: &Path) { + match case { + TestCase::Single { case } => assert!( + !case.comments.unwrap_or_default().is_empty(), + "missing documentation for additional tests in {}", + path.display() + ), + TestCase::Group { cases, .. } => cases.into_iter().for_each(|c| rec(c, path)), + } + } + for case in test_cases { + rec(case, &path); + } + } +} diff --git a/rust-tooling/ci-tests/tests/bash_script_conventions.rs b/rust-tooling/ci-tests/tests/bash_script_conventions.rs new file mode 100644 index 000000000..907c2077e --- /dev/null +++ b/rust-tooling/ci-tests/tests/bash_script_conventions.rs @@ -0,0 +1,68 @@ +use std::path::PathBuf; + +use convert_case::{Case, Casing}; + +/// Runs a function for each bash script in the bin directory. +/// The function is passed the path of the script. +fn for_all_scripts(f: fn(&str)) { + utils::fs::cd_into_repo_root(); + + for entry in std::fs::read_dir("bin").unwrap() { + let path = entry.unwrap().path(); + let file_name = path.file_name().unwrap().to_str().unwrap(); + + // exceptions: + // configlet is not a bash script, but it must be in `bin`. + // fetch-configlet comes from upstream, we don't control it. + if file_name == "configlet" || file_name == "fetch-configlet" { + continue; + } + + f(file_name) + } +} + +#[test] +fn file_extension() { + for_all_scripts(|file_name| { + assert!( + file_name.ends_with(".sh"), + "name of '{file_name}' should end with .sh" + ); + }) +} + +#[test] +fn snake_case_name() { + for_all_scripts(|file_name| { + let file_name = file_name.trim_end_matches(".sh"); + + assert!( + file_name.is_case(Case::Snake), + "name of '{file_name}' should be snake_case" + ); + }) +} + +/// Notably on nixOS and macOS, bash is not installed in `/bin/bash`. +#[test] +fn portable_shebang() { + for_all_scripts(|file_name| { + let contents = std::fs::read_to_string(PathBuf::from("bin").join(file_name)).unwrap(); + assert!( + contents.starts_with("#!/usr/bin/env bash"), + "'{file_name}' should start with the shebang '#!/usr/bin/env bash'" + ); + }) +} + +#[test] +fn error_handling_flags() { + for_all_scripts(|file_name| { + let contents = std::fs::read_to_string(PathBuf::from("bin").join(file_name)).unwrap(); + assert!( + contents.contains("set -euo pipefail") || contents.contains("set -eo pipefail"), + "'{file_name}' should set error handling flags 'set -eo pipefail'" + ); + }) +} diff --git a/rust-tooling/ci-tests/tests/count_ignores.rs b/rust-tooling/ci-tests/tests/count_ignores.rs new file mode 100644 index 000000000..f0722c047 --- /dev/null +++ b/rust-tooling/ci-tests/tests/count_ignores.rs @@ -0,0 +1,18 @@ +use models::exercise_config::get_all_exercise_paths; + +#[test] +fn count_ignores() { + for path in get_all_exercise_paths() { + let slug = path.split('/').last().unwrap(); + let snake_slug = slug.replace('-', "_"); + let test_path = format!("{path}/tests/{snake_slug}.rs"); + let test_contents = std::fs::read_to_string(test_path).unwrap(); + let num_tests = test_contents.matches("#[test]").count(); + let num_ignores = test_contents.matches("#[ignore]").count(); + assert_eq!( + num_tests, + num_ignores + 1, + "should have one more test than ignore in {slug}" + ) + } +} diff --git a/rust-tooling/ci-tests/tests/docs_are_valid.rs b/rust-tooling/ci-tests/tests/docs_are_valid.rs new file mode 100644 index 000000000..37d814d8a --- /dev/null +++ b/rust-tooling/ci-tests/tests/docs_are_valid.rs @@ -0,0 +1,32 @@ +use glob::glob; +use utils::fs::cd_into_repo_root; + +// https://exercism.org/docs/building/tracks/practice-exercises#h-file-docs-hints-md +#[test] +fn hints_have_correct_heading() { + cd_into_repo_root(); + for entry in glob("exercises/practice/*/.docs/hints.md").unwrap() { + let path = entry.unwrap(); + let content = std::fs::read_to_string(&path).unwrap(); + assert!( + content.starts_with("## General"), + "incorrect heading in {}", + path.display() + ) + } +} + +// https://exercism.org/docs/building/tracks/practice-exercises#h-file-docs-introduction-append-md +#[test] +fn instructions_append_have_correct_heading() { + cd_into_repo_root(); + for entry in glob("exercises/*/*/.docs/instructions.append.md").unwrap() { + let path = entry.unwrap(); + let content = std::fs::read_to_string(&path).unwrap(); + assert!( + content.starts_with("# Instructions append"), + "incorrect heading in {}", + path.display() + ) + } +} diff --git a/rust-tooling/ci-tests/tests/no_authors_in_cargo_toml.rs b/rust-tooling/ci-tests/tests/no_authors_in_cargo_toml.rs new file mode 100644 index 000000000..7cbece376 --- /dev/null +++ b/rust-tooling/ci-tests/tests/no_authors_in_cargo_toml.rs @@ -0,0 +1,16 @@ +use models::exercise_config::get_all_exercise_paths; + +/// The package manifest of each exercise should not contain an `authors` field. +/// The authors are already specified in the track configuration. +#[test] +fn no_authors_in_cargo_toml() { + let cargo_toml_paths = get_all_exercise_paths().map(|p| format!("{p}/Cargo.toml")); + + for path in cargo_toml_paths { + let cargo_toml = std::fs::read_to_string(path).unwrap(); + assert!( + !cargo_toml.contains("authors"), + "Cargo.toml should not contain an 'authors' field" + ); + } +} diff --git a/rust-tooling/ci-tests/tests/no_trailing_whitespace.rs b/rust-tooling/ci-tests/tests/no_trailing_whitespace.rs new file mode 100644 index 000000000..aa368e3ff --- /dev/null +++ b/rust-tooling/ci-tests/tests/no_trailing_whitespace.rs @@ -0,0 +1,32 @@ +use std::path::Path; + +fn contains_trailing_whitespace(p: &Path) -> bool { + let contents = std::fs::read_to_string(p).unwrap(); + for line in contents.lines() { + if line != line.trim_end() { + return true; + } + } + false +} + +#[test] +fn no_trailing_whitespace() { + utils::fs::cd_into_repo_root(); + + for entry in ignore::Walk::new("./") { + let entry = entry.unwrap(); + if !entry.file_type().is_some_and(|t| t.is_file()) { + continue; + } + let path = entry.path(); + let ext = path.extension().unwrap_or_default().to_str().unwrap(); + if matches!(ext, "rs" | "toml" | "md" | "sh") { + assert!( + !contains_trailing_whitespace(path), + "trailing whitespace in {}", + path.display() + ); + } + } +} diff --git a/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs b/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs new file mode 100644 index 000000000..7fa88b88a --- /dev/null +++ b/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs @@ -0,0 +1,102 @@ +//! A simple exercise stub might look like this: +//! +//! ```rust +//! pub fn fn_to_implement(parameter: i32) -> i32 { +//! todo!() +//! } +//! ``` +//! +//! The problem with this is that a warning will be generated because +//! `parameter` is unused. There are two obvious ways to fix this: +//! +//! 1. Prefix the parameter name with an underscore +//! +//! ```rust +//! pub fn fn_to_implement(_parameter: i32) -> i32 { +//! todo!() +//! } +//! ``` +//! +//! 2. Use the parameter in the `todo!()` macro +//! +//! ```rust +//! pub fn fn_to_implement(parameter: i32) -> i32 { +//! todo!("use {parameter:?} to solve the exercise") +//! } +//! ``` +//! +//! The second approach has the advantage that it disappears automatically. +//! Students will remove the macro in the process of solving the exercise. +//! The first approach requires a manual step to remove the underscore. +//! Some students don't do this (as was noticed during mentoring sessions). +//! This is ugly and even unidiomatic, because it means the compiler won't +//! warn about unused variables when that would actually be helpful. +//! +//! The second approach does have a disadvantage: it requires the parameter +//! to be `Debug`. This is usually not a problem, most of our test inputs are +//! indeed `Debug`, but it becomes a problem with generics. If a parameter is +//! generic, there must be a `Debug` bound on it. That would usually be +//! fulfilled, but it could be confusing to the students why the bound exists. +//! In that case, the first approach may be used as a fallback. +//! +//! This test makes sure the second approach is used consistently, with +//! explicitly listed exceptions. + +use glob::glob; +use utils::fs::cd_into_repo_root; + +static EXCEPTIONS: &[&str] = &[ + "list-ops", // has generics + "circular-buffer", // has generics + "custom-set", // has generics + "doubly-linked-list", // has generics + "fizzy", // has generics + "paasio", // has generics + "react", // has generics + "roman-numerals", // std::fmt::Formatter is not Debug + "simple-linked-list", // has generics + "sublist", // has generics +]; + +fn line_is_not_a_comment(line: &&str) -> bool { + !line.trim().starts_with("//") +} + +#[test] +fn no_underscore_prefixed_idents() { + cd_into_repo_root(); + for lib_rs in glob("exercises/*/*/src/lib.rs") + .unwrap() + .map(Result::unwrap) + { + if EXCEPTIONS + .iter() + .any(|e| lib_rs.to_string_lossy().contains(e)) + { + continue; + } + + let exercise_stub = std::fs::read_to_string(&lib_rs).unwrap(); + + for identifier_like in exercise_stub + .lines() + .filter(line_is_not_a_comment) + // This is extremely crude parsing, but it can be improved if the need arises. + .flat_map(|line| line.split(|c: char| !(c.is_alphabetic() || c == '_'))) + { + if identifier_like.starts_with('_') && identifier_like != "_" { + panic!( + "Exercise stub in {} contains underscore-prefixed identifier {} + + ╔════════════════════════════════════════════════════════════════╗ + ║ The use of an underscore-prefixed identifier may be justified. ║ + ║ If you think it is, add it to the list of exceptions. ║ + ╚════════════════════════════════════════════════════════════════╝ +", + lib_rs.display(), + identifier_like + ); + } + } + } +} diff --git a/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs new file mode 100644 index 000000000..82708b045 --- /dev/null +++ b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs @@ -0,0 +1,77 @@ +use std::process::{Command, Stdio}; + +use glob::glob; + +#[test] +fn stubs_are_warning_free() { + utils::fs::cd_into_repo_root(); + + let temp_dir = tempfile::tempdir().unwrap(); + + let mut handles = vec![]; + + for manifest in glob("exercises/*/*/Cargo.toml") + .unwrap() + .map(Result::unwrap) + { + if std::fs::read_to_string(manifest.parent().unwrap().join(".meta").join("config.json")) + .unwrap() + .contains("allowed-to-not-compile") + { + continue; + } + let slug = manifest + .parent() + .unwrap() + .file_name() + .unwrap() + .to_str() + .unwrap(); + let handle = Command::new("cargo") + .args([ + "clippy", + "--quiet", + "--tests", + "--manifest-path", + &manifest.display().to_string(), + "--target-dir", + &temp_dir.path().join(slug).display().to_string(), + "--", + // necessary for clippy to return a non-zero exit code + // if it finds warnings + "--deny", + "warnings", + ]) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + handles.push((slug.to_string(), handle)); + } + + let mut log = String::new(); + + for (slug, handle) in handles { + let output = handle.wait_with_output().unwrap(); + + if output.status.success() { + continue; + } + let stderr = String::from_utf8(output.stderr).unwrap(); + log.push_str(&format!( + "\ +################################################################ +################ +################ {slug} + +{stderr} + +" + )); + } + + if !log.is_empty() { + std::fs::write("clippy.log", &log).expect("should write clippy.log"); + } + assert!(log.is_empty(), "{log}"); +} diff --git a/rust-tooling/generate/Cargo.toml b/rust-tooling/generate/Cargo.toml new file mode 100644 index 000000000..2525ee049 --- /dev/null +++ b/rust-tooling/generate/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "generate" +version = "0.1.0" +edition = "2024" +description = "Generates exercise boilerplate, especially test cases" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lints.clippy] +unwrap_used = "warn" +expect_used = "warn" +panic = "warn" + +[dependencies] +anyhow = "1.0.81" +clap = { version = "4.4.8", features = ["derive"] } +convert_case = "0.6.0" +glob = "0.3.1" +inquire = "0.6.2" +models = { version = "0.1.0", path = "../models" } +serde_json = { version = "1.0.105", features = ["arbitrary_precision", "preserve_order"] } +slug = "0.1.5" +tera = "1.19.1" +utils = { version = "0.1.0", path = "../utils" } +uuid = { version = "1.4.1", features = ["v4"] } diff --git a/rust-tooling/generate/src/cli.rs b/rust-tooling/generate/src/cli.rs new file mode 100644 index 000000000..4461e30c8 --- /dev/null +++ b/rust-tooling/generate/src/cli.rs @@ -0,0 +1,225 @@ +use anyhow::{Context, Result}; +use clap::{Parser, Subcommand}; +use convert_case::{Case, Casing}; +use glob::glob; +use inquire::{Select, Text, validator::Validation}; +use models::track_config; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +pub struct CliArgs { + #[command(subcommand)] + pub command: Command, +} + +#[derive(Subcommand)] +pub enum Command { + Add(AddArgs), + Update(UpdateArgs), +} + +impl std::fmt::Display for Command { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Command::Add(_) => write!(f, "Add"), + Command::Update(_) => write!(f, "Update"), + } + } +} + +#[derive(Parser)] +pub struct AddArgs { + #[arg(short, long)] + slug: Option, + + #[arg(short, long)] + name: Option, + + #[arg(short, long)] + difficulty: Option, + + #[arg(short, long)] + author: Option, +} + +pub struct FullAddArgs { + pub slug: String, + pub name: String, + pub difficulty: track_config::Difficulty, + pub author: String, +} + +impl AddArgs { + pub fn unwrap_args_or_prompt(self) -> Result { + let slug = match self.slug { + Some(slug) => slug, + _ => prompt_for_add_slug()?, + }; + let name = match self.name { + Some(name) => name, + None => prompt_for_exercise_name(&slug)?, + }; + let difficulty = match self.difficulty { + Some(diff) => diff, + None => prompt_for_difficulty()?, + } + .into(); + + let author = match self.author { + Some(author) => author, + None => prompt_for_author_name()?, + }; + + Ok(FullAddArgs { + slug, + name, + difficulty, + author, + }) + } +} + +#[derive(Parser)] +pub struct UpdateArgs { + /// slug of the exercise to update + #[arg(short, long)] + slug: Option, +} + +impl UpdateArgs { + pub fn unwrap_slug_or_prompt(self) -> Result { + match self.slug { + Some(slug) => Ok(slug), + _ => prompt_for_update_slug(), + } + } +} + +pub fn prompt_for_update_slug() -> Result { + let implemented_exercises = glob("exercises/practice/*") + .map_err(anyhow::Error::from)? + .filter_map(Result::ok) + .flat_map(|path| path.file_name()?.to_str().map(|s| s.to_owned())) + .collect::>(); + + Select::new( + "Which exercise would you like to update?", + implemented_exercises, + ) + .prompt() + .context("failed to select slug") +} + +pub fn prompt_for_add_slug() -> Result { + let implemented_exercises = glob("exercises/concept/*") + .map_err(anyhow::Error::from)? + .chain(glob("exercises/practice/*").map_err(anyhow::Error::from)?) + .filter_map(Result::ok) + .flat_map(|path| path.file_name()?.to_str().map(|s| s.to_owned())) + .collect::>(); + + let todo_with_spec = glob("problem-specifications/exercises/*") + .map_err(anyhow::Error::from)? + .filter_map(Result::ok) + .flat_map(|path| path.file_name()?.to_str().map(|s| s.to_owned())) + .filter(|e| !implemented_exercises.contains(e)) + .collect::>(); + + println!("(suggestions are from problem-specifications)"); + Text::new("What's the slug of your exercise?") + .with_autocomplete(move |input: &_| { + let mut slugs = todo_with_spec.clone(); + slugs.retain(|e| e.starts_with(input)); + Ok(slugs) + }) + .with_validator(|input: &str| { + if input.is_empty() { + Ok(Validation::Invalid("The slug must not be empty.".into())) + } else if !input.is_case(Case::Kebab) { + Ok(Validation::Invalid( + "The slug must be in kebab-case.".into(), + )) + } else { + Ok(Validation::Valid) + } + }) + .with_validator(move |input: &str| { + if !implemented_exercises.contains(&input.to_string()) { + Ok(Validation::Valid) + } else { + Ok(Validation::Invalid( + "An exercise with this slug already exists.".into(), + )) + } + }) + .prompt() + .context("failed to prompt for slug") +} + +pub fn prompt_for_exercise_name(slug: &str) -> Result { + Text::new("What's the name of your exercise?") + .with_initial_value(&slug.to_case(Case::Title)) + .prompt() + .context("failed to prompt for exercise name") +} + +pub fn prompt_for_author_name() -> Result { + Text::new("What is your Github username? (author credit)") + .prompt() + .context("failed to prompt for author name") +} + +/// Mostly a clone of the `Difficulty` enum from `models::track_config`. +/// The purpose of this is that we can implement cli-specific traits in this crate. +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +#[repr(u8)] +pub enum Difficulty { + Easy = 1, + Medium = 4, + // I'm not sure why there are two medium difficulties + Medium2 = 7, + Hard = 10, +} + +impl From for track_config::Difficulty { + fn from(value: Difficulty) -> Self { + match value { + Difficulty::Easy => track_config::Difficulty::Easy, + Difficulty::Medium => track_config::Difficulty::Medium, + Difficulty::Medium2 => track_config::Difficulty::Medium2, + Difficulty::Hard => track_config::Difficulty::Hard, + } + } +} + +impl std::fmt::Display for Difficulty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Difficulty::Easy => write!(f, "Easy (1)"), + Difficulty::Medium => write!(f, "Medium (4)"), + Difficulty::Medium2 => write!(f, "Medium (7)"), + Difficulty::Hard => write!(f, "Hard (10)"), + } + } +} + +pub fn prompt_for_difficulty() -> Result { + Select::new( + "What's the difficulty of your exercise?", + vec![ + Difficulty::Easy, + Difficulty::Medium, + Difficulty::Medium2, + Difficulty::Hard, + ], + ) + .prompt() + .context("failed to select difficulty") +} + +pub fn prompt_for_template_generation() -> bool { + inquire::Confirm::new("Would you like to add a template for test generation?") + .with_default(false) + .prompt() + .unwrap_or_default() +} diff --git a/rust-tooling/generate/src/custom_filters.rs b/rust-tooling/generate/src/custom_filters.rs new file mode 100644 index 000000000..755dba17b --- /dev/null +++ b/rust-tooling/generate/src/custom_filters.rs @@ -0,0 +1,75 @@ +use std::collections::HashMap; + +use tera::{Result, Value}; + +type Filter = fn(&Value, &HashMap) -> Result; + +pub static CUSTOM_FILTERS: &[(&str, Filter)] = &[ + ("to_hex", to_hex), + ("make_ident", make_ident), + ("fmt_num", fmt_num), +]; + +pub fn to_hex(value: &Value, _args: &HashMap) -> Result { + let Some(value) = value.as_u64() else { + return Err(tera::Error::call_filter( + "to_hex filter expects an unsigned integer", + "serde_json::value::Value::as_u64", + )); + }; + Ok(serde_json::Value::String(format!("{:x}", value))) +} + +pub fn make_ident(value: &Value, _args: &HashMap) -> Result { + let Some(value) = value.as_str() else { + return Err(tera::Error::call_filter( + "make_ident filter expects a string", + "serde_json::value::Value::as_str", + )); + }; + let value = camel_to_snake_case(value); + let value = slug::slugify(value).replace('-', "_"); + if !value.chars().next().unwrap_or_default().is_alphabetic() { + // identifiers cannot start with digits etc. + return Ok(Value::String(format!("test_{value}"))); + } + Ok(Value::String(value)) +} + +fn camel_to_snake_case(input: &str) -> String { + let mut chars: Vec<_> = input.chars().collect(); + let mut i = 0; + while i + 1 < chars.len() { + let (left, right) = (chars[i], chars[i + 1]); + if right.is_ascii_uppercase() { + chars[i + 1] = right.to_ascii_lowercase(); + if left.is_ascii_alphabetic() { + chars.insert(i + 1, '_'); + } + } + i += 1; + } + chars.into_iter().collect() +} + +pub fn fmt_num(value: &Value, _args: &HashMap) -> Result { + let Some(value) = value.as_number() else { + return Err(tera::Error::call_filter( + "fmt_num filter expects a number", + "serde_json::value::Value::as_number", + )); + }; + let mut num: Vec<_> = value.to_string().into(); + num.reverse(); + + let mut pretty_digits = num + .chunks(3) + .flat_map(|digits| digits.iter().copied().chain([b'_'])) + .collect::>(); + if pretty_digits.last() == Some(&b'_') { + pretty_digits.pop(); + } + pretty_digits.reverse(); + let pretty_num = String::from_utf8(pretty_digits).unwrap_or_default(); + Ok(Value::String(pretty_num)) +} diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs new file mode 100644 index 000000000..fc58df20e --- /dev/null +++ b/rust-tooling/generate/src/lib.rs @@ -0,0 +1,172 @@ +use std::{ + io::Write, + process::{Command, Stdio}, +}; + +use anyhow::{Context, Result}; +use tera::Tera; + +use custom_filters::CUSTOM_FILTERS; +use models::{ + exercise_config::get_excluded_tests, + problem_spec::{TestCase, get_additional_test_cases, get_canonical_data}, +}; + +mod custom_filters; + +pub struct GeneratedExercise { + pub gitignore: String, + pub manifest: String, + pub lib_rs: String, + pub example: String, + pub test_template: String, + pub tests: String, +} + +pub fn new(slug: &str) -> GeneratedExercise { + let crate_name = slug.replace('-', "_"); + + let tests = generate_tests(slug).unwrap_or_else(|e| { + eprintln!("WARNING: Failed to generate tests:\n{e:?}"); + FALLBACK_TESTS.into() + }); + + GeneratedExercise { + gitignore: GITIGNORE.into(), + manifest: generate_manifest(&crate_name), + lib_rs: LIB_RS.into(), + example: EXAMPLE_RS.into(), + test_template: TEST_TEMPLATE.into(), + tests, + } +} + +static GITIGNORE: &str = "\ +/target +Cargo.lock +"; + +fn generate_manifest(crate_name: &str) -> String { + format!( + "\ +[package] +name = \"{crate_name}\" +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] +" + ) +} + +static LIB_RS: &str = "\ +pub fn TODO(input: TODO) -> TODO { + todo!(\"use {input} to solve the exercise\") +} +"; + +static EXAMPLE_RS: &str = "\ +pub fn TODO(input: TODO) -> TODO { + TODO +} +"; + +static TEST_TEMPLATE: &str = include_str!("../templates/default_test_template.tera"); + +static FALLBACK_TESTS: &str = "\ +#[test] +fn invalid_template() { + panic!(\"The exercise generator failed to produce valid tests from the template. Fix `.meta/test_template.tera`. To write tests manually you MUST delete the template.\"); +} +"; + +fn remove_excluded_tests(cases: &mut Vec, excluded_tests: &[String]) { + cases.retain(|case| match case { + TestCase::Single { case } => !excluded_tests.contains(&case.uuid), + _ => true, + }); + for case in cases { + if let TestCase::Group { cases, .. } = case { + remove_excluded_tests(cases, excluded_tests) + } + } +} + +fn generate_tests(slug: &str) -> Result { + let mut cases = { + let mut cases = get_canonical_data(slug) + .map(|data| data.cases) + .unwrap_or_default(); + cases.extend_from_slice(&get_additional_test_cases(slug)); + cases + }; + let excluded_tests = get_excluded_tests(slug); + let mut template = get_test_template(slug).context("failed to get test template")?; + if template.get_template_names().next().is_none() { + template + .add_raw_template("test_template.tera", TEST_TEMPLATE) + .context("failed to add default template")?; + } + for (name, filter) in CUSTOM_FILTERS { + template.register_filter(name, filter); + } + + remove_excluded_tests(&mut cases, &excluded_tests); + + let mut context = tera::Context::new(); + context.insert("cases", &cases); + + let rendered = template + .render("test_template.tera", &context) + .with_context(|| format!("failed to render template of '{slug}'"))?; + + // Remove ignore-annotation on first test. + // This could be done in the template itself, + // but doing it here makes all templates more readable. + // Also, it is harder to do this in the template when the template + // generates test functions inside a macro for modules. + let rendered = rendered.replacen("#[ignore]\n", "", 1); + + let mut child = Command::new("rustfmt") + .args(["--color=always", "--edition=2024"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .context("failed to spawn rustfmt process")?; + + child + .stdin + .as_mut() + .context("failed to get rustfmt's stdin")? + .write_all(rendered.as_bytes()) + .context("failed to write to rustfmt's stdin")?; + let rustfmt_out = child + .wait_with_output() + .context("failed to get rustfmt's output")?; + + if !rustfmt_out.status.success() { + let rustfmt_error = String::from_utf8_lossy(&rustfmt_out.stderr); + let mut last_16_error_lines = rustfmt_error.lines().rev().take(16).collect::>(); + last_16_error_lines.reverse(); + let last_16_error_lines = last_16_error_lines.join("\n"); + + eprintln!( + "{last_16_error_lines}\ +^ last 16 lines of errors from rustfmt +Check the test template (.meta/test_template.tera) +It probably generates invalid Rust code." + ); + + // still return the unformatted content to be written to the file + return Ok(rendered); + } + Ok(String::from_utf8_lossy(&rustfmt_out.stdout).into_owned()) +} + +pub fn get_test_template(slug: &str) -> Result { + Tera::new(format!("exercises/practice/{slug}/.meta/*.tera").as_str()).map_err(Into::into) +} diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs new file mode 100644 index 000000000..984223e3b --- /dev/null +++ b/rust-tooling/generate/src/main.rs @@ -0,0 +1,158 @@ +use std::path::PathBuf; + +use anyhow::{Context, Result}; +use clap::Parser; +use cli::{AddArgs, FullAddArgs, UpdateArgs, prompt_for_template_generation}; +use models::{ + exercise_config::PracticeExercise, + track_config::{self, TRACK_CONFIG}, +}; + +mod cli; + +fn main() -> Result<()> { + utils::fs::cd_into_repo_root(); + + let cli_args = cli::CliArgs::parse(); + + match cli_args.command { + cli::Command::Add(args) => add_exercise(args), + cli::Command::Update(args) => update_exercise(args), + } +} + +fn add_exercise(args: AddArgs) -> Result<()> { + let FullAddArgs { + slug, + name, + difficulty, + author, + } = args.unwrap_args_or_prompt()?; + + let config = track_config::PracticeExercise::new(slug.clone(), name, difficulty); + + let mut track_config = TRACK_CONFIG.clone(); + track_config.exercises.practice.push(config); + let mut new_config = serde_json::to_string_pretty(&track_config) + .context("failed to deserialize track config")? + .to_string(); + new_config += "\n"; + std::fs::write("config.json", new_config)?; + + println!( + "\ +Added your exercise to config.json. +You can add practices, prerequisites and topics if you like." + ); + + make_configlet_generate_what_it_can(&slug)?; + + if let Err(e) = ensure_exercise_files_are_filled(&slug, (!author.is_empty()).then_some(&author)) + { + eprintln!("WARNING: failed to ensure exercise files are filled:\n{e:?}"); + } + + let is_update = false; + generate_exercise_files(&slug, is_update) +} + +fn update_exercise(args: UpdateArgs) -> Result<()> { + let slug = args.unwrap_slug_or_prompt()?; + + make_configlet_generate_what_it_can(&slug)?; + + let author = None; + if let Err(e) = ensure_exercise_files_are_filled(&slug, author) { + eprintln!("WARNING: failed to ensure exercise files are filled:\n{e:?}"); + } + + let is_update = true; + generate_exercise_files(&slug, is_update) +} + +fn make_configlet_generate_what_it_can(slug: &str) -> Result<()> { + let status = std::process::Command::new("mise") + .args([ + "run", + "configlet", + "sync", + "--update", + "--yes", + "--docs", + "--metadata", + "--tests", + "include", + "--exercise", + slug, + ]) + .status() + .context("failed to run configlet sync")?; + if !status.success() { + anyhow::bail!("configlet sync failed"); + } + + Ok(()) +} + +fn ensure_exercise_files_are_filled(slug: &str, author: Option<&str>) -> Result<()> { + let config_path = format!("exercises/practice/{slug}/.meta/config.json"); + let config = std::fs::read_to_string(&config_path).context("failed to read exercise config")?; + let mut config: PracticeExercise = + serde_json::from_str(&config).context("failed to deserialize exercise config")?; + + let snake_slug = slug.replace('-', "_"); + + let ensure_filled = |list: &mut Vec, content: &str| { + if !list.iter().any(|s| s == content) { + list.push(content.into()) + } + }; + ensure_filled(&mut config.files.solution, "src/lib.rs"); + ensure_filled(&mut config.files.solution, "Cargo.toml"); + ensure_filled(&mut config.files.test, &format!("tests/{snake_slug}.rs")); + ensure_filled(&mut config.files.example, ".meta/example.rs"); + + if let Some(author) = author { + ensure_filled(&mut config.authors, author); + } + + let mut config = + serde_json::to_string_pretty(&config).context("failed to deserialize config")?; + config.push('\n'); // trailing newline + std::fs::write(config_path, config).context("failed to write config")?; + + Ok(()) +} + +fn generate_exercise_files(slug: &str, is_update: bool) -> Result<()> { + let exercise = generate::new(slug); + + let exercise_path = PathBuf::from("exercises/practice").join(slug); + + if !is_update { + std::fs::write(exercise_path.join(".gitignore"), exercise.gitignore)?; + std::fs::write(exercise_path.join("Cargo.toml"), exercise.manifest)?; + std::fs::create_dir(exercise_path.join("src")).ok(); + std::fs::write(exercise_path.join("src/lib.rs"), exercise.lib_rs)?; + std::fs::write(exercise_path.join(".meta/example.rs"), exercise.example)?; + } + + let template_path = exercise_path.join(".meta/test_template.tera"); + if !template_path.exists() && (!is_update || prompt_for_template_generation()) { + // Some exercises have existing test cases that are difficult to migrate + // to the test generator. That's why we only generate a template for + // existing exercises if the users requests it. + std::fs::write(&template_path, exercise.test_template)?; + } + + let snake_slug = slug.replace('-', "_"); + + std::fs::create_dir(exercise_path.join("tests")).ok(); + if template_path.exists() { + std::fs::write( + exercise_path.join(format!("tests/{snake_slug}.rs")), + exercise.tests, + )?; + } + Ok(()) +} diff --git a/rust-tooling/generate/templates/default_test_template.tera b/rust-tooling/generate/templates/default_test_template.tera new file mode 100644 index 000000000..d20dde1f6 --- /dev/null +++ b/rust-tooling/generate/templates/default_test_template.tera @@ -0,0 +1,12 @@ +use crate_name::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = {{ test.input | json_encode() }}; + let output = function_name(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/rust-tooling/models/Cargo.toml b/rust-tooling/models/Cargo.toml new file mode 100644 index 000000000..1fcdebad4 --- /dev/null +++ b/rust-tooling/models/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "models" +version = "0.1.0" +edition = "2024" +description = "Data structures for exercism stuff" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ignore = "0.4.20" +once_cell = "1.18.0" +serde = { version = "1.0.188", features = ["derive"] } +serde_json = { version = "1.0.105", features = ["preserve_order"] } +serde_repr = "0.1.17" +utils = { version = "0.1.0", path = "../utils" } +uuid = { version = "1.6.1", features = ["v4"] } diff --git a/rust-tooling/models/src/exercise_config.rs b/rust-tooling/models/src/exercise_config.rs new file mode 100644 index 000000000..cdd23d954 --- /dev/null +++ b/rust-tooling/models/src/exercise_config.rs @@ -0,0 +1,127 @@ +//! This module provides a data structure for exercise configuration stored in +//! `.meta/config`. It is capable of serializing and deserializing the +//! configuration, for example with `serde_json`. + +use serde::{Deserialize, Serialize}; + +use crate::track_config::TRACK_CONFIG; + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConceptExercise { + pub authors: Vec, + pub contributors: Option>, + pub files: ConceptFiles, + pub icon: Option, + pub blurb: String, + pub source: Option, + pub source_url: Option, + pub test_runner: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConceptFiles { + pub solution: Vec, + pub test: Vec, + pub exemplar: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PracticeExercise { + pub authors: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub contributors: Option>, + pub files: PracticeFiles, + #[serde(skip_serializing_if = "Option::is_none")] + pub icon: Option, + pub blurb: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub source_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub test_runner: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub custom: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PracticeFiles { + pub solution: Vec, + pub test: Vec, + pub example: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Custom { + #[serde(rename = "allowed-to-not-compile")] + pub allowed_to_not_compile: Option, + #[serde(rename = "test-in-release-mode")] + pub test_in_release_mode: Option, +} + +pub fn get_all_concept_exercise_paths() -> impl Iterator { + use utils::fs::REPO_ROOT_DIR; + + TRACK_CONFIG + .exercises + .concept + .iter() + .map(move |e| format!("{REPO_ROOT_DIR}/exercises/concept/{}", e.slug)) +} + +pub fn get_all_practice_exercise_paths() -> impl Iterator { + use utils::fs::REPO_ROOT_DIR; + + TRACK_CONFIG + .exercises + .practice + .iter() + .map(move |e| format!("{REPO_ROOT_DIR}/exercises/practice/{}", e.slug)) +} + +pub fn get_all_exercise_paths() -> impl Iterator { + get_all_concept_exercise_paths().chain(get_all_practice_exercise_paths()) +} + +#[test] +fn deserialize_all() { + for path in get_all_concept_exercise_paths() { + let config_path = format!("{path}/.meta/config.json"); + let config_contents = std::fs::read_to_string(config_path).unwrap(); + let _: ConceptExercise = serde_json::from_str(config_contents.as_str()) + .expect("should deserialize concept exercise config"); + } + for path in get_all_practice_exercise_paths() { + let config_path = format!("{path}/.meta/config.json"); + let config_contents = std::fs::read_to_string(config_path).unwrap(); + let _: PracticeExercise = serde_json::from_str(config_contents.as_str()) + .expect("should deserialize practice exercise config"); + } +} + +/// Returns the uuids of the tests excluded in .meta/tests.toml +pub fn get_excluded_tests(slug: &str) -> Vec { + let path = std::path::PathBuf::from("exercises/practice") + .join(slug) + .join(".meta/tests.toml"); + let Ok(contents) = std::fs::read_to_string(path) else { + return vec![]; + }; + + let mut excluded_tests = Vec::new(); + + // shitty toml parser + for case in contents.split("\n[").skip(1) { + let (uuid, rest) = case.split_once(']').unwrap(); + if rest.contains("include = false") { + excluded_tests.push(uuid.to_string()); + } + } + + excluded_tests +} diff --git a/rust-tooling/models/src/lib.rs b/rust-tooling/models/src/lib.rs new file mode 100644 index 000000000..6833d8f0e --- /dev/null +++ b/rust-tooling/models/src/lib.rs @@ -0,0 +1,3 @@ +pub mod exercise_config; +pub mod problem_spec; +pub mod track_config; diff --git a/rust-tooling/models/src/problem_spec.rs b/rust-tooling/models/src/problem_spec.rs new file mode 100644 index 000000000..88e6af04f --- /dev/null +++ b/rust-tooling/models/src/problem_spec.rs @@ -0,0 +1,89 @@ +use serde::{Deserialize, Serialize}; + +/// Remember that this is actually optional, not all exercises +/// must have a canonical data file in the problem-specifications repo. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct CanonicalData { + pub exercise: String, + pub comments: Option>, + pub cases: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +#[allow(clippy::large_enum_variant)] +pub enum TestCase { + Single { + #[serde(flatten)] + case: SingleTestCase, + }, + Group { + description: String, + comments: Option>, + cases: Vec, + }, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct SingleTestCase { + pub uuid: String, + pub reimplements: Option, + pub description: String, + pub comments: Option>, + pub scenarios: Option>, + pub property: String, + pub input: serde_json::Value, + pub expected: serde_json::Value, +} + +pub fn get_canonical_data(slug: &str) -> Option { + let path = std::path::PathBuf::from("problem-specifications/exercises") + .join(slug) + .join("canonical-data.json"); + let contents = std::fs::read_to_string(&path).ok()?; + serde_json::from_str(contents.as_str()).unwrap_or_else(|e| { + panic!( + "should deserialize canonical data for {}: {e}", + path.display() + ) + }) +} + +/// The Rust track may define additional test cases in +/// `.meta/additional-tests.json`, which are not appropriate +/// to upstream for all other languages to implement. +pub fn get_additional_test_cases(slug: &str) -> Vec { + let path = std::path::PathBuf::from("exercises/practice") + .join(slug) + .join(".meta/additional-tests.json"); + if !path.exists() { + return Vec::new(); + } + let contents = std::fs::read_to_string(&path).unwrap(); + serde_json::from_str(contents.as_str()).unwrap_or_else(|e| { + panic!( + "should deserialize additional tests for {}: {e}", + path.display() + ) + }) +} + +#[test] +fn deserialize_canonical_data() { + utils::fs::cd_into_repo_root(); + for entry in ignore::Walk::new("problem-specifications/exercises") + .filter_map(|e| e.ok()) + .filter(|e| e.file_name().to_str().unwrap() == "canonical-data.json") + { + let contents = std::fs::read_to_string(entry.path()).unwrap(); + let _: CanonicalData = serde_json::from_str(contents.as_str()).unwrap_or_else(|e| { + panic!( + "should deserialize canonical data for {}: {e}", + entry.path().display() + ) + }); + } +} diff --git a/rust-tooling/models/src/track_config.rs b/rust-tooling/models/src/track_config.rs new file mode 100644 index 000000000..0704b4564 --- /dev/null +++ b/rust-tooling/models/src/track_config.rs @@ -0,0 +1,156 @@ +//! This module provides a data structure for the track configuration. +//! It is capable of serializing and deserializing the configuration, +//! for example with `serde_json`. +//! +//! Some definitions may not be perfectly precise. +//! Feel free to improve this if need be. +//! (e.g. replace `String` with an enum of possible values) + +use std::collections::HashMap; + +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +pub static TRACK_CONFIG: Lazy = Lazy::new(|| { + let config = include_str!("../../../config.json"); + serde_json::from_str(config).expect("should deserialize the track config") +}); + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TrackConfig { + pub language: String, + pub slug: String, + pub active: bool, + pub status: Status, + pub blurb: String, + pub version: u8, + pub online_editor: OnlineEditor, + pub test_runner: HashMap, + pub files: Files, + pub exercises: Exercises, + pub concepts: Vec, + pub key_features: Vec, + pub tags: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Status { + pub concept_exercises: bool, + pub test_runner: bool, + pub representer: bool, + pub analyzer: bool, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct OnlineEditor { + pub indent_style: String, + pub indent_size: u8, + pub highlightjs_language: String, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Files { + pub solution: Vec, + pub test: Vec, + pub example: Vec, + pub exemplar: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Exercises { + pub concept: Vec, + pub practice: Vec, + pub foregone: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ConceptExerciseStatus { + Active, + Wip, +} + +#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum Difficulty { + Easy = 1, + Medium = 4, + // I'm not sure why there are two medium difficulties + Medium2 = 7, + Hard = 10, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConceptExercise { + pub slug: String, + pub uuid: String, + pub name: String, + pub difficulty: Difficulty, + pub concepts: Vec, + pub prerequisites: Vec, + pub status: ConceptExerciseStatus, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum PracticeExerciseStatus { + Deprecated, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PracticeExercise { + pub slug: String, + pub name: String, + pub uuid: String, + pub practices: Vec, + pub prerequisites: Vec, + pub difficulty: Difficulty, + pub topics: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, +} + +impl PracticeExercise { + pub fn new(slug: String, name: String, difficulty: Difficulty) -> Self { + Self { + slug, + name, + uuid: uuid::Uuid::new_v4().to_string(), + practices: Vec::new(), + prerequisites: Vec::new(), + difficulty, + topics: Vec::new(), + status: None, + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConceptConfig { + pub uuid: String, + pub slug: String, + pub name: String, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct KeyFeature { + pub icon: String, + pub title: String, + pub content: String, +} + +#[test] +fn test_deserialize() { + // force deserialization of lazy static + assert!(TRACK_CONFIG.active, "should deserialize track config"); +} diff --git a/rust-tooling/utils/Cargo.toml b/rust-tooling/utils/Cargo.toml new file mode 100644 index 000000000..b45993731 --- /dev/null +++ b/rust-tooling/utils/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "utils" +version = "0.1.0" +edition = "2024" diff --git a/rust-tooling/utils/src/fs.rs b/rust-tooling/utils/src/fs.rs new file mode 100644 index 000000000..6a86bca2b --- /dev/null +++ b/rust-tooling/utils/src/fs.rs @@ -0,0 +1,18 @@ +//! This module contains utilities for working with the files in this repo. + +pub static REPO_ROOT_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../.."); + +#[test] +fn repo_root_dir_is_correct() { + let git_database = std::path::PathBuf::from(REPO_ROOT_DIR).join(".git"); + assert!(git_database.is_dir()) +} + +/// Changes the current working directory to the root of the repository. +/// +/// This is intended to be used by executables which operate on files +/// of the repository, so they can use relative paths and still work +/// when called from anywhere within the repository. +pub fn cd_into_repo_root() { + std::env::set_current_dir(REPO_ROOT_DIR).unwrap(); +} diff --git a/rust-tooling/utils/src/lib.rs b/rust-tooling/utils/src/lib.rs new file mode 100644 index 000000000..d521fbd77 --- /dev/null +++ b/rust-tooling/utils/src/lib.rs @@ -0,0 +1 @@ +pub mod fs; diff --git a/util/exercise/Cargo.lock b/util/exercise/Cargo.lock deleted file mode 100644 index c6e419cc8..000000000 --- a/util/exercise/Cargo.lock +++ /dev/null @@ -1,2078 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler32" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" - -[[package]] -name = "aho-corasick" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "atty" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" -dependencies = [ - "libc", - "winapi 0.3.8", -] - -[[package]] -name = "autocfg" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" - -[[package]] -name = "autocfg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" - -[[package]] -name = "backtrace" -version = "0.3.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" -dependencies = [ - "backtrace-sys", - "cfg-if 0.1.10", - "libc", - "rustc-demangle", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - -[[package]] -name = "bstr" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ede750122d9d1f87919570cb2cccee38c84fbc8c5599b25c289af40625b7030" -dependencies = [ - "memchr", -] - -[[package]] -name = "bumpalo" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - -[[package]] -name = "byteorder" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" - -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "either", - "iovec", -] - -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -dependencies = [ - "ppv-lite86", -] - -[[package]] -name = "cc" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" -dependencies = [ - "num-integer", - "num-traits", - "time", -] - -[[package]] -name = "chrono-tz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0e430fad0384e4defc3dc6b1223d1b886087a8bf9b7080e5ae027f73851ea15" -dependencies = [ - "chrono", - "parse-zoneinfo", -] - -[[package]] -name = "clap" -version = "2.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim", - "textwrap", - "unicode-width", - "vec_map", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - -[[package]] -name = "cookie" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" -dependencies = [ - "time", - "url 1.7.2", -] - -[[package]] -name = "cookie_store" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" -dependencies = [ - "cookie", - "failure", - "idna 0.1.5", - "log", - "publicsuffix", - "serde", - "serde_json", - "time", - "try_from", - "url 1.7.2", -] - -[[package]] -name = "crc32fast" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "crossbeam-channel" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" -dependencies = [ - "crossbeam-utils 0.7.0", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-deque" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils 0.7.0", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg 1.0.0", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.0", - "lazy_static", - "maybe-uninit", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" -dependencies = [ - "crossbeam-utils 0.6.6", -] - -[[package]] -name = "crossbeam-utils" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" -dependencies = [ - "cfg-if 0.1.10", - "lazy_static", -] - -[[package]] -name = "crossbeam-utils" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" -dependencies = [ - "autocfg 0.1.6", - "cfg-if 0.1.10", - "lazy_static", -] - -[[package]] -name = "ct-logs" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3686f5fa27dbc1d76c751300376e167c5a43387f44bb451fd1c24776e49113" -dependencies = [ - "sct", -] - -[[package]] -name = "deunicode" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "dtoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" - -[[package]] -name = "either" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" - -[[package]] -name = "encoding_rs" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "error-chain" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" -dependencies = [ - "backtrace", - "version_check", -] - -[[package]] -name = "exercise" -version = "1.6.2" -dependencies = [ - "clap", - "failure", - "failure_derive", - "flate2", - "indexmap", - "lazy_static", - "reqwest", - "serde", - "serde_json", - "tar", - "tera", - "toml", - "uuid", -] - -[[package]] -name = "failure" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - -[[package]] -name = "filetime" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall 0.2.13", - "winapi 0.3.8", -] - -[[package]] -name = "flate2" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad3c5233c9a940c8719031b423d7e6c16af66e031cb0420b0896f5245bf181d3" -dependencies = [ - "cfg-if 0.1.10", - "crc32fast", - "libc", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - -[[package]] -name = "futures" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" - -[[package]] -name = "futures-cpupool" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -dependencies = [ - "futures", - "num_cpus", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "getrandom" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "wasi", -] - -[[package]] -name = "globset" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - -[[package]] -name = "globwalk" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cbcf0368596897b0a3b8ff2110acf2400e80ffad4ca9238b52ff282a9b267b" -dependencies = [ - "ignore", - "walkdir", -] - -[[package]] -name = "h2" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" -dependencies = [ - "byteorder", - "bytes", - "fnv", - "futures", - "http", - "indexmap", - "log", - "slab", - "string", - "tokio-io", -] - -[[package]] -name = "heck" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "http" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" -dependencies = [ - "bytes", - "futures", - "http", - "tokio-buf", -] - -[[package]] -name = "httparse" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" - -[[package]] -name = "humansize" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e" - -[[package]] -name = "hyper" -version = "0.12.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c843caf6296fc1f93444735205af9ed4e109a539005abb2564ae1d6fad34c52" -dependencies = [ - "bytes", - "futures", - "futures-cpupool", - "h2", - "http", - "http-body", - "httparse", - "iovec", - "itoa", - "log", - "net2", - "rustc_version", - "time", - "tokio", - "tokio-buf", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719d85c7df4a7f309a77d145340a063ea929dcb2e025bae46a80345cffec2952" -dependencies = [ - "bytes", - "ct-logs", - "futures", - "hyper", - "rustls", - "tokio-io", - "tokio-rustls", - "webpki", - "webpki-roots", -] - -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "ignore" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "522daefc3b69036f80c7d2990b28ff9e0471c683bad05ca258e0a01dd22c5a1e" -dependencies = [ - "crossbeam-channel", - "globset", - "lazy_static", - "log", - "memchr", - "regex", - "same-file", - "thread_local 1.0.1", - "walkdir", - "winapi-util", -] - -[[package]] -name = "indexmap" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" -dependencies = [ - "autocfg 0.1.6", - "serde", -] - -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - -[[package]] -name = "itoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" - -[[package]] -name = "js-sys" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc9a97d7cec30128fd8b28a7c1f9df1c001ceb9b441e2b755e24130a6b43c79" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" - -[[package]] -name = "lock_api" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" - -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - -[[package]] -name = "memchr" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" - -[[package]] -name = "memoffset" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "mime" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf" - -[[package]] -name = "mime_guess" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "miniz_oxide" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "304f66c19be2afa56530fa7c39796192eef38618da8d19df725ad7c6d6b2aaae" -dependencies = [ - "adler32", -] - -[[package]] -name = "mio" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" -dependencies = [ - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - -[[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.8", -] - -[[package]] -name = "nom" -version = "4.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" -dependencies = [ - "memchr", - "version_check", -] - -[[package]] -name = "num-integer" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" -dependencies = [ - "autocfg 1.0.0", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" -dependencies = [ - "autocfg 1.0.0", -] - -[[package]] -name = "num_cpus" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" -dependencies = [ - "libc", -] - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - -[[package]] -name = "parking_lot" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -dependencies = [ - "lock_api", - "parking_lot_core", - "rustc_version", -] - -[[package]] -name = "parking_lot_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -dependencies = [ - "cfg-if 0.1.10", - "cloudabi", - "libc", - "redox_syscall 0.1.56", - "rustc_version", - "smallvec", - "winapi 0.3.8", -] - -[[package]] -name = "parse-zoneinfo" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feece9d0113b400182a7d00adcff81ccf29158c49c5abd11e2eed8589bf6ff07" -dependencies = [ - "regex", -] - -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pest" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4fb201c5c22a55d8b24fef95f78be52738e5e1361129be1b5e862ecdb6894a" -dependencies = [ - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b9fcf299b5712d06ee128a556c94709aaa04512c4dffb8ead07c5c998447fc0" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df43fd99896fd72c485fe47542c7b500e4ac1e8700bf995544d1317a60ded547" -dependencies = [ - "maplit", - "pest", - "sha-1", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" - -[[package]] -name = "proc-macro2" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "publicsuffix" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf259a81de2b2eb9850ec990ec78e6a25319715584fd7652b9b26f96fcb1510" -dependencies = [ - "error-chain", - "idna 0.2.0", - "lazy_static", - "regex", - "url 2.1.0", -] - -[[package]] -name = "quote" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.6", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi 0.3.8", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom", - "libc", - "rand_chacha 0.2.1", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.6", - "rand_core 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" -dependencies = [ - "c2-chacha", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi 0.3.8", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi 0.3.8", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.6", - "rand_core 0.4.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" - -[[package]] -name = "redox_syscall" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", - "thread_local 0.3.6", -] - -[[package]] -name = "regex-syntax" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" - -[[package]] -name = "reqwest" -version = "0.9.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c2064233e442ce85c77231ebd67d9eca395207dec2127fe0bbedde4bd29a650" -dependencies = [ - "base64", - "bytes", - "cookie", - "cookie_store", - "encoding_rs", - "flate2", - "futures", - "http", - "hyper", - "hyper-rustls", - "log", - "mime", - "mime_guess", - "rustls", - "serde", - "serde_json", - "serde_urlencoded", - "time", - "tokio", - "tokio-executor", - "tokio-io", - "tokio-rustls", - "tokio-threadpool", - "tokio-timer", - "url 1.7.2", - "uuid", - "webpki-roots", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6747f8da1f2b1fabbee1aaa4eb8a11abf9adef0bf58a41cee45db5d59cecdfac" -dependencies = [ - "cc", - "lazy_static", - "libc", - "spin", - "untrusted", - "web-sys", - "winapi 0.3.8", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustls" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" -dependencies = [ - "base64", - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "ryu" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" - -[[package]] -name = "sct" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" -dependencies = [ - "dtoa", - "itoa", - "serde", - "url 1.7.2", -] - -[[package]] -name = "sha-1" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", -] - -[[package]] -name = "slab" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" - -[[package]] -name = "slug" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" -dependencies = [ - "deunicode", -] - -[[package]] -name = "smallvec" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] - -[[package]] -name = "sourcefile" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "string" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" -dependencies = [ - "bytes", -] - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "syn" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "synstructure" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "tar" -version = "0.4.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8190d9cdacf6ee1b080605fd719b58d80a9fcbcea64db6744b26f743da02e447" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "tera" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8501ae034d1c2d2e8c29f3c259d919c11489220d8ee8a268e7e1fe3eff94c86a" -dependencies = [ - "chrono", - "chrono-tz", - "globwalk", - "humansize", - "lazy_static", - "percent-encoding 2.1.0", - "pest", - "pest_derive", - "rand 0.7.3", - "regex", - "serde", - "serde_json", - "slug", - "unic-segment", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "thread_local" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "time" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -dependencies = [ - "libc", - "redox_syscall 0.1.56", - "winapi 0.3.8", -] - -[[package]] -name = "tokio" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" -dependencies = [ - "bytes", - "futures", - "mio", - "num_cpus", - "tokio-current-thread", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer", -] - -[[package]] -name = "tokio-buf" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" -dependencies = [ - "bytes", - "either", - "futures", -] - -[[package]] -name = "tokio-current-thread" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" -dependencies = [ - "futures", - "tokio-executor", -] - -[[package]] -name = "tokio-executor" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f27ee0e6db01c5f0b2973824547ce7e637b2ed79b891a9677b0de9bd532b6ac" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures", -] - -[[package]] -name = "tokio-io" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" -dependencies = [ - "bytes", - "futures", - "log", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56391be9805bc80163151c0b9e5164ee64f4b0200962c346fea12773158f22d" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures", - "lazy_static", - "log", - "mio", - "num_cpus", - "parking_lot", - "slab", - "tokio-executor", - "tokio-io", - "tokio-sync", -] - -[[package]] -name = "tokio-rustls" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2fa53ac211c136832f530ccb081af9af891af22d685a9493e232c7a359bc2" -dependencies = [ - "bytes", - "futures", - "iovec", - "rustls", - "tokio-io", - "webpki", -] - -[[package]] -name = "tokio-sync" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06554cce1ae4a50f42fba8023918afa931413aded705b560e29600ccf7c6d76" -dependencies = [ - "fnv", - "futures", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" -dependencies = [ - "bytes", - "futures", - "iovec", - "mio", - "tokio-io", - "tokio-reactor", -] - -[[package]] -name = "tokio-threadpool" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2c6a3885302581f4401c82af70d792bb9df1700e7437b0aeb4ada94d5388c" -dependencies = [ - "crossbeam-deque", - "crossbeam-queue", - "crossbeam-utils 0.6.6", - "futures", - "lazy_static", - "log", - "num_cpus", - "slab", - "tokio-executor", -] - -[[package]] -name = "tokio-timer" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures", - "slab", - "tokio-executor", -] - -[[package]] -name = "toml" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" -dependencies = [ - "serde", -] - -[[package]] -name = "try-lock" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" - -[[package]] -name = "try_from" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "typenum" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" - -[[package]] -name = "ucd-trie" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2" - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicase" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" -dependencies = [ - "smallvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" - -[[package]] -name = "unicode-width" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" - -[[package]] -name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" - -[[package]] -name = "untrusted" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece" - -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", -] - -[[package]] -name = "url" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" -dependencies = [ - "idna 0.2.0", - "matches", - "percent-encoding 2.1.0", -] - -[[package]] -name = "uuid" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" -dependencies = [ - "rand 0.6.5", -] - -[[package]] -name = "vec_map" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" - -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - -[[package]] -name = "walkdir" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" -dependencies = [ - "same-file", - "winapi 0.3.8", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" -dependencies = [ - "futures", - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasm-bindgen" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd34c5ba0d228317ce388e87724633c57edca3e7531feb4e25e35aaa07a656af" -dependencies = [ - "cfg-if 0.1.10", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927196b315c23eed2748442ba675a4c54a1a079d90d9bdc5ad16ce31cf90b15b" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c2442bf04d89792816650820c3fb407af8da987a9f10028d5317f5b04c2b4a" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c075d27b7991c68ca0f77fe628c3513e64f8c477d422b859e03f28751b46fc5" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d61fe986a7af038dd8b5ec660e5849cbd9f38e7492b9404cc48b2b4df731d1" - -[[package]] -name = "wasm-bindgen-webidl" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b979afb0535fe4749906a674082db1211de8aef466331d43232f63accb7c07c" -dependencies = [ - "failure", - "heck", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "weedle", -] - -[[package]] -name = "web-sys" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84440699cd02ca23bed6f045ffb1497bc18a3c2628bd13e2093186faaaacf6b" -dependencies = [ - "failure", - "js-sys", - "sourcefile", - "wasm-bindgen", - "wasm-bindgen-webidl", -] - -[[package]] -name = "webpki" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7e664e770ac0110e2384769bcc59ed19e329d81f555916a6e072714957b81b4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a262ae37dd9d60f60dd473d1158f9fbebf110ba7b6a5051c8160460f6043718b" -dependencies = [ - "webpki", -] - -[[package]] -name = "weedle" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" -dependencies = [ - "nom", -] - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - -[[package]] -name = "winapi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" -dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "xattr" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" -dependencies = [ - "libc", -] diff --git a/util/exercise/Cargo.toml b/util/exercise/Cargo.toml deleted file mode 100644 index 0a7db15a2..000000000 --- a/util/exercise/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "exercise" -version = "1.6.2" -description = "An utility for creating or updating the exercises on the Exercism Rust track" -edition = "2021" - -[dependencies] -clap = "2.32.0" -failure = "0.1.5" -failure_derive = "0.1.5" -flate2 = "1.0.7" -lazy_static = "1.2.0" -tar = "0.4.36" -tera = "1.0.2" -toml = "0.4.10" -uuid = { version = "0.7", features = ["v4"] } - -[dependencies.indexmap] -version = "1.3.0" -# To implement `Deserialize` on `IndexMap`. -features = ["serde-1"] - -[dependencies.reqwest] -version = "0.9.16" -default-features = false -features = ["rustls-tls"] - -[dependencies.serde] -version = "1.0.102" -features = ["derive"] - -[dependencies.serde_json] -version = "1.0.38" -features = ["preserve_order"] diff --git a/util/exercise/src/cmd/configure.rs b/util/exercise/src/cmd/configure.rs deleted file mode 100644 index a74845def..000000000 --- a/util/exercise/src/cmd/configure.rs +++ /dev/null @@ -1,332 +0,0 @@ -use crate::{self as exercise, errors::Result, get, get_mut, val_as}; -use failure::format_err; -use serde_json::{self, json, Value}; -use std::{ - fs, - io::{stdin, stdout, Write}, - path::Path, -}; -use uuid::Uuid; - -fn get_user_input(prompt: &str) -> Result { - print!("{}", prompt); - - let mut buffer = String::new(); - - stdout().flush()?; - stdin().read_line(&mut buffer)?; - - Ok(buffer.trim().to_string()) -} - -fn get_user_config(exercise_name: &str, config_content: &Value) -> Result { - let existing_config = get!(config_content, "exercises", as_array) - .iter() - .find(|exercise| exercise["slug"] == exercise_name); - - let uuid = if let Some(existing_config) = existing_config { - get!(existing_config, "uuid", as_str).to_string() - } else { - Uuid::new_v4().to_hyphenated().to_string() - }; - - let unlocked_by: Option = loop { - let default_value = if let Some(existing_config) = existing_config { - if let Value::String(s) = get!(existing_config, "unlocked_by") { - s - } else { - "null" - } - } else { - "hello-world" - }; - - let user_input = get_user_input(&format!( - "Exercise slug which unlocks this (blank for '{}'): ", - default_value - ))?; - - if user_input.is_empty() { - break Some(default_value.to_string()); - } else if user_input == "null" { - break None; - } else if !get!(config_content, "exercises", as_array) - .iter() - .any(|exercise| exercise["slug"] == user_input) - { - println!("{} is not an existing exercise slug", user_input); - continue; - } else { - break Some(user_input); - }; - }; - - let core = unlocked_by.is_none(); - - let difficulty = loop { - let unlocked_by_difficulty = match unlocked_by { - Some(ref unlocked_by) => { - let unlocked_by_exercise = get!(config_content, "exercises", as_array) - .iter() - .find(|exercise| exercise["slug"] == unlocked_by.as_str()) - .ok_or_else(|| format_err!("exercise '{}' not found in config", unlocked_by))?; - - get!(unlocked_by_exercise, "difficulty", as_u64) - } - - None => 1, - }; - - let available_difficulties: Vec = [1, 4, 7, 10] - .iter() - .skip_while(|&&difficulty| difficulty < unlocked_by_difficulty) - .cloned() - .collect(); - - let default_value = if let Some(existing_config) = existing_config { - get!(existing_config, "difficulty", as_u64) - } else { - *available_difficulties - .first() - .ok_or_else(|| format_err!("no available difficulties"))? - }; - - let user_input = get_user_input(&format!( - "Difficulty for this exercise {:?} (blank for {}): ", - available_difficulties, default_value - ))?; - - if user_input.is_empty() { - break default_value; - } else if let Ok(difficulty) = user_input.parse::() { - if !available_difficulties.contains(&difficulty) { - println!( - "Difficulty should be {:?}, not '{}'.", - available_difficulties, difficulty - ); - - continue; - } - - break difficulty; - } else { - println!("Difficulty should be a number, not '{}'.", user_input); - - continue; - } - }; - - let topics = loop { - let default_value = if let Some(existing_config) = existing_config { - use serde_json::Value::*; - use std::string; - match get!(existing_config, "topics") { - String(s) => vec![s.to_string()], - Array(topics_values) => { - let mut topics: Vec = Vec::with_capacity(topics_values.len()); - for topic in topics_values.iter() { - topics.push(val_as!(topic, as_str).to_string()) - } - topics - } - _ => { - return Err(exercise::errors::Error::SchemaTypeError { - file: "config.json".to_string(), - field: "topics".to_string(), - as_type: "array or string".to_string(), - }); - } - } - } else { - vec![exercise_name.to_string()] - }; - - let user_input = get_user_input(&format!( - "List of topics for this exercise, comma-separated (blank for {:?}): ", - default_value, - ))?; - - if user_input.is_empty() { - break default_value; - } - - let topics = user_input - .split(',') - .map(|topic| topic.trim().to_string()) - .filter(|topic| !topic.is_empty()) - .collect::>(); - - if topics.is_empty() { - println!("Must enter at least one topic"); - - continue; - } - - break topics; - }; - - Ok(json!({ - "slug": exercise_name, - "uuid": uuid, - "core": core, - "unlocked_by": unlocked_by, - "difficulty": difficulty, - "topics": topics - })) -} - -fn choose_exercise_insert_index( - exercise_name: &str, - exercises: &[Value], - difficulty: &Value, -) -> Result { - loop { - let mut exercises_with_similar_difficulty = Vec::with_capacity(exercises.len()); - for (index, exercise) in exercises - .iter() - .enumerate() - .filter(|(_, exercise)| exercise["difficulty"] == *difficulty) - { - exercises_with_similar_difficulty.push((index, get!(exercise, "slug", as_str))); - } - - let mut start_index = 0; - - let mut end_index = exercises_with_similar_difficulty.len() - 1; - - let insert_index = loop { - if start_index == end_index { - break start_index; - } - - let middle_index = start_index + ((end_index - start_index) / 2); - - let user_input = get_user_input(&format!( - "Is {} easier then {}? (y/N): ", - exercise_name, exercises_with_similar_difficulty[middle_index].1 - ))?; - - if user_input.to_lowercase().starts_with('y') { - end_index = middle_index; - } else { - start_index = middle_index + 1; - } - }; - - let insert_index = exercises_with_similar_difficulty[insert_index].0; - - let prompt = if insert_index == 0 { - format!( - "{} is the easiest exercise of difficulty {}.", - exercise_name, *difficulty - ) - } else if insert_index == exercises.len() - 1 { - format!( - "{} is the hardest exercise of difficulty {}.", - exercise_name, *difficulty - ) - } else { - format!( - "{} is placed between {} and {} exercises in difficulty.", - exercise_name, - get!(exercises[insert_index - 1], "slug", as_str), - get!(exercises[insert_index], "slug", as_str), - ) - }; - - let user_input = get_user_input(&format!( - "You have configured that {}.\nIs this correct? (y/N): ", - prompt - ))?; - - if user_input.to_lowercase().starts_with('y') { - break Ok(insert_index); - } - } -} - -fn insert_user_config( - exercise_name: &str, - config_content: &mut Value, - user_config: Value, -) -> Result<()> { - let exercises = get_mut!(config_content, "exercises", as_array_mut); - - let insert_index = - choose_exercise_insert_index(exercise_name, exercises, &user_config["difficulty"])?; - - exercises.insert(insert_index, user_config); - - Ok(()) -} - -fn update_existing_config( - exercise_name: &str, - config_content: &mut Value, - user_config: Value, -) -> Result<()> { - let exercises = get_mut!(config_content, "exercises", as_array_mut); - - let existing_exercise_index = exercises - .iter() - .position(|exercise| exercise["slug"] == exercise_name) - .ok_or_else(|| format_err!("exercise '{}' not found in config.json", exercise_name))?; - - let insert_index = - if exercises[existing_exercise_index]["difficulty"] == user_config["difficulty"] { - existing_exercise_index - } else { - choose_exercise_insert_index(exercise_name, &exercises, &user_config["difficulty"])? - }; - - exercises.remove(existing_exercise_index); - - exercises.insert(insert_index, user_config); - - Ok(()) -} - -pub fn configure_exercise(exercise_name: &str) -> Result<()> { - println!( - "Configuring config.json for the {} exercise.", - exercise_name - ); - - let config_path = Path::new(&*exercise::TRACK_ROOT).join("config.json"); - - let config_content_string = fs::read_to_string(&config_path)?; - - let mut config_content: Value = serde_json::from_str(&config_content_string)?; - - let config_exists = get!(config_content, "exercises", as_array) - .iter() - .any(|exercise| exercise["slug"] == exercise_name); - - let user_config: Value = loop { - let user_config = get_user_config(exercise_name, &config_content)?; - - let user_input = get_user_input(&format!( - "You have configured the {} exercise as follows:\n{}\nIs this correct? (y/N):", - exercise_name, - serde_json::to_string_pretty(&user_config)? - ))?; - - if user_input.to_lowercase().starts_with('y') { - break user_config; - } - }; - - if config_exists { - update_existing_config(exercise_name, &mut config_content, user_config)?; - } else { - insert_user_config(exercise_name, &mut config_content, user_config)?; - } - - fs::write(&config_path, serde_json::to_string_pretty(&config_content)?)?; - - println!("Formatting the config.json file via 'bin/configlet fmt'"); - - exercise::run_configlet_command("fmt", &["."])?; - - Ok(()) -} diff --git a/util/exercise/src/cmd/defaults/README.md b/util/exercise/src/cmd/defaults/README.md deleted file mode 100644 index 2ff7d26cb..000000000 --- a/util/exercise/src/cmd/defaults/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Defaults - -The files in this directory are used in appropriate places for defaults. They -are included at build-time in the compiled executable: if you change one, you -will need to recompile to see those changes reflected in the output. - -Using external files makes it easier to ensure that formatting etc is correct, -without needing to worry about proper escaping within a Rust source file. diff --git a/util/exercise/src/cmd/defaults/description.md b/util/exercise/src/cmd/defaults/description.md deleted file mode 100644 index 6986282dc..000000000 --- a/util/exercise/src/cmd/defaults/description.md +++ /dev/null @@ -1,6 +0,0 @@ -# Description - -Describe your exercise here. - -Don't forget that `README.md` is automatically generated; update this within -`.meta/description.md`. diff --git a/util/exercise/src/cmd/defaults/example.rs b/util/exercise/src/cmd/defaults/example.rs deleted file mode 100644 index 768f0813a..000000000 --- a/util/exercise/src/cmd/defaults/example.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Example implementation -//! -//! - Implement the solution to your exercise here. -//! - Put the stubs for any tested functions in `src/lib.rs`, -//! whose variable names are `_` and -//! whose contents are `unimplemented!()`. -//! - If your example implementation has dependencies, copy -//! `Cargo.toml` into `Cargo-example.toml` and then make -//! any modifications necessary to the latter so your example will run. -//! - Test your example by running `../../bin/test-exercise` diff --git a/util/exercise/src/cmd/defaults/gitignore b/util/exercise/src/cmd/defaults/gitignore deleted file mode 100644 index e130ceb2d..000000000 --- a/util/exercise/src/cmd/defaults/gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 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 -Cargo.lock diff --git a/util/exercise/src/cmd/defaults/metadata.yml b/util/exercise/src/cmd/defaults/metadata.yml deleted file mode 100644 index c44a80f49..000000000 --- a/util/exercise/src/cmd/defaults/metadata.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -blurb: "" -source: "" -source_url: "" diff --git a/util/exercise/src/cmd/fetch_configlet.rs b/util/exercise/src/cmd/fetch_configlet.rs deleted file mode 100644 index 8351c9232..000000000 --- a/util/exercise/src/cmd/fetch_configlet.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::errors::Result; -use failure::format_err; -use flate2::read::GzDecoder; -use serde_json::Value; -use std::{ - path::{Path, PathBuf}, - string::ToString, -}; -use tar::Archive; - -// Returns the "os-arch" string, according to the host machine parameters -fn get_os_arch() -> String { - let os = if cfg!(target_os = "windows") { - "windows" - } else if cfg!(target_os = "macos") { - "mac" - } else { - "linux" - }; - let arch = if cfg!(target_pointer_width = "32") { - "32bit" - } else { - "64bit" - }; - format!("{}-{}", os, arch) -} - -// Makes a request to the Github API to get and return the download url for the latest configlet release -fn get_download_url() -> Result { - reqwest::get("https://api.github.com/repos/exercism/configlet/releases/latest")? - .json::()? - .get("assets") - .and_then(Value::as_array) - .ok_or_else(|| format_err!("failed to get the 'assets' field from the Github response"))? - .iter() - .filter_map(|asset| asset.get("browser_download_url").and_then(Value::as_str)) - .find(|url| url.contains(&get_os_arch())) - .map(ToString::to_string) - .ok_or_else(|| format_err!("failed to get the configlet release url")) - .map_err(|e| e.into()) -} - -// download and extract configlet into the repo's /bin folder -// -// returns the path into which the bin was extracted on success -pub fn download() -> Result { - let response = reqwest::get(&get_download_url()?)?; - let mut archive = Archive::new(GzDecoder::new(response)); - let download_path = Path::new(&*crate::TRACK_ROOT).join("bin"); - archive - .unpack(&download_path) - .map(|_| download_path) - .map_err(|e| e.into()) -} diff --git a/util/exercise/src/cmd/generate.rs b/util/exercise/src/cmd/generate.rs deleted file mode 100644 index 8810b05ab..000000000 --- a/util/exercise/src/cmd/generate.rs +++ /dev/null @@ -1,198 +0,0 @@ -/// This module contains source for the `generate` command. -use crate::{self as exercise, errors::Result, structs::CanonicalData, TEMPLATES}; -use failure::format_err; -use std::{ - fs::{self, File, OpenOptions}, - io::Write, - path::Path, - process::{Command, Stdio}, -}; -use tera::Context; - -const GITIGNORE_CONTENT: &str = include_str!("defaults/gitignore"); -const EXAMPLE_RS_CONTENT: &str = include_str!("defaults/example.rs"); -const DESCRIPTION_MD_CONTENT: &str = include_str!("defaults/description.md"); -const METADATA_YML_CONTENT: &str = include_str!("defaults/metadata.yml"); - -// Generate .meta directory and its contents without using the canonical data -fn generate_meta(exercise_name: &str, exercise_path: &Path) -> Result<()> { - let meta_dir = exercise_path.join(".meta"); - fs::create_dir(&meta_dir)?; - - for (file, content) in [ - ("description.md", DESCRIPTION_MD_CONTENT), - ("metadata.yml", METADATA_YML_CONTENT), - ] - .iter() - { - if !exercise::canonical_file_exists(exercise_name, file)? { - fs::write(exercise_path.join(".meta").join(file), content)?; - } - } - - if fs::read_dir(&meta_dir)?.count() == 0 { - fs::remove_dir(meta_dir)?; - } - - Ok(()) -} - -// Generate test suite using the canonical data -fn generate_tests_from_canonical_data( - exercise_name: &str, - tests_path: &Path, - canonical_data: &CanonicalData, - use_maplit: bool, -) -> Result<()> { - exercise::update_cargo_toml_version(exercise_name, canonical_data)?; - - let mut context = Context::from_serialize(canonical_data)?; - context.insert("use_maplit", &use_maplit); - context.insert("properties", &canonical_data.properties()); - - let test_file = TEMPLATES.render("test_file.rs", &context)?; - fs::write(&tests_path, test_file)?; - - exercise::rustfmt(&tests_path)?; - - Ok(()) -} - -// Run bin/configlet generate command to generate README for the exercise -fn generate_readme(exercise_name: &str) -> Result<()> { - println!( - "Generating README for {} via 'bin/configlet generate'", - exercise_name - ); - - let problem_specifications_path = Path::new(&*exercise::TRACK_ROOT) - .join("..") - .join("problem-specifications"); - - if !problem_specifications_path.exists() { - let problem_specifications_url = "https://github.com/exercism/problem-specifications.git"; - println!( - "problem-specifications repository not found. Cloning the repository from {}", - problem_specifications_url - ); - - Command::new("git") - .current_dir(&*exercise::TRACK_ROOT) - .stdout(Stdio::inherit()) - .arg("clone") - .arg(problem_specifications_url) - .arg(&problem_specifications_path) - .output()?; - } - - exercise::run_configlet_command( - "generate", - &[ - ".", - "--only", - exercise_name, - "--spec-path", - problem_specifications_path - .to_str() - .ok_or_else(|| format_err!("path inexpressable as str"))?, - ], - )?; - - Ok(()) -} - -// Generate a new exercise with specified name and flags -pub fn generate_exercise(exercise_name: &str, use_maplit: bool) -> Result<()> { - if exercise::exercise_exists(exercise_name) { - return Err(format_err!("exercise with the name {} already exists", exercise_name,).into()); - } - - let exercise_path = Path::new(&*exercise::TRACK_ROOT) - .join("exercises") - .join(exercise_name); - - println!( - "Generating a new exercise at path: {}", - exercise_path - .to_str() - .ok_or_else(|| format_err!("path inexpressable as str"))? - ); - - let _cargo_new_output = Command::new("cargo") - .arg("new") - .arg("--lib") - .arg( - exercise_path - .to_str() - .ok_or_else(|| format_err!("path inexpressable as str"))?, - ) - .output()?; - - fs::write(exercise_path.join(".gitignore"), GITIGNORE_CONTENT)?; - - if use_maplit { - let mut cargo_toml_file = OpenOptions::new() - .append(true) - .open(exercise_path.join("Cargo.toml"))?; - - cargo_toml_file.write_all(b"maplit = \"1.0.1\"")?; - } - - fs::create_dir(exercise_path.join("tests"))?; - - let test_file_name = exercise_path - .join("tests") - .join(format!("{}.rs", exercise_name)); - - fs::write(exercise_path.join("example.rs"), EXAMPLE_RS_CONTENT)?; - - match exercise::get_canonical_data(exercise_name) { - Ok(canonical_data) => { - println!("Generating tests from canonical data"); - - generate_tests_from_canonical_data( - &exercise_name, - &test_file_name, - &canonical_data, - use_maplit, - )?; - } - Err(_) => { - println!( - "No canonical data for exercise '{}' found. Generating standard exercise template.", - &exercise_name - ); - - let mut test_file = File::create(test_file_name)?; - - test_file.write_all( - &format!( - "//! Tests for {exercise_name} \n\ - //! \n\ - //! Generated by [utility][utility]\n\ - //! \n\ - //! [utility]: https://github.com/exercism/rust/tree/main/util/exercise\n\ - \n", - exercise_name = exercise_name, - ) - .into_bytes(), - )?; - if use_maplit { - test_file.write_all(b"use maplit::hashmap;\n")?; - } - - test_file.write_all( - &format!( - "use {escaped_exercise_name}::*;\n\n\n", - escaped_exercise_name = exercise_name.replace("-", "_"), - ) - .into_bytes(), - )?; - } - } - - generate_meta(&exercise_name, &exercise_path)?; - generate_readme(&exercise_name)?; - - Ok(()) -} diff --git a/util/exercise/src/cmd/mod.rs b/util/exercise/src/cmd/mod.rs deleted file mode 100644 index 84607911c..000000000 --- a/util/exercise/src/cmd/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod configure; -pub mod fetch_configlet; -pub mod generate; -pub mod update; diff --git a/util/exercise/src/cmd/templates/macros.rs b/util/exercise/src/cmd/templates/macros.rs deleted file mode 100644 index c7a41b7bb..000000000 --- a/util/exercise/src/cmd/templates/macros.rs +++ /dev/null @@ -1,46 +0,0 @@ -{% macro to_literal(value, use_maplit=false) -%} - {% if value is object -%} - {% if use_maplit -%} - hashmap! { - {% for k, v in value -%} - {{ self::to_literal(value=k, use_maplit=use_maplit) }} => - {{ self::to_literal(value=v, use_maplit=use_maplit) }}, - {% endfor -%} - } - {% else -%} - { - let mut hm = ::std::collections::HashMap::new(); - {% for k, v in value -%} - hm.insert( - {{ self::to_literal(value=k, use_maplit=use_maplit) }}, - {{ self::to_literal(value=v, use_maplit=use_maplit) }} - ); - {% endfor -%} - hm - } - {% endif -%} - {% elif value is iterable -%} - vec![ - {% for element in value -%} - {{ self::to_literal(value=element, use_maplit=use_maplit) }}, - {% endfor -%} - ] - {% elif value is string -%} - "{{ value }}" - {% elif value is number -%} - {{ value }} - {% else -%} - None - {% endif -%} -{% endmacro -%} - -{% macro gen_test_fn(case, first_test_case=false) -%} - {# Need to set up the variables for the template. #} - {% set description = case.description -%} - {% set comments = case.comments -%} - {% set property = case.property -%} - {% set input = case.input -%} - {% set expected = case.expected -%} - - {% include "test_fn.rs" %} -{% endmacro generate_test_fn -%} \ No newline at end of file diff --git a/util/exercise/src/cmd/templates/property_fn.rs b/util/exercise/src/cmd/templates/property_fn.rs deleted file mode 100644 index 1bac5233e..000000000 --- a/util/exercise/src/cmd/templates/property_fn.rs +++ /dev/null @@ -1,18 +0,0 @@ -/// Process a single test case for the property `{{ property }}` -/// -/// All cases for the `{{ property }}` property are implemented in terms of -/// this function. -/// -/// Note that you'll need to both name the expected transform which the student -/// needs to write, and name the types of the inputs and outputs. -/// While rustc _may_ be able to handle things properly given a working example, -/// students will face confusing errors if the `I` and `O` types are not -/// concrete. -fn process_{{ format_property(property=property) }}_case(input: I, expected: O) { - // typical implementation: - // assert_eq!( - // student_{{ format_property(property=property) }}_func(input), - // expected, - // ) - unimplemented!() -} diff --git a/util/exercise/src/cmd/templates/test_file.rs b/util/exercise/src/cmd/templates/test_file.rs deleted file mode 100644 index e4c7ecb84..000000000 --- a/util/exercise/src/cmd/templates/test_file.rs +++ /dev/null @@ -1,49 +0,0 @@ -{% import "macros.rs" as macros -%} - -//! Tests for {{ exercise }} -//! -//! Generated by [utility][utility] using [canonical data][canonical_data] -//! -//! [utility]: https://github.com/exercism/rust/tree/main/util/exercise -//! [canonical_data]: https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/{{ exercise }}/canonical-data.json - -{% for comment in comments -%} -/// {{ comment }} -{% endfor %} - -{% if use_maplit -%} -use maplit::hashmap; -{% endif %} - -{% for property in properties | sort -%} -{% include "property_fn.rs" %} -{% endfor -%} - -{# Don't ignore the first case. -#} -{% set first_test_case = true -%} - -{% for item in cases -%} - {# Check if we're dealing with a group of cases. #} - {% if item.cases -%} - /// {{ item.description }} - {% if item.optional -%} - /// {{ item.optional }} - {% endif -%} - - {% if item.comments -%} - {% for comment in item.comments -%} - /// {{ comment }} - {% endfor -%} - {% endif -%} - - {% for case in item.cases -%} - {{ macros::gen_test_fn(case=case, first_test_case=first_test_case) }} - {% set_global first_test_case = false -%} - {% endfor -%} - - {# Or just a single one. #} - {% else -%} - {{ macros::gen_test_fn(case=item, first_test_case=first_test_case) }} - {% set_global first_test_case = false -%} - {% endif -%} -{% endfor -%} diff --git a/util/exercise/src/cmd/templates/test_fn.rs b/util/exercise/src/cmd/templates/test_fn.rs deleted file mode 100644 index 3232bf4eb..000000000 --- a/util/exercise/src/cmd/templates/test_fn.rs +++ /dev/null @@ -1,19 +0,0 @@ -{% import "macros.rs" as macros -%} - -#[test] -{% if not first_test_case -%} -#[ignore] -{% endif -%} -/// {{ description }} -{% if comments -%} - /// - {% for comment in comments %} - /// {{ comment }} - {% endfor %} -{% endif -%} -fn test_{{ format_description(description=description) }}() { - process_{{ format_property(property=property) }}_case( - {{ macros::to_literal(value=input, use_maplit=use_maplit) }}, - {{ macros::to_literal(value=expected, use_maplit=use_maplit) }} - ); -} diff --git a/util/exercise/src/cmd/update.rs b/util/exercise/src/cmd/update.rs deleted file mode 100644 index f1c1a28b2..000000000 --- a/util/exercise/src/cmd/update.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::{ - self as exercise, - errors::Result, - structs::{LabeledTest, LabeledTestItem}, -}; -use failure::format_err; -use std::{collections::HashSet, fs, path::Path}; - -enum DiffType { - NEW, - UPDATED, -} - -fn generate_diff_test( - case: &LabeledTest, - diff_type: &DiffType, - use_maplit: bool, -) -> Result { - Ok(format!( - "//{}\n{}", - match diff_type { - DiffType::NEW => "NEW", - DiffType::UPDATED => "UPDATED", - }, - exercise::generate_test_function(case, use_maplit)? - )) -} - -fn generate_diff_property(property: &str) -> Result { - Ok(format!( - "//{}\n{}", - "NEW", - exercise::generate_property_body(property)? - )) -} - -fn generate_diffs( - case: &LabeledTest, - tests_content: &str, - diffs: &mut HashSet, - use_maplit: bool, -) -> Result<()> { - let description = &case.description; - let description_formatted = exercise::format_exercise_description(description); - - let diff_type = if !tests_content.contains(&format!("test_{}", description_formatted)) { - DiffType::NEW - } else { - DiffType::UPDATED - }; - - if diffs.insert(generate_diff_test(case, &diff_type, use_maplit)?) { - match diff_type { - DiffType::NEW => println!("New test case detected: {}.", description_formatted), - DiffType::UPDATED => println!("Updated test case: {}.", description_formatted), - } - } - - let property = &case.property; - let property_formatted = exercise::format_exercise_property(property); - - if !tests_content.contains(&format!("process_{}_case", property_formatted)) - && diffs.insert(generate_diff_property(property)?) - { - println!("New property detected: {}.", property); - } - - Ok(()) -} - -fn get_diffs( - case: &LabeledTestItem, - diffs: &mut HashSet, - tests_content: &str, - use_maplit: bool, -) -> Result<()> { - match case { - LabeledTestItem::Single(case) => generate_diffs(case, &tests_content, diffs, use_maplit)?, - LabeledTestItem::Array(group) => { - for case in &group.cases { - get_diffs(case, diffs, tests_content, use_maplit)?; - } - } - } - - Ok(()) -} - -fn apply_diffs(exercise_name: &str, diffs: &HashSet, tests_content: &str) -> Result<()> { - let updated_tests_content = format!( - "{}\n{}", - tests_content, - diffs - .iter() - .map(|diff| format!("\n{}", diff)) - .collect::() - ); - - let tests_path = Path::new(&*exercise::TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .join("tests") - .join(format!("{}.rs", exercise_name)); - - fs::write(&tests_path, updated_tests_content.as_bytes())?; - - exercise::rustfmt(&tests_path)?; - - Ok(()) -} - -pub fn update_exercise(exercise_name: &str, use_maplit: bool) -> Result<()> { - if !exercise::exercise_exists(exercise_name) { - return Err( - format_err!("exercise with the name '{}' does not exist", exercise_name).into(), - ); - } - - let tests_content = exercise::get_tests_content(exercise_name)?; - - let canonical_data = exercise::get_canonical_data(exercise_name)?; - - let mut diffs: HashSet = HashSet::new(); - - for case in &canonical_data.cases { - get_diffs(case, &mut diffs, &tests_content, use_maplit)?; - } - - apply_diffs(exercise_name, &diffs, &tests_content)?; - - exercise::update_cargo_toml_version(exercise_name, &canonical_data)?; - - Ok(()) -} diff --git a/util/exercise/src/errors.rs b/util/exercise/src/errors.rs deleted file mode 100644 index a0fb6d125..000000000 --- a/util/exercise/src/errors.rs +++ /dev/null @@ -1,76 +0,0 @@ -use failure; -use reqwest; -use serde_json; -use std::{convert::From, io, result}; -use tera; -use toml; - -#[derive(Debug, failure::Fail)] -pub enum Error { - #[fail(display = "IO error: {}", _0)] - IoError(#[cause] io::Error), - #[fail( - display = "config.json malformed: '{}' must have field '{}'", - parent, field - )] - ConfigJsonSchemaError { parent: String, field: String }, - #[fail( - display = "{} malformed: field '{}' must have type '{}'", - file, field, as_type - )] - SchemaTypeError { - file: String, - field: String, - as_type: String, - }, - #[fail(display = "json error: {}", _0)] - JsonError(#[cause] serde_json::Error), - #[fail(display = "config.toml parse error: {}", _0)] - ConfigTomlParseError(#[cause] toml::de::Error), - #[fail(display = "HTTP error: {}", _0)] - HTTPError(reqwest::Error), - #[fail(display = "Tera rendering error: {}", _0)] - TeraError(tera::Error), - #[fail(display = "{}", _0)] - Failure(#[cause] failure::Error), - #[fail(display = "Unknown Failure: {}", _0)] - UnknownError(String), -} - -impl From for Error { - fn from(err: io::Error) -> Self { - Error::IoError(err) - } -} - -impl From for Error { - fn from(err: toml::de::Error) -> Self { - Error::ConfigTomlParseError(err) - } -} - -impl From for Error { - fn from(err: reqwest::Error) -> Self { - Error::HTTPError(err) - } -} - -impl From for Error { - fn from(err: failure::Error) -> Self { - Error::Failure(err) - } -} - -impl From for Error { - fn from(err: serde_json::Error) -> Self { - Error::JsonError(err) - } -} - -impl From for Error { - fn from(err: tera::Error) -> Self { - Error::TeraError(err) - } -} - -pub type Result = result::Result; diff --git a/util/exercise/src/lib.rs b/util/exercise/src/lib.rs deleted file mode 100644 index d7609a39e..000000000 --- a/util/exercise/src/lib.rs +++ /dev/null @@ -1,282 +0,0 @@ -pub mod cmd; -pub mod errors; -pub mod structs; - -use errors::Result; -use failure::format_err; -use lazy_static::lazy_static; -use reqwest; -use serde_json::Value; -use std::{ - collections::HashMap, - env, fs, io, - path::Path, - process::{Command, Stdio}, -}; -use structs::{CanonicalData, LabeledTest}; -use tera::{Context, Tera}; -use toml; -use toml::Value as TomlValue; - -// we look for the track root in various places, but it's never going to change -// we therefore cache the value for efficiency -lazy_static! { - pub static ref TRACK_ROOT: String = { - let rev_parse_output = Command::new("git") - .arg("rev-parse") - .arg("--show-toplevel") - .output() - .expect("Failed to get the path to the track repo."); - - String::from_utf8(rev_parse_output.stdout) - .expect("git rev-parse produced non-utf8 output") - .trim() - .to_string() - }; -} - -// Create a static `Tera` struct so we can access the templates from anywhere. -lazy_static! { - pub static ref TEMPLATES: Tera = { - let templates = Path::new(&*TRACK_ROOT) - .join("util") - .join("exercise") - .join("src") - .join("cmd") - .join("templates") - .join("**") - .join("*.rs"); - - // Since `TRACK_ROOT` already checks for UTF-8 and nothing added is not - // UTF-8, unwrapping is fine. - let mut tera = match Tera::new(templates.to_str().unwrap()) { - Ok(t) => t, - Err(e) => { - println!("Parsing error(s): {}", e); - ::std::process::exit(1); - } - }; - - // Build wrappers around the formatting functions. - let format_description = |args: &HashMap| - args.get("description") - .and_then(Value::as_str) - .map(format_exercise_description) - .map(Value::from) - .ok_or(tera::Error::from("Problem formatting the description.")) - ; - - let format_property = |args: &HashMap| - args.get("property") - .and_then(Value::as_str) - .map(format_exercise_property) - .map(Value::from) - .ok_or(tera::Error::from("Problem formatting the property.")) - ; - - tera.register_function("format_description", format_description); - tera.register_function("format_property", format_property); - tera - }; -} - -#[macro_export] -macro_rules! val_as { - ($value:expr, $as:ident) => { - $value - .$as() - .ok_or_else(|| $crate::errors::Error::SchemaTypeError { - file: "config.json".to_string(), - field: stringify!($name).to_string(), - as_type: stringify!($as)[3..].to_string(), - })? - }; -} - -#[macro_export] -macro_rules! get { - ($value:expr, $name:expr) => { - $value - .get($name) - .ok_or_else(|| $crate::errors::Error::ConfigJsonSchemaError { - parent: stringify!($value).to_string(), - field: stringify!($name).to_string(), - })? - }; - ($value:expr, $name:expr, $as:ident) => { - val_as!(get!($value, $name), $as) - }; -} - -#[macro_export] -macro_rules! get_mut { - ($value:expr, $name:expr) => { - $value - .get_mut($name) - .ok_or_else(|| $crate::errors::Error::ConfigJsonSchemaError { - parent: stringify!($value).to_string(), - field: stringify!($name).to_string(), - })? - }; - ($value:expr, $name:expr, $as:ident) => { - val_as!(get_mut!($value, $name), $as) - }; -} - -pub fn run_configlet_command(command: &str, args: &[&str]) -> Result<()> { - let track_root = &*TRACK_ROOT; - - let bin_path = Path::new(track_root).join("bin"); - - let configlet_name_unix = "configlet"; - - let configlet_name_windows = "configlet.exe"; - - let configlet_name = if bin_path.join(configlet_name_unix).exists() { - configlet_name_unix - } else if bin_path.join(configlet_name_windows).exists() { - configlet_name_windows - } else { - println!("Configlet not found in the bin directory. Running bin/fetch-configlet."); - - let bin_path = crate::cmd::fetch_configlet::download()?; - - if bin_path.join(configlet_name_unix).exists() { - configlet_name_unix - } else if bin_path.join(configlet_name_windows).exists() { - configlet_name_windows - } else { - return Err(format_err!( - "could not locate configlet after running bin/fetch-configlet" - ) - .into()); - } - }; - - Command::new(&bin_path.join(configlet_name)) - .current_dir(track_root) - .stdout(Stdio::inherit()) - .arg(command) - .args(args) - .output()?; - - Ok(()) -} - -fn url_for(exercise: &str, file: &str) -> String { - format!( - "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/%7B%7D/%7B%7D", - exercise, file, - ) -} - -fn get_canonical(exercise: &str, file: &str) -> Result { - reqwest::get(&url_for(exercise, file)).map_err(|e| e.into()) -} - -// Try to get the canonical data for the exercise of the given name -pub fn get_canonical_data(exercise_name: &str) -> Result { - let mut response = get_canonical(exercise_name, "canonical-data.json")?.error_for_status()?; - response.json().map_err(|e| e.into()) -} - -pub fn canonical_file_exists(exercise: &str, file: &str) -> Result { - Ok(get_canonical(exercise, file)?.status().is_success()) -} - -pub fn get_tests_content(exercise_name: &str) -> io::Result { - let tests_path = Path::new(&*TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .join("tests") - .join(format!("{}.rs", exercise_name)); - - fs::read_to_string(tests_path) -} - -pub fn format_exercise_description(description: &str) -> String { - description - .chars() - .filter(|c| c.is_alphanumeric() || *c == ' ') - .collect::() - .replace(" ", "_") - .to_lowercase() -} - -pub fn format_exercise_property(property: &str) -> String { - property.replace(" ", "_").to_lowercase() -} - -pub fn generate_property_body(property: &str) -> Result { - let mut context = Context::new(); - context.insert("property", property); - TEMPLATES - .render("property_fn.rs", &context) - .map_err(|e| e.into()) -} - -pub fn generate_test_function(case: &LabeledTest, use_maplit: bool) -> Result { - let mut context = Context::from_serialize(case)?; - context.insert("use_maplit", &use_maplit); - TEMPLATES - .render("test_fn.rs", &context) - .map_err(|e| e.into()) -} - -pub fn rustfmt(file_path: &Path) -> Result<()> { - let rustfmt_is_available = { - if let Some(path_var) = env::var_os("PATH") { - env::split_paths(&path_var).any(|path| path.join("rustfmt").exists()) - } else { - false - } - }; - - if rustfmt_is_available { - Command::new("rustfmt").arg(file_path).output()?; - } - - Ok(()) -} - -pub fn exercise_exists(exercise_name: &str) -> bool { - Path::new(&*TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .exists() -} - -// Update the version of the specified exercise in the Cargo.toml file according to the passed canonical data -pub fn update_cargo_toml_version( - exercise_name: &str, - canonical_data: &CanonicalData, -) -> Result<()> { - let cargo_toml_path = Path::new(&*TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .join("Cargo.toml"); - - let cargo_toml_content = fs::read_to_string(&cargo_toml_path)?; - - let mut cargo_toml: TomlValue = cargo_toml_content.parse()?; - - { - let package_table = - cargo_toml["package"] - .as_table_mut() - .ok_or_else(|| errors::Error::SchemaTypeError { - file: "Config.toml".to_string(), - field: "package".to_string(), - as_type: "table".to_string(), - })?; - - package_table.insert( - "version".to_string(), - TomlValue::String(canonical_data.version.to_string()), - ); - } - - fs::write(&cargo_toml_path, cargo_toml.to_string())?; - - Ok(()) -} diff --git a/util/exercise/src/main.rs b/util/exercise/src/main.rs deleted file mode 100644 index ea9f1fa7a..000000000 --- a/util/exercise/src/main.rs +++ /dev/null @@ -1,119 +0,0 @@ -use clap::{App, Arg, ArgMatches, SubCommand}; -use exercise::{ - cmd::{configure, fetch_configlet, generate, update}, - errors::Result, -}; -use failure::format_err; - -// Creates a new CLI app with appropriate matches -// and returns the initialized matches. -fn init_app<'a>() -> ArgMatches<'a> { - App::new(env!("CARGO_PKG_NAME")) - .version(env!("CARGO_PKG_VERSION")) - .about(env!("CARGO_PKG_DESCRIPTION")) - .subcommand( - SubCommand::with_name("generate") - .about("Generates new exercise") - .arg(Arg::with_name("exercise_name").required(true).help("The name of the generated exercise")) - .arg(Arg::with_name("configure").long("configure").short("c").help( - "If set, the command will edit the config.json file after generating the exercise", - )) - .arg(Arg::with_name("use_maplit").long("use-maplit").short("m").help("Use the maplit crate to improve the readability of the generated test suite")), - ) - .subcommand( - SubCommand::with_name("update") - .about("Updates the specified exercise") - .arg(Arg::with_name("exercise_name").help("The name of the updated exercise")) - .arg(Arg::with_name("use_maplit").long("use-maplit").short("m").help("Use the maplit crate to improve the readability of the updated test suite")) - .arg(Arg::with_name("dont_update_readme").long("dont-update-readme").short("r").help("If set, the README of the exercise would not be updated")) - .arg(Arg::with_name("configure").long("configure").short("c").help( - "If set, the command will edit the config.json file after updating the exercise", - )) - ) - .subcommand( - SubCommand::with_name("configure") - .about("Edits config.json for the specified exercise") - .arg(Arg::with_name("exercise_name").required(true).help("The name of the configured exercise")), - ) - .subcommand( - SubCommand::with_name("fetch_configlet") - .about("Downloads and extracts configlet utility into the repo's /bin directory") - ) - .get_matches() -} - -// Determine which subcommand was used -// and call the appropriate function. -fn process_matches(matches: &ArgMatches<'_>) -> Result<()> { - match matches.subcommand() { - ("generate", Some(generate_matches)) => { - let exercise_name = generate_matches - .value_of("exercise_name") - .ok_or_else(|| format_err!("exercise name not present in args"))?; - let run_configure = generate_matches.is_present("configure"); - let use_maplit = generate_matches.is_present("use_maplit"); - - generate::generate_exercise(exercise_name, use_maplit)?; - - if run_configure { - configure::configure_exercise(exercise_name)?; - } - } - - ("update", Some(update_matches)) => { - let exercise_name = update_matches - .value_of("exercise_name") - .ok_or_else(|| format_err!("exercise name not present in args"))?; - - let run_configure = update_matches.is_present("configure"); - - let use_maplit = update_matches.is_present("use_maplit"); - - let update_readme = !update_matches.is_present("dont_update_readme"); - - update::update_exercise(exercise_name, use_maplit)?; - - if run_configure { - configure::configure_exercise(exercise_name)?; - } - - if update_readme { - exercise::run_configlet_command("generate", &[".", "-o", exercise_name])?; - } - } - - ("configure", Some(configure_matches)) => { - configure::configure_exercise( - configure_matches - .value_of("exercise_name") - .ok_or_else(|| format_err!("exercise name not present in args"))?, - )?; - } - - ("fetch_configlet", Some(_fetch_configlet_matches)) => { - if let Ok(fetch_path) = fetch_configlet::download() { - println!( - "Downloaded and moved the configlet utility to the {:?} path", - fetch_path - ); - } else { - println!("Failed to fetch the configlet utility"); - } - } - - ("", None) => { - println!("No subcommand was used.\nUse exercise --help to learn about the possible subcommands."); - } - - _ => unreachable!(), - }; - - Ok(()) -} - -fn main() -> Result<()> { - let matches = init_app(); - - process_matches(&matches)?; - Ok(()) -} diff --git a/util/exercise/src/structs.rs b/util/exercise/src/structs.rs deleted file mode 100644 index dd4e4bd5c..000000000 --- a/util/exercise/src/structs.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Rust struct for canonical test data. -//! -//! See https://github.com/exercism/problem-specifications/blob/main/canonical-schema.json -//! for more details on the JSON schema, which makes it possible to implement -//! `serde::Deserialize`. - -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::collections::HashSet; - -#[derive(Serialize, Deserialize, Debug)] -pub struct CanonicalData { - pub exercise: Exercise, - pub version: Version, - pub comments: Option, - pub cases: TestGroup, -} - -type Exercise = String; -type Version = String; -type Comments = Vec; -type TestGroup = Vec; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(untagged)] -pub enum LabeledTestItem { - Single(LabeledTest), - Array(LabeledTestGroup), -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct LabeledTest { - pub description: Description, - pub optional: Option, - pub comments: Option, - pub property: Property, - pub input: Input, - pub expected: Expected, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct LabeledTestGroup { - pub description: Description, - pub optional: Option, - pub comments: Option, - pub cases: TestGroup, -} - -type Description = String; -type Optional = String; -type Property = String; -type Input = Value; -type Expected = Value; - -impl CanonicalData { - pub fn properties(&self) -> HashSet<&str> { - self.cases - .iter() - .flat_map(LabeledTestItem::iter) - .map(|case| case.property.as_str()) - .collect() - } -} - -impl LabeledTestItem { - fn iter(&self) -> Box + '_> { - match self { - LabeledTestItem::Single(case) => Box::new(case.iter()), - LabeledTestItem::Array(cases) => Box::new(cases.iter()), - } - } -} - -impl LabeledTest { - fn iter(&self) -> impl Iterator { - std::iter::once(self) - } -} - -impl LabeledTestGroup { - fn iter(&self) -> impl Iterator { - self.cases.iter().flat_map(LabeledTestItem::iter) - } -}