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))
-
From the above graph, it is observed that the members are quite
consistent with higher number of rides throughout the work week compared
to the casual riders, however the differences in number of rides between
the members and casual riders during the weekends(Saturdays and Sundays)
is not nearly as much as the differences during the other days of the
week.
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
-
Members have more bikes compared to casual riders.
-
We have more members riding in all months compared to casual riders.
-
Casual riders travel for a longer time period.
-
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.
-
Casual riders go farther in terms of distance.
Deliverable
-
Your recommendations based on your analysis
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==