Merge branch 'master' into master
This commit is contained in:
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @exercism/python
|
||||
1
.github/stale.yml
vendored
1
.github/stale.yml
vendored
@@ -9,6 +9,7 @@ exemptLabels:
|
||||
- epic
|
||||
- enhancement
|
||||
- beginner friendly
|
||||
- awaiting review
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: abandoned
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
|
||||
30
.travis.yml
30
.travis.yml
@@ -1,26 +1,32 @@
|
||||
sudo: false
|
||||
|
||||
language: python
|
||||
|
||||
dist: xenial
|
||||
|
||||
python:
|
||||
- 2.7
|
||||
- 3.3
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- 3.7
|
||||
- nightly
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Python 3.4 not supported in xenial
|
||||
- python: 3.4
|
||||
dist: trusty
|
||||
- env: JOB=HOUSEKEEPING
|
||||
install: ./bin/fetch-configlet
|
||||
before_script: ./bin/check-readmes.sh
|
||||
script:
|
||||
# May provide more useful output than configlet fmt
|
||||
# if JSON is not valid or items are incomplete
|
||||
- ./bin/configlet lint .
|
||||
# Verify correct formatting in config.json
|
||||
- ./bin/configlet fmt . && git diff --quiet
|
||||
allow_failures:
|
||||
- python: nightly
|
||||
|
||||
install:
|
||||
- pip install -r requirements-travis.txt
|
||||
|
||||
before_script:
|
||||
- flake8
|
||||
- ./bin/fetch-configlet
|
||||
- ./bin/configlet lint .
|
||||
|
||||
script:
|
||||
|
||||
script:
|
||||
- ./test/check-exercises.py
|
||||
|
||||
@@ -14,7 +14,7 @@ Please see the [contributing guide](https://github.com/exercism/docs/blob/master
|
||||
## Working on the Exercises
|
||||
|
||||
We welcome both improvements to the existing exercises and new exercises.
|
||||
A list of missing exercise can be found here: http://exercism.io/languages/python/todo
|
||||
A list of missing exercise can be found here: https://github.com/exercism/python/issues/417#issuecomment-366040062
|
||||
|
||||
|
||||
### Conventions
|
||||
@@ -23,13 +23,14 @@ A list of missing exercise can be found here: http://exercism.io/languages/pytho
|
||||
- We use `unittest` (Python Standard Library) and no 3rd-party-framework.
|
||||
- We use the parameter order `self.assertEqual(actual, expected)` ([#440](https://github.com/exercism/python/issues/440)).
|
||||
- We use context managers (`with self.assertRaises(\<exception type\>):`) for testing for exceptions ([#477](https://github.com/exercism/python/issues/477)).
|
||||
- We use an established utility method to confirm that expected exceptions contain a non-empty message. This method must be included for any test class with an exception-based test case ([#1080](https://github.com/exercism/python/issues/1080#issuecomment-442068539)).
|
||||
- We use `assertIs(actual, True)` and `assertIs(actual, False)` rather than `assertTrue(actual)` or `assertFalse(actual)` ([#419](https://github.com/exercism/python/pull/419)).
|
||||
- We use a comment string in the test file to reference the version of the exercise's `canonical-data.json` that tests were adapted from (wording can be found in: [#784](https://github.com/exercism/python/issues/784)).
|
||||
|
||||
|
||||
### Testing
|
||||
|
||||
All exercises must be compatible with Python versions 2.7 and 3.3 upwards.
|
||||
All exercises must be compatible with Python versions 2.7 and 3.4 upwards.
|
||||
|
||||
To test a single exercise (e.g., with Python 2.7):
|
||||
```
|
||||
@@ -71,9 +72,5 @@ If you're interested, Tim Pope even has an [entire blog post](http://tbaggery.co
|
||||
|
||||
If you're new to Git, take a look at [this short guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md#git-basics).
|
||||
|
||||
|
||||
## Python icon
|
||||
The Python logo is an unregistered trademark. We are using a derived logo with the permission of the Python Software Foundation.
|
||||
|
||||
## License
|
||||
This repository uses the [MIT License](/LICENSE).
|
||||
|
||||
22
bin/check-readmes.sh
Executable file
22
bin/check-readmes.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
get_timestamp()
|
||||
{
|
||||
path="$1"
|
||||
git log -n1 --pretty=format:%ct -- "$path"
|
||||
}
|
||||
|
||||
ret=0
|
||||
for exercise in $(ls -d exercises/*/); do
|
||||
meta_dir="${exercise}.meta"
|
||||
if [ -d "$meta_dir" ]; then
|
||||
meta_timestamp="$(get_timestamp "$meta_dir")"
|
||||
readme_timestamp="$(get_timestamp "${exercise}README.md")"
|
||||
if [ "$meta_timestamp" -gt "$readme_timestamp" ]; then
|
||||
ret=1
|
||||
echo "$(basename "$exercise"): .meta/ contents newer than README. Please regenerate it with configlet."
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
exit $ret
|
||||
331
bin/check-test-version.py
Executable file
331
bin/check-test-version.py
Executable file
@@ -0,0 +1,331 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from glob import glob
|
||||
|
||||
from create_issue import GitHub
|
||||
|
||||
if sys.version_info[0] == 3:
|
||||
FileNotFoundError = OSError
|
||||
else:
|
||||
FileNotFoundError = IOError
|
||||
|
||||
VERSION_PATTERN = r'(\d+\.\d+\.\d+)'
|
||||
CANONICAL_PATTERN = (
|
||||
'# Tests adapted from `?problem-specifications//canonical-data.json`? '
|
||||
'@ v' + VERSION_PATTERN
|
||||
)
|
||||
rgx_version = re.compile(VERSION_PATTERN)
|
||||
rgx_canonical = re.compile(CANONICAL_PATTERN)
|
||||
DEFAULT_SPEC_PATH = os.path.join(
|
||||
'..',
|
||||
'problem-specifications'
|
||||
)
|
||||
gh = None
|
||||
|
||||
with open('config.json') as f:
|
||||
config = json.load(f)
|
||||
|
||||
|
||||
class CustomFormatter(
|
||||
argparse.ArgumentDefaultsHelpFormatter,
|
||||
argparse.RawDescriptionHelpFormatter
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class bcolors:
|
||||
HEADER = '\033[95m'
|
||||
OKBLUE = '\033[94m'
|
||||
OKGREEN = '\033[92m'
|
||||
WARNING = '\033[93m'
|
||||
FAIL = '\033[91m'
|
||||
ENDC = '\033[0m'
|
||||
BOLD = '\033[1m'
|
||||
UNDERLINE = '\033[4m'
|
||||
|
||||
|
||||
def cjust(string, width, fillchar=' '):
|
||||
while len(string) < width:
|
||||
string = fillchar + string
|
||||
if len(string) >= width:
|
||||
break
|
||||
string += fillchar
|
||||
return string
|
||||
|
||||
|
||||
def verify_spec_location(path):
|
||||
with open(os.path.join(path, 'package.json')) as f:
|
||||
data = json.load(f)
|
||||
if data['name'] != 'problem-specifications':
|
||||
raise ValueError(
|
||||
'{} is not the problem-specifications directory'.format(path)
|
||||
)
|
||||
|
||||
|
||||
def get_test_file_path(exercise):
|
||||
return os.path.join(
|
||||
'exercises',
|
||||
exercise,
|
||||
exercise.replace('-', '_') + '_test.py'
|
||||
)
|
||||
|
||||
|
||||
def get_test_file_url(exercise):
|
||||
return '/'.join([
|
||||
'https://github.com',
|
||||
'exercism',
|
||||
'python',
|
||||
'blob',
|
||||
'master',
|
||||
'exercises',
|
||||
exercise,
|
||||
exercise.replace('-', '_') + '_test.py'
|
||||
])
|
||||
|
||||
|
||||
def get_canonical_data_path(exercise, spec_path=DEFAULT_SPEC_PATH):
|
||||
return os.path.join(
|
||||
spec_path,
|
||||
'exercises',
|
||||
exercise,
|
||||
'canonical-data.json'
|
||||
)
|
||||
|
||||
|
||||
def get_canonical_data_url(exercise):
|
||||
return '/'.join([
|
||||
'https://github.com',
|
||||
'exercism',
|
||||
'problem-specifications',
|
||||
'blob',
|
||||
'master',
|
||||
'exercises',
|
||||
exercise,
|
||||
'canonical-data.json'
|
||||
])
|
||||
|
||||
|
||||
def get_referenced_version(exercise):
|
||||
with open(get_test_file_path(exercise)) as f:
|
||||
for line in f.readlines():
|
||||
m = rgx_canonical.match(line)
|
||||
if m is not None:
|
||||
return m.group(1)
|
||||
return '0.0.0'
|
||||
|
||||
|
||||
def get_available_version(exercise, spec_path=DEFAULT_SPEC_PATH):
|
||||
try:
|
||||
with open(get_canonical_data_path(exercise, spec_path)) as f:
|
||||
data = json.load(f)
|
||||
m = rgx_version.match(data['version'])
|
||||
return m.group(1)
|
||||
except FileNotFoundError:
|
||||
return '0.0.0'
|
||||
|
||||
|
||||
def is_deprecated(exercise):
|
||||
for e in config['exercises']:
|
||||
if e['slug'] == exercise:
|
||||
return e.get('deprecated', False)
|
||||
return False
|
||||
|
||||
|
||||
def create_issue_for_exercise(
|
||||
exercise,
|
||||
available_data_version,
|
||||
extra_labels=None
|
||||
):
|
||||
title = '{}: update tests to v{}'.format(exercise, available_data_version)
|
||||
body = (
|
||||
'The [test suite]({}) for {} is out of date and needs updated to '
|
||||
'conform to the [latest canonical data]({}).'
|
||||
).format(
|
||||
get_test_file_url(exercise),
|
||||
exercise,
|
||||
get_canonical_data_url(exercise),
|
||||
)
|
||||
labels = [
|
||||
'beginner friendly',
|
||||
'help wanted',
|
||||
'enhancement',
|
||||
]
|
||||
if extra_labels is not None:
|
||||
labels = list(set(labels + extra_labels))
|
||||
issue = gh.create_issue(
|
||||
'exercism',
|
||||
'python',
|
||||
title,
|
||||
body=body,
|
||||
labels=labels
|
||||
)
|
||||
return issue['number']
|
||||
|
||||
|
||||
def check_test_version(
|
||||
exercise,
|
||||
spec=DEFAULT_SPEC_PATH,
|
||||
no_color=True,
|
||||
print_ok=True,
|
||||
name_only=False,
|
||||
has_data=False,
|
||||
include_deprecated=False,
|
||||
create_issue=False,
|
||||
token=None,
|
||||
extra_labels=None,
|
||||
):
|
||||
if not include_deprecated and is_deprecated(exercise):
|
||||
return True
|
||||
available = get_available_version(exercise, spec)
|
||||
if available == '0.0.0' and has_data:
|
||||
return True
|
||||
referenced = get_referenced_version(exercise)
|
||||
up_to_date = available == referenced
|
||||
if up_to_date:
|
||||
status, status_color = 'OK', bcolors.OKGREEN
|
||||
else:
|
||||
status, status_color = 'NOT OK', bcolors.FAIL
|
||||
status = cjust(status, 8)
|
||||
if not no_color:
|
||||
status = status_color + status + bcolors.ENDC
|
||||
if not up_to_date or print_ok:
|
||||
if create_issue:
|
||||
issue_number = create_issue_for_exercise(
|
||||
exercise,
|
||||
available,
|
||||
extra_labels
|
||||
)
|
||||
issue_info = '(#{})'.format(issue_number)
|
||||
else:
|
||||
issue_info = ''
|
||||
if name_only:
|
||||
baseline = exercise
|
||||
else:
|
||||
baseline = '[{}] {}: {}{}{}'.format(
|
||||
status,
|
||||
exercise,
|
||||
referenced,
|
||||
'=' if up_to_date else '!=',
|
||||
available
|
||||
)
|
||||
print(' '.join((baseline, issue_info)))
|
||||
return up_to_date
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=CustomFormatter,
|
||||
epilog=(
|
||||
"Results are of the form:\n <exercise>: <referenced>!=<current>"
|
||||
)
|
||||
)
|
||||
parser._optionals.title = 'options'
|
||||
parser.add_argument(
|
||||
'--version',
|
||||
action='store_true',
|
||||
help='Print version info.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-o', '--only',
|
||||
default='*',
|
||||
metavar='<exercise>',
|
||||
help='Check just the exercise specified (by the slug).',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--ignore',
|
||||
action='append',
|
||||
help='Check all except exercise[s] specified (by the slug).',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--spec-path',
|
||||
default=DEFAULT_SPEC_PATH,
|
||||
metavar='<path/to/spec>',
|
||||
help='The location of the problem-specifications directory.'
|
||||
)
|
||||
g = parser.add_argument_group('output')
|
||||
g.add_argument(
|
||||
'-w', '--no-color',
|
||||
action='store_true',
|
||||
help='Disable colored output.'
|
||||
)
|
||||
g.add_argument(
|
||||
'-s', '--has-data',
|
||||
action='store_true',
|
||||
help='Only print exercises with existing canonical data.'
|
||||
)
|
||||
g.add_argument(
|
||||
'-d', '--include-deprecated',
|
||||
action='store_true',
|
||||
help='Include deprecated exercises'
|
||||
)
|
||||
mut_g = g.add_mutually_exclusive_group()
|
||||
mut_g.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help='Enable verbose output.'
|
||||
)
|
||||
mut_g.add_argument(
|
||||
'-n', '--name-only',
|
||||
action='store_true',
|
||||
help='Print exercise names only.'
|
||||
)
|
||||
g = parser.add_argument_group('issue creation')
|
||||
g.add_argument(
|
||||
'--create-issue',
|
||||
action='store_true',
|
||||
help='Create issue for out-of-date exercises'
|
||||
)
|
||||
g.add_argument(
|
||||
'-t', '--token',
|
||||
help='GitHub personal access token (permissions: repo)'
|
||||
)
|
||||
g.add_argument(
|
||||
'--labels',
|
||||
nargs='+',
|
||||
metavar='LABEL',
|
||||
help=(
|
||||
'additional issue labels ("beginner friendly", "enhancement", and '
|
||||
'"help wanted" are always set)'
|
||||
)
|
||||
)
|
||||
opts = parser.parse_args()
|
||||
verify_spec_location(opts.spec_path)
|
||||
if opts.create_issue:
|
||||
if opts.token is None:
|
||||
if os.path.isfile('.github.api_token'):
|
||||
with open('.github.api_token') as f:
|
||||
opts.token = f.read().strip()
|
||||
if opts.token is not None:
|
||||
gh = GitHub(api_token=opts.token)
|
||||
else:
|
||||
gh = GitHub()
|
||||
kwargs = dict(
|
||||
spec=opts.spec_path,
|
||||
no_color=opts.no_color,
|
||||
print_ok=opts.verbose,
|
||||
name_only=opts.name_only,
|
||||
has_data=opts.has_data,
|
||||
create_issue=opts.create_issue,
|
||||
extra_labels=opts.labels,
|
||||
)
|
||||
if opts.version:
|
||||
print('check-test-version.py v1.1')
|
||||
sys.exit(0)
|
||||
result = True
|
||||
for exercise in glob(os.path.join('exercises', opts.only)):
|
||||
exercise = exercise.split(os.path.sep)[-1]
|
||||
if opts.ignore and exercise in opts.ignore:
|
||||
continue
|
||||
if os.path.isdir(os.path.join('exercises', exercise)):
|
||||
try:
|
||||
result = check_test_version(exercise, **kwargs) and result
|
||||
except FileNotFoundError as e:
|
||||
print(str(e))
|
||||
result = False
|
||||
sys.exit(0 if result else 1)
|
||||
38
bin/create_issue.py
Normal file
38
bin/create_issue.py
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python
|
||||
import requests
|
||||
import json
|
||||
|
||||
|
||||
# https://stackoverflow.com/a/17626704
|
||||
class GitHub(object):
|
||||
def __init__(self, **config_options):
|
||||
self.__dict__.update(**config_options)
|
||||
self.session = requests.Session()
|
||||
if hasattr(self, 'api_token'):
|
||||
self.session.headers['Authorization'] = 'token %s' % self.api_token
|
||||
elif hasattr(self, 'username') and hasattr(self, 'password'):
|
||||
self.session.auth = (self.username, self.password)
|
||||
|
||||
def create_issue(
|
||||
self,
|
||||
owner,
|
||||
repo,
|
||||
title,
|
||||
body=None,
|
||||
assignees=None,
|
||||
labels=None
|
||||
):
|
||||
payload = dict(title=title)
|
||||
if body is not None:
|
||||
payload['body'] = body
|
||||
if assignees is not None:
|
||||
payload['assignees'] = assignees
|
||||
if labels is not None:
|
||||
payload['labels'] = labels
|
||||
response = self.session.post(
|
||||
'https://api.github.com/repos/{}/{}/issues'.format(owner, repo),
|
||||
data=json.dumps(payload),
|
||||
)
|
||||
if response.status_code != 201:
|
||||
raise ValueError('Failed to create issue: ' + response.content)
|
||||
return json.loads(response.content)
|
||||
1533
config.json
1533
config.json
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,52 @@
|
||||
{{- with .Hints }}
|
||||
{{ . }}
|
||||
{{ end }}
|
||||
{{- with .TrackInsert }}
|
||||
{{ . }}
|
||||
{{ end }}
|
||||
{{- with .Spec.Credits -}}
|
||||
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test {{ .Spec.SnakeCaseName }}_test.py`
|
||||
- Python 3.4+: `pytest {{ .Spec.SnakeCaseName }}_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest {{ .Spec.SnakeCaseName }}_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/{{ .Spec.Slug }}` directory.
|
||||
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
{{ with .Spec.Credits }}
|
||||
## Source
|
||||
|
||||
{{ . }}
|
||||
{{ end }}
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
{
|
||||
"docs_url": "https://github.com/exercism/docs/blob/master/maintaining-a-track/maintainer-configuration.md",
|
||||
"maintainers": [
|
||||
{
|
||||
"github_username": "behrtam",
|
||||
"show_on_website": false,
|
||||
"alumnus": false,
|
||||
"show_on_website": false,
|
||||
"name": null,
|
||||
"bio": null,
|
||||
"link_text": null,
|
||||
"link_url": null,
|
||||
"avatar_url": null
|
||||
"avatar_url": null,
|
||||
"bio": null
|
||||
},
|
||||
{
|
||||
"github_username": "Dog",
|
||||
"show_on_website": true,
|
||||
"alumnus": false,
|
||||
"show_on_website": true,
|
||||
"name": "Dog",
|
||||
"bio": "I can not only fetch JSON, but parse it too.",
|
||||
"link_text": null,
|
||||
"link_url": null,
|
||||
"avatar_url": null
|
||||
"avatar_url": null,
|
||||
"bio": "I can not only fetch JSON, but parse it too."
|
||||
},
|
||||
{
|
||||
"github_username": "m-a-ge",
|
||||
"show_on_website": false,
|
||||
"alumnus": false,
|
||||
"show_on_website": false,
|
||||
"name": null,
|
||||
"bio": null,
|
||||
"link_text": null,
|
||||
"link_url": null,
|
||||
"avatar_url": null
|
||||
"avatar_url": null,
|
||||
"bio": null
|
||||
},
|
||||
{
|
||||
"github_username": "cmccandless",
|
||||
"show_on_website": false,
|
||||
"alumnus": false,
|
||||
"name": null,
|
||||
"bio": null,
|
||||
"show_on_website": true,
|
||||
"name": "Corey McCandless",
|
||||
"link_text": null,
|
||||
"link_url": null,
|
||||
"avatar_url": null
|
||||
"avatar_url": null,
|
||||
"bio": "Big fan of homemade bread and reusable code."
|
||||
}
|
||||
],
|
||||
"docs_url": "https://github.com/exercism/docs/blob/master/maintaining-a-track/maintainer-configuration.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
Python is a strong language for beginners.
|
||||
Python is a strong language for beginners.
|
||||
|
||||
There are many resources available for programmers of all levels, the code is highly readable, and in many cases phrases are comparable to those in the English language.
|
||||
|
||||
Code can be written and executed from the command line, in an interactive IPython session, or in a [Jupyter](http://jupyter.org) (IPython) notebook.
|
||||
|
||||
The most common form of Python is compiled in C.
|
||||
This is often invisible to the beginning programmer, but if there are uses for which exceptionally fast implementation is needed then C extensions can be written to optimize Python execution.
|
||||
The most common form of Python is compiled in C; this is often invisible to the beginning programmer, but if there are uses for which exceptionally fast implementation is needed then C extensions can be written to optimize Python execution.
|
||||
|
||||
[Python is used extensively](https://www.python.org/about/apps/) in scientific computing, finance, games, networking, internet development, and in assembling pipelines of other programs.
|
||||
|
||||
Python was started by Guido van Rossum in 1989.
|
||||
Its name is an homage to the comedy troupe Monty Python.
|
||||
Python 2 is used widely but support [may end by 2020](https://www.python.org/dev/peps/pep-0373/#id2).
|
||||
Python 3 was introduced in 2008 and is beginning to be adopted more widely.
|
||||
They are similar, but users will encounter [some differences](http://blog.teamtreehouse.com/python-2-vs-python-3).
|
||||
Python was started by Guido van Rossum in 1989; its name is an homage to the comedy troupe Monty Python.
|
||||
|
||||
Python 2 is used widely but support [may end by 2020](https://www.python.org/dev/peps/pep-0373/#id2); it is highly recommended that beginners use Python 3 (they are similar, but users will encounter [some differences](http://blog.teamtreehouse.com/python-2-vs-python-3)).
|
||||
|
||||
Python development is shepherded by [The Python Software Foundation](https://www.python.org/about/) and there are active community-based user groups worldwide.
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
If Python isn't already available on your system follow the instructions at [the Hitchhiker's Guide to Python](http://docs.python-guide.org/en/latest/starting/installation/) to install Python on your computer.
|
||||
|
||||
Exercism currently supports Python2.7 and Python 3.3+.
|
||||
Exercism currently supports Python2.7 and Python 3.4+.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Learning Python From Scratch
|
||||
## Learning Python From Scratch
|
||||
Python is, as Wikipedia goes, a powerful *general-purpose high-level programming language*. It basically means that it can be used to write a wide variety of different kinds of software, from videogames to HTTP servers to command-line tools.
|
||||
|
||||
One of the main characteristics that differentiates Python from other programming languages is its strong emphasis on readability and code cleaness. In fact, differently from other languages like JavaScript or C++, in Python code indentation has a syntactical meaning and you are forced to chose and adhere to a writing style (e.g. don't mix *tabs* and *spaces* for identation; don't use two spaces where you should use four etc.). Yes, forced: the Python interpreter will raise SyntaxErrors if it recognize wrong indentation.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Installing `pytest`
|
||||
## Installing `pytest`
|
||||
|
||||
We recommend you install [pytest](http://pytest.org/latest/) and
|
||||
[pytest-cache](http://pythonhosted.org/pytest-cache/). `pytest` is a testing
|
||||
@@ -44,9 +44,9 @@ cd exercism/python/bob
|
||||
python bob_test.py
|
||||
```
|
||||
|
||||
# Running the Tests
|
||||
## Running the Tests
|
||||
|
||||
## Run All Tests
|
||||
### Run All Tests
|
||||
|
||||
To run all tests for a specific exercise (we will take the `bob.py` exercise as
|
||||
an example here), place yourself in the directory where that exercise has been
|
||||
@@ -73,9 +73,9 @@ Ran 0 tests in 0.000s
|
||||
OK
|
||||
```
|
||||
|
||||
## More `pytest` Examples
|
||||
### More `pytest` Examples
|
||||
|
||||
### Stop After First Failure
|
||||
#### Stop After First Failure
|
||||
The above will run all the tests, whether they fail or not. If you'd rather stop
|
||||
the process and exit on the first failure, run:
|
||||
|
||||
@@ -83,7 +83,7 @@ the process and exit on the first failure, run:
|
||||
pytest -x bob_test.py
|
||||
```
|
||||
|
||||
### Failed Tests First
|
||||
#### Failed Tests First
|
||||
|
||||
`pytest-cache` remembers which tests failed, and can run those tests first.
|
||||
|
||||
@@ -91,7 +91,7 @@ pytest -x bob_test.py
|
||||
pytest --ff bob_test.py
|
||||
```
|
||||
|
||||
### Running All Tests for All Exercises
|
||||
#### Running All Tests for All Exercises
|
||||
|
||||
```bash
|
||||
cd exercism/python/
|
||||
|
||||
@@ -33,25 +33,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test accumulate_test.py`
|
||||
- Python 3.4+: `pytest accumulate_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest accumulate_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/accumulate` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Conversation with James Edward Gray II [https://twitter.com/jeg2](https://twitter.com/jeg2)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -15,25 +15,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test acronym_test.py`
|
||||
- Python 3.4+: `pytest acronym_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest acronym_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/acronym` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Julien Vanier [https://github.com/monkbroc](https://github.com/monkbroc)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -3,7 +3,7 @@ import unittest
|
||||
from acronym import abbreviate
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.7.0
|
||||
|
||||
class AcronymTest(unittest.TestCase):
|
||||
def test_basic(self):
|
||||
@@ -16,15 +16,27 @@ class AcronymTest(unittest.TestCase):
|
||||
self.assertEqual(abbreviate('First In, First Out'), 'FIFO')
|
||||
|
||||
def test_all_caps_words(self):
|
||||
self.assertEqual(abbreviate('PHP: Hypertext Preprocessor'), 'PHP')
|
||||
|
||||
def test_non_acronym_all_caps_word(self):
|
||||
self.assertEqual(abbreviate('GNU Image Manipulation Program'), 'GIMP')
|
||||
|
||||
def test_hyphenated(self):
|
||||
def test_punctuation_without_whitespace(self):
|
||||
self.assertEqual(
|
||||
abbreviate('Complementary metal-oxide semiconductor'), 'CMOS')
|
||||
|
||||
def test_very_long_abbreviation(self):
|
||||
self.assertEqual(
|
||||
abbreviate("Rolling On The Floor Laughing So Hard That "
|
||||
"My Dogs Came Over And Licked Me"), "ROTFLSHTMDCOALM")
|
||||
|
||||
def test_consecutive_delimiters(self):
|
||||
self.assertEqual(
|
||||
abbreviate('Something - I made up from thin air'), 'SIMUFTA')
|
||||
|
||||
def test_apostrophes(self):
|
||||
self.assertEqual(abbreviate("Halley's Comet"), 'HC')
|
||||
|
||||
def test_underscore_emphasis(self):
|
||||
self.assertEqual(abbreviate("The Road _Not_ Taken"), 'TRNT')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -2,5 +2,5 @@ import re
|
||||
|
||||
|
||||
def abbreviate(words):
|
||||
regex = '[A-Z]+[a-z]*|[a-z]+'
|
||||
regex = "[A-Z]+['a-z]*|['a-z]+"
|
||||
return ''.join(word[0].upper() for word in re.findall(regex, words))
|
||||
|
||||
119
exercises/affine-cipher/README.md
Normal file
119
exercises/affine-cipher/README.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Affine Cipher
|
||||
|
||||
Create an implementation of the affine cipher,
|
||||
an ancient encryption system created in the Middle East.
|
||||
|
||||
The affine cipher is a type of monoalphabetic substitution cipher.
|
||||
Each character is mapped to its numeric equivalent, encrypted with
|
||||
a mathematical function and then converted to the letter relating to
|
||||
its new numeric value. Although all monoalphabetic ciphers are weak,
|
||||
the affine cypher is much stronger than the atbash cipher,
|
||||
because it has many more keys.
|
||||
|
||||
the encryption function is:
|
||||
|
||||
`E(x) = (ax + b) mod m`
|
||||
- where `x` is the letter's index from 0 - length of alphabet - 1
|
||||
- `m` is the length of the alphabet. For the roman alphabet `m == 26`.
|
||||
- and `a` and `b` make the key
|
||||
|
||||
the decryption function is:
|
||||
|
||||
`D(y) = a^-1(y - b) mod m`
|
||||
- where `y` is the numeric value of an encrypted letter, ie. `y = E(x)`
|
||||
- it is important to note that `a^-1` is the modular multiplicative inverse
|
||||
of `a mod m`
|
||||
- the modular multiplicative inverse of `a` only exists if `a` and `m` are
|
||||
coprime.
|
||||
|
||||
To find the MMI of `a`:
|
||||
|
||||
`an mod m = 1`
|
||||
- where `n` is the modular multiplicative inverse of `a mod m`
|
||||
|
||||
More information regarding how to find a Modular Multiplicative Inverse
|
||||
and what it means can be found [here.](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse)
|
||||
|
||||
Because automatic decryption fails if `a` is not coprime to `m` your
|
||||
program should return status 1 and `"Error: a and m must be coprime."`
|
||||
if they are not. Otherwise it should encode or decode with the
|
||||
provided key.
|
||||
|
||||
The Caesar (shift) cipher is a simple affine cipher where `a` is 1 and
|
||||
`b` as the magnitude results in a static displacement of the letters.
|
||||
This is much less secure than a full implementation of the affine cipher.
|
||||
|
||||
Ciphertext is written out in groups of fixed length, the traditional group
|
||||
size being 5 letters, and punctuation is excluded. This is to make it
|
||||
harder to guess things based on word boundaries.
|
||||
|
||||
## Examples
|
||||
|
||||
- Encoding `test` gives `ybty` with the key a=5 b=7
|
||||
- Decoding `ybty` gives `test` with the key a=5 b=7
|
||||
- Decoding `ybty` gives `lqul` with the wrong key a=11 b=7
|
||||
- Decoding `kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx`
|
||||
- gives `thequickbrownfoxjumpsoverthelazydog` with the key a=19 b=13
|
||||
- Encoding `test` with the key a=18 b=13
|
||||
- gives `Error: a and m must be coprime.`
|
||||
- because a and m are not relatively prime
|
||||
|
||||
### Examples of finding a Modular Multiplicative Inverse (MMI)
|
||||
|
||||
- simple example:
|
||||
- `9 mod 26 = 9`
|
||||
- `9 * 3 mod 26 = 27 mod 26 = 1`
|
||||
- `3` is the MMI of `9 mod 26`
|
||||
- a more complicated example:
|
||||
- `15 mod 26 = 15`
|
||||
- `15 * 7 mod 26 = 105 mod 26 = 1`
|
||||
- `7` is the MMI of `15 mod 26`
|
||||
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test affine_cipher_test.py`
|
||||
- Python 3.4+: `pytest affine_cipher_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest affine_cipher_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/affine-cipher` directory.
|
||||
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Wikipedia [http://en.wikipedia.org/wiki/Affine_cipher](http://en.wikipedia.org/wiki/Affine_cipher)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
6
exercises/affine-cipher/affine_cipher.py
Normal file
6
exercises/affine-cipher/affine_cipher.py
Normal file
@@ -0,0 +1,6 @@
|
||||
def encode(plain_text, a, b):
|
||||
pass
|
||||
|
||||
|
||||
def decode(ciphered_text, a, b):
|
||||
pass
|
||||
83
exercises/affine-cipher/affine_cipher_test.py
Normal file
83
exercises/affine-cipher/affine_cipher_test.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import unittest
|
||||
|
||||
from affine_cipher import decode, encode
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v2.0.0
|
||||
|
||||
class AffineCipherTest(unittest.TestCase):
|
||||
def test_encode_yes(self):
|
||||
self.assertEqual(encode("yes", 5, 7), "xbt")
|
||||
|
||||
def test_encode_no(self):
|
||||
self.assertEqual(encode("no", 15, 18), "fu")
|
||||
|
||||
def test_encode_OMG(self):
|
||||
self.assertEqual(encode("OMG", 21, 3), "lvz")
|
||||
|
||||
def test_encode_O_M_G(self):
|
||||
self.assertEqual(encode("O M G", 25, 47), "hjp")
|
||||
|
||||
def test_encode_mindblowingly(self):
|
||||
self.assertEqual(encode("mindblowingly", 11, 15), "rzcwa gnxzc dgt")
|
||||
|
||||
def test_encode_numbers(self):
|
||||
self.assertEqual(encode("Testing,1 2 3, testing.", 3, 4),
|
||||
"jqgjc rw123 jqgjc rw")
|
||||
|
||||
def test_encode_deep_thought(self):
|
||||
self.assertEqual(encode("Truth is fiction.", 5, 17),
|
||||
"iynia fdqfb ifje")
|
||||
|
||||
def test_encode_all_the_letters(self):
|
||||
self.assertEqual(
|
||||
encode("The quick brown fox jumps over the lazy dog.", 17, 33),
|
||||
"swxtj npvyk lruol iejdc blaxk swxmh qzglf")
|
||||
|
||||
def test_encode_raises_meaningful_exception(self):
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
encode("This is a test", 6, 17)
|
||||
|
||||
def test_decode_exercism(self):
|
||||
self.assertEqual(decode("tytgn fjr", 3, 7), "exercism")
|
||||
|
||||
def test_decode_sentence(self):
|
||||
self.assertEqual(
|
||||
decode("qdwju nqcro muwhn odqun oppmd aunwd o", 19, 16),
|
||||
"anobstacleisoftenasteppingstone")
|
||||
|
||||
def test_decode_numbers(self):
|
||||
self.assertEqual(decode("odpoz ub123 odpoz ub", 25, 7),
|
||||
"testing123testing")
|
||||
|
||||
def test_decode_all_the_letters(self):
|
||||
self.assertEqual(
|
||||
decode("swxtj npvyk lruol iejdc blaxk swxmh qzglf", 17, 33),
|
||||
"thequickbrownfoxjumpsoverthelazydog")
|
||||
|
||||
def test_decode_with_no_spaces(self):
|
||||
self.assertEqual(
|
||||
decode("swxtjnpvyklruoliejdcblaxkswxmhqzglf", 17, 33),
|
||||
"thequickbrownfoxjumpsoverthelazydog")
|
||||
|
||||
def test_decode_with_too_many_spaces(self):
|
||||
self.assertEqual(
|
||||
decode("vszzm cly yd cg qdp", 15, 16), "jollygreengiant")
|
||||
|
||||
def test_decode_raises_meaningful_exception(self):
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
decode("Test", 13, 5)
|
||||
|
||||
# Utility functions
|
||||
def setUp(self):
|
||||
try:
|
||||
self.assertRaisesRegex
|
||||
except AttributeError:
|
||||
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||
|
||||
def assertRaisesWithMessage(self, exception):
|
||||
return self.assertRaisesRegex(exception, r".+")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
41
exercises/affine-cipher/example.py
Normal file
41
exercises/affine-cipher/example.py
Normal file
@@ -0,0 +1,41 @@
|
||||
BLKSZ = 5
|
||||
ALPHSZ = 26
|
||||
|
||||
|
||||
def modInverse(a, ALPHSZ):
|
||||
a = a % ALPHSZ
|
||||
for x in range(1, ALPHSZ):
|
||||
if ((a * x) % ALPHSZ == 1):
|
||||
return x
|
||||
return 1
|
||||
|
||||
|
||||
def translate(text, a, b, mode):
|
||||
inv = modInverse(a, ALPHSZ)
|
||||
if inv == 1:
|
||||
raise ValueError("a and alphabet size must be coprime.")
|
||||
|
||||
chars = []
|
||||
for c in text:
|
||||
if c.isalnum():
|
||||
orig = ord(c.lower()) - 97
|
||||
if orig < 0:
|
||||
chars.append(c)
|
||||
continue
|
||||
if mode == 0:
|
||||
new = (a * orig + b) % ALPHSZ
|
||||
elif mode == 1:
|
||||
new = (inv * (orig - b)) % ALPHSZ
|
||||
chars.append(chr(new + 97))
|
||||
|
||||
return ''.join(chars)
|
||||
|
||||
|
||||
def encode(plain, a, b):
|
||||
cipher = translate(plain, a, b, 0)
|
||||
return " ".join([cipher[i:i + BLKSZ]
|
||||
for i in range(0, len(cipher), BLKSZ)])
|
||||
|
||||
|
||||
def decode(ciphered, a, b):
|
||||
return translate(ciphered, a, b, 1)
|
||||
@@ -39,22 +39,39 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test all_your_base_test.py`
|
||||
- Python 3.4+: `pytest all_your_base_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest all_your_base_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/all-your-base` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
def rebase(from_base, digits, to_base):
|
||||
def rebase(input_base, digits, output_base):
|
||||
pass
|
||||
|
||||
@@ -3,9 +3,9 @@ import unittest
|
||||
from all_your_base import rebase
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v2.3.0
|
||||
|
||||
class AllYourBaseTests(unittest.TestCase):
|
||||
class AllYourBaseTest(unittest.TestCase):
|
||||
|
||||
def test_single_bit_to_one_decimal(self):
|
||||
self.assertEqual(rebase(2, [1], 10), [1])
|
||||
@@ -43,15 +43,15 @@ class AllYourBaseTests(unittest.TestCase):
|
||||
def test_leading_zeros(self):
|
||||
self.assertEqual(rebase(7, [0, 6, 0], 10), [4, 2])
|
||||
|
||||
def test_first_base_is_one(self):
|
||||
def test_input_base_is_one(self):
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
rebase(1, [], 10)
|
||||
rebase(1, [0], 10)
|
||||
|
||||
def test_first_base_is_zero(self):
|
||||
def test_input_base_is_zero(self):
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
rebase(0, [], 10)
|
||||
|
||||
def test_first_base_is_negative(self):
|
||||
def test_input_base_is_negative(self):
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
rebase(-2, [1], 10)
|
||||
|
||||
@@ -63,15 +63,15 @@ class AllYourBaseTests(unittest.TestCase):
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
rebase(2, [1, 2, 1, 0, 1, 0], 10)
|
||||
|
||||
def test_second_base_is_one(self):
|
||||
def test_output_base_is_one(self):
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
rebase(2, [1, 0, 1, 0, 1, 0], 1)
|
||||
|
||||
def test_second_base_is_zero(self):
|
||||
def test_output_base_is_zero(self):
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
rebase(10, [7], 0)
|
||||
|
||||
def test_second_base_is_negative(self):
|
||||
def test_output_base_is_negative(self):
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
rebase(2, [1], -7)
|
||||
|
||||
|
||||
@@ -37,25 +37,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test allergies_test.py`
|
||||
- Python 3.4+: `pytest allergies_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest allergies_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/allergies` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Jumpstart Lab Warm-up [http://jumpstartlab.com](http://jumpstartlab.com)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -7,9 +7,9 @@ if not hasattr(unittest.TestCase, 'assertCountEqual'):
|
||||
unittest.TestCase.assertCountEqual = unittest.TestCase.assertItemsEqual
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.2.0
|
||||
|
||||
class AllergiesTests(unittest.TestCase):
|
||||
class AllergiesTest(unittest.TestCase):
|
||||
def test_no_allergies_means_not_allergic(self):
|
||||
allergies = Allergies(0)
|
||||
self.assertIs(allergies.is_allergic_to('peanuts'), False)
|
||||
@@ -25,6 +25,13 @@ class AllergiesTests(unittest.TestCase):
|
||||
self.assertIs(allergies.is_allergic_to('shellfish'), True)
|
||||
self.assertIs(allergies.is_allergic_to('strawberries'), False)
|
||||
|
||||
def test_allergic_to_strawberries_but_not_peanuts(self):
|
||||
allergies = Allergies(9)
|
||||
self.assertIs(allergies.is_allergic_to('eggs'), True)
|
||||
self.assertIs(allergies.is_allergic_to('peanuts'), False)
|
||||
self.assertIs(allergies.is_allergic_to('shellfish'), False)
|
||||
self.assertIs(allergies.is_allergic_to('strawberries'), True)
|
||||
|
||||
def test_no_allergies_at_all(self):
|
||||
self.assertEqual(Allergies(0).lst, [])
|
||||
|
||||
@@ -55,9 +62,6 @@ class AllergiesTests(unittest.TestCase):
|
||||
'chocolate', 'pollen', 'cats'
|
||||
])
|
||||
|
||||
def test_ignore_non_allergen_score_parts_only_eggs(self):
|
||||
self.assertEqual(Allergies(257).lst, ['eggs'])
|
||||
|
||||
def test_ignore_non_allergen_score_parts(self):
|
||||
self.assertCountEqual(
|
||||
Allergies(509).lst, [
|
||||
|
||||
@@ -39,22 +39,39 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test alphametics_test.py`
|
||||
- Python 3.4+: `pytest alphametics_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest alphametics_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/alphametics` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -3,23 +3,30 @@ import unittest
|
||||
from alphametics import solve
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.3.0
|
||||
|
||||
class TestAlphametics(unittest.TestCase):
|
||||
def test_puzzle_with_03_letters(self):
|
||||
class AlphameticsTest(unittest.TestCase):
|
||||
def test_puzzle_with_three_letters(self):
|
||||
self.assertEqual(solve("I + BB == ILL"), {"I": 1, "B": 9, "L": 0})
|
||||
|
||||
def test_invalid_solution_must_have_unique_value_for_each_letter(self):
|
||||
def test_solution_must_have_unique_value_for_each_letter(self):
|
||||
self.assertEqual(solve("A == B"), {})
|
||||
|
||||
def test_invalid_leading_zero_solution(self):
|
||||
def test_leading_zero_solution_is_invalid(self):
|
||||
self.assertEqual(solve("ACA + DD == BD"), {})
|
||||
|
||||
def test_puzzle_with_04_letters(self):
|
||||
def test_puzzle_with_two_digits_final_carry(self):
|
||||
self.assertEqual(
|
||||
solve("A + A + A + A + A + A + A + A + A + A + A + B == BCC"),
|
||||
{"A": 9,
|
||||
"B": 1,
|
||||
"C": 0})
|
||||
|
||||
def test_puzzle_with_four_letters(self):
|
||||
self.assertEqual(
|
||||
solve("AS + A == MOM"), {"A": 9, "S": 2, "M": 1, "O": 0})
|
||||
|
||||
def test_puzzle_with_06_letters(self):
|
||||
def test_puzzle_with_six_letters(self):
|
||||
self.assertEqual(
|
||||
solve("NO + NO + TOO == LATE"),
|
||||
{"N": 7,
|
||||
@@ -29,7 +36,7 @@ class TestAlphametics(unittest.TestCase):
|
||||
"A": 0,
|
||||
"E": 2})
|
||||
|
||||
def test_puzzle_with_07_letters(self):
|
||||
def test_puzzle_with_seven_letters(self):
|
||||
self.assertEqual(
|
||||
solve("HE + SEES + THE == LIGHT"),
|
||||
{"E": 4,
|
||||
@@ -40,7 +47,7 @@ class TestAlphametics(unittest.TestCase):
|
||||
"S": 9,
|
||||
"T": 7})
|
||||
|
||||
def test_puzzle_with_08_letters(self):
|
||||
def test_puzzle_with_eight_letters(self):
|
||||
self.assertEqual(
|
||||
solve("SEND + MORE == MONEY"),
|
||||
{"S": 9,
|
||||
@@ -52,7 +59,7 @@ class TestAlphametics(unittest.TestCase):
|
||||
"R": 8,
|
||||
"Y": 2})
|
||||
|
||||
def test_puzzle_with_10_letters(self):
|
||||
def test_puzzle_with_ten_letters(self):
|
||||
self.assertEqual(
|
||||
solve("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE"),
|
||||
{"A": 5,
|
||||
@@ -66,6 +73,47 @@ class TestAlphametics(unittest.TestCase):
|
||||
"S": 6,
|
||||
"T": 9})
|
||||
|
||||
@unittest.skip("extra-credit")
|
||||
def test_puzzle_with_ten_letters_and_199_addends(self):
|
||||
self.assertEqual(
|
||||
solve(
|
||||
"THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + "
|
||||
"TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + "
|
||||
"A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + "
|
||||
"LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + "
|
||||
"HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + "
|
||||
"HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + "
|
||||
"HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + "
|
||||
"ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + "
|
||||
"HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + "
|
||||
"HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + "
|
||||
"THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + "
|
||||
"FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + "
|
||||
"STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + "
|
||||
"AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + "
|
||||
"TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + "
|
||||
"THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + "
|
||||
"FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + "
|
||||
"AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + "
|
||||
"THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + "
|
||||
"FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + "
|
||||
"TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + "
|
||||
"ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + "
|
||||
"SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + "
|
||||
"AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + "
|
||||
"TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES"
|
||||
),
|
||||
{"A": 1,
|
||||
"E": 0,
|
||||
"F": 5,
|
||||
"H": 8,
|
||||
"I": 7,
|
||||
"L": 2,
|
||||
"O": 6,
|
||||
"R": 3,
|
||||
"S": 4,
|
||||
"T": 9})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -14,25 +14,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test anagram_test.py`
|
||||
- Python 3.4+: `pytest anagram_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest anagram_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/anagram` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Inspired by the Extreme Startup game [https://github.com/rchatley/extreme_startup](https://github.com/rchatley/extreme_startup)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
def detect_anagrams(word, candidates):
|
||||
def find_anagrams(word, candidates):
|
||||
pass
|
||||
|
||||
@@ -1,75 +1,62 @@
|
||||
import unittest
|
||||
|
||||
from anagram import detect_anagrams
|
||||
from anagram import find_anagrams
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.1
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.4.0
|
||||
|
||||
class AnagramTests(unittest.TestCase):
|
||||
class AnagramTest(unittest.TestCase):
|
||||
def test_no_matches(self):
|
||||
candidates = ["hello", "world", "zombies", "pants"]
|
||||
self.assertEqual(detect_anagrams("diaper", candidates), [])
|
||||
|
||||
def test_detects_simple_anagram(self):
|
||||
candidates = ["tan", "stand", "at"]
|
||||
self.assertEqual(detect_anagrams("ant", candidates), ["tan"])
|
||||
|
||||
def test_does_not_detect_false_positives(self):
|
||||
self.assertEqual(detect_anagrams("galea", ["eagle"]), [])
|
||||
self.assertEqual(find_anagrams("diaper", candidates), [])
|
||||
|
||||
def test_detects_two_anagrams(self):
|
||||
candidates = ["stream", "pigeon", "maters"]
|
||||
self.assertEqual(
|
||||
detect_anagrams("master", candidates), ["stream", "maters"])
|
||||
find_anagrams("master", candidates), ["stream", "maters"])
|
||||
|
||||
def test_does_not_detect_anagram_subsets(self):
|
||||
self.assertEqual(detect_anagrams("good", ["dog", "goody"]), [])
|
||||
self.assertEqual(find_anagrams("good", ["dog", "goody"]), [])
|
||||
|
||||
def test_detects_anagram(self):
|
||||
candidates = ["enlists", "google", "inlets", "banana"]
|
||||
self.assertEqual(detect_anagrams("listen", candidates), ["inlets"])
|
||||
self.assertEqual(find_anagrams("listen", candidates), ["inlets"])
|
||||
|
||||
def test_detects_three_anagrams(self):
|
||||
candidates = [
|
||||
"gallery", "ballerina", "regally", "clergy", "largely", "leading"
|
||||
]
|
||||
self.assertEqual(
|
||||
detect_anagrams("allergy", candidates),
|
||||
find_anagrams("allergy", candidates),
|
||||
["gallery", "regally", "largely"])
|
||||
|
||||
def test_does_not_detect_identical_words(self):
|
||||
candidates = ["corn", "dark", "Corn", "rank", "CORN", "cron", "park"]
|
||||
self.assertEqual(detect_anagrams("corn", candidates), ["cron"])
|
||||
|
||||
def test_does_not_detect_non_anagrams_with_identical_checksum(self):
|
||||
self.assertEqual(detect_anagrams("mass", ["last"]), [])
|
||||
self.assertEqual(find_anagrams("mass", ["last"]), [])
|
||||
|
||||
def test_detects_anagrams_case_insensitively(self):
|
||||
candidates = ["cashregister", "Carthorse", "radishes"]
|
||||
self.assertEqual(
|
||||
detect_anagrams("Orchestra", candidates), ["Carthorse"])
|
||||
find_anagrams("Orchestra", candidates), ["Carthorse"])
|
||||
|
||||
def test_detects_anagrams_using_case_insensitive_subjec(self):
|
||||
def test_detects_anagrams_using_case_insensitive_subject(self):
|
||||
candidates = ["cashregister", "carthorse", "radishes"]
|
||||
self.assertEqual(
|
||||
detect_anagrams("Orchestra", candidates), ["carthorse"])
|
||||
find_anagrams("Orchestra", candidates), ["carthorse"])
|
||||
|
||||
def test_detects_anagrams_using_case_insensitive_possible_matches(self):
|
||||
candidates = ["cashregister", "Carthorse", "radishes"]
|
||||
self.assertEqual(
|
||||
detect_anagrams("orchestra", candidates), ["Carthorse"])
|
||||
|
||||
def test_does_not_detect_a_word_as_its_own_anagram(self):
|
||||
self.assertEqual(detect_anagrams("banana", ["Banana"]), [])
|
||||
find_anagrams("orchestra", candidates), ["Carthorse"])
|
||||
|
||||
def test_does_not_detect_a_anagram_if_the_original_word_is_repeated(self):
|
||||
self.assertEqual(detect_anagrams("go", ["go Go GO"]), [])
|
||||
self.assertEqual(find_anagrams("go", ["go Go GO"]), [])
|
||||
|
||||
def test_anagrams_must_use_all_letters_exactly_once(self):
|
||||
self.assertEqual(detect_anagrams("tapper", ["patter"]), [])
|
||||
self.assertEqual(find_anagrams("tapper", ["patter"]), [])
|
||||
|
||||
def test_capital_word_is_not_own_anagram(self):
|
||||
self.assertEqual(detect_anagrams("BANANA", ["Banana"]), [])
|
||||
def test_words_are_not_anagrams_of_themselves_case_insensitive(self):
|
||||
candidates = ["BANANA", "Banana", "banana"]
|
||||
self.assertEqual(find_anagrams("BANANA", candidates), [])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
def detect_anagrams(word, candidates):
|
||||
def find_anagrams(word, candidates):
|
||||
return [candidate
|
||||
for candidate in candidates
|
||||
if _letters(candidate) == _letters(word)
|
||||
|
||||
@@ -5,24 +5,57 @@ An [Armstrong number](https://en.wikipedia.org/wiki/Narcissistic_number) is a nu
|
||||
For example:
|
||||
|
||||
- 9 is an Armstrong number, because `9 = 9^1 = 9`
|
||||
- 10 is *not* an Armstrong number, because `10 != 1^2 + 0^2 = 2`
|
||||
- 10 is *not* an Armstrong number, because `10 != 1^2 + 0^2 = 1`
|
||||
- 153 is an Armstrong number, because: `153 = 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153`
|
||||
- 154 is *not* an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190`
|
||||
|
||||
Write some code to determine whether a number is an Armstrong number.
|
||||
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test armstrong_numbers_test.py`
|
||||
- Python 3.4+: `pytest armstrong_numbers_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest armstrong_numbers_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/armstrong-numbers` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Wikipedia [https://en.wikipedia.org/wiki/Narcissistic_number](https://en.wikipedia.org/wiki/Narcissistic_number)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -5,7 +5,7 @@ from armstrong_numbers import is_armstrong
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0
|
||||
|
||||
class ArmstrongTests(unittest.TestCase):
|
||||
class ArmstrongNumbersTest(unittest.TestCase):
|
||||
|
||||
def test_single_digit_numbers_are_armstrong_numbers(self):
|
||||
self.assertIs(is_armstrong(5), True)
|
||||
|
||||
@@ -36,25 +36,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test atbash_cipher_test.py`
|
||||
- Python 3.4+: `pytest atbash_cipher_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest atbash_cipher_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/atbash-cipher` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Wikipedia [http://en.wikipedia.org/wiki/Atbash](http://en.wikipedia.org/wiki/Atbash)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -3,7 +3,7 @@ import unittest
|
||||
from atbash_cipher import decode, encode
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.2.0
|
||||
|
||||
class AtbashCipherTest(unittest.TestCase):
|
||||
def test_encode_no(self):
|
||||
@@ -51,6 +51,14 @@ class AtbashCipherTest(unittest.TestCase):
|
||||
plaintext = "thequickbrownfoxjumpsoverthelazydog"
|
||||
self.assertMultiLineEqual(decode(ciphertext), plaintext)
|
||||
|
||||
def test_decode_with_too_many_spaces(self):
|
||||
self.assertMultiLineEqual(decode("vc vix r hn"), "exercism")
|
||||
|
||||
def test_decode_with_no_spaces(self):
|
||||
ciphertext = "zmlyhgzxovrhlugvmzhgvkkrmthglmv"
|
||||
plaintext = "anobstacleisoftenasteppingstone"
|
||||
self.assertMultiLineEqual(decode(ciphertext), plaintext)
|
||||
|
||||
# additional track specific test
|
||||
def test_encode_decode(self):
|
||||
self.assertMultiLineEqual(
|
||||
|
||||
72
exercises/bank-account/README.md
Normal file
72
exercises/bank-account/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Bank Account
|
||||
|
||||
Simulate a bank account supporting opening/closing, withdrawals, and deposits
|
||||
of money. Watch out for concurrent transactions!
|
||||
|
||||
A bank account can be accessed in multiple ways. Clients can make
|
||||
deposits and withdrawals using the internet, mobile phones, etc. Shops
|
||||
can charge against the account.
|
||||
|
||||
Create an account that can be accessed from multiple threads/processes
|
||||
(terminology depends on your programming language).
|
||||
|
||||
It should be possible to close an account; operations against a closed
|
||||
account must fail.
|
||||
|
||||
## Instructions
|
||||
|
||||
Run the test file, and fix each of the errors in turn. When you get the
|
||||
first test to pass, go to the first pending or skipped test, and make
|
||||
that pass as well. When all of the tests are passing, feel free to
|
||||
submit.
|
||||
|
||||
Remember that passing code is just the first step. The goal is to work
|
||||
towards a solution that is as readable and expressive as you can make
|
||||
it.
|
||||
|
||||
Have fun!
|
||||
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test bank_account_test.py`
|
||||
- Python 3.4+: `pytest bank_account_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest bank_account_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/bank-account` directory.
|
||||
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
18
exercises/bank-account/bank_account.py
Normal file
18
exercises/bank-account/bank_account.py
Normal file
@@ -0,0 +1,18 @@
|
||||
class BankAccount(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_balance(self):
|
||||
pass
|
||||
|
||||
def open(self):
|
||||
pass
|
||||
|
||||
def deposit(self, amount):
|
||||
pass
|
||||
|
||||
def withdraw(self, amount):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
153
exercises/bank-account/bank_account_test.py
Normal file
153
exercises/bank-account/bank_account_test.py
Normal file
@@ -0,0 +1,153 @@
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from bank_account import BankAccount
|
||||
|
||||
|
||||
class BankAccountTest(unittest.TestCase):
|
||||
def test_newly_opened_account_has_zero_balance(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
self.assertEqual(account.get_balance(), 0)
|
||||
|
||||
def test_can_deposit_money(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
account.deposit(100)
|
||||
self.assertEqual(account.get_balance(), 100)
|
||||
|
||||
def test_can_deposit_money_sequentially(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
account.deposit(100)
|
||||
account.deposit(50)
|
||||
|
||||
self.assertEqual(account.get_balance(), 150)
|
||||
|
||||
def test_can_withdraw_money(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
account.deposit(100)
|
||||
account.withdraw(50)
|
||||
|
||||
self.assertEqual(account.get_balance(), 50)
|
||||
|
||||
def test_can_withdraw_money_sequentially(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
account.deposit(100)
|
||||
account.withdraw(20)
|
||||
account.withdraw(80)
|
||||
|
||||
self.assertEqual(account.get_balance(), 0)
|
||||
|
||||
def test_checking_balance_of_closed_account_throws_error(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
account.close()
|
||||
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
account.get_balance()
|
||||
|
||||
def test_deposit_into_closed_account(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
account.close()
|
||||
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
account.deposit(50)
|
||||
|
||||
def test_withdraw_from_closed_account(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
account.close()
|
||||
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
account.withdraw(50)
|
||||
|
||||
def test_close_already_closed_account(self):
|
||||
account = BankAccount()
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
account.close()
|
||||
|
||||
def test_open_already_opened_account(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
account.open()
|
||||
|
||||
def test_reopened_account_does_not_retain_balance(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
account.deposit(50)
|
||||
account.close()
|
||||
account.open()
|
||||
self.assertEqual(account.get_balance(), 0)
|
||||
|
||||
def test_cannot_withdraw_more_than_deposited(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
account.deposit(25)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
account.withdraw(50)
|
||||
|
||||
def test_cannot_withdraw_negative(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
account.deposit(100)
|
||||
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
account.withdraw(-50)
|
||||
|
||||
def test_cannot_deposit_negative(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
account.deposit(-50)
|
||||
|
||||
def test_can_handle_concurrent_transactions(self):
|
||||
account = BankAccount()
|
||||
account.open()
|
||||
account.deposit(1000)
|
||||
|
||||
self.adjust_balance_concurrently(account)
|
||||
|
||||
self.assertEqual(account.get_balance(), 1000)
|
||||
|
||||
def adjust_balance_concurrently(self, account):
|
||||
def transact():
|
||||
account.deposit(5)
|
||||
time.sleep(0.001)
|
||||
account.withdraw(5)
|
||||
|
||||
# Greatly improve the chance of an operation being interrupted
|
||||
# by thread switch, thus testing synchronization effectively
|
||||
try:
|
||||
sys.setswitchinterval(1e-12)
|
||||
except AttributeError:
|
||||
# For Python 2 compatibility
|
||||
sys.setcheckinterval(1)
|
||||
|
||||
threads = [threading.Thread(target=transact) for _ in range(1000)]
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
# Utility functions
|
||||
def setUp(self):
|
||||
try:
|
||||
self.assertRaisesRegex
|
||||
except AttributeError:
|
||||
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||
|
||||
def assertRaisesWithMessage(self, exception):
|
||||
return self.assertRaisesRegex(exception, r".+")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
43
exercises/bank-account/example.py
Normal file
43
exercises/bank-account/example.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import threading
|
||||
|
||||
|
||||
class BankAccount(object):
|
||||
def __init__(self):
|
||||
self.is_open = False
|
||||
self.balance = 0
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def get_balance(self):
|
||||
with self.lock:
|
||||
if not self.is_open:
|
||||
raise ValueError('account not open')
|
||||
return self.balance
|
||||
|
||||
def open(self):
|
||||
if self.is_open:
|
||||
raise ValueError('account already open')
|
||||
self.is_open = True
|
||||
self.balance = 0
|
||||
|
||||
def deposit(self, amount):
|
||||
with self.lock:
|
||||
if not self.is_open:
|
||||
raise ValueError('account not open')
|
||||
if amount <= 0:
|
||||
raise ValueError('amount must be greater than 0')
|
||||
self.balance += amount
|
||||
|
||||
def withdraw(self, amount):
|
||||
with self.lock:
|
||||
if not self.is_open:
|
||||
raise ValueError('account not open')
|
||||
if amount <= 0:
|
||||
raise ValueError('amount must be greater than 0')
|
||||
if amount > self.balance:
|
||||
raise ValueError('amount must be less than balance')
|
||||
self.balance -= amount
|
||||
|
||||
def close(self):
|
||||
if not self.is_open:
|
||||
raise ValueError('account not open')
|
||||
self.is_open = False
|
||||
@@ -328,25 +328,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test beer_song_test.py`
|
||||
- Python 3.4+: `pytest beer_song_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest beer_song_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/beer-song` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Learn to Program by Chris Pine [http://pine.fm/LearnToProgram/?Chapter=06](http://pine.fm/LearnToProgram/?Chapter=06)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -1,6 +1,2 @@
|
||||
def verse(number):
|
||||
pass
|
||||
|
||||
|
||||
def song(number1, number2=0):
|
||||
def recite(start, take=1):
|
||||
pass
|
||||
|
||||
@@ -1,70 +1,389 @@
|
||||
import unittest
|
||||
|
||||
from beer_song import song, verse
|
||||
from beer_song import recite
|
||||
|
||||
|
||||
class BeerTest(unittest.TestCase):
|
||||
def test_a_verse(self):
|
||||
self.assertEqual(
|
||||
verse(8),
|
||||
"8 bottles of beer on the wall, 8 bottles of beer.\n"
|
||||
"Take one down and pass it around, "
|
||||
"7 bottles of beer on the wall.\n"
|
||||
)
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v2.1.0
|
||||
|
||||
def test_verse_1(self):
|
||||
self.assertEqual(
|
||||
verse(1),
|
||||
"1 bottle of beer on the wall, 1 bottle of beer.\n"
|
||||
"Take it down and pass it around, "
|
||||
"no more bottles of beer on the wall.\n"
|
||||
)
|
||||
class BeerSongTest(unittest.TestCase):
|
||||
def test_first_generic_verse(self):
|
||||
expected = [
|
||||
"99 bottles of beer on the wall, 99 bottles of beer.",
|
||||
"Take one down and pass it around, 98 bottles of beer on the wall."
|
||||
]
|
||||
self.assertEqual(recite(start=99), expected)
|
||||
|
||||
def test_verse_2(self):
|
||||
self.assertEqual(
|
||||
verse(2),
|
||||
"2 bottles of beer on the wall, 2 bottles of beer.\n"
|
||||
"Take one down and pass it around, 1 bottle of beer on the wall.\n"
|
||||
)
|
||||
def test_last_generic_verse(self):
|
||||
expected = [
|
||||
"3 bottles of beer on the wall, 3 bottles of beer.",
|
||||
"Take one down and pass it around, 2 bottles of beer on the wall."
|
||||
]
|
||||
self.assertEqual(recite(start=3), expected)
|
||||
|
||||
def test_verse_0(self):
|
||||
self.assertEqual(
|
||||
verse(0),
|
||||
"No more bottles of beer on the wall, no more bottles of beer.\n"
|
||||
"Go to the store and buy some more, "
|
||||
"99 bottles of beer on the wall.\n"
|
||||
)
|
||||
def test_verse_with_two_bottles(self):
|
||||
expected = [
|
||||
"2 bottles of beer on the wall, 2 bottles of beer.",
|
||||
"Take one down and pass it around, 1 bottle of beer on the wall."
|
||||
]
|
||||
self.assertEqual(recite(start=2), expected)
|
||||
|
||||
def test_songing_several_verses(self):
|
||||
self.assertEqual(
|
||||
song(8, 6),
|
||||
"8 bottles of beer on the wall, 8 bottles of beer.\n"
|
||||
"Take one down and pass it around, "
|
||||
"7 bottles of beer on the wall.\n\n"
|
||||
"7 bottles of beer on the wall, 7 bottles of beer.\n"
|
||||
"Take one down and pass it around, "
|
||||
"6 bottles of beer on the wall.\n\n"
|
||||
"6 bottles of beer on the wall, 6 bottles of beer.\n"
|
||||
"Take one down and pass it around, "
|
||||
"5 bottles of beer on the wall.\n\n"
|
||||
)
|
||||
def test_verse_with_one_bottle(self):
|
||||
expected = [
|
||||
"1 bottle of beer on the wall, 1 bottle of beer.",
|
||||
(
|
||||
"Take it down and pass it around, "
|
||||
"no more bottles of beer on the wall."
|
||||
)
|
||||
]
|
||||
self.assertEqual(recite(start=1), expected)
|
||||
|
||||
def test_song_all_the_rest_of_the_verses(self):
|
||||
self.assertEqual(
|
||||
song(3),
|
||||
"3 bottles of beer on the wall, 3 bottles of beer.\n"
|
||||
"Take one down and pass it around, "
|
||||
"2 bottles of beer on the wall.\n\n"
|
||||
"2 bottles of beer on the wall, 2 bottles of beer.\n"
|
||||
"Take one down and pass it around, "
|
||||
"1 bottle of beer on the wall.\n\n"
|
||||
"1 bottle of beer on the wall, 1 bottle of beer.\n"
|
||||
"Take it down and pass it around, "
|
||||
"no more bottles of beer on the wall.\n\n"
|
||||
"No more bottles of beer on the wall, no more bottles of beer.\n"
|
||||
"Go to the store and buy some more, "
|
||||
"99 bottles of beer on the wall.\n\n"
|
||||
)
|
||||
def test_verse_with_zero_bottles(self):
|
||||
expected = [
|
||||
"No more bottles of beer on the wall, no more bottles of beer.",
|
||||
(
|
||||
"Go to the store and buy some more, "
|
||||
"99 bottles of beer on the wall."
|
||||
)
|
||||
]
|
||||
self.assertEqual(recite(start=0), expected)
|
||||
|
||||
def test_first_two_verses(self):
|
||||
expected = [
|
||||
"99 bottles of beer on the wall, 99 bottles of beer.",
|
||||
(
|
||||
"Take one down and pass it around, "
|
||||
"98 bottles of beer on the wall."
|
||||
),
|
||||
"",
|
||||
"98 bottles of beer on the wall, 98 bottles of beer.",
|
||||
"Take one down and pass it around, 97 bottles of beer on the wall."
|
||||
]
|
||||
self.assertEqual(recite(start=99, take=2), expected)
|
||||
|
||||
def test_last_three_verses(self):
|
||||
expected = [
|
||||
"2 bottles of beer on the wall, 2 bottles of beer.",
|
||||
"Take one down and pass it around, 1 bottle of beer on the wall.",
|
||||
"",
|
||||
"1 bottle of beer on the wall, 1 bottle of beer.",
|
||||
(
|
||||
"Take it down and pass it around, "
|
||||
"no more bottles of beer on the wall."
|
||||
),
|
||||
"",
|
||||
"No more bottles of beer on the wall, no more bottles of beer.",
|
||||
(
|
||||
"Go to the store and buy some more, "
|
||||
"99 bottles of beer on the wall."
|
||||
)
|
||||
]
|
||||
self.assertEqual(recite(start=2, take=3), expected)
|
||||
|
||||
def test_all_verses(self):
|
||||
self.assertEqual(recite(start=99, take=100), SONG)
|
||||
|
||||
|
||||
SONG = [
|
||||
"99 bottles of beer on the wall, 99 bottles of beer.",
|
||||
"Take one down and pass it around, 98 bottles of beer on the wall.",
|
||||
"",
|
||||
"98 bottles of beer on the wall, 98 bottles of beer.",
|
||||
"Take one down and pass it around, 97 bottles of beer on the wall.",
|
||||
"",
|
||||
"97 bottles of beer on the wall, 97 bottles of beer.",
|
||||
"Take one down and pass it around, 96 bottles of beer on the wall.",
|
||||
"",
|
||||
"96 bottles of beer on the wall, 96 bottles of beer.",
|
||||
"Take one down and pass it around, 95 bottles of beer on the wall.",
|
||||
"",
|
||||
"95 bottles of beer on the wall, 95 bottles of beer.",
|
||||
"Take one down and pass it around, 94 bottles of beer on the wall.",
|
||||
"",
|
||||
"94 bottles of beer on the wall, 94 bottles of beer.",
|
||||
"Take one down and pass it around, 93 bottles of beer on the wall.",
|
||||
"",
|
||||
"93 bottles of beer on the wall, 93 bottles of beer.",
|
||||
"Take one down and pass it around, 92 bottles of beer on the wall.",
|
||||
"",
|
||||
"92 bottles of beer on the wall, 92 bottles of beer.",
|
||||
"Take one down and pass it around, 91 bottles of beer on the wall.",
|
||||
"",
|
||||
"91 bottles of beer on the wall, 91 bottles of beer.",
|
||||
"Take one down and pass it around, 90 bottles of beer on the wall.",
|
||||
"",
|
||||
"90 bottles of beer on the wall, 90 bottles of beer.",
|
||||
"Take one down and pass it around, 89 bottles of beer on the wall.",
|
||||
"",
|
||||
"89 bottles of beer on the wall, 89 bottles of beer.",
|
||||
"Take one down and pass it around, 88 bottles of beer on the wall.",
|
||||
"",
|
||||
"88 bottles of beer on the wall, 88 bottles of beer.",
|
||||
"Take one down and pass it around, 87 bottles of beer on the wall.",
|
||||
"",
|
||||
"87 bottles of beer on the wall, 87 bottles of beer.",
|
||||
"Take one down and pass it around, 86 bottles of beer on the wall.",
|
||||
"",
|
||||
"86 bottles of beer on the wall, 86 bottles of beer.",
|
||||
"Take one down and pass it around, 85 bottles of beer on the wall.",
|
||||
"",
|
||||
"85 bottles of beer on the wall, 85 bottles of beer.",
|
||||
"Take one down and pass it around, 84 bottles of beer on the wall.",
|
||||
"",
|
||||
"84 bottles of beer on the wall, 84 bottles of beer.",
|
||||
"Take one down and pass it around, 83 bottles of beer on the wall.",
|
||||
"",
|
||||
"83 bottles of beer on the wall, 83 bottles of beer.",
|
||||
"Take one down and pass it around, 82 bottles of beer on the wall.",
|
||||
"",
|
||||
"82 bottles of beer on the wall, 82 bottles of beer.",
|
||||
"Take one down and pass it around, 81 bottles of beer on the wall.",
|
||||
"",
|
||||
"81 bottles of beer on the wall, 81 bottles of beer.",
|
||||
"Take one down and pass it around, 80 bottles of beer on the wall.",
|
||||
"",
|
||||
"80 bottles of beer on the wall, 80 bottles of beer.",
|
||||
"Take one down and pass it around, 79 bottles of beer on the wall.",
|
||||
"",
|
||||
"79 bottles of beer on the wall, 79 bottles of beer.",
|
||||
"Take one down and pass it around, 78 bottles of beer on the wall.",
|
||||
"",
|
||||
"78 bottles of beer on the wall, 78 bottles of beer.",
|
||||
"Take one down and pass it around, 77 bottles of beer on the wall.",
|
||||
"",
|
||||
"77 bottles of beer on the wall, 77 bottles of beer.",
|
||||
"Take one down and pass it around, 76 bottles of beer on the wall.",
|
||||
"",
|
||||
"76 bottles of beer on the wall, 76 bottles of beer.",
|
||||
"Take one down and pass it around, 75 bottles of beer on the wall.",
|
||||
"",
|
||||
"75 bottles of beer on the wall, 75 bottles of beer.",
|
||||
"Take one down and pass it around, 74 bottles of beer on the wall.",
|
||||
"",
|
||||
"74 bottles of beer on the wall, 74 bottles of beer.",
|
||||
"Take one down and pass it around, 73 bottles of beer on the wall.",
|
||||
"",
|
||||
"73 bottles of beer on the wall, 73 bottles of beer.",
|
||||
"Take one down and pass it around, 72 bottles of beer on the wall.",
|
||||
"",
|
||||
"72 bottles of beer on the wall, 72 bottles of beer.",
|
||||
"Take one down and pass it around, 71 bottles of beer on the wall.",
|
||||
"",
|
||||
"71 bottles of beer on the wall, 71 bottles of beer.",
|
||||
"Take one down and pass it around, 70 bottles of beer on the wall.",
|
||||
"",
|
||||
"70 bottles of beer on the wall, 70 bottles of beer.",
|
||||
"Take one down and pass it around, 69 bottles of beer on the wall.",
|
||||
"",
|
||||
"69 bottles of beer on the wall, 69 bottles of beer.",
|
||||
"Take one down and pass it around, 68 bottles of beer on the wall.",
|
||||
"",
|
||||
"68 bottles of beer on the wall, 68 bottles of beer.",
|
||||
"Take one down and pass it around, 67 bottles of beer on the wall.",
|
||||
"",
|
||||
"67 bottles of beer on the wall, 67 bottles of beer.",
|
||||
"Take one down and pass it around, 66 bottles of beer on the wall.",
|
||||
"",
|
||||
"66 bottles of beer on the wall, 66 bottles of beer.",
|
||||
"Take one down and pass it around, 65 bottles of beer on the wall.",
|
||||
"",
|
||||
"65 bottles of beer on the wall, 65 bottles of beer.",
|
||||
"Take one down and pass it around, 64 bottles of beer on the wall.",
|
||||
"",
|
||||
"64 bottles of beer on the wall, 64 bottles of beer.",
|
||||
"Take one down and pass it around, 63 bottles of beer on the wall.",
|
||||
"",
|
||||
"63 bottles of beer on the wall, 63 bottles of beer.",
|
||||
"Take one down and pass it around, 62 bottles of beer on the wall.",
|
||||
"",
|
||||
"62 bottles of beer on the wall, 62 bottles of beer.",
|
||||
"Take one down and pass it around, 61 bottles of beer on the wall.",
|
||||
"",
|
||||
"61 bottles of beer on the wall, 61 bottles of beer.",
|
||||
"Take one down and pass it around, 60 bottles of beer on the wall.",
|
||||
"",
|
||||
"60 bottles of beer on the wall, 60 bottles of beer.",
|
||||
"Take one down and pass it around, 59 bottles of beer on the wall.",
|
||||
"",
|
||||
"59 bottles of beer on the wall, 59 bottles of beer.",
|
||||
"Take one down and pass it around, 58 bottles of beer on the wall.",
|
||||
"",
|
||||
"58 bottles of beer on the wall, 58 bottles of beer.",
|
||||
"Take one down and pass it around, 57 bottles of beer on the wall.",
|
||||
"",
|
||||
"57 bottles of beer on the wall, 57 bottles of beer.",
|
||||
"Take one down and pass it around, 56 bottles of beer on the wall.",
|
||||
"",
|
||||
"56 bottles of beer on the wall, 56 bottles of beer.",
|
||||
"Take one down and pass it around, 55 bottles of beer on the wall.",
|
||||
"",
|
||||
"55 bottles of beer on the wall, 55 bottles of beer.",
|
||||
"Take one down and pass it around, 54 bottles of beer on the wall.",
|
||||
"",
|
||||
"54 bottles of beer on the wall, 54 bottles of beer.",
|
||||
"Take one down and pass it around, 53 bottles of beer on the wall.",
|
||||
"",
|
||||
"53 bottles of beer on the wall, 53 bottles of beer.",
|
||||
"Take one down and pass it around, 52 bottles of beer on the wall.",
|
||||
"",
|
||||
"52 bottles of beer on the wall, 52 bottles of beer.",
|
||||
"Take one down and pass it around, 51 bottles of beer on the wall.",
|
||||
"",
|
||||
"51 bottles of beer on the wall, 51 bottles of beer.",
|
||||
"Take one down and pass it around, 50 bottles of beer on the wall.",
|
||||
"",
|
||||
"50 bottles of beer on the wall, 50 bottles of beer.",
|
||||
"Take one down and pass it around, 49 bottles of beer on the wall.",
|
||||
"",
|
||||
"49 bottles of beer on the wall, 49 bottles of beer.",
|
||||
"Take one down and pass it around, 48 bottles of beer on the wall.",
|
||||
"",
|
||||
"48 bottles of beer on the wall, 48 bottles of beer.",
|
||||
"Take one down and pass it around, 47 bottles of beer on the wall.",
|
||||
"",
|
||||
"47 bottles of beer on the wall, 47 bottles of beer.",
|
||||
"Take one down and pass it around, 46 bottles of beer on the wall.",
|
||||
"",
|
||||
"46 bottles of beer on the wall, 46 bottles of beer.",
|
||||
"Take one down and pass it around, 45 bottles of beer on the wall.",
|
||||
"",
|
||||
"45 bottles of beer on the wall, 45 bottles of beer.",
|
||||
"Take one down and pass it around, 44 bottles of beer on the wall.",
|
||||
"",
|
||||
"44 bottles of beer on the wall, 44 bottles of beer.",
|
||||
"Take one down and pass it around, 43 bottles of beer on the wall.",
|
||||
"",
|
||||
"43 bottles of beer on the wall, 43 bottles of beer.",
|
||||
"Take one down and pass it around, 42 bottles of beer on the wall.",
|
||||
"",
|
||||
"42 bottles of beer on the wall, 42 bottles of beer.",
|
||||
"Take one down and pass it around, 41 bottles of beer on the wall.",
|
||||
"",
|
||||
"41 bottles of beer on the wall, 41 bottles of beer.",
|
||||
"Take one down and pass it around, 40 bottles of beer on the wall.",
|
||||
"",
|
||||
"40 bottles of beer on the wall, 40 bottles of beer.",
|
||||
"Take one down and pass it around, 39 bottles of beer on the wall.",
|
||||
"",
|
||||
"39 bottles of beer on the wall, 39 bottles of beer.",
|
||||
"Take one down and pass it around, 38 bottles of beer on the wall.",
|
||||
"",
|
||||
"38 bottles of beer on the wall, 38 bottles of beer.",
|
||||
"Take one down and pass it around, 37 bottles of beer on the wall.",
|
||||
"",
|
||||
"37 bottles of beer on the wall, 37 bottles of beer.",
|
||||
"Take one down and pass it around, 36 bottles of beer on the wall.",
|
||||
"",
|
||||
"36 bottles of beer on the wall, 36 bottles of beer.",
|
||||
"Take one down and pass it around, 35 bottles of beer on the wall.",
|
||||
"",
|
||||
"35 bottles of beer on the wall, 35 bottles of beer.",
|
||||
"Take one down and pass it around, 34 bottles of beer on the wall.",
|
||||
"",
|
||||
"34 bottles of beer on the wall, 34 bottles of beer.",
|
||||
"Take one down and pass it around, 33 bottles of beer on the wall.",
|
||||
"",
|
||||
"33 bottles of beer on the wall, 33 bottles of beer.",
|
||||
"Take one down and pass it around, 32 bottles of beer on the wall.",
|
||||
"",
|
||||
"32 bottles of beer on the wall, 32 bottles of beer.",
|
||||
"Take one down and pass it around, 31 bottles of beer on the wall.",
|
||||
"",
|
||||
"31 bottles of beer on the wall, 31 bottles of beer.",
|
||||
"Take one down and pass it around, 30 bottles of beer on the wall.",
|
||||
"",
|
||||
"30 bottles of beer on the wall, 30 bottles of beer.",
|
||||
"Take one down and pass it around, 29 bottles of beer on the wall.",
|
||||
"",
|
||||
"29 bottles of beer on the wall, 29 bottles of beer.",
|
||||
"Take one down and pass it around, 28 bottles of beer on the wall.",
|
||||
"",
|
||||
"28 bottles of beer on the wall, 28 bottles of beer.",
|
||||
"Take one down and pass it around, 27 bottles of beer on the wall.",
|
||||
"",
|
||||
"27 bottles of beer on the wall, 27 bottles of beer.",
|
||||
"Take one down and pass it around, 26 bottles of beer on the wall.",
|
||||
"",
|
||||
"26 bottles of beer on the wall, 26 bottles of beer.",
|
||||
"Take one down and pass it around, 25 bottles of beer on the wall.",
|
||||
"",
|
||||
"25 bottles of beer on the wall, 25 bottles of beer.",
|
||||
"Take one down and pass it around, 24 bottles of beer on the wall.",
|
||||
"",
|
||||
"24 bottles of beer on the wall, 24 bottles of beer.",
|
||||
"Take one down and pass it around, 23 bottles of beer on the wall.",
|
||||
"",
|
||||
"23 bottles of beer on the wall, 23 bottles of beer.",
|
||||
"Take one down and pass it around, 22 bottles of beer on the wall.",
|
||||
"",
|
||||
"22 bottles of beer on the wall, 22 bottles of beer.",
|
||||
"Take one down and pass it around, 21 bottles of beer on the wall.",
|
||||
"",
|
||||
"21 bottles of beer on the wall, 21 bottles of beer.",
|
||||
"Take one down and pass it around, 20 bottles of beer on the wall.",
|
||||
"",
|
||||
"20 bottles of beer on the wall, 20 bottles of beer.",
|
||||
"Take one down and pass it around, 19 bottles of beer on the wall.",
|
||||
"",
|
||||
"19 bottles of beer on the wall, 19 bottles of beer.",
|
||||
"Take one down and pass it around, 18 bottles of beer on the wall.",
|
||||
"",
|
||||
"18 bottles of beer on the wall, 18 bottles of beer.",
|
||||
"Take one down and pass it around, 17 bottles of beer on the wall.",
|
||||
"",
|
||||
"17 bottles of beer on the wall, 17 bottles of beer.",
|
||||
"Take one down and pass it around, 16 bottles of beer on the wall.",
|
||||
"",
|
||||
"16 bottles of beer on the wall, 16 bottles of beer.",
|
||||
"Take one down and pass it around, 15 bottles of beer on the wall.",
|
||||
"",
|
||||
"15 bottles of beer on the wall, 15 bottles of beer.",
|
||||
"Take one down and pass it around, 14 bottles of beer on the wall.",
|
||||
"",
|
||||
"14 bottles of beer on the wall, 14 bottles of beer.",
|
||||
"Take one down and pass it around, 13 bottles of beer on the wall.",
|
||||
"",
|
||||
"13 bottles of beer on the wall, 13 bottles of beer.",
|
||||
"Take one down and pass it around, 12 bottles of beer on the wall.",
|
||||
"",
|
||||
"12 bottles of beer on the wall, 12 bottles of beer.",
|
||||
"Take one down and pass it around, 11 bottles of beer on the wall.",
|
||||
"",
|
||||
"11 bottles of beer on the wall, 11 bottles of beer.",
|
||||
"Take one down and pass it around, 10 bottles of beer on the wall.",
|
||||
"",
|
||||
"10 bottles of beer on the wall, 10 bottles of beer.",
|
||||
"Take one down and pass it around, 9 bottles of beer on the wall.",
|
||||
"",
|
||||
"9 bottles of beer on the wall, 9 bottles of beer.",
|
||||
"Take one down and pass it around, 8 bottles of beer on the wall.",
|
||||
"",
|
||||
"8 bottles of beer on the wall, 8 bottles of beer.",
|
||||
"Take one down and pass it around, 7 bottles of beer on the wall.",
|
||||
"",
|
||||
"7 bottles of beer on the wall, 7 bottles of beer.",
|
||||
"Take one down and pass it around, 6 bottles of beer on the wall.",
|
||||
"",
|
||||
"6 bottles of beer on the wall, 6 bottles of beer.",
|
||||
"Take one down and pass it around, 5 bottles of beer on the wall.",
|
||||
"",
|
||||
"5 bottles of beer on the wall, 5 bottles of beer.",
|
||||
"Take one down and pass it around, 4 bottles of beer on the wall.",
|
||||
"",
|
||||
"4 bottles of beer on the wall, 4 bottles of beer.",
|
||||
"Take one down and pass it around, 3 bottles of beer on the wall.",
|
||||
"",
|
||||
"3 bottles of beer on the wall, 3 bottles of beer.",
|
||||
"Take one down and pass it around, 2 bottles of beer on the wall.",
|
||||
"",
|
||||
"2 bottles of beer on the wall, 2 bottles of beer.",
|
||||
"Take one down and pass it around, 1 bottle of beer on the wall.",
|
||||
"",
|
||||
"1 bottle of beer on the wall, 1 bottle of beer.",
|
||||
"Take it down and pass it around, no more bottles of beer on the wall.",
|
||||
"",
|
||||
"No more bottles of beer on the wall, no more bottles of beer.",
|
||||
"Go to the store and buy some more, 99 bottles of beer on the wall."
|
||||
]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
def song(first, last=0):
|
||||
verses = ''
|
||||
for number in reversed(range(last, first + 1)):
|
||||
verses += verse(number) + '\n'
|
||||
|
||||
return verses
|
||||
def recite(start, take=1):
|
||||
results = []
|
||||
for i in range(start, start - take, -1):
|
||||
results.extend(verse(i))
|
||||
if i > start - take + 1:
|
||||
results.append('')
|
||||
return results
|
||||
|
||||
|
||||
def verse(number):
|
||||
return ''.join([
|
||||
"{} of beer on the wall, ".format(_bottles(number).capitalize()),
|
||||
"{} of beer.\n".format(_bottles(number)),
|
||||
_action(number),
|
||||
_next_bottle(number),
|
||||
])
|
||||
return [
|
||||
''.join([
|
||||
"{} of beer on the wall, ".format(_bottles(number).capitalize()),
|
||||
"{} of beer.".format(_bottles(number))
|
||||
]),
|
||||
''.join([
|
||||
_action(number),
|
||||
_next_bottle(number)
|
||||
])
|
||||
]
|
||||
|
||||
|
||||
def _action(current_verse):
|
||||
@@ -25,7 +30,7 @@ def _action(current_verse):
|
||||
|
||||
|
||||
def _next_bottle(current_verse):
|
||||
return "{} of beer on the wall.\n".format(
|
||||
return "{} of beer on the wall.".format(
|
||||
_bottles(_next_verse(current_verse)),
|
||||
)
|
||||
|
||||
|
||||
@@ -61,25 +61,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test binary_search_tree_test.py`
|
||||
- Python 3.4+: `pytest binary_search_tree_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest binary_search_tree_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/binary-search-tree` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Josh Cheek [https://twitter.com/josh_cheek](https://twitter.com/josh_cheek)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
class TreeNode(object):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
def __init__(self, data, left, right):
|
||||
self.data = None
|
||||
self.left = None
|
||||
self.right = None
|
||||
|
||||
def __str__(self):
|
||||
fmt = 'TreeNode(data={}, left={}, right={})'
|
||||
return fmt.format(self.data, self.left, self.right)
|
||||
|
||||
|
||||
class BinarySearchTree(object):
|
||||
def __init__(self):
|
||||
def __init__(self, tree_data):
|
||||
pass
|
||||
|
||||
def add(self, value):
|
||||
def data(self):
|
||||
pass
|
||||
|
||||
def search(self, value):
|
||||
def sorted_data(self):
|
||||
pass
|
||||
|
||||
@@ -1,57 +1,98 @@
|
||||
import unittest
|
||||
|
||||
from binary_search_tree import BinarySearchTree
|
||||
from binary_search_tree import BinarySearchTree, TreeNode
|
||||
|
||||
|
||||
class BinarySearchTreeTests(unittest.TestCase):
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0
|
||||
|
||||
def test_add_integer_numbers(self):
|
||||
bst = BinarySearchTree()
|
||||
bst.add(1)
|
||||
bst.add(8)
|
||||
bst.add(3)
|
||||
bst.add(5)
|
||||
bst.add(2)
|
||||
self.assertEqual(list(bst.list()), [1, 2, 3, 5, 8])
|
||||
class BinarySearchTreeTest(unittest.TestCase):
|
||||
|
||||
def test_add_float_numbers(self):
|
||||
bst = BinarySearchTree()
|
||||
bst.add(7.5)
|
||||
bst.add(5.3)
|
||||
bst.add(5.5)
|
||||
bst.add(6.0)
|
||||
bst.add(7.7)
|
||||
self.assertEqual(list(bst.list()), [5.3, 5.5, 6.0, 7.5, 7.7])
|
||||
def test_data_is_retained(self):
|
||||
expected = TreeNode('4', None, None)
|
||||
self.assertTreeEqual(BinarySearchTree(['4']).data(), expected)
|
||||
|
||||
def test_add_mixed_numbers(self):
|
||||
bst = BinarySearchTree()
|
||||
bst.add(1)
|
||||
bst.add(8)
|
||||
bst.add(7.5)
|
||||
bst.add(5.3)
|
||||
self.assertEqual(list(bst.list()), [1, 5.3, 7.5, 8])
|
||||
# Test inserting data at proper node
|
||||
def test_smaller_data_at_left_node(self):
|
||||
expected = TreeNode('4', TreeNode('2', None, None), None)
|
||||
self.assertTreeEqual(BinarySearchTree(['4', '2']).data(), expected)
|
||||
|
||||
def test_add_duplicated_numbers(self):
|
||||
bst = BinarySearchTree()
|
||||
bst.add(1)
|
||||
bst.add(1)
|
||||
bst.add(7.5)
|
||||
bst.add(5.3)
|
||||
self.assertEqual(list(bst.list()), [1, 1, 5.3, 7.5])
|
||||
def test_same_number_at_left_node(self):
|
||||
expected = TreeNode('4', TreeNode('4', None, None), None)
|
||||
self.assertTreeEqual(BinarySearchTree(['4', '4']).data(), expected)
|
||||
|
||||
def test_search_existent_numbers(self):
|
||||
bst = BinarySearchTree()
|
||||
bst.add(1)
|
||||
bst.add(7.5)
|
||||
self.assertEqual(bst.search(1).value, 1)
|
||||
self.assertEqual(bst.search(7.5).value, 7.5)
|
||||
def test_greater_number_at_right_node(self):
|
||||
expected = TreeNode('4', None, TreeNode('5', None, None))
|
||||
self.assertTreeEqual(BinarySearchTree(['4', '5']).data(), expected)
|
||||
|
||||
def test_search_nonexistent_numbers(self):
|
||||
bst = BinarySearchTree()
|
||||
bst.add(1)
|
||||
bst.add(7.5)
|
||||
self.assertIs(bst.search(6), None)
|
||||
self.assertIs(bst.search(8.8), None)
|
||||
def test_can_create_complex_tree(self):
|
||||
expected = TreeNode(
|
||||
'4',
|
||||
TreeNode(
|
||||
'2',
|
||||
TreeNode('1', None, None),
|
||||
TreeNode('3', None, None)
|
||||
),
|
||||
TreeNode(
|
||||
'6',
|
||||
TreeNode('5', None, None),
|
||||
TreeNode('7', None, None)
|
||||
)
|
||||
)
|
||||
self.assertTreeEqual(
|
||||
BinarySearchTree(['4', '2', '6', '1', '3', '5', '7']).data(),
|
||||
expected
|
||||
)
|
||||
|
||||
# Test can sort data
|
||||
def test_can_sort_single_number(self):
|
||||
self.assertEqual(BinarySearchTree(['2']).sorted_data(), ['2'])
|
||||
|
||||
def test_can_sort_if_second_number_is_smaller_than_first(self):
|
||||
self.assertEqual(
|
||||
BinarySearchTree(['2', '1']).sorted_data(), ['1', '2']
|
||||
)
|
||||
|
||||
def test_can_sort_if_second_number_is_same_as_first(self):
|
||||
self.assertEqual(
|
||||
BinarySearchTree(['2', '2']).sorted_data(), ['2', '2']
|
||||
)
|
||||
|
||||
def test_can_sort_if_second_number_is_greater_than_first(self):
|
||||
self.assertEqual(
|
||||
BinarySearchTree(['2', '3']).sorted_data(), ['2', '3']
|
||||
)
|
||||
|
||||
def test_can_sort_complex_tree(self):
|
||||
self.assertEqual(
|
||||
BinarySearchTree(['2', '1', '3', '6', '7', '5']).sorted_data(),
|
||||
['1', '2', '3', '5', '6', '7']
|
||||
)
|
||||
|
||||
# Utilities
|
||||
def assertTreeEqual(self, tree_one, tree_two):
|
||||
try:
|
||||
self.compare_tree(tree_one, tree_two)
|
||||
except AssertionError:
|
||||
raise AssertionError("{} != {}".format(tree_one, tree_two))
|
||||
|
||||
def compare_tree(self, tree_one, tree_two):
|
||||
self.assertEqual(tree_one.data, tree_two.data)
|
||||
|
||||
# Compare left tree nodes
|
||||
if tree_one.left and tree_two.left:
|
||||
self.compare_tree(tree_one.left, tree_two.left)
|
||||
elif tree_one.left is None and tree_two.left is None:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError
|
||||
|
||||
# Compare right tree nodes
|
||||
if tree_one.right and tree_two.right:
|
||||
self.compare_tree(tree_one.right, tree_two.right)
|
||||
elif tree_one.right is None and tree_two.right is None:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,61 +1,51 @@
|
||||
from collections import deque
|
||||
|
||||
|
||||
class TreeNode(object):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.left_node = None
|
||||
self.right_node = None
|
||||
def __init__(self, data, left, right):
|
||||
self.data = data
|
||||
self.left = left
|
||||
self.right = right
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
fmt = 'TreeNode(data={}, left={}, right={})'
|
||||
return fmt.format(self.data, self.left, self.right)
|
||||
|
||||
|
||||
class BinarySearchTree(object):
|
||||
def __init__(self):
|
||||
def __init__(self, tree_data):
|
||||
self.root = None
|
||||
for data in tree_data:
|
||||
self.add(data)
|
||||
|
||||
def add(self, value):
|
||||
if(self.root is None):
|
||||
self.root = TreeNode(value)
|
||||
else:
|
||||
inserted = False
|
||||
cur_node = self.root
|
||||
|
||||
while not inserted:
|
||||
if(value <= cur_node.value):
|
||||
if(cur_node.left_node):
|
||||
cur_node = cur_node.left_node
|
||||
else:
|
||||
cur_node.left_node = TreeNode(value)
|
||||
inserted = True
|
||||
elif(value > cur_node.value):
|
||||
if(cur_node.right_node):
|
||||
cur_node = cur_node.right_node
|
||||
else:
|
||||
cur_node.right_node = TreeNode(value)
|
||||
inserted = True
|
||||
|
||||
def search(self, value):
|
||||
def add(self, data):
|
||||
if self.root is None:
|
||||
self.root = TreeNode(data, None, None)
|
||||
return
|
||||
inserted = False
|
||||
cur_node = self.root
|
||||
found = False
|
||||
while not found:
|
||||
if(cur_node is None):
|
||||
return None
|
||||
elif(value < cur_node.value):
|
||||
cur_node = cur_node.left_node
|
||||
elif(value > cur_node.value):
|
||||
cur_node = cur_node.right_node
|
||||
elif(value == cur_node.value):
|
||||
return cur_node
|
||||
|
||||
def list(self):
|
||||
elements = deque()
|
||||
self.trav_inorder(self.root, elements)
|
||||
while not inserted:
|
||||
if data <= cur_node.data:
|
||||
if cur_node.left:
|
||||
cur_node = cur_node.left
|
||||
else:
|
||||
cur_node.left = TreeNode(data, None, None)
|
||||
inserted = True
|
||||
elif data > cur_node.data:
|
||||
if cur_node.right:
|
||||
cur_node = cur_node.right
|
||||
else:
|
||||
cur_node.right = TreeNode(data, None, None)
|
||||
inserted = True
|
||||
|
||||
def _inorder_traverse(self, node, elements):
|
||||
if node is not None:
|
||||
self._inorder_traverse(node.left, elements)
|
||||
elements.append(node.data)
|
||||
self._inorder_traverse(node.right, elements)
|
||||
|
||||
def data(self):
|
||||
return self.root
|
||||
|
||||
def sorted_data(self):
|
||||
elements = []
|
||||
self._inorder_traverse(self.root, elements)
|
||||
return elements
|
||||
|
||||
def trav_inorder(self, node, elements):
|
||||
if(node is not None):
|
||||
self.trav_inorder(node.left_node, elements)
|
||||
elements.append(node.value)
|
||||
self.trav_inorder(node.right_node, elements)
|
||||
|
||||
@@ -42,25 +42,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test binary_search_test.py`
|
||||
- Python 3.4+: `pytest binary_search_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest binary_search_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/binary-search` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Wikipedia [http://en.wikipedia.org/wiki/Binary_search_algorithm](http://en.wikipedia.org/wiki/Binary_search_algorithm)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -2,10 +2,10 @@ import unittest
|
||||
|
||||
from binary_search import binary_search
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.3.0
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0
|
||||
|
||||
class BinarySearchTests(unittest.TestCase):
|
||||
class BinarySearchTest(unittest.TestCase):
|
||||
def test_finds_value_in_array_with_one_element(self):
|
||||
self.assertEqual(binary_search([6], 6), 0)
|
||||
|
||||
@@ -44,6 +44,10 @@ class BinarySearchTests(unittest.TestCase):
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
binary_search([], 1)
|
||||
|
||||
def test_nothing_is_found_when_left_and_right_bounds_cross(self):
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
binary_search([1, 2], 0)
|
||||
|
||||
# Utility functions
|
||||
def setUp(self):
|
||||
try:
|
||||
|
||||
@@ -38,25 +38,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test binary_test.py`
|
||||
- Python 3.4+: `pytest binary_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest binary_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/binary` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
All of Computer Science [http://www.wolframalpha.com/input/?i=binary&a=*C.binary-_*MathWorld-](http://www.wolframalpha.com/input/?i=binary&a=*C.binary-_*MathWorld-)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -9,7 +9,7 @@ import unittest
|
||||
from binary import parse_binary
|
||||
|
||||
|
||||
class BinaryTests(unittest.TestCase):
|
||||
class BinaryTest(unittest.TestCase):
|
||||
def test_binary_1_is_decimal_1(self):
|
||||
self.assertEqual(parse_binary("1"), 1)
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ Bob answers 'Sure.' if you ask him a question.
|
||||
|
||||
He answers 'Whoa, chill out!' if you yell at him.
|
||||
|
||||
He answers 'Calm down, I know what I'm doing!' if you yell a question at him.
|
||||
|
||||
He says 'Fine. Be that way!' if you address him without actually saying
|
||||
anything.
|
||||
|
||||
@@ -19,25 +21,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test bob_test.py`
|
||||
- Python 3.4+: `pytest bob_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest bob_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/bob` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Inspired by the 'Deaf Grandma' exercise in Chris Pine's Learn to Program tutorial. [http://pine.fm/LearnToProgram/?Chapter=06](http://pine.fm/LearnToProgram/?Chapter=06)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -1,100 +1,100 @@
|
||||
import unittest
|
||||
|
||||
import bob
|
||||
from bob import hey
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.2.0
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.4.0
|
||||
|
||||
class BobTests(unittest.TestCase):
|
||||
class BobTest(unittest.TestCase):
|
||||
def test_stating_something(self):
|
||||
self.assertEqual(bob.hey("Tom-ay-to, tom-aaaah-to."), "Whatever.")
|
||||
self.assertEqual(hey("Tom-ay-to, tom-aaaah-to."), "Whatever.")
|
||||
|
||||
def test_shouting(self):
|
||||
self.assertEqual(bob.hey("WATCH OUT!"), "Whoa, chill out!")
|
||||
self.assertEqual(hey("WATCH OUT!"), "Whoa, chill out!")
|
||||
|
||||
def test_shouting_gibberish(self):
|
||||
self.assertEqual(bob.hey("FCECDFCAAB"), "Whoa, chill out!")
|
||||
self.assertEqual(hey("FCECDFCAAB"), "Whoa, chill out!")
|
||||
|
||||
def test_asking_a_question(self):
|
||||
self.assertEqual(
|
||||
bob.hey("Does this cryogenic chamber make me look fat?"), "Sure.")
|
||||
hey("Does this cryogenic chamber make me look fat?"), "Sure.")
|
||||
|
||||
def test_asking_a_numeric_question(self):
|
||||
self.assertEqual(bob.hey("You are, what, like 15?"), "Sure.")
|
||||
self.assertEqual(hey("You are, what, like 15?"), "Sure.")
|
||||
|
||||
def test_asking_gibberish(self):
|
||||
self.assertEqual(bob.hey("fffbbcbeab?"), "Sure.")
|
||||
self.assertEqual(hey("fffbbcbeab?"), "Sure.")
|
||||
|
||||
def test_talking_forcefully(self):
|
||||
self.assertEqual(
|
||||
bob.hey("Let's go make out behind the gym!"), "Whatever.")
|
||||
hey("Let's go make out behind the gym!"), "Whatever.")
|
||||
|
||||
def test_using_acronyms_in_regular_speech(self):
|
||||
self.assertEqual(
|
||||
bob.hey("It's OK if you don't want to go to the DMV."),
|
||||
hey("It's OK if you don't want to go to the DMV."),
|
||||
"Whatever.")
|
||||
|
||||
def test_forceful_question(self):
|
||||
self.assertEqual(
|
||||
bob.hey("WHAT THE HELL WERE YOU THINKING?"),
|
||||
hey("WHAT THE HELL WERE YOU THINKING?"),
|
||||
"Calm down, I know what I'm doing!"
|
||||
)
|
||||
|
||||
def test_shouting_numbers(self):
|
||||
self.assertEqual(bob.hey("1, 2, 3 GO!"), "Whoa, chill out!")
|
||||
self.assertEqual(hey("1, 2, 3 GO!"), "Whoa, chill out!")
|
||||
|
||||
def test_only_numbers(self):
|
||||
self.assertEqual(bob.hey("1, 2, 3"), "Whatever.")
|
||||
def test_no_letters(self):
|
||||
self.assertEqual(hey("1, 2, 3"), "Whatever.")
|
||||
|
||||
def test_question_with_only_numbers(self):
|
||||
self.assertEqual(bob.hey("4?"), "Sure.")
|
||||
def test_question_with_no_letters(self):
|
||||
self.assertEqual(hey("4?"), "Sure.")
|
||||
|
||||
def test_shouting_with_special_characters(self):
|
||||
self.assertEqual(
|
||||
bob.hey("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"),
|
||||
hey("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"),
|
||||
"Whoa, chill out!")
|
||||
|
||||
def test_shouting_with_no_exclamation_mark(self):
|
||||
self.assertEqual(bob.hey("I HATE YOU"), "Whoa, chill out!")
|
||||
self.assertEqual(hey("I HATE THE DMV"), "Whoa, chill out!")
|
||||
|
||||
def test_statement_containing_question_mark(self):
|
||||
self.assertEqual(
|
||||
bob.hey("Ending with ? means a question."), "Whatever.")
|
||||
hey("Ending with ? means a question."), "Whatever.")
|
||||
|
||||
def test_non_letters_with_question(self):
|
||||
self.assertEqual(bob.hey(":) ?"), "Sure.")
|
||||
self.assertEqual(hey(":) ?"), "Sure.")
|
||||
|
||||
def test_prattling_on(self):
|
||||
self.assertEqual(
|
||||
bob.hey("Wait! Hang on. Are you going to be OK?"), "Sure.")
|
||||
hey("Wait! Hang on. Are you going to be OK?"), "Sure.")
|
||||
|
||||
def test_silence(self):
|
||||
self.assertEqual(bob.hey(""), "Fine. Be that way!")
|
||||
self.assertEqual(hey(""), "Fine. Be that way!")
|
||||
|
||||
def test_prolonged_silence(self):
|
||||
self.assertEqual(bob.hey(" "), "Fine. Be that way!")
|
||||
self.assertEqual(hey(" "), "Fine. Be that way!")
|
||||
|
||||
def test_alternate_silence(self):
|
||||
self.assertEqual(bob.hey("\t\t\t\t\t\t\t\t\t\t"), "Fine. Be that way!")
|
||||
self.assertEqual(hey("\t\t\t\t\t\t\t\t\t\t"), "Fine. Be that way!")
|
||||
|
||||
def test_multiple_line_question(self):
|
||||
self.assertEqual(
|
||||
bob.hey("\nDoes this cryogenic chamber make me look fat?\nno"),
|
||||
hey("\nDoes this cryogenic chamber make me look fat?\nNo."),
|
||||
"Whatever.")
|
||||
|
||||
def test_starting_with_whitespace(self):
|
||||
self.assertEqual(bob.hey(" hmmmmmmm..."), "Whatever.")
|
||||
self.assertEqual(hey(" hmmmmmmm..."), "Whatever.")
|
||||
|
||||
def test_ending_with_whitespace(self):
|
||||
self.assertEqual(
|
||||
bob.hey("Okay if like my spacebar quite a bit? "), "Sure.")
|
||||
hey("Okay if like my spacebar quite a bit? "), "Sure.")
|
||||
|
||||
def test_other_whitespace(self):
|
||||
self.assertEqual(bob.hey("\n\r \t"), "Fine. Be that way!")
|
||||
self.assertEqual(hey("\n\r \t"), "Fine. Be that way!")
|
||||
|
||||
def test_non_question_ending_with_whitespace(self):
|
||||
self.assertEqual(
|
||||
bob.hey("This is a statement ending with whitespace "),
|
||||
hey("This is a statement ending with whitespace "),
|
||||
"Whatever.")
|
||||
|
||||
|
||||
|
||||
@@ -75,25 +75,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test book_store_test.py`
|
||||
- Python 3.4+: `pytest book_store_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest book_store_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/book-store` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Inspired by the harry potter kata from Cyber-Dojo. [http://cyber-dojo.org](http://cyber-dojo.org)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -3,75 +3,57 @@ import unittest
|
||||
from book_store import calculate_total
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.4.0
|
||||
|
||||
class BookStoreTests(unittest.TestCase):
|
||||
class BookStoreTest(unittest.TestCase):
|
||||
def test_only_a_single_book(self):
|
||||
self.assertAlmostEqual(calculate_total([1]), 8.00,
|
||||
places=2)
|
||||
self.assertEqual(calculate_total([1]), 800)
|
||||
|
||||
def test_two_of_the_same_book(self):
|
||||
self.assertAlmostEqual(calculate_total([2, 2]), 16.00,
|
||||
places=2)
|
||||
self.assertEqual(calculate_total([2, 2]), 1600)
|
||||
|
||||
def test_empty_basket(self):
|
||||
self.assertAlmostEqual(calculate_total([]), 0.00,
|
||||
places=2)
|
||||
self.assertEqual(calculate_total([]), 0)
|
||||
|
||||
def test_two_different_books(self):
|
||||
self.assertAlmostEqual(calculate_total([1, 2]), 15.20,
|
||||
places=2)
|
||||
self.assertEqual(calculate_total([1, 2]), 1520)
|
||||
|
||||
def test_three_different_books(self):
|
||||
self.assertAlmostEqual(calculate_total([1, 2, 3]), 21.60,
|
||||
places=2)
|
||||
self.assertEqual(calculate_total([1, 2, 3]), 2160)
|
||||
|
||||
def test_four_different_books(self):
|
||||
self.assertAlmostEqual(calculate_total([1, 2, 3, 4]), 25.60,
|
||||
places=2)
|
||||
self.assertEqual(calculate_total([1, 2, 3, 4]), 2560)
|
||||
|
||||
def test_five_different_books(self):
|
||||
self.assertAlmostEqual(
|
||||
calculate_total([1, 2, 3, 4, 5]), 30.00,
|
||||
places=2)
|
||||
self.assertEqual(calculate_total([1, 2, 3, 4, 5]), 3000)
|
||||
|
||||
def test_two_groups_of_4_is_cheaper_than_group_of_5_plus_group_of_3(self):
|
||||
self.assertAlmostEqual(
|
||||
calculate_total([1, 1, 2, 2, 3, 3, 4, 5]), 51.20,
|
||||
places=2)
|
||||
self.assertEqual(calculate_total([1, 1, 2, 2, 3, 3, 4, 5]), 5120)
|
||||
|
||||
def test_two_groups_of_4_is_cheaper_than_groups_of_5_and_3(self):
|
||||
self.assertEqual(calculate_total([1, 1, 2, 3, 4, 4, 5, 5]), 5120)
|
||||
|
||||
def test_group_of_4_plus_group_of_2_is_cheaper_than_2_groups_of_3(self):
|
||||
self.assertAlmostEqual(
|
||||
calculate_total([1, 1, 2, 2, 3, 4]), 40.80,
|
||||
places=2)
|
||||
self.assertEqual(calculate_total([1, 1, 2, 2, 3, 4]), 4080)
|
||||
|
||||
def test_two_each_of_first_4_books_and_1_copy_each_of_rest(self):
|
||||
self.assertAlmostEqual(
|
||||
calculate_total([1, 1, 2, 2, 3, 3, 4, 4, 5]), 55.60,
|
||||
places=2)
|
||||
self.assertEqual(calculate_total([1, 1, 2, 2, 3, 3, 4, 4, 5]), 5560)
|
||||
|
||||
def test_two_copies_of_each_book(self):
|
||||
self.assertAlmostEqual(
|
||||
calculate_total([1, 1, 2, 2, 3, 3, 4, 4, 5, 5]), 60.00,
|
||||
places=2)
|
||||
self.assertEqual(calculate_total([1, 1, 2, 2, 3, 3, 4, 4, 5, 5]), 6000)
|
||||
|
||||
def test_three_copies_of_first_book_and_2_each_of_remaining(self):
|
||||
self.assertAlmostEqual(
|
||||
calculate_total([1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1]),
|
||||
68.00,
|
||||
places=2)
|
||||
self.assertEqual(
|
||||
calculate_total([1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1]), 6800)
|
||||
|
||||
def test_three_each_of_first_2_books_and_2_each_of_remaining_books(self):
|
||||
self.assertAlmostEqual(
|
||||
calculate_total([1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2]),
|
||||
75.20,
|
||||
places=2)
|
||||
self.assertEqual(
|
||||
calculate_total([1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2]), 7520)
|
||||
|
||||
def test_four_groups_of_4_are_cheaper_than_2_groups_each_of_5_and_3(self):
|
||||
self.assertAlmostEqual(
|
||||
self.assertEqual(
|
||||
calculate_total([1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5]),
|
||||
102.40,
|
||||
places=2)
|
||||
10240)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,29 +1,49 @@
|
||||
BOOK_PRICE = 8
|
||||
|
||||
|
||||
def _group_price(size):
|
||||
discounts = [0, .05, .1, .2, .25]
|
||||
if not (0 < size <= 5):
|
||||
raise ValueError('size must be in 1..' + len(discounts))
|
||||
return BOOK_PRICE * size * (1 - discounts[size - 1])
|
||||
|
||||
|
||||
def calculate_total(books, price_so_far=0.):
|
||||
if not books:
|
||||
return price_so_far
|
||||
|
||||
groups = list(set(books))
|
||||
min_price = float('inf')
|
||||
|
||||
for i in range(len(groups)):
|
||||
|
||||
remaining_books = books[:]
|
||||
|
||||
for v in groups[:i + 1]:
|
||||
remaining_books.remove(v)
|
||||
|
||||
price = calculate_total(remaining_books,
|
||||
price_so_far + _group_price(i + 1))
|
||||
min_price = min(min_price, price)
|
||||
|
||||
return min_price
|
||||
from functools import reduce
|
||||
|
||||
BASE_COST = 800
|
||||
discount = [1.0, 1.0, 0.95, 0.9, 0.8, 0.75]
|
||||
|
||||
|
||||
def groupCost(g):
|
||||
return len(g) * discount[len(g)]
|
||||
|
||||
|
||||
class Grouping:
|
||||
def __init__(self, groups=None):
|
||||
self.groups = [set()] if groups is None else groups
|
||||
|
||||
def total(self):
|
||||
return sum(map(groupCost, self.groups)) * BASE_COST
|
||||
|
||||
def dup(self):
|
||||
return Grouping(list(map(set, self.groups)))
|
||||
|
||||
def add_to_valid(self, b):
|
||||
"""Returns all possible groupings from current grouping adding book b
|
||||
"""
|
||||
other = self.dup()
|
||||
other.groups.sort(key=lambda g: len(g))
|
||||
results = []
|
||||
for i, g in enumerate(other.groups):
|
||||
if b not in g:
|
||||
o2 = other.dup()
|
||||
o2.groups[i].add(b)
|
||||
results.append(o2)
|
||||
if not results:
|
||||
other.groups.append(set([b]))
|
||||
return [other]
|
||||
return results
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.total() < other.total()
|
||||
|
||||
|
||||
def step(rs, b):
|
||||
return [g for r in rs for g in r.add_to_valid(b)]
|
||||
|
||||
|
||||
def calculate_total(books):
|
||||
if len(books) == 0:
|
||||
return 0
|
||||
start = Grouping([{books[0]}])
|
||||
return round(min(reduce(step, books[1:], [start])).total())
|
||||
|
||||
110
exercises/bowling/README.md
Normal file
110
exercises/bowling/README.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Bowling
|
||||
|
||||
Score a bowling game.
|
||||
|
||||
Bowling is a game where players roll a heavy ball to knock down pins
|
||||
arranged in a triangle. Write code to keep track of the score
|
||||
of a game of bowling.
|
||||
|
||||
## Scoring Bowling
|
||||
|
||||
The game consists of 10 frames. A frame is composed of one or two ball
|
||||
throws with 10 pins standing at frame initialization. There are three
|
||||
cases for the tabulation of a frame.
|
||||
|
||||
* An open frame is where a score of less than 10 is recorded for the
|
||||
frame. In this case the score for the frame is the number of pins
|
||||
knocked down.
|
||||
|
||||
* A spare is where all ten pins are knocked down by the second
|
||||
throw. The total value of a spare is 10 plus the number of pins
|
||||
knocked down in their next throw.
|
||||
|
||||
* A strike is where all ten pins are knocked down by the first
|
||||
throw. The total value of a strike is 10 plus the number of pins
|
||||
knocked down in the next two throws. If a strike is immediately
|
||||
followed by a second strike, then the value of the first strike
|
||||
cannot be determined until the ball is thrown one more time.
|
||||
|
||||
Here is a three frame example:
|
||||
|
||||
| Frame 1 | Frame 2 | Frame 3 |
|
||||
| :-------------: |:-------------:| :---------------------:|
|
||||
| X (strike) | 5/ (spare) | 9 0 (open frame) |
|
||||
|
||||
Frame 1 is (10 + 5 + 5) = 20
|
||||
|
||||
Frame 2 is (5 + 5 + 9) = 19
|
||||
|
||||
Frame 3 is (9 + 0) = 9
|
||||
|
||||
This means the current running total is 48.
|
||||
|
||||
The tenth frame in the game is a special case. If someone throws a
|
||||
strike or a spare then they get a fill ball. Fill balls exist to
|
||||
calculate the total of the 10th frame. Scoring a strike or spare on
|
||||
the fill ball does not give the player more fill balls. The total
|
||||
value of the 10th frame is the total number of pins knocked down.
|
||||
|
||||
For a tenth frame of X1/ (strike and a spare), the total value is 20.
|
||||
|
||||
For a tenth frame of XXX (three strikes), the total value is 30.
|
||||
|
||||
## Requirements
|
||||
|
||||
Write code to keep track of the score of a game of bowling. It should
|
||||
support two operations:
|
||||
|
||||
* `roll(pins : int)` is called each time the player rolls a ball. The
|
||||
argument is the number of pins knocked down.
|
||||
* `score() : int` is called only at the very end of the game. It
|
||||
returns the total score for that game.
|
||||
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test bowling_test.py`
|
||||
- Python 3.4+: `pytest bowling_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest bowling_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/bowling` directory.
|
||||
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
The Bowling Game Kata at but UncleBob [http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata](http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
class BowlingGame(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@@ -3,11 +3,9 @@ import unittest
|
||||
from bowling import BowlingGame
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.1
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.2.0
|
||||
|
||||
class BowlingTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.game = BowlingGame()
|
||||
class BowlingTest(unittest.TestCase):
|
||||
|
||||
def roll(self, rolls):
|
||||
[self.game.roll(roll) for roll in rolls]
|
||||
@@ -120,23 +118,27 @@ class BowlingTests(unittest.TestCase):
|
||||
|
||||
def test_rolls_cannot_score_negative_points(self):
|
||||
|
||||
self.assertRaises(ValueError, self.game.roll, -11)
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
self.game.roll(-1)
|
||||
|
||||
def test_a_roll_cannot_score_more_than_10_points(self):
|
||||
|
||||
self.assertRaises(ValueError, self.game.roll, 11)
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
self.game.roll(11)
|
||||
|
||||
def test_two_rolls_in_a_frame_cannot_score_more_than_10_points(self):
|
||||
self.game.roll(5)
|
||||
|
||||
self.assertRaises(ValueError, self.game.roll, 6)
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
self.game.roll(6)
|
||||
|
||||
def test_bonus_after_strike_in_last_frame_cannot_score_more_than_10(self):
|
||||
rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10]
|
||||
|
||||
self.roll(rolls)
|
||||
|
||||
self.assertRaises(ValueError, self.game.roll, 11)
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
self.game.roll(11)
|
||||
|
||||
def test_bonus_aft_last_frame_strk_can_be_more_than_10_if_1_is_strk(self):
|
||||
rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10,
|
||||
@@ -151,42 +153,76 @@ class BowlingTests(unittest.TestCase):
|
||||
|
||||
self.roll(rolls)
|
||||
|
||||
self.assertRaises(ValueError, self.game.roll, 10)
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
self.game.roll(10)
|
||||
|
||||
def test_an_incomplete_game_cannot_be_scored(self):
|
||||
rolls = [0, 0]
|
||||
|
||||
self.roll(rolls)
|
||||
|
||||
self.assertRaises(IndexError, self.game.score)
|
||||
with self.assertRaisesWithMessage(IndexError):
|
||||
self.game.score()
|
||||
|
||||
def test_cannot_roll_if_there_are_already_ten_frames(self):
|
||||
rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
|
||||
self.roll(rolls)
|
||||
|
||||
self.assertRaises(IndexError, self.game.roll, 0)
|
||||
with self.assertRaisesWithMessage(IndexError):
|
||||
self.game.roll(0)
|
||||
|
||||
def test_bonus_rolls_for_strike_must_be_rolled_before_score_is_calc(self):
|
||||
rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10]
|
||||
|
||||
self.roll(rolls)
|
||||
|
||||
self.assertRaises(IndexError, self.game.score)
|
||||
with self.assertRaisesWithMessage(IndexError):
|
||||
self.game.score()
|
||||
|
||||
def test_both_bonuses_for_strike_must_be_rolled_before_score(self):
|
||||
rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10]
|
||||
|
||||
self.roll(rolls)
|
||||
|
||||
self.assertRaises(IndexError, self.game.score)
|
||||
with self.assertRaisesWithMessage(IndexError):
|
||||
self.game.score()
|
||||
|
||||
def test_bonus_rolls_for_spare_must_be_rolled_before_score_is_calc(self):
|
||||
rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3]
|
||||
|
||||
self.roll(rolls)
|
||||
|
||||
self.assertRaises(IndexError, self.game.score)
|
||||
with self.assertRaisesWithMessage(IndexError):
|
||||
self.game.score()
|
||||
|
||||
def test_cannot_roll_after_bonus_roll_for_spare(self):
|
||||
rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 2]
|
||||
|
||||
self.roll(rolls)
|
||||
|
||||
with self.assertRaisesWithMessage(IndexError):
|
||||
self.game.roll(2)
|
||||
|
||||
def test_cannot_roll_after_bonus_rolls_for_strike(self):
|
||||
rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10,
|
||||
3, 2]
|
||||
|
||||
self.roll(rolls)
|
||||
|
||||
with self.assertRaisesWithMessage(IndexError):
|
||||
self.game.roll(2)
|
||||
|
||||
# Utility functions
|
||||
def setUp(self):
|
||||
self.game = BowlingGame()
|
||||
try:
|
||||
self.assertRaisesRegex
|
||||
except AttributeError:
|
||||
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||
|
||||
def assertRaisesWithMessage(self, exception):
|
||||
return self.assertRaisesRegex(exception, r".+")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,149 +1,99 @@
|
||||
MAX_PINS = 10
|
||||
NUM_FRAMES = 10
|
||||
|
||||
|
||||
class BowlingGame(object):
|
||||
"""This class manages the Bowling Game including the roll and score
|
||||
methods"""
|
||||
def __init__(self):
|
||||
self.rolls = []
|
||||
self.totalScore = 0
|
||||
self.currentFrame = Frame()
|
||||
self.bonusRollsAccrued = 0
|
||||
self.bonusRollsSeen = 0
|
||||
|
||||
def roll(self, pins):
|
||||
if self.isBonusRoll():
|
||||
self.bonusRollsSeen += 1
|
||||
|
||||
# is the second roll valid based off the first?
|
||||
if (self.currentFrame.isOpen() and
|
||||
self.currentFrame.getFrame()[0] is not None):
|
||||
if self.currentFrame.getFrame()[0] + pins > MAX_PINS:
|
||||
raise ValueError("This roll will cause the current frame "
|
||||
"to be getter than the max number of pins")
|
||||
|
||||
# open a new frame if the last one has been closed
|
||||
if not self.currentFrame.isOpen():
|
||||
self.currentFrame = Frame()
|
||||
|
||||
# valid roll between 0-10
|
||||
if pins in range(MAX_PINS + 1):
|
||||
# raise an error if the game is over and they try to roll again
|
||||
if ((len(self.rolls) == NUM_FRAMES) and
|
||||
self.bonusRollsAccrued == 0):
|
||||
raise IndexError("Max Frames have been reached. Too many "
|
||||
"rolls")
|
||||
else:
|
||||
self.currentFrame.roll(pins,
|
||||
self.isBonusRoll(),
|
||||
self.bonusRollsAccrued,
|
||||
self.bonusRollsSeen)
|
||||
# if we closed it add it to our rolls
|
||||
if not self.currentFrame.isOpen():
|
||||
self.rolls.append(self.currentFrame)
|
||||
# If this is the last frame did we earn any bonus rolls?
|
||||
if len(self.rolls) == NUM_FRAMES:
|
||||
self.bonusRollsEarned()
|
||||
else:
|
||||
raise ValueError("Amount of pins rolled is greater than the max "
|
||||
"number of pins")
|
||||
|
||||
def score(self):
|
||||
frame_index = 0
|
||||
|
||||
while (frame_index <= NUM_FRAMES-1):
|
||||
frame = self.rolls[frame_index].getFrame()
|
||||
|
||||
roll1 = frame[0]
|
||||
roll2 = frame[1]
|
||||
|
||||
if self.isStrike(roll1):
|
||||
self.totalScore += roll1 + self.stikeBonus(frame_index)
|
||||
else:
|
||||
if self.isSpare(roll1, roll2):
|
||||
self.totalScore += roll1 + roll2 + \
|
||||
self.spareBonus(frame_index)
|
||||
else:
|
||||
self.totalScore += roll1 + roll2
|
||||
|
||||
frame_index += 1
|
||||
|
||||
return self.totalScore
|
||||
|
||||
def isStrike(self, pins):
|
||||
return True if pins == MAX_PINS else False
|
||||
|
||||
def isSpare(self, roll1, roll2):
|
||||
return True if roll1 + roll2 == MAX_PINS else False
|
||||
|
||||
def stikeBonus(self, frame_index):
|
||||
bonusroll1 = self.rolls[frame_index+1].getFrame()[0]
|
||||
bonusroll2 = 0
|
||||
# need to go further out if the next on is a strike
|
||||
if bonusroll1 == 10:
|
||||
bonusroll2 = self.rolls[frame_index+2].getFrame()[0]
|
||||
else:
|
||||
bonusroll2 = self.rolls[frame_index+1].getFrame()[1]
|
||||
# edge case - if the last roll is a stike the bonus rolls needs to be
|
||||
# validated
|
||||
if (not self.isStrike(bonusroll1) and
|
||||
(bonusroll1 + bonusroll2 > MAX_PINS)):
|
||||
raise ValueError("The bonus rolls total to greater than the max "
|
||||
"number of pins")
|
||||
else:
|
||||
return bonusroll1 + bonusroll2
|
||||
|
||||
def spareBonus(self, frame_index):
|
||||
return self.rolls[frame_index+1].getFrame()[0]
|
||||
|
||||
def isLastFrame(self, frame_index):
|
||||
return True if frame_index >= len(self.rolls)-1 else False
|
||||
|
||||
def bonusRollsEarned(self):
|
||||
if len(self.rolls) == NUM_FRAMES:
|
||||
lastFrame = self.rolls[NUM_FRAMES-1].getFrame()
|
||||
if self.isStrike(lastFrame[0]):
|
||||
self.bonusRollsAccrued = 2
|
||||
elif self.isSpare(lastFrame[0], lastFrame[1]):
|
||||
self.bonusRollsAccrued = 1
|
||||
else:
|
||||
self.bonusRollsAccrued = 0
|
||||
return
|
||||
|
||||
def isBonusRoll(self):
|
||||
# if we've already seen all
|
||||
return True if len(self.rolls) >= NUM_FRAMES else False
|
||||
MAX_FRAME = 10
|
||||
|
||||
|
||||
class Frame(object):
|
||||
"""This class is for internal use only. It divides up the array of
|
||||
rolls into Frame objects"""
|
||||
def __init__(self, idx):
|
||||
self.idx = idx
|
||||
self.throws = []
|
||||
|
||||
@property
|
||||
def total_pins(self):
|
||||
"""Total pins knocked down in a frame."""
|
||||
return sum(self.throws)
|
||||
|
||||
def is_strike(self):
|
||||
return self.total_pins == 10 and len(self.throws) == 1
|
||||
|
||||
def is_spare(self):
|
||||
return self.total_pins == 10 and len(self.throws) == 2
|
||||
|
||||
def is_open(self):
|
||||
return self.total_pins < 10 and len(self.throws) == 2
|
||||
|
||||
def is_closed(self):
|
||||
"""Return whether a frame is over."""
|
||||
return self.total_pins == 10 or len(self.throws) == 2
|
||||
|
||||
def throw(self, pins):
|
||||
if self.total_pins + pins > 10:
|
||||
raise ValueError("a frame's rolls cannot exceed 10")
|
||||
self.throws.append(pins)
|
||||
|
||||
def score(self, next_throws):
|
||||
result = self.total_pins
|
||||
if self.is_strike():
|
||||
result += sum(next_throws[:2])
|
||||
elif self.is_spare():
|
||||
result += sum(next_throws[:1])
|
||||
return result
|
||||
|
||||
|
||||
class BowlingGame(object):
|
||||
def __init__(self):
|
||||
self.rolls = [None, None]
|
||||
self.open = True
|
||||
self.current_frame_idx = 0
|
||||
self.bonus_throws = []
|
||||
self.frames = [Frame(idx) for idx in range(MAX_FRAME)]
|
||||
|
||||
def roll(self, roll, bonusRoll, accruedBonuses, seenBonuses):
|
||||
# if it's a strike we close the frame
|
||||
if roll == 10:
|
||||
self.rolls[0] = 10
|
||||
self.rolls[1] = 0
|
||||
self.open = False
|
||||
@property
|
||||
def current_frame(self):
|
||||
return self.frames[self.current_frame_idx]
|
||||
|
||||
def next_throws(self, frame_idx):
|
||||
"""Return a frame's next throws in the form of a list."""
|
||||
throws = []
|
||||
for idx in range(frame_idx + 1, MAX_FRAME):
|
||||
throws.extend(self.frames[idx].throws)
|
||||
throws.extend(self.bonus_throws)
|
||||
return throws
|
||||
|
||||
def roll_bonus(self, pins):
|
||||
tenth_frame = self.frames[-1]
|
||||
if tenth_frame.is_open():
|
||||
raise IndexError("cannot throw bonus with an open tenth frame")
|
||||
|
||||
self.bonus_throws.append(pins)
|
||||
|
||||
# Check against invalid fill balls, e.g. [3, 10]
|
||||
if (len(self.bonus_throws) == 2 and self.bonus_throws[0] != 10 and
|
||||
sum(self.bonus_throws) > 10):
|
||||
raise ValueError("invalid fill balls")
|
||||
|
||||
# Check if there are more bonuses than it should be
|
||||
if tenth_frame.is_strike() and len(self.bonus_throws) > 2:
|
||||
raise IndexError(
|
||||
"wrong number of fill balls when the tenth frame is a strike")
|
||||
elif tenth_frame.is_spare() and len(self.bonus_throws) > 1:
|
||||
raise IndexError(
|
||||
"wrong number of fill balls when the tenth frame is a spare")
|
||||
|
||||
def roll(self, pins):
|
||||
if not 0 <= pins <= 10:
|
||||
raise ValueError("invalid pins")
|
||||
elif self.current_frame_idx == MAX_FRAME:
|
||||
self.roll_bonus(pins)
|
||||
else:
|
||||
# first roll, but frame is still open
|
||||
if self.rolls[0] is None:
|
||||
self.rolls[0] = roll
|
||||
# may need to close bonus roll frames before 2 have been seen
|
||||
if bonusRoll and seenBonuses == accruedBonuses:
|
||||
self.rolls[1] = 0
|
||||
self.open = False
|
||||
else:
|
||||
# second roll, closes frame
|
||||
self.rolls[1] = roll
|
||||
self.open = False
|
||||
self.current_frame.throw(pins)
|
||||
if self.current_frame.is_closed():
|
||||
self.current_frame_idx += 1
|
||||
|
||||
def isOpen(self):
|
||||
return self.open
|
||||
|
||||
def getFrame(self):
|
||||
return self.rolls
|
||||
def score(self):
|
||||
if self.current_frame_idx < MAX_FRAME:
|
||||
raise IndexError("frame less than 10")
|
||||
if self.frames[-1].is_spare() and len(self.bonus_throws) != 1:
|
||||
raise IndexError(
|
||||
"one bonus must be rolled when the tenth frame is spare")
|
||||
if self.frames[-1].is_strike() and len(self.bonus_throws) != 2:
|
||||
raise IndexError(
|
||||
"two bonuses must be rolled when the tenth frame is strike")
|
||||
return sum(frame.score(self.next_throws(frame.idx))
|
||||
for frame in self.frames)
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# Bracket Push
|
||||
|
||||
Given a string containing brackets `[]`, braces `{}` and parentheses `()`,
|
||||
verify that all the pairs are matched and nested correctly.
|
||||
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
|
||||
## Source
|
||||
|
||||
Ginna Baker
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
@@ -1,2 +0,0 @@
|
||||
def check_brackets(input_string):
|
||||
pass
|
||||
@@ -1,57 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from bracket_push import check_brackets
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0
|
||||
|
||||
class BracketPushTests(unittest.TestCase):
|
||||
def test_paired_square_brackets(self):
|
||||
self.assertEqual(check_brackets("[]"), True)
|
||||
|
||||
def test_empty_string(self):
|
||||
self.assertEqual(check_brackets(""), True)
|
||||
|
||||
def test_unpaired_brackets(self):
|
||||
self.assertEqual(check_brackets("[["), False)
|
||||
|
||||
def test_wrong_ordered_brackets(self):
|
||||
self.assertEqual(check_brackets("}{"), False)
|
||||
|
||||
def test_wrong_closing_bracket(self):
|
||||
self.assertEqual(check_brackets("{]"), False)
|
||||
|
||||
def test_paired_with_whitespace(self):
|
||||
self.assertEqual(check_brackets("{ }"), True)
|
||||
|
||||
def test_simple_nested_brackets(self):
|
||||
self.assertEqual(check_brackets("{[]}"), True)
|
||||
|
||||
def test_several_paired_brackets(self):
|
||||
self.assertEqual(check_brackets("{}[]"), True)
|
||||
|
||||
def test_paired_and_nested_brackets(self):
|
||||
self.assertEqual(check_brackets("([{}({}[])])"), True)
|
||||
|
||||
def test_unopened_closing_brackets(self):
|
||||
self.assertEqual(check_brackets("{[)][]}"), False)
|
||||
|
||||
def test_unpaired_and_nested_brackets(self):
|
||||
self.assertEqual(check_brackets("([{])"), False)
|
||||
|
||||
def test_paired_and_wrong_nested_brackets(self):
|
||||
self.assertEqual(check_brackets("[({]})"), False)
|
||||
|
||||
def test_math_expression(self):
|
||||
self.assertEqual(
|
||||
check_brackets("(((185 + 223.85) * 15) - 543)/2"), True)
|
||||
|
||||
def test_complex_latex_expression(self):
|
||||
self.assertEqual(
|
||||
check_brackets(
|
||||
("\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{"
|
||||
"x} &... x^2 \\end{array}\\right)")), True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -6,9 +6,9 @@ that the sum of the coins' value would equal the correct amount of change.
|
||||
## For example
|
||||
|
||||
- An input of 15 with [1, 5, 10, 25, 100] should return one nickel (5)
|
||||
and one dime (10) or [0, 1, 1, 0, 0]
|
||||
and one dime (10) or [5, 10]
|
||||
- An input of 40 with [1, 5, 10, 25, 100] should return one nickel (5)
|
||||
and one dime (10) and one quarter (25) or [0, 1, 1, 1, 0]
|
||||
and one dime (10) and one quarter (25) or [5, 10, 25]
|
||||
|
||||
## Edge cases
|
||||
|
||||
@@ -24,25 +24,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test change_test.py`
|
||||
- Python 3.4+: `pytest change_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest change_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/change` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Software Craftsmanship - Coin Change Kata [https://web.archive.org/web/20130115115225/http://craftsmanship.sv.cmu.edu:80/exercises/coin-change-kata](https://web.archive.org/web/20130115115225/http://craftsmanship.sv.cmu.edu:80/exercises/coin-change-kata)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -2,8 +2,8 @@ import unittest
|
||||
|
||||
from change import find_minimum_coins
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.3.0
|
||||
|
||||
class ChangeTest(unittest.TestCase):
|
||||
def test_single_coin_change(self):
|
||||
@@ -37,13 +37,26 @@ class ChangeTest(unittest.TestCase):
|
||||
self.assertEqual(find_minimum_coins(0, [1, 5, 10, 21, 25]), [])
|
||||
|
||||
def test_error_testing_for_change_smaller_than_smallest_coin(self):
|
||||
self.assertEqual(find_minimum_coins(3, [5, 10]), -1)
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
find_minimum_coins(3, [5, 10])
|
||||
|
||||
def test_error_if_no_combination_can_add_up_to_target(self):
|
||||
self.assertEqual(find_minimum_coins(94, [5, 10]), -1)
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
find_minimum_coins(94, [5, 10])
|
||||
|
||||
def test_cannot_find_negative_change_values(self):
|
||||
self.assertEqual(find_minimum_coins(-5, [1, 2, 5]), -1)
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
find_minimum_coins(-5, [1, 2, 5])
|
||||
|
||||
# Utility functions
|
||||
def setUp(self):
|
||||
try:
|
||||
self.assertRaisesRegex
|
||||
except AttributeError:
|
||||
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||
|
||||
def assertRaisesWithMessage(self, exception):
|
||||
return self.assertRaisesRegex(exception, r".+")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
def find_minimum_coins(total_change, coins):
|
||||
if total_change < 0:
|
||||
return -1
|
||||
raise ValueError("cannot find negative change values")
|
||||
min_coins_required = [1e9] * (total_change + 1)
|
||||
last_coin = [0]*(total_change + 1)
|
||||
min_coins_required[0] = 0
|
||||
@@ -15,7 +15,7 @@ def find_minimum_coins(total_change, coins):
|
||||
last_coin[change] = change - coin
|
||||
min_coins_required[change] = final_result
|
||||
if min_coins_required[total_change] == 1e9:
|
||||
return -1
|
||||
raise ValueError("no combination can add up to target")
|
||||
else:
|
||||
last_coin_value = total_change
|
||||
array = []
|
||||
|
||||
@@ -58,25 +58,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test circular_buffer_test.py`
|
||||
- Python 3.4+: `pytest circular_buffer_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest circular_buffer_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/circular-buffer` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Wikipedia [http://en.wikipedia.org/wiki/Circular_buffer](http://en.wikipedia.org/wiki/Circular_buffer)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -9,3 +9,15 @@ class BufferEmptyException(Exception):
|
||||
class CircularBuffer(object):
|
||||
def __init__(self, capacity):
|
||||
pass
|
||||
|
||||
def read(self):
|
||||
pass
|
||||
|
||||
def write(self, data):
|
||||
pass
|
||||
|
||||
def overwrite(self, data):
|
||||
pass
|
||||
|
||||
def clear(self):
|
||||
pass
|
||||
|
||||
@@ -7,7 +7,7 @@ from circular_buffer import (
|
||||
)
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.1
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0
|
||||
|
||||
class CircularBufferTest(unittest.TestCase):
|
||||
def test_read_empty_buffer(self):
|
||||
@@ -34,7 +34,7 @@ class CircularBufferTest(unittest.TestCase):
|
||||
self.assertEqual(buf.read(), '1')
|
||||
self.assertEqual(buf.read(), '2')
|
||||
|
||||
def test_full_buffer_cant_written(self):
|
||||
def test_cant_write_to_full_buffer(self):
|
||||
buf = CircularBuffer(1)
|
||||
buf.write('1')
|
||||
with self.assertRaisesWithMessage(BufferFullException):
|
||||
@@ -70,7 +70,7 @@ class CircularBufferTest(unittest.TestCase):
|
||||
buf.write('2')
|
||||
self.assertEqual(buf.read(), '2')
|
||||
|
||||
def test_clear_does_nothin_empty_buffer(self):
|
||||
def test_clear_does_nothing_on_empty_buffer(self):
|
||||
buf = CircularBuffer(1)
|
||||
buf.clear()
|
||||
buf.write('1')
|
||||
@@ -91,14 +91,7 @@ class CircularBufferTest(unittest.TestCase):
|
||||
self.assertEqual(buf.read(), '2')
|
||||
self.assertEqual(buf.read(), '3')
|
||||
|
||||
def test_write_full_buffer(self):
|
||||
buf = CircularBuffer(2)
|
||||
buf.write('1')
|
||||
buf.write('2')
|
||||
with self.assertRaisesWithMessage(BufferFullException):
|
||||
buf.write('A')
|
||||
|
||||
def test_over_write_replaces_oldest_remaning_item(self):
|
||||
def test_overwrite_replaces_oldest_remaining_item(self):
|
||||
buf = CircularBuffer(3)
|
||||
buf.write('1')
|
||||
buf.write('2')
|
||||
|
||||
@@ -14,25 +14,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test clock_test.py`
|
||||
- Python 3.4+: `pytest clock_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest clock_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/clock` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Pairing session with Erin Drummond [https://twitter.com/ebdrummond](https://twitter.com/ebdrummond)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -2,5 +2,14 @@ class Clock(object):
|
||||
def __init__(self, hour, minute):
|
||||
pass
|
||||
|
||||
def __add__(self, other):
|
||||
def __repr__(self):
|
||||
pass
|
||||
|
||||
def __eq__(self, other):
|
||||
pass
|
||||
|
||||
def __add__(self, minutes):
|
||||
pass
|
||||
|
||||
def __sub__(self, minutes):
|
||||
pass
|
||||
|
||||
@@ -2,8 +2,8 @@ import unittest
|
||||
|
||||
from clock import Clock
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.1
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v2.4.0
|
||||
|
||||
class ClockTest(unittest.TestCase):
|
||||
# Test creating a new clock with an initial time.
|
||||
@@ -58,6 +58,9 @@ class ClockTest(unittest.TestCase):
|
||||
def test_negative_minutes_roll_over_continuously(self):
|
||||
self.assertEqual(str(Clock(1, -4820)), '16:40')
|
||||
|
||||
def test_negative_sixty_minutes_is_previous_hour(self):
|
||||
self.assertEqual(str(Clock(2, -60)), '01:00')
|
||||
|
||||
def test_negative_hour_and_minutes_both_roll_over(self):
|
||||
self.assertEqual(str(Clock(-25, -160)), '20:20')
|
||||
|
||||
@@ -90,28 +93,28 @@ class ClockTest(unittest.TestCase):
|
||||
self.assertEqual(str(Clock(1, 1) + 3500), '11:21')
|
||||
|
||||
def test_subtract_minutes(self):
|
||||
self.assertEqual(str(Clock(10, 3) + -3), '10:00')
|
||||
self.assertEqual(str(Clock(10, 3) - 3), '10:00')
|
||||
|
||||
def test_subtract_to_previous_hour(self):
|
||||
self.assertEqual(str(Clock(10, 3) + -3), '10:00')
|
||||
self.assertEqual(str(Clock(10, 3) - 30), '09:33')
|
||||
|
||||
def test_subtract_more_than_an_hour(self):
|
||||
self.assertEqual(str(Clock(10, 3) + -30), '09:33')
|
||||
self.assertEqual(str(Clock(10, 3) - 70), '08:53')
|
||||
|
||||
def test_subtract_across_midnight(self):
|
||||
self.assertEqual(str(Clock(10, 3) + -70), '08:53')
|
||||
self.assertEqual(str(Clock(0, 3) - 4), '23:59')
|
||||
|
||||
def test_subtract_more_than_two_hours(self):
|
||||
self.assertEqual(str(Clock(0, 0) + -160), '21:20')
|
||||
self.assertEqual(str(Clock(0, 0) - 160), '21:20')
|
||||
|
||||
def test_subtract_more_than_two_hours_with_borrow(self):
|
||||
self.assertEqual(str(Clock(6, 15) + -160), '03:35')
|
||||
self.assertEqual(str(Clock(6, 15) - 160), '03:35')
|
||||
|
||||
def test_subtract_more_than_one_day(self):
|
||||
self.assertEqual(str(Clock(5, 32) + -1500), '04:32')
|
||||
self.assertEqual(str(Clock(5, 32) - 1500), '04:32')
|
||||
|
||||
def test_subtract_more_than_two_days(self):
|
||||
self.assertEqual(str(Clock(2, 20) + -3000), '00:20')
|
||||
self.assertEqual(str(Clock(2, 20) - 3000), '00:20')
|
||||
|
||||
# Construct two separate clocks, set times, test if they are equal.
|
||||
def test_clocks_with_same_time(self):
|
||||
@@ -159,6 +162,9 @@ class ClockTest(unittest.TestCase):
|
||||
def test_clocks_with_negative_hours_and_minutes_that_wrap(self):
|
||||
self.assertEqual(Clock(18, 7), Clock(-54, -11513))
|
||||
|
||||
def test_full_clock_and_zeroed_clock(self):
|
||||
self.assertEqual(Clock(24, 0), Clock(0, 0))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
class Clock(object):
|
||||
'Clock that displays 24 hour clock that rollsover properly'
|
||||
|
||||
@@ -17,6 +16,10 @@ class Clock(object):
|
||||
self.minute += minutes
|
||||
return self.cleanup()
|
||||
|
||||
def __sub__(self, minutes):
|
||||
self.minute -= minutes
|
||||
return self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
self.hour += self.minute // 60
|
||||
self.hour %= 24
|
||||
|
||||
@@ -39,25 +39,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test collatz_conjecture_test.py`
|
||||
- Python 3.4+: `pytest collatz_conjecture_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest collatz_conjecture_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/collatz-conjecture` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
An unsolved problem in mathematics named after mathematician Lothar Collatz [https://en.wikipedia.org/wiki/3x_%2B_1_problem](https://en.wikipedia.org/wiki/3x_%2B_1_problem)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -2,10 +2,10 @@ import unittest
|
||||
|
||||
from collatz_conjecture import collatz_steps
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.1
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.2.1
|
||||
|
||||
class CollatzConjectureTests(unittest.TestCase):
|
||||
class CollatzConjectureTest(unittest.TestCase):
|
||||
|
||||
def test_zero_steps_for_one(self):
|
||||
self.assertEqual(collatz_steps(1), 0)
|
||||
@@ -20,12 +20,22 @@ class CollatzConjectureTests(unittest.TestCase):
|
||||
self.assertEqual(collatz_steps(1000000), 152)
|
||||
|
||||
def test_zero_is_invalid_input(self):
|
||||
self.assertEqual(collatz_steps(0), None)
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
collatz_steps(0)
|
||||
|
||||
def test_negative_number_is_invalid_input(self):
|
||||
self.assertEqual(collatz_steps(-1), None)
|
||||
with self.assertRaisesWithMessage(ValueError):
|
||||
collatz_steps(-15)
|
||||
|
||||
self.assertEqual(collatz_steps(-15), None)
|
||||
# Utility functions
|
||||
def setUp(self):
|
||||
try:
|
||||
self.assertRaisesRegex
|
||||
except AttributeError:
|
||||
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||
|
||||
def assertRaisesWithMessage(self, exception):
|
||||
return self.assertRaisesRegex(exception, r".+")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
def collatz_steps(n):
|
||||
if n <= 0:
|
||||
return
|
||||
raise ValueError("input should be positive")
|
||||
|
||||
step_count = 0
|
||||
while n > 1:
|
||||
|
||||
@@ -35,6 +35,7 @@ Assume the programming language you are using does not have an implementation of
|
||||
|
||||
See [Emulating numeric types](https://docs.python.org/2/reference/datamodel.html#emulating-numeric-types) for help on operator overloading.
|
||||
|
||||
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
@@ -43,25 +44,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test complex_numbers_test.py`
|
||||
- Python 3.4+: `pytest complex_numbers_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest complex_numbers_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/complex-numbers` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Wikipedia [https://en.wikipedia.org/wiki/Complex_number](https://en.wikipedia.org/wiki/Complex_number)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -7,11 +7,40 @@ import math
|
||||
from complex_numbers import ComplexNumber
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.3.0
|
||||
|
||||
class ComplexNumbersTest(unittest.TestCase):
|
||||
|
||||
def test_real_part_of_a_purely_real_number(self):
|
||||
input_number = ComplexNumber(1, 0)
|
||||
self.assertEqual(input_number.real, 1)
|
||||
|
||||
def test_real_part_of_a_purely_imaginary_number(self):
|
||||
input_number = ComplexNumber(0, 1)
|
||||
self.assertEqual(input_number.real, 0)
|
||||
|
||||
def test_real_part_of_a_number_with_real_and_imaginary_part(self):
|
||||
input_number = ComplexNumber(1, 2)
|
||||
self.assertEqual(input_number.real, 1)
|
||||
|
||||
def test_imaginary_part_of_a_purely_real_number(self):
|
||||
input_number = ComplexNumber(1, 0)
|
||||
self.assertEqual(input_number.imaginary, 0)
|
||||
|
||||
def test_imaginary_part_of_a_purely_imaginary_number(self):
|
||||
input_number = ComplexNumber(0, 1)
|
||||
self.assertEqual(input_number.imaginary, 1)
|
||||
|
||||
def test_imaginary_part_of_a_number_with_real_and_imaginary_part(self):
|
||||
input_number = ComplexNumber(1, 2)
|
||||
self.assertEqual(input_number.imaginary, 2)
|
||||
|
||||
def test_imaginary_unit(self):
|
||||
first_input = ComplexNumber(0, 1)
|
||||
second_input = ComplexNumber(0, 1)
|
||||
expected = ComplexNumber(-1, 0)
|
||||
self.assertEqual(first_input * second_input, expected)
|
||||
|
||||
def test_add_purely_real_numbers(self):
|
||||
first_input = ComplexNumber(1, 0)
|
||||
second_input = ComplexNumber(2, 0)
|
||||
@@ -120,47 +149,33 @@ class ComplexNumbersTest(unittest.TestCase):
|
||||
self.assertEqual(input_number.conjugate().imaginary,
|
||||
expected.imaginary)
|
||||
|
||||
def test_real_part_of_a_purely_real_number(self):
|
||||
input_number = ComplexNumber(1, 0)
|
||||
self.assertEqual(input_number.real, 1)
|
||||
|
||||
def test_real_part_of_a_purely_imaginary_number(self):
|
||||
input_number = ComplexNumber(0, 1)
|
||||
self.assertEqual(input_number.real, 0)
|
||||
|
||||
def test_real_part_of_a_number_with_real_and_imaginary_part(self):
|
||||
input_number = ComplexNumber(1, 2)
|
||||
self.assertEqual(input_number.real, 1)
|
||||
|
||||
def test_imaginary_part_of_a_purely_real_number(self):
|
||||
input_number = ComplexNumber(1, 0)
|
||||
self.assertEqual(input_number.imaginary, 0)
|
||||
|
||||
def test_imaginary_part_of_a_purely_imaginary_number(self):
|
||||
input_number = ComplexNumber(0, 1)
|
||||
self.assertEqual(input_number.imaginary, 1)
|
||||
|
||||
def test_imaginary_part_of_a_number_with_real_and_imaginary_part(self):
|
||||
input_number = ComplexNumber(1, 2)
|
||||
self.assertEqual(input_number.imaginary, 2)
|
||||
|
||||
def test_eulers_identity_formula(self):
|
||||
input_number = ComplexNumber(0, math.pi)
|
||||
expected = ComplexNumber(-1, 0)
|
||||
self.assertEqual(input_number.exp().real, expected.real)
|
||||
self.assertEqual(input_number.exp().imaginary, expected.imaginary)
|
||||
actual = input_number.exp()
|
||||
self.assertAlmostEqual(actual.real, expected.real)
|
||||
self.assertAlmostEqual(actual.imaginary, expected.imaginary)
|
||||
|
||||
def test_exponential_of_0(self):
|
||||
input_number = ComplexNumber(0, 0)
|
||||
expected = ComplexNumber(1, 0)
|
||||
self.assertEqual(input_number.exp().real, expected.real)
|
||||
self.assertEqual(input_number.exp().imaginary, expected.imaginary)
|
||||
actual = input_number.exp()
|
||||
self.assertAlmostEqual(actual.real, expected.real)
|
||||
self.assertAlmostEqual(actual.imaginary, expected.imaginary)
|
||||
|
||||
def test_exponential_of_a_purely_real_number(self):
|
||||
input_number = ComplexNumber(1, 0)
|
||||
expected = ComplexNumber(math.e, 0)
|
||||
self.assertEqual(input_number.exp().real, expected.real)
|
||||
self.assertEqual(input_number.exp().imaginary, expected.imaginary)
|
||||
actual = input_number.exp()
|
||||
self.assertAlmostEqual(actual.real, expected.real)
|
||||
self.assertAlmostEqual(actual.imaginary, expected.imaginary)
|
||||
|
||||
def test_exponential_of_a_number_with_real_and_imaginary_part(self):
|
||||
input_number = ComplexNumber(math.log(2), math.pi)
|
||||
expected = ComplexNumber(-2, 0)
|
||||
actual = input_number.exp()
|
||||
self.assertAlmostEqual(actual.real, expected.real)
|
||||
self.assertAlmostEqual(actual.imaginary, expected.imaginary)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -40,6 +40,6 @@ class ComplexNumber(object):
|
||||
return ComplexNumber(self.real, -1 * self.imaginary)
|
||||
|
||||
def exp(self):
|
||||
r = round(math.cos(self.imaginary), 8) * math.exp(self.real)
|
||||
i = round(math.sin(self.imaginary), 8) * math.exp(self.real)
|
||||
r = math.cos(self.imaginary) * math.exp(self.real)
|
||||
i = math.sin(self.imaginary) * math.exp(self.real)
|
||||
return ComplexNumber(r, i)
|
||||
|
||||
@@ -38,22 +38,39 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test connect_test.py`
|
||||
- Python 3.4+: `pytest connect_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest connect_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/connect` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -12,11 +12,15 @@ regarded as forming a rectangle when printed with intervening newlines.
|
||||
|
||||
For example, the sentence
|
||||
|
||||
> If man was meant to stay on the ground, god would have given us roots.
|
||||
```text
|
||||
"If man was meant to stay on the ground, god would have given us roots."
|
||||
```
|
||||
|
||||
is normalized to:
|
||||
|
||||
> ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots
|
||||
```text
|
||||
"ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots"
|
||||
```
|
||||
|
||||
The plaintext should be organized in to a rectangle. The size of the
|
||||
rectangle (`r x c`) should be decided by the length of the message,
|
||||
@@ -27,13 +31,13 @@ Our normalized text is 54 characters long, dictating a rectangle with
|
||||
`c = 8` and `r = 7`:
|
||||
|
||||
```text
|
||||
ifmanwas
|
||||
meanttos
|
||||
tayonthe
|
||||
groundgo
|
||||
dwouldha
|
||||
vegivenu
|
||||
sroots
|
||||
"ifmanwas"
|
||||
"meanttos"
|
||||
"tayonthe"
|
||||
"groundgo"
|
||||
"dwouldha"
|
||||
"vegivenu"
|
||||
"sroots "
|
||||
```
|
||||
|
||||
The coded message is obtained by reading down the columns going left to
|
||||
@@ -42,31 +46,30 @@ right.
|
||||
The message above is coded as:
|
||||
|
||||
```text
|
||||
imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau
|
||||
"imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau"
|
||||
```
|
||||
|
||||
Output the encoded text in chunks. Phrases that fill perfect rectangles
|
||||
`(r X c)` should be output `c` chunks of `r` length, separated by spaces.
|
||||
Phrases that do not fill perfect rectangles will have `n` empty spaces.
|
||||
Those spaces should be distributed evenly, added to the end of the last
|
||||
`n` chunks.
|
||||
Output the encoded text in chunks that fill perfect rectangles `(r X c)`,
|
||||
with `c` chunks of `r` length, separated by spaces. For phrases that are
|
||||
`n` characters short of the perfect rectangle, pad each of the last `n`
|
||||
chunks with a single trailing space.
|
||||
|
||||
```text
|
||||
imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau
|
||||
"imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau "
|
||||
```
|
||||
|
||||
Notice that were we to stack these, we could visually decode the
|
||||
cyphertext back in to the original message:
|
||||
|
||||
```text
|
||||
imtgdvs
|
||||
fearwer
|
||||
mayoogo
|
||||
anouuio
|
||||
ntnnlvt
|
||||
wttddes
|
||||
aohghn
|
||||
sseoau
|
||||
"imtgdvs"
|
||||
"fearwer"
|
||||
"mayoogo"
|
||||
"anouuio"
|
||||
"ntnnlvt"
|
||||
"wttddes"
|
||||
"aohghn "
|
||||
"sseoau "
|
||||
```
|
||||
|
||||
## Exception messages
|
||||
@@ -77,25 +80,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test crypto_square_test.py`
|
||||
- Python 3.4+: `pytest crypto_square_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest crypto_square_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/crypto-square` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
J Dalbey's Programming Practice problems [http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html](http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -3,7 +3,7 @@ import unittest
|
||||
from crypto_square import encode
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v3.1.0
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v3.2.0
|
||||
|
||||
class CryptoSquareTest(unittest.TestCase):
|
||||
def test_empty_string(self):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Custom-Set
|
||||
# Custom Set
|
||||
|
||||
Create a custom set type.
|
||||
|
||||
@@ -7,14 +7,47 @@ type, like a set. In this exercise you will define your own set. How it
|
||||
works internally doesn't matter, as long as it behaves like a set of
|
||||
unique elements.
|
||||
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test custom_set_test.py`
|
||||
- Python 3.4+: `pytest custom_set_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest custom_set_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/custom-set` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -23,8 +23,8 @@ class CustomSet(object):
|
||||
def intersection(self, other):
|
||||
pass
|
||||
|
||||
def difference(self, other):
|
||||
def __sub__(self, other):
|
||||
pass
|
||||
|
||||
def union(self, other):
|
||||
def __add__(self, other):
|
||||
pass
|
||||
|
||||
@@ -3,7 +3,7 @@ import unittest
|
||||
from custom_set import CustomSet
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.3.0
|
||||
|
||||
class CustomSetTest(unittest.TestCase):
|
||||
def test_sets_with_no_elements_are_empty(self):
|
||||
|
||||
65
exercises/darts/README.md
Normal file
65
exercises/darts/README.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Darts
|
||||
|
||||
Write a function that returns the earned points in a single toss of a Darts game.
|
||||
|
||||
[Darts](https://en.wikipedia.org/wiki/Darts) is a game where players
|
||||
throw darts to a [target](https://en.wikipedia.org/wiki/Darts#/media/File:Darts_in_a_dartboard.jpg).
|
||||
|
||||
In our particular instance of the game, the target rewards with 4 different amounts of points, depending on where the dart lands:
|
||||
|
||||
* If the dart lands outside the target, player earns no points (0 points).
|
||||
* If the dart lands in the outer circle of the target, player earns 1 point.
|
||||
* If the dart lands in the middle circle of the target, player earns 5 points.
|
||||
* If the dart lands in the inner circle of the target, player earns 10 points.
|
||||
|
||||
The outer circle has a radius of 10 units (This is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1. Of course, they are all centered to the same point (That is, the circles are [concentric](http://mathworld.wolfram.com/ConcentricCircles.html)) defined by the coordinates (0, 0).
|
||||
|
||||
Write a function that given a point in the target (defined by its `real` cartesian coordinates `x` and `y`), returns the correct amount earned by a dart landing in that point.
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test darts_test.py`
|
||||
- Python 3.4+: `pytest darts_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest darts_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/darts` directory.
|
||||
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Inspired by an excersie created by a professor Della Paolera in Argentina
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
2
exercises/darts/darts.py
Normal file
2
exercises/darts/darts.py
Normal file
@@ -0,0 +1,2 @@
|
||||
def score(x, y):
|
||||
pass
|
||||
31
exercises/darts/darts_test.py
Normal file
31
exercises/darts/darts_test.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import unittest
|
||||
from darts import score
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0
|
||||
|
||||
class darts_test(unittest.TestCase):
|
||||
def test_dart_lands_outside_target(self):
|
||||
self.assertEqual(score(-9, 9), 0)
|
||||
|
||||
def test_dart_lands_just_in_border_of_target(self):
|
||||
self.assertEqual(score(0, 10), 1)
|
||||
|
||||
def test_dart_lands_outer_circle(self):
|
||||
self.assertEqual(score(4, 4), 1)
|
||||
|
||||
def test_dart_lands_border_between_outside_middle(self):
|
||||
self.assertEqual(score(5, 0), 5)
|
||||
|
||||
def test_dart_lands_in_middle_circle(self):
|
||||
self.assertEqual(score(0.8, -0.8), 5)
|
||||
|
||||
def test_dart_lands_border_betweeen_middle_inner(self):
|
||||
self.assertEqual(score(0, -1), 10)
|
||||
|
||||
def test_dart_lands_inner_circle(self):
|
||||
self.assertEqual(score(-0.1, -0.1), 10)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
14
exercises/darts/example.py
Normal file
14
exercises/darts/example.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from math import sqrt
|
||||
|
||||
|
||||
def score(x, y):
|
||||
dart_location = sqrt(x * x + y * y)
|
||||
|
||||
if dart_location <= 1.0:
|
||||
return 10
|
||||
elif dart_location <= 5.0:
|
||||
return 5
|
||||
elif dart_location <= 10.0:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
@@ -60,25 +60,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test diamond_test.py`
|
||||
- Python 3.4+: `pytest diamond_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest diamond_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/diamond` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Seb Rose [http://claysnow.co.uk/recycling-tests-in-tdd/](http://claysnow.co.uk/recycling-tests-in-tdd/)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -3,9 +3,9 @@ import unittest
|
||||
from diamond import make_diamond
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0
|
||||
|
||||
class DiamondTests(unittest.TestCase):
|
||||
class DiamondTest(unittest.TestCase):
|
||||
def test_degenerate_case_with_a_single_row(self):
|
||||
self.assertMultiLineEqual(make_diamond('A'), 'A\n')
|
||||
|
||||
|
||||
@@ -20,25 +20,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test difference_of_squares_test.py`
|
||||
- Python 3.4+: `pytest difference_of_squares_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest difference_of_squares_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/difference-of-squares` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Problem 6 at Project Euler [http://projecteuler.net/problem=6](http://projecteuler.net/problem=6)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -3,7 +3,7 @@ import unittest
|
||||
from difference_of_squares import difference, square_of_sum, sum_of_squares
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.2.0
|
||||
|
||||
class DifferenceOfSquaresTest(unittest.TestCase):
|
||||
def test_square_of_sum_1(self):
|
||||
|
||||
@@ -62,25 +62,43 @@ every exercise will require you to raise an exception, but for those that do, th
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you shold write:
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test diffie_hellman_test.py`
|
||||
- Python 3.4+: `pytest diffie_hellman_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest diffie_hellman_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/diffie-hellman` directory.
|
||||
|
||||
For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see the [help page](http://exercism.io/languages/python).
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Wikipedia, 1024 bit key from www.cryptopp.com/wiki. [http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange](http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
||||
@@ -3,23 +3,25 @@ import unittest
|
||||
import diffie_hellman
|
||||
|
||||
|
||||
# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0
|
||||
|
||||
class DiffieHellmanTest(unittest.TestCase):
|
||||
|
||||
def test_private_in_range(self):
|
||||
def test_private_key_is_in_range(self):
|
||||
primes = [5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
|
||||
for i in primes:
|
||||
self.assertTrue(1 < diffie_hellman.private_key(i) < i)
|
||||
|
||||
# Can fail due to randomness, but most likely will not,
|
||||
# due to pseudo-randomness and the large number chosen
|
||||
def test_private_key_randomness(self):
|
||||
def test_private_key_is_random(self):
|
||||
p = 2147483647
|
||||
private_keys = []
|
||||
for i in range(5):
|
||||
private_keys.append(diffie_hellman.private_key(p))
|
||||
self.assertEqual(len(list(set(private_keys))), len(private_keys))
|
||||
self.assertEqual(len(set(private_keys)), len(private_keys))
|
||||
|
||||
def test_public_key_correct(self):
|
||||
def test_can_calculate_public_key_using_private_key(self):
|
||||
p = 23
|
||||
g = 5
|
||||
private = 6
|
||||
@@ -28,7 +30,7 @@ class DiffieHellmanTest(unittest.TestCase):
|
||||
actual = diffie_hellman.public_key(p, g, private)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_secret_key_correct(self):
|
||||
def test_can_calculate_secret_using_other_party_s_public_key(self):
|
||||
p = 23
|
||||
public = 19
|
||||
private = 6
|
||||
@@ -37,50 +39,17 @@ class DiffieHellmanTest(unittest.TestCase):
|
||||
actual = diffie_hellman.secret(p, public, private)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_secret_key_correct_large_nums(self):
|
||||
p = int("""120227323036150778550155526710966921740030662\
|
||||
69457894729842354923526575959371158734103742634711454153\
|
||||
30066288563005527069961435922404533456428692335628867529\
|
||||
30249953227657883929905072620233073626594386072962776144\
|
||||
69143365881426187411323246174903542571280506720291038940\
|
||||
7991986070558964461330091797026762932543""".replace(
|
||||
"\n", "").replace(" ", ""))
|
||||
public = int("""7520544115435791944292554616920871123548\
|
||||
58559049691782063133092992058683123990461493675163366079\
|
||||
66149689640419216591714331722664409474612463910928128055\
|
||||
99415792293044373353565984826436410603792531597409532111\
|
||||
27577117569121441377056137760635413505489115127155125391\
|
||||
86192176020596861210448363099541947258202188""".replace(
|
||||
"\n", "").replace(" ", ""))
|
||||
private = int("""248347939362593293991108130435688850515\
|
||||
37971354473275017926961991904690152151776307586179022004\
|
||||
17377685436170904594686456961202706692908603181062371925\
|
||||
882""".replace("\n", "").replace(" ", ""))
|
||||
expected = int("""70900735223964890815905879227737819348\
|
||||
80851869892044649134650898046120174656773533145582564442\
|
||||
98779465564310958207858354973848497783442169812282262526\
|
||||
39932672153547963980483673419756271345828771971984887453\
|
||||
01448857224581986445413661898091472983952358126388674082\
|
||||
1363010486083940557620831348661126601106717071""".replace(
|
||||
"\n", "").replace(" ", ""))
|
||||
|
||||
actual = diffie_hellman.secret(p, public, private)
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_exchange(self):
|
||||
def test_key_exchange(self):
|
||||
p = 23
|
||||
g = 5
|
||||
alice_private_key = diffie_hellman.private_key(p)
|
||||
bob_private_key = diffie_hellman.private_key(p)
|
||||
alice_public_key = diffie_hellman.public_key(p, g, alice_private_key)
|
||||
bob_public_key = diffie_hellman.public_key(p, g, bob_private_key)
|
||||
secret_a = diffie_hellman.secret(p, bob_public_key, alice_private_key)
|
||||
secret_b = diffie_hellman.secret(p, alice_public_key, bob_private_key)
|
||||
|
||||
privateA = diffie_hellman.private_key(p)
|
||||
privateB = diffie_hellman.private_key(p)
|
||||
|
||||
publicA = diffie_hellman.public_key(p, g, privateA)
|
||||
publicB = diffie_hellman.public_key(p, g, privateB)
|
||||
|
||||
secretA = diffie_hellman.secret(p, publicB, privateA)
|
||||
secretB = diffie_hellman.secret(p, publicA, privateB)
|
||||
|
||||
self.assertEqual(secretA, secretB)
|
||||
self.assertEqual(secret_a, secret_b)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
82
exercises/dnd-character/README.md
Normal file
82
exercises/dnd-character/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Dnd Character
|
||||
|
||||
For a game of [Dungeons & Dragons][DND], each player starts by generating a
|
||||
character they can play with. This character has, among other things, six
|
||||
abilities; strength, dexterity, constitution, intelligence, wisdom and
|
||||
charisma. These six abilities have scores that are determined randomly. You
|
||||
do this by rolling four 6-sided dice and record the sum of the largest three
|
||||
dice. You do this six times, once for each ability.
|
||||
|
||||
Your character's initial hitpoints are 10 + your character's constitution
|
||||
modifier. You find your character's constitution modifier by subtracting 10
|
||||
from your character's constitution, divide by 2 and round down.
|
||||
|
||||
Write a random character generator that follows the rules above.
|
||||
|
||||
For example, the six throws of four dice may look like:
|
||||
|
||||
* 5, 3, 1, 6: You discard the 1 and sum 5 + 3 + 6 = 14, which you assign to strength.
|
||||
* 3, 2, 5, 3: You discard the 2 and sum 3 + 5 + 3 = 11, which you assign to dexterity.
|
||||
* 1, 1, 1, 1: You discard the 1 and sum 1 + 1 + 1 = 3, which you assign to constitution.
|
||||
* 2, 1, 6, 6: You discard the 1 and sum 2 + 6 + 6 = 14, which you assign to intelligence.
|
||||
* 3, 5, 3, 4: You discard the 3 and sum 5 + 3 + 4 = 12, which you assign to wisdom.
|
||||
* 6, 6, 6, 6: You discard the 6 and sum 6 + 6 + 6 = 18, which you assign to charisma.
|
||||
|
||||
Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6.
|
||||
|
||||
## Notes
|
||||
|
||||
Most programming languages feature (pseudo-)random generators, but few
|
||||
programming languages are designed to roll dice. One such language is [Troll].
|
||||
|
||||
[DND]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons
|
||||
[Troll]: http://hjemmesider.diku.dk/~torbenm/Troll/
|
||||
|
||||
## Exception messages
|
||||
|
||||
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
|
||||
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
|
||||
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
|
||||
a message.
|
||||
|
||||
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
|
||||
`raise Exception`, you should write:
|
||||
|
||||
```python
|
||||
raise Exception("Meaningful message indicating the source of the error")
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)):
|
||||
|
||||
- Python 2.7: `py.test dnd_character_test.py`
|
||||
- Python 3.4+: `pytest dnd_character_test.py`
|
||||
|
||||
Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version):
|
||||
`python -m pytest dnd_character_test.py`
|
||||
|
||||
### Common `pytest` options
|
||||
|
||||
- `-v` : enable verbose output
|
||||
- `-x` : stop running tests on first failure
|
||||
- `--ff` : run failures from previous test before running other test cases
|
||||
|
||||
For other options, see `python -m pytest -h`
|
||||
|
||||
## Submitting Exercises
|
||||
|
||||
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/dnd-character` directory.
|
||||
|
||||
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
|
||||
|
||||
For more detailed information about running tests, code style and linting,
|
||||
please see [Running the Tests](http://exercism.io/tracks/python/tests).
|
||||
|
||||
## Source
|
||||
|
||||
Simon Shine, Erik Schierboom [https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945](https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945)
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
3
exercises/dnd-character/dnd_character.py
Normal file
3
exercises/dnd-character/dnd_character.py
Normal file
@@ -0,0 +1,3 @@
|
||||
class Character:
|
||||
def __init__(self):
|
||||
pass
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user