Purpose: A living notebook of short, reliable R snippets I actually use. Keep it short. Keep it runnable. Update as I learn.

1. Quick Setup

# Install-once (uncomment as needed)
# install.packages(c("tidyverse", "janitor", "lubridate", "readr", "readxl", "openxlsx",
#                    "skimr", "here", "fs", "glue", "knitr", "rmarkdown", "ggthemes",
#                    "patchwork", "gt", "gtExtras", "stringr", "forcats"))

# Load every session
suppressPackageStartupMessages({
  library(tidyverse)
  library(janitor)
  library(lubridate)
  library(here)
  library(glue)
  library(skimr)
  library(gt)
})

# Reproducibility
set.seed(42)

Project tip: Use an RStudio Project and here::here() for paths. Never hard‑code "C:/Users/...".

2. Reading & Writing Data (fast + safe)

# CSV (robust defaults)
df_csv <- readr::read_csv(here("data", "my_data.csv"))

# Excel (first sheet)
df_xlsx <- readxl::read_excel(here("data", "my_data.xlsx"), sheet = 1)

# Write outputs with timestamps
out_path <- here("output", glue("cleaned_{format(Sys.Date(), '%Y%m%d')}.csv"))
# readr::write_csv(df_csv, out_path)

Gotcha: If you see encoding issues, try locale = locale(encoding = "UTF-8") in read_csv().

3. Inspecting Data (what is this?)

# High‑level skim
skimr::skim(df_csv)

# Structure & types
str(df_csv)

# Column names (clean + check)
names(df_csv)
janitor::compare_df_cols(df_csv)

Rule of thumb: If a column should be a date, convert it immediately with lubridate.

4. Cleaning Columns & Rows

# Consistent names
clean <- df_csv %>%
  janitor::clean_names() %>%                 # snake_case column names
  mutate(across(where(is.character), trimws)) # trim leading/trailing spaces

# Remove complete duplicate rows
clean <- distinct(clean)

# Handle blanks as NA
clean <- mutate(clean, across(everything(), ~na_if(.x, "")))

Tip: Use distinct(.keep_all = TRUE) to de‑dupe by subset of columns.

5. dplyr Cheatsheet (minimal set)

result <- clean %>%
  filter(!is.na(id)) %>%
  mutate(
    date = lubridate::ymd(date),
    category = forcats::fct_lump_n(as.factor(category), n = 5)
  ) %>%
  group_by(category) %>%
  summarize(
    n = n(),
    mean_val = mean(value, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(desc(n))

Mnemonic: Select–Filter–Mutate–Summarize–Arrange covers 80% of wrangling.

6. Joins (I always mix these up)

# left_join: keep all rows from x, bring matches from y
joined <- df_csv %>% left_join(df_xlsx, by = "id")

# anti_join: rows in x with no match in y (great for QA)
missing_keys <- df_csv %>% anti_join(df_xlsx, by = "id")

QA trick: anti_join() first to see what won’t match before any heavy processing.

7. Dates & Times (lubridate)

# Parse and standardize
clean_dates <- clean %>%
  mutate(
    date = ymd(date),
    year = year(date),
    month = month(date, label = TRUE, abbr = TRUE),
    wk = isoweek(date)
  )

Tip: If parsing fails, inspect with parse_date_time(x, orders = c("ymd", "mdy", "dmy")).

8. Strings (stringr)

text_clean <- clean %>%
  mutate(
    email = str_to_lower(email),
    domain = str_extract(email, "@.+$")
  )

Regex sanity: Test patterns at https://regex101.com/ before committing.

9. Factors (forcats)

fac <- clean %>%
  mutate(
    status = fct_relevel(as.factor(status), c("new", "active", "inactive")),
    top_cat = fct_lump_n(as.factor(category), n = 6)
  )

Plotting tip: Relevel factors to control ggplot ordering.

10. Plotting (ggplot2: minimal patterns)

# Bar (counts)
clean %>%
  ggplot(aes(x = category)) +
  geom_bar(fill = "#2E86AB") +
  theme_minimal(base_size = 12) +
  labs(title = "Counts by Category", x = NULL, y = "Count")

# Line (time series)
clean_dates %>%
  group_by(date) %>% summarize(n = n(), .groups = "drop") %>%
  ggplot(aes(date, n)) +
  geom_line(color = "#7D3C98", linewidth = 0.9) +
  theme_minimal(base_size = 12) +
  labs(title = "Daily Counts", x = NULL, y = NULL)

Small multiples: Use + facet_wrap(~group) when categories are many.

11. Tables (gt quick pattern)

result %>%
  gt::gt() %>%
  gt::fmt_number(columns = where(is.numeric), decimals = 2) %>%
  gt::tab_header(title = md("**Summary by Category**"))

Export: gtsave("table.png") or gt::gtsave() to PNG/PDF/HTML.

12. Modeling (tidymodels tiny starter)

# install.packages("tidymodels")  # once
# library(tidymodels)
# set.seed(42)
# split <- initial_split(clean, prop = 0.8)
# train <- training(split); test <- testing(split)
# rec <- recipe(target ~ ., data = train) %>% step_dummy(all_nominal(), -all_outcomes())
# mod <- linear_reg() %>% set_engine("lm")
# wf  <- workflow() %>% add_model(mod) %>% add_recipe(rec)
# fit <- fit(wf, data = train)
# metrics <- predict(fit, test) %>% bind_cols(test) %>% metrics(truth = target, estimate = .pred)

Reality check: Always baseline with a simple model (e.g., lm) before anything fancy.

13. Debugging & Safety Nets

stopifnot(!anyDuplicated(clean$id))  # ids should be unique

14. Reproducible Paths & Projects

sessionInfo()

15. Handy Snippets I Reuse

# Percent of total
percent_of_total <- function(x) round(100 * x / sum(x, na.rm = TRUE), 1)

# Not-in operator
`%nin%` <- function(x, y) !(x %in% y)

# Quietly run an expression
quietly <- purrr::quietly

16. Checklist Before You Ship

17. Appendix: swirl (learn by doing)

18. Appendix: Keyboard Macros (RStudio)

19. To Do / Parking Lot

LS0tCnRpdGxlOiAiTWVsYW5pZeKAmXMgUiBHcmFi4oCRQmFnOiBVc2VmdWwgQ29kZSAmIFRpcHMgZm9yIEZ1dHVyZSBNZSIKYXV0aG9yOiAiTWVsYW5pZSBIb2xkZW4iCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVCICVkLCAlWScpYCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKPiAqKlB1cnBvc2U6KiogQSBsaXZpbmcgbm90ZWJvb2sgb2Ygc2hvcnQsIHJlbGlhYmxlIFIgc25pcHBldHMgSSBhY3R1YWxseSB1c2UuIEtlZXAgaXQgc2hvcnQuIEtlZXAgaXQgcnVubmFibGUuIFVwZGF0ZSBhcyBJIGxlYXJuLgoKIyAxLiBRdWljayBTZXR1cAoKYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgSW5zdGFsbC1vbmNlICh1bmNvbW1lbnQgYXMgbmVlZGVkKQojIGluc3RhbGwucGFja2FnZXMoYygidGlkeXZlcnNlIiwgImphbml0b3IiLCAibHVicmlkYXRlIiwgInJlYWRyIiwgInJlYWR4bCIsICJvcGVueGxzeCIsCiMgICAgICAgICAgICAgICAgICAgICJza2ltciIsICJoZXJlIiwgImZzIiwgImdsdWUiLCAia25pdHIiLCAicm1hcmtkb3duIiwgImdndGhlbWVzIiwKIyAgICAgICAgICAgICAgICAgICAgInBhdGNod29yayIsICJndCIsICJndEV4dHJhcyIsICJzdHJpbmdyIiwgImZvcmNhdHMiKSkKCiMgTG9hZCBldmVyeSBzZXNzaW9uCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeSh0aWR5dmVyc2UpCiAgbGlicmFyeShqYW5pdG9yKQogIGxpYnJhcnkobHVicmlkYXRlKQogIGxpYnJhcnkoaGVyZSkKICBsaWJyYXJ5KGdsdWUpCiAgbGlicmFyeShza2ltcikKICBsaWJyYXJ5KGd0KQp9KQoKIyBSZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoNDIpCmBgYAoKKipQcm9qZWN0IHRpcDoqKiBVc2UgYW4gUlN0dWRpbyBQcm9qZWN0IGFuZCBgaGVyZTo6aGVyZSgpYCBmb3IgcGF0aHMuIE5ldmVyIGhhcmTigJFjb2RlIGAiQzovVXNlcnMvLi4uImAuCgojIDIuIFJlYWRpbmcgJiBXcml0aW5nIERhdGEgKGZhc3QgKyBzYWZlKQoKYGBge3IgaW99CiMgQ1NWIChyb2J1c3QgZGVmYXVsdHMpCmRmX2NzdiA8LSByZWFkcjo6cmVhZF9jc3YoaGVyZSgiZGF0YSIsICJteV9kYXRhLmNzdiIpKQoKIyBFeGNlbCAoZmlyc3Qgc2hlZXQpCmRmX3hsc3ggPC0gcmVhZHhsOjpyZWFkX2V4Y2VsKGhlcmUoImRhdGEiLCAibXlfZGF0YS54bHN4IiksIHNoZWV0ID0gMSkKCiMgV3JpdGUgb3V0cHV0cyB3aXRoIHRpbWVzdGFtcHMKb3V0X3BhdGggPC0gaGVyZSgib3V0cHV0IiwgZ2x1ZSgiY2xlYW5lZF97Zm9ybWF0KFN5cy5EYXRlKCksICclWSVtJWQnKX0uY3N2IikpCiMgcmVhZHI6OndyaXRlX2NzdihkZl9jc3YsIG91dF9wYXRoKQpgYGAKCioqR290Y2hhOioqIElmIHlvdSBzZWUgZW5jb2RpbmcgaXNzdWVzLCB0cnkgYGxvY2FsZSA9IGxvY2FsZShlbmNvZGluZyA9ICJVVEYtOCIpYCBpbiBgcmVhZF9jc3YoKWAuCgojIDMuIEluc3BlY3RpbmcgRGF0YSAod2hhdCBpcyB0aGlzPykKCmBgYHtyIGluc3BlY3R9CiMgSGlnaOKAkWxldmVsIHNraW0Kc2tpbXI6OnNraW0oZGZfY3N2KQoKIyBTdHJ1Y3R1cmUgJiB0eXBlcwpzdHIoZGZfY3N2KQoKIyBDb2x1bW4gbmFtZXMgKGNsZWFuICsgY2hlY2spCm5hbWVzKGRmX2NzdikKamFuaXRvcjo6Y29tcGFyZV9kZl9jb2xzKGRmX2NzdikKYGBgCgoqKlJ1bGUgb2YgdGh1bWI6KiogSWYgYSBjb2x1bW4gc2hvdWxkIGJlIGEgZGF0ZSwgY29udmVydCBpdCAqaW1tZWRpYXRlbHkqIHdpdGggYGx1YnJpZGF0ZWAuCgojIDQuIENsZWFuaW5nIENvbHVtbnMgJiBSb3dzCgpgYGB7ciBjbGVhbmluZ30KIyBDb25zaXN0ZW50IG5hbWVzCmNsZWFuIDwtIGRmX2NzdiAlPiUKICBqYW5pdG9yOjpjbGVhbl9uYW1lcygpICU+JSAgICAgICAgICAgICAgICAgIyBzbmFrZV9jYXNlIGNvbHVtbiBuYW1lcwogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMuY2hhcmFjdGVyKSwgdHJpbXdzKSkgIyB0cmltIGxlYWRpbmcvdHJhaWxpbmcgc3BhY2VzCgojIFJlbW92ZSBjb21wbGV0ZSBkdXBsaWNhdGUgcm93cwpjbGVhbiA8LSBkaXN0aW5jdChjbGVhbikKCiMgSGFuZGxlIGJsYW5rcyBhcyBOQQpjbGVhbiA8LSBtdXRhdGUoY2xlYW4sIGFjcm9zcyhldmVyeXRoaW5nKCksIH5uYV9pZigueCwgIiIpKSkKYGBgCgoqKlRpcDoqKiBVc2UgYGRpc3RpbmN0KC5rZWVwX2FsbCA9IFRSVUUpYCB0byBkZeKAkWR1cGUgYnkgc3Vic2V0IG9mIGNvbHVtbnMuCgojIDUuIGRwbHlyIENoZWF0c2hlZXQgKG1pbmltYWwgc2V0KQoKYGBge3IgZHBseXJ9CnJlc3VsdCA8LSBjbGVhbiAlPiUKICBmaWx0ZXIoIWlzLm5hKGlkKSkgJT4lCiAgbXV0YXRlKAogICAgZGF0ZSA9IGx1YnJpZGF0ZTo6eW1kKGRhdGUpLAogICAgY2F0ZWdvcnkgPSBmb3JjYXRzOjpmY3RfbHVtcF9uKGFzLmZhY3RvcihjYXRlZ29yeSksIG4gPSA1KQogICkgJT4lCiAgZ3JvdXBfYnkoY2F0ZWdvcnkpICU+JQogIHN1bW1hcml6ZSgKICAgIG4gPSBuKCksCiAgICBtZWFuX3ZhbCA9IG1lYW4odmFsdWUsIG5hLnJtID0gVFJVRSksCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKSAlPiUKICBhcnJhbmdlKGRlc2MobikpCmBgYAoKKipNbmVtb25pYzoqKiAqU2VsZWN04oCTRmlsdGVy4oCTTXV0YXRl4oCTU3VtbWFyaXpl4oCTQXJyYW5nZSogY292ZXJzIDgwJSBvZiB3cmFuZ2xpbmcuCgojIDYuIEpvaW5zIChJIGFsd2F5cyBtaXggdGhlc2UgdXApCgpgYGB7ciBqb2luc30KIyBsZWZ0X2pvaW46IGtlZXAgYWxsIHJvd3MgZnJvbSB4LCBicmluZyBtYXRjaGVzIGZyb20geQpqb2luZWQgPC0gZGZfY3N2ICU+JSBsZWZ0X2pvaW4oZGZfeGxzeCwgYnkgPSAiaWQiKQoKIyBhbnRpX2pvaW46IHJvd3MgaW4geCB3aXRoIG5vIG1hdGNoIGluIHkgKGdyZWF0IGZvciBRQSkKbWlzc2luZ19rZXlzIDwtIGRmX2NzdiAlPiUgYW50aV9qb2luKGRmX3hsc3gsIGJ5ID0gImlkIikKYGBgCgoqKlFBIHRyaWNrOioqIGBhbnRpX2pvaW4oKWAgZmlyc3QgdG8gc2VlIHdoYXQgd29u4oCZdCBtYXRjaCBiZWZvcmUgYW55IGhlYXZ5IHByb2Nlc3NpbmcuCgojIDcuIERhdGVzICYgVGltZXMgKGx1YnJpZGF0ZSkKCmBgYHtyIGRhdGVzfQojIFBhcnNlIGFuZCBzdGFuZGFyZGl6ZQpjbGVhbl9kYXRlcyA8LSBjbGVhbiAlPiUKICBtdXRhdGUoCiAgICBkYXRlID0geW1kKGRhdGUpLAogICAgeWVhciA9IHllYXIoZGF0ZSksCiAgICBtb250aCA9IG1vbnRoKGRhdGUsIGxhYmVsID0gVFJVRSwgYWJiciA9IFRSVUUpLAogICAgd2sgPSBpc293ZWVrKGRhdGUpCiAgKQpgYGAKCioqVGlwOioqIElmIHBhcnNpbmcgZmFpbHMsIGluc3BlY3Qgd2l0aCBgcGFyc2VfZGF0ZV90aW1lKHgsIG9yZGVycyA9IGMoInltZCIsICJtZHkiLCAiZG15IikpYC4KCiMgOC4gU3RyaW5ncyAoc3RyaW5ncikKCmBgYHtyIHN0cmluZ3N9CnRleHRfY2xlYW4gPC0gY2xlYW4gJT4lCiAgbXV0YXRlKAogICAgZW1haWwgPSBzdHJfdG9fbG93ZXIoZW1haWwpLAogICAgZG9tYWluID0gc3RyX2V4dHJhY3QoZW1haWwsICJALiskIikKICApCmBgYAoKKipSZWdleCBzYW5pdHk6KiogVGVzdCBwYXR0ZXJucyBhdCA8aHR0cHM6Ly9yZWdleDEwMS5jb20vPiBiZWZvcmUgY29tbWl0dGluZy4KCiMgOS4gRmFjdG9ycyAoZm9yY2F0cykKCmBgYHtyIGZhY3RvcnN9CmZhYyA8LSBjbGVhbiAlPiUKICBtdXRhdGUoCiAgICBzdGF0dXMgPSBmY3RfcmVsZXZlbChhcy5mYWN0b3Ioc3RhdHVzKSwgYygibmV3IiwgImFjdGl2ZSIsICJpbmFjdGl2ZSIpKSwKICAgIHRvcF9jYXQgPSBmY3RfbHVtcF9uKGFzLmZhY3RvcihjYXRlZ29yeSksIG4gPSA2KQogICkKYGBgCgoqKlBsb3R0aW5nIHRpcDoqKiBSZWxldmVsIGZhY3RvcnMgdG8gY29udHJvbCBnZ3Bsb3Qgb3JkZXJpbmcuCgojIDEwLiBQbG90dGluZyAoZ2dwbG90MjogbWluaW1hbCBwYXR0ZXJucykKCmBgYHtyIHBsb3RzLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00fQojIEJhciAoY291bnRzKQpjbGVhbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBjYXRlZ29yeSkpICsKICBnZW9tX2JhcihmaWxsID0gIiMyRTg2QUIiKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgKwogIGxhYnModGl0bGUgPSAiQ291bnRzIGJ5IENhdGVnb3J5IiwgeCA9IE5VTEwsIHkgPSAiQ291bnQiKQoKIyBMaW5lICh0aW1lIHNlcmllcykKY2xlYW5fZGF0ZXMgJT4lCiAgZ3JvdXBfYnkoZGF0ZSkgJT4lIHN1bW1hcml6ZShuID0gbigpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUKICBnZ3Bsb3QoYWVzKGRhdGUsIG4pKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gIiM3RDNDOTgiLCBsaW5ld2lkdGggPSAwLjkpICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEyKSArCiAgbGFicyh0aXRsZSA9ICJEYWlseSBDb3VudHMiLCB4ID0gTlVMTCwgeSA9IE5VTEwpCmBgYAoKKipTbWFsbCBtdWx0aXBsZXM6KiogVXNlIGArIGZhY2V0X3dyYXAofmdyb3VwKWAgd2hlbiBjYXRlZ29yaWVzIGFyZSBtYW55LgoKIyAxMS4gVGFibGVzIChndCBxdWljayBwYXR0ZXJuKQoKYGBge3IgZ3R9CnJlc3VsdCAlPiUKICBndDo6Z3QoKSAlPiUKICBndDo6Zm10X251bWJlcihjb2x1bW5zID0gd2hlcmUoaXMubnVtZXJpYyksIGRlY2ltYWxzID0gMikgJT4lCiAgZ3Q6OnRhYl9oZWFkZXIodGl0bGUgPSBtZCgiKipTdW1tYXJ5IGJ5IENhdGVnb3J5KioiKSkKYGBgCgoqKkV4cG9ydDoqKiBgZ3RzYXZlKCJ0YWJsZS5wbmciKWAgb3IgYGd0OjpndHNhdmUoKWAgdG8gUE5HL1BERi9IVE1MLgoKIyAxMi4gTW9kZWxpbmcgKHRpZHltb2RlbHMgKnRpbnkqIHN0YXJ0ZXIpCgpgYGB7ciBtb2RlbGluZywgbWVzc2FnZT1GQUxTRX0KIyBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5bW9kZWxzIikgICMgb25jZQojIGxpYnJhcnkodGlkeW1vZGVscykKIyBzZXQuc2VlZCg0MikKIyBzcGxpdCA8LSBpbml0aWFsX3NwbGl0KGNsZWFuLCBwcm9wID0gMC44KQojIHRyYWluIDwtIHRyYWluaW5nKHNwbGl0KTsgdGVzdCA8LSB0ZXN0aW5nKHNwbGl0KQojIHJlYyA8LSByZWNpcGUodGFyZ2V0IH4gLiwgZGF0YSA9IHRyYWluKSAlPiUgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCAtYWxsX291dGNvbWVzKCkpCiMgbW9kIDwtIGxpbmVhcl9yZWcoKSAlPiUgc2V0X2VuZ2luZSgibG0iKQojIHdmICA8LSB3b3JrZmxvdygpICU+JSBhZGRfbW9kZWwobW9kKSAlPiUgYWRkX3JlY2lwZShyZWMpCiMgZml0IDwtIGZpdCh3ZiwgZGF0YSA9IHRyYWluKQojIG1ldHJpY3MgPC0gcHJlZGljdChmaXQsIHRlc3QpICU+JSBiaW5kX2NvbHModGVzdCkgJT4lIG1ldHJpY3ModHJ1dGggPSB0YXJnZXQsIGVzdGltYXRlID0gLnByZWQpCmBgYAoKKipSZWFsaXR5IGNoZWNrOioqIEFsd2F5cyBiYXNlbGluZSB3aXRoIGEgc2ltcGxlIG1vZGVsIChlLmcuLCBgbG1gKSBiZWZvcmUgYW55dGhpbmcgZmFuY3kuCgojIDEzLiBEZWJ1Z2dpbmcgJiBTYWZldHkgTmV0cwoKLSAgICoqQ29tbW9uIGVycm9yczoqKiBtaXNzaW5nIHBhY2thZ2VzLCB3cm9uZyBjb2x1bW4gbmFtZXMsIGJhZCBqb2lucywgZmFjdG9yIGxldmVscyBub3Qgc2V0LgotICAgKipUYWN0aWNzOioqCiAgICAtICAgYHJsYW5nOjpsYXN0X2Vycm9yKClgIHRvIHNlZSBjb250ZXh0CiAgICAtICAgYGRwbHlyOjpnbGltcHNlKClgIGJlZm9yZS9hZnRlciBrZXkgc3RlcHMKICAgIC0gICBgc3RvcGlmbm90KClgIGZvciBhc3N1bXB0aW9ucyAoZS5nLiwgdW5pcXVlIGtleXMpCiAgICAtICAgVXNlIGB0cnlDYXRjaCgpYCBhcm91bmQgZnJhZ2lsZSBJL08KCmBgYHtyIGd1YXJkcmFpbHN9CnN0b3BpZm5vdCghYW55RHVwbGljYXRlZChjbGVhbiRpZCkpICAjIGlkcyBzaG91bGQgYmUgdW5pcXVlCmBgYAoKIyAxNC4gUmVwcm9kdWNpYmxlIFBhdGhzICYgUHJvamVjdHMKCi0gICBVc2UgUlN0dWRpbyBQcm9qZWN0czsgcm9vdCBwYXRocyB3aXRoIGBoZXJlOjpoZXJlKClgLgotICAgS2VlcCBmb2xkZXJzOiBgZGF0YS9gLCBgUi9gLCBgb3V0cHV0L2AsIGBmaWdzL2AsIGBkb2NzL2AuCi0gICBTYXZlIHNlc3Npb24gaW5mbyB3aXRoIG91dHB1dHMuCgpgYGB7ciBzZXNzaW9uLWluZm99CnNlc3Npb25JbmZvKCkKYGBgCgojIDE1LiBIYW5keSBTbmlwcGV0cyBJIFJldXNlCgpgYGB7ciBzbmlwcGV0c30KIyBQZXJjZW50IG9mIHRvdGFsCnBlcmNlbnRfb2ZfdG90YWwgPC0gZnVuY3Rpb24oeCkgcm91bmQoMTAwICogeCAvIHN1bSh4LCBuYS5ybSA9IFRSVUUpLCAxKQoKIyBOb3QtaW4gb3BlcmF0b3IKYCVuaW4lYCA8LSBmdW5jdGlvbih4LCB5KSAhKHggJWluJSB5KQoKIyBRdWlldGx5IHJ1biBhbiBleHByZXNzaW9uCnF1aWV0bHkgPC0gcHVycnI6OnF1aWV0bHkKYGBgCgojIDE2LiBDaGVja2xpc3QgQmVmb3JlIFlvdSBTaGlwCgotICAgWyBdIENvbHVtbiBuYW1lcyBhcmUgY2xlYW4gJiBjb25zaXN0ZW50Ci0gICBbIF0gRGF0ZXMgcGFyc2VkIGFuZCBpbiBjb3JyZWN0IHRpbWV6b25lL2Zvcm1hdAotICAgWyBdIEpvaW5zIGF1ZGl0ZWQgd2l0aCBgYW50aV9qb2luKClgCi0gICBbIF0gTkFzIGhhbmRsZWQgaW50ZW50aW9uYWxseQotICAgWyBdIEZpZ3VyZXMgaGF2ZSB0aXRsZXMsIGxhYmVscywgdW5pdHMKLSAgIFsgXSBDb2RlIGNodW5rcyBhcmUgZGV0ZXJtaW5pc3RpYyAoc2V0IHNlZWRzKQotICAgWyBdIFNhdmUgYXJ0aWZhY3RzIHdpdGggdmVyc2lvbmVkIGZpbGVuYW1lcwoKIyAxNy4gQXBwZW5kaXg6IHN3aXJsIChsZWFybiBieSBkb2luZykKCi0gICBJbnN0YWxsIG9uY2U6IGBpbnN0YWxsLnBhY2thZ2VzKCJzd2lybCIpYAotICAgRWFjaCBzZXNzaW9uOiBgbGlicmFyeShzd2lybCk7IHN3aXJsKClgCi0gICBOYXZpZ2F0ZSB3aXRoOiBgc2tpcCgpYCwgYHBsYXkoKWAg4oaSIGBueHQoKWAsIGBtYWluKClgLCBgaW5mbygpYCwgYGJ5ZSgpYAoKIyAxOC4gQXBwZW5kaXg6IEtleWJvYXJkIE1hY3JvcyAoUlN0dWRpbykKCi0gICBSdW4gbGluZS9zZWxlY3Rpb246ICoqQ3RybC9DbWQgKyBFbnRlcioqCi0gICBSdW4gYWxsIGNodW5rcyBhYm92ZTogKipDdHJsICsgU2hpZnQgKyBQKiogKFdpbmRvd3MvTGludXgpIG9yICoqQ21kICsgT3B0aW9uICsgUCoqIChtYWNPUykKLSAgIEluc2VydCBjaHVuazogKipDdHJsICsgQWx0ICsgSSoqIC8gKipDbWQgKyBPcHRpb24gKyBJKioKCiMgMTkuIFRvIERvIC8gUGFya2luZyBMb3QKCi0gICBbIF0gQWRkIGEgYHRhcmdldHNgIG9yIGByZW52YCBzZWN0aW9uIHdoZW4gcHJvamVjdHMgZ3JvdwotICAgWyBdIEFkZCB1bml0IHRlc3RzIHdpdGggYHRlc3R0aGF0YCBmb3Iga2V5IGhlbHBlcnMKLSAgIFsgXSBBZGQgYSBzdHlsZSBndWlkZSBkZWNpc2lvbiAobGludHIvc3R5bGVyKQo=