diff --git a/concepts/lists/about.md b/concepts/lists/about.md
index 014a6d5672..851c10e138 100644
--- a/concepts/lists/about.md
+++ b/concepts/lists/about.md
@@ -18,7 +18,7 @@ Accessing elements, checking for membership via `in`, or appending items to the
For a similar data structure that supports memory efficient `appends`/`pops` from both sides, see [`collections.deque`][deque], which has approximately the same O(1) performance in either direction.
-Because lists are mutable and can contain references to arbitrary objects, they also take up more space in memory than a fixed-size [`array.array`][array.array] type of the same apparent length.
+Because lists are mutable and can contain references to arbitrary Python objects, they also take up more space in memory than an [`array.array`][array.array] or a [`tuple`][tuple] (_which is immutable_) of the same apparent length.
Despite this, lists are an extremely flexible and useful data structure and many built-in methods and operations in Python produce lists as their output.
@@ -135,7 +135,8 @@ TypeError: 'int' object is not iterable
## Accessing elements
-Items inside lists (_as well as elements in other sequence types such as [`str`][string] & [`tuple`][tuple]_), can be accessed using _bracket notation_. Indexes can be from **`left`** --> **`right`** (_starting at zero_) or **`right`** --> **`left`** (_starting at -1_).
+Items inside lists (_as well as elements in other sequence types such as [`str`][string] & [`tuple`][tuple]_), can be accessed using _bracket notation_.
+Indexes can be from **`left`** --> **`right`** (_starting at zero_) or **`right`** --> **`left`** (_starting at -1_).
@@ -173,9 +174,11 @@ Items inside lists (_as well as elements in other sequence types such as [`str`]
'Toast'
```
-A section of a list can be accessed via _slice notation_ (`[start:stop]`). A _slice_ is defined as an element sequence at position `index`, such that `start <= index < stop`. [_Slicing_][slice notation] returns a copy of the "sliced" items and does not modify the original `list`.
+A section of a list can be accessed via _slice notation_ (`[start:stop]`).
+A _slice_ is defined as an element sequence at position `index`, such that `start <= index < stop`.
+[_Slicing_][slice notation] returns a copy of the "sliced" items and does not modify the original `list`.
-A `step` parameter can also be used in the slice (`[start:stop:step]`) to "skip over" or filter the returned elements (_for example, a `step` of 2 will select every other element in the section_):
+A `step` parameter can also be used in the slice (`[::]`) to "skip over" or filter the returned elements (_for example, a `step` of 2 will select every other element in the section_):
```python
>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"]
@@ -269,7 +272,7 @@ Lists can also be combined via various techniques:
>>> first_one
['George', 5, 'cat', 'Tabby']
-# This loops through the first list and appends it's items to the end of the second list.
+# This loops through the first list and appends its items to the end of the second list.
>>> first_one = ["cat", "Tabby"]
>>> second_one = ["George", 5]
@@ -284,7 +287,7 @@ Lists can also be combined via various techniques:
## Some cautions
Recall that variables in Python are _labels_ that point to _underlying objects_.
-`lists` add one more layer as _container objects_ -- they hold object references for their collected items.
+`lists` add one more layer as _container objects_ -- they hold object _references_ for their collected items.
This can lead to multiple potential issues when working with lists, if not handled properly.
@@ -305,21 +308,22 @@ A `shallow_copy` will create a new `list` object, but **will not** create new ob
# Altering the list via the new name is the same as altering the list via the old name.
>>> same_list.append("Clarke")
->>> same_list
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
+
>>> actual_names
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
# Likewise, altering the data in the list via the original name will also alter the data under the new name.
>>> actual_names[0] = "Wanda"
->>> same_list
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']
# If you copy the list, there will be two separate list objects which can be changed independently.
>>> copied_list = actual_names.copy()
>>> copied_list[0] = "Tony"
+
>>> actual_names
['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']
+
>>> copied_list
["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
```
@@ -455,4 +459,4 @@ The collections module also provides a `UserList` type that can be customized to
[set]: https://docs.python.org/3/library/stdtypes.html#set
[slice notation]: https://docs.python.org/3/reference/expressions.html#slicings
[string]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str
-[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
\ No newline at end of file
+[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
diff --git a/exercises/concept/card-games/.docs/hints.md b/exercises/concept/card-games/.docs/hints.md
index e293fffc92..56343b7b16 100644
--- a/exercises/concept/card-games/.docs/hints.md
+++ b/exercises/concept/card-games/.docs/hints.md
@@ -4,45 +4,44 @@
## 1. Tracking Poker Rounds
-- Lists in Python may be [constructed][constructed] in several ways.
+- Lists in Python may be [constructed][constructed] in multiple ways.
- This function should [return][return] a `list`.
## 2. Keeping all Rounds in the Same Place
-- Sequence types such as `list` already support [common operations][common sequence operations].
+- Sequence types such as `list` support [common operations][common sequence operations].
- This function should [return][return] a `list`.
## 3. Finding Prior Rounds
-- Sequence types such as `list` already support a few [common operations][common sequence operations].
+- Sequence types such as `list` support a few [common operations][common sequence operations].
- This function should [return][return] a `bool`.
## 4. Averaging Card Values
-- To get the average, this function should count how many items are in the `list` and sum up their values. Then, return sum/count.
+- To get the average, this function should count how many items are in the `list` and sum up their values. Then, return the sum divided by the count.
## 5. Alternate Averages
-- Sequence types such as `list` already support a few [common operations][common sequence operations].
-- To access an element use the square brackets (`[]`) notation.
-- Remember that the first element of the `list` is at index 0 from the left.
-- In Python, negative indexing starts the count from the right-hand side. This mean that you can find the last element of a `list` at `index -1`.
+- Sequence types such as `list` support a few [common operations][common sequence operations].
+- To access an element, use the square brackets (`[]`) notation.
+- Remember that the first element of the `list` is at index 0 from the **left-hand** side.
+- In Python, negative indexing starts at -1 from the **right-hand** side. This means that you can find the last element of a `list` by using `[-1]`.
- Think about how you could reuse the code from the functions that you have already implemented.
## 6. More Averaging Techniques
- Sequence types such as `list` already support a few [common operations][common sequence operations].
- Think about reusing the code from the functions that you just implemented.
-- The slice syntax supports a step value.
+- The slice syntax supports a _step value_ (`[::]`).
## 7. Bonus Round Rules
-- Lists are mutable. Once a `list` is created, you can modify, delete or add any type of element you wish.
+- Lists are _mutable_. Once a `list` is created, you can modify, delete or add any type of element you wish.
- Python provides a wide range of [ways to modify `lists`][ways to modify `lists`].
[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range
[constructed]: https://docs.python.org/3/library/stdtypes.html#list
-[iterate over a list in python]: https://www.geeksforgeeks.org/iterate-over-a-list-in-python/
[return]: https://www.w3schools.com/python/ref_keyword_return.asp
[ways to modify `lists`]: https://realpython.com/python-lists-tuples/#lists-are-mutable
diff --git a/exercises/concept/card-games/.docs/introduction.md b/exercises/concept/card-games/.docs/introduction.md
index 1650246024..bb0c238117 100644
--- a/exercises/concept/card-games/.docs/introduction.md
+++ b/exercises/concept/card-games/.docs/introduction.md
@@ -2,7 +2,7 @@
A [`list`][list] is a mutable collection of items in _sequence_.
Like most collections (_see the built-ins [`tuple`][tuple], [`dict`][dict] and [`set`][set]_), lists can hold reference to any (or multiple) data type(s) - including other lists.
-Like any [sequence][sequence type], items can be accessed via `0-based index` number from the left and `-1-base index` from the right.
+Like any [sequence][sequence type], items can be accessed via `0-based index` number from the left and `-1-based index` from the right.
Lists can be copied in whole or in part via [slice notation][slice notation] or `.copy()`.
Lists support both [common][common sequence operations] and [mutable][mutable sequence operations] sequence operations such as `min()`/`max()`, `.index()`, `.append()` and `.reverse()`.
diff --git a/exercises/concept/card-games/lists_test.py b/exercises/concept/card-games/lists_test.py
index dd8ea2efc3..e55011294a 100644
--- a/exercises/concept/card-games/lists_test.py
+++ b/exercises/concept/card-games/lists_test.py
@@ -1,5 +1,6 @@
import unittest
import pytest
+
from lists import (
get_rounds,
concatenate_rounds,
@@ -16,92 +17,121 @@ class CardGamesTest(unittest.TestCase):
@pytest.mark.task(taskno=1)
def test_get_rounds(self):
- input_vars = [0, 1, 10, 27, 99, 666]
+ input_data = [0, 1, 10, 27, 99, 666]
+ result_data = [[0, 1, 2], [1, 2, 3],
+ [10, 11, 12], [27, 28, 29],
+ [99, 100, 101], [666, 667, 668]]
- results = [[0, 1, 2], [1, 2, 3],
- [10, 11, 12], [27, 28, 29],
- [99, 100, 101], [666, 667, 668]]
+ for variant, (number, expected) in enumerate(zip(input_data, result_data), start=1):
+ with self.subTest(f'variation #{variant}', number=number, expected=expected):
+ actual_result = get_rounds(number)
+ error_message = (f'Called get_rounds({number}). '
+ f'The function returned {actual_result}, '
+ f'but the tests expected rounds {expected} '
+ f'given the current round {number}.')
- for variant, (number, rounds) in enumerate(zip(input_vars, results), start=1):
- error_message = f'Expected rounds {rounds} given the current round {number}.'
- with self.subTest(f'variation #{variant}', input=number, output=rounds):
- self.assertEqual(rounds, get_rounds(number), msg=error_message)
+ self.assertEqual(actual_result, expected, msg=error_message)
@pytest.mark.task(taskno=2)
def test_concatenate_rounds(self):
- input_vars = [([], []), ([0, 1], []), ([], [1, 2]),
+ input_data = [([], []), ([0, 1], []), ([], [1, 2]),
([1], [2]), ([27, 28, 29], [35, 36]),
([1, 2, 3], [4, 5, 6])]
- results = [[], [0, 1], [1, 2], [1, 2],
- [27, 28, 29, 35, 36],
- [1, 2, 3, 4, 5, 6]]
+ result_data = [[], [0, 1], [1, 2], [1, 2],
+ [27, 28, 29, 35, 36],
+ [1, 2, 3, 4, 5, 6]]
+
+ for variant, ((rounds_1, rounds_2), expected) in enumerate(zip(input_data, result_data), start=1):
+ with self.subTest(f'variation #{variant}', rounds_1=rounds_1, rounds_2=rounds_2, expected=expected):
+ actual_result = concatenate_rounds(rounds_1, rounds_2)
+ error_message = (f'Called concatenate_rounds({rounds_1}, {rounds_2}). '
+ f'The function returned {actual_result}, but the tests '
+ f'expected {expected} as the concatenation '
+ f'of {rounds_1} and {rounds_2}.')
- for variant, ((rounds_1, rounds_2), rounds) in enumerate(zip(input_vars, results), start=1):
- error_message = f'Expected {rounds} as the concatenation of {rounds_1} and {rounds_2}.'
- with self.subTest(f'variation #{variant}', input=(rounds_1, rounds_2), output=rounds):
- self.assertEqual(rounds, concatenate_rounds(rounds_1, rounds_2), msg=error_message)
+ self.assertEqual(actual_result, expected, msg=error_message)
@pytest.mark.task(taskno=3)
def test_list_contains_round(self):
- input_vars = [([], 1), ([1, 2, 3], 0), ([27, 28, 29, 35, 36], 30),
- ([1], 1), ([1, 2, 3], 1), ([27, 28, 29, 35, 36], 29)]
+ input_data = [([], 1), ([1, 2, 3], 0),
+ ([27, 28, 29, 35, 36], 30),
+ ([1], 1), ([1, 2, 3], 1),
+ ([27, 28, 29, 35, 36], 29)]
+ result_data = [False, False, False, True, True, True]
- results = [False, False, False, True, True, True]
+ for variant, ((rounds, round_number), expected) in enumerate(zip(input_data, result_data), start=1):
+ with self.subTest(f'variation #{variant}', rounds=rounds, round_number=round_number, expected=expected):
+ actual_result = list_contains_round(rounds, round_number)
+ error_message = (f'Called list_contains_round({rounds}, {round_number}). '
+ f'The function returned {actual_result}, but round {round_number} '
+ f'{"is" if expected else "is not"} in {rounds}.')
- for variant, ((rounds, round_number), contains) in enumerate(zip(input_vars, results), start=1):
- error_message = f'Round {round_number} {"is" if contains else "is not"} in {rounds}.'
- with self.subTest(f'variation #{variant}', input=(rounds, round_number), output=contains):
- self.assertEqual(contains, list_contains_round(rounds, round_number), msg=error_message)
+ self.assertEqual(actual_result, expected, msg=error_message)
@pytest.mark.task(taskno=4)
def test_card_average(self):
- input_vars = [[1], [5, 6, 7], [1, 2, 3, 4], [1, 10, 100]]
+ input_data = [[1], [5, 6, 7], [1, 2, 3, 4], [1, 10, 100]]
+ result_data = [1.0, 6.0, 2.5, 37.0]
- results = [1.0, 6.0, 2.5, 37.0]
+ for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1):
+ with self.subTest(f'variation #{variant}', hand=hand, expected=expected):
+ actual_result = card_average(hand)
+ error_message = (f'Called card_average({hand}). '
+ f'The function returned {actual_result}, but '
+ f'the tests expected {expected} as the average of {hand}.')
- for variant, (hand, average) in enumerate(zip(input_vars, results), start=1):
- error_message = f'Expected {average} as the average of {hand}.'
- with self.subTest(f'variation #{variant}', input=hand, output=average):
- self.assertEqual(average, card_average(hand), msg=error_message)
+ self.assertEqual(actual_result, expected, msg=error_message)
@pytest.mark.task(taskno=5)
def test_approx_average_is_average(self):
- input_vars = [[0, 1, 5], [3, 6, 9, 12, 150], [1, 2, 3, 5, 9],
+ input_data = [[0, 1, 5], [3, 6, 9, 12, 150], [1, 2, 3, 5, 9],
[2, 3, 4, 7, 8], [1, 2, 3], [2, 3, 4],
[2, 3, 4, 8, 8], [1, 2, 4, 5, 8]]
- results = [False, False, False, False, True, True, True, True]
+ result_data = [False, False, False, False, True, True, True, True]
+
+ for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1):
+ with self.subTest(f'variation #{variant}', hand=hand, expected=expected):
+ actual_result = approx_average_is_average(hand)
+ error_message = (f'Called approx_average_is_average({hand}). '
+ f'The function returned {actual_result}, but '
+ f'the hand {hand} {"does" if expected else "does not"} '
+ f'yield the same approximate average.')
- for variant, (hand, same) in enumerate(zip(input_vars, results), start=1):
- error_message = f'Hand {hand} {"does" if same else "does not"} yield the same approximate average.'
- with self.subTest(f'variation #{variant}', input=hand, output=same):
- self.assertEqual(same, approx_average_is_average(hand), msg=error_message)
+ self.assertEqual(actual_result, expected, msg=error_message)
@pytest.mark.task(taskno=6)
def test_average_even_is_average_odd(self):
- input_vars = [[5, 6, 8], [1, 2, 3, 4], [1, 2, 3], [5, 6, 7], [1, 3, 5, 7, 9]]
+ input_data = [[5, 6, 8], [1, 2, 3, 4], [1, 2, 3], [5, 6, 7], [1, 3, 5, 7, 9]]
+ result_data = [False, False, True, True, True]
- results = [False, False, True, True, True]
+ for variant, (input_hand, expected) in enumerate(zip(input_data, result_data), start=1):
+ with self.subTest(f'variation #{variant}', input_hand=input_hand, expected=expected):
+ actual_result = average_even_is_average_odd(input_hand)
+ error_message = (f'Called average_even_is_average_odd({input_hand}). '
+ f'The function returned {actual_result}, but '
+ f'the hand {"does" if expected else "does not"} '
+ f'yield the same odd-even average.')
- for variant, (hand, same) in enumerate(zip(input_vars, results), start=1):
- error_message = f'Hand {hand} {"does" if same else "does not"} yield the same odd-even average.'
- with self.subTest(f'variation #{variant}', input=hand, output=same):
- self.assertEqual(same, average_even_is_average_odd(hand), msg=error_message)
+ self.assertEqual(actual_result, expected, msg=error_message)
@pytest.mark.task(taskno=7)
def test_maybe_double_last(self):
- input_vars = [[1, 2, 11], [5, 9, 11], [5, 9, 10], [1, 2, 3]]
+ input_data = [(1, 2, 11), (5, 9, 11), (5, 9, 10), (1, 2, 3), (1, 11, 8)]
+ result_data = [[1, 2, 22], [5, 9, 22], [5, 9, 10], [1, 2, 3], [1, 11, 8]]
- results = [[1, 2, 22], [5, 9, 22], [5, 9, 10], [1, 2, 3]]
+ for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1):
+ with self.subTest(f'variation #{variant}', hand=list(hand), expected=expected):
+ actual_result = maybe_double_last(list(hand))
+ error_message = (f'Called maybe_double_last({list(hand)}). '
+ f'The function returned {actual_result}, but '
+ f'the tests expected {expected} as the maybe-doubled version of {list(hand)}.')
- for variant, (hand, doubled_hand) in enumerate(zip(input_vars, results), start=1):
- error_message = f'Expected {doubled_hand} as the maybe-doubled version of {hand}.'
- with self.subTest(f'variation #{variant}', input=hand, output=doubled_hand):
- self.assertEqual(doubled_hand, maybe_double_last(hand), msg=error_message)
+ self.assertEqual(actual_result, expected, msg=error_message)