We will be working with data from the nycflights13 package, and use ggplot2 to help us understand the data. So we will need to load the library for nycflights13 and tidyverse.
There will be a conflict error message when you load tidyverse after dplyr. Dplyr overwrites some functions in base R. If you want to use the base version of these functions after loading dplyr, you will need to use thier full names: stats::filter() and stats::lag().
Tibbles are data frames, but slightly tweaked to work better in the tidyverse. Each column has different abbreviation under them:
All verbs work similarly. 1. The first argument is a data frame 2. The subsequent arguments describe what to do with the data frame, using the variable names (without quotes) 3. The sesult is a new data frame.
The underlying data is never modified. To save use assignment operator [ <- ]
In propositional logic and boolean algebra, De Morgan’s laws[1][2][3] are a pair of transformation rules that are both valid rules of inference. They are named after Augustus De Morgan, a 19th-century British mathematician. The rules allow the expression of conjunctions and disjunctions purely in terms of each other via negation.
The rules can be expressed in English as: the negation of a conjunction is the disjunction of the negations; and the negation of a disjunction is the conjunction of the negations;
or the complement of the union of two sets is the same as the intersection of their complements; and the complement of the intersection of two sets is the same as the union of their complements.
For example, if you were to find flights that weren’t delayed (on arrival or departure) by more than two hours, you could use either of hte following two filters:
Multiple arguments are equivalent to and
filter(mtcars, cyl < 6, vs == 1) There are also && and || but it will taught later on under “Conditional execution”
Missing Values
Missing values or “NAs” (not available) represent an unknown value so missing values are “contagious”. Almost any operation involving an unknown value will also be unkown.
NA > 5
[1] NA
10 == NA
[1] NA
NA + 10
[1] NA
# but this one is confusing
NA == NA
[1] NA
To test of NA, use is.na()
x <- NA
y <- NA
is.na(x)
[1] TRUE
Filter() only includes rows where the condition is TRUE. It excludes both FALSE and NA. So if you want to preserve missing values, ask for them explicitly
In this case, the NA is not listed.
df <- tibble(x=c(1,NA,3))
filter(df,x>1)
In this case, the NA is listed.
filter(df, is.na(x) | x>1)
Exercises 1. find all flights that: a. had an arrival delay of two hours or more:
filter(flights, arr_delay>120)
- flew to Houston (IAH or HOU)
filter(flights, dest %in% c("IAH","HOU"))
- Were operated by United, American or Delta
filter(flights, carrier %in% c("UA","AA","DL"))
- Departed in summer
filter(flights, month>6 & month<10)
- Arrived more than 2 hours late, but didn’t leave late
filter(flights, arr_delay>120, dep_delay<2)
- Were delayed by at least an hour, but made up over 30 minutes in flight
filter(flights, arr_delay<=90, dep_delay>120)
- Departed between midnight and 6am inclusive
filter(flights, dep_time>=000, dep_time<=600)
Use between() to simplify the code:
filter(flights, between(dep_time,000,600))
- How many flights have a missing dep_time? What other variables are missing? What might these rows represent?
# list missing dep_time
filter(flights, is.na(dep_time))
Answer: dep_delay, arr_time, arr_delay are all missing. This might represent cancelled flights.
Now we can also sort in descending order using the Arrange() verb.
arrange(filter(flights, is.na(dep_time)),desc(month))
Arrange Rows with arrange()
Arrange(data, arg1, arg2, arg3…) If you provide more than one column name, each additional column will be used to break ties in the values of preceding columns. You can also use desc(arg) to reorder a column in descending order.
arrange(flights, year, month, day)
arrange(flights, year, desc(month), day)
Missing values are always sorted at the end. To sort missing values at the start, use is.na()
arrange(flights, dep_time,is.na(dep_time))
Note: Couldnt get NA to show up in the view.
Select columns with select()
Select() allows you to zoom in on a useful subset using operations based on the names of the variables.
# show only year, month and day
select(flights, year, month, day)
So notice that there are only 3 columns now.
# select all columns between year and day inclusive
select(flights, year:day)
# select all columns EXCEPT those from year to day
select(flights, -(year:day))
Helper functions within select()
starts_with(“abc”)
ends_with(“abc”)
contains(“ijk”)
matches(“(.)\1”)
num_range(“x”,1:3) matches x1,x2,x3
see ?select for more details.
Rename
While you can use select() to rename variables, it is not useful because it drops all of the variables not explicitly mentioned. For this, use the rename() that keeps all the variables that aren’t explicitly mentioned.
rename(flights, DepartureTime = dep_time)
What happens when we use a variable mulitple times in a select call?
select(flights, origin, dest, air_time, origin)
select(flights, contains("TIME"))
Add new variables with Mutate()
Use the mutate function when you want to add new variables that are a function of existing columns. Mutate always adds new variables to the end of the dataset.
flights_sml <- select(flights, year:day, ends_with("delay"), distance, air_time)
mutate(flights_sml, gain=arr_delay -dep_delay, speed= distance /air_time * 60)
Note: gain and speed are the new columns generated by mutate() If you only want to keep the newly generated variables use transmut()
transmute(flights, gain=arr_delay -dep_delay, speed= distance /air_time * 60)
Useful Creation functions
Modular arithemetic (%/% and %%)
Integer Division (%/%) and remainder (%%) You can use this in the NYC Flightst
library(tidyverse)
library(nycflights13)
transmute(flights, dep_time, hour=dep_time %/% 100, minute=dep_time %% 100)
Logs log(),log2() , log10()
Logarithms are an incredibly useful transformation for dealling with data that ranges across multiple orders of magnitude. They also convert multiplicative relationships to additive
h3> Offsets
lead() and Lag() allowyou to refer to leading or lagging values. This can be used to compute running differences x-lag(x) or find when values change (x!=lag(x)). They are most useful in conjunction with group_by()
x<-1:10
# display x values
x
[1] 1 2 3 4 5 6 7 8 9 10
# display lag(x)
lag(x)
[1] NA 1 2 3 4 5 6 7 8 9
# display lead(x)
lead(x)
[1] 2 3 4 5 6 7 8 9 10 NA
Cummulative and rolling aggregates
Functions for running sums, products, mins, and maxes: cumsum(), cumprod(), cummin(), cummax() and dplyr provides cummean() for cummulative means. Try also the RcppRoll package
# print x again
x
[1] 1 2 3 4 5 6 7 8 9 10
# what is the cummulative sum of x?
cumsum(x)
[1] 1 3 6 10 15 21 28 36 45 55
# what is the cummulative mean of x?
cummean(x)
[1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5
Ranking
Try min_rank() smallest values= smallest ranks
# set up variable
y <- c(1,2,2,NA,3,4)
# display Y values
y
[1] 1 2 2 NA 3 4
# display rank
min_rank(y)
[1] 1 2 2 NA 4 5
min_rank(desc(y))
[1] 5 3 3 NA 2 1
If rank() doesnt do what you need, look at the variants row_number(), dense_rank(), percent_rank(), cume_dist(), and ntile().
y
[1] 1 2 2 NA 3 4
row_number(y)
[1] 1 2 3 NA 4 5
dense_rank(y)
[1] 1 2 2 NA 3 4
cume_dist(y)
[1] 0.2 0.6 0.6 NA 0.8 1.0
percent_rank(y)
[1] 0.00 0.25 0.25 NA 0.75 1.00
Grouped Summaries with Summarize()
summarize() collapses a data frame to a single row.
library(nycflights13)
summarize(flights, delay=mean(dep_delay, na.rm=TRUE))
Enhance summarize() with group_by to get summaries by group of data.
library(nycflights13)
by_day <- group_by(flights, year, month, day)
summarize(by_day, delay=mean(dep_delay, na.rm=TRUE))
Then we can combine multiple operations with the pipe Suppose we want to find the relationship between the distance and average delay for each location.
# Old method without using Pipes
library(nycflights13)
by_dest <- group_by(flights,dest)
delay <- summarize(by_dest, count=n(), dist=mean(distance, na.rm=TRUE), delay=mean(arr_delay, na.rm = TRUE))
delay <- filter(delay, count>20, dest !="HNL")
delay
# let us plot the values to see the correlation
ggplot(data=delay, mapping=aes(x=dist, y=delay))+
geom_point(aes(size=count), aplpha=1/3)+
geom_smooth(se=FALSE)
Ignoring unknown parameters: aplpha

Now, let us see how Pipe (%>%) will make the code more efficient:
delays <- flights %>%
group_by(dest) %>%
summarize(count=n(), dist=mean(distance, na.rm=TRUE), delay=mean(arr_delay, na.rm=TRUE))%>%
filter(count>20, dest !="HNL")
ggplot(data=delay, mapping=aes(x=dist, y=delay))+
geom_point(aes(size=count), alpha=1/3)+
geom_smooth(se=FALSE)

A good way to think of Pipe (%>%) is by reading it as “then”. Behind the scenes, x %>% f(y) means f(x,y) x %>% f(y) %>% g(z) turns into g(f(x,y),z) Read left to right, top to bottom.
Missing Values
na.rm argument is use to take care of missing values.
# see how many missing values there are
flights %>% group_by(year,month,day) %>%
summarize(mean=mean(dep_delay))
Fortunately, all aggregate functions have the na.rm argument (NA remove) prior to computation
flights %>% group_by(year,month,day) %>%
summarize(mean=mean(dep_delay, na.rm =TRUE))
Taking care of cancelled flights
not_cancelled <- flights %>%
filter(!is.na(dep_delay), !is.na(arr_delay))
not_cancelled %>%
group_by(year,month, day) %>%
summarize(mean=mean(dep_delay))
# no need to use na.rm since all missing values have been filter out already
Counts
Its a good idea to include either a count n() or a count of nonmissing values sum(!is.na(x)) so that you know the sample size.
For example this below shows an average delay of 5 hours!
delays <- not_cancelled %>%
group_by(tailnum) %>%
summarize(delay=mean(arr_delay))
ggplot(data=delays, mapping=aes(x=delay))+
geom_freqpoly(binwidth=10)

We can get insight if we draw a scatterplot of number of lights versus average delay:
delays <- not_cancelled %>%
group_by(tailnum) %>%
summarize(delay=mean(arr_delay, na.rm=TRUE), n= n())
ggplot(data=delays, mapping=aes(x=n, y=delay))+
geom_point(alpha=1/10)

There are wider variations when there are lesser flights than when there are a lot of flights. So get more details, let us filter out the wild patterns when n>25
delays %>%
filter(n>25) %>%
ggplot(mapping=aes(x=n, y=delay))+
geom_point(alpha=1/10)

Look at avverage performance of baseball batters to numbe of times they’re at a bat. Using Lahman package to compute the batting average (number of hits/number of attempts) of every majore league baseball player.
# you need to install the Lahman package first for this to work
batting <- as_tibble(Lahman::Batting)
batters <- batting %>%
group_by(playerID) %>%
summarize(
ba=sum(H, na.rm=TRUE)/sum(AB, na.rm=TRUE),
ab=sum(AB, na.rm=TRUE)
)
batters %>%
filter(ab >100) %>%
ggplot(mapping=aes(x=ab, y=ba))+
geom_point()+
geom_smooth(se=FALSE)

NA
The implication, if you naively sort on desc(ba) the pople with the best batting averages are clearly lucky…not skilled.
batters %>%
arrange(desc(ba))
Useful Summary Functions
Measures of location
Median is a value where 50% of x is above it, and 50% is below it. Try combining aggregations with subsetting
not_cancelled %>%
group_by(year, month, day) %>%
summarize(
avg_delay1=mean(arr_delay),
avg_delay2=mean(arr_delay[arr_delay>0])
)
measures of spread
Mean squared deviation, or standard deviation or SD for short is the standard measure of spread. The interquartile range IQR() and median absoute deviation mad(x) are robust equivalents that may be more useful if you have outliers.
# why is distance to some destinations more variable than to others?
not_cancelled %>%
group_by(dest) %>%
summarise(distance_sd=sd(distance))%>%
arrange(desc(distance_sd))
Measures of rank min(x), quantile(x,0.25), max(x)
Qunatiles are a generalization of the median. Quantile(x,0.25) will find a value of x that is greater than 25% of the values and less than the remaining 75%
# when do the first and last flights leave each day?
not_cancelled %>%
group_by(year, month,day) %>%
summarize(first=min(dep_time), last=max(dep_time))
Measures of poistion first(x), nth(x,2) last(x)
# find the first and last departure for each day
not_cancelled %>%
group_by(year, month,day) %>%
summarize(first_dep=first(dep_time), last_dep=last(dep_time))
These functions are complementary to filtering on ranks
not_cancelled %>%
group_by(year, month,day) %>%
mutate(r=min_rank(desc(dep_time))) %>%
filter(r %in% range(r))
To count the number of distinct values
not_cancelled %>%
group_by(dest) %>%
summarize(carriers=n_distinct(carrier)) %>%
arrange(desc(carriers))
You can use a weight variable. You can use this to count the total number of miles a plane flew.
not_cancelled %>%
count(tailnum,wt=distance)
Counts and proportions of logical values
When you want to count the number of trues (1) and false(0)
not_cancelled %>%
group_by(year, month, day) %>%
summarize(n_early =sum(dep_time<500))
Grouping Multiple Variables
When you group by multiple variables , each summary peels off one level of the grouping. That makes it easy to progressively roll up a dataset.
daily <- group_by(flights, year, month, day)
(per_day <- summarize(daily, flights=n()))
(per_month <- summarize(per_day, flights=sum(flights)))
# per year
(per_year <- summarize(per_month, flights=sum(flights)))
To remove groupings, use Ungroup()
daily %>%
ungroup() %>%
summarize(flights=n())
Grouped Mutates and Filters
Grouping is most useful in conjunction with summarize(), but you can also do convenient operations with mutate() and filter()
# find the worst members of each group
flights_sml %>%
group_by(year, month, day) %>%
filter(rank(desc(arr_delay))<10)
# find all groups bigger than a threshold
popular_dests <- flights %>%
group_by(dest) %>%
filter(n() >365)
# display
popular_dests
# standardie to compute per group metrics
popular_dests <- flights %>%
filter(arr_delay>0) %>%
mutate(prop_delay=arr_delay /sum(arr_delay)) %>%
select(year:day,dest,arr_delay, prop_delay)
# display
popular_dests
LS0tDQp0aXRsZTogIlIgZm9yIERhdGEgU2NpZW5jZUNoYXB0ZXIgMyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCjxoMT4gQ2hhcHRlciAzIERhdGEgVHJhbnNmb3JtYXRpb24gd2l0aCBkcGx5ciA8L2gxPg0KWW91IHdpbGwgbGVhcm4gaG93IHRvIHRyYW5zZm9ybSB5b3VyIGRhdGEgd2l0aCBkcGx5ciBwYWNrYWdlLg0KPGgyPiBQcmUgcmVxdWlzaXRlcyA8L2gyPg0KV2Ugd2lsbCBiZSB3b3JraW5nIHdpdGggZGF0YSBmcm9tIHRoZSBueWNmbGlnaHRzMTMgcGFja2FnZSwgYW5kIHVzZSBnZ3Bsb3QyIHRvIGhlbHAgdXMgdW5kZXJzdGFuZCB0aGUgZGF0YS4gU28gd2Ugd2lsbCBuZWVkIHRvIGxvYWQgdGhlIGxpYnJhcnkgZm9yIG55Y2ZsaWdodHMxMyBhbmQgdGlkeXZlcnNlLjwvcD4NCg0KVGhlcmUgd2lsbCBiZSBhIGNvbmZsaWN0IGVycm9yIG1lc3NhZ2Ugd2hlbiB5b3UgbG9hZCB0aWR5dmVyc2UgYWZ0ZXIgZHBseXIuIERwbHlyIG92ZXJ3cml0ZXMgc29tZSBmdW5jdGlvbnMgaW4gYmFzZSBSLiBJZiB5b3Ugd2FudCB0byB1c2UgdGhlIGJhc2UgdmVyc2lvbiBvZiB0aGVzZSBmdW5jdGlvbnMgYWZ0ZXIgbG9hZGluZyBkcGx5ciwgeW91IHdpbGwgbmVlZCB0byB1c2UgdGhpZXIgZnVsbCBuYW1lczogc3RhdHM6OmZpbHRlcigpIGFuZCBzdGF0czo6bGFnKCkuIDwvcD4NCg0KPGgyPiBFeHBsb3JlIE55Y2ZsaWdodHMgZGF0YSA8L2gyPg0KDQpgYGB7cn0NCiMgeW91IG1heSBuZWVkIHRvIGluc3RhbGwucGFja2FnZXMoIm55Y2ZsaWdodHMxMyIpDQoNCmxpYnJhcnkobnljZmxpZ2h0czEzKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCiMgZmxpZ2h0cyBjb250YWluIDMzNiw3NzYgcmVjb3JkcyBvZiBmbGlnaHRzIHRoYXQgZGVwYXJ0ZWQgZnJvbSBOWSBpbiAyMDEzDQo/ZmxpZ2h0cw0KDQojIHRvIHNlZSB0aGUgd2hvbGUgZGF0YXNldCB1c2UgdGhlIGZ1bmN0aW9uIHZpZXcoKQ0KVmlldyhmbGlnaHRzKQ0KIyBpdCBwcmludHMgYXMgYSB0aWJibGUNCmBgYA0KDQpUaWJibGVzIGFyZSBkYXRhIGZyYW1lcywgYnV0IHNsaWdodGx5IHR3ZWFrZWQgdG8gd29yayBiZXR0ZXIgaW4gdGhlIHRpZHl2ZXJzZS4gDQpFYWNoIGNvbHVtbiBoYXMgZGlmZmVyZW50IGFiYnJldmlhdGlvbiB1bmRlciB0aGVtOiA8L2JyPg0KPGxpPmludCAtIGludGVnZXIgPC9saT4NCjxsaT5kYmwgLSBkb3VibGVzIG9yIHJlYWwgbnVtYmVycyA8L2xpPg0KPGxpPmNociAtIGNoYXJhY3RlciB2ZWN0b3Igb3Igc3RyaW5ncyA8L2xpPg0KPGxpPmR0dG0gLSBkYXRlIHRpbWU8L2xpPg0KPGxpPmxnbCAtIGxvZ2ljYWwgPC9saT4NCjxsaT5mY3RyIC0gZmFjdG9ycyB1c2VkIGJ5IFIgdG8gcmVwcmVzZW50IGNhdGVnb3JpY2FsIHZhbHVlcyA8L2xpPg0KDQo8aDI+IERwbHlyIEJhc2lzYyA8L2gyPg0KDQpGaXZlIEtleSBEcGxyIGZ1bmN0aW9ucyAgZm9yIGRhdGEgbWFuaXB1bGF0aW9uOiA8L2JyPg0KPGxpPiBmaWx0ZXIoKSB0byBmaWx0ZXIgcm93cyBvciBkYXRhIHBvaW50czwvbGk+DQo8bGk+IGFycmFuZ2UoKSB0byBzb3J0IHJlb3JkZXIgdGhlIGRhdGE8L2xpPg0KPGxpPiBtdXRhdGUoKSB0byBjcmVhdGUgbmV3IHZhcmlhYmxlcyB3aXRoIGZ1bmN0aW9ucyBvZiBleGlzdGluZyB2YXJpYWJsZXM8L2xpPg0KPGxpPiBzdW1tYXJpemUoKSB0byBjb2xsYXBzZSBtYW55IHZhbHVlcyBkb3duIHRvIGEgc2luZ2xlIHN1bW1hcnkgPC9saT4NCjxsaT4gZ3JvdXBfYnkoKSBjaGFuZ2VzIHRoZSBzY29wZSBvZiBlYWNoIGZ1bmN0aW9uIGZyb20gb3BlcmF0aW5nIG9uIHRoZSBlbnRpcmUgZGF0YXNldCB0byBvcGVyYXRpbmcgb24gYSBncm91cCBieSBncm91cCBiYXNpczwvbGk+IDwvcD4NCg0KQWxsIHZlcmJzIHdvcmsgc2ltaWxhcmx5LiA8L2JyPg0KMS4gVGhlIGZpcnN0IGFyZ3VtZW50IGlzIGEgZGF0YSBmcmFtZTwvYnI+DQoyLiBUaGUgc3Vic2VxdWVudCBhcmd1bWVudHMgZGVzY3JpYmUgd2hhdCB0byBkbyB3aXRoIHRoZSBkYXRhIGZyYW1lLCB1c2luZyB0aGUgdmFyaWFibGUgbmFtZXMgKHdpdGhvdXQgcXVvdGVzKTwvYnI+DQozLiBUaGUgc2VzdWx0IGlzIGEgbmV3IGRhdGEgZnJhbWUuIDwvcD4NCg0KYGBge3J9DQojIGZpbHRlciByb3dzDQoNCmZpbHRlcihmbGlnaHRzLCBtb250aD09MSwgZGF5ID09MSkNCg0KYGBgDQoNClRoZSB1bmRlcmx5aW5nIGRhdGEgaXMgbmV2ZXIgbW9kaWZpZWQuIFRvIHNhdmUgdXNlIGFzc2lnbm1lbnQgb3BlcmF0b3IgWyA8LSBdDQoNCjxoMj4gQ29tcGFyaXNvbnMgPC9oMj4NCnVzZSB0aGUgPT0gZm9yIGVxdWFsLCA+LD49LCA8LDw9IT0NCjwvYnI+DQoNCg0KPGgyPiBMb2dpY2FsIG9wZXJhdG9ycyA8L2gyPg0KPGxpPiYgZm9yIEFORCA8L2xpPg0KPGxpPnwgZm9yIE9SIDwvbGk+DQo8bGk+ISBmb3IgTk9UIDwvbGk+DQo8L3A+DQoNClRoZSBmb2xsb3dpbmcgY29kZSB3aWxsIGZpbmQgYWxsIGZsaWdodHMgdGhhdCBkZXBhcnRlZCBpbiBOb3ZlbWJlciBvciBEZWNlbWJlcg0KDQpgYGB7cn0NCmZpbHRlcihmbGlnaHRzLCBtb250aD09MTEgfCBtb250aD09MTIpDQpgYGANCg0Kb3IgdXNlIHRoZSB4ICVpbiUgeSBhcHByb2FjaA0KDQpgYGB7cn0NCmZpbHRlcihmbGlnaHRzLCBtb250aCAlaW4lIGMoMTEsMTIpKQ0KYGBgDQoNClNlZSBhbHNvIGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0RlX01vcmdhbiUyN3NfbGF3cyA8L2JyPg0KSW4gcHJvcG9zaXRpb25hbCBsb2dpYyBhbmQgYm9vbGVhbiBhbGdlYnJhLCBEZSBNb3JnYW4ncyBsYXdzWzFdWzJdWzNdIGFyZSBhIHBhaXIgb2YgdHJhbnNmb3JtYXRpb24gcnVsZXMgdGhhdCBhcmUgYm90aCB2YWxpZCBydWxlcyBvZiBpbmZlcmVuY2UuIFRoZXkgYXJlIG5hbWVkIGFmdGVyIEF1Z3VzdHVzIERlIE1vcmdhbiwgYSAxOXRoLWNlbnR1cnkgQnJpdGlzaCBtYXRoZW1hdGljaWFuLiBUaGUgcnVsZXMgYWxsb3cgdGhlIGV4cHJlc3Npb24gb2YgY29uanVuY3Rpb25zIGFuZCBkaXNqdW5jdGlvbnMgcHVyZWx5IGluIHRlcm1zIG9mIGVhY2ggb3RoZXIgdmlhIG5lZ2F0aW9uLjwvcD4NCg0KVGhlIHJ1bGVzIGNhbiBiZSBleHByZXNzZWQgaW4gRW5nbGlzaCBhczo8L2JyPg0KdGhlIG5lZ2F0aW9uIG9mIGEgY29uanVuY3Rpb24gaXMgdGhlIGRpc2p1bmN0aW9uIG9mIHRoZSBuZWdhdGlvbnM7IGFuZDwvYnI+DQp0aGUgbmVnYXRpb24gb2YgYSBkaXNqdW5jdGlvbiBpcyB0aGUgY29uanVuY3Rpb24gb2YgdGhlIG5lZ2F0aW9uczs8L3A+DQpvcjwvYnI+DQp0aGUgY29tcGxlbWVudCBvZiB0aGUgdW5pb24gb2YgdHdvIHNldHMgaXMgdGhlIHNhbWUgYXMgdGhlIGludGVyc2VjdGlvbiBvZiB0aGVpciBjb21wbGVtZW50czsgYW5kPC9icj4NCnRoZSBjb21wbGVtZW50IG9mIHRoZSBpbnRlcnNlY3Rpb24gb2YgdHdvIHNldHMgaXMgdGhlIHNhbWUgYXMgdGhlIHVuaW9uIG9mIHRoZWlyIGNvbXBsZW1lbnRzLjwvYnI+DQoNCjxpbWcgc3JjPSJodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zL3RodW1iLzAvMDYvRGVtb3JnYW5sYXdzLnN2Zy8zMzBweC1EZW1vcmdhbmxhd3Muc3ZnLnBuZyIgPg0KDQpGb3IgZXhhbXBsZSwgaWYgeW91IHdlcmUgdG8gZmluZCBmbGlnaHRzIHRoYXQgd2VyZW4ndCBkZWxheWVkIChvbiBhcnJpdmFsIG9yIGRlcGFydHVyZSkgYnkgbW9yZSB0aGFuIHR3byBob3VycywgeW91IGNvdWxkIHVzZSBlaXRoZXIgb2YgaHRlIGZvbGxvd2luZyB0d28gZmlsdGVyczoNCg0KYGBge3J9DQpmaWx0ZXIoZmxpZ2h0cywgIShhcnJfZGVsYXk+MTIwIHwgZGVwX2RlbGF5PjEyMCkpDQpgYGANCg0Kb3INCg0KYGBge3J9DQpmaWx0ZXIoZmxpZ2h0cywgYXJyX2RlbGF5PD0xMjAsIGRlcF9kZWxheTw9MTIwKQ0KYGBgDQpFeGFtcGxlcyA8L2JyPg0KDQpmaWx0ZXIobXRjYXJzLCBjeWwgPT0gOCk8L2JyPg0KZmlsdGVyKG10Y2FycywgY3lsIDwgNik8L2JyPg0KDQojIE11bHRpcGxlIGNyaXRlcmlhPC9icj4NCmZpbHRlcihtdGNhcnMsIGN5bCA8IDYgJiB2cyA9PSAxKTwvYnI+DQpmaWx0ZXIobXRjYXJzLCBjeWwgPCA2IHwgdnMgPT0gMSk8L2JyPg0KDQojIE11bHRpcGxlIGFyZ3VtZW50cyBhcmUgZXF1aXZhbGVudCB0byBhbmQ8L2JyPg0KZmlsdGVyKG10Y2FycywgY3lsIDwgNiwgdnMgPT0gMSk8L2JyPg0KVGhlcmUgYXJlIGFsc28gJiYgYW5kIHx8IGJ1dCBpdCB3aWxsIHRhdWdodCBsYXRlciBvbiB1bmRlciAiQ29uZGl0aW9uYWwgZXhlY3V0aW9uIg0KPC9wPg0KDQoNCjxoMj4gTWlzc2luZyBWYWx1ZXMgPC9oMj4NCk1pc3NpbmcgdmFsdWVzIG9yICJOQXMiIChub3QgYXZhaWxhYmxlKSByZXByZXNlbnQgYW4gdW5rbm93biB2YWx1ZSBzbyBtaXNzaW5nIHZhbHVlcyBhcmUgImNvbnRhZ2lvdXMiLiBBbG1vc3QgYW55IG9wZXJhdGlvbiBpbnZvbHZpbmcgYW4gdW5rbm93biB2YWx1ZSB3aWxsIGFsc28gYmUgdW5rb3duLg0KDQpgYGB7cn0NCk5BID4gNQ0KMTAgPT0gTkENCk5BICsgMTANCg0KIyBidXQgdGhpcyBvbmUgaXMgY29uZnVzaW5nDQpOQSA9PSBOQQ0KDQoNCmBgYA0KDQpUbyB0ZXN0IG9mIE5BLCB1c2UgaXMubmEoKQ0KDQpgYGB7cn0NCnggPC0gIE5BDQp5IDwtICBOQQ0KDQppcy5uYSh4KQ0KDQoNCmBgYA0KDQpGaWx0ZXIoKSBvbmx5IGluY2x1ZGVzIHJvd3Mgd2hlcmUgdGhlIGNvbmRpdGlvbiBpcyBUUlVFLiBJdCBleGNsdWRlcyBib3RoIEZBTFNFIGFuZCBOQS4gU28gaWYgeW91IHdhbnQgdG8gcHJlc2VydmUgbWlzc2luZyB2YWx1ZXMsIGFzayBmb3IgdGhlbSBleHBsaWNpdGx5DQoNCkluIHRoaXMgY2FzZSwgdGhlIE5BIGlzIG5vdCBsaXN0ZWQuDQoNCmBgYHtyfQ0KZGYgPC0gdGliYmxlKHg9YygxLE5BLDMpKQ0KZmlsdGVyKGRmLHg+MSkNCmBgYA0KDQoNCkluIHRoaXMgY2FzZSwgdGhlIE5BIGlzIGxpc3RlZC4NCg0KYGBge3J9DQpmaWx0ZXIoZGYsIGlzLm5hKHgpIHwgeD4xKQ0KYGBgDQoNCkV4ZXJjaXNlcyA8L2JyPg0KMS4gZmluZCBhbGwgZmxpZ2h0cyB0aGF0Og0KYS4gaGFkIGFuIGFycml2YWwgZGVsYXkgb2YgdHdvIGhvdXJzIG9yIG1vcmU6DQoNCmBgYHtyfQ0KDQpmaWx0ZXIoZmxpZ2h0cywgYXJyX2RlbGF5PjEyMCkNCg0KYGBgDQoNCmIuIGZsZXcgdG8gSG91c3RvbiAoSUFIIG9yIEhPVSkNCg0KYGBge3J9DQoNCmZpbHRlcihmbGlnaHRzLCBkZXN0ICVpbiUgYygiSUFIIiwiSE9VIikpDQoNCmBgYA0KDQpjLiBXZXJlIG9wZXJhdGVkIGJ5IFVuaXRlZCwgQW1lcmljYW4gb3IgRGVsdGENCmBgYHtyfQ0KZmlsdGVyKGZsaWdodHMsIGNhcnJpZXIgJWluJSBjKCJVQSIsIkFBIiwiREwiKSkNCmBgYA0KDQpkLiBEZXBhcnRlZCBpbiBzdW1tZXINCg0KYGBge3J9DQpmaWx0ZXIoZmxpZ2h0cywgbW9udGg+NiAmIG1vbnRoPDEwKQ0KYGBgDQoNCmUuIEFycml2ZWQgbW9yZSB0aGFuIDIgaG91cnMgbGF0ZSwgYnV0IGRpZG4ndCBsZWF2ZSBsYXRlDQoNCmBgYHtyfQ0KZmlsdGVyKGZsaWdodHMsIGFycl9kZWxheT4xMjAsIGRlcF9kZWxheTwyKQ0KYGBgDQoNCmYuIFdlcmUgZGVsYXllZCBieSBhdCBsZWFzdCBhbiBob3VyLCBidXQgbWFkZSB1cCBvdmVyIDMwIG1pbnV0ZXMgaW4gZmxpZ2h0DQoNCmBgYHtyfQ0KZmlsdGVyKGZsaWdodHMsIGFycl9kZWxheTw9OTAsIGRlcF9kZWxheT4xMjApDQpgYGANCg0KZy4gRGVwYXJ0ZWQgYmV0d2VlbiBtaWRuaWdodCBhbmQgNmFtIGluY2x1c2l2ZQ0KYGBge3J9DQpmaWx0ZXIoZmxpZ2h0cywgZGVwX3RpbWU+PTAwMCwgZGVwX3RpbWU8PTYwMCkNCmBgYA0KDQpVc2UgPGI+YmV0d2VlbigpPC9iPiB0byBzaW1wbGlmeSB0aGUgY29kZToNCmBgYHtyfQ0KZmlsdGVyKGZsaWdodHMsIGJldHdlZW4oZGVwX3RpbWUsMDAwLDYwMCkpDQpgYGANCg0KMy4gSG93IG1hbnkgZmxpZ2h0cyBoYXZlIGEgbWlzc2luZyBkZXBfdGltZT8gV2hhdCBvdGhlciB2YXJpYWJsZXMgYXJlIG1pc3Npbmc/IFdoYXQgbWlnaHQgdGhlc2Ugcm93cyByZXByZXNlbnQ/DQoNCmBgYHtyfQ0KIyBsaXN0IG1pc3NpbmcgZGVwX3RpbWUNCmZpbHRlcihmbGlnaHRzLCBpcy5uYShkZXBfdGltZSkpDQpgYGANCg0KQW5zd2VyOiBkZXBfZGVsYXksIGFycl90aW1lLCBhcnJfZGVsYXkgYXJlIGFsbCBtaXNzaW5nLiBUaGlzIG1pZ2h0IHJlcHJlc2VudCBjYW5jZWxsZWQgZmxpZ2h0cy4NCg0KTm93IHdlIGNhbiBhbHNvIHNvcnQgaW4gZGVzY2VuZGluZyBvcmRlciB1c2luZyB0aGUgQXJyYW5nZSgpIHZlcmIuDQoNCmBgYHtyfQ0KYXJyYW5nZShmaWx0ZXIoZmxpZ2h0cywgaXMubmEoZGVwX3RpbWUpKSxkZXNjKG1vbnRoKSkNCmBgYA0KDQoNCjxoMj4gQXJyYW5nZSBSb3dzIHdpdGggYXJyYW5nZSgpIDwvaDI+DQpBcnJhbmdlKGRhdGEsIGFyZzEsIGFyZzIsIGFyZzMuLi4pDQpJZiB5b3UgcHJvdmlkZSBtb3JlIHRoYW4gb25lIGNvbHVtbiBuYW1lLCBlYWNoIGFkZGl0aW9uYWwgY29sdW1uIHdpbGwgYmUgdXNlZCB0byBicmVhayB0aWVzIGluIHRoZSB2YWx1ZXMgb2YgcHJlY2VkaW5nIGNvbHVtbnMuIFlvdSBjYW4gYWxzbyB1c2UgZGVzYyhhcmcpIHRvIHJlb3JkZXIgYSBjb2x1bW4gaW4gZGVzY2VuZGluZyBvcmRlci4NCg0KYGBge3J9DQphcnJhbmdlKGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpDQpgYGANCg0KYGBge3J9DQphcnJhbmdlKGZsaWdodHMsIHllYXIsIGRlc2MobW9udGgpLCBkYXkpDQpgYGANCk1pc3NpbmcgdmFsdWVzIGFyZSBhbHdheXMgc29ydGVkIGF0IHRoZSBlbmQuIA0KVG8gc29ydCBtaXNzaW5nIHZhbHVlcyBhdCB0aGUgc3RhcnQsIHVzZSBpcy5uYSgpDQoNCmBgYHtyfQ0KYXJyYW5nZShmbGlnaHRzLCBkZXBfdGltZSxpcy5uYShkZXBfdGltZSkpDQpgYGANCg0KTm90ZTogQ291bGRudCBnZXQgTkEgdG8gc2hvdyB1cCBpbiB0aGUgdmlldy4NCjxoMj4gU2VsZWN0IGNvbHVtbnMgd2l0aCBzZWxlY3QoKSA8L2gyPg0KU2VsZWN0KCkgYWxsb3dzIHlvdSB0byB6b29tIGluIG9uIGEgdXNlZnVsIHN1YnNldCB1c2luZyBvcGVyYXRpb25zIGJhc2VkIG9uIHRoZSBuYW1lcyBvZiB0aGUgdmFyaWFibGVzLg0KDQpgYGB7cn0NCiMgc2hvdyBvbmx5IHllYXIsIG1vbnRoIGFuZCBkYXkNCnNlbGVjdChmbGlnaHRzLCB5ZWFyLCBtb250aCwgZGF5KQ0KYGBgDQoNClNvIG5vdGljZSB0aGF0IHRoZXJlIGFyZSBvbmx5IDMgY29sdW1ucyBub3cuIA0KDQpgYGB7cn0NCiMgc2VsZWN0IGFsbCBjb2x1bW5zIGJldHdlZW4geWVhciBhbmQgZGF5IGluY2x1c2l2ZQ0Kc2VsZWN0KGZsaWdodHMsIHllYXI6ZGF5KQ0KYGBgDQoNCmBgYHtyfQ0KIyBzZWxlY3QgYWxsIGNvbHVtbnMgRVhDRVBUIHRob3NlIGZyb20geWVhciB0byBkYXkgDQpzZWxlY3QoZmxpZ2h0cywgLSh5ZWFyOmRheSkpDQpgYGANCg0KPGgzPiBIZWxwZXIgZnVuY3Rpb25zIHdpdGhpbiBzZWxlY3QoKSA8L2gzPg0KPGxpPnN0YXJ0c193aXRoKCJhYmMiKTwvbGk+DQo8bGk+ZW5kc193aXRoKCJhYmMiKTwvbGk+DQo8bGk+Y29udGFpbnMoImlqayIpIDwvbGk+DQo8bGk+bWF0Y2hlcygiKC4pXFwxIikgPC9saT4NCjxsaT5udW1fcmFuZ2UoIngiLDE6MykgbWF0Y2hlcyB4MSx4Mix4MyAgPC9saT4gPC9wPg0KDQpzZWUgP3NlbGVjdCBmb3IgbW9yZSBkZXRhaWxzLiANCg0KPGgzPiBSZW5hbWUgPC9oMz4NCldoaWxlIHlvdSBjYW4gdXNlIHNlbGVjdCgpIHRvIHJlbmFtZSB2YXJpYWJsZXMsIGl0IGlzIG5vdCB1c2VmdWwgYmVjYXVzZSBpdCBkcm9wcyBhbGwgb2YgdGhlIHZhcmlhYmxlcyBub3QgZXhwbGljaXRseSBtZW50aW9uZWQuIEZvciB0aGlzLCB1c2UgdGhlIHJlbmFtZSgpIHRoYXQga2VlcHMgYWxsIHRoZSB2YXJpYWJsZXMgdGhhdCBhcmVuJ3QgZXhwbGljaXRseSBtZW50aW9uZWQuDQoNCmBgYHtyfQ0KcmVuYW1lKGZsaWdodHMsIERlcGFydHVyZVRpbWUgPSBkZXBfdGltZSkNCmBgYA0KDQoNCg0KV2hhdCBoYXBwZW5zIHdoZW4gd2UgdXNlIGEgdmFyaWFibGUgbXVsaXRwbGUgdGltZXMgaW4gYSBzZWxlY3QgY2FsbD8NCmBgYHtyfQ0Kc2VsZWN0KGZsaWdodHMsIG9yaWdpbiwgZGVzdCwgYWlyX3RpbWUsIG9yaWdpbikNCmBgYA0KDQpgYGB7cn0NCnNlbGVjdChmbGlnaHRzLCBjb250YWlucygiVElNRSIpKQ0KYGBgDQoNCjxoMj4gQWRkIG5ldyB2YXJpYWJsZXMgd2l0aCBNdXRhdGUoKSA8L2gyPg0KVXNlIHRoZSBtdXRhdGUgZnVuY3Rpb24gd2hlbiB5b3Ugd2FudCB0byBhZGQgbmV3IHZhcmlhYmxlcyB0aGF0IGFyZSBhIGZ1bmN0aW9uIG9mIGV4aXN0aW5nIGNvbHVtbnMuIDwvYnI+DQpNdXRhdGUgYWx3YXlzIGFkZHMgbmV3IHZhcmlhYmxlcyB0byB0aGUgZW5kIG9mIHRoZSBkYXRhc2V0Lg0KDQpgYGB7cn0NCmZsaWdodHNfc21sIDwtICBzZWxlY3QoZmxpZ2h0cywgeWVhcjpkYXksIGVuZHNfd2l0aCgiZGVsYXkiKSwgZGlzdGFuY2UsIGFpcl90aW1lKQ0KbXV0YXRlKGZsaWdodHNfc21sLCBnYWluPWFycl9kZWxheSAtZGVwX2RlbGF5LCBzcGVlZD0gZGlzdGFuY2UgL2Fpcl90aW1lICogNjApDQpgYGANCg0KTm90ZTogZ2FpbiBhbmQgc3BlZWQgYXJlIHRoZSBuZXcgY29sdW1ucyBnZW5lcmF0ZWQgYnkgbXV0YXRlKCkNCjwvYnI+DQpJZiB5b3Ugb25seSB3YW50IHRvIGtlZXAgdGhlIG5ld2x5IGdlbmVyYXRlZCB2YXJpYWJsZXMgdXNlIHRyYW5zbXV0KCkNCg0KYGBge3J9DQp0cmFuc211dGUoZmxpZ2h0cywgZ2Fpbj1hcnJfZGVsYXkgLWRlcF9kZWxheSwgc3BlZWQ9IGRpc3RhbmNlIC9haXJfdGltZSAqIDYwKQ0KYGBgDQoNCg0KPGgyPiBVc2VmdWwgQ3JlYXRpb24gZnVuY3Rpb25zIDwvaDI+DQoNCjxoMz5Nb2R1bGFyIGFyaXRoZW1ldGljICglLyUgYW5kICUlKSA8L2gzPg0KSW50ZWdlciBEaXZpc2lvbiAoJS8lKSBhbmQgcmVtYWluZGVyICglJSkgWW91IGNhbiB1c2UgdGhpcyBpbiB0aGUgTllDIEZsaWdodHN0IA0KDQpgYGB7cn0NCiMgY29udmVydCBkZXBfdGltZSBpdG8gaG91cnMgYW5kIG1pbnV0ZXMNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShueWNmbGlnaHRzMTMpDQp0cmFuc211dGUoZmxpZ2h0cywgZGVwX3RpbWUsIGhvdXI9ZGVwX3RpbWUgJS8lIDEwMCwgbWludXRlPWRlcF90aW1lICUlIDEwMCkNCg0KYGBgDQo8aDM+IExvZ3MgbG9nKCksbG9nMigpICwgbG9nMTAoKTwvaDM+DQpMb2dhcml0aG1zIGFyZSBhbiBpbmNyZWRpYmx5IHVzZWZ1bCB0cmFuc2Zvcm1hdGlvbiBmb3IgZGVhbGxpbmcgd2l0aCBkYXRhIHRoYXQgcmFuZ2VzIGFjcm9zcyBtdWx0aXBsZSBvcmRlcnMgb2YgbWFnbml0dWRlLiBUaGV5IGFsc28gY29udmVydCBtdWx0aXBsaWNhdGl2ZSByZWxhdGlvbnNoaXBzIHRvIGFkZGl0aXZlIDwvUD4NCg0KPmgzPiBPZmZzZXRzIDwvaDM+DQpsZWFkKCkgYW5kIExhZygpIGFsbG93eW91IHRvIHJlZmVyIHRvIGxlYWRpbmcgb3IgbGFnZ2luZyB2YWx1ZXMuIFRoaXMgY2FuIGJlIHVzZWQgdG8gY29tcHV0ZSBydW5uaW5nIGRpZmZlcmVuY2VzICB4LWxhZyh4KSBvciBmaW5kIHdoZW4gdmFsdWVzIGNoYW5nZSAoeCE9bGFnKHgpKS4gIFRoZXkgYXJlIG1vc3QgdXNlZnVsIGluIGNvbmp1bmN0aW9uIHdpdGggZ3JvdXBfYnkoKSANCjwvcD4NCg0KYGBge3J9DQp4PC0xOjEwDQojIGRpc3BsYXkgeCB2YWx1ZXMNCngNCiMgZGlzcGxheSBsYWcoeCkNCmxhZyh4KQ0KIyBkaXNwbGF5IGxlYWQoeCkNCmxlYWQoeCkNCg0KYGBgDQoNCjxoMz4gQ3VtbXVsYXRpdmUgYW5kIHJvbGxpbmcgYWdncmVnYXRlcyA8L2gzPg0KRnVuY3Rpb25zIGZvciBydW5uaW5nIHN1bXMsIHByb2R1Y3RzLCBtaW5zLCBhbmQgbWF4ZXM6IGN1bXN1bSgpLCBjdW1wcm9kKCksIGN1bW1pbigpLCBjdW1tYXgoKSBhbmQgZHBseXIgcHJvdmlkZXMgY3VtbWVhbigpIGZvciBjdW1tdWxhdGl2ZSBtZWFucy4gVHJ5IGFsc28gdGhlIFJjcHBSb2xsIHBhY2thZ2UNCg0KYGBge3J9DQojIHByaW50IHggYWdhaW4NCngNCiMgd2hhdCBpcyB0aGUgY3VtbXVsYXRpdmUgc3VtIG9mIHg/DQpjdW1zdW0oeCkNCiMgd2hhdCBpcyB0aGUgY3VtbXVsYXRpdmUgbWVhbiBvZiB4Pw0KY3VtbWVhbih4KQ0KYGBgDQoNCg0KPGgzPiBSYW5raW5nPC9oMz4NClRyeSBtaW5fcmFuaygpIHNtYWxsZXN0IHZhbHVlcz0gc21hbGxlc3QgcmFua3MNCg0KYGBge3J9DQoNCiMgc2V0IHVwIHZhcmlhYmxlDQp5IDwtIGMoMSwyLDIsTkEsMyw0KQ0KIyBkaXNwbGF5IFkgdmFsdWVzDQp5DQojIGRpc3BsYXkgcmFuaw0KbWluX3JhbmsoeSkNCm1pbl9yYW5rKGRlc2MoeSkpDQoNCmBgYA0KDQoNCklmIHJhbmsoKSBkb2VzbnQgZG8gd2hhdCB5b3UgbmVlZCwgbG9vayBhdCB0aGUgdmFyaWFudHMgcm93X251bWJlcigpLCBkZW5zZV9yYW5rKCksIHBlcmNlbnRfcmFuaygpLCBjdW1lX2Rpc3QoKSwgYW5kIG50aWxlKCkuDQoNCmBgYHtyfQ0KeSANCnJvd19udW1iZXIoeSkNCmRlbnNlX3JhbmsoeSkNCmN1bWVfZGlzdCh5KQ0KcGVyY2VudF9yYW5rKHkpDQpgYGANCg0KPGgyPiBHcm91cGVkIFN1bW1hcmllcyB3aXRoIFN1bW1hcml6ZSgpIDwvaDI+DQpzdW1tYXJpemUoKSBjb2xsYXBzZXMgYSBkYXRhIGZyYW1lIHRvIGEgc2luZ2xlIHJvdy4gDQoNCmBgYHtyfQ0KbGlicmFyeShueWNmbGlnaHRzMTMpDQpzdW1tYXJpemUoZmxpZ2h0cywgZGVsYXk9bWVhbihkZXBfZGVsYXksIG5hLnJtPVRSVUUpKQ0KYGBgDQoNCg0KRW5oYW5jZSBzdW1tYXJpemUoKSB3aXRoIGdyb3VwX2J5IHRvIGdldCBzdW1tYXJpZXMgYnkgZ3JvdXAgb2YgZGF0YS4NCg0KYGBge3J9DQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykNCmJ5X2RheSA8LSBncm91cF9ieShmbGlnaHRzLCB5ZWFyLCBtb250aCwgZGF5KQ0Kc3VtbWFyaXplKGJ5X2RheSwgZGVsYXk9bWVhbihkZXBfZGVsYXksIG5hLnJtPVRSVUUpKQ0KYGBgDQoNClRoZW4gd2UgY2FuIGNvbWJpbmUgbXVsdGlwbGUgb3BlcmF0aW9ucyB3aXRoIHRoZSBwaXBlIDwvYnI+DQpTdXBwb3NlIHdlIHdhbnQgdG8gZmluZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGRpc3RhbmNlIGFuZCBhdmVyYWdlIGRlbGF5IGZvciBlYWNoIGxvY2F0aW9uLiANCg0KYGBge3J9DQojIE9sZCBtZXRob2Qgd2l0aG91dCB1c2luZyBQaXBlcw0KbGlicmFyeShueWNmbGlnaHRzMTMpDQpieV9kZXN0IDwtIGdyb3VwX2J5KGZsaWdodHMsZGVzdCkNCmRlbGF5IDwtIHN1bW1hcml6ZShieV9kZXN0LCBjb3VudD1uKCksIGRpc3Q9bWVhbihkaXN0YW5jZSwgbmEucm09VFJVRSksIGRlbGF5PW1lYW4oYXJyX2RlbGF5LCBuYS5ybSA9IFRSVUUpKQ0KIyBmaWx0ZXIgb3V0IEhOTCBob25vbHVsdSBhcyBpdCBpcyB0d2ljZSBhcyBmYXIgYXdheSBhcyB0aGUgbmV4dCBjbG9zZXN0IGFpcnBvcnQNCmRlbGF5IDwtIGZpbHRlcihkZWxheSwgY291bnQ+MjAsIGRlc3QgIT0iSE5MIikNCmRlbGF5DQpgYGANCg0KYGBge3J9DQojIGxldCB1cyBwbG90IHRoZSB2YWx1ZXMgdG8gc2VlIHRoZSBjb3JyZWxhdGlvbg0KZ2dwbG90KGRhdGE9ZGVsYXksIG1hcHBpbmc9YWVzKHg9ZGlzdCwgeT1kZWxheSkpKw0KICBnZW9tX3BvaW50KGFlcyhzaXplPWNvdW50KSwgYWxwaGE9MS8zKSsNCiAgZ2VvbV9zbW9vdGgoc2U9RkFMU0UpDQoNCmBgYA0KDQpOb3csIGxldCB1cyBzZWUgaG93IFBpcGUgPGI+KCU+JSk8L2I+IHdpbGwgbWFrZSB0aGUgY29kZSBtb3JlIGVmZmljaWVudDoNCg0KYGBge3J9DQpkZWxheXMgPC0gZmxpZ2h0cyAlPiUgDQogIGdyb3VwX2J5KGRlc3QpICU+JSANCiAgc3VtbWFyaXplKGNvdW50PW4oKSwgIGRpc3Q9bWVhbihkaXN0YW5jZSwgbmEucm09VFJVRSksICBkZWxheT1tZWFuKGFycl9kZWxheSwgbmEucm09VFJVRSkpJT4lIA0KICBmaWx0ZXIoY291bnQ+MjAsIGRlc3QgIT0iSE5MIikNCg0KZ2dwbG90KGRhdGE9ZGVsYXksIG1hcHBpbmc9YWVzKHg9ZGlzdCwgeT1kZWxheSkpKw0KICBnZW9tX3BvaW50KGFlcyhzaXplPWNvdW50KSwgYWxwaGE9MS8zKSsNCiAgZ2VvbV9zbW9vdGgoc2U9RkFMU0UpDQpgYGANCg0KQSBnb29kIHdheSB0byB0aGluayBvZiBQaXBlIDxiPiglPiUpPC9iPiBpcyBieSByZWFkaW5nIGl0IGFzICJ0aGVuIi4NCkJlaGluZCB0aGUgc2NlbmVzLCB4ICU+JSBmKHkpIG1lYW5zIGYoeCx5KSA8L2JyPg0KeCAlPiUgZih5KSAlPiUgZyh6KSB0dXJucyBpbnRvIGcoZih4LHkpLHopIDwvYnI+DQpSZWFkIGxlZnQgdG8gcmlnaHQsIHRvcCB0byBib3R0b20uIA0KDQo8aDI+IE1pc3NpbmcgVmFsdWVzIDwvaDI+DQpuYS5ybSBhcmd1bWVudCBpcyB1c2UgdG8gdGFrZSBjYXJlIG9mIG1pc3NpbmcgdmFsdWVzLiANCg0KYGBge3J9DQojIHNlZSBob3cgbWFueSBtaXNzaW5nIHZhbHVlcyB0aGVyZSBhcmUNCmZsaWdodHMgJT4lIGdyb3VwX2J5KHllYXIsbW9udGgsZGF5KSAlPiUNCiAgc3VtbWFyaXplKG1lYW49bWVhbihkZXBfZGVsYXkpKQ0KDQpgYGANCg0KDQpGb3J0dW5hdGVseSwgYWxsIGFnZ3JlZ2F0ZSBmdW5jdGlvbnMgaGF2ZSB0aGUgbmEucm0gYXJndW1lbnQgKE5BIHJlbW92ZSkgcHJpb3IgdG8gY29tcHV0YXRpb24NCg0KYGBge3J9DQpmbGlnaHRzICU+JSBncm91cF9ieSh5ZWFyLG1vbnRoLGRheSkgJT4lDQogIHN1bW1hcml6ZShtZWFuPW1lYW4oZGVwX2RlbGF5LCBuYS5ybSA9VFJVRSkpDQpgYGANCg0KVGFraW5nIGNhcmUgb2YgY2FuY2VsbGVkIGZsaWdodHMNCg0KYGBge3J9DQpub3RfY2FuY2VsbGVkIDwtICBmbGlnaHRzICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGRlcF9kZWxheSksICFpcy5uYShhcnJfZGVsYXkpKQ0KDQpub3RfY2FuY2VsbGVkICU+JQ0KICBncm91cF9ieSh5ZWFyLG1vbnRoLCBkYXkpICU+JQ0KICBzdW1tYXJpemUobWVhbj1tZWFuKGRlcF9kZWxheSkpDQojIG5vIG5lZWQgdG8gdXNlIG5hLnJtIHNpbmNlIGFsbCBtaXNzaW5nIHZhbHVlcyBoYXZlIGJlZW4gZmlsdGVyIG91dCBhbHJlYWR5DQpgYGANCg0KPGgyPiBDb3VudHMgPC9oMj4NCkl0cyBhIGdvb2QgaWRlYSB0byBpbmNsdWRlIGVpdGhlciBhIGNvdW50IG4oKSBvciBhIGNvdW50IG9mIG5vbm1pc3NpbmcgdmFsdWVzIHN1bSghaXMubmEoeCkpIHNvIHRoYXQgeW91IGtub3cgdGhlIHNhbXBsZSBzaXplLiANCg0KRm9yIGV4YW1wbGUgdGhpcyBiZWxvdyBzaG93cyBhbiBhdmVyYWdlIGRlbGF5IG9mIDUgaG91cnMhDQoNCmBgYHtyfQ0KZGVsYXlzIDwtIG5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KHRhaWxudW0pICU+JQ0KICBzdW1tYXJpemUoZGVsYXk9bWVhbihhcnJfZGVsYXkpKQ0KDQpnZ3Bsb3QoZGF0YT1kZWxheXMsIG1hcHBpbmc9YWVzKHg9ZGVsYXkpKSsNCiAgZ2VvbV9mcmVxcG9seShiaW53aWR0aD0xMCkNCmBgYA0KDQpXZSBjYW4gZ2V0IGluc2lnaHQgaWYgd2UgZHJhdyBhIHNjYXR0ZXJwbG90IG9mIG51bWJlciBvZiBsaWdodHMgdmVyc3VzIGF2ZXJhZ2UgZGVsYXk6DQoNCmBgYHtyfQ0KZGVsYXlzIDwtIG5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KHRhaWxudW0pICU+JQ0KICBzdW1tYXJpemUoZGVsYXk9bWVhbihhcnJfZGVsYXksIG5hLnJtPVRSVUUpLCBuPSBuKCkpDQoNCmdncGxvdChkYXRhPWRlbGF5cywgbWFwcGluZz1hZXMoeD1uLCB5PWRlbGF5KSkrDQogIGdlb21fcG9pbnQoYWxwaGE9MS8xMCkNCmBgYA0KVGhlcmUgYXJlIHdpZGVyIHZhcmlhdGlvbnMgd2hlbiB0aGVyZSBhcmUgbGVzc2VyIGZsaWdodHMgdGhhbiB3aGVuIHRoZXJlIGFyZSBhIGxvdCBvZiBmbGlnaHRzLg0KU28gZ2V0IG1vcmUgZGV0YWlscywgbGV0IHVzIGZpbHRlciBvdXQgdGhlIHdpbGQgcGF0dGVybnMgd2hlbiBuPjI1DQoNCmBgYHtyfQ0KZGVsYXlzICU+JQ0KICBmaWx0ZXIobj4yNSkgJT4lDQogIGdncGxvdChtYXBwaW5nPWFlcyh4PW4sIHk9ZGVsYXkpKSsNCiAgZ2VvbV9wb2ludChhbHBoYT0xLzEwKQ0KDQpgYGANCg0KTG9vayBhdCBhdnZlcmFnZSBwZXJmb3JtYW5jZSBvZiBiYXNlYmFsbCBiYXR0ZXJzIHRvIG51bWJlIG9mIHRpbWVzIHRoZXkncmUgYXQgYSBiYXQuIFVzaW5nIExhaG1hbiBwYWNrYWdlIHRvIGNvbXB1dGUgdGhlIGJhdHRpbmcgYXZlcmFnZSAobnVtYmVyIG9mIGhpdHMvbnVtYmVyIG9mIGF0dGVtcHRzKSBvZiBldmVyeSBtYWpvcmUgbGVhZ3VlIGJhc2ViYWxsIHBsYXllci4NCg0KYGBge3J9DQojIHlvdSBuZWVkIHRvIGluc3RhbGwgdGhlIExhaG1hbiBwYWNrYWdlIGZpcnN0IGZvciB0aGlzIHRvIHdvcmsgDQpiYXR0aW5nIDwtIGFzX3RpYmJsZShMYWhtYW46OkJhdHRpbmcpDQoNCmJhdHRlcnMgPC0gIGJhdHRpbmcgJT4lDQogIGdyb3VwX2J5KHBsYXllcklEKSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIGJhPXN1bShILCBuYS5ybT1UUlVFKS9zdW0oQUIsIG5hLnJtPVRSVUUpLA0KICAgIGFiPXN1bShBQiwgbmEucm09VFJVRSkNCiAgKQ0KDQpiYXR0ZXJzICU+JQ0KICBmaWx0ZXIoYWIgPjEwMCkgJT4lDQogIGdncGxvdChtYXBwaW5nPWFlcyh4PWFiLCB5PWJhKSkrDQogIGdlb21fcG9pbnQoKSsNCiAgZ2VvbV9zbW9vdGgoc2U9RkFMU0UpDQoNCiAgICANCmBgYA0KVGhlIGltcGxpY2F0aW9uLCBpZiB5b3UgbmFpdmVseSBzb3J0IG9uIGRlc2MoYmEpIHRoZSBwb3BsZSB3aXRoIHRoZSBiZXN0IGJhdHRpbmcgYXZlcmFnZXMgYXJlIGNsZWFybHkgbHVja3kuLi5ub3Qgc2tpbGxlZC4NCg0KDQpgYGB7cn0NCmJhdHRlcnMgJT4lDQogIGFycmFuZ2UoZGVzYyhiYSkpDQpgYGANCg0KPGgyPiBVc2VmdWwgU3VtbWFyeSBGdW5jdGlvbnMgPC9oMj4NCg0KDQoNCg0KDQoNCjxoMz4gTWVhc3VyZXMgb2YgbG9jYXRpb24gIDwvaDM+DQpNZWRpYW4gaXMgYSB2YWx1ZSB3aGVyZSA1MCUgb2YgeCBpcyBhYm92ZSBpdCwgYW5kIDUwJSBpcyBiZWxvdyBpdC4gDQpUcnkgY29tYmluaW5nIGFnZ3JlZ2F0aW9ucyB3aXRoIHN1YnNldHRpbmcNCg0KYGBge3J9DQpub3RfY2FuY2VsbGVkICU+JQ0KICBncm91cF9ieSh5ZWFyLCBtb250aCwgZGF5KSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIGF2Z19kZWxheTE9bWVhbihhcnJfZGVsYXkpLA0KICAgIGF2Z19kZWxheTI9bWVhbihhcnJfZGVsYXlbYXJyX2RlbGF5PjBdKQ0KICApDQpgYGANCg0KPGgzPiBtZWFzdXJlcyBvZiBzcHJlYWQgPC9oMz4NCk1lYW4gc3F1YXJlZCBkZXZpYXRpb24sIG9yIHN0YW5kYXJkIGRldmlhdGlvbiBvciBTRCBmb3Igc2hvcnQgaXMgdGhlIHN0YW5kYXJkIG1lYXN1cmUgb2Ygc3ByZWFkLiBUaGUgaW50ZXJxdWFydGlsZSByYW5nZSBJUVIoKSBhbmQgbWVkaWFuIGFic291dGUgZGV2aWF0aW9uIG1hZCh4KSBhcmUgcm9idXN0IGVxdWl2YWxlbnRzIHRoYXQgbWF5IGJlIG1vcmUgdXNlZnVsIGlmIHlvdSBoYXZlIG91dGxpZXJzLg0KDQpgYGB7cn0NCiMgd2h5IGlzIGRpc3RhbmNlIHRvIHNvbWUgZGVzdGluYXRpb25zIG1vcmUgdmFyaWFibGUgdGhhbiB0byBvdGhlcnM/DQpub3RfY2FuY2VsbGVkICU+JQ0KICBncm91cF9ieShkZXN0KSAlPiUNCiAgc3VtbWFyaXNlKGRpc3RhbmNlX3NkPXNkKGRpc3RhbmNlKSklPiUNCiAgYXJyYW5nZShkZXNjKGRpc3RhbmNlX3NkKSkNCmBgYA0KDQo8aDM+IE1lYXN1cmVzIG9mIHJhbmsgbWluKHgpLCBxdWFudGlsZSh4LDAuMjUpLCBtYXgoeCkgPC9oMz4NClF1bmF0aWxlcyBhcmUgYSBnZW5lcmFsaXphdGlvbiBvZiB0aGUgbWVkaWFuLiBRdWFudGlsZSh4LDAuMjUpIHdpbGwgZmluZCBhIHZhbHVlIG9mIHggdGhhdCBpcyBncmVhdGVyIHRoYW4gMjUlIG9mIHRoZSB2YWx1ZXMgYW5kIGxlc3MgdGhhbiB0aGUgcmVtYWluaW5nIDc1JQ0KDQpgYGB7cn0NCiMgd2hlbiBkbyB0aGUgZmlyc3QgYW5kIGxhc3QgZmxpZ2h0cyBsZWF2ZSBlYWNoIGRheT8NCm5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KHllYXIsIG1vbnRoLGRheSkgJT4lDQogIHN1bW1hcml6ZShmaXJzdD1taW4oZGVwX3RpbWUpLCBsYXN0PW1heChkZXBfdGltZSkpDQpgYGANCg0KPGgzPiBNZWFzdXJlcyBvZiBwb2lzdGlvbiBmaXJzdCh4KSwgbnRoKHgsMikgbGFzdCh4KSA8L2gzPg0KYGBge3J9DQojIGZpbmQgdGhlIGZpcnN0IGFuZCBsYXN0IGRlcGFydHVyZSBmb3IgZWFjaCBkYXkNCm5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KHllYXIsIG1vbnRoLGRheSkgJT4lDQogIHN1bW1hcml6ZShmaXJzdF9kZXA9Zmlyc3QoZGVwX3RpbWUpLCBsYXN0X2RlcD1sYXN0KGRlcF90aW1lKSkNCmBgYA0KDQpUaGVzZSBmdW5jdGlvbnMgYXJlIGNvbXBsZW1lbnRhcnkgdG8gZmlsdGVyaW5nIG9uIHJhbmtzIA0KDQpgYGB7cn0NCm5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KHllYXIsIG1vbnRoLGRheSkgJT4lDQogIG11dGF0ZShyPW1pbl9yYW5rKGRlc2MoZGVwX3RpbWUpKSkgJT4lDQogIGZpbHRlcihyICVpbiUgcmFuZ2UocikpDQoNCmBgYA0KDQpUbyBjb3VudCB0aGUgbnVtYmVyIG9mIGRpc3RpbmN0IHZhbHVlcw0KDQpgYGB7cn0NCm5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KGRlc3QpICU+JQ0KICBzdW1tYXJpemUoY2FycmllcnM9bl9kaXN0aW5jdChjYXJyaWVyKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhjYXJyaWVycykpDQpgYGANCg0KWW91IGNhbiB1c2UgYSB3ZWlnaHQgdmFyaWFibGUuIFlvdSBjYW4gdXNlIHRoaXMgdG8gY291bnQgdGhlIHRvdGFsIG51bWJlciBvZiBtaWxlcyBhIHBsYW5lIGZsZXcuDQoNCmBgYHtyfQ0Kbm90X2NhbmNlbGxlZCAlPiUNCiAgY291bnQodGFpbG51bSx3dD1kaXN0YW5jZSkNCmBgYA0KDQo8aDM+IENvdW50cyBhbmQgcHJvcG9ydGlvbnMgb2YgbG9naWNhbCB2YWx1ZXMgPC9oMz4NCldoZW4geW91IHdhbnQgdG8gY291bnQgdGhlIG51bWJlciBvZiB0cnVlcyAoMSkgYW5kIGZhbHNlKDApDQoNCmBgYHtyfQ0Kbm90X2NhbmNlbGxlZCAlPiUNCiAgZ3JvdXBfYnkoeWVhciwgbW9udGgsIGRheSkgJT4lDQogIHN1bW1hcml6ZShuX2Vhcmx5ID1zdW0oZGVwX3RpbWU8NTAwKSkNCmBgYA0KDQo8aDM+IEdyb3VwaW5nIE11bHRpcGxlIFZhcmlhYmxlcyA8L2gzPg0KV2hlbiB5b3UgZ3JvdXAgYnkgbXVsdGlwbGUgdmFyaWFibGVzICwgZWFjaCBzdW1tYXJ5IHBlZWxzIG9mZiBvbmUgbGV2ZWwgb2YgdGhlIGdyb3VwaW5nLiBUaGF0IG1ha2VzIGl0IGVhc3kgdG8gcHJvZ3Jlc3NpdmVseSByb2xsIHVwIGEgZGF0YXNldC4gDQoNCmBgYHtyfQ0KZGFpbHkgPC0gZ3JvdXBfYnkoZmxpZ2h0cywgeWVhciwgbW9udGgsIGRheSkNCihwZXJfZGF5IDwtIHN1bW1hcml6ZShkYWlseSwgZmxpZ2h0cz1uKCkpKQ0KYGBgDQogDQpgYGB7cn0NCihwZXJfbW9udGggPC0gIHN1bW1hcml6ZShwZXJfZGF5LCBmbGlnaHRzPXN1bShmbGlnaHRzKSkpDQpgYGANCg0KYGBge3J9DQojIHBlciB5ZWFyDQoocGVyX3llYXIgPC0gc3VtbWFyaXplKHBlcl9tb250aCwgZmxpZ2h0cz1zdW0oZmxpZ2h0cykpKQ0KYGBgDQoNClRvIHJlbW92ZSBncm91cGluZ3MsIHVzZSBVbmdyb3VwKCkNCg0KYGBge3J9DQpkYWlseSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzdW1tYXJpemUoZmxpZ2h0cz1uKCkpDQpgYGANCg0KPGgyPiBHcm91cGVkIE11dGF0ZXMgYW5kIEZpbHRlcnMgPC9oMj4NCkdyb3VwaW5nIGlzIG1vc3QgdXNlZnVsIGluIGNvbmp1bmN0aW9uIHdpdGggc3VtbWFyaXplKCksIGJ1dCB5b3UgY2FuIGFsc28gZG8gY29udmVuaWVudCBvcGVyYXRpb25zIHdpdGggbXV0YXRlKCkgYW5kIGZpbHRlcigpDQoNCmBgYHtyfQ0KIyBmaW5kIHRoZSB3b3JzdCBtZW1iZXJzIG9mIGVhY2ggZ3JvdXANCmZsaWdodHNfc21sICU+JQ0KICBncm91cF9ieSh5ZWFyLCBtb250aCwgZGF5KSAlPiUNCiAgZmlsdGVyKHJhbmsoZGVzYyhhcnJfZGVsYXkpKTwxMCkNCmBgYA0KDQpgYGB7cn0NCiMgZmluZCBhbGwgZ3JvdXBzIGJpZ2dlciB0aGFuIGEgdGhyZXNob2xkDQpwb3B1bGFyX2Rlc3RzIDwtIGZsaWdodHMgJT4lDQogIGdyb3VwX2J5KGRlc3QpICU+JQ0KICBmaWx0ZXIobigpID4zNjUpDQojIGRpc3BsYXkNCnBvcHVsYXJfZGVzdHMNCg0KYGBgDQoNCmBgYHtyfQ0KIyBzdGFuZGFyZGllIHRvIGNvbXB1dGUgcGVyIGdyb3VwIG1ldHJpY3MNCnBvcHVsYXJfZGVzdHMgPC0gZmxpZ2h0cyAlPiUNCiAgIGZpbHRlcihhcnJfZGVsYXk+MCkgJT4lDQogIG11dGF0ZShwcm9wX2RlbGF5PWFycl9kZWxheSAvc3VtKGFycl9kZWxheSkpICU+JQ0KICBzZWxlY3QoeWVhcjpkYXksZGVzdCxhcnJfZGVsYXksIHByb3BfZGVsYXkpDQojIGRpc3BsYXkNCnBvcHVsYXJfZGVzdHMNCmBgYA0KDQo=