library(data.table)
data.table 1.10.4
  The fastest way to learn (by data.table authors): https://www.datacamp.com/courses/data-analysis-the-data-table-way
  Documentation: ?data.table, example(data.table) and browseVignettes("data.table")
  Release notes, videos and slides: http://r-datatable.com
library(dplyr)
-----------------------------------------------------------------------------------------------------------------------------
data.table + dplyr code now lives in dtplyr.
Please library(dtplyr)!
-----------------------------------------------------------------------------------------------------------------------------

Attaching package: 'dplyr'

The following objects are masked from 'package:data.table':

    between, first, last

The following objects are masked from 'package:stats':

    filter, lag

The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
library(magrittr)
library(tidyr)

Attaching package: 'tidyr'

The following object is masked from 'package:magrittr':

    extract
library(stringi)
library(stringr)
library(xda)
library(caret)
Loading required package: lattice
Loading required package: ggplot2
library(doMC)
Loading required package: foreach
foreach: simple, scalable parallel programming from Revolution Analytics
Use Revolution R for scalability, fault tolerance and more.
http://www.revolutionanalytics.com
Loading required package: iterators
Loading required package: parallel
library(dummy)
dummy 0.1.3
dummyNews()
library(leaflet)
library(rgdal)
package 'rgdal' was built under R version 3.4.1Loading required package: sp
package 'sp' was built under R version 3.4.1rgdal: version: 1.2-8, (SVN revision 663)
 Geospatial Data Abstraction Library extensions to R successfully loaded
 Loaded GDAL runtime: GDAL 2.1.3, released 2017/20/01
 Path to GDAL shared files: /Users/CA/Library/R/3.4/library/rgdal/gdal
 Loaded PROJ.4 runtime: Rel. 4.9.3, 15 August 2016, [PJ_VERSION: 493]
 Path to PROJ.4 shared files: /Users/CA/Library/R/3.4/library/rgdal/proj
 Linking to sp version: 1.2-4 
library(geojsonio)
package 'geojsonio' was built under R version 3.4.1
Attaching package: 'geojsonio'

The following object is masked from 'package:base':

    pretty
library(lubridate)

Attaching package: 'lubridate'

The following objects are masked from 'package:data.table':

    hour, isoweek, mday, minute, month, quarter, second, wday, week, yday, year

The following object is masked from 'package:base':

    date
library(geosphere)

Attaching package: 'geosphere'

The following object is masked from 'package:geojsonio':

    centroid
library(gridExtra)

Attaching package: 'gridExtra'

The following object is masked from 'package:dplyr':

    combine
library(ggmap)
Google Maps API Terms of Service: http://developers.google.com/maps/terms.
Please cite ggmap if you use it: see citation('ggmap') for details.

Attaching package: 'ggmap'

The following object is masked from 'package:magrittr':

    inset
library(geohash)
package 'geohash' was built under R version 3.4.1
train <- fread("/Users/CA/Downloads/NY_taxi/train.csv", na.strings = "")
test <- fread("/Users/CA/Downloads/NY_taxi/test.csv", na.strings = "")
numSummary(train)
charSummary(train)
coord2distance <- Vectorize(function(lat1, lng1, lat2, lng2) {
  rad_per_deg = pi / 180
  rkm = 6371
  rm = rkm * 1000
  
  dlat_rad = (lat2 - lat1) * rad_per_deg
  dlng_rad = (lng2 - lng1) * rad_per_deg
  
  lat1_rad = lat1 * rad_per_deg
  lng1_rad = lng1 * rad_per_deg
  lat2_rad = lat2 * rad_per_deg
  lng2_rad = lng2 * rad_per_deg
  
  a = sin(dlat_rad/2)**2 + cos(lat1_rad) * cos(lat2_rad) * sin(dlng_rad/2)**2
  c = 2 * atan2(sqrt(a), sqrt(1-a))
  
  return(rm * c)
})
## 시간 데이터( String -> DateTime ), 출도착 직선거리(meter), 직선거리 기준 속도(km/h) 추가 
## train data 의 기간은 1월 ~ 6월까지 총 6개월간의 데이터이다. 
train %<>%
  mutate(pickup_datetime   = ymd_hms(pickup_datetime), 
         dropoff_datetime  = ymd_hms(dropoff_datetime),
         distance          = coord2distance( pickup_longitude, pickup_latitude, dropoff_longitude, dropoff_latitude),
         speed             = round(distance / 1000 / (trip_duration/3600),2)) %>%
  mutate_each_(funs(as.double(.)), c("pickup_longitude", "pickup_latitude", "dropoff_longitude", "dropoff_latitude"))
head(train)
## vendor : a code indicating the provider associated with the trip record
## Q1 : 사업구역? #=> 지역별 사업구역은 별도로 확인되고 있지 않음. 
table(train$vendor_id)

     1      2 
678342 780302 
train$vendor_id <- factor(train$vendor_id)
qmplot(pickup_longitude, pickup_latitude, data = sample_frac(train,0.0001), colour = vendor_id,
       zoom = 12)
Map from URL : http://tile.stamen.com/toner-lite/12/1205/1538.png
Map from URL : http://tile.stamen.com/toner-lite/12/1206/1538.png
Map from URL : http://tile.stamen.com/toner-lite/12/1207/1538.png
Map from URL : http://tile.stamen.com/toner-lite/12/1208/1538.png
Map from URL : http://tile.stamen.com/toner-lite/12/1205/1539.png
Map from URL : http://tile.stamen.com/toner-lite/12/1206/1539.png
Map from URL : http://tile.stamen.com/toner-lite/12/1207/1539.png
Map from URL : http://tile.stamen.com/toner-lite/12/1208/1539.png
Map from URL : http://tile.stamen.com/toner-lite/12/1205/1540.png
Map from URL : http://tile.stamen.com/toner-lite/12/1206/1540.png
Map from URL : http://tile.stamen.com/toner-lite/12/1207/1540.png
Map from URL : http://tile.stamen.com/toner-lite/12/1208/1540.png
Map from URL : http://tile.stamen.com/toner-lite/12/1205/1541.png
Map from URL : http://tile.stamen.com/toner-lite/12/1206/1541.png
Map from URL : http://tile.stamen.com/toner-lite/12/1207/1541.png
Map from URL : http://tile.stamen.com/toner-lite/12/1208/1541.png
`panel.margin` is deprecated. Please use `panel.spacing` property instead

# pickup_datetime : date and time when the meter was engaged
train %<>%
  mutate(pickup_wday = wday(pickup_datetime, label = T), pickup_hour = hour(pickup_datetime)) %>%
  mutate(dropoff_wday = wday(dropoff_datetime, label = T), dropoff_hour = hour(dropoff_datetime))
## Q1. 요일별 pickup 수 분포     #=> 월요일이 제일 적음 ( 의외! , 휴일이라도 끼어 있나 ? ) 
## Q2. 시간대별 pickup 수 분포   #=> 출근보다 퇴근시간에 더 많음  
table(train$pickup_wday)

   Sun    Mon   Tues    Wed  Thurs    Fri    Sat 
195366 187418 202749 210136 218574 223533 220868 
table(train$pickup_hour)

    0     1     2     3     4     5     6     7     8     9    10    11    12    13    14    15    16    17    18    19 
53248 38571 27972 20895 15792 15002 33248 55600 67053 67663 65437 68476 71873 71473 74292 71811 64313 76483 90600 90308 
   20    21    22    23 
84072 84185 80492 69785 
p1 <- ggplot(data = train, mapping = aes(x = pickup_wday, group = vendor_id, fill = vendor_id)) + 
  geom_bar(position="identity", alpha=0.7) + 
  theme_bw()
p2 <- ggplot(data = train, mapping = aes(x = pickup_hour, group = vendor_id, fill = vendor_id)) + 
  geom_bar(position="identity", alpha=0.7) + 
  theme_bw()
grid.arrange(p1, p2, nrow = 2, ncol = 1)

## Q3. 요일/시간대별 평균 속도 
##      #=> 요일별 평균속도는 월/일/토 , 화/금 , 수/목 순으로 좋음 
##      #=> 시간별 평균속도는 퇴근시간대보다 일과시간이 나쁘고, 새벽시간대가 제일 좋음 
## Q4. 요일/시간대별 평균 운행 직선 거리 
##      #=> 요일별 평균 운행 직선 거리는 월/일 이 상대적으로 길고, 기타 요일에는 대동소이 
##      #=> 시간별 평균 운행 직선 거리는 평균속도 패턴과 동일.
avg_speed_by_wday    <- train %>% group_by(pickup_wday) %>% summarise(avg_speed_by_wday = mean(speed))
avg_distance_by_wday <- train %>% group_by(pickup_wday) %>% summarise(avg_distance_by_wday = mean(distance))
avg_speed_by_hour    <- train %>% group_by(pickup_hour) %>% summarise(avg_speed_by_hour = mean(speed))
avg_distance_by_hour <- train %>% group_by(pickup_hour) %>% summarise(avg_distance_by_hour = mean(distance))
head(merge(avg_speed_by_wday, avg_distance_by_wday, by = "pickup_wday") %>% arrange(desc(avg_distance_by_wday)), n = 7)
head(merge(avg_speed_by_hour, avg_distance_by_hour, by = "pickup_hour") %>% arrange(desc(avg_speed_by_hour)), n = 24)
hourly_data <- merge(avg_speed_by_hour, avg_distance_by_hour, by = "pickup_hour") 
#p1 <- ggplot(data = hourly_data, aes(pickup_hour, avg_speed_by_hour)) + geom_line() + geom_point()
#p2 <- ggplot(data = hourly_data, aes(pickup_hour, avg_distance_by_hour)) + geom_line() + geom_point()
#grid.arrange(p1, p2, nrow = 2, ncol = 1)
ggplot(data = hourly_data, aes(x = pickup_hour)) +
  geom_line(aes(y = avg_speed_by_hour, colour = "speed")) +
  geom_line(aes(y = round(avg_distance_by_hour/250 ,2), colour = "distance")) +
  scale_y_continuous(sec.axis = sec_axis(~.*250, name = "Distance ( meter )")) +
  labs(y = "Speed ( km/h )")

# passenger count 에 따른 속도 변화 
# A1 : 합승자가 많으면, 경유지 증가에 따라 거리 증가 및 평균 속도 감소가 있을 것 이다.
table(train$passenger_count)

      0       1       2       3       4       5       6       7       8       9 
     60 1033540  210318   59896   28404   78088   48333       3       1       1 
train$passenger_count <- as.numeric(as.character(train$passenger_count))
avg_speed_by_pc    <- train %>% 
                        filter(passenger_count %in% c(1,2,3,4,5,6)) %>% 
                        group_by(passenger_count) %>% 
                        summarise(avg_speed = mean(speed), avg_distnace = mean(distance))
ggplot(data = avg_speed_by_pc, aes(x = passenger_count)) +
  geom_line(aes(y = avg_speed, colour = "speed")) +
  geom_line(aes(y = round(avg_distnace/260 ,2), colour = "distance")) +
  scale_y_continuous(sec.axis = sec_axis(~.*260, name = "Distance ( meter )")) +
  labs(y = "Speed ( km/h )")

nrow(routes_5)                       #=> 860,132 개
[1] 2214
nrow(routes_5 %>% distinct(pickup))  #=> 9,238 개 
[1] 263
nrow(routes_5 %>% distinct(dropoff)) #=> 21,959 개
[1] 369
route_freq <- as.data.frame(table(train$pickup_geohash_5, train$dropoff_geohash_5))
Warning message:
In scan(file = file, what = what, sep = sep, quote = quote, dec = dec,  :
  EOF within quoted string
route_freq %<>% filter(Freq > 0)
library(plotly)
route_spec <- train %>% 
                group_by(pickup_geohash_5, dropoff_geohash_5) %>%
                summarise(count = n(), avg_speed = mean(speed), avg_distnace = mean(distance)) %>%
                filter(count > 0 & avg_speed > 1 & avg_speed < 70 )
plot_ly(x = route_spec$pickup_geohash_5, y = route_spec$dropoff_geohash_5, z = route_spec$count, type = "heatmap", colorscale = "Greys")

plot_ly(x = route_spec$pickup_geohash_5, y = route_spec$dropoff_geohash_5, z = route_spec$avg_speed, type = "heatmap", colorscale = "Greys")

plot_ly(x = route_spec$pickup_geohash_5, y = route_spec$dropoff_geohash_5, z = route_spec$avg_distnace, type = "heatmap", colorscale = "Greys")

plot_ly(x = route_spec$pickup_geohash_5, y = route_spec$dropoff_geohash_5, z = log(route_spec$count * route_spec$avg_distnace), type = "heatmap", colorscale = "Greys")
# 자.. 그전에 distance 들을 좀 살펴보자. 
par(mfrow = c(1,3))
Warning messages:
1: In scan(file = file, what = what, sep = sep, quote = quote, dec = dec,  :
  EOF within quoted string
2: In scan(file = file, what = what, sep = sep, quote = quote, dec = dec,  :
  EOF within quoted string
plot(density(train$distance))         # 굉장히 심한 L-Shape
plot(density(log(train$distance)))    # log-transform
plot(density(log(train$distance+10))) # +10 씩 움직여주고, log-transform

summary(train$distance)               # 851 km 짜리 직선거리도 있다. ( 명백한 outlier ) , 역시 야들도 GPS 가 튄다.
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      0     848    1531    2876    2847  851968 
# GPS 가 튀는 것들은 거리와 상관없이 발생할텐데, 851km 는 거리로 잘 보인 케이스일뿐이다.
# 이런 outlier 들은 평균 속도가 이상한 것들로 잡아보자 
summary(train$speed)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      0       6      10      11      15    8619 
nrow(filter(train, speed > 60))       
[1] 932
# 932 건이다. 이렇게 그냥 평균 속도로 짜를 것인지, 
# 출도착지의 시간대별 평균 속도에 기반하여 솎아낼지는 판단해야한다... 귀찮다;; 
# 위와는 조금 다르게, GPS 가 튀어서 거리가 매우 작게 잡히는 경우도 있다. 
nrow(train %>% filter(distance < 50 | trip_duration > 3000 ))
[1] 35795
# 위치데이터 자체로는 이상값판별이 안되므로, 시간, 속도 등을 기반으로 다 걸러내야 한다. 
plot(train$distance, train$trip_duration)

plot(train$distance, train$speed)

remove_outliers <- function(df, col1, col2) {
  library(ldbod)
  scores <- ldbod(subset(df, select = c(col1, col2)), k = 50)
  head(scores$lrd); head(scores$rkof)
  
  s <- subset(df, select = c(col1, col2))
  plot(s)
  top50outliers <- s[order(scores$lpde,decreasing=FALSE)[1:50],]
  points(top50outliers,col=2)
  
  df <- df[!(as.matrix(df[col1]) %in% as.matrix(top50outliers[col1]) & as.matrix(df[col2]) %in% as.matrix(top50outliers[col2])),]
  
  return(df)
}
# package 'ldbod' : Flexible procedures to compute local density-based outlier scores for ranking outliers
# 이번 케이스는, 밀도기반의 outlier 탐지가 잘 동작할 것으로 기대해봄. 
# 계산량이 많아, parallel 이 지원되어야 함. 
# 몇가지 lof 관련 package 가 있지만, ldbod 가 제일 좋은 결과를 보여줌.. 하지만 속도가 망 
# train <- remove_outliers(train, "speed", "distance")
# train <- remove_outliers(train, "trip_duration", "distance")
spd_dist_time <- subset(train, select = c(speed, distance, trip_duration))
registerDoMC(cores = 4)
sample_train <- sample_frac(train, 0.1)
#sample_train <- rbind(sample_train, train %>% filter(speed > 100))
sample_train <- remove_outliers(sample_train, 'speed', 'distance')

sample_train <- remove_outliers(sample_train, 'trip_duration', 'distance')

sample_train <- remove_outliers(sample_train, 'trip_duration', 'speed')

library(MVN)
sROC 0.1-2 loaded
#res <- mvOutlier(spd_dist_time, label = F, position = 4)
library(Rlof)
Loading required package: doParallel
#sdt <- sample_frac(spd_dist_time, 0.01)
#sdt <- rbind(sdt, spd_dist_time %>% filter(speed > 100))
#sdt <- cbind(sdt,lof(sdt, c(1:5)))
#head(sdt %>% arrange(desc(`1`)), n = 100)
library(DMwR)
Loading required package: grid
#sdt <- sample_frac(spd_dist_time, 0.01)
#sdt <- rbind(sdt, spd_dist_time %>% filter(speed > 100))
#sdt %<>%
#  mutate(speed = round(speed / 10, 0)) %>%
#  mutate(distance = round(distance / 1000, 0)) 
#sdt <- cbind(sdt, lofactor(subset(sdt, select = c(speed, distance)), k = 10))
# density based 로 outlier 를 탐지하는 것의 약점은, normal case 도 제거가능하다는 것이긴하다......
# 일단 lof 로 제거된 후, 다시 distance~duration / distance~speed 상태를 보자 
par(mfrow = c(1,2))
plot(sample_train$distance, sample_train$trip_duration)
plot(sample_train$distance, sample_train$speed)

# distance ~ duration 그래프의 좌상단 과 distance ~ speed 에서 speed ~ 0 의 일직선 부분이 density based 로 못잡은
# outlier 들이다. 이들은 특정패턴 ( 출도착지 좌표는 거의 변화가 없는 케이스 ) 들이 조밀하게 모여 있어서 그런것이다. 
# 즉 특정패턴의 이상현상이 일정 빈도이상 발생하게 되면, density based 로는 잡을 수 없다.
# 이런 녀석들은 rule 로 잡아줘야 할 것 같다. 
# 아니면, density 를 먹이기 전에, 미리 특정 density 이상의 패턴을 갖는 outlier 들을 제거한 후, density 를 먹여도 된다.
# ( 음.. 그러니까.. 다시해야한다.. )
# 사설이 좀 길어졌지만, 일단은 거리, 운행시간, 속도를 각각 최대/최소 기준값으로 1차 filtering 을 한다.
quantile(train$speed, 0.999)          #=> 56 km/h
99.9% 
 56.9 
quantile(train$distance, 0.9999)      #=> 45 km
 100% 
45676 
quantile(train$trip_duration, 0.999)  #=> 85000 seconds
99.9% 
85128 
train %<>% filter(
  speed < quantile(train$speed, 0.999) & 
  distance < quantile(train$distance, 0.9999) & 
  trip_duration < quantile(train$trip_duration, 0.999)
)
par(mfrow = c(1,2))
plot(train$distance, train$trip_duration)
plot(train$distance, train$speed)
# [todo] 일단 geojson 으로 pickup 위치를 변환하여, map 에 올려놓고 대충 파악해본다. ( geojson 은 일단 너무 느리다.. R 에서.. 내가 못하는 것일 수도 있지만  )
#pickups <- sample_frac(train, 0.001)
#coordinates(pickups) = c("pickup_latitude", "pickup_longitude")
#class(pickups)
#writeOGR(pickups, "/Users/CA/pickup_locations.geojson", layer = "train", driver = "GeoJSON")
#pickups_geojson <- rgdal::readOGR("/Users/CA/pickup_locations.geojson", "OGRGeoJSON")
#plot(pickups_geojson)
library(leaflet)
Warning messages:
1: In scan(file = file, what = what, sep = sep, quote = quote, dec = dec,  :
  EOF within quoted string
2: In scan(file = file, what = what, sep = sep, quote = quote, dec = dec,  :
  EOF within quoted string
3: In scan(file = file, what = what, sep = sep, quote = quote, dec = dec,  :
  EOF within quoted string
pickups <- sample_frac(train, 0.001)
leaflet(data = pickups, width = "100%") %>%
  setView(lng = -73.9821548461914, lat = 40.767936706543, zoom = 12) %>%
  addTiles() %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addMarkers(lng = ~pickup_longitude, lat = ~pickup_latitude, clusterOptions = markerClusterOptions())
#library(tmap)
#library(tmaptools)
#library(sp)

#pickups <- sample_frac(train, 0.001)
#coordinates(pickups) <- ~ pickup_longitude + pickup_latitude
#pickups              <- set_projection(pickups, current.projection = "longlat")#, projection = 27700)

#tmap_mode("plot") # plot | view
#qtm(pickups)

#pickups_osm <- read_osm(pickups)
#qtm(pickups_osm) + qtm(pickups, symbols.col = "red", symbols.size = 0.01)
# netcount 를 중심으로, 도심 업무지구, 교외 거주지구 등을 구분해본다. 
pickup_count <- train %>% group_by(pickup_geohash_5) %>% summarise(pc = n())
dropoff_count <- train %>% group_by(dropoff_geohash_5) %>% summarise(dc = n())
colnames(pickup_count) <- c("geohash", "pc")
colnames(dropoff_count) <- c("geohash", "dc")
m <- merge(pickup_count, dropoff_count, by = "geohash", all = T) %>% replace_na(list(pc = 0, dc = 0)) 
m %<>% mutate(net_count = pc - dc, total = pc + dc)
plot(mm$total, mm$net_count)
abline(v=median(mm$total), h=median(mm$net_count))

# 속도
# outgoing, incoming, internal 을 구분해서 살펴본다. 
train %<>% mutate(is_internal = pickup_geohash_5 == dropoff_geohash_5)
internal_route <- train %>% filter(is_internal == T)
internal_spec <- internal_route %>% 
  group_by(pickup_geohash_5, pickup_hour) %>% 
  summarise(count = n(), avg_speed = mean(speed), avg_distance = mean(distance))
# internal speed  
boxplot(avg_speed ~ pickup_hour, data = internal_spec %>% filter(count > 1000))

# intersection speed 
intersection_spec <- train %>% 
  filter(is_internal == F) %>%
  group_by(pickup_geohash_5, dropoff_geohash_5) %>%
  summarise(count = n(), avg_speed = median(speed), avg_distance = median(distance))
plot_ly(x = intersection_spec$pickup_geohash_5, 
        y = intersection_spec$dropoff_geohash_5, 
        z = intersection_spec$avg_speed, 
        type = "heatmap", colorscale = "Viridis")
outgoing_spec <- train %>% 
  filter(is_internal == F) %>%
  group_by(pickup_geohash_5) %>%
  summarise(count = n(), avg_speed = median(speed), avg_distance = median(distance))
plot(outgoing_spec$avg_distance, outgoing_spec$avg_speed)

incoming_spec <- train %>% 
  filter(is_internal == F) %>%
  group_by(dropoff_geohash_5) %>%
  summarise(count = n(), avg_speed = median(speed), avg_distance = median(distance))
plot(incoming_spec$avg_distance, incoming_spec$avg_speed)

# try#1
# 경로별 데이터 프레임을 만든다. 
# 경로별 데이터 프레임은 geohash 기준 5, 6, 7 레벨에 대해 각각 만든다. 
# 이들 frame 에서 추가되는 feature 들에는, 경로별 평균 시간, 속도, 거리 와 최빈 운행 시간대 등이다. 
# trip duration 은 각 경로 data frame 에 의해 예측된 값의 mean 을 취한다. 
# baseline 은 요일/시간 + 출도착지별 평균시간 예측값의 RMSE 
routes_lvl_5 <- train %>% 
  subset(select = c("pickup_geohash_5", "dropoff_geohash_5", "route5", 
                    "pickup_datetime", 
                    "passenger_count", 
                    "store_and_fwd_flag",
                    "trip_duration", "distance", "speed")) %>%
  mutate(wday = wday(pickup_datetime, label = T), hour = hour(pickup_datetime), month = month(pickup_datetime)) 
routes_lvl_5.monthly <- routes_lvl_5 %>% 
  group_by(month, route5) %>%
  summarise(count = n(), avg_speed = mean(speed), avg_duration = mean(trip_duration), avg_distance = mean(distance))
# 누적 월 운행수 1,000 건 이상인 경로들에서는 월별로 운행시간 차이가 크지 않음, 
# 이들 경로에서의 운행발생건수는 전체의 86.3% 정도 
boxplot(avg_speed ~ route5, data = routes_lvl_5.monthly %>% filter(count > 1000))

sum((routes_lvl_5.monthly %>% filter(count >= 1000))$count) * 1.0 / sum(routes_lvl_5.monthly$count)
[1] 0.8637101
# 누적 월 운행수 100 ~ 1000 건 경로들에서는 다소 fluctuation 이 있음. 당연;;
boxplot(avg_speed ~ route5, data = routes_lvl_5.monthly %>% filter(count < 1000 & count > 100))

# 월운행건수 평균 1000건 이상 경로들에 대해서만 자세히 살펴보자 ( 핵심 경로 )
core_routes <- routes_lvl_5.monthly %>% group_by(route5) %>% summarise(avg_count = mean(count)) %>% filter(avg_count > 1000)
core_routes_stat <- routes_lvl_5 %>% filter(route5 %in% core_routes$route5)
nrow(core_routes_stat) / nrow(routes_lvl_5) * 1.0 # 86% 
[1] 0.8634319
boxplot(speed ~ route5, data = core_routes_stat)

core_routes_stat %<>% 
  mutate(time_window = ifelse(hour > 22 | hour < 7, "dawn", ifelse(hour>12, "afternoon", "morning")))
ggplot(core_routes_stat, aes(route5, speed)) +
  geom_boxplot(outlier.alpha = 0.1, outlier.colour = "red", aes(colour = time_window)) +
  coord_flip() 

NA
ggplot(core_routes_stat %>% filter(month == 1 & hour == 12), aes(route5, speed)) +
  geom_boxplot(outlier.alpha = 0.1, outlier.colour = "red") +
  coord_flip() 

routes_lvl_5 %>% 
  group_by(route5) %>%
  summarise(sd_speed = sd(speed), sd_distance = sd(distance), count = n()) %>%
  filter(count > 1) -> route5_sd
View(route5_sd)
# dr5r4 에서 dr5r4 로 가는 운행건들은 모두 출도착 좌표가 동일, speed , distance 모두 0, trip_duration 만 있음;;
# 이런 케이스의 경우에는, 아래 density plot 의 모양을 갖는 분포에서 sampling 하는 수밖에 없어 보임 
head(train %>% filter(pickup_geohash_5 == "dr5r4" & dropoff_geohash_5 == "dr5r4"))
plot(density((train %>% filter(pickup_geohash_5 == "dr5r4" & dropoff_geohash_5 == "dr5r4"))$trip_duration))

par(mfrow = c(1,2))
plot(density(route5_sd$avg_distance))
plot(density(route5_sd$avg_speed))

routes_lvl_5 %>%
  filter(pickup_geohash_5 != dropoff_geohash_5) %>%
  group_by(route5, round(hour/3,0)) %>%
  summarise(count = n()) %>% 
  group_by(route5) %>% 
  top_n(1, count) -> route5.most_freq_hour
routes_lvl_5 %>% group_by(route5) %>% summarise(total_count = n()) -> tmp
route5.most_freq_hour %<>% 
  merge(tmp, by = "route5") %>%
  mutate(raito = count * 1.0 / total_count)
colnames(route5.most_freq_hour) <- c("route5", "time_group", "count", "total_count", "ratio")
route5.most_freq_hour %<>% mutate(time_group = paste(3*time_group, "~", 3*(time_group+1)-1))
geo_tokens <- strsplit(route5.most_freq_hour$route5, ">")
route5.most_freq_hour$start = unlist(geo_tokens)[2*(1:nrow(route5.most_freq_hour))-1]
route5.most_freq_hour$end   = unlist(geo_tokens)[2*(1:nrow(route5.most_freq_hour))]
head(route5.most_freq_hour)
table(
  (route5.most_freq_hour %>% filter(start == "dr5ru", total_count > 100))$time_group
)

  0 ~ 2 15 ~ 17 18 ~ 20 21 ~ 23   3 ~ 5  9 ~ 11 
      1       7       2      23       3       1 
route5.most_freq_hour %>% 
  filter(total_count > 50) %>%
  group_by(start, time_group) %>%
  summarise(time_group_count = n()) %>%
  group_by(start) %>%
  top_n(1, time_group_count) -> start_spec
route5.most_freq_hour %>% filter(total_count > 50) %>% group_by(start) %>% summarise(count = n()) -> start_spec_2
start_spec %<>% merge(start_spec_2, by = "start") %>% mutate(ratio = time_group_count * 1.0 / count)
# 특정 지역에서 다른 지역으로 나가는 시간대를 Grouping 하여, 
# 주로 발생되는 시간대를 보면 저녁시간 , 새벽시간 , 아침 시간 으로 나뉜다.
# 이 중 전형적으로 특정시간대에 운행이 집중되는 지역들이 있는데,
# 저녁시간대면 업무지구, 새벽시간대면 유흥지구, 아침시간대면 거주지구 로 추측해 볼 수 있을 것 같다.
head(start_spec %>% arrange(desc(count)), n = 20)
LS0tCnRpdGxlOiAiTmV3IFlvcmsgQ2l0eSBUYXhpIFRyaXAgRHVyYXRpb24iCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyfQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoc3RyaW5naSkKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KHhkYSkKbGlicmFyeShjYXJldCkKbGlicmFyeShkb01DKQpsaWJyYXJ5KGR1bW15KQpsaWJyYXJ5KGxlYWZsZXQpCmxpYnJhcnkocmdkYWwpCmxpYnJhcnkoZ2VvanNvbmlvKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShnZW9zcGhlcmUpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KGdnbWFwKQpsaWJyYXJ5KGdlb2hhc2gpCmBgYAoKYGBge3J9CnRyYWluIDwtIGZyZWFkKCIvVXNlcnMvQ0EvRG93bmxvYWRzL05ZX3RheGkvdHJhaW4uY3N2IiwgbmEuc3RyaW5ncyA9ICIiKQp0ZXN0IDwtIGZyZWFkKCIvVXNlcnMvQ0EvRG93bmxvYWRzL05ZX3RheGkvdGVzdC5jc3YiLCBuYS5zdHJpbmdzID0gIiIpCmBgYAoKYGBge3J9Cm51bVN1bW1hcnkodHJhaW4pCmNoYXJTdW1tYXJ5KHRyYWluKQpgYGAKCmBgYHtyfQpjb29yZDJkaXN0YW5jZSA8LSBWZWN0b3JpemUoZnVuY3Rpb24obGF0MSwgbG5nMSwgbGF0MiwgbG5nMikgewogIHJhZF9wZXJfZGVnID0gcGkgLyAxODAKICBya20gPSA2MzcxCiAgcm0gPSBya20gKiAxMDAwCiAgCiAgZGxhdF9yYWQgPSAobGF0MiAtIGxhdDEpICogcmFkX3Blcl9kZWcKICBkbG5nX3JhZCA9IChsbmcyIC0gbG5nMSkgKiByYWRfcGVyX2RlZwogIAogIGxhdDFfcmFkID0gbGF0MSAqIHJhZF9wZXJfZGVnCiAgbG5nMV9yYWQgPSBsbmcxICogcmFkX3Blcl9kZWcKICBsYXQyX3JhZCA9IGxhdDIgKiByYWRfcGVyX2RlZwogIGxuZzJfcmFkID0gbG5nMiAqIHJhZF9wZXJfZGVnCiAgCiAgYSA9IHNpbihkbGF0X3JhZC8yKSoqMiArIGNvcyhsYXQxX3JhZCkgKiBjb3MobGF0Ml9yYWQpICogc2luKGRsbmdfcmFkLzIpKioyCiAgYyA9IDIgKiBhdGFuMihzcXJ0KGEpLCBzcXJ0KDEtYSkpCiAgCiAgcmV0dXJuKHJtICogYykKfSkKYGBgCgoKYGBge3J9CiMjIOyLnOqwhCDrjbDsnbTthLAoIFN0cmluZyAtPiBEYXRlVGltZSApLCDstpzrj4TssKkg7KeB7ISg6rGw66asKG1ldGVyKSwg7KeB7ISg6rGw66asIOq4sOykgCDsho3rj4Qoa20vaCkg7LaU6rCAIAojIyB0cmFpbiBkYXRhIOydmCDquLDqsITsnYAgMeyblCB+IDbsm5TquYzsp4Ag7LSdIDbqsJzsm5TqsITsnZgg642w7J207YSw7J2064ukLiAKdHJhaW4gJTw+JQogIG11dGF0ZShwaWNrdXBfZGF0ZXRpbWUgICA9IHltZF9obXMocGlja3VwX2RhdGV0aW1lKSwgCiAgICAgICAgIGRyb3BvZmZfZGF0ZXRpbWUgID0geW1kX2htcyhkcm9wb2ZmX2RhdGV0aW1lKSwKICAgICAgICAgZGlzdGFuY2UgICAgICAgICAgPSBjb29yZDJkaXN0YW5jZSggcGlja3VwX2xvbmdpdHVkZSwgcGlja3VwX2xhdGl0dWRlLCBkcm9wb2ZmX2xvbmdpdHVkZSwgZHJvcG9mZl9sYXRpdHVkZSksCiAgICAgICAgIHNwZWVkICAgICAgICAgICAgID0gcm91bmQoZGlzdGFuY2UgLyAxMDAwIC8gKHRyaXBfZHVyYXRpb24vMzYwMCksMikpICU+JQogIG11dGF0ZV9lYWNoXyhmdW5zKGFzLmRvdWJsZSguKSksIGMoInBpY2t1cF9sb25naXR1ZGUiLCAicGlja3VwX2xhdGl0dWRlIiwgImRyb3BvZmZfbG9uZ2l0dWRlIiwgImRyb3BvZmZfbGF0aXR1ZGUiKSkKCmhlYWQodHJhaW4pCmBgYAoKCmBgYHtyfQojIyB2ZW5kb3IgOiBhIGNvZGUgaW5kaWNhdGluZyB0aGUgcHJvdmlkZXIgYXNzb2NpYXRlZCB3aXRoIHRoZSB0cmlwIHJlY29yZAojIyBRMSA6IOyCrOyXheq1rOyXrT8gIz0+IOyngOyXreuzhCDsgqzsl4Xqtazsl63snYAg67OE64+E66GcIO2ZleyduOuQmOqzoCDsnojsp4Ag7JWK7J2MLiAKdGFibGUodHJhaW4kdmVuZG9yX2lkKQp0cmFpbiR2ZW5kb3JfaWQgPC0gZmFjdG9yKHRyYWluJHZlbmRvcl9pZCkKCnFtcGxvdChwaWNrdXBfbG9uZ2l0dWRlLCBwaWNrdXBfbGF0aXR1ZGUsIGRhdGEgPSBzYW1wbGVfZnJhYyh0cmFpbiwwLjAwMDEpLCBjb2xvdXIgPSB2ZW5kb3JfaWQsCiAgICAgICB6b29tID0gMTIpCmBgYAoKYGBge3J9CiMgcGlja3VwX2RhdGV0aW1lIDogZGF0ZSBhbmQgdGltZSB3aGVuIHRoZSBtZXRlciB3YXMgZW5nYWdlZAp0cmFpbiAlPD4lCiAgbXV0YXRlKHBpY2t1cF93ZGF5ID0gd2RheShwaWNrdXBfZGF0ZXRpbWUsIGxhYmVsID0gVCksIHBpY2t1cF9ob3VyID0gaG91cihwaWNrdXBfZGF0ZXRpbWUpKSAlPiUKICBtdXRhdGUoZHJvcG9mZl93ZGF5ID0gd2RheShkcm9wb2ZmX2RhdGV0aW1lLCBsYWJlbCA9IFQpLCBkcm9wb2ZmX2hvdXIgPSBob3VyKGRyb3BvZmZfZGF0ZXRpbWUpKQpgYGAKCmBgYHtyfQojIyBRMS4g7JqU7J2867OEIHBpY2t1cCDsiJgg67aE7Y+sICAgICAjPT4g7JuU7JqU7J287J20IOygnOydvCDsoIHsnYwgKCDsnZjsmbghICwg7Zy07J287J20652864+EIOuBvOyWtCDsnojrgpggPyApIAojIyBRMi4g7Iuc6rCE64yA67OEIHBpY2t1cCDsiJgg67aE7Y+sICAgIz0+IOy2nOq3vOuztOuLpCDth7Tqt7zsi5zqsITsl5Ag642UIOunjuydjCAgCgp0YWJsZSh0cmFpbiRwaWNrdXBfd2RheSkKdGFibGUodHJhaW4kcGlja3VwX2hvdXIpCgpwMSA8LSBnZ3Bsb3QoZGF0YSA9IHRyYWluLCBtYXBwaW5nID0gYWVzKHggPSBwaWNrdXBfd2RheSwgZ3JvdXAgPSB2ZW5kb3JfaWQsIGZpbGwgPSB2ZW5kb3JfaWQpKSArIAogIGdlb21fYmFyKHBvc2l0aW9uPSJpZGVudGl0eSIsIGFscGhhPTAuNykgKyAKICB0aGVtZV9idygpCnAyIDwtIGdncGxvdChkYXRhID0gdHJhaW4sIG1hcHBpbmcgPSBhZXMoeCA9IHBpY2t1cF9ob3VyLCBncm91cCA9IHZlbmRvcl9pZCwgZmlsbCA9IHZlbmRvcl9pZCkpICsgCiAgZ2VvbV9iYXIocG9zaXRpb249ImlkZW50aXR5IiwgYWxwaGE9MC43KSArIAogIHRoZW1lX2J3KCkKCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAyLCBuY29sID0gMSkKYGBgCgpgYGB7cn0KIyMgUTMuIOyalOydvC/si5zqsITrjIDrs4Qg7Y+J6regIOyGjeuPhCAKIyMgICAgICAjPT4g7JqU7J2867OEIO2Pieq3oOyGjeuPhOuKlCDsm5Qv7J28L+2GoCAsIO2ZlC/quIggLCDsiJgv66qpIOyInOycvOuhnCDsoovsnYwgCiMjICAgICAgIz0+IOyLnOqwhOuzhCDtj4nqt6Dsho3rj4TripQg7Ye06re87Iuc6rCE64yA67O064ukIOydvOqzvOyLnOqwhOydtCDrgpjsgZjqs6AsIOyDiOuyveyLnOqwhOuMgOqwgCDsoJzsnbwg7KKL7J2MIAojIyBRNC4g7JqU7J28L+yLnOqwhOuMgOuzhCDtj4nqt6Ag7Jq07ZaJIOyngeyEoCDqsbDrpqwgCiMjICAgICAgIz0+IOyalOydvOuzhCDtj4nqt6Ag7Jq07ZaJIOyngeyEoCDqsbDrpqzripQg7JuUL+ydvCDsnbQg7IOB64yA7KCB7Jy866GcIOq4uOqzoCwg6riw7YOAIOyalOydvOyXkOuKlCDrjIDrj5nshozsnbQgCiMjICAgICAgIz0+IOyLnOqwhOuzhCDtj4nqt6Ag7Jq07ZaJIOyngeyEoCDqsbDrpqzripQg7Y+J6reg7IaN64+EIO2MqO2EtOqzvCDrj5nsnbwuCgphdmdfc3BlZWRfYnlfd2RheSAgICA8LSB0cmFpbiAlPiUgZ3JvdXBfYnkocGlja3VwX3dkYXkpICU+JSBzdW1tYXJpc2UoYXZnX3NwZWVkX2J5X3dkYXkgPSBtZWFuKHNwZWVkKSkKYXZnX2Rpc3RhbmNlX2J5X3dkYXkgPC0gdHJhaW4gJT4lIGdyb3VwX2J5KHBpY2t1cF93ZGF5KSAlPiUgc3VtbWFyaXNlKGF2Z19kaXN0YW5jZV9ieV93ZGF5ID0gbWVhbihkaXN0YW5jZSkpCgphdmdfc3BlZWRfYnlfaG91ciAgICA8LSB0cmFpbiAlPiUgZ3JvdXBfYnkocGlja3VwX2hvdXIpICU+JSBzdW1tYXJpc2UoYXZnX3NwZWVkX2J5X2hvdXIgPSBtZWFuKHNwZWVkKSkKYXZnX2Rpc3RhbmNlX2J5X2hvdXIgPC0gdHJhaW4gJT4lIGdyb3VwX2J5KHBpY2t1cF9ob3VyKSAlPiUgc3VtbWFyaXNlKGF2Z19kaXN0YW5jZV9ieV9ob3VyID0gbWVhbihkaXN0YW5jZSkpCgpoZWFkKG1lcmdlKGF2Z19zcGVlZF9ieV93ZGF5LCBhdmdfZGlzdGFuY2VfYnlfd2RheSwgYnkgPSAicGlja3VwX3dkYXkiKSAlPiUgYXJyYW5nZShkZXNjKGF2Z19kaXN0YW5jZV9ieV93ZGF5KSksIG4gPSA3KQpoZWFkKG1lcmdlKGF2Z19zcGVlZF9ieV9ob3VyLCBhdmdfZGlzdGFuY2VfYnlfaG91ciwgYnkgPSAicGlja3VwX2hvdXIiKSAlPiUgYXJyYW5nZShkZXNjKGF2Z19zcGVlZF9ieV9ob3VyKSksIG4gPSAyNCkKYGBgCgpgYGB7cn0KaG91cmx5X2RhdGEgPC0gbWVyZ2UoYXZnX3NwZWVkX2J5X2hvdXIsIGF2Z19kaXN0YW5jZV9ieV9ob3VyLCBieSA9ICJwaWNrdXBfaG91ciIpIAoKI3AxIDwtIGdncGxvdChkYXRhID0gaG91cmx5X2RhdGEsIGFlcyhwaWNrdXBfaG91ciwgYXZnX3NwZWVkX2J5X2hvdXIpKSArIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludCgpCiNwMiA8LSBnZ3Bsb3QoZGF0YSA9IGhvdXJseV9kYXRhLCBhZXMocGlja3VwX2hvdXIsIGF2Z19kaXN0YW5jZV9ieV9ob3VyKSkgKyBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKQojZ3JpZC5hcnJhbmdlKHAxLCBwMiwgbnJvdyA9IDIsIG5jb2wgPSAxKQoKZ2dwbG90KGRhdGEgPSBob3VybHlfZGF0YSwgYWVzKHggPSBwaWNrdXBfaG91cikpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBhdmdfc3BlZWRfYnlfaG91ciwgY29sb3VyID0gInNwZWVkIikpICsKICBnZW9tX2xpbmUoYWVzKHkgPSByb3VuZChhdmdfZGlzdGFuY2VfYnlfaG91ci8yNTAgLDIpLCBjb2xvdXIgPSAiZGlzdGFuY2UiKSkgKwogIHNjYWxlX3lfY29udGludW91cyhzZWMuYXhpcyA9IHNlY19heGlzKH4uKjI1MCwgbmFtZSA9ICJEaXN0YW5jZSAoIG1ldGVyICkiKSkgKwogIGxhYnMoeSA9ICJTcGVlZCAoIGttL2ggKSIpCmBgYAoKYGBge3J9CiMgcGFzc2VuZ2VyIGNvdW50IOyXkCDrlLDrpbgg7IaN64+EIOuzgO2ZlCAKIyBBMSA6IO2VqeyKueyekOqwgCDrp47snLzrqbQsIOqyveycoOyngCDspp3qsIDsl5Ag65Sw6528IOqxsOumrCDspp3qsIAg67CPIO2Pieq3oCDsho3rj4Qg6rCQ7IaM6rCAIOyeiOydhCDqsoMg7J2064ukLgp0YWJsZSh0cmFpbiRwYXNzZW5nZXJfY291bnQpCnRyYWluJHBhc3Nlbmdlcl9jb3VudCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih0cmFpbiRwYXNzZW5nZXJfY291bnQpKQoKYXZnX3NwZWVkX2J5X3BjICAgIDwtIHRyYWluICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKHBhc3Nlbmdlcl9jb3VudCAlaW4lIGMoMSwyLDMsNCw1LDYpKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KHBhc3Nlbmdlcl9jb3VudCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UoYXZnX3NwZWVkID0gbWVhbihzcGVlZCksIGF2Z19kaXN0bmFjZSA9IG1lYW4oZGlzdGFuY2UpKQoKZ2dwbG90KGRhdGEgPSBhdmdfc3BlZWRfYnlfcGMsIGFlcyh4ID0gcGFzc2VuZ2VyX2NvdW50KSkgKwogIGdlb21fbGluZShhZXMoeSA9IGF2Z19zcGVlZCwgY29sb3VyID0gInNwZWVkIikpICsKICBnZW9tX2xpbmUoYWVzKHkgPSByb3VuZChhdmdfZGlzdG5hY2UvMjYwICwyKSwgY29sb3VyID0gImRpc3RhbmNlIikpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoc2VjLmF4aXMgPSBzZWNfYXhpcyh+LioyNjAsIG5hbWUgPSAiRGlzdGFuY2UgKCBtZXRlciApIikpICsKICBsYWJzKHkgPSAiU3BlZWQgKCBrbS9oICkiKQpgYGAKCmBgYHtyfQojIOqyveuhnCA6IOy2nOuPhOywqeyngCDsooztkZzrpbwgZ2VvaGFzaCDroZwg67CU6r+ULCBnZW9oYXNoIOq4sOykgOycvOuhnCDqsr3roZzrpbwg672R64qU64ukLiAKIyBwcmVjaXNpb24gbGV2ZWwgNyA6IDE1MG0gKiAxNTBtIAp0cmFpbiAlPD4lCiAgbXV0YXRlKHBpY2t1cF9nZW9oYXNoXzcgID0gZ2hfZW5jb2RlKHBpY2t1cF9sYXRpdHVkZSwgcGlja3VwX2xvbmdpdHVkZSwgcHJlY2lzaW9uID0gNykpICU+JQogIG11dGF0ZShkcm9wb2ZmX2dlb2hhc2hfNyA9IGdoX2VuY29kZShkcm9wb2ZmX2xhdGl0dWRlLCBkcm9wb2ZmX2xvbmdpdHVkZSwgcHJlY2lzaW9uID0gNykpICAlPiUKICBtdXRhdGUocGlja3VwX2dlb2hhc2hfNiA9IGdoX2VuY29kZShwaWNrdXBfbGF0aXR1ZGUsIHBpY2t1cF9sb25naXR1ZGUsIHByZWNpc2lvbiA9IDYpKSAgJT4lCiAgbXV0YXRlKGRyb3BvZmZfZ2VvaGFzaF82ID0gZ2hfZW5jb2RlKGRyb3BvZmZfbGF0aXR1ZGUsIGRyb3BvZmZfbG9uZ2l0dWRlLCBwcmVjaXNpb24gPSA2KSkgJT4lCiAgbXV0YXRlKHBpY2t1cF9nZW9oYXNoXzUgPSBnaF9lbmNvZGUocGlja3VwX2xhdGl0dWRlLCBwaWNrdXBfbG9uZ2l0dWRlLCBwcmVjaXNpb24gPSA1KSkgICU+JQogIG11dGF0ZShkcm9wb2ZmX2dlb2hhc2hfNSA9IGdoX2VuY29kZShkcm9wb2ZmX2xhdGl0dWRlLCBkcm9wb2ZmX2xvbmdpdHVkZSwgcHJlY2lzaW9uID0gNSkpICU+JQogIG11dGF0ZShyb3V0ZTcgPSBwYXN0ZShwaWNrdXBfZ2VvaGFzaF83LCBkcm9wb2ZmX2dlb2hhc2hfNywgc2VwID0gIj4iKSkgJT4lCiAgbXV0YXRlKHJvdXRlNiA9IHBhc3RlKHBpY2t1cF9nZW9oYXNoXzYsIGRyb3BvZmZfZ2VvaGFzaF82LCBzZXAgPSAiPiIpKSAlPiUKICBtdXRhdGUocm91dGU1ID0gcGFzdGUocGlja3VwX2dlb2hhc2hfNSwgZHJvcG9mZl9nZW9oYXNoXzUsIHNlcCA9ICI+IikpCgoKcm91dGVzXzcgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZSh0cmFpbiRwaWNrdXBfZ2VvaGFzaF83LCB0cmFpbiRkcm9wb2ZmX2dlb2hhc2hfNykpICU+JSBmaWx0ZXIoRnJlcSA+IDApCnJvdXRlc182IDwtIGFzLmRhdGEuZnJhbWUodGFibGUodHJhaW4kcGlja3VwX2dlb2hhc2hfNiwgdHJhaW4kZHJvcG9mZl9nZW9oYXNoXzYpKSAlPiUgZmlsdGVyKEZyZXEgPiAwKQpyb3V0ZXNfNSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKHRyYWluJHBpY2t1cF9nZW9oYXNoXzUsIHRyYWluJGRyb3BvZmZfZ2VvaGFzaF81KSkgJT4lIGZpbHRlcihGcmVxID4gMCkKY29sbmFtZXMocm91dGVzXzcpIDwtIGMoInBpY2t1cCIsICJkcm9wb2ZmIiwgIkZyZXEiKQpjb2xuYW1lcyhyb3V0ZXNfNikgPC0gYygicGlja3VwIiwgImRyb3BvZmYiLCAiRnJlcSIpCmNvbG5hbWVzKHJvdXRlc181KSA8LSBjKCJwaWNrdXAiLCAiZHJvcG9mZiIsICJGcmVxIikKCm5yb3cocm91dGVzXzYpICAgICAgICAgICAgICAgICAgICAgICAjPT4gNTUsNjA5IOqwnApucm93KHJvdXRlc182ICU+JSBkaXN0aW5jdChwaWNrdXApKSAgIz0+IDEsNDM2IOqwnCAKbnJvdyhyb3V0ZXNfNiAlPiUgZGlzdGluY3QoZHJvcG9mZikpICM9PiAyLDY3NSDqsJwKCm5yb3cocm91dGVzXzcpICAgICAgICAgICAgICAgICAgICAgICAjPT4gODYwLDEzMiDqsJwKbnJvdyhyb3V0ZXNfNyAlPiUgZGlzdGluY3QocGlja3VwKSkgICM9PiA5LDIzOCDqsJwgCm5yb3cocm91dGVzXzcgJT4lIGRpc3RpbmN0KGRyb3BvZmYpKSAjPT4gMjEsOTU5IOqwnAoKbnJvdyhyb3V0ZXNfNSkgICAgICAgICAgICAgICAgICAgICAgICM9PiAyLDIxNCDqsJwKbnJvdyhyb3V0ZXNfNSAlPiUgZGlzdGluY3QocGlja3VwKSkgICM9PiAyNjMg6rCcIApucm93KHJvdXRlc181ICU+JSBkaXN0aW5jdChkcm9wb2ZmKSkgIz0+IDM2OSDqsJwKCiMg7J6QLi4g7J207KCcIOusuOygnOulvCAKIyDstpzrsJzsp4AgMTUwMCDqsJzsmYAg64+E7LCp7KeAIDI3MDAg6rCc7J2YIO2KueyEseqzvAojIOqyveuhnCA1NSwwMDAg6rCc7JeQIOuMgO2VtOyEnCDsi5zqsIQs7JqU7J28LO2DkeyKueyekCzqsbDrpqwgZmVhdHVyZSDrpbwg6riw67CY7Jy866GcIOyGjOyalOyLnOqwhOydhCDsmIjsuKHtlZjripQg66qo642466GcIOygkeq3vOydhCDtlZjsnpAuIAojCiMga2FnZ2xlIGRpc2N1c3Npb24g7J2EIOuztOuptCwg7LaU6rCAIOuNsOydtO2EsCDsgqzsmqnrj4Qg6rCA64ql7ZWcIOqygyDqsJnri6QuIAojIOyYiOulvOuTpOyWtOyEnCBsYW5kbWFyayAvIHdoZWF0aGVyIC8gcmVhbHRpbWUgdHJhZmZpYyBzcGVlZAojIO2VmOyngOunjCwgb3JpZ2luYWwgTllDIGRhdGFzZXQgb24gQmlnUXVlcnkg66W8IO2Gte2VnCDsmrTtlonqsbTrs4Qg7KeB7KCRIOygleuztOuKlCDsgqzsmqntlaAg7IiYIOyXhuuLpC4gKCDsi6TsoJwg7Jq07ZaJIOqxsOumrCAsIOyalOq4iCAuLiApCiMgaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9jL255Yy10YXhpLXRyaXAtZHVyYXRpb24vZGlzY3Vzc2lvbi8zNjY5OQpgYGAKCmBgYHtyfQpyb3V0ZV9mcmVxIDwtIGFzLmRhdGEuZnJhbWUodGFibGUodHJhaW4kcGlja3VwX2dlb2hhc2hfNSwgdHJhaW4kZHJvcG9mZl9nZW9oYXNoXzUpKQpyb3V0ZV9mcmVxICU8PiUgZmlsdGVyKEZyZXEgPiAwKQpsaWJyYXJ5KHBsb3RseSkKCgoKcm91dGVfc3BlYyA8LSB0cmFpbiAlPiUgCiAgICAgICAgICAgICAgICBncm91cF9ieShwaWNrdXBfZ2VvaGFzaF81LCBkcm9wb2ZmX2dlb2hhc2hfNSkgJT4lCiAgICAgICAgICAgICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCksIGF2Z19zcGVlZCA9IG1lYW4oc3BlZWQpLCBhdmdfZGlzdG5hY2UgPSBtZWFuKGRpc3RhbmNlKSkgJT4lCiAgICAgICAgICAgICAgICBmaWx0ZXIoY291bnQgPiAwICYgYXZnX3NwZWVkID4gMSAmIGF2Z19zcGVlZCA8IDcwICkKCnBsb3RfbHkoeCA9IHJvdXRlX3NwZWMkcGlja3VwX2dlb2hhc2hfNSwgeSA9IHJvdXRlX3NwZWMkZHJvcG9mZl9nZW9oYXNoXzUsIHogPSByb3V0ZV9zcGVjJGNvdW50LCB0eXBlID0gImhlYXRtYXAiLCBjb2xvcnNjYWxlID0gIkdyZXlzIikKcGxvdF9seSh4ID0gcm91dGVfc3BlYyRwaWNrdXBfZ2VvaGFzaF81LCB5ID0gcm91dGVfc3BlYyRkcm9wb2ZmX2dlb2hhc2hfNSwgeiA9IHJvdXRlX3NwZWMkYXZnX3NwZWVkLCB0eXBlID0gImhlYXRtYXAiLCBjb2xvcnNjYWxlID0gIkdyZXlzIikKcGxvdF9seSh4ID0gcm91dGVfc3BlYyRwaWNrdXBfZ2VvaGFzaF81LCB5ID0gcm91dGVfc3BlYyRkcm9wb2ZmX2dlb2hhc2hfNSwgeiA9IHJvdXRlX3NwZWMkYXZnX2Rpc3RuYWNlLCB0eXBlID0gImhlYXRtYXAiLCBjb2xvcnNjYWxlID0gIkdyZXlzIikKCnBsb3RfbHkoeCA9IHJvdXRlX3NwZWMkcGlja3VwX2dlb2hhc2hfNSwgeSA9IHJvdXRlX3NwZWMkZHJvcG9mZl9nZW9oYXNoXzUsIHogPSBsb2cocm91dGVfc3BlYyRjb3VudCAqIHJvdXRlX3NwZWMkYXZnX2Rpc3RuYWNlKSwgdHlwZSA9ICJoZWF0bWFwIiwgY29sb3JzY2FsZSA9ICJHcmV5cyIpCmBgYAoKCmBgYHtyfQojIOyekC4uIOq3uOyghOyXkCBkaXN0YW5jZSDrk6TsnYQg7KKAIOyCtO2OtOuztOyekC4gCnBhcihtZnJvdyA9IGMoMSwzKSkKCnBsb3QoZGVuc2l0eSh0cmFpbiRkaXN0YW5jZSkpICAgICAgICAgIyDqtYnsnqXtnogg7Ius7ZWcIEwtU2hhcGUKcGxvdChkZW5zaXR5KGxvZyh0cmFpbiRkaXN0YW5jZSkpKSAgICAjIGxvZy10cmFuc2Zvcm0KcGxvdChkZW5zaXR5KGxvZyh0cmFpbiRkaXN0YW5jZSsxMCkpKSAjICsxMCDslKkg7JuA7KeB7Jes7KO86rOgLCBsb2ctdHJhbnNmb3JtCgpzdW1tYXJ5KHRyYWluJGRpc3RhbmNlKSAgICAgICAgICAgICAgICMgODUxIGttIOynnOumrCDsp4HshKDqsbDrpqzrj4Qg7J6I64ukLiAoIOuqheuwse2VnCBvdXRsaWVyICkgLCDsl63si5wg7JW865Ok64+EIEdQUyDqsIAg7YqE64ukLgoKIyBHUFMg6rCAIO2KgOuKlCDqsoPrk6TsnYAg6rGw66as7JmAIOyDgeq0gOyXhuydtCDrsJzsg53tlaDthZDrjbAsIDg1MWttIOuKlCDqsbDrpqzroZwg7J6YIOuztOyduCDsvIDsnbTsiqTsnbzrv5DsnbTri6QuCiMg7J2065+wIG91dGxpZXIg65Ok7J2AIO2Pieq3oCDsho3rj4TqsIAg7J207IOB7ZWcIOqyg+uTpOuhnCDsnqHslYTrs7TsnpAgCgpzdW1tYXJ5KHRyYWluJHNwZWVkKQpucm93KGZpbHRlcih0cmFpbiwgc3BlZWQgPiA2MCkpICAgICAgIAoKIyA5MzIg6rG07J2064ukLiDsnbTroIfqsowg6re464OlIO2Pieq3oCDsho3rj4TroZwg7Kec66W8IOqyg+yduOyngCwgCiMg7Lac64+E7LCp7KeA7J2YIOyLnOqwhOuMgOuzhCDtj4nqt6Ag7IaN64+E7JeQIOq4sOuwmO2VmOyXrCDsho7slYTrgrzsp4DripQg7YyQ64uo7ZW07JW87ZWc64ukLi4uIOq3gOywruuLpDs7IAoKIyDsnITsmYDripQg7KGw6riIIOuLpOultOqyjCwgR1BTIOqwgCDtioDslrTshJwg6rGw66as6rCAIOunpOyasCDsnpHqsowg7J6h7Z6I64qUIOqyveyasOuPhCDsnojri6QuIApucm93KHRyYWluICU+JSBmaWx0ZXIoZGlzdGFuY2UgPCA1MCB8IHRyaXBfZHVyYXRpb24gPiAzMDAwICkpCmBgYAoKYGBge3J9CiMg7JyE7LmY642w7J207YSwIOyekOyytOuhnOuKlCDsnbTsg4HqsJLtjJDrs4TsnbQg7JWI65CY66+A66GcLCDsi5zqsIQsIOyGjeuPhCDrk7HsnYQg6riw67CY7Jy866GcIOuLpCDqsbjrn6zrgrTslbwg7ZWc64ukLiAKcGxvdCh0cmFpbiRkaXN0YW5jZSwgdHJhaW4kdHJpcF9kdXJhdGlvbikKcGxvdCh0cmFpbiRkaXN0YW5jZSwgdHJhaW4kc3BlZWQpCmBgYAoKYGBge3J9CnJlbW92ZV9vdXRsaWVycyA8LSBmdW5jdGlvbihkZiwgY29sMSwgY29sMikgewogIGxpYnJhcnkobGRib2QpCiAgc2NvcmVzIDwtIGxkYm9kKHN1YnNldChkZiwgc2VsZWN0ID0gYyhjb2wxLCBjb2wyKSksIGsgPSA1MCkKICBoZWFkKHNjb3JlcyRscmQpOyBoZWFkKHNjb3JlcyRya29mKQogIAogIHMgPC0gc3Vic2V0KGRmLCBzZWxlY3QgPSBjKGNvbDEsIGNvbDIpKQogIHBsb3QocykKICB0b3A1MG91dGxpZXJzIDwtIHNbb3JkZXIoc2NvcmVzJGxwZGUsZGVjcmVhc2luZz1GQUxTRSlbMTo1MF0sXQogIHBvaW50cyh0b3A1MG91dGxpZXJzLGNvbD0yKQogIAogIGRmIDwtIGRmWyEoYXMubWF0cml4KGRmW2NvbDFdKSAlaW4lIGFzLm1hdHJpeCh0b3A1MG91dGxpZXJzW2NvbDFdKSAmIGFzLm1hdHJpeChkZltjb2wyXSkgJWluJSBhcy5tYXRyaXgodG9wNTBvdXRsaWVyc1tjb2wyXSkpLF0KICAKICByZXR1cm4oZGYpCn0KCiMgcGFja2FnZSAnbGRib2QnIDogRmxleGlibGUgcHJvY2VkdXJlcyB0byBjb21wdXRlIGxvY2FsIGRlbnNpdHktYmFzZWQgb3V0bGllciBzY29yZXMgZm9yIHJhbmtpbmcgb3V0bGllcnMKIyDsnbTrsogg7LyA7J207Iqk64qULCDrsIDrj4TquLDrsJjsnZggb3V0bGllciDtg5Dsp4DqsIAg7J6YIOuPmeyeke2VoCDqsoPsnLzroZwg6riw64yA7ZW067SELiAKIyDqs4TsgrDrn4nsnbQg66eO7JWELCBwYXJhbGxlbCDsnbQg7KeA7JuQ65CY7Ja07JW8IO2VqC4gCiMg66qH6rCA7KeAIGxvZiDqtIDroKggcGFja2FnZSDqsIAg7J6I7KeA66eMLCBsZGJvZCDqsIAg7KCc7J28IOyii+ydgCDqsrDqs7zrpbwg67O07Jes7KSMLi4g7ZWY7KeA66eMIOyGjeuPhOqwgCDrp50gCiMgdHJhaW4gPC0gcmVtb3ZlX291dGxpZXJzKHRyYWluLCAic3BlZWQiLCAiZGlzdGFuY2UiKQojIHRyYWluIDwtIHJlbW92ZV9vdXRsaWVycyh0cmFpbiwgInRyaXBfZHVyYXRpb24iLCAiZGlzdGFuY2UiKQoKc3BkX2Rpc3RfdGltZSA8LSBzdWJzZXQodHJhaW4sIHNlbGVjdCA9IGMoc3BlZWQsIGRpc3RhbmNlLCB0cmlwX2R1cmF0aW9uKSkKCnJlZ2lzdGVyRG9NQyhjb3JlcyA9IDQpCnNhbXBsZV90cmFpbiA8LSBzYW1wbGVfZnJhYyh0cmFpbiwgMC4xKQojc2FtcGxlX3RyYWluIDwtIHJiaW5kKHNhbXBsZV90cmFpbiwgdHJhaW4gJT4lIGZpbHRlcihzcGVlZCA+IDEwMCkpCnNhbXBsZV90cmFpbiA8LSByZW1vdmVfb3V0bGllcnMoc2FtcGxlX3RyYWluLCAnc3BlZWQnLCAnZGlzdGFuY2UnKQpzYW1wbGVfdHJhaW4gPC0gcmVtb3ZlX291dGxpZXJzKHNhbXBsZV90cmFpbiwgJ3RyaXBfZHVyYXRpb24nLCAnZGlzdGFuY2UnKQpzYW1wbGVfdHJhaW4gPC0gcmVtb3ZlX291dGxpZXJzKHNhbXBsZV90cmFpbiwgJ3RyaXBfZHVyYXRpb24nLCAnc3BlZWQnKQoKbGlicmFyeShNVk4pCiNyZXMgPC0gbXZPdXRsaWVyKHNwZF9kaXN0X3RpbWUsIGxhYmVsID0gRiwgcG9zaXRpb24gPSA0KQoKbGlicmFyeShSbG9mKQojc2R0IDwtIHNhbXBsZV9mcmFjKHNwZF9kaXN0X3RpbWUsIDAuMDEpCiNzZHQgPC0gcmJpbmQoc2R0LCBzcGRfZGlzdF90aW1lICU+JSBmaWx0ZXIoc3BlZWQgPiAxMDApKQojc2R0IDwtIGNiaW5kKHNkdCxsb2Yoc2R0LCBjKDE6NSkpKQojaGVhZChzZHQgJT4lIGFycmFuZ2UoZGVzYyhgMWApKSwgbiA9IDEwMCkKCmxpYnJhcnkoRE13UikKI3NkdCA8LSBzYW1wbGVfZnJhYyhzcGRfZGlzdF90aW1lLCAwLjAxKQojc2R0IDwtIHJiaW5kKHNkdCwgc3BkX2Rpc3RfdGltZSAlPiUgZmlsdGVyKHNwZWVkID4gMTAwKSkKI3NkdCAlPD4lCiMgIG11dGF0ZShzcGVlZCA9IHJvdW5kKHNwZWVkIC8gMTAsIDApKSAlPiUKIyAgbXV0YXRlKGRpc3RhbmNlID0gcm91bmQoZGlzdGFuY2UgLyAxMDAwLCAwKSkgCiNzZHQgPC0gY2JpbmQoc2R0LCBsb2ZhY3RvcihzdWJzZXQoc2R0LCBzZWxlY3QgPSBjKHNwZWVkLCBkaXN0YW5jZSkpLCBrID0gMTApKQpgYGAKCmBgYHtyfQojIGRlbnNpdHkgYmFzZWQg66GcIG91dGxpZXIg66W8IO2DkOyngO2VmOuKlCDqsoPsnZgg7JW97KCQ7J2ALCBub3JtYWwgY2FzZSDrj4Qg7KCc6rGw6rCA64ql7ZWY64uk64qUIOqyg+ydtOq4tO2VmOuLpC4uLi4uLgojIOydvOuLqCBsb2Yg66GcIOygnOqxsOuQnCDtm4QsIOuLpOyLnCBkaXN0YW5jZX5kdXJhdGlvbiAvIGRpc3RhbmNlfnNwZWVkIOyDge2DnOulvCDrs7TsnpAgCnBhcihtZnJvdyA9IGMoMSwyKSkKcGxvdChzYW1wbGVfdHJhaW4kZGlzdGFuY2UsIHNhbXBsZV90cmFpbiR0cmlwX2R1cmF0aW9uKQpwbG90KHNhbXBsZV90cmFpbiRkaXN0YW5jZSwgc2FtcGxlX3RyYWluJHNwZWVkKQpgYGAKCmBgYHtyfQojIGRpc3RhbmNlIH4gZHVyYXRpb24g6re4656Y7ZSE7J2YIOyijOyDgeuLqCDqs7wgZGlzdGFuY2UgfiBzcGVlZCDsl5DshJwgc3BlZWQgfiAwIOydmCDsnbzsp4HshKAg67aA67aE7J20IGRlbnNpdHkgYmFzZWQg66GcIOuqu+yeoeydgAojIG91dGxpZXIg65Ok7J2064ukLiDsnbTrk6TsnYAg7Yq57KCV7Yyo7YS0ICgg7Lac64+E7LCp7KeAIOyijO2RnOuKlCDqsbDsnZgg67OA7ZmU6rCAIOyXhuuKlCDsvIDsnbTsiqQgKSDrk6TsnbQg7KGw67CA7ZWY6rKMIOuqqOyXrCDsnojslrTshJwg6re465+w6rKD7J2064ukLiAKIyDspokg7Yq57KCV7Yyo7YS07J2YIOydtOyDge2YhOyDgeydtCDsnbzsoJUg67mI64+E7J207IOBIOuwnOyDne2VmOqyjCDrkJjrqbQsIGRlbnNpdHkgYmFzZWQg66Gc64qUIOyeoeydhCDsiJgg7JeG64ukLgojIOydtOufsCDrhYDshJ3rk6TsnYAgcnVsZSDroZwg7J6h7JWE7KSY7JW8IO2VoCDqsoMg6rCZ64ukLiAKIyDslYTri4jrqbQsIGRlbnNpdHkg66W8IOuoueydtOq4sCDsoITsl5AsIOuvuOumrCDtirnsoJUgZGVuc2l0eSDsnbTsg4HsnZgg7Yyo7YS07J2EIOqwluuKlCBvdXRsaWVyIOuTpOydhCDsoJzqsbDtlZwg7ZuELCBkZW5zaXR5IOulvCDrqLnsl6zrj4Qg65Cc64ukLgojICgg7J2MLi4g6re465+s64uI6rmMLi4g64uk7Iuc7ZW07JW87ZWc64ukLi4gKQpgYGAKCmBgYHtyfQojIOyCrOyEpOydtCDsooAg6ri47Ja07KGM7KeA66eMLCDsnbzri6jsnYAg6rGw66asLCDsmrTtlonsi5zqsIQsIOyGjeuPhOulvCDqsIHqsIEg7LWc64yAL+y1nOyGjCDquLDspIDqsJLsnLzroZwgMeywqCBmaWx0ZXJpbmcg7J2EIO2VnOuLpC4KcXVhbnRpbGUodHJhaW4kc3BlZWQsIDAuOTk5KSAgICAgICAgICAjPT4gNTYga20vaApxdWFudGlsZSh0cmFpbiRkaXN0YW5jZSwgMC45OTk5KSAgICAgICM9PiA0NSBrbQpxdWFudGlsZSh0cmFpbiR0cmlwX2R1cmF0aW9uLCAwLjk5OSkgICM9PiA4NTAwMCBzZWNvbmRzCgp0cmFpbiAlPD4lIGZpbHRlcigKICBzcGVlZCA8IHF1YW50aWxlKHRyYWluJHNwZWVkLCAwLjk5OSkgJiAKICBkaXN0YW5jZSA8IHF1YW50aWxlKHRyYWluJGRpc3RhbmNlLCAwLjk5OTkpICYgCiAgdHJpcF9kdXJhdGlvbiA8IHF1YW50aWxlKHRyYWluJHRyaXBfZHVyYXRpb24sIDAuOTk5KQopCgpwYXIobWZyb3cgPSBjKDEsMikpCnBsb3QodHJhaW4kZGlzdGFuY2UsIHRyYWluJHRyaXBfZHVyYXRpb24pCnBsb3QodHJhaW4kZGlzdGFuY2UsIHRyYWluJHNwZWVkKQpgYGAKCgpgYGB7cn0KIyBbdG9kb10g7J2864uoIGdlb2pzb24g7Jy866GcIHBpY2t1cCDsnITsuZjrpbwg67OA7ZmY7ZWY7JesLCBtYXAg7JeQIOyYrOugpOuGk+qzoCDrjIDstqkg7YyM7JWF7ZW067O464ukLiAoIGdlb2pzb24g7J2AIOydvOuLqCDrhIjrrLQg64qQ66as64ukLi4gUiDsl5DshJwuLiDrgrTqsIAg66q77ZWY64qUIOqyg+ydvCDsiJjrj4Qg7J6I7KeA66eMICApCiNwaWNrdXBzIDwtIHNhbXBsZV9mcmFjKHRyYWluLCAwLjAwMSkKI2Nvb3JkaW5hdGVzKHBpY2t1cHMpID0gYygicGlja3VwX2xhdGl0dWRlIiwgInBpY2t1cF9sb25naXR1ZGUiKQojY2xhc3MocGlja3VwcykKI3dyaXRlT0dSKHBpY2t1cHMsICIvVXNlcnMvQ0EvcGlja3VwX2xvY2F0aW9ucy5nZW9qc29uIiwgbGF5ZXIgPSAidHJhaW4iLCBkcml2ZXIgPSAiR2VvSlNPTiIpCgojcGlja3Vwc19nZW9qc29uIDwtIHJnZGFsOjpyZWFkT0dSKCIvVXNlcnMvQ0EvcGlja3VwX2xvY2F0aW9ucy5nZW9qc29uIiwgIk9HUkdlb0pTT04iKQoKI3Bsb3QocGlja3Vwc19nZW9qc29uKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KGxlYWZsZXQpCgpwaWNrdXBzIDwtIHNhbXBsZV9mcmFjKHRyYWluLCAwLjAwMSkKCmxlYWZsZXQoZGF0YSA9IHBpY2t1cHMsIHdpZHRoID0gIjEwMCUiKSAlPiUKICBzZXRWaWV3KGxuZyA9IC03My45ODIxNTQ4NDYxOTE0LCBsYXQgPSA0MC43Njc5MzY3MDY1NDMsIHpvb20gPSAxMikgJT4lCiAgYWRkVGlsZXMoKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUKICBhZGRNYXJrZXJzKGxuZyA9IH5waWNrdXBfbG9uZ2l0dWRlLCBsYXQgPSB+cGlja3VwX2xhdGl0dWRlLCBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkpCmBgYAoKYGBge3J9CiNsaWJyYXJ5KHRtYXApCiNsaWJyYXJ5KHRtYXB0b29scykKI2xpYnJhcnkoc3ApCgojcGlja3VwcyA8LSBzYW1wbGVfZnJhYyh0cmFpbiwgMC4wMDEpCiNjb29yZGluYXRlcyhwaWNrdXBzKSA8LSB+IHBpY2t1cF9sb25naXR1ZGUgKyBwaWNrdXBfbGF0aXR1ZGUKI3BpY2t1cHMgICAgICAgICAgICAgIDwtIHNldF9wcm9qZWN0aW9uKHBpY2t1cHMsIGN1cnJlbnQucHJvamVjdGlvbiA9ICJsb25nbGF0IikjLCBwcm9qZWN0aW9uID0gMjc3MDApCgojdG1hcF9tb2RlKCJwbG90IikgIyBwbG90IHwgdmlldwojcXRtKHBpY2t1cHMpCgojcGlja3Vwc19vc20gPC0gcmVhZF9vc20ocGlja3VwcykKI3F0bShwaWNrdXBzX29zbSkgKyBxdG0ocGlja3Vwcywgc3ltYm9scy5jb2wgPSAicmVkIiwgc3ltYm9scy5zaXplID0gMC4wMSkKYGBgCgpgYGB7cn0Kcm91dGVfc3BlYyA8LSB0cmFpbiAlPiUgCiAgICAgICAgICAgICAgICBncm91cF9ieShwaWNrdXBfZ2VvaGFzaF81LCBkcm9wb2ZmX2dlb2hhc2hfNSkgJT4lCiAgICAgICAgICAgICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCksIGF2Z19zcGVlZCA9IG1lYW4oc3BlZWQpLCBhdmdfZGlzdG5hY2UgPSBtZWFuKGRpc3RhbmNlKSkgJT4lCiAgICAgICAgICAgICAgICBmaWx0ZXIoY291bnQgPiAwICYgYXZnX3NwZWVkID4gMSAmIGF2Z19zcGVlZCA8IDEwMCApCgpwbG90X2x5KHggPSByb3V0ZV9zcGVjJHBpY2t1cF9nZW9oYXNoXzUsIHkgPSByb3V0ZV9zcGVjJGRyb3BvZmZfZ2VvaGFzaF81LCB6ID0gbG9nKHJvdXRlX3NwZWMkY291bnQrMTApLCB0eXBlID0gImhlYXRtYXAiLCBjb2xvcnNjYWxlID0gIkdyZXlzIikKcGxvdF9seSh4ID0gcm91dGVfc3BlYyRwaWNrdXBfZ2VvaGFzaF81LCB5ID0gcm91dGVfc3BlYyRkcm9wb2ZmX2dlb2hhc2hfNSwgeiA9IHJvdXRlX3NwZWMkYXZnX3NwZWVkLCB0eXBlID0gImhlYXRtYXAiLCBjb2xvcnNjYWxlID0gIkdyZXlzIikKcGxvdF9seSh4ID0gcm91dGVfc3BlYyRwaWNrdXBfZ2VvaGFzaF81LCB5ID0gcm91dGVfc3BlYyRkcm9wb2ZmX2dlb2hhc2hfNSwgeiA9IHJvdXRlX3NwZWMkYXZnX2Rpc3RuYWNlLCB0eXBlID0gImhlYXRtYXAiLCBjb2xvcnNjYWxlID0gIkdyZXlzIikKCnBsb3RfbHkoeCA9IHJvdXRlX3NwZWMkcGlja3VwX2dlb2hhc2hfNSwgeSA9IHJvdXRlX3NwZWMkZHJvcG9mZl9nZW9oYXNoXzUsIHogPSBsb2cocm91dGVfc3BlYyRjb3VudCAqIHJvdXRlX3NwZWMkYXZnX2Rpc3RuYWNlKSwgdHlwZSA9ICJoZWF0bWFwIiwgY29sb3JzY2FsZSA9ICJHcmV5cyIpCmBgYAoKYGBge3J9CiMgbmV0Y291bnQg66W8IOykkeyLrOycvOuhnCwg64+E7IusIOyXheustOyngOq1rCwg6rWQ7Jm4IOqxsOyjvOyngOq1rCDrk7HsnYQg6rWs67aE7ZW067O464ukLiAKcGlja3VwX2NvdW50IDwtIHRyYWluICU+JSBncm91cF9ieShwaWNrdXBfZ2VvaGFzaF81KSAlPiUgc3VtbWFyaXNlKHBjID0gbigpKQpkcm9wb2ZmX2NvdW50IDwtIHRyYWluICU+JSBncm91cF9ieShkcm9wb2ZmX2dlb2hhc2hfNSkgJT4lIHN1bW1hcmlzZShkYyA9IG4oKSkKCmNvbG5hbWVzKHBpY2t1cF9jb3VudCkgPC0gYygiZ2VvaGFzaCIsICJwYyIpCmNvbG5hbWVzKGRyb3BvZmZfY291bnQpIDwtIGMoImdlb2hhc2giLCAiZGMiKQoKbSA8LSBtZXJnZShwaWNrdXBfY291bnQsIGRyb3BvZmZfY291bnQsIGJ5ID0gImdlb2hhc2giLCBhbGwgPSBUKSAlPiUgcmVwbGFjZV9uYShsaXN0KHBjID0gMCwgZGMgPSAwKSkgCm0gJTw+JSBtdXRhdGUobmV0X2NvdW50ID0gcGMgLSBkYywgdG90YWwgPSBwYyArIGRjKQoKcGxvdChtbSR0b3RhbCwgbW0kbmV0X2NvdW50KQphYmxpbmUodj1tZWRpYW4obW0kdG90YWwpLCBoPW1lZGlhbihtbSRuZXRfY291bnQpKQpgYGAKCmBgYHtyfQojIOyGjeuPhAojIG91dGdvaW5nLCBpbmNvbWluZywgaW50ZXJuYWwg7J2EIOq1rOu2hO2VtOyEnCDsgrTtjrTrs7jri6QuIAoKdHJhaW4gJTw+JSBtdXRhdGUoaXNfaW50ZXJuYWwgPSBwaWNrdXBfZ2VvaGFzaF81ID09IGRyb3BvZmZfZ2VvaGFzaF81KQoKaW50ZXJuYWxfcm91dGUgPC0gdHJhaW4gJT4lIGZpbHRlcihpc19pbnRlcm5hbCA9PSBUKQppbnRlcm5hbF9zcGVjIDwtIGludGVybmFsX3JvdXRlICU+JSAKICBncm91cF9ieShwaWNrdXBfZ2VvaGFzaF81LCBwaWNrdXBfaG91cikgJT4lIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgYXZnX3NwZWVkID0gbWVhbihzcGVlZCksIGF2Z19kaXN0YW5jZSA9IG1lYW4oZGlzdGFuY2UpKQoKIyBpbnRlcm5hbCBzcGVlZCAgCmJveHBsb3QoYXZnX3NwZWVkIH4gcGlja3VwX2hvdXIsIGRhdGEgPSBpbnRlcm5hbF9zcGVjICU+JSBmaWx0ZXIoY291bnQgPiAxMDAwKSkKYGBgCgpgYGB7cn0KIyBpbnRlcnNlY3Rpb24gc3BlZWQgCmludGVyc2VjdGlvbl9zcGVjIDwtIHRyYWluICU+JSAKICBmaWx0ZXIoaXNfaW50ZXJuYWwgPT0gRikgJT4lCiAgZ3JvdXBfYnkocGlja3VwX2dlb2hhc2hfNSwgZHJvcG9mZl9nZW9oYXNoXzUpICU+JQogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgYXZnX3NwZWVkID0gbWVkaWFuKHNwZWVkKSwgYXZnX2Rpc3RhbmNlID0gbWVkaWFuKGRpc3RhbmNlKSkKCnBsb3RfbHkoeCA9IGludGVyc2VjdGlvbl9zcGVjJHBpY2t1cF9nZW9oYXNoXzUsIAogICAgICAgIHkgPSBpbnRlcnNlY3Rpb25fc3BlYyRkcm9wb2ZmX2dlb2hhc2hfNSwgCiAgICAgICAgeiA9IGludGVyc2VjdGlvbl9zcGVjJGF2Z19zcGVlZCwgCiAgICAgICAgdHlwZSA9ICJoZWF0bWFwIiwgY29sb3JzY2FsZSA9ICJWaXJpZGlzIikKYGBgCgpgYGB7cn0Kb3V0Z29pbmdfc3BlYyA8LSB0cmFpbiAlPiUgCiAgZmlsdGVyKGlzX2ludGVybmFsID09IEYpICU+JQogIGdyb3VwX2J5KHBpY2t1cF9nZW9oYXNoXzUpICU+JQogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgYXZnX3NwZWVkID0gbWVkaWFuKHNwZWVkKSwgYXZnX2Rpc3RhbmNlID0gbWVkaWFuKGRpc3RhbmNlKSkKCnBsb3Qob3V0Z29pbmdfc3BlYyRhdmdfZGlzdGFuY2UsIG91dGdvaW5nX3NwZWMkYXZnX3NwZWVkKQpgYGAKCmBgYHtyfQppbmNvbWluZ19zcGVjIDwtIHRyYWluICU+JSAKICBmaWx0ZXIoaXNfaW50ZXJuYWwgPT0gRikgJT4lCiAgZ3JvdXBfYnkoZHJvcG9mZl9nZW9oYXNoXzUpICU+JQogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgYXZnX3NwZWVkID0gbWVkaWFuKHNwZWVkKSwgYXZnX2Rpc3RhbmNlID0gbWVkaWFuKGRpc3RhbmNlKSkKCnBsb3QoaW5jb21pbmdfc3BlYyRhdmdfZGlzdGFuY2UsIGluY29taW5nX3NwZWMkYXZnX3NwZWVkKQpgYGAKCmBgYHtyfQojIHRyeSMxCiMg6rK966Gc67OEIOuNsOydtO2EsCDtlITroIjsnoTsnYQg66eM65Og64ukLiAKIyDqsr3roZzrs4Qg642w7J207YSwIO2UhOugiOyehOydgCBnZW9oYXNoIOq4sOykgCA1LCA2LCA3IOugiOuyqOyXkCDrjIDtlbQg6rCB6rCBIOunjOuToOuLpC4gCiMg7J2065OkIGZyYW1lIOyXkOyEnCDstpTqsIDrkJjripQgZmVhdHVyZSDrk6Tsl5DripQsIOqyveuhnOuzhCDtj4nqt6Ag7Iuc6rCELCDsho3rj4QsIOqxsOumrCDsmYAg7LWc67mIIOyatO2WiSDsi5zqsITrjIAg65Ox7J2064ukLiAKIyB0cmlwIGR1cmF0aW9uIOydgCDqsIEg6rK966GcIGRhdGEgZnJhbWUg7JeQIOydmO2VtCDsmIjsuKHrkJwg6rCS7J2YIG1lYW4g7J2EIOy3qO2VnOuLpC4gCiMgYmFzZWxpbmUg7J2AIOyalOydvC/si5zqsIQgKyDstpzrj4TssKnsp4Drs4Qg7Y+J6reg7Iuc6rCEIOyYiOy4oeqwkuydmCBSTVNFIAoKcm91dGVzX2x2bF81IDwtIHRyYWluICU+JSAKICBzdWJzZXQoc2VsZWN0ID0gYygicGlja3VwX2dlb2hhc2hfNSIsICJkcm9wb2ZmX2dlb2hhc2hfNSIsICJyb3V0ZTUiLCAKICAgICAgICAgICAgICAgICAgICAicGlja3VwX2RhdGV0aW1lIiwgCiAgICAgICAgICAgICAgICAgICAgInBhc3Nlbmdlcl9jb3VudCIsIAogICAgICAgICAgICAgICAgICAgICJzdG9yZV9hbmRfZndkX2ZsYWciLAogICAgICAgICAgICAgICAgICAgICJ0cmlwX2R1cmF0aW9uIiwgImRpc3RhbmNlIiwgInNwZWVkIikpICU+JQogIG11dGF0ZSh3ZGF5ID0gd2RheShwaWNrdXBfZGF0ZXRpbWUsIGxhYmVsID0gVCksIGhvdXIgPSBob3VyKHBpY2t1cF9kYXRldGltZSksIG1vbnRoID0gbW9udGgocGlja3VwX2RhdGV0aW1lKSkgCgpyb3V0ZXNfbHZsXzUubW9udGhseSA8LSByb3V0ZXNfbHZsXzUgJT4lIAogIGdyb3VwX2J5KG1vbnRoLCByb3V0ZTUpICU+JQogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgYXZnX3NwZWVkID0gbWVhbihzcGVlZCksIGF2Z19kdXJhdGlvbiA9IG1lYW4odHJpcF9kdXJhdGlvbiksIGF2Z19kaXN0YW5jZSA9IG1lYW4oZGlzdGFuY2UpKQoKIyDriITsoIEg7JuUIOyatO2WieyImCAxLDAwMCDqsbQg7J207IOB7J24IOqyveuhnOuTpOyXkOyEnOuKlCDsm5Trs4TroZwg7Jq07ZaJ7Iuc6rCEIOywqOydtOqwgCDtgazsp4Ag7JWK7J2MLCAKIyDsnbTrk6Qg6rK966Gc7JeQ7ISc7J2YIOyatO2WieuwnOyDneqxtOyImOuKlCDsoITssrTsnZggODYuMyUg7KCV64+EIApib3hwbG90KGF2Z19zcGVlZCB+IHJvdXRlNSwgZGF0YSA9IHJvdXRlc19sdmxfNS5tb250aGx5ICU+JSBmaWx0ZXIoY291bnQgPiAxMDAwKSkKYGBgCgpgYGB7cn0Kc3VtKChyb3V0ZXNfbHZsXzUubW9udGhseSAlPiUgZmlsdGVyKGNvdW50ID49IDEwMDApKSRjb3VudCkgKiAxLjAgLyBzdW0ocm91dGVzX2x2bF81Lm1vbnRobHkkY291bnQpCgojIOuIhOyggSDsm5Qg7Jq07ZaJ7IiYIDEwMCB+IDEwMDAg6rG0IOqyveuhnOuTpOyXkOyEnOuKlCDri6TshowgZmx1Y3R1YXRpb24g7J20IOyeiOydjC4g64u57JewOzsKYm94cGxvdChhdmdfc3BlZWQgfiByb3V0ZTUsIGRhdGEgPSByb3V0ZXNfbHZsXzUubW9udGhseSAlPiUgZmlsdGVyKGNvdW50IDwgMTAwMCAmIGNvdW50ID4gMTAwKSkKYGBgCgpgYGB7cn0KIyDsm5TsmrTtlonqsbTsiJgg7Y+J6regIDEwMDDqsbQg7J207IOBIOqyveuhnOuTpOyXkCDrjIDtlbTshJzrp4wg7J6Q7IS47Z6IIOyCtO2OtOuztOyekCAoIO2VteyLrCDqsr3roZwgKQpjb3JlX3JvdXRlcyA8LSByb3V0ZXNfbHZsXzUubW9udGhseSAlPiUgZ3JvdXBfYnkocm91dGU1KSAlPiUgc3VtbWFyaXNlKGF2Z19jb3VudCA9IG1lYW4oY291bnQpKSAlPiUgZmlsdGVyKGF2Z19jb3VudCA+IDEwMDApCgpjb3JlX3JvdXRlc19zdGF0IDwtIHJvdXRlc19sdmxfNSAlPiUgZmlsdGVyKHJvdXRlNSAlaW4lIGNvcmVfcm91dGVzJHJvdXRlNSkKbnJvdyhjb3JlX3JvdXRlc19zdGF0KSAvIG5yb3cocm91dGVzX2x2bF81KSAqIDEuMCAjIDg2JSAKCmJveHBsb3Qoc3BlZWQgfiByb3V0ZTUsIGRhdGEgPSBjb3JlX3JvdXRlc19zdGF0KQpgYGAKCmBgYHtyfQpjb3JlX3JvdXRlc19zdGF0ICU8PiUgCiAgbXV0YXRlKHRpbWVfd2luZG93ID0gaWZlbHNlKGhvdXIgPiAyMiB8IGhvdXIgPCA3LCAiZGF3biIsIGlmZWxzZShob3VyPjEyLCAiYWZ0ZXJub29uIiwgIm1vcm5pbmciKSkpCgpnZ3Bsb3QoY29yZV9yb3V0ZXNfc3RhdCwgYWVzKHJvdXRlNSwgc3BlZWQpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuYWxwaGEgPSAwLjEsIG91dGxpZXIuY29sb3VyID0gInJlZCIsIGFlcyhjb2xvdXIgPSB0aW1lX3dpbmRvdykpICsKICBjb29yZF9mbGlwKCkgCiAgCmBgYAoKYGBge3J9CmdncGxvdChjb3JlX3JvdXRlc19zdGF0ICU+JSBmaWx0ZXIobW9udGggPT0gMSAmIGhvdXIgPT0gMTIpLCBhZXMocm91dGU1LCBzcGVlZCkpICsKICBnZW9tX2JveHBsb3Qob3V0bGllci5hbHBoYSA9IDAuMSwgb3V0bGllci5jb2xvdXIgPSAicmVkIikgKwogIGNvb3JkX2ZsaXAoKSAKYGBgCgpgYGB7cn0Kcm91dGVzX2x2bF81ICU+JSAKICBncm91cF9ieShyb3V0ZTUpICU+JQogIHN1bW1hcmlzZShzZF9zcGVlZCA9IHNkKHNwZWVkKSwgc2RfZGlzdGFuY2UgPSBzZChkaXN0YW5jZSksIGF2Z19zcGVlZCA9IG1lYW4oc3BlZWQpLCBhdmdfZGlzdGFuY2UgPSBtZWFuKGRpc3RhbmNlKSwgY291bnQgPSBuKCkpICU+JQogIGZpbHRlcihjb3VudCA+IDEpIC0+IHJvdXRlNV9zZApgYGAKCmBgYHtyfQojIGRyNXI0IOyXkOyEnCBkcjVyNCDroZwg6rCA64qUIOyatO2WieqxtOuTpOydgCDrqqjrkZAg7Lac64+E7LCpIOyijO2RnOqwgCDrj5nsnbwsIHNwZWVkICwgZGlzdGFuY2Ug66qo65GQIDAsIHRyaXBfZHVyYXRpb24g66eMIOyeiOydjDs7CiMg7J2065+wIOy8gOydtOyKpOydmCDqsr3smrDsl5DripQsIOyVhOuemCBkZW5zaXR5IHBsb3Qg7J2YIOuqqOyWkeydhCDqsJbripQg67aE7Y+s7JeQ7IScIHNhbXBsaW5nIO2VmOuKlCDsiJjrsJbsl5Ag7JeG7Ja0IOuztOyehCAKaGVhZCh0cmFpbiAlPiUgZmlsdGVyKHBpY2t1cF9nZW9oYXNoXzUgPT0gImRyNXI0IiAmIGRyb3BvZmZfZ2VvaGFzaF81ID09ICJkcjVyNCIpKQpwbG90KGRlbnNpdHkoKHRyYWluICU+JSBmaWx0ZXIocGlja3VwX2dlb2hhc2hfNSA9PSAiZHI1cjQiICYgZHJvcG9mZl9nZW9oYXNoXzUgPT0gImRyNXI0IikpJHRyaXBfZHVyYXRpb24pKQpgYGAKCmBgYHtyfQpwYXIobWZyb3cgPSBjKDEsMikpCgpwbG90KGRlbnNpdHkocm91dGU1X3NkJGF2Z19kaXN0YW5jZSkpCnBsb3QoZGVuc2l0eShyb3V0ZTVfc2QkYXZnX3NwZWVkKSkKYGBgCgpgYGB7cn0Kcm91dGVzX2x2bF81ICU+JQogIGZpbHRlcihwaWNrdXBfZ2VvaGFzaF81ICE9IGRyb3BvZmZfZ2VvaGFzaF81KSAlPiUKICBncm91cF9ieShyb3V0ZTUsIHJvdW5kKGhvdXIvMywwKSkgJT4lCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUgCiAgZ3JvdXBfYnkocm91dGU1KSAlPiUgCiAgdG9wX24oMSwgY291bnQpIC0+IHJvdXRlNS5tb3N0X2ZyZXFfaG91cgoKcm91dGVzX2x2bF81ICU+JSBncm91cF9ieShyb3V0ZTUpICU+JSBzdW1tYXJpc2UodG90YWxfY291bnQgPSBuKCkpIC0+IHRtcAoKcm91dGU1Lm1vc3RfZnJlcV9ob3VyICU8PiUgCiAgbWVyZ2UodG1wLCBieSA9ICJyb3V0ZTUiKSAlPiUKICBtdXRhdGUocmFpdG8gPSBjb3VudCAqIDEuMCAvIHRvdGFsX2NvdW50KQoKY29sbmFtZXMocm91dGU1Lm1vc3RfZnJlcV9ob3VyKSA8LSBjKCJyb3V0ZTUiLCAidGltZV9ncm91cCIsICJjb3VudCIsICJ0b3RhbF9jb3VudCIsICJyYXRpbyIpCnJvdXRlNS5tb3N0X2ZyZXFfaG91ciAlPD4lIG11dGF0ZSh0aW1lX2dyb3VwID0gcGFzdGUoMyp0aW1lX2dyb3VwLCAifiIsIDMqKHRpbWVfZ3JvdXArMSktMSkpCgoKZ2VvX3Rva2VucyA8LSBzdHJzcGxpdChyb3V0ZTUubW9zdF9mcmVxX2hvdXIkcm91dGU1LCAiPiIpCnJvdXRlNS5tb3N0X2ZyZXFfaG91ciRzdGFydCA9IHVubGlzdChnZW9fdG9rZW5zKVsyKigxOm5yb3cocm91dGU1Lm1vc3RfZnJlcV9ob3VyKSktMV0Kcm91dGU1Lm1vc3RfZnJlcV9ob3VyJGVuZCAgID0gdW5saXN0KGdlb190b2tlbnMpWzIqKDE6bnJvdyhyb3V0ZTUubW9zdF9mcmVxX2hvdXIpKV0KCmhlYWQocm91dGU1Lm1vc3RfZnJlcV9ob3VyKQoKdGFibGUoCiAgKHJvdXRlNS5tb3N0X2ZyZXFfaG91ciAlPiUgZmlsdGVyKHN0YXJ0ID09ICJkcjVydSIsIHRvdGFsX2NvdW50ID4gMTAwKSkkdGltZV9ncm91cAopCgpyb3V0ZTUubW9zdF9mcmVxX2hvdXIgJT4lIAogIGZpbHRlcih0b3RhbF9jb3VudCA+IDUwKSAlPiUKICBncm91cF9ieShzdGFydCwgdGltZV9ncm91cCkgJT4lCiAgc3VtbWFyaXNlKHRpbWVfZ3JvdXBfY291bnQgPSBuKCkpICU+JQogIGdyb3VwX2J5KHN0YXJ0KSAlPiUKICB0b3BfbigxLCB0aW1lX2dyb3VwX2NvdW50KSAtPiBzdGFydF9zcGVjCgpyb3V0ZTUubW9zdF9mcmVxX2hvdXIgJT4lIGZpbHRlcih0b3RhbF9jb3VudCA+IDUwKSAlPiUgZ3JvdXBfYnkoc3RhcnQpICU+JSBzdW1tYXJpc2UoY291bnQgPSBuKCkpIC0+IHN0YXJ0X3NwZWNfMgoKc3RhcnRfc3BlYyAlPD4lIG1lcmdlKHN0YXJ0X3NwZWNfMiwgYnkgPSAic3RhcnQiKSAlPiUgbXV0YXRlKHJhdGlvID0gdGltZV9ncm91cF9jb3VudCAqIDEuMCAvIGNvdW50KQoKIyDtirnsoJUg7KeA7Jet7JeQ7IScIOuLpOuluCDsp4Dsl63snLzroZwg64KY6rCA64qUIOyLnOqwhOuMgOulvCBHcm91cGluZyDtlZjsl6wsIAojIOyjvOuhnCDrsJzsg53rkJjripQg7Iuc6rCE64yA66W8IOuztOuptCDsoIDrhYHsi5zqsIQgLCDsg4jrsr3si5zqsIQgLCDslYTsuagg7Iuc6rCEIOycvOuhnCDrgpjriZzri6QuCiMg7J20IOykkSDsoITtmJXsoIHsnLzroZwg7Yq57KCV7Iuc6rCE64yA7JeQIOyatO2WieydtCDsp5HspJHrkJjripQg7KeA7Jet65Ok7J20IOyeiOuKlOuNsCwKIyDsoIDrhYHsi5zqsITrjIDrqbQg7JeF66y07KeA6rWsLCDsg4jrsr3si5zqsITrjIDrqbQg7Jyg7Z2l7KeA6rWsLCDslYTsuajsi5zqsITrjIDrqbQg6rGw7KO87KeA6rWsIOuhnCDstpTsuKHtlbQg67O8IOyImCDsnojsnYQg6rKDIOqwmeuLpC4KaGVhZChzdGFydF9zcGVjICU+JSBhcnJhbmdlKGRlc2MoY291bnQpKSwgbiA9IDIwKQpgYGAKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgpgYGB7cn0KYGBg