In this R Notebook we load the Chicago Crime 2001 - present data into R using a data.table.

First Download the data in a large .csv file or try the SODA API.

To see what is possible with the data check out Minging Chi - Chicago Crime Data Visualization R Shiny App.

library(pacman)
p_load(RSocrata, tidyverse, arsenal, dtplyr, data.table, fst, tictoc, lubridate, disk.frame, beepr)

We could download the data using the Socrata API. However this will take way too long.

# ChiCrimeDataFrame <- read.socrata("https://data.cityofchicago.org/api/odata/v4/ijzp-q8t2")

# nrow(ChiCrimeDataFrame) 

So we should just download the .csv file once to the /data directory. This is very slow also.

# download.file("https://data.cityofchicago.org/api/views/ijzp-q8t2/rows.csv?accessType=DOWNLOAD", "data/Crimes_-_2001_to_present.csv")

Read the data into a data.table

tic()
ChiCrimes_dt <- fread("data/Crimes_-_2001_to_present.csv", nThread = 12)
|--------------------------------------------------|
|==================================================|
toc()
17.059 sec elapsed

#Parallel fread

tic()
fwrite(ChiCrimes_dt, file="data/Crimes_-_2001_to_present01.csv", nThread=12)
toc()
4.448 sec elapsed

#Parallel fwrite

Using read.csv() is much slower.

tic()
ChiCrimes_df <- read_csv("data/Crimes_-_2001_to_present.csv")
35324024 parsing failures.
  row          col           expected                     actual                                file
63303 X Coordinate 1/0/T/F/TRUE/FALSE 1181051                    'data/Crimes_-_2001_to_present.csv'
63303 Y Coordinate 1/0/T/F/TRUE/FALSE 1837225                    'data/Crimes_-_2001_to_present.csv'
63303 Latitude     1/0/T/F/TRUE/FALSE 41.708589                  'data/Crimes_-_2001_to_present.csv'
63303 Longitude    1/0/T/F/TRUE/FALSE -87.612583094              'data/Crimes_-_2001_to_present.csv'
63303 Location     1/0/T/F/TRUE/FALSE (41.708589, -87.612583094) 'data/Crimes_-_2001_to_present.csv'
..... ............ .................. .......................... ...................................
See problems(...) for more details.
toc()
50.645 sec elapsed

#Not parallel read_csv

comparedf(ChiCrimes_dt, ChiCrimes_df)
Compare Object

Function Call: 
comparedf(x = ChiCrimes_dt, y = ChiCrimes_df)

Shared: 22 non-by variables and 7134610 observations.
Not shared: 0 variables and 0 observations.

Differences found in 2/11 variables compared.
0 variables compared have non-identical attributes.
rm(ChiCrimes_df)

As an aside, check out the fst package and the write_fst() function that stores the data in a compressed format. How much space is saved storing the data in a .fst file?

tic()
write_fst(ChiCrimes_dt, "data/Crimes_-_2001_to_present.fst", compress = 100, uniform_encoding = TRUE)
toc()
13.282 sec elapsed

Then we can read the data into a data.table in R using the read_fst() R function.

tic()
ChiCrimes2_dt <- read_fst("data/Crimes_-_2001_to_present.fst",
  columns = NULL,
  from = 1,
  to = NULL,
  as.data.table = TRUE,
  old_format = FALSE
)
toc()
9.568 sec elapsed
dim(ChiCrimes2_dt)
[1] 7134610      22
rm(ChiCrimes2_dt)

Note that the fst package can create a link to the .fst file that is saved on your harddrive. This link can be used to access the data without loading it into memory.

ChiCrimes2_fs <- fst("data/Crimes_-_2001_to_present.fst")

dim(ChiCrimes2_fs)
[1] 7134610      22

##In memory

ChiCrimes2_fs %>% dim()
[1] 7134610      22

Analyze the data in the data.table

ChiCrimes_dt %>% head()
tic()
ChiCrimes_dt %>% group_by(`FBI Code`) %>%
  summarise(n=n())
You are using a dplyr method on a raw data.table, which will call the
* data frame implementation, and is likely to be inefficient.
* 
* To suppress this message, either generate a data.table translation with
* `lazy_dt()` or convert to a data frame or tibble with
* `as.data.frame()`/`as_tibble()`.`summarise()` ungrouping output (override with `.groups` argument)
toc()
0.149 sec elapsed
ChiCrimes_dt %>% group_by(Year) %>%
  summarise(n=n())
You are using a dplyr method on a raw data.table, which will call the
* data frame implementation, and is likely to be inefficient.
* 
* To suppress this message, either generate a data.table translation with
* `lazy_dt()` or convert to a data frame or tibble with
* `as.data.frame()`/`as_tibble()`.`summarise()` ungrouping output (override with `.groups` argument)
ChiCrimes_dt %>% group_by(Year, `Community Area`) %>%
  summarise(n=n(), Arrest_total = sum(Arrest), Arrest_rate=mean(Arrest)) %>%
  ggplot(aes(x=`Community Area`, y=`Arrest_rate`)) +
  geom_point() +
  geom_smooth()
You are using a dplyr method on a raw data.table, which will call the
* data frame implementation, and is likely to be inefficient.
* 
* To suppress this message, either generate a data.table translation with
* `lazy_dt()` or convert to a data frame or tibble with
* `as.data.frame()`/`as_tibble()`.`summarise()` regrouping output by 'Year' (override with `.groups` argument)

Lets make the plots in the Dashboard.

Number of Reported Crimes by Date. Date when the incident occurred. this is sometimes a best estimate.

ChiCrimes_dt %>% mutate(Date2 = mdy_hms(Date,tz=Sys.timezone())) %>%
  mutate(day = date(Date2)) %>%
  group_by(day) %>%
  summarize(n=n()) %>%
  ggplot(aes(x=day, y=n)) +
  geom_line()
You are using a dplyr method on a raw data.table, which will call the
* data frame implementation, and is likely to be inefficient.
* 
* To suppress this message, either generate a data.table translation with
* `lazy_dt()` or convert to a data frame or tibble with
* `as.data.frame()`/`as_tibble()`.You are using a dplyr method on a raw data.table, which will call the
* data frame implementation, and is likely to be inefficient.
* 
* To suppress this message, either generate a data.table translation with
* `lazy_dt()` or convert to a data frame or tibble with
* `as.data.frame()`/`as_tibble()`.You are using a dplyr method on a raw data.table, which will call the
* data frame implementation, and is likely to be inefficient.
* 
* To suppress this message, either generate a data.table translation with
* `lazy_dt()` or convert to a data frame or tibble with
* `as.data.frame()`/`as_tibble()`.`summarise()` ungrouping output (override with `.groups` argument)

Number of Reported Crimes by Primary Type. The primary description of the IUCR code.

ChiCrimes_dt %>% 
  ggplot(aes(Arrest)) +
  geom_bar()

ChiCrimes_dt %>% 
  ggplot(aes(`Location Description`)) +
  geom_bar()

ChiCrimes_dt %>% 
  ggplot(aes(`Primary Type`)) +
  geom_bar()

ChiCrimes_dt %>% 
  ggplot(aes(`District`)) +
  geom_bar()

ChiCrimes_dt %>% 
  ggplot(aes(`Domestic`)) +
  geom_bar()

Test out disk.frame

setup_disk.frame()
The number of workers available for disk.frame is 6
# this will allow unlimited amount of data to be passed from worker to worker
options(future.globals.maxSize = Inf)
library(nycflights13)

# convert the flights data to a disk.frame and store the disk.frame in the folder
# "tmp_flights" and overwrite any content if needed
flights.df <- as.disk.frame(
  flights, 
  outdir = file.path("data_disk_frame", "tmp_flights.df"),
  overwrite = TRUE)
flights.df
path: "data_disk_frame/tmp_flights.df"
nchunks: 6
nrow (at source): 336776
ncol (at source): 19
nrow (post operations): ???
ncol (post operations): ???
class(flights.df1)
[1] "disk.frame"        "disk.frame.folder"
filter(flights.df, dep_delay > 1000) %>% collect %>% head(2)
c4 <- flights %>%
  filter(month == 5, day == 17, carrier %in% c('UA', 'WN', 'AA', 'DL')) %>%
  select(carrier, dep_delay, air_time, distance) %>%
  mutate(air_time_hours = air_time / 60) %>%
  collect %>%
  arrange(carrier)# arrange should occur after `collect`
You are using a dplyr method on a raw data.table, which will call the
* data frame implementation, and is likely to be inefficient.
* 
* To suppress this message, either generate a data.table translation with
* `lazy_dt()` or convert to a data frame or tibble with
* `as.data.frame()`/`as_tibble()`.You are using a dplyr method on a raw data.table, which will call the
* data frame implementation, and is likely to be inefficient.
* 
* To suppress this message, either generate a data.table translation with
* `lazy_dt()` or convert to a data frame or tibble with
* `as.data.frame()`/`as_tibble()`.You are using a dplyr method on a raw data.table, which will call the
* data frame implementation, and is likely to be inefficient.
* 
* To suppress this message, either generate a data.table translation with
* `lazy_dt()` or convert to a data frame or tibble with
* `as.data.frame()`/`as_tibble()`.
c4  %>% head
flights.df %>%
  group_by(carrier) %>% # notice that hard_group_by needs to be set
  summarize(count = n(), mean_dep_delay = mean(dep_delay, na.rm=T)) %>%  # mean follows normal R rules
  collect %>% 
  arrange(carrier)
beep();beep();beep(sound=4)
LS0tCnRpdGxlOiAiQ2hpY2FnbyBDcmltZSBEYXRhIC0gZGF0YS50YWJsZSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKLS0tCgpJbiB0aGlzIFIgTm90ZWJvb2sgd2UgbG9hZCB0aGUgW0NoaWNhZ28gQ3JpbWUgMjAwMSAtIHByZXNlbnRdKGh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9QdWJsaWMtU2FmZXR5L0NyaW1lcy0yMDAxLXRvLXByZXNlbnQvaWp6cC1xOHQyKSBkYXRhIGludG8gUiB1c2luZyBhIGRhdGEudGFibGUuCgpGaXJzdCBEb3dubG9hZCB0aGUgZGF0YSBpbiBhIGxhcmdlIC5jc3YgZmlsZSBvciB0cnkgdGhlIFNPREEgQVBJLgoKVG8gc2VlIHdoYXQgaXMgcG9zc2libGUgd2l0aCB0aGUgZGF0YSBjaGVjayBvdXQgW01pbmdpbmcgQ2hpIC0gQ2hpY2FnbyBDcmltZSBEYXRhIFZpc3VhbGl6YXRpb25dKGh0dHBzOi8vbWluaW5nY2hpMi5zaGlueWFwcHMuaW8vY2hpY3JpbWUvKSBSIFNoaW55IEFwcC4KCmBgYHtyfQpsaWJyYXJ5KHBhY21hbikKcF9sb2FkKFJTb2NyYXRhLCB0aWR5dmVyc2UsIGFyc2VuYWwsIGR0cGx5ciwgZGF0YS50YWJsZSwgZnN0LCB0aWN0b2MsIGx1YnJpZGF0ZSwgZGlzay5mcmFtZSwgYmVlcHIpCmBgYAoKV2UgY291bGQgZG93bmxvYWQgdGhlIGRhdGEgdXNpbmcgdGhlIFNvY3JhdGEgQVBJLiAgSG93ZXZlciB0aGlzIHdpbGwgdGFrZSB3YXkgdG9vIGxvbmcuCgpgYGB7cn0KIyBDaGlDcmltZURhdGFGcmFtZSA8LSByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9hcGkvb2RhdGEvdjQvaWp6cC1xOHQyIikKCiMgbnJvdyhDaGlDcmltZURhdGFGcmFtZSkgCmBgYAoKU28gd2Ugc2hvdWxkIGp1c3QgZG93bmxvYWQgdGhlIC5jc3YgZmlsZSBvbmNlIHRvIHRoZSAvZGF0YSBkaXJlY3RvcnkuICBUaGlzIGlzIHZlcnkgc2xvdyBhbHNvLgoKYGBge3J9CiMgZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL2FwaS92aWV3cy9panpwLXE4dDIvcm93cy5jc3Y/YWNjZXNzVHlwZT1ET1dOTE9BRCIsICJkYXRhL0NyaW1lc18tXzIwMDFfdG9fcHJlc2VudC5jc3YiKQpgYGAKIyMgUmVhZCB0aGUgZGF0YSBpbnRvIGEgZGF0YS50YWJsZQoKYGBge3IgbWVzc2FnZT1GQUxTRX0KdGljKCkKQ2hpQ3JpbWVzX2R0IDwtIGZyZWFkKCJkYXRhL0NyaW1lc18tXzIwMDFfdG9fcHJlc2VudC5jc3YiLCBuVGhyZWFkID0gMTIpCnRvYygpCmBgYAoKIyFbUGFyYWxsZWwgZnJlYWRdKGZyZWFkLnBuZykKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CnRpYygpCmZ3cml0ZShDaGlDcmltZXNfZHQsIGZpbGU9ImRhdGEvQ3JpbWVzXy1fMjAwMV90b19wcmVzZW50MDEuY3N2IiwgblRocmVhZD0xMikKdG9jKCkKYGBgCgojIVtQYXJhbGxlbCBmd3JpdGVdKGZ3cml0ZS5wbmcpCgojIyBVc2luZyByZWFkLmNzdigpIGlzIG11Y2ggc2xvd2VyLgoKYGBge3IgbWVzc2FnZT1GQUxTRX0KdGljKCkKQ2hpQ3JpbWVzX2RmIDwtIHJlYWRfY3N2KCJkYXRhL0NyaW1lc18tXzIwMDFfdG9fcHJlc2VudC5jc3YiKQp0b2MoKQpgYGAKCiMhW05vdCBwYXJhbGxlbCByZWFkX2Nzdl0ocmVhZF9kZi5wbmcpCgpgYGB7cn0KY29tcGFyZWRmKENoaUNyaW1lc19kdCwgQ2hpQ3JpbWVzX2RmKQoKcm0oQ2hpQ3JpbWVzX2RmKQpgYGAKCkFzIGFuIGFzaWRlLCBjaGVjayBvdXQgdGhlIGZzdCBwYWNrYWdlIGFuZCB0aGUgKndyaXRlX2ZzdCgpKiBmdW5jdGlvbiB0aGF0IHN0b3JlcyB0aGUgZGF0YSBpbiBhIGNvbXByZXNzZWQgZm9ybWF0LiAgSG93IG11Y2ggc3BhY2UgaXMgc2F2ZWQgc3RvcmluZyB0aGUgZGF0YSBpbiBhIC5mc3QgZmlsZT8gCgpgYGB7cn0KdGljKCkKd3JpdGVfZnN0KENoaUNyaW1lc19kdCwgImRhdGEvQ3JpbWVzXy1fMjAwMV90b19wcmVzZW50LmZzdCIsIGNvbXByZXNzID0gMTAwLCB1bmlmb3JtX2VuY29kaW5nID0gVFJVRSkKdG9jKCkKYGBgCgpUaGVuIHdlIGNhbiByZWFkIHRoZSBkYXRhIGludG8gYSBkYXRhLnRhYmxlIGluIFIgdXNpbmcgdGhlICpyZWFkX2ZzdCgpKiBSIGZ1bmN0aW9uLgoKYGBge3J9CnRpYygpCkNoaUNyaW1lczJfZHQgPC0gcmVhZF9mc3QoImRhdGEvQ3JpbWVzXy1fMjAwMV90b19wcmVzZW50LmZzdCIsCiAgY29sdW1ucyA9IE5VTEwsCiAgZnJvbSA9IDEsCiAgdG8gPSBOVUxMLAogIGFzLmRhdGEudGFibGUgPSBUUlVFLAogIG9sZF9mb3JtYXQgPSBGQUxTRQopCnRvYygpCgpkaW0oQ2hpQ3JpbWVzMl9kdCkKCnJtKENoaUNyaW1lczJfZHQpCmBgYAoKTm90ZSB0aGF0IHRoZSBmc3QgcGFja2FnZSBjYW4gY3JlYXRlIGEgbGluayB0byB0aGUgLmZzdCBmaWxlIHRoYXQgaXMgc2F2ZWQgb24geW91ciBoYXJkZHJpdmUuICBUaGlzIGxpbmsgY2FuIGJlIHVzZWQgdG8gYWNjZXNzIHRoZSBkYXRhIHdpdGhvdXQgbG9hZGluZyBpdCBpbnRvIG1lbW9yeS4KCmBgYHtyfQpDaGlDcmltZXMyX2ZzIDwtIGZzdCgiZGF0YS9DcmltZXNfLV8yMDAxX3RvX3ByZXNlbnQuZnN0IikKCmRpbShDaGlDcmltZXMyX2ZzKQpgYGAKIyNJbiBtZW1vcnkKCmBgYHtyfQpDaGlDcmltZXMyX2ZzICU+JSBkaW0oKQpgYGAKCkFuYWx5emUgdGhlIGRhdGEgaW4gdGhlIGRhdGEudGFibGUKCmBgYHtyfQpDaGlDcmltZXNfZHQgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQp0aWMoKQpDaGlDcmltZXNfZHQgJT4lIGdyb3VwX2J5KGBGQkkgQ29kZWApICU+JQogIHN1bW1hcmlzZShuPW4oKSkKdG9jKCkKCmBgYAoKYGBge3J9CkNoaUNyaW1lc19kdCAlPiUgZ3JvdXBfYnkoWWVhcikgJT4lCiAgc3VtbWFyaXNlKG49bigpKQpgYGAKCmBgYHtyfQpDaGlDcmltZXNfZHQgJT4lIGdyb3VwX2J5KFllYXIsIGBDb21tdW5pdHkgQXJlYWApICU+JQogIHN1bW1hcmlzZShuPW4oKSwgQXJyZXN0X3RvdGFsID0gc3VtKEFycmVzdCksIEFycmVzdF9yYXRlPW1lYW4oQXJyZXN0KSkgJT4lCiAgZ2dwbG90KGFlcyh4PWBDb21tdW5pdHkgQXJlYWAsIHk9YEFycmVzdF9yYXRlYCkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKCkKYGBgCgpMZXRzIG1ha2UgdGhlIHBsb3RzIGluIHRoZSBbRGFzaGJvYXJkXShodHRwczovL2RhdGEuY2l0eW9mY2hpY2Fnby5vcmcvUHVibGljLVNhZmV0eS9DcmltZXMtMjAwMS10by1wcmVzZW50LURhc2hib2FyZC81Y2Q2LXJ5NWcpLgoKTnVtYmVyIG9mIFJlcG9ydGVkIENyaW1lcyBieSBEYXRlLgpEYXRlIHdoZW4gdGhlIGluY2lkZW50IG9jY3VycmVkLiB0aGlzIGlzIHNvbWV0aW1lcyBhIGJlc3QgZXN0aW1hdGUuCgoKYGBge3J9CkNoaUNyaW1lc19kdCAlPiUgbXV0YXRlKERhdGUyID0gbWR5X2htcyhEYXRlLHR6PVN5cy50aW1lem9uZSgpKSkgJT4lCiAgbXV0YXRlKGRheSA9IGRhdGUoRGF0ZTIpKSAlPiUKICBncm91cF9ieShkYXkpICU+JQogIHN1bW1hcml6ZShuPW4oKSkgJT4lCiAgZ2dwbG90KGFlcyh4PWRheSwgeT1uKSkgKwogIGdlb21fbGluZSgpCmBgYAoKTnVtYmVyIG9mIFJlcG9ydGVkIENyaW1lcyBieSBQcmltYXJ5IFR5cGUuClRoZSBwcmltYXJ5IGRlc2NyaXB0aW9uIG9mIHRoZSBJVUNSIGNvZGUuCgoKYGBge3J9CkNoaUNyaW1lc19kdCAlPiUgCiAgZ2dwbG90KGFlcyhBcnJlc3QpKSArCiAgZ2VvbV9iYXIoKQpgYGAKCmBgYHtyfQpDaGlDcmltZXNfZHQgJT4lIAogIGdncGxvdChhZXMoYExvY2F0aW9uIERlc2NyaXB0aW9uYCkpICsKICBnZW9tX2JhcigpCmBgYAoKYGBge3J9CkNoaUNyaW1lc19kdCAlPiUgCiAgZ2dwbG90KGFlcyhgUHJpbWFyeSBUeXBlYCkpICsKICBnZW9tX2JhcigpCmBgYAoKCmBgYHtyfQpDaGlDcmltZXNfZHQgJT4lIAogIGdncGxvdChhZXMoYERpc3RyaWN0YCkpICsKICBnZW9tX2JhcigpCmBgYAoKCmBgYHtyfQpDaGlDcmltZXNfZHQgJT4lIAogIGdncGxvdChhZXMoYERvbWVzdGljYCkpICsKICBnZW9tX2JhcigpCmBgYAoKIyMgVGVzdCBvdXQgZGlzay5mcmFtZQoKYGBge3J9CnNldHVwX2Rpc2suZnJhbWUoKQoKIyB0aGlzIHdpbGwgYWxsb3cgdW5saW1pdGVkIGFtb3VudCBvZiBkYXRhIHRvIGJlIHBhc3NlZCBmcm9tIHdvcmtlciB0byB3b3JrZXIKb3B0aW9ucyhmdXR1cmUuZ2xvYmFscy5tYXhTaXplID0gSW5mKQpgYGAKYGBge3J9CmxpYnJhcnkobnljZmxpZ2h0czEzKQoKIyBjb252ZXJ0IHRoZSBmbGlnaHRzIGRhdGEgdG8gYSBkaXNrLmZyYW1lIGFuZCBzdG9yZSB0aGUgZGlzay5mcmFtZSBpbiB0aGUgZm9sZGVyCiMgInRtcF9mbGlnaHRzIiBhbmQgb3ZlcndyaXRlIGFueSBjb250ZW50IGlmIG5lZWRlZApmbGlnaHRzLmRmIDwtIGFzLmRpc2suZnJhbWUoCiAgZmxpZ2h0cywgCiAgb3V0ZGlyID0gZmlsZS5wYXRoKCJkYXRhX2Rpc2tfZnJhbWUiLCAidG1wX2ZsaWdodHMuZGYiKSwKICBvdmVyd3JpdGUgPSBUUlVFKQpmbGlnaHRzLmRmCmBgYAoKYGBge3J9CmZsaWdodHMuZGYxIDwtIHNlbGVjdChmbGlnaHRzLmRmLCB5ZWFyOmRheSwgYXJyX2RlbGF5LCBkZXBfZGVsYXkpCmZsaWdodHMuZGYxCgpjbGFzcyhmbGlnaHRzLmRmMSkKYGBgCgpgYGB7cn0KY29sbGVjdChmbGlnaHRzLmRmMSkgJT4lIGhlYWQoMikKCmZpbHRlcihmbGlnaHRzLmRmLCBkZXBfZGVsYXkgPiAxMDAwKSAlPiUgY29sbGVjdCAlPiUgaGVhZCgyKQpgYGAKCmBgYHtyfQpjNCA8LSBmbGlnaHRzICU+JQogIGZpbHRlcihtb250aCA9PSA1LCBkYXkgPT0gMTcsIGNhcnJpZXIgJWluJSBjKCdVQScsICdXTicsICdBQScsICdETCcpKSAlPiUKICBzZWxlY3QoY2FycmllciwgZGVwX2RlbGF5LCBhaXJfdGltZSwgZGlzdGFuY2UpICU+JQogIG11dGF0ZShhaXJfdGltZV9ob3VycyA9IGFpcl90aW1lIC8gNjApICU+JQogIGNvbGxlY3QgJT4lCiAgYXJyYW5nZShjYXJyaWVyKSMgYXJyYW5nZSBzaG91bGQgb2NjdXIgYWZ0ZXIgYGNvbGxlY3RgCgpjNCAgJT4lIGhlYWQKYGBgCgpgYGB7cn0KZmxpZ2h0cy5kZiAlPiUKICBncm91cF9ieShjYXJyaWVyKSAlPiUgIyBub3RpY2UgdGhhdCBoYXJkX2dyb3VwX2J5IG5lZWRzIHRvIGJlIHNldAogIHN1bW1hcml6ZShjb3VudCA9IG4oKSwgbWVhbl9kZXBfZGVsYXkgPSBtZWFuKGRlcF9kZWxheSwgbmEucm09VCkpICU+JSAgIyBtZWFuIGZvbGxvd3Mgbm9ybWFsIFIgcnVsZXMKICBjb2xsZWN0ICU+JSAKICBhcnJhbmdlKGNhcnJpZXIpCmBgYAoKYGBge3J9CmZsaWdodHMuc2FtcGxlIDwtIGZsaWdodHMuZGYgJT4lIHNhbXBsZV9mcmFjKDAuMDEpICU+JSAKICBjb2xsZWN0IApmbGlnaHRzLnNhbXBsZQpgYGAKCgpgYGB7cn0KYmVlcCgpO2JlZXAoKTtiZWVwKHNvdW5kPTQpCmBgYAoK