1 Getting Started

library(tidyverse)
library(lubridate)
library(gridExtra)
library(scales)
library(zoo)
library(forecast)

1.1 Load and Prepare Data Set

dfraw <- read_csv(file="dfosf001.csv", 
               locale = locale(tz ="Australia/Sydney"))
dfraw %>% head()
dfraw %>% glimpse()
dfraw$OFFENCE_MONTH <- dfraw$OFFENCE_MONTH %>% dmy()
df <- dfraw %>% 
  select(OFFENCE_FINYEAR, OFFENCE_MONTH, OFFENCE_CODE, OFFENCE_DESC, FACE_VALUE, TOTAL_NUMBER, TOTAL_VALUE)
# raw data set specs
dfraw %>% spec()
cols(
  OFFENCE_FINYEAR = col_character(),
  OFFENCE_MONTH = col_character(),
  OFFENCE_CODE = col_integer(),
  OFFENCE_DESC = col_character(),
  LEGISLATION = col_character(),
  SECTION_CLAUSE = col_character(),
  FACE_VALUE = col_integer(),
  CAMERA_IND = col_character(),
  CAMERA_TYPE = col_character(),
  LOCATION_CODE = col_integer(),
  LOCATION_DETAILS = col_character(),
  SCHOOL_ZONE_IND = col_character(),
  SPEED_BAND = col_character(),
  SPEED_IND = col_character(),
  POINT_TO_POINT_IND = col_character(),
  RED_LIGHT_CAMERA_IND = col_character(),
  SPEED_CAMERA_IND = col_character(),
  SEATBELT_IND = col_character(),
  MOBILE_PHONE_IND = col_character(),
  PARKING_IND = col_character(),
  CINS_IND = col_character(),
  FOOD_IND = col_character(),
  BICYCLE_TOY_ETC_IND = col_character(),
  TOTAL_NUMBER = col_integer(),
  TOTAL_VALUE = col_integer()
)
# sample cleansed data
df %>% head() 
df %>% summarise( "From Date" = min(OFFENCE_MONTH),
                  "To Date" = max(OFFENCE_MONTH), 
                  "Total Revenue" = sum(TOTAL_VALUE,na.rm = T), 
                  "Number of Offences" = sum(TOTAL_NUMBER), 
                  "Min Offence Value" = min(FACE_VALUE), 
                  "Max Offence Value" = max(FACE_VALUE),
                  "Number of Records" = n()) -> dfSummary
knitr::kable(dfSummary)
From Date To Date Total Revenue Number of Offences Min Offence Value Max Offence Value Number of Records
2013-01-01 2019-02-01 4379485352 17946602 20 18455 289284

1.2 Offences with Exceptionaly High Fines

df %>%   group_by(OFFENCE_DESC) %>% 
  summarise(Value = first(FACE_VALUE)) %>% 
  top_n(5, Value  ) %>% 
  ggplot(aes(x = reorder(OFFENCE_DESC,Value), y = Value)) +
  geom_bar(stat ="identity") + #geom_label(aes(label=Value)) +
  theme_light() + 
  labs (x = "Offence Description") +
  coord_flip()

df %>%   group_by(OFFENCE_DESC) %>% 
  summarise(Value = first(FACE_VALUE)) %>% 
  top_n(-5, Value  ) %>% 
  ggplot(aes(x = reorder(OFFENCE_DESC,Value), y = Value)) +
  geom_bar(stat ="identity") + #geom_label(aes(label=Value)) +
  theme_light() + 
  labs (x = "Offence Description") +
  coord_flip()

2 Data Exploration

2.1 Preparing Plotting and Styling functions

# numbers metric formatter 
format_si <- function(...) {
  # Format a vector of numeric values according
  # to the International System of Units.
  # http://en.wikipedia.org/wiki/SI_prefix
  #
  # Based on code by Ben Tupper
  # https://stat.ethz.ch/pipermail/r-help/2012-January/299804.html
  # Args:
  #   ...: Args passed to format()
  #
  # Returns:
  #   A function to format a vector of strings using
  #   SI prefix notation
  #
  
  function(x) {
    limits <- c(1e-24, 1e-21, 1e-18, 1e-15, 1e-12,
                1e-9,  1e-6,  1e-3,  1e0,   1e3,
                1e6,   1e9,   1e12,  1e15,  1e18,
                1e21,  1e24)
    prefix <- c("y",   "z",   "a",   "f",   "p",
                "n",   "µ",   "m",   " ",   "k",
                "M",   "G",   "T",   "P",   "E",
                "Z",   "Y")
    
    # Vector with array indices according to position in intervals
    i <- findInterval(abs(x), limits)
    
    # Set prefix to " " for very small values < 1e-24
    i <- ifelse(i==0, which(limits == 1e0), i)
    
    paste(format(round(x/limits[i], 1),
                 trim=TRUE, scientific=FALSE, ...),
          prefix[i])
  }
}
# decluttering theme 
theme_dvn <- function() {
  retTheme = theme_minimal() + 
    theme(panel.grid = element_blank())
  
  return(retTheme)
}

2.2 Financial Year Summaries

# plotting function
topPlot <- function(df, v){
  
  df %>% group_by(OFFENCE_FINYEAR) %>% 
    summarise(Value = sum(TOTAL_VALUE), Number = sum(TOTAL_NUMBER)) %>% 
    ggplot(aes(x=OFFENCE_FINYEAR, y=!!as.name(v))) + 
    geom_bar(stat = "identity") + 
    labs( y = v, 
          x = "Financial Year", 
          title = paste0(v, " of offences by Financial Year")) +
    scale_y_continuous(label=format_si()) +
    theme_dvn()-> returnPlot
  
  return(returnPlot)
}
df %>% topPlot("Value")

df %>% topPlot("Number")

dfTop <- df %>% group_by(OFFENCE_FINYEAR) %>% 
  summarise(Value = sum(TOTAL_VALUE), Number = sum(TOTAL_NUMBER))
knitr::kable(dfTop, caption = "Top Offences by Year")
OFFENCE_FINYEAR Value Number
2012-2013 287861625 1304922
2013-2014 650827664 2842871
2014-2015 671614637 2840545
2015-2016 715113972 2937351
2016-2017 766545273 3011925
2017-2018 788953652 3101149
2018-2019 498568529 1907839

2.3 Highest Revnue Offences by Year

First must prepare the plots to aggregate by year and generate plot of top

# aggregates data set
df %>% mutate(Year = year(OFFENCE_MONTH)) %>% 
  group_by(Year, OFFENCE_DESC) %>%
  summarise(Value = sum(TOTAL_VALUE)) %>% 
  #tally (TOTAL_VALUE) %>% 
  top_n(10, Value) %>% 
  ungroup() -> df2
# list of plots by year
pl <- c()
for (y in 2013:2019){
  df2 %>% 
    filter(Year == y) %>% 
    ggplot(aes(x = reorder(OFFENCE_DESC,Value), y = Value)) +
    facet_grid(cols = vars( Year)) +
    geom_bar(stat ="identity") + #geom_label(aes(label=Value)) +
    theme_dvn() + 
    labs( x = "Offence Type")+
    theme(plot.title = element_text(hjust = -4))+
    scale_y_continuous(label=format_si())+
    coord_flip() -> p
  
 # print( p)
  pl[[toString(y)]] = p
  
}

Display the top offences by year

invisible(print(pl))
$`2013`

$`2014`

$`2015`

$`2016`

$`2017`

$`2018`

$`2019`

2.4 Increase in Parking offences

### plot a line 
df %>% filter(grepl("parking", df$OFFENCE_DESC)) %>% 
  group_by(OFFENCE_MONTH) %>% 
  summarise(Value = sum(TOTAL_VALUE)) %>% 
  ungroup() %>% 
  mutate(Year = year(OFFENCE_MONTH), 
         Month=month(OFFENCE_MONTH)) %>% 
  select(Year, Month, Value) %>% 
  ggplot(aes(Month, Value, group=Year)) + 
  geom_line(aes(color = as.factor(Year))) + # facet_grid(rows =vars( Year))
  scale_x_continuous(breaks = 1:12) + 
  scale_y_continuous(label = format_si()) + 
  theme_minimal() + 
  labs (
    color = "Year", 
    title = "Revenue of Parking Offences by Year ", 
    caption = "Significant Increase of Parking Offences Revenue after June 2018 ")

3 Forecasting Revenue and Number of Offences beyond 2019

3.1 Prepare a timeseries object

df %>% group_by(OFFENCE_MONTH) %>% 
  summarise(Value = sum(TOTAL_VALUE), 
            Number = sum(TOTAL_NUMBER)) %>% 
  ungroup() %>% 
  select(Value, Number) -> tsdf
ts1 <- ts(tsdf, 
          start = c(year(dfSummary$`From Date`), month(dfSummary$`From Date`)), 
          frequency = 12) %>% head(-1)
plot_ts <- function (obj, endyear = 2019){
 return(
   autoplot(obj) + theme_dvn() + scale_y_continuous(label = format_si()) + scale_x_continuous(breaks = 2013:endyear)
 )
}
plot_ts(ts1[,1]) + labs(y="Value [AUD]")

plot_ts(ts1[,2]) + labs(y="Number of Offences")

3.2 Forecasting and Plotting the Results

revenueFit <- auto.arima(ts1[,1])
numberFit <- auto.arima(ts1[,2])
revenueForecast <- forecast(revenueFit, h =2*12)
plot_ts(revenueForecast, endyear = 2021) + labs(y="Value [AUD]", title="Revenue Forecast")

numberForecast <- forecast(numberFit,h=2*12)
plot_ts(numberForecast, endyear = 2021) + labs(y="Number of Offences", title="Total Offences Forecast")

The forecasting performed in this example is for explaratory purposes only, and is only meant to demostrate the ability to forecast with this data set.

Producing a meaningful and forecast requires deeper analysis of the timeseries decomposition and proper validation the forecast errors and accuracy on a test data set before accepting any of the forecasting results.

4 Conclusion

R markdown combined with plotting libraries like ggplot and other analysis library, like the time series function, can be used to explore data and communicate findings and insights. The output of the R markdown is not necessiarly interactive, but it can be used to publish the findings into the web as an HTML page, or to share as a Word or PowerPoint documents with stakeholders.

Applying these tools requires proper and careful preperation of the data, and a good handle on plotting library in order to produce correctly formatted visualisations.

LS0tDQp0aXRsZTogIk5TVyBSb2FkIE9mZmVuY2VzIGFuZCBQZW5hbHRpZXMiDQphdXRob3I6ICJNdXRheiBBYnUgR2hhemFsZWgiDQpkYXRlOiAiMTMvNC8yMDE5Ig0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogDQogICAgZmlnX2hlaWdodDogNw0KICAgIGZpZ193aWR0aDogOA0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IGx1bWVuDQogICAgdG9jOiB5ZXMNCiAgaHRtbF9kb2N1bWVudDogDQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IGx1bWVuDQogICAgdG9jOiB5ZXMNCi0tLQ0KDQojIEdldHRpbmcgU3RhcnRlZA0KDQpgYGB7ciBsb2FkTGlicmFyaWVzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUUsIHBhZ2VkLnByaW50PUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KbGlicmFyeShzY2FsZXMpDQoNCmxpYnJhcnkoem9vKQ0KbGlicmFyeShmb3JlY2FzdCkNCmBgYA0KDQojIyBMb2FkIGFuZCBQcmVwYXJlIERhdGEgU2V0IA0KDQpgYGB7ciBlY2hvPVRSVUUsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZScsIHBhZ2VkLnByaW50PVRSVUV9DQpkZnJhdyA8LSByZWFkX2NzdihmaWxlPSJkZm9zZjAwMS5jc3YiLCANCiAgICAgICAgICAgICAgIGxvY2FsZSA9IGxvY2FsZSh0eiA9IkF1c3RyYWxpYS9TeWRuZXkiKSkNCg0KDQpkZnJhdyAlPiUgaGVhZCgpDQpkZnJhdyAlPiUgc3BlYygpDQoNCg0KIyBjaGFuZ2luZyB0aGUgZGF0YSB0eXBlIGZvciBPRkZFTkNFX01PTlRIIHRvIGRhdGUNCmRmcmF3JE9GRkVOQ0VfTU9OVEggPC0gZGZyYXckT0ZGRU5DRV9NT05USCAlPiUgZG15KCkNCg0KIyBzZWxlY3RpbmcgYSBzdWIgc2V0IG9mIGNvbHVtbnMgDQpkZiA8LSBkZnJhdyAlPiUgDQogIHNlbGVjdChPRkZFTkNFX0ZJTllFQVIsIE9GRkVOQ0VfTU9OVEgsIE9GRkVOQ0VfQ09ERSwgT0ZGRU5DRV9ERVNDLCBGQUNFX1ZBTFVFLCBUT1RBTF9OVU1CRVIsIFRPVEFMX1ZBTFVFKQ0KYGBgDQoNCg0KYGBge3IgZWNobz1UUlVFfQ0KIyByYXcgZGF0YSBzZXQgc3BlY3MNCmRmcmF3ICU+JSBzcGVjKCkNCg0KDQojIHNhbXBsZSBjbGVhbnNlZCBkYXRhDQpkZiAlPiUgaGVhZCgpIA0KDQpgYGANCg0KDQpgYGB7cn0NCmRmICU+JSBzdW1tYXJpc2UoICJGcm9tIERhdGUiID0gbWluKE9GRkVOQ0VfTU9OVEgpLA0KICAgICAgICAgICAgICAgICAgIlRvIERhdGUiID0gbWF4KE9GRkVOQ0VfTU9OVEgpLCANCiAgICAgICAgICAgICAgICAgICJUb3RhbCBSZXZlbnVlIiA9IHN1bShUT1RBTF9WQUxVRSxuYS5ybSA9IFQpLCANCiAgICAgICAgICAgICAgICAgICJOdW1iZXIgb2YgT2ZmZW5jZXMiID0gc3VtKFRPVEFMX05VTUJFUiksIA0KICAgICAgICAgICAgICAgICAgIk1pbiBPZmZlbmNlIFZhbHVlIiA9IG1pbihGQUNFX1ZBTFVFKSwgDQogICAgICAgICAgICAgICAgICAiTWF4IE9mZmVuY2UgVmFsdWUiID0gbWF4KEZBQ0VfVkFMVUUpLA0KICAgICAgICAgICAgICAgICAgIk51bWJlciBvZiBSZWNvcmRzIiA9IG4oKSkgLT4gZGZTdW1tYXJ5DQoNCmtuaXRyOjprYWJsZShkZlN1bW1hcnksIGNhcHRpb24gPSAiU3VtbWFyeSBvZiBOU1cgUm9hZCBPZmZlbmNlcyBhbmQgUGVuYWx0aWVzIikNCmBgYA0KDQoNCiMjIE9mZmVuY2VzIHdpdGggRXhjZXB0aW9uYWx5IEhpZ2ggRmluZXMgDQoNCg0KYGBge3IgZmlnLndpZHRoPSAxMH0NCmRmICU+JSAgIGdyb3VwX2J5KE9GRkVOQ0VfREVTQykgJT4lIA0KICBzdW1tYXJpc2UoVmFsdWUgPSBmaXJzdChGQUNFX1ZBTFVFKSkgJT4lIA0KICB0b3Bfbig1LCBWYWx1ZSAgKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoT0ZGRU5DRV9ERVNDLFZhbHVlKSwgeSA9IFZhbHVlKSkgKw0KICBnZW9tX2JhcihzdGF0ID0iaWRlbnRpdHkiKSArICNnZW9tX2xhYmVsKGFlcyhsYWJlbD1WYWx1ZSkpICsNCiAgdGhlbWVfbGlnaHQoKSArIA0KICBsYWJzICh4ID0gIk9mZmVuY2UgRGVzY3JpcHRpb24iKSArDQogIGNvb3JkX2ZsaXAoKQ0KDQoNCmBgYA0KDQoNCmBgYHtyIGZpZy53aWR0aD0gMTB9DQpkZiAlPiUgICBncm91cF9ieShPRkZFTkNFX0RFU0MpICU+JSANCiAgc3VtbWFyaXNlKFZhbHVlID0gZmlyc3QoRkFDRV9WQUxVRSkpICU+JSANCiAgdG9wX24oLTUsIFZhbHVlICApICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihPRkZFTkNFX0RFU0MsVmFsdWUpLCB5ID0gVmFsdWUpKSArDQogIGdlb21fYmFyKHN0YXQgPSJpZGVudGl0eSIpICsgI2dlb21fbGFiZWwoYWVzKGxhYmVsPVZhbHVlKSkgKw0KICB0aGVtZV9saWdodCgpICsgDQogIGxhYnMgKHggPSAiT2ZmZW5jZSBEZXNjcmlwdGlvbiIpICsNCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KDQojIERhdGEgRXhwbG9yYXRpb24gDQoNCiMjIFByZXBhcmluZyBQbG90dGluZyBhbmQgU3R5bGluZyBmdW5jdGlvbnMNCg0KYGBge3IgbnVtYmVyRm9ybWF0dGVyfQ0KIyBudW1iZXJzIG1ldHJpYyBmb3JtYXR0ZXIgDQpmb3JtYXRfc2kgPC0gZnVuY3Rpb24oLi4uKSB7DQogICMgRm9ybWF0IGEgdmVjdG9yIG9mIG51bWVyaWMgdmFsdWVzIGFjY29yZGluZw0KICAjIHRvIHRoZSBJbnRlcm5hdGlvbmFsIFN5c3RlbSBvZiBVbml0cy4NCiAgIyBodHRwOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1NJX3ByZWZpeA0KICAjDQogICMgQmFzZWQgb24gY29kZSBieSBCZW4gVHVwcGVyDQogICMgaHR0cHM6Ly9zdGF0LmV0aHouY2gvcGlwZXJtYWlsL3ItaGVscC8yMDEyLUphbnVhcnkvMjk5ODA0Lmh0bWwNCiAgIyBBcmdzOg0KICAjICAgLi4uOiBBcmdzIHBhc3NlZCB0byBmb3JtYXQoKQ0KICAjDQogICMgUmV0dXJuczoNCiAgIyAgIEEgZnVuY3Rpb24gdG8gZm9ybWF0IGEgdmVjdG9yIG9mIHN0cmluZ3MgdXNpbmcNCiAgIyAgIFNJIHByZWZpeCBub3RhdGlvbg0KICAjDQogIA0KICBmdW5jdGlvbih4KSB7DQogICAgbGltaXRzIDwtIGMoMWUtMjQsIDFlLTIxLCAxZS0xOCwgMWUtMTUsIDFlLTEyLA0KICAgICAgICAgICAgICAgIDFlLTksICAxZS02LCAgMWUtMywgIDFlMCwgICAxZTMsDQogICAgICAgICAgICAgICAgMWU2LCAgIDFlOSwgICAxZTEyLCAgMWUxNSwgIDFlMTgsDQogICAgICAgICAgICAgICAgMWUyMSwgIDFlMjQpDQogICAgcHJlZml4IDwtIGMoInkiLCAgICJ6IiwgICAiYSIsICAgImYiLCAgICJwIiwNCiAgICAgICAgICAgICAgICAibiIsICAgIsK1IiwgICAibSIsICAgIiAiLCAgICJrIiwNCiAgICAgICAgICAgICAgICAiTSIsICAgIkciLCAgICJUIiwgICAiUCIsICAgIkUiLA0KICAgICAgICAgICAgICAgICJaIiwgICAiWSIpDQogICAgDQogICAgIyBWZWN0b3Igd2l0aCBhcnJheSBpbmRpY2VzIGFjY29yZGluZyB0byBwb3NpdGlvbiBpbiBpbnRlcnZhbHMNCiAgICBpIDwtIGZpbmRJbnRlcnZhbChhYnMoeCksIGxpbWl0cykNCiAgICANCiAgICAjIFNldCBwcmVmaXggdG8gIiAiIGZvciB2ZXJ5IHNtYWxsIHZhbHVlcyA8IDFlLTI0DQogICAgaSA8LSBpZmVsc2UoaT09MCwgd2hpY2gobGltaXRzID09IDFlMCksIGkpDQogICAgDQogICAgcGFzdGUoZm9ybWF0KHJvdW5kKHgvbGltaXRzW2ldLCAxKSwNCiAgICAgICAgICAgICAgICAgdHJpbT1UUlVFLCBzY2llbnRpZmljPUZBTFNFLCAuLi4pLA0KICAgICAgICAgIHByZWZpeFtpXSkNCiAgfQ0KfQ0KDQoNCmBgYA0KDQoNCg0KYGBge3IgZGVjbHV0dGVyfQ0KIyBkZWNsdXR0ZXJpbmcgdGhlbWUgDQp0aGVtZV9kdm4gPC0gZnVuY3Rpb24oKSB7DQogIHJldFRoZW1lID0gdGhlbWVfbWluaW1hbCgpICsgDQogICAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkNCiAgDQogIHJldHVybihyZXRUaGVtZSkNCn0NCmBgYA0KDQoNCiMjIEZpbmFuY2lhbCBZZWFyIFN1bW1hcmllcw0KDQpgYGB7cn0NCg0KIyBwbG90dGluZyBmdW5jdGlvbg0KdG9wUGxvdCA8LSBmdW5jdGlvbihkZiwgdil7DQogIA0KICBkZiAlPiUgZ3JvdXBfYnkoT0ZGRU5DRV9GSU5ZRUFSKSAlPiUgDQogICAgc3VtbWFyaXNlKFZhbHVlID0gc3VtKFRPVEFMX1ZBTFVFKSwgTnVtYmVyID0gc3VtKFRPVEFMX05VTUJFUikpICU+JSANCiAgICBnZ3Bsb3QoYWVzKHg9T0ZGRU5DRV9GSU5ZRUFSLCB5PSEhYXMubmFtZSh2KSkpICsgDQogICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgDQogICAgbGFicyggeSA9IHYsIA0KICAgICAgICAgIHggPSAiRmluYW5jaWFsIFllYXIiLCANCiAgICAgICAgICB0aXRsZSA9IHBhc3RlMCh2LCAiIG9mIG9mZmVuY2VzIGJ5IEZpbmFuY2lhbCBZZWFyIikpICsNCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWw9Zm9ybWF0X3NpKCkpICsNCiAgICB0aGVtZV9kdm4oKS0+IHJldHVyblBsb3QNCiAgDQogIHJldHVybihyZXR1cm5QbG90KQ0KfQ0KDQoNCg0KYGBgDQoNCg0KYGBge3J9DQpkZiAlPiUgdG9wUGxvdCgiVmFsdWUiKQ0KZGYgJT4lIHRvcFBsb3QoIk51bWJlciIpDQoNCmRmVG9wIDwtIGRmICU+JSBncm91cF9ieShPRkZFTkNFX0ZJTllFQVIpICU+JSANCiAgc3VtbWFyaXNlKFZhbHVlID0gc3VtKFRPVEFMX1ZBTFVFKSwgTnVtYmVyID0gc3VtKFRPVEFMX05VTUJFUikpDQoNCmtuaXRyOjprYWJsZShkZlRvcCwgY2FwdGlvbiA9ICJPZmZlbmNlcyBieSBGaW5hbmNpYWwgWWVhciIpDQpgYGANCg0KDQojIyBIaWdoZXN0IFJldm51ZSBPZmZlbmNlcyBieSBZZWFyIA0KDQpGaXJzdCBtdXN0IHByZXBhcmUgdGhlIHBsb3RzIHRvIGFnZ3JlZ2F0ZSBieSB5ZWFyIGFuZCBnZW5lcmF0ZSBwbG90IG9mIHRvcCANCmBgYHtyfQ0KDQoNCiMgYWdncmVnYXRlcyBkYXRhIHNldA0KZGYgJT4lIG11dGF0ZShZZWFyID0geWVhcihPRkZFTkNFX01PTlRIKSkgJT4lIA0KICBncm91cF9ieShZZWFyLCBPRkZFTkNFX0RFU0MpICU+JQ0KICBzdW1tYXJpc2UoVmFsdWUgPSBzdW0oVE9UQUxfVkFMVUUpKSAlPiUgDQogICN0YWxseSAoVE9UQUxfVkFMVUUpICU+JSANCiAgdG9wX24oMTAsIFZhbHVlKSAlPiUgDQogIHVuZ3JvdXAoKSAtPiBkZjINCg0KDQojIGxpc3Qgb2YgcGxvdHMgYnkgeWVhcg0KcGwgPC0gYygpDQpmb3IgKHkgaW4gMjAxMzoyMDE5KXsNCiAgZGYyICU+JSANCiAgICBmaWx0ZXIoWWVhciA9PSB5KSAlPiUgDQogICAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihPRkZFTkNFX0RFU0MsVmFsdWUpLCB5ID0gVmFsdWUpKSArDQogICAgZmFjZXRfZ3JpZChjb2xzID0gdmFycyggWWVhcikpICsNCiAgICBnZW9tX2JhcihzdGF0ID0iaWRlbnRpdHkiKSArICNnZW9tX2xhYmVsKGFlcyhsYWJlbD1WYWx1ZSkpICsNCiAgICB0aGVtZV9kdm4oKSArIA0KICAgIGxhYnMoIHggPSAiT2ZmZW5jZSBUeXBlIikrDQogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IC00KSkrDQogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVsPWZvcm1hdF9zaSgpKSsNCiAgICBjb29yZF9mbGlwKCkgLT4gcA0KICANCiAjIHByaW50KCBwKQ0KICBwbFtbdG9TdHJpbmcoeSldXSA9IHANCiAgDQp9DQoNCg0KDQpgYGANCg0KRGlzcGxheSB0aGUgdG9wIG9mZmVuY2VzIGJ5IHllYXINCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KaW52aXNpYmxlKHByaW50KHBsKSkNCg0KYGBgDQoNCg0KIyMgSW5jcmVhc2UgaW4gUGFya2luZyBvZmZlbmNlcyANCg0KYGBge3J9DQojIyMgcGxvdCBhIGxpbmUgDQoNCmRmICU+JSBmaWx0ZXIoZ3JlcGwoInBhcmtpbmciLCBkZiRPRkZFTkNFX0RFU0MpKSAlPiUgDQogIGdyb3VwX2J5KE9GRkVOQ0VfTU9OVEgpICU+JSANCiAgc3VtbWFyaXNlKFZhbHVlID0gc3VtKFRPVEFMX1ZBTFVFKSkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBtdXRhdGUoWWVhciA9IHllYXIoT0ZGRU5DRV9NT05USCksIA0KICAgICAgICAgTW9udGg9bW9udGgoT0ZGRU5DRV9NT05USCkpICU+JSANCiAgc2VsZWN0KFllYXIsIE1vbnRoLCBWYWx1ZSkgJT4lIA0KICBnZ3Bsb3QoYWVzKE1vbnRoLCBWYWx1ZSwgZ3JvdXA9WWVhcikpICsgDQogIGdlb21fbGluZShhZXMoY29sb3IgPSBhcy5mYWN0b3IoWWVhcikpKSArICMgZmFjZXRfZ3JpZChyb3dzID12YXJzKCBZZWFyKSkNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IDE6MTIpICsgDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbCA9IGZvcm1hdF9zaSgpKSArIA0KICB0aGVtZV9taW5pbWFsKCkgKyANCiAgbGFicyAoDQogICAgY29sb3IgPSAiWWVhciIsIA0KICAgIHRpdGxlID0gIlJldmVudWUgb2YgUGFya2luZyBPZmZlbmNlcyBieSBZZWFyICIsIA0KICAgIGNhcHRpb24gPSAiU2lnbmlmaWNhbnQgSW5jcmVhc2Ugb2YgUGFya2luZyBPZmZlbmNlcyBSZXZlbnVlIGFmdGVyIEp1bmUgMjAxOCAiKQ0KDQoNCmBgYA0KDQojIEZvcmVjYXN0aW5nIFJldmVudWUgYW5kIE51bWJlciBvZiBPZmZlbmNlcyBiZXlvbmQgMjAxOQ0KDQoNCiMjIFByZXBhcmUgYSB0aW1lc2VyaWVzIG9iamVjdA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGYgJT4lIGdyb3VwX2J5KE9GRkVOQ0VfTU9OVEgpICU+JSANCiAgc3VtbWFyaXNlKFZhbHVlID0gc3VtKFRPVEFMX1ZBTFVFKSwgDQogICAgICAgICAgICBOdW1iZXIgPSBzdW0oVE9UQUxfTlVNQkVSKSkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBzZWxlY3QoVmFsdWUsIE51bWJlcikgLT4gdHNkZg0KDQp0czEgPC0gdHModHNkZiwgDQogICAgICAgICAgc3RhcnQgPSBjKHllYXIoZGZTdW1tYXJ5JGBGcm9tIERhdGVgKSwgbW9udGgoZGZTdW1tYXJ5JGBGcm9tIERhdGVgKSksIA0KICAgICAgICAgIGZyZXF1ZW5jeSA9IDEyKSAlPiUgaGVhZCgtMSkNCg0KDQoNCnBsb3RfdHMgPC0gZnVuY3Rpb24gKG9iaiwgZW5keWVhciA9IDIwMTkpew0KIHJldHVybigNCiAgIGF1dG9wbG90KG9iaikgKyB0aGVtZV9kdm4oKSArIHNjYWxlX3lfY29udGludW91cyhsYWJlbCA9IGZvcm1hdF9zaSgpKSArIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAyMDEzOmVuZHllYXIpDQogKQ0KfQ0KDQpwbG90X3RzKHRzMVssMV0pICsgbGFicyh5PSJWYWx1ZSBbQVVEXSIpDQpwbG90X3RzKHRzMVssMl0pICsgbGFicyh5PSJOdW1iZXIgb2YgT2ZmZW5jZXMiKQ0KDQoNCg0KYGBgDQoNCiMjIEZvcmVjYXN0aW5nIGFuZCBQbG90dGluZyB0aGUgUmVzdWx0cw0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcmV2ZW51ZUZpdCA8LSBhdXRvLmFyaW1hKHRzMVssMV0pDQpudW1iZXJGaXQgPC0gYXV0by5hcmltYSh0czFbLDJdKQ0KDQpyZXZlbnVlRm9yZWNhc3QgPC0gZm9yZWNhc3QocmV2ZW51ZUZpdCwgaCA9MioxMikNCnBsb3RfdHMocmV2ZW51ZUZvcmVjYXN0LCBlbmR5ZWFyID0gMjAyMSkgKyBsYWJzKHk9IlZhbHVlIFtBVURdIiwgdGl0bGU9IlJldmVudWUgRm9yZWNhc3QiKQ0KDQoNCm51bWJlckZvcmVjYXN0IDwtIGZvcmVjYXN0KG51bWJlckZpdCxoPTIqMTIpDQpwbG90X3RzKG51bWJlckZvcmVjYXN0LCBlbmR5ZWFyID0gMjAyMSkgKyBsYWJzKHk9Ik51bWJlciBvZiBPZmZlbmNlcyIsIHRpdGxlPSJUb3RhbCBPZmZlbmNlcyBGb3JlY2FzdCIpDQoNCmBgYA0KDQpUaGUgZm9yZWNhc3RpbmcgcGVyZm9ybWVkIGluIHRoaXMgZXhhbXBsZSBpcyBmb3IgZXhwbGFyYXRvcnkgcHVycG9zZXMgb25seSwgYW5kIGlzIG9ubHkgbWVhbnQgdG8gZGVtb3N0cmF0ZSB0aGUgYWJpbGl0eSB0byBmb3JlY2FzdCB3aXRoIHRoaXMgZGF0YSBzZXQuIA0KDQpQcm9kdWNpbmcgYSBtZWFuaW5nZnVsIGFuZCBmb3JlY2FzdCByZXF1aXJlcyBkZWVwZXIgYW5hbHlzaXMgb2YgdGhlIHRpbWVzZXJpZXMgZGVjb21wb3NpdGlvbiBhbmQgcHJvcGVyIHZhbGlkYXRpb24gdGhlIGZvcmVjYXN0IGVycm9ycyBhbmQgYWNjdXJhY3kgb24gYSB0ZXN0IGRhdGEgc2V0IGJlZm9yZSBhY2NlcHRpbmcgYW55IG9mIHRoZSBmb3JlY2FzdGluZyByZXN1bHRzLiANCg0KDQojIENvbmNsdXNpb24gDQoNClIgbWFya2Rvd24gY29tYmluZWQgd2l0aCBwbG90dGluZyBsaWJyYXJpZXMgbGlrZSBnZ3Bsb3QgYW5kIG90aGVyIGFuYWx5c2lzIGxpYnJhcnksIGxpa2UgdGhlIHRpbWUgc2VyaWVzIGZ1bmN0aW9uLCBjYW4gYmUgdXNlZCB0byBleHBsb3JlIGRhdGEgYW5kIGNvbW11bmljYXRlIGZpbmRpbmdzIGFuZCBpbnNpZ2h0cy4gVGhlIG91dHB1dCBvZiB0aGUgUiBtYXJrZG93biBpcyBub3QgbmVjZXNzaWFybHkgaW50ZXJhY3RpdmUsIGJ1dCBpdCBjYW4gYmUgdXNlZCB0byBwdWJsaXNoIHRoZSBmaW5kaW5ncyBpbnRvIHRoZSB3ZWIgYXMgYW4gSFRNTCBwYWdlLCBvciB0byBzaGFyZSBhcyBhIFdvcmQgb3IgUG93ZXJQb2ludCBkb2N1bWVudHMgd2l0aCBzdGFrZWhvbGRlcnMuIA0KDQpBcHBseWluZyB0aGVzZSB0b29scyByZXF1aXJlcyBwcm9wZXIgYW5kIGNhcmVmdWwgcHJlcGVyYXRpb24gb2YgdGhlIGRhdGEsIGFuZCBhIGdvb2QgaGFuZGxlIG9uIHBsb3R0aW5nIGxpYnJhcnkgaW4gb3JkZXIgdG8gcHJvZHVjZSBjb3JyZWN0bHkgZm9ybWF0dGVkIHZpc3VhbGlzYXRpb25zLiANCg0K