suppressPackageStartupMessages(library("tidyverse"))
package 㤼㸱tidyverse㤼㸲 was built under R version 3.6.3
suppressPackageStartupMessages(library("nycflights13"))
package 㤼㸱nycflights13㤼㸲 was built under R version 3.6.3
suppressPackageStartupMessages(library("viridis"))
package 㤼㸱viridis㤼㸲 was built under R version 3.6.2
1. What does it mean for a flight to have a missing tailnum? What do the tail numbers that don’t have a matching record in planes have in common? (Hint: one variable explains ~90% of the problems.)
Flights that have a missing tailnum all have missing values of arr_time
, meaning that the flight was canceled.
flights %>%
filter(is.na(tailnum), !is.na(arr_time)) %>%
nrow()
[1] 0
Many of the tail numbers that don’t have a matching value in planes are registered to American Airlines (AA) or Envoy Airlines (MQ). The documentation for planes
states
American Airways (AA) and Envoy Air (MQ) report fleet numbers rather than tail numbers so can’t be matched.
flights %>%
anti_join(planes, by = "tailnum") %>%
count(carrier, sort = TRUE) %>%
mutate(p = n / sum(n))
However, not all tail numbers appearing in flights
from these carriers are missing from the planes
table. I don’t know how to reconcile this discrepancy.
flights %>%
distinct(carrier, tailnum) %>%
left_join(planes, by = "tailnum") %>%
group_by(carrier) %>%
summarise(
total_planes = n(),
not_in_planes = sum(is.na(model))
) %>%
mutate(missing_pct = not_in_planes / total_planes) %>%
arrange(desc(missing_pct))
2. Filter flights to only show flights with planes that have flown at least 100 flights.
First, I find all planes that have flown at least 100 flights. I need to filter flights that are missing a tail number otherwise all flights missing a tail number will be treated as a single plane.
planes_gte100 <- flights %>%
filter(!is.na(tailnum)) %>%
group_by(tailnum) %>%
count() %>%
filter(n >= 100)
Now, I will semi join the data frame of planes that have flown at least 100 flights to the data frame of flights to select the flights by those planes.
flights %>%
semi_join(planes_gte100, by = "tailnum")
This can also be answered with a grouped mutate.
flights %>%
filter(!is.na(tailnum)) %>%
group_by(tailnum) %>%
mutate(n = n()) %>%
filter(n >= 100)
3.Combine fueleconomy::vehicles
and fueleconomy::common
to find only the records for the most common models.
fueleconomy::vehicles %>%
semi_join(fueleconomy::common, by = c("make", "model"))
Detecting old grouped_df format, replacing `vars` attribute by `groups`
Why does the above code join on make
and model
and not just model
? It is possible for two car brands (make) to produce a car with the same name (model). In both the vehicles and common data we can find some examples. For example, “Truck 4WD”
is produced by many different brands.
fueleconomy::vehicles %>%
distinct(model, make) %>%
group_by(model) %>%
filter(n() > 1) %>%
arrange(model)
fueleconomy::common %>%
distinct(model, make) %>%
group_by(model) %>%
filter(n() > 1) %>%
arrange(model)
If we were to merge these data on the model
column alone, there would be incorrect matches.
4. Find the 48 hours (over the course of the whole year) that have the worst delays. Cross-reference it with the weather data. Can you see any patterns?
I will start by clarifying how I will be measuring the concepts in the question. There are three concepts that need to be defined more precisely.
What is meant by “delay”? I will use departure delay. Since the weather data only contains data for the New York City airports, and departure delays will be more sensitive to New York City weather conditions than arrival delays.
What is meant by “worst”? I define worst delay as the average departure delay per flight for flights scheduled to depart in that hour. For hour, I will use the scheduled departure time rather than the actual departure time. If planes are delayed due to weather conditions, the weather conditions during the scheduled time are more important than the actual departure time, at which point, the weather could have improved.
What is meant by “48 hours over the course of the year”? This could mean two days, a span of 48 contiguous hours, or 48 hours that are not necessarily contiguous hours. I will find 48 not-necessarily contiguous hours. That definition makes better use of the methods introduced in this section and chapter.
What is the unit of analysis? Although the question mentions only hours, I will use airport hours. The weather dataset has an observation for each airport for each hour. Since all the departure airports are in the vicinity of New York City, their weather should be similar, it will not be the same.
First, I need to find the 48 hours with the worst delays. I group flights by hour of scheduled departure time and calculate the average delay. Then I select the 48 observations (hours) with the highest average delay.
worst_hours <- flights %>%
mutate(hour = sched_dep_time %/% 100) %>%
group_by(origin, year, month, day, hour) %>%
summarise(dep_delay = mean(dep_delay, na.rm = TRUE)) %>%
ungroup() %>%
arrange(desc(dep_delay)) %>%
slice(1:48)
Then I can use semi_join()
to get the weather for these hours.
weather_most_delayed <- semi_join(weather, worst_hours,
by = c(
"origin", "year",
"month", "day", "hour"
)
)
For weather, I’ll focus on precipitation, wind speed, and temperature. I will display these in both a table and a plot.
Many of these observations have a higher than average wind speed (10 mph) or some precipitation. However, I would have expected the weather for the hours with the worst delays to be much worse.
select(weather_most_delayed, temp, wind_speed, precip) %>%
print(n = 48)
ggplot(weather_most_delayed, aes(x = precip, y = wind_speed, color = temp)) +
geom_point()

It’s hard to say much more than that without using the tools from Exploratory Data Analysis section to look for covariation between weather and flight delays using all flights. Implicitly in my informal analysis of trends in weather using only the 48 hours with the worst delays, I was comparing the weather in these hours to some belief I had about what constitutes “normal” or “good” weather. It would be better to actually use data to make that comparison.
5. What does anti_join(flights, airports, by = c("dest" = "faa"))
tell you? What does anti_join(airports, flights, by = c("faa" = "dest"))
tell you?
The expression anti_join(flights, airports, by = c("dest" = "faa"))
returns the flights that went to an airport that is not in the FAA list of destinations. Since the FAA list only contains domestic airports, these are likely foreign flights.
The expression anti_join(airports, flights, by = c("faa" = "dest"))
returns the US airports that were not the destination of any flight in the data. Since the data contains all flights from New York City airports, this is also the list of US airports that did not have a nonstop flight from New York City in 2013.
LS0tDQp0aXRsZTogIkZpbHRlcmluZyBqb2lucyINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQotLS0NCg0KYGBge3J9DQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeSgidGlkeXZlcnNlIikpDQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeSgibnljZmxpZ2h0czEzIikpDQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeSgidmlyaWRpcyIpKQ0KYGBgDQoNCiMjIyAxLiBXaGF0IGRvZXMgaXQgbWVhbiBmb3IgYSBmbGlnaHQgdG8gaGF2ZSBhIG1pc3NpbmcgdGFpbG51bT8gV2hhdCBkbyB0aGUgdGFpbCBudW1iZXJzIHRoYXQgZG9u4oCZdCBoYXZlIGEgbWF0Y2hpbmcgcmVjb3JkIGluIHBsYW5lcyBoYXZlIGluIGNvbW1vbj8gKEhpbnQ6IG9uZSB2YXJpYWJsZSBleHBsYWlucyB+OTAlIG9mIHRoZSBwcm9ibGVtcy4pDQoNCkZsaWdodHMgdGhhdCBoYXZlIGEgbWlzc2luZyB0YWlsbnVtIGFsbCBoYXZlIG1pc3NpbmcgdmFsdWVzIG9mIGBhcnJfdGltZWAsIG1lYW5pbmcgdGhhdCB0aGUgZmxpZ2h0IHdhcyBjYW5jZWxlZC4NCg0KYGBge3J9DQpmbGlnaHRzICU+JQ0KICBmaWx0ZXIoaXMubmEodGFpbG51bSksICFpcy5uYShhcnJfdGltZSkpICU+JQ0KICBucm93KCkNCmBgYA0KDQpNYW55IG9mIHRoZSB0YWlsIG51bWJlcnMgdGhhdCBkb27igJl0IGhhdmUgYSBtYXRjaGluZyB2YWx1ZSBpbiBwbGFuZXMgYXJlIHJlZ2lzdGVyZWQgdG8gQW1lcmljYW4gQWlybGluZXMgKEFBKSBvciBFbnZveSBBaXJsaW5lcyAoTVEpLiBUaGUgZG9jdW1lbnRhdGlvbiBmb3IgYHBsYW5lc2Agc3RhdGVzDQoNCj4gQW1lcmljYW4gQWlyd2F5cyAoQUEpIGFuZCBFbnZveSBBaXIgKE1RKSByZXBvcnQgZmxlZXQgbnVtYmVycyByYXRoZXIgdGhhbiB0YWlsIG51bWJlcnMgc28gY2Fu4oCZdCBiZSBtYXRjaGVkLg0KDQpgYGB7cn0NCmZsaWdodHMgJT4lDQogIGFudGlfam9pbihwbGFuZXMsIGJ5ID0gInRhaWxudW0iKSAlPiUNCiAgY291bnQoY2Fycmllciwgc29ydCA9IFRSVUUpICU+JQ0KICBtdXRhdGUocCA9IG4gLyBzdW0obikpDQpgYGANCg0KSG93ZXZlciwgbm90IGFsbCB0YWlsIG51bWJlcnMgYXBwZWFyaW5nIGluIGBmbGlnaHRzYCBmcm9tIHRoZXNlIGNhcnJpZXJzIGFyZSBtaXNzaW5nIGZyb20gdGhlIGBwbGFuZXNgIHRhYmxlLiBJIGRvbuKAmXQga25vdyBob3cgdG8gcmVjb25jaWxlIHRoaXMgZGlzY3JlcGFuY3kuDQoNCmBgYHtyfQ0KZmxpZ2h0cyAlPiUNCiAgZGlzdGluY3QoY2FycmllciwgdGFpbG51bSkgJT4lDQogIGxlZnRfam9pbihwbGFuZXMsIGJ5ID0gInRhaWxudW0iKSAlPiUNCiAgZ3JvdXBfYnkoY2FycmllcikgJT4lDQogIHN1bW1hcmlzZSgNCiAgICB0b3RhbF9wbGFuZXMgPSBuKCksDQogICAgbm90X2luX3BsYW5lcyA9IHN1bShpcy5uYShtb2RlbCkpDQogICkgJT4lDQogIG11dGF0ZShtaXNzaW5nX3BjdCA9IG5vdF9pbl9wbGFuZXMgLyB0b3RhbF9wbGFuZXMpICU+JQ0KICBhcnJhbmdlKGRlc2MobWlzc2luZ19wY3QpKQ0KYGBgDQoNCg0KIyMjIDIuIEZpbHRlciBmbGlnaHRzIHRvIG9ubHkgc2hvdyBmbGlnaHRzIHdpdGggcGxhbmVzIHRoYXQgaGF2ZSBmbG93biBhdCBsZWFzdCAxMDAgZmxpZ2h0cy4NCg0KRmlyc3QsIEkgZmluZCBhbGwgcGxhbmVzIHRoYXQgaGF2ZSBmbG93biBhdCBsZWFzdCAxMDAgZmxpZ2h0cy4gSSBuZWVkIHRvIGZpbHRlciBmbGlnaHRzIHRoYXQgYXJlIG1pc3NpbmcgYSB0YWlsIG51bWJlciBvdGhlcndpc2UgYWxsIGZsaWdodHMgbWlzc2luZyBhIHRhaWwgbnVtYmVyIHdpbGwgYmUgdHJlYXRlZCBhcyBhIHNpbmdsZSBwbGFuZS4NCg0KYGBge3J9DQpwbGFuZXNfZ3RlMTAwIDwtIGZsaWdodHMgJT4lDQogIGZpbHRlcighaXMubmEodGFpbG51bSkpICU+JQ0KICBncm91cF9ieSh0YWlsbnVtKSAlPiUNCiAgY291bnQoKSAlPiUNCiAgZmlsdGVyKG4gPj0gMTAwKQ0KYGBgDQoNCk5vdywgSSB3aWxsIHNlbWkgam9pbiB0aGUgZGF0YSBmcmFtZSBvZiBwbGFuZXMgdGhhdCBoYXZlIGZsb3duIGF0IGxlYXN0IDEwMCBmbGlnaHRzIHRvIHRoZSBkYXRhIGZyYW1lIG9mIGZsaWdodHMgdG8gc2VsZWN0IHRoZSBmbGlnaHRzIGJ5IHRob3NlIHBsYW5lcy4NCg0KYGBge3J9DQpmbGlnaHRzICU+JQ0KICBzZW1pX2pvaW4ocGxhbmVzX2d0ZTEwMCwgYnkgPSAidGFpbG51bSIpDQpgYGANCg0KVGhpcyBjYW4gYWxzbyBiZSBhbnN3ZXJlZCB3aXRoIGEgZ3JvdXBlZCBtdXRhdGUuDQoNCmBgYHtyfQ0KZmxpZ2h0cyAlPiUNCiAgZmlsdGVyKCFpcy5uYSh0YWlsbnVtKSkgJT4lDQogIGdyb3VwX2J5KHRhaWxudW0pICU+JQ0KICBtdXRhdGUobiA9IG4oKSkgJT4lDQogIGZpbHRlcihuID49IDEwMCkNCmBgYA0KDQojIyMgMy5Db21iaW5lIGBmdWVsZWNvbm9teTo6dmVoaWNsZXNgIGFuZCBgZnVlbGVjb25vbXk6OmNvbW1vbmAgdG8gZmluZCBvbmx5IHRoZSByZWNvcmRzIGZvciB0aGUgbW9zdCBjb21tb24gbW9kZWxzLg0KDQpgYGB7cn0NCmZ1ZWxlY29ub215Ojp2ZWhpY2xlcyAlPiUNCiAgc2VtaV9qb2luKGZ1ZWxlY29ub215Ojpjb21tb24sIGJ5ID0gYygibWFrZSIsICJtb2RlbCIpKQ0KYGBgDQoNCldoeSBkb2VzIHRoZSBhYm92ZSBjb2RlIGpvaW4gb24gYG1ha2VgIGFuZCBgbW9kZWxgIGFuZCBub3QganVzdCBgbW9kZWxgPyBJdCBpcyBwb3NzaWJsZSBmb3IgdHdvIGNhciBicmFuZHMgKG1ha2UpIHRvIHByb2R1Y2UgYSBjYXIgd2l0aCB0aGUgc2FtZSBuYW1lIChtb2RlbCkuIEluIGJvdGggdGhlIHZlaGljbGVzIGFuZCBjb21tb24gZGF0YSB3ZSBjYW4gZmluZCBzb21lIGV4YW1wbGVzLiBGb3IgZXhhbXBsZSwgYOKAnFRydWNrIDRXROKAnWAgaXMgcHJvZHVjZWQgYnkgbWFueSBkaWZmZXJlbnQgYnJhbmRzLg0KDQpgYGB7cn0NCmZ1ZWxlY29ub215Ojp2ZWhpY2xlcyAlPiUNCiAgZGlzdGluY3QobW9kZWwsIG1ha2UpICU+JQ0KICBncm91cF9ieShtb2RlbCkgJT4lDQogIGZpbHRlcihuKCkgPiAxKSAlPiUNCiAgYXJyYW5nZShtb2RlbCkNCmZ1ZWxlY29ub215Ojpjb21tb24gJT4lDQogIGRpc3RpbmN0KG1vZGVsLCBtYWtlKSAlPiUNCiAgZ3JvdXBfYnkobW9kZWwpICU+JQ0KICBmaWx0ZXIobigpID4gMSkgJT4lDQogIGFycmFuZ2UobW9kZWwpDQpgYGANCg0KSWYgd2Ugd2VyZSB0byBtZXJnZSB0aGVzZSBkYXRhIG9uIHRoZSBgbW9kZWxgIGNvbHVtbiBhbG9uZSwgdGhlcmUgd291bGQgYmUgaW5jb3JyZWN0IG1hdGNoZXMuDQoNCiMjIyA0LiBGaW5kIHRoZSA0OCBob3VycyAob3ZlciB0aGUgY291cnNlIG9mIHRoZSB3aG9sZSB5ZWFyKSB0aGF0IGhhdmUgdGhlIHdvcnN0IGRlbGF5cy4gQ3Jvc3MtcmVmZXJlbmNlIGl0IHdpdGggdGhlIHdlYXRoZXIgZGF0YS4gQ2FuIHlvdSBzZWUgYW55IHBhdHRlcm5zPw0KDQpJIHdpbGwgc3RhcnQgYnkgY2xhcmlmeWluZyBob3cgSSB3aWxsIGJlIG1lYXN1cmluZyB0aGUgY29uY2VwdHMgaW4gdGhlIHF1ZXN0aW9uLiBUaGVyZSBhcmUgdGhyZWUgY29uY2VwdHMgdGhhdCBuZWVkIHRvIGJlIGRlZmluZWQgbW9yZSBwcmVjaXNlbHkuDQoNCjEuIFdoYXQgaXMgbWVhbnQgYnkg4oCcZGVsYXnigJ0/IEkgd2lsbCB1c2UgZGVwYXJ0dXJlIGRlbGF5LiBTaW5jZSB0aGUgd2VhdGhlciBkYXRhIG9ubHkgY29udGFpbnMgZGF0YSBmb3IgdGhlIE5ldyBZb3JrIENpdHkgYWlycG9ydHMsIGFuZCBkZXBhcnR1cmUgZGVsYXlzIHdpbGwgYmUgbW9yZSBzZW5zaXRpdmUgdG8gTmV3IFlvcmsgQ2l0eSB3ZWF0aGVyIGNvbmRpdGlvbnMgdGhhbiBhcnJpdmFsIGRlbGF5cy4NCg0KMi4gV2hhdCBpcyBtZWFudCBieSDigJx3b3JzdOKAnT8gSSBkZWZpbmUgd29yc3QgZGVsYXkgYXMgdGhlIGF2ZXJhZ2UgZGVwYXJ0dXJlIGRlbGF5IHBlciBmbGlnaHQgZm9yIGZsaWdodHMgc2NoZWR1bGVkIHRvIGRlcGFydCBpbiB0aGF0IGhvdXIuIEZvciBob3VyLCBJIHdpbGwgdXNlIHRoZSBzY2hlZHVsZWQgZGVwYXJ0dXJlIHRpbWUgcmF0aGVyIHRoYW4gdGhlIGFjdHVhbCBkZXBhcnR1cmUgdGltZS4gSWYgcGxhbmVzIGFyZSBkZWxheWVkIGR1ZSB0byB3ZWF0aGVyIGNvbmRpdGlvbnMsIHRoZSB3ZWF0aGVyIGNvbmRpdGlvbnMgZHVyaW5nIHRoZSBzY2hlZHVsZWQgdGltZSBhcmUgbW9yZSBpbXBvcnRhbnQgdGhhbiB0aGUgYWN0dWFsIGRlcGFydHVyZSB0aW1lLCBhdCB3aGljaCBwb2ludCwgdGhlIHdlYXRoZXIgY291bGQgaGF2ZSBpbXByb3ZlZC4NCg0KMy4gV2hhdCBpcyBtZWFudCBieSDigJw0OCBob3VycyBvdmVyIHRoZSBjb3Vyc2Ugb2YgdGhlIHllYXLigJ0/IFRoaXMgY291bGQgbWVhbiB0d28gZGF5cywgYSBzcGFuIG9mIDQ4IGNvbnRpZ3VvdXMgaG91cnMsIG9yIDQ4IGhvdXJzIHRoYXQgYXJlIG5vdCBuZWNlc3NhcmlseSBjb250aWd1b3VzIGhvdXJzLiBJIHdpbGwgZmluZCA0OCBub3QtbmVjZXNzYXJpbHkgY29udGlndW91cyBob3Vycy4gVGhhdCBkZWZpbml0aW9uIG1ha2VzIGJldHRlciB1c2Ugb2YgdGhlIG1ldGhvZHMgaW50cm9kdWNlZCBpbiB0aGlzIHNlY3Rpb24gYW5kIGNoYXB0ZXIuDQoNCjQuIFdoYXQgaXMgdGhlIHVuaXQgb2YgYW5hbHlzaXM/IEFsdGhvdWdoIHRoZSBxdWVzdGlvbiBtZW50aW9ucyBvbmx5IGhvdXJzLCBJIHdpbGwgdXNlIGFpcnBvcnQgaG91cnMuIFRoZSB3ZWF0aGVyIGRhdGFzZXQgaGFzIGFuIG9ic2VydmF0aW9uIGZvciBlYWNoIGFpcnBvcnQgZm9yIGVhY2ggaG91ci4gU2luY2UgYWxsIHRoZSBkZXBhcnR1cmUgYWlycG9ydHMgYXJlIGluIHRoZSB2aWNpbml0eSBvZiBOZXcgWW9yayBDaXR5LCB0aGVpciB3ZWF0aGVyIHNob3VsZCBiZSBzaW1pbGFyLCBpdCB3aWxsIG5vdCBiZSB0aGUgc2FtZS4NCg0KRmlyc3QsIEkgbmVlZCB0byBmaW5kIHRoZSA0OCBob3VycyB3aXRoIHRoZSB3b3JzdCBkZWxheXMuIEkgZ3JvdXAgZmxpZ2h0cyBieSBob3VyIG9mIHNjaGVkdWxlZCBkZXBhcnR1cmUgdGltZSBhbmQgY2FsY3VsYXRlIHRoZSBhdmVyYWdlIGRlbGF5LiBUaGVuIEkgc2VsZWN0IHRoZSA0OCBvYnNlcnZhdGlvbnMgKGhvdXJzKSB3aXRoIHRoZSBoaWdoZXN0IGF2ZXJhZ2UgZGVsYXkuDQoNCmBgYHtyfQ0Kd29yc3RfaG91cnMgPC0gZmxpZ2h0cyAlPiUNCiAgbXV0YXRlKGhvdXIgPSBzY2hlZF9kZXBfdGltZSAlLyUgMTAwKSAlPiUNCiAgZ3JvdXBfYnkob3JpZ2luLCB5ZWFyLCBtb250aCwgZGF5LCBob3VyKSAlPiUNCiAgc3VtbWFyaXNlKGRlcF9kZWxheSA9IG1lYW4oZGVwX2RlbGF5LCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBhcnJhbmdlKGRlc2MoZGVwX2RlbGF5KSkgJT4lDQogIHNsaWNlKDE6NDgpDQpgYGANCg0KVGhlbiBJIGNhbiB1c2UgYHNlbWlfam9pbigpYCB0byBnZXQgdGhlIHdlYXRoZXIgZm9yIHRoZXNlIGhvdXJzLg0KDQpgYGB7cn0NCndlYXRoZXJfbW9zdF9kZWxheWVkIDwtIHNlbWlfam9pbih3ZWF0aGVyLCB3b3JzdF9ob3VycywNCiAgYnkgPSBjKA0KICAgICJvcmlnaW4iLCAieWVhciIsDQogICAgIm1vbnRoIiwgImRheSIsICJob3VyIg0KICApDQopDQpgYGANCg0KRm9yIHdlYXRoZXIsIEnigJlsbCBmb2N1cyBvbiBwcmVjaXBpdGF0aW9uLCB3aW5kIHNwZWVkLCBhbmQgdGVtcGVyYXR1cmUuIEkgd2lsbCBkaXNwbGF5IHRoZXNlIGluIGJvdGggYSB0YWJsZSBhbmQgYSBwbG90Lg0KDQpNYW55IG9mIHRoZXNlIG9ic2VydmF0aW9ucyBoYXZlIGEgaGlnaGVyIHRoYW4gYXZlcmFnZSB3aW5kIHNwZWVkICgxMCBtcGgpIG9yIHNvbWUgcHJlY2lwaXRhdGlvbi4gSG93ZXZlciwgSSB3b3VsZCBoYXZlIGV4cGVjdGVkIHRoZSB3ZWF0aGVyIGZvciB0aGUgaG91cnMgd2l0aCB0aGUgd29yc3QgZGVsYXlzIHRvIGJlIG11Y2ggd29yc2UuDQoNCmBgYHtyfQ0Kc2VsZWN0KHdlYXRoZXJfbW9zdF9kZWxheWVkLCB0ZW1wLCB3aW5kX3NwZWVkLCBwcmVjaXApICU+JQ0KICBwcmludChuID0gNDgpDQpnZ3Bsb3Qod2VhdGhlcl9tb3N0X2RlbGF5ZWQsIGFlcyh4ID0gcHJlY2lwLCB5ID0gd2luZF9zcGVlZCwgY29sb3IgPSB0ZW1wKSkgKw0KICBnZW9tX3BvaW50KCkNCmBgYA0KDQpJdOKAmXMgaGFyZCB0byBzYXkgbXVjaCBtb3JlIHRoYW4gdGhhdCB3aXRob3V0IHVzaW5nIHRoZSB0b29scyBmcm9tIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMgc2VjdGlvbiB0byBsb29rIGZvciBjb3ZhcmlhdGlvbiBiZXR3ZWVuIHdlYXRoZXIgYW5kIGZsaWdodCBkZWxheXMgdXNpbmcgYWxsIGZsaWdodHMuIEltcGxpY2l0bHkgaW4gbXkgaW5mb3JtYWwgYW5hbHlzaXMgb2YgdHJlbmRzIGluIHdlYXRoZXIgdXNpbmcgb25seSB0aGUgNDggaG91cnMgd2l0aCB0aGUgd29yc3QgZGVsYXlzLCBJIHdhcyBjb21wYXJpbmcgdGhlIHdlYXRoZXIgaW4gdGhlc2UgaG91cnMgdG8gc29tZSBiZWxpZWYgSSBoYWQgYWJvdXQgd2hhdCBjb25zdGl0dXRlcyDigJxub3JtYWzigJ0gb3Ig4oCcZ29vZOKAnSB3ZWF0aGVyLiBJdCB3b3VsZCBiZSBiZXR0ZXIgdG8gYWN0dWFsbHkgdXNlIGRhdGEgdG8gbWFrZSB0aGF0IGNvbXBhcmlzb24uDQoNCiMjIyA1LiBXaGF0IGRvZXMgYGFudGlfam9pbihmbGlnaHRzLCBhaXJwb3J0cywgYnkgPSBjKCJkZXN0IiA9ICJmYWEiKSlgIHRlbGwgeW91PyBXaGF0IGRvZXMgYGFudGlfam9pbihhaXJwb3J0cywgZmxpZ2h0cywgYnkgPSBjKCJmYWEiID0gImRlc3QiKSlgIHRlbGwgeW91Pw0KDQpUaGUgZXhwcmVzc2lvbiBgYW50aV9qb2luKGZsaWdodHMsIGFpcnBvcnRzLCBieSA9IGMoImRlc3QiID0gImZhYSIpKWAgcmV0dXJucyB0aGUgZmxpZ2h0cyB0aGF0IHdlbnQgdG8gYW4gYWlycG9ydCB0aGF0IGlzIG5vdCBpbiB0aGUgRkFBIGxpc3Qgb2YgZGVzdGluYXRpb25zLiBTaW5jZSB0aGUgRkFBIGxpc3Qgb25seSBjb250YWlucyBkb21lc3RpYyBhaXJwb3J0cywgdGhlc2UgYXJlIGxpa2VseSBmb3JlaWduIGZsaWdodHMuDQoNClRoZSBleHByZXNzaW9uIGBhbnRpX2pvaW4oYWlycG9ydHMsIGZsaWdodHMsIGJ5ID0gYygiZmFhIiA9ICJkZXN0IikpYCByZXR1cm5zIHRoZSBVUyBhaXJwb3J0cyB0aGF0IHdlcmUgbm90IHRoZSBkZXN0aW5hdGlvbiBvZiBhbnkgZmxpZ2h0IGluIHRoZSBkYXRhLiBTaW5jZSB0aGUgZGF0YSBjb250YWlucyBhbGwgZmxpZ2h0cyBmcm9tIE5ldyBZb3JrIENpdHkgYWlycG9ydHMsIHRoaXMgaXMgYWxzbyB0aGUgbGlzdCBvZiBVUyBhaXJwb3J0cyB0aGF0IGRpZCBub3QgaGF2ZSBhIG5vbnN0b3AgZmxpZ2h0IGZyb20gTmV3IFlvcmsgQ2l0eSBpbiAyMDEzLg0KDQojIyMgNi4gWW91IG1pZ2h0IGV4cGVjdCB0aGF0IHRoZXJl4oCZcyBhbiBpbXBsaWNpdCByZWxhdGlvbnNoaXAgYmV0d2VlbiBwbGFuZSBhbmQgYWlybGluZSwgYmVjYXVzZSBlYWNoIHBsYW5lIGlzIGZsb3duIGJ5IGEgc2luZ2xlIGFpcmxpbmUuIENvbmZpcm0gb3IgcmVqZWN0IHRoaXMgaHlwb3RoZXNpcyB1c2luZyB0aGUgdG9vbHMgeW914oCZdmUgbGVhcm5lZCBhYm92ZS4NCg0KQXQgZWFjaCBwb2ludCBpbiB0aW1lLCBlYWNoIHBsYW5lIGlzIGZsb3duIGJ5IGEgc2luZ2xlIGFpcmxpbmUuIEhvd2V2ZXIsIGEgcGxhbmUgY2FuIGJlIHNvbGQgYW5kIGZseSBmb3IgbXVsdGlwbGUgYWlybGluZXMuIExvZ2ljYWxseSwgaXQgaXMgcG9zc2libGUgdGhhdCBhIHBsYW5lIGNhbiBmbHkgZm9yIG11bHRpcGxlIGFpcmxpbmVzIG92ZXIgdGhlIGNvdXJzZSBvZiBpdHMgbGlmZXRpbWUuIEJ1dCwgaXQgaXMgbm90IG5lY2Vzc2FyaWx5IHRoZSBjYXNlIHRoYXQgYSBwbGFuZSB3aWxsIGZseSBmb3IgbW9yZSB0aGFuIG9uZSBhaXJsaW5lIGluIHRoaXMgZGF0YSwgZXNwZWNpYWxseSBzaW5jZSBpdCBjb21wcmlzZXMgb25seSBhIHllYXIgb2YgZGF0YS4gU28gbGV04oCZcyBjaGVjayB0byBzZWUgaWYgdGhlcmUgYXJlIGFueSBwbGFuZXMgaW4gdGhlIGRhdGEgZmxldyBmb3IgbXVsdGlwbGUgYWlybGluZXMuDQoNCkZpcnN0LCBmaW5kIGFsbCBkaXN0aW5jdCBhaXJsaW5lLCBwbGFuZSBjb21iaW5hdGlvbnMuDQoNCmBgYHtyfQ0KcGxhbmVzX2NhcnJpZXJzIDwtDQogIGZsaWdodHMgJT4lDQogIGZpbHRlcighaXMubmEodGFpbG51bSkpICU+JQ0KICBkaXN0aW5jdCh0YWlsbnVtLCBjYXJyaWVyKQ0KYGBgDQoNClRoZSBudW1iZXIgb2YgcGxhbmVzIHRoYXQgaGF2ZSBmbG93biBmb3IgbW9yZSB0aGFuIG9uZSBhaXJsaW5lIGFyZSB0aG9zZSB0YWlsbnVtIHRoYXQgYXBwZWFyIG1vcmUgdGhhbiBvbmNlIGluIHRoZSBwbGFuZXNfY2FycmllcnMgZGF0YS4NCg0KYGBge3J9DQpwbGFuZXNfY2FycmllcnMgJT4lDQogIGNvdW50KHRhaWxudW0pICU+JQ0KICBmaWx0ZXIobiA+IDEpICU+JQ0KICBucm93KCkNCmBgYA0KDQpUaGUgbmFtZXMgb2YgYWlybGluZXMgYXJlIGVhc2llciB0byB1bmRlcnN0YW5kIHRoYW4gdGhlIHR3by1sZXR0ZXIgY2FycmllciBjb2Rlcy4gVGhlIGFpcmxpbmVzIGRhdGEgZnJhbWUgY29udGFpbnMgdGhlIG5hbWVzIG9mIHRoZSBhaXJsaW5lcy4NCg0KYGBge3J9DQpjYXJyaWVyX3RyYW5zZmVyX3RibCA8LSBwbGFuZXNfY2FycmllcnMgJT4lDQogICMga2VlcCBvbmx5IHBsYW5lcyB3aGljaCBoYXZlIGZsb3duIGZvciBtb3JlIHRoYW4gb25lIGFpcmxpbmUNCiAgZ3JvdXBfYnkodGFpbG51bSkgJT4lDQogIGZpbHRlcihuKCkgPiAxKSAlPiUNCiAgIyBqb2luIHdpdGggYWlybGluZXMgdG8gZ2V0IGFpcmxpbmUgbmFtZXMNCiAgbGVmdF9qb2luKGFpcmxpbmVzLCBieSA9ICJjYXJyaWVyIikgJT4lDQogIGFycmFuZ2UodGFpbG51bSwgY2FycmllcikNCg0KY2Fycmllcl90cmFuc2Zlcl90YmwNCmBgYA==