Réalisé avec RStudio 1.0.44 notebook.

Préambule

L’idée de cette étude est venue lors d’une discussion au sujet de l’incohérence qu’il semble parfois y avoir concernant les règles de répartition de la carte scolaire. Il peut ainsi arriver que pour une même rue, l’accès aux écoles ne soit pas le même en fonction du numéro de résidence.

La question s’est alors posée de savoir si les contours de la cartes scolaire de la ville de Rennes suivaient une règle de segmentation purement mathématique.

L’étude portera sur les écoles publiques primaires.

switch(
  Sys.info()[["sysname"]],
  Windows = {},
  Linux  = {},
  Darwin = {lc <- Sys.setlocale("LC_ALL", "en_US.UTF-8")}
)

Données

Les données proviennent du site Open Data de Rennes Métropole.

Ecoles de Rennes

Source : Ecoles maternelles et primaires de Rennes.

ecoles_rennes <- read.csv2("data/ecoles-rennes.csv", dec = ".")
ecoles_maternelle <- subset(ecoles_rennes, grepl("maternelle", OrgaNom))
ecoles_primaire <- subset(ecoles_rennes, grepl("élémentaire", OrgaNom))
ecoles_autres <- ecoles_rennes[-c(as.integer(row.names(ecoles_maternelle)), as.integer(row.names(ecoles_primaire))),]
ecoles <- ecoles_primaire
ecoles[, c("OrgaNom", "QuarNom")]

Le jeu de données contient 41 établissements.

Carte des écoles publiques primaires

library(leaflet)
library(htmltools)
leaflet() %>%
  addTiles() %>%
  addCircleMarkers(
    lng = ecoles$Longitude,
    lat = ecoles$Latitude,
    popup = paste0(
      "<p>", "Ecole : <b>", htmlEscape(ecoles$OrgaNom), "</b>", "</p>",
      "<p>", "Quartier : <b>", htmlEscape(ecoles$QuarNom), "</b>", "</p>"
    ),
    clusterOptions = markerClusterOptions()
  )

Cliquer sur les cercles bleus pour visualiser le nom de l’établissement.

Rues de Rennes

Source : Adresses du référentiel voies et adresses de Rennes Métropole.

adresses_metropole <- read.csv2("data/adresses-du-referentiel-voies-et-adresses-de-rennes-metropole.csv", dec = ".", stringsAsFactors = F)
adresses_rennes <- subset(adresses_metropole, nom == "Rennes")
longlat <- lapply(adresses_rennes$Geo.Point, function(x) { strsplit(x, ", ") })
adresses_rennes$Latitude = as.numeric(sapply(longlat, function(x) { x[[1]][1] }))
adresses_rennes$Longitude = as.numeric(sapply(longlat, function(x) { x[[1]][2] }))
# Nettoyage
adresses_metropole <- NULL
longlat <- NULL

Rues de Rennes

Le jeu de données contient 29938 rues.

plot(
  adresses_rennes$Longitude,
  adresses_rennes$Latitude,
  pch = ".",
  cex = .1,
  cex.axis = .8,
  cex.lab = .8,
  col = rgb(.5, .5, .5, .8),
  xlab = "longitude",
  ylab = "latitude",
  main = "Rues de Rennes"
)

Segmentation

k-means

secteur_count <- length(unique(ecoles$QuarNom))
adresses_kmeans <- kmeans(adresses_rennes[, c("Longitude", "Latitude")], secteur_count, algorithm = "Lloyd", iter.max = 100)
adresses_rennes$cluster <- adresses_kmeans$cluster
# Assignation des secteurs aux écoles
library(class)
ecoles$cluster <- knn(
  adresses_rennes[, c("Longitude", "Latitude")],
  ecoles[, c("Longitude", "Latitude")],
  adresses_rennes$cluster
)

Définition de la carte scolaire selon la méthode des k-means, pour 12 secteurs.

cols <- rainbow(secteur_count)
map_adresses <- leaflet() %>%
  addTiles() %>%
  # Ecoles
  addCircleMarkers(
    lng = ecoles$Longitude,
    lat = ecoles$Latitude,
    popup = paste0(
      "<p>", "Ecole : <b>", htmlEscape(ecoles$OrgaNom), "</b>", "</p>",
      "<p>", "Quartier : <b>", htmlEscape(ecoles$QuarNom), "</b>", "</p>",
      "<p>", "Secteur : <b>", htmlEscape(ecoles$cluster), "</b>", "</p>"
    ),
    color = "black",
    fillColor = cols[ecoles$cluster],
    fillOpacity = .5,
    clusterOptions = markerClusterOptions()
  )
# Rues par segments
for (k in 1:secteur_count) {
  map_adresses <-  map_adresses %>%
    addCircles(
      lng = adresses_rennes[adresses_rennes$cluster == k, ]$Longitude,
      lat = adresses_rennes[adresses_rennes$cluster == k, ]$Latitude,
      radius = 5,
      opacity = .1,
      color = cols[k]
    )
}
# Barycentres des segments (centroïdes)
map_adresses <- map_adresses %>%
  addMarkers(
    lng = adresses_kmeans$centers[, "Longitude"],
    lat = adresses_kmeans$centers[, "Latitude"],
    popup = paste0("Secteur ", "<b>", 1:secteur_count, "</b>"),
    clusterOptions = markerClusterOptions()
  )
map_adresses

Cliquer sur les cercles aux contours noirs pour visualiser le nom de l’établissement.

Etablissements par secteurs, selon la carte scolaire.

library(formattable)
secteurs <- data.frame(Secteur = levels(ecoles$QuarNom))
secteurs$Etablissement <- sapply(
  secteurs$Secteur,
  function(q) {
    paste0("<li>", ecoles[ecoles$QuarNom == q, "OrgaNom"], "</li>", collapse = "")
  }
)
formattable(secteurs)

Etablissements par secteurs, selon la segmentation.

cont <- outer(
  levels(ecoles$QuarNom),
  1:secteur_count,
  Vectorize(
    function(x, y) {
      c <- nrow(ecoles[ecoles$QuarNom == x & ecoles$cluster == y,])
      return(ifelse(c == 0, "", c))
    }
  )
)
row.names(cont) <- levels(ecoles$QuarNom)
colnames(cont) <- paste0(1:secteur_count)
formattable(as.data.frame(cont))

Le montant à l’intersection d’un secteur (lignes) et d’un segment (colonnes) indique le nombre d’établissements pour ce secteur affectés à ce segment.

La dispersion des établissements d’un secteur sur plusieurs segments indique que la carte scolaire ne suit pas une logique mathématique.

Diagramme de Voronoï

Quelle serait la répartition des rues si un secteur ne correspondait qu’à une école et une seule (nombre de secteurs = nombre d’écoles = 41) ?

La segmentation peut-être réalisée avec un diagramme de Voronoï.

library(tripack)
ecoles_voronoi <- voronoi.mosaic(ecoles$Longitude, ecoles$Latitude)
ecoles_polygons <- voronoi.polygons(ecoles_voronoi)
map_ecoles <- leaflet() %>%
  addTiles() %>%
  addCircleMarkers(
    lng = ecoles$Longitude,
    lat = ecoles$Latitude,
    popup = paste0(
      "<p>", "Ecole : <b>", htmlEscape(ecoles$OrgaNom), "</b>", "</p>",
      "<p>", "Quartier : <b>", htmlEscape(ecoles$QuarNom), "</b>", "</p>"
    ),
    clusterOptions = markerClusterOptions()
  )
for (p in 1:length(ecoles_polygons)) {
  map_ecoles <- map_ecoles %>%
    addPolygons(
      lng = ecoles_polygons[[p]][,1],
      lat = ecoles_polygons[[p]][,2],
      color = rainbow(length(ecoles_polygons))[p]
    )
}
map_ecoles

Cliquer sur les cercles bleus pour visualiser le nom de l’établissement.

Conclusion

L’organisation de la vie en société est rarement le résultat d’une démarche exclusivement scientifique, car impactée par une histoire et diverses influences. C’est donc sans surprise que l’on constate un écart entre la carte scolaire actuelle, et celle obtenue par segmentation.

Cette étude aura été l’occasion d’utiliser des “algorithmes” tels que k-means, k-nearest-neighbor et diagrammes de Voronoï avec le logiciel R, sur la base de données issues de l’Open Data de Rennes Métropole.

LS0tCnRpdGxlOiAiRXR1ZGUgZGUgbGEgY2FydGUgc2NvbGFpcmUgcmVubmFpc2UiCm91dHB1dDogaHRtbF9ub3RlYm9vawphdXRob3I6IE1pY2hlbCBDYXJhZGVjCi0tLQoKKlLDqWFsaXPDqSBhdmVjIFJTdHVkaW8gMS4wLjQ0IG5vdGVib29rLioKCiMjIFByw6lhbWJ1bGUKCkwnaWTDqWUgZGUgY2V0dGUgw6l0dWRlIGVzdCB2ZW51ZSBsb3JzIGQndW5lIGRpc2N1c3Npb24gYXUgc3VqZXQgZGUgbCdpbmNvaMOpcmVuY2UgcXUnaWwgc2VtYmxlIHBhcmZvaXMgeSBhdm9pciBjb25jZXJuYW50IGxlcyByw6hnbGVzIGRlIHLDqXBhcnRpdGlvbiBkZSBsYSBjYXJ0ZSBzY29sYWlyZS4gSWwgcGV1dCBhaW5zaSBhcnJpdmVyIHF1ZSBwb3VyIHVuZSBtw6ptZSBydWUsIGwnYWNjw6hzIGF1eCDDqWNvbGVzIG5lIHNvaXQgcGFzIGxlIG3Dqm1lIGVuIGZvbmN0aW9uIGR1IG51bcOpcm8gZGUgcsOpc2lkZW5jZS4KCkxhIHF1ZXN0aW9uIHMnZXN0IGFsb3JzIHBvc8OpZSBkZSBzYXZvaXIgc2kgbGVzIGNvbnRvdXJzIGRlIGxhIGNhcnRlcyBzY29sYWlyZSBkZSBsYSB2aWxsZSBkZSBSZW5uZXMgc3VpdmFpZW50IHVuZSByw6hnbGUgZGUgc2VnbWVudGF0aW9uIHB1cmVtZW50IG1hdGjDqW1hdGlxdWUuCgpMJ8OpdHVkZSBwb3J0ZXJhIHN1ciBsZXMgKirDqWNvbGVzIHB1YmxpcXVlcyBwcmltYWlyZXMqKi4KCmBgYHtyIHNldHVwfQpzd2l0Y2goCiAgU3lzLmluZm8oKVtbInN5c25hbWUiXV0sCiAgV2luZG93cyA9IHt9LAogIExpbnV4ICA9IHt9LAogIERhcndpbiA9IHtsYyA8LSBTeXMuc2V0bG9jYWxlKCJMQ19BTEwiLCAiZW5fVVMuVVRGLTgiKX0KKQpgYGAKCiMjIERvbm7DqWVzCgpMZXMgZG9ubsOpZXMgcHJvdmllbm5lbnQgZHUgc2l0ZSAqKk9wZW4gRGF0YSoqIGRlIFtSZW5uZXMgTcOpdHJvcG9sZV0oaHR0cHM6Ly9kYXRhLnJlbm5lc21ldHJvcG9sZS5mcikuCgojIyMgRWNvbGVzIGRlIFJlbm5lcwoKU291cmNlIDogW0Vjb2xlcyBtYXRlcm5lbGxlcyBldCBwcmltYWlyZXMgZGUgUmVubmVzXShodHRwczovL2RhdGEucmVubmVzbWV0cm9wb2xlLmZyL2V4cGxvcmUvZGF0YXNldC9lY29sZXMtcmVubmVzLykuCgpgYGB7ciBkc19lY29sZXN9CmVjb2xlc19yZW5uZXMgPC0gcmVhZC5jc3YyKCJkYXRhL2Vjb2xlcy1yZW5uZXMuY3N2IiwgZGVjID0gIi4iKQplY29sZXNfbWF0ZXJuZWxsZSA8LSBzdWJzZXQoZWNvbGVzX3Jlbm5lcywgZ3JlcGwoIm1hdGVybmVsbGUiLCBPcmdhTm9tKSkKZWNvbGVzX3ByaW1haXJlIDwtIHN1YnNldChlY29sZXNfcmVubmVzLCBncmVwbCgiw6lsw6ltZW50YWlyZSIsIE9yZ2FOb20pKQplY29sZXNfYXV0cmVzIDwtIGVjb2xlc19yZW5uZXNbLWMoYXMuaW50ZWdlcihyb3cubmFtZXMoZWNvbGVzX21hdGVybmVsbGUpKSwgYXMuaW50ZWdlcihyb3cubmFtZXMoZWNvbGVzX3ByaW1haXJlKSkpLF0KCmVjb2xlcyA8LSBlY29sZXNfcHJpbWFpcmUKZWNvbGVzWywgYygiT3JnYU5vbSIsICJRdWFyTm9tIildCmBgYAoKTGUgamV1IGRlIGRvbm7DqWVzIGNvbnRpZW50ICoqYHIgbnJvdyhlY29sZXMpYCoqIMOpdGFibGlzc2VtZW50cy4KCiMjIyMgQ2FydGUgZGVzIMOpY29sZXMgcHVibGlxdWVzIHByaW1haXJlcwoKYGBge3IgbWFwX2Vjb2xlc30KbGlicmFyeShsZWFmbGV0KQpsaWJyYXJ5KGh0bWx0b29scykKCmxlYWZsZXQoKSAlPiUKICBhZGRUaWxlcygpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoCiAgICBsbmcgPSBlY29sZXMkTG9uZ2l0dWRlLAogICAgbGF0ID0gZWNvbGVzJExhdGl0dWRlLAogICAgcG9wdXAgPSBwYXN0ZTAoCiAgICAgICI8cD4iLCAiRWNvbGUgOiA8Yj4iLCBodG1sRXNjYXBlKGVjb2xlcyRPcmdhTm9tKSwgIjwvYj4iLCAiPC9wPiIsCiAgICAgICI8cD4iLCAiUXVhcnRpZXIgOiA8Yj4iLCBodG1sRXNjYXBlKGVjb2xlcyRRdWFyTm9tKSwgIjwvYj4iLCAiPC9wPiIKICAgICksCiAgICBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkKICApCmBgYAoKKkNsaXF1ZXIgc3VyIGxlcyBjZXJjbGVzIGJsZXVzIHBvdXIgdmlzdWFsaXNlciBsZSBub20gZGUgbCfDqXRhYmxpc3NlbWVudC4qCgojIyMgUnVlcyBkZSBSZW5uZXMKClNvdXJjZSA6IFtBZHJlc3NlcyBkdSByw6lmw6lyZW50aWVsIHZvaWVzIGV0IGFkcmVzc2VzIGRlIFJlbm5lcyBNw6l0cm9wb2xlXShodHRwczovL2RhdGEucmVubmVzbWV0cm9wb2xlLmZyL2V4cGxvcmUvZGF0YXNldC9hZHJlc3Nlcy1kdS1yZWZlcmVudGllbC12b2llcy1ldC1hZHJlc3Nlcy1kZS1yZW5uZXMtbWV0cm9wb2xlLykuCgpgYGB7ciBkc19hZHJlc3Nlc30KYWRyZXNzZXNfbWV0cm9wb2xlIDwtIHJlYWQuY3N2MigiZGF0YS9hZHJlc3Nlcy1kdS1yZWZlcmVudGllbC12b2llcy1ldC1hZHJlc3Nlcy1kZS1yZW5uZXMtbWV0cm9wb2xlLmNzdiIsIGRlYyA9ICIuIiwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmFkcmVzc2VzX3Jlbm5lcyA8LSBzdWJzZXQoYWRyZXNzZXNfbWV0cm9wb2xlLCBub20gPT0gIlJlbm5lcyIpCgpsb25nbGF0IDwtIGxhcHBseShhZHJlc3Nlc19yZW5uZXMkR2VvLlBvaW50LCBmdW5jdGlvbih4KSB7IHN0cnNwbGl0KHgsICIsICIpIH0pCmFkcmVzc2VzX3Jlbm5lcyRMYXRpdHVkZSA9IGFzLm51bWVyaWMoc2FwcGx5KGxvbmdsYXQsIGZ1bmN0aW9uKHgpIHsgeFtbMV1dWzFdIH0pKQphZHJlc3Nlc19yZW5uZXMkTG9uZ2l0dWRlID0gYXMubnVtZXJpYyhzYXBwbHkobG9uZ2xhdCwgZnVuY3Rpb24oeCkgeyB4W1sxXV1bMl0gfSkpCgojIE5ldHRveWFnZQphZHJlc3Nlc19tZXRyb3BvbGUgPC0gTlVMTApsb25nbGF0IDwtIE5VTEwKYGBgCgojIyMjIFJ1ZXMgZGUgUmVubmVzCgpMZSBqZXUgZGUgZG9ubsOpZXMgY29udGllbnQgKipgciBucm93KGFkcmVzc2VzX3Jlbm5lcylgKiogcnVlcy4KCmBgYHtyIHJ1ZXNfcmVubmVzfQpwbG90KAogIGFkcmVzc2VzX3Jlbm5lcyRMb25naXR1ZGUsCiAgYWRyZXNzZXNfcmVubmVzJExhdGl0dWRlLAogIHBjaCA9ICIuIiwKICBjZXggPSAuMSwKICBjZXguYXhpcyA9IC44LAogIGNleC5sYWIgPSAuOCwKICBjb2wgPSByZ2IoLjUsIC41LCAuNSwgLjgpLAogIHhsYWIgPSAibG9uZ2l0dWRlIiwKICB5bGFiID0gImxhdGl0dWRlIiwKICBtYWluID0gIlJ1ZXMgZGUgUmVubmVzIgopCmBgYAoKIyMgU2VnbWVudGF0aW9uCgojIyMgay1tZWFucwoKYGBge3IgYWRyZXNzZXNfa21lYW5zfQpzZWN0ZXVyX2NvdW50IDwtIGxlbmd0aCh1bmlxdWUoZWNvbGVzJFF1YXJOb20pKQoKYWRyZXNzZXNfa21lYW5zIDwtIGttZWFucyhhZHJlc3Nlc19yZW5uZXNbLCBjKCJMb25naXR1ZGUiLCAiTGF0aXR1ZGUiKV0sIHNlY3RldXJfY291bnQsIGFsZ29yaXRobSA9ICJMbG95ZCIsIGl0ZXIubWF4ID0gMTAwKQphZHJlc3Nlc19yZW5uZXMkY2x1c3RlciA8LSBhZHJlc3Nlc19rbWVhbnMkY2x1c3RlcgoKIyBBc3NpZ25hdGlvbiBkZXMgc2VjdGV1cnMgYXV4IMOpY29sZXMKbGlicmFyeShjbGFzcykKCmVjb2xlcyRjbHVzdGVyIDwtIGtubigKICBhZHJlc3Nlc19yZW5uZXNbLCBjKCJMb25naXR1ZGUiLCAiTGF0aXR1ZGUiKV0sCiAgZWNvbGVzWywgYygiTG9uZ2l0dWRlIiwgIkxhdGl0dWRlIildLAogIGFkcmVzc2VzX3Jlbm5lcyRjbHVzdGVyCikKYGBgCgpEw6lmaW5pdGlvbiBkZSBsYSBjYXJ0ZSBzY29sYWlyZSBzZWxvbiBsYSBtw6l0aG9kZSBkZXMgW2stbWVhbnNdKGh0dHBzOi8vZnIud2lraXBlZGlhLm9yZy93aWtpL0stbW95ZW5uZXMpLCBwb3VyICoqYHIgc2VjdGV1cl9jb3VudGAgc2VjdGV1cnMqKi4KCmBgYHtyIG1hcF9hZHJlc3Nlc19rbWVhbnN9CmNvbHMgPC0gcmFpbmJvdyhzZWN0ZXVyX2NvdW50KQoKbWFwX2FkcmVzc2VzIDwtIGxlYWZsZXQoKSAlPiUKICBhZGRUaWxlcygpICU+JQogICMgRWNvbGVzCiAgYWRkQ2lyY2xlTWFya2VycygKICAgIGxuZyA9IGVjb2xlcyRMb25naXR1ZGUsCiAgICBsYXQgPSBlY29sZXMkTGF0aXR1ZGUsCiAgICBwb3B1cCA9IHBhc3RlMCgKICAgICAgIjxwPiIsICJFY29sZSA6IDxiPiIsIGh0bWxFc2NhcGUoZWNvbGVzJE9yZ2FOb20pLCAiPC9iPiIsICI8L3A+IiwKICAgICAgIjxwPiIsICJRdWFydGllciA6IDxiPiIsIGh0bWxFc2NhcGUoZWNvbGVzJFF1YXJOb20pLCAiPC9iPiIsICI8L3A+IiwKICAgICAgIjxwPiIsICJTZWN0ZXVyIDogPGI+IiwgaHRtbEVzY2FwZShlY29sZXMkY2x1c3RlciksICI8L2I+IiwgIjwvcD4iCiAgICApLAogICAgY29sb3IgPSAiYmxhY2siLAogICAgZmlsbENvbG9yID0gY29sc1tlY29sZXMkY2x1c3Rlcl0sCiAgICBmaWxsT3BhY2l0eSA9IC41LAogICAgY2x1c3Rlck9wdGlvbnMgPSBtYXJrZXJDbHVzdGVyT3B0aW9ucygpCiAgKQoKIyBSdWVzIHBhciBzZWdtZW50cwpmb3IgKGsgaW4gMTpzZWN0ZXVyX2NvdW50KSB7CiAgbWFwX2FkcmVzc2VzIDwtICBtYXBfYWRyZXNzZXMgJT4lCiAgICBhZGRDaXJjbGVzKAogICAgICBsbmcgPSBhZHJlc3Nlc19yZW5uZXNbYWRyZXNzZXNfcmVubmVzJGNsdXN0ZXIgPT0gaywgXSRMb25naXR1ZGUsCiAgICAgIGxhdCA9IGFkcmVzc2VzX3Jlbm5lc1thZHJlc3Nlc19yZW5uZXMkY2x1c3RlciA9PSBrLCBdJExhdGl0dWRlLAogICAgICByYWRpdXMgPSA1LAogICAgICBvcGFjaXR5ID0gLjEsCiAgICAgIGNvbG9yID0gY29sc1trXQogICAgKQp9CgojIEJhcnljZW50cmVzIGRlcyBzZWdtZW50cyAoY2VudHJvw69kZXMpCm1hcF9hZHJlc3NlcyA8LSBtYXBfYWRyZXNzZXMgJT4lCiAgYWRkTWFya2VycygKICAgIGxuZyA9IGFkcmVzc2VzX2ttZWFucyRjZW50ZXJzWywgIkxvbmdpdHVkZSJdLAogICAgbGF0ID0gYWRyZXNzZXNfa21lYW5zJGNlbnRlcnNbLCAiTGF0aXR1ZGUiXSwKICAgIHBvcHVwID0gcGFzdGUwKCJTZWN0ZXVyICIsICI8Yj4iLCAxOnNlY3RldXJfY291bnQsICI8L2I+IiksCiAgICBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkKICApCgptYXBfYWRyZXNzZXMKYGBgCgoqQ2xpcXVlciBzdXIgbGVzIGNlcmNsZXMgYXV4IGNvbnRvdXJzIG5vaXJzIHBvdXIgdmlzdWFsaXNlciBsZSBub20gZGUgbCfDqXRhYmxpc3NlbWVudC4qCgpFdGFibGlzc2VtZW50cyBwYXIgc2VjdGV1cnMsIHNlbG9uIGxhIGNhcnRlIHNjb2xhaXJlLgoKYGBge3IgZWNvbGVzX3NlY3RldXJzfQpsaWJyYXJ5KGZvcm1hdHRhYmxlKQoKc2VjdGV1cnMgPC0gZGF0YS5mcmFtZShTZWN0ZXVyID0gbGV2ZWxzKGVjb2xlcyRRdWFyTm9tKSkKc2VjdGV1cnMkRXRhYmxpc3NlbWVudCA8LSBzYXBwbHkoCiAgc2VjdGV1cnMkU2VjdGV1ciwKICBmdW5jdGlvbihxKSB7CiAgICBwYXN0ZTAoIjxsaT4iLCBlY29sZXNbZWNvbGVzJFF1YXJOb20gPT0gcSwgIk9yZ2FOb20iXSwgIjwvbGk+IiwgY29sbGFwc2UgPSAiIikKICB9CikKCmZvcm1hdHRhYmxlKHNlY3RldXJzKQpgYGAKCkV0YWJsaXNzZW1lbnRzIHBhciBzZWN0ZXVycywgc2Vsb24gbGEgc2VnbWVudGF0aW9uLgoKYGBge3IgZWNvbGVzX2NvbnRpZ2VuY2V9CmNvbnQgPC0gb3V0ZXIoCiAgbGV2ZWxzKGVjb2xlcyRRdWFyTm9tKSwKICAxOnNlY3RldXJfY291bnQsCiAgVmVjdG9yaXplKAogICAgZnVuY3Rpb24oeCwgeSkgewogICAgICBjIDwtIG5yb3coZWNvbGVzW2Vjb2xlcyRRdWFyTm9tID09IHggJiBlY29sZXMkY2x1c3RlciA9PSB5LF0pCiAgICAgIHJldHVybihpZmVsc2UoYyA9PSAwLCAiIiwgYykpCiAgICB9CiAgKQopCnJvdy5uYW1lcyhjb250KSA8LSBsZXZlbHMoZWNvbGVzJFF1YXJOb20pCmNvbG5hbWVzKGNvbnQpIDwtIHBhc3RlMCgxOnNlY3RldXJfY291bnQpCmZvcm1hdHRhYmxlKGFzLmRhdGEuZnJhbWUoY29udCkpCmBgYAoKTGUgbW9udGFudCDDoCBsJ2ludGVyc2VjdGlvbiBkJ3VuIHNlY3RldXIgKGxpZ25lcykgZXQgZCd1biBzZWdtZW50IChjb2xvbm5lcykgaW5kaXF1ZSBsZSBub21icmUgZCfDqXRhYmxpc3NlbWVudHMgcG91ciBjZSBzZWN0ZXVyIGFmZmVjdMOpcyDDoCBjZSBzZWdtZW50LgoKTGEgZGlzcGVyc2lvbiBkZXMgw6l0YWJsaXNzZW1lbnRzIGQndW4gc2VjdGV1ciBzdXIgcGx1c2lldXJzIHNlZ21lbnRzIGluZGlxdWUgcXVlIGxhIGNhcnRlIHNjb2xhaXJlIG5lIHN1aXQgcGFzIHVuZSBsb2dpcXVlIG1hdGjDqW1hdGlxdWUuCgojIyMgRGlhZ3JhbW1lIGRlIFZvcm9ub8OvCgpRdWVsbGUgc2VyYWl0IGxhIHLDqXBhcnRpdGlvbiBkZXMgcnVlcyBzaSB1biBzZWN0ZXVyIG5lIGNvcnJlc3BvbmRhaXQgcXUnw6AgKip1bmUgw6ljb2xlIGV0IHVuZSBzZXVsZSoqIChub21icmUgZGUgc2VjdGV1cnMgPSBub21icmUgZCfDqWNvbGVzID0gKipgciBucm93KGVjb2xlcylgKiopID8KCkxhIHNlZ21lbnRhdGlvbiBwZXV0LcOqdHJlIHLDqWFsaXPDqWUgYXZlYyB1biBbZGlhZ3JhbW1lIGRlIFZvcm9ub8OvXShodHRwczovL2ZyLndpa2lwZWRpYS5vcmcvd2lraS9EaWFncmFtbWVfZGVfVm9yb25vw68pLgoKYGBge3IgZWNvbGVzX3Zvcm9ub2l9CmxpYnJhcnkodHJpcGFjaykKCmVjb2xlc192b3Jvbm9pIDwtIHZvcm9ub2kubW9zYWljKGVjb2xlcyRMb25naXR1ZGUsIGVjb2xlcyRMYXRpdHVkZSkKZWNvbGVzX3BvbHlnb25zIDwtIHZvcm9ub2kucG9seWdvbnMoZWNvbGVzX3Zvcm9ub2kpCmBgYAoKYGBge3IgbWFwX2Vjb2xlc192b3Jvbm9pfQptYXBfZWNvbGVzIDwtIGxlYWZsZXQoKSAlPiUKICBhZGRUaWxlcygpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoCiAgICBsbmcgPSBlY29sZXMkTG9uZ2l0dWRlLAogICAgbGF0ID0gZWNvbGVzJExhdGl0dWRlLAogICAgcG9wdXAgPSBwYXN0ZTAoCiAgICAgICI8cD4iLCAiRWNvbGUgOiA8Yj4iLCBodG1sRXNjYXBlKGVjb2xlcyRPcmdhTm9tKSwgIjwvYj4iLCAiPC9wPiIsCiAgICAgICI8cD4iLCAiUXVhcnRpZXIgOiA8Yj4iLCBodG1sRXNjYXBlKGVjb2xlcyRRdWFyTm9tKSwgIjwvYj4iLCAiPC9wPiIKICAgICksCiAgICBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkKICApCgpmb3IgKHAgaW4gMTpsZW5ndGgoZWNvbGVzX3BvbHlnb25zKSkgewogIG1hcF9lY29sZXMgPC0gbWFwX2Vjb2xlcyAlPiUKICAgIGFkZFBvbHlnb25zKAogICAgICBsbmcgPSBlY29sZXNfcG9seWdvbnNbW3BdXVssMV0sCiAgICAgIGxhdCA9IGVjb2xlc19wb2x5Z29uc1tbcF1dWywyXSwKICAgICAgY29sb3IgPSByYWluYm93KGxlbmd0aChlY29sZXNfcG9seWdvbnMpKVtwXQogICAgKQp9CgptYXBfZWNvbGVzCmBgYAoKKkNsaXF1ZXIgc3VyIGxlcyBjZXJjbGVzIGJsZXVzIHBvdXIgdmlzdWFsaXNlciBsZSBub20gZGUgbCfDqXRhYmxpc3NlbWVudC4qCgojIyBDb25jbHVzaW9uCgpMJ29yZ2FuaXNhdGlvbiBkZSBsYSB2aWUgZW4gc29jacOpdMOpIGVzdCByYXJlbWVudCBsZSByw6lzdWx0YXQgZCd1bmUgZMOpbWFyY2hlIGV4Y2x1c2l2ZW1lbnQgc2NpZW50aWZpcXVlLCBjYXIgaW1wYWN0w6llIHBhciB1bmUgaGlzdG9pcmUgZXQgZGl2ZXJzZXMgaW5mbHVlbmNlcy4gQydlc3QgZG9uYyBzYW5zIHN1cnByaXNlIHF1ZSBsJ29uIGNvbnN0YXRlIHVuIMOpY2FydCBlbnRyZSBsYSBjYXJ0ZSBzY29sYWlyZSBhY3R1ZWxsZSwgZXQgY2VsbGUgb2J0ZW51ZSBwYXIgc2VnbWVudGF0aW9uLgoKQ2V0dGUgw6l0dWRlIGF1cmEgw6l0w6kgbCdvY2Nhc2lvbiBkJ3V0aWxpc2VyIGRlcyAiYWxnb3JpdGhtZXMiIHRlbHMgcXVlICoqay1tZWFucyoqLCAqKmstbmVhcmVzdC1uZWlnaGJvcioqIGV0ICoqZGlhZ3JhbW1lcyBkZSBWb3Jvbm/DryoqIGF2ZWMgbGUgbG9naWNpZWwgKipSKiosIHN1ciBsYSBiYXNlIGRlIGRvbm7DqWVzIGlzc3VlcyBkZSBsJyoqT3BlbiBEYXRhKiogZGUgUmVubmVzIE3DqXRyb3BvbGUuCgo8IS0tClRPRE86IMOpY2FydCBlbnRyZSB6b25lIGttZWFucyBldCB6b25lIHLDqWVsbGUKVE9ETzogdHJvdXZlciBsZXMgem9uZXMgYWZmZWN0w6llcyDDoCBjaGFxdWUgYWRyZXNzZQoKU2VjdGV1ciAxLTMgOiBodHRwOi8vd3d3LmlhMzUuYWMtcmVubmVzLmZyL2phaGlhL0phaGlhL3NpdGUvaWEzNS9waWQvNjczOQoKU2VjdG9yaXNhdGlvbiAoMjAxNSkKaHR0cDovL3d3dy5pYTM1LmFjLXJlbm5lcy5mci9qYWhpYS9KYWhpYS9sYW5nL2ZyL3BpZC8xNzkxMQotLT4K