2. Concepts Distance Matrix
concepts <- rownames(word_topic_matrix)
# - Hellinger distances
conceptDist <- parDist(as.matrix(word_topic_matrix),
method = "hellinger",
diag = T,
upper = T,
threads = 7)
rm(word_topic_matrix); gc()
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 2130230 113.8 3968292 212.0 3019832 161.3
Vcells 153158176 1168.6 213581747 1629.5 171223122 1306.4
conceptDistMat <- as.matrix(conceptDist)
rm(conceptDist); gc()
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 2139946 114.3 3968292 212.0 3019832 161.3
Vcells 103343176 788.5 408129829 3113.8 502043035 3830.3
rownames(conceptDistMat) <- concepts
colnames(conceptDistMat) <- concepts
saveRDS(conceptDistMat,
paste0(analyticsDir, "conceptDistMat.Rds")
)
rm(concepts)
3 Visualize!
3.2 Visualize local concept neighbourhoods: Google
# - locate "google"
term <- "google"
# - size of the neighbourhood
n_size <- 5
wTerm <- which(grepl(term, rownames(conceptDistMat)))
# - fetch neighbourhood
ng1 <- vector(mode = "list", length = length(wTerm))
for (i in 1:length(wTerm)) {
ng1[[i]] <- names(
sort(conceptDistMat[wTerm[i], ], decreasing = F)[1:n_size+1]
)
}
names(ng1) <- rownames(conceptDistMat)[wTerm]
for (i in 1:length(ng1)) {
ng1[[i]] <- setdiff(ng1[[i]], names(ng1)[i])
}
ng2 <- vector(mode = "list", length = sum(sapply(ng1, length)))
c <- 0
for (i in 1:length(ng1)) {
for (j in 1:length(ng1[[i]])) {
c <- c + 1
ng2[[c]] <- names(
sort(
conceptDistMat[which(rownames(conceptDistMat) == ng1[[i]][j]), ],
decreasing = F)[1:n_size+1]
)
}
}
names(ng2) <- unname(unlist(ng1))
for (i in 1:length(ng2)) {
ng2[[i]] <- setdiff(ng2[[i]], names(ng2)[i])
}
graphData <- rbind(stack(ng1), stack(ng2))
graphData$ind <- as.character(graphData$ind)
graphData <- graphData[, c(2, 1)]
colnames(graphData) <- c('outgoing', 'incoming')
graphData$incoming[grepl("^wd_", graphData$incoming)] <-
toupper(graphData$incoming[grepl("^wd_", graphData$incoming)])
graphData$outgoing[grepl("^wd_", graphData$outgoing)] <-
toupper(graphData$outgoing[grepl("^wd_", graphData$outgoing)])
# - visualize w. {visNetwork}
nodes <- unique(c(graphData$outgoing, graphData$incoming))
nodes <- data.frame(id = 1:length(nodes),
label = nodes,
stringsAsFactors = F)
edges <- graphData
colnames(edges) <- c("from", "to")
edges$from <- sapply(edges$from, function(x) {
nodes$id[which(nodes$label == x)]
})
edges$to <- sapply(edges$to, function(x) {
nodes$id[which(nodes$label == x)]
})
# - visualize
visNetwork(nodes, edges, width = "100%") %>%
visIgraphLayout() %>%
visNodes(
shape = "dot",
color = list(
background = "#0085AF",
border = "#013848",
highlight = "#FF8000"
),
shadow = list(enabled = TRUE, size = 10)
) %>%
visEdges(
shadow = FALSE,
color = list(color = "#0085AF", highlight = "#C62F4B")
) %>%
visOptions(highlightNearest = TRUE, selectedBy = "label") %>%
visLayout(randomSeed = 11)
3.3 Visualize local concept neighbourhoods: Apple
# - locate "Apple"
term <- "appl"
# - size of the neighbourhood
n_size <- 5
wTerm <- which(grepl(term, rownames(conceptDistMat)))
# - fetch neighbourhood
ng1 <- vector(mode = "list", length = length(wTerm))
for (i in 1:length(wTerm)) {
ng1[[i]] <- names(
sort(conceptDistMat[wTerm[i], ], decreasing = F)[1:n_size+1]
)
}
names(ng1) <- rownames(conceptDistMat)[wTerm]
for (i in 1:length(ng1)) {
ng1[[i]] <- setdiff(ng1[[i]], names(ng1)[i])
}
ng2 <- vector(mode = "list", length = sum(sapply(ng1, length)))
c <- 0
for (i in 1:length(ng1)) {
for (j in 1:length(ng1[[i]])) {
c <- c + 1
ng2[[c]] <- names(
sort(
conceptDistMat[which(rownames(conceptDistMat) == ng1[[i]][j]), ],
decreasing = F)[1:n_size+1]
)
}
}
names(ng2) <- unname(unlist(ng1))
for (i in 1:length(ng2)) {
ng2[[i]] <- setdiff(ng2[[i]], names(ng2)[i])
}
graphData <- rbind(stack(ng1), stack(ng2))
graphData$ind <- as.character(graphData$ind)
graphData <- graphData[, c(2, 1)]
colnames(graphData) <- c('outgoing', 'incoming')
graphData$incoming[grepl("^wd_", graphData$incoming)] <-
toupper(graphData$incoming[grepl("^wd_", graphData$incoming)])
graphData$outgoing[grepl("^wd_", graphData$outgoing)] <-
toupper(graphData$outgoing[grepl("^wd_", graphData$outgoing)])
# - visualize w. {visNetwork}
nodes <- unique(c(graphData$outgoing, graphData$incoming))
nodes <- data.frame(id = 1:length(nodes),
label = nodes,
stringsAsFactors = F)
edges <- graphData
colnames(edges) <- c("from", "to")
edges$from <- sapply(edges$from, function(x) {
nodes$id[which(nodes$label == x)]
})
edges$to <- sapply(edges$to, function(x) {
nodes$id[which(nodes$label == x)]
})
# - visualize
visNetwork(nodes, edges, width = "100%") %>%
visIgraphLayout() %>%
visNodes(
shape = "dot",
color = list(
background = "#0085AF",
border = "#013848",
highlight = "#FF8000"
),
shadow = list(enabled = TRUE, size = 10)
) %>%
visEdges(
shadow = FALSE,
color = list(color = "#0085AF", highlight = "#C62F4B")
) %>%
visOptions(highlightNearest = TRUE, selectedBy = "label") %>%
visLayout(randomSeed = 11)
3.4 Visualize local concept neighbourhoods: Amazon
# - locate "Amazon"
term <- "amazon"
# - size of the neighbourhood
n_size <- 5
wTerm <- which(grepl(term, rownames(conceptDistMat)))
# - fetch neighbourhood
ng1 <- vector(mode = "list", length = length(wTerm))
for (i in 1:length(wTerm)) {
ng1[[i]] <- names(
sort(conceptDistMat[wTerm[i], ], decreasing = F)[1:n_size+1]
)
}
names(ng1) <- rownames(conceptDistMat)[wTerm]
for (i in 1:length(ng1)) {
ng1[[i]] <- setdiff(ng1[[i]], names(ng1)[i])
}
ng2 <- vector(mode = "list", length = sum(sapply(ng1, length)))
c <- 0
for (i in 1:length(ng1)) {
for (j in 1:length(ng1[[i]])) {
c <- c + 1
ng2[[c]] <- names(
sort(
conceptDistMat[which(rownames(conceptDistMat) == ng1[[i]][j]), ],
decreasing = F)[1:n_size+1]
)
}
}
names(ng2) <- unname(unlist(ng1))
for (i in 1:length(ng2)) {
ng2[[i]] <- setdiff(ng2[[i]], names(ng2)[i])
}
graphData <- rbind(stack(ng1), stack(ng2))
graphData$ind <- as.character(graphData$ind)
graphData <- graphData[, c(2, 1)]
colnames(graphData) <- c('outgoing', 'incoming')
graphData$incoming[grepl("^wd_", graphData$incoming)] <-
toupper(graphData$incoming[grepl("^wd_", graphData$incoming)])
graphData$outgoing[grepl("^wd_", graphData$outgoing)] <-
toupper(graphData$outgoing[grepl("^wd_", graphData$outgoing)])
# - visualize w. {visNetwork}
nodes <- unique(c(graphData$outgoing, graphData$incoming))
nodes <- data.frame(id = 1:length(nodes),
label = nodes,
stringsAsFactors = F)
edges <- graphData
colnames(edges) <- c("from", "to")
edges$from <- sapply(edges$from, function(x) {
nodes$id[which(nodes$label == x)]
})
edges$to <- sapply(edges$to, function(x) {
nodes$id[which(nodes$label == x)]
})
# - visualize
visNetwork(nodes, edges, width = "100%") %>%
visIgraphLayout() %>%
visNodes(
shape = "dot",
color = list(
background = "#0085AF",
border = "#013848",
highlight = "#FF8000"
),
shadow = list(enabled = TRUE, size = 10)
) %>%
visEdges(
shadow = FALSE,
color = list(color = "#0085AF", highlight = "#C62F4B")
) %>%
visOptions(highlightNearest = TRUE, selectedBy = "label") %>%
visLayout(randomSeed = 11)
3.5 Visualize local concept neighbourhoods: Microsoft
# - locate "Microsoft"
term <- "microsoft"
# - size of the neighbourhood
n_size <- 10
wTerm <- which(grepl(term, rownames(conceptDistMat)))
# - fetch neighbourhood
ng1 <- vector(mode = "list", length = length(wTerm))
for (i in 1:length(wTerm)) {
ng1[[i]] <- names(
sort(conceptDistMat[wTerm[i], ], decreasing = F)[1:n_size+1]
)
}
names(ng1) <- rownames(conceptDistMat)[wTerm]
for (i in 1:length(ng1)) {
ng1[[i]] <- setdiff(ng1[[i]], names(ng1)[i])
}
ng2 <- vector(mode = "list", length = sum(sapply(ng1, length)))
c <- 0
for (i in 1:length(ng1)) {
for (j in 1:length(ng1[[i]])) {
c <- c + 1
ng2[[c]] <- names(
sort(
conceptDistMat[which(rownames(conceptDistMat) == ng1[[i]][j]), ],
decreasing = F)[1:n_size+1]
)
}
}
names(ng2) <- unname(unlist(ng1))
for (i in 1:length(ng2)) {
ng2[[i]] <- setdiff(ng2[[i]], names(ng2)[i])
}
graphData <- rbind(stack(ng1), stack(ng2))
graphData$ind <- as.character(graphData$ind)
graphData <- graphData[, c(2, 1)]
colnames(graphData) <- c('outgoing', 'incoming')
graphData$incoming[grepl("^wd_", graphData$incoming)] <-
toupper(graphData$incoming[grepl("^wd_", graphData$incoming)])
graphData$outgoing[grepl("^wd_", graphData$outgoing)] <-
toupper(graphData$outgoing[grepl("^wd_", graphData$outgoing)])
# - visualize w. {visNetwork}
nodes <- unique(c(graphData$outgoing, graphData$incoming))
nodes <- data.frame(id = 1:length(nodes),
label = nodes,
stringsAsFactors = F)
edges <- graphData
colnames(edges) <- c("from", "to")
edges$from <- sapply(edges$from, function(x) {
nodes$id[which(nodes$label == x)]
})
edges$to <- sapply(edges$to, function(x) {
nodes$id[which(nodes$label == x)]
})
# - visualize
visNetwork(nodes, edges, width = "100%") %>%
visIgraphLayout() %>%
visNodes(
shape = "dot",
color = list(
background = "#0085AF",
border = "#013848",
highlight = "#FF8000"
),
shadow = list(enabled = TRUE, size = 10)
) %>%
visEdges(
shadow = FALSE,
color = list(color = "#0085AF", highlight = "#C62F4B")
) %>%
visOptions(highlightNearest = TRUE, selectedBy = "label") %>%
visLayout(randomSeed = 11)
3.6 Visualize local concept neighbourhoods: Facebook
# - locate "Facebook"
term <- "facebook"
# - size of the neighbourhood
n_size <- 3
wTerm <- which(grepl(term, rownames(conceptDistMat)))
# - fetch neighbourhood
ng1 <- vector(mode = "list", length = length(wTerm))
for (i in 1:length(wTerm)) {
ng1[[i]] <- names(
sort(conceptDistMat[wTerm[i], ], decreasing = F)[1:n_size+1]
)
}
names(ng1) <- rownames(conceptDistMat)[wTerm]
for (i in 1:length(ng1)) {
ng1[[i]] <- setdiff(ng1[[i]], names(ng1)[i])
}
ng2 <- vector(mode = "list", length = sum(sapply(ng1, length)))
c <- 0
for (i in 1:length(ng1)) {
for (j in 1:length(ng1[[i]])) {
c <- c + 1
ng2[[c]] <- names(
sort(
conceptDistMat[which(rownames(conceptDistMat) == ng1[[i]][j]), ],
decreasing = F)[1:n_size+1]
)
}
}
names(ng2) <- unname(unlist(ng1))
for (i in 1:length(ng2)) {
ng2[[i]] <- setdiff(ng2[[i]], names(ng2)[i])
}
graphData <- rbind(stack(ng1), stack(ng2))
graphData$ind <- as.character(graphData$ind)
graphData <- graphData[, c(2, 1)]
colnames(graphData) <- c('outgoing', 'incoming')
graphData$incoming[grepl("^wd_", graphData$incoming)] <-
toupper(graphData$incoming[grepl("^wd_", graphData$incoming)])
graphData$outgoing[grepl("^wd_", graphData$outgoing)] <-
toupper(graphData$outgoing[grepl("^wd_", graphData$outgoing)])
# - visualize w. {visNetwork}
nodes <- unique(c(graphData$outgoing, graphData$incoming))
nodes <- data.frame(id = 1:length(nodes),
label = nodes,
stringsAsFactors = F)
edges <- graphData
colnames(edges) <- c("from", "to")
edges$from <- sapply(edges$from, function(x) {
nodes$id[which(nodes$label == x)]
})
edges$to <- sapply(edges$to, function(x) {
nodes$id[which(nodes$label == x)]
})
# - visualize
visNetwork(nodes, edges, width = "100%") %>%
visIgraphLayout() %>%
visNodes(
shape = "dot",
color = list(
background = "#0085AF",
border = "#013848",
highlight = "#FF8000"
),
shadow = list(enabled = TRUE, size = 10)
) %>%
visEdges(
shadow = FALSE,
color = list(color = "#0085AF", highlight = "#C62F4B")
) %>%
visOptions(highlightNearest = TRUE, selectedBy = "label") %>%
visLayout(randomSeed = 11)
Goran S. Milovanović & Mike Page
DataKolektiv, 2019.
contact: datakolektiv@datakolektiv.com
![](data:image/png;base64,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)
License: GPLv3 This Notebook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This Notebook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this Notebook. If not, see http://www.gnu.org/licenses/.
LS0tCnRpdGxlOiBTZW1hbnRpYyBXZWIgVGVjaG5vbG9naWVzIGFuZCBXaWtpZGF0YSBmcm9tIFIKYXV0aG9yOgotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhwogIGFmZmlsaWF0aW9uOiBXaWtpbWVkaWEgRGV1dHNjaGxhbmQsIERhdGEgU2NpZW50aXN0LCBEYXRhS29sZWt0aXYsIE93bmVyCi0gbmFtZTogTWlrZSBQYWdlCiAgYWZmaWxpYXRpb246IERhdGFLb2xla3RpdiwgSnVuaW9yIERhdGEgU2NpZW50aXN0CmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCICVZJylgIgphYnN0cmFjdDogCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgdG9jX2RlcHRoOiA1CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDUKLS0tCgohW10oaW1nL0RLX0xvZ29fMTAwLnBuZykKCioqKgojIyMgTm90ZWJvb2sgMDg6IFZpc3VhbGl6YXRpb25zCioqRmVlZGJhY2sqKiBzaG91bGQgYmUgc2VuZCB0byBgZ29yYW4ubWlsb3Zhbm92aWNAZGF0YWtvbGVrdGl2LmNvbWAuIApUaGVzZSBub3RlYm9va3MgYWNjb21wYW55IHRoZSBNaWxhbm9SIHRhbGsgMjAxOS8wNi8yNS4KCioqKgoKIyMjIDEuIFNldHVwCgoqKk5vdGUuKiogVGhlIGZvbGxvd2luZyBjaHVua3MgbG9hZCBwYWNrYWdlcywgZGVmaW5lIHRoZSBwcm9qZWN0IGRpcmVjdG9yeSB0cmVlIGFuZCBzb21lIGNvbnN0YW50cy4KCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVCwgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gbGlicmFyaWVzCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHZpc05ldHdvcmspCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeSh0bSkKbGlicmFyeShCQm1pc2MpCmxpYnJhcnkodGV4dDJ2ZWMpCmxpYnJhcnkocGFyYWxsZWxEaXN0KQoKIyMjIC0tLSBkaXJlY3RvcmllcwpkYXRhRGlyIDwtICdkYXRhLycKYW5hbHl0aWNzRGlyIDwtICdhbmFseXRpY3MvJwpmdW5EaXIgPC0gJ2Z1bmN0aW9ucy8nCmBgYAoKIyMjIDIuIExvYWQgdGhlIHRvcGljYWwgZGlzdHJpYnV0aW9uczogYGRvY3VtZW50X3RvcGljX21hdHJpeGAgYW5kIGB3b3JkX3RvcGljX21hdHJpeGAKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVH0Kd29yZF90b3BpY19tYXRyaXggPC0gcmVhZC5jc3YoCiAgcGFzdGUwKGFuYWx5dGljc0RpciwgImFuYWx5c2lzX3dvcmRfdG9waWNfbWF0cml4LmNzdiIpLCAKICBoZWFkZXIgPSBULCAKICBjaGVjay5uYW1lcyA9IEYsIAogIHJvdy5uYW1lcyA9IDEsCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmBgYAoKIyMjIDIuIENvbmNlcHRzIERpc3RhbmNlIE1hdHJpeAoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBUfQpjb25jZXB0cyA8LSByb3duYW1lcyh3b3JkX3RvcGljX21hdHJpeCkKIyAtIEhlbGxpbmdlciBkaXN0YW5jZXMKY29uY2VwdERpc3QgPC0gcGFyRGlzdChhcy5tYXRyaXgod29yZF90b3BpY19tYXRyaXgpLAogICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJoZWxsaW5nZXIiLAogICAgICAgICAgICAgICAgICAgICAgIGRpYWcgPSBULAogICAgICAgICAgICAgICAgICAgICAgIHVwcGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICB0aHJlYWRzID0gNykKcm0od29yZF90b3BpY19tYXRyaXgpOyBnYygpCmNvbmNlcHREaXN0TWF0IDwtIGFzLm1hdHJpeChjb25jZXB0RGlzdCkKcm0oY29uY2VwdERpc3QpOyBnYygpCnJvd25hbWVzKGNvbmNlcHREaXN0TWF0KSA8LSBjb25jZXB0cwpjb2xuYW1lcyhjb25jZXB0RGlzdE1hdCkgPC0gY29uY2VwdHMKc2F2ZVJEUyhjb25jZXB0RGlzdE1hdCwgCiAgICAgICAgcGFzdGUwKGFuYWx5dGljc0RpciwgImNvbmNlcHREaXN0TWF0LlJkcyIpCiAgICAgICAgKQpybShjb25jZXB0cykKYGBgCgojIyMgMyBWaXN1YWxpemUhCgojIyMjIDMuMSBWaXN1YWxpemUgbG9jYWwgY29uY2VwdCBuZWlnaGJvdXJob29kczogYFR3aXR0ZXJgCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFR9CiMgLSBsb2NhdGUgInR3aXR0ZXIiCnRlcm0gPC0gInR3aXR0ZXIiCiMgLSBzaXplIG9mIHRoZSBuZWlnaGJvdXJob29kCm5fc2l6ZSA8LSAyMAp3VGVybSA8LSB3aGljaChncmVwbCh0ZXJtLCByb3duYW1lcyhjb25jZXB0RGlzdE1hdCkpKQojIC0gZmV0Y2ggbmVpZ2hib3VyaG9vZApuZzEgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IGxlbmd0aCh3VGVybSkpCmZvciAoaSBpbiAxOmxlbmd0aCh3VGVybSkpIHsKICBuZzFbW2ldXSA8LSBuYW1lcygKICAgIHNvcnQoY29uY2VwdERpc3RNYXRbd1Rlcm1baV0sIF0sIGRlY3JlYXNpbmcgPSBGKVsxOm5fc2l6ZSsxXQogICkKfQpuYW1lcyhuZzEpIDwtIHJvd25hbWVzKGNvbmNlcHREaXN0TWF0KVt3VGVybV0KZm9yIChpIGluIDE6bGVuZ3RoKG5nMSkpIHsKICBuZzFbW2ldXSA8LSBzZXRkaWZmKG5nMVtbaV1dLCBuYW1lcyhuZzEpW2ldKQp9Cm5nMiA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gc3VtKHNhcHBseShuZzEsIGxlbmd0aCkpKQpjIDwtIDAKZm9yIChpIGluIDE6bGVuZ3RoKG5nMSkpIHsKICBmb3IgKGogaW4gMTpsZW5ndGgobmcxW1tpXV0pKSB7CiAgICBjIDwtIGMgKyAxCiAgICBuZzJbW2NdXSA8LSBuYW1lcygKICAgIHNvcnQoCiAgICAgIGNvbmNlcHREaXN0TWF0W3doaWNoKHJvd25hbWVzKGNvbmNlcHREaXN0TWF0KSA9PSBuZzFbW2ldXVtqXSksIF0sIAogICAgICBkZWNyZWFzaW5nID0gRilbMTpuX3NpemUrMV0KICAgICkKICB9Cn0KbmFtZXMobmcyKSA8LSB1bm5hbWUodW5saXN0KG5nMSkpCmZvciAoaSBpbiAxOmxlbmd0aChuZzIpKSB7CiAgbmcyW1tpXV0gPC0gc2V0ZGlmZihuZzJbW2ldXSwgbmFtZXMobmcyKVtpXSkKfQpncmFwaERhdGEgPC0gcmJpbmQoc3RhY2sobmcxKSwgc3RhY2sobmcyKSkKZ3JhcGhEYXRhJGluZCA8LSBhcy5jaGFyYWN0ZXIoZ3JhcGhEYXRhJGluZCkKZ3JhcGhEYXRhIDwtIGdyYXBoRGF0YVssIGMoMiwgMSldCmNvbG5hbWVzKGdyYXBoRGF0YSkgPC0gYygnb3V0Z29pbmcnLCAnaW5jb21pbmcnKQpncmFwaERhdGEkaW5jb21pbmdbZ3JlcGwoIl53ZF8iLCBncmFwaERhdGEkaW5jb21pbmcpXSA8LSAKICB0b3VwcGVyKGdyYXBoRGF0YSRpbmNvbWluZ1tncmVwbCgiXndkXyIsIGdyYXBoRGF0YSRpbmNvbWluZyldKQpncmFwaERhdGEkb3V0Z29pbmdbZ3JlcGwoIl53ZF8iLCBncmFwaERhdGEkb3V0Z29pbmcpXSA8LSAKICB0b3VwcGVyKGdyYXBoRGF0YSRvdXRnb2luZ1tncmVwbCgiXndkXyIsIGdyYXBoRGF0YSRvdXRnb2luZyldKQoKIyAtIHZpc3VhbGl6ZSB3LiB7dmlzTmV0d29ya30Kbm9kZXMgPC0gdW5pcXVlKGMoZ3JhcGhEYXRhJG91dGdvaW5nLCBncmFwaERhdGEkaW5jb21pbmcpKQpub2RlcyA8LSBkYXRhLmZyYW1lKGlkID0gMTpsZW5ndGgobm9kZXMpLAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gbm9kZXMsCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmVkZ2VzIDwtIGdyYXBoRGF0YQpjb2xuYW1lcyhlZGdlcykgPC0gYygiZnJvbSIsICJ0byIpCmVkZ2VzJGZyb20gPC0gc2FwcGx5KGVkZ2VzJGZyb20sIGZ1bmN0aW9uKHgpIHsKICBub2RlcyRpZFt3aGljaChub2RlcyRsYWJlbCA9PSB4KV0KfSkKZWRnZXMkdG8gPC0gc2FwcGx5KGVkZ2VzJHRvLCBmdW5jdGlvbih4KSB7CiAgbm9kZXMkaWRbd2hpY2gobm9kZXMkbGFiZWwgPT0geCldCn0pCgojIC0gdmlzdWFsaXplCnZpc05ldHdvcmsobm9kZXMsIGVkZ2VzLCB3aWR0aCA9ICIxMDAlIikgJT4lCiAgdmlzSWdyYXBoTGF5b3V0KCkgJT4lCiAgdmlzTm9kZXMoCiAgICBzaGFwZSA9ICJkb3QiLAogICAgY29sb3IgPSBsaXN0KAogICAgICBiYWNrZ3JvdW5kID0gIiMwMDg1QUYiLAogICAgICBib3JkZXIgPSAiIzAxMzg0OCIsCiAgICAgIGhpZ2hsaWdodCA9ICIjRkY4MDAwIgogICAgKSwKICAgIHNoYWRvdyA9IGxpc3QoZW5hYmxlZCA9IFRSVUUsIHNpemUgPSAxMCkKICApICU+JQogIHZpc0VkZ2VzKAogICAgc2hhZG93ID0gRkFMU0UsCiAgICBjb2xvciA9IGxpc3QoY29sb3IgPSAiIzAwODVBRiIsIGhpZ2hsaWdodCA9ICIjQzYyRjRCIikKICApICU+JQogIHZpc09wdGlvbnMoaGlnaGxpZ2h0TmVhcmVzdCA9IFRSVUUsIHNlbGVjdGVkQnkgPSAibGFiZWwiKSAlPiUKICB2aXNMYXlvdXQocmFuZG9tU2VlZCA9IDExKQpgYGAKCiMjIyMgMy4yIFZpc3VhbGl6ZSBsb2NhbCBjb25jZXB0IG5laWdoYm91cmhvb2RzOiBgR29vZ2xlYAoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBUfQojIC0gbG9jYXRlICJnb29nbGUiCnRlcm0gPC0gImdvb2dsZSIKIyAtIHNpemUgb2YgdGhlIG5laWdoYm91cmhvb2QKbl9zaXplIDwtIDUKd1Rlcm0gPC0gd2hpY2goZ3JlcGwodGVybSwgcm93bmFtZXMoY29uY2VwdERpc3RNYXQpKSkKIyAtIGZldGNoIG5laWdoYm91cmhvb2QKbmcxIDwtIHZlY3Rvcihtb2RlID0gImxpc3QiLCBsZW5ndGggPSBsZW5ndGgod1Rlcm0pKQpmb3IgKGkgaW4gMTpsZW5ndGgod1Rlcm0pKSB7CiAgbmcxW1tpXV0gPC0gbmFtZXMoCiAgICBzb3J0KGNvbmNlcHREaXN0TWF0W3dUZXJtW2ldLCBdLCBkZWNyZWFzaW5nID0gRilbMTpuX3NpemUrMV0KICApCn0KbmFtZXMobmcxKSA8LSByb3duYW1lcyhjb25jZXB0RGlzdE1hdClbd1Rlcm1dCmZvciAoaSBpbiAxOmxlbmd0aChuZzEpKSB7CiAgbmcxW1tpXV0gPC0gc2V0ZGlmZihuZzFbW2ldXSwgbmFtZXMobmcxKVtpXSkKfQpuZzIgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IHN1bShzYXBwbHkobmcxLCBsZW5ndGgpKSkKYyA8LSAwCmZvciAoaSBpbiAxOmxlbmd0aChuZzEpKSB7CiAgZm9yIChqIGluIDE6bGVuZ3RoKG5nMVtbaV1dKSkgewogICAgYyA8LSBjICsgMQogICAgbmcyW1tjXV0gPC0gbmFtZXMoCiAgICBzb3J0KAogICAgICBjb25jZXB0RGlzdE1hdFt3aGljaChyb3duYW1lcyhjb25jZXB0RGlzdE1hdCkgPT0gbmcxW1tpXV1bal0pLCBdLCAKICAgICAgZGVjcmVhc2luZyA9IEYpWzE6bl9zaXplKzFdCiAgICApCiAgfQp9Cm5hbWVzKG5nMikgPC0gdW5uYW1lKHVubGlzdChuZzEpKQpmb3IgKGkgaW4gMTpsZW5ndGgobmcyKSkgewogIG5nMltbaV1dIDwtIHNldGRpZmYobmcyW1tpXV0sIG5hbWVzKG5nMilbaV0pCn0KZ3JhcGhEYXRhIDwtIHJiaW5kKHN0YWNrKG5nMSksIHN0YWNrKG5nMikpCmdyYXBoRGF0YSRpbmQgPC0gYXMuY2hhcmFjdGVyKGdyYXBoRGF0YSRpbmQpCmdyYXBoRGF0YSA8LSBncmFwaERhdGFbLCBjKDIsIDEpXQpjb2xuYW1lcyhncmFwaERhdGEpIDwtIGMoJ291dGdvaW5nJywgJ2luY29taW5nJykKZ3JhcGhEYXRhJGluY29taW5nW2dyZXBsKCJed2RfIiwgZ3JhcGhEYXRhJGluY29taW5nKV0gPC0gCiAgdG91cHBlcihncmFwaERhdGEkaW5jb21pbmdbZ3JlcGwoIl53ZF8iLCBncmFwaERhdGEkaW5jb21pbmcpXSkKZ3JhcGhEYXRhJG91dGdvaW5nW2dyZXBsKCJed2RfIiwgZ3JhcGhEYXRhJG91dGdvaW5nKV0gPC0gCiAgdG91cHBlcihncmFwaERhdGEkb3V0Z29pbmdbZ3JlcGwoIl53ZF8iLCBncmFwaERhdGEkb3V0Z29pbmcpXSkKCiMgLSB2aXN1YWxpemUgdy4ge3Zpc05ldHdvcmt9Cm5vZGVzIDwtIHVuaXF1ZShjKGdyYXBoRGF0YSRvdXRnb2luZywgZ3JhcGhEYXRhJGluY29taW5nKSkKbm9kZXMgPC0gZGF0YS5mcmFtZShpZCA9IDE6bGVuZ3RoKG5vZGVzKSwKICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IG5vZGVzLAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQplZGdlcyA8LSBncmFwaERhdGEKY29sbmFtZXMoZWRnZXMpIDwtIGMoImZyb20iLCAidG8iKQplZGdlcyRmcm9tIDwtIHNhcHBseShlZGdlcyRmcm9tLCBmdW5jdGlvbih4KSB7CiAgbm9kZXMkaWRbd2hpY2gobm9kZXMkbGFiZWwgPT0geCldCn0pCmVkZ2VzJHRvIDwtIHNhcHBseShlZGdlcyR0bywgZnVuY3Rpb24oeCkgewogIG5vZGVzJGlkW3doaWNoKG5vZGVzJGxhYmVsID09IHgpXQp9KQoKIyAtIHZpc3VhbGl6ZQp2aXNOZXR3b3JrKG5vZGVzLCBlZGdlcywgd2lkdGggPSAiMTAwJSIpICU+JQogIHZpc0lncmFwaExheW91dCgpICU+JQogIHZpc05vZGVzKAogICAgc2hhcGUgPSAiZG90IiwKICAgIGNvbG9yID0gbGlzdCgKICAgICAgYmFja2dyb3VuZCA9ICIjMDA4NUFGIiwKICAgICAgYm9yZGVyID0gIiMwMTM4NDgiLAogICAgICBoaWdobGlnaHQgPSAiI0ZGODAwMCIKICAgICksCiAgICBzaGFkb3cgPSBsaXN0KGVuYWJsZWQgPSBUUlVFLCBzaXplID0gMTApCiAgKSAlPiUKICB2aXNFZGdlcygKICAgIHNoYWRvdyA9IEZBTFNFLAogICAgY29sb3IgPSBsaXN0KGNvbG9yID0gIiMwMDg1QUYiLCBoaWdobGlnaHQgPSAiI0M2MkY0QiIpCiAgKSAlPiUKICB2aXNPcHRpb25zKGhpZ2hsaWdodE5lYXJlc3QgPSBUUlVFLCBzZWxlY3RlZEJ5ID0gImxhYmVsIikgJT4lCiAgdmlzTGF5b3V0KHJhbmRvbVNlZWQgPSAxMSkKYGBgCgoKIyMjIyAzLjMgVmlzdWFsaXplIGxvY2FsIGNvbmNlcHQgbmVpZ2hib3VyaG9vZHM6IGBBcHBsZWAKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVH0KIyAtIGxvY2F0ZSAiQXBwbGUiCnRlcm0gPC0gImFwcGwiCiMgLSBzaXplIG9mIHRoZSBuZWlnaGJvdXJob29kCm5fc2l6ZSA8LSA1CndUZXJtIDwtIHdoaWNoKGdyZXBsKHRlcm0sIHJvd25hbWVzKGNvbmNlcHREaXN0TWF0KSkpCiMgLSBmZXRjaCBuZWlnaGJvdXJob29kCm5nMSA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKHdUZXJtKSkKZm9yIChpIGluIDE6bGVuZ3RoKHdUZXJtKSkgewogIG5nMVtbaV1dIDwtIG5hbWVzKAogICAgc29ydChjb25jZXB0RGlzdE1hdFt3VGVybVtpXSwgXSwgZGVjcmVhc2luZyA9IEYpWzE6bl9zaXplKzFdCiAgKQp9Cm5hbWVzKG5nMSkgPC0gcm93bmFtZXMoY29uY2VwdERpc3RNYXQpW3dUZXJtXQpmb3IgKGkgaW4gMTpsZW5ndGgobmcxKSkgewogIG5nMVtbaV1dIDwtIHNldGRpZmYobmcxW1tpXV0sIG5hbWVzKG5nMSlbaV0pCn0KbmcyIDwtIHZlY3Rvcihtb2RlID0gImxpc3QiLCBsZW5ndGggPSBzdW0oc2FwcGx5KG5nMSwgbGVuZ3RoKSkpCmMgPC0gMApmb3IgKGkgaW4gMTpsZW5ndGgobmcxKSkgewogIGZvciAoaiBpbiAxOmxlbmd0aChuZzFbW2ldXSkpIHsKICAgIGMgPC0gYyArIDEKICAgIG5nMltbY11dIDwtIG5hbWVzKAogICAgc29ydCgKICAgICAgY29uY2VwdERpc3RNYXRbd2hpY2gocm93bmFtZXMoY29uY2VwdERpc3RNYXQpID09IG5nMVtbaV1dW2pdKSwgXSwgCiAgICAgIGRlY3JlYXNpbmcgPSBGKVsxOm5fc2l6ZSsxXQogICAgKQogIH0KfQpuYW1lcyhuZzIpIDwtIHVubmFtZSh1bmxpc3QobmcxKSkKZm9yIChpIGluIDE6bGVuZ3RoKG5nMikpIHsKICBuZzJbW2ldXSA8LSBzZXRkaWZmKG5nMltbaV1dLCBuYW1lcyhuZzIpW2ldKQp9CmdyYXBoRGF0YSA8LSByYmluZChzdGFjayhuZzEpLCBzdGFjayhuZzIpKQpncmFwaERhdGEkaW5kIDwtIGFzLmNoYXJhY3RlcihncmFwaERhdGEkaW5kKQpncmFwaERhdGEgPC0gZ3JhcGhEYXRhWywgYygyLCAxKV0KY29sbmFtZXMoZ3JhcGhEYXRhKSA8LSBjKCdvdXRnb2luZycsICdpbmNvbWluZycpCmdyYXBoRGF0YSRpbmNvbWluZ1tncmVwbCgiXndkXyIsIGdyYXBoRGF0YSRpbmNvbWluZyldIDwtIAogIHRvdXBwZXIoZ3JhcGhEYXRhJGluY29taW5nW2dyZXBsKCJed2RfIiwgZ3JhcGhEYXRhJGluY29taW5nKV0pCmdyYXBoRGF0YSRvdXRnb2luZ1tncmVwbCgiXndkXyIsIGdyYXBoRGF0YSRvdXRnb2luZyldIDwtIAogIHRvdXBwZXIoZ3JhcGhEYXRhJG91dGdvaW5nW2dyZXBsKCJed2RfIiwgZ3JhcGhEYXRhJG91dGdvaW5nKV0pCgojIC0gdmlzdWFsaXplIHcuIHt2aXNOZXR3b3JrfQpub2RlcyA8LSB1bmlxdWUoYyhncmFwaERhdGEkb3V0Z29pbmcsIGdyYXBoRGF0YSRpbmNvbWluZykpCm5vZGVzIDwtIGRhdGEuZnJhbWUoaWQgPSAxOmxlbmd0aChub2RlcyksCiAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBub2RlcywKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKZWRnZXMgPC0gZ3JhcGhEYXRhCmNvbG5hbWVzKGVkZ2VzKSA8LSBjKCJmcm9tIiwgInRvIikKZWRnZXMkZnJvbSA8LSBzYXBwbHkoZWRnZXMkZnJvbSwgZnVuY3Rpb24oeCkgewogIG5vZGVzJGlkW3doaWNoKG5vZGVzJGxhYmVsID09IHgpXQp9KQplZGdlcyR0byA8LSBzYXBwbHkoZWRnZXMkdG8sIGZ1bmN0aW9uKHgpIHsKICBub2RlcyRpZFt3aGljaChub2RlcyRsYWJlbCA9PSB4KV0KfSkKCiMgLSB2aXN1YWxpemUKdmlzTmV0d29yayhub2RlcywgZWRnZXMsIHdpZHRoID0gIjEwMCUiKSAlPiUKICB2aXNJZ3JhcGhMYXlvdXQoKSAlPiUKICB2aXNOb2RlcygKICAgIHNoYXBlID0gImRvdCIsCiAgICBjb2xvciA9IGxpc3QoCiAgICAgIGJhY2tncm91bmQgPSAiIzAwODVBRiIsCiAgICAgIGJvcmRlciA9ICIjMDEzODQ4IiwKICAgICAgaGlnaGxpZ2h0ID0gIiNGRjgwMDAiCiAgICApLAogICAgc2hhZG93ID0gbGlzdChlbmFibGVkID0gVFJVRSwgc2l6ZSA9IDEwKQogICkgJT4lCiAgdmlzRWRnZXMoCiAgICBzaGFkb3cgPSBGQUxTRSwKICAgIGNvbG9yID0gbGlzdChjb2xvciA9ICIjMDA4NUFGIiwgaGlnaGxpZ2h0ID0gIiNDNjJGNEIiKQogICkgJT4lCiAgdmlzT3B0aW9ucyhoaWdobGlnaHROZWFyZXN0ID0gVFJVRSwgc2VsZWN0ZWRCeSA9ICJsYWJlbCIpICU+JQogIHZpc0xheW91dChyYW5kb21TZWVkID0gMTEpCmBgYAoKIyMjIyAzLjQgVmlzdWFsaXplIGxvY2FsIGNvbmNlcHQgbmVpZ2hib3VyaG9vZHM6IGBBbWF6b25gCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IFR9CiMgLSBsb2NhdGUgIkFtYXpvbiIKdGVybSA8LSAiYW1hem9uIgojIC0gc2l6ZSBvZiB0aGUgbmVpZ2hib3VyaG9vZApuX3NpemUgPC0gNQp3VGVybSA8LSB3aGljaChncmVwbCh0ZXJtLCByb3duYW1lcyhjb25jZXB0RGlzdE1hdCkpKQojIC0gZmV0Y2ggbmVpZ2hib3VyaG9vZApuZzEgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IGxlbmd0aCh3VGVybSkpCmZvciAoaSBpbiAxOmxlbmd0aCh3VGVybSkpIHsKICBuZzFbW2ldXSA8LSBuYW1lcygKICAgIHNvcnQoY29uY2VwdERpc3RNYXRbd1Rlcm1baV0sIF0sIGRlY3JlYXNpbmcgPSBGKVsxOm5fc2l6ZSsxXQogICkKfQpuYW1lcyhuZzEpIDwtIHJvd25hbWVzKGNvbmNlcHREaXN0TWF0KVt3VGVybV0KZm9yIChpIGluIDE6bGVuZ3RoKG5nMSkpIHsKICBuZzFbW2ldXSA8LSBzZXRkaWZmKG5nMVtbaV1dLCBuYW1lcyhuZzEpW2ldKQp9Cm5nMiA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gc3VtKHNhcHBseShuZzEsIGxlbmd0aCkpKQpjIDwtIDAKZm9yIChpIGluIDE6bGVuZ3RoKG5nMSkpIHsKICBmb3IgKGogaW4gMTpsZW5ndGgobmcxW1tpXV0pKSB7CiAgICBjIDwtIGMgKyAxCiAgICBuZzJbW2NdXSA8LSBuYW1lcygKICAgIHNvcnQoCiAgICAgIGNvbmNlcHREaXN0TWF0W3doaWNoKHJvd25hbWVzKGNvbmNlcHREaXN0TWF0KSA9PSBuZzFbW2ldXVtqXSksIF0sIAogICAgICBkZWNyZWFzaW5nID0gRilbMTpuX3NpemUrMV0KICAgICkKICB9Cn0KbmFtZXMobmcyKSA8LSB1bm5hbWUodW5saXN0KG5nMSkpCmZvciAoaSBpbiAxOmxlbmd0aChuZzIpKSB7CiAgbmcyW1tpXV0gPC0gc2V0ZGlmZihuZzJbW2ldXSwgbmFtZXMobmcyKVtpXSkKfQpncmFwaERhdGEgPC0gcmJpbmQoc3RhY2sobmcxKSwgc3RhY2sobmcyKSkKZ3JhcGhEYXRhJGluZCA8LSBhcy5jaGFyYWN0ZXIoZ3JhcGhEYXRhJGluZCkKZ3JhcGhEYXRhIDwtIGdyYXBoRGF0YVssIGMoMiwgMSldCmNvbG5hbWVzKGdyYXBoRGF0YSkgPC0gYygnb3V0Z29pbmcnLCAnaW5jb21pbmcnKQpncmFwaERhdGEkaW5jb21pbmdbZ3JlcGwoIl53ZF8iLCBncmFwaERhdGEkaW5jb21pbmcpXSA8LSAKICB0b3VwcGVyKGdyYXBoRGF0YSRpbmNvbWluZ1tncmVwbCgiXndkXyIsIGdyYXBoRGF0YSRpbmNvbWluZyldKQpncmFwaERhdGEkb3V0Z29pbmdbZ3JlcGwoIl53ZF8iLCBncmFwaERhdGEkb3V0Z29pbmcpXSA8LSAKICB0b3VwcGVyKGdyYXBoRGF0YSRvdXRnb2luZ1tncmVwbCgiXndkXyIsIGdyYXBoRGF0YSRvdXRnb2luZyldKQoKIyAtIHZpc3VhbGl6ZSB3LiB7dmlzTmV0d29ya30Kbm9kZXMgPC0gdW5pcXVlKGMoZ3JhcGhEYXRhJG91dGdvaW5nLCBncmFwaERhdGEkaW5jb21pbmcpKQpub2RlcyA8LSBkYXRhLmZyYW1lKGlkID0gMTpsZW5ndGgobm9kZXMpLAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gbm9kZXMsCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmVkZ2VzIDwtIGdyYXBoRGF0YQpjb2xuYW1lcyhlZGdlcykgPC0gYygiZnJvbSIsICJ0byIpCmVkZ2VzJGZyb20gPC0gc2FwcGx5KGVkZ2VzJGZyb20sIGZ1bmN0aW9uKHgpIHsKICBub2RlcyRpZFt3aGljaChub2RlcyRsYWJlbCA9PSB4KV0KfSkKZWRnZXMkdG8gPC0gc2FwcGx5KGVkZ2VzJHRvLCBmdW5jdGlvbih4KSB7CiAgbm9kZXMkaWRbd2hpY2gobm9kZXMkbGFiZWwgPT0geCldCn0pCgojIC0gdmlzdWFsaXplCnZpc05ldHdvcmsobm9kZXMsIGVkZ2VzLCB3aWR0aCA9ICIxMDAlIikgJT4lCiAgdmlzSWdyYXBoTGF5b3V0KCkgJT4lCiAgdmlzTm9kZXMoCiAgICBzaGFwZSA9ICJkb3QiLAogICAgY29sb3IgPSBsaXN0KAogICAgICBiYWNrZ3JvdW5kID0gIiMwMDg1QUYiLAogICAgICBib3JkZXIgPSAiIzAxMzg0OCIsCiAgICAgIGhpZ2hsaWdodCA9ICIjRkY4MDAwIgogICAgKSwKICAgIHNoYWRvdyA9IGxpc3QoZW5hYmxlZCA9IFRSVUUsIHNpemUgPSAxMCkKICApICU+JQogIHZpc0VkZ2VzKAogICAgc2hhZG93ID0gRkFMU0UsCiAgICBjb2xvciA9IGxpc3QoY29sb3IgPSAiIzAwODVBRiIsIGhpZ2hsaWdodCA9ICIjQzYyRjRCIikKICApICU+JQogIHZpc09wdGlvbnMoaGlnaGxpZ2h0TmVhcmVzdCA9IFRSVUUsIHNlbGVjdGVkQnkgPSAibGFiZWwiKSAlPiUKICB2aXNMYXlvdXQocmFuZG9tU2VlZCA9IDExKQpgYGAKCiMjIyMgMy41IFZpc3VhbGl6ZSBsb2NhbCBjb25jZXB0IG5laWdoYm91cmhvb2RzOiBgTWljcm9zb2Z0YAoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBUfQojIC0gbG9jYXRlICJNaWNyb3NvZnQiCnRlcm0gPC0gIm1pY3Jvc29mdCIKIyAtIHNpemUgb2YgdGhlIG5laWdoYm91cmhvb2QKbl9zaXplIDwtIDEwCndUZXJtIDwtIHdoaWNoKGdyZXBsKHRlcm0sIHJvd25hbWVzKGNvbmNlcHREaXN0TWF0KSkpCiMgLSBmZXRjaCBuZWlnaGJvdXJob29kCm5nMSA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKHdUZXJtKSkKZm9yIChpIGluIDE6bGVuZ3RoKHdUZXJtKSkgewogIG5nMVtbaV1dIDwtIG5hbWVzKAogICAgc29ydChjb25jZXB0RGlzdE1hdFt3VGVybVtpXSwgXSwgZGVjcmVhc2luZyA9IEYpWzE6bl9zaXplKzFdCiAgKQp9Cm5hbWVzKG5nMSkgPC0gcm93bmFtZXMoY29uY2VwdERpc3RNYXQpW3dUZXJtXQpmb3IgKGkgaW4gMTpsZW5ndGgobmcxKSkgewogIG5nMVtbaV1dIDwtIHNldGRpZmYobmcxW1tpXV0sIG5hbWVzKG5nMSlbaV0pCn0KbmcyIDwtIHZlY3Rvcihtb2RlID0gImxpc3QiLCBsZW5ndGggPSBzdW0oc2FwcGx5KG5nMSwgbGVuZ3RoKSkpCmMgPC0gMApmb3IgKGkgaW4gMTpsZW5ndGgobmcxKSkgewogIGZvciAoaiBpbiAxOmxlbmd0aChuZzFbW2ldXSkpIHsKICAgIGMgPC0gYyArIDEKICAgIG5nMltbY11dIDwtIG5hbWVzKAogICAgc29ydCgKICAgICAgY29uY2VwdERpc3RNYXRbd2hpY2gocm93bmFtZXMoY29uY2VwdERpc3RNYXQpID09IG5nMVtbaV1dW2pdKSwgXSwgCiAgICAgIGRlY3JlYXNpbmcgPSBGKVsxOm5fc2l6ZSsxXQogICAgKQogIH0KfQpuYW1lcyhuZzIpIDwtIHVubmFtZSh1bmxpc3QobmcxKSkKZm9yIChpIGluIDE6bGVuZ3RoKG5nMikpIHsKICBuZzJbW2ldXSA8LSBzZXRkaWZmKG5nMltbaV1dLCBuYW1lcyhuZzIpW2ldKQp9CmdyYXBoRGF0YSA8LSByYmluZChzdGFjayhuZzEpLCBzdGFjayhuZzIpKQpncmFwaERhdGEkaW5kIDwtIGFzLmNoYXJhY3RlcihncmFwaERhdGEkaW5kKQpncmFwaERhdGEgPC0gZ3JhcGhEYXRhWywgYygyLCAxKV0KY29sbmFtZXMoZ3JhcGhEYXRhKSA8LSBjKCdvdXRnb2luZycsICdpbmNvbWluZycpCmdyYXBoRGF0YSRpbmNvbWluZ1tncmVwbCgiXndkXyIsIGdyYXBoRGF0YSRpbmNvbWluZyldIDwtIAogIHRvdXBwZXIoZ3JhcGhEYXRhJGluY29taW5nW2dyZXBsKCJed2RfIiwgZ3JhcGhEYXRhJGluY29taW5nKV0pCmdyYXBoRGF0YSRvdXRnb2luZ1tncmVwbCgiXndkXyIsIGdyYXBoRGF0YSRvdXRnb2luZyldIDwtIAogIHRvdXBwZXIoZ3JhcGhEYXRhJG91dGdvaW5nW2dyZXBsKCJed2RfIiwgZ3JhcGhEYXRhJG91dGdvaW5nKV0pCgojIC0gdmlzdWFsaXplIHcuIHt2aXNOZXR3b3JrfQpub2RlcyA8LSB1bmlxdWUoYyhncmFwaERhdGEkb3V0Z29pbmcsIGdyYXBoRGF0YSRpbmNvbWluZykpCm5vZGVzIDwtIGRhdGEuZnJhbWUoaWQgPSAxOmxlbmd0aChub2RlcyksCiAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBub2RlcywKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKZWRnZXMgPC0gZ3JhcGhEYXRhCmNvbG5hbWVzKGVkZ2VzKSA8LSBjKCJmcm9tIiwgInRvIikKZWRnZXMkZnJvbSA8LSBzYXBwbHkoZWRnZXMkZnJvbSwgZnVuY3Rpb24oeCkgewogIG5vZGVzJGlkW3doaWNoKG5vZGVzJGxhYmVsID09IHgpXQp9KQplZGdlcyR0byA8LSBzYXBwbHkoZWRnZXMkdG8sIGZ1bmN0aW9uKHgpIHsKICBub2RlcyRpZFt3aGljaChub2RlcyRsYWJlbCA9PSB4KV0KfSkKCiMgLSB2aXN1YWxpemUKdmlzTmV0d29yayhub2RlcywgZWRnZXMsIHdpZHRoID0gIjEwMCUiKSAlPiUKICB2aXNJZ3JhcGhMYXlvdXQoKSAlPiUKICB2aXNOb2RlcygKICAgIHNoYXBlID0gImRvdCIsCiAgICBjb2xvciA9IGxpc3QoCiAgICAgIGJhY2tncm91bmQgPSAiIzAwODVBRiIsCiAgICAgIGJvcmRlciA9ICIjMDEzODQ4IiwKICAgICAgaGlnaGxpZ2h0ID0gIiNGRjgwMDAiCiAgICApLAogICAgc2hhZG93ID0gbGlzdChlbmFibGVkID0gVFJVRSwgc2l6ZSA9IDEwKQogICkgJT4lCiAgdmlzRWRnZXMoCiAgICBzaGFkb3cgPSBGQUxTRSwKICAgIGNvbG9yID0gbGlzdChjb2xvciA9ICIjMDA4NUFGIiwgaGlnaGxpZ2h0ID0gIiNDNjJGNEIiKQogICkgJT4lCiAgdmlzT3B0aW9ucyhoaWdobGlnaHROZWFyZXN0ID0gVFJVRSwgc2VsZWN0ZWRCeSA9ICJsYWJlbCIpICU+JQogIHZpc0xheW91dChyYW5kb21TZWVkID0gMTEpCmBgYAoKIyMjIyAzLjYgVmlzdWFsaXplIGxvY2FsIGNvbmNlcHQgbmVpZ2hib3VyaG9vZHM6IGBGYWNlYm9va2AKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVH0KIyAtIGxvY2F0ZSAiRmFjZWJvb2siCnRlcm0gPC0gImZhY2Vib29rIgojIC0gc2l6ZSBvZiB0aGUgbmVpZ2hib3VyaG9vZApuX3NpemUgPC0gMwp3VGVybSA8LSB3aGljaChncmVwbCh0ZXJtLCByb3duYW1lcyhjb25jZXB0RGlzdE1hdCkpKQojIC0gZmV0Y2ggbmVpZ2hib3VyaG9vZApuZzEgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IGxlbmd0aCh3VGVybSkpCmZvciAoaSBpbiAxOmxlbmd0aCh3VGVybSkpIHsKICBuZzFbW2ldXSA8LSBuYW1lcygKICAgIHNvcnQoY29uY2VwdERpc3RNYXRbd1Rlcm1baV0sIF0sIGRlY3JlYXNpbmcgPSBGKVsxOm5fc2l6ZSsxXQogICkKfQpuYW1lcyhuZzEpIDwtIHJvd25hbWVzKGNvbmNlcHREaXN0TWF0KVt3VGVybV0KZm9yIChpIGluIDE6bGVuZ3RoKG5nMSkpIHsKICBuZzFbW2ldXSA8LSBzZXRkaWZmKG5nMVtbaV1dLCBuYW1lcyhuZzEpW2ldKQp9Cm5nMiA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gc3VtKHNhcHBseShuZzEsIGxlbmd0aCkpKQpjIDwtIDAKZm9yIChpIGluIDE6bGVuZ3RoKG5nMSkpIHsKICBmb3IgKGogaW4gMTpsZW5ndGgobmcxW1tpXV0pKSB7CiAgICBjIDwtIGMgKyAxCiAgICBuZzJbW2NdXSA8LSBuYW1lcygKICAgIHNvcnQoCiAgICAgIGNvbmNlcHREaXN0TWF0W3doaWNoKHJvd25hbWVzKGNvbmNlcHREaXN0TWF0KSA9PSBuZzFbW2ldXVtqXSksIF0sIAogICAgICBkZWNyZWFzaW5nID0gRilbMTpuX3NpemUrMV0KICAgICkKICB9Cn0KbmFtZXMobmcyKSA8LSB1bm5hbWUodW5saXN0KG5nMSkpCmZvciAoaSBpbiAxOmxlbmd0aChuZzIpKSB7CiAgbmcyW1tpXV0gPC0gc2V0ZGlmZihuZzJbW2ldXSwgbmFtZXMobmcyKVtpXSkKfQpncmFwaERhdGEgPC0gcmJpbmQoc3RhY2sobmcxKSwgc3RhY2sobmcyKSkKZ3JhcGhEYXRhJGluZCA8LSBhcy5jaGFyYWN0ZXIoZ3JhcGhEYXRhJGluZCkKZ3JhcGhEYXRhIDwtIGdyYXBoRGF0YVssIGMoMiwgMSldCmNvbG5hbWVzKGdyYXBoRGF0YSkgPC0gYygnb3V0Z29pbmcnLCAnaW5jb21pbmcnKQpncmFwaERhdGEkaW5jb21pbmdbZ3JlcGwoIl53ZF8iLCBncmFwaERhdGEkaW5jb21pbmcpXSA8LSAKICB0b3VwcGVyKGdyYXBoRGF0YSRpbmNvbWluZ1tncmVwbCgiXndkXyIsIGdyYXBoRGF0YSRpbmNvbWluZyldKQpncmFwaERhdGEkb3V0Z29pbmdbZ3JlcGwoIl53ZF8iLCBncmFwaERhdGEkb3V0Z29pbmcpXSA8LSAKICB0b3VwcGVyKGdyYXBoRGF0YSRvdXRnb2luZ1tncmVwbCgiXndkXyIsIGdyYXBoRGF0YSRvdXRnb2luZyldKQoKIyAtIHZpc3VhbGl6ZSB3LiB7dmlzTmV0d29ya30Kbm9kZXMgPC0gdW5pcXVlKGMoZ3JhcGhEYXRhJG91dGdvaW5nLCBncmFwaERhdGEkaW5jb21pbmcpKQpub2RlcyA8LSBkYXRhLmZyYW1lKGlkID0gMTpsZW5ndGgobm9kZXMpLAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gbm9kZXMsCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmVkZ2VzIDwtIGdyYXBoRGF0YQpjb2xuYW1lcyhlZGdlcykgPC0gYygiZnJvbSIsICJ0byIpCmVkZ2VzJGZyb20gPC0gc2FwcGx5KGVkZ2VzJGZyb20sIGZ1bmN0aW9uKHgpIHsKICBub2RlcyRpZFt3aGljaChub2RlcyRsYWJlbCA9PSB4KV0KfSkKZWRnZXMkdG8gPC0gc2FwcGx5KGVkZ2VzJHRvLCBmdW5jdGlvbih4KSB7CiAgbm9kZXMkaWRbd2hpY2gobm9kZXMkbGFiZWwgPT0geCldCn0pCgojIC0gdmlzdWFsaXplCnZpc05ldHdvcmsobm9kZXMsIGVkZ2VzLCB3aWR0aCA9ICIxMDAlIikgJT4lCiAgdmlzSWdyYXBoTGF5b3V0KCkgJT4lCiAgdmlzTm9kZXMoCiAgICBzaGFwZSA9ICJkb3QiLAogICAgY29sb3IgPSBsaXN0KAogICAgICBiYWNrZ3JvdW5kID0gIiMwMDg1QUYiLAogICAgICBib3JkZXIgPSAiIzAxMzg0OCIsCiAgICAgIGhpZ2hsaWdodCA9ICIjRkY4MDAwIgogICAgKSwKICAgIHNoYWRvdyA9IGxpc3QoZW5hYmxlZCA9IFRSVUUsIHNpemUgPSAxMCkKICApICU+JQogIHZpc0VkZ2VzKAogICAgc2hhZG93ID0gRkFMU0UsCiAgICBjb2xvciA9IGxpc3QoY29sb3IgPSAiIzAwODVBRiIsIGhpZ2hsaWdodCA9ICIjQzYyRjRCIikKICApICU+JQogIHZpc09wdGlvbnMoaGlnaGxpZ2h0TmVhcmVzdCA9IFRSVUUsIHNlbGVjdGVkQnkgPSAibGFiZWwiKSAlPiUKICB2aXNMYXlvdXQocmFuZG9tU2VlZCA9IDExKQpgYGAKCioqKgpHb3JhbiBTLiBNaWxvdmFub3ZpxIcgJiBNaWtlIFBhZ2UKCkRhdGFLb2xla3RpdiwgMjAxOS4KCmNvbnRhY3Q6IGRhdGFrb2xla3RpdkBkYXRha29sZWt0aXYuY29tCgohW10oaW1nL0RLX0xvZ29fMTAwLnBuZykKCioqKgpMaWNlbnNlOiBbR1BMdjNdKGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy9ncGwtMy4wLnR4dCkKVGhpcyBOb3RlYm9vayBpcyBmcmVlIHNvZnR3YXJlOiB5b3UgY2FuIHJlZGlzdHJpYnV0ZSBpdCBhbmQvb3IgbW9kaWZ5IGl0IHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgYXMgcHVibGlzaGVkIGJ5IHRoZSBGcmVlIFNvZnR3YXJlIEZvdW5kYXRpb24sIGVpdGhlciB2ZXJzaW9uIDMgb2YgdGhlIExpY2Vuc2UsIG9yIChhdCB5b3VyIG9wdGlvbikgYW55IGxhdGVyIHZlcnNpb24uClRoaXMgTm90ZWJvb2sgaXMgZGlzdHJpYnV0ZWQgaW4gdGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCwgYnV0IFdJVEhPVVQgQU5ZIFdBUlJBTlRZOyB3aXRob3V0IGV2ZW4gdGhlIGltcGxpZWQgd2FycmFudHkgb2YgTUVSQ0hBTlRBQklMSVRZIG9yIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFLiAgU2VlIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLgpZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhbG9uZyB3aXRoIHRoaXMgTm90ZWJvb2suIElmIG5vdCwgc2VlIDxodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvPi4KCioqKgoK