english improved

This commit is contained in:
2026-03-05 20:09:16 +01:00
parent c6609d15f5
commit 733fe8c290
21 changed files with 954 additions and 1042 deletions

View File

@@ -5,7 +5,7 @@ engine: julia
# Containers
Julia offers a wide selection of container types with largely similar interfaces.
We introduce `Tuple`, `Range`, and `Dict` here, and in the next chapter we will cover `Array`, `Vector`, and `Matrix`.
This chapter introduces `Tuple`, `Range`, and `Dict`; the next chapter covers `Array`, `Vector`, and `Matrix`.
These containers are:
@@ -24,14 +24,14 @@ and some are also
Furthermore, there are several common functions, e.g.,
- `length(container)` --- number of elements
- `eltype(container)` --- type of elements
- `isempty(container)` --- test whether container is empty
- `empty!(container)` --- empties container (only if mutable)
- `eltype(container)` --- element type
- `isempty(container)` --- test if container is empty
- `empty!(container)` --- empties the container (if mutable)
## Tuples
A tuple is an immutable container of elements. It is therefore not possible to add new elements or change the value of an element.
A tuple is an immutable container of elements. You cannot add new elements or change existing values.
@@ -54,13 +54,13 @@ Tuples are frequently used as function return values to return more than one obj
```{julia}
# Integer division and remainder:
# quotient and remainder are assigned to variables q and r
# Assign quotient and remainder to variables `q` and `r`:
q, r = divrem(71, 6)
@show q r;
```
As you can see here, parentheses can be omitted in certain constructs.
This *implicit tuple packing/unpacking* is also commonly used in multiple assignments:
Parentheses can be omitted in certain constructs.
This *implicit tuple packing/unpacking* is commonly used in multiple assignments:
```{julia}
@@ -72,9 +72,7 @@ x, y, z = 12, 17, 203
y
```
Some functions require tuples as arguments or always return tuples. Then you sometimes need a tuple with a single element.
This is written as:
Some functions require tuples as arguments or always return tuples. A single-element tuple is written as:
```{julia}
x = (13,) # a 1-element tuple
@@ -95,7 +93,7 @@ We have already used *range* objects in numerical `for` loops.
r = 1:1000
typeof(r)
```
There are various *range* types. As you can see, they are parameterized types based on the numeric type, and `UnitRange` is, for example, a *range* with step size 1. Their constructors are usually named `range()`.
There are various *range* types. `UnitRange`, for example, is a *range* with step size 1. Their constructors are typically all named `range()`.
The colon is a special syntax.
@@ -104,7 +102,7 @@ The colon is a special syntax.
*Ranges* are obviously iterable, not mutable, but indexable.
*Ranges* are iterable, immutable, and indexable.
```{julia}
(3:100)[20] # the 20th element
@@ -113,14 +111,11 @@ The colon is a special syntax.
Recall the semantics of the `for` loop: `for i in 1:1000` means **not**:
Recall the semantics of the `for` loop: `for i in 1:1000` does **not** mean 'increment the loop variable `i` by one each iteration'; **rather**, it means 'successively assign the values 1, 2, 3, ..., 1000 to the loop variable from the container'.
- 'The loop variable `i` is incremented by one in each iteration' **but rather**
- 'The loop variable is successively assigned the values 1,2,3,...,1000 from the container'.
Creating this container explicitly would be very inefficient.
However, it would be very inefficient to actually create this container explicitly.
- _Ranges_ are "lazy" vectors that are never really stored as a concrete list anywhere. This makes them so useful as iterators in `for` loops: memory-efficient and fast.
- _Ranges_ are "lazy" vectors never stored as concrete lists. This makes them ideal as `for` loop iterators: memory-efficient and fast.
- They are "recipes" or generators that respond to the query "Give me your next element!".
- In fact, the supertype `AbstractRange` is a subtype of `AbstractVector`.
@@ -138,7 +133,7 @@ The macro `@allocated` outputs how many bytes of memory were allocated during th
The function `collect()` is used to convert to a "real" vector.
The `collect()` function converts a range to a concrete vector.
```{julia}
@@ -150,186 +145,183 @@ Quite useful, e.g., when preparing data for plotting, is the *range* type `LinRa
```{julia}
LinRange(2, 50, 300)
```
`LinRange(start, stop, n)` generates an equidistant list of `n` values where the first and last are the specified limits.
With `collect()` you can also obtain the corresponding vector if needed.
`LinRange(start, stop, n)` generates `n` equidistant values from start to stop. Use `collect()` to obtain the corresponding vector if needed.
## Dictionaries
- _Dictionaries_ (German: "associative list" or "lookup table" or ...) are special containers.
- Entries in a vector `v` are addressable by an index 1,2,3....: `v[i]`
- Entries in a _dictionary_ are addressable by more general _keys_.
- A _dictionary_ is a collection of _key-value_ pairs.
- Thus, _dictionaries_ in Julia have the parameterized type `Dict{S,T}`, where `S` is the type of _keys_ and `T` is the type of _values_.
- _Dictionaries_ (also known as associative arrays or lookup tables) are special containers.
- Whereas vector entries are addressed by integer indices: `v[i]`; dictionary entries are addressed by more general _keys_.
- A dictionary is a collection of _key-value_ pairs with parameterized type `Dict{S,T}`, where `S` is the key type and `T` is the value type.
They can be created explicitly:
Create a dictionary explicitly:
```{julia}
# Population in 2020 in millions, source: wikipedia
EW = Dict("Berlin" => 3.66, "Hamburg" => 1.85,
Ppl = Dict("Berlin" => 3.66, "Hamburg" => 1.85,
"München" => 1.49, "Köln" => 1.08)
```
```{julia}
typeof(EW)
typeof(Ppl)
```
and indexed with the _keys_:
```{julia}
EW["Berlin"]
Ppl["Berlin"]
```
Of course, querying a non-existent _key_ is an error.
Querying a non-existent _key_ throws an error.
```{julia}
EW["Leipzig"]
Ppl["Leipzig"]
```
You can also ask beforehand...
Check beforehand with `haskey()`...
```{julia}
haskey(EW, "Leipzig")
haskey(Ppl, "Leipzig")
```
... or use the function `get(dict, key, default)`, which does not throw an error for non-existent keys but returns the third argument.
Or use `get(dict, key, default)`, which returns the default value instead of throwing an error.
```{julia}
@show get(EW, "Leipzig", -1) get(EW, "Berlin", -1);
@show get(Ppl, "Leipzig", -1) get(Ppl, "Berlin", -1);
```
You can also request all `keys` and `values` as special containers.
```{julia}
keys(EW)
keys(Ppl)
```
```{julia}
values(EW)
values(Ppl)
```
You can iterate over the `keys`...
Iterate over the `keys`...
```{julia}
for i in keys(EW)
n = EW[i]
for i in keys(Ppl)
n = Ppl[i]
println("The city $i has $n million inhabitants.")
end
```
or directly over `key-value` pairs.
Or iterate directly over `key-value` pairs.
```{julia}
for (stadt, ew) ∈ EW
println("$stadt : $ew Million.")
for (city, pop) ∈ Ppl
println("$city : $pop Million.")
end
```
### Extending and Modifying
You can add additional `key-value` pairs to a `Dict`...
Add `key-value` pairs to a `Dict`...
```{julia}
EW["Leipzig"] = 0.52
EW["Dresden"] = 0.52
EW
Ppl["Leipzig"] = 0.52
Ppl["Dresden"] = 0.52
Ppl
```
and change a `value`.
Change a `value`:
```{julia}
# Oh, the Leipzig number was from 2010, not 2020
# Update: Leipzig data was from 2010, not 2020
EW["Leipzig"] = 0.597
EW
Ppl["Leipzig"] = 0.597
Ppl
```
A pair can also be deleted via its `key`.
Delete a pair by its `key`:
```{julia}
delete!(EW, "Dresden")
delete!(Ppl, "Dresden")
```
Many functions can work with `Dicts` like with other containers.
Many functions work with `Dicts` like other containers.
```{julia}
maximum(values(EW))
maximum(values(Ppl))
```
### Creating an Empty Dictionary
Without type specification ...
Without explicit types:
```{julia}
d1 = Dict()
```
and with type specification:
With explicit types:
```{julia}
d2 = Dict{String, Int}()
```
### Conversion to Vectors: `collect()`
- `keys(dict)` and `values(dict)` are special data types.
- The function `collect()` converts them to a `Vector` type.
- `collect(dict)` returns a list of type `Vector{Pair{S,T}}`
- `keys(dict)` and `values(dict)` return special container types.
- `collect()` converts them to `Vector`s.
- `collect(dict)` returns a `Vector{Pair{S,T}}`.
```{julia}
collect(EW)
collect(Ppl)
```
```{julia}
collect(keys(EW)), collect(values(EW))
collect(keys(Ppl)), collect(values(Ppl))
```
### Ordered Iteration over a Dictionary
We sort the keys. As strings, they are sorted alphabetically. With the `rev` parameter, sorting is done in reverse order.
```{julia}
for k in sort(collect(keys(EW)), rev = true)
n = EW[k]
for k in sort(collect(keys(Ppl)), rev = true)
n = Ppl[k]
println("$k has $n million inhabitants ")
end
```
We sort `collect(dict)`. This is a vector of pairs. With `by` we define what to sort by: the second element of the pair.
Let's sort `collect(dict)`, a vector of pairs. Use `by` to specify the sort key: the second element of each pair.
```{julia}
for (k,v) in sort(collect(EW), by = pair -> last(pair), rev=false)
for (k,v) in sort(collect(Ppl), by = pair -> last(pair), rev=false)
println("$k has $v million inhabitants")
end
```
### An Application of Dictionaries: Counting Frequencies
We do "experimental probability" with 2 dice:
Let's do "experimental stochastics" with 2 dice:
Given `l`, a list with the results of 100,000 double dice rolls, i.e., 100,000 numbers between 2 and 12.
Let `l` be a vector containing 100,000 sums of two dice rolls (numbers from 2 to 12).
How frequently do the numbers 2 to 12 occur?
How frequently does each number from 2 to 12 occur?
We (let) roll:
Roll the dice:
```{julia}
l = rand(1:6, 100_000) .+ rand(1:6, 100_000)
```
We count the frequencies of the events using a dictionary. We take the event as the `key` and its frequency as the `value`.
Count event frequencies using a dictionary. Use the event as the `key` and its frequency as the `value`.
```{julia}
# In this case, one could also solve this with a simple vector.
# A better illustration would be, e.g., word frequency in
# a text. Then i is not an integer but a word=string
# In this case, a simple vector would also work.
# A better use case for dictionaries is word frequency in texts,
# where keys are strings instead of integers.
d = Dict{Int,Int}() # the dict for counting
d = Dict{Int,Int}() # dictionary for counting
for i in l # for each i, d[i] is incremented.
for i in l # for each i, increment d[i]
d[i] = get(d, i, 0) + 1
end
d
```
The result:
Result:
```{julia}
using Plots
@@ -337,6 +329,6 @@ using Plots
plot(collect(keys(d)), collect(values(d)), seriestype=:scatter)
```
##### The explanatory image:
Explanatory image:
[https://math.stackexchange.com/questions/1204396/why-is-the-sum-of-the-rolls-of-two-dices-a-binomial-distribution-what-is-define](https://math.stackexchange.com/questions/1204396/why-is-the-sum-of-the-rolls-of-two-dices-a-binomial-distribution-what-is-define)