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)