Today, we’ll work on just one (or 1.5) topic(s).
In labs to date, I’ve been very irresponsible in my pedagogical approach, using a ton of dplyr
verbs without going through this technology. That’s rectified today.
Then, we’ll touch on simple example of reshaping.
First, we’ll download some data on the Billboard Hot 100, gathering all chart locations for popular music in the United States, between June 1958
If you’d like to be sure you have the right libraries, first install them as follows.
install.packages(c("plyr", "tidyverse", "magrittr", "lubridate"))
(No need to install these packages if you’ve successfully used them on your machine previously.)
Now we’ll load the packages, and the data:
library(plyr)
library(tidyverse)
library(magrittr)
library(lubridate)
d1 <- "https://github.com/thomasjwood/ps4160/raw/master/billboard_58_21.rds" %>%
url %>%
gzcon %>%
readRDS
To make sure everything loaded ok
d1$week_id %>% year %>% table
Ok–dplyr
The dplyr
verbs
dplyr
is principally a psychological innovation. dplyr
is an attempt to abstract away the computational details of what you intend to do with some data matrix, and simply express your intention in the most concise possible form. Do you want to
• Look at only some specific cases (ie, what are is the typical chart performance for songs released in the summer time, among songs with high spotify-scored ‘danceability’ scores?)?
• Compute a summary statistic for some group of cases (ie, compare total weeks in the charts for songs that debut in the top 10, and those songs which debuted outside of the top-10?)
• Sort rows, by some characteristic inside the data set (ie, by total weeks spent in the top 100?)
While there are a million competitors which do precisely this–SQL, Pandas, Spark, data.table, etc… but for people with minimal technical background, I think this is the most elegant, tractable approach.
Here are the 5 major dplyr
verbs:

Here is a gratuitous textual recapitulation:
filter
You pass a set of logical conditions, and filter
only returns the rows which match
select
You pass a set of logical conditions, and select
returns only those columns which match
mutate
You generate a new variable, often through computation
summarize
You replace separate dis-aggregated rows with a summary row, where each row summarizes (aggregates) the separate data in the old table.
arrange
You sort rows
Omitted here is the equally useful group_by
, which allows you to perform separate calculations for each group of rows.
Maybe some of these processes will be more tractable through some simple examples?
filter
examples
Let’s do some filtering. If we want to only look at songs which hit #1:
d1 %>%
filter(
week_position == 1
)
Or songs that debuted at #1
d1 %>%
filter(
week_position == 1 &
weeks_on_chart == 1
)
Or songs that debuted at #1 this year
d1 %>%
filter(
week_position == 1 &
weeks_on_chart == 1 &
week_id %>%
year == 2021
)
Or songs that debuted at #1 in 2021, 2020, or2019
d1 %>%
filter(
week_position == 1 &
weeks_on_chart == 1 &
(
week_id %>%
year == 2021 |
week_id %>%
year == 2020 |
week_id %>%
year == 2019
)
)
So this vertical bar operator |
is used as the logical operator “or”.
In the above, the parentheses are very important, since they string together:
the requirement to have the week_position
equal 1, AND
the requirement to have the week_id equal 1, AND
the year equal 2021, OR
the year equal 2020, OR
the year equal 2019
select
examples
This is used to select
only a small number of columns. This is a very useful utility for when we join between tables, and for reshaping.
We can select
using bare variable names
d1 %>%
select(
week_id, week_position, song, performer
)
Or we can select
using values which correspond to column positions
d1 %>%
select(
2:5, 20:22
)
The range operator :
works for names too:
d1 %>%
select(
week_id:song, danceability:loudness
)
There are also very important convenience functions, which allow you to make selections when there’s a pattern of names you’d like to select. Examples include contains()
, ends_with()
, starts_with(),
or everything()
.
d1 %>%
select(
starts_with("week_"),
ends_with("_position")
)
summarize
examples
Imagine we want to know how many artists have had top ten debut songs, by year. We’re not interested (for the moment) in all the specific variation encapsulated inside each separate row. Instead, we just want a single numeric summary for each year group.
d1 %>%
filter(
weeks_on_chart %>%
equals(1) &
week_position <= 10
) %>%
group_by(
week_id %>% year
) %>%
summarize(
num_performers = performer %>%
unique %>%
length
)
Or imagine we want to know the songs which spent the most weeks on the chart
d1 %>%
group_by(performer, song) %>%
summarize(
week_id = week_id %>% min,
tote_weeks = weeks_on_chart %>% max
)
Which should return
performer song week_id tote_weeks
<chr> <chr> <date> <dbl>
1 'N Sync (God Must Have Spent) A Little More Time On You 1998-12-05 22
2 'N Sync Bye Bye Bye 2000-01-29 23
3 'N Sync Gone 2001-09-22 24
4 'N Sync I Drive Myself Crazy 1999-04-24 12
5 'N Sync I Want You Back 1998-03-07 24
6 'N Sync It's Gonna Be Me 2000-05-06 25
7 'N Sync Pop 2001-06-02 15
8 'N Sync Tearin' Up My Heart 1998-12-05 1
9 'N Sync This I Promise You 2000-09-30 26
10 'N Sync & Gloria Estefan Music Of My Heart 1999-09-04 20
Oh no–this table is not very nicely sorted. Instead, we need to do:
d1 %>%
group_by(performer, song) %>%
summarize(
week_id = week_id %>% min,
tote_weeks = weeks_on_chart %>% max
) %>%
arrange(
desc(tote_weeks)
)
which is
performer song week_id tote_weeks
<chr> <chr> <date> <dbl>
1 Imagine Dragons Radioactive 2012-08-18 87
2 AWOLNATION Sail 2011-09-03 79
3 Jason Mraz I'm Yours 2008-05-03 76
4 The Weeknd Blinding Lights 2019-12-14 76
5 LeAnn Rimes How Do I Live 1997-06-21 69
6 LMFAO Featuring Lauren Bennett & GoonRock Party Rock Anthem 2011-02-12 68
7 OneRepublic Counting Stars 2013-07-06 68
8 Adele Rolling In The Deep 2010-12-25 65
9 Jewel Foolish Games/You Were Meant For Me 1996-11-30 65
10 Carrie Underwood Before He Cheats 2006-09-16 64
arrange
examples
Our final verb provides the ability to sort rows (we used it above to get a sense of the songs which persisted the longest on the Billboard 100.) For instance, which are the most “danceable” songs?
d1 %>%
group_by(song_id) %>%
slice(1) %>%
ungroup %>%
arrange(desc(danceability)) %>%
select(week_id:performer)
should return
week_id week_position song performer
<date> <dbl> <chr> <chr>
1 1989-03-04 60 Funky Cold Medina Tone-Loc
2 2007-12-15 92 Go Girl Pitbull Featuring Trina & Young Bo$$
3 2017-03-04 88 Cash Me Outside (CashMeOutside) DJ Suede The Remix God
4 2010-05-22 74 Ice Ice Baby Glee Cast
5 1984-06-30 30 State of Shock The Jacksons
6 1990-09-08 72 Ice Ice Baby Vanilla Ice
7 2019-08-17 82 Uno Ambjaay
8 2019-08-31 32 Bad Bad Bad Young Thug Featuring Lil Baby
9 1983-01-15 90 Betcha She Don't Love You Evelyn King
10 2002-07-13 95 In Da Wind Trick Daddy Featuring Cee-Lo & Big Boi
# ... with 29,379 more rows
pivot_longer
and pivot_wider
examples
This is a tough one to communicate to students. The principle things to keep in mind about reshaping
No data are introduced or omitted. Every pair of cell coordinate–variable names and the ID vector in the example below, remain unchanged in each form of the data–whether it’s wide (on the left) or wide (on the right.)
The most useful typical example of this process is when you want to do a bunch of calculations on separate variables. Now, you might be inclined/equipped to write a clever for loop, which operates on each separate variable. But if you don’t want to learn how to do this, instead, just encode the difference variable names, themselves, in a variable. That’s what we’re doing in the example below.

Let’s think of an example–which of the song attributes are most strongly related to a song’s peak position?
d1 %>%
group_by(song_id) %>%
slice(
peak_position %>%
which.min
) %>%
select(
song, performer, song_id, peak_position,
danceability:tempo
) %>%
pivot_longer(
cols = danceability:tempo,
names_to = "attr",
values_to = "val",
values_drop_na = T
) %>%
group_by(
attr
) %>%
summarise(
rel = cor(peak_position, val)
) %>%
arrange(
desc(rel)
)
pivot_longer
is saying:
apply the pivot longer mechanism to the variables between danceability
and tempo
call my vector of variable names “attr
”
call my vector of variable values “val
”
Then we compute the correlation for each gathered variable, comparing that variable to the overall margin
Gosh how did I make a whole set of lab notes without including a single plot!?

Let this be a lesson to us all – de-emphasize speechiness to maximize the Billboard performance of your next single.
LS0tDQp0aXRsZTogIlBTIDQxNjAgTGFiIDMiDQphdXRob3I6ICJUaG9tYXMgV29vZCwgUG9saXRpY2FsIFNjaWVuY2UtLU9oaW8gU3RhdGUsIHdvb2QuMTA4MEBvc3UuZWR1Ig0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRoZW1lOiBjZXJ1bGVhbg0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KLS0tDQoNClRvZGF5LCB3ZSdsbCB3b3JrIG9uIGp1c3Qgb25lIChvciAxLjUpIHRvcGljKHMpLg0KDQoxLiAgSW4gbGFicyB0byBkYXRlLCBJJ3ZlIGJlZW4gdmVyeSBpcnJlc3BvbnNpYmxlIGluIG15IHBlZGFnb2dpY2FsIGFwcHJvYWNoLCB1c2luZyBhIHRvbiBvZiBgZHBseXJgIHZlcmJzIHdpdGhvdXQgZ29pbmcgdGhyb3VnaCB0aGlzIHRlY2hub2xvZ3kuIFRoYXQncyByZWN0aWZpZWQgdG9kYXkuDQoNCjIuICBUaGVuLCB3ZSdsbCB0b3VjaCBvbiBzaW1wbGUgZXhhbXBsZSBvZiByZXNoYXBpbmcuDQoNCkZpcnN0LCB3ZSdsbCBkb3dubG9hZCBzb21lIGRhdGEgb24gdGhlIFtCaWxsYm9hcmQgSG90IDEwMF0oaHR0cHM6Ly93d3cuYmlsbGJvYXJkLmNvbS9jaGFydHMvaG90LTEwMC8pLCBnYXRoZXJpbmcgYWxsIGNoYXJ0IGxvY2F0aW9ucyBmb3IgcG9wdWxhciBtdXNpYyBpbiB0aGUgVW5pdGVkIFN0YXRlcywgYmV0d2VlbiBKdW5lIDE5NTgNCg0KSWYgeW91J2QgbGlrZSB0byBiZSBzdXJlIHlvdSBoYXZlIHRoZSByaWdodCBsaWJyYXJpZXMsIGZpcnN0IGluc3RhbGwgdGhlbSBhcyBmb2xsb3dzLg0KDQpgYGAgeyNpbnN0YWxsX3BhY2thZ2VzIC5yfQ0KaW5zdGFsbC5wYWNrYWdlcyhjKCJwbHlyIiwgInRpZHl2ZXJzZSIsICJtYWdyaXR0ciIsICJsdWJyaWRhdGUiKSkNCmBgYA0KDQooTm8gbmVlZCB0byBpbnN0YWxsIHRoZXNlIHBhY2thZ2VzIGlmIHlvdSd2ZSBzdWNjZXNzZnVsbHkgdXNlZCB0aGVtIG9uIHlvdXIgbWFjaGluZSBwcmV2aW91c2x5LilcDQoNCk5vdyB3ZSdsbCBsb2FkIHRoZSBwYWNrYWdlcywgYW5kIHRoZSBkYXRhOg0KDQpgYGAgeyNsb2FkX2RhdGEgLnIgLlJ9DQpsaWJyYXJ5KHBseXIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobWFncml0dHIpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCg0KZDEgPC0gImh0dHBzOi8vZ2l0aHViLmNvbS90aG9tYXNqd29vZC9wczQxNjAvcmF3L21hc3Rlci9iaWxsYm9hcmRfNThfMjEucmRzIiAlPiUNCiAgdXJsICU+JQ0KICBnemNvbiAlPiUNCiAgcmVhZFJEUyANCmBgYA0KDQpUbyBtYWtlIHN1cmUgZXZlcnl0aGluZyBsb2FkZWQgb2sNCg0KYGBgIHINCmQxJHdlZWtfaWQgJT4lIHllYXIgJT4lIHRhYmxlDQpgYGANCg0KT2stLWBkcGx5cmANCg0KIyMgVGhlIGBkcGx5cmAgdmVyYnMNCg0KYGRwbHlyYCBpcyBwcmluY2lwYWxseSBhIHBzeWNob2xvZ2ljYWwgaW5ub3ZhdGlvbi4gYGRwbHlyYCBpcyBhbiBhdHRlbXB0IHRvIGFic3RyYWN0IGF3YXkgdGhlIGNvbXB1dGF0aW9uYWwgZGV0YWlscyBvZiB3aGF0IHlvdSBpbnRlbmQgdG8gZG8gd2l0aCBzb21lIGRhdGEgbWF0cml4LCBhbmQgc2ltcGx5IGV4cHJlc3MgeW91ciBpbnRlbnRpb24gaW4gdGhlIG1vc3QgY29uY2lzZSBwb3NzaWJsZSBmb3JtLiBEbyB5b3Ugd2FudCB0bw0KDQrigKIgTG9vayBhdCBvbmx5IHNvbWUgc3BlY2lmaWMgY2FzZXMgKGllLCB3aGF0IGFyZSBpcyB0aGUgdHlwaWNhbCBjaGFydCBwZXJmb3JtYW5jZSBmb3Igc29uZ3MgcmVsZWFzZWQgaW4gdGhlIHN1bW1lciB0aW1lLCBhbW9uZyBzb25ncyB3aXRoIGhpZ2ggc3BvdGlmeS1zY29yZWQgJypkYW5jZWFiaWxpdHkqJyBzY29yZXM/KT8NCg0K4oCiIENvbXB1dGUgYSBzdW1tYXJ5IHN0YXRpc3RpYyBmb3Igc29tZSAqZ3JvdXAqIG9mIGNhc2VzIChpZSwgY29tcGFyZSB0b3RhbCB3ZWVrcyBpbiB0aGUgY2hhcnRzIGZvciBzb25ncyB0aGF0IGRlYnV0IGluIHRoZSB0b3AgMTAsIGFuZCB0aG9zZSBzb25ncyB3aGljaCBkZWJ1dGVkIG91dHNpZGUgb2YgdGhlIHRvcC0xMD8pDQoNCuKAoiBTb3J0IHJvd3MsIGJ5IHNvbWUgY2hhcmFjdGVyaXN0aWMgaW5zaWRlIHRoZSBkYXRhIHNldCAoaWUsIGJ5IHRvdGFsIHdlZWtzIHNwZW50IGluIHRoZSB0b3AgMTAwPykNCg0KV2hpbGUgdGhlcmUgYXJlIGEgbWlsbGlvbiBjb21wZXRpdG9ycyB3aGljaCBkbyBwcmVjaXNlbHkgdGhpcy0tU1FMLCBQYW5kYXMsIFNwYXJrLCBkYXRhLnRhYmxlLCBldGMuLi4gYnV0IGZvciBwZW9wbGUgd2l0aCBtaW5pbWFsIHRlY2huaWNhbCBiYWNrZ3JvdW5kLCBJIHRoaW5rIHRoaXMgaXMgdGhlIG1vc3QgZWxlZ2FudCwgdHJhY3RhYmxlIGFwcHJvYWNoLg0KDQpIZXJlIGFyZSB0aGUgNSBtYWpvciBgZHBseXJgIHZlcmJzOg0KDQohW10oZml2ZV92ZXJiX2RpYWcuanBnKQ0KDQpIZXJlIGlzIGEgZ3JhdHVpdG91cyB0ZXh0dWFsIHJlY2FwaXR1bGF0aW9uOg0KDQoqKmBmaWx0ZXJgKiogWW91IHBhc3MgYSBzZXQgb2YgKmxvZ2ljYWwgY29uZGl0aW9ucyosIGFuZCAqKmBmaWx0ZXJgKiogb25seSByZXR1cm5zIHRoZSByb3dzIHdoaWNoIG1hdGNoDQoNCioqYHNlbGVjdGAqKiBZb3UgcGFzcyBhIHNldCBvZiAqbG9naWNhbCBjb25kaXRpb25zKiwgYW5kICoqYHNlbGVjdGAqKiByZXR1cm5zIG9ubHkgdGhvc2UgY29sdW1ucyB3aGljaCBtYXRjaA0KDQoqKmBtdXRhdGVgKiogWW91IGdlbmVyYXRlIGEgbmV3IHZhcmlhYmxlLCBvZnRlbiB0aHJvdWdoIGNvbXB1dGF0aW9uDQoNCioqYHN1bW1hcml6ZWAqKiBZb3UgcmVwbGFjZSBzZXBhcmF0ZSBkaXMtYWdncmVnYXRlZCByb3dzIHdpdGggYSBzdW1tYXJ5IHJvdywgd2hlcmUgZWFjaCByb3cgc3VtbWFyaXplcyAoYWdncmVnYXRlcykgdGhlIHNlcGFyYXRlIGRhdGEgaW4gdGhlIG9sZCB0YWJsZS4NCg0KKipgYXJyYW5nZWAqKiBZb3Ugc29ydCByb3dzDQoNCk9taXR0ZWQgaGVyZSBpcyB0aGUgZXF1YWxseSB1c2VmdWwgKipgZ3JvdXBfYnlgKiogLCB3aGljaCBhbGxvd3MgeW91IHRvIHBlcmZvcm0gc2VwYXJhdGUgY2FsY3VsYXRpb25zIGZvciBlYWNoIGdyb3VwIG9mIHJvd3MuDQoNCk1heWJlIHNvbWUgb2YgdGhlc2UgcHJvY2Vzc2VzIHdpbGwgYmUgbW9yZSB0cmFjdGFibGUgdGhyb3VnaCBzb21lIHNpbXBsZSBleGFtcGxlcz8NCg0KIyMgYGZpbHRlcmAgZXhhbXBsZXMNCg0KTGV0J3MgZG8gc29tZSBmaWx0ZXJpbmcuIElmIHdlIHdhbnQgdG8gb25seSBsb29rIGF0IHNvbmdzIHdoaWNoIGhpdCAjMToNCg0KYGBgIHINCmQxICU+JSANCiAgZmlsdGVyKA0KICAgIHdlZWtfcG9zaXRpb24gPT0gMQ0KICApDQpgYGANCg0KT3Igc29uZ3MgdGhhdCBkZWJ1dGVkIGF0ICMxDQoNCmBgYCByDQpkMSAlPiUgDQogIGZpbHRlcigNCiAgICB3ZWVrX3Bvc2l0aW9uID09IDEgJg0KICAgIHdlZWtzX29uX2NoYXJ0ID09IDENCiAgKQ0KYGBgDQoNCk9yIHNvbmdzIHRoYXQgZGVidXRlZCBhdCAjMSB0aGlzIHllYXINCg0KYGBgIHINCmQxICU+JSANCiAgZmlsdGVyKA0KICAgIHdlZWtfcG9zaXRpb24gPT0gMSAmDQogICAgd2Vla3Nfb25fY2hhcnQgPT0gMSAmDQogICAgd2Vla19pZCAlPiUgDQogICAgICB5ZWFyID09IDIwMjENCiAgKQ0KYGBgDQoNCk9yIHNvbmdzIHRoYXQgZGVidXRlZCBhdCAjMSBpbiAyMDIxLCAyMDIwLCAqKipvcioqKjIwMTkNCg0KYGBgIHINCmQxICU+JSANCiAgZmlsdGVyKA0KICAgIHdlZWtfcG9zaXRpb24gPT0gMSAmDQogICAgd2Vla3Nfb25fY2hhcnQgPT0gMSAmDQogICAgICAoDQogICAgICAgIHdlZWtfaWQgJT4lDQogICAgICAgICAgeWVhciA9PSAyMDIxIHwNCiAgICAgICAgd2Vla19pZCAlPiUNCiAgICAgICAgICB5ZWFyID09IDIwMjAgfA0KICAgICAgICB3ZWVrX2lkICU+JQ0KICAgICAgICAgIHllYXIgPT0gMjAxOQ0KICAgICAgKQ0KICAgICkNCmBgYA0KDQpTbyB0aGlzIHZlcnRpY2FsIGJhciBvcGVyYXRvciBgfGAgaXMgdXNlZCBhcyB0aGUgbG9naWNhbCBvcGVyYXRvciAiKm9yKiIuDQoNCkluIHRoZSBhYm92ZSwgdGhlIHBhcmVudGhlc2VzIGFyZSB2ZXJ5IGltcG9ydGFudCwgc2luY2UgdGhleSBzdHJpbmcgdG9nZXRoZXI6DQoNCi0gICB0aGUgcmVxdWlyZW1lbnQgdG8gaGF2ZSB0aGUgYHdlZWtfcG9zaXRpb25gIGVxdWFsIDEsICoqQU5EKioNCg0KLSAgIHRoZSByZXF1aXJlbWVudCB0byBoYXZlIHRoZSAqKndlZWtfaWQqKiBlcXVhbCAxLCAqKkFORCoqDQoNCiAgICAtICAgdGhlIHllYXIgZXF1YWwgMjAyMSwgKipPUioqDQoNCiAgICAtICAgdGhlIHllYXIgZXF1YWwgMjAyMCwgKipPUioqDQoNCiAgICAtICAgdGhlIHllYXIgZXF1YWwgMjAxOQ0KDQojIyBgc2VsZWN0YCBleGFtcGxlcw0KDQpUaGlzIGlzIHVzZWQgdG8gYHNlbGVjdGAgb25seSBhIHNtYWxsIG51bWJlciBvZiBjb2x1bW5zLiBUaGlzIGlzIGEgdmVyeSB1c2VmdWwgdXRpbGl0eSBmb3Igd2hlbiB3ZSBqb2luIGJldHdlZW4gdGFibGVzLCBhbmQgZm9yIHJlc2hhcGluZy4NCg0KV2UgY2FuIGBzZWxlY3RgIHVzaW5nIGJhcmUgdmFyaWFibGUgbmFtZXMNCg0KYGBgIHINCmQxICU+JSANCiAgc2VsZWN0KA0KICAgIHdlZWtfaWQsIHdlZWtfcG9zaXRpb24sIHNvbmcsIHBlcmZvcm1lcg0KICApDQpgYGANCg0KT3Igd2UgY2FuIGBzZWxlY3RgIHVzaW5nIHZhbHVlcyB3aGljaCBjb3JyZXNwb25kIHRvIGNvbHVtbiBwb3NpdGlvbnMNCg0KYGBgIHINCmQxICU+JSANCiAgc2VsZWN0KA0KICAgIDI6NSwgMjA6MjINCiAgKQ0KYGBgDQoNClRoZSByYW5nZSBvcGVyYXRvciBgOmAgd29ya3MgZm9yIG5hbWVzIHRvbzoNCg0KYGBgIHINCmQxICU+JSANCiAgc2VsZWN0KA0KICAgIHdlZWtfaWQ6c29uZywgZGFuY2VhYmlsaXR5OmxvdWRuZXNzDQogICAgKQ0KYGBgDQoNClRoZXJlIGFyZSBhbHNvIHZlcnkgaW1wb3J0YW50ICpjb252ZW5pZW5jZSBmdW5jdGlvbnMqLCB3aGljaCBhbGxvdyB5b3UgdG8gbWFrZSBzZWxlY3Rpb25zIHdoZW4gdGhlcmUncyBhICpwYXR0ZXJuKiBvZiBuYW1lcyB5b3UnZCBsaWtlIHRvIHNlbGVjdC4gRXhhbXBsZXMgaW5jbHVkZSBgY29udGFpbnMoKWAsIGBlbmRzX3dpdGgoKWAsIGBzdGFydHNfd2l0aCgpLGAgb3IgYGV2ZXJ5dGhpbmcoKWAuDQoNCmBgYCByDQpkMSAlPiUgDQogIHNlbGVjdCgNCiAgICBzdGFydHNfd2l0aCgid2Vla18iKSwgDQogICAgZW5kc193aXRoKCJfcG9zaXRpb24iKQ0KICAgICkNCmBgYA0KDQojIyBgc3VtbWFyaXplYCBleGFtcGxlcw0KDQpJbWFnaW5lIHdlIHdhbnQgdG8ga25vdyBob3cgbWFueSBhcnRpc3RzIGhhdmUgaGFkIHRvcCB0ZW4gZGVidXQgc29uZ3MsIGJ5IHllYXIuIFdlJ3JlIG5vdCBpbnRlcmVzdGVkIChmb3IgdGhlIG1vbWVudCkgaW4gYWxsIHRoZSBzcGVjaWZpYyB2YXJpYXRpb24gZW5jYXBzdWxhdGVkIGluc2lkZSBlYWNoIHNlcGFyYXRlIHJvdy4gSW5zdGVhZCwgd2UganVzdCB3YW50IGEgc2luZ2xlIG51bWVyaWMgc3VtbWFyeSBmb3IgZWFjaCB5ZWFyIGdyb3VwLg0KDQpgYGAgcg0KZDEgJT4lIA0KICBmaWx0ZXIoDQogICAgd2Vla3Nfb25fY2hhcnQgJT4lIA0KICAgICAgZXF1YWxzKDEpICYgDQogICAgd2Vla19wb3NpdGlvbiA8PSAxMA0KICAgICkgJT4lIA0KICBncm91cF9ieSgNCiAgICB3ZWVrX2lkICU+JSB5ZWFyDQogICAgKSAlPiUgDQogIHN1bW1hcml6ZSgNCiAgICBudW1fcGVyZm9ybWVycyA9IHBlcmZvcm1lciAlPiUgDQogICAgICB1bmlxdWUgJT4lIA0KICAgICAgbGVuZ3RoDQogICkNCmBgYA0KDQpPciBpbWFnaW5lIHdlIHdhbnQgdG8ga25vdyB0aGUgc29uZ3Mgd2hpY2ggc3BlbnQgdGhlIG1vc3Qgd2Vla3Mgb24gdGhlIGNoYXJ0DQoNCmBgYCByDQpkMSAlPiUgDQogIGdyb3VwX2J5KHBlcmZvcm1lciwgc29uZykgJT4lIA0KICBzdW1tYXJpemUoDQogICAgd2Vla19pZCA9IHdlZWtfaWQgJT4lIG1pbiwNCiAgICB0b3RlX3dlZWtzID0gd2Vla3Nfb25fY2hhcnQgJT4lIG1heA0KICAgICkNCmBgYA0KDQpXaGljaCBzaG91bGQgcmV0dXJuDQoNCiAgICAgICBwZXJmb3JtZXIgICAgICAgICAgICAgICAgc29uZyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2Vla19pZCAgICB0b3RlX3dlZWtzDQogICAgICAgPGNocj4gICAgICAgICAgICAgICAgICAgIDxjaHI+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkYXRlPiAgICAgICAgICA8ZGJsPg0KICAgICAxICdOIFN5bmMgICAgICAgICAgICAgICAgICAoR29kIE11c3QgSGF2ZSBTcGVudCkgQSBMaXR0bGUgTW9yZSBUaW1lIE9uIFlvdSAxOTk4LTEyLTA1ICAgICAgICAgMjINCiAgICAgMiAnTiBTeW5jICAgICAgICAgICAgICAgICAgQnllIEJ5ZSBCeWUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMjAwMC0wMS0yOSAgICAgICAgIDIzDQogICAgIDMgJ04gU3luYyAgICAgICAgICAgICAgICAgIEdvbmUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDIwMDEtMDktMjIgICAgICAgICAyNA0KICAgICA0ICdOIFN5bmMgICAgICAgICAgICAgICAgICBJIERyaXZlIE15c2VsZiBDcmF6eSAgICAgICAgICAgICAgICAgICAgICAgICAgICAxOTk5LTA0LTI0ICAgICAgICAgMTINCiAgICAgNSAnTiBTeW5jICAgICAgICAgICAgICAgICAgSSBXYW50IFlvdSBCYWNrICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMTk5OC0wMy0wNyAgICAgICAgIDI0DQogICAgIDYgJ04gU3luYyAgICAgICAgICAgICAgICAgIEl0J3MgR29ubmEgQmUgTWUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDIwMDAtMDUtMDYgICAgICAgICAyNQ0KICAgICA3ICdOIFN5bmMgICAgICAgICAgICAgICAgICBQb3AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyMDAxLTA2LTAyICAgICAgICAgMTUNCiAgICAgOCAnTiBTeW5jICAgICAgICAgICAgICAgICAgVGVhcmluJyBVcCBNeSBIZWFydCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMTk5OC0xMi0wNSAgICAgICAgICAxDQogICAgIDkgJ04gU3luYyAgICAgICAgICAgICAgICAgIFRoaXMgSSBQcm9taXNlIFlvdSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDIwMDAtMDktMzAgICAgICAgICAyNg0KICAgIDEwICdOIFN5bmMgJiBHbG9yaWEgRXN0ZWZhbiBNdXNpYyBPZiBNeSBIZWFydCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAxOTk5LTA5LTA0ICAgICAgICAgMjANCg0KT2ggbm8tLXRoaXMgdGFibGUgaXMgbm90IHZlcnkgbmljZWx5IHNvcnRlZC4gSW5zdGVhZCwgd2UgbmVlZCB0byBkbzoNCg0KYGBgIHINCmQxICU+JSANCiAgZ3JvdXBfYnkocGVyZm9ybWVyLCBzb25nKSAlPiUgDQogIHN1bW1hcml6ZSgNCiAgICB3ZWVrX2lkID0gd2Vla19pZCAlPiUgbWluLA0KICAgIHRvdGVfd2Vla3MgPSB3ZWVrc19vbl9jaGFydCAlPiUgbWF4DQogICAgKSAlPiUgDQogIGFycmFuZ2UoDQogICAgZGVzYyh0b3RlX3dlZWtzKQ0KICAgICkNCmBgYA0KDQp3aGljaCBpcw0KDQogICAgICAgICBwZXJmb3JtZXIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzb25nICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWVrX2lkICAgIHRvdGVfd2Vla3MNCiAgICAgICA8Y2hyPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8Y2hyPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGF0ZT4gICAgICAgICAgPGRibD4NCiAgICAgMSBJbWFnaW5lIERyYWdvbnMgICAgICAgICAgICAgICAgICAgICAgICAgICBSYWRpb2FjdGl2ZSAgICAgICAgICAgICAgICAgICAgICAgICAyMDEyLTA4LTE4ICAgICAgICAgODcNCiAgICAgMiBBV09MTkFUSU9OICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTYWlsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyMDExLTA5LTAzICAgICAgICAgNzkNCiAgICAgMyBKYXNvbiBNcmF6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJJ20gWW91cnMgICAgICAgICAgICAgICAgICAgICAgICAgICAyMDA4LTA1LTAzICAgICAgICAgNzYNCiAgICAgNCBUaGUgV2Vla25kICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBCbGluZGluZyBMaWdodHMgICAgICAgICAgICAgICAgICAgICAyMDE5LTEyLTE0ICAgICAgICAgNzYNCiAgICAgNSBMZUFubiBSaW1lcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIb3cgRG8gSSBMaXZlICAgICAgICAgICAgICAgICAgICAgICAxOTk3LTA2LTIxICAgICAgICAgNjkNCiAgICAgNiBMTUZBTyBGZWF0dXJpbmcgTGF1cmVuIEJlbm5ldHQgJiBHb29uUm9jayBQYXJ0eSBSb2NrIEFudGhlbSAgICAgICAgICAgICAgICAgICAyMDExLTAyLTEyICAgICAgICAgNjgNCiAgICAgNyBPbmVSZXB1YmxpYyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudGluZyBTdGFycyAgICAgICAgICAgICAgICAgICAgICAyMDEzLTA3LTA2ICAgICAgICAgNjgNCiAgICAgOCBBZGVsZSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSb2xsaW5nIEluIFRoZSBEZWVwICAgICAgICAgICAgICAgICAyMDEwLTEyLTI1ICAgICAgICAgNjUNCiAgICAgOSBKZXdlbCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGb29saXNoIEdhbWVzL1lvdSBXZXJlIE1lYW50IEZvciBNZSAxOTk2LTExLTMwICAgICAgICAgNjUNCiAgICAxMCBDYXJyaWUgVW5kZXJ3b29kICAgICAgICAgICAgICAgICAgICAgICAgICBCZWZvcmUgSGUgQ2hlYXRzICAgICAgICAgICAgICAgICAgICAyMDA2LTA5LTE2ICAgICAgICAgNjQNCg0KIyMgYGFycmFuZ2VgICBleGFtcGxlcw0KDQpPdXIgZmluYWwgdmVyYiBwcm92aWRlcyB0aGUgYWJpbGl0eSB0byBzb3J0IHJvd3MgKHdlIHVzZWQgaXQgYWJvdmUgdG8gZ2V0IGEgc2Vuc2Ugb2YgdGhlIHNvbmdzIHdoaWNoIHBlcnNpc3RlZCB0aGUgbG9uZ2VzdCBvbiB0aGUgQmlsbGJvYXJkIDEwMC4pIEZvciBpbnN0YW5jZSwgd2hpY2ggYXJlIHRoZSBtb3N0ICJkYW5jZWFibGUiIHNvbmdzPw0KDQpgYGAgcg0KZDEgJT4lIA0KICBncm91cF9ieShzb25nX2lkKSAlPiUgDQogIHNsaWNlKDEpICU+JSANCiAgdW5ncm91cCAlPiUgDQogIGFycmFuZ2UoZGVzYyhkYW5jZWFiaWxpdHkpKSAlPiUgDQogIHNlbGVjdCh3ZWVrX2lkOnBlcmZvcm1lcikNCmBgYA0KDQpzaG91bGQgcmV0dXJuDQoNCiAgICAgICB3ZWVrX2lkICAgIHdlZWtfcG9zaXRpb24gc29uZyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGVyZm9ybWVyICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICA8ZGF0ZT4gICAgICAgICAgICAgPGRibD4gPGNocj4gICAgICAgICAgICAgICAgICAgICAgICAgICAgPGNocj4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgMSAxOTg5LTAzLTA0ICAgICAgICAgICAgNjAgRnVua3kgQ29sZCBNZWRpbmEgICAgICAgICAgICAgICAgVG9uZS1Mb2MgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgMiAyMDA3LTEyLTE1ICAgICAgICAgICAgOTIgR28gR2lybCAgICAgICAgICAgICAgICAgICAgICAgICAgUGl0YnVsbCBGZWF0dXJpbmcgVHJpbmEgJiBZb3VuZyBCbyQkICANCiAgICAgMyAyMDE3LTAzLTA0ICAgICAgICAgICAgODggQ2FzaCBNZSBPdXRzaWRlIChDYXNoTWVPdXRzaWRlKSAgREogU3VlZGUgVGhlIFJlbWl4IEdvZCAgICAgICAgICAgICAgICANCiAgICAgNCAyMDEwLTA1LTIyICAgICAgICAgICAgNzQgSWNlIEljZSBCYWJ5ICAgICAgICAgICAgICAgICAgICAgR2xlZSBDYXN0ICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgNSAxOTg0LTA2LTMwICAgICAgICAgICAgMzAgU3RhdGUgb2YgU2hvY2sgICAgICAgICAgICAgICAgICAgVGhlIEphY2tzb25zICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgNiAxOTkwLTA5LTA4ICAgICAgICAgICAgNzIgSWNlIEljZSBCYWJ5ICAgICAgICAgICAgICAgICAgICAgVmFuaWxsYSBJY2UgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgNyAyMDE5LTA4LTE3ICAgICAgICAgICAgODIgVW5vICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQW1iamFheSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgOCAyMDE5LTA4LTMxICAgICAgICAgICAgMzIgQmFkIEJhZCBCYWQgICAgICAgICAgICAgICAgICAgICAgWW91bmcgVGh1ZyBGZWF0dXJpbmcgTGlsIEJhYnkgICAgICAgICANCiAgICAgOSAxOTgzLTAxLTE1ICAgICAgICAgICAgOTAgQmV0Y2hhIFNoZSBEb24ndCBMb3ZlIFlvdSAgICAgICAgRXZlbHluIEtpbmcgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAxMCAyMDAyLTA3LTEzICAgICAgICAgICAgOTUgSW4gRGEgV2luZCAgICAgICAgICAgICAgICAgICAgICAgVHJpY2sgRGFkZHkgRmVhdHVyaW5nIENlZS1MbyAmIEJpZyBCb2kNCiAgICAjIC4uLiB3aXRoIDI5LDM3OSBtb3JlIHJvd3MNCg0KIyMgYHBpdm90X2xvbmdlcmAgYW5kIGBwaXZvdF93aWRlcmAgZXhhbXBsZXMNCg0KVGhpcyBpcyBhIHRvdWdoIG9uZSB0byBjb21tdW5pY2F0ZSB0byBzdHVkZW50cy4gVGhlIHByaW5jaXBsZSB0aGluZ3MgdG8ga2VlcCBpbiBtaW5kIGFib3V0IHJlc2hhcGluZw0KDQotICAgTm8gZGF0YSBhcmUgaW50cm9kdWNlZCBvciBvbWl0dGVkLiBFdmVyeSBwYWlyIG9mIGNlbGwgY29vcmRpbmF0ZS0tdmFyaWFibGUgbmFtZXMgYW5kIHRoZSBJRCB2ZWN0b3IgaW4gdGhlIGV4YW1wbGUgYmVsb3csIHJlbWFpbiB1bmNoYW5nZWQgaW4gZWFjaCBmb3JtIG9mIHRoZSBkYXRhLS13aGV0aGVyIGl0J3Mgd2lkZSAob24gdGhlIGxlZnQpIG9yIHdpZGUgKG9uIHRoZSByaWdodC4pDQoNCi0gICBUaGUgbW9zdCB1c2VmdWwgdHlwaWNhbCBleGFtcGxlIG9mIHRoaXMgcHJvY2VzcyBpcyB3aGVuIHlvdSB3YW50IHRvIGRvIGEgYnVuY2ggb2YgY2FsY3VsYXRpb25zIG9uIHNlcGFyYXRlIHZhcmlhYmxlcy4gTm93LCB5b3UgbWlnaHQgYmUgaW5jbGluZWQvZXF1aXBwZWQgdG8gd3JpdGUgYSBjbGV2ZXIgZm9yIGxvb3AsIHdoaWNoIG9wZXJhdGVzIG9uIGVhY2ggc2VwYXJhdGUgdmFyaWFibGUuIEJ1dCBpZiB5b3UgZG9uJ3Qgd2FudCB0byBsZWFybiBob3cgdG8gZG8gdGhpcywgaW5zdGVhZCwgKipqdXN0IGVuY29kZSB0aGUgZGlmZmVyZW5jZSB2YXJpYWJsZSBuYW1lcywgdGhlbXNlbHZlcywgaW4gYSB2YXJpYWJsZS4qKiBUaGF0J3Mgd2hhdCB3ZSdyZSBkb2luZyBpbiB0aGUgZXhhbXBsZSBiZWxvdy4NCg0KIVtdKHRpZHlyX2RpYWcuanBnKXt3aWR0aD0iNzAwIn0NCg0KTGV0J3MgdGhpbmsgb2YgYW4gZXhhbXBsZS0td2hpY2ggb2YgdGhlIHNvbmcgYXR0cmlidXRlcyBhcmUgbW9zdCBzdHJvbmdseSByZWxhdGVkIHRvIGEgc29uZydzIHBlYWsgcG9zaXRpb24/DQoNCmBgYCByDQpkMSAlPiUgDQogIGdyb3VwX2J5KHNvbmdfaWQpICU+JSANCiAgc2xpY2UoDQogICAgcGVha19wb3NpdGlvbiAlPiUgDQogICAgICB3aGljaC5taW4NCiAgICApICU+JSANCiAgc2VsZWN0KA0KICAgIHNvbmcsIHBlcmZvcm1lciwgc29uZ19pZCwgcGVha19wb3NpdGlvbiwgDQogICAgZGFuY2VhYmlsaXR5OnRlbXBvDQogICAgKSAlPiUgDQogIHBpdm90X2xvbmdlcigNCiAgICBjb2xzID0gZGFuY2VhYmlsaXR5OnRlbXBvLCANCiAgICBuYW1lc190byA9ICJhdHRyIiwgDQogICAgdmFsdWVzX3RvID0gInZhbCIsIA0KICAgIHZhbHVlc19kcm9wX25hID0gVA0KICAgICkgJT4lIA0KICBncm91cF9ieSgNCiAgICBhdHRyDQogICkgJT4lIA0KICBzdW1tYXJpc2UoDQogICAgcmVsID0gY29yKHBlYWtfcG9zaXRpb24sIHZhbCkNCiAgICApICU+JSANCiAgYXJyYW5nZSgNCiAgICBkZXNjKHJlbCkNCiAgKQ0KYGBgDQoNCmBwaXZvdF9sb25nZXJgIGlzIHNheWluZzoNCg0KLSAgIGFwcGx5IHRoZSBwaXZvdCBsb25nZXIgbWVjaGFuaXNtIHRvIHRoZSB2YXJpYWJsZXMgYmV0d2VlbiBgZGFuY2VhYmlsaXR5YCBhbmQgYHRlbXBvYA0KDQotICAgY2FsbCBteSB2ZWN0b3Igb2YgdmFyaWFibGUgbmFtZXMgImBhdHRyYCINCg0KLSAgIGNhbGwgbXkgdmVjdG9yIG9mIHZhcmlhYmxlIHZhbHVlcyAiYHZhbGAiDQoNClRoZW4gd2UgY29tcHV0ZSB0aGUgY29ycmVsYXRpb24gZm9yIGVhY2ggZ2F0aGVyZWQgdmFyaWFibGUsIGNvbXBhcmluZyB0aGF0IHZhcmlhYmxlIHRvIHRoZSBvdmVyYWxsIG1hcmdpbg0KDQpHb3NoIGhvdyBkaWQgSSBtYWtlIGEgd2hvbGUgc2V0IG9mIGxhYiBub3RlcyB3aXRob3V0IGluY2x1ZGluZyBhIHNpbmdsZSBwbG90IT8NCg0KIVtdKHBvc2l0aW9uX2Nvci5qcGVnKXt3aWR0aD0iNzAwIn0NCg0KTGV0IHRoaXMgYmUgYSBsZXNzb24gdG8gdXMgYWxsIC0tICpkZS1lbXBoYXNpemUqIHNwZWVjaGluZXNzIHRvIG1heGltaXplIHRoZSBCaWxsYm9hcmQgcGVyZm9ybWFuY2Ugb2YgeW91ciBuZXh0IHNpbmdsZS4NCg==