Split-Apply-Combine and Data Wrangling

Grouped summaries in the tidyverse

The main data-wrangling use of “split-apply-combine” is for “grouped summaries.” The best introduction to this is Grolemund and Wickham’s R for Data Science, Chapter 5, where they are introducing data transformations through the tidyverse tool dplyr. (The popularization of “split-apply-combine” as an organizing principle for data-wrangling and data analysis is due to Wickham 2014, which introduced the dplyr predecessor plyr.)

So, let’s load the tidyverse and work with that first. (Install the tidyverse package if necessary.)

library(tidyverse)

For our main running example, we’ll use the mtcars data that comes with R.

mtcars

We’ll use this to see how to calculate grouped summaries. Say we want to know the average mileage of cars within groups of 4-, 6-, and 8-cylinder cars. The main verbs are group_by and summarise.

by_cyl <- group_by(mtcars,cyl)
summarise(by_cyl,mean_mpg = mean(mpg))

The true tidyverse-ian way to do this is to use the purrr/magrittr “pipe”, %>%, to avoid creating and naming the intermediate by_cyl object (or nesting functions inside one another). Picture the thing on the left (mtcars) passing through the pipes to each transformation as it moves to the right (by default, the object/result on the left becomes the first implied argument of the function on the right):

mtcars %>% group_by(cyl) %>% summarise(mean_mpg=mean(mpg))

Grouped summaries in base R

We can accomplish the above in base R. We can get the same information, in slightly different format with:

stack(                                          # COMBINE - many ways to do this
  lapply(                                       # APPLY
    split(mtcars$mpg, f=list(cyl = mtcars$cyl)), # SPLIT
    mean  # computation to apply
) )

Note that the base R way to do this nests / composes the functions: COMBINE(APPLY(SPLIT))). But we can use the tidyverse pipe even with the base R commands to make the SPLIT->APPLY->COMBINE pipeline clearer:

split(mtcars$mpg, f=list(cyl = mtcars$cyl)) %>% # SPLIT
  lapply(mean) %>%  # APPLY
  stack # COMBINE

There are also more direct functions that do similar jobs, at least when it’s this simple. The most straightforward is the aggregate function:

aggregate(mtcars$mpg, by=list(cyl = mtcars$cyl), FUN=mean)

Grouped summaries with data.table

We can also do this operation easily with data.table, a library organized around a more computationally efficient alternative to the base R data.frame. (Install the data.table library if necessary.)

library(data.table)
mtcars.dt <- data.table(mtcars)
mtcars.dt[,mean(mpg),by=list(cyl)]

Again, note the different format for output. Consider data.table when you’re scaling up, as its memory and computation advantages become more relevant than in this tiny example.

Split-Apply-Combine and Map-Reduce

Split-Apply-Combine is also a reasonable metaphor for what’s happening in map-reduce sorts of operations.

A map operation can be thought of as replacing a type of for loop. It applies some operation, or set of operations, to every element of a vector or list. Most definitions of map functions also require the output to have the same cardinality as the output. That is, if there are 10 things being mapped, they should map to 10 things.

x <- 1:10
rt.x <- rep(NA,10)
for (i in x) {rt.x[i] <- sqrt(x[i])}
rt.x
 [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427 3.000000 3.162278

Of course this isn’t necessary. R already uses “vectorized” operations to naturally create map functions from one vector to another vector:

rt.x <- sqrt(x)
rt.x
 [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427 3.000000 3.162278

But bear with me for a bit.

The tidyverse provides a map function via purrr:

x %>% map(sqrt)
[[1]]
[1] 1

[[2]]
[1] 1.414214

[[3]]
[1] 1.732051

[[4]]
[1] 2

[[5]]
[1] 2.236068

[[6]]
[1] 2.44949

[[7]]
[1] 2.645751

[[8]]
[1] 2.828427

[[9]]
[1] 3

[[10]]
[1] 3.162278

The generic map function takes a vector or list as an input and outputs a list. There are two ways to produce a vector output, either use the map variant that outputs a vector of the desired type (in this case, map_dbl), or just unlist the list.

x %>% map_dbl(sqrt)
 [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427 3.000000 3.162278
x %>% map(sqrt) %>% unlist
 [1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427 3.000000 3.162278

The map function becomes more interesting when the function that you wish to apply is more complicated, and/or you wish to apply it over a list of more complex objects.

Let’s say we want to evaluate the idea that cars with higher weight get lower gas mileage, but we also think that this impact is lessened in cars with higher number of cylinders. So, we decide we want to estimate a regression of mpg on weight, separately for cars with 4, 6, and 8 cylinders. The map function shines at this:

mtcars %>%                            # Data about cars
  split(.$cyl) %>%                    # SPLIT into subsets by number of cylinders
  map(~ lm(mpg ~ wt, data = .x)) %>%  # APPLY - estimate a linear model of mileage on weight for each
  map(coef) %>%                       # APPLY - extract coefficients for each linear model object
  map_dbl("wt")                       # APPLY - extract the weight coefficient from each 
        4         6         8 
-5.647025 -2.780106 -2.192438 

Let’s break that down, to see what happens at each step. For some of these steps, we’ll pipe each output through the str function, which gives you a display of the internal guts of the object it is passed (similar to what we see in the “Environment” window of RStudio). First we split the data:

mtcars %>%                            # Data about cars
  split(.$cyl)   %>%                  # SPLIT into subsets by number of cylinders
  str()
List of 3
 $ 4:'data.frame':  11 obs. of  11 variables:
  ..$ mpg : num [1:11] 22.8 24.4 22.8 32.4 30.4 33.9 21.5 27.3 26 30.4 ...
  ..$ cyl : num [1:11] 4 4 4 4 4 4 4 4 4 4 ...
  ..$ disp: num [1:11] 108 146.7 140.8 78.7 75.7 ...
  ..$ hp  : num [1:11] 93 62 95 66 52 65 97 66 91 113 ...
  ..$ drat: num [1:11] 3.85 3.69 3.92 4.08 4.93 4.22 3.7 4.08 4.43 3.77 ...
  ..$ wt  : num [1:11] 2.32 3.19 3.15 2.2 1.61 ...
  ..$ qsec: num [1:11] 18.6 20 22.9 19.5 18.5 ...
  ..$ vs  : num [1:11] 1 1 1 1 1 1 1 1 0 1 ...
  ..$ am  : num [1:11] 1 0 0 1 1 1 0 1 1 1 ...
  ..$ gear: num [1:11] 4 4 4 4 4 4 3 4 5 5 ...
  ..$ carb: num [1:11] 1 2 2 1 2 1 1 1 2 2 ...
 $ 6:'data.frame':  7 obs. of  11 variables:
  ..$ mpg : num [1:7] 21 21 21.4 18.1 19.2 17.8 19.7
  ..$ cyl : num [1:7] 6 6 6 6 6 6 6
  ..$ disp: num [1:7] 160 160 258 225 168 ...
  ..$ hp  : num [1:7] 110 110 110 105 123 123 175
  ..$ drat: num [1:7] 3.9 3.9 3.08 2.76 3.92 3.92 3.62
  ..$ wt  : num [1:7] 2.62 2.88 3.21 3.46 3.44 ...
  ..$ qsec: num [1:7] 16.5 17 19.4 20.2 18.3 ...
  ..$ vs  : num [1:7] 0 0 1 1 1 1 0
  ..$ am  : num [1:7] 1 1 0 0 0 0 1
  ..$ gear: num [1:7] 4 4 3 3 4 4 5
  ..$ carb: num [1:7] 4 4 1 1 4 4 6
 $ 8:'data.frame':  14 obs. of  11 variables:
  ..$ mpg : num [1:14] 18.7 14.3 16.4 17.3 15.2 10.4 10.4 14.7 15.5 15.2 ...
  ..$ cyl : num [1:14] 8 8 8 8 8 8 8 8 8 8 ...
  ..$ disp: num [1:14] 360 360 276 276 276 ...
  ..$ hp  : num [1:14] 175 245 180 180 180 205 215 230 150 150 ...
  ..$ drat: num [1:14] 3.15 3.21 3.07 3.07 3.07 2.93 3 3.23 2.76 3.15 ...
  ..$ wt  : num [1:14] 3.44 3.57 4.07 3.73 3.78 ...
  ..$ qsec: num [1:14] 17 15.8 17.4 17.6 18 ...
  ..$ vs  : num [1:14] 0 0 0 0 0 0 0 0 0 0 ...
  ..$ am  : num [1:14] 0 0 0 0 0 0 0 0 0 0 ...
  ..$ gear: num [1:14] 3 3 3 3 3 3 3 3 3 3 ...
  ..$ carb: num [1:14] 2 4 3 3 3 4 4 4 2 2 ...

That leaves us with a list of three data.frames. Now we estimate the regression on each subset:

mtcars %>%                            # Data about cars
  split(.$cyl) %>%                    # SPLIT into subsets by number of cylinders
  map(~ lm(mpg ~ wt, data = .x))      # APPLY - estimate a linear model of mileage on weight for each
$`4`

Call:
lm(formula = mpg ~ wt, data = .x)

Coefficients:
(Intercept)           wt  
     39.571       -5.647  


$`6`

Call:
lm(formula = mpg ~ wt, data = .x)

Coefficients:
(Intercept)           wt  
      28.41        -2.78  


$`8`

Call:
lm(formula = mpg ~ wt, data = .x)

Coefficients:
(Intercept)           wt  
     23.868       -2.192  

That leaves us with a list of lm (linear model) objects. Run the coef function on each of those to access the coefficients:

mtcars %>%                            # Data about cars
  split(.$cyl) %>%                    # SPLIT into subsets by number of cylinders
  map(~ lm(mpg ~ wt, data = .x)) %>%  # APPLY - estimate a linear model of mileage on weight for each
  map(coef)                           # APPLY - extract the coefficients
$`4`
(Intercept)          wt 
  39.571196   -5.647025 

$`6`
(Intercept)          wt 
  28.408845   -2.780106 

$`8`
(Intercept)          wt 
  23.868029   -2.192438 

Now we have a list of coefficient vectors. Extract the wt element from each:

mtcars %>%                            # Data about cars
  split(.$cyl) %>%                    # SPLIT into subsets by number of cylinders
  map(~ lm(mpg ~ wt, data = .x)) %>%  # APPLY - estimate a linear model of mileage on weight for each
  map(coef) %>%                       # APPLY - extract coefficients for each linear model object
  map_dbl("wt")                       # APPLY - extract the weight coefficient from each 
        4         6         8 
-5.647025 -2.780106 -2.192438 

The map function has many more capabilities. A good place to learn about these is the “Iteration” chapter of R for Data Science.

The reduce function is used to COMBINE a list or vector of objects into one. The purrr reduce function is passed a list/vector object and a binary function, that is a function that takes, or can take, two arguments, like + or intersect. It applies this function to the first two elements of the list/vector, then to that result and the next element, then to that result and the next element, and so on. So, typically the binary function is associative and this is equivalent to applying the function to all elements simultaneously. The easiest example is + or sum, in that \(A+B+C+D+E = (((A+B)+C)+D)+E\). So, all of these produce the same answer:

x <- sum(c(1,2,3,4,5))
x
[1] 15
x <- 1+2+3+4+5
x
[1] 15
x <- `+`(`+`(`+`(`+`(1,2),3),4),5)
x
[1] 15
x <- `+`(1,2) %>% `+`(.,3) %>% `+`(.,4) %>% `+`(.,5)
x
[1] 15
x <- reduce(c(1,2,3,4,5),`+`)
x
[1] 15
x <- c(1,2,3,4,5) %>% reduce(`+`)
x
[1] 15
x <- reduce(c(1,2,3,4,5),sum)
x
[1] 15
x <- c(1,2,3,4,5) %>% reduce(sum)
x
[1] 15

So, reduce can follow map to complete a SPLIT-APPLY-COMBINE pipeline. It’s a little silly, but say we wanted to find the smallest intercept from the map pipeline above. The min function is associative – \(\text{min}(A,B,C) = \text{min}(\text{min}(A,B),C)\) – so it is a candidate for reduce.

mtcars %>%                            # Data about cars
  split(.$cyl) %>%                    # SPLIT into subsets by number of cylinders
  map(~ lm(mpg ~ wt, data = .x)) %>%  # APPLY - estimate a linear model of mileage on weight for each
  map(coef) %>%                       # APPLY - extract coefficients for each linear model object
  map_dbl("(Intercept)")  %>%         # APPLY - extract the weight coefficient from each 
  reduce(min)
[1] 23.86803

It is also worth noting that the purrr library is generally designed to make it easier to use R’s functional programming capabilities.

In R, functions are first-class objects in that they can be passed as arguments to other functions, and, in turn, R has higher-order functions that can take other functions as arguments. For example, in the block of code above, the coef function is passed as an argument to the map function, and the min function is passed as an argument to the reduce function.

R also has anonymous functions, functions that don’t have to be named. In the code block above, look at the argument passed to the first map function: ~ lm(mpg ~ wt, data = .x). That’s a function that can be read “for any given input data .x, run a linear model of its mpg variable on its weight variable.” We could be more heavy-handed about it and create namedfunction (as below), but the anonymous function allows us to define this specialized function when it’s used and then throw it away.

namedfunction <- function(x) {
  lm(mpg ~ wt, data=x)
}
mtcars %>%                            # Data about cars
  split(.$cyl) %>%                    # SPLIT into subsets by number of cylinders
  map(namedfunction) %>%              # APPLY - estimate a linear model of mileage on weight for each
  map(coef) %>%                       # APPLY - extract coefficients for each linear model object
  map_dbl("(Intercept)")  %>%         # APPLY - extract the weight coefficient from each 
  reduce(min)
[1] 23.86803

Functional programming can be an important part of data analysis at scale. The primary “purely functional” language used in data science is Haskell, while other languages are hybrids that have support for the functional programming paradigm within them – e.g., R, Python, Java, Julia, Scala, Clojure.

From map and reduce to Hadoop’s “MapReduce”

Let’s look at a “Hadoopy” MapReduce-style map-reduce operation. Let’s start with the canonical “count words.”

There are lots of ways to deal with strings, but we’ll keep within the tidyverse and use functions from stringr. Let’s use those to build a preprocessing function we’ll apply to lines and output a vector of normalized words:

cleanandsplitline <- function(line) {
  splitline <- line %>%
    str_to_lower %>%
    str_replace_all("[[:punct:]]", "") %>%
    str_replace_all("\\s\\s+","\\s")   %>%
    str_split("\\s",simplify=FALSE)     %>%
    unlist
  splitline
}
text <- "The quick brown fox jumps over the lazy dog."
text %>% cleanandsplitline
[1] "the"   "quick" "brown" "fox"   "jumps" "over"  "the"   "lazy"  "dog"  

In MapReduce, the mapper emits a key-value pair for every observation. So, we might accomplish this here by emitting a one-row dataframe for every word, the word as the key and “1” as the value. (I bind_rows here to clean up the output.)

text <- "The quick brown fox jumps over the lazy dog."
mapoutput <- text %>% cleanandsplitline %>% map(~data_frame(key=.,value=1)) %>% bind_rows
mapoutput

Hadoop then sorts these outputs by key to send to reducers which aggregate by key.

mapoutput %>% split(.$key) %>% map_dbl(~reduce(.$value,`+`))
brown   dog   fox jumps  lazy  over quick   the 
    1     1     1     1     1     1     1     2 

We can now count the frequency of words with two map-reduce operations. Let’s look at a more interesting example with lots of rows, Trump’s inaugural “State of the Union” (technically an Address to Congress).

trump <- readLines("trumpsotu2017.txt")
text <- trump[trump!=""]
wordcounts <- map(text,cleanandsplitline) %>%   # SPLIT text lines into individual words
  map(~data_frame(key=., value=1)) %>%          # MAP: APPLY map word -> <key,value> = <word,1>
  reduce(bind_rows) %>%                         # REDUCE: COMBINE into one stream of <key,value> pairs
  split(.$key) %>%                              # SPLIT (SORT) into groups by unique keys (by word)
  map_dbl(~reduce(.$value,`+`)) %>%             # MAP-REDUCE: (APPLY) sum values by key & COMBINE
  sort(decreasing=TRUE)
wordcounts[1:50] # For space, just display the top 50.
      the       and        to        of       our        we         a        in      that        is 
      231       208       150       149       116       102        94        79        63        58 
      for      will      have         i       are       all  american      with      they        on 
       57        56        50        38        37        33        33        33        31        30 
       be      this   america        by       not        as       but      from       you   country 
       28        28        27        26        26        25        24        23        23        21 
     must        us        at     their        an       its       new       who        it        so 
       20        20        19        19        18        18        18        18        17        17 
     very americans     great    people       was     world      just      more       one    united 
       16        15        15        15        15        15        14        14        14        14 

At the expense of obscuring the second map-reduce abstraction, let’s make that a little bit clearer by using our group_by and summarise verbs:

trump <- readLines("trumpsotu2017.txt")
text <- trump[trump!=""]
wordcounts <- map(text,cleanandsplitline) %>%   # SPLIT text lines into individual words
  map(~data_frame(key=., value=1)) %>%          # MAP: APPLY map word -> <key,value> = <word,1>
  reduce(bind_rows) %>%                         # REDUCE: COMBINE into one stream of <key,value> pairs
  group_by(key) %>%                             # SPLIT (SORT) into groups by unique keys (by word)
  summarise(count=sum(value)) %>%               # MAP-REDUCE: APPLY sum values by key & COMBINE
  arrange(-count)                               # just for clarity, sort by count
wordcounts[1:50,]

(And just so we’re super clear, it’s not necessary to pull back to the MapReduce level of abstraction for a problem this small on one computer.)

trump <- readLines("trumpsotu2017.txt")
text <- trump[trump!=""]
wordcounts <- text %>% 
  str_c(sep=" ",collapse=" ") %>%   # concatenate into one long line
  cleanandsplitline %>%      # clean that line and split into a word vector
  as.factor %>%              # make that a "factor" (categorical variable)
  summary(maxsum=50)      # summary will give sorted vector of counts # look at first 50
wordcounts
      the       and        to        of       our        we         a        in      that        is 
      231       208       150       149       116       102        94        79        63        58 
      for      will      have         i       are       all  american      with      they        on 
       57        56        50        38        37        33        33        33        31        30 
       be      this   america        by       not        as       but      from       you   country 
       28        28        27        26        26        25        24        23        23        21 
     must        us        at     their        an       its       new       who        it        so 
       20        20        19        19        18        18        18        18        17        17 
     very americans     great    people       was     world      just      more       one   (Other) 
       16        15        15        15        15        15        14        14        14      2771 

Now, let’s make a Hadoopy calculation of a more statistical quantity, (sample) variance.

The (unbiased) estimator for sample variance of random variable \(x\) is \[\begin{aligned} s^2 &= \frac{1}{n-1}\sum_{i=1}^{n}(x_i-\bar{x})^2\\ &= \frac{1}{n-1}\bigg(\sum_{i=1}^{n}x_i^2 - \frac{1}{n}\big(\sum_{i=1}^{n}x_i\big)^2 \bigg)\end{aligned}\]

There is a big difference computationally between the first equation and the second. The first one requires two passes through the data: one to calculate the mean, \(\bar{x}\), and then a second to calculate each observation’s squared distance from the mean. The second requires only one pass through the data, and each data point’s contribution to the calculation can be calculated without reference to the others.

So, we need three quantities: the sum of \(x_i\), the sum of \(x_i^2\), and the count of \(x_i\) (\(n\)). All of these are associative and can be map-reduced. In the word count example, the mapper converted each word in a line of text into a key-value pair of <word,1> and the “1”s were grouped by word and summed. This works similarly, except we’ll map each observed \(x_i\) into three key-value pairs: < xi, \(x_i\) >, < xi2, \(x_i^2\) >, and < n, 1 >, which will then be grouped by key and summed in the reducer.

x <- mtcars$mpg
## For reference, the answer with the variance function
var(x)
[1] 36.3241
## Calculation with map -> reduce -> group_by -> summarize
mapper <- function(x=0) {
  df <- data_frame(key=c("xi","xi2","n"), value = c(x,x^2,1))
  df
  }
varsums <- x %>%
  map(mapper) %>%
  reduce(bind_rows) %>%
  group_by(key) %>%
  summarise(sums = sum(value))
(1/(varsums$sums[1]-1))*(varsums$sums[3]-(varsums$sums[2]^2)/varsums$sums[1])
[1] 36.3241
## Calculation with map -> reduce -> split -> map(reduce) [That last step maps a reduce function]
varsums <- x %>%
  map(mapper) %>%
  reduce(bind_rows) %>%
  split(.$key) %>%
  map(~reduce(.$value,`+`))
(1/(varsums[['n']]-1))*(varsums[['xi2']]-(varsums[['xi']]^2)/varsums[['n']])
[1] 36.3241
  

Hopefully, that gives some flavor of how this sort of thing might be useful at scale.

LS0tCnRpdGxlOiAiU3BsaXQtQXBwbHktQ29tYmluZSBhbmQgTWFwLVJlZHVjZSBpbiBSIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgdGhlbWU6IHVuaXRlZAogICAgdG9jOiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwotLS0KCiMjIFNwbGl0LUFwcGx5LUNvbWJpbmUgYW5kIERhdGEgV3JhbmdsaW5nCgojIyMgR3JvdXBlZCBzdW1tYXJpZXMgaW4gdGhlIGB0aWR5dmVyc2VgCgpUaGUgbWFpbiBkYXRhLXdyYW5nbGluZyB1c2Ugb2YgInNwbGl0LWFwcGx5LWNvbWJpbmUiIGlzIGZvciAiZ3JvdXBlZCBzdW1tYXJpZXMuIiBUaGUgYmVzdCBpbnRyb2R1Y3Rpb24gdG8gdGhpcyBpcyBHcm9sZW11bmQgYW5kIFdpY2toYW0ncyBbKlIgZm9yIERhdGEgU2NpZW5jZSpdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnopLCBDaGFwdGVyIDUsIHdoZXJlIHRoZXkgYXJlIGludHJvZHVjaW5nIGRhdGEgdHJhbnNmb3JtYXRpb25zIHRocm91Z2ggdGhlIHRpZHl2ZXJzZSB0b29sICoqZHBseXIqKi4gKFRoZSBwb3B1bGFyaXphdGlvbiBvZiAic3BsaXQtYXBwbHktY29tYmluZSIgYXMgYW4gb3JnYW5pemluZyBwcmluY2lwbGUgZm9yIGRhdGEtd3JhbmdsaW5nIGFuZCBkYXRhIGFuYWx5c2lzIGlzIGR1ZSB0byBbV2lja2hhbSAyMDE0XShodHRwczovL3d3dy5qc3RhdHNvZnQub3JnL2FydGljbGUvdmlldy92MDQwaTAxKSwgd2hpY2ggaW50cm9kdWNlZCB0aGUgYGRwbHlyYCBwcmVkZWNlc3NvciBgcGx5cmAuKSAKClNvLCBsZXQncyBsb2FkIHRoZSB0aWR5dmVyc2UgYW5kIHdvcmsgd2l0aCB0aGF0IGZpcnN0LiAoSW5zdGFsbCB0aGUgdGlkeXZlcnNlIHBhY2thZ2UgaWYgbmVjZXNzYXJ5LikKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgpGb3Igb3VyIG1haW4gcnVubmluZyBleGFtcGxlLCB3ZSdsbCB1c2UgdGhlIG10Y2FycyBkYXRhIHRoYXQgY29tZXMgd2l0aCBSLgoKYGBge3J9Cm10Y2FycwpgYGAKCldlJ2xsIHVzZSB0aGlzIHRvIHNlZSBob3cgdG8gY2FsY3VsYXRlIGdyb3VwZWQgc3VtbWFyaWVzLiBTYXkgd2Ugd2FudCB0byBrbm93IHRoZSBhdmVyYWdlIG1pbGVhZ2Ugb2YgY2FycyB3aXRoaW4gZ3JvdXBzIG9mIDQtLCA2LSwgYW5kIDgtY3lsaW5kZXIgY2Fycy4gVGhlIG1haW4gdmVyYnMgYXJlIGBncm91cF9ieWAgYW5kIGBzdW1tYXJpc2VgLgoKYGBge3J9CmJ5X2N5bCA8LSBncm91cF9ieShtdGNhcnMsY3lsKQpzdW1tYXJpc2UoYnlfY3lsLG1lYW5fbXBnID0gbWVhbihtcGcpKQpgYGAKClRoZSB0cnVlIHRpZHl2ZXJzZS1pYW4gd2F5IHRvIGRvIHRoaXMgaXMgdG8gdXNlIHRoZSAqKnB1cnJyL21hZ3JpdHRyKiogInBpcGUiLCBgJT4lYCwgdG8gYXZvaWQgY3JlYXRpbmcgYW5kIG5hbWluZyB0aGUgaW50ZXJtZWRpYXRlIGBieV9jeWxgIG9iamVjdCAob3IgbmVzdGluZyBmdW5jdGlvbnMgaW5zaWRlIG9uZSBhbm90aGVyKS4gUGljdHVyZSB0aGUgdGhpbmcgb24gdGhlIGxlZnQgKG10Y2FycykgcGFzc2luZyB0aHJvdWdoIHRoZSBwaXBlcyB0byBlYWNoIHRyYW5zZm9ybWF0aW9uIGFzIGl0IG1vdmVzIHRvIHRoZSByaWdodCAoYnkgZGVmYXVsdCwgdGhlIG9iamVjdC9yZXN1bHQgb24gdGhlIGxlZnQgYmVjb21lcyB0aGUgZmlyc3QgaW1wbGllZCBhcmd1bWVudCBvZiB0aGUgZnVuY3Rpb24gb24gdGhlIHJpZ2h0KToKCmBgYHtyfQptdGNhcnMgJT4lIGdyb3VwX2J5KGN5bCkgJT4lIHN1bW1hcmlzZShtZWFuX21wZz1tZWFuKG1wZykpCmBgYAoKIyMjIEdyb3VwZWQgc3VtbWFyaWVzIGluIGJhc2UgUgoKV2UgY2FuIGFjY29tcGxpc2ggdGhlIGFib3ZlIGluIGJhc2UgUi4gV2UgY2FuIGdldCB0aGUgc2FtZSBpbmZvcm1hdGlvbiwgaW4gc2xpZ2h0bHkgZGlmZmVyZW50IGZvcm1hdCB3aXRoOgoKYGBge3J9CnN0YWNrKCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ09NQklORSAtIG1hbnkgd2F5cyB0byBkbyB0aGlzCiAgbGFwcGx5KCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQVBQTFkKICAgIHNwbGl0KG10Y2FycyRtcGcsIGY9bGlzdChjeWwgPSBtdGNhcnMkY3lsKSksICMgU1BMSVQKICAgIG1lYW4gICMgY29tcHV0YXRpb24gdG8gYXBwbHkKKSApCmBgYAoKTm90ZSB0aGF0IHRoZSBiYXNlIFIgd2F5IHRvIGRvIHRoaXMgbmVzdHMgLyBjb21wb3NlcyB0aGUgZnVuY3Rpb25zOiBDT01CSU5FKEFQUExZKFNQTElUKSkpLiBCdXQgd2UgKmNhbiogdXNlIHRoZSB0aWR5dmVyc2UgcGlwZSBldmVuIHdpdGggdGhlIGJhc2UgUiBjb21tYW5kcyB0byBtYWtlIHRoZSBTUExJVC0+QVBQTFktPkNPTUJJTkUgcGlwZWxpbmUgY2xlYXJlcjoKCmBgYHtyfQpzcGxpdChtdGNhcnMkbXBnLCBmPWxpc3QoY3lsID0gbXRjYXJzJGN5bCkpICU+JSAjIFNQTElUCiAgbGFwcGx5KG1lYW4pICU+JSAgIyBBUFBMWQogIHN0YWNrICMgQ09NQklORQpgYGAKClRoZXJlIGFyZSBhbHNvIG1vcmUgZGlyZWN0IGZ1bmN0aW9ucyB0aGF0IGRvIHNpbWlsYXIgam9icywgYXQgbGVhc3Qgd2hlbiBpdCdzIHRoaXMgc2ltcGxlLiBUaGUgbW9zdCBzdHJhaWdodGZvcndhcmQgaXMgdGhlIGBhZ2dyZWdhdGVgIGZ1bmN0aW9uOgoKYGBge3J9CmFnZ3JlZ2F0ZShtdGNhcnMkbXBnLCBieT1saXN0KGN5bCA9IG10Y2FycyRjeWwpLCBGVU49bWVhbikKYGBgCgojIyMgR3JvdXBlZCBzdW1tYXJpZXMgd2l0aCBgZGF0YS50YWJsZWAKCldlIGNhbiBhbHNvIGRvIHRoaXMgb3BlcmF0aW9uIGVhc2lseSB3aXRoICoqZGF0YS50YWJsZSoqLCBhIGxpYnJhcnkgb3JnYW5pemVkIGFyb3VuZCBhIG1vcmUgY29tcHV0YXRpb25hbGx5IGVmZmljaWVudCBhbHRlcm5hdGl2ZSB0byB0aGUgYmFzZSBSIGBkYXRhLmZyYW1lYC4gKEluc3RhbGwgdGhlIGBkYXRhLnRhYmxlYCBsaWJyYXJ5IGlmIG5lY2Vzc2FyeS4pCgpgYGB7cn0KbGlicmFyeShkYXRhLnRhYmxlKQptdGNhcnMuZHQgPC0gZGF0YS50YWJsZShtdGNhcnMpCm10Y2Fycy5kdFssbWVhbihtcGcpLGJ5PWxpc3QoY3lsKV0KYGBgCgpBZ2Fpbiwgbm90ZSB0aGUgZGlmZmVyZW50IGZvcm1hdCBmb3Igb3V0cHV0LiBDb25zaWRlciBkYXRhLnRhYmxlIHdoZW4geW91J3JlIHNjYWxpbmcgdXAsIGFzIGl0cyBtZW1vcnkgYW5kIGNvbXB1dGF0aW9uIGFkdmFudGFnZXMgYmVjb21lIG1vcmUgcmVsZXZhbnQgdGhhbiBpbiB0aGlzIHRpbnkgZXhhbXBsZS4KCiMjIFNwbGl0LUFwcGx5LUNvbWJpbmUgYW5kIE1hcC1SZWR1Y2UKClNwbGl0LUFwcGx5LUNvbWJpbmUgaXMgYWxzbyBhIHJlYXNvbmFibGUgbWV0YXBob3IgZm9yIHdoYXQncyBoYXBwZW5pbmcgaW4gbWFwLXJlZHVjZSBzb3J0cyBvZiBvcGVyYXRpb25zLgoKQSBtYXAgb3BlcmF0aW9uIGNhbiBiZSB0aG91Z2h0IG9mIGFzIHJlcGxhY2luZyBhIHR5cGUgb2YgYGZvcmAgbG9vcC4gSXQgYXBwbGllcyBzb21lIG9wZXJhdGlvbiwgb3Igc2V0IG9mIG9wZXJhdGlvbnMsIHRvIGV2ZXJ5IGVsZW1lbnQgb2YgYSB2ZWN0b3Igb3IgbGlzdC4gTW9zdCBkZWZpbml0aW9ucyBvZiBtYXAgZnVuY3Rpb25zIGFsc28gcmVxdWlyZSB0aGUgb3V0cHV0IHRvIGhhdmUgdGhlIHNhbWUgKmNhcmRpbmFsaXR5KiBhcyB0aGUgb3V0cHV0LiBUaGF0IGlzLCBpZiB0aGVyZSBhcmUgMTAgdGhpbmdzIGJlaW5nIG1hcHBlZCwgdGhleSBzaG91bGQgbWFwIHRvIDEwIHRoaW5ncy4gCgpgYGB7cn0KeCA8LSAxOjEwCnJ0LnggPC0gcmVwKE5BLDEwKQpmb3IgKGkgaW4geCkge3J0LnhbaV0gPC0gc3FydCh4W2ldKX0KcnQueApgYGAKCk9mIGNvdXJzZSB0aGlzIGlzbid0IG5lY2Vzc2FyeS4gUiBhbHJlYWR5IHVzZXMgInZlY3Rvcml6ZWQiIG9wZXJhdGlvbnMgdG8gbmF0dXJhbGx5IGNyZWF0ZSBtYXAgZnVuY3Rpb25zIGZyb20gb25lIHZlY3RvciB0byBhbm90aGVyIHZlY3RvcjoKCmBgYHtyfQpydC54IDwtIHNxcnQoeCkKcnQueApgYGAKCkJ1dCBiZWFyIHdpdGggbWUgZm9yIGEgYml0LgoKVGhlIHRpZHl2ZXJzZSBwcm92aWRlcyBhIGBtYXBgIGZ1bmN0aW9uIHZpYSAqKnB1cnJyKio6CgpgYGB7cn0KeCAlPiUgbWFwKHNxcnQpCmBgYAoKVGhlIGdlbmVyaWMgbWFwIGZ1bmN0aW9uIHRha2VzIGEgdmVjdG9yIG9yIGxpc3QgYXMgYW4gaW5wdXQgYW5kIG91dHB1dHMgYSAqbGlzdCouIFRoZXJlIGFyZSB0d28gd2F5cyB0byBwcm9kdWNlIGEgdmVjdG9yIG91dHB1dCwgZWl0aGVyIHVzZSB0aGUgbWFwIHZhcmlhbnQgdGhhdCBvdXRwdXRzIGEgdmVjdG9yIG9mIHRoZSBkZXNpcmVkIHR5cGUgKGluIHRoaXMgY2FzZSwgYG1hcF9kYmxgKSwgb3IganVzdCBgdW5saXN0YCB0aGUgbGlzdC4KCmBgYHtyfQp4ICU+JSBtYXBfZGJsKHNxcnQpCmBgYAoKYGBge3J9CnggJT4lIG1hcChzcXJ0KSAlPiUgdW5saXN0CmBgYAoKVGhlIG1hcCBmdW5jdGlvbiBiZWNvbWVzIG1vcmUgaW50ZXJlc3Rpbmcgd2hlbiB0aGUgZnVuY3Rpb24gdGhhdCB5b3Ugd2lzaCB0byBhcHBseSBpcyBtb3JlIGNvbXBsaWNhdGVkLCBhbmQvb3IgeW91IHdpc2ggdG8gYXBwbHkgaXQgb3ZlciBhIGxpc3Qgb2YgbW9yZSBjb21wbGV4IG9iamVjdHMuCgpMZXQncyBzYXkgd2Ugd2FudCB0byBldmFsdWF0ZSB0aGUgaWRlYSB0aGF0IGNhcnMgd2l0aCBoaWdoZXIgd2VpZ2h0IGdldCBsb3dlciBnYXMgbWlsZWFnZSwgYnV0IHdlIGFsc28gdGhpbmsgdGhhdCB0aGlzIGltcGFjdCBpcyBsZXNzZW5lZCBpbiBjYXJzIHdpdGggaGlnaGVyIG51bWJlciBvZiBjeWxpbmRlcnMuIFNvLCB3ZSBkZWNpZGUgd2Ugd2FudCB0byBlc3RpbWF0ZSBhIHJlZ3Jlc3Npb24gb2YgbXBnIG9uIHdlaWdodCwgc2VwYXJhdGVseSBmb3IgY2FycyB3aXRoIDQsIDYsIGFuZCA4IGN5bGluZGVycy4gVGhlIGBtYXBgIGZ1bmN0aW9uIHNoaW5lcyBhdCB0aGlzOgoKYGBge3J9Cm10Y2FycyAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEYXRhIGFib3V0IGNhcnMKICBzcGxpdCguJGN5bCkgJT4lICAgICAgICAgICAgICAgICAgICAjIFNQTElUIGludG8gc3Vic2V0cyBieSBudW1iZXIgb2YgY3lsaW5kZXJzCiAgbWFwKH4gbG0obXBnIH4gd3QsIGRhdGEgPSAueCkpICU+JSAgIyBBUFBMWSAtIGVzdGltYXRlIGEgbGluZWFyIG1vZGVsIG9mIG1pbGVhZ2Ugb24gd2VpZ2h0IGZvciBlYWNoCiAgbWFwKGNvZWYpICU+JSAgICAgICAgICAgICAgICAgICAgICAgIyBBUFBMWSAtIGV4dHJhY3QgY29lZmZpY2llbnRzIGZvciBlYWNoIGxpbmVhciBtb2RlbCBvYmplY3QKICBtYXBfZGJsKCJ3dCIpICAgICAgICAgICAgICAgICAgICAgICAjIEFQUExZIC0gZXh0cmFjdCB0aGUgd2VpZ2h0IGNvZWZmaWNpZW50IGZyb20gZWFjaCAKYGBgCgpMZXQncyBicmVhayB0aGF0IGRvd24sIHRvIHNlZSB3aGF0IGhhcHBlbnMgYXQgZWFjaCBzdGVwLiBGb3Igc29tZSBvZiB0aGVzZSBzdGVwcywgd2UnbGwgcGlwZSBlYWNoIG91dHB1dCB0aHJvdWdoIHRoZSBgc3RyYCBmdW5jdGlvbiwgd2hpY2ggZ2l2ZXMgeW91IGEgZGlzcGxheSBvZiB0aGUgaW50ZXJuYWwgZ3V0cyBvZiB0aGUgb2JqZWN0IGl0IGlzIHBhc3NlZCAoc2ltaWxhciB0byB3aGF0IHdlIHNlZSBpbiB0aGUgIkVudmlyb25tZW50IiB3aW5kb3cgb2YgUlN0dWRpbykuIEZpcnN0IHdlIGBzcGxpdGAgdGhlIGRhdGE6CgpgYGB7cn0KbXRjYXJzICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhdGEgYWJvdXQgY2FycwogIHNwbGl0KC4kY3lsKSAgICU+JSAgICAgICAgICAgICAgICAgICMgU1BMSVQgaW50byBzdWJzZXRzIGJ5IG51bWJlciBvZiBjeWxpbmRlcnMKICBzdHIoKQpgYGAKClRoYXQgbGVhdmVzIHVzIHdpdGggYSBsaXN0IG9mIHRocmVlIGRhdGEuZnJhbWVzLiBOb3cgd2UgZXN0aW1hdGUgdGhlIHJlZ3Jlc3Npb24gb24gZWFjaCBzdWJzZXQ6CgpgYGB7cn0KbXRjYXJzICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhdGEgYWJvdXQgY2FycwogIHNwbGl0KC4kY3lsKSAlPiUgICAgICAgICAgICAgICAgICAgICMgU1BMSVQgaW50byBzdWJzZXRzIGJ5IG51bWJlciBvZiBjeWxpbmRlcnMKICBtYXAofiBsbShtcGcgfiB3dCwgZGF0YSA9IC54KSkgICAgICAjIEFQUExZIC0gZXN0aW1hdGUgYSBsaW5lYXIgbW9kZWwgb2YgbWlsZWFnZSBvbiB3ZWlnaHQgZm9yIGVhY2gKYGBgCgpUaGF0IGxlYXZlcyB1cyB3aXRoIGEgbGlzdCBvZiBgbG1gIChsaW5lYXIgbW9kZWwpIG9iamVjdHMuIFJ1biB0aGUgYGNvZWZgIGZ1bmN0aW9uIG9uIGVhY2ggb2YgdGhvc2UgdG8gYWNjZXNzIHRoZSBjb2VmZmljaWVudHM6CgpgYGB7cn0KbXRjYXJzICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhdGEgYWJvdXQgY2FycwogIHNwbGl0KC4kY3lsKSAlPiUgICAgICAgICAgICAgICAgICAgICMgU1BMSVQgaW50byBzdWJzZXRzIGJ5IG51bWJlciBvZiBjeWxpbmRlcnMKICBtYXAofiBsbShtcGcgfiB3dCwgZGF0YSA9IC54KSkgJT4lICAjIEFQUExZIC0gZXN0aW1hdGUgYSBsaW5lYXIgbW9kZWwgb2YgbWlsZWFnZSBvbiB3ZWlnaHQgZm9yIGVhY2gKICBtYXAoY29lZikgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEFQUExZIC0gZXh0cmFjdCB0aGUgY29lZmZpY2llbnRzCmBgYAoKTm93IHdlIGhhdmUgYSBsaXN0IG9mIGNvZWZmaWNpZW50IHZlY3RvcnMuIEV4dHJhY3QgdGhlIGB3dGAgZWxlbWVudCBmcm9tIGVhY2g6CgpgYGB7cn0KbXRjYXJzICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERhdGEgYWJvdXQgY2FycwogIHNwbGl0KC4kY3lsKSAlPiUgICAgICAgICAgICAgICAgICAgICMgU1BMSVQgaW50byBzdWJzZXRzIGJ5IG51bWJlciBvZiBjeWxpbmRlcnMKICBtYXAofiBsbShtcGcgfiB3dCwgZGF0YSA9IC54KSkgJT4lICAjIEFQUExZIC0gZXN0aW1hdGUgYSBsaW5lYXIgbW9kZWwgb2YgbWlsZWFnZSBvbiB3ZWlnaHQgZm9yIGVhY2gKICBtYXAoY29lZikgJT4lICAgICAgICAgICAgICAgICAgICAgICAjIEFQUExZIC0gZXh0cmFjdCBjb2VmZmljaWVudHMgZm9yIGVhY2ggbGluZWFyIG1vZGVsIG9iamVjdAogIG1hcF9kYmwoInd0IikgICAgICAgICAgICAgICAgICAgICAgICMgQVBQTFkgLSBleHRyYWN0IHRoZSB3ZWlnaHQgY29lZmZpY2llbnQgZnJvbSBlYWNoIApgYGAKClRoZSBgbWFwYCBmdW5jdGlvbiBoYXMgbWFueSBtb3JlIGNhcGFiaWxpdGllcy4gQSBnb29kIHBsYWNlIHRvIGxlYXJuIGFib3V0IHRoZXNlIGlzIHRoZSAiSXRlcmF0aW9uIiBjaGFwdGVyIG9mICpSIGZvciBEYXRhIFNjaWVuY2UqLgoKVGhlIGByZWR1Y2VgIGZ1bmN0aW9uIGlzIHVzZWQgdG8gQ09NQklORSBhIGxpc3Qgb3IgdmVjdG9yIG9mIG9iamVjdHMgaW50byBvbmUuIFRoZSBgcHVycnJgIGByZWR1Y2VgIGZ1bmN0aW9uIGlzIHBhc3NlZCBhIGxpc3QvdmVjdG9yIG9iamVjdCBhbmQgYSAqYmluYXJ5KiBmdW5jdGlvbiwgdGhhdCBpcyBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMsIG9yIGNhbiB0YWtlLCB0d28gYXJndW1lbnRzLCBsaWtlIGArYCBvciBgaW50ZXJzZWN0YC4gSXQgYXBwbGllcyB0aGlzIGZ1bmN0aW9uIHRvIHRoZSBmaXJzdCB0d28gZWxlbWVudHMgb2YgdGhlIGxpc3QvdmVjdG9yLCB0aGVuIHRvIHRoYXQgcmVzdWx0IGFuZCB0aGUgbmV4dCBlbGVtZW50LCB0aGVuIHRvIHRoYXQgcmVzdWx0IGFuZCB0aGUgbmV4dCBlbGVtZW50LCBhbmQgc28gb24uIFNvLCB0eXBpY2FsbHkgdGhlIGJpbmFyeSBmdW5jdGlvbiBpcyAqYXNzb2NpYXRpdmUqIGFuZCB0aGlzIGlzIGVxdWl2YWxlbnQgdG8gYXBwbHlpbmcgdGhlIGZ1bmN0aW9uIHRvIGFsbCBlbGVtZW50cyBzaW11bHRhbmVvdXNseS4gVGhlIGVhc2llc3QgZXhhbXBsZSBpcyBgK2Agb3IgYHN1bWAsIGluIHRoYXQgJEErQitDK0QrRSA9ICgoKEErQikrQykrRCkrRSQuIFNvLCBhbGwgb2YgdGhlc2UgcHJvZHVjZSB0aGUgc2FtZSBhbnN3ZXI6CgpgYGB7cn0KeCA8LSBzdW0oYygxLDIsMyw0LDUpKQp4CnggPC0gMSsyKzMrNCs1CngKeCA8LSBgK2AoYCtgKGArYChgK2AoMSwyKSwzKSw0KSw1KQp4CnggPC0gYCtgKDEsMikgJT4lIGArYCguLDMpICU+JSBgK2AoLiw0KSAlPiUgYCtgKC4sNSkKeAp4IDwtIHJlZHVjZShjKDEsMiwzLDQsNSksYCtgKQp4CnggPC0gYygxLDIsMyw0LDUpICU+JSByZWR1Y2UoYCtgKQp4CnggPC0gcmVkdWNlKGMoMSwyLDMsNCw1KSxzdW0pCngKeCA8LSBjKDEsMiwzLDQsNSkgJT4lIHJlZHVjZShzdW0pCngKYGBgCgpTbywgYHJlZHVjZWAgY2FuIGZvbGxvdyBgbWFwYCB0byBjb21wbGV0ZSBhIFNQTElULUFQUExZLUNPTUJJTkUgcGlwZWxpbmUuIEl0J3MgYSBsaXR0bGUgc2lsbHksIGJ1dCBzYXkgd2Ugd2FudGVkIHRvIGZpbmQgdGhlIHNtYWxsZXN0IGludGVyY2VwdCBmcm9tIHRoZSBtYXAgcGlwZWxpbmUgYWJvdmUuIFRoZSBgbWluYCBmdW5jdGlvbiBpcyBhc3NvY2lhdGl2ZSAtLSAkXHRleHR7bWlufShBLEIsQykgPSBcdGV4dHttaW59KFx0ZXh0e21pbn0oQSxCKSxDKSQgLS0gc28gaXQgaXMgYSBjYW5kaWRhdGUgZm9yIGByZWR1Y2VgLgoKYGBge3J9Cm10Y2FycyAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEYXRhIGFib3V0IGNhcnMKICBzcGxpdCguJGN5bCkgJT4lICAgICAgICAgICAgICAgICAgICAjIFNQTElUIGludG8gc3Vic2V0cyBieSBudW1iZXIgb2YgY3lsaW5kZXJzCiAgbWFwKH4gbG0obXBnIH4gd3QsIGRhdGEgPSAueCkpICU+JSAgIyBBUFBMWSAtIGVzdGltYXRlIGEgbGluZWFyIG1vZGVsIG9mIG1pbGVhZ2Ugb24gd2VpZ2h0IGZvciBlYWNoCiAgbWFwKGNvZWYpICU+JSAgICAgICAgICAgICAgICAgICAgICAgIyBBUFBMWSAtIGV4dHJhY3QgY29lZmZpY2llbnRzIGZvciBlYWNoIGxpbmVhciBtb2RlbCBvYmplY3QKICBtYXBfZGJsKCIoSW50ZXJjZXB0KSIpICAlPiUgICAgICAgICAjIEFQUExZIC0gZXh0cmFjdCB0aGUgd2VpZ2h0IGNvZWZmaWNpZW50IGZyb20gZWFjaCAKICByZWR1Y2UobWluKQpgYGAKCkl0IGlzIGFsc28gd29ydGggbm90aW5nIHRoYXQgdGhlIGBwdXJycmAgbGlicmFyeSBpcyBnZW5lcmFsbHkgZGVzaWduZWQgdG8gbWFrZSBpdCBlYXNpZXIgdG8gdXNlIFIncyAqKmZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcqKiBjYXBhYmlsaXRpZXMuCgpJbiBSLCBmdW5jdGlvbnMgYXJlICoqZmlyc3QtY2xhc3Mgb2JqZWN0cyoqIGluIHRoYXQgdGhleSBjYW4gYmUgcGFzc2VkIGFzIGFyZ3VtZW50cyB0byBvdGhlciBmdW5jdGlvbnMsIGFuZCwgaW4gdHVybiwgUiBoYXMgKipoaWdoZXItb3JkZXIgZnVuY3Rpb25zKiogdGhhdCBjYW4gdGFrZSBvdGhlciBmdW5jdGlvbnMgYXMgYXJndW1lbnRzLiBGb3IgZXhhbXBsZSwgaW4gdGhlIGJsb2NrIG9mIGNvZGUgYWJvdmUsIHRoZSBgY29lZmAgZnVuY3Rpb24gaXMgcGFzc2VkIGFzIGFuIGFyZ3VtZW50IHRvIHRoZSBgbWFwYCBmdW5jdGlvbiwgYW5kIHRoZSBgbWluYCBmdW5jdGlvbiBpcyBwYXNzZWQgYXMgYW4gYXJndW1lbnQgdG8gdGhlIGByZWR1Y2VgIGZ1bmN0aW9uLgoKUiBhbHNvIGhhcyAqKmFub255bW91cyBmdW5jdGlvbnMqKiwgZnVuY3Rpb25zIHRoYXQgZG9uJ3QgaGF2ZSB0byBiZSBuYW1lZC4gSW4gdGhlIGNvZGUgYmxvY2sgYWJvdmUsIGxvb2sgYXQgdGhlIGFyZ3VtZW50IHBhc3NlZCB0byB0aGUgZmlyc3QgbWFwIGZ1bmN0aW9uOiBgfiBsbShtcGcgfiB3dCwgZGF0YSA9IC54KWAuIFRoYXQncyBhIGZ1bmN0aW9uIHRoYXQgY2FuIGJlIHJlYWQgImZvciBhbnkgZ2l2ZW4gaW5wdXQgZGF0YSBgLnhgLCBydW4gYSBsaW5lYXIgbW9kZWwgb2YgaXRzIGBtcGdgIHZhcmlhYmxlIG9uIGl0cyBgd2VpZ2h0YCB2YXJpYWJsZS4iIFdlIGNvdWxkIGJlIG1vcmUgaGVhdnktaGFuZGVkIGFib3V0IGl0IGFuZCBjcmVhdGUgYG5hbWVkZnVuY3Rpb25gIChhcyBiZWxvdyksIGJ1dCB0aGUgYW5vbnltb3VzIGZ1bmN0aW9uIGFsbG93cyB1cyB0byBkZWZpbmUgdGhpcyBzcGVjaWFsaXplZCBmdW5jdGlvbiB3aGVuIGl0J3MgdXNlZCBhbmQgdGhlbiB0aHJvdyBpdCBhd2F5LgoKYGBge3J9Cm5hbWVkZnVuY3Rpb24gPC0gZnVuY3Rpb24oeCkgewogIGxtKG1wZyB+IHd0LCBkYXRhPXgpCn0KCm10Y2FycyAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEYXRhIGFib3V0IGNhcnMKICBzcGxpdCguJGN5bCkgJT4lICAgICAgICAgICAgICAgICAgICAjIFNQTElUIGludG8gc3Vic2V0cyBieSBudW1iZXIgb2YgY3lsaW5kZXJzCiAgbWFwKG5hbWVkZnVuY3Rpb24pICU+JSAgICAgICAgICAgICAgIyBBUFBMWSAtIGVzdGltYXRlIGEgbGluZWFyIG1vZGVsIG9mIG1pbGVhZ2Ugb24gd2VpZ2h0IGZvciBlYWNoCiAgbWFwKGNvZWYpICU+JSAgICAgICAgICAgICAgICAgICAgICAgIyBBUFBMWSAtIGV4dHJhY3QgY29lZmZpY2llbnRzIGZvciBlYWNoIGxpbmVhciBtb2RlbCBvYmplY3QKICBtYXBfZGJsKCIoSW50ZXJjZXB0KSIpICAlPiUgICAgICAgICAjIEFQUExZIC0gZXh0cmFjdCB0aGUgd2VpZ2h0IGNvZWZmaWNpZW50IGZyb20gZWFjaCAKICByZWR1Y2UobWluKQpgYGAKCkZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcgY2FuIGJlIGFuIGltcG9ydGFudCBwYXJ0IG9mIGRhdGEgYW5hbHlzaXMgYXQgc2NhbGUuIFRoZSBwcmltYXJ5ICJwdXJlbHkgZnVuY3Rpb25hbCIgbGFuZ3VhZ2UgdXNlZCBpbiBkYXRhIHNjaWVuY2UgaXMgSGFza2VsbCwgd2hpbGUgb3RoZXIgbGFuZ3VhZ2VzIGFyZSBoeWJyaWRzIHRoYXQgaGF2ZSBzdXBwb3J0IGZvciB0aGUgZnVuY3Rpb25hbCBwcm9ncmFtbWluZyBwYXJhZGlnbSB3aXRoaW4gdGhlbSAtLSBlLmcuLCBSLCBQeXRob24sIEphdmEsIEp1bGlhLCBTY2FsYSwgQ2xvanVyZS4KCiMjIEZyb20gYG1hcGAgYW5kIGByZWR1Y2VgIHRvIEhhZG9vcCdzICJNYXBSZWR1Y2UiCgpMZXQncyBsb29rIGF0IGEgIkhhZG9vcHkiIE1hcFJlZHVjZS1zdHlsZSBtYXAtcmVkdWNlIG9wZXJhdGlvbi4gTGV0J3Mgc3RhcnQgd2l0aCB0aGUgY2Fub25pY2FsICJjb3VudCB3b3Jkcy4iCgpUaGVyZSBhcmUgbG90cyBvZiB3YXlzIHRvIGRlYWwgd2l0aCBzdHJpbmdzLCBidXQgd2UnbGwga2VlcCB3aXRoaW4gdGhlIHRpZHl2ZXJzZSBhbmQgdXNlIGZ1bmN0aW9ucyBmcm9tICoqc3RyaW5ncioqLiBMZXQncyB1c2UgdGhvc2UgdG8gYnVpbGQgYSBwcmVwcm9jZXNzaW5nIGZ1bmN0aW9uIHdlJ2xsIGFwcGx5IHRvIGxpbmVzIGFuZCBvdXRwdXQgYSB2ZWN0b3Igb2Ygbm9ybWFsaXplZCB3b3JkczoKCmBgYHtyfQpjbGVhbmFuZHNwbGl0bGluZSA8LSBmdW5jdGlvbihsaW5lKSB7CiAgc3BsaXRsaW5lIDwtIGxpbmUgJT4lCiAgICBzdHJfdG9fbG93ZXIgJT4lCiAgICBzdHJfcmVwbGFjZV9hbGwoIltbOnB1bmN0Ol1dIiwgIiIpICU+JQogICAgc3RyX3JlcGxhY2VfYWxsKCJcXHNcXHMrIiwiXFxzIikgICAlPiUKICAgIHN0cl9zcGxpdCgiXFxzIixzaW1wbGlmeT1GQUxTRSkgICAgICU+JQogICAgdW5saXN0CiAgc3BsaXRsaW5lCn0KdGV4dCA8LSAiVGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4iCnRleHQgJT4lIGNsZWFuYW5kc3BsaXRsaW5lCmBgYAoKSW4gTWFwUmVkdWNlLCB0aGUgbWFwcGVyIGVtaXRzIGEga2V5LXZhbHVlIHBhaXIgZm9yIGV2ZXJ5IG9ic2VydmF0aW9uLiBTbywgd2UgbWlnaHQgYWNjb21wbGlzaCB0aGlzIGhlcmUgYnkgZW1pdHRpbmcgYSBvbmUtcm93IGRhdGFmcmFtZSBmb3IgZXZlcnkgd29yZCwgdGhlIHdvcmQgYXMgdGhlIGtleSBhbmQgIjEiIGFzIHRoZSB2YWx1ZS4gKEkgYGJpbmRfcm93c2AgaGVyZSB0byBjbGVhbiB1cCB0aGUgb3V0cHV0LikgCmBgYHtyfQp0ZXh0IDwtICJUaGUgcXVpY2sgYnJvd24gZm94IGp1bXBzIG92ZXIgdGhlIGxhenkgZG9nLiIKbWFwb3V0cHV0IDwtIHRleHQgJT4lIGNsZWFuYW5kc3BsaXRsaW5lICU+JSBtYXAofmRhdGFfZnJhbWUoa2V5PS4sdmFsdWU9MSkpICU+JSBiaW5kX3Jvd3MKbWFwb3V0cHV0CmBgYAoKSGFkb29wIHRoZW4gc29ydHMgdGhlc2Ugb3V0cHV0cyBieSBrZXkgdG8gc2VuZCB0byByZWR1Y2VycyB3aGljaCBhZ2dyZWdhdGUgYnkga2V5LiAKCmBgYHtyfQptYXBvdXRwdXQgJT4lIHNwbGl0KC4ka2V5KSAlPiUgbWFwX2RibCh+cmVkdWNlKC4kdmFsdWUsYCtgKSkKYGBgCgpXZSBjYW4gbm93IGNvdW50IHRoZSBmcmVxdWVuY3kgb2Ygd29yZHMgd2l0aCB0d28gbWFwLXJlZHVjZSBvcGVyYXRpb25zLiBMZXQncyBsb29rIGF0IGEgbW9yZSBpbnRlcmVzdGluZyBleGFtcGxlIHdpdGggbG90cyBvZiByb3dzLCBUcnVtcCdzIGluYXVndXJhbCAiU3RhdGUgb2YgdGhlIFVuaW9uIiAodGVjaG5pY2FsbHkgYW4gQWRkcmVzcyB0byBDb25ncmVzcykuCgpgYGB7cn0KdHJ1bXAgPC0gcmVhZExpbmVzKCJ0cnVtcHNvdHUyMDE3LnR4dCIpCnRleHQgPC0gdHJ1bXBbdHJ1bXAhPSIiXQp3b3JkY291bnRzIDwtIG1hcCh0ZXh0LGNsZWFuYW5kc3BsaXRsaW5lKSAlPiUgICAjIFNQTElUIHRleHQgbGluZXMgaW50byBpbmRpdmlkdWFsIHdvcmRzCiAgbWFwKH5kYXRhX2ZyYW1lKGtleT0uLCB2YWx1ZT0xKSkgJT4lICAgICAgICAgICMgTUFQOiBBUFBMWSBtYXAgd29yZCAtPiA8a2V5LHZhbHVlPiA9IDx3b3JkLDE+CiAgcmVkdWNlKGJpbmRfcm93cykgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICMgUkVEVUNFOiBDT01CSU5FIGludG8gb25lIHN0cmVhbSBvZiA8a2V5LHZhbHVlPiBwYWlycwogIHNwbGl0KC4ka2V5KSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNQTElUIChTT1JUKSBpbnRvIGdyb3VwcyBieSB1bmlxdWUga2V5cyAoYnkgd29yZCkKICBtYXBfZGJsKH5yZWR1Y2UoLiR2YWx1ZSxgK2ApKSAlPiUgICAgICAgICAgICAgIyBNQVAtUkVEVUNFOiAoQVBQTFkpIHN1bSB2YWx1ZXMgYnkga2V5ICYgQ09NQklORQogIHNvcnQoZGVjcmVhc2luZz1UUlVFKQp3b3JkY291bnRzWzE6NTBdICMgRm9yIHNwYWNlLCBqdXN0IGRpc3BsYXkgdGhlIHRvcCA1MC4KYGBgCgpBdCB0aGUgZXhwZW5zZSBvZiBvYnNjdXJpbmcgdGhlIHNlY29uZCBtYXAtcmVkdWNlIGFic3RyYWN0aW9uLCBsZXQncyBtYWtlIHRoYXQgYSBsaXR0bGUgYml0IGNsZWFyZXIgYnkgdXNpbmcgb3VyIGBncm91cF9ieWAgYW5kIGBzdW1tYXJpc2VgIHZlcmJzOgoKYGBge3J9CnRydW1wIDwtIHJlYWRMaW5lcygidHJ1bXBzb3R1MjAxNy50eHQiKQp0ZXh0IDwtIHRydW1wW3RydW1wIT0iIl0Kd29yZGNvdW50cyA8LSBtYXAodGV4dCxjbGVhbmFuZHNwbGl0bGluZSkgJT4lICAgIyBTUExJVCB0ZXh0IGxpbmVzIGludG8gaW5kaXZpZHVhbCB3b3JkcwogIG1hcCh+ZGF0YV9mcmFtZShrZXk9LiwgdmFsdWU9MSkpICU+JSAgICAgICAgICAjIE1BUDogQVBQTFkgbWFwIHdvcmQgLT4gPGtleSx2YWx1ZT4gPSA8d29yZCwxPgogIHJlZHVjZShiaW5kX3Jvd3MpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAjIFJFRFVDRTogQ09NQklORSBpbnRvIG9uZSBzdHJlYW0gb2YgPGtleSx2YWx1ZT4gcGFpcnMKICBncm91cF9ieShrZXkpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTUExJVCAoU09SVCkgaW50byBncm91cHMgYnkgdW5pcXVlIGtleXMgKGJ5IHdvcmQpCiAgc3VtbWFyaXNlKGNvdW50PXN1bSh2YWx1ZSkpICU+JSAgICAgICAgICAgICAgICMgTUFQLVJFRFVDRTogQVBQTFkgc3VtIHZhbHVlcyBieSBrZXkgJiBDT01CSU5FCiAgYXJyYW5nZSgtY291bnQpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMganVzdCBmb3IgY2xhcml0eSwgc29ydCBieSBjb3VudAp3b3JkY291bnRzWzE6NTAsXQpgYGAKCihBbmQganVzdCBzbyB3ZSdyZSBzdXBlciBjbGVhciwgaXQncyBub3QgbmVjZXNzYXJ5IHRvIHB1bGwgYmFjayB0byB0aGUgTWFwUmVkdWNlIGxldmVsIG9mIGFic3RyYWN0aW9uIGZvciBhIHByb2JsZW0gdGhpcyBzbWFsbCBvbiBvbmUgY29tcHV0ZXIuKQoKYGBge3J9CnRydW1wIDwtIHJlYWRMaW5lcygidHJ1bXBzb3R1MjAxNy50eHQiKQp0ZXh0IDwtIHRydW1wW3RydW1wIT0iIl0Kd29yZGNvdW50cyA8LSB0ZXh0ICU+JSAKICBzdHJfYyhzZXA9IiAiLGNvbGxhcHNlPSIgIikgJT4lICAgIyBjb25jYXRlbmF0ZSBpbnRvIG9uZSBsb25nIGxpbmUKICBjbGVhbmFuZHNwbGl0bGluZSAlPiUgICAgICAjIGNsZWFuIHRoYXQgbGluZSBhbmQgc3BsaXQgaW50byBhIHdvcmQgdmVjdG9yCiAgYXMuZmFjdG9yICU+JSAgICAgICAgICAgICAgIyBtYWtlIHRoYXQgYSAiZmFjdG9yIiAoY2F0ZWdvcmljYWwgdmFyaWFibGUpCiAgc3VtbWFyeShtYXhzdW09NTApICAgICAgIyBzdW1tYXJ5IHdpbGwgZ2l2ZSBzb3J0ZWQgdmVjdG9yIG9mIGNvdW50cyAjIGxvb2sgYXQgZmlyc3QgNTAKd29yZGNvdW50cwpgYGAKCk5vdywgbGV0J3MgbWFrZSBhIEhhZG9vcHkgY2FsY3VsYXRpb24gb2YgYSBtb3JlIHN0YXRpc3RpY2FsIHF1YW50aXR5LCAoc2FtcGxlKSB2YXJpYW5jZS4KClRoZSAodW5iaWFzZWQpIGVzdGltYXRvciBmb3Igc2FtcGxlIHZhcmlhbmNlIG9mIHJhbmRvbSB2YXJpYWJsZSAkeCQgaXMgJCRcYmVnaW57YWxpZ25lZH0Kc14yICY9IFxmcmFjezF9e24tMX1cc3VtX3tpPTF9XntufSh4X2ktXGJhcnt4fSleMlxcICY9IFxmcmFjezF9e24tMX1cYmlnZyhcc3VtX3tpPTF9XntufXhfaV4yIC0gXGZyYWN7MX17bn1cYmlnKFxzdW1fe2k9MX1ee259eF9pXGJpZyleMiBcYmlnZylcZW5ke2FsaWduZWR9JCQKClRoZXJlIGlzIGEgYmlnIGRpZmZlcmVuY2UgY29tcHV0YXRpb25hbGx5IGJldHdlZW4gdGhlIGZpcnN0IGVxdWF0aW9uIGFuZCB0aGUgc2Vjb25kLiBUaGUgZmlyc3Qgb25lIHJlcXVpcmVzIHR3byBwYXNzZXMgdGhyb3VnaCB0aGUgZGF0YTogb25lIHRvIGNhbGN1bGF0ZSB0aGUgbWVhbiwgJFxiYXJ7eH0kLCBhbmQgdGhlbiBhIHNlY29uZCB0byBjYWxjdWxhdGUgZWFjaCBvYnNlcnZhdGlvbidzIHNxdWFyZWQgZGlzdGFuY2UgZnJvbSB0aGUgbWVhbi4gVGhlIHNlY29uZCByZXF1aXJlcyBvbmx5IG9uZSBwYXNzIHRocm91Z2ggdGhlIGRhdGEsIGFuZCBlYWNoIGRhdGEgcG9pbnQncyBjb250cmlidXRpb24gdG8gdGhlIGNhbGN1bGF0aW9uIGNhbiBiZSBjYWxjdWxhdGVkIHdpdGhvdXQgcmVmZXJlbmNlIHRvIHRoZSBvdGhlcnMuCgpTbywgd2UgbmVlZCB0aHJlZSBxdWFudGl0aWVzOiB0aGUgc3VtIG9mICR4X2kkLCB0aGUgc3VtIG9mICR4X2leMiQsIGFuZCB0aGUgY291bnQgb2YgJHhfaSQgKCRuJCkuIEFsbCBvZiB0aGVzZSBhcmUgYXNzb2NpYXRpdmUgYW5kIGNhbiBiZSBtYXAtcmVkdWNlZC4gSW4gdGhlIHdvcmQgY291bnQgZXhhbXBsZSwgdGhlIG1hcHBlciBjb252ZXJ0ZWQgZWFjaCB3b3JkIGluIGEgbGluZSBvZiB0ZXh0IGludG8gYSBrZXktdmFsdWUgcGFpciBvZiBcPHdvcmQsMVw+IGFuZCB0aGUgIjEicyB3ZXJlIGdyb3VwZWQgYnkgd29yZCBhbmQgc3VtbWVkLiBUaGlzIHdvcmtzIHNpbWlsYXJseSwgZXhjZXB0IHdlJ2xsIG1hcCBlYWNoIG9ic2VydmVkICR4X2kkIGludG8gdGhyZWUga2V5LXZhbHVlIHBhaXJzOiBcPCB4aSwgJHhfaSQgXD4sIFw8IHhpMiwgJHhfaV4yJCBcPiwgYW5kIFw8IG4sIDEgXD4sIHdoaWNoIHdpbGwgdGhlbiBiZSBncm91cGVkIGJ5IGtleSBhbmQgc3VtbWVkIGluIHRoZSByZWR1Y2VyLgoKYGBge3J9Cgp4IDwtIG10Y2FycyRtcGcKCiMjIEZvciByZWZlcmVuY2UsIHRoZSBhbnN3ZXIgd2l0aCB0aGUgdmFyaWFuY2UgZnVuY3Rpb24KdmFyKHgpCgojIyBDYWxjdWxhdGlvbiB3aXRoIG1hcCAtPiByZWR1Y2UgLT4gZ3JvdXBfYnkgLT4gc3VtbWFyaXplCm1hcHBlciA8LSBmdW5jdGlvbih4PTApIHsKICBkZiA8LSBkYXRhX2ZyYW1lKGtleT1jKCJ4aSIsInhpMiIsIm4iKSwgdmFsdWUgPSBjKHgseF4yLDEpKQogIGRmCiAgfQoKdmFyc3VtcyA8LSB4ICU+JQogIG1hcChtYXBwZXIpICU+JQogIHJlZHVjZShiaW5kX3Jvd3MpICU+JQogIGdyb3VwX2J5KGtleSkgJT4lCiAgc3VtbWFyaXNlKHN1bXMgPSBzdW0odmFsdWUpKQooMS8odmFyc3VtcyRzdW1zWzFdLTEpKSoodmFyc3VtcyRzdW1zWzNdLSh2YXJzdW1zJHN1bXNbMl1eMikvdmFyc3VtcyRzdW1zWzFdKQoKIyMgQ2FsY3VsYXRpb24gd2l0aCBtYXAgLT4gcmVkdWNlIC0+IHNwbGl0IC0+IG1hcChyZWR1Y2UpIFtUaGF0IGxhc3Qgc3RlcCBtYXBzIGEgcmVkdWNlIGZ1bmN0aW9uXQp2YXJzdW1zIDwtIHggJT4lCiAgbWFwKG1hcHBlcikgJT4lCiAgcmVkdWNlKGJpbmRfcm93cykgJT4lCiAgc3BsaXQoLiRrZXkpICU+JQogIG1hcCh+cmVkdWNlKC4kdmFsdWUsYCtgKSkKKDEvKHZhcnN1bXNbWyduJ11dLTEpKSoodmFyc3Vtc1tbJ3hpMiddXS0odmFyc3Vtc1tbJ3hpJ11dXjIpL3ZhcnN1bXNbWyduJ11dKQogIApgYGAKCkhvcGVmdWxseSwgdGhhdCBnaXZlcyBzb21lIGZsYXZvciBvZiBob3cgdGhpcyBzb3J0IG9mIHRoaW5nIG1pZ2h0IGJlIHVzZWZ1bCBhdCBzY2FsZS4=