Our goal is to understand how some very powerful packages work their magic. Of somewhat recent vintage, {dplyr}, {tidyr} and {lubridate} have quickly gained a fan following because they allow you to clean and organize your data and then calculate quantities of interest with surprising ease. In this module we will start to see some of these packages’ core functionalities and wrap up this particular leg of our learning journey in the next module.

1 {dplyr}

There is a common quote that is tossed about a good bit in the hallways of data science, that, and I am paraphrasing here, a data scientists spends about 80% of their time gathering, cleaning, and organizing their data and spends only about 20% of their time on the analysis per se. This may or may not be true, especially in the initial stages of a new project but yes, we do spend an awfully large amount of our time getting the data ready for visualizations and other analyses. You do this work long enough and you come to realize that anything you could do to speed up the cleaning phase would be time and money saved. And yet, data cleaning skills are in short supply. No more, I say, because packages like {dplyr} and {data.table} have simplified what were once nightmare tasks.

Nightmare is not a word to be tossed around lightly and so let us setup a seemingly large data-set with 100+ columns, and tons of information hidden in it. Once we setup this Cyclops, spell out a few questions we would like to answer, we might better appreciate how {dplyr} comes to our aid. In particular, we might come to understand how {dplyr} uses seven core verbs:

What you want to do … {dplyr} function
you need to select columns to work with? select()
you need to use a subset of the data based on some criterion? filter()
you need to arrange the data in ascending/descending order of variable(s)? arrange()
you want the results of your calculations to be a standalone data frame? summarise()
you want to add your calculated value(s) to the existing data frame? mutate()
you want to add your calculated value(s) to the existing data frame but also drop all variables/columns not being used in the calculation? transmute()
you need to calculate averages, frequencies, etc by groups? group_by()

Here comes cyclops – all flights originating and departing from Columbus (Ohio) January through September of 2017. Let us load the data, and the {tidyverse} and {here} packages.

##   [1] "Year"                 "Quarter"              "Month"               
##   [4] "DayofMonth"           "DayOfWeek"            "FlightDate"          
##   [7] "UniqueCarrier"        "AirlineID"            "Carrier"             
##  [10] "TailNum"              "FlightNum"            "OriginAirportID"     
##  [13] "OriginAirportSeqID"   "OriginCityMarketID"   "Origin"              
##  [16] "OriginCityName"       "OriginState"          "OriginStateFips"     
##  [19] "OriginStateName"      "OriginWac"            "DestAirportID"       
##  [22] "DestAirportSeqID"     "DestCityMarketID"     "Dest"                
##  [25] "DestCityName"         "DestState"            "DestStateFips"       
##  [28] "DestStateName"        "DestWac"              "CRSDepTime"          
##  [31] "DepTime"              "DepDelay"             "DepDelayMinutes"     
##  [34] "DepDel15"             "DepartureDelayGroups" "DepTimeBlk"          
##  [37] "TaxiOut"              "WheelsOff"            "WheelsOn"            
##  [40] "TaxiIn"               "CRSArrTime"           "ArrTime"             
##  [43] "ArrDelay"             "ArrDelayMinutes"      "ArrDel15"            
##  [46] "ArrivalDelayGroups"   "ArrTimeBlk"           "Cancelled"           
##  [49] "CancellationCode"     "Diverted"             "CRSElapsedTime"      
##  [52] "ActualElapsedTime"    "AirTime"              "Flights"             
##  [55] "Distance"             "DistanceGroup"        "CarrierDelay"        
##  [58] "WeatherDelay"         "NASDelay"             "SecurityDelay"       
##  [61] "LateAircraftDelay"    "FirstDepTime"         "TotalAddGTime"       
##  [64] "LongestAddGTime"      "DivAirportLandings"   "DivReachedDest"      
##  [67] "DivActualElapsedTime" "DivArrDelay"          "DivDistance"         
##  [70] "Div1Airport"          "Div1AirportID"        "Div1AirportSeqID"    
##  [73] "Div1WheelsOn"         "Div1TotalGTime"       "Div1LongestGTime"    
##  [76] "Div1WheelsOff"        "Div1TailNum"          "Div2Airport"         
##  [79] "Div2AirportID"        "Div2AirportSeqID"     "Div2WheelsOn"        
##  [82] "Div2TotalGTime"       "Div2LongestGTime"     "Div2WheelsOff"       
##  [85] "Div2TailNum"          "Div3Airport"          "Div3AirportID"       
##  [88] "Div3AirportSeqID"     "Div3WheelsOn"         "Div3TotalGTime"      
##  [91] "Div3LongestGTime"     "Div3WheelsOff"        "Div3TailNum"         
##  [94] "Div4Airport"          "Div4AirportID"        "Div4AirportSeqID"    
##  [97] "Div4WheelsOn"         "Div4TotalGTime"       "Div4LongestGTime"    
## [100] "Div4WheelsOff"        "Div4TailNum"          "Div5Airport"         
## [103] "Div5AirportID"        "Div5AirportSeqID"     "Div5WheelsOn"        
## [106] "Div5TotalGTime"       "Div5LongestGTime"     "Div5WheelsOff"       
## [109] "Div5TailNum"          "X110"

The output should be rather onerous with 110 columns displayed, the last being X110, an empty column that can be dropped.

Now we are down to 109 columns and ready to get to work.

1.1 select()

As in the present case of 100+ columns, often our data frame will have more columns than we plan to work with. In such instances it is a good idea to drop all unwanted columns; this will make the data-set more manageable and tax our computing resources less. For example, say I only want the first five columns (Year, Quarter, Month, DayofMonth, DayOfWeek). I could use select to create a data frame with only these columns:

## [1] "Year"       "Quarter"    "Month"      "DayofMonth" "DayOfWeek"

The same result could have been obtained by taking the longer route of listing each column:

## [1] "Year"       "Quarter"    "Month"      "DayofMonth" "DayOfWeek"

What if the columns were not sequentially located? In that case we would need to list each column we want. Say I want Year, FlightDate, UniqueCarrier, and TailNum.

## [1] "Year"          "FlightDate"    "UniqueCarrier" "TailNum"

Could we have used column numbers instead of column names? Absolutely.

## [1] "Year"          "Month"         "DayOfWeek"     "UniqueCarrier"
## [1] "Year"       "Quarter"    "Month"      "DayofMonth" "DayOfWeek"
## [1] "Year"            "FlightDate"      "UniqueCarrier"   "AirlineID"      
## [5] "Carrier"         "OriginAirportID"

select() in other ways

We can also select columns in other ways, by specifying that the column name contain some element. The code below shows you how this is done if I am looking for column names with the phrase “Carrier”, then with “De”, and then with “Num”

## [1] "UniqueCarrier" "Carrier"       "CarrierDelay"
##  [1] "DestAirportID"        "DestAirportSeqID"     "DestCityMarketID"    
##  [4] "Dest"                 "DestCityName"         "DestState"           
##  [7] "DestStateFips"        "DestStateName"        "DestWac"             
## [10] "DepTime"              "DepDelay"             "DepDelayMinutes"     
## [13] "DepDel15"             "DepartureDelayGroups" "DepTimeBlk"
## [1] "TailNum"     "FlightNum"   "Div1TailNum" "Div2TailNum" "Div3TailNum"
## [6] "Div4TailNum" "Div5TailNum"

1.2 filter()

Do you really want all the rows in the data-set or very specific rows that meet some criteria? Say we only want to look at certain months, or just flights on Saturdays and Sundays, or flights in a given month. For example, say we only want flights in January, i.e., Month == 1.

## 
##    1 
## 3757

What about only American Airline (UniqueCarrier code for this airline is AA) flights in January?

##    
##      AA
##   1 387

What about United Airlines flights in January to CMH (the airport code for Columbus, OH) to any destination?

What if I wanted a more complicated filter, say, flights in January or February to CMH or ORD (the airport code for O’Hare in Chicago)?

## 
##   1   2 
## 106 145
## 
##  UA 
## 251
## 
## CMH ORD 
## 132 119

Beautiful, just beautiful. It may not be readily apparent at this point but using %in% c(...) makes applying complex filters quite easy. Note the operators that work with filter()

Operator Meaning Operator Meaning
\(<\) less than \(>\) greater than
\(==\) equal to \(\leq\) less than or equal to
\(\geq\) greater than or equal to != not equal to
%in% is a member of is.na is NA
!is.na is not NA &,!,etc Boolean operators

Now let us say I wanted to arrange the resulting data frame in ascending order of departure delays. How might I do that? Via arrange()

1.3 arrange()

And now in descending order of delays by adding the minus symbol - to the column name.

We could tweak this further, perhaps saying sort by departure delays to CMH, and then to ORD.

So far, we have seen each function in isolation. Now we streamline things a bit so that we only end up with the columns and rows we want to work with, arranged as we want the resulting data-set to be.

Here, the end result is a data frame arranged by Month, then within Month by Destination, and then finally by descending order of flight delays. This is the beauty of dplyr, allowing us to chain together various functions to get what we want. How is this helpful? Well, now you have a data frame that you can analyze.

1.4 summarise()

What if we need to calculate frequencies? For example, how many flights per month are there? What if we want the mean DepDelay or median ArrDelay? These can be easily calculated as shown below.

## # A tibble: 9 x 2
##   Month     n
##   <int> <int>
## 1     1  3757
## 2     2  3413
## 3     3  4101
## 4     4  4123
## 5     5  4098
## 6     6  4138
## 7     7  4295
## 8     8  4279
## 9     9  3789

What about by days of the week AND by month?

## # A tibble: 63 x 3
##    Month DayOfWeek     n
##    <int>     <int> <int>
##  1     1         1   660
##  2     1         2   635
##  3     1         3   516
##  4     1         4   522
##  5     1         5   523
##  6     1         6   361
##  7     1         7   540
##  8     2         1   527
##  9     2         2   506
## 10     2         3   516
## # … with 53 more rows

I want to know the average departure delay and the average arrival delay for all flights, with the averages calculated in two ways – as the mean, and as the median.

## # A tibble: 1 x 4
##   mean_arr_delay mean_dep_delay median_arr_delay median_dep_delay
##            <dbl>          <dbl>            <dbl>            <dbl>
## 1           3.26           9.20               -6               -2

Here, the na.rm = TRUE command is useful because R will not allow you to calculate any mean or median etc. You can see this below, where I have a small data-set called df with 4 values of x, but one of the four is missing and recorded as NA. See what happens when I try to calculate the mean with/without na.rm = TRUE.

##   mean.x
## 1     NA
##   mean.x
## 1      5

1.5 group_by()

These summaries are fine if you want to calculate aggregate quantities of interest but what if you wanted to calculate number of flights per month by airline? Average delays by airline? Now things get interesting because group-by() will open up this new world for us! The first thing I will calculate is the number of flights by airline per month.

## # A tibble: 63 x 3
## # Groups:   Month [9]
##    Month Carrier     n
##    <int> <chr>   <int>
##  1     1 AA        387
##  2     1 DL        549
##  3     1 EV        436
##  4     1 F9        152
##  5     1 OO        123
##  6     1 UA        106
##  7     1 WN       2004
##  8     2 AA        360
##  9     2 DL        511
## 10     2 EV        362
## # … with 53 more rows
## # A tibble: 63 x 3
## # Groups:   Month [9]
##    Month Carrier frequency
##    <int> <chr>       <int>
##  1     1 AA            387
##  2     1 DL            549
##  3     1 EV            436
##  4     1 F9            152
##  5     1 OO            123
##  6     1 UA            106
##  7     1 WN           2004
##  8     2 AA            360
##  9     2 DL            511
## 10     2 EV            362
## # … with 53 more rows

Two ways to do this, first with tally() and the second with summarise(frequency = n()) … both yielding the same result. I would urge you to remember tally() because it is shorter code. For example, now I want a silly table that gives us the number of flights per month per airline per destination.

## # A tibble: 398 x 4
## # Groups:   Month, Carrier [63]
##    Month Carrier Dest      n
##    <int> <chr>   <chr> <int>
##  1     1 AA      CMH     193
##  2     1 AA      DFW     112
##  3     1 AA      LAX      24
##  4     1 AA      PHX      58
##  5     1 DL      ATL     224
##  6     1 DL      CMH     275
##  7     1 DL      DTW       2
##  8     1 DL      LAX      27
##  9     1 DL      MSP      21
## 10     1 EV      CMH     218
## # … with 388 more rows

We could keep complicating the grouping structure. For example, let us add the day of the week to the mix …

## # A tibble: 2,536 x 5
## # Groups:   Month, Carrier, Dest [398]
##    Month Carrier Dest  DayOfWeek     n
##    <int> <chr>   <chr>     <int> <int>
##  1     1 AA      CMH           1    35
##  2     1 AA      CMH           2    23
##  3     1 AA      CMH           3    28
##  4     1 AA      CMH           4    28
##  5     1 AA      CMH           5    28
##  6     1 AA      CMH           6    21
##  7     1 AA      CMH           7    30
##  8     1 AA      DFW           1    20
##  9     1 AA      DFW           2    16
## 10     1 AA      DFW           3    16
## # … with 2,526 more rows

Now say I am really curious about mean departure delays for the preceding grouping structure. That is, what does mean departure delay look like for flights by day of the week, by month, by carrier, and by destination?

## # A tibble: 2,536 x 5
## # Groups:   Month, Carrier, Dest [398]
##    Month Carrier Dest  DayOfWeek mean_dep_delay
##    <int> <chr>   <chr>     <int>          <dbl>
##  1     1 AA      CMH           1         16.3  
##  2     1 AA      CMH           2          0.304
##  3     1 AA      CMH           3          1.14 
##  4     1 AA      CMH           4          3.11 
##  5     1 AA      CMH           5          4.11 
##  6     1 AA      CMH           6         24.9  
##  7     1 AA      CMH           7         21.8  
##  8     1 AA      DFW           1         25.4  
##  9     1 AA      DFW           2         -2.62 
## 10     1 AA      DFW           3         17.6  
## # … with 2,526 more rows

But this is a complicated summary table. What if all I really want to know is what airline has the highest mean departure delays, regardless of month or destination or day of the week?

## # A tibble: 7 x 2
##   Carrier mean_dep_delay
##   <chr>            <dbl>
## 1 EV               15.2 
## 2 F9               10.8 
## 3 WN                9.58
## 4 AA                7.73
## 5 DL                7.10
## 6 OO                6.44
## 7 UA                6.39

EV is Express Jet; F9 is Frontier Airlines; WN is Southwest Airlines; OO is SkyWest Airlines; AA is American Airlines; DL is Delta Airlines; UA is United Airlines. So clearly United Airlines had the lowest average departure delays. Would this still be true if we repeated the calculation by Month?

## # A tibble: 63 x 3
## # Groups:   Carrier [7]
##    Carrier Month mean_dep_delay
##    <chr>   <int>          <dbl>
##  1 OO          8         -0.873
##  2 OO          2         -0.570
##  3 OO          9         -0.179
##  4 UA          9          0.699
##  5 DL          8          1.85 
##  6 DL          2          2.24 
##  7 AA          2          2.30 
##  8 F9          9          2.51 
##  9 AA          9          2.62 
## 10 DL          3          2.98 
## # … with 53 more rows

All righty then! Looks like three of the lowest mean departure delays were for SkyWest. Do not let the negative numbers throw you for a loop; a negative value implies the flight left earlier than scheduled.

So far so good. But now I am curious about what percent of flights operated by AA, DL, UA, and WN were delayed. How could I calculate this?

  1. I need to use filter() to restrict the data-set to just these four airlines.
  2. Then I need to generate a new column that identifies whether a flight was delayed or not (late).
  3. Now we can calculate the total number of flights (nflights) and the total number of flights that were delayed (nlate).
  4. If I then calculate \(\left( \dfrac{nlate}{nflights} \right)\times 100\) we will end up with the percent of flights that were delayed.
## # A tibble: 29,328 x 6
## # Groups:   Carrier, late [12]
##    Carrier DepDelay late  nflights nlate pct_late
##    <chr>      <dbl> <chr>    <int> <int>    <dbl>
##  1 AA            -9 No        3891  2698     69.3
##  2 AA            24 Yes       3891  1162     29.9
##  3 AA            -6 No        3891  2698     69.3
##  4 AA            -5 No        3891  2698     69.3
##  5 AA            -7 No        3891  2698     69.3
##  6 AA            22 Yes       3891  1162     29.9
##  7 AA            -4 No        3891  2698     69.3
##  8 AA            -4 No        3891  2698     69.3
##  9 AA            42 Yes       3891  1162     29.9
## 10 AA            -6 No        3891  2698     69.3
## # … with 29,318 more rows

There is a whole lot going on here so let us break it down.

  • filter(Carrier %in% c("AA", "DL", "UA", "WN")) is keeping specified airlines’ data while dropping the rest
  • mutate(late = case_when(...) is creating a new column called late and storing a value of “Yes” if DepDelay > 0 and “No” if DepDelay <= 0
  • group_by(Carrier) is grouping by Carrier and then counting how many flights there were per Carrier and storing this sum in a new column called nflights
  • group_by(Carrier, late) is grouping the data by Carrier and late
  • mutate(nlate = n(), pct_late = (nlate / nflights) * 100) is then creating two new columns, nlate, the number of flights per late values of “Yes” and “No”, respectively, and then pct_late

Now, we only want the flights that were late so let us apply select() to keep just a few columns and then we use filter() to keep only rows corresponding to late = "Yes". This will still leave us with duplicate rows but we can drop these duplicate rows via a new command, distinct()

## # A tibble: 4 x 3
## # Groups:   Carrier, late [4]
##   late  Carrier pct_late
##   <chr> <chr>      <dbl>
## 1 Yes   UA          24.0
## 2 Yes   DL          27.7
## 3 Yes   AA          29.9
## 4 Yes   WN          41.1

So! 24% of UA flights were late, the lowest in this group.

What if we wanted to do this for all airlines, and we want it by Month?

## # A tibble: 36 x 4
## # Groups:   Carrier, Month, late [36]
##    late  Carrier Month pct_late
##    <chr> <chr>   <int>    <dbl>
##  1 Yes   UA          9     17.9
##  2 Yes   UA          2     18.6
##  3 Yes   DL          9     20.2
##  4 Yes   UA          5     20.2
##  5 Yes   UA          4     20.4
##  6 Yes   AA          9     21.4
##  7 Yes   DL          2     21.5
##  8 Yes   DL          8     22.5
##  9 Yes   AA          2     23.1
## 10 Yes   UA          3     23.3
## # … with 26 more rows

Before we move on, I want to point out something about case_when(). Specifically, we used it to create a new column called late from numeric values found in DepDelay. But what if we wanted to create a new column from a column that had categorical variables in it, like Dest or Carrier? Easy.

## # A tibble: 10,864 x 2
##    Carrier carrier_name     
##    <chr>   <chr>            
##  1 AA      American Airlines
##  2 AA      American Airlines
##  3 AA      American Airlines
##  4 AA      American Airlines
##  5 AA      American Airlines
##  6 AA      American Airlines
##  7 AA      American Airlines
##  8 AA      American Airlines
##  9 AA      American Airlines
## 10 AA      American Airlines
## # … with 10,854 more rows

Second, case_when() includes an option that cuts down on our work. In particular, say I want to create a new column and label its values as “Weekend” if the DayOfWeek is Saturday or Sunday and “Weekday” if DayOfWeek is any other day. In doing this, it would serve us well to remember that the week begins on Sunday so DayOfWeek == 1 is Sunday, not Monday.

## # A tibble: 7 x 2
##   DayOfWeek weekend
##       <int> <chr>  
## 1         7 Yes    
## 2         1 Yes    
## 3         2 No     
## 4         3 No     
## 5         4 No     
## 6         5 No     
## 7         6 No

Notice how TRUE swept up all other values of DayOfWeek and coded them as “No.”

One final showcasing of case_when(). In Module 01 we looked at the hsb2 data and created some factors for columns such as female, ses, schtyp, and so on. Well, let us see how the same thing could be done with case_when().

1.6 Some other dplyr() commands

We have seen count() in action but let us see it again, in a slightly different light. In particular, say I want to know how many unique destinations are there connected by air from Columbus.

1.6.1 count()

## # A tibble: 26 x 2
##    Dest      n
##    <chr> <int>
##  1 ATL    2884
##  2 MDW    1511
##  3 MCO    1148
##  4 DFW    1122
##  5 DEN     971
##  6 BWI     948
##  7 LAS     815
##  8 PHX     815
##  9 ORD     803
## 10 EWR     736
## # … with 16 more rows

Note: There is no need for group_by() here. And sort = TRUE arranges the result in descending order of the frequency (n). Here is another code example, this time adding Carrier to the mix.

## # A tibble: 45 x 3
##    Carrier Dest      n
##    <chr>   <chr> <int>
##  1 DL      ATL    2141
##  2 WN      MDW    1511
##  3 AA      DFW    1122
##  4 WN      BWI     948
##  5 WN      MCO     929
##  6 WN      ATL     743
##  7 EV      EWR     736
##  8 WN      TPA     595
##  9 UA      ORD     577
## 10 WN      LAS     542
## # … with 35 more rows

How does this help us? Well, now we know that if we were flying to Atlanta, Delta would have the most flights, but if we were flying to the Chicago area then Southwest should be our pick.

1.6.2 n_distinct()

Another useful command is n_distinct(), useful in the sense of allowing us to calculate the the number of distinct values of any column. For example, say I want to know how many unique aircraft (not airlines) are there in this data-set.

## # A tibble: 1 x 1
##   `n_distinct(TailNum)`
##                   <int>
## 1                  2248

1.6.3 top_n()

If you want to see the top ‘n’ number of observations, for example the 4 airlines with the most aircraft, you can lean on top_n(), as shown below.

## # A tibble: 4 x 2
##   Carrier num.flights
##   <chr>         <int>
## 1 WN              751
## 2 DL              539
## 3 UA              289
## 4 OO              222

I am also curious about which aircraft has flown the most, and then maybe 9 other aircraft that follow in descending order.

## # A tibble: 4 x 2
##   TailNum     n
##   <chr>   <int>
## 1 N396SW     74
## 2 N601WN     66
## 3 N646SW     64
## 4 N635SW     62

1.6.4 join()

You will, from time to time, need to merge multiple data-sets together. For example, say I have the following data-sets I have created for demonstration purposes.

## # A tibble: 4 x 2
##   Name    Score
##   <chr>   <dbl>
## 1 Tim         5
## 2 Tammy       8
## 3 Bubbles     9
## 4 Panda      10
## # A tibble: 3 x 2
##   Name      Age
##   <chr>   <dbl>
## 1 Tim        25
## 2 Tammy      78
## 3 Bubbles    19
## # A tibble: 3 x 2
##   Name  Education
##   <chr> <chr>    
## 1 Tim   BA       
## 2 Tammy PhD      
## 3 Panda JD

Notice that Panda is absent from df2 and Bubbles is absent from df3. So if we wanted to build ONE data-set with all data for Tim, Tammy, Bubbles, and Panda, some of the information would be missing for some of these folks. But how could we construct ONE data-set? Via one of a few join() commands.

1.6.4.1 full_join()

Let us start with a simple full_join, where we link up every individual in df1 or df2 or df3 regardless of whether they are seen in both data-sets.

## # A tibble: 4 x 4
##   Name    Score   Age Education
##   <chr>   <dbl> <dbl> <chr>    
## 1 Tim         5    25 BA       
## 2 Tammy       8    78 PhD      
## 3 Bubbles     9    19 <NA>     
## 4 Panda      10    NA JD

Pay attention to two things: (i) Name connects the records in each data-set, and so it must be spelled exactly the same for a link to be made, and (ii) the full_join() links up all individuals regardless of whether they are missing any information in any of the data-sets. This is usually how most folks will link up multiple files unless they only want records found in a master file. For example, say I want to link up df2 and df3 but only such that the final result will include all records found in BOTH df2 and df3, with df2 serving as the master data-set. Eh?

## # A tibble: 3 x 3
##   Name      Age Education
##   <chr>   <dbl> <chr>    
## 1 Tim        25 BA       
## 2 Tammy      78 PhD      
## 3 Bubbles    19 <NA>

Notice that Panda is dropped because it is not found in df2.

Maybe you want df3 to be the master file, in which case you would see a different result (with Bubbles not seen in the result since Bubbles is found in df2 but not in df3):

## # A tibble: 3 x 3
##   Name  Education   Age
##   <chr> <chr>     <dbl>
## 1 Tim   BA           25
## 2 Tammy PhD          78
## 3 Panda JD           NA

Rarely, but definitely not “never,” you may want to see the records that are not found in both. Here, anti_join() comes in handy, thus:

## # A tibble: 1 x 2
##   Name      Age
##   <chr>   <dbl>
## 1 Bubbles    19
## # A tibble: 1 x 2
##   Name  Education
##   <chr> <chr>    
## 1 Panda JD

1.7 Two other useful commands

1.7.1 {santoku}

Every now and then you may want to or need to create a grouped version of some numeric variable. For example, we have DepDelay for all flights but want to group this into quartiles. How can we do that? In many ways but the easiest might be to use a specific library – {santoku}. Say, for example, I want to create 4 groups of dep_delay, and I want these such that we are grouping DepDelay into the bottom 25%, next 25%, the next 25%, and finally the highest 25%. Wait, these are the quartiles! Fair enough, but how can I do this?

## # A tibble: 5 x 2
##   depdelay_groups     n
##   <fct>           <int>
## 1 [0%, 25%)        6887
## 2 [25%, 50%)       9267
## 3 [50%, 75%)      10143
## 4 [75%, 100%]      9225
## 5 <NA>              471

What if we wanted to slice up DepDelay in specific intervals, first at 0, then at 15, then at 30, and then at 45?

## # A tibble: 6 x 2
##   depdelay_groups     n
##   <fct>           <int>
## 1 [-27, 0)        21108
## 2 [0, 15)          7883
## 3 [15, 30)         2567
## 4 [30, 45]         1302
## 5 (45, 1323]       2662
## 6 <NA>              471

We could also create quintiles (5 groups) or deciles (10 groups) as shown below:

## # A tibble: 5 x 2
##   depdelay_groups     n
##   <fct>           <int>
## 1 [0%, 20%)        6887
## 2 [20%, 40%)       6342
## 3 [40%, 60%)       7879
## 4 [60%, 80%]       7362
## 5 (80%, 100%]      7052
## # A tibble: 10 x 2
##    depdelay_groups     n
##    <fct>           <int>
##  1 [0%, 10%)        3006
##  2 [10%, 20%)       3881
##  3 [20%, 30%)       3344
##  4 [30%, 40%)       2998
##  5 [40%, 50%)       2925
##  6 [50%, 60%)       4954
##  7 [60%, 70%)       3262
##  8 [70%, 80%)       3773
##  9 [80%, 90%]       3904
## 10 (90%, 100%]      3475

1.7.2 ordered()

more often than we would like to see happen, we end up with categorical variables that should follow a certain order but do not. For example, say you have survey data where people were asked to respond whether they Agree, are Neutral, or Disagree with some statement. Let us also assume that the frequencies are as follows:

## # A tibble: 3 x 2
##   response     n
##   <chr>    <int>
## 1 Agree       25
## 2 Disagree    45
## 3 Neutral     30

Notice how the responses are out of order, with Agree followed by Disagree, then Neutral, since R defaults to alphabetic ordering for anything that is a categorical variable. One way to ensure the correct ordering of categorical variables is via ordered, as shown below.

## # A tibble: 3 x 2
##   ordered.response     n
##   <ord>            <int>
## 1 Agree               25
## 2 Neutral             30
## 3 Disagree            45

1.8 Concluding thoughts

We have a covered a lot of ground here but every inch has been critical space. Google any question we have tackled and you will see how many R-users ask the same questions … how do I calculate mean for groups in R? What you have seen is the heart of the dplyr() package. We saw grouped operations, we saw the use of summarise, mutate, case_when, distinct, filter, arrange, select, count, and tally. I will let you in on a secret; while these are core functions, there are others you could experiment with. Look up the cheat-sheet here.

Practice what we have done, with the {nycflights13} data-set perhaps to get something familiar yet sufficiently different to test your fundamentals. Maybe pick a non-travel data-set altogether, perhaps one of the tidytuesday data-sets. What is that you ask? Discover it for yourself here. Bon voyage! Don’t go too far because we will be working with two new packages next week – {tidyr} and {lubridate}.

2 Practice Exercises

2.1 Exercise 1

Why are our best and most experienced employees leaving prematurely?

The data available here includes information on several current and former employees of an anonymous organization.** Fields in the data-set include:

  • satisfaction_level = Level of satisfaction (numeric; 0-1)
  • last_evaluation = Evaluation score of the employee (numeric; 0-1)
  • number_project = Number of projects completed while at work (numeric)
  • average_monthly_hours = Average monthly hours spent at the workplace (numeric)
  • time_spend_company = Number of years spent in the company (numeric)
  • Work_accident = Whether the employee had a workplace accident (categorical; 1 = yes or 0 = no)
  • left = Whether the employee left the workplace or not (categorical; 1 = left or 0 = stayed)
  • promotion_last_5years = Whether the employee was promoted in the last five years (categorical; 1 = yes or 0 = no)
  • sales = Department in which they work (categorical)
  • salary = Relative level of salary (categorical; low, med, and high)
  1. Read in the csv-format data-set, naming it hrdata and save it in RData format as hrdata.RData
  1. Create new variables that add labels to Work_accident, left, promotion_last_5years, and create a new variable that orders salary from low to high, and add these to hrdata.
  1. Convert satisfaction_level from a 0-1 scale to a 0-100 scale, making sure to create a new variable of course.
  1. Now retain only employees who left the company, and had not been promoted in the last five years. Save this result as hr01
  1. In this hr01 data-set, how many employees do you have per sales department? What sales department has the most number of employees?
## # A tibble: 10 x 2
##    sales           n
##    <chr>       <int>
##  1 sales        1007
##  2 technical     694
##  3 support       552
##  4 IT            270
##  5 hr            215
##  6 accounting    204
##  7 marketing     203
##  8 product_mng   198
##  9 RandD         121
## 10 management     88
  1. By sales department, calculate mean and standard deviation of (i) satisfaction_level, and (ii) last_evaluation.
## # A tibble: 10 x 5
##    sales       mean.satisfaction sd.satisfaction mean.evaluation sd.evaluation
##    <chr>                   <dbl>           <dbl>           <dbl>         <dbl>
##  1 accounting              0.403           0.257           0.695         0.199
##  2 hr                      0.433           0.243           0.680         0.197
##  3 IT                      0.411           0.273           0.733         0.193
##  4 management              0.410           0.266           0.732         0.204
##  5 marketing               0.453           0.249           0.692         0.200
##  6 product_mng             0.482           0.264           0.727         0.203
##  7 RandD                   0.433           0.282           0.745         0.194
##  8 sales                   0.447           0.259           0.712         0.198
##  9 support                 0.451           0.264           0.728         0.192
## 10 technical               0.434           0.276           0.734         0.198
  1. What department has the lowest mean satisfaction? How much difference is there in mean satisfaction between departments?
## # A tibble: 10 x 2
##    sales       mean.satisfaction
##    <chr>                   <dbl>
##  1 accounting              0.403
##  2 management              0.410
##  3 IT                      0.411
##  4 RandD                   0.433
##  5 hr                      0.433
##  6 technical               0.434
##  7 sales                   0.447
##  8 support                 0.451
##  9 marketing               0.453
## 10 product_mng             0.482

Accounting has the lowest mean satisfaction level. All departments have mean satisfaction in the 0.403 to 0.482 range, so some difference but a huge one.

  1. Create a new variable that groups average monthly hours into 4 groups. You can let the group cut-points be chosen automatically with chop_evenly(). Then show the frequencies of each group.
## # A tibble: 4 x 2
##   grouped_hours     n
##   <fct>         <int>
## 1 [126, 172)     1594
## 2 [172, 218)       88
## 3 [218, 264)     1037
## 4 [264, 310]      833

2.2 Exercise 2

Thanks to the frenetic work of many individuals, the global spread of the Novel Coronavirus (COVID-19) has been tracked and the data made available for analysis. Yanchang Zhao is one such individual and for this exercise we will use a spcific version of his data that I have named cvdata.RData and made available via Slack. Make sure to upload that data-set to your RStudio Cloud data folder, and then to read it in via the load() command. We can then answer a few questions. Note the contents:

  • country = name of the country
  • date = date of indidents as recorded
  • confirmed = cumulative count of the number of people who tested positive
  • deaths = cumulative count of the number of people lost to Covid-19
  • deaths = cumulative count of the number of people recovered
  1. Filter the data-set so that we have only one row per country, the data from March 10, 2020 and call it cv0310.
  1. How many countries have lost at least one person to this tragedy? “Others” should not show up as one of the countries.
## # A tibble: 1 x 2
##   ncountries     n
##        <int> <int>
## 1         24    24
  1. What 10 countries have had the most number of confirmed cases? “Others” should not show up as one of the countries. Also ensure the result is organized in descending order of the number of confirmed cases.
## # A tibble: 10 x 2
##    country                    confirmed
##    <fct>                          <int>
##  1 Mainland China                 80757
##  2 Italy                          10149
##  3 Iran (Islamic Republic of)      8042
##  4 Republic of Korea               7513
##  5 France                          1784
##  6 Spain                           1695
##  7 US                              1670
##  8 Germany                         1457
##  9 Japan                            581
## 10 Switzerland                      491
  1. Calculate the fatality_rate, defined for our purposes as the percent of deaths. excluding “Others”, and only keeping countried that have had at least 10 confirmed cases, arrange the result to show the top-10 countries in descending order of fatality_rate.
## # A tibble: 10 x 2
##    country                    fatality_rate
##    <fct>                              <dbl>
##  1 Iraq                                9.86
##  2 Italy                               6.22
##  3 Argentina                           5.88
##  4 San Marino                          3.92
##  5 Mainland China                      3.88
##  6 Iran (Islamic Republic of)          3.62
##  7 US                                  3.35
##  8 Philippines                         3.03
##  9 Australia                           2.80
## 10 Hong Kong SAR                       2.5
  1. Say we only want to focus on the Baltic countries (Estonia, Latvia, and Lithuania) as a unified group and compare this group to the ASEAN nations (Brunei, Cambodia, Indonesia, Laos, Malaysia, Myanmar, Philippines, Singapore, Thailand, and Vietnam). Use cv0310 to complete the followng tasks:

  2. Create a new variable called region that only takes on two values – “Baltic” if the country is a Baltic country and “Asean” if the country is an ASEAN country.
  1. Use this variable to calculate the total number of confirmed cases in each region.
## # A tibble: 3 x 2
##   region total.confirmed
##   <chr>            <int>
## 1 ASEAN              405
## 2 Baltic              21
## 3 <NA>            118877
LS0tCnRpdGxlOiAiTVBBIDU4MzAgLSBNb2R1bGUgMDIiCnN1YnRpdGxlOiAiU3ByaW5nIDIwMjAiCmF1dGhvcjogIlByb2Zlc3NvciBSdWhpbCIKZGF0ZTogIlVwZGF0ZWQgb24gYHIgU3lzLkRhdGUoKWAiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDogCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMKICAgIGZpZ19jYXB0aW9uOiB5ZXMKICAgIGhpZ2hsaWdodDogemVuYnVybgogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGNvbnNvbGUKLS0tCgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoKYm9keXsgLyogTm9ybWFsICAqLwovKiAgICBmb250LWZhbWlseTogTGF0bywgc2Fucy1zZXJpZjsgIAogICAgICBmb250LWZhbWlseTogTXVrdGEsIHNhbnMtc2VyaWY7IAogICAgICBmb250LWZhbWlseTogJ051bml0byBTYW5zJywgc2Fucy1zZXJpZjsKICAgICAgZm9udC1mYW1pbHk6IEthcmxhLCBzYW5zLXNlcmlmOyAgKi8KICAgICAgZm9udC1mYW1pbHk6ICdNZXJyaXdlYXRoZXIgU2FucycsIHNhbnMtc2VyaWY7IAogICAgICBmb250LXNpemU6IDE4cHg7CiAgfQoKaDEudGl0bGUgewogIGZvbnQtc2l6ZTogMzhweDsKICBjb2xvcjogRGFya1JlZDsKfQoKaDEgeyAvKiBIZWFkZXIgMSAqLwogIGZvbnQtc2l6ZTogMjhweDsKICBjb2xvcjogRGFya0JsdWU7Cn0KCmgyIHsgLyogSGVhZGVyIDIgKi8KICAgIGZvbnQtc2l6ZTogMjJweDsKICBjb2xvcjogRGFya0JsdWU7Cn0KCmgzIHsgLyogSGVhZGVyIDMgKi8KICBmb250LXNpemU6IDE4cHg7CiAgY29sb3I6IERhcmtCbHVlOwp9Cgpjb2RlLnJ7IC8qIENvZGUgYmxvY2sgKi8KICAgIGZvbnQtZmFtaWx5OiBNdWt0YSwgc2Fucy1zZXJpZjsgCiAgICBmb250LXdlaWdodDogNjAwOyAgCiAgICBmb250LXNpemU6IDE2cHg7Cn0KCi8qIHByZSB7IC8qIENvZGUgYmxvY2sgLSBkZXRlcm1pbmVzIGNvZGUgc3BhY2luZyBiZXR3ZWVuIGxpbmVzICovCiAgICBmb250LXNpemU6IDE2cHg7Cn0gKi8KPC9zdHlsZT4KCgpgYGB7ciBrbGlwcHksIGVjaG8gPSBGQUxTRSwgaW5jbHVkZSA9IFRSVUV9CmtsaXBweTo6a2xpcHB5KHRvb2x0aXBfbWVzc2FnZSA9ICdDbGljayB0byBjb3B5JywgdG9vbHRpcF9zdWNjZXNzID0gJ0RvbmUnLCBjb2xvciA9ICdjb3JuZmxvd2VyYmx1ZScsIHBvc2l0aW9uID0gYygndG9wJywgJ3JpZ2h0JykpCmBgYAoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGRwaSA9IDMwMCwgY2FjaGUgPSBUUlVFLCBmaWcuYWxpZ24gPSAiY2VudGVyIiwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA4LCBvdXQud2lkdGggPSAiMTAwJSIsIGhpZ2hsaWdodCA9IFRSVUUpIApgYGAKCmBgYHtyIHNldHVwbiwgaW5jbHVkZT1GQUxTRX0KIyBjYW1vID0ga25pdHI6OmtuaXRfdGhlbWUkZ2V0KCJjYW1vIikKIyBrbml0cjo6a25pdF90aGVtZSRzZXQoY2FtbykKYGBgCgpPdXIgZ29hbCBpcyB0byB1bmRlcnN0YW5kIGhvdyBzb21lIHZlcnkgcG93ZXJmdWwgcGFja2FnZXMgd29yayB0aGVpciBtYWdpYy4gT2Ygc29tZXdoYXQgcmVjZW50IHZpbnRhZ2UsIGB7ZHBseXJ9YCwgYHt0aWR5cn1gIGFuZCBge2x1YnJpZGF0ZX1gIGhhdmUgcXVpY2tseSBnYWluZWQgYSBmYW4gZm9sbG93aW5nIGJlY2F1c2UgdGhleSBhbGxvdyB5b3UgdG8gY2xlYW4gYW5kIG9yZ2FuaXplIHlvdXIgZGF0YSBhbmQgdGhlbiBjYWxjdWxhdGUgcXVhbnRpdGllcyBvZiBpbnRlcmVzdCB3aXRoIHN1cnByaXNpbmcgZWFzZS4gSW4gdGhpcyBtb2R1bGUgd2Ugd2lsbCBzdGFydCB0byBzZWUgc29tZSBvZiB0aGVzZSBwYWNrYWdlcycgY29yZSBmdW5jdGlvbmFsaXRpZXMgYW5kIHdyYXAgdXAgdGhpcyBwYXJ0aWN1bGFyIGxlZyBvZiBvdXIgbGVhcm5pbmcgam91cm5leSBpbiB0aGUgbmV4dCBtb2R1bGUuICAKCiMgYHtkcGx5cn1gIApUaGVyZSBpcyBhIGNvbW1vbiBxdW90ZSB0aGF0IGlzIHRvc3NlZCBhYm91dCBhIGdvb2QgYml0IGluIHRoZSBoYWxsd2F5cyBvZiBkYXRhIHNjaWVuY2UsIHRoYXQsIGFuZCBJIGFtIHBhcmFwaHJhc2luZyBoZXJlLCBhIGRhdGEgc2NpZW50aXN0cyBzcGVuZHMgYWJvdXQgODAlIG9mIHRoZWlyIHRpbWUgZ2F0aGVyaW5nLCBjbGVhbmluZywgYW5kIG9yZ2FuaXppbmcgdGhlaXIgZGF0YSBhbmQgc3BlbmRzIG9ubHkgYWJvdXQgMjAlIG9mIHRoZWlyIHRpbWUgb24gdGhlIGFuYWx5c2lzIHBlciBzZS4gVGhpcyBtYXkgb3IgbWF5IG5vdCBiZSB0cnVlLCBlc3BlY2lhbGx5IGluIHRoZSBpbml0aWFsIHN0YWdlcyBvZiBhIG5ldyBwcm9qZWN0IGJ1dCB5ZXMsIHdlIGRvIHNwZW5kIGFuIGF3ZnVsbHkgbGFyZ2UgYW1vdW50IG9mIG91ciB0aW1lIGdldHRpbmcgdGhlIGRhdGEgcmVhZHkgZm9yIHZpc3VhbGl6YXRpb25zIGFuZCBvdGhlciBhbmFseXNlcy4gWW91IGRvIHRoaXMgd29yayBsb25nIGVub3VnaCBhbmQgeW91IGNvbWUgdG8gcmVhbGl6ZSB0aGF0IGFueXRoaW5nIHlvdSBjb3VsZCBkbyB0byBzcGVlZCB1cCB0aGUgY2xlYW5pbmcgcGhhc2Ugd291bGQgYmUgdGltZSBhbmQgbW9uZXkgc2F2ZWQuIEFuZCB5ZXQsIGRhdGEgY2xlYW5pbmcgc2tpbGxzIGFyZSBpbiBzaG9ydCBzdXBwbHkuIE5vIG1vcmUsIEkgc2F5LCBiZWNhdXNlIHBhY2thZ2VzIGxpa2UgYHtkcGx5cn1gIGFuZCBge2RhdGEudGFibGV9YCBoYXZlIHNpbXBsaWZpZWQgd2hhdCB3ZXJlIG9uY2UgbmlnaHRtYXJlIHRhc2tzLiAKCmBOaWdodG1hcmVgIGlzIG5vdCBhIHdvcmQgdG8gYmUgdG9zc2VkIGFyb3VuZCBsaWdodGx5IGFuZCBzbyBsZXQgdXMgc2V0dXAgYSBzZWVtaW5nbHkgbGFyZ2UgZGF0YS1zZXQgd2l0aCAxMDArIGNvbHVtbnMsIGFuZCB0b25zIG9mIGluZm9ybWF0aW9uIGhpZGRlbiBpbiBpdC4gT25jZSB3ZSBzZXR1cCB0aGlzIEN5Y2xvcHMsIHNwZWxsIG91dCBhIGZldyBxdWVzdGlvbnMgd2Ugd291bGQgbGlrZSB0byBhbnN3ZXIsIHdlIG1pZ2h0IGJldHRlciBhcHByZWNpYXRlIGhvdyBge2RwbHlyfWAgY29tZXMgdG8gb3VyIGFpZC4gSW4gcGFydGljdWxhciwgd2UgbWlnaHQgY29tZSB0byB1bmRlcnN0YW5kIGhvdyBge2RwbHlyfWAgdXNlcyBzZXZlbiBjb3JlIHZlcmJzOiAgCgp8ICoqV2hhdCB5b3Ugd2FudCB0byBkbyAuLi4qKiB8ICoqYHtkcGx5cn1gIGZ1bmN0aW9uKiogfAp8IDotLS0tLSAgfCA6LS0tLS0gfAp8IHlvdSBuZWVkIHRvIHNlbGVjdCBjb2x1bW5zIHRvIHdvcmsgd2l0aD8gfCBgc2VsZWN0KClgfAp8IHlvdSBuZWVkIHRvIHVzZSBhIHN1YnNldCBvZiB0aGUgZGF0YSBiYXNlZCBvbiBzb21lIGNyaXRlcmlvbj8gfCBgZmlsdGVyKClgfAp8IHlvdSBuZWVkIHRvIGFycmFuZ2UgdGhlIGRhdGEgaW4gYXNjZW5kaW5nL2Rlc2NlbmRpbmcgb3JkZXIgb2YgdmFyaWFibGUocyk/IHwgYGFycmFuZ2UoKWB8CnwgeW91IHdhbnQgdGhlIHJlc3VsdHMgb2YgeW91ciBjYWxjdWxhdGlvbnMgdG8gYmUgYSBzdGFuZGFsb25lIGRhdGEgZnJhbWU/IHwgYHN1bW1hcmlzZSgpYHwKfCB5b3Ugd2FudCB0byBhZGQgeW91ciBjYWxjdWxhdGVkIHZhbHVlKHMpIHRvIHRoZSBleGlzdGluZyBkYXRhIGZyYW1lPyB8IGBtdXRhdGUoKWB8CnwgeW91IHdhbnQgdG8gYWRkIHlvdXIgY2FsY3VsYXRlZCB2YWx1ZShzKSB0byB0aGUgZXhpc3RpbmcgZGF0YSBmcmFtZSBidXQgYWxzbyAgZHJvcCBhbGwgIHZhcmlhYmxlcy9jb2x1bW5zIG5vdCAgYmVpbmcgdXNlZCBpbiB0aGUgY2FsY3VsYXRpb24/IHwgYHRyYW5zbXV0ZSgpYHwKfCB5b3UgbmVlZCB0byBjYWxjdWxhdGUgYXZlcmFnZXMsIGZyZXF1ZW5jaWVzLCBldGMgYnkgZ3JvdXBzPyB8IGBncm91cF9ieSgpYHwKCkhlcmUgY29tZXMgY3ljbG9wcyAtLSBhbGwgZmxpZ2h0cyBvcmlnaW5hdGluZyBhbmQgZGVwYXJ0aW5nIGZyb20gQ29sdW1idXMgKE9oaW8pIEphbnVhcnkgdGhyb3VnaCBTZXB0ZW1iZXIgb2YgMjAxNy4gTGV0ICB1cyBsb2FkIHRoZSBkYXRhLCBhbmQgdGhlIGB7dGlkeXZlcnNlfWAgYW5kIGB7aGVyZX1gIHBhY2thZ2VzLiAKCmBgYHtyIGNtaGZsaWdodHN9CmxpYnJhcnkodGlkeXZlcnNlKSAKbGlicmFyeShoZXJlKQoKbG9hZCgKICBoZXJlKCJkYXRhIiwgImNtaGZsaWdodHNfMDEwOTIwMTcuUkRhdGEiKQogICkKCm5hbWVzKGNtaGZsaWdodHMpICMgd2lsbCBzaG93IHlvdSB0aGUgY29sdW1uIG5hbWVzCmBgYAoKVGhlIG91dHB1dCBzaG91bGQgYmUgcmF0aGVyIG9uZXJvdXMgd2l0aCAxMTAgY29sdW1ucyBkaXNwbGF5ZWQsIHRoZSBsYXN0IGJlaW5nIGBYMTEwYCwgYW4gZW1wdHkgY29sdW1uIHRoYXQgY2FuIGJlIGRyb3BwZWQuIAoKYGBge3IgZHJvcHgxMTB9CmNtaGZsaWdodHMkWDExMCA8LSBOVUxMICNUaGlzIHdpbGwgZGVsZXRlIHRoZSBjb2x1bW4gbmFtZWQgWDExMCAKYGBgCgpOb3cgd2UgYXJlIGRvd24gdG8gMTA5IGNvbHVtbnMgYW5kIHJlYWR5IHRvIGdldCB0byB3b3JrLiAKCiMjIHNlbGVjdCgpIApBcyBpbiB0aGUgcHJlc2VudCBjYXNlIG9mIDEwMCsgY29sdW1ucywgb2Z0ZW4gb3VyIGRhdGEgZnJhbWUgd2lsbCBoYXZlIG1vcmUgY29sdW1ucyB0aGFuIHdlIHBsYW4gdG8gd29yayB3aXRoLiBJbiBzdWNoIGluc3RhbmNlcyBpdCBpcyBhIGdvb2QgaWRlYSB0byBkcm9wIGFsbCB1bndhbnRlZCBjb2x1bW5zOyB0aGlzIHdpbGwgbWFrZSB0aGUgZGF0YS1zZXQgbW9yZSBtYW5hZ2VhYmxlIGFuZCB0YXggb3VyIGNvbXB1dGluZyByZXNvdXJjZXMgbGVzcy4gRm9yIGV4YW1wbGUsIHNheSBJIG9ubHkgd2FudCB0aGUgZmlyc3QgZml2ZSBjb2x1bW5zIChZZWFyLCBRdWFydGVyLCBNb250aCwgRGF5b2ZNb250aCwgRGF5T2ZXZWVrKS4gSSBjb3VsZCB1c2UgYHNlbGVjdGAgdG8gY3JlYXRlIGEgZGF0YSBmcmFtZSB3aXRoIG9ubHkgdGhlc2UgY29sdW1uczoKCmBgYHtyIHNlbGVjdDAxYX0KY21oZmxpZ2h0cyAlPiUgCiAgc2VsZWN0KFllYXI6RGF5T2ZXZWVrKSAtPiBteS5kZiAjIFRoZSA6IGlzIHRoZSBicmlkZ2UgYmV0d2VlbiBjb25zZWN1dGl2ZSBjb2x1bW5zCgpuYW1lcyhteS5kZikKYGBgCgpUaGUgc2FtZSByZXN1bHQgY291bGQgaGF2ZSBiZWVuIG9idGFpbmVkIGJ5IHRha2luZyB0aGUgbG9uZ2VyIHJvdXRlIG9mIGxpc3RpbmcgZWFjaCBjb2x1bW46IAoKYGBge3Igc2VsZWN0MDFifQpjbWhmbGlnaHRzICU+JQogIHNlbGVjdChZZWFyLCBRdWFydGVyLCBNb250aCwgRGF5b2ZNb250aCwgRGF5T2ZXZWVrKSAtPiBteS5kZiAKCm5hbWVzKG15LmRmKQpgYGAKCldoYXQgaWYgdGhlIGNvbHVtbnMgd2VyZSBub3Qgc2VxdWVudGlhbGx5IGxvY2F0ZWQ/IEluIHRoYXQgY2FzZSB3ZSB3b3VsZCBuZWVkIHRvIGxpc3QgZWFjaCBjb2x1bW4gd2Ugd2FudC4gU2F5IEkgd2FudCBZZWFyLCBGbGlnaHREYXRlLCBVbmlxdWVDYXJyaWVyLCBhbmQgVGFpbE51bS4gCgpgYGB7ciBzZWxlY3QwMn0KY21oZmxpZ2h0cyAlPiUgCiAgc2VsZWN0KFllYXIsIEZsaWdodERhdGU6VW5pcXVlQ2FycmllciwgVGFpbE51bSkgLT4gbXkuZGYgCgpuYW1lcyhteS5kZikKYGBgCgpDb3VsZCB3ZSBoYXZlIHVzZWQgY29sdW1uIG51bWJlcnMgaW5zdGVhZCBvZiBjb2x1bW4gbmFtZXM/IEFic29sdXRlbHkuIAoKYGBge3Igc2VsZWN0MDNhfQpjbWhmbGlnaHRzICU+JSAKICBzZWxlY3QoYygxLCAzLCA1LCA3KSkgLT4gbXkuZGYgCgpuYW1lcyhteS5kZikKYGBgCgpgYGB7ciBzZWxlY3QwM2J9CmNtaGZsaWdodHMgJT4lIAogIHNlbGVjdChjKDE6NSkpIC0+IG15LmRmIAoKbmFtZXMobXkuZGYpCmBgYAoKYGBge3Igc2VsZWN0MDNjfQpjbWhmbGlnaHRzICU+JSAKICBzZWxlY3QoYygxLCA2OjksIDEyKSkgLT4gbXkuZGYKCm5hbWVzKG15LmRmKQpgYGAKCiMjIHNlbGVjdCgpIGluIG90aGVyIHdheXMgey19CldlIGNhbiBhbHNvIHNlbGVjdCBjb2x1bW5zIGluIG90aGVyIHdheXMsIGJ5IHNwZWNpZnlpbmcgdGhhdCAgdGhlIGNvbHVtbiBuYW1lIGBjb250YWluYCBzb21lIGVsZW1lbnQuIFRoZSBjb2RlIGJlbG93IHNob3dzIHlvdSBob3cgdGhpcyBpcyBkb25lIGlmIEkgYW0gbG9va2luZyBmb3IgY29sdW1uIG5hbWVzIHdpdGggdGhlIHBocmFzZSAiQ2FycmllciIsIHRoZW4gd2l0aCAiRGUiLCBhbmQgdGhlbiB3aXRoICJOdW0iICAKCmBgYHtyIHNlbGVjdDA0fQpjbWhmbGlnaHRzICU+JSAKICBzZWxlY3QoY29udGFpbnMoIkNhcnJpZXIiKSkgLT4gbXkuZGYKCm5hbWVzKG15LmRmKQoKY21oZmxpZ2h0cyAlPiUgCiAgc2VsZWN0KHN0YXJ0c193aXRoKCJEZSIpKSAtPiBteS5kZgoKbmFtZXMobXkuZGYpCgpjbWhmbGlnaHRzICU+JSAKICBzZWxlY3QoZW5kc193aXRoKCJOdW0iKSkgLT4gbXkuZGYKCm5hbWVzKG15LmRmKQpgYGAKCiMjIGZpbHRlcigpCkRvIHlvdSByZWFsbHkgd2FudCBhbGwgdGhlIHJvd3MgaW4gdGhlIGRhdGEtc2V0IG9yIHZlcnkgc3BlY2lmaWMgcm93cyB0aGF0IG1lZXQgc29tZSBjcml0ZXJpYT8gU2F5IHdlIG9ubHkgd2FudCB0byBsb29rIGF0IGNlcnRhaW4gbW9udGhzLCBvciBqdXN0IGZsaWdodHMgb24gU2F0dXJkYXlzIGFuZCBTdW5kYXlzLCBvciBmbGlnaHRzIGluIGEgZ2l2ZW4gbW9udGguIEZvciBleGFtcGxlLCBzYXkgd2Ugb25seSB3YW50IGZsaWdodHMgaW4gSmFudWFyeSwgaS5lLiwgYE1vbnRoID09IDFgLiAKCmBgYHtyIGZpbHRlcjAxfQpjbWhmbGlnaHRzICU+JSAKICBmaWx0ZXIoTW9udGggPT0gMSkgLT4gbXkuZGYKCnRhYmxlKG15LmRmJE1vbnRoKSAjIFNob3cgbWUgYSBmcmVxdWVuY3kgdGFibGUgZm9yIE1vbnRoIGluIG15LmRmCmBgYAoKV2hhdCBhYm91dCBvbmx5IEFtZXJpY2FuIEFpcmxpbmUgKFVuaXF1ZUNhcnJpZXIgY29kZSBmb3IgdGhpcyBhaXJsaW5lIGlzIEFBKSBmbGlnaHRzIGluIEphbnVhcnk/IAoKYGBge3IgZmlsdGVyMDJ9CmNtaGZsaWdodHMgJT4lIAogIGZpbHRlcihNb250aCA9PSAxLCBVbmlxdWVDYXJyaWVyID09ICJBQSIpIC0+IG15LmRmIAoKdGFibGUobXkuZGYkTW9udGgsIG15LmRmJFVuaXF1ZUNhcnJpZXIpICMgYSBzaW1wbGUgZnJlcXVlbmN5IHRhYmxlCmBgYAoKV2hhdCBhYm91dCBVbml0ZWQgQWlybGluZXMgZmxpZ2h0cyBpbiBKYW51YXJ5IHRvIENNSCAodGhlIGFpcnBvcnQgY29kZSBmb3IgQ29sdW1idXMsIE9IKSB0byBhbnkgZGVzdGluYXRpb24/IAoKYGBge3IgZmlsdGVyMDN9CmNtaGZsaWdodHMgJT4lIAogIGZpbHRlcihNb250aCA9PSAxLCBVbmlxdWVDYXJyaWVyID09ICJVQSIsIERlc3QgPT0gIkNNSCIpIC0+IG15LmRmCmBgYAoKV2hhdCBpZiBJIHdhbnRlZCBhIG1vcmUgY29tcGxpY2F0ZWQgZmlsdGVyLCBzYXksIGZsaWdodHMgaW4gSmFudWFyeSBvciBGZWJydWFyeSB0byBDTUggb3IgT1JEICh0aGUgYWlycG9ydCBjb2RlIGZvciBPJ0hhcmUgaW4gQ2hpY2Fnbyk/CgpgYGB7ciBmaWx0ZXIwNH0KY21oZmxpZ2h0cyAlPiUgCiAgZmlsdGVyKAogICAgTW9udGggJWluJSBjKDEsIDIpLCBVbmlxdWVDYXJyaWVyID09ICJVQSIsIERlc3QgJWluJSBjKCJDTUgiLCAiT1JEIikKICAgICkgLT4gbXkuZGYgCgp0YWJsZShteS5kZiRNb250aCkgIyBmcmVxdWVuY3kgdGFibGUgb2YgTW9udGgKdGFibGUobXkuZGYkVW5pcXVlQ2FycmllcikgIyBmcmVxdWVuY3kgdGFibGUgb2YgVW5pcXVlQ2Fycmllcgp0YWJsZShteS5kZiREZXN0KSAjIGZyZXF1ZW5jeSB0YWJsZSBvZiBEZXN0CmBgYAoKQmVhdXRpZnVsLCBqdXN0IGJlYXV0aWZ1bC4gSXQgbWF5IG5vdCBiZSByZWFkaWx5IGFwcGFyZW50IGF0IHRoaXMgcG9pbnQgYnV0IHVzaW5nIGAlaW4lIGMoLi4uKWAgbWFrZXMgYXBwbHlpbmcgY29tcGxleCBmaWx0ZXJzIHF1aXRlIGVhc3kuIE5vdGUgIHRoZSBvcGVyYXRvcnMgIHRoYXQgd29yayB3aXRoIGBmaWx0ZXIoKWAgCgp8ICoqT3BlcmF0b3IqKiB8ICoqTWVhbmluZyoqICB8ICoqT3BlcmF0b3IqKiB8ICoqTWVhbmluZyoqIHwKfCA6LS0tLSB8IDotLS0tIHwgOi0tLS0gfCAgOi0tLS0gfAp8ICQ8JCAgICB8IGxlc3MgdGhhbiB8ICQ+JCAgICB8IGdyZWF0ZXIgdGhhbiB8CnwgJD09JCAgIHwgZXF1YWwgdG8gIHwgJFxsZXEkIHwgbGVzcyB0aGFuIG9yIGVxdWFsIHRvIHwKfCAkXGdlcSQgfCBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gfCBgIT1gIHwgbm90IGVxdWFsIHRvIHwKfCBgJWluJWAgICB8IGlzIGEgbWVtYmVyIG9mIHwgIGBpcy5uYWAgfCBpcyBOQSB8CnwgYCFpcy5uYWAgfCBpcyBub3QgTkEgIHwgYCYsISxldGNgIHwgQm9vbGVhbiBvcGVyYXRvcnMgfAoKCk5vdyBsZXQgdXMgc2F5IEkgd2FudGVkIHRvIGFycmFuZ2UgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lIGluIGBhc2NlbmRpbmcgb3JkZXJgIG9mIGRlcGFydHVyZSBkZWxheXMuIEhvdyBtaWdodCBJIGRvIHRoYXQ/IFZpYSBgYXJyYW5nZSgpYAoKIyMgYXJyYW5nZSgpCgpgYGB7ciBhcnJhbmdlMDF9Cm15LmRmICU+JSAKICBhcnJhbmdlKERlcERlbGF5TWludXRlcykgLT4gbXkuZGYyCmBgYAoKQW5kIG5vdyBpbiBgZGVzY2VuZGluZyBvcmRlcmAgb2YgZGVsYXlzIGJ5IGFkZGluZyB0aGUgbWludXMgc3ltYm9sIGAtYCB0byB0aGUgIGNvbHVtbiBuYW1lLiAKCmBgYHtyIGFycmFuZ2UwMn0KbXkuZGYgJT4lIAogIGFycmFuZ2UoLURlcERlbGF5TWludXRlcykgLT4gbXkuZGYyCmBgYAoKV2UgY291bGQgdHdlYWsgdGhpcyBmdXJ0aGVyLCBwZXJoYXBzIHNheWluZyBzb3J0IGJ5IGRlcGFydHVyZSBkZWxheXMgdG8gQ01ILCBhbmQgdGhlbiB0byBPUkQuIAoKYGBge3IgYXJyYW5nZTAzfQpteS5kZiAlPiUgCiAgYXJyYW5nZShEZXN0LCAtRGVwRGVsYXlNaW51dGVzKSAtPiBteS5kZjIKYGBgCgpTbyBmYXIsIHdlIGhhdmUgc2VlbiBlYWNoIGZ1bmN0aW9uIGluIGlzb2xhdGlvbi4gTm93IHdlIHN0cmVhbWxpbmUgdGhpbmdzIGEgYml0IHNvIHRoYXQgd2Ugb25seSBlbmQgdXAgd2l0aCB0aGUgY29sdW1ucyBhbmQgcm93cyB3ZSB3YW50IHRvIHdvcmsgd2l0aCwgYXJyYW5nZWQgYXMgd2Ugd2FudCB0aGUgcmVzdWx0aW5nIGRhdGEtc2V0IHRvIGJlLiAKCmBgYHtyIHN0cmluZ2luZzAxfQpjbWhmbGlnaHRzICU+JSAKICBzZWxlY3QoTW9udGgsIFVuaXF1ZUNhcnJpZXIsIERlc3QsIERlcERlbGF5TWludXRlcykgJT4lIAogIGZpbHRlcigKICAgIE1vbnRoICVpbiUgYygxLCAyKSwgVW5pcXVlQ2FycmllciA9PSAiVUEiLCBEZXN0ICVpbiUgYygiQ01IIiwgIk9SRCIpCiAgICApICU+JSAKICBhcnJhbmdlKE1vbnRoLCBEZXN0LCAtRGVwRGVsYXlNaW51dGVzKSAtPiBteS5kZjMKYGBgCgpIZXJlLCB0aGUgZW5kIHJlc3VsdCBpcyBhIGRhdGEgZnJhbWUgYXJyYW5nZWQgYnkgTW9udGgsIHRoZW4gd2l0aGluIE1vbnRoIGJ5IERlc3RpbmF0aW9uLCBhbmQgdGhlbiBmaW5hbGx5IGJ5IGRlc2NlbmRpbmcgb3JkZXIgb2YgZmxpZ2h0IGRlbGF5cy4gVGhpcyBpcyB0aGUgYmVhdXR5IG9mIGBkcGx5cmAsIGFsbG93aW5nIHVzIHRvIGNoYWluIHRvZ2V0aGVyIHZhcmlvdXMgZnVuY3Rpb25zIHRvIGdldCB3aGF0IHdlIHdhbnQuIEhvdyBpcyB0aGlzIGhlbHBmdWw/IFdlbGwsIG5vdyB5b3UgaGF2ZSBhIGRhdGEgZnJhbWUgdGhhdCB5b3UgY2FuIGFuYWx5emUuIAoKIyMgc3VtbWFyaXNlKCkKV2hhdCBpZiB3ZSBuZWVkIHRvIGNhbGN1bGF0ZSBmcmVxdWVuY2llcz8gRm9yIGV4YW1wbGUsIGhvdyBtYW55IGZsaWdodHMgcGVyIG1vbnRoIGFyZSB0aGVyZT8gV2hhdCBpZiB3ZSB3YW50IHRoZSBtZWFuIERlcERlbGF5IG9yIG1lZGlhbiBBcnJEZWxheT8gVGhlc2UgY2FuIGJlIGVhc2lseSBjYWxjdWxhdGVkIGFzIHNob3duIGJlbG93LiAKCmBgYHtyIHN1bW1hcmlzZTAxfQpjbWhmbGlnaHRzICU+JQogIGNvdW50KE1vbnRoKSAjIE1vc3QgZmxpZ2h0cyB3ZXJlIGluIEp1bHkgKG4gPSA0Mjk1KQpgYGAKCldoYXQgYWJvdXQgYnkgZGF5cyBvZiB0aGUgd2VlayBBTkQgYnkgbW9udGg/IAoKYGBge3Igc3VtbWFyaXNlMDJ9CmNtaGZsaWdodHMgJT4lCiAgY291bnQoTW9udGgsIERheU9mV2VlaykgIyBPdXRwdXQgaXMgc29ydGVkIGJ5IE1vbnRoIGFuZCB0aGVuIERheU9mV2VlawpgYGAKCkkgd2FudCB0byBrbm93IHRoZSBhdmVyYWdlIGRlcGFydHVyZSBkZWxheSBhbmQgdGhlIGF2ZXJhZ2UgYXJyaXZhbCBkZWxheSBmb3IgYWxsIGZsaWdodHMsIHdpdGggdGhlIGF2ZXJhZ2VzIGNhbGN1bGF0ZWQgaW4gdHdvIHdheXMgLS0gYXMgdGhlIG1lYW4sIGFuZCBhcyB0aGUgbWVkaWFuLiAKCmBgYHtyIHN1bW1hcmlzZTAzfQpjbWhmbGlnaHRzICU+JQogIHN1bW1hcmlzZSgKICAgIG1lYW5fYXJyX2RlbGF5ID0gbWVhbihBcnJEZWxheSwgbmEucm0gPSBUUlVFKSwKICAgIG1lYW5fZGVwX2RlbGF5ID0gbWVhbihEZXBEZWxheSwgbmEucm0gPSBUUlVFKSwKICAgIG1lZGlhbl9hcnJfZGVsYXkgPSBtZWRpYW4oQXJyRGVsYXksIG5hLnJtID0gVFJVRSksCiAgICBtZWRpYW5fZGVwX2RlbGF5ID0gbWVkaWFuKERlcERlbGF5LCBuYS5ybSA9IFRSVUUpCiAgICApCmBgYAoKSGVyZSwgdGhlIGBuYS5ybSA9IFRSVUVgIGNvbW1hbmQgaXMgdXNlZnVsIGJlY2F1c2UgUiB3aWxsIG5vdCBhbGxvdyB5b3UgdG8gY2FsY3VsYXRlIGFueSBtZWFuIG9yIG1lZGlhbiBldGMuIFlvdSBjYW4gc2VlIHRoaXMgYmVsb3csIHdoZXJlIEkgaGF2ZSBhIHNtYWxsIGRhdGEtc2V0IGNhbGxlZCBgZGZgIHdpdGggNCB2YWx1ZXMgb2YgeCwgYnV0IG9uZSBvZiB0aGUgZm91ciBpcyBtaXNzaW5nIGFuZCByZWNvcmRlZCBhcyBgTkFgLiBTZWUgd2hhdCBoYXBwZW5zIHdoZW4gSSB0cnkgdG8gY2FsY3VsYXRlIHRoZSBtZWFuIHdpdGgvd2l0aG91dCBgbmEucm0gPSBUUlVFYC4KCmBgYHtyIG5hcm19CmRmID0gZGF0YS5mcmFtZSh4ID0gYygyLCA0LCA5LCBOQSkpCgpkZiAlPiUKICBzdW1tYXJpc2UobWVhbi54ID0gbWVhbih4KSkgIyBZb3UgZ2V0IG5vIG1lYW5pbmdmdWwgdmFsdWVzIAoKZGYgJT4lCiAgc3VtbWFyaXNlKG1lYW4ueCA9IG1lYW4oeCwgbmEucm0gPSBUUlVFKSkgIyBOb3cgeW91IGRvIGdldCBzb21ldGhpbmcgbWVhbmluZ2Z1bApgYGAKCiMjIGdyb3VwX2J5KCkKVGhlc2Ugc3VtbWFyaWVzIGFyZSBmaW5lIGlmIHlvdSB3YW50IHRvIGNhbGN1bGF0ZSBhZ2dyZWdhdGUgcXVhbnRpdGllcyBvZiBpbnRlcmVzdCBidXQgd2hhdCBpZiB5b3Ugd2FudGVkIHRvIGNhbGN1bGF0ZSBudW1iZXIgb2YgZmxpZ2h0cyBwZXIgbW9udGggYnkgYWlybGluZT8gQXZlcmFnZSBkZWxheXMgYnkgYWlybGluZT8gTm93IHRoaW5ncyBnZXQgaW50ZXJlc3RpbmcgYmVjYXVzZSBgZ3JvdXAtYnkoKWAgd2lsbCBvcGVuIHVwIHRoaXMgbmV3IHdvcmxkIGZvciB1cyEgVGhlIGZpcnN0IHRoaW5nIEkgd2lsbCBjYWxjdWxhdGUgaXMgdGhlIG51bWJlciBvZiBmbGlnaHRzIGJ5IGFpcmxpbmUgcGVyIG1vbnRoLiAKCmBgYHtyIGdyb3VwYnkwMX0KY21oZmxpZ2h0cyAlPiUKICBncm91cF9ieShNb250aCwgQ2FycmllcikgJT4lCiAgdGFsbHkoKQoKY21oZmxpZ2h0cyAlPiUKICBncm91cF9ieShNb250aCwgQ2FycmllcikgJT4lCiAgc3VtbWFyaXNlKAogICAgZnJlcXVlbmN5ID0gbigpCiAgICApCmBgYAoKVHdvIHdheXMgdG8gZG8gdGhpcywgZmlyc3Qgd2l0aCBgdGFsbHkoKWAgYW5kIHRoZSBzZWNvbmQgd2l0aCBgc3VtbWFyaXNlKGZyZXF1ZW5jeSA9IG4oKSlgIC4uLiBib3RoIHlpZWxkaW5nIHRoZSBzYW1lIHJlc3VsdC4gSSB3b3VsZCB1cmdlIHlvdSB0byByZW1lbWJlciBgdGFsbHkoKWAgYmVjYXVzZSBpdCBpcyBzaG9ydGVyIGNvZGUuIEZvciBleGFtcGxlLCBub3cgSSB3YW50IGEgc2lsbHkgdGFibGUgdGhhdCBnaXZlcyB1cyB0aGUgbnVtYmVyIG9mIGZsaWdodHMgcGVyIG1vbnRoIHBlciBhaXJsaW5lIHBlciBkZXN0aW5hdGlvbi4gCgpgYGB7ciBncm91cGJ5MDJ9CmNtaGZsaWdodHMgJT4lCiAgZ3JvdXBfYnkoTW9udGgsIENhcnJpZXIsIERlc3QpICU+JQogIHRhbGx5KCkKYGBgCgpXZSBjb3VsZCBrZWVwIGNvbXBsaWNhdGluZyB0aGUgZ3JvdXBpbmcgc3RydWN0dXJlLiBGb3IgZXhhbXBsZSwgbGV0IHVzIGFkZCB0aGUgZGF5IG9mIHRoZSB3ZWVrIHRvIHRoZSBtaXggLi4uIAoKYGBge3IgZ3JvdXBieTAzfQpjbWhmbGlnaHRzICU+JQogIGdyb3VwX2J5KE1vbnRoLCBDYXJyaWVyLCBEZXN0LCBEYXlPZldlZWspICU+JQogIHRhbGx5KCkKYGBgCgpOb3cgc2F5IEkgYW0gcmVhbGx5IGN1cmlvdXMgYWJvdXQgbWVhbiBkZXBhcnR1cmUgZGVsYXlzIGZvciB0aGUgcHJlY2VkaW5nIGdyb3VwaW5nIHN0cnVjdHVyZS4gVGhhdCBpcywgd2hhdCBkb2VzIG1lYW4gZGVwYXJ0dXJlIGRlbGF5IGxvb2sgbGlrZSBmb3IgZmxpZ2h0cyBieSBkYXkgb2YgdGhlIHdlZWssIGJ5IG1vbnRoLCBieSBjYXJyaWVyLCBhbmQgYnkgZGVzdGluYXRpb24/CgpgYGB7ciBncm91cGJ5MDR9CmNtaGZsaWdodHMgJT4lCiAgZ3JvdXBfYnkoTW9udGgsIENhcnJpZXIsIERlc3QsIERheU9mV2VlaykgJT4lCiAgc3VtbWFyaXNlKG1lYW5fZGVwX2RlbGF5ID0gbWVhbihEZXBEZWxheSwgbmEucm0gPSBUUlVFKSkKYGBgCgpCdXQgdGhpcyBpcyBhIGNvbXBsaWNhdGVkIHN1bW1hcnkgdGFibGUuIFdoYXQgaWYgYWxsIEkgcmVhbGx5IHdhbnQgdG8ga25vdyBpcyB3aGF0IGFpcmxpbmUgaGFzIHRoZSBoaWdoZXN0IG1lYW4gZGVwYXJ0dXJlIGRlbGF5cywgcmVnYXJkbGVzcyBvZiBtb250aCBvciBkZXN0aW5hdGlvbiBvciBkYXkgb2YgdGhlIHdlZWs/ICAKCmBgYHtyIGdyb3VwYnkwNX0KY21oZmxpZ2h0cyAlPiUKICBncm91cF9ieShDYXJyaWVyKSAlPiUKICBzdW1tYXJpc2UobWVhbl9kZXBfZGVsYXkgPSBtZWFuKERlcERlbGF5LCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKC1tZWFuX2RlcF9kZWxheSkgIyBvcmRlcmVkIGluIGRlc2NlbmRpbmcgb3JkZXIgb2YgZGVsYXlzCmBgYAoKRVYgaXMgRXhwcmVzcyBKZXQ7IEY5IGlzIEZyb250aWVyIEFpcmxpbmVzOyBXTiBpcyBTb3V0aHdlc3QgQWlybGluZXM7IE9PIGlzIFNreVdlc3QgQWlybGluZXM7IEFBIGlzIEFtZXJpY2FuIEFpcmxpbmVzOyBETCBpcyBEZWx0YSBBaXJsaW5lczsgVUEgaXMgVW5pdGVkIEFpcmxpbmVzLiBTbyBjbGVhcmx5IFVuaXRlZCBBaXJsaW5lcyBoYWQgdGhlIGxvd2VzdCBhdmVyYWdlIGRlcGFydHVyZSBkZWxheXMuIFdvdWxkIHRoaXMgc3RpbGwgYmUgdHJ1ZSBpZiB3ZSByZXBlYXRlZCB0aGUgY2FsY3VsYXRpb24gYnkgTW9udGg/IAoKYGBge3IgZ3JvdXBieTA2fQpjbWhmbGlnaHRzICU+JQogIGdyb3VwX2J5KENhcnJpZXIsIE1vbnRoKSAlPiUKICBzdW1tYXJpc2UobWVhbl9kZXBfZGVsYXkgPSBtZWFuKERlcERlbGF5LCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKG1lYW5fZGVwX2RlbGF5KSAjIG9yZGVyZWQgaW4gZGVzY2VuZGluZyBvcmRlciBvZiBkZWxheXMKYGBgCgpBbGwgcmlnaHR5IHRoZW4hIExvb2tzIGxpa2UgdGhyZWUgb2YgdGhlIGxvd2VzdCBtZWFuIGRlcGFydHVyZSBkZWxheXMgd2VyZSBmb3IgU2t5V2VzdC4gRG8gbm90IGxldCB0aGUgbmVnYXRpdmUgbnVtYmVycyB0aHJvdyB5b3UgZm9yIGEgbG9vcDsgYSBuZWdhdGl2ZSB2YWx1ZSBpbXBsaWVzIHRoZSBmbGlnaHQgbGVmdCBlYXJsaWVyIHRoYW4gc2NoZWR1bGVkLiAKClNvIGZhciBzbyBnb29kLiBCdXQgbm93IEkgYW0gY3VyaW91cyBhYm91dCB3aGF0IHBlcmNlbnQgb2YgZmxpZ2h0cyBvcGVyYXRlZCBieSBBQSwgREwsIFVBLCBhbmQgV04gd2VyZSBkZWxheWVkLiBIb3cgY291bGQgSSBjYWxjdWxhdGUgdGhpcz8gCgooMSkgSSBuZWVkIHRvIHVzZSBgZmlsdGVyKClgIHRvIHJlc3RyaWN0IHRoZSBkYXRhLXNldCB0byBqdXN0IHRoZXNlIGZvdXIgYWlybGluZXMuIAooMikgVGhlbiBJIG5lZWQgdG8gZ2VuZXJhdGUgYSBuZXcgY29sdW1uIHRoYXQgaWRlbnRpZmllcyB3aGV0aGVyIGEgZmxpZ2h0IHdhcyBkZWxheWVkIG9yIG5vdCAoYGxhdGVgKS4gCigzKSBOb3cgd2UgY2FuIGNhbGN1bGF0ZSB0aGUgdG90YWwgbnVtYmVyIG9mIGZsaWdodHMgKGBuZmxpZ2h0c2ApIGFuZCB0aGUgdG90YWwgbnVtYmVyIG9mIGZsaWdodHMgdGhhdCB3ZXJlIGRlbGF5ZWQgKGBubGF0ZWApLiAKKDQpIElmIEkgdGhlbiBjYWxjdWxhdGUgJFxsZWZ0KCBcZGZyYWN7bmxhdGV9e25mbGlnaHRzfSBccmlnaHQpXHRpbWVzIDEwMCQgd2Ugd2lsbCBlbmQgdXAgd2l0aCB0aGUgcGVyY2VudCBvZiBmbGlnaHRzIHRoYXQgd2VyZSBkZWxheWVkLiAKCmBgYHtyIGdyb3VwYnkwN30KY21oZmxpZ2h0cyAlPiUKICBzZWxlY3QoYyhDYXJyaWVyLCBEZXBEZWxheSkpICU+JQogIGZpbHRlcihDYXJyaWVyICVpbiUgYygiQUEiLCAiREwiLCAiVUEiLCAiV04iKSkgJT4lCiAgbXV0YXRlKGxhdGUgPSBjYXNlX3doZW4oCiAgICBEZXBEZWxheSA+IDAgfiAiWWVzIiwKICAgIERlcERlbGF5IDw9IDAgfiAiTm8iCiAgICApCiAgICApICU+JSAKICBncm91cF9ieShDYXJyaWVyKSAlPiUKICBtdXRhdGUobmZsaWdodHMgPSBuKCkpICU+JQogIGdyb3VwX2J5KENhcnJpZXIsIGxhdGUpICU+JQogIG11dGF0ZSgKICAgIG5sYXRlID0gbigpLAogICAgcGN0X2xhdGUgPSAobmxhdGUgLyBuZmxpZ2h0cykgKiAxMDApIC0+IGRmMQoKZGYxCmBgYAoKVGhlcmUgaXMgYSB3aG9sZSBsb3QgZ29pbmcgb24gaGVyZSBzbyBsZXQgdXMgYnJlYWsgaXQgZG93bi4gCgorIGBmaWx0ZXIoQ2FycmllciAlaW4lIGMoIkFBIiwgIkRMIiwgIlVBIiwgIldOIikpYCBpcyBrZWVwaW5nIHNwZWNpZmllZCBhaXJsaW5lcycgZGF0YSB3aGlsZSBkcm9wcGluZyB0aGUgcmVzdAorIGBtdXRhdGUobGF0ZSA9IGNhc2Vfd2hlbiguLi4pYCBpcyBjcmVhdGluZyBhIG5ldyBjb2x1bW4gY2FsbGVkIGBsYXRlYCBhbmQgc3RvcmluZyBhIHZhbHVlIG9mICJZZXMiIGlmIGBEZXBEZWxheSA+IDBgIGFuZCAiTm8iIGlmIGBEZXBEZWxheSA8PSAwYAorIGBncm91cF9ieShDYXJyaWVyKWAgaXMgZ3JvdXBpbmcgYnkgQ2FycmllciBhbmQgdGhlbiBjb3VudGluZyBob3cgbWFueSBmbGlnaHRzIHRoZXJlIHdlcmUgcGVyIENhcnJpZXIgYW5kIHN0b3JpbmcgdGhpcyBzdW0gaW4gYSBuZXcgY29sdW1uIGNhbGxlZCBgbmZsaWdodHNgCisgYGdyb3VwX2J5KENhcnJpZXIsIGxhdGUpYCBpcyBncm91cGluZyB0aGUgZGF0YSBieSBDYXJyaWVyIGFuZCBsYXRlCisgYG11dGF0ZShubGF0ZSA9IG4oKSwgcGN0X2xhdGUgPSAobmxhdGUgLyBuZmxpZ2h0cykgKiAxMDApYCBpcyB0aGVuIGNyZWF0aW5nIHR3byBuZXcgY29sdW1ucywgYG5sYXRlYCwgdGhlIG51bWJlciBvZiBmbGlnaHRzIHBlciBsYXRlIHZhbHVlcyBvZiAiWWVzIiBhbmQgIk5vIiwgcmVzcGVjdGl2ZWx5LCBhbmQgdGhlbiBgcGN0X2xhdGVgCgpOb3csIHdlIG9ubHkgd2FudCB0aGUgZmxpZ2h0cyB0aGF0IHdlcmUgbGF0ZSBzbyBsZXQgdXMgYXBwbHkgYHNlbGVjdCgpYCB0byBrZWVwIGp1c3QgYSBmZXcgY29sdW1ucyBhbmQgdGhlbiB3ZSB1c2UgYGZpbHRlcigpYCB0byBrZWVwIG9ubHkgcm93cyBjb3JyZXNwb25kaW5nIHRvIGBsYXRlID0gIlllcyJgLiBUaGlzIHdpbGwgc3RpbGwgbGVhdmUgdXMgd2l0aCBkdXBsaWNhdGUgcm93cyBidXQgd2UgY2FuIGRyb3AgdGhlc2UgZHVwbGljYXRlIHJvd3MgdmlhIGEgbmV3IGNvbW1hbmQsIGBkaXN0aW5jdCgpYCAgCgpgYGB7ciBncm91cGJ5MDh9CmRmMSAlPiUKICBmaWx0ZXIobGF0ZSA9PSAiWWVzIikgJT4lCiAgc2VsZWN0KENhcnJpZXIsIHBjdF9sYXRlKSAlPiUKICBkaXN0aW5jdCgpICU+JQogIGFycmFuZ2UocGN0X2xhdGUpCmBgYAoKU28hIDI0JSBvZiBVQSBmbGlnaHRzIHdlcmUgbGF0ZSwgdGhlIGxvd2VzdCBpbiB0aGlzIGdyb3VwLiAKCldoYXQgaWYgd2Ugd2FudGVkIHRvIGRvIHRoaXMgZm9yIGFsbCBhaXJsaW5lcywgYW5kIHdlIHdhbnQgaXQgYnkgTW9udGg/CgpgYGB7ciBncm91cGJ5MDl9CmNtaGZsaWdodHMgJT4lCiAgc2VsZWN0KGMoQ2FycmllciwgTW9udGgsIERlcERlbGF5KSkgJT4lCiAgZmlsdGVyKENhcnJpZXIgJWluJSBjKCJBQSIsICJETCIsICJVQSIsICJXTiIpKSAlPiUKICBtdXRhdGUobGF0ZSA9IGNhc2Vfd2hlbigKICAgIERlcERlbGF5ID4gMCB+ICJZZXMiLAogICAgRGVwRGVsYXkgPD0gMCB+ICJObyIKICAgICkKICAgICkgJT4lIAogIGdyb3VwX2J5KENhcnJpZXIsIE1vbnRoKSAlPiUKICBtdXRhdGUobmZsaWdodHMgPSBuKCkpICU+JQogIGdyb3VwX2J5KENhcnJpZXIsIE1vbnRoLCBsYXRlKSAlPiUKICBtdXRhdGUoCiAgICBubGF0ZSA9IG4oKSwKICAgIHBjdF9sYXRlID0gKG5sYXRlIC8gbmZsaWdodHMpICogMTAwKSAlPiUKICBmaWx0ZXIobGF0ZSA9PSAiWWVzIikgJT4lCiAgc2VsZWN0KENhcnJpZXIsIE1vbnRoLCBwY3RfbGF0ZSkgJT4lCiAgZGlzdGluY3QoKSAlPiUKICBhcnJhbmdlKHBjdF9sYXRlKQpgYGAKCkJlZm9yZSB3ZSBtb3ZlIG9uLCBJIHdhbnQgdG8gcG9pbnQgb3V0IHNvbWV0aGluZyBhYm91dCBgY2FzZV93aGVuKClgLiBTcGVjaWZpY2FsbHksIHdlIHVzZWQgaXQgdG8gY3JlYXRlIGEgbmV3IGNvbHVtbiBjYWxsZWQgYGxhdGVgIGZyb20gbnVtZXJpYyB2YWx1ZXMgZm91bmQgaW4gYERlcERlbGF5YC4gQnV0IHdoYXQgaWYgd2Ugd2FudGVkIHRvIGNyZWF0ZSBhIG5ldyBjb2x1bW4gZnJvbSBhIGNvbHVtbiB0aGF0IGhhZCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaW4gaXQsIGxpa2UgYERlc3RgIG9yIGBDYXJyaWVyYD8gRWFzeS4KCmBgYHtyIGNhc2V3aGVuMDF9CmNtaGZsaWdodHMgJT4lCiAgZmlsdGVyKENhcnJpZXIgJWluJSBjKCJBQSIsICJETCIsICJVQSIpKSAlPiUKICBtdXRhdGUoY2Fycmllcl9uYW1lID0gY2FzZV93aGVuKAogICAgQ2FycmllciA9PSAiQUEiIH4gIkFtZXJpY2FuIEFpcmxpbmVzIiwKICAgIENhcnJpZXIgPT0gIkRMIiB+ICJEZWx0YSBBaXJsaW5lcyIsCiAgICBDYXJyaWVyID09ICJVQSIgfiAiVW5pdGVkIEFpcmxpbmVzIgogICAgKQogICAgKSAlPiUKICBzZWxlY3QoQ2FycmllciwgY2Fycmllcl9uYW1lKSAKYGBgCgpTZWNvbmQsIGBjYXNlX3doZW4oKWAgaW5jbHVkZXMgYW4gb3B0aW9uIHRoYXQgY3V0cyBkb3duIG9uIG91ciB3b3JrLiBJbiBwYXJ0aWN1bGFyLCBzYXkgSSB3YW50IHRvIGNyZWF0ZSBhIG5ldyBjb2x1bW4gYW5kIGxhYmVsIGl0cyB2YWx1ZXMgYXMgIldlZWtlbmQiIGlmIHRoZSBEYXlPZldlZWsgaXMgU2F0dXJkYXkgb3IgU3VuZGF5IGFuZCAiV2Vla2RheSIgaWYgRGF5T2ZXZWVrIGlzIGFueSBvdGhlciBkYXkuIEluIGRvaW5nIHRoaXMsIGl0IHdvdWxkIHNlcnZlIHVzIHdlbGwgdG8gcmVtZW1iZXIgdGhhdCB0aGUgd2VlayBiZWdpbnMgb24gU3VuZGF5IHNvIERheU9mV2VlayA9PSAxIGlzIFN1bmRheSwgbm90IE1vbmRheS4gCgpgYGB7ciBjYXNld2hlbjAyfQpjbWhmbGlnaHRzICU+JQogIG11dGF0ZSh3ZWVrZW5kID0gY2FzZV93aGVuKAogICAgRGF5T2ZXZWVrICVpbiUgYyg3LCAxKSB+ICJZZXMiLAogICAgVFJVRSB+ICJObyIKICAgICkKICAgICkgJT4lCiAgc2VsZWN0KERheU9mV2Vlaywgd2Vla2VuZCkgJT4lCiAgZGlzdGluY3QoKQpgYGAKCk5vdGljZSBob3cgYFRSVUVgIHN3ZXB0IHVwIGFsbCBvdGhlciB2YWx1ZXMgb2YgYERheU9mV2Vla2AgYW5kIGNvZGVkIHRoZW0gYXMgIk5vLiIgIAoKT25lIGZpbmFsIHNob3djYXNpbmcgb2YgYGNhc2Vfd2hlbigpYC4gSW4gKipNb2R1bGUgMDEqKiB3ZSBsb29rZWQgYXQgdGhlIGBoc2IyYCBkYXRhIGFuZCBjcmVhdGVkIHNvbWUgYGZhY3RvcnNgIGZvciBjb2x1bW5zIHN1Y2ggYXMgZmVtYWxlLCBzZXMsIHNjaHR5cCwgYW5kIHNvIG9uLiBXZWxsLCBsZXQgdXMgc2VlIGhvdyB0aGUgc2FtZSB0aGluZyBjb3VsZCBiZSBkb25lIHdpdGggYGNhc2Vfd2hlbigpYC4KCmBgYHtyIGNhc2V3aGVuMDN9CnJlYWQudGFibGUoCiAgJ2h0dHBzOi8vc3RhdHMuaWRyZS51Y2xhLmVkdS9zdGF0L2RhdGEvaHNiMi5jc3YnLAogIGhlYWRlciA9IFRSVUUsCiAgc2VwID0gIiwiCiAgKSAtPiBoc2IyCgpoc2IyICU+JQogIG11dGF0ZSgKICAgIGZlbWFsZS5mID0gY2FzZV93aGVuKAogICAgICBmZW1hbGUgPT0gMCB+ICJNYWxlIiwKICAgICAgZmVtYWxlID09IDEgfiAiRmVtYWxlIiksCiAgICByYWNlLmYgPSBjYXNlX3doZW4oCiAgICAgIHJhY2UgPT0gMSB+ICJIaXNwYW5pYyIsCiAgICAgIHJhY2UgPT0gMiB+ICJBc2lhbiIsCiAgICAgIHJhY2UgPT0gMyB+ICJBZnJpY2FuLUFtZXJpY2FuIiwKICAgICAgVFJVRSB+ICJXaGl0ZSIpLAogICAgc2VzLmYgPSBjYXNlX3doZW4oCiAgICAgIHNlcyA9PSAxIH4gIkxvdyIsCiAgICAgIHNlcyA9PSAyIH4gIk1lZGl1bSIsCiAgICAgIFRSVUUgfiAiSGlnaCIpLAogICAgc2NodHlwLmYgPSBjYXNlX3doZW4oCiAgICAgIHNjaHR5cCA9PSAxIH4gIlB1YmxpYyIsCiAgICAgIFRSVUUgfiAiUHJpdmF0ZSIpLAogICAgcHJvZy5mID0gY2FzZV93aGVuKAogICAgICBwcm9nID09IDEgfiAiR2VuZXJhbCIsCiAgICAgIHByb2cgPT0gMiB+ICJBY2FkZW1pYyIsCiAgICAgIFRSVUUgfiAiVm9jYXRpb25hbCIpCiAgICApIC0+IGhzYjIKYGBgCgoKCiMjIFNvbWUgb3RoZXIgYGRwbHlyKClgIGNvbW1hbmRzCldlIGhhdmUgc2VlbiBgY291bnQoKWAgaW4gYWN0aW9uIGJ1dCBsZXQgdXMgc2VlIGl0IGFnYWluLCBpbiBhIHNsaWdodGx5IGRpZmZlcmVudCBsaWdodC4gSW4gcGFydGljdWxhciwgc2F5IEkgd2FudCB0byBrbm93IGhvdyBtYW55IHVuaXF1ZSBkZXN0aW5hdGlvbnMgYXJlIHRoZXJlIGNvbm5lY3RlZCBieSBhaXIgZnJvbSBDb2x1bWJ1cy4gCgojIyMgY291bnQoKQoKYGBge3IgY291bnQwMX0KY21oZmxpZ2h0cyAlPiUKICBmaWx0ZXIoT3JpZ2luID09ICJDTUgiKSAlPiUKICBjb3VudChEZXN0LCBzb3J0ID0gVFJVRSkKYGBgCgpOb3RlOiBUaGVyZSBpcyBubyBuZWVkIGZvciBgZ3JvdXBfYnkoKWAgaGVyZS4gQW5kIGBzb3J0ID0gVFJVRWAgYXJyYW5nZXMgdGhlIHJlc3VsdCBpbiBkZXNjZW5kaW5nIG9yZGVyIG9mIHRoZSBmcmVxdWVuY3kgKGBuYCkuIEhlcmUgaXMgYW5vdGhlciBjb2RlIGV4YW1wbGUsIHRoaXMgdGltZSBhZGRpbmcgQ2FycmllciB0byB0aGUgbWl4LiAKCmBgYHtyIGNvdW50MDJ9CmNtaGZsaWdodHMgJT4lCiAgZmlsdGVyKE9yaWdpbiA9PSAiQ01IIikgJT4lCiAgY291bnQoQ2FycmllciwgRGVzdCwgc29ydCA9IFRSVUUpCmBgYAoKSG93IGRvZXMgdGhpcyBoZWxwIHVzPyBXZWxsLCBub3cgd2Uga25vdyB0aGF0IGlmIHdlIHdlcmUgZmx5aW5nIHRvIEF0bGFudGEsIERlbHRhIHdvdWxkIGhhdmUgdGhlIG1vc3QgZmxpZ2h0cywgYnV0IGlmIHdlIHdlcmUgZmx5aW5nIHRvIHRoZSBDaGljYWdvIGFyZWEgdGhlbiBTb3V0aHdlc3Qgc2hvdWxkIGJlIG91ciBwaWNrLiAgCgojIyMgbl9kaXN0aW5jdCgpIApBbm90aGVyIHVzZWZ1bCBjb21tYW5kIGlzIGBuX2Rpc3RpbmN0KClgLCB1c2VmdWwgaW4gdGhlIHNlbnNlIG9mIGFsbG93aW5nIHVzIHRvIGNhbGN1bGF0ZSB0aGUgdGhlIG51bWJlciBvZiBkaXN0aW5jdCB2YWx1ZXMgb2YgYW55IGNvbHVtbi4gRm9yIGV4YW1wbGUsIHNheSBJIHdhbnQgdG8ga25vdyBob3cgbWFueSB1bmlxdWUgYWlyY3JhZnQgKG5vdCBhaXJsaW5lcykgYXJlIHRoZXJlIGluIHRoaXMgZGF0YS1zZXQuIAoKYGBge3IgbmRpc3RpbmN0MDF9CmNtaGZsaWdodHMgJT4lCiAgc3VtbWFyaXNlKG5fZGlzdGluY3QoVGFpbE51bSkpCmBgYAoKCiMjIyB0b3BfbigpCklmIHlvdSB3YW50IHRvIHNlZSB0aGUgdG9wICduJyBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zLCBmb3IgZXhhbXBsZSB0aGUgNCBhaXJsaW5lcyB3aXRoIHRoZSBtb3N0IGFpcmNyYWZ0LCB5b3UgY2FuIGxlYW4gb24gYHRvcF9uKClgLCBhcyBzaG93biBiZWxvdy4KCmBgYHtyIHRvcG4wMX0KY21oZmxpZ2h0cyAlPiUKICBncm91cF9ieShDYXJyaWVyKSAlPiUKICBzdW1tYXJpc2UobnVtLmZsaWdodHMgPSBuX2Rpc3RpbmN0KFRhaWxOdW0pKSAlPiUKICBhcnJhbmdlKC1udW0uZmxpZ2h0cykgJT4lIAogIHRvcF9uKDQpCmBgYAoKSSBhbSBhbHNvIGN1cmlvdXMgYWJvdXQgd2hpY2ggYWlyY3JhZnQgaGFzIGZsb3duIHRoZSBtb3N0LCBhbmQgdGhlbiBtYXliZSA5IG90aGVyIGFpcmNyYWZ0IHRoYXQgZm9sbG93IGluIGRlc2NlbmRpbmcgb3JkZXIuCgpgYGB7ciB0b3BuMDJ9CmNtaGZsaWdodHMgJT4lCiAgZmlsdGVyKCFpcy5uYShUYWlsTnVtKSkgJT4lICMgUmVtb3Zpbmcgc29tZSBtaXNzaW5nIGNhc2VzIAogIGdyb3VwX2J5KFRhaWxOdW0pICU+JQogIHRhbGx5KCkgJT4lIAogIGFycmFuZ2UoLW4pICU+JQogIHRvcF9uKDQpCmBgYAoKIyMjIGpvaW4oKQpZb3Ugd2lsbCwgZnJvbSB0aW1lIHRvIHRpbWUsIG5lZWQgdG8gbWVyZ2UgbXVsdGlwbGUgZGF0YS1zZXRzIHRvZ2V0aGVyLiBGb3IgZXhhbXBsZSwgc2F5IEkgaGF2ZSB0aGUgZm9sbG93aW5nIGRhdGEtc2V0cyBJIGhhdmUgY3JlYXRlZCBmb3IgZGVtb25zdHJhdGlvbiBwdXJwb3Nlcy4gCgpgYGB7ciBqb2luMH0KdGliYmxlKAogIE5hbWUgPSBjKCJUaW0iLCAiVGFtbXkiLCAiQnViYmxlcyIsICJQYW5kYSIpLAogIFNjb3JlID0gYyg1LCA4LCA5LCAxMCkpIC0+IGRmMQoKdGliYmxlKAogIE5hbWUgPSBjKCJUaW0iLCAiVGFtbXkiLCAiQnViYmxlcyIpLAogIEFnZSA9IGMoMjUsIDc4LCAxOSkpIC0+IGRmMgoKdGliYmxlKAogIE5hbWUgPSBjKCJUaW0iLCAiVGFtbXkiLCAiUGFuZGEiKSwKICBFZHVjYXRpb24gPSBjKCJCQSIsICJQaEQiLCAiSkQiKSkgLT4gZGYzCgpkZjE7IGRmMjsgZGYzCmBgYAoKTm90aWNlIHRoYXQgUGFuZGEgaXMgYWJzZW50IGZyb20gYGRmMmAgYW5kIEJ1YmJsZXMgaXMgYWJzZW50IGZyb20gYGRmM2AuIFNvIGlmIHdlIHdhbnRlZCB0byBidWlsZCBPTkUgZGF0YS1zZXQgd2l0aCBhbGwgZGF0YSBmb3IgVGltLCBUYW1teSwgQnViYmxlcywgYW5kIFBhbmRhLCBzb21lIG9mIHRoZSBpbmZvcm1hdGlvbiB3b3VsZCBiZSBtaXNzaW5nIGZvciBzb21lIG9mIHRoZXNlIGZvbGtzLiBCdXQgaG93IGNvdWxkIHdlIGNvbnN0cnVjdCBPTkUgZGF0YS1zZXQ/IFZpYSBvbmUgb2YgYSBmZXcgYGpvaW4oKWAgY29tbWFuZHMuIAoKIyMjIyBmdWxsX2pvaW4oKSAKTGV0IHVzIHN0YXJ0IHdpdGggYSBzaW1wbGUgZnVsbF9qb2luLCB3aGVyZSB3ZSBsaW5rIHVwIGV2ZXJ5IGluZGl2aWR1YWwgaW4gZGYxIG9yIGRmMiBvciBkZjMgKipyZWdhcmRsZXNzIG9mIHdoZXRoZXIgdGhleSBhcmUgc2VlbiBpbiBib3RoIGRhdGEtc2V0cyoqLiAKCmBgYHtyIGpvaW4wMn0KZGYxICU+JQogIGZ1bGxfam9pbihkZjIsIGJ5ID0gIk5hbWUiKSAlPiUKICBmdWxsX2pvaW4oZGYzLCBieSA9ICJOYW1lIikgCmBgYAoKUGF5IGF0dGVudGlvbiB0byB0d28gdGhpbmdzOiAoaSkgTmFtZSBjb25uZWN0cyB0aGUgcmVjb3JkcyBpbiBlYWNoIGRhdGEtc2V0LCBhbmQgc28gaXQgbXVzdCBiZSBzcGVsbGVkIGV4YWN0bHkgdGhlIHNhbWUgZm9yIGEgbGluayB0byBiZSBtYWRlLCBhbmQgKGlpKSB0aGUgYGZ1bGxfam9pbigpYCBsaW5rcyB1cCBhbGwgaW5kaXZpZHVhbHMgcmVnYXJkbGVzcyBvZiB3aGV0aGVyIHRoZXkgYXJlIG1pc3NpbmcgYW55IGluZm9ybWF0aW9uIGluIGFueSBvZiB0aGUgZGF0YS1zZXRzLiBUaGlzIGlzIHVzdWFsbHkgaG93IG1vc3QgZm9sa3Mgd2lsbCBsaW5rIHVwIG11bHRpcGxlIGZpbGVzIHVubGVzcyB0aGV5IG9ubHkgd2FudCByZWNvcmRzIGZvdW5kIGluIGEgbWFzdGVyIGZpbGUuIEZvciBleGFtcGxlLCBzYXkgSSB3YW50IHRvIGxpbmsgdXAgZGYyIGFuZCBkZjMgYnV0IG9ubHkgc3VjaCB0aGF0IHRoZSBmaW5hbCByZXN1bHQgd2lsbCBpbmNsdWRlIGFsbCByZWNvcmRzIGZvdW5kIGluIEJPVEggZGYyIGFuZCBkZjMsIHdpdGggZGYyIHNlcnZpbmcgYXMgdGhlIG1hc3RlciBkYXRhLXNldC4gRWg/CgpgYGB7ciBqb2luMDN9CmRmMiAlPiUKICBsZWZ0X2pvaW4oZGYzLCBieSA9ICJOYW1lIikgIApgYGAKCk5vdGljZSB0aGF0IFBhbmRhIGlzIGRyb3BwZWQgYmVjYXVzZSBpdCBpcyBub3QgZm91bmQgaW4gZGYyLiAKCk1heWJlIHlvdSB3YW50IGRmMyB0byBiZSB0aGUgbWFzdGVyIGZpbGUsIGluIHdoaWNoIGNhc2UgeW91IHdvdWxkIHNlZSBhIGRpZmZlcmVudCByZXN1bHQgKHdpdGggQnViYmxlcyBub3Qgc2VlbiBpbiB0aGUgcmVzdWx0IHNpbmNlIEJ1YmJsZXMgaXMgZm91bmQgaW4gZGYyIGJ1dCBub3QgaW4gZGYzKTogCgpgYGB7ciBqb2luMDR9CmRmMyAlPiUKICBsZWZ0X2pvaW4oZGYyLCBieSA9ICJOYW1lIikgIApgYGAKClJhcmVseSwgYnV0IGRlZmluaXRlbHkgbm90ICJuZXZlciwiIHlvdSBtYXkgd2FudCB0byBzZWUgdGhlIHJlY29yZHMgdGhhdCBhcmUgbm90IGZvdW5kIGluIGJvdGguIEhlcmUsIGFudGlfam9pbigpIGNvbWVzIGluIGhhbmR5LCB0aHVzOgoKYGBge3J9CmRmMiAlPiUKICBhbnRpX2pvaW4oZGYzLCBieSA9ICJOYW1lIikKCmRmMyAlPiUKICBhbnRpX2pvaW4oZGYyLCBieSA9ICJOYW1lIikKYGBgCgojIyBUd28gb3RoZXIgdXNlZnVsIGNvbW1hbmRzIAojIyMge3NhbnRva3V9CkV2ZXJ5IG5vdyBhbmQgdGhlbiB5b3UgbWF5IHdhbnQgdG8gb3IgbmVlZCB0byBjcmVhdGUgYSBncm91cGVkIHZlcnNpb24gb2Ygc29tZSBudW1lcmljIHZhcmlhYmxlLiBGb3IgZXhhbXBsZSwgd2UgaGF2ZSBEZXBEZWxheSBmb3IgYWxsIGZsaWdodHMgYnV0IHdhbnQgdG8gZ3JvdXAgdGhpcyBpbnRvIGBxdWFydGlsZXNgLiBIb3cgY2FuIHdlIGRvIHRoYXQ/IEluIG1hbnkgd2F5cyBidXQgdGhlIGVhc2llc3QgbWlnaHQgYmUgdG8gdXNlIGEgc3BlY2lmaWMgbGlicmFyeSAtLSBge3NhbnRva3V9YC4gU2F5LCBmb3IgZXhhbXBsZSwgSSB3YW50IHRvIGNyZWF0ZSA0IGdyb3VwcyBvZiBgZGVwX2RlbGF5YCwgYW5kIEkgd2FudCB0aGVzZSBzdWNoIHRoYXQgd2UgYXJlIGdyb3VwaW5nIGBEZXBEZWxheWAgaW50byB0aGUgYm90dG9tIDI1JSwgbmV4dCAyNSUsIHRoZSBuZXh0IDI1JSwgYW5kIGZpbmFsbHkgdGhlIGhpZ2hlc3QgMjUlLiBXYWl0LCB0aGVzZSBhcmUgdGhlIGBxdWFydGlsZXNgISBGYWlyIGVub3VnaCwgYnV0IGhvdyBjYW4gSSBkbyB0aGlzPyAKCmBgYHtyIHNhbnRva3UwMX0KbGlicmFyeShzYW50b2t1KQoKY21oZmxpZ2h0cyAlPiUKICBtdXRhdGUoCiAgICBkZXBkZWxheV9ncm91cHMgPSBjaG9wX2VxdWFsbHkoRGVwRGVsYXksIGdyb3VwcyA9IDQpCiAgKSAlPiUKICBncm91cF9ieShkZXBkZWxheV9ncm91cHMpICU+JQogIHRhbGx5KCkKYGBgCgpXaGF0IGlmIHdlIHdhbnRlZCB0byBzbGljZSB1cCBEZXBEZWxheSBpbiBzcGVjaWZpYyBpbnRlcnZhbHMsIGZpcnN0IGF0IDAsIHRoZW4gYXQgMTUsIHRoZW4gYXQgMzAsIGFuZCB0aGVuIGF0IDQ1PyAKCmBgYHtyIHNhbnRva3UwMn0KY21oZmxpZ2h0cyAlPiUKICBtdXRhdGUoCiAgICBkZXBkZWxheV9ncm91cHMgPSBjaG9wKERlcERlbGF5LCBicmVha3MgPSBjKDAsIDE1LCAzMCwgNDUpKQogICkgJT4lCiAgZ3JvdXBfYnkoZGVwZGVsYXlfZ3JvdXBzKSAlPiUKICB0YWxseSgpCmBgYAoKCldlIGNvdWxkIGFsc28gY3JlYXRlIHF1aW50aWxlcyAoNSBncm91cHMpIG9yIGRlY2lsZXMgKDEwIGdyb3VwcykgYXMgc2hvd24gYmVsb3c6IAoKYGBge3Igc2FudG9rdTAzfQpjbWhmbGlnaHRzICU+JQogIGZpbHRlcighaXMubmEoRGVwRGVsYXkpKSAlPiUKICBtdXRhdGUoCiAgICBkZXBkZWxheV9ncm91cHMgPSBjaG9wX3F1YW50aWxlcygKICAgICAgRGVwRGVsYXksIGMoMC4yLCAwLjQsIDAuNiwgMC44KQogICAgICApCiAgKSAlPiUKICBncm91cF9ieShkZXBkZWxheV9ncm91cHMpICU+JQogIHRhbGx5KCkKYGBgIAoKCmBgYHtyIHNhbnRva3UwNH0KY21oZmxpZ2h0cyAlPiUKICBmaWx0ZXIoIWlzLm5hKERlcERlbGF5KSkgJT4lCiAgbXV0YXRlKAogICAgZGVwZGVsYXlfZ3JvdXBzID0gY2hvcF9xdWFudGlsZXMoCiAgICAgIERlcERlbGF5LCBzZXEoMC4xLCAwLjksIGJ5ID0gMC4xKQogICAgICApCiAgKSAlPiUKICBncm91cF9ieShkZXBkZWxheV9ncm91cHMpICU+JQogIHRhbGx5KCkKYGBgCgoKIyMjIG9yZGVyZWQoKQptb3JlIG9mdGVuIHRoYW4gd2Ugd291bGQgbGlrZSB0byBzZWUgaGFwcGVuLCB3ZSBlbmQgdXAgd2l0aCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgdGhhdCBzaG91bGQgZm9sbG93IGEgY2VydGFpbiBvcmRlciBidXQgZG8gbm90LiBGb3IgZXhhbXBsZSwgc2F5IHlvdSBoYXZlIHN1cnZleSBkYXRhIHdoZXJlIHBlb3BsZSB3ZXJlIGFza2VkIHRvIHJlc3BvbmQgd2hldGhlciB0aGV5IEFncmVlLCBhcmUgTmV1dHJhbCwgb3IgRGlzYWdyZWUgd2l0aCBzb21lIHN0YXRlbWVudC4gTGV0IHVzIGFsc28gYXNzdW1lIHRoYXQgdGhlIGZyZXF1ZW5jaWVzIGFyZSBhcyBmb2xsb3dzOgoKYGBge3Igb3JkZXJlZDAxfQp0aWJibGUoCiAgcmVzcG9uc2UgPSBjKHJlcCgiQWdyZWUiLCAyNSksIHJlcCgiTmV1dHJhbCIsIDMwKSwgcmVwKCJEaXNhZ3JlZSIsIDQ1KSkKICAgICAgICkgLT4gbXlkZgoKbXlkZiAlPiUKICBncm91cF9ieShyZXNwb25zZSkgJT4lCiAgdGFsbHkoKQpgYGAKCk5vdGljZSBob3cgdGhlIHJlc3BvbnNlcyBhcmUgb3V0IG9mIG9yZGVyLCB3aXRoIEFncmVlIGZvbGxvd2VkIGJ5IERpc2FncmVlLCB0aGVuIE5ldXRyYWwsIHNpbmNlIFIgZGVmYXVsdHMgdG8gYWxwaGFiZXRpYyBvcmRlcmluZyBmb3IgYW55dGhpbmcgdGhhdCBpcyBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlLiBPbmUgd2F5IHRvIGVuc3VyZSB0aGUgY29ycmVjdCBvcmRlcmluZyBvZiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaXMgdmlhIGBvcmRlcmVkYCwgYXMgc2hvd24gYmVsb3cuIAoKYGBge3Igb3JkZXJlZDAyfQpteWRmICU+JQogIG11dGF0ZShvcmRlcmVkLnJlc3BvbnNlID0gb3JkZXJlZCgKICAgIHJlc3BvbnNlLAogICAgbGV2ZWxzID0gYygiQWdyZWUiLCAiTmV1dHJhbCIsICJEaXNhZ3JlZSIpCiAgICApCiAgICApICU+JQogIGdyb3VwX2J5KG9yZGVyZWQucmVzcG9uc2UpICU+JQogIHRhbGx5KCkKYGBgCgoKCiMjIENvbmNsdWRpbmcgdGhvdWdodHMgCldlIGhhdmUgYSBjb3ZlcmVkIGEgbG90IG9mIGdyb3VuZCBoZXJlIGJ1dCBldmVyeSBpbmNoIGhhcyBiZWVuIGNyaXRpY2FsIHNwYWNlLiBHb29nbGUgYW55IHF1ZXN0aW9uIHdlIGhhdmUgdGFja2xlZCBhbmQgeW91IHdpbGwgc2VlIGhvdyBtYW55IFItdXNlcnMgYXNrIHRoZSBzYW1lIHF1ZXN0aW9ucyAuLi4gaG93IGRvIEkgY2FsY3VsYXRlIG1lYW4gZm9yIGdyb3VwcyBpbiBSPyBXaGF0IHlvdSBoYXZlIHNlZW4gaXMgdGhlIGhlYXJ0IG9mIHRoZSBgZHBseXIoKWAgcGFja2FnZS4gV2Ugc2F3IGdyb3VwZWQgb3BlcmF0aW9ucywgd2Ugc2F3IHRoZSB1c2Ugb2Ygc3VtbWFyaXNlLCBtdXRhdGUsIGNhc2Vfd2hlbiwgZGlzdGluY3QsIGZpbHRlciwgYXJyYW5nZSwgc2VsZWN0LCBjb3VudCwgYW5kIHRhbGx5LiBJIHdpbGwgbGV0IHlvdSBpbiBvbiBhIHNlY3JldDsgd2hpbGUgdGhlc2UgYXJlIGNvcmUgZnVuY3Rpb25zLCB0aGVyZSBhcmUgb3RoZXJzIHlvdSBjb3VsZCBleHBlcmltZW50IHdpdGguIExvb2sgdXAgdGhlIGNoZWF0LXNoZWV0IFtoZXJlXShodHRwczovL3JzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE1LzAyL2RhdGEtd3JhbmdsaW5nLWNoZWF0c2hlZXQucGRmKS4gCgpQcmFjdGljZSB3aGF0IHdlIGhhdmUgZG9uZSwgd2l0aCB0aGUgYHtueWNmbGlnaHRzMTN9YCBkYXRhLXNldCBwZXJoYXBzIHRvIGdldCBzb21ldGhpbmcgZmFtaWxpYXIgeWV0IHN1ZmZpY2llbnRseSBkaWZmZXJlbnQgdG8gdGVzdCB5b3VyIGZ1bmRhbWVudGFscy4gTWF5YmUgcGljayBhIG5vbi10cmF2ZWwgZGF0YS1zZXQgYWx0b2dldGhlciwgcGVyaGFwcyBvbmUgb2YgdGhlIGB0aWR5dHVlc2RheWAgZGF0YS1zZXRzLiBXaGF0IGlzIHRoYXQgeW91IGFzaz8gRGlzY292ZXIgaXQgZm9yIHlvdXJzZWxmIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5KS4gQm9uIHZveWFnZSEgRG9uJ3QgZ28gdG9vIGZhciBiZWNhdXNlIHdlIHdpbGwgYmUgd29ya2luZyB3aXRoIHR3byBuZXcgcGFja2FnZXMgbmV4dCB3ZWVrIC0tIGB7dGlkeXJ9YCBhbmQgYHtsdWJyaWRhdGV9YC4gCgojIFByYWN0aWNlIEV4ZXJjaXNlcwojIyBFeGVyY2lzZSAxCldoeSBhcmUgb3VyIGJlc3QgYW5kIG1vc3QgZXhwZXJpZW5jZWQgZW1wbG95ZWVzIGxlYXZpbmcgcHJlbWF0dXJlbHk/IAoKVGhlIGRhdGEgW2F2YWlsYWJsZSBoZXJlXShodHRwczovL2FuaXJ1aGlsLmdpdGh1Yi5pby9hdnNyL3RlYWNoaW5nL2RhdGF2aXovSFJfY29tbWFfc2VwLmNzdikgaW5jbHVkZXMgaW5mb3JtYXRpb24gb24gc2V2ZXJhbCBjdXJyZW50IGFuZCBmb3JtZXIgZW1wbG95ZWVzIG9mIGFuIGFub255bW91cyBvcmdhbml6YXRpb24uKiogRmllbGRzIGluIHRoZSBkYXRhLXNldCBpbmNsdWRlOiAKCisgc2F0aXNmYWN0aW9uX2xldmVsID0gTGV2ZWwgb2Ygc2F0aXNmYWN0aW9uIChudW1lcmljOyAwLTEpIAorIGxhc3RfZXZhbHVhdGlvbiA9IEV2YWx1YXRpb24gc2NvcmUgb2YgdGhlIGVtcGxveWVlIChudW1lcmljOyAwLTEpIAorIG51bWJlcl9wcm9qZWN0ID0gTnVtYmVyIG9mIHByb2plY3RzIGNvbXBsZXRlZCB3aGlsZSBhdCB3b3JrIChudW1lcmljKSAKKyBhdmVyYWdlX21vbnRobHlfaG91cnMgPSBBdmVyYWdlIG1vbnRobHkgaG91cnMgc3BlbnQgYXQgdGhlIHdvcmtwbGFjZSAobnVtZXJpYykgIAorIHRpbWVfc3BlbmRfY29tcGFueSA9IE51bWJlciBvZiB5ZWFycyBzcGVudCBpbiB0aGUgY29tcGFueSAobnVtZXJpYykgCisgV29ya19hY2NpZGVudCA9IFdoZXRoZXIgdGhlIGVtcGxveWVlIGhhZCBhIHdvcmtwbGFjZSBhY2NpZGVudCAoY2F0ZWdvcmljYWw7IDEgPSB5ZXMgb3IgMCA9IG5vKSAKKyBsZWZ0ID0gV2hldGhlciB0aGUgZW1wbG95ZWUgbGVmdCB0aGUgd29ya3BsYWNlIG9yIG5vdCAoY2F0ZWdvcmljYWw7IDEgPSBsZWZ0IG9yIDAgPSBzdGF5ZWQpICAKKyBwcm9tb3Rpb25fbGFzdF81eWVhcnMgPSBXaGV0aGVyIHRoZSBlbXBsb3llZSB3YXMgcHJvbW90ZWQgaW4gdGhlIGxhc3QgZml2ZSB5ZWFycyAoY2F0ZWdvcmljYWw7IDEgPSB5ZXMgb3IgMCA9IG5vKSAKKyBzYWxlcyA9IERlcGFydG1lbnQgaW4gd2hpY2ggdGhleSB3b3JrIChjYXRlZ29yaWNhbCkgCisgc2FsYXJ5ID0gUmVsYXRpdmUgbGV2ZWwgb2Ygc2FsYXJ5IChjYXRlZ29yaWNhbDsgbG93LCBtZWQsIGFuZCBoaWdoKSAKCihhKSBSZWFkIGluIHRoZSBjc3YtZm9ybWF0IGRhdGEtc2V0LCBuYW1pbmcgaXQgYGhyZGF0YWAgYW5kIHNhdmUgaXQgaW4gUkRhdGEgZm9ybWF0IGFzIGBocmRhdGEuUkRhdGFgIAoKYGBge3IgaHJkYXRhLWlufQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5bG9nKQpsaWJyYXJ5KGhlcmUpCnJlYWRfY3N2KAogICJodHRwczovL2FuaXJ1aGlsLmdpdGh1Yi5pby9hdnNyL3RlYWNoaW5nL2RhdGF2aXovSFJfY29tbWFfc2VwLmNzdiIKICApIC0+IGhyZGF0YQpgYGAKCihiKSBDcmVhdGUgbmV3IHZhcmlhYmxlcyB0aGF0IGFkZCBsYWJlbHMgdG8gV29ya19hY2NpZGVudCwgbGVmdCwgcHJvbW90aW9uX2xhc3RfNXllYXJzLCBhbmQgY3JlYXRlIGEgbmV3IHZhcmlhYmxlIHRoYXQgb3JkZXJzIHNhbGFyeSBmcm9tIGxvdyB0byBoaWdoLCBhbmQgYWRkIHRoZXNlIHRvIGBocmRhdGFgLiAgCgpgYGB7ciBtdXQwMX0KaHJkYXRhICU+JQogIG11dGF0ZSgKICAgIGhhZF9hY2NpZGVudCA9IGNhc2Vfd2hlbigKICAgICAgV29ya19hY2NpZGVudCA9PSAwIH4gIk5vIiwKICAgICAgV29ya19hY2NpZGVudCA9PSAxIH4gIlllcyIpLAogICAgbGVmdF9jb21wYW55ID0gY2FzZV93aGVuKAogICAgICBsZWZ0ID09IDAgfiAiTm8iLAogICAgICBsZWZ0ID09IDEgfiAiWWVzIiksCiAgICByZWNlbnRseV9wcm9tb3RlZCA9IGNhc2Vfd2hlbigKICAgICAgcHJvbW90aW9uX2xhc3RfNXllYXJzID09IDAgfiAiTm8iLAogICAgICBwcm9tb3Rpb25fbGFzdF81eWVhcnMgPT0gMSB+ICJZZXMiKSwKICAgIHNhbGFyeV9ncm91cCA9IG9yZGVyZWQoCiAgICAgIHNhbGFyeSwgCiAgICAgIGxldmVscyA9IGMoImxvdyIsICJtZWRpdW0iLCAiaGlnaCIpLAogICAgICBsYWJlbHMgPSBjKCJMb3ciLCAiTWVkaXVtIiwgIkhpZ2giKSkKICAgICkgLT4gaHJkYXRhIApgYGAKCihjKSBDb252ZXJ0IHNhdGlzZmFjdGlvbl9sZXZlbCBmcm9tIGEgMC0xIHNjYWxlIHRvIGEgMC0xMDAgc2NhbGUsIG1ha2luZyBzdXJlIHRvIGNyZWF0ZSBhIG5ldyB2YXJpYWJsZSBvZiBjb3Vyc2UuICAKCmBgYHtyIG11dDAyfQpocmRhdGEgJT4lCiAgbXV0YXRlKAogICAgc2F0bGV2ZWwgPSBzYXRpc2ZhY3Rpb25fbGV2ZWwgKiAxMDAKICAgICkgLT4gaHJkYXRhCmBgYAoKKGQpIE5vdyByZXRhaW4gb25seSBlbXBsb3llZXMgd2hvIGxlZnQgdGhlIGNvbXBhbnksIGFuZCBoYWQgbm90IGJlZW4gcHJvbW90ZWQgaW4gdGhlIGxhc3QgZml2ZSB5ZWFycy4gU2F2ZSB0aGlzIHJlc3VsdCBhcyBgaHIwMWAKCmBgYHtyIG11dDAzfQpocmRhdGEgJT4lCiAgZmlsdGVyKAogICAgbGVmdF9jb21wYW55ID09ICJZZXMiLAogICAgcmVjZW50bHlfcHJvbW90ZWQgPT0gIk5vIgogICAgKSAtPiBocjAxCmBgYAoKKGUpIEluIHRoaXMgYGhyMDFgIGRhdGEtc2V0LCBob3cgbWFueSBlbXBsb3llZXMgZG8geW91IGhhdmUgcGVyIHNhbGVzIGRlcGFydG1lbnQ/IFdoYXQgc2FsZXMgZGVwYXJ0bWVudCBoYXMgdGhlIG1vc3QgbnVtYmVyIG9mIGVtcGxveWVlcz8gCgpgYGB7ciBtdXQwNH0KaHIwMSAlPiUKICBncm91cF9ieShzYWxlcykgJT4lCiAgdGFsbHkoKSAlPiUKICBhcnJhbmdlKC1uKQpgYGAKCihmKSBCeSBzYWxlcyBkZXBhcnRtZW50LCBjYWxjdWxhdGUgbWVhbiBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIChpKSBzYXRpc2ZhY3Rpb25fbGV2ZWwsIGFuZCAoaWkpIGxhc3RfZXZhbHVhdGlvbi4gCgpgYGB7ciBtdXQwNX0KaHIwMSAlPiUKICBncm91cF9ieShzYWxlcykgJT4lCiAgc3VtbWFyaXNlKAogICAgbWVhbi5zYXRpc2ZhY3Rpb24gPSBtZWFuKHNhdGlzZmFjdGlvbl9sZXZlbCwgbmEucm0gPSBUUlVFKSwKICAgIHNkLnNhdGlzZmFjdGlvbiA9IHNkKHNhdGlzZmFjdGlvbl9sZXZlbCwgbmEucm0gPSBUUlVFKSwKICAgIG1lYW4uZXZhbHVhdGlvbiA9IG1lYW4obGFzdF9ldmFsdWF0aW9uLCBuYS5ybSA9IFRSVUUpLAogICAgc2QuZXZhbHVhdGlvbiA9IHNkKGxhc3RfZXZhbHVhdGlvbiwgbmEucm0gPSBUUlVFKQogICkKYGBgCgooZykgV2hhdCBkZXBhcnRtZW50IGhhcyB0aGUgbG93ZXN0IG1lYW4gc2F0aXNmYWN0aW9uPyBIb3cgbXVjaCBkaWZmZXJlbmNlIGlzIHRoZXJlIGluIG1lYW4gc2F0aXNmYWN0aW9uIGJldHdlZW4gZGVwYXJ0bWVudHM/ICAKCmBgYHtyIG11dDA2fQpocjAxICU+JQogIGdyb3VwX2J5KHNhbGVzKSAlPiUKICBzdW1tYXJpc2UobWVhbi5zYXRpc2ZhY3Rpb24gPSBtZWFuKHNhdGlzZmFjdGlvbl9sZXZlbCwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShtZWFuLnNhdGlzZmFjdGlvbikKYGBgCgpBY2NvdW50aW5nIGhhcyB0aGUgbG93ZXN0IG1lYW4gc2F0aXNmYWN0aW9uIGxldmVsLiBBbGwgZGVwYXJ0bWVudHMgaGF2ZSBtZWFuIHNhdGlzZmFjdGlvbiBpbiB0aGUgMC40MDMgdG8gMC40ODIgcmFuZ2UsIHNvIHNvbWUgZGlmZmVyZW5jZSBidXQgYSBodWdlIG9uZS4gCgooaCkgQ3JlYXRlIGEgbmV3IHZhcmlhYmxlIHRoYXQgZ3JvdXBzIGF2ZXJhZ2UgbW9udGhseSBob3VycyBpbnRvIDQgZ3JvdXBzLiBZb3UgY2FuIGxldCB0aGUgZ3JvdXAgY3V0LXBvaW50cyBiZSBjaG9zZW4gYXV0b21hdGljYWxseSB3aXRoIGBjaG9wX2V2ZW5seSgpYC4gVGhlbiBzaG93IHRoZSBmcmVxdWVuY2llcyBvZiBlYWNoIGdyb3VwLgoKYGBge3IgY2hvcH0KbGlicmFyeShzYW50b2t1KQpocjAxICU+JQogIG11dGF0ZSgKICAgIGdyb3VwZWRfaG91cnMgPSBjaG9wX2V2ZW5seShhdmVyYWdlX21vbnRseV9ob3VycywgZ3JvdXBzID0gNCkKICAgICkgLT4gaHIwMQoKaHIwMSAlPiUKICBncm91cF9ieShncm91cGVkX2hvdXJzKSAlPiUKICB0YWxseSgpCmBgYAoKIyMgRXhlcmNpc2UgMgpUaGFua3MgdG8gdGhlIGZyZW5ldGljIHdvcmsgb2YgbWFueSBpbmRpdmlkdWFscywgdGhlIGdsb2JhbCBzcHJlYWQgb2YgdGhlIE5vdmVsIENvcm9uYXZpcnVzIChDT1ZJRC0xOSkgaGFzIGJlZW4gdHJhY2tlZCBhbmQgdGhlIGRhdGEgbWFkZSBhdmFpbGFibGUgZm9yIGFuYWx5c2lzLiBbWWFuY2hhbmcgWmhhb10oaHR0cHM6Ly9yZGF0YW1pbmluZy53b3JkcHJlc3MuY29tLzIwMjAvMDMvMTAvY29yb25hdmlydXMtZGF0YS1hbmFseXNpcy13aXRoLXItdGlkeXZlcnNlLWFuZC1nZ3Bsb3QyLykgaXMgb25lIHN1Y2ggaW5kaXZpZHVhbCBhbmQgZm9yIHRoaXMgZXhlcmNpc2Ugd2Ugd2lsbCB1c2UgYSBzcGNpZmljIHZlcnNpb24gb2YgaGlzIGRhdGEgdGhhdCBJIGhhdmUgbmFtZWQgYGN2ZGF0YS5SRGF0YWAgYW5kIG1hZGUgYXZhaWxhYmxlIHZpYSBTbGFjay4gTWFrZSBzdXJlIHRvIHVwbG9hZCB0aGF0IGRhdGEtc2V0IHRvIHlvdXIgUlN0dWRpbyBDbG91ZCBgZGF0YWAgZm9sZGVyLCBhbmQgdGhlbiB0byByZWFkIGl0IGluIHZpYSB0aGUgYGxvYWQoKWAgY29tbWFuZC4gV2UgY2FuIHRoZW4gYW5zd2VyIGEgZmV3IHF1ZXN0aW9ucy4gTm90ZSB0aGUgY29udGVudHM6IAoKKyBgY291bnRyeSA9YCBuYW1lIG9mIHRoZSBjb3VudHJ5IAorIGBkYXRlID1gIGRhdGUgb2YgaW5kaWRlbnRzIGFzIHJlY29yZGVkIAorIGBjb25maXJtZWQgPWAgY3VtdWxhdGl2ZSBjb3VudCBvZiB0aGUgbnVtYmVyIG9mIHBlb3BsZSB3aG8gdGVzdGVkIHBvc2l0aXZlICAKKyBgZGVhdGhzID1gIGN1bXVsYXRpdmUgY291bnQgb2YgdGhlIG51bWJlciBvZiBwZW9wbGUgbG9zdCB0byBDb3ZpZC0xOSAKKyBgZGVhdGhzID1gIGN1bXVsYXRpdmUgY291bnQgb2YgdGhlIG51bWJlciBvZiBwZW9wbGUgcmVjb3ZlcmVkICAKCmBgYHtyIGN2MDF9CmxvYWQoaGVyZSgiZGF0YSIsICJjdmRhdGEuUkRhdGEiKSkKYGBgCgooYSkgRmlsdGVyIHRoZSBkYXRhLXNldCBzbyB0aGF0IHdlIGhhdmUgb25seSBvbmUgcm93IHBlciBjb3VudHJ5LCB0aGUgZGF0YSBmcm9tIE1hcmNoIDEwLCAyMDIwIGFuZCBjYWxsIGl0IGBjdjAzMTBgLiAKCmBgYHtyIGN2MDJ9CmN2ZGF0YSAlPiUKICBmaWx0ZXIoZGF0ZSA9PSAiMjAyMC0wMy0xMCIpIC0+IGN2MDMxMApgYGAKCihiKSBIb3cgbWFueSBjb3VudHJpZXMgaGF2ZSBsb3N0IGBhdCBsZWFzdCBvbmVgIHBlcnNvbiB0byB0aGlzIHRyYWdlZHk/ICJPdGhlcnMiIHNob3VsZCBub3Qgc2hvdyB1cCBhcyBvbmUgb2YgdGhlIGNvdW50cmllcy4gIAoKYGBge3IgY3YwM30KY3YwMzEwICU+JQogIGZpbHRlcihkZWF0aHMgPj0gMSwgY291bnRyeSAhPSAiT3RoZXJzIikgJT4lCiAgY291bnQobmNvdW50cmllcyA9IG5fZGlzdGluY3QoY291bnRyeSkpCmBgYAoKKGMpIFdoYXQgMTAgY291bnRyaWVzIGhhdmUgaGFkIHRoZSBtb3N0IG51bWJlciBvZiBjb25maXJtZWQgY2FzZXM/ICJPdGhlcnMiIHNob3VsZCBub3Qgc2hvdyB1cCBhcyBvbmUgb2YgdGhlIGNvdW50cmllcy4gQWxzbyBlbnN1cmUgdGhlIHJlc3VsdCBpcyBvcmdhbml6ZWQgaW4gZGVzY2VuZGluZyBvcmRlciBvZiB0aGUgbnVtYmVyIG9mIGNvbmZpcm1lZCBjYXNlcy4gCgpgYGB7ciBjdjA0fQpjdjAzMTAgJT4lCiAgZmlsdGVyKGRlYXRocyA+PSAxLCBjb3VudHJ5ICE9ICJPdGhlcnMiKSAlPiUKICBncm91cF9ieShjb3VudHJ5KSAlPiUKICBhcnJhbmdlKC1jb25maXJtZWQpICU+JQogIHNlbGVjdChjb3VudHJ5LCBjb25maXJtZWQpICU+JQogIHVuZ3JvdXAoKSAlPiUKICB0b3BfbigxMCkKYGBgCgooZCkgQ2FsY3VsYXRlIHRoZSBgZmF0YWxpdHlfcmF0ZWAsIGRlZmluZWQgZm9yIG91ciBwdXJwb3NlcyBhcyB0aGUgcGVyY2VudCBvZiBkZWF0aHMuIGV4Y2x1ZGluZyAiT3RoZXJzIiwgYW5kIG9ubHkga2VlcGluZyBjb3VudHJpZWQgdGhhdCBoYXZlIGhhZCBgYXQgbGVhc3QgMTBgIGNvbmZpcm1lZCBjYXNlcywgYXJyYW5nZSB0aGUgcmVzdWx0IHRvIHNob3cgdGhlIHRvcC0xMCBjb3VudHJpZXMgaW4gZGVzY2VuZGluZyBvcmRlciBvZiBgZmF0YWxpdHlfcmF0ZWAuICAKCmBgYHtyIGN2MDV9CmN2MDMxMCAlPiUKICBmaWx0ZXIoY291bnRyeSAhPSAiT3RoZXJzIiwgY29uZmlybWVkID49IDEwKSAlPiUKICBtdXRhdGUoZmF0YWxpdHlfcmF0ZSA9IChkZWF0aHMgLyBjb25maXJtZWQpKjEwMCkgJT4lCiAgZ3JvdXBfYnkoY291bnRyeSkgJT4lCiAgYXJyYW5nZSgtZmF0YWxpdHlfcmF0ZSkgJT4lCiAgc2VsZWN0KGNvdW50cnksIGZhdGFsaXR5X3JhdGUpICU+JQogIHVuZ3JvdXAoKSAlPiUKICB0b3BfbigxMCkKYGBgCgooZSkgU2F5IHdlIG9ubHkgd2FudCB0byBmb2N1cyBvbiB0aGUgQmFsdGljIGNvdW50cmllcyAoRXN0b25pYSwgTGF0dmlhLCBhbmQgTGl0aHVhbmlhKSBhcyBhIHVuaWZpZWQgZ3JvdXAgYW5kIGNvbXBhcmUgdGhpcyBncm91cCB0byB0aGUgQVNFQU4gbmF0aW9ucyAoQnJ1bmVpLCBDYW1ib2RpYSwgSW5kb25lc2lhLCBMYW9zLCBNYWxheXNpYSwgTXlhbm1hciwgUGhpbGlwcGluZXMsIFNpbmdhcG9yZSwgVGhhaWxhbmQsIGFuZCBWaWV0bmFtKS4gVXNlIGBjdjAzMTBgIHRvIGNvbXBsZXRlIHRoZSBmb2xsb3duZyB0YXNrczogCgooaSkgQ3JlYXRlIGEgbmV3IHZhcmlhYmxlIGNhbGxlZCBgcmVnaW9uYCB0aGF0IG9ubHkgdGFrZXMgb24gdHdvIHZhbHVlcyAtLSAiQmFsdGljIiBpZiB0aGUgY291bnRyeSBpcyBhIEJhbHRpYyBjb3VudHJ5IGFuZCAiQXNlYW4iIGlmIHRoZSBjb3VudHJ5IGlzIGFuIEFTRUFOIGNvdW50cnkuIAooaWkpIFVzZSB0aGlzIHZhcmlhYmxlIHRvIGNhbGN1bGF0ZSB0aGUgdG90YWwgbnVtYmVyIG9mIGNvbmZpcm1lZCBjYXNlcyBpbiBlYWNoIHJlZ2lvbi4gCgpgYGB7ciBjdjA2fQpjdjAzMTAgJT4lCiAgbXV0YXRlKAogICAgcmVnaW9uID0gY2FzZV93aGVuKAogICAgICBjb3VudHJ5ICVpbiUgYygiRXN0b25pYSIsICJMYXR2aWEiLCAiTGl0aHVhbmlhIikgfiAiQmFsdGljIiwKICAgICAgY291bnRyeSAlaW4lIGMoIkJydW5laSIsICJDYW1ib2RpYSIsICJJbmRvbmVzaWEiLCAiTGFvcyIsCiAgICAgICAgICAgICAgICAgICAiTWFsYXlzaWEiLCAiTXlhbm1hciIsICJQaGlsaXBwaW5lcyIsICJTaW5nYXBvcmUiLAogICAgICAgICAgICAgICAgICAgIlRoYWlsYW5kIiwgIlZpZXRuYW0iKSB+ICJBU0VBTiIpCiAgICApICU+JQogIGdyb3VwX2J5KHJlZ2lvbikgJT4lCiAgc3VtbWFyaXNlKHRvdGFsLmNvbmZpcm1lZCA9IHN1bShjb25maXJtZWQpKQpgYGAKCg==