Skip to content

Commit 06bec00

Browse files
authored
Merge branch 'exercism:main' into statistics-draft
2 parents 86277c6 + 11dab2b commit 06bec00

File tree

374 files changed

+14149
-857
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

374 files changed

+14149
-857
lines changed

.github/workflows/auto-rebase.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
99
runs-on: ubuntu-22.04
1010
steps:
11-
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
11+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
1212
- name: Automatic Rebase
1313
uses: cirrus-actions/rebase@b87d48154a87a85666003575337e27b8cd65f691
1414
env:

.github/workflows/exercise-tests.yml

+6-9
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,17 @@ jobs:
2222
strategy:
2323
fail-fast: false
2424
matrix:
25-
julia-version: ["1.6", "1", nightly]
25+
julia-version: ["1.10", "1.11", nightly]
2626
os: [ubuntu-22.04, windows-2022, macos-14]
27-
exclude:
28-
- julia-version: 1.6
29-
os: macos-14
3027

3128
steps:
32-
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
29+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
3330

34-
- uses: julia-actions/setup-julia@780022b48dfc0c2c6b94cfee6a9284850107d037
31+
- uses: julia-actions/setup-julia@5c9647d97b78a5debe5164e9eec09d653d29bd71
3532
with:
3633
version: ${{ matrix.julia-version }}
3734

38-
- uses: julia-actions/cache@8608484607cba0bdb5437a7bad85d6502499d029
35+
- uses: julia-actions/cache@d10a6fd8f31b12404a54613ebad242900567f2b9
3936

4037
- name: Install test dependencies
4138
run: julia --color=yes --project -e "using Pkg; Pkg.instantiate()"
@@ -48,7 +45,7 @@ jobs:
4845
runs-on: ubuntu-22.04
4946

5047
steps:
51-
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
48+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
5249

5350
- name: Pull julia-test-runner image
5451
run: docker pull exercism/julia-test-runner
@@ -61,7 +58,7 @@ jobs:
6158
run: julia --color=yes --project runtestrunner.jl
6259

6360
- name: Upload reports as artifact
64-
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874
61+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
6562
with:
6663
name: test-reports
6764
path: ${{ steps.generate-reports.outputs.results-path }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"authors": [
3+
"colinleach"
4+
],
5+
"contributors": [],
6+
"blurb": "Julia provides many ways to operate on an array, either as a unit or element-wise."
7+
}
+257
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# About
2+
3+
In the [`Arrays`][arrays] Concept, we said that "arrays are at the heart of the Julia language".
4+
5+
Given this, we could reasonably hope that the language provides many versatile and powerful ways to _do things_ with arrays, whatever that means.
6+
7+
A note on terminology: though this document talks a lot about "arrays", much of it also applies to any iterable type: [ranges][ranges], [tuples][tuples], [sets][sets], and various others.
8+
9+
## Functions expecting array input
10+
11+
Some very simple functions take an array input and (for 1-D input) return a scalar output.
12+
13+
```julia
14+
v = [2, 3, 4]
15+
length(v) # => 3
16+
sum(v) # => 9
17+
```
18+
19+
When we reach the Concept on multidimensional arrays, it will become clearer that this is _dimension reduction_ rather than necessarily returning a scalar.
20+
If that makes no sense to you, skip worrying about it for now.
21+
22+
There are many more functions of this type.
23+
See the [`Statistics`][statistics] Concept for some examples.
24+
25+
There are also functions that operate on multiple arrays, such as the (very useful) [`zip`][zip].
26+
27+
```julia-repl
28+
julia> z = zip( 1:3, ['a', 'b', 'c'], ["I", "make", "tuples"] )
29+
zip([1, 2, 3], ['a', 'b', 'c'], ["I", "make", "tuples"])
30+
31+
# convert iterator to vector
32+
julia> collect(z)
33+
3-element Vector{Tuple{Int64, Char, String}}:
34+
(1, 'a', "I")
35+
(2, 'b', "make")
36+
(3, 'c', "tuples")
37+
```
38+
39+
`zip()` takes an arbitrary number of vector-like inputs and returns an iterator of tuples.
40+
41+
The inputs are usually all the same length.
42+
If one is shorter, the others are truncated to the shortest length: _maybe_ what you intended, but _more commonly_ a bug in your code.
43+
44+
## Arithmetic
45+
46+
Suppose you have a numerical vector and want to subtract 0.5 from each value.
47+
48+
```julia-repl
49+
julia> v = [1.2, 1.5, 1.7]
50+
3-element Vector{Float64}:
51+
1.2
52+
1.5
53+
1.7
54+
55+
julia> v - 0.5
56+
ERROR: MethodError: no method matching -(::Vector{Float64}, ::Float64)
57+
```
58+
59+
That fails, so what about subtracting another vector?
60+
61+
```julia-repl
62+
julia> v - [0.5, 0.5, 0.5]
63+
3-element Vector{Float64}:
64+
0.7
65+
1.0
66+
1.2
67+
```
68+
69+
Successful, but quite tedious and memory-hungry as the arrays get longer.
70+
71+
Depending on how far you have reached in the syllabus, you can probably think of other approaches:
72+
73+
- Write a loop, though this would be verbose and clunky.
74+
- Use a comprehension: `[x - 0.5 for x in v]` gives the desired result (Python-style).
75+
- Use a higher-order function: `map(x -> x - 0.5, v)` also works (Haskell-style, though common in many languages).
76+
77+
Fortunately, Julia has a "magic" dot to solve this problem very simply: `v .- 0.5` is all you need.
78+
79+
The next section explains why.
80+
81+
## [Broadcasting][broadcasting]
82+
83+
So, `v - 0.5` fails but `v .- 0.5` succeeds, and we need to understand what the dot is doing.
84+
85+
Two things, which combine to give the desired result.
86+
87+
### 1) Element-wise application
88+
89+
Firstly, adding a dot before any infix operator means "apply this operation to each element separately".
90+
91+
Similarly, adding a dot _after_ a function name "vectorizes" it, even if the function was written for scalar inputs.
92+
93+
```julia-repl
94+
julia> sqrt.([1, 4, 9])
95+
3-element Vector{Float64}:
96+
1.0
97+
2.0
98+
3.0
99+
```
100+
101+
As an aside, infix operators are really just syntactic sugar for the underlying function.
102+
103+
This means that, for example, `[1, 5, 10] .% 3` is translated to ` mod.([1, 5, 10], 3)` by the interpreter, and the `mod.` syntax then executes (both versions return `[1, 2, 1]`).
104+
105+
### 2) Singleton expansion
106+
107+
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.
108+
109+
```julia-repl
110+
julia> v .- [0.5, 0.5, 0.5]
111+
3-element Vector{Float64}:
112+
0.7
113+
1.0
114+
1.2
115+
```
116+
117+
What about arrays of unequal length?
118+
119+
```julia-repl
120+
julia> v .- [0.5, 0.5]
121+
ERROR: DimensionMismatch: arrays could not be broadcast to a common size
122+
123+
julia> v .- [0.5,]
124+
3-element Vector{Float64}:
125+
0.7
126+
1.0
127+
1.2
128+
```
129+
130+
In general, unequal lengths are an error, _except_ when one has length 1 (technically, a "singleton" dimension).
131+
132+
Singletons like `[0.5,]` or just `0.5` are automatically expanded to the necessary length by repetition.
133+
This is at the heart of `broadcasting`.
134+
135+
Anyone worrying about memory usage from this "repetition" can relax: it is implemented in a very efficient way that does not actually copy the values in memory.
136+
137+
Programmers familiar with broadcasting in other languages should note that Julia's approach is (mostly) similar to NumPy, but much less tolerant of size mismatches than R.
138+
139+
### Un-dotted operators: a cautionary tale
140+
141+
This subsection is rather math-heavy, so most students are not expected to really understand it.
142+
However, it is a useful warning that may help with debugging when you see unexpected error messages.
143+
144+
```julia-repl
145+
julia> v = [1, 2, 3]
146+
3-element Vector{Int64}:
147+
1
148+
2
149+
3
150+
151+
julia> v * v
152+
ERROR: MethodError: no method matching *(::Vector{Int64}, ::Vector{Int64})
153+
154+
# look, no commas
155+
julia> u = [1 2 3]
156+
1×3 Matrix{Int64}:
157+
1 2 3
158+
159+
julia> u * v
160+
1-element Vector{Int64}:
161+
14
162+
163+
julia> v * u
164+
3×3 Matrix{Int64}:
165+
1 2 3
166+
2 4 6
167+
3 6 9
168+
```
169+
170+
If you happen to have a background in linear algebra then (1) you are not a typical Exercism user _(but very welcome here!)_ and (2) you may recognize that `v` is a column vector, `u` is a row vector, `u * v` is the inner product and `v * u` is the outer product.
171+
_Julia follows the rules of mathematics, in this as in everything_.
172+
173+
**For everyone else:** please just understand why we recommend you should always use dotted operators for element-wise calculations: `v .* v` works exactly as you might expect, to give `[1, 4, 9]`.
174+
175+
## Indexing
176+
177+
Selecting elements of an array by index number has been discussed in previous Concepts.
178+
179+
```julia
180+
a = collect('A':'Z') # => 26-element Vector{Char}
181+
182+
# index with an integer
183+
a[2] # => 'B'
184+
185+
# index with a range
186+
a[12:2:18] # => ['L', 'N', 'P, 'R']
187+
188+
# index with another array
189+
a[ [1, 3, 5] ] # => ['A', 'C', 'E']
190+
```
191+
192+
### Logical indexing
193+
194+
It is also possible to select elements that satisfy some logical expression (technically, a "predicate").
195+
This usually requires broadcasting.
196+
197+
```julia-repl
198+
julia> a[a .< 'D']
199+
3-element Vector{Char}:
200+
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
201+
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
202+
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
203+
```
204+
205+
For more complex expression the dots tend to proliferate (but they are small and easy to type).
206+
207+
```julia-repl
208+
julia> a[a .< 'D' .|| a .> 'W']
209+
6-element Vector{Char}:
210+
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
211+
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
212+
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
213+
'X': ASCII/Unicode U+0058 (category Lu: Letter, uppercase)
214+
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
215+
'Z': ASCII/Unicode U+005A (category Lu: Letter, uppercase)
216+
```
217+
218+
A reminder that the "array" can in fact be any appropriate ordered iterable, such as a range:
219+
220+
```julia-repl
221+
julia> n = 3:10
222+
3:10
223+
224+
julia> n[isodd.(n)]
225+
4-element Vector{Int64}:
226+
3
227+
5
228+
7
229+
9
230+
```
231+
232+
Internally, the predicate is converted to a [`BitVector`][bitarray] which is then used as an index.
233+
234+
```julia-repl
235+
julia> condition = a .< 'D'
236+
26-element BitVector:
237+
1
238+
1
239+
1
240+
0
241+
# display truncated
242+
243+
julia> a[condition]
244+
3-element Vector{Char}:
245+
'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
246+
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
247+
'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
248+
```
249+
250+
[arrays]: https://exercism.org/tracks/julia/concepts/arrays
251+
[ranges]: https://exercism.org/tracks/julia/concepts/ranges
252+
[sets]: https://exercism.org/tracks/julia/concepts/sets
253+
[tuples]: https://exercism.org/tracks/julia/concepts/tuples
254+
[statistics]: https://exercism.org/tracks/julia/concepts/statistics
255+
[zip]: https://docs.julialang.org/en/v1/base/iterators/#Base.Iterators.zip
256+
[bitarray]: https://docs.julialang.org/en/v1/base/arrays/#Base.BitArray
257+
[broadcasting]: https://docs.julialang.org/en/v1/manual/arrays/#Broadcasting

0 commit comments

Comments
 (0)