library(taoteprog)
library(tidyverse)
── Attaching packages ───────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 2.2.1          ✔ purrr   0.2.4     
✔ tibble  1.4.2          ✔ dplyr   0.7.4     
✔ tidyr   0.8.0.9000     ✔ stringr 1.3.0     
✔ readr   1.1.1          ✔ forcats 0.3.0     
── Conflicts ──────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(tidygraph)

Присоединяю пакет: ‘tidygraph’

Следующий объект скрыт от ‘package:stats’:

    filter
library(ggraph)
knitr::opts_chunk$set(
  echo = TRUE,
  eval = TRUE,
  collapse = TRUE,
  fig.width = 9,
  fig.height = 8
)
set.seed(201803)

Data creation and exploration

Creation

ttp_raw <- tbl_graph(nodes = ttp_chapters, edges = ttp_edges, directed = TRUE)
ttp_raw
# A tbl_graph: 81 nodes and 124 edges
#
# A directed multigraph with 20 components
#
# Node Data: 81 x 2 (active)
  chapter name                   
    <int> <chr>                  
1       1 Program                
2       2 Program Well           
3       3 Think Chess            
4       4 Carve Reality          
5       5 Solve the Problem      
6       6 Don't Solve the Problem
# ... with 75 more rows
#
# Edge Data: 124 x 3
   from    to type    
  <int> <int> <chr>   
1     4     9 ally    
2     4    54 ally    
3     5     6 opponent
# ... with 121 more rows
ttp <- ttp_raw %>%
  # Remove loops
  activate(edges) %>%
  filter(!edge_is_loop()) %>%
  # Create edge sign
  mutate(sign = if_else(type == "ally", 1, -1)) %>%
  # Remove isolated nodes
  activate(nodes) %>%
  filter(!node_is_isolated()) %>%
  # Compute weak components
  mutate(component = group_components())
ttp
# A tbl_graph: 67 nodes and 122 edges
#
# A directed simple graph with 6 components
#
# Node Data: 67 x 3 (active)
  chapter name                    component
    <int> <chr>                       <int>
1       4 Carve Reality                   2
2       5 Solve the Problem               3
3       6 Don't Solve the Problem         3
4       7 Enjoy Confusion                 1
5       8 Procrastinate                   2
6       9 Verbalize and Nounalize         2
# ... with 61 more rows
#
# Edge Data: 122 x 4
   from    to type      sign
  <int> <int> <chr>    <dbl>
1     1     6 ally      1.00
2     1    44 ally      1.00
3     2     3 opponent -1.00
# ... with 119 more rows

Visualization

ttp %>%
  ggraph(layout = "nicely") +
    geom_edge_link(
      aes(colour = type),
      arrow = arrow(length = unit(1.5, "mm")),
      start_cap = circle(3, "mm"),
      end_cap = circle(3, "mm")
    ) +
    geom_node_text(aes(label = chapter), size = 5) +
    scale_edge_colour_manual(values = c(ally = "#22B022",
                                        opponent = "#A4AAF6")) +
    theme_graph() +
    labs(
      title = '"Tao Te Programming" alliance graph',
      subtitle = "Nodes represent chapter numbers, edges - connections",
      caption = "@echasnovski"
    )

Signed graph partition

Functions

Algorithm is based on this paper.

as_edge_vec <- function(adj_mat) {
  c(as.matrix(adj_mat))
}
imb <- function(edge_vec, part_vec, alpha = 0.5) {
  row_clus <- rep(part_vec, times = length(part_vec))
  col_clus <- rep(part_vec, each = length(part_vec))
  is_same_clus <- row_clus == col_clus
  edges_within <- edge_vec[is_same_clus]
  edges_between <- edge_vec[!is_same_clus]
  imb_within <- -sum(edges_within[edges_within < 0])
  imb_between <- sum(edges_between[edges_between > 0])
  alpha * imb_within + (1 - alpha) * imb_between
}
rclus <- function(n, k) {
  sample(seq_len(k), n, replace = TRUE)
}
step_grid <- function(part_vec, k) {
  labels <- seq_len(k)
  res <- lapply(seq_along(part_vec), function(ind) {
    target_clusters <- setdiff(labels, part_vec[ind])
    lapply(target_clusters, function(new_clus) {
      part_vec[ind] <- new_clus
      part_vec
    })
  })
  Reduce(c, res)
}
partition_try <- function(edge_vec, init_part_vec, k = 2,
                          max_iter = 1000, imb_tol = 0, alpha = 0.5) {
  best_imb <- imb(edge_vec, init_part_vec, alpha)
  cur_imb <- best_imb
  best_part_vec <- init_part_vec
  cur_part_vec <- best_part_vec
  for (i in seq_len(max_iter)) {
    part_vec_grid <- step_grid(cur_part_vec, k)
    for (j in seq_along(part_vec_grid)) {
      grid_imb <- imb(edge_vec, part_vec_grid[[j]], alpha)
      if (grid_imb < best_imb) {
        best_imb <- grid_imb
        best_part_vec <- part_vec_grid[[j]]
      }
      if (best_imb <= imb_tol) {
        break
      }
    }
    if ((best_imb <= imb_tol) || (cur_imb <= best_imb)) {
      break
    } else {
      cur_imb <- best_imb
      cur_part_vec <- best_part_vec
    }
  }
  list(imb = best_imb, part_vec = best_part_vec)
}
partition <- function(adj_mat, k = 2, max_iter = 1000, n_try = 100,
                      imb_tol = 0, alpha = 0.5) {
  n_nodes <- nrow(adj_mat)
  edge_vec <- as_edge_vec(adj_mat)
  best_imb <- Inf
  best_part_vec <- rep(NA_integer_, n_nodes)
  for (try_ind in seq_len(n_try)) {
    init_part_vec <- rclus(n_nodes, k)
    try_res <- partition_try(edge_vec, init_part_vec, k,
                             max_iter, imb_tol, alpha)
    if (try_res$imb < best_imb) {
      best_imb <- try_res$imb
      best_part_vec <- try_res$part_vec
    }
    if (best_imb <= imb_tol) {
      break
    }
  }
  names(best_part_vec) <- rownames(adj_mat)
  list(imb = best_imb, part_vec = best_part_vec)
}

Partition

WARNING: Takes pretty long time to run. Change max_iter and/or n_try to smaller values for faster (but less precise) computation

ttp_data <- ttp %>%
  morph(to_components) %>%
  crystallise() %>%
  mutate(
    # Compute partition data
    adj_mat = map(graph, igraph::as_adj, attr = "sign"),
    partition_data = map(adj_mat, partition, max_iter = 100, n_try = 5000),
    best_imb = map_dbl(partition_data, "imb"),
    partition = map(partition_data, "part_vec"),
    # Merge best partition to graphs
    graph = map2(
      graph, partition,
      ~ activate(.x, nodes) %>%
        left_join(y = enframe(.y, value = "partition") %>%
                    mutate(partition = as.character(partition)),
                  by = "name")
    )
  )
saveRDS(ttp_data, "ttp_data.rds")
ttp_updated <- reduce(ttp_data$graph, bind_graphs)
ttp_updated %>%
  ggraph(layout = "nicely") +
    geom_edge_link(
      aes(colour = type),
      arrow = arrow(length = unit(1.5, "mm")),
      start_cap = circle(3, "mm"),
      end_cap = circle(3, "mm")
    ) +
    geom_node_text(aes(label = chapter, colour = partition), size = 5) +
    scale_edge_colour_manual(values = c(ally = "#22B022",
                                        opponent = "#A4AAF6")) +
    scale_colour_manual(values = c("blue", "red")) +
    theme_graph() +
    labs(
      title = '"Tao Te Programming" alliance graph',
      subtitle = "Nodes represent chapter numbers, edges - connections",
      caption = "@echasnovski"
    )

Central advice

edges_peace <- ttp_edges %>%
  transmute(
    from = if_else(type == "ally", chapter1, chapter2),
    to = if_else(type == "ally", chapter2, chapter1)
  )
tbl_graph(
  nodes = ttp_chapters, edges = edges_peace, directed = TRUE
  ) %>%
  activate(nodes) %>%
  mutate(centr = centrality_pagerank()) %>%
  as_tibble() %>%
  arrange(desc(centr))
LS0tCnRpdGxlOiAnUmF3IEFuYWx5c2lzIG9mICJUYW8gVGUgUHJvZ3JhbW1pbmciJwpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBzZXR1cH0KbGlicmFyeSh0YW90ZXByb2cpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHlncmFwaCkKbGlicmFyeShnZ3JhcGgpCgprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFRSVUUsCiAgZXZhbCA9IFRSVUUsCiAgY29sbGFwc2UgPSBUUlVFLAogIGZpZy53aWR0aCA9IDksCiAgZmlnLmhlaWdodCA9IDgKKQoKc2V0LnNlZWQoMjAxODAzKQpgYGAKCiMgRGF0YSBjcmVhdGlvbiBhbmQgZXhwbG9yYXRpb24KCiMjIENyZWF0aW9uCgpgYGB7ciBjcmVhdGlvbn0KdHRwX3JhdyA8LSB0YmxfZ3JhcGgobm9kZXMgPSB0dHBfY2hhcHRlcnMsIGVkZ2VzID0gdHRwX2VkZ2VzLCBkaXJlY3RlZCA9IFRSVUUpCnR0cF9yYXcKCnR0cCA8LSB0dHBfcmF3ICU+JQogICMgUmVtb3ZlIGxvb3BzCiAgYWN0aXZhdGUoZWRnZXMpICU+JQogIGZpbHRlcighZWRnZV9pc19sb29wKCkpICU+JQogICMgQ3JlYXRlIGVkZ2Ugc2lnbgogIG11dGF0ZShzaWduID0gaWZfZWxzZSh0eXBlID09ICJhbGx5IiwgMSwgLTEpKSAlPiUKICAjIFJlbW92ZSBpc29sYXRlZCBub2RlcwogIGFjdGl2YXRlKG5vZGVzKSAlPiUKICBmaWx0ZXIoIW5vZGVfaXNfaXNvbGF0ZWQoKSkgJT4lCiAgIyBDb21wdXRlIHdlYWsgY29tcG9uZW50cwogIG11dGF0ZShjb21wb25lbnQgPSBncm91cF9jb21wb25lbnRzKCkpCnR0cApgYGAKCiMjIFZpc3VhbGl6YXRpb24KYGBge3IgdmlzdWFsaXphdGlvbn0KdHRwICU+JQogIGdncmFwaChsYXlvdXQgPSAibmljZWx5IikgKwogICAgZ2VvbV9lZGdlX2xpbmsoCiAgICAgIGFlcyhjb2xvdXIgPSB0eXBlKSwKICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDEuNSwgIm1tIikpLAogICAgICBzdGFydF9jYXAgPSBjaXJjbGUoMywgIm1tIiksCiAgICAgIGVuZF9jYXAgPSBjaXJjbGUoMywgIm1tIikKICAgICkgKwogICAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gY2hhcHRlciksIHNpemUgPSA1KSArCiAgICBzY2FsZV9lZGdlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYyhhbGx5ID0gIiMyMkIwMjIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3Bwb25lbnQgPSAiI0E0QUFGNiIpKSArCiAgICB0aGVtZV9ncmFwaCgpICsKICAgIGxhYnMoCiAgICAgIHRpdGxlID0gJyJUYW8gVGUgUHJvZ3JhbW1pbmciIGFsbGlhbmNlIGdyYXBoJywKICAgICAgc3VidGl0bGUgPSAiTm9kZXMgcmVwcmVzZW50IGNoYXB0ZXIgbnVtYmVycywgZWRnZXMgLSBjb25uZWN0aW9ucyIsCiAgICAgIGNhcHRpb24gPSAiQGVjaGFzbm92c2tpIgogICAgKQpgYGAKCiMgU2lnbmVkIGdyYXBoIHBhcnRpdGlvbgoKIyMgRnVuY3Rpb25zCgpBbGdvcml0aG0gaXMgYmFzZWQgb24gdGhpcyBbcGFwZXJdKGh0dHA6Ly9tcnZhci5mZHYudW5pLWxqLnNpL3BhamVrL1NpZ25lZE5ldHdvcmtzL0JsZWQ5NC5wZGYpLgoKYGBge3IgcGFydGl0aW9uLWZ1bmN0aW9uc30KYXNfZWRnZV92ZWMgPC0gZnVuY3Rpb24oYWRqX21hdCkgewogIGMoYXMubWF0cml4KGFkal9tYXQpKQp9CgppbWIgPC0gZnVuY3Rpb24oZWRnZV92ZWMsIHBhcnRfdmVjLCBhbHBoYSA9IDAuNSkgewogIHJvd19jbHVzIDwtIHJlcChwYXJ0X3ZlYywgdGltZXMgPSBsZW5ndGgocGFydF92ZWMpKQogIGNvbF9jbHVzIDwtIHJlcChwYXJ0X3ZlYywgZWFjaCA9IGxlbmd0aChwYXJ0X3ZlYykpCgogIGlzX3NhbWVfY2x1cyA8LSByb3dfY2x1cyA9PSBjb2xfY2x1cwogIGVkZ2VzX3dpdGhpbiA8LSBlZGdlX3ZlY1tpc19zYW1lX2NsdXNdCiAgZWRnZXNfYmV0d2VlbiA8LSBlZGdlX3ZlY1shaXNfc2FtZV9jbHVzXQoKICBpbWJfd2l0aGluIDwtIC1zdW0oZWRnZXNfd2l0aGluW2VkZ2VzX3dpdGhpbiA8IDBdKQogIGltYl9iZXR3ZWVuIDwtIHN1bShlZGdlc19iZXR3ZWVuW2VkZ2VzX2JldHdlZW4gPiAwXSkKCiAgYWxwaGEgKiBpbWJfd2l0aGluICsgKDEgLSBhbHBoYSkgKiBpbWJfYmV0d2Vlbgp9CgpyY2x1cyA8LSBmdW5jdGlvbihuLCBrKSB7CiAgc2FtcGxlKHNlcV9sZW4oayksIG4sIHJlcGxhY2UgPSBUUlVFKQp9CgpzdGVwX2dyaWQgPC0gZnVuY3Rpb24ocGFydF92ZWMsIGspIHsKICBsYWJlbHMgPC0gc2VxX2xlbihrKQoKICByZXMgPC0gbGFwcGx5KHNlcV9hbG9uZyhwYXJ0X3ZlYyksIGZ1bmN0aW9uKGluZCkgewogICAgdGFyZ2V0X2NsdXN0ZXJzIDwtIHNldGRpZmYobGFiZWxzLCBwYXJ0X3ZlY1tpbmRdKQoKICAgIGxhcHBseSh0YXJnZXRfY2x1c3RlcnMsIGZ1bmN0aW9uKG5ld19jbHVzKSB7CiAgICAgIHBhcnRfdmVjW2luZF0gPC0gbmV3X2NsdXMKCiAgICAgIHBhcnRfdmVjCiAgICB9KQogIH0pCgogIFJlZHVjZShjLCByZXMpCn0KCnBhcnRpdGlvbl90cnkgPC0gZnVuY3Rpb24oZWRnZV92ZWMsIGluaXRfcGFydF92ZWMsIGsgPSAyLAogICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9pdGVyID0gMTAwMCwgaW1iX3RvbCA9IDAsIGFscGhhID0gMC41KSB7CiAgYmVzdF9pbWIgPC0gaW1iKGVkZ2VfdmVjLCBpbml0X3BhcnRfdmVjLCBhbHBoYSkKICBjdXJfaW1iIDwtIGJlc3RfaW1iCiAgYmVzdF9wYXJ0X3ZlYyA8LSBpbml0X3BhcnRfdmVjCiAgY3VyX3BhcnRfdmVjIDwtIGJlc3RfcGFydF92ZWMKCiAgZm9yIChpIGluIHNlcV9sZW4obWF4X2l0ZXIpKSB7CiAgICBwYXJ0X3ZlY19ncmlkIDwtIHN0ZXBfZ3JpZChjdXJfcGFydF92ZWMsIGspCiAgICBmb3IgKGogaW4gc2VxX2Fsb25nKHBhcnRfdmVjX2dyaWQpKSB7CiAgICAgIGdyaWRfaW1iIDwtIGltYihlZGdlX3ZlYywgcGFydF92ZWNfZ3JpZFtbal1dLCBhbHBoYSkKICAgICAgaWYgKGdyaWRfaW1iIDwgYmVzdF9pbWIpIHsKICAgICAgICBiZXN0X2ltYiA8LSBncmlkX2ltYgogICAgICAgIGJlc3RfcGFydF92ZWMgPC0gcGFydF92ZWNfZ3JpZFtbal1dCiAgICAgIH0KICAgICAgaWYgKGJlc3RfaW1iIDw9IGltYl90b2wpIHsKICAgICAgICBicmVhawogICAgICB9CiAgICB9CiAgICBpZiAoKGJlc3RfaW1iIDw9IGltYl90b2wpIHx8IChjdXJfaW1iIDw9IGJlc3RfaW1iKSkgewogICAgICBicmVhawogICAgfSBlbHNlIHsKICAgICAgY3VyX2ltYiA8LSBiZXN0X2ltYgogICAgICBjdXJfcGFydF92ZWMgPC0gYmVzdF9wYXJ0X3ZlYwogICAgfQogIH0KCiAgbGlzdChpbWIgPSBiZXN0X2ltYiwgcGFydF92ZWMgPSBiZXN0X3BhcnRfdmVjKQp9CgpwYXJ0aXRpb24gPC0gZnVuY3Rpb24oYWRqX21hdCwgayA9IDIsIG1heF9pdGVyID0gMTAwMCwgbl90cnkgPSAxMDAsCiAgICAgICAgICAgICAgICAgICAgICBpbWJfdG9sID0gMCwgYWxwaGEgPSAwLjUpIHsKICBuX25vZGVzIDwtIG5yb3coYWRqX21hdCkKICBlZGdlX3ZlYyA8LSBhc19lZGdlX3ZlYyhhZGpfbWF0KQoKICBiZXN0X2ltYiA8LSBJbmYKICBiZXN0X3BhcnRfdmVjIDwtIHJlcChOQV9pbnRlZ2VyXywgbl9ub2RlcykKCiAgZm9yICh0cnlfaW5kIGluIHNlcV9sZW4obl90cnkpKSB7CiAgICBpbml0X3BhcnRfdmVjIDwtIHJjbHVzKG5fbm9kZXMsIGspCiAgICB0cnlfcmVzIDwtIHBhcnRpdGlvbl90cnkoZWRnZV92ZWMsIGluaXRfcGFydF92ZWMsIGssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2l0ZXIsIGltYl90b2wsIGFscGhhKQoKICAgIGlmICh0cnlfcmVzJGltYiA8IGJlc3RfaW1iKSB7CiAgICAgIGJlc3RfaW1iIDwtIHRyeV9yZXMkaW1iCiAgICAgIGJlc3RfcGFydF92ZWMgPC0gdHJ5X3JlcyRwYXJ0X3ZlYwogICAgfQogICAgaWYgKGJlc3RfaW1iIDw9IGltYl90b2wpIHsKICAgICAgYnJlYWsKICAgIH0KICB9CiAgbmFtZXMoYmVzdF9wYXJ0X3ZlYykgPC0gcm93bmFtZXMoYWRqX21hdCkKCiAgbGlzdChpbWIgPSBiZXN0X2ltYiwgcGFydF92ZWMgPSBiZXN0X3BhcnRfdmVjKQp9CmBgYAoKIyMgUGFydGl0aW9uCgpfX1dBUk5JTkdfXzogVGFrZXMgcHJldHR5IGxvbmcgdGltZSB0byBydW4uIENoYW5nZSBgbWF4X2l0ZXJgIGFuZC9vciBgbl90cnlgIHRvIHNtYWxsZXIgdmFsdWVzIGZvciBmYXN0ZXIgKGJ1dCBsZXNzIHByZWNpc2UpIGNvbXB1dGF0aW9uCmBgYHtyIHBhcml0aW9ufQp0dHBfZGF0YSA8LSB0dHAgJT4lCiAgbW9ycGgodG9fY29tcG9uZW50cykgJT4lCiAgY3J5c3RhbGxpc2UoKSAlPiUKICBtdXRhdGUoCiAgICAjIENvbXB1dGUgcGFydGl0aW9uIGRhdGEKICAgIGFkal9tYXQgPSBtYXAoZ3JhcGgsIGlncmFwaDo6YXNfYWRqLCBhdHRyID0gInNpZ24iKSwKICAgIHBhcnRpdGlvbl9kYXRhID0gbWFwKGFkal9tYXQsIHBhcnRpdGlvbiwgbWF4X2l0ZXIgPSAxMDAsIG5fdHJ5ID0gNTAwMCksCiAgICBiZXN0X2ltYiA9IG1hcF9kYmwocGFydGl0aW9uX2RhdGEsICJpbWIiKSwKICAgIHBhcnRpdGlvbiA9IG1hcChwYXJ0aXRpb25fZGF0YSwgInBhcnRfdmVjIiksCiAgICAjIE1lcmdlIGJlc3QgcGFydGl0aW9uIHRvIGdyYXBocwogICAgZ3JhcGggPSBtYXAyKAogICAgICBncmFwaCwgcGFydGl0aW9uLAogICAgICB+IGFjdGl2YXRlKC54LCBub2RlcykgJT4lCiAgICAgICAgbGVmdF9qb2luKHkgPSBlbmZyYW1lKC55LCB2YWx1ZSA9ICJwYXJ0aXRpb24iKSAlPiUKICAgICAgICAgICAgICAgICAgICBtdXRhdGUocGFydGl0aW9uID0gYXMuY2hhcmFjdGVyKHBhcnRpdGlvbikpLAogICAgICAgICAgICAgICAgICBieSA9ICJuYW1lIikKICAgICkKICApCgpzYXZlUkRTKHR0cF9kYXRhLCAidHRwX2RhdGEucmRzIikKCnR0cF91cGRhdGVkIDwtIHJlZHVjZSh0dHBfZGF0YSRncmFwaCwgYmluZF9ncmFwaHMpCgp0dHBfdXBkYXRlZCAlPiUKICBnZ3JhcGgobGF5b3V0ID0gIm5pY2VseSIpICsKICAgIGdlb21fZWRnZV9saW5rKAogICAgICBhZXMoY29sb3VyID0gdHlwZSksCiAgICAgIGFycm93ID0gYXJyb3cobGVuZ3RoID0gdW5pdCgxLjUsICJtbSIpKSwKICAgICAgc3RhcnRfY2FwID0gY2lyY2xlKDMsICJtbSIpLAogICAgICBlbmRfY2FwID0gY2lyY2xlKDMsICJtbSIpCiAgICApICsKICAgIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IGNoYXB0ZXIsIGNvbG91ciA9IHBhcnRpdGlvbiksIHNpemUgPSA1KSArCiAgICBzY2FsZV9lZGdlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYyhhbGx5ID0gIiMyMkIwMjIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3Bwb25lbnQgPSAiI0E0QUFGNiIpKSArCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsKICAgIHRoZW1lX2dyYXBoKCkgKwogICAgbGFicygKICAgICAgdGl0bGUgPSAnIlRhbyBUZSBQcm9ncmFtbWluZyIgYWxsaWFuY2UgZ3JhcGgnLAogICAgICBzdWJ0aXRsZSA9ICJOb2RlcyByZXByZXNlbnQgY2hhcHRlciBudW1iZXJzLCBlZGdlcyAtIGNvbm5lY3Rpb25zIiwKICAgICAgY2FwdGlvbiA9ICJAZWNoYXNub3Zza2kiCiAgICApCmBgYAoKIyBDZW50cmFsIGFkdmljZQoKYGBge3IgY2VudHJhbCBhZHZpY2V9CmVkZ2VzX3BlYWNlIDwtIHR0cF9lZGdlcyAlPiUKICB0cmFuc211dGUoCiAgICBmcm9tID0gaWZfZWxzZSh0eXBlID09ICJhbGx5IiwgY2hhcHRlcjEsIGNoYXB0ZXIyKSwKICAgIHRvID0gaWZfZWxzZSh0eXBlID09ICJhbGx5IiwgY2hhcHRlcjIsIGNoYXB0ZXIxKQogICkKCnRibF9ncmFwaCgKICBub2RlcyA9IHR0cF9jaGFwdGVycywgZWRnZXMgPSBlZGdlc19wZWFjZSwgZGlyZWN0ZWQgPSBUUlVFCiAgKSAlPiUKICBhY3RpdmF0ZShub2RlcykgJT4lCiAgbXV0YXRlKGNlbnRyID0gY2VudHJhbGl0eV9wYWdlcmFuaygpKSAlPiUKICBhc190aWJibGUoKSAlPiUKICBhcnJhbmdlKGRlc2MoY2VudHIpKQpgYGAKCg==