Biased coins
(subtitle: the Central limit theorem, a milestone)
We start with simulations of a biased coin. The coin produces “HEADS” 60% of the time and “TAILS” 40% of the time. It is biased. The question is: how many throws are needed to find this out?
First, let’s simulate tosses. Easy with the rbernoulli() function:
library(tidyverse)
library(plotly)
n <- 10
rbernoulli(n, p = 0.6)
[1] FALSE TRUE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE
Ok, so let’s represent 40 draws. We will code HEADS as positive (TRUE) values and TAILS as negative (FALSE) values. We use set.seed() to fix the random seed. This way, in all chunks, results will be the same.
set.seed(42) # This fixed the random number generator
n <- 40
tibble(coin = rbernoulli(n, p = 0.6)) %>%
mutate(toss = coin - 0.5,
id = row_number()) %>%
ggplot(aes(x = id, y = toss, fill = toss)) + geom_col()

Now, let’s take the cumulative average: each time a new toss happens, we compute the average of HEADS.
set.seed(42) # This fixed the random number generator
N <- 40
tibble(coin = rbernoulli(N, p = 0.6)) %>%
mutate(toss = coin-0.5,
n = row_number(),
cum_avg = cummean(coin)) %>%
ggplot() +
geom_col(aes(x = n, y = toss, fill = toss)) +
geom_line(aes(x = n, y = cum_avg)) +
annotate("text", x = 43, y = 0.25, label = "HEADS", color = "#0077BB") +
annotate("text", x = 43, y = -0.25, label = "TAILS", color = "#000011")

After 40 trials, we are at a 75% rate!
Let’s take more…
set.seed(42) # This fixed the random number generator
N <- 100
tibble(coin = rbernoulli(N, p = 0.6)) %>%
mutate(toss = coin-0.5,
n = row_number(),
cum_avg = cummean(coin)) %>%
ggplot() +
geom_col(aes(x = n, y = toss, fill = toss)) +
geom_line(aes(x = n, y = cum_avg)) +
geom_hline(yintercept = 0.6, color = "#999999", linetype = 2) +
annotate("text", x = 50, y = 0.82, label = "cumulative average")

After 100 draws, we are getting closer to the “true” frequency, which is 60%. What the Central Limit Theorem implies is that the “speed” of convergence is equal to the standard deviation of the distribution divided by \(\sqrt{n}\).
The variable (cum_avg-0.6)/sqrt(n) converges towards a Gaussian distribution.
So, basically, the more draws we make the more confident we are about the bias of the coin.
set.seed(42) # This fixed the random number generator
N <- 400
tibble(coin = rbernoulli(N, p = 0.6)) %>%
mutate(toss = coin-0.5,
n = row_number(),
cum_avg = cummean(coin),
cum_low = cum_avg - 0.5/sqrt(n),
cum_high = cum_avg + 0.5/sqrt(n)) %>%
ggplot() +
geom_ribbon(aes(x = n, ymin = cum_low, ymax = cum_high), fill = "#AAAAAA", alpha = 0.5, color = "#777777") +
geom_col(aes(x = n, y = toss, fill = toss)) +
geom_line(aes(x = n, y = cum_avg)) +
geom_hline(yintercept = 0.6, color = "#999999", linetype = 2) +
annotate("text", x = 9, y = 1.22, label = "confidence \n interval", color = "#999999") +
annotate("text", x = 125, y = 0.82, label = "cumulative average") +
scale_x_sqrt() + xlab("n (sqrt-scale)")

At first, the confidence interval (at some given level) is very large. Even 0.5 is included inside it! However, after ~20-25 trials, it becomes clear that 0.5 is no longer inside it. After \(n=100\), 0.6 is almost always included in the confidence interval. Thus, in this case, we can say that it takes: - roughly 25 draws to understand that the coin is biased; - but at least 100 draws to be confident whereas to the level of the bias.
Back to the data
Ok so let’s say you have a dataset of songs. In it, you have artists’ names and the popularity of songs.
A natural question that pops up is whether a given artist is more popular than another one…
Of course, this supposes to have enough songs for each artist: if we compare artists with 2-3 songs, the result will not be robust (more on that later).
First let’s load the data & the tidyverse. patchwork is a package that helps arrange graphs.
library(tidyverse)
library(patchwork)
load("songs.RData")
Then, let’s have a look a which artists have enough songs.
songs %>%
group_by(artist) %>%
count(sort = TRUE) %>%
filter(n > 15)
Drake & Kanye West have the same number, 33, which is enough. Let’s go with them!
First analysis
First: the average popularity.
songs %>%
filter(artist %in% c("Drake", "Kanye West")) %>%
group_by(artist) %>%
summarise(avg_pop = mean(popularity))
So, Kanye West is slightly more popular, on average.
Now, the question is: is this small edge statistically significant.
Let’s have a look at the details.
songs_kanye <- songs %>%
filter(artist == "Kanye West") %>%
ggplot(aes(y = reorder(song_name, popularity), x = popularity)) + ggtitle("Kanye West") +
geom_col(alpha = 0.5) + theme_light() + ylab("") + geom_vline(xintercept = 62.8)
songs_drake <- songs %>%
filter(artist == "Drake") %>%
ggplot(aes(y = reorder(song_name, popularity), x = popularity)) + ggtitle("Drake") +
geom_col(alpha = 0.5) + theme_light() + ylab("") + geom_vline(xintercept = 60.6)
songs_kanye | songs_drake

A lot of the songs are fairly close to the average, but there are some outliers!
Typically, both artists have one song that cripples their average! (the bottom one).
One step back
Given the fact that the popularity score ranges between 0 and 1, a first guess would be that, on average, a song has a popularity of ~50.
Below we plot the histogram of popularity across all 13K+ songs.
songs %>% ggplot(aes(x = popularity)) +
geom_histogram(alpha = 0.5) + geom_vline(xintercept = 50) + theme_light() +
geom_vline(xintercept = mean(songs$popularity), color = "red") +
annotate(geom = "text", x = 42, y = 1100, label = "sample \n average", color = "red")

The sample average is indeed rather close to 50!
Now, let’s do another exercise:
1. we take the first 10 songs and compute the average popularity. 2. we take the first 100 songs and compute the average popularity. 3. we take the first 1,000 songs and compute the average popularity. 4. we take the first 10,000 songs and compute the average popularity.
Of course, the average popularity is going to change for each step. But, as the number of songs grows, the change will be small because the average will slowly converge to the population average. We illustrate this below. The right graph uses a log-scale for the x-axis.
g1 <- songs %>%
mutate(nb_row = row_number(),
cum_pop = cumsum(popularity),
avg_pop = cum_pop/nb_row) %>%
ggplot(aes(x = nb_row, y = avg_pop)) + geom_line() + theme_light() +
annotate(geom = "text", x = 2980, y = 72, label = "small sample \n = \n instable average") +
annotate(geom = "text", x = 11800, y = 50, label = "full sample") +
xlab("Number of songs used to compute the average") +
ylab("Average popularity")
g2 <- songs %>%
mutate(nb_row = row_number(),
cum_pop = cumsum(popularity),
avg_pop = cum_pop/nb_row) %>%
ggplot(aes(x = nb_row, y = avg_pop)) + geom_line() + theme_light() +
annotate(geom = "text", x = 50, y = 60, label = "small \n sample \n = \n instable \n average") +
xlab("Number of songs (sqrt-scale)") +
ylab("") +
scale_x_sqrt()
g1 | g2

Now, the line crossed the 50 threshold at some point: who is to say it won’t do it again?
The problem is that as the number of songs grows, large shifts become improbable because they would require many new songs to have a popularity which is strictly above 50.
One important lesson is that the sample size matters a lot in tests.
A very important result in probability is the Central Limit Theorem. Simply put, it states that if things remain always the same (stationarity in distribution with finite variance), then the distribution of the sample mean converges to a Gaussian distribution.
The tests
Back to the Gaussian distribution.
mean_kanye <- mean(songs_kanye$data$popularity)
sd_kanye <- sd(songs_kanye$data$popularity)
mean_drake <- mean(songs_drake$data$popularity)
g_norm <- songs_kanye$data %>%
ggplot(aes(x = popularity)) +
geom_point(data = tibble(x = seq(15, 100, by = 0.1), y = dnorm(x, mean = mean_kanye, sd = sd_kanye)),
aes(x = x, y = y), size = 0.3) +
geom_point(aes(x = popularity, y = dnorm(popularity, mean = mean_kanye, sd = sd_kanye), label = song_name),
color = "#2266FF", size = 2) +
xlim(15,100) + theme_light() + xlab("Popularity (of Kanye West)") +
geom_vline(xintercept = mean_drake, color = "#555555") +
annotate("text", x = 57.5, y = 0.023, label = "Drake", color = "#555555")
Ignoring unknown aesthetics: label
#ggplotly(g_norm)
g_norm

Basically, if we assume that Kanye & Drake have the same average popularity, then their popularity should be such that their mean “behaves” like a Gaussian distribution with a standard deviation that is shrunk by \(\sqrt(33)\).
A statistical test will determine how likely we would be to observe data that would reject the hypothesis that the two means are equal. Our baseline hypothesis is: Kanye West & Drake have equal popularities.
We plot the Gaussian curve and the t-stat (which comes from the CLT!).
mean_kanye <- mean(songs_kanye$data$popularity)
sd_kanye <- sd(songs_kanye$data$popularity) / sqrt(33)
mean_drake <- mean(songs_drake$data$popularity)
g_norm2 <- tibble(x = c(-5,5)) %>%
ggplot(aes(x = x)) +
geom_point(data = tibble(x = seq(-5, 5, by = 0.1), y = dnorm(x, mean = 0, sd = 1)),
aes(x = x, y = y), size = 0.3) +
xlim(-5,5) + theme_light() +
geom_vline(xintercept = (mean_kanye - mean_drake)/sd_kanye, color = "#555555") +
annotate("text", x = 1, y = 0.42, label = "t-stat", color = "#555555")
g_norm2

Overall, the statistic lies in the bulk of the distribution: the situation is not “extreme”, so it’s difficult to conclude that the two means are truly different. Basically, it would happen often that 2 artists with same popularity have songs that are rated like those in the dataset.
In R, it’s easy to perform t-tests.
t.test(songs_kanye$data$popularity, songs_drake$data$popularity)
Welch Two Sample t-test
data: songs_kanye$data$popularity and songs_drake$data$popularity
t = 0.48815, df = 63.758, p-value = 0.6271
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
-7.028972 11.574426
sample estimates:
mean of x mean of y
62.84848 60.57576
The p-value (0.62) has a tricky interpretation: it tells us how probable it is to encounter similar datasets such that the two means are equal.
A counter-example
songs %>%
group_by(artist) %>%
summarize(avg_pop = mean(popularity),
nb_songs = n()) %>%
arrange(desc(nb_songs)) %>%
head(10)
Now let’s compare David Guetta and Future: fewer songs, and much bigger difference in popularity! The vertical black lines shows the average popularity of the artists.
songs_guetta <- songs %>%
filter(artist == "David Guetta") %>%
ggplot(aes(y = reorder(song_name, popularity), x = popularity)) + ggtitle("David Guetta") +
geom_col(alpha = 0.3, fill = "#0099EE") + theme_light() + ylab("") + geom_vline(xintercept = 53.8) +
xlab("") + xlim(0,90) + theme(axis.text.y = element_text(size = 7))
songs_williams <- songs %>%
filter(artist == "Hank Williams") %>%
ggplot(aes(y = reorder(song_name, popularity), x = popularity)) + ggtitle("Hank Williams") +
geom_col(alpha = 0.3, fill = "#EE9900") + theme_light() + ylab("") + geom_vline(xintercept = 33.9) + xlim(0,90) +
theme(axis.text.y = element_text(size = 7))
songs_guetta / songs_williams

Now let’s have a look at the test!
pop_guetta <- songs %>% filter(artist == "David Guetta") %>% pull(popularity)
pop_williams <- songs %>% filter(artist == "Hank Williams") %>% pull(popularity)
t.test(pop_guetta, pop_williams)
Welch Two Sample t-test
data: pop_guetta and pop_williams
t = 2.7969, df = 21.786, p-value = 0.01057
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
5.131275 34.633431
sample estimates:
mean of x mean of y
53.82353 33.94118
The p-value is small: it’s unlikely to find data with 2 equally popular artist that would have such a discrepancy in the rating of their songs…
LS0tCnRpdGxlOiAiU3RhdGlzdGljYWwgVGVzdGluZyBtYWRlIGZ1biIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBCaWFzZWQgY29pbnMKCgooc3VidGl0bGU6ICp0aGUgQ2VudHJhbCBsaW1pdCB0aGVvcmVtLCBhIG1pbGVzdG9uZSopCgpXZSBzdGFydCB3aXRoIHNpbXVsYXRpb25zIG9mIGEgYmlhc2VkIGNvaW4uIFRoZSBjb2luIHByb2R1Y2VzICIqSEVBRFMqIiA2MCUgb2YgdGhlIHRpbWUgYW5kICIqVEFJTFMqIiA0MCUgb2YgdGhlIHRpbWUuIEl0IGlzICoqYmlhc2VkKiouIFRoZSBxdWVzdGlvbiBpczogaG93IG1hbnkgdGhyb3dzIGFyZSBuZWVkZWQgdG8gZmluZCB0aGlzIG91dD8KCkZpcnN0LCBsZXQncyBzaW11bGF0ZSB0b3NzZXMuIEVhc3kgd2l0aCB0aGUgKnJiZXJub3VsbGkqKCkgZnVuY3Rpb246CgpgYGB7ciwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwbG90bHkpCm4gPC0gMTAKcmJlcm5vdWxsaShuLCBwID0gMC42KQpgYGAKT2ssIHNvIGxldCdzIHJlcHJlc2VudCA0MCBkcmF3cy4gV2Ugd2lsbCBjb2RlIEhFQURTIGFzIHBvc2l0aXZlIChUUlVFKSB2YWx1ZXMgYW5kIFRBSUxTIGFzIG5lZ2F0aXZlIChGQUxTRSkgdmFsdWVzLiBXZSB1c2Ugc2V0LnNlZWQoKSB0byBmaXggdGhlICpyYW5kb20gc2VlZCouIFRoaXMgd2F5LCBpbiBhbGwgY2h1bmtzLCByZXN1bHRzIHdpbGwgYmUgdGhlIHNhbWUuCgpgYGB7cn0Kc2V0LnNlZWQoNDIpICAjIFRoaXMgZml4ZWQgdGhlIHJhbmRvbSBudW1iZXIgZ2VuZXJhdG9yCm4gPC0gNDAKdGliYmxlKGNvaW4gPSByYmVybm91bGxpKG4sIHAgPSAwLjYpKSAlPiUKICAgIG11dGF0ZSh0b3NzID0gY29pbiAtIDAuNSwKICAgICAgICAgICBpZCA9IHJvd19udW1iZXIoKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBpZCwgeSA9IHRvc3MsIGZpbGwgPSB0b3NzKSkgKyBnZW9tX2NvbCgpCmBgYApOb3csIGxldCdzIHRha2UgdGhlIGN1bXVsYXRpdmUgYXZlcmFnZTogZWFjaCB0aW1lIGEgbmV3IHRvc3MgaGFwcGVucywgd2UgY29tcHV0ZSB0aGUgYXZlcmFnZSBvZiBIRUFEUy4KCmBgYHtyfQpzZXQuc2VlZCg0MikgICMgVGhpcyBmaXhlZCB0aGUgcmFuZG9tIG51bWJlciBnZW5lcmF0b3IKTiA8LSA0MAp0aWJibGUoY29pbiA9IHJiZXJub3VsbGkoTiwgcCA9IDAuNikpICU+JQogICAgbXV0YXRlKHRvc3MgPSBjb2luLTAuNSwKICAgICAgICAgICBuID0gcm93X251bWJlcigpLAogICAgICAgICAgIGN1bV9hdmcgPSBjdW1tZWFuKGNvaW4pKSAlPiUKICAgIGdncGxvdCgpICsgCiAgICBnZW9tX2NvbChhZXMoeCA9IG4sIHkgPSB0b3NzLCBmaWxsID0gdG9zcykpICsgCiAgICBnZW9tX2xpbmUoYWVzKHggPSBuLCB5ID0gY3VtX2F2ZykpICsKICAgIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDQzLCB5ID0gMC4yNSwgbGFiZWwgPSAiSEVBRFMiLCBjb2xvciA9ICIjMDA3N0JCIikgKwogICAgYW5ub3RhdGUoInRleHQiLCB4ID0gNDMsIHkgPSAtMC4yNSwgbGFiZWwgPSAiVEFJTFMiLCBjb2xvciA9ICIjMDAwMDExIikKYGBgCgpBZnRlciA0MCB0cmlhbHMsIHdlIGFyZSBhdCBhIDc1JSByYXRlISAgIApMZXQncyB0YWtlIG1vcmUuLi4KCmBgYHtyfQpzZXQuc2VlZCg0MikgICMgVGhpcyBmaXhlZCB0aGUgcmFuZG9tIG51bWJlciBnZW5lcmF0b3IKTiA8LSAxMDAKdGliYmxlKGNvaW4gPSByYmVybm91bGxpKE4sIHAgPSAwLjYpKSAlPiUKICAgIG11dGF0ZSh0b3NzID0gY29pbi0wLjUsCiAgICAgICAgICAgbiA9IHJvd19udW1iZXIoKSwKICAgICAgICAgICBjdW1fYXZnID0gY3VtbWVhbihjb2luKSkgJT4lCiAgICBnZ3Bsb3QoKSArIAogICAgZ2VvbV9jb2woYWVzKHggPSBuLCB5ID0gdG9zcywgZmlsbCA9IHRvc3MpKSArIAogICAgZ2VvbV9saW5lKGFlcyh4ID0gbiwgeSA9IGN1bV9hdmcpKSArIAogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC42LCBjb2xvciA9ICIjOTk5OTk5IiwgbGluZXR5cGUgPSAyKSArCiAgICBhbm5vdGF0ZSgidGV4dCIsIHggPSA1MCwgeSA9IDAuODIsIGxhYmVsID0gImN1bXVsYXRpdmUgYXZlcmFnZSIpCmBgYApBZnRlciAxMDAgZHJhd3MsIHdlIGFyZSBnZXR0aW5nIGNsb3NlciB0byB0aGUgInRydWUiIGZyZXF1ZW5jeSwgd2hpY2ggaXMgNjAlLgpXaGF0IHRoZSAqKkNlbnRyYWwgTGltaXQgVGhlb3JlbSoqIGltcGxpZXMgaXMgdGhhdCB0aGUgIipzcGVlZCoiIG9mIGNvbnZlcmdlbmNlIGlzIGVxdWFsIHRvIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIGRpc3RyaWJ1dGlvbiBkaXZpZGVkIGJ5ICRcc3FydHtufSQuICAgIApUaGUgdmFyaWFibGUgKGN1bV9hdmctMC42KS9zcXJ0KG4pIGNvbnZlcmdlcyB0b3dhcmRzIGEgKipHYXVzc2lhbioqIGRpc3RyaWJ1dGlvbi4gIApTbywgYmFzaWNhbGx5LCB0aGUgbW9yZSBkcmF3cyB3ZSBtYWtlIHRoZSBtb3JlIGNvbmZpZGVudCB3ZSBhcmUgYWJvdXQgdGhlICpiaWFzKiBvZiB0aGUgY29pbi4KCgpgYGB7cn0Kc2V0LnNlZWQoNDIpICAjIFRoaXMgZml4ZWQgdGhlIHJhbmRvbSBudW1iZXIgZ2VuZXJhdG9yCk4gPC0gNDAwCnRpYmJsZShjb2luID0gcmJlcm5vdWxsaShOLCBwID0gMC42KSkgJT4lCiAgICBtdXRhdGUodG9zcyA9IGNvaW4tMC41LAogICAgICAgICAgIG4gPSByb3dfbnVtYmVyKCksCiAgICAgICAgICAgY3VtX2F2ZyA9IGN1bW1lYW4oY29pbiksCiAgICAgICAgICAgY3VtX2xvdyA9IGN1bV9hdmcgLSAwLjUvc3FydChuKSwKICAgICAgICAgICBjdW1faGlnaCA9IGN1bV9hdmcgKyAwLjUvc3FydChuKSkgJT4lCiAgICBnZ3Bsb3QoKSArIAogICAgZ2VvbV9yaWJib24oYWVzKHggPSBuLCB5bWluID0gY3VtX2xvdywgeW1heCA9IGN1bV9oaWdoKSwgZmlsbCA9ICIjQUFBQUFBIiwgYWxwaGEgPSAwLjUsIGNvbG9yID0gIiM3Nzc3NzciKSArIAogICAgZ2VvbV9jb2woYWVzKHggPSBuLCB5ID0gdG9zcywgZmlsbCA9IHRvc3MpKSArIAogICAgZ2VvbV9saW5lKGFlcyh4ID0gbiwgeSA9IGN1bV9hdmcpKSArIAogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC42LCBjb2xvciA9ICIjOTk5OTk5IiwgbGluZXR5cGUgPSAyKSArCiAgICBhbm5vdGF0ZSgidGV4dCIsIHggPSA5LCB5ID0gMS4yMiwgbGFiZWwgPSAiY29uZmlkZW5jZSBcbiBpbnRlcnZhbCIsIGNvbG9yID0gIiM5OTk5OTkiKSArCiAgICBhbm5vdGF0ZSgidGV4dCIsIHggPSAxMjUsIHkgPSAwLjgyLCBsYWJlbCA9ICJjdW11bGF0aXZlIGF2ZXJhZ2UiKSArCiAgICBzY2FsZV94X3NxcnQoKSArIHhsYWIoIm4gKHNxcnQtc2NhbGUpIikKYGBgCkF0IGZpcnN0LCB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbCAoYXQgc29tZSBnaXZlbiBsZXZlbCkgaXMgdmVyeSBsYXJnZS4gRXZlbiAwLjUgaXMgaW5jbHVkZWQgaW5zaWRlIGl0ISBIb3dldmVyLCBhZnRlciB+MjAtMjUgdHJpYWxzLCBpdCBiZWNvbWVzIGNsZWFyIHRoYXQgMC41IGlzIG5vIGxvbmdlciBpbnNpZGUgaXQuCkFmdGVyICRuPTEwMCQsIDAuNiBpcyBhbG1vc3QgYWx3YXlzIGluY2x1ZGVkIGluIHRoZSBjb25maWRlbmNlIGludGVydmFsLiBUaHVzLCBpbiAqdGhpcyogY2FzZSwgd2UgY2FuIHNheSB0aGF0IGl0IHRha2VzOgotIHJvdWdobHkgMjUgZHJhd3MgdG8gdW5kZXJzdGFuZCB0aGF0IHRoZSBjb2luIGlzIGJpYXNlZDsKLSBidXQgYXQgbGVhc3QgMTAwIGRyYXdzIHRvIGJlIGNvbmZpZGVudCB3aGVyZWFzIHRvIHRoZSAqbGV2ZWwqIG9mIHRoZSBiaWFzLgoKCiMgQmFjayB0byB0aGUgZGF0YQoKT2sgc28gbGV0J3Mgc2F5IHlvdSBoYXZlIGEgZGF0YXNldCBvZiAqKnNvbmdzKiouIEluIGl0LCB5b3UgaGF2ZSBhcnRpc3RzJyBuYW1lcyBhbmQgdGhlIHBvcHVsYXJpdHkgb2Ygc29uZ3MuICAgIApBIG5hdHVyYWwgcXVlc3Rpb24gdGhhdCBwb3BzIHVwIGlzIHdoZXRoZXIgYSBnaXZlbiBhcnRpc3QgaXMgbW9yZSBwb3B1bGFyIHRoYW4gYW5vdGhlciBvbmUuLi4gICAKT2YgY291cnNlLCB0aGlzIHN1cHBvc2VzIHRvIGhhdmUgZW5vdWdoIHNvbmdzIGZvciBlYWNoIGFydGlzdDogaWYgd2UgY29tcGFyZSBhcnRpc3RzIHdpdGggMi0zIHNvbmdzLCB0aGUgcmVzdWx0IHdpbGwgbm90IGJlIHJvYnVzdCAobW9yZSBvbiB0aGF0IGxhdGVyKS4gICAKCkZpcnN0IGxldCdzIGxvYWQgdGhlICoqZGF0YSoqICYgdGhlICoqdGlkeXZlcnNlKiouICoqcGF0Y2h3b3JrKiogaXMgYSBwYWNrYWdlIHRoYXQgaGVscHMgYXJyYW5nZSBncmFwaHMuCgpgYGB7ciwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwYXRjaHdvcmspCmxvYWQoInNvbmdzLlJEYXRhIikKYGBgCgpUaGVuLCBsZXQncyBoYXZlIGEgbG9vayBhIHdoaWNoIGFydGlzdHMgaGF2ZSBlbm91Z2ggc29uZ3MuCgpgYGB7ciwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQpzb25ncyAlPiUKICAgIGdyb3VwX2J5KGFydGlzdCkgJT4lCiAgICBjb3VudChzb3J0ID0gVFJVRSkgJT4lCiAgICBmaWx0ZXIobiA+IDE1KQpgYGAKCkRyYWtlICYgS2FueWUgV2VzdCBoYXZlIHRoZSBzYW1lIG51bWJlciwgMzMsIHdoaWNoIGlzIGVub3VnaC4gTGV0J3MgZ28gd2l0aCB0aGVtISAgIAoKCiMgRmlyc3QgYW5hbHlzaXMgIAoKRmlyc3Q6IHRoZSBhdmVyYWdlIHBvcHVsYXJpdHkuCgpgYGB7ciwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQpzb25ncyAlPiUKICAgIGZpbHRlcihhcnRpc3QgJWluJSBjKCJEcmFrZSIsICJLYW55ZSBXZXN0IikpICU+JQogICAgZ3JvdXBfYnkoYXJ0aXN0KSAlPiUKICAgIHN1bW1hcmlzZShhdmdfcG9wID0gbWVhbihwb3B1bGFyaXR5KSkKYGBgCgpTbywgS2FueWUgV2VzdCBpcyBzbGlnaHRseSBtb3JlIHBvcHVsYXIsIG9uIGF2ZXJhZ2UuICAgCk5vdywgdGhlIHF1ZXN0aW9uIGlzOiBpcyB0aGlzIHNtYWxsIGVkZ2UgKnN0YXRpc3RpY2FsbHkqIHNpZ25pZmljYW50LiAgIApMZXQncyBoYXZlIGEgbG9vayBhdCB0aGUgZGV0YWlscy4KCmBgYHtyLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CnNvbmdzX2thbnllIDwtIHNvbmdzICU+JQogICAgZmlsdGVyKGFydGlzdCA9PSAiS2FueWUgV2VzdCIpICU+JQogICAgZ2dwbG90KGFlcyh5ID0gcmVvcmRlcihzb25nX25hbWUsIHBvcHVsYXJpdHkpLCB4ID0gcG9wdWxhcml0eSkpICsgZ2d0aXRsZSgiS2FueWUgV2VzdCIpICsgCiAgICBnZW9tX2NvbChhbHBoYSA9IDAuNSkgKyB0aGVtZV9saWdodCgpICsgeWxhYigiIikgKyBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA2Mi44KQpzb25nc19kcmFrZSA8LSBzb25ncyAlPiUKICAgIGZpbHRlcihhcnRpc3QgPT0gIkRyYWtlIikgJT4lCiAgICBnZ3Bsb3QoYWVzKHkgPSByZW9yZGVyKHNvbmdfbmFtZSwgcG9wdWxhcml0eSksIHggPSBwb3B1bGFyaXR5KSkgKyBnZ3RpdGxlKCJEcmFrZSIpICsKICAgIGdlb21fY29sKGFscGhhID0gMC41KSArIHRoZW1lX2xpZ2h0KCkgKyB5bGFiKCIiKSArIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDYwLjYpCnNvbmdzX2thbnllIHwgc29uZ3NfZHJha2UKYGBgCgpBIGxvdCBvZiB0aGUgc29uZ3MgYXJlIGZhaXJseSBjbG9zZSB0byB0aGUgYXZlcmFnZSwgYnV0IHRoZXJlIGFyZSBzb21lIG91dGxpZXJzISAgIApUeXBpY2FsbHksIGJvdGggYXJ0aXN0cyBoYXZlIG9uZSBzb25nIHRoYXQgY3JpcHBsZXMgdGhlaXIgYXZlcmFnZSEgKHRoZSBib3R0b20gb25lKS4KCiMgT25lIHN0ZXAgYmFjawoKR2l2ZW4gdGhlIGZhY3QgdGhhdCB0aGUgcG9wdWxhcml0eSBzY29yZSByYW5nZXMgYmV0d2VlbiAwIGFuZCAxLCBhIGZpcnN0IGd1ZXNzIHdvdWxkIGJlIHRoYXQsIG9uIGF2ZXJhZ2UsIGEgc29uZyBoYXMgYSBwb3B1bGFyaXR5IG9mIH41MC4gICAKCkJlbG93IHdlIHBsb3QgdGhlIGhpc3RvZ3JhbSBvZiBwb3B1bGFyaXR5IGFjcm9zcyBhbGwgMTNLKyBzb25ncy4KCmBgYHtyLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CnNvbmdzICU+JSBnZ3Bsb3QoYWVzKHggPSBwb3B1bGFyaXR5KSkgKyAKICAgIGdlb21faGlzdG9ncmFtKGFscGhhID0gMC41KSArIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDUwKSArIHRoZW1lX2xpZ2h0KCkgKwogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbihzb25ncyRwb3B1bGFyaXR5KSwgY29sb3IgPSAicmVkIikgKwogICAgYW5ub3RhdGUoZ2VvbSA9ICJ0ZXh0IiwgeCA9IDQyLCB5ID0gMTEwMCwgbGFiZWwgPSAic2FtcGxlIFxuIGF2ZXJhZ2UiLCBjb2xvciA9ICJyZWQiKQoKYGBgCgoKVGhlIHNhbXBsZSBhdmVyYWdlIGlzIGluZGVlZCByYXRoZXIgY2xvc2UgdG8gNTAhICAgCgpOb3csIGxldCdzIGRvIGFub3RoZXIgZXhlcmNpc2U6ICAgCjEuIHdlIHRha2UgdGhlIGZpcnN0IDEwIHNvbmdzIGFuZCBjb21wdXRlIHRoZSBhdmVyYWdlIHBvcHVsYXJpdHkuCjIuIHdlIHRha2UgdGhlIGZpcnN0IDEwMCBzb25ncyBhbmQgY29tcHV0ZSB0aGUgYXZlcmFnZSBwb3B1bGFyaXR5LgozLiB3ZSB0YWtlIHRoZSBmaXJzdCAxLDAwMCBzb25ncyBhbmQgY29tcHV0ZSB0aGUgYXZlcmFnZSBwb3B1bGFyaXR5Lgo0LiB3ZSB0YWtlIHRoZSBmaXJzdCAxMCwwMDAgc29uZ3MgYW5kIGNvbXB1dGUgdGhlIGF2ZXJhZ2UgcG9wdWxhcml0eS4KCk9mIGNvdXJzZSwgdGhlIGF2ZXJhZ2UgcG9wdWxhcml0eSBpcyBnb2luZyB0byBjaGFuZ2UgZm9yIGVhY2ggc3RlcC4gQnV0LCBhcyB0aGUgbnVtYmVyIG9mIHNvbmdzIGdyb3dzLCB0aGUgY2hhbmdlIHdpbGwgYmUgc21hbGwgYmVjYXVzZSB0aGUgYXZlcmFnZSB3aWxsIHNsb3dseSBjb252ZXJnZSB0byB0aGUgKnBvcHVsYXRpb24qIGF2ZXJhZ2UuIFdlIGlsbHVzdHJhdGUgdGhpcyBiZWxvdy4gVGhlIHJpZ2h0IGdyYXBoIHVzZXMgYSBsb2ctc2NhbGUgZm9yIHRoZSB4LWF4aXMuCgpgYGB7ciwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQpnMSA8LSBzb25ncyAlPiUKICAgIG11dGF0ZShuYl9yb3cgPSByb3dfbnVtYmVyKCksCiAgICAgICAgICAgY3VtX3BvcCA9IGN1bXN1bShwb3B1bGFyaXR5KSwKICAgICAgICAgICBhdmdfcG9wID0gY3VtX3BvcC9uYl9yb3cpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gbmJfcm93LCB5ID0gYXZnX3BvcCkpICsgZ2VvbV9saW5lKCkgKyB0aGVtZV9saWdodCgpICsKICAgIGFubm90YXRlKGdlb20gPSAidGV4dCIsIHggPSAyOTgwLCB5ID0gNzIsIGxhYmVsID0gInNtYWxsIHNhbXBsZSBcbiA9IFxuICBpbnN0YWJsZSBhdmVyYWdlIikgKyAKICAgIGFubm90YXRlKGdlb20gPSAidGV4dCIsIHggPSAxMTgwMCwgeSA9IDUwLCBsYWJlbCA9ICJmdWxsIHNhbXBsZSIpICsKICAgIHhsYWIoIk51bWJlciBvZiBzb25ncyB1c2VkIHRvIGNvbXB1dGUgdGhlIGF2ZXJhZ2UiKSArIAogICAgeWxhYigiQXZlcmFnZSBwb3B1bGFyaXR5IikgCgpnMiA8LSBzb25ncyAlPiUKICAgIG11dGF0ZShuYl9yb3cgPSByb3dfbnVtYmVyKCksCiAgICAgICAgICAgY3VtX3BvcCA9IGN1bXN1bShwb3B1bGFyaXR5KSwKICAgICAgICAgICBhdmdfcG9wID0gY3VtX3BvcC9uYl9yb3cpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gbmJfcm93LCB5ID0gYXZnX3BvcCkpICsgZ2VvbV9saW5lKCkgKyB0aGVtZV9saWdodCgpICsKICAgIGFubm90YXRlKGdlb20gPSAidGV4dCIsIHggPSA1MCwgeSA9IDYwLCBsYWJlbCA9ICJzbWFsbCBcbiBzYW1wbGUgXG4gPSBcbiAgaW5zdGFibGUgXG4gYXZlcmFnZSIpICsgCiAgICB4bGFiKCJOdW1iZXIgb2Ygc29uZ3MgKHNxcnQtc2NhbGUpIikgKyAKICAgIHlsYWIoIiIpICsKICAgIHNjYWxlX3hfc3FydCgpCmcxIHwgZzIKYGBgCgpOb3csIHRoZSBsaW5lIGNyb3NzZWQgdGhlIDUwIHRocmVzaG9sZCBhdCBzb21lIHBvaW50OiB3aG8gaXMgdG8gc2F5IGl0IHdvbid0IGRvIGl0IGFnYWluPyAgIApUaGUgcHJvYmxlbSBpcyB0aGF0IGFzIHRoZSBudW1iZXIgb2Ygc29uZ3MgZ3Jvd3MsIGxhcmdlIHNoaWZ0cyBiZWNvbWUgaW1wcm9iYWJsZSBiZWNhdXNlIHRoZXkgd291bGQgcmVxdWlyZSAqbWFueSogbmV3IHNvbmdzIHRvIGhhdmUgYSBwb3B1bGFyaXR5IHdoaWNoIGlzIHN0cmljdGx5IGFib3ZlIDUwLgoKT25lIGltcG9ydGFudCBsZXNzb24gaXMgdGhhdCB0aGUgKipzYW1wbGUgc2l6ZSoqIG1hdHRlcnMgYSBsb3QgaW4gdGVzdHMuCgpBIHZlcnkgaW1wb3J0YW50IHJlc3VsdCBpbiBwcm9iYWJpbGl0eSBpcyB0aGUgKipDZW50cmFsIExpbWl0IFRoZW9yZW0qKi4gU2ltcGx5IHB1dCwgaXQgc3RhdGVzIHRoYXQgaWYgdGhpbmdzIHJlbWFpbiBhbHdheXMgdGhlIHNhbWUgKHN0YXRpb25hcml0eSBpbiBkaXN0cmlidXRpb24gd2l0aCBmaW5pdGUgdmFyaWFuY2UpLCB0aGVuIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHNhbXBsZSBtZWFuIGNvbnZlcmdlcyB0byBhICoqR2F1c3NpYW4gZGlzdHJpYnV0aW9uKiouCgoKIyBUaGUgdGVzdHMKCkJhY2sgdG8gdGhlIEdhdXNzaWFuIGRpc3RyaWJ1dGlvbi4gCgpgYGB7cn0KbWVhbl9rYW55ZSA8LSBtZWFuKHNvbmdzX2thbnllJGRhdGEkcG9wdWxhcml0eSkKc2Rfa2FueWUgPC0gc2Qoc29uZ3Nfa2FueWUkZGF0YSRwb3B1bGFyaXR5KSAKbWVhbl9kcmFrZSA8LSBtZWFuKHNvbmdzX2RyYWtlJGRhdGEkcG9wdWxhcml0eSkKZ19ub3JtIDwtIHNvbmdzX2thbnllJGRhdGEgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBwb3B1bGFyaXR5KSkgKyAKICAgIGdlb21fcG9pbnQoZGF0YSA9IHRpYmJsZSh4ID0gc2VxKDE1LCAxMDAsIGJ5ID0gMC4xKSwgeSA9IGRub3JtKHgsIG1lYW4gPSBtZWFuX2thbnllLCBzZCA9IHNkX2thbnllKSksIAogICAgICAgICAgICAgICBhZXMoeCA9IHgsIHkgPSB5KSwgc2l6ZSA9IDAuMykgKwogICAgZ2VvbV9wb2ludChhZXMoeCA9IHBvcHVsYXJpdHksIHkgPSBkbm9ybShwb3B1bGFyaXR5LCBtZWFuID0gbWVhbl9rYW55ZSwgc2QgPSBzZF9rYW55ZSksIGxhYmVsID0gc29uZ19uYW1lKSwgCiAgICAgICAgICAgICAgIGNvbG9yID0gIiMyMjY2RkYiLCBzaXplID0gMikgKyAKICAgIHhsaW0oMTUsMTAwKSArIHRoZW1lX2xpZ2h0KCkgKyB4bGFiKCJQb3B1bGFyaXR5IChvZiBLYW55ZSBXZXN0KSIpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IG1lYW5fZHJha2UsIGNvbG9yID0gIiM1NTU1NTUiKSArIAogICAgYW5ub3RhdGUoInRleHQiLCB4ID0gNTcuNSwgeSA9IDAuMDIzLCBsYWJlbCA9ICJEcmFrZSIsIGNvbG9yID0gIiM1NTU1NTUiKQojZ2dwbG90bHkoZ19ub3JtKQpnX25vcm0KYGBgCgpCYXNpY2FsbHksIGlmIHdlIGFzc3VtZSB0aGF0IEthbnllICYgRHJha2UgaGF2ZSB0aGUgc2FtZSBhdmVyYWdlIHBvcHVsYXJpdHksIHRoZW4gdGhlaXIgcG9wdWxhcml0eSBzaG91bGQgYmUgc3VjaCB0aGF0IHRoZWlyIG1lYW4gImJlaGF2ZXMiIGxpa2UgYSBHYXVzc2lhbiBkaXN0cmlidXRpb24gd2l0aCBhIHN0YW5kYXJkIGRldmlhdGlvbiB0aGF0IGlzIHNocnVuayBieSAkXHNxcnQoMzMpJC4KCkEgc3RhdGlzdGljYWwgdGVzdCB3aWxsIGRldGVybWluZSBob3cgbGlrZWx5IHdlIHdvdWxkIGJlIHRvIG9ic2VydmUgZGF0YSB0aGF0IHdvdWxkIHJlamVjdCB0aGUgaHlwb3RoZXNpcyB0aGF0IHRoZSB0d28gbWVhbnMgYXJlIGVxdWFsLiBPdXIgYmFzZWxpbmUgaHlwb3RoZXNpcyBpczogKipLYW55ZSBXZXN0ICYgRHJha2UgaGF2ZSBlcXVhbCBwb3B1bGFyaXRpZXMqKi4gICAKV2UgcGxvdCB0aGUgR2F1c3NpYW4gY3VydmUgYW5kIHRoZSB0LXN0YXQgKHdoaWNoIGNvbWVzIGZyb20gdGhlIENMVCEpLgoKYGBge3J9Cm1lYW5fa2FueWUgPC0gbWVhbihzb25nc19rYW55ZSRkYXRhJHBvcHVsYXJpdHkpCnNkX2thbnllIDwtIHNkKHNvbmdzX2thbnllJGRhdGEkcG9wdWxhcml0eSkgLyBzcXJ0KDMzKQptZWFuX2RyYWtlIDwtIG1lYW4oc29uZ3NfZHJha2UkZGF0YSRwb3B1bGFyaXR5KQpnX25vcm0yIDwtIHRpYmJsZSh4ID0gYygtNSw1KSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSB4KSkgKyAKICAgIGdlb21fcG9pbnQoZGF0YSA9IHRpYmJsZSh4ID0gc2VxKC01LCA1LCBieSA9IDAuMSksIHkgPSBkbm9ybSh4LCBtZWFuID0gMCwgc2QgPSAxKSksIAogICAgICAgICAgICAgICBhZXMoeCA9IHgsIHkgPSB5KSwgc2l6ZSA9IDAuMykgKwogICAgeGxpbSgtNSw1KSArIHRoZW1lX2xpZ2h0KCkgKyAKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IChtZWFuX2thbnllIC0gbWVhbl9kcmFrZSkvc2Rfa2FueWUsIGNvbG9yID0gIiM1NTU1NTUiKSArIAogICAgYW5ub3RhdGUoInRleHQiLCB4ID0gMSwgeSA9IDAuNDIsIGxhYmVsID0gInQtc3RhdCIsIGNvbG9yID0gIiM1NTU1NTUiKQpnX25vcm0yCmBgYAoKT3ZlcmFsbCwgdGhlIHN0YXRpc3RpYyBsaWVzIGluIHRoZSBidWxrIG9mIHRoZSBkaXN0cmlidXRpb246IHRoZSBzaXR1YXRpb24gaXMgbm90ICJleHRyZW1lIiwgc28gaXQncyBkaWZmaWN1bHQgdG8gY29uY2x1ZGUgdGhhdCB0aGUgdHdvIG1lYW5zIGFyZSB0cnVseSBkaWZmZXJlbnQuIEJhc2ljYWxseSwgaXQgd291bGQgaGFwcGVuIG9mdGVuIHRoYXQgMiBhcnRpc3RzIHdpdGggc2FtZSBwb3B1bGFyaXR5IGhhdmUgc29uZ3MgdGhhdCBhcmUgcmF0ZWQgbGlrZSB0aG9zZSBpbiB0aGUgZGF0YXNldC4KCkluIFIsIGl0J3MgZWFzeSB0byBwZXJmb3JtICp0Ki10ZXN0cy4KCmBgYHtyfQp0LnRlc3Qoc29uZ3Nfa2FueWUkZGF0YSRwb3B1bGFyaXR5LCBzb25nc19kcmFrZSRkYXRhJHBvcHVsYXJpdHkpCmBgYAoKVGhlIHAtdmFsdWUgKDAuNjIpIGhhcyBhIHRyaWNreSBpbnRlcnByZXRhdGlvbjogaXQgdGVsbHMgdXMgaG93IHByb2JhYmxlIGl0IGlzIHRvIGVuY291bnRlciBzaW1pbGFyIGRhdGFzZXRzIHN1Y2ggdGhhdCB0aGUgdHdvIG1lYW5zIGFyZSBlcXVhbC4KCiMgQSBjb3VudGVyLWV4YW1wbGUKCmBgYHtyfQpzb25ncyAlPiUKICAgIGdyb3VwX2J5KGFydGlzdCkgJT4lCiAgICBzdW1tYXJpemUoYXZnX3BvcCA9IG1lYW4ocG9wdWxhcml0eSksCiAgICAgICAgICAgICAgbmJfc29uZ3MgPSBuKCkpICU+JQogICAgYXJyYW5nZShkZXNjKG5iX3NvbmdzKSkgJT4lCiAgICBoZWFkKDEwKQpgYGAKCk5vdyBsZXQncyBjb21wYXJlIERhdmlkIEd1ZXR0YSBhbmQgRnV0dXJlOiBmZXdlciBzb25ncywgYW5kIG11Y2ggYmlnZ2VyIGRpZmZlcmVuY2UgaW4gcG9wdWxhcml0eSEgVGhlIHZlcnRpY2FsIGJsYWNrIGxpbmVzIHNob3dzIHRoZSBhdmVyYWdlIHBvcHVsYXJpdHkgb2YgdGhlIGFydGlzdHMuCgpgYGB7ciwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQpzb25nc19ndWV0dGEgPC0gc29uZ3MgJT4lCiAgICBmaWx0ZXIoYXJ0aXN0ID09ICJEYXZpZCBHdWV0dGEiKSAlPiUKICAgIGdncGxvdChhZXMoeSA9IHJlb3JkZXIoc29uZ19uYW1lLCBwb3B1bGFyaXR5KSwgeCA9IHBvcHVsYXJpdHkpKSArIGdndGl0bGUoIkRhdmlkIEd1ZXR0YSIpICsgCiAgICBnZW9tX2NvbChhbHBoYSA9IDAuMywgZmlsbCA9ICIjMDA5OUVFIikgKyB0aGVtZV9saWdodCgpICsgeWxhYigiIikgKyBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA1My44KSArCiAgICB4bGFiKCIiKSArIHhsaW0oMCw5MCkgKyB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNykpCnNvbmdzX3dpbGxpYW1zIDwtIHNvbmdzICU+JQogICAgZmlsdGVyKGFydGlzdCA9PSAiSGFuayBXaWxsaWFtcyIpICU+JQogICAgZ2dwbG90KGFlcyh5ID0gcmVvcmRlcihzb25nX25hbWUsIHBvcHVsYXJpdHkpLCB4ID0gcG9wdWxhcml0eSkpICsgZ2d0aXRsZSgiSGFuayBXaWxsaWFtcyIpICsKICAgIGdlb21fY29sKGFscGhhID0gMC4zLCBmaWxsID0gIiNFRTk5MDAiKSArIHRoZW1lX2xpZ2h0KCkgKyB5bGFiKCIiKSArIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDMzLjkpICsgeGxpbSgwLDkwKSArCiAgICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNykpCnNvbmdzX2d1ZXR0YSAvIHNvbmdzX3dpbGxpYW1zCmBgYApOb3cgbGV0J3MgaGF2ZSBhIGxvb2sgYXQgdGhlIHRlc3QhCgpgYGB7cn0KcG9wX2d1ZXR0YSA8LSBzb25ncyAlPiUgZmlsdGVyKGFydGlzdCA9PSAiRGF2aWQgR3VldHRhIikgJT4lIHB1bGwocG9wdWxhcml0eSkKcG9wX3dpbGxpYW1zIDwtIHNvbmdzICU+JSBmaWx0ZXIoYXJ0aXN0ID09ICJIYW5rIFdpbGxpYW1zIikgJT4lIHB1bGwocG9wdWxhcml0eSkKdC50ZXN0KHBvcF9ndWV0dGEsIHBvcF93aWxsaWFtcykKYGBgCgpUaGUgcC12YWx1ZSBpcyBzbWFsbDogaXQncyB1bmxpa2VseSB0byBmaW5kIGRhdGEgd2l0aCAyIGVxdWFsbHkgcG9wdWxhciBhcnRpc3QgdGhhdCB3b3VsZCBoYXZlIHN1Y2ggYSBkaXNjcmVwYW5jeSBpbiB0aGUgcmF0aW5nIG9mIHRoZWlyIHNvbmdzLi4uCgpgYGB7cn0KCmBgYAoK