Cramer V

Data

# Sample data: counts of preferences by gender
data <- matrix(c(
  20, 30, 25,  # Male: electronics, clothing, food
  15, 40, 20   # Female: electronics, clothing, food
), nrow = 2, byrow = TRUE)

# Add row and column names
rownames(data) <- c("Male", "Female")
colnames(data) <- c("Electronics", "Clothing", "Food")

# Show the contingency table
print(data)
       Electronics Clothing Food
Male            20       30   25
Female          15       40   20

Chi square

# Perform chi-square test
chisq_result <- chisq.test(data)

# View results
chisq_result

    Pearson's Chi-squared test

data:  data
X-squared = 2.6984, df = 2, p-value = 0.2594

Cramer’s V

# Install package if needed
#install.packages("lsr")  # only once
library(lsr)

# Calculate Cramér's V
cramersV(data)
[1] 0.1341246

Interpretation

Chi-square test will give you a p-value to say if the association is statistically significant.

Cramér’s V will tell you the strength:

~0.1 → weak

~0.3 → moderate

~0.5+ → strong

Example on Cohen’s D

Cohen’s d is a measure of effect size used to indicate the standardized difference between two means. It tells you how large the difference is, in terms of standard deviations.

✅ Purpose of Cohen’s d Quantifies the magnitude of the difference between two groups (e.g. treatment vs. control).

Often used in t-tests, psychology, education, and medical research.

\[ d = \frac{M_1 - M_2}{SD_{\text{pooled}}} \quad \text{where} \quad SD_{\text{pooled}} = \sqrt{\frac{SD_1^2 + SD_2^2}{2}} \]

# Sample data
group1 <- c(100, 102, 98, 95, 101)   # e.g. control
group2 <- c(110, 108, 112, 107, 111) # e.g. treatment

# Install effectsize package (if needed)
#install.packages("effectsize") 
library(effectsize)

# Compute Cohen's d
cohens_d(group2, group1)
Cohen's d |       95% CI
------------------------
4.25      | [1.83, 6.60]

- Estimated using pooled SD.

Interpretation (per Cohen’s conventions) Cohen’s d Effect Size 0.0 – 0.2 Small 0.2 – 0.5 Medium 0.5 – 0.8 Large > 0.8 Very large

These are general guidelines—context matters.

Adjusted Rand Index

✅ Purpose of ARI To compare the true labels (e.g. ground truth) with the clustering results from an algorithm (like K-means).

To assess how well a clustering algorithm has performed.

🧮 Rand Index (RI) vs. Adjusted Rand Index (ARI) The Rand Index (RI) counts the proportion of pairs of elements that are assigned consistently in both clusterings (same cluster in both, or different clusters in both).

However, RI can be biased — high even for random clusterings.

So, ARI adjusts for chance.

\[ \text{ARI} = \frac{ \sum_{ij} \binom{n_{ij}}{2} - \left[ \sum_i \binom{a_i}{2} \sum_j \binom{b_j}{2} \middle/ \binom{n}{2} \right] }{ \frac{1}{2} \left[ \sum_i \binom{a_i}{2} + \sum_j \binom{b_j}{2} \right] - \left[ \sum_i \binom{a_i}{2} \sum_j \binom{b_j}{2} \middle/ \binom{n}{2} \right] } \]

\[\begin{align*} & n_{ij} \text{ is the number of elements in both cluster } i \text{ of } U \text{ and cluster } j \text{ of } V, \\ & a_i = \sum_j n_{ij} \quad \text{(sum over row } i\text{)}, \\ & b_j = \sum_i n_{ij} \quad \text{(sum over column } j\text{)}, \\ & n = \sum_{ij} n_{ij} \quad \text{(total number of samples)}. \end{align*}\]

Where:

ARI = 1 → perfect agreement

ARI ≈ 0 → random labeling

ARI < 0 → worse than random

# Install needed package
#install.packages("mclust")
library(mclust)

# True labels vs. predicted clusters
true_labels <- c(1, 1, 0, 0, 2, 2)
predicted_clusters <- c(1, 1, 2, 2, 3, 3)

# Compute Adjusted Rand Index
adjustedRandIndex(true_labels, predicted_clusters)
[1] 1

Normalized mutual information (NMI)

Normalized Mutual Information (NMI) is another popular measure for evaluating the similarity between two clusterings, like the Adjusted Rand Index — but it’s based on information theory rather than pair counting.

✅ What is NMI? NMI measures the mutual dependence between two clusterings — how much information one clustering gives about the other — and normalizes the result so that it ranges from 0 to 1:

NMI

1 NMI=1: perfect match

NMI

0 NMI=0: completely independent (no mutual information)

\[ \text{NMI}(U, V) = \frac{2 \cdot I(U; V)}{H(U) + H(V)} \]

\[\begin{align*} I(U; V) &= \sum_{i=1}^{|U|} \sum_{j=1}^{|V|} \frac{n_{ij}}{n} \log \left( \frac{n_{ij} \cdot n}{n_{i\cdot} \cdot n_{\cdot j}} \right) \\ H(U) &= - \sum_{i=1}^{|U|} \frac{n_{i\cdot}}{n} \log \left( \frac{n_{i\cdot}}{n} \right) \\ H(V) &= - \sum_{j=1}^{|V|} \frac{n_{\cdot j}}{n} \log \left( \frac{n_{\cdot j}}{n} \right) \end{align*}\]

Where:

# Install package if needed
# install.packages("aricode")
library(aricode)

# Example labels
true_labels <- c(1, 1, 0, 0, 2, 2)
predicted_clusters <- c(1, 1, 2, 2, 3, 3)

# Compute Normalized Mutual Information
NMI(true_labels, predicted_clusters)
[1] 1

Comparison with ARI Measure Based on Range Sensitive to Interpretation ARI Pair counting [-1, 1] Overlap of pairs 1 = perfect match NMI Information theory [0, 1] Shared information 1 = perfect match

Gini impurity

Gini impurity is a measure originally used in decision trees (e.g., CART – Classification and Regression Trees) to quantify how “pure” a node is. While not typically used in standard unsupervised clustering (like K-means), it can be applied to evaluate the purity of clusters with respect to known class labels (i.e., in a supervised validation context).

Important: Gini impurity is not a clustering criterion itself but rather a post-hoc evaluation metric to assess how homogeneous clusters are with respect to some known labels.

The Gini impurity of a cluster \(C\) is defined as:

\[ \text{Gini}(C) = 1 - \sum_{k=1}^{K} p_k^2 \]

where \(p_k\) is the proportion of elements in cluster \(C\) that belong to class \(k\). It reaches its minimum (0) when all elements belong to a single class.

interpretation

The Gini impurity of a cluster \(C\) lies within the range:

\[ \text{Gini}(C) \in \left[ 0, 1 - \frac{1}{K} \right] \]

  • implies the cluster is — all instances belong to a single class.
  • impurity occurs when classes are uniformly distributed within the cluster. In this case, the Gini impurity approaches \(1 - \frac{1}{K}\), where \(K\) is the number of classes.

Examples:

  • Cluster with 100% of class “A”: Gini = 1 - (1)^2 = 0

  • Cluster with 50% class “A” and 50% class “B”: Gini = 1 - (0.5^2 + 0.5^2) = 0.5

  • Cluster with equal thirds of classes A, B, and C: \[ \text{Gini} = 1 - 3 \times \left(\frac{1}{3}\right)^2 = 1 - \frac{1}{3} = \frac{2}{3} \approx 0.666 \]

# Example in R
set.seed(123)

# Create a simple dataset with labels
library(dplyr)
n <- 100
x <- rbind(
  matrix(rnorm(n, mean=0), ncol=2),
  matrix(rnorm(n, mean=3), ncol=2)
)
labels <- factor(c(rep("A", n/2), rep("B", n/2)))
df <- data.frame(x1 = x[,1], x2 = x[,2], label = labels)

# Perform k-means clustering
kmeans_result <- kmeans(df[, c("x1", "x2")], centers = 2)
df$cluster <- kmeans_result$cluster

# Compute Gini impurity for each cluster
gini_impurity <- function(cluster_labels) {
  p <- prop.table(table(cluster_labels))
  1 - sum(p^2)
}

gini_by_cluster <- df %>%
  group_by(cluster) %>%
  summarise(gini = gini_impurity(label))

print(gini_by_cluster)
NA

Entropy as a Cluster Purity Measure

The entropy of a cluster \(C\) with respect to class distribution is defined as:

\[ \text{Entropy}(C) = - \sum_{k=1}^{K} p_k \log_2(p_k) \]

where \(p_k\) is the proportion of class \(k\) in cluster \(C\).

Interpretation:

  • If all points in a cluster belong to the same class: \(\text{Entropy} = 0\)
  • If classes are equally mixed: \(\text{Entropy} = \log_2(K)\)

To compute the total entropy across all clusters:

\[ \text{Total Entropy} = \sum_{i=1}^{M} \frac{n_i}{n} \cdot \text{Entropy}(C_i) \]

where:

  • \(M\): total number of clusters
  • \(n_i\): number of items in cluster \(C_i\)
  • \(n\): total number of data points
# Assume df has 'label' and 'cluster' columns as before
library(dplyr)

# Entropy function
entropy <- function(cluster_labels) {
  p <- prop.table(table(cluster_labels))
  -sum(p * log2(p), na.rm = TRUE)
}

# Calculate entropy per cluster
entropy_by_cluster <- df %>%
  group_by(cluster) %>%
  summarise(entropy = entropy(label),
            n = n()) %>%
  mutate(weight = n / sum(n),
         weighted_entropy = entropy * weight)

# Total entropy
total_entropy <- sum(entropy_by_cluster$weighted_entropy)
print(entropy_by_cluster)
print(paste("Total Entropy:", round(total_entropy, 4)))
[1] "Total Entropy: 0.071"

CART: Classification and Regression Trees

Explanation

CART is a decision tree learning technique that can be used for both classification (categorical outcome) and regression (continuous outcome). It works by recursively splitting the data into subsets based on the feature that yields the highest purity gain.

In classification, the algorithm:

  • Starts with all the data at the root node.

  • Splits it using the variable and threshold that maximizes the decrease in impurity (e.g., Gini impurity or Entropy).

  • Repeats the process on each subset until a stopping criterion is met (e.g., minimum samples per node or max depth).

Maths:

Let \(D\) be the dataset at a node, with \(K\) classes. The Gini impurity is defined as:

\[ Gini(D) = 1 - \sum_{k=1}^K p_k^2 \]

where \(p_k\) is the proportion of observations in class \(k\) at the node.

Given a split \(s\) that partitions \(D\) into two subsets \(D_1\) and \(D_2\), the impurity of the split is:

\[ Gini_{split}(s) = \frac{|D_1|}{|D|} Gini(D_1) + \frac{|D_2|}{|D|} Gini(D_2) \]

The best split minimizes this impurity.

Example in R

# Load required library
library(rpart)
library(rpart.plot)

# Load a sample dataset
data(iris)

# Convert Species to binary classification (e.g., setosa vs. others)
iris$BinarySpecies <- ifelse(iris$Species == "setosa", "setosa", "other")
iris$BinarySpecies <- as.factor(iris$BinarySpecies)

# Build a CART classification model
cart_model <- rpart(BinarySpecies ~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width,
                    data = iris, method = "class")

# Print model summary
print(cart_model)
n= 150 

node), split, n, loss, yval, (yprob)
      * denotes terminal node

1) root 150 50 other (0.6666667 0.3333333)  
  2) Petal.Length>=2.45 100  0 other (1.0000000 0.0000000) *
  3) Petal.Length< 2.45 50  0 setosa (0.0000000 1.0000000) *
# Plot the tree
rpart.plot(cart_model, type = 4, extra = 104, fallen.leaves = TRUE)

Notes:

  • method = “class” ensures it performs classification.
  • The impurity metric used by default is Gini impurity.
  • The model chooses splits that minimize the weighted sum of child node impurities.

CART Classification: Entropy

Given a dataset \(D\) with \(K\) classes, the entropy is defined as:

\[ Entropy(D) = - \sum_{k=1}^K p_k \log_2 p_k \]

where \(p_k\) is the proportion of class \(k\) in \(D\).

For a split \(s\) that divides \(D\) into \(D_1\) and \(D_2\), the entropy after the split is:

\[ Entropy_{split}(s) = \frac{|D_1|}{|D|} Entropy(D_1) + \frac{|D_2|}{|D|} Entropy(D_2) \]

The information gain is:

\[ Gain(s) = Entropy(D) - Entropy_{split}(s) \]

The best split maximizes \(Gain(s)\).

library(RWeka)

# Convert Species to binary classification
data(iris)
iris$BinarySpecies <- ifelse(iris$Species == "setosa", "setosa", "other")
iris$BinarySpecies <- as.factor(iris$BinarySpecies)

# Build an entropy-based decision tree using J48 (C4.5)
model_entropy <- J48(BinarySpecies ~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width, data = iris)

# Print the model
summary(model_entropy)

=== Summary ===

Correctly Classified Instances         150              100      %
Incorrectly Classified Instances         0                0      %
Kappa statistic                          1     
Mean absolute error                      0     
Root mean squared error                  0     
Relative absolute error                  0      %
Root relative squared error              0      %
Total Number of Instances              150     

=== Confusion Matrix ===

   a   b   <-- classified as
 100   0 |   a = other
   0  50 |   b = setosa

HDBSCAN: Hierarchical Density-Based Clustering

Overview

  • HDBSCAN is an advanced clustering method that:

  • Extends DBSCAN by converting it into a hierarchical clustering algorithm.

  • Extracts the most stable clusters from the hierarchy.

  • Automatically determines the number of clusters and handles noise/outliers effectively.

It is non-parametric (except for minPts) and works well for clusters of varying density, unlike DBSCAN.

HDBSCAN Clustering

Let \(x, y \in \mathbb{R}^d\) be data points. Define:

1. Core distance:

\[ \text{core}_{k}(x) = \text{distance to the } k\text{-th nearest neighbor of } x \]

** 2. Mutual Reachability Distance (MRD):

\[ \text{MRD}(x, y) = \max \left\{ \text{core}_k(x), \text{core}_k(y), \|x - y\| \right\} \]

3. Cluster hierarchy:

Build a minimum spanning tree (MST) from all pairwise MRDs. Perform single-linkage clustering to obtain a hierarchy.

4. Stability:

Let \(C\) be a cluster, and \(\lambda = \frac{1}{\text{MRD}}\). Define the cluster stability as:

\[ \text{Stability}(C) = \sum_{x \in C} (\lambda_{\text{birth}}(x) - \lambda_{\text{death}}(x)) \]

Only the most stable clusters are retained.

Example

# Install required package if not already
# install.packages("dbscan")

library(dbscan)
library(ggplot2)

# Simulated 2D data
set.seed(42)
n <- 100
x <- cbind(
  x = c(rnorm(n, 0, 0.3), rnorm(n, 3, 0.3)),
  y = c(rnorm(n, 0, 0.3), rnorm(n, 3, 0.3))
)

# HDBSCAN clustering
hdb <- hdbscan(x, minPts = 10)

# Print results
print(hdb)
HDBSCAN clustering for 200 objects.
Parameters: minPts = 10
The clustering contains 2 cluster(s) and 0 noise points.

  1   2 
100 100 

Available fields: cluster, minPts, coredist, cluster_scores, membership_prob, outlier_scores, hc
# Plot clusters
df <- data.frame(x, cluster = factor(hdb$cluster))
ggplot(df, aes(x = x, y = y, color = cluster)) +
  geom_point(size = 3) +
  ggtitle("HDBSCAN Clustering") +
  theme_minimal()

When to Use HDBSCAN

  • When clusters have different densities.

  • When number of clusters is unknown.

  • When noise/outliers should be automatically detected.

LS0tCnRpdGxlOiAiQUkgbWV0aG9kcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgQ3JhbWVyIFYKCgoKLSBDcmVhdGUgYSBjb250aW5nZW5jeSB0YWJsZQoKLSBQZXJmb3JtIGEgQ2hpLXNxdWFyZSB0ZXN0CgotIENhbGN1bGF0ZSBDcmFtw6ly4oCZcyBWCgojIyMgRGF0YQoKYGBge3J9CiMgU2FtcGxlIGRhdGE6IGNvdW50cyBvZiBwcmVmZXJlbmNlcyBieSBnZW5kZXIKZGF0YSA8LSBtYXRyaXgoYygKICAyMCwgMzAsIDI1LCAgIyBNYWxlOiBlbGVjdHJvbmljcywgY2xvdGhpbmcsIGZvb2QKICAxNSwgNDAsIDIwICAgIyBGZW1hbGU6IGVsZWN0cm9uaWNzLCBjbG90aGluZywgZm9vZAopLCBucm93ID0gMiwgYnlyb3cgPSBUUlVFKQoKIyBBZGQgcm93IGFuZCBjb2x1bW4gbmFtZXMKcm93bmFtZXMoZGF0YSkgPC0gYygiTWFsZSIsICJGZW1hbGUiKQpjb2xuYW1lcyhkYXRhKSA8LSBjKCJFbGVjdHJvbmljcyIsICJDbG90aGluZyIsICJGb29kIikKCiMgU2hvdyB0aGUgY29udGluZ2VuY3kgdGFibGUKcHJpbnQoZGF0YSkKCmBgYAoKIyMjIENoaSBzcXVhcmUKCmBgYHtyfQojIFBlcmZvcm0gY2hpLXNxdWFyZSB0ZXN0CmNoaXNxX3Jlc3VsdCA8LSBjaGlzcS50ZXN0KGRhdGEpCgojIFZpZXcgcmVzdWx0cwpjaGlzcV9yZXN1bHQKCmBgYAoKIyMgQ3JhbWVyJ3MgVgoKYGBge3J9CiMgSW5zdGFsbCBwYWNrYWdlIGlmIG5lZWRlZAojaW5zdGFsbC5wYWNrYWdlcygibHNyIikgICMgb25seSBvbmNlCmxpYnJhcnkobHNyKQoKIyBDYWxjdWxhdGUgQ3JhbcOpcidzIFYKY3JhbWVyc1YoZGF0YSkKCmBgYAoKCiMjIyBJbnRlcnByZXRhdGlvbgoKQ2hpLXNxdWFyZSB0ZXN0IHdpbGwgZ2l2ZSB5b3UgYSBwLXZhbHVlIHRvIHNheSBpZiB0aGUgYXNzb2NpYXRpb24gaXMgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudC4KCkNyYW3DqXLigJlzIFYgd2lsbCB0ZWxsIHlvdSB0aGUgc3RyZW5ndGg6Cgp+MC4xIOKGkiB3ZWFrCgp+MC4zIOKGkiBtb2RlcmF0ZQoKfjAuNSsg4oaSIHN0cm9uZwoKCgojIyBFeGFtcGxlIG9uIENvaGVuJ3MgRAoKQ29oZW4ncyBkIGlzIGEgbWVhc3VyZSBvZiBlZmZlY3Qgc2l6ZSB1c2VkIHRvIGluZGljYXRlIHRoZSBzdGFuZGFyZGl6ZWQgZGlmZmVyZW5jZSBiZXR3ZWVuIHR3byBtZWFucy4gSXQgdGVsbHMgeW91IGhvdyBsYXJnZSB0aGUgZGlmZmVyZW5jZSBpcywgaW4gdGVybXMgb2Ygc3RhbmRhcmQgZGV2aWF0aW9ucy4KCuKchSBQdXJwb3NlIG9mIENvaGVuJ3MgZApRdWFudGlmaWVzIHRoZSBtYWduaXR1ZGUgb2YgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0d28gZ3JvdXBzIChlLmcuIHRyZWF0bWVudCB2cy4gY29udHJvbCkuCgpPZnRlbiB1c2VkIGluIHQtdGVzdHMsIHBzeWNob2xvZ3ksIGVkdWNhdGlvbiwgYW5kIG1lZGljYWwgcmVzZWFyY2guCgpcWwpkID0gXGZyYWN7TV8xIC0gTV8yfXtTRF97XHRleHR7cG9vbGVkfX19ClxxdWFkIFx0ZXh0e3doZXJlfSBccXVhZApTRF97XHRleHR7cG9vbGVkfX0gPSBcc3FydHtcZnJhY3tTRF8xXjIgKyBTRF8yXjJ9ezJ9fQpcXQoKCmBgYHtyfQojIFNhbXBsZSBkYXRhCmdyb3VwMSA8LSBjKDEwMCwgMTAyLCA5OCwgOTUsIDEwMSkgICAjIGUuZy4gY29udHJvbApncm91cDIgPC0gYygxMTAsIDEwOCwgMTEyLCAxMDcsIDExMSkgIyBlLmcuIHRyZWF0bWVudAoKIyBJbnN0YWxsIGVmZmVjdHNpemUgcGFja2FnZSAoaWYgbmVlZGVkKQojaW5zdGFsbC5wYWNrYWdlcygiZWZmZWN0c2l6ZSIpIApsaWJyYXJ5KGVmZmVjdHNpemUpCgojIENvbXB1dGUgQ29oZW4ncyBkCmNvaGVuc19kKGdyb3VwMiwgZ3JvdXAxKQoKYGBgCgpJbnRlcnByZXRhdGlvbiAocGVyIENvaGVuJ3MgY29udmVudGlvbnMpCkNvaGVu4oCZcyBkCUVmZmVjdCBTaXplCjAuMCDigJMgMC4yCVNtYWxsCjAuMiDigJMgMC41CU1lZGl1bQowLjUg4oCTIDAuOAlMYXJnZQo+IDAuOAlWZXJ5IGxhcmdlCgpUaGVzZSBhcmUgZ2VuZXJhbCBndWlkZWxpbmVz4oCUY29udGV4dCBtYXR0ZXJzLgoKCiMjIEFkanVzdGVkIFJhbmQgSW5kZXgKCuKchSBQdXJwb3NlIG9mIEFSSQpUbyBjb21wYXJlIHRoZSB0cnVlIGxhYmVscyAoZS5nLiBncm91bmQgdHJ1dGgpIHdpdGggdGhlIGNsdXN0ZXJpbmcgcmVzdWx0cyBmcm9tIGFuIGFsZ29yaXRobSAobGlrZSBLLW1lYW5zKS4KClRvIGFzc2VzcyBob3cgd2VsbCBhIGNsdXN0ZXJpbmcgYWxnb3JpdGhtIGhhcyBwZXJmb3JtZWQuCgrwn6euIFJhbmQgSW5kZXggKFJJKSB2cy4gQWRqdXN0ZWQgUmFuZCBJbmRleCAoQVJJKQpUaGUgUmFuZCBJbmRleCAoUkkpIGNvdW50cyB0aGUgcHJvcG9ydGlvbiBvZiBwYWlycyBvZiBlbGVtZW50cyB0aGF0IGFyZSBhc3NpZ25lZCBjb25zaXN0ZW50bHkgaW4gYm90aCBjbHVzdGVyaW5ncyAoc2FtZSBjbHVzdGVyIGluIGJvdGgsIG9yIGRpZmZlcmVudCBjbHVzdGVycyBpbiBib3RoKS4KCkhvd2V2ZXIsIFJJIGNhbiBiZSBiaWFzZWQg4oCUIGhpZ2ggZXZlbiBmb3IgcmFuZG9tIGNsdXN0ZXJpbmdzLgoKU28sIEFSSSBhZGp1c3RzIGZvciBjaGFuY2UuCgpcWwpcdGV4dHtBUkl9ID0gXGZyYWN7ClxzdW1fe2lqfSBcYmlub217bl97aWp9fXsyfQotIFxsZWZ0WyBcc3VtX2kgXGJpbm9te2FfaX17Mn0gXHN1bV9qIFxiaW5vbXtiX2p9ezJ9IFxtaWRkbGUvIFxiaW5vbXtufXsyfSBccmlnaHRdCn17ClxmcmFjezF9ezJ9IFxsZWZ0WyBcc3VtX2kgXGJpbm9te2FfaX17Mn0gKyBcc3VtX2ogXGJpbm9te2Jfan17Mn0gXHJpZ2h0XQotIFxsZWZ0WyBcc3VtX2kgXGJpbm9te2FfaX17Mn0gXHN1bV9qIFxiaW5vbXtiX2p9ezJ9IFxtaWRkbGUvIFxiaW5vbXtufXsyfSBccmlnaHRdCn0KXF0KClx0ZXh0e3doZXJlOn0KXGJlZ2lue2FsaWduKn0KJiBuX3tpan0gXHRleHR7IGlzIHRoZSBudW1iZXIgb2YgZWxlbWVudHMgaW4gYm90aCBjbHVzdGVyIH0gaSBcdGV4dHsgb2YgfSBVIFx0ZXh0eyBhbmQgY2x1c3RlciB9IGogXHRleHR7IG9mIH0gViwgXFwKJiBhX2kgPSBcc3VtX2ogbl97aWp9IFxxdWFkIFx0ZXh0eyhzdW0gb3ZlciByb3cgfSBpXHRleHR7KX0sIFxcCiYgYl9qID0gXHN1bV9pIG5fe2lqfSBccXVhZCBcdGV4dHsoc3VtIG92ZXIgY29sdW1uIH0galx0ZXh0eyl9LCBcXAomIG4gPSBcc3VtX3tpan0gbl97aWp9IFxxdWFkIFx0ZXh0eyh0b3RhbCBudW1iZXIgb2Ygc2FtcGxlcyl9LgpcZW5ke2FsaWduKn0KCgpXaGVyZToKCkFSSSA9IDEg4oaSIHBlcmZlY3QgYWdyZWVtZW50CgpBUkkg4omIIDAg4oaSIHJhbmRvbSBsYWJlbGluZwoKQVJJIDwgMCDihpIgd29yc2UgdGhhbiByYW5kb20KCmBgYHtyfQojIEluc3RhbGwgbmVlZGVkIHBhY2thZ2UKI2luc3RhbGwucGFja2FnZXMoIm1jbHVzdCIpCmxpYnJhcnkobWNsdXN0KQoKIyBUcnVlIGxhYmVscyB2cy4gcHJlZGljdGVkIGNsdXN0ZXJzCnRydWVfbGFiZWxzIDwtIGMoMSwgMSwgMCwgMCwgMiwgMikKcHJlZGljdGVkX2NsdXN0ZXJzIDwtIGMoMSwgMSwgMiwgMiwgMywgMykKCiMgQ29tcHV0ZSBBZGp1c3RlZCBSYW5kIEluZGV4CmFkanVzdGVkUmFuZEluZGV4KHRydWVfbGFiZWxzLCBwcmVkaWN0ZWRfY2x1c3RlcnMpCgpgYGAKCiMjIE5vcm1hbGl6ZWQgbXV0dWFsIGluZm9ybWF0aW9uIChOTUkpCgpOb3JtYWxpemVkIE11dHVhbCBJbmZvcm1hdGlvbiAoTk1JKSBpcyBhbm90aGVyIHBvcHVsYXIgbWVhc3VyZSBmb3IgZXZhbHVhdGluZyB0aGUgc2ltaWxhcml0eSBiZXR3ZWVuIHR3byBjbHVzdGVyaW5ncywgbGlrZSB0aGUgQWRqdXN0ZWQgUmFuZCBJbmRleCDigJQgYnV0IGl0J3MgYmFzZWQgb24gaW5mb3JtYXRpb24gdGhlb3J5IHJhdGhlciB0aGFuIHBhaXIgY291bnRpbmcuCgrinIUgV2hhdCBpcyBOTUk/Ck5NSSBtZWFzdXJlcyB0aGUgbXV0dWFsIGRlcGVuZGVuY2UgYmV0d2VlbiB0d28gY2x1c3RlcmluZ3Mg4oCUIGhvdyBtdWNoIGluZm9ybWF0aW9uIG9uZSBjbHVzdGVyaW5nIGdpdmVzIGFib3V0IHRoZSBvdGhlciDigJQgYW5kIG5vcm1hbGl6ZXMgdGhlIHJlc3VsdCBzbyB0aGF0IGl0IHJhbmdlcyBmcm9tIDAgdG8gMToKCk5NSQo9CjEKTk1JPTE6IHBlcmZlY3QgbWF0Y2gKCk5NSQo9CjAKTk1JPTA6IGNvbXBsZXRlbHkgaW5kZXBlbmRlbnQgKG5vIG11dHVhbCBpbmZvcm1hdGlvbikKClxbClx0ZXh0e05NSX0oVSwgVikgPSBcZnJhY3syIFxjZG90IEkoVTsgVil9e0goVSkgKyBIKFYpfQpcXQoKXHRleHR7d2hlcmU6fQoKXGJlZ2lue2FsaWduKn0KSShVOyBWKSAmPSBcc3VtX3tpPTF9Xnt8VXx9IFxzdW1fe2o9MX1ee3xWfH0gClxmcmFje25fe2lqfX17bn0gXGxvZyBcbGVmdCggXGZyYWN7bl97aWp9IFxjZG90IG59e25fe2lcY2RvdH0gXGNkb3Qgbl97XGNkb3Qgan19IFxyaWdodCkgXFwKSChVKSAmPSAtIFxzdW1fe2k9MX1ee3xVfH0gXGZyYWN7bl97aVxjZG90fX17bn0gXGxvZyBcbGVmdCggXGZyYWN7bl97aVxjZG90fX17bn0gXHJpZ2h0KSBcXApIKFYpICY9IC0gXHN1bV97aj0xfV57fFZ8fSBcZnJhY3tuX3tcY2RvdCBqfX17bn0gXGxvZyBcbGVmdCggXGZyYWN7bl97XGNkb3Qgan19e259IFxyaWdodCkKXGVuZHthbGlnbip9CgoKV2hlcmU6CgogIC0gJFUsIFYkIGFyZSB0aGUgdHdvIGNsdXN0ZXJpbmdzLgogIC0gJG5fe2lqfSQ6IG51bWJlciBvZiBzYW1wbGVzIGluIGNsdXN0ZXIgJGkkIG9mICRVJCBhbmQgY2x1c3RlciAkaiQgb2YgJFYkLgogIC0gJG5fe2lcY2RvdH0sIG5fe1xjZG90IGp9JDogcm93IGFuZCBjb2x1bW4gc3VtcyBvZiB0aGUgY29udGluZ2VuY3kgdGFibGUuCiAgLSAkbiQ6IHRvdGFsIG51bWJlciBvZiBzYW1wbGVzLgogIC0gJEkoVTsgVikkOiBtdXR1YWwgaW5mb3JtYXRpb24gYmV0d2VlbiAkVSQgYW5kICRWJC4KICAtICRIKFUpLCBIKFYpJDogZW50cm9waWVzIG9mIHRoZSBjbHVzdGVyaW5ncyAkVSQgYW5kICRWJCwgcmVzcGVjdGl2ZWx5LgoKCgpgYGB7cn0KIyBJbnN0YWxsIHBhY2thZ2UgaWYgbmVlZGVkCiMgaW5zdGFsbC5wYWNrYWdlcygiYXJpY29kZSIpCmxpYnJhcnkoYXJpY29kZSkKCiMgRXhhbXBsZSBsYWJlbHMKdHJ1ZV9sYWJlbHMgPC0gYygxLCAxLCAwLCAwLCAyLCAyKQpwcmVkaWN0ZWRfY2x1c3RlcnMgPC0gYygxLCAxLCAyLCAyLCAzLCAzKQoKIyBDb21wdXRlIE5vcm1hbGl6ZWQgTXV0dWFsIEluZm9ybWF0aW9uCk5NSSh0cnVlX2xhYmVscywgcHJlZGljdGVkX2NsdXN0ZXJzKQoKYGBgCgpDb21wYXJpc29uIHdpdGggQVJJCk1lYXN1cmUJQmFzZWQgb24JUmFuZ2UJU2Vuc2l0aXZlIHRvCUludGVycHJldGF0aW9uCkFSSQlQYWlyIGNvdW50aW5nCVstMSwgMV0JT3ZlcmxhcCBvZiBwYWlycwkxID0gcGVyZmVjdCBtYXRjaApOTUkJSW5mb3JtYXRpb24gdGhlb3J5CVswLCAxXQlTaGFyZWQgaW5mb3JtYXRpb24JMSA9IHBlcmZlY3QgbWF0Y2gKCgojIyBHaW5pIGltcHVyaXR5CgpHaW5pIGltcHVyaXR5IGlzIGEgbWVhc3VyZSBvcmlnaW5hbGx5IHVzZWQgaW4gZGVjaXNpb24gdHJlZXMgKGUuZy4sIENBUlQg4oCTIENsYXNzaWZpY2F0aW9uIGFuZCBSZWdyZXNzaW9uIFRyZWVzKSB0byBxdWFudGlmeSBob3cgInB1cmUiIGEgbm9kZSBpcy4gV2hpbGUgbm90IHR5cGljYWxseSB1c2VkIGluIHN0YW5kYXJkIHVuc3VwZXJ2aXNlZCBjbHVzdGVyaW5nIChsaWtlIEstbWVhbnMpLCBpdCBjYW4gYmUgYXBwbGllZCB0byBldmFsdWF0ZSB0aGUgcHVyaXR5IG9mIGNsdXN0ZXJzIHdpdGggcmVzcGVjdCB0byBrbm93biBjbGFzcyBsYWJlbHMgKGkuZS4sIGluIGEgc3VwZXJ2aXNlZCB2YWxpZGF0aW9uIGNvbnRleHQpLgoKSW1wb3J0YW50OgpHaW5pIGltcHVyaXR5IGlzIG5vdCBhIGNsdXN0ZXJpbmcgY3JpdGVyaW9uIGl0c2VsZiBidXQgcmF0aGVyIGEgcG9zdC1ob2MgZXZhbHVhdGlvbiBtZXRyaWMgdG8gYXNzZXNzIGhvdyBob21vZ2VuZW91cyBjbHVzdGVycyBhcmUgd2l0aCByZXNwZWN0IHRvIHNvbWUga25vd24gbGFiZWxzLgoKVGhlIEdpbmkgaW1wdXJpdHkgb2YgYSBjbHVzdGVyIFwoIEMgXCkgaXMgZGVmaW5lZCBhczoKClxbClx0ZXh0e0dpbml9KEMpID0gMSAtIFxzdW1fe2s9MX1ee0t9IHBfa14yClxdCgp3aGVyZSBcKCBwX2sgXCkgaXMgdGhlIHByb3BvcnRpb24gb2YgZWxlbWVudHMgaW4gY2x1c3RlciBcKCBDIFwpIHRoYXQgYmVsb25nIHRvIGNsYXNzIFwoIGsgXCkuIEl0IHJlYWNoZXMgaXRzIG1pbmltdW0gKDApIHdoZW4gYWxsIGVsZW1lbnRzIGJlbG9uZyB0byBhIHNpbmdsZSBjbGFzcy4KCiMjIyBpbnRlcnByZXRhdGlvbgoKVGhlIEdpbmkgaW1wdXJpdHkgb2YgYSBjbHVzdGVyIFwoIEMgXCkgbGllcyB3aXRoaW4gdGhlIHJhbmdlOgoKXFsKXHRleHR7R2luaX0oQykgXGluIFxsZWZ0WyAwLCAxIC0gXGZyYWN7MX17S30gXHJpZ2h0XQpcXQoKLSAgXHRleHRiZnswfSBccXVhZCBpbXBsaWVzIHRoZSBjbHVzdGVyIGlzIFxlbXBoe3B1cmV9IOKAlCBhbGwgaW5zdGFuY2VzIGJlbG9uZyB0byBhIHNpbmdsZSBjbGFzcy4KLSBcdGV4dGJme01heGltdW19IGltcHVyaXR5IG9jY3VycyB3aGVuIGNsYXNzZXMgYXJlIHVuaWZvcm1seSBkaXN0cmlidXRlZCB3aXRoaW4gdGhlIGNsdXN0ZXIuIEluIHRoaXMgY2FzZSwgdGhlIEdpbmkgaW1wdXJpdHkgYXBwcm9hY2hlcyBcKCAxIC0gXGZyYWN7MX17S30gXCksIHdoZXJlIFwoIEsgXCkgaXMgdGhlIG51bWJlciBvZiBjbGFzc2VzLgoKCioqRXhhbXBsZXM6KioKCgotIENsdXN0ZXIgd2l0aCAxMDBcJSBvZiBjbGFzcyAiQSI6IApHaW5pID0gMSAtICgxKV4yID0gMAoKLSBDbHVzdGVyIHdpdGggNTBcJSBjbGFzcyAiQSIgYW5kIDUwXCUgY2xhc3MgIkIiOiAKR2luaSA9IDEgLSBcbGVmdCgwLjVeMiArIDAuNV4yXHJpZ2h0KSA9IDAuNQoKCi0gQ2x1c3RlciB3aXRoIGVxdWFsIHRoaXJkcyBvZiBjbGFzc2VzIEEsIEIsIGFuZCBDOgogIFxbCiAgXHRleHR7R2luaX0gPSAxIC0gMyBcdGltZXMgXGxlZnQoXGZyYWN7MX17M31ccmlnaHQpXjIgPSAxIC0gXGZyYWN7MX17M30gPSBcZnJhY3syfXszfSBcYXBwcm94IDAuNjY2CiAgXF0KCgoKYGBge3J9CiMgRXhhbXBsZSBpbiBSCnNldC5zZWVkKDEyMykKCiMgQ3JlYXRlIGEgc2ltcGxlIGRhdGFzZXQgd2l0aCBsYWJlbHMKbGlicmFyeShkcGx5cikKbiA8LSAxMDAKeCA8LSByYmluZCgKICBtYXRyaXgocm5vcm0obiwgbWVhbj0wKSwgbmNvbD0yKSwKICBtYXRyaXgocm5vcm0obiwgbWVhbj0zKSwgbmNvbD0yKQopCmxhYmVscyA8LSBmYWN0b3IoYyhyZXAoIkEiLCBuLzIpLCByZXAoIkIiLCBuLzIpKSkKZGYgPC0gZGF0YS5mcmFtZSh4MSA9IHhbLDFdLCB4MiA9IHhbLDJdLCBsYWJlbCA9IGxhYmVscykKCiMgUGVyZm9ybSBrLW1lYW5zIGNsdXN0ZXJpbmcKa21lYW5zX3Jlc3VsdCA8LSBrbWVhbnMoZGZbLCBjKCJ4MSIsICJ4MiIpXSwgY2VudGVycyA9IDIpCmRmJGNsdXN0ZXIgPC0ga21lYW5zX3Jlc3VsdCRjbHVzdGVyCgojIENvbXB1dGUgR2luaSBpbXB1cml0eSBmb3IgZWFjaCBjbHVzdGVyCmdpbmlfaW1wdXJpdHkgPC0gZnVuY3Rpb24oY2x1c3Rlcl9sYWJlbHMpIHsKICBwIDwtIHByb3AudGFibGUodGFibGUoY2x1c3Rlcl9sYWJlbHMpKQogIDEgLSBzdW0ocF4yKQp9CgpnaW5pX2J5X2NsdXN0ZXIgPC0gZGYgJT4lCiAgZ3JvdXBfYnkoY2x1c3RlcikgJT4lCiAgc3VtbWFyaXNlKGdpbmkgPSBnaW5pX2ltcHVyaXR5KGxhYmVsKSkKCnByaW50KGdpbmlfYnlfY2x1c3RlcikKCmBgYAoKCiMjIEVudHJvcHkgYXMgYSBDbHVzdGVyIFB1cml0eSBNZWFzdXJlCgpUaGUgZW50cm9weSBvZiBhIGNsdXN0ZXIgXCggQyBcKSB3aXRoIHJlc3BlY3QgdG8gY2xhc3MgZGlzdHJpYnV0aW9uIGlzIGRlZmluZWQgYXM6CgpcWwpcdGV4dHtFbnRyb3B5fShDKSA9IC0gXHN1bV97az0xfV57S30gcF9rIFxsb2dfMihwX2spClxdCgp3aGVyZSBcKCBwX2sgXCkgaXMgdGhlIHByb3BvcnRpb24gb2YgY2xhc3MgXCggayBcKSBpbiBjbHVzdGVyIFwoIEMgXCkuCgoqKkludGVycHJldGF0aW9uOioqCgotIElmIGFsbCBwb2ludHMgaW4gYSBjbHVzdGVyIGJlbG9uZyB0byB0aGUgc2FtZSBjbGFzczogXCggXHRleHR7RW50cm9weX0gPSAwIFwpCi0gSWYgY2xhc3NlcyBhcmUgZXF1YWxseSBtaXhlZDogXCggXHRleHR7RW50cm9weX0gPSBcbG9nXzIoSykgXCkKCgpUbyBjb21wdXRlIHRoZSB0b3RhbCBlbnRyb3B5IGFjcm9zcyBhbGwgY2x1c3RlcnM6CgpcWwpcdGV4dHtUb3RhbCBFbnRyb3B5fSA9IFxzdW1fe2k9MX1ee019IFxmcmFje25faX17bn0gXGNkb3QgXHRleHR7RW50cm9weX0oQ19pKQpcXQoKd2hlcmU6CgotIFwoIE0gXCk6IHRvdGFsIG51bWJlciBvZiBjbHVzdGVycwotIFwoIG5faSBcKTogbnVtYmVyIG9mIGl0ZW1zIGluIGNsdXN0ZXIgXCggQ19pIFwpCi0gXCggbiBcKTogdG90YWwgbnVtYmVyIG9mIGRhdGEgcG9pbnRzCgpgYGB7cn0KIyBBc3N1bWUgZGYgaGFzICdsYWJlbCcgYW5kICdjbHVzdGVyJyBjb2x1bW5zIGFzIGJlZm9yZQpsaWJyYXJ5KGRwbHlyKQoKIyBFbnRyb3B5IGZ1bmN0aW9uCmVudHJvcHkgPC0gZnVuY3Rpb24oY2x1c3Rlcl9sYWJlbHMpIHsKICBwIDwtIHByb3AudGFibGUodGFibGUoY2x1c3Rlcl9sYWJlbHMpKQogIC1zdW0ocCAqIGxvZzIocCksIG5hLnJtID0gVFJVRSkKfQoKIyBDYWxjdWxhdGUgZW50cm9weSBwZXIgY2x1c3RlcgplbnRyb3B5X2J5X2NsdXN0ZXIgPC0gZGYgJT4lCiAgZ3JvdXBfYnkoY2x1c3RlcikgJT4lCiAgc3VtbWFyaXNlKGVudHJvcHkgPSBlbnRyb3B5KGxhYmVsKSwKICAgICAgICAgICAgbiA9IG4oKSkgJT4lCiAgbXV0YXRlKHdlaWdodCA9IG4gLyBzdW0obiksCiAgICAgICAgIHdlaWdodGVkX2VudHJvcHkgPSBlbnRyb3B5ICogd2VpZ2h0KQoKIyBUb3RhbCBlbnRyb3B5CnRvdGFsX2VudHJvcHkgPC0gc3VtKGVudHJvcHlfYnlfY2x1c3RlciR3ZWlnaHRlZF9lbnRyb3B5KQpwcmludChlbnRyb3B5X2J5X2NsdXN0ZXIpCnByaW50KHBhc3RlKCJUb3RhbCBFbnRyb3B5OiIsIHJvdW5kKHRvdGFsX2VudHJvcHksIDQpKSkKCmBgYAojIyBDQVJUOiBDbGFzc2lmaWNhdGlvbiBhbmQgUmVncmVzc2lvbiBUcmVlcwoKCiMjIyBFeHBsYW5hdGlvbgpDQVJUIGlzIGEgZGVjaXNpb24gdHJlZSBsZWFybmluZyB0ZWNobmlxdWUgdGhhdCBjYW4gYmUgdXNlZCBmb3IgYm90aCBjbGFzc2lmaWNhdGlvbiAoY2F0ZWdvcmljYWwgb3V0Y29tZSkgYW5kIHJlZ3Jlc3Npb24gKGNvbnRpbnVvdXMgb3V0Y29tZSkuIEl0IHdvcmtzIGJ5IHJlY3Vyc2l2ZWx5IHNwbGl0dGluZyB0aGUgZGF0YSBpbnRvIHN1YnNldHMgYmFzZWQgb24gdGhlIGZlYXR1cmUgdGhhdCB5aWVsZHMgdGhlIGhpZ2hlc3QgcHVyaXR5IGdhaW4uCgpJbiBjbGFzc2lmaWNhdGlvbiwgdGhlIGFsZ29yaXRobToKCi0gU3RhcnRzIHdpdGggYWxsIHRoZSBkYXRhIGF0IHRoZSByb290IG5vZGUuCgotIFNwbGl0cyBpdCB1c2luZyB0aGUgdmFyaWFibGUgYW5kIHRocmVzaG9sZCB0aGF0IG1heGltaXplcyB0aGUgZGVjcmVhc2UgaW4gaW1wdXJpdHkgKGUuZy4sIEdpbmkgaW1wdXJpdHkgb3IgRW50cm9weSkuCgotIFJlcGVhdHMgdGhlIHByb2Nlc3Mgb24gZWFjaCBzdWJzZXQgdW50aWwgYSBzdG9wcGluZyBjcml0ZXJpb24gaXMgbWV0IChlLmcuLCBtaW5pbXVtIHNhbXBsZXMgcGVyIG5vZGUgb3IgbWF4IGRlcHRoKS4KCiMjIE1hdGhzOgoKTGV0ICREJCBiZSB0aGUgZGF0YXNldCBhdCBhIG5vZGUsIHdpdGggJEskIGNsYXNzZXMuIFRoZSBHaW5pIGltcHVyaXR5IGlzIGRlZmluZWQgYXM6CgpcWwpHaW5pKEQpID0gMSAtIFxzdW1fe2s9MX1eSyBwX2teMgpcXQoKd2hlcmUgJHBfayQgaXMgdGhlIHByb3BvcnRpb24gb2Ygb2JzZXJ2YXRpb25zIGluIGNsYXNzICRrJCBhdCB0aGUgbm9kZS4KCkdpdmVuIGEgc3BsaXQgJHMkIHRoYXQgcGFydGl0aW9ucyAkRCQgaW50byB0d28gc3Vic2V0cyAkRF8xJCBhbmQgJERfMiQsIHRoZSBpbXB1cml0eSBvZiB0aGUgc3BsaXQgaXM6CgpcWwpHaW5pX3tzcGxpdH0ocykgPSBcZnJhY3t8RF8xfH17fER8fSBHaW5pKERfMSkgKyBcZnJhY3t8RF8yfH17fER8fSBHaW5pKERfMikKXF0KClRoZSBiZXN0IHNwbGl0IG1pbmltaXplcyB0aGlzIGltcHVyaXR5LgoKIyMjIEV4YW1wbGUgaW4gUgoKYGBge3J9CiMgTG9hZCByZXF1aXJlZCBsaWJyYXJ5CmxpYnJhcnkocnBhcnQpCmxpYnJhcnkocnBhcnQucGxvdCkKCiMgTG9hZCBhIHNhbXBsZSBkYXRhc2V0CmRhdGEoaXJpcykKCiMgQ29udmVydCBTcGVjaWVzIHRvIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiAoZS5nLiwgc2V0b3NhIHZzLiBvdGhlcnMpCmlyaXMkQmluYXJ5U3BlY2llcyA8LSBpZmVsc2UoaXJpcyRTcGVjaWVzID09ICJzZXRvc2EiLCAic2V0b3NhIiwgIm90aGVyIikKaXJpcyRCaW5hcnlTcGVjaWVzIDwtIGFzLmZhY3RvcihpcmlzJEJpbmFyeVNwZWNpZXMpCgojIEJ1aWxkIGEgQ0FSVCBjbGFzc2lmaWNhdGlvbiBtb2RlbApjYXJ0X21vZGVsIDwtIHJwYXJ0KEJpbmFyeVNwZWNpZXMgfiBTZXBhbC5MZW5ndGggKyBTZXBhbC5XaWR0aCArIFBldGFsLkxlbmd0aCArIFBldGFsLldpZHRoLAogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBpcmlzLCBtZXRob2QgPSAiY2xhc3MiKQoKIyBQcmludCBtb2RlbCBzdW1tYXJ5CnByaW50KGNhcnRfbW9kZWwpCgojIFBsb3QgdGhlIHRyZWUKcnBhcnQucGxvdChjYXJ0X21vZGVsLCB0eXBlID0gNCwgZXh0cmEgPSAxMDQsIGZhbGxlbi5sZWF2ZXMgPSBUUlVFKQoKYGBgCiMjIyBOb3RlczoKCi0gbWV0aG9kID0gImNsYXNzIiBlbnN1cmVzIGl0IHBlcmZvcm1zIGNsYXNzaWZpY2F0aW9uLgotIFRoZSBpbXB1cml0eSBtZXRyaWMgdXNlZCBieSBkZWZhdWx0IGlzIEdpbmkgaW1wdXJpdHkuCi0gVGhlIG1vZGVsIGNob29zZXMgc3BsaXRzIHRoYXQgbWluaW1pemUgdGhlIHdlaWdodGVkIHN1bSBvZiBjaGlsZCBub2RlIGltcHVyaXRpZXMuCgoKCiMjIyBDQVJUIENsYXNzaWZpY2F0aW9uOiBFbnRyb3B5CgpHaXZlbiBhIGRhdGFzZXQgJEQkIHdpdGggJEskIGNsYXNzZXMsIHRoZSBlbnRyb3B5IGlzIGRlZmluZWQgYXM6CgpcWwpFbnRyb3B5KEQpID0gLSBcc3VtX3trPTF9XksgcF9rIFxsb2dfMiBwX2sKXF0KCndoZXJlICRwX2skIGlzIHRoZSBwcm9wb3J0aW9uIG9mIGNsYXNzICRrJCBpbiAkRCQuCgpGb3IgYSBzcGxpdCAkcyQgdGhhdCBkaXZpZGVzICREJCBpbnRvICREXzEkIGFuZCAkRF8yJCwgdGhlIGVudHJvcHkgYWZ0ZXIgdGhlIHNwbGl0IGlzOgoKXFsKRW50cm9weV97c3BsaXR9KHMpID0gXGZyYWN7fERfMXx9e3xEfH0gRW50cm9weShEXzEpICsgXGZyYWN7fERfMnx9e3xEfH0gRW50cm9weShEXzIpClxdCgpUaGUgaW5mb3JtYXRpb24gZ2FpbiBpczoKClxbCkdhaW4ocykgPSBFbnRyb3B5KEQpIC0gRW50cm9weV97c3BsaXR9KHMpClxdCgpUaGUgYmVzdCBzcGxpdCBtYXhpbWl6ZXMgJEdhaW4ocykkLgoKYGBge3J9CmxpYnJhcnkoUldla2EpCgojIENvbnZlcnQgU3BlY2llcyB0byBiaW5hcnkgY2xhc3NpZmljYXRpb24KZGF0YShpcmlzKQppcmlzJEJpbmFyeVNwZWNpZXMgPC0gaWZlbHNlKGlyaXMkU3BlY2llcyA9PSAic2V0b3NhIiwgInNldG9zYSIsICJvdGhlciIpCmlyaXMkQmluYXJ5U3BlY2llcyA8LSBhcy5mYWN0b3IoaXJpcyRCaW5hcnlTcGVjaWVzKQoKIyBCdWlsZCBhbiBlbnRyb3B5LWJhc2VkIGRlY2lzaW9uIHRyZWUgdXNpbmcgSjQ4IChDNC41KQptb2RlbF9lbnRyb3B5IDwtIEo0OChCaW5hcnlTcGVjaWVzIH4gU2VwYWwuTGVuZ3RoICsgU2VwYWwuV2lkdGggKyBQZXRhbC5MZW5ndGggKyBQZXRhbC5XaWR0aCwgZGF0YSA9IGlyaXMpCgojIFByaW50IHRoZSBtb2RlbApzdW1tYXJ5KG1vZGVsX2VudHJvcHkpCmBgYAoKCiMjIEhEQlNDQU46IEhpZXJhcmNoaWNhbCBEZW5zaXR5LUJhc2VkIENsdXN0ZXJpbmcKCk92ZXJ2aWV3CgotIEhEQlNDQU4gaXMgYW4gYWR2YW5jZWQgY2x1c3RlcmluZyBtZXRob2QgdGhhdDoKCi0gRXh0ZW5kcyBEQlNDQU4gYnkgY29udmVydGluZyBpdCBpbnRvIGEgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgYWxnb3JpdGhtLgoKLSBFeHRyYWN0cyB0aGUgbW9zdCBzdGFibGUgY2x1c3RlcnMgZnJvbSB0aGUgaGllcmFyY2h5LgoKLSBBdXRvbWF0aWNhbGx5IGRldGVybWluZXMgdGhlIG51bWJlciBvZiBjbHVzdGVycyBhbmQgaGFuZGxlcyBub2lzZS9vdXRsaWVycyBlZmZlY3RpdmVseS4KCkl0IGlzIG5vbi1wYXJhbWV0cmljIChleGNlcHQgZm9yIG1pblB0cykgYW5kIHdvcmtzIHdlbGwgZm9yIGNsdXN0ZXJzIG9mIHZhcnlpbmcgZGVuc2l0eSwgdW5saWtlIERCU0NBTi4KCioqSERCU0NBTiBDbHVzdGVyaW5nKioKCkxldCAkeCwgeSBcaW4gXG1hdGhiYntSfV5kJCBiZSBkYXRhIHBvaW50cy4gRGVmaW5lOgoKKioxLiBDb3JlIGRpc3RhbmNlOioqCgpcWwpcdGV4dHtjb3JlfV97a30oeCkgPSBcdGV4dHtkaXN0YW5jZSB0byB0aGUgfSBrXHRleHR7LXRoIG5lYXJlc3QgbmVpZ2hib3Igb2YgfSB4ClxdCgoqKiAyLiBNdXR1YWwgUmVhY2hhYmlsaXR5IERpc3RhbmNlIChNUkQpOgoKXFsKXHRleHR7TVJEfSh4LCB5KSA9IFxtYXggXGxlZnRceyBcdGV4dHtjb3JlfV9rKHgpLCBcdGV4dHtjb3JlfV9rKHkpLCBcfHggLSB5XHwgXHJpZ2h0XH0KXF0KCioqMy4gQ2x1c3RlciBoaWVyYXJjaHk6KioKCkJ1aWxkIGEgbWluaW11bSBzcGFubmluZyB0cmVlIChNU1QpIGZyb20gYWxsIHBhaXJ3aXNlIE1SRHMuIFBlcmZvcm0gc2luZ2xlLWxpbmthZ2UgY2x1c3RlcmluZyB0byBvYnRhaW4gYSBoaWVyYXJjaHkuCgoqKjQuIFN0YWJpbGl0eToqKgoKTGV0ICRDJCBiZSBhIGNsdXN0ZXIsIGFuZCAkXGxhbWJkYSA9IFxmcmFjezF9e1x0ZXh0e01SRH19JC4gRGVmaW5lIHRoZSBjbHVzdGVyIHN0YWJpbGl0eSBhczoKClxbClx0ZXh0e1N0YWJpbGl0eX0oQykgPSBcc3VtX3t4IFxpbiBDfSAoXGxhbWJkYV97XHRleHR7YmlydGh9fSh4KSAtIFxsYW1iZGFfe1x0ZXh0e2RlYXRofX0oeCkpClxdCgpPbmx5IHRoZSBtb3N0IHN0YWJsZSBjbHVzdGVycyBhcmUgcmV0YWluZWQuCgojIyMgRXhhbXBsZQoKYGBge3J9CiMgSW5zdGFsbCByZXF1aXJlZCBwYWNrYWdlIGlmIG5vdCBhbHJlYWR5CiMgaW5zdGFsbC5wYWNrYWdlcygiZGJzY2FuIikKCmxpYnJhcnkoZGJzY2FuKQpsaWJyYXJ5KGdncGxvdDIpCgojIFNpbXVsYXRlZCAyRCBkYXRhCnNldC5zZWVkKDQyKQpuIDwtIDEwMAp4IDwtIGNiaW5kKAogIHggPSBjKHJub3JtKG4sIDAsIDAuMyksIHJub3JtKG4sIDMsIDAuMykpLAogIHkgPSBjKHJub3JtKG4sIDAsIDAuMyksIHJub3JtKG4sIDMsIDAuMykpCikKCiMgSERCU0NBTiBjbHVzdGVyaW5nCmhkYiA8LSBoZGJzY2FuKHgsIG1pblB0cyA9IDEwKQoKIyBQcmludCByZXN1bHRzCnByaW50KGhkYikKCiMgUGxvdCBjbHVzdGVycwpkZiA8LSBkYXRhLmZyYW1lKHgsIGNsdXN0ZXIgPSBmYWN0b3IoaGRiJGNsdXN0ZXIpKQpnZ3Bsb3QoZGYsIGFlcyh4ID0geCwgeSA9IHksIGNvbG9yID0gY2x1c3RlcikpICsKICBnZW9tX3BvaW50KHNpemUgPSAzKSArCiAgZ2d0aXRsZSgiSERCU0NBTiBDbHVzdGVyaW5nIikgKwogIHRoZW1lX21pbmltYWwoKQoKYGBgCgoKCgojIyMgV2hlbiB0byBVc2UgSERCU0NBTgoKLSBXaGVuIGNsdXN0ZXJzIGhhdmUgZGlmZmVyZW50IGRlbnNpdGllcy4KCi0gV2hlbiBudW1iZXIgb2YgY2x1c3RlcnMgaXMgdW5rbm93bi4KCi0gV2hlbiBub2lzZS9vdXRsaWVycyBzaG91bGQgYmUgYXV0b21hdGljYWxseSBkZXRlY3RlZC4KCgoKCgo=