primeiro commit com exercicios de Exercism realizados até esta data
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"authors": [
|
||||
"meatball133",
|
||||
"BethanyG"
|
||||
],
|
||||
"contributors": [
|
||||
"IsaacG"
|
||||
],
|
||||
"files": {
|
||||
"solution": [
|
||||
"locomotive_engineer.py"
|
||||
],
|
||||
"test": [
|
||||
"locomotive_engineer_test.py"
|
||||
],
|
||||
"exemplar": [
|
||||
".meta/exemplar.py"
|
||||
]
|
||||
},
|
||||
"forked_from": [
|
||||
"javascript/train-driver"
|
||||
],
|
||||
"icon": "tracks-on-tracks-on-tracks",
|
||||
"blurb": "Learn about unpacking and multiple assignment in Python while helping Linus with his train control system."
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"track":"python","exercise":"locomotive-engineer","id":"2b97ea96693c48de834541d3b8b3d18b","url":"https://exercism.org/tracks/python/exercises/locomotive-engineer","handle":"sanctushck","is_requester":true,"auto_approve":false}
|
||||
@@ -0,0 +1,130 @@
|
||||
# Help
|
||||
|
||||
## Running the tests
|
||||
|
||||
We use [pytest][pytest: Getting Started Guide] as our website test runner.
|
||||
You will need to install `pytest` on your development machine if you want to run tests for the Python track locally.
|
||||
You should also install the following `pytest` plugins:
|
||||
|
||||
- [pytest-cache][pytest-cache]
|
||||
- [pytest-subtests][pytest-subtests]
|
||||
|
||||
Extended information can be found in our website [Python testing guide][Python track tests page].
|
||||
|
||||
|
||||
### Running Tests
|
||||
|
||||
To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_).
|
||||
Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded.
|
||||
|
||||
Linux/MacOS
|
||||
```bash
|
||||
$ cd {path/to/exercise-folder-location}
|
||||
```
|
||||
|
||||
Windows
|
||||
```powershell
|
||||
PS C:\Users\foobar> cd {path\to\exercise-folder-location}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file:
|
||||
|
||||
Linux/MacOS
|
||||
```bash
|
||||
$ python3 -m pytest -o markers=task {exercise_test.py}
|
||||
==================== 7 passed in 0.08s ====================
|
||||
```
|
||||
|
||||
Windows
|
||||
```powershell
|
||||
PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py}
|
||||
==================== 7 passed in 0.08s ====================
|
||||
```
|
||||
|
||||
|
||||
### Common options
|
||||
- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_)
|
||||
- `-v` : enable verbose output.
|
||||
- `-x` : stop running tests on first failure.
|
||||
- `--ff` : run failures from previous test before running other test cases.
|
||||
|
||||
For additional options, use `python3 -m pytest -h` or `py -m pytest -h`.
|
||||
|
||||
|
||||
### Fixing warnings
|
||||
|
||||
If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax:
|
||||
|
||||
```bash
|
||||
PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html
|
||||
```
|
||||
|
||||
To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file.
|
||||
We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini].
|
||||
|
||||
You can also create your own `pytest.ini` file with the following content:
|
||||
|
||||
```ini
|
||||
[pytest]
|
||||
markers =
|
||||
task: A concept exercise task.
|
||||
```
|
||||
|
||||
Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings.
|
||||
More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers].
|
||||
|
||||
Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats].
|
||||
|
||||
|
||||
### Extending your IDE or Code Editor
|
||||
|
||||
Many IDEs and code editors have built-in support for using `pytest` and other code quality tools.
|
||||
Some community-sourced options can be found on our [Python track tools page][Python track tools page].
|
||||
|
||||
[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html
|
||||
[Python track tools page]: https://exercism.org/docs/tracks/python/tools
|
||||
[Python track tests page]: https://exercism.org/docs/tracks/python/tests
|
||||
[pytest-cache]:http://pythonhosted.org/pytest-cache/
|
||||
[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests
|
||||
[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini
|
||||
[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats
|
||||
[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks
|
||||
[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers
|
||||
|
||||
## Submitting your solution
|
||||
|
||||
You can submit your solution using the `exercism submit locomotive_engineer.py` command.
|
||||
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||
|
||||
It's possible to submit an incomplete solution which allows you to:
|
||||
|
||||
- See how others have completed the exercise
|
||||
- Request help from a mentor
|
||||
|
||||
## Need to get help?
|
||||
|
||||
If you'd like help solving the exercise, check the following pages:
|
||||
|
||||
- The [Python track's documentation](https://exercism.org/docs/tracks/python)
|
||||
- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python)
|
||||
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
|
||||
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||
|
||||
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||
|
||||
Below are some resources for getting help if you run into trouble:
|
||||
|
||||
- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources.
|
||||
- [The Exercism Community on Discord](https://exercism.org/r/discord)
|
||||
- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community.
|
||||
- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners.
|
||||
- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done.
|
||||
- [Python Community Forums](https://discuss.python.org/)
|
||||
- [Free Code Camp Community Forums](https://forum.freecodecamp.org/)
|
||||
- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help)
|
||||
- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually.
|
||||
|
||||
Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already.
|
||||
If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Hints
|
||||
|
||||
## General
|
||||
|
||||
- To extract multiple arguments in the function parameters so can you pack them with the `*args` operator for `list` or `tuples` or `**kwargs` for keyword-based arguments.
|
||||
- To pack or unpack use the `*` or `**` operator.
|
||||
|
||||
## 1. Create a list of all wagons
|
||||
|
||||
- Multiple arguments in the function parameters can be packed with the `*args` operator.
|
||||
|
||||
## 2. Fix list of wagons
|
||||
|
||||
- Using unpacking with the `*` operator, lets you extract the first two elements of a `list` while keeping the rest intact.
|
||||
- To add another `list` into an existing `list`, you can use the `*` operator to "spread" the `list`.
|
||||
|
||||
## 3. Add missing stops
|
||||
|
||||
- Using `**kwargs` as a function parameter will allow an arbitrary number of keyword arguments to be passed.
|
||||
- Using `**<dict>` as an argument will unpack a dictionary into keyword arguments.
|
||||
- You can put keyword arguments in a `{}` or `dict()`.
|
||||
- To get the values out of a dictionary, you can use the `<dict>.values()` method.
|
||||
|
||||
## 4. Extend routing information
|
||||
|
||||
- Using `**<dict>` as an argument will unpack a dictionary into keyword arguments.
|
||||
- You can put keyword arguments in a `{}` or `dict()`.
|
||||
|
||||
## 5. Fix the wagon depot
|
||||
|
||||
- `zip(*iterators)` can use used to transpose a nested `list`.
|
||||
- To extract data from zipped iterators, you can use a for loop.
|
||||
- you can also unpack zipped iterators using `*`.
|
||||
`[*content] = zip(iterator_1, iterator_2)` will unzip the `tuple` produced by `zip()` into a `list`.
|
||||
@@ -0,0 +1,504 @@
|
||||
# Locomotive Engineer
|
||||
|
||||
Welcome to Locomotive Engineer on Exercism's Python Track.
|
||||
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||
|
||||
## Introduction
|
||||
|
||||
Unpacking refers to the act of extracting the elements of a collection, such as a `list`, `tuple`, or `dict`, using iteration.
|
||||
Unpacked values can then be assigned to variables within the same statement, which is commonly referred to as [Multiple assignment][multiple assignment].
|
||||
|
||||
The special operators `*` and `**` are often used in unpacking contexts and with multiple assignment.
|
||||
|
||||
~~~~exercism/caution
|
||||
`*<variable_name>` and `**<variable_name>` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*<variable_name>` and `**<variable_name>` are used as packing and unpacking operators.
|
||||
~~~~
|
||||
|
||||
|
||||
## Multiple assignment
|
||||
|
||||
In multiple assignment, the number of variables on the left side of the assignment operator (`=`) must match the number of values on the right side.
|
||||
To separate the values, use a comma `,`:
|
||||
|
||||
```python
|
||||
>>> a, b = 1, 2
|
||||
>>> a
|
||||
1
|
||||
```
|
||||
|
||||
If the multiple assignment gets an incorrect number of variables for the values given, a `ValueError` will be thrown:
|
||||
|
||||
```python
|
||||
>>> x, y, z = 1, 2
|
||||
|
||||
ValueError: too many values to unpack (expected 3, got 2)
|
||||
```
|
||||
|
||||
Multiple assignment is not limited to one data type:
|
||||
|
||||
```python
|
||||
>>> x, y, z = 1, "Hello", True
|
||||
>>> x
|
||||
1
|
||||
|
||||
>>> y
|
||||
'Hello'
|
||||
|
||||
>>> z
|
||||
True
|
||||
```
|
||||
|
||||
Multiple assignment can be used to swap elements in `lists`.
|
||||
This practice is pretty common in [sorting algorithms][sorting algorithms].
|
||||
For example:
|
||||
|
||||
```python
|
||||
>>> numbers = [1, 2]
|
||||
>>> numbers[0], numbers[1] = numbers[1], numbers[0]
|
||||
>>> numbers
|
||||
[2, 1]
|
||||
```
|
||||
|
||||
Since `tuples` are immutable, you can't swap elements in a `tuple`.
|
||||
|
||||
|
||||
## Unpacking
|
||||
|
||||
~~~~exercism/note
|
||||
The examples below use `lists` but the same concepts apply to `tuples`.
|
||||
~~~~
|
||||
|
||||
In Python, it is possible to [unpack the elements of `list`/`tuple`/`dictionary`][unpacking] into distinct variables.
|
||||
Since values appear within `lists`/`tuples` in a specific order, they are unpacked into variables in the same order:
|
||||
|
||||
```python
|
||||
>>> fruits = ["apple", "banana", "cherry"]
|
||||
>>> x, y, z = fruits
|
||||
>>> x
|
||||
"apple"
|
||||
```
|
||||
|
||||
If there are values that are not needed then you can use `_` to flag them:
|
||||
|
||||
```python
|
||||
>>> fruits = ["apple", "banana", "cherry"]
|
||||
>>> _, _, z = fruits
|
||||
>>> z
|
||||
"cherry"
|
||||
```
|
||||
|
||||
|
||||
### Deep unpacking
|
||||
|
||||
Unpacking and assigning values from a `list`/`tuple` enclosed inside a `list` or `tuple` (_also known as nested lists/tuples_) works in the same way a shallow unpacking does — but often needs qualifiers to clarify the context or position:
|
||||
|
||||
```python
|
||||
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
|
||||
>>> [[a, b], [c, d]] = fruits_vegetables
|
||||
>>> a
|
||||
"apple"
|
||||
|
||||
>>> d
|
||||
"potato"
|
||||
```
|
||||
|
||||
You can also deeply unpack just a portion of a nested `list`/`tuple`:
|
||||
|
||||
```python
|
||||
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
|
||||
>>> [a, [c, d]] = fruits_vegetables
|
||||
>>> a
|
||||
["apple", "banana"]
|
||||
|
||||
>>> c
|
||||
"carrot"
|
||||
```
|
||||
|
||||
If the unpacking has variables with incorrect placement and/or an incorrect number of values, you will get a `ValueError`:
|
||||
|
||||
```python
|
||||
>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
|
||||
>>> [[a, b], [d]] = fruits_vegetables
|
||||
|
||||
ValueError: too many values to unpack (expected 1)
|
||||
```
|
||||
|
||||
### Unpacking a list/tuple with `*`
|
||||
|
||||
When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the "leftover" values.
|
||||
This is clearer than slicing the `list`/`tuple` (_which in some situations is less readable_).
|
||||
For example, the first element can be extracted and then the remaining values can be placed into a new `list` without the first element:
|
||||
|
||||
```python
|
||||
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
|
||||
>>> x, *last = fruits
|
||||
>>> x
|
||||
"apple"
|
||||
|
||||
>>> last
|
||||
["banana", "cherry", "orange", "kiwi", "melon", "mango"]
|
||||
```
|
||||
|
||||
We can also extract the values at the beginning and end of the `list` while grouping all the values in the middle:
|
||||
|
||||
```python
|
||||
>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
|
||||
>>> x, *middle, y, z = fruits
|
||||
>>> y
|
||||
"melon"
|
||||
|
||||
>>> middle
|
||||
["banana", "cherry", "orange", "kiwi"]
|
||||
```
|
||||
|
||||
We can also use `*` in deep unpacking:
|
||||
|
||||
```python
|
||||
>>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
|
||||
>>> [[a, *rest], b] = fruits_vegetables
|
||||
>>> a
|
||||
"apple"
|
||||
|
||||
>>> rest
|
||||
["banana", "melon"]
|
||||
```
|
||||
|
||||
### Unpacking a dictionary
|
||||
|
||||
[Unpacking a dictionary][packing and unpacking] is a bit different from unpacking a `list`/`tuple`.
|
||||
Iteration over dictionaries defaults to the **keys**.
|
||||
So when unpacking a `dict`, you can only unpack the **keys** and not the **values**:
|
||||
|
||||
```python
|
||||
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
|
||||
>>> x, y, z = fruits_inventory
|
||||
>>> x
|
||||
"apple"
|
||||
```
|
||||
|
||||
If you want to unpack the values then you can use the `<dict>.values()` method:
|
||||
|
||||
```python
|
||||
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
|
||||
>>> x, y, z = fruits_inventory.values()
|
||||
>>> x
|
||||
6
|
||||
```
|
||||
|
||||
If both **keys** and **values** are needed, use the [`<dict>.items()`][items] method.
|
||||
`<dict>.items()` generates an [iterable view][view-objects] containing **key-value** pairs.
|
||||
These can be unpacked into a `tuple`:
|
||||
|
||||
```python
|
||||
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
|
||||
>>> x, y, z = fruits_inventory.items()
|
||||
>>> x
|
||||
("apple", 6)
|
||||
```
|
||||
|
||||
## Packing
|
||||
|
||||
[Packing][packing and unpacking] is the ability to group multiple values into one `list` that is assigned to a variable.
|
||||
This is useful when you want to _unpack_ values, make changes, and then _pack_ the results back into a variable.
|
||||
It also makes it possible to perform merges on 2 or more `lists`/`tuples`/`dicts`.
|
||||
|
||||
### Packing a list/tuple with `*`
|
||||
|
||||
Packing a `list`/`tuple` can be done using the `*` operator.
|
||||
This will pack all the values into a `list`/`tuple`.
|
||||
|
||||
```python
|
||||
>>> fruits = ("apple", "banana", "cherry")
|
||||
>>> more_fruits = ["orange", "kiwi", "melon", "mango"]
|
||||
|
||||
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
|
||||
>>> combined_fruits = *fruits, *more_fruits
|
||||
|
||||
# If there is no * on to the left of the "=" the result is a tuple
|
||||
>>> combined_fruits
|
||||
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")
|
||||
|
||||
# If the * operator is used on the left side of "=" the result is a list.
|
||||
# Note the trailing comma.
|
||||
>>> *combined_fruits_too, = *fruits, *more_fruits
|
||||
>>> combined_fruits_too
|
||||
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']
|
||||
```
|
||||
|
||||
For more background on using `*` on the left-hand side, see [PEP 3132][pep-3132].
|
||||
|
||||
|
||||
### Packing a dictionary with `**`
|
||||
|
||||
Packing a dictionary is done by using the `**` operator.
|
||||
This will pack all **key**-**value** pairs from one dictionary into another dictionary, or combine two dictionaries together.
|
||||
|
||||
```python
|
||||
>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
|
||||
>>> more_fruits_inventory = {"orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
|
||||
|
||||
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
|
||||
>>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
|
||||
|
||||
# then the pairs are packed into combined_fruits_inventory
|
||||
>>> combined_fruits_inventory
|
||||
{"apple": 6, "banana": 2, "cherry": 3, "orange": 4, "kiwi": 1, "melon": 2, "mango": 3}
|
||||
```
|
||||
|
||||
## Usage of `*` and `**` with functions
|
||||
|
||||
### Packing with function parameters
|
||||
|
||||
When you create a function that accepts an arbitrary number of arguments, you can use [`*args` or `**kwargs`][args and kwargs] in the function definition.
|
||||
`*args` is used to pack an arbitrary number of positional (_non-keyword_) arguments as a `tuple` and
|
||||
`**kwargs` is used to pack an arbitrary number of keyword arguments as a dictionary.
|
||||
|
||||
Usage of `*args`:
|
||||
|
||||
```python
|
||||
# This function is defined to take any number of positional arguments
|
||||
|
||||
>>> def my_function(*args):
|
||||
... print(args)
|
||||
|
||||
# Arguments given to the function are packed into a tuple
|
||||
|
||||
>>> my_function(1, 2, 3)
|
||||
(1, 2, 3)
|
||||
|
||||
>>> my_function("Hello")
|
||||
("Hello")
|
||||
|
||||
>>> my_function(1, 2, 3, "Hello", "Mars")
|
||||
(1, 2, 3, "Hello", "Mars")
|
||||
```
|
||||
|
||||
Usage of `**kwargs`:
|
||||
|
||||
```python
|
||||
# This function is defined to take any number of keyword arguments
|
||||
|
||||
>>> def my_function(**kwargs):
|
||||
... print(kwargs)
|
||||
|
||||
# Arguments given to the function are packed into a dictionary
|
||||
|
||||
>>> my_function(a=1, b=2, c=3)
|
||||
{"a": 1, "b": 2, "c": 3}
|
||||
```
|
||||
|
||||
`*args` and `**kwargs` can also be used in combination with one another:
|
||||
|
||||
```python
|
||||
>>> def my_function(*args, **kwargs):
|
||||
... print(sum(args))
|
||||
... for key, value in kwargs.items():
|
||||
... print(str(key) + " = " + str(value))
|
||||
|
||||
>>> my_function(1, 2, 3, a=1, b=2, c=3)
|
||||
6
|
||||
a = 1
|
||||
b = 2
|
||||
c = 3
|
||||
```
|
||||
|
||||
You can also write parameters before `*args` to allow for specific positional arguments.
|
||||
Individual keyword arguments then have to appear before `**kwargs`.
|
||||
|
||||
~~~~exercism/caution
|
||||
[Arguments have to be structured](https://www.python-engineer.com/courses/advancedpython/18-function-arguments/) like this:
|
||||
|
||||
`def my_function(<positional_args>, *args, <key-word_args>, **kwargs)`
|
||||
|
||||
If you don't follow this order then you will get an error.
|
||||
~~~~
|
||||
|
||||
```python
|
||||
>>> def my_function(a, b, *args):
|
||||
... print(a)
|
||||
... print(b)
|
||||
... print(args)
|
||||
|
||||
>>> my_function(1, 2, 3, 4, 5)
|
||||
1
|
||||
2
|
||||
(3, 4, 5)
|
||||
```
|
||||
|
||||
Writing arguments in an incorrect order will result in an error:
|
||||
|
||||
```python
|
||||
>>>def my_function(*args, a, b):
|
||||
... print(args)
|
||||
|
||||
>>>my_function(1, 2, 3, 4, 5)
|
||||
Traceback (most recent call last):
|
||||
File "c:\something.py", line 3, in <module>
|
||||
my_function(1, 2, 3, 4, 5)
|
||||
TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'
|
||||
```
|
||||
|
||||
### Unpacking into function calls
|
||||
|
||||
You can use `*` to unpack a `list`/`tuple` of arguments into a function call.
|
||||
This is very useful for functions that don't accept an `iterable`:
|
||||
|
||||
```python
|
||||
>>> def my_function(a, b, c):
|
||||
... print(c)
|
||||
... print(b)
|
||||
... print(a)
|
||||
|
||||
numbers = [1, 2, 3]
|
||||
>>> my_function(*numbers)
|
||||
3
|
||||
2
|
||||
1
|
||||
```
|
||||
|
||||
Using `*` unpacking with the `zip()` function is another common use case.
|
||||
Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the values from each `iterable` grouped:
|
||||
|
||||
```python
|
||||
>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
|
||||
>>> a, *rest = zip(*values)
|
||||
>>> rest
|
||||
[('y', 2, False), ('z', 3, True)]
|
||||
```
|
||||
|
||||
[args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/
|
||||
[items]: https://docs.python.org/3/library/stdtypes.html#dict.items
|
||||
[multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/
|
||||
[packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/
|
||||
[pep-3132]: https://peps.python.org/pep-3132/
|
||||
[sorting algorithms]: https://realpython.com/sorting-algorithms-python/
|
||||
[unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp
|
||||
[view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views
|
||||
|
||||
## Instructions
|
||||
|
||||
Your friend Linus is a Locomotive Engineer who drives cargo trains between cities.
|
||||
Although they are amazing at handling trains, they are not amazing at handling logistics or computers.
|
||||
They would like to enlist your programming help organizing train details and correcting mistakes in route data.
|
||||
|
||||
~~~~exercism/note
|
||||
This exercise could easily be solved using slicing, indexing, and various `dict` methods.
|
||||
However, we would like you to practice packing, unpacking, and multiple assignment in solving each of the tasks below.
|
||||
~~~~
|
||||
|
||||
## 1. Create a list of all wagons
|
||||
|
||||
Your friend has been keeping track of each wagon identifier (ID), but they are never sure how many wagons the system is going to have to process at any given time. It would be much easier for the rest of the logistics program to have this data packaged into a unified `list`.
|
||||
|
||||
Implement a function `get_list_of_wagons()` that accepts an arbitrary number of wagon IDs.
|
||||
Each ID will be a positive integer.
|
||||
The function should then `return` the given IDs as a single `list`.
|
||||
|
||||
```python
|
||||
>>> get_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
|
||||
[1, 7, 12, 3, 14, 8, 5]
|
||||
```
|
||||
|
||||
## 2. Fix the list of wagons
|
||||
|
||||
At this point, you are starting to get a feel for the data and how it's used in the logistics program.
|
||||
The ID system always assigns the locomotive an ID of **1**, with the remainder of the wagons in the train assigned a randomly chosen ID greater than **1**.
|
||||
|
||||
Your friend had to connect two new wagons to the train and forgot to update the system!
|
||||
Now, the first two wagons in the train `list` have to be moved to the end, or everything will be out of order.
|
||||
|
||||
To make matters more complicated, your friend just uncovered a second `list` that appears to contain missing wagon IDs.
|
||||
All they can remember is that once the new wagons are moved, the IDs from this second `list` should be placed directly after the designated locomotive.
|
||||
|
||||
Linus would be really grateful to you for fixing their mistakes and consolidating the data.
|
||||
|
||||
Implement a function `fix_list_of_wagons()` that takes two `lists` containing wagon IDs.
|
||||
It should reposition the first two items of the first `list` to the end, and insert the values from the second `list` behind (_on the right hand side of_) the locomotive ID (**1**).
|
||||
The function should then `return` a `list` with the modifications.
|
||||
|
||||
```python
|
||||
>>> fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15])
|
||||
[1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5]
|
||||
```
|
||||
|
||||
## 3. Add missing stops
|
||||
|
||||
Now that all the wagon data is correct, Linus would like you to update the system's routing information.
|
||||
Along a transport route, a train might make stops at a few different stations to pick up and/or drop off cargo.
|
||||
Each journey could have a different number of these intermediary delivery points.
|
||||
Your friend would like you to update the systems routing `dict` with any missing/additional delivery information.
|
||||
|
||||
Implement a function `add_missing_stops()` that accepts a routing `dict` followed by a variable number of keyword arguments.
|
||||
These arguments could be in the form of a `dict` holding one or more stops, or any number of `stop_number=city` keyword pairs.
|
||||
Your function should then return the routing `dict` updated with an additional `key` that holds a `list` of all the added stops in order.
|
||||
|
||||
```python
|
||||
>>> add_missing_stops({"from": "New York", "to": "Miami"},
|
||||
stop_1="Washington, DC", stop_2="Charlotte", stop_3="Atlanta",
|
||||
stop_4="Jacksonville", stop_5="Orlando")
|
||||
|
||||
{"from": "New York", "to": "Miami", "stops": ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"]}
|
||||
```
|
||||
|
||||
## 4. Extend routing information
|
||||
|
||||
Linus has been working on the routing program and has noticed that certain routes are missing some important details.
|
||||
Initial route information has been constructed as a `dict` and your friend would like you to update that `dict` with whatever might be missing.
|
||||
Every route in the system requires slightly different details, so Linus would really prefer a generic solution.
|
||||
|
||||
Implement a function called `extend_route_information()` that accepts two `dicts`.
|
||||
The first `dict` contains the origin and destination cities the train route runs between.
|
||||
|
||||
The second `dict` contains other routing details such as train speed, length, or temperature.
|
||||
The function should return a consolidated `dict` with all routing information.
|
||||
|
||||
~~~~exercism/note
|
||||
The second `dict` can contain different/more properties than the ones shown in the example.
|
||||
~~~~
|
||||
|
||||
```python
|
||||
>>> extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
|
||||
{"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
|
||||
```
|
||||
|
||||
## 5. Fix the wagon depot
|
||||
|
||||
When Linus was surveying the wagon depot they noticed that the wagons were not getting stored in the correct order.
|
||||
In addition to an ID, each wagon has a color that corresponds to the type of cargo it carries.
|
||||
Wagons are stored in the depot in grids, where each column in the grid has wagons of the same color.
|
||||
|
||||
However, the logistics system shows `lists` of wagons to be stored in the depot have their _rows_ grouped by color.
|
||||
But for the storage grid to work correctly, each _row_ should have three different colors so that the _columns_ align by color.
|
||||
Your friend would like you to sort out the wagon depot `lists`, so that the wagons get stored correctly.
|
||||
|
||||
Implement a function called `fix_wagon_depot()` that accepts a `list` of three items.
|
||||
Each `list` item is a sublist (or "row") that contains three `tuples`.
|
||||
Each `tuple` is a `(<wagon ID>, <wagon color>)` pair.
|
||||
|
||||
Your function should return a `list` with the three "row" `lists` reordered to have the wagons swapped into their correct positions.
|
||||
|
||||
```python
|
||||
>>> fix_wagon_depot([
|
||||
[(2, "red"), (4, "red"), (8, "red")],
|
||||
[(5, "blue"), (9, "blue"), (13,"blue")],
|
||||
[(3, "orange"), (7, "orange"), (11, "orange")],
|
||||
])
|
||||
|
||||
[
|
||||
[(2, "red"), (5, "blue"), (3, "orange")],
|
||||
[(4, "red"), (9, "blue"), (7, "orange")],
|
||||
[(8, "red"), (13,"blue"), (11, "orange")]
|
||||
]
|
||||
```
|
||||
|
||||
## Source
|
||||
|
||||
### Created by
|
||||
|
||||
- @meatball133
|
||||
- @BethanyG
|
||||
|
||||
### Contributed to by
|
||||
|
||||
- @IsaacG
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,63 @@
|
||||
"""Functions which helps the locomotive engineer to keep track of the train."""
|
||||
|
||||
|
||||
def get_list_of_wagons(*wagons_ids):
|
||||
"""Return a list of wagons.
|
||||
|
||||
:param: arbitrary number of wagons.
|
||||
:return: list - list of wagons.
|
||||
"""
|
||||
return list(wagons_ids)
|
||||
|
||||
|
||||
def fix_list_of_wagons(each_wagons_id, missing_wagons):
|
||||
"""Fix the list of wagons.
|
||||
|
||||
:param each_wagons_id: list - the list of wagons.
|
||||
:param missing_wagons: list - the list of missing wagons.
|
||||
:return: list - list of wagons.
|
||||
"""
|
||||
first, second, locomotive, *rest = each_wagons_id
|
||||
(*fixed_list,) = locomotive, *missing_wagons, *rest, first, second
|
||||
return fixed_list
|
||||
|
||||
|
||||
def add_missing_stops(route: dict, **kwargs):
|
||||
"""Add missing stops to route dict.
|
||||
|
||||
:param route: dict - the dict of routing information.
|
||||
:param: arbitrary number of stops.
|
||||
:return: dict - updated route dictionary.
|
||||
"""
|
||||
(*route["stops"],) = kwargs.values()
|
||||
return route
|
||||
|
||||
|
||||
def extend_route_information(route, more_route_information):
|
||||
"""Extend route information with more_route_information.
|
||||
|
||||
:param route: dict - the route information.
|
||||
:param more_route_information: dict - extra route information.
|
||||
:return: dict - extended route information.
|
||||
"""
|
||||
return {**route, **more_route_information}
|
||||
|
||||
|
||||
def fix_wagon_depot(wagons_rows):
|
||||
"""Fix the list of rows of wagons.
|
||||
|
||||
:param wagons_rows: list[list[tuple]] - the list of rows of wagons.
|
||||
:return: list[list[tuple]] - list of rows of wagons.
|
||||
"""
|
||||
# reordered_wagons: list[list[tuple]] = []
|
||||
# grid_1: list = []
|
||||
# grid_2: list = []
|
||||
# grid_3: list = []
|
||||
# for row in wagons_rows:
|
||||
# wagon_1, wagon_2, wagon_3 = row
|
||||
# grid_1.append(wagon_1)
|
||||
# grid_2.append(wagon_2)
|
||||
# grid_3.append(wagon_3)
|
||||
# reordered_wagons = [grid_1, grid_2, grid_3]
|
||||
# return reordered_wagons
|
||||
return [[row_1, row_2, row_3] for (row_1, row_2, row_3) in zip(*wagons_rows)]
|
||||
@@ -0,0 +1,115 @@
|
||||
import unittest
|
||||
import pytest
|
||||
from locomotive_engineer import (get_list_of_wagons,
|
||||
fix_list_of_wagons,
|
||||
add_missing_stops,
|
||||
extend_route_information,
|
||||
fix_wagon_depot)
|
||||
|
||||
|
||||
class LocomotiveEngineerTest(unittest.TestCase):
|
||||
|
||||
@pytest.mark.task(taskno=1)
|
||||
def test_get_list_of_wagons(self):
|
||||
input_data = [(1,5,2,7,4), (1,5), (1,), (1,9,3), (1,10,6,3,9,8,4,14,24,7)]
|
||||
output_data = [[1,5,2,7,4], [1,5], [1], [1,9,3], [1,10,6,3,9,8,4,14,24,7]]
|
||||
|
||||
for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1):
|
||||
with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected):
|
||||
|
||||
actual_result = get_list_of_wagons(*input_data)
|
||||
error_msg= (f'Called get_list_of_wagons{input_data}. '
|
||||
f'The function returned {actual_result}, but the '
|
||||
f'tests expected: {expected} as the wagon list instead.')
|
||||
|
||||
self.assertEqual(actual_result, expected, msg=error_msg)
|
||||
|
||||
@pytest.mark.task(taskno=2)
|
||||
def test_fix_list_of_wagons(self):
|
||||
input_data = [([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15]),
|
||||
([3, 27, 1, 14, 10, 4, 12, 6, 23, 17, 13, 22, 28, 19], [8, 10, 5, 9, 36, 7, 20]),
|
||||
([4, 2, 1], [8, 6, 15]),
|
||||
([3, 14, 1, 25, 7, 19, 10], [8, 6, 4, 5, 9, 21, 2, 13])
|
||||
]
|
||||
output_data = [[1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5],
|
||||
[1, 8, 10, 5, 9, 36, 7, 20, 14, 10, 4, 12, 6, 23, 17, 13, 22, 28, 19, 3, 27],
|
||||
[1, 8, 6, 15, 4, 2],
|
||||
[1, 8, 6, 4, 5, 9, 21, 2, 13, 25, 7, 19, 10, 3, 14]
|
||||
]
|
||||
for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1):
|
||||
with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected):
|
||||
|
||||
actual_result = fix_list_of_wagons(input_data[0], input_data[1])
|
||||
error_msg= (f'Called fix_list_of_wagons({input_data[0]}, {input_data[1]}). '
|
||||
f'The function returned {actual_result}, but the '
|
||||
f'tests expected: {expected} as the wagon list instead.')
|
||||
|
||||
self.assertEqual(actual_result, expected, msg=error_msg)
|
||||
|
||||
@pytest.mark.task(taskno=3)
|
||||
def test_add_missing_stops(self):
|
||||
input_data = (({'from': 'Berlin', 'to': 'Hamburg'}, {'stop_1': 'Lepzig', 'stop_2': 'Hannover', 'stop_3': 'Frankfurt'}),
|
||||
({'from': 'Paris', 'to': 'London'}, {'stop_1': 'Lille'}),
|
||||
({'from': 'New York', 'to': 'Philadelphia'},{}),
|
||||
({'from': 'Gothenburg', 'to': 'Copenhagen'}, {'stop_1': 'Kungsbacka', 'stop_2': 'Varberg', 'stop_3': 'Halmstad', 'stop_4': 'Angelholm', 'stop_5': 'Lund', 'stop_6': 'Malmo'})
|
||||
)
|
||||
output_data = [{'from': 'Berlin', 'to': 'Hamburg', 'stops': ['Lepzig', 'Hannover', 'Frankfurt']},
|
||||
{'from': 'Paris', 'to': 'London', 'stops': ['Lille']},
|
||||
{'from': 'New York', 'to': 'Philadelphia', 'stops': []},
|
||||
{'from': 'Gothenburg', 'to': 'Copenhagen', 'stops': ['Kungsbacka', 'Varberg', 'Halmstad', 'Angelholm', 'Lund', 'Malmo']}
|
||||
]
|
||||
for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1):
|
||||
with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected):
|
||||
|
||||
actual_result = add_missing_stops(input_data[0], **input_data[1])
|
||||
error_msg= (f'Called add_missing_stops({input_data[0]}, {input_data[1]}). '
|
||||
f'The function returned {actual_result}, but the '
|
||||
f'tests expected: {expected} as the set of stops.')
|
||||
|
||||
self.assertEqual(actual_result, expected, msg=error_msg)
|
||||
|
||||
@pytest.mark.task(taskno=4)
|
||||
def test_extend_route_information(self):
|
||||
input_data = [({'from': 'Berlin', 'to': 'Hamburg'}, {'timeOfArrival': '12:00', 'precipitation': '10', 'temperature': '5', 'caboose': 'yes'}),
|
||||
({'from': 'Paris', 'to': 'London'}, {'timeOfArrival': '10:30', 'temperature': '20', 'length': '15'}),
|
||||
({'from': 'Gothenburg', 'to': 'Copenhagen'}, {'precipitation': '1', 'timeOfArrival': '21:20', 'temperature': '-6'})]
|
||||
output_data = [{'from': 'Berlin', 'to': 'Hamburg', 'timeOfArrival': '12:00', 'precipitation': '10', 'temperature': '5', 'caboose': 'yes'},
|
||||
{'from': 'Paris', 'to': 'London', 'timeOfArrival': '10:30', 'temperature': '20', 'length': '15'},
|
||||
{'from': 'Gothenburg', 'to': 'Copenhagen', 'precipitation': '1', 'timeOfArrival': '21:20', 'temperature': '-6'}
|
||||
]
|
||||
|
||||
for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1):
|
||||
with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected):
|
||||
|
||||
actual_result = extend_route_information(input_data[0], input_data[1])
|
||||
error_msg= (f'Called extend_route_information({input_data[0]}, {input_data[1]}). '
|
||||
f'The function returned {actual_result}, but the '
|
||||
f'tests expected: {expected} as the route dictionary.')
|
||||
|
||||
self.assertEqual(actual_result, expected, msg=error_msg)
|
||||
|
||||
@pytest.mark.task(taskno=5)
|
||||
def test_fix_wagon_depot(self):
|
||||
input_data = (
|
||||
[[(2, 'red'), (4, 'red'), (8, 'red')], [(5, 'blue'), (9, 'blue'), (13, 'blue')], [(3, 'orange'), (7, 'orange'), (11, 'orange')]],
|
||||
[[(6, 'blue'), (10, 'blue'), (14, 'blue')], [(7, 'red'), (4, 'red'), (2, 'red')], [(3, 'orange'), (11, 'orange'), (15, 'orange')]],
|
||||
[[(7, 'pink'), (4, 'pink'), (2, 'pink')], [(10, 'green'), (6, 'green'), (14, 'green')], [(9, 'yellow'), (5, 'yellow'), (13, 'yellow')]],
|
||||
[[(3, 'purple'), (11, 'purple'), (15, 'purple')], [(20, 'black'), (16, 'black'), (12, 'black')], [(19, 'white'), (17, 'white'), (18, 'white')]]
|
||||
)
|
||||
|
||||
output_data = (
|
||||
[[(2, 'red'), (5, 'blue'), (3, 'orange')], [(4, 'red'), (9, 'blue'), (7, 'orange')], [(8, 'red'), (13, 'blue'), (11, 'orange')]],
|
||||
[[(6, 'blue'), (7, 'red'), (3, 'orange')], [(10, 'blue'), (4, 'red'), (11, 'orange')], [(14, 'blue'), (2, 'red'), (15, 'orange')]],
|
||||
[[(7, 'pink'), (10, 'green'), (9, 'yellow')], [(4, 'pink'), (6, 'green'), (5, 'yellow')], [(2, 'pink'), (14, 'green'), (13, 'yellow')]],
|
||||
[[(3, 'purple'), (20, 'black'), (19, 'white')], [(11, 'purple'), (16, 'black'), (17, 'white')], [(15, 'purple'), (12, 'black'), (18, 'white')]]
|
||||
)
|
||||
|
||||
for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1):
|
||||
with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected):
|
||||
|
||||
actual_result = fix_wagon_depot(input_data)
|
||||
error_msg= (f'Called fix_wagon_depot({input_data}). '
|
||||
f'The function returned {actual_result}, but the '
|
||||
f'tests expected: {expected} as the wagon depot list.')
|
||||
|
||||
self.assertEqual(actual_result, expected, msg=error_msg)
|
||||
Reference in New Issue
Block a user