Names - Distributions of names of American babies. See Chapter 2 in Regression and Other Stories.


Load packages

library("rprojroot")
root<-has_file(".ROS-Examples-root")$make_fix_file()

Load data

allnames <- read.csv(root("Names/data","allnames_clean.csv"))
girl <- as.vector(allnames$sex)=="F"
names <- as.vector(allnames$name)
columns <- colnames(allnames)
range <- (1:length(columns))[columns=="X1931"]:(1:length(columns))[columns=="X2000"]
years <- 1931:2000
colRenorm <- function(a){
  a / matrix(colSums(a), nrow=nrow(a), ncol=ncol(a), byrow=TRUE)
}
counts <- as.matrix(allnames[,range])
counts.norm <- colRenorm(counts)
totals <- rowMeans(counts.norm)
counts.adj <- ifelse (counts==0, 2, counts)
counts.adj.norm <- colRenorm(counts.adj)/colSums(counts.adj)

Compute stats

N <- nrow(allnames)
stats.labels <- c("avg.year", "avg.pop", "max.pop", "ratio", "year.of.max.pop",
  "volatility", "slope.1931.1965", "slope.1966.2000","slope.1931.2000",
  "slope.1981.2000", "pop.2000","avg.year.2")
stats <- array(NA, c(N,length(stats.labels)))
dimnames(stats) <- list(names, stats.labels)
for (i in 1:N){
  avg.year <- sum(years*counts.norm[i,])/sum(counts.norm[i,])
  avg.year.2 <- sum(years*as.numeric(counts[i,]))/sum(as.numeric(counts[i,]))
  avg.pop <- mean(counts.norm[i,])
  max.pop <- max(counts.norm[i,])
  ratio <- max(counts.adj.norm[i,])/ min(counts.adj.norm[i,])
  year.of.max.pop <- min(years[counts.norm[i,]==max.pop])
  logcounts <- log(counts.adj.norm[i,])
  volatility <- sd(logcounts)
  M1 <- lm(logcounts ~ I(years/10), subset=years<=1965)
  M2 <- lm(logcounts ~ I(years/10), subset=years>=1966)
  M3 <- lm(logcounts ~ I(years/10))
  M4 <- lm(logcounts ~ I(years/10), subset=years>=1981)
  slope.1931.1965 <- coef(M1)[2]
  slope.1966.2000 <- coef(M2)[2]
  slope.1931.2000 <- coef(M3)[2]
  slope.1981.2000 <- coef(M4)[2]
  pop.2000 <- counts.norm[i,years==2000]
  stats[i,] <- unlist(lapply(stats.labels, get))
}

Helper function

name.subset <- function(subset, n){
  if (n==0){
    n <- length(years)
    probs.remaining <- counts.norm
    sample.1.raw <- rep(NA, n)
    for (i in 1:n){
      sample.1.raw[i] <- sample(1:N, 1, prob=ifelse(subset,probs.remaining[,i],0))
      probs.remaining[sample.1.raw[i],] <- 0
    }
  }
  else {
    sample.1.raw <- sample(1:N, n, prob=ifelse(subset,totals,0))
  }
  year.of.max.pop.1 <- stats[sample.1.raw,"year.of.max.pop"]
  sample.1 <- sample.1.raw[order(year.of.max.pop.1)]
  a <- stats[sample.1, c("year.of.max.pop","avg.year.2","max.pop","ratio","slope.1931.2000","slope.1981.2000","pop.2000")]
  return(a)
}

Helper plot function

namesplot <- function(a){
  labels <- c("Peak year", "Avg year", "Peak\npopularity", "Ratio max/min\npopularity", "Avg trend,\n1931-2000", "Avg trend,\n1981-2000", "Popularity\nin 2000")
  digits <- rep(0, 5)
  is.log <- c(FALSE, FALSE, TRUE, TRUE, FALSE, FALSE, TRUE)
  lo <- c(1930, 1930, -5, 0, -2, -2, -5)
  hi <- c(2000, 2000, -1, 4, 2, 2, -1)
  J <- ncol(a)
  n <- nrow(a)
  plot(c(-.6,J), c(-n,3), xlab="", ylab="", xaxt="n", yaxt="n", type="n", bty="n")
  for (i in seq(0,-n,-6)){
    polygon(c(-.6,J,J,-.6), rep(c(i-.5, max(i-3,-n)-.5), c(2,2)), col="gray90", border=NA)
  }
  text(-.1, -(1:n), dimnames(a)[[1]], adj=1, cex=.7)
  for (j in 1:J){
    if (is.log[j]){
      x <- log10(a[,j])
      text(c(j-.8,j-.5,j-.2), c(1,1,1), 10^seq(lo[j],hi[j],length=3), cex=.7)
    }
    else {
      x <- a[,j]
      text(c(j-.8,j-.5,j-.2), c(1,1,1), seq(lo[j],hi[j],length=3), cex=.7)
    }
    lines(c(j-.8,j-.2), c(0,0))
    segments(c(j-.8,j-.5,j-.2), c(0,0,0), c(j-.8,j-.5,j-.2), c(.2,.2,.2))
    lines(c(j-1,j-1), c(0,-n), col="gray")
    
    text(j-.5, 3, labels[j], cex=.7)
    points(j-.8 + .6*(x-lo[j])/(hi[j]-lo[j]), -(1:n), pch=20, cex=.6)
  }
}

Plot stats

girls50 <- name.subset(girl, 50)
boys50 <- name.subset(!girl, 50)
par(mar=c(1,2,1,1))
namesplot(girls50)

par(mar=c(1,2,1,1))
namesplot(boys50)

girls70 <- name.subset(girl, 0)
boys70 <- name.subset(!girl, 0)
par(mar=c(1,2,1,1))
namesplot(girls70)

par(mar=c(1,2,1,1))
namesplot(boys70)

Restrict to top 1000

top1000 <- array(NA, c(N,length(years)))
for (i in 1:length(years)){
  top1000[girl,i] <- counts[girl,i] >= rev(sort(counts[girl,i]))[1000]
  top1000[!girl,i] <- counts[!girl,i] >= rev(sort(counts[!girl,i]))[1000]
}
evertop1000 <- rowSums(top1000) > 0

girls50new <- name.subset(girl, 50)
boys50new <- name.subset(!girl, 50)
par(mar=c(1,2,1,1))
namesplot(girls50new)

par(mar=c(1,2,1,1))
namesplot(boys50new)

Add new column

avg.year.2 <- rep(NA, 50)
names50 <- row.names(girls50new)
for (i in 1:50){
  ok <- (1:N)[names==names50[i]&girl]
  avg.year.2[i] <- stats[ok,"avg.year.2"]
}
girls50new <- cbind(girls50new[,1], avg.year.2, girls50new[,2:6])
colnames(girls50new)[1] <- "year.of.max.pop"
par(mar=c(1,2,1,1))
namesplot(girls50new)

avg.year.2 <- rep(NA, 50)
names50 <- row.names(boys50new)
for (i in 1:50){
  ok <- (1:N)[names==names50[i]&!girl]
  avg.year.2[i] <- stats[ok,"avg.year.2"]
}
boys50new <- cbind(boys50new[,1], avg.year.2, boys50new[,2:6])
colnames(boys50new)[1] <- "year.of.max.pop"
par(mar=c(1,2,1,1))
namesplot(boys50new)

LS0tCnRpdGxlOiAiUmVncmVzc2lvbiBhbmQgT3RoZXIgU3RvcmllczogTmFtZXMiCmF1dGhvcjogIkFuZHJldyBHZWxtYW4sIEplbm5pZmVyIEhpbGwsIEFraSBWZWh0YXJpIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy5EYXRlKCkpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogcmVhZGFibGUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCk5hbWVzIC0gRGlzdHJpYnV0aW9ucyBvZiBuYW1lcyBvZiBBbWVyaWNhbiBiYWJpZXMuIFNlZSBDaGFwdGVyIDIgaW4KUmVncmVzc2lvbiBhbmQgT3RoZXIgU3Rvcmllcy4KCi0tLS0tLS0tLS0tLS0KCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjb21tZW50PU5BKQojIHN3aXRjaCB0aGlzIHRvIFRSVUUgdG8gc2F2ZSBmaWd1cmVzIGluIHNlcGFyYXRlIGZpbGVzCnNhdmVmaWdzIDwtIEZBTFNFCmBgYAoKIyMjIyBMb2FkIHBhY2thZ2VzCgpgYGB7ciB9CmxpYnJhcnkoInJwcm9qcm9vdCIpCnJvb3Q8LWhhc19maWxlKCIuUk9TLUV4YW1wbGVzLXJvb3QiKSRtYWtlX2ZpeF9maWxlKCkKYGBgCgojIyMjIExvYWQgZGF0YQoKYGBge3IgfQphbGxuYW1lcyA8LSByZWFkLmNzdihyb290KCJOYW1lcy9kYXRhIiwiYWxsbmFtZXNfY2xlYW4uY3N2IikpCmdpcmwgPC0gYXMudmVjdG9yKGFsbG5hbWVzJHNleCk9PSJGIgpuYW1lcyA8LSBhcy52ZWN0b3IoYWxsbmFtZXMkbmFtZSkKY29sdW1ucyA8LSBjb2xuYW1lcyhhbGxuYW1lcykKcmFuZ2UgPC0gKDE6bGVuZ3RoKGNvbHVtbnMpKVtjb2x1bW5zPT0iWDE5MzEiXTooMTpsZW5ndGgoY29sdW1ucykpW2NvbHVtbnM9PSJYMjAwMCJdCnllYXJzIDwtIDE5MzE6MjAwMApjb2xSZW5vcm0gPC0gZnVuY3Rpb24oYSl7CiAgYSAvIG1hdHJpeChjb2xTdW1zKGEpLCBucm93PW5yb3coYSksIG5jb2w9bmNvbChhKSwgYnlyb3c9VFJVRSkKfQpjb3VudHMgPC0gYXMubWF0cml4KGFsbG5hbWVzWyxyYW5nZV0pCmNvdW50cy5ub3JtIDwtIGNvbFJlbm9ybShjb3VudHMpCnRvdGFscyA8LSByb3dNZWFucyhjb3VudHMubm9ybSkKY291bnRzLmFkaiA8LSBpZmVsc2UgKGNvdW50cz09MCwgMiwgY291bnRzKQpjb3VudHMuYWRqLm5vcm0gPC0gY29sUmVub3JtKGNvdW50cy5hZGopL2NvbFN1bXMoY291bnRzLmFkaikKYGBgCgojIyMjIENvbXB1dGUgc3RhdHMKCmBgYHtyIH0KTiA8LSBucm93KGFsbG5hbWVzKQpzdGF0cy5sYWJlbHMgPC0gYygiYXZnLnllYXIiLCAiYXZnLnBvcCIsICJtYXgucG9wIiwgInJhdGlvIiwgInllYXIub2YubWF4LnBvcCIsCiAgInZvbGF0aWxpdHkiLCAic2xvcGUuMTkzMS4xOTY1IiwgInNsb3BlLjE5NjYuMjAwMCIsInNsb3BlLjE5MzEuMjAwMCIsCiAgInNsb3BlLjE5ODEuMjAwMCIsICJwb3AuMjAwMCIsImF2Zy55ZWFyLjIiKQpzdGF0cyA8LSBhcnJheShOQSwgYyhOLGxlbmd0aChzdGF0cy5sYWJlbHMpKSkKZGltbmFtZXMoc3RhdHMpIDwtIGxpc3QobmFtZXMsIHN0YXRzLmxhYmVscykKZm9yIChpIGluIDE6Til7CiAgYXZnLnllYXIgPC0gc3VtKHllYXJzKmNvdW50cy5ub3JtW2ksXSkvc3VtKGNvdW50cy5ub3JtW2ksXSkKICBhdmcueWVhci4yIDwtIHN1bSh5ZWFycyphcy5udW1lcmljKGNvdW50c1tpLF0pKS9zdW0oYXMubnVtZXJpYyhjb3VudHNbaSxdKSkKICBhdmcucG9wIDwtIG1lYW4oY291bnRzLm5vcm1baSxdKQogIG1heC5wb3AgPC0gbWF4KGNvdW50cy5ub3JtW2ksXSkKICByYXRpbyA8LSBtYXgoY291bnRzLmFkai5ub3JtW2ksXSkvIG1pbihjb3VudHMuYWRqLm5vcm1baSxdKQogIHllYXIub2YubWF4LnBvcCA8LSBtaW4oeWVhcnNbY291bnRzLm5vcm1baSxdPT1tYXgucG9wXSkKICBsb2djb3VudHMgPC0gbG9nKGNvdW50cy5hZGoubm9ybVtpLF0pCiAgdm9sYXRpbGl0eSA8LSBzZChsb2djb3VudHMpCiAgTTEgPC0gbG0obG9nY291bnRzIH4gSSh5ZWFycy8xMCksIHN1YnNldD15ZWFyczw9MTk2NSkKICBNMiA8LSBsbShsb2djb3VudHMgfiBJKHllYXJzLzEwKSwgc3Vic2V0PXllYXJzPj0xOTY2KQogIE0zIDwtIGxtKGxvZ2NvdW50cyB+IEkoeWVhcnMvMTApKQogIE00IDwtIGxtKGxvZ2NvdW50cyB+IEkoeWVhcnMvMTApLCBzdWJzZXQ9eWVhcnM+PTE5ODEpCiAgc2xvcGUuMTkzMS4xOTY1IDwtIGNvZWYoTTEpWzJdCiAgc2xvcGUuMTk2Ni4yMDAwIDwtIGNvZWYoTTIpWzJdCiAgc2xvcGUuMTkzMS4yMDAwIDwtIGNvZWYoTTMpWzJdCiAgc2xvcGUuMTk4MS4yMDAwIDwtIGNvZWYoTTQpWzJdCiAgcG9wLjIwMDAgPC0gY291bnRzLm5vcm1baSx5ZWFycz09MjAwMF0KICBzdGF0c1tpLF0gPC0gdW5saXN0KGxhcHBseShzdGF0cy5sYWJlbHMsIGdldCkpCn0KYGBgCgpIZWxwZXIgZnVuY3Rpb24KCmBgYHtyIH0KbmFtZS5zdWJzZXQgPC0gZnVuY3Rpb24oc3Vic2V0LCBuKXsKICBpZiAobj09MCl7CiAgICBuIDwtIGxlbmd0aCh5ZWFycykKICAgIHByb2JzLnJlbWFpbmluZyA8LSBjb3VudHMubm9ybQogICAgc2FtcGxlLjEucmF3IDwtIHJlcChOQSwgbikKICAgIGZvciAoaSBpbiAxOm4pewogICAgICBzYW1wbGUuMS5yYXdbaV0gPC0gc2FtcGxlKDE6TiwgMSwgcHJvYj1pZmVsc2Uoc3Vic2V0LHByb2JzLnJlbWFpbmluZ1ssaV0sMCkpCiAgICAgIHByb2JzLnJlbWFpbmluZ1tzYW1wbGUuMS5yYXdbaV0sXSA8LSAwCiAgICB9CiAgfQogIGVsc2UgewogICAgc2FtcGxlLjEucmF3IDwtIHNhbXBsZSgxOk4sIG4sIHByb2I9aWZlbHNlKHN1YnNldCx0b3RhbHMsMCkpCiAgfQogIHllYXIub2YubWF4LnBvcC4xIDwtIHN0YXRzW3NhbXBsZS4xLnJhdywieWVhci5vZi5tYXgucG9wIl0KICBzYW1wbGUuMSA8LSBzYW1wbGUuMS5yYXdbb3JkZXIoeWVhci5vZi5tYXgucG9wLjEpXQogIGEgPC0gc3RhdHNbc2FtcGxlLjEsIGMoInllYXIub2YubWF4LnBvcCIsImF2Zy55ZWFyLjIiLCJtYXgucG9wIiwicmF0aW8iLCJzbG9wZS4xOTMxLjIwMDAiLCJzbG9wZS4xOTgxLjIwMDAiLCJwb3AuMjAwMCIpXQogIHJldHVybihhKQp9CmBgYAoKSGVscGVyIHBsb3QgZnVuY3Rpb24KCmBgYHtyIH0KbmFtZXNwbG90IDwtIGZ1bmN0aW9uKGEpewogIGxhYmVscyA8LSBjKCJQZWFrIHllYXIiLCAiQXZnIHllYXIiLCAiUGVha1xucG9wdWxhcml0eSIsICJSYXRpbyBtYXgvbWluXG5wb3B1bGFyaXR5IiwgIkF2ZyB0cmVuZCxcbjE5MzEtMjAwMCIsICJBdmcgdHJlbmQsXG4xOTgxLTIwMDAiLCAiUG9wdWxhcml0eVxuaW4gMjAwMCIpCiAgZGlnaXRzIDwtIHJlcCgwLCA1KQogIGlzLmxvZyA8LSBjKEZBTFNFLCBGQUxTRSwgVFJVRSwgVFJVRSwgRkFMU0UsIEZBTFNFLCBUUlVFKQogIGxvIDwtIGMoMTkzMCwgMTkzMCwgLTUsIDAsIC0yLCAtMiwgLTUpCiAgaGkgPC0gYygyMDAwLCAyMDAwLCAtMSwgNCwgMiwgMiwgLTEpCiAgSiA8LSBuY29sKGEpCiAgbiA8LSBucm93KGEpCiAgcGxvdChjKC0uNixKKSwgYygtbiwzKSwgeGxhYj0iIiwgeWxhYj0iIiwgeGF4dD0ibiIsIHlheHQ9Im4iLCB0eXBlPSJuIiwgYnR5PSJuIikKICBmb3IgKGkgaW4gc2VxKDAsLW4sLTYpKXsKICAgIHBvbHlnb24oYygtLjYsSixKLC0uNiksIHJlcChjKGktLjUsIG1heChpLTMsLW4pLS41KSwgYygyLDIpKSwgY29sPSJncmF5OTAiLCBib3JkZXI9TkEpCiAgfQogIHRleHQoLS4xLCAtKDE6biksIGRpbW5hbWVzKGEpW1sxXV0sIGFkaj0xLCBjZXg9LjcpCiAgZm9yIChqIGluIDE6Sil7CiAgICBpZiAoaXMubG9nW2pdKXsKICAgICAgeCA8LSBsb2cxMChhWyxqXSkKICAgICAgdGV4dChjKGotLjgsai0uNSxqLS4yKSwgYygxLDEsMSksIDEwXnNlcShsb1tqXSxoaVtqXSxsZW5ndGg9MyksIGNleD0uNykKICAgIH0KICAgIGVsc2UgewogICAgICB4IDwtIGFbLGpdCiAgICAgIHRleHQoYyhqLS44LGotLjUsai0uMiksIGMoMSwxLDEpLCBzZXEobG9bal0saGlbal0sbGVuZ3RoPTMpLCBjZXg9LjcpCiAgICB9CiAgICBsaW5lcyhjKGotLjgsai0uMiksIGMoMCwwKSkKICAgIHNlZ21lbnRzKGMoai0uOCxqLS41LGotLjIpLCBjKDAsMCwwKSwgYyhqLS44LGotLjUsai0uMiksIGMoLjIsLjIsLjIpKQogICAgbGluZXMoYyhqLTEsai0xKSwgYygwLC1uKSwgY29sPSJncmF5IikKICAgIAogICAgdGV4dChqLS41LCAzLCBsYWJlbHNbal0sIGNleD0uNykKICAgIHBvaW50cyhqLS44ICsgLjYqKHgtbG9bal0pLyhoaVtqXS1sb1tqXSksIC0oMTpuKSwgcGNoPTIwLCBjZXg9LjYpCiAgfQp9CmBgYAoKIyMjIyBQbG90IHN0YXRzCgpgYGB7ciB9CmdpcmxzNTAgPC0gbmFtZS5zdWJzZXQoZ2lybCwgNTApCmJveXM1MCA8LSBuYW1lLnN1YnNldCghZ2lybCwgNTApCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIHBkZihyb290KCJOYW1lcy9maWdzIiwiZ2lybHM1MC5wZGYiKSwgaGVpZ2h0PTgsIHdpZHRoPTgpCmBgYApgYGB7ciB9CnBhcihtYXI9YygxLDIsMSwxKSkKbmFtZXNwbG90KGdpcmxzNTApCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQppZiAoc2F2ZWZpZ3MpIHBkZihyb290KCJOYW1lcy9maWdzIiwiYm95czUwLnBkZiIpLCBoZWlnaHQ9OCwgd2lkdGg9OCkKYGBgCmBgYHtyIH0KcGFyKG1hcj1jKDEsMiwxLDEpKQpuYW1lc3Bsb3QoYm95czUwKQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKYGBgCmBgYHtyIH0KZ2lybHM3MCA8LSBuYW1lLnN1YnNldChnaXJsLCAwKQpib3lzNzAgPC0gbmFtZS5zdWJzZXQoIWdpcmwsIDApCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIHBkZihyb290KCJOYW1lcy9maWdzIiwiZ2lybHM3MC5wZGYiKSwgaGVpZ2h0PTEwLCB3aWR0aD04KQpgYGAKYGBge3IgfQpwYXIobWFyPWMoMSwyLDEsMSkpCm5hbWVzcGxvdChnaXJsczcwKQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKaWYgKHNhdmVmaWdzKSBwZGYocm9vdCgiTmFtZXMvZmlncyIsImJveXM3MC5wZGYiKSwgaGVpZ2h0PTEwLCB3aWR0aD04KQpgYGAKYGBge3IgfQpwYXIobWFyPWMoMSwyLDEsMSkpCm5hbWVzcGxvdChib3lzNzApCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQpgYGAKCiMjIyMgUmVzdHJpY3QgdG8gdG9wIDEwMDAKCmBgYHtyIH0KdG9wMTAwMCA8LSBhcnJheShOQSwgYyhOLGxlbmd0aCh5ZWFycykpKQpmb3IgKGkgaW4gMTpsZW5ndGgoeWVhcnMpKXsKICB0b3AxMDAwW2dpcmwsaV0gPC0gY291bnRzW2dpcmwsaV0gPj0gcmV2KHNvcnQoY291bnRzW2dpcmwsaV0pKVsxMDAwXQogIHRvcDEwMDBbIWdpcmwsaV0gPC0gY291bnRzWyFnaXJsLGldID49IHJldihzb3J0KGNvdW50c1shZ2lybCxpXSkpWzEwMDBdCn0KZXZlcnRvcDEwMDAgPC0gcm93U3Vtcyh0b3AxMDAwKSA+IDAKCmdpcmxzNTBuZXcgPC0gbmFtZS5zdWJzZXQoZ2lybCwgNTApCmJveXM1MG5ldyA8LSBuYW1lLnN1YnNldCghZ2lybCwgNTApCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIHBkZihyb290KCJOYW1lcy9maWdzIiwiZ2lybHM1MG5ldy5wZGYiKSwgaGVpZ2h0PTgsIHdpZHRoPTgpCmBgYApgYGB7ciB9CnBhcihtYXI9YygxLDIsMSwxKSkKbmFtZXNwbG90KGdpcmxzNTBuZXcpCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQppZiAoc2F2ZWZpZ3MpIHBkZihyb290KCJOYW1lcy9maWdzIiwiYm95czUwbmV3LnBkZiIpLCBoZWlnaHQ9OCwgd2lkdGg9OCkKYGBgCmBgYHtyIH0KcGFyKG1hcj1jKDEsMiwxLDEpKQpuYW1lc3Bsb3QoYm95czUwbmV3KQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKYGBgCgpBZGQgbmV3IGNvbHVtbgoKYGBge3IgfQphdmcueWVhci4yIDwtIHJlcChOQSwgNTApCm5hbWVzNTAgPC0gcm93Lm5hbWVzKGdpcmxzNTBuZXcpCmZvciAoaSBpbiAxOjUwKXsKICBvayA8LSAoMTpOKVtuYW1lcz09bmFtZXM1MFtpXSZnaXJsXQogIGF2Zy55ZWFyLjJbaV0gPC0gc3RhdHNbb2ssImF2Zy55ZWFyLjIiXQp9CmdpcmxzNTBuZXcgPC0gY2JpbmQoZ2lybHM1MG5ld1ssMV0sIGF2Zy55ZWFyLjIsIGdpcmxzNTBuZXdbLDI6Nl0pCmNvbG5hbWVzKGdpcmxzNTBuZXcpWzFdIDwtICJ5ZWFyLm9mLm1heC5wb3AiCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIHBkZihyb290KCJOYW1lcy9maWdzIiwiZ2lybHM1MG5ldy5wZGYiKSwgaGVpZ2h0PTgsIHdpZHRoPTkpCmBgYApgYGB7ciB9CnBhcihtYXI9YygxLDIsMSwxKSkKbmFtZXNwbG90KGdpcmxzNTBuZXcpCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQpgYGAKCgoKYGBge3IgfQphdmcueWVhci4yIDwtIHJlcChOQSwgNTApCm5hbWVzNTAgPC0gcm93Lm5hbWVzKGJveXM1MG5ldykKZm9yIChpIGluIDE6NTApewogIG9rIDwtICgxOk4pW25hbWVzPT1uYW1lczUwW2ldJiFnaXJsXQogIGF2Zy55ZWFyLjJbaV0gPC0gc3RhdHNbb2ssImF2Zy55ZWFyLjIiXQp9CmJveXM1MG5ldyA8LSBjYmluZChib3lzNTBuZXdbLDFdLCBhdmcueWVhci4yLCBib3lzNTBuZXdbLDI6Nl0pCmNvbG5hbWVzKGJveXM1MG5ldylbMV0gPC0gInllYXIub2YubWF4LnBvcCIKYGBgCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgcGRmKHJvb3QoIk5hbWVzL2ZpZ3MiLCJib3lzNTBuZXcucGRmIiksIGhlaWdodD04LCB3aWR0aD05KQpgYGAKYGBge3IgfQpwYXIobWFyPWMoMSwyLDEsMSkpCm5hbWVzcGxvdChib3lzNTBuZXcpCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQpgYGAKCg==