Introduction

The problem treated here involves assigning disjoint sets of items to a smaller number of collections such that (a) each set goes into one collection in its entirety (i.e., all elements of a set end up in the same collection) and (b) the collection sizes are as close to each other as is possible. The source of the problem is a question on Mathematics Stack Exchange.

Before beginning, we load a few packages. The OMPR package provides a domain specific language (DSL) for writing linear and integer programming models in R. It uses the ROI package to interface to a solver, which here will be CPLEX. (This in turn requires that the Rcplex package be installed.) The dplyr package is used for data manipulation, and the tictoc package for calculating execution times.

library(ROI)
ROI: R Optimization Infrastructure
Registered solver plugins: nlminb, cplex.
Default solver: auto.
library(ROI.plugin.cplex)
library(ompr)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
library(ompr.roi)
library(dplyr)

Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(tictoc)

Problem setup

We’ll pick some arbitrary problem dimensions and randomly assign items to sets. Note: The distribution of items across sets is fairly uniform due to the use of the sample() function with default probabilities. Creating a distribution of set sizes with heavier tales might change the performance of any or all of the solution methods shown here.

nItems <- 5723      # total number of items
nSets <- 137        # the number of original sets
nCollections <- 10  # the number of collections to create
# Set a seed for reproducibility.
set.seed(81620)
# Define set memberships for the items.
# To ensure no empty sets, we start with an item in each set and randomly assign the remaining items.
setID <- c(1:nSets, sample(1:nSets, nItems - nSets, replace = TRUE))
# Convert the set IDs into cardinalities for each set.
cardinality <- as.vector(table(setID))

Test function

To catch possible errors, we will use a function that takes as input a vector of collection assignments (where the i-th vector entry is the index of the collection into which set i is assigned) and outputs the sizes of the collections.

sizeOf <- function(x) {
    suppressMessages({                    # suppresses an annoying warning
    data.frame(c = cardinality, p = x) %>%
    group_by(p) %>%
    summarize(psize = sum(c)) %>% 
    pull(psize)
  })
}

For convenience, we define another function that gives the difference between the largest and smallest sizes of the collections.

spreadOf <- function(x) {
  temp <- range(sizeOf(x))
  temp[2] - temp[1]
}

Greedy Heuristic

The greedy heuristic assigns sets in descending size order, putting each set in the currently smallest collection.

tic("greedy")
# Store the collection index of each set in a vector (initially zeroes).
collection.index <- rep(0, nSets)
# Keep a running list of collection sizes.
collection.size <- rep(0, nCollections)
# Get the descending cardinality sort order for the sets.
sort.index <- order(cardinality, decreasing = TRUE)
# Assign sets to collections.
for (i in 1:nSets) {
  j <- sort.index[i]               # index of the set to be assigned
  k <- which.min(collection.size)  # index of the currently smallest collection
  collection.index[j] <- k
  collection.size[k] <- collection.size[k] + cardinality[j] # update the size
}
toc()
greedy: 0.038 sec elapsed

Show the range of collection sizes.

cat("Collection size range: ", range(collection.size), "\n")
Collection size range:  552 582 
cat("Spread = ", spreadOf(collection.index), "\n")
Spread =  30 

Show the individual collection sizes and the spread.

sizeOf(collection.index)
 [1] 582 582 582 581 580 578 552 552 552 582

Pairwise interchange

The pairwise interchange heuristic attempts to shrink the range in collection sizes by swapping a member of the smallest collection (A) with a member of a larger collection (B) so as to increase the size of A without shrinking B so much that it is as small as, or smaller than, the starting size of A.

tic("Improvement heuristic")
retry <- TRUE
# Make a working copy of the first heuristic's solution.
collection.2.index <- collection.index
collection.2.size <- sizeOf(collection.2.index)
spread <- spreadOf(collection.2.index)  # to be reduced/minimized
while (retry) {
  retry <- FALSE
  # Find the smallest collection (i) and its size (s).
  i <- which.min(collection.2.size)
  i.size <- collection.2.size[i]
  # Identify the smallest set in collection i. This is the target for swapping.
  in.i <- which(collection.2.index == i)   # sets in collection i
  g <- in.i[which.min(cardinality[in.i])]  # the set to swap
  g.size <- cardinality[g]                 # the size of the set to be moved
  # Identify the indices of larger collections and sort them into descending size order.
  bigger <- which(collection.2.size > i.size)
  temp <- collection.2.size[bigger]
  temp <- order(temp, decreasing = TRUE)
  bigger <- bigger[temp]
  # Process the larger collections in descending size order until a valid swap is found.
  for (j in bigger) {
    j.size <- collection.2.size[j]
    # Get the difference in collection sizes, which is a bound on the difference in size between the two sets being swapped.
    bd <- collection.2.size[j] - i.size    # bound on swap size
    # Get the indices of the sets in collection j.
    available <- which(collection.2.index == j)
    # Select the largest set that is bigger than g and but not bigger than g plus the bound on the swap difference.
    temp <- cardinality[available]
    available <- available[which(temp > g.size & temp < g.size + bd)]
    # Check whether any set is eligible to be swapped.
    if (length(available) >= 1) {
      # Find the largest eligible set.
      g2 <- available[which.max(cardinality[available])]
      g2.size <- cardinality[g2]
      # Swap g and g2.
      collection.2.index[g] <- j
      collection.2.index[g2] <- i
      collection.2.size[i] <- i.size - g.size + g2.size
      collection.2.size[j] <- j.size + g.size - g2.size
      retry <- TRUE
      break # Break out of the for loop and start over, looking for another swap.
    }
  }
}
toc()
Improvement heuristic: 0.111 sec elapsed
cat("Improvement heuristic spread = ", spreadOf(collection.2.index), "\n")
Improvement heuristic spread =  3 
sizeOf(collection.2.index)
 [1] 572 572 574 573 572 571 572 572 572 573

Integer program

Assign sets to collections to minimize the range of collection sizes, using a mixed integer linear program.

tic("IP")
model <- 
  MIPModel() %>% 
  # x[i, j] = 1 iff set i is assigned to collection j.
  add_variable(x[i, j], i = 1:nSets, j = 1:nCollections, type = "binary") %>% 
  # size[j] is the size of collection j.
  add_variable(size[j], j = 1:nCollections, lb = 0, ub = nItems) %>% 
  # least and most are the sizes of the smallest and largest collections.
  add_variable(least, lb = 0, ub = nItems) %>% 
  add_variable(most, lb = 0, ub = nItems) %>% 
  # The objective is to minimize the spread between smallest and largest collections.
  set_objective(most - least, "min") %>% 
  # Every set must be assigned to exactly one collection.
  add_constraint(sum_expr(x[i, j], j = 1:nCollections) == 1, i = 1:nSets) %>% 
  # The size of a collection is the sum of the cardinalities of the member sets.
  add_constraint(sum_expr(cardinality[i] * x[i, j], i = 1:nSets) == size[j], j = 1:nCollections) %>% 
  # Every collection size is between least and most.
  add_constraint(size[j] <= most, j = 1:nCollections) %>% 
  add_constraint(size[j] >= least, j = 1:nCollections) %>% 
  # To mitigate symmetry with respect to the indexing of the collections, we require the collection sizes to be nondecreasing.
  add_constraint(size[j] >= size[j - 1], j = 2:nCollections)

  adding constraints [====>-------------------------------]  14% eta  1s
  adding constraints [====>-------------------------------]  15% eta  1s
  adding constraints [=====>------------------------------]  15% eta  1s
  adding constraints [=====>------------------------------]  16% eta  1s
  adding constraints [=====>------------------------------]  17% eta  1s
  adding constraints [=====>------------------------------]  18% eta  1s
  adding constraints [======>-----------------------------]  18% eta  1s
  adding constraints [======>-----------------------------]  19% eta  1s
  adding constraints [======>-----------------------------]  20% eta  1s
  adding constraints [=======>----------------------------]  21% eta  1s
  adding constraints [=======>----------------------------]  22% eta  1s
  adding constraints [=======>----------------------------]  23% eta  1s
  adding constraints [========>---------------------------]  24% eta  1s
  adding constraints [========>---------------------------]  25% eta  1s
  adding constraints [========>---------------------------]  26% eta  1s
  adding constraints [=========>--------------------------]  27% eta  1s
  adding constraints [=========>--------------------------]  28% eta  1s
  adding constraints [==========>-------------------------]  29% eta  1s
  adding constraints [==========>-------------------------]  30% eta  1s
  adding constraints [==========>-------------------------]  31% eta  1s
  adding constraints [===========>------------------------]  32% eta  1s
  adding constraints [===========>------------------------]  33% eta  1s
  adding constraints [===========>------------------------]  34% eta  1s
  adding constraints [============>-----------------------]  35% eta  1s
  adding constraints [============>-----------------------]  36% eta  1s
  adding constraints [============>-----------------------]  37% eta  1s
  adding constraints [=============>----------------------]  38% eta  1s
  adding constraints [=============>----------------------]  39% eta  1s
  adding constraints [=============>----------------------]  40% eta  1s
  adding constraints [==============>---------------------]  41% eta  1s
  adding constraints [==============>---------------------]  42% eta  1s
  adding constraints [===============>--------------------]  43% eta  1s
  adding constraints [===============>--------------------]  44% eta  1s
  adding constraints [===============>--------------------]  45% eta  1s
  adding constraints [================>-------------------]  46% eta  1s
  adding constraints [================>-------------------]  47% eta  1s
  adding constraints [================>-------------------]  48% eta  1s
  adding constraints [=================>------------------]  49% eta  1s
  adding constraints [=================>------------------]  50% eta  1s
  adding constraints [=================>------------------]  51% eta  1s
  adding constraints [==================>-----------------]  52% eta  0s
  adding constraints [==================>-----------------]  53% eta  0s
  adding constraints [==================>-----------------]  54% eta  0s
  adding constraints [===================>----------------]  55% eta  0s
  adding constraints [===================>----------------]  56% eta  0s
  adding constraints [===================>----------------]  57% eta  0s
  adding constraints [====================>---------------]  58% eta  0s
  adding constraints [====================>---------------]  59% eta  0s
  adding constraints [=====================>--------------]  60% eta  0s
  adding constraints [=====================>--------------]  61% eta  0s
  adding constraints [=====================>--------------]  62% eta  0s
  adding constraints [======================>-------------]  63% eta  0s
  adding constraints [======================>-------------]  64% eta  0s
  adding constraints [======================>-------------]  65% eta  0s
  adding constraints [=======================>------------]  66% eta  0s
  adding constraints [=======================>------------]  67% eta  0s
  adding constraints [=======================>------------]  68% eta  0s
  adding constraints [========================>-----------]  69% eta  0s
  adding constraints [========================>-----------]  70% eta  0s
  adding constraints [========================>-----------]  71% eta  0s
  adding constraints [=========================>----------]  72% eta  0s
  adding constraints [=========================>----------]  73% eta  0s
  adding constraints [==========================>---------]  74% eta  0s
  adding constraints [==========================>---------]  75% eta  0s
  adding constraints [==========================>---------]  76% eta  0s
  adding constraints [===========================>--------]  77% eta  0s
  adding constraints [===========================>--------]  78% eta  0s
  adding constraints [===========================>--------]  79% eta  0s
  adding constraints [============================>-------]  80% eta  0s
  adding constraints [============================>-------]  81% eta  0s
  adding constraints [============================>-------]  82% eta  0s
  adding constraints [=============================>------]  82% eta  0s
  adding constraints [=============================>------]  83% eta  0s
  adding constraints [=============================>------]  84% eta  0s
  adding constraints [=============================>------]  85% eta  0s
  adding constraints [==============================>-----]  85% eta  0s
  adding constraints [==============================>-----]  86% eta  0s
  adding constraints [==============================>-----]  87% eta  0s
  adding constraints [===============================>----]  88% eta  0s
  adding constraints [===============================>----]  89% eta  0s
  adding constraints [===============================>----]  90% eta  0s
  adding constraints [================================>---]  91% eta  0s
  adding constraints [================================>---]  92% eta  0s
  adding constraints [================================>---]  93% eta  0s
  adding constraints [=================================>--]  93% eta  0s
  adding constraints [=================================>--]  94% eta  0s
  adding constraints [=================================>--]  95% eta  0s
  adding constraints [=================================>--]  96% eta  0s
  adding constraints [==================================>-]  96% eta  0s
  adding constraints [==================================>-]  97% eta  0s
  adding constraints [==================================>-]  98% eta  0s
  adding constraints [==================================>-]  99% eta  0s
  adding constraints [===================================>]  99% eta  0s
  adding constraints [====================================] 100% eta  0s
                                                                        

  adding constraints [==========>-------------------------]  30% eta  1s
  adding constraints [=============>----------------------]  40% eta  1s
  adding constraints [=================>------------------]  50% eta  0s
  adding constraints [=====================>--------------]  60% eta  0s
  adding constraints [========================>-----------]  70% eta  0s
  adding constraints [============================>-------]  80% eta  0s
  adding constraints [===============================>----]  90% eta  0s
  adding constraints [====================================] 100% eta  0s
                                                                        

Solve the model.

modelObject <<- as_ROI_model(model)
solution <- model %>% solve_model(with_ROI(solver = "cplex"))
CPLEX environment opened
Closed CPLEX environment
toc()
IP: 20.943 sec elapsed

Display the sizes of the smallest and largest collections.

cat("Smallest collection size = ", get_solution(solution, least), "\n")
Smallest collection size =  572 
cat("Largest collection size = ", get_solution(solution, most), "\n")
Largest collection size =  573 

Get the collection assignments as a vector.

temp <-
  get_solution(solution, x[i, j]) %>% 
  filter(value == 1) %>% 
  arrange(i) %>% 
  select(j)
collection.3.index <- temp$j

Show the individual collection sizes and their spread.

sizeOf(collection.3.index)
 [1] 572 572 572 572 572 572 572 573 573 573
spreadOf(collection.3.index)
[1] 1
LS0tCnRpdGxlOiAiUGFydGl0aW9uaW5nIgphdXRob3I6IFBhdWwgQS4gUnViaW4KZGF0ZTogMTggQXVndXN0IDIwMjAKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBJbnRyb2R1Y3Rpb24KClRoZSBwcm9ibGVtIHRyZWF0ZWQgaGVyZSBpbnZvbHZlcyBhc3NpZ25pbmcgZGlzam9pbnQgc2V0cyBvZiBpdGVtcyB0byBhIHNtYWxsZXIgbnVtYmVyIG9mIGNvbGxlY3Rpb25zIHN1Y2ggdGhhdCAoYSkgZWFjaCBzZXQgZ29lcyBpbnRvIG9uZSBjb2xsZWN0aW9uIGluIGl0cyBlbnRpcmV0eSAoaS5lLiwgYWxsIGVsZW1lbnRzIG9mIGEgc2V0IGVuZCB1cCBpbiB0aGUgc2FtZSBjb2xsZWN0aW9uKSBhbmQgKGIpIHRoZSBjb2xsZWN0aW9uIHNpemVzIGFyZSBhcyBjbG9zZSB0byBlYWNoIG90aGVyIGFzIGlzIHBvc3NpYmxlLiBUaGUgc291cmNlIG9mIHRoZSBwcm9ibGVtIGlzIGEgW3F1ZXN0aW9uXShodHRwczovL21hdGguc3RhY2tleGNoYW5nZS5jb20vcXVlc3Rpb25zLzM3OTIxMTUvcGFydGl0aW9uLW4taXRlbXMtaW50by1rLWVxdWFsbHktc2l6ZWQtcGFydGl0aW9ucy13aGlsZS1yZXRhaW5pbmctZ3JvdXBzLWluLykgb24gTWF0aGVtYXRpY3MgU3RhY2sgRXhjaGFuZ2UuCgpCZWZvcmUgYmVnaW5uaW5nLCB3ZSBsb2FkIGEgZmV3IHBhY2thZ2VzLiBUaGUgYE9NUFJgIHBhY2thZ2UgcHJvdmlkZXMgYSBkb21haW4gc3BlY2lmaWMgbGFuZ3VhZ2UgKERTTCkgZm9yIHdyaXRpbmcgbGluZWFyIGFuZCBpbnRlZ2VyIHByb2dyYW1taW5nIG1vZGVscyBpbiBSLiBJdCB1c2VzIHRoZSBgUk9JYCBwYWNrYWdlIHRvIGludGVyZmFjZSB0byBhIHNvbHZlciwgd2hpY2ggaGVyZSB3aWxsIGJlIENQTEVYLiAoVGhpcyBpbiB0dXJuIHJlcXVpcmVzIHRoYXQgdGhlIGBSY3BsZXhgIHBhY2thZ2UgYmUgaW5zdGFsbGVkLikgVGhlIGBkcGx5cmAgcGFja2FnZSBpcyB1c2VkIGZvciBkYXRhIG1hbmlwdWxhdGlvbiwgYW5kIHRoZSBgdGljdG9jYCBwYWNrYWdlIGZvciBjYWxjdWxhdGluZyBleGVjdXRpb24gdGltZXMuCgpgYGB7cn0KbGlicmFyeShST0kpCmxpYnJhcnkoUk9JLnBsdWdpbi5jcGxleCkKbGlicmFyeShvbXByKQpsaWJyYXJ5KG9tcHIucm9pKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpY3RvYykKYGBgCgojIFByb2JsZW0gc2V0dXAKCldlJ2xsIHBpY2sgc29tZSBhcmJpdHJhcnkgcHJvYmxlbSBkaW1lbnNpb25zIGFuZCByYW5kb21seSBhc3NpZ24gaXRlbXMgdG8gc2V0cy4gTm90ZTogVGhlIGRpc3RyaWJ1dGlvbiBvZiBpdGVtcyBhY3Jvc3Mgc2V0cyBpcyBmYWlybHkgdW5pZm9ybSBkdWUgdG8gdGhlIHVzZSBvZiB0aGUgYHNhbXBsZSgpYCBmdW5jdGlvbiB3aXRoIGRlZmF1bHQgcHJvYmFiaWxpdGllcy4gQ3JlYXRpbmcgYSBkaXN0cmlidXRpb24gb2Ygc2V0IHNpemVzIHdpdGggaGVhdmllciB0YWxlcyBtaWdodCBjaGFuZ2UgdGhlIHBlcmZvcm1hbmNlIG9mIGFueSBvciBhbGwgb2YgdGhlIHNvbHV0aW9uIG1ldGhvZHMgc2hvd24gaGVyZS4KCmBgYHtyfQpuSXRlbXMgPC0gNTcyMyAgICAgICMgdG90YWwgbnVtYmVyIG9mIGl0ZW1zCm5TZXRzIDwtIDEzNyAgICAgICAgIyB0aGUgbnVtYmVyIG9mIG9yaWdpbmFsIHNldHMKbkNvbGxlY3Rpb25zIDwtIDEwICAjIHRoZSBudW1iZXIgb2YgY29sbGVjdGlvbnMgdG8gY3JlYXRlCiMgU2V0IGEgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5LgpzZXQuc2VlZCg4MTYyMCkKIyBEZWZpbmUgc2V0IG1lbWJlcnNoaXBzIGZvciB0aGUgaXRlbXMuCiMgVG8gZW5zdXJlIG5vIGVtcHR5IHNldHMsIHdlIHN0YXJ0IHdpdGggYW4gaXRlbSBpbiBlYWNoIHNldCBhbmQgcmFuZG9tbHkgYXNzaWduIHRoZSByZW1haW5pbmcgaXRlbXMuCnNldElEIDwtIGMoMTpuU2V0cywgc2FtcGxlKDE6blNldHMsIG5JdGVtcyAtIG5TZXRzLCByZXBsYWNlID0gVFJVRSkpCiMgQ29udmVydCB0aGUgc2V0IElEcyBpbnRvIGNhcmRpbmFsaXRpZXMgZm9yIGVhY2ggc2V0LgpjYXJkaW5hbGl0eSA8LSBhcy52ZWN0b3IodGFibGUoc2V0SUQpKQpgYGAKCiMgVGVzdCBmdW5jdGlvbgoKVG8gY2F0Y2ggcG9zc2libGUgZXJyb3JzLCB3ZSB3aWxsIHVzZSBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMgYXMgaW5wdXQgYSB2ZWN0b3Igb2YgY29sbGVjdGlvbiBhc3NpZ25tZW50cyAod2hlcmUgdGhlIGktdGggdmVjdG9yIGVudHJ5IGlzIHRoZSBpbmRleCBvZiB0aGUgY29sbGVjdGlvbiBpbnRvIHdoaWNoIHNldCBpIGlzIGFzc2lnbmVkKSBhbmQgb3V0cHV0cyB0aGUgc2l6ZXMgb2YgdGhlIGNvbGxlY3Rpb25zLgoKYGBge3J9CnNpemVPZiA8LSBmdW5jdGlvbih4KSB7CiAgICBzdXBwcmVzc01lc3NhZ2VzKHsgICAgICAgICAgICAgICAgICAgICMgc3VwcHJlc3NlcyBhbiBhbm5veWluZyB3YXJuaW5nCiAgICBkYXRhLmZyYW1lKGMgPSBjYXJkaW5hbGl0eSwgcCA9IHgpICU+JQogICAgZ3JvdXBfYnkocCkgJT4lCiAgICBzdW1tYXJpemUocHNpemUgPSBzdW0oYykpICU+JSAKICAgIHB1bGwocHNpemUpCiAgfSkKfQpgYGAKCkZvciBjb252ZW5pZW5jZSwgd2UgZGVmaW5lIGFub3RoZXIgZnVuY3Rpb24gdGhhdCBnaXZlcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBsYXJnZXN0IGFuZCBzbWFsbGVzdCBzaXplcyBvZiB0aGUgY29sbGVjdGlvbnMuCgpgYGB7cn0Kc3ByZWFkT2YgPC0gZnVuY3Rpb24oeCkgewogIHRlbXAgPC0gcmFuZ2Uoc2l6ZU9mKHgpKQogIHRlbXBbMl0gLSB0ZW1wWzFdCn0KYGBgCgojIEdyZWVkeSBIZXVyaXN0aWMKClRoZSBncmVlZHkgaGV1cmlzdGljIGFzc2lnbnMgc2V0cyBpbiBkZXNjZW5kaW5nIHNpemUgb3JkZXIsIHB1dHRpbmcgZWFjaCBzZXQgaW4gdGhlIGN1cnJlbnRseSBzbWFsbGVzdCBjb2xsZWN0aW9uLgoKYGBge3J9CnRpYygiZ3JlZWR5IikKIyBTdG9yZSB0aGUgY29sbGVjdGlvbiBpbmRleCBvZiBlYWNoIHNldCBpbiBhIHZlY3RvciAoaW5pdGlhbGx5IHplcm9lcykuCmNvbGxlY3Rpb24uaW5kZXggPC0gcmVwKDAsIG5TZXRzKQojIEtlZXAgYSBydW5uaW5nIGxpc3Qgb2YgY29sbGVjdGlvbiBzaXplcy4KY29sbGVjdGlvbi5zaXplIDwtIHJlcCgwLCBuQ29sbGVjdGlvbnMpCiMgR2V0IHRoZSBkZXNjZW5kaW5nIGNhcmRpbmFsaXR5IHNvcnQgb3JkZXIgZm9yIHRoZSBzZXRzLgpzb3J0LmluZGV4IDwtIG9yZGVyKGNhcmRpbmFsaXR5LCBkZWNyZWFzaW5nID0gVFJVRSkKIyBBc3NpZ24gc2V0cyB0byBjb2xsZWN0aW9ucy4KZm9yIChpIGluIDE6blNldHMpIHsKICBqIDwtIHNvcnQuaW5kZXhbaV0gICAgICAgICAgICAgICAjIGluZGV4IG9mIHRoZSBzZXQgdG8gYmUgYXNzaWduZWQKICBrIDwtIHdoaWNoLm1pbihjb2xsZWN0aW9uLnNpemUpICAjIGluZGV4IG9mIHRoZSBjdXJyZW50bHkgc21hbGxlc3QgY29sbGVjdGlvbgogIGNvbGxlY3Rpb24uaW5kZXhbal0gPC0gawogIGNvbGxlY3Rpb24uc2l6ZVtrXSA8LSBjb2xsZWN0aW9uLnNpemVba10gKyBjYXJkaW5hbGl0eVtqXSAjIHVwZGF0ZSB0aGUgc2l6ZQp9CnRvYygpCmBgYAoKU2hvdyB0aGUgcmFuZ2Ugb2YgY29sbGVjdGlvbiBzaXplcy4KCmBgYHtyfQpjYXQoIkNvbGxlY3Rpb24gc2l6ZSByYW5nZTogIiwgcmFuZ2UoY29sbGVjdGlvbi5zaXplKSwgIlxuIikKY2F0KCJTcHJlYWQgPSAiLCBzcHJlYWRPZihjb2xsZWN0aW9uLmluZGV4KSwgIlxuIikKYGBgCgpTaG93IHRoZSBpbmRpdmlkdWFsIGNvbGxlY3Rpb24gc2l6ZXMgYW5kIHRoZSBzcHJlYWQuCgpgYGB7cn0Kc2l6ZU9mKGNvbGxlY3Rpb24uaW5kZXgpCmBgYAoKIyBQYWlyd2lzZSBpbnRlcmNoYW5nZQoKVGhlIHBhaXJ3aXNlIGludGVyY2hhbmdlIGhldXJpc3RpYyBhdHRlbXB0cyB0byBzaHJpbmsgdGhlIHJhbmdlIGluIGNvbGxlY3Rpb24gc2l6ZXMgYnkgc3dhcHBpbmcgYSBtZW1iZXIgb2YgdGhlIHNtYWxsZXN0IGNvbGxlY3Rpb24gKEEpIHdpdGggYSBtZW1iZXIgb2YgYSBsYXJnZXIgY29sbGVjdGlvbiAoQikgc28gYXMgdG8gaW5jcmVhc2UgdGhlIHNpemUgb2YgQSB3aXRob3V0IHNocmlua2luZyBCIHNvIG11Y2ggdGhhdCBpdCBpcyBhcyBzbWFsbCBhcywgb3Igc21hbGxlciB0aGFuLCB0aGUgc3RhcnRpbmcgc2l6ZSBvZiBBLgoKYGBge3J9CnRpYygiSW1wcm92ZW1lbnQgaGV1cmlzdGljIikKcmV0cnkgPC0gVFJVRQojIE1ha2UgYSB3b3JraW5nIGNvcHkgb2YgdGhlIGZpcnN0IGhldXJpc3RpYydzIHNvbHV0aW9uLgpjb2xsZWN0aW9uLjIuaW5kZXggPC0gY29sbGVjdGlvbi5pbmRleApjb2xsZWN0aW9uLjIuc2l6ZSA8LSBzaXplT2YoY29sbGVjdGlvbi4yLmluZGV4KQpzcHJlYWQgPC0gc3ByZWFkT2YoY29sbGVjdGlvbi4yLmluZGV4KSAgIyB0byBiZSByZWR1Y2VkL21pbmltaXplZAp3aGlsZSAocmV0cnkpIHsKICByZXRyeSA8LSBGQUxTRQogICMgRmluZCB0aGUgc21hbGxlc3QgY29sbGVjdGlvbiAoaSkgYW5kIGl0cyBzaXplIChzKS4KICBpIDwtIHdoaWNoLm1pbihjb2xsZWN0aW9uLjIuc2l6ZSkKICBpLnNpemUgPC0gY29sbGVjdGlvbi4yLnNpemVbaV0KICAjIElkZW50aWZ5IHRoZSBzbWFsbGVzdCBzZXQgaW4gY29sbGVjdGlvbiBpLiBUaGlzIGlzIHRoZSB0YXJnZXQgZm9yIHN3YXBwaW5nLgogIGluLmkgPC0gd2hpY2goY29sbGVjdGlvbi4yLmluZGV4ID09IGkpICAgIyBzZXRzIGluIGNvbGxlY3Rpb24gaQogIGcgPC0gaW4uaVt3aGljaC5taW4oY2FyZGluYWxpdHlbaW4uaV0pXSAgIyB0aGUgc2V0IHRvIHN3YXAKICBnLnNpemUgPC0gY2FyZGluYWxpdHlbZ10gICAgICAgICAgICAgICAgICMgdGhlIHNpemUgb2YgdGhlIHNldCB0byBiZSBtb3ZlZAogICMgSWRlbnRpZnkgdGhlIGluZGljZXMgb2YgbGFyZ2VyIGNvbGxlY3Rpb25zIGFuZCBzb3J0IHRoZW0gaW50byBkZXNjZW5kaW5nIHNpemUgb3JkZXIuCiAgYmlnZ2VyIDwtIHdoaWNoKGNvbGxlY3Rpb24uMi5zaXplID4gaS5zaXplKQogIHRlbXAgPC0gY29sbGVjdGlvbi4yLnNpemVbYmlnZ2VyXQogIHRlbXAgPC0gb3JkZXIodGVtcCwgZGVjcmVhc2luZyA9IFRSVUUpCiAgYmlnZ2VyIDwtIGJpZ2dlclt0ZW1wXQogICMgUHJvY2VzcyB0aGUgbGFyZ2VyIGNvbGxlY3Rpb25zIGluIGRlc2NlbmRpbmcgc2l6ZSBvcmRlciB1bnRpbCBhIHZhbGlkIHN3YXAgaXMgZm91bmQuCiAgZm9yIChqIGluIGJpZ2dlcikgewogICAgai5zaXplIDwtIGNvbGxlY3Rpb24uMi5zaXplW2pdCiAgICAjIEdldCB0aGUgZGlmZmVyZW5jZSBpbiBjb2xsZWN0aW9uIHNpemVzLCB3aGljaCBpcyBhIGJvdW5kIG9uIHRoZSBkaWZmZXJlbmNlIGluIHNpemUgYmV0d2VlbiB0aGUgdHdvIHNldHMgYmVpbmcgc3dhcHBlZC4KICAgIGJkIDwtIGNvbGxlY3Rpb24uMi5zaXplW2pdIC0gaS5zaXplICAgICMgYm91bmQgb24gc3dhcCBzaXplCiAgICAjIEdldCB0aGUgaW5kaWNlcyBvZiB0aGUgc2V0cyBpbiBjb2xsZWN0aW9uIGouCiAgICBhdmFpbGFibGUgPC0gd2hpY2goY29sbGVjdGlvbi4yLmluZGV4ID09IGopCiAgICAjIFNlbGVjdCB0aGUgbGFyZ2VzdCBzZXQgdGhhdCBpcyBiaWdnZXIgdGhhbiBnIGFuZCBidXQgbm90IGJpZ2dlciB0aGFuIGcgcGx1cyB0aGUgYm91bmQgb24gdGhlIHN3YXAgZGlmZmVyZW5jZS4KICAgIHRlbXAgPC0gY2FyZGluYWxpdHlbYXZhaWxhYmxlXQogICAgYXZhaWxhYmxlIDwtIGF2YWlsYWJsZVt3aGljaCh0ZW1wID4gZy5zaXplICYgdGVtcCA8IGcuc2l6ZSArIGJkKV0KICAgICMgQ2hlY2sgd2hldGhlciBhbnkgc2V0IGlzIGVsaWdpYmxlIHRvIGJlIHN3YXBwZWQuCiAgICBpZiAobGVuZ3RoKGF2YWlsYWJsZSkgPj0gMSkgewogICAgICAjIEZpbmQgdGhlIGxhcmdlc3QgZWxpZ2libGUgc2V0LgogICAgICBnMiA8LSBhdmFpbGFibGVbd2hpY2gubWF4KGNhcmRpbmFsaXR5W2F2YWlsYWJsZV0pXQogICAgICBnMi5zaXplIDwtIGNhcmRpbmFsaXR5W2cyXQogICAgICAjIFN3YXAgZyBhbmQgZzIuCiAgICAgIGNvbGxlY3Rpb24uMi5pbmRleFtnXSA8LSBqCiAgICAgIGNvbGxlY3Rpb24uMi5pbmRleFtnMl0gPC0gaQogICAgICBjb2xsZWN0aW9uLjIuc2l6ZVtpXSA8LSBpLnNpemUgLSBnLnNpemUgKyBnMi5zaXplCiAgICAgIGNvbGxlY3Rpb24uMi5zaXplW2pdIDwtIGouc2l6ZSArIGcuc2l6ZSAtIGcyLnNpemUKICAgICAgcmV0cnkgPC0gVFJVRQogICAgICBicmVhayAjIEJyZWFrIG91dCBvZiB0aGUgZm9yIGxvb3AgYW5kIHN0YXJ0IG92ZXIsIGxvb2tpbmcgZm9yIGFub3RoZXIgc3dhcC4KICAgIH0KICB9Cn0KdG9jKCkKY2F0KCJJbXByb3ZlbWVudCBoZXVyaXN0aWMgc3ByZWFkID0gIiwgc3ByZWFkT2YoY29sbGVjdGlvbi4yLmluZGV4KSwgIlxuIikKc2l6ZU9mKGNvbGxlY3Rpb24uMi5pbmRleCkKYGBgCgojIEludGVnZXIgcHJvZ3JhbQoKQXNzaWduIHNldHMgdG8gY29sbGVjdGlvbnMgdG8gbWluaW1pemUgdGhlIHJhbmdlIG9mIGNvbGxlY3Rpb24gc2l6ZXMsIHVzaW5nIGEgbWl4ZWQgaW50ZWdlciBsaW5lYXIgcHJvZ3JhbS4KCmBgYHtyfQp0aWMoIklQIikKbW9kZWwgPC0gCiAgTUlQTW9kZWwoKSAlPiUgCiAgIyB4W2ksIGpdID0gMSBpZmYgc2V0IGkgaXMgYXNzaWduZWQgdG8gY29sbGVjdGlvbiBqLgogIGFkZF92YXJpYWJsZSh4W2ksIGpdLCBpID0gMTpuU2V0cywgaiA9IDE6bkNvbGxlY3Rpb25zLCB0eXBlID0gImJpbmFyeSIpICU+JSAKICAjIHNpemVbal0gaXMgdGhlIHNpemUgb2YgY29sbGVjdGlvbiBqLgogIGFkZF92YXJpYWJsZShzaXplW2pdLCBqID0gMTpuQ29sbGVjdGlvbnMsIGxiID0gMCwgdWIgPSBuSXRlbXMpICU+JSAKICAjIGxlYXN0IGFuZCBtb3N0IGFyZSB0aGUgc2l6ZXMgb2YgdGhlIHNtYWxsZXN0IGFuZCBsYXJnZXN0IGNvbGxlY3Rpb25zLgogIGFkZF92YXJpYWJsZShsZWFzdCwgbGIgPSAwLCB1YiA9IG5JdGVtcykgJT4lIAogIGFkZF92YXJpYWJsZShtb3N0LCBsYiA9IDAsIHViID0gbkl0ZW1zKSAlPiUgCiAgIyBUaGUgb2JqZWN0aXZlIGlzIHRvIG1pbmltaXplIHRoZSBzcHJlYWQgYmV0d2VlbiBzbWFsbGVzdCBhbmQgbGFyZ2VzdCBjb2xsZWN0aW9ucy4KICBzZXRfb2JqZWN0aXZlKG1vc3QgLSBsZWFzdCwgIm1pbiIpICU+JSAKICAjIEV2ZXJ5IHNldCBtdXN0IGJlIGFzc2lnbmVkIHRvIGV4YWN0bHkgb25lIGNvbGxlY3Rpb24uCiAgYWRkX2NvbnN0cmFpbnQoc3VtX2V4cHIoeFtpLCBqXSwgaiA9IDE6bkNvbGxlY3Rpb25zKSA9PSAxLCBpID0gMTpuU2V0cykgJT4lIAogICMgVGhlIHNpemUgb2YgYSBjb2xsZWN0aW9uIGlzIHRoZSBzdW0gb2YgdGhlIGNhcmRpbmFsaXRpZXMgb2YgdGhlIG1lbWJlciBzZXRzLgogIGFkZF9jb25zdHJhaW50KHN1bV9leHByKGNhcmRpbmFsaXR5W2ldICogeFtpLCBqXSwgaSA9IDE6blNldHMpID09IHNpemVbal0sIGogPSAxOm5Db2xsZWN0aW9ucykgJT4lIAogICMgRXZlcnkgY29sbGVjdGlvbiBzaXplIGlzIGJldHdlZW4gbGVhc3QgYW5kIG1vc3QuCiAgYWRkX2NvbnN0cmFpbnQoc2l6ZVtqXSA8PSBtb3N0LCBqID0gMTpuQ29sbGVjdGlvbnMpICU+JSAKICBhZGRfY29uc3RyYWludChzaXplW2pdID49IGxlYXN0LCBqID0gMTpuQ29sbGVjdGlvbnMpICU+JSAKICAjIFRvIG1pdGlnYXRlIHN5bW1ldHJ5IHdpdGggcmVzcGVjdCB0byB0aGUgaW5kZXhpbmcgb2YgdGhlIGNvbGxlY3Rpb25zLCB3ZSByZXF1aXJlIHRoZSBjb2xsZWN0aW9uIHNpemVzIHRvIGJlIG5vbmRlY3JlYXNpbmcuCiAgYWRkX2NvbnN0cmFpbnQoc2l6ZVtqXSA+PSBzaXplW2ogLSAxXSwgaiA9IDI6bkNvbGxlY3Rpb25zKQpgYGAKClNvbHZlIHRoZSBtb2RlbC4KCmBgYHtyfQptb2RlbE9iamVjdCA8PC0gYXNfUk9JX21vZGVsKG1vZGVsKQpzb2x1dGlvbiA8LSBtb2RlbCAlPiUgc29sdmVfbW9kZWwod2l0aF9ST0koc29sdmVyID0gImNwbGV4IikpCnRvYygpCmBgYAoKRGlzcGxheSB0aGUgc2l6ZXMgb2YgdGhlIHNtYWxsZXN0IGFuZCBsYXJnZXN0IGNvbGxlY3Rpb25zLgoKYGBge3J9CmNhdCgiU21hbGxlc3QgY29sbGVjdGlvbiBzaXplID0gIiwgZ2V0X3NvbHV0aW9uKHNvbHV0aW9uLCBsZWFzdCksICJcbiIpCmNhdCgiTGFyZ2VzdCBjb2xsZWN0aW9uIHNpemUgPSAiLCBnZXRfc29sdXRpb24oc29sdXRpb24sIG1vc3QpLCAiXG4iKQpgYGAKCkdldCB0aGUgY29sbGVjdGlvbiBhc3NpZ25tZW50cyBhcyBhIHZlY3Rvci4KCmBgYHtyfQp0ZW1wIDwtCiAgZ2V0X3NvbHV0aW9uKHNvbHV0aW9uLCB4W2ksIGpdKSAlPiUgCiAgZmlsdGVyKHZhbHVlID09IDEpICU+JSAKICBhcnJhbmdlKGkpICU+JSAKICBzZWxlY3QoaikKY29sbGVjdGlvbi4zLmluZGV4IDwtIHRlbXAkagpgYGAKClNob3cgdGhlIGluZGl2aWR1YWwgY29sbGVjdGlvbiBzaXplcyBhbmQgdGhlaXIgc3ByZWFkLgoKYGBge3J9CnNpemVPZihjb2xsZWN0aW9uLjMuaW5kZXgpCnNwcmVhZE9mKGNvbGxlY3Rpb24uMy5pbmRleCkKYGBgCgo=