1 Prerequisites

Before you get started, make sure you have all tutorial materials downloaded and R and RStudio installed on your machine. If you don’t have these installed on your machine, the following are instructions for you to install this software.

1.1 Installing R

R is a powerful, open-source statistical programming language that anyone can download for free! You can simply go to CRAN’s website and install R by following these steps:

1.1.1 Under the ‘Download’ heading, select ‘CRAN’

1.1.2 Install a CRAN Mirror

Install the CRAN mirror that’s nearest to your geographic location. For example, if you live in Orange County, California, you should install the UCLA CRAN Mirror.

1.1.3 Install your Machine’s Version of R

1.1.3.1 R for Windows

Install R for the first time.

Install R for the first time.

After you click this link, follow the instructions given in the installation.

After you click this link, follow the instructions given in the installation.

1.1.3.2 R for (Mac) OS X

After you click this link, follow the instructions given in the installation.

After you click this link, follow the instructions given in the installation.

1.1.3.3 R for Linux

Install the R version relevant to your Linux server, and follow the instructions given in the installation.

Install the R version relevant to your Linux server, and follow the instructions given in the installation.

1.2 Installing RStudio

RStudio is an open-source professional software that makes R much easier to use. Download the free, open-source license version from RStudio’s website. The installation steps are very similar to those of R’s for all operating systems.

1.3 Updating R and RStudio

If the aforementioned packages and functions start to not work after an extended period of time, you may need to update your versions of R, R packages, and RStudio software to the latest versions.

1.3.1 Updating R

To update your version of R, first close any R or RStudio windows you have open.

1.3.1.1 Updating R on Windows

Open the R GUI (x64, not i386). This is not the same as RStudio. The R GUI program icon should look very similar to this:

Install the installr package into R by typing

install.packages("installr")

into the Console.

After the package is finished installing, call the package by typing

library(installr)

into the Console. You can now update your R software and packages to the latest versions by typing

updateR() 

into the Console. Then R will walk you through a detailed and intuitive process of updating your R software and packages to the latest versions.

1.3.1.2 Updating R on (Mac) OS X

Open RStudio again, and type the following lines of code into the Console:

install.packages('devtools') #assuming it isn't already installed
library(devtools)
install_github('andreacirilloac/updateR')
library(updateR)
updateR(admin_password = "os_admin_user_password")

R will then walk you through a detailed and intuitive process of updating your R software and packages to the latest versions.

1.3.1.3 Updating R on Linux

This resource will walk you through how to update your R software and packages to the latest versions on Linux.

1.3.2 Updating R Packages Without Updating R

Updating out-of-date packages that were installed from CRAN (with install.packages()) is easy with the update.packages() function. Type this function into the RStudio Console.

update.packages()

After entering this function, it will ask you what packages you want to update. To update all packages at once, use ask = FALSE.

update.packages(ask = FALSE)

To update packages installed from devtools::install_github(), type the following function into your RStudio Console (I would also recommend saving this function in an R Script for later use):

update_github_pkgs <- function() {
  # check/load necessary packages
  # devtools package
  if (!("package:devtools" %in% search())) {
    tryCatch(require(devtools), error = function(x) {warning(x); cat("Cannot load devtools package \n")})
    on.exit(detach("package:devtools", unload=TRUE))
  }

  pkgs <- installed.packages(fields = "RemoteType")
  github_pkgs <- pkgs[pkgs[, "RemoteType"] %in% "github", "Package"]

  print(github_pkgs)
  lapply(github_pkgs, function(pac) {
    message("Updating ", pac, " from GitHub...")

    repo = packageDescription(pac, fields = "GithubRepo")
    username = packageDescription(pac, fields = "GithubUsername")

    install_github(repo = paste0(username, "/", repo))
  })
}

Then call the function.

update_github_pkgs()

1.3.3 Updating RStudio

To update RStudio, open RStudio and go to Help > Check for Updates to install the newest version.

2 Data Types

R has a wide variety of data types including vectors (numerical, character, logical), matrices, lists, and data frames. Matrices are composed of vectors, and data frames are composed of lists.

2.1 Vectors

a <- c(1,2,5.3,6,-2,4) # numeric vector
b <- c("one","two","three") # character vector
c <- c(TRUE,TRUE,TRUE,FALSE,TRUE,FALSE) #logical vector

You can refer to elements of a vector using subscripts.

a[c(2,4)] # 2nd and 4th elements of vector
## [1] 2 6

2.2 Matrices

All columns in a matrix must have the same mode (numeric, character, etc.) and the same length. The general format is

mymatrix <- matrix(vector, nrow=r, ncol=c, byrow=FALSE,
   dimnames=list(char_vector_rownames, char_vector_colnames))

byrow=TRUE indicates that the matrix should be filled by rows. byrow=FALSE indicates that the matrix should be filled by columns (the default). dimnames provides optional labels for the columns and rows.

# generates 5 x 4 numeric matrix
x <- matrix(1:20, nrow=5,ncol=4)

# another example
cells <- c(1,26,24,68)
rnames <- c("R1", "R2")
cnames <- c("C1", "C2")
mymatrix <- matrix(cells, nrow=2, ncol=2, byrow=TRUE,
  dimnames=list(rnames, cnames))

Like in vectors, you can identify rows, columns or elements using subscripts.

x[,4] # 4th column of matrix
## [1] 16 17 18 19 20
x[3,] # 3rd row of matrix
## [1]  3  8 13 18
x[2:4,1:3] # rows 2,3,4 of columns 1,2,3
##      [,1] [,2] [,3]
## [1,]    2    7   12
## [2,]    3    8   13
## [3,]    4    9   14

2.3 Lists

An ordered collection of objects (components). A list allows you to gather a variety of (possibly unrelated) objects under one name.

# example of a list with 4 components -
# a string, a numeric vector, a matrix, and a scalar
list1 <- list(name="Fred", mynumbers=a, mymatrix=x, age=5.3)
list2 <- list(character="Louise", show="Bob's Burgers", time=830)

# example of a list containing two lists
ultimate_list <- c(list1,list2)

Identify elements of a list using the [[]] convention.

ultimate_list[[2]] # 2nd component of the list
## [1]  1.0  2.0  5.3  6.0 -2.0  4.0
ultimate_list[["mynumbers"]] # component named mynumbers in list
## [1]  1.0  2.0  5.3  6.0 -2.0  4.0

2.4 Data Frames

A data frame is more general than a matrix, in that different columns can have different modes (numeric, character, factor, etc.). This is similar to SAS and SPSS datasets.

d <- c(1,2,3,4)
e <- c("red", "white", "red", NA)
f <- c(TRUE,TRUE,TRUE,FALSE)
mydata <- data.frame(d,e,f)
names(mydata) <- c("ID","Color","Passed") # variable names

There are a variety of ways to identify the elements of a data frame.

mydata[2:3] # columns 2,3 of data frame
mydata[c("ID","Passed")] # columns ID and Passed from data frame
mydata$Color # variable Color in the data frame
## [1] red   white red   <NA> 
## Levels: red white

2.5 Useful Functions for All Data Types

length(object) # number of elements or components
str(object)    # structure of an object
class(object)  # class or type of an object
names(object)  # names

c(object,object,...)       # combine objects into a vector
cbind(object, object, ...) # combine objects as columns
rbind(object, object, ...) # combine objects as rows

object     # prints the object

ls()       # list current objects
rm(object) # delete an object

newobject <- edit(object) # edit copy and save as newobject
fix(object)               # edit in place 

3 Importing and Working with Data

3.1 Reading in the Dataset

Today, we’re going to be using one of the over 40,000 open datasets available in NASA’s Open Data Repository. This “Meteorite Landings” dataset is from the Meteoritical Soceity and contains information on all of the known meteorite landings, going as far back as the early 1800’s.

This data can be downloaded at this link.

To read in our data (.csv) file, we can use the following command:

meteorite_landings <- read.csv("data/Meteorite_Landings.csv")

Note 1: The syntax above assumes the .csv file is saved to the working directory. To change the working directory, navigate to Session > Set Working Directory > Choose Working Directory or type setwd("C:/Users/path") into the Console.

Note 2: read.csv reads the file, but we can’t use the data unless we assign it to a variable. We can think of a variable as a container with a name, such as x, current_temperature, or subject_id that contains one or more values. We can create a new variable in R and assign a value to it using <-.

Once you run the above command, you should see a meteorite_landings object in the “Environment” box in the top right corner of RStudio.

Now, let’s explore the dataset.

3.2 Viewing the Dataset

If we want to view our entire dataset, we can type View(meteorite_landings) into the Console. However, for large data sets it is much faster and more convenient to use the function head to display only the first few rows of data.

head(meteorite_landings)

3.2.1 Structure

To view the structure of, or data types for, each variable in a dataset, use the str, or “structure” function.

str(meteorite_landings)
## 'data.frame':    45716 obs. of  10 variables:
##  $ name       : Factor w/ 45716 levels "Österplana 002",..: 68 69 73 77 473 484 496 497 502 521 ...
##  $ id         : num  1 2 6 10 370 379 390 392 398 417 ...
##  $ nametype   : Factor w/ 2 levels "Relict","Valid": 2 2 2 2 2 2 2 2 2 2 ...
##  $ recclass   : Factor w/ 466 levels "Acapulcoite",..: 333 197 85 1 339 85 360 190 339 242 ...
##  $ mass       : num  21 720 107000 1914 780 ...
##  $ fall       : Factor w/ 2 levels "Fell","Found": 1 1 1 1 1 1 1 1 1 1 ...
##  $ year       : Factor w/ 270 levels "","01/01/1583 12:00:00 AM",..: 124 197 198 223 148 165 195 59 176 166 ...
##  $ reclat     : num  50.8 56.2 54.2 16.9 -33.2 ...
##  $ reclong    : num  6.08 10.23 -113 -99.9 -64.95 ...
##  $ GeoLocation: Factor w/ 17101 levels "","(-1.002780, 37.150280)",..: 16779 16983 16923 9106 844 14808 16496 16453 784 721 ...

3.2.1.1 Data Types

Note that meteorite_landings is a data frame. You can think of this structure as a spreadsheet in MS Excel. Data frames are very useful for storing data, and you will use them frequently when programming in R. A typical data frame of experimental data contains individual observations in rows and variables in columns. Also note that there are 2 different data types in our dataset:

  1. Factor - A Class (not a string or number!)

  2. Num - Numeric, float/number than can include decimal places

3.2.2 Dimensions

We can see the shape, or dimensions, of the data frame with the function dim:

dim(meteorite_landings)
## [1] 45716    10

This tells us that our data frame, meteorite_landings, has 45,716 rows and 10 columns.

3.2.2.1 Indexing

If we want to get a single value from the data frame, we can provide an index in square brackets. The first number specifies the row and the second the column:

# first value in meteorite_landings, row 1, column 1
meteorite_landings[1, 1]
## [1] Aachen
## 45716 Levels: Österplana 002 Österplana 003 ... Zvonkov
# middle value in meteorite_landings, row 22858, column 5
meteorite_landings[22858, 5]
## [1] 4.7

3.2.2.2 Subsetting

If we want to select more than one row or column, we can use the function c, which stands for combine. For example, to pick columns 1 and 5 from rows 1, 3, and 5, we can do this:

meteorite_landings[c(1, 3, 5), c(1, 5)]

We frequently want to select contiguous rows or columns, such as the first ten rows, or columns 3 through 7. You can use c for this, but it’s more convenient to use the : operator. This special function generates sequences of numbers:

1:5
## [1] 1 2 3 4 5
3:12
##  [1]  3  4  5  6  7  8  9 10 11 12

For example, we can select the first 2 columns of values for the first four rows like this:

meteorite_landings[1:4, 1:2]

or the first 5 columns of rows 5 to 10 like this:

meteorite_landings[5:10, 1:5]

If you want to select all rows or all columns, leave that index value empty.

# All columns from row 5
meteorite_landings[5, ]
# All rows from columns 6-8
meteorite_landings[, 6:8]

If you leave both index values empty (i.e., meteorite_landings[,]), you get the entire data frame.

3.3 Mathematical Operations

Now let’s perform some common mathematical operations to learn more about our meteorite data. When analyzing data we often want to look at partial statistics, such as the maximum value per id or the average value per year. One way to do this is to select the data we want to create a new temporary data frame, and then perform the calculation on this subset:

# first 5 rows, columns 1,5
first_fifty_meteors <- meteorite_landings[1:50, 5]
# max mass of first 50 meteors
max(first_fifty_meteors, na.rm = T)
## [1] 2e+06
# also correct:
max(meteorite_landings[1:50, 5], na.rm = T)
## [1] 2e+06

R also has functions for other common calculations, e.g. finding the minimum, mean, median, and standard deviation of the data:

# minimum mass of first 50 meteors
min(first_fifty_meteors, na.rm = T)
## [1] 21
# mean mass of first 50 meteors
mean(first_fifty_meteors, na.rm = T)
## [1] 54289.93
# median mass of first 50 meteors
median(first_fifty_meteors, na.rm = T)
## [1] 2600
# standard deviation of the mass of the first 50 meteors
sd(first_fifty_meteors, na.rm = T)
## [1] 289093.5

R also has a function that summaries the previous common calculations. For every column in the data frame, the function summary calculates: the minimum value, the first quartile, the median, the mean, the third quartile, and the max value, giving helpful details about the sample distribution.

# Summarize function
summary(meteorite_landings)
##               name             id          nametype        recclass    
##  Österplana 002:    1   Min.   :    1   Relict:   75   L6     : 8285  
##  Österplana 003:    1   1st Qu.:12689   Valid :45641   H5     : 7142  
##  Österplana 004:    1   Median :24262                  L5     : 4796  
##  Österplana 005:    1   Mean   :26890                  H6     : 4528  
##  Österplana 006:    1   3rd Qu.:40657                  H4     : 4211  
##  Österplana 007:    1   Max.   :57458                  LL5    : 2766  
##  (Other)        :45710                                  (Other):13988  
##       mass             fall                  year           reclat      
##  Min.   :       0   Fell : 1107   1/1/2003 0:00: 3323   Min.   :-87.37  
##  1st Qu.:       7   Found:44609   1/1/1979 0:00: 3046   1st Qu.:-76.71  
##  Median :      33                 1/1/1998 0:00: 2697   Median :-71.50  
##  Mean   :   13278                 1/1/2006 0:00: 2456   Mean   :-39.12  
##  3rd Qu.:     203                 1/1/1988 0:00: 2296   3rd Qu.:  0.00  
##  Max.   :60000000                 1/1/2002 0:00: 2078   Max.   : 81.17  
##  NA's   :131                      (Other)      :29820   NA's   :7315    
##     reclong                          GeoLocation   
##  Min.   :-165.43                           : 7315  
##  1st Qu.:   0.00   (0.000000, 0.000000)    : 6214  
##  Median :  35.67   (-71.500000, 35.666670) : 4761  
##  Mean   :  61.07   (-84.000000, 168.000000): 3040  
##  3rd Qu.: 157.17   (-72.000000, 26.000000) : 1505  
##  Max.   : 354.47   (-79.683330, 159.750000):  657  
##  NA's   :7315      (Other)                 :22224

4 Manipulating and sorting dataframes with dplyr

4.1 What is dplyr?

dplyr is an R package in the tidyverse that aims to make data wrangling/cleaning/manipulation more human-readable. The dplyr package makes these steps fast and easy:

  • By constraining your options, it helps you think about your data manipulation challenges.

  • It provides simple “verbs”, functions that correspond to the most common data manipulation tasks, to help you translate your thoughts into code.

  • It uses efficient backends, so you spend less time waiting for the computer.

This tutorial introduces you to dplyr’s basic set of tools, and shows you how to apply them to data frames. Aside from this tutorial, a helpful resource for getting familiar with dplyr is this comprehensive RStudio cheatsheet.

Side Note: dplyr also supports databases via the dbplyr package. Once you install dbplyr, you can read vignette("dbplyr") to learn more.

4.2 Getting Started with dplyr

If you don’t already have dplyr or the tidyverse installed, you can install the dplyr package by typing the following command into the Console:

install.packages("dplyr")

Once you have dplyr installed, load it into your R session with

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

dplyr aims to provide a function for each basic verb of data manipulation:

Function Purpose
filter() to select cases based on their values
arrange() to reorder the cases
select(), rename() to select variables based on their names
mutate(), transmute() to add new variables that are functions of existing variables
summarise() to condense multiple values to a single value
sample_n(), sample_frac() to take random samples

In this tutorial, we’ll spend some time working with each individual function.

4.2.1 Piping Operator (%>%)

The operators %>% pipe their left-hand side values forward into expressions that appear on the right-hand side, i.e. one can replace f(x) with x %>% f(), where %>% is the (main) pipe-operator. When coupling several function calls with the pipe-operator, the benefit will become more apparent. Consider this pseudo example:

the_data <- read.csv('/path/to/data/file.csv') %>%
  subset(variable_a > x) %>%
  transform(variable_c = variable_a/variable_b) %>%
  head(100)

Four operations are performed to arrive at the desired data set, and they are written in a natural order: the same as the order of execution. Also, no temporary variables are needed. If yet another operation is required, it is straight-forward to add to the sequence of operations wherever it may be needed.

4.3 Filter rows with filter()

filter() allows you to select a subset of rows in a data frame. Like all single verbs, the first argument is the data frame. The second and subsequent arguments refer to variables within that data frame, selecting rows where the expression is TRUE.

For example, we can select all meteorites in the L5 class with mass greater than or equal to 10,000 grams with:

meteorite_landings %>% 
  filter(mass >= 10000 & recclass == "L5")

This is roughly equivalent to this base R code:

meteorite_landings[meteorite_landings$mass >= 10000 & meteorite_landings$recclass == "L5", ]

4.4 Arrange rows with arrange()

arrange() works similarly to filter() except that instead of filtering or selecting rows, it reorders them. It takes a data frame and a set of column names (or more complicated expressions) to order by. If you provide more than one column name, each additional column will be used to break ties in the values of preceding columns:

meteorite_landings %>% 
  arrange(reclat, reclong, mass)

Use desc() to order a column in descending order:

meteorite_landings %>% 
  arrange(desc(mass))

4.5 Select columns with select()

Often you work with large datasets with many columns but only a few are actually of interest to you. select() allows you to rapidly zoom in on a useful subset using operations that usually only work on numeric variable positions:

# Select columns by name
meteorite_landings %>% 
  select(name, recclass, mass)
# Select all columns between name and year (inclusive)
meteorite_landings %>% 
  select(name:year)
# Select all columns except those from name to year (inclusive)
meteorite_landings %>% 
  select(-(name:year))

There are a number of helper functions you can use within select(), like starts_with(), ends_with(), matches() and contains(). These let you quickly match larger blocks of variables that meet some criterion. See ?select for more details.

You can rename variables with select() by using named arguments:

meteorite_landings %>% 
  select(mass_g = mass)

But because select() drops all the variables not explicitly mentioned, it’s not that useful. Instead, use rename():

meteorite_landings %>% 
  rename(mass_g = mass)

4.6 Add new columns with mutate()

Besides selecting sets of existing columns, it’s often useful to add new columns that are functions of existing columns. This is the job of mutate():

meteorite_landings %>% 
  mutate(mass_kg = mass/1000)

dplyr::mutate() is similar to the base transform(), but allows you to refer to columns that you’ve just created.

If you only want to keep the new variables, use transmute():

meteorite_landings %>% 
  transmute(mass_kg = mass/1000)

4.7 Summarise values with summarise()

The last verb is summarise(). It collapses a data frame to a single row.

meteorite_landings %>% 
  summarise(mean_mass = mean(mass, na.rm = T))

It’s not that useful until we learn the group_by() verb below.

meteorite_landings %>% 
  group_by(recclass) %>% 
  summarise(mean_mass = mean(mass, na.rm = T))

4.8 Randomly sample rows with sample_n() and sample_frac()

You can use sample_n() and sample_frac() to take a random sample of rows: use sample_n() for a fixed number and sample_frac() for a fixed fraction.

meteorite_landings %>% 
  sample_n(10)
meteorite_landings %>% 
  sample_frac(0.01)

Use replace = TRUE to perform a bootstrap sample. If needed, you can weight the sample with the weight argument.

4.9 Ten dplyr Tricks!

4.9.1 Are you often selecting the same columns over and over again?

You can make a vector of pre-identified columns once and then refer to them using one_of() or !! (even shorter).

library(dplyr)

cols <- c("name", "reclat", "reclong")

ex1 <- meteorite_landings %>% 
  select(!!cols)

head(ex1)

4.9.2 Select columns via regex

If you have matching patterns, you can use starts_with(), contains(), or ends_with. But what if your pattern isn’t that exact? Simple: enter regex into matches().

library(dplyr)

ex2 <- iris %>% 
  select(matches("S.+th"))

head(ex2)

4.9.3 Reordering your columns

If you just want to bring one or more columns to the front, you can use everything() to add all the remaining columns.

library(dplyr)

ex3 <- meteorite_landings %>% 
  select(id, everything())

head(ex3)

4.9.4 Renaming all variables in one go

One command to get them all in lower case, and one more to replace “..g.” in the mass variable.

library(dplyr)
library(stringr)

ex4 <- meteorite_landings %>% 
  rename_all(tolower) %>% 
  rename_all(~str_replace(., "..g.", ""))

head(ex4)

4.9.5 Cleaning up your observations in one go

The select_all/if/at and rename_all/if/at functions will only modify the variable names, not the observations. If you want to change those, use the mutate variant.

library(dplyr)
library(stringr)

ex5 <- meteorite_landings %>% 
  select(name, nametype, fall) %>% 
  mutate_all(tolower) %>% 
  mutate_all(~str_replace_all(., " ", "_"))

head(ex5)

4.9.6 Finding the 5 highest/lowest values

You can use top_n to find the 5 meteorites with the highest mass without ordering them first.

library(dplyr)

meteorite_landings %>% 
  top_n(5, mass)

4.9.7 Adding the amount of observations

You can add the amount of observations without summarising them yourself. If you don’t like the default column name n, you can change it again with a rename() statement.

library(dplyr)

ex7 <- meteorite_landings %>% 
  add_count(recclass) %>% 
  rename(n_recclass = n)

head(ex7)

4.9.8 Making new discrete variables

case_when() can be a very powerful tool to make new discrete variables based on other columns.

library(dplyr)

ex8 <- starwars %>% 
  select(name, species, homeworld, birth_year, hair_color) %>% 
  mutate(new_group = case_when(
    species == "Droid" ~ "Robot",
    homeworld == "Tatooine" & hair_color == "blond" ~ "Blond Tatooinian",
    homeworld == "Tatooine" ~ "Other Tatooinian",
    hair_color == "blond" ~ "Blond non-Tatooinian",
    TRUE ~ "Other Human"))

head(ex8)

4.9.9 Going rowwise…

Mutating with aggregate functions by default will take the average/sum/… of the entire column. Via adding rowwise() you can aggregate within an observation.

library(dplyr)

ex9 <- iris %>% 
  select(contains("Length")) %>% 
  rowwise() %>% 
  mutate(avg_length = mean(c(Petal.Length, Sepal.Length)))

head(ex9)

4.9.10 Changing your column names after summarise_if

If you’ve used the summarise_all/if/at variants before, you know that the variable name by default does not get changed. If you want a modified name, you can wrap your function inside funs() and add a tag that will be added to the variable name.

library(dplyr)

iris %>% 
  summarise_if(is.numeric, funs(avg = mean))

5 Additional Resources

5.1 Helpful Packages To Get Started With

To learn more about and read documentation for each of these packages, either type ?<packagename> into your RStudio console or search <packagename> in the CRAN repository.

  • blob - for storing blob (binary) data
  • boot - bootstrap functions
  • broom - tidies statistical models into data frames
  • caret - streamlines the process for creating predictive models (Helpful Website)
  • cluster - clustering methods
  • coefplot - plots coefficients from fitted models
  • data.table - extension of data.frames
  • devtools - tools to make an R developer’s life easier
  • dplyr - part of the Tidyverse, convenient data manipulation, munging, and cleaning in R
  • forcats - part of the Tidyverse, solves common problems with factors
  • gbm - gradient boosting models, can be integrated with caret
  • ggplot2 - part of the Tidyverse, data visualization in R that follows the “Grammar of Graphics”
  • glmnet - lasso and elastic-net regularized GLMs, can be integrated with caret
  • gridExtra - miscellaneous functions for “grid” graphics
  • hms - for working with time-of-day values
  • ISLR - data for an “Introduction to Statistical Learning with Applications in R”
  • lubridate - for working with dates and date-times
  • MASS - functions and datasets for applied statistics
  • pdp - partial dependence plots
  • pls - partial least squares and principal component regression, can be integrated with caret
  • plyr - part of the Tidyverse, tools for split-apply-combine analyses
  • pROC - displays and analyzes ROC curves
  • purrr - part of the Tidyverse, enhances R’s functional programming set of tools for working with functions and vectors
  • randomForest - random forest models, can be integrated with caret
  • readr - part of the Tidyverse, provides a fast and friendly way to read rectangular data
  • readxl - part of the Tidyverse,makes reading Excel files much easier
  • rpart - decision tree models, can be integrated with caret
  • rpart.plot - plotting of decision tree models
  • stringr - part of the Tidyverse, makes working with string objects much easier
  • tibble - part of the Tidyverse, creates data.frames that are easier to work with
  • tidyr - part of the Tidyverse, provides a set of functions to help you get to tidy data
  • tidyverse - set of helpful packages for tidier data (Helpful Website)
  • xgboost - eXtreme Gradient Boosting models, can be integrated with caret

6 Thank you for attending my tutorial on “Data Manipulation with Tidy Tools.”

All tutorial materials can be found here on my GitHub.

Thanks so much for listening and following along, and I hope you’ll have a great rest of your hackathon!


A work by Alyssa Columbus.

hello@alyssacolumbus.com

LS0tDQp0aXRsZTogIjxicj5EYXRhIE1hbmlwdWxhdGlvbiB3aXRoIFRpZHkgVG9vbHMiDQphdXRob3I6ICJBbHlzc2EgQ29sdW1idXMiDQpkYXRlOiAnTWF5IDE4LCAyMDE5Jw0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDoNCiAgICAgIHRvY19jb2xsYXBzZWQ6IGZhbHNlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGZpZ19jYXB0aW9uOiB0cnVlDQogICAgY3NzOiBzdHlsZXMuY3NzDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIyBQcmVyZXF1aXNpdGVzDQpCZWZvcmUgeW91IGdldCBzdGFydGVkLCBtYWtlIHN1cmUgeW91IGhhdmUgW2FsbCB0dXRvcmlhbCBtYXRlcmlhbHNdKGh0dHBzOi8vZ2l0aHViLmNvbS9hY29sdW0vY29uZmVyZW5jZS1wcmVzZW50YXRpb25zL3RyZWUvbWFzdGVyL0RhdGElMjBNYW5pcHVsYXRpb24lMjB3aXRoJTIwVGlkeSUyMFRvb2xzKXt0YXJnZXQ9Il9ibGFuayJ9IGRvd25sb2FkZWQgYW5kIFtSXShodHRwczovL3d3dy5yLXByb2plY3Qub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSBhbmQgW1JTdHVkaW9dKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tLyl7dGFyZ2V0PSJfYmxhbmsifSBpbnN0YWxsZWQgb24geW91ciBtYWNoaW5lLiBJZiB5b3UgZG9uJ3QgaGF2ZSB0aGVzZSBpbnN0YWxsZWQgb24geW91ciBtYWNoaW5lLCB0aGUgZm9sbG93aW5nIGFyZSBpbnN0cnVjdGlvbnMgZm9yIHlvdSB0byBpbnN0YWxsIHRoaXMgc29mdHdhcmUuDQoNCiMjIEluc3RhbGxpbmcgUg0KUiBpcyBhIHBvd2VyZnVsLCBvcGVuLXNvdXJjZSBzdGF0aXN0aWNhbCBwcm9ncmFtbWluZyBsYW5ndWFnZSB0aGF0IGFueW9uZSBjYW4gZG93bmxvYWQgZm9yIGZyZWUhIFlvdSBjYW4gc2ltcGx5IGdvIHRvIFtDUkFOJ3Mgd2Vic2l0ZV0oaHR0cHM6Ly93d3cuci1wcm9qZWN0Lm9yZy8pe3RhcmdldD0iX2JsYW5rIn0gYW5kIGluc3RhbGwgUiBieSBmb2xsb3dpbmcgdGhlc2Ugc3RlcHM6DQoNCiMjIyBVbmRlciB0aGUgJ0Rvd25sb2FkJyBoZWFkaW5nLCBzZWxlY3QgJ0NSQU4nDQohW10oaW1nL0NSQU4ucG5nKQ0KDQojIyMgSW5zdGFsbCBhIENSQU4gTWlycm9yDQoNCkluc3RhbGwgdGhlIENSQU4gbWlycm9yIHRoYXQncyBuZWFyZXN0IHRvIHlvdXIgZ2VvZ3JhcGhpYyBsb2NhdGlvbi4gRm9yIGV4YW1wbGUsIGlmIHlvdSBsaXZlIGluIE9yYW5nZSBDb3VudHksIENhbGlmb3JuaWEsIHlvdSBzaG91bGQgaW5zdGFsbCB0aGUgW1VDTEEgQ1JBTiBNaXJyb3IuXShodHRwOi8vY3Jhbi5zdGF0LnVjbGEuZWR1Lyl7dGFyZ2V0PSJfYmxhbmsifQ0KDQojIyMgSW5zdGFsbCB5b3VyIE1hY2hpbmUncyBWZXJzaW9uIG9mIFINCiFbXShpbWcvRG93bmxvYWRSLnBuZykNCg0KIyMjIyBSIGZvciBXaW5kb3dzDQohW0luc3RhbGwgUiBmb3IgdGhlIGZpcnN0IHRpbWUuXShpbWcvV2luZG93c1IxLnBuZykNCg0KIVtBZnRlciB5b3UgY2xpY2sgdGhpcyBsaW5rLCBmb2xsb3cgdGhlIGluc3RydWN0aW9ucyBnaXZlbiBpbiB0aGUgaW5zdGFsbGF0aW9uLl0oaW1nL1dpbmRvd3NSMi5wbmcpDQoNCiMjIyMgUiBmb3IgKE1hYykgT1MgWA0KIVtBZnRlciB5b3UgY2xpY2sgdGhpcyBsaW5rLCBmb2xsb3cgdGhlIGluc3RydWN0aW9ucyBnaXZlbiBpbiB0aGUgaW5zdGFsbGF0aW9uLl0oaW1nL01hY1IxLnBuZykNCg0KIyMjIyBSIGZvciBMaW51eA0KIVtJbnN0YWxsIHRoZSBSIHZlcnNpb24gcmVsZXZhbnQgdG8geW91ciBMaW51eCBzZXJ2ZXIsIGFuZCBmb2xsb3cgdGhlIGluc3RydWN0aW9ucyBnaXZlbiBpbiB0aGUgaW5zdGFsbGF0aW9uLl0oaW1nL0xpbnV4UjEucG5nKQ0KDQojIyBJbnN0YWxsaW5nIFJTdHVkaW8NClJTdHVkaW8gaXMgYW4gb3Blbi1zb3VyY2UgcHJvZmVzc2lvbmFsIHNvZnR3YXJlIHRoYXQgbWFrZXMgUiBtdWNoIGVhc2llciB0byB1c2UuIERvd25sb2FkIHRoZSBmcmVlLCBvcGVuLXNvdXJjZSBsaWNlbnNlIHZlcnNpb24gZnJvbSBbUlN0dWRpbydzIHdlYnNpdGVdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Byb2R1Y3RzL3JzdHVkaW8vZG93bmxvYWQvI2Rvd25sb2FkKXt0YXJnZXQ9Il9ibGFuayJ9LiBUaGUgaW5zdGFsbGF0aW9uIHN0ZXBzIGFyZSB2ZXJ5IHNpbWlsYXIgdG8gdGhvc2Ugb2YgUidzIGZvciBhbGwgb3BlcmF0aW5nIHN5c3RlbXMuDQoNCiMjIFVwZGF0aW5nIFIgYW5kIFJTdHVkaW8NCklmIHRoZSBhZm9yZW1lbnRpb25lZCBwYWNrYWdlcyBhbmQgZnVuY3Rpb25zIHN0YXJ0IHRvIG5vdCB3b3JrIGFmdGVyIGFuIGV4dGVuZGVkIHBlcmlvZCBvZiB0aW1lLCB5b3UgbWF5IG5lZWQgdG8gdXBkYXRlIHlvdXIgdmVyc2lvbnMgb2YgUiwgUiBwYWNrYWdlcywgYW5kIFJTdHVkaW8gc29mdHdhcmUgdG8gdGhlIGxhdGVzdCB2ZXJzaW9ucy4NCg0KIyMjIFVwZGF0aW5nIFINClRvIHVwZGF0ZSB5b3VyIHZlcnNpb24gb2YgUiwgZmlyc3QgY2xvc2UgYW55IFIgb3IgUlN0dWRpbyB3aW5kb3dzIHlvdSBoYXZlIG9wZW4uDQoNCiMjIyMgVXBkYXRpbmcgUiBvbiBXaW5kb3dzDQpPcGVuIHRoZSBSIEdVSSAoeDY0LCBub3QgaTM4NikuIFRoaXMgaXMgKipub3QqKiB0aGUgc2FtZSBhcyBSU3R1ZGlvLiBUaGUgUiBHVUkgcHJvZ3JhbSBpY29uIHNob3VsZCBsb29rIHZlcnkgc2ltaWxhciB0byB0aGlzOg0KDQohW10oaW1nL1JHdWkucG5nKQ0KDQoNCkluc3RhbGwgdGhlIGBpbnN0YWxscmAgcGFja2FnZSBpbnRvIFIgYnkgdHlwaW5nDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KaW5zdGFsbC5wYWNrYWdlcygiaW5zdGFsbHIiKQ0KYGBgICANCg0KaW50byB0aGUgQ29uc29sZS4NCg0KQWZ0ZXIgdGhlIHBhY2thZ2UgaXMgZmluaXNoZWQgaW5zdGFsbGluZywgY2FsbCB0aGUgcGFja2FnZSBieSB0eXBpbmcgDQoNCmBgYHtyLCBldmFsPUZBTFNFfSANCmxpYnJhcnkoaW5zdGFsbHIpDQpgYGAgDQoNCmludG8gdGhlIENvbnNvbGUuIFlvdSBjYW4gbm93IHVwZGF0ZSB5b3VyIFIgc29mdHdhcmUgYW5kIHBhY2thZ2VzIHRvIHRoZSBsYXRlc3QgdmVyc2lvbnMgYnkgdHlwaW5nIA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCnVwZGF0ZVIoKSANCmBgYA0KDQppbnRvIHRoZSBDb25zb2xlLiBUaGVuIFIgd2lsbCB3YWxrIHlvdSB0aHJvdWdoIGEgZGV0YWlsZWQgYW5kIGludHVpdGl2ZSBwcm9jZXNzIG9mIHVwZGF0aW5nIHlvdXIgUiBzb2Z0d2FyZSBhbmQgcGFja2FnZXMgdG8gdGhlIGxhdGVzdCB2ZXJzaW9ucy4NCg0KIyMjIyBVcGRhdGluZyBSIG9uIChNYWMpIE9TIFgNCg0KT3BlbiBSU3R1ZGlvIGFnYWluLCBhbmQgdHlwZSB0aGUgZm9sbG93aW5nIGxpbmVzIG9mIGNvZGUgaW50byB0aGUgQ29uc29sZToNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQppbnN0YWxsLnBhY2thZ2VzKCdkZXZ0b29scycpICNhc3N1bWluZyBpdCBpc24ndCBhbHJlYWR5IGluc3RhbGxlZA0KbGlicmFyeShkZXZ0b29scykNCmluc3RhbGxfZ2l0aHViKCdhbmRyZWFjaXJpbGxvYWMvdXBkYXRlUicpDQpsaWJyYXJ5KHVwZGF0ZVIpDQp1cGRhdGVSKGFkbWluX3Bhc3N3b3JkID0gIm9zX2FkbWluX3VzZXJfcGFzc3dvcmQiKQ0KYGBgDQoNClIgd2lsbCB0aGVuIHdhbGsgeW91IHRocm91Z2ggYSBkZXRhaWxlZCBhbmQgaW50dWl0aXZlIHByb2Nlc3Mgb2YgdXBkYXRpbmcgeW91ciBSIHNvZnR3YXJlIGFuZCBwYWNrYWdlcyB0byB0aGUgbGF0ZXN0IHZlcnNpb25zLg0KDQojIyMjIFVwZGF0aW5nIFIgb24gTGludXgNCg0KW1RoaXMgcmVzb3VyY2VdKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzEwNDc2NzEzL2hvdy10by11cGdyYWRlLXItaW4tdWJ1bnR1KSB3aWxsIHdhbGsgeW91IHRocm91Z2ggaG93IHRvIHVwZGF0ZSB5b3VyIFIgc29mdHdhcmUgYW5kIHBhY2thZ2VzIHRvIHRoZSBsYXRlc3QgdmVyc2lvbnMgb24gTGludXguDQoNCiMjIyBVcGRhdGluZyBSIFBhY2thZ2VzIFdpdGhvdXQgVXBkYXRpbmcgUg0KDQpVcGRhdGluZyBvdXQtb2YtZGF0ZSBwYWNrYWdlcyB0aGF0IHdlcmUgaW5zdGFsbGVkIGZyb20gQ1JBTiAod2l0aCBgaW5zdGFsbC5wYWNrYWdlcygpYCkgaXMgZWFzeSB3aXRoIHRoZSBgdXBkYXRlLnBhY2thZ2VzKClgIGZ1bmN0aW9uLiBUeXBlIHRoaXMgZnVuY3Rpb24gaW50byB0aGUgUlN0dWRpbyBDb25zb2xlLg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCnVwZGF0ZS5wYWNrYWdlcygpDQpgYGANCg0KQWZ0ZXIgZW50ZXJpbmcgdGhpcyBmdW5jdGlvbiwgaXQgd2lsbCBhc2sgeW91IHdoYXQgcGFja2FnZXMgeW91IHdhbnQgdG8gdXBkYXRlLiBUbyB1cGRhdGUgYWxsIHBhY2thZ2VzIGF0IG9uY2UsIHVzZSBgYXNrID0gRkFMU0VgLg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCnVwZGF0ZS5wYWNrYWdlcyhhc2sgPSBGQUxTRSkNCmBgYA0KDQpUbyB1cGRhdGUgcGFja2FnZXMgaW5zdGFsbGVkIGZyb20gYGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigpYCwgdHlwZSB0aGUgZm9sbG93aW5nIGZ1bmN0aW9uIGludG8geW91ciBSU3R1ZGlvIENvbnNvbGUgKEkgd291bGQgYWxzbyByZWNvbW1lbmQgc2F2aW5nIHRoaXMgZnVuY3Rpb24gaW4gYW4gUiBTY3JpcHQgZm9yIGxhdGVyIHVzZSk6DQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KdXBkYXRlX2dpdGh1Yl9wa2dzIDwtIGZ1bmN0aW9uKCkgew0KICAjIGNoZWNrL2xvYWQgbmVjZXNzYXJ5IHBhY2thZ2VzDQogICMgZGV2dG9vbHMgcGFja2FnZQ0KICBpZiAoISgicGFja2FnZTpkZXZ0b29scyIgJWluJSBzZWFyY2goKSkpIHsNCiAgICB0cnlDYXRjaChyZXF1aXJlKGRldnRvb2xzKSwgZXJyb3IgPSBmdW5jdGlvbih4KSB7d2FybmluZyh4KTsgY2F0KCJDYW5ub3QgbG9hZCBkZXZ0b29scyBwYWNrYWdlIFxuIil9KQ0KICAgIG9uLmV4aXQoZGV0YWNoKCJwYWNrYWdlOmRldnRvb2xzIiwgdW5sb2FkPVRSVUUpKQ0KICB9DQoNCiAgcGtncyA8LSBpbnN0YWxsZWQucGFja2FnZXMoZmllbGRzID0gIlJlbW90ZVR5cGUiKQ0KICBnaXRodWJfcGtncyA8LSBwa2dzW3BrZ3NbLCAiUmVtb3RlVHlwZSJdICVpbiUgImdpdGh1YiIsICJQYWNrYWdlIl0NCg0KICBwcmludChnaXRodWJfcGtncykNCiAgbGFwcGx5KGdpdGh1Yl9wa2dzLCBmdW5jdGlvbihwYWMpIHsNCiAgICBtZXNzYWdlKCJVcGRhdGluZyAiLCBwYWMsICIgZnJvbSBHaXRIdWIuLi4iKQ0KDQogICAgcmVwbyA9IHBhY2thZ2VEZXNjcmlwdGlvbihwYWMsIGZpZWxkcyA9ICJHaXRodWJSZXBvIikNCiAgICB1c2VybmFtZSA9IHBhY2thZ2VEZXNjcmlwdGlvbihwYWMsIGZpZWxkcyA9ICJHaXRodWJVc2VybmFtZSIpDQoNCiAgICBpbnN0YWxsX2dpdGh1YihyZXBvID0gcGFzdGUwKHVzZXJuYW1lLCAiLyIsIHJlcG8pKQ0KICB9KQ0KfQ0KYGBgDQoNClRoZW4gY2FsbCB0aGUgZnVuY3Rpb24uDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KdXBkYXRlX2dpdGh1Yl9wa2dzKCkNCmBgYA0KDQojIyMgVXBkYXRpbmcgUlN0dWRpbw0KVG8gdXBkYXRlIFJTdHVkaW8sIG9wZW4gUlN0dWRpbyBhbmQgZ28gdG8gYEhlbHAgPiBDaGVjayBmb3IgVXBkYXRlc2AgdG8gaW5zdGFsbCB0aGUgbmV3ZXN0IHZlcnNpb24uDQoNCiMgRGF0YSBUeXBlcw0KDQpSIGhhcyBhIHdpZGUgdmFyaWV0eSBvZiBkYXRhIHR5cGVzIGluY2x1ZGluZyB2ZWN0b3JzIChudW1lcmljYWwsIGNoYXJhY3RlciwgbG9naWNhbCksIG1hdHJpY2VzLCBsaXN0cywgYW5kIGRhdGEgZnJhbWVzLiBNYXRyaWNlcyBhcmUgY29tcG9zZWQgb2YgdmVjdG9ycywgYW5kIGRhdGEgZnJhbWVzIGFyZSBjb21wb3NlZCBvZiBsaXN0cy4NCg0KIyMgVmVjdG9ycw0KDQpgYGB7cn0NCmEgPC0gYygxLDIsNS4zLDYsLTIsNCkgIyBudW1lcmljIHZlY3Rvcg0KYiA8LSBjKCJvbmUiLCJ0d28iLCJ0aHJlZSIpICMgY2hhcmFjdGVyIHZlY3Rvcg0KYyA8LSBjKFRSVUUsVFJVRSxUUlVFLEZBTFNFLFRSVUUsRkFMU0UpICNsb2dpY2FsIHZlY3Rvcg0KYGBgDQoNCllvdSBjYW4gcmVmZXIgdG8gZWxlbWVudHMgb2YgYSB2ZWN0b3IgdXNpbmcgc3Vic2NyaXB0cy4NCg0KYGBge3J9DQphW2MoMiw0KV0gIyAybmQgYW5kIDR0aCBlbGVtZW50cyBvZiB2ZWN0b3INCmBgYA0KDQojIyBNYXRyaWNlcw0KDQpBbGwgY29sdW1ucyBpbiBhIG1hdHJpeCBtdXN0IGhhdmUgdGhlIHNhbWUgbW9kZSAobnVtZXJpYywgY2hhcmFjdGVyLCBldGMuKSBhbmQgdGhlIHNhbWUgbGVuZ3RoLiBUaGUgZ2VuZXJhbCBmb3JtYXQgaXMNCg0KYGBge3IsIGV2YWwgPSBGfQ0KbXltYXRyaXggPC0gbWF0cml4KHZlY3RvciwgbnJvdz1yLCBuY29sPWMsIGJ5cm93PUZBTFNFLA0KICAgZGltbmFtZXM9bGlzdChjaGFyX3ZlY3Rvcl9yb3duYW1lcywgY2hhcl92ZWN0b3JfY29sbmFtZXMpKQ0KYGBgDQoNCmBieXJvdz1UUlVFYCBpbmRpY2F0ZXMgdGhhdCB0aGUgbWF0cml4IHNob3VsZCBiZSBmaWxsZWQgYnkgcm93cy4gYGJ5cm93PUZBTFNFYCBpbmRpY2F0ZXMgdGhhdCB0aGUgbWF0cml4IHNob3VsZCBiZSBmaWxsZWQgYnkgY29sdW1ucyAodGhlIGRlZmF1bHQpLiBkaW1uYW1lcyBwcm92aWRlcyBvcHRpb25hbCBsYWJlbHMgZm9yIHRoZSBjb2x1bW5zIGFuZCByb3dzLg0KDQpgYGB7cn0NCiMgZ2VuZXJhdGVzIDUgeCA0IG51bWVyaWMgbWF0cml4DQp4IDwtIG1hdHJpeCgxOjIwLCBucm93PTUsbmNvbD00KQ0KDQojIGFub3RoZXIgZXhhbXBsZQ0KY2VsbHMgPC0gYygxLDI2LDI0LDY4KQ0Kcm5hbWVzIDwtIGMoIlIxIiwgIlIyIikNCmNuYW1lcyA8LSBjKCJDMSIsICJDMiIpDQpteW1hdHJpeCA8LSBtYXRyaXgoY2VsbHMsIG5yb3c9MiwgbmNvbD0yLCBieXJvdz1UUlVFLA0KICBkaW1uYW1lcz1saXN0KHJuYW1lcywgY25hbWVzKSkNCmBgYA0KDQpMaWtlIGluIHZlY3RvcnMsIHlvdSBjYW4gaWRlbnRpZnkgcm93cywgY29sdW1ucyBvciBlbGVtZW50cyB1c2luZyBzdWJzY3JpcHRzLg0KDQpgYGB7cn0NCnhbLDRdICMgNHRoIGNvbHVtbiBvZiBtYXRyaXgNCnhbMyxdICMgM3JkIHJvdyBvZiBtYXRyaXgNCnhbMjo0LDE6M10gIyByb3dzIDIsMyw0IG9mIGNvbHVtbnMgMSwyLDMNCmBgYA0KDQojIyBMaXN0cw0KDQpBbiBvcmRlcmVkIGNvbGxlY3Rpb24gb2Ygb2JqZWN0cyAoY29tcG9uZW50cykuIEEgbGlzdCBhbGxvd3MgeW91IHRvIGdhdGhlciBhIHZhcmlldHkgb2YgKHBvc3NpYmx5IHVucmVsYXRlZCkgb2JqZWN0cyB1bmRlciBvbmUgbmFtZS4NCg0KYGBge3J9DQojIGV4YW1wbGUgb2YgYSBsaXN0IHdpdGggNCBjb21wb25lbnRzIC0NCiMgYSBzdHJpbmcsIGEgbnVtZXJpYyB2ZWN0b3IsIGEgbWF0cml4LCBhbmQgYSBzY2FsYXINCmxpc3QxIDwtIGxpc3QobmFtZT0iRnJlZCIsIG15bnVtYmVycz1hLCBteW1hdHJpeD14LCBhZ2U9NS4zKQ0KbGlzdDIgPC0gbGlzdChjaGFyYWN0ZXI9IkxvdWlzZSIsIHNob3c9IkJvYidzIEJ1cmdlcnMiLCB0aW1lPTgzMCkNCg0KIyBleGFtcGxlIG9mIGEgbGlzdCBjb250YWluaW5nIHR3byBsaXN0cw0KdWx0aW1hdGVfbGlzdCA8LSBjKGxpc3QxLGxpc3QyKQ0KYGBgDQoNCklkZW50aWZ5IGVsZW1lbnRzIG9mIGEgbGlzdCB1c2luZyB0aGUgW1tdXSBjb252ZW50aW9uLg0KDQpgYGB7cn0NCnVsdGltYXRlX2xpc3RbWzJdXSAjIDJuZCBjb21wb25lbnQgb2YgdGhlIGxpc3QNCnVsdGltYXRlX2xpc3RbWyJteW51bWJlcnMiXV0gIyBjb21wb25lbnQgbmFtZWQgbXludW1iZXJzIGluIGxpc3QNCmBgYA0KDQojIyBEYXRhIEZyYW1lcw0KDQpBIGRhdGEgZnJhbWUgaXMgbW9yZSBnZW5lcmFsIHRoYW4gYSBtYXRyaXgsIGluIHRoYXQgZGlmZmVyZW50IGNvbHVtbnMgY2FuIGhhdmUgZGlmZmVyZW50IG1vZGVzIChudW1lcmljLCBjaGFyYWN0ZXIsIGZhY3RvciwgZXRjLikuIFRoaXMgaXMgc2ltaWxhciB0byBTQVMgYW5kIFNQU1MgZGF0YXNldHMuDQoNCmBgYHtyfQ0KZCA8LSBjKDEsMiwzLDQpDQplIDwtIGMoInJlZCIsICJ3aGl0ZSIsICJyZWQiLCBOQSkNCmYgPC0gYyhUUlVFLFRSVUUsVFJVRSxGQUxTRSkNCm15ZGF0YSA8LSBkYXRhLmZyYW1lKGQsZSxmKQ0KbmFtZXMobXlkYXRhKSA8LSBjKCJJRCIsIkNvbG9yIiwiUGFzc2VkIikgIyB2YXJpYWJsZSBuYW1lcw0KYGBgDQoNClRoZXJlIGFyZSBhIHZhcmlldHkgb2Ygd2F5cyB0byBpZGVudGlmeSB0aGUgZWxlbWVudHMgb2YgYSBkYXRhIGZyYW1lLg0KDQpgYGB7cn0NCm15ZGF0YVsyOjNdICMgY29sdW1ucyAyLDMgb2YgZGF0YSBmcmFtZQ0KbXlkYXRhW2MoIklEIiwiUGFzc2VkIildICMgY29sdW1ucyBJRCBhbmQgUGFzc2VkIGZyb20gZGF0YSBmcmFtZQ0KbXlkYXRhJENvbG9yICMgdmFyaWFibGUgQ29sb3IgaW4gdGhlIGRhdGEgZnJhbWUNCmBgYA0KDQojIyBVc2VmdWwgRnVuY3Rpb25zIGZvciBBbGwgRGF0YSBUeXBlcw0KDQpgYGB7ciwgZXZhbCA9IEZ9DQpsZW5ndGgob2JqZWN0KSAjIG51bWJlciBvZiBlbGVtZW50cyBvciBjb21wb25lbnRzDQpzdHIob2JqZWN0KSAgICAjIHN0cnVjdHVyZSBvZiBhbiBvYmplY3QNCmNsYXNzKG9iamVjdCkgICMgY2xhc3Mgb3IgdHlwZSBvZiBhbiBvYmplY3QNCm5hbWVzKG9iamVjdCkgICMgbmFtZXMNCg0KYyhvYmplY3Qsb2JqZWN0LC4uLikgICAgICAgIyBjb21iaW5lIG9iamVjdHMgaW50byBhIHZlY3Rvcg0KY2JpbmQob2JqZWN0LCBvYmplY3QsIC4uLikgIyBjb21iaW5lIG9iamVjdHMgYXMgY29sdW1ucw0KcmJpbmQob2JqZWN0LCBvYmplY3QsIC4uLikgIyBjb21iaW5lIG9iamVjdHMgYXMgcm93cw0KDQpvYmplY3QgICAgICMgcHJpbnRzIHRoZSBvYmplY3QNCg0KbHMoKSAgICAgICAjIGxpc3QgY3VycmVudCBvYmplY3RzDQpybShvYmplY3QpICMgZGVsZXRlIGFuIG9iamVjdA0KDQpuZXdvYmplY3QgPC0gZWRpdChvYmplY3QpICMgZWRpdCBjb3B5IGFuZCBzYXZlIGFzIG5ld29iamVjdA0KZml4KG9iamVjdCkgICAgICAgICAgICAgICAjIGVkaXQgaW4gcGxhY2UgDQpgYGANCg0KIyBJbXBvcnRpbmcgYW5kIFdvcmtpbmcgd2l0aCBEYXRhDQoNCiMjIFJlYWRpbmcgaW4gdGhlIERhdGFzZXQNCg0KVG9kYXksIHdlJ3JlIGdvaW5nIHRvIGJlIHVzaW5nIG9uZSBvZiB0aGUgb3ZlciA0MCwwMDAgb3BlbiBkYXRhc2V0cyBhdmFpbGFibGUgaW4gW05BU0EncyBPcGVuIERhdGEgUmVwb3NpdG9yeV0oaHR0cHM6Ly9kYXRhLm5hc2EuZ292L1NwYWNlLVNjaWVuY2UvTWV0ZW9yaXRlLUxhbmRpbmdzL2doNGctOXNmaCl7dGFyZ2V0PSJfYmxhbmsifS4gVGhpcyAiTWV0ZW9yaXRlIExhbmRpbmdzIiBkYXRhc2V0IGlzIGZyb20gdGhlIE1ldGVvcml0aWNhbCBTb2NlaXR5IGFuZCBjb250YWlucyBpbmZvcm1hdGlvbiBvbiAqYWxsKiBvZiB0aGUga25vd24gbWV0ZW9yaXRlIGxhbmRpbmdzLCBnb2luZyBhcyBmYXIgYmFjayBhcyB0aGUgZWFybHkgMTgwMCdzLg0KDQpUaGlzIGRhdGEgY2FuIGJlIGRvd25sb2FkZWQgW2F0IHRoaXMgbGlua10oaHR0cHM6Ly9naXRodWIuY29tL2Fjb2x1bS9jb25mZXJlbmNlLXByZXNlbnRhdGlvbnMvcmF3L21hc3Rlci9EYXRhJTIwTWFuaXB1bGF0aW9uJTIwd2l0aCUyMFRpZHklMjBUb29scy9kYXRhL01ldGVvcml0ZV9MYW5kaW5ncy5jc3Ype3RhcmdldD0iX2JsYW5rIn0uDQoNClRvIHJlYWQgaW4gb3VyIGRhdGEgKC5jc3YpIGZpbGUsIHdlIGNhbiB1c2UgdGhlIGZvbGxvd2luZyBjb21tYW5kOg0KDQpgYGB7cn0NCm1ldGVvcml0ZV9sYW5kaW5ncyA8LSByZWFkLmNzdigiZGF0YS9NZXRlb3JpdGVfTGFuZGluZ3MuY3N2IikNCmBgYA0KDQoqKk5vdGUgMToqKiBUaGUgc3ludGF4IGFib3ZlIGFzc3VtZXMgdGhlIC5jc3YgZmlsZSBpcyBzYXZlZCB0byB0aGUgd29ya2luZyBkaXJlY3RvcnkuIFRvIGNoYW5nZSB0aGUgd29ya2luZyBkaXJlY3RvcnksIG5hdmlnYXRlIHRvIGBTZXNzaW9uYCA+IGBTZXQgV29ya2luZyBEaXJlY3RvcnlgID4gYENob29zZSBXb3JraW5nIERpcmVjdG9yeWAgb3IgdHlwZSBgc2V0d2QoIkM6L1VzZXJzL3BhdGgiKWAgaW50byB0aGUgQ29uc29sZS4NCg0KKipOb3RlIDI6KiogYHJlYWQuY3N2YCByZWFkcyB0aGUgZmlsZSwgYnV0IHdlIGNhbid0IHVzZSB0aGUgZGF0YSB1bmxlc3Mgd2UgYXNzaWduIGl0IHRvIGEgdmFyaWFibGUuIFdlIGNhbiB0aGluayBvZiBhIHZhcmlhYmxlIGFzIGEgY29udGFpbmVyIHdpdGggYSBuYW1lLCBzdWNoIGFzIHgsIGN1cnJlbnRfdGVtcGVyYXR1cmUsIG9yIHN1YmplY3RfaWQgdGhhdCBjb250YWlucyBvbmUgb3IgbW9yZSB2YWx1ZXMuIFdlIGNhbiBjcmVhdGUgYSBuZXcgdmFyaWFibGUgaW4gUiBhbmQgYXNzaWduIGEgdmFsdWUgdG8gaXQgdXNpbmcgYDwtYC4NCg0KT25jZSB5b3UgcnVuIHRoZSBhYm92ZSBjb21tYW5kLCB5b3Ugc2hvdWxkIHNlZSBhIGBtZXRlb3JpdGVfbGFuZGluZ3NgIG9iamVjdCBpbiB0aGUgIkVudmlyb25tZW50IiBib3ggaW4gdGhlIHRvcCByaWdodCBjb3JuZXIgb2YgUlN0dWRpby4NCg0KTm93LCBsZXQncyBleHBsb3JlIHRoZSBkYXRhc2V0Lg0KDQojIyBWaWV3aW5nIHRoZSBEYXRhc2V0DQoNCklmIHdlIHdhbnQgdG8gdmlldyBvdXIgZW50aXJlIGRhdGFzZXQsIHdlIGNhbiB0eXBlIGBWaWV3KG1ldGVvcml0ZV9sYW5kaW5ncylgIGludG8gdGhlIENvbnNvbGUuIEhvd2V2ZXIsIGZvciBsYXJnZSBkYXRhIHNldHMgaXQgaXMgbXVjaCBmYXN0ZXIgYW5kIG1vcmUgY29udmVuaWVudCB0byB1c2UgdGhlIGZ1bmN0aW9uIGBoZWFkYCB0byBkaXNwbGF5IG9ubHkgdGhlIGZpcnN0IGZldyByb3dzIG9mIGRhdGEuDQoNCmBgYHtyfQ0KaGVhZChtZXRlb3JpdGVfbGFuZGluZ3MpDQpgYGANCg0KIyMjIFN0cnVjdHVyZQ0KDQpUbyB2aWV3IHRoZSBzdHJ1Y3R1cmUgb2YsIG9yIGRhdGEgdHlwZXMgZm9yLCBlYWNoIHZhcmlhYmxlIGluIGEgZGF0YXNldCwgdXNlIHRoZSBgc3RyYCwgb3IgInN0cnVjdHVyZSIgZnVuY3Rpb24uDQoNCmBgYHtyfQ0Kc3RyKG1ldGVvcml0ZV9sYW5kaW5ncykNCmBgYA0KDQojIyMjIERhdGEgVHlwZXMNCg0KTm90ZSB0aGF0IGBtZXRlb3JpdGVfbGFuZGluZ3NgIGlzIGEgZGF0YSBmcmFtZS4gWW91IGNhbiB0aGluayBvZiB0aGlzIHN0cnVjdHVyZSBhcyBhIHNwcmVhZHNoZWV0IGluIE1TIEV4Y2VsLiBEYXRhIGZyYW1lcyBhcmUgdmVyeSB1c2VmdWwgZm9yIHN0b3JpbmcgZGF0YSwgYW5kIHlvdSB3aWxsIHVzZSB0aGVtIGZyZXF1ZW50bHkgd2hlbiBwcm9ncmFtbWluZyBpbiBSLiBBIHR5cGljYWwgZGF0YSBmcmFtZSBvZiBleHBlcmltZW50YWwgZGF0YSBjb250YWlucyBpbmRpdmlkdWFsIG9ic2VydmF0aW9ucyBpbiByb3dzIGFuZCB2YXJpYWJsZXMgaW4gY29sdW1ucy4gQWxzbyBub3RlIHRoYXQgdGhlcmUgYXJlIDIgZGlmZmVyZW50IGRhdGEgdHlwZXMgaW4gb3VyIGRhdGFzZXQ6DQoNCjEuICoqRmFjdG9yKiogLSBBIENsYXNzIChub3QgYSBzdHJpbmcgb3IgbnVtYmVyISkNCg0KMi4gKipOdW0qKiAtIE51bWVyaWMsIGZsb2F0L251bWJlciB0aGFuIGNhbiBpbmNsdWRlIGRlY2ltYWwgcGxhY2VzDQoNCiMjIyBEaW1lbnNpb25zDQoNCldlIGNhbiBzZWUgdGhlIHNoYXBlLCBvciBkaW1lbnNpb25zLCBvZiB0aGUgZGF0YSBmcmFtZSB3aXRoIHRoZSBmdW5jdGlvbiBgZGltYDoNCg0KYGBge3J9DQpkaW0obWV0ZW9yaXRlX2xhbmRpbmdzKQ0KYGBgDQoNClRoaXMgdGVsbHMgdXMgdGhhdCBvdXIgZGF0YSBmcmFtZSwgYG1ldGVvcml0ZV9sYW5kaW5nc2AsIGhhcyA0NSw3MTYgcm93cyBhbmQgMTAgY29sdW1ucy4NCg0KIyMjIyBJbmRleGluZw0KDQpJZiB3ZSB3YW50IHRvIGdldCBhIHNpbmdsZSB2YWx1ZSBmcm9tIHRoZSBkYXRhIGZyYW1lLCB3ZSBjYW4gcHJvdmlkZSBhbiBpbmRleCBpbiBzcXVhcmUgYnJhY2tldHMuIFRoZSBmaXJzdCBudW1iZXIgc3BlY2lmaWVzIHRoZSByb3cgYW5kIHRoZSBzZWNvbmQgdGhlIGNvbHVtbjoNCg0KYGBge3J9DQojIGZpcnN0IHZhbHVlIGluIG1ldGVvcml0ZV9sYW5kaW5ncywgcm93IDEsIGNvbHVtbiAxDQptZXRlb3JpdGVfbGFuZGluZ3NbMSwgMV0NCg0KIyBtaWRkbGUgdmFsdWUgaW4gbWV0ZW9yaXRlX2xhbmRpbmdzLCByb3cgMjI4NTgsIGNvbHVtbiA1DQptZXRlb3JpdGVfbGFuZGluZ3NbMjI4NTgsIDVdDQpgYGANCg0KIyMjIyBTdWJzZXR0aW5nDQoNCklmIHdlIHdhbnQgdG8gc2VsZWN0IG1vcmUgdGhhbiBvbmUgcm93IG9yIGNvbHVtbiwgd2UgY2FuIHVzZSB0aGUgZnVuY3Rpb24gYGNgLCB3aGljaCBzdGFuZHMgZm9yIGNvbWJpbmUuIEZvciBleGFtcGxlLCB0byBwaWNrIGNvbHVtbnMgMSBhbmQgNSBmcm9tIHJvd3MgMSwgMywgYW5kIDUsIHdlIGNhbiBkbyB0aGlzOg0KDQpgYGB7cn0NCm1ldGVvcml0ZV9sYW5kaW5nc1tjKDEsIDMsIDUpLCBjKDEsIDUpXQ0KYGBgDQoNCldlIGZyZXF1ZW50bHkgd2FudCB0byBzZWxlY3QgY29udGlndW91cyByb3dzIG9yIGNvbHVtbnMsIHN1Y2ggYXMgdGhlIGZpcnN0IHRlbiByb3dzLCBvciBjb2x1bW5zIDMgdGhyb3VnaCA3LiBZb3UgY2FuIHVzZSBgY2AgZm9yIHRoaXMsIGJ1dCBpdCdzIG1vcmUgY29udmVuaWVudCB0byB1c2UgdGhlIGA6YCBvcGVyYXRvci4gVGhpcyBzcGVjaWFsIGZ1bmN0aW9uIGdlbmVyYXRlcyBzZXF1ZW5jZXMgb2YgbnVtYmVyczoNCg0KYGBge3J9DQoxOjUNCg0KMzoxMg0KYGBgDQoNCkZvciBleGFtcGxlLCB3ZSBjYW4gc2VsZWN0IHRoZSBmaXJzdCAyIGNvbHVtbnMgb2YgdmFsdWVzIGZvciB0aGUgZmlyc3QgZm91ciByb3dzIGxpa2UgdGhpczoNCg0KYGBge3J9DQptZXRlb3JpdGVfbGFuZGluZ3NbMTo0LCAxOjJdDQpgYGANCg0Kb3IgdGhlIGZpcnN0IDUgY29sdW1ucyBvZiByb3dzIDUgdG8gMTAgbGlrZSB0aGlzOg0KDQpgYGB7cn0NCm1ldGVvcml0ZV9sYW5kaW5nc1s1OjEwLCAxOjVdDQpgYGANCg0KSWYgeW91IHdhbnQgdG8gc2VsZWN0IGFsbCByb3dzIG9yIGFsbCBjb2x1bW5zLCBsZWF2ZSB0aGF0IGluZGV4IHZhbHVlIGVtcHR5Lg0KDQpgYGB7cn0NCiMgQWxsIGNvbHVtbnMgZnJvbSByb3cgNQ0KbWV0ZW9yaXRlX2xhbmRpbmdzWzUsIF0NCg0KIyBBbGwgcm93cyBmcm9tIGNvbHVtbnMgNi04DQptZXRlb3JpdGVfbGFuZGluZ3NbLCA2OjhdDQpgYGANCg0KSWYgeW91IGxlYXZlIGJvdGggaW5kZXggdmFsdWVzIGVtcHR5IChpLmUuLCBgbWV0ZW9yaXRlX2xhbmRpbmdzWyxdYCksIHlvdSBnZXQgdGhlIGVudGlyZSBkYXRhIGZyYW1lLg0KDQojIyBNYXRoZW1hdGljYWwgT3BlcmF0aW9ucw0KDQpOb3cgbGV0J3MgcGVyZm9ybSBzb21lIGNvbW1vbiBtYXRoZW1hdGljYWwgb3BlcmF0aW9ucyB0byBsZWFybiBtb3JlIGFib3V0IG91ciBtZXRlb3JpdGUgZGF0YS4gV2hlbiBhbmFseXppbmcgZGF0YSB3ZSBvZnRlbiB3YW50IHRvIGxvb2sgYXQgcGFydGlhbCBzdGF0aXN0aWNzLCBzdWNoIGFzIHRoZSBtYXhpbXVtIHZhbHVlIHBlciBpZCBvciB0aGUgYXZlcmFnZSB2YWx1ZSBwZXIgeWVhci4gT25lIHdheSB0byBkbyB0aGlzIGlzIHRvIHNlbGVjdCB0aGUgZGF0YSB3ZSB3YW50IHRvIGNyZWF0ZSBhIG5ldyB0ZW1wb3JhcnkgZGF0YSBmcmFtZSwgYW5kIHRoZW4gcGVyZm9ybSB0aGUgY2FsY3VsYXRpb24gb24gdGhpcyBzdWJzZXQ6DQoNCmBgYHtyfQ0KIyBmaXJzdCA1IHJvd3MsIGNvbHVtbnMgMSw1DQpmaXJzdF9maWZ0eV9tZXRlb3JzIDwtIG1ldGVvcml0ZV9sYW5kaW5nc1sxOjUwLCA1XQ0KIyBtYXggbWFzcyBvZiBmaXJzdCA1MCBtZXRlb3JzDQptYXgoZmlyc3RfZmlmdHlfbWV0ZW9ycywgbmEucm0gPSBUKQ0KDQojIGFsc28gY29ycmVjdDoNCm1heChtZXRlb3JpdGVfbGFuZGluZ3NbMTo1MCwgNV0sIG5hLnJtID0gVCkNCmBgYA0KDQpSIGFsc28gaGFzIGZ1bmN0aW9ucyBmb3Igb3RoZXIgY29tbW9uIGNhbGN1bGF0aW9ucywgZS5nLiBmaW5kaW5nIHRoZSBtaW5pbXVtLCBtZWFuLCBtZWRpYW4sIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIGRhdGE6DQoNCmBgYHtyfQ0KIyBtaW5pbXVtIG1hc3Mgb2YgZmlyc3QgNTAgbWV0ZW9ycw0KbWluKGZpcnN0X2ZpZnR5X21ldGVvcnMsIG5hLnJtID0gVCkNCg0KIyBtZWFuIG1hc3Mgb2YgZmlyc3QgNTAgbWV0ZW9ycw0KbWVhbihmaXJzdF9maWZ0eV9tZXRlb3JzLCBuYS5ybSA9IFQpDQoNCiMgbWVkaWFuIG1hc3Mgb2YgZmlyc3QgNTAgbWV0ZW9ycw0KbWVkaWFuKGZpcnN0X2ZpZnR5X21ldGVvcnMsIG5hLnJtID0gVCkNCg0KIyBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIG1hc3Mgb2YgdGhlIGZpcnN0IDUwIG1ldGVvcnMNCnNkKGZpcnN0X2ZpZnR5X21ldGVvcnMsIG5hLnJtID0gVCkNCmBgYA0KDQpSIGFsc28gaGFzIGEgZnVuY3Rpb24gdGhhdCBzdW1tYXJpZXMgdGhlIHByZXZpb3VzIGNvbW1vbiBjYWxjdWxhdGlvbnMuIEZvciBldmVyeSBjb2x1bW4gaW4gdGhlIGRhdGEgZnJhbWUsIHRoZSBmdW5jdGlvbiBgc3VtbWFyeWAgY2FsY3VsYXRlczogdGhlIG1pbmltdW0gdmFsdWUsIHRoZSBmaXJzdCBxdWFydGlsZSwgdGhlIG1lZGlhbiwgdGhlIG1lYW4sIHRoZSB0aGlyZCBxdWFydGlsZSwgYW5kIHRoZSBtYXggdmFsdWUsIGdpdmluZyBoZWxwZnVsIGRldGFpbHMgYWJvdXQgdGhlIHNhbXBsZSBkaXN0cmlidXRpb24uDQoNCmBgYHtyfQ0KIyBTdW1tYXJpemUgZnVuY3Rpb24NCnN1bW1hcnkobWV0ZW9yaXRlX2xhbmRpbmdzKQ0KYGBgDQoNCiMgTWFuaXB1bGF0aW5nIGFuZCBzb3J0aW5nIGRhdGFmcmFtZXMgd2l0aCBgZHBseXJgDQoNCiMjIFdoYXQgaXMgYGRwbHlyYD8NCg0KYGRwbHlyYCBpcyBhbiBSIHBhY2thZ2UgaW4gdGhlIGB0aWR5dmVyc2VgIHRoYXQgYWltcyB0byBtYWtlIGRhdGEgd3JhbmdsaW5nL2NsZWFuaW5nL21hbmlwdWxhdGlvbiBtb3JlIGh1bWFuLXJlYWRhYmxlLiBUaGUgYGRwbHlyYCBwYWNrYWdlIG1ha2VzIHRoZXNlIHN0ZXBzIGZhc3QgYW5kIGVhc3k6DQoNCiogQnkgY29uc3RyYWluaW5nIHlvdXIgb3B0aW9ucywgaXQgaGVscHMgeW91IHRoaW5rIGFib3V0IHlvdXIgZGF0YSBtYW5pcHVsYXRpb24gY2hhbGxlbmdlcy4NCg0KKiBJdCBwcm92aWRlcyBzaW1wbGUgInZlcmJzIiwgZnVuY3Rpb25zIHRoYXQgY29ycmVzcG9uZCB0byB0aGUgbW9zdCBjb21tb24gZGF0YSBtYW5pcHVsYXRpb24gdGFza3MsIHRvIGhlbHAgeW91IHRyYW5zbGF0ZSB5b3VyIHRob3VnaHRzIGludG8gY29kZS4NCg0KKiBJdCB1c2VzIGVmZmljaWVudCBiYWNrZW5kcywgc28geW91IHNwZW5kIGxlc3MgdGltZSB3YWl0aW5nIGZvciB0aGUgY29tcHV0ZXIuDQoNClRoaXMgdHV0b3JpYWwgaW50cm9kdWNlcyB5b3UgdG8gYGRwbHlyYCdzIGJhc2ljIHNldCBvZiB0b29scywgYW5kIHNob3dzIHlvdSBob3cgdG8gYXBwbHkgdGhlbSB0byBkYXRhIGZyYW1lcy4gQXNpZGUgZnJvbSB0aGlzIHR1dG9yaWFsLCBhIGhlbHBmdWwgcmVzb3VyY2UgZm9yIGdldHRpbmcgZmFtaWxpYXIgd2l0aCBgZHBseXJgIGlzIFt0aGlzIGNvbXByZWhlbnNpdmUgUlN0dWRpbyBjaGVhdHNoZWV0Ll0oaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8vY2hlYXRzaGVldHMvcmF3L21hc3Rlci9kYXRhLXRyYW5zZm9ybWF0aW9uLnBkZil7dGFyZ2V0PSJfYmxhbmsifQ0KDQoqKlNpZGUgTm90ZToqKiBgZHBseXJgIGFsc28gc3VwcG9ydHMgZGF0YWJhc2VzIHZpYSB0aGUgYGRicGx5cmAgcGFja2FnZS4gT25jZSB5b3UgaW5zdGFsbCBgZGJwbHlyYCwgeW91IGNhbiByZWFkIGB2aWduZXR0ZSgiZGJwbHlyIilgIHRvIGxlYXJuIG1vcmUuDQoNCiMjIEdldHRpbmcgU3RhcnRlZCB3aXRoIGBkcGx5cmANCg0KSWYgeW91IGRvbid0IGFscmVhZHkgaGF2ZSBgZHBseXJgIG9yIHRoZSBgdGlkeXZlcnNlYCBpbnN0YWxsZWQsIHlvdSBjYW4gaW5zdGFsbCB0aGUgYGRwbHlyYCBwYWNrYWdlIGJ5IHR5cGluZyB0aGUgZm9sbG93aW5nIGNvbW1hbmQgaW50byB0aGUgQ29uc29sZToNCg0KYGBge3IsIGV2YWwgPSBGfQ0KaW5zdGFsbC5wYWNrYWdlcygiZHBseXIiKQ0KYGBgDQoNCk9uY2UgeW91IGhhdmUgYGRwbHlyYCBpbnN0YWxsZWQsIGxvYWQgaXQgaW50byB5b3VyIFIgc2Vzc2lvbiB3aXRoIA0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpgYGANCg0KYGRwbHlyYCBhaW1zIHRvIHByb3ZpZGUgYSBmdW5jdGlvbiBmb3IgZWFjaCBiYXNpYyB2ZXJiIG9mIGRhdGEgbWFuaXB1bGF0aW9uOg0KDQp8IEZ1bmN0aW9uIHwgUHVycG9zZSB8DQp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgYGZpbHRlcigpYCB8IHRvIHNlbGVjdCBjYXNlcyBiYXNlZCBvbiB0aGVpciB2YWx1ZXMgfA0KfCBgYXJyYW5nZSgpYCB8IHRvIHJlb3JkZXIgdGhlIGNhc2VzIHwNCnwgYHNlbGVjdCgpYCwgYHJlbmFtZSgpYCB8IHRvIHNlbGVjdCB2YXJpYWJsZXMgYmFzZWQgb24gdGhlaXIgbmFtZXMgfA0KfCBgbXV0YXRlKClgLCBgdHJhbnNtdXRlKClgIHwgdG8gYWRkIG5ldyB2YXJpYWJsZXMgdGhhdCBhcmUgZnVuY3Rpb25zIG9mIGV4aXN0aW5nIHZhcmlhYmxlcyB8DQp8IGBzdW1tYXJpc2UoKWAgfCB0byBjb25kZW5zZSBtdWx0aXBsZSB2YWx1ZXMgdG8gYSBzaW5nbGUgdmFsdWUgfA0KfCBgc2FtcGxlX24oKWAsIGBzYW1wbGVfZnJhYygpYCB8IHRvIHRha2UgcmFuZG9tIHNhbXBsZXMgfA0KICAgIA0KSW4gdGhpcyB0dXRvcmlhbCwgd2UnbGwgc3BlbmQgc29tZSB0aW1lIHdvcmtpbmcgd2l0aCBlYWNoIGluZGl2aWR1YWwgZnVuY3Rpb24uDQoNCiMjIyBQaXBpbmcgT3BlcmF0b3IgKGAlPiVgKQ0KDQpUaGUgb3BlcmF0b3JzIGAlPiVgIHBpcGUgdGhlaXIgbGVmdC1oYW5kIHNpZGUgdmFsdWVzIGZvcndhcmQgaW50byBleHByZXNzaW9ucyB0aGF0IGFwcGVhciBvbiB0aGUgcmlnaHQtaGFuZCBzaWRlLCBpLmUuIG9uZSBjYW4gcmVwbGFjZSBmKHgpIHdpdGggeCBgJT4lYCBmKCksIHdoZXJlIGAlPiVgIGlzIHRoZSAobWFpbikgcGlwZS1vcGVyYXRvci4gV2hlbiBjb3VwbGluZyBzZXZlcmFsIGZ1bmN0aW9uIGNhbGxzIHdpdGggdGhlIHBpcGUtb3BlcmF0b3IsIHRoZSBiZW5lZml0IHdpbGwgYmVjb21lIG1vcmUgYXBwYXJlbnQuIENvbnNpZGVyIHRoaXMgcHNldWRvIGV4YW1wbGU6DQoNCmBgYHtyLCBldmFsID0gRn0NCnRoZV9kYXRhIDwtIHJlYWQuY3N2KCcvcGF0aC90by9kYXRhL2ZpbGUuY3N2JykgJT4lDQogIHN1YnNldCh2YXJpYWJsZV9hID4geCkgJT4lDQogIHRyYW5zZm9ybSh2YXJpYWJsZV9jID0gdmFyaWFibGVfYS92YXJpYWJsZV9iKSAlPiUNCiAgaGVhZCgxMDApDQpgYGANCg0KRm91ciBvcGVyYXRpb25zIGFyZSBwZXJmb3JtZWQgdG8gYXJyaXZlIGF0IHRoZSBkZXNpcmVkIGRhdGEgc2V0LCBhbmQgdGhleSBhcmUgd3JpdHRlbiBpbiBhIG5hdHVyYWwgb3JkZXI6IHRoZSBzYW1lIGFzIHRoZSBvcmRlciBvZiBleGVjdXRpb24uIEFsc28sIG5vIHRlbXBvcmFyeSB2YXJpYWJsZXMgYXJlIG5lZWRlZC4gSWYgeWV0IGFub3RoZXIgb3BlcmF0aW9uIGlzIHJlcXVpcmVkLCBpdCBpcyBzdHJhaWdodC1mb3J3YXJkIHRvIGFkZCB0byB0aGUgc2VxdWVuY2Ugb2Ygb3BlcmF0aW9ucyB3aGVyZXZlciBpdCBtYXkgYmUgbmVlZGVkLg0KDQojIyBGaWx0ZXIgcm93cyB3aXRoIGBmaWx0ZXIoKWANCg0KYGZpbHRlcigpYCBhbGxvd3MgeW91IHRvIHNlbGVjdCBhIHN1YnNldCBvZiByb3dzIGluIGEgZGF0YSBmcmFtZS4gTGlrZSBhbGwgc2luZ2xlIHZlcmJzLCB0aGUgZmlyc3QgYXJndW1lbnQgaXMgdGhlIGRhdGEgZnJhbWUuIFRoZSBzZWNvbmQgYW5kIHN1YnNlcXVlbnQgYXJndW1lbnRzIHJlZmVyIHRvIHZhcmlhYmxlcyB3aXRoaW4gdGhhdCBkYXRhIGZyYW1lLCBzZWxlY3Rpbmcgcm93cyB3aGVyZSB0aGUgZXhwcmVzc2lvbiBpcyBgVFJVRWAuDQoNCkZvciBleGFtcGxlLCB3ZSBjYW4gc2VsZWN0IGFsbCBtZXRlb3JpdGVzIGluIHRoZSBMNSBjbGFzcyB3aXRoIG1hc3MgZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIDEwLDAwMCBncmFtcyB3aXRoOg0KDQpgYGB7cn0NCm1ldGVvcml0ZV9sYW5kaW5ncyAlPiUgDQogIGZpbHRlcihtYXNzID49IDEwMDAwICYgcmVjY2xhc3MgPT0gIkw1IikNCmBgYA0KDQpUaGlzIGlzIHJvdWdobHkgZXF1aXZhbGVudCB0byB0aGlzIGJhc2UgUiBjb2RlOg0KDQpgYGB7ciwgZXZhbCA9IEZ9DQptZXRlb3JpdGVfbGFuZGluZ3NbbWV0ZW9yaXRlX2xhbmRpbmdzJG1hc3MgPj0gMTAwMDAgJiBtZXRlb3JpdGVfbGFuZGluZ3MkcmVjY2xhc3MgPT0gIkw1IiwgXQ0KYGBgDQoNCiMjIEFycmFuZ2Ugcm93cyB3aXRoIGBhcnJhbmdlKClgDQoNCmBhcnJhbmdlKClgIHdvcmtzIHNpbWlsYXJseSB0byBgZmlsdGVyKClgIGV4Y2VwdCB0aGF0IGluc3RlYWQgb2YgZmlsdGVyaW5nIG9yIHNlbGVjdGluZyByb3dzLCBpdCByZW9yZGVycyB0aGVtLiBJdCB0YWtlcyBhIGRhdGEgZnJhbWUgYW5kIGEgc2V0IG9mIGNvbHVtbiBuYW1lcyAob3IgbW9yZSBjb21wbGljYXRlZCBleHByZXNzaW9ucykgdG8gb3JkZXIgYnkuIElmIHlvdSBwcm92aWRlIG1vcmUgdGhhbiBvbmUgY29sdW1uIG5hbWUsIGVhY2ggYWRkaXRpb25hbCBjb2x1bW4gd2lsbCBiZSB1c2VkIHRvIGJyZWFrIHRpZXMgaW4gdGhlIHZhbHVlcyBvZiBwcmVjZWRpbmcgY29sdW1uczoNCg0KYGBge3J9DQptZXRlb3JpdGVfbGFuZGluZ3MgJT4lIA0KICBhcnJhbmdlKHJlY2xhdCwgcmVjbG9uZywgbWFzcykNCmBgYA0KDQpVc2UgYGRlc2MoKWAgdG8gb3JkZXIgYSBjb2x1bW4gaW4gZGVzY2VuZGluZyBvcmRlcjoNCg0KYGBge3J9DQptZXRlb3JpdGVfbGFuZGluZ3MgJT4lIA0KICBhcnJhbmdlKGRlc2MobWFzcykpDQpgYGANCg0KIyMgU2VsZWN0IGNvbHVtbnMgd2l0aCBgc2VsZWN0KClgDQoNCk9mdGVuIHlvdSB3b3JrIHdpdGggbGFyZ2UgZGF0YXNldHMgd2l0aCBtYW55IGNvbHVtbnMgYnV0IG9ubHkgYSBmZXcgYXJlIGFjdHVhbGx5IG9mIGludGVyZXN0IHRvIHlvdS4gYHNlbGVjdCgpYCBhbGxvd3MgeW91IHRvIHJhcGlkbHkgem9vbSBpbiBvbiBhIHVzZWZ1bCBzdWJzZXQgdXNpbmcgb3BlcmF0aW9ucyB0aGF0IHVzdWFsbHkgb25seSB3b3JrIG9uIG51bWVyaWMgdmFyaWFibGUgcG9zaXRpb25zOg0KDQpgYGB7cn0NCiMgU2VsZWN0IGNvbHVtbnMgYnkgbmFtZQ0KbWV0ZW9yaXRlX2xhbmRpbmdzICU+JSANCiAgc2VsZWN0KG5hbWUsIHJlY2NsYXNzLCBtYXNzKQ0KDQojIFNlbGVjdCBhbGwgY29sdW1ucyBiZXR3ZWVuIG5hbWUgYW5kIHllYXIgKGluY2x1c2l2ZSkNCm1ldGVvcml0ZV9sYW5kaW5ncyAlPiUgDQogIHNlbGVjdChuYW1lOnllYXIpDQoNCiMgU2VsZWN0IGFsbCBjb2x1bW5zIGV4Y2VwdCB0aG9zZSBmcm9tIG5hbWUgdG8geWVhciAoaW5jbHVzaXZlKQ0KbWV0ZW9yaXRlX2xhbmRpbmdzICU+JSANCiAgc2VsZWN0KC0obmFtZTp5ZWFyKSkNCmBgYA0KDQpUaGVyZSBhcmUgYSBudW1iZXIgb2YgaGVscGVyIGZ1bmN0aW9ucyB5b3UgY2FuIHVzZSB3aXRoaW4gYHNlbGVjdCgpYCwgbGlrZSBgc3RhcnRzX3dpdGgoKWAsIGBlbmRzX3dpdGgoKWAsIGBtYXRjaGVzKClgIGFuZCBgY29udGFpbnMoKWAuIFRoZXNlIGxldCB5b3UgcXVpY2tseSBtYXRjaCBsYXJnZXIgYmxvY2tzIG9mIHZhcmlhYmxlcyB0aGF0IG1lZXQgc29tZSBjcml0ZXJpb24uIFNlZSBgP3NlbGVjdGAgZm9yIG1vcmUgZGV0YWlscy4NCg0KWW91IGNhbiByZW5hbWUgdmFyaWFibGVzIHdpdGggYHNlbGVjdCgpYCBieSB1c2luZyBuYW1lZCBhcmd1bWVudHM6DQoNCmBgYHtyfQ0KbWV0ZW9yaXRlX2xhbmRpbmdzICU+JSANCiAgc2VsZWN0KG1hc3NfZyA9IG1hc3MpDQpgYGANCg0KQnV0IGJlY2F1c2UgYHNlbGVjdCgpYCBkcm9wcyBhbGwgdGhlIHZhcmlhYmxlcyBub3QgZXhwbGljaXRseSBtZW50aW9uZWQsIGl0J3Mgbm90IHRoYXQgdXNlZnVsLiBJbnN0ZWFkLCB1c2UgYHJlbmFtZSgpYDoNCg0KYGBge3J9DQptZXRlb3JpdGVfbGFuZGluZ3MgJT4lIA0KICByZW5hbWUobWFzc19nID0gbWFzcykNCmBgYA0KDQojIyBBZGQgbmV3IGNvbHVtbnMgd2l0aCBgbXV0YXRlKClgDQoNCkJlc2lkZXMgc2VsZWN0aW5nIHNldHMgb2YgZXhpc3RpbmcgY29sdW1ucywgaXQncyBvZnRlbiB1c2VmdWwgdG8gYWRkIG5ldyBjb2x1bW5zIHRoYXQgYXJlIGZ1bmN0aW9ucyBvZiBleGlzdGluZyBjb2x1bW5zLiBUaGlzIGlzIHRoZSBqb2Igb2YgYG11dGF0ZSgpYDoNCg0KYGBge3J9DQptZXRlb3JpdGVfbGFuZGluZ3MgJT4lIA0KICBtdXRhdGUobWFzc19rZyA9IG1hc3MvMTAwMCkNCmBgYA0KDQpgZHBseXI6Om11dGF0ZSgpYCBpcyBzaW1pbGFyIHRvIHRoZSBiYXNlIGB0cmFuc2Zvcm0oKWAsIGJ1dCBhbGxvd3MgeW91IHRvIHJlZmVyIHRvIGNvbHVtbnMgdGhhdCB5b3UndmUganVzdCBjcmVhdGVkLg0KDQpJZiB5b3Ugb25seSB3YW50IHRvIGtlZXAgdGhlIG5ldyB2YXJpYWJsZXMsIHVzZSBgdHJhbnNtdXRlKClgOg0KDQpgYGB7cn0NCm1ldGVvcml0ZV9sYW5kaW5ncyAlPiUgDQogIHRyYW5zbXV0ZShtYXNzX2tnID0gbWFzcy8xMDAwKQ0KYGBgDQoNCiMjIFN1bW1hcmlzZSB2YWx1ZXMgd2l0aCBgc3VtbWFyaXNlKClgDQoNClRoZSBsYXN0IHZlcmIgaXMgYHN1bW1hcmlzZSgpYC4gSXQgY29sbGFwc2VzIGEgZGF0YSBmcmFtZSB0byBhIHNpbmdsZSByb3cuDQoNCmBgYHtyfQ0KbWV0ZW9yaXRlX2xhbmRpbmdzICU+JSANCiAgc3VtbWFyaXNlKG1lYW5fbWFzcyA9IG1lYW4obWFzcywgbmEucm0gPSBUKSkNCmBgYA0KDQpJdCdzIG5vdCB0aGF0IHVzZWZ1bCB1bnRpbCB3ZSBsZWFybiB0aGUgYGdyb3VwX2J5KClgIHZlcmIgYmVsb3cuDQoNCmBgYHtyfQ0KbWV0ZW9yaXRlX2xhbmRpbmdzICU+JSANCiAgZ3JvdXBfYnkocmVjY2xhc3MpICU+JSANCiAgc3VtbWFyaXNlKG1lYW5fbWFzcyA9IG1lYW4obWFzcywgbmEucm0gPSBUKSkNCmBgYA0KDQojIyBSYW5kb21seSBzYW1wbGUgcm93cyB3aXRoIGBzYW1wbGVfbigpYCBhbmQgYHNhbXBsZV9mcmFjKClgDQoNCllvdSBjYW4gdXNlIGBzYW1wbGVfbigpYCBhbmQgYHNhbXBsZV9mcmFjKClgIHRvIHRha2UgYSByYW5kb20gc2FtcGxlIG9mIHJvd3M6IHVzZSBgc2FtcGxlX24oKWAgZm9yIGEgZml4ZWQgbnVtYmVyIGFuZCBgc2FtcGxlX2ZyYWMoKWAgZm9yIGEgZml4ZWQgZnJhY3Rpb24uDQoNCmBgYHtyfQ0KbWV0ZW9yaXRlX2xhbmRpbmdzICU+JSANCiAgc2FtcGxlX24oMTApDQoNCm1ldGVvcml0ZV9sYW5kaW5ncyAlPiUgDQogIHNhbXBsZV9mcmFjKDAuMDEpDQpgYGANCg0KVXNlIGByZXBsYWNlID0gVFJVRWAgdG8gcGVyZm9ybSBhIGJvb3RzdHJhcCBzYW1wbGUuIElmIG5lZWRlZCwgeW91IGNhbiB3ZWlnaHQgdGhlIHNhbXBsZSB3aXRoIHRoZSBgd2VpZ2h0YCBhcmd1bWVudC4NCg0KIyMgVGVuIGBkcGx5cmAgVHJpY2tzIQ0KDQojIyMgQXJlIHlvdSBvZnRlbiBzZWxlY3RpbmcgdGhlIHNhbWUgY29sdW1ucyBvdmVyIGFuZCBvdmVyIGFnYWluPw0KDQpZb3UgY2FuIG1ha2UgYSB2ZWN0b3Igb2YgcHJlLWlkZW50aWZpZWQgY29sdW1ucyBvbmNlIGFuZCB0aGVuIHJlZmVyIHRvIHRoZW0gdXNpbmcgYG9uZV9vZigpYCBvciBgISFgIChldmVuIHNob3J0ZXIpLg0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQoNCmNvbHMgPC0gYygibmFtZSIsICJyZWNsYXQiLCAicmVjbG9uZyIpDQoNCmV4MSA8LSBtZXRlb3JpdGVfbGFuZGluZ3MgJT4lIA0KICBzZWxlY3QoISFjb2xzKQ0KDQpoZWFkKGV4MSkNCmBgYA0KDQojIyMgU2VsZWN0IGNvbHVtbnMgdmlhIHJlZ2V4DQoNCklmIHlvdSBoYXZlIG1hdGNoaW5nIHBhdHRlcm5zLCB5b3UgY2FuIHVzZSBgc3RhcnRzX3dpdGgoKWAsIGBjb250YWlucygpYCwgb3IgYGVuZHNfd2l0aGAuIEJ1dCB3aGF0IGlmIHlvdXIgcGF0dGVybiBpc24ndCB0aGF0IGV4YWN0PyBTaW1wbGU6IGVudGVyIHJlZ2V4IGludG8gYG1hdGNoZXMoKWAuDQoNCmBgYHtyfQ0KbGlicmFyeShkcGx5cikNCg0KZXgyIDwtIGlyaXMgJT4lIA0KICBzZWxlY3QobWF0Y2hlcygiUy4rdGgiKSkNCg0KaGVhZChleDIpDQpgYGANCg0KIyMjIFJlb3JkZXJpbmcgeW91ciBjb2x1bW5zDQoNCklmIHlvdSBqdXN0IHdhbnQgdG8gYnJpbmcgb25lIG9yIG1vcmUgY29sdW1ucyB0byB0aGUgZnJvbnQsIHlvdSBjYW4gdXNlIGBldmVyeXRoaW5nKClgIHRvIGFkZCBhbGwgdGhlIHJlbWFpbmluZyBjb2x1bW5zLg0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQoNCmV4MyA8LSBtZXRlb3JpdGVfbGFuZGluZ3MgJT4lIA0KICBzZWxlY3QoaWQsIGV2ZXJ5dGhpbmcoKSkNCg0KaGVhZChleDMpDQpgYGANCg0KIyMjIFJlbmFtaW5nIGFsbCB2YXJpYWJsZXMgaW4gb25lIGdvDQoNCk9uZSBjb21tYW5kIHRvIGdldCB0aGVtIGFsbCBpbiBsb3dlciBjYXNlLCBhbmQgb25lIG1vcmUgdG8gcmVwbGFjZSAiLi5nLiIgaW4gdGhlIGBtYXNzYCB2YXJpYWJsZS4NCg0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShzdHJpbmdyKQ0KDQpleDQgPC0gbWV0ZW9yaXRlX2xhbmRpbmdzICU+JSANCiAgcmVuYW1lX2FsbCh0b2xvd2VyKSAlPiUgDQogIHJlbmFtZV9hbGwofnN0cl9yZXBsYWNlKC4sICIuLmcuIiwgIiIpKQ0KDQpoZWFkKGV4NCkNCmBgYA0KDQojIyMgQ2xlYW5pbmcgdXAgeW91ciBvYnNlcnZhdGlvbnMgaW4gb25lIGdvDQoNClRoZSBgc2VsZWN0X2FsbC9pZi9hdGAgYW5kIGByZW5hbWVfYWxsL2lmL2F0YCBmdW5jdGlvbnMgd2lsbCBvbmx5IG1vZGlmeSB0aGUgdmFyaWFibGUgbmFtZXMsIG5vdCB0aGUgb2JzZXJ2YXRpb25zLiBJZiB5b3Ugd2FudCB0byBjaGFuZ2UgdGhvc2UsIHVzZSB0aGUgYG11dGF0ZWAgdmFyaWFudC4NCg0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShzdHJpbmdyKQ0KDQpleDUgPC0gbWV0ZW9yaXRlX2xhbmRpbmdzICU+JSANCiAgc2VsZWN0KG5hbWUsIG5hbWV0eXBlLCBmYWxsKSAlPiUgDQogIG11dGF0ZV9hbGwodG9sb3dlcikgJT4lIA0KICBtdXRhdGVfYWxsKH5zdHJfcmVwbGFjZV9hbGwoLiwgIiAiLCAiXyIpKQ0KDQpoZWFkKGV4NSkNCmBgYA0KDQojIyMgRmluZGluZyB0aGUgNSBoaWdoZXN0L2xvd2VzdCB2YWx1ZXMNCg0KWW91IGNhbiB1c2UgYHRvcF9uYCB0byBmaW5kIHRoZSA1IG1ldGVvcml0ZXMgd2l0aCB0aGUgaGlnaGVzdCBtYXNzIHdpdGhvdXQgb3JkZXJpbmcgdGhlbSBmaXJzdC4NCg0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KDQptZXRlb3JpdGVfbGFuZGluZ3MgJT4lIA0KICB0b3Bfbig1LCBtYXNzKQ0KYGBgDQoNCiMjIyBBZGRpbmcgdGhlIGFtb3VudCBvZiBvYnNlcnZhdGlvbnMNCg0KWW91IGNhbiBhZGQgdGhlIGFtb3VudCBvZiBvYnNlcnZhdGlvbnMgd2l0aG91dCBzdW1tYXJpc2luZyB0aGVtIHlvdXJzZWxmLiBJZiB5b3UgZG9uJ3QgbGlrZSB0aGUgZGVmYXVsdCBjb2x1bW4gbmFtZSBgbmAsIHlvdSBjYW4gY2hhbmdlIGl0IGFnYWluIHdpdGggYSBgcmVuYW1lKClgIHN0YXRlbWVudC4NCg0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KDQpleDcgPC0gbWV0ZW9yaXRlX2xhbmRpbmdzICU+JSANCiAgYWRkX2NvdW50KHJlY2NsYXNzKSAlPiUgDQogIHJlbmFtZShuX3JlY2NsYXNzID0gbikNCg0KaGVhZChleDcpDQpgYGANCg0KIyMjIE1ha2luZyBuZXcgZGlzY3JldGUgdmFyaWFibGVzDQoNCmBjYXNlX3doZW4oKWAgY2FuIGJlIGEgdmVyeSBwb3dlcmZ1bCB0b29sIHRvIG1ha2UgbmV3IGRpc2NyZXRlIHZhcmlhYmxlcyBiYXNlZCBvbiBvdGhlciBjb2x1bW5zLg0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQoNCmV4OCA8LSBzdGFyd2FycyAlPiUgDQogIHNlbGVjdChuYW1lLCBzcGVjaWVzLCBob21ld29ybGQsIGJpcnRoX3llYXIsIGhhaXJfY29sb3IpICU+JSANCiAgbXV0YXRlKG5ld19ncm91cCA9IGNhc2Vfd2hlbigNCiAgICBzcGVjaWVzID09ICJEcm9pZCIgfiAiUm9ib3QiLA0KICAgIGhvbWV3b3JsZCA9PSAiVGF0b29pbmUiICYgaGFpcl9jb2xvciA9PSAiYmxvbmQiIH4gIkJsb25kIFRhdG9vaW5pYW4iLA0KICAgIGhvbWV3b3JsZCA9PSAiVGF0b29pbmUiIH4gIk90aGVyIFRhdG9vaW5pYW4iLA0KICAgIGhhaXJfY29sb3IgPT0gImJsb25kIiB+ICJCbG9uZCBub24tVGF0b29pbmlhbiIsDQogICAgVFJVRSB+ICJPdGhlciBIdW1hbiIpKQ0KDQpoZWFkKGV4OCkNCmBgYA0KDQojIyMgR29pbmcgcm93d2lzZS4uLg0KDQpNdXRhdGluZyB3aXRoIGFnZ3JlZ2F0ZSBmdW5jdGlvbnMgYnkgZGVmYXVsdCB3aWxsIHRha2UgdGhlIGF2ZXJhZ2Uvc3VtLy4uLiBvZiB0aGUgZW50aXJlIGNvbHVtbi4gVmlhIGFkZGluZyBgcm93d2lzZSgpYCB5b3UgY2FuIGFnZ3JlZ2F0ZSB3aXRoaW4gYW4gb2JzZXJ2YXRpb24uDQoNCmBgYHtyfQ0KbGlicmFyeShkcGx5cikNCg0KZXg5IDwtIGlyaXMgJT4lIA0KICBzZWxlY3QoY29udGFpbnMoIkxlbmd0aCIpKSAlPiUgDQogIHJvd3dpc2UoKSAlPiUgDQogIG11dGF0ZShhdmdfbGVuZ3RoID0gbWVhbihjKFBldGFsLkxlbmd0aCwgU2VwYWwuTGVuZ3RoKSkpDQoNCmhlYWQoZXg5KQ0KYGBgDQoNCiMjIyBDaGFuZ2luZyB5b3VyIGNvbHVtbiBuYW1lcyBhZnRlciBgc3VtbWFyaXNlX2lmYA0KDQpJZiB5b3UndmUgdXNlZCB0aGUgYHN1bW1hcmlzZV9hbGwvaWYvYXRgIHZhcmlhbnRzIGJlZm9yZSwgeW91IGtub3cgdGhhdCB0aGUgdmFyaWFibGUgbmFtZSBieSBkZWZhdWx0IGRvZXMgbm90IGdldCBjaGFuZ2VkLiBJZiB5b3Ugd2FudCBhIG1vZGlmaWVkIG5hbWUsIHlvdSBjYW4gd3JhcCB5b3VyIGZ1bmN0aW9uIGluc2lkZSBgZnVucygpYCBhbmQgYWRkIGEgdGFnIHRoYXQgd2lsbCBiZSBhZGRlZCB0byB0aGUgdmFyaWFibGUgbmFtZS4NCg0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KDQppcmlzICU+JSANCiAgc3VtbWFyaXNlX2lmKGlzLm51bWVyaWMsIGZ1bnMoYXZnID0gbWVhbikpDQpgYGANCg0KIyBBZGRpdGlvbmFsIFJlc291cmNlcw0KDQojIyBIZWxwZnVsIFBhY2thZ2VzIFRvIEdldCBTdGFydGVkIFdpdGgNCg0KVG8gbGVhcm4gbW9yZSBhYm91dCBhbmQgcmVhZCBkb2N1bWVudGF0aW9uIGZvciBlYWNoIG9mIHRoZXNlIHBhY2thZ2VzLCBlaXRoZXIgdHlwZSBgPzxwYWNrYWdlbmFtZT5gIGludG8geW91ciBSU3R1ZGlvIGNvbnNvbGUgb3Igc2VhcmNoIGA8cGFja2FnZW5hbWU+YCBpbiB0aGUgW0NSQU4gcmVwb3NpdG9yeV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2F2YWlsYWJsZV9wYWNrYWdlc19ieV9uYW1lLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0uDQoNCi0gYGJsb2JgIC0gZm9yIHN0b3JpbmcgYmxvYiAoYmluYXJ5KSBkYXRhDQotIGBib290YCAtIGJvb3RzdHJhcCBmdW5jdGlvbnMNCi0gYGJyb29tYCAtIHRpZGllcyBzdGF0aXN0aWNhbCBtb2RlbHMgaW50byBkYXRhIGZyYW1lcw0KLSBgY2FyZXRgIC0gc3RyZWFtbGluZXMgdGhlIHByb2Nlc3MgZm9yIGNyZWF0aW5nIHByZWRpY3RpdmUgbW9kZWxzIFsoSGVscGZ1bCBXZWJzaXRlKV0oaHR0cDovL3RvcGVwby5naXRodWIuaW8vY2FyZXQvaW5kZXguaHRtbCl7dGFyZ2V0PSJfYmxhbmsifQ0KLSBgY2x1c3RlcmAgLSBjbHVzdGVyaW5nIG1ldGhvZHMNCi0gYGNvZWZwbG90YCAtIHBsb3RzIGNvZWZmaWNpZW50cyBmcm9tIGZpdHRlZCBtb2RlbHMNCi0gYGRhdGEudGFibGVgIC0gZXh0ZW5zaW9uIG9mIGRhdGEuZnJhbWVzDQotIGBkZXZ0b29sc2AgLSB0b29scyB0byBtYWtlIGFuIFIgZGV2ZWxvcGVyJ3MgbGlmZSBlYXNpZXINCi0gYGRwbHlyYCAtICoqcGFydCBvZiB0aGUgVGlkeXZlcnNlLCoqIGNvbnZlbmllbnQgZGF0YSBtYW5pcHVsYXRpb24sIG11bmdpbmcsIGFuZCBjbGVhbmluZyBpbiBSDQotIGBmb3JjYXRzYCAtICoqcGFydCBvZiB0aGUgVGlkeXZlcnNlLCoqIHNvbHZlcyBjb21tb24gcHJvYmxlbXMgd2l0aCBmYWN0b3JzDQotIGBnYm1gIC0gZ3JhZGllbnQgYm9vc3RpbmcgbW9kZWxzLCBjYW4gYmUgaW50ZWdyYXRlZCB3aXRoIGBjYXJldGANCi0gYGdncGxvdDJgIC0gKipwYXJ0IG9mIHRoZSBUaWR5dmVyc2UsKiogZGF0YSB2aXN1YWxpemF0aW9uIGluIFIgdGhhdCBmb2xsb3dzIHRoZSBbIkdyYW1tYXIgb2YgR3JhcGhpY3MiXShodHRwczovL3d3dy5hbWF6b24uY29tL0dyYW1tYXItR3JhcGhpY3MtU3RhdGlzdGljcy1Db21wdXRpbmcvZHAvMDM4NzI0NTQ0OCl7dGFyZ2V0PSJfYmxhbmsifQ0KLSBgZ2xtbmV0YCAtIGxhc3NvIGFuZCBlbGFzdGljLW5ldCByZWd1bGFyaXplZCBHTE1zLCBjYW4gYmUgaW50ZWdyYXRlZCB3aXRoIGBjYXJldGANCi0gYGdyaWRFeHRyYWAgLSBtaXNjZWxsYW5lb3VzIGZ1bmN0aW9ucyBmb3IgImdyaWQiIGdyYXBoaWNzDQotIGBobXNgIC0gZm9yIHdvcmtpbmcgd2l0aCB0aW1lLW9mLWRheSB2YWx1ZXMNCi0gYElTTFJgIC0gZGF0YSBmb3IgYW4gIkludHJvZHVjdGlvbiB0byBTdGF0aXN0aWNhbCBMZWFybmluZyB3aXRoIEFwcGxpY2F0aW9ucyBpbiBSIg0KLSBgbHVicmlkYXRlYCAtIGZvciB3b3JraW5nIHdpdGggZGF0ZXMgYW5kIGRhdGUtdGltZXMNCi0gYE1BU1NgIC0gZnVuY3Rpb25zIGFuZCBkYXRhc2V0cyBmb3IgYXBwbGllZCBzdGF0aXN0aWNzDQotIGBwZHBgIC0gcGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzDQotIGBwbHNgIC0gcGFydGlhbCBsZWFzdCBzcXVhcmVzIGFuZCBwcmluY2lwYWwgY29tcG9uZW50IHJlZ3Jlc3Npb24sIGNhbiBiZSBpbnRlZ3JhdGVkIHdpdGggYGNhcmV0YA0KLSBgcGx5cmAgLSAqKnBhcnQgb2YgdGhlIFRpZHl2ZXJzZSwqKiB0b29scyBmb3Igc3BsaXQtYXBwbHktY29tYmluZSBhbmFseXNlcw0KLSBgcFJPQ2AgLSBkaXNwbGF5cyBhbmQgYW5hbHl6ZXMgUk9DIGN1cnZlcw0KLSBgcHVycnJgIC0gKipwYXJ0IG9mIHRoZSBUaWR5dmVyc2UsKiogZW5oYW5jZXMgUidzIGZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcgc2V0IG9mIHRvb2xzIGZvciB3b3JraW5nIHdpdGggZnVuY3Rpb25zIGFuZCB2ZWN0b3JzDQotIGByYW5kb21Gb3Jlc3RgIC0gcmFuZG9tIGZvcmVzdCBtb2RlbHMsIGNhbiBiZSBpbnRlZ3JhdGVkIHdpdGggYGNhcmV0YA0KLSBgcmVhZHJgIC0gKipwYXJ0IG9mIHRoZSBUaWR5dmVyc2UsKiogcHJvdmlkZXMgYSBmYXN0IGFuZCBmcmllbmRseSB3YXkgdG8gcmVhZCByZWN0YW5ndWxhciBkYXRhDQotIGByZWFkeGxgIC0gKipwYXJ0IG9mIHRoZSBUaWR5dmVyc2UsKiptYWtlcyByZWFkaW5nIEV4Y2VsIGZpbGVzIG11Y2ggZWFzaWVyDQotIGBycGFydGAgLSBkZWNpc2lvbiB0cmVlIG1vZGVscywgY2FuIGJlIGludGVncmF0ZWQgd2l0aCBgY2FyZXRgDQotIGBycGFydC5wbG90YCAtIHBsb3R0aW5nIG9mIGRlY2lzaW9uIHRyZWUgbW9kZWxzDQotIGBzdHJpbmdyYCAtICoqcGFydCBvZiB0aGUgVGlkeXZlcnNlLCoqIG1ha2VzIHdvcmtpbmcgd2l0aCBzdHJpbmcgb2JqZWN0cyBtdWNoIGVhc2llcg0KLSBgdGliYmxlYCAtICoqcGFydCBvZiB0aGUgVGlkeXZlcnNlLCoqIGNyZWF0ZXMgZGF0YS5mcmFtZXMgdGhhdCBhcmUgZWFzaWVyIHRvIHdvcmsgd2l0aA0KLSBgdGlkeXJgIC0gKipwYXJ0IG9mIHRoZSBUaWR5dmVyc2UsKiogcHJvdmlkZXMgYSBzZXQgb2YgZnVuY3Rpb25zIHRvIGhlbHAgeW91IGdldCB0byB0aWR5IGRhdGENCi0gYHRpZHl2ZXJzZWAgLSAqKnNldCBvZiBoZWxwZnVsIHBhY2thZ2VzIGZvciB0aWRpZXIgZGF0YSoqIFsoSGVscGZ1bCBXZWJzaXRlKV0oaHR0cDovL3RpZHl2ZXJzZS5vcmcpe3RhcmdldD0iX2JsYW5rIn0NCi0gYHhnYm9vc3RgIC0gZVh0cmVtZSBHcmFkaWVudCBCb29zdGluZyBtb2RlbHMsIGNhbiBiZSBpbnRlZ3JhdGVkIHdpdGggYGNhcmV0YA0KDQojIyBSIGFuZCBSU3R1ZGlvIFJlc291cmNlcw0KDQojIyMgUlN0dWRpbyBDaGVhdHNoZWV0cw0KDQpbUlN0dWRpbyBDaGVhdHNoZWV0c10oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcmVzb3VyY2VzL2NoZWF0c2hlZXRzLyl7dGFyZ2V0PSJfYmxhbmsifSBjYW4gYmUgZG93bmxvYWRlZCBmcm9tIHRoZSBSU3R1ZGlvIElERSBieSBuYXZpZ2F0aW5nIHRvIGBIZWxwYCA+IGBDaGVhdHNoZWV0c2AuIFRoZSBtb3N0IGNvbW1vbmx5LXVzZWQgY2hlYXRzaGVldHMgaW5jbHVkZToNCg0KLSBbUlN0dWRpbyBJREUgQ2hlYXQgU2hlZXRdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL3Jhdy9tYXN0ZXIvcnN0dWRpby1pZGUucGRmKXt0YXJnZXQ9Il9ibGFuayJ9DQotIFtEYXRhIEltcG9ydCBDaGVhdCBTaGVldF0oaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8vY2hlYXRzaGVldHMvcmF3L21hc3Rlci9kYXRhLWltcG9ydC5wZGYpe3RhcmdldD0iX2JsYW5rIn0NCi0gW0RhdGEgVHJhbnNmb3JtYXRpb24gd2l0aCBgZHBseXJgXShodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9yYXcvbWFzdGVyL2RhdGEtdHJhbnNmb3JtYXRpb24ucGRmKXt0YXJnZXQ9Il9ibGFuayJ9DQotIFtEYXRhIFZpc3VhbGl6YXRpb24gd2l0aCBgZ2dwbG90MmBdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL3Jhdy9tYXN0ZXIvZGF0YS12aXN1YWxpemF0aW9uLTIuMS5wZGYpe3RhcmdldD0iX2JsYW5rIn0NCi0gW0RhdGVzIGFuZCBUaW1lcyB3aXRoIGBsdWJyaWRhdGVgXShodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9yYXcvbWFzdGVyL2x1YnJpZGF0ZS5wZGYpe3RhcmdldD0iX2JsYW5rIn0NCi0gW1N0cmluZyBNYW5pcHVsYXRpb24gd2l0aCBgc3RyaW5ncmBdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL3Jhdy9tYXN0ZXIvc3RyaW5ncy5wZGYpe3RhcmdldD0iX2JsYW5rIn0NCi0gW0FwcGx5IGZ1bmN0aW9ucyB3aXRoIGBwdXJycmBdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL3Jhdy9tYXN0ZXIvcHVycnIucGRmKXt0YXJnZXQ9Il9ibGFuayJ9DQoNCiMjIyBIZWxwZnVsIExpbmtzDQoNCi0gW0NSQU4gSW50cm8gdG8gUiBNYW51YWxdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL2RvYy9tYW51YWxzL3ItcmVsZWFzZS9SLWludHJvLnBkZil7dGFyZ2V0PSJfYmxhbmsifQ0KLSBbVGhlIFIgR3JhcGggR2FsbGVyeV0oaHR0cHM6Ly93d3cuci1ncmFwaC1nYWxsZXJ5LmNvbS8pe3RhcmdldD0iX2JsYW5rIn0NCi0gW1RvcCA1MCBgZ2dwbG90MmAgdmlzdWFsaXphdGlvbnNdKGh0dHA6Ly9yLXN0YXRpc3RpY3MuY28vVG9wNTAtR2dwbG90Mi1WaXN1YWxpemF0aW9ucy1NYXN0ZXJMaXN0LVItQ29kZS5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9DQoNCiMjIyBHb29nbGUgYW5kIENvbW11bml0eQ0KDQotIFtHb29nbGUgYW5kIFRyeSFdKGh0dHA6Ly9nb29nbGUuY29tKXt0YXJnZXQ9Il9ibGFuayJ9DQotIFtSU2Vlay5vcmcgKEdvb2dsZSBmb3IgUildKGh0dHA6Ly9yc2Vlay5vcmcpe3RhcmdldD0iX2JsYW5rIn0NCi0gW09DIFIgVXNlciBHcm91cF0oaHR0cDovL21lZXR1cC5jb20vT0MtUlVHLyl7dGFyZ2V0PSJfYmxhbmsifQ0KLSBbUi1MYWRpZXMgSXJ2aW5lXShodHRwOi8vbWVldHVwLmNvbS9ybGFkaWVzLWlydmluZS8pe3RhcmdldD0iX2JsYW5rIn0NCi0gWyNyc3RhdHMgY29tbXVuaXR5IG9uIFR3aXR0ZXJdKGh0dHA6Ly90d2l0dGVyLmNvbSl7dGFyZ2V0PSJfYmxhbmsifQ0KDQojIFRoYW5rIHlvdSBmb3IgYXR0ZW5kaW5nIG15IHR1dG9yaWFsIG9uICJEYXRhIE1hbmlwdWxhdGlvbiB3aXRoIFRpZHkgVG9vbHMuIg0KDQpBbGwgdHV0b3JpYWwgbWF0ZXJpYWxzIGNhbiBiZSBmb3VuZCBbaGVyZSBvbiBteSBHaXRIdWIuXShodHRwczovL2dpdGh1Yi5jb20vYWNvbHVtL2NvbmZlcmVuY2UtcHJlc2VudGF0aW9ucy90cmVlL21hc3Rlci9EYXRhJTIwTWFuaXB1bGF0aW9uJTIwd2l0aCUyMFRpZHklMjBUb29scyl7dGFyZ2V0PSJfYmxhbmsifQ0KDQpUaGFua3Mgc28gbXVjaCBmb3IgbGlzdGVuaW5nIGFuZCBmb2xsb3dpbmcgYWxvbmcsIGFuZCBJIGhvcGUgeW91J2xsIGhhdmUgYSBncmVhdCByZXN0IG9mIHlvdXIgaGFja2F0aG9uIQ0KDQo8aHIgLz4NCg0KPGNlbnRlcj4NCg0KQSB3b3JrIGJ5IFtBbHlzc2EgQ29sdW1idXNdKGh0dHBzOi8vYWx5c3NhY29sdW1idXMuY29tLyl7dGFyZ2V0PSJfYmxhbmsifS4NCg0KKltoZWxsb0BhbHlzc2Fjb2x1bWJ1cy5jb21dKG1haWx0bzpoZWxsb0BhbHlzc2Fjb2x1bWJ1cy5jb20pe3RhcmdldD0iX2JsYW5rIn0qDQoNCjxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly9jZG5qcy5jbG91ZGZsYXJlLmNvbS9hamF4L2xpYnMvZm9udC1hd2Vzb21lLzQuNy4wL2Nzcy9mb250LWF3ZXNvbWUubWluLmNzcyI+DQogICAgDQo8cCBzdHlsZT0idGV4dC1hbGlnbjogY2VudGVyOyI+DQo8YSBocmVmPSJodHRwczovL2FseXNzYWNvbHVtYnVzLmNvbS8iIGNsYXNzPSJmYSBmYS1saW5rIiB0YXJnZXQ9Il9ibGFuayI+PC9hPg0KPGEgaHJlZj0iaHR0cHM6Ly90d2l0dGVyLmNvbS9hbHljb2x1bWJ1cz9sYW5nPWVuIiBjbGFzcz0iZmEgZmEtdHdpdHRlciIgdGFyZ2V0PSJfYmxhbmsiPjwvYT4NCjxhIGhyZWY9Imh0dHBzOi8vd3d3LmxpbmtlZGluLmNvbS9pbi9hY29sdW0vIiBjbGFzcz0iZmEgZmEtbGlua2VkaW4iIHRhcmdldD0iX2JsYW5rIj48L2E+DQo8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vYWNvbHVtLyIgY2xhc3M9ImZhIGZhLWdpdGh1YiIgdGFyZ2V0PSJfYmxhbmsiPjwvYT4NCjwvcD4NCg0KPC9jZW50ZXI+