Stack Overflow questions

We will analyze Stack Overflow questions, answers, and tags dataset.

This will include calculating and visualizing trends for some notable tags like dplyr and ggplot2.

library(readr)
library(tidyr)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
library(dplyr)

Attaching package: 㤼㸱dplyr㤼㸲

The following objects are masked from 㤼㸱package:stats㤼㸲:

    filter, lag

The following objects are masked from 㤼㸱package:base㤼㸲:

    intersect, setdiff, setequal, union
questions <- read_rds(url("https://assets.datacamp.com/production/repositories/5284/datasets/89d5a716b4f41dbe4fcda1a7a1190f24f58f0e47/questions.rds"))
tags <- read_rds(url("https://assets.datacamp.com/production/repositories/5284/datasets/207c31b235786e73496fd7e58e416779911a9d98/tags.rds"))
question_tags <- read_rds(url("https://assets.datacamp.com/production/repositories/5284/datasets/966938d665c69bffd87393b345ea2837a94bab97/question_tags.rds"))
answers <- read_rds(url("https://assets.datacamp.com/production/repositories/5284/datasets/6cb9c039aa8326d98de37afefa32e1c458764638/answers.rds"))
head(questions)
head(tags)
head(question_tags)
head(answers)

Left-joining questions and tags

Three of the Stack Overflow survey datasets are questions, question_tags, and tags:

questions: an ID and the score, or how many times the question has been upvoted; the data only includes R-based questions question_tags: a tag ID for each question and the question’s id tags: a tag id and the tag’s name, which can be used to identify the subject of each question, such as ggplot2 or dplyr In this exercise, we’ll be stitching together these datasets and replacing NAs in important fields.

Note that we’ll be using left_joins in this exercise to ensure we keep all questions, even those without a corresponding tag. However, since we know the questions data is all R data, we’ll want to manually tag these as R questions with replace_na.

# Join the questions and question_tags tables
questions_with_tags <- questions %>%
  left_join(question_tags, by = c("id" = "question_id")) %>%
  # Join the tags as well
  left_join(tags, by = c("tag_id" = "id")) %>%
  # Replace the NAs in the tag_name column
  replace_na(list(tag_name = "only-r"))

head(questions_with_tags)

We now have a dataset that we can analyze after all that joining.

Comparing scores across tags

Let’s do a quick bit of analysis on it! We’ll use familiar dplyr verbs like group_by, summarize, arrange, and n to find out the average score of the most asked questions.

questions_with_tags %>%
    # Group by tag_name
  group_by(tag_name) %>%
    # Get mean score and num_questions
    summarize(score = mean(score),
              num_questions = n()) %>%
    # Sort num_questions in descending order
    arrange(desc(num_questions))

It looks like questions with the R tag get a relatively low score, but questions with the loops tag are even lower.

What tags never appear on R questions?

The tags table includes all Stack Overflow tags, but some have nothing to do with R. How could you filter for just the tags that never appear on an R question?

# Using a join, filter for tags that are never on an R question
tags %>%
  anti_join(question_tags, c("id" = "tag_id")) %>%
  filter(tag_name != "only-r")

It looks like there are more than 40,000 tags that have never appeared along R!

Joining questions and answers

Finding gaps between questions and answers

Now we’ll join together questions with answers so we can measure the time between questions and answers.

questions %>%
    # Inner join questions and answers with proper suffixes
    inner_join(answers, c("id" = "question_id"), suffix = c("_question", "_answer")) %>%
    # Subtract creation_date_question from creation_date_answer to create gap
    mutate(gap = as.integer(creation_date_answer - creation_date_question))

Now we could use this information to identify how long it takes different questions to get answers.

Joining question and answer counts

We can also determine how many questions actually yield answers. If we count the number of answers for each question, we can then join the answers counts with the questions table.

# Count and sort the question id column in the answers table
answer_counts <- answers %>%
  count(question_id, sort = TRUE)

# Combine the answer_counts and questions tables
question_answer_counts <- questions %>%
    left_join(answer_counts, by = c("id" = "question_id"))%>%
    # Replace the NAs in the n column
  replace_na(list(n = 0))

head(question_answer_counts)

We can use this combined table to see which questions have the most answers, and which questions have no answers.

Joining questions, answers, and tags

Let’s build on the last exercise by adding the tags table to our previous joins. This will allow us to do a better job of identifying which R topics get the most traction on Stack Overflow.

tagged_answers <- question_answer_counts %>%
    # Join the question_tags tables
    inner_join(question_tags, by = c("id" = "question_id")) %>%
    # Join the tags table
  inner_join(tags, by = c("tag_id" = "id"))
tagged_answers

Now we have a more holistic view of how questions are answered by each tag.

Average answers by question

We can use tagged_answers table to determine, on average, how many answers each questions gets.

Some of the important variables from this table include: n, the number of answers for each question, and tag_name, the name of each tag associated with each question.

tagged_answers %>%
    # Aggregate by tag_name
  group_by(tag_name) %>% 
    # Summarize questions and average_answers
    summarize(questions = n(),
              average_answers = mean(n)) %>%
    # Sort the questions in descending order
    arrange(desc(questions))

We can see if you post a question about ggplot2, on average you’ll get an answer.

The bind rows verb

Joining questions and answers with tags

To learn more about the questions and answers table, we’ll want to use the question_tags table to understand the tags associated with each question that was asked, and each answer that was provided. We’ll be able to combine these tables using two inner joins on both the questions table and the answers table.

# Inner join the question_tags and tags tables with the questions table
questions_with_tags <- questions %>%
  inner_join(question_tags, by = c("id" = "question_id")) %>%
  inner_join(tags, by = c("tag_id" = "id"))
questions_with_tags
# Inner join the question_tags and tags tables with the answers table
answers_with_tags <- answers %>%
  inner_join(question_tags, by = c("question_id" = "question_id")) %>%
  inner_join(tags, by = c("tag_id" = "id"))
answers_with_tags

Now we will be able to combine each of these individual tables into a single cohesive table to have a better understanding of the information we have about the questions, answers, and associated tags.

Binding and counting posts with tags

First, we’ll want to combine these tables into a single table called posts_with_tags. Once the information is consolidated into a single table, we can add more information by creating a date variable using the lubridate package.

library(lubridate)

Attaching package: 㤼㸱lubridate㤼㸲

The following object is masked from 㤼㸱package:base㤼㸲:

    date
# Combine the two tables into posts_with_tags
posts_with_tags <- bind_rows(questions_with_tags %>% mutate(type = "question"),
                              answers_with_tags %>% mutate(type = "answer"))


# Add a year column, then aggregate by type, year, and tag_name
by_type_year_tag <- posts_with_tags %>%
  mutate(year = year(creation_date)) %>%
  group_by(type, year, tag_name) %>%
  count()
by_type_year_tag

Visualizing questions and answers in tags

Let’s create a plot to examine the information that the table contains about questions and answers for the dplyr and ggplot2 tags.

library(ggplot2)
package 㤼㸱ggplot2㤼㸲 was built under R version 3.6.3
# Filter for the dplyr and ggplot2 tag names 
by_type_year_tag_filtered <- by_type_year_tag %>%
  filter(tag_name == "dplyr" | tag_name == "ggplot2")

# Create a line plot faceted by the tag name 
ggplot(by_type_year_tag_filtered, aes(x = year, y = n, color = type)) +
  geom_line() +
  facet_wrap(~ tag_name)

Notice answers on dplyr questions are growing faster than dplyr questions themselves; meaning the average dplyr question has more answers than the average ggplot2 question.

LS0tDQp0aXRsZTogIkpvaW5zIG9uIFN0YWNrIE92ZXJmbG93IERhdGEiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgdG9jX2NvbGxhcHNlZDogdHJ1ZQ0KICAgIA0KdG9jX2RlcHRoOiAzDQotLS0NCiMgU3RhY2sgT3ZlcmZsb3cgcXVlc3Rpb25zDQoNCldlIHdpbGwgYW5hbHl6ZSBTdGFjayBPdmVyZmxvdyBxdWVzdGlvbnMsIGFuc3dlcnMsIGFuZCB0YWdzIGRhdGFzZXQuDQoNClRoaXMgd2lsbCBpbmNsdWRlIGNhbGN1bGF0aW5nIGFuZCB2aXN1YWxpemluZyB0cmVuZHMgZm9yIHNvbWUgbm90YWJsZSB0YWdzIGxpa2UgZHBseXIgYW5kIGdncGxvdDIuDQpgYGB7cn0NCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShkcGx5cikNCmBgYA0KDQpgYGB7cn0NCnF1ZXN0aW9ucyA8LSByZWFkX3Jkcyh1cmwoImh0dHBzOi8vYXNzZXRzLmRhdGFjYW1wLmNvbS9wcm9kdWN0aW9uL3JlcG9zaXRvcmllcy81Mjg0L2RhdGFzZXRzLzg5ZDVhNzE2YjRmNDFkYmU0ZmNkYTFhN2ExMTkwZjI0ZjU4ZjBlNDcvcXVlc3Rpb25zLnJkcyIpKQ0KdGFncyA8LSByZWFkX3Jkcyh1cmwoImh0dHBzOi8vYXNzZXRzLmRhdGFjYW1wLmNvbS9wcm9kdWN0aW9uL3JlcG9zaXRvcmllcy81Mjg0L2RhdGFzZXRzLzIwN2MzMWIyMzU3ODZlNzM0OTZmZDdlNThlNDE2Nzc5OTExYTlkOTgvdGFncy5yZHMiKSkNCnF1ZXN0aW9uX3RhZ3MgPC0gcmVhZF9yZHModXJsKCJodHRwczovL2Fzc2V0cy5kYXRhY2FtcC5jb20vcHJvZHVjdGlvbi9yZXBvc2l0b3JpZXMvNTI4NC9kYXRhc2V0cy85NjY5MzhkNjY1YzY5YmZmZDg3MzkzYjM0NWVhMjgzN2E5NGJhYjk3L3F1ZXN0aW9uX3RhZ3MucmRzIikpDQphbnN3ZXJzIDwtIHJlYWRfcmRzKHVybCgiaHR0cHM6Ly9hc3NldHMuZGF0YWNhbXAuY29tL3Byb2R1Y3Rpb24vcmVwb3NpdG9yaWVzLzUyODQvZGF0YXNldHMvNmNiOWMwMzlhYTgzMjZkOThkZTM3YWZlZmEzMmUxYzQ1ODc2NDYzOC9hbnN3ZXJzLnJkcyIpKQ0KYGBgDQoNCg0KDQoNCg0KYGBge3J9DQpoZWFkKHF1ZXN0aW9ucykNCmhlYWQodGFncykNCmhlYWQocXVlc3Rpb25fdGFncykNCmhlYWQoYW5zd2VycykNCmBgYA0KIyMgTGVmdC1qb2luaW5nIHF1ZXN0aW9ucyBhbmQgdGFncw0KDQpUaHJlZSBvZiB0aGUgU3RhY2sgT3ZlcmZsb3cgc3VydmV5IGRhdGFzZXRzIGFyZSBxdWVzdGlvbnMsIHF1ZXN0aW9uX3RhZ3MsIGFuZCB0YWdzOg0KDQpxdWVzdGlvbnM6IGFuIElEIGFuZCB0aGUgc2NvcmUsIG9yIGhvdyBtYW55IHRpbWVzIHRoZSBxdWVzdGlvbiBoYXMgYmVlbiB1cHZvdGVkOyB0aGUgZGF0YSBvbmx5IGluY2x1ZGVzIFItYmFzZWQgcXVlc3Rpb25zDQpxdWVzdGlvbl90YWdzOiBhIHRhZyBJRCBmb3IgZWFjaCBxdWVzdGlvbiBhbmQgdGhlIHF1ZXN0aW9uJ3MgaWQNCnRhZ3M6IGEgdGFnIGlkIGFuZCB0aGUgdGFnJ3MgbmFtZSwgd2hpY2ggY2FuIGJlIHVzZWQgdG8gaWRlbnRpZnkgdGhlIHN1YmplY3Qgb2YgZWFjaCBxdWVzdGlvbiwgc3VjaCBhcyBnZ3Bsb3QyIG9yIGRwbHlyDQpJbiB0aGlzIGV4ZXJjaXNlLCB3ZSdsbCBiZSBzdGl0Y2hpbmcgdG9nZXRoZXIgdGhlc2UgZGF0YXNldHMgYW5kIHJlcGxhY2luZyBOQXMgaW4gaW1wb3J0YW50IGZpZWxkcy4NCg0KTm90ZSB0aGF0IHdlJ2xsIGJlIHVzaW5nIGxlZnRfam9pbnMgaW4gdGhpcyBleGVyY2lzZSB0byBlbnN1cmUgd2Uga2VlcCBhbGwgcXVlc3Rpb25zLCBldmVuIHRob3NlIHdpdGhvdXQgYSBjb3JyZXNwb25kaW5nIHRhZy4gSG93ZXZlciwgc2luY2Ugd2Uga25vdyB0aGUgcXVlc3Rpb25zIGRhdGEgaXMgYWxsIFIgZGF0YSwgd2UnbGwgd2FudCB0byBtYW51YWxseSB0YWcgdGhlc2UgYXMgUiBxdWVzdGlvbnMgd2l0aCByZXBsYWNlX25hLg0KYGBge3J9DQojIEpvaW4gdGhlIHF1ZXN0aW9ucyBhbmQgcXVlc3Rpb25fdGFncyB0YWJsZXMNCnF1ZXN0aW9uc193aXRoX3RhZ3MgPC0gcXVlc3Rpb25zICU+JQ0KICBsZWZ0X2pvaW4ocXVlc3Rpb25fdGFncywgYnkgPSBjKCJpZCIgPSAicXVlc3Rpb25faWQiKSkgJT4lDQogICMgSm9pbiB0aGUgdGFncyBhcyB3ZWxsDQogIGxlZnRfam9pbih0YWdzLCBieSA9IGMoInRhZ19pZCIgPSAiaWQiKSkgJT4lDQogICMgUmVwbGFjZSB0aGUgTkFzIGluIHRoZSB0YWdfbmFtZSBjb2x1bW4NCiAgcmVwbGFjZV9uYShsaXN0KHRhZ19uYW1lID0gIm9ubHktciIpKQ0KDQpoZWFkKHF1ZXN0aW9uc193aXRoX3RhZ3MpDQpgYGANCldlIG5vdyBoYXZlIGEgZGF0YXNldCB0aGF0IHdlIGNhbiBhbmFseXplIGFmdGVyIGFsbCB0aGF0IGpvaW5pbmcuDQoNCiMjIENvbXBhcmluZyBzY29yZXMgYWNyb3NzIHRhZ3MNCg0KTGV0J3MgZG8gYSBxdWljayBiaXQgb2YgYW5hbHlzaXMgb24gaXQhIFdlJ2xsIHVzZSBmYW1pbGlhciBkcGx5ciB2ZXJicyBsaWtlIGdyb3VwX2J5LCBzdW1tYXJpemUsIGFycmFuZ2UsIGFuZCBuIHRvIGZpbmQgb3V0IHRoZSBhdmVyYWdlIHNjb3JlIG9mIHRoZSBtb3N0IGFza2VkIHF1ZXN0aW9ucy4NCmBgYHtyfQ0KcXVlc3Rpb25zX3dpdGhfdGFncyAlPiUNCgkjIEdyb3VwIGJ5IHRhZ19uYW1lDQogIGdyb3VwX2J5KHRhZ19uYW1lKSAlPiUNCgkjIEdldCBtZWFuIHNjb3JlIGFuZCBudW1fcXVlc3Rpb25zDQoJc3VtbWFyaXplKHNjb3JlID0gbWVhbihzY29yZSksDQogICAgICAgICAgCSAgbnVtX3F1ZXN0aW9ucyA9IG4oKSkgJT4lDQoJIyBTb3J0IG51bV9xdWVzdGlvbnMgaW4gZGVzY2VuZGluZyBvcmRlcg0KCWFycmFuZ2UoZGVzYyhudW1fcXVlc3Rpb25zKSkNCmBgYA0KSXQgbG9va3MgbGlrZSBxdWVzdGlvbnMgd2l0aCB0aGUgUiB0YWcgZ2V0IGEgcmVsYXRpdmVseSBsb3cgc2NvcmUsIGJ1dCBxdWVzdGlvbnMgd2l0aCB0aGUgbG9vcHMgdGFnIGFyZSBldmVuIGxvd2VyLg0KDQojIyMgV2hhdCB0YWdzIG5ldmVyIGFwcGVhciBvbiBSIHF1ZXN0aW9ucz8NCg0KVGhlIHRhZ3MgdGFibGUgaW5jbHVkZXMgYWxsIFN0YWNrIE92ZXJmbG93IHRhZ3MsIGJ1dCBzb21lIGhhdmUgbm90aGluZyB0byBkbyB3aXRoIFIuIEhvdyBjb3VsZCB5b3UgZmlsdGVyIGZvciBqdXN0IHRoZSB0YWdzIHRoYXQgbmV2ZXIgYXBwZWFyIG9uIGFuIFIgcXVlc3Rpb24/DQpgYGB7cn0NCiMgVXNpbmcgYSBqb2luLCBmaWx0ZXIgZm9yIHRhZ3MgdGhhdCBhcmUgbmV2ZXIgb24gYW4gUiBxdWVzdGlvbg0KdGFncyAlPiUNCiAgYW50aV9qb2luKHF1ZXN0aW9uX3RhZ3MsIGMoImlkIiA9ICJ0YWdfaWQiKSkgJT4lDQogIGZpbHRlcih0YWdfbmFtZSAhPSAib25seS1yIikNCmBgYA0KSXQgbG9va3MgbGlrZSB0aGVyZSBhcmUgbW9yZSB0aGFuIDQwLDAwMCB0YWdzIHRoYXQgaGF2ZSBuZXZlciBhcHBlYXJlZCBhbG9uZyBSIQ0KDQojIEpvaW5pbmcgcXVlc3Rpb25zIGFuZCBhbnN3ZXJzDQoNCiMjIEZpbmRpbmcgZ2FwcyBiZXR3ZWVuIHF1ZXN0aW9ucyBhbmQgYW5zd2Vycw0KDQpOb3cgd2UnbGwgam9pbiB0b2dldGhlciBxdWVzdGlvbnMgd2l0aCBhbnN3ZXJzIHNvIHdlIGNhbiBtZWFzdXJlIHRoZSB0aW1lIGJldHdlZW4gcXVlc3Rpb25zIGFuZCBhbnN3ZXJzLg0KYGBge3J9DQpxdWVzdGlvbnMgJT4lDQoJIyBJbm5lciBqb2luIHF1ZXN0aW9ucyBhbmQgYW5zd2VycyB3aXRoIHByb3BlciBzdWZmaXhlcw0KCWlubmVyX2pvaW4oYW5zd2VycywgYygiaWQiID0gInF1ZXN0aW9uX2lkIiksIHN1ZmZpeCA9IGMoIl9xdWVzdGlvbiIsICJfYW5zd2VyIikpICU+JQ0KCSMgU3VidHJhY3QgY3JlYXRpb25fZGF0ZV9xdWVzdGlvbiBmcm9tIGNyZWF0aW9uX2RhdGVfYW5zd2VyIHRvIGNyZWF0ZSBnYXANCgltdXRhdGUoZ2FwID0gYXMuaW50ZWdlcihjcmVhdGlvbl9kYXRlX2Fuc3dlciAtIGNyZWF0aW9uX2RhdGVfcXVlc3Rpb24pKQ0KYGBgDQpOb3cgd2UgY291bGQgdXNlIHRoaXMgaW5mb3JtYXRpb24gdG8gaWRlbnRpZnkgaG93IGxvbmcgaXQgdGFrZXMgZGlmZmVyZW50IHF1ZXN0aW9ucyB0byBnZXQgYW5zd2Vycy4NCg0KIyMgSm9pbmluZyBxdWVzdGlvbiBhbmQgYW5zd2VyIGNvdW50cw0KDQpXZSBjYW4gYWxzbyBkZXRlcm1pbmUgaG93IG1hbnkgcXVlc3Rpb25zIGFjdHVhbGx5IHlpZWxkIGFuc3dlcnMuIElmIHdlIGNvdW50IHRoZSBudW1iZXIgb2YgYW5zd2VycyBmb3IgZWFjaCBxdWVzdGlvbiwgd2UgY2FuIHRoZW4gam9pbiB0aGUgYW5zd2VycyBjb3VudHMgd2l0aCB0aGUgcXVlc3Rpb25zIHRhYmxlLg0KYGBge3J9DQojIENvdW50IGFuZCBzb3J0IHRoZSBxdWVzdGlvbiBpZCBjb2x1bW4gaW4gdGhlIGFuc3dlcnMgdGFibGUNCmFuc3dlcl9jb3VudHMgPC0gYW5zd2VycyAlPiUNCiAgY291bnQocXVlc3Rpb25faWQsIHNvcnQgPSBUUlVFKQ0KDQojIENvbWJpbmUgdGhlIGFuc3dlcl9jb3VudHMgYW5kIHF1ZXN0aW9ucyB0YWJsZXMNCnF1ZXN0aW9uX2Fuc3dlcl9jb3VudHMgPC0gcXVlc3Rpb25zICU+JQ0KCWxlZnRfam9pbihhbnN3ZXJfY291bnRzLCBieSA9IGMoImlkIiA9ICJxdWVzdGlvbl9pZCIpKSU+JQ0KCSMgUmVwbGFjZSB0aGUgTkFzIGluIHRoZSBuIGNvbHVtbg0KICByZXBsYWNlX25hKGxpc3QobiA9IDApKQ0KDQpoZWFkKHF1ZXN0aW9uX2Fuc3dlcl9jb3VudHMpDQpgYGANCldlIGNhbiB1c2UgdGhpcyBjb21iaW5lZCB0YWJsZSB0byBzZWUgd2hpY2ggcXVlc3Rpb25zIGhhdmUgdGhlIG1vc3QgYW5zd2VycywgYW5kIHdoaWNoIHF1ZXN0aW9ucyBoYXZlIG5vIGFuc3dlcnMuDQoNCiMjIEpvaW5pbmcgcXVlc3Rpb25zLCBhbnN3ZXJzLCBhbmQgdGFncw0KDQpMZXQncyBidWlsZCBvbiB0aGUgbGFzdCBleGVyY2lzZSBieSBhZGRpbmcgdGhlIHRhZ3MgdGFibGUgdG8gb3VyIHByZXZpb3VzIGpvaW5zLiBUaGlzIHdpbGwgYWxsb3cgdXMgdG8gZG8gYSBiZXR0ZXIgam9iIG9mIGlkZW50aWZ5aW5nIHdoaWNoIFIgdG9waWNzIGdldCB0aGUgbW9zdCB0cmFjdGlvbiBvbiBTdGFjayBPdmVyZmxvdy4NCmBgYHtyfQ0KdGFnZ2VkX2Fuc3dlcnMgPC0gcXVlc3Rpb25fYW5zd2VyX2NvdW50cyAlPiUNCgkjIEpvaW4gdGhlIHF1ZXN0aW9uX3RhZ3MgdGFibGVzDQoJaW5uZXJfam9pbihxdWVzdGlvbl90YWdzLCBieSA9IGMoImlkIiA9ICJxdWVzdGlvbl9pZCIpKSAlPiUNCgkjIEpvaW4gdGhlIHRhZ3MgdGFibGUNCiAgaW5uZXJfam9pbih0YWdzLCBieSA9IGMoInRhZ19pZCIgPSAiaWQiKSkNCnRhZ2dlZF9hbnN3ZXJzDQpgYGANCk5vdyB3ZSBoYXZlIGEgbW9yZSBob2xpc3RpYyB2aWV3IG9mIGhvdyBxdWVzdGlvbnMgYXJlIGFuc3dlcmVkIGJ5IGVhY2ggdGFnLg0KDQojIyBBdmVyYWdlIGFuc3dlcnMgYnkgcXVlc3Rpb24NCg0KV2UgY2FuIHVzZSB0YWdnZWRfYW5zd2VycyB0YWJsZSB0byBkZXRlcm1pbmUsIG9uIGF2ZXJhZ2UsIGhvdyBtYW55IGFuc3dlcnMgZWFjaCBxdWVzdGlvbnMgZ2V0cy4NCg0KU29tZSBvZiB0aGUgaW1wb3J0YW50IHZhcmlhYmxlcyBmcm9tIHRoaXMgdGFibGUgaW5jbHVkZTogbiwgdGhlIG51bWJlciBvZiBhbnN3ZXJzIGZvciBlYWNoIHF1ZXN0aW9uLCBhbmQgdGFnX25hbWUsIHRoZSBuYW1lIG9mIGVhY2ggdGFnIGFzc29jaWF0ZWQgd2l0aCBlYWNoIHF1ZXN0aW9uLg0KYGBge3J9DQp0YWdnZWRfYW5zd2VycyAlPiUNCgkjIEFnZ3JlZ2F0ZSBieSB0YWdfbmFtZQ0KICBncm91cF9ieSh0YWdfbmFtZSkgJT4lIA0KCSMgU3VtbWFyaXplIHF1ZXN0aW9ucyBhbmQgYXZlcmFnZV9hbnN3ZXJzDQogICAgc3VtbWFyaXplKHF1ZXN0aW9ucyA9IG4oKSwNCiAgICAgICAgICAgICAgYXZlcmFnZV9hbnN3ZXJzID0gbWVhbihuKSkgJT4lDQoJIyBTb3J0IHRoZSBxdWVzdGlvbnMgaW4gZGVzY2VuZGluZyBvcmRlcg0KCWFycmFuZ2UoZGVzYyhxdWVzdGlvbnMpKQ0KYGBgDQpXZSBjYW4gc2VlIGlmIHlvdSBwb3N0IGEgcXVlc3Rpb24gYWJvdXQgZ2dwbG90Miwgb24gYXZlcmFnZSB5b3UnbGwgZ2V0IGFuIGFuc3dlci4NCg0KIyBUaGUgYmluZCByb3dzIHZlcmINCg0KIyMgSm9pbmluZyBxdWVzdGlvbnMgYW5kIGFuc3dlcnMgd2l0aCB0YWdzDQoNClRvIGxlYXJuIG1vcmUgYWJvdXQgdGhlIHF1ZXN0aW9ucyBhbmQgYW5zd2VycyB0YWJsZSwgd2UnbGwgd2FudCB0byB1c2UgdGhlIHF1ZXN0aW9uX3RhZ3MgdGFibGUgdG8gdW5kZXJzdGFuZCB0aGUgdGFncyBhc3NvY2lhdGVkIHdpdGggZWFjaCBxdWVzdGlvbiB0aGF0IHdhcyBhc2tlZCwgYW5kIGVhY2ggYW5zd2VyIHRoYXQgd2FzIHByb3ZpZGVkLiBXZSdsbCBiZSBhYmxlIHRvIGNvbWJpbmUgdGhlc2UgdGFibGVzIHVzaW5nIHR3byBpbm5lciBqb2lucyBvbiBib3RoIHRoZSBxdWVzdGlvbnMgdGFibGUgYW5kIHRoZSBhbnN3ZXJzIHRhYmxlLg0KDQpgYGB7cn0NCiMgSW5uZXIgam9pbiB0aGUgcXVlc3Rpb25fdGFncyBhbmQgdGFncyB0YWJsZXMgd2l0aCB0aGUgcXVlc3Rpb25zIHRhYmxlDQpxdWVzdGlvbnNfd2l0aF90YWdzIDwtIHF1ZXN0aW9ucyAlPiUNCiAgaW5uZXJfam9pbihxdWVzdGlvbl90YWdzLCBieSA9IGMoImlkIiA9ICJxdWVzdGlvbl9pZCIpKSAlPiUNCiAgaW5uZXJfam9pbih0YWdzLCBieSA9IGMoInRhZ19pZCIgPSAiaWQiKSkNCnF1ZXN0aW9uc193aXRoX3RhZ3MNCiMgSW5uZXIgam9pbiB0aGUgcXVlc3Rpb25fdGFncyBhbmQgdGFncyB0YWJsZXMgd2l0aCB0aGUgYW5zd2VycyB0YWJsZQ0KYW5zd2Vyc193aXRoX3RhZ3MgPC0gYW5zd2VycyAlPiUNCiAgaW5uZXJfam9pbihxdWVzdGlvbl90YWdzLCBieSA9IGMoInF1ZXN0aW9uX2lkIiA9ICJxdWVzdGlvbl9pZCIpKSAlPiUNCiAgaW5uZXJfam9pbih0YWdzLCBieSA9IGMoInRhZ19pZCIgPSAiaWQiKSkNCmFuc3dlcnNfd2l0aF90YWdzDQpgYGANCk5vdyB3ZSB3aWxsIGJlIGFibGUgdG8gY29tYmluZSBlYWNoIG9mIHRoZXNlIGluZGl2aWR1YWwgdGFibGVzIGludG8gYSBzaW5nbGUgY29oZXNpdmUgdGFibGUgdG8gaGF2ZSBhIGJldHRlciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBpbmZvcm1hdGlvbiB3ZSBoYXZlIGFib3V0IHRoZSBxdWVzdGlvbnMsIGFuc3dlcnMsIGFuZCBhc3NvY2lhdGVkIHRhZ3MuDQoNCiMjIEJpbmRpbmcgYW5kIGNvdW50aW5nIHBvc3RzIHdpdGggdGFncw0KDQpGaXJzdCwgd2UnbGwgd2FudCB0byBjb21iaW5lIHRoZXNlIHRhYmxlcyBpbnRvIGEgc2luZ2xlIHRhYmxlIGNhbGxlZCBwb3N0c193aXRoX3RhZ3MuIE9uY2UgdGhlIGluZm9ybWF0aW9uIGlzIGNvbnNvbGlkYXRlZCBpbnRvIGEgc2luZ2xlIHRhYmxlLCB3ZSBjYW4gYWRkIG1vcmUgaW5mb3JtYXRpb24gYnkgY3JlYXRpbmcgYSBkYXRlIHZhcmlhYmxlIHVzaW5nIHRoZSBsdWJyaWRhdGUgcGFja2FnZS4NCg0KYGBge3J9DQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCiMgQ29tYmluZSB0aGUgdHdvIHRhYmxlcyBpbnRvIHBvc3RzX3dpdGhfdGFncw0KcG9zdHNfd2l0aF90YWdzIDwtIGJpbmRfcm93cyhxdWVzdGlvbnNfd2l0aF90YWdzICU+JSBtdXRhdGUodHlwZSA9ICJxdWVzdGlvbiIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYW5zd2Vyc193aXRoX3RhZ3MgJT4lIG11dGF0ZSh0eXBlID0gImFuc3dlciIpKQ0KDQoNCiMgQWRkIGEgeWVhciBjb2x1bW4sIHRoZW4gYWdncmVnYXRlIGJ5IHR5cGUsIHllYXIsIGFuZCB0YWdfbmFtZQ0KYnlfdHlwZV95ZWFyX3RhZyA8LSBwb3N0c193aXRoX3RhZ3MgJT4lDQogIG11dGF0ZSh5ZWFyID0geWVhcihjcmVhdGlvbl9kYXRlKSkgJT4lDQogIGdyb3VwX2J5KHR5cGUsIHllYXIsIHRhZ19uYW1lKSAlPiUNCiAgY291bnQoKQ0KYnlfdHlwZV95ZWFyX3RhZw0KYGBgDQojIyBWaXN1YWxpemluZyBxdWVzdGlvbnMgYW5kIGFuc3dlcnMgaW4gdGFncw0KDQpMZXQncyBjcmVhdGUgYSBwbG90IHRvIGV4YW1pbmUgdGhlIGluZm9ybWF0aW9uIHRoYXQgdGhlIHRhYmxlIGNvbnRhaW5zIGFib3V0IHF1ZXN0aW9ucyBhbmQgYW5zd2VycyBmb3IgdGhlIGRwbHlyIGFuZCBnZ3Bsb3QyIHRhZ3MuIA0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQojIEZpbHRlciBmb3IgdGhlIGRwbHlyIGFuZCBnZ3Bsb3QyIHRhZyBuYW1lcyANCmJ5X3R5cGVfeWVhcl90YWdfZmlsdGVyZWQgPC0gYnlfdHlwZV95ZWFyX3RhZyAlPiUNCiAgZmlsdGVyKHRhZ19uYW1lID09ICJkcGx5ciIgfCB0YWdfbmFtZSA9PSAiZ2dwbG90MiIpDQoNCiMgQ3JlYXRlIGEgbGluZSBwbG90IGZhY2V0ZWQgYnkgdGhlIHRhZyBuYW1lIA0KZ2dwbG90KGJ5X3R5cGVfeWVhcl90YWdfZmlsdGVyZWQsIGFlcyh4ID0geWVhciwgeSA9IG4sIGNvbG9yID0gdHlwZSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBmYWNldF93cmFwKH4gdGFnX25hbWUpDQoNCmBgYA0KTm90aWNlIGFuc3dlcnMgb24gZHBseXIgcXVlc3Rpb25zIGFyZSBncm93aW5nIGZhc3RlciB0aGFuIGRwbHlyIHF1ZXN0aW9ucyB0aGVtc2VsdmVzOyBtZWFuaW5nIHRoZSBhdmVyYWdlIGRwbHlyIHF1ZXN0aW9uIGhhcyBtb3JlIGFuc3dlcnMgdGhhbiB0aGUgYXZlcmFnZSBnZ3Bsb3QyIHF1ZXN0aW9uLg0KDQoNCg==