Question 1. File import
a problematic file has been received. here’s the first 5 lines of the file:
#human gene information
gene_id;gene_name;chr;start;end;strand;tx_count;gc_perc;biotype
ENSG00000210049;MT-TF;MT;577;647;1;1;40.85;Mt_tRNA
ENSG00000211459;MT-RNR1;MT;648;1601;1;1;45.49;Mt_rRNA
ENSG00000210077;MT-TV;MT;1602;1670;1;1;42.03;Mt_tRNA
The file is located at: https://s3-us-west-2.amazonaws.com/veri-analizi/problem_file.txt
Please write a code to import this file. (Import means can you successfully get the data into data frame structure. For example, does filter(gc_perc > 42)
work on imported table?)
Solution 1.
The file is not a csv file, thus we should not be using read.csv
or read_csv
functions. As you remember from the discussion in class, there are custom functions for specific file types (csv, tsv) and then there’s generic functions read.table
and read_delim
. The delimeter is |
character and there are two comment line on top. As you can see, both functions have completely different options.
# this is base R solution and it automatically skips first commnent line,
# due to `comment.char = "#"` default option
read.table("problem_file.txt", sep = ";", header = TRUE, stringsAsFactors = FALSE)
# OR
library(readr)
read_delim("problem_file.txt", delim = ";", skip=1) #this is tidyverse solution
Below is the related slide from “Importing Data in R” course, Chapter 2.
Question 2. A simple question - Summary of columns
A. general summary
Using human_genes
table (which is loaded for you), please calculate the following summary
- total number of genes
- total number of chromosomes
- total number of biotypes
Expected output:
Solution 2.A
library(dplyr)
human_genes %>%
summarise(n_genes=n(),
n_chr=n_distinct(chr),
n_biotypes=n_distinct(biotype))
B. protein coding genes not located on mitochondria chromosome
Using the same table please calculate the following summary, considering only protein_coding
genes which are not on mitochandrial chromosome (MT
in table)
- total number of genes (after filtering)
- average gene length
- average GC percentage
Expected output:
Solution 2.B
human_genes %>%
filter(biotype=="protein_coding" & chr != "MT") %>%
summarise(n_genes=n(),
avg_length_genes=mean(end-start),
avg_gc_perc=mean(gc_perc))
Here’s the screenshot from DataCamp exercise which uses aggregate functions with summarise()
. “Data Manipulation in R with dplyr” lecture, Chapter 4, Exercise title “dplyr aggregate functions”
Question 3. Airline carrier with better delay stats
Please write down the code below which can produce the plot above The plot describes average departure delay (Red) and arrival delay (Blue) for each carrier.
Solution 3
library(ggplot2)
hflights %>%
group_by(UniqueCarrier) %>%
summarise(avg_arr_delay=mean(ArrDelay,na.rm = TRUE),
avg_dep_delay=mean(DepDelay,na.rm = TRUE)) %>%
ggplot() +
# it would be difficult to plot two separate delay data as scatter plot. remember, ggplot draws in layers,
geom_point(aes(x=UniqueCarrier,y=avg_arr_delay,col="arr_delay")) + # this is first point layer
geom_point(aes(x=UniqueCarrier,y=avg_dep_delay,col="dep_delay")) + # second point layer for departure delays
ylab("Average \nArr or Dep Delay") + # this is not required
scale_colour_manual(name = "Delays", # this part is completely cosmetic please ignore
values =c("arr_delay" = 'blue',"dep_delay" = 'red'), # theming - you can ignore
labels = c('Arr Delay','Dep Delay'), # theming - you can ignore
breaks=c("arr_delay","dep_delay")) + # theming - you can ignore
theme_light() # theming - you can ignore

Question 4. A troublesome join
You already studied and are aware of hflights
table from DataCamp courses. Three more tables are already defined for you (when you run the first chunk). Instead of using lookup tables as you did DataCamp, let’s use join
to combine all data. Use airline_codes
, cancellation_codes
and airport_codes
tables (already loaded for you) to find out most frequent weather
related cancellations for airline and destination.
“airline” column should have carriers full name, “destination” column should have full name of airport and “cancellation” column should have the explanation of cancellation.
Expected output:
Solution 4
hflights %>%
inner_join(airline_codes, by=c("UniqueCarrier"="name")) %>% # the keys have different names
inner_join(airport_codes, by=c("Dest"="faa")) %>% # the keys have different names
inner_join(cancellation_codes, by=c("CancellationCode"="name")) %>% # the keys have different names
# you can use suffix option in joins to keep track of which column comes from which table
select(airline=value.x,destination=name,cancellation=value.y) %>% # selecting and renaming columns
filter(cancellation=="weather") %>%
group_by(airline,destination,cancellation) %>% # to count per airline and destination pairs, we group them
count() %>%
arrange(-n)
LS0tCnRpdGxlOiAiR3IyIE1pZHRlcm0gU29sdXRpb25zIgphdXRob3I6ICJhbHBlciB5aWxtYXoiCmRhdGU6ICJOb3ZlbWJlciAxNSwgMjAxNyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CiMgdGhlcmUgYXJlIG1pc3NpbmcvbmVjZXNzYXJ5IGxpYnJhcmllcyBwbGVhc2UgbG9hZCB0aGVtIHlvdXJzZWxmCmxpYnJhcnkoaGZsaWdodHMpCgpmaWxlc190b19kb3dubG9hZCA8LSBjKCJhaXJsaW5lX2NvZGVzIiwiY2FuY2VsbGF0aW9uX2NvZGVzIiwiYWlycG9ydF9jb2RlcyIsImh1bWFuX2dlbmVzIikKdXJsIDwtICJodHRwczovL3MzLXVzLXdlc3QtMi5hbWF6b25hd3MuY29tL3ZlcmktYW5hbGl6aS8iCiMgd2Ugc2hvdWxkIG5vdCBiZSBkb3dubG9hZGluZyBmaWxlcyBhdCBlYWNoIHJ1biAtIGZvciBzaW5nbGUgcnVuIHRoaXMgaXMgYmV0dGVyOiByZWFkUkRTKGd6Y29uKHVybCgicmVtb3RlLXVybC1yZHMtZmlsZSIpKSkKZG93bmxvYWRzIDwtIGxhcHBseShmaWxlc190b19kb3dubG9hZCwgZnVuY3Rpb24oeCkgewogIGlmICghZmlsZS5leGlzdHMocGFzdGUwKHggLCAiLnJkcyIpKSkgeyBkb3dubG9hZC5maWxlKHBhc3RlMCggdXJsICwgeCAsICIucmRzIiApLCBwYXN0ZTAoeCAsICIucmRzIikpCiAgfSAgCiAgYXNzaWduKCB4LCByZWFkUkRTKCBwYXN0ZTAoIHggLCAiLnJkcyIgKSksIGVudmlyID0gcGFyZW50LmZyYW1lKDIpICkgCk5VTEwKfSkKYGBgCgojIFF1ZXN0aW9uIDEuIEZpbGUgaW1wb3J0CgphIHByb2JsZW1hdGljIGZpbGUgaGFzIGJlZW4gcmVjZWl2ZWQuIGhlcmUncyB0aGUgZmlyc3QgNSBsaW5lcyBvZiB0aGUgZmlsZToKCmBgYAojaHVtYW4gZ2VuZSBpbmZvcm1hdGlvbgpnZW5lX2lkO2dlbmVfbmFtZTtjaHI7c3RhcnQ7ZW5kO3N0cmFuZDt0eF9jb3VudDtnY19wZXJjO2Jpb3R5cGUKRU5TRzAwMDAwMjEwMDQ5O01ULVRGO01UOzU3Nzs2NDc7MTsxOzQwLjg1O010X3RSTkEKRU5TRzAwMDAwMjExNDU5O01ULVJOUjE7TVQ7NjQ4OzE2MDE7MTsxOzQ1LjQ5O010X3JSTkEKRU5TRzAwMDAwMjEwMDc3O01ULVRWO01UOzE2MDI7MTY3MDsxOzE7NDIuMDM7TXRfdFJOQQpgYGAKClRoZSBmaWxlIGlzIGxvY2F0ZWQgYXQ6IGh0dHBzOi8vczMtdXMtd2VzdC0yLmFtYXpvbmF3cy5jb20vdmVyaS1hbmFsaXppL3Byb2JsZW1fZmlsZS50eHQKClBsZWFzZSB3cml0ZSBhIGNvZGUgdG8gaW1wb3J0IHRoaXMgZmlsZS4gKCoqSW1wb3J0KiogbWVhbnMgY2FuIHlvdSBzdWNjZXNzZnVsbHkgZ2V0IHRoZSBkYXRhIGludG8gZGF0YSBmcmFtZSBzdHJ1Y3R1cmUuIEZvciBleGFtcGxlLCBkb2VzIGBmaWx0ZXIoZ2NfcGVyYyA+IDQyKWAgd29yayBvbiBpbXBvcnRlZCB0YWJsZT8pCgojIFNvbHV0aW9uIDEuIAoKVGhlIGZpbGUgaXMgbm90IGEgY3N2IGZpbGUsIHRodXMgd2Ugc2hvdWxkIG5vdCBiZSB1c2luZyBgcmVhZC5jc3ZgIG9yIGByZWFkX2NzdmAgZnVuY3Rpb25zLiBBcyB5b3UgcmVtZW1iZXIgZnJvbSB0aGUgZGlzY3Vzc2lvbiBpbiBjbGFzcywgdGhlcmUgYXJlIGN1c3RvbSBmdW5jdGlvbnMgZm9yIHNwZWNpZmljIGZpbGUgdHlwZXMgKGNzdiwgdHN2KSBhbmQgdGhlbiB0aGVyZSdzIGdlbmVyaWMgZnVuY3Rpb25zIGByZWFkLnRhYmxlYCBhbmQgYHJlYWRfZGVsaW1gLiBUaGUgZGVsaW1ldGVyIGlzIGB8YCBjaGFyYWN0ZXIgYW5kIHRoZXJlIGFyZSB0d28gY29tbWVudCBsaW5lIG9uIHRvcC4gQXMgeW91IGNhbiBzZWUsIGJvdGggZnVuY3Rpb25zIGhhdmUgY29tcGxldGVseSBkaWZmZXJlbnQgb3B0aW9ucy4gCgpgYGB7ciBzb2x1dGlvbjEsIHdhcm5pbmc9IEZBTFNFLCBtZXNzYWdlPUZBTFNFfQojIHRoaXMgaXMgYmFzZSBSIHNvbHV0aW9uIGFuZCBpdCBhdXRvbWF0aWNhbGx5IHNraXBzIGZpcnN0IGNvbW1uZW50IGxpbmUsIAojIGR1ZSB0byBgY29tbWVudC5jaGFyID0gIiMiYCBkZWZhdWx0IG9wdGlvbgpyZWFkLnRhYmxlKCJwcm9ibGVtX2ZpbGUudHh0Iiwgc2VwID0gIjsiLCBoZWFkZXIgPSBUUlVFLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiMgT1IKCmxpYnJhcnkocmVhZHIpCnJlYWRfZGVsaW0oInByb2JsZW1fZmlsZS50eHQiLCAgZGVsaW0gPSAiOyIsIHNraXA9MSkgICN0aGlzIGlzIHRpZHl2ZXJzZSBzb2x1dGlvbgpgYGAKCkJlbG93IGlzIHRoZSByZWxhdGVkIHNsaWRlIGZyb20gIkltcG9ydGluZyBEYXRhIGluIFIiIGNvdXJzZSwgQ2hhcHRlciAyLgoKIVtJbXBvcnQgd2l0aCBkZWxpbSBvciBzZXAgb3B0aW9uc10oaW1wb3J0X2RlbGltLnBuZykKCiMgUXVlc3Rpb24gMi4gQSBzaW1wbGUgcXVlc3Rpb24gLSBTdW1tYXJ5IG9mIGNvbHVtbnMKCiMjIEEuIGdlbmVyYWwgc3VtbWFyeQoKVXNpbmcgYGh1bWFuX2dlbmVzYCB0YWJsZSAod2hpY2ggaXMgbG9hZGVkIGZvciB5b3UpLCBwbGVhc2UgY2FsY3VsYXRlIHRoZSBmb2xsb3dpbmcgc3VtbWFyeQoKKiB0b3RhbCBudW1iZXIgb2YgZ2VuZXMKKiB0b3RhbCBudW1iZXIgb2YgY2hyb21vc29tZXMKKiB0b3RhbCBudW1iZXIgb2YgYmlvdHlwZXMKCkV4cGVjdGVkIG91dHB1dDoKCnwgbl9nZW5lcyB8IG5fY2hyIHwgbl9iaW90eXBlcyB8CnwtLS0tLS0tLS18LS0tLS0tLXwtLS0tLS0tLS0tLS18CnwgICAuLiAgICB8ICAgLi4gIHwgICAgIC4uICAgICB8CgojIyBTb2x1dGlvbiAyLkEKCmBgYHtyIGFuc3dlcjJhLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KGRwbHlyKQpodW1hbl9nZW5lcyAlPiUKICBzdW1tYXJpc2Uobl9nZW5lcz1uKCksCiAgICAgICAgICAgIG5fY2hyPW5fZGlzdGluY3QoY2hyKSwKICAgICAgICAgICAgbl9iaW90eXBlcz1uX2Rpc3RpbmN0KGJpb3R5cGUpKQpgYGAKCgojIyBCLiBwcm90ZWluIGNvZGluZyBnZW5lcyBub3QgbG9jYXRlZCBvbiBtaXRvY2hvbmRyaWEgY2hyb21vc29tZQoKVXNpbmcgdGhlIHNhbWUgdGFibGUgcGxlYXNlIGNhbGN1bGF0ZSB0aGUgZm9sbG93aW5nIHN1bW1hcnksIGNvbnNpZGVyaW5nIG9ubHkgYHByb3RlaW5fY29kaW5nYCBnZW5lcyB3aGljaCBhcmUgbm90IG9uIG1pdG9jaGFuZHJpYWwgY2hyb21vc29tZSAoYE1UYCBpbiB0YWJsZSkKCiogdG90YWwgbnVtYmVyIG9mIGdlbmVzIChhZnRlciBmaWx0ZXJpbmcpCiogYXZlcmFnZSBnZW5lIGxlbmd0aAoqIGF2ZXJhZ2UgR0MgcGVyY2VudGFnZQoKRXhwZWN0ZWQgb3V0cHV0OgoKfCBuX2dlbmVzIHwgYXZnX2xlbmd0aF9nZW5lcyB8IGF2Z19nY19wZXJjIHwgCnwtLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS18CnwgICAuLiAgICB8ICAgICAgIC4uICAgICAgICAgfCAgICAgIC4uICAgICB8CgojIyBTb2x1dGlvbiAyLkIKCmBgYHtyIGFuc3dlcjJifQpodW1hbl9nZW5lcyAlPiUKICBmaWx0ZXIoYmlvdHlwZT09InByb3RlaW5fY29kaW5nIiAmIGNociAhPSAiTVQiKSAlPiUgCiAgc3VtbWFyaXNlKG5fZ2VuZXM9bigpLAogICAgICAgICAgICBhdmdfbGVuZ3RoX2dlbmVzPW1lYW4oZW5kLXN0YXJ0KSwKICAgICAgICAgICAgYXZnX2djX3BlcmM9bWVhbihnY19wZXJjKSkKYGBgCgpIZXJlJ3MgdGhlIHNjcmVlbnNob3QgZnJvbSBEYXRhQ2FtcCBleGVyY2lzZSB3aGljaCB1c2VzIGFnZ3JlZ2F0ZSBmdW5jdGlvbnMgd2l0aCBgc3VtbWFyaXNlKClgLiAiRGF0YSBNYW5pcHVsYXRpb24gaW4gUiB3aXRoIGRwbHlyIiBsZWN0dXJlLCBDaGFwdGVyIDQsIEV4ZXJjaXNlIHRpdGxlICJkcGx5ciBhZ2dyZWdhdGUgZnVuY3Rpb25zIiAKCiFbZHBseXIgc3VtbWFyaXNlIGFnZ3JlZ2F0ZSBmdW5jdGlvbnNdKGRwbHlyX3N1bW1hcmlzZV9hZ2dyZWdhdGUucG5nKQoKCgojIFF1ZXN0aW9uIDMuIEFpcmxpbmUgY2FycmllciB3aXRoIGJldHRlciBkZWxheSBzdGF0cwoKIVtBdmVyYWdlIERlbGF5cyBwZXIgQ2Fycmllcl0oaHR0cHM6Ly9zMy11cy13ZXN0LTIuYW1hem9uYXdzLmNvbS92ZXJpLWFuYWxpemkvYXZlcmFnZV9kZWxheS5wbmcpCgpQbGVhc2Ugd3JpdGUgZG93biB0aGUgY29kZSBiZWxvdyB3aGljaCBjYW4gcHJvZHVjZSB0aGUgcGxvdCBhYm92ZSBUaGUgcGxvdCBkZXNjcmliZXMgYXZlcmFnZSBkZXBhcnR1cmUgZGVsYXkgKFJlZCkgYW5kIGFycml2YWwgZGVsYXkgKEJsdWUpIGZvciBlYWNoIGNhcnJpZXIuCgojIFNvbHV0aW9uIDMKCmBgYHtyIGFuc3dlcjN9CmxpYnJhcnkoZ2dwbG90MikKaGZsaWdodHMgJT4lIAogIGdyb3VwX2J5KFVuaXF1ZUNhcnJpZXIpICU+JSAKICBzdW1tYXJpc2UoYXZnX2Fycl9kZWxheT1tZWFuKEFyckRlbGF5LG5hLnJtID0gVFJVRSksCiAgICAgICAgICAgIGF2Z19kZXBfZGVsYXk9bWVhbihEZXBEZWxheSxuYS5ybSA9IFRSVUUpKSAlPiUgCiAgZ2dwbG90KCkgKwogICMgaXQgd291bGQgYmUgZGlmZmljdWx0IHRvIHBsb3QgdHdvIHNlcGFyYXRlIGRlbGF5IGRhdGEgYXMgc2NhdHRlciBwbG90LiByZW1lbWJlciwgZ2dwbG90IGRyYXdzIGluIGxheWVycywgCiAgZ2VvbV9wb2ludChhZXMoeD1VbmlxdWVDYXJyaWVyLHk9YXZnX2Fycl9kZWxheSxjb2w9ImFycl9kZWxheSIpKSArICAgIyB0aGlzIGlzIGZpcnN0IHBvaW50IGxheWVyCiAgZ2VvbV9wb2ludChhZXMoeD1VbmlxdWVDYXJyaWVyLHk9YXZnX2RlcF9kZWxheSxjb2w9ImRlcF9kZWxheSIpKSArICAgIyBzZWNvbmQgcG9pbnQgbGF5ZXIgZm9yIGRlcGFydHVyZSBkZWxheXMKICB5bGFiKCJBdmVyYWdlIFxuQXJyIG9yIERlcCBEZWxheSIpICsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHRoaXMgaXMgbm90IHJlcXVpcmVkCiAgc2NhbGVfY29sb3VyX21hbnVhbChuYW1lID0gIkRlbGF5cyIsICAgICAgICAgICAgICAgICAgICAgIyB0aGlzIHBhcnQgaXMgY29tcGxldGVseSBjb3NtZXRpYyBwbGVhc2UgaWdub3JlCiAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID1jKCJhcnJfZGVsYXkiID0gJ2JsdWUnLCJkZXBfZGVsYXkiID0gJ3JlZCcpLCAgICAgICMgdGhlbWluZyAtIHlvdSBjYW4gaWdub3JlCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygnQXJyIERlbGF5JywnRGVwIERlbGF5JyksICAgICAgICAgICAgICAgICAgICAgICMgdGhlbWluZyAtIHlvdSBjYW4gaWdub3JlCiAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzPWMoImFycl9kZWxheSIsImRlcF9kZWxheSIpKSArICAgICAgICAgICAgICAgICAgICAgICMgdGhlbWluZyAtIHlvdSBjYW4gaWdub3JlCiAgdGhlbWVfbGlnaHQoKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgdGhlbWluZyAtIHlvdSBjYW4gaWdub3JlCmBgYAoKIyBRdWVzdGlvbiA0LiBBIHRyb3VibGVzb21lIGpvaW4KCllvdSBhbHJlYWR5IHN0dWRpZWQgYW5kIGFyZSBhd2FyZSBvZiBgaGZsaWdodHNgIHRhYmxlIGZyb20gRGF0YUNhbXAgY291cnNlcy4gVGhyZWUgbW9yZSB0YWJsZXMgYXJlIGFscmVhZHkgZGVmaW5lZCBmb3IgeW91ICh3aGVuIHlvdSBydW4gdGhlIGZpcnN0IGNodW5rKS4gSW5zdGVhZCBvZiB1c2luZyBsb29rdXAgdGFibGVzIGFzIHlvdSBkaWQgRGF0YUNhbXAsIGxldCdzIHVzZSBgam9pbmAgdG8gY29tYmluZSBhbGwgZGF0YS4gVXNlIGBhaXJsaW5lX2NvZGVzYCwgYGNhbmNlbGxhdGlvbl9jb2Rlc2AgYW5kIGBhaXJwb3J0X2NvZGVzYCB0YWJsZXMgKGFscmVhZHkgbG9hZGVkIGZvciB5b3UpIHRvIGZpbmQgb3V0IG1vc3QgZnJlcXVlbnQgYHdlYXRoZXJgIHJlbGF0ZWQgY2FuY2VsbGF0aW9ucyBmb3IgYWlybGluZSBhbmQgZGVzdGluYXRpb24uCgoiYWlybGluZSIgY29sdW1uIHNob3VsZCBoYXZlIGNhcnJpZXJzIGZ1bGwgbmFtZSwgImRlc3RpbmF0aW9uIiBjb2x1bW4gc2hvdWxkIGhhdmUgZnVsbCBuYW1lIG9mIGFpcnBvcnQgYW5kICJjYW5jZWxsYXRpb24iIGNvbHVtbiBzaG91bGQgaGF2ZSB0aGUgZXhwbGFuYXRpb24gb2YgY2FuY2VsbGF0aW9uLgoKRXhwZWN0ZWQgb3V0cHV0OgoKfCBhaXJsaW5lIHwgZGVzdGluYXRpb24gfCBjYW5jZWxsYXRpb24gfCAgY291bnQgfAp8LS0tLS0tLS0tfC0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS18LS0tLS0tLS18CnwgICAuLiAgICB8ICAgLi4gICAgICAgIHwgICAgICAgLi4gICAgIHwgIC4uICAgIHwKCiMgU29sdXRpb24gNAoKYGBge3IgYW5zd2VyNH0KaGZsaWdodHMgJT4lIAogIGlubmVyX2pvaW4oYWlybGluZV9jb2RlcywgYnk9YygiVW5pcXVlQ2FycmllciI9Im5hbWUiKSkgJT4lICAgICAgICAgICAjIHRoZSBrZXlzIGhhdmUgZGlmZmVyZW50IG5hbWVzIAogIGlubmVyX2pvaW4oYWlycG9ydF9jb2RlcywgYnk9YygiRGVzdCI9ImZhYSIpKSAlPiUgICAgICAgICAgICAgICAgICAgICAjIHRoZSBrZXlzIGhhdmUgZGlmZmVyZW50IG5hbWVzCiAgaW5uZXJfam9pbihjYW5jZWxsYXRpb25fY29kZXMsIGJ5PWMoIkNhbmNlbGxhdGlvbkNvZGUiPSJuYW1lIikpICU+JSAgICMgdGhlIGtleXMgaGF2ZSBkaWZmZXJlbnQgbmFtZXMgCiAgIyB5b3UgY2FuIHVzZSBzdWZmaXggb3B0aW9uIGluIGpvaW5zIHRvIGtlZXAgdHJhY2sgb2Ygd2hpY2ggY29sdW1uIGNvbWVzIGZyb20gd2hpY2ggdGFibGUKICBzZWxlY3QoYWlybGluZT12YWx1ZS54LGRlc3RpbmF0aW9uPW5hbWUsY2FuY2VsbGF0aW9uPXZhbHVlLnkpICU+JSAgICAgIyBzZWxlY3RpbmcgYW5kIHJlbmFtaW5nIGNvbHVtbnMKICBmaWx0ZXIoY2FuY2VsbGF0aW9uPT0id2VhdGhlciIpICU+JSAKICBncm91cF9ieShhaXJsaW5lLGRlc3RpbmF0aW9uLGNhbmNlbGxhdGlvbikgJT4lICAjIHRvIGNvdW50IHBlciBhaXJsaW5lIGFuZCBkZXN0aW5hdGlvbiBwYWlycywgd2UgZ3JvdXAgdGhlbQogIGNvdW50KCkgJT4lIAogIGFycmFuZ2UoLW4pCmBgYAoK