text:
####### VARYING SLOPES #######

## Divide up dataframes into each slope

#### rate = y ####

# empty object to store multiple plots
Psly = []

for i in 1:length(slopes)
    sl = slopes[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Slope = $sl" * " (y)")

    # filter simulation output df by slope
    dfy_temp = filter(:slope => slope -> slope == slopes[i], dfy)

    # Add to empty plot
    plot!(plt, dfy_temp.dose, dfy_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, dfy_temp.dose, dfy_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, dfy_temp.dose, dfy_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, dfy_temp.dose, dfy_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, dfy_temp.dose, dfy_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, dfy_temp.dose, dfy_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(Psly, plt)
end

#### rate = B0 ####

# empty object to store multiple plots
PslB0 = []

for i in 1:length(slopes)
    sl = slopes[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Slope = $sl" * " (B0)")

    # filter simulation output df by slope
    dfB0_temp = filter(:slope => slope -> slope == slopes[i], dfB0)

    # Add to empty plot
    plot!(plt, dfB0_temp.dose, dfB0_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, dfB0_temp.dose, dfB0_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, dfB0_temp.dose, dfB0_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, dfB0_temp.dose, dfB0_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, dfB0_temp.dose, dfB0_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, dfB0_temp.dose, dfB0_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(PslB0, plt)
end

#### rate = r ####

# empty object to store multiple plots
Pslr = []

for i in 1:length(slopes)
    sl = slopes[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Slope = $sl" * " (r)")

    # filter simulation output df by slope
    dfr_temp = filter(:slope => slope -> slope == slopes[i], dfr)

    # Add to empty plot
    plot!(plt, dfr_temp.dose, dfr_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, dfr_temp.dose, dfr_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, dfr_temp.dose, dfr_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, dfr_temp.dose, dfr_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, dfr_temp.dose, dfr_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, dfr_temp.dose, dfr_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(Pslr, plt)
end

#### rate = x ####

# empty object to store multiple plots
Pslx = []

for i in 1:length(slopes)
    sl = slopes[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Slope = $sl" * " (x)")

    # filter simulation output df by slope
    dfx_temp = filter(:slope => slope -> slope == slopes[i], dfx)

    # Add to empty plot
    plot!(plt, dfx_temp.dose, dfx_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, dfx_temp.dose, dfx_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, dfx_temp.dose, dfx_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, dfx_temp.dose, dfx_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, dfx_temp.dose, dfx_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, dfx_temp.dose, dfx_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(Pslx, plt)
end

####### VARYING ASYMPTOTES #######

#### rate = y ####
# empty object to store multiple plots
Pasy = []

for i in 1:length(asymptotes)
    as = asymptotes[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Asymptote = $as" * " (y)")

    # filter simulation output df by slope
    df2y_temp = filter(:asymptote => asymptote -> asymptote == as, df2y)

    # Add to empty plot
    plot!(plt, df2y_temp.dose, df2y_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, df2y_temp.dose, df2y_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, df2y_temp.dose, df2y_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, df2y_temp.dose, df2y_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, df2y_temp.dose, df2y_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, df2y_temp.dose, df2y_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(Pasy, plt)
end

#### rate = B0 ####
# empty object to store multiple plots
PasB0 = []

for i in 1:length(asymptotes)
    as = asymptotes[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Asymptote = $as" * " (B0)")

    # filter simulation output df by slope
    df2B0_temp = filter(:asymptote => asymptote -> asymptote == as, df2B0)

    # Add to empty plot
    plot!(plt, df2B0_temp.dose, df2B0_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, df2B0_temp.dose, df2B0_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, df2B0_temp.dose, df2B0_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, df2B0_temp.dose, df2B0_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, df2B0_temp.dose, df2B0_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, df2B0_temp.dose, df2B0_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(PasB0, plt)
end

#### rate = r ####
# empty object to store multiple plots
Pasr = []

for i in 1:length(asymptotes)
    as = asymptotes[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Asymptote = $as" * " (r)")

    # filter simulation output df by slope
    df2r_temp = filter(:asymptote => asymptote -> asymptote == as, df2r)

    # Add to empty plot
    plot!(plt, df2r_temp.dose, df2r_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, df2r_temp.dose, df2r_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, df2r_temp.dose, df2r_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, df2r_temp.dose, df2r_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, df2r_temp.dose, df2r_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, df2r_temp.dose, df2r_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(Pasr, plt)
end

#### rate = x ####
# empty object to store multiple plots
Pasx = []

for i in 1:length(asymptotes)
    as = asymptotes[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Asymptote = $as" * " (x)")

    # filter simulation output df by slope
    df2x_temp = filter(:asymptote => asymptote -> asymptote == as, df2x)

    # Add to empty plot
    plot!(plt, df2x_temp.dose, df2x_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, df2x_temp.dose, df2x_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, df2x_temp.dose, df2x_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, df2x_temp.dose, df2x_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, df2x_temp.dose, df2x_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, df2x_temp.dose, df2x_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(Pasx, plt)
end

####### VARYING INFLECTION POINTS #######

#### rate = y ####
# empty object to store multiple plots
Piny = []

for i in 1:length(inflections)
    in = inflections[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Inflection point = $in" * " (y)")

    # filter simulation output df by slope
    df3y_temp = filter(:xmid => xmid -> xmid == in, df3y)

    # Add to empty plot
    plot!(plt, df3y_temp.dose, df3y_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, df3y_temp.dose, df3y_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, df3y_temp.dose, df3y_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, df3y_temp.dose, df3y_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, df3y_temp.dose, df3y_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, df3y_temp.dose, df3y_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(Piny, plt)
end

#### rate = B0 ####
# empty object to store multiple plots
PinB0 = []

for i in 1:length(inflections)
    in = inflections[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Inflection point = $in" * " (B0)")

    # filter simulation output df by slope
    df3B0_temp = filter(:xmid => xmid -> xmid == in, df3B0)

    # Add to empty plot
    plot!(plt, df3B0_temp.dose, df3B0_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, df3B0_temp.dose, df3B0_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, df3B0_temp.dose, df3B0_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, df3B0_temp.dose, df3B0_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, df3B0_temp.dose, df3B0_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, df3B0_temp.dose, df3B0_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(PinB0, plt)
end

#### rate = r ####
# empty object to store multiple plots
Pinr = []

for i in 1:length(inflections)
    in = inflections[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Inflection point = $in" * " (r)")

    # filter simulation output df by inflection
    df3r_temp = filter(:xmid => xmid -> xmid == in, df3r)

    # Add to empty plot
    plot!(plt, df3r_temp.dose, df3r_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, df3r_temp.dose, df3r_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, df3r_temp.dose, df3r_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, df3r_temp.dose, df3r_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, df3r_temp.dose, df3r_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, df3r_temp.dose, df3r_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(Pinr, plt)
end

#### rate = x ####
# empty object to store multiple plots
Pinx = []

for i in
    1:length(inflections)
    in = inflections[i]
    # initialise an empty plot
    plt = Plots.plot([NaN], [NaN],
            label = "",
            xlims = (0,100),
            ylims = (0,1),
            foreground_colour_legend = nothing,
            xlabel = "dose",
            ylabel = "biomass extremes",
            leg = :topleft,
            title = "Inflection point = $in" * " (x)")

    # filter simulation output df by slope
    df3x_temp = filter(:xmid => xmid -> xmid == in, df3x)

    # Add to empty plot
    plot!(plt, df3x_temp.dose, df3x_temp.maxB1, ls = :dot, c = :green, lw = 3, label = "basal")
    plot!(plt, df3x_temp.dose, df3x_temp.minB1, ls = :dot, c = :green, lw = 3, label = "")
    plot!(plt, df3x_temp.dose, df3x_temp.maxB2, ls = :dot, c = :blue, lw = 3, label = "intermediate")
    plot!(plt, df3x_temp.dose, df3x_temp.minB2, ls = :dot, c = :blue, lw = 3, label = "")
    plot!(plt, df3x_temp.dose, df3x_temp.maxB3, ls = :dot, c = :red, lw = 3, label = "top")
    plot!(plt, df3x_temp.dose, df3x_temp.minB3, ls = :dot, c = :red, lw = 3, label = "")

    display(plt)

    # add bifurc plot to collection of plots
    push!(Pinx, plt)
end

max_stars_repo_name: hanamayall/simple-feeding-DR

max_issues_repo_name: hanamayall/simple-feeding-DR

max_forks_repo_name: hanamayall/simple-feeding-DR


text:
# ---
# title: 383. Ransom Note
# id: problem383
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: String
# link: <https://leetcode.com/problems/ransom-note/description/>
# hidden: true
# ---
#
# Given an arbitrary ransom note string and another string containing letters
# from all the magazines, write a function that will return true if the ransom
# note can be constructed from the magazines ; otherwise, it will return false.
#
# Each letter in the magazine string can only be used once in your ransom note.
#
#
#
# **Example 1:**
#
#
#
#     Input: ransomNote = "a", magazine = "b"
#     Output: false
#
#
# **Example 2:**
#
#
#
#     Input: ransomNote = "aa", magazine = "ab"
#     Output: false
#
#
# **Example 3:**
#
#
#
#     Input: ransomNote = "aa", magazine = "aab"
#     Output: true
#
#
#
#
# **Constraints:**
#
#   * You may assume that both strings contain only lowercase letters.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end

ext: jl


text:
# Read LCM log files directly

mutable struct lcm_eventlog_t
    f::Ptr{Cvoid}
    eventcount::Int64 #Clonglong
end

struct lcm_eventlog_event_t
    eventnum::Int64 #Clonglong
    timestamp::Int64 #Clonglong
    channellen::Int32 #Cint
    datalen::Int32 #Cint
    channel::Ptr{Cuchar}
    data::Ptr{Cuchar}
end

function lcm_eventlog_create(file::S where S <: AbstractString)
    ccall(
    (:lcm_eventlog_create, LCMCore.liblcm),
    Ptr{lcm_eventlog_t},
    (Cstring, Cstring),
    file, "r"
    )
end

function lcm_eventlog_destroy(_log::Ptr{lcm_eventlog_t})
    ccall(
    (:lcm_eventlog_destroy, LCMCore.liblcm),
    Cvoid,
    (Ptr{lcm_eventlog_t}, ),
    _log
    )
end

function lcm_eventlog_read_next_event(_log::Ptr{lcm_eventlog_t})
    ccall(
    (:lcm_eventlog_read_next_event, LCMCore.liblcm),
    Ptr{lcm_eventlog_event_t},
    (Ptr{lcm_eventlog_t},),
    _log
    )
end

#
# Doesn't work yet
# event = unsafe_wrap(lcm_eventlog_event_t, _event, dim??)

mutable struct LCMLog
    _log::Ptr{lcm_eventlog_t}
    subscriptions::Dict{AbstractString, LCMCore.SubscriptionOptions}
    function LCMLog(filename::S) where {S <: AbstractString}
        _log = lcm_eventlog_create(filename)
        if !isgood(_log)
            throw(ArgumentError("Cannot open the LCM log file at: $filename"))
        end
        return new(_log, Dict{AbstractString, LCMCore.SubscriptionOptions}())
    end
end

function close(lcmlog::LCMLog)
    if isgood(lcmlog)
        lcm_eventlog_destroy(lcmlog._log)
    end
end

isgood(_log::Ptr{lcm_eventlog_t}) = _log != C_NULL
isgood(lcmlog::LCMLog) = isgood(lcmlog._log)
isgood(_event::Ptr{lcm_eventlog_event_t}) = _event != C_NULL

function read_next_event(lcmlog::LCMLog)::Union{Nothing, lcm_eventlog_event_t}
    _event = lcm_eventlog_read_next_event(lcmlog._log)
    if isgood(_event)
        return unsafe_load(_event)
    end
    nothing
end

function handle(lcmlog::LCMLog)::Bool
    # do memory copy and then check for subscribed channel -- This is not efficient, but easiest to do. TBD is `unsafe_wrap` to avoid memory copy of _event
    event = read_next_event(lcmlog)
    if event != nothing
        # need a convert from lcm_eventlog_event_t to (RecvBuf, channelbytes, chnlen)
        rb = LCMCore.RecvBuf(event.data, UInt32(event.datalen), event.timestamp, 0)
        # need a LCMCore.SubscriptionOptions{T}
        chn = unsafe_string(event.channel, event.channellen)
        if haskey(lcmlog.subscriptions, chn)
            opts = lcmlog.subscriptions[chn]
            # use onresponse similar to regular live LCM traffic
            LCMCore.onresponse(rb, event.channel, opts)
        end
        return true
    end
    return false
end

function subscribe(lcmlog::LCMLog, channel::S, callback::F, msgtype=Nothing) where {S <: AbstractString, F}
    opts = SubscriptionOptions(msgtype, callback, channel)
    lcmlog.subscriptions[channel] = opts
    nothing
end


text:
# include("splitting_measures_mse.jl")
include("difference.jl")
include("msePointwise.jl")
include("mse.jl")
include("poissonGammaNormal.jl")

# these are not maintained any more
include("maxsplitvalue.jl")
include("maxabssplitvalue.jl")
include("maxminussplitvalue.jl")


text:
using Distributions

function sliceSampleStep(logf, x0, y0, w=1.0)
    newy = y0 + log(rand())

    # step outward
    L = x0 - w * rand()
    R = L + w
    yL = logf(L)
    yR = logf(R)
    while yL > newy || yR > newy
        if rand(Bool)
            L -= R - L
            yL = logf(L)
        else
            R += R - L
            yR = logf(L)
        end
    end

    #Step inward
    x = rand(Uniform(L, R))
    y = logf(x)
    while y < newy
        if x < x0
            L = x
            yL = logf(L)
        else
            R = x
            yR = logf(R)
        end
        x = rand(Uniform(L, R))
        y = logf(x)
    end

    return (x, y)
end

function sliceSample(logf, n, x0=0.0, y0=0.0, w=1.0)
    (x,y) = (x0,logf(x0))
    xs = zeros(n)
    for i in 1:n
        (x,y) = sliceSampleStep(logf, x, y, w)
        xs[i] = x
    end

    return xs
end

function test(n)
    sliceSample(x -> logpdf(Normal(),x), n)
end

x = test(10000)
scatter(x[2:10000],x[1:9999],α=0.1)


text: f(x) = 2x


text:
using DataStructures

mutable struct Compound
  word     :: String
  rules    :: Array{Int16}              # List of rules (pattern => letter) by index
  patterns :: Array{String}             # List of two-letter patterns
  letters  :: Array{Char}               # List of intercalation letters
  chains   :: Array{Tuple{Int16,Int16}} # Tree of rule relations
end

function Compound(word::String,rules::Array{Int16},patterns::Array{String},letters::Array{Char})
  chains :: Array{Tuple{Int16,Int16}} = []
  for iR in 1:length(rules) # The rule AB -> C will create two new patterns (AC,CB)
    pattern1 = string(patterns[iR][1],letters[rules[iR]])
    pattern2 = string(letters[rules[iR]], patterns[iR][2])
    push!(chains,(findfirst(isequal(pattern1),patterns), findfirst(isequal(pattern2),patterns)))
  end
  return Compound(word, rules, patterns, letters, chains)
end

function parseLine(line::String) :: Pair{String,String}
  return Pair(map(s -> string(s), split(line," -> "))...)
end

function readInput()
  lines = readlines("./inputs/day14.txt")

  word = lines[1]

  nPattern :: Int16 = 0
  nLetter  :: Int16 = 0
  iLetter  :: Int16 = 0
  rules    :: Array{Int16} = []
  patterns :: Array{String} = []
  letters  :: Array{Char} = []
  for line in lines[3:end]
    rule = parseLine(line)

    push!(patterns,rule.first)
    nPattern += 1

    if in(first(rule.second),letters)
      iLetter = findfirst(c->c==first(rule.second),letters)
    else
      nLetter += 1
      iLetter = nLetter
      push!(letters,first(rule.second))
    end

    push!(rules,iLetter)
  end

  return Compound(word,rules,patterns,letters)
end

############################################################################################
# Non-optimised way of solving the problem.

function steps!(nSteps::Int, pattern::Int16, c::Compound, cnter::Array{Int})
  (nSteps == 0) && return

  cnter[c.rules[pattern]] += 1
  steps!(nSteps-1,c.chains[pattern][1],c,cnter)
  steps!(nSteps-1,c.chains[pattern][2],c,cnter)
end

function solve(nSteps::Int, c::Compound)
  cnter :: Array{Int} = map(l -> count(x->x==l,c.word), c.letters)
  for i in 1:length(c.word)-1
    pattern :: Int16 = findfirst(isequal(c.word[i:i+1]), c.patterns)
    steps!(nSteps, pattern, c, cnter)
  end
  return maximum(cnter) - minimum(cnter)
end

############################################################################################
# Optimised procedure: Just count the total number of each pattern and update at each step.

function dostep(c::Compound, cnter::Array{Int}) :: Array{Int}
  newCnter :: Array{Int} = fill(0,length(cnter))
  for i in 1:length(c.patterns)
    newCnter[c.chains[i][1]] += cnter[i]
    newCnter[c.chains[i][2]] += cnter[i]
  end
  return newCnter
end

function solve2(nSteps::Int, c::Compound)
  cnter :: Array{Int} = fill(0,length(c.patterns))
  for i in 1:length(c.word)-1
    pattern :: Int16 = findfirst(isequal(c.word[i:i+1]), c.patterns)
    cnter[pattern] += 1
  end
  for i in 1:nSteps
    cnter = dostep(c,cnter)
  end
  res :: Array{Int} = fill(0,length(c.letters))
  for i in 1:length(cnter)
    letter = first(c.patterns[i])
    res[findfirst(isequal(letter),c.letters)] += cnter[i]
  end
  res[findfirst(isequal(c.word[end]),c.letters)] += 1
  return maximum(res) - minimum(res)
end

############################################################################################
myCompound = readInput()

nSteps = 40
res = solve2(nSteps,myCompound)
# res = solve(nSteps,myCompound)
println(res)


text:
import SSGE
using Test

using Distributions
using LinearAlgebra
using ForwardDiff
using Distances
import Random

@testset "SSGEstimator" begin
    F = Float32
    test_dist = MvNormal([F(2),F(2)], Symmetric([F(1) F(0.3); F(0.3) F(1)]))

    test_log_pdf(x) = log.(pdf(test_dist, x))
    # Analytic solution
    ∇test_log_pdf(x) = ForwardDiff.gradient(test_log_pdf, x)

    Random.seed!(1337)
    num_samples = 100
    x_samples = rand(test_dist, num_samples)
    num_eig_fun = 3
    σ = F(1000.0)

    g = SSGE.SSGEstimator(x_samples, num_eig_fun, SSGE.SqExp(σ));

    num_tests = 100
    x_tests = rand(test_dist, num_tests)

    @test mean([norm(∇test_log_pdf(x_tests[:, i]) - g(x_tests[:, i])) for i in 1:num_tests]) < 0.4

    g_tuned = SSGE.SSGEstimator(x_samples, F(0.99));
    @test mean([norm(∇test_log_pdf(x_tests[:, i]) - g_tuned(x_tests[:, i])) for i in 1:num_tests]) < 0.45

end