16. Network science

16.1. Introduction to network science

16.1.1. Definitions

네트워크 이론의 뿌리는 그래프 이론이라는 수학 분야에 있다.



  1. 그래프 G=(V, E)에서 V는 꼭지점(vertices)의 집합이고 E는 노드 간의 가장자리(Edges), 즉 연결의 집합이다.
  2. 그래프의 연결(E)은 방향이 있을수도, 없을수도 있다. 차이점은 관계가 상호적인지 일방적인지이다.
  3. 연결선 E에 가중치가 부여될 수 있다. 가중치가 표시되지 않은 경우, 모든 가중치를 1로 설정하는 것과 같다.
  4. 경로는 두 꼭짓점을 잇는 일련의 연결이다. 경로가 있는 경우 반드시 하나의 최단 경로가 있으며, 엣지 수나 가중치의 합에따라 달라진다.
  5. 그래프의 지름은 두 꼭지점 사이에서 가장 긴 경로의 길이다.
  6. 일반적으로 그래프에는 좌표가 없다. 즉, 그리는 올바른 방법은 없지만 몇 가지 알고리즘이 존재한다.
  • Centrality, 중심성: 어떤 노드를 중앙에 위치시킬 것인가.
    ++ 1. Degree centrality, 차수중심성: 더 많은 연결을 가지는 노드를 더 높은 순위에 두는 것
    ++ 2. Betweenness centrality, 매개중심성: 같은 점 s와 t사이를 지나는 \(최단경로의 수/엣지의 수\)로 판단하는 것
    ++ 3. Eigenvector centrality, 아이겐벡터중심성




16.2. EX: Six degrees of Kristen Stewart

16.2.1. Collecting Hollywood data

IMDb의 배우를 노드로 하여 할리우드 네트워크를 만들어보자. 네트워크가 너무 커질 수 있으므로 100,000 rating 이상의 2012년부터의 장편영화로 제한하고 각 영화의 상위 20개 역할만 고려하기로 한다.

library(mdsr)
db <- src_scidb("imdb")
sql <-
  "SELECT a.person_id as src, b.person_id as dest,
  a.movie_id,
  a.nr_order * b.nr_order as weight,
  t.title, idx.info as ratings
  FROM imdb.cast_info a
  CROSS JOIN imdb.cast_info b USING (movie_id)
  LEFT JOIN imdb.title t ON a.movie_id = t.id
  LEFT JOIN imdb.movie_info_idx idx ON idx.movie_id = a.movie_id
  WHERE t.production_year = 2012 AND t.kind_id = 1
  AND info_type_id = 100 AND idx.info > 125000
  AND a.nr_order <= 20 AND b.nr_order <= 20
  AND a.role_id IN (1,2) AND b.role_id IN (1,2)
  AND a.person_id < b.person_id
  GROUP BY src, dest, movie_id"

E <- DBI::dbGetQuery(db$con, sql) %>%
  mutate(ratings = as.numeric(ratings))

glimpse(E)
Rows: 11,344
Columns: 6
$ src      <int> 3139, 3139, 3139, 3139, 3139, 3139, 3139, 3139, 3139, 3139, 3139, 3139, 3139, 3139,…
$ dest     <int> 131060, 415839, 913049, 1035060, 1395260, 1414112, 1547500, 1548964, 1563748, 21785…
$ movie_id <int> 3979041, 3979041, 3979041, 3979041, 3979041, 3979041, 3979041, 3979041, 3979041, 39…
$ weight   <int64> 153, 255, 204, 272, 340, 68, 17, 306, 102, 136, 85, 51, 170, 323, 221, 238, 119, …
$ title    <chr> "Mud", "Mud", "Mud", "Mud", "Mud", "Mud", "Mud", "Mud", "Mud", "Mud", "Mud", "Mud",…
$ ratings  <dbl> 146464, 146464, 146464, 146464, 146464, 146464, 146464, 146464, 146464, 146464, 146…

코드 내에 가중치 변수를 계산한 것을 볼 수 있는데, 여기서 가중치는 배우가 크레딧에 등장하는 순서를 기준으로 한다.



nrow(E)
[1] 11344
length(unique(E$title))
[1] 61
E %>%
group_by(movie_id) %>%
summarize(title = max(title), N = n(), numRatings = max(ratings)) %>%
arrange(desc(numRatings))

쿼리 결과, 61개 영화간에 11,344개의 연결이 생성되었다. 배트맨: 더 다크나이트 라이즈가 가장 많은 사용자 평가를 받은 것을 확인할 수 있다. 다음으로 각 노드에 대한 정보를 수집해야한다. 이 경우, 배우의 이름과 IMDb식별자만이 필요하다.


actor_ids <- unique(c(E$src, E$dest))
V <- db %>%
  tbl("name") %>%
  filter(id %in% actor_ids) %>%
  select(id, name) %>%
  rename(actor_name = name) %>%
  collect() %>%
  arrange(id)

glimpse(V)
Rows: 1,115
Columns: 2
$ id         <int> 3139, 6388, 6897, 8462, 16644, 17039, 18760, 28535, 33799, 42958, 44858, 45167, …
$ actor_name <chr> "Abbott Jr., Michael", "Abkarian, Simon", "Aboutboul, Alon", "Abtahi, Omid", "Ad…




16.2.2. Building the Hollywood network

R의 네트워크 분석을 위한 패키지는 igraph와 sna다. 예제에서는 igraph를 사용하며, 연결과 방향을 지정하고 꼭지점에 대한 정보를 추가한다.

library(igraph)

g <- graph_from_data_frame(E, directed = FALSE, vertices = V)
summary(g)
IGRAPH fa89a8e UNW- 1115 11344 -- 
+ attr: name (v/c), actor_name (v/c), movie_id (e/n), weight (e/n), title (e/c), ratings
| (e/n)

요약 결과 1,115명의 배우와 11,344개의 연결이 있음을 알 수 있다. 보통 첫번째 꼭지점 속성은 이름이지만, 더 많은 정보를 제공할 수 있는 IMDb레이블을 유지해보고자 한다.

g <- set_vertex_attr(g, "imdbId", value = V(g)$name)
plot(g, edge.color = "lightgray", vertex.size = 2, vertex.label = NA)

이 경우, 한 배우가 여러 영화에 출연했다면 다른 배우들과 더 많은 인연을 맺는 것이 당연하기 때문에 중심에 위치하는 경향이 있다. 이를 degree centrality로 확인할 수 있다.

g <- set_vertex_attr(g, "degree", value = degree(g))

as_data_frame(g, what = "vertices") %>%
  arrange(desc(degree)) %>%
  head()

Cranston, Bryan이 가장 높은 차수중심성을 갖는 결과가 출력되었다.



getMovies <- function(imdbId, E) {
  E %>%
    filter(src == imdbId | dest == imdbId) %>%
    tally(~ title, data = .)
}

getMovies(502126, E)
title
                              Argo                        John Carter 
                                19                                 19 
Madagascar 3: Europe's Most Wanted                       Total Recall 
                                19                                 19 

Cranston은 이 4편의 영화에 모두 출연했다. degree를 플롯을 통해 살펴보자.


ggplot(data = data.frame(degree = degree(g)), aes(x = degree)) +
  geom_density(size = 2)

degree의 분포가 고르지 않은 모습이다. 수정을 위해 igraph의 플롯 대신 ggplot2로 직접 그래프를 그리기 위해 ggnetwork 패키지를 사용해보자.



library(ggnetwork)

g_df <- ggnetwork(g)
hollywood <- ggplot(g_df, aes(x, y, xend = xend, yend = yend)) +
  geom_edges(aes(size = weight), color = "lightgray") +
  geom_nodes(aes(color = degree), alpha = 0.6) +
  scale_size_continuous(range = c(0.001, 0.2)) +
  theme_blank()

hollywood +
  geom_nodetext(aes(label = gsub(", ", ",\n", actor_name)),
                data = subset(g_df, degree > 40))

ggnetwork()함수는 igraph 오브젝트를 데이터프레임으로 변환한다.




16.2.3. Building a Kristen Stewart oracle

차수중심성은 edge의 가중치를 고려하지 않는다. 주연 배우를 통한 경로를 강조하려면 betweenness centrality, 매개중심성을 고려한다.

g <- g %>%
  set_vertex_attr("btw", value = igraph::betweenness(g, normalized = TRUE))

get.data.frame(g, what = "vertices") %>%
  arrange(desc(btw)) %>%
  head()

Cho, John의 매개중심성이 가장 높고, 차수중심성은 Nighy, Bill이 Cho, John과 동시에 가장 높다. John Cho를 중심에 두고 ’Cho_번호’를 고려할 경우, 다른 배우와의 거리를 계산할 수 있다.

jc <- V(g)[actor_name == "Cho, John"]
bn <- V(g)[actor_name == "Nighy, Bill"]

p <- shortest_paths(g, from = bn, to = jc, weights = NA, output = "both")
vertex_attr(g, "actor_name", index = p$vpath[[1]])
[1] "Nighy, Bill" "Cho, John"  

현재 예시의 두 배우는 거리가 1이다. 이러한 최단 거리는 유일한 것은 아니다.

length(all_shortest_paths(g, from = jc, to = bn, weights = NA)$res)
[1] 1

하지만 본 예시에서는 유일한 최단거리로 나타나고 있다. 항상 최단 거리가 유일하진 않다는 것을 알아야한다.



diameter()는 가장 멀리 떨어져있는 두 배우의 거리를 나타낸다.

diameter(g, weights = NA)
[1] 10



eccentricity()는 이심률을 나타낸다.

eccentricity(g, vids = jc)
427111 
     7 

여기서 Cho, John과의 이심률이 7이라는 것은, Cho, John과 거리가 7이상 떨어진 배우가 없다는 것을 의미한다.






LS0tDQp0aXRsZTogIk1vZGVybiBEYXRhIFNjaWVuY2Ugd2l0aCBSX0NwdDE2Ig0KYXV0aG9yOiBzZW9uZ3N1LCBraW0NCmRhdGU6IDIwMjMtMDEtMjcNCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KYGBge3IgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShtb3NhaWMpDQpsaWJyYXJ5KG1kc3IpDQpsaWJyYXJ5KHRpZHlyKQ0KYGBgDQoNCiMgMTYuIE5ldHdvcmsgc2NpZW5jZQ0KDQojIyAxNi4xLiBJbnRyb2R1Y3Rpb24gdG8gbmV0d29yayBzY2llbmNlDQoNCiMjIyAxNi4xLjEuIERlZmluaXRpb25zDQrrhKTtirjsm4ztgawg7J2066Gg7J2YIOu/jOumrOuKlCAqKuq3uOuemO2UhCDsnbTroaAqKuydtOudvOuKlCDsiJjtlZkg67aE7JW87JeQIOyeiOuLpC4NCg0KXA0KXA0KDQoxLiDqt7jrnpjtlIQgRz0oViwgRSnsl5DshJwgVuuKlCDqvK3sp4DsoJAodmVydGljZXMp7J2YIOynke2VqeydtOqzoCBF64qUIOuFuOuTnCDqsITsnZgg6rCA7J6l7J6Q66asKEVkZ2VzKSwg7KaJICDsl7DqsrDsnZgg7KeR7ZWp7J2064ukLlwNCjIuIOq3uOuemO2UhOydmCDsl7DqsrAoRSnsnYAg67Cp7Zal7J20IOyeiOydhOyImOuPhCwg7JeG7J2E7IiY64+EIOyeiOuLpC4g7LCo7J207KCQ7J2AIOq0gOqzhOqwgCDsg4HtmLjsoIHsnbjsp4Ag7J2867Cp7KCB7J247KeA7J2064ukLlwNCjMuIOyXsOqysOyEoCBF7JeQIOqwgOykkey5mOqwgCDrtoDsl6zrkKAg7IiYIOyeiOuLpC4g6rCA7KSR7LmY6rCAIO2RnOyLnOuQmOyngCDslYrsnYAg6rK97JqwLCDrqqjrk6Ag6rCA7KSR7LmY66W8IDHroZwg7ISk7KCV7ZWY64qUIOqyg+qzvCDqsJnri6QuXA0KNC4g6rK966Gc64qUIOuRkCDqvK3sp5PsoJDsnYQg7J6H64qUIOydvOugqOydmCDsl7DqsrDsnbTri6QuIOqyveuhnOqwgCDsnojripQg6rK97JqwIOuwmOuTnOyLnCDtlZjrgpjsnZgg7LWc64uoIOqyveuhnOqwgCDsnojsnLzrqbAsIOyXo+yngCDsiJjrgpgg6rCA7KSR7LmY7J2YIO2VqeyXkOuUsOudvCDri6zrnbzsp4Tri6QuXA0KNS4g6re4656Y7ZSE7J2YIOyngOumhOydgCDrkZAg6ryt7KeA7KCQIOyCrOydtOyXkOyEnCDqsIDsnqUg6ri0IOqyveuhnOydmCDquLjsnbTri6QuXA0KNi4g7J2867CY7KCB7Jy866GcIOq3uOuemO2UhOyXkOuKlCDsooztkZzqsIAg7JeG64ukLiDspoksIOq3uOumrOuKlCDsmKzrsJTrpbgg67Cp67KV7J2AIOyXhuyngOunjCDrqocg6rCA7KeAIOyVjOqzoOumrOymmOydtCDsobTsnqztlZzri6QuXA0KKyBDZW50cmFsaXR5LCDspJHsi6zshLE6IOyWtOuWpCDrhbjrk5zrpbwg7KSR7JWZ7JeQIOychOy5mOyLnO2CrCDqsoPsnbjqsIAuXA0KKysgMS4gRGVncmVlIGNlbnRyYWxpdHksIOywqOyImOykkeyLrOyEsTog642UIOunjuydgCDsl7DqsrDsnYQg6rCA7KeA64qUIOuFuOuTnOulvCDrjZQg64aS7J2AIOyInOychOyXkCDrkZDripQg6rKDXA0KKysgMi4gQmV0d2Vlbm5lc3MgY2VudHJhbGl0eSwg66ek6rCc7KSR7Ius7ISxOiDqsJnsnYAg7KCQIHPsmYAgdOyCrOydtOulvCDsp4DrgpjripQgKiok7LWc64uo6rK966Gc7J2YIOyImC/sl6Psp4DsnZgg7IiYJCoq66GcIO2MkOuLqO2VmOuKlCDqsoNcDQorKyAzLiBFaWdlbnZlY3RvciBjZW50cmFsaXR5LCDslYTsnbTqspDrsqHthLDspJHsi6zshLENCg0KXA0KXA0KXA0KDQojIyAxNi4yLiBFWDogU2l4IGRlZ3JlZXMgb2YgS3Jpc3RlbiBTdGV3YXJ0DQoNCiMjIyAxNi4yLjEuIENvbGxlY3RpbmcgSG9sbHl3b29kIGRhdGENCklNRGLsnZgg67Cw7Jqw66W8IOuFuOuTnOuhnCDtlZjsl6wg7ZWg66as7Jqw65OcIOuEpO2KuOybjO2BrOulvCDrp4zrk6TslrTrs7TsnpAuIOuEpO2KuOybjO2BrOqwgCDrhIjrrLQg7Luk7KeIIOyImCDsnojsnLzrr4DroZwgMTAwLDAwMCByYXRpbmcg7J207IOB7J2YIDIwMTLrhYTrtoDthLDsnZgg7J6l7Y647JiB7ZmU66GcIOygnO2VnO2VmOqzoCDqsIEg7JiB7ZmU7J2YIOyDgeychCAyMOqwnCDsl63tlaDrp4wg6rOg66Ck7ZWY6riw66GcIO2VnOuLpC4NCmBgYHtyfQ0KbGlicmFyeShtZHNyKQ0KZGIgPC0gc3JjX3NjaWRiKCJpbWRiIikNCnNxbCA8LQ0KICAiU0VMRUNUIGEucGVyc29uX2lkIGFzIHNyYywgYi5wZXJzb25faWQgYXMgZGVzdCwNCiAgYS5tb3ZpZV9pZCwNCiAgYS5ucl9vcmRlciAqIGIubnJfb3JkZXIgYXMgd2VpZ2h0LA0KICB0LnRpdGxlLCBpZHguaW5mbyBhcyByYXRpbmdzDQogIEZST00gaW1kYi5jYXN0X2luZm8gYQ0KICBDUk9TUyBKT0lOIGltZGIuY2FzdF9pbmZvIGIgVVNJTkcgKG1vdmllX2lkKQ0KICBMRUZUIEpPSU4gaW1kYi50aXRsZSB0IE9OIGEubW92aWVfaWQgPSB0LmlkDQogIExFRlQgSk9JTiBpbWRiLm1vdmllX2luZm9faWR4IGlkeCBPTiBpZHgubW92aWVfaWQgPSBhLm1vdmllX2lkDQogIFdIRVJFIHQucHJvZHVjdGlvbl95ZWFyID0gMjAxMiBBTkQgdC5raW5kX2lkID0gMQ0KICBBTkQgaW5mb190eXBlX2lkID0gMTAwIEFORCBpZHguaW5mbyA+IDEyNTAwMA0KICBBTkQgYS5ucl9vcmRlciA8PSAyMCBBTkQgYi5ucl9vcmRlciA8PSAyMA0KICBBTkQgYS5yb2xlX2lkIElOICgxLDIpIEFORCBiLnJvbGVfaWQgSU4gKDEsMikNCiAgQU5EIGEucGVyc29uX2lkIDwgYi5wZXJzb25faWQNCiAgR1JPVVAgQlkgc3JjLCBkZXN0LCBtb3ZpZV9pZCINCg0KRSA8LSBEQkk6OmRiR2V0UXVlcnkoZGIkY29uLCBzcWwpICU+JQ0KICBtdXRhdGUocmF0aW5ncyA9IGFzLm51bWVyaWMocmF0aW5ncykpDQoNCmdsaW1wc2UoRSkNCmBgYA0K7L2U65OcIOuCtOyXkCDqsIDspJHsuZgg67OA7IiY66W8IOqzhOyCsO2VnCDqsoPsnYQg67O8IOyImCDsnojripTrjbAsIOyXrOq4sOyEnCDqsIDspJHsuZjripQg67Cw7Jqw6rCAIO2BrOugiOuUp+yXkCDrk7HsnqXtlZjripQg7Iic7ISc66W8IOq4sOykgOycvOuhnCDtlZzri6QuDQoNClwNClwNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm5yb3coRSkNCmxlbmd0aCh1bmlxdWUoRSR0aXRsZSkpDQoNCkUgJT4lDQpncm91cF9ieShtb3ZpZV9pZCkgJT4lDQpzdW1tYXJpemUodGl0bGUgPSBtYXgodGl0bGUpLCBOID0gbigpLCBudW1SYXRpbmdzID0gbWF4KHJhdGluZ3MpKSAlPiUNCmFycmFuZ2UoZGVzYyhudW1SYXRpbmdzKSkNCmBgYA0K7L+866asIOqysOqzvCwgNjHqsJwg7JiB7ZmU6rCE7JeQIDExLDM0NOqwnOydmCDsl7DqsrDsnbQg7IOd7ISx65CY7JeI64ukLiAq67Cw7Yq466eoOiDrjZQg64uk7YGs64KY7J207Yq4IOudvOydtOymiCrqsIAg6rCA7J6lIOunjuydgCDsgqzsmqnsnpAg7Y+J6rCA66W8IOuwm+ydgCDqsoPsnYQg7ZmV7J247ZWgIOyImCDsnojri6QuIOuLpOydjOycvOuhnCDqsIEg64W465Oc7JeQIOuMgO2VnCDsoJXrs7Trpbwg7IiY7KeR7ZW07JW87ZWc64ukLiDsnbQg6rK97JqwLCDrsLDsmrDsnZgg7J2066aE6rO8IElNRGLsi53rs4TsnpDrp4zsnbQg7ZWE7JqU7ZWY64ukLg0KDQpcDQpgYGB7cn0NCmFjdG9yX2lkcyA8LSB1bmlxdWUoYyhFJHNyYywgRSRkZXN0KSkNClYgPC0gZGIgJT4lDQogIHRibCgibmFtZSIpICU+JQ0KICBmaWx0ZXIoaWQgJWluJSBhY3Rvcl9pZHMpICU+JQ0KICBzZWxlY3QoaWQsIG5hbWUpICU+JQ0KICByZW5hbWUoYWN0b3JfbmFtZSA9IG5hbWUpICU+JQ0KICBjb2xsZWN0KCkgJT4lDQogIGFycmFuZ2UoaWQpDQoNCmdsaW1wc2UoVikNCmBgYA0KDQpcDQpcDQpcDQoNCiMjIyAxNi4yLjIuIEJ1aWxkaW5nIHRoZSBIb2xseXdvb2QgbmV0d29yaw0KUuydmCDrhKTtirjsm4ztgawg67aE7ISd7J2EIOychO2VnCDtjKjtgqTsp4DripQgaWdyYXBo7JmAIHNuYeuLpC4g7JiI7KCc7JeQ7ISc64qUIGlncmFwaOulvCDsgqzsmqntlZjrqbAsIOyXsOqysOqzvCDrsKntlqXsnYQg7KeA7KCV7ZWY6rOgIOq8reyngOygkOyXkCDrjIDtlZwg7KCV67O066W8IOy2lOqwgO2VnOuLpC4NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGlncmFwaCkNCg0KZyA8LSBncmFwaF9mcm9tX2RhdGFfZnJhbWUoRSwgZGlyZWN0ZWQgPSBGQUxTRSwgdmVydGljZXMgPSBWKQ0Kc3VtbWFyeShnKQ0KYGBgDQrsmpTslb0g6rKw6rO8IDEsMTE166qF7J2YIOuwsOyasOyZgCAxMSwzNDTqsJzsnZgg7Jew6rKw7J20IOyeiOydjOydhCDslYwg7IiYIOyeiOuLpC4g67O07Ya1IOyyq+uyiOynuCDqvK3sp4DsoJAg7IaN7ISx7J2AIOydtOumhOydtOyngOunjCwg642UIOunjuydgCDsoJXrs7Trpbwg7KCc6rO17ZWgIOyImCDsnojripQgSU1EYuugiOydtOu4lOydhCDsnKDsp4DtlbTrs7Tqs6DsnpAg7ZWc64ukLg0KXA0KDQpgYGB7cn0NCmcgPC0gc2V0X3ZlcnRleF9hdHRyKGcsICJpbWRiSWQiLCB2YWx1ZSA9IFYoZykkbmFtZSkNCnBsb3QoZywgZWRnZS5jb2xvciA9ICJsaWdodGdyYXkiLCB2ZXJ0ZXguc2l6ZSA9IDIsIHZlcnRleC5sYWJlbCA9IE5BKQ0KYGBgDQrsnbQg6rK97JqwLCDtlZwg67Cw7Jqw6rCAIOyXrOufrCDsmIHtmZTsl5Ag7Lac7Jew7ZaI64uk66m0IOuLpOuluCDrsLDsmrDrk6Tqs7wg642UIOunjuydgCDsnbjsl7DsnYQg66e664qUIOqyg+ydtCDri7nsl7DtlZjquLAg65WM66y47JeQIOykkeyLrOyXkCDsnITsuZjtlZjripQg6rK97Zal7J20IOyeiOuLpC4g7J2066W8IGRlZ3JlZSBjZW50cmFsaXR566GcIO2ZleyduO2VoCDsiJgg7J6I64ukLg0KXA0KDQpgYGB7cn0NCmcgPC0gc2V0X3ZlcnRleF9hdHRyKGcsICJkZWdyZWUiLCB2YWx1ZSA9IGRlZ3JlZShnKSkNCg0KYXNfZGF0YV9mcmFtZShnLCB3aGF0ID0gInZlcnRpY2VzIikgJT4lDQogIGFycmFuZ2UoZGVzYyhkZWdyZWUpKSAlPiUNCiAgaGVhZCgpDQpgYGANCkNyYW5zdG9uLCBCcnlhbuydtCDqsIDsnqUg64aS7J2AIOywqOyImOykkeyLrOyEseydhCDqsJbripQg6rKw6rO86rCAIOy2nOugpeuQmOyXiOuLpC4NCg0KXA0KXA0KDQpgYGB7cn0NCmdldE1vdmllcyA8LSBmdW5jdGlvbihpbWRiSWQsIEUpIHsNCiAgRSAlPiUNCiAgICBmaWx0ZXIoc3JjID09IGltZGJJZCB8IGRlc3QgPT0gaW1kYklkKSAlPiUNCiAgICB0YWxseSh+IHRpdGxlLCBkYXRhID0gLikNCn0NCg0KZ2V0TW92aWVzKDUwMjEyNiwgRSkNCmBgYA0KQ3JhbnN0b27snYAg7J20IDTtjrjsnZgg7JiB7ZmU7JeQIOuqqOuRkCDstpzsl7Dtlojri6QuIGRlZ3JlZeulvCDtlIzroa/snYQg7Ya17ZW0IOyCtO2OtOuztOyekC4NCg0KXA0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gZGF0YS5mcmFtZShkZWdyZWUgPSBkZWdyZWUoZykpLCBhZXMoeCA9IGRlZ3JlZSkpICsNCiAgZ2VvbV9kZW5zaXR5KHNpemUgPSAyKQ0KYGBgDQpkZWdyZWXsnZgg67aE7Y+s6rCAIOqzoOultOyngCDslYrsnYAg66qo7Iq17J2064ukLiDsiJjsoJXsnYQg7JyE7ZW0IGlncmFwaOydmCDtlIzroa8g64yA7IugIGdncGxvdDLroZwg7KeB7KCRIOq3uOuemO2UhOulvCDqt7jrpqzquLAg7JyE7ZW0IGdnbmV0d29yayDtjKjtgqTsp4Drpbwg7IKs7Jqp7ZW067O07J6QLg0KDQpcDQpcDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGdnbmV0d29yaykNCg0KZ19kZiA8LSBnZ25ldHdvcmsoZykNCmhvbGx5d29vZCA8LSBnZ3Bsb3QoZ19kZiwgYWVzKHgsIHksIHhlbmQgPSB4ZW5kLCB5ZW5kID0geWVuZCkpICsNCiAgZ2VvbV9lZGdlcyhhZXMoc2l6ZSA9IHdlaWdodCksIGNvbG9yID0gImxpZ2h0Z3JheSIpICsNCiAgZ2VvbV9ub2RlcyhhZXMoY29sb3IgPSBkZWdyZWUpLCBhbHBoYSA9IDAuNikgKw0KICBzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDAuMDAxLCAwLjIpKSArDQogIHRoZW1lX2JsYW5rKCkNCg0KaG9sbHl3b29kICsNCiAgZ2VvbV9ub2RldGV4dChhZXMobGFiZWwgPSBnc3ViKCIsICIsICIsXG4iLCBhY3Rvcl9uYW1lKSksDQogICAgICAgICAgICAgICAgZGF0YSA9IHN1YnNldChnX2RmLCBkZWdyZWUgPiA0MCkpDQpgYGANCmdnbmV0d29yaygp7ZWo7IiY64qUIGlncmFwaCDsmKTruIzsoJ3tirjrpbwg642w7J207YSw7ZSE66CI7J6E7Jy866GcIOuzgO2ZmO2VnOuLpC4NCg0KXA0KXA0KXA0KDQojIyMgMTYuMi4zLiBCdWlsZGluZyBhIEtyaXN0ZW4gU3Rld2FydCBvcmFjbGUNCuywqOyImOykkeyLrOyEseydgCBlZGdl7J2YIOqwgOykkey5mOulvCDqs6DroKTtlZjsp4Ag7JWK64qU64ukLiDso7zsl7Ag67Cw7Jqw66W8IO2Gte2VnCDqsr3roZzrpbwg6rCV7KGw7ZWY66Ck66m0IGJldHdlZW5uZXNzIGNlbnRyYWxpdHksIOunpOqwnOykkeyLrOyEseydhCDqs6DroKTtlZzri6QuDQpcDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnIDwtIGcgJT4lDQogIHNldF92ZXJ0ZXhfYXR0cigiYnR3IiwgdmFsdWUgPSBpZ3JhcGg6OmJldHdlZW5uZXNzKGcsIG5vcm1hbGl6ZWQgPSBUUlVFKSkNCg0KZ2V0LmRhdGEuZnJhbWUoZywgd2hhdCA9ICJ2ZXJ0aWNlcyIpICU+JQ0KICBhcnJhbmdlKGRlc2MoYnR3KSkgJT4lDQogIGhlYWQoKQ0KYGBgDQpDaG8sIEpvaG7snZgg66ek6rCc7KSR7Ius7ISx7J20IOqwgOyepSDrhpLqs6AsIOywqOyImOykkeyLrOyEseydgCBOaWdoeSwgQmlsbOydtCBDaG8sIEpvaG7qs7wg64+Z7Iuc7JeQIOqwgOyepSDrhpLri6QuIEpvaG4gQ2hv66W8IOykkeyLrOyXkCDrkZDqs6AgJ0Nob1/rsojtmLgn66W8IOqzoOugpO2VoCDqsr3smrAsIOuLpOuluCDrsLDsmrDsmYDsnZgg6rGw66as66W8IOqzhOyCsO2VoCDsiJgg7J6I64ukLg0KXA0KDQpgYGB7cn0NCmpjIDwtIFYoZylbYWN0b3JfbmFtZSA9PSAiQ2hvLCBKb2huIl0NCmJuIDwtIFYoZylbYWN0b3JfbmFtZSA9PSAiTmlnaHksIEJpbGwiXQ0KDQpwIDwtIHNob3J0ZXN0X3BhdGhzKGcsIGZyb20gPSBibiwgdG8gPSBqYywgd2VpZ2h0cyA9IE5BLCBvdXRwdXQgPSAiYm90aCIpDQp2ZXJ0ZXhfYXR0cihnLCAiYWN0b3JfbmFtZSIsIGluZGV4ID0gcCR2cGF0aFtbMV1dKQ0KYGBgDQrtmITsnqwg7JiI7Iuc7J2YIOuRkCDrsLDsmrDripQg6rGw66as6rCAIDHsnbTri6QuIOydtOufrO2VnCDstZzri6gg6rGw66as64qUIOycoOydvO2VnCDqsoPsnYAg7JWE64uI64ukLg0KXA0KXA0KDQoNCmBgYHtyfQ0KbGVuZ3RoKGFsbF9zaG9ydGVzdF9wYXRocyhnLCBmcm9tID0gamMsIHRvID0gYm4sIHdlaWdodHMgPSBOQSkkcmVzKQ0KYGBgDQrtlZjsp4Drp4wg67O4IOyYiOyLnOyXkOyEnOuKlCDsnKDsnbztlZwg7LWc64uo6rGw66as66GcIOuCmO2DgOuCmOqzoCDsnojri6QuIO2VreyDgSDstZzri6gg6rGw66as6rCAICDsnKDsnbztlZjsp4Qg7JWK64uk64qUIOqyg+ydhCDslYzslYTslbztlZzri6QuDQoNClwNClwNCg0KZGlhbWV0ZXIoKeuKlCDqsIDsnqUg66mA66asIOuWqOyWtOyguOyeiOuKlCDrkZAg67Cw7Jqw7J2YIOqxsOumrOulvCDrgpjtg4Drgrjri6QuDQpgYGB7cn0NCmRpYW1ldGVyKGcsIHdlaWdodHMgPSBOQSkNCmBgYA0KXA0KXA0KDQplY2NlbnRyaWNpdHkoKeuKlCDsnbTsi6zrpaDsnYQg64KY7YOA64K464ukLg0KYGBge3J9DQplY2NlbnRyaWNpdHkoZywgdmlkcyA9IGpjKQ0KYGBgDQrsl6zquLDshJwgQ2hvLCBKb2hu6rO87J2YIOydtOyLrOuloOydtCA37J20652864qUIOqyg+ydgCwgQ2hvLCBKb2hu6rO8IOqxsOumrOqwgCA37J207IOBIOuWqOyWtOynhCDrsLDsmrDqsIAg7JeG64uk64qUIOqyg+ydhCDsnZjrr7jtlZzri6QuDQoNClwNClwNClwNClwNClwNCg==