Built using RStudio 1.0.35 notebook.

Problem

What is the pattern of the following equations?

Source: http://www.analyticbridge.com/forum/topics/interesting-math-brain-teaser

It takes a human few seconds/minutes to find the solution.

But, can we solve this problem using machine learning?

Machine Learning Solving

Dataset

(data <- data.frame(x1 = c(6, 9, 8, 5, 7, 9, 10, 15), x2 = c(4, 2, 5, 2, 6, 8, 6, 3), y = c(210, 711, 313, 37, 113, 117, 416, 1218)))

Multinomial Linear Regression

Let’s start by coding few helpers.

Compute models

get.predict <- function(model, train, test, max.degrees = 5) {
    lapply(
      1:max.degrees,
      function(d) {
        m <- model(train, d)
        suppressWarnings(round(predict(m, test)))
      }
    )
}
get.predict.lm <- function(train, test, max.degrees = 5) {
  get.predict(
    function(train, degree) {
      lm(y ~ poly(x1, degree = degree, raw = T) + poly(x2, degree = degree, raw = T), train)
    },
    train,
    test,
    max.degrees
  )
}

Compute errors

get.errors <- function(y, pred) {
  if (!is.list(pred)) {
    pred <- list(pred)
  }
  err <- lapply(
    pred,
    function(p) {
      sum((y - p) ^ 2) / length(y)
    }
  )
  df <- data.frame(
      degree = 1:length(err),
      error = unlist(err)
  )
  return(df)
}

Prediction

pred <- get.predict.lm(data, data[, c("x1", "x2")])
(err <- get.errors(data$y, pred))

Only 4 degrees are required to correctly predict result (error = 0).

Reasons are:

  • We used a (very) small dataset, which allows model to learn from it, and easily overfit.
  • We predicted with the same dataset used to train the model, which is not a good way to do in machine learning.

Dataset++

Let’s generate a bigger dataset.

get.dataset <- function(to = 20) {
    l <- lapply(
      1:to,
      function(x) {
        as.data.frame(
          t(
            sapply(
              1:x,
              function(y) {
                c(x, y, strtoi(paste0(ifelse(x == y, "", sprintf("%s", x - y)), sprintf("%s", x + y))))
              }
            )
          )
        )
      }
    )
    df <- do.call(rbind, l)
    names(df) <- c("x1", "x2", "y")
    
    return(df)
}
# Will generate a dataset of 10585 samples
data.pp <- get.dataset(150)
plot.bt <- function(d) {
  cols <- terrain.colors(max(d$y))
  plot(
    d$x1, d$x2,
    xlab = "x1", ylab = "x2",
    col = cols[d$y],
    pch = 19,
    cex.axis = .8,
    cex.lab = .7
  )
}
plot.bt(data.pp)

get.train.test <- function(data, train.pct = .8){
  train.idx <- sample(1:nrow(data), nrow(data) * train.pct)
  return(list(train = data[train.idx,], test = data[-train.idx,]))
}
eval.model <- function(f.pred, data, train.pct = .8, max.degrees = 20) {
  tt <- get.train.test(data, train.pct)
  train <- tt$train
  test <- tt$test
  
  pred <- f.pred(train, test[, c("x1", "x2")], max.degrees)
  err <- get.errors(test$y, pred)
  
  return(list(pred = pred, err = err))
}
eval.model.lm <- function(data, train.pct = .8, max.degrees = 20) {
  eval.model(
    function(train, test, max.degrees) {
      get.predict.lm(train, test, max.degrees = max.degrees)
    },
    data,
    train.pct = train.pct,
    max.degrees = max.degrees
  )
}
plot.eval.model <- function(em) {
  plot(
    em$err$degree,
    em$err$error,
    type = "b",
    ylim = c(0, max(em$err$error)),
    xlab = "degree",
    ylab = "error",
    sub = paste0("Smallest error = ", round(min(em$err$error))),
    pch = 19,
    cex.axis = .8,
    cex.lab = .7
  )
}
em.lm <- eval.model.lm(data.pp)
plot.eval.model(em.lm)

When we try to apply some machine learning with train/test dataset, multinomial linear regression doesn’t succeed in predicting correct values.

This is because there is no linearity in the pattern (due to the specific pattern (x1, x2) => (x1 - x2)(x1 + x2).

Feature Engineering

Let’s add 2 extra features (f1 and f2).

data.ppf <- data.pp
data.ppf$f1 <- data.ppf$x1 + data.ppf$x2
data.ppf$f2 <- as.integer((data.ppf$x1 - data.ppf$x2) * 10 ^ ceiling(log10(data.ppf$f1)))
data.ppf$x1 <- NULL
data.ppf$x2 <- NULL
names(data.ppf) <- c("y", "x1", "x2")
head(data.ppf, 20)
plot.bt(data.ppf)

em.ppf <- eval.model.lm(data.ppf)
plot.eval.model(em.ppf)

While still far from being perfect, feature engineering helped minimizing error (1.450495e+07 vs 1.00194e+08). Note better approximation is not surprising as new features include brain teaser logic.

Neural Network

Let’s try with a neural network, supposed to better suit non-linear problems.

Train neural network

Resources:

library(nnet)
maxs <- apply(data.pp, 2, max)
mins <- apply(data.pp, 2, min)
tt.nn <- get.train.test(as.data.frame(scale(data.pp, center = mins, scale = maxs)))
model.nn <- nnet(
  y ~ x1 + x2,
  data = tt.nn$train,
  size = 5,
  maxit = 500,
  trace = F
)

Prediction

Even if neural network got a lower error (3.628796e+06), we are still far from perfection, as its capacity to find the correct prediction is poor (2 exact guesses for 2265 items).

Conclusion

This concludes my attempt to predict this brain teaser using machine learning.

My feeling is machine learning is better in finding approximations than predicting exact values, as there is always some uncertainty.

I’m convinced there is a better way to do, but I must admit my knowledge is to limited to go further.

Please do not hesitate to contact me if you have remarks or suggestions. They will be very welcome.

LS0tCnRpdGxlOiAiQnJhaW4gVGVhc2VyIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKLS0tCgoqQnVpbHQgdXNpbmcgUlN0dWRpbyAxLjAuMzUgbm90ZWJvb2suKgoKIyMgUHJvYmxlbQoKV2hhdCBpcyB0aGUgcGF0dGVybiBvZiB0aGUgZm9sbG93aW5nIGVxdWF0aW9ucz8KCi0gNiArIDQgPSAyMTAKLSA5ICsgMiA9IDcxMQotIDggKyA1ID0gMzEzCi0gNSArIDIgPSAzNwotIDcgKyA2ID0gMTEzCi0gOSArIDggPSAxMTcKLSAxMCArIDYgPSA0MTYKLSAxNSArIDMgPSAxMjE4CgpTb3VyY2U6IGh0dHA6Ly93d3cuYW5hbHl0aWNicmlkZ2UuY29tL2ZvcnVtL3RvcGljcy9pbnRlcmVzdGluZy1tYXRoLWJyYWluLXRlYXNlcgoKSXQgdGFrZXMgYSBodW1hbiBmZXcgc2Vjb25kcy9taW51dGVzIHRvIGZpbmQgdGhlIHNvbHV0aW9uLgoKQnV0LCBjYW4gd2Ugc29sdmUgdGhpcyBwcm9ibGVtIHVzaW5nIG1hY2hpbmUgbGVhcm5pbmc/CgojIyBNYWNoaW5lIExlYXJuaW5nIFNvbHZpbmcKCiMjIyBEYXRhc2V0CgpgYGB7ciBkYXRhc2V0fQooZGF0YSA8LSBkYXRhLmZyYW1lKHgxID0gYyg2LCA5LCA4LCA1LCA3LCA5LCAxMCwgMTUpLCB4MiA9IGMoNCwgMiwgNSwgMiwgNiwgOCwgNiwgMyksIHkgPSBjKDIxMCwgNzExLCAzMTMsIDM3LCAxMTMsIDExNywgNDE2LCAxMjE4KSkpCmBgYAoKIyMjIE11bHRpbm9taWFsIExpbmVhciBSZWdyZXNzaW9uCgpMZXQncyBzdGFydCBieSBjb2RpbmcgZmV3IGhlbHBlcnMuCgojIyMjIENvbXB1dGUgbW9kZWxzCgpgYGB7ciBjb21wdXRlX21vZGVsc30KZ2V0LnByZWRpY3QgPC0gZnVuY3Rpb24obW9kZWwsIHRyYWluLCB0ZXN0LCBtYXguZGVncmVlcyA9IDUpIHsKICAgIGxhcHBseSgKICAgICAgMTptYXguZGVncmVlcywKICAgICAgZnVuY3Rpb24oZCkgewogICAgICAgIG0gPC0gbW9kZWwodHJhaW4sIGQpCiAgICAgICAgc3VwcHJlc3NXYXJuaW5ncyhyb3VuZChwcmVkaWN0KG0sIHRlc3QpKSkKICAgICAgfQogICAgKQp9CgpnZXQucHJlZGljdC5sbSA8LSBmdW5jdGlvbih0cmFpbiwgdGVzdCwgbWF4LmRlZ3JlZXMgPSA1KSB7CiAgZ2V0LnByZWRpY3QoCiAgICBmdW5jdGlvbih0cmFpbiwgZGVncmVlKSB7CiAgICAgIGxtKHkgfiBwb2x5KHgxLCBkZWdyZWUgPSBkZWdyZWUsIHJhdyA9IFQpICsgcG9seSh4MiwgZGVncmVlID0gZGVncmVlLCByYXcgPSBUKSwgdHJhaW4pCiAgICB9LAogICAgdHJhaW4sCiAgICB0ZXN0LAogICAgbWF4LmRlZ3JlZXMKICApCn0KYGBgCgojIyMjIENvbXB1dGUgZXJyb3JzCgpgYGB7ciBjb21wdXRlX2Vycm9yc30KZ2V0LmVycm9ycyA8LSBmdW5jdGlvbih5LCBwcmVkKSB7CiAgaWYgKCFpcy5saXN0KHByZWQpKSB7CiAgICBwcmVkIDwtIGxpc3QocHJlZCkKICB9CiAgZXJyIDwtIGxhcHBseSgKICAgIHByZWQsCiAgICBmdW5jdGlvbihwKSB7CiAgICAgIHN1bSgoeSAtIHApIF4gMikgLyBsZW5ndGgoeSkKICAgIH0KICApCiAgZGYgPC0gZGF0YS5mcmFtZSgKICAgICAgZGVncmVlID0gMTpsZW5ndGgoZXJyKSwKICAgICAgZXJyb3IgPSB1bmxpc3QoZXJyKQogICkKICByZXR1cm4oZGYpCn0KYGBgCgojIyMjIFByZWRpY3Rpb24KCmBgYHtyfQpwcmVkIDwtIGdldC5wcmVkaWN0LmxtKGRhdGEsIGRhdGFbLCBjKCJ4MSIsICJ4MiIpXSkKKGVyciA8LSBnZXQuZXJyb3JzKGRhdGEkeSwgcHJlZCkpCmBgYAoKT25seSA0IGRlZ3JlZXMgYXJlIHJlcXVpcmVkIHRvIGNvcnJlY3RseSBwcmVkaWN0IHJlc3VsdCAoZXJyb3IgPSAwKS4KClJlYXNvbnMgYXJlOgoKLSBXZSB1c2VkIGEgKHZlcnkpIHNtYWxsIGRhdGFzZXQsIHdoaWNoIGFsbG93cyBtb2RlbCB0byBsZWFybiBmcm9tIGl0LCBhbmQgZWFzaWx5IG92ZXJmaXQuCi0gV2UgcHJlZGljdGVkIHdpdGggdGhlIHNhbWUgZGF0YXNldCB1c2VkIHRvIHRyYWluIHRoZSBtb2RlbCwgd2hpY2ggaXMgbm90IGEgZ29vZCB3YXkgdG8gZG8gaW4gbWFjaGluZSBsZWFybmluZy4KCiMjIyBEYXRhc2V0KysKCkxldCdzIGdlbmVyYXRlIGEgYmlnZ2VyIGRhdGFzZXQuCgpgYGB7ciBkYXRhc2V0X3BsdXNwbHVzfQpnZXQuZGF0YXNldCA8LSBmdW5jdGlvbih0byA9IDIwKSB7CiAgICBsIDwtIGxhcHBseSgKICAgICAgMTp0bywKICAgICAgZnVuY3Rpb24oeCkgewogICAgICAgIGFzLmRhdGEuZnJhbWUoCiAgICAgICAgICB0KAogICAgICAgICAgICBzYXBwbHkoCiAgICAgICAgICAgICAgMTp4LAogICAgICAgICAgICAgIGZ1bmN0aW9uKHkpIHsKICAgICAgICAgICAgICAgIGMoeCwgeSwgc3RydG9pKHBhc3RlMChpZmVsc2UoeCA9PSB5LCAiIiwgc3ByaW50ZigiJXMiLCB4IC0geSkpLCBzcHJpbnRmKCIlcyIsIHggKyB5KSkpKQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgKQogICAgICAgICAgKQogICAgICAgICkKICAgICAgfQogICAgKQoKICAgIGRmIDwtIGRvLmNhbGwocmJpbmQsIGwpCiAgICBuYW1lcyhkZikgPC0gYygieDEiLCAieDIiLCAieSIpCiAgICAKICAgIHJldHVybihkZikKfQpgYGAKCmBgYHtyfQojIFdpbGwgZ2VuZXJhdGUgYSBkYXRhc2V0IG9mIDEwNTg1IHNhbXBsZXMKZGF0YS5wcCA8LSBnZXQuZGF0YXNldCgxNTApCmBgYAoKYGBge3IgdGVycmFpbl9wbG90fQpwbG90LmJ0IDwtIGZ1bmN0aW9uKGQpIHsKICBjb2xzIDwtIHRlcnJhaW4uY29sb3JzKG1heChkJHkpKQogIHBsb3QoCiAgICBkJHgxLCBkJHgyLAogICAgeGxhYiA9ICJ4MSIsIHlsYWIgPSAieDIiLAogICAgY29sID0gY29sc1tkJHldLAogICAgcGNoID0gMTksCiAgICBjZXguYXhpcyA9IC44LAogICAgY2V4LmxhYiA9IC43CiAgKQp9CmBgYAoKYGBge3J9CnBsb3QuYnQoZGF0YS5wcCkKYGBgCgpgYGB7cn0KZ2V0LnRyYWluLnRlc3QgPC0gZnVuY3Rpb24oZGF0YSwgdHJhaW4ucGN0ID0gLjgpewogIHRyYWluLmlkeCA8LSBzYW1wbGUoMTpucm93KGRhdGEpLCBucm93KGRhdGEpICogdHJhaW4ucGN0KQoKICByZXR1cm4obGlzdCh0cmFpbiA9IGRhdGFbdHJhaW4uaWR4LF0sIHRlc3QgPSBkYXRhWy10cmFpbi5pZHgsXSkpCn0KCmV2YWwubW9kZWwgPC0gZnVuY3Rpb24oZi5wcmVkLCBkYXRhLCB0cmFpbi5wY3QgPSAuOCwgbWF4LmRlZ3JlZXMgPSAyMCkgewogIHR0IDwtIGdldC50cmFpbi50ZXN0KGRhdGEsIHRyYWluLnBjdCkKICB0cmFpbiA8LSB0dCR0cmFpbgogIHRlc3QgPC0gdHQkdGVzdAogIAogIHByZWQgPC0gZi5wcmVkKHRyYWluLCB0ZXN0WywgYygieDEiLCAieDIiKV0sIG1heC5kZWdyZWVzKQogIGVyciA8LSBnZXQuZXJyb3JzKHRlc3QkeSwgcHJlZCkKICAKICByZXR1cm4obGlzdChwcmVkID0gcHJlZCwgZXJyID0gZXJyKSkKfQoKZXZhbC5tb2RlbC5sbSA8LSBmdW5jdGlvbihkYXRhLCB0cmFpbi5wY3QgPSAuOCwgbWF4LmRlZ3JlZXMgPSAyMCkgewogIGV2YWwubW9kZWwoCiAgICBmdW5jdGlvbih0cmFpbiwgdGVzdCwgbWF4LmRlZ3JlZXMpIHsKICAgICAgZ2V0LnByZWRpY3QubG0odHJhaW4sIHRlc3QsIG1heC5kZWdyZWVzID0gbWF4LmRlZ3JlZXMpCiAgICB9LAogICAgZGF0YSwKICAgIHRyYWluLnBjdCA9IHRyYWluLnBjdCwKICAgIG1heC5kZWdyZWVzID0gbWF4LmRlZ3JlZXMKICApCn0KCnBsb3QuZXZhbC5tb2RlbCA8LSBmdW5jdGlvbihlbSkgewogIHBsb3QoCiAgICBlbSRlcnIkZGVncmVlLAogICAgZW0kZXJyJGVycm9yLAogICAgdHlwZSA9ICJiIiwKICAgIHlsaW0gPSBjKDAsIG1heChlbSRlcnIkZXJyb3IpKSwKICAgIHhsYWIgPSAiZGVncmVlIiwKICAgIHlsYWIgPSAiZXJyb3IiLAogICAgc3ViID0gcGFzdGUwKCJTbWFsbGVzdCBlcnJvciA9ICIsIHJvdW5kKG1pbihlbSRlcnIkZXJyb3IpKSksCiAgICBwY2ggPSAxOSwKICAgIGNleC5heGlzID0gLjgsCiAgICBjZXgubGFiID0gLjcKICApCn0KYGBgCgpgYGB7cn0KZW0ubG0gPC0gZXZhbC5tb2RlbC5sbShkYXRhLnBwKQpwbG90LmV2YWwubW9kZWwoZW0ubG0pCmBgYAoKV2hlbiB3ZSB0cnkgdG8gYXBwbHkgc29tZSBtYWNoaW5lIGxlYXJuaW5nIHdpdGggdHJhaW4vdGVzdCBkYXRhc2V0LCBtdWx0aW5vbWlhbCBsaW5lYXIgcmVncmVzc2lvbiBkb2Vzbid0IHN1Y2NlZWQgaW4gcHJlZGljdGluZyBjb3JyZWN0IHZhbHVlcy4KClRoaXMgaXMgYmVjYXVzZSB0aGVyZSBpcyAqKm5vIGxpbmVhcml0eSoqIGluIHRoZSBwYXR0ZXJuIChkdWUgdG8gdGhlIHNwZWNpZmljIHBhdHRlcm4gKiooeDEsIHgyKSA9PiAoeDEgLSB4MikoeDEgKyB4MikqKi4KCiMjIyBGZWF0dXJlIEVuZ2luZWVyaW5nCgpMZXQncyBhZGQgMiBleHRyYSBmZWF0dXJlcyAoZjEgYW5kIGYyKS4KCmBgYHtyfQpkYXRhLnBwZiA8LSBkYXRhLnBwCmRhdGEucHBmJGYxIDwtIGRhdGEucHBmJHgxICsgZGF0YS5wcGYkeDIKZGF0YS5wcGYkZjIgPC0gYXMuaW50ZWdlcigoZGF0YS5wcGYkeDEgLSBkYXRhLnBwZiR4MikgKiAxMCBeIGNlaWxpbmcobG9nMTAoZGF0YS5wcGYkZjEpKSkKZGF0YS5wcGYkeDEgPC0gTlVMTApkYXRhLnBwZiR4MiA8LSBOVUxMCm5hbWVzKGRhdGEucHBmKSA8LSBjKCJ5IiwgIngxIiwgIngyIikKYGBgCgpgYGB7cn0KaGVhZChkYXRhLnBwZiwgMjApCmBgYAoKYGBge3J9CnBsb3QuYnQoZGF0YS5wcGYpCmBgYAoKYGBge3J9CmVtLnBwZiA8LSBldmFsLm1vZGVsLmxtKGRhdGEucHBmKQpwbG90LmV2YWwubW9kZWwoZW0ucHBmKQpgYGAKCldoaWxlIHN0aWxsIGZhciBmcm9tIGJlaW5nIHBlcmZlY3QsIGZlYXR1cmUgZW5naW5lZXJpbmcgaGVscGVkIG1pbmltaXppbmcgZXJyb3IgKGByIGZvcm1hdChtaW4oZW0ucHBmJGVyciRlcnJvciksIHNjaWVudGlmaWMgPSBUKWAgdnMgYHIgZm9ybWF0KG1pbihlbS5sbSRlcnIkZXJyb3IpLCBzY2llbnRpZmljID0gVClgKS4gTm90ZSBiZXR0ZXIgYXBwcm94aW1hdGlvbiBpcyBub3Qgc3VycHJpc2luZyBhcyBuZXcgZmVhdHVyZXMgaW5jbHVkZSBicmFpbiB0ZWFzZXIgbG9naWMuCgojIyMgTmV1cmFsIE5ldHdvcmsKCkxldCdzIHRyeSB3aXRoIGEgbmV1cmFsIG5ldHdvcmssIHN1cHBvc2VkIHRvIGJldHRlciBzdWl0IG5vbi1saW5lYXIgcHJvYmxlbXMuCgojIyMjIFRyYWluIG5ldXJhbCBuZXR3b3JrCgpSZXNvdXJjZXM6CgotIGh0dHA6Ly9kYXRhc2NpZW5jZXBsdXMuY29tL2ZpdHRpbmctbmV1cmFsLW5ldHdvcmstaW4tci8KLSBodHRwOi8vd3d3LmRpLmZjLnVsLnB0L35qcG4vci9uZXVyYWxuZXRzL25ldXJhbG5ldHMuaHRtbAotIGh0dHBzOi8vaGV1cmlzdGljYWxseS53b3JkcHJlc3MuY29tLzIwMTEvMTEvMTcvdXNpbmctbmV1cmFsLW5ldHdvcmstZm9yLXJlZ3Jlc3Npb24vCgpgYGB7cn0KbGlicmFyeShubmV0KQoKbWF4cyA8LSBhcHBseShkYXRhLnBwLCAyLCBtYXgpCm1pbnMgPC0gYXBwbHkoZGF0YS5wcCwgMiwgbWluKQoKdHQubm4gPC0gZ2V0LnRyYWluLnRlc3QoYXMuZGF0YS5mcmFtZShzY2FsZShkYXRhLnBwLCBjZW50ZXIgPSBtaW5zLCBzY2FsZSA9IG1heHMpKSkKCm1vZGVsLm5uIDwtIG5uZXQoCiAgeSB+IHgxICsgeDIsCiAgZGF0YSA9IHR0Lm5uJHRyYWluLAogIHNpemUgPSAyMCwKICBtYXhpdCA9IDUwMCwKICB0cmFjZSA9IEYKKQpgYGAKCiMjIyMgUHJlZGljdGlvbgoKYGBge3J9CnByZWQubm4gPC0gcHJlZGljdChtb2RlbC5ubiwgdHQubm4kdGVzdFtjKCJ4MSIsICJ4MiIpXSkKYGBgCgpgYGB7cn0KIyBVbi1zY2FsZQpubi5jaGVjayA8LSBhcy5kYXRhLmZyYW1lKAogIGNiaW5kKAogICAgcm91bmQodHQubm4kdGVzdCR5ICogKG1heHNbInkiXSAtIG1pbnNbInkiXSkgKyBtaW5zWyJ5Il0pLAogICAgcm91bmQocHJlZC5ubiAqIChtYXhzWyJ5Il0gLSBtaW5zWyJ5Il0pICsgbWluc1sieSJdKQogICkKKQpuYW1lcyhubi5jaGVjaykgPC0gYygidHJ1ZSIsICJndWVzcyIpCgplcnIubm4gPC0gZ2V0LmVycm9ycyhubi5jaGVjayR0cnVlLCBubi5jaGVjayRndWVzcykKCmJveHBsb3QoCiAgbm4uY2hlY2skdHJ1ZSAtIG5uLmNoZWNrJGd1ZXNzLAogIGhvcml6b250YWwgPSBULAogIG1haW4gPSAiRXN0aW1hdGlvbiBFcnJvciBEaXN0cmlidXRpb24iLAogIHN1YiA9IHBhc3RlMCgiRXJyb3IgPSAiLCByb3VuZChlcnIubm4kZXJyb3IpKSwKICBjZXguYXhpcyA9IC44LAogIGNleC5sYWIgPSAuNywKICBjb2wgPSAibGlnaHRncmVlbiIKKQpgYGAKCkV2ZW4gaWYgbmV1cmFsIG5ldHdvcmsgZ290IGEgbG93ZXIgZXJyb3IgKGByIGZvcm1hdChlcnIubm4kZXJyb3IsIHNjaWVudGlmaWMgPSBUKWApLCB3ZSBhcmUgc3RpbGwgZmFyIGZyb20gcGVyZmVjdGlvbiwgYXMgaXRzIGNhcGFjaXR5IHRvIGZpbmQgdGhlIGNvcnJlY3QgcHJlZGljdGlvbiBpcyBwb29yIChgciBucm93KG5uLmNoZWNrW25uLmNoZWNrJHRydWUgPT0gbm4uY2hlY2skZ3Vlc3MsXSlgIGV4YWN0IGd1ZXNzZXMgZm9yIGByIG5yb3cobm4uY2hlY2spYCBpdGVtcykuCgojIyBDb25jbHVzaW9uCgpUaGlzIGNvbmNsdWRlcyBteSBhdHRlbXB0IHRvIHByZWRpY3QgdGhpcyBicmFpbiB0ZWFzZXIgdXNpbmcgbWFjaGluZSBsZWFybmluZy4KCk15IGZlZWxpbmcgaXMgbWFjaGluZSBsZWFybmluZyBpcyBiZXR0ZXIgaW4gZmluZGluZyBhcHByb3hpbWF0aW9ucyB0aGFuIHByZWRpY3RpbmcgZXhhY3QgdmFsdWVzLCBhcyB0aGVyZSBpcyBhbHdheXMgc29tZSB1bmNlcnRhaW50eS4KCkknbSBjb252aW5jZWQgdGhlcmUgaXMgYSBiZXR0ZXIgd2F5IHRvIGRvLCBidXQgSSBtdXN0IGFkbWl0IG15IGtub3dsZWRnZSBpcyB0byBsaW1pdGVkIHRvIGdvIGZ1cnRoZXIuCgpQbGVhc2UgZG8gbm90IGhlc2l0YXRlIHRvIGNvbnRhY3QgbWUgaWYgeW91IGhhdmUgcmVtYXJrcyBvciBzdWdnZXN0aW9ucy4gVGhleSB3aWxsIGJlIHZlcnkgd2VsY29tZS4K