Skip to content
Merged
17 changes: 17 additions & 0 deletions exercises/practice/say/.docs/instructions.append.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Instructions append

## Exception messages

Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always 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. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.

This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` if the number input to `say()` is out of range. The tests will only pass if you both `raise` the `exception` and include a message with it.

To raise a `ValueError` with a message, write the message as an argument to the `exception` type:

```python
# if the number is negative
raise ValueError("input out of range")

# if the number is larger than 999,999,999,99
raise ValueError("input out of range")
```
10 changes: 5 additions & 5 deletions exercises/practice/say/.meta/example.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
def say(number, recursive=False):
small = dict(enumerate((
'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen',
'sixteen', 'seventeen', 'eighteen', 'nineteen')))
'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen',
'sixteen', 'seventeen', 'eighteen', 'nineteen')))

tens = {20: 'twenty', 30: 'thirty', 40: 'forty', 50: 'fifty',
60: 'sixty', 70: 'seventy', 80: 'eighty', 90: 'ninety'}

k, m, b, t = 1e3, 1e6, 1e9, 1e12

if number < 0:
raise ValueError('number is negative')
raise ValueError("input out of range")
if number >= t:
raise ValueError('number is too large: {}'.format(number))
raise ValueError("input out of range")

if number < 20:
return small[number] if not recursive else + small[number]
Expand Down
10 changes: 6 additions & 4 deletions exercises/practice/say/.meta/template.j2
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
{% for case in cases -%}
def test_{{ case["description"] | to_snake }}(self):
{%- if case is error_case %}
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
{{- test_call(case) }}
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
{%- else %}
self.assertEqual({{- test_call(case) }}, "{{ case["expected"] }}")
{%- endif %}
Expand All @@ -23,11 +25,11 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
{% for case in additional_cases -%}
def test_{{ case["description"] | to_snake }}(self):
{%- if case is error_case %}
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
{{- test_call(case) }}
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
{%- else %}
self.assertEqual({{- test_call(case) }}, "{{ case["expected"] }}")
{%- endif %}
{% endfor %}

{{ macros.footer() }}
18 changes: 8 additions & 10 deletions exercises/practice/say/say_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,19 @@ def test_a_big_number(self):
)

def test_numbers_below_zero_are_out_of_range(self):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
say(-1)

self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "input out of range")

def test_numbers_above_999_999_999_999_are_out_of_range(self):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
say(1000000000000)

self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "input out of range")

# Additional tests for this track
def test_one_hundred_seventy(self):
self.assertEqual(say(170), "one hundred seventy")

# Utility functions
def assertRaisesWithMessage(self, exception):
return self.assertRaisesRegex(exception, r".+")


if __name__ == "__main__":
unittest.main()
23 changes: 23 additions & 0 deletions exercises/practice/series/.docs/instructions.append.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Instructions append

## Exception messages

Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always 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. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.

This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` if the series and/or length parameters input into the `slice()` function are empty or out of range. The tests will only pass if you both `raise` the `exception` and include a message with it.

To raise a `ValueError` with a message, write the message as an argument to the `exception` type:

```python
# if the slice length is zero.
raise ValueError("slice length cannot be zero")

# if the slice length is negative.
raise ValueError("slice length cannot be negative")

# if the series provided is empty.
raise ValueError("series cannot be empty")

# if the slice length is longer than the series.
raise ValueError("slice length cannot be greater than series length")
```
12 changes: 9 additions & 3 deletions exercises/practice/series/.meta/example.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
def slices(series, length):
if not 1 <= length <= len(series):
raise ValueError("Invalid slice length for this series: " + str(
length))
if not series:
raise ValueError("series cannot be empty")
elif length == 0:
raise ValueError("slice length cannot be zero")
elif length < 0:
raise ValueError("slice length cannot be negative")
elif len(series) < length:
raise ValueError("slice length cannot be greater than series length")

return [series[i:i + length] for i in range(len(series) - length + 1)]
5 changes: 3 additions & 2 deletions exercises/practice/series/.meta/template.j2
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
{% set expected = case["expected"] -%}
def test_{{ case["description"] | to_snake }}(self):
{% if expected["error"] -%}
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
{{ case["property"] }}("{{ input["series"] }}", {{ input["sliceLength"] }})
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
{% else -%}
self.assertEqual({{ case["property"] }}("{{ input["series"] }}", {{ input["sliceLength"] }}), {{ case["expected"] }})
{% endif %}
{% endfor %}
{{ macros.footer() }}
26 changes: 14 additions & 12 deletions exercises/practice/series/series_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,27 @@ def test_slices_of_a_long_series(self):
)

def test_slice_length_is_too_large(self):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
slices("12345", 6)
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(
err.exception.args[0], "slice length cannot be greater than series length"
)

def test_slice_length_cannot_be_zero(self):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
slices("12345", 0)
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "slice length cannot be zero")

def test_slice_length_cannot_be_negative(self):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
slices("123", -1)
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "slice length cannot be negative")

def test_empty_series_is_invalid(self):
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
slices("", 1)

# Utility functions
def assertRaisesWithMessage(self, exception):
return self.assertRaisesRegex(exception, r".+")


if __name__ == "__main__":
unittest.main()
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "series cannot be empty")
23 changes: 23 additions & 0 deletions exercises/practice/sgf-parsing/.docs/instructions.append.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Instructions append

## Exception messages

Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always 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. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message.

This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` if the input lacks proper delimiters, is not in uppercase, does not form a tree with nodes, or does not form a tree at all. The tests will only pass if you both `raise` the `exception` and include a message with it.

To raise a `ValueError` with a message, write the message as an argument to the `exception` type:

```python
# if the tree properties as given do not have proper delimiters.
raise ValueError("properties without delimiter")

# if the tree properties as given are not all in uppercase.
raise ValueError("property must be in uppercase")

# if the input does not form a tree, or is empty.
raise ValueError("tree missing")

# if the input is a tree without any nodes.
raise ValueError("tree with no nodes")
```
128 changes: 65 additions & 63 deletions exercises/practice/sgf-parsing/.meta/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,98 +6,100 @@ def __init__(self, properties=None, children=None):
def __eq__(self, other):
if not isinstance(other, SgfTree):
return False
for k, v in self.properties.items():
if k not in other.properties:

for key, value in self.properties.items():
if key not in other.properties:
return False
if other.properties[k] != v:

if other.properties[key] != value:
return False
for k in other.properties.keys():
if k not in self.properties:

for key in other.properties.keys():
if key not in self.properties:
return False

if len(self.children) != len(other.children):
return False
for a, b in zip(self.children, other.children):
if not (a == b):

for child, other_child in zip(self.children, other.children):
if not (child == other_child):
return False
return True

def __repr__(self):
"""Ironically, encoding to SGF is much easier"""
"""Ironically, encoding to SGF is much easier."""
rep = '(;'
for k, vs in self.properties.items():
rep += k
for v in vs:
rep += '[{}]'.format(v)
for key, property_value in self.properties.items():
rep += key
for value in property_value:
rep += '[{}]'.format(value)
if self.children:
if len(self.children) > 1:
rep += '('
for c in self.children:
rep += repr(c)[1:-1]
for child in self.children:
rep += repr(child)[1:-1]
if len(self.children) > 1:
rep += ')'
return rep + ')'


def is_upper(s):
a, z = map(ord, 'AZ')
return all(
a <= o and o <= z
for o in map(ord, s)
)


def parse(input_string):
root = None
current = None
stack = list(input_string)

def assert_that(condition):
if not condition:
raise ValueError(
'invalid format at {}:{}: {}'.format(
repr(input_string),
len(input_string) - len(stack),
repr(''.join(stack))
)
)
assert_that(stack)
if input_string == "()":
raise ValueError("tree with no nodes")

if not stack:
raise ValueError("tree missing")

def pop():
if stack[0] == '\\':
stack.pop(0)
ch = stack.pop(0)
return ' ' if ch in ['\t'] else ch
characters = stack.pop(0)
return ' ' if characters in ['\t'] else characters

def peek():
return stack[0]

def pop_until(ch):
def pop_until(delimiter):
try:
v = ''
while peek() != ch:
v += pop()
return v
value = ''
while stack[0] != delimiter:
value += pop()
return value
except IndexError:
raise ValueError('Unable to find {}'.format(ch))
raise ValueError("properties without delimiter")

while stack:
assert_that(pop() == '(' and peek() == ';')
while pop() == ';':
properties = {}
while is_upper(peek()):
key = pop_until('[')
assert_that(is_upper(key))
values = []
while peek() == '[':
pop()
values.append(pop_until(']'))
pop()
properties[key] = values
if root is None:
current = root = SgfTree(properties)
else:
current = SgfTree(properties)
root.children.append(current)
while peek() == '(':
child_input = pop() + pop_until(')') + pop()
current.children.append(parse(child_input))
if pop() == '(' and stack[0] == ';':
while pop() == ';':
properties = {}
while stack[0].isupper():
key = pop_until('[')
if not key.isupper():
raise ValueError("property must be in uppercase")
values = []
while stack[0] == '[':
pop()
values.append(pop_until(']'))
pop()
properties[key] = values

if stack[0].isalpha():
if not stack[0].isupper():
raise ValueError("property must be in uppercase")

if root is None:
current = root = SgfTree(properties)

else:
current = SgfTree(properties)
root.children.append(current)

while stack[0] == '(':
child_input = pop() + pop_until(')') + pop()
current.children.append(parse(child_input))

elif root is None and current is None:
raise ValueError("tree missing")

return root
6 changes: 3 additions & 3 deletions exercises/practice/sgf-parsing/.meta/template.j2
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
def test_{{ case["description"] | to_snake }}(self):
input_string = '{{ case["input"]["encoded"] | escape_invalid_escapes }}'
{% if case["expected"]["error"] -%}
with self.assertRaisesWithMessage(ValueError):
with self.assertRaises(ValueError) as err:
{{ case["property"] | to_snake }}(input_string)
self.assertEqual(type(err.exception), ValueError)
self.assertEqual(err.exception.args[0], "{{ case["expected"]["error"] }}")
{% else -%}
expected = {{ sgftree(case["expected"]) }}
self.assertEqual({{ case["property"] | to_snake }}(input_string), expected)
Expand Down Expand Up @@ -43,5 +45,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
{% for case in cases %}
{{ test_case(case) }}
{%- endfor -%}

{{ macros.footer() }}
Loading