In the last lesson, you learned about the five main data manipulation ‘verbs’ in dplyr: select(), filter(), arrange(), mutate(), and summarize(). The last of these, summarize(), is most powerful when applied to grouped data.

The main idea behind grouping data is that you want to break up your dataset into groups of rows based on the values of one or more variables. The group_by() function is reponsible for doing this.

We’ll continue where we left off with RStudio’s CRAN download log from July 8, 2014, which contains information on roughly 225,000 R package downloads (http://cran-logs.rstudio.com/).

As with the last lesson, the dplyr package was automatically installed (if necessary) and loaded at the beginning of this lesson. Normally, this is something you would have to do on your own. Just to build the habit, type library(dplyr) now to load the package again.

library(dplyr)


I’ve made the dataset available to you in a data frame called mydf. Put it in a ‘data frame tbl’ using the tbl_df() function and store the result in a object called cran. If you’re not sure what I’m talking about, you should start with the previous lesson. Otherwise, practice makes perfect!

cran <- tbl_df(mydf)


To avoid confusion and keep things running smoothly, let’s remove the original dataframe from your workspace with rm(“mydf”).

rm("mydf")


Print cran to the console.

cran


Our first goal is to group the data by package name. Bring up the help file for group_by().

?group_by


Group cran by the package variable and store the result in a new object called by_package.

by_package <- group_by(cran, package)


Let’s take a look at by_package. Print it to the console.

by_package


At the top of the output above, you’ll see ‘Groups: package’, which tells us that this tbl has been grouped by the package variable. Everything else looks the same, but now any operation we apply to the grouped data will take place on a per package basis.

Recall that when we applied mean(size) to the original tbl_df via summarize(), it returned a single number – the mean of all values in the size column. We may care about what that number is, but wouldn’t it be so much more interesting to look at the mean download size for each unique package?

That’s exactly what you’ll get if you use summarize() to apply mean(size) to the grouped data in by_package. Give it a shot.

summarize(by_package, mean(size))


Instead of returning a single value, summarize() now returns the mean size for EACH package in our dataset.

Let’s take it a step further. I just opened an R script for you that contains a partially constructed call to summarize(). Follow the instructions in the script comments.

When you are ready to move on, save the script and type submit(), or type reset() to reset the script to its original state.


Script: summarize1.R

Compute four values, in the following order, from the grouped data:

  1. count = n()
  2. unique = n_distinct(ip_id)
  3. countries = n_distinct(country)
  4. avg_bytes = mean(size)

A few thing to be careful of:

  1. Separate arguments by commas
  2. Make sure you have a closing parenthesis
  3. Check your spelling!
  4. Store the result in pack_sum (for ‘package summary’)

You should also take a look at ?n and ?n_distinct, so that you really understand what is going on.

pack_sum <- summarize(by_package,
                      count = n(),
                      unique = n_distinct(ip_id),
                      countries = n_distinct(country),
                      avg_bytes = mean(size))


submit()


Print the resulting tbl, pack_sum, to the console to examine its contents.

pack_sum


It’s important that you understand how each column of pack_sum was created and what it means. Now that we’ve summarized the data by individual packages, let’s play around with it some more to see what we can learn.

Naturally, we’d like to know which packages were most popular on the day these data were collected (July 8, 2014). Let’s start by isolating the top 1% of packages, based on the total number of downloads as measured by the ‘count’ column.

We need to know the value of ‘count’ that splits the data into the top 1% and bottom 99% of packages based on total downloads. In statistics, this is called the 0.99, or 99%, sample quantile. Use quantile(pack_sum$count, probs = 0.99) to determine this number.

quantile(pack_sum$count, probs = 0.99)
   99% 
679.56 


Now we can isolate only those packages which had more than 679 total downloads. Use filter() to select all rows from pack_sum for which ‘count’ is strictly greater (>) than 679. Store the result in a new object called top_counts.

top_counts <- filter(pack_sum, count > 679)


Let’s take a look at top_counts. Print it to the console.

top_counts


There are only 61 packages in our top 1%, so we’d like to see all of them. Since dplyr only shows us the first 10 rows, we can use the View() function to see more.

View all 61 rows with View(top_counts). Note that the ‘V’ in View() is capitalized.

View(top_counts)


arrange() the rows of top_counts based on the ‘count’ column and assign the result to a new object called top_counts_sorted. We want the packages with the highest number of downloads at the top, which means we want ‘count’ to be in descending order. If you need help, check out ?arrange and/or ?desc.

top_counts_sorted <- arrange(top_counts, desc(count))


Now use View() again to see all 61 rows of top_counts_sorted.

View(top_counts_sorted)


If we use total number of downloads as our metric for popularity, then the above output shows us the most popular packages downloaded from the RStudio CRAN mirror on July 8, 2014. Not surprisingly, ggplot2 leads the pack with 4602 downloads, followed by Rcpp, plyr, rJava, ….

…And if you keep on going, you’ll see swirl at number 43, with 820 total downloads. Sweet!

Perhaps we’re more interested in the number of unique downloads on this particular day. In other words, if a package is downloaded ten times in one day from the same computer, we may wish to count that as only one download. That’s what the ‘unique’ column will tell us.

Like we did with ‘count’, let’s find the 0.99, or 99%, quantile for the ‘unique’ variable with quantile(pack_sum$unique, probs = 0.99).

quantile(pack_sum$unique, probs = 0.99)
99% 
465 


Apply filter() to pack_sum to select all rows corresponding to values of ‘unique’ that are strictly greater than 465. Assign the result to a object called top_unique.

top_unique <- filter(pack_sum, unique > 465)


Let’s View() our top contenders!

View(top_unique)


Now arrange() top_unique by the ‘unique’ column, in descending order, to see which packages were downloaded from the greatest number of unique IP addresses. Assign the result to top_unique_sorted.

top_unique_sorted <- arrange(top_unique, desc(unique))


View() the sorted data.

View(top_unique_sorted)


Now Rcpp is in the lead, followed by stringr, digest, plyr, and ggplot2. swirl moved up a few spaces to number 40, with 698 unique downloads. Nice!

Our final metric of popularity is the number of distinct countries from which each package was downloaded. We’ll approach this one a little differently to introduce you to a method called ‘chaining’ (or ‘piping’).

Chaining allows you to string together multiple function calls in a way that is compact and readable, while still accomplishing the desired result. To make it more concrete, let’s compute our last popularity metric from scratch, starting with our original data.

I’ve opened up a script that contains code similar to what you’ve seen so far. Don’t change anything. Just study it for a minute, make sure you understand everything that’s there, then submit() when you are ready to move on.


Script: summarize2.R

Don’t change any of the code below. Just type submit() when you think you understand it.

We’ve already done this part, but we’re repeating it here for clarity.

by_package <- group_by(cran, package)
pack_sum <- summarize(by_package,
                      count = n(),
                      unique = n_distinct(ip_id),
                      countries = n_distinct(country),
                      avg_bytes = mean(size))


Here’s the new bit, but using the same approach we’ve been using this whole time.

top_countries <- filter(pack_sum, countries > 60)
result1 <- arrange(top_countries, desc(countries), avg_bytes)
# Print the results to the console.
print(result1)


submit()


It’s worth noting that we sorted primarily by country, but used avg_bytes (in ascending order) as a tie breaker. This means that if two packages were downloaded from the same number of countries, the package with a smaller average download size received a higher ranking.

We’d like to accomplish the same result as the last script, but avoid saving our intermediate results. This requires embedding function calls within one another.

That’s exactly what we’ve done in this script. The result is equivalent, but the code is much less readable and some of the arguments are far away from the function to which they belong. Again, just try to understand what is going on here, then submit() when you are ready to see a better solution.


Script: summarize3.R

Don’t change any of the code below. Just type submit() when you think you understand it. If you find it confusing, you’re absolutely right!

result2 <-
  arrange(
    filter(
      summarize(
        group_by(cran,
                 package
        ),
        count = n(),
        unique = n_distinct(ip_id),
        countries = n_distinct(country),
        avg_bytes = mean(size)
      ),
      countries > 60
    ),
    desc(countries),
    avg_bytes
  )
print(result2)
submit()


In this script, we’ve used a special chaining operator, %>%, which was originally introduced in the magrittr R package and has now become a key component of dplyr. You can pull up the related documentation with ?chain. The benefit of %>% is that it allows us to chain the function calls in a linear fashion. The code to the right of %>% operates on the result from the code to the left of %>%.

Once again, just try to understand the code, then type submit() to continue.


Script: summarize4.R

Read the code below, but don’t change anything. As you read it, you can pronounce the %>% operator as the word ‘then’.

Type submit() when you think you understand everything here.

result3 <-
  cran %>%
  group_by(package) %>%
  summarize(count = n(),
            unique = n_distinct(ip_id),
            countries = n_distinct(country),
            avg_bytes = mean(size)
  ) %>%
  filter(countries > 60) %>%
  arrange(desc(countries), avg_bytes)
# Print result to console
print(result3)


submit()


So, the results of the last three scripts are all identical. But, the third script provides a convenient and concise alternative to the more traditional method that we’ve taken previously, which involves saving results as we go along.

Once again, let’s View() the full data, which has been stored in result3.

View(result3)


It looks like Rcpp is on top with downloads from 84 different countries, followed by digest, stringr, plyr, and ggplot2. swirl jumped up the rankings again, this time to 27th.

To help drive the point home, let’s work through a few more examples of chaining.

Let’s build a chain of dplyr commands one step at a time, starting with the script I just opened for you.


Script: chain1.R

select() the following columns from cran. Keep in mind that when you’re using the chaining operator, you don’t need to specify the name of the data tbl in your call to select().

  1. ip_id
  2. country
  3. package
  4. size

The call to print() at the end of the chain is optional, but necessary if you want your results printed to the console. Note that since there are no additional arguments to print(), you can leave off the parentheses after the function name. This is a convenient feature of the %>% operator.

cran %>%
  select(ip_id, country, package, size) %>%
    print


submit()


Let’s add to the chain.


Script: chain2.R

Use mutate() to add a column called size_mb that contains the size of each download in megabytes (i.e. size / 2^20).

If you want your results printed to the console, add print to the end of your chain.

cran %>%
  select(ip_id, country, package, size) %>%
  mutate(size_mb = size / 2^20) %>%
  print


submit()


A little bit more now.


Script: chain3.R

Use filter() to select all rows for which size_mb is less than or equal to (<=) 0.5.

If you want your results printed to the console, add print to the end of your chain.

cran %>%
  select(ip_id, country, package, size) %>%
  mutate(size_mb = size / 2^20) %>%
  filter(size_mb <= 0.5) %>%
  print
submit()


And finish it off.


Script: chain4.R

arrange() the result by size_mb, in descending order.

If you want your results printed to the console, add print to the end of your chain.

cran %>%
  select(ip_id, country, package, size) %>%
  mutate(size_mb = size / 2^20) %>%
  filter(size_mb <= 0.5) %>%
  arrange(desc(size_mb)) %>%
  print
submit()


In this lesson, you learned about grouping and chaining using dplyr. You combined some of the things you learned in the previous lesson with these more advanced ideas to produce concise, readable, and highly effective code. Welcome to the wonderful world of dplyr!



END

LS0tCnRpdGxlOiAiR3JvdXBpbmcgYW5kIENoYWluaW5nIHdpdGggZHBseXIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KICAgIAotLS0KCjxiciAvPgogIApJbiB0aGUgbGFzdCBsZXNzb24sIHlvdSBsZWFybmVkIGFib3V0IHRoZSBmaXZlIG1haW4gZGF0YSBtYW5pcHVsYXRpb24gJ3ZlcmJzJyBpbiBkcGx5cjogKipzZWxlY3QoKSwgZmlsdGVyKCksIGFycmFuZ2UoKSwgbXV0YXRlKCkqKiwgYW5kICoqc3VtbWFyaXplKCkqKi4gVGhlIGxhc3Qgb2YgdGhlc2UsICoqc3VtbWFyaXplKCkqKiwgaXMgbW9zdCBwb3dlcmZ1bCB3aGVuIGFwcGxpZWQgdG8gZ3JvdXBlZCBkYXRhLgoKVGhlIG1haW4gaWRlYSBiZWhpbmQgZ3JvdXBpbmcgZGF0YSBpcyB0aGF0IHlvdSB3YW50IHRvIGJyZWFrIHVwIHlvdXIgZGF0YXNldCBpbnRvIGdyb3VwcyBvZiByb3dzIGJhc2VkIG9uIHRoZSB2YWx1ZXMgb2Ygb25lIG9yIG1vcmUgdmFyaWFibGVzLiBUaGUgKipncm91cF9ieSgpKiogZnVuY3Rpb24gaXMgcmVwb25zaWJsZSBmb3IgZG9pbmcgdGhpcy4KCldlJ2xsIGNvbnRpbnVlIHdoZXJlIHdlIGxlZnQgb2ZmIHdpdGggUlN0dWRpbydzIENSQU4gZG93bmxvYWQgbG9nIGZyb20gSnVseSA4LCAyMDE0LCB3aGljaCBjb250YWlucyBpbmZvcm1hdGlvbiBvbiByb3VnaGx5IDIyNSwwMDAgUiBwYWNrYWdlIGRvd25sb2FkcyAoaHR0cDovL2NyYW4tbG9ncy5yc3R1ZGlvLmNvbS8pLgoKQXMgd2l0aCB0aGUgbGFzdCBsZXNzb24sIHRoZSBkcGx5ciBwYWNrYWdlIHdhcyBhdXRvbWF0aWNhbGx5IGluc3RhbGxlZCAoaWYgbmVjZXNzYXJ5KSBhbmQgbG9hZGVkIGF0IHRoZSBiZWdpbm5pbmcgb2YgdGhpcyBsZXNzb24uIE5vcm1hbGx5LCB0aGlzIGlzIHNvbWV0aGluZyB5b3Ugd291bGQgaGF2ZSB0byBkbyBvbiB5b3VyIG93bi4gSnVzdCB0byBidWlsZCB0aGUgaGFiaXQsIHR5cGUgKmxpYnJhcnkoZHBseXIpKiBub3cgdG8gbG9hZCB0aGUgcGFja2FnZSBhZ2Fpbi4KCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpgYGAKPGJyIC8+CkkndmUgbWFkZSB0aGUgZGF0YXNldCBhdmFpbGFibGUgdG8geW91IGluIGEgZGF0YSBmcmFtZSBjYWxsZWQgKm15ZGYqLiBQdXQgaXQgaW4gYSAqJ2RhdGEgZnJhbWUgdGJsJyogdXNpbmcgdGhlICoqdGJsX2RmKCkqKiBmdW5jdGlvbiBhbmQgc3RvcmUgdGhlIHJlc3VsdCBpbiBhIG9iamVjdCBjYWxsZWQgKmNyYW4qLiBJZiB5b3UncmUgbm90IHN1cmUgd2hhdCBJJ20gdGFsa2luZyBhYm91dCwgeW91IHNob3VsZCBzdGFydCB3aXRoIHRoZSBwcmV2aW91cyBsZXNzb24uIE90aGVyd2lzZSwgcHJhY3RpY2UgbWFrZXMgcGVyZmVjdCEKCmBgYHtyfQpjcmFuIDwtIHRibF9kZihteWRmKQpgYGAKPGJyIC8+ClRvIGF2b2lkIGNvbmZ1c2lvbiBhbmQga2VlcCB0aGluZ3MgcnVubmluZyBzbW9vdGhseSwgbGV0J3MgcmVtb3ZlIHRoZSBvcmlnaW5hbCBkYXRhZnJhbWUgZnJvbSB5b3VyIHdvcmtzcGFjZSB3aXRoICpybSgibXlkZiIpKi4KCmBgYHtyfQpybSgibXlkZiIpCmBgYAo8YnIgLz4KUHJpbnQgKmNyYW4qIHRvIHRoZSBjb25zb2xlLgpgYGB7cn0KY3JhbgpgYGAKPGJyIC8+Ck91ciBmaXJzdCBnb2FsIGlzIHRvIGdyb3VwIHRoZSBkYXRhIGJ5IHBhY2thZ2UgbmFtZS4gQnJpbmcgdXAgdGhlIGhlbHAgZmlsZSBmb3IgKipncm91cF9ieSgpKiouCgpgYGB7cn0KP2dyb3VwX2J5CmBgYAo8YnIgLz4KR3JvdXAgY3JhbiBieSB0aGUgcGFja2FnZSB2YXJpYWJsZSBhbmQgc3RvcmUgdGhlIHJlc3VsdCBpbiBhIG5ldyBvYmplY3QgY2FsbGVkICpieV9wYWNrYWdlKi4KYGBge3J9CmJ5X3BhY2thZ2UgPC0gZ3JvdXBfYnkoY3JhbiwgcGFja2FnZSkKYGBgCjxiciAvPgpMZXQncyB0YWtlIGEgbG9vayBhdCAqYnlfcGFja2FnZSouIFByaW50IGl0IHRvIHRoZSBjb25zb2xlLgpgYGB7cn0KYnlfcGFja2FnZQpgYGAKPGJyIC8+CkF0IHRoZSB0b3Agb2YgdGhlIG91dHB1dCBhYm92ZSwgeW91J2xsIHNlZSAqJ0dyb3VwczogcGFja2FnZScqLCB3aGljaCB0ZWxscyB1cyB0aGF0IHRoaXMgdGJsIGhhcyBiZWVuIGdyb3VwZWQgYnkgdGhlIHBhY2thZ2UgdmFyaWFibGUuIEV2ZXJ5dGhpbmcgZWxzZSBsb29rcyB0aGUgc2FtZSwgYnV0IG5vdyBhbnkgb3BlcmF0aW9uIHdlIGFwcGx5IHRvIHRoZSBncm91cGVkIGRhdGEgd2lsbCB0YWtlIHBsYWNlIG9uIGEgcGVyIHBhY2thZ2UgYmFzaXMuCgpSZWNhbGwgdGhhdCB3aGVuIHdlIGFwcGxpZWQgKm1lYW4oc2l6ZSkqIHRvIHRoZSBvcmlnaW5hbCB0YmxfZGYgdmlhICoqc3VtbWFyaXplKCkqKiwgaXQgcmV0dXJuZWQgYSBzaW5nbGUgbnVtYmVyIC0tIHRoZSBtZWFuIG9mIGFsbCB2YWx1ZXMgaW4gdGhlIHNpemUgY29sdW1uLiBXZSBtYXkgY2FyZSBhYm91dCB3aGF0IHRoYXQgbnVtYmVyIGlzLCBidXQgd291bGRuJ3QgaXQgYmUgc28gbXVjaCBtb3JlIGludGVyZXN0aW5nIHRvIGxvb2sgYXQgdGhlIG1lYW4gZG93bmxvYWQgc2l6ZSBmb3IgZWFjaCB1bmlxdWUgcGFja2FnZT8KClRoYXQncyBleGFjdGx5IHdoYXQgeW91J2xsIGdldCBpZiB5b3UgdXNlICoqc3VtbWFyaXplKCkqKiB0byBhcHBseSAqbWVhbihzaXplKSogdG8gdGhlIGdyb3VwZWQgZGF0YSBpbiAqYnlfcGFja2FnZSouIEdpdmUgaXQgYSBzaG90LgpgYGB7cn0Kc3VtbWFyaXplKGJ5X3BhY2thZ2UsIG1lYW4oc2l6ZSkpCmBgYAo8YnIgLz4KSW5zdGVhZCBvZiByZXR1cm5pbmcgYSBzaW5nbGUgdmFsdWUsICoqc3VtbWFyaXplKCkqKiBub3cgcmV0dXJucyB0aGUgbWVhbiBzaXplIGZvciBFQUNIIHBhY2thZ2UgaW4gb3VyIGRhdGFzZXQuCgpMZXQncyB0YWtlIGl0IGEgc3RlcCBmdXJ0aGVyLiBJIGp1c3Qgb3BlbmVkIGFuIFIgc2NyaXB0IGZvciB5b3UgdGhhdCBjb250YWlucyBhIHBhcnRpYWxseSBjb25zdHJ1Y3RlZCBjYWxsIHRvICoqc3VtbWFyaXplKCkqKi4gRm9sbG93IHRoZSBpbnN0cnVjdGlvbnMgaW4gdGhlIHNjcmlwdCBjb21tZW50cy4KCldoZW4geW91IGFyZSByZWFkeSB0byBtb3ZlIG9uLCBzYXZlIHRoZSBzY3JpcHQgYW5kIHR5cGUgKipzdWJtaXQoKSoqLCBvciB0eXBlICoqcmVzZXQoKSoqIHRvIHJlc2V0IHRoZSBzY3JpcHQgdG8gaXRzIG9yaWdpbmFsIHN0YXRlLgoKPGJyLz4KCjxkaXYgc3R5bGU9ICJib3JkZXI6IDVweCBzb2xpZCBncmF5OyBwYWRkaW5nOiAxMHB4IDIwcHg7IGJhY2tncm91bmQtY29sb3I6I2Y5ZjlmOTsgYm94LXNoYWRvdzogMCAxcHggNXB4IHJnYmEoMCwgMCwgMCwgMC4yNSk7Ij4KCiMjIyMgU2NyaXB0OiBzdW1tYXJpemUxLlIKCgpDb21wdXRlIGZvdXIgdmFsdWVzLCBpbiB0aGUgZm9sbG93aW5nIG9yZGVyLCBmcm9tIHRoZSBncm91cGVkIGRhdGE6CgoxLiBjb3VudCA9IG4oKQoyLiB1bmlxdWUgPSBuX2Rpc3RpbmN0KGlwX2lkKQozLiBjb3VudHJpZXMgPSBuX2Rpc3RpbmN0KGNvdW50cnkpCjQuIGF2Z19ieXRlcyA9IG1lYW4oc2l6ZSkKCkEgZmV3IHRoaW5nIHRvIGJlIGNhcmVmdWwgb2Y6CgoxLiBTZXBhcmF0ZSBhcmd1bWVudHMgYnkgY29tbWFzCjIuIE1ha2Ugc3VyZSB5b3UgaGF2ZSBhIGNsb3NpbmcgcGFyZW50aGVzaXMKMy4gQ2hlY2sgeW91ciBzcGVsbGluZyEKNC4gU3RvcmUgdGhlIHJlc3VsdCBpbiBwYWNrX3N1bSAoZm9yICdwYWNrYWdlIHN1bW1hcnknKQoKWW91IHNob3VsZCBhbHNvIHRha2UgYSBsb29rIGF0ID9uIGFuZCA/bl9kaXN0aW5jdCwgc28KdGhhdCB5b3UgcmVhbGx5IHVuZGVyc3RhbmQgd2hhdCBpcyBnb2luZyBvbi4KCmBgYHtyfQpwYWNrX3N1bSA8LSBzdW1tYXJpemUoYnlfcGFja2FnZSwKICAgICAgICAgICAgICAgICAgICAgIGNvdW50ID0gbigpLAogICAgICAgICAgICAgICAgICAgICAgdW5pcXVlID0gbl9kaXN0aW5jdChpcF9pZCksCiAgICAgICAgICAgICAgICAgICAgICBjb3VudHJpZXMgPSBuX2Rpc3RpbmN0KGNvdW50cnkpLAogICAgICAgICAgICAgICAgICAgICAgYXZnX2J5dGVzID0gbWVhbihzaXplKSkKYGBgCjwvZGl2PgoKPGJyIC8+CgpgYGB7cn0Kc3VibWl0KCkKYGBgCgo8YnIgLz4KUHJpbnQgdGhlIHJlc3VsdGluZyB0YmwsICpwYWNrX3N1bSosIHRvIHRoZSBjb25zb2xlIHRvIGV4YW1pbmUgaXRzIGNvbnRlbnRzLgoKYGBge3J9CnBhY2tfc3VtCmBgYAoKPGJyIC8+CgoqIFRoZSAqKidjb3VudCcqKiBjb2x1bW4sIGNyZWF0ZWQgd2l0aCAqbigpKiwgY29udGFpbnMgdGhlIHRvdGFsIG51bWJlciBvZiByb3dzIChpLmUuIGRvd25sb2FkcykgZm9yIGVhY2ggcGFja2FnZS4KKiBUaGUgKiondW5pcXVlJyoqIGNvbHVtbiwgY3JlYXRlZCB3aXRoICpuX2Rpc3RpbmN0KGlwX2lkKSosIGdpdmVzIHRoZSB0b3RhbCBudW1iZXIgb2YgdW5pcXVlIGRvd25sb2FkcyBmb3IgZWFjaCBwYWNrYWdlLCBhcyBtZWFzdXJlZCBieSB0aGUgbnVtYmVyIG9mIGRpc3RpbmN0IGlwX2lkJ3MuCiogVGhlICoqJ2NvdW50cmllcycqKiBjb2x1bW4sIGNyZWF0ZWQgd2l0aCAqbl9kaXN0aW5jdChjb3VudHJ5KSosIHByb3ZpZGVzIHRoZSBudW1iZXIgb2YgY291bnRyaWVzIGluIHdoaWNoIGVhY2ggcGFja2FnZSB3YXMgZG93bmxvYWRlZC4gCiogVGhlICoqJ2F2Z19ieXRlcycqKiBjb2x1bW4sIGNyZWF0ZWQgd2l0aCAqbWVhbihzaXplKSosIGNvbnRhaW5zIHRoZSBtZWFuIGRvd25sb2FkIHNpemUgKGluIGJ5dGVzKSBmb3IgZWFjaCBwYWNrYWdlLgoKSXQncyBpbXBvcnRhbnQgdGhhdCB5b3UgdW5kZXJzdGFuZCBob3cgZWFjaCBjb2x1bW4gb2YgKnBhY2tfc3VtKiB3YXMgY3JlYXRlZCBhbmQgd2hhdCBpdCBtZWFucy4gTm93IHRoYXQgd2UndmUgc3VtbWFyaXplZCB0aGUgZGF0YSBieSBpbmRpdmlkdWFsIHBhY2thZ2VzLCBsZXQncyBwbGF5IGFyb3VuZCB3aXRoIGl0IHNvbWUgbW9yZSB0byBzZWUgd2hhdCB3ZSBjYW4gbGVhcm4uCgpOYXR1cmFsbHksIHdlJ2QgbGlrZSB0byBrbm93IHdoaWNoIHBhY2thZ2VzIHdlcmUgbW9zdCBwb3B1bGFyIG9uIHRoZSBkYXkgdGhlc2UgZGF0YSB3ZXJlIGNvbGxlY3RlZCAoSnVseSA4LCAyMDE0KS4gTGV0J3Mgc3RhcnQgYnkgaXNvbGF0aW5nIHRoZSB0b3AgMSUgb2YgcGFja2FnZXMsIGJhc2VkIG9uIHRoZSB0b3RhbCBudW1iZXIgb2YgZG93bmxvYWRzIGFzIG1lYXN1cmVkIGJ5IHRoZSAqJ2NvdW50JyogY29sdW1uLgoKV2UgbmVlZCB0byBrbm93IHRoZSB2YWx1ZSBvZiAqJ2NvdW50JyogdGhhdCBzcGxpdHMgdGhlIGRhdGEgaW50byB0aGUgdG9wIDElIGFuZCBib3R0b20gOTklIG9mIHBhY2thZ2VzIGJhc2VkIG9uIHRvdGFsIGRvd25sb2Fkcy4gSW4gc3RhdGlzdGljcywgdGhpcyBpcyBjYWxsZWQgdGhlIDAuOTksIG9yIDk5JSwgc2FtcGxlIHF1YW50aWxlLiBVc2UgcXVhbnRpbGUocGFja19zdW0kY291bnQsIHByb2JzID0gMC45OSkgdG8gZGV0ZXJtaW5lIHRoaXMgbnVtYmVyLgoKYGBge3J9CnF1YW50aWxlKHBhY2tfc3VtJGNvdW50LCBwcm9icyA9IDAuOTkpCmBgYAo8YnIgLz4KTm93IHdlIGNhbiBpc29sYXRlIG9ubHkgdGhvc2UgcGFja2FnZXMgd2hpY2ggaGFkIG1vcmUgdGhhbiA2NzkgdG90YWwgZG93bmxvYWRzLiBVc2UgKipmaWx0ZXIoKSoqIHRvIHNlbGVjdCBhbGwgcm93cyBmcm9tICoqcGFja19zdW0qKiBmb3Igd2hpY2ggKidjb3VudCcqIGlzIHN0cmljdGx5IGdyZWF0ZXIgKD4pIHRoYW4gNjc5LiBTdG9yZSB0aGUgcmVzdWx0IGluIGEgbmV3IG9iamVjdCBjYWxsZWQgKip0b3BfY291bnRzKiouCgpgYGB7cn0KdG9wX2NvdW50cyA8LSBmaWx0ZXIocGFja19zdW0sIGNvdW50ID4gNjc5KQpgYGAKPGJyIC8+CkxldCdzIHRha2UgYSBsb29rIGF0ICoqdG9wX2NvdW50cyoqLiBQcmludCBpdCB0byB0aGUgY29uc29sZS4KYGBge3J9CnRvcF9jb3VudHMKYGBgCjxiciAvPgpUaGVyZSBhcmUgb25seSA2MSBwYWNrYWdlcyBpbiBvdXIgdG9wIDElLCBzbyB3ZSdkIGxpa2UgdG8gc2VlIGFsbCBvZiB0aGVtLiBTaW5jZSBkcGx5ciBvbmx5IHNob3dzIHVzIHRoZSBmaXJzdCAxMCByb3dzLCB3ZSBjYW4gdXNlIHRoZSAqKlZpZXcoKSoqIGZ1bmN0aW9uIHRvIHNlZSBtb3JlLgoKVmlldyBhbGwgNjEgcm93cyB3aXRoICpWaWV3KHRvcF9jb3VudHMpKi4gTm90ZSB0aGF0IHRoZSAnVicgaW4gKipWaWV3KCkqKiBpcyBjYXBpdGFsaXplZC4KCmBgYHtyfQpWaWV3KHRvcF9jb3VudHMpCmBgYAo8YnIgLz4KKiphcnJhbmdlKCkqKiB0aGUgcm93cyBvZiAqdG9wX2NvdW50cyogYmFzZWQgb24gdGhlIConY291bnQnKiBjb2x1bW4gYW5kIGFzc2lnbiB0aGUgcmVzdWx0IHRvIGEgbmV3IG9iamVjdCBjYWxsZWQgKnRvcF9jb3VudHNfc29ydGVkKi4gV2Ugd2FudCB0aGUgcGFja2FnZXMgd2l0aCB0aGUgaGlnaGVzdCBudW1iZXIgb2YgZG93bmxvYWRzIGF0IHRoZSB0b3AsIHdoaWNoIG1lYW5zIHdlIHdhbnQgKidjb3VudCcqIHRvIGJlIGluIGRlc2NlbmRpbmcgb3JkZXIuIElmIHlvdSBuZWVkIGhlbHAsIGNoZWNrIG91dCAqKj9hcnJhbmdlKiogYW5kL29yICoqP2Rlc2MuKioKCmBgYHtyfQp0b3BfY291bnRzX3NvcnRlZCA8LSBhcnJhbmdlKHRvcF9jb3VudHMsIGRlc2MoY291bnQpKQpgYGAKPGJyIC8+Ck5vdyB1c2UgKipWaWV3KCkqKiBhZ2FpbiB0byBzZWUgYWxsIDYxIHJvd3Mgb2YgKnRvcF9jb3VudHNfc29ydGVkKi4KCmBgYHtyfQpWaWV3KHRvcF9jb3VudHNfc29ydGVkKQpgYGAKCjxiciAvPgpJZiB3ZSB1c2UgdG90YWwgbnVtYmVyIG9mIGRvd25sb2FkcyBhcyBvdXIgbWV0cmljIGZvciBwb3B1bGFyaXR5LCB0aGVuIHRoZSBhYm92ZSBvdXRwdXQgc2hvd3MgdXMgdGhlIG1vc3QgcG9wdWxhciBwYWNrYWdlcyBkb3dubG9hZGVkIGZyb20gdGhlIFJTdHVkaW8gQ1JBTiBtaXJyb3Igb24gSnVseSA4LCAyMDE0LiBOb3Qgc3VycHJpc2luZ2x5LCBnZ3Bsb3QyIGxlYWRzIHRoZSBwYWNrIHdpdGggNDYwMiBkb3dubG9hZHMsIGZvbGxvd2VkIGJ5IFJjcHAsIHBseXIsIHJKYXZhLCAuLi4uCgouLi5BbmQgaWYgeW91IGtlZXAgb24gZ29pbmcsIHlvdSdsbCBzZWUgc3dpcmwgYXQgbnVtYmVyIDQzLCB3aXRoIDgyMCB0b3RhbCBkb3dubG9hZHMuIFN3ZWV0IQoKUGVyaGFwcyB3ZSdyZSBtb3JlIGludGVyZXN0ZWQgaW4gdGhlIG51bWJlciBvZiAqdW5pcXVlKiBkb3dubG9hZHMgb24gdGhpcyBwYXJ0aWN1bGFyIGRheS4gSW4gb3RoZXIgd29yZHMsIGlmIGEgcGFja2FnZSBpcyBkb3dubG9hZGVkIHRlbiB0aW1lcyBpbiBvbmUgZGF5IGZyb20gdGhlIHNhbWUgY29tcHV0ZXIsIHdlIG1heSB3aXNoIHRvIGNvdW50IHRoYXQgYXMgb25seSBvbmUgZG93bmxvYWQuIFRoYXQncyB3aGF0IHRoZSAndW5pcXVlJyBjb2x1bW4gd2lsbCB0ZWxsIHVzLgoKTGlrZSB3ZSBkaWQgd2l0aCAqJ2NvdW50JyosIGxldCdzIGZpbmQgdGhlIDAuOTksIG9yIDk5JSwgcXVhbnRpbGUgZm9yIHRoZSAqJ3VuaXF1ZScqIHZhcmlhYmxlIHdpdGggKnF1YW50aWxlKHBhY2tfc3VtJHVuaXF1ZSwgcHJvYnMgPSAwLjk5KS4qCgpgYGB7cn0KcXVhbnRpbGUocGFja19zdW0kdW5pcXVlLCBwcm9icyA9IDAuOTkpCmBgYAo8YnIgLz4KQXBwbHkgKipmaWx0ZXIoKSoqIHRvICpwYWNrX3N1bSogdG8gc2VsZWN0IGFsbCByb3dzIGNvcnJlc3BvbmRpbmcgdG8gdmFsdWVzIG9mICondW5pcXVlJyogdGhhdCBhcmUgc3RyaWN0bHkgZ3JlYXRlciB0aGFuIDQ2NS4gQXNzaWduIHRoZSByZXN1bHQgdG8gYSBvYmplY3QgY2FsbGVkICoqdG9wX3VuaXF1ZSoqLgpgYGB7cn0KdG9wX3VuaXF1ZSA8LSBmaWx0ZXIocGFja19zdW0sIHVuaXF1ZSA+IDQ2NSkKYGBgCjxiciAvPgpMZXQncyAqKlZpZXcoKSoqIG91ciB0b3AgY29udGVuZGVycyEKYGBge3J9ClZpZXcodG9wX3VuaXF1ZSkKYGBgCjxiciAvPgpOb3cgKiphcnJhbmdlKCkqKiAqdG9wX3VuaXF1ZSogYnkgdGhlICondW5pcXVlJyogY29sdW1uLCBpbiBkZXNjZW5kaW5nIG9yZGVyLCB0byBzZWUgd2hpY2ggcGFja2FnZXMgd2VyZSBkb3dubG9hZGVkIGZyb20gdGhlIGdyZWF0ZXN0IG51bWJlciBvZiB1bmlxdWUgSVAgYWRkcmVzc2VzLiBBc3NpZ24gdGhlIHJlc3VsdCB0byAqKnRvcF91bmlxdWVfc29ydGVkKiouCmBgYHtyfQp0b3BfdW5pcXVlX3NvcnRlZCA8LSBhcnJhbmdlKHRvcF91bmlxdWUsIGRlc2ModW5pcXVlKSkKYGBgCjxiciAvPgoqKlZpZXcoKSoqIHRoZSBzb3J0ZWQgZGF0YS4KYGBge3J9ClZpZXcodG9wX3VuaXF1ZV9zb3J0ZWQpCmBgYAo8YnIgLz4KTm93IFJjcHAgaXMgaW4gdGhlIGxlYWQsIGZvbGxvd2VkIGJ5IHN0cmluZ3IsIGRpZ2VzdCwgcGx5ciwgYW5kIGdncGxvdDIuIHN3aXJsIG1vdmVkIHVwIGEgZmV3IHNwYWNlcyB0byBudW1iZXIgNDAsIHdpdGggNjk4IHVuaXF1ZSBkb3dubG9hZHMuIE5pY2UhCgpPdXIgZmluYWwgbWV0cmljIG9mIHBvcHVsYXJpdHkgaXMgdGhlIG51bWJlciBvZiBkaXN0aW5jdCBjb3VudHJpZXMgZnJvbSB3aGljaCBlYWNoIHBhY2thZ2Ugd2FzIGRvd25sb2FkZWQuIFdlJ2xsIGFwcHJvYWNoIHRoaXMgb25lIGEgbGl0dGxlIGRpZmZlcmVudGx5IHRvIGludHJvZHVjZSB5b3UgdG8gYSBtZXRob2QgY2FsbGVkIConY2hhaW5pbmcnKiAob3IgKidwaXBpbmcnKikuCgpDaGFpbmluZyBhbGxvd3MgeW91IHRvIHN0cmluZyB0b2dldGhlciBtdWx0aXBsZSBmdW5jdGlvbiBjYWxscyBpbiBhIHdheSB0aGF0IGlzIGNvbXBhY3QgYW5kIHJlYWRhYmxlLCB3aGlsZSBzdGlsbCBhY2NvbXBsaXNoaW5nIHRoZSBkZXNpcmVkIHJlc3VsdC4gVG8gbWFrZSBpdCBtb3JlIGNvbmNyZXRlLCBsZXQncyBjb21wdXRlIG91ciBsYXN0IHBvcHVsYXJpdHkgbWV0cmljIGZyb20gc2NyYXRjaCwgc3RhcnRpbmcgd2l0aCBvdXIgb3JpZ2luYWwgZGF0YS4KCkkndmUgb3BlbmVkIHVwIGEgc2NyaXB0IHRoYXQgY29udGFpbnMgY29kZSBzaW1pbGFyIHRvIHdoYXQgeW91J3ZlIHNlZW4gc28gZmFyLiBEb24ndCBjaGFuZ2UgYW55dGhpbmcuIEp1c3Qgc3R1ZHkgaXQgZm9yIGEgbWludXRlLCBtYWtlIHN1cmUgeW91IHVuZGVyc3RhbmQgZXZlcnl0aGluZyB0aGF0J3MgdGhlcmUsIHRoZW4gKipzdWJtaXQoKSoqIHdoZW4geW91IGFyZSByZWFkeSB0byBtb3ZlIG9uLgoKPGJyLz4KCjxkaXYgc3R5bGU9ICJib3JkZXI6IDVweCBzb2xpZCBncmF5OyBwYWRkaW5nOiAxMHB4IDIwcHg7IGJhY2tncm91bmQtY29sb3I6I2Y5ZjlmOTsgYm94LXNoYWRvdzogMCAxcHggNXB4IHJnYmEoMCwgMCwgMCwgMC4yNSk7Ij4KCiMjIyMgU2NyaXB0OiBzdW1tYXJpemUyLlIKCgpEb24ndCBjaGFuZ2UgYW55IG9mIHRoZSBjb2RlIGJlbG93LiBKdXN0IHR5cGUgKnN1Ym1pdCgpKiB3aGVuIHlvdSB0aGluayB5b3UgdW5kZXJzdGFuZCBpdC4KCldlJ3ZlIGFscmVhZHkgZG9uZSB0aGlzIHBhcnQsIGJ1dCB3ZSdyZSByZXBlYXRpbmcgaXQgaGVyZSBmb3IgY2xhcml0eS4KYGBge3J9CmJ5X3BhY2thZ2UgPC0gZ3JvdXBfYnkoY3JhbiwgcGFja2FnZSkKcGFja19zdW0gPC0gc3VtbWFyaXplKGJ5X3BhY2thZ2UsCiAgICAgICAgICAgICAgICAgICAgICBjb3VudCA9IG4oKSwKICAgICAgICAgICAgICAgICAgICAgIHVuaXF1ZSA9IG5fZGlzdGluY3QoaXBfaWQpLAogICAgICAgICAgICAgICAgICAgICAgY291bnRyaWVzID0gbl9kaXN0aW5jdChjb3VudHJ5KSwKICAgICAgICAgICAgICAgICAgICAgIGF2Z19ieXRlcyA9IG1lYW4oc2l6ZSkpCmBgYAo8YnIgLz4KCkhlcmUncyB0aGUgbmV3IGJpdCwgYnV0IHVzaW5nIHRoZSBzYW1lIGFwcHJvYWNoIHdlJ3ZlIGJlZW4gdXNpbmcgdGhpcyB3aG9sZSB0aW1lLgpgYGB7cn0KdG9wX2NvdW50cmllcyA8LSBmaWx0ZXIocGFja19zdW0sIGNvdW50cmllcyA+IDYwKQpyZXN1bHQxIDwtIGFycmFuZ2UodG9wX2NvdW50cmllcywgZGVzYyhjb3VudHJpZXMpLCBhdmdfYnl0ZXMpCgojIFByaW50IHRoZSByZXN1bHRzIHRvIHRoZSBjb25zb2xlLgpwcmludChyZXN1bHQxKQpgYGAKCjwvZGl2PgoKPGJyIC8+CmBgYHtyfQpzdWJtaXQoKQpgYGAKPGJyIC8+Ckl0J3Mgd29ydGggbm90aW5nIHRoYXQgd2Ugc29ydGVkIHByaW1hcmlseSBieSAqY291bnRyeSosIGJ1dCB1c2VkICphdmdfYnl0ZXMqIChpbiBhc2NlbmRpbmcgb3JkZXIpIGFzIGEgdGllIGJyZWFrZXIuIFRoaXMgbWVhbnMgdGhhdCBpZiB0d28gcGFja2FnZXMgd2VyZSBkb3dubG9hZGVkIGZyb20gdGhlIHNhbWUgbnVtYmVyIG9mIGNvdW50cmllcywgdGhlIHBhY2thZ2Ugd2l0aCBhIHNtYWxsZXIgYXZlcmFnZSBkb3dubG9hZCBzaXplIHJlY2VpdmVkIGEgaGlnaGVyIHJhbmtpbmcuCgpXZSdkIGxpa2UgdG8gYWNjb21wbGlzaCB0aGUgc2FtZSByZXN1bHQgYXMgdGhlIGxhc3Qgc2NyaXB0LCBidXQgYXZvaWQgc2F2aW5nIG91ciBpbnRlcm1lZGlhdGUgcmVzdWx0cy4gVGhpcyByZXF1aXJlcyBlbWJlZGRpbmcgZnVuY3Rpb24gY2FsbHMgd2l0aGluIG9uZSBhbm90aGVyLgoKVGhhdCdzIGV4YWN0bHkgd2hhdCB3ZSd2ZSBkb25lIGluIHRoaXMgc2NyaXB0LiBUaGUgcmVzdWx0IGlzIGVxdWl2YWxlbnQsIGJ1dCB0aGUgY29kZSBpcyBtdWNoIGxlc3MgcmVhZGFibGUgYW5kIHNvbWUgb2YgdGhlIGFyZ3VtZW50cyBhcmUgZmFyIGF3YXkgZnJvbSB0aGUgZnVuY3Rpb24gdG8gd2hpY2ggdGhleSBiZWxvbmcuIEFnYWluLCBqdXN0IHRyeSB0byB1bmRlcnN0YW5kIHdoYXQgaXMgZ29pbmcgb24gaGVyZSwgdGhlbiAqKnN1Ym1pdCgpKiogd2hlbiB5b3UgYXJlIHJlYWR5IHRvIHNlZSBhIGJldHRlciBzb2x1dGlvbi4KCjxici8+Cgo8ZGl2IHN0eWxlPSAiYm9yZGVyOiA1cHggc29saWQgZ3JheTsgcGFkZGluZzogMTBweCAyMHB4OyBiYWNrZ3JvdW5kLWNvbG9yOiNmOWY5Zjk7IGJveC1zaGFkb3c6IDAgMXB4IDVweCByZ2JhKDAsIDAsIDAsIDAuMjUpOyI+CiMjIyMgU2NyaXB0OiBzdW1tYXJpemUzLlIKCgpEb24ndCBjaGFuZ2UgYW55IG9mIHRoZSBjb2RlIGJlbG93LiBKdXN0IHR5cGUgKnN1Ym1pdCgpKiB3aGVuIHlvdSB0aGluayB5b3UgdW5kZXJzdGFuZCBpdC4gSWYgeW91IGZpbmQgaXQgY29uZnVzaW5nLCB5b3UncmUgYWJzb2x1dGVseSByaWdodCEKYGBge3J9CnJlc3VsdDIgPC0KICBhcnJhbmdlKAogICAgZmlsdGVyKAogICAgICBzdW1tYXJpemUoCiAgICAgICAgZ3JvdXBfYnkoY3JhbiwKICAgICAgICAgICAgICAgICBwYWNrYWdlCiAgICAgICAgKSwKICAgICAgICBjb3VudCA9IG4oKSwKICAgICAgICB1bmlxdWUgPSBuX2Rpc3RpbmN0KGlwX2lkKSwKICAgICAgICBjb3VudHJpZXMgPSBuX2Rpc3RpbmN0KGNvdW50cnkpLAogICAgICAgIGF2Z19ieXRlcyA9IG1lYW4oc2l6ZSkKICAgICAgKSwKICAgICAgY291bnRyaWVzID4gNjAKICAgICksCiAgICBkZXNjKGNvdW50cmllcyksCiAgICBhdmdfYnl0ZXMKICApCgpwcmludChyZXN1bHQyKQpgYGAKCjwvZGl2PgoKYGBge3J9CnN1Ym1pdCgpCmBgYAoKPGJyIC8+CkluIHRoaXMgc2NyaXB0LCB3ZSd2ZSB1c2VkIGEgc3BlY2lhbCBjaGFpbmluZyBvcGVyYXRvciwgKiolPiUqKiwgd2hpY2ggd2FzIG9yaWdpbmFsbHkgaW50cm9kdWNlZCBpbiB0aGUgKiptYWdyaXR0cioqIFIgcGFja2FnZSBhbmQgaGFzIG5vdyBiZWNvbWUgYSBrZXkgY29tcG9uZW50IG9mICoqZHBseXIqKi4gWW91IGNhbiBwdWxsIHVwIHRoZSByZWxhdGVkIGRvY3VtZW50YXRpb24gd2l0aCAqP2NoYWluKi4gVGhlIGJlbmVmaXQgb2YgKiolPiUqKiBpcyB0aGF0IGl0IGFsbG93cyB1cyB0byBjaGFpbiB0aGUgZnVuY3Rpb24gY2FsbHMgaW4gYSBsaW5lYXIgZmFzaGlvbi4gVGhlIGNvZGUgdG8gdGhlIHJpZ2h0IG9mICoqJT4lKiogb3BlcmF0ZXMgb24gdGhlIHJlc3VsdCBmcm9tIHRoZSBjb2RlIHRvIHRoZSBsZWZ0IG9mICoqJT4lKiouCgpPbmNlIGFnYWluLCBqdXN0IHRyeSB0byB1bmRlcnN0YW5kIHRoZSBjb2RlLCB0aGVuIHR5cGUgKnN1Ym1pdCgpKiB0byBjb250aW51ZS4KCjxici8+Cgo8ZGl2IHN0eWxlPSAiYm9yZGVyOiA1cHggc29saWQgZ3JheTsgcGFkZGluZzogMTBweCAyMHB4OyBiYWNrZ3JvdW5kLWNvbG9yOiNmOWY5Zjk7IGJveC1zaGFkb3c6IDAgMXB4IDVweCByZ2JhKDAsIDAsIDAsIDAuMjUpOyI+CiMjIyMgU2NyaXB0OiBzdW1tYXJpemU0LlIKClJlYWQgdGhlIGNvZGUgYmVsb3csIGJ1dCBkb24ndCBjaGFuZ2UgYW55dGhpbmcuIEFzIHlvdSByZWFkIGl0LCB5b3UgY2FuIHByb25vdW5jZSB0aGUgKiolPiUqKiBvcGVyYXRvciBhcyB0aGUgd29yZCAqJ3RoZW4nKi4KClR5cGUgKnN1Ym1pdCgpKiB3aGVuIHlvdSB0aGluayB5b3UgdW5kZXJzdGFuZCBldmVyeXRoaW5nIGhlcmUuCmBgYHtyfQpyZXN1bHQzIDwtCiAgY3JhbiAlPiUKICBncm91cF9ieShwYWNrYWdlKSAlPiUKICBzdW1tYXJpemUoY291bnQgPSBuKCksCiAgICAgICAgICAgIHVuaXF1ZSA9IG5fZGlzdGluY3QoaXBfaWQpLAogICAgICAgICAgICBjb3VudHJpZXMgPSBuX2Rpc3RpbmN0KGNvdW50cnkpLAogICAgICAgICAgICBhdmdfYnl0ZXMgPSBtZWFuKHNpemUpCiAgKSAlPiUKICBmaWx0ZXIoY291bnRyaWVzID4gNjApICU+JQogIGFycmFuZ2UoZGVzYyhjb3VudHJpZXMpLCBhdmdfYnl0ZXMpCgojIFByaW50IHJlc3VsdCB0byBjb25zb2xlCnByaW50KHJlc3VsdDMpCmBgYAoKPC9kaXY+Cgo8YnIgLz4KYGBge3J9CnN1Ym1pdCgpCmBgYAoKPGJyIC8+ClNvLCB0aGUgcmVzdWx0cyBvZiB0aGUgbGFzdCB0aHJlZSBzY3JpcHRzIGFyZSBhbGwgaWRlbnRpY2FsLiBCdXQsIHRoZSB0aGlyZCBzY3JpcHQgcHJvdmlkZXMgYSBjb252ZW5pZW50IGFuZCBjb25jaXNlIGFsdGVybmF0aXZlIHRvIHRoZSBtb3JlIHRyYWRpdGlvbmFsIG1ldGhvZCB0aGF0IHdlJ3ZlIHRha2VuIHByZXZpb3VzbHksIHdoaWNoIGludm9sdmVzIHNhdmluZyByZXN1bHRzIGFzIHdlIGdvIGFsb25nLgoKT25jZSBhZ2FpbiwgbGV0J3MgKipWaWV3KCkqKiB0aGUgZnVsbCBkYXRhLCB3aGljaCBoYXMgYmVlbiBzdG9yZWQgaW4gKipyZXN1bHQzKiouCgpgYGB7cn0KVmlldyhyZXN1bHQzKQpgYGAKCjxiciAvPgpJdCBsb29rcyBsaWtlIFJjcHAgaXMgb24gdG9wIHdpdGggZG93bmxvYWRzIGZyb20gODQgZGlmZmVyZW50IGNvdW50cmllcywgZm9sbG93ZWQgYnkgZGlnZXN0LCBzdHJpbmdyLCBwbHlyLCBhbmQgZ2dwbG90Mi4gc3dpcmwganVtcGVkIHVwIHRoZSByYW5raW5ncyBhZ2FpbiwgdGhpcyB0aW1lIHRvIDI3dGguCgpUbyBoZWxwIGRyaXZlIHRoZSBwb2ludCBob21lLCBsZXQncyB3b3JrIHRocm91Z2ggYSBmZXcgbW9yZSBleGFtcGxlcyBvZiBjaGFpbmluZy4KCkxldCdzIGJ1aWxkIGEgY2hhaW4gb2YgZHBseXIgY29tbWFuZHMgb25lIHN0ZXAgYXQgYSB0aW1lLCBzdGFydGluZyB3aXRoIHRoZSBzY3JpcHQgSSBqdXN0IG9wZW5lZCBmb3IgeW91LgoKPGJyLz4KCjxkaXYgc3R5bGU9ICJib3JkZXI6IDVweCBzb2xpZCBncmF5OyBwYWRkaW5nOiAxMHB4IDIwcHg7IGJhY2tncm91bmQtY29sb3I6I2Y5ZjlmOTsgYm94LXNoYWRvdzogMCAxcHggNXB4IHJnYmEoMCwgMCwgMCwgMC4yNSk7Ij4KIyMjIyBTY3JpcHQ6IGNoYWluMS5SCgoKKipzZWxlY3QoKSoqIHRoZSBmb2xsb3dpbmcgY29sdW1ucyBmcm9tICpjcmFuKi4gS2VlcCBpbiBtaW5kIHRoYXQgd2hlbiB5b3UncmUgdXNpbmcgdGhlIGNoYWluaW5nIG9wZXJhdG9yLCB5b3UgZG9uJ3QgbmVlZCB0byBzcGVjaWZ5IHRoZSBuYW1lIG9mIHRoZSBkYXRhIHRibCBpbiB5b3VyIGNhbGwgdG8gKipzZWxlY3QoKSoqLgoKMS4gaXBfaWQKMi4gY291bnRyeQozLiBwYWNrYWdlCjQuIHNpemUKClRoZSBjYWxsIHRvICoqcHJpbnQoKSoqIGF0IHRoZSBlbmQgb2YgdGhlIGNoYWluIGlzIG9wdGlvbmFsLCBidXQgbmVjZXNzYXJ5IGlmIHlvdSB3YW50IHlvdXIgcmVzdWx0cyBwcmludGVkIHRvIHRoZSBjb25zb2xlLiBOb3RlIHRoYXQgc2luY2UgdGhlcmUgYXJlIG5vIGFkZGl0aW9uYWwgYXJndW1lbnRzIHRvICoqcHJpbnQoKSoqLCB5b3UgY2FuIGxlYXZlIG9mZiB0aGUgcGFyZW50aGVzZXMgYWZ0ZXIgdGhlIGZ1bmN0aW9uIG5hbWUuIFRoaXMgaXMgYSBjb252ZW5pZW50IGZlYXR1cmUgb2YgdGhlICoqJT4lKiogb3BlcmF0b3IuCgoKYGBge3J9CmNyYW4gJT4lCiAgc2VsZWN0KGlwX2lkLCBjb3VudHJ5LCBwYWNrYWdlLCBzaXplKSAlPiUKCXByaW50CmBgYAoKPC9kaXY+Cgo8YnIgLz4KYGBge3J9CnN1Ym1pdCgpCmBgYAoKPGJyIC8+CkxldCdzIGFkZCB0byB0aGUgY2hhaW4uCgo8YnIvPgoKPGRpdiBzdHlsZT0gImJvcmRlcjogNXB4IHNvbGlkIGdyYXk7IHBhZGRpbmc6IDEwcHggMjBweDsgYmFja2dyb3VuZC1jb2xvcjojZjlmOWY5OyBib3gtc2hhZG93OiAwIDFweCA1cHggcmdiYSgwLCAwLCAwLCAwLjI1KTsiPgojIyMjIFNjcmlwdDogY2hhaW4yLlIKCgpVc2UgKiptdXRhdGUoKSoqIHRvIGFkZCBhIGNvbHVtbiBjYWxsZWQgKnNpemVfbWIqIHRoYXQgY29udGFpbnMgdGhlIHNpemUgb2YgZWFjaCBkb3dubG9hZCBpbiBtZWdhYnl0ZXMgKGkuZS4gc2l6ZSAvIDJeMjApLgoKSWYgeW91IHdhbnQgeW91ciByZXN1bHRzIHByaW50ZWQgdG8gdGhlIGNvbnNvbGUsIGFkZCBwcmludCB0byB0aGUgZW5kIG9mIHlvdXIgY2hhaW4uCgoKYGBge3J9CmNyYW4gJT4lCiAgc2VsZWN0KGlwX2lkLCBjb3VudHJ5LCBwYWNrYWdlLCBzaXplKSAlPiUKICBtdXRhdGUoc2l6ZV9tYiA9IHNpemUgLyAyXjIwKSAlPiUKICBwcmludApgYGAKPC9kaXY+Cgo8YnIgLz4KCmBgYHtyfQpzdWJtaXQoKQpgYGAKCjxiciAvPgpBIGxpdHRsZSBiaXQgbW9yZSBub3cuCgo8YnIvPgoKPGRpdiBzdHlsZT0gImJvcmRlcjogNXB4IHNvbGlkIGdyYXk7IHBhZGRpbmc6IDEwcHggMjBweDsgYmFja2dyb3VuZC1jb2xvcjojZjlmOWY5OyBib3gtc2hhZG93OiAwIDFweCA1cHggcmdiYSgwLCAwLCAwLCAwLjI1KTsiPgojIyMjIFNjcmlwdDogY2hhaW4zLlIKCgpVc2UgKipmaWx0ZXIoKSoqIHRvIHNlbGVjdCBhbGwgcm93cyBmb3Igd2hpY2ggKnNpemVfbWIqIGlzIGxlc3MgdGhhbiBvciBlcXVhbCB0byAoPD0pIDAuNS4KCklmIHlvdSB3YW50IHlvdXIgcmVzdWx0cyBwcmludGVkIHRvIHRoZSBjb25zb2xlLCBhZGQgcHJpbnQgdG8gdGhlIGVuZCBvZiB5b3VyIGNoYWluLgoKYGBge3J9CmNyYW4gJT4lCiAgc2VsZWN0KGlwX2lkLCBjb3VudHJ5LCBwYWNrYWdlLCBzaXplKSAlPiUKICBtdXRhdGUoc2l6ZV9tYiA9IHNpemUgLyAyXjIwKSAlPiUKICBmaWx0ZXIoc2l6ZV9tYiA8PSAwLjUpICU+JQogIHByaW50CmBgYAoKPC9kaXY+CgpgYGB7cn0Kc3VibWl0KCkKYGBgCgo8YnIgLz4KQW5kIGZpbmlzaCBpdCBvZmYuCgo8YnIvPgoKPGRpdiBzdHlsZT0gImJvcmRlcjogNXB4IHNvbGlkIGdyYXk7IHBhZGRpbmc6IDEwcHggMjBweDsgYmFja2dyb3VuZC1jb2xvcjojZjlmOWY5OyBib3gtc2hhZG93OiAwIDFweCA1cHggcmdiYSgwLCAwLCAwLCAwLjI1KTsiPgojIyMjIFNjcmlwdDogY2hhaW40LlIKCgoqKmFycmFuZ2UoKSoqIHRoZSByZXN1bHQgYnkgKnNpemVfbWIqLCBpbiBkZXNjZW5kaW5nIG9yZGVyLgoKSWYgeW91IHdhbnQgeW91ciByZXN1bHRzIHByaW50ZWQgdG8gdGhlIGNvbnNvbGUsIGFkZCBwcmludCB0byB0aGUgZW5kIG9mIHlvdXIgY2hhaW4uCgpgYGB7cn0KY3JhbiAlPiUKICBzZWxlY3QoaXBfaWQsIGNvdW50cnksIHBhY2thZ2UsIHNpemUpICU+JQogIG11dGF0ZShzaXplX21iID0gc2l6ZSAvIDJeMjApICU+JQogIGZpbHRlcihzaXplX21iIDw9IDAuNSkgJT4lCiAgYXJyYW5nZShkZXNjKHNpemVfbWIpKSAlPiUKICBwcmludApgYGAKCjwvZGl2PgoKYGBge3J9CnN1Ym1pdCgpCmBgYAoKPGJyIC8+CkluIHRoaXMgbGVzc29uLCB5b3UgbGVhcm5lZCBhYm91dCBncm91cGluZyBhbmQgY2hhaW5pbmcgdXNpbmcgKipkcGx5cioqLiBZb3UgY29tYmluZWQgc29tZSBvZiB0aGUgdGhpbmdzIHlvdSBsZWFybmVkIGluIHRoZSBwcmV2aW91cyBsZXNzb24gd2l0aCB0aGVzZSBtb3JlIGFkdmFuY2VkIGlkZWFzIHRvIHByb2R1Y2UgY29uY2lzZSwgcmVhZGFibGUsIGFuZCBoaWdobHkgZWZmZWN0aXZlIGNvZGUuIFdlbGNvbWUgdG8gdGhlIHdvbmRlcmZ1bCB3b3JsZCBvZiAqKmRwbHlyKiohCgo8YnIgLz4KCi0tLQoKPGNlbnRlcj5FTkQ8L2NlbnRlcj4KCi0tLQ==