INTRODUCTION

In this case study, I will be analyzing a public dataset for a fictional company called Cyclistic, provided by the course. Here, I will be using R programming language for this analysis because of its potential benefits to reproducibility, transparency, easy statistical analysis tools and data visualizations.

The following sets of data analysis process will be followed:

Ask,

Prepare,

Process,

Analyze,

Share,

Act.

The case study road map as listed below will be followed on each step

Codes, when needed.

Key tasks.

Deliverable.

I will use Cyclistic’s trip data ranging from July 2023 to June 2024 to analyze and identify trends.The data has been made available by Motivate International Inc. under this license. Datasets are available here link. Key tasks

Download data and store it appropriately. Data has been downloaded and copies have been stored securely on my computer. Identify how it’s organized. The data is in CSV (comma-separated values) format, and there are a total of 13 columns. Sort and filter the data. For this analysis, I will be using data for the year 2019 and 2020. Determine the credibility of the data. For the purposes of this case study, the datasets are appropriate and will enable me to answer the business questions. The data has been made available by Motivate International Inc. This is public data that I can use to explore how different customer types are using Cyclistic bikes. But data-privacy issues will prohibit me from using rider’s personally identifiable information and this will prevent me from determining if riders have purchased multiple single passes. All ride ids are unique. Deliverable

A description of all data sources used The main source of all the data used was provided by the Cyclistic Company.

library(tidyverse)
library(DT)
library(lubridate)
options(scipen = 999)
library(modeest)

Importing Data from 2023

Import Data from 2024

Combining Rows

#bind rows 2023
q3_2023 <- rbind(July_2023,August_2023,September_2023)


q4_2023 <- rbind(October_2023,Novemeber_2023,December_2023)

#bindrows 2024
q1_2024 <- rbind(January_2024,February_2024,March_2024)


q2_2024 <- rbind(April_2024,May_2024,June_2024)


all_trips <-  rbind(q3_2023,q4_2023,q1_2024,q2_2024)

PROCESS

Cleaning up data and adding data to prepare for analysis

Key tasks

Check the data for errors.

Choose your tools.

Transform the data so you can work with it effectively.

Document the cleaning process.

Deliverables

Documentation of any cleaning or manipulation of data. I inspected the new table that has been created using the following code chunks

colnames(all_trips)  #List of column names
nrow(all_trips)  #How many rows are in data frame
dim(all_trips)  #Dimensions of the data frame
head(all_trips)  #See the first 6 rows of data frame
tail(all_trips)  #see the last 6 rows of data frame
str(all_trips)  #See list of columns and data types (numeric, character, etc)
summary(all_trips)  #Statistical summary of data. Mainly for numeric

Adding columns that list the date, month, day, and year of each ride.

This will allow us to aggregate ride data for each month, day, or year. Therefore, the code chunks used are as follows

all_trips$date <- as.Date(all_trips$started_at) #The default format is yyyy-mm-dd
all_trips$month <- format(as.Date(all_trips$date), "%m")
all_trips$day <- format(as.Date(all_trips$date), "%d")
all_trips$year <- format(as.Date(all_trips$date), "%Y")
all_trips$day_of_week <- format(as.Date(all_trips$date), "%A")

Adding a “ride_length” calculation to all_trips (in seconds) convert to minutes

all_trips$ride_length <- difftime(all_trips$ended_at,all_trips$started_at)

str(all_trips)  #to inspect the structure of the columns

Convert “ride_length” from factor to numeric so we can run calculations on the data

all_trips$ride_length <- as.numeric(as.character(all_trips$ride_length))

is.numeric(all_trips$ride_length)

Remove “bad” data

The dataframe includes a few hundred entries when bikes were taken out of docks and checked for quality by Divvy or ride_length was negative or Zero, so I created a new version of the dataframe (v2) since data is being removed

all_trips[!(all_trips$start_station_name == "HQ QR" | all_trips$ride_length<0),]

all_trips_v2 <- all_trips[!(all_trips$start_station_name == "HQ QR" | all_trips$ride_length<0),]

sum(duplicated(all_trips_v2))

ANALYZE

All the data has been stored appropriately and has been prepared for analysis, so they are ready for exploration. Key tasks

Aggregate your data so it’s useful and accessible. Organize and format your data. Perform calculations. Identify trends and relationships. Deliverables

A summary of your analysis Conduct descriptive analysis - Descriptive analysis on ride_length (all figures in seconds)

mean(all_trips_v2$ride_length,na_rm=T) 
median(all_trips_v2$ride_length,na_rm=T) 
mfv(all_trips_v2$ride_length,na_rm=T) 
max(all_trips_v2$ride_length,na_rm=T) 
min(all_trips_v2$ride_length,na_rm=T) 
range = max(all_trips_v2$ride_length,na_rm=T) - min(all_trips_v2$ride_length,na_rm=T) 
range

Let’s visualize members and casuals by the total ride taken (ride count)

all_trips_v2 %>% 
  group_by(member_casual) %>% 
  summarise(ride_count = n()) %>%

ggplot(aes(member_casual,ride_count,fill=member_casual))+geom_col(position = 'dodge')+
  labs(title = 'Total rides taken (ride_count) of Members and Casual riders')+
  theme_classic()+geom_col(width = .5,position = position_dodge(width=.5))+
  geom_text(aes(label=ride_count),size=4)+
  theme(plot.title = element_text(size=12, hjust = .4 ))
  
all_trips_v2

From the above graph, we can observe that there are far more riders as registered members compared to casual rides based on the ride count.




Let’s see the average time ride by each day for members vs casual users

wdaytoavgridel <- all_trips_v2 %>% 
  select(ride_length, member_casual,day_of_week) %>% 
   mutate(ride_length=ride_length/60) %>% # ride length converted from seconds to minutes 

  group_by(member_casual,day_of_week) %>% 
  summarise(avgride_length=round(mean(ride_length)))
datatable(wdaytoavgridel)

average ride time by each day for member vs casual users (in minutes)

memcas_durationandridenumber <-  all_trips_v2 %>%  #week trends
    mutate(ride_length=ride_length/60) %>% # ride length converted from seconds to minutes
  mutate(weekday = wday(started_at, label = TRUE)) %>%
  group_by(member_casual, weekday) %>%  
  summarise(number_of_rides = n(), average_duration = round(mean(ride_length) )) %>%
  arrange(member_casual, weekday)

datatable(memcas_durationandridenumber)

total rides and average ride_time duration(in minutes) by each day for members vs casual riders


Let’s visualize the above table by days of the week and number of rides taken by member and casual riders.

all_trips_v2 %>% 
    mutate(ride_length=ride_length/60) %>% # ride length converted from seconds to minutes
  mutate(weekday = wday(started_at, label = TRUE)) %>%
  group_by(member_casual, weekday) %>%  
  summarise(number_of_rides = n(), average_duration = round(mean(ride_length))) %>%
  arrange(member_casual, weekday) %>% 
  ggplot(aes(weekday,number_of_rides,fill=member_casual))+theme_classic()+geom_col()+geom_text(aes(label=number_of_rides),size=2.8)+ggtitle('Total rides of Members and Casual riders Vs. Day of the week')+
  theme(plot.title = element_text(size=11,hjust=.3))
  


Let’s visualize the average duration of Members and Casual riders Vs. Day of the week

all_trips_v2 %>%  
  mutate(ride_length=ride_length/60) %>% 
  mutate(weekday = wday(started_at, label = TRUE)) %>% 
  group_by(member_casual, weekday) %>% 
  summarise(number_of_rides = n()
            ,average_duration = round(mean(ride_length))) %>% 
  arrange(member_casual, weekday)  %>% 
  ggplot(aes(x = weekday, y = average_duration, fill = member_casual)) +
  geom_col(position = "dodge") +
  labs(title ="Average duration (minutes) of Members and Casual riders") +theme_classic()+
  geom_col(width=0.5)+geom_text(aes(label=average_duration),size=2.8)+theme(plot.title = element_text(size = 10,hjust=.05))

From the graph above, it is observed that the casual riders ride the longest throughout the weekend and shortest duration between Tuesday and Thursday, while registered riders ride at a faster more consistent pace during the week with the longest rides on the weekend.

Let’s create a visualization for Total rides by members and casual riders by month


all_trips_v2 %>%  
   
  group_by(member_casual, month) %>% 
  summarise(number_of_rides = n(),.groups="drop") %>% 
  arrange(member_casual, month)  %>% 
  ggplot(aes(x = month, y = number_of_rides, fill = member_casual)) +
  labs(title ="Total rides by Members and Casual riders by Month") +
  theme(axis.text.x = element_text(angle = 45)) +theme_classic()+
  geom_col(width=0.5)+geom_text(aes(label=number_of_rides),size=2.4)+
  theme(plot.title = element_text(size=11,hjust=.6))
  

From the above graph, it is observed that members have the highest number of rides throughout the year with August being the month with the highest number of rides for members whilst july has the most casual riders


Let’s compare Members and Casual riders depending on ride distance.


all_trips_v2 %>% 
  group_by(member_casual) %>% drop_na() %>%
  summarise(average_ride_length_kilometers = mean(ride_length)) %>%
mutate(average_ride_length_Miles = round(( average_ride_length_kilometers * 2)/3 )) %>% 
  select(member_casual,average_ride_length_Miles) %>% 
  ggplot(aes(x= member_casual,y= average_ride_length_Miles,fill=member_casual)) + 
  geom_col()+geom_text(aes(label = average_ride_length_Miles))+
  labs(title = "Mean distance traveled by Members and Casual riders")+
  theme(plot.title = element_text(size=11,hjust=.2))+theme_classic()
  

From the graph above, we can observe that the distance traveled by the casual riders is far more than the distance traveled by the member with a very large difference in miles. Roughly 855 miles is the average distance traveled by casual riders and 494 miles is the average distance traveled by registered members
note: converted kilometers to miles




Conclusion
  1. Members have more bikes compared to casual riders.
  2. We have more members riding in all months compared to casual riders.
  3. Casual riders travel for a longer time period.
  4. Members ride more throughout the entire weekday while the casual riders also have a high ride record during the weekends(Saturday and Sunday) compared to the other days of the week.
  5. Casual riders go farther in terms of distance.

Deliverable

I would recommend a weekend warrior membership that can take full advantage of casual rider’s busy lifestyle and behavioral patterns, plus implement levels of targeted marketing during months of June, July & August when Casual Riders are at their peak in riding.

LS0tDQp0aXRsZTogIkJpa2UgU2hhcmUiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmF1dGhvcjogSGFycmlzb24NCg0KLS0tDQoNCklOVFJPRFVDVElPTg0KDQogSW4gdGhpcyBjYXNlIHN0dWR5LCBJIHdpbGwgYmUgYW5hbHl6aW5nIGEgcHVibGljIGRhdGFzZXQgZm9yIGEgZmljdGlvbmFsIGNvbXBhbnkgY2FsbGVkIEN5Y2xpc3RpYywgcHJvdmlkZWQgYnkgdGhlIGNvdXJzZS4gSGVyZSwgSSB3aWxsIGJlIHVzaW5nIFIgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UgZm9yIHRoaXMgYW5hbHlzaXMgYmVjYXVzZSBvZiBpdHMgcG90ZW50aWFsIGJlbmVmaXRzIHRvIHJlcHJvZHVjaWJpbGl0eSwgdHJhbnNwYXJlbmN5LCBlYXN5IHN0YXRpc3RpY2FsIGFuYWx5c2lzIHRvb2xzIGFuZCBkYXRhIHZpc3VhbGl6YXRpb25zLg0KDQpUaGUgZm9sbG93aW5nIHNldHMgb2YgZGF0YSBhbmFseXNpcyBwcm9jZXNzIHdpbGwgYmUgZm9sbG93ZWQ6DQoNCkFzaywNCg0KUHJlcGFyZSwNCg0KUHJvY2VzcywNCg0KQW5hbHl6ZSwNCg0KU2hhcmUsDQoNCkFjdC4NCg0KVGhlIGNhc2Ugc3R1ZHkgcm9hZCBtYXAgYXMgbGlzdGVkIGJlbG93IHdpbGwgYmUgZm9sbG93ZWQgb24gZWFjaCBzdGVwDQoNCkNvZGVzLCB3aGVuIG5lZWRlZC4NCg0KS2V5IHRhc2tzLg0KDQpEZWxpdmVyYWJsZS4NCg0KDQoNCkkgd2lsbCB1c2UgQ3ljbGlzdGlj4oCZcyB0cmlwIGRhdGEgcmFuZ2luZyBmcm9tIEp1bHkgMjAyMyB0byBKdW5lIDIwMjQgdG8gYW5hbHl6ZSBhbmQgaWRlbnRpZnkgdHJlbmRzLlRoZSBkYXRhIGhhcyBiZWVuIG1hZGUgYXZhaWxhYmxlIGJ5IE1vdGl2YXRlIEludGVybmF0aW9uYWwgSW5jLiB1bmRlciB0aGlzIGxpY2Vuc2UuIERhdGFzZXRzIGFyZSBhdmFpbGFibGUgaGVyZSBsaW5rLg0KS2V5IHRhc2tzDQoNCkRvd25sb2FkIGRhdGEgYW5kIHN0b3JlIGl0IGFwcHJvcHJpYXRlbHkuDQpEYXRhIGhhcyBiZWVuIGRvd25sb2FkZWQgYW5kIGNvcGllcyBoYXZlIGJlZW4gc3RvcmVkIHNlY3VyZWx5IG9uIG15IGNvbXB1dGVyLg0KSWRlbnRpZnkgaG93IGl04oCZcyBvcmdhbml6ZWQuDQpUaGUgZGF0YSBpcyBpbiBDU1YgKGNvbW1hLXNlcGFyYXRlZCB2YWx1ZXMpIGZvcm1hdCwgYW5kIHRoZXJlIGFyZSBhIHRvdGFsIG9mIDEzIGNvbHVtbnMuDQpTb3J0IGFuZCBmaWx0ZXIgdGhlIGRhdGEuDQpGb3IgdGhpcyBhbmFseXNpcywgSSB3aWxsIGJlIHVzaW5nIGRhdGEgZm9yIHRoZSB5ZWFyIDIwMTkgYW5kIDIwMjAuDQpEZXRlcm1pbmUgdGhlIGNyZWRpYmlsaXR5IG9mIHRoZSBkYXRhLg0KRm9yIHRoZSBwdXJwb3NlcyBvZiB0aGlzIGNhc2Ugc3R1ZHksIHRoZSBkYXRhc2V0cyBhcmUgYXBwcm9wcmlhdGUgYW5kIHdpbGwgZW5hYmxlIG1lIHRvIGFuc3dlciB0aGUgYnVzaW5lc3MgcXVlc3Rpb25zLiBUaGUgZGF0YSBoYXMgYmVlbiBtYWRlIGF2YWlsYWJsZSBieSBNb3RpdmF0ZSBJbnRlcm5hdGlvbmFsIEluYy4gVGhpcyBpcyBwdWJsaWMgZGF0YSB0aGF0IEkgY2FuIHVzZSB0byBleHBsb3JlIGhvdyBkaWZmZXJlbnQgY3VzdG9tZXIgdHlwZXMgYXJlIHVzaW5nIEN5Y2xpc3RpYyBiaWtlcy4gQnV0IGRhdGEtcHJpdmFjeSBpc3N1ZXMgd2lsbCBwcm9oaWJpdCBtZSBmcm9tIHVzaW5nIHJpZGVy4oCZcyBwZXJzb25hbGx5IGlkZW50aWZpYWJsZSBpbmZvcm1hdGlvbiBhbmQgdGhpcyB3aWxsIHByZXZlbnQgbWUgZnJvbSBkZXRlcm1pbmluZyBpZiByaWRlcnMgaGF2ZSBwdXJjaGFzZWQgbXVsdGlwbGUgc2luZ2xlIHBhc3Nlcy4gQWxsIHJpZGUgaWRzIGFyZSB1bmlxdWUuDQpEZWxpdmVyYWJsZQ0KDQpBIGRlc2NyaXB0aW9uIG9mIGFsbCBkYXRhIHNvdXJjZXMgdXNlZA0KVGhlIG1haW4gc291cmNlIG9mIGFsbCB0aGUgZGF0YSB1c2VkIHdhcyBwcm92aWRlZCBieSB0aGUgQ3ljbGlzdGljIENvbXBhbnkuDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KERUKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCmxpYnJhcnkobW9kZWVzdCkNCmBgYA0KDQpJbXBvcnRpbmcgRGF0YSBmcm9tIDIwMjMNCg0KYGBge3IsZWNobz1GQUxTRX0NCiNyZWFkX2NzdigiQzpcVXNlcnNcdGhlcHlcRG93bmxvYWRzXGN5Y2xpc3QgZGF0YVwyMDIzMDctZGl2dnktdHJpcGRhdGEuY3N2IikNCg0KSnVseV8yMDIzIDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy90aGVweS9Eb3dubG9hZHMvYmlrZTIwMjMvMjAyMzA3LWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpBdWd1c3RfMjAyMyA8LSByZWFkLmNzdigiQzovVXNlcnMvdGhlcHkvRG93bmxvYWRzL2Jpa2UyMDIzLzIwMjMwOC1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KU2VwdGVtYmVyXzIwMjMgPC0gcmVhZC5jc3YgKCJDOi9Vc2Vycy90aGVweS9Eb3dubG9hZHMvYmlrZTIwMjMvMjAyMzA5LWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpPY3RvYmVyXzIwMjMgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL3RoZXB5L0Rvd25sb2Fkcy9iaWtlMjAyMy8yMDIzMTAtZGl2dnktdHJpcGRhdGEuY3N2IikNCk5vdmVtZWJlcl8yMDIzIDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy90aGVweS9Eb3dubG9hZHMvYmlrZTIwMjMvMjAyMzExLWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpEZWNlbWJlcl8yMDIzIDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy90aGVweS9Eb3dubG9hZHMvYmlrZTIwMjMvMjAyMzEyLWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQoNCg0KDQoNCg0KYGBgDQoNCkltcG9ydCBEYXRhIGZyb20gMjAyNA0KYGBge3IsZWNobz1GQUxTRX0NCkphbnVhcnlfMjAyNCA8LSByZWFkLmNzdigiQzovVXNlcnMvdGhlcHkvRG93bmxvYWRzL2Jpa2UyMDI0LzIwMjQwMS1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KRmVicnVhcnlfMjAyNCA8LSByZWFkLmNzdigiQzovVXNlcnMvdGhlcHkvRG93bmxvYWRzL2Jpa2UyMDI0LzIwMjQwMi1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KTWFyY2hfMjAyNCA8LSByZWFkLmNzdiAoIkM6L1VzZXJzL3RoZXB5L0Rvd25sb2Fkcy9iaWtlMjAyNC8yMDI0MDMtZGl2dnktdHJpcGRhdGEuY3N2IikNCkFwcmlsXzIwMjQgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL3RoZXB5L0Rvd25sb2Fkcy9iaWtlMjAyNC8yMDI0MDQtZGl2dnktdHJpcGRhdGEuY3N2IikNCk1heV8yMDI0IDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy90aGVweS9Eb3dubG9hZHMvYmlrZTIwMjQvMjAyNDA1LWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpKdW5lXzIwMjQgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL3RoZXB5L0Rvd25sb2Fkcy9iaWtlMjAyNC8yMDI0MDYtZGl2dnktdHJpcGRhdGEuY3N2IikNCmBgYA0KDQpDb21iaW5pbmcgUm93cw0KDQpgYGB7cn0NCiNiaW5kIHJvd3MgMjAyMw0KcTNfMjAyMyA8LSByYmluZChKdWx5XzIwMjMsQXVndXN0XzIwMjMsU2VwdGVtYmVyXzIwMjMpDQoNCg0KcTRfMjAyMyA8LSByYmluZChPY3RvYmVyXzIwMjMsTm92ZW1lYmVyXzIwMjMsRGVjZW1iZXJfMjAyMykNCg0KI2JpbmRyb3dzIDIwMjQNCnExXzIwMjQgPC0gcmJpbmQoSmFudWFyeV8yMDI0LEZlYnJ1YXJ5XzIwMjQsTWFyY2hfMjAyNCkNCg0KDQpxMl8yMDI0IDwtIHJiaW5kKEFwcmlsXzIwMjQsTWF5XzIwMjQsSnVuZV8yMDI0KQ0KDQoNCmFsbF90cmlwcyA8LSAgcmJpbmQocTNfMjAyMyxxNF8yMDIzLHExXzIwMjQscTJfMjAyNCkNCmBgYA0KDQoNCmBgYHtyfQ0KYGBgDQoNClBST0NFU1MNCg0KQ2xlYW5pbmcgdXAgZGF0YSBhbmQgYWRkaW5nIGRhdGEgdG8gcHJlcGFyZSBmb3IgYW5hbHlzaXMNCg0KS2V5IHRhc2tzDQoNCkNoZWNrIHRoZSBkYXRhIGZvciBlcnJvcnMuDQoNCkNob29zZSB5b3VyIHRvb2xzLg0KDQpUcmFuc2Zvcm0gdGhlIGRhdGEgc28geW91IGNhbiB3b3JrIHdpdGggaXQgZWZmZWN0aXZlbHkuDQoNCkRvY3VtZW50IHRoZSBjbGVhbmluZyBwcm9jZXNzLg0KDQpEZWxpdmVyYWJsZXMNCg0KRG9jdW1lbnRhdGlvbiBvZiBhbnkgY2xlYW5pbmcgb3IgbWFuaXB1bGF0aW9uIG9mIGRhdGEuDQpJIGluc3BlY3RlZCB0aGUgbmV3IHRhYmxlIHRoYXQgaGFzIGJlZW4gY3JlYXRlZCB1c2luZyB0aGUgZm9sbG93aW5nIGNvZGUgY2h1bmtzDQpgYGB7cn0NCmBgYA0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpjb2xuYW1lcyhhbGxfdHJpcHMpICAjTGlzdCBvZiBjb2x1bW4gbmFtZXMNCm5yb3coYWxsX3RyaXBzKSAgI0hvdyBtYW55IHJvd3MgYXJlIGluIGRhdGEgZnJhbWUNCmRpbShhbGxfdHJpcHMpICAjRGltZW5zaW9ucyBvZiB0aGUgZGF0YSBmcmFtZQ0KaGVhZChhbGxfdHJpcHMpICAjU2VlIHRoZSBmaXJzdCA2IHJvd3Mgb2YgZGF0YSBmcmFtZQ0KdGFpbChhbGxfdHJpcHMpICAjc2VlIHRoZSBsYXN0IDYgcm93cyBvZiBkYXRhIGZyYW1lDQpzdHIoYWxsX3RyaXBzKSAgI1NlZSBsaXN0IG9mIGNvbHVtbnMgYW5kIGRhdGEgdHlwZXMgKG51bWVyaWMsIGNoYXJhY3RlciwgZXRjKQ0Kc3VtbWFyeShhbGxfdHJpcHMpICAjU3RhdGlzdGljYWwgc3VtbWFyeSBvZiBkYXRhLiBNYWlubHkgZm9yIG51bWVyaWMNCmBgYA0KDQpBZGRpbmcgY29sdW1ucyB0aGF0IGxpc3QgdGhlIGRhdGUsIG1vbnRoLCBkYXksIGFuZCB5ZWFyIG9mIGVhY2ggcmlkZS4NCg0KVGhpcyB3aWxsIGFsbG93IHVzIHRvIGFnZ3JlZ2F0ZSByaWRlIGRhdGEgZm9yIGVhY2ggbW9udGgsIGRheSwgb3IgeWVhci4gVGhlcmVmb3JlLCB0aGUgY29kZSBjaHVua3MgdXNlZCBhcmUgYXMgZm9sbG93cw0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFsbF90cmlwcyRkYXRlIDwtIGFzLkRhdGUoYWxsX3RyaXBzJHN0YXJ0ZWRfYXQpICNUaGUgZGVmYXVsdCBmb3JtYXQgaXMgeXl5eS1tbS1kZA0KYWxsX3RyaXBzJG1vbnRoIDwtIGZvcm1hdChhcy5EYXRlKGFsbF90cmlwcyRkYXRlKSwgIiVtIikNCmFsbF90cmlwcyRkYXkgPC0gZm9ybWF0KGFzLkRhdGUoYWxsX3RyaXBzJGRhdGUpLCAiJWQiKQ0KYWxsX3RyaXBzJHllYXIgPC0gZm9ybWF0KGFzLkRhdGUoYWxsX3RyaXBzJGRhdGUpLCAiJVkiKQ0KYWxsX3RyaXBzJGRheV9vZl93ZWVrIDwtIGZvcm1hdChhcy5EYXRlKGFsbF90cmlwcyRkYXRlKSwgIiVBIikNCmBgYA0KDQpBZGRpbmcgYSDigJxyaWRlX2xlbmd0aOKAnSBjYWxjdWxhdGlvbiB0byBhbGxfdHJpcHMgKGluIHNlY29uZHMpDQpjb252ZXJ0IHRvIG1pbnV0ZXMNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbGxfdHJpcHMkcmlkZV9sZW5ndGggPC0gZGlmZnRpbWUoYWxsX3RyaXBzJGVuZGVkX2F0LGFsbF90cmlwcyRzdGFydGVkX2F0KQ0KDQpzdHIoYWxsX3RyaXBzKSAgI3RvIGluc3BlY3QgdGhlIHN0cnVjdHVyZSBvZiB0aGUgY29sdW1ucw0KYGBgDQoNCg0KYGBge3J9DQpgYGANCg0KDQoNCkNvbnZlcnQg4oCccmlkZV9sZW5ndGjigJ0gZnJvbSBmYWN0b3IgdG8gbnVtZXJpYyBzbyB3ZSBjYW4gcnVuIGNhbGN1bGF0aW9ucyBvbiB0aGUgZGF0YQ0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFsbF90cmlwcyRyaWRlX2xlbmd0aCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihhbGxfdHJpcHMkcmlkZV9sZW5ndGgpKQ0KDQppcy5udW1lcmljKGFsbF90cmlwcyRyaWRlX2xlbmd0aCkNCg0KYGBgDQoNCg0KYGBge3J9DQpgYGANCg0KDQoNClJlbW92ZSDigJxiYWTigJ0gZGF0YQ0KDQpUaGUgZGF0YWZyYW1lIGluY2x1ZGVzIGEgZmV3IGh1bmRyZWQgZW50cmllcyB3aGVuIGJpa2VzIHdlcmUgdGFrZW4gb3V0IG9mIGRvY2tzIGFuZCBjaGVja2VkIGZvciBxdWFsaXR5IGJ5IERpdnZ5IG9yIHJpZGVfbGVuZ3RoIHdhcyBuZWdhdGl2ZSBvciBaZXJvLCBzbyBJIGNyZWF0ZWQgYSBuZXcgdmVyc2lvbiBvZiB0aGUgZGF0YWZyYW1lICh2Mikgc2luY2UgZGF0YSBpcyBiZWluZyByZW1vdmVkDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbGxfdHJpcHNbIShhbGxfdHJpcHMkc3RhcnRfc3RhdGlvbl9uYW1lID09ICJIUSBRUiIgfCBhbGxfdHJpcHMkcmlkZV9sZW5ndGg8MCksXQ0KDQphbGxfdHJpcHNfdjIgPC0gYWxsX3RyaXBzWyEoYWxsX3RyaXBzJHN0YXJ0X3N0YXRpb25fbmFtZSA9PSAiSFEgUVIiIHwgYWxsX3RyaXBzJHJpZGVfbGVuZ3RoPDApLF0NCg0Kc3VtKGR1cGxpY2F0ZWQoYWxsX3RyaXBzX3YyKSkNCmBgYA0KDQoNCmBgYHtyfQ0KYGBgDQoNCg0KYGBge3J9DQpgYGANCg0KPGI+QU5BTFlaRTwvYj4NCg0KQWxsIHRoZSBkYXRhIGhhcyBiZWVuIHN0b3JlZCBhcHByb3ByaWF0ZWx5IGFuZCBoYXMgYmVlbiBwcmVwYXJlZCBmb3IgYW5hbHlzaXMsIHNvIHRoZXkgYXJlIHJlYWR5IGZvciBleHBsb3JhdGlvbi4NCktleSB0YXNrcw0KDQpBZ2dyZWdhdGUgeW91ciBkYXRhIHNvIGl04oCZcyB1c2VmdWwgYW5kIGFjY2Vzc2libGUuDQpPcmdhbml6ZSBhbmQgZm9ybWF0IHlvdXIgZGF0YS4NClBlcmZvcm0gY2FsY3VsYXRpb25zLg0KSWRlbnRpZnkgdHJlbmRzIGFuZCByZWxhdGlvbnNoaXBzLg0KRGVsaXZlcmFibGVzDQoNCkEgc3VtbWFyeSBvZiB5b3VyIGFuYWx5c2lzDQpDb25kdWN0IGRlc2NyaXB0aXZlIGFuYWx5c2lzIC0gRGVzY3JpcHRpdmUgYW5hbHlzaXMgb24gcmlkZV9sZW5ndGggKGFsbCBmaWd1cmVzIGluIHNlY29uZHMpDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQptZWFuKGFsbF90cmlwc192MiRyaWRlX2xlbmd0aCxuYV9ybT1UKSANCm1lZGlhbihhbGxfdHJpcHNfdjIkcmlkZV9sZW5ndGgsbmFfcm09VCkgDQptZnYoYWxsX3RyaXBzX3YyJHJpZGVfbGVuZ3RoLG5hX3JtPVQpIA0KbWF4KGFsbF90cmlwc192MiRyaWRlX2xlbmd0aCxuYV9ybT1UKSANCm1pbihhbGxfdHJpcHNfdjIkcmlkZV9sZW5ndGgsbmFfcm09VCkgDQpyYW5nZSA9IG1heChhbGxfdHJpcHNfdjIkcmlkZV9sZW5ndGgsbmFfcm09VCkgLSBtaW4oYWxsX3RyaXBzX3YyJHJpZGVfbGVuZ3RoLG5hX3JtPVQpIA0KcmFuZ2UNCmBgYA0KDQoNCg0KTGV04oCZcyB2aXN1YWxpemUgbWVtYmVycyBhbmQgY2FzdWFscyBieSB0aGUgdG90YWwgcmlkZSB0YWtlbiAocmlkZSBjb3VudCkNCjxicj4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFsbF90cmlwc192MiAlPiUgDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwpICU+JSANCiAgc3VtbWFyaXNlKHJpZGVfY291bnQgPSBuKCkpICU+JQ0KDQpnZ3Bsb3QoYWVzKG1lbWJlcl9jYXN1YWwscmlkZV9jb3VudCxmaWxsPW1lbWJlcl9jYXN1YWwpKStnZW9tX2NvbChwb3NpdGlvbiA9ICdkb2RnZScpKw0KICBsYWJzKHRpdGxlID0gJ1RvdGFsIHJpZGVzIHRha2VuIChyaWRlX2NvdW50KSBvZiBNZW1iZXJzIGFuZCBDYXN1YWwgcmlkZXJzJykrDQogIHRoZW1lX2NsYXNzaWMoKStnZW9tX2NvbCh3aWR0aCA9IC41LHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGg9LjUpKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1yaWRlX2NvdW50KSxzaXplPTQpKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTIsIGhqdXN0ID0gLjQgKSkNCiAgDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFsbF90cmlwc192Mg0KYGBgDQoNCkZyb20gdGhlIGFib3ZlIGdyYXBoLCB3ZSBjYW4gb2JzZXJ2ZSB0aGF0IHRoZXJlIGFyZSBmYXIgbW9yZSByaWRlcnMgYXMgcmVnaXN0ZXJlZCBtZW1iZXJzIGNvbXBhcmVkIHRvIGNhc3VhbCByaWRlcyBiYXNlZCBvbiB0aGUgcmlkZSBjb3VudC4NCg0KPGJyPg0KPGJyPg0KYGBge3J9DQpgYGANCjxicj4NCkxldOKAmXMgc2VlIHRoZSBhdmVyYWdlIHRpbWUgcmlkZSBieSBlYWNoIGRheSBmb3IgbWVtYmVycyB2cyBjYXN1YWwgdXNlcnMNCg0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp3ZGF5dG9hdmdyaWRlbCA8LSBhbGxfdHJpcHNfdjIgJT4lIA0KICBzZWxlY3QocmlkZV9sZW5ndGgsIG1lbWJlcl9jYXN1YWwsZGF5X29mX3dlZWspICU+JSANCiAgIG11dGF0ZShyaWRlX2xlbmd0aD1yaWRlX2xlbmd0aC82MCkgJT4lICMgcmlkZSBsZW5ndGggY29udmVydGVkIGZyb20gc2Vjb25kcyB0byBtaW51dGVzIA0KDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwsZGF5X29mX3dlZWspICU+JSANCiAgc3VtbWFyaXNlKGF2Z3JpZGVfbGVuZ3RoPXJvdW5kKG1lYW4ocmlkZV9sZW5ndGgpKSkNCmRhdGF0YWJsZSh3ZGF5dG9hdmdyaWRlbCkNCmBgYA0KYXZlcmFnZSByaWRlIHRpbWUgYnkgZWFjaCBkYXkgZm9yIG1lbWJlciB2cyBjYXN1YWwgdXNlcnMgKGluIG1pbnV0ZXMpDQo8YnI+DQoNCg0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQptZW1jYXNfZHVyYXRpb25hbmRyaWRlbnVtYmVyIDwtICBhbGxfdHJpcHNfdjIgJT4lICAjd2VlayB0cmVuZHMNCiAgICBtdXRhdGUocmlkZV9sZW5ndGg9cmlkZV9sZW5ndGgvNjApICU+JSAjIHJpZGUgbGVuZ3RoIGNvbnZlcnRlZCBmcm9tIHNlY29uZHMgdG8gbWludXRlcw0KICBtdXRhdGUod2Vla2RheSA9IHdkYXkoc3RhcnRlZF9hdCwgbGFiZWwgPSBUUlVFKSkgJT4lDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICU+JSAgDQogIHN1bW1hcmlzZShudW1iZXJfb2ZfcmlkZXMgPSBuKCksIGF2ZXJhZ2VfZHVyYXRpb24gPSByb3VuZChtZWFuKHJpZGVfbGVuZ3RoKSApKSAlPiUNCiAgYXJyYW5nZShtZW1iZXJfY2FzdWFsLCB3ZWVrZGF5KQ0KDQpkYXRhdGFibGUobWVtY2FzX2R1cmF0aW9uYW5kcmlkZW51bWJlcikNCg0KYGBgDQoNCg0KdG90YWwgcmlkZXMgYW5kIGF2ZXJhZ2UgcmlkZV90aW1lIGR1cmF0aW9uKGluIG1pbnV0ZXMpIGJ5IGVhY2ggZGF5IGZvciBtZW1iZXJzIHZzIGNhc3VhbCByaWRlcnMNCmBgYHtyfQ0KYGBgDQo8YnI+DQo8Yj5MZXTigJlzIHZpc3VhbGl6ZSB0aGUgYWJvdmUgdGFibGUgYnkgZGF5cyBvZiB0aGUgd2VlayBhbmQgbnVtYmVyIG9mIHJpZGVzIHRha2VuIGJ5IG1lbWJlciBhbmQgY2FzdWFsIHJpZGVycy48L2I+DQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFsbF90cmlwc192MiAlPiUgDQogICAgbXV0YXRlKHJpZGVfbGVuZ3RoPXJpZGVfbGVuZ3RoLzYwKSAlPiUgIyByaWRlIGxlbmd0aCBjb252ZXJ0ZWQgZnJvbSBzZWNvbmRzIHRvIG1pbnV0ZXMNCiAgbXV0YXRlKHdlZWtkYXkgPSB3ZGF5KHN0YXJ0ZWRfYXQsIGxhYmVsID0gVFJVRSkpICU+JQ0KICBncm91cF9ieShtZW1iZXJfY2FzdWFsLCB3ZWVrZGF5KSAlPiUgIA0KICBzdW1tYXJpc2UobnVtYmVyX29mX3JpZGVzID0gbigpLCBhdmVyYWdlX2R1cmF0aW9uID0gcm91bmQobWVhbihyaWRlX2xlbmd0aCkpKSAlPiUNCiAgYXJyYW5nZShtZW1iZXJfY2FzdWFsLCB3ZWVrZGF5KSAlPiUgDQogIGdncGxvdChhZXMod2Vla2RheSxudW1iZXJfb2ZfcmlkZXMsZmlsbD1tZW1iZXJfY2FzdWFsKSkrdGhlbWVfY2xhc3NpYygpK2dlb21fY29sKCkrZ2VvbV90ZXh0KGFlcyhsYWJlbD1udW1iZXJfb2ZfcmlkZXMpLHNpemU9Mi44KStnZ3RpdGxlKCdUb3RhbCByaWRlcyBvZiBNZW1iZXJzIGFuZCBDYXN1YWwgcmlkZXJzIFZzLiBEYXkgb2YgdGhlIHdlZWsnKSsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTExLGhqdXN0PS4zKSkNCiAgDQpgYGANCg0KPHVsPg0KPGxpPg0KRnJvbSB0aGUgYWJvdmUgZ3JhcGgsIGl0IGlzIG9ic2VydmVkIHRoYXQgdGhlIG1lbWJlcnMgYXJlIHF1aXRlIGNvbnNpc3RlbnQgd2l0aCBoaWdoZXIgbnVtYmVyIG9mIHJpZGVzIHRocm91Z2hvdXQgdGhlIHdvcmsgd2VlayBjb21wYXJlZCB0byB0aGUgY2FzdWFsIHJpZGVycywgaG93ZXZlciB0aGUgZGlmZmVyZW5jZXMgaW4gbnVtYmVyIG9mIHJpZGVzIGJldHdlZW4gdGhlIG1lbWJlcnMgYW5kIGNhc3VhbCByaWRlcnMgZHVyaW5nIHRoZSB3ZWVrZW5kcyhTYXR1cmRheXMgYW5kIFN1bmRheXMpIGlzIG5vdCBuZWFybHkgYXMgbXVjaCBhcyB0aGUgZGlmZmVyZW5jZXMgZHVyaW5nIHRoZSBvdGhlciBkYXlzIG9mIHRoZSB3ZWVrLg0KPC9saT4NCjwvdWw+DQpgYGB7cn0NCmBgYA0KPGJyPg0KDQpgYGB7cn0NCmBgYA0KDQo8Yj5MZXTigJlzIHZpc3VhbGl6ZSB0aGUgYXZlcmFnZSBkdXJhdGlvbiBvZiBNZW1iZXJzIGFuZCBDYXN1YWwgcmlkZXJzIFZzLiBEYXkgb2YgdGhlIHdlZWs8L2I+DQpgYGB7cn0NCmBgYA0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbGxfdHJpcHNfdjIgJT4lICANCiAgbXV0YXRlKHJpZGVfbGVuZ3RoPXJpZGVfbGVuZ3RoLzYwKSAlPiUgDQogIG11dGF0ZSh3ZWVrZGF5ID0gd2RheShzdGFydGVkX2F0LCBsYWJlbCA9IFRSVUUpKSAlPiUgDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICU+JSANCiAgc3VtbWFyaXNlKG51bWJlcl9vZl9yaWRlcyA9IG4oKQ0KICAgICAgICAgICAgLGF2ZXJhZ2VfZHVyYXRpb24gPSByb3VuZChtZWFuKHJpZGVfbGVuZ3RoKSkpICU+JSANCiAgYXJyYW5nZShtZW1iZXJfY2FzdWFsLCB3ZWVrZGF5KSAgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB3ZWVrZGF5LCB5ID0gYXZlcmFnZV9kdXJhdGlvbiwgZmlsbCA9IG1lbWJlcl9jYXN1YWwpKSArDQogIGdlb21fY29sKHBvc2l0aW9uID0gImRvZGdlIikgKw0KICBsYWJzKHRpdGxlID0iQXZlcmFnZSBkdXJhdGlvbiAobWludXRlcykgb2YgTWVtYmVycyBhbmQgQ2FzdWFsIHJpZGVycyIpICt0aGVtZV9jbGFzc2ljKCkrDQogIGdlb21fY29sKHdpZHRoPTAuNSkrZ2VvbV90ZXh0KGFlcyhsYWJlbD1hdmVyYWdlX2R1cmF0aW9uKSxzaXplPTIuOCkrdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsaGp1c3Q9LjA1KSkNCg0KYGBgDQpGcm9tIHRoZSBncmFwaCBhYm92ZSwgaXQgaXMgb2JzZXJ2ZWQgdGhhdCB0aGUgY2FzdWFsIHJpZGVycyByaWRlIHRoZSBsb25nZXN0IHRocm91Z2hvdXQgdGhlIHdlZWtlbmQgYW5kIHNob3J0ZXN0IGR1cmF0aW9uIGJldHdlZW4gVHVlc2RheSBhbmQgVGh1cnNkYXksIHdoaWxlIHJlZ2lzdGVyZWQgcmlkZXJzIHJpZGUgYXQgYSBmYXN0ZXIgbW9yZSBjb25zaXN0ZW50IHBhY2UgZHVyaW5nIHRoZSB3ZWVrIHdpdGggdGhlIGxvbmdlc3QgcmlkZXMgb24gdGhlIHdlZWtlbmQuDQo8YnI+DQpgYGB7cn0NCmBgYA0KDQo8Yj4gTGV04oCZcyBjcmVhdGUgYSB2aXN1YWxpemF0aW9uIGZvciBUb3RhbCByaWRlcyBieSBtZW1iZXJzIGFuZCBjYXN1YWwgcmlkZXJzIGJ5IG1vbnRoDQoNCjwvYj4NCjxicj4NCg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYWxsX3RyaXBzX3YyICU+JSAgDQogICANCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCwgbW9udGgpICU+JSANCiAgc3VtbWFyaXNlKG51bWJlcl9vZl9yaWRlcyA9IG4oKSwuZ3JvdXBzPSJkcm9wIikgJT4lIA0KICBhcnJhbmdlKG1lbWJlcl9jYXN1YWwsIG1vbnRoKSAgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBtb250aCwgeSA9IG51bWJlcl9vZl9yaWRlcywgZmlsbCA9IG1lbWJlcl9jYXN1YWwpKSArDQogIGxhYnModGl0bGUgPSJUb3RhbCByaWRlcyBieSBNZW1iZXJzIGFuZCBDYXN1YWwgcmlkZXJzIGJ5IE1vbnRoIikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1KSkgK3RoZW1lX2NsYXNzaWMoKSsNCiAgZ2VvbV9jb2wod2lkdGg9MC41KStnZW9tX3RleHQoYWVzKGxhYmVsPW51bWJlcl9vZl9yaWRlcyksc2l6ZT0yLjQpKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTEsaGp1c3Q9LjYpKQ0KICANCmBgYA0KRnJvbSB0aGUgYWJvdmUgZ3JhcGgsIGl0IGlzIG9ic2VydmVkIHRoYXQgbWVtYmVycyBoYXZlIHRoZSBoaWdoZXN0IG51bWJlciBvZiByaWRlcyB0aHJvdWdob3V0IHRoZSB5ZWFyIHdpdGggQXVndXN0IGJlaW5nIHRoZSBtb250aCB3aXRoIHRoZSBoaWdoZXN0IG51bWJlciBvZiByaWRlcyBmb3IgbWVtYmVycyB3aGlsc3QganVseSBoYXMgdGhlIG1vc3QgY2FzdWFsIHJpZGVycw0KDQo8YnI+DQo8Yj5MZXTigJlzIGNvbXBhcmUgTWVtYmVycyBhbmQgQ2FzdWFsIHJpZGVycyBkZXBlbmRpbmcgb24gcmlkZSBkaXN0YW5jZS48L2I+DQpgYGB7cn0NCg0KYWxsX3RyaXBzX3YyICU+JSANCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCkgJT4lIGRyb3BfbmEoKSAlPiUNCiAgc3VtbWFyaXNlKGF2ZXJhZ2VfcmlkZV9sZW5ndGhfa2lsb21ldGVycyA9IG1lYW4ocmlkZV9sZW5ndGgpKSAlPiUNCm11dGF0ZShhdmVyYWdlX3JpZGVfbGVuZ3RoX01pbGVzID0gcm91bmQoKCBhdmVyYWdlX3JpZGVfbGVuZ3RoX2tpbG9tZXRlcnMgKiAyKS8zICkpICU+JSANCiAgc2VsZWN0KG1lbWJlcl9jYXN1YWwsYXZlcmFnZV9yaWRlX2xlbmd0aF9NaWxlcykgJT4lIA0KICBnZ3Bsb3QoYWVzKHg9IG1lbWJlcl9jYXN1YWwseT0gYXZlcmFnZV9yaWRlX2xlbmd0aF9NaWxlcyxmaWxsPW1lbWJlcl9jYXN1YWwpKSArIA0KICBnZW9tX2NvbCgpK2dlb21fdGV4dChhZXMobGFiZWwgPSBhdmVyYWdlX3JpZGVfbGVuZ3RoX01pbGVzKSkrDQogIGxhYnModGl0bGUgPSAiTWVhbiBkaXN0YW5jZSB0cmF2ZWxlZCBieSBNZW1iZXJzIGFuZCBDYXN1YWwgcmlkZXJzIikrDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxoanVzdD0uMikpK3RoZW1lX2NsYXNzaWMoKQ0KICANCg0KDQpgYGANCkZyb20gdGhlIGdyYXBoIGFib3ZlLCB3ZSBjYW4gb2JzZXJ2ZSB0aGF0IHRoZSBkaXN0YW5jZSB0cmF2ZWxlZCBieSB0aGUgY2FzdWFsIHJpZGVycyBpcyBmYXIgbW9yZSB0aGFuIHRoZSBkaXN0YW5jZSB0cmF2ZWxlZCBieSB0aGUgbWVtYmVyIHdpdGggYSB2ZXJ5IGxhcmdlIGRpZmZlcmVuY2UgaW4gbWlsZXMuDQpSb3VnaGx5IDg1NSBtaWxlcyBpcyB0aGUgYXZlcmFnZSBkaXN0YW5jZSB0cmF2ZWxlZCBieSBjYXN1YWwgcmlkZXJzIGFuZCA0OTQgbWlsZXMgaXMgdGhlIGF2ZXJhZ2UgZGlzdGFuY2UgdHJhdmVsZWQgYnkgcmVnaXN0ZXJlZCBtZW1iZXJzDQo8YnI+DQpub3RlOiBjb252ZXJ0ZWQga2lsb21ldGVycyB0byBtaWxlcw0KDQo8YnI+DQoNCjxicj4NCg0KDQo8YnI+DQoNCg0KPGI+Q29uY2x1c2lvbjwvYj4NCjxvbD4NCjxsaT5NZW1iZXJzIGhhdmUgbW9yZSBiaWtlcyBjb21wYXJlZCB0byBjYXN1YWwgcmlkZXJzLjwvbGk+DQoNCjxsaT5XZSBoYXZlIG1vcmUgbWVtYmVycyByaWRpbmcgaW4gYWxsIG1vbnRocyBjb21wYXJlZCB0byBjYXN1YWwgcmlkZXJzLjwvbGk+DQoNCjxsaT5DYXN1YWwgcmlkZXJzIHRyYXZlbCBmb3IgYSBsb25nZXIgdGltZSBwZXJpb2QuPC9saT4NCg0KPGxpPk1lbWJlcnMgcmlkZSBtb3JlIHRocm91Z2hvdXQgdGhlIGVudGlyZSB3ZWVrZGF5IHdoaWxlIHRoZSBjYXN1YWwgcmlkZXJzIGFsc28gaGF2ZSBhIGhpZ2ggcmlkZSByZWNvcmQgZHVyaW5nIHRoZSB3ZWVrZW5kcyhTYXR1cmRheSBhbmQgU3VuZGF5KSBjb21wYXJlZCB0byB0aGUgb3RoZXIgZGF5cyBvZiB0aGUgd2Vlay48L2xpPg0KDQo8bGk+Q2FzdWFsIHJpZGVycyBnbyBmYXJ0aGVyIGluIHRlcm1zIG9mIGRpc3RhbmNlLjwvbGk+DQo8L29sPg0KDQpgYGB7cn0NCg0KYGBgDQoNCmBgYHtyfQ0KYGBgDQoNCjxiPkRlbGl2ZXJhYmxlPC9iPg0KDQo8dWw+PGxpPllvdXIgcmVjb21tZW5kYXRpb25zIGJhc2VkIG9uIHlvdXIgYW5hbHlzaXM8L2xpPjwvdWw+DQoNCkkgd291bGQgcmVjb21tZW5kIGEgd2Vla2VuZCB3YXJyaW9yIG1lbWJlcnNoaXAgdGhhdCBjYW4gdGFrZSBmdWxsIGFkdmFudGFnZSBvZiBjYXN1YWwgcmlkZXIncyBidXN5IGxpZmVzdHlsZSBhbmQgYmVoYXZpb3JhbCBwYXR0ZXJucywgcGx1cyBpbXBsZW1lbnQgbGV2ZWxzIG9mIHRhcmdldGVkIG1hcmtldGluZyBkdXJpbmcgbW9udGhzIG9mIEp1bmUsIEp1bHkgJiBBdWd1c3Qgd2hlbiBDYXN1YWwgUmlkZXJzIGFyZSBhdCB0aGVpciBwZWFrIGluIHJpZGluZy4NCmBgYHtyfQ0KYGBgDQoNCg0KYGBge3J9DQpgYGANCg0KDQoNCmBgYHtyfQ0KYGBgDQoNCg==