This script replicates Table 1 of the manuscript (Descriptive statistics of Strava Clubs).


Getting started

clean up

rm (list = ls( ))


custom functions

  • fpackage.check: Check if packages are installed (and install if not) in R (source)
fpackage.check <- function(packages) {
    lapply(packages, FUN = function(x) {
        if (!require(x, character.only = TRUE)) {
            install.packages(x, dependencies = TRUE)
            library(x, character.only = TRUE)
        }
    })
}


necessary packages

We install and load the packages we need later on: - RSiena - igraph: Descriptives (dyad/triad census, degrees) - dplyr: for data manipulation - sna: for network analysis - knitr: for generating tables - kableExtra: for manipulating tables

packages = c("RSiena", "igraph", "dplyr", "sna", "knitr", "kableExtra")
fpackage.check(packages)

additional functions

# density: observed relations divided by possible relations
fdensity <- function(x) {
    # x is your nomination network make sure diagonal cells are NA
    diag(x) <- NA
    # take care of RSiena structural zeros, set as missing.
    x[x == 10] <- NA
    sum(x == 1, na.rm = T)/(sum(x == 1 | x == 0, na.rm = T))
}

# calculate intragroup density
fdensityintra <- function(x, A) {
    # A is matrix indicating whether nodes in dyad have same node attributes
    diag(x) <- NA
    x[x == 10] <- NA
    diag(A) <- NA
    sum(x == 1 & A == 1, na.rm = T)/(sum((x == 1 | x == 0) & A == 1, na.rm = T))
}

# calculate intragroup density
fdensityinter <- function(x, A) {
    # A is matrix indicating whether nodes in dyad have same node attributes
    diag(x) <- NA
    x[x == 10] <- NA
    diag(A) <- NA
    sum(x == 1 & A != 1, na.rm = T)/(sum((x == 1 | x == 0) & A != 1, na.rm = T))
}

# construct dyad characteristic whether nodes are similar/homogenous
fhomomat <- function(x) {
    # x is a vector of node-covariate
    xmat <- matrix(x, nrow = length(x), ncol = length(x))
    xmatt <- t(xmat)
    xhomo <- xmat == xmatt
    return(xhomo)
}

# a function to calculate all valid dyads.
fndyads <- function(x) {
    diag(x) <- NA
    x[x == 10] <- NA
    (sum((x == 1 | x == 0), na.rm = T))
}

# a function to calculate all valid intragroupdyads.
fndyads2 <- function(x, A) {
    diag(x) <- NA
    x[x == 10] <- NA
    diag(A) <- NA
    (sum((x == 1 | x == 0) & A == 1, na.rm = T))
}


fscolnet <- function(network, ccovar) {
    # Calculate coleman on network level:
    # https://reader.elsevier.com/reader/sd/pii/S0378873314000239?token=A42F99FF6E2B750436DD2CB0DB7B1F41BDEC16052A45683C02644DAF88215A3379636B2AA197B65941D6373E9E2EE413
    
    fhomomat <- function(x) {
        xmat <- matrix(x, nrow = length(x), ncol = length(x))
        xmatt <- t(xmat)
        xhomo <- xmat == xmatt
        return(xhomo)
    }
    
    fsumintra <- function(x, A) {
        # A is matrix indicating whether nodes constituting dyad have same characteristics
        diag(x) <- NA
        x[x == 10] <- NA
        diag(A) <- NA
        sum(x == 1 & A == 1, na.rm = T)
    }
    
    # expecation w*=sum_g sum_i (ni((ng-1)/(N-1)))
    network[network == 10] <- NA
    ni <- rowSums(network, na.rm = T)
    ng <- NA
    for (i in 1:length(ccovar)) {
        ng[i] <- table(ccovar)[rownames(table(ccovar)) == ccovar[i]]
    }
    N <- length(ccovar)
    wexp <- sum(ni * ((ng - 1)/(N - 1)), na.rm = T)
    
    # wgg1 how many intragroup ties
    w <- fsumintra(network, fhomomat(ccovar))
    
    Scol_net <- ifelse(w >= wexp, (w - wexp)/(sum(ni, na.rm = T) - wexp), (w - wexp)/wexp)
    return(Scol_net)
}

fMoran.I <- function(x, weight, scaled = FALSE, na.rm = FALSE, alternative = "two.sided", rowstandardize = TRUE) {
    if (rowstandardize) {
        if (dim(weight)[1] != dim(weight)[2]) 
            stop("'weight' must be a square matrix")
        n <- length(x)
        if (dim(weight)[1] != n) 
            stop("'weight' must have as many rows as observations in 'x'")
        ei <- -1/(n - 1)
        nas <- is.na(x)
        if (any(nas)) {
            if (na.rm) {
                x <- x[!nas]
                n <- length(x)
                weight <- weight[!nas, !nas]
            } else {
                warning("'x' has missing values: maybe you wanted to set na.rm = TRUE?")
                return(list(observed = NA, expected = ei, sd = NA, p.value = NA))
            }
        }
        ROWSUM <- rowSums(weight)
        ROWSUM[ROWSUM == 0] <- 1
        weight <- weight/ROWSUM
        s <- sum(weight)
        m <- mean(x)
        y <- x - m
        cv <- sum(weight * y %o% y)
        v <- sum(y^2)
        obs <- (n/s) * (cv/v)
        if (scaled) {
            i.max <- (n/s) * (sd(rowSums(weight) * y)/sqrt(v/(n - 1)))
            obs <- obs/i.max
        }
        S1 <- 0.5 * sum((weight + t(weight))^2)
        S2 <- sum((apply(weight, 1, sum) + apply(weight, 2, sum))^2)
        s.sq <- s^2
        k <- (sum(y^4)/n)/(v/n)^2
        sdi <- sqrt((n * ((n^2 - 3 * n + 3) * S1 - n * S2 + 3 * s.sq) - k * (n * (n - 1) * S1 - 2 * n * 
            S2 + 6 * s.sq))/((n - 1) * (n - 2) * (n - 3) * s.sq) - 1/((n - 1)^2))
        alternative <- match.arg(alternative, c("two.sided", "less", "greater"))
        pv <- pnorm(obs, mean = ei, sd = sdi)
        if (alternative == "two.sided") 
            pv <- if (obs <= ei) 
                2 * pv else 2 * (1 - pv)
        if (alternative == "greater") 
            pv <- 1 - pv
        list(observed = obs, expected = ei, sd = sdi, p.value = pv)
    } else {
        if (dim(weight)[1] != dim(weight)[2]) 
            stop("'weight' must be a square matrix")
        n <- length(x)
        if (dim(weight)[1] != n) 
            stop("'weight' must have as many rows as observations in 'x'")
        ei <- -1/(n - 1)
        nas <- is.na(x)
        if (any(nas)) {
            if (na.rm) {
                x <- x[!nas]
                n <- length(x)
                weight <- weight[!nas, !nas]
            } else {
                warning("'x' has missing values: maybe you wanted to set na.rm = TRUE?")
                return(list(observed = NA, expected = ei, sd = NA, p.value = NA))
            }
        }
        # ROWSUM <- rowSums(weight) ROWSUM[ROWSUM == 0] <- 1 weight <- weight/ROWSUM
        s <- sum(weight)
        m <- mean(x)
        y <- x - m
        cv <- sum(weight * y %o% y)
        v <- sum(y^2)
        obs <- (n/s) * (cv/v)
        if (scaled) {
            i.max <- (n/s) * (sd(rowSums(weight) * y)/sqrt(v/(n - 1)))
            obs <- obs/i.max
        }
        S1 <- 0.5 * sum((weight + t(weight))^2)
        S2 <- sum((apply(weight, 1, sum) + apply(weight, 2, sum))^2)
        s.sq <- s^2
        k <- (sum(y^4)/n)/(v/n)^2
        sdi <- sqrt((n * ((n^2 - 3 * n + 3) * S1 - n * S2 + 3 * s.sq) - k * (n * (n - 1) * S1 - 2 * n * 
            S2 + 6 * s.sq))/((n - 1) * (n - 2) * (n - 3) * s.sq) - 1/((n - 1)^2))
        alternative <- match.arg(alternative, c("two.sided", "less", "greater"))
        pv <- pnorm(obs, mean = ei, sd = sdi)
        if (alternative == "two.sided") 
            pv <- if (obs <= ei) 
                2 * pv else 2 * (1 - pv)
        if (alternative == "greater") 
            pv <- 1 - pv
        list(observed = obs, expected = ei, sd = sdi, p.value = pv)
    }
    
    
}


load data objects

# load the RSiena objects
load("clubdata_rsiena_freq.RData")
load("clubdata_rsiena_vol.RData")
load("clubdata.RData")


Descriptives table

Replicate the descriptives table:

tab <- matrix(nrow=16, ncol=5)
rownames(tab) <- c("Density", "Average degree", "Reciprocity index", "Global transitivity index", "Jaccard index", "Gender segregation", "Network autocorrelation", "Direct ties", "Frequency (per week)", "Hours (per week)", "Distance decay", "Frequency (per week)", "Hours (per week)", "Weekly running frequency at t1", "Weekly running hours at t1", "Male sex (%)" )
colnames(tab) <- c("Club 1","Club 2", "Club 3", "Club 4", "Club 5")

for (i in 1:5) { # for each club
  # densities over time
  densities <- as.numeric(strsplit(substring(capture.output(clubdata_rsiena_freq[[i]])[12], 20), " ")[[1]])
  # mean (standard deviation)
  tab[1, i] <- paste(as.character(round(mean(densities), 3)), " (", as.character(round(sd(densities), 3)), ")", sep="")
}


# Average degrees
for (i in 1:5) { # for each club
  # calculate average out-degree over time
  d <- vector ()
  for (t in 1:12) {
    d[[t]] <- mean(igraph::degree(igraph::graph_from_adjacency_matrix(clubdata_rsiena_freq[[i]]$depvars$kudonet[,,t]), mode = "out"))
    
  }
  # mean (standard deviation)
  tab[2, i] <- paste(as.character(round(mean(d), 3)), " (", as.character(round(sd(d), 3)), ")", sep="")
}


# Reciprocity index
for (i in 1:5) { # for each club
  # calculate reciprocity index over time
  ri <- vector ()
  
  for (t in 1:12) {
    mut <- igraph::dyad.census(igraph::graph_from_adjacency_matrix(clubdata_rsiena_freq[[i]]$depvars$kudonet[,,t]))[[1]]
    nonmut <- igraph::dyad.census(igraph::graph_from_adjacency_matrix(clubdata_rsiena_freq[[i]]$depvars$kudonet[,,t]))[[2]]
    ri[[t]] <- mut/(mut+nonmut)
    
  }
  tab[3, i] <- paste(as.character(round(mean(ri), 3)), " (", as.character(round(sd(ri), 3)), ")", sep="")
}

# Transitivity index
for (i in 1:5) { # for each club
  # calculate transitivity index over time
  ti <- vector ()
  
  for (t in 1:12) {
    ti[[t]] <- igraph::transitivity(igraph::graph_from_adjacency_matrix(clubdata_rsiena_freq[[i]]$depvars$kudonet[,,t]))
    
  }
  tab[4, i] <- paste(as.character(round(mean(ti), 3)), " (", as.character(round(sd(ti), 3)), ")", sep="")
}

# Jaccard index

# Gender segregation 
for (i in 1:5) { # for each club
  # calculate coleman's homophily index over time
  col <- vector ()
  
  for (t in 1:12) {
    col[[t]] <- fscolnet(clubdata_rsiena_freq[[i]]$depvars$kudonet[,,t], clubdata_rsiena_freq[[i]]$cCovars$gender)
  }
  tab[6, i] <- paste(as.character(round(mean(col), 3)), " (", as.character(round(sd(col), 3)), ")", sep="")
}

# Network autocorrelation
# frequency
for (i in 1:5) { # for each club
  # we calculate moran's i
  # both with direct ties and indirect ties as well
  # for frequency and volume
  mor_freq_dir <- vector ()
  mor_freq_dist <- vector ()
  mor_vol_dir <- vector ()
  mor_vol_dist <- vector ()
  
  for (t in 1:12) {         
    freq <- clubdata[[i]]$freq_run[,,t]   # attributes at time t
    vol <- clubdata[[i]]$time_run[,,t]   
    knet <- network::as.network(clubdata_rsiena_freq[[i]]$depvars$kudonet[,,t]) # net a time t
    geodistances <- sna::geodist(knet, count.paths=T) # geodesic distance
    geodistances <- geodistances$gdist 
    diag(geodistances) <- Inf
    weights1 <- geodistances == 1               # only direct ties
    weights2 <- exp(-geodistances)              # also indirect
    
    mor_freq_dir[[t]] <- fMoran.I(freq, scaled = FALSE, # calculate moran on direct
                                  weight = weights1, 
                                  na.rm = TRUE, 
                                  rowstandardize = FALSE)[[1]] 
    mor_freq_dist[[t]] <- fMoran.I(freq, scaled = FALSE, # and indirect
                                   weight = weights2, 
                                   na.rm = TRUE, 
                                   rowstandardize = FALSE)[[1]]
    mor_vol_dir[[t]] <- fMoran.I(vol, scaled = FALSE, # calculate moran on direct
                                 weight = weights1, 
                                 na.rm = TRUE, 
                                 rowstandardize = FALSE)[[1]] 
    mor_vol_dist[[t]] <- fMoran.I(vol, scaled = FALSE, # and indirect
                                  weight = weights2, 
                                  na.rm = TRUE, 
                                  rowstandardize = FALSE)[[1]]
  }
  tab[9, i] <- paste(as.character(round(mean(mor_freq_dir), 3)), " (", as.character(round(sd(mor_freq_dir), 3)), ")", sep="")
  tab[10, i] <- paste(as.character(round(mean(mor_vol_dir), 3)), " (", as.character(round(sd(mor_vol_dir), 3)), ")", sep="")
  tab[12, i] <- paste(as.character(round(mean(mor_freq_dist), 3)), " (", as.character(round(sd(mor_freq_dist), 3)), ")", sep="")
  tab[13, i] <- paste(as.character(round(mean(mor_vol_dist), 3)), " (", as.character(round(sd(mor_vol_dist), 3)), ")", sep="")
}

# behavior at baseline (w1)
for (i in 1:5) { # for each club
  # running attributes at w1
  tab[14, i] <- paste(as.character(round(mean(clubdata[[i]]$freq_run[,,1], na.rm=T), 3)), " (", as.character(round(sd(clubdata[[i]]$freq_run[,,1], na.rm=T), 3)), ")", sep="")
  tab[15, i] <- paste(as.character(round(mean(clubdata[[i]]$time_run[,,1], na.rm=T), 3)), " (", as.character(round(sd(clubdata[[i]]$time_run[,,1], na.rm=T), 3)), ")", sep="")
}
# male sex (%)
for (i in 1:5) { # for each club
  # percentage male
  tab[16, i] <- round((length(clubdata[[i]][clubdata[[i]]$male==1])/clubdata[[i]]$netsize)*100, 3)
}

# for now we include jaccard manually; but we want to use a function to calculate these ideally.
tab[5, ] <- c("0.850 (0.060)", "0.75 (0.030)", "0.500 (0.060)", "0.800 (0.090)", "0.690 (0.020)")

Table

Table 1. Descriptive statistics of Strava clubs
Club 1 Club 2 Club 3 Club 4 Club 5
Density 0.083 (0.014) 0.168 (0.014) 0.011 (0.002) 0.115 (0.018) 0.098 (0.013)
Average degree 2.392 (0.408) 10.156 (0.838) 1.787 (0.28) 1.385 (0.205) 7.418 (0.994)
Reciprocity index 0.803 (0.06) 0.634 (0.036) 0.386 (0.062) 0.72 (0.129) 0.607 (0.03)
Global transitivity index 0.581 (0.035) 0.544 (0.016) 0.276 (0.076) 0.735 (0.143) 0.526 (0.018)
Jaccard index 0.850 (0.060) 0.75 (0.030) 0.500 (0.060) 0.800 (0.090) 0.690 (0.020)
Gender segregation 0.235 (0.109) 0.294 (0.038) 0.093 (0.064) -0.139 (0.12) 0.015 (0.034)
Network autocorrelation
Direct ties
Frequency (per week) 0.173 (0.169) 0.217 (0.081) 0.203 (0.069) 0.296 (0.264) 0.276 (0.04)
Hours (per week) 0.207 (0.151) 0.197 (0.099) 0.244 (0.065) 0.243 (0.302) 0.324 (0.054)
Distance decay
Frequency (per week) 0.108 (0.117) 0.112 (0.036) 0.141 (0.061) 0.311 (0.254) 0.157 (0.022)
Hours (per week) 0.138 (0.101) 0.107 (0.043) 0.161 (0.04) 0.275 (0.276) 0.18 (0.028)
Weekly running frequency at t1 2.033 (1.351) 3 (2.15) 1.885 (1.658) 1.385 (1.502) 3.208 (2.313)
Weekly running hours at t1 2.1 (1.561) 2.952 (2.161) 1.915 (1.726) 1.538 (1.613) 3.143 (2.475)
Male sex (%) 56.667 70.968 70.909 61.538 67.532
Note:
For network measures, mean values over time are given with standard deviations in parentheses. For behavioral attributes, means and standard deviations at baseline (t1: December) are given. Degrees reflect kudos ties, with awarding 1 or more kudos constituting the presence of a tie. The reciprocity index is the proportion of kudos ties that were reciprocated; the transitivity index is the number of closed triplets over the total number of triplets (both open and closed).
a The Jaccard similarity index measures the extent of tie change between consecutive waves
b The gender segregation measure used is Coleman’s homophily index.
c The network autocorrelation measure used is Moran’s I. We calculated Moran’s I for direct ties (i.e. with undirected path length 1) and for all ties to whom ego is (directly or indirectly) tied, using a distance-decay function for assigning weights. To construct the weight matrix, we measured the geodistance (d) for each dyad as the shortest (undirected) path length. We then used the negative exponential distance-decay function. We did not row-standardize.
LS0tDQp0aXRsZTogIlJlcGxpY2F0aW5nIFRhYmxlIDEiDQpkYXRlOiAiTGFzdCBjb21waWxlZCBvbiBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCLCAlWScpYCINCmJpYmxpb2dyYXBoeTogcmVmZXJlbmNlcy5iaWINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjc3M6IHR3ZWFrcy5jc3MNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQ0KICAgIHRvY19kZXB0aDogMQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KLS0tDQoNCg0KYGBge3IsIGdsb2JhbHNldHRpbmdzLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCg0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0Kb3B0c19jaHVuayRzZXQodGlkeS5vcHRzPWxpc3Qod2lkdGguY3V0b2ZmPTEwMCksdGlkeT1UUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSxjb21tZW50ID0gIiM+IiwgY2FjaGU9VFJVRSwgY2xhc3Muc291cmNlPWMoInRlc3QiKSwgY2xhc3Mub3V0cHV0PWMoInRlc3QyIikpDQpvcHRpb25zKHdpZHRoID0gMTAwKQ0KcmdsOjpzZXR1cEtuaXRyKCkNCg0KDQoNCmNvbG9yaXplIDwtIGZ1bmN0aW9uKHgsIGNvbG9yKSB7c3ByaW50ZigiPHNwYW4gc3R5bGU9J2NvbG9yOiAlczsnPiVzPC9zcGFuPiIsIGNvbG9yLCB4KSB9DQoNCmBgYA0KDQpgYGB7ciBrbGlwcHksIGVjaG89RkFMU0UsIGluY2x1ZGU9VFJVRX0NCmtsaXBweTo6a2xpcHB5KHBvc2l0aW9uID0gYygndG9wJywgJ3JpZ2h0JykpDQoja2xpcHB5OjprbGlwcHkoY29sb3IgPSAnZGFya3JlZCcpDQoja2xpcHB5OjprbGlwcHkodG9vbHRpcF9tZXNzYWdlID0gJ0NsaWNrIHRvIGNvcHknLCB0b29sdGlwX3N1Y2Nlc3MgPSAnRG9uZScpDQpgYGANCg0KDQoNCi0tLQ0KDQpUaGlzIHNjcmlwdCByZXBsaWNhdGVzIFRhYmxlIDEgb2YgdGhlIG1hbnVzY3JpcHQgKERlc2NyaXB0aXZlIHN0YXRpc3RpY3Mgb2YgU3RyYXZhIENsdWJzKS4NCg0KPGJyPg0KDQoNCiMgR2V0dGluZyBzdGFydGVkDQoNCiMjIGNsZWFuIHVwDQoNCmBgYHtyLCBhdHRyLm91dHB1dD0nc3R5bGU9Im1heC1oZWlnaHQ6IDIwMHB4OyInfQ0Kcm0gKGxpc3QgPSBscyggKSkNCmBgYA0KDQo8YnI+IA0KDQojIyBjdXN0b20gZnVuY3Rpb25zDQoNCi0gYGZwYWNrYWdlLmNoZWNrYDogQ2hlY2sgaWYgcGFja2FnZXMgYXJlIGluc3RhbGxlZCAoYW5kIGluc3RhbGwgaWYgbm90KSBpbiBSIChbc291cmNlXShodHRwczovL3ZiYWxpZ2EuZ2l0aHViLmlvL3ZlcmlmeS10aGF0LXItcGFja2FnZXMtYXJlLWluc3RhbGxlZC1hbmQtbG9hZGVkLykpDQoNCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30NCg0KZnBhY2thZ2UuY2hlY2sgPC0gZnVuY3Rpb24ocGFja2FnZXMpIHsNCiAgICBsYXBwbHkocGFja2FnZXMsIEZVTiA9IGZ1bmN0aW9uKHgpIHsNCiAgICAgICAgaWYgKCFyZXF1aXJlKHgsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkpIHsNCiAgICAgICAgICAgIGluc3RhbGwucGFja2FnZXMoeCwgZGVwZW5kZW5jaWVzID0gVFJVRSkNCiAgICAgICAgICAgIGxpYnJhcnkoeCwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQ0KICAgICAgICB9DQogICAgfSkNCn0NCmBgYA0KDQo8YnI+DQoNCg0KIyMgbmVjZXNzYXJ5IHBhY2thZ2VzDQoNCldlIGluc3RhbGwgYW5kIGxvYWQgdGhlIHBhY2thZ2VzIHdlIG5lZWQgbGF0ZXIgb246DQotIGBSU2llbmFgDQotIGBpZ3JhcGhgOiBEZXNjcmlwdGl2ZXMgKGR5YWQvdHJpYWQgY2Vuc3VzLCBkZWdyZWVzKQ0KLSBgZHBseXJgOiBmb3IgZGF0YSBtYW5pcHVsYXRpb24NCi0gYHNuYWA6IGZvciBuZXR3b3JrIGFuYWx5c2lzDQotIGBrbml0cmA6IGZvciBnZW5lcmF0aW5nIHRhYmxlcw0KLSBga2FibGVFeHRyYWA6IGZvciBtYW5pcHVsYXRpbmcgdGFibGVzDQoNCmBgYHtyIHBhY2thZ2VzLCByZXN1bHRzPSdoaWRlJ30NCnBhY2thZ2VzID0gYygiUlNpZW5hIiwgImlncmFwaCIsICJkcGx5ciIsICJzbmEiLCAia25pdHIiLCAia2FibGVFeHRyYSIpDQpmcGFja2FnZS5jaGVjayhwYWNrYWdlcykNCmBgYA0KIyMgYWRkaXRpb25hbCBmdW5jdGlvbnMgDQoNCmBgYHtyIGV2YWw9VH0NCiMgZGVuc2l0eTogb2JzZXJ2ZWQgcmVsYXRpb25zIGRpdmlkZWQgYnkgcG9zc2libGUgcmVsYXRpb25zDQpmZGVuc2l0eSA8LSBmdW5jdGlvbih4KSB7DQogICAgIyB4IGlzIHlvdXIgbm9taW5hdGlvbiBuZXR3b3JrIG1ha2Ugc3VyZSBkaWFnb25hbCBjZWxscyBhcmUgTkENCiAgICBkaWFnKHgpIDwtIE5BDQogICAgIyB0YWtlIGNhcmUgb2YgUlNpZW5hIHN0cnVjdHVyYWwgemVyb3MsIHNldCBhcyBtaXNzaW5nLg0KICAgIHhbeCA9PSAxMF0gPC0gTkENCiAgICBzdW0oeCA9PSAxLCBuYS5ybSA9IFQpLyhzdW0oeCA9PSAxIHwgeCA9PSAwLCBuYS5ybSA9IFQpKQ0KfQ0KDQojIGNhbGN1bGF0ZSBpbnRyYWdyb3VwIGRlbnNpdHkNCmZkZW5zaXR5aW50cmEgPC0gZnVuY3Rpb24oeCwgQSkgew0KICAgICMgQSBpcyBtYXRyaXggaW5kaWNhdGluZyB3aGV0aGVyIG5vZGVzIGluIGR5YWQgaGF2ZSBzYW1lIG5vZGUgYXR0cmlidXRlcw0KICAgIGRpYWcoeCkgPC0gTkENCiAgICB4W3ggPT0gMTBdIDwtIE5BDQogICAgZGlhZyhBKSA8LSBOQQ0KICAgIHN1bSh4ID09IDEgJiBBID09IDEsIG5hLnJtID0gVCkvKHN1bSgoeCA9PSAxIHwgeCA9PSAwKSAmIEEgPT0gMSwgbmEucm0gPSBUKSkNCn0NCg0KIyBjYWxjdWxhdGUgaW50cmFncm91cCBkZW5zaXR5DQpmZGVuc2l0eWludGVyIDwtIGZ1bmN0aW9uKHgsIEEpIHsNCiAgICAjIEEgaXMgbWF0cml4IGluZGljYXRpbmcgd2hldGhlciBub2RlcyBpbiBkeWFkIGhhdmUgc2FtZSBub2RlIGF0dHJpYnV0ZXMNCiAgICBkaWFnKHgpIDwtIE5BDQogICAgeFt4ID09IDEwXSA8LSBOQQ0KICAgIGRpYWcoQSkgPC0gTkENCiAgICBzdW0oeCA9PSAxICYgQSAhPSAxLCBuYS5ybSA9IFQpLyhzdW0oKHggPT0gMSB8IHggPT0gMCkgJiBBICE9IDEsIG5hLnJtID0gVCkpDQp9DQoNCiMgY29uc3RydWN0IGR5YWQgY2hhcmFjdGVyaXN0aWMgd2hldGhlciBub2RlcyBhcmUgc2ltaWxhci9ob21vZ2Vub3VzDQpmaG9tb21hdCA8LSBmdW5jdGlvbih4KSB7DQogICAgIyB4IGlzIGEgdmVjdG9yIG9mIG5vZGUtY292YXJpYXRlDQogICAgeG1hdCA8LSBtYXRyaXgoeCwgbnJvdyA9IGxlbmd0aCh4KSwgbmNvbCA9IGxlbmd0aCh4KSkNCiAgICB4bWF0dCA8LSB0KHhtYXQpDQogICAgeGhvbW8gPC0geG1hdCA9PSB4bWF0dA0KICAgIHJldHVybih4aG9tbykNCn0NCg0KIyBhIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSBhbGwgdmFsaWQgZHlhZHMuDQpmbmR5YWRzIDwtIGZ1bmN0aW9uKHgpIHsNCiAgICBkaWFnKHgpIDwtIE5BDQogICAgeFt4ID09IDEwXSA8LSBOQQ0KICAgIChzdW0oKHggPT0gMSB8IHggPT0gMCksIG5hLnJtID0gVCkpDQp9DQoNCiMgYSBmdW5jdGlvbiB0byBjYWxjdWxhdGUgYWxsIHZhbGlkIGludHJhZ3JvdXBkeWFkcy4NCmZuZHlhZHMyIDwtIGZ1bmN0aW9uKHgsIEEpIHsNCiAgICBkaWFnKHgpIDwtIE5BDQogICAgeFt4ID09IDEwXSA8LSBOQQ0KICAgIGRpYWcoQSkgPC0gTkENCiAgICAoc3VtKCh4ID09IDEgfCB4ID09IDApICYgQSA9PSAxLCBuYS5ybSA9IFQpKQ0KfQ0KDQoNCmZzY29sbmV0IDwtIGZ1bmN0aW9uKG5ldHdvcmssIGNjb3Zhcikgew0KICAgICMgQ2FsY3VsYXRlIGNvbGVtYW4gb24gbmV0d29yayBsZXZlbDoNCiAgICAjIGh0dHBzOi8vcmVhZGVyLmVsc2V2aWVyLmNvbS9yZWFkZXIvc2QvcGlpL1MwMzc4ODczMzE0MDAwMjM5P3Rva2VuPUE0MkY5OUZGNkUyQjc1MDQzNkREMkNCMERCN0IxRjQxQkRFQzE2MDUyQTQ1NjgzQzAyNjQ0REFGODgyMTVBMzM3OTYzNkIyQUExOTdCNjU5NDFENjM3M0U5RTJFRTQxMw0KICAgIA0KICAgIGZob21vbWF0IDwtIGZ1bmN0aW9uKHgpIHsNCiAgICAgICAgeG1hdCA8LSBtYXRyaXgoeCwgbnJvdyA9IGxlbmd0aCh4KSwgbmNvbCA9IGxlbmd0aCh4KSkNCiAgICAgICAgeG1hdHQgPC0gdCh4bWF0KQ0KICAgICAgICB4aG9tbyA8LSB4bWF0ID09IHhtYXR0DQogICAgICAgIHJldHVybih4aG9tbykNCiAgICB9DQogICAgDQogICAgZnN1bWludHJhIDwtIGZ1bmN0aW9uKHgsIEEpIHsNCiAgICAgICAgIyBBIGlzIG1hdHJpeCBpbmRpY2F0aW5nIHdoZXRoZXIgbm9kZXMgY29uc3RpdHV0aW5nIGR5YWQgaGF2ZSBzYW1lIGNoYXJhY3RlcmlzdGljcw0KICAgICAgICBkaWFnKHgpIDwtIE5BDQogICAgICAgIHhbeCA9PSAxMF0gPC0gTkENCiAgICAgICAgZGlhZyhBKSA8LSBOQQ0KICAgICAgICBzdW0oeCA9PSAxICYgQSA9PSAxLCBuYS5ybSA9IFQpDQogICAgfQ0KICAgIA0KICAgICMgZXhwZWNhdGlvbiB3Kj1zdW1fZyBzdW1faSAobmkoKG5nLTEpLyhOLTEpKSkNCiAgICBuZXR3b3JrW25ldHdvcmsgPT0gMTBdIDwtIE5BDQogICAgbmkgPC0gcm93U3VtcyhuZXR3b3JrLCBuYS5ybSA9IFQpDQogICAgbmcgPC0gTkENCiAgICBmb3IgKGkgaW4gMTpsZW5ndGgoY2NvdmFyKSkgew0KICAgICAgICBuZ1tpXSA8LSB0YWJsZShjY292YXIpW3Jvd25hbWVzKHRhYmxlKGNjb3ZhcikpID09IGNjb3ZhcltpXV0NCiAgICB9DQogICAgTiA8LSBsZW5ndGgoY2NvdmFyKQ0KICAgIHdleHAgPC0gc3VtKG5pICogKChuZyAtIDEpLyhOIC0gMSkpLCBuYS5ybSA9IFQpDQogICAgDQogICAgIyB3Z2cxIGhvdyBtYW55IGludHJhZ3JvdXAgdGllcw0KICAgIHcgPC0gZnN1bWludHJhKG5ldHdvcmssIGZob21vbWF0KGNjb3ZhcikpDQogICAgDQogICAgU2NvbF9uZXQgPC0gaWZlbHNlKHcgPj0gd2V4cCwgKHcgLSB3ZXhwKS8oc3VtKG5pLCBuYS5ybSA9IFQpIC0gd2V4cCksICh3IC0gd2V4cCkvd2V4cCkNCiAgICByZXR1cm4oU2NvbF9uZXQpDQp9DQoNCmZNb3Jhbi5JIDwtIGZ1bmN0aW9uKHgsIHdlaWdodCwgc2NhbGVkID0gRkFMU0UsIG5hLnJtID0gRkFMU0UsIGFsdGVybmF0aXZlID0gInR3by5zaWRlZCIsIHJvd3N0YW5kYXJkaXplID0gVFJVRSkgew0KICAgIGlmIChyb3dzdGFuZGFyZGl6ZSkgew0KICAgICAgICBpZiAoZGltKHdlaWdodClbMV0gIT0gZGltKHdlaWdodClbMl0pIA0KICAgICAgICAgICAgc3RvcCgiJ3dlaWdodCcgbXVzdCBiZSBhIHNxdWFyZSBtYXRyaXgiKQ0KICAgICAgICBuIDwtIGxlbmd0aCh4KQ0KICAgICAgICBpZiAoZGltKHdlaWdodClbMV0gIT0gbikgDQogICAgICAgICAgICBzdG9wKCInd2VpZ2h0JyBtdXN0IGhhdmUgYXMgbWFueSByb3dzIGFzIG9ic2VydmF0aW9ucyBpbiAneCciKQ0KICAgICAgICBlaSA8LSAtMS8obiAtIDEpDQogICAgICAgIG5hcyA8LSBpcy5uYSh4KQ0KICAgICAgICBpZiAoYW55KG5hcykpIHsNCiAgICAgICAgICAgIGlmIChuYS5ybSkgew0KICAgICAgICAgICAgICAgIHggPC0geFshbmFzXQ0KICAgICAgICAgICAgICAgIG4gPC0gbGVuZ3RoKHgpDQogICAgICAgICAgICAgICAgd2VpZ2h0IDwtIHdlaWdodFshbmFzLCAhbmFzXQ0KICAgICAgICAgICAgfSBlbHNlIHsNCiAgICAgICAgICAgICAgICB3YXJuaW5nKCIneCcgaGFzIG1pc3NpbmcgdmFsdWVzOiBtYXliZSB5b3Ugd2FudGVkIHRvIHNldCBuYS5ybSA9IFRSVUU/IikNCiAgICAgICAgICAgICAgICByZXR1cm4obGlzdChvYnNlcnZlZCA9IE5BLCBleHBlY3RlZCA9IGVpLCBzZCA9IE5BLCBwLnZhbHVlID0gTkEpKQ0KICAgICAgICAgICAgfQ0KICAgICAgICB9DQogICAgICAgIFJPV1NVTSA8LSByb3dTdW1zKHdlaWdodCkNCiAgICAgICAgUk9XU1VNW1JPV1NVTSA9PSAwXSA8LSAxDQogICAgICAgIHdlaWdodCA8LSB3ZWlnaHQvUk9XU1VNDQogICAgICAgIHMgPC0gc3VtKHdlaWdodCkNCiAgICAgICAgbSA8LSBtZWFuKHgpDQogICAgICAgIHkgPC0geCAtIG0NCiAgICAgICAgY3YgPC0gc3VtKHdlaWdodCAqIHkgJW8lIHkpDQogICAgICAgIHYgPC0gc3VtKHleMikNCiAgICAgICAgb2JzIDwtIChuL3MpICogKGN2L3YpDQogICAgICAgIGlmIChzY2FsZWQpIHsNCiAgICAgICAgICAgIGkubWF4IDwtIChuL3MpICogKHNkKHJvd1N1bXMod2VpZ2h0KSAqIHkpL3NxcnQodi8obiAtIDEpKSkNCiAgICAgICAgICAgIG9icyA8LSBvYnMvaS5tYXgNCiAgICAgICAgfQ0KICAgICAgICBTMSA8LSAwLjUgKiBzdW0oKHdlaWdodCArIHQod2VpZ2h0KSleMikNCiAgICAgICAgUzIgPC0gc3VtKChhcHBseSh3ZWlnaHQsIDEsIHN1bSkgKyBhcHBseSh3ZWlnaHQsIDIsIHN1bSkpXjIpDQogICAgICAgIHMuc3EgPC0gc14yDQogICAgICAgIGsgPC0gKHN1bSh5XjQpL24pLyh2L24pXjINCiAgICAgICAgc2RpIDwtIHNxcnQoKG4gKiAoKG5eMiAtIDMgKiBuICsgMykgKiBTMSAtIG4gKiBTMiArIDMgKiBzLnNxKSAtIGsgKiAobiAqIChuIC0gMSkgKiBTMSAtIDIgKiBuICogDQogICAgICAgICAgICBTMiArIDYgKiBzLnNxKSkvKChuIC0gMSkgKiAobiAtIDIpICogKG4gLSAzKSAqIHMuc3EpIC0gMS8oKG4gLSAxKV4yKSkNCiAgICAgICAgYWx0ZXJuYXRpdmUgPC0gbWF0Y2guYXJnKGFsdGVybmF0aXZlLCBjKCJ0d28uc2lkZWQiLCAibGVzcyIsICJncmVhdGVyIikpDQogICAgICAgIHB2IDwtIHBub3JtKG9icywgbWVhbiA9IGVpLCBzZCA9IHNkaSkNCiAgICAgICAgaWYgKGFsdGVybmF0aXZlID09ICJ0d28uc2lkZWQiKSANCiAgICAgICAgICAgIHB2IDwtIGlmIChvYnMgPD0gZWkpIA0KICAgICAgICAgICAgICAgIDIgKiBwdiBlbHNlIDIgKiAoMSAtIHB2KQ0KICAgICAgICBpZiAoYWx0ZXJuYXRpdmUgPT0gImdyZWF0ZXIiKSANCiAgICAgICAgICAgIHB2IDwtIDEgLSBwdg0KICAgICAgICBsaXN0KG9ic2VydmVkID0gb2JzLCBleHBlY3RlZCA9IGVpLCBzZCA9IHNkaSwgcC52YWx1ZSA9IHB2KQ0KICAgIH0gZWxzZSB7DQogICAgICAgIGlmIChkaW0od2VpZ2h0KVsxXSAhPSBkaW0od2VpZ2h0KVsyXSkgDQogICAgICAgICAgICBzdG9wKCInd2VpZ2h0JyBtdXN0IGJlIGEgc3F1YXJlIG1hdHJpeCIpDQogICAgICAgIG4gPC0gbGVuZ3RoKHgpDQogICAgICAgIGlmIChkaW0od2VpZ2h0KVsxXSAhPSBuKSANCiAgICAgICAgICAgIHN0b3AoIid3ZWlnaHQnIG11c3QgaGF2ZSBhcyBtYW55IHJvd3MgYXMgb2JzZXJ2YXRpb25zIGluICd4JyIpDQogICAgICAgIGVpIDwtIC0xLyhuIC0gMSkNCiAgICAgICAgbmFzIDwtIGlzLm5hKHgpDQogICAgICAgIGlmIChhbnkobmFzKSkgew0KICAgICAgICAgICAgaWYgKG5hLnJtKSB7DQogICAgICAgICAgICAgICAgeCA8LSB4WyFuYXNdDQogICAgICAgICAgICAgICAgbiA8LSBsZW5ndGgoeCkNCiAgICAgICAgICAgICAgICB3ZWlnaHQgPC0gd2VpZ2h0WyFuYXMsICFuYXNdDQogICAgICAgICAgICB9IGVsc2Ugew0KICAgICAgICAgICAgICAgIHdhcm5pbmcoIid4JyBoYXMgbWlzc2luZyB2YWx1ZXM6IG1heWJlIHlvdSB3YW50ZWQgdG8gc2V0IG5hLnJtID0gVFJVRT8iKQ0KICAgICAgICAgICAgICAgIHJldHVybihsaXN0KG9ic2VydmVkID0gTkEsIGV4cGVjdGVkID0gZWksIHNkID0gTkEsIHAudmFsdWUgPSBOQSkpDQogICAgICAgICAgICB9DQogICAgICAgIH0NCiAgICAgICAgIyBST1dTVU0gPC0gcm93U3Vtcyh3ZWlnaHQpIFJPV1NVTVtST1dTVU0gPT0gMF0gPC0gMSB3ZWlnaHQgPC0gd2VpZ2h0L1JPV1NVTQ0KICAgICAgICBzIDwtIHN1bSh3ZWlnaHQpDQogICAgICAgIG0gPC0gbWVhbih4KQ0KICAgICAgICB5IDwtIHggLSBtDQogICAgICAgIGN2IDwtIHN1bSh3ZWlnaHQgKiB5ICVvJSB5KQ0KICAgICAgICB2IDwtIHN1bSh5XjIpDQogICAgICAgIG9icyA8LSAobi9zKSAqIChjdi92KQ0KICAgICAgICBpZiAoc2NhbGVkKSB7DQogICAgICAgICAgICBpLm1heCA8LSAobi9zKSAqIChzZChyb3dTdW1zKHdlaWdodCkgKiB5KS9zcXJ0KHYvKG4gLSAxKSkpDQogICAgICAgICAgICBvYnMgPC0gb2JzL2kubWF4DQogICAgICAgIH0NCiAgICAgICAgUzEgPC0gMC41ICogc3VtKCh3ZWlnaHQgKyB0KHdlaWdodCkpXjIpDQogICAgICAgIFMyIDwtIHN1bSgoYXBwbHkod2VpZ2h0LCAxLCBzdW0pICsgYXBwbHkod2VpZ2h0LCAyLCBzdW0pKV4yKQ0KICAgICAgICBzLnNxIDwtIHNeMg0KICAgICAgICBrIDwtIChzdW0oeV40KS9uKS8odi9uKV4yDQogICAgICAgIHNkaSA8LSBzcXJ0KChuICogKChuXjIgLSAzICogbiArIDMpICogUzEgLSBuICogUzIgKyAzICogcy5zcSkgLSBrICogKG4gKiAobiAtIDEpICogUzEgLSAyICogbiAqIA0KICAgICAgICAgICAgUzIgKyA2ICogcy5zcSkpLygobiAtIDEpICogKG4gLSAyKSAqIChuIC0gMykgKiBzLnNxKSAtIDEvKChuIC0gMSleMikpDQogICAgICAgIGFsdGVybmF0aXZlIDwtIG1hdGNoLmFyZyhhbHRlcm5hdGl2ZSwgYygidHdvLnNpZGVkIiwgImxlc3MiLCAiZ3JlYXRlciIpKQ0KICAgICAgICBwdiA8LSBwbm9ybShvYnMsIG1lYW4gPSBlaSwgc2QgPSBzZGkpDQogICAgICAgIGlmIChhbHRlcm5hdGl2ZSA9PSAidHdvLnNpZGVkIikgDQogICAgICAgICAgICBwdiA8LSBpZiAob2JzIDw9IGVpKSANCiAgICAgICAgICAgICAgICAyICogcHYgZWxzZSAyICogKDEgLSBwdikNCiAgICAgICAgaWYgKGFsdGVybmF0aXZlID09ICJncmVhdGVyIikgDQogICAgICAgICAgICBwdiA8LSAxIC0gcHYNCiAgICAgICAgbGlzdChvYnNlcnZlZCA9IG9icywgZXhwZWN0ZWQgPSBlaSwgc2QgPSBzZGksIHAudmFsdWUgPSBwdikNCiAgICB9DQogICAgDQogICAgDQp9DQpgYGANCg0KPGJyPg0KDQojIyBsb2FkIGRhdGEgb2JqZWN0cyANCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIGxvYWQgdGhlIFJTaWVuYSBvYmplY3RzDQpsb2FkKCJjbHViZGF0YV9yc2llbmFfZnJlcS5SRGF0YSIpDQpsb2FkKCJjbHViZGF0YV9yc2llbmFfdm9sLlJEYXRhIikNCmxvYWQoImNsdWJkYXRhLlJEYXRhIikNCg0KYGBgDQoNCg0KDQotLS0tDQoNCg0KPGJyPg0KDQoNCiMgRGVzY3JpcHRpdmVzIHRhYmxlDQoNClJlcGxpY2F0ZSB0aGUgZGVzY3JpcHRpdmVzIHRhYmxlOg0KDQpgYGB7ciBldmFsPVR9DQp0YWIgPC0gbWF0cml4KG5yb3c9MTYsIG5jb2w9NSkNCnJvd25hbWVzKHRhYikgPC0gYygiRGVuc2l0eSIsICJBdmVyYWdlIGRlZ3JlZSIsICJSZWNpcHJvY2l0eSBpbmRleCIsICJHbG9iYWwgdHJhbnNpdGl2aXR5IGluZGV4IiwgIkphY2NhcmQgaW5kZXgiLCAiR2VuZGVyIHNlZ3JlZ2F0aW9uIiwgIk5ldHdvcmsgYXV0b2NvcnJlbGF0aW9uIiwgIkRpcmVjdCB0aWVzIiwgIkZyZXF1ZW5jeSAocGVyIHdlZWspIiwgIkhvdXJzIChwZXIgd2VlaykiLCAiRGlzdGFuY2UgZGVjYXkiLCAiRnJlcXVlbmN5IChwZXIgd2VlaykiLCAiSG91cnMgKHBlciB3ZWVrKSIsICJXZWVrbHkgcnVubmluZyBmcmVxdWVuY3kgYXQgdDEiLCAiV2Vla2x5IHJ1bm5pbmcgaG91cnMgYXQgdDEiLCAiTWFsZSBzZXggKCUpIiApDQpjb2xuYW1lcyh0YWIpIDwtIGMoIkNsdWIgMSIsIkNsdWIgMiIsICJDbHViIDMiLCAiQ2x1YiA0IiwgIkNsdWIgNSIpDQoNCmZvciAoaSBpbiAxOjUpIHsgIyBmb3IgZWFjaCBjbHViDQogICMgZGVuc2l0aWVzIG92ZXIgdGltZQ0KICBkZW5zaXRpZXMgPC0gYXMubnVtZXJpYyhzdHJzcGxpdChzdWJzdHJpbmcoY2FwdHVyZS5vdXRwdXQoY2x1YmRhdGFfcnNpZW5hX2ZyZXFbW2ldXSlbMTJdLCAyMCksICIgIilbWzFdXSkNCiAgIyBtZWFuIChzdGFuZGFyZCBkZXZpYXRpb24pDQogIHRhYlsxLCBpXSA8LSBwYXN0ZShhcy5jaGFyYWN0ZXIocm91bmQobWVhbihkZW5zaXRpZXMpLCAzKSksICIgKCIsIGFzLmNoYXJhY3Rlcihyb3VuZChzZChkZW5zaXRpZXMpLCAzKSksICIpIiwgc2VwPSIiKQ0KfQ0KDQoNCiMgQXZlcmFnZSBkZWdyZWVzDQpmb3IgKGkgaW4gMTo1KSB7ICMgZm9yIGVhY2ggY2x1Yg0KICAjIGNhbGN1bGF0ZSBhdmVyYWdlIG91dC1kZWdyZWUgb3ZlciB0aW1lDQogIGQgPC0gdmVjdG9yICgpDQogIGZvciAodCBpbiAxOjEyKSB7DQogICAgZFtbdF1dIDwtIG1lYW4oaWdyYXBoOjpkZWdyZWUoaWdyYXBoOjpncmFwaF9mcm9tX2FkamFjZW5jeV9tYXRyaXgoY2x1YmRhdGFfcnNpZW5hX2ZyZXFbW2ldXSRkZXB2YXJzJGt1ZG9uZXRbLCx0XSksIG1vZGUgPSAib3V0IikpDQogICAgDQogIH0NCiAgIyBtZWFuIChzdGFuZGFyZCBkZXZpYXRpb24pDQogIHRhYlsyLCBpXSA8LSBwYXN0ZShhcy5jaGFyYWN0ZXIocm91bmQobWVhbihkKSwgMykpLCAiICgiLCBhcy5jaGFyYWN0ZXIocm91bmQoc2QoZCksIDMpKSwgIikiLCBzZXA9IiIpDQp9DQoNCg0KIyBSZWNpcHJvY2l0eSBpbmRleA0KZm9yIChpIGluIDE6NSkgeyAjIGZvciBlYWNoIGNsdWINCiAgIyBjYWxjdWxhdGUgcmVjaXByb2NpdHkgaW5kZXggb3ZlciB0aW1lDQogIHJpIDwtIHZlY3RvciAoKQ0KICANCiAgZm9yICh0IGluIDE6MTIpIHsNCiAgICBtdXQgPC0gaWdyYXBoOjpkeWFkLmNlbnN1cyhpZ3JhcGg6OmdyYXBoX2Zyb21fYWRqYWNlbmN5X21hdHJpeChjbHViZGF0YV9yc2llbmFfZnJlcVtbaV1dJGRlcHZhcnMka3Vkb25ldFssLHRdKSlbWzFdXQ0KICAgIG5vbm11dCA8LSBpZ3JhcGg6OmR5YWQuY2Vuc3VzKGlncmFwaDo6Z3JhcGhfZnJvbV9hZGphY2VuY3lfbWF0cml4KGNsdWJkYXRhX3JzaWVuYV9mcmVxW1tpXV0kZGVwdmFycyRrdWRvbmV0WywsdF0pKVtbMl1dDQogICAgcmlbW3RdXSA8LSBtdXQvKG11dCtub25tdXQpDQogICAgDQogIH0NCiAgdGFiWzMsIGldIDwtIHBhc3RlKGFzLmNoYXJhY3Rlcihyb3VuZChtZWFuKHJpKSwgMykpLCAiICgiLCBhcy5jaGFyYWN0ZXIocm91bmQoc2QocmkpLCAzKSksICIpIiwgc2VwPSIiKQ0KfQ0KDQojIFRyYW5zaXRpdml0eSBpbmRleA0KZm9yIChpIGluIDE6NSkgeyAjIGZvciBlYWNoIGNsdWINCiAgIyBjYWxjdWxhdGUgdHJhbnNpdGl2aXR5IGluZGV4IG92ZXIgdGltZQ0KICB0aSA8LSB2ZWN0b3IgKCkNCiAgDQogIGZvciAodCBpbiAxOjEyKSB7DQogICAgdGlbW3RdXSA8LSBpZ3JhcGg6OnRyYW5zaXRpdml0eShpZ3JhcGg6OmdyYXBoX2Zyb21fYWRqYWNlbmN5X21hdHJpeChjbHViZGF0YV9yc2llbmFfZnJlcVtbaV1dJGRlcHZhcnMka3Vkb25ldFssLHRdKSkNCiAgICANCiAgfQ0KICB0YWJbNCwgaV0gPC0gcGFzdGUoYXMuY2hhcmFjdGVyKHJvdW5kKG1lYW4odGkpLCAzKSksICIgKCIsIGFzLmNoYXJhY3Rlcihyb3VuZChzZCh0aSksIDMpKSwgIikiLCBzZXA9IiIpDQp9DQoNCiMgSmFjY2FyZCBpbmRleA0KDQojIEdlbmRlciBzZWdyZWdhdGlvbiANCmZvciAoaSBpbiAxOjUpIHsgIyBmb3IgZWFjaCBjbHViDQogICMgY2FsY3VsYXRlIGNvbGVtYW4ncyBob21vcGhpbHkgaW5kZXggb3ZlciB0aW1lDQogIGNvbCA8LSB2ZWN0b3IgKCkNCiAgDQogIGZvciAodCBpbiAxOjEyKSB7DQogICAgY29sW1t0XV0gPC0gZnNjb2xuZXQoY2x1YmRhdGFfcnNpZW5hX2ZyZXFbW2ldXSRkZXB2YXJzJGt1ZG9uZXRbLCx0XSwgY2x1YmRhdGFfcnNpZW5hX2ZyZXFbW2ldXSRjQ292YXJzJGdlbmRlcikNCiAgfQ0KICB0YWJbNiwgaV0gPC0gcGFzdGUoYXMuY2hhcmFjdGVyKHJvdW5kKG1lYW4oY29sKSwgMykpLCAiICgiLCBhcy5jaGFyYWN0ZXIocm91bmQoc2QoY29sKSwgMykpLCAiKSIsIHNlcD0iIikNCn0NCg0KIyBOZXR3b3JrIGF1dG9jb3JyZWxhdGlvbg0KIyBmcmVxdWVuY3kNCmZvciAoaSBpbiAxOjUpIHsgIyBmb3IgZWFjaCBjbHViDQogICMgd2UgY2FsY3VsYXRlIG1vcmFuJ3MgaQ0KICAjIGJvdGggd2l0aCBkaXJlY3QgdGllcyBhbmQgaW5kaXJlY3QgdGllcyBhcyB3ZWxsDQogICMgZm9yIGZyZXF1ZW5jeSBhbmQgdm9sdW1lDQogIG1vcl9mcmVxX2RpciA8LSB2ZWN0b3IgKCkNCiAgbW9yX2ZyZXFfZGlzdCA8LSB2ZWN0b3IgKCkNCiAgbW9yX3ZvbF9kaXIgPC0gdmVjdG9yICgpDQogIG1vcl92b2xfZGlzdCA8LSB2ZWN0b3IgKCkNCiAgDQogIGZvciAodCBpbiAxOjEyKSB7ICAgICAgICAgDQogICAgZnJlcSA8LSBjbHViZGF0YVtbaV1dJGZyZXFfcnVuWywsdF0gICAjIGF0dHJpYnV0ZXMgYXQgdGltZSB0DQogICAgdm9sIDwtIGNsdWJkYXRhW1tpXV0kdGltZV9ydW5bLCx0XSAgIA0KICAgIGtuZXQgPC0gbmV0d29yazo6YXMubmV0d29yayhjbHViZGF0YV9yc2llbmFfZnJlcVtbaV1dJGRlcHZhcnMka3Vkb25ldFssLHRdKSAjIG5ldCBhIHRpbWUgdA0KICAgIGdlb2Rpc3RhbmNlcyA8LSBzbmE6Omdlb2Rpc3Qoa25ldCwgY291bnQucGF0aHM9VCkgIyBnZW9kZXNpYyBkaXN0YW5jZQ0KICAgIGdlb2Rpc3RhbmNlcyA8LSBnZW9kaXN0YW5jZXMkZ2Rpc3QgDQogICAgZGlhZyhnZW9kaXN0YW5jZXMpIDwtIEluZg0KICAgIHdlaWdodHMxIDwtIGdlb2Rpc3RhbmNlcyA9PSAxICAgICAgICAgICAgICAgIyBvbmx5IGRpcmVjdCB0aWVzDQogICAgd2VpZ2h0czIgPC0gZXhwKC1nZW9kaXN0YW5jZXMpICAgICAgICAgICAgICAjIGFsc28gaW5kaXJlY3QNCiAgICANCiAgICBtb3JfZnJlcV9kaXJbW3RdXSA8LSBmTW9yYW4uSShmcmVxLCBzY2FsZWQgPSBGQUxTRSwgIyBjYWxjdWxhdGUgbW9yYW4gb24gZGlyZWN0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gd2VpZ2h0czEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hLnJtID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93c3RhbmRhcmRpemUgPSBGQUxTRSlbWzFdXSANCiAgICBtb3JfZnJlcV9kaXN0W1t0XV0gPC0gZk1vcmFuLkkoZnJlcSwgc2NhbGVkID0gRkFMU0UsICMgYW5kIGluZGlyZWN0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IHdlaWdodHMyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEucm0gPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93c3RhbmRhcmRpemUgPSBGQUxTRSlbWzFdXQ0KICAgIG1vcl92b2xfZGlyW1t0XV0gPC0gZk1vcmFuLkkodm9sLCBzY2FsZWQgPSBGQUxTRSwgIyBjYWxjdWxhdGUgbW9yYW4gb24gZGlyZWN0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHQgPSB3ZWlnaHRzMSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5ybSA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93c3RhbmRhcmRpemUgPSBGQUxTRSlbWzFdXSANCiAgICBtb3Jfdm9sX2Rpc3RbW3RdXSA8LSBmTW9yYW4uSSh2b2wsIHNjYWxlZCA9IEZBTFNFLCAjIGFuZCBpbmRpcmVjdA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IHdlaWdodHMyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5ybSA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd3N0YW5kYXJkaXplID0gRkFMU0UpW1sxXV0NCiAgfQ0KICB0YWJbOSwgaV0gPC0gcGFzdGUoYXMuY2hhcmFjdGVyKHJvdW5kKG1lYW4obW9yX2ZyZXFfZGlyKSwgMykpLCAiICgiLCBhcy5jaGFyYWN0ZXIocm91bmQoc2QobW9yX2ZyZXFfZGlyKSwgMykpLCAiKSIsIHNlcD0iIikNCiAgdGFiWzEwLCBpXSA8LSBwYXN0ZShhcy5jaGFyYWN0ZXIocm91bmQobWVhbihtb3Jfdm9sX2RpciksIDMpKSwgIiAoIiwgYXMuY2hhcmFjdGVyKHJvdW5kKHNkKG1vcl92b2xfZGlyKSwgMykpLCAiKSIsIHNlcD0iIikNCiAgdGFiWzEyLCBpXSA8LSBwYXN0ZShhcy5jaGFyYWN0ZXIocm91bmQobWVhbihtb3JfZnJlcV9kaXN0KSwgMykpLCAiICgiLCBhcy5jaGFyYWN0ZXIocm91bmQoc2QobW9yX2ZyZXFfZGlzdCksIDMpKSwgIikiLCBzZXA9IiIpDQogIHRhYlsxMywgaV0gPC0gcGFzdGUoYXMuY2hhcmFjdGVyKHJvdW5kKG1lYW4obW9yX3ZvbF9kaXN0KSwgMykpLCAiICgiLCBhcy5jaGFyYWN0ZXIocm91bmQoc2QobW9yX3ZvbF9kaXN0KSwgMykpLCAiKSIsIHNlcD0iIikNCn0NCg0KIyBiZWhhdmlvciBhdCBiYXNlbGluZSAodzEpDQpmb3IgKGkgaW4gMTo1KSB7ICMgZm9yIGVhY2ggY2x1Yg0KICAjIHJ1bm5pbmcgYXR0cmlidXRlcyBhdCB3MQ0KICB0YWJbMTQsIGldIDwtIHBhc3RlKGFzLmNoYXJhY3Rlcihyb3VuZChtZWFuKGNsdWJkYXRhW1tpXV0kZnJlcV9ydW5bLCwxXSwgbmEucm09VCksIDMpKSwgIiAoIiwgYXMuY2hhcmFjdGVyKHJvdW5kKHNkKGNsdWJkYXRhW1tpXV0kZnJlcV9ydW5bLCwxXSwgbmEucm09VCksIDMpKSwgIikiLCBzZXA9IiIpDQogIHRhYlsxNSwgaV0gPC0gcGFzdGUoYXMuY2hhcmFjdGVyKHJvdW5kKG1lYW4oY2x1YmRhdGFbW2ldXSR0aW1lX3J1blssLDFdLCBuYS5ybT1UKSwgMykpLCAiICgiLCBhcy5jaGFyYWN0ZXIocm91bmQoc2QoY2x1YmRhdGFbW2ldXSR0aW1lX3J1blssLDFdLCBuYS5ybT1UKSwgMykpLCAiKSIsIHNlcD0iIikNCn0NCiMgbWFsZSBzZXggKCUpDQpmb3IgKGkgaW4gMTo1KSB7ICMgZm9yIGVhY2ggY2x1Yg0KICAjIHBlcmNlbnRhZ2UgbWFsZQ0KICB0YWJbMTYsIGldIDwtIHJvdW5kKChsZW5ndGgoY2x1YmRhdGFbW2ldXVtjbHViZGF0YVtbaV1dJG1hbGU9PTFdKS9jbHViZGF0YVtbaV1dJG5ldHNpemUpKjEwMCwgMykNCn0NCg0KIyBmb3Igbm93IHdlIGluY2x1ZGUgamFjY2FyZCBtYW51YWxseTsgYnV0IHdlIHdhbnQgdG8gdXNlIGEgZnVuY3Rpb24gdG8gY2FsY3VsYXRlIHRoZXNlIGlkZWFsbHkuDQp0YWJbNSwgXSA8LSBjKCIwLjg1MCAoMC4wNjApIiwgIjAuNzUgKDAuMDMwKSIsICIwLjUwMCAoMC4wNjApIiwgIjAuODAwICgwLjA5MCkiLCAiMC42OTAgKDAuMDIwKSIpDQoNCg0KDQoNCmBgYCANCiAgDQoNCg0KPGI+DQoNCiMgVGFibGUNCg0KYGBge3IgdGFibGUsIGVjaG89RiwgaW5jbHVkZT1UfQ0KDQpvcHRpb25zKGtuaXRyLmthYmxlLk5BID0gJycpDQoNCmtuaXRyOjprYWJsZSh0YWIsIGRpZ2l0cz0zLCAiaHRtbCIsIGNhcHRpb249IlRhYmxlIDEuIERlc2NyaXB0aXZlIHN0YXRpc3RpY3Mgb2YgU3RyYXZhIGNsdWJzIikgJT4lIA0KICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpKSAlPiUNCiAgZm9vdG5vdGUoZ2VuZXJhbCA9ICJGb3IgbmV0d29yayBtZWFzdXJlcywgbWVhbiB2YWx1ZXMgb3ZlciB0aW1lIGFyZSBnaXZlbiB3aXRoIHN0YW5kYXJkIGRldmlhdGlvbnMgaW4gcGFyZW50aGVzZXMuIEZvciBiZWhhdmlvcmFsIGF0dHJpYnV0ZXMsIG1lYW5zIGFuZCBzdGFuZGFyZCBkZXZpYXRpb25zIGF0IGJhc2VsaW5lICh0MTogRGVjZW1iZXIpIGFyZSBnaXZlbi4gRGVncmVlcyByZWZsZWN0IGt1ZG9zIHRpZXMsIHdpdGggYXdhcmRpbmcgMSBvciBtb3JlIGt1ZG9zIGNvbnN0aXR1dGluZyB0aGUgcHJlc2VuY2Ugb2YgYSB0aWUuIFRoZSByZWNpcHJvY2l0eSBpbmRleCBpcyB0aGUgcHJvcG9ydGlvbiBvZiBrdWRvcyB0aWVzIHRoYXQgd2VyZSByZWNpcHJvY2F0ZWQ7IHRoZSB0cmFuc2l0aXZpdHkgaW5kZXggaXMgdGhlIG51bWJlciBvZiBjbG9zZWQgdHJpcGxldHMgb3ZlciB0aGUgdG90YWwgbnVtYmVyIG9mIHRyaXBsZXRzIChib3RoIG9wZW4gYW5kIGNsb3NlZCkuIiwNCiAgICAgICAgICAgYWxwaGFiZXQgID0gYygiVGhlIEphY2NhcmQgc2ltaWxhcml0eSBpbmRleCBtZWFzdXJlcyB0aGUgZXh0ZW50IG9mIHRpZSBjaGFuZ2UgYmV0d2VlbiBjb25zZWN1dGl2ZSB3YXZlcyIsICJUaGUgZ2VuZGVyIHNlZ3JlZ2F0aW9uIG1lYXN1cmUgdXNlZCBpcyBDb2xlbWFu4oCZcyBob21vcGhpbHkgaW5kZXguIiwgIlRoZSBuZXR3b3JrIGF1dG9jb3JyZWxhdGlvbiBtZWFzdXJlIHVzZWQgaXMgTW9yYW7igJlzIEkuIFdlIGNhbGN1bGF0ZWQgTW9yYW7igJlzIEkgZm9yIGRpcmVjdCB0aWVzIChpLmUuIHdpdGggdW5kaXJlY3RlZCBwYXRoIGxlbmd0aCAxKSBhbmQgZm9yIGFsbCB0aWVzIHRvIHdob20gZWdvIGlzIChkaXJlY3RseSBvciBpbmRpcmVjdGx5KSB0aWVkLCB1c2luZyBhIGRpc3RhbmNlLWRlY2F5IGZ1bmN0aW9uIGZvciBhc3NpZ25pbmcgd2VpZ2h0cy4gVG8gY29uc3RydWN0IHRoZSB3ZWlnaHQgbWF0cml4LCB3ZSBtZWFzdXJlZCB0aGUgZ2VvZGlzdGFuY2UgKGQpIGZvciBlYWNoIGR5YWQgYXMgdGhlIHNob3J0ZXN0ICh1bmRpcmVjdGVkKSBwYXRoIGxlbmd0aC4gV2UgdGhlbiB1c2VkIHRoZSBuZWdhdGl2ZSBleHBvbmVudGlhbCBkaXN0YW5jZS1kZWNheSBmdW5jdGlvbi4gV2UgZGlkIG5vdCByb3ctc3RhbmRhcmRpemUuIikpDQpgYGANCg0K


Copyright © 2021 Rob Franken