From 909f0d9c9187d74252f544ba6350399c24d772e4 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 24 Nov 2025 20:21:56 -0800 Subject: [PATCH 1/4] Add secret-handshake exercise implementation Initial commit for the secret-handshake Exercism exercise. Includes configuration, metadata, help and readme files, the implementation in secret_handshake.py, and corresponding unit tests in secret_handshake_test.py. --- secret-handshake/.exercism/config.json | 35 ++++++ secret-handshake/.exercism/metadata.json | 1 + secret-handshake/HELP.md | 130 ++++++++++++++++++++++ secret-handshake/README.md | 95 ++++++++++++++++ secret-handshake/secret_handshake.py | 51 +++++++++ secret-handshake/secret_handshake_test.py | 49 ++++++++ 6 files changed, 361 insertions(+) create mode 100644 secret-handshake/.exercism/config.json create mode 100644 secret-handshake/.exercism/metadata.json create mode 100644 secret-handshake/HELP.md create mode 100644 secret-handshake/README.md create mode 100644 secret-handshake/secret_handshake.py create mode 100644 secret-handshake/secret_handshake_test.py diff --git a/secret-handshake/.exercism/config.json b/secret-handshake/.exercism/config.json new file mode 100644 index 0000000..7972aa1 --- /dev/null +++ b/secret-handshake/.exercism/config.json @@ -0,0 +1,35 @@ +{ + "authors": [ + "betegelse" + ], + "contributors": [ + "behrtam", + "cmccandless", + "crsmi", + "Dog", + "Grociu", + "ikhadykin", + "kytrinyx", + "N-Parsons", + "pheanex", + "sjakobi", + "sjwarner-bp", + "subkrish", + "tqa236", + "yawpitch" + ], + "files": { + "solution": [ + "secret_handshake.py" + ], + "test": [ + "secret_handshake_test.py" + ], + "example": [ + ".meta/example.py" + ] + }, + "blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.", + "source": "Bert, in Mary Poppins", + "source_url": "https://www.imdb.com/title/tt0058331/quotes/?item=qt0437047" +} diff --git a/secret-handshake/.exercism/metadata.json b/secret-handshake/.exercism/metadata.json new file mode 100644 index 0000000..02484b8 --- /dev/null +++ b/secret-handshake/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"secret-handshake","id":"07c42c9a6f124b9db60e31d3b1896971","url":"https://exercism.org/tracks/python/exercises/secret-handshake","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/secret-handshake/HELP.md b/secret-handshake/HELP.md new file mode 100644 index 0000000..a2fef07 --- /dev/null +++ b/secret-handshake/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit secret_handshake.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/secret-handshake/README.md b/secret-handshake/README.md new file mode 100644 index 0000000..fffc74c --- /dev/null +++ b/secret-handshake/README.md @@ -0,0 +1,95 @@ +# Secret Handshake + +Welcome to Secret Handshake on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Introduction + +You are starting a secret coding club with some friends and friends-of-friends. +Not everyone knows each other, so you and your friends have decided to create a secret handshake that you can use to recognize that someone is a member. +You don't want anyone who isn't in the know to be able to crack the code. + +You've designed the code so that one person says a number between 1 and 31, and the other person turns it into a series of actions. + +## Instructions + +Your task is to convert a number between 1 and 31 to a sequence of actions in the secret handshake. + +The sequence of actions is chosen by looking at the rightmost five digits of the number once it's been converted to binary. +Start at the right-most digit and move left. + +The actions for each number place are: + +```plaintext +00001 = wink +00010 = double blink +00100 = close your eyes +01000 = jump +10000 = Reverse the order of the operations in the secret handshake. +``` + +Let's use the number `9` as an example: + +- 9 in binary is `1001`. +- The digit that is farthest to the right is 1, so the first action is `wink`. +- Going left, the next digit is 0, so there is no double-blink. +- Going left again, the next digit is 0, so you leave your eyes open. +- Going left again, the next digit is 1, so you jump. + +That was the last digit, so the final code is: + +```plaintext +wink, jump +``` + +Given the number 26, which is `11010` in binary, we get the following actions: + +- double blink +- jump +- reverse actions + +The secret handshake for 26 is therefore: + +```plaintext +jump, double blink +``` + +~~~~exercism/note +If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. + +[intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa +~~~~ + +To keep things simple (and to let you focus on the important part of this exercise), your function will receive its inputs as binary strings: + +``` +>>> commands("00011") +["wink", "double blink"] +``` + +## Source + +### Created by + +- @betegelse + +### Contributed to by + +- @behrtam +- @cmccandless +- @crsmi +- @Dog +- @Grociu +- @ikhadykin +- @kytrinyx +- @N-Parsons +- @pheanex +- @sjakobi +- @sjwarner-bp +- @subkrish +- @tqa236 +- @yawpitch + +### Based on + +Bert, in Mary Poppins - https://www.imdb.com/title/tt0058331/quotes/?item=qt0437047 \ No newline at end of file diff --git a/secret-handshake/secret_handshake.py b/secret-handshake/secret_handshake.py new file mode 100644 index 0000000..e6a637b --- /dev/null +++ b/secret-handshake/secret_handshake.py @@ -0,0 +1,51 @@ +""" +Secret Handshake. + +Convert a number's binary representation (up to five bits) to a +sequence of actions in the secret handshake. + +The sequence of actions is determined by inspecting the rightmost five +binary digits (least-significant bit on the right): + +- 1 (..00001): "wink" +- 2 (..00010): "double blink" +- 4 (..00100): "close your eyes" +- 8 (..01000): "jump" +- 16(..10000): reverse the order of the operations selected above + +Only the presence of a bit matters; higher bits beyond the five listed +are ignored. The reverse bit (16) inverses the final list of actions. +""" + +ACTIONS: dict[int:tuple[str, str]] = { + 1: ("00001", "wink"), + 2: ("00010", "double blink"), + 3: ("00100", "close your eyes"), + 4: ("01000", "jump"), + 5: ("10000", "Reverse the order of the operations in the secret handshake"), +} + + +def commands(binary_str: str) -> list[str]: + """ + Return the list of secret-handshake actions for the given binary string. + + The input should be a binary string whose rightmost character is the + least-significant bit. Up to five bits are considered, mapping to the + actions defined by the classic Exercism "Secret Handshake" exercise. + If the fifth bit is set, the final list of actions is reversed. + + :param binary_str: Binary string (e.g. "10101"). Rightmost char is LSB. + :returns: A list of action strings in the computed order. + """ + results: list = [] + # Iterate from least- to most-significant bit by reversing the string. + for i, char in enumerate(binary_str[::-1]): + # Check if the corresponding bit position matches the expected "1". + if ACTIONS[i + 1][0][-(i + 1)] == char: + if i + 1 != 5: + results.append(ACTIONS[i + 1][1]) + else: + # Fifth bit indicates the final list should be reversed. + results = results[::-1] + return results diff --git a/secret-handshake/secret_handshake_test.py b/secret-handshake/secret_handshake_test.py new file mode 100644 index 0000000..0b45c57 --- /dev/null +++ b/secret-handshake/secret_handshake_test.py @@ -0,0 +1,49 @@ +# pylint: disable=C0301, C0114, C0115, C0116, R0904 +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/secret-handshake/canonical-data.json +# File last updated on 2023-07-19 + +import unittest + +from secret_handshake import ( + commands, +) + + +class SecretHandshakeTest(unittest.TestCase): + def test_wink_for_1(self): + self.assertEqual(commands("00001"), ["wink"]) + + def test_double_blink_for_10(self): + self.assertEqual(commands("00010"), ["double blink"]) + + def test_close_your_eyes_for_100(self): + self.assertEqual(commands("00100"), ["close your eyes"]) + + def test_jump_for_1000(self): + self.assertEqual(commands("01000"), ["jump"]) + + def test_combine_two_actions(self): + self.assertEqual(commands("00011"), ["wink", "double blink"]) + + def test_reverse_two_actions(self): + self.assertEqual(commands("10011"), ["double blink", "wink"]) + + def test_reversing_one_action_gives_the_same_action(self): + self.assertEqual(commands("11000"), ["jump"]) + + def test_reversing_no_actions_still_gives_no_actions(self): + self.assertEqual(commands("10000"), []) + + def test_all_possible_actions(self): + self.assertEqual( + commands("01111"), ["wink", "double blink", "close your eyes", "jump"] + ) + + def test_reverse_all_possible_actions(self): + self.assertEqual( + commands("11111"), ["jump", "close your eyes", "double blink", "wink"] + ) + + def test_do_nothing_for_zero(self): + self.assertEqual(commands("00000"), []) From 83b939b44a833d8a3f84e55400b864a057f862a5 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 24 Nov 2025 20:37:30 -0800 Subject: [PATCH 2/4] Refactor secret handshake bit mapping logic Simplifies the ACTIONS dictionary to use zero-based indices and updates the commands function to correctly process bits and reverse the result when the fifth bit is set. This improves clarity and correctness in mapping binary input to handshake actions. --- secret-handshake/secret_handshake.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/secret-handshake/secret_handshake.py b/secret-handshake/secret_handshake.py index e6a637b..78fd821 100644 --- a/secret-handshake/secret_handshake.py +++ b/secret-handshake/secret_handshake.py @@ -18,11 +18,10 @@ """ ACTIONS: dict[int:tuple[str, str]] = { - 1: ("00001", "wink"), - 2: ("00010", "double blink"), - 3: ("00100", "close your eyes"), - 4: ("01000", "jump"), - 5: ("10000", "Reverse the order of the operations in the secret handshake"), + 0: ("00001", "wink"), + 1: ("00010", "double blink"), + 2: ("00100", "close your eyes"), + 3: ("01000", "jump"), } @@ -38,14 +37,13 @@ def commands(binary_str: str) -> list[str]: :param binary_str: Binary string (e.g. "10101"). Rightmost char is LSB. :returns: A list of action strings in the computed order. """ - results: list = [] + results: list[str] = [] # Iterate from least- to most-significant bit by reversing the string. for i, char in enumerate(binary_str[::-1]): # Check if the corresponding bit position matches the expected "1". - if ACTIONS[i + 1][0][-(i + 1)] == char: - if i + 1 != 5: - results.append(ACTIONS[i + 1][1]) - else: - # Fifth bit indicates the final list should be reversed. - results = results[::-1] + if char == "1" and i < 4: + results.append(ACTIONS[i][1]) + elif char == "1" and i == 4: + # Fifth bit indicates the final list should be reversed. + results = results[::-1] return results From fda5753df7220af9d291c6be4d993ae79170bc82 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 24 Nov 2025 20:37:56 -0800 Subject: [PATCH 3/4] Update secret_handshake.py --- secret-handshake/secret_handshake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secret-handshake/secret_handshake.py b/secret-handshake/secret_handshake.py index 78fd821..c2d8971 100644 --- a/secret-handshake/secret_handshake.py +++ b/secret-handshake/secret_handshake.py @@ -17,7 +17,7 @@ are ignored. The reverse bit (16) inverses the final list of actions. """ -ACTIONS: dict[int:tuple[str, str]] = { +ACTIONS: dict[int : tuple[str, str]] = { 0: ("00001", "wink"), 1: ("00010", "double blink"), 2: ("00100", "close your eyes"), From bbbcef8491517668955d8ebb4c0cba27ce3fbd4c Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Mon, 24 Nov 2025 20:43:27 -0800 Subject: [PATCH 4/4] Update secret_handshake.py --- secret-handshake/secret_handshake.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/secret-handshake/secret_handshake.py b/secret-handshake/secret_handshake.py index c2d8971..74b082a 100644 --- a/secret-handshake/secret_handshake.py +++ b/secret-handshake/secret_handshake.py @@ -17,12 +17,7 @@ are ignored. The reverse bit (16) inverses the final list of actions. """ -ACTIONS: dict[int : tuple[str, str]] = { - 0: ("00001", "wink"), - 1: ("00010", "double blink"), - 2: ("00100", "close your eyes"), - 3: ("01000", "jump"), -} +ACTIONS: tuple[str, str, str, str] = ("wink", "double blink", "close your eyes", "jump") def commands(binary_str: str) -> list[str]: @@ -42,7 +37,7 @@ def commands(binary_str: str) -> list[str]: for i, char in enumerate(binary_str[::-1]): # Check if the corresponding bit position matches the expected "1". if char == "1" and i < 4: - results.append(ACTIONS[i][1]) + results.append(ACTIONS[i]) elif char == "1" and i == 4: # Fifth bit indicates the final list should be reversed. results = results[::-1]