This notebook uses a heuristic to compute a nonconvex polygon of “minimal” area containing a specified set of points. It is motivated by a question on OR Stack Exchange.

We are going to assume that the solution must be a Hamiltonian tour of a subset of the given points such that all remaining points are contained in the closed polygon defined by that tour. The original question is a bit unspecific as to what is sought. A single piecewise-linear path visiting all points (possibly more than once) would generate a set of area 0 containing the points, but this is likely not what was intended.

library(ggplot2)   # used for plotting
library(plotly)    # for plots with "tooltips" identifying points
library(interp)    # for generating triangulations

To start, we generate a set of points and plot it.

# Set the random number seed for reproducibility.
seedValue <- 20211020
set.seed(seedValue)
# Set the size of the point set.
npoints <- 100
# Generate a data frame of points.
points <- runif(2*npoints) |>
          matrix(nrow = npoints) |>
          data.frame()
colnames(points) <- c("x", "y")
points$index <- 1:npoints
# Plot the points.
basePlot <- ggplot(data = points, mapping = aes(x, y, label = index)) + geom_point()
ggplotly(basePlot)

We compute a triangulation of the points and plot that.

triangulation <- tri.mesh(points[, 1:2])
plot(triangulation)

The triangulation provides the convex hull (as a “tour” of the original points), which we identify and add it to the plot.

# Compute the convex hull.
boundary <- triangulation$chull
# Close the tour by repeating the first point.
tour <- c(boundary, boundary[1])
# Compute the area of the convex hull, using the triangle areas computed by tri.mesh.
cat("Hull area = ", sum(triangulation$cclist[, "area"]), "\n")
Hull area =  0.8651773 
# Add the tour to the point plot and display it.
hullPlot <- basePlot + geom_path(points[tour, ], mapping = aes(x, y))
plot(hullPlot)

Before continuing, we build some convenience functions. The first one takes as input the indices of the endpoints of an edge and returns the index of that edge in the edge list belonging to the triangulation. Note that, in the triangulation, edges are oriented (arcs). To compensate, we try both orientations of the edge.

# Inputs: i, j = indices of two points
# Output: the index in triangulation$arcs of the arc between those points
find.edge <- function(i, j) {
  temp <- apply(triangulation$arcs, 1, function(x) (x[1] == i && x[2] == j) || (x[1] == j && x[2] == i))
  which(temp)[1]
}

The next function takes an edge index and finds the indices of all triangles containing that edge

# Input: the index of an edge
# Output: the indices of all triangles containing that edge
find.triangles <- function(i) {
  c(which(triangulation$trlist[, "k1"] == i),
    which(triangulation$trlist[, "k2"] == i),
    which(triangulation$trlist[, "k3"] == i))
}

The following function takes a triangle index and returns the indices of the vertices of that triangle.

# Input the index of a triangle
# Output: the indices of the vertices of the triangle
find.vertices <- function(i) {
  triangulation$trlist[i, c("i1", "i2", "i3")]
}

In what follows, we will be removing edges and triangles. In order to avoid corrupting the original triangulation, we maintain a separate vector showing the number of triangles currently sharing each edge, along with vectors recording the indices of edges and triangles targeted for deletion.

deleted.edges <- c()            # edges to delete
deleted.triangles <- c()        # triangles to delete
n.edges <- triangulation$narcs  # number of edges in the original triangulation
# Count how often each edge is shared.
sharing <- sapply(1:n.edges, function(x) length(find.triangles(x)))
# For future use, list the columns in the triangle matrix that give the indices of the edges in a triangle.
edge.cols <- c("k1", "k2", "k3")

The logic of our heuristic is as follows. Consider an edge [a, b] belonging to the current boundary. It must belong to a single triangle, say [a, b, c]. Now assume that c is not currently on the boundary. If we replace [a, b] with [a, c] and [c, b] on the boundary and delete triangle [a, b, c] from the triangulation (adding point c to the boundary), we reduce the area of the polygon without losing any points. The requirement that c not already be on the boundary stems from the need for the boundary to be a Hamiltonian path (each point visited exactly once).

We now define a function that takes a lap around the boundary. For each qualifying edge (belonging to only one triangle, with the third vertex not yet on the boundary), it identifies the triangle containing it, replaces the qualifying edge with the other two edges of the triangle, and deletes the qualifying edge and its parent triangle. The function returns true if any changes were made, false if not.

# Input: none
# Output: true if at least one change to the boundary was made
shrink <- function() {
  change <- FALSE
  ix <- 1  # index of the first boundary point in the segment being considered
  while (ix < length(tour)) {
    # Find the index of the edge in question.
    ie <- find.edge(tour[ix], tour[ix + 1])
    # Find the parent triangle, removing any deleted triangles.
    owner <- setdiff(find.triangles(ie), deleted.triangles)
    # Sanity check: a boundary edge should be owned by exactly one triangle.
    if (length(owner) != 1) {
      stop("Boundary edge ", ie, " owned by triangle(s) ", as.character(owner))
    }
    # Identify the third vertex of the triangle.
    v <- setdiff(find.vertices(owner), c(tour[ix], tour[ix + 1]))
    # If the third vertex is already on the boundary, punt.
    if (v %in% boundary) {
      ix <- ix + 1
      next
    }
    # Mark the edge and the parent triangle for deletion.
    deleted.edges <<- c(deleted.edges, ie)
    deleted.triangles <<- c(deleted.triangles, owner)
    # Insert the new vertex in the tour.
    tour <<- c(tour[1:ix], v, tour[-(1:ix)])
    # Update the boundary.
    boundary <<- head(tour, -1)
    # Note the change.
    change <- TRUE
    # Move on.
    ix <- ix + 1
  }
  change
}

We repeatedly call the shrink function until it takes a full lap around the boundary without making any changes.

ct <- 1                # counts changes
while (shrink()) {
  cat("Pass ",  ct, " changed the boundary\n")
  ct <- ct + 1
}
Pass  1  changed the boundary
Pass  2  changed the boundary
Pass  3  changed the boundary
Pass  4  changed the boundary
Pass  5  changed the boundary
Pass  6  changed the boundary
Pass  7  changed the boundary
cat("No change after pass ", ct, "\n")
No change after pass  8 

Plot the revised boundary.

hullPlot2 <- basePlot + geom_path(points[tour, ], mapping = aes(x, y))
plot(hullPlot2)

Copy the original triangulation, remove the deleted elements, and display the result.

# Make a copy of the triangulation.
temp <- triangulation
# Remove the deleted triangles.
temp$trlist <- temp$trlist[-deleted.triangles, ]
temp$nt <- nrow(temp$trlist)
temp$cclist <- temp$cclist[-deleted.triangles, ]
# Removing arcs would cause inconsistencies in arc numbering, so instead we set them to NA, which prevents them from plotting.
temp$arcs[deleted.edges, ] <- NA
# Compute the area contained by the surviving triangles.
cat("Revised area = ", sum(temp$cclist[, "area"]), "\n")
Revised area =  0.5106936 
# Plot the surviving triangles.
plot(temp)

Plot what is left.

# Start with a copy of the polygon plot.
shadedPlot <- basePlot + geom_path(points[tour, ], mapping = aes(x, y))
# Shade the surviving triangles.
tlist <- setdiff(1:triangulation$nt, deleted.triangles)
for (i in tlist) {
  temp <- triangulation$trlist[i, 1:3]
  temp <- c(temp, temp[1])
  shadedPlot <- shadedPlot + geom_polygon(points[temp, ], mapping = aes(x, y), fill = "lightblue", alpha = 0.5)
}
plot(shadedPlot)

LS0tCnRpdGxlOiAiSHVsbCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBub3RlYm9vayB1c2VzIGEgaGV1cmlzdGljIHRvIGNvbXB1dGUgYSBub25jb252ZXggcG9seWdvbiBvZiAibWluaW1hbCIgYXJlYSBjb250YWluaW5nIGEgc3BlY2lmaWVkIHNldCBvZiBwb2ludHMuIEl0IGlzIG1vdGl2YXRlZCBieSBhIFtxdWVzdGlvbl0oaHR0cHM6Ly9vci5zdGFja2V4Y2hhbmdlLmNvbS9xdWVzdGlvbnMvNzE1OC9ob3ctdG8tZmluZC10aGUtcG9pbnQtb24tdGhlLWV4dGVyaW9yLW9mLWEtZ2l2ZW4tc2V0LW9mLXBvaW50cykgb24gT1IgU3RhY2sgRXhjaGFuZ2UuCgpXZSBhcmUgZ29pbmcgdG8gYXNzdW1lIHRoYXQgdGhlIHNvbHV0aW9uIG11c3QgYmUgYSBIYW1pbHRvbmlhbiB0b3VyIG9mIGEgc3Vic2V0IG9mIHRoZSBnaXZlbiBwb2ludHMgc3VjaCB0aGF0IGFsbCByZW1haW5pbmcgcG9pbnRzIGFyZSBjb250YWluZWQgaW4gdGhlIGNsb3NlZCBwb2x5Z29uIGRlZmluZWQgYnkgdGhhdCB0b3VyLiBUaGUgb3JpZ2luYWwgcXVlc3Rpb24gaXMgYSBiaXQgdW5zcGVjaWZpYyBhcyB0byB3aGF0IGlzIHNvdWdodC4gQSBzaW5nbGUgcGllY2V3aXNlLWxpbmVhciBwYXRoIHZpc2l0aW5nIGFsbCBwb2ludHMgKHBvc3NpYmx5IG1vcmUgdGhhbiBvbmNlKSB3b3VsZCBnZW5lcmF0ZSBhIHNldCBvZiBhcmVhIDAgY29udGFpbmluZyB0aGUgcG9pbnRzLCBidXQgdGhpcyBpcyBsaWtlbHkgbm90IHdoYXQgd2FzIGludGVuZGVkLgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikgICAjIHVzZWQgZm9yIHBsb3R0aW5nCmxpYnJhcnkocGxvdGx5KSAgICAjIGZvciBwbG90cyB3aXRoICJ0b29sdGlwcyIgaWRlbnRpZnlpbmcgcG9pbnRzCmxpYnJhcnkoaW50ZXJwKSAgICAjIGZvciBnZW5lcmF0aW5nIHRyaWFuZ3VsYXRpb25zCmBgYAoKVG8gc3RhcnQsIHdlIGdlbmVyYXRlIGEgc2V0IG9mIHBvaW50cyBhbmQgcGxvdCBpdC4KCmBgYHtyfQojIFNldCB0aGUgcmFuZG9tIG51bWJlciBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkuCnNlZWRWYWx1ZSA8LSAyMDIxMTAyMApzZXQuc2VlZChzZWVkVmFsdWUpCiMgU2V0IHRoZSBzaXplIG9mIHRoZSBwb2ludCBzZXQuCm5wb2ludHMgPC0gMTAwCiMgR2VuZXJhdGUgYSBkYXRhIGZyYW1lIG9mIHBvaW50cy4KcG9pbnRzIDwtIHJ1bmlmKDIqbnBvaW50cykgfD4KICAgICAgICAgIG1hdHJpeChucm93ID0gbnBvaW50cykgfD4KICAgICAgICAgIGRhdGEuZnJhbWUoKQpjb2xuYW1lcyhwb2ludHMpIDwtIGMoIngiLCAieSIpCnBvaW50cyRpbmRleCA8LSAxOm5wb2ludHMKIyBQbG90IHRoZSBwb2ludHMuCmJhc2VQbG90IDwtIGdncGxvdChkYXRhID0gcG9pbnRzLCBtYXBwaW5nID0gYWVzKHgsIHksIGxhYmVsID0gaW5kZXgpKSArIGdlb21fcG9pbnQoKQpnZ3Bsb3RseShiYXNlUGxvdCkKYGBgCgoKV2UgY29tcHV0ZSBhIHRyaWFuZ3VsYXRpb24gb2YgdGhlIHBvaW50cyBhbmQgcGxvdCB0aGF0LgoKYGBge3J9CnRyaWFuZ3VsYXRpb24gPC0gdHJpLm1lc2gocG9pbnRzWywgMToyXSkKcGxvdCh0cmlhbmd1bGF0aW9uKQpgYGAKClRoZSB0cmlhbmd1bGF0aW9uIHByb3ZpZGVzIHRoZSBjb252ZXggaHVsbCAoYXMgYSAidG91ciIgb2YgdGhlIG9yaWdpbmFsIHBvaW50cyksIHdoaWNoIHdlIGlkZW50aWZ5IGFuZCBhZGQgaXQgdG8gdGhlIHBsb3QuCgpgYGB7cn0KIyBDb21wdXRlIHRoZSBjb252ZXggaHVsbC4KYm91bmRhcnkgPC0gdHJpYW5ndWxhdGlvbiRjaHVsbAojIENsb3NlIHRoZSB0b3VyIGJ5IHJlcGVhdGluZyB0aGUgZmlyc3QgcG9pbnQuCnRvdXIgPC0gYyhib3VuZGFyeSwgYm91bmRhcnlbMV0pCiMgQ29tcHV0ZSB0aGUgYXJlYSBvZiB0aGUgY29udmV4IGh1bGwsIHVzaW5nIHRoZSB0cmlhbmdsZSBhcmVhcyBjb21wdXRlZCBieSB0cmkubWVzaC4KY2F0KCJIdWxsIGFyZWEgPSAiLCBzdW0odHJpYW5ndWxhdGlvbiRjY2xpc3RbLCAiYXJlYSJdKSwgIlxuIikKIyBBZGQgdGhlIHRvdXIgdG8gdGhlIHBvaW50IHBsb3QgYW5kIGRpc3BsYXkgaXQuCmh1bGxQbG90IDwtIGJhc2VQbG90ICsgZ2VvbV9wYXRoKHBvaW50c1t0b3VyLCBdLCBtYXBwaW5nID0gYWVzKHgsIHkpKQpgYGAKCmBgYHtyfQpwbG90KGh1bGxQbG90KQpgYGAKCkJlZm9yZSBjb250aW51aW5nLCB3ZSBidWlsZCBzb21lIGNvbnZlbmllbmNlIGZ1bmN0aW9ucy4gVGhlIGZpcnN0IG9uZSB0YWtlcyBhcyBpbnB1dCB0aGUgaW5kaWNlcyBvZiB0aGUgZW5kcG9pbnRzIG9mIGFuIGVkZ2UgYW5kIHJldHVybnMgdGhlIGluZGV4IG9mIHRoYXQgZWRnZSBpbiB0aGUgZWRnZSBsaXN0IGJlbG9uZ2luZyB0byB0aGUgdHJpYW5ndWxhdGlvbi4gTm90ZSB0aGF0LCBpbiB0aGUgdHJpYW5ndWxhdGlvbiwgZWRnZXMgYXJlIG9yaWVudGVkIChhcmNzKS4gVG8gY29tcGVuc2F0ZSwgd2UgdHJ5IGJvdGggb3JpZW50YXRpb25zIG9mIHRoZSBlZGdlLgoKYGBge3J9CiMgSW5wdXRzOiBpLCBqID0gaW5kaWNlcyBvZiB0d28gcG9pbnRzCiMgT3V0cHV0OiB0aGUgaW5kZXggaW4gdHJpYW5ndWxhdGlvbiRhcmNzIG9mIHRoZSBhcmMgYmV0d2VlbiB0aG9zZSBwb2ludHMKZmluZC5lZGdlIDwtIGZ1bmN0aW9uKGksIGopIHsKICB0ZW1wIDwtIGFwcGx5KHRyaWFuZ3VsYXRpb24kYXJjcywgMSwgZnVuY3Rpb24oeCkgKHhbMV0gPT0gaSAmJiB4WzJdID09IGopIHx8ICh4WzFdID09IGogJiYgeFsyXSA9PSBpKSkKICB3aGljaCh0ZW1wKVsxXQp9CmBgYAoKVGhlIG5leHQgZnVuY3Rpb24gdGFrZXMgYW4gZWRnZSBpbmRleCBhbmQgZmluZHMgdGhlIGluZGljZXMgb2YgYWxsIHRyaWFuZ2xlcyBjb250YWluaW5nIHRoYXQgZWRnZQoKYGBge3J9CiMgSW5wdXQ6IHRoZSBpbmRleCBvZiBhbiBlZGdlCiMgT3V0cHV0OiB0aGUgaW5kaWNlcyBvZiBhbGwgdHJpYW5nbGVzIGNvbnRhaW5pbmcgdGhhdCBlZGdlCmZpbmQudHJpYW5nbGVzIDwtIGZ1bmN0aW9uKGkpIHsKICBjKHdoaWNoKHRyaWFuZ3VsYXRpb24kdHJsaXN0WywgImsxIl0gPT0gaSksCiAgICB3aGljaCh0cmlhbmd1bGF0aW9uJHRybGlzdFssICJrMiJdID09IGkpLAogICAgd2hpY2godHJpYW5ndWxhdGlvbiR0cmxpc3RbLCAiazMiXSA9PSBpKSkKfQpgYGAKClRoZSBmb2xsb3dpbmcgZnVuY3Rpb24gdGFrZXMgYSB0cmlhbmdsZSBpbmRleCBhbmQgcmV0dXJucyB0aGUgaW5kaWNlcyBvZiB0aGUgdmVydGljZXMgb2YgdGhhdCB0cmlhbmdsZS4KCmBgYHtyfQojIElucHV0IHRoZSBpbmRleCBvZiBhIHRyaWFuZ2xlCiMgT3V0cHV0OiB0aGUgaW5kaWNlcyBvZiB0aGUgdmVydGljZXMgb2YgdGhlIHRyaWFuZ2xlCmZpbmQudmVydGljZXMgPC0gZnVuY3Rpb24oaSkgewogIHRyaWFuZ3VsYXRpb24kdHJsaXN0W2ksIGMoImkxIiwgImkyIiwgImkzIildCn0KYGBgCgpJbiB3aGF0IGZvbGxvd3MsIHdlIHdpbGwgYmUgcmVtb3ZpbmcgZWRnZXMgYW5kIHRyaWFuZ2xlcy4gSW4gb3JkZXIgdG8gYXZvaWQgY29ycnVwdGluZyB0aGUgb3JpZ2luYWwgdHJpYW5ndWxhdGlvbiwgd2UgbWFpbnRhaW4gYSBzZXBhcmF0ZSB2ZWN0b3Igc2hvd2luZyB0aGUgbnVtYmVyIG9mIHRyaWFuZ2xlcyBjdXJyZW50bHkgc2hhcmluZyBlYWNoIGVkZ2UsIGFsb25nIHdpdGggdmVjdG9ycyByZWNvcmRpbmcgdGhlIGluZGljZXMgb2YgZWRnZXMgYW5kIHRyaWFuZ2xlcyB0YXJnZXRlZCBmb3IgZGVsZXRpb24uCgpgYGB7cn0KZGVsZXRlZC5lZGdlcyA8LSBjKCkgICAgICAgICAgICAjIGVkZ2VzIHRvIGRlbGV0ZQpkZWxldGVkLnRyaWFuZ2xlcyA8LSBjKCkgICAgICAgICMgdHJpYW5nbGVzIHRvIGRlbGV0ZQpuLmVkZ2VzIDwtIHRyaWFuZ3VsYXRpb24kbmFyY3MgICMgbnVtYmVyIG9mIGVkZ2VzIGluIHRoZSBvcmlnaW5hbCB0cmlhbmd1bGF0aW9uCiMgQ291bnQgaG93IG9mdGVuIGVhY2ggZWRnZSBpcyBzaGFyZWQuCnNoYXJpbmcgPC0gc2FwcGx5KDE6bi5lZGdlcywgZnVuY3Rpb24oeCkgbGVuZ3RoKGZpbmQudHJpYW5nbGVzKHgpKSkKIyBGb3IgZnV0dXJlIHVzZSwgbGlzdCB0aGUgY29sdW1ucyBpbiB0aGUgdHJpYW5nbGUgbWF0cml4IHRoYXQgZ2l2ZSB0aGUgaW5kaWNlcyBvZiB0aGUgZWRnZXMgaW4gYSB0cmlhbmdsZS4KZWRnZS5jb2xzIDwtIGMoImsxIiwgImsyIiwgImszIikKYGBgCgpUaGUgbG9naWMgb2Ygb3VyIGhldXJpc3RpYyBpcyBhcyBmb2xsb3dzLiBDb25zaWRlciBhbiBlZGdlIFthLCBiXSBiZWxvbmdpbmcgdG8gdGhlIGN1cnJlbnQgYm91bmRhcnkuIEl0IG11c3QgYmVsb25nIHRvIGEgc2luZ2xlIHRyaWFuZ2xlLCBzYXkgW2EsIGIsIGNdLiBOb3cgYXNzdW1lIHRoYXQgYyBpcyBub3QgY3VycmVudGx5IG9uIHRoZSBib3VuZGFyeS4gSWYgd2UgcmVwbGFjZSBbYSwgYl0gd2l0aCBbYSwgY10gYW5kIFtjLCBiXSBvbiB0aGUgYm91bmRhcnkgYW5kIGRlbGV0ZSB0cmlhbmdsZSBbYSwgYiwgY10gZnJvbSB0aGUgdHJpYW5ndWxhdGlvbiAoYWRkaW5nIHBvaW50IGMgdG8gdGhlIGJvdW5kYXJ5KSwgd2UgcmVkdWNlIHRoZSBhcmVhIG9mIHRoZSBwb2x5Z29uIHdpdGhvdXQgbG9zaW5nIGFueSBwb2ludHMuIFRoZSByZXF1aXJlbWVudCB0aGF0IGMgbm90IGFscmVhZHkgYmUgb24gdGhlIGJvdW5kYXJ5IHN0ZW1zIGZyb20gdGhlIG5lZWQgZm9yIHRoZSBib3VuZGFyeSB0byBiZSBhIEhhbWlsdG9uaWFuIHBhdGggKGVhY2ggcG9pbnQgdmlzaXRlZCBleGFjdGx5IG9uY2UpLgoKV2Ugbm93IGRlZmluZSBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMgYSBsYXAgYXJvdW5kIHRoZSBib3VuZGFyeS4gRm9yIGVhY2ggcXVhbGlmeWluZyBlZGdlIChiZWxvbmdpbmcgdG8gb25seSBvbmUgdHJpYW5nbGUsIHdpdGggdGhlIHRoaXJkIHZlcnRleCBub3QgeWV0IG9uIHRoZSBib3VuZGFyeSksIGl0IGlkZW50aWZpZXMgdGhlIHRyaWFuZ2xlIGNvbnRhaW5pbmcgaXQsIHJlcGxhY2VzIHRoZSBxdWFsaWZ5aW5nIGVkZ2Ugd2l0aCB0aGUgb3RoZXIgdHdvIGVkZ2VzIG9mIHRoZSB0cmlhbmdsZSwgYW5kIGRlbGV0ZXMgdGhlIHF1YWxpZnlpbmcgZWRnZSBhbmQgaXRzIHBhcmVudCB0cmlhbmdsZS4gVGhlIGZ1bmN0aW9uIHJldHVybnMgdHJ1ZSBpZiBhbnkgY2hhbmdlcyB3ZXJlIG1hZGUsIGZhbHNlIGlmIG5vdC4KCmBgYHtyfQojIElucHV0OiBub25lCiMgT3V0cHV0OiB0cnVlIGlmIGF0IGxlYXN0IG9uZSBjaGFuZ2UgdG8gdGhlIGJvdW5kYXJ5IHdhcyBtYWRlCnNocmluayA8LSBmdW5jdGlvbigpIHsKICBjaGFuZ2UgPC0gRkFMU0UKICBpeCA8LSAxICAjIGluZGV4IG9mIHRoZSBmaXJzdCBib3VuZGFyeSBwb2ludCBpbiB0aGUgc2VnbWVudCBiZWluZyBjb25zaWRlcmVkCiAgd2hpbGUgKGl4IDwgbGVuZ3RoKHRvdXIpKSB7CiAgICAjIEZpbmQgdGhlIGluZGV4IG9mIHRoZSBlZGdlIGluIHF1ZXN0aW9uLgogICAgaWUgPC0gZmluZC5lZGdlKHRvdXJbaXhdLCB0b3VyW2l4ICsgMV0pCiAgICAjIEZpbmQgdGhlIHBhcmVudCB0cmlhbmdsZSwgcmVtb3ZpbmcgYW55IGRlbGV0ZWQgdHJpYW5nbGVzLgogICAgb3duZXIgPC0gc2V0ZGlmZihmaW5kLnRyaWFuZ2xlcyhpZSksIGRlbGV0ZWQudHJpYW5nbGVzKQogICAgIyBTYW5pdHkgY2hlY2s6IGEgYm91bmRhcnkgZWRnZSBzaG91bGQgYmUgb3duZWQgYnkgZXhhY3RseSBvbmUgdHJpYW5nbGUuCiAgICBpZiAobGVuZ3RoKG93bmVyKSAhPSAxKSB7CiAgICAgIHN0b3AoIkJvdW5kYXJ5IGVkZ2UgIiwgaWUsICIgb3duZWQgYnkgdHJpYW5nbGUocykgIiwgYXMuY2hhcmFjdGVyKG93bmVyKSkKICAgIH0KICAgICMgSWRlbnRpZnkgdGhlIHRoaXJkIHZlcnRleCBvZiB0aGUgdHJpYW5nbGUuCiAgICB2IDwtIHNldGRpZmYoZmluZC52ZXJ0aWNlcyhvd25lciksIGModG91cltpeF0sIHRvdXJbaXggKyAxXSkpCiAgICAjIElmIHRoZSB0aGlyZCB2ZXJ0ZXggaXMgYWxyZWFkeSBvbiB0aGUgYm91bmRhcnksIHB1bnQuCiAgICBpZiAodiAlaW4lIGJvdW5kYXJ5KSB7CiAgICAgIGl4IDwtIGl4ICsgMQogICAgICBuZXh0CiAgICB9CiAgICAjIE1hcmsgdGhlIGVkZ2UgYW5kIHRoZSBwYXJlbnQgdHJpYW5nbGUgZm9yIGRlbGV0aW9uLgogICAgZGVsZXRlZC5lZGdlcyA8PC0gYyhkZWxldGVkLmVkZ2VzLCBpZSkKICAgIGRlbGV0ZWQudHJpYW5nbGVzIDw8LSBjKGRlbGV0ZWQudHJpYW5nbGVzLCBvd25lcikKICAgICMgSW5zZXJ0IHRoZSBuZXcgdmVydGV4IGluIHRoZSB0b3VyLgogICAgdG91ciA8PC0gYyh0b3VyWzE6aXhdLCB2LCB0b3VyWy0oMTppeCldKQogICAgIyBVcGRhdGUgdGhlIGJvdW5kYXJ5LgogICAgYm91bmRhcnkgPDwtIGhlYWQodG91ciwgLTEpCiAgICAjIE5vdGUgdGhlIGNoYW5nZS4KICAgIGNoYW5nZSA8LSBUUlVFCiAgICAjIE1vdmUgb24uCiAgICBpeCA8LSBpeCArIDEKICB9CiAgY2hhbmdlCn0KYGBgCgpXZSByZXBlYXRlZGx5IGNhbGwgdGhlIGBzaHJpbmtgIGZ1bmN0aW9uIHVudGlsIGl0IHRha2VzIGEgZnVsbCBsYXAgYXJvdW5kIHRoZSBib3VuZGFyeSB3aXRob3V0IG1ha2luZyBhbnkgY2hhbmdlcy4KCmBgYHtyfQpjdCA8LSAxICAgICAgICAgICAgICAgICMgY291bnRzIGNoYW5nZXMKd2hpbGUgKHNocmluaygpKSB7CiAgY2F0KCJQYXNzICIsICBjdCwgIiBjaGFuZ2VkIHRoZSBib3VuZGFyeVxuIikKICBjdCA8LSBjdCArIDEKfQpjYXQoIk5vIGNoYW5nZSBhZnRlciBwYXNzICIsIGN0LCAiXG4iKQpgYGAKClBsb3QgdGhlIHJldmlzZWQgYm91bmRhcnkuCgpgYGB7cn0KaHVsbFBsb3QyIDwtIGJhc2VQbG90ICsgZ2VvbV9wYXRoKHBvaW50c1t0b3VyLCBdLCBtYXBwaW5nID0gYWVzKHgsIHkpKQpwbG90KGh1bGxQbG90MikKYGBgCgpDb3B5IHRoZSBvcmlnaW5hbCB0cmlhbmd1bGF0aW9uLCByZW1vdmUgdGhlIGRlbGV0ZWQgZWxlbWVudHMsIGFuZCBkaXNwbGF5IHRoZSByZXN1bHQuCgpgYGB7cn0KIyBNYWtlIGEgY29weSBvZiB0aGUgdHJpYW5ndWxhdGlvbi4KdGVtcCA8LSB0cmlhbmd1bGF0aW9uCiMgUmVtb3ZlIHRoZSBkZWxldGVkIHRyaWFuZ2xlcy4KdGVtcCR0cmxpc3QgPC0gdGVtcCR0cmxpc3RbLWRlbGV0ZWQudHJpYW5nbGVzLCBdCnRlbXAkbnQgPC0gbnJvdyh0ZW1wJHRybGlzdCkKdGVtcCRjY2xpc3QgPC0gdGVtcCRjY2xpc3RbLWRlbGV0ZWQudHJpYW5nbGVzLCBdCiMgUmVtb3ZpbmcgYXJjcyB3b3VsZCBjYXVzZSBpbmNvbnNpc3RlbmNpZXMgaW4gYXJjIG51bWJlcmluZywgc28gaW5zdGVhZCB3ZSBzZXQgdGhlbSB0byBOQSwgd2hpY2ggcHJldmVudHMgdGhlbSBmcm9tIHBsb3R0aW5nLgp0ZW1wJGFyY3NbZGVsZXRlZC5lZGdlcywgXSA8LSBOQQojIENvbXB1dGUgdGhlIGFyZWEgY29udGFpbmVkIGJ5IHRoZSBzdXJ2aXZpbmcgdHJpYW5nbGVzLgpjYXQoIlJldmlzZWQgYXJlYSA9ICIsIHN1bSh0ZW1wJGNjbGlzdFssICJhcmVhIl0pLCAiXG4iKQpgYGAKYGBge3J9CiMgUGxvdCB0aGUgc3Vydml2aW5nIHRyaWFuZ2xlcy4KcGxvdCh0ZW1wKQpgYGAKClBsb3Qgd2hhdCBpcyBsZWZ0LgoKYGBge3J9CiMgU3RhcnQgd2l0aCBhIGNvcHkgb2YgdGhlIHBvbHlnb24gcGxvdC4Kc2hhZGVkUGxvdCA8LSBiYXNlUGxvdCArIGdlb21fcGF0aChwb2ludHNbdG91ciwgXSwgbWFwcGluZyA9IGFlcyh4LCB5KSkKIyBTaGFkZSB0aGUgc3Vydml2aW5nIHRyaWFuZ2xlcy4KdGxpc3QgPC0gc2V0ZGlmZigxOnRyaWFuZ3VsYXRpb24kbnQsIGRlbGV0ZWQudHJpYW5nbGVzKQpmb3IgKGkgaW4gdGxpc3QpIHsKICB0ZW1wIDwtIHRyaWFuZ3VsYXRpb24kdHJsaXN0W2ksIDE6M10KICB0ZW1wIDwtIGModGVtcCwgdGVtcFsxXSkKICBzaGFkZWRQbG90IDwtIHNoYWRlZFBsb3QgKyBnZW9tX3BvbHlnb24ocG9pbnRzW3RlbXAsIF0sIG1hcHBpbmcgPSBhZXMoeCwgeSksIGZpbGwgPSAibGlnaHRibHVlIiwgYWxwaGEgPSAwLjUpCn0KcGxvdChzaGFkZWRQbG90KQpgYGAKCg==