ggplot2 cheatsheet.pdf

Labs(绘图区外部的各种元素的标题)

labs(..., title = , subtitle = , caption = , tag = )

图形标题、副标题和说明

title, 主标题。使用标题的目的是概括主要成果。尽量不要使用那些只对图形进行描述的标题,如”发动机排量与燃油效率散点图”。

subtitle, 副标题。在标题下以更小的字体添加更多附加信息。

caption, 说明,在图形下方添加文本,常用于描述数据来源。

ggplot(mpg, aes(displ, hwy)) +
  geom_point(aes(color = class)) +
  geom_smooth(se = FALSE) +
  labs(
    title = paste("Fuel efficiency generally decreases with", "engine size"),
    subtitle = paste(
      "Two seaters (sports cars) are an exception",
      "because of their light weight"
    ),
    caption = "Data from fueleconomy.gov"
  )

坐标轴和图例的标题

简短的变量名称加单位

# 气泡图
ggplot(mtcars, aes(x = wt, y = mpg, size = disp)) +
  # 将disp变量映射为图形的size属性,本例中即为散点的大小。同时生成size图例
  geom_point(shape = 21, color = "black", fill = "cornsilk") +
  # shape=21表示有框圆形(不同于单一颜色的小圆点)
  labs(
    x = "Weight (1000 lbs)", y = "Miles/(US) gallon",
    title = "Bubble Chart",
    size = "Engine\nDisplacement (cu.in.)"
    # size意为对图例添加标签
  )

使用数学公式代替字符串文本

quote()

使用?plotmath命令查看可用选项

df <- tibble(
  x = runif(10),
  y = runif(10)
)

ggplot(df, aes(x, y)) +
  geom_point() +
  labs(
    x = quote(sum(x[i]^2, i == 1, n)),
    y = quote(alpha + beta + frac(delta, theta))
  )

绘图区内部的文本标签和说明

geom_label()ggrepel::geom_label_repel()在数据旁添加文本标签

geom_text()ggrepel::geom_text_repel()在图内添加文本说明

数据点旁的标签

# 先选取出每类汽车中效率最高的型号,然后在图形中标记出来
best_in_class <- mpg %>%
  group_by(class) %>%
  filter(row_number(desc(hwy)) == 1) # desc()排序


ggplot(mpg, aes(displ, hwy)) + # 全局映射和全局数据
  geom_point(aes(color = class)) +
  geom_text(aes(label = model), data = best_in_class) # 用局部数据,将model变量映射为文本标签


ggplot(mpg, aes(displ, hwy)) +
  geom_point(aes(color = class)) +
  geom_label(aes(label = model),
    data = best_in_class,
    nudge_y = 2, alpha = 0.5
  ) # nudge_y参数可以调整标签相对于数据点的位置


# ggrepel包可以自动调整标签的位置,使它们免于重叠
ggplot(mpg, aes(displ, hwy)) +
  geom_point(aes(color = class)) +
  geom_point(size = 3, shape = 1, data = best_in_class) +
  # 添加了一个图层,用较大的空心圆来强调添加了标签的数据点
  ggrepel::geom_label_repel(aes(label = model), data = best_in_class)


# 将标签直接放在图形上,以替代图例
# 分组,对每组取数据分布的中位数,在该处放置组名
class_avg <- mpg %>%
  group_by(class) %>%
  summarize(displ = median(displ), hwy = median(hwy))

ggplot(mpg, aes(displ, hwy, color = class)) +
  ggrepel::geom_label_repel(
    aes(label = class),
    data = class_avg, size = 6,
    label.size = 0, segment.color = NA
  ) +
  geom_point() +
  theme(legend.position = "none") # 不显示图例

图中空白处的说明

创建一个数据点,位置在空白处,借这个数据添加标签

# 创建只有一个观测的数据框,用以保存标签的坐标
label <- mpg %>%
  summarize(
    displ = max(displ), hwy = max(hwy),
    label = paste(
      "Increasing engine size is \nrelated to",
      "decreasing fuel economy."
    )
  )

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  geom_text(
    aes(label = label),
    data = label,
    vjust = "top", hjust = "right"
  )


# 如果想让标签紧贴着图形的边界,可以使用+Inf和-Inf值(因为坐标轴的实际范围要比数据范围大一些)
label <- tibble(
  displ = Inf, hwy = Inf,
  label = paste(
    "Increasing engine size is \nrelated to",
    "decreasing fuel economy."
  )
)

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  geom_text(aes(label = label), data = label, vjust = "top", hjust = "right")


# 自动为标签断行的方法
"Increasing engine size related to decreasing fuel economy." %>%
  stringr::str_wrap(width = 40) %>%
  writeLines()
#> Increasing engine size related to
#> decreasing fuel economy.

vjust和hjust参数

设置标签相对于坐标的对齐方式

20220323-标签相对于坐标的对齐方式

参考线、箭头和方框

  • 可以使用geom_hline(yintercept = 0)geom_vline(xintercept = 0)添加参考线。我们经常使用加粗(size = 2)和白色(color = white)的直线作为参考线,并将它们绘制在基本数据层的下面。这样的参考线既清晰可见,又不至于喧宾夺主,影响我们查看数据。

  • 可以使用geom_rect()在我们感兴趣的数据点周围绘制一个矩形。矩形的边界由图形属性 xmin、 xmax、 ymin 和 ymax 确定。

  • 可以使用geom_segment()arrow参数绘制箭头,指向需要关注的数据点。使用 x 和 y 属性来定义开始位置,使用 xend 和 yend 属性来定义结束位置。

geom_segment(
  x = 6, y = 4, xend = 3, yend = 1, colour = "blue",
  arrow = arrow(angle = 30, length = unit(0.5, "cm"), type = "open")
)
# angle为箭头所张角度,type决定箭头是空心还是实心

图例 Legends

guides()

要想控制单个图例的显示,可以配合guide_legend()(负责离散型图例)或guide_colorbar()(负责连续型图例)来使用guides()

ggplot(mpg, aes(displ, hwy)) +
  geom_point(aes(color = class)) +
  geom_smooth(se = FALSE) +
  theme(legend.position = "bottom") +
  guides(color = guide_legend(nrow = 1, override.aes = list(size = 4)))

# nrow使图例排列为1行,size = 4使图例中的数据点更大一些

图例的位置

theme()中的legend.position参数可以控制图例的整体位置:

base <- ggplot(mpg, aes(displ, hwy)) +
  geom_point(aes(color = class))

base + theme(legend.position = "left")

base + theme(legend.position = "top")

base + theme(legend.position = "bottom")

base + theme(legend.position = "right") # 默认设置

base + theme(legend.position = "none")

也可以指定一个二元素向量,表示图例的中心相对于整张图左下角的距离(都用比例表示)

data(Salaries, package = "car")

Salaries %>%
  ggplot(aes(x = rank, y = salary, fill = sex)) +
  geom_boxplot() +
  scale_x_discrete(
    breaks = c("AsstProf", "AssocProf", "Prof"),
    labels = c(
      "Assistant\nProfessor",
      "Associate\nProfessor",
      "Full\nProfessor"
    )
  ) +
  scale_y_continuous(
    breaks = c(50000, 100000, 150000, 200000),
    labels = c("$50K", "$100K", "$150K", "$200K")
  ) +
  labs(
    title = "Faculty Salary by Rank and Gender",
    x = "", y = "", fill = "Gender"
  ) +
  theme(legend.position = c(0.1, 0.8))

# 图例中心相对于整张图左下角的距离,0.1和0.8都是相对于整个画面的比例

主题

theme()可以定制图形中的非数据元素,使图表有自己的风格,在做图表时不考虑格式,最后一次性应用主题。

ggplot2内置的八种主题

20220323-内置主题

默认主题使用灰色背景,这样可以在网格线可见的情况下更加突出数据。白色网格线既是可见的(这非常重要,因为它们非常有助于位置判定),又对视觉没有什么严重影响,我们完全可以对其视而不见。图表的灰色背景不像白色背景那么突兀,与文本印刷颜色非常相近,保证了图形与文档其他部分浑然一体。最后,灰色背景可以创建一片连续的颜色区域,使图形成为形象鲜明的一个独立视觉实体。

自定义主题

所有可定义的参数可查询ggplot2::theme()的帮助文档

参数 设置的对象
plot.title 图标题
plot.caption 图标注(一般是资料来源)
axis.title 轴标题
axis.text 轴须标签
panel.background 画图区域(fill填充,color边框)
panel.grid.major.y y轴(水平)主要网格线
panel.grid.minor.y y轴(水平)次要网格线
panel.grid.major.x x轴(竖直)主要网格线
panel.grid.minor.x x轴(竖直)次要网格线
legend.position 图例的位置
legend.title 图例的标题
legend.text 图例各项的文字
data(Salaries, package = "car")

# 定义自己的主题mytheme
mytheme <-
  theme(
    plot.title = element_text(
      face = "bold.italic",
      size = 14, color = "brown"
    ),
    axis.title = element_text(
      face = "bold.italic", size = 10,
      color = "brown"
    ),
    axis.text = element_text(
      face = "bold", size = 9,
      color = "darkblue"
    ),
    panel.background = element_rect(
      fill = "white",
      color = "darkblue"
    ),
    panel.grid.major.y = element_line(color = "grey", linetype = 1),
    panel.grid.minor.y = element_line(color = "grey", linetype = 2),
    panel.grid.major.x = element_blank(),
    panel.grid.minor.x = element_blank(),
    legend.position = "top"
  )

# 在画图时,应用自己的主题mytheme
ggplot(Salaries, aes(x = rank, y = salary, fill = sex)) +
  geom_boxplot() +
  labs(title = "Salary by Rank and Sex", x = "Rank", y = "Salary") +
  mytheme

套用现成主题:ggthemes包

ggplot(Salaries, aes(x = rank, y = salary, fill = sex)) +
  geom_boxplot() +
  labs(title = "Salary by Rank and Sex", x = "Rank", y = "Salary") +
  theme_wsj() +
  scale_fill_wsj()


ggplot(Salaries, aes(x = rank, y = salary, fill = sex)) +
  geom_boxplot() +
  labs(title = "Salary by Rank and Sex", x = "Rank", y = "Salary") +
  theme_wsj() +
  scale_fill_wsj("rgby", "") +
  theme(axis.ticks.length = unit(0.5, "cm")) +
  guides(fill = guide_legend(title = NULL))


ggplot(Salaries, aes(x = rank, y = salary, fill = sex)) +
  geom_boxplot() +
  labs(title = "Salary by Rank and Sex", x = "Rank", y = "Salary") +
  theme_economist(base_size = 14) +
  scale_fill_economist() +
  theme(axis.ticks.length = unit(0.5, "cm")) +
  guides(fill = guide_legend(title = NULL))

多图拼接

ggplot2包认为它提供的刻面功能已经足够了。为了组合ggplot图形,需要使用其他包。

patchwork 包

(p1/p2)|p3 表示p1和p2居左,分别上下;p3居右,合并成一幅图

gridExtra 包

gridExtra::grid.arrange()可以组合多幅图

data(Salaries, package = "car")
# ggplot()画的图可以保存为一个对象!
p1 <- ggplot(data = Salaries, aes(x = rank)) +
  geom_bar()
p2 <- ggplot(data = Salaries, aes(x = sex)) +
  geom_bar()
p3 <- ggplot(data = Salaries, aes(x = yrs.since.phd, y = salary)) +
  geom_point()

library(gridExtra)
grid.arrange(p1, p2, p3, ncol = 3)

arrangeGrob()则可以处理一个由图对象组成的列表(循环作图时很有用)

p <- list(p1, p2, p3)
graph <- arrangeGrob(grobs = p, ncol = 3)
grid.arrange(graph)

LS0tDQp0aXRsZTogImdncGxvdDItMiINCnN1YnRpdGxlOiAn5qC35byP576O5YyWJw0KYXV0aG9yOiAiSHVtb29uIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGNzczogLi4vY3NzL3N0eWxlLmNzcw0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICB0aGVtZTogdW5pdGVkDQogICAgaGlnaGxpZ2h0OiBoYWRkb2NrDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiB5ZXMNCiAgICAgIHNtb290aF9zY3JvbGw6IHllcw0KZG9jdW1lbnRjbGFzczogY3RleGFydA0KY2xhc3NvcHRpb246IGh5cGVycmVmLA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9DQpzb3VyY2UoIi4uL1JtYXJrZG93bi10ZW1wbGF0ZS9SbWFya2Rvd25fY29uZmlnLlIiKQ0KDQojIyBnbG9iYWwgb3B0aW9ucyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICB3aWR0aCA9IGNvbmZpZyR3aWR0aCwNCiAgZmlnLndpZHRoID0gY29uZmlnJGZpZy53aWR0aCwNCiAgZmlnLmFzcCA9IGNvbmZpZyRmaWcuYXNwLA0KICBvdXQud2lkdGggPSBjb25maWckb3V0LndpZHRoLA0KICBmaWcuYWxpZ24gPSBjb25maWckZmlnLmFsaWduLA0KICBmaWcucGF0aCA9IGNvbmZpZyRmaWcucGF0aCwNCiAgZmlnLnNob3cgPSBjb25maWckZmlnLnNob3csDQogIHdhcm4gPSBjb25maWckd2FybiwNCiAgd2FybmluZyA9IGNvbmZpZyR3YXJuaW5nLA0KICBtZXNzYWdlID0gY29uZmlnJG1lc3NhZ2UsDQogIGVjaG8gPSBjb25maWckZWNobywgDQogIGV2YWwgPSBjb25maWckZXZhbCwgDQogIHRpZHkgPSBjb25maWckdGlkeSwgDQogIGNvbW1lbnQgPSBjb25maWckY29tbWVudCwgDQogIGNvbGxhcHNlID0gY29uZmlnJGNvbGxhcHNlLCANCiAgY2FjaGUgPSBjb25maWckY2FjaGUsDQogIGNhY2hlLmNvbW1lbnRzID0gY29uZmlnJGNhY2hlLmNvbW1lbnRzLA0KICBhdXRvZGVwID0gY29uZmlnJGF1dG9kZXANCikNCg0KIyMgdXNlIG5lY2Vzc2FyeSBwYWNrYWdlcyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShodG1sd2lkZ2V0cykNCg0KcGFjbWFuOjpwX2xvYWQoDQogIGx1YnJpZGF0ZSwNCiAgc2hvd3RleHQsDQogIERULA0KICBqc29ubGl0ZSwNCiAgcmV0aWN1bGF0ZSwNCiAgY2FyLCANCiAgcmVhZHhsLCANCiAgcmVzaGFwZTIsIA0KICBSQ29sb3JCcmV3ZXINCikNCg0KZGF0YShjYXI6OlNhbGFyaWVzKQ0KDQojIyBwbG90dGluZyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KIyDljIXlkKvlm77nmoTku6PnoIHlnZfpnIDopoFmaWcuc2hvd3RleHQgPSBUUlVF6YCJ6aG5DQpzaG93dGV4dF9hdXRvKGVuYWJsZSA9IFRSVUUpDQp3aW5kb3dzRm9udHMoSCA9IHdpbmRvd3NGb250KCJNaWNyb3NvZnQgWWFIZWkiKSkNCmBgYA0KDQo8YSBocmVmPSIuLi9wZGYvY2hlYXRzaGVldC1nZ3Bsb3QucGRmIj4qZ2dwbG90MiBjaGVhdHNoZWV0LnBkZio8L2E+DQoNCjxvYmplY3QgZGF0YT0iLi4vcGRmL2NoZWF0c2hlZXQtZ2dwbG90LnBkZiIgdHlwZT0iYXBwbGljYXRpb24vcGRmIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIj48L29iamVjdD4NCg0KIyMgTGFicyjnu5jlm77ljLrlpJbpg6jnmoTlkITnp43lhYPntKDnmoTmoIfpopgpDQoNCmBsYWJzKC4uLiwgdGl0bGUgPSAsIHN1YnRpdGxlID0gLCBjYXB0aW9uID0gLCB0YWcgPSApYA0KDQojIyMg5Zu+5b2i5qCH6aKY44CB5Ymv5qCH6aKY5ZKM6K+05piODQoNCnRpdGxlLCDkuLvmoIfpopjjgILkvb/nlKjmoIfpopjnmoTnm67nmoTmmK/mpoLmi6zkuLvopoHmiJDmnpzjgILlsL3ph4/kuI3opoHkvb/nlKjpgqPkupvlj6rlr7nlm77lvaLov5vooYzmj4/ov7DnmoTmoIfpopjvvIzlpoIi5Y+R5Yqo5py65o6S6YeP5LiO54eD5rK55pWI546H5pWj54K55Zu+IuOAgg0KDQpzdWJ0aXRsZSwg5Ymv5qCH6aKY44CC5Zyo5qCH6aKY5LiL5Lul5pu05bCP55qE5a2X5L2T5re75Yqg5pu05aSa6ZmE5Yqg5L+h5oGv44CCDQoNCmNhcHRpb24sIOivtOaYju+8jOWcqOWbvuW9ouS4i+aWuea3u+WKoOaWh+acrO+8jOW4uOeUqOS6juaPj+i/sOaVsOaNruadpea6kOOAgg0KDQpgYGB7cn0NCmdncGxvdChtcGcsIGFlcyhkaXNwbCwgaHd5KSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGNsYXNzKSkgKw0KICBnZW9tX3Ntb290aChzZSA9IEZBTFNFKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSBwYXN0ZSgiRnVlbCBlZmZpY2llbmN5IGdlbmVyYWxseSBkZWNyZWFzZXMgd2l0aCIsICJlbmdpbmUgc2l6ZSIpLA0KICAgIHN1YnRpdGxlID0gcGFzdGUoDQogICAgICAiVHdvIHNlYXRlcnMgKHNwb3J0cyBjYXJzKSBhcmUgYW4gZXhjZXB0aW9uIiwNCiAgICAgICJiZWNhdXNlIG9mIHRoZWlyIGxpZ2h0IHdlaWdodCINCiAgICApLA0KICAgIGNhcHRpb24gPSAiRGF0YSBmcm9tIGZ1ZWxlY29ub215LmdvdiINCiAgKQ0KYGBgDQoNCiMjIyDlnZDmoIfovbTlkozlm77kvovnmoTmoIfpopgNCg0K566A55+t55qE5Y+Y6YeP5ZCN56ew5Yqg5Y2V5L2NDQoNCmBgYHtyLCBmaWcuc2hvdz0nYXNpcyd9DQojIOawlOazoeWbvg0KZ2dwbG90KG10Y2FycywgYWVzKHggPSB3dCwgeSA9IG1wZywgc2l6ZSA9IGRpc3ApKSArDQogICMg5bCGZGlzcOWPmOmHj+aYoOWwhOS4uuWbvuW9oueahHNpemXlsZ7mgKfvvIzmnKzkvovkuK3ljbPkuLrmlaPngrnnmoTlpKflsI/jgILlkIzml7bnlJ/miJBzaXpl5Zu+5L6LDQogIGdlb21fcG9pbnQoc2hhcGUgPSAyMSwgY29sb3IgPSAiYmxhY2siLCBmaWxsID0gImNvcm5zaWxrIikgKw0KICAjIHNoYXBlPTIx6KGo56S65pyJ5qGG5ZyG5b2i77yI5LiN5ZCM5LqO5Y2V5LiA6aKc6Imy55qE5bCP5ZyG54K577yJDQogIGxhYnMoDQogICAgeCA9ICJXZWlnaHQgKDEwMDAgbGJzKSIsIHkgPSAiTWlsZXMvKFVTKSBnYWxsb24iLA0KICAgIHRpdGxlID0gIkJ1YmJsZSBDaGFydCIsDQogICAgc2l6ZSA9ICJFbmdpbmVcbkRpc3BsYWNlbWVudCAoY3UuaW4uKSINCiAgICAjIHNpemXmhI/kuLrlr7nlm77kvovmt7vliqDmoIfnrb4NCiAgKQ0KYGBgDQoNCiMjIyDkvb/nlKjmlbDlrablhazlvI/ku6Pmm7/lrZfnrKbkuLLmlofmnKwNCg0KYHF1b3RlKClgDQoNCuS9v+eUqD9wbG90bWF0aOWRveS7pOafpeeci+WPr+eUqOmAiemhuQ0KDQpgYGB7cn0NCmRmIDwtIHRpYmJsZSgNCiAgeCA9IHJ1bmlmKDEwKSwNCiAgeSA9IHJ1bmlmKDEwKQ0KKQ0KDQpnZ3Bsb3QoZGYsIGFlcyh4LCB5KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBsYWJzKA0KICAgIHggPSBxdW90ZShzdW0oeFtpXV4yLCBpID09IDEsIG4pKSwNCiAgICB5ID0gcXVvdGUoYWxwaGEgKyBiZXRhICsgZnJhYyhkZWx0YSwgdGhldGEpKQ0KICApDQpgYGANCg0KIyMg57uY5Zu+5Yy65YaF6YOo55qE5paH5pys5qCH562+5ZKM6K+05piODQoNCmBnZW9tX2xhYmVsKClg5ZKMYGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoKWDlnKjmlbDmja7ml4Hmt7vliqDmlofmnKzmoIfnrb4NCg0KYGdlb21fdGV4dCgpYOWSjGBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoKWDlnKjlm77lhoXmt7vliqDmlofmnKzor7TmmI4NCg0KIyMjIOaVsOaNrueCueaXgeeahOagh+etvg0KDQpgYGB7ciwgZmlnLnNob3c9J2FzaXMnfQ0KIyDlhYjpgInlj5blh7rmr4/nsbvmsb3ovabkuK3mlYjnjofmnIDpq5jnmoTlnovlj7fvvIznhLblkI7lnKjlm77lvaLkuK3moIforrDlh7rmnaUNCmJlc3RfaW5fY2xhc3MgPC0gbXBnICU+JQ0KICBncm91cF9ieShjbGFzcykgJT4lDQogIGZpbHRlcihyb3dfbnVtYmVyKGRlc2MoaHd5KSkgPT0gMSkgIyBkZXNjKCnmjpLluo8NCg0KDQpnZ3Bsb3QobXBnLCBhZXMoZGlzcGwsIGh3eSkpICsgIyDlhajlsYDmmKDlsITlkozlhajlsYDmlbDmja4NCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjbGFzcykpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IG1vZGVsKSwgZGF0YSA9IGJlc3RfaW5fY2xhc3MpICMg55So5bGA6YOo5pWw5o2u77yM5bCGbW9kZWzlj5jph4/mmKDlsITkuLrmlofmnKzmoIfnrb4NCg0KZ2dwbG90KG1wZywgYWVzKGRpc3BsLCBod3kpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY2xhc3MpKSArDQogIGdlb21fbGFiZWwoYWVzKGxhYmVsID0gbW9kZWwpLA0KICAgIGRhdGEgPSBiZXN0X2luX2NsYXNzLA0KICAgIG51ZGdlX3kgPSAyLCBhbHBoYSA9IDAuNQ0KICApICMgbnVkZ2VfeeWPguaVsOWPr+S7peiwg+aVtOagh+etvuebuOWvueS6juaVsOaNrueCueeahOS9jee9rg0KDQojIGdncmVwZWzljIXlj6/ku6Xoh6rliqjosIPmlbTmoIfnrb7nmoTkvY3nva7vvIzkvb/lroPku6zlhY3kuo7ph43lj6ANCmdncGxvdChtcGcsIGFlcyhkaXNwbCwgaHd5KSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGNsYXNzKSkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAzLCBzaGFwZSA9IDEsIGRhdGEgPSBiZXN0X2luX2NsYXNzKSArDQogICMg5re75Yqg5LqG5LiA5Liq5Zu+5bGC77yM55So6L6D5aSn55qE56m65b+D5ZyG5p2l5by66LCD5re75Yqg5LqG5qCH562+55qE5pWw5o2u54K5DQogIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoYWVzKGxhYmVsID0gbW9kZWwpLCBkYXRhID0gYmVzdF9pbl9jbGFzcykNCg0KIyDlsIbmoIfnrb7nm7TmjqXmlL7lnKjlm77lvaLkuIrvvIzku6Xmm7/ku6Plm77kvosNCiMg5YiG57uE77yM5a+55q+P57uE5Y+W5pWw5o2u5YiG5biD55qE5Lit5L2N5pWw77yM5Zyo6K+l5aSE5pS+572u57uE5ZCNDQpjbGFzc19hdmcgPC0gbXBnICU+JQ0KICBncm91cF9ieShjbGFzcykgJT4lDQogIHN1bW1hcml6ZShkaXNwbCA9IG1lZGlhbihkaXNwbCksIGh3eSA9IG1lZGlhbihod3kpKQ0KDQpnZ3Bsb3QobXBnLCBhZXMoZGlzcGwsIGh3eSwgY29sb3IgPSBjbGFzcykpICsNCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbCgNCiAgICBhZXMobGFiZWwgPSBjbGFzcyksDQogICAgZGF0YSA9IGNsYXNzX2F2Zywgc2l6ZSA9IDYsDQogICAgbGFiZWwuc2l6ZSA9IDAsIHNlZ21lbnQuY29sb3IgPSBOQQ0KICApICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSAjIOS4jeaYvuekuuWbvuS+iw0KYGBgDQoNCiMjIyDlm77kuK3nqbrnmb3lpITnmoTor7TmmI4NCg0K5Yib5bu65LiA5Liq5pWw5o2u54K577yM5L2N572u5Zyo56m655m95aSE77yM5YCf6L+Z5Liq5pWw5o2u5re75Yqg5qCH562+DQoNCmBgYHtyLCBmaWcuc2hvdz0nYXNpcyd9DQojIOWIm+W7uuWPquacieS4gOS4quingua1i+eahOaVsOaNruahhu+8jOeUqOS7peS/neWtmOagh+etvueahOWdkOaghw0KbGFiZWwgPC0gbXBnICU+JQ0KICBzdW1tYXJpemUoDQogICAgZGlzcGwgPSBtYXgoZGlzcGwpLCBod3kgPSBtYXgoaHd5KSwNCiAgICBsYWJlbCA9IHBhc3RlKA0KICAgICAgIkluY3JlYXNpbmcgZW5naW5lIHNpemUgaXMgXG5yZWxhdGVkIHRvIiwNCiAgICAgICJkZWNyZWFzaW5nIGZ1ZWwgZWNvbm9teS4iDQogICAgKQ0KICApDQoNCmdncGxvdChtcGcsIGFlcyhkaXNwbCwgaHd5KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3RleHQoDQogICAgYWVzKGxhYmVsID0gbGFiZWwpLA0KICAgIGRhdGEgPSBsYWJlbCwNCiAgICB2anVzdCA9ICJ0b3AiLCBoanVzdCA9ICJyaWdodCINCiAgKQ0KDQojIOWmguaenOaDs+iuqeagh+etvue0p+i0tOedgOWbvuW9oueahOi+ueeVjO+8jOWPr+S7peS9v+eUqCtJbmblkowtSW5m5YC877yI5Zug5Li65Z2Q5qCH6L2055qE5a6e6ZmF6IyD5Zu06KaB5q+U5pWw5o2u6IyD5Zu05aSn5LiA5Lqb77yJDQpsYWJlbCA8LSB0aWJibGUoDQogIGRpc3BsID0gSW5mLCBod3kgPSBJbmYsDQogIGxhYmVsID0gcGFzdGUoDQogICAgIkluY3JlYXNpbmcgZW5naW5lIHNpemUgaXMgXG5yZWxhdGVkIHRvIiwNCiAgICAiZGVjcmVhc2luZyBmdWVsIGVjb25vbXkuIg0KICApDQopDQoNCmdncGxvdChtcGcsIGFlcyhkaXNwbCwgaHd5KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbGFiZWwpLCBkYXRhID0gbGFiZWwsIHZqdXN0ID0gInRvcCIsIGhqdXN0ID0gInJpZ2h0IikNCg0KIyDoh6rliqjkuLrmoIfnrb7mlq3ooYznmoTmlrnms5UNCiJJbmNyZWFzaW5nIGVuZ2luZSBzaXplIHJlbGF0ZWQgdG8gZGVjcmVhc2luZyBmdWVsIGVjb25vbXkuIiAlPiUNCiAgc3RyaW5ncjo6c3RyX3dyYXAod2lkdGggPSA0MCkgJT4lDQogIHdyaXRlTGluZXMoKQ0KYGBgDQoNCiMjIyB2anVzdOWSjGhqdXN05Y+C5pWwDQoNCuiuvue9ruagh+etvuebuOWvueS6juWdkOagh+eahOWvuem9kOaWueW8jw0KDQohWzIwMjIwMzIzLeagh+etvuebuOWvueS6juWdkOagh+eahOWvuem9kOaWueW8j10oaHR0cDovL2h1bW9vbi1pbWFnZS1ob3N0aW5nLXNlcnZpY2Uub3NzLWNuLWJlaWppbmcuYWxpeXVuY3MuY29tL2ltZy90eXBvcmEvMjAyMi8yMDIyMDMyMy3moIfnrb7nm7jlr7nkuo7lnZDmoIfnmoTlr7npvZDmlrnlvI8ucG5nKQ0KDQojIyDlj4LogIPnur/jgIHnrq3lpLTlkozmlrnmoYYNCg0KLSAgIOWPr+S7peS9v2DnlKhnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwKWDlkoxgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMClg5re75Yqg5Y+C6ICD57q/44CC5oiR5Lus57uP5bi45L2/55So5Yqg57KX77yIc2l6ZSA9IDLvvInlkoznmb3oibLvvIhjb2xvciA9IHdoaXRl77yJ55qE55u057q/5L2c5Li65Y+C6ICD57q/77yM5bm25bCG5a6D5Lus57uY5Yi25Zyo5Z+65pys5pWw5o2u5bGC55qE5LiL6Z2i44CC6L+Z5qC355qE5Y+C6ICD57q/5pei5riF5pmw5Y+v6KeB77yM5Y+I5LiN6Iez5LqO5Zan5a6+5aS65Li777yM5b2x5ZON5oiR5Lus5p+l55yL5pWw5o2u44CCDQoNCi0gICDlj6/ku6Xkvb/nlKhgZ2VvbV9yZWN0KClg5Zyo5oiR5Lus5oSf5YW06Laj55qE5pWw5o2u54K55ZGo5Zu057uY5Yi25LiA5Liq55+p5b2i44CC55+p5b2i55qE6L6555WM55Sx5Zu+5b2i5bGe5oCnIHhtaW7jgIEgeG1heOOAgSB5bWluIOWSjCB5bWF4IOehruWumuOAgg0KDQotICAg5Y+v5Lul5L2/55SoYGdlb21fc2VnbWVudCgpYOWPimBhcnJvd2Dlj4LmlbDnu5jliLbnrq3lpLTvvIzmjIflkJHpnIDopoHlhbPms6jnmoTmlbDmja7ngrnjgILkvb/nlKggeCDlkowgeSDlsZ7mgKfmnaXlrprkuYnlvIDlp4vkvY3nva7vvIzkvb/nlKggeGVuZCDlkowgeWVuZCDlsZ7mgKfmnaXlrprkuYnnu5PmnZ/kvY3nva7jgIINCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpnZW9tX3NlZ21lbnQoDQogIHggPSA2LCB5ID0gNCwgeGVuZCA9IDMsIHllbmQgPSAxLCBjb2xvdXIgPSAiYmx1ZSIsDQogIGFycm93ID0gYXJyb3coYW5nbGUgPSAzMCwgbGVuZ3RoID0gdW5pdCgwLjUsICJjbSIpLCB0eXBlID0gIm9wZW4iKQ0KKQ0KIyBhbmdsZeS4uueureWktOaJgOW8oOinkuW6pu+8jHR5cGXlhrPlrprnrq3lpLTmmK/nqbrlv4Pov5jmmK/lrp7lv4MNCmBgYA0KDQojIyDlm77kvosgTGVnZW5kcw0KDQojIyMgZ3VpZGVzKCkNCg0K6KaB5oOz5o6n5Yi25Y2V5Liq5Zu+5L6L55qE5pi+56S677yM5Y+v5Lul6YWN5ZCIZ3VpZGVfbGVnZW5kKCnvvIjotJ/otKPnprvmlaPlnovlm77kvovvvInmiJZndWlkZV9jb2xvcmJhcigp77yI6LSf6LSj6L+e57ut5Z6L5Zu+5L6L77yJ5p2l5L2/55SoZ3VpZGVzKCkNCg0KYGBge3J9DQpnZ3Bsb3QobXBnLCBhZXMoZGlzcGwsIGh3eSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjbGFzcykpICsNCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKw0KICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQobnJvdyA9IDEsIG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDQpKSkNCiMgbnJvd+S9v+WbvuS+i+aOkuWIl+S4ujHooYzvvIxzaXplID0gNOS9v+WbvuS+i+S4reeahOaVsOaNrueCueabtOWkp+S4gOS6mw0KYGBgDQoNCiMjIyDlm77kvovnmoTkvY3nva4NCg0KdGhlbWUoKeS4reeahGxlZ2VuZC5wb3NpdGlvbuWPguaVsOWPr+S7peaOp+WItuWbvuS+i+eahOaVtOS9k+S9jee9ru+8mg0KDQpgYGB7ciwgZmlnLnNob3c9J2FzaXMnfQ0KYmFzZSA8LSBnZ3Bsb3QobXBnLCBhZXMoZGlzcGwsIGh3eSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjbGFzcykpDQoNCmJhc2UgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibGVmdCIpDQpiYXNlICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpDQpiYXNlICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQpiYXNlICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgIyDpu5jorqTorr7nva4NCmJhc2UgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQpgYGANCg0K5Lmf5Y+v5Lul5oyH5a6a5LiA5Liq5LqM5YWD57Sg5ZCR6YeP77yM6KGo56S65Zu+5L6L55qE5Lit5b+D55u45a+55LqO5pW05byg5Zu+5bem5LiL6KeS55qE6Led56a777yI6YO955So5q+U5L6L6KGo56S677yJDQoNCmBgYHtyfQ0KZGF0YShTYWxhcmllcywgcGFja2FnZSA9ICJjYXIiKQ0KDQpTYWxhcmllcyAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gcmFuaywgeSA9IHNhbGFyeSwgZmlsbCA9IHNleCkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKA0KICAgIGJyZWFrcyA9IGMoIkFzc3RQcm9mIiwgIkFzc29jUHJvZiIsICJQcm9mIiksDQogICAgbGFiZWxzID0gYygNCiAgICAgICJBc3Npc3RhbnRcblByb2Zlc3NvciIsDQogICAgICAiQXNzb2NpYXRlXG5Qcm9mZXNzb3IiLA0KICAgICAgIkZ1bGxcblByb2Zlc3NvciINCiAgICApDQogICkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoDQogICAgYnJlYWtzID0gYyg1MDAwMCwgMTAwMDAwLCAxNTAwMDAsIDIwMDAwMCksDQogICAgbGFiZWxzID0gYygiJDUwSyIsICIkMTAwSyIsICIkMTUwSyIsICIkMjAwSyIpDQogICkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkZhY3VsdHkgU2FsYXJ5IGJ5IFJhbmsgYW5kIEdlbmRlciIsDQogICAgeCA9ICIiLCB5ID0gIiIsIGZpbGwgPSAiR2VuZGVyIg0KICApICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjEsIDAuOCkpDQojIOWbvuS+i+S4reW/g+ebuOWvueS6juaVtOW8oOWbvuW3puS4i+inkueahOi3neemu++8jDAuMeWSjDAuOOmDveaYr+ebuOWvueS6juaVtOS4queUu+mdoueahOavlOS+iw0KYGBgDQoNCiMjIOS4u+mimA0KDQp0aGVtZSgp5Y+v5Lul5a6a5Yi25Zu+5b2i5Lit55qE6Z2e5pWw5o2u5YWD57Sg77yM5L2/5Zu+6KGo5pyJ6Ieq5bex55qE6aOO5qC877yM5Zyo5YGa5Zu+6KGo5pe25LiN6ICD6JmR5qC85byP77yM5pyA5ZCO5LiA5qyh5oCn5bqU55So5Li76aKY44CCDQoNCiMjIyBnZ3Bsb3Qy5YaF572u55qE5YWr56eN5Li76aKYDQoNCiFbMjAyMjAzMjMt5YaF572u5Li76aKYXShodHRwOi8vaHVtb29uLWltYWdlLWhvc3Rpbmctc2VydmljZS5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vaW1nL3R5cG9yYS8yMDIyLzIwMjIwMzIzLeWGhee9ruS4u+mimC5wbmcpIA0KDQrpu5jorqTkuLvpopjkvb/nlKjngbDoibLog4zmma/vvIzov5nmoLflj6/ku6XlnKjnvZHmoLznur/lj6/op4HnmoTmg4XlhrXkuIvmm7TliqDnqoHlh7rmlbDmja7jgILnmb3oibLnvZHmoLznur/ml6LmmK/lj6/op4HnmoTvvIjov5npnZ7luLjph43opoHvvIzlm6DkuLrlroPku6zpnZ7luLjmnInliqnkuo7kvY3nva7liKTlrpop77yM5Y+I5a+56KeG6KeJ5rKh5pyJ5LuA5LmI5Lil6YeN5b2x5ZON77yM5oiR5Lus5a6M5YWo5Y+v5Lul5a+55YW26KeG6ICM5LiN6KeB44CC5Zu+6KGo55qE54Gw6Imy6IOM5pmv5LiN5YOP55m96Imy6IOM5pmv6YKj5LmI56qB5YWA77yM5LiO5paH5pys5Y2w5Yi36aKc6Imy6Z2e5bi455u46L+R77yM5L+d6K+B5LqG5Zu+5b2i5LiO5paH5qGj5YW25LuW6YOo5YiG5rWR54S25LiA5L2T44CC5pyA5ZCO77yM54Gw6Imy6IOM5pmv5Y+v5Lul5Yib5bu65LiA54mH6L+e57ut55qE6aKc6Imy5Yy65Z+f77yM5L2/5Zu+5b2i5oiQ5Li65b2i6LGh6bKc5piO55qE5LiA5Liq54us56uL6KeG6KeJ5a6e5L2T44CCDQoNCiMjIyDoh6rlrprkuYnkuLvpopgNCg0K5omA5pyJ5Y+v5a6a5LmJ55qE5Y+C5pWw5Y+v5p+l6K+iYGdncGxvdDI6OnRoZW1lKClg55qE5biu5Yqp5paH5qGjDQoNCnwg5Y+C5pWwICAgICAgICAgICAgICAgfCDorr7nva7nmoTlr7nosaEgICAgICAgICAgICAgICAgICAgICAgfA0KfC0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgcGxvdC50aXRsZSAgICAgICAgIHwg5Zu+5qCH6aKYICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IHBsb3QuY2FwdGlvbiAgICAgICB8IOWbvuagh+azqO+8iOS4gOiIrOaYr+i1hOaWmeadpea6kO+8iSAgICAgICAgfA0KfCBheGlzLnRpdGxlICAgICAgICAgfCDovbTmoIfpopggICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgYXhpcy50ZXh0ICAgICAgICAgIHwg6L206aG75qCH562+ICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IHBhbmVsLmJhY2tncm91bmQgICB8IOeUu+WbvuWMuuWfn++8iGZpbGzloavlhYXvvIxjb2xvcui+ueahhu+8iSB8DQp8IHBhbmVsLmdyaWQubWFqb3IueSB8IHnovbTvvIjmsLTlubPvvInkuLvopoHnvZHmoLznur8gICAgICAgICAgIHwNCnwgcGFuZWwuZ3JpZC5taW5vci55IHwgeei9tO+8iOawtOW5s++8ieasoeimgee9keagvOe6vyAgICAgICAgICAgICAgIHwNCnwgcGFuZWwuZ3JpZC5tYWpvci54IHwgeOi9tO+8iOerluebtO+8ieS4u+imgee9keagvOe6vyAgICAgICAgICAgICAgIHwNCnwgcGFuZWwuZ3JpZC5taW5vci54IHwgeOi9tO+8iOerluebtO+8ieasoeimgee9keagvOe6vyAgICAgICAgICAgICAgIHwNCnwgbGVnZW5kLnBvc2l0aW9uICAgIHwg5Zu+5L6L55qE5L2N572uICAgICAgICAgICAgICAgICAgICAgIHwNCnwgbGVnZW5kLnRpdGxlICAgICAgIHwg5Zu+5L6L55qE5qCH6aKYICAgICAgICAgICAgICAgICAgICAgIHwNCnwgbGVnZW5kLnRleHQgICAgICAgIHwg5Zu+5L6L5ZCE6aG555qE5paH5a2XICAgICAgICAgICAgICAgICAgfA0KDQpgYGB7cn0NCmRhdGEoU2FsYXJpZXMsIHBhY2thZ2UgPSAiY2FyIikNCg0KIyDlrprkuYnoh6rlt7HnmoTkuLvpophteXRoZW1lDQpteXRoZW1lIDwtDQogIHRoZW1lKA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoDQogICAgICBmYWNlID0gImJvbGQuaXRhbGljIiwNCiAgICAgIHNpemUgPSAxNCwgY29sb3IgPSAiYnJvd24iDQogICAgKSwNCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KA0KICAgICAgZmFjZSA9ICJib2xkLml0YWxpYyIsIHNpemUgPSAxMCwNCiAgICAgIGNvbG9yID0gImJyb3duIg0KICAgICksDQogICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KA0KICAgICAgZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDksDQogICAgICBjb2xvciA9ICJkYXJrYmx1ZSINCiAgICApLA0KICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgIGNvbG9yID0gImRhcmtibHVlIg0KICAgICksDQogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyZXkiLCBsaW5ldHlwZSA9IDEpLA0KICAgIHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJncmV5IiwgbGluZXR5cGUgPSAyKSwNCiAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgcGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiDQogICkNCg0KIyDlnKjnlLvlm77ml7bvvIzlupTnlKjoh6rlt7HnmoTkuLvpophteXRoZW1lDQpnZ3Bsb3QoU2FsYXJpZXMsIGFlcyh4ID0gcmFuaywgeSA9IHNhbGFyeSwgZmlsbCA9IHNleCkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBsYWJzKHRpdGxlID0gIlNhbGFyeSBieSBSYW5rIGFuZCBTZXgiLCB4ID0gIlJhbmsiLCB5ID0gIlNhbGFyeSIpICsNCiAgbXl0aGVtZQ0KYGBgDQoNCiMjIyDlpZfnlKjnjrDmiJDkuLvpopjvvJpnZ3RoZW1lc+WMhQ0KDQpgYGB7ciwgZmlnLnNob3c9J2FzaXMnfQ0KZ2dwbG90KFNhbGFyaWVzLCBhZXMoeCA9IHJhbmssIHkgPSBzYWxhcnksIGZpbGwgPSBzZXgpKSArDQogIGdlb21fYm94cGxvdCgpICsNCiAgbGFicyh0aXRsZSA9ICJTYWxhcnkgYnkgUmFuayBhbmQgU2V4IiwgeCA9ICJSYW5rIiwgeSA9ICJTYWxhcnkiKSArDQogIHRoZW1lX3dzaigpICsNCiAgc2NhbGVfZmlsbF93c2ooKQ0KDQpnZ3Bsb3QoU2FsYXJpZXMsIGFlcyh4ID0gcmFuaywgeSA9IHNhbGFyeSwgZmlsbCA9IHNleCkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBsYWJzKHRpdGxlID0gIlNhbGFyeSBieSBSYW5rIGFuZCBTZXgiLCB4ID0gIlJhbmsiLCB5ID0gIlNhbGFyeSIpICsNCiAgdGhlbWVfd3NqKCkgKw0KICBzY2FsZV9maWxsX3dzaigicmdieSIsICIiKSArDQogIHRoZW1lKGF4aXMudGlja3MubGVuZ3RoID0gdW5pdCgwLjUsICJjbSIpKSArDQogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gTlVMTCkpDQoNCmdncGxvdChTYWxhcmllcywgYWVzKHggPSByYW5rLCB5ID0gc2FsYXJ5LCBmaWxsID0gc2V4KSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGxhYnModGl0bGUgPSAiU2FsYXJ5IGJ5IFJhbmsgYW5kIFNleCIsIHggPSAiUmFuayIsIHkgPSAiU2FsYXJ5IikgKw0KICB0aGVtZV9lY29ub21pc3QoYmFzZV9zaXplID0gMTQpICsNCiAgc2NhbGVfZmlsbF9lY29ub21pc3QoKSArDQogIHRoZW1lKGF4aXMudGlja3MubGVuZ3RoID0gdW5pdCgwLjUsICJjbSIpKSArDQogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gTlVMTCkpDQpgYGANCg0KIyMg5aSa5Zu+5ou85o6lDQoNCmdncGxvdDLljIXorqTkuLrlroPmj5DkvpvnmoTliLvpnaLlip/og73lt7Lnu4/otrPlpJ/kuobjgILkuLrkuobnu4TlkIhnZ3Bsb3Tlm77lvaLvvIzpnIDopoHkvb/nlKjlhbbku5bljIXjgIINCg0KIyMjIHBhdGNod29yayDljIUNCg0KYChwMS9wMil8cDNgIOihqOekunAx5ZKMcDLlsYXlt6bvvIzliIbliKvkuIrkuIvvvJtwM+WxheWPs++8jOWQiOW5tuaIkOS4gOW5heWbvg0KDQojIyMgZ3JpZEV4dHJhIOWMhQ0KDQpgZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UoKWDlj6/ku6Xnu4TlkIjlpJrluYXlm74NCg0KYGBge3J9DQpkYXRhKFNhbGFyaWVzLCBwYWNrYWdlID0gImNhciIpDQojIGdncGxvdCgp55S755qE5Zu+5Y+v5Lul5L+d5a2Y5Li65LiA5Liq5a+56LGh77yBDQpwMSA8LSBnZ3Bsb3QoZGF0YSA9IFNhbGFyaWVzLCBhZXMoeCA9IHJhbmspKSArDQogIGdlb21fYmFyKCkNCnAyIDwtIGdncGxvdChkYXRhID0gU2FsYXJpZXMsIGFlcyh4ID0gc2V4KSkgKw0KICBnZW9tX2JhcigpDQpwMyA8LSBnZ3Bsb3QoZGF0YSA9IFNhbGFyaWVzLCBhZXMoeCA9IHlycy5zaW5jZS5waGQsIHkgPSBzYWxhcnkpKSArDQogIGdlb21fcG9pbnQoKQ0KDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmdyaWQuYXJyYW5nZShwMSwgcDIsIHAzLCBuY29sID0gMykNCmBgYA0KDQpgYXJyYW5nZUdyb2IoKWDliJnlj6/ku6XlpITnkIbkuIDkuKrnlLHlm77lr7nosaHnu4TmiJDnmoTliJfooajvvIjlvqrnjq/kvZzlm77ml7blvojmnInnlKjvvIkNCg0KYGBge3J9DQpwIDwtIGxpc3QocDEsIHAyLCBwMykNCmdyYXBoIDwtIGFycmFuZ2VHcm9iKGdyb2JzID0gcCwgbmNvbCA9IDMpDQpncmlkLmFycmFuZ2UoZ3JhcGgpDQpgYGANCg==