Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Secret Handshake approach #1636

Merged
merged 13 commits into from Mar 3, 2023
15 changes: 15 additions & 0 deletions exercises/practice/secret-handshake/.approaches/config.json
@@ -0,0 +1,15 @@
{
"introduction": {
"authors": ["bobahop"],
"contributors": []
},
"approaches": [
{
"uuid": "cd0c0a31-0905-47f0-815c-02597a7cdf40",
"slug": "iterate-once",
"title": "Iterate once",
"blurb": "Iterate once even when reversed.",
"authors": ["bobahop"]
}
]
}
39 changes: 39 additions & 0 deletions exercises/practice/secret-handshake/.approaches/introduction.md
@@ -0,0 +1,39 @@
# Introduction

There are many ways to solve Secret Handshake.
One approach is to iterate only once, even when the signs are to be reversed.

## General guidance

Something to consider is to keep the number of iterations at a minimum to get the best performance.
However, if that is felt to adversely impact readability, then to use a series of `if` statements and then reverse is also valid.

## Approach: Iterate once

```rust
const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"];
const REVERSE_SIGNS: u8 = 16;

pub fn actions(n: u8) -> Vec<&'static str> {
let (mut action, action_incr, end) = match n & REVERSE_SIGNS {
0 => (0, 1, 4),
_ => (3, -1, -1),
};
let mut output: Vec<&'static str> = Vec::new();

loop {
if action == end {
break;
}
if (n & (1 << action)) != 0 {
output.push(SIGNS[action as usize])
}
action += action_incr
}
output
}
```

For more information, check the [Iterate once approach][approach-iterate-once].

[approach-iterate-once]: https://exercism.org/tracks/rust/exercises/secret-handshake/approaches/iterate-once
@@ -0,0 +1,97 @@
# Iterate once

```rust
const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"];
const REVERSE_SIGNS: u8 = 16;

pub fn actions(n: u8) -> Vec<&'static str> {
let (mut action, action_incr, end) = match n & REVERSE_SIGNS {
0 => (0, 1, 4),
_ => (3, -1, -1),
};
let mut output: Vec<&'static str> = Vec::new();

loop {
if action == end {
break;
}
if (n & (1 << action)) != 0 {
output.push(SIGNS[action as usize])
}
action += action_incr
}
output
}
Comment on lines +7 to +24
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must admit that I'm not well-versed in Rust, but to me this is very hard to read. It seems to almost obscure the logic. Is this an idiomatic way to solve the exercise in Rust? What about something simple like: https://exercism.org/tracks/rust/exercises/secret-handshake/solutions/aciba90?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are simpler approaches which don't offer as much in pedagogic value. Since I only did one approach, I used one which is not exactly unidiomatic, but which afforded more in the way of explanation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. Okay!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an interesting approach

```

This approach starts by defining a fixed-size [array][array] to hold the signal values in normal order.

The `[&'static str; 4]` is used to give the type and length of the array.
To be a [`const`][const], the size of the array must be known at compile time, so setting the type and length must be done explicitly,
so the size in bytes of the fixed array can be deduced by the compiler from that and not by inspecting the element types and counting
the elements itself.

The value of `16` is defined as a `const` with a meaningful name so it won't be used as a [magic number][magic-number].

The `actions` function uses multiple assignment with a `match` expression to define the variables that control iterating through the signals array,
setting their values to iterate in either the normal or reverse order.

The [bitwise AND operator][bitand] is used to check if the input number contains the signal for reversing the order of the other signals.

For example, if the number passed in is `19`, which is `10011` in binary, then it is ANDed with `16`, which is `10000` in binary.
The `1` in `10000` is also at the same position in `10011`, so the two values ANDed will not be `0`.
- `10011` AND
- `10000` =
- `10000`

If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `16`, which is `10000` in binary.
The `1` in `10000` is not at the same position in `00011`, so the two values ANDed will be `0`.
- `00011` AND
- `10000` =
- `00000`

If the number passed in does not contain the signal for reverse, then the iteration variables are set to iterate through the array of signals
in their normal order, otherwise they are set to iterate through the arrray backwards..

The output `vector` is defined, and then the [`loop`][loop] begins.

Normal iteration will start at index `0`.
Reverse iteration will start at index `3`.

Normal iteration will terminate when the index equals `4`.
Reverse iteration will terminate when the index equals `-1`.

Normal iteration will increase the index by `1` for each iteration.
Reverse iteration will decrease the index by `1` for each iteration.

For each iteration of the `loop`, the AND operator is used to check if the number passed in contains `1` [shifted left][shl] (`<<`) for the number of positions
as the value being iterated.

```rust
if (n & (1 << action)) != 0 {
output.push(SIGNS[action as usize])
}
```

For example, if the number being iterated is `0`, then `1` is shifted left `0` times (so not shifted at all), and the number passed in is ANDed with `00001`.
If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00001`.
`00011` ANDed with `00001` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`.
The index used is the number being iterated, which is `0`, so the element at index `0` (`"wink"`) would be added to the output `vector`
using the [push][push] function.

If the number being iterated is `1`, then `1` is shifted left `1` time, and the number passed in is ANDed with `00010`.
If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00010`.
`00011` ANDed with `00010` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`.
The index used is the number being iterated, which is `1`, so the element at index `1` (`"double blink"`) would be added to the output `vector`.

If the number passed in ANDed with the number being iterated is equal to `0`, then the signal in the array for that index is not added to the output `vector`.

After iterating through the array of signals is done, the output `vector` is returned from the function.

[array]: https://doc.rust-lang.org/std/primitive.array.html
[const]: https://doc.rust-lang.org/std/keyword.const.html
[magic-number]: https://en.wikipedia.org/wiki/Magic_number_(programming)
[bitand]: https://doc.rust-lang.org/std/ops/trait.BitAnd.html
[shl]: https://doc.rust-lang.org/std/ops/trait.Shl.html
[loop]: https://doc.rust-lang.org/rust-by-example/flow_control/loop.html
[push]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.push
@@ -0,0 +1,4 @@
let (mut action, action_incr, end) = match n & REVERSE_SIGNS {
0 => (0, 1, 4),
_ => (3, -1, -1),
};