diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ed8f4a432bc..234b07e766c 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/ci-workflow.yml b/.github/workflows/ci-workflow.yml
index 01ddab0cac5..e853469c6d0 100644
--- a/.github/workflows/ci-workflow.yml
+++ b/.github/workflows/ci-workflow.yml
@@ -12,12 +12,12 @@ on:
jobs:
housekeeping:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
with:
python-version: 3.11.2
@@ -49,15 +49,15 @@ jobs:
./bin/template_status.py -v -p .problem-specifications
canonical_sync:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
needs: housekeeping
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
with:
python-version: ${{ matrix.python-version }}
diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml
index 7bddcd077c0..4f6bff60471 100644
--- a/.github/workflows/issue-commenter.yml
+++ b/.github/workflows/issue-commenter.yml
@@ -5,20 +5,20 @@ on:
jobs:
comment-on-new-issue:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
name: Comments for every NEW issue.
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- name: Read issue-comment.md
id: issue-comment
- uses: juliangruber/read-file-action@v1
+ uses: juliangruber/read-file-action@b549046febe0fe86f8cb4f93c24e284433f9ab58
with:
path: .github/issue-comment.md
- name: Base comment
- uses: jd-0001/gh-action-comment-on-new-issue@v2.0.3
+ uses: jd-0001/gh-action-comment-on-new-issue@c443e1151cc69b146fd6918cc983ec1bd27ab254
with:
message: "${{ steps.issue-comment.outputs.content }}"
ignore-label: ":anger: prickle"
diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml
new file mode 100644
index 00000000000..812e9129668
--- /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 00000000000..b6ec9c5662f
--- /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/pr-commenter.yml b/.github/workflows/pr-commenter.yml
index 13c0b62d44c..f12714aec38 100644
--- a/.github/workflows/pr-commenter.yml
+++ b/.github/workflows/pr-commenter.yml
@@ -4,9 +4,9 @@ on:
jobs:
pr-comment:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- - uses: exercism/pr-commenter-action@v1.4.0
+ - uses: exercism/pr-commenter-action@085ef62d2a541a112c3ade1d24deea83665ea186
with:
github-token: "${{ github.token }}"
config-file: ".github/pr-commenter.yml"
\ No newline at end of file
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 41156435d82..4a5a9a772f1 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -6,9 +6,9 @@ on:
jobs:
stale:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- - uses: actions/stale@v8
+ - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 21
diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml
index ecfc2a10fc8..97fcf6e5be3 100644
--- a/.github/workflows/test-runner.yml
+++ b/.github/workflows/test-runner.yml
@@ -8,8 +8,8 @@ on:
jobs:
test-runner:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- name: Run test-runner
- run: docker-compose run test-runner
+ run: docker compose run test-runner
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index df8e36761c1..3f7813de10a 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/CONTRIBUTING.md b/CONTRIBUTING.md
index 0bac23e0af3..d9c30d85e0a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,31 +4,35 @@
Contributing
[](https://forum.exercism.org)
- [](https://exercism.org)
+ [](https://exercism.org)
[](https://exercism.org/blog/freeing-our-maintainers)
[](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22)
-
-
-Hi. 👋🏽 👋 **We are happy you are here.** 🎉 🌟
-
-
-
-
+> [!IMPORTANT]
+> We are not accepting community contributions at this time.
+>
+>
+>
+>
+> We love our community. We're grateful you are interested in improving the Python track.
+> But our maintainers are **not accepting community contributions at this time.**
+> If you would like to discuss possible future changes, please open a [thread on the forum](https://forum.exercism.org/).
+>
+> This [community blog post](https://exercism.org/blog/freeing-our-maintainers) contains more details.
+>
+>
+>
-We 💛 💙 our community.
-**`But our maintainers are not accepting community contributions at this time.`**
-Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for details.
-
-
+Hi. 👋🏽 👋 **We are happy you are here.** 🎉 🌟
+
**`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website].
This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students.
-🌟 Track exercises support Python `3.7` - `3.11.2`.
+🌟 Track exercises support Python `3.7` - `3.11.5`.
Exceptions to this support are noted where they occur.
🌟 Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.2`.
@@ -43,20 +47,25 @@ Practice exercises are open-ended, and can be used to practice concepts learned,
It is not uncommon to discover typos, confusing directions, or incorrect implementations of certain tests or code examples. Or you might have a great suggestion for a hint to aid students ( 💙 ), see optimizations for exemplar or test code, find missing test cases to add, or want to correct factual and/or logical errors. Or maybe you have a great idea 💡 for an exercise or feature ( 💙 ).
_Our track is always a work in progress!_ 🌟🌟
-While contributions are paused, we ask that you [`open a thread in our community forum`](https://forum.exercism.org) to let us know what you have found/suggest.
+While contributions are paused, we ask that you [**open a thread in our community forum**](https://forum.exercism.org) to let us know what you have found/suggest.
## 🚧 **Did you write a patch that fixes a bug?**
-**`Our maintainers are not accepting community contributions at this time.`**
-Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for details.
+Our maintainers are not accepting community contributions at this time.
+
+Until the pause on contributions ends, all PRs from the larger community will be **automatically closed** with a note.
+We ask that you [**open a thread in our community forum**](https://forum.exercism.org) to discuss any potential changes. Changes may or may not be approved, depending on the forum discussion.
-Once the pause ends, we will **happily** consider your PR.
-Until that time, all PRs from the larger community will be **automatically closed** with a note.
+Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for additional details.
+
-We're leaving the general contributing docs below for our long-term collaborators and maintainers.
+We're leaving the track contributing docs below for our long-term collaborators and maintainers.
+
+
+ Python Track Contributing Docs
In General
@@ -170,7 +179,7 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer
- Favor `f-strings` for dynamic failure messages. Please make your error messages as relevant and human-readable as possible.
- We relate test cases to **task number** via a custom [PyTest Marker][pytestmark].
- These take the form of `@pytest.mark.task(taskno=)`. See [Guido's Gorgeous Lasagna][guidos-gorgeous-lasagna-testfile] for an example.
-- We prefer **test data files** when test inputs/ouputs are verbose.
+- We prefer **test data files** when test inputs/outputs are verbose.
- These should be named with `_data` or `_test_data` at the end of the filename, and saved alongside the test case file.
- See the [Cater-Waiter][cater-waiter] exercise directory for an example of this setup.
- **Test data files** need to be added under an `editor` key within [`config.json "files"`][exercise-config-json].
@@ -270,9 +279,9 @@ Although the majority of test cases are written using `unittest.TestCase`,
- [ ] `.meta/config.json` (**required**)
- [ ] `.meta/example.py` (**required**)
- [ ] `.meta/design.md` (_optional_)
- - [ ] `.meta/template.j2` (_template for generating tests from cannonical data_)
- - [ ] `.meta/tests.toml` (_tests configuration from cannonical data_)
- - [ ] `_test.py` (_**auto-generated from cannonical data**_)
+ - [ ] `.meta/template.j2` (_template for generating tests from canonical data_)
+ - [ ] `.meta/tests.toml` (_tests configuration from canonical data_)
+ - [ ] `_test.py` (_**auto-generated from canonical data**_)
- [ ] `.py` (**required**)
@@ -369,45 +378,32 @@ configlet generate --spec-path path/to/problem/specifications --
configlet generate --spec-path path/to/problem/specifications
```
+
+
[.flake8]: https://github.com/exercism/python/blob/main/.flake8
[.style.yapf]: https://github.com/exercism/python/blob/main/.style.yapf
[american-english]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md
-[being-a-good-community-member]: https://github.com/exercism/docs/tree/main/community/good-member
[card-games-testfile]: https://github.com/exercism/python/blob/main/exercises/concept/card-games/lists_test.py
[cater-waiter]: https://github.com/exercism/python/tree/main/exercises/concept/cater-waiter
[concept-exercise-anatomy]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md
-[concept-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md
[config-json]: https://github.com/exercism/javascript/blob/main/config.json
-[config-json]: https://github.com/exercism/python/blob/main/config.json
-[configlet-general]: https://github.com/exercism/configlet
[configlet-lint]: https://github.com/exercism/configlet#configlet-lint
[configlet]: https://github.com/exercism/docs/blob/main/building/configlet/generating-documents.md
[distinguishing-test-iterations]: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests
[enumerate]: https://docs.python.org/3/library/functions.html#enumerate
[eol]: https://en.wikipedia.org/wiki/Newline
[exercise-config-json]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md#full-example
-[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-internal-linking]: https://github.com/exercism/docs/blob/main/building/markdown/internal-linking.md
[exercism-markdown-specification]: https://github.com/exercism/docs/blob/main/building/markdown/markdown.md
[exercism-markdown-widgets]: https://github.com/exercism/docs/blob/main/building/markdown/widgets.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
[flake8-noqa]: https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html#in-line-ignoring-errors
[flake8]: http://flake8.pycqa.org/
[google-coding-style]: https://google.github.io/styleguide/pyguide.html
[guidos-gorgeous-lasagna-testfile]: https://github.com/exercism/python/blob/main/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py
-[help-wanted]: https://github.com/exercism/python/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22
[implicit-line-joining]: https://google.github.io/styleguide/pyguide.html#32-line-length
[markdown-language]: https://guides.github.com/pdfs/markdown-cheatsheet-online.pdf
[open-an-issue]: https://github.com/exercism/python/issues/new/choose
@@ -429,5 +425,4 @@ configlet generate --spec-path path/to/problem/specifications
[the-words-that-we-use]: https://github.com/exercism/docs/blob/main/community/good-member/words.md
[unittest]: https://docs.python.org/3/library/unittest.html#unittest.TestCase
[version-tagged-language-features]: https://docs.python.org/3/library/stdtypes.html#dict.popitem
-[website-contributing-section]: https://exercism.org/docs/building
[yapf]: https://github.com/google/yapf
diff --git a/README.md b/README.md
index 1c82f33b9d0..f3d083aab42 100644
--- a/README.md
+++ b/README.md
@@ -10,16 +10,33 @@
-Hi. 👋🏽 👋 **We are happy you are here.** 🎉 🌟
+> [!IMPORTANT]
+> We are not accepting community contributions at this time.
+>
+>
+>
+>
+> We love our community. We're grateful you are interested in improving the Python track.
+> But our maintainers are **not accepting community contributions at this time.**
+> If you would like to suggest a change / discuss an issue, please open a [thread on the forum](https://forum.exercism.org/).
+>
+> This [community blog post](https://exercism.org/blog/freeing-our-maintainers) contains more details.
+>
+>
+>
+Hi. 👋🏽 👋 **We are happy you are here.** 🎉 🌟
+
+
+
**`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website].
This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students.
-🌟 Track exercises support Python `3.7` - `3.11.2`.
+🌟 Track exercises support Python `3.7` - `3.11.5`.
Exceptions to this support are noted where they occur.
-🌟 Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.2`.
+🌟 Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.5`.
Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree 🌴 .
Concept exercises are constrained to a small set of language or syntax features.
@@ -43,17 +60,17 @@ It might also be helpful to look at [Being a Good Community Member][being-a-good
-We 💛 💙 our community.
-**`But our maintainers are not accepting community contributions at this time.`**
+We 💛 💙 our community.
+**But our maintainers are not accepting community contributions at this time.**
Please read this [community blog post][freeing-maintainers] for details.
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/).
+We'd love if you did that via our [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._
+_Thoughtful suggestions will likely result in faster & more enthusiastic responses from volunteers._
@@ -67,7 +84,7 @@ _Thoughtful suggestions will likely result faster & more enthusiastic responses
## Python Software and Documentation
-**Copyright © 2001-2022 Python Software Foundation. All rights reserved.**
+**Copyright © 2001-2025 Python Software Foundation. All rights reserved.**
Python software and documentation are licensed under the [PSF License Agreement][psf-license].
@@ -99,7 +116,6 @@ This repository uses the [MIT License](/LICENSE).
[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
-[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md
[psf-license]: https://docs.python.org/3/license.html#psf-license
[python-syllabus]: https://exercism.org/tracks/python/concepts
[suggesting-improvements]: https://github.com/exercism/docs/blob/main/community/good-member/suggesting-exercise-improvements.md
diff --git a/bin/fetch-configlet b/bin/fetch-configlet
index 4800e150849..6bef43ab722 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/generate_tests.py b/bin/generate_tests.py
index 33580137058..2ad23a9b5f1 100755
--- a/bin/generate_tests.py
+++ b/bin/generate_tests.py
@@ -138,7 +138,7 @@ def parse_datetime(string: str, strip_module: bool = False) -> datetime:
]| # OR
o(?:[0-8]{1,3}) # an octal value
| # OR
- x(?:[0-9A-Fa-f]{2}) # a hexidecimal value
+ x(?:[0-9A-Fa-f]{2}) # a hexadecimal value
| # OR
N # a unicode char name composed of
\{ # an opening brace
@@ -204,6 +204,8 @@ def regex_find(s: str, find: str) -> List[Any]:
def regex_split(s: str, find: str) -> List[str]:
return re.split(find, s)
+def join_test_inputs(test_inputs: list) -> str:
+ return "\n".join(test_inputs)
def filter_test_cases(cases: List[TypeJSON], opts: TestsTOML) -> List[TypeJSON]:
"""
@@ -261,6 +263,19 @@ def format_file(path: Path) -> NoReturn:
def check_template(slug: str, tests_path: Path, tmpfile: Path):
+ """Generate a new test file and diff against existing file.
+
+ Note: The timestamp in each test file creates issues with
+ Python difflib, so it is skipped when being prepped
+ for diff.
+
+ You can see this "skipping" on lines 281 & 283.
+ However, this rather crude method creates
+ an empty "false positive" diff. This empty diff is
+ then skipped in lines 293 & 294, so that it can be
+ considered a pass..
+ """
+
try:
check_ok = True
if not tmpfile.is_file():
@@ -271,24 +286,25 @@ def check_template(slug: str, tests_path: Path, tmpfile: Path):
check_ok = False
if check_ok and not filecmp.cmp(tmpfile, tests_path):
with tests_path.open() as f:
- for line in range(4):
- next(f)
- current_lines = f.readlines()
+ current_lines = f.readlines()[3:]
with tmpfile.open() as f:
- for line in range(4):
- next(f)
- rendered_lines = f.readlines()
- diff = difflib.unified_diff(
+ rendered_lines = f.readlines()[3:]
+
+ diff = list(difflib.unified_diff(
current_lines,
rendered_lines,
fromfile=f"[current] {tests_path.name}",
tofile=f"[generated] {tmpfile.name}",
- )
- logger.debug(f"{slug}: ##### DIFF START #####")
- for line in diff:
- logger.debug(line.strip())
- logger.debug(f"{slug}: ##### DIFF END #####")
- check_ok = False
+ lineterm="\n",
+ ))
+ if not diff:
+ check_ok = True
+ else:
+ logger.debug(f"{slug}: ##### DIFF START #####")
+ for line in diff:
+ logger.debug(line.strip())
+ logger.debug(f"{slug}: ##### DIFF END #####")
+ check_ok = False
if not check_ok:
logger.error(
f"{slug}: check failed; tests must be regenerated with bin/generate_tests.py"
@@ -395,6 +411,7 @@ def generate(
env.filters["regex_replace"] = regex_replace
env.filters["regex_find"] = regex_find
env.filters["regex_split"] = regex_split
+ env.filters["join_test_inputs"] = join_test_inputs
env.filters["zip"] = zip
env.filters["parse_datetime"] = parse_datetime
env.filters["escape_invalid_escapes"] = escape_invalid_escapes
diff --git a/concepts/basics/about.md b/concepts/basics/about.md
index 30ea083022f..ef873ce418f 100644
--- a/concepts/basics/about.md
+++ b/concepts/basics/about.md
@@ -6,7 +6,8 @@ Imperative, declarative (e.g., functional), and object-oriented programming _sty
Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions.
-Python was created by Guido van Rossum and first released in 1991. The [Python Software Foundation][psf] manages and directs resources for Python and CPython development and receives proposals for changes to the language from [members][psf membership] of the community via [Python Enhancement Proposals or PEPs][peps].
+Python was created by Guido van Rossum and first released in 1991.
+The [Python Software Foundation][psf] manages and directs resources for Python and CPython development and receives proposals for changes to the language from [members][psf membership] of the community via [Python Enhancement Proposals or PEPs][peps].
Complete documentation for the current release can be found at [docs.python.org][python docs].
@@ -18,8 +19,14 @@ Complete documentation for the current release can be found at [docs.python.org]
- [Python FAQs][python faqs]
- [Python Glossary of Terms][python glossary of terms]
+
+
+This first concept introduces 4 major Python language features:
+1. Name Assignment (_variables and constants_),
+2. Functions (_the `def` keyword and the `return` keyword_),
+3. Comments, and
+4. Docstrings.
-This concept introduces 4 major Python language features: Name Assignment (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings.
~~~~exercism/note
@@ -32,9 +39,9 @@ On the Python track, [variables][variables] are always written in [`snake_case`]
[snake case]: https://en.wikipedia.org/wiki/Snake_case
+[the zen of python]: https://www.python.org/dev/peps/pep-0020/
[variables]: https://realpython.com/python-variables/
[what is pythonic]: https://blog.startifact.com/posts/older/what-is-pythonic.html
-[the zen of python]: https://www.python.org/dev/peps/pep-0020/
~~~~
@@ -102,7 +109,7 @@ Related functions and classes (_with their methods_) can be grouped together in
The `def` keyword begins a [function definition][function definition].
Each function can have zero or more formal [parameters][parameters] in `()` parenthesis, followed by a `:` colon.
-Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_.
+Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_:
```python
@@ -127,24 +134,55 @@ def add_two_numbers(number_one, number_two):
IndentationError: unindent does not match any outer indentation level
```
-Functions explicitly return a value or object via the [`return`][return] keyword.
-Functions that do not have an explicit `return` expression will _implicitly_ return [`None`][none].
+
+Functions _explicitly_ return a value or object via the [`return`][return] keyword:
+
```python
-# Function definition on first line.
+# Function definition on first line, explicit return used on final line.
def add_two_numbers(number_one, number_two):
- result = number_one + number_two
- return result # Returns the sum of the numbers.
+ return number_one + number_two
+
+# Calling the function in the Python terminal returns the sum of the numbers.
>>> add_two_numbers(3, 4)
7
-# This function will return None.
+# Assigning the function call to a variable and printing
+# the variable will also return the value.
+>>> sum_with_return = add_two_numbers(5, 6)
+>>> print(sum_with_return)
+11
+```
+
+Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object.
+The details of `None` will be covered in a later exercise.
+For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null:
+
+
+```python
+# This function does not have an explicit return.
def add_two_numbers(number_one, number_two):
result = number_one + number_two
+
+# Calling the function in the Python terminal appears
+# to not return anything at all.
+>>> add_two_numbers(5, 7)
+>>>
+
+
+# Using print() with the function call shows that
+# the function is actually returning the **None** object.
>>> print(add_two_numbers(5, 7))
None
+
+
+# Assigning the function call to a variable and printing
+# the variable will also show None.
+>>> sum_without_return = add_two_numbers(5, 6)
+>>> print(sum_without_return)
+None
```
diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md
index 34e2a8804d7..818dd47deac 100644
--- a/concepts/basics/introduction.md
+++ b/concepts/basics/introduction.md
@@ -2,18 +2,26 @@
Python is a [dynamic and strongly typed][dynamic typing in python] programming language.
It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints].
+Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions.
+
+Python was created by Guido van Rossum and first released in 1991.
Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally **[everything in Python is an object][everythings an object]**.
-Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions.
+We'll dig more into what all of that means as we continue through the Python track concepts.
-Python was created by Guido van Rossum and first released in 1991.
+This first concept (`basics`) introduces 4 major Python language features:
+1. Name Assignment (_variables and constants_),
+2. Functions (_the `def` keyword and the `return` keyword_),
+3. Comments, and
+4. Docstrings.
+
## Name Assignment (Variables & Constants)
Programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables_) to any type of object using the assignment `=` operator: ` = `.
-A name can be reassigned (or re-bound) to different values (different object types) over its lifetime.
+A name can be reassigned (or re-bound) to different values (different object types) over its lifetime:
```python
@@ -37,9 +45,10 @@ A name can be reassigned (or re-bound) to different values (different object typ
### Constants
-Constants are names meant to be assigned only once in a program.
-They should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in the program.
-Using `SCREAMING_SNAKE_CASE` signals that the name should not be re-assigned, or its value mutated.
+Constants are names meant to be assigned only once in a program — although Python will not prevent re-assignment.
+Using `SCREAMING_SNAKE_CASE` signals to anyone reading the code that the name should **not** be re-assigned, or its value mutated.
+Constants should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in a program.
+
## Functions
@@ -50,7 +59,7 @@ Statements for the _body_ of the function begin on the line following `def` and
```python
-# The body of a function is indented by 2 spaces, & prints the sum of the numbers.
+# The body of this function is indented by 2 spaces,& prints the sum of the numbers.
def add_two_numbers(number_one, number_two):
total = number_one + number_two
print(total)
@@ -71,24 +80,57 @@ def add_two_numbers(number_one, number_two):
IndentationError: unindent does not match any outer indentation level
```
-Functions explicitly return a value or object via the [`return`][return] keyword.
-Functions that do not have an explicit `return` expression will _implicitly_ return [`None`][none].
+
+Functions _explicitly_ return a value or object via the [`return`][return] keyword:
+
```python
-# Function definition on first line.
+# Function definition on first line, explicit return used on final line.
def add_two_numbers(number_one, number_two):
- result = number_one + number_two
- return result # Returns the sum of the numbers.
+ return number_one + number_two
+
+# Calling the function in the Python terminal returns the sum of the numbers.
>>> add_two_numbers(3, 4)
7
-# This function will return None.
+# Assigning the function call to a variable and printing
+# the variable will also return the value.
+>>> sum_with_return = add_two_numbers(5, 6)
+>>> print(sum_with_return)
+11
+```
+
+
+Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object.
+This means that if you do not use `return` in a function, Python will return the `None` object for you.
+The details of `None` will be covered in a later exercise.
+For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null:
+
+
+```python
+# This function does not have an explicit return.
def add_two_numbers(number_one, number_two):
result = number_one + number_two
+
+# Calling the function in the Python terminal appears
+# to not return anything at all.
+>>> add_two_numbers(5, 7)
+>>>
+
+
+# Using print() with the function call shows that
+# the function is actually returning the **None** object.
>>> print(add_two_numbers(5, 7))
None
+
+
+# Assigning the function call to a variable and printing
+# the variable will also show None.
+>>> sum_without_return = add_two_numbers(5, 6)
+>>> print(sum_without_return)
+None
```
diff --git a/concepts/basics/links.json b/concepts/basics/links.json
index 3e5561228ee..1d1d640c9e7 100644
--- a/concepts/basics/links.json
+++ b/concepts/basics/links.json
@@ -1,5 +1,6 @@
[
- {"url": "https://lerner.co.il/2019/06/18/understanding-python-assignment/",
+ {
+ "url": "https://lerner.co.il/2019/06/18/understanding-python-assignment/",
"description": "Reuven Lerner: Understanding Python Assignment"
},
{
@@ -14,6 +15,10 @@
"url": "https://www.pythonmorsels.com/everything-is-an-object/",
"description": "Python Morsels: Everything is an Object"
},
+ {
+ "url": "https://eli.thegreenplace.net/2012/03/23/python-internals-how-callables-work/",
+ "description": "Eli Bendersky: Python internals: how callables work"
+ },
{
"url": "https://stackoverflow.com/questions/11328920/is-python-strongly-typed",
"description": "dynamic typing and strong typing"
diff --git a/concepts/binary-octal-hexadecimal/about.md b/concepts/binary-octal-hexadecimal/about.md
index 667f3eb6cd7..a7fca3714e3 100644
--- a/concepts/binary-octal-hexadecimal/about.md
+++ b/concepts/binary-octal-hexadecimal/about.md
@@ -190,8 +190,8 @@ As with binary and octal, Python will automatically convert hexadecimal literals
291
```
-As with binary and octal - hexidecimal literals **are ints**, and you can perform all integer operations.
-Prefixing a non-hexidecimal number with `0x` will raise a `SyntaxError`.
+As with binary and octal - hexadecimal literals **are ints**, and you can perform all integer operations.
+Prefixing a non-hexadecimal number with `0x` will raise a `SyntaxError`.
### Converting to and from Hexadecimal Representation
@@ -216,9 +216,6 @@ As with binary and octal, giving the wrong base will raise a `ValueError`.
[binary]: https://en.wikipedia.org/wiki/Binary_number
[bit_count]: https://docs.python.org/3/library/stdtypes.html#int.bit_count
[bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length
-[bit_count]: https://docs.python.org/3/library/stdtypes.html#int.bit_count
-[bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length
[hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal
-[methods-int]: https://docs.python.org/3/library/stdtypes.html#additional-methods-on-integer-types
[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system
[octal]: https://en.wikipedia.org/wiki/Octal
diff --git a/concepts/binary-octal-hexadecimal/introduction.md b/concepts/binary-octal-hexadecimal/introduction.md
index 820aac33ee7..a06ac922faf 100644
--- a/concepts/binary-octal-hexadecimal/introduction.md
+++ b/concepts/binary-octal-hexadecimal/introduction.md
@@ -5,7 +5,7 @@ Binary is base 2, octal is base 8, and hexadecimal is base 16.
Normal integers are base 10 in python.
Binary, octal, and hexadecimal literals are all considered `int` subtypes and Python automatically converts between them.
This means that they can only represent zero, positive, and negative numbers that do not have a fractional or decimal part.
-Binary, octal, and hexidecimal numbers support all integer operations.
+Binary, octal, and hexadecimal numbers support all integer operations.
However, division (_as with ints_) will return a `float`.
[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system
diff --git a/concepts/bitwise-operators/.meta/config.json b/concepts/bitwise-operators/.meta/config.json
index 9b9e8da5a9b..7767ff5d740 100644
--- a/concepts/bitwise-operators/.meta/config.json
+++ b/concepts/bitwise-operators/.meta/config.json
@@ -1,5 +1,5 @@
{
- "blurb": "TODO: add blurb for this concept",
- "authors": ["bethanyg", "cmccandless"],
+ "blurb": "Python supports bitwise operations such as left/right shift, and, or, xor, and not.",
+ "authors": ["BethanyG", "colinleach"],
"contributors": []
}
diff --git a/concepts/bitwise-operators/about.md b/concepts/bitwise-operators/about.md
index c628150d565..a68e5378f12 100644
--- a/concepts/bitwise-operators/about.md
+++ b/concepts/bitwise-operators/about.md
@@ -1,2 +1,197 @@
-#TODO: Add about for this concept.
+# About
+Down at the hardware level, transistors can only be on or off: two states that we traditionally represent with `1` and `0`.
+These are the [`binary digits`][binary-digits], abbreviated as [`bits`][bits].
+Awareness of `bits` and `binary` is particularly important for systems programmers working in low-level languages.
+
+However, for most of the history of computing the programming priority has been to find increasingly sophisticated ways to _abstract away_ this binary reality.
+In Python (and many other [high-level programming languages][high-level-language]), we work with `int`, `float`, `string` and other defined _types_, up to and including audio and video formats.
+We let the Python internals take care of (eventually) translating everything to bits.
+
+Nevertheless, using [bitwise-operators][python-bitwise-operators] and [bitwise operations][python-bitwise-operations] can sometimes have significant advantages in speed and memory efficiency, even in a high-level language like Python.
+
+
+## Entering and Displaying Binary Numbers
+
+Unsurprisingly, Python interacts with the user using decimal numbers, but a programmer can override this default.
+In fact, Python will readily accept an `int` in `binary`, `hexadecimal`, or `octal` format, and will happily perform mathematical operations between them.
+For more details, you can review the [concept:python/binary-octal-hexadecimal]() concept.
+
+Binary numbers are entered with a `0b` prefix, just as `0x` can be used for hexadecimal (_hex numbers are a concise way to represent groups of 4 bits_), and `oct` can be used for octal numbers.
+
+There are multiple ways to convert integers to binary strings, varying in whether they include the `0b` prefix and whether they support left-padding with zeros:
+
+
+```python
+# Binary entry.
+>>> 0b10111
+23
+
+# Converting an int display to binary string, with prefix.
+>>> bin(23)
+'0b10111'
+
+>>> number = 23
+
+# Binary without prefix, padded to 8 digits.
+>>> format(number, '08b')
+'00010111'
+
+# Same format, but using an f-string.
+>>> f"{number} in decimal is {number:08b} in binary and {number:x} in hex"
+'23 in decimal is 00010111 in binary and 17 in hex'
+```
+
+
+## [`Bitwise Logic`][python-bitwise-operations]
+
+In the [concept:python/bools]() concept, we discussed the _logical operators_ `and`, `or` and `not` used with Boolean (_`True` and `False`_) values.
+The same logic rules apply when working with bits.
+
+However, the bitwise equivalents of the logical operators `&` (_and_), `|` (_or_), `~` (_not_), and `^` (_[XOR][xor]_), are applied to each _bit_ in a binary representation, treating `1` as `True` ("on") and `0` as `False` ("off").
+An example with the bitwise `&` might make this clearer:
+
+
+```python
+>>> x = 0b01100110
+>>> y = 0b00101010
+
+>>> format(x & y, '08b')
+'00100010'
+```
+
+Only positions with a `1` in _**both**_ the input numbers are set to `1` in the output.
+
+Bitwise `&` is commonly used as a way to isolate single bits in a compacted set of `True`/`False` values, such as user-configurable settings in an app.
+This enables the value of individual bits to control program logic:
+
+
+```python
+>>> number = 0b0110
+>>> number & 0b0001 > 0
+False
+
+>>> number & 0b0010 > 0
+True
+```
+
+
+For a bitwise `|` (or), a `1` is set in the output if there is a `1` in _**either**_ of the inputs:
+
+
+```python
+>>> x = 0b01100110
+>>> y = 0b00101010
+
+>>> format(x | y, '08b')
+'01101110'
+```
+
+
+With the `^` operator for bitwise e**x**clusive **or** (xor), a `1` is set if it appears in _**either**_ of the inputs _**but not both**_ inputs.
+This symbol might seem familiar from the [concept:python/sets]() concept, where it is used for `set` _symmetric difference_, which is the same as [xor applied to sets][symmetric-difference].
+If xor `^` seems strange, be aware that this is by far the [most common operation in cryptography][xor-cipher].
+
+
+```python
+>>> x = 0b01100110
+>>> y = 0b00101010
+
+>>> format(x ^ y, '08b')
+'01001100'
+```
+
+
+Finally, there is the `~` operator (_the [tilde][tilde] character_), which is a bitwise `not` that takes a single input and _**inverts all the bits**_, which might not be the result you were expecting!
+Each `1` in the representation changes to `0`, and vice versa.
+See the section below for details.
+
+
+## Negative Numbers and Binary Representation
+
+In decimal representation, we distinguish positive and negative numbers by using a `+` or `-` sign to the left of the digits.
+Using these symbols at a binary level proved inefficient for digital computing and raised the problem that `+0` is not the same as `-0`.
+
+Rather than using `-` and `+`, all modern computers use a [`twos-complement`][twos-complement] representation for negative numbers, right down to the silicon chip level.
+This means that all bits are inverted and a number is _**interpreted as negative**_ if the left-most bit (also termed the "most significant bit", or MSB) is a `1`.
+Positive numbers have an MSB of `0`.
+This representation has the advantage of only having one version of zero, so that the programmer doesn't have to manage `-0` and `+0`.
+
+This way of representing negative and positive numbers adds a complication for Python: there are no finite-integer concepts like `int32` or `int64` internally in the core language.
+In 'modern' Python, `int`s are of unlimited size (_limited only by hardware capacity_), and a negative or bit-inverted number has a (_theoretically_) infinite number of `1`'s to the left, just as a positive number has unlimited `0`'s.
+
+This makes it difficult to give a useful example of `bitwise not`:
+
+```python
+>>> x = 0b01100110
+>>> format(x, '08b')
+'01100110'
+
+# This is a negative binary (not twos-complement display).
+>>> format(~x, '08b')
+'-1100111'
+
+ # Decimal representation.
+>>> x
+102
+
+# Using the Bitwise not, with an unintuitive result.
+>>> ~x
+-103
+```
+
+This is **not** the `0b10011001` we would see in languages with fixed-size integers.
+
+The `~` operator only works as expected with _**unsigned**_ byte or integer types, or with fixed-sized integer types.
+These numeric types are supported in third-party packages such as [`NumPy`][numpy], [`pandas`][pandas], and [`sympy`][sympy] but not in core Python.
+
+In practice, Python programmers quite often use the shift operators described below and `& | ^` with positive numbers only.
+Bitwise operations with negative numbers are much less common.
+One technique is to add [`2**32 (or 1 << 32)`][unsigned-int-python] to a negative value to make an `int` unsigned, but this gets difficult to manage.
+Another strategy is to work with the [`ctypes`][ctypes-module] module, and use c-style integer types, but this is equally unwieldy.
+
+
+## [`Shift operators`][bitwise-shift-operators]
+
+The left-shift operator `x << y` simply moves all the bits in `x` by `y` places to the left, filling the new gaps with zeros.
+Note that this is arithmetically identical to multiplying a number by `2**y`.
+
+The right-shift operator `x >> y` does the opposite.
+This is arithmetically identical to integer division `x // 2**y`.
+
+Keep in mind the previous section on negative numbers and their pitfalls when shifting.
+
+
+```python
+>>> x = 8
+>>> format(x, '08b')
+'00001000'
+
+# A left bit shift.
+>>> x << 2
+32
+
+>>> format(x << 2, '08b')
+'00100000'
+
+# A right bit shift.
+>>> format(x >> 2, '08b')
+'00000010'
+```
+
+[binary-digits]: https://www.khanacademy.org/computing/computers-and-internet/xcae6f4a7ff015e7d:digital-information/xcae6f4a7ff015e7d:binary-numbers/v/the-binary-number-system
+[bits]: https://en.wikipedia.org/wiki/Bit
+[bitwise-shift-operators]: https://docs.python.org/3/reference/expressions.html#shifting-operations
+[ctypes-module]: https://docs.python.org/3/library/ctypes.html#module-ctypes
+[high-level-language]: https://en.wikipedia.org/wiki/High-level_programming_language
+[numpy]: https://numpy.org/doc/stable/user/basics.types.html
+[pandas]: https://pandas.pydata.org/docs/reference/arrays.html#nullable-integer
+[python-bitwise-operations]: https://docs.python.org/3/reference/expressions.html#binary-bitwise-operations
+[python-bitwise-operators]: https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations
+[symmetric-difference]: https://math.stackexchange.com/questions/84184/relation-between-xor-and-symmetric-difference#:~:text=It%20is%20the%20same%20thing,they%20are%20indeed%20the%20same.
+[sympy]: https://docs.sympy.org/latest/modules/codegen.html#predefined-types
+[tilde]: https://en.wikipedia.org/wiki/Tilde
+[twos-complement]: https://en.wikipedia.org/wiki/Two%27s_complement#:~:text=Two's%20complement%20is%20the%20most,number%20is%20positive%20or%20negative.
+[unsigned-int-python]: https://stackoverflow.com/a/20768199
+[xor-cipher]: https://en.wikipedia.org/wiki/XOR_cipher
+[xor]: https://stackoverflow.com/a/2451393
diff --git a/concepts/bitwise-operators/introduction.md b/concepts/bitwise-operators/introduction.md
index bbe12ffd5e9..88aba3a6a7b 100644
--- a/concepts/bitwise-operators/introduction.md
+++ b/concepts/bitwise-operators/introduction.md
@@ -1 +1,20 @@
-#TODO: Add introduction for this concept.
+# Introduction
+
+Down at the hardware level, transistors can only be on or off: two states that we traditionally represent with `1` and `0`.
+These are the [`binary digits`][binary-digits], abbreviated as [`bits`][bits].
+Awareness of `bits` and `binary` is particularly important for systems programmers working in low-level languages.
+
+However, for most of the history of computing the programming priority has been to find increasingly sophisticated ways to _abstract away_ this binary reality.
+
+
+In Python (and many other [high-level programming languages][high-level-language]), we work with `int`, `float`, `string` and other defined _types_, up to and including audio and video formats.
+We let the Python internals take care of (eventually) translating everything to bits.
+
+
+Nevertheless, using [bitwise-operators][python-bitwise-operators] and [bitwise operations][python-bitwise-operations] can sometimes have significant advantages in speed and memory efficiency, even in a high-level language like Python.
+
+[high-level-language]: https://en.wikipedia.org/wiki/High-level_programming_language
+[binary-digits]: https://www.khanacademy.org/computing/computers-and-internet/xcae6f4a7ff015e7d:digital-information/xcae6f4a7ff015e7d:binary-numbers/v/the-binary-number-system
+[bits]: https://en.wikipedia.org/wiki/Bit
+[python-bitwise-operations]: https://docs.python.org/3/reference/expressions.html#binary-bitwise-operations
+[python-bitwise-operators]: https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations
diff --git a/concepts/bitwise-operators/links.json b/concepts/bitwise-operators/links.json
index eb5fb7c38a5..7c103c84630 100644
--- a/concepts/bitwise-operators/links.json
+++ b/concepts/bitwise-operators/links.json
@@ -1,18 +1,22 @@
[
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://wiki.python.org/moin/BitwiseOperators/",
+ "description": "BitwiseOperators on the Python wiki."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://realpython.com/python-bitwise-operators",
+ "description": "Real Python: Bitwise Operators in Python."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://stackoverflow.com/a/20768199",
+ "description": "Stack Overflow: Convert a Python int to an unsigned int."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://www.khanacademy.org/computing/computer-science/cryptography/ciphers/a/xor-bitwise-operation",
+ "description": "Khan Academy: The Ultimate Shift Cipher."
+ },
+ {
+ "url": "https://en.wikipedia.org/wiki/XOR_cipher",
+ "description": "The XOR Cipher"
}
]
diff --git a/concepts/bools/about.md b/concepts/bools/about.md
index 4b697270659..a2680fc06b3 100644
--- a/concepts/bools/about.md
+++ b/concepts/bools/about.md
@@ -1,6 +1,6 @@
# About
-Python represents True and False values with the [bool][bool] type.
+Python represents true and false values with the [`bool`][bools] type, which is a subtype of `int`.
There are only two Boolean values in this type: `True` and `False`.
These values can be assigned to a variable and combined with the [Boolean operators][boolean-operators] (`and`, `or`, `not`):
@@ -139,3 +139,5 @@ It is considered a [Python anti-pattern][comparing to true in the wrong way] to
[Boolean-operators]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
[comparing to true in the wrong way]: https://docs.quantifiedcode.com/python-anti-patterns/readability/comparison_to_true.html
[comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons
+
+[bools]: https://docs.python.org/3/library/stdtypes.html#typebool
\ No newline at end of file
diff --git a/concepts/bools/introduction.md b/concepts/bools/introduction.md
index 535f90be07c..af24137025e 100644
--- a/concepts/bools/introduction.md
+++ b/concepts/bools/introduction.md
@@ -1,6 +1,6 @@
# Introduction
-Python represents true and false values with the `bool` type.
+Python represents true and false values with the [`bool`][bools] type, which is a subtype of `int`.
There are only two values under that type: `True` and `False`.
These values can be bound to a variable:
@@ -21,3 +21,5 @@ We can evaluate Boolean expressions using the `and`, `or`, and `not` operators.
>>> true_variable = not False
>>> false_variable = not True
```
+
+[bools]: https://docs.python.org/3/library/stdtypes.html#typebool
\ No newline at end of file
diff --git a/concepts/class-inheritance/about.md b/concepts/class-inheritance/about.md
index 5db7909e2c7..9f1bdf30cd9 100644
--- a/concepts/class-inheritance/about.md
+++ b/concepts/class-inheritance/about.md
@@ -7,7 +7,7 @@ In situations where only a small amount of functionality needs to be customized
`Inheritance` describes `is a kind of` relationship between two or more classes, abstracting common details into super (_base_ or _parent_) class and storing specific ones in the subclass (_derived class_ or _child class_).
-To create a child class, specify the parent class name inside the pair of parenthesis, followed by it's name.
+To create a child class, specify the parent class name inside the pair of parenthesis, followed by its name.
Example
```python
class Child(Parent):
diff --git a/concepts/classes/about.md b/concepts/classes/about.md
index f50af7321d3..11b03643543 100644
--- a/concepts/classes/about.md
+++ b/concepts/classes/about.md
@@ -118,7 +118,7 @@ class MyClass:
def __init__(self, location):
# This is an instance or object property, attribute, or variable.
- # Note that we are unpacking the tuple argument into two seperate instance variables.
+ # Note that we are unpacking the tuple argument into two separate instance variables.
self.location_x = location[0]
self.location_y = location[1]
@@ -314,12 +314,12 @@ class MyClass:
# This will compile and run without error, but has no current functionality.
def pending_functionality(self):
- # Stubbing or placholding the body of this method.
+ # Stubbing or place-holding the body of this method.
pass
```
[class method]: https://stackoverflow.com/questions/17134653/difference-between-class-and-instance-methods
-[dunder]: https://www.dataindependent.com/python/python-glossary/python-dunder/
+[dunder]: https://mathspp.com/blog/pydonts/dunder-methods
[oop]: https://www.educative.io/blog/object-oriented-programming
[dot notation]: https://stackoverflow.com/questions/45179186/understanding-the-dot-notation-in-python
[shadowing]: https://oznetnerd.com/2017/07/17/python-shadowing/
diff --git a/concepts/classes/links.json b/concepts/classes/links.json
index 5687b92a3d1..8cc9ba5926e 100644
--- a/concepts/classes/links.json
+++ b/concepts/classes/links.json
@@ -17,7 +17,7 @@
},
{
"url": "https://dbader.org/blog/6-things-youre-missing-out-on-by-never-using-classes-in-your-python-code",
- "description": "6 Things Youre Missing out on by never using classes in your Python code."
+ "description": "6 Things You are Missing out on by never using classes in your Python code."
},
{
"url": "http://python-history.blogspot.com/2010/06/inside-story-on-new-style-classes.html",
diff --git a/concepts/comparisons/about.md b/concepts/comparisons/about.md
index c2f5faaad91..1d2c677d22a 100644
--- a/concepts/comparisons/about.md
+++ b/concepts/comparisons/about.md
@@ -29,7 +29,7 @@ Numeric types are (mostly) an exception to this type matching rule.
An `integer` **can** be considered equal to a `float` (_or an [`octal`][octal] equal to a [`hexadecimal`][hex]_), as long as the types can be implicitly converted for comparison.
For the other numeric types ([complex][complex numbers], [decimal][decimal numbers], [fractions][rational numbers]), comparison operators are defined where they "make sense" (_where implicit conversion does not change the outcome_), but throw a `TypeError` if the underlying objects cannot be accurately converted for comparison.
-For more information on the rules that python uses for numeric conversion, see [arithmetic conversions][arithmetic conversions] in the Python documentation.
+For more information on the rules that Python uses for numeric conversion, see [arithmetic conversions][arithmetic conversions] in the Python documentation.
```python
>>> import fractions
@@ -47,7 +47,8 @@ True
>>> 6/3 == 0b10
True
-# An int can be converted to a complex number with a 0 imaginary part.
+# An int can be converted to a complex
+# number with a 0 imaginary part.
>>> 17 == complex(17)
True
@@ -60,8 +61,8 @@ True
```
Any ordered comparison of a number to a `NaN` (_not a number_) type is `False`.
-A confusing side-effect of Python's `NaN` definition is that `NaN` never compares equal to `NaN`.
-If you are curious as to why `Nan` was defined this way in Python, this [Stack Overflow Post on NaN][so nan post] around the setting of the international standard is an interesting read.
+A confusing side effect of Python's `NaN` definition is that `NaN` never compares equal to `NaN`.
+If you are curious as to why `NaN` was defined this way in Python, this [Stack Overflow Post on NaN][so nan post] around the setting of the international standard is an interesting read.
```python
>>> x = float('NaN')
@@ -188,7 +189,7 @@ See the Python reference docs on [value comparisons][value comparisons none] and
>>> my_fav_numbers is your_fav_numbers
True
-# The returned id will differ by system and python version.
+# The returned id will differ by system and Python version.
>>> id(my_fav_numbers)
4517478208
diff --git a/concepts/comparisons/links.json b/concepts/comparisons/links.json
index ed61054b722..f16869e2b92 100644
--- a/concepts/comparisons/links.json
+++ b/concepts/comparisons/links.json
@@ -1,27 +1,19 @@
[
- {
- "url": "https://docs.python.org/3/reference/expressions.html#comparisons",
- "description": "Comparisons in Python (Python language reference)"
- },
{
"url": "https://www.tutorialspoint.com/python/python_basic_operators.htm",
"description": "Python basic operators on Tutorials Point"
},
{
- "url": "https://data-flair.training/blogs/python-comparison-operators/",
- "description": "Python comparison operators on Data Flair"
- },
- {
- "url": "https://www.python.org/dev/peps/pep-0207/",
- "description": "PEP 207 to allow Operator Overloading for Comparison"
+ "url": "https://docs.python.org/3/reference/expressions.html#comparisons",
+ "description": "Comparisons in Python (Python language reference)"
},
{
"url": "https://docs.python.org/3/reference/expressions.html#is-not",
"description": "Identity comparisons in Python (Python language reference)"
},
{
- "url": "https://docs.python.org/3/library/operator.html",
- "description": "Operators (Python Docs)"
+ "url": "https://docs.python.org/3/reference/expressions.html#value-comparisons",
+ "description": "Value comparisons in Python (Python language reference)"
},
{
"url": "https://docs.python.org/3/library/stdtypes.html#typesnumeric",
@@ -44,11 +36,11 @@
"description": "Python Object Model (Python docs)"
},
{
- "url": "https://docs.python.org/3/reference/datamodel.html#customization",
- "description": "Basic Customization (Python language reference)"
+ "url": "https://www.python.org/dev/peps/pep-0207/",
+ "description": "PEP 207 to allow Operator Overloading for Comparison"
},
{
- "url": "https://docs.python.org/3/reference/expressions.html#value-comparisons",
- "description": "Value comparisons in Python (Python language reference)"
+ "url": "https://docs.python.org/3/reference/datamodel.html#customization",
+ "description": "Basic Customization (Python language reference)"
}
]
diff --git a/concepts/complex-numbers/.meta/config.json b/concepts/complex-numbers/.meta/config.json
index 9b9e8da5a9b..ca6ccc8811d 100644
--- a/concepts/complex-numbers/.meta/config.json
+++ b/concepts/complex-numbers/.meta/config.json
@@ -1,5 +1,5 @@
{
- "blurb": "TODO: add blurb for this concept",
- "authors": ["bethanyg", "cmccandless"],
+ "blurb": "Complex numbers are a fundamental data type in Python, along with int and float. Further support is added with the cmath module, which is part of the Python standard library.",
+ "authors": ["BethanyG", "colinleach"],
"contributors": []
}
diff --git a/concepts/complex-numbers/about.md b/concepts/complex-numbers/about.md
index c628150d565..dfe067be4ee 100644
--- a/concepts/complex-numbers/about.md
+++ b/concepts/complex-numbers/about.md
@@ -1,2 +1,260 @@
-#TODO: Add about for this concept.
+# About
+
+`Complex numbers` are not complicated.
+They just need a less alarming name.
+
+They are so useful, especially in engineering and science, that Python includes [`complex`][complex] as a standard numeric type alongside integers ([`int`s][ints]) and floating-point numbers ([`float`s][floats]).
+
+
+## Basics
+
+A `complex` value in Python is essentially a pair of floating-point numbers.
+These are called the "real" and "imaginary" parts, for unfortunate historical reasons.
+Again, it is best to focus on the underlying simplicity and not the strange names.
+
+There are two common ways to create complex numbers.
+
+1) The [`complex(real, imag)`][complex] constructor takes two `float` parameters:
+
+```python
+>>> z1 = complex(1.5, 2.0)
+>>> z1
+(1.5+2j)
+```
+
+The constructor can also parse string input.
+This has the odd limitation that it fails if the string contains spaces.
+
+```python
+>>> complex('4+2j')
+(4+2j)
+
+>>> complex('4 + 2j')
+Traceback (most recent call last):
+ File "", line 1, in
+ValueError: complex() arg is a malformed string
+```
+
+
+2) The complex number can be specified as ` + j` literal, or just `j` if the real part is zero:
+
+
+```python
+>>> z2 = 2.0 + 1.5j
+>>> z2
+(2+1.5j)
+```
+The end result is identical to using the `complex()` constructor.
+
+
+There are two rules for that imaginary part of the complex number:
+
+
+- It is designated with `j` (not `i` as you may see in math textbooks).
+
+- The `j` must immediately follow a number, to prevent Python seeing it as a variable name. If necessary, use `1j`.
+
+```python
+>>> j
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'j' is not defined
+
+>>> 1j
+1j
+
+>>> type(1j)
+
+```
+
+Most engineers are happy with `j`.
+Most scientists and mathematicians prefer the mathematical notation `i` for _imaginary_, but that notation conflicts with the use of `i` to mean _current_ in Electrical Engineering.
+So in designing Python, the Electrical Engineers won.
+
+
+To access the parts of a complex number individually:
+
+```python
+>>> z2.real
+2.0
+>>> z2.imag
+1.5
+```
+
+Either part can be zero and mathematicians may then talk of the number being "wholly real" or "wholly imaginary".
+However, it is still a complex number in Python:
+
+
+```python
+>>> complex(0, 1)
+1j
+>>> type(complex(0, 1))
+
+
+>>> complex(1, 0)
+(1+0j)
+```
+
+You may have heard that "`i` (or `j`) is the square root of -1".
+
+For now, all this means is that the imaginary part _by definition_ satisfies the equality
+```python
+1j * 1j == -1 # => True
+```
+
+This is a simple idea, but it leads to interesting consequences.
+
+## Arithmetic
+
+Most of the [`operators`][operators] used with floats and ints also work with complex numbers:
+
+
+```python
+>>> z1 = (1.5+2j)
+>>> z2 = (2+1.5j)
+
+
+>>> z1 + z2 # addition
+(3.5+3.5j)
+
+>>> z1 - z2 # subtraction
+(-0.5+0.5j)
+
+>>> z1 * z2 # multiplication
+6.25j
+
+>>> z1 / z2 # division
+(0.96+0.28j)
+
+>>> z1 ** 2 # exponentiation
+(-1.75+6j)
+
+>>> 2 ** z1 # another exponentiation
+(0.5188946835878313+2.7804223253571183j)
+
+>>> 1j ** 2 # j * j == -1
+(-1+0j)
+```
+
+Explaining the rules for complex number multiplication and division is out of scope for this concept (_and you are unlikely to have to perform those operations "by hand" very often_).
+
+Any [mathematical][math-complex] or [electrical engineering][engineering-complex] introduction to complex numbers will cover this, should you want to dig into the topic.
+
+Alternatively, Exercism has a `Complex Numbers` practice exercise where you can implement a complex number class with these operations from first principles.
+
+
+Integer division is ___not___ possible on complex numbers, so the `//` and `%` operators and `divmod()` functions will fail for the complex number type.
+
+
+There are two functions implemented for numeric types that are very useful when working with complex numbers:
+
+- `.conjugate()` simply flips the sign of the imaginary part of a complex number (_from + to - or vice-versa_).
+ - Because of the way complex multiplication works, this is more useful than you might think.
+- `abs()` is guaranteed to return a real number with no imaginary part.
+
+
+```python
+>>> z1
+(1.5+2j)
+
+>>> z1.conjugate() # flip the z1.imag sign
+(1.5-2j)
+
+>>> abs(z1) # sqrt(z1.real ** 2 + z1.imag ** 2)
+2.5
+```
+
+## The `cmath` module
+
+The Python standard library has a [`math`][math-module] module full of useful functionality for working with real numbers.
+
+It also has an equivalent [`cmath`][cmath] module for working with complex numbers.
+
+
+We encourage you to read through the module and experiment, but the main categories are:
+
+- Conversion between Cartesian and polar coordinates,
+- Exponential and log functions,
+- Trigonometric functions,
+- Hyperbolic functions,
+- Classification functions, and
+- Useful constants.
+
+Here is an example using some constants:
+
+```python
+>>> import cmath
+
+>>> euler = cmath.exp(1j * cmath.pi) # Euler's equation
+
+>>> euler.real
+-1.0
+>>> round(euler.imag, 15) # round to 15 decimal places
+0.0
+```
+
+So a simple expression with three of the most important constants in nature `e`, `i` (or `j`) and `pi` gives the result `-1`.
+Some people believe this is the most beautiful result in all of mathematics.
+It dates back to around 1740.
+
+-----
+
+## Optional section: a Complex Numbers FAQ
+
+This part can be skipped, unless you are interested.
+
+### Isn't this some strange new piece of pure mathematics?
+
+It was strange and new in the 16th century.
+
+500 years later, it is central to most of engineering and the physical sciences.
+
+### Why would anyone use these?
+
+It turns out that complex numbers are the simplest way to describe anything that rotates or anything with a wave-like property.
+So they are used widely in electrical engineering, audio processing, physics, computer gaming, and navigation - to name only a few applications.
+
+You can see things rotate.
+Complex numbers may not make the world go round, but they are great for explaining _what happens_ as a result of the world going round: look at any satellite image of a major storm.
+
+
+Less obviously, sound is wave-like, light is wave-like, radio signals are wave-like, and even the economy of your home country is at least partly wave-like.
+
+
+A lot of this wave processing can be done with trig functions (`sin()` and `cos()`) but that gets messy quite quickly.
+
+Complex exponentials are ___much___ easier to work with.
+
+### But I don't need complex numbers!
+
+
+Only true if you are living in a cave and foraging for your food.
+
+If you are reading this on any sort of screen, you are utterly dependent on some useful 20th-Century advances made through the use of complex numbers.
+
+
+1. __Semiconductor chips__.
+ - These make no sense in classical physics and can only be explained (and designed) by quantum mechanics (QM).
+ - In QM, everything is complex-valued by definition. (_its waveforms all the way down_)
+
+2. __The Fast Fourier Transform algorithm__.
+ - FFT is an application of complex numbers, and it is in _everything_ connected to sound transmission, audio processing, photos, and video.
+
+ -MP3 and other audio formats use FFT for compression, ensuring more audio can fit within a smaller storage space.
+ - JPEG compression and MP4 video, among many other image and video formats also use FTT for compression.
+
+ - FFT is also deployed in the digital filters that allow cellphone towers to separate your personal cell signal from everyone else's.
+
+
+So, you are probably using technology that relies on complex number calculations thousands of times per second.
+
+
+[complex]: https://docs.python.org/3/library/functions.html#complex
+[cmath]: https://docs.python.org/3/library/cmath.html
+[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
+[math-module]: https://docs.python.org/3/library/math.html
+[math-complex]: https://www.nagwa.com/en/videos/143121736364/
+[engineering-complex]: https://www.khanacademy.org/science/electrical-engineering/ee-circuit-analysis-topic/ee-ac-analysis/v/ee-complex-numbers
+[ints]: https://docs.python.org/3/library/functions.html#int
+[floats]: https://docs.python.org/3/library/functions.html#float
diff --git a/concepts/complex-numbers/introduction.md b/concepts/complex-numbers/introduction.md
index fcde74642ca..a82f47cb6cb 100644
--- a/concepts/complex-numbers/introduction.md
+++ b/concepts/complex-numbers/introduction.md
@@ -1,2 +1,93 @@
-#TODO: Add introduction for this concept.
+# Introduction
+`Complex numbers` are not complicated.
+They just need a less alarming name.
+
+They are so useful, especially in engineering and science (_everything from JPEG compression to quantum mechanics_), that Python includes [`complex`][complex] as a standard numeric type alongside integers ([`int`s][ints]) and floating-point numbers ([`float`s][floats]).
+
+A `complex` value in Python is essentially a pair of floating-point numbers:
+
+```python
+>>> my_complex = 5.443+6.77j
+(5.443+6.77j)
+```
+
+These are called the "real" and "imaginary" parts.
+You may have heard that "`i` (or `j`) is the square root of -1".
+For now, all this means is that the imaginary part _by definition_ satisfies the equality `1j * 1j == -1`.
+This is a simple idea, but it leads to interesting mathematical consequences.
+
+In Python, the "imaginary" part is designated with `j` (_not `i` as you would see in math textbooks_), and
+the `j` must immediately follow a number, to prevent Python seeing it as a variable name:
+
+
+```python
+>>> j
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'j' is not defined
+
+>>> 1j
+1j
+
+>>> type(1j)
+
+```
+
+
+There are two common ways to create complex numbers.
+
+1) The [`complex(real, imag)`][complex] constructor takes two `float` parameters:
+
+ ```python
+ >>> z1 = complex(1.5, 2.0)
+ >>> z1
+ (1.5+2j)
+ ```
+
+ The constructor can also parse string input.
+ This has the odd limitation that it fails if the string contains spaces.
+
+ ```python
+ >>> complex('4+2j')
+ (4+2j)
+
+ >>> complex('4 + 2j')
+ Traceback (most recent call last):
+ File "", line 1, in
+ ValueError: complex() arg is a malformed string
+ ```
+
+
+2) The complex number can be specified as ` + j` literal, or just `j` if the real part is zero:
+
+
+ ```python
+ >>> z2 = 2.0 + 1.5j
+ >>> z2
+ (2+1.5j)
+ ```
+ The end result is identical to using the `complex()` constructor.
+
+
+## Arithmetic
+
+Most of the [`operators`][operators] used with floats and ints also work with complex numbers.
+
+Integer division is _**not**_ possible on complex numbers, so the `//` and `%` operators and `divmod()` functions will fail for the complex number type.
+
+Explaining the rules for complex number multiplication and division is out of scope for this concept (_and you are unlikely to have to perform those operations "by hand" very often_).
+
+Any [mathematical][math-complex] or [electrical engineering][engineering-complex] introduction to complex numbers will cover these scenarios, should you want to dig into the topic.
+
+The Python standard library has a [`math`][math-module] module full of useful functionality for working with real numbers and the [`cmath`][cmath] module is its equivalent for working with complex numbers.
+
+
+[cmath]: https://docs.python.org/3/library/cmath.html
+[complex]: https://docs.python.org/3/library/functions.html#complex
+[engineering-complex]: https://www.khanacademy.org/science/electrical-engineering/ee-circuit-analysis-topic/ee-ac-analysis/v/ee-complex-numbers
+[floats]: https://docs.python.org/3/library/functions.html#float
+[ints]: https://docs.python.org/3/library/functions.html#int
+[math-complex]: https://www.nagwa.com/en/videos/143121736364/
+[math-module]: https://docs.python.org/3/library/math.html
+[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
diff --git a/concepts/complex-numbers/links.json b/concepts/complex-numbers/links.json
index eb5fb7c38a5..759ef1689ff 100644
--- a/concepts/complex-numbers/links.json
+++ b/concepts/complex-numbers/links.json
@@ -1,18 +1,18 @@
[
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex/",
+ "description": "Operations on numeric types."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://docs.python.org/3/library/functions.html#complex/",
+ "description": "The complex class."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://docs.python.org/3/library/cmath.html/",
+ "description": "Module documentation for cmath."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://docs.python.org/3/library/math.html/",
+ "description": "Module documentation for math."
}
]
diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md
index 3b3d5b1ba1d..2060905b335 100644
--- a/concepts/conditionals/about.md
+++ b/concepts/conditionals/about.md
@@ -8,7 +8,6 @@ Python 3.10 introduces a variant case-switch statement called `pattern matching`
Conditional statements use expressions that must resolve to `True` or `False` -- either by returning a `bool` directly, or by evaluating ["truthy" or "falsy"][truth value testing].
-
```python
x = 10
y = 5
@@ -55,19 +54,21 @@ else:
>>> z is greater than x and y
```
-[Boolen operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing:
+[Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing:
```python
>>> def classic_fizzbuzz(number):
if number % 3 == 0 and number % 5 == 0:
- return 'FizzBuzz!'
+ say = 'FizzBuzz!'
elif number % 5 == 0:
- return 'Buzz!'
+ say = 'Buzz!'
elif number % 3 == 0:
- return 'Fizz!'
+ say = 'Fizz!'
else:
- return str(number)
+ say = str(number)
+
+ return say
>>> classic_fizzbuzz(15)
'FizzBuzz!'
@@ -76,29 +77,54 @@ else:
'13'
```
+As an alternative, the example above can be re-written to only use `if` statements with `returns`.
+However, re-writing in this way might obscure that the conditions are intended to be [_mutually exclusive_][mutually-exclusive] and could lead to future bugs or maintenance issues.
+
+
+```python
+>>> def classic_fizzbuzz(number):
+ if number % 3 == 0 and number % 5 == 0:
+ return 'FizzBuzz!'
+ if number % 5 == 0:
+ return 'Buzz!'
+ if number % 3 == 0:
+ return 'Fizz!'
+
+ return str(number)
+
+>>> classic_fizzbuzz(15)
+'FizzBuzz!'
+
+>>> classic_fizzbuzz(13)
+'13'
+```
+
+
Conditionals can also be nested.
```python
>>> def driving_status(driver_age, test_score):
if test_score >= 80:
if 18 > driver_age >= 16:
- return "Student driver, needs supervision."
+ status = "Student driver, needs supervision."
elif driver_age == 18:
- return "Permitted driver, on probation."
+ status = "Permitted driver, on probation."
elif driver_age > 18:
- return "Fully licensed driver."
+ status = "Fully licensed driver."
else:
- return "Unlicensed!"
+ status = "Unlicensed!"
+
+ return status
>>> driving_status(63, 78)
-'Unlicsensed!'
+'Unlicensed!'
>>> driving_status(16, 81)
'Student driver, needs supervision.'
>>> driving_status(23, 80)
-'Fully licsensed driver.'
+'Fully licensed driver.'
```
## Conditional expressions or "ternary operators"
@@ -115,8 +141,8 @@ def just_the_buzz(number):
>>> just_the_buzz(15)
'Buzz!'
->>> just_the_buzz(10)
-'10'
+>>> just_the_buzz(7)
+'7'
```
## Truthy and Falsy
@@ -151,8 +177,9 @@ This is Truthy.
Nope. It's Falsey.
```
-[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement
-[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools
-[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
[comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons
+[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools
+[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement
+[mutually-exclusive]: https://stackoverflow.com/a/22783232
+[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
diff --git a/concepts/conditionals/introduction.md b/concepts/conditionals/introduction.md
index 29e3e635975..ee1d4336207 100644
--- a/concepts/conditionals/introduction.md
+++ b/concepts/conditionals/introduction.md
@@ -45,10 +45,8 @@ z = 20
# The elif statement allows for the checking of more conditions.
if x > y > z:
-
print("x is greater than y and z")
elif y > x > z:
-
print("y is greater than x and z")
else:
print("z is greater than x and y")
@@ -56,19 +54,20 @@ else:
>>> z is greater than x and y
```
-[Boolen operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing:
+[Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing:
```python
-
>>> def classic_fizzbuzz(number):
if number % 3 == 0 and number % 5 == 0:
- return 'FizzBuzz!'
+ say = 'FizzBuzz!'
elif number % 5 == 0:
- return 'Buzz!'
+ say = 'Buzz!'
elif number % 3 == 0:
- return 'Fizz!'
+ say = 'Fizz!'
else:
- return str(number)
+ say = str(number)
+
+ return say
>>> classic_fizzbuzz(15)
'FizzBuzz!'
@@ -77,8 +76,8 @@ else:
'13'
```
-[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement
-[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools
-[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
[comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons
+[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools
+[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement
+[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
diff --git a/concepts/dataclasses-and-namedtuples/.meta/config.json b/concepts/dataclasses/.meta/config.json
similarity index 100%
rename from concepts/dataclasses-and-namedtuples/.meta/config.json
rename to concepts/dataclasses/.meta/config.json
diff --git a/concepts/dataclasses-and-namedtuples/about.md b/concepts/dataclasses/about.md
similarity index 100%
rename from concepts/dataclasses-and-namedtuples/about.md
rename to concepts/dataclasses/about.md
diff --git a/concepts/dataclasses-and-namedtuples/introduction.md b/concepts/dataclasses/introduction.md
similarity index 100%
rename from concepts/dataclasses-and-namedtuples/introduction.md
rename to concepts/dataclasses/introduction.md
diff --git a/concepts/dataclasses-and-namedtuples/links.json b/concepts/dataclasses/links.json
similarity index 100%
rename from concepts/dataclasses-and-namedtuples/links.json
rename to concepts/dataclasses/links.json
diff --git a/concepts/dict-methods/about.md b/concepts/dict-methods/about.md
index d910d3e9168..6dcf9b4ae7a 100644
--- a/concepts/dict-methods/about.md
+++ b/concepts/dict-methods/about.md
@@ -214,7 +214,7 @@ When both dictionaries share keys, `dict_two` values take precedence.
'Purple baseline': '#161748'}
```
-`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs:
+`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs:
```python
>>> palette_III = {'Grassy Green': (155, 196, 0),
diff --git a/concepts/dicts/about.md b/concepts/dicts/about.md
index c34160b2ef6..72ea9079c6d 100644
--- a/concepts/dicts/about.md
+++ b/concepts/dicts/about.md
@@ -172,7 +172,7 @@ You can change an entry `value` by assigning to its _key_:
New `key`:`value` pairs can be _added_ in the same fashion:
```python
-# Adding an new "color" key with a new "tawney" value.
+# Adding a new "color" key with a new "tawney" value.
>>> bear["color"] = 'tawney'
{'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True, 'color': 'tawney'}
diff --git a/concepts/fractions/.meta/config.json b/concepts/fractions/.meta/config.json
new file mode 100644
index 00000000000..621a3766d84
--- /dev/null
+++ b/concepts/fractions/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "The fractions module enables working with rational numbers, which preserve exact values and avoid the rounding errors common with floats.",
+ "authors": ["BethanyG", "colinleach"],
+ "contributors": []
+}
diff --git a/concepts/fractions/about.md b/concepts/fractions/about.md
new file mode 100644
index 00000000000..d41124c39c4
--- /dev/null
+++ b/concepts/fractions/about.md
@@ -0,0 +1,122 @@
+# About
+
+The [`Fractions`][fractions] module allows us to create and work with [`rational numbers`][rational]: fractions with an integer numerator divided by an integer denominator.
+
+For example, we can store `2/3` as an exact fraction instead of the approximate `float` value `0.6666...`
+
+## Creating Fractions
+
+
+Unlike `int`, `float`, and `complex` numbers, fractions do not have a literal form.
+However, the fractions constructor is quite flexible.
+
+Most obviously, it can take take two integers.
+Common factors are automatically removed, converting the fraction to its "lowest form": the smallest integers that accurately represent the fraction.
+
+
+```python
+>>> from fractions import Fraction
+
+>>> f1 = Fraction(2, 3) # 2/3
+>>> f1
+Fraction(2, 3)
+
+>>> f2 = Fraction(6, 9)
+>>> f2
+Fraction(2, 3) # automatically simplified
+
+>>> f1 == f2
+True
+```
+
+The fractions constructor can also parse a string representation:
+
+
+```python
+>>> f3 = Fraction('2/3')
+>>> f3
+Fraction(2, 3)
+```
+
+It can also work with `float` parameters, but this may run into problems with the approximate nature of representing the decimal value internally as binary.
+For more on this representation issue, see the [0.30000000000000004][0.30000000000000004] website, and [Floating Point Arithmetic: Issues and Limitations ][fp-issues] in the Python documentation.
+
+For a more reliable result when using floats with fractions, there is the `.limit_denominator()` method.
+
+
+[`.limit_denominator()`][limit_denominator] can take an integer parameter if you have specific requirements, but even the default (`max_denominator=1000000`) can work well and give an acceptable, simple approximation.
+
+```python
+>>> Fraction(1.2)
+Fraction(5404319552844595, 4503599627370496)
+
+>>> Fraction(1.2).limit_denominator()
+Fraction(6, 5)
+```
+
+## Arithmetic with Fractions
+
+
+The usual [`arithmetic operators`][operators] `+ - * / **` work with fractions, as with other numeric types.
+
+Integers and other `Fraction`s can be included and give a `Fraction` result.
+Including a `float` in the expression results in `float` output, with a consequent (possible) loss in precision.
+
+
+```python
+>>> Fraction(2, 3) + Fraction(1, 4) # addition
+Fraction(11, 12)
+
+>>> Fraction(2, 3) * Fraction(6, 5) # multiply fractions
+Fraction(4, 5)
+
+>>> Fraction(2, 3) * 6 / 5 # fraction with integers
+Fraction(4, 5)
+
+>>> Fraction(2, 3) * 1.2 # fraction with float -> float
+0.7999999999999999
+
+>>> Fraction(2, 3) ** 2 # exponentiation with integer
+Fraction(4, 9)
+```
+
+## Conversions to and from Fractions
+
+
+Fractions are great for preserving precision during intermediate calculations, but may not be what you want for the final output.
+
+It is possible to get the numerator and denominator individually or as a tuple ([`tuples`][tuple] will be discussed in a later Concept):
+
+```python
+>>> Fraction(2, 3).numerator
+2
+>>> Fraction(2, 3).denominator
+3
+>>> Fraction(2, 3).as_integer_ratio()
+(2, 3)
+```
+
+Various standard Python numeric functions also give the result you might expect from working with `int` and `float` types:
+
+```python
+>>> round(Fraction(11, 3))
+4
+
+>>> from math import floor, ceil
+>>> floor(Fraction(11, 3))
+3
+>>> ceil(Fraction(11, 3))
+4
+
+>>> float(Fraction(11, 3))
+3.6666666666666665
+```
+
+[fractions]: https://docs.python.org/3/library/fractions.html
+[0.30000000000000004]: https://0.30000000000000004.com/
+[fp-issues]: https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues
+[tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences
+
+[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
+[rational]: https://en.wikipedia.org/wiki/Rational_number
+[limit_denominator]: https://docs.python.org/3/library/fractions.html
diff --git a/concepts/fractions/introduction.md b/concepts/fractions/introduction.md
new file mode 100644
index 00000000000..437ccbbeb07
--- /dev/null
+++ b/concepts/fractions/introduction.md
@@ -0,0 +1,85 @@
+# Introduction
+
+The [`Fractions`][fractions] module allows us to create and work with [`rational numbers`][rational]: fractions with an integer numerator divided by an integer denominator.
+For example, we can store `2/3` as an exact fraction instead of the approximate `float` value `0.6666...`.
+
+Unlike `int`, `float`, and `complex` numbers, fractions do not have a literal form.
+However, the fractions constructor is quite flexible.
+
+Most obviously, it can take take two integers as arguments.
+Common factors are automatically removed, converting the fraction to its "lowest form": the smallest integers that accurately represent the fraction:
+
+```python
+>>> from fractions import Fraction
+
+>>> f1 = Fraction(2, 3) # 2/3
+>>> f1
+Fraction(2, 3)
+
+>>> f2 = Fraction(6, 9)
+>>> f2
+Fraction(2, 3) # automatically simplified
+
+>>> f1 == f2
+True
+```
+
+The fractions constructor can also parse a string representation:
+
+```python
+>>> f3 = Fraction('2/3')
+>>> f3
+Fraction(2, 3)
+```
+
+Fractions can also work with `float` parameters, but this may run into problems with the approximate nature of representing the decimal value internally as binary.
+For more on this representation issue, see the [0.30000000000000004][0.30000000000000004] website, and [Floating Point Arithmetic: Issues and Limitations ][fp-issues] in the Python documentation.
+
+For a more reliable result when using floats with fractions, there is the `.limit_denominator()` method.
+
+
+## Arithmetic with Fractions
+
+The usual [`arithmetic operators`][operators] `+ - * / **` will work with fractions, as with other numeric types.
+
+Integers and other `Fraction`s can be included in the equation and give a `Fraction` result.
+Including a `float` in the expression results in `float` output, with a consequent (possible) loss in precision:
+
+```python
+>>> Fraction(2, 3) + Fraction(1, 4) # addition
+Fraction(11, 12)
+
+>>> Fraction(2, 3) * Fraction(6, 5) # multiply fractions
+Fraction(4, 5)
+
+>>> Fraction(2, 3) * 6 / 5 # fraction with integers
+Fraction(4, 5)
+
+>>> Fraction(2, 3) * 1.2 # fraction with float -> float
+0.7999999999999999
+
+>>> Fraction(2, 3) ** 2 # exponentiation with integer
+Fraction(4, 9)
+```
+
+Various standard Python numeric functions also give the result you might expect from working with `int` and `float` types:
+
+```python
+>>> round(Fraction(11, 3))
+4
+
+>>> from math import floor, ceil
+>>> floor(Fraction(11, 3))
+3
+>>> ceil(Fraction(11, 3))
+4
+
+>>> float(Fraction(11, 3))
+3.6666666666666665
+```
+
+[0.30000000000000004]: https://0.30000000000000004.com/
+[fp-issues]: https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues
+[fractions]: https://docs.python.org/3/library/fractions.html
+[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
+[rational]: https://en.wikipedia.org/wiki/Rational_number
diff --git a/concepts/fractions/links.json b/concepts/fractions/links.json
new file mode 100644
index 00000000000..78d349bcfc3
--- /dev/null
+++ b/concepts/fractions/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "https://docs.python.org/3/library/fractions.html/",
+ "description": "Documentation for the Fractions module."
+ },
+ {
+ "url": "https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues",
+ "description": "Limitations of Floating Point Arithmetic."
+ },
+ {
+ "url": "https://leancrew.com/all-this/2023/08/decimal-to-fraction/",
+ "description": "And now it's all this: Decimal to fraction."
+ },
+ {
+ "url": "https://nrich.maths.org/2515",
+ "description": "History of Fractions."
+ }
+]
diff --git a/concepts/function-arguments/about.md b/concepts/function-arguments/about.md
index 09b01b10862..0f2ab5dddda 100644
--- a/concepts/function-arguments/about.md
+++ b/concepts/function-arguments/about.md
@@ -4,7 +4,7 @@ For the basics on function arguments, please see the [function concept][function
## Parameter Names
-Paramater names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers.
+Parameter names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers.
Parameter names should not contain spaces or punctuation.
## Positional Arguments
@@ -182,7 +182,7 @@ For instance, `*` is used for multiplication, it is used for unpacking, and it i
Since a tuple can be iterated, `args` can be passed to any other function which takes an iterable.
Although `*args` is commonly juxtaposed with `**kwargs`, it doesn't have to be.
-Following is an example of an arbitrary amount of values being passed to a function:
+Following is an example of an arbitrary number of values being passed to a function:
```python
@@ -196,7 +196,7 @@ Following is an example of an arbitrary amount of values being passed to a funct
If `*args` follows one or more positional arguments, then `*args` will be what is left over after the positional arguments.
-Following is an example of an arbitrary amount of values being passed to a function after a positional argument:
+Following is an example of an arbitrary number of values being passed to a function after a positional argument:
```python
@@ -210,7 +210,7 @@ Following is an example of an arbitrary amount of values being passed to a funct
If one or more default arguments are defined after `*args` they are separate from the `*args` values.
-To put it all together is an example of an arbitrary amount of values being passed to a function that also has a positional argument and a default argument:
+To put it all together is an example of an arbitrary number of values being passed to a function that also has a positional argument and a default argument:
```python
@@ -228,7 +228,7 @@ To put it all together is an example of an arbitrary amount of values being pass
```
-Note that when an argument is already in an iterable, such as a tuple or list, it needs to be unpacked before being passed to a function that takes an arbitrary amount of separate arguments.
+Note that when an argument is already in an iterable, such as a tuple or list, it needs to be unpacked before being passed to a function that takes an arbitrary number of separate arguments.
This is accomplished by using `*`, which is the [unpacking operator][unpacking operator].
`*` in this context _unpacks_ the container into its separate elements which are then transformed by `*args` into a tuple.
@@ -257,7 +257,7 @@ The `**` transforms the group of named arguments into a [`dictionary`][dictionar
Since a dictionary can be iterated, `kwargs` can be passed to any other function which takes an iterable.
Although `**kwargs` is commonly juxtaposed with `*args`, it doesn't have to be.
-Following is an example of an arbitrary amount of key-value pairs being passed to a function:
+Following is an example of an arbitrary number of key-value pairs being passed to a function:
```python
>>> def add(**kwargs):
@@ -271,7 +271,7 @@ Note that the `dict.values()` method is called to iterate through the `kwargs` d
When iterating a dictionary the default is to iterate the keys.
-Following is an example of an arbitrary amount of key-value pairs being passed to a function that then iterates over `kwargs.keys()`:
+Following is an example of an arbitrary number of key-value pairs being passed to a function that then iterates over `kwargs.keys()`:
```python
>>> def concat(**kwargs):
diff --git a/concepts/function-arguments/introduction.md b/concepts/function-arguments/introduction.md
index 171675ce3c4..07b885f332e 100644
--- a/concepts/function-arguments/introduction.md
+++ b/concepts/function-arguments/introduction.md
@@ -4,7 +4,7 @@ For the basics on function arguments, please see the [function concept][function
## Parameter Names
-Paramater names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers.
+Parameter names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers.
Parameter names should not contain spaces or punctuation.
## Positional Arguments
diff --git a/concepts/functions/about.md b/concepts/functions/about.md
index 9d8fddfa956..f3630af763c 100644
--- a/concepts/functions/about.md
+++ b/concepts/functions/about.md
@@ -2,11 +2,11 @@
A [`function`][function] is a block of organized, reusable code that is used to perform a specific task.
`Functions` provide better modularity for your application and a high degree of code reuse.
-Python, like other programming languages, has [_built-in functions_][build-in functions] ([`print`][print], [`map`][map], and so on) that are readily available.
+Python, like other programming languages, has [_built-in functions_][built-in functions] ([`print`][print], [`map`][map], and so on) that are readily available.
You can also define your own functions. Those are called [`user-defined functions`][user defined functions].
Functions can run something as simple as _printing a message to the console_ or they can be quite complex.
-To execute the code inside a function, you need to call the function, which is done by using the function name followed by parenthesese [`()`].
+To execute the code inside a function, you need to call the function, which is done by using the function name followed by parentheses [`()`].
Data, known as [`arguments`][arguments], can be passed to the function by placing them inside the parenthesese.
A function definition may include zero or more [`parameters`][parameters].
Parameters define what argument(s) the function accepts.
@@ -376,7 +376,7 @@ The full list of function attributes can be found at [Python DataModel][attribut
[LEGB Rule]: https://realpython.com/python-scope-legb-rule/
[arguments]: https://www.w3schools.com/python/gloss_python_function_arguments.asp
[attributes]: https://docs.python.org/3/reference/datamodel.html#index-33
-[build-in functions]: https://docs.python.org/3/library/functions.html
+[built-in functions]: https://docs.python.org/3/library/functions.html
[def]: https://www.geeksforgeeks.org/python-def-keyword/
[dict]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
[first class objects]: https://en.wikipedia.org/wiki/First-class_object
diff --git a/concepts/functools/about.md b/concepts/functools/about.md
index 32748a45c23..e5afb577d39 100644
--- a/concepts/functools/about.md
+++ b/concepts/functools/about.md
@@ -12,7 +12,7 @@ The functools module is for higher-order functions: functions that act on or ret
Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable.
-Here ```maxsize = 128``` means that it is going to memoize lastest 128 function calls at max.
+Here ```maxsize = 128``` means that it is going to memoize latest 128 function calls at max.
The lru_cache works the same way but it can cache at max maxsize calls and if type = True, then the function arguments of different types will be cached separately i.e. 5 and 5.0 will be cached differently.
@@ -23,8 +23,8 @@ The lru_cache works the same way but it can cache at max maxsize calls and if ty
```python
>>> @cache
- def factorial(n):
- return n * factorial(n-1) if n else 1
+>>> def factorial(n):
+>>> return n * factorial(n-1) if n else 1
>>> factorial(10) # no previously cached result, makes 11 recursive calls
3628800
@@ -37,9 +37,10 @@ The lru_cache works the same way but it can cache at max maxsize calls and if ty
# Some types such as str and int may be cached separately even when typed is false.
-@lru_cache(maxsize = 128)
- def factorial(n):
- return n * factorial(n-1) if n else 1
+>>> @lru_cache(maxsize = 128)
+>>> def factorial(n):
+>>> return n * factorial(n-1) if n else 1
+
>>> factorial(10)
3628800
@@ -50,7 +51,7 @@ CacheInfo(hits=0, misses=11, maxsize=128, currsize=11)
## Generic functions
-***[Generic functions](https://pymotw.com/3/functools/#generic-functions)*** are those which preform the operation based on the argument given to them. In statically typed languages it can be done by function overloading.
+***[Generic functions](https://pymotw.com/3/functools/#generic-functions)*** are those which perform the operation based on the argument given to them. In statically typed languages it can be done by function overloading.
In python functools provides the `singledispatch()` decorator to register a set of generic functions for automatic switching based on the type of the first argument to a function.
@@ -193,11 +194,11 @@ True
The ```pow_2.func``` is same as the ```pow``` function.
-Here ```pow_2.args``` return an empty tuple becuse we does not pass any positional argument to out partial object call.
+Here ```pow_2.args``` returns an empty tuple because we do not pass any positional argument to our partial object call.
-```pow_2.keywords``` return a dictionary of keywords argument which will be supplied when the partial object is called.
+```pow_2.keywords``` returns a dictionary of keywords argument which will be supplied when the partial object is called.
-Here ```two_pow.args``` return an ```(2,)``` tuple because we passed 2 as an argument while creating the pratial object, which fixed the value of ```base``` argument as ```2```.
+Here ```two_pow.args``` returns a ```(2,)``` tuple because we passed 2 as an argument while creating the partial object, which fixed the value of ```base``` argument as ```2```.
### ```partialmethod```
diff --git a/concepts/functools/introduction.md b/concepts/functools/introduction.md
index 15e83e3e61a..c91aedc81bd 100644
--- a/concepts/functools/introduction.md
+++ b/concepts/functools/introduction.md
@@ -12,7 +12,7 @@ The functools module is for higher-order functions: functions that act on or ret
Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable.
-Here ```maxsize = 128``` means that it is going to memoize lastest 128 function calls at max.
+Here ```maxsize = 128``` means that it is going to memoize latest 128 function calls at max.
### ```@functools.cache(user_function)```
diff --git a/concepts/generators/.meta/config.json b/concepts/generators/.meta/config.json
index 6c29169d3f4..3322727ef74 100644
--- a/concepts/generators/.meta/config.json
+++ b/concepts/generators/.meta/config.json
@@ -1,5 +1,9 @@
{
- "blurb": "Generator are functions that returns a lazy iterator, lazy iterator is an iterator like: list, tuple, etc. But doesn't need store its content in memory",
+ "blurb": "Generators are functions or expressions that return a special type of iterator called a 'generator-iterator'. Generator-iterators are lazy: they do not store their values in memory but generate them when needed.",
"authors": ["J08K"],
- "contributors": []
+ "contributors": [
+ "BethanyG",
+ "kytrinyx",
+ "meatball133"
+ ]
}
diff --git a/concepts/generators/about.md b/concepts/generators/about.md
index 9a26ab55480..59b5035d6b9 100644
--- a/concepts/generators/about.md
+++ b/concepts/generators/about.md
@@ -1,5 +1,13 @@
# About
+A `generator` is a function or expression that returns a special type of [iterator][iterator] called [generator iterator][generator-iterator].
+`Generator-iterators` are [lazy][lazy iterator]: they do not store their `values` in memory, but _generate_ their values when needed.
+
+A generator function looks like any other function, but contains one or more [yield expressions][yield expression].
+Each `yield` will suspend code execution, saving the current execution state (_including all local variables and try-statements_).
+When the generator resumes, it picks up state from the suspension - unlike regular functions which reset with every call.
+
+
## Constructing a generator
Generators are constructed much like other looping or recursive functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later.
@@ -94,9 +102,9 @@ When `yield` is evaluated, it pauses the execution of the enclosing function and
The function then _stays in scope_, and when `__next__()` is called, execution resumes until `yield` is encountered again.
-```exercism/note
+~~~~exercism/note
Using `yield` expressions is prohibited outside of functions.
-```
+~~~~
```python
>>> def infinite_sequence():
@@ -131,5 +139,9 @@ Generators are also very helpful when a process or calculation is _complex_, _ex
Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1.
+
+[generator-iterator]: https://docs.python.org/3.11/glossary.html#term-generator-iterator
[iterables]: https://wiki.python.org/moin/Iterator
+[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator
+[lazy iterator]: https://en.wikipedia.org/wiki/Lazy_evaluation
[yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions
diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md
index 82a686d1e05..ad1175ca0b6 100644
--- a/concepts/generators/introduction.md
+++ b/concepts/generators/introduction.md
@@ -1,7 +1,13 @@
# Introduction
-A generator in Python is a _callable function_ that returns a [lazy iterator][lazy iterator].
+A generator in Python is a _callable function_ or expression that returns a special type of [iterator][iterator] called [generator iterator][generator-iterator].
+`Generator-iterators` are [lazy][lazy iterator]: they do not store their `values` in memory, but _generate_ their values when needed.
-_Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed.
+A generator function looks like any other function, but contains one or more [yield expressions][yield expression].
+Each `yield` will suspend code execution, saving the current execution state (_including all local variables and try-statements_).
+When the generator function resumes, it picks up state from the suspension - unlike regular functions which reset with every call.
[lazy iterator]: https://en.wikipedia.org/wiki/Lazy_evaluation
+[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator
+[yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions
+[generator-iterator]: https://docs.python.org/3.11/glossary.html#term-generator-iterator
diff --git a/concepts/generators/links.json b/concepts/generators/links.json
index 972bbe7ae97..134a723c693 100644
--- a/concepts/generators/links.json
+++ b/concepts/generators/links.json
@@ -1,12 +1,16 @@
[
{
- "url": "https://docs.python.org/3.10/reference/expressions.html#yield-expressions",
+ "url": "https://docs.python.org/3.11/reference/expressions.html#yield-expressions",
"description": "Official Python 3.10 docs for the yield expression."
},
{
"url": "https://en.wikipedia.org/wiki/Lazy_evaluation",
"description": "Wikipedia page about lazy evaluation"
},
+ {
+ "url": "https://www.pythonmorsels.com/iterators/",
+ "description": "Python Morsels: Iterators & Generators"
+ },
{
"url": "https://realpython.com/introduction-to-python-generators/",
"description": "Real python, introduction to generators and yield"
diff --git a/concepts/list-methods/about.md b/concepts/list-methods/about.md
index 3a5132fd0ad..1c9686360d4 100644
--- a/concepts/list-methods/about.md
+++ b/concepts/list-methods/about.md
@@ -136,10 +136,22 @@ The order of list elements can be reversed _**in place**_ with `.reverse(
[3, 2, 1]
```
-List elements can be sorted _**in place**_ using `.sort()`.
- Internally, Python uses [`Timsort`][timsort] to arrange the elements.
- The default order is _ascending_.
- The Python docs have [additional tips and techniques for sorting][sorting how to] `lists` effectively.
+A list can be re-ordered _**in place**_ with the help of [`.sort()`][sort].
+Default sort order is _ascending_ from the left.
+The Python docs offer [additional tips and techniques for sorting][sorting how to].
+
+
+~~~~exercism/note
+ From 2002 to 2022, Python used an algorithm called [`Timsort`][timsort] internally to arrange lists, but switched to [`Powersort`][powersort] from `Python 3.11` onward.
+You can read more details and discussion on the change from the core Python team in the GitHub [issue 78742][78742].
+
+For technical details on the algorithm, see the J. Ian Munro and Sebastian Wild paper [Nearly-Optimal Mergesorts: Fast, Practical Sorting Methods That Optimally Adapt to Existing Runs][nearly-optimal-mergesorts]
+
+[78742]: https://github.com/python/cpython/issues/78742
+[nearly-optimal-mergesorts]: https://arxiv.org/abs/1805.04154
+[powersort]: https://www.wild-inter.net/publications/munro-wild-2018
+[timsort]: https://en.wikipedia.org/wiki/Timsort
+~~~~
```python
@@ -254,7 +266,7 @@ For a detailed explanation of names, values, list, and nested list behavior, tak
[set]: https://docs.python.org/3/library/stdtypes.html#set
[shallow vs deep]: https://realpython.com/copying-python-objects/
[slice notation]: https://docs.python.org/3/reference/expressions.html#slicings
+[sort]: https://docs.python.org/3/library/stdtypes.html#list.sort
[sorted]: https://docs.python.org/3/library/functions.html#sorted
[sorting how to]: https://docs.python.org/3/howto/sorting.html
-[timsort]: https://en.wikipedia.org/wiki/Timsort
[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
diff --git a/concepts/lists/about.md b/concepts/lists/about.md
index 014a6d56725..f7d4054eef0 100644
--- a/concepts/lists/about.md
+++ b/concepts/lists/about.md
@@ -7,7 +7,7 @@ A [`list`][list] is a mutable collection of items in _sequence_.
Lists support both [common][common sequence operations] and [mutable][mutable sequence operations] sequence operations such as `min()`/`max()`, `.index()`, `.append()` and `.reverse()`.
- List elements can be iterated over using the `for item in ` construct. `for index, item in enumerate(` construct. `for index, item in enumerate()` can be used when both the element index and the element value are needed.
Lists are implemented as [dynamic arrays][dynamic array] -- similar to Java's [`Arraylist`][arraylist] type, and are most often used to store groups of similar data (_strings, numbers, sets etc._) of unknown length (_the number of entries may arbitrarily expand or shrink_).
@@ -18,7 +18,7 @@ Accessing elements, checking for membership via `in`, or appending items to the
For a similar data structure that supports memory efficient `appends`/`pops` from both sides, see [`collections.deque`][deque], which has approximately the same O(1) performance in either direction.
-Because lists are mutable and can contain references to arbitrary objects, they also take up more space in memory than a fixed-size [`array.array`][array.array] type of the same apparent length.
+Because lists are mutable and can contain references to arbitrary Python objects, they also take up more space in memory than an [`array.array`][array.array] or a [`tuple`][tuple] (_which is immutable_) of the same apparent length.
Despite this, lists are an extremely flexible and useful data structure and many built-in methods and operations in Python produce lists as their output.
@@ -135,7 +135,8 @@ TypeError: 'int' object is not iterable
## Accessing elements
-Items inside lists (_as well as elements in other sequence types such as [`str`][string] & [`tuple`][tuple]_), can be accessed using _bracket notation_. Indexes can be from **`left`** --> **`right`** (_starting at zero_) or **`right`** --> **`left`** (_starting at -1_).
+Items inside lists (_as well as elements in other sequence types such as [`str`][string] & [`tuple`][tuple]_), can be accessed using _bracket notation_.
+Indexes can be from **`left`** --> **`right`** (_starting at zero_) or **`right`** --> **`left`** (_starting at -1_).
@@ -173,9 +174,11 @@ Items inside lists (_as well as elements in other sequence types such as [`str`]
'Toast'
```
-A section of a list can be accessed via _slice notation_ (`[start:stop]`). A _slice_ is defined as an element sequence at position `index`, such that `start <= index < stop`. [_Slicing_][slice notation] returns a copy of the "sliced" items and does not modify the original `list`.
+A section of a list can be accessed via _slice notation_ (`[start:stop]`).
+A _slice_ is defined as an element sequence at position `index`, such that `start <= index < stop`.
+[_Slicing_][slice notation] returns a copy of the "sliced" items and does not modify the original `list`.
-A `step` parameter can also be used in the slice (`[start:stop:step]`) to "skip over" or filter the returned elements (_for example, a `step` of 2 will select every other element in the section_):
+A `step` parameter can also be used in the slice (`[::]`) to "skip over" or filter the returned elements (_for example, a `step` of 2 will select every other element in the section_):
```python
>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"]
@@ -269,7 +272,7 @@ Lists can also be combined via various techniques:
>>> first_one
['George', 5, 'cat', 'Tabby']
-# This loops through the first list and appends it's items to the end of the second list.
+# This loops through the first list and appends its items to the end of the second list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]
@@ -284,7 +287,7 @@ Lists can also be combined via various techniques:
## Some cautions
Recall that variables in Python are _labels_ that point to _underlying objects_.
-`lists` add one more layer as _container objects_ -- they hold object references for their collected items.
+`lists` add one more layer as _container objects_ -- they hold object _references_ for their collected items.
This can lead to multiple potential issues when working with lists, if not handled properly.
@@ -293,7 +296,7 @@ Assigning a `list` object to a new variable _name_ **does not copy the `list` ob
Any change made to the elements in the `list` under the _new_ name _impact the original_.
-Making a `shallow_copy` via `list.copy()` or slice will avoid this first-leve referencing complication.
+Making a `shallow_copy` via `list.copy()` or slice will avoid this first-level referencing complication.
A `shallow_copy` will create a new `list` object, but **will not** create new objects for the contained list _elements_. This type of copy will usually be enough for you to add or remove items from the two `list` objects independently, and effectively have two "separate" lists.
@@ -305,21 +308,22 @@ A `shallow_copy` will create a new `list` object, but **will not** create new ob
# Altering the list via the new name is the same as altering the list via the old name.
>>> same_list.append("Clarke")
->>> same_list
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
+
>>> actual_names
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
# Likewise, altering the data in the list via the original name will also alter the data under the new name.
>>> actual_names[0] = "Wanda"
->>> same_list
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']
# If you copy the list, there will be two separate list objects which can be changed independently.
>>> copied_list = actual_names.copy()
>>> copied_list[0] = "Tony"
+
>>> actual_names
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']
+
>>> copied_list
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
```
@@ -455,4 +459,4 @@ The collections module also provides a `UserList` type that can be customized to
[set]: https://docs.python.org/3/library/stdtypes.html#set
[slice notation]: https://docs.python.org/3/reference/expressions.html#slicings
[string]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str
-[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
\ No newline at end of file
+[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
diff --git a/concepts/loops/about.md b/concepts/loops/about.md
index 88bb71e6194..0f39e733d0c 100644
--- a/concepts/loops/about.md
+++ b/concepts/loops/about.md
@@ -235,17 +235,17 @@ The loop [`else` clause][loop else] is unique to Python and can be used for "wra
'Found an S, stopping iteration.'
```
-[loop else]: https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops
-[range]: https://docs.python.org/3/library/stdtypes.html#range
[break statement]: https://docs.python.org/3/reference/simple_stmts.html#the-break-statement
+[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
[continue statement]: https://docs.python.org/3/reference/simple_stmts.html#the-continue-statement
-[while statement]: https://docs.python.org/3/reference/compound_stmts.html#the-while-statement
-[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
[enumerate]: https://docs.python.org/3/library/functions.html#enumerate
-[iterator]: https://docs.python.org/3/glossary.html#term-iterator
-[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
-[range is not an iterator]: https://treyhunner.com/2018/02/python-range-is-not-an-iterator/
[for statement]: https://docs.python.org/3/reference/compound_stmts.html#for
[iterable]: https://docs.python.org/3/glossary.html#term-iterable
+[iterator]: https://docs.python.org/3/glossary.html#term-iterator
+[loop else]: https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops
[next built-in]: https://docs.python.org/3/library/functions.html#next
+[range is not an iterator]: https://treyhunner.com/2018/02/python-range-is-not-an-iterator/
+[range]: https://docs.python.org/3/library/stdtypes.html#range
[stopiteration]: https://docs.python.org/3/library/exceptions.html#StopIteration
+[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
+[while statement]: https://docs.python.org/3/reference/compound_stmts.html#the-while-statement
diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md
index d5a4f7271ce..1155bcf7a5c 100644
--- a/concepts/numbers/about.md
+++ b/concepts/numbers/about.md
@@ -177,7 +177,7 @@ This means calculations within `()` have the highest priority, followed by `**`,
## Precision & Representation
-Integers in Python have [arbitrary precision][arbitrary-precision] -- the amount of digits is limited only by the available memory of the host system.
+Integers in Python have [arbitrary precision][arbitrary-precision] -- the number of digits is limited only by the available memory of the host system.
Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system.
Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers.
diff --git a/concepts/random/.meta/config.json b/concepts/random/.meta/config.json
new file mode 100644
index 00000000000..7319e329bad
--- /dev/null
+++ b/concepts/random/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "The random module contains functionality to generate random values for modelling, simulations and games. It should not be used for security or cryptographic applications.",
+ "authors": ["BethanyG", "colinleach"],
+ "contributors": []
+}
diff --git a/concepts/random/about.md b/concepts/random/about.md
new file mode 100644
index 00000000000..9ed984179d3
--- /dev/null
+++ b/concepts/random/about.md
@@ -0,0 +1,245 @@
+# About
+
+Many programs need (apparently) random values to simulate real-world events.
+
+Common, familiar examples include:
+- A coin toss: a random value from `('H', 'T')`.
+- The roll of a die: a random integer from 1 to 6.
+- Shuffling a deck of cards: a random ordering of a card list.
+
+Generating truly random values with a computer is a [surprisingly difficult technical challenge][truly-random], so you may see these results referred to as "pseudorandom".
+
+In practice, a well-designed library like the [`random`][random] module in the Python standard library is fast, flexible, and gives results that are amply good enough for most applications in modelling, simulation and games.
+
+The rest of this page will list a few of the most common functions in `random`.
+We encourage you to explore the full `random` documentation, as there are many more options than what we cover here.
+
+
+
+~~~~exercism/caution
+
+The `random` module should __NOT__ be used for security and cryptographic applications.
+
+Instead, Python provides the [`secrets`][secrets] module.
+This is specially optimized for cryptographic security.
+Some of the prior issues and reasons for creating the secrets module can be found in [PEP 506][PEP 506].
+
+[secrets]: https://docs.python.org/3.11/library/secrets.html#module-secrets
+[PEP 506]: https://peps.python.org/pep-0506/
+~~~~
+
+
+
+## Importing
+
+Before you can utilize the tools in the `random` module, you must first import it:
+
+```python
+>>> import random
+
+# Choose random integer from a range
+>>> random.randrange(1000)
+360
+
+>>> random.randrange(-1, 500)
+228
+
+>>> random.randrange(-10, 11, 2)
+-8
+
+# Choose random integer between two values (inclusive)
+>>> random.randint(5, 25)
+22
+
+```
+
+To avoid typing the name of the module, you can import specific functions by name:
+
+```python
+>>> from random import choice, choices
+
+# Using choice() to pick Heads or Tails 10 times
+>>> tosses = []
+>>> for side in range(10):
+>>> tosses.append(choice(['H', 'T']))
+
+>>> print(tosses)
+['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H']
+
+
+# Using choices() to pick Heads or Tails 8 times
+>>> picks = []
+>>> picks.extend(choices(['H', 'T'], k=8))
+>>> print(picks)
+['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T']
+```
+
+
+## Creating random integers
+
+The `randrange()` function has three forms, to select a random value from `range(start, stop, step)`:
+ 1. `randrange(stop)` gives an integer `n` such that `0 <= n < stop`
+ 2. `randrange(start, stop)` gives an integer `n` such that `start <= n < stop`
+ 3. `randrange(start, stop, step)` gives an integer `n` such that `start <= n < stop` and `n` is in the sequence `start, start + step, start + 2*step...`
+
+For the common case where `step == 1`, the `randint(a, b)` function may be more convenient and readable.
+Possible results from `randint()` _include_ the upper bound, so `randint(a, b)` is the same as using `randrange(a, b+1)`:
+
+```python
+>>> import random
+
+# Select one number at random from the range 0, 499
+>>> random.randrange(500)
+219
+
+# Select 10 numbers at random between 0 and 9 two steps apart.
+>>> numbers = []
+>>> for integer in range(10):
+>>> numbers.append(random.randrange(0, 10, 2))
+>>> print(numbers)
+[2, 8, 4, 0, 4, 2, 6, 6, 8, 8]
+
+# roll a die
+>>> random.randint(1, 6)
+4
+```
+
+
+
+## Working with sequences
+
+The functions in this section assume that you are starting from some [sequence][sequence-types], or other container.
+
+
+This will typically be a `list`, or with some limitations a `tuple` or a `set` (_a `tuple` is immutable, and `set` is unordered_).
+
+
+
+### `choice()` and `choices()`
+
+The `choice()` function will return one entry chosen at random from a given sequence.
+At its simplest, this might be a coin-flip:
+
+```python
+# This will pick one of the two values in the list at random 5 separate times
+>>> [random.choice(['H', 'T']) for _ in range(5)]
+['T', 'H', 'H', 'T', 'H']
+
+We could accomplish essentially the same thing using the `choices()` function, supplying a keyword argument with the list length:
+
+
+```python
+>>> random.choices(['H', 'T'], k=5)
+['T', 'H', 'T', 'H', 'H']
+```
+
+
+In the examples above, we assumed a fair coin with equal probability of heads or tails, but weights can also be specified.
+For example, if a bag contains 10 red balls and 15 green balls, and we would like to pull one out at random:
+
+```python
+>>> random.choices(['red', 'green'], [10, 15])
+['red']
+```
+
+
+
+### `sample()`
+
+The `choices()` example above assumes what statisticians call ["sampling with replacement"][sampling-with-replacement].
+Each pick or choice has **no effect** on the probability of future choices, and the distribution of potential choices remains the same from pick to pick.
+
+
+In the example with red and green balls: after each choice, we _return_ the ball to the bag and shake well before the next pick.
+This is in contrast to a situation where we pull out a red ball and _it stays out_.
+Not returning the ball means there are now fewer red balls in the bag, and the next choice is now _less likely_ to be red.
+
+To simulate this "sampling without replacement", the random module provides the `sample()` function.
+The syntax of `sample()` is similar to `choices()`, except it adds a `counts` keyword parameter:
+
+
+```python
+>>> random.sample(['red', 'green'], counts=[10, 15], k=10)
+['green', 'green', 'green', 'green', 'green', 'red', 'red', 'red', 'red', 'green']
+```
+
+Samples are returned in the order they were chosen.
+
+
+
+### `shuffle()`
+
+Both `choices()` and `sample()` return new lists when `k > 1`.
+In contrast, `shuffle()` randomizes the order of a list _**in place**_, and the original ordering is lost:
+
+```python
+>>> my_list = [1, 2, 3, 4, 5]
+>>> random.shuffle(my_list)
+>>> my_list
+[4, 1, 5, 2, 3]
+```
+
+
+## Working with Distributions
+
+Until now, we have concentrated on cases where all outcomes are equally likely.
+For example, `random.randrange(100)` is equally likely to give any integer from 0 to 99.
+
+Many real-world situations are far less simple than this.
+As a result, statisticians have created a wide variety of [`distributions`][probability-distribution] to describe "real world" results mathematically.
+
+
+
+### Uniform distributions
+
+For integers, `randrange()` and `randint()` are used when all probabilities are equal.
+This is called a [`uniform`][uniform-distribution] distribution.
+
+
+There are floating-point equivalents to `randrange()` and `randint()`.
+
+__`random()`__ gives a `float` value `x` such that `0.0 <= x < 1.0`.
+
+__`uniform(a, b)`__ gives `x` such that `a <= x <= b`.
+
+```python
+>>> [round(random.random(), 3) for _ in range(5)]
+[0.876, 0.084, 0.483, 0.22, 0.863]
+
+>>> [round(random.uniform(2, 5), 3) for _ in range(5)]
+[2.798, 2.539, 3.779, 3.363, 4.33]
+```
+
+
+
+### Gaussian distribution
+
+Also called the "normal" distribution or the "bell-shaped" curve, this is a very common way to describe imprecision in measured values.
+
+For example, suppose the factory where you work has just bought 10,000 bolts which should be identical.
+You want to set up the factory robot to handle them, so you weigh a sample of 100 and find that they have an average (or `mean`) weight of 4.731g.
+This is extremely unlikely to mean that they all weigh exactly 4.731g.
+Perhaps you find that values range from 4.627 to 4.794g but cluster around 4.731g.
+
+This is the [`Gaussian distribution`][gaussian-distribution], for which probabilities peak at the mean and tails off symmetrically on both sides (hence "bell-shaped").
+To simulate this in software, we need some way to specify the width of the curve (_typically, expensive bolts will cluster more tightly around the mean than cheap bolts!_).
+
+By convention, this is done with the [`standard deviation`][standard-deviation]: small values for a sharp, narrow curve, large for a low, broad curve.
+Mathematicians love Greek letters, so we use `μ` ('mu') to represent the mean and `σ` ('sigma') to represent the standard deviation.
+Thus, if you read that "95% of values are within 2σ of μ" or "the Higgs boson has been detected with 5-sigma confidence", such comments relate to the standard deviation.
+
+```python
+>>> mu = 4.731
+>>> sigma = 0.316
+>>> [round(random.gauss(mu, sigma), 3) for _ in range(5)]
+[4.72, 4.957, 4.64, 4.556, 4.968]
+```
+
+[gaussian-distribution]: https://simple.wikipedia.org/wiki/Normal_distribution
+[probability-distribution]: https://simple.wikipedia.org/wiki/Probability_distribution
+[random]: https://docs.python.org/3/library/random.html
+[sampling-with-replacement]: https://www.youtube.com/watch?v=LnGFL_A6A6A
+[sequence-types]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range
+[standard-deviation]: https://simple.wikipedia.org/wiki/Standard_deviation
+[truly-random]: https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random
+[uniform-distribution]: https://www.investopedia.com/terms/u/uniform-distribution.asp#:~:text=In%20statistics%2C%20uniform%20distribution%20refers,a%20spade%20is%20equally%20likely.
diff --git a/concepts/random/introduction.md b/concepts/random/introduction.md
new file mode 100644
index 00000000000..6bf880be57f
--- /dev/null
+++ b/concepts/random/introduction.md
@@ -0,0 +1,108 @@
+# Introduction
+
+Many programs need (apparently) random values to simulate real-world events.
+
+Common, familiar examples include:
+- A coin toss: a random value from `('H', 'T')`.
+- The roll of a die: a random integer from 1 to 6.
+- Shuffling a deck of cards: a random ordering of a card list.
+- The creation of trees and bushes in a 3-D graphics simulation.
+
+Generating _truly_ random values with a computer is a [surprisingly difficult technical challenge][truly-random], so you may see these results referred to as "pseudorandom".
+
+In practice, a well-designed library like the [`random`][random] module in the Python standard library is fast, flexible, and gives results that are amply good enough for most applications in modelling, simulation and games.
+
+For this brief introduction, we show the four most commonly used functions from the module.
+We encourage you to explore the full [`random`][random] documentation, as there are many tools and options.
+
+
+~~~~exercism/caution
+
+The `random` module should __NOT__ be used for security and cryptographic applications!!
+
+Instead, Python provides the [`secrets`][secrets] module.
+This is specially optimized for cryptographic security.
+Some of the prior issues and reasons for creating the secrets module can be found in [PEP 506][PEP 506].
+
+[secrets]: https://docs.python.org/3.11/library/secrets.html#module-secrets
+[PEP 506]: https://peps.python.org/pep-0506/
+~~~~
+
+
+Before you can utilize the tools in the `random` module, you must first import it:
+
+```python
+>>> import random
+
+# Choose random integer from a range
+>>> random.randrange(1000)
+360
+
+>>> random.randrange(-1, 500)
+228
+
+>>> random.randrange(-10, 11, 2)
+-8
+
+# Choose random integer between two values (inclusive)
+>>> random.randint(5, 25)
+22
+
+```
+
+To avoid typing the name of the module, you can import specific functions by name:
+
+```python
+>>> from random import choice, choices
+
+# Using choice() to pick Heads or Tails 10 times
+>>> tosses = []
+>>> for side in range(10):
+>>> tosses.append(choice(['H', 'T']))
+
+>>> print(tosses)
+['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H']
+
+
+# Using choices() to pick Heads or Tails 8 times
+>>> picks = []
+>>> picks.extend(choices(['H', 'T'], k=8))
+>>> print(picks)
+['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T']
+```
+
+
+
+## `randrange()` and `randint()`
+
+Shown in the first example above, the `randrange()` function has three forms:
+
+1. `randrange(stop)` gives an integer `n` such that `0 <= n < stop`
+2. `randrange(start, stop)` gives an integer `n` such that `start <= n < stop`
+3. `randrange(start, stop, step)` gives an integer `n` such that `start <= n < stop`
+ and `n` is in the sequence `start, start + step, start + 2*step...`
+
+For the most common case where `step == 1`, `randint(a, b)` may be more convenient and readable.
+Possible results from `randint()` _include_ the upper bound, so `randint(a, b)` is the same as using `randrange(a, b+1)`.
+
+
+
+## `choice()` and `choices()`
+
+These two functions assume that you are starting from some [sequence][sequence-types], or other container.
+This will typically be a `list`, or with some limitations a `tuple` or a `set` (_a `tuple` is immutable, and `set` is unordered_).
+
+The `choice()` function will return one entry chosen at random from a given sequence, and `choices()` will return `k` number of entries chosen at random from a given sequence.
+In the examples shown above, we assumed a fair coin with equal probability of heads or tails, but weights can also be specified.
+
+For example, if a bag contains 10 red balls and 15 green balls, and we would like to pull one out at random:
+
+
+```python
+>>> random.choices(['red', 'green'], [10, 15])
+['red']
+```
+
+[random]: https://docs.python.org/3/library/random.html
+[sequence-types]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range
+[truly-random]: https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random
diff --git a/concepts/random/links.json b/concepts/random/links.json
new file mode 100644
index 00000000000..22f60dbfb46
--- /dev/null
+++ b/concepts/random/links.json
@@ -0,0 +1,14 @@
+[
+ {
+ "url": "https://docs.python.org/3/library/random.html/",
+ "description": "Official documentation for the random module."
+ },
+ {
+ "url": "https://engineering.mit.edu/engage/ask-an-engineer/can-a-computer-generate-a-truly-random-number/",
+ "description": "MIT Engineering: Can a computer generate a truly random number?"
+ },
+ {
+ "url": "https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random",
+ "description": "Are Random Numbers Really Random?"
+ }
+]
diff --git a/concepts/recursion/about.md b/concepts/recursion/about.md
index 1c66756caf2..1cf24388269 100644
--- a/concepts/recursion/about.md
+++ b/concepts/recursion/about.md
@@ -6,7 +6,7 @@ Recursion can be viewed as another way to loop/iterate.
And like looping, a Boolean expression or `True/False` test is used to know when to stop the recursive execution.
_Unlike_ looping, recursion without termination in Python cannot not run infinitely.
Values used in each function call are placed in their own frame on the Python interpreter stack.
-If the total amount of function calls takes up more space than the stack has room for, it will result in an error.
+If the total number of function calls takes up more space than the stack has room for, it will result in an error.
## Looping vs Recursive Implementation
diff --git a/concepts/recursion/introduction.md b/concepts/recursion/introduction.md
index aebfd6596be..fb7e1970705 100644
--- a/concepts/recursion/introduction.md
+++ b/concepts/recursion/introduction.md
@@ -5,7 +5,7 @@ It can be viewed as another way to loop/iterate.
Like looping, a Boolean expression or `True/False` test is used to know when to stop the recursive execution.
_Unlike_ looping, recursion without termination in Python cannot not run infinitely.
Values used in each function call are placed in their own frame on the Python interpreter stack.
-If the total amount of function calls takes up more space than the stack has room for, it will result in an error.
+If the total number of function calls takes up more space than the stack has room for, it will result in an error.
```python
def print_increment(step, max_value):
diff --git a/concepts/secrets/.meta/config.json b/concepts/secrets/.meta/config.json
new file mode 100644
index 00000000000..152aa0eb3ba
--- /dev/null
+++ b/concepts/secrets/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "The secrets module is a cryptographically-secure alternative to the random module, intended for security-critical uses.",
+ "authors": ["BethanyG", "colinleach"],
+ "contributors": []
+}
diff --git a/concepts/secrets/about.md b/concepts/secrets/about.md
new file mode 100644
index 00000000000..5987ab37e91
--- /dev/null
+++ b/concepts/secrets/about.md
@@ -0,0 +1,51 @@
+# About
+
+A previous concept discussed the [concept:python/random]() module, which produces [pseudo-random numbers][pseudo-random-numbers] and pseudo-random list orderings.
+
+The [`secrets`][secrets] module overlaps with `random` in some of its functionality, but the two modules are designed with very different priorities.
+
+- `random` is optimized for high performance in modelling and simulation, with "good enough" pseudo-random number generation.
+- `secrets` is designed to be crytographically secure for applications such as password hashing, security token generation, and account authentication.
+
+
+Further details on why the addition of the `secrets` module proved necessary are given in [PEP 506][PEP506].
+
+The `secrets` is relatively small and straightforward, with methods for generating random integers, bits, bytes or tokens, or a random entry from a given sequence.
+
+To use `scerets`, you mush first `import` it:
+
+
+```python
+>>> import secrets
+
+#Returns n, where 0 <= n < 1000.
+>>> secrets.randbelow(1000)
+577
+
+#32-bit integers.
+>>> secrets.randbits(32)
+3028709440
+
+>>> bin(secrets.randbits(32))
+'0b11111000101100101111110011110100'
+
+#Pick at random from a sequence.
+>>> secrets.choice(['my', 'secret', 'thing'])
+'thing'
+
+#Generate a token made up of random hexadecimal digits.
+>>> secrets.token_hex()
+'f683d093ea9aa1f2607497c837cf11d7afaefa903c5805f94b64f068e2b9e621'
+
+#Generate a URL-safe token of random alphanumeric characters.
+>>> secrets.token_urlsafe(16)
+'gkSUKRdiPDHqmImPi2HMnw'
+```
+
+
+If you are writing security-sensitive applications, you will certainly want to read the [full documentation][secrets], which gives further advice and examples.
+
+
+[PEP506]: https://peps.python.org/pep-0506/
+[pseudo-random-numbers]: https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators
+[secrets]: https://docs.python.org/3/library/secrets.html
diff --git a/concepts/secrets/introduction.md b/concepts/secrets/introduction.md
new file mode 100644
index 00000000000..04308ed0a2a
--- /dev/null
+++ b/concepts/secrets/introduction.md
@@ -0,0 +1,17 @@
+# Introduction
+
+A previous concept discussed the [concept:python/random]() module, which produces [pseudo-random numbers][pseudo-random-numbers] and pseudo-random list orderings.
+
+The [`secrets`][secrets] module overlaps with `random` in some of its functionality, but the two modules are designed with very different priorities.
+
+- `random` is optimized for high performance in modelling and simulation, with "good enough" pseudo-random number generation.
+- `secrets` is designed to be crytographically secure for applications such as password hashing, security token generation, and account authentication.
+
+
+Further details on why the addition of the `secrets` module proved necessary are given in [PEP 506][PEP506].
+
+If you are writing security-sensitive applications, you will certainly want to read the [full documentation][secrets], which gives further advice and examples.
+
+[PEP506]: https://peps.python.org/pep-0506/
+[pseudo-random-numbers]: https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators
+[secrets]: https://docs.python.org/3/library/secrets.html
diff --git a/concepts/secrets/links.json b/concepts/secrets/links.json
new file mode 100644
index 00000000000..ccf44e6fe59
--- /dev/null
+++ b/concepts/secrets/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "https://docs.python.org/3/library/secrets.html/",
+ "description": "The secrets module."
+ },
+ {
+ "url": "https://peps.python.org/pep-0506/",
+ "description": "PEP 506, giving reasons why the secrets module is necessary."
+ },
+ {
+ "url": "https://en.wikipedia.org/wiki/Pseudorandomness",
+ "description": "Wikipedia: Pseudorandomness."
+ },
+ {
+ "url": "https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators",
+ "description": "Khan Academy: Pseudorandom Number Generators."
+ }
+]
diff --git a/concepts/sets/about.md b/concepts/sets/about.md
index 5f272402eef..204df380577 100644
--- a/concepts/sets/about.md
+++ b/concepts/sets/about.md
@@ -1,119 +1,153 @@
# Sets
-A [`set`][type-set] is a mutable and _unordered_ collection of _hashable_ objects.
-Items within a `set` are distinct and duplicate members are not allowed.
-Like most collections, `sets` can hold any (or multiple) data type(s) -- as long as those types can be [hashed][hashable].
-Sets also come in an _immutable_ [`frozenset`][type-frozenset] flavor.
+A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects.
+Set members must be distinct — duplicate items are not allowed.
+They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_.
+Sets also come in an immutable [`frozensets`][type-frozenset] flavor.
-Like other collection types, `sets` support membership testing through `in`, length calculation through `len()`, shallow copies through `copy()`, and iteration via `for item in `.
-_Unlike_ sequence types (_`string`, `list` & `tuple`_), `sets` are **neither ordered nor indexed**, and _do not support_ slicing, sorting, or other sequence-type behaviors.
+Sets are most commonly used to quickly remove duplicates from other data structures or item groupings.
+They are also used for efficient comparisons when sequencing and duplicate tracking are not needed.
-`sets` are most commonly used to quickly dedupe groups of items.
-They're also used for fast membership testing, finding supersets & subsets of items, and performing "set math" (_calculating union, intersection, difference & symmetric difference between groups of items._).
+Like other collection types (_dictionaries, lists, tuples_), `sets` support:
+- Iteration via `for item in `
+- Membership checking via `in` and `not in`,
+- Length calculation through `len()`, and
+- Shallow copies through `copy()`
-Checking membership in a `set` has only O(1) time complexity versus checking for membership in a `list` or `string`, which has worst-case O(n) time complexity.
-Operations such as `.union()`, `.intersection()`, or `.difference()` have an average O(n) time complexity.
+`sets` do not support:
+- Indexing of any kind
+- Ordering via sorting or insertion
+- Slicing
+- Concatenation via `+`
-## Construction
-A `set` can be declared as a _set literal_ with curly `{}` brackets and commas between elements.
+Checking membership in a `set` has constant time complexity (on average) versus checking membership in a `list` or `string`, where the time complexity grows as the length of the data increases.
+Methods such as `.union()`, `.intersection()`, or `.difference()` also have constant time complexity (on average).
+
+
+## Set Construction
+
+While sets can be created in many different ways, the most straightforward construction methods are declaring a _set literal_, using the `set` class constructor (`set()`), and using a _set comprehension_.
+
+### Set Literals
+
+A `set` can be directly entered as a _set literal_ with curly `{}` brackets and commas between elements.
Duplicates are silently omitted:
+
```python
->>> one_element = {'😀'}
->>> one_element
-{'😀'}
+>>> one_element = {'➕'}
+{'➕'}
->>> multiple_elements = {'Hello!', '¡Hola!', 'Привет!', 'こんにちは!'}
->>> multiple_elements
-{'こんにちは!', '¡Hola!', 'Hello!', 'Привет!'}
+>>> multiple_elements = {'➕', '🔻', '🔹', '🔆'}
+{'➕', '🔻', '🔹', '🔆'}
->>> multiple_duplicates = {'Hello!', '¡Hola!', 'Привет!', 'こんにちは!', '¡Hola!', 'Привет!'}
->>> multiple_duplicates
-{'こんにちは!', '¡Hola!', 'Hello!', 'Привет!'}
+>>> multiple_duplicates = {'Hello!', 'Hello!', 'Hello!',
+ '¡Hola!','Привіт!', 'こんにちは!',
+ '¡Hola!','Привіт!', 'こんにちは!'}
+{'こんにちは!', '¡Hola!', 'Hello!', 'Привіт!'}
```
-Set literals use the same curly braces as `dict` literals, so the `set()` constructor must be used to declare an empty `set`.
+Set literals use the same curly braces as `dict` literals, which means you need to use `set()` to create an empty `set`.
+
+### The Set Constructor
+
+`set()` (_the constructor for the `set` class_) can be used with any `iterable` passed as an argument.
+Elements of the `iterable` are cycled through and added to the `set` individually.
+Element order is not preserved and duplicates are silently omitted:
-The `set()` constructor can also be used with any _iterable_ passed as an argument.
-Elements are cycled through by the constructor and added to the `set` individually.
-Order is not preserved and duplicates are silently omitted:
```python
+# To create an empty set, the constructor must be used.
>>> no_elements = set()
->>> no_elements
set()
-# The tuple is unpacked and each distinct element is added. Duplicates are removed.
->>> multiple_elements_from_tuple = set(("Parrot", "Bird", 334782, "Bird", "Parrot"))
->>> multiple_elements_from_tuple
+# The tuple is unpacked & each element is added.
+# Duplicates are removed.
+>>> elements_from_tuple = set(("Parrot", "Bird",
+ 334782, "Bird", "Parrot"))
{334782, 'Bird', 'Parrot'}
-# The list is unpacked and each distinct element is added.
->>> multiple_elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 7, 11, 7, 11, 13, 13])
->>> multiple_elements_from_set
+# The list is unpacked & each element is added.
+# Duplicates are removed.
+>>> elements_from_list = set([2, 3, 2, 3, 3, 3, 5,
+ 7, 11, 7, 11, 13, 13])
{2, 3, 5, 7, 11, 13}
```
-Results when using a set constructor with a string or dictionary may be surprising:
+### Set Comprehensions
+
+Like `lists` and `dicts`, sets can be created via _comprehension_:
```python
-# String elements (Unicode code points) are iterated through and added *individually*.
->>> multiple_elements_string = set("Timbuktu")
->>> multiple_elements_string
+# First, a list with duplicates
+>>> numbers = [1,2,3,4,5,6,6,5,4,8,9,9,9,2,3,12,18]
+
+# This set comprehension squares the numbers divisible by 3
+# Duplicates are removed.
+>>> calculated = {item**2 for item in numbers if item % 3 == 0}
+{9, 36, 81, 144, 324}
+```
+
+### Gotchas when Creating Sets
+
+Due to its "unpacking" behavior, using the `set` constructor with a string might be surprising:
+
+```python
+# String elements (Unicode code points) are
+# iterated through and added *individually*.
+>>> elements_string = set("Timbuktu")
{'T', 'b', 'i', 'k', 'm', 't', 'u'}
-# Unicode separators and positioning code points are also added *individually*.
+# Unicode separators and positioning code points
+# are also added *individually*.
>>> multiple_code_points_string = set('अभ्यास')
->>> multiple_code_points_string
{'अ', 'भ', 'य', 'स', 'ा', '्'}
-
-# The iteration default for dictionaries is over the keys.
->>> source_data = {"fish": "gold", "monkey": "brown", "duck" : "white", "crow": "black"}
->>> set(source_data)
-{'crow', 'duck', 'fish', 'monkey'}
```
-Sets can hold heterogeneous datatypes, but all `set` elements must be _hashable_:
+Remember: sets can hold different datatypes and _nested_ datatypes, but all `set` elements must be _hashable_:
```python
-
->>> lists_as_elements = {['😅','🤣'], ['😂','🙂','🙃'], ['😜', '🤪', '😝']}
+# Attempting to use a list for a set member throws a TypeError
+>>> lists_as_elements = {['🌈','💦'],
+ ['☁️','⭐️','🌍'],
+ ['⛵️', '🚲', '🚀']}
Traceback (most recent call last):
-
- File "", line 1, in
- lists_as_elements = {['😅','🤣'], ['😂','🙂','🙃'], ['😜', '🤪', '😝']}
-
+ File "", line 1, in
TypeError: unhashable type: 'list'
-# standard sets are mutable, so they cannot be hashed.
->>> sets_as_elements = {{'😅','🤣'}, {'😂','🙂','🙃'}, {'😜', '🤪', '😝'}}
-Traceback (most recent call last):
- File "", line 1, in
- sets_as_elements = {{'😅','🤣'}, {'😂','🙂','🙃'}, {'😜', '🤪', '😝'}}
+# Standard sets are mutable, so they cannot be hashed.
+>>> sets_as_elements = {{'🌈','💦'},
+ {'☁️','⭐️','🌍'},
+ {'⛵️', '🚲', '🚀'}}
+Traceback (most recent call last):
+ File "", line 1, in
TypeError: unhashable type: 'set'
```
-Therefore, to create a `set` of `sets`, the contained sets must be of type `frozenset()`
+However, a `set` of `sets` can be created via type `frozenset()`:
```python
-# frozensets don't have a literal form
->>> set_1 = frozenset({'😜', '😝', '🤪'})
->>> set_2 = frozenset({'😅', '🤣'})
->>> set_3 = frozenset({'😂', '🙂', '🙃'})
+# Frozensets don't have a literal form.
+>>> set_1 = frozenset({'🌈','💦'})
+>>> set_2 = frozenset({'☁️','⭐️','🌍'})
+>>> set_3 = frozenset({'⛵️', '🚲', '🚀'})
>>> frozen_sets_as_elements = {set_1, set_2, set_3}
>>> frozen_sets_as_elements
-{frozenset({'😜', '😝', '🤪'}), frozenset({'😅', '🤣'}), frozenset({'😂', '🙂', '🙃'})}
+{frozenset({'⛵️', '🚀', '🚲'}),
+ frozenset({'🌈', '💦'}),
+ frozenset({'☁️', '⭐️', '🌍'})}
```
-## Working with Sets
-Elements can be added/removed using `.add(- )` / `.remove(
- )`.
-`remove(
- )` will raise a `KeyError` if the item is not present in the `set`.
+## Adding and Removing Set Members
+
+Elements can be added or removed from a `set` using the methods `.add(
- )` and `.remove(
- )`.
+The `.remove(
- )` method will raise a `KeyError` if the item is not present in the `set`:
```python
>>> creatures = {'crow', 'duck', 'fish', 'monkey', 'elephant'}
@@ -122,100 +156,139 @@ Elements can be added/removed using `.add(
- )` / `.remove(
- )`
>>> creatures
{'beaver', 'crow', 'elephant', 'fish', 'monkey'}
-# Trying to remove an item that is not present will raise a KeyError
+# Trying to remove an item that is not present raises a KeyError
>>> creatures.remove('bear')
Traceback (most recent call last):
-
- File "", line 1, in
- creatures.remove('bear')
-
-KeyError: 'bear'
+ File "", line 1, in
+ KeyError: 'bear'
```
-`.discard(
- )` will also remove an item from the `set`, but will **not** raise a `KeyError` if the item is not present.
-`.clear()` will remove all items.
-`.pop()` will remove and _return_ an **arbitrary** item and raises a `KeyError` if the `set` is empty.
+### Additional Strategies for Removing Set Members
-## Set Methods
+- `.discard(
- )` will remove an item from the `set`, but will **not** raise a `KeyError` if the item is not present.
+- `.clear()` will remove all items from the set.
+- `.pop()` will remove and _return_ an **arbitrary** item, and raises a `KeyError` if the `set` is empty.
-Sets implement methods that generally mimic [mathematical set operations][mathematical-sets].
-Most (_though not all_) of these methods can be performed using either operator(s) or method call(s).
-Using operators requires that both inputs be `sets` or `frozensets`, while methods will generally take any iterable as an argument.
-### Fast Membership Testing Between Groups
+## Set Operations
-The `.isdisjoint()` method is used to test if a `set` has **no elements in common** with another set or iterable.
-It will accept any `iterable` or `set` as an arugment, returning `True` if they are **disjoint**, `False` otherwise.
-Note that for `dcts`, the iteration default is over`.keys()`.
+Sets have methods that generally mimic [mathematical set operations][mathematical-sets].
+Most (_not all_) of these methods have an [operator][operator] equivalent.
+Methods generally take any `iterable` as an argument, while operators require that both sides of the operation are `sets` or `frozensets`.
-```python
->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'}
->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}
-# Dictionary of animal names with colors
->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white',
- 'albatross': 'grey and white','crow': 'black','elephant': 'grey',
- 'dog': 'rust','cow': 'black and white','tiger': 'orange and black',
- 'cat': 'grey','squirrel': 'black'}
+### Membership Testing Between Sets
-# List of additonal animals
->>> additional_animals = ['pangolin', 'panda', 'parrot', 'lemur', 'tiger', 'pangolin']
-...
+The `.isdisjoint()` method is used to test if a `sets` elements have any overlap with the elements of another.
+The method will accept any `iterable` or `set` as an argument.
+It will return `True` if the two sets have **no elements in common**, `False` if elements are **shared**.
->>> mammals.isdisjoint(birds)
+```python
+# Both mammals and additional_animals are lists.
+>>> mammals = ['squirrel','dog','cat','cow', 'tiger', 'elephant']
+>>> additional_animals = ['pangolin', 'panda', 'parrot',
+ 'lemur', 'tiger', 'pangolin']
+
+# Animals is a dict.
+>>> animals = {'chicken': 'white',
+ 'sparrow': 'grey',
+ 'eagle': 'brown and white',
+ 'albatross': 'grey and white',
+ 'crow': 'black',
+ 'elephant': 'grey',
+ 'dog': 'rust',
+ 'cow': 'black and white',
+ 'tiger': 'orange and black',
+ 'cat': 'grey',
+ 'squirrel': 'black'}
+
+# Birds is a set.
+>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}
+
+# Mammals and birds don't share any elements.
+>>> birds.isdisjoint(mammals)
True
->>> mammals.isdisjoint(animals)
-False
-
+# There are also no shared elements between
+# additional_animals and birds.
>>> birds.isdisjoint(additional_animals)
True
->>> set(additional_animals).isdisjoint(animals)
+# Animals and mammals have shared elements.
+# **Note** The first object needs to be a set or converted to a set
+# since .isdisjoint() is a set method.
+>>> set(animals).isdisjoint(mammals)
False
```
-`.issubset()` | ` <= ` are used to check if every element in `` is also in ``.
-`.issuperset()` | ` >= ` are used to check the inverse -- if every element in `` is also in ``.
+### Checking for Subsets and Supersets
-```python
->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white',
- 'albatross': 'grey and white','crow': 'black','elephant': 'grey',
- 'dog': 'rust','cow': 'black and white','tiger': 'organge and black',
- 'cat': 'grey','squirrel': 'black'}
+`.issubset()` is used to check if every element in `` is also in ``.
+The operator form is ` <= `:
->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'}
->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}
+```python
+# Set methods will take any iterable as an argument.
+# All members of birds are also members of animals.
+>>> birds.issubset(animals)
+True
-# Methods will take any iterable as an argument
->>> mammals.issubset(animal_colors)
+# All members of mammals also appear in animals.
+# **Note** The first object needs to be a set or converted to a set
+# since .issubset() is a set method.
+>>> set(mammals).issubset(animals)
True
+# Both objects need to be sets to use a set operator
+>>> birds <= set(mammals)
+False
-# A set is always a loose subset of itself
->>> animals <= animals
+# A set is always a loose subset of itself.
+>>> set(additional_animals) <= set(additional_animals)
True
+```
+
+`.issuperset()` is the inverse of `.issubset()`.
+It is used to check if every element in `` is also in ``.
+The operator form is ` >= `:
->>> birds <= animals
+
+```python
+# All members of mammals also appear in animals.
+# **Note** The first object needs to be a set or converted to a set
+# since .issuperset() is a set method.
+>>> set(animals).issuperset(mammals)
True
->>> birds <= mammals
+# All members of animals do not show up as members of birds.
+>>> birds.issuperset(animals)
False
+
+# Both objects need to be sets to use a set operator
+>>> birds >= set(mammals)
+False
+
+# A set is always a loose superset of itself.
+>>> set(animals) >= set(animals)
+True
```
-` < ` and ` > ` are used to test for _proper subsets_:
-(`` <= ``) AND (`` != ``) for the `<` operator; (`` >= ``) AND (`` != ``) for the `>` operator.
-They have no method equivelent.
+### 'Proper' Subsets and Supersets
+
+` < ` and ` > ` are used to test for _proper subsets_.
+A `set` is a proper subset if (`` <= ``) **AND** (`` != ``) for the `<` operator.
+
+A `set is a proper superset if `(`` >= ``) **AND** (`` != ``) for the `>` operator.
+These operators have no method equivalent:
```python
->>> animal_names = {'albatross','cat','chicken','cow','crow','dog',
- 'eagle','elephant','sparrow','squirrel','tiger'}
+>>> animal_names = {'albatross','cat','chicken','cow','crow','dog',
+ 'eagle','elephant','sparrow','squirrel','tiger'}
->>> animal_names_also = {'albatross','cat','chicken','cow','crow','dog',
- 'eagle','elephant','sparrow','squirrel','tiger'}
+>>> animals_also = {'albatross','cat','chicken','cow','crow','dog',
+ 'eagle','elephant','sparrow','squirrel','tiger'}
->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'}
->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}
+>>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'}
+>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}
>>> mammals < animal_names
True
@@ -223,81 +296,114 @@ True
>>> animal_names > birds
True
-# A set is never a *proper subset* of itself
->>> animal_names_also < animal_names
+# A set is not a *proper subset* if set == other set.
+>>> animals_also < animal_names
False
-
->>> animals < animals
-
+# A set is never a *proper subset* of itself
+>>> animals_also < animals_also
+False
```
-### Set Operations
+### Set Unions
-`.union(*)` and ` | | | ... | ` return a new `set` with elements from `` and all ``.
+`.union(*)` returns a new `set` with elements from `` and all ``.
+The operator form of this method is ` | | | ... | `.
```python
->>> perennial_vegetables = {'Asparagus', 'Broccoli', 'Sweet Potatoe', 'Kale'}
->>> annual_vegetables = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'}
-
->>> more_perennials = ['Radicchio', 'Rhubarb', 'Spinach', 'Watercress']
+>>> perennials = {'Asparagus', 'Broccoli', 'Sweet Potato', 'Kale'}
+>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'}
+>>> more_perennials = ['Radicchio', 'Rhubarb',
+ 'Spinach', 'Watercress']
# Methods will take any iterable as an argument.
->>> perennial_vegetables.union(more_perennials)
-{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb','Spinach','Sweet Potatoe','Watercress'}
+>>> perennials.union(more_perennials)
+{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb',
+'Spinach','Sweet Potato','Watercress'}
# Operators require sets.
->>> perennial_vegetables | annual_vegetables
-{'Asparagus','Broccoli','Corn','Kale','Summer Squash','Sweet Peas','Sweet Potatoe','Zucchini'}
-
+>>> set(more_perennials) | perennials
+{'Asparagus',
+ 'Broccoli',
+ 'Kale',
+ 'Radicchio',
+ 'Rhubarb',
+ 'Spinach',
+ 'Sweet Potato',
+ 'Watercress'}
```
-`.difference(*)` and ` - - - ...` return a new `set` with elements from the original `` that are not in ``.
+### Set Differences
+
+`.difference(*)` returns a new `set` with elements from the original `` that are not in ``.
+The operator version of this method is ` - - - ...`.
```python
->>> berries_and_veggies = {'Asparagus', 'Broccoli', 'Watercress', 'Goji Berries', 'Goose Berries', 'Ramps',
- 'Walking Onions', 'Raspberries','Blueberries', 'Blackberries', 'Strawberries',
- 'Rhubarb', 'Kale', 'Artichokes', 'Currants', 'Honeyberries'}
+>>> berries_and_veggies = {'Asparagus',
+ 'Broccoli',
+ 'Watercress',
+ 'Goji Berries',
+ 'Goose Berries',
+ 'Ramps',
+ 'Walking Onions',
+ 'Blackberries',
+ 'Strawberries',
+ 'Rhubarb',
+ 'Kale',
+ 'Artichokes',
+ 'Currants'}
-# Methods will take any iterable as an argument.
>>> veggies = ('Asparagus', 'Broccoli', 'Watercress', 'Ramps',
'Walking Onions', 'Rhubarb', 'Kale', 'Artichokes')
->>> just_berries = berries_and_veggies.difference(veggies)
->>> just_berries
-{'Blackberries','Blueberries','Currants','Goji Berries',
- 'Goose Berries','Honeyberries','Raspberries','Strawberries'}
+# Methods will take any iterable as an argument.
+>>> berries = berries_and_veggies.difference(veggies)
+{'Blackberries','Currants','Goji Berries',
+ 'Goose Berries', 'Strawberries'}
->>> berries_and_veggies - just_berries
-{'Artichokes','Asparagus','Broccoli','Kale','Ramps','Rhubarb','Walking Onions','Watercress'}
+# Operators require sets.
+>>> berries_and_veggies - berries
+{'Artichokes','Asparagus','Broccoli','Kale',
+'Ramps','Rhubarb','Walking Onions','Watercress'}
```
-`.intersection(*)` and ` & & & ... ` return a new `set` with elements common to the original `set` and all ``.
+### Set Intersections
+
+`.intersection(*)` returns a new `set` with elements common to the original `set` and all `` (in other words, the `set` where everything [intersects][intersection]).
+The operator version of this method is ` & & & ... `
```python
->>> perennials = {'Annatto','Asafetida','Asparagus','Azalea','Winter Savory', 'Blackberries','Broccoli','Curry Leaf',
- 'Fennel','French Sorrel','Fuchsia','Kaffir Lime','Kale','Lavender','Mint','Oranges',
- 'Oregano','Ramps','Roses','Tarragon','Watercress','Wild Bergamot'}
+>>> perennials = {'Annatto','Asafetida','Asparagus','Azalea',
+ 'Winter Savory', 'Broccoli','Curry Leaf','Fennel',
+ 'Kaffir Lime','Kale','Lavender','Mint','Oranges',
+ 'Oregano', 'Tarragon', 'Wild Bergamot'}
->>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram', 'Summer Squash', 'Okra',
- 'Shallots', 'Basil', 'Cilantro', 'Cumin', 'Sunflower', 'Chervil', 'Summer Savory'}
+>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram',
+ 'Summer Squash', 'Okra','Shallots', 'Basil',
+ 'Cilantro', 'Cumin', 'Sunflower', 'Chervil',
+ 'Summer Savory'}
->>> herbs = ['Annatto','Asafetida','Basil','Chervil','Cilantro','Curry Leaf','Fennel','Kaffir Lime',
- 'Lavender','Marjoram','Mint','Oregano','Summer Savory' 'Tarragon','Wild Bergamot',
- 'Wild Celery','Winter Savory']
+>>> herbs = ['Annatto','Asafetida','Basil','Chervil','Cilantro',
+ 'Curry Leaf','Fennel','Kaffir Lime','Lavender',
+ 'Marjoram','Mint','Oregano','Summer Savory'
+ 'Tarragon','Wild Bergamot','Wild Celery',
+ 'Winter Savory']
# Methods will take any iterable as an argument.
>>> perennial_herbs = perennials.intersection(herbs)
->>> perennial_herbs
-{'Mint', 'Annatto', 'Winter Savory', 'Curry Leaf', 'Lavender', 'Fennel',
- 'Oregano', 'Kaffir Lime','Asafetida', 'Wild Bergamot', 'Tarragon'}
+{'Annatto', 'Asafetida', 'Curry Leaf', 'Fennel', 'Kaffir Lime',
+ 'Lavender', 'Mint', 'Oregano', 'Wild Bergamot','Winter Savory'}
+# Operators require both groups be sets.
>>> annuals & set(herbs)
{'Basil', 'Chervil', 'Marjoram', 'Cilantro'}
```
-`.symmetric_difference()` and ` ^ ` return a new `set` that contains elements that are in `` OR ``, but **not in both**.
+### Set Symmetric Differences
+
+`.symmetric_difference()` returns a new `set` that contains elements that are in `` OR ``, but **not in both**.
+The operator version of this method is ` ^ `.
```python
>>> plants_1 = {'🌲','🍈','🌵', '🥑','🌴', '🥭'}
@@ -309,6 +415,8 @@ False
>>> fruit_and_flowers
{'🌸', '🌺', '🍈', '🥑', '🥭','🌻' }
+
+# Operators require both groups be sets.
>>> fruit_and_flowers ^ plants_1
{'🌲', '🌸', '🌴', '🌵','🌺', '🌻'}
@@ -316,7 +424,62 @@ False
{ '🥑', '🌴','🌲', '🌵', '🍈', '🥭'}
```
-[type-set]: https://docs.python.org/3/library/stdtypes.html#set
-[type-frozenset]: https://docs.python.org/3/library/stdtypes.html#frozenset
-[mathematical-sets]: https://en.wikipedia.org/wiki/Set_theory#Basic_concepts_and_notation
+~~~~exercism/note
+
+A symmetric difference of more than two sets will result in a `set` that includes both the elements unique to each `set` AND elements shared between more than two sets in the series (_details in the Wikipedia article on [symmetric difference][symmetric_difference]_).
+
+To obtain only items unique to each `set` in the series, intersections between all 2-set combinations need to be aggregated in a separate step, and removed:
+
+
+```python
+>>> one = {'black pepper','breadcrumbs','celeriac','chickpea flour',
+ 'flour','lemon','parsley','salt','soy sauce',
+ 'sunflower oil','water'}
+
+>>> two = {'black pepper','cornstarch','garlic','ginger',
+ 'lemon juice','lemon zest','salt','soy sauce','sugar',
+ 'tofu','vegetable oil','vegetable stock','water'}
+
+>>> three = {'black pepper','garlic','lemon juice','mixed herbs',
+ 'nutritional yeast', 'olive oil','salt','silken tofu',
+ 'smoked tofu','soy sauce','spaghetti','turmeric'}
+
+>>> four = {'barley malt','bell pepper','cashews','flour',
+ 'fresh basil','garlic','garlic powder', 'honey',
+ 'mushrooms','nutritional yeast','olive oil','oregano',
+ 'red onion', 'red pepper flakes','rosemary','salt',
+ 'sugar','tomatoes','water','yeast'}
+
+>>> intersections = (one & two | one & three | one & four |
+ two & three | two & four | three & four)
+...
+{'black pepper','flour','garlic','lemon juice','nutritional yeast',
+'olive oil','salt','soy sauce', 'sugar','water'}
+
+# The ^ operation will include some of the items in intersections,
+# which means it is not a "clean" symmetric difference - there
+# are overlapping members.
+>>> (one ^ two ^ three ^ four) & intersections
+{'black pepper', 'garlic', 'soy sauce', 'water'}
+
+# Overlapping members need to be removed in a separate step
+# when there are more than two sets that need symmetric difference.
+>>> (one ^ two ^ three ^ four) - intersections
+...
+{'barley malt','bell pepper','breadcrumbs', 'cashews','celeriac',
+ 'chickpea flour','cornstarch','fresh basil', 'garlic powder',
+ 'ginger','honey','lemon','lemon zest','mixed herbs','mushrooms',
+ 'oregano','parsley','red onion','red pepper flakes','rosemary',
+ 'silken tofu','smoked tofu','spaghetti','sunflower oil', 'tofu',
+ 'tomatoes','turmeric','vegetable oil','vegetable stock','yeast'}
+```
+
+[symmetric_difference]: https://en.wikipedia.org/wiki/Symmetric_difference
+~~~~
+
[hashable]: https://docs.python.org/3.7/glossary.html#term-hashable
+[mathematical-sets]: https://en.wikipedia.org/wiki/Set_theory#Basic_concepts_and_notation
+[operator]: https://www.computerhope.com/jargon/o/operator.htm
+[type-frozenset]: https://docs.python.org/3/library/stdtypes.html#frozenset
+[type-set]: https://docs.python.org/3/library/stdtypes.html#set
+[intersection]: https://www.mathgoodies.com/lessons/sets/intersection
diff --git a/concepts/sets/introduction.md b/concepts/sets/introduction.md
index b2eaddc8e6f..5d66b6a8ad8 100644
--- a/concepts/sets/introduction.md
+++ b/concepts/sets/introduction.md
@@ -1,14 +1,28 @@
# Sets
-A [`set`][type-set] is a mutable and _unordered_ collection of _hashable_ objects.
-Items within a `set` are unique, and no duplicates are allowed.
-Like most collections, `sets` can hold any (or multiple) data type(s) -- as long as those types can be [hashed][hashable].
-Sets also come in an _immutable_ [`frozenset`][type-frozenset] flavor.
+A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects.
+Set members must be distinct — duplicate items are not allowed.
+They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_.
+Sets also come in an immutable [`frozensets`][type-frozenset] flavor.
-Like other collection types, `sets` support membership testing through `in`, length calculation through `len()`, shallow copies through `copy()`, & iteration via `for item in `.
-_Unlike_ sequence types (_`string`, `list` & `tuple`_), `sets` are **neither ordered nor indexed**, and _do not support_ slicing, sorting, or other sequence-type behaviors.
+Sets are most commonly used to quickly remove duplicates from other data structures or item groupings.
+They are also used for efficient comparisons when sequencing and duplicate tracking are not needed.
-`sets` are most commonly used to quickly dedupe groups of items.
+Like other collection types (_dictionaries, lists, tuples_), `sets` support:
+- Iteration via `for item in `
+- Membership checking via `in` and `not in`,
+- Length calculation through `len()`, and
+- Shallow copies through `copy()`
+
+`sets` do not support:
+- Indexing of any kind
+- Ordering via sorting or insertion
+- Slicing
+- Concatenation via `+`
+
+
+Checking membership in a `set` has constant time complexity (on average) versus checking membership in a `list` or `string`, where the time complexity grows as the length of the data increases.
+Methods such as `.union()`, `.intersection()`, or `.difference()` also have constant time complexity (on average).
[type-set]: https://docs.python.org/3/library/stdtypes.html#set
[hashable]: https://docs.python.org/3.7/glossary.html#term-hashable
diff --git a/concepts/string-methods/.meta/config.json b/concepts/string-methods/.meta/config.json
index 4bb37fcda42..d429a738102 100644
--- a/concepts/string-methods/.meta/config.json
+++ b/concepts/string-methods/.meta/config.json
@@ -1,5 +1,5 @@
{
"blurb": "The 'str' class provides many useful 'string-methods'. They can be used for cleaning, splitting, translating, or otherwise working with any 'str' object. Because strings are immutable, any functions or methods that operate on a 'str' will return a new instance or copy of the 'str' rather than modifying the original 'str' object.",
"authors": ["kimolivia"],
- "contributors": ["valentin-p", "bethanyg"]
+ "contributors": ["valentin-p", "BethanyG"]
}
diff --git a/concepts/string-methods/about.md b/concepts/string-methods/about.md
index e01cbaf8908..308b89d9d6a 100644
--- a/concepts/string-methods/about.md
+++ b/concepts/string-methods/about.md
@@ -18,6 +18,7 @@ Some of the more commonly used `str` methods include:
Being _immutable_, a `str` object's value in memory cannot change; methods that appear to modify a string return a new copy or instance of that `str` object.
+
[`.endswith()`][str-endswith] returns `True` if the string ends with ``, `False` otherwise.
```python
@@ -41,13 +42,13 @@ There may also be [locale][locale] rules in place for a language or character se
```python
->>> man_in_hat_th = 'ู้ชายในหมวก'
->>> man_in_hat_ru = 'mужчина в шляпе'
+>>> man_in_hat_th = 'ผู้ชายใส่หมวก'
+>>> man_in_hat_ru = 'мужчина в шляпе'
>>> man_in_hat_ko = '모자를 쓴 남자'
>>> man_in_hat_en = 'the man in the hat.'
>>> man_in_hat_th.title()
-'ผู้ชายในหมวก'
+'ผู้ชายใส่หมวก'
>>> man_in_hat_ru.title()
'Мужчина В Шляпе'
diff --git a/concepts/string-methods/links.json b/concepts/string-methods/links.json
index 2e6fc8460f3..c153ea44531 100644
--- a/concepts/string-methods/links.json
+++ b/concepts/string-methods/links.json
@@ -1,18 +1,18 @@
[
{
"url": "https://docs.python.org/3/library/stdtypes.html#string-methods",
- "description": "string methods"
+ "description": "Python Documentation: string methods"
},
{
"url": "https://docs.python.org/3/library/stdtypes.html#common-sequence-operations",
- "description": "common sequence operations"
+ "description": "Python Documentation: common sequence operations"
},
{
"url": "https://realpython.com/python-strings/",
- "description": "strings and character data in Python"
+ "description": "Real Python: strings and character data in Python"
},
{
"url": "https://www.programiz.com/python-programming/string",
- "description": "more string operations and functions"
+ "description": "Programiz: more string operations and functions"
}
]
diff --git a/concepts/strings/about.md b/concepts/strings/about.md
index 0107f6e70f0..064c4c11bcb 100644
--- a/concepts/strings/about.md
+++ b/concepts/strings/about.md
@@ -9,7 +9,7 @@ The Python docs also provide a very detailed [unicode HOWTO][unicode how-to] tha
Strings implement all [common sequence operations][common sequence operations] and can be iterated through using `for item in ` or `for index, item in enumerate()` syntax.
Individual code points (_strings of length 1_) can be referenced by `0-based index` number from the left, or `-1-based index` number from the right.
-Strings can be concatenated with `+`, or via `.join()`, split via `.split()`, and offer multiple formatting and assembly options.
+Strings can be concatenated with `+`, or via `.join()`, split via `.split()`, and offer multiple formatting, assembly, and templating options.
A `str` literal can be declared via single `'` or double `"` quotes. The escape `\` character is available as needed.
@@ -168,12 +168,12 @@ sentence = word + " " + "means" + " " + number + " in " + language + "."
"дев'ять means nine in Ukrainian."
```
-If a `list`, `tuple`, `set` or other collection of individual strings needs to be combined into a single `str`, [`.join()`][str-join], is a better option:
+If a `list`, `tuple`, `set` or other collection of individual strings needs to be combined into a single `str`, [`.join()`][str-join] is a better option:
```python
# str.join() makes a new string from the iterables elements.
->>> chickens = ["hen", "egg", "rooster"]
+>>> chickens = ["hen", "egg", "rooster"] # Lists are iterable.
>>> ' '.join(chickens)
'hen egg rooster'
@@ -183,6 +183,34 @@ If a `list`, `tuple`, `set` or other collection of individual strings needs to b
>>> ' 🌿 '.join(chickens)
'hen 🌿 egg 🌿 rooster'
+
+
+# Any iterable can be used as input.
+>>> flowers = ("rose", "daisy", "carnation") # Tuples are iterable.
+>>> '*-*'.join(flowers)
+'rose*-*daisy*-*carnation'
+
+>>> flowers = {"rose", "daisy", "carnation"} # Sets are iterable, but output order is not guaranteed.
+>>> '*-*'.join(flowers)
+'rose*-*carnation*-*daisy'
+
+>>> phrase = "This is my string" # Strings are iterable, but be careful!
+>>> '..'.join(phrase)
+'T..h..i..s.. ..i..s.. ..m..y.. ..s..t..r..i..n..g'
+
+
+# Separators are inserted **between** elements, but can be any string (including spaces).
+# This can be exploited for interesting effects.
+>>> under_words = ['under', 'current', 'sea', 'pin', 'dog', 'lay']
+>>> separator = ' ⤴️ under' # Note the leading space, but no trailing space.
+>>> separator.join(under_words)
+'under ⤴️ undercurrent ⤴️ undersea ⤴️ underpin ⤴️ underdog ⤴️ underlay'
+
+# The separator can be composed different ways, as long as the result is a string.
+>>> upper_words = ['upper', 'crust', 'case', 'classmen', 'most', 'cut']
+>>> separator = ' 🌟 ' + upper_words[0] # This becomes one string, similar to ' ⤴️ under'.
+>>> separator.join(upper_words)
+ 'upper 🌟 uppercrust 🌟 uppercase 🌟 upperclassmen 🌟 uppermost 🌟 uppercut'
```
Strings support all [common sequence operations][common sequence operations].
@@ -194,7 +222,9 @@ Indexes _with_ items can be iterated through in a loop via `for index, item in e
>>> exercise = 'လေ့ကျင့်'
-# Note that there are more code points than perceived glyphs or characters
+# Note that there are more code points than perceived glyphs or characters.
+# Care should be used when iterating over languages that use
+# combining characters, or when dealing with emoji.
>>> for code_point in exercise:
... print(code_point)
...
diff --git a/concepts/tuples/about.md b/concepts/tuples/about.md
index bc0e81462e5..ea8179e2d8a 100644
--- a/concepts/tuples/about.md
+++ b/concepts/tuples/about.md
@@ -98,7 +98,7 @@ Other data structures can be included as `tuple` elements, including other `tupl
>>> nested_data_structures = ({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird"))
({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird"))
->>> nested_data_structures_1 : (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird"))
+>>> nested_data_structures_1 = (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird"))
(["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird"))
```
@@ -219,11 +219,11 @@ Additionally, users can adapt a [`dataclass`][dataclass] to provide similar name
[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
[dataclass pros and cons]: https://stackoverflow.com/questions/51671699/data-classes-vs-typing-namedtuple-primary-use-cases
[dataclass]: https://docs.python.org/3/library/dataclasses.html
-[dict]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/dict.md
+[dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
[hashability]: https://docs.python.org/3/glossary.html#hashable
-[list]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/list.md
+[list]: https://docs.python.org/3/library/stdtypes.html#list
[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types
[namedtuple]: https://docs.python.org/3/library/collections.html#collections.namedtuple
[sequence]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range
-[set]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/set.md
-[tuple]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/tuple.md
\ No newline at end of file
+[set]: https://docs.python.org/3/library/stdtypes.html#set
+[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
diff --git a/concepts/tuples/introduction.md b/concepts/tuples/introduction.md
index 9ab6042e694..4271336863e 100644
--- a/concepts/tuples/introduction.md
+++ b/concepts/tuples/introduction.md
@@ -7,6 +7,6 @@ The elements of a tuple can be iterated over using the `for item in ` con
If both element index and value are needed, `for index, item in enumerate()` can be used.
Like any sequence, elements within `tuples` can be accessed via _bracket notation_ using a `0-based index` number from the left or a `-1-based index` number from the right.
-[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
-[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types
\ No newline at end of file
+[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types
+[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
\ No newline at end of file
diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md
index 1cec2f92ec9..d4b9168ad13 100644
--- a/concepts/unpacking-and-multiple-assignment/about.md
+++ b/concepts/unpacking-and-multiple-assignment/about.md
@@ -129,9 +129,9 @@ ValueError: too many values to unpack (expected 1)
### Unpacking a list/tuple with `*`
-When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the "leftover" values.
+When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture "leftover" values.
This is clearer than slicing the `list`/`tuple` (_which in some situations is less readable_).
-For example, we can extract the first element and then assign the remaining values into a new `list` without the first element:
+For example, we can extract the first element and pack the remaining values into a new `list` without the first element:
```python
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
@@ -169,7 +169,7 @@ We can also use `*` in deep unpacking:
### Unpacking a dictionary
-[Unpacking a dictionary][packing and unpacking] is a bit different than unpacking a `list`/`tuple`.
+[Unpacking a dictionary][packing and unpacking] is a bit different from unpacking a `list`/`tuple`.
Iteration over dictionaries defaults to the **keys**.
So when unpacking a `dict`, you can only unpack the **keys** and not the **values**:
@@ -180,7 +180,7 @@ So when unpacking a `dict`, you can only unpack the **keys** and not the **value
"apple"
```
-If you want to unpack the values then you can use the `values()` method:
+If you want to unpack the values then you can use the `.values()` method:
```python
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
@@ -189,9 +189,9 @@ If you want to unpack the values then you can use the `values()` method:
6
```
-If both **keys** and **values** are needed, use the `items()` method.
-Using `items()` will generate tuples with **key-value** pairs.
-This is because of [`dict.items()` generates an iterable with key-value `tuples`][items].
+If both **keys** and **values** are needed, use the [`.items()`][items] method.
+`.items()` generates an [iterable view][view-objects] containing **key-value** pairs.
+These can be unpacked into a `tuple`:
```python
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
@@ -222,12 +222,20 @@ This will pack all the values into a `list`/`tuple`.
>>> combined_fruits
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")
-# If the * operator is used on the left side of "=" the result is a list
+# If the * operator is used on the left side of "=" the result is a list.
+# Note the trailing comma.
>>> *combined_fruits_too, = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
+
+# A list literal can be used instead, but might not be as readable.
+>>> [*combined_fruits_too] = *fruits, *more_fruits
+>>> combined_fruits_too
+['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
```
+For more details on the use of `*` and `**`, check out [PEP 3132][pep-3132] and [PEP 448][pep-448].
+
### Packing a dictionary with `**`
Packing a dictionary is done by using the `**` operator.
@@ -356,8 +364,8 @@ numbers = [1, 2, 3]
1
```
-Using `*` unpacking with the `zip()` function is another common use case.
-Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the values from each `iterable` grouped:
+Using `*` unpacking with the [`zip()` built-in][zip] is another common use case.
+The `zip()` function takes multiple iterables and returns a `list` of `tuples` with the values from each `iterable` grouped:
```python
>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
@@ -367,8 +375,12 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the
```
[args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/
-[items]: https://www.geeksforgeeks.org/python-dictionary-items-method/
+[items]: https://docs.python.org/3/library/stdtypes.html#dict.items
[multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/
[packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/
+[pep-448]: https://peps.python.org/pep-0448/
+[pep-3132]: https://peps.python.org/pep-3132/
[sorting algorithms]: https://realpython.com/sorting-algorithms-python/
[unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp
+[view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views
+[zip]: https://docs.python.org/3/library/functions.html#zip
diff --git a/config.json b/config.json
index ec35a0a9c33..ff5fad2c81e 100644
--- a/config.json
+++ b/config.json
@@ -150,7 +150,7 @@
"uuid": "5ac0c40c-4038-47b8-945b-8480e4a3f44c",
"concepts": ["dict-methods"],
"prerequisites": ["dicts"],
- "status": "wip"
+ "status": "beta"
},
{
"slug": "locomotive-engineer",
@@ -193,7 +193,7 @@
"uuid": "3ba3fc89-3e1b-48a5-aff0-5aeaba8c8810",
"concepts": ["generators"],
"prerequisites": ["conditionals", "dicts", "lists", "loops", "classes"],
- "status": "wip"
+ "status": "beta"
},
{
"slug": "log-levels",
@@ -206,7 +206,6 @@
"comprehensions",
"loops",
"sequences",
- "string-formatting",
"string-methods",
"tuples"
],
@@ -238,57 +237,83 @@
"difficulty": 1
},
{
- "slug": "reverse-string",
- "name": "Reverse String",
- "uuid": "d39f86fe-db56-461c-8a93-d87058af8366",
- "practices": [],
- "prerequisites": [
- "basics",
- "bools",
- "conditionals",
- "lists",
- "list-methods",
- "loops",
- "strings"
- ],
+ "slug": "leap",
+ "name": "Leap",
+ "uuid": "b6acda85-5f62-4d9c-bb4f-42b7a360355a",
+ "practices": ["bools"],
+ "prerequisites": ["basics", "bools", "numbers"],
"difficulty": 1
},
{
- "slug": "resistor-color",
- "name": "Resistor Color",
- "uuid": "d17bee9c-e803-4745-85ea-864f255fb04e",
- "practices": ["lists"],
- "prerequisites": ["strings", "lists"],
+ "slug": "triangle",
+ "name": "Triangle",
+ "uuid": "f0bc144f-3226-4e53-93ee-e60316b29e31",
+ "practices": ["bools"],
+ "prerequisites": ["basics", "bools", "numbers"],
"difficulty": 1
},
{
- "slug": "two-fer",
- "name": "Two Fer",
- "uuid": "4177de10-f767-4306-b45d-5e9c08ef4753",
- "practices": ["string-formatting", "function-arguments"],
- "prerequisites": ["basics"],
+ "slug": "grains",
+ "name": "Grains",
+ "uuid": "a24e6d34-9952-44f4-a0cd-02c7fedb4875",
+ "practices": ["numbers"],
+ "prerequisites": ["basics", "numbers"],
"difficulty": 1
},
{
- "slug": "leap",
- "name": "Leap",
- "uuid": "b6acda85-5f62-4d9c-bb4f-42b7a360355a",
- "practices": ["bools"],
- "prerequisites": ["basics", "bools", "numbers"],
+ "slug": "armstrong-numbers",
+ "name": "Armstrong Numbers",
+ "uuid": "d9ceb246-b518-42b9-9fa3-112e25c7ecd8",
+ "practices": ["numbers"],
+ "prerequisites": ["basics", "numbers"],
"difficulty": 1
},
{
- "slug": "resistor-color-duo",
- "name": "Resistor Color Duo",
- "uuid": "089f06a6-0759-479c-8c00-d699525a1e22",
- "practices": ["list-methods"],
- "prerequisites": [
- "basics",
- "bools",
- "lists",
- "list-methods",
- "numbers"
- ],
+ "slug": "collatz-conjecture",
+ "name": "Collatz Conjecture",
+ "uuid": "33f689ee-1d9c-4908-a71c-f84bff3510df",
+ "practices": ["numbers"],
+ "prerequisites": ["basics", "numbers"],
+ "difficulty": 1
+ },
+ {
+ "slug": "bob",
+ "name": "Bob",
+ "uuid": "009a80e2-7901-4d3b-9af2-cdcbcc0b49ae",
+ "practices": ["conditionals"],
+ "prerequisites": ["basics", "conditionals"],
+ "difficulty": 1
+ },
+ {
+ "slug": "raindrops",
+ "name": "Raindrops",
+ "uuid": "82d82e32-cb30-4119-8862-d019563dd1e3",
+ "practices": ["conditionals"],
+ "prerequisites": ["basics", "numbers", "conditionals", "bools"],
+ "difficulty": 1
+ },
+ {
+ "slug": "darts",
+ "name": "Darts",
+ "uuid": "cb581e2c-66ab-4221-9884-44bacb7c4ebe",
+ "practices": ["comparisons"],
+ "prerequisites": ["basics", "numbers", "comparisons", "conditionals"],
+ "difficulty": 1
+ },
+ {
+ "slug": "perfect-numbers",
+ "name": "Perfect Numbers",
+ "uuid": "c23ae7a3-3095-4608-8720-ee9ce8938f26",
+ "practices": ["comparisons"],
+ "prerequisites": ["basics", "bools", "conditionals", "numbers"],
+ "difficulty": 1
+ },
+ {
+ "slug": "reverse-string",
+ "name": "Reverse String",
+ "uuid": "d39f86fe-db56-461c-8a93-d87058af8366",
+ "practices": ["sequences"],
+ "prerequisites": ["basics", "bools", "conditionals", "strings"],
"difficulty": 1
},
{
@@ -308,37 +333,19 @@
"difficulty": 1
},
{
- "slug": "grains",
- "name": "Grains",
- "uuid": "a24e6d34-9952-44f4-a0cd-02c7fedb4875",
- "practices": ["numbers"],
- "prerequisites": ["basics", "numbers"],
- "difficulty": 1
- },
- {
- "slug": "hamming",
- "name": "Hamming",
- "uuid": "8648fa0c-d85f-471b-a3ae-0f8c05222c89",
- "practices": [
- "generator-expressions",
- "raising-and-handling-errors",
- "sequences"
- ],
- "prerequisites": [
- "basics",
- "loops",
- "lists",
- "conditionals",
- "numbers"
- ],
+ "slug": "isbn-verifier",
+ "name": "ISBN Verifier",
+ "uuid": "7961c852-c87a-44b0-b152-efea3ac8555c",
+ "practices": ["strings"],
+ "prerequisites": ["basics", "bools", "conditionals", "strings"],
"difficulty": 1
},
{
- "slug": "bob",
- "name": "Bob",
- "uuid": "009a80e2-7901-4d3b-9af2-cdcbcc0b49ae",
- "practices": ["conditionals"],
- "prerequisites": ["basics", "conditionals"],
+ "slug": "rotational-cipher",
+ "name": "Rotational Cipher",
+ "uuid": "4c408aab-80b9-475d-9c06-b01cd0fcd08f",
+ "practices": ["strings"],
+ "prerequisites": ["basics", "conditionals", "numbers", "strings"],
"difficulty": 1
},
{
@@ -357,49 +364,68 @@
"difficulty": 1
},
{
- "slug": "armstrong-numbers",
- "name": "Armstrong Numbers",
- "uuid": "d9ceb246-b518-42b9-9fa3-112e25c7ecd8",
- "practices": ["numbers"],
- "prerequisites": ["basics", "numbers"],
+ "slug": "resistor-color",
+ "name": "Resistor Color",
+ "uuid": "d17bee9c-e803-4745-85ea-864f255fb04e",
+ "practices": ["lists"],
+ "prerequisites": ["strings", "lists"],
"difficulty": 1
},
{
- "slug": "etl",
- "name": "ETL",
- "uuid": "a3b24ef2-303a-494e-8804-e52a67ef406b",
- "practices": ["dicts"],
- "prerequisites": ["dicts"],
+ "slug": "resistor-color-duo",
+ "name": "Resistor Color Duo",
+ "uuid": "089f06a6-0759-479c-8c00-d699525a1e22",
+ "practices": ["lists"],
+ "prerequisites": ["basics", "bools", "lists", "numbers", "strings"],
"difficulty": 1
},
{
- "slug": "darts",
- "name": "Darts",
- "uuid": "cb581e2c-66ab-4221-9884-44bacb7c4ebe",
- "practices": ["comparisons"],
- "prerequisites": ["basics", "numbers", "comparisons", "conditionals"],
+ "slug": "resistor-color-trio",
+ "name": "Resistor Color Trio",
+ "uuid": "f987a5b7-96f2-49c2-99e8-aef30d23dd58",
+ "practices": ["list-methods"],
+ "prerequisites": [
+ "basics",
+ "bools",
+ "lists",
+ "list-methods",
+ "numbers",
+ "strings",
+ "comparisons"
+ ],
"difficulty": 1
},
{
- "slug": "raindrops",
- "name": "Raindrops",
- "uuid": "82d82e32-cb30-4119-8862-d019563dd1e3",
- "practices": ["conditionals"],
- "prerequisites": ["basics", "numbers", "conditionals", "bools"],
+ "slug": "resistor-color-expert",
+ "name": "Resistor Color Expert",
+ "uuid": "8a738365-0efa-444f-9466-a757ddaddcdb",
+ "practices": ["list-methods"],
+ "prerequisites": [
+ "basics",
+ "bools",
+ "lists",
+ "list-methods",
+ "numbers",
+ "strings",
+ "comparisons"
+ ],
"difficulty": 1
},
{
- "slug": "sum-of-multiples",
- "name": "Sum of Multiples",
- "uuid": "6e0caa0a-6a1a-4f03-bf0f-e07711f4b069",
- "practices": ["sets"],
+ "slug": "secret-handshake",
+ "name": "Secret Handshake",
+ "uuid": "0d5b2a0e-31ff-4c8c-a155-0406f7dca3ae",
+ "practices": ["list-methods"],
"prerequisites": [
"basics",
+ "bools",
"conditionals",
+ "list-methods",
"lists",
"loops",
"numbers",
- "sets"
+ "string-methods",
+ "strings"
],
"difficulty": 1
},
@@ -407,183 +433,161 @@
"slug": "anagram",
"name": "Anagram",
"uuid": "43eaf8bd-0b4d-4ea9-850a-773f013325ef",
- "practices": ["list-comprehensions"],
+ "practices": ["list-methods"],
"prerequisites": [
"basics",
"bools",
"conditionals",
- "lists",
"list-methods",
+ "lists",
"loops",
- "strings",
- "string-methods"
+ "string-methods",
+ "strings"
],
"difficulty": 1
},
{
- "slug": "difference-of-squares",
- "name": "Difference of Squares",
- "uuid": "913b6099-d75a-4c27-8243-476081752c31",
- "practices": ["numbers"],
- "prerequisites": ["basics", "numbers"],
+ "slug": "house",
+ "name": "House",
+ "uuid": "7c2e93ae-d265-4481-b583-a496608c6031",
+ "practices": [],
+ "prerequisites": [
+ "basics",
+ "lists",
+ "list-methods",
+ "loops",
+ "strings",
+ "string-methods"
+ ],
"difficulty": 1
},
{
- "slug": "flatten-array",
- "name": "Flatten Array",
- "uuid": "07481204-fe88-4aa2-995e-d40d1ae15070",
- "practices": ["lists"],
+ "slug": "binary-search",
+ "name": "Binary Search",
+ "uuid": "a8288e93-93c5-4e0f-896c-2a376f6f6e5e",
+ "practices": ["loops"],
"prerequisites": [
"basics",
+ "bools",
"conditionals",
- "strings",
"lists",
- "loops"
+ "list-methods",
+ "loops",
+ "strings",
+ "string-methods"
],
"difficulty": 1
},
{
- "slug": "perfect-numbers",
- "name": "Perfect Numbers",
- "uuid": "c23ae7a3-3095-4608-8720-ee9ce8938f26",
- "practices": ["comparisons"],
- "prerequisites": ["basics", "bools", "conditionals", "numbers"],
- "difficulty": 1
- },
- {
- "slug": "gigasecond",
- "name": "Gigasecond",
- "uuid": "22606e91-57f3-44cf-ab2d-94f6ee6402e8",
- "practices": [],
- "prerequisites": ["classes"],
- "difficulty": 1
- },
- {
- "slug": "isbn-verifier",
- "name": "ISBN Verifier",
- "uuid": "7961c852-c87a-44b0-b152-efea3ac8555c",
- "practices": ["strings"],
- "prerequisites": ["basics", "bools", "conditionals", "strings"],
- "difficulty": 1
- },
- {
- "slug": "space-age",
- "name": "Space Age",
- "uuid": "f8303c4d-bbbb-495b-b61b-0f617f7c9a13",
- "practices": ["dicts"],
+ "slug": "hamming",
+ "name": "Hamming",
+ "uuid": "8648fa0c-d85f-471b-a3ae-0f8c05222c89",
+ "practices": [
+ "generator-expressions",
+ "raising-and-handling-errors",
+ "sequences"
+ ],
"prerequisites": [
"basics",
- "bools",
- "dicts",
"lists",
- "list-methods",
"loops",
+ "conditionals",
"numbers"
],
"difficulty": 1
},
{
- "slug": "collatz-conjecture",
- "name": "Collatz Conjecture",
- "uuid": "33f689ee-1d9c-4908-a71c-f84bff3510df",
- "practices": ["numbers"],
- "prerequisites": ["basics", "numbers"],
- "difficulty": 1
- },
- {
- "slug": "secret-handshake",
- "name": "Secret Handshake",
- "uuid": "0d5b2a0e-31ff-4c8c-a155-0406f7dca3ae",
- "practices": ["list-methods"],
+ "slug": "flatten-array",
+ "name": "Flatten Array",
+ "uuid": "07481204-fe88-4aa2-995e-d40d1ae15070",
+ "practices": [],
"prerequisites": [
"basics",
- "bools",
"conditionals",
+ "strings",
"lists",
"list-methods",
- "numbers",
- "strings",
- "string-methods"
+ "loops"
],
"difficulty": 1
},
{
- "slug": "wordy",
- "name": "Wordy",
- "uuid": "af50bb9a-e400-49ce-966f-016c31720be1",
- "practices": ["string-methods"],
- "prerequisites": [
- "basics",
- "lists",
- "loops",
- "strings",
- "string-methods",
- "numbers"
- ],
+ "slug": "difference-of-squares",
+ "name": "Difference of Squares",
+ "uuid": "913b6099-d75a-4c27-8243-476081752c31",
+ "practices": [],
+ "prerequisites": ["basics", "numbers", "loops"],
"difficulty": 1
},
{
- "slug": "triangle",
- "name": "Triangle",
- "uuid": "f0bc144f-3226-4e53-93ee-e60316b29e31",
- "practices": ["bools"],
- "prerequisites": ["basics", "bools", "numbers"],
+ "slug": "list-ops",
+ "name": "List Ops",
+ "uuid": "818c6472-b734-4ff4-8016-ce540141faec",
+ "practices": [],
+ "prerequisites": ["conditionals", "lists", "list-methods", "loops"],
"difficulty": 1
},
{
- "slug": "house",
- "name": "House",
- "uuid": "7c2e93ae-d265-4481-b583-a496608c6031",
- "practices": ["loops"],
+ "slug": "etl",
+ "name": "ETL",
+ "uuid": "a3b24ef2-303a-494e-8804-e52a67ef406b",
+ "practices": ["dicts"],
+ "prerequisites": ["dicts"],
+ "difficulty": 1
+ },
+ {
+ "slug": "space-age",
+ "name": "Space Age",
+ "uuid": "f8303c4d-bbbb-495b-b61b-0f617f7c9a13",
+ "practices": ["dicts"],
"prerequisites": [
"basics",
+ "bools",
+ "dicts",
"lists",
"list-methods",
"loops",
- "strings",
- "string-methods"
+ "numbers"
],
"difficulty": 1
},
{
- "slug": "rotational-cipher",
- "name": "Rotational Cipher",
- "uuid": "4c408aab-80b9-475d-9c06-b01cd0fcd08f",
- "practices": ["strings"],
- "prerequisites": ["basics", "conditionals", "numbers", "strings"],
- "difficulty": 1
- },
- {
- "slug": "binary-search",
- "name": "Binary Search",
- "uuid": "a8288e93-93c5-4e0f-896c-2a376f6f6e5e",
- "practices": ["loops"],
+ "slug": "sum-of-multiples",
+ "name": "Sum of Multiples",
+ "uuid": "6e0caa0a-6a1a-4f03-bf0f-e07711f4b069",
+ "practices": ["sets"],
"prerequisites": [
"basics",
- "bools",
"conditionals",
"lists",
- "list-methods",
"loops",
- "strings",
- "string-methods"
+ "numbers",
+ "sets"
],
"difficulty": 1
},
{
- "slug": "list-ops",
- "name": "List Ops",
- "uuid": "818c6472-b734-4ff4-8016-ce540141faec",
- "practices": ["list-methods"],
- "prerequisites": ["conditionals", "lists", "list-methods", "loops"],
+ "slug": "gigasecond",
+ "name": "Gigasecond",
+ "uuid": "22606e91-57f3-44cf-ab2d-94f6ee6402e8",
+ "practices": [],
+ "prerequisites": ["classes"],
"difficulty": 1
},
{
- "slug": "acronym",
- "name": "Acronym",
- "uuid": "038c7f7f-02f6-496f-9e16-9372621cc4cd",
- "practices": ["regular-expressions"],
- "prerequisites": ["basics", "loops", "strings", "string-methods"],
+ "slug": "two-fer",
+ "name": "Two Fer",
+ "uuid": "4177de10-f767-4306-b45d-5e9c08ef4753",
+ "practices": ["function-arguments"],
+ "prerequisites": ["basics", "sets"],
+ "difficulty": 1
+ },
+ {
+ "slug": "square-root",
+ "name": "Square Root",
+ "uuid": "c32f994a-1080-4f05-bf88-051975a75d64",
+ "practices": ["numbers"],
+ "prerequisites": ["basics", "numbers", "conditionals", "loops"],
"difficulty": 2
},
{
@@ -595,123 +599,150 @@
"difficulty": 2
},
{
- "slug": "protein-translation",
- "name": "Protein Translation",
- "uuid": "c89243f3-703e-4fe0-8e43-f200eedf2825",
- "practices": ["loops"],
+ "slug": "matching-brackets",
+ "name": "Matching Brackets",
+ "uuid": "45229a7c-6703-4240-8287-16645881a043",
+ "practices": ["conditionals"],
"prerequisites": [
"basics",
+ "bools",
"conditionals",
"lists",
+ "list-methods",
"loops",
- "strings",
- "string-methods"
+ "strings"
],
"difficulty": 2
},
{
- "slug": "square-root",
- "name": "Square Root",
- "uuid": "c32f994a-1080-4f05-bf88-051975a75d64",
- "practices": ["numbers"],
+ "slug": "sublist",
+ "name": "Sublist",
+ "uuid": "cc5eb848-09bc-458c-8fb6-3a17687cb4eb",
+ "practices": ["comparisons"],
"prerequisites": [
"basics",
- "numbers",
+ "bools",
"conditionals",
- "loops"
+ "comparisons",
+ "lists"
],
"difficulty": 2
},
{
- "slug": "scrabble-score",
- "name": "Scrabble Score",
- "uuid": "d081446b-f26b-41a2-ab7f-dd7f6736ecfe",
- "practices": ["regular-expressions"],
+ "slug": "atbash-cipher",
+ "name": "Atbash Cipher",
+ "uuid": "02b91a90-244d-479e-a039-0e1d328c0be9",
+ "practices": ["string-methods"],
"prerequisites": [
"basics",
- "lists",
+ "conditionals",
"loops",
- "dicts",
+ "lists",
+ "list-methods",
"strings",
"string-methods"
],
"difficulty": 2
},
{
- "slug": "atbash-cipher",
- "name": "Atbash Cipher",
- "uuid": "02b91a90-244d-479e-a039-0e1d328c0be9",
- "practices": ["string-methods"],
+ "slug": "diamond",
+ "name": "Diamond",
+ "uuid": "a7bc6837-59e4-46a1-89a2-a5aa44f5e66e",
+ "practices": ["lists"],
"prerequisites": [
"basics",
+ "bools",
"conditionals",
- "loops",
"lists",
- "list-methods",
+ "loops",
"strings",
"string-methods"
],
"difficulty": 2
},
{
- "slug": "resistor-color-trio",
- "name": "Resistor Color Trio",
- "uuid": "f987a5b7-96f2-49c2-99e8-aef30d23dd58",
- "practices": ["list-methods"],
+ "slug": "protein-translation",
+ "name": "Protein Translation",
+ "uuid": "c89243f3-703e-4fe0-8e43-f200eedf2825",
+ "practices": [],
"prerequisites": [
"basics",
- "bools",
+ "conditionals",
"lists",
- "numbers",
+ "loops",
"strings",
- "comparisons"
+ "string-methods"
],
"difficulty": 2
},
{
- "slug": "word-count",
- "name": "Word Count",
- "uuid": "04316811-0bc3-4377-8ff5-5a300ba41d61",
- "practices": ["dicts"],
+ "slug": "prime-factors",
+ "name": "Prime Factors",
+ "uuid": "41dd9178-76b4-4f78-b71a-b5ff8d12645b",
+ "practices": [],
"prerequisites": [
"basics",
+ "conditionals",
+ "lists",
+ "list-methods",
"loops",
+ "numbers"
+ ],
+ "difficulty": 2
+ },
+ {
+ "slug": "say",
+ "name": "Say",
+ "uuid": "2f86ce8e-47c7-4858-89fc-e7729feb0f2f",
+ "practices": [],
+ "prerequisites": [
+ "basics",
+ "conditionals",
+ "dicts",
+ "lists",
+ "loops",
+ "numbers",
"strings",
- "string-methods",
- "dicts"
+ "string-methods"
],
"difficulty": 2
},
{
- "slug": "proverb",
- "name": "Proverb",
- "uuid": "9fd94229-f974-45bb-97ea-8bfe484f6eb3",
- "practices": ["unpacking-and-multiple-assignment"],
- "prerequisites": ["dicts",
- "unpacking-and-multiple-assignment"],
+ "slug": "acronym",
+ "name": "Acronym",
+ "uuid": "038c7f7f-02f6-496f-9e16-9372621cc4cd",
+ "practices": ["regular-expressions"],
+ "prerequisites": ["basics", "loops", "strings", "string-methods"],
"difficulty": 2
},
{
- "slug": "yacht",
- "name": "Yacht",
- "uuid": "22f937e5-52a7-4956-9dde-61c985251a6b",
- "practices": ["bools"],
- "prerequisites": ["basics", "bools", "numbers"],
+ "slug": "series",
+ "name": "Series",
+ "uuid": "aa4c2e85-b8f8-4309-9708-d8ff805054c2",
+ "practices": ["sequences"],
+ "prerequisites": [
+ "basics",
+ "conditionals",
+ "lists",
+ "loops",
+ "numbers",
+ "strings"
+ ],
"difficulty": 2
},
{
- "slug": "robot-name",
- "name": "Robot Name",
- "uuid": "bf30b17f-6b71-4bb5-815a-88f8181b89ae",
- "practices": [],
+ "slug": "run-length-encoding",
+ "name": "Run-Length Encoding",
+ "uuid": "505e7bdb-e18d-45fd-9849-0bf33492efd9",
+ "practices": ["iteration", "regular-expressions"],
"prerequisites": [
"basics",
"bools",
"conditionals",
- "classes",
"lists",
+ "list-methods",
"loops",
- "sets",
+ "numbers",
"strings",
"string-methods"
],
@@ -721,7 +752,7 @@
"slug": "nth-prime",
"name": "Nth Prime",
"uuid": "a20924d2-fe6d-4714-879f-3239feb9d2f2",
- "practices": [],
+ "practices": ["generators"],
"prerequisites": [
"basics",
"bools",
@@ -753,76 +784,125 @@
"difficulty": 2
},
{
- "slug": "series",
- "name": "Series",
- "uuid": "aa4c2e85-b8f8-4309-9708-d8ff805054c2",
- "practices": ["sequences"],
+ "slug": "roman-numerals",
+ "name": "Roman Numerals",
+ "uuid": "bffe2007-717a-44ee-b628-b9c86a5001e8",
+ "practices": ["tuples"],
"prerequisites": [
"basics",
"conditionals",
"lists",
+ "list-methods",
"loops",
"numbers",
- "strings"
+ "strings",
+ "string-methods",
+ "tuples"
],
"difficulty": 2
},
{
- "slug": "phone-number",
- "name": "Phone Number",
- "uuid": "f384c6f8-987d-41a2-b504-e50506585526",
- "practices": ["raising-and-handling-errors", "string-formatting"],
+ "slug": "word-count",
+ "name": "Word Count",
+ "uuid": "04316811-0bc3-4377-8ff5-5a300ba41d61",
+ "practices": ["dicts"],
"prerequisites": [
"basics",
- "classes",
- "lists",
+ "dicts",
"loops",
- "numbers",
"strings",
"string-methods"
],
"difficulty": 2
},
{
- "slug": "matching-brackets",
- "name": "Matching Brackets",
- "uuid": "45229a7c-6703-4240-8287-16645881a043",
- "practices": ["conditionals"],
+ "slug": "scrabble-score",
+ "name": "Scrabble Score",
+ "uuid": "d081446b-f26b-41a2-ab7f-dd7f6736ecfe",
+ "practices": ["regular-expressions"],
+ "prerequisites": [
+ "basics",
+ "dicts",
+ "lists",
+ "loops",
+ "string-methods",
+ "strings"
+ ],
+ "difficulty": 2
+ },
+ {
+ "slug": "proverb",
+ "name": "Proverb",
+ "uuid": "9fd94229-f974-45bb-97ea-8bfe484f6eb3",
+ "practices": ["unpacking-and-multiple-assignment"],
+ "prerequisites": ["dicts", "unpacking-and-multiple-assignment"],
+ "difficulty": 2
+ },
+ {
+ "slug": "luhn",
+ "name": "Luhn",
+ "uuid": "34dde040-672e-472f-bf2e-b87b6f9933c0",
+ "practices": ["classes"],
"prerequisites": [
"basics",
"bools",
"conditionals",
+ "classes",
"lists",
+ "list-methods",
"loops",
- "strings"
+ "strings",
+ "string-methods",
+ "numbers"
],
"difficulty": 2
},
{
- "slug": "say",
- "name": "Say",
- "uuid": "2f86ce8e-47c7-4858-89fc-e7729feb0f2f",
- "practices": ["lists"],
+ "slug": "dnd-character",
+ "name": "D&D Character",
+ "uuid": "58625685-b5cf-4e8a-b3aa-bff54da0689d",
+ "practices": ["classes"],
"prerequisites": [
"basics",
+ "bools",
"conditionals",
+ "classes",
"dicts",
"lists",
- "numbers",
- "strings",
- "string-methods"
+ "list-methods",
+ "loops",
+ "numbers"
],
"difficulty": 2
},
{
- "slug": "queen-attack",
- "name": "Queen Attack",
- "uuid": "b280c252-5320-4e53-8294-1385d564eb02",
+ "slug": "robot-name",
+ "name": "Robot Name",
+ "uuid": "bf30b17f-6b71-4bb5-815a-88f8181b89ae",
"practices": [],
"prerequisites": [
+ "basics",
"bools",
+ "classes",
"conditionals",
+ "lists",
+ "loops",
+ "sets",
+ "string-methods",
+ "strings"
+ ],
+ "difficulty": 2
+ },
+ {
+ "slug": "phone-number",
+ "name": "Phone Number",
+ "uuid": "f384c6f8-987d-41a2-b504-e50506585526",
+ "practices": ["raising-and-handling-errors", "string-formatting"],
+ "prerequisites": [
+ "basics",
"classes",
+ "lists",
+ "loops",
"numbers",
"strings",
"string-methods"
@@ -830,17 +910,14 @@
"difficulty": 2
},
{
- "slug": "run-length-encoding",
- "name": "Run-Length Encoding",
- "uuid": "505e7bdb-e18d-45fd-9849-0bf33492efd9",
- "practices": ["iteration", "regular-expressions"],
+ "slug": "queen-attack",
+ "name": "Queen Attack",
+ "uuid": "b280c252-5320-4e53-8294-1385d564eb02",
+ "practices": [],
"prerequisites": [
- "basics",
"bools",
"conditionals",
- "lists",
- "list-methods",
- "loops",
+ "classes",
"numbers",
"strings",
"string-methods"
@@ -848,54 +925,67 @@
"difficulty": 2
},
{
- "slug": "luhn",
- "name": "Luhn",
- "uuid": "34dde040-672e-472f-bf2e-b87b6f9933c0",
- "practices": ["classes"],
+ "slug": "transpose",
+ "name": "Transpose",
+ "uuid": "dc6e61a2-e9b9-4406-ba5c-188252afbba1",
+ "practices": ["unpacking-and-multiple-assignment"],
"prerequisites": [
"basics",
"bools",
"conditionals",
- "classes",
"lists",
"list-methods",
"loops",
+ "numbers",
"strings",
"string-methods",
- "numbers"
+ "unpacking-and-multiple-assignment"
],
"difficulty": 2
},
{
- "slug": "sublist",
- "name": "Sublist",
- "uuid": "cc5eb848-09bc-458c-8fb6-3a17687cb4eb",
- "practices": ["comparisons"],
- "prerequisites": ["basics", "bools", "conditionals", "comparisons"],
+ "slug": "yacht",
+ "name": "Yacht",
+ "uuid": "22f937e5-52a7-4956-9dde-61c985251a6b",
+ "practices": ["enums"],
+ "prerequisites": ["basics", "bools", "numbers", "classes"],
"difficulty": 2
},
{
- "slug": "diamond",
- "name": "Diamond",
- "uuid": "a7bc6837-59e4-46a1-89a2-a5aa44f5e66e",
- "practices": ["lists"],
+ "slug": "eliuds-eggs",
+ "name": "Eliud's Eggs",
+ "uuid": "356e2d29-7efc-4fa3-bec7-8b61c3e967da",
+ "practices": ["loops"],
+ "prerequisites": [
+ "basics",
+ "lists",
+ "list-methods",
+ "loops",
+ "strings",
+ "string-methods"
+ ],
+ "difficulty": 3
+ },
+ {
+ "slug": "saddle-points",
+ "name": "Saddle Points",
+ "uuid": "71c96c5f-f3b6-4358-a9c6-fc625e2edda2",
+ "practices": ["loops"],
"prerequisites": [
"basics",
- "bools",
"conditionals",
- "dicts",
"lists",
+ "list-methods",
"loops",
- "strings",
- "string-methods"
+ "sets"
],
- "difficulty": 2
+ "difficulty": 3
},
{
- "slug": "transpose",
- "name": "Transpose",
- "uuid": "dc6e61a2-e9b9-4406-ba5c-188252afbba1",
- "practices": ["list-methods"],
+ "slug": "ocr-numbers",
+ "name": "OCR Numbers",
+ "uuid": "98ca48ed-5818-442c-bce1-308c8b3b3b77",
+ "practices": ["loops"],
"prerequisites": [
"basics",
"bools",
@@ -907,89 +997,92 @@
"strings",
"string-methods"
],
- "difficulty": 2
+ "difficulty": 3
},
{
- "slug": "prime-factors",
- "name": "Prime Factors",
- "uuid": "41dd9178-76b4-4f78-b71a-b5ff8d12645b",
- "practices": [],
+ "slug": "robot-simulator",
+ "name": "Robot Simulator",
+ "uuid": "ca474c47-57bb-4995-bf9a-b6937479de29",
+ "practices": ["class-customization", "decorators", "dict-methods"],
"prerequisites": [
"basics",
"conditionals",
+ "classes",
+ "dicts",
"lists",
- "list-methods",
"loops",
- "numbers"
+ "numbers",
+ "tuples"
],
- "difficulty": 2
+ "difficulty": 3
},
{
- "slug": "dnd-character",
- "name": "D&D Character",
- "uuid": "58625685-b5cf-4e8a-b3aa-bff54da0689d",
- "practices": ["classes"],
+ "slug": "grade-school",
+ "name": "Grade School",
+ "uuid": "aadde1a8-ed7a-4242-bfc0-6dddfd382cf3",
+ "practices": ["collections", "dict-methods"],
"prerequisites": [
"basics",
- "bools",
- "conditionals",
- "classes",
"dicts",
"lists",
"list-methods",
- "loops",
- "numbers"
+ "classes"
],
- "difficulty": 2
+ "difficulty": 3
},
{
- "slug": "roman-numerals",
- "name": "Roman Numerals",
- "uuid": "bffe2007-717a-44ee-b628-b9c86a5001e8",
- "practices": ["tuples"],
+ "slug": "sieve",
+ "name": "Sieve",
+ "uuid": "ad0192e6-7742-4922-a53e-791e25eb9ba3",
+ "practices": ["sets"],
"prerequisites": [
"basics",
"conditionals",
- "tuples",
"lists",
"list-methods",
"loops",
"numbers",
- "strings",
- "string-methods"
+ "sets"
],
- "difficulty": 2
+ "difficulty": 3
},
{
- "slug": "simple-cipher",
- "name": "Simple Cipher",
- "uuid": "09b2f396-00d7-4d89-ac47-5c444e00dd99",
- "practices": [],
+ "slug": "pythagorean-triplet",
+ "name": "Pythagorean Triplet",
+ "uuid": "7b53865e-a981-46e0-8e47-6f8e1f3854b3",
+ "practices": ["sets"],
"prerequisites": [
"basics",
+ "bools",
"conditionals",
- "classes",
"lists",
"list-methods",
"loops",
"numbers",
- "strings",
- "string-methods"
+ "sets"
],
"difficulty": 3
},
{
- "slug": "resistor-color-expert",
- "name": "Resistor Color Expert",
- "uuid": "8a738365-0efa-444f-9466-a757ddaddcdb",
- "practices": ["list-methods"],
+ "slug": "circular-buffer",
+ "name": "Circular Buffer",
+ "uuid": "77ee3b0e-a4e9-4257-bcfc-ff2c8f1477ab",
+ "practices": [
+ "class-inheritance",
+ "function-arguments",
+ "user-defined-errors"
+ ],
"prerequisites": [
"basics",
"bools",
+ "conditionals",
+ "classes",
+ "dicts",
"lists",
+ "list-methods",
+ "loops",
"numbers",
- "strings",
- "comparisons"
+ "strings"
],
"difficulty": 3
},
@@ -1010,21 +1103,6 @@
],
"difficulty": 3
},
- {
- "slug": "allergies",
- "name": "Allergies",
- "uuid": "83627e35-4689-4d9b-a81b-284c2c084466",
- "practices": [],
- "prerequisites": [
- "basics",
- "conditionals",
- "classes",
- "dicts",
- "loops",
- "numbers"
- ],
- "difficulty": 3
- },
{
"slug": "high-scores",
"name": "High Scores",
@@ -1034,16 +1112,15 @@
"difficulty": 3
},
{
- "slug": "crypto-square",
- "name": "Crypto Square",
- "uuid": "e8685468-8006-480f-87c6-6295700def38",
- "practices": ["list-comprehensions"],
+ "slug": "kindergarten-garden",
+ "name": "Kindergarten Garden",
+ "uuid": "42a2916c-ef03-44ac-b6d8-7eda375352c2",
+ "practices": ["classes"],
"prerequisites": [
- "conditionals",
- "lists",
- "list-methods",
+ "basics",
+ "classes",
+ "dicts",
"loops",
- "numbers",
"strings",
"string-methods"
],
@@ -1053,7 +1130,7 @@
"slug": "bottle-song",
"name": "Bottle Song",
"uuid": "70bec74a-0677-40c9-b9c9-cbcc49a2eae4",
- "practices": ["generators"],
+ "practices": ["list-methods"],
"prerequisites": [
"basics",
"conditionals",
@@ -1069,154 +1146,117 @@
"difficulty": 3
},
{
- "slug": "poker",
- "name": "Poker",
- "uuid": "dcc0ee26-e384-4bd4-8c4b-613fa0bb8188",
- "practices": ["functions", "higher-order-functions"],
+ "slug": "allergies",
+ "name": "Allergies",
+ "uuid": "83627e35-4689-4d9b-a81b-284c2c084466",
+ "practices": [],
"prerequisites": [
"basics",
- "bools",
"conditionals",
"classes",
- "lists",
- "list-methods",
- "loops",
- "numbers"
- ],
- "difficulty": 3
- },
- {
- "slug": "kindergarten-garden",
- "name": "Kindergarten Garden",
- "uuid": "42a2916c-ef03-44ac-b6d8-7eda375352c2",
- "practices": ["classes"],
- "prerequisites": [
- "basics",
- "classes",
"dicts",
"loops",
- "strings",
- "string-methods"
+ "numbers"
],
"difficulty": 3
},
{
- "slug": "saddle-points",
- "name": "Saddle Points",
- "uuid": "71c96c5f-f3b6-4358-a9c6-fc625e2edda2",
- "practices": ["loops"],
+ "slug": "simple-cipher",
+ "name": "Simple Cipher",
+ "uuid": "09b2f396-00d7-4d89-ac47-5c444e00dd99",
+ "practices": [],
"prerequisites": [
"basics",
"conditionals",
+ "classes",
"lists",
"list-methods",
"loops",
- "sets"
+ "numbers",
+ "strings",
+ "string-methods"
],
"difficulty": 3
},
{
- "slug": "robot-simulator",
- "name": "Robot Simulator",
- "uuid": "ca474c47-57bb-4995-bf9a-b6937479de29",
- "practices": ["class-customization", "decorators", "dict-methods"],
+ "slug": "poker",
+ "name": "Poker",
+ "uuid": "dcc0ee26-e384-4bd4-8c4b-613fa0bb8188",
+ "practices": ["functions", "higher-order-functions"],
"prerequisites": [
"basics",
+ "bools",
"conditionals",
"classes",
- "dicts",
"lists",
+ "list-methods",
"loops",
- "numbers",
- "tuples"
+ "numbers"
],
"difficulty": 3
},
{
- "slug": "rectangles",
- "name": "Rectangles",
- "uuid": "4bebdd8d-a032-4993-85c5-7cc74fc89312",
- "practices": ["iteration", "itertools", "sequences"],
+ "slug": "wordy",
+ "name": "Wordy",
+ "uuid": "af50bb9a-e400-49ce-966f-016c31720be1",
+ "practices": ["string-methods"],
"prerequisites": [
"basics",
- "bools",
- "conditionals",
- "classes",
"lists",
- "list-methods",
"loops",
- "numbers",
"strings",
"string-methods",
- "sets",
- "tuples"
+ "numbers"
],
"difficulty": 3
},
{
- "slug": "sieve",
- "name": "Sieve",
- "uuid": "ad0192e6-7742-4922-a53e-791e25eb9ba3",
- "practices": ["sets"],
+ "slug": "crypto-square",
+ "name": "Crypto Square",
+ "uuid": "e8685468-8006-480f-87c6-6295700def38",
+ "practices": ["list-comprehensions"],
"prerequisites": [
- "basics",
"conditionals",
"lists",
"list-methods",
"loops",
"numbers",
- "sets"
+ "strings",
+ "string-methods"
],
"difficulty": 3
},
{
- "slug": "grade-school",
- "name": "Grade School",
- "uuid": "aadde1a8-ed7a-4242-bfc0-6dddfd382cf3",
- "practices": ["collections", "dict-methods"],
- "prerequisites": [
- "basics",
- "dicts",
- "lists",
- "list-methods",
- "classes"
+ "slug": "clock",
+ "name": "Clock",
+ "uuid": "459fda78-851e-4bb0-a416-953528f46bd7",
+ "practices": [
+ "class-composition",
+ "rich-comparisons",
+ "string-formatting"
],
+ "prerequisites": ["basics", "numbers", "strings", "classes"],
"difficulty": 3
},
{
- "slug": "circular-buffer",
- "name": "Circular Buffer",
- "uuid": "77ee3b0e-a4e9-4257-bcfc-ff2c8f1477ab",
- "practices": [
- "class-inheritance",
- "function-arguments",
- "unpacking-and-multiple-assignment",
- "user-defined-errors"
- ],
+ "slug": "rectangles",
+ "name": "Rectangles",
+ "uuid": "4bebdd8d-a032-4993-85c5-7cc74fc89312",
+ "practices": ["iteration", "itertools", "sequences"],
"prerequisites": [
"basics",
"bools",
- "conditionals",
"classes",
- "dicts",
- "lists",
+ "conditionals",
"list-methods",
+ "lists",
"loops",
"numbers",
- "strings"
- ],
- "difficulty": 3
- },
- {
- "slug": "clock",
- "name": "Clock",
- "uuid": "459fda78-851e-4bb0-a416-953528f46bd7",
- "practices": [
- "class-composition",
- "rich-comparisons",
- "string-formatting"
+ "sets",
+ "string-methods",
+ "strings",
+ "tuples"
],
- "prerequisites": ["basics", "numbers", "strings", "classes"],
"difficulty": 3
},
{
@@ -1252,24 +1292,6 @@
],
"difficulty": 3
},
- {
- "slug": "ocr-numbers",
- "name": "OCR Numbers",
- "uuid": "98ca48ed-5818-442c-bce1-308c8b3b3b77",
- "practices": ["loops"],
- "prerequisites": [
- "basics",
- "bools",
- "conditionals",
- "lists",
- "list-methods",
- "loops",
- "numbers",
- "strings",
- "string-methods"
- ],
- "difficulty": 3
- },
{
"slug": "connect",
"name": "Connect",
@@ -1291,85 +1313,116 @@
"difficulty": 3
},
{
- "slug": "pythagorean-triplet",
- "name": "Pythagorean Triplet",
- "uuid": "7b53865e-a981-46e0-8e47-6f8e1f3854b3",
- "practices": ["sets"],
+ "slug": "all-your-base",
+ "name": "All Your Base",
+ "uuid": "a2ff75f9-8b2c-4c4b-975d-913711def9ab",
+ "practices": ["comparisons"],
"prerequisites": [
"basics",
+ "bools",
+ "conditionals",
+ "comparisons",
+ "lists",
+ "numbers"
+ ],
+ "difficulty": 4
+ },
+ {
+ "slug": "swift-scheduling",
+ "name": "Swift Scheduling",
+ "uuid": "ebddfc37-a3fc-4524-bd62-9c70f979713c",
+ "practices": [],
+ "prerequisites": ["basics",
"bools",
"conditionals",
"lists",
"list-methods",
"loops",
"numbers",
- "sets"
+ "strings",
+ "string-methods"
],
- "difficulty": 3
+ "difficulty": 4
},
{
- "slug": "pascals-triangle",
- "name": "Pascals Triangle",
- "uuid": "e1e1c7d7-c1d9-4027-b90d-fad573182419",
- "practices": ["recursion"],
+ "slug": "spiral-matrix",
+ "name": "Spiral Matrix",
+ "uuid": "b0c7cf95-6470-4c1a-8eaa-6775310926a2",
+ "practices": ["lists"],
"prerequisites": [
"basics",
"conditionals",
+ "classes",
+ "dicts",
"lists",
- "numbers"
+ "loops",
+ "numbers",
+ "strings",
+ "string-methods"
],
"difficulty": 4
},
{
- "slug": "grep",
- "name": "Grep",
- "uuid": "ecc97fc6-2e72-4325-9b67-b56c83b13a91",
- "practices": [],
+ "slug": "variable-length-quantity",
+ "name": "Variable Length Quantity",
+ "uuid": "aa4332bd-fc38-47a4-8bff-e1b660798418",
+ "practices": ["list-methods"],
"prerequisites": [
"basics",
"bools",
"conditionals",
"lists",
+ "list-methods",
"loops",
+ "numbers",
"strings",
"string-methods"
],
"difficulty": 4
},
{
- "slug": "minesweeper",
- "name": "Minesweeper",
- "uuid": "7e768b54-4591-4a30-9ddb-66ca13400ca3",
- "practices": ["lists"],
+ "slug": "change",
+ "name": "Change",
+ "uuid": "889df88a-767d-490f-92c4-552d8ec9de34",
+ "practices": ["loops"],
"prerequisites": [
"basics",
"bools",
"conditionals",
"lists",
+ "list-methods",
"loops",
"numbers"
],
"difficulty": 4
},
{
- "slug": "meetup",
- "name": "Meetup",
- "uuid": "a5aff23f-7829-403f-843a-d3312dca31e8",
- "practices": [
- "class-composition",
- "dict-methods",
- "raising-and-handling-errors",
- "user-defined-errors"
+ "slug": "killer-sudoku-helper",
+ "name": "Killer Sudoku Helper",
+ "uuid": "7b16fc93-791b-42a9-8aae-1f78fef2f2f3",
+ "practices": ["list-comprehensions"],
+ "prerequisites": [
+ "conditionals",
+ "lists",
+ "list-methods",
+ "loops",
+ "numbers",
+ "strings",
+ "string-methods"
],
+ "difficulty": 4
+ },
+ {
+ "slug": "flower-field",
+ "name": "Flower Field",
+ "uuid": "0c2751c1-5d2f-499a-81b8-226e5092ea88",
+ "practices": ["lists"],
"prerequisites": [
- "basics",
- "bools",
"conditionals",
- "classes",
- "dicts",
"lists",
"list-methods",
"loops",
+ "numbers",
"strings",
"string-methods"
],
@@ -1379,7 +1432,7 @@
"slug": "rail-fence-cipher",
"name": "Rail Fence Cipher",
"uuid": "6434cc19-1ea3-43dd-9580-72267ec76b80",
- "practices": ["list-methods"],
+ "practices": [],
"prerequisites": [
"basics",
"conditionals",
@@ -1393,11 +1446,13 @@
"difficulty": 4
},
{
- "slug": "killer-sudoku-helper",
- "name": "Killer Sudoku Helper",
- "uuid": "7b16fc93-791b-42a9-8aae-1f78fef2f2f3",
- "practices": ["list-comprehensions"],
+ "slug": "palindrome-products",
+ "name": "Palindrome Products",
+ "uuid": "fa795dcc-d390-4e98-880c-6e8e638485e3",
+ "practices": ["functions", "function-arguments"],
"prerequisites": [
+ "basics",
+ "bools",
"conditionals",
"lists",
"list-methods",
@@ -1415,24 +1470,16 @@
"practices": ["tuples"],
"prerequisites": [
"basics",
- "strings",
- "string-methods",
"dicts",
- "lists",
"list-methods",
+ "lists",
"loops",
+ "string-methods",
+ "strings",
"tuples"
],
"difficulty": 4
},
- {
- "slug": "markdown",
- "name": "Markdown",
- "uuid": "88610b9a-6d3e-4924-a092-6d2f907ed4e2",
- "practices": ["regular-expressions", "functions"],
- "prerequisites": ["lists", "string-methods"],
- "difficulty": 4
- },
{
"slug": "food-chain",
"name": "Food Chain",
@@ -1443,136 +1490,129 @@
"bools",
"conditionals",
"dicts",
- "lists",
"list-methods",
+ "lists",
"loops",
- "strings",
"string-methods",
+ "strings",
"tuples"
],
"difficulty": 4
},
{
- "slug": "palindrome-products",
- "name": "Palindrome Products",
- "uuid": "fa795dcc-d390-4e98-880c-6e8e638485e3",
- "practices": ["functions", "function-arguments"],
+ "slug": "scale-generator",
+ "name": "Scale Generator",
+ "uuid": "8cd58325-61fc-46fd-85f9-425b4c41f3de",
+ "practices": [],
"prerequisites": [
"basics",
"bools",
"conditionals",
- "lists",
+ "dicts",
"list-methods",
+ "lists",
"loops",
- "numbers",
- "strings",
- "string-methods"
+ "string-methods",
+ "strings"
],
"difficulty": 4
},
{
- "slug": "linked-list",
- "name": "Linked List",
- "uuid": "ca7a8b16-e5d5-4211-84f0-2f8e35b4a665",
- "practices": [
- "function-arguments",
- "iterators",
- "none",
- "operator-overloading",
- "rich-comparisons"
- ],
+ "slug": "largest-series-product",
+ "name": "Largest Series Product",
+ "uuid": "21624a3e-6e43-4c0e-94b0-dee5cdaaf2aa",
+ "practices": ["generators"],
"prerequisites": [
"basics",
- "bools",
"conditionals",
- "classes",
"lists",
+ "list-methods",
"loops",
"numbers"
],
"difficulty": 4
},
{
- "slug": "variable-length-quantity",
- "name": "Variable Length Quantity",
- "uuid": "aa4332bd-fc38-47a4-8bff-e1b660798418",
- "practices": ["list-methods"],
- "prerequisites": [
- "basics",
- "bools",
- "conditionals",
- "lists",
- "list-methods",
- "loops",
- "numbers",
- "strings",
- "string-methods"
- ],
+ "slug": "markdown",
+ "name": "Markdown",
+ "uuid": "88610b9a-6d3e-4924-a092-6d2f907ed4e2",
+ "practices": ["regular-expressions", "functions"],
+ "prerequisites": ["lists", "loops", "sets", "string-methods"],
"difficulty": 4
},
{
- "slug": "all-your-base",
- "name": "All Your Base",
- "uuid": "a2ff75f9-8b2c-4c4b-975d-913711def9ab",
- "practices": ["comparisons"],
+ "slug": "meetup",
+ "name": "Meetup",
+ "uuid": "a5aff23f-7829-403f-843a-d3312dca31e8",
+ "practices": [
+ "class-composition",
+ "dict-methods",
+ "raising-and-handling-errors",
+ "user-defined-errors"
+ ],
"prerequisites": [
"basics",
"bools",
+ "classes",
"conditionals",
- "comparisons",
- "numbers"
+ "dicts",
+ "dict-methods",
+ "list-methods",
+ "lists",
+ "loops",
+ "string-methods",
+ "strings"
],
"difficulty": 4
},
{
- "slug": "largest-series-product",
- "name": "Largest Series Product",
- "uuid": "21624a3e-6e43-4c0e-94b0-dee5cdaaf2aa",
- "practices": [
- "functions",
- "higher-order-functions",
- "functional-tools",
- "anonymous-functions"
- ],
+ "slug": "pascals-triangle",
+ "name": "Pascal's Triangle",
+ "uuid": "e1e1c7d7-c1d9-4027-b90d-fad573182419",
+ "practices": ["recursion"],
"prerequisites": [
"basics",
"conditionals",
+ "classes",
"lists",
- "list-methods",
- "loops",
"numbers"
],
"difficulty": 4
},
{
- "slug": "spiral-matrix",
- "name": "Spiral Matrix",
- "uuid": "b0c7cf95-6470-4c1a-8eaa-6775310926a2",
- "practices": ["lists"],
+ "slug": "grep",
+ "name": "Grep",
+ "uuid": "ecc97fc6-2e72-4325-9b67-b56c83b13a91",
+ "practices": ["with-statement"],
"prerequisites": [
"basics",
+ "bools",
"conditionals",
"classes",
- "dicts",
"lists",
"loops",
- "numbers",
"strings",
"string-methods"
],
- "difficulty": 4
- },
- {
- "slug": "change",
- "name": "Change",
- "uuid": "889df88a-767d-490f-92c4-552d8ec9de34",
- "practices": ["loops"],
+ "difficulty": 4
+ },
+ {
+ "slug": "linked-list",
+ "name": "Linked List",
+ "uuid": "ca7a8b16-e5d5-4211-84f0-2f8e35b4a665",
+ "practices": [
+ "function-arguments",
+ "iterators",
+ "none",
+ "operator-overloading",
+ "rich-comparisons"
+ ],
"prerequisites": [
"basics",
"bools",
"conditionals",
+ "classes",
"lists",
- "list-methods",
"loops",
"numbers"
],
@@ -1616,48 +1656,48 @@
"difficulty": 4
},
{
- "slug": "go-counting",
- "name": "Go Counting",
- "uuid": "8a9a437d-c967-4ea3-8ecb-6a9ad4380c03",
+ "slug": "hangman",
+ "name": "Hangman",
+ "uuid": "adad6be5-855d-4d61-b14a-22e468ba5b44",
"practices": [],
"prerequisites": [
"basics",
"bools",
"conditionals",
"classes",
+ "dicts",
"lists",
"list-methods",
"loops",
- "sets",
- "tuples"
+ "numbers",
+ "strings",
+ "string-methods"
],
"difficulty": 4
},
{
- "slug": "hangman",
- "name": "Hangman",
- "uuid": "adad6be5-855d-4d61-b14a-22e468ba5b44",
+ "slug": "go-counting",
+ "name": "Go Counting",
+ "uuid": "8a9a437d-c967-4ea3-8ecb-6a9ad4380c03",
"practices": [],
"prerequisites": [
"basics",
"bools",
"conditionals",
"classes",
- "dicts",
"lists",
"list-methods",
"loops",
- "numbers",
- "strings",
- "string-methods"
+ "sets",
+ "tuples"
],
"difficulty": 4
},
{
- "slug": "scale-generator",
- "name": "Scale Generator",
- "uuid": "8cd58325-61fc-46fd-85f9-425b4c41f3de",
- "practices": ["generators"],
+ "slug": "forth",
+ "name": "Forth",
+ "uuid": "14e1dfe3-a45c-40c1-bf61-2e4f0cca5579",
+ "practices": ["dicts"],
"prerequisites": [
"basics",
"bools",
@@ -1666,25 +1706,27 @@
"lists",
"list-methods",
"loops",
+ "numbers",
"strings",
- "string-methods"
+ "tuples"
],
- "difficulty": 4
+ "difficulty": 5
},
{
- "slug": "knapsack",
- "name": "Knapsack",
- "uuid": "b0301d0b-d97a-4043-bd82-ba1edf8c1b16",
- "practices": ["itertools", "list-comprehensions"],
+ "slug": "binary-search-tree",
+ "name": "Binary Search Tree",
+ "uuid": "df7cd9b9-283a-4466-accf-98c4a7609450",
+ "practices": ["classes"],
"prerequisites": [
"basics",
"bools",
"conditionals",
- "dicts",
+ "classes",
"lists",
"list-methods",
"loops",
- "strings"
+ "strings",
+ "string-methods"
],
"difficulty": 5
},
@@ -1706,15 +1748,15 @@
"difficulty": 5
},
{
- "slug": "forth",
- "name": "Forth",
- "uuid": "14e1dfe3-a45c-40c1-bf61-2e4f0cca5579",
- "practices": ["dicts"],
+ "slug": "bowling",
+ "name": "Bowling",
+ "uuid": "ca970fee-71b4-41e1-a5c3-b23bf574eb33",
+ "practices": [],
"prerequisites": [
"basics",
"bools",
"conditionals",
- "dicts",
+ "classes",
"lists",
"list-methods",
"loops",
@@ -1725,45 +1767,19 @@
"difficulty": 5
},
{
- "slug": "custom-set",
- "name": "Custom Set",
- "uuid": "23a567b5-c184-4e65-9216-df7caba00d75",
- "practices": [
- "class-inheritance",
- "operator-overloading",
- "rich-comparisons"
- ],
+ "slug": "knapsack",
+ "name": "Knapsack",
+ "uuid": "b0301d0b-d97a-4043-bd82-ba1edf8c1b16",
+ "practices": ["itertools", "list-comprehensions"],
"prerequisites": [
"basics",
"bools",
"conditionals",
- "classes",
- "lists",
+ "dicts",
"list-methods",
- "loops",
- "numbers",
- "sets",
- "strings",
- "string-methods"
- ],
- "difficulty": 5
- },
- {
- "slug": "bowling",
- "name": "Bowling",
- "uuid": "ca970fee-71b4-41e1-a5c3-b23bf574eb33",
- "practices": [],
- "prerequisites": [
- "basics",
- "bools",
- "conditionals",
- "classes",
"lists",
- "list-methods",
"loops",
- "numbers",
- "strings",
- "tuples"
+ "strings"
],
"difficulty": 5
},
@@ -1793,33 +1809,39 @@
"difficulty": 5
},
{
- "slug": "zebra-puzzle",
- "name": "Zebra Puzzle",
- "uuid": "7e1d90d5-dbc9-47e0-8e26-c3ff83b73c2b",
- "practices": ["itertools"],
+ "slug": "custom-set",
+ "name": "Custom Set",
+ "uuid": "23a567b5-c184-4e65-9216-df7caba00d75",
+ "practices": [
+ "class-inheritance",
+ "operator-overloading",
+ "rich-comparisons"
+ ],
"prerequisites": [
"basics",
"bools",
"conditionals",
- "dicts",
+ "classes",
"lists",
"list-methods",
"loops",
+ "numbers",
+ "sets",
"strings",
"string-methods"
],
"difficulty": 5
},
{
- "slug": "binary-search-tree",
- "name": "Binary Search Tree",
- "uuid": "df7cd9b9-283a-4466-accf-98c4a7609450",
- "practices": ["classes"],
+ "slug": "zebra-puzzle",
+ "name": "Zebra Puzzle",
+ "uuid": "7e1d90d5-dbc9-47e0-8e26-c3ff83b73c2b",
+ "practices": ["itertools"],
"prerequisites": [
"basics",
"bools",
"conditionals",
- "classes",
+ "dicts",
"lists",
"list-methods",
"loops",
@@ -1836,6 +1858,7 @@
"prerequisites": [
"basics",
"bools",
+ "classes",
"conditionals",
"lists",
"list-methods",
@@ -1850,12 +1873,7 @@
"slug": "word-search",
"name": "Word Search",
"uuid": "dc2917d5-aaa9-43d9-b9f4-a32919fdbe18",
- "practices": [
- "iteration",
- "operator-overloading",
- "rich-comparisons",
- "string-formatting"
- ],
+ "practices": ["iteration"],
"prerequisites": [
"basics",
"bools",
@@ -1871,20 +1889,6 @@
],
"difficulty": 6
},
- {
- "slug": "bank-account",
- "name": "Bank Account",
- "uuid": "83a3ff95-c043-401c-bc2c-547d52344b02",
- "practices": ["enums", "raising-and-handling-errors"],
- "prerequisites": [
- "basics",
- "bools",
- "conditionals",
- "classes",
- "loops"
- ],
- "difficulty": 6
- },
{
"slug": "alphametics",
"name": "Alphametics",
@@ -1893,19 +1897,34 @@
"prerequisites": [
"basics",
"bools",
+ "classes",
"conditionals",
"dicts",
- "lists",
"list-methods",
+ "lists",
"loops",
"numbers",
"sets",
- "strings",
"string-methods",
+ "strings",
"tuples"
],
"difficulty": 6
},
+ {
+ "slug": "bank-account",
+ "name": "Bank Account",
+ "uuid": "83a3ff95-c043-401c-bc2c-547d52344b02",
+ "practices": ["enums", "raising-and-handling-errors"],
+ "prerequisites": [
+ "basics",
+ "bools",
+ "conditionals",
+ "classes",
+ "loops"
+ ],
+ "difficulty": 6
+ },
{
"slug": "react",
"name": "React",
@@ -1954,6 +1973,7 @@
"prerequisites": [
"basics",
"bools",
+ "classes",
"conditionals",
"dicts",
"lists",
@@ -1965,30 +1985,6 @@
],
"difficulty": 6
},
- {
- "slug": "book-store",
- "name": "Book Store",
- "uuid": "4899b2ef-675f-4d14-b68a-1a457de91276",
- "practices": [
- "collections",
- "functools",
- "generator-expressions",
- "other-comprehensions",
- "sets"
- ],
- "prerequisites": [
- "basics",
- "conditionals",
- "dicts",
- "lists",
- "list-methods",
- "loops",
- "tuples",
- "sets",
- "numbers"
- ],
- "difficulty": 7
- },
{
"slug": "dominoes",
"name": "Dominoes",
@@ -2029,6 +2025,31 @@
],
"difficulty": 7
},
+ {
+ "slug": "book-store",
+ "name": "Book Store",
+ "uuid": "4899b2ef-675f-4d14-b68a-1a457de91276",
+ "practices": [
+ "collections",
+ "functools",
+ "generator-expressions",
+ "other-comprehensions",
+ "sets"
+ ],
+ "prerequisites": [
+ "basics",
+ "classes",
+ "conditionals",
+ "dicts",
+ "list-methods",
+ "lists",
+ "loops",
+ "numbers",
+ "sets",
+ "tuples"
+ ],
+ "difficulty": 7
+ },
{
"slug": "sgf-parsing",
"name": "SGF Parsing",
@@ -2135,15 +2156,6 @@
"difficulty": 2,
"status": "deprecated"
},
- {
- "slug": "nucleotide-count",
- "name": "Nucleotide Count",
- "uuid": "105f25ec-7ce2-4797-893e-05e3792ebd91",
- "practices": [],
- "prerequisites": [],
- "difficulty": 2,
- "status": "deprecated"
- },
{
"slug": "binary",
"name": "Binary",
@@ -2180,15 +2192,6 @@
"difficulty": 3,
"status": "deprecated"
},
- {
- "slug": "parallel-letter-frequency",
- "name": "Parallel Letter Frequency",
- "uuid": "da03fca4-4606-48d8-9137-6e40396f7759",
- "practices": [],
- "prerequisites": [],
- "difficulty": 3,
- "status": "deprecated"
- },
{
"slug": "point-mutations",
"name": "Point Mutations",
@@ -2233,9 +2236,18 @@
"prerequisites": [],
"difficulty": 4,
"status": "deprecated"
+ },
+ {
+ "slug": "minesweeper",
+ "name": "Minesweeper",
+ "uuid": "7e768b54-4591-4a30-9ddb-66ca13400ca3",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 4,
+ "status": "deprecated"
}
],
- "foregone": ["lens-person"]
+ "foregone": ["lens-person", "nucleotide-count", "parallel-letter-frequency"]
},
"concepts": [
{
@@ -2335,8 +2347,8 @@
},
{
"uuid": "ba20a459-99ac-4643-b386-8b90e9c94328",
- "slug": "dataclasses-and-namedtuples",
- "name": "Dataclasses And Namedtuples"
+ "slug": "dataclasses",
+ "name": "Dataclasses"
},
{
"uuid": "48ef77af-50f5-466e-a537-bcd016550058",
@@ -2363,6 +2375,11 @@
"slug": "enums",
"name": "Enums"
},
+ {
+ "uuid": "8ac7c6b5-5786-45dd-8ce3-5b139da06471",
+ "slug": "fractions",
+ "name": "Fractions"
+ },
{
"uuid": "26b147e0-2cdc-4325-a6b4-6a2dd5bb69b1",
"slug": "function-arguments",
@@ -2557,6 +2574,21 @@
"uuid": "565f7618-4552-4eb0-b829-d6bacd03deaf",
"slug": "with-statement",
"name": "With Statement"
+ },
+ {
+ "uuid": "af6cad74-50c2-48f4-a6ce-cfeb72548d00",
+ "slug": "random",
+ "name": "Random"
+ },
+ {
+ "uuid": "000e7768-38b9-4904-9ae2-9a4e448f366c",
+ "slug": "fractions",
+ "name": "Fractions"
+ },
+ {
+ "uuid": "e1496136-8d58-4409-9a41-4b6ee4721c6b",
+ "slug": "secrets",
+ "name": "Secrets"
}
],
"key_features": [
diff --git a/config/complexnumbers_template.j2 b/config/complexnumbers_template.j2
new file mode 100644
index 00000000000..d70c866f1c5
--- /dev/null
+++ b/config/complexnumbers_template.j2
@@ -0,0 +1,31 @@
+{%- import "generator_macros.j2" as macros with context -%}
+{{ macros.canonical_ref() }}
+
+import math
+{{ macros.header(imports=imports, ignore=ignore) }}
+
+{%- macro test_cases_recursive(cases) -%}
+{% for case in cases -%}
+{% if "cases" in case %}
+ # {{ case["description"] }}
+ {{ test_cases_recursive(case["cases"]) }}
+{% else %}
+ {{ test_case(case) }}
+{% endif -%}
+{% endfor -%}
+{% endmacro %}
+
+{% if not additional_tests -%}
+{%- macro additional_tests() -%}
+ {{ test_cases_recursive(additional_cases) }}
+{% endmacro %}
+{%- endif %}
+
+class {{ exercise | camel_case }}Test(unittest.TestCase):
+ {{ test_cases_recursive(cases) }}
+
+ {% if additional_cases | length -%}
+ # Additional tests for this track
+ {{ additional_tests() }}
+ {%- endif %}
+
diff --git a/config/generator_macros.j2 b/config/generator_macros.j2
index 37d8e45a800..b1927552927 100644
--- a/config/generator_macros.j2
+++ b/config/generator_macros.j2
@@ -16,7 +16,6 @@
{%- endmacro %}
{% macro header(imports=[], ignore=[]) -%}
-{{ canonical_ref() }}
import unittest
@@ -38,14 +37,6 @@ from {{ exercise | to_snake }} import ({% if imports -%}
return self.assertRaisesRegex(exception, r".+")
{%- endmacro %}
-{% macro footer(_has_error_case) -%}
-{% if has_error_case or _has_error_case %}
-{{ utility() }}
-{% endif %}
-if __name__ == '__main__':
- unittest.main()
-{%- endmacro %}
-
{% macro empty_set(set, list, class_name) -%}
{%- if list|length > 0 -%}
{{ set }} = {{ class_name }}({{ list }})
diff --git a/config/master_template.j2 b/config/master_template.j2
index 4fe2b9798b5..774771fd5d1 100644
--- a/config/master_template.j2
+++ b/config/master_template.j2
@@ -1,4 +1,6 @@
{%- import "generator_macros.j2" as macros with context -%}
+{{ macros.canonical_ref() }}
+
{{ macros.header(imports=imports, ignore=ignore) }}
{%- macro test_cases_recursive(cases) -%}
@@ -25,4 +27,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
# Additional tests for this track
{{ additional_tests() }}
{%- endif %}
-{{ macros.footer() }}
diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md
index 04f9c899349..7be6910710d 100644
--- a/docs/INSTALLATION.md
+++ b/docs/INSTALLATION.md
@@ -18,7 +18,7 @@ Some quick links into the documentation by operating system:
We recommend reviewing some of the methods outlined in the Real Python article [Installing Python][installing-python] or the articles by Brett Cannon linked above.
-Exercism tests and tooling currently support `3.7` - `3.11.2` (_tests_) and [`Python 3.11.2`][311-new-features] (_tooling_).
+Exercism tests and tooling currently support `3.7` - `3.11.5` (_tests_) and [`Python 3.11.5`][311-new-features] (_tooling_).
Exceptions to this support are noted where they occur.
Most of the exercises will work with `Python 3.6+`, or even earlier versions.
But we don't guarantee support for versions not listed under [Active Python Releases][active-python-releases].
diff --git a/docs/LEARNING.md b/docs/LEARNING.md
index 50a3259eed7..d71a95455cc 100644
--- a/docs/LEARNING.md
+++ b/docs/LEARNING.md
@@ -6,7 +6,7 @@ Python is (_as [Wikipedia says][wikipython]_), a *general-purpose and high-level
It is especially good at 'gluing' different systems and programs together.
-And we think the best way to lean is to _play_ and to _practice_ with coding projects big and small - or with small problems like the ones here on exercism!
+And we think the best way to learn is to _play_ and to _practice_ with coding projects big and small - or with small problems like the ones here on exercism!
Below you will find some additional jumping-off places to start your learning journey, recommended by our community.
diff --git a/docs/TESTS.md b/docs/TESTS.md
index 242555371f3..8c01c524816 100644
--- a/docs/TESTS.md
+++ b/docs/TESTS.md
@@ -10,8 +10,7 @@ You should also install the following `pytest` plugins:
We also recommend using the code linting program [pylint][pylint], as it is part of our automated feedback on the website and can be a very useful static code analysis tool.
For ease-of-use, the [pytest-pylint][pytest-pylint] plugin for `pytest` will allow you to run `pylint` via `pytest` on the command line.
-Pylint configuration can be a bit much, so this [tutorial from pycqa.org][tutorial from pycqa.org] can be helpful for getting started, as can this overview of [Code Quality: Tools and Best Practices][Code Quality: Tools and Best Practices] from Real Python.
-
+Pylint configuration can be a bit much, so this [tutorial from pylint.readthedocs.io][tutorial from pylint.readthedocs.io] can be helpful for getting started, as can this overview of [Code Quality: Tools and Best Practices][Code Quality: Tools and Best Practices] from Real Python.
## Installing pytest
@@ -25,44 +24,55 @@ Please adjust the install commands below accordingly.
To install `pytest` in a virtual environment, ensure the environment **is activated** prior to executing commands.
Otherwise, the `pytest` installation will be global.
-
#### Windows
```powershell
PS C:\Users\foobar> py -m pip install pytest pytest-cache pytest-subtests pytest-pylint
-Successfully installed pytest-7.2.2 ...
+Successfully installed pytest-8.3.3 ...
```
#### Linux / MacOS
```bash
$ python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint
-Successfully installed pytest-7.2.2 ...
+Successfully installed pytest-8.3.3 ...
```
-
To check if installation was successful:
```bash
$ python3 -m pytest --version
-pytest 7.2.2
+pytest 8.3.3
```
## Running the tests
-To run the tests, go to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_).
+To run the tests, go to the folder where the exercise is stored using `cd` in your terminal (_replace `` below with your path_).
```bash
-$ cd {exercise-folder-location}
+$ cd
```
+
+
+~~~~exercism/note
+ `` or most things inside angle brackets denote a **_placeholder value_**.
+A normal path or file name should be written _without_ any brackets.
+
+
+For example: `/Users/janedoe/exercism/python/exercises/concept/chaitanas-colossal-coaster` (on *nix systems), `C:\Users\janedoe\exercism\python\exercises\practice\hello-world\` (on Windows), `myFolder` or `my_file.py`.
+~~~~
+
+
+
+
The file you will want to run usually ends in `_test.py`.
This file contains the tests for the exercise solution, and are the same tests that run on the website when a solution is uploaded.
-Next, run the following command in your terminal, replacing `{exercise_test.py}` with the location/name of the test file:
+Next, run the following command in your terminal, replacing `` with the location/name of the test file:
```bash
-$ python3 -m pytest -o markers=task {exercise_test.py}
+$ python3 -m pytest -o markers=task
==================== 7 passed in 0.08s ====================
```
@@ -85,22 +95,21 @@ More information on pytest marks can be found in the `pytest` documentation on [
_More information on customizing pytest configurations can be found in the pytest documentation on [configuration file formats][configuration file formats]_
-
### Test Failures
When tests fail, `pytest` prints the text of each failed test, along with the expected and actual `return` values of each to the terminal.
Below is an generic example of a failed test:
```bash
-$(my_venv) python3 -m pytest -o markers=task {exercise_test.py}
+$(my_venv) python3 -m pytest -o markers=task
=================== FAILURES ====================
______________ name_of_failed_test ______________
-# Test code inside of {exercise_test.py} that failed.
+# Test code inside of that failed.
...
E TypeOfError: ReturnedValue != ExpectedValue
-exercise_test.py:{line_of_failed_test}: TypeOfError
+exercise_test.py:: TypeOfError
============ short test summary info ============
FAILED exercise_test.py::ExerciseTest::name_of_failed_test
========== 1 failed, 2 passed in 0.13s ==========
@@ -110,13 +119,12 @@ FAILED exercise_test.py::ExerciseTest::name_of_failed_test
If you really want to be specific about what pytest returns on your screen, here are some handy command-line arguments that allows you to configure its behavior.
-
#### Return All Details [`-v`]
Adding the `-v` (_verbose_) flag will return both environment information and a test summary in addition to test failures:
```bash
-$(my_venv) python3 -m pytest -o markers=task -v exercises//
+$(my_venv) python3 -m pytest -o markers=task -v exercises//
======================================== test session starts ===========================================
platform darwin -- Python 3.9.0, pytest-6.2.5, -- /usr/local/envs/my_env/bin/python3
@@ -125,7 +133,7 @@ metadata: {'Python': '3.9.0', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Pa
rootdir: /Users//exercism/python, configfile: pytest.ini
plugins: subtests-0.5.0, pylint-0.18.0
-collected 5 items
+collected 5 items
exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_one FAILED [ 20%]
exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_two FAILED
@@ -149,7 +157,7 @@ Using the `-x` flag will run the tests as normal, but stop the test run upon the
This helps when you want to debug a single task or test failure at a time:
```bash
-$(my_venv) python3 -m pytest -o markers=task -x exercises//
+$(my_venv) python3 -m pytest -o markers=task -x exercises//
=================== FAILURES ====================
_______________ example_test_foo ________________
@@ -166,7 +174,6 @@ FAILED example_test.py::ExampleTest::example_test_foo
The `pytest-cache` plugin remembers which tests failed last time you ran `pytest`, so using the flag `--ff` will tell `pytest` to run previously failed tests **first**, then continue with the remainder of the tests.
This might speed up your testing if you are making a lot of smaller fixes around one particular task or set of inputs.
-
```bash
$(my_venv) python3 -m pytest -o markers=task --ff
==================== 7 passed in 503s ====================
@@ -192,7 +199,6 @@ This will test your solution.
When `pytest` encounters a failed test, the program will stop and tell you which test failed.
When you make fixes and run the test again, `pytest` will first run the previous test that failed, then continue with the remaining tests.
-
### Using PDB, the Python Debugger, with pytest
If you want to "debug like a pro", you can use the `--pdb` argument after the `pytest` command, and drop into the built-in [Python debugger][pdb], `PDB`.
@@ -206,13 +212,11 @@ When a test fails, dropping into `PDB` will allow you to step through your code
More details on the `PDB` module can be found in the [Python documentation on PDB][pdb].
Additionally, the [pytest docs on PDB][pytest-pdb] and [this guide from Real Python](https://realpython.com/python-debugging-pdb/) are extremely helpful.
-
## Extending your IDE
If you'd like to extend your IDE with some tools that will help you with testing and improving your code, check the [tools](./tools) page.
We explore multiple IDEs, editors and some useful extensions for linting and debugging there.
-
## Additional information
### Adding python to your PATH
@@ -225,10 +229,10 @@ If you do not know where you have installed Python, run the following command in
```bash
$ python3 -c "import os, sys; print(os.path.dirname(sys.executable))"
-{python_directory}
+
```
-The _returned_ directory is where your current active Python version is installed, in this section it is referred to as `{python_directory}`.
+The _returned_ directory is where your current active Python version is installed, in this section it is referred to as ``.
#### Windows
@@ -241,36 +245,35 @@ Then find the `Path` variable in your _User variables_, select it, and click `Ed

-Then add a new line, as shown in the picture, replacing `{python_directory}` with your Python installation's directory:
+Then add a new line, as shown in the picture, replacing `` with your Python installation's directory:

-
#### MacOS/Linux
The below should work for most Linux and MacOS flavors with a `bash` shell.
Commands may vary by Linux distro, and whether a `fish` or `zsh` shell is used.
-Replace `{python_directory}` with the output of `python3 -c "import os, sys; print(os.path.dirname(sys.executable))"`
+Replace `` with the output of `python3 -c "import os, sys; print(os.path.dirname(sys.executable))"`
```bash
-export PATH=”$PATH:{python_directory}}”
+export PATH="$PATH:"
```
[Code Quality: Tools and Best Practices]: https://realpython.com/python-code-quality/
[Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html
[configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats
[marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks
-[pdb]: https://docs.python.org/3.9/library/pdb.html
+[pdb]: https://docs.python.org/3.11/library/pdb.html
[pip]: https://pip.pypa.io/en/stable/getting-started/
[psf-installer]: https://www.python.org/downloads/
[pylint]: https://pylint.pycqa.org/en/latest/user_guide/
-[pytest-cache]:http://pythonhosted.org/pytest-cache/
+[pytest-cache]: http://pythonhosted.org/pytest-cache/
[pytest-pdb]: https://docs.pytest.org/en/6.2.x/usage.html#dropping-to-pdb-python-debugger-on-failures
-[pytest-pylint]:https://github.com/carsongee/pytest-pylint
-[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests
+[pytest-pylint]: https://github.com/carsongee/pytest-pylint
+[pytest-subtests]: https://github.com/pytest-dev/pytest-subtests
[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini
[python command line]: https://docs.python.org/3/using/cmdline.html
[python-m-pip]: https://snarky.ca/why-you-should-use-python-m-pip/
[quick-and-dirty]: https://snarky.ca/a-quick-and-dirty-guide-on-how-to-install-packages-for-python/
-[tutorial from pycqa.org]: https://pylint.pycqa.org/en/v2.17.2/tutorial.html
+[tutorial from pylint.readthedocs.io]: https://pylint.readthedocs.io/en/v2.17.7/tutorial.html
[working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers
diff --git a/docs/TOOLS.md b/docs/TOOLS.md
index 20ce04ded09..bacb8626aaa 100644
--- a/docs/TOOLS.md
+++ b/docs/TOOLS.md
@@ -30,7 +30,10 @@ If you have an editor, IDE, tool, or plugin recommendation, we encourage you to
Before you start exploring, make sure that you have a recent version of Python installed.
-The Exercism platform currently supports `Python 3.7 - 3.11.2` (_exercises and tests_) and `Python 3.11.2` (_tooling_).
+The Exercism web platform currently supports `Python 3.7 - 3.11.5` (_exercises and tests_) and `Python 3.11.5` (_tooling_).
+Our online test runner currently uses `pytest 7.2.2` and `pytest-subtests 0.11.0`.
+Our online analyzer uses `pylint 2.17.7`.
+Using different versions of `Python`, `pytest`, or `pylint` locally might give you different results than the website.
For more information, please refer to [Installing Python locally][Installing Python locally].
diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md
index 5914429db9d..b7a4b010b91 100644
--- a/docs/TRACEBACKS.md
+++ b/docs/TRACEBACKS.md
@@ -415,7 +415,7 @@ def halve_and_quadruple(num):
print((num / 2) * 4)
return (num / 2) * 4
-What the `print` calls revealed is that we used `/` when we should have used `//`, the [floor divison operator][floor divison operator].
+What the `print` calls revealed is that we used `/` when we should have used `//`, the [floor division operator][floor division operator].
## Logging
@@ -487,7 +487,7 @@ AssertionError: divisor must not be 0
```
-If we start reading the Traceback at the bottom (as we should) we quickly see the problem is that `0` should not be passsed as the `divisor`.
+If we start reading the Traceback at the bottom (as we should) we quickly see the problem is that `0` should not be passed as the `divisor`.
`assert` can also be used to test that a value is of the expected type:
diff --git a/docs/config.json b/docs/config.json
index ccaea6ac247..8474ac07ad9 100644
--- a/docs/config.json
+++ b/docs/config.json
@@ -14,20 +14,6 @@
"title": "How to learn Python",
"blurb": "An overview of how to get started from scratch with Python."
},
- {
- "uuid": "7a2e1a4f-1fa8-4327-b700-5af101fcdc89",
- "slug": "test-driven-development",
- "path": "docs/TDD.md",
- "title": "Test Driven Development",
- "blurb": "An overview of Test Driven Development."
- },
- {
- "uuid": "3829fdff-47ac-4283-ae47-a5db1dbce956",
- "slug": "traceback-reading",
- "path": "docs/TRACEBACKS.md",
- "title": "How to read tracebacks",
- "blurb": "An overview of how to read Python tracebacks for debugging."
- },
{
"uuid": "8666f259-de7d-4928-ae6f-15ff6fe6bb74",
"slug": "tests",
@@ -35,6 +21,13 @@
"title": "Testing on the Python track",
"blurb": "Learn how to test your Python exercises on Exercism."
},
+ {
+ "uuid": "7a2e1a4f-1fa8-4327-b700-5af101fcdc89",
+ "slug": "test-driven-development",
+ "path": "docs/TDD.md",
+ "title": "Test Driven Development",
+ "blurb": "An overview of Test Driven Development."
+ },
{
"uuid": "f18d3af2-fb71-41c6-984a-32b3ba86bf02",
"slug": "problem-solving",
@@ -42,6 +35,13 @@
"title": "Problem Solving Resources",
"blurb": "Learn some general problem-solving techniques to help you with programming."
},
+ {
+ "uuid": "3829fdff-47ac-4283-ae47-a5db1dbce956",
+ "slug": "traceback-reading",
+ "path": "docs/TRACEBACKS.md",
+ "title": "How to read tracebacks",
+ "blurb": "An overview of how to read Python tracebacks for debugging."
+ },
{
"uuid": "73ced51d-76d0-45af-952c-8a6d7b5f3f7a",
"slug": "resources",
diff --git a/exercises/concept/black-jack/.docs/instructions.md b/exercises/concept/black-jack/.docs/instructions.md
index 199fabc900e..e95c5fadb9f 100644
--- a/exercises/concept/black-jack/.docs/instructions.md
+++ b/exercises/concept/black-jack/.docs/instructions.md
@@ -67,7 +67,7 @@ Define the `value_of_ace(, )` function with parameters `card
Your function will have to decide if the upcoming ace will get a value of 1 or a value of 11, and return that value.
Remember: the value of the hand with the ace needs to be as high as possible _without_ going over 21.
-**Hint**: if we already have an ace in hand then its value would be 11.
+**Hint**: if we already have an ace in hand, then the value for the upcoming ace would be 1.
```python
>>> value_of_ace('6', 'K')
@@ -79,13 +79,15 @@ Remember: the value of the hand with the ace needs to be as high as possible _wi
## 4. Determine a "Natural" or "Blackjack" Hand
-If the first two cards a player is dealt are an ace (`A`) and a ten-card (10, `K`, `Q` or `J`), giving a score of 21 in two cards, the hand is considered a `natural` or `blackjack`.
+If a player is dealt an ace (`A`) and a ten-card (10, `K`, `Q`, or `J`) as their first two cards, then the player has a score of 21.
+This is known as a **blackjack** hand.
+
Define the `is_blackjack(, )` function with parameters `card_one` and `card_two`, which are a pair of cards.
-Determine if the two-card hand is a `blackjack`, and return the boolean `True` if it is, `False` otherwise.
+Determine if the two-card hand is a **blackjack**, and return the boolean `True` if it is, `False` otherwise.
**Note** : The score _calculation_ can be done in many ways.
-But if possible, we'd like you to check if there is an ace and a ten-card **_in_** the hand (or at a certain position), as opposed to _summing_ the hand values.
+But if possible, we'd like you to check if there is an ace and a ten-card **_in_** the hand (_or at a certain position_), as opposed to _summing_ the hand values.
```python
>>> is_blackjack('A', 'K')
diff --git a/exercises/concept/black-jack/.docs/introduction.md b/exercises/concept/black-jack/.docs/introduction.md
index 207229359dd..ec19d2f71f7 100644
--- a/exercises/concept/black-jack/.docs/introduction.md
+++ b/exercises/concept/black-jack/.docs/introduction.md
@@ -59,7 +59,7 @@ True
```
Any ordered comparison of a number to a `NaN` (_not a number_) type is `False`.
-A confusing side-effect of Python's `NaN` definition is that `NaN` never compares equal to `NaN`.
+A confusing side effect of Python's `NaN` definition is that `NaN` never compares equal to `NaN`.
```python
>>> x = float('NaN')
@@ -186,7 +186,6 @@ The operators `in` and `not in` test for _membership_.
For string and bytes types, ` in ` is `True` _**if and only if**_ `