Today, we’ll work on just one (or 1.5) topic(s).

  1. 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.

  2. 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:

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

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:

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==