breaking(curriculum): add scientific-computing to /learn (#53143) (#53146)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
Co-authored-by: Dario-DC <105294544+Dario-DC@users.noreply.github.com>
Co-authored-by: Zaira <33151350+zairahira@users.noreply.github.com>
This commit is contained in:
Shaun Hamilton
2024-02-28 08:05:23 -06:00
committed by GitHub
co-authored by Oliver Eyton-Williams Dario-DC Zaira
parent 5b56bbd1ff
commit d361e128c0
26 changed files with 2633 additions and 216 deletions
+23 -4
View File
@@ -120,11 +120,25 @@ const jsCertProjectIds = [
];
const multifileCertProjectIds = getChallenges()
.filter(challenge => challenge.challengeType === 14)
.filter(
challenge => challenge.challengeType === challengeTypes.multifileCertProject
)
.map(challenge => challenge.id);
const multifilePythonCertProjectIds = getChallenges()
.filter(
challenge =>
challenge.challengeType === challengeTypes.multifilePythonCertProject
)
.map(challenge => challenge.id);
const savableChallenges = getChallenges()
.filter(challenge => challenge.challengeType === 14)
.filter(challenge => {
return (
challenge.challengeType === challengeTypes.multifileCertProject ||
challenge.challengeType === challengeTypes.multifilePythonCertProject
);
})
.map(challenge => challenge.id);
const msTrophyChallenges = getChallenges()
@@ -141,7 +155,8 @@ export function buildUserUpdate(
let completedChallenge = {};
if (
jsCertProjectIds.includes(challengeId) ||
multifileCertProjectIds.includes(challengeId)
multifileCertProjectIds.includes(challengeId) ||
multifilePythonCertProjectIds.includes(challengeId)
) {
completedChallenge = {
..._completedChallenge,
@@ -397,7 +412,11 @@ export async function modernChallengeCompleted(req, res, next) {
// We only need to know the challenge type if it's a project. If it's a
// step or normal challenge we can avoid storing in the database.
if (jsCertProjectIds.includes(id) || multifileCertProjectIds.includes(id)) {
if (
jsCertProjectIds.includes(id) ||
multifileCertProjectIds.includes(id) ||
multifilePythonCertProjectIds.includes(id)
) {
completedChallenge.challengeType = challengeType;
}
+4 -1
View File
@@ -34,7 +34,10 @@ function* saveChallengeSaga() {
}
// only allow saving of multifileCertProject's
if (challengeType === challengeTypes.multifileCertProject) {
if (
challengeType === challengeTypes.multifileCertProject ||
challengeType === challengeTypes.multifilePythonCertProject
) {
const body = standardizeRequestBody({ id, challengeFiles, challengeType });
const bodySizeInBytes = getStringSizeInBytes(body);
@@ -169,7 +169,8 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
const challengeFile = getChallengeFile();
const projectBasedChallenge = hasEditableBoundaries;
const isMultifileCertProject =
challengeType === challengeTypes.multifileCertProject;
challengeType === challengeTypes.multifileCertProject ||
challengeType === challengeTypes.multifilePythonCertProject;
const displayPreviewPane = hasPreview && showPreviewPane;
const displayPreviewPortal = hasPreview && showPreviewPortal;
const displayNotes = projectBasedChallenge ? showNotes && hasNotes : false;
@@ -283,7 +283,9 @@ const Editor = (props: EditorProps): JSX.Element => {
selectionHighlight: false,
overviewRulerBorder: false,
hideCursorInOverviewRuler: true,
renderIndentGuides: props.challengeType === challengeTypes.python,
renderIndentGuides:
props.challengeType === challengeTypes.python ||
props.challengeType === challengeTypes.multifilePythonCertProject,
minimap: {
enabled: false
},
@@ -302,7 +304,11 @@ const Editor = (props: EditorProps): JSX.Element => {
parameterHints: {
enabled: false
},
tabSize: props.challengeType !== challengeTypes.python ? 2 : 4,
tabSize:
props.challengeType !== challengeTypes.python &&
props.challengeType !== challengeTypes.multifilePythonCertProject
? 2
: 4,
dragAndDrop: true,
lightbulb: {
enabled: false
@@ -538,7 +544,8 @@ const Editor = (props: EditorProps): JSX.Element => {
monaco.KeyMod.WinCtrl | monaco.KeyCode.KEY_S
],
run:
props.challengeType === challengeTypes.multifileCertProject &&
(props.challengeType === challengeTypes.multifileCertProject ||
props.challengeType === challengeTypes.multifilePythonCertProject) &&
props.isSignedIn
? // save to database
props.saveChallenge
@@ -158,7 +158,8 @@ const StepPreview = ({
challengeType: number;
xtermFitRef: React.MutableRefObject<FitAddon | null>;
}) => {
return challengeType === challengeTypes.python ? (
return challengeType === challengeTypes.python ||
challengeType === challengeTypes.multifilePythonCertProject ? (
<XtermTerminal xtermFitRef={xtermFitRef} />
) : (
<Preview
@@ -240,6 +241,7 @@ function ShowClassic({
challengeType === challengeTypes.html ||
challengeType === challengeTypes.modern ||
challengeType === challengeTypes.multifileCertProject ||
challengeType === challengeTypes.multifilePythonCertProject ||
challengeType === challengeTypes.python;
const getLayoutState = () => {
@@ -77,27 +77,30 @@ function ToolPanel({
<Button block={true} variant='primary' onClick={handleRunTests}>
{isMobile ? t('buttons.run') : t('buttons.run-test')}
</Button>
{isSignedIn && challengeType === challengeTypes.multifileCertProject && (
<>
<Spacer size='xxSmall' />
<Button
block={true}
variant='primary'
data-cy='save-code-to-database-btn'
onClick={saveChallenge}
>
{isMobile ? t('buttons.save') : t('buttons.save-code')}
</Button>
</>
)}
{challengeType !== challengeTypes.multifileCertProject && (
<>
<Spacer size='xxSmall' />
<Button block={true} variant='primary' onClick={openResetModal}>
{isMobile ? t('buttons.reset') : t('buttons.reset-lesson')}
</Button>
</>
)}
{isSignedIn &&
(challengeType === challengeTypes.multifileCertProject ||
challengeType === challengeTypes.multifilePythonCertProject) && (
<>
<Spacer size='xxSmall' />
<Button
block={true}
variant='primary'
data-cy='save-code-to-database-btn'
onClick={saveChallenge}
>
{isMobile ? t('buttons.save') : t('buttons.save-code')}
</Button>
</>
)}
{challengeType !== challengeTypes.multifileCertProject &&
challengeType !== challengeTypes.multifilePythonCertProject && (
<>
<Spacer size='xxSmall' />
<Button block={true} variant='primary' onClick={openResetModal}>
{isMobile ? t('buttons.reset') : t('buttons.reset-lesson')}
</Button>
</>
)}
<Spacer size='xxSmall' />
<Dropdown dropup>
<Dropdown.Toggle
@@ -116,7 +116,8 @@ function submitModern(type, state) {
let body;
if (
block === 'javascript-algorithms-and-data-structures-projects' ||
challengeType === challengeTypes.multifileCertProject
challengeType === challengeTypes.multifileCertProject ||
challengeType === challengeTypes.multifilePythonCertProject
) {
body = standardizeRequestBody({ id, challengeType, challengeFiles });
} else {
@@ -70,7 +70,10 @@ function* executeCancellableChallengeSaga(payload) {
const { challengeFiles } = yield select(challengeDataSelector);
// if multifileCertProject, see if body/code size is submittable
if (challengeType === challengeTypes.multifileCertProject) {
if (
challengeType === challengeTypes.multifileCertProject ||
challengeType === challengeTypes.multifilePythonCertProject
) {
const body = standardizeRequestBody({ id, challengeFiles, challengeType });
const bodySizeInBytes = getStringSizeInBytes(body);
@@ -278,7 +281,11 @@ export function* previewChallengeSaga(action) {
// Python challenges do not use the preview frame, they use a web worker
// to run the code. The UI is handled by the xterm component, so there
// is no need to update the preview frame.
if (challengeData.challengeType === challengeTypes.python) {
if (
challengeData.challengeType === challengeTypes.python ||
challengeData.challengeType ===
challengeTypes.multifilePythonCertProject
) {
yield updatePython(challengeData);
} else {
yield call(updatePreview, buildData, finalDocument, proxyLogger);
@@ -309,7 +316,10 @@ export function* previewChallengeSaga(action) {
// appropriately)
function* updatePreviewSaga(action) {
const challengeData = yield select(challengeDataSelector);
if (challengeData.challengeType === challengeTypes.python) {
if (
challengeData.challengeType === challengeTypes.python ||
challengeData.challengeType === challengeTypes.multifilePythonCertProject
) {
yield updatePython(challengeData);
} else {
// all other challenges have to recreate the preview
@@ -89,6 +89,7 @@ export const challengeDataSelector = state => {
challengeType === challengeTypes.html ||
challengeType === challengeTypes.modern ||
challengeType === challengeTypes.multifileCertProject ||
challengeType === challengeTypes.multifilePythonCertProject ||
challengeType === challengeTypes.python
) {
const { required = [], template = '' } = challengeMetaSelector(state);
@@ -123,7 +123,8 @@ const buildFunctions = {
[challengeTypes.pythonProject]: buildBackendChallenge,
[challengeTypes.multifileCertProject]: buildDOMChallenge,
[challengeTypes.colab]: buildBackendChallenge,
[challengeTypes.python]: buildPythonChallenge
[challengeTypes.python]: buildPythonChallenge,
[challengeTypes.multifilePythonCertProject]: buildPythonChallenge
};
export function canBuildChallenge(challengeData: BuildChallengeData): boolean {
@@ -151,7 +152,8 @@ const testRunners = {
[challengeTypes.backend]: getDOMTestRunner,
[challengeTypes.pythonProject]: getDOMTestRunner,
[challengeTypes.python]: getPyTestRunner,
[challengeTypes.multifileCertProject]: getDOMTestRunner
[challengeTypes.multifileCertProject]: getDOMTestRunner,
[challengeTypes.multifilePythonCertProject]: getPyTestRunner
};
// TODO: Figure out and (hopefully) simplify the return type.
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
@@ -322,7 +324,10 @@ export function buildPythonChallenge({
.then(checkFilesErrors)
// Unlike the DOM challenges, there's no need to embed the files in HTML
.then(challengeFiles => ({
challengeType: challengeTypes.python,
challengeType:
challengeFiles[0].editableRegionBoundaries?.length === 0
? challengeTypes.multifilePythonCertProject
: challengeTypes.python,
sources: buildSourceMap(challengeFiles)
}))
);
@@ -393,6 +398,7 @@ export function challengeHasPreview({ challengeType }: ChallengeMeta): boolean {
challengeType === challengeTypes.html ||
challengeType === challengeTypes.modern ||
challengeType === challengeTypes.multifileCertProject ||
challengeType === challengeTypes.multifilePythonCertProject ||
challengeType === challengeTypes.python
);
}
@@ -21,6 +21,7 @@ import { playTone } from '../../../utils/tone';
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
import { isGridBased, isProjectBased } from '../../../utils/curriculum-layout';
import Challenges from './challenges';
import '../intro.css';
const { curriculumLocale, showUpcomingChanges, showNewCurriculum } = envData;
@@ -1,4 +1,5 @@
import { challengeFiles } from '../../../utils/__fixtures__/challenges';
import { challengeTypes } from '../../../../shared/config/challenge-types';
const baseChallenge = {
id: '1',
@@ -29,7 +30,12 @@ export const withChallenges = {
export const multifileSolution = {
...withChallenges,
challengeType: 14
challengeType: challengeTypes.multifileCertProject
}
export const multifilePythonSolution = {
...withChallenges,
challengeType: challengeTypes.multifilePythonCertProject
}
export const onlyGithubLink = {
+8 -4
View File
@@ -16,10 +16,13 @@ export const isGridBased = (
) => {
// Python projects should not be displayed as a grid, but should be displayed
// as a list of projects. Otherwise, if we do not do this the project will be
// shown as a single certificaton project, which is not what we want.
if (challengeType === 10) return false;
// shown as a single certification project.
if (
challengeType === challengeTypes.pythonProject ||
challengeType === challengeTypes.multifilePythonCertProject
)
return false;
return gridBasedSuperBlocks.includes(superBlock);
};
@@ -33,7 +36,8 @@ const projectBasedChallengeTypes = [
challengeTypes.codeAllyCert,
challengeTypes.multifileCertProject,
challengeTypes.exam,
challengeTypes.codeAllyPractice
challengeTypes.codeAllyPractice,
challengeTypes.multifilePythonCertProject
];
export const isProjectBased = (
@@ -2,6 +2,7 @@ import {
bothLinks,
invalidGithubLink,
legacySolution,
multifilePythonSolution,
multifileSolution,
onlyGithubLink,
onlySolution,
@@ -17,14 +18,15 @@ describe('getSolutionDisplayType', () => {
expect(getSolutionDisplayType(legacySolution)).toBe('showUserCode');
});
it('should handle solutions with files', () => {
expect.assertions(2);
expect(getSolutionDisplayType(withChallenges)).toBe('showUserCode');
expect(getSolutionDisplayType(multifileSolution)).toBe(
'showMultifileProjectSolution'
);
expect(getSolutionDisplayType(multifilePythonSolution)).toBe(
'showMultifileProjectSolution'
);
});
it('should handle solutions with a single valid url', () => {
expect.assertions(2);
expect(getSolutionDisplayType(onlySolution)).toBe('showProjectLink');
expect(getSolutionDisplayType(invalidGithubLink)).toBe('showProjectLink');
});
+2 -1
View File
@@ -19,7 +19,8 @@ export const getSolutionDisplayType = ({
}: CompletedChallenge): DisplayType => {
if (examResults) return 'showExamResults';
if (challengeFiles?.length)
return challengeType === challengeTypes.multifileCertProject
return challengeType === challengeTypes.multifileCertProject ||
challengeType === challengeTypes.multifilePythonCertProject
? 'showMultifileProjectSolution'
: 'showUserCode';
if (!solution) return 'none';
@@ -170,6 +170,7 @@ function getProjectPreviewConfig(challenge, allChallengeEdges) {
challengeOrder === 0 &&
usesMultifileEditor &&
challengeType !== challengeTypes.multifileCertProject &&
challengeType !== challengeTypes.multifilePythonCertProject &&
// TODO: revert this to enable project previews for python challenges
challengeType !== challengeTypes.python,
challengeData: {
@@ -1,22 +1,13 @@
---
id: 5e44412c903586ffb414c94c
title: Arithmetic Formatter
challengeType: 10
challengeType: 23
forumTopicId: 462359
dashedName: arithmetic-formatter
---
# --description--
You will be <a href="https://replit.com/github/freeCodeCamp/boilerplate-arithmetic-formatter" target="_blank" rel="noopener noreferrer nofollow"> working on this project with our Replit starter code</a>.
- Start by importing the project on Replit.
- Next, you will see a `.replit` window.
- Select `Use run command` and click the `Done` button.
# --instructions--
Students in primary school often arrange arithmetic problems vertically to make them easier to solve. For example, "235 + 52" becomes:
```py
@@ -25,7 +16,7 @@ Students in primary school often arrange arithmetic problems vertically to make
-----
```
Create a function that receives a list of strings that are arithmetic problems and returns the problems arranged vertically and side-by-side. The function should optionally take a second argument. When the second argument is set to `True`, the answers should be displayed.
Finish the `arithmetic_arranger` function that receives a list of strings which are arithmetic problems, and returns the problems arranged vertically and side-by-side. The function should optionally take a second argument. When the second argument is set to `True`, the answers should be displayed.
## Example
@@ -62,7 +53,6 @@ Output:
The function will return the correct conversion if the supplied problems are properly formatted, otherwise, it will **return** a **string** that describes an error that is meaningful to the user.
- Situations that will return an error:
- If there are **too many problems** supplied to the function. The limit is **five**, anything more will return:
`Error: Too many problems.`
@@ -78,32 +68,185 @@ The function will return the correct conversion if the supplied problems are pro
- There should be four spaces between each problem.
- There should be dashes at the bottom of each problem. The dashes should run along the entire length of each problem individually. (The example above shows what this should look like.)
## Development
Write your code in `arithmetic_arranger.py`. For development, you can use `main.py` to test your `arithmetic_arranger()` function. Click the "run" button and `main.py` will run.
## Testing
The unit tests for this project are in `test_module.py`. We are running the tests from `test_module.py` in `main.py` for your convenience. The tests will run automatically whenever you hit the "run" button. Alternatively you may run the tests by inputting `pytest` in the console.
## Submitting
Copy your project's URL and submit it to freeCodeCamp.
# --hints--
It should correctly format an arithmetic problem and pass all tests.
`arithmetic_arranger(["3801 - 2", "123 + 49"])` should return ` 3801 123\n- 2 + 49\n------ -----`.
```js
({
test: () => {
runPython(`
from unittest import TestCase
TestCase().assertEqual(arithmetic_arranger(["3801 - 2", "123 + 49"]), ' 3801 123\\n- 2 + 49\\n------ -----')`);
}
})
```
`arithmetic_arranger(["1 + 2", "1 - 9380"])` should return ` 1 1\n+ 2 - 9380\n--- ------`.
```js
({
test: () => {
runPython(`
from unittest import TestCase
TestCase().assertEqual(arithmetic_arranger(["1 + 2", "1 - 9380"]), ' 1 1\\n+ 2 - 9380\\n--- ------')`);
}
})
```
`arithmetic_arranger(["3 + 855", "3801 - 2", "45 + 43", "123 + 49"])` should return ` 3 3801 45 123\n+ 855 - 2 + 43 + 49\n----- ------ ---- -----`.
```js
({
test: () => {
runPython(`
from unittest import TestCase
TestCase().assertEqual(arithmetic_arranger(["3 + 855", "3801 - 2", "45 + 43", "123 + 49"]), ' 3 3801 45 123\\n+ 855 - 2 + 43 + 49\\n----- ------ ---- -----')`);
}
})
```
`arithmetic_arranger(["11 + 4", "3801 - 2999", "1 + 2", "123 + 49", "1 - 9380"])` should return ` 11 3801 1 123 1\n+ 4 - 2999 + 2 + 49 - 9380\n---- ------ --- ----- ------`.
```js
({
test: () => {
runPython(`
from unittest import TestCase
TestCase().assertEqual(arithmetic_arranger(["11 + 4", "3801 - 2999", "1 + 2", "123 + 49", "1 - 9380"]), ' 11 3801 1 123 1\\n+ 4 - 2999 + 2 + 49 - 9380\\n---- ------ --- ----- ------')`);
}
})
```
`arithmetic_arranger(["44 + 815", "909 - 2", "45 + 43", "123 + 49", "888 + 40", "653 + 87"])` should return `Error: Too many problems.`.
```js
({
test: () => {
runPython(`
from unittest import TestCase
TestCase().assertEqual(arithmetic_arranger(["44 + 815", "909 - 2", "45 + 43", "123 + 49", "888 + 40", "653 + 87"]), 'Error: Too many problems.')`);
}
})
```
`arithmetic_arranger(["3 / 855", "3801 - 2", "45 + 43", "123 + 49"])` should return `Error: Operator must be '+' or '-'.`.
```js
({
test: () => {
runPython(`
from unittest import TestCase
TestCase().assertEqual(arithmetic_arranger(["3 / 855", "3801 - 2", "45 + 43", "123 + 49"]), "Error: Operator must be '+' or '-'.")`);
}
})
```
`arithmetic_arranger(["24 + 85215", "3801 - 2", "45 + 43", "123 + 49"])` should return `Error: Numbers cannot be more than four digits.`.
```js
({
test: () => {
runPython(`
from unittest import TestCase
TestCase().assertEqual(arithmetic_arranger(["24 + 85215", "3801 - 2", "45 + 43", "123 + 49"]), "Error: Numbers cannot be more than four digits.")`);
}
})
```
`arithmetic_arranger(["98 + 3g5", "3801 - 2", "45 + 43", "123 + 49"])` should return `Error: Numbers must only contain digits.`.
```js
({
test: () => {
runPython(`
from unittest import TestCase
TestCase().assertEqual(arithmetic_arranger(["98 + 3g5", "3801 - 2", "45 + 43", "123 + 49"]), "Error: Numbers must only contain digits.")`);
}
})
```
`arithmetic_arranger(["3 + 855", "988 + 40"], True)` should return ` 3 988\n+ 855 + 40\n----- -----\n 858 1028`.
```js
({
test: () => {
runPython(`
from unittest import TestCase
TestCase().assertEqual(arithmetic_arranger(["3 + 855", "988 + 40"], True), " 3 988\\n+ 855 + 40\\n----- -----\\n 858 1028")`);
}
})
```
`arithmetic_arranger(["32 - 698", "1 - 3801", "45 + 43", "123 + 49", "988 + 40"], True)` should return ` 32 1 45 123 988\n- 698 - 3801 + 43 + 49 + 40\n----- ------ ---- ----- -----\n -666 -3800 88 172 1028`.
```js
({
test: () => {
runPython(`
from unittest import TestCase
TestCase().assertEqual(arithmetic_arranger(["32 - 698", "1 - 3801", "45 + 43", "123 + 49", "988 + 40"], True), " 32 1 45 123 988\\n- 698 - 3801 + 43 + 49 + 40\\n----- ------ ---- ----- -----\\n -666 -3800 88 172 1028")`);
}
})
```
# --seed--
## --seed-contents--
```py
def arithmetic_arranger(problems, show_answers=False):
return problems
print(f'\n{arithmetic_arranger(["32 + 698", "3801 - 2", "45 + 43", "123 + 49"])}')
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```py
def arithmetic_arranger(problems, result=False):
lin1 = ""
lin2 = ""
lin3 = ""
lin4 = ""
if len(problems) > 5:
return 'Error: Too many problems.'
for problem in problems:
[num1, sym, num2] = problem.split()
sign = ['+', '-']
if sym not in sign:
return ("Error: Operator must be '+' or '-'.")
if len(num1) > 4 or len(num2) > 4:
return ("Error: Numbers cannot be more than four digits.")
if not num1.isnumeric() or not num2.isnumeric():
return ("Error: Numbers must only contain digits.")
lin1 += " " + num1 + " " if len(num1) >= len(
num2) else " " * (len(num2) + 2 - len(num1)) + num1 + " "
lin2 += sym + " " + num2 + " " if len(num2) >= len(
num1) else sym + " " * (len(num1) - len(num2) + 1) + num2 + " "
nmax = (len(num1) + 2) if len(num1) >= len(num2) else (len(num2) + 2)
lin3 += "-" * nmax + " "
op = int(num1) + int(num2) if sym == "+" else int(num1) - int(num2)
lin4 += (" " * (nmax - len(str(op)))) + str(op) + " "
arranged_problems = lin1.rstrip() + "\n" + lin2.rstrip(
) + "\n" + lin3.rstrip()
if result:
arranged_problems += "\n" + lin4.rstrip()
return arranged_problems
```
@@ -1,23 +1,14 @@
---
id: 5e44413e903586ffb414c94e
title: Budget App
challengeType: 10
challengeType: 23
forumTopicId: 462361
dashedName: budget-app
---
# --description--
You will be <a href="https://replit.com/github/freeCodeCamp/boilerplate-budget-app" target="_blank" rel="noopener noreferrer nofollow">working on this project with our Replit starter code</a>.
- Start by importing the project on Replit.
- Next, you will see a `.replit` window.
- Select `Use run command` and click the `Done` button.
# --instructions--
Complete the `Category` class in `budget.py`. It should be able to instantiate objects based on different budget categories like *food*, *clothing*, and *entertainment*. When objects are created, they are passed in the name of the category. The class should have an instance variable called `ledger` that is a list. The class should also contain the following methods:
Complete the `Category` class. It should be able to instantiate objects based on different budget categories like *food*, *clothing*, and *entertainment*. When objects are created, they are passed in the name of the category. The class should have an instance variable called `ledger` that is a list. The class should also contain the following methods:
- A `deposit` method that accepts an amount and description. If no description is given, it should default to an empty string. The method should append an object to the ledger list in the form of `{"amount": amount, "description": description}`.
- A `withdraw` method that is similar to the `deposit` method, but the amount passed in should be stored in the ledger as a negative number. If there are not enough funds, nothing should be added to the ledger. This method should return `True` if the withdrawal took place, and `False` otherwise.
@@ -31,7 +22,19 @@ When the budget object is printed it should display:
- A list of the items in the ledger. Each line should show the description and amount. The first 23 characters of the description should be displayed, then the amount. The amount should be right aligned, contain two decimal places, and display a maximum of 7 characters.
- A line displaying the category total.
Here is an example of the output:
Here is an example usage:
```py
food = Category("Food")
food.deposit(1000, "deposit")
food.withdraw(10.15, "groceries")
food.withdraw(15.89, "restaurant and more food for dessert")
clothing = Category("Clothing")
food.transfer(50, clothing)
print(food)
```
And here is an example of the output:
```bash
*************Food*************
@@ -74,34 +77,811 @@ Percentage spent by category
g
```
The unit tests for this project are in `test_module.py`.
## Development
Write your code in `budget.py`. For development, you can use `main.py` to test your `Category` class. Click the "run" button and `main.py` will run.
## Testing
We imported the tests from `test_module.py` to `main.py` for your convenience. The tests will run automatically whenever you hit the "run" button.
## Submitting
Copy your project's URL and submit it to freeCodeCamp.
# --hints--
It should create a Category class and pass all tests.
The `deposit` method should create a specific object in the ledger instance variable.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
def test_deposit(self):
self.food.deposit(900, "deposit")
actual = self.food.ledger[0]
expected = {"amount": 900, "description": "deposit"}
self.assertEqual(actual, expected, 'Expected "deposit" method to create a specific object in the ledger instance variable.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling the `deposit` method with no description should create a blank description.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
def test_deposit_no_description(self):
self.food.deposit(45.56)
actual = self.food.ledger[0]
expected = {"amount": 45.56, "description": ""}
self.assertEqual(actual, expected, 'Expected calling "deposit" method with no description to create a blank description.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `withdraw` method should create a specific object in the `ledger` instance variable.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
def test_withdraw(self):
self.food.deposit(900, "deposit")
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
actual = self.food.ledger[1]
expected = {"amount": -45.67, "description": "milk, cereal, eggs, bacon, bread"}
self.assertEqual(actual, expected, 'Expected "withdraw" method to create a specific object in the ledger instance variable.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling the `withdraw` method with no description should create a blank description.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
def test_withdraw_no_description(self):
self.food.deposit(900, "deposit")
good_withdraw = self.food.withdraw(45.67)
actual = self.food.ledger[1]
expected = {"amount": -45.67, "description": ""}
self.assertEqual(actual, expected, 'Expected "withdraw" method with no description to create a blank description.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `withdraw` method should return `True` if the withdrawal took place.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
def test_withdraw_no_description(self):
self.food.deposit(900, "deposit")
good_withdraw = self.food.withdraw(45.67)
self.assertEqual(good_withdraw, True, 'Expected "withdraw" method to return "True".')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling `food.deposit(900, "deposit")` and `food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")` should return a balance of `854.33`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
def test_get_balance(self):
self.food.deposit(900, "deposit")
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
actual = self.food.get_balance()
expected = 854.33
self.assertEqual(actual, expected, 'Expected balance to be 854.33')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling the `transfer` method on a category object should create a specific ledger item in that category object.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
self.entertainment = budget.Category("Entertainment")
def test_transfer(self):
self.food.deposit(900, "deposit")
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
transfer_amount = 20
good_transfer = self.food.transfer(transfer_amount, self.entertainment)
actual = self.food.ledger[2]
expected = {"amount": -transfer_amount, "description": "Transfer to Entertainment"}
self.assertEqual(actual, expected, 'Expected "transfer" method to create a specific ledger item in food object.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `transfer` method should return `True` if the transfer took place.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
self.entertainment = budget.Category("Entertainment")
def test_transfer(self):
self.food.deposit(900, "deposit")
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
transfer_amount = 20
good_transfer = self.food.transfer(transfer_amount, self.entertainment)
self.assertEqual(good_transfer, True, 'Expected "transfer" method to return "True".')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling `transfer` on a category object should reduce the balance in the category object.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
self.entertainment = budget.Category("Entertainment")
def test_transfer(self):
self.food.deposit(900, "deposit")
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
transfer_amount = 20
food_balance_before = self.food.get_balance()
good_transfer = self.food.transfer(transfer_amount, self.entertainment)
food_balance_after = self.food.get_balance()
self.assertEqual(food_balance_before - food_balance_after, transfer_amount, 'Expected "transfer" method to reduce balance in food object.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `transfer` method should increase the balance of the category object passed as its argument.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
self.entertainment = budget.Category("Entertainment")
def test_transfer(self):
self.food.deposit(900, "deposit")
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
transfer_amount = 20
entertainment_balance_before = self.entertainment.get_balance()
good_transfer = self.food.transfer(transfer_amount, self.entertainment)
entertainment_balance_after = self.entertainment.get_balance()
self.assertEqual(entertainment_balance_after - entertainment_balance_before, transfer_amount, 'Expected "transfer" method to increase balance in entertainment object.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `transfer` method should create a specific ledger item in the category object passed as its argument.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
self.entertainment = budget.Category("Entertainment")
def test_transfer(self):
self.food.deposit(900, "deposit")
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
transfer_amount = 20
good_transfer = self.food.transfer(transfer_amount, self.entertainment)
actual = self.entertainment.ledger[0]
expected = {"amount": transfer_amount, "description": "Transfer from Food"}
self.assertEqual(actual, expected, 'Expected "transfer" method to create a specific ledger item in entertainment object.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `check_funds` method should return `False` if the amount passed to the method is greater than the category balance.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
def test_check_funds(self):
self.food.deposit(10, "deposit")
actual = self.food.check_funds(20)
expected = False
self.assertEqual(actual, expected, 'Expected "check_funds" method to be False')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `check_funds` method should return `True` if the amount passed to the method is not greater than the category balance.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
def test_check_funds(self):
self.food.deposit(10, "deposit")
actual = self.food.check_funds(10)
expected = True
self.assertEqual(actual, expected, 'Expected "check_funds" method to be True')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `withdraw` method should return `False` if the withdrawal didn't take place.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
def test_withdraw_no_funds(self):
self.food.deposit(100, "deposit")
good_withdraw = self.food.withdraw(100.10)
self.assertEqual(good_withdraw, False, 'Expected "withdraw" method to return "False".')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `transfer` method should return `False` if the transfer didn't take place.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
self.entertainment = budget.Category("Entertainment")
def test_transfer_no_funds(self):
self.food.deposit(100, "deposit")
good_transfer = self.food.transfer(200, self.entertainment)
self.assertEqual(good_transfer, False, 'Expected "transfer" method to return "False".')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Printing a `Category` instance should give a different string representation of the object.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
self.entertainment = budget.Category("Entertainment")
def test_to_string(self):
self.food.deposit(900, "deposit")
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
self.food.transfer(20, self.entertainment)
actual = str(self.food)
expected = "*************Food*************\\ndeposit 900.00\\nmilk, cereal, eggs, bac -45.67\\nTransfer to Entertainme -20.00\\nTotal: 834.33"
self.assertEqual(actual, expected, 'Expected different string representation of object.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
`create_spend_chart` should print a different chart representation. Check that all spacing is exact.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/budget.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import budget
from importlib import reload
reload(budget)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.food = budget.Category("Food")
self.entertainment = budget.Category("Entertainment")
self.business = budget.Category("Business")
def test_create_spend_chart(self):
self.food.deposit(900, "deposit")
self.entertainment.deposit(900, "deposit")
self.business.deposit(900, "deposit")
self.food.withdraw(105.55)
self.entertainment.withdraw(33.40)
self.business.withdraw(10.99)
actual = budget.create_spend_chart([self.business, self.food, self.entertainment])
expected = "Percentage spent by category\\n100| \\n 90| \\n 80| \\n 70| o \\n 60| o \\n 50| o \\n 40| o \\n 30| o \\n 20| o o \\n 10| o o \\n 0| o o o \\n ----------\\n B F E \\n u o n \\n s o t \\n i d e \\n n r \\n e t \\n s a \\n s i \\n n \\n m \\n e \\n n \\n t "
self.assertEqual(actual, expected, 'Expected different chart representation. Check that all spacing is exact.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
# --seed--
## --seed-contents--
```py
class Category:
pass
def create_spend_chart(categories):
pass
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```py
class Category:
def __init__(self, name):
self.name = name
self.ledger = []
self.balance = 0
self.spent = 0
def __str__(self):
first_line = f'{self.name.center(30, "*")}\n'
lines = ''
total = f'Total: {format(self.balance, ".2f")}'
for n in range(len(self.ledger)):
descr = self.ledger[n]["description"][:23]
am = format(float(self.ledger[n]["amount"]), ".2f")[:7]
lines = lines + f'{descr:<23}{am:>7}\n'
return f'{first_line}{lines}{total}'
def deposit(self, amount, description=''):
self.ledger.append({
'amount': float(amount),
'description': description
})
self.balance = self.balance + float(amount)
def withdraw(self, amount, description=''):
if self.check_funds(amount):
self.ledger.append({
'amount': -float(amount),
'description': description
})
self.balance = self.balance - float(amount)
self.spent = self.spent + float(amount)
return True
else:
return False
def get_balance(self):
return self.balance
def transfer(self, amount, category):
if self.check_funds(amount):
# withdraw
self.ledger.append({
'amount': -float(amount),
'description': f'Transfer to {category.name}'
})
self.balance = self.balance - float(amount)
# deposit
category.deposit(amount, f'Transfer from {self.name}')
return True
else:
return False
def check_funds(self, amount):
if float(amount) > self.balance:
return False
else:
return True
def create_spend_chart(categories):
total_expenses = 0
obj = {}
col1 = []
str = []
final_str = 'Percentage spent by category\n'
label_max_length = 0
label_strings = []
for category in categories:
total_expenses = total_expenses + category.spent
obj[category.name] = {'expenses': category.spent}
obj[category.name]['label'] = list(category.name)
if len(obj[category.name]['label']) > label_max_length:
label_max_length = len(obj[category.name]['label'])
for category in categories:
obj[category.name]['percent'] = (
(category.spent / total_expenses * 100) // 10) * 10
obj[category.name]['column'] = []
for i in range(0, 110, 10):
if obj[category.name]['percent'] >= i:
obj[category.name]['column'].insert(0, 'o')
else:
obj[category.name]['column'].insert(0, ' ')
for i in range(0, 110, 10):
col1.insert(0, i)
for i in range(11):
str.append("")
for key in obj:
str[i] += (f'{obj[key]["column"][i]} ')
final_str += f'{col1[i]:>3}| {str[i]}\n'
final_str += f' {"-"*(1+3*len(obj))}\n '
for i in range(label_max_length):
label_strings.append(' ')
for k in obj:
if len(obj[k]['label']) < label_max_length:
obj[k]['label'].extend(
f'{" "*(label_max_length-len(obj[k]["label"]))}')
label_strings[i] += f'{obj[k]["label"][i]} '
if i < label_max_length - 1:
label_strings[i] += '\n '
final_str += label_strings[i]
print(final_str)
return (final_str)
```
@@ -1,23 +1,14 @@
---
id: 5e444147903586ffb414c94f
title: Polygon Area Calculator
challengeType: 10
challengeType: 23
forumTopicId: 462363
dashedName: polygon-area-calculator
---
# --description--
You will be <a href="https://replit.com/github/freeCodeCamp/boilerplate-polygon-area-calculator" target="_blank" rel="noopener noreferrer nofollow">working on this project with our Replit starter code</a>.
- Start by importing the project on Replit.
- Next, you will see a `.replit` window.
- Select `Use run command` and click the `Done` button.
# --instructions--
In this project you will use object oriented programming to create a Rectangle class and a Square class. The Square class should be a subclass of Rectangle and inherit methods and attributes.
In this project you will use object oriented programming to create a `Rectangle` class and a `Square` class. The `Square` class should be a subclass of `Rectangle`, and inherit its methods and attributes.
## Rectangle class
@@ -31,27 +22,27 @@ When a Rectangle object is created, it should be initialized with `width` and `h
- `get_picture`: Returns a string that represents the shape using lines of "\*". The number of lines should be equal to the height and the number of "\*" in each line should be equal to the width. There should be a new line (`\n`) at the end of each line. If the width or height is larger than 50, this should return the string: "Too big for picture.".
- `get_amount_inside`: Takes another shape (square or rectangle) as an argument. Returns the number of times the passed in shape could fit inside the shape (with no rotations). For instance, a rectangle with a width of 4 and a height of 8 could fit in two squares with sides of 4.
Additionally, if an instance of a Rectangle is represented as a string, it should look like: `Rectangle(width=5, height=10)`
Additionally, if an instance of a `Rectangle` is represented as a string, it should look like: `Rectangle(width=5, height=10)`
## Square class
The Square class should be a subclass of Rectangle. When a Square object is created, a single side length is passed in. The `__init__` method should store the side length in both the `width` and `height` attributes from the Rectangle class.
The `Square` class should be a subclass of `Rectangle`. When a `Square` object is created, a single side length is passed in. The `__init__` method should store the side length in both the `width` and `height` attributes from the `Rectangle` class.
The Square class should be able to access the Rectangle class methods but should also contain a `set_side` method. If an instance of a Square is represented as a string, it should look like: `Square(side=9)`
The `Square` class should be able to access the `Rectangle` class methods but should also contain a `set_side` method. If an instance of a `Square` is represented as a string, it should look like: `Square(side=9)`
Additionally, the `set_width` and `set_height` methods on the Square class should set both the width and height.
Additionally, the `set_width` and `set_height` methods on the `Square` class should set both the width and height.
## Usage example
```py
rect = shape_calculator.Rectangle(10, 5)
rect = Rectangle(10, 5)
print(rect.get_area())
rect.set_height(3)
print(rect.get_perimeter())
print(rect)
print(rect.get_picture())
sq = shape_calculator.Square(9)
sq = Square(9)
print(sq.get_area())
sq.set_side(4)
print(sq.get_diagonal())
@@ -84,34 +75,795 @@ Square(side=4)
8
```
The unit tests for this project are in `test_module.py`.
## Development
Write your code in `shape_calculator.py`. For development, you can use `main.py` to test your `shape_calculator()` function. Click the "run" button and `main.py` will run.
## Testing
We imported the tests from `test_module.py` to `main.py` for your convenience. The tests will run automatically whenever you hit the "run" button.
## Submitting
Copy your project's URL and submit it to freeCodeCamp.
# --hints--
It should create a Rectangle class and Square class and pass all tests.
The `Square` class should be a subclass of the `Rectangle` class.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_subclass(self):
actual = issubclass(shape_calculator.Square, shape_calculator.Rectangle)
expected = True
self.assertEqual(actual, expected, 'Expected Square class to be a subclass of the Rectangle class.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `Square` class should be a distinct class from the `Rectangle` class.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_distinct_classes(self):
actual = shape_calculator.Square is not shape_calculator.Rectangle
expected = True
self.assertEqual(actual, expected, 'Expected Square class to be a distinct class from the Rectangle class.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
A square object should be an instance of the `Square` class and the `Rectangle` class.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.sq = shape_calculator.Square(5)
def test_square_is_square_and_rectangle(self):
actual = isinstance(self.sq, shape_calculator.Square) and isinstance(self.sq, shape_calculator.Rectangle)
expected = True
self.assertEqual(actual, expected, 'Expected square object to be an instance of the Square class and the Rectangle class.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The string representation of `Rectangle(3, 6)` should be `Rectangle(width=3, height=6)`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.rect = shape_calculator.Rectangle(3, 6)
def test_rectangle_string(self):
actual = str(self.rect)
expected = "Rectangle(width=3, height=6)"
self.assertEqual(actual, expected, 'Expected string representation of rectangle to be "Rectangle(width=3, height=6)"')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The string representation of `Square(5)` should be `Square(side=5)`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.sq = shape_calculator.Square(5)
def test_square_string(self):
actual = str(self.sq)
expected = "Square(side=5)"
self.assertEqual(actual, expected, 'Expected string representation of square to be "Square(side=5)"')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
`Rectangle(3, 6).get_area()` should return `18`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.rect = shape_calculator.Rectangle(3, 6)
def test_area(self):
actual = self.rect.get_area()
expected = 18
self.assertEqual(actual, expected, 'Expected area of rectangle to be 18')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
`Square(5).get_area()` should return `25`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.sq = shape_calculator.Square(5)
def test_area(self):
actual = self.sq.get_area()
expected = 25
self.assertEqual(actual, expected, 'Expected area of square to be 25')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
`Rectangle(3, 6).get_perimeter()` should return `18`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.rect = shape_calculator.Rectangle(3, 6)
def test_perimeter(self):
actual = self.rect.get_perimeter()
expected = 18
self.assertEqual(actual, expected, 'Expected perimeter of rectangle to be 18')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
`Square(5).get_perimeter()` should return `20`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.sq = shape_calculator.Square(5)
def test_perimeter(self):
actual = self.sq.get_perimeter()
expected = 20
self.assertEqual(actual, expected, 'Expected perimeter of square to be 20')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
`Rectangle(3, 6).get_diagonal()` should return `6.708203932499369`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.rect = shape_calculator.Rectangle(3, 6)
def test_diagonal(self):
actual = self.rect.get_diagonal()
expected = 6.708203932499369
self.assertEqual(actual, expected, 'Expected diagonal of rectangle to be 6.708203932499369')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
`Square(5).get_diagonal()` should return `7.0710678118654755`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.sq = shape_calculator.Square(5)
def test_diagonal(self):
actual = self.sq.get_diagonal()
expected = 7.0710678118654755
self.assertEqual(actual, expected, 'Expected diagonal of square to be 7.0710678118654755')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
An instance of the `Rectangle` class should have a different string representation after setting new values.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.rect = shape_calculator.Rectangle(3, 6)
def test_set_attributes(self):
self.rect.set_width(7)
self.rect.set_height(8)
actual = str(self.rect)
expected = "Rectangle(width=7, height=8)"
self.assertEqual(actual, expected, 'Expected string representation of rectangle after setting new values to be "Rectangle(width=7, height=8)"')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
An instance of the `Square` class should have a different string representation after setting new values by using `.set_side()`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.sq = shape_calculator.Square(5)
def test_set_attributes(self):
self.sq.set_side(2)
actual = str(self.sq)
expected = "Square(side=2)"
self.assertEqual(actual, expected, 'Expected string representation of square after setting new values to be "Square(side=2)"')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
An instance of the `Square` class should have a different string representation after setting new values by using `.set_width()` or `set_height()`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.sq = shape_calculator.Square(5)
def test_set_attributes(self):
self.sq.set_height(2)
actual = str(self.sq)
expected = "Square(side=2)"
self.assertEqual(actual, expected, 'Expected string representation of square after setting new values to be "Square(side=2)"')
self.sq.set_width(4)
actual = str(self.sq)
expected = "Square(side=4)"
self.assertEqual(actual, expected, 'Expected string representation of square after setting width to be "Square(side=4)"')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `.get_picture()` method should return a different string representation of a `Rectangle` instance.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.rect = shape_calculator.Rectangle(3, 6)
def test_rectangle_picture(self):
self.rect.set_width(7)
self.rect.set_height(3)
actual = self.rect.get_picture()
expected = "*******\\n*******\\n*******\\n"
self.assertEqual(actual, expected, 'Expected rectangle picture to be different.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `.get_picture()` method should return a different string representation of a `Square` instance.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.sq = shape_calculator.Square(5)
def test_square_picture(self):
self.sq.set_side(2)
actual = self.sq.get_picture()
expected = "**\\n**\\n"
self.assertEqual(actual, expected, 'Expected square picture to be different.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
The `.get_picture()` method should return the string `Too big for picture.` if the `width` or `height` attributes are larger than `50`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.rect = shape_calculator.Rectangle(3, 6)
def test_big_picture(self):
self.rect.set_width(51)
self.rect.set_height(3)
actual = self.rect.get_picture()
expected = "Too big for picture."
self.assertEqual(actual, expected, 'Expected message: "Too big for picture."')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
`Rectangle(15,10).get_amount_inside(Square(5))` should return `6`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.rect = shape_calculator.Rectangle(3, 6)
self.sq = shape_calculator.Square(5)
def test_get_amount_inside(self):
self.rect.set_height(10)
self.rect.set_width(15)
actual = self.rect.get_amount_inside(self.sq)
expected = 6
self.assertEqual(actual, expected, 'Expected "get_amount_inside" to return 6.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
`Rectangle(4,8).get_amount_inside(Rectangle(3, 6))` should return `1`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.rect = shape_calculator.Rectangle(3, 6)
def test_get_amount_inside_two_rectangles(self):
rect2 = shape_calculator.Rectangle(4, 8)
actual = rect2.get_amount_inside(self.rect)
expected = 1
self.assertEqual(actual, expected, 'Expected "get_amount_inside" to return 1.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
`Rectangle(2,3).get_amount_inside(Rectangle(3, 6))` should return `0`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/shape_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py',`
import unittest
import shape_calculator
from importlib import reload
reload(shape_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def setUp(self):
self.rect = shape_calculator.Rectangle(3, 6)
def test_get_amount_inside_none(self):
rect2 = shape_calculator.Rectangle(2, 3)
actual = rect2.get_amount_inside(self.rect)
expected = 0
self.assertEqual(actual, expected, 'Expected "get_amount_inside" to return 0.')
`);
const testCode = `
from unittest import main
from importlib import reload
import test_module
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
# --seed--
## --seed-contents--
```py
class Rectangle:
pass
class Square:
pass
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```py
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def __str__(self):
return f'Rectangle(width={self.width}, height={self.height})'
def set_width(self, width):
self.width = width
def set_height(self, height):
self.height = height
def get_area(self):
area = self.width * self.height
return area
def get_perimeter(self):
perimeter = self.width * 2 + self.height * 2
return perimeter
def get_diagonal(self):
diagonal = (self.width ** 2 + self.height ** 2) ** 0.5
return diagonal
def get_picture(self):
if self.width < 50 and self.height < 50:
picture = f'{"*"*self.width}\n'*self.height
return picture
else:
return 'Too big for picture.'
def get_amount_inside(self, polygon):
h_number = self.height // polygon.height
w_number = self.width // polygon.width
repetition = h_number * w_number
return repetition
class Square(Rectangle):
def __init__(self, side):
self.width = side
self.height = side
def __str__(self):
return f'Square(side={self.width})'
def set_width(self, side):
self.width = side
self.height = side
def set_height(self, side):
self.height = side
self.width = side
def set_side(self,side):
self.width = side
self.height = side
```
@@ -1,27 +1,18 @@
---
id: 5e44414f903586ffb414c950
title: Probability Calculator
challengeType: 10
challengeType: 23
forumTopicId: 462364
dashedName: probability-calculator
---
# --description--
You will be <a href="https://replit.com/github/freeCodeCamp/boilerplate-probability-calculator" target="_blank" rel="noopener noreferrer nofollow">working on this project with our Replit starter code</a>.
- Start by importing the project on Replit.
- Next, you will see a `.replit` window.
- Select `Use run command` and click the `Done` button.
# --instructions--
Suppose there is a hat containing 5 blue balls, 4 red balls, and 2 green balls. What is the probability that a random draw of 4 balls will contain at least 1 red ball and 2 green balls? While it would be possible to calculate the probability using advanced mathematics, an easier way is to write a program to perform a large number of experiments to estimate an approximate probability.
For this project, you will write a program to determine the approximate probability of drawing certain balls randomly from a hat.
First, create a `Hat` class in `prob_calculator.py`. The class should take a variable number of arguments that specify the number of balls of each color that are in the hat. For example, a class object could be created in any of these ways:
First, create a `Hat` class in `main.py`. The class should take a variable number of arguments that specify the number of balls of each color that are in the hat. For example, a class object could be created in any of these ways:
```py
hat1 = Hat(yellow=3, blue=2, green=6)
@@ -33,7 +24,7 @@ A hat will always be created with at least one ball. The arguments passed into t
The `Hat` class should have a `draw` method that accepts an argument indicating the number of balls to draw from the hat. This method should remove balls at random from `contents` and return those balls as a list of strings. The balls should not go back into the hat during the draw, similar to an urn experiment without replacement. If the number of balls to draw exceeds the available quantity, return all the balls.
Next, create an `experiment` function in `prob_calculator.py` (not inside the `Hat` class). This function should accept the following arguments:
Next, create an `experiment` function in `main.py` (not inside the `Hat` class). This function should accept the following arguments:
- `hat`: A hat object containing balls that should be copied inside the function.
- `expected_balls`: An object indicating the exact group of balls to attempt to draw from the hat for the experiment. For example, to determine the probability of drawing 2 blue balls and 1 red ball from the hat, set `expected_balls` to `{"blue":2, "red":1}`.
@@ -54,38 +45,215 @@ probability = experiment(hat=hat,
num_experiments=2000)
```
The output would be something like this:
```bash
>>> 0.356
```
Since this is based on random draws, the probability will be slightly different each time the code is run.
*Hint: Consider using the modules that are already imported at the top of `prob_calculator.py`. Do not initialize random seed within `prob_calculator.py`.*
_Hint: Consider using the modules that are already imported at the top. Do not initialize random seed within the file._
## Development
Write your code in `prob_calculator.py`. For development, you can use `main.py` to test your code. Click the "run" button and `main.py` will run.
The boilerplate includes `import` statements for the `copy` and `random` modules. Consider using those in your project.
## Testing
The unit tests for this project are in `test_module.py`. We imported the tests from `test_module.py` to `main.py` for your convenience. The tests will run automatically whenever you hit the "run" button.
## Submitting
Copy your project's URL and submit it to freeCodeCamp.
# --hints--
It should correctly calculate probabilities and pass all tests.
Creation of `hat` object should add correct contents.
```js
({
test: () => {
pyodide.FS.writeFile("/home/pyodide/probability_calculator.py", code);
pyodide.FS.writeFile(
"/home/pyodide/test_module.py",
`
import unittest
import probability_calculator
from importlib import reload
reload(probability_calculator)
probability_calculator.random.seed(95)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_hat_class_contents(self):
hat = probability_calculator.Hat(red=3,blue=2)
actual = hat.contents
expected = ["red","red","red","blue","blue"]
self.assertEqual(actual, expected, 'Expected creation of hat object to add correct contents.')
`
);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
},
});
```
The `draw` method in `hat` class should reduce number of items in contents.
```js
({
test: () => {
pyodide.FS.writeFile("/home/pyodide/probability_calculator.py", code);
pyodide.FS.writeFile(
"/home/pyodide/test_module.py",
`
import unittest
import probability_calculator
from importlib import reload
reload(probability_calculator)
probability_calculator.random.seed(95)
def test_hat_draw(self):
hat = probability_calculator.Hat(red=5,blue=2)
actual = hat.draw(2)
expected = ['blue', 'red']
self.assertEqual(actual, expected, 'Expected hat draw to return two random items from hat contents.')
actual = len(hat.contents)
expected = 5
self.assertEqual(actual, expected, 'Expected hat draw to reduce number of items in contents.')
`
);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
},
});
```
The `experiment` method should return a different probability.
```js
({
test: () => {
pyodide.FS.writeFile("/home/pyodide/probability_calculator.py", code);
pyodide.FS.writeFile(
"/home/pyodide/test_module.py",
`
import unittest
import probability_calculator
from importlib import reload
reload(probability_calculator)
probability_calculator.random.seed(95)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_prob_experiment(self):
hat = probability_calculator.Hat(blue=3,red=2,green=6)
probability = probability_calculator.experiment(hat=hat, expected_balls={"blue":2,"green":1}, num_balls_drawn=4, num_experiments=1000)
actual = probability
expected = 0.272
self.assertAlmostEqual(actual, expected, delta = 0.01, msg = 'Expected experiment method to return a different probability.')
hat = probability_calculator.Hat(yellow=5,red=1,green=3,blue=9,test=1)
probability = probability_calculator.experiment(hat=hat, expected_balls={"yellow":2,"blue":3,"test":1}, num_balls_drawn=20, num_experiments=100)
actual = probability
expected = 1.0
self.assertAlmostEqual(actual, expected, delta = 0.01, msg = 'Expected experiment method to return a different probability.')
`
);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
},
});
```
# --seed--
## --seed-contents--
```py
import copy
import random
class Hat:
pass
def experiment(hat, expected_balls, num_balls_drawn, num_experiments):
pass
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```py
import copy
import random
class Hat:
def __init__(self, **hat):
self.hat = hat
contents = []
for i in hat:
for j in range(hat[i]):
contents.append(i)
self.contents = contents
def draw(self, number):
drawn = []
if number >= len(self.contents):
return self.contents
else:
for i in range(number):
drawn.append(
self.contents.pop(random.randrange(len(self.contents)))
)
return drawn
def experiment(hat, expected_balls, num_balls_drawn, num_experiments):
expected_balls_list = []
drawn_list = []
success = 0
for i in expected_balls:
for j in range(expected_balls[i]):
expected_balls_list.append(i)
for j in range(num_experiments):
hat_copy = copy.deepcopy(hat)
drawn_list.append(hat_copy.draw(num_balls_drawn))
exp_ball_list_copy = expected_balls_list[:]
for k in range(len(drawn_list[j])):
try:
ind = exp_ball_list_copy.index(drawn_list[j][k])
exp_ball_list_copy.pop(ind)
except:
continue
if len(exp_ball_list_copy) == 0:
success += 1
probability = success/num_experiments
return probability
```
@@ -1,21 +1,13 @@
---
id: 5e444136903586ffb414c94d
title: Time Calculator
challengeType: 10
challengeType: 23
forumTopicId: 462360
dashedName: time-calculator
---
# --description--
You will be <a href="https://replit.com/github/freeCodeCamp/boilerplate-time-calculator" target="_blank" rel="noopener noreferrer nofollow">working on this project with our Replit starter code</a>.
- Start by importing the project on Replit.
- Next, you will see a `.replit` window.
- Select `Use run command` and click the `Done` button.
# --instructions--
Write a function named `add_time` that takes in two required parameters and one optional parameter:
- a start time in the 12-hour clock format (ending in AM or PM)
@@ -52,32 +44,539 @@ add_time("6:30 PM", "205:12")
Do not import any Python libraries. Assume that the start times are valid times. The minutes in the duration time will be a whole number less than 60, but the hour can be any whole number.
## Development
Write your code in `time_calculator.py`. For development, you can use `main.py` to test your `time_calculator()` function. Click the "run" button and `main.py` will run.
## Testing
The unit tests for this project are in `test_module.py`. We imported the tests from `test_module.py` to `main.py` for your convenience. The tests will run automatically whenever you hit the "run" button.
## Submitting
Copy your project's URL and submit it to freeCodeCamp.
# --hints--
It should correctly add times and pass all tests.
Calling `add_time("3:30 PM", "2:12")` should return `5:42 PM`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
import time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_same_period(self):
actual = time_calculator.add_time("3:30 PM", "2:12")
expected = "5:42 PM"
self.assertEqual(actual, expected, 'Expected calling "add_time()" with "3:30 PM", "2:12" to return "5:42 PM"')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling `add_time("11:55 AM", "3:12")` should return `3:07 PM`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_different_period(self):
actual = time_calculator.add_time("11:55 AM", "3:12")
expected = "3:07 PM"
self.assertEqual(actual, expected, 'Expected calling "add_time()" with "11:55 AM", "3:12" to return "3:07 PM"')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Expected time to end with `"(next day)"` when it is the next day.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_next_day(self):
actual = time_calculator.add_time("9:15 PM", "5:30")
expected = "2:45 AM (next day)"
self.assertEqual(actual, expected, 'Expected time to end with "(next day)" when it is the next day.')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Expected period to change from `AM` to `PM` at `12:00`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_period_change_at_twelve(self):
actual = time_calculator.add_time("11:40 AM", "0:25")
expected = "12:05 PM"
self.assertEqual(actual, expected, 'Expected period to change from AM to PM at 12:00')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling `add_time("2:59 AM", "24:00")` should return `2:59 AM`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_twenty_four(self):
actual = time_calculator.add_time("2:59 AM", "24:00")
expected = "2:59 AM (next day)"
self.assertEqual(actual, expected, 'Expected calling "add_time()" with "2:59 AM", "24:00" to return "2:59 AM"')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling `add_time("11:59 PM", "24:05")` should return `12:04 AM (2 days later)`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_two_days_later(self):
actual = time_calculator.add_time("11:59 PM", "24:05")
expected = "12:04 AM (2 days later)"
self.assertEqual(actual, expected, 'Expected calling "add_time()" with "11:59 PM", "24:05" to return "12:04 AM (2 days later)"')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling `add_time("8:16 PM", "466:02")` should return `6:18 AM (20 days later)`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_high_duration(self):
actual = time_calculator.add_time("8:16 PM", "466:02")
expected = "6:18 AM (20 days later)"
self.assertEqual(actual, expected, 'Expected calling "add_time()" with "8:16 PM", "466:02" to return "6:18 AM (20 days later)"')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Expected adding `0:00` to return the initial time.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_no_change(self):
actual = time_calculator.add_time("5:01 AM", "0:00")
expected = "5:01 AM"
self.assertEqual(actual, expected, 'Expected adding 0:00 to return initial time.')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling `add_time("3:30 PM", "2:12", "Monday")`should return `5:42 PM, Monday`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_same_period_with_day(self):
actual = time_calculator.add_time("3:30 PM", "2:12", "Monday")
expected = "5:42 PM, Monday"
self.assertEqual(actual, expected, 'Expected calling "add_time()" with "3:30 PM", "2:12", "Monday" to return "5:42 PM, Monday"')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling `add_time("2:59 AM", "24:00", "saturDay")` should return `2:59 AM, Sunday (next day)`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_twenty_four_with_day(self):
actual = time_calculator.add_time("2:59 AM", "24:00", "saturDay")
expected = "2:59 AM, Sunday (next day)"
self.assertEqual(actual, expected, 'Expected calling "add_time()" with "2:59 AM", "24:00", "saturDay" to return "2:59 AM, Sunday (next day)"')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling `add_time("11:59 PM", "24:05", "Wednesday")` should return `"12:04 AM, Friday (2 days later)"`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_two_days_later_with_day(self):
actual = time_calculator.add_time("11:59 PM", "24:05", "Wednesday")
expected = "12:04 AM, Friday (2 days later)"
self.assertEqual(actual, expected, 'Expected calling "add_time()" with "11:59 PM", "24:05", "Wednesday" to return "12:04 AM, Friday (2 days later)"')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
Calling `add_time("8:16 PM", "466:02", "tuesday") `should return `6:18 AM, Monday (20 days later)`.
```js
({
test: () => {
pyodide.FS.writeFile('/home/pyodide/time_calculator.py', code);
pyodide.FS.writeFile('/home/pyodide/test_module.py', `
import unittest
time_calculator
from importlib import reload
reload(time_calculator)
class UnitTests(unittest.TestCase):
maxDiff = None
def test_high_duration_with_day(self):
actual = time_calculator.add_time("8:16 PM", "466:02", "tuesday")
expected = "6:18 AM, Monday (20 days later)"
self.assertEqual(actual, expected, 'Expected calling "add_time()" with "8:16 PM", "466:02", "tuesday" to return "6:18 AM, Monday (20 days later)"')
`);
const testCode = `
from unittest import main
import test_module
from importlib import reload
reload(test_module)
t = main(module='test_module', exit=False)
t.result.wasSuccessful()
`;
const out = __pyodide.runPython(testCode);
assert(out);
}
})
```
# --seed--
## --seed-contents--
```py
def add_time(start, duration):
return new_time
```
# --solutions--
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
```py
def add_time(start, duration, day=''):
start_arr = start[0:-3].split(':')
dur_arr = duration.split(':')
tail = ''
# converting to 24h format
if 'AM' in start:
if start_arr[0] == '12':
start_arr[0] = '00'
elif 'PM' in start:
if start_arr[0] == '12':
start_arr[0] = '12'
else:
start_arr[0] = f'{int(start_arr[0]) + 12}'
# adding minutes
sum_m = int(start_arr[1]) + int(dur_arr[1])
if sum_m > 59:
if (sum_m - 60 * (sum_m//60)) < 10:
mins = f'0{(sum_m - 60 * (sum_m//60))}'
dur_arr[0] = int(dur_arr[0]) + sum_m//60
else:
mins = sum_m - 60 * (sum_m//60)
dur_arr[0] = int(dur_arr[0]) + sum_m//60
else:
if sum_m < 10:
mins = f'0{sum_m}'
else:
mins = sum_m
#adding hours
sum_h = int(start_arr[0]) + int(dur_arr[0])
if sum_h < 24:
hours= sum_h
#time_24 = f'{hours}:{mins}'
else:
days_after = sum_h//24
if days_after == 1:
hours = sum_h - 24
tail = ' (next day)'
else:
hours = sum_h - 24 * days_after
tail = f' ({days_after} days later)'
#converting back to AM/PM
if hours == 0:
hours = 12
time = f'{hours}:{mins} AM'
final_time = f'{time}{tail}'
elif hours < 12:
time = f'{hours}:{mins} AM'
final_time = f'{time}{tail}'
else:
if hours > 12:
hours = hours - 12
time = f'{hours}:{mins} PM'
final_time = f'{time}{tail}'
#days of the week
week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
if day:
day = day.capitalize()
if not tail:
final_time = f'{time}, {day}'
elif tail == ' (next day)':
index = week.index(day) + 1
if index == 7:
index = 0
week_day = f', {week[index]}'
final_time = f'{time}{week_day}{tail}'
elif tail:
index = (week.index(day) + days_after) % 7
week_day = f', {week[index]}'
final_time = f'{time}{week_day}{tail}'
print('\n')
print(final_time)
print('\n')
return final_time
```
@@ -92,7 +92,7 @@ const schema = Joi.object()
challengeOrder: Joi.number(),
removeComments: Joi.bool().required(),
certification: Joi.string().regex(slugWithSlashRE),
challengeType: Joi.number().min(0).max(22).required(),
challengeType: Joi.number().min(0).max(23).required(),
checksum: Joi.number(),
// TODO: require this only for normal challenges, not certs
dashedName: Joi.string().regex(slugRE),
+1 -1
View File
@@ -89,7 +89,7 @@ const schema = Joi.object()
challengeOrder: Joi.number(),
removeComments: Joi.bool().required(),
certification: Joi.string().regex(slugWithSlashRE),
challengeType: Joi.number().min(0).max(22).required(),
challengeType: Joi.number().min(0).max(23).required(),
checksum: Joi.number(),
// TODO: require this only for normal challenges, not certs
dashedName: Joi.string().regex(slugRE),
+3 -1
View File
@@ -408,7 +408,9 @@ function populateTestsForLang({ lang, challenges, meta, superBlocks }) {
{
[challengeTypes.js]: buildJSChallenge,
[challengeTypes.jsProject]: buildJSChallenge,
[challengeTypes.python]: buildPythonChallenge
[challengeTypes.python]: buildPythonChallenge,
[challengeTypes.multifilePythonCertProject]:
buildPythonChallenge
}[challengeType] ?? buildDOMChallenge;
// The python tests are (currently) slow, so we give them more time.
+7 -3
View File
@@ -22,6 +22,7 @@ const multipleChoice = 19;
const python = 20;
const dialogue = 21;
const fillInTheBlank = 22;
const multifilePythonCertProject = 23;
export const challengeTypes = {
html,
@@ -47,7 +48,8 @@ export const challengeTypes = {
multipleChoice,
python,
dialogue,
fillInTheBlank
fillInTheBlank,
multifilePythonCertProject
};
export const hasNoTests = (challengeType: number): boolean =>
@@ -105,7 +107,8 @@ export const viewTypes = {
[multipleChoice]: 'odin',
[python]: 'modern',
[dialogue]: 'dialogue',
[fillInTheBlank]: 'fillInTheBlank'
[fillInTheBlank]: 'fillInTheBlank',
[multifilePythonCertProject]: 'classic'
};
// determine the type of submit function to use for the challenge on completion
@@ -135,5 +138,6 @@ export const submitTypes = {
[multipleChoice]: 'tests',
[python]: 'tests',
[dialogue]: 'tests',
[fillInTheBlank]: 'tests'
[fillInTheBlank]: 'tests',
[multifilePythonCertProject]: 'tests'
};
@@ -134,6 +134,7 @@ function initRunPython() {
else:
return ""
`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const getResetId = globals.get('__get_reset_id') as PyProxy & (() => string);
return { runPython, getResetId, globals };