Objectives
After completing this lesson, you will be able to:
- state the role and importance of distance measures in machine learning algorithms such as kNN
- calculate Hamming, Euclidean, and Manhattan distance measures
- list additional distance measures
- appreciate the effect that distance measure may have on model performance
Distance Measures
Machine learning algorithms often need to measure the distance or similarity between data points for tasks such as clustering, classification, or nearest neighbors. Here are some common distance measures used in machine learning:
Euclidean Distance: Also known as L2 distance. It is calculated as the square root of the sum of the squared differences between two points. It’s the most common use of distance, often used in k-means, k-NN, and other ML algorithms.
Manhattan Distance: Also known as L1 distance. It is calculated as the sum of the absolute differences between two points. It is used when the difference in each dimension matters equally, and is often used in various NLP or computer vision tasks.
Minkowski Distance: This is a generalization of Euclidean and Manhattan distance and adds a parameter, known as the norm order p. When p is 1, it becomes Manhattan distance and when p is 2, it becomes Euclidean distance.
Hamming Distance: Used for categorical variables. It measures the number of positions at which the corresponding symbols are different. It is used in information theory and error detection/correction.
Cosine Distance: Measures the cosine of the angle between two vectors, rather than the magnitude of the difference of the vectors like Euclidean distance. It’s often used in high-dimensional and sparse spaces, such as text documents (TF-IDF vectors) and collaborative filtering recommendation systems.
Mahalanobis Distance: Measures distance relative to the centroid — a base or mean point, taking into account the covariance of the data. This distance measure is scale-invariant and takes into account the correlations of the dataset.
Jaccard Distance: A measure of how dissimilar two sets are. The lower the distance, the more similar the sets are. It’s often used in text mining for comparing documents or other instances that can be represented as sets.
Levenshtein Distance: Also known as Edit Distance, it measures the minimum number of edits (insertions, deletions or substitutions) needed to change one string (or sequence) into another. It is used in spell correction, DNA sequence alignment, etc.
Hellinger Distance or Bhattacharyya Distance: These are used for comparing probability distributions.
Chebyshev Distance: Also known as maximum value distance. It’s defined as the greatest of differences along any coordinate dimension and is used in chessboard distance calculations.
Different distance measures are used for different types of data, so the choice of distance measure can greatly influence the results of your machine learning algorithm. It’s often a good idea to try different distance measures to see which one works best for your specific problem (Alfeilat et al, 2019).
In machine learning, we assume that each feature is a dimension in an N-dimensional space. The features, naturally, must be numeric. Any categorical features require a numeric encoding (see Lesson 3.207 - Encoding Categorical Features).
Euclidean Distance
Euclidean distance is a commonly used distance measure between two points in a Euclidean space (i.e., a space in which the Pythagorean theorem holds). It is calculated using the Pythagorean theorem.
The formula for Euclidean distance between two points in a two-dimensional space is given by:
Definition
In an n-dimensional space, the Euclidean distance is the square root of the sum of the squared differences of each dimension. The formula can be generalized for a multidimensional space to calculate the Euclidean distance between two points p and q in an n-dimensional real space:
\[
D(p,q)=\sqrt{\sum_{i=1}^{n}((p_i-q_i)^2)}
\]
In this general form, \(p = (p_1, p_2, ..., p_n)\) and \(q = (q_1, q_2, ..., q_n)\) are two points in n-dimensional space.
Implementation in R
The code below defines a function that calculates the Euclidean distance between two numeric vectors p and q. Note how we are leveraging the power of vector operations in R. The statement (p - q)^2
actually subtracts each element from one vector by the corresponding element of the other and then squares each difference. Finally, the squared differences are added with the function sum
.
dist.euclidean <- function (p, q) {
r <- sqrt(sum((p - q)^2))
return (r)
}
Let’s try the function by calling it with various vectors. Calculate the result by hand (or using a spreadsheet program) to convince yourself that the function works correctly.
p <- c(-2,3.1,0.2,4.5)
q <- c(-3,4.5,-0.1,6.1)
d <- dist.euclidean(p,q)
print(d)
## [1] 2.368544
Manhattan Distance
Manhattan distance, also known as the L1 norm, taxicab or city block distance, computes the absolute difference between pairs of coordinates. It gets its name from the grid layout of most streets in Manhattan, where you can only move along the grid lines. The image below illustrates this concepts of measuring distance by counting “blocks”:
Definition
In an n-dimensional space, the Manhattan distance is the sum of the absolute differences along each dimension. The formula can be generalized for a multidimensional space to calculate the Euclidean distance between two points p and q in an n-dimensional real space:
\[
D(p,q)=\sum_{i=1}^{n}(|p_i - q_i|)
\]
In this general form, \(p = (p_1, p_2, ..., p_n)\) and \(q = (q_1, q_2, ..., q_n)\) are two points in n-dimensional space.
This calculation basically sums up the absolute differences of their coordinates. Note that in high-dimensional spaces, the Euclidean and Manhattan distances can behave quite differently, and the choice between them depends on the specific application and often requires knowledge of the domain and some experimentation.
Implementation in R
The code below defines a function that calculates the Manhattan distance between two numeric vectors p and q. Note how we are leveraging the power of vector operations in R. The statement abs(p - q)
actually subtracts each element from one vector by the corresponding element of the other and then applies the function abs
to each element of the resulting vector. Finally, the absolute differences are added with the function sum
.
dist.manhattan <- function (p, q) {
r <- sum(abs(p - q))
return (r)
}
Let’s try the function by calling it with various vectors. Calculate the result by hand (or using a spreadsheet program) to convince yourself that the function works correctly.
p <- c(-2,3.1,0.2,4.5)
q <- c(-3,4.5,-0.1,6.1)
d <- dist.manhattan(p,q)
print(d)
## [1] 4.3
Hamming Distance
Hamming distance is a measure of difference between two strings of equal length. It’s named after Richard Hamming, who introduced it in the field of information theory.
Definition
Hamming distance is simply the count of the positions at which the corresponding symbols (characters, bits, etc.) are different.
For example, consider the binary vectors \(v_1 = <1,0,0,1,1,0,1,1>\) and \(v_2 = <1,1,0,1,1,0,0,1>\). The Hamming distance between these two vectors is 2 because there are two bits that differ:
Hamming distance is used in computer science in various applications such as error detection and error correction. It’s commonly used in genetics to measure the genetic distance between two DNA strings, in information theory for coding theory, and also in some machine learning algorithms, usually for binary data or categorical data encoded using a binary encoding (such as one-hot encoding).
Note that Hamming distance is defined only for strings of equal length. If one needs to compare strings of different lengths, one would need to use a different measure, such as the Levenshtein distance, which can handle strings of different lengths by including insertions and deletions, in addition to substitutions, in its calculation.
Implementation in R
The code below defines a function that calculates the distance between two Boolean vectors p and q. Note how the code leverages the power of vector operations in R. The statement p != q
returns a Boolean vector that contains the value of the logical operation != for each corresponding element in two vectors.
dist.hamming <- function(p, q) {
diffs <- p != q
return (length(which(diffs == T)))
}
Let’s try the function by calling it with various Boolean “strings”.
p <- c(T,T,F,F)
q <- c(T,T,T,F)
w <- c(F,F,T,T)
print(dist.hamming(p,q))
## [1] 1
## [1] 4
## [1] 0
The dist
Function of R
The dist
function of the stats package in R can calculate Euclidean, Hamming, and Manhattan distance, as well as Minkowski.
Cosine Distance
Cosine distance, or more often Cosine similarity, is a measure of similarity between two non-zero vectors. Instead of measuring the distance between the two points in Euclidean space like Euclidean distance, it measures the cosine of the angle between the two vectors. The cosine similarity is particularly used when the magnitude of the vectors does not matter.
Definition
The cosine similarity between two vectors A and B is calculated as the dot product of A and B divided by the product of the magnitudes of A and B:
\[
cos(\theta)= \frac{A \cdot B}{\| A\| \times \| B\|}
\]
The dot product \(A \cdot B\) is the sum of the product of the corresponding elements of A and B. The magnitude of a vector A, \(\| A\|\), is the square root of the sum of the squares of the elements.
If the vectors are normalized (each vector has a length/magnitude of 1), the cosine similarity simply becomes the dot product of the vectors.
The cosine distance is then defined as 1 minus the cosine similarity and it ranges from 0 (meaning the vectors are identical) to 2 (meaning the vectors are diametrically opposed).
Cosine similarity is especially advantageous in text analysis and other high-dimensional and sparse data. When comparing text documents represented as bag-of-words or TF-IDF vectors, we often don’t care about the length of the documents, just their content. Here, two documents are considered similar if they contain similar words, even if one document is much longer than the other. The cosine similarity handles this situation well, which is why it’s commonly used in Natural Language Processing (NLP) and Information Retrieval.
In summary, cosine similarity/distance is most appropriate when the magnitude of the vectors does not matter, and we’re more interested in the orientation (the direction in which they’re heading) of the vectors.
Tutorial
The chalk-talk by Dr. Schedlbauer will go into more detail on Euclidean, Manhattan, and Hamming Distance measures.
Nota Bene: In the video, the Hamming Distance between the vectors <0,0,1> and <1,0,1> is incorrectly calculated as 2 rather than the correct 1 as the vectors differ in one position, so the sum of the differences is 1.
Conclusion
Distance measures in machine learning help to quantify the similarity or dissimilarity between data points, playing a crucial role in algorithms such as clustering, classification, and nearest neighbors. Common distance measures include Euclidean, Manhattan, Minkowski, Hamming, Cosine, Mahalanobis, Jaccard, Levenshtein, Hellinger, and Chebyshev. The choice of distance measure can significantly influence the outcome of a machine learning model and typically depends on the specific nature and structure of the data.
References
Abu Alfeilat, H. A., Hassanat, A. B., Lasassmeh, O., Tarawneh, A. S., Alhasanat, M. B., Eyal Salman, H. S., & Prasath, V. S. (2019). Effects of distance measure choice on k-nearest neighbor classifier performance: a review. Big data, 7(4), 221-248.
LS0tCnRpdGxlOiAiRGlzdGFuY2UgTWVhc3VyZXMiCnBhcmFtczoKICBjYXRlZ29yeTogMwogIHN0YWNrczogMAogIG51bWJlcjogMjA4CiAgdGltZTogMzAKICBsZXZlbDogYmVnaW5uZXIKICB0YWdzOiBkaXN0YW5jZSxldWNsaWRlYW4sbWFuaGF0dGFuLG1pbmtvd3NraQogIGRlc2NyaXB0aW9uOiAiRXhwbGFpbnMgaG93IGRpc3RhbmNlIGlzIG1lYXN1cmVkIGluIGFuIG4tZGltZW5zaW9uYWwgc3BhY2UuIgpkYXRlOiAiPHNtYWxsPmByIFN5cy5EYXRlKClgPC9zbWFsbD4iCmF1dGhvcjogIjxzbWFsbD5NYXJ0aW4gU2NoZWRsYmF1ZXI8L3NtYWxsPiIKZW1haWw6ICJtLnNjaGVkbGJhdWVyQG5ldS5lZHUiCmFmZmlsaXRhdGlvbjogIk5vcnRoZWFzdGVybiBVbml2ZXJzaXR5IgpvdXRwdXQ6IAogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0aGVtZTogc3BhY2VsYWIKICAgIGhpZ2hsaWdodDogdGFuZ28KLS0tCgotLS0KdGl0bGU6ICI8c21hbGw+YHIgcGFyYW1zJGNhdGVnb3J5YC5gciBwYXJhbXMkbnVtYmVyYDwvc21hbGw+PGJyLz48c3BhbiBzdHlsZT0nY29sb3I6ICMyRTQwNTM7IGZvbnQtc2l6ZTogMC45ZW0nPmByIHJtYXJrZG93bjo6bWV0YWRhdGEkdGl0bGVgPC9zcGFuPiIKLS0tCgpgYGB7ciBjb2RlPXhmdW46OnJlYWRfdXRmOChwYXN0ZTAoaGVyZTo6aGVyZSgpLCcvUi9faW5zZXJ0MkRCLlInKSksIGluY2x1ZGUgPSBGQUxTRX0KYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIE9iamVjdGl2ZXMKCkFmdGVyIGNvbXBsZXRpbmcgdGhpcyBsZXNzb24sIHlvdSB3aWxsIGJlIGFibGUgdG86CgotICAgc3RhdGUgdGhlIHJvbGUgYW5kIGltcG9ydGFuY2Ugb2YgZGlzdGFuY2UgbWVhc3VyZXMgaW4gbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIHN1Y2ggYXMgKmtOTioKLSAgIGNhbGN1bGF0ZSBIYW1taW5nLCBFdWNsaWRlYW4sIGFuZCBNYW5oYXR0YW4gZGlzdGFuY2UgbWVhc3VyZXMKLSAgIGxpc3QgYWRkaXRpb25hbCBkaXN0YW5jZSBtZWFzdXJlcwotICAgYXBwcmVjaWF0ZSB0aGUgZWZmZWN0IHRoYXQgZGlzdGFuY2UgbWVhc3VyZSBtYXkgaGF2ZSBvbiBtb2RlbCBwZXJmb3JtYW5jZQoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBEaXN0YW5jZSBNZWFzdXJlcwoKTWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIG9mdGVuIG5lZWQgdG8gbWVhc3VyZSB0aGUgZGlzdGFuY2Ugb3Igc2ltaWxhcml0eSBiZXR3ZWVuIGRhdGEgcG9pbnRzIGZvciB0YXNrcyBzdWNoIGFzIGNsdXN0ZXJpbmcsIGNsYXNzaWZpY2F0aW9uLCBvciBuZWFyZXN0IG5laWdoYm9ycy4gSGVyZSBhcmUgc29tZSBjb21tb24gZGlzdGFuY2UgbWVhc3VyZXMgdXNlZCBpbiBtYWNoaW5lIGxlYXJuaW5nOgoKKipFdWNsaWRlYW4gRGlzdGFuY2UqKjogQWxzbyBrbm93biBhcyBMMiBkaXN0YW5jZS4gSXQgaXMgY2FsY3VsYXRlZCBhcyB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIHN1bSBvZiB0aGUgc3F1YXJlZCBkaWZmZXJlbmNlcyBiZXR3ZWVuIHR3byBwb2ludHMuIEl0J3MgdGhlIG1vc3QgY29tbW9uIHVzZSBvZiBkaXN0YW5jZSwgb2Z0ZW4gdXNlZCBpbiBrLW1lYW5zLCBrLU5OLCBhbmQgb3RoZXIgTUwgYWxnb3JpdGhtcy4KCioqTWFuaGF0dGFuIERpc3RhbmNlKio6IEFsc28ga25vd24gYXMgTDEgZGlzdGFuY2UuIEl0IGlzIGNhbGN1bGF0ZWQgYXMgdGhlIHN1bSBvZiB0aGUgYWJzb2x1dGUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0d28gcG9pbnRzLiBJdCBpcyB1c2VkIHdoZW4gdGhlIGRpZmZlcmVuY2UgaW4gZWFjaCBkaW1lbnNpb24gbWF0dGVycyBlcXVhbGx5LCBhbmQgaXMgb2Z0ZW4gdXNlZCBpbiB2YXJpb3VzIE5MUCBvciBjb21wdXRlciB2aXNpb24gdGFza3MuCgoqKk1pbmtvd3NraSBEaXN0YW5jZSoqOiBUaGlzIGlzIGEgZ2VuZXJhbGl6YXRpb24gb2YgRXVjbGlkZWFuIGFuZCBNYW5oYXR0YW4gZGlzdGFuY2UgYW5kIGFkZHMgYSBwYXJhbWV0ZXIsIGtub3duIGFzIHRoZSBub3JtIG9yZGVyIHAuIFdoZW4gcCBpcyAxLCBpdCBiZWNvbWVzIE1hbmhhdHRhbiBkaXN0YW5jZSBhbmQgd2hlbiBwIGlzIDIsIGl0IGJlY29tZXMgRXVjbGlkZWFuIGRpc3RhbmNlLgoKKipIYW1taW5nIERpc3RhbmNlKio6IFVzZWQgZm9yIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gSXQgbWVhc3VyZXMgdGhlIG51bWJlciBvZiBwb3NpdGlvbnMgYXQgd2hpY2ggdGhlIGNvcnJlc3BvbmRpbmcgc3ltYm9scyBhcmUgZGlmZmVyZW50LiBJdCBpcyB1c2VkIGluIGluZm9ybWF0aW9uIHRoZW9yeSBhbmQgZXJyb3IgZGV0ZWN0aW9uL2NvcnJlY3Rpb24uCgoqKkNvc2luZSBEaXN0YW5jZSoqOiBNZWFzdXJlcyB0aGUgY29zaW5lIG9mIHRoZSBhbmdsZSBiZXR3ZWVuIHR3byB2ZWN0b3JzLCByYXRoZXIgdGhhbiB0aGUgbWFnbml0dWRlIG9mIHRoZSBkaWZmZXJlbmNlIG9mIHRoZSB2ZWN0b3JzIGxpa2UgRXVjbGlkZWFuIGRpc3RhbmNlLiBJdCdzIG9mdGVuIHVzZWQgaW4gaGlnaC1kaW1lbnNpb25hbCBhbmQgc3BhcnNlIHNwYWNlcywgc3VjaCBhcyB0ZXh0IGRvY3VtZW50cyAoVEYtSURGIHZlY3RvcnMpIGFuZCBjb2xsYWJvcmF0aXZlIGZpbHRlcmluZyByZWNvbW1lbmRhdGlvbiBzeXN0ZW1zLgoKKipNYWhhbGFub2JpcyBEaXN0YW5jZSoqOiBNZWFzdXJlcyBkaXN0YW5jZSByZWxhdGl2ZSB0byB0aGUgY2VudHJvaWQgLS0tIGEgYmFzZSBvciBtZWFuIHBvaW50LCB0YWtpbmcgaW50byBhY2NvdW50IHRoZSBjb3ZhcmlhbmNlIG9mIHRoZSBkYXRhLiBUaGlzIGRpc3RhbmNlIG1lYXN1cmUgaXMgc2NhbGUtaW52YXJpYW50IGFuZCB0YWtlcyBpbnRvIGFjY291bnQgdGhlIGNvcnJlbGF0aW9ucyBvZiB0aGUgZGF0YXNldC4KCioqSmFjY2FyZCBEaXN0YW5jZSoqOiBBIG1lYXN1cmUgb2YgaG93IGRpc3NpbWlsYXIgdHdvIHNldHMgYXJlLiBUaGUgbG93ZXIgdGhlIGRpc3RhbmNlLCB0aGUgbW9yZSBzaW1pbGFyIHRoZSBzZXRzIGFyZS4gSXQncyBvZnRlbiB1c2VkIGluIHRleHQgbWluaW5nIGZvciBjb21wYXJpbmcgZG9jdW1lbnRzIG9yIG90aGVyIGluc3RhbmNlcyB0aGF0IGNhbiBiZSByZXByZXNlbnRlZCBhcyBzZXRzLgoKKipMZXZlbnNodGVpbiBEaXN0YW5jZSoqOiBBbHNvIGtub3duIGFzICpFZGl0IERpc3RhbmNlKiwgaXQgbWVhc3VyZXMgdGhlIG1pbmltdW0gbnVtYmVyIG9mIGVkaXRzIChpbnNlcnRpb25zLCBkZWxldGlvbnMgb3Igc3Vic3RpdHV0aW9ucykgbmVlZGVkIHRvIGNoYW5nZSBvbmUgc3RyaW5nIChvciBzZXF1ZW5jZSkgaW50byBhbm90aGVyLiBJdCBpcyB1c2VkIGluIHNwZWxsIGNvcnJlY3Rpb24sIEROQSBzZXF1ZW5jZSBhbGlnbm1lbnQsIGV0Yy4KCioqSGVsbGluZ2VyIERpc3RhbmNlIG9yIEJoYXR0YWNoYXJ5eWEgRGlzdGFuY2UqKjogVGhlc2UgYXJlIHVzZWQgZm9yIGNvbXBhcmluZyBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb25zLgoKKipDaGVieXNoZXYgRGlzdGFuY2UqKjogQWxzbyBrbm93biBhcyBtYXhpbXVtIHZhbHVlIGRpc3RhbmNlLiBJdCdzIGRlZmluZWQgYXMgdGhlIGdyZWF0ZXN0IG9mIGRpZmZlcmVuY2VzIGFsb25nIGFueSBjb29yZGluYXRlIGRpbWVuc2lvbiBhbmQgaXMgdXNlZCBpbiBjaGVzc2JvYXJkIGRpc3RhbmNlIGNhbGN1bGF0aW9ucy4KCkRpZmZlcmVudCBkaXN0YW5jZSBtZWFzdXJlcyBhcmUgdXNlZCBmb3IgZGlmZmVyZW50IHR5cGVzIG9mIGRhdGEsIHNvIHRoZSBjaG9pY2Ugb2YgZGlzdGFuY2UgbWVhc3VyZSBjYW4gZ3JlYXRseSBpbmZsdWVuY2UgdGhlIHJlc3VsdHMgb2YgeW91ciBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobS4gSXQncyBvZnRlbiBhIGdvb2QgaWRlYSB0byB0cnkgZGlmZmVyZW50IGRpc3RhbmNlIG1lYXN1cmVzIHRvIHNlZSB3aGljaCBvbmUgd29ya3MgYmVzdCBmb3IgeW91ciBzcGVjaWZpYyBwcm9ibGVtIChBbGZlaWxhdCAqZXQgYWwqLCAyMDE5KS4KCkluIG1hY2hpbmUgbGVhcm5pbmcsIHdlIGFzc3VtZSB0aGF0IGVhY2ggZmVhdHVyZSBpcyBhIGRpbWVuc2lvbiBpbiBhbiAqTiotZGltZW5zaW9uYWwgc3BhY2UuIFRoZSBmZWF0dXJlcywgbmF0dXJhbGx5LCBtdXN0IGJlIG51bWVyaWMuIEFueSBjYXRlZ29yaWNhbCBmZWF0dXJlcyByZXF1aXJlIGEgbnVtZXJpYyBlbmNvZGluZyAoc2VlIFtMZXNzb24gMy4yMDcgLSBFbmNvZGluZyBDYXRlZ29yaWNhbCBGZWF0dXJlc10oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wMy5tbC9sLTMtMjA3LWNhdGVnb3JpY2FsLWVuY29kaW5nL2wtMy0yMDcuaHRtbCkpLgoKIyMgRXVjbGlkZWFuIERpc3RhbmNlCgpFdWNsaWRlYW4gZGlzdGFuY2UgaXMgYSBjb21tb25seSB1c2VkIGRpc3RhbmNlIG1lYXN1cmUgYmV0d2VlbiB0d28gcG9pbnRzIGluIGEgRXVjbGlkZWFuIHNwYWNlICgqaS5lLiosIGEgc3BhY2UgaW4gd2hpY2ggdGhlIFB5dGhhZ29yZWFuIHRoZW9yZW0gaG9sZHMpLiBJdCBpcyBjYWxjdWxhdGVkIHVzaW5nIHRoZSBQeXRoYWdvcmVhbiB0aGVvcmVtLgoKVGhlIGZvcm11bGEgZm9yIEV1Y2xpZGVhbiBkaXN0YW5jZSBiZXR3ZWVuIHR3byBwb2ludHMgaW4gYSB0d28tZGltZW5zaW9uYWwgc3BhY2UgaXMgZ2l2ZW4gYnk6CgohW10oX2ltYWdlcy9ldWNsaWRlYW4tMmQuanBnKXt3aWR0aD0iNzUlIn0KCiMjIyBEZWZpbml0aW9uCgpJbiBhbiAqbiotZGltZW5zaW9uYWwgc3BhY2UsIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2UgaXMgdGhlIHNxdWFyZSByb290IG9mIHRoZSBzdW0gb2YgdGhlIHNxdWFyZWQgZGlmZmVyZW5jZXMgb2YgZWFjaCBkaW1lbnNpb24uIFRoZSBmb3JtdWxhIGNhbiBiZSBnZW5lcmFsaXplZCBmb3IgYSBtdWx0aWRpbWVuc2lvbmFsIHNwYWNlIHRvIGNhbGN1bGF0ZSB0aGUgRXVjbGlkZWFuIGRpc3RhbmNlIGJldHdlZW4gdHdvIHBvaW50cyAqcCogYW5kICpxKiBpbiBhbiAqbiotZGltZW5zaW9uYWwgcmVhbCBzcGFjZToKCiQkCkQocCxxKT1cc3FydHtcc3VtX3tpPTF9XntufSgocF9pLXFfaSleMil9CiQkCgpJbiB0aGlzIGdlbmVyYWwgZm9ybSwgJHAgPSAocF8xLCBwXzIsIC4uLiwgcF9uKSQgYW5kICRxID0gKHFfMSwgcV8yLCAuLi4sIHFfbikkIGFyZSB0d28gcG9pbnRzIGluICpuKi1kaW1lbnNpb25hbCBzcGFjZS4KCiMjIyBJbXBsZW1lbnRhdGlvbiBpbiBSCgpUaGUgY29kZSBiZWxvdyBkZWZpbmVzIGEgZnVuY3Rpb24gdGhhdCBjYWxjdWxhdGVzIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2UgYmV0d2VlbiB0d28gbnVtZXJpYyB2ZWN0b3JzICpwKiBhbmQgKnEqLiBOb3RlIGhvdyB3ZSBhcmUgbGV2ZXJhZ2luZyB0aGUgcG93ZXIgb2YgdmVjdG9yIG9wZXJhdGlvbnMgaW4gUi4gVGhlIHN0YXRlbWVudCBgKHAgLSBxKV4yYCBhY3R1YWxseSBzdWJ0cmFjdHMgZWFjaCBlbGVtZW50IGZyb20gb25lIHZlY3RvciBieSB0aGUgY29ycmVzcG9uZGluZyBlbGVtZW50IG9mIHRoZSBvdGhlciBhbmQgdGhlbiBzcXVhcmVzIGVhY2ggZGlmZmVyZW5jZS4gRmluYWxseSwgdGhlIHNxdWFyZWQgZGlmZmVyZW5jZXMgYXJlIGFkZGVkIHdpdGggdGhlIGZ1bmN0aW9uIGBzdW1gLgoKYGBge3J9CmRpc3QuZXVjbGlkZWFuIDwtIGZ1bmN0aW9uIChwLCBxKSB7CiAgciA8LSBzcXJ0KHN1bSgocCAtIHEpXjIpKQogIHJldHVybiAocikKfQpgYGAKCkxldCdzIHRyeSB0aGUgZnVuY3Rpb24gYnkgY2FsbGluZyBpdCB3aXRoIHZhcmlvdXMgdmVjdG9ycy4gQ2FsY3VsYXRlIHRoZSByZXN1bHQgYnkgaGFuZCAob3IgdXNpbmcgYSBzcHJlYWRzaGVldCBwcm9ncmFtKSB0byBjb252aW5jZSB5b3Vyc2VsZiB0aGF0IHRoZSBmdW5jdGlvbiB3b3JrcyBjb3JyZWN0bHkuCgpgYGB7cn0KcCA8LSBjKC0yLDMuMSwwLjIsNC41KQpxIDwtIGMoLTMsNC41LC0wLjEsNi4xKQoKZCA8LSBkaXN0LmV1Y2xpZGVhbihwLHEpCgpwcmludChkKQpgYGAKCiMjIE1hbmhhdHRhbiBEaXN0YW5jZQoKTWFuaGF0dGFuIGRpc3RhbmNlLCBhbHNvIGtub3duIGFzIHRoZSAqTDEqIG5vcm0sIHRheGljYWIgb3IgY2l0eSBibG9jayBkaXN0YW5jZSwgY29tcHV0ZXMgdGhlIGFic29sdXRlIGRpZmZlcmVuY2UgYmV0d2VlbiBwYWlycyBvZiBjb29yZGluYXRlcy4gSXQgZ2V0cyBpdHMgbmFtZSBmcm9tIHRoZSBncmlkIGxheW91dCBvZiBtb3N0IHN0cmVldHMgaW4gTWFuaGF0dGFuLCB3aGVyZSB5b3UgY2FuIG9ubHkgbW92ZSBhbG9uZyB0aGUgZ3JpZCBsaW5lcy4gVGhlIGltYWdlIGJlbG93IGlsbHVzdHJhdGVzIHRoaXMgY29uY2VwdHMgb2YgbWVhc3VyaW5nIGRpc3RhbmNlIGJ5IGNvdW50aW5nICJibG9ja3MiOgoKIVtdKF9pbWFnZXMvbWFuaGF0dGFuLTJkLmpwZyl7d2lkdGg9Ijc1JSJ9CgojIyMgRGVmaW5pdGlvbgoKSW4gYW4gKm4qLWRpbWVuc2lvbmFsIHNwYWNlLCB0aGUgTWFuaGF0dGFuIGRpc3RhbmNlIGlzIHRoZSBzdW0gb2YgdGhlIGFic29sdXRlIGRpZmZlcmVuY2VzIGFsb25nIGVhY2ggZGltZW5zaW9uLiBUaGUgZm9ybXVsYSBjYW4gYmUgZ2VuZXJhbGl6ZWQgZm9yIGEgbXVsdGlkaW1lbnNpb25hbCBzcGFjZSB0byBjYWxjdWxhdGUgdGhlIEV1Y2xpZGVhbiBkaXN0YW5jZSBiZXR3ZWVuIHR3byBwb2ludHMgKnAqIGFuZCAqcSogaW4gYW4gKm4qLWRpbWVuc2lvbmFsIHJlYWwgc3BhY2U6CgokJApEKHAscSk9XHN1bV97aT0xfV57bn0ofHBfaSAtIHFfaXwpCiQkCgpJbiB0aGlzIGdlbmVyYWwgZm9ybSwgJHAgPSAocF8xLCBwXzIsIC4uLiwgcF9uKSQgYW5kICRxID0gKHFfMSwgcV8yLCAuLi4sIHFfbikkIGFyZSB0d28gcG9pbnRzIGluICpuKi1kaW1lbnNpb25hbCBzcGFjZS4KClRoaXMgY2FsY3VsYXRpb24gYmFzaWNhbGx5IHN1bXMgdXAgdGhlIGFic29sdXRlIGRpZmZlcmVuY2VzIG9mIHRoZWlyIGNvb3JkaW5hdGVzLiBOb3RlIHRoYXQgaW4gaGlnaC1kaW1lbnNpb25hbCBzcGFjZXMsIHRoZSBFdWNsaWRlYW4gYW5kIE1hbmhhdHRhbiBkaXN0YW5jZXMgY2FuIGJlaGF2ZSBxdWl0ZSBkaWZmZXJlbnRseSwgYW5kIHRoZSBjaG9pY2UgYmV0d2VlbiB0aGVtIGRlcGVuZHMgb24gdGhlIHNwZWNpZmljIGFwcGxpY2F0aW9uIGFuZCBvZnRlbiByZXF1aXJlcyBrbm93bGVkZ2Ugb2YgdGhlIGRvbWFpbiBhbmQgc29tZSBleHBlcmltZW50YXRpb24uCgojIyMgSW1wbGVtZW50YXRpb24gaW4gUgoKVGhlIGNvZGUgYmVsb3cgZGVmaW5lcyBhIGZ1bmN0aW9uIHRoYXQgY2FsY3VsYXRlcyB0aGUgTWFuaGF0dGFuIGRpc3RhbmNlIGJldHdlZW4gdHdvIG51bWVyaWMgdmVjdG9ycyAqcCogYW5kICpxKi4gTm90ZSBob3cgd2UgYXJlIGxldmVyYWdpbmcgdGhlIHBvd2VyIG9mIHZlY3RvciBvcGVyYXRpb25zIGluIFIuIFRoZSBzdGF0ZW1lbnQgYGFicyhwIC0gcSlgIGFjdHVhbGx5IHN1YnRyYWN0cyBlYWNoIGVsZW1lbnQgZnJvbSBvbmUgdmVjdG9yIGJ5IHRoZSBjb3JyZXNwb25kaW5nIGVsZW1lbnQgb2YgdGhlIG90aGVyIGFuZCB0aGVuIGFwcGxpZXMgdGhlIGZ1bmN0aW9uIGBhYnNgIHRvIGVhY2ggZWxlbWVudCBvZiB0aGUgcmVzdWx0aW5nIHZlY3Rvci4gRmluYWxseSwgdGhlIGFic29sdXRlIGRpZmZlcmVuY2VzIGFyZSBhZGRlZCB3aXRoIHRoZSBmdW5jdGlvbiBgc3VtYC4KCmBgYHtyfQpkaXN0Lm1hbmhhdHRhbiA8LSBmdW5jdGlvbiAocCwgcSkgewogIHIgPC0gc3VtKGFicyhwIC0gcSkpCiAgcmV0dXJuIChyKQp9CmBgYAoKTGV0J3MgdHJ5IHRoZSBmdW5jdGlvbiBieSBjYWxsaW5nIGl0IHdpdGggdmFyaW91cyB2ZWN0b3JzLiBDYWxjdWxhdGUgdGhlIHJlc3VsdCBieSBoYW5kIChvciB1c2luZyBhIHNwcmVhZHNoZWV0IHByb2dyYW0pIHRvIGNvbnZpbmNlIHlvdXJzZWxmIHRoYXQgdGhlIGZ1bmN0aW9uIHdvcmtzIGNvcnJlY3RseS4KCmBgYHtyfQpwIDwtIGMoLTIsMy4xLDAuMiw0LjUpCnEgPC0gYygtMyw0LjUsLTAuMSw2LjEpCgpkIDwtIGRpc3QubWFuaGF0dGFuKHAscSkKCnByaW50KGQpCmBgYAoKIyMgSGFtbWluZyBEaXN0YW5jZQoKSGFtbWluZyBkaXN0YW5jZSBpcyBhIG1lYXN1cmUgb2YgZGlmZmVyZW5jZSBiZXR3ZWVuIHR3byBzdHJpbmdzIG9mIGVxdWFsIGxlbmd0aC4gSXQncyBuYW1lZCBhZnRlciBSaWNoYXJkIEhhbW1pbmcsIHdobyBpbnRyb2R1Y2VkIGl0IGluIHRoZSBmaWVsZCBvZiBpbmZvcm1hdGlvbiB0aGVvcnkuCgojIyMgRGVmaW5pdGlvbgoKSGFtbWluZyBkaXN0YW5jZSBpcyBzaW1wbHkgdGhlIGNvdW50IG9mIHRoZSBwb3NpdGlvbnMgYXQgd2hpY2ggdGhlIGNvcnJlc3BvbmRpbmcgc3ltYm9scyAoY2hhcmFjdGVycywgYml0cywgZXRjLikgYXJlIGRpZmZlcmVudC4KCkZvciBleGFtcGxlLCBjb25zaWRlciB0aGUgYmluYXJ5IHZlY3RvcnMgJHZfMSA9IDwxLDAsMCwxLDEsMCwxLDE+JCBhbmQgJHZfMiA9IDwxLDEsMCwxLDEsMCwwLDE+JC4gVGhlIEhhbW1pbmcgZGlzdGFuY2UgYmV0d2VlbiB0aGVzZSB0d28gdmVjdG9ycyBpcyAyIGJlY2F1c2UgdGhlcmUgYXJlIHR3byBiaXRzIHRoYXQgZGlmZmVyOgoKSGFtbWluZyBkaXN0YW5jZSBpcyB1c2VkIGluIGNvbXB1dGVyIHNjaWVuY2UgaW4gdmFyaW91cyBhcHBsaWNhdGlvbnMgc3VjaCBhcyBlcnJvciBkZXRlY3Rpb24gYW5kIGVycm9yIGNvcnJlY3Rpb24uIEl0J3MgY29tbW9ubHkgdXNlZCBpbiBnZW5ldGljcyB0byBtZWFzdXJlIHRoZSBnZW5ldGljIGRpc3RhbmNlIGJldHdlZW4gdHdvIEROQSBzdHJpbmdzLCBpbiBpbmZvcm1hdGlvbiB0aGVvcnkgZm9yIGNvZGluZyB0aGVvcnksIGFuZCBhbHNvIGluIHNvbWUgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zLCB1c3VhbGx5IGZvciBiaW5hcnkgZGF0YSBvciBjYXRlZ29yaWNhbCBkYXRhIGVuY29kZWQgdXNpbmcgYSBiaW5hcnkgZW5jb2RpbmcgKHN1Y2ggYXMgb25lLWhvdCBlbmNvZGluZykuCgpOb3RlIHRoYXQgSGFtbWluZyBkaXN0YW5jZSBpcyBkZWZpbmVkIG9ubHkgZm9yIHN0cmluZ3Mgb2YgZXF1YWwgbGVuZ3RoLiBJZiBvbmUgbmVlZHMgdG8gY29tcGFyZSBzdHJpbmdzIG9mIGRpZmZlcmVudCBsZW5ndGhzLCBvbmUgd291bGQgbmVlZCB0byB1c2UgYSBkaWZmZXJlbnQgbWVhc3VyZSwgc3VjaCBhcyB0aGUgTGV2ZW5zaHRlaW4gZGlzdGFuY2UsIHdoaWNoIGNhbiBoYW5kbGUgc3RyaW5ncyBvZiBkaWZmZXJlbnQgbGVuZ3RocyBieSBpbmNsdWRpbmcgaW5zZXJ0aW9ucyBhbmQgZGVsZXRpb25zLCBpbiBhZGRpdGlvbiB0byBzdWJzdGl0dXRpb25zLCBpbiBpdHMgY2FsY3VsYXRpb24uCgojIyMgSW1wbGVtZW50YXRpb24gaW4gUgoKVGhlIGNvZGUgYmVsb3cgZGVmaW5lcyBhIGZ1bmN0aW9uIHRoYXQgY2FsY3VsYXRlcyB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0d28gQm9vbGVhbiB2ZWN0b3JzICpwKiBhbmQgKnEqLiBOb3RlIGhvdyB0aGUgY29kZSBsZXZlcmFnZXMgdGhlIHBvd2VyIG9mIHZlY3RvciBvcGVyYXRpb25zIGluIFIuIFRoZSBzdGF0ZW1lbnQgYHAgIT0gcWAgcmV0dXJucyBhIEJvb2xlYW4gdmVjdG9yIHRoYXQgY29udGFpbnMgdGhlIHZhbHVlIG9mIHRoZSBsb2dpY2FsIG9wZXJhdGlvbiAqIT0qIGZvciBlYWNoIGNvcnJlc3BvbmRpbmcgZWxlbWVudCBpbiB0d28gdmVjdG9ycy4KCmBgYHtyfQpkaXN0LmhhbW1pbmcgPC0gZnVuY3Rpb24ocCwgcSkgewogIGRpZmZzIDwtIHAgIT0gcQogIAogIHJldHVybiAobGVuZ3RoKHdoaWNoKGRpZmZzID09IFQpKSkKfQpgYGAKCkxldCdzIHRyeSB0aGUgZnVuY3Rpb24gYnkgY2FsbGluZyBpdCB3aXRoIHZhcmlvdXMgQm9vbGVhbiAic3RyaW5ncyIuCgpgYGB7cn0KcCA8LSBjKFQsVCxGLEYpCnEgPC0gYyhULFQsVCxGKQp3IDwtIGMoRixGLFQsVCkKCnByaW50KGRpc3QuaGFtbWluZyhwLHEpKQpwcmludChkaXN0LmhhbW1pbmcocCx3KSkKcHJpbnQoZGlzdC5oYW1taW5nKHAscCkpCmBgYAoKIyMgVGhlIGBkaXN0YCBGdW5jdGlvbiBvZiBSCgpUaGUgYGRpc3RgIGZ1bmN0aW9uIG9mIHRoZSAqKnN0YXRzKiogcGFja2FnZSBpbiBSIGNhbiBjYWxjdWxhdGUgRXVjbGlkZWFuLCBIYW1taW5nLCBhbmQgTWFuaGF0dGFuIGRpc3RhbmNlLCBhcyB3ZWxsIGFzIE1pbmtvd3NraS4KCiMjIENvc2luZSBEaXN0YW5jZQoKQ29zaW5lIGRpc3RhbmNlLCBvciBtb3JlIG9mdGVuIENvc2luZSBzaW1pbGFyaXR5LCBpcyBhIG1lYXN1cmUgb2Ygc2ltaWxhcml0eSBiZXR3ZWVuIHR3byBub24temVybyB2ZWN0b3JzLiBJbnN0ZWFkIG9mIG1lYXN1cmluZyB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0aGUgdHdvIHBvaW50cyBpbiBFdWNsaWRlYW4gc3BhY2UgbGlrZSBFdWNsaWRlYW4gZGlzdGFuY2UsIGl0IG1lYXN1cmVzIHRoZSBjb3NpbmUgb2YgdGhlIGFuZ2xlIGJldHdlZW4gdGhlIHR3byB2ZWN0b3JzLiBUaGUgY29zaW5lIHNpbWlsYXJpdHkgaXMgcGFydGljdWxhcmx5IHVzZWQgd2hlbiB0aGUgbWFnbml0dWRlIG9mIHRoZSB2ZWN0b3JzIGRvZXMgbm90IG1hdHRlci4KCiMjIyBEZWZpbml0aW9uCgpUaGUgY29zaW5lIHNpbWlsYXJpdHkgYmV0d2VlbiB0d28gdmVjdG9ycyBBIGFuZCBCIGlzIGNhbGN1bGF0ZWQgYXMgdGhlIGRvdCBwcm9kdWN0IG9mIEEgYW5kIEIgZGl2aWRlZCBieSB0aGUgcHJvZHVjdCBvZiB0aGUgbWFnbml0dWRlcyBvZiBBIGFuZCBCOgoKJCQKY29zKFx0aGV0YSk9IFxmcmFje0EgXGNkb3QgQn17XHwgQVx8IFx0aW1lcyBcfCBCXHx9CiQkCgpUaGUgZG90IHByb2R1Y3QgJEEgXGNkb3QgQiQgaXMgdGhlIHN1bSBvZiB0aGUgcHJvZHVjdCBvZiB0aGUgY29ycmVzcG9uZGluZyBlbGVtZW50cyBvZiBBIGFuZCBCLiBUaGUgbWFnbml0dWRlIG9mIGEgdmVjdG9yIEEsICRcfCBBXHwkLCBpcyB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIHN1bSBvZiB0aGUgc3F1YXJlcyBvZiB0aGUgZWxlbWVudHMuCgpJZiB0aGUgdmVjdG9ycyBhcmUgbm9ybWFsaXplZCAoZWFjaCB2ZWN0b3IgaGFzIGEgbGVuZ3RoL21hZ25pdHVkZSBvZiAxKSwgdGhlIGNvc2luZSBzaW1pbGFyaXR5IHNpbXBseSBiZWNvbWVzIHRoZSBkb3QgcHJvZHVjdCBvZiB0aGUgdmVjdG9ycy4KClRoZSBjb3NpbmUgZGlzdGFuY2UgaXMgdGhlbiBkZWZpbmVkIGFzIDEgbWludXMgdGhlIGNvc2luZSBzaW1pbGFyaXR5IGFuZCBpdCByYW5nZXMgZnJvbSAwIChtZWFuaW5nIHRoZSB2ZWN0b3JzIGFyZSBpZGVudGljYWwpIHRvIDIgKG1lYW5pbmcgdGhlIHZlY3RvcnMgYXJlIGRpYW1ldHJpY2FsbHkgb3Bwb3NlZCkuCgpDb3NpbmUgc2ltaWxhcml0eSBpcyBlc3BlY2lhbGx5IGFkdmFudGFnZW91cyBpbiB0ZXh0IGFuYWx5c2lzIGFuZCBvdGhlciBoaWdoLWRpbWVuc2lvbmFsIGFuZCBzcGFyc2UgZGF0YS4gV2hlbiBjb21wYXJpbmcgdGV4dCBkb2N1bWVudHMgcmVwcmVzZW50ZWQgYXMgYmFnLW9mLXdvcmRzIG9yIFRGLUlERiB2ZWN0b3JzLCB3ZSBvZnRlbiBkb24ndCBjYXJlIGFib3V0IHRoZSBsZW5ndGggb2YgdGhlIGRvY3VtZW50cywganVzdCB0aGVpciBjb250ZW50LiBIZXJlLCB0d28gZG9jdW1lbnRzIGFyZSBjb25zaWRlcmVkIHNpbWlsYXIgaWYgdGhleSBjb250YWluIHNpbWlsYXIgd29yZHMsIGV2ZW4gaWYgb25lIGRvY3VtZW50IGlzIG11Y2ggbG9uZ2VyIHRoYW4gdGhlIG90aGVyLiBUaGUgY29zaW5lIHNpbWlsYXJpdHkgaGFuZGxlcyB0aGlzIHNpdHVhdGlvbiB3ZWxsLCB3aGljaCBpcyB3aHkgaXQncyBjb21tb25seSB1c2VkIGluIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZyAoTkxQKSBhbmQgSW5mb3JtYXRpb24gUmV0cmlldmFsLgoKSW4gc3VtbWFyeSwgY29zaW5lIHNpbWlsYXJpdHkvZGlzdGFuY2UgaXMgbW9zdCBhcHByb3ByaWF0ZSB3aGVuIHRoZSBtYWduaXR1ZGUgb2YgdGhlIHZlY3RvcnMgZG9lcyBub3QgbWF0dGVyLCBhbmQgd2UncmUgbW9yZSBpbnRlcmVzdGVkIGluIHRoZSBvcmllbnRhdGlvbiAodGhlIGRpcmVjdGlvbiBpbiB3aGljaCB0aGV5J3JlIGhlYWRpbmcpIG9mIHRoZSB2ZWN0b3JzLgoKIyMgVHV0b3JpYWwKClRoZSBjaGFsay10YWxrIGJ5IERyLiBTY2hlZGxiYXVlciB3aWxsIGdvIGludG8gbW9yZSBkZXRhaWwgb24gRXVjbGlkZWFuLCBNYW5oYXR0YW4sIGFuZCBIYW1taW5nIERpc3RhbmNlIG1lYXN1cmVzLgoKKipOb3RhIEJlbmUqKjogSW4gdGhlIHZpZGVvLCB0aGUgSGFtbWluZyBEaXN0YW5jZSBiZXR3ZWVuIHRoZSB2ZWN0b3JzIFw8MCwwLDFcPiBhbmQgXDwxLDAsMVw+IGlzIGluY29ycmVjdGx5IGNhbGN1bGF0ZWQgYXMgKjIqIHJhdGhlciB0aGFuIHRoZSBjb3JyZWN0ICoxKiBhcyB0aGUgdmVjdG9ycyBkaWZmZXIgaW4gb25lIHBvc2l0aW9uLCBzbyB0aGUgc3VtIG9mIHRoZSBkaWZmZXJlbmNlcyBpcyAqMSouCgpgYGB7PWh0bWx9CjxpZnJhbWUgc3JjPSJodHRwczovL3BsYXllci52aW1lby5jb20vdmlkZW8vODMwMzI1OTc2P2g9YzE2ZGE3MDc0NiZhbXA7dGl0bGU9MCZhbXA7YnlsaW5lPTAmYW1wO3BvcnRyYWl0PTAmYW1wO3NwZWVkPTAmYW1wO2JhZGdlPTAmYW1wO2F1dG9wYXVzZT0wJmFtcDtwbGF5ZXJfaWQ9MCZhbXA7YXBwX2lkPTU4NDc5IiB3aWR0aD0iNjQwIiBoZWlnaHQ9IjM2MCIgZnJhbWVib3JkZXI9IjAiIGFsbG93PSJhdXRvcGxheTsgZnVsbHNjcmVlbjsgcGljdHVyZS1pbi1waWN0dXJlIiBhbGxvd2Z1bGxzY3JlZW4gdGl0bGU9IkRpc3RhbmNlIE1lYXN1cmVzIiBkYXRhLWV4dGVybmFsPSIxIj48L2lmcmFtZT4KYGBgCiMjIENvbmNsdXNpb24KCkRpc3RhbmNlIG1lYXN1cmVzIGluIG1hY2hpbmUgbGVhcm5pbmcgaGVscCB0byBxdWFudGlmeSB0aGUgc2ltaWxhcml0eSBvciBkaXNzaW1pbGFyaXR5IGJldHdlZW4gZGF0YSBwb2ludHMsIHBsYXlpbmcgYSBjcnVjaWFsIHJvbGUgaW4gYWxnb3JpdGhtcyBzdWNoIGFzIGNsdXN0ZXJpbmcsIGNsYXNzaWZpY2F0aW9uLCBhbmQgbmVhcmVzdCBuZWlnaGJvcnMuIENvbW1vbiBkaXN0YW5jZSBtZWFzdXJlcyBpbmNsdWRlIEV1Y2xpZGVhbiwgTWFuaGF0dGFuLCBNaW5rb3dza2ksIEhhbW1pbmcsIENvc2luZSwgTWFoYWxhbm9iaXMsIEphY2NhcmQsIExldmVuc2h0ZWluLCBIZWxsaW5nZXIsIGFuZCBDaGVieXNoZXYuIFRoZSBjaG9pY2Ugb2YgZGlzdGFuY2UgbWVhc3VyZSBjYW4gc2lnbmlmaWNhbnRseSBpbmZsdWVuY2UgdGhlIG91dGNvbWUgb2YgYSBtYWNoaW5lIGxlYXJuaW5nIG1vZGVsIGFuZCB0eXBpY2FsbHkgZGVwZW5kcyBvbiB0aGUgc3BlY2lmaWMgbmF0dXJlIGFuZCBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEuCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIEZpbGVzICYgUmVzb3VyY2VzCgpgYGB7ciB6aXBGaWxlcywgZWNobz1GQUxTRX0KemlwTmFtZSA9IHNwcmludGYoIkxlc3NvbkZpbGVzLSVzLSVzLnppcCIsIAogICAgICAgICAgICAgICAgIHBhcmFtcyRjYXRlZ29yeSwKICAgICAgICAgICAgICAgICBwYXJhbXMkbnVtYmVyKQoKdGV4dEFMaW5rID0gcGFzdGUwKCJBbGwgRmlsZXMgZm9yIExlc3NvbiAiLCAKICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LCIuIixwYXJhbXMkbnVtYmVyKQoKIyBkb3dubG9hZEZpbGVzTGluaygpIGlzIGluY2x1ZGVkIGZyb20gX2luc2VydDJEQi5SCmtuaXRyOjpyYXdfaHRtbChkb3dubG9hZEZpbGVzTGluaygiLiIsIHppcE5hbWUsIHRleHRBTGluaykpCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBSZWZlcmVuY2VzCgpbQWJ1IEFsZmVpbGF0LCBILiBBLiwgSGFzc2FuYXQsIEEuIEIuLCBMYXNhc3NtZWgsIE8uLCBUYXJhd25laCwgQS4gUy4sIEFsaGFzYW5hdCwgTS4gQi4sIEV5YWwgU2FsbWFuLCBILiBTLiwgJiBQcmFzYXRoLCBWLiBTLiAoMjAxOSkuIEVmZmVjdHMgb2YgZGlzdGFuY2UgbWVhc3VyZSBjaG9pY2Ugb24gay1uZWFyZXN0IG5laWdoYm9yIGNsYXNzaWZpZXIgcGVyZm9ybWFuY2U6IGEgcmV2aWV3LiBCaWcgZGF0YSwgNyg0KSwgMjIxLTI0OC5dKGh0dHBzOi8vYXJ4aXYub3JnL3BkZi8xNzA4LjA0MzIxLnBkZikKCiMjIEVycmF0YQoKW0xldCB1cyBrbm93XShodHRwczovL2Zvcm0uam90Zm9ybS5jb20vMjEyMTg3MDcyNzg0MTU3KXt0YXJnZXQ9Il9ibGFuayJ9Lgo=