-
-
Notifications
You must be signed in to change notification settings - Fork 738
Adding approach for Bob #2861
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
Adding approach for Bob #2861
Changes from 29 commits
a58600a
fa97c07
f5f7680
f6ae1f1
4be572f
da71900
51a8cd3
3ddea06
64261e0
b644b1c
1dcbe19
f06c782
d0346c1
8f2ac6b
0bf84b5
e509381
f0543c3
e67fc43
ad27bb2
6def244
498ae46
ff35ee4
b017e6c
e8da28d
c3dcc96
173ce32
521160b
eaa5cdf
1802823
fc74587
2e6b670
1093c5b
f73bf04
c43db37
1b3feed
b9bc033
51bea83
7beebce
3663467
ad5c8e6
5c9fd46
7761f47
017e1c7
9f37095
c1c9bbd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,88 +1,87 @@ | ||
| # `if` statements | ||
|
|
||
| ```java | ||
| import java.util.function.Predicate; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| class Bob { | ||
|
|
||
| final private static Pattern isAlpha = Pattern.compile("[a-zA-Z]"); | ||
| final private static Predicate < String > isShout = msg -> isAlpha.matcher(msg).find() && msg == msg.toUpperCase(); | ||
|
|
||
| public String hey(String message) { | ||
| var speech = message.trim(); | ||
| if (speech.isEmpty()) { | ||
| return "Fine. Be that way!"; | ||
| } | ||
| var questioning = speech.endsWith("?"); | ||
| var shouting = isShout.test(speech); | ||
| if (questioning) { | ||
| if (shouting) { | ||
| return "Calm down, I know what I'm doing!"; | ||
| } | ||
| return "Sure."; | ||
| } | ||
| if (shouting) { | ||
| String hey(String input) { | ||
| var inputTrimmed = input.trim(); | ||
|
|
||
| if (isSilent(inputTrimmed)) | ||
| return "Fine. Be that way!"; | ||
| if (isShouting(inputTrimmed) && isQuestioning(inputTrimmed)) | ||
| return "Calm down, I know what I'm doing!"; | ||
| if (isShouting(inputTrimmed)) | ||
| return "Whoa, chill out!"; | ||
| } | ||
| if (isQuestioning(inputTrimmed)) | ||
| return "Sure."; | ||
|
|
||
| return "Whatever."; | ||
| } | ||
|
|
||
| private boolean isShouting(String input) { | ||
| return input.chars() | ||
| .anyMatch(Character::isLetter) && | ||
| input.chars() | ||
| .filter(Character::isLetter) | ||
| .allMatch(Character::isUpperCase); | ||
| } | ||
|
|
||
| private boolean isQuestioning(String input) { | ||
| return input.endsWith("?"); | ||
| } | ||
|
|
||
| private boolean isSilent(String input) { | ||
| return input.length() == 0; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| In this approach you have a series of `if` statements using the private methods to evaluate the conditions. | ||
| As soon as the right condition is found, the correct response is returned. | ||
| In this approach, the different conditions for Bob’s responses are separated into dedicated private methods within the `Bob` class. This method-based approach improves readability and modularity by organizing each condition check into its own method, making the main response method easier to understand and maintain. | ||
|
|
||
| Note that there are no `else if` or `else` statements. | ||
| If an `if` statement can return, then an `else if` or `else` is not needed. | ||
| Execution will either return or will continue to the next statement anyway. | ||
| ## Explanation | ||
|
|
||
| The `String` [`trim()`][trim] method is applied to the input to eliminate any whitespace at either end of the input. | ||
| If the string has no characters left, it returns the response for saying nothing. | ||
| This approach simplifies the main method `hey` by breaking down each response condition into helper methods: | ||
|
|
||
| ~~~~exercism/caution | ||
| Note that a `null` `string` would be different from a `String` of all whitespace. | ||
| A `null` `String` would throw a `NullPointerException` if `trim()` were applied to it. | ||
| ~~~~ | ||
| ### Trimming the Input | ||
|
|
||
| A [Pattern][pattern] is defined to look for at least one English alphabetic character. | ||
| The `input` is trimmed using the `String` [`trim()`][trim] method to remove any leading or trailing whitespace. This helps to accurately detect if the input is empty and should prompt a `"Fine. Be that way!"` response. | ||
|
|
||
| The first half of the `isShout` [Predicate][predicate] | ||
| ### Delegating to Helper Methods | ||
|
|
||
| ```java | ||
| isAlpha.matcher(msg).find() && msg == msg.toUpperCase(); | ||
| ``` | ||
| Each condition is evaluated using the following helper methods: | ||
|
|
||
| 1. **`isSilent`**: Checks if the trimmed input has no characters. | ||
| 2. **`isShouting`**: Checks if the input is all uppercase and contains at least one alphabetic character, indicating shouting. | ||
| 3. **`isQuestioning`**: Verifies if the trimmed input ends with a question mark. | ||
|
|
||
| This modular approach keeps each condition encapsulated, enhancing code clarity. | ||
|
|
||
| ### Order of Checks | ||
|
|
||
| The order of checks within `hey` is important: | ||
|
|
||
| is constructed from the `Pattern` [`matcher()`][matcher-method] method and the [`Matcher`][matcher] [`find()`][find] method | ||
| to ensure there is at least one letter character in the `String`. | ||
| This is because the second half of the condition tests that the uppercased input is the same as the input. | ||
| If the input were only `"123"` it would equal itself uppercased, but without letters it would not be a shout. | ||
| 1. Silence is evaluated first, as it requires an immediate response. | ||
| 2. Shouted questions take precedence over individual checks for shouting and questioning. | ||
| 3. Shouting comes next, requiring its response if not combined with a question. | ||
| 4. Questioning (a non-shouted question) is checked afterward. | ||
|
|
||
| A question is determined by use of the [`endsWith()`][endswith] method to see if the input ends with a question mark. | ||
| This ordering ensures that Bob’s response matches the expected behavior without redundancy. | ||
|
|
||
| ## Shortening | ||
|
|
||
| When the body of an `if` statement is a single line, both the test expression and the body _could_ be put on the same line, like so | ||
| When the body of an `if` statement is a single line, both the test expression and the body _could_ be put on the same line, like so: | ||
|
|
||
| ```java | ||
| if (speech.isEmpty()) return "Fine. Be that way!"; | ||
| if (isSilent(inputTrimmed)) return "Fine. Be that way!"; | ||
| ``` | ||
|
|
||
| or the body _could_ be put on a separate line without curly braces | ||
| or the body _could_ be put on a separate line without curly braces: | ||
|
|
||
| ```java | ||
| if (speech.isEmpty()) | ||
| if (isSilent(inputTrimmed)) | ||
| return "Fine. Be that way!"; | ||
| ``` | ||
|
|
||
| However, the [Java Coding Conventions][coding-conventions] advise to always use curly braces for `if` statements, which helps to avoid errors. | ||
| Your team may choose to overrule them at its own risk. | ||
| However, the [Java Coding Conventions][coding-conventions] advise always using curly braces for `if` statements, which helps to avoid errors. Your team may choose to overrule them at its own risk. | ||
|
|
||
| [trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim() | ||
| [pattern]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html | ||
| [predicate]: https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html | ||
| [matcher]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html | ||
| [matcher-method]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#matcher-java.lang.CharSequence- | ||
| [find]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html#find-- | ||
| [endswith]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#endsWith(java.lang.String) | ||
| [trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim() | ||
| [coding-conventions]: https://www.oracle.com/java/technologies/javase/codeconventions-statements.html#449 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| if (questioning) { | ||
| if (shouting) | ||
| return "Calm down, I know what I'm doing!"; | ||
| return "Sure."; | ||
| } | ||
| if (shouting) | ||
| if (isSilent(inputTrimmed)) | ||
| return "Fine. Be that way!"; | ||
| if (isShouting(inputTrimmed) && isQuestioning(inputTrimmed)) | ||
| return "Calm down, I know what I'm doing!"; | ||
| if (isShouting(inputTrimmed)) | ||
| return "Whoa, chill out!"; | ||
| return "Whatever."; | ||
| if (isQuestioning(inputTrimmed)) | ||
| return "Sure."; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,58 @@ | ||
| # Introduction | ||
|
|
||
| There are various idiomatic approaches to solve Bob. | ||
| A basic approach can use a series of `if` statements to test the conditions. | ||
| An array can contain answers from which the right response is selected by an index calculated from scores given to the conditions. | ||
| In this exercise, we’re working on a program to determine Bob’s responses based on the tone and style of given messages. Bob responds differently depending on whether a message is a question, a shout, both, or silence. Various approaches can be used to implement this logic efficiently and cleanly, ensuring the code remains readable and easy to maintain. | ||
|
||
|
|
||
| ## General guidance | ||
|
|
||
| Regardless of the approach used, some things you could look out for include | ||
| When implementing your solution, consider the following tips to keep your code optimized and idiomatic: | ||
|
|
||
| - If the input is trimmed, [`trim()`][trim] only once. | ||
| - **Trim the Input Once**: Use [`trim()`][trim] only once at the start to remove any unnecessary whitespace. | ||
| - **Use Built-in Methods**: For checking if a message is a question, prefer [`endsWith("?")`][endswith] instead of manually checking the last character. | ||
| - **Single Determinations**: Use variables for `questioning` and `shouting` rather than calling these checks multiple times to improve efficiency. | ||
| - **DRY Code**: Avoid duplicating code by combining the logic for determining a shout and a question when handling shouted questions. Following the [DRY][dry] principle helps maintain clear and maintainable code. | ||
| - **Return Statements**: An early return in an `if` statement eliminates the need for additional `else` blocks, making the code more readable. | ||
| - **Curly Braces**: While optional for single-line statements, some teams may require them for readability and consistency. | ||
|
|
||
| - Use the [`endsWith()`][endswith] `String` method instead of checking the last character by index for `?`. | ||
| ## Approach: `if` statements | ||
|
|
||
| ```java | ||
| class Bob { | ||
| String hey(String input) { | ||
| var inputTrimmed = input.trim(); | ||
|
|
||
| if (isSilent(inputTrimmed)) | ||
|
||
| return "Fine. Be that way!"; | ||
| if (isShouting(inputTrimmed) && isQuestioning(inputTrimmed)) | ||
| return "Calm down, I know what I'm doing!"; | ||
| if (isShouting(inputTrimmed)) | ||
| return "Whoa, chill out!"; | ||
| if (isQuestioning(inputTrimmed)) | ||
| return "Sure."; | ||
|
|
||
| return "Whatever."; | ||
| } | ||
|
|
||
| - Don't copy/paste the logic for determining a shout and for determining a question into determining a shouted question. | ||
| Combine the two determinations instead of copying them. | ||
| Not duplicating the code will keep the code [DRY][dry]. | ||
| private boolean isShouting(String input) { | ||
| return input.chars() | ||
| .anyMatch(Character::isLetter) && | ||
| input.chars() | ||
| .filter(Character::isLetter) | ||
| .allMatch(Character::isUpperCase); | ||
| } | ||
|
|
||
| - Perhaps consider making `questioning` and `shouting` values set once instead of functions that are possibly called twice. | ||
| private boolean isQuestioning(String input) { | ||
| return input.endsWith("?"); | ||
| } | ||
|
|
||
| - If an `if` statement can return, then an `else if` or `else` is not needed. | ||
| Execution will either return or will continue to the next statement anyway. | ||
| private boolean isSilent(String input) { | ||
| return input.length() == 0; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| - If the body of an `if` statement is only one line, curly braces aren't needed. | ||
| Some teams may still require them in their style guidelines, though. | ||
| This approach defines helper methods for each type of message—silent, shouting, and questioning—to keep each condition clean and easily testable. For more details, refer to the [`if` Statements Approach][approach-if]. | ||
|
|
||
| ## Approach: `if` statements | ||
| ## Approach: nested `if` statements | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading this from top to bottom, I think seeing "method-based if statements" and then "if statements" is a little jarring (the first time, I thought "if statements" should come first because the heading "if statements" suggests it is a more general than "method-based if statements"). To improve the flow, I'd suggest either moving this "if statements" approach to appear before the "method-based if statements" or renaming this approach to something like "variables-based if statements". |
||
| ```java | ||
| import java.util.function.Predicate; | ||
|
|
@@ -56,7 +84,7 @@ class Bob { | |
| } | ||
| ``` | ||
|
|
||
| For more information, check the [`if` statements approach][approach-if]. | ||
| This approach utilizes nested `if` statements and a predicate for determining if a message is a shout. For more details, refer to the [nested `if` Statements Approach][approach-nested-if]. | ||
|
|
||
| ## Approach: answer array | ||
|
|
||
|
|
@@ -86,16 +114,21 @@ class Bob { | |
| } | ||
| ``` | ||
|
|
||
| For more information, check the [Answer array approach][approach-answer-array]. | ||
| This approach uses an array of answers and calculates the appropriate index based on flags for shouting and questioning. For more details, refer to the [Answer Array Approach][approach-answer-array]. | ||
jagdish-15 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Which Approach to Use? | ||
|
|
||
| The choice between the **`if` Statements Approach**, **Nested `if` Statements Approach**, and the **Answer Array Approach** depends on readability, maintainability, and efficiency: | ||
|
|
||
| ## Which approach to use? | ||
| - **`if` Statements Approach**: This is clear and easy to follow but checks conditions multiple times, potentially affecting performance. Storing results in variables like `questioning` and `shouting` can improve efficiency but may reduce clarity slightly. | ||
| - **Nested `if` Statements Approach**: This approach can be more efficient by avoiding redundant checks, but its nested structure can reduce readability and maintainability. | ||
| - **Answer Array Approach**: Efficient and compact, this method uses an array of responses based on flags for questioning and shouting. However, it may be less intuitive and harder to modify if more responses are needed. | ||
|
|
||
| Since benchmarking with the [Java Microbenchmark Harness][jmh] is currently outside the scope of this document, | ||
| the choice between `if` statements and answers array can be made by perceived readability. | ||
| Each approach offers a balance between readability and performance, with trade-offs in flexibility and clarity. | ||
|
|
||
| [trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim() | ||
| [endswith]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#endsWith(java.lang.String) | ||
| [dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself | ||
| [approach-nested-if]: https://exercism.org/tracks/java/exercises/bob/approaches/nested-if-statements | ||
| [approach-if]: https://exercism.org/tracks/java/exercises/bob/approaches/if-statements | ||
| [approach-answer-array]: https://exercism.org/tracks/java/exercises/bob/approaches/answer-array | ||
| [jmh]: https://github.com/openjdk/jmh | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # nested `if` statements | ||
|
|
||
| ```java | ||
| import java.util.function.Predicate; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| class Bob { | ||
|
|
||
| final private static Pattern isAlpha = Pattern.compile("[a-zA-Z]"); | ||
| final private static Predicate < String > isShout = msg -> isAlpha.matcher(msg).find() && msg == msg.toUpperCase(); | ||
|
|
||
| public String hey(String message) { | ||
| var speech = message.trim(); | ||
| if (speech.isEmpty()) { | ||
| return "Fine. Be that way!"; | ||
| } | ||
| var questioning = speech.endsWith("?"); | ||
| var shouting = isShout.test(speech); | ||
| if (questioning) { | ||
| if (shouting) { | ||
| return "Calm down, I know what I'm doing!"; | ||
| } | ||
| return "Sure."; | ||
| } | ||
| if (shouting) { | ||
| return "Whoa, chill out!"; | ||
| } | ||
| return "Whatever."; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| In this approach you have a series of `if` statements using the private methods to evaluate the conditions. | ||
| As soon as the right condition is found, the correct response is returned. | ||
|
|
||
| Note that there are no `else if` or `else` statements. | ||
| If an `if` statement can return, then an `else if` or `else` is not needed. | ||
| Execution will either return or will continue to the next statement anyway. | ||
|
|
||
| The `String` [`trim()`][trim] method is applied to the input to eliminate any whitespace at either end of the input. | ||
| If the string has no characters left, it returns the response for saying nothing. | ||
|
|
||
| ~~~~exercism/caution | ||
| Note that a `null` `string` would be different from a `String` of all whitespace. | ||
| A `null` `String` would throw a `NullPointerException` if `trim()` were applied to it. | ||
| ~~~~ | ||
|
|
||
| A [Pattern][pattern] is defined to look for at least one English alphabetic character. | ||
|
|
||
| The first half of the `isShout` [Predicate][predicate] | ||
|
|
||
| ```java | ||
| isAlpha.matcher(msg).find() && msg == msg.toUpperCase(); | ||
| ``` | ||
|
|
||
| is constructed from the `Pattern` [`matcher()`][matcher-method] method and the [`Matcher`][matcher] [`find()`][find] method | ||
| to ensure there is at least one letter character in the `String`. | ||
| This is because the second half of the condition tests that the uppercased input is the same as the input. | ||
| If the input were only `"123"` it would equal itself uppercased, but without letters it would not be a shout. | ||
|
|
||
| A question is determined by use of the [`endsWith()`][endswith] method to see if the input ends with a question mark. | ||
|
|
||
| ## Shortening | ||
|
|
||
| When the body of an `if` statement is a single line, both the test expression and the body _could_ be put on the same line, like so | ||
|
|
||
| ```java | ||
| if (speech.isEmpty()) return "Fine. Be that way!"; | ||
| ``` | ||
|
|
||
| or the body _could_ be put on a separate line without curly braces | ||
|
|
||
| ```java | ||
| if (speech.isEmpty()) | ||
| return "Fine. Be that way!"; | ||
| ``` | ||
|
|
||
| However, the [Java Coding Conventions][coding-conventions] advise to always use curly braces for `if` statements, which helps to avoid errors. | ||
| Your team may choose to overrule them at its own risk. | ||
|
|
||
| [trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim() | ||
| [pattern]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html | ||
| [predicate]: https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html | ||
| [matcher]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html | ||
| [matcher-method]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#matcher-java.lang.CharSequence- | ||
| [find]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html#find-- | ||
| [endswith]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#endsWith(java.lang.String) | ||
| [coding-conventions]: https://www.oracle.com/java/technologies/javase/codeconventions-statements.html#449 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I think there might been a bit of misundstanding, as I didn't mean to copy Python's approaches entirely. I was hoping keep the original
if-statementapproach as it was before your changes and your approach added asif-statement-using-methods(or something similar). Sorry for not being clearer earlier.