Bộ dữ liệu Iris được tạo ra khi nhà sinh vật học người Anh Ronald Fisher xuất bản bài báo năm 1936 của ông “Việc sử dụng nhiều phép đo trong các vấn đề phân loại” như một ví dụ về phân tích phân biệt tuyến tính. Đôi khi nó cũng được gọi là tập dữ liệu Anderson’s Iris vì Edgar Anderson là người đã thu thập dữ liệu để định lượng sự biến đổi hình thái của hoa Iris theo ba loài có liên quan. Về cơ bản, hai nhà khoa học lâu đời này đã khai sinh ra tập dữ liệu, tập dữ liệu vẫn được sử dụng cho đến ngày nay để học các kiến thức cơ bản về RStudio.

Bộ dữ liệu iris đưa ra các phép đo tính bằng cm của các biến chiều dài và chiều rộng của lá đài “Sepal” và chiều dài và chiều rộng của cánh hoa “Petal”, tương ứng, đối với 50 bông hoa từ mỗi trong số 3 loài iris. Các loài là Iris setosa, versicolor, và virginica.

#Install package
install.packages('dplyr')
install.packages('ggplot2')
install.packages('GGally')
install.packages('cluster')
install.packages('fpc')
library(dplyr)
library(reshape2)
library(ggplot2)
library(GGally)
library(cluster)
library(fpc)
#Explore Iris Dataset
iris
data(iris)

Bước 1: Khám phá dữ liệu Iris

Ở bước đầu tiên chúng ta sẽ tìm hiểu các công thức trong R giúp có thể xem và khám phá dữ liệu một cách tổng quan. Những tính năng này cho phép chúng ta nhìn được dữ liệu hoặc cung cấp các thống kê tổng quan về dữ liệu như những chỉ số cơ bản về Mean, Median, Min, Max, Std, Variance, … Đồng thời chúng ta cũng có thể xem và xác nhận cấu trúc dữ liệu và cả định dạng của dữ liệu trước khi phân tích.

#STEP 1: EXPLORE DATA ----
#1.1 Function summary to review data
summary(iris)
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100   setosa    :50  
 1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300   versicolor:50  
 Median :5.800   Median :3.000   Median :4.350   Median :1.300   virginica :50  
 Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199                  
 3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800                  
 Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500                  
#1.2 Function names to show columns name
names(iris)
[1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width"  "Species"     
names(iris) <- tolower(names(iris))
#1.3 Function dim to show row and column counts
dim(iris)
[1] 150   5
#1.4 Function class to show data structure
class(iris)
[1] "data.frame"
#1.5 Function typeof and str to show data type
typeof(iris$sepal.length)
[1] "double"
str(iris$sepal.length)
 num [1:150] 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
typeof(iris$species)
[1] "integer"
class(iris$species)
[1] "factor"
str(iris$species)
 Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

Bước 2: Chỉnh sửa và thay đổi cấu trúc dữ liệu

Ở bước tiếp theo, chúng ta sẽ cùng sử dụng những câu lệnh để chỉnh sửa cấu trúc dữ liệu Iris theo yêu cầu. Điều này bao gồm việc bóc tách dữ liệu thành những bảng nhỏ hơn như phân thành các bảng theo từng loài hoa. Hoặc chúng mong muốn thêm các cột, sort bảng theo một tiêu chí nào đó.

Và một yếu tố quan trọng không kém là chuyển đổi cấu trúc giống với tính năng Unpivot trong PowerBI thì chúng ta sẽ chuyển đổi dữ liệu từ dạng Wide (cấu trúc giống Pivot Table) sang dạng Long (cấu trúc giống Tabular). Điều này yêu cầu chúng ta sẽ phải cài đặt thêm Package dplyr là một bộ ngôn ngữ lập trình trong R giúp thay đổi cấu trúc và chỉnh sửa dữ liệu. Chúng ta sẽ thêm ở bên trên phần Install Package nội dung câu lệnh để cài đặt package dplyr và sử dụng chúng trong môi trường hiện tại.

#2.1 Split data into subset
virginica <- iris[iris$species == 'virginica',]
virginica2 <- iris[iris$species == 'virginica' & iris$sepal.length > 6,]
head(virginica)
#2.2 Function select to select columns
selected <- select(iris, sepal.length, sepal.width)
head(selected)
#2.3 Function mutate to add column
newcol <- mutate(iris, longer = sepal.length / sepal.width )
newcol <- mutate(newcol, longer.2x = sepal.length > 2*sepal.width )
tail(newcol)
#2.4 Function arrange to sort data
newcol <- arrange(newcol, sepal.width)
newcol <- arrange(newcol, desc(sepal.width))
head(newcol)
#2.5 Function melt to unpivot table (wide -> long)
iris.melt <- melt(iris, id = 'species', variable.name = 'size')
head(iris.melt)

Bước 3: Trực quan hóa dữ liệu và tạo biểu đồ

Sau khi đã có các bảng dữ liệu theo dạng cần thiết, chúng ta có thể sử dụng các package và code xây dựng các biểu đồ giúp trực quan hóa thông tin. Ở bước này chúng ta sẽ tạo những biểu đồ có ý nghĩa về mặt thống kê với dữ liệu để hiểu về tập đối tượng đang phân tích. Chúng bao gồm biểu đồ Histogram để xem sự phân bổ về các chiều dài của hoa, sử dụng Box Plot giúp trực quan các con số thống kê. Sử dụng Scatter Plot để xem phân bổ của dữ liệu theo 2 chiều cụ thể.

Để tạo ra các biểu đồ này, chúng ta có thể sử dụng các câu lệnh có sẵn trong RStudio hoặc cài đặt thêm Package GGPlot & Ggally để có thể tạo các biểu đồ tương tự nhưng có nhiều khả năng thay đổi điều chỉnh cũng như dễ dàng tạo hơn.

#STEP 3: VISUALIZE DATA ----

#3.1 Function hist to show histogram
hist(iris$sepal.length)


hist(iris$sepal.length,
     col='light blue',
     main='Histogram',
     xlab='Sepal.Length',
     ylab='Frequency')


hist(iris$sepal.length, col='red', breaks=20, main='Histogram', xlab='Size')

hist(iris$petal.length, col='green',breaks=30, add=TRUE)

legend('topright',
       c('Sepal Length', 'Petal Length'),
       fill=c('red', 'green'))

#3.2 Use ggplot to create charts
ggplot(iris.melt, aes(x=value, fill=size)) +
  geom_histogram(color ='#e9ecef', alpha = 0.6, position = 'identity')


ggplot(iris.melt, aes(x=value, fill=size)) +
  geom_histogram(color ='#e9ecef', alpha = 0.6, position = 'identity') +
  facet_wrap(~size)

#3.3 Function boxplot to create boxplot
boxplot(sepal.length ~ species,
        data = iris,
        main = 'Sepal Length by Species',
        xlab = 'Species',
        ylab = 'Sepal Length',
        col = 'light blue',
        border = 'black')

#boxplot with melt data
boxplot(value ~ size,
        data = iris.melt,
        main = 'Compare different size',
        xlab = 'Size',
        ylab = 'Value',
        col = 'light blue',
        border = 'black')

#Advanced boxplot with ggplot
ggplot(iris.melt, aes(x=size, y=value, fill=size)) +
  geom_boxplot()+
  geom_jitter(color = 'black', size = 0.4, alpha = 0.9)

#3.4 Function plot to create scatter plot
plot(iris)

plot(iris[,1:4])


plot(iris$sepal.width, iris$sepal.length,
     col = iris[,5],
     main = 'Scatterplot',
     xlab = 'Sepal Width',
     ylab = 'Sepal Length',
     pch = 19)

pairs(iris[,1:4],col=iris[,5],oma=c(4,4,6,12))
par(xpd=TRUE)

ggplot(iris, aes(x=sepal.length, y=sepal.width, color=species)) +
  geom_point(size=5)

ggpairs(iris,
        columns = 1:4,
        aes(color = species, alpha = 0.5))

Bước 4: Kiểm chứng giả định với T-test

Ở buổi trước chúng ta đã học cách sử dụng T-test để xác định giả thuyết có chính xác hay không. Tương tự với RStudio, chúng ta có thể dễ dàng sử dụng nhiều loại T-test, ở phần thực hành này chúng ta sẽ sử dụng loại T-test để xác định giả thuyết cơ bản về các loại hoa như sau. Câu hỏi: liệu loài setosa và versicolor có chiều dài cánh hoa khác nhau hay không?

Giả định:

#STEP 4: HYPOTHESIS TESTING WITH T-test ----

setosa <- iris[iris$species == 'setosa',]
versicolor <- iris[iris$species == 'versicolor',]

t.test(x= setosa$petal.length, y = versicolor$petal.length)

    Welch Two Sample t-test

data:  setosa$petal.length and versicolor$petal.length
t = -39.493, df = 62.14, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -2.939618 -2.656382
sample estimates:
mean of x mean of y 
    1.462     4.260 

Bước 5: Phân tích phương sai ANOVA

Tương tự chúng ta cũng có thể chạy các mô hình phân tích ANOVA trong RStudio. Trong phần thực hành này, chúng ta sẽ cùng phân tích ANOVA để xác định giả thuyết so sánh về chiều dài cánh hoa giữa cả 3 loài hoa.

Câu hỏi: liệu 3 loài hoa khác nhau có chiều dài cánh hoa khác nhau hay không? Giả định:

#STEP 5: ANALYSIS WITH ANOVA ----

petal.length.aov <- aov(formula = petal.length ~ species, data = iris)

summary(object = petal.length.aov)
             Df Sum Sq Mean Sq F value Pr(>F)    
species       2  437.1  218.55    1180 <2e-16 ***
Residuals   147   27.2    0.19                   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
TukeyHSD(petal.length.aov)
  Tukey multiple comparisons of means
    95% family-wise confidence level

Fit: aov(formula = petal.length ~ species, data = iris)

$species
                      diff     lwr     upr p adj
versicolor-setosa    2.798 2.59422 3.00178     0
virginica-setosa     4.090 3.88622 4.29378     0
virginica-versicolor 1.292 1.08822 1.49578     0

Bước 6: Thực hành phân tích phương sai ANOVA

Giả sử như bạn chỉ có các thông số về chiều dài và chiều rộng của cánh hoa thì có thể từ chúng xác nhận loài hoa hay không? Nếu với 2 phương pháp trước bên trên chúng ta đã chỉ định được rằng kích thước cánh hoa của 3 loài là khác biệt (tương tự với nhụy hoa) thì chúng ta có thể tự tin xây dựng mô hình để dự đoán tên loài hoa với các số liệu về kích thước cánh hoa và nhụy hoa.

Chúng ta sẽ sử dụng mô hình K-mean Clustering để giúp phân loại các nhóm loài hoa từ các chỉ số trên và từ chúng ta có thể nhận biết được hoa nào là thuộc loài gì. Để làm được điều này, chúng ta sẽ chuẩn bị một vài bước xử lý dữ liệu để có dữ liệu để thử nghiệm.

Đầu tiên, ta sẽ tạo ra một bộ dữ liệu iris.test gồm chỉ có 4 cột thông số về kích thước chứ không có tên của loài hoa. Sau đó ở đây, chúng ta sẽ cùng sử dụng mô hình K-mean Clustering để phân loại chúng và biết được giống hoa nào dựa trên các chỉ số về độ dài đó.

K-mean Clustering là một trong những phương pháp học không giám sát phổ biến nhất trong học máy. Thuật toán này giúp xác định “k” nhóm (cụm) có thể có từ “n” phần tử dựa trên khoảng cách giữa các phần tử.

Giải thích chi tiết hơn sẽ là: thuật toán tìm ra khoảng cách giữa mỗi phần tử trong dữ liệu của bạn, sau đó tìm số lượng tâm, phân bổ phần tử cho các trung tâm gần nhất để tạo thành các cụm và mục tiêu cuối cùng là giữ nguyên kích thước của mỗi cụm càng nhỏ càng tốt.

Một trong những câu hỏi phổ biến liên quan đến thuật toán K-mean là liệu nó có thể xử lý dữ liệu không phải là số hay không. Câu trả lời ngắn gọn là KHÔNG vì thuật toán đang sử dụng khoảng cách giữa các lần quan sát. Tuy nhiên, có rất nhiều thuật toán có thể giúp chuyển đổi các tính năng không phải số thành các tính năng số, điều này sẽ cho phép bạn áp dụng thuật toán K-mean cho dữ liệu của mình.

iris.test <- iris
iris.test$species <- NULL
head(iris.test)
NA
kmeans.result <- kmeans(iris.test, 3)
table(iris$species, kmeans.result$cluster)
            
              1  2  3
  setosa     50  0  0
  versicolor  0  2 48
  virginica   0 36 14
plot(iris.test[c('sepal.length', 'sepal.width')], col = kmeans.result$cluster)

plotcluster(iris.test, kmeans.result$cluster)

clusplot(iris.test, kmeans.result$cluster, color = TRUE, shade = TRUE)

LS0tCnRpdGxlOiAiUiBOb3RlYm9vazogSXJpcyBBbmFseXNpcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKQuG7mSBk4buvIGxp4buHdSBJcmlzIMSRxrDhu6NjIHThuqFvIHJhIGtoaSBuaMOgIHNpbmggduG6rXQgaOG7jWMgbmfGsOG7nWkgQW5oIFJvbmFsZCBGaXNoZXIgeHXhuqV0IGLhuqNuIGLDoGkgYsOhbyBuxINtIDE5MzYgY+G7p2Egw7RuZyAiVmnhu4djIHPhu60gZOG7pW5nIG5oaeG7gXUgcGjDqXAgxJFvIHRyb25nIGPDoWMgduG6pW4gxJHhu4EgcGjDom4gbG/huqFpIiBuaMawIG3hu5l0IHbDrSBk4bulIHbhu4EgcGjDom4gdMOtY2ggcGjDom4gYmnhu4d0IHR1eeG6v24gdMOtbmguIMSQw7RpIGtoaSBuw7MgY8WpbmcgxJHGsOG7o2MgZ+G7jWkgbMOgIHThuq1wIGThu68gbGnhu4d1IEFuZGVyc29uJ3MgSXJpcyB2w6wgRWRnYXIgQW5kZXJzb24gbMOgIG5nxrDhu51pIMSRw6MgdGh1IHRo4bqtcCBk4buvIGxp4buHdSDEkeG7gyDEkeG7i25oIGzGsOG7o25nIHPhu7EgYmnhur9uIMSR4buVaSBow6xuaCB0aMOhaSBj4bunYSBob2EgSXJpcyB0aGVvIGJhIGxvw6BpIGPDsyBsacOqbiBxdWFuLiBW4buBIGPGoSBi4bqjbiwgaGFpIG5ow6Aga2hvYSBo4buNYyBsw6J1IMSR4budaSBuw6B5IMSRw6Mga2hhaSBzaW5oIHJhIHThuq1wIGThu68gbGnhu4d1LCB04bqtcCBk4buvIGxp4buHdSB24bqrbiDEkcaw4bujYyBz4butIGThu6VuZyBjaG8gxJHhur9uIG5nw6B5IG5heSDEkeG7gyBo4buNYyBjw6FjIGtp4bq/biB0aOG7qWMgY8ahIGLhuqNuIHbhu4EgUlN0dWRpby4KCkLhu5kgZOG7ryBsaeG7h3UgaXJpcyDEkcawYSByYSBjw6FjIHBow6lwIMSRbyB0w61uaCBi4bqxbmcgY20gY+G7p2EgY8OhYyAqKmJp4bq/biBjaGnhu4F1IGTDoGkgdsOgIGNoaeG7gXUgcuG7mW5nIGPhu6dhIGzDoSDEkcOgaSAiU2VwYWwiIHbDoCBjaGnhu4F1IGTDoGkgdsOgIGNoaeG7gXUgcuG7mW5nIGPhu6dhIGPDoW5oIGhvYSAiUGV0YWwiLCB0xrDGoW5nIOG7qW5nLCDEkeG7kWkgduG7m2kgNTAgYsO0bmcgaG9hIHThu6sgbeG7l2kgdHJvbmcgc+G7kSAzIGxvw6BpIGlyaXMqKi4gQ8OhYyBsb8OgaSBsw6AgSXJpcyBzZXRvc2EsIHZlcnNpY29sb3IsIHbDoCB2aXJnaW5pY2EuCgpgYGB7cn0KI0luc3RhbGwgcGFja2FnZQppbnN0YWxsLnBhY2thZ2VzKCdkcGx5cicpCmluc3RhbGwucGFja2FnZXMoJ2dncGxvdDInKQppbnN0YWxsLnBhY2thZ2VzKCdHR2FsbHknKQppbnN0YWxsLnBhY2thZ2VzKCdjbHVzdGVyJykKaW5zdGFsbC5wYWNrYWdlcygnZnBjJykKYGBgCgpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShyZXNoYXBlMikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KEdHYWxseSkKbGlicmFyeShjbHVzdGVyKQpsaWJyYXJ5KGZwYykKYGBgCgpgYGB7cn0KI0V4cGxvcmUgSXJpcyBEYXRhc2V0CmlyaXMKZGF0YShpcmlzKQpgYGAKCiMjICoqQsaw4bubYyAxOiBLaMOhbSBwaMOhIGThu68gbGnhu4d1IElyaXMqKgoK4bueIGLGsOG7m2MgxJHhuqd1IHRpw6puIGNow7puZyB0YSBz4bq9IHTDrG0gaGnhu4N1IGPDoWMgY8O0bmcgdGjhu6ljIHRyb25nIFIgZ2nDunAgY8OzIHRo4buDIHhlbSB2w6Aga2jDoW0gcGjDoSBk4buvIGxp4buHdSBt4buZdCBjw6FjaCB04buVbmcgcXVhbi4gTmjhu69uZyB0w61uaCBuxINuZyBuw6B5IGNobyBwaMOpcCBjaMO6bmcgdGEgbmjDrG4gxJHGsOG7o2MgZOG7ryBsaeG7h3UgaG/hurdjIGN1bmcgY+G6pXAgY8OhYyB0aOG7kW5nIGvDqiB04buVbmcgcXVhbiB24buBIGThu68gbGnhu4d1IG5oxrAgbmjhu69uZyBjaOG7iSBz4buRIGPGoSBi4bqjbiB24buBIE1lYW4sIE1lZGlhbiwgTWluLCBNYXgsIFN0ZCwgVmFyaWFuY2UsIC4uLiDEkOG7k25nIHRo4budaSBjaMO6bmcgdGEgY8WpbmcgY8OzIHRo4buDIHhlbSB2w6AgeMOhYyBuaOG6rW4gY+G6pXUgdHLDumMgZOG7ryBsaeG7h3UgdsOgIGPhuqMgxJHhu4tuaCBk4bqhbmcgY+G7p2EgZOG7ryBsaeG7h3UgdHLGsOG7m2Mga2hpIHBow6JuIHTDrWNoLgoKYGBge3J9CiNTVEVQIDE6IEVYUExPUkUgREFUQSAtLS0tCiMxLjEgRnVuY3Rpb24gc3VtbWFyeSB0byByZXZpZXcgZGF0YQpzdW1tYXJ5KGlyaXMpCmBgYAoKYGBge3J9CiMxLjIgRnVuY3Rpb24gbmFtZXMgdG8gc2hvdyBjb2x1bW5zIG5hbWUKbmFtZXMoaXJpcykKbmFtZXMoaXJpcykgPC0gdG9sb3dlcihuYW1lcyhpcmlzKSkKYGBgCgpgYGB7cn0KIzEuMyBGdW5jdGlvbiBkaW0gdG8gc2hvdyByb3cgYW5kIGNvbHVtbiBjb3VudHMKZGltKGlyaXMpCmBgYAoKYGBge3J9CiMxLjQgRnVuY3Rpb24gY2xhc3MgdG8gc2hvdyBkYXRhIHN0cnVjdHVyZQpjbGFzcyhpcmlzKQoKIzEuNSBGdW5jdGlvbiB0eXBlb2YgYW5kIHN0ciB0byBzaG93IGRhdGEgdHlwZQp0eXBlb2YoaXJpcyRzZXBhbC5sZW5ndGgpCnN0cihpcmlzJHNlcGFsLmxlbmd0aCkKdHlwZW9mKGlyaXMkc3BlY2llcykKY2xhc3MoaXJpcyRzcGVjaWVzKQpzdHIoaXJpcyRzcGVjaWVzKQpgYGAKCiMjICoqQsaw4bubYyAyOiBDaOG7iW5oIHPhu61hIHbDoCB0aGF5IMSR4buVaSBj4bqldSB0csO6YyBk4buvIGxp4buHdSoqCgrhu54gYsaw4bubYyB0aeG6v3AgdGhlbywgY2jDum5nIHRhIHPhur0gY8O5bmcgc+G7rSBk4bulbmcgbmjhu69uZyBjw6J1IGzhu4duaCDEkeG7gyBjaOG7iW5oIHPhu61hIGPhuqV1IHRyw7pjIGThu68gbGnhu4d1IElyaXMgdGhlbyB5w6p1IGPhuqd1LiDEkGnhu4F1IG7DoHkgYmFvIGfhu5NtIHZp4buHYyBiw7NjIHTDoWNoIGThu68gbGnhu4d1IHRow6BuaCBuaOG7r25nIGLhuqNuZyBuaOG7jyBoxqFuIG5oxrAgcGjDom4gdGjDoG5oIGPDoWMgYuG6o25nIHRoZW8gdOG7q25nIGxvw6BpIGhvYS4gSG/hurdjIGNow7puZyBtb25nIG114buRbiB0aMOqbSBjw6FjIGPhu5l0LCBzb3J0IGLhuqNuZyB0aGVvIG3hu5l0IHRpw6p1IGNow60gbsOgbyDEkcOzLgoKVsOgIG3hu5l0IHnhur91IHThu5EgcXVhbiB0cuG7jW5nIGtow7RuZyBrw6ltIGzDoCBjaHV54buDbiDEkeG7lWkgY+G6pXUgdHLDumMgZ2nhu5FuZyB24bubaSB0w61uaCBuxINuZyBVbnBpdm90IHRyb25nIFBvd2VyQkkgdGjDrCBjaMO6bmcgdGEgc+G6vSBjaHV54buDbiDEkeG7lWkgZOG7ryBsaeG7h3UgdOG7qyBk4bqhbmcgV2lkZSAoY+G6pXUgdHLDumMgZ2nhu5FuZyBQaXZvdCBUYWJsZSkgc2FuZyBk4bqhbmcgTG9uZyAoY+G6pXUgdHLDumMgZ2nhu5FuZyBUYWJ1bGFyKS4gxJBp4buBdSBuw6B5IHnDqnUgY+G6p3UgY2jDum5nIHRhIHPhur0gcGjhuqNpIGPDoGkgxJHhurd0IHRow6ptIFBhY2thZ2UgZHBseXIgbMOgIG3hu5l0IGLhu5kgbmfDtG4gbmfhu68gbOG6rXAgdHLDrG5oIHRyb25nIFIgZ2nDunAgdGhheSDEkeG7lWkgY+G6pXUgdHLDumMgdsOgIGNo4buJbmggc+G7rWEgZOG7ryBsaeG7h3UuIENow7puZyB0YSBz4bq9IHRow6ptIOG7nyBiw6puIHRyw6puIHBo4bqnbiBJbnN0YWxsIFBhY2thZ2UgbuG7mWkgZHVuZyBjw6J1IGzhu4duaCDEkeG7gyBjw6BpIMSR4bq3dCBwYWNrYWdlIGRwbHlyIHbDoCBz4butIGThu6VuZyBjaMO6bmcgdHJvbmcgbcO0aSB0csaw4budbmcgaGnhu4duIHThuqFpLgoKYGBge3J9CiMyLjEgU3BsaXQgZGF0YSBpbnRvIHN1YnNldAp2aXJnaW5pY2EgPC0gaXJpc1tpcmlzJHNwZWNpZXMgPT0gJ3ZpcmdpbmljYScsXQp2aXJnaW5pY2EyIDwtIGlyaXNbaXJpcyRzcGVjaWVzID09ICd2aXJnaW5pY2EnICYgaXJpcyRzZXBhbC5sZW5ndGggPiA2LF0KaGVhZCh2aXJnaW5pY2EpCmBgYAoKYGBge3J9CiMyLjIgRnVuY3Rpb24gc2VsZWN0IHRvIHNlbGVjdCBjb2x1bW5zCnNlbGVjdGVkIDwtIHNlbGVjdChpcmlzLCBzZXBhbC5sZW5ndGgsIHNlcGFsLndpZHRoKQpoZWFkKHNlbGVjdGVkKQpgYGAKCmBgYHtyfQojMi4zIEZ1bmN0aW9uIG11dGF0ZSB0byBhZGQgY29sdW1uCm5ld2NvbCA8LSBtdXRhdGUoaXJpcywgbG9uZ2VyID0gc2VwYWwubGVuZ3RoIC8gc2VwYWwud2lkdGggKQpuZXdjb2wgPC0gbXV0YXRlKG5ld2NvbCwgbG9uZ2VyLjJ4ID0gc2VwYWwubGVuZ3RoID4gMipzZXBhbC53aWR0aCApCnRhaWwobmV3Y29sKQpgYGAKCmBgYHtyfQojMi40IEZ1bmN0aW9uIGFycmFuZ2UgdG8gc29ydCBkYXRhCm5ld2NvbCA8LSBhcnJhbmdlKG5ld2NvbCwgc2VwYWwud2lkdGgpCm5ld2NvbCA8LSBhcnJhbmdlKG5ld2NvbCwgZGVzYyhzZXBhbC53aWR0aCkpCmhlYWQobmV3Y29sKQpgYGAKCmBgYHtyfQojMi41IEZ1bmN0aW9uIG1lbHQgdG8gdW5waXZvdCB0YWJsZSAod2lkZSAtPiBsb25nKQppcmlzLm1lbHQgPC0gbWVsdChpcmlzLCBpZCA9ICdzcGVjaWVzJywgdmFyaWFibGUubmFtZSA9ICdzaXplJykKaGVhZChpcmlzLm1lbHQpCmBgYAoKIyMgKipCxrDhu5tjIDM6IFRy4buxYyBxdWFuIGjDs2EgZOG7ryBsaeG7h3UgdsOgIHThuqFvIGJp4buDdSDEkeG7kyoqCgpTYXUga2hpIMSRw6MgY8OzIGPDoWMgYuG6o25nIGThu68gbGnhu4d1IHRoZW8gZOG6oW5nIGPhuqduIHRoaeG6v3QsIGNow7puZyB0YSBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgY8OhYyBwYWNrYWdlIHbDoCBjb2RlIHjDonkgZOG7sW5nIGPDoWMgYmnhu4N1IMSR4buTIGdpw7pwIHRy4buxYyBxdWFuIGjDs2EgdGjDtG5nIHRpbi4g4bueIGLGsOG7m2MgbsOgeSBjaMO6bmcgdGEgc+G6vSB04bqhbyBuaOG7r25nIGJp4buDdSDEkeG7kyBjw7Mgw70gbmdoxKlhIHbhu4EgbeG6t3QgdGjhu5FuZyBrw6ogduG7m2kgZOG7ryBsaeG7h3UgxJHhu4MgaGnhu4N1IHbhu4EgdOG6rXAgxJHhu5FpIHTGsOG7o25nIMSRYW5nIHBow6JuIHTDrWNoLiBDaMO6bmcgYmFvIGfhu5NtIGJp4buDdSDEkeG7kyBIaXN0b2dyYW0gxJHhu4MgeGVtIHPhu7EgcGjDom4gYuG7lSB24buBIGPDoWMgY2hp4buBdSBkw6BpIGPhu6dhIGhvYSwgc+G7rSBk4bulbmcgQm94IFBsb3QgZ2nDunAgdHLhu7FjIHF1YW4gY8OhYyBjb24gc+G7kSB0aOG7kW5nIGvDqi4gU+G7rSBk4bulbmcgU2NhdHRlciBQbG90IMSR4buDIHhlbSBwaMOibiBi4buVIGPhu6dhIGThu68gbGnhu4d1IHRoZW8gMiBjaGnhu4F1IGPhu6UgdGjhu4MuCgrEkOG7gyB04bqhbyByYSBjw6FjIGJp4buDdSDEkeG7kyBuw6B5LCBjaMO6bmcgdGEgY8OzIHRo4buDIHPhu60gZOG7pW5nIGPDoWMgY8OidSBs4buHbmggY8OzIHPhurVuIHRyb25nIFJTdHVkaW8gaG/hurdjIGPDoGkgxJHhurd0IHRow6ptIFBhY2thZ2UgR0dQbG90ICYgR2dhbGx5IMSR4buDIGPDsyB0aOG7gyB04bqhbyBjw6FjIGJp4buDdSDEkeG7kyB0xrDGoW5nIHThu7EgbmjGsG5nIGPDsyBuaGnhu4F1IGto4bqjIG7Eg25nIHRoYXkgxJHhu5VpIMSRaeG7gXUgY2jhu4luaCBjxaluZyBuaMawIGThu4UgZMOgbmcgdOG6oW8gaMahbi4KCmBgYHtyfQojU1RFUCAzOiBWSVNVQUxJWkUgREFUQSAtLS0tCgojMy4xIEZ1bmN0aW9uIGhpc3QgdG8gc2hvdyBoaXN0b2dyYW0KaGlzdChpcmlzJHNlcGFsLmxlbmd0aCkKCmhpc3QoaXJpcyRzZXBhbC5sZW5ndGgsCiAgICAgY29sPSdsaWdodCBibHVlJywKICAgICBtYWluPSdIaXN0b2dyYW0nLAogICAgIHhsYWI9J1NlcGFsLkxlbmd0aCcsCiAgICAgeWxhYj0nRnJlcXVlbmN5JykKCmhpc3QoaXJpcyRzZXBhbC5sZW5ndGgsIGNvbD0ncmVkJywgYnJlYWtzPTIwLCBtYWluPSdIaXN0b2dyYW0nLCB4bGFiPSdTaXplJykKCmhpc3QoaXJpcyRwZXRhbC5sZW5ndGgsIGNvbD0nZ3JlZW4nLGJyZWFrcz0zMCwgYWRkPVRSVUUpCgpsZWdlbmQoJ3RvcHJpZ2h0JywKICAgICAgIGMoJ1NlcGFsIExlbmd0aCcsICdQZXRhbCBMZW5ndGgnKSwKICAgICAgIGZpbGw9YygncmVkJywgJ2dyZWVuJykpCmBgYAoKYGBge3J9CiMzLjIgVXNlIGdncGxvdCB0byBjcmVhdGUgY2hhcnRzCmdncGxvdChpcmlzLm1lbHQsIGFlcyh4PXZhbHVlLCBmaWxsPXNpemUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPScjZTllY2VmJywgYWxwaGEgPSAwLjYsIHBvc2l0aW9uID0gJ2lkZW50aXR5JykKCmdncGxvdChpcmlzLm1lbHQsIGFlcyh4PXZhbHVlLCBmaWxsPXNpemUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPScjZTllY2VmJywgYWxwaGEgPSAwLjYsIHBvc2l0aW9uID0gJ2lkZW50aXR5JykgKwogIGZhY2V0X3dyYXAofnNpemUpCmBgYAoKYGBge3J9CiMzLjMgRnVuY3Rpb24gYm94cGxvdCB0byBjcmVhdGUgYm94cGxvdApib3hwbG90KHNlcGFsLmxlbmd0aCB+IHNwZWNpZXMsCiAgICAgICAgZGF0YSA9IGlyaXMsCiAgICAgICAgbWFpbiA9ICdTZXBhbCBMZW5ndGggYnkgU3BlY2llcycsCiAgICAgICAgeGxhYiA9ICdTcGVjaWVzJywKICAgICAgICB5bGFiID0gJ1NlcGFsIExlbmd0aCcsCiAgICAgICAgY29sID0gJ2xpZ2h0IGJsdWUnLAogICAgICAgIGJvcmRlciA9ICdibGFjaycpCmBgYAoKYGBge3J9CiNib3hwbG90IHdpdGggbWVsdCBkYXRhCmJveHBsb3QodmFsdWUgfiBzaXplLAogICAgICAgIGRhdGEgPSBpcmlzLm1lbHQsCiAgICAgICAgbWFpbiA9ICdDb21wYXJlIGRpZmZlcmVudCBzaXplJywKICAgICAgICB4bGFiID0gJ1NpemUnLAogICAgICAgIHlsYWIgPSAnVmFsdWUnLAogICAgICAgIGNvbCA9ICdsaWdodCBibHVlJywKICAgICAgICBib3JkZXIgPSAnYmxhY2snKQpgYGAKCmBgYHtyfQojQWR2YW5jZWQgYm94cGxvdCB3aXRoIGdncGxvdApnZ3Bsb3QoaXJpcy5tZWx0LCBhZXMoeD1zaXplLCB5PXZhbHVlLCBmaWxsPXNpemUpKSArCiAgZ2VvbV9ib3hwbG90KCkrCiAgZ2VvbV9qaXR0ZXIoY29sb3IgPSAnYmxhY2snLCBzaXplID0gMC40LCBhbHBoYSA9IDAuOSkKYGBgCgpgYGB7cn0KIzMuNCBGdW5jdGlvbiBwbG90IHRvIGNyZWF0ZSBzY2F0dGVyIHBsb3QKcGxvdChpcmlzKQoKYGBgCgpgYGB7cn0KcGxvdChpcmlzWywxOjRdKQpgYGAKCmBgYHtyfQoKcGxvdChpcmlzJHNlcGFsLndpZHRoLCBpcmlzJHNlcGFsLmxlbmd0aCwKICAgICBjb2wgPSBpcmlzWyw1XSwKICAgICBtYWluID0gJ1NjYXR0ZXJwbG90JywKICAgICB4bGFiID0gJ1NlcGFsIFdpZHRoJywKICAgICB5bGFiID0gJ1NlcGFsIExlbmd0aCcsCiAgICAgcGNoID0gMTkpCmBgYAoKYGBge3J9CnBhaXJzKGlyaXNbLDE6NF0sY29sPWlyaXNbLDVdLG9tYT1jKDQsNCw2LDEyKSkKcGFyKHhwZD1UUlVFKQoKYGBgCgpgYGB7cn0KZ2dwbG90KGlyaXMsIGFlcyh4PXNlcGFsLmxlbmd0aCwgeT1zZXBhbC53aWR0aCwgY29sb3I9c3BlY2llcykpICsKICBnZW9tX3BvaW50KHNpemU9NSkKCmBgYAoKYGBge3J9CmdncGFpcnMoaXJpcywKICAgICAgICBjb2x1bW5zID0gMTo0LAogICAgICAgIGFlcyhjb2xvciA9IHNwZWNpZXMsIGFscGhhID0gMC41KSkKYGBgCgojIyAqKkLGsOG7m2MgNDogS2nhu4NtIGNo4bupbmcgZ2nhuqMgxJHhu4tuaCB24bubaSBULXRlc3QqKgoK4bueIGJ14buVaSB0csaw4bubYyBjaMO6bmcgdGEgxJHDoyBo4buNYyBjw6FjaCBz4butIGThu6VuZyBULXRlc3QgxJHhu4MgeMOhYyDEkeG7i25oIGdp4bqjIHRodXnhur90IGPDsyBjaMOtbmggeMOhYyBoYXkga2jDtG5nLiBUxrDGoW5nIHThu7EgduG7m2kgUlN0dWRpbywgY2jDum5nIHRhIGPDsyB0aOG7gyBk4buFIGTDoG5nIHPhu60gZOG7pW5nIG5oaeG7gXUgbG/huqFpIFQtdGVzdCwg4bufIHBo4bqnbiB0aOG7sWMgaMOgbmggbsOgeSBjaMO6bmcgdGEgc+G6vSBz4butIGThu6VuZyBsb+G6oWkgVC10ZXN0IMSR4buDIHjDoWMgxJHhu4tuaCBnaeG6oyB0aHV54bq/dCBjxqEgYuG6o24gduG7gSBjw6FjIGxv4bqhaSBob2EgbmjGsCBzYXUuICoqQ8OidSBo4buPaToqKiBsaeG7h3UgbG/DoGkgc2V0b3NhIHbDoCB2ZXJzaWNvbG9yIGPDsyBjaGnhu4F1IGTDoGkgY8OhbmggaG9hIGtow6FjIG5oYXUgaGF5IGtow7RuZz8KCioqR2nhuqMgxJHhu4tuaDoqKgoKLSAgIE51bGwgSHlwb3RoZXNpcyAoSG8pOiBUcnVuZyBiw6xuaCBjaGnhu4F1IGTDoGkgY8OhbmggaG9hIDIgbG/DoGkgZ2nhu5FuZyBuaGF1IChraMOhYyBiaeG7h3QgbWVhbiBjw6FjIGxvw6BpID0gMCkKCi0gICBBbHRlcm5hdGl2ZSBIeXBvdGhlc2lzIChIYSk6IFRydW5nIGLDrG5oIGNoaeG7gXUgZMOgaSBjw6FuaCBob2EgMiBsb8OgaSBraMOhYyBuaGF1CgpgYGB7cn0KI1NURVAgNDogSFlQT1RIRVNJUyBURVNUSU5HIFdJVEggVC10ZXN0IC0tLS0KCnNldG9zYSA8LSBpcmlzW2lyaXMkc3BlY2llcyA9PSAnc2V0b3NhJyxdCnZlcnNpY29sb3IgPC0gaXJpc1tpcmlzJHNwZWNpZXMgPT0gJ3ZlcnNpY29sb3InLF0KCnQudGVzdCh4PSBzZXRvc2EkcGV0YWwubGVuZ3RoLCB5ID0gdmVyc2ljb2xvciRwZXRhbC5sZW5ndGgpCmBgYAoKIyMgKipCxrDhu5tjIDU6IFBow6JuIHTDrWNoIHBoxrDGoW5nIHNhaSBBTk9WQSoqCgpUxrDGoW5nIHThu7EgY2jDum5nIHRhIGPFqW5nIGPDsyB0aOG7gyBjaOG6oXkgY8OhYyBtw7QgaMOsbmggcGjDom4gdMOtY2ggQU5PVkEgdHJvbmcgUlN0dWRpby4gVHJvbmcgcGjhuqduIHRo4buxYyBow6BuaCBuw6B5LCBjaMO6bmcgdGEgc+G6vSBjw7luZyBwaMOibiB0w61jaCBBTk9WQSDEkeG7gyB4w6FjIMSR4buLbmggZ2nhuqMgdGh1eeG6v3Qgc28gc8OhbmggduG7gSBjaGnhu4F1IGTDoGkgY8OhbmggaG9hIGdp4buvYSBj4bqjIDMgbG/DoGkgaG9hLgoKKipDw6J1IGjhu49pOioqIGxp4buHdSAzIGxvw6BpIGhvYSBraMOhYyBuaGF1IGPDsyBjaGnhu4F1IGTDoGkgY8OhbmggaG9hIGtow6FjIG5oYXUgaGF5IGtow7RuZz8gKipHaeG6oyDEkeG7i25oOioqCgotICAgTnVsbCBIeXBvdGhlc2lzIChIbyk6IFRydW5nIGLDrG5oIGNoaeG7gXUgZMOgaSBjw6FuaCBob2EgMyBsb8OgaSBnaeG7kW5nIG5oYXUgKGtow6FjIGJp4buHdCBtZWFuIGPDoWMgbG/DoGkgPSAwKQoKLSAgIEFsdGVybmF0aXZlIEh5cG90aGVzaXMgKEhhKTogVHJ1bmcgYsOsbmggY2hp4buBdSBkw6BpIGPDoW5oIGhvYSAzIGxvw6BpIGtow6FjIG5oYXUKCmBgYHtyfQojU1RFUCA1OiBBTkFMWVNJUyBXSVRIIEFOT1ZBIC0tLS0KCnBldGFsLmxlbmd0aC5hb3YgPC0gYW92KGZvcm11bGEgPSBwZXRhbC5sZW5ndGggfiBzcGVjaWVzLCBkYXRhID0gaXJpcykKCnN1bW1hcnkob2JqZWN0ID0gcGV0YWwubGVuZ3RoLmFvdikKClR1a2V5SFNEKHBldGFsLmxlbmd0aC5hb3YpCmBgYAoKIyMgKipCxrDhu5tjIDY6IFRo4buxYyBow6BuaCBwaMOibiB0w61jaCBwaMawxqFuZyBzYWkgQU5PVkEqKgoKR2nhuqMgc+G7rSBuaMawIGLhuqFuIGNo4buJIGPDsyBjw6FjIHRow7RuZyBz4buRIHbhu4EgY2hp4buBdSBkw6BpIHbDoCBjaGnhu4F1IHLhu5luZyBj4bunYSBjw6FuaCBob2EgdGjDrCBjw7MgdGjhu4MgdOG7qyBjaMO6bmcgeMOhYyBuaOG6rW4gbG/DoGkgaG9hIGhheSBraMO0bmc/IE7hur91IHbhu5tpIDIgcGjGsMahbmcgcGjDoXAgdHLGsOG7m2MgYsOqbiB0csOqbiBjaMO6bmcgdGEgxJHDoyBjaOG7iSDEkeG7i25oIMSRxrDhu6NjIHLhurFuZyBrw61jaCB0aMaw4bubYyBjw6FuaCBob2EgY+G7p2EgMyBsb8OgaSBsw6Aga2jDoWMgYmnhu4d0ICh0xrDGoW5nIHThu7EgduG7m2kgbmjhu6V5IGhvYSkgdGjDrCBjaMO6bmcgdGEgY8OzIHRo4buDIHThu7EgdGluIHjDonkgZOG7sW5nIG3DtCBow6xuaCDEkeG7gyBk4buxIMSRb8OhbiB0w6puIGxvw6BpIGhvYSB24bubaSBjw6FjIHPhu5EgbGnhu4d1IHbhu4Ega8OtY2ggdGjGsOG7m2MgY8OhbmggaG9hIHbDoCBuaOG7pXkgaG9hLgoKQ2jDum5nIHRhIHPhur0gc+G7rSBk4bulbmcgbcO0IGjDrG5oIEstbWVhbiBDbHVzdGVyaW5nIMSR4buDIGdpw7pwIHBow6JuIGxv4bqhaSBjw6FjIG5ow7NtIGxvw6BpIGhvYSB04burIGPDoWMgY2jhu4kgc+G7kSB0csOqbiB2w6AgdOG7qyBjaMO6bmcgdGEgY8OzIHRo4buDIG5o4bqtbiBiaeG6v3QgxJHGsOG7o2MgaG9hIG7DoG8gbMOgIHRodeG7mWMgbG/DoGkgZ8OsLiDEkOG7gyBsw6BtIMSRxrDhu6NjIMSRaeG7gXUgbsOgeSwgY2jDum5nIHRhIHPhur0gY2h14bqpbiBi4buLIG3hu5l0IHbDoGkgYsaw4bubYyB44butIGzDvSBk4buvIGxp4buHdSDEkeG7gyBjw7MgZOG7ryBsaeG7h3UgxJHhu4MgdGjhu60gbmdoaeG7h20uCgrEkOG6p3UgdGnDqm4sIHRhIHPhur0gdOG6oW8gcmEgbeG7mXQgYuG7mSBk4buvIGxp4buHdSBpcmlzLnRlc3QgZ+G7k20gY2jhu4kgY8OzIDQgY+G7mXQgdGjDtG5nIHPhu5EgduG7gSBrw61jaCB0aMaw4bubYyBjaOG7qSBraMO0bmcgY8OzIHTDqm4gY+G7p2EgbG/DoGkgaG9hLiBTYXUgxJHDsyDhu58gxJHDonksIGNow7puZyB0YSBz4bq9IGPDuW5nIHPhu60gZOG7pW5nIG3DtCBow6xuaCBLLW1lYW4gQ2x1c3RlcmluZyDEkeG7gyBwaMOibiBsb+G6oWkgY2jDum5nIHbDoCBiaeG6v3QgxJHGsOG7o2MgZ2nhu5FuZyBob2EgbsOgbyBk4buxYSB0csOqbiBjw6FjIGNo4buJIHPhu5EgduG7gSDEkeG7mSBkw6BpIMSRw7MuCgpLLW1lYW4gQ2x1c3RlcmluZyBsw6AgbeG7mXQgdHJvbmcgbmjhu69uZyBwaMawxqFuZyBwaMOhcCBo4buNYyBraMO0bmcgZ2nDoW0gc8OhdCBwaOG7lSBiaeG6v24gbmjhuqV0IHRyb25nIGjhu41jIG3DoXkuIFRodeG6rXQgdG/DoW4gbsOgeSBnacO6cCB4w6FjIMSR4buLbmggImsiIG5ow7NtIChj4bulbSkgY8OzIHRo4buDIGPDsyB04burICJuIiBwaOG6p24gdOG7rSBk4buxYSB0csOqbiBraG/huqNuZyBjw6FjaCBnaeG7r2EgY8OhYyBwaOG6p24gdOG7rS4KCkdp4bqjaSB0aMOtY2ggY2hpIHRp4bq/dCBoxqFuIHPhur0gbMOgOiB0aHXhuq10IHRvw6FuIHTDrG0gcmEga2hv4bqjbmcgY8OhY2ggZ2nhu69hIG3hu5dpIHBo4bqnbiB04butIHRyb25nIGThu68gbGnhu4d1IGPhu6dhIGLhuqFuLCBzYXUgxJHDsyB0w6xtIHPhu5EgbMaw4bujbmcgdMOibSwgcGjDom4gYuG7lSBwaOG6p24gdOG7rSBjaG8gY8OhYyB0cnVuZyB0w6JtIGfhuqduIG5o4bqldCDEkeG7gyB04bqhbyB0aMOgbmggY8OhYyBj4bulbSB2w6AgbeG7pWMgdGnDqnUgY3Xhu5FpIGPDuW5nIGzDoCBnaeG7ryBuZ3V5w6puIGvDrWNoIHRoxrDhu5tjIGPhu6dhIG3hu5dpIGPhu6VtIGPDoG5nIG5o4buPIGPDoG5nIHThu5F0LgoKTeG7mXQgdHJvbmcgbmjhu69uZyBjw6J1IGjhu49pIHBo4buVIGJp4bq/biBsacOqbiBxdWFuIMSR4bq/biB0aHXhuq10IHRvw6FuIEstbWVhbiBsw6AgbGnhu4d1IG7DsyBjw7MgdGjhu4MgeOG7rSBsw70gZOG7ryBsaeG7h3Uga2jDtG5nIHBo4bqjaSBsw6Agc+G7kSBoYXkga2jDtG5nLiBDw6J1IHRy4bqjIGzhu51pIG5n4bqvbiBn4buNbiBsw6AgS0jDlE5HIHbDrCB0aHXhuq10IHRvw6FuIMSRYW5nIHPhu60gZOG7pW5nIGtob+G6o25nIGPDoWNoIGdp4buvYSBjw6FjIGzhuqduIHF1YW4gc8OhdC4gVHV5IG5oacOqbiwgY8OzIHLhuqV0IG5oaeG7gXUgdGh14bqtdCB0b8OhbiBjw7MgdGjhu4MgZ2nDunAgY2h1eeG7g24gxJHhu5VpIGPDoWMgdMOtbmggbsSDbmcga2jDtG5nIHBo4bqjaSBz4buRIHRow6BuaCBjw6FjIHTDrW5oIG7Eg25nIHPhu5EsIMSRaeG7gXUgbsOgeSBz4bq9IGNobyBwaMOpcCBi4bqhbiDDoXAgZOG7pW5nIHRodeG6rXQgdG/DoW4gSy1tZWFuIGNobyBk4buvIGxp4buHdSBj4bunYSBtw6xuaC4KCmBgYHtyfQppcmlzLnRlc3QgPC0gaXJpcwppcmlzLnRlc3Qkc3BlY2llcyA8LSBOVUxMCmhlYWQoaXJpcy50ZXN0KQoKYGBgCgpgYGB7cn0Ka21lYW5zLnJlc3VsdCA8LSBrbWVhbnMoaXJpcy50ZXN0LCAzKQp0YWJsZShpcmlzJHNwZWNpZXMsIGttZWFucy5yZXN1bHQkY2x1c3RlcikKYGBgCgpgYGB7cn0KcGxvdChpcmlzLnRlc3RbYygnc2VwYWwubGVuZ3RoJywgJ3NlcGFsLndpZHRoJyldLCBjb2wgPSBrbWVhbnMucmVzdWx0JGNsdXN0ZXIpCmBgYAoKYGBge3J9CnBsb3RjbHVzdGVyKGlyaXMudGVzdCwga21lYW5zLnJlc3VsdCRjbHVzdGVyKQpgYGAKCmBgYHtyfQpjbHVzcGxvdChpcmlzLnRlc3QsIGttZWFucy5yZXN1bHQkY2x1c3RlciwgY29sb3IgPSBUUlVFLCBzaGFkZSA9IFRSVUUpCmBgYAo=