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
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:
- \(U, V\) are the two
clusterings.
- \(n_{ij}\): number of samples in
cluster \(i\) of \(U\) and cluster \(j\) of \(V\).
- \(n_{i\cdot}, n_{\cdot j}\): row
and column sums of the contingency table.
- \(n\): total number of
samples.
- \(I(U; V)\): mutual information
between \(U\) and \(V\).
- \(H(U), H(V)\): entropies of the
clusterings \(U\) and \(V\), respectively.
# 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=