Overview

This is a reproduction of the the Functionals chapter, which comes from Chapter 11 of Advanced R. The purpose was to learn the concepts. I skipped a lot of the extraneous information, focusing on the apply and Map functions.

Prerequisites

library(tidyverse)

My First Functional

lapply2(): R implementation of lapply():

lapply2 <- function(x, f, ...) {
  out <- vector("list", length(x))
  for (i in seq_along(x)) {
    out[[i]] <- f(x[[i]], ...)
  }
  out
}

Show how lapply2() works:

replicate() takes n, a number of replications, and a function to replicate. We pass rnorm(10), which computes 10 random normals. The simplify = FALSE argument returns a list instead of a matrix. lapply2(l, mean) computes the mean of each column of the list, l.

set.seed(159)
l <- replicate(20, rnorm(10), simplify = FALSE)
lapply2(l, mean) %>% unlist()
 [1] -0.44782126 -0.28139494 -0.04527234 -0.38860758 -0.33920482 -0.81036404  0.18424530
 [8] -0.42299603  0.44155869 -0.26928065 -0.05602635 -0.14358085  0.44727663 -0.11995728
[15] -0.25086861 -0.12244024 -0.23319594  0.48824863  0.22955091 -0.51299327

Very useful for getting information from columns of a data frame:

mtcars %>% 
    lapply(class) %>%
    unlist()
      mpg       cyl      disp        hp      drat        wt      qsec        vs 
"numeric" "numeric" "numeric" "numeric" "numeric" "numeric" "numeric" "numeric" 
       am      gear      carb 
"numeric" "numeric" "numeric" 

Divide each column of mtcars by it’s mean:

mtcars %>% 
    lapply(function(x) round(x / mean(x), 2)) %>%
    as_tibble()

Exercises:

  1. Why are the following two invocations of lapply() equivalent?
trims <- c(0, 0.1, 0.2, 0.5)
x <- rcauchy(100)

lapply(trims, function(trim) mean(x, trim = trim))
lapply(trims, mean, x = x)

The two functions are the same due to lapply()’s ... argument, which passes additional arguments to the function being looped. The first one is more readable in my opinion, but they do the same thing.

  1. The function below scales a vector so it falls in the range [0, 1]. How would you apply it to every column of a data frame? How would you apply it to every numeric column in a data frame?
scale01 <- function(x) {
  rng <- range(x, na.rm = TRUE)
  (x - rng[1]) / (rng[2] - rng[1])
}

We’ll use the flights data set from the nycflights13 library because it has columns in various formats. To apply to only numeric columns, first get numeric columns using is.numeric().

library(nycflights13)
flights_num <- flights %>%
    lapply(is.numeric) %>%
    unlist()
flights_num
          year          month            day       dep_time sched_dep_time 
          TRUE           TRUE           TRUE           TRUE           TRUE 
     dep_delay       arr_time sched_arr_time      arr_delay        carrier 
          TRUE           TRUE           TRUE           TRUE          FALSE 
        flight        tailnum         origin           dest       air_time 
          TRUE          FALSE          FALSE          FALSE           TRUE 
      distance           hour         minute      time_hour 
          TRUE           TRUE           TRUE          FALSE 

Then, apply scale01 to subset.

flights[,flights_num] %>%
    lapply(scale01) %>%
    as_tibble()
  1. Use lapply() to fit linear models to the mtcars using the formulas stored in this list:
formulas <- list(
  mpg ~ disp,
  mpg ~ I(1 / disp),
  mpg ~ disp + wt,
  mpg ~ I(1 / disp) + wt
)
fits <- lapply(formulas, function(x) lm(x, mtcars))
fits[[1]] # show first fit

Call:
lm(formula = x, data = mtcars)

Coefficients:
(Intercept)         disp  
   29.59985     -0.04122  
  1. Fit the model mpg ~ disp to each of the bootstrap replicates of mtcars in the list below by using lapply().
bootstraps <- lapply(1:10, function(i) {
  rows <- sample(1:nrow(mtcars), rep = TRUE)
  mtcars[rows, ]
})

Use lapply() to loop over the bootstraps. Need to use an anonymous function to send the bootstraps to the data argument.

fits_boot <- bootstraps %>%
    lapply(function(x) lm(formulas[[1]], data = x))
fits_boot[[1]] # show first fit

Call:
lm(formula = formulas[[1]], data = x)

Coefficients:
(Intercept)         disp  
   31.27484     -0.04702  
  1. For each model in the previous two exercises, extract R2 using the function below:
rsq <- function(mod) summary(mod)$r.squared
lapply(fits, rsq) %>% unlist()
[1] 0.7183433 0.8596865 0.7809306 0.8838038
# lapply(fits_boot, rsq) %>% unlist() # Uncomment to get boostrapped fits

Friends of lapply()

sapply() and vapply()

Used for vector output.

The output of sapply() is equivalent to lapply() %>% unlist() since the default is simplify = TRUE.

sapply(mtcars, is.numeric)
 mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE 
lapply(mtcars, is.numeric) %>% unlist()
 mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE 

vapply() is the same as sapply() but with more structure as the output type and length must be specified.

vapply(mtcars, is.numeric, logical(length = 1))
 mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE 

mapply()

mapply() is used for multiple inputs; however, Map() is preferred.

set.seed(10)
x <- 1:10
y <- 2 * (x + rnorm(10))
mapply(`/`, y, x) %>%
    mean()
[1] 1.803832

apply()

Used for working in two dimensions (i.e. matrixstructure).

a <- matrix(1:25, ncol = 5) %>% 
    t()
a
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    2    3    4    5
[2,]    6    7    8    9   10
[3,]   11   12   13   14   15
[4,]   16   17   18   19   20
[5,]   21   22   23   24   25
# Rowwise
apply(a, 1, sum)
[1]  15  40  65  90 115
# Columnwise
apply(a, 2, sum)
[1] 55 60 65 70 75
# Row and column
apply(a, 2, function(x) x ^ 2)
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    4    9   16   25
[2,]   36   49   64   81  100
[3,]  121  144  169  196  225
[4,]  256  289  324  361  400
[5,]  441  484  529  576  625

Two other useful matrix functions are sweep() and outer(). These are skipped for brevity.

tapply()

Used for group apply.

tapply(mtcars$mpg, mtcars$gear, mean)
       3        4        5 
16.10667 24.53333 21.38000 

Can achieve the same result using split() and sapply().

mtcars$mpg %>% 
    split(mtcars$gear) %>%
    sapply(mean)
       3        4        5 
16.10667 24.53333 21.38000 

Manipulating Lists

I restructured the contents here to discuss Map(), Reduce(), and predicate functions including Filter(), Find(), and Position() all together.

Map()

Used for multiple inputs so we can process lists or data frames in parallel.

# Generate some sample data
xs <- replicate(5, runif(10), simplify = FALSE)
ws <- replicate(5, rpois(10, 5) + 1, simplify = FALSE)
Map(weighted.mean, xs, ws) %>%
    unlist()
[1] 0.5938165 0.5972073 0.4717003 0.5697301 0.4826299

Reduce()

Used to recursively apply a function two arguments at a time to a vector.

x <- 1:10
Reduce(`*`, x) # Factorial
[1] 3628800
factorial(10)
[1] 3628800

Can find intersection between all list elements using reduce.

set.seed(684)
l <- replicate(5, sample(1:10, 15, replace = T), simplify = FALSE)
l
[[1]]
 [1] 10  5  7  6  8  5  4  3  9  5  1  3 10  1  1

[[2]]
 [1]  8  1  5  3  6 10  3  7  3  2  2  6  2  6  9

[[3]]
 [1] 1 4 6 6 4 7 3 9 9 5 1 8 4 7 1

[[4]]
 [1]  7  4  7  9  1  1 10  9  1  8 10  5  2  2  4

[[5]]
 [1]  2  1  8 10  6  4  4  1  1  2  1  6  6  5  3
Reduce(intersect, l) %>% unlist()
[1] 5 8 1

Predicate Functions

A predicate is a function that returns a TRUE or FALSE. Predicate functions apply a predicate to each member of a list.

Filter()

Selects only items that match the predicate function.

df <- data.frame(x = 1:3, y = c("a", "b", "c"))
Filter(is.numeric, df)

Find()

Finds column values that meet the criteria.

Find(is.numeric, df)
[1] 1 2 3

Position()

Finds the column position that meets the criteria

Position(is.numeric, df)
[1] 1
LS0tDQp0aXRsZTogJ0NoYXB0ZXIgMTE6IEZ1bmN0aW9uYWxzJw0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMw0KICBodG1sX2RvY3VtZW50Og0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMw0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICczJw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KbGlicmFyeShrbml0cikNCm9wdHNfY2h1bmskc2V0KGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTMsIGZpZy5hbGlnbj0nY2VudGVyJywNCiAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFKQ0KYGBgDQoNCiMgT3ZlcnZpZXcNCg0KVGhpcyBpcyBhIHJlcHJvZHVjdGlvbiBvZiB0aGUgdGhlIF9GdW5jdGlvbmFsc18gY2hhcHRlciwgd2hpY2ggY29tZXMgZnJvbSBfQ2hhcHRlciAxMV8gb2YgX0FkdmFuY2VkIFJfLiBUaGUgcHVycG9zZSB3YXMgdG8gbGVhcm4gdGhlIGNvbmNlcHRzLiBJIHNraXBwZWQgYSBsb3Qgb2YgdGhlIGV4dHJhbmVvdXMgaW5mb3JtYXRpb24sIGZvY3VzaW5nIG9uIHRoZSBhcHBseSBhbmQgTWFwIGZ1bmN0aW9ucy4gIA0KDQojIFByZXJlcXVpc2l0ZXMNCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQoNCiMgTXkgRmlyc3QgRnVuY3Rpb25hbA0KDQpgbGFwcGx5MigpYDogX19SX18gaW1wbGVtZW50YXRpb24gb2YgYGxhcHBseSgpYDoNCg0KYGBge3J9DQpsYXBwbHkyIDwtIGZ1bmN0aW9uKHgsIGYsIC4uLikgew0KICBvdXQgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoKHgpKQ0KICBmb3IgKGkgaW4gc2VxX2Fsb25nKHgpKSB7DQogICAgb3V0W1tpXV0gPC0gZih4W1tpXV0sIC4uLikNCiAgfQ0KICBvdXQNCn0NCmBgYA0KDQpfX1Nob3cgaG93IGBsYXBwbHkyKClgIHdvcmtzOl9fDQoNCmByZXBsaWNhdGUoKWAgdGFrZXMgYG5gLCBhIG51bWJlciBvZiByZXBsaWNhdGlvbnMsIGFuZCBhIGZ1bmN0aW9uIHRvIHJlcGxpY2F0ZS4gV2UgcGFzcyBgcm5vcm0oMTApYCwgd2hpY2ggY29tcHV0ZXMgMTAgcmFuZG9tIG5vcm1hbHMuIFRoZSBgc2ltcGxpZnkgPSBGQUxTRWAgYXJndW1lbnQgcmV0dXJucyBhIGxpc3QgaW5zdGVhZCBvZiBhIG1hdHJpeC4gYGxhcHBseTIobCwgbWVhbilgIGNvbXB1dGVzIHRoZSBtZWFuIG9mIGVhY2ggY29sdW1uIG9mIHRoZSBsaXN0LCBgbGAuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTU5KQ0KbCA8LSByZXBsaWNhdGUoMjAsIHJub3JtKDEwKSwgc2ltcGxpZnkgPSBGQUxTRSkNCmxhcHBseTIobCwgbWVhbikgJT4lIHVubGlzdCgpDQpgYGANCg0KVmVyeSB1c2VmdWwgZm9yIGdldHRpbmcgaW5mb3JtYXRpb24gZnJvbSBjb2x1bW5zIG9mIGEgZGF0YSBmcmFtZToNCg0KYGBge3J9DQptdGNhcnMgJT4lIA0KICAgIGxhcHBseShjbGFzcykgJT4lDQogICAgdW5saXN0KCkNCmBgYA0KDQpEaXZpZGUgZWFjaCBjb2x1bW4gb2YgYG10Y2Fyc2AgYnkgaXQncyBtZWFuOg0KDQpgYGB7cn0NCm10Y2FycyAlPiUgDQogICAgbGFwcGx5KGZ1bmN0aW9uKHgpIHJvdW5kKHggLyBtZWFuKHgpLCAyKSkgJT4lDQogICAgYXNfdGliYmxlKCkNCmBgYA0KDQojIyBFeGVyY2lzZXM6DQoNCjEuIFdoeSBhcmUgdGhlIGZvbGxvd2luZyB0d28gaW52b2NhdGlvbnMgb2YgYGxhcHBseSgpYCBlcXVpdmFsZW50Pw0KDQpgYGB7ciwgZXZhbCA9IEZ9DQp0cmltcyA8LSBjKDAsIDAuMSwgMC4yLCAwLjUpDQp4IDwtIHJjYXVjaHkoMTAwKQ0KDQpsYXBwbHkodHJpbXMsIGZ1bmN0aW9uKHRyaW0pIG1lYW4oeCwgdHJpbSA9IHRyaW0pKQ0KbGFwcGx5KHRyaW1zLCBtZWFuLCB4ID0geCkNCmBgYA0KDQpUaGUgdHdvIGZ1bmN0aW9ucyBhcmUgdGhlIHNhbWUgZHVlIHRvIGBsYXBwbHkoKWAncyBgLi4uYCBhcmd1bWVudCwgd2hpY2ggcGFzc2VzIGFkZGl0aW9uYWwgYXJndW1lbnRzIHRvIHRoZSBmdW5jdGlvbiBiZWluZyBsb29wZWQuIFRoZSBmaXJzdCBvbmUgaXMgbW9yZSByZWFkYWJsZSBpbiBteSBvcGluaW9uLCBidXQgdGhleSBkbyB0aGUgc2FtZSB0aGluZy4gDQoNCjIuIFRoZSBmdW5jdGlvbiBiZWxvdyBzY2FsZXMgYSB2ZWN0b3Igc28gaXQgZmFsbHMgaW4gdGhlIHJhbmdlIFswLCAxXS4gSG93IHdvdWxkIHlvdSBhcHBseSBpdCB0byBldmVyeSBjb2x1bW4gb2YgYSBkYXRhIGZyYW1lPyBIb3cgd291bGQgeW91IGFwcGx5IGl0IHRvIGV2ZXJ5IG51bWVyaWMgY29sdW1uIGluIGEgZGF0YSBmcmFtZT8NCg0KYGBge3J9DQpzY2FsZTAxIDwtIGZ1bmN0aW9uKHgpIHsNCiAgcm5nIDwtIHJhbmdlKHgsIG5hLnJtID0gVFJVRSkNCiAgKHggLSBybmdbMV0pIC8gKHJuZ1syXSAtIHJuZ1sxXSkNCn0NCmBgYA0KDQpXZSdsbCB1c2UgdGhlIGBmbGlnaHRzYCBkYXRhIHNldCBmcm9tIHRoZSBgbnljZmxpZ2h0czEzYCBsaWJyYXJ5IGJlY2F1c2UgaXQgaGFzIGNvbHVtbnMgaW4gdmFyaW91cyBmb3JtYXRzLiBUbyBhcHBseSB0byBvbmx5IG51bWVyaWMgY29sdW1ucywgZmlyc3QgZ2V0IG51bWVyaWMgY29sdW1ucyB1c2luZyBgaXMubnVtZXJpYygpYC4gDQoNCmBgYHtyfQ0KbGlicmFyeShueWNmbGlnaHRzMTMpDQpmbGlnaHRzX251bSA8LSBmbGlnaHRzICU+JQ0KICAgIGxhcHBseShpcy5udW1lcmljKSAlPiUNCiAgICB1bmxpc3QoKQ0KZmxpZ2h0c19udW0NCmBgYA0KDQpUaGVuLCBhcHBseSBgc2NhbGUwMWAgdG8gc3Vic2V0Lg0KDQpgYGB7cn0NCmZsaWdodHNbLGZsaWdodHNfbnVtXSAlPiUNCiAgICBsYXBwbHkoc2NhbGUwMSkgJT4lDQogICAgYXNfdGliYmxlKCkNCmBgYA0KDQoNCjMuIFVzZSBgbGFwcGx5KClgIHRvIGZpdCBsaW5lYXIgbW9kZWxzIHRvIHRoZSBgbXRjYXJzYCB1c2luZyB0aGUgYGZvcm11bGFzYCBzdG9yZWQgaW4gdGhpcyBsaXN0Og0KDQpgYGB7cn0NCmZvcm11bGFzIDwtIGxpc3QoDQogIG1wZyB+IGRpc3AsDQogIG1wZyB+IEkoMSAvIGRpc3ApLA0KICBtcGcgfiBkaXNwICsgd3QsDQogIG1wZyB+IEkoMSAvIGRpc3ApICsgd3QNCikNCmZpdHMgPC0gbGFwcGx5KGZvcm11bGFzLCBmdW5jdGlvbih4KSBsbSh4LCBtdGNhcnMpKQ0KZml0c1tbMV1dICMgc2hvdyBmaXJzdCBmaXQNCmBgYA0KDQo0LiBGaXQgdGhlIG1vZGVsIGBtcGcgfiBkaXNwYCB0byBlYWNoIG9mIHRoZSBib290c3RyYXAgcmVwbGljYXRlcyBvZiBgbXRjYXJzYCBpbiB0aGUgbGlzdCBiZWxvdyBieSB1c2luZyBgbGFwcGx5KClgLg0KDQpgYGB7cn0NCmJvb3RzdHJhcHMgPC0gbGFwcGx5KDE6MTAsIGZ1bmN0aW9uKGkpIHsNCiAgcm93cyA8LSBzYW1wbGUoMTpucm93KG10Y2FycyksIHJlcCA9IFRSVUUpDQogIG10Y2Fyc1tyb3dzLCBdDQp9KQ0KYGBgDQoNClVzZSBgbGFwcGx5KClgIHRvIGxvb3Agb3ZlciB0aGUgYGJvb3RzdHJhcHNgLiBOZWVkIHRvIHVzZSBhbiBhbm9ueW1vdXMgZnVuY3Rpb24gdG8gc2VuZCB0aGUgYm9vdHN0cmFwcyB0byB0aGUgZGF0YSBhcmd1bWVudC4NCg0KYGBge3J9DQpmaXRzX2Jvb3QgPC0gYm9vdHN0cmFwcyAlPiUNCiAgICBsYXBwbHkoZnVuY3Rpb24oeCkgbG0oZm9ybXVsYXNbWzFdXSwgZGF0YSA9IHgpKQ0KZml0c19ib290W1sxXV0gIyBzaG93IGZpcnN0IGZpdA0KYGBgDQoNCjUuIEZvciBlYWNoIG1vZGVsIGluIHRoZSBwcmV2aW91cyB0d28gZXhlcmNpc2VzLCBleHRyYWN0IFIyIHVzaW5nIHRoZSBmdW5jdGlvbiBiZWxvdzoNCg0KYGBge3J9DQpyc3EgPC0gZnVuY3Rpb24obW9kKSBzdW1tYXJ5KG1vZCkkci5zcXVhcmVkDQpsYXBwbHkoZml0cywgcnNxKSAlPiUgdW5saXN0KCkNCiMgbGFwcGx5KGZpdHNfYm9vdCwgcnNxKSAlPiUgdW5saXN0KCkgIyBVbmNvbW1lbnQgdG8gZ2V0IGJvb3N0cmFwcGVkIGZpdHMNCmBgYA0KDQojIyBGcmllbmRzIG9mIGxhcHBseSgpDQoNCiMjIHNhcHBseSgpIGFuZCB2YXBwbHkoKQ0KDQpVc2VkIGZvciB2ZWN0b3Igb3V0cHV0Lg0KDQpUaGUgb3V0cHV0IG9mIGBzYXBwbHkoKWAgaXMgZXF1aXZhbGVudCB0byBgbGFwcGx5KCkgJT4lIHVubGlzdCgpYCBzaW5jZSB0aGUgZGVmYXVsdCBpcyBgc2ltcGxpZnkgPSBUUlVFYC4gDQoNCmBgYHtyfQ0Kc2FwcGx5KG10Y2FycywgaXMubnVtZXJpYykNCmBgYA0KDQpgYGB7cn0NCmxhcHBseShtdGNhcnMsIGlzLm51bWVyaWMpICU+JSB1bmxpc3QoKQ0KYGBgDQoNCmB2YXBwbHkoKWAgaXMgdGhlIHNhbWUgYXMgYHNhcHBseSgpYCBidXQgd2l0aCBtb3JlIHN0cnVjdHVyZSBhcyB0aGUgb3V0cHV0IHR5cGUgYW5kIGxlbmd0aCBtdXN0IGJlIHNwZWNpZmllZC4NCg0KYGBge3J9DQp2YXBwbHkobXRjYXJzLCBpcy5udW1lcmljLCBsb2dpY2FsKGxlbmd0aCA9IDEpKQ0KYGBgDQoNCiMjIG1hcHBseSgpDQoNCmBtYXBwbHkoKWAgaXMgdXNlZCBmb3IgbXVsdGlwbGUgaW5wdXRzOyBob3dldmVyLCBgTWFwKClgIGlzIHByZWZlcnJlZC4NCg0KYGBge3J9DQpzZXQuc2VlZCgxMCkNCnggPC0gMToxMA0KeSA8LSAyICogKHggKyBybm9ybSgxMCkpDQptYXBwbHkoYC9gLCB5LCB4KSAlPiUNCiAgICBtZWFuKCkNCmBgYA0KDQojIyBhcHBseSgpDQoNClVzZWQgZm9yIHdvcmtpbmcgaW4gdHdvIGRpbWVuc2lvbnMgKGkuZS4gYG1hdHJpeGBzdHJ1Y3R1cmUpLg0KDQpgYGB7cn0NCmEgPC0gbWF0cml4KDE6MjUsIG5jb2wgPSA1KSAlPiUgDQogICAgdCgpDQphDQpgYGANCg0KYGBge3J9DQojIFJvd3dpc2UNCmFwcGx5KGEsIDEsIHN1bSkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBDb2x1bW53aXNlDQphcHBseShhLCAyLCBzdW0pDQpgYGANCg0KYGBge3J9DQojIFJvdyBhbmQgY29sdW1uDQphcHBseShhLCAyLCBmdW5jdGlvbih4KSB4IF4gMikNCmBgYA0KDQpUd28gb3RoZXIgdXNlZnVsIG1hdHJpeCBmdW5jdGlvbnMgYXJlIGBzd2VlcCgpYCBhbmQgYG91dGVyKClgLiBUaGVzZSBhcmUgc2tpcHBlZCBmb3IgYnJldml0eS4NCg0KIyMgdGFwcGx5KCkNCg0KVXNlZCBmb3IgZ3JvdXAgYXBwbHkuDQoNCmBgYHtyfQ0KdGFwcGx5KG10Y2FycyRtcGcsIG10Y2FycyRnZWFyLCBtZWFuKQ0KYGBgDQoNCkNhbiBhY2hpZXZlIHRoZSBzYW1lIHJlc3VsdCB1c2luZyBgc3BsaXQoKWAgYW5kIGBzYXBwbHkoKWAuDQoNCmBgYHtyfQ0KbXRjYXJzJG1wZyAlPiUgDQogICAgc3BsaXQobXRjYXJzJGdlYXIpICU+JQ0KICAgIHNhcHBseShtZWFuKQ0KYGBgDQoNCg0KIyBNYW5pcHVsYXRpbmcgTGlzdHMNCg0KSSByZXN0cnVjdHVyZWQgdGhlIGNvbnRlbnRzIGhlcmUgdG8gZGlzY3VzcyBgTWFwKClgLCBgUmVkdWNlKClgLCBhbmQgcHJlZGljYXRlIGZ1bmN0aW9ucyBpbmNsdWRpbmcgYEZpbHRlcigpYCwgYEZpbmQoKWAsIGFuZCBgUG9zaXRpb24oKWAgYWxsIHRvZ2V0aGVyLg0KDQojIyBNYXAoKQ0KDQpVc2VkIGZvciBtdWx0aXBsZSBpbnB1dHMgc28gd2UgY2FuIHByb2Nlc3MgbGlzdHMgb3IgZGF0YSBmcmFtZXMgaW4gcGFyYWxsZWwuDQoNCmBgYHtyfQ0KIyBHZW5lcmF0ZSBzb21lIHNhbXBsZSBkYXRhDQp4cyA8LSByZXBsaWNhdGUoNSwgcnVuaWYoMTApLCBzaW1wbGlmeSA9IEZBTFNFKQ0Kd3MgPC0gcmVwbGljYXRlKDUsIHJwb2lzKDEwLCA1KSArIDEsIHNpbXBsaWZ5ID0gRkFMU0UpDQpNYXAod2VpZ2h0ZWQubWVhbiwgeHMsIHdzKSAlPiUNCiAgICB1bmxpc3QoKQ0KYGBgDQoNCiMjIFJlZHVjZSgpDQoNClVzZWQgdG8gcmVjdXJzaXZlbHkgYXBwbHkgYSBmdW5jdGlvbiB0d28gYXJndW1lbnRzIGF0IGEgdGltZSB0byBhIHZlY3Rvci4NCg0KYGBge3J9DQp4IDwtIDE6MTANClJlZHVjZShgKmAsIHgpICMgRmFjdG9yaWFsDQpgYGANCg0KYGBge3J9DQpmYWN0b3JpYWwoMTApDQpgYGANCg0KQ2FuIGZpbmQgaW50ZXJzZWN0aW9uIGJldHdlZW4gYWxsIGxpc3QgZWxlbWVudHMgdXNpbmcgcmVkdWNlLg0KDQpgYGB7cn0NCnNldC5zZWVkKDY4NCkNCmwgPC0gcmVwbGljYXRlKDUsIHNhbXBsZSgxOjEwLCAxNSwgcmVwbGFjZSA9IFQpLCBzaW1wbGlmeSA9IEZBTFNFKQ0KbA0KYGBgDQoNCmBgYHtyfQ0KUmVkdWNlKGludGVyc2VjdCwgbCkgJT4lIHVubGlzdCgpDQpgYGANCg0KIyMgUHJlZGljYXRlIEZ1bmN0aW9ucw0KDQpBIHByZWRpY2F0ZSBpcyBhIGZ1bmN0aW9uIHRoYXQgcmV0dXJucyBhIGBUUlVFYCBvciBgRkFMU0VgLiBQcmVkaWNhdGUgZnVuY3Rpb25zIGFwcGx5IGEgcHJlZGljYXRlIHRvIGVhY2ggbWVtYmVyIG9mIGEgbGlzdC4NCg0KIyMjIEZpbHRlcigpDQoNClNlbGVjdHMgb25seSBpdGVtcyB0aGF0IG1hdGNoIHRoZSBwcmVkaWNhdGUgZnVuY3Rpb24uDQoNCmBgYHtyfQ0KZGYgPC0gZGF0YS5mcmFtZSh4ID0gMTozLCB5ID0gYygiYSIsICJiIiwgImMiKSkNCkZpbHRlcihpcy5udW1lcmljLCBkZikNCmBgYA0KDQojIyMgRmluZCgpDQoNCkZpbmRzIGNvbHVtbiB2YWx1ZXMgdGhhdCBtZWV0IHRoZSBjcml0ZXJpYS4NCg0KYGBge3J9DQpGaW5kKGlzLm51bWVyaWMsIGRmKQ0KYGBgDQoNCiMjIyBQb3NpdGlvbigpDQoNCkZpbmRzIHRoZSBjb2x1bW4gcG9zaXRpb24gdGhhdCBtZWV0cyB0aGUgY3JpdGVyaWENCg0KYGBge3J9DQpQb3NpdGlvbihpcy5udW1lcmljLCBkZikNCmBgYA0KDQo=