Skip to content

Commit 4363378

Browse files
colinleachColin Leach
and
Colin Leach
authored
Locomotive Engineer concept exercise (#879)
* concepts.wip exercises changed to deprecated * Revert "concepts.wip exercises changed to deprecated" This reverts commit 28f91a8. * Change annelyns-infiltration slug for old version * Revert "Change annelyns-infiltration slug for old version" This reverts commit 09dfe5d. * locomotive-engineer concept exercise * various updates in response to reviewer --------- Co-authored-by: Colin Leach <[email protected]>
1 parent 2b6e74f commit 4363378

File tree

9 files changed

+525
-0
lines changed

9 files changed

+525
-0
lines changed

config.json

+13
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,19 @@
153153
],
154154
"status": "wip"
155155
},
156+
{
157+
"slug": "locomotive-engineer",
158+
"name": "Locomotive Engineer",
159+
"uuid": "3341044c-6dd6-4a7b-a841-3a1215cd177b",
160+
"concepts": [
161+
"functions"
162+
],
163+
"prerequisites": [
164+
"vector-operations",
165+
"pairs-and-dicts"
166+
],
167+
"status": "wip"
168+
},
156169
{
157170
"slug": "high-school-sweetheart",
158171
"name": "High School Sweetheart",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Hints
2+
3+
## 1. Create a vector of all wagons
4+
5+
- The [`collect()`][collect] function is useful here.
6+
- Alternatively, splatting works within a vector constructor:
7+
8+
```julia-repl
9+
julia> input = (1, 5, 9)
10+
(1, 5, 9)
11+
12+
julia> [input...]
13+
3-element Vector{Int64}:
14+
1
15+
5
16+
9
17+
```
18+
19+
## 2. Fix the vector of wagons
20+
21+
- It is useful to split the front wagons and the engine from everything else, so that they can be reassembled in the correct order.
22+
- Multiple assignment with splatting may help you.
23+
24+
## 3. Add missing stops
25+
26+
- Only the stop name is needed, not the stop number, so each pair must be split.
27+
- There are multiple ways to assemple the vector of names:
28+
- Array [comprehension][comprehensions]
29+
- [`map()`][map] a function over the vector.
30+
- [Broadcasting][broadcasting] of an [anonymous][anonymous] function is possible:
31+
32+
```julia-repl
33+
julia> (x -> x^4).([1, 2, 3]) == [1, 16, 81]
34+
true
35+
```
36+
37+
## 4. Extend routing information
38+
39+
- After slurping, `more_route_information` is a `Pairs()` collection.
40+
- The [`merge()`][merge] function will combine two or more `Dicts`, but will also accept a `Pairs()` collection.
41+
Explicit conversion to a `Dict` is possible but unnecessary.
42+
43+
44+
[collect]: https://docs.julialang.org/en/v1/base/collections/#Base.collect-Tuple{Any}
45+
[merge]: https://docs.julialang.org/en/v1/base/collections/#Base.merge
46+
[comprehensions]: https://docs.julialang.org/en/v1/manual/arrays/#man-comprehensions
47+
[map]: https://docs.julialang.org/en/v1/base/collections/#Base.map
48+
[broadcasting]: https://docs.julialang.org/en/v1/manual/arrays/#Broadcasting
49+
[anonymous]: https://docs.julialang.org/en/v1/manual/functions/#man-anonymous-functions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Instructions
2+
3+
Your friend Linus is a Locomotive Engineer who drives cargo trains between cities.
4+
Although they are amazing at handling trains, they are not amazing at handling logistics or computers.
5+
They would like to enlist your programming help organizing train details and correcting mistakes in route data.
6+
7+
~~~~exercism/note
8+
This exercise could easily be solved using slicing, indexing, and various `Dict` methods.
9+
However, we would like you to practice packing, unpacking, and multiple assignment in solving each of the tasks below.
10+
~~~~
11+
12+
## 1. Create a vector of all wagons
13+
14+
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 `vector`.
15+
16+
Implement a function `get_vector_of_wagons()` that accepts an arbitrary number of wagon IDs.
17+
Each ID will be a positive integer.
18+
The function should then `return` the given IDs as a single `vector`.
19+
20+
```julia-repl
21+
julia> get_vector_of_wagons(1, 7, 12, 3, 14, 8, 5)
22+
[1, 7, 12, 3, 14, 8, 5]
23+
```
24+
25+
## 2. Fix the vector of wagons
26+
27+
At this point, you are starting to get a feel for the data and how it's used in the logistics program.
28+
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**.
29+
30+
Your friend had to connect two new wagons to the train and forgot to update the system!
31+
Now, the first two wagons in the train `vector` have to be moved to the end, or everything will be out of order.
32+
33+
To make matters more complicated, your friend just uncovered a second `vector` that appears to contain missing wagon IDs.
34+
All they can remember is that once the new wagons are moved, the IDs from this second `vector` should be placed directly after the designated locomotive.
35+
36+
Linus would be really grateful to you for fixing their mistakes and consolidating the data.
37+
38+
Implement a function `fix_vector_of_wagons()` that takes two `vectors` containing wagon IDs.
39+
It should reposition the first two items of the first `vector` to the end, and insert the values from the second `vector` behind (_on the right hand side of_) the locomotive ID (**1**).
40+
The function should then `return` a `vector` with the modifications.
41+
42+
```julia-repl
43+
julia> fix_vector_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15])
44+
[1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5]
45+
```
46+
47+
## 3. Add missing stops
48+
49+
Now that all the wagon data is correct, Linus would like you to update the system's routing information.
50+
Along a transport route, a train might make stops at a few different stations to pick up and/or drop off cargo.
51+
Each journey could have a different number of these intermediary delivery points.
52+
Your friend would like you to update the system's routing `Dict` with any missing/additional delivery information.
53+
54+
Implement a function `add_missing_stops()` that accepts a routing `Dict` followed by a variable number of `stop_number => city` Pairs.
55+
Your function should then return the routing `Dict` updated with an additional `key` that holds a `vector` of all the added stops in order.
56+
57+
```julia-repl
58+
julia> add_missing_stops(Dict("from" => "New York", "to" => "Miami"),
59+
stop_1= > "Washington, DC", stop_2 => "Charlotte", stop_3 => "Atlanta",
60+
stop_4 => "Jacksonville", stop_5 => "Orlando")
61+
62+
Dict("from" => "New York", "to" => "Miami", "stops" => ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"])
63+
```
64+
65+
## 4. Extend routing information
66+
67+
Linus has been working on the routing program and has noticed that certain routes are missing some important details.
68+
Initial route information has been constructed as a `Dict` and your friend would like you to update that `Dict` with whatever might be missing.
69+
Every route in the system requires slightly different details, so Linus would really prefer a generic solution.
70+
71+
Implement a function called `extend_route_information()` that accepts a `Dict` which contains the origin and destination cities the train route runs between, plus a variable number of keyword arguments containing routing details such as train speed, length, or temperature.
72+
The function should return a consolidated `Dict` with all routing information.
73+
74+
```julia-repl
75+
julia> extend_route_information(Dict("from" => "Berlin", "to" => "Hamburg"), :length = "100", :speed = "50")
76+
Dict("from" => "Berlin", "to" => "Hamburg", :length => "100", :speed => "50")
77+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
# Introduction
2+
3+
The [`Basics`][basics] Concept introduced two ways to define a function.
4+
5+
Most generally, the multiline form:
6+
7+
```julia
8+
function muladd(x, y, z)
9+
x * y + z
10+
end
11+
```
12+
13+
The "assignment", or "single-line" form for short definitions:
14+
```julia
15+
muladd(x, y, z) = x * y + z
16+
```
17+
18+
In a third and even shorter form, a short, single-use function can be created without a name:
19+
20+
```julia-repl
21+
julia> map(x -> 2x, 1:3)
22+
4-element Vector{Int64}:
23+
2
24+
4
25+
6
26+
27+
julia> map((x, y) -> x * y, 1:3, 4:6)
28+
3-element Vector{Int64}:
29+
4
30+
10
31+
18
32+
```
33+
34+
In this case, `x -> 2x` is an "anonymous function".
35+
This is equivalent to what some other languages call a "lambda function".
36+
37+
Note that multiple arguments need parentheses, as in `(x, y) -> x * y`.
38+
39+
Anonymous functions are common in Julia code, especially when combined with higher-order functions such as `map()` and `filter()` (which will be covered in more detail in a later concept).
40+
41+
```julia-repl
42+
julia> map(x -> x^4, [1, 2, 3])
43+
3-element Vector{Int64}:
44+
1
45+
16
46+
81
47+
```
48+
49+
## Function arguments
50+
51+
So far in the syllabus, we have only looked at functions which have a precise number of arguments, and require function calls to supply all of them, in the correct order.
52+
This would be limiting and inconvenient, so there are several other options.
53+
54+
### Optional arguments
55+
56+
Like many languages, Julia allows function definitions to supply default values for individual arguments.
57+
58+
Function call can then either supply a value for that argument, or omit it and rely on the default.
59+
60+
```julia-repl
61+
julia> f(x, y=10) = x * y
62+
f (generic function with 2 methods)
63+
64+
julia> f(2, 3)
65+
6
66+
67+
julia> f(2)
68+
20
69+
```
70+
71+
All arguments _without_ defaults must come before any arguments _with_ defaults, meaning that `f(x=2, y)` would be invalid.
72+
73+
### Keyword arguments
74+
75+
All the examples so far use `positional arguments`, where values supplied in a function call must match the order of the corresponding arguments in the function definition.
76+
77+
Like many languages, Julia also allows `keyword arguments`.
78+
Function calls must specify the argument name, but multiple keyword arguments can then be specified in any order.
79+
80+
A distinctive feature of Julia is that the keyword arguments (if any) in the function definition must be preceded by a semicolon `;` to separate them from any positional arguments.
81+
A function call can use either `;` or `,` between the last positional argument and the first keyword argument.
82+
83+
```julia-repl
84+
julia> b(x; y) = x + y
85+
b (generic function with 1 method)
86+
87+
julia> b(2, y=3)
88+
5
89+
90+
# keyword is required when calling
91+
julia> b(2, 3)
92+
ERROR: MethodError: no method matching b(::Int64, ::Int64)
93+
The function `b` exists, but no method is defined for this combination of argument types.
94+
```
95+
96+
Default values can optionally be specified, exactly as for positional arguments.
97+
98+
It is common to end up with syntax like `myarg=myarg` within a function call, when a variable with the same name as the parameter was pre-calculated.
99+
A shorthand syntax is allowed in this situation:
100+
101+
```julia-repl
102+
julia> width = 4.0
103+
4.0
104+
105+
julia> height = √ width
106+
2.0
107+
108+
julia> area(; width, height) = width * height
109+
area (generic function with 1 method)
110+
111+
# repetition
112+
julia> area(; width=width, height=height)
113+
8.0
114+
115+
# shorthand form
116+
julia> area(; width, height)
117+
8.0
118+
```
119+
120+
### Splat and slurp
121+
122+
These are the standard names for a useful aspect of Julia syntax, in case you wondered.
123+
Both refer to the `...` operator.
124+
125+
#### Splat
126+
127+
Splatting is used in function _calls_, to expand collections into individual values required by the function.
128+
129+
This may be easier to demonstrate than to explain:
130+
131+
```julia-repl
132+
julia> fxyz(x, y, z) = x * y * z
133+
fxyz (generic function with 1 method)
134+
135+
julia> xyz = [2, 3, 4]
136+
3-element Vector{Int64}:
137+
2
138+
3
139+
4
140+
141+
# Using the vector directly in a function call is invalid
142+
julia> fxyz(xyz)
143+
ERROR: MethodError: no method matching fxyz(::Vector{Int64})
144+
The function `fxyz` exists, but no method is defined for this combination of argument types.
145+
146+
# splatting converts the vector to 3 numbers, used as positional argumants
147+
julia> fxyz(xyz...)
148+
24
149+
```
150+
151+
Some "function calls" are hidden by syntactic sugar, so splatting can also be used in less obvious ways.
152+
153+
For example, multiple assignment uses a tuple constructor function internally:
154+
155+
```julia-repl
156+
julia> first, rest... = [1, 2, 3, 4]
157+
4-element Vector{Int64}:
158+
1
159+
2
160+
3
161+
4
162+
163+
julia> first
164+
1
165+
166+
julia> rest
167+
3-element Vector{Int64}:
168+
2
169+
3
170+
4
171+
```
172+
173+
Keyword arguments can also be supplied by splatting, typically using a `named tuple`.
174+
A `Dict` will also work, but the keys must be symbols (strings will not work).
175+
176+
```julia-repl
177+
# function with 3 keyword arguments
178+
julia> fabc(; a, b, c) = a + b + c
179+
fabc (generic function with 1 method)
180+
181+
# named tuple
182+
julia> abc_nt = (a=2, b=3, c=4)
183+
(a = 2, b = 3, c = 4)
184+
185+
# there are no positional arguments, so need to use ; before kw argument
186+
julia> fabc(;abc_nt...)
187+
9
188+
189+
# Dict
190+
julia> abc_dict = Dict(:a=>2, :b=>3, :c=>4)
191+
Dict{Symbol, Int64} with 3 entries:
192+
:a => 2
193+
:b => 3
194+
:c => 4
195+
196+
julia> fabc(;abc_dict...)
197+
9
198+
```
199+
200+
#### Slurp
201+
202+
Slurping is used in the function _definition_, to pack an arbitrary number of individual values into a collection.
203+
204+
```julia-repl
205+
julia> f_more(i, j, more...) = i + j + sum(more)
206+
f_more (generic function with 1 method)
207+
208+
julia> f_more(1, 3, 5, 7, 9, 11)
209+
36
210+
```
211+
212+
The name of the slurped argument (in this case `more`) is not significant.
213+
The type of this variable is chosen by the compiler, but for positional arguments is likely to be `tuple` or something similar.
214+
215+
Keyword arguments can also be slurped, giving a `Dict` (or similar).
216+
217+
```julia-repl
218+
julia> f_kwslurp(x, y; switches...) = :mult in keys(switches) ? x * y : x + y
219+
f_kwslurp (generic function with 1 method)
220+
221+
julia> f_kwslurp(5, 6; mult=true)
222+
30
223+
224+
julia> f_kwslurp(5, 6)
225+
11
226+
```
227+
228+
Any keyword arguments can be used in the call.
229+
It is for the function definition to decide which keywords to respond to and which to ignore.
230+
231+
232+
[basics]: https://exercism.org/tracks/julia/concepts/basics

0 commit comments

Comments
 (0)