1 - Description
MovieLens is a data set that contains ratings from the MovieLense website (http://movielens.org). This data set is broken into a number of different sizes for research purposes. We will b using the small data set containing 100,000 ratings applied to 9,000 movies by 700 users.
Using the MovieLense data set we will construct a simple recommender system to recommend movies to users. This system will be built using user based collaborative filtering using both hand-built algorithms and the recomenderlab package.
2 - DataSet
The MovieLens data is broken into two data sets that we are interested in using. The first is the movies data that contains the movie ID, the title, and genres. The second is the user ratings data that contains the user ID, the movie ID, and the rating, and a time-stamp in UNIX time. We will drop the time-stamp data for this project.
Reading in the required data sets.
Lets take a quick look at the data that we have loaded.
3 - Building the Recommender by Hand
The first thing that we need to do is construct the functions that we will use to generate recommendations. We will be using the cosine similarity between users to generate our recommendations so we first build a cosine similarity function. We then build our function to generate the recommendations. This function takes the similarity matrix generated by the cosine similarity function, the movies and ratings data, and info on the user, number of recommendations requested, and the number of nearest neighbors to use.
4 - Recommender with Normalization
We first try to build a recommendation system using normalization. There are some issues that we will see below. The first is the meaning of zero. In order for R to calculate the matrix multiplication used in our similarity function we need to fill the NA’s as 0. If we don’t we get a matrix with diagonals of 0 and the rest of the element is NA. However once normalized 0 means a centered review and this leads to issues with the list of movies that get recommended.
4.1 - Converting the Pairwise Ratings into a Sparse Matrix with Normalization
To calculate the user based collaborative filter we need to transform the data from our pairwise data to a sparse matrix. We will use the TidyR package to reshape the data.
dim(user_movie_mat)
[1] 671 9066
4.2 - Calculating Similaity using the Cosine Similarity
Now that we have the data loaded into a sparse matrix we want to calculate the similarity using the cosine distance of each of the users. We also plot the similarity values. We see that there may be issues already given that there is not a line indicating each user’s similarity with themselves.

4.3 Recomending a Movie Based on the Cosine Similarity with Normalization
We will now use the recommender and the normalized similarity matrix to recommend a set of 10 movies to user 100 using the 30 nearest neighbors.
norm_recs
[,1]
[1,] "Young Poisoner's Handbook, The (1995)"
[2,] "Addams Family Values (1993)"
[3,] "Contact (1997)"
[4,] "No Holds Barred (1989)"
[5,] "National Velvet (1944)"
[6,] "Dracula: Dead and Loving It (1995)"
[7,] "Shanghai Triad (Yao a yao yao dao waipo qiao) (1995)"
[8,] "Friday (1995)"
[9,] "Misérables, Les (1995)"
[10,] "Screamers (1995)"
5 - Building a Recommendation System Using Non-Normalized Data
Given that we ran into a potential issue with the normalized data we will build out the recommender using the same data and functions with normalizing the data first.
5.1 - Generating the Similarity Matrix
We first need to recreate the user matrix without normalization.
5.2 - Generating the Cosine Similarities
Now that we have our non-normalized matrix we will generate the cosine similarities. We see that plot of the similarity matrix also lacks any real patterns and not surprisingly we get a different set of movies recommended to the user.

5.3 - Non-Normalized Movie Recommendations
We note that this is a different set then
recs
[,1]
[1,] "Crossing Guard, The (1995)"
[2,] "Murder in the First (1995)"
[3,] "On Golden Pond (1981)"
[4,] "Howling, The (1980)"
[5,] "Fifth Element, The (1997)"
[6,] "Hot Lead and Cold Feet (1978)"
[7,] "Metroland (1997)"
[8,] "Morning After, The (1986)"
[9,] "Sister Act 2: Back in the Habit (1993)"
[10,] "Where the Money Is (2000)"
6 - Building A Recommendation System Using recomenderlab
For the final portion of the project we will recommenderlab package to build a recommendation for users. The process is relatively straight forward as the package takes the sparse user-review matrix, converts it to a real rating matrix, and then builds the recommender model based on this information.
movie_lab <- function(user_mat, movies, user, num_recs = 10, neigh = 10){
#Convert rating matrix into a recommenderlab sparse matrix
user_mat <- as(user_mat, "realRatingMatrix")
#Create Recommender Model. "UBCF" stands for User-Based Collaborative Filtering
recommender_model <- Recommender(user_mat,
method = "UBCF",
param=list(method="Cosine",nn = neigh))
recom <- predict(recommender_model,
user_mat[user],
n=num_recs) #Obtain top 10 recommendations for 1st user in dataset
recom_list <- as(recom, "list") #convert recommenderlab object to readable list
recom_result <- matrix(0,num_recs)
for (i in c(1:num_recs)){
recom_result[i] <- movies$title[as.integer(recom_list[[1]][i])]
}h
Error: unexpected symbol in:
" recom_result[i] <- movies$title[as.integer(recom_list[[1]][i])]
}h"
6.1 - Recommenderlab Movie Recommendations
Using the function above we can now generate the top to recommendation for user 100 using the 30 nearest neighbors. We see that these recommendations are once again different from the ones that we generated by hand above.
lab_recs
[,1]
[1,] "Scarlet Letter, The (1926)"
[2,] "Only You (1994)"
[3,] "Crooklyn (1994)"
[4,] "Substitute, The (1996)"
[5,] "Mary Reilly (1996)"
[6,] "Month by the Lake, A (1995)"
[7,] "Top Hat (1935)"
[8,] "Eye for an Eye (1996)"
[9,] "Mighty Aphrodite (1995)"
[10,] "Tales from the Hood (1995)"
LS0tDQp0aXRsZTogJ0RBVEE2NDMgLSBQcm9qZWN0IDE6IEJhc2ljIFJlY29tbWVuZGVyIFN5c3RlbScNCmF1dGhvcjogIkVyaWsgTnlsYW5kZXIiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCmBgYHtSLCBpbmNsdWRlID0gRkFMU0V9DQpyZXF1aXJlKHRpZHlyKQ0KcmVxdWlyZShkcGx5cikNCmBgYA0KIyMgMSAtIERlc2NyaXB0aW9uDQoqKioNCltNb3ZpZUxlbnNdKGh0dHBzOi8vZ3JvdXBsZW5zLm9yZy9kYXRhc2V0cy9tb3ZpZWxlbnMvKSBpcyBhIGRhdGEgc2V0IHRoYXQgY29udGFpbnMgcmF0aW5ncyBmcm9tIHRoZSBNb3ZpZUxlbnNlIHdlYnNpdGUgKGh0dHA6Ly9tb3ZpZWxlbnMub3JnKS4gVGhpcyBkYXRhIHNldCBpcyBicm9rZW4gaW50byBhIG51bWJlciBvZiBkaWZmZXJlbnQgc2l6ZXMgZm9yIHJlc2VhcmNoIHB1cnBvc2VzLiBXZSB3aWxsIGIgdXNpbmcgdGhlIHNtYWxsIGRhdGEgc2V0IGNvbnRhaW5pbmcgMTAwLDAwMCByYXRpbmdzIGFwcGxpZWQgdG8gOSwwMDAgbW92aWVzIGJ5IDcwMCB1c2Vycy4gIA0KDQpVc2luZyB0aGUgTW92aWVMZW5zZSBkYXRhIHNldCB3ZSB3aWxsIGNvbnN0cnVjdCBhIHNpbXBsZSByZWNvbW1lbmRlciBzeXN0ZW0gdG8gcmVjb21tZW5kIG1vdmllcyB0byB1c2Vycy4gVGhpcyBzeXN0ZW0gd2lsbCBiZSBidWlsdCB1c2luZyB1c2VyIGJhc2VkIGNvbGxhYm9yYXRpdmUgZmlsdGVyaW5nIHVzaW5nIGJvdGggaGFuZC1idWlsdCBhbGdvcml0aG1zIGFuZCB0aGUgKnJlY29tZW5kZXJsYWIqIHBhY2thZ2UuDQoNCiMjIDIgLSBEYXRhU2V0DQoqKioNClRoZSBNb3ZpZUxlbnMgZGF0YSBpcyBicm9rZW4gaW50byB0d28gZGF0YSBzZXRzIHRoYXQgd2UgYXJlIGludGVyZXN0ZWQgaW4gdXNpbmcuIFRoZSBmaXJzdCBpcyB0aGUgbW92aWVzIGRhdGEgdGhhdCBjb250YWlucyB0aGUgbW92aWUgSUQsIHRoZSB0aXRsZSwgYW5kIGdlbnJlcy4gVGhlIHNlY29uZCBpcyB0aGUgdXNlciByYXRpbmdzIGRhdGEgdGhhdCBjb250YWlucyB0aGUgdXNlciBJRCwgdGhlIG1vdmllIElELCBhbmQgdGhlIHJhdGluZywgYW5kIGEgdGltZS1zdGFtcCBpbiBVTklYIHRpbWUuIFdlIHdpbGwgZHJvcCB0aGUgdGltZS1zdGFtcCBkYXRhIGZvciB0aGlzIHByb2plY3QuDQoNClJlYWRpbmcgaW4gdGhlIHJlcXVpcmVkIGRhdGEgc2V0cy4NCg0KYGBge1IsIGluY2x1ZGUgPSBUUlVFfQ0KbW92aWVzIDwtIHJlYWQuY3N2KCJ+L0dpdEh1Yi9EQVRBNjQzL2RhdGEvbWwtbGF0ZXN0LXNtYWxsL21vdmllcy5jc3YiLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiLCIsDQogICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFLCBlbmNvZGluZyA9ICJVVEYtOCIpDQpyYXRpbmdzIDwtIHJlYWQuY3N2KCJ+L0dpdEh1Yi9EQVRBNjQzL2RhdGEvbWwtbGF0ZXN0LXNtYWxsL3JhdGluZ3MuY3N2IiwgaGVhZGVyID0gVFJVRSwgc2VwID0iLCIsDQogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCnJhdGluZ3MgPC0gcmF0aW5nc1ssYygxLDIsMyldDQpgYGANCg0KTGV0cyB0YWtlIGEgcXVpY2sgbG9vayBhdCB0aGUgZGF0YSB0aGF0IHdlIGhhdmUgbG9hZGVkLg0KDQojIyMjIDIuMSAtIE1vdmllcw0KYGBge1J9DQpkaW0obW92aWVzKQ0KaGVhZChtb3ZpZXMpDQpgYGANCg0KIyMjIyAyLjIgLSBSYXRpbmdzDQpgYGB7Un0NCmRpbShyYXRpbmdzKQ0KaGVhZChyYXRpbmdzKQ0KYGBgDQoNCg0KIyMgMyAtIEJ1aWxkaW5nIHRoZSBSZWNvbW1lbmRlciBieSBIYW5kDQoqKioNClRoZSBmaXJzdCB0aGluZyB0aGF0IHdlIG5lZWQgdG8gZG8gaXMgY29uc3RydWN0IHRoZSBmdW5jdGlvbnMgdGhhdCB3ZSB3aWxsIHVzZSB0byBnZW5lcmF0ZSByZWNvbW1lbmRhdGlvbnMuIFdlIHdpbGwgYmUgdXNpbmcgdGhlIGNvc2luZSBzaW1pbGFyaXR5IGJldHdlZW4gdXNlcnMgdG8gZ2VuZXJhdGUgb3VyIHJlY29tbWVuZGF0aW9ucyBzbyB3ZSBmaXJzdCBidWlsZCBhIGNvc2luZSBzaW1pbGFyaXR5IGZ1bmN0aW9uLiBXZSB0aGVuIGJ1aWxkIG91ciBmdW5jdGlvbiB0byBnZW5lcmF0ZSB0aGUgcmVjb21tZW5kYXRpb25zLiBUaGlzIGZ1bmN0aW9uIHRha2VzIHRoZSBzaW1pbGFyaXR5IG1hdHJpeCBnZW5lcmF0ZWQgYnkgdGhlIGNvc2luZSBzaW1pbGFyaXR5IGZ1bmN0aW9uLCB0aGUgbW92aWVzIGFuZCByYXRpbmdzIGRhdGEsIGFuZCBpbmZvIG9uIHRoZSB1c2VyLCBudW1iZXIgb2YgcmVjb21tZW5kYXRpb25zIHJlcXVlc3RlZCwgYW5kIHRoZSBudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMgdG8gdXNlLg0KDQpgYGB7Un0NCiMgQ2FsY3VsYXRlIHRoZSBjb3NpbmUgc2ltaWxhcml0eSB1c2luZyBidWlsdCBpbiBsaW5lYXIgYWxnZWJyYS4NCmNvc2luZVNpbSA8LSBmdW5jdGlvbih4KXsNCiAgeCA8LSBhcy5kaXN0KHglKiV0KHgpLyhzcXJ0KHJvd1N1bXMoeF4yKSAlKiUgdChyb3dTdW1zKHheMikpKSkpIA0KICByZXR1cm4oeCkNCn0NCg0KIyBNb3ZlIHJlY29tbWVuZGF0aW9uIHVuY3Rpb24NCm1vdmllX3JlYyA8LSBmdW5jdGlvbihzaW1fbWF0LCBtb3ZpZXMsIHJhdGluZ3MsIHVzZXIsIG51bV9yZWNzID0gMTAsIG5uID0gMTApew0KICBzaW1fdmVjIDwtIHNpbV9tYXRbdXNlcixdDQogIHNpbV92ZWMgPC0gbmFtZXMoc29ydChzaW1fdmVjKSkNCiAgc2ltX3ZlYyA8LSBzaW1fdmVjWzI6bm4rMV0NCiAgcmV2aWV3ZWQgPC0gcmF0aW5nc1tyYXRpbmdzJHVzZXJJZCA9PSB1c2VyLF0NCiAgDQogIHN1Z2dlc3RlZCA8LSByYXRpbmdzICU+JQ0KICAgIGZpbHRlcihyYXRpbmdzJHVzZXJJZCAlaW4lIHNpbV92ZWMpICU+JQ0KICAgIGdyb3VwX2J5KG1vdmllSWQpICU+JQ0KICAgIHN1bW1hcmlzZSgNCiAgICAgIGNvdW50ID0gbigpLCAjSG93IG1hbnkgdGltZXMgd2FzIHRoZSBtb3ZpZSByYXRlZA0KICAgICAgYXZnX3JhdGluZyA9IG1lYW4ocmF0aW5nKSAjV2hhdCBpcyB0aGUgYXZlcmFnZSByYXRpbmcgZnJvbSB0aGUgc2ltaWxhciByZXZpZXdlcnMNCiAgICApICU+JQ0KICAgIGZpbHRlcighKG1vdmllSWQgJWluJSByZXZpZXdlZCRtb3ZpZUlkKSkgJT4lDQogICAgYXJyYW5nZShkZXNjKGF2Z19yYXRpbmcpLCBkZXNjKGNvdW50KSkNCg0KICByZWNfbGlzdCA8LSBtYXRyaXgoMCwgbnVtX3JlY3MpDQogIGZvcihpIGluIDE6bnVtX3JlY3Mpew0KICAgIHJlY19saXN0W2ldIDwtIG1vdmllcyR0aXRsZVtzdWdnZXN0ZWQkbW92aWVJZFtpXV0NCiAgfQ0KICByZXR1cm4ocmVjX2xpc3QpDQp9DQpgYGANCg0KIyMgNCAtIFJlY29tbWVuZGVyIHdpdGggTm9ybWFsaXphdGlvbg0KKioqDQpXZSBmaXJzdCB0cnkgdG8gYnVpbGQgYSByZWNvbW1lbmRhdGlvbiBzeXN0ZW0gdXNpbmcgbm9ybWFsaXphdGlvbi4gVGhlcmUgYXJlIHNvbWUgaXNzdWVzIHRoYXQgd2Ugd2lsbCBzZWUgYmVsb3cuIFRoZSBmaXJzdCBpcyB0aGUgbWVhbmluZyBvZiB6ZXJvLiBJbiBvcmRlciBmb3IgUiB0byBjYWxjdWxhdGUgdGhlIG1hdHJpeCBtdWx0aXBsaWNhdGlvbiB1c2VkIGluIG91ciBzaW1pbGFyaXR5IGZ1bmN0aW9uIHdlIG5lZWQgdG8gZmlsbCB0aGUgTkEncyBhcyAwLiBJZiB3ZSBkb24ndCB3ZSBnZXQgYSBtYXRyaXggd2l0aCBkaWFnb25hbHMgb2YgMCBhbmQgdGhlIHJlc3Qgb2YgdGhlIGVsZW1lbnQgaXMgTkEuIEhvd2V2ZXIgb25jZSBub3JtYWxpemVkIDAgbWVhbnMgYSBjZW50ZXJlZCByZXZpZXcgYW5kIHRoaXMgbGVhZHMgdG8gaXNzdWVzIHdpdGggdGhlIGxpc3Qgb2YgbW92aWVzIHRoYXQgZ2V0IHJlY29tbWVuZGVkLg0KDQojIyMjIDQuMSAtIENvbnZlcnRpbmcgdGhlIFBhaXJ3aXNlIFJhdGluZ3MgaW50byBhIFNwYXJzZSBNYXRyaXggd2l0aCBOb3JtYWxpemF0aW9uDQpUbyBjYWxjdWxhdGUgdGhlIHVzZXIgYmFzZWQgY29sbGFib3JhdGl2ZSBmaWx0ZXIgd2UgbmVlZCB0byB0cmFuc2Zvcm0gdGhlIGRhdGEgZnJvbSBvdXIgcGFpcndpc2UgZGF0YSB0byBhIHNwYXJzZSBtYXRyaXguIFdlIHdpbGwgdXNlIHRoZSAqVGlkeVIqIHBhY2thZ2UgdG8gcmVzaGFwZSB0aGUgZGF0YS4gIA0KYGBge1J9DQojIENyZWF0aW5nIGEgc3BhcnNlIG1hdHJpeCBvZiB1c2VycyBhcyByb3dzIGFuZCBtb3ZpZUlkIGFzIGNvbHVtbnMuDQp1c2VyX21vdmllX21hdCA8LSByYXRpbmdzICU+JQ0KICBzcHJlYWQoa2V5ID0gbW92aWVJZCwgdmFsdWUgPSByYXRpbmcpICU+JQ0KICBhcy5tYXRyaXgoKQ0KDQp1c2VyX21vdmllX21hdCA9IHVzZXJfbW92aWVfbWF0WywtMV0gI3JlbW92ZSB1c2VySUQgY29sLiBSb3dzIGFyZSB1c2VySWRzLCBjb2xzIGFyZSBtb3ZpZUlkcw0KDQp1c2VyX21vdmllX21hdCA8LSB0KHNjYWxlKHQodXNlcl9tb3ZpZV9tYXQpLCBjZW50ZXIgPSBUUlVFLCBzY2FsZSA9IFRSVUUpKSAjcHJlZm9ybWlubmcgcm93IHdpc2Ugbm9ybWFsaXphdGlvbi4NCg0KdXNlcl9tb3ZpZV9tYXRbaXMubmEodXNlcl9tb3ZpZV9tYXQpXSA8LSAwICNjb252ZXJ0aW5nIHRoZSBOQSdzIHRvIDAgc28gdGhhdCB3ZSBjYW4gbXVsdGlwbHkgdGhlIG1hdHJpY2VzIGJlbG93LiANCmBgYA0KDQojIyMjIDQuMiAtIENhbGN1bGF0aW5nIFNpbWlsYWl0eSB1c2luZyB0aGUgQ29zaW5lIFNpbWlsYXJpdHkNCk5vdyB0aGF0IHdlIGhhdmUgdGhlIGRhdGEgbG9hZGVkIGludG8gYSBzcGFyc2UgbWF0cml4IHdlIHdhbnQgdG8gY2FsY3VsYXRlIHRoZSBzaW1pbGFyaXR5IHVzaW5nIHRoZSBjb3NpbmUgZGlzdGFuY2Ugb2YgZWFjaCBvZiB0aGUgdXNlcnMuIFdlIGFsc28gcGxvdCB0aGUgc2ltaWxhcml0eSB2YWx1ZXMuIFdlIHNlZSB0aGF0IHRoZXJlIG1heSBiZSBpc3N1ZXMgYWxyZWFkeSBnaXZlbiB0aGF0IHRoZXJlIGlzIG5vdCBhIGxpbmUgaW5kaWNhdGluZyBlYWNoIHVzZXIncyBzaW1pbGFyaXR5IHdpdGggdGhlbXNlbHZlcy4NCg0KYGBge1J9DQojIENyZWF0aW5nIHRoZSBzaW1pbGFyaXR5IG1hdHJpeA0Kc2ltaWxhcl9yZXZpZXdzX25vcm0gPC0gYXMubWF0cml4KGNvc2luZVNpbSh1c2VyX21vdmllX21hdCkpDQoNCmltYWdlKHNpbWlsYXJfcmV2aWV3c19ub3JtKQ0KYGBgDQoNCg0KIyMjIyA0LjMgUmVjb21lbmRpbmcgYSBNb3ZpZSBCYXNlZCBvbiB0aGUgQ29zaW5lIFNpbWlsYXJpdHkgd2l0aCBOb3JtYWxpemF0aW9uDQpXZSB3aWxsIG5vdyB1c2UgdGhlIHJlY29tbWVuZGVyIGFuZCB0aGUgbm9ybWFsaXplZCBzaW1pbGFyaXR5IG1hdHJpeCB0byByZWNvbW1lbmQgYSBzZXQgb2YgMTAgbW92aWVzIHRvIHVzZXIgMTAwIHVzaW5nIHRoZSAzMCBuZWFyZXN0IG5laWdoYm9ycy4gDQpgYGB7Un0NCm5vcm1fcmVjcyA8LSBtb3ZpZV9yZWMoc2ltaWxhcl9yZXZpZXdzX25vcm0sIG1vdmllcywgcmF0aW5ncywgdXNlciA9IDEwMCwgbnVtX3JlY3MgPSAxMCwgbm4gPSAzMCkNCm5vcm1fcmVjcw0KYGBgDQoNCiMjIDUgLSBCdWlsZGluZyBhIFJlY29tbWVuZGF0aW9uIFN5c3RlbSBVc2luZyBOb24tTm9ybWFsaXplZCBEYXRhDQoqKioNCkdpdmVuIHRoYXQgd2UgcmFuIGludG8gYSBwb3RlbnRpYWwgaXNzdWUgd2l0aCB0aGUgbm9ybWFsaXplZCBkYXRhIHdlIHdpbGwgYnVpbGQgb3V0IHRoZSByZWNvbW1lbmRlciB1c2luZyB0aGUgc2FtZSBkYXRhIGFuZCBmdW5jdGlvbnMgd2l0aCBub3JtYWxpemluZyB0aGUgZGF0YSBmaXJzdC4NCg0KIyMjIyA1LjEgLSBHZW5lcmF0aW5nIHRoZSBTaW1pbGFyaXR5IE1hdHJpeA0KV2UgZmlyc3QgbmVlZCB0byByZWNyZWF0ZSB0aGUgdXNlciBtYXRyaXggd2l0aG91dCBub3JtYWxpemF0aW9uLg0KYGBge1J9DQojIENyZWF0aW5nIGEgc3BhcnNlIG1hdHJpeCBvZiB1c2VycyBhcyByb3dzIGFuZCBtb3ZpZUlkIGFzIGNvbHVtbnMuDQp1c2VyX21vdmllX21hdCA8LSByYXRpbmdzICU+JQ0KICBzcHJlYWQoa2V5ID0gbW92aWVJZCwgdmFsdWUgPSByYXRpbmcpICU+JQ0KICBhcy5tYXRyaXgoKQ0KDQp1c2VyX21vdmllX21hdCA9IHVzZXJfbW92aWVfbWF0WywtMV0gI3JlbW92ZSB1c2VySUQgY29sLiBSb3dzIGFyZSB1c2VySWRzLCBjb2xzIGFyZSBtb3ZpZUlkcw0KDQp1c2VyX21vdmllX21hdFtpcy5uYSh1c2VyX21vdmllX21hdCldIDwtIDAgI2NvbnZlcnRpbmcgdGhlIE5BJ3MgdG8gMCBzbyB0aGF0IHdlIGNhbiBtdWx0aXBseSB0aGUgbWF0cmljZXMgYmVsb3cuIA0KYGBgDQoNCiMjIyMgNS4yIC0gR2VuZXJhdGluZyB0aGUgQ29zaW5lIFNpbWlsYXJpdGllcw0KTm93IHRoYXQgd2UgaGF2ZSBvdXIgbm9uLW5vcm1hbGl6ZWQgbWF0cml4IHdlIHdpbGwgZ2VuZXJhdGUgdGhlIGNvc2luZSBzaW1pbGFyaXRpZXMuIFdlIHNlZSB0aGF0IHBsb3Qgb2YgdGhlIHNpbWlsYXJpdHkgbWF0cml4IGFsc28gbGFja3MgYW55IHJlYWwgcGF0dGVybnMgYW5kIG5vdCBzdXJwcmlzaW5nbHkgd2UgZ2V0IGEgZGlmZmVyZW50IHNldCBvZiBtb3ZpZXMgcmVjb21tZW5kZWQgdG8gdGhlIHVzZXIuIA0KYGBge1J9DQojIENyZWF0aW5nIHRoZSBzaW1pbGFyaXR5IG1hdHJpeA0Kc2ltaWxhcl9yZXZpZXdzIDwtIGFzLm1hdHJpeChjb3NpbmVTaW0odXNlcl9tb3ZpZV9tYXQpKQ0KDQppbWFnZShzaW1pbGFyX3Jldmlld3MpDQpgYGANCiMjIyMgNS4zIC0gTm9uLU5vcm1hbGl6ZWQgTW92aWUgUmVjb21tZW5kYXRpb25zDQpXZSBub3RlIHRoYXQgdGhpcyBpcyBhIGRpZmZlcmVudCBzZXQgdGhlbiANCmBgYHtSfQ0KcmVjcyA8LSBtb3ZpZV9yZWMoc2ltaWxhcl9yZXZpZXdzLCBtb3ZpZXMsIHJhdGluZ3MsIHVzZXIgPSAxMDAsIG51bV9yZWNzID0gMTAsIG5uID0gMzApDQpyZWNzDQpgYGANCg0KIyMgNiAtIEJ1aWxkaW5nIEEgUmVjb21tZW5kYXRpb24gU3lzdGVtIFVzaW5nICpyZWNvbWVuZGVybGFiKg0KKioqDQpGb3IgdGhlIGZpbmFsIHBvcnRpb24gb2YgdGhlIHByb2plY3Qgd2Ugd2lsbCAqcmVjb21tZW5kZXJsYWIqIHBhY2thZ2UgdG8gYnVpbGQgYSByZWNvbW1lbmRhdGlvbiBmb3IgdXNlcnMuIFRoZSBwcm9jZXNzIGlzIHJlbGF0aXZlbHkgc3RyYWlnaHQgZm9yd2FyZCBhcyB0aGUgcGFja2FnZSB0YWtlcyB0aGUgc3BhcnNlIHVzZXItcmV2aWV3IG1hdHJpeCwgY29udmVydHMgaXQgdG8gYSByZWFsIHJhdGluZyBtYXRyaXgsIGFuZCB0aGVuIGJ1aWxkcyB0aGUgcmVjb21tZW5kZXIgbW9kZWwgYmFzZWQgb24gdGhpcyBpbmZvcm1hdGlvbi4gDQpgYGB7Un0NCmxpYnJhcnkocmVjb21tZW5kZXJsYWIpDQoNCiMgQ3JlYXRpbmcgYSBzcGFyc2UgbWF0cml4IG9mIHVzZXJzIGFzIHJvd3MgYW5kIG1vdmllSWQgYXMgY29sdW1ucy4NCnVzZXJfbW92aWVfbWF0IDwtIHJhdGluZ3MgJT4lDQogIHNwcmVhZChrZXkgPSBtb3ZpZUlkLCB2YWx1ZSA9IHJhdGluZykgJT4lDQogIGFzLm1hdHJpeCgpDQoNCnVzZXJfbW92aWVfbWF0ID0gdXNlcl9tb3ZpZV9tYXRbLC0xXQ0KDQptb3ZpZV9sYWIgPC0gZnVuY3Rpb24odXNlcl9tYXQsIG1vdmllcywgdXNlciwgbnVtX3JlY3MgPSAxMCwgbmVpZ2ggPSAxMCl7DQogICNDb252ZXJ0IHJhdGluZyBtYXRyaXggaW50byBhIHJlY29tbWVuZGVybGFiIHNwYXJzZSBtYXRyaXgNCiAgdXNlcl9tYXQgPC0gYXModXNlcl9tYXQsICJyZWFsUmF0aW5nTWF0cml4IikNCiAgDQogICNDcmVhdGUgUmVjb21tZW5kZXIgTW9kZWwuICJVQkNGIiBzdGFuZHMgZm9yIFVzZXItQmFzZWQgQ29sbGFib3JhdGl2ZSBGaWx0ZXJpbmcNCiAgcmVjb21tZW5kZXJfbW9kZWwgPC0gUmVjb21tZW5kZXIodXNlcl9tYXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiVUJDRiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbT1saXN0KG1ldGhvZD0iQ29zaW5lIixubiA9IG5laWdoKSkNCiAgcmVjb20gPC0gcHJlZGljdChyZWNvbW1lbmRlcl9tb2RlbCwgDQogICAgICAgICAgICAgICAgICAgdXNlcl9tYXRbdXNlcl0sIA0KICAgICAgICAgICAgICAgICAgIG49bnVtX3JlY3MpICNPYnRhaW4gdG9wIDEwIHJlY29tbWVuZGF0aW9ucyBmb3IgMXN0IHVzZXIgaW4gZGF0YXNldA0KICANCiAgcmVjb21fbGlzdCA8LSBhcyhyZWNvbSwgImxpc3QiKSAjY29udmVydCByZWNvbW1lbmRlcmxhYiBvYmplY3QgdG8gcmVhZGFibGUgbGlzdA0KICANCiAgcmVjb21fcmVzdWx0IDwtIG1hdHJpeCgwLG51bV9yZWNzKQ0KICBmb3IgKGkgaW4gYygxOm51bV9yZWNzKSl7DQogICAgcmVjb21fcmVzdWx0W2ldIDwtIG1vdmllcyR0aXRsZVthcy5pbnRlZ2VyKHJlY29tX2xpc3RbWzFdXVtpXSldDQogIH0NCiAgcmV0dXJuKHJlY29tX3Jlc3VsdCkNCn0NCmBgYA0KIyMjIyA2LjEgLSBSZWNvbW1lbmRlcmxhYiBNb3ZpZSBSZWNvbW1lbmRhdGlvbnMNClVzaW5nIHRoZSBmdW5jdGlvbiBhYm92ZSB3ZSBjYW4gbm93IGdlbmVyYXRlIHRoZSB0b3AgdG8gcmVjb21tZW5kYXRpb24gZm9yIHVzZXIgMTAwIHVzaW5nIHRoZSAzMCBuZWFyZXN0IG5laWdoYm9ycy4gV2Ugc2VlIHRoYXQgdGhlc2UgcmVjb21tZW5kYXRpb25zIGFyZSBvbmNlIGFnYWluIGRpZmZlcmVudCBmcm9tIHRoZSBvbmVzIHRoYXQgd2UgZ2VuZXJhdGVkIGJ5IGhhbmQgYWJvdmUuDQpgYGB7Un0NCmxhYl9yZWNzIDwtIG1vdmllX2xhYih1c2VyX21vdmllX21hdCwgbW92aWVzLCB1c2VyID0gMTAwLCBudW1fcmVjcyA9IDEwLCBuZWlnaCA9IDMwKQ0KbGFiX3JlY3MNCmBgYA0KDQojIyA3IC0gQ29tcGFyaXNvbiBvZiB0aGUgVGhyZWUgUmVjb21lbmRhdGlvbnMgYW5kIFBlcmZvcm1hbmNlDQpUaGUgZmlyc3QgdGhpbmcgdGhhdCB3ZSBub3RlIGlzIHRoYXQgdGhlcmUgaXMgYSBkZWZpbml0ZSBkaWZmZXJlbmNlIGluIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgaGFuZCBjb2RlZCBhbGdvcml0aG0gYW5kIHRoZSAqcmVjb21lbmRlcmxhYiogcGFja2FnZS4gVGhlIGhhbmQgY29kZWQgcG9ydGlvbiBvZiB0aGUgYWxnb3JpdGhtIHRoYXQgZ2VuZXJhdGVzIHRoZSBjb3NpbmUgc2ltaWxhcml0eSB3YXMgYSBzbG93IHByb2Nlc3MuIEl0IHRvb2sgMjAgdG8gMzAgc2Vjb25kcyBpZiBub3QgbW9yZSB0byBjYWxjdWxhdGUgdGhlIG11bHRpcGxpY2F0aW9ucyB0byBnZW5lcmF0ZSB0aGUgc2ltaWxhcml0eSBtYXRyaXguIFRoZSAqcmVjb21tZW5kZXJsYWIqIHBhY2thZ2UgZG9lcyB0aGlzIHByb2Nlc3MgbXVjaCBtb3JlIHF1aWNrbHkgYW5kIHRoZSBtb2RlbCBjYW4gYmUgZ2VuZXJhdGVkIGVhY2ggdGltZSB0aGUgZnVuY3Rpb24gaXMgY2FsbGVkIHdpdGhvdXQgcGVyZm9ybWFuY2UgaGl0cy4gIA0KDQpUaGUgc2Vjb25kIHRoaW5nIHRoYXQgd2Ugbm90aWNlIGlzIHRoYXQgYWxsIHRocmVlIG9mIHRoZSBtZXRob2RzIG9mIGRvaW5nIHJlY29tbWVuZGF0aW9ucyBnZW5lcmF0ZSBkaWZmZXJlbnQgcmVzdWx0cyBhcyB3ZSBjYW4gc2VlIGJlbG93LiBNeSBiZXN0IGd1ZXNzIGlzIHRoYXQgdGhlIG1ldGhvZCB0aGF0IEkgdXNlZCBpbiB0aGUgZnVuY3Rpb24gdGhhdCBnZW5lcmF0ZXMgdGhlIHJlY29tbWVuZGF0aW9ucyBmcm9tIHRoZSBzaW1pbGFyaXR5IG1hdHJpeCBpcyBub3QgdXNpbmcgdGhlIGNvcnJlY3QgbWV0aG9kIGZvciBnZXR0aW5nIHRoZSBiZXN0IHJlc3VsdHMuIEl0IGlzIGludGVyZXN0aW5nIHRvIHNlZSB0aGF0IHRoZSBub3JtYWxpemVkIGFuZCBub24tbm9ybWFsaXplZCBsZWFkIHRvIGRpZmZlcmVudCByZXN1bHRzIGJ1dCB0aGlzIGlzIGxpa2VseSB0aGUgZmFjdCB0aGF0IHdlIG5lZWQgdG8gaW5jbHVkZSB6ZXJvJ3MgdG8gZG8gdGhlIG1hdHJpeCBtdWx0aXBsaWNhdGlvbiB3aGljaCBoYXZlIGEgbWVhbmluZyBvbmNlIHdlIG5vcm1hbGl6ZSB0aGUgZGF0YS4NCmBgYHtSfQ0KcmVzdWx0cyA8LSBkYXRhLmZyYW1lKE5vcm1hbGl6ZWQgPSBub3JtX3JlY3MsIE5vbl9Ob3JtYWxpemVkID0gcmVjcywgUmVjb21tZW5kZXJsYWIgPSBsYWJfcmVjcykNCnJlc3VsdHMNCmBgYA==