library(readr)
library(dplyr)
library(jiebaR)
library(tidyr)
library(tidytext)
library(igraph)
library(topicmodels)
library(stringr)
library(ggplot2)
library(jsonlite)

資料來源:kaggle上的NBA shot logs資料集
https://www.kaggle.com/dansbecker/nba-shot-logs

讀入資料

# 讀入資料
records = read_csv("shot_logs.csv")
Parsed with column specification:
cols(
  .default = col_double(),
  MATCHUP = col_character(),
  LOCATION = col_character(),
  W = col_character(),
  GAME_CLOCK = col_time(format = ""),
  SHOT_RESULT = col_character(),
  CLOSEST_DEFENDER = col_character(),
  player_name = col_character()
)
See spec(...) for full column specifications.
records

會發現 CLOSEST_DEFENDER 的名字與 player_name 的格式不一樣,因此我們先將格式統一。

# 出手球員的id與name
player_list <- records %>% select(player_name, player_id) %>% unique() %>% rename(name = player_name)
# 統一姓名格式
pro_records <- records %>% 
  separate(CLOSEST_DEFENDER, c("first_name", "last_name"), fill = "warn", sep = ", ") %>% 
  unite("defender_name", last_name, first_name, sep =" ") %>%
  left_join(player_list, by = c("CLOSEST_DEFENDER_PLAYER_ID" = "player_id")) %>% 
  mutate(defender_name = ifelse(!is.na(name), name, tolower(defender_name))) %>% 
  select(-name)
Expected 2 pieces. Missing pieces filled with `NA` in 389 rows [86, 658, 659, 671, 679, 682, 683, 1353, 1354, 1358, 2218, 2227, 2230, 3443, 3811, 5670, 5671, 5673, 6179, 6180, ...].
pro_records
# 挑出defender_name(防守者名字)、player_name(出手者名字)、MATCHUP(比賽資訊) 三個欄位
link <- pro_records %>% 
  select(defender_name, player_name, MATCHUP)
# 建立網路關係
def_Network <- graph_from_data_frame(d=link, directed=T)
# 畫出網路圖
plot(def_Network, vertex.size=2, edge.arrow.size=0.5, vertex.label.cex=0.8)

挑出有該年度冠軍金州勇士的球員出手紀錄

該年度總冠軍隊伍為金州勇士,我們將其球員的出手紀錄與對到的防守球員挑出來做分析。

warriors <- pro_records %>% 
  filter(str_detect(MATCHUP, "GSW ")) %>% 
  add_count(defender_name, player_name, name = "d_FG") %>% 
  group_by(defender_name, player_name, d_FG) %>% 
  summarise(d_FGM = sum(FGM)) %>% 
  filter(d_FG >= 10) %>% 
  ungroup() %>% 
  mutate(d_FGP = d_FGM/d_FG)
warriors
# 建立網路關係
was_Network <- graph_from_data_frame(d=warriors, directed=T)
# 畫出網路圖
# 線的粗細代表防守到的次數多寡
# 綠線表示防守效益優於平均
# 紅線表示防守效益差於平均
set.seed(231)
E(was_Network)$color <- ifelse(E(was_Network)$d_FGP < mean(E(was_Network)$d_FGP) , "lightgreen", "palevioletred")
E(was_Network)$width <- (E(was_Network)$d_FG/10)^1.5
plot(was_Network, vertex.size=2, edge.arrow.size=0.1, vertex.label.cex=0.7)

勇士教練Kerr在設計戰術時,球員可以多挑防守效益不佳的球員做單打,若對上防守效益好的球員則可透過傳導化解。

篩選出總出手次數大於一定次數的紀錄

選出所有球員中總出手次數大於940的球員,相對其他球員可表示他們在場上時間更多,為隊上的主力輸出。

# 篩選出總出手次數大於940的紀錄
# 挑出相遇次數大於 15 次的紀錄
count_records <- pro_records %>% 
  add_count(player_name, name = "p_count") %>% 
  filter(p_count >= 940) %>% 
  add_count(defender_name, player_name, name = "d_FG") %>% 
  group_by(defender_name, player_name, d_FG) %>% 
  summarise(d_FGM = sum(FGM)) %>% 
  filter(d_FG >= 15) %>% 
  ungroup() %>% 
  mutate(d_FGP = d_FGM/d_FG)
count_records
# 建立網路關係
count_Network <- graph_from_data_frame(d=count_records, directed=T)
# 畫出網路圖
# 線的粗細代表防守到的次數多寡
# 綠線表示防守效益優於平均
# 紅線表示防守效益差於平均
set.seed(1213)
E(count_Network)$color <- ifelse(E(count_Network)$d_FGP < mean(E(count_Network)$d_FGP) , "lightgreen", "palevioletred")
E(count_Network)$width <- E(count_Network)$d_FG/10
plot(count_Network, vertex.size=2, edge.arrow.size=0.2, vertex.label.cex=0.7)

防守效益比較

透過計算出一名出手球員的總命中率,與個別球員防守時的命中率差值,
來單獨看每位防守球員在對上哪位出手球員時,有較大的防守效益。

# 出手球員總命中率
field_goal_per <- pro_records %>% 
  group_by(player_name) %>% 
  mutate(total_FGM = sum(FGM), total_FG = n()) %>% 
  mutate(total_FGP = total_FGM/total_FG) %>% 
  ungroup()
field_goal_per
# 出手球員與個別防守球員相對時之命中率
pair_def_records <- field_goal_per %>% 
  select(defender_name, player_name, FGM, total_FGP) %>% 
  group_by(defender_name, player_name, total_FGP) %>% 
  summarise(def_FGM = sum(FGM), def_FG = n()) %>% 
  mutate(def_FGP = def_FGM/def_FG) %>% 
  ungroup()
pair_def_records
# 個別組合間的防守效益差距
# 挑出相遇次數大於 8 次的紀錄
# 挑出差異大於 30% 的組合
s_pair_def_records <- pair_def_records %>% 
  filter(def_FG >= 8) %>% 
  mutate(dif_FGP = def_FGP - total_FGP) %>% 
  filter(abs(dif_FGP) >= 0.3) %>% 
  select(defender_name, player_name, dif_FGP)
s_pair_def_records
# 建立網路關係
s_pair_def_Network <- graph_from_data_frame(d=s_pair_def_records, directed=T)
# 畫出網路圖
# 防守影響較大的關係:綠色
# 防守影響較小的關係:紅色
set.seed(1234)
E(s_pair_def_Network)$color <- ifelse(E(s_pair_def_Network)$dif_FGP < 0 , "lightgreen", "palevioletred")
plot(s_pair_def_Network, vertex.size=2, edge.arrow.size=0.3, vertex.label.cex=0.7)

年度防守隊伍

NBA每年都會投票選出防守第一隊與第二隊,從球場上的各個位置選最適合的人選。
這裡我們找出2014-15賽季,防守第一隊與第二隊的名單,
透過在名單上各球員的防守下,出手球員命中率的差別來驗證這份名單。

年度防守第一隊

# 個別組合間的防守效益差距
# 挑出相遇次數的大於 3 次的紀錄
# 挑出差異大於 25% 的組合
first_team_records <- pair_def_records %>% 
  filter(defender_name == "kawhi leonard" | 
         defender_name == "draymond green" |
         defender_name == "deandre jordan" |
         defender_name == "tony allen" |
         defender_name == "chris paul") %>% 
  filter(def_FG >= 3) %>% 
  mutate(dif_FGP = def_FGP - total_FGP) %>% 
  filter(abs(dif_FGP) >= 0.25) %>% 
  select(defender_name, player_name, dif_FGP)
first_team_records
# 建立網路關係
first_team_Network <- graph_from_data_frame(d=first_team_records, directed=T)
# 畫出網路圖
# 防守影響較大的關係:綠色
# 防守影響較小的關係:紅色
set.seed(31)
E(first_team_Network)$color <- ifelse(E(first_team_Network)$dif_FGP < 0 , "lightgreen", "palevioletred")
plot(first_team_Network, vertex.size=2, edge.arrow.size=0.3, vertex.label.cex=0.7)

年度防守第二隊

# 個別組合間的防守效益差距
# 挑出相遇次數的大於 3 次的紀錄
# 挑出差異大於 25% 的組合
second_team_records <- pair_def_records %>% 
  filter(defender_name == "anthony davis" | 
         defender_name == "tim duncan" |
         defender_name == "andrew bogut" |
         defender_name == "jimmy butler" |
         defender_name == "john wall") %>% 
  filter(def_FG >= 3) %>% 
  mutate(dif_FGP = def_FGP - total_FGP) %>% 
  filter(abs(dif_FGP) >= 0.25) %>% 
  select(defender_name, player_name, dif_FGP)
second_team_records
# 建立網路關係
second_team_Network <- graph_from_data_frame(d=second_team_records, directed=T)
# 畫出網路圖
# 防守影響較大的關係:綠色
# 防守影響較小的關係:紅色
set.seed(1234)
E(second_team_Network)$color <- ifelse(E(second_team_Network)$dif_FGP < 0 , "lightgreen", "palevioletred")
plot(second_team_Network, vertex.size=2, edge.arrow.size=0.3, vertex.label.cex=0.7)

去除Wide Open狀態下的資料

來源資料中是每一筆的出手紀錄,故其中的防守球員只是距離出手球員最近的球員,
但球場上可能透過卡位、空切、傳球等手段來跑出空檔,此時出手球員會與防守球員拉開一段距離,
此時防守球員是誰的意義便不會那麼大,因此我們透過數據中的最近防守球員距離為依據,
將距離超過 4.92feet(=1.5公尺) 的紀錄去除,再看剛剛的年度防守第一隊和第二隊的紀錄。

# 挑出與防守者距離小於 4.92feet 的紀錄
no_wide_records <- pro_records %>% 
  filter(CLOSE_DEF_DIST <= 4.92)
no_wide_records
# 出手球員總命中率
n_field_goal_per <- no_wide_records %>% 
  group_by(player_name) %>% 
  mutate(total_FGM = sum(FGM), total_FG = n()) %>% 
  mutate(total_FGP = total_FGM/total_FG) %>% 
  ungroup()
# 出手球員與個別防守球員相對時之命中率
n_pair_def_records <- n_field_goal_per %>% 
  select(defender_name, player_name, FGM, total_FGP) %>% 
  group_by(defender_name, player_name, total_FGP) %>% 
  summarise(def_FGM = sum(FGM), def_FG = n()) %>% 
  mutate(def_FGP = def_FGM/def_FG) %>% 
  ungroup()
n_pair_def_records
# 個別組合間的防守效益差距
# 挑出相遇次數的大於 3 次的紀錄
# 挑出差異大於 25% 的組合
n_first_team_records <- n_pair_def_records %>% 
  filter(defender_name == "kawhi leonard" | 
         defender_name == "draymond green" |
         defender_name == "deandre jordan" |
         defender_name == "tony allen" |
         defender_name == "chris paul") %>% 
  filter(def_FG >= 3) %>% 
  mutate(dif_FGP = def_FGP - total_FGP) %>% 
  filter(abs(dif_FGP) >= 0.25) %>% 
  select(defender_name, player_name, dif_FGP)
n_first_team_records
# 建立網路關係
n_first_team_Network <- graph_from_data_frame(d=n_first_team_records, directed=T)
# 畫出網路圖
# 防守影響較大的關係:綠色
# 防守影響較小的關係:紅色
set.seed(31)
E(n_first_team_Network)$color <- ifelse(E(n_first_team_Network)$dif_FGP < 0 , "lightgreen", "palevioletred")
plot(n_first_team_Network, vertex.size=2, edge.arrow.size=0.3, vertex.label.cex=0.7)

# 個別組合間的防守效益差距
# 挑出相遇次數的大於 3 次的紀錄
# 挑出差異大於 25% 的組合
n_second_team_records <- n_pair_def_records %>% 
  filter(defender_name == "anthony davis" | 
         defender_name == "tim duncan" |
         defender_name == "andrew bogut" |
         defender_name == "jimmy butler" |
         defender_name == "john wall") %>% 
  filter(def_FG >= 3) %>% 
  mutate(dif_FGP = def_FGP - total_FGP) %>% 
  filter(abs(dif_FGP) >= 0.25) %>% 
  select(defender_name, player_name, dif_FGP)
n_second_team_records
# 建立網路關係
n_second_team_Network <- graph_from_data_frame(d=n_second_team_records, directed=T)
# 畫出網路圖
# 防守影響較大的關係:綠色
# 防守影響較小的關係:紅色
set.seed(2019)
E(n_second_team_Network)$color <- ifelse(E(n_second_team_Network)$dif_FGP < 0 , "lightgreen", "palevioletred")
plot(n_second_team_Network, vertex.size=2, edge.arrow.size=0.3, vertex.label.cex=0.7)

LS0tCnRpdGxlOiAiTkJBIgphdXRob3I6ICLpmbPnkKjnv5QiCmRhdGU6ICIyMDE5LzYvNCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoamllYmFSKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHRpZHl0ZXh0KQpsaWJyYXJ5KGlncmFwaCkKbGlicmFyeSh0b3BpY21vZGVscykKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoanNvbmxpdGUpCmBgYAoKPiDos4fmlpnkvobmupDvvJprYWdnbGXkuIrnmoROQkEgc2hvdCBsb2dz6LOH5paZ6ZuGPGJyPgo+IGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGFuc2JlY2tlci9uYmEtc2hvdC1sb2dzCgojIOiugOWFpeizh+aWmQoKYGBge3J9CiMg6K6A5YWl6LOH5paZCnJlY29yZHMgPSByZWFkX2Nzdigic2hvdF9sb2dzLmNzdiIpCnJlY29yZHMKYGBgCj4g5pyD55m854++IENMT1NFU1RfREVGRU5ERVIg55qE5ZCN5a2X6IiHIHBsYXllcl9uYW1lIOeahOagvOW8j+S4jeS4gOaoo++8jOWboOatpOaIkeWAkeWFiOWwh+agvOW8j+e1seS4gOOAggoKYGBge3J9CiMg5Ye65omL55CD5ZOh55qEaWToiIduYW1lCnBsYXllcl9saXN0IDwtIHJlY29yZHMgJT4lIHNlbGVjdChwbGF5ZXJfbmFtZSwgcGxheWVyX2lkKSAlPiUgdW5pcXVlKCkgJT4lIHJlbmFtZShuYW1lID0gcGxheWVyX25hbWUpCgojIOe1seS4gOWnk+WQjeagvOW8jwpwcm9fcmVjb3JkcyA8LSByZWNvcmRzICU+JSAKICBzZXBhcmF0ZShDTE9TRVNUX0RFRkVOREVSLCBjKCJmaXJzdF9uYW1lIiwgImxhc3RfbmFtZSIpLCBmaWxsID0gIndhcm4iLCBzZXAgPSAiLCAiKSAlPiUgCiAgdW5pdGUoImRlZmVuZGVyX25hbWUiLCBsYXN0X25hbWUsIGZpcnN0X25hbWUsIHNlcCA9IiAiKSAlPiUKICBsZWZ0X2pvaW4ocGxheWVyX2xpc3QsIGJ5ID0gYygiQ0xPU0VTVF9ERUZFTkRFUl9QTEFZRVJfSUQiID0gInBsYXllcl9pZCIpKSAlPiUgCiAgbXV0YXRlKGRlZmVuZGVyX25hbWUgPSBpZmVsc2UoIWlzLm5hKG5hbWUpLCBuYW1lLCB0b2xvd2VyKGRlZmVuZGVyX25hbWUpKSkgJT4lIAogIHNlbGVjdCgtbmFtZSkKcHJvX3JlY29yZHMKYGBgCgpgYGB7cn0KIyDmjJHlh7pkZWZlbmRlcl9uYW1lKOmYsuWuiOiAheWQjeWtlynjgIFwbGF5ZXJfbmFtZSjlh7rmiYvogIXlkI3lrZcp44CBTUFUQ0hVUCjmr5Tos73os4foqIopIOS4ieWAi+ashOS9jQpsaW5rIDwtIHByb19yZWNvcmRzICU+JSAKICBzZWxlY3QoZGVmZW5kZXJfbmFtZSwgcGxheWVyX25hbWUsIE1BVENIVVApCiMg5bu656uL57ay6Lev6Zec5L+CCmRlZl9OZXR3b3JrIDwtIGdyYXBoX2Zyb21fZGF0YV9mcmFtZShkPWxpbmssIGRpcmVjdGVkPVQpCiMg55Wr5Ye657ay6Lev5ZyWCnBsb3QoZGVmX05ldHdvcmssIHZlcnRleC5zaXplPTIsIGVkZ2UuYXJyb3cuc2l6ZT0wLjUsIHZlcnRleC5sYWJlbC5jZXg9MC44KQpgYGAKCiMg5oyR5Ye65pyJ6Kmy5bm05bqm5Yag6LuN6YeR5bee5YuH5aOr55qE55CD5ZOh5Ye65omL57SA6YyECj4g6Kmy5bm05bqm57i95Yag6LuN6ZqK5LyN54K66YeR5bee5YuH5aOr77yM5oiR5YCR5bCH5YW255CD5ZOh55qE5Ye65omL57SA6YyE6IiH5bCN5Yiw55qE6Ziy5a6I55CD5ZOh5oyR5Ye65L6G5YGa5YiG5p6Q44CCCgpgYGB7cn0Kd2FycmlvcnMgPC0gcHJvX3JlY29yZHMgJT4lIAogIGZpbHRlcihzdHJfZGV0ZWN0KE1BVENIVVAsICJHU1cgIikpICU+JSAKICBhZGRfY291bnQoZGVmZW5kZXJfbmFtZSwgcGxheWVyX25hbWUsIG5hbWUgPSAiZF9GRyIpICU+JSAKICBncm91cF9ieShkZWZlbmRlcl9uYW1lLCBwbGF5ZXJfbmFtZSwgZF9GRykgJT4lIAogIHN1bW1hcmlzZShkX0ZHTSA9IHN1bShGR00pKSAlPiUgCiAgZmlsdGVyKGRfRkcgPj0gMTApICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIG11dGF0ZShkX0ZHUCA9IGRfRkdNL2RfRkcpCndhcnJpb3JzCmBgYAoKYGBge3J9CiMg5bu656uL57ay6Lev6Zec5L+CCndhc19OZXR3b3JrIDwtIGdyYXBoX2Zyb21fZGF0YV9mcmFtZShkPXdhcnJpb3JzLCBkaXJlY3RlZD1UKQojIOeVq+WHuue2sui3r+WclgojIOe3mueahOeyl+e0sOS7o+ihqOmYsuWuiOWIsOeahOasoeaVuOWkmuWvoQojIOe2oOe3muihqOekuumYsuWuiOaViOebiuWEquaWvOW5s+WdhwojIOe0hee3muihqOekuumYsuWuiOaViOebiuW3ruaWvOW5s+WdhwpzZXQuc2VlZCgyMzEpCkUod2FzX05ldHdvcmspJGNvbG9yIDwtIGlmZWxzZShFKHdhc19OZXR3b3JrKSRkX0ZHUCA8IG1lYW4oRSh3YXNfTmV0d29yaykkZF9GR1ApICwgImxpZ2h0Z3JlZW4iLCAicGFsZXZpb2xldHJlZCIpCkUod2FzX05ldHdvcmspJHdpZHRoIDwtIChFKHdhc19OZXR3b3JrKSRkX0ZHLzEwKV4xLjUKcGxvdCh3YXNfTmV0d29yaywgdmVydGV4LnNpemU9MiwgZWRnZS5hcnJvdy5zaXplPTAuMSwgdmVydGV4LmxhYmVsLmNleD0wLjcpCmBgYAo+IOWLh+Wjq+aVmee3tEtlcnLlnKjoqK3oqIjmiLDooZPmmYLvvIznkIPlk6Hlj6/ku6XlpJrmjJHpmLLlrojmlYjnm4rkuI3kvbPnmoTnkIPlk6HlgZrllq7miZPvvIzoi6XlsI3kuIrpmLLlrojmlYjnm4rlpb3nmoTnkIPlk6HliYflj6/pgI/pgY7lgrPlsI7ljJbop6PjgIIKCiMjIOevqemBuOWHuue4veWHuuaJi+asoeaVuOWkp+aWvOS4gOWumuasoeaVuOeahOe0gOmMhAo+IOmBuOWHuuaJgOacieeQg+WToeS4ree4veWHuuaJi+asoeaVuOWkp+aWvDk0MOeahOeQg+WToe+8jOebuOWwjeWFtuS7lueQg+WToeWPr+ihqOekuuS7luWAkeWcqOWgtOS4iuaZgumWk+abtOWkmu+8jOeCuumaiuS4iueahOS4u+WKm+i8uOWHuuOAggoKYGBge3J9CiMg56+p6YG45Ye657i95Ye65omL5qyh5pW45aSn5pa8OTQw55qE57SA6YyECiMg5oyR5Ye655u46YGH5qyh5pW45aSn5pa8IDE1IOasoeeahOe0gOmMhApjb3VudF9yZWNvcmRzIDwtIHByb19yZWNvcmRzICU+JSAKICBhZGRfY291bnQocGxheWVyX25hbWUsIG5hbWUgPSAicF9jb3VudCIpICU+JSAKICBmaWx0ZXIocF9jb3VudCA+PSA5NDApICU+JSAKICBhZGRfY291bnQoZGVmZW5kZXJfbmFtZSwgcGxheWVyX25hbWUsIG5hbWUgPSAiZF9GRyIpICU+JSAKICBncm91cF9ieShkZWZlbmRlcl9uYW1lLCBwbGF5ZXJfbmFtZSwgZF9GRykgJT4lIAogIHN1bW1hcmlzZShkX0ZHTSA9IHN1bShGR00pKSAlPiUgCiAgZmlsdGVyKGRfRkcgPj0gMTUpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIG11dGF0ZShkX0ZHUCA9IGRfRkdNL2RfRkcpCmNvdW50X3JlY29yZHMKYGBgCgpgYGB7cn0KIyDlu7rnq4vntrLot6/pl5zkv4IKY291bnRfTmV0d29yayA8LSBncmFwaF9mcm9tX2RhdGFfZnJhbWUoZD1jb3VudF9yZWNvcmRzLCBkaXJlY3RlZD1UKQojIOeVq+WHuue2sui3r+WclgojIOe3mueahOeyl+e0sOS7o+ihqOmYsuWuiOWIsOeahOasoeaVuOWkmuWvoQojIOe2oOe3muihqOekuumYsuWuiOaViOebiuWEquaWvOW5s+WdhwojIOe0hee3muihqOekuumYsuWuiOaViOebiuW3ruaWvOW5s+WdhwpzZXQuc2VlZCgxMjEzKQpFKGNvdW50X05ldHdvcmspJGNvbG9yIDwtIGlmZWxzZShFKGNvdW50X05ldHdvcmspJGRfRkdQIDwgbWVhbihFKGNvdW50X05ldHdvcmspJGRfRkdQKSAsICJsaWdodGdyZWVuIiwgInBhbGV2aW9sZXRyZWQiKQpFKGNvdW50X05ldHdvcmspJHdpZHRoIDwtIEUoY291bnRfTmV0d29yaykkZF9GRy8xMApwbG90KGNvdW50X05ldHdvcmssIHZlcnRleC5zaXplPTIsIGVkZ2UuYXJyb3cuc2l6ZT0wLjIsIHZlcnRleC5sYWJlbC5jZXg9MC43KQpgYGAKIVtdKGNvdW50X3Jlc3VsdC5wbmcpCgojIOmYsuWuiOaViOebiuavlOi8gwo+IOmAj+mBjuioiOeul+WHuuS4gOWQjeWHuuaJi+eQg+WToeeahOe4veWRveS4reeOh++8jOiIh+WAi+WIpeeQg+WToemYsuWuiOaZgueahOWRveS4reeOh+W3ruWAvO+8jDxicj4KPiDkvobllq7njajnnIvmr4/kvY3pmLLlrojnkIPlk6HlnKjlsI3kuIrlk6rkvY3lh7rmiYvnkIPlk6HmmYLvvIzmnInovIPlpKfnmoTpmLLlrojmlYjnm4rjgIIKCmBgYHtyfQojIOWHuuaJi+eQg+WToee4veWRveS4reeOhwpmaWVsZF9nb2FsX3BlciA8LSBwcm9fcmVjb3JkcyAlPiUgCiAgZ3JvdXBfYnkocGxheWVyX25hbWUpICU+JSAKICBtdXRhdGUodG90YWxfRkdNID0gc3VtKEZHTSksIHRvdGFsX0ZHID0gbigpKSAlPiUgCiAgbXV0YXRlKHRvdGFsX0ZHUCA9IHRvdGFsX0ZHTS90b3RhbF9GRykgJT4lIAogIHVuZ3JvdXAoKQpmaWVsZF9nb2FsX3BlcgpgYGAKCmBgYHtyfQojIOWHuuaJi+eQg+WToeiIh+WAi+WIpemYsuWuiOeQg+WToeebuOWwjeaZguS5i+WRveS4reeOhwpwYWlyX2RlZl9yZWNvcmRzIDwtIGZpZWxkX2dvYWxfcGVyICU+JSAKICBzZWxlY3QoZGVmZW5kZXJfbmFtZSwgcGxheWVyX25hbWUsIEZHTSwgdG90YWxfRkdQKSAlPiUgCiAgZ3JvdXBfYnkoZGVmZW5kZXJfbmFtZSwgcGxheWVyX25hbWUsIHRvdGFsX0ZHUCkgJT4lIAogIHN1bW1hcmlzZShkZWZfRkdNID0gc3VtKEZHTSksIGRlZl9GRyA9IG4oKSkgJT4lIAogIG11dGF0ZShkZWZfRkdQID0gZGVmX0ZHTS9kZWZfRkcpICU+JSAKICB1bmdyb3VwKCkKcGFpcl9kZWZfcmVjb3JkcwpgYGAKCmBgYHtyfQojIOWAi+WIpee1hOWQiOmWk+eahOmYsuWuiOaViOebiuW3rui3nQojIOaMkeWHuuebuOmBh+asoeaVuOWkp+aWvCA4IOasoeeahOe0gOmMhAojIOaMkeWHuuW3rueVsOWkp+aWvCAzMCUg55qE57WE5ZCICnNfcGFpcl9kZWZfcmVjb3JkcyA8LSBwYWlyX2RlZl9yZWNvcmRzICU+JSAKICBmaWx0ZXIoZGVmX0ZHID49IDgpICU+JSAKICBtdXRhdGUoZGlmX0ZHUCA9IGRlZl9GR1AgLSB0b3RhbF9GR1ApICU+JSAKICBmaWx0ZXIoYWJzKGRpZl9GR1ApID49IDAuMykgJT4lIAogIHNlbGVjdChkZWZlbmRlcl9uYW1lLCBwbGF5ZXJfbmFtZSwgZGlmX0ZHUCkKc19wYWlyX2RlZl9yZWNvcmRzCmBgYAoKYGBge3J9CiMg5bu656uL57ay6Lev6Zec5L+CCnNfcGFpcl9kZWZfTmV0d29yayA8LSBncmFwaF9mcm9tX2RhdGFfZnJhbWUoZD1zX3BhaXJfZGVmX3JlY29yZHMsIGRpcmVjdGVkPVQpCiMg55Wr5Ye657ay6Lev5ZyWCiMg6Ziy5a6I5b2x6Z+/6LyD5aSn55qE6Zec5L+C77ya57ag6ImyCiMg6Ziy5a6I5b2x6Z+/6LyD5bCP55qE6Zec5L+C77ya57SF6ImyCnNldC5zZWVkKDEyMzQpCkUoc19wYWlyX2RlZl9OZXR3b3JrKSRjb2xvciA8LSBpZmVsc2UoRShzX3BhaXJfZGVmX05ldHdvcmspJGRpZl9GR1AgPCAwICwgImxpZ2h0Z3JlZW4iLCAicGFsZXZpb2xldHJlZCIpCnBsb3Qoc19wYWlyX2RlZl9OZXR3b3JrLCB2ZXJ0ZXguc2l6ZT0yLCBlZGdlLmFycm93LnNpemU9MC4zLCB2ZXJ0ZXgubGFiZWwuY2V4PTAuNykKYGBgCgohW10ocmVzdWx0MS5wbmcpCgojIOW5tOW6pumYsuWuiOmaiuS8jQoKPiBOQkHmr4/lubTpg73mnIPmipXnpajpgbjlh7rpmLLlrojnrKzkuIDpmoroiIfnrKzkuozpmorvvIzlvp7nkIPloLTkuIrnmoTlkITlgIvkvY3nva7pgbjmnIDpganlkIjnmoTkurrpgbjjgII8YnI+Cj4g6YCZ6KOh5oiR5YCR5om+5Ye6MjAxNC0xNeizveWto++8jOmYsuWuiOesrOS4gOmaiuiIh+esrOS6jOmaiueahOWQjeWWru+8jDxicj4KPiDpgI/pgY7lnKjlkI3llq7kuIrlkITnkIPlk6HnmoTpmLLlrojkuIvvvIzlh7rmiYvnkIPlk6Hlkb3kuK3njofnmoTlt67liKXkvobpqZforYnpgJnku73lkI3llq7jgIIKCiMjIOW5tOW6pumYsuWuiOesrOS4gOmaigo8aW1nIHNyYz0iLi9maXJzdF90ZWFtLnBuZyIgd2lkdGg9IjQwJSIgLz4KCmBgYHtyfQojIOWAi+WIpee1hOWQiOmWk+eahOmYsuWuiOaViOebiuW3rui3nQojIOaMkeWHuuebuOmBh+asoeaVuOeahOWkp+aWvCAzIOasoeeahOe0gOmMhAojIOaMkeWHuuW3rueVsOWkp+aWvCAyNSUg55qE57WE5ZCICmZpcnN0X3RlYW1fcmVjb3JkcyA8LSBwYWlyX2RlZl9yZWNvcmRzICU+JSAKICBmaWx0ZXIoZGVmZW5kZXJfbmFtZSA9PSAia2F3aGkgbGVvbmFyZCIgfCAKICAgICAgICAgZGVmZW5kZXJfbmFtZSA9PSAiZHJheW1vbmQgZ3JlZW4iIHwKICAgICAgICAgZGVmZW5kZXJfbmFtZSA9PSAiZGVhbmRyZSBqb3JkYW4iIHwKICAgICAgICAgZGVmZW5kZXJfbmFtZSA9PSAidG9ueSBhbGxlbiIgfAogICAgICAgICBkZWZlbmRlcl9uYW1lID09ICJjaHJpcyBwYXVsIikgJT4lIAogIGZpbHRlcihkZWZfRkcgPj0gMykgJT4lIAogIG11dGF0ZShkaWZfRkdQID0gZGVmX0ZHUCAtIHRvdGFsX0ZHUCkgJT4lIAogIGZpbHRlcihhYnMoZGlmX0ZHUCkgPj0gMC4yNSkgJT4lIAogIHNlbGVjdChkZWZlbmRlcl9uYW1lLCBwbGF5ZXJfbmFtZSwgZGlmX0ZHUCkKZmlyc3RfdGVhbV9yZWNvcmRzCmBgYAoKYGBge3J9CiMg5bu656uL57ay6Lev6Zec5L+CCmZpcnN0X3RlYW1fTmV0d29yayA8LSBncmFwaF9mcm9tX2RhdGFfZnJhbWUoZD1maXJzdF90ZWFtX3JlY29yZHMsIGRpcmVjdGVkPVQpCiMg55Wr5Ye657ay6Lev5ZyWCiMg6Ziy5a6I5b2x6Z+/6LyD5aSn55qE6Zec5L+C77ya57ag6ImyCiMg6Ziy5a6I5b2x6Z+/6LyD5bCP55qE6Zec5L+C77ya57SF6ImyCnNldC5zZWVkKDMxKQpFKGZpcnN0X3RlYW1fTmV0d29yaykkY29sb3IgPC0gaWZlbHNlKEUoZmlyc3RfdGVhbV9OZXR3b3JrKSRkaWZfRkdQIDwgMCAsICJsaWdodGdyZWVuIiwgInBhbGV2aW9sZXRyZWQiKQpwbG90KGZpcnN0X3RlYW1fTmV0d29yaywgdmVydGV4LnNpemU9MiwgZWRnZS5hcnJvdy5zaXplPTAuMywgdmVydGV4LmxhYmVsLmNleD0wLjcpCmBgYAohW10oZmlyc3RfdGVhbV9yZXN1bHQucG5nKQoKIyMg5bm05bqm6Ziy5a6I56ys5LqM6ZqKCjxpbWcgc3JjPSIuL3NlY29uZF90ZWFtLnBuZyIgd2lkdGg9IjQwJSIgLz4KCmBgYHtyfQojIOWAi+WIpee1hOWQiOmWk+eahOmYsuWuiOaViOebiuW3rui3nQojIOaMkeWHuuebuOmBh+asoeaVuOeahOWkp+aWvCAzIOasoeeahOe0gOmMhAojIOaMkeWHuuW3rueVsOWkp+aWvCAyNSUg55qE57WE5ZCICnNlY29uZF90ZWFtX3JlY29yZHMgPC0gcGFpcl9kZWZfcmVjb3JkcyAlPiUgCiAgZmlsdGVyKGRlZmVuZGVyX25hbWUgPT0gImFudGhvbnkgZGF2aXMiIHwgCiAgICAgICAgIGRlZmVuZGVyX25hbWUgPT0gInRpbSBkdW5jYW4iIHwKICAgICAgICAgZGVmZW5kZXJfbmFtZSA9PSAiYW5kcmV3IGJvZ3V0IiB8CiAgICAgICAgIGRlZmVuZGVyX25hbWUgPT0gImppbW15IGJ1dGxlciIgfAogICAgICAgICBkZWZlbmRlcl9uYW1lID09ICJqb2huIHdhbGwiKSAlPiUgCiAgZmlsdGVyKGRlZl9GRyA+PSAzKSAlPiUgCiAgbXV0YXRlKGRpZl9GR1AgPSBkZWZfRkdQIC0gdG90YWxfRkdQKSAlPiUgCiAgZmlsdGVyKGFicyhkaWZfRkdQKSA+PSAwLjI1KSAlPiUgCiAgc2VsZWN0KGRlZmVuZGVyX25hbWUsIHBsYXllcl9uYW1lLCBkaWZfRkdQKQpzZWNvbmRfdGVhbV9yZWNvcmRzCmBgYAoKYGBge3J9CiMg5bu656uL57ay6Lev6Zec5L+CCnNlY29uZF90ZWFtX05ldHdvcmsgPC0gZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKGQ9c2Vjb25kX3RlYW1fcmVjb3JkcywgZGlyZWN0ZWQ9VCkKIyDnlavlh7rntrLot6/lnJYKIyDpmLLlrojlvbHpn7/ovIPlpKfnmoTpl5zkv4LvvJrntqDoibIKIyDpmLLlrojlvbHpn7/ovIPlsI/nmoTpl5zkv4LvvJrntIXoibIKc2V0LnNlZWQoMTIzNCkKRShzZWNvbmRfdGVhbV9OZXR3b3JrKSRjb2xvciA8LSBpZmVsc2UoRShzZWNvbmRfdGVhbV9OZXR3b3JrKSRkaWZfRkdQIDwgMCAsICJsaWdodGdyZWVuIiwgInBhbGV2aW9sZXRyZWQiKQpwbG90KHNlY29uZF90ZWFtX05ldHdvcmssIHZlcnRleC5zaXplPTIsIGVkZ2UuYXJyb3cuc2l6ZT0wLjMsIHZlcnRleC5sYWJlbC5jZXg9MC43KQpgYGAKIVtdKHNlY29uZF90ZWFtX3Jlc3VsdC5wbmcpCgojIyDljrvpmaRXaWRlIE9wZW7ni4DmhYvkuIvnmoTos4fmlpkKPiDkvobmupDos4fmlpnkuK3mmK/mr4/kuIDnrYbnmoTlh7rmiYvntIDpjITvvIzmlYXlhbbkuK3nmoTpmLLlrojnkIPlk6Hlj6rmmK/ot53pm6Llh7rmiYvnkIPlk6HmnIDov5HnmoTnkIPlk6HvvIw8YnI+Cj4g5L2G55CD5aC05LiK5Y+v6IO96YCP6YGO5Y2h5L2N44CB56m65YiH44CB5YKz55CD562J5omL5q615L6G6LeR5Ye656m65qqU77yM5q2k5pmC5Ye65omL55CD5ZOh5pyD6IiH6Ziy5a6I55CD5ZOh5ouJ6ZaL5LiA5q616Led6Zui77yMPGJyPgo+IOatpOaZgumYsuWuiOeQg+WToeaYr+iqsOeahOaEj+e+qeS+v+S4jeacg+mCo+m6vOWkp++8jOWboOatpOaIkeWAkemAj+mBjuaVuOaTmuS4reeahOacgOi/kemYsuWuiOeQg+WToei3nemboueCuuS+neaTmu+8jDxicj4KPiDlsIfot53pm6LotoXpgY4gNC45MmZlZXQoPTEuNeWFrOWwuikg55qE57SA6YyE5Y676Zmk77yM5YaN55yL5Ymb5Ymb55qE5bm05bqm6Ziy5a6I56ys5LiA6ZqK5ZKM56ys5LqM6ZqK55qE57SA6YyE44CCCgpgYGB7cn0KIyDmjJHlh7roiIfpmLLlrojogIXot53pm6LlsI/mlrwgNC45MmZlZXQg55qE57SA6YyECm5vX3dpZGVfcmVjb3JkcyA8LSBwcm9fcmVjb3JkcyAlPiUgCiAgZmlsdGVyKENMT1NFX0RFRl9ESVNUIDw9IDQuOTIpCm5vX3dpZGVfcmVjb3JkcwpgYGAKCmBgYHtyfQojIOWHuuaJi+eQg+WToee4veWRveS4reeOhwpuX2ZpZWxkX2dvYWxfcGVyIDwtIG5vX3dpZGVfcmVjb3JkcyAlPiUgCiAgZ3JvdXBfYnkocGxheWVyX25hbWUpICU+JSAKICBtdXRhdGUodG90YWxfRkdNID0gc3VtKEZHTSksIHRvdGFsX0ZHID0gbigpKSAlPiUgCiAgbXV0YXRlKHRvdGFsX0ZHUCA9IHRvdGFsX0ZHTS90b3RhbF9GRykgJT4lIAogIHVuZ3JvdXAoKQoKIyDlh7rmiYvnkIPlk6HoiIflgIvliKXpmLLlrojnkIPlk6Hnm7jlsI3mmYLkuYvlkb3kuK3njocKbl9wYWlyX2RlZl9yZWNvcmRzIDwtIG5fZmllbGRfZ29hbF9wZXIgJT4lIAogIHNlbGVjdChkZWZlbmRlcl9uYW1lLCBwbGF5ZXJfbmFtZSwgRkdNLCB0b3RhbF9GR1ApICU+JSAKICBncm91cF9ieShkZWZlbmRlcl9uYW1lLCBwbGF5ZXJfbmFtZSwgdG90YWxfRkdQKSAlPiUgCiAgc3VtbWFyaXNlKGRlZl9GR00gPSBzdW0oRkdNKSwgZGVmX0ZHID0gbigpKSAlPiUgCiAgbXV0YXRlKGRlZl9GR1AgPSBkZWZfRkdNL2RlZl9GRykgJT4lIAogIHVuZ3JvdXAoKQpuX3BhaXJfZGVmX3JlY29yZHMKYGBgCgpgYGB7cn0KIyDlgIvliKXntYTlkIjplpPnmoTpmLLlrojmlYjnm4rlt67ot50KIyDmjJHlh7rnm7jpgYfmrKHmlbjnmoTlpKfmlrwgMyDmrKHnmoTntIDpjIQKIyDmjJHlh7rlt67nlbDlpKfmlrwgMjUlIOeahOe1hOWQiApuX2ZpcnN0X3RlYW1fcmVjb3JkcyA8LSBuX3BhaXJfZGVmX3JlY29yZHMgJT4lIAogIGZpbHRlcihkZWZlbmRlcl9uYW1lID09ICJrYXdoaSBsZW9uYXJkIiB8IAogICAgICAgICBkZWZlbmRlcl9uYW1lID09ICJkcmF5bW9uZCBncmVlbiIgfAogICAgICAgICBkZWZlbmRlcl9uYW1lID09ICJkZWFuZHJlIGpvcmRhbiIgfAogICAgICAgICBkZWZlbmRlcl9uYW1lID09ICJ0b255IGFsbGVuIiB8CiAgICAgICAgIGRlZmVuZGVyX25hbWUgPT0gImNocmlzIHBhdWwiKSAlPiUgCiAgZmlsdGVyKGRlZl9GRyA+PSAzKSAlPiUgCiAgbXV0YXRlKGRpZl9GR1AgPSBkZWZfRkdQIC0gdG90YWxfRkdQKSAlPiUgCiAgZmlsdGVyKGFicyhkaWZfRkdQKSA+PSAwLjI1KSAlPiUgCiAgc2VsZWN0KGRlZmVuZGVyX25hbWUsIHBsYXllcl9uYW1lLCBkaWZfRkdQKQpuX2ZpcnN0X3RlYW1fcmVjb3JkcwpgYGAKCmBgYHtyfQojIOW7uueri+e2sui3r+mXnOS/ggpuX2ZpcnN0X3RlYW1fTmV0d29yayA8LSBncmFwaF9mcm9tX2RhdGFfZnJhbWUoZD1uX2ZpcnN0X3RlYW1fcmVjb3JkcywgZGlyZWN0ZWQ9VCkKIyDnlavlh7rntrLot6/lnJYKIyDpmLLlrojlvbHpn7/ovIPlpKfnmoTpl5zkv4LvvJrntqDoibIKIyDpmLLlrojlvbHpn7/ovIPlsI/nmoTpl5zkv4LvvJrntIXoibIKc2V0LnNlZWQoMzEpCkUobl9maXJzdF90ZWFtX05ldHdvcmspJGNvbG9yIDwtIGlmZWxzZShFKG5fZmlyc3RfdGVhbV9OZXR3b3JrKSRkaWZfRkdQIDwgMCAsICJsaWdodGdyZWVuIiwgInBhbGV2aW9sZXRyZWQiKQpwbG90KG5fZmlyc3RfdGVhbV9OZXR3b3JrLCB2ZXJ0ZXguc2l6ZT0yLCBlZGdlLmFycm93LnNpemU9MC4zLCB2ZXJ0ZXgubGFiZWwuY2V4PTAuNykKYGBgCiFbXShmaXJzdF90ZWFtX3Jlc3VsdDIucG5nKQoKYGBge3J9CiMg5YCL5Yil57WE5ZCI6ZaT55qE6Ziy5a6I5pWI55uK5beu6LedCiMg5oyR5Ye655u46YGH5qyh5pW455qE5aSn5pa8IDMg5qyh55qE57SA6YyECiMg5oyR5Ye65beu55Ww5aSn5pa8IDI1JSDnmoTntYTlkIgKbl9zZWNvbmRfdGVhbV9yZWNvcmRzIDwtIG5fcGFpcl9kZWZfcmVjb3JkcyAlPiUgCiAgZmlsdGVyKGRlZmVuZGVyX25hbWUgPT0gImFudGhvbnkgZGF2aXMiIHwgCiAgICAgICAgIGRlZmVuZGVyX25hbWUgPT0gInRpbSBkdW5jYW4iIHwKICAgICAgICAgZGVmZW5kZXJfbmFtZSA9PSAiYW5kcmV3IGJvZ3V0IiB8CiAgICAgICAgIGRlZmVuZGVyX25hbWUgPT0gImppbW15IGJ1dGxlciIgfAogICAgICAgICBkZWZlbmRlcl9uYW1lID09ICJqb2huIHdhbGwiKSAlPiUgCiAgZmlsdGVyKGRlZl9GRyA+PSAzKSAlPiUgCiAgbXV0YXRlKGRpZl9GR1AgPSBkZWZfRkdQIC0gdG90YWxfRkdQKSAlPiUgCiAgZmlsdGVyKGFicyhkaWZfRkdQKSA+PSAwLjI1KSAlPiUgCiAgc2VsZWN0KGRlZmVuZGVyX25hbWUsIHBsYXllcl9uYW1lLCBkaWZfRkdQKQpuX3NlY29uZF90ZWFtX3JlY29yZHMKYGBgCgpgYGB7cn0KIyDlu7rnq4vntrLot6/pl5zkv4IKbl9zZWNvbmRfdGVhbV9OZXR3b3JrIDwtIGdyYXBoX2Zyb21fZGF0YV9mcmFtZShkPW5fc2Vjb25kX3RlYW1fcmVjb3JkcywgZGlyZWN0ZWQ9VCkKIyDnlavlh7rntrLot6/lnJYKIyDpmLLlrojlvbHpn7/ovIPlpKfnmoTpl5zkv4LvvJrntqDoibIKIyDpmLLlrojlvbHpn7/ovIPlsI/nmoTpl5zkv4LvvJrntIXoibIKc2V0LnNlZWQoMjAxOSkKRShuX3NlY29uZF90ZWFtX05ldHdvcmspJGNvbG9yIDwtIGlmZWxzZShFKG5fc2Vjb25kX3RlYW1fTmV0d29yaykkZGlmX0ZHUCA8IDAgLCAibGlnaHRncmVlbiIsICJwYWxldmlvbGV0cmVkIikKcGxvdChuX3NlY29uZF90ZWFtX05ldHdvcmssIHZlcnRleC5zaXplPTIsIGVkZ2UuYXJyb3cuc2l6ZT0wLjMsIHZlcnRleC5sYWJlbC5jZXg9MC43KQpgYGAKIVtdKHNlY29uZF90ZWFtX3Jlc3VsdDIucG5nKQoKCg==