Skip to content

Commit 2fddc90

Browse files
colinleachColin Leachdepial
authored
bird-watcher concept exercise (exercism#838)
* `bird-watcher` concept exercise * Update introduction.md Clear up two typos * Update instructions.md added demo for `average_per_day()` * Update instructions.md --------- Co-authored-by: Colin Leach <[email protected]> Co-authored-by: depial <[email protected]>
1 parent 82fb806 commit 2fddc90

File tree

9 files changed

+449
-2
lines changed

9 files changed

+449
-2
lines changed

config.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
"name": "Lasagna",
4040
"uuid": "39ebdd04-4f84-4817-bf9d-f1f9e066c283",
4141
"concepts": [
42-
"basics"
42+
"functions",
43+
"integer-introduction",
44+
"comments"
4345
],
4446
"prerequisites": [],
4547
"status": "wip"
@@ -113,10 +115,22 @@
113115
"function-composition"
114116
],
115117
"prerequisites": [
116-
"functions",
118+
"functions",
117119
"strings"
118120
],
119121
"status": "wip"
122+
},
123+
{
124+
"slug": "bird-watcher",
125+
"name": "bird-watcher",
126+
"uuid": "6e6968db-e320-4c73-b692-9f58248d20a3",
127+
"concepts": [
128+
"vector-operations"
129+
],
130+
"prerequisites": [
131+
"ranges"
132+
],
133+
"status": "wip"
120134
}
121135
],
122136
"practice": [
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Hints
2+
3+
## 1. Check how many birds visited today
4+
5+
- Todays's count is the last entry.
6+
7+
## 2. Increment today's count
8+
9+
- Modify the last entry in-place.
10+
- Julia has a `+=` operator but not `++`.
11+
- You must return the modified vector.
12+
13+
## 3. Check if there was a day with no visiting birds
14+
15+
- The `.==` operator tests for equality element-wise.
16+
- The `any()` function may be useful.
17+
18+
19+
## 4. Calculate the number of visiting birds for the first number of days
20+
21+
- Index with a range to get a subset of a vector.
22+
- The `sum()` function takes vector input and returns a single number.
23+
24+
25+
## 5. Calculate the number of busy days
26+
27+
- Logical indexing will give a subset of a vector, with entries matching the given condition.
28+
- The condition must operate element-wise.
29+
- Find the length of a vector with `length()`.
30+
31+
32+
## 6. Calculate averages by day of the week
33+
34+
- Arithmetic (infix) operators can have a dot in front of the operator to convert them to element-wise operation.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Instructions
2+
3+
You're an avid bird watcher that keeps track of how many birds have visited your garden in the last seven days.
4+
5+
You have six tasks, all dealing with the numbers of birds that visited your garden.
6+
7+
## 1. Check how many birds visited today
8+
9+
Implement the `today()` function to return how many birds visited your garden today.
10+
The bird counts are ordered by day, with the first element being the count of the oldest day, and the last element being today's count.
11+
12+
```julia-repl
13+
birds_per_day = [2, 5, 0, 7, 4, 1]
14+
today(birds_per_day)
15+
# => 1
16+
```
17+
18+
## 2. Increment today's count
19+
20+
Implement the `increment_todays_count()` function to increment today's count:
21+
22+
```julia-repl
23+
birds_per_day = [2, 5, 0, 7, 4, 1]
24+
increment_todays_count(birds_per_day)
25+
# => [2, 5, 0, 7, 4, 2]
26+
```
27+
28+
## 3. Check if there was a day with no visiting birds
29+
30+
Implement the `has_day_without_birds()` function that returns `TRUE` if there was a day at which zero birds visited the garden; otherwise, return `FALSE`:
31+
32+
```julia-repl
33+
birds_per_day = [2, 5, 0, 7, 4, 1]
34+
has_day_without_birds(birds_per_day)
35+
# => true
36+
```
37+
38+
## 4. Calculate the number of visiting birds for the first number of days
39+
40+
Implement the `count_for_first_days()` function that returns the number of birds that have visited your garden from the start of the week, but limit the count to the specified number of days from the start of the week.
41+
42+
```julia-repl
43+
birds_per_day = [2, 5, 0, 7, 4, 1]
44+
count_for_first_days(birds_per_day, 4)
45+
# => 14
46+
```
47+
48+
## 5. Calculate the number of busy days
49+
50+
Some days are busier that others.
51+
A busy day is one where five or more birds have visited your garden.
52+
Implement the `busy_days()` function to return the number of busy days:
53+
54+
```julia-repl
55+
birds_per_day = [2, 5, 0, 7, 4, 1]
56+
busy_days(birds_per_day)
57+
# => 2
58+
```
59+
60+
## 6. Calculate averages by day of the week
61+
62+
You decide to extend your records by keeping counts for multiple weeks.
63+
In each case, the counts are arranged by day of the week, from Monday as the first entry to Sunday as the last.
64+
65+
Implement the `average_per_day()` function that returns the average for 2 weeks.
66+
67+
```julia-repl
68+
week1 = [7, 2, 9, 1, 3, 0, 10]
69+
week2 = [2, 6, 4, 1, 3, 8, 9]
70+
average_per_day(week1, week2)
71+
# => [4.5, 4.0, 6.5, 1.0, 4.0, 3.0, 9.5]
72+
```
73+
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Introduction
2+
3+
In the [`Vectors`][vectors] Concept, we said that "arrays are at the heart of the Julia language" and a vector is a 1-dimensional.
4+
5+
Given this, we could reasonably hope that the language provides many versatile and powerful ways to _do things_ with vectors, whatever that means.
6+
7+
## Functions expecting vector input
8+
9+
Some very simple functions take a vector input and return a scalar output.
10+
11+
```julia
12+
v = [2, 3, 4]
13+
length(v) # => 3
14+
sum(v) # => 9
15+
```
16+
17+
## Arithmetic
18+
19+
Suppose you have a numerical vector and want to subtract 0.5 from each value.
20+
21+
```julia-repl
22+
julia> v = [1.2, 1.5, 1.7]
23+
3-element Vector{Float64}:
24+
1.2
25+
1.5
26+
1.7
27+
28+
julia> v - 0.5
29+
ERROR: MethodError: no method matching -(::Vector{Float64}, ::Float64)
30+
```
31+
32+
That fails, so what about subtracting another vector?
33+
34+
```julia-repl
35+
julia> v - [0.5, 0.5, 0.5]
36+
3-element Vector{Float64}:
37+
0.7
38+
1.0
39+
1.2
40+
```
41+
42+
Successful, but quite tedious and memory-hungry as the vectors get longer.
43+
44+
Fortunately, Julia has a "magic" dot to solve this problem very simply: `v .- 0.5` is all you need.
45+
46+
The next section explains why.
47+
48+
## Broadcasting
49+
50+
So, `v - 0.5` fails but `v .- 0.5` succeeds, and we need to understand what the dot is doing.
51+
52+
Two things, which combine to give the desired result.
53+
54+
### 1. Element-wise application
55+
56+
Firstly, adding a dot before any infix operator means "apply this operation to each element separately".
57+
58+
Similarly, adding a dot _after_ a function name "vectorizes" it, even if the function was written for scalar inputs.
59+
60+
```julia-repl
61+
julia> sqrt.([1, 4, 9])
62+
3-element Vector{Float64}:
63+
1.0
64+
2.0
65+
3.0
66+
```
67+
68+
### 2. Singleton expansion
69+
70+
We saw in a previous example that we can subtract vectors of equal length, though please understand that `.-` is a _safer_ operator than `-` by making the element-wise intention clear.
71+
72+
```julia-repl
73+
julia> v .- [0.5, 0.5, 0.5]
74+
3-element Vector{Float64}:
75+
0.7
76+
1.0
77+
1.2
78+
```
79+
80+
What about vectors of unequal length?
81+
82+
```julia-repl
83+
julia> v .- [0.5, 0.5]
84+
ERROR: DimensionMismatch: arrays could not be broadcast to a common size
85+
86+
julia> v .- [0.5,]
87+
3-element Vector{Float64}:
88+
0.7
89+
1.0
90+
1.2
91+
```
92+
93+
In general, unequal lengths are an error, _except_ when one has length 1 (technically, a "singleton" dimension).
94+
95+
Singletons like `[0.5,]` or just `0.5` are automatically expanded to the necessary length by repetition.
96+
This is at the heart of `broadcasting`.
97+
98+
## Indexing
99+
100+
Selecting elements of a vector by index number has been discussed in previous Concepts.
101+
102+
```julia
103+
a = collect('A':'Z') # => 26-element Vector{Char}
104+
105+
# index with an integer
106+
a[2] # => 'B'
107+
108+
# index with a range
109+
a[12:2:18] # => ['L', 'N', 'P, 'R']
110+
111+
# index with another vector
112+
a[ [1, 3, 5] ] # => ['A', 'C', 'E']
113+
```
114+
115+
### Logical indexing
116+
117+
It is also possible to select elements that satisfy some logical expression (technically, a "predicate").
118+
This usually requires broadcasting.
119+
120+
```julia-repl
121+
julia> a[a .< 'D']
122+
3-element Vector{Char}:
123+
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
124+
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
125+
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
126+
```
127+
128+
For more complex expression the dots tend to proliferate (but they are small and easy to type).
129+
130+
```julia-repl
131+
julia> a[a .< 'D' .|| a .> 'W']
132+
6-element Vector{Char}:
133+
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
134+
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
135+
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
136+
'X': ASCII/Unicode U+0058 (category Lu: Letter, uppercase)
137+
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
138+
'Z': ASCII/Unicode U+005A (category Lu: Letter, uppercase)
139+
```
140+
141+
A reminder that the "vector" can in fact be any appropriate ordered iterable, such as a range:
142+
143+
```julia-repl
144+
julia> n = 3:10
145+
3:10
146+
147+
julia> n[isodd.(n)]
148+
4-element Vector{Int64}:
149+
3
150+
5
151+
7
152+
9
153+
```
154+
155+
[vectors]: https://exercism.org/tracks/julia/concepts/vectors
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"authors": [
3+
"colinleach"
4+
],
5+
"files": {
6+
"solution": [
7+
"bird-watcher.jl"
8+
],
9+
"test": [
10+
"runtests.jl"
11+
],
12+
"exemplar": [
13+
".meta/exemplar.jl"
14+
]
15+
},
16+
"forked_from": [
17+
"Csharp/bird-watcher"
18+
],
19+
"blurb": "Learn about Julia vector operations by keeping track of how many birds visit your garden."
20+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Design
2+
3+
## Goal
4+
5+
The goal of this exercise is to build on the `vectors` concept by teaching the student more about vector operations in Julia.
6+
7+
## Learning objectives
8+
9+
- Understand the role of broadcasting in operating on vectors.
10+
- Practice the use of conditional expressions to get a subset of the original vector.
11+
12+
## Out of scope
13+
14+
These will all have separate Concepts:
15+
16+
- `function-composition`
17+
- `higher-order-functions`
18+
- `multiple-dispatch`
19+
20+
## Concepts
21+
22+
The Concepts this exercise unlocks are:
23+
24+
- `vector-operations`
25+
26+
## Prerequisites
27+
28+
- `ranges`
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
today(birds_per_day) = birds_per_day[end]
2+
3+
function increment_todays_count(birds_per_day)
4+
birds_per_day[end] += 1
5+
birds_per_day
6+
end
7+
8+
has_day_without_birds(birds_per_day) = any(birds_per_day .== 0)
9+
10+
count_for_first_days(birds_per_day, num_days) = sum(birds_per_day[1:num_days])
11+
12+
busy_days(birds_per_day) = length(birds_per_day[birds_per_day .>= 5])
13+
14+
average_per_day(week1, week2) = (week1 .+ week2) ./ 2
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
function today(birds_per_day)
2+
3+
end
4+
5+
function increment_todays_count(birds_per_day)
6+
7+
end
8+
9+
function has_day_without_birds(birds_per_day)
10+
11+
end
12+
13+
function count_for_first_days(birds_per_day, num_days)
14+
15+
end
16+
17+
function busy_days(birds_per_day)
18+
19+
end
20+
21+
function average_per_day(week1, week2)
22+
23+
end

0 commit comments

Comments
 (0)