### A Pluto.jl notebook ###
# v0.18.4

using Markdown
using InteractiveUtils

# ╔═╡ 49a44e94-29c0-4004-8d0b-e37008eafc1c
using BenchmarkTools, PlutoUI, Wordlegames

# ╔═╡ f9816eb0-078e-41f2-bf7c-29ed5c663fd0
# hideall
title = "Scoring guesses in Wordle";

# ╔═╡ a9678ae8-48c0-4bb9-920d-7de176105bd0
"""
+++
title = "$title"
+++
""" |> Base.Text

# ╔═╡ 4eabf46e-f75c-4b15-8583-6abe17c0fd85
md"""
# $title

[Wordle](https://en.wikipedia.org/wiki/Wordle) is a recently developed, extremely popular word game that has already spawned many imitators such as [Primel](https://converged.yt/primel/).

These tutorials illustrate some [Julia](https://julialang.org) programming concepts using functions in the [Wordlegames](https://github.com/dmbates/Wordlegames.jl) package for illustration.
Part of the purpose is to illustrate the unique nature of Julia as a dynamically-typed language with a just-in-time (JIT) compiler.
It allows you to write "generic", both in the common meaning of "general purpose" and in the technical meaning of generic functions, and performative code.

This posting originated from a conversation on the Julia [discourse channel](https://discourse.julialang.org/t/rust-julia-comparison-post/75403) referring to a case where Julia code to perform a certain Wordle-related task - determine the "best" initial guess in a Wordle game - was horribly slow.
Julia code described in a [Hacker News](https://news.ycombinator.com/) posting took several hours to do this.

In situations like this the Julia community inevitably responds with suggested modifications to make the code run faster.
Someone joked that we wouldn't be satisfied until we could do that task in less than 1 second, and we did.

The code in these postings can be used to solve a Wordle game very rapidly, as well as related games like Primel.

Before beginning we attach some packages that will be used in this notebook.
"""

# ╔═╡ 03b41118-5756-447b-9900-15af37667529
md"""
## Target pools

If you are not familiar with the rules of Wordle, please check the [Wikipedia page](https://en.wikipedia.org/wiki/Wordle).
It is a word game with the objective of guessing a 5-letter English word, which we will call the "target".
The target word is changed every day but it is always chosen from a set of 2315 words, which we will call the "target pool".

The original target pool is available with the `Wordlegames` package.  (Apparently the New York Times removed a few of these words after they purchased the rights to Wordle.)
"""

# ╔═╡ 73e3e38b-0935-4ef9-97b1-8a8cdfa2feb7
datadir = joinpath(pkgdir(Wordlegames), "data");

# ╔═╡ 4c5e781c-fd10-425f-95d2-1fec5bf1015f
wordlestrings = collect(readlines(joinpath(datadir, "Wordletargets.txt")))

# ╔═╡ 2046a4ca-c4d3-4804-9373-930d9cbb58fd
md"We call this pool `wordlestrings` because it is stored as a vector of `String`s"

# ╔═╡ 560ad4d4-945c-4d21-8947-cac68c1880a1
typeof(wordlestrings)

# ╔═╡ ddd7f90c-7cad-4279-a275-b1800b4ae83b
md"""
The `Wordlegames` package defines a `GamePool` struct for playing Wordle or related games.
In that `struct` the `String`s are converted to a more efficient storage mode as a vector of `NTuple{5,Char}`, which takes advantage of the fact that each string is exactly 5 characters long.

Speaking of which, it would be a good idea to check that this collection has the properties we were told it had.
It should be a vector of 2315 strings, each of which is 5 characters.
"""

# ╔═╡ 49500976-7ab8-43cc-9902-b25ad73ff42e
length(wordlestrings)

# ╔═╡ f6681018-f729-48de-9342-a5bcb151aca2
all(w -> length(w) == 5, wordlestrings)

# ╔═╡ 22f7e1ad-3a2b-4a88-8f81-91feada88621
md"""
That last expression may look, well, "interesting".
It is a way of checking that a function, in this case an anonymous function expressed using the [stabby lambda](https://dev.to/keithrbennett/why-i-prefer-stabby-lambda-notation-5gcj) notation, returns `true` for each element of an iterator, in this case the vector `wordlestrings`.
You can read the whole expression as "is `length(w)` equal to `5` for each word `w` in `wordlestrings`".

These words are supposed to be exactly 5 letters long but it never hurts to check.
I've been a data scientist for several decades and one of the first lessons in the field is to [trust, but verify](https://en.wikipedia.org/wiki/Trust%2C_but_verify) any claims about the data you are provided.

It turns out this check is redundant because the property is checked when creating a `GamePool`.
"""

# ╔═╡ 5787eef2-7a2c-46ad-8fa0-7313c07be84b
wordle = GamePool(wordlestrings);

# ╔═╡ 8df0e0c0-cdae-450c-abc1-5cea1d520ec9
propertynames(wordle)

# ╔═╡ 8580bdcf-24ce-4eb6-ac5a-4c77b729b38b
typeof(wordle.guesspool)

# ╔═╡ 9ed9e8ab-3ebc-4c6e-b2c3-46ad1987561e
first(wordle.guesspool, 3)

# ╔═╡ fdd6631e-8b3a-4b4f-b8a7-11135f13cc23
md"""
## Game play

A Wordle game is a dialog between the player and an "oracle", which, for the official game, is the web site.
The player submits a question to the oracle and the oracle responds, using information to which the player does not have access.
In this case the information is the target word.
The question is the player's guess - a 5-letter word - and the response is a score for that word.
The score indicates, for each character, whether it matches the character in the same position in the target or it is in the target in another position or it is not in the target at all.

Using the sample game for Wordle #196 from the Wikipedia page for illustration
"""

# ╔═╡ 9b911b3c-17d5-42d5-b7e5-a4187ce5787c
PlutoUI.Resource(
    "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Wordle_196_example.svg/440px-Wordle_196_example.svg.png",
)

# ╔═╡ 4d5681f9-56a5-4f83-9b31-810bdef26d98
md"""
The target is "rebus".

The player's first guess is "arise" and the response, or score, from the oracle is coded as 🟫🟨🟫🟨🟨 where 🟫 indicates that the letter is not in the target (neither `a` nor `i` occur in "rebus") and 🟨 indicates that the letter is in the target but not at that position.
(I'm using 🟫 instead of a gray square because I can't find a gray square Unicode character.)

The second guess is "route" for which the response is 🟩🟫🟨🟫🟨 indicating that the first letter in the guess occurs as the first letter in the target.
Notice that this guess does not include an "s", which is known from the score of the first guess to be one of the characters in the target.
This guess would not be allowed if playing the game under the "Hard Mode" setting.

Of course, the colors are just one way of summarizing the response to a guess.
Within a computer program it is easier to use an integer to represent each of the 243 = 3⁵ possible scores.
An obvious way of mapping the result to an integer in the (decimal) range 0:242 is by mapping the response for each character to 2 (in target at that position), 1 (in target not at that position), or 0 (not in target) and regarding the pattern as a base-3 number.

In this coding the response for the first guess, "arise", is 01011 in base-3 or 31 in decimal.
The response for the second guess, "route", is 20101 in base-3 or 172 in decimal.

A function to evaluate this score can be written as
"""

# ╔═╡ db787d98-d2a4-4d4a-8e13-8b61e80e48dd
function score(guess, target)
    s = 0
    for (g, t) in zip(guess, target)
        s *= 3
        s += (g == t) ? 2 : Int(g ∈ target)
    end
    return s
end

# ╔═╡ e9e23c37-e4d8-4b77-ab6f-264bcba4ebec
md"""
These numeric scores are not on a scale where "smaller is better" or "larger is better".
(It happens that the best score is 242, corresponding to a perfect match, or five green tiles, but that's incidental.)

The score is just a way of representing each of the 243 patterns that can be produced.

We can convert back to colored tiles if desired using the `tiles` function from the `Wordlegames` package, defined as
```julia
function tiles(sc, ntiles)
    result = Char[]       # initialize to an empty array of Char
    for _ in 1:ntiles     # _ indicates the value of the iterator is not used
        sc, r = divrem(sc, 3)
        push!(result, iszero(r) ? '🟫' : (isone(r) ? '🟨' : '🟩'))
    end
    return String(reverse(result))
end
```
"""

# ╔═╡ b0970feb-103d-4560-b577-f611171e8da6
tiles(31, 5)

# ╔═╡ 39ae1224-9cdf-4d4b-b543-985a5667a601
md"""
## Examining the score function

In the Sherlock Holmes story [The Adventure of Silver Blaze](thttps://en.wikipedia.org/wiki/The_Adventure_of_Silver_Blaze) there is a famous exchange where Holmes remarks on "the curious incident of the dog in the night-time" (see the link).
The critical clue in the case is not what happened but what didn't happen - the dog didn't bark.

Just as Holmes found it interesting that the dog didn't bark, we should find some of the functions in this notebook interesting for what they don't include.
In many of the functions shown here the arguments aren't given explicit types.

Knowing the concrete types of arguments is very important when compiling functions, as is done in Julia, but these functions are written without explicit types.

Consider the `score` function which we reproduce here

```julia
function score(guess, target)
    s = 0
    for (g, t) in zip(guess, target)
        s *= 3
        s += (g == t) ? 2 : Int(g ∈ target)
    end
    return s
end
```

The arguments to `score` can be any type.
In fact, formally they are of an abstract type called `Any`.

So how do we make sure that the actual arguments make sense for this function?
Well, the first thing that is done with the arguments is to pass them to `zip(guess, target)` to produce pairs of values, `g` and `t`, that can be compared for equality, `g == t`.
In a sense `score` delegates the task of checking that the arguments are sensible to the `zip` function.

For those unfamiliar with zipping two or more iterators, we can check what the result is.
"""

# ╔═╡ 8a1cc31b-f747-49b4-9860-a2c381d98c44
collect(zip("arise", "rebus"))

# ╔═╡ 6830dbd6-edc9-4193-8d73-becf3be3855a
md"""
One of the great advantages of dynamically-typed languages with a REPL (read-eval-print-loop) like Julia is that we can easily check what `zip` produces in a couple of examples (or even read the documentation returned by `?zip`, if we are desperate).

The rest of the function is a common pattern - initialize `s`, which will be the result, modify `s` in a loop, and return it.
The Julia expression
```julia
s *= 3
```
indicates, as in several other languages, that `s` is to be multiplied by 3 in-place.

An expression like
```julia
(g == t) ? 2 : Int(g  ∈ target)
```
is a *ternary operator* expression (the name comes from the operator taking three operands).
It evaluates the condition, `g == t`, and returns `2` if the condition is `true`.
If `g == t` is `false` the operator returns the value of the Boolean expression `g  ∈ target`, converted to an `Int`.
(The expression could also be written `g in target`.
In the Julia REPL the `∈` character is created by typing `\in<tab>`.)
The Boolean expression will return `false` or `true`, which is promoted to `0` or `1` for the `+=` operation.

The operation of multiplying by 3 and adding 2 or 1 or 0 is an implementation of [Horner's method](https://en.wikipedia.org/wiki/Horner%27s_method) for evaluating a polynomial.

The function is remarkable because it is both general and compact.
Even more remarkable is that it will be very, very fast after its first usage triggers compilation.
That's important because this function will be in a "hot loop".
It will be called many, many times when evaluating the next guess.

(Unfortunately, this version doesn't properly account for cases where a character is repeated in the guess - an example of [Kernighan and Plauger's](https://en.wikipedia.org/wiki/The_Elements_of_Programming_Style) aphorism, "Efficiency often means getting the wrong answer quickly."
We will return to this issue later.)

We won't go into detail about the Julia compiler except to note that compilation is performed for specific *method signatures* (or "method instances") not for general method definitions.

There are several functions and macros in Julia that allow for inspection at different stages of compilation.
One of the most useful is the macro `@code_warntype` which is used to check for situations where type inference has not been successful.
Applying it as
```julia
julia> @code_warntype score("arise", "rebus")
MethodInstance for score(::String, ::String)
  from score(guess, target) in Main at REPL[1]:1
Arguments
  #self#::Core.Const(score)
  guess::String
  target::String
Locals
  @_4::Union{Nothing, Tuple{Tuple{Char, Char}, Tuple{Int64, Int64}}}
  s::Int64
  @_6::Int64
  t::Char
  g::Char
  @_9::Int64
Body::Int64
...
```
shows that type inference is based on concrete types (`String`) for the arguments.

Some argument types are handled more efficiently than others.
Without going in to details we note that we can take advantage of the fact that we have exactly 5 characters and convert the elements of `words` from `String` to `NTuple{5,Char}`, which is an ordered, fixed-length homogeneous collection.

Using the `@benchmark` macro from the `BenchmarkTools` package gives run times of a few tens of nanoseconds for these arguments, and shows that the function applied to the fixed-length collections is faster.
"""

# ╔═╡ e4410d39-6680-4eac-9f70-a425df352998
md"""
New methods can be defined for a generic function like `score`.
A reason for this could be, for example, that the method can more effectively use information from the type.

For example, the `NTuple{N,Char}` type has exactly `N` characters - information that can be used in a loop where we can turn off bounds checking.
"""

# ╔═╡ a031c3db-6630-4deb-a90d-d2b13202dec6
function score(guess::NTuple{N,Char}, target::NTuple{N,Char}) where {N}
    s = 0
    @inbounds for i = 1:N
        s *= 3
        gi = guess[i]
        s += (gi == target[i]) ? 2 : Int(gi ∈ target)
    end
    return s
end

# ╔═╡ 9e1d6017-f79e-49b5-916e-8e3a9e49ab1d
score("arise", "rebus")

# ╔═╡ a69f62bd-ed6d-42e7-8405-9321d13a52d1
@benchmark score(guess, target) setup = (guess = "arise"; target = "rebus")

# ╔═╡ 1b9261b3-c90c-4db1-9e70-51bf364072b8
md"""
This method returns the same result as the other method, only faster.
"""

# ╔═╡ 1887c091-46a0-4e8c-9a66-2ab17968729c
score(('a', 'r', 'i', 's', 'e'), ('r', 'e', 'b', 'u', 's'))

# ╔═╡ 39202f1a-e7d6-4961-9776-ec67c3fd9743
@benchmark score(guess1, target1) setup =
    (guess1 = ('a', 'r', 'i', 's', 'e'); target1 = ('r', 'e', 'b', 'u', 's'))

# ╔═╡ 2b73c7f4-0203-4e2b-b634-eb310152834b
md"""
## Repeated characters in the guess

The simple `score` methods shown above don't give the correct score (meaning the score that would be returned on the web site) when there are repeated characters in the guess.
For example, a guess of `"sheer"` for the target `"super"` is scored as
"""

# ╔═╡ 79f0ceab-0c9f-42f7-b3ec-a58b5ac1c083
tiles(score("sheer", "super"), 5)

# ╔═╡ b92abc9c-2a86-47ab-a6e8-127c4d7de52e
md"""
but the score should be `"🟩🟫🟫🟩🟩"` because there is only one `e` in the target `"super"`.
In a case like this where a character occurs multiple times in a guess but only once in the target the rules about which position in the guess is marked are that "correct position" takes precedence over "in the word" and, if none of the guess positions are correct, then leftmost takes precedence.

This makes for a considerably more complex score evaluation.
Essentially there have to be two passes over the score and target - the first to check for correct position and the second to check for "in the target, not in the correct position".

However, the simple algorithm in the current `score` methods works if there are no duplicate characters in the guess.
Thus it is probably worthwhile checking for duplicates, using the simple function

```julia
function hasdups(guess::NTuple{N,Char}) where {N}
    @inbounds for i in 1:(N - 1)
        gi = guess[i]
        for j in (i + 1):N
            gi == guess[j] && return true
        end
    end
    return false
end
```

and choose the simple scoring algorithm when there are no duplicates.

In the [Wordlegames](https://github.com/dmbates/Wordlegames.jl) package these operations are combined in a `scorecolumn!` function that updates a vector of scores on a single guess against a vector of targets.

```julia
function scorecolumn!(
    col::AbstractVector{<:Integer},
    guess::NTuple{N,Char},
    targets::AbstractVector{NTuple{N,Char}},
) where {N}
    if axes(col) ≠ axes(targets)
        throw(
            DimensionMismatch(
                "axes(col) = $(axes(col)) ≠ $(axes(targets)) = axes(targets)",
            )
        )
    end
    if hasdups(guess)
        onetoN = (1:N...,)           # 1:N as a Tuple
        svec = zeros(Int, N)         # scores for characters in guess
        unused = trues(N)            # unused positions in targets[i]
        @inbounds for i in axes(targets, 1)
            targeti = targets[i]
            fill!(unused, true)      # reset to all unused
            fill!(svec, 0)           # reset to all guess characters not in target
            for j = 1:N              # first pass for target in same position
                if guess[j] == targeti[j]
                    unused[j] = false
                    svec[j] = 2
                end
            end
            for j = 1:N              # second pass for match in unused position
                if iszero(svec[j])
                    for k in onetoN[unused]
                        if guess[j] == targeti[k]
                            svec[j] = 1
                            unused[k] = false
                            break
                        end
                    end
                end
            end
            sc = 0                   # Horner's method to evaluate the score
            for s in svec
                sc *= 3
                sc += s
            end
            col[i] = sc
        end
    else                             # simplified alg. for guess w/o duplicates
        @inbounds for i in axes(targets, 1)
            sc = 0
            targeti = targets[i]
            for j = 1:N
                sc *= 3
                gj = guess[j]
                sc += (gj == targeti[j]) ? 2 : Int(gj ∈ targeti)
            end
            col[i] = sc
        end
    end
    return col
end
```

This is "production code" which has gone through several refinement steps so it may seem a bit daunting at first.
However, we can break it down.

First, does it give the desired result?
"""

# ╔═╡ 2f32604f-b64c-47f5-bf14-a3cbde36cc13
scores1 = zeros(Int, 1)  # initialize a vector of 1 integer to zero

# ╔═╡ f899d69c-ee68-47e3-963f-90bc401558b2
scorecolumn!(scores1, ('s', 'h', 'e', 'e', 'r'), [('s', 'u', 'p', 'e', 'r')])

# ╔═╡ 643bb3f5-c8e7-446c-b7d4-ea40952c4f59
tiles(first(scores1), 5)

# ╔═╡ 4742dec1-1a46-4095-bc49-467182beb163
md"""
We see that the call to `scorecolumn!` overwrites the contents of the `scores1` vector with the score for the guess on the first (and only) target.

Thus `scorecolumn!` is a "mutating function", meaning that it changes the contents of one or more of its arguments.
By convention we give such functions names ending in `"!"`, as a warning to the user that the function may mutate its arguments.
(This is merely a convention; the `"!"` has no syntactic significance.)
Furthermore, the convention is to list any arguments that may be modified first.

The reason this function is called `scorecolumn!` is because the scores for all possible guesses on all possible targets are evaluated and cached as a matrix in a `GamePool` object.
This may seem extravagant but most methods for determining an initial guess algorithmically will end up evaluating all these scores so it makes sense to save them in an array.
In this case the rows correspond to targets and the columns to guesses and evaluating the scores for a single guess against all possible targets updates a column of this matrix.

A section from the upper left corner of this matrix
"""

# ╔═╡ 225c813a-e63e-44fc-bc08-caf4cf36e21b
view(wordle.allscores, 1:7, 1:10)

# ╔═╡ 875f7d47-b71f-4f67-90ae-755b00e2b464
md"""
shows that the scores, which are in the range `0:242`, are stored as unsigned, 8-bit integers to conserve storage.
Even so, the storage required is (2315)² bytes, or over 5 megabytes.
"""

# ╔═╡ 7c6e0762-de57-4041-a683-7ed6f0715057
Base.summarysize(wordle.allscores)

# ╔═╡ 178cfcd4-e198-4e44-b5bd-7da6c8b21456
md"""
Five megabytes is not a large amount of memory by today's standards, but for games with larger pools of guesses or targets the storage may start to mount up.
In those cases there is provision for [memory-mapping](https://en.wikipedia.org/wiki/Memory-mapped_file) the array.
The evaluation of the array is multi-threaded when Julia is running with multiple threads.

The scores in the first column,
"""

# ╔═╡ 90f09143-4046-4e2f-967e-1df115411b32
tiles.(view(wordle.allscores, 1:7, 1), 5)

# ╔═╡ 628fb954-e44f-4c6d-af03-c41e9e2894d5
md"""
are for the first guess, "aback", against the first 7 targets
"""

# ╔═╡ c976042e-2e91-401b-acfa-88199c22326c
[String(collect(t)) for t in view(wordle.targetpool, 1:7)]

# ╔═╡ 197e3fbe-6172-47b8-99c5-c8293223747c
md"""
The `scorecolumn!` function itself uses the `axes` function in several places.
By default Julia uses 1-based indexing but other forms of indexing are allowed.
(I am obligated at this point to mention [StarWarsArrays](https://github.com/giordano/StarWarsArrays.jl) which begins indexing at 4, 5, 6 then 1, 2, 3 then 7, 8, and 9.)

The call to `axes(targets, 1)` returns the indices in the first (and only) axis of the `target` vector.
The `col` and `targets` arguments are typed as `AbstractVector`, not `Vector`, because `Vector` is a concrete, specific type and we wish to allow for "vector-like" objects such as a one-dimensional view in a multi-dimensional array.

The call to `scorecolumn!` in the constructor for a `GamePool` is in the code segment

```julia
    S = scoretype(N)
    vtargs = view(guesspool, validtargets)
    allscores = Array{S}(undef, length(vtargs), length(guesspool))
    Threads.@threads for j in axes(allscores, 2)
        scorecolumn!(view(allscores, :, j), guesspool[j], vtargs)
    end
```

There are two arrays, `svec` and `unused` allocated within the `scorecolumn!` function when guesses have repeated characters.
These are very small arrays but nonetheless we would want to minimize the number of allocations if feasible.
This is why the check for duplicate characters is carried out and the allocation of these arrays is done only once per column of `allscores`.
The allocation is done within the function so that it can be called from multiple threads simultaneously without the threads interfering with each other.

The branch for a guess without duplicates is still much faster than for a guess with duplicate characters but neither case is horribly slow.
"""

# ╔═╡ de2cac98-2ca9-4d27-8b02-ff152a73df7e
@benchmark scorecolumn!(col, ('a', 'r', 'i', 's', 'e'), $(wordle.guesspool)) setup =
    (col = zeros(UInt8, length(wordle.guesspool)))

# ╔═╡ 04cbc640-70be-402b-8a7e-3e9fc8e2cb00
md"""
Notice that there are no allocations of memory when there are no duplicated characters in the guess.
There are allocations, and consequently some garbage collection (GC), when the guess has duplicated characters.
"""

# ╔═╡ 01d24948-480d-4eb5-9cc4-7c1e806f22fd
@benchmark scorecolumn!(col1, ('a', 'b', 'a', 'c', 'k'), $(wordle.guesspool)) setup =
    (col1 = zeros(UInt8, length(wordle.guesspool)))

# ╔═╡ 2c6d5aa5-7a23-4c67-8ff0-27b95ef58375
md"""
## Conclusion

These few examples have introduced, at least in passing, several advanced programming concepts - multi-threading, memory-mapping, control of storage allocation and garbage collection - that one typically would not associate with a dynamically-typed, REPL-based language like Julia.

Of course, all of these facilities are available in compiled languages like C/C++ or Rust but usually without the "rapid development and testing" capability of a language like Julia.

Julia provides a wide range of tools so that a programmer can start at a very simple level, like the original `score` method and refine as needed to reach speeds previously only achievable with compiled, statically-typed languages.
"""

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
Wordlegames = "1cb69566-e1cf-455f-a587-fd79a2e00f5a"

[compat]
BenchmarkTools = "~1.3.1"
PlutoUI = "~0.7.38"
Wordlegames = "~0.3.0"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.8.0-beta3"
manifest_format = "2.0"
project_hash = "045a6e2c86e27f8d763b85c40450eb9e85eed07e"

[[deps.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "8eaf9f1b4921132a4cff3f36a1d9ba923b14a481"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.4"

[[deps.AbstractTrees]]
git-tree-sha1 = "03e0550477d86222521d254b741d470ba17ea0b5"
uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
version = "0.3.4"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
version = "1.1.1"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.BenchmarkTools]]
deps = ["JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"]
git-tree-sha1 = "4c10eee4af024676200bc7752e536f858c6b8f93"
uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
version = "1.3.1"

[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "96b0bc6c52df76506efc8a441c6cf1adcb1babc4"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.42.0"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "0.5.2+0"

[[deps.Crayons]]
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.1.1"

[[deps.DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[deps.DataFrames]]
deps = ["Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "ae02104e835f219b8930c7664b8012c93475c340"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.3.2"

[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "3daef5523dd2e769dad2365274f760ff5f282c7d"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.11"

[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[deps.Downloads]]
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
version = "1.6.0"

[[deps.FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"

[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[deps.Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[deps.Future]]
deps = ["Random"]
uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"

[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[deps.HypertextLiteral]]
git-tree-sha1 = "2b078b5a615c6c0396c77810d92ee8c6f470d238"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.3"

[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.InvertedIndices]]
git-tree-sha1 = "bee5f1ef5bf65df56bdd2e40447590b272a5471f"
uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
version = "1.1.0"

[[deps.IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.3"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
version = "0.6.3"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
version = "7.81.0+0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
version = "1.10.2+0"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LinearAlgebra]]
deps = ["Libdl", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
version = "2.28.0+0"

[[deps.Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "bf210ce90b6c9eed32d25dbcae1ebc565df2687f"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.2"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
version = "2022.2.1"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.2.0"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
version = "0.3.20+0"

[[deps.OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "621f4f3b4977325b9128d5fae7a8b4829a0c2222"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.2.4"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
version = "1.8.0"

[[deps.PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "670e559e5c8e191ded66fa9ea89c97f10376bb4c"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.38"

[[deps.PooledArrays]]
deps = ["DataAPI", "Future"]
git-tree-sha1 = "28ef6c7ce353f0b35d0df0d5930e0d072c1f5b9b"
uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
version = "1.4.1"

[[deps.PrettyTables]]
deps = ["Crayons", "Formatting", "Markdown", "Reexport", "Tables"]
git-tree-sha1 = "dfb54c4e414caa595a1f2ed759b160f5a3ddcba5"
uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
version = "1.3.1"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.Profile]]
deps = ["Printf"]
uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

[[deps.SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
version = "1.0.0"

[[deps.TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[deps.Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "OrderedCollections", "TableTraits", "Test"]
git-tree-sha1 = "5ce79ce186cc678bbb5c5681ca3379d1ddae11a1"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.7.0"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
version = "1.10.0"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.Wordlegames]]
deps = ["AbstractTrees", "DataFrames", "Random", "Tables"]
git-tree-sha1 = "4c463de78d2f3f9447b695e241eba43cc945f866"
uuid = "1cb69566-e1cf-455f-a587-fd79a2e00f5a"
version = "0.3.0"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
version = "1.2.12+1"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
version = "5.1.0+0"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
version = "1.41.0+1"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
version = "16.2.1+1"
"""

# ╔═╡ Cell order:
# ╟─f9816eb0-078e-41f2-bf7c-29ed5c663fd0
# ╟─a9678ae8-48c0-4bb9-920d-7de176105bd0
# ╟─4eabf46e-f75c-4b15-8583-6abe17c0fd85
# ╠═49a44e94-29c0-4004-8d0b-e37008eafc1c
# ╟─03b41118-5756-447b-9900-15af37667529
# ╠═73e3e38b-0935-4ef9-97b1-8a8cdfa2feb7
# ╠═4c5e781c-fd10-425f-95d2-1fec5bf1015f
# ╟─2046a4ca-c4d3-4804-9373-930d9cbb58fd
# ╠═560ad4d4-945c-4d21-8947-cac68c1880a1
# ╟─ddd7f90c-7cad-4279-a275-b1800b4ae83b
# ╠═49500976-7ab8-43cc-9902-b25ad73ff42e
# ╠═f6681018-f729-48de-9342-a5bcb151aca2
# ╟─22f7e1ad-3a2b-4a88-8f81-91feada88621
# ╠═5787eef2-7a2c-46ad-8fa0-7313c07be84b
# ╠═8df0e0c0-cdae-450c-abc1-5cea1d520ec9
# ╠═8580bdcf-24ce-4eb6-ac5a-4c77b729b38b
# ╠═9ed9e8ab-3ebc-4c6e-b2c3-46ad1987561e
# ╟─fdd6631e-8b3a-4b4f-b8a7-11135f13cc23
# ╟─9b911b3c-17d5-42d5-b7e5-a4187ce5787c
# ╟─4d5681f9-56a5-4f83-9b31-810bdef26d98
# ╠═db787d98-d2a4-4d4a-8e13-8b61e80e48dd
# ╠═9e1d6017-f79e-49b5-916e-8e3a9e49ab1d
# ╟─e9e23c37-e4d8-4b77-ab6f-264bcba4ebec
# ╠═b0970feb-103d-4560-b577-f611171e8da6
# ╟─39ae1224-9cdf-4d4b-b543-985a5667a601
# ╠═8a1cc31b-f747-49b4-9860-a2c381d98c44
# ╟─6830dbd6-edc9-4193-8d73-becf3be3855a
# ╠═a69f62bd-ed6d-42e7-8405-9321d13a52d1
# ╟─e4410d39-6680-4eac-9f70-a425df352998
# ╠═a031c3db-6630-4deb-a90d-d2b13202dec6
# ╟─1b9261b3-c90c-4db1-9e70-51bf364072b8
# ╠═1887c091-46a0-4e8c-9a66-2ab17968729c
# ╠═39202f1a-e7d6-4961-9776-ec67c3fd9743
# ╟─2b73c7f4-0203-4e2b-b634-eb310152834b
# ╠═79f0ceab-0c9f-42f7-b3ec-a58b5ac1c083
# ╟─b92abc9c-2a86-47ab-a6e8-127c4d7de52e
# ╠═2f32604f-b64c-47f5-bf14-a3cbde36cc13
# ╠═f899d69c-ee68-47e3-963f-90bc401558b2
# ╠═643bb3f5-c8e7-446c-b7d4-ea40952c4f59
# ╟─4742dec1-1a46-4095-bc49-467182beb163
# ╠═225c813a-e63e-44fc-bc08-caf4cf36e21b
# ╟─875f7d47-b71f-4f67-90ae-755b00e2b464
# ╠═7c6e0762-de57-4041-a683-7ed6f0715057
# ╟─178cfcd4-e198-4e44-b5bd-7da6c8b21456
# ╠═90f09143-4046-4e2f-967e-1df115411b32
# ╟─628fb954-e44f-4c6d-af03-c41e9e2894d5
# ╠═c976042e-2e91-401b-acfa-88199c22326c
# ╟─197e3fbe-6172-47b8-99c5-c8293223747c
# ╠═de2cac98-2ca9-4d27-8b02-ff152a73df7e
# ╟─04cbc640-70be-402b-8a7e-3e9fc8e2cb00
# ╠═01d24948-480d-4eb5-9cc4-7c1e806f22fd
# ╟─2c6d5aa5-7a23-4c67-8ff0-27b95ef58375
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


## Exercise 4-2
## Write a function called square that takes a parameter named t, which is a turtle. It should use the turtle to draw a square.
using ThinkJulia

println("Ans: ")

function square(turtle::Turtle, distance::Int = 100)
    @svg begin
        for i in 1:3
            forward(turtle, distance)
            turn(turtle, -90)
        end

        forward(turtle, distance)
    end
end

turtle = Turtle()
square(turtle)

println("End.")


# Copyright (c) 2019 Arpit Bhatia and contributors                               #src
#                                                                                #src
# Permission is hereby granted, free of charge, to any person obtaining a copy   #src
# of this software and associated documentation files (the "Software"), to deal  #src
# in the Software without restriction, including without limitation the rights   #src
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      #src
# copies of the Software, and to permit persons to whom the Software is          #src
# furnished to do so, subject to the following conditions:                       #src
#                                                                                #src
# The above copyright notice and this permission notice shall be included in all #src
# copies or substantial portions of the Software.                                #src
#                                                                                #src
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     #src
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       #src
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    #src
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         #src
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  #src
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  #src
# SOFTWARE.                                                                      #src

# # Getting started with JuMP

# This tutorial is aimed at providing a quick introduction to writing JuMP code.

# ## What is JuMP?

# JuMP ("Julia for Mathematical Programming") is an open-source modeling
# language that is embedded in Julia. It allows users to formulate various
# classes of optimization problems (linear, mixed-integer, quadratic, conic
# quadratic, semidefinite, and nonlinear) with easy-to-read code. These problems
# can then be solved using state-of-the-art open-source and commercial solvers.

# JuMP also makes advanced optimization techniques easily accessible from a
# high-level language.

# ## Installation

# JuMP is a package for Julia. From Julia, JuMP is installed by using the
# built-in package manager.

# ```julia
# import Pkg
# Pkg.add("JuMP")
# ```

# You also need to include a Julia package which provides an appropriate solver.
# One such solver is `GLPK.Optimizer`, which is provided by the
# [GLPK.jl package](https://github.com/jump-dev/GLPK.jl).
# ```julia
# import Pkg
# Pkg.add("GLPK")
# ```
# See [Installation Guide](@ref) for a list of other solvers you can use.

# ## An example

# Let's try to solve the following linear programming problem by using JuMP and
# GLPK. We will first look at the complete code to solve the problem and then go
# through it step by step.

# ```math
# \begin{aligned}
# & \min & 12x + 20y \\
# & \;\;\text{s.t.} & 6x + 8y \geq 100 \\
# & & 7x + 12y \geq 120 \\
# & & x \geq 0 \\
# & & y \in [0, 3] \\
# \end{aligned}
# ```

using JuMP
using GLPK
model = Model(GLPK.Optimizer)
@variable(model, x >= 0)
@variable(model, 0 <= y <= 3)
@objective(model, Min, 12x + 20y)
@constraint(model, c1, 6x + 8y >= 100)
@constraint(model, c2, 7x + 12y >= 120)
print(model)
optimize!(model)
@show termination_status(model)
@show primal_status(model)
@show dual_status(model)
@show objective_value(model)
@show value(x)
@show value(y)
@show shadow_price(c1)
@show shadow_price(c2)

# ## Step-by-step

# Once JuMP is installed, to use JuMP in your programs, we just need to write:

using JuMP

# We also need to include a Julia package which provides an appropriate solver.
# We want to use `GLPK.Optimizer` here which is provided by the `GLPK.jl`
# package.

using GLPK

# A model object is a container for variables, constraints, solver options, etc.
# Models are created with the [`Model`](@ref) function. The model can be created
# with an optimizer attached with default arguments by calling the constructor
# with the optimizer type, as follows:

model = Model(GLPK.Optimizer)

# Variables are modeled using [`@variable`](@ref):

@variable(model, x >= 0)

# They can have lower and upper bounds.

@variable(model, 0 <= y <= 30)

# The objective is set using [`@objective`](@ref):

@objective(model, Min, 12x + 20y)

# Constraints are modeled using [`@constraint`](@ref). Here `c1` and `c2` are
# the names of our constraint.

@constraint(model, c1, 6x + 8y >= 100)

#-

@constraint(model, c2, 7x + 12y >= 120)

#- Call `print` to display the model:

print(model)

# To solve the optimization problem, call the `optimize!` function.

optimize!(model)

# !!! info
#     The `!` after optimize is just part of the name. It's nothing special.
#     Julia has a convention that functions which mutate their arguments should
#     end in `!`. A common example is `push!`.

# Now let's see what information we can query about the solution.

# [`termination_status`](@ref) tells us why the solver stopped:

termination_status(model)

# In this case, the solver found an optimal solution. We should also check
# [`primal_status`](@ref) to see if the solver found a primal feasible point:

primal_status(model)

# and [`dual_status`](@ref) to see if the solver found a dual feasible point:

dual_status(model)

# Now we know that our solver found an optimal solution, and has a primal and a
# dual solution to query.

# Query the objective value using [`objective_value`](@ref):

objective_value(model)

# The primal solution using [`value`](@ref):

value(x)

#-

value(y)

# and the dual solution using [`shadow_price`](@ref):

shadow_price(c1)

#-

shadow_price(c2)

# ## Variable basics

model = Model()

# ### Variable bounds

# All of the variables we have created till now have had a bound. We can also
# create a free variable.

@variable(model, free_x)

# While creating a variable, instead of using the <= and >= syntax, we can also
# use the `lower_bound` and `upper_bound` keyword arguments.

@variable(model, keyword_x, lower_bound = 1, upper_bound = 2)

# We can query whether a variable has a bound using the `has_lower_bound` and
# `has_upper_bound` functions. The values of the bound can be obtained using the
# `lower_bound` and `upper_bound` functions.

has_upper_bound(keyword_x)

#-

upper_bound(keyword_x)

# Note querying the value of a bound that does not exist will result in an error.

lower_bound(free_x)

# JuMP also allows us to change the bounds on variable. We will learn this in
# the problem modification tutorial.

# ### [Containers](@id tutorial_variable_container)

# We have already seen how to add a single variable to a model using the
# [`@variable`](@ref) macro. Let's now look at more ways to add variables to a
# JuMP model.

# JuMP provides data structures for adding collections of variables to a model.
# These data structures are referred to as Containers and are of three types:
# `Arrays`, `DenseAxisArrays`, and `SparseAxisArrays`.

# #### Arrays

# JuMP arrays are created in a similar syntax to Julia arrays with the addition
# of specifying that the indices start with 1. If we do not tell JuMP that the
# indices start at 1, it will create a `DenseAxisArray` instead.

@variable(model, a[1:2, 1:2])

# An n-dimensional variable $x \in {R}^n$ having a bound $l \preceq x \preceq u$
# ($l, u \in {R}^n$) is added in the following manner.

n = 10
l = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
u = [10; 11; 12; 13; 14; 15; 16; 17; 18; 19]

@variable(model, l[i] <= x[i = 1:n] <= u[i])

# Note that while working with Containers, we can also create variable bounds
# depending upon the indices:

@variable(model, y[i = 1:2, j = 1:2] >= 2i + j)

# #### DenseAxisArrays

# `DenseAxisArrays` are used when the required indices are not one-based integer
# ranges. The syntax is similar except with an arbitrary vector as an index as
# opposed to a one-based range.

# An example where the indices are integers but do not start with one.

@variable(model, z[i = 2:3, j = 1:2:3] >= 0)

# Another example where the indices are an arbitrary vector.

@variable(model, w[1:5, ["red", "blue"]] <= 1)

# #### SparseAxisArrays

# `SparseAxisArrays` are created when the indices do not form a rectangular set.
# For example, this applies when indices have a dependence upon previous indices
# (called triangular indexing).

@variable(model, u[i = 1:3, j = i:5])

# We can also conditionally create variables by adding a comparison check that
# depends upon the named indices and is separated from the indices by a
# semi-colon (;).

@variable(model, v[i = 1:9; mod(i, 3) == 0])

# ### Variable types

# The last argument to the `@variable` macro is usually the variable type. Here
# we'll look at how to specify the variable type.

# #### Integer variables

# Integer optimization variables are constrained to the set $x \in {Z}$

@variable(model, integer_x, Int)

# or

@variable(model, integer_z, integer = true)

# #### Binary variables

# Binary optimization variables are constrained to the set $x \in \{0, 1\}$.

@variable(model, binary_x, Bin)

# or

@variable(model, binary_z, binary = true)

# ## Constraint basics

model = Model()
@variable(model, x)
@variable(model, y)
@variable(model, z[1:10]);

# ### Constraint references

# While calling the `@constraint` macro, we can also set up a constraint
# reference. Such a reference is useful for obtaining additional information
# about the constraint, such as its dual solution.

@constraint(model, con, x <= 4)

# ### [Containers](@id tutorial_constraint_container)

# Just as we had containers for variables, JuMP also provides `Arrays`,
# `DenseAxisArrays`, and `SparseAxisArrays` for storing collections of
# constraints. Examples for each container type are given below.

# #### Arrays

@constraint(model, [i = 1:3], i * x <= i + 1)

# #### DenseAxisArrays

@constraint(model, [i = 1:2, j = 2:3], i * x <= j + 1)

# #### SparseAxisArrays

@constraint(model, [i = 1:2, j = 1:2; i != j], i * x <= j + 1)

# ### Constraints in a loop

# We can add constraints using regular Julia loops

for i in 1:3
    @constraint(model, 6x + 4y >= 5i)
end

# or use for each loops inside the `@constraint` macro.

@constraint(model, [i in 1:3], 6x + 4y >= 5i)

# We can also create constraints such as $\sum _{i = 1}^{10} z_i \leq 1$

@constraint(model, sum(z[i] for i in 1:10) <= 1)

# ## Objective functions

# While the recommended way to set the objective is with the [`@objective`](@ref)
# macro, the functions [`set_objective_sense`](@ref) and [`set_objective_function`](@ref)
# provide an equivalent lower-level interface.

using GLPK

model = Model(GLPK.Optimizer)
@variable(model, x >= 0)
@variable(model, y >= 0)
set_objective_sense(model, MOI.MIN_SENSE)
set_objective_function(model, x + y)

optimize!(model)

#-

objective_value(model)

# To query the objective function from a model, we use the [`objective_sense`](@ref),
# [`objective_function`](@ref), and [`objective_function_type`](@ref) functions.

objective_sense(model)

#-

objective_function(model)

#-

objective_function_type(model)

# ## Vectorized syntax

# We can also add constraints and an objective to JuMP using vectorized linear
# algebra. We'll illustrate this by solving an LP in standard form i.e.

# ```math
# \begin{aligned}
# & \min & c^T x \\
# & \;\;\text{s.t.} & A x = b \\
# & & x \succeq 0 \\
# & & x \in \mathbb{R}^n
# \end{aligned}
# ```

vector_model = Model(GLPK.Optimizer)

A = [
    1 1 9 5
    3 5 0 8
    2 0 6 13
]

b = [7; 3; 5]

c = [1; 3; 5; 2]

@variable(vector_model, x[1:4] >= 0)
@constraint(vector_model, A * x .== b)
@objective(vector_model, Min, c' * x)

optimize!(vector_model)

#-

objective_value(vector_model)


### A Pluto.jl notebook ###
# v0.12.21

using Markdown
using InteractiveUtils

# ╔═╡ bcaefa4e-85c6-11eb-3316-0db4881ebd30
begin
	using PlutoUI
	using NativeSVG
end

# ╔═╡ 13d62b06-85c7-11eb-00b9-0399467b1ab5
md"""# Dictionaries

This chapter presents another built-in type called a dictionary."""

# ╔═╡ 1f1c885e-85c7-11eb-1452-13a8943ebc90
md"""## A Dictionary Is a Mapping

A *dictionary* is like an array, but more general. In an array, the indices have to be integers; in a dictionary they can be (almost) any type.

A dictionary contains a collection of indices, which are called *keys*, and a collection of *values*. Each key is associated with a single value. The association of a key and a value is called a *key-value* pair, or sometimes an *item*.

In mathematical language, a dictionary represents a *mapping* from keys to values, so you can also say that each key “maps to” a value. As an example, we’ll build a dictionary that maps from English to Spanish words, so the keys and the values are all strings.

The function `Dict` creates a new dictionary with no items (because Dict is the name of a built-in function, you should avoid using it as a variable name):

```julia
julia> eng2sp = Dict()
Dict{Any,Any} with 0 entries
```

The types of the keys and values in the dictionary are specified in curly braces: here, both are of type `Any`.

The dictionary is empty. To add items to the dictionary, you can use square brackets:

```julia
julia> eng2sp["one"] = "uno";
```

This line creates an item that maps from the key `"one"` to the value `"uno"`. If we print the dictionary again, we see a key-value pair with an arrow `=>` between the key and value:

```julia
julia> eng2sp
Dict{Any,Any} with 1 entry:
  "one" => "uno"
```

This output format is also an input format. For example, you can create a new dictionary with three items as follows:

```julia
julia> eng2sp = Dict("one" => "uno", "two" => "dos", "three" => "tres") Dict{String,String} with 3 entries:
  "two"   => "dos"
  "one"   => "uno"
  "three" => "tres"
```

Here all the initial keys and values are strings, so a `Dict{String,String}` is created.

The order of the items in a dictionary is unpredictable. If you type the same example
on your computer, you might get a different result.

But that’s not a problem because the elements of a dictionary are never indexed with integer indices. Instead, you use the keys to look up the corresponding values:

```julia
julia> eng2sp["two"]
"dos"
```

The key `"two"` always maps to the value `"dos"`, so the order of the items doesn’t matter.

If the key isn’t in the dictionary, you get an exception:

```julia
julia> eng2sp["four"]
ERROR: KeyError: key "four" not found
```

The `length` function works on dictionaries; it returns the number of key-value pairs:

```julia
julia> length(eng2sp)
3
```

The function keys returns a collection with the keys of the dictionary:

```julia
julia> ks = keys(eng2sp);

julia> print(ks)
["two", "one", "three"]
```

Now you can use the `∈` operator to see whether something appears as a key in the dictionary:

```julia
julia> "one" ∈ ks
true
julia> "uno" ∈ ks
false
```

To see whether something appears as a value in a dictionary, you can use the function `values`, which returns a collection of values, and then use the `∈` operator:

```julia
julia> vs = values(eng2sp);

julia> "uno" ∈ vs
true
```

The `∈` operator uses different algorithms for arrays and dictionaries. For arrays, it searches the elements of the array in order, as described in “Searching”. As the array gets longer, the search time gets longer in direct proportion.

For dictionaries, Julia uses an algorithm called a *hash table* that has a remarkable property: the `∈` operator takes about the same amount of time no matter how many items are in the dictionary.
"""

# ╔═╡ 27e4cf0c-85c8-11eb-0d95-ab9ddc845387
md"""## Dictionaries as Collections of Counters

Suppose you are given a string and you want to count how many times each letter appears. There are several ways you could do it:

* You could create 26 variables, one for each letter of the alphabet. Then you could traverse the string and, for each character, increment the corresponding counter, probably using a chained conditional.
* You could create an array with 26 elements. Then you could convert each character to a number (using the built-in function Int), use the number as an index into the array, and increment the appropriate counter.
* You could create a dictionary with characters as keys and counters as the corresponding values. The first time you see a character, you would add an item to the dictionary. After that you would increment the value of an existing item.

Each of these options performs the same computation, but each of them implements that computation in a different way.

An *implementation* is a way of performing a computation. Some implementations are better than others. For example, an advantage of the dictionary implementation is that we don’t have to know ahead of time which letters appear in the string and we only have to make room for the letters that do appear.

Here is what the code might look like:

```julia
function histogram(s)
	d = Dict()
	for c in s
		if c ∉ keys(d)
			d[c] = 1
		else
			d[c] += 1
		end
	end
	d
end
```

The name of the function is `histogram`, which is a statistical term for a collection of counters (or frequencies).

The first line of the function creates an empty dictionary. The `for` loop traverses the string. Each time through the loop, if the character `c` is not in the dictionary, we create a new item with key `c` and the initial value `1` (since we have seen this letter once). If `c` is already in the dictionary we increment `d[c]`.

Here’s how it works:

```julia
julia> h = histogram("brontosaurus") Dict{Any,Any} with 8 entries:
  'n' => 1
  's' => 2
  'a' => 1
  'r' => 2
  't' => 1
  'o' => 2
  'u' => 2
  'b' => 1
```

The histogram indicates that the letters *a* and *b* appear once, *o* appears twice, and so on.

Dictionaries have a function called `get` that takes a key and a default value. If the key appears in the dictionary, `get` returns the corresponding value; otherwise, it returns the default value. For example:

```julia
julia> h = histogram("a")
Dict{Any,Any} with 1 entry:
  'a' => 1
julia> get(h, 'a', 0)
1
julia> get(h, 'b', 0)
0
```
"""

# ╔═╡ c8dff15e-85c8-11eb-1230-bd39702cf460
md"""#### Exercise 11-1

Use `get` to write `histogram` more concisely. You should be able to eliminate the `if` statement. What does the function `get!` do? Can you use it to simplify `histogram`?
"""

# ╔═╡ 0b2e93b0-85c9-11eb-0dc3-61b1cbfb022b
md"""## Looping and Dictionaries

You can traverse the keys of a dictionary in a for statement. For example, `printhist` prints each key and the corresponding value:

```julia
function printhist(h)
	for c in keys(h)
		println(c, " ", h[c])
	end
end
```

Here’s what the output looks like:

```julia
julia> h = histogram("parrot");

julia> printhist(h)
a 1
r 2
p 1
o 1
t 1
```

Again, the keys are in no particular order. To traverse the keys in sorted order, you can combine `sort` and `collect`:

```julia
julia> for c in sort(collect(keys(h)))
	       println(c, " ", h[c])
       end
a 1
o 1
p 1
r 2
t 1
```
"""

# ╔═╡ 82a2f936-85c9-11eb-1d33-0d9dc98fdac0
md"""## Reverse Lookup

Given a dictionary `d` and a key `k`, it is easy to find the corresponding value `v = d[k]`. This operation is called a *lookup*.

But what if you have `v` and you want to find `k`? You have two problems. First, there might be more than one key that maps to the value `v`. Depending on the application, you might be able to pick one, or you might have to make an array that contains all of them. Second, there is no simple syntax to do a *reverse lookup*; you have to search.

!!! danger
    A reverse lookup is much slower than a forward lookup; if you have to do it often, or if the dictionary gets big, the performance of your program will suffer.

Here is a function that takes a value and returns the first key that maps to that value:

```julia
function reverselookup(d, v)
	for k in keys(d)
		if d[k]==v
			return k
		end
	end
	error("LookupError")
end
```

This function is yet another example of the search pattern, but it uses a function we haven’t seen before: `error`. The `error` function is used to produce an `ErrorException` that interrupts the normal flow of control. In this case it has the message `"LookupError"`, indicating that a key does not exist.

If we get to the end of the loop, that means `v` doesn’t appear in the dictionary as a value, so we throw an exception.

Here is an example of a successful reverse lookup:

```julia
julia> h = histogram("parrot");

julia> key = reverselookup(h, 2)
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
```

And an unsuccessful one:

```julia
julia> key = reverselookup(h, 3)
ERROR: LookupError
```

The effect when you generate an exception is the same as when Julia throws one: it prints a stacktrace and an error message.

!!! tip
    Julia provides an optimized way to do a reverse lookup: findall(isequal(3), h).
"""

# ╔═╡ 6d87ad02-85ca-11eb-2a3d-999d28a7c4b7
md"""## Dictionaries and Arrays

Arrays can appear as values in a dictionary. For example, if you are given a dictionary that maps from letters to frequencies, you might want to invert it—that is, create a dictionary that maps from frequencies to letters. Since there might be several letters with the same frequency, each value in the inverted dictionary should be an array of letters.

Here is a function that inverts a dictionary:

```julia
function invertdict(d)
	inverse = Dict()
	for key in keys(d)
		val = d[key]
		if val ∉ keys(inverse)
			inverse[val] = [key]
		else
			push!(inverse[val], key)
		end
	end
	inverse
end
```

Each time through the loop, `key` gets a key from `d` and `val` gets the corresponding value. If `val` is not in `inverse`, that means we haven’t seen it before, so we create a new item and initialize it with a *singleton* (an array that contains a single element). Otherwise, we have seen this value before, so we append the corresponding key to the array.

Here is an example:

```julia
julia> hist = histogram("parrot");

julia> inverse = invertdict(hist) Dict{Any,Any} with 2 entries:
  2 => ['r']
  1 => ['a', 'p', 'o', 't']
```

Figure 11-1 is a state diagram showing `hist` and `inverse`. A dictionary is represented as a box with the key-value pairs inside. If the values are integers, floats, or strings, I draw them inside the box, but I usually draw arrays outside the box, just to keep the diagram simple.
"""

# ╔═╡ 4fb90040-85cb-11eb-23dd-ad9ea62b94c3
Drawing(width=720, height=150) do
	defs() do
        marker(id="arrow", markerWidth="10", markerHeight="10", refX="0", refY="3", orient="auto", markerUnits="strokeWidth") do
      		path(d="M0,0 L0,6 L9,3 z", fill="black")
		end
	end
	text(x=105, y=80, font_family="JuliaMono, monospace", text_anchor="end", font_size="0.85rem", font_weight=600) do
		str("hist ->")
	end
	rect(x=120, y=20, width=150, height=110, fill="rgb(242, 242, 242)", stroke="black")
	text(x=140, y=40, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("'o'")
	end
	line(x1=170, y1=35, x2=220, y2=35, stroke="black", marker_end="url(#arrow)")
	text(x=240, y=40, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("1")
	end
	text(x=140, y=60, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("'a'")
	end
	line(x1=170, y1=55, x2=220, y2=55, stroke="black", marker_end="url(#arrow)")
	text(x=240, y=60, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("1")
	end
	text(x=140, y=80, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("'p'")
	end
	text(x=140, y=100, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("'t'")
	end
	text(x=140, y=120, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("'r'")
	end
	line(x1=170, y1=115, x2=220, y2=115, stroke="black", marker_end="url(#arrow)")
	text(x=240, y=120, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("2")
	end
	line(x1=170, y1=95, x2=220, y2=95, stroke="black", marker_end="url(#arrow)")
	text(x=240, y=100, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("1")
	end
	line(x1=170, y1=75, x2=220, y2=75, stroke="black", marker_end="url(#arrow)")
	text(x=240, y=80, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("1")
	end
	text(x=410, y=80, font_family="JuliaMono, monospace", text_anchor="end", font_size="0.85rem", font_weight=600) do
		str("inverse ->")
	end
    rect(x=520, y=10, width=150, height=90, fill="rgb(242, 242, 242)", stroke="black")
	text(x=540, y=30, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("1")
	end
	line(x1=560, y1=25, x2=610, y2=25, stroke="black", marker_end="url(#arrow)")
	text(x=630, y=30, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("'o'")
	end
	text(x=540, y=50, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("2")
	end
	line(x1=560, y1=45, x2=610, y2=45, stroke="black", marker_end="url(#arrow)")
	text(x=630, y=50, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("'a'")
	end
	text(x=540, y=70, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("3")
	end
	line(x1=560, y1=65, x2=610, y2=65, stroke="black", marker_end="url(#arrow)")
	text(x=630, y=70, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("'p'")
	end
	text(x=540, y=90, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("4")
	end
	line(x1=560, y1=85, x2=610, y2=85, stroke="black", marker_end="url(#arrow)")
	text(x=630, y=90, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("'t'")
	end
	rect(x=520, y=110, width=150, height=30, fill="rgb(242, 242, 242)", stroke="black")
	text(x=540, y=130, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("1")
	end
	line(x1=560, y1=125, x2=610, y2=125, stroke="black", marker_end="url(#arrow)")
	text(x=630, y=130, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("'r'")
	end
	rect(x=420, y=10, width=50, height=130, fill="rgb(242, 242, 242)", stroke="black")
	text(x=440, y=60, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("1")
	end
	line(x1=460, y1=55, x2=510, y2=55, stroke="black", marker_end="url(#arrow)")
	text(x=440, y=130, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("2")
	end
	line(x1=460, y1=125, x2=510, y2=125, stroke="black", marker_end="url(#arrow)")
end

# ╔═╡ 26c0d704-85cf-11eb-2a13-293b42210e70
md"*Figure 11-1. State diagram.*"

# ╔═╡ 35e8ee92-85cf-11eb-215c-21912c5fc92b
md"""!!! note
    I mentioned earlier that a dictionary is implemented using a hash table. That means that the keys have to be *hashable*.

    A *hash* is a function that takes a value (of any kind) and returns an integer. Dictionaries use these integers, called hash values, to store and look up key-value pairs.
"""

# ╔═╡ 8316e7a0-85cf-11eb-2ecb-df63279cadc6
md"""## Memos

If you played with the `fibonacci` function from “One More Example”, you might have noticed that the bigger the argument you provide, the longer the function takes to run. Furthermore, the runtime increases quickly.

To understand why, consider Figure 11-2, which shows the *call graph* for `fibonacci` with `n = 4`.
"""

# ╔═╡ c1e519e8-85cf-11eb-2ce1-4bcd389f56bf
Drawing(width=720, height=310) do
	defs() do
        marker(id="arrow", markerWidth="10", markerHeight="10", refX="0", refY="3", orient="auto", markerUnits="strokeWidth") do
      		path(d="M0,0 L0,6 L9,3 z", fill="black")
		end
	end
	rect(x=310, y=10, width=100, height=50, fill="rgb(242, 242, 242)", stroke="black")
	text(x=325, y=30, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("fibonacci")
	end
	text(x=325, y=50, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	line(x1=340, y1=45, x2=375, y2=45, stroke="black", marker_end="url(#arrow)")
	text(x=390, y=50, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("4")
	end
	line(x1=310, y1=60, x2=260, y2=85, stroke="black", marker_end="url(#arrow)")
	line(x1=410, y1=60, x2=460, y2=85, stroke="black", marker_end="url(#arrow)")
	rect(x=150, y=90, width=100, height=50, fill="rgb(242, 242, 242)", stroke="black")
	text(x=165, y=110, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("fibonacci")
	end
	text(x=165, y=130, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	line(x1=180, y1=125, x2=215, y2=125, stroke="black", marker_end="url(#arrow)")
	text(x=230, y=130, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("3")
	end
	line(x1=150, y1=140, x2=130, y2=162, stroke="black", marker_end="url(#arrow)")
	rect(x=75, y=170, width=100, height=50, fill="rgb(242, 242, 242)", stroke="black")
	text(x=90, y=190, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("fibonacci")
	end
	text(x=90, y=210, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	line(x1=105, y1=205, x2=140, y2=205, stroke="black", marker_end="url(#arrow)")
	text(x=155, y=210, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("2")
	end
	line(x1=250, y1=140, x2=270, y2=162, stroke="black", marker_end="url(#arrow)")
	rect(x=225, y=170, width=100, height=50, fill="rgb(242, 242, 242)", stroke="black")
	text(x=240, y=190, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("fibonacci")
	end
	text(x=240, y=210, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	line(x1=255, y1=205, x2=290, y2=205, stroke="black", marker_end="url(#arrow)")
	text(x=305, y=210, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("1")
	end
	line(x1=75, y1=220, x2=53, y2=242, stroke="black", marker_end="url(#arrow)")
	rect(x=0, y=250, width=100, height=50, fill="rgb(242, 242, 242)", stroke="black")
	text(x=15, y=270, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("fibonacci")
	end
	text(x=15, y=290, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	line(x1=30, y1=285, x2=65, y2=285, stroke="black", marker_end="url(#arrow)")
	text(x=80, y=290, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("1")
	end
	line(x1=175, y1=220, x2=197, y2=242, stroke="black", marker_end="url(#arrow)")
	rect(x=150, y=250, width=100, height=50, fill="rgb(242, 242, 242)", stroke="black")
	text(x=150+15, y=270, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("fibonacci")
	end
	text(x=150+15, y=290, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	line(x1=150+30, y1=285, x2=150+65, y2=285, stroke="black", marker_end="url(#arrow)")
	text(x=150+80, y=290, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("0")
	end
	rect(x=470, y=90, width=100, height=50, fill="rgb(242, 242, 242)", stroke="black")
	text(x=485, y=110, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("fibonacci")
	end
	text(x=485, y=130, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	line(x1=500, y1=125, x2=535, y2=125, stroke="black", marker_end="url(#arrow)")
	text(x=550, y=130, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("2")
	end
	line(x1=570, y1=140, x2=590, y2=162, stroke="black", marker_end="url(#arrow)")
	rect(x=720-175, y=170, width=100, height=50, fill="rgb(242, 242, 242)", stroke="black")
	text(x=720-175+15, y=190, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("fibonacci")
	end
	text(x=720-175+15, y=210, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	line(x1=720-175+30, y1=205, x2=720-175+65, y2=205, stroke="black", marker_end="url(#arrow)")
	text(x=720-175+80, y=210, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("0")
	end
	line(x1=470, y1=140, x2=450, y2=162, stroke="black", marker_end="url(#arrow)")
	rect(x=720-325, y=170, width=100, height=50, fill="rgb(242, 242, 242)", stroke="black")
	text(x=720-325+15, y=190, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("fibonacci")
	end
	text(x=720-325+15, y=210, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("n")
	end
	line(x1=720-325+30, y1=205, x2=720-325+65, y2=205, stroke="black", marker_end="url(#arrow)")
	text(x=720-325+80, y=210, font_family="JuliaMono, monospace", font_size="0.85rem", font_weight=600) do
		str("1")
	end
end

# ╔═╡ 2a7646fe-85d9-11eb-3bbe-95f975f3901f
md"*Figure 11-2. Call graph.*"

# ╔═╡ 388a8d90-85d9-11eb-08f1-6b622cfb2c11
md"""A call graph shows a set of function frames, with lines connecting each frame to the frames of the functions it calls. At the top of the graph, `fibonacci` with `n = 4` calls `fibonacci` with `n = 3` and `n = 2`. In turn, `fibonacci` with `n = 3` calls `fibonacci` with `n = 2`and `n = 1`, and so on.

Count how many times `fibonacci(0)` and `fibonacci(1)` are called. This is an inefficient solution to the problem, and it gets worse as the argument gets bigger.

One solution is to keep track of values that have already been computed by storing them in a dictionary. A previously computed value that is stored for later use is called a *memo*. Here is a “memoized” version of `fibonacci`:

```julia
const known = Dict(0=>0, 1=>1)

function fibonacci(n)
	if n ∈ keys(known)
		return known[n]
	end
    res = fibonacci(n-1) + fibonacci(n-2)
	known[n] = res
	res
end
```

`known` is a constant dictionary that keeps track of the Fibonacci numbers we already know. It starts with two items: `0` maps to `0` and `1` maps to `1`.

Whenever `fibonacci` is called, it checks `known`. If the result is already there, it can return immediately. Otherwise, it has to compute the new value, add it to the dictionary, and return it.

If you run this version of `fibonacci` and compare it with the original, you will find that it is much faster.
"""

# ╔═╡ 170c40ea-85da-11eb-2079-a912e41da021
md"""## Global Variables

In the previous example, `known` is created outside the function, so it belongs to the special frame called `Main`. Variables in `Main` are sometimes called *global* because they can be accessed from any function. Unlike local variables, which disappear when their function ends, global variables persist from one function call to the next.

For performance reasons, you should declare a global variable *constant* with the keyword `const`. You can no longer reassign the variable, but if it refers to a mutable value, you can modify the value.

!!! danger
    Global variables can be useful, but if you have a lot of them, and you modify them frequently, they can make programs hard to debug and perform badly.
"""

# ╔═╡ 698eb48a-85da-11eb-272f-157bb3fec731
md"""## Debugging

As you work with bigger datasets, it can become unwieldy to debug by printing and checking the output by hand. Here are some suggestions for debugging large datasets:
* Scale down the input.

  If possible, reduce the size of the dataset. For example, if the program reads a text file, start with just the first 10 lines, or with the smallest example you can find that errors. You should not edit the files themselves, but rather modify the pro‐ gram so it reads only the first n lines.

  If there is an error, you can reduce n to the smallest value that manifests the error, and then increase it gradually as you find and correct errors.

* Check summaries and types.

  Instead of printing and checking the entire dataset, consider printing summaries of the data: for example, the number of items in a dictionary or the total of an array of numbers.

  A common cause of runtime errors is a value that is not the right type. For debugging this kind of error, it is often enough to print the type of a value.

* Write self-checks.

  Sometimes you can write code to check for errors automatically. For example, if you are computing the average of an array of numbers, you could check that the result is not greater than the largest element in the array or less than the smallest. This is called a “sanity check.”

  Another kind of check compares the results of two different computations to see if they are consistent. This is called a “consistency check.”

* Format the output.

  Formatting debugging output can make it easier to spot an error. We saw an example in “Debugging”.

  Again, time you spend building scaffolding can reduce the time you spend debugging.
"""

# ╔═╡ 057b7444-85db-11eb-1dc3-179e19732c1c
md"""## Glossary

*dictionary*:
A mapping from keys to their corresponding values.

*key*:
An object that appears in a dictionary as the first part of a key-value pair.

*value*:
An object that appears in a dictionary as the second part of a key-value pair. This is more specific than our previous use of the word “value.”

*key-value pair*:
The representation of the mapping from a key to a value.

*item*:
In a dictionary, another name for a key-value pair.

*mapping*:
A relationship in which each element of one set corresponds to an element of another set.

*hash table*:
The algorithm used to implement Julia dictionaries.

*implementation*:
A way of performing a computation.

*lookup*:
A dictionary operation that takes a key and finds the corresponding value.

*reverse lookup*:
A dictionary operation that takes a value and finds one or more keys that map to it.

*singleton*:
An array (or other sequence) with a single element.

*hashable*:
A type that has a hash function.

*hash function*:
A function used by a hash table to compute the location for a key.

*call graph*:
A diagram that shows every frame created during the execution of a program, with an arrow from each caller to each callee.

*memo*:
A computed value stored to avoid unnecessary future computation.

*global variable*:
A variable defined outside a function. Global variables can be accessed from any function.

*constant global variable*
A global variable that cannot be reassigned.
"""

# ╔═╡ 64397616-85db-11eb-0150-fd07faf0ee9b
md"""## Exercises

#### Exercise 11-2

Write a function that reads the words in words.txt and stores them as keys in a dictionary. It doesn’t matter what the values are. Then you can use the `∈` operator as a fast way to check whether a string is in the dictionar.

If you did “Exercise 10-10”, you can compare the speed of this implementation with the array `∈` operator and the bisection search.
"""

# ╔═╡ 9513aed2-85db-11eb-19ee-dd547267503b
md"""#### Exercise 11-3

Read the documentation of the dictionary function `get!` and use it to write a more concise version of `invertdict`.
"""

# ╔═╡ a5cd8176-85db-11eb-193c-75864c42871e
md"""#### Exercise 11-4

Memoize the Ackermann function from “Exercise 6-5” and see if memoization makes it possible to evaluate the function with bigger arguments.
"""

# ╔═╡ b8989c78-85db-11eb-3a4a-c3bb2dc3a988
md"""#### Exercise 11-5

If you did “Exercise 10-7”, you already have a function named `hasduplicates` that takes an array as a parameter and returns true if there is any object that appears more than once in the array.

Use a dictionary to write a faster, simpler version of `hasduplicates`.
"""

# ╔═╡ d4c39484-85db-11eb-0ed1-a3268690454e
md"""#### Exercise 11-6

Two words are “rotate pairs” if you can rotate one of them and get the other (see rotateword in “Exercise 8-11”).

Write a program that reads a word array and finds all the rotate pairs.
"""

# ╔═╡ e388e7f8-85db-11eb-0451-df6af9de2adc
md"""#### Exercise 11-7

Here’s another Puzzler from Car Talk:

> [A contributor] came upon a common one-syllable, five-letter word recently that has the following unique property. When you remove the first letter, the remaining letters form a homophone of the original word, that is a word that sounds exactly the same. Replace the first letter, that is, put it back and remove the second letter and the result is yet another homophone of the original word. And the question is, what’s the word?
>
> Now I’m going to give you an example that doesn’t work. Let’s look at the five-letter word, ‘wrack.’ W-R-A-C-K, you know like to ‘wrack with pain.’ If I remove the first letter, I am left with a four-letter word, ‘R-A-C-K.’ As in, ‘Holy cow, did you see the rack on that buck! It must have been a nine-pointer!’ It’s a perfect homophone. If you put the ‘w’ back, and remove the ‘r,’ instead, you’re left with the word, ‘wack,’ which is a real word, it’s just not a homophone of the other two words.
>
> But there is, however, at least one word that [I] know of, which will yield two homophones if you remove either of the first two letters to make two, new four-letter words. The question is, what’s the word?

You can use the dictionary from “Exercise 11-2” to check whether a string is in the word array.
!!! tip
    To check whether two words are homophones, you can use the Carnegie Mellon University Pronouncing Dictionary.

Write a program that lists all the words that solve the Puzzler.
"""

# ╔═╡ Cell order:
# ╟─bcaefa4e-85c6-11eb-3316-0db4881ebd30
# ╟─13d62b06-85c7-11eb-00b9-0399467b1ab5
# ╟─1f1c885e-85c7-11eb-1452-13a8943ebc90
# ╟─27e4cf0c-85c8-11eb-0d95-ab9ddc845387
# ╟─c8dff15e-85c8-11eb-1230-bd39702cf460
# ╟─0b2e93b0-85c9-11eb-0dc3-61b1cbfb022b
# ╟─82a2f936-85c9-11eb-1d33-0d9dc98fdac0
# ╟─6d87ad02-85ca-11eb-2a3d-999d28a7c4b7
# ╟─4fb90040-85cb-11eb-23dd-ad9ea62b94c3
# ╟─26c0d704-85cf-11eb-2a13-293b42210e70
# ╟─35e8ee92-85cf-11eb-215c-21912c5fc92b
# ╟─8316e7a0-85cf-11eb-2ecb-df63279cadc6
# ╟─c1e519e8-85cf-11eb-2ce1-4bcd389f56bf
# ╟─2a7646fe-85d9-11eb-3bbe-95f975f3901f
# ╟─388a8d90-85d9-11eb-08f1-6b622cfb2c11
# ╟─170c40ea-85da-11eb-2079-a912e41da021
# ╟─698eb48a-85da-11eb-272f-157bb3fec731
# ╟─057b7444-85db-11eb-1dc3-179e19732c1c
# ╟─64397616-85db-11eb-0150-fd07faf0ee9b
# ╟─9513aed2-85db-11eb-19ee-dd547267503b
# ╟─a5cd8176-85db-11eb-193c-75864c42871e
# ╟─b8989c78-85db-11eb-3a4a-c3bb2dc3a988
# ╟─d4c39484-85db-11eb-0ed1-a3268690454e
# ╟─e388e7f8-85db-11eb-0451-df6af9de2adc


### A Pluto.jl notebook ###
# v0.14.1

using Markdown
using InteractiveUtils

# ╔═╡ 000c1c07-fd7a-4e08-9e36-3f0aa6069309
begin
	using PlutoUI

	using CSV, DataFrames, Pipe, TabularDisplay
	using Plots, StatsPlots
end

# ╔═╡ 578530b0-9b50-11eb-07e1-5db7ceb1b1c7
md"""
## Data wrangling in Julia

Based on presentations by Tom Kwong:
  - [Data Wrangling Techniques in Julia - Part 1](https://www.youtube.com/watch?v=txme9o0EdLk)
  - [Data Wrangling Techniques in Julia - Part 2](https://www.youtube.com/watch?v=NbqQZq42gLc)

$(html"
© Pascal, April 2021
")
"""

# ╔═╡ 6bcfba84-98ab-4d26-b98c-d86cb65dc277
PlutoUI.TableOfContents(indent=true, depth=4, aside=true)

# ╔═╡ f88a1737-7a84-4103-82c8-1920bae6b2e8
md"""
### What is data wrangling?

Take raw data (and/or unorganized data) and turn them (data as plural of datum) into something useful.

A possible data wrangling is as follows:

 read | $\Rightarrow$ | clean |  $\Rightarrow$ | Tidy
  --- | --- | --- | --- | ---
      |     |         $\Uparrow$ |           | $\Downarrow$
      |              | **Visualize** | $\Leftarrow$ | **Analyze**

**Julia ecosystem**:

Read | Clean, Tidy, Analyze | Visualize
--- | --- | --- |
CSV | DataFrames| Plots, StatsPlots
Parquet | DataFramesMeta | Makie, StatsMakie
SASLib | Query| Gadfly
ReadStat | JuliaDB, JuliaDBMeta | Gaston
JSON, JSON2/3 | | VegaLite
LightXML, EzXML | | UnicodePlots
XLSX, ExcelReader, Taro | |
PDFIO | |
"""

# ╔═╡ 02ac758d-3706-4bf4-8a36-83aff4422d0e
md"""
### Read

Utilize automated tools to read data into memory as cleanly as possible.

`CSV.jl`:
  - Skip leading/trailing rows
  - Parse missing values e.g "NA"
  - Parse date/time values
  - Normalize columns
  - Select/drop columns on read
  - User specified column types
  - Auto-delimiter detection
  - Automatic pooled string columns
  - Parse booleans
  - Transpose data
"""

# ╔═╡ fb1f4efc-6b89-48a6-95d5-8e3661edf53d
md"""
### Clean

  - Delete unwanted columns
  - Delete rows with missing data
  - Rename columns
  - Fix column data type
  - ...

Examples
```julia
describe(df)          # take a quick peek

select!(df, 1:5)      # keep "good" columns / delete unwanted ones
select!(df, Not(["garbaggecol1", "garbaggecol2"]))

rename!(df, "Region Name" => :region_name)

dropmissing!(df)      # Remove rows with missing data
dropmissing!(df, Between(:x3, :x5))
```

"""

# ╔═╡ f22c3d88-e0e6-4d1d-92d4-c99f80aa4733
md"""
### Tidy Data

  - Each variable forms a *column*
  - Each observation forms a *row*
  - Each type of observational unit forms a *table*

**Tidy Recipes, examples:**

```julia
# Stack all date columns into rows
# The column names are stored ion a new column "Date"
sdf = stack(df, Not(:Region_name); variable_name=:Date)
sdf = stack(df, Not(1); variable_name=:Date)
sdf = stack(df, 2:5; variable_name=:Date)

# Date column is a CategoricalArray type. Make it Date type
sdf.Date = [Date(get(x)) for x ∈ sdf.Date]
# or maybe using broadcast:
sdf.Date .= get.(sdf.Data) .|> Date

# Hoe to turn it back to a wide format?
df2 = unstack(sdf, :date, :value)

# It may introduce Union{Missing, T} column type. Let's fix it.
disallowmissing!(df2, 2:5)
```
"""

# ╔═╡ 2dc07426-ad94-46a0-8da2-68d687ad87c2
md"""
### Analyze

Get some insights from data.

**Analuyze Recipe**:

```julia
selct(df, :region_name, 4:5)                   # select by column

filter(:region_name => ==("Abilene, TX"), df)  # note df is the 2nd arg here
filter("2020-01-31" => >(400_000), df)

sort(df, :region_name)
sort(df, :region_name, rev=true)

# Transform means adding new columns - many more ways of using Transform
transform(df, :region_name => ByRow(length) => :region_name_len)

# Group
groupby(sdf, :Date)

# Summarize the grouped data:
combine(groupby(sdf, :Date), :value => mean => :avg)

# Using Pipe.jl to build a transformation pipeline
@pipe sdf |>
    groupby(_, :Date) |>
	combine(_, :value => mean => :avg)

country = DataFrame(Name=["United States"])

# Joining data is easy
innerjoin(df, country, on=[:region_name => :name])
leftjoin(df, country, on=[:region_name => :name])
rightjoin(df, country, on=[:region_name => :name])
outerjoin(df, country, on=[:region_name => :name])

# Also
semijoin(df, country, on=[:region_name => :name])
antijoin(df, country, on=[:region_name => :name])
```
"""

# ╔═╡ cb11e3e6-f385-44db-b739-cf056a7f8841
md"""
### Visualize

Gain more insight by looking at the data in a graphical form.

**Visualize Recipes:**

`Plots.jl` | `StatsPlots.jl`
--- | ---
`plot` | `groupedbar`
`scatter` | `corrplot`
`histogram` | `marginhist`
`heatmap` | `boxplot`
`bar` | `violin`
... | ...

"""

# ╔═╡ 944f1843-1802-45b7-a505-8bb32ad09902
md"""
## Let's practice

### Tidying
"""

# ╔═╡ f9d0761d-a859-404a-812f-f2a424c7b191
begin
	df = CSV.File("data/youth_suicide.csv", header=true) |> DataFrame;
	size(df)
end

# ╔═╡ 6790c878-d494-4247-ab1a-d5122d3d845b
first(df, 5)

# ╔═╡ ff4cc1c3-5967-4614-82eb-cc8771e962dc
names(df)

# ╔═╡ 5315e6b5-344e-42e7-a9a7-b53cfaf2b5b5
with_terminal() do
	displaytable(names(df), index=true)
end

# ╔═╡ 8083c3d3-51f6-437e-ba12-38f8b5814e1e
describe(df, :eltype, :nmissing, :first => first)

# ╔═╡ e70b9da8-34f9-4193-a86e-5e7dc9ed23aa
## turn colums 2 to 19 to row (observations)
sdf = stack(df, 2:19; variable_name=:type_year);

# ╔═╡ e783b82c-052e-49d3-87c8-70142b990d5f
size(sdf)

# ╔═╡ e9d507d7-df67-4bd4-b80b-02385b2f9be5
begin
	## Now spread content of column `:type_year` into 2 separate columns
	sdf[!, :ctype] .= split.(sdf.type_year, " ") .|> a -> getindex(a, 1)
	sdf[!, :year] .= split.(sdf.type_year, " ") .|> a -> getindex(a, 2)
	select!(sdf, Not(:type_year))
	first(sdf, 3)
end

# ╔═╡ 723dc0d9-96c4-43c0-81fd-611209e10106
## Let's check if the Total type really contains the total
## 1 - Pick a county
sdf[sdf.County .== "Yakima", :]

# ╔═╡ 0031eb09-8e5f-47fe-b04d-1600fad8f994
## Remove the total row which is redundant

sdf₁ = @pipe sdf |>
	filter(:ctype => ≠("Total"), _) |>
	filter(:year => ≠("(2008-2012)"), _);

# ╔═╡ f8ee2398-2b69-4133-b2d6-11a2c12b9d27
sdf₁[sdf₁.County .== "Yakima", :]

# ╔═╡ c52c8ce7-c68b-4b7b-b507-5a813d32cf93
describe(sdf₁)

# ╔═╡ 4b14a6ad-6dc5-4319-95bf-007e84ae6877
unique(sdf₁.year)
## Now we want integer instead of string and we want to get rid of the parentheses.

# ╔═╡ 54a8cfd0-5daa-40e3-8a0e-12413cc36e83
## mutate column year from String to Int
sdf₁[!, :year] .= replace.(sdf₁.year, r"[\(\)]" => "") |> a -> parse.(Int, a)

# ╔═╡ 3378787b-c9cf-4d47-a119-2a35abc3b0b4
describe(sdf₁, :eltype, :min, :max, :nunique, :nmissing)

# ╔═╡ 53aa180c-7260-46cf-a2a3-4a49e0c30844
begin
	# Male and Female variables can be put back as columns now
	df₁ = unstack(sdf₁, :ctype, :value)
	df₁[df₁.County .== "Yakima", :]
end

# ╔═╡ 56d8dcc1-8b76-4332-bbe4-f7081e178300
md"""
### Visualize
"""

# ╔═╡ 73a99bae-e8a6-44d1-af74-9778e17df7b9
# Plot King county's yearly rate
filter(:County => ==("King"), df₁)

# ╔═╡ 5eaa88b0-95e4-4410-bd99-cb9ad40fdb79
# bar chart
@pipe df₁ |>
	filter(:County => ==("King"), _) |>
	bar(_.year, _.Male,
        title = "King County Youth Suicides (Male)",
        legend = :none,
        size = (450, 300))

# ╔═╡ 5548eaac-d390-4cb1-bf49-0b8985c1f0f0
begin
	# Prepare to plot both Male and Female together
	df_stacked = stack(df₁, 3:4; variable_name = "gender");
	names(df_stacked)
end

# ╔═╡ bb947d94-62f3-45f6-a0d1-9ed596e289c8
@pipe df_stacked |>
	filter(:County => ==("King"), _) |>
	 groupedbar(    # StatsPlots.jl
		_.year,     # x-axis
	    _.value;    # y-axis
        group = _.gender,
        bar_position = :stack,
        bar_width = 0.7,
        title = "King County Suicides",
        size = (450, 300),
        legend = :topleft)

# ╔═╡ 5fadd975-cbcf-4a0e-b493-0382875354ba
@pipe df₁ |>
    groupby(_, :County)

# ╔═╡ a92c1c18-39bd-4dcf-81a7-f2f62577e156
# 5 year totals
@pipe df₁ |>
    groupby(_, :County) |>
    combine(_, :Female => sum, :Male => sum)

# ╔═╡ 2c21d894-7e57-4f92-b08d-a4d527f5c25a
# 5 year totals with male/female combined
@pipe df₁ |>
    groupby(_, :County) |>
    combine(_, :Female => sum, :Male => sum) |>
    select(_, :County, [:Female_sum, :Male_sum] => ByRow(+) => :Total)

# ╔═╡ 72aeaa39-6529-4fc3-ba86-f2b113a5a075
let
    data₁ = @pipe df₁ |>
        groupby(_, :year) |>
        combine(_, :Female => sum => :Female, :Male => sum => :Male) |>
        stack(_, 2:3; variable_name = :gender, value_name = :suicides)

    plot(data₁.year, data₁.suicides; groups = data₁.gender,
        title = "Youth Suicides Trend",
        legend = :topleft,
        linewidth = 3,
        size = (450, 300))
end

# ╔═╡ a7802df0-6819-4650-a539-ccbb99333e92
let
    data₂ = @pipe df₁ |>
        select(_, :County, :year, [:Female, :Male] => ByRow(+) => :total) |>
        unstack(_, :year, :total)

    values = Matrix(data₂[:, 2:end])

    StatsPlots.heatmap(2008:2012, data₂.County, values;
        title = "Youth Suicide Heatmap",
        xticks = :all,
        yticks = :all,
        size = (400, 600))
end

# ╔═╡ 8018394c-a123-43c4-aaaf-3d942284e182
html"""

"""

# ╔═╡ Cell order:
# ╟─578530b0-9b50-11eb-07e1-5db7ceb1b1c7
# ╠═000c1c07-fd7a-4e08-9e36-3f0aa6069309
# ╟─6bcfba84-98ab-4d26-b98c-d86cb65dc277
# ╟─f88a1737-7a84-4103-82c8-1920bae6b2e8
# ╟─02ac758d-3706-4bf4-8a36-83aff4422d0e
# ╟─fb1f4efc-6b89-48a6-95d5-8e3661edf53d
# ╟─f22c3d88-e0e6-4d1d-92d4-c99f80aa4733
# ╟─2dc07426-ad94-46a0-8da2-68d687ad87c2
# ╟─cb11e3e6-f385-44db-b739-cf056a7f8841
# ╟─944f1843-1802-45b7-a505-8bb32ad09902
# ╠═f9d0761d-a859-404a-812f-f2a424c7b191
# ╠═6790c878-d494-4247-ab1a-d5122d3d845b
# ╠═ff4cc1c3-5967-4614-82eb-cc8771e962dc
# ╠═5315e6b5-344e-42e7-a9a7-b53cfaf2b5b5
# ╠═8083c3d3-51f6-437e-ba12-38f8b5814e1e
# ╠═e70b9da8-34f9-4193-a86e-5e7dc9ed23aa
# ╠═e783b82c-052e-49d3-87c8-70142b990d5f
# ╠═e9d507d7-df67-4bd4-b80b-02385b2f9be5
# ╠═723dc0d9-96c4-43c0-81fd-611209e10106
# ╠═0031eb09-8e5f-47fe-b04d-1600fad8f994
# ╠═f8ee2398-2b69-4133-b2d6-11a2c12b9d27
# ╠═c52c8ce7-c68b-4b7b-b507-5a813d32cf93
# ╠═4b14a6ad-6dc5-4319-95bf-007e84ae6877
# ╠═54a8cfd0-5daa-40e3-8a0e-12413cc36e83
# ╠═3378787b-c9cf-4d47-a119-2a35abc3b0b4
# ╠═53aa180c-7260-46cf-a2a3-4a49e0c30844
# ╟─56d8dcc1-8b76-4332-bbe4-f7081e178300
# ╠═73a99bae-e8a6-44d1-af74-9778e17df7b9
# ╠═5eaa88b0-95e4-4410-bd99-cb9ad40fdb79
# ╠═5548eaac-d390-4cb1-bf49-0b8985c1f0f0
# ╠═bb947d94-62f3-45f6-a0d1-9ed596e289c8
# ╠═5fadd975-cbcf-4a0e-b493-0382875354ba
# ╠═a92c1c18-39bd-4dcf-81a7-f2f62577e156
# ╠═2c21d894-7e57-4f92-b08d-a4d527f5c25a
# ╠═72aeaa39-6529-4fc3-ba86-f2b113a5a075
# ╠═a7802df0-6819-4650-a539-ccbb99333e92
# ╟─8018394c-a123-43c4-aaaf-3d942284e182


### A Pluto.jl notebook ###
# v0.12.7

using Markdown
using InteractiveUtils

# ╔═╡ 845202ce-23d9-11eb-085e-ad2c26fd531c
begin
	using TextAnalysis
	using CSV
	using DataFrames
end

# ╔═╡ cac12010-23d8-11eb-0321-3fc66858fc84
md"# Authorship Analysis using TextAnalysis.jl

In this notebook we will implement an authorship analysis technique that uses n-gram frequencies for identifying authors. This technique is described in [Grieve 2018](https://research.birmingham.ac.uk/portal/files/53402456/Bixby_PREPRINT.pdf) The technique counts token and character n-grams and compares them to a reference corpus. It counts the number of overlapping n-grams in the reference corpus and identifies the author based on which reference corpus contains more of the n-grams from the text."

# ╔═╡ f400d820-23d9-11eb-091a-898d693456ad
md"First off, the necessary imports are made."

# ╔═╡ c9d140dc-2463-11eb-06a2-2d0dcb8dbed2
md"Our data will be from [Spooky Author Identification](https://www.kaggle.com/c/spooky-author-identification/overview) Kaggle contest. The data comes in the form of a CSV file which contains sentences attributed to one of three authors. The data was manualy cleaned to remove rows that were corropted as well as making sure that each author had approximately equal word counts.

The data is read into a dataframe after which we iterate over the df and turn each sentence into a StringDocument which is then accumalted into a Corpus for each author."

# ╔═╡ 68255d02-23da-11eb-3232-613c0e1b0ee2
referenceData = CSV.read("train.csv", DataFrame);

# ╔═╡ c8b1e874-23e0-11eb-1aad-995c03f040ef
begin
	MWS = GenericDocument[]
	EAP = GenericDocument[]
	HPL = GenericDocument[]

	for line in eachrow(referenceData)
		if line.author == "MWS"
			push!(MWS, StringDocument(line.text))
		elseif line.author == "EAP"
			push!(EAP, StringDocument(line.text))
		else
			push!(HPL, StringDocument(line.text))
		end
	end

	MWS_corpus = Corpus(MWS)
	EAP_corpus = Corpus(EAP)
	HPL_corpus = Corpus(HPL);
end

# ╔═╡ 60390844-2472-11eb-010b-d155abe74997
md"The following code snippet allows one to retrieve the word count of a corpus. The word counts are as follows:
- MWS_corpus: 147914
- EAP_corpus: 147911
- HPL_corpus: 147916"

# ╔═╡ 7b9ce77e-23e4-11eb-39d4-03e26b33cad7
begin
	wordCount = 0
	for doc in HPL_corpus
		global wordCount += length(ngrams(doc, 1))
	end
end

# ╔═╡ 43a58408-246f-11eb-29ab-91cc00373c2a
wordCount

# ╔═╡ 96f5d60a-248b-11eb-2795-a51a9fbf3fc0
md"Now that we have our corpora it is time to extract the ngrams that are needed for the analysis. The original paper used both character ngrams and token ngrams. Due to TextAnalysis.jl's functionality I will be restricting this analysis to only using token ngrams."

# ╔═╡ 95691ad4-2472-11eb-346e-07a9f5dc8f6c
begin
	MWS_ngrams = []
	HPL_ngrams = []
	EAP_ngrams = []
	for i in 1:4
		push!(MWS_ngrams, map(x -> ngrams(x,i), MWS_corpus))
		push!(HPL_ngrams, map(x -> ngrams(x,i), HPL_corpus))
		push!(EAP_ngrams, map(x -> ngrams(x,i), EAP_corpus))
	end
end

# ╔═╡ e207949e-2526-11eb-3ae4-0712b7082681
md"We also retrieve the token ngrams from the files we want to analyze and classify."

# ╔═╡ 62443a4c-2525-11eb-0e70-393860da7b4f
begin
	# first we load our files from memory
	text1 = FileDocument("./MWS.txt")
	text2 = FileDocument("./HPL.txt")
	text3 = FileDocument("./EAP.txt")

	# this is the same as retrieveing ngrams for the reference corpora
	text1_ngrams = []
	text2_ngrams = []
	text3_ngrams = []
	for i in 1:4
		push!(text1_ngrams, ngrams(text1, i))
		push!(text2_ngrams, ngrams(text2, i))
		push!(text3_ngrams, ngrams(text3, i))
	end
end

# ╔═╡ 32909f1e-2527-11eb-0efd-6d81943df28e
md"Finally we will a script that calculates the intersection of the ngrams from the text and the three reference corpora. To try different files edit the second for loop."

# ╔═╡ 5d1320ee-2479-11eb-3664-c33f5ace1ba7
begin
	MWS_count = 0
	HPL_count = 0
	EAP_count = 0
	for i in 1:4
		for j in text1_ngrams[i]
			for k in MWS_ngrams[i]
				if haskey(k, j[1])
					global MWS_count += 1
					break
				end
			end
			for k in HPL_ngrams[i]
				if haskey(k, j[1])
					global HPL_count += 1
					break
				end
			end
			for k in EAP_ngrams[i]
				if haskey(k, j[1])
					global EAP_count += 1
					break
				end
			end
		end
	end
end

# ╔═╡ 16a5c100-252a-11eb-1258-b769d50904d3
md"The following block will output the author as identified by this script.
The correct authors should be as follows:
- text1 = MWS
- text2 = HPL
- text3 = EAP"

# ╔═╡ df84c6ee-247a-11eb-24f9-e92653c334bb
begin
	if MWS_count > HPL_count && MWS_count > EAP_count
		"MWS"
	elseif HPL_count > MWS_count && HPL_count > EAP_count
		"HPL"
	else
		"EAP"
	end
end

# ╔═╡ 34de486c-252b-11eb-2071-f12f0f445d9f
md"## Conclusion
While this is a very watered down analysis of what the paper presents it is a good starting point and shows that this technique is viable as it correctly predicts the authors of our random test data. One way to improve the current analysis is to add character ngrams as the paper does.

All in all this was a fun project to learn more about Julia and its NLP cabalities. This project only scratches the surface of what Julia has to offer given that the TextAnalysis.jl package offers things such as part-of-speech(POS) tagging, tf-idf measures, a sentiment analysis model, and much more."

# ╔═╡ Cell order:
# ╟─cac12010-23d8-11eb-0321-3fc66858fc84
# ╟─f400d820-23d9-11eb-091a-898d693456ad
# ╠═845202ce-23d9-11eb-085e-ad2c26fd531c
# ╟─c9d140dc-2463-11eb-06a2-2d0dcb8dbed2
# ╠═68255d02-23da-11eb-3232-613c0e1b0ee2
# ╠═c8b1e874-23e0-11eb-1aad-995c03f040ef
# ╟─60390844-2472-11eb-010b-d155abe74997
# ╠═7b9ce77e-23e4-11eb-39d4-03e26b33cad7
# ╠═43a58408-246f-11eb-29ab-91cc00373c2a
# ╟─96f5d60a-248b-11eb-2795-a51a9fbf3fc0
# ╠═95691ad4-2472-11eb-346e-07a9f5dc8f6c
# ╟─e207949e-2526-11eb-3ae4-0712b7082681
# ╠═62443a4c-2525-11eb-0e70-393860da7b4f
# ╟─32909f1e-2527-11eb-0efd-6d81943df28e
# ╠═5d1320ee-2479-11eb-3664-c33f5ace1ba7
# ╟─16a5c100-252a-11eb-1258-b769d50904d3
# ╠═df84c6ee-247a-11eb-24f9-e92653c334bb
# ╟─34de486c-252b-11eb-2071-f12f0f445d9f