This document addresses one solution to the data-wrangling exercise described in Exercise3.pdf.

The data.table format does not seem to display consistently – or I’m missing something – within the usual nb.html format. The html version here may be prettier. The R Notebook with executable code – the Rmd file – should be downloadable from the pulldown at upper right of the nb.html file or can be downloaded separately here: Exercise3-datatableSolution.Rmd.

The input to the exercise is the raw data on 2016 Centre County, PA, precinct level votes found in “CentreCountyPrecinctResults2016GeneralElection.txt”, which were retrieved from http://centrecountypa.gov/Index.aspx?NID=802. (Update - 2020: This link is no longer accurate, and the original file does not appear to be available there any more. You can, however, find it on the Internet Archive’s Wayback Machine, for example, here: http://web.archive.org/web/20181106161514/centrecountypa.gov/index.aspx?NID=802. The file is no longer available due to the website being “updated” to provide the data in even less accessible form (pdf).)

The exercise asks you to extract the data on votes cast by precinct in statewide elections, and process them into a new table with precinct level data on total votes, Democratic share of two-party vote, and ballot rolloff from presidential votes to votes in other statewide races.

This solution uses the R data.table library. For alternative solutions see https://burtmonroe.github.io/SoDA501/Exercise3-2018.

Solving with data.table

We will proceed with the same general strategy as with the tidyverse solution, creating three tables – total votes, rolloffs, and two party shares – that we then merge in the final step.

First we read in the raw data (this time using the fread command to read into a data.table), and look at the first 50 rows.

library(data.table)
rawdata.dt <- fread("CentreCountyPrecinctResults2016GeneralElection.txt") # 3520 rows, 16 columns
dim(rawdata.dt)
[1] 3520   16
rawdata.dt
                           County
   1: CENTRE COUNTY, PENNSYLVANIA
   2: CENTRE COUNTY, PENNSYLVANIA
   3: CENTRE COUNTY, PENNSYLVANIA
   4: CENTRE COUNTY, PENNSYLVANIA
   5: CENTRE COUNTY, PENNSYLVANIA
  ---                            
3516: CENTRE COUNTY, PENNSYLVANIA
3517: CENTRE COUNTY, PENNSYLVANIA
3518: CENTRE COUNTY, PENNSYLVANIA
3519: CENTRE COUNTY, PENNSYLVANIA
3520: CENTRE COUNTY, PENNSYLVANIA
      ElecYear         Election
   1:     2016 GENERAL ELECTION
   2:     2016 GENERAL ELECTION
   3:     2016 GENERAL ELECTION
   4:     2016 GENERAL ELECTION
   5:     2016 GENERAL ELECTION
  ---                          
3516:     2016 GENERAL ELECTION
3517:     2016 GENERAL ELECTION
3518:     2016 GENERAL ELECTION
3519:     2016 GENERAL ELECTION
3520:     2016 GENERAL ELECTION
             ElecDate PrecNo
   1: NOVEMBER 8 2016      1
   2: NOVEMBER 8 2016      1
   3: NOVEMBER 8 2016      1
   4: NOVEMBER 8 2016      1
   5: NOVEMBER 8 2016      1
  ---                       
3516: NOVEMBER 8 2016     91
3517: NOVEMBER 8 2016     91
3518: NOVEMBER 8 2016     91
3519: NOVEMBER 8 2016     91
3520: NOVEMBER 8 2016     91
                       PrecName
   1:       01 BELLEFONTE NORTH
   2:       01 BELLEFONTE NORTH
   3:       01 BELLEFONTE NORTH
   4:       01 BELLEFONTE NORTH
   5:       01 BELLEFONTE NORTH
  ---                          
3516: 91 FERGUSON NORTH CENTRAL
3517: 91 FERGUSON NORTH CENTRAL
3518: 91 FERGUSON NORTH CENTRAL
3519: 91 FERGUSON NORTH CENTRAL
3520: 91 FERGUSON NORTH CENTRAL
           Party
   1:           
   2:           
   3:           
   4: DEMOCRATIC
   5: REPUBLICAN
  ---           
3516: DEMOCRATIC
3517: REPUBLICAN
3518:           
3519:           
3520:           
                                                       Contest
   1:                                REGISTERED VOTERS - TOTAL
   2:                                     BALLOTS CAST - TOTAL
   3:                                    VOTER TURNOUT - TOTAL
   4:                                           STRAIGHT PARTY
   5:                                           STRAIGHT PARTY
  ---                                                         
3516: REPRESENTATIVE IN THE GENERAL ASSEMBLY 171ST LEGISLATIVE
3517: REPRESENTATIVE IN THE GENERAL ASSEMBLY 171ST LEGISLATIVE
3518: REPRESENTATIVE IN THE GENERAL ASSEMBLY 171ST LEGISLATIVE
3519:                                               JUDGES AGE
3520:                                               JUDGES AGE
      Descr
   1:    NA
   2:    NA
   3:    NA
   4:    NA
   5:    NA
  ---      
3516:    NA
3517:    NA
3518:    NA
3519:    NA
3520:    NA
                        VoteFor
   1:                          
   2:                          
   3:                          
   4: VOTE FOR NOT MORE THAN  1
   5: VOTE FOR NOT MORE THAN  1
  ---                          
3516: VOTE FOR NOT MORE THAN  1
3517: VOTE FOR NOT MORE THAN  1
3518: VOTE FOR NOT MORE THAN  1
3519: VOTE FOR NOT MORE THAN  1
3520: VOTE FOR NOT MORE THAN  1
                Candidate Posn
   1:                       10
   2:                       11
   3:                       12
   4:          DEMOCRATIC   17
   5:          REPUBLICAN   18
  ---                         
3516:      MELODY S FLECK 7700
3517: KERRY A BENNINGHOFF 7701
3518:            WRITE-IN 7702
3519:                 YES 7707
3520:                  NO 7708
      Count PctCnt Total PctTot
   1:   507     NA    NA     NA
   2:   391     NA    NA     NA
   3:    NA  77.12    NA     NA
   4:    46  40.35    NA     NA
   5:    66  57.89    NA     NA
  ---                          
3516:   301  43.81    NA     NA
3517:   385  56.04    NA     NA
3518:     1   0.15    NA     NA
3519:   317  53.64    NA     NA
3520:   274  46.36    NA     NA

These data have, roughly, 39 rows for each of 91 precincts in Centre County, PA, identified by the variables PrecNo (which ranges from 0001 to 0091) and PrecName (which ranges from 01 BELLEFONTE NORTH to 91 FERGUSON NORTH CENTRAL). Each precinct starts with three rows for summary information, identified in the Contest variable by values REGISTERED VOTERS - TOTAL, BALLOTS CAST - TOTAL, VOTER TURNOUT - TOTAL, followed by five rows with information on straight ticket voting for each of five parties (which can be ignored for our purposes here). From there, each row contains information for a single candidate in a particular contest. We care specifically about the statewide contests that are held in every precinct here: President (Contest=="PRESIDENTIAL ELECTORS"), US Senator (Contest=="UNITED STATES SENATOR"), Attorney General (Contest=="ATTORNEY GENERAL"), Auditor General (Contest=="AUDITOR GENERAL"), and Treasurer (Contest=="STATE TREASURER"). All of our calculations are based on the number in the Count variable.

Solve in three pieces

This solution creates three data.tables that it joins together in the last step: the total votes, the two-party shares, and the rolloffs. Each of these data.tables should ultimately have 91 rows.

Table 1: Total votes by precinct

Creating the total vote table requires just picking the right rows and relabeling the Count variable.

Tot.dt <- rawdata.dt[Contest=="BALLOTS CAST - TOTAL",.(PrecNo,PrecName, Tot=Count)]
Tot.dt

Table 2: Rolloffs in down-ballot races

As with the tidyverse solution, there are two nontrivial steps here. The first is the grouped summary step (the third assignment below), where vote counts are summed by Precinct Number and Contest. This is more or less the core process in data.table, so its implementation is very compact. The second is the dcast command which does the equivalent of what pivot_wider (spread) does in the tidyverse. The notation here uses a formula (here PrecNo ~ Con) to define the “key” on which the table is cast.

# Create data.table with just vars PrecNo, Count, and Con (abbreviated Contest)
NeededColumnsRows.dt <- rawdata.dt[,.(PrecNo,Count,Con=substr(Contest,1,3))] # pick columns, abbreviate
NeededColumnsRows.dt <- NeededColumnsRows.dt[Con %in% c("PRE","UNI","ATT","AUD","STA"),] # pick rows
NeededColumnsRows.dt   #  2093 rows, 3 columns
# Create grouped summary data.table with Total votes by Precinct-Contest 
PrecinctContestsLong.dt <- NeededColumnsRows.dt[,.(ConTot = sum(Count)), by=.(PrecNo,Con)]
PrecinctContestsLong.dt <- PrecinctContestsLong.dt[,.(PrecNo,Con,ConTot)] # could be "chained" w above
PrecinctContestsLong.dt     # 455 rows, 6 columns
# "Cast" the data by Contest (spread from long to wide)
PrecinctContestsWide.dt <- dcast(PrecinctContestsLong.dt, PrecNo ~ Con, value.var = "ConTot")
PrecinctContestsWide.dt     # 91 rows, 6 columns
# Calculate Rolloff Variables
Rolloffs.dt <- PrecinctContestsWide.dt[,.(PrecNo,                 # Keep Precinct Number
                                          ROSen=100*(1-UNI/PRE),  # Rolloff for US Senator
                                          ROAtt=100*(1-ATT/PRE),  # Rolloff for Attorney General
                                          ROAud=100*(1-AUD/PRE),  # Rolloff for Auditor General 
                                          ROTre=100*(1-STA/PRE))] # Rolloff for Treasurer
Rolloffs.dt

Table 3: Democratic share of two-party vote

In the tidyverse version, we had to create a single column of Contest-Party indicators to act as a key. With data.table we can use two keys, just listing them on the right side of the formula (here, PrecNo ~ Con + Pty), and the keys from all pairings are constructed automatically. So this is a couple of commands more compact in data.table.

# Create data.table with just vars PrecNo, Count, and Con (abbreviated Contest)
NeededColumnsRows.dt <- rawdata.dt[,.(PrecNo,Pty=substr(Party,1,3),Con=substr(Contest,1,3), Count)] # pick columns, abbreviate
NeededColumnsRows.dt <- NeededColumnsRows.dt[Pty %in% c("DEM","REP") & Con %in% c("PRE","UNI","ATT","AUD","STA"),] # pick rows
NeededColumnsRows.dt   #  910 rows, 3 columns
# "Cast" the data by Party & Contest (spread from long to wide)
#    Note that data.table has no problem automatically creating a key based on two columns
PrecPartyContestsWide.dt <- dcast(NeededColumnsRows.dt, PrecNo ~ Con + Pty, value.var = "Count")
PrecPartyContestsWide.dt     # 91 rows, 6 columns
# Calculate Democratic Two-Party Share Variables
DemTwoPartyShares.dt <- PrecPartyContestsWide.dt[,.(PrecNo,                 # Keep Precinct Number
                         D2Pre=100*(PRE_DEM/(PRE_DEM+PRE_REP)),# D2Pre = Dem 2 party share, President
                         D2Sen=100*(UNI_DEM/(UNI_DEM+UNI_REP)),# D2Pre = Dem 2 party share, US Senator
                         D2Att=100*(ATT_DEM/(ATT_DEM+ATT_REP)),# D2Pre = Dem 2 party share, Attorney Gen
                         D2Aud=100*(AUD_DEM/(AUD_DEM+AUD_REP)),# D2Pre = Dem 2 party share, Auditor Gen
                         D2Tre=100*(STA_DEM/(STA_DEM+STA_REP)))]# D2Pre = Dem 2 party share, State Treas
DemTwoPartyShares.dt
NA

Final output table: Merged Data

Now we merge the tables and format the Precinct Number and Name as requested in the Exercise.

Ex3Data.dt <- merge(Tot.dt,Rolloffs.dt, by="PrecNo")              # Merge Total with Rolloffs
Ex3Data.dt <- merge(Ex3Data.dt,DemTwoPartyShares.dt, by="PrecNo") # Merge that with Dem 2-party Shares
Ex3Data.dt[,PrecNo := as.integer(PrecNo)]                         # Make Precinct number a number
Ex3Data.dt[,PrecName := substr(PrecName,4,stop=40L)]              # Strip duplicate info from Name
Ex3Data.dt
LS0tCnRpdGxlOiAiU29EQSA1MDEsIEV4ZXJjaXNlIDMgLSBkYXRhLnRhYmxlIFNvbHV0aW9uIgphdXRob3I6ICJCdXJ0IEwuIE1vbnJvZSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogeWVzCiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICB0aGVtZTogdW5pdGVkCiAgICB0b2M6IHllcwotLS0KCgpUaGlzIGRvY3VtZW50IGFkZHJlc3NlcyBvbmUgc29sdXRpb24gdG8gdGhlIGRhdGEtd3JhbmdsaW5nIGV4ZXJjaXNlIGRlc2NyaWJlZCBpbiBbRXhlcmNpc2UzLnBkZl0oaHR0cHM6Ly9idXJ0bW9ucm9lLmdpdGh1Yi5pby9Tb0RBNTAxL0V4ZXJjaXNlcy9FeGVyY2lzZTMtMjAxOC9FeGVyY2lzZTMucGRmKS4KClRoZSBgZGF0YS50YWJsZWAgZm9ybWF0IGRvZXMgbm90IHNlZW0gdG8gZGlzcGxheSBjb25zaXN0ZW50bHkgLS0gb3IgSSdtIG1pc3Npbmcgc29tZXRoaW5nIC0tIHdpdGhpbiB0aGUgdXN1YWwgYG5iLmh0bWxgIGZvcm1hdC4gVGhlIGBodG1sYCB2ZXJzaW9uIFtoZXJlXShodHRwczovL2J1cnRtb25yb2UuZ2l0aHViLmlvL1NvREE1MDEvRXhlcmNpc2VzL0V4ZXJjaXNlMy0yMDE4L2RhdGF0YWJsZVNvbHV0aW9uL0V4ZXJjaXNlMy1kYXRhdGFibGVTb2x1dGlvbi5odG1sKSBtYXkgYmUgcHJldHRpZXIuIFRoZSBSIE5vdGVib29rIHdpdGggZXhlY3V0YWJsZSBjb2RlIC0tIHRoZSBSbWQgZmlsZSAtLSBzaG91bGQgYmUgZG93bmxvYWRhYmxlIGZyb20gdGhlIHB1bGxkb3duIGF0IHVwcGVyIHJpZ2h0IG9mIHRoZSBgbmIuaHRtbGAgZmlsZSBvciBjYW4gYmUgZG93bmxvYWRlZCBzZXBhcmF0ZWx5IGhlcmU6IFtFeGVyY2lzZTMtZGF0YXRhYmxlU29sdXRpb24uUm1kXShodHRwczovL2J1cnRtb25yb2UuZ2l0aHViLmlvL1NvREE1MDEvRXhlcmNpc2VzL0V4ZXJjaXNlMy0yMDE4L2RhdGF0YWJsZVNvbHV0aW9uL0V4ZXJjaXNlMy1kYXRhdGFibGVTb2x1dGlvbi5SbWQpLiAgCgpUaGUgaW5wdXQgdG8gdGhlIGV4ZXJjaXNlIGlzIHRoZSByYXcgZGF0YSBvbiAyMDE2IENlbnRyZSBDb3VudHksIFBBLCBwcmVjaW5jdCBsZXZlbCB2b3RlcyBmb3VuZCBpbiBbIkNlbnRyZUNvdW50eVByZWNpbmN0UmVzdWx0czIwMTZHZW5lcmFsRWxlY3Rpb24udHh0Il0oaHR0cHM6Ly9idXJ0bW9ucm9lLmdpdGh1Yi5pby9Tb0RBNTAxL0V4ZXJjaXNlcy9FeGVyY2lzZTMtMjAxOC9DZW50cmVDb3VudHlQcmVjaW5jdFJlc3VsdHMyMDE2R2VuZXJhbEVsZWN0aW9uLnR4dCksIHdoaWNoIHdlcmUgcmV0cmlldmVkIGZyb20gW2h0dHA6Ly9jZW50cmVjb3VudHlwYS5nb3YvSW5kZXguYXNweD9OSUQ9ODAyXShodHRwOi8vY2VudHJlY291bnR5cGEuZ292L0luZGV4LmFzcHg/TklEPTgwMikuIChVcGRhdGUgLSAyMDIwOiBUaGlzIGxpbmsgaXMgbm8gbG9uZ2VyIGFjY3VyYXRlLCBhbmQgdGhlIG9yaWdpbmFsIGZpbGUgZG9lcyBub3QgYXBwZWFyIHRvIGJlIGF2YWlsYWJsZSB0aGVyZSBhbnkgbW9yZS4gWW91IGNhbiwgaG93ZXZlciwgZmluZCBpdCBvbiB0aGUgSW50ZXJuZXQgQXJjaGl2ZSdzIFdheWJhY2sgTWFjaGluZSwgZm9yIGV4YW1wbGUsIGhlcmU6IFtodHRwOi8vd2ViLmFyY2hpdmUub3JnL3dlYi8yMDE4MTEwNjE2MTUxNC9jZW50cmVjb3VudHlwYS5nb3YvaW5kZXguYXNweD9OSUQ9ODAyXShodHRwOi8vd2ViLmFyY2hpdmUub3JnL3dlYi8yMDE4MTEwNjE2MTUxNC9jZW50cmVjb3VudHlwYS5nb3YvaW5kZXguYXNweD9OSUQ9ODAyKS4gVGhlIGZpbGUgaXMgbm8gbG9uZ2VyIGF2YWlsYWJsZSBkdWUgdG8gdGhlIHdlYnNpdGUgYmVpbmcgInVwZGF0ZWQiIHRvIHByb3ZpZGUgdGhlIGRhdGEgaW4gZXZlbiBsZXNzIGFjY2Vzc2libGUgZm9ybSAocGRmKS4pCgoKVGhlIGV4ZXJjaXNlIGFza3MgeW91IHRvIGV4dHJhY3QgdGhlIGRhdGEgb24gdm90ZXMgY2FzdCBieSBwcmVjaW5jdCBpbiBzdGF0ZXdpZGUgZWxlY3Rpb25zLCBhbmQgcHJvY2VzcyB0aGVtIGludG8gYSBuZXcgdGFibGUgd2l0aCBwcmVjaW5jdCBsZXZlbCBkYXRhIG9uIHRvdGFsIHZvdGVzLCBEZW1vY3JhdGljIHNoYXJlIG9mIHR3by1wYXJ0eSB2b3RlLCBhbmQgYmFsbG90IHJvbGxvZmYgZnJvbSBwcmVzaWRlbnRpYWwgdm90ZXMgdG8gdm90ZXMgaW4gb3RoZXIgc3RhdGV3aWRlIHJhY2VzLgoKVGhpcyBzb2x1dGlvbiB1c2VzIHRoZSBSIGBkYXRhLnRhYmxlYCBsaWJyYXJ5LiBGb3IgYWx0ZXJuYXRpdmUgc29sdXRpb25zIHNlZSBbaHR0cHM6Ly9idXJ0bW9ucm9lLmdpdGh1Yi5pby9Tb0RBNTAxL0V4ZXJjaXNlMy0yMDE4XShodHRwczovL2J1cnRtb25yb2UuZ2l0aHViLmlvL1NvREE1MDEvRXhlcmNpc2UzLTIwMTgpLgoKIyBTb2x2aW5nIHdpdGggYGRhdGEudGFibGVgCgpXZSB3aWxsIHByb2NlZWQgd2l0aCB0aGUgc2FtZSBnZW5lcmFsIHN0cmF0ZWd5IGFzIHdpdGggdGhlIHRpZHl2ZXJzZSBzb2x1dGlvbiwgY3JlYXRpbmcgdGhyZWUgdGFibGVzIC0tIHRvdGFsIHZvdGVzLCByb2xsb2ZmcywgYW5kIHR3byBwYXJ0eSBzaGFyZXMgLS0gdGhhdCB3ZSB0aGVuIG1lcmdlIGluIHRoZSBmaW5hbCBzdGVwLgoKRmlyc3Qgd2UgcmVhZCBpbiB0aGUgcmF3IGRhdGEgKHRoaXMgdGltZSB1c2luZyB0aGUgYGZyZWFkYCBjb21tYW5kIHRvIHJlYWQgaW50byBhIGBkYXRhLnRhYmxlYCksIGFuZCBsb29rIGF0IHRoZSBmaXJzdCA1MCByb3dzLgoKYGBge3J9CmxpYnJhcnkoZGF0YS50YWJsZSkKcmF3ZGF0YS5kdCA8LSBmcmVhZCgiQ2VudHJlQ291bnR5UHJlY2luY3RSZXN1bHRzMjAxNkdlbmVyYWxFbGVjdGlvbi50eHQiKSAjIDM1MjAgcm93cywgMTYgY29sdW1ucwpkaW0ocmF3ZGF0YS5kdCkKcmF3ZGF0YS5kdApgYGAKClRoZXNlIGRhdGEgaGF2ZSwgcm91Z2hseSwgMzkgcm93cyBmb3IgZWFjaCBvZiA5MSBwcmVjaW5jdHMgaW4gQ2VudHJlIENvdW50eSwgUEEsIGlkZW50aWZpZWQgYnkgdGhlIHZhcmlhYmxlcyBgUHJlY05vYCAod2hpY2ggcmFuZ2VzIGZyb20gYDAwMDFgIHRvIGAwMDkxYCkgYW5kIGBQcmVjTmFtZWAgKHdoaWNoIHJhbmdlcyBmcm9tIGAwMSBCRUxMRUZPTlRFIE5PUlRIYCB0byBgOTEgRkVSR1VTT04gTk9SVEggQ0VOVFJBTGApLiBFYWNoIHByZWNpbmN0IHN0YXJ0cyB3aXRoIHRocmVlIHJvd3MgZm9yIHN1bW1hcnkgaW5mb3JtYXRpb24sIGlkZW50aWZpZWQgaW4gdGhlIGBDb250ZXN0YCB2YXJpYWJsZSBieSB2YWx1ZXMgYFJFR0lTVEVSRUQgVk9URVJTIC0gVE9UQUxgLCBgQkFMTE9UUyBDQVNUIC0gVE9UQUxgLCBgVk9URVIgVFVSTk9VVCAtIFRPVEFMYCwgZm9sbG93ZWQgYnkgZml2ZSByb3dzIHdpdGggaW5mb3JtYXRpb24gb24gc3RyYWlnaHQgdGlja2V0IHZvdGluZyBmb3IgZWFjaCBvZiBmaXZlIHBhcnRpZXMgKHdoaWNoIGNhbiBiZSBpZ25vcmVkIGZvciBvdXIgcHVycG9zZXMgaGVyZSkuIEZyb20gdGhlcmUsIGVhY2ggcm93IGNvbnRhaW5zIGluZm9ybWF0aW9uIGZvciBhIHNpbmdsZSBjYW5kaWRhdGUgaW4gYSBwYXJ0aWN1bGFyIGNvbnRlc3QuIFdlIGNhcmUgc3BlY2lmaWNhbGx5IGFib3V0IHRoZSBzdGF0ZXdpZGUgY29udGVzdHMgdGhhdCBhcmUgaGVsZCBpbiBldmVyeSBwcmVjaW5jdCBoZXJlOiBQcmVzaWRlbnQgKGBDb250ZXN0PT0iUFJFU0lERU5USUFMIEVMRUNUT1JTImApLCBVUyBTZW5hdG9yIChgQ29udGVzdD09IlVOSVRFRCBTVEFURVMgU0VOQVRPUiJgKSwgQXR0b3JuZXkgR2VuZXJhbCAoYENvbnRlc3Q9PSJBVFRPUk5FWSBHRU5FUkFMImApLCBBdWRpdG9yIEdlbmVyYWwgKGBDb250ZXN0PT0iQVVESVRPUiBHRU5FUkFMImApLCBhbmQgVHJlYXN1cmVyIChgQ29udGVzdD09IlNUQVRFIFRSRUFTVVJFUiJgKS4gQWxsIG9mIG91ciBjYWxjdWxhdGlvbnMgYXJlIGJhc2VkIG9uIHRoZSBudW1iZXIgaW4gdGhlIGBDb3VudGAgdmFyaWFibGUuCgojIyBTb2x2ZSBpbiB0aHJlZSBwaWVjZXMKClRoaXMgc29sdXRpb24gY3JlYXRlcyB0aHJlZSBkYXRhLnRhYmxlcyB0aGF0IGl0IGpvaW5zIHRvZ2V0aGVyIGluIHRoZSBsYXN0IHN0ZXA6IHRoZSB0b3RhbCB2b3RlcywgdGhlIHR3by1wYXJ0eSBzaGFyZXMsIGFuZCB0aGUgcm9sbG9mZnMuIEVhY2ggb2YgdGhlc2UgZGF0YS50YWJsZXMgc2hvdWxkIHVsdGltYXRlbHkgaGF2ZSA5MSByb3dzLgoKIyMjIFRhYmxlIDE6IFRvdGFsIHZvdGVzIGJ5IHByZWNpbmN0CgpDcmVhdGluZyB0aGUgdG90YWwgdm90ZSB0YWJsZSByZXF1aXJlcyBqdXN0IHBpY2tpbmcgdGhlIHJpZ2h0IHJvd3MgYW5kIHJlbGFiZWxpbmcgdGhlIENvdW50IHZhcmlhYmxlLgoKYGBge3J9ClRvdC5kdCA8LSByYXdkYXRhLmR0W0NvbnRlc3Q9PSJCQUxMT1RTIENBU1QgLSBUT1RBTCIsLihQcmVjTm8sUHJlY05hbWUsIFRvdD1Db3VudCldClRvdC5kdApgYGAKCiMjIyBUYWJsZSAyOiBSb2xsb2ZmcyBpbiBkb3duLWJhbGxvdCByYWNlcwoKQXMgd2l0aCB0aGUgdGlkeXZlcnNlIHNvbHV0aW9uLCB0aGVyZSBhcmUgdHdvIG5vbnRyaXZpYWwgc3RlcHMgaGVyZS4gVGhlIGZpcnN0IGlzIHRoZSBncm91cGVkIHN1bW1hcnkgc3RlcCAodGhlIHRoaXJkIGFzc2lnbm1lbnQgYmVsb3cpLCB3aGVyZSB2b3RlIGNvdW50cyBhcmUgc3VtbWVkIGJ5IFByZWNpbmN0IE51bWJlciBhbmQgQ29udGVzdC4gVGhpcyBpcyBtb3JlIG9yIGxlc3MgdGhlIGNvcmUgcHJvY2VzcyBpbiBgZGF0YS50YWJsZWAsIHNvIGl0cyBpbXBsZW1lbnRhdGlvbiBpcyB2ZXJ5IGNvbXBhY3QuIFRoZSBzZWNvbmQgaXMgdGhlIGBkY2FzdGAgY29tbWFuZCB3aGljaCBkb2VzIHRoZSBlcXVpdmFsZW50IG9mIHdoYXQgYHBpdm90X3dpZGVyYCAoYHNwcmVhZGApIGRvZXMgaW4gdGhlIHRpZHl2ZXJzZS4gVGhlIG5vdGF0aW9uIGhlcmUgdXNlcyBhIGZvcm11bGEgKGhlcmUgYFByZWNObyB+IENvbmApIHRvIGRlZmluZSB0aGUgImtleSIgb24gd2hpY2ggdGhlIHRhYmxlIGlzIGNhc3QuCgpgYGB7cn0KIyBDcmVhdGUgZGF0YS50YWJsZSB3aXRoIGp1c3QgdmFycyBQcmVjTm8sIENvdW50LCBhbmQgQ29uIChhYmJyZXZpYXRlZCBDb250ZXN0KQpOZWVkZWRDb2x1bW5zUm93cy5kdCA8LSByYXdkYXRhLmR0WywuKFByZWNObyxDb3VudCxDb249c3Vic3RyKENvbnRlc3QsMSwzKSldICMgcGljayBjb2x1bW5zLCBhYmJyZXZpYXRlCk5lZWRlZENvbHVtbnNSb3dzLmR0IDwtIE5lZWRlZENvbHVtbnNSb3dzLmR0W0NvbiAlaW4lIGMoIlBSRSIsIlVOSSIsIkFUVCIsIkFVRCIsIlNUQSIpLF0gIyBwaWNrIHJvd3MKTmVlZGVkQ29sdW1uc1Jvd3MuZHQgICAjICAyMDkzIHJvd3MsIDMgY29sdW1ucwoKIyBDcmVhdGUgZ3JvdXBlZCBzdW1tYXJ5IGRhdGEudGFibGUgd2l0aCBUb3RhbCB2b3RlcyBieSBQcmVjaW5jdC1Db250ZXN0IApQcmVjaW5jdENvbnRlc3RzTG9uZy5kdCA8LSBOZWVkZWRDb2x1bW5zUm93cy5kdFssLihDb25Ub3QgPSBzdW0oQ291bnQpKSwgYnk9LihQcmVjTm8sQ29uKV0KUHJlY2luY3RDb250ZXN0c0xvbmcuZHQgPC0gUHJlY2luY3RDb250ZXN0c0xvbmcuZHRbLC4oUHJlY05vLENvbixDb25Ub3QpXSAjIGNvdWxkIGJlICJjaGFpbmVkIiB3IGFib3ZlClByZWNpbmN0Q29udGVzdHNMb25nLmR0ICAgICAjIDQ1NSByb3dzLCA2IGNvbHVtbnMKCiMgIkNhc3QiIHRoZSBkYXRhIGJ5IENvbnRlc3QgKHNwcmVhZCBmcm9tIGxvbmcgdG8gd2lkZSkKUHJlY2luY3RDb250ZXN0c1dpZGUuZHQgPC0gZGNhc3QoUHJlY2luY3RDb250ZXN0c0xvbmcuZHQsIFByZWNObyB+IENvbiwgdmFsdWUudmFyID0gIkNvblRvdCIpClByZWNpbmN0Q29udGVzdHNXaWRlLmR0ICAgICAjIDkxIHJvd3MsIDYgY29sdW1ucwoKIyBDYWxjdWxhdGUgUm9sbG9mZiBWYXJpYWJsZXMKUm9sbG9mZnMuZHQgPC0gUHJlY2luY3RDb250ZXN0c1dpZGUuZHRbLC4oUHJlY05vLCAgICAgICAgICAgICAgICAgIyBLZWVwIFByZWNpbmN0IE51bWJlcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBST1Nlbj0xMDAqKDEtVU5JL1BSRSksICAjIFJvbGxvZmYgZm9yIFVTIFNlbmF0b3IKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUk9BdHQ9MTAwKigxLUFUVC9QUkUpLCAgIyBSb2xsb2ZmIGZvciBBdHRvcm5leSBHZW5lcmFsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJPQXVkPTEwMCooMS1BVUQvUFJFKSwgICMgUm9sbG9mZiBmb3IgQXVkaXRvciBHZW5lcmFsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBST1RyZT0xMDAqKDEtU1RBL1BSRSkpXSAjIFJvbGxvZmYgZm9yIFRyZWFzdXJlcgpSb2xsb2Zmcy5kdApgYGAKCiMjIyBUYWJsZSAzOiBEZW1vY3JhdGljIHNoYXJlIG9mIHR3by1wYXJ0eSB2b3RlCgpJbiB0aGUgYHRpZHl2ZXJzZWAgdmVyc2lvbiwgd2UgaGFkIHRvIGNyZWF0ZSBhIHNpbmdsZSBjb2x1bW4gb2YgQ29udGVzdC1QYXJ0eSBpbmRpY2F0b3JzIHRvIGFjdCBhcyBhIGtleS4gV2l0aCBgZGF0YS50YWJsZWAgd2UgY2FuIHVzZSB0d28ga2V5cywganVzdCBsaXN0aW5nIHRoZW0gb24gdGhlIHJpZ2h0IHNpZGUgb2YgdGhlIGZvcm11bGEgKGhlcmUsIGBQcmVjTm8gfiBDb24gKyBQdHlgKSwgYW5kIHRoZSBrZXlzIGZyb20gYWxsIHBhaXJpbmdzIGFyZSBjb25zdHJ1Y3RlZCBhdXRvbWF0aWNhbGx5LiBTbyB0aGlzIGlzIGEgY291cGxlIG9mIGNvbW1hbmRzIG1vcmUgY29tcGFjdCBpbiBgZGF0YS50YWJsZS5gCgpgYGB7cn0KIyBDcmVhdGUgZGF0YS50YWJsZSB3aXRoIGp1c3QgdmFycyBQcmVjTm8sIENvdW50LCBhbmQgQ29uIChhYmJyZXZpYXRlZCBDb250ZXN0KQpOZWVkZWRDb2x1bW5zUm93cy5kdCA8LSByYXdkYXRhLmR0WywuKFByZWNObyxQdHk9c3Vic3RyKFBhcnR5LDEsMyksQ29uPXN1YnN0cihDb250ZXN0LDEsMyksIENvdW50KV0gIyBwaWNrIGNvbHVtbnMsIGFiYnJldmlhdGUKTmVlZGVkQ29sdW1uc1Jvd3MuZHQgPC0gTmVlZGVkQ29sdW1uc1Jvd3MuZHRbUHR5ICVpbiUgYygiREVNIiwiUkVQIikgJiBDb24gJWluJSBjKCJQUkUiLCJVTkkiLCJBVFQiLCJBVUQiLCJTVEEiKSxdICMgcGljayByb3dzCk5lZWRlZENvbHVtbnNSb3dzLmR0ICAgIyAgOTEwIHJvd3MsIDMgY29sdW1ucwoKIyAiQ2FzdCIgdGhlIGRhdGEgYnkgUGFydHkgJiBDb250ZXN0IChzcHJlYWQgZnJvbSBsb25nIHRvIHdpZGUpCiMgICAgTm90ZSB0aGF0IGRhdGEudGFibGUgaGFzIG5vIHByb2JsZW0gYXV0b21hdGljYWxseSBjcmVhdGluZyBhIGtleSBiYXNlZCBvbiB0d28gY29sdW1ucwpQcmVjUGFydHlDb250ZXN0c1dpZGUuZHQgPC0gZGNhc3QoTmVlZGVkQ29sdW1uc1Jvd3MuZHQsIFByZWNObyB+IENvbiArIFB0eSwgdmFsdWUudmFyID0gIkNvdW50IikKUHJlY1BhcnR5Q29udGVzdHNXaWRlLmR0ICAgICAjIDkxIHJvd3MsIDYgY29sdW1ucwoKIyBDYWxjdWxhdGUgRGVtb2NyYXRpYyBUd28tUGFydHkgU2hhcmUgVmFyaWFibGVzCkRlbVR3b1BhcnR5U2hhcmVzLmR0IDwtIFByZWNQYXJ0eUNvbnRlc3RzV2lkZS5kdFssLihQcmVjTm8sICAgICAgICAgICAgICAgICAjIEtlZXAgUHJlY2luY3QgTnVtYmVyCiAgICAgICAgICAgICAgICAgICAgICAgICBEMlByZT0xMDAqKFBSRV9ERU0vKFBSRV9ERU0rUFJFX1JFUCkpLCMgRDJQcmUgPSBEZW0gMiBwYXJ0eSBzaGFyZSwgUHJlc2lkZW50CiAgICAgICAgICAgICAgICAgICAgICAgICBEMlNlbj0xMDAqKFVOSV9ERU0vKFVOSV9ERU0rVU5JX1JFUCkpLCMgRDJQcmUgPSBEZW0gMiBwYXJ0eSBzaGFyZSwgVVMgU2VuYXRvcgogICAgICAgICAgICAgICAgICAgICAgICAgRDJBdHQ9MTAwKihBVFRfREVNLyhBVFRfREVNK0FUVF9SRVApKSwjIEQyUHJlID0gRGVtIDIgcGFydHkgc2hhcmUsIEF0dG9ybmV5IEdlbgogICAgICAgICAgICAgICAgICAgICAgICAgRDJBdWQ9MTAwKihBVURfREVNLyhBVURfREVNK0FVRF9SRVApKSwjIEQyUHJlID0gRGVtIDIgcGFydHkgc2hhcmUsIEF1ZGl0b3IgR2VuCiAgICAgICAgICAgICAgICAgICAgICAgICBEMlRyZT0xMDAqKFNUQV9ERU0vKFNUQV9ERU0rU1RBX1JFUCkpKV0jIEQyUHJlID0gRGVtIDIgcGFydHkgc2hhcmUsIFN0YXRlIFRyZWFzCkRlbVR3b1BhcnR5U2hhcmVzLmR0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKYGBgCgojIyBGaW5hbCBvdXRwdXQgdGFibGU6IE1lcmdlZCBEYXRhCgpOb3cgd2UgYG1lcmdlYCB0aGUgdGFibGVzIGFuZCBmb3JtYXQgdGhlIFByZWNpbmN0IE51bWJlciBhbmQgTmFtZSBhcyByZXF1ZXN0ZWQgaW4gdGhlIEV4ZXJjaXNlLgoKYGBge3J9CkV4M0RhdGEuZHQgPC0gbWVyZ2UoVG90LmR0LFJvbGxvZmZzLmR0LCBieT0iUHJlY05vIikgICAgICAgICAgICAgICMgTWVyZ2UgVG90YWwgd2l0aCBSb2xsb2ZmcwpFeDNEYXRhLmR0IDwtIG1lcmdlKEV4M0RhdGEuZHQsRGVtVHdvUGFydHlTaGFyZXMuZHQsIGJ5PSJQcmVjTm8iKSAjIE1lcmdlIHRoYXQgd2l0aCBEZW0gMi1wYXJ0eSBTaGFyZXMKRXgzRGF0YS5kdFssUHJlY05vIDo9IGFzLmludGVnZXIoUHJlY05vKV0gICAgICAgICAgICAgICAgICAgICAgICAgIyBNYWtlIFByZWNpbmN0IG51bWJlciBhIG51bWJlcgpFeDNEYXRhLmR0WyxQcmVjTmFtZSA6PSBzdWJzdHIoUHJlY05hbWUsNCxzdG9wPTQwTCldICAgICAgICAgICAgICAjIFN0cmlwIGR1cGxpY2F0ZSBpbmZvIGZyb20gTmFtZQpFeDNEYXRhLmR0CmBgYAoKCg==