feat(factorial): create exercise
Because
It was decided to add new recursion exercises
This PR
- Adds exercise 13
Issue
Related to #27265
Additional Information
Pull Request Requirements
- [x] I have thoroughly read and understand The Odin Project Contributing Guide
- [x] The title of this PR follows the
location of change: brief description of changeformat, e.g.01_helloWorld: Update test cases - [x] The
Becausesection summarizes the reason for this PR - [x] The
This PRsection has a bullet point list describing the changes in this PR - [x] If this PR addresses an open issue, it is linked in the
Issuesection - [x] If this PR includes any changes that affect the solution of an exercise, I've also updated the solution in the
/solutionsfolder
EDIT: The more I think about it, the more I feel that accepting strings is unnecessary for the intent of this exercise. The point of the exercise is to practise recursion, and it's the first exercise for that to boot.
Therefore, learners should be focusing on wrestling with the recursion part, not type checking and various additional unrelated things to prevent edge cases like
factorial([4])being valid without anArray.isArrayrejection. Accepting only numbers allows for only!Number.isInteger(n)andn < 0rejections. Far more relevant and intuitive, and does not reduce complexity for the actual point of the task, which is recursion.Additional scenarios that need to be tested:
- Accept integers with
.0e.g.5.0or'5.0'. String would be better as we'd only need a single test case for that (needing to convert it to a number would mean it also effectively tests5.0)- Reject string floats e.g.
'8.9'- Reject some other invalid value e.g.
'foo'Though having said that, I'm wondering if there's much actual value in accepting string values at all. Since the exercise is primarily about recursion, I'd probably argue that it makes more sense to only accept numbers (so
'4'or'4.0'would be rejected). That would also solve edge cases likefactorial([])andfactorial([7])being the equivalents offactorial(0)andfactorial(7)respectively, as arrays coerce to strings which then cleanly convert to legit numbers in those two cases.
I agree that it would be better to make the learner not have to deal with strings and instead focus on the recursion aspect. Should this approach be employed for all future exercises?
EDIT: The more I think about it, the more I feel that accepting strings is unnecessary for the intent of this exercise. The point of the exercise is to practise recursion, and it's the first exercise for that to boot.
Therefore, learners should be focusing on wrestling with the recursion part, not type checking and various additional unrelated things to prevent edge cases like
factorial([4])being valid without anArray.isArrayrejection. Accepting only numbers allows for only!Number.isInteger(n)andn < 0rejections. Far more relevant and intuitive, and does not reduce complexity for the actual point of the task, which is recursion.Additional scenarios that need to be tested:
- Accept integers with
.0e.g.5.0or'5.0'. String would be better as we'd only need a single test case for that (needing to convert it to a number would mean it also effectively tests5.0)- Reject string floats e.g.
'8.9'- Reject some other invalid value e.g.
'foo'Though having said that, I'm wondering if there's much actual value in accepting string values at all. Since the exercise is primarily about recursion, I'd probably argue that it makes more sense to only accept numbers (so
'4'or'4.0'would be rejected). That would also solve edge cases likefactorial([])andfactorial([7])being the equivalents offactorial(0)andfactorial(7)respectively, as arrays coerce to strings which then cleanly convert to legit numbers in those two cases.
Also, instead of making it return undefined lets have the learner make it throw an error! The curriculum does not officially cover how to throw, but they can look it up. They should know this is something that can be done.
@nikitarevenco I think that an exercise should focus on challenging a learner on things related to its intent.
For example, this exercise's main intents are:
- Factorial
- Recursive
The recursion part is obvious. The factorial part requires we only handle valid values (non-negative integers). Therefore, the exercise should be made to challenge on those parts. Rejecting non-negative integers is a key part of the factorial process, therefore is relevant to be included.
Strings can be converted to numbers before being passed in as arguments - it does not need to be handled by the function itself. Adding that capability in is an extra that's unrelated to the exercise's purpose, as it doesn't challenge the learner on any of the above key concepts (factorial/recursion). Those are what we want to challenge the learner on with this exercise, not type conversion.
Regarding throwing an error instead of returning undefined, I also think the above is relevant. We should keep to challenging on the intent of the exercise.
For future reference as well, if you want to increase the complexity of an exercise, you do so by making the relevant problem itself more challenging.
If you try to do so by adding in arbitrary unrelated extras, that doesn't make the exercise more challenging in a beneficial way given what its purpose is. That's just arbitrarily putting in extra stuff for no reason other than "to make it more complex".
For future reference as well, if you want to increase the complexity of an exercise, you do so by making the relevant problem itself more challenging.
If you try to do so by adding in arbitrary unrelated extras, that doesn't make the exercise more challenging in a beneficial way given what its purpose is. That's just arbitrarily putting in extra stuff for no reason other than "to make it more complex".
Cool! I updated it.
For future reference as well, if you want to increase the complexity of an exercise, you do so by making the relevant problem itself more challenging.
If you try to do so by adding in arbitrary unrelated extras, that doesn't make the exercise more challenging in a beneficial way given what its purpose is. That's just arbitrarily putting in extra stuff for no reason other than "to make it more complex".
Shall I start working on these now -
- contains() from the JS challenges
- totalIntegers() from the JS challenges
- sumSquares() from the JS challenges
- #flatten from the Ruby challenges
For future reference as well, if you want to increase the complexity of an exercise, you do so by making the relevant problem itself more challenging. If you try to do so by adding in arbitrary unrelated extras, that doesn't make the exercise more challenging in a beneficial way given what its purpose is. That's just arbitrarily putting in extra stuff for no reason other than "to make it more complex".
Shall I start working on these now -
- contains() from the JS challenges
- totalIntegers() from the JS challenges
- sumSquares() from the JS challenges
- #flatten from the Ruby challenges
I would say start working on the next intended JS exercise (so we do them one by one), and include a link to this PR in that one's text.
I think it would be most sensible to only add these exercises to the repo once we've agreed on the state of all proposed exercises, then we can merge them en masse. I'll probably discuss with the team what the best plan of action would be, but for the time being, this feels more sensible than merging an exercise that won't be used by the curriculum until all exercises are merged anyway.
It'll also give other maintainers a chance to look at and add their reviews if necessary.
@MaoShizhong @JoshDevHub
I think once all of these new exercises get merged it wouldn't make sense to keep them separate from the project so I propose to also convert the tasks in the project into TDD exercises too and remove the project entirely
Just letting you know, I haven't forgotten about these. I plan to get to reviewing these this week when I have more time at my computer.
Just letting you know, I haven't forgotten about these. I plan to get to reviewing these this week when I have more time at my computer.
Okay. i hope you're having a good time in the meantime!
@JoshDevHub Anything additional thoughts before you get a Ruby translation for stuff going?
Will get these merged once all have been approved.
I think it's looking pretty good. Should have some time this week to work on Ruby translations.
I don't know why the stale review was dismissed.
I think it happens automatically when a new commit is pushed