-
-
Notifications
You must be signed in to change notification settings - Fork 692
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
wordy: add to track #192
wordy: add to track #192
Conversation
BTW, as a reviewer, I really dig the reference to the canonical data. When reviewing, I grab the description, that data and hit the test suite, first off. Having the link in the PR is a friendly optimization. Thanks, man! |
test { | ||
testLogging { | ||
exceptionFormat = 'full' | ||
events = ["passed", "failed", "skipped"] |
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.
I added showStandardStreams = true
here while I was testing my implementation; could be something we consider as standard in the future to help folks?
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.
I totally get this. The more TDD "purist"s of my colleagues would say that if you feel the impulse to render intermediate values, redirect that energy into a unit test.
Seems like at this point in the curriculum (clearly a later-stage exercise?) we could include a HINT.md
that addresses this potential need and presents both options...?
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.
Sounds good, will keep it in mind!
private static final String BINARY_OPERATION_REGEX_STRING | ||
= "(plus|minus|multiplied by|divided by|raised to the power)"; | ||
|
||
private static final String VALID_QUESTION_REGEX_STRING = |
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.
Splitting into components allows for the entire problem to be validated, as well as the individual pieces of information to be extracted for the actual calculations. For example, not checking the overall structure could allow a question like
What is 3 nonsense words lorem ipsum plus 4?
to be considered valid. Still feels quite awkward though.
I initially tried writing a single regex string that would grab every piece of information required at once while simultaneously validating the entire problem structure, but then I ran into difficulties with backtracking.
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.
insert obligatory line about regexes... like if you've got a problem then try to solve it with a regex, now you got two problems. :)
It's great mental push-ups, but likely too dense to be maintainable.
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.
Agreed!
|
||
int solve(final String wordProblem) { | ||
if (!wordProblem.matches(VALID_QUESTION_REGEX_STRING)) { | ||
throw new IllegalArgumentException("I'm sorry, I don't understand the question!"); |
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.
😢
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.
Why the long face?
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.
Our word problem solver is only so smart!
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.
ah! yes. :)
|
||
final Matcher initialValueMatcher = Pattern.compile(INTEGER_REGEX_STRING).matcher(wordProblem); | ||
|
||
initialValueMatcher.find(); |
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.
Guaranteed to find something, because we know the question has the appropriate structure.
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.
Such a great example of non-defensive programming (which increases readability).
int result = Integer.parseInt(initialValueMatcher.group()); | ||
|
||
final Matcher operationAndValueMatcher | ||
= Pattern.compile(BINARY_OPERATION_REGEX_STRING + " " + INTEGER_REGEX_STRING).matcher(wordProblem); |
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.
Finds strings like "divided by 5" and captures the groups "divided by" and "5".
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.
You have fun with this one! :) So delighted you're adding this exercise to this track, @stkent !
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.
Haha yeah, this was a good exercise to build!
return firstValue * secondValue; | ||
case "divided by": | ||
return firstValue / secondValue; | ||
case "raised to the power": |
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.
Exponentiation was noted as extra credit in the common tests due to needing a different sentence structure, but it is also quite common to say "2 raised to the power 5", so I threw it in as a regular test in that format instead. Let me know if this deviation is dangerous; if so, I'll probably pull powers entirely.
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.
I believe we should have a complete solution in the example code. So, keep it, please.
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.
I'm not sure I clearly described what I had hoped to here. Let me try again:
In the canonical tests, exponentiation is included as extra credit based on the fact that supporting a sentence structure like "What is 52 cubed?" would require additional sentence structure matching. However, since I noted that "What is 52 raised to the power 3?" fit the existing structure, I wrote a test for that case which doesn't require new matching code. This felt opportunistic but is a deviation from the canonical data and its intent.
@jtigger does that perturb you more? I think it should.
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.
I'll blame the cold. 😬 I now understand your original question.
It's not obvious to me...
- it's a bonus iteration, so this would indicate to me a requirement that likely causes the practitioner to rethink their whole solution.
- In general, I wish we had something like this for all exercises to both encourage exploration and increase fluency in the skill of deferring design choices that can be deferred (i.e. make the solution as concrete as feasible and generalize as requirements arrive).
- even better is a pair of conflicting bonuses
- the practitioner is being asked to write a parser, if the requirement encourages them to "mature" that part of the code, seems on target.
- however, it's kindof a one-off... while that is a nice simulation of real-world software, it's a bit frustrating to the Exercism practitioner.
In the sum, I personally come down on the side of supporting the additional sentence structure.
That said, it is truly up to you: if you dig writing the more general solution, go for it. If you either disagree with the sum or you agree but want to get this off your plate, we can take this for now and amend the exercise later (if someone wants to). There's no additional cost to deferring.
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.
@jtigger thanks for the thoughts! Agreed that this one is far from clear-cut.
I'm in the "want to get this off my 🍽" camp at this point in time, so I've simplified the implementation and replaced the old exponentiation test with a new one. PR should be good for another look!
case "raised to the power": | ||
return (int) Math.pow(firstValue, secondValue); | ||
default: | ||
throw new IllegalArgumentException("This branch should never be executed."); |
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.
Again, this should be the case because we passed the overall structure check already.
expectedException.expect(IllegalArgumentException.class); | ||
expectedException.expectMessage("I'm sorry, I don't understand the question!"); | ||
|
||
// See https://en.wikipedia.org/wiki/President_of_the_United_States if you really need to know! |
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.
Distracting, or fun?
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.
FUN!
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.
Hooray! :D
Updated with implementation, and ready for review! |
I've been out of commission with a cold for a number of days. All I had the energy for was tweeting. ;) After a nap, here, I'll give ya a proper review, @stkent; sorry for the delay. |
No rush; feel better first! |
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.
Great stuff, @stkent !
test { | ||
testLogging { | ||
exceptionFormat = 'full' | ||
events = ["passed", "failed", "skipped"] |
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.
I totally get this. The more TDD "purist"s of my colleagues would say that if you feel the impulse to render intermediate values, redirect that energy into a unit test.
Seems like at this point in the curriculum (clearly a later-stage exercise?) we could include a HINT.md
that addresses this potential need and presents both options...?
private static final String BINARY_OPERATION_REGEX_STRING | ||
= "(plus|minus|multiplied by|divided by|raised to the power)"; | ||
|
||
private static final String VALID_QUESTION_REGEX_STRING = |
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.
insert obligatory line about regexes... like if you've got a problem then try to solve it with a regex, now you got two problems. :)
It's great mental push-ups, but likely too dense to be maintainable.
|
||
int solve(final String wordProblem) { | ||
if (!wordProblem.matches(VALID_QUESTION_REGEX_STRING)) { | ||
throw new IllegalArgumentException("I'm sorry, I don't understand the question!"); |
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.
Why the long face?
|
||
final Matcher initialValueMatcher = Pattern.compile(INTEGER_REGEX_STRING).matcher(wordProblem); | ||
|
||
initialValueMatcher.find(); |
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.
Such a great example of non-defensive programming (which increases readability).
int result = Integer.parseInt(initialValueMatcher.group()); | ||
|
||
final Matcher operationAndValueMatcher | ||
= Pattern.compile(BINARY_OPERATION_REGEX_STRING + " " + INTEGER_REGEX_STRING).matcher(wordProblem); |
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.
You have fun with this one! :) So delighted you're adding this exercise to this track, @stkent !
return firstValue * secondValue; | ||
case "divided by": | ||
return firstValue / secondValue; | ||
case "raised to the power": |
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.
I believe we should have a complete solution in the example code. So, keep it, please.
@Ignore | ||
@Test | ||
public void testExponentiation() { | ||
assertEquals(32, new WordProblemSolver().solve("What is 2 raised to the power 5?")); |
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.
What do you think of splitting this out into a separate test class whose name clearly indicates it's bonus tests that can remain ignored?
Also, I'm wondering if we'd like to expand a little on the canonical data set and include more cases for this. This is inbounds, AFAIK... it's okay to provide additional cases as long as it is sufficiently additive to the experience. I think the expectation is that we contribute back to x-common.
In general, wordy
seems to need some love. I've already submitted a PR to make the x-common descriptive bits... want to address some the weaknesses in the canonical data as well.
expectedException.expect(IllegalArgumentException.class); | ||
expectedException.expectMessage("I'm sorry, I don't understand the question!"); | ||
|
||
// See https://en.wikipedia.org/wiki/President_of_the_United_States if you really need to know! |
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.
FUN!
There was a question about maybe adding a few bits in |
@stkent back to ya. |
So, I attempted to use GitHub's new "resolve conflict" feature and apparently failed. :) Looking into it. |
Thank you, @stkent !!! |
Canonical data: https://github.com/exercism/x-common/blob/master/exercises/wordy/canonical-data.json