Introduction
Data transformation is a fundamental aspect of data analysis, enabling the conversion of raw data into a structured format suitable for analytics. While there are many ways to transform and prepare data, the dplyr
package – an integral part of the Tidyverse – offers a suite of functions designed to simplify this process. In this lesson, we explore key dplyr
functions, including filter()
, arrange()
, select()
, mutate()
, and summarize()
.
What is the Tidyverse?
The “Tidyverse” is a collection of R packages designed to work seamlessly together, adhering to a consistent philosophy of “tidy data.” It provides tools for a wide range of data-related tasks, from importing and cleaning data to transforming, visualizing, and modeling it.
The Tidyverse was developed by Hadley Wickham (CTO at R Studio), a prominent statistician and software developer in the R community. Wickham is well-known for creating many influential R packages and shaping the modern landscape of data analysis in R. His work has revolutionized how analysts and researchers interact with data, emphasizing simplicity, consistency, and the concept of “tidy data.”
Key features of the Tidyverse include:
- Tidy Data Paradigm: Data is organized so that each variable forms a column, each observation forms a row, and each type of observation unit forms a table.
- Consistency: All packages in the Tidyverse use a consistent syntax and grammar, making them intuitive to learn and use.
- Ease of Use: The Tidyverse simplifies complex operations, enabling analysts to focus more on insights rather than the mechanics of data handling.
The Tidyverse includes several key packages, including:
- ggplot2: For data visualization.
- dplyr: For data manipulation.
- tidyr: For tidying and reshaping data.
- readr: For reading data files.
- tibble: For improved data frame functionality.
- purrr: For functional programming with lists.
What is dplyr?
dplyr is a core package within the Tidyverse, specifically focused on data manipulation. It provides a grammar of data manipulation, making it easy to perform tasks such as filtering, sorting, summarizing, and transforming data.
Key operations in dplyr, often called the “verbs” of data manipulation, include:
filter()
: Select rows based on conditions.
select()
: Choose specific columns.
mutate()
: Add or modify columns.
arrange()
: Sort rows.
summarize()
: Calculate summary statistics.
group_by()
: Group data for grouped operations.
These functions are designed to be intuitive, readable, and chainable using the %>%
operator (pipe), which allows you to express sequences of transformations in a clear and linear fashion. They present an alternative to using SQL for data filtering, summarizing, and selection.
The dplyr Package
dplyr is part of the Tidyverse, meaning it shares the same principles, syntax, and goals as other Tidyverse packages. It serves as the data manipulation backbone for many data analysis workflows in the Tidyverse. In this section, we will explore key functions in detail. To use the dplyr package, you must install it and then load it:
suppressPackageStartupMessages(library(dplyr))
The call to suppressPackageStartupMessages()
is necessary to “suppress” all warnings and messages; if not done, those would end up in your knitted notebook.
The dplyr package uses a modified (and simplified) tabular structure called a tibble that replaces dataframes in Tidyverse.
To load all packages of the Tidyverse, you can simply load tidyverse.
tibbles
Tibbles are a modernized re-imagination of data frames in R, introduced by the tibble package, which is part of the Tidyverse. The goal is to address some of the limitations and frustrations associated with base R data frames by providing a more “user-friendly” and consistent experience for data manipulation.
Tibbles provide some key benefits over dataframes, including:
Simplified Printing
Tibbles display only the first 10 rows and as many columns as fit the screen, avoiding overwhelming output for large datasets. For example:
library(tibble)
tibble_example <- tibble(
x = 1:100,
y = rnorm(100)
)
tibble_example
## # A tibble: 100 × 2
## x y
## <int> <dbl>
## 1 1 1.24
## 2 2 0.0969
## 3 3 -0.403
## 4 4 0.946
## 5 5 0.323
## 6 6 0.0385
## 7 7 -0.582
## 8 8 0.416
## 9 9 0.636
## 10 10 -0.462
## # ℹ 90 more rows
Output will show:
# A tibble: 100 × 2
x y
<int> <dbl>
1 1 -0.276
2 2 1.003
3 3 0.735
# … with 97 more rows
Preservation of Data Types
Unlike base R data frames, tibbles never automatically convert strings into factors or perform other unexpected type coercion. For example:
df <- data.frame(name = "Alice", age = 25)
class(df$name) # Output: factor
## [1] "character"
tb <- tibble(name = "Alice", age = 25)
class(tb$name) # Output: character
## [1] "character"
Column Access by Name
Tibbles enforce stricter rules for column access:
- Accessing columns with
$
or [[
requires exact matches to column names.
- Partial matching of names is disallowed, reducing errors.
tb <- tibble(name = "Alice", age = 25)
tb$n # Error: Unknown column 'n'
## Warning: Unknown or uninitialised column: `n`.
## NULL
Support for Non-Syntactic Names
Tibbles allow column names that aren’t syntactically valid in R, such as names containing spaces or special characters. These must be referenced using backticks:
tb <- tibble(`student name` = "Alice", age = 25)
tb$`student name` # Access column
## [1] "Alice"
Creation Flexibility
Tibbles can be created using the tibble()
function or the tribble()
function for row-wise data entry. For example:
# Using tibble()
tb <- tibble(name = c("Alice", "Bob"), age = c(25, 30))
# Using tribble()
tb <- tribble(
~name, ~age,
"Alice", 25,
"Bob", 30
)
Tibbles are especially useful when working with the Tidyverse because all Tidyverse packages are designed to work optimally with tibbles. They are recommended whenever you want a more predictable and consistent data structure for data manipulation and analysis.
All of the functions that expect a dataframe also work with tibbles.
Reading CSV Files
To load a CSV file into a tibble rather than a dataframe, use read_csv()
instead of read.csv()
. Using read_csv()
over Base R’s read.csv()
has several benefits (aside from returning a tibble).
- Better Defaults:
- Strings are not automatically converted to factors.
- Column types are automatically guessed and more accurately handled.
- Faster Performance:
read_csv()
is (a bit) faster for large files.
- Tidyverse Integration:
- Returns a tibble, which integrates seamlessly with other Tidyverse packages.
- Informative Output:
- Prints metadata about the data during import (e.g., column types).
The function read_csv()
is part of the readr package, so either install and load that package of the full tidyverse package set.
Consider a dataset students
containing information on students’ names, majors, and GPA’s (from the CSV file students.csv):
library(readr)
students <- read_csv("students.csv")
## Rows: 7 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): name, major
## dbl (1): gpa
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
The function read_csv()
displays information about the structure of the CSV. If you wish to “suppress” this information, then use:
library(readr)
students <- read_csv("students.csv", show_col_types = FALSE)
Filtering Rows with filter()
The filter()
function extracts rows from a data frame that meet specified logical conditions. To select students with a GPA of 3.5 or better, you can use the following dplyr pipe:
filtered_students <- students %>%
filter(gpa >= 3.50)
This returns rows where the gpa
column is greater than or equal to 3.50. To display the result, you can use the print()
function:
## # A tibble: 4 × 3
## name major gpa
## <chr> <chr> <dbl>
## 1 Alice Jones CS 3.82
## 2 Liu Chen CS 3.9
## 3 Karl Langenfelder Bus 3.68
## 4 Susan Myers Arch 3.76
Arranging Rows with arrange()
The arrange()
function orders rows based on the values of specified columns. To sort the students
dataset by gpa
in descending order:
arranged_students <- students %>%
arrange(desc(gpa))
print(arranged_students)
## # A tibble: 7 × 3
## name major gpa
## <chr> <chr> <dbl>
## 1 Liu Chen CS 3.9
## 2 Alice Jones CS 3.82
## 3 Susan Myers Arch 3.76
## 4 Karl Langenfelder Bus 3.68
## 5 Cameron Wu IE 3.45
## 6 Sandeep Patel Econ 3.4
## 7 Gert Wilder Math 2.84
This sorts the students from the highest to the lowest grade.
Selecting Columns with select()
The select()
function allows for the extraction of specific columns from a data frame. To create a new dataset with only the name
and gpa
columns:
selected_students <- students %>%
select(name, gpa)
print(selected_students)
## # A tibble: 7 × 2
## name gpa
## <chr> <dbl>
## 1 Alice Jones 3.82
## 2 Sandeep Patel 3.4
## 3 Gert Wilder 2.84
## 4 Liu Chen 3.9
## 5 Cameron Wu 3.45
## 6 Karl Langenfelder 3.68
## 7 Susan Myers 3.76
This results in a dataset containing only the name
and gpa
columns. From a relational perspective, this is a “projection” operation.
Adding or Modifying Columns with mutate()
The mutate()
function is used to add new columns or modify existing ones. To add a column honors
that categorizes students based on their GPA:
mutated_students <- students %>%
mutate(grade_category = case_when(
gpa >= 3.9 ~ "Excellent",
gpa >= 3.5 ~ "Good",
gpa >= 3.0 ~ "Passing",
TRUE ~ "Below Passing"
))
print(mutated_students)
## # A tibble: 7 × 4
## name major gpa grade_category
## <chr> <chr> <dbl> <chr>
## 1 Alice Jones CS 3.82 Good
## 2 Sandeep Patel Econ 3.4 Passing
## 3 Gert Wilder Math 2.84 Below Passing
## 4 Liu Chen CS 3.9 Excellent
## 5 Cameron Wu IE 3.45 Passing
## 6 Karl Langenfelder Bus 3.68 Good
## 7 Susan Myers Arch 3.76 Good
This adds a honors
column with values for various ranges of GPA.
Summarizing Data with summarize()
The summarize()
function computes summary statistics for a data frame. For example, to calculate the mean (average) GPA of the students:
average_gpa <- students %>%
summarize(mean_gpa = mean(gpa))
print(average_gpa)
## # A tibble: 1 × 1
## mean_gpa
## <dbl>
## 1 3.55
This computes the mean of the gpa
column, providing the average GPA
Grouped Operations with group_by()
and summarize()
For more complex analyses, group_by()
in conjunction with summarize()
enables grouped operations. To calculate the average GPA by major:
avg_by_major <- students %>%
group_by(major) %>%
summarize(avg_gpa = mean(gpa))
print(avg_by_major)
## # A tibble: 6 × 2
## major avg_gpa
## <chr> <dbl>
## 1 Arch 3.76
## 2 Bus 3.68
## 3 CS 3.86
## 4 Econ 3.4
## 5 IE 3.45
## 6 Math 2.84
Whenever there’s a “by” or “per” it means that we want to computer some “fact” for grouped data.
These examples demonstrate the versatility of dplyr
in performing data transformations, facilitating efficient data manipulation for complex analyses using an alternative to SQL.
LS0tCnRpdGxlOiAiRGF0YSBNYW5pcHVsYXRpb24gd2l0aCA8Yj5kcGx5cjwvYj4iCnBhcmFtczoKICB0eXBlIDogbGVzc29uCiAgY2F0ZWdvcnk6IDYKICBudW1iZXI6IDEwNwogIHRpbWU6IDQ1CiAgbGV2ZWw6IGJlZ2lubmVyCiAgdGFnczogInIsZHBseXIsZmlsdGVyaW5nLHN1bW1hcml6aW5nLHNlbGVjdGluZyIKICBkZXNjcmlwdGlvbjogIkRlbW9uc3RyYXRlcyB0aGUgdXNlIG9mIHRoZSA8Yj5kcGx5cjwvYj4gcGFja2FnZSBvZiB0aGUgVGlkeXZlcnNlCiAgICAgICAgICAgICAgICBmb3IgZGF0YSBtYW5pcHVsYXRpb24sIHN1bW1hcml6YXRpb24sIGFuZCBmaWx0ZXJpbmcuIgpkYXRlOiAiPHNtYWxsPmByIFN5cy5EYXRlKClgPC9zbWFsbD4iCmF1dGhvcjogIjxzbWFsbD5NYXJ0aW4gU2NoZWRsYmF1ZXI8L3NtYWxsPiIKZW1haWw6ICJtLnNjaGVkbGJhdWVyQG5ldS5lZHUiCmFmZmlsaXRhdGlvbjogIk5vcnRoZWFzdGVybiBVbml2ZXJzaXR5IgpvdXRwdXQ6IAogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0aGVtZTogc3BhY2VsYWIKICAgIGhpZ2hsaWdodDogdGFuZ28KLS0tCgotLS0KdGl0bGU6ICI8c21hbGw+YHIgcGFyYW1zJGNhdGVnb3J5YC5gciBwYXJhbXMkbnVtYmVyYDwvc21hbGw+PGJyLz48c3BhbiBzdHlsZT0nY29sb3I6ICMyRTQwNTM7IGZvbnQtc2l6ZTogMC45ZW0nPmByIHJtYXJrZG93bjo6bWV0YWRhdGEkdGl0bGVgPC9zcGFuPiIKLS0tCgpgYGB7ciBjb2RlPXhmdW46OnJlYWRfdXRmOChwYXN0ZTAoaGVyZTo6aGVyZSgpLCcvUi9faW5zZXJ0MkRCLlInKSksIGluY2x1ZGUgPSBGQUxTRX0KYGBgCgojIyBQcmVxdWlzaXRlcwoKVGhpcyBsZXNzb24gcHJlc3VtZXMgdGhhdCB5b3UgYXJlIGZhbWlsaWFyIHdpdGggdGhlIGNvbnRlbnRzIG9mOgoKLSAgIFs2LjEwNCAtLSBRdWljayBHdWlkZSB0byBSIEZvciBQcm9ncmFtbWVyc10oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wNi5yL2wtNi0xMDQtcjRwcm9ncy9sLTYtMTA0Lmh0bWwpCgojIyBJbnRyb2R1Y3Rpb24KCkRhdGEgdHJhbnNmb3JtYXRpb24gaXMgYSBmdW5kYW1lbnRhbCBhc3BlY3Qgb2YgZGF0YSBhbmFseXNpcywgZW5hYmxpbmcgdGhlIGNvbnZlcnNpb24gb2YgcmF3IGRhdGEgaW50byBhIHN0cnVjdHVyZWQgZm9ybWF0IHN1aXRhYmxlIGZvciBhbmFseXRpY3MuIFdoaWxlIHRoZXJlIGFyZSBtYW55IHdheXMgdG8gdHJhbnNmb3JtIGFuZCBwcmVwYXJlIGRhdGEsIHRoZSBgZHBseXJgIHBhY2thZ2UgLS0gYW4gaW50ZWdyYWwgcGFydCBvZiB0aGUgVGlkeXZlcnNlIC0tIG9mZmVycyBhIHN1aXRlIG9mIGZ1bmN0aW9ucyBkZXNpZ25lZCB0byBzaW1wbGlmeSB0aGlzIHByb2Nlc3MuIEluIHRoaXMgbGVzc29uLCB3ZSBleHBsb3JlIGtleSBgZHBseXJgIGZ1bmN0aW9ucywgaW5jbHVkaW5nIGBmaWx0ZXIoKWAsIGBhcnJhbmdlKClgLCBgc2VsZWN0KClgLCBgbXV0YXRlKClgLCBhbmQgYHN1bW1hcml6ZSgpYC4KCiMjIyAqKldoYXQgaXMgdGhlIFRpZHl2ZXJzZT8qKgoKVGhlICJUaWR5dmVyc2UiIGlzIGEgY29sbGVjdGlvbiBvZiBSIHBhY2thZ2VzIGRlc2lnbmVkIHRvIHdvcmsgc2VhbWxlc3NseSB0b2dldGhlciwgYWRoZXJpbmcgdG8gYSBjb25zaXN0ZW50IHBoaWxvc29waHkgb2YgInRpZHkgZGF0YS4iIEl0IHByb3ZpZGVzIHRvb2xzIGZvciBhIHdpZGUgcmFuZ2Ugb2YgZGF0YS1yZWxhdGVkIHRhc2tzLCBmcm9tIGltcG9ydGluZyBhbmQgY2xlYW5pbmcgZGF0YSB0byB0cmFuc2Zvcm1pbmcsIHZpc3VhbGl6aW5nLCBhbmQgbW9kZWxpbmcgaXQuCgpUaGUgVGlkeXZlcnNlIHdhcyBkZXZlbG9wZWQgYnkgSGFkbGV5IFdpY2toYW0gKENUTyBhdCBSIFN0dWRpbyksIGEgcHJvbWluZW50IHN0YXRpc3RpY2lhbiBhbmQgc29mdHdhcmUgZGV2ZWxvcGVyIGluIHRoZSBSIGNvbW11bml0eS4gV2lja2hhbSBpcyB3ZWxsLWtub3duIGZvciBjcmVhdGluZyBtYW55IGluZmx1ZW50aWFsIFIgcGFja2FnZXMgYW5kIHNoYXBpbmcgdGhlIG1vZGVybiBsYW5kc2NhcGUgb2YgZGF0YSBhbmFseXNpcyBpbiBSLiBIaXMgd29yayBoYXMgcmV2b2x1dGlvbml6ZWQgaG93IGFuYWx5c3RzIGFuZCByZXNlYXJjaGVycyBpbnRlcmFjdCB3aXRoIGRhdGEsIGVtcGhhc2l6aW5nIHNpbXBsaWNpdHksIGNvbnNpc3RlbmN5LCBhbmQgdGhlIGNvbmNlcHQgb2YgInRpZHkgZGF0YS4iCgpLZXkgZmVhdHVyZXMgb2YgdGhlIFRpZHl2ZXJzZSBpbmNsdWRlOgoKLSAgICoqVGlkeSBEYXRhIFBhcmFkaWdtOioqIERhdGEgaXMgb3JnYW5pemVkIHNvIHRoYXQgZWFjaCB2YXJpYWJsZSBmb3JtcyBhIGNvbHVtbiwgZWFjaCBvYnNlcnZhdGlvbiBmb3JtcyBhIHJvdywgYW5kIGVhY2ggdHlwZSBvZiBvYnNlcnZhdGlvbiB1bml0IGZvcm1zIGEgdGFibGUuCi0gICAqKkNvbnNpc3RlbmN5OioqIEFsbCBwYWNrYWdlcyBpbiB0aGUgVGlkeXZlcnNlIHVzZSBhIGNvbnNpc3RlbnQgc3ludGF4IGFuZCBncmFtbWFyLCBtYWtpbmcgdGhlbSBpbnR1aXRpdmUgdG8gbGVhcm4gYW5kIHVzZS4KLSAgICoqRWFzZSBvZiBVc2U6KiogVGhlIFRpZHl2ZXJzZSBzaW1wbGlmaWVzIGNvbXBsZXggb3BlcmF0aW9ucywgZW5hYmxpbmcgYW5hbHlzdHMgdG8gZm9jdXMgbW9yZSBvbiBpbnNpZ2h0cyByYXRoZXIgdGhhbiB0aGUgbWVjaGFuaWNzIG9mIGRhdGEgaGFuZGxpbmcuCgpUaGUgVGlkeXZlcnNlIGluY2x1ZGVzIHNldmVyYWwga2V5IHBhY2thZ2VzLCBpbmNsdWRpbmc6CgotICAgKipnZ3Bsb3QyOioqIEZvciBkYXRhIHZpc3VhbGl6YXRpb24uCi0gICAqKmRwbHlyOioqIEZvciBkYXRhIG1hbmlwdWxhdGlvbi4KLSAgICoqdGlkeXI6KiogRm9yIHRpZHlpbmcgYW5kIHJlc2hhcGluZyBkYXRhLgotICAgKipyZWFkcjoqKiBGb3IgcmVhZGluZyBkYXRhIGZpbGVzLgotICAgKip0aWJibGU6KiogRm9yIGltcHJvdmVkIGRhdGEgZnJhbWUgZnVuY3Rpb25hbGl0eS4KLSAgICoqcHVycnI6KiogRm9yIGZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcgd2l0aCBsaXN0cy4KCiMjIyAqKldoYXQgaXMgZHBseXI/KioKCioqZHBseXIqKiBpcyBhIGNvcmUgcGFja2FnZSB3aXRoaW4gdGhlIFRpZHl2ZXJzZSwgc3BlY2lmaWNhbGx5IGZvY3VzZWQgb24gZGF0YSBtYW5pcHVsYXRpb24uIEl0IHByb3ZpZGVzIGEgZ3JhbW1hciBvZiBkYXRhIG1hbmlwdWxhdGlvbiwgbWFraW5nIGl0IGVhc3kgdG8gcGVyZm9ybSB0YXNrcyBzdWNoIGFzIGZpbHRlcmluZywgc29ydGluZywgc3VtbWFyaXppbmcsIGFuZCB0cmFuc2Zvcm1pbmcgZGF0YS4KCktleSBvcGVyYXRpb25zIGluICoqZHBseXIqKiwgb2Z0ZW4gY2FsbGVkIHRoZSAidmVyYnMiIG9mIGRhdGEgbWFuaXB1bGF0aW9uLCBpbmNsdWRlOgoKLSAgICoqYGZpbHRlcigpYCoqOiBTZWxlY3Qgcm93cyBiYXNlZCBvbiBjb25kaXRpb25zLgotICAgKipgc2VsZWN0KClgKio6IENob29zZSBzcGVjaWZpYyBjb2x1bW5zLgotICAgKipgbXV0YXRlKClgKio6IEFkZCBvciBtb2RpZnkgY29sdW1ucy4KLSAgICoqYGFycmFuZ2UoKWAqKjogU29ydCByb3dzLgotICAgKipgc3VtbWFyaXplKClgKio6IENhbGN1bGF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MuCi0gICAqKmBncm91cF9ieSgpYCoqOiBHcm91cCBkYXRhIGZvciBncm91cGVkIG9wZXJhdGlvbnMuCgpUaGVzZSBmdW5jdGlvbnMgYXJlIGRlc2lnbmVkIHRvIGJlIGludHVpdGl2ZSwgcmVhZGFibGUsIGFuZCBjaGFpbmFibGUgdXNpbmcgdGhlIGAlPiVgIG9wZXJhdG9yIChwaXBlKSwgd2hpY2ggYWxsb3dzIHlvdSB0byBleHByZXNzIHNlcXVlbmNlcyBvZiB0cmFuc2Zvcm1hdGlvbnMgaW4gYSBjbGVhciBhbmQgbGluZWFyIGZhc2hpb24uIFRoZXkgcHJlc2VudCBhbiBhbHRlcm5hdGl2ZSB0byB1c2luZyBTUUwgZm9yIGRhdGEgZmlsdGVyaW5nLCBzdW1tYXJpemluZywgYW5kIHNlbGVjdGlvbi4KCiMjIFRoZSAqKmRwbHlyKiogUGFja2FnZQoKKipkcGx5cioqIGlzIHBhcnQgb2YgdGhlIFRpZHl2ZXJzZSwgbWVhbmluZyBpdCBzaGFyZXMgdGhlIHNhbWUgcHJpbmNpcGxlcywgc3ludGF4LCBhbmQgZ29hbHMgYXMgb3RoZXIgVGlkeXZlcnNlIHBhY2thZ2VzLiBJdCBzZXJ2ZXMgYXMgdGhlICoqZGF0YSBtYW5pcHVsYXRpb24gYmFja2JvbmUqKiBmb3IgbWFueSBkYXRhIGFuYWx5c2lzIHdvcmtmbG93cyBpbiB0aGUgVGlkeXZlcnNlLiBJbiB0aGlzIHNlY3Rpb24sIHdlIHdpbGwgZXhwbG9yZSBrZXkgZnVuY3Rpb25zIGluIGRldGFpbC4gVG8gdXNlIHRoZSAqKmRwbHlyKiogcGFja2FnZSwgeW91IG11c3QgaW5zdGFsbCBpdCBhbmQgdGhlbiBsb2FkIGl0OgoKYGBge3Igd2FybmluZz1GQUxTRX0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoZHBseXIpKQpgYGAKClRoZSBjYWxsIHRvIGBzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoKWAgaXMgbmVjZXNzYXJ5IHRvICJzdXBwcmVzcyIgYWxsIHdhcm5pbmdzIGFuZCBtZXNzYWdlczsgaWYgbm90IGRvbmUsIHRob3NlIHdvdWxkIGVuZCB1cCBpbiB5b3VyIGtuaXR0ZWQgbm90ZWJvb2suCgpUaGUgKipkcGx5cioqIHBhY2thZ2UgdXNlcyBhIG1vZGlmaWVkIChhbmQgc2ltcGxpZmllZCkgdGFidWxhciBzdHJ1Y3R1cmUgY2FsbGVkIGEgKnRpYmJsZSogdGhhdCByZXBsYWNlcyBkYXRhZnJhbWVzIGluIFRpZHl2ZXJzZS4KClRvIGxvYWQgYWxsIHBhY2thZ2VzIG9mIHRoZSBUaWR5dmVyc2UsIHlvdSBjYW4gc2ltcGx5IGxvYWQgKip0aWR5dmVyc2UqKi4KCmBgYHtyIHdhcm5pbmc9RkFMU0UsIGV2YWw9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCiMjIyB0aWJibGVzCgoqKlRpYmJsZXMqKiBhcmUgYSBtb2Rlcm5pemVkIHJlLWltYWdpbmF0aW9uIG9mIGRhdGEgZnJhbWVzIGluIFIsIGludHJvZHVjZWQgYnkgdGhlICoqdGliYmxlKiogcGFja2FnZSwgd2hpY2ggaXMgcGFydCBvZiB0aGUgKipUaWR5dmVyc2UqKi4gVGhlIGdvYWwgaXMgdG8gYWRkcmVzcyBzb21lIG9mIHRoZSBsaW1pdGF0aW9ucyBhbmQgZnJ1c3RyYXRpb25zIGFzc29jaWF0ZWQgd2l0aCBiYXNlIFIgZGF0YSBmcmFtZXMgYnkgcHJvdmlkaW5nIGEgbW9yZSAidXNlci1mcmllbmRseSIgYW5kIGNvbnNpc3RlbnQgZXhwZXJpZW5jZSBmb3IgZGF0YSBtYW5pcHVsYXRpb24uCgpUaWJibGVzIHByb3ZpZGUgc29tZSBrZXkgYmVuZWZpdHMgb3ZlciBkYXRhZnJhbWVzLCBpbmNsdWRpbmc6CgoxLiAgKipTaW1wbGlmaWVkIFByaW50aW5nKipcCiAgICBUaWJibGVzIGRpc3BsYXkgb25seSB0aGUgZmlyc3QgMTAgcm93cyBhbmQgYXMgbWFueSBjb2x1bW5zIGFzIGZpdCB0aGUgc2NyZWVuLCBhdm9pZGluZyBvdmVyd2hlbG1pbmcgb3V0cHV0IGZvciBsYXJnZSBkYXRhc2V0cy4gRm9yIGV4YW1wbGU6CgogICAgYGBge3J9CiAgICBsaWJyYXJ5KHRpYmJsZSkKCiAgICB0aWJibGVfZXhhbXBsZSA8LSB0aWJibGUoCiAgICAgIHggPSAxOjEwMCwKICAgICAgeSA9IHJub3JtKDEwMCkKICAgICkKICAgIHRpYmJsZV9leGFtcGxlCiAgICBgYGAKCiAgICBPdXRwdXQgd2lsbCBzaG93OgoKICAgIGBgYCAgICAgICAgIAogICAgIyBBIHRpYmJsZTogMTAwIMOXIDIKICAgICAgICAgIHggICAgICAgeQogICAgICA8aW50PiAgIDxkYmw+CiAgICAxICAgICAxICAtMC4yNzYKICAgIDIgICAgIDIgICAxLjAwMwogICAgMyAgICAgMyAgIDAuNzM1CiAgICAjIOKApiB3aXRoIDk3IG1vcmUgcm93cwogICAgYGBgCgoyLiAgKipQcmVzZXJ2YXRpb24gb2YgRGF0YSBUeXBlcyoqXAogICAgVW5saWtlIGJhc2UgUiBkYXRhIGZyYW1lcywgdGliYmxlcyBuZXZlciBhdXRvbWF0aWNhbGx5IGNvbnZlcnQgc3RyaW5ncyBpbnRvIGZhY3RvcnMgb3IgcGVyZm9ybSBvdGhlciB1bmV4cGVjdGVkIHR5cGUgY29lcmNpb24uIEZvciBleGFtcGxlOgoKICAgIGBgYHtyfQogICAgZGYgPC0gZGF0YS5mcmFtZShuYW1lID0gIkFsaWNlIiwgYWdlID0gMjUpCiAgICBjbGFzcyhkZiRuYW1lKSAgIyBPdXRwdXQ6IGZhY3RvcgoKICAgIHRiIDwtIHRpYmJsZShuYW1lID0gIkFsaWNlIiwgYWdlID0gMjUpCiAgICBjbGFzcyh0YiRuYW1lKSAgIyBPdXRwdXQ6IGNoYXJhY3RlcgogICAgYGBgCgozLiAgKipDb2x1bW4gQWNjZXNzIGJ5IE5hbWUqKlwKICAgIFRpYmJsZXMgZW5mb3JjZSBzdHJpY3RlciBydWxlcyBmb3IgY29sdW1uIGFjY2VzczoKCiAgICAtICAgQWNjZXNzaW5nIGNvbHVtbnMgd2l0aCBgJGAgb3IgYFtbYCByZXF1aXJlcyBleGFjdCBtYXRjaGVzIHRvIGNvbHVtbiBuYW1lcy4KICAgIC0gICBQYXJ0aWFsIG1hdGNoaW5nIG9mIG5hbWVzIGlzIGRpc2FsbG93ZWQsIHJlZHVjaW5nIGVycm9ycy4KCiAgICBgYGB7cn0KICAgIHRiIDwtIHRpYmJsZShuYW1lID0gIkFsaWNlIiwgYWdlID0gMjUpCiAgICB0YiRuICAgICAgIyBFcnJvcjogVW5rbm93biBjb2x1bW4gJ24nCiAgICBgYGAKCjQuICAqKlN1cHBvcnQgZm9yIE5vbi1TeW50YWN0aWMgTmFtZXMqKlwKICAgIFRpYmJsZXMgYWxsb3cgY29sdW1uIG5hbWVzIHRoYXQgYXJlbid0IHN5bnRhY3RpY2FsbHkgdmFsaWQgaW4gUiwgc3VjaCBhcyBuYW1lcyBjb250YWluaW5nIHNwYWNlcyBvciBzcGVjaWFsIGNoYXJhY3RlcnMuIFRoZXNlIG11c3QgYmUgcmVmZXJlbmNlZCB1c2luZyBiYWNrdGlja3M6CgogICAgYGBge3J9CiAgICB0YiA8LSB0aWJibGUoYHN0dWRlbnQgbmFtZWAgPSAiQWxpY2UiLCBhZ2UgPSAyNSkKICAgIHRiJGBzdHVkZW50IG5hbWVgICAjIEFjY2VzcyBjb2x1bW4KICAgIGBgYAoKNS4gICoqQ3JlYXRpb24gRmxleGliaWxpdHkqKlwKICAgIFRpYmJsZXMgY2FuIGJlIGNyZWF0ZWQgdXNpbmcgdGhlIGB0aWJibGUoKWAgZnVuY3Rpb24gb3IgdGhlIGB0cmliYmxlKClgIGZ1bmN0aW9uIGZvciByb3ctd2lzZSBkYXRhIGVudHJ5LiBGb3IgZXhhbXBsZToKCiAgICBgYGB7cn0KICAgICMgVXNpbmcgdGliYmxlKCkKICAgIHRiIDwtIHRpYmJsZShuYW1lID0gYygiQWxpY2UiLCAiQm9iIiksIGFnZSA9IGMoMjUsIDMwKSkKCiAgICAjIFVzaW5nIHRyaWJibGUoKQogICAgdGIgPC0gdHJpYmJsZSgKICAgICAgfm5hbWUsIH5hZ2UsCiAgICAgICJBbGljZSIsIDI1LAogICAgICAiQm9iIiwgMzAKICAgICkKICAgIGBgYAoKVGliYmxlcyBhcmUgZXNwZWNpYWxseSB1c2VmdWwgd2hlbiB3b3JraW5nIHdpdGggdGhlIFRpZHl2ZXJzZSBiZWNhdXNlIGFsbCBUaWR5dmVyc2UgcGFja2FnZXMgYXJlIGRlc2lnbmVkIHRvIHdvcmsgb3B0aW1hbGx5IHdpdGggdGliYmxlcy4gVGhleSBhcmUgcmVjb21tZW5kZWQgd2hlbmV2ZXIgeW91IHdhbnQgYSBtb3JlIHByZWRpY3RhYmxlIGFuZCBjb25zaXN0ZW50IGRhdGEgc3RydWN0dXJlIGZvciBkYXRhIG1hbmlwdWxhdGlvbiBhbmQgYW5hbHlzaXMuCgpBbGwgb2YgdGhlIGZ1bmN0aW9ucyB0aGF0IGV4cGVjdCBhIGRhdGFmcmFtZSBhbHNvIHdvcmsgd2l0aCB0aWJibGVzLgoKIyMjIFJlYWRpbmcgQ1NWIEZpbGVzCgpUbyBsb2FkIGEgQ1NWIGZpbGUgaW50byBhIHRpYmJsZSByYXRoZXIgdGhhbiBhIGRhdGFmcmFtZSwgdXNlIGByZWFkX2NzdigpYCBpbnN0ZWFkIG9mIGByZWFkLmNzdigpYC4gVXNpbmcgYHJlYWRfY3N2KClgIG92ZXIgQmFzZSBSJ3MgYHJlYWQuY3N2KClgIGhhcyBzZXZlcmFsIGJlbmVmaXRzIChhc2lkZSBmcm9tIHJldHVybmluZyBhIHRpYmJsZSkuCgoxLiAgKipCZXR0ZXIgRGVmYXVsdHMqKjoKICAgIC0gICBTdHJpbmdzIGFyZSBub3QgYXV0b21hdGljYWxseSBjb252ZXJ0ZWQgdG8gZmFjdG9ycy4KICAgIC0gICBDb2x1bW4gdHlwZXMgYXJlIGF1dG9tYXRpY2FsbHkgZ3Vlc3NlZCBhbmQgbW9yZSBhY2N1cmF0ZWx5IGhhbmRsZWQuCjIuICAqKkZhc3RlciBQZXJmb3JtYW5jZSoqOgogICAgLSAgIGByZWFkX2NzdigpYCBpcyAoYSBiaXQpIGZhc3RlciBmb3IgbGFyZ2UgZmlsZXMuCjMuICAqKlRpZHl2ZXJzZSBJbnRlZ3JhdGlvbioqOgogICAgLSAgIFJldHVybnMgYSB0aWJibGUsIHdoaWNoIGludGVncmF0ZXMgc2VhbWxlc3NseSB3aXRoIG90aGVyIFRpZHl2ZXJzZSBwYWNrYWdlcy4KNC4gICoqSW5mb3JtYXRpdmUgT3V0cHV0Kio6CiAgICAtICAgUHJpbnRzIG1ldGFkYXRhIGFib3V0IHRoZSBkYXRhIGR1cmluZyBpbXBvcnQgKCplLmcuKiwgY29sdW1uIHR5cGVzKS4KClRoZSBmdW5jdGlvbiBgcmVhZF9jc3YoKWAgaXMgcGFydCBvZiB0aGUgKipyZWFkcioqIHBhY2thZ2UsIHNvIGVpdGhlciBpbnN0YWxsIGFuZCBsb2FkIHRoYXQgcGFja2FnZSBvZiB0aGUgZnVsbCAqKnRpZHl2ZXJzZSoqIHBhY2thZ2Ugc2V0LgoKQ29uc2lkZXIgYSBkYXRhc2V0IGBzdHVkZW50c2AgY29udGFpbmluZyBpbmZvcm1hdGlvbiBvbiBzdHVkZW50cycgbmFtZXMsIG1ham9ycywgYW5kIEdQQSdzIChmcm9tIHRoZSBDU1YgZmlsZSBbc3R1ZGVudHMuY3N2XShzdHVkZW50cy5jc3YpKToKCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpzdHVkZW50cyA8LSByZWFkX2Nzdigic3R1ZGVudHMuY3N2IikKYGBgCgpUaGUgZnVuY3Rpb24gYHJlYWRfY3N2KClgIGRpc3BsYXlzIGluZm9ybWF0aW9uIGFib3V0IHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIENTVi4gSWYgeW91IHdpc2ggdG8gInN1cHByZXNzIiB0aGlzIGluZm9ybWF0aW9uLCB0aGVuIHVzZToKCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpzdHVkZW50cyA8LSByZWFkX2Nzdigic3R1ZGVudHMuY3N2Iiwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkKYGBgCgojIyMgRmlsdGVyaW5nIFJvd3Mgd2l0aCBgZmlsdGVyKClgCgpUaGUgYGZpbHRlcigpYCBmdW5jdGlvbiBleHRyYWN0cyByb3dzIGZyb20gYSBkYXRhIGZyYW1lIHRoYXQgbWVldCBzcGVjaWZpZWQgbG9naWNhbCBjb25kaXRpb25zLiBUbyBzZWxlY3Qgc3R1ZGVudHMgd2l0aCBhIEdQQSBvZiAzLjUgb3IgYmV0dGVyLCB5b3UgY2FuIHVzZSB0aGUgZm9sbG93aW5nICoqZHBseXIqKiBwaXBlOgoKYGBge3J9CmZpbHRlcmVkX3N0dWRlbnRzIDwtIHN0dWRlbnRzICU+JQogIGZpbHRlcihncGEgPj0gMy41MCkKYGBgCgpUaGlzIHJldHVybnMgcm93cyB3aGVyZSB0aGUgYGdwYWAgY29sdW1uIGlzIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0byAzLjUwLiBUbyBkaXNwbGF5IHRoZSByZXN1bHQsIHlvdSBjYW4gdXNlIHRoZSBgcHJpbnQoKWAgZnVuY3Rpb246CgpgYGB7cn0KcHJpbnQoZmlsdGVyZWRfc3R1ZGVudHMpCmBgYAoKIyMjIEFycmFuZ2luZyBSb3dzIHdpdGggYGFycmFuZ2UoKWAKClRoZSBgYXJyYW5nZSgpYCBmdW5jdGlvbiBvcmRlcnMgcm93cyBiYXNlZCBvbiB0aGUgdmFsdWVzIG9mIHNwZWNpZmllZCBjb2x1bW5zLiBUbyBzb3J0IHRoZSBgc3R1ZGVudHNgIGRhdGFzZXQgYnkgYGdwYWAgaW4gZGVzY2VuZGluZyBvcmRlcjoKCmBgYHtyfQphcnJhbmdlZF9zdHVkZW50cyA8LSBzdHVkZW50cyAlPiUKICBhcnJhbmdlKGRlc2MoZ3BhKSkKCnByaW50KGFycmFuZ2VkX3N0dWRlbnRzKQpgYGAKClRoaXMgc29ydHMgdGhlIHN0dWRlbnRzIGZyb20gdGhlIGhpZ2hlc3QgdG8gdGhlIGxvd2VzdCBncmFkZS4KCiMjIyBTZWxlY3RpbmcgQ29sdW1ucyB3aXRoIGBzZWxlY3QoKWAKClRoZSBgc2VsZWN0KClgIGZ1bmN0aW9uIGFsbG93cyBmb3IgdGhlIGV4dHJhY3Rpb24gb2Ygc3BlY2lmaWMgY29sdW1ucyBmcm9tIGEgZGF0YSBmcmFtZS4gVG8gY3JlYXRlIGEgbmV3IGRhdGFzZXQgd2l0aCBvbmx5IHRoZSBgbmFtZWAgYW5kIGBncGFgIGNvbHVtbnM6CgpgYGB7cn0Kc2VsZWN0ZWRfc3R1ZGVudHMgPC0gc3R1ZGVudHMgJT4lCiAgc2VsZWN0KG5hbWUsIGdwYSkKCnByaW50KHNlbGVjdGVkX3N0dWRlbnRzKQpgYGAKClRoaXMgcmVzdWx0cyBpbiBhIGRhdGFzZXQgY29udGFpbmluZyBvbmx5IHRoZSBgbmFtZWAgYW5kIGBncGFgIGNvbHVtbnMuIEZyb20gYSByZWxhdGlvbmFsIHBlcnNwZWN0aXZlLCB0aGlzIGlzIGEgInByb2plY3Rpb24iIG9wZXJhdGlvbi4KCiMjIyBBZGRpbmcgb3IgTW9kaWZ5aW5nIENvbHVtbnMgd2l0aCBgbXV0YXRlKClgCgpUaGUgYG11dGF0ZSgpYCBmdW5jdGlvbiBpcyB1c2VkIHRvIGFkZCBuZXcgY29sdW1ucyBvciBtb2RpZnkgZXhpc3Rpbmcgb25lcy4gVG8gYWRkIGEgY29sdW1uIGBob25vcnNgIHRoYXQgY2F0ZWdvcml6ZXMgc3R1ZGVudHMgYmFzZWQgb24gdGhlaXIgR1BBOgoKYGBge3J9Cm11dGF0ZWRfc3R1ZGVudHMgPC0gc3R1ZGVudHMgJT4lCiAgbXV0YXRlKGdyYWRlX2NhdGVnb3J5ID0gY2FzZV93aGVuKAogICAgZ3BhID49IDMuOSB+ICJFeGNlbGxlbnQiLAogICAgZ3BhID49IDMuNSB+ICJHb29kIiwKICAgIGdwYSA+PSAzLjAgfiAiUGFzc2luZyIsCiAgICBUUlVFIH4gIkJlbG93IFBhc3NpbmciCiAgKSkKCnByaW50KG11dGF0ZWRfc3R1ZGVudHMpCmBgYAoKVGhpcyBhZGRzIGEgYGhvbm9yc2AgY29sdW1uIHdpdGggdmFsdWVzIGZvciB2YXJpb3VzIHJhbmdlcyBvZiBHUEEuCgojIyMgU3VtbWFyaXppbmcgRGF0YSB3aXRoIGBzdW1tYXJpemUoKWAKClRoZSBgc3VtbWFyaXplKClgIGZ1bmN0aW9uIGNvbXB1dGVzIHN1bW1hcnkgc3RhdGlzdGljcyBmb3IgYSBkYXRhIGZyYW1lLiBGb3IgZXhhbXBsZSwgdG8gY2FsY3VsYXRlIHRoZSBtZWFuIChhdmVyYWdlKSBHUEEgb2YgdGhlIHN0dWRlbnRzOgoKYGBge3J9CmF2ZXJhZ2VfZ3BhIDwtIHN0dWRlbnRzICU+JQogIHN1bW1hcml6ZShtZWFuX2dwYSA9IG1lYW4oZ3BhKSkKCnByaW50KGF2ZXJhZ2VfZ3BhKQpgYGAKClRoaXMgY29tcHV0ZXMgdGhlIG1lYW4gb2YgdGhlIGBncGFgIGNvbHVtbiwgcHJvdmlkaW5nIHRoZSBhdmVyYWdlIEdQQQoKIyMjIEdyb3VwZWQgT3BlcmF0aW9ucyB3aXRoIGBncm91cF9ieSgpYCBhbmQgYHN1bW1hcml6ZSgpYAoKRm9yIG1vcmUgY29tcGxleCBhbmFseXNlcywgYGdyb3VwX2J5KClgIGluIGNvbmp1bmN0aW9uIHdpdGggYHN1bW1hcml6ZSgpYCBlbmFibGVzIGdyb3VwZWQgb3BlcmF0aW9ucy4gVG8gY2FsY3VsYXRlIHRoZSBhdmVyYWdlIEdQQSBieSBtYWpvcjoKCmBgYHtyfQphdmdfYnlfbWFqb3IgPC0gc3R1ZGVudHMgJT4lCiAgZ3JvdXBfYnkobWFqb3IpICU+JQogIHN1bW1hcml6ZShhdmdfZ3BhID0gbWVhbihncGEpKQoKcHJpbnQoYXZnX2J5X21ham9yKQpgYGAKCldoZW5ldmVyIHRoZXJlJ3MgYSAiYnkiIG9yICJwZXIiIGl0IG1lYW5zIHRoYXQgd2Ugd2FudCB0byBjb21wdXRlciBzb21lICJmYWN0IiBmb3IgZ3JvdXBlZCBkYXRhLgoKVGhlc2UgZXhhbXBsZXMgZGVtb25zdHJhdGUgdGhlIHZlcnNhdGlsaXR5IG9mIGBkcGx5cmAgaW4gcGVyZm9ybWluZyBkYXRhIHRyYW5zZm9ybWF0aW9ucywgZmFjaWxpdGF0aW5nIGVmZmljaWVudCBkYXRhIG1hbmlwdWxhdGlvbiBmb3IgY29tcGxleCBhbmFseXNlcyB1c2luZyBhbiBhbHRlcm5hdGl2ZSB0byBTUUwuCgojIyBTdW1tYXJ5CgpUaGUgKipUaWR5dmVyc2UqKiBhbmQgKipkcGx5cioqIGFyZSBpbnRlZ3JhbCBwYXJ0cyBvZiB0aGUgbW9kZXJuIGRhdGEgYW5hbHlzaXMgZWNvc3lzdGVtIGluIFIsIGRlc2lnbmVkIHRvIHNpbXBsaWZ5IGFuZCBzdHJlYW1saW5lIGRhdGEgbWFuaXB1bGF0aW9uIGFuZCB2aXN1YWxpemF0aW9uIHRhc2tzLgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBGaWxlcyAmIFJlc291cmNlcwoKYGBge3IgemlwRmlsZXMsIGVjaG89RkFMU0V9CnppcE5hbWUgPSBzcHJpbnRmKCJMZXNzb25GaWxlcy0lcy0lcy56aXAiLCAKICAgICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksCiAgICAgICAgICAgICAgICAgcGFyYW1zJG51bWJlcikKCnRleHRBTGluayA9IHBhc3RlMCgiQWxsIEZpbGVzIGZvciBMZXNzb24gIiwgCiAgICAgICAgICAgICAgIHBhcmFtcyRjYXRlZ29yeSwiLiIscGFyYW1zJG51bWJlcikKCiMgZG93bmxvYWRGaWxlc0xpbmsoKSBpcyBpbmNsdWRlZCBmcm9tIF9pbnNlcnQyREIuUgprbml0cjo6cmF3X2h0bWwoZG93bmxvYWRGaWxlc0xpbmsoIi4iLCB6aXBOYW1lLCB0ZXh0QUxpbmspKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgUmVmZXJlbmNlcwoKMS4gIFdpY2toYW0sIEguLCAmIEdyb2xlbXVuZCwgRy4gKDIwMTcpLiAqUiBmb3IgRGF0YSBTY2llbmNlOiBJbXBvcnQsIHRpZHksIHRyYW5zZm9ybSwgdmlzdWFsaXplLCBhbmQgbW9kZWwgZGF0YSouIE8nUmVpbGx5IE1lZGlhLiBSZXRyaWV2ZWQgZnJvbSA8aHR0cHM6Ly9yNGRzLmhhZGxleS5uej4KCjIuICBXaWNraGFtLCBILiAoMjAxNCkuIFRpZHkgZGF0YS4gKkpvdXJuYWwgb2YgU3RhdGlzdGljYWwgU29mdHdhcmUsIDU5KigxMCksIDHigJMyMy4gPGh0dHBzOi8vZG9pLm9yZy8xMC4xODYzNy9qc3MudjA1OS5pMTA+CgpQb3J0aW9ucyBvZiB0aGlzIGxlc3NvbiB3ZXJlIGNyZWF0ZWQgd2l0aCB0aGUgYXNzaXN0YW5jZSBvZiBDaGF0R1BUIDRvLgoKIyMgRXJyYXRhCgpbTGV0IHVzIGtub3ddKGh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS8yMTIxODcwNzI3ODQxNTcpe3RhcmdldD0iX2JsYW5rIn0uCg==