Myanimelist collects the ratings of several million users, and uses
those to make an overall anime rank. The problem with this method is
that some people systematically give higher ratings than others, and
those people might be more likely to watch certain shows. Conversely,
there is the same issue on the user side, where some people might
deliberately search out shows with higher ratings than others.
Normally this issue would be solved with regression, which cannot be
done here because there are 10,000 different titles and 1,000,000
different users, making it impossible to run the algorithm due to memory
constraints. Instead, I used an interated method where I observed which
raters gave higher ratings than others controlling for the anime they
watched, and what anime received higher ratings than others controlling
for the users who rated them. Because the confounding goes both ways, so
I had to repeat the method to get roughly reliable results.
Loading and cleaning data
setwd('~')
Warning: The working directory was changed to C:/Users/micha/OneDrive/Documents inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
setwd('rfolder/anime')
animeids <- read.csv('data/anime.csv', sep='\t')
#original anime ranks
ogranks <- read.csv('data2/aranks_v1.csv', sep=',')
#first iteration
aranks2 <- read.csv('data2/aranks_v2.csv', sep=',')
#fifth iteration
aranks5 <- read.csv('data2/aranks_v6.csv', sep=',')
sequel <- read.csv('data2/sequels.csv', sep=',') %>% select(anime_id, Sequel)
#tenth iteration
aranks10 <- read.csv('data2/aranks_v11.csv', sep=',')
animeids <- read.csv('data/anime.csv', sep='\t')
names <- animeids %>% select(anime_id, title)
original_ranks <- left_join(ogranks, names, by='anime_id') %>% arrange(-animescore)
first_iteration <- left_join(aranks2, names, by='anime_id') %>% arrange(-animescore)
fifth_iteration <- left_join(aranks5, names, by='anime_id') %>% arrange(-animescore)
tenth_iteration <- left_join(aranks10, names, by='anime_id') %>% arrange(-animescore)
mean(original_ranks$animescore, na.rm=T)
[1] 6.284712
sd(original_ranks$animescore, na.rm=T)
[1] 1.052224
first_iteration$animescore = normalise(first_iteration$animescore)*1.052224+6.284712
fifth_iteration$animescore = normalise(fifth_iteration$animescore)*1.052224+6.284712
tenth_iteration$animescore = normalise(tenth_iteration$animescore)*1.052224+6.284712
first_iteration$animescore1 = first_iteration$animescore
fifth_iteration$animescore5 = fifth_iteration$animescore
tenth_iteration$animescore10 = tenth_iteration$animescore
newdata <- left_join(original_ranks, first_iteration %>% select(anime_id, animescore1), by='anime_id') %>% arrange(-animescore)
newdata <- left_join(newdata, fifth_iteration %>% select(anime_id, animescore5), by='anime_id') %>% arrange(-animescore)
newdata <- left_join(newdata, tenth_iteration %>% select(anime_id, animescore10), by='anime_id') %>% arrange(-animescore)
newdata <- left_join(newdata, sequel %>% select(anime_id, Sequel), by='anime_id') %>% arrange(-animescore)
newdata$adv <- newdata$animescore10 - newdata$animescore
Correlation matrix. Correlation between fifth iteration ratings and
tenth iteration is about .996.
correlation_matrix(newdata %>% select(animescore, animescore1, animescore5, animescore10))
animescore animescore1 animescore5 animescore10
animescore "NA" "0.912 ***" "0.952 ***" "0.952 ***"
animescore1 "0.912 ***" "NA" "0.967 ***" "0.959 ***"
animescore5 "0.952 ***" "0.967 ***" "NA" "1 ***"
animescore10 "0.952 ***" "0.959 ***" "1 ***" "NA"
The rankings
Kicking out anime with less than 5k ratings
newdata$originalrank <- ranker(newdata$animescore)
newdata$newrank <- ranker(newdata$animescore10)
newdata2 <- newdata %>% filter(an > 4999)
Top 20 for each category. animescore – original rating, animescore10
– user adjusted rating.
original <- newdata2 %>% filter(Sequel==0)
sequel <- newdata2 %>% filter(Sequel==1)
head(newdata2 %>% filter(Sequel==1) %>% select(title, animescore) %>% arrange(-animescore), n=20)
head(newdata2 %>% filter(Sequel==1) %>% select(title, animescore10) %>% arrange(-animescore10), n=20)
head(newdata2 %>% filter(Sequel==0) %>% select(title, animescore) %>% arrange(-animescore), n=20)
head(newdata2 %>% filter(Sequel==0) %>% select(title, animescore10) %>% arrange(-animescore10), n=20)
head(newdata2 %>% filter(Sequel==0) %>% select(title, adv) %>% arrange(-adv), n=20)
head(newdata2 %>% filter(Sequel==0) %>% select(title, adv) %>% arrange(adv), n=20)
head(newdata2 %>% filter(Sequel==1) %>% select(title, adv) %>% arrange(-adv), n=20)
head(newdata2 %>% filter(Sequel==1) %>% select(title, adv) %>% arrange(adv), n=20)
Some notes: - Traditionally “elitist” anime like Ashita no Joe,
Kaiba, and Legend of the Galactic Heroes gain the most from the
user-adjusted method, as the kind of people who are predisposed to
watching them are harsher raters. - Anime watched by men tend to gain
more from user-adjustment, and vice versa. - Legend of the Galactic
Heroes goes from top 4 to indisputably number 1 after adjusting for user
ratings.
This is the code snippet I used to calculate the user-adjusted
ratings. I had to run 10 iterations separately (no loops) of it due to
memory limitations.
```r
setwd('~')
setwd('Documents/rstuff/anime')
animeids <- read.csv('data/anime.csv', sep='\t')
test <- read.csv('data2/fullfilev1.csv', sep=',')
ranksv3 <- read.csv('data2/ranks_v3.csv', sep=',')
names <- animeids %>% select(anime_id, title)
########3naive means
m <- test %>% group_by(anime_id) %>% summarise(animescore=mean(score, na.rm=T), n=n())
m2 <- test %>% group_by(user_id) %>% summarise(userscore=mean(score, na.rm=T), n=n())
ranks <- left_join(m, names, by='anime_id') %>% arrange(-animescore)
uranks <- m2 %>% arrange(-userscore)
##############
test2 <- test
test2$ascore2 <- test2$score
test2$uscore2 <- test2$score
test2$score2 <- test2$score
########3
uranks <- test2 %>% group_by(user_id) %>% summarise(userscore=mean(uscore2, na.rm=T), un=n()) %>% arrange(-userscore)
aranks <- test2 %>% group_by(anime_id) %>% summarise(animescore=mean(ascore2, na.rm=T), an=n()) %>% arrange(-animescore)
write.csv(aranks, paste0('data2/aranks_v', 1, '.csv'))
write.csv(uranks, paste0('data2/uranks_v', 1, '.csv'))
test2 <- left_join(test2, uranks %>% select(user_id, userscore, un), by='user_id')
test2 <- left_join(test2, aranks %>% select(anime_id, animescore, an), by='anime_id')
test2$adiff <- test2$score2-test2$userscore
test2$udiff <- test2$score2-test2$animescore
test2$ascore2 <- test2$adiff+mean(aranks$animescore, na.rm=T)
test2$uscore2 <- test2$udiff+mean(uranks$userscore, na.rm=T)
aranks2 <- test2 %>% group_by(anime_id) %>% summarise(animescore=mean(ascore2, na.rm=T), an=n()) %>% arrange(-animescore)
uranks2 <- test2 %>% group_by(user_id) %>% summarise(userscore=mean(uscore2, na.rm=T), un=n()) %>% arrange(-userscore)
aranks2 <- left_join(names, aranks2 %>% select(anime_id, animescore, an), by='anime_id')
write.csv(aranks2, paste0('data2/aranks_v', 2, '.csv'))
write.csv(uranks2, paste0('data2/uranks_v', 2, '.csv'))
```
LS0tDQp0aXRsZTogIlJlY29uc2lkZXJpbmcgYW5pbWUgcmFua2luZ3MiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpNeWFuaW1lbGlzdCBjb2xsZWN0cyB0aGUgcmF0aW5ncyBvZiBzZXZlcmFsIG1pbGxpb24gdXNlcnMsIGFuZCB1c2VzIHRob3NlIHRvIG1ha2UNCmFuIG92ZXJhbGwgYW5pbWUgcmFuay4gVGhlIHByb2JsZW0gd2l0aCB0aGlzIG1ldGhvZCBpcyB0aGF0IHNvbWUgcGVvcGxlIHN5c3RlbWF0aWNhbGx5DQpnaXZlIGhpZ2hlciByYXRpbmdzIHRoYW4gb3RoZXJzLCBhbmQgdGhvc2UgcGVvcGxlIG1pZ2h0IGJlIG1vcmUgbGlrZWx5IHRvIHdhdGNoIGNlcnRhaW4NCnNob3dzLiBDb252ZXJzZWx5LCB0aGVyZSBpcyB0aGUgc2FtZSBpc3N1ZSBvbiB0aGUgdXNlciBzaWRlLCB3aGVyZSBzb21lIHBlb3BsZSBtaWdodA0KZGVsaWJlcmF0ZWx5IHNlYXJjaCBvdXQgc2hvd3Mgd2l0aCBoaWdoZXIgcmF0aW5ncyB0aGFuIG90aGVycy4NCg0KTm9ybWFsbHkgdGhpcyBpc3N1ZSB3b3VsZCBiZSBzb2x2ZWQgd2l0aCByZWdyZXNzaW9uLCB3aGljaCBjYW5ub3QgYmUgZG9uZSBoZXJlDQpiZWNhdXNlIHRoZXJlIGFyZSAxMCwwMDAgZGlmZmVyZW50IHRpdGxlcyBhbmQgMSwwMDAsMDAwIGRpZmZlcmVudCB1c2VycywgbWFraW5nDQppdCBpbXBvc3NpYmxlIHRvIHJ1biB0aGUgYWxnb3JpdGhtIGR1ZSB0byBtZW1vcnkgY29uc3RyYWludHMuIEluc3RlYWQsIEkgdXNlZCBhbiBpbnRlcmF0ZWQNCm1ldGhvZCB3aGVyZSBJIG9ic2VydmVkIHdoaWNoIHJhdGVycyBnYXZlIGhpZ2hlciByYXRpbmdzIHRoYW4gb3RoZXJzIGNvbnRyb2xsaW5nDQpmb3IgdGhlIGFuaW1lIHRoZXkgd2F0Y2hlZCwgYW5kIHdoYXQgYW5pbWUgcmVjZWl2ZWQgaGlnaGVyIHJhdGluZ3MgdGhhbiBvdGhlcnMNCmNvbnRyb2xsaW5nIGZvciB0aGUgdXNlcnMgd2hvIHJhdGVkIHRoZW0uIEJlY2F1c2UgdGhlIGNvbmZvdW5kaW5nIGdvZXMgYm90aCB3YXlzLA0Kc28gSSBoYWQgdG8gcmVwZWF0IHRoZSBtZXRob2QgdG8gZ2V0IHJvdWdobHkgcmVsaWFibGUgcmVzdWx0cy4NCg0KIyMgTG9hZGluZyBhbmQgY2xlYW5pbmcgZGF0YQ0KDQoNCmBgYHtyfQ0Kc2V0d2QoJ34nKQ0Kc2V0d2QoJ3Jmb2xkZXIvYW5pbWUnKQ0KYW5pbWVpZHMgPC0gcmVhZC5jc3YoJ2RhdGEvYW5pbWUuY3N2Jywgc2VwPSdcdCcpDQojb3JpZ2luYWwgYW5pbWUgcmFua3MNCm9ncmFua3MgPC0gcmVhZC5jc3YoJ2RhdGEyL2FyYW5rc192MS5jc3YnLCBzZXA9JywnKQ0KI2ZpcnN0IGl0ZXJhdGlvbg0KYXJhbmtzMiA8LSByZWFkLmNzdignZGF0YTIvYXJhbmtzX3YyLmNzdicsIHNlcD0nLCcpDQojZmlmdGggaXRlcmF0aW9uDQphcmFua3M1IDwtIHJlYWQuY3N2KCdkYXRhMi9hcmFua3NfdjYuY3N2Jywgc2VwPScsJykNCnNlcXVlbCA8LSByZWFkLmNzdignZGF0YTIvc2VxdWVscy5jc3YnLCBzZXA9JywnKSAlPiUgc2VsZWN0KGFuaW1lX2lkLCBTZXF1ZWwpDQojdGVudGggaXRlcmF0aW9uDQphcmFua3MxMCA8LSByZWFkLmNzdignZGF0YTIvYXJhbmtzX3YxMS5jc3YnLCBzZXA9JywnKQ0KYW5pbWVpZHMgPC0gcmVhZC5jc3YoJ2RhdGEvYW5pbWUuY3N2Jywgc2VwPSdcdCcpDQpgYGANCg0KYGBge3J9DQpuYW1lcyA8LSBhbmltZWlkcyAlPiUgc2VsZWN0KGFuaW1lX2lkLCB0aXRsZSkNCg0Kb3JpZ2luYWxfcmFua3MgPC0gbGVmdF9qb2luKG9ncmFua3MsIG5hbWVzLCBieT0nYW5pbWVfaWQnKSAlPiUgYXJyYW5nZSgtYW5pbWVzY29yZSkNCmZpcnN0X2l0ZXJhdGlvbiA8LSBsZWZ0X2pvaW4oYXJhbmtzMiwgbmFtZXMsIGJ5PSdhbmltZV9pZCcpICU+JSBhcnJhbmdlKC1hbmltZXNjb3JlKQ0KZmlmdGhfaXRlcmF0aW9uIDwtIGxlZnRfam9pbihhcmFua3M1LCBuYW1lcywgYnk9J2FuaW1lX2lkJykgJT4lIGFycmFuZ2UoLWFuaW1lc2NvcmUpDQp0ZW50aF9pdGVyYXRpb24gPC0gbGVmdF9qb2luKGFyYW5rczEwLCBuYW1lcywgYnk9J2FuaW1lX2lkJykgJT4lIGFycmFuZ2UoLWFuaW1lc2NvcmUpDQoNCm1lYW4ob3JpZ2luYWxfcmFua3MkYW5pbWVzY29yZSwgbmEucm09VCkNCnNkKG9yaWdpbmFsX3JhbmtzJGFuaW1lc2NvcmUsIG5hLnJtPVQpDQoNCmZpcnN0X2l0ZXJhdGlvbiRhbmltZXNjb3JlID0gbm9ybWFsaXNlKGZpcnN0X2l0ZXJhdGlvbiRhbmltZXNjb3JlKSoxLjA1MjIyNCs2LjI4NDcxMg0KZmlmdGhfaXRlcmF0aW9uJGFuaW1lc2NvcmUgPSBub3JtYWxpc2UoZmlmdGhfaXRlcmF0aW9uJGFuaW1lc2NvcmUpKjEuMDUyMjI0KzYuMjg0NzEyDQp0ZW50aF9pdGVyYXRpb24kYW5pbWVzY29yZSA9IG5vcm1hbGlzZSh0ZW50aF9pdGVyYXRpb24kYW5pbWVzY29yZSkqMS4wNTIyMjQrNi4yODQ3MTINCg0KZmlyc3RfaXRlcmF0aW9uJGFuaW1lc2NvcmUxID0gZmlyc3RfaXRlcmF0aW9uJGFuaW1lc2NvcmUNCmZpZnRoX2l0ZXJhdGlvbiRhbmltZXNjb3JlNSA9IGZpZnRoX2l0ZXJhdGlvbiRhbmltZXNjb3JlDQp0ZW50aF9pdGVyYXRpb24kYW5pbWVzY29yZTEwID0gdGVudGhfaXRlcmF0aW9uJGFuaW1lc2NvcmUNCg0KbmV3ZGF0YSA8LSBsZWZ0X2pvaW4ob3JpZ2luYWxfcmFua3MsIGZpcnN0X2l0ZXJhdGlvbiAlPiUgc2VsZWN0KGFuaW1lX2lkLCBhbmltZXNjb3JlMSksIGJ5PSdhbmltZV9pZCcpICU+JSBhcnJhbmdlKC1hbmltZXNjb3JlKQ0KbmV3ZGF0YSA8LSBsZWZ0X2pvaW4obmV3ZGF0YSwgZmlmdGhfaXRlcmF0aW9uICU+JSBzZWxlY3QoYW5pbWVfaWQsIGFuaW1lc2NvcmU1KSwgYnk9J2FuaW1lX2lkJykgJT4lIGFycmFuZ2UoLWFuaW1lc2NvcmUpDQpuZXdkYXRhIDwtIGxlZnRfam9pbihuZXdkYXRhLCB0ZW50aF9pdGVyYXRpb24gJT4lIHNlbGVjdChhbmltZV9pZCwgYW5pbWVzY29yZTEwKSwgYnk9J2FuaW1lX2lkJykgJT4lIGFycmFuZ2UoLWFuaW1lc2NvcmUpDQpuZXdkYXRhIDwtIGxlZnRfam9pbihuZXdkYXRhLCBzZXF1ZWwgJT4lIHNlbGVjdChhbmltZV9pZCwgU2VxdWVsKSwgYnk9J2FuaW1lX2lkJykgJT4lIGFycmFuZ2UoLWFuaW1lc2NvcmUpDQpuZXdkYXRhJGFkdiA8LSBuZXdkYXRhJGFuaW1lc2NvcmUxMCAtIG5ld2RhdGEkYW5pbWVzY29yZQ0KYGBgDQoNCkNvcnJlbGF0aW9uIG1hdHJpeC4gQ29ycmVsYXRpb24gYmV0d2VlbiBmaWZ0aCBpdGVyYXRpb24gcmF0aW5ncyBhbmQgdGVudGggaXRlcmF0aW9uIGlzDQphYm91dCAuOTk2Lg0KDQpgYGB7cn0NCmNvcnJlbGF0aW9uX21hdHJpeChuZXdkYXRhICU+JSBzZWxlY3QoYW5pbWVzY29yZSwgYW5pbWVzY29yZTEsIGFuaW1lc2NvcmU1LCBhbmltZXNjb3JlMTApKQ0KYGBgDQoNCiMjIFRoZSByYW5raW5ncw0KDQpLaWNraW5nIG91dCBhbmltZSB3aXRoIGxlc3MgdGhhbiA1ayByYXRpbmdzDQpgYGB7cn0NCm5ld2RhdGEkb3JpZ2luYWxyYW5rIDwtIHJhbmtlcihuZXdkYXRhJGFuaW1lc2NvcmUpDQpuZXdkYXRhJG5ld3JhbmsgPC0gcmFua2VyKG5ld2RhdGEkYW5pbWVzY29yZTEwKQ0KbmV3ZGF0YTIgPC0gbmV3ZGF0YSAlPiUgZmlsdGVyKGFuID4gNDk5OSkNCmBgYA0KDQpUb3AgMjAgZm9yIGVhY2ggY2F0ZWdvcnkuIGFuaW1lc2NvcmUgLS0gb3JpZ2luYWwgcmF0aW5nLCBhbmltZXNjb3JlMTAgLS0gdXNlciBhZGp1c3RlZCByYXRpbmcuIA0KYGBge3J9DQpvcmlnaW5hbCA8LSBuZXdkYXRhMiAlPiUgZmlsdGVyKFNlcXVlbD09MCkNCnNlcXVlbCA8LSBuZXdkYXRhMiAlPiUgZmlsdGVyKFNlcXVlbD09MSkNCg0KaGVhZChuZXdkYXRhMiAlPiUgZmlsdGVyKFNlcXVlbD09MSkgJT4lIHNlbGVjdCh0aXRsZSwgYW5pbWVzY29yZSkgJT4lIGFycmFuZ2UoLWFuaW1lc2NvcmUpLCBuPTIwKQ0KaGVhZChuZXdkYXRhMiAlPiUgZmlsdGVyKFNlcXVlbD09MSkgJT4lIHNlbGVjdCh0aXRsZSwgYW5pbWVzY29yZTEwKSAlPiUgYXJyYW5nZSgtYW5pbWVzY29yZTEwKSwgbj0yMCkNCg0KaGVhZChuZXdkYXRhMiAlPiUgZmlsdGVyKFNlcXVlbD09MCkgJT4lIHNlbGVjdCh0aXRsZSwgYW5pbWVzY29yZSkgJT4lIGFycmFuZ2UoLWFuaW1lc2NvcmUpLCBuPTIwKQ0KaGVhZChuZXdkYXRhMiAlPiUgZmlsdGVyKFNlcXVlbD09MCkgJT4lIHNlbGVjdCh0aXRsZSwgYW5pbWVzY29yZTEwKSAlPiUgYXJyYW5nZSgtYW5pbWVzY29yZTEwKSwgbj0yMCkNCg0KaGVhZChuZXdkYXRhMiAlPiUgZmlsdGVyKFNlcXVlbD09MCkgJT4lIHNlbGVjdCh0aXRsZSwgYWR2KSAlPiUgYXJyYW5nZSgtYWR2KSwgbj0yMCkNCmhlYWQobmV3ZGF0YTIgJT4lIGZpbHRlcihTZXF1ZWw9PTApICU+JSBzZWxlY3QodGl0bGUsIGFkdikgJT4lIGFycmFuZ2UoYWR2KSwgbj0yMCkNCg0KaGVhZChuZXdkYXRhMiAlPiUgZmlsdGVyKFNlcXVlbD09MSkgJT4lIHNlbGVjdCh0aXRsZSwgYWR2KSAlPiUgYXJyYW5nZSgtYWR2KSwgbj0yMCkNCmhlYWQobmV3ZGF0YTIgJT4lIGZpbHRlcihTZXF1ZWw9PTEpICU+JSBzZWxlY3QodGl0bGUsIGFkdikgJT4lIGFycmFuZ2UoYWR2KSwgbj0yMCkNCmBgYA0KDQpTb21lIG5vdGVzOg0KLSBUcmFkaXRpb25hbGx5ICJlbGl0aXN0IiBhbmltZSBsaWtlIEFzaGl0YSBubyBKb2UsIEthaWJhLCBhbmQgTGVnZW5kIG9mIHRoZSBHYWxhY3RpYw0KSGVyb2VzIGdhaW4gdGhlIG1vc3QgZnJvbSB0aGUgdXNlci1hZGp1c3RlZCBtZXRob2QsIGFzIHRoZSBraW5kIG9mIHBlb3BsZSB3aG8NCmFyZSBwcmVkaXNwb3NlZCB0byB3YXRjaGluZyB0aGVtIGFyZSBoYXJzaGVyIHJhdGVycy4NCi0gQW5pbWUgd2F0Y2hlZCBieSBtZW4gdGVuZCB0byBnYWluIG1vcmUgZnJvbSB1c2VyLWFkanVzdG1lbnQsIGFuZCB2aWNlIHZlcnNhLg0KLSBMZWdlbmQgb2YgdGhlIEdhbGFjdGljIEhlcm9lcyBnb2VzIGZyb20gdG9wIDQgdG8gaW5kaXNwdXRhYmx5IG51bWJlciAxIGFmdGVyDQphZGp1c3RpbmcgZm9yIHVzZXIgcmF0aW5ncy4NCg0KDQpUaGlzIGlzIHRoZSBjb2RlIHNuaXBwZXQgSSB1c2VkIHRvIGNhbGN1bGF0ZSB0aGUgdXNlci1hZGp1c3RlZCByYXRpbmdzLg0KSSBoYWQgdG8gcnVuIDEwIGl0ZXJhdGlvbnMgc2VwYXJhdGVseSAobm8gbG9vcHMpIG9mIGl0IGR1ZSB0byBtZW1vcnkgbGltaXRhdGlvbnMuDQoNCmBgYHtyfQ0Kc2V0d2QoJ34nKQ0Kc2V0d2QoJ0RvY3VtZW50cy9yc3R1ZmYvYW5pbWUnKQ0KYW5pbWVpZHMgPC0gcmVhZC5jc3YoJ2RhdGEvYW5pbWUuY3N2Jywgc2VwPSdcdCcpDQp0ZXN0IDwtIHJlYWQuY3N2KCdkYXRhMi9mdWxsZmlsZXYxLmNzdicsIHNlcD0nLCcpDQpyYW5rc3YzIDwtIHJlYWQuY3N2KCdkYXRhMi9yYW5rc192My5jc3YnLCBzZXA9JywnKQ0KDQpuYW1lcyA8LSBhbmltZWlkcyAlPiUgc2VsZWN0KGFuaW1lX2lkLCB0aXRsZSkNCg0KIyMjIyMjIyMzbmFpdmUgbWVhbnMNCm0gPC0gdGVzdCAlPiUgZ3JvdXBfYnkoYW5pbWVfaWQpICU+JSBzdW1tYXJpc2UoYW5pbWVzY29yZT1tZWFuKHNjb3JlLCBuYS5ybT1UKSwgbj1uKCkpDQptMiA8LSB0ZXN0ICU+JSBncm91cF9ieSh1c2VyX2lkKSAlPiUgc3VtbWFyaXNlKHVzZXJzY29yZT1tZWFuKHNjb3JlLCBuYS5ybT1UKSwgbj1uKCkpDQoNCnJhbmtzIDwtIGxlZnRfam9pbihtLCBuYW1lcywgYnk9J2FuaW1lX2lkJykgJT4lIGFycmFuZ2UoLWFuaW1lc2NvcmUpDQp1cmFua3MgPC0gbTIgJT4lIGFycmFuZ2UoLXVzZXJzY29yZSkNCg0KIyMjIyMjIyMjIyMjIyMNCnRlc3QyIDwtIHRlc3QNCnRlc3QyJGFzY29yZTIgPC0gdGVzdDIkc2NvcmUNCnRlc3QyJHVzY29yZTIgPC0gdGVzdDIkc2NvcmUNCnRlc3QyJHNjb3JlMiA8LSB0ZXN0MiRzY29yZQ0KDQojIyMjIyMjIzMNCnVyYW5rcyA8LSB0ZXN0MiAlPiUgZ3JvdXBfYnkodXNlcl9pZCkgJT4lIHN1bW1hcmlzZSh1c2Vyc2NvcmU9bWVhbih1c2NvcmUyLCBuYS5ybT1UKSwgdW49bigpKSAlPiUgYXJyYW5nZSgtdXNlcnNjb3JlKQ0KYXJhbmtzIDwtIHRlc3QyICU+JSBncm91cF9ieShhbmltZV9pZCkgJT4lIHN1bW1hcmlzZShhbmltZXNjb3JlPW1lYW4oYXNjb3JlMiwgbmEucm09VCksIGFuPW4oKSkgJT4lIGFycmFuZ2UoLWFuaW1lc2NvcmUpDQp3cml0ZS5jc3YoYXJhbmtzLCBwYXN0ZTAoJ2RhdGEyL2FyYW5rc192JywgMSwgJy5jc3YnKSkNCndyaXRlLmNzdih1cmFua3MsIHBhc3RlMCgnZGF0YTIvdXJhbmtzX3YnLCAxLCAnLmNzdicpKQ0KDQp0ZXN0MiA8LSBsZWZ0X2pvaW4odGVzdDIsIHVyYW5rcyAlPiUgc2VsZWN0KHVzZXJfaWQsIHVzZXJzY29yZSwgdW4pLCBieT0ndXNlcl9pZCcpDQp0ZXN0MiA8LSBsZWZ0X2pvaW4odGVzdDIsIGFyYW5rcyAlPiUgc2VsZWN0KGFuaW1lX2lkLCBhbmltZXNjb3JlLCBhbiksIGJ5PSdhbmltZV9pZCcpDQp0ZXN0MiRhZGlmZiA8LSB0ZXN0MiRzY29yZTItdGVzdDIkdXNlcnNjb3JlDQp0ZXN0MiR1ZGlmZiA8LSB0ZXN0MiRzY29yZTItdGVzdDIkYW5pbWVzY29yZQ0KDQp0ZXN0MiRhc2NvcmUyIDwtIHRlc3QyJGFkaWZmK21lYW4oYXJhbmtzJGFuaW1lc2NvcmUsIG5hLnJtPVQpDQp0ZXN0MiR1c2NvcmUyIDwtIHRlc3QyJHVkaWZmK21lYW4odXJhbmtzJHVzZXJzY29yZSwgbmEucm09VCkNCg0KYXJhbmtzMiA8LSB0ZXN0MiAlPiUgZ3JvdXBfYnkoYW5pbWVfaWQpICU+JSBzdW1tYXJpc2UoYW5pbWVzY29yZT1tZWFuKGFzY29yZTIsIG5hLnJtPVQpLCBhbj1uKCkpICU+JSBhcnJhbmdlKC1hbmltZXNjb3JlKQ0KdXJhbmtzMiA8LSB0ZXN0MiAlPiUgZ3JvdXBfYnkodXNlcl9pZCkgJT4lIHN1bW1hcmlzZSh1c2Vyc2NvcmU9bWVhbih1c2NvcmUyLCBuYS5ybT1UKSwgdW49bigpKSAlPiUgYXJyYW5nZSgtdXNlcnNjb3JlKQ0KYXJhbmtzMiA8LSBsZWZ0X2pvaW4obmFtZXMsIGFyYW5rczIgJT4lIHNlbGVjdChhbmltZV9pZCwgYW5pbWVzY29yZSwgYW4pLCBieT0nYW5pbWVfaWQnKQ0KDQp3cml0ZS5jc3YoYXJhbmtzMiwgcGFzdGUwKCdkYXRhMi9hcmFua3NfdicsIDIsICcuY3N2JykpDQp3cml0ZS5jc3YodXJhbmtzMiwgcGFzdGUwKCdkYXRhMi91cmFua3NfdicsIDIsICcuY3N2JykpDQpgYGANCg0KDQoNCg==