Municipal elections in Espoo in 2017

Data is from https://github.com/HS-Datadesk/avoindata/tree/master/vaalikoneet/kuntavaalit2017

Also see:

set.seed(42)
library(RColorBrewer)
library(plotly)
Loading required package: ggplot2
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: ‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
library(vegan)
Loading required package: permute
Loading required package: lattice
This is vegan 2.5-6
library(clue)
library(dendextend)
Registered S3 method overwritten by 'dendextend':
  method     from 
  rev.hclust vegan

---------------------
Welcome to dendextend version 1.12.0
Type citation('dendextend') for how to cite the package.

Type browseVignettes(package = 'dendextend') for the package vignette.
The github page is: https://github.com/talgalili/dendextend/

Suggestions and bug-reports can be submitted at: https://github.com/talgalili/dendextend/issues
Or contact: <tal.galili@gmail.com>

    To suppress this message use:  suppressPackageStartupMessages(library(dendextend))
---------------------


Attaching package: ‘dendextend’

The following object is masked from ‘package:permute’:

    shuffle

The following object is masked from ‘package:stats’:

    cutree
library(MASS)

Attaching package: ‘MASS’

The following object is masked from ‘package:plotly’:

    select
library(png)
library(grid)

partyplot <- function(xy,cl=class,col0=brewer.pal(length(levels(cl)),"Paired"),alpha=0.6,
                      acol=sapply(col0,function(x) adjustcolor(x,alpha=alpha)),
                      lab=levels(cl),main="Espoo 2017",textp=FALSE) {
  plot(c(min(xy[,1])-if(textp) 0 else 0,max(xy[,1])),range(xy[,2]),bty="n",xlab="x",ylab="y",type="n",main=main)
  
  p <- sample.int(dim(xy)[1])
  if(textp) {
    text(xy[p,],lab[cl[p]],cex=1,col=col0[cl[p]])
  } else {
    ## legend("bottomleft",legend=lab,pch=19,col=col0,bty="n")
    points(xy[p,],pch=19,col=acol[cl[p]])
    aux <- aggregate(xy,by=list(cl),FUN=mean)
    text(aux[,2:3],as.character(aux[,1]),font=2,cex=2,col=col0)
    text(aux[,2:3],as.character(aux[,1]),pos=1,font=2)
  }
}

partyplot3d <- function(xyz,cl=class,col0=brewer.pal(length(levels(cl)),"Paired"),alpha=0.6,
                      acol=sapply(col0,function(x) adjustcolor(x,alpha=alpha)),
                      lab=levels(cl),main="Espoo 2017") {
  xyz <- as.data.frame(xyz)
  colnames(xyz) <- c("x","y","z")
  xyz <- cbind(xyz,w=as.integer(cl))
  plot_ly(xyz,x=~x,y=~y,z=~z,color=~w,colors=acol,text=rownames(xyz)) %>% layout(scene=list(xaxis=list(title="x"),yaxis=list(title="y"),zaxis=list(title="z")))  %>% add_markers()  %>% hide_colorbar() %>% hide_legend()
}

HS <- readRDS("HS.rds")
dHS <- dist(HS[,4:52])
mHS <- as.matrix(HS[,4:52])

## Do a trick to make the order of the factors to present
## similarity of answers within a party
a <- aggregate(svd(scale(mHS))$u[,1],by=list(class=HS[,"class"]),mean)
a <- a[order(a[,"x"]),]
HS[,"class"] <- factor(as.character(HS[,"class"]),levels=a[,"class"])
print(table(HS[,"class"]))

  ps  kok   kd kesk  pir  rkp  sdp vihr  vas  skp 
  59  102   39   33    6   43   84  108   36    5 

Let’s take a look at the data. Here we use non-metric multidimensional scaling (MDS) which “flattens” the 49-dimensional data into 2 or 3 dimensions, trying to preserve the inter-candidate distances \(p_{ij}\) as faithfully as possible

partyplot(isoMDS(dHS,k=2)$points,cl=HS[,3],main="Espoo 2017 (MDS)")
initial  value 28.005607 
iter   5 value 22.431213
iter  10 value 20.964772
final  value 20.249786 
converged

partyplot3d(data.frame(isoMDS(dHS,k=3)$points,row.names=rownames(HS)),cl=HS[,3])
initial  value 22.348438 
iter   5 value 16.222087
iter  10 value 15.085854
iter  15 value 14.744647
iter  15 value 14.738425
iter  15 value 14.735757
final  value 14.735757 
converged
# kmeansppcenters - Finds initial kmeans++ centroids
# Arguments:
# x        numeric matrix, rows correspond to data items
# k        integer, number of clusters
# Value:
# centers  a matrix of cluster centroids
#
# Reference: Arthur & Vassilivitskii (2007) k-means++: the
# advantages of careful seeding. In Proc SODA '07, 1027-1035. 
kmeansppcenters <- function(x,k) {
  x <- as.matrix(x)
  n <- dim(x)[1]
  centers <- matrix(NA,k,dim(x)[2])
  p <- rep(1/n,n)
  d <- rep(NA,n)
  for(i in 1:k) {
    centers[i,] <- x[sample.int(n,size=1,prob=p),]
    dd <- rowSums((x-(rep(1,n) %o% centers[i,]))^2)
    d <- if(i>1) pmin(d,dd) else dd
    if(max(d)>0) { p <- d/sum(d) }
  }
  centers
}

# kmeanspp - kmeans++ algorithm
# Arguments:
# x numeric matrix, rows correspond to data items
# k integer, number of clusters
# iter.max   the maximum number of iterations allowed (default 1000)
# algorithm  see kmeans documentation (default "Lloyd")
# Value:
# See kmeanspp documentation
#
# Reference: Arthur & Vassilivitskii (2007) k-means++: the
# advantages of careful seeding. In Proc SODA '07, 1027-1035. 
kmeanspp <- function(x,k,iter.max=1000,algorithm="Lloyd",...) {
  kmeans(x,centers=kmeansppcenters(x,k),iter.max=iter.max,
         algorithm=algorithm,...)
}

# kmeansppn 
# Run kmeanspp algorithm n times (default n=10) and return a solution
# with the smallest loss.
kmeansppn <- function(x,k,n=10,iter.max=1000,algorithm="Lloyd",...) {
  best <- NULL
  for(i in 1:n) {
    res <- kmeanspp(x,k,iter.max=iter.max,algorithm=algorithm,...)
    if(is.null(best) || res$tot.withinss<best$tot.withinss) {
      best <- res
    }
  }
  best
}

K-means clustering with \(k=10\) and kmeans++ initialization. Let’s look contingency table where the rows represent the clusters and columns the known party affiliations.

a <- kmeansppn(mHS,k=10)
## t1 <- table(levels(HS[,"class"])[a$cluster],HS[,"class"])
t1 <- table(a$cluster,HS[,"class"])
print(t1)
    
     ps kok kd kesk pir rkp sdp vihr vas skp
  1   1   0  3    2   0   8  21   22   4   2
  2   8  10  1    5   1   3   7    5   2   0
  3  39   1  3    0   0   0   1    0   2   0
  4   8  27  3    2   0   1   0    0   0   0
  5   0   1  0    1   1   3   8   57   1   0
  6   0   0  0    0   0   0  17    5  27   3
  7   1   0  0    0   1   0  11    0   0   0
  8   1  10 28   11   0   3   3    2   0   0
  9   0   7  1    6   0  22  16   13   0   0
  10  1  46  0    6   3   3   0    4   0   0
print(sum(diag(t1))/sum(t1))
[1] 0.05825243

We notice that the found clusters do not “match” the known party affiliations. Let’s find a permutation of cluster indices such that the cluster indices match the parties as well as possible. For this, we can use the Hungarian algorithm (e.g., solve_LSAP from clue libary in R). Hungarian algorithm finds a permutation of rows (cluster indices) such that the sum of entries in the diagonal is maximised. We find a better match with the clusters and parties already, but not perfect (is it surprising?).

t2 <- t1[order(solve_LSAP(t1,maximum=TRUE)),]
print(t2)
    
     ps kok kd kesk pir rkp sdp vihr vas skp
  3  39   1  3    0   0   0   1    0   2   0
  10  1  46  0    6   3   3   0    4   0   0
  8   1  10 28   11   0   3   3    2   0   0
  2   8  10  1    5   1   3   7    5   2   0
  7   1   0  0    0   1   0  11    0   0   0
  9   0   7  1    6   0  22  16   13   0   0
  1   1   0  3    2   0   8  21   22   4   2
  5   0   1  0    1   1   3   8   57   1   0
  6   0   0  0    0   0   0  17    5  27   3
  4   8  27  3    2   0   1   0    0   0   0
print(sum(diag(t2))/sum(t2))
[1] 0.4776699

Lets just see how the number of clusters affects the clustering error.

r <-sapply(1:20,function(k) kmeansppn(mHS,k)$tot.withinss)
plot(1:20,r,type="l",main="",xlab="number of clusters (k)",ylab="total within-cluster sum of squares")
points(1:20,r)

Lets then do some image compression.

me <- readPNG("kaip.png")[,,1:3] # only rgb channels
mem <- matrix(me,dim(me)[1]*dim(me)[2],dim(me)[3])
colnames(mem) <- c("red","green","blue")
a <- kmeansppn(mem,8)
mem0 <- a$centers[a$cluster,]
me0 <- array(mem0,dim=dim(me))

mec <- rgb(me[,,1],me[,,2],me[,,3])
dim(mec) <- dim(me)[1:2]
png("kaip1.png")
grid.raster(mec,interpolate=FALSE)
dev.off()
null device 
          1 
mec0 <- rgb(me0[,,1],me0[,,2],me0[,,3])
dim(mec0) <- dim(me0)[1:2]
png("kaip0.png")
grid.raster(mec0,interpolate=FALSE)
dev.off()
null device 
          1 

Original image:

Compressed image using k-means and 8 colour channels:

Here I have taken 200x200 = 40000 pixels as 3-dimensional rgb-vectors and clustered them into 8 clusters. Then I have replaced the actual pixel values with the values of respective cluster centroids. A pixel in the original image takes 3x8 = 24 bits and a pixel in the compressed image takes 3 bits, i.e., we obtain a compression ratio of 24/3 = 8 by this simple operation!

Let’s then try hierarchical clustering with different cluster indices.

hc.ward <- hclust(dHS,method="ward.D2") # similar to k-means
hc.complete <- hclust(dHS,method="complete")
hc.single <- hclust(dHS,method="single")
hc.average <- hclust(dHS,method="average")
plotdend <- function(hc,main="",col0=brewer.pal(10,"Paired")) {
  d <- as.dendrogram(hc)
  labels_colors(d) <- col0[HS[,"class"]]
  par(cex=0.25)
  plot(d,main=main)
}

Ward is similar to kmeans, working with cluster centroids.

plotdend(hc.ward,main="ward")

Complete linkage leads to clusters with roughly similar diameters.

plotdend(hc.complete,main="complete")

plotdend(hc.single,main="single")

plotdend(hc.average,main="average")

LS0tCnRpdGxlOiAiQ2x1c3RlcmluZyBlbGVjdGlvbiByZXN1bHRzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIE11bmljaXBhbCBlbGVjdGlvbnMgaW4gRXNwb28gaW4gMjAxNwoKKiBTdXJ2ZXkgb2YgY2FuZGlkYXRlcyBkb25lIGJ5IEhlbHNpbmdpbiBTYW5vbWF0CiogSGVyZSBpbmNsdWRlZCBvbmx5IDEwIHBhcnRpZXMgd2l0aCBsYXJnZXN0IG51bWJlciBvZiBjYW5kaWRhdGVzIG5hdGlvbmFsbHkuCiogRWFjaCBjYW5kaWRhdGUgcmF0ZWQgZWFjaCBvZiB0aGUgbT00OSBzdGF0ZW1lbnRzIG9uIGEgc2NhbGUgZnJvbSAxIHRvIDUsIHdoZXJlIDE9ZGlzYWdyZWUgYW5kIDU9YWdyZWU6CiAgMS4gRXV0aGFuYXNpYSBzaG91bGQgYmUgYWxsb3dlZC4KICAyLiBJIHByZWZlciBwdWJsaWMgaW5zdGVhZCBvZiB0aGUgcHJpdmF0ZSBzZWN0b3IgdG8gcHJvZHVjZSBteSBsb2NhbCBoZWFsdGgKc2VydmljZXMuCiAgMy4gU2FtZSBnZW5kZXIgY291cGxlcyBzaG91bGQgaGF2ZSB0aGUgc2FtZSBtYXJpdGFsIGFuZCBhZG9wdGF0aW9uCnJpZ2h0cyB0aGFuIHRoZSBkaWZmZXJlbnQgZ2VucmUgY291cGxlcy4KICA0LiBHb29kIGJyb3RoZXIgbmV0d29ya3MgaW5mbHVlbmNlIG11bmljaXBhbCBkZWNpc2lvbi1tYWtpbmcuCiAgNS4gLi4uCiogJG49NTE1JCBjYW5kaWRhdGVzIGluIHRvdGFsLCBpLmUuLCB3ZSBoYXZlIGEgZGF0YSA1MTV4NDkgbWF0cml4LgoqIERpc3RhbmNlICRwX3tpan0kIGJldHdlZW4gY2FuZGlkYXRlcyAkaSQgYW5kICRqJCBpcyB0aGUgRXVjbGlkZWFuIGRpc3RhbmNlIG9mIHRoZWlyIHJlc3BlY3RpdmUgNDktZGltZW5zaW9uYWwgcmF0aW5nIHZlY3RvcnMuIAoKRGF0YSBpcyBmcm9tCjxodHRwczovL2dpdGh1Yi5jb20vSFMtRGF0YWRlc2svYXZvaW5kYXRhL3RyZWUvbWFzdGVyL3ZhYWxpa29uZWV0L2t1bnRhdmFhbGl0MjAxNz4KCkFsc28gc2VlOgoKKiA8aHR0cHM6Ly91c2Vycy5hYWx0by5maS9+bGVpbm9uYTEvdmFhbGl0MjAxNS8+CiogPGh0dHBzOi8veWxlLmZpL3V1dGlzZXQvMy05NTI2MjkwPgoKCmBgYHtyfQpzZXQuc2VlZCg0MikKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHZlZ2FuKQpsaWJyYXJ5KGNsdWUpCmxpYnJhcnkoZGVuZGV4dGVuZCkKbGlicmFyeShNQVNTKQpsaWJyYXJ5KHBuZykKbGlicmFyeShncmlkKQoKcGFydHlwbG90IDwtIGZ1bmN0aW9uKHh5LGNsPWNsYXNzLGNvbDA9YnJld2VyLnBhbChsZW5ndGgobGV2ZWxzKGNsKSksIlBhaXJlZCIpLGFscGhhPTAuNiwKICAgICAgICAgICAgICAgICAgICAgIGFjb2w9c2FwcGx5KGNvbDAsZnVuY3Rpb24oeCkgYWRqdXN0Y29sb3IoeCxhbHBoYT1hbHBoYSkpLAogICAgICAgICAgICAgICAgICAgICAgbGFiPWxldmVscyhjbCksbWFpbj0iRXNwb28gMjAxNyIsdGV4dHA9RkFMU0UpIHsKICBwbG90KGMobWluKHh5WywxXSktaWYodGV4dHApIDAgZWxzZSAwLG1heCh4eVssMV0pKSxyYW5nZSh4eVssMl0pLGJ0eT0ibiIseGxhYj0ieCIseWxhYj0ieSIsdHlwZT0ibiIsbWFpbj1tYWluKQogIAogIHAgPC0gc2FtcGxlLmludChkaW0oeHkpWzFdKQogIGlmKHRleHRwKSB7CiAgICB0ZXh0KHh5W3AsXSxsYWJbY2xbcF1dLGNleD0xLGNvbD1jb2wwW2NsW3BdXSkKICB9IGVsc2UgewogICAgIyMgbGVnZW5kKCJib3R0b21sZWZ0IixsZWdlbmQ9bGFiLHBjaD0xOSxjb2w9Y29sMCxidHk9Im4iKQogICAgcG9pbnRzKHh5W3AsXSxwY2g9MTksY29sPWFjb2xbY2xbcF1dKQogICAgYXV4IDwtIGFnZ3JlZ2F0ZSh4eSxieT1saXN0KGNsKSxGVU49bWVhbikKICAgIHRleHQoYXV4WywyOjNdLGFzLmNoYXJhY3RlcihhdXhbLDFdKSxmb250PTIsY2V4PTIsY29sPWNvbDApCiAgICB0ZXh0KGF1eFssMjozXSxhcy5jaGFyYWN0ZXIoYXV4WywxXSkscG9zPTEsZm9udD0yKQogIH0KfQoKcGFydHlwbG90M2QgPC0gZnVuY3Rpb24oeHl6LGNsPWNsYXNzLGNvbDA9YnJld2VyLnBhbChsZW5ndGgobGV2ZWxzKGNsKSksIlBhaXJlZCIpLGFscGhhPTAuNiwKICAgICAgICAgICAgICAgICAgICAgIGFjb2w9c2FwcGx5KGNvbDAsZnVuY3Rpb24oeCkgYWRqdXN0Y29sb3IoeCxhbHBoYT1hbHBoYSkpLAogICAgICAgICAgICAgICAgICAgICAgbGFiPWxldmVscyhjbCksbWFpbj0iRXNwb28gMjAxNyIpIHsKICB4eXogPC0gYXMuZGF0YS5mcmFtZSh4eXopCiAgY29sbmFtZXMoeHl6KSA8LSBjKCJ4IiwieSIsInoiKQogIHh5eiA8LSBjYmluZCh4eXosdz1hcy5pbnRlZ2VyKGNsKSkKICBwbG90X2x5KHh5eix4PX54LHk9fnksej1+eixjb2xvcj1+dyxjb2xvcnM9YWNvbCx0ZXh0PXJvd25hbWVzKHh5eikpICU+JSBsYXlvdXQoc2NlbmU9bGlzdCh4YXhpcz1saXN0KHRpdGxlPSJ4IikseWF4aXM9bGlzdCh0aXRsZT0ieSIpLHpheGlzPWxpc3QodGl0bGU9InoiKSkpICAlPiUgYWRkX21hcmtlcnMoKSAgJT4lIGhpZGVfY29sb3JiYXIoKSAlPiUgaGlkZV9sZWdlbmQoKQp9CgpIUyA8LSByZWFkUkRTKCJIUy5yZHMiKQpkSFMgPC0gZGlzdChIU1ssNDo1Ml0pCm1IUyA8LSBhcy5tYXRyaXgoSFNbLDQ6NTJdKQoKIyMgRG8gYSB0cmljayB0byBtYWtlIHRoZSBvcmRlciBvZiB0aGUgZmFjdG9ycyB0byBwcmVzZW50CiMjIHNpbWlsYXJpdHkgb2YgYW5zd2VycyB3aXRoaW4gYSBwYXJ0eQphIDwtIGFnZ3JlZ2F0ZShzdmQoc2NhbGUobUhTKSkkdVssMV0sYnk9bGlzdChjbGFzcz1IU1ssImNsYXNzIl0pLG1lYW4pCmEgPC0gYVtvcmRlcihhWywieCJdKSxdCkhTWywiY2xhc3MiXSA8LSBmYWN0b3IoYXMuY2hhcmFjdGVyKEhTWywiY2xhc3MiXSksbGV2ZWxzPWFbLCJjbGFzcyJdKQpwcmludCh0YWJsZShIU1ssImNsYXNzIl0pKQpgYGAKCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBkYXRhLiBIZXJlIHdlIHVzZSBub24tbWV0cmljIG11bHRpZGltZW5zaW9uYWwgc2NhbGluZyAoTURTKSB3aGljaCAiZmxhdHRlbnMiIHRoZSA0OS1kaW1lbnNpb25hbCBkYXRhIGludG8gMiBvciAzIGRpbWVuc2lvbnMsIHRyeWluZyB0byBwcmVzZXJ2ZSB0aGUgaW50ZXItY2FuZGlkYXRlIGRpc3RhbmNlcyAkcF97aWp9JCBhcyBmYWl0aGZ1bGx5IGFzIHBvc3NpYmxlCgpgYGB7cn0KcGFydHlwbG90KGlzb01EUyhkSFMsaz0yKSRwb2ludHMsY2w9SFNbLDNdLG1haW49IkVzcG9vIDIwMTcgKE1EUykiKQpwYXJ0eXBsb3QzZChkYXRhLmZyYW1lKGlzb01EUyhkSFMsaz0zKSRwb2ludHMscm93Lm5hbWVzPXJvd25hbWVzKEhTKSksY2w9SFNbLDNdKQoKYGBgCgpgYGB7cn0KIyBrbWVhbnNwcGNlbnRlcnMgLSBGaW5kcyBpbml0aWFsIGttZWFucysrIGNlbnRyb2lkcwojIEFyZ3VtZW50czoKIyB4ICAgICAgICBudW1lcmljIG1hdHJpeCwgcm93cyBjb3JyZXNwb25kIHRvIGRhdGEgaXRlbXMKIyBrICAgICAgICBpbnRlZ2VyLCBudW1iZXIgb2YgY2x1c3RlcnMKIyBWYWx1ZToKIyBjZW50ZXJzICBhIG1hdHJpeCBvZiBjbHVzdGVyIGNlbnRyb2lkcwojCiMgUmVmZXJlbmNlOiBBcnRodXIgJiBWYXNzaWxpdml0c2tpaSAoMjAwNykgay1tZWFucysrOiB0aGUKIyBhZHZhbnRhZ2VzIG9mIGNhcmVmdWwgc2VlZGluZy4gSW4gUHJvYyBTT0RBICcwNywgMTAyNy0xMDM1LiAKa21lYW5zcHBjZW50ZXJzIDwtIGZ1bmN0aW9uKHgsaykgewogIHggPC0gYXMubWF0cml4KHgpCiAgbiA8LSBkaW0oeClbMV0KICBjZW50ZXJzIDwtIG1hdHJpeChOQSxrLGRpbSh4KVsyXSkKICBwIDwtIHJlcCgxL24sbikKICBkIDwtIHJlcChOQSxuKQogIGZvcihpIGluIDE6aykgewogICAgY2VudGVyc1tpLF0gPC0geFtzYW1wbGUuaW50KG4sc2l6ZT0xLHByb2I9cCksXQogICAgZGQgPC0gcm93U3VtcygoeC0ocmVwKDEsbikgJW8lIGNlbnRlcnNbaSxdKSleMikKICAgIGQgPC0gaWYoaT4xKSBwbWluKGQsZGQpIGVsc2UgZGQKICAgIGlmKG1heChkKT4wKSB7IHAgPC0gZC9zdW0oZCkgfQogIH0KICBjZW50ZXJzCn0KCiMga21lYW5zcHAgLSBrbWVhbnMrKyBhbGdvcml0aG0KIyBBcmd1bWVudHM6CiMgeCBudW1lcmljIG1hdHJpeCwgcm93cyBjb3JyZXNwb25kIHRvIGRhdGEgaXRlbXMKIyBrIGludGVnZXIsIG51bWJlciBvZiBjbHVzdGVycwojIGl0ZXIubWF4ICAgdGhlIG1heGltdW0gbnVtYmVyIG9mIGl0ZXJhdGlvbnMgYWxsb3dlZCAoZGVmYXVsdCAxMDAwKQojIGFsZ29yaXRobSAgc2VlIGttZWFucyBkb2N1bWVudGF0aW9uIChkZWZhdWx0ICJMbG95ZCIpCiMgVmFsdWU6CiMgU2VlIGttZWFuc3BwIGRvY3VtZW50YXRpb24KIwojIFJlZmVyZW5jZTogQXJ0aHVyICYgVmFzc2lsaXZpdHNraWkgKDIwMDcpIGstbWVhbnMrKzogdGhlCiMgYWR2YW50YWdlcyBvZiBjYXJlZnVsIHNlZWRpbmcuIEluIFByb2MgU09EQSAnMDcsIDEwMjctMTAzNS4gCmttZWFuc3BwIDwtIGZ1bmN0aW9uKHgsayxpdGVyLm1heD0xMDAwLGFsZ29yaXRobT0iTGxveWQiLC4uLikgewogIGttZWFucyh4LGNlbnRlcnM9a21lYW5zcHBjZW50ZXJzKHgsayksaXRlci5tYXg9aXRlci5tYXgsCiAgICAgICAgIGFsZ29yaXRobT1hbGdvcml0aG0sLi4uKQp9CgojIGttZWFuc3BwbiAKIyBSdW4ga21lYW5zcHAgYWxnb3JpdGhtIG4gdGltZXMgKGRlZmF1bHQgbj0xMCkgYW5kIHJldHVybiBhIHNvbHV0aW9uCiMgd2l0aCB0aGUgc21hbGxlc3QgbG9zcy4Ka21lYW5zcHBuIDwtIGZ1bmN0aW9uKHgsayxuPTEwLGl0ZXIubWF4PTEwMDAsYWxnb3JpdGhtPSJMbG95ZCIsLi4uKSB7CiAgYmVzdCA8LSBOVUxMCiAgZm9yKGkgaW4gMTpuKSB7CiAgICByZXMgPC0ga21lYW5zcHAoeCxrLGl0ZXIubWF4PWl0ZXIubWF4LGFsZ29yaXRobT1hbGdvcml0aG0sLi4uKQogICAgaWYoaXMubnVsbChiZXN0KSB8fCByZXMkdG90LndpdGhpbnNzPGJlc3QkdG90LndpdGhpbnNzKSB7CiAgICAgIGJlc3QgPC0gcmVzCiAgICB9CiAgfQogIGJlc3QKfQpgYGAKCkstbWVhbnMgY2x1c3RlcmluZyB3aXRoICRrPTEwJCBhbmQga21lYW5zKysgaW5pdGlhbGl6YXRpb24uIExldCdzIGxvb2sgY29udGluZ2VuY3kgdGFibGUgd2hlcmUgdGhlIHJvd3MgcmVwcmVzZW50IHRoZSBjbHVzdGVycyBhbmQgY29sdW1ucyB0aGUga25vd24gcGFydHkgYWZmaWxpYXRpb25zLiAKCmBgYHtyfQphIDwtIGttZWFuc3BwbihtSFMsaz0xMCkKIyMgdDEgPC0gdGFibGUobGV2ZWxzKEhTWywiY2xhc3MiXSlbYSRjbHVzdGVyXSxIU1ssImNsYXNzIl0pCnQxIDwtIHRhYmxlKGEkY2x1c3RlcixIU1ssImNsYXNzIl0pCnByaW50KHQxKQpwcmludChzdW0oZGlhZyh0MSkpL3N1bSh0MSkpCmBgYAoKV2Ugbm90aWNlIHRoYXQgdGhlIGZvdW5kIGNsdXN0ZXJzIGRvIG5vdCAibWF0Y2giIHRoZSBrbm93biBwYXJ0eSBhZmZpbGlhdGlvbnMuIExldCdzIGZpbmQgYSBwZXJtdXRhdGlvbiBvZiBjbHVzdGVyIGluZGljZXMgc3VjaCB0aGF0IHRoZSBjbHVzdGVyIGluZGljZXMgbWF0Y2ggdGhlIHBhcnRpZXMgYXMgd2VsbCBhcyBwb3NzaWJsZS4gRm9yIHRoaXMsIHdlIGNhbiB1c2UgdGhlIEh1bmdhcmlhbiBhbGdvcml0aG0gKGUuZy4sIGBzb2x2ZV9MU0FQYCBmcm9tIGBjbHVlYCBsaWJhcnkgaW4gUikuIEh1bmdhcmlhbiBhbGdvcml0aG0gZmluZHMgYSBwZXJtdXRhdGlvbiBvZiByb3dzIChjbHVzdGVyIGluZGljZXMpIHN1Y2ggdGhhdCB0aGUgc3VtIG9mIGVudHJpZXMgaW4gdGhlIGRpYWdvbmFsIGlzIG1heGltaXNlZC4gV2UgZmluZCBhIGJldHRlciBtYXRjaCB3aXRoIHRoZSBjbHVzdGVycyBhbmQgcGFydGllcyBhbHJlYWR5LCBidXQgbm90IHBlcmZlY3QgKGlzIGl0IHN1cnByaXNpbmc/KS4KCmBgYHtyfQp0MiA8LSB0MVtvcmRlcihzb2x2ZV9MU0FQKHQxLG1heGltdW09VFJVRSkpLF0KcHJpbnQodDIpCnByaW50KHN1bShkaWFnKHQyKSkvc3VtKHQyKSkKYGBgCgpMZXRzIGp1c3Qgc2VlIGhvdyB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIGFmZmVjdHMgdGhlIGNsdXN0ZXJpbmcgZXJyb3IuCgpgYGB7cn0KciA8LXNhcHBseSgxOjIwLGZ1bmN0aW9uKGspIGttZWFuc3BwbihtSFMsaykkdG90LndpdGhpbnNzKQpwbG90KDE6MjAscix0eXBlPSJsIixtYWluPSIiLHhsYWI9Im51bWJlciBvZiBjbHVzdGVycyAoaykiLHlsYWI9InRvdGFsIHdpdGhpbi1jbHVzdGVyIHN1bSBvZiBzcXVhcmVzIikKcG9pbnRzKDE6MjAscikKYGBgCgoKTGV0cyB0aGVuIGRvIHNvbWUgaW1hZ2UgY29tcHJlc3Npb24uCmBgYHtyfQptZSA8LSByZWFkUE5HKCJrYWlwLnBuZyIpWywsMTozXSAjIG9ubHkgcmdiIGNoYW5uZWxzCm1lbSA8LSBtYXRyaXgobWUsZGltKG1lKVsxXSpkaW0obWUpWzJdLGRpbShtZSlbM10pCmNvbG5hbWVzKG1lbSkgPC0gYygicmVkIiwiZ3JlZW4iLCJibHVlIikKYSA8LSBrbWVhbnNwcG4obWVtLDgpCmBgYAoKYGBge3J9Cm1lbTAgPC0gYSRjZW50ZXJzW2EkY2x1c3RlcixdCm1lMCA8LSBhcnJheShtZW0wLGRpbT1kaW0obWUpKQoKbWVjIDwtIHJnYihtZVssLDFdLG1lWywsMl0sbWVbLCwzXSkKZGltKG1lYykgPC0gZGltKG1lKVsxOjJdCnBuZygia2FpcDEucG5nIikKZ3JpZC5yYXN0ZXIobWVjLGludGVycG9sYXRlPUZBTFNFKQpkZXYub2ZmKCkKCm1lYzAgPC0gcmdiKG1lMFssLDFdLG1lMFssLDJdLG1lMFssLDNdKQpkaW0obWVjMCkgPC0gZGltKG1lMClbMToyXQpwbmcoImthaXAwLnBuZyIpCmdyaWQucmFzdGVyKG1lYzAsaW50ZXJwb2xhdGU9RkFMU0UpCmRldi5vZmYoKQoKYGBgCk9yaWdpbmFsIGltYWdlOgoKIVtdKGthaXAxLnBuZykKCkNvbXByZXNzZWQgaW1hZ2UgdXNpbmcgay1tZWFucyBhbmQgOCBjb2xvdXIgY2hhbm5lbHM6CgohW10oa2FpcDAucG5nKQoKSGVyZSBJIGhhdmUgdGFrZW4gMjAweDIwMCA9IDQwMDAwIHBpeGVscyBhcyAzLWRpbWVuc2lvbmFsIHJnYi12ZWN0b3JzIGFuZCBjbHVzdGVyZWQgdGhlbSBpbnRvIDggY2x1c3RlcnMuIFRoZW4gSSBoYXZlIHJlcGxhY2VkIHRoZSBhY3R1YWwgcGl4ZWwgdmFsdWVzIHdpdGggdGhlIHZhbHVlcyBvZiByZXNwZWN0aXZlIGNsdXN0ZXIgY2VudHJvaWRzLiBBIHBpeGVsIGluIHRoZSBvcmlnaW5hbCBpbWFnZSB0YWtlcyAzeDggPSAyNCBiaXRzIGFuZCBhIHBpeGVsIGluIHRoZSBjb21wcmVzc2VkIGltYWdlIHRha2VzIDMgYml0cywgaS5lLiwgd2Ugb2J0YWluIGEgY29tcHJlc3Npb24gcmF0aW8gb2YgMjQvMyA9IDggYnkgdGhpcyBzaW1wbGUgb3BlcmF0aW9uIQoKCkxldCdzIHRoZW4gdHJ5IGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIHdpdGggZGlmZmVyZW50IGNsdXN0ZXIgaW5kaWNlcy4KCmBgYHtyfQpoYy53YXJkIDwtIGhjbHVzdChkSFMsbWV0aG9kPSJ3YXJkLkQyIikgIyBzaW1pbGFyIHRvIGstbWVhbnMKaGMuY29tcGxldGUgPC0gaGNsdXN0KGRIUyxtZXRob2Q9ImNvbXBsZXRlIikKaGMuc2luZ2xlIDwtIGhjbHVzdChkSFMsbWV0aG9kPSJzaW5nbGUiKQpoYy5hdmVyYWdlIDwtIGhjbHVzdChkSFMsbWV0aG9kPSJhdmVyYWdlIikKcGxvdGRlbmQgPC0gZnVuY3Rpb24oaGMsbWFpbj0iIixjb2wwPWJyZXdlci5wYWwoMTAsIlBhaXJlZCIpKSB7CiAgZCA8LSBhcy5kZW5kcm9ncmFtKGhjKQogIGxhYmVsc19jb2xvcnMoZCkgPC0gY29sMFtIU1ssImNsYXNzIl1dCiAgcGFyKGNleD0wLjI1KQogIHBsb3QoZCxtYWluPW1haW4pCn0KYGBgCgpXYXJkIGlzIHNpbWlsYXIgdG8ga21lYW5zLCB3b3JraW5nIHdpdGggY2x1c3RlciBjZW50cm9pZHMuCgpgYGB7cn0KcGxvdGRlbmQoaGMud2FyZCxtYWluPSJ3YXJkIikKYGBgCgpDb21wbGV0ZSBsaW5rYWdlIGxlYWRzIHRvIGNsdXN0ZXJzIHdpdGggcm91Z2hseSBzaW1pbGFyIGRpYW1ldGVycy4KCmBgYHtyfQpwbG90ZGVuZChoYy5jb21wbGV0ZSxtYWluPSJjb21wbGV0ZSIpCmBgYAoKYGBge3J9CnBsb3RkZW5kKGhjLnNpbmdsZSxtYWluPSJzaW5nbGUiKQpwbG90ZGVuZChoYy5hdmVyYWdlLG1haW49ImF2ZXJhZ2UiKQpgYGA=