Panoramica (sparklyr/dplyr)

dplyr è un pacchetto R per lavorare con i dati strutturati all’interno e all’esterno di R. dplyr rende la manipolazione dei dati per gli utenti R facile, coerente e performante. Con dplyr come interfaccia per manipolare Spark DataFrames, puoi:

Flights Data

Questa guida mostrerà alcuni dei verbi di manipolazione dei dati di base di dplyr usando i dati di nycflights13 pacchetto R. Questo pacchetto contiene dati per tutti i 336.776 voli in partenza da New York City nel 2013. Include anche utili metadati su compagnie aeree, aeroporti, meteo e aerei.

Connettiti al cluster e copia i dati dei voli utilizzando la funzione copy_to. Avvertenza: i dati di volo in nycflights13 sono utili per le dimostrazioni di dplyr perché sono piccoli, ma in pratica i dati di grandi dimensioni dovrebbero essere copiati raramente direttamente dagli oggetti R.

library(sparklyr)
library(dplyr)
library(nycflights13)
library(ggplot2)
sc <- spark_connect(master = "local", version = "2.0.0")
flights1 <- copy_to(sc, flights, "flights1")
airlines <- copy_to(sc, airlines, "airlines")

dplyr Verbs

Quando è connesso a Spark DataFrame, dplyr traduce i comandi in istruzioni Spark SQL. Le remote data sources utilizzano esattamente gli stessi cinque verbi delle local data sources. Ecco i cinque verbi con i corrispondenti comandi SQL:

  • select ~ SELECT
  • filter ~ WHERE
  • arrange ~ ORDER
  • group_by ~ GROUP BY
  • mutate ~ operators: +, *, log, etc.
  • summarise ~ aggregators: sum, min, sd, etc.
flights1 %>%
  select(month, day, carrier, air_time) %>%
  filter(month == 5, day == 17, carrier %in% c('UA', 'WN', 'AA', 'DL')) %>%
  arrange(carrier) %>%
  mutate(air_time_hours = air_time / 60)
flights1 %>%
  group_by(month) %>%
  summarize(count = n(), mean_dep_delay = mean(dep_delay), sd_dep_delay = sd(dep_delay)) %>%
  arrange(month)

window functions

dplyr supporta le funzioni della finestra di Spark SQL. Le funzioni della finestra vengono utilizzate insieme a mutate e filter per risolvere una vasta gamma di problemi.

# Rank the top three largest delays for each carrier
flights1 %>%
  group_by(carrier) %>%
  mutate(rank = rank(desc(dep_delay))) %>%
  filter(rank <= 3) %>%
  select(carrier, year, month, day, dep_delay, rank)

SQL Translation

dplyr sa anche come convertire le funzioni R in Spark SQL:

sql0 <- flights1 %>%
  select(carrier, dep_delay, origin) %>%
  mutate(target = ifelse(origin == "JFK", 1L, 0L)) %>% # case statement
  mutate(rank = rank(desc(dep_delay))) %>% # windows function
  left_join(airlines, by = "carrier") # join

dbplyr::sql_render(sql0)

Laziness

Quando lavora con i database, dplyr cerca di essere il più pigro possibile. - Non trasferisce mai i dati in R senza richiederlo esplicitamente. - Ritarda a fare qualsiasi lavoro fino all’ultimo momento: raccoglie tutto ciò che vuoi fare e poi lo invia al database in un solo passaggio Ad esempio, prendi il seguente codice:

sql1 <- flights1 %>%
  select(origin, dest, month, day, air_time) %>%
  filter(origin == "JFK", dest == "SFO", air_time > 0) %>%
  arrange(desc(air_time))

sql2 <- sql1 %>%
  mutate(air_time_hours = air_time / 60)

sql2

Register e cashe

sdf_register(sql2, "jfk2sfo") #register the table
tbl_cache(sc, "jfk2sfo") # cashe the table 
jfk2sfo_tbl <- tbl(sc, "jfk2sfo") # assign a reference to the table

Porta i dati da Spark a R

collect () esegue la query Spark e restituisce i risultati a R per ulteriori analisi e visualizzazione.

jfk2sfo <- collect(jfk2sfo_tbl)
jfk2sfo

Machine learning

È possibile orchestrare algoritmi di machine learning in un cluster Spark tramite le funzioni machine learning all’interno di sparklyr. Queste funzioni si connettono a un set di API di alto livello basate su DataFrames che consentono di creare e ottimizzare i flussi di lavoro di machine learning.

  • Ecco un esempio in cui usiamo ml_linear_regression per adattare un modello di regressione lineare. Useremo il dataset mtcars e vediamo se possiamo prevedere il consumo di carburante di una vettura (mpg) in base al suo peso (wt) e il numero di cilindri che il motore contiene (cil). In ogni caso, assumeremo che la relazione tra mpg e ciascuna delle nostre funzionalità sia lineare.
# copiamo mtcars nel spark
mtcars_tbl <- copy_to(sc, mtcars)

# trasforma il nostro dataset e quindi esegui la partizione in 'training', 'test'
partitions <- mtcars_tbl %>%
  filter(hp >= 100) %>%
  mutate(cyl8 = cyl == 8) %>%
  sdf_partition(training = 0.5, test = 0.5, seed = 1099)

# adatta un modello lineare al training dataset
fit <- partitions$training %>%
  ml_linear_regression(response = "mpg", features = c("wt", "cyl"))
fit

we can use summary() to learn a bit more about the quality of our fit, and the statistical significance of each of our predictors.

summary(fit)

Usare pachetti non-spparkly


In questo caso usando il pacchetto dplyr Le query fatte sopra, quando si lavora con il dataset di nycflights13 danno lo stesso risultato di quando si lavora senza il pacchetto sparklyr aggiunto ad esso. Ma ci sono alcune differenze, per esempio guardando la query sotto

delay <- flights_tbl %>%   
group_by(tailnum) %>%  
summarise(count = n(), dist = mean(distance), delay = 
mean(arr_delay)) %>% 
 filter(count > 20, dist < 2000, !is.na(delay)) %>%  collect
nrow(delay)  
[1] 2961
summary(delay)
   tailnum              count            dist            delay         
 Length:2961        Min.   : 21.0   Min.   : 173.1   Min.   :-17.0909  
 Class :character   1st Qu.: 41.0   1st Qu.: 635.1   1st Qu.:  0.6154  
 Mode  :character   Median : 78.0   Median : 980.9   Median :  5.2581  
                    Mean   :103.6   Mean   : 986.0   Mean   :  6.5679  
                    3rd Qu.:126.0   3rd Qu.:1287.0   3rd Qu.: 11.3038  
                    Max.   :575.0   Max.   :1999.9   Max.   : 59.1219  
delay_1 <- flights_tbl_1 %>%   group_by(tailnum) %>%  
summarise(count = n(), dist = mean(distance), 
delay = mean(arr_delay)) %>%  
filter(count > 20, dist < 2000, !is.na(delay))
nrow(delay_1) 
[1] 1319
summary(delay_1)
   tailnum              count             dist            delay          
 Length:1319        Min.   : 21.00   Min.   : 173.1   Min.   :-17.09091  
 Class :character   1st Qu.: 34.00   1st Qu.: 818.4   1st Qu.: -0.00472  
 Mode  :character   Median : 60.00   Median :1084.9   Median :  3.92593  
                    Mean   : 70.56   Mean   :1075.6   Mean   :  5.27312  
                    3rd Qu.: 99.00   3rd Qu.:1405.5   3rd Qu.:  9.06380  
                    Max.   :311.00   Max.   :1999.9   Max.   : 59.12195  

Disegna i grafici per entrambi i casi:

ggplot(delay, aes(dist, delay)) + 
 geom_point(aes(size = count), alpha = 1/2) +  
geom_smooth() +  scale_size_area(max_size = 2) +
ggtitle("sparklyr/dplyr")

ggplot(delay_1, aes(dist, delay)) +  
geom_point(aes(size = count), alpha = 1/2) +  
geom_smooth() +  scale_size_area(max_size = 2) +
ggtitle("dplyr")

Le differenze

  • Spark ha un impostazione na.rm = TRUE by default per tutti operazioni arimetiche; i valori NA sono già stati rimossi Il risultato sara uguale se mettiamo na.rm = TRUE sul codice equivalente in R

  • spaklyr è un’implementazione R dell’interfaccia di Spark. Spark in sé è una soluzione che ti permette di lavorare con i big data (pensa a terabyte o persino a petabyte) che è semplicemente impossibile su una singola macchina. Quindi useresti sparklyr quando e se non vuoi lavorare direttamente con Spark (attraverso Scala, ad esempio), ma vuoi rimanere nell’ecosistema R.

  • Inoltre, dplyr e sparklyr non sono opposti l’uno all’altro. Puoi (e probabilmente lo faranno) usarli insieme. Per quanto riguarda Spark, l’aumento della velocità di calcolo - in realtà non è il caso in senso stretto. Ad esempio, se vuoi costruire un modello e i dati si adattano alla memoria della tua macchina (per esempio, sono solo 100k righe), è più veloce farlo localmente e non preoccuparti di Spark

LS0tCnRpdGxlOiAic3BhcmtseXIvZHBseXIgVlMgcGFjY2hldHRpIG5vbi1zcGFya2x5ciIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShzcGFya2x5cikKbGlicmFyeShkcGx5cikKc2MgPC0gc3BhcmtfY29ubmVjdChtYXN0ZXIgPSAibG9jYWwiLCB2ZXJzaW9uID0gIjIuMC4wIikKYGBgCgoKIyMgUGFub3JhbWljYSAoc3BhcmtseXIvZHBseXIpCgoqKmRwbHlyKiogw6ggdW4gcGFjY2hldHRvIFIgcGVyIGxhdm9yYXJlIGNvbiBpIGRhdGkgc3RydXR0dXJhdGkgYWxsJ2ludGVybm8gZSBhbGwnZXN0ZXJubyBkaSBSLiBkcGx5ciByZW5kZSBsYSBtYW5pcG9sYXppb25lIGRlaSBkYXRpIHBlciBnbGkgdXRlbnRpIFIgZmFjaWxlLCBjb2VyZW50ZSBlIHBlcmZvcm1hbnRlLiBDb24gZHBseXIgY29tZSBpbnRlcmZhY2NpYSBwZXIgbWFuaXBvbGFyZSBTcGFyayBEYXRhRnJhbWVzLCBwdW9pOgoKLSBTZWxlemlvbmFyZSwgZmlsdHJhcmUgZSBhZ2dyZWdhcmUgaSBkYXRpCi0gVXNhcmUgbGUgZnVuemlvbmkgd2luZG93IGZ1bmN0aW9ucyAoYWQgZXMuIHNhbXBsaW5nKQotIEVzZWd1aXJlIGpvaW4gc3UgRGF0YUZyYW1lcwotIFJhY2NvZ2xpZXJlIGRhdGkgZGEgU3BhcmsgaW4gUgoKIyMjRmxpZ2h0cyBEYXRhCgpRdWVzdGEgZ3VpZGEgbW9zdHJlcsOgIGFsY3VuaSBkZWkgdmVyYmkgZGkgbWFuaXBvbGF6aW9uZSBkZWkgZGF0aSBkaSBiYXNlIGRpIGRwbHlyIHVzYW5kbyBpIGRhdGkgZGkgKipueWNmbGlnaHRzMTMqKiBwYWNjaGV0dG8gUi4gUXVlc3RvIHBhY2NoZXR0byBjb250aWVuZSBkYXRpIHBlciB0dXR0aSBpIDMzNi43NzYgdm9saSBpbiBwYXJ0ZW56YSBkYSBOZXcgWW9yayBDaXR5IG5lbCAyMDEzLiBJbmNsdWRlIGFuY2hlIHV0aWxpIG1ldGFkYXRpIHN1IGNvbXBhZ25pZSBhZXJlZSwgYWVyb3BvcnRpLCBtZXRlbyBlIGFlcmVpLgoKQ29ubmV0dGl0aSBhbCBjbHVzdGVyIGUgY29waWEgaSBkYXRpIGRlaSB2b2xpIHV0aWxpenphbmRvIGxhIGZ1bnppb25lICoqY29weV90byoqLiBBdnZlcnRlbnphOiBpIGRhdGkgZGkgdm9sbyBpbiBueWNmbGlnaHRzMTMgc29ubyB1dGlsaSBwZXIgbGUgZGltb3N0cmF6aW9uaSBkaSBkcGx5ciBwZXJjaMOpIHNvbm8gcGljY29saSwgbWEgaW4gcHJhdGljYSBpIGRhdGkgZGkgZ3JhbmRpIGRpbWVuc2lvbmkgZG92cmViYmVybyBlc3NlcmUgY29waWF0aSByYXJhbWVudGUgZGlyZXR0YW1lbnRlIGRhZ2xpIG9nZ2V0dGkgUi4KIAogCmBgYHtyfQpsaWJyYXJ5KHNwYXJrbHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykKbGlicmFyeShnZ3Bsb3QyKQpzYyA8LSBzcGFya19jb25uZWN0KG1hc3RlciA9ICJsb2NhbCIsIHZlcnNpb24gPSAiMi4wLjAiKQpmbGlnaHRzMSA8LSBjb3B5X3RvKHNjLCBmbGlnaHRzLCAiZmxpZ2h0czEiKQphaXJsaW5lcyA8LSBjb3B5X3RvKHNjLCBhaXJsaW5lcywgImFpcmxpbmVzIikKYGBgCiMjIyBkcGx5ciBWZXJicwoKUXVhbmRvIMOoIGNvbm5lc3NvIGEgU3BhcmsgRGF0YUZyYW1lLCBkcGx5ciB0cmFkdWNlIGkgY29tYW5kaSBpbiBpc3RydXppb25pIFNwYXJrIFNRTC4gTGUgcmVtb3RlIGRhdGEgc291cmNlcyB1dGlsaXp6YW5vIGVzYXR0YW1lbnRlIGdsaSBzdGVzc2kgY2lucXVlIHZlcmJpIGRlbGxlICBsb2NhbCBkYXRhIHNvdXJjZXMuIEVjY28gaSBjaW5xdWUgdmVyYmkgY29uIGkgY29ycmlzcG9uZGVudGkgY29tYW5kaSBTUUw6CgotIHNlbGVjdCB+IFNFTEVDVAotIGZpbHRlciB+IFdIRVJFCi0gYXJyYW5nZSB+IE9SREVSCi0gZ3JvdXBfYnkgfiBHUk9VUCBCWQotIG11dGF0ZSB+IG9wZXJhdG9yczogKywgKiwgbG9nLCBldGMuCi0gc3VtbWFyaXNlIH4gYWdncmVnYXRvcnM6IHN1bSwgbWluLCBzZCwgZXRjLgoKCmBgYHtyfQpmbGlnaHRzMSAlPiUKICBzZWxlY3QobW9udGgsIGRheSwgY2FycmllciwgYWlyX3RpbWUpICU+JQogIGZpbHRlcihtb250aCA9PSA1LCBkYXkgPT0gMTcsIGNhcnJpZXIgJWluJSBjKCdVQScsICdXTicsICdBQScsICdETCcpKSAlPiUKICBhcnJhbmdlKGNhcnJpZXIpICU+JQogIG11dGF0ZShhaXJfdGltZV9ob3VycyA9IGFpcl90aW1lIC8gNjApCmBgYApgYGB7cn0KZmxpZ2h0czEgJT4lCiAgZ3JvdXBfYnkobW9udGgpICU+JQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSwgbWVhbl9kZXBfZGVsYXkgPSBtZWFuKGRlcF9kZWxheSksIHNkX2RlcF9kZWxheSA9IHNkKGRlcF9kZWxheSkpICU+JQogIGFycmFuZ2UobW9udGgpCmBgYAoKIyMjIyB3aW5kb3cgZnVuY3Rpb25zICMjIyMKCioqZHBseXIqKiBzdXBwb3J0YSBsZSBmdW56aW9uaSBkZWxsYSBmaW5lc3RyYSBkaSBTcGFyayBTUUwuIExlIGZ1bnppb25pIGRlbGxhIGZpbmVzdHJhIHZlbmdvbm8gdXRpbGl6emF0ZSBpbnNpZW1lIGEgKm11dGF0ZSogZSAqZmlsdGVyKiBwZXIgcmlzb2x2ZXJlIHVuYSB2YXN0YSBnYW1tYSBkaSBwcm9ibGVtaS4KCgpgYGB7cn0KIyBSYW5rIHRoZSB0b3AgdGhyZWUgbGFyZ2VzdCBkZWxheXMgZm9yIGVhY2ggY2FycmllcgpmbGlnaHRzMSAlPiUKICBncm91cF9ieShjYXJyaWVyKSAlPiUKICBtdXRhdGUocmFuayA9IHJhbmsoZGVzYyhkZXBfZGVsYXkpKSkgJT4lCiAgZmlsdGVyKHJhbmsgPD0gMykgJT4lCiAgc2VsZWN0KGNhcnJpZXIsIHllYXIsIG1vbnRoLCBkYXksIGRlcF9kZWxheSwgcmFuaykKYGBgCiMjIyBTUUwgVHJhbnNsYXRpb24gCiAKZHBseXIgc2EgYW5jaGUgY29tZSBjb252ZXJ0aXJlIGxlIGZ1bnppb25pIFIgaW4gU3BhcmsgU1FMOgoKYGBge3J9CnNxbDAgPC0gZmxpZ2h0czEgJT4lCiAgc2VsZWN0KGNhcnJpZXIsIGRlcF9kZWxheSwgb3JpZ2luKSAlPiUKICBtdXRhdGUodGFyZ2V0ID0gaWZlbHNlKG9yaWdpbiA9PSAiSkZLIiwgMUwsIDBMKSkgJT4lICMgY2FzZSBzdGF0ZW1lbnQKICBtdXRhdGUocmFuayA9IHJhbmsoZGVzYyhkZXBfZGVsYXkpKSkgJT4lICMgd2luZG93cyBmdW5jdGlvbgogIGxlZnRfam9pbihhaXJsaW5lcywgYnkgPSAiY2FycmllciIpICMgam9pbgoKZGJwbHlyOjpzcWxfcmVuZGVyKHNxbDApCmBgYAoKIAojIyMgTGF6aW5lc3MKUXVhbmRvIGxhdm9yYSBjb24gaSBkYXRhYmFzZSwgZHBseXIgY2VyY2EgZGkgZXNzZXJlIGlsIHBpw7kgcGlncm8gcG9zc2liaWxlLgotIE5vbiB0cmFzZmVyaXNjZSBtYWkgaSBkYXRpIGluIFIgc2VuemEgcmljaGllZGVybG8gZXNwbGljaXRhbWVudGUuCi0gUml0YXJkYSBhIGZhcmUgcXVhbHNpYXNpIGxhdm9ybyBmaW5vIGFsbCd1bHRpbW8gbW9tZW50bzogcmFjY29nbGllIHR1dHRvIGNpw7IgY2hlIHZ1b2kgZmFyZSBlIHBvaSBsbyBpbnZpYSBhbCBkYXRhYmFzZSBpbiB1biBzb2xvIHBhc3NhZ2dpbwpBZCBlc2VtcGlvLCBwcmVuZGkgaWwgc2VndWVudGUgY29kaWNlOgoKYGBge3J9CnNxbDEgPC0gZmxpZ2h0czEgJT4lCiAgc2VsZWN0KG9yaWdpbiwgZGVzdCwgbW9udGgsIGRheSwgYWlyX3RpbWUpICU+JQogIGZpbHRlcihvcmlnaW4gPT0gIkpGSyIsIGRlc3QgPT0gIlNGTyIsIGFpcl90aW1lID4gMCkgJT4lCiAgYXJyYW5nZShkZXNjKGFpcl90aW1lKSkKCnNxbDIgPC0gc3FsMSAlPiUKICBtdXRhdGUoYWlyX3RpbWVfaG91cnMgPSBhaXJfdGltZSAvIDYwKQoKc3FsMgpgYGAKCiMjIyBSZWdpc3RlciBlICBjYXNoZSAKCmBgYHtyfQpzZGZfcmVnaXN0ZXIoc3FsMiwgImpmazJzZm8iKSAjcmVnaXN0ZXIgdGhlIHRhYmxlCnRibF9jYWNoZShzYywgImpmazJzZm8iKSAjIGNhc2hlIHRoZSB0YWJsZSAKamZrMnNmb190YmwgPC0gdGJsKHNjLCAiamZrMnNmbyIpICMgYXNzaWduIGEgcmVmZXJlbmNlIHRvIHRoZSB0YWJsZQpgYGAKIyMjIFBvcnRhIGkgZGF0aSBkYSBTcGFyayBhIFIKCmNvbGxlY3QgKCkgZXNlZ3VlIGxhIHF1ZXJ5IFNwYXJrIGUgcmVzdGl0dWlzY2UgaSByaXN1bHRhdGkgYSBSIHBlciB1bHRlcmlvcmkgYW5hbGlzaSBlIHZpc3VhbGl6emF6aW9uZS4KCmBgYHtyfQpqZmsyc2ZvIDwtIGNvbGxlY3QoamZrMnNmb190YmwpCmpmazJzZm8KYGBgCiMjIyBNYWNoaW5lIGxlYXJuaW5nIAoKw4ggcG9zc2liaWxlIG9yY2hlc3RyYXJlIGFsZ29yaXRtaSBkaSBtYWNoaW5lIGxlYXJuaW5nIGluIHVuIGNsdXN0ZXIgU3BhcmsgdHJhbWl0ZSBsZSBmdW56aW9uaSBtYWNoaW5lIGxlYXJuaW5nIGFsbCdpbnRlcm5vIGRpIHNwYXJrbHlyLiBRdWVzdGUgZnVuemlvbmkgc2kgY29ubmV0dG9ubyBhIHVuIHNldCBkaSBBUEkgZGkgYWx0byBsaXZlbGxvIGJhc2F0ZSBzdSBEYXRhRnJhbWVzIGNoZSBjb25zZW50b25vIGRpIGNyZWFyZSBlIG90dGltaXp6YXJlIGkgZmx1c3NpIGRpIGxhdm9ybyBkaSBtYWNoaW5lIGxlYXJuaW5nLgoKLSBFY2NvIHVuIGVzZW1waW8gaW4gY3VpIHVzaWFtbyBtbF9saW5lYXJfcmVncmVzc2lvbiBwZXIgYWRhdHRhcmUgdW4gbW9kZWxsbyBkaSByZWdyZXNzaW9uZSBsaW5lYXJlLiBVc2VyZW1vIGlsIGRhdGFzZXQgbXRjYXJzIGUgdmVkaWFtbyBzZSBwb3NzaWFtbyBwcmV2ZWRlcmUgaWwgY29uc3VtbyBkaSBjYXJidXJhbnRlIGRpIHVuYSB2ZXR0dXJhIChtcGcpIGluIGJhc2UgYWwgc3VvIHBlc28gKHd0KSBlIGlsIG51bWVybyBkaSBjaWxpbmRyaSBjaGUgaWwgbW90b3JlIGNvbnRpZW5lIChjaWwpLiBJbiBvZ25pIGNhc28sIGFzc3VtZXJlbW8gY2hlIGxhIHJlbGF6aW9uZSB0cmEgbXBnIGUgY2lhc2N1bmEgZGVsbGUgbm9zdHJlIGZ1bnppb25hbGl0w6Agc2lhIGxpbmVhcmUuCgpgYGB7cn0KIyBjb3BpYW1vIG10Y2FycyBuZWwgc3BhcmsKbXRjYXJzX3RibCA8LSBjb3B5X3RvKHNjLCBtdGNhcnMpCgojIHRyYXNmb3JtYSBpbCBub3N0cm8gZGF0YXNldCBlIHF1aW5kaSBlc2VndWkgbGEgcGFydGl6aW9uZSBpbiAndHJhaW5pbmcnLCAndGVzdCcKcGFydGl0aW9ucyA8LSBtdGNhcnNfdGJsICU+JQogIGZpbHRlcihocCA+PSAxMDApICU+JQogIG11dGF0ZShjeWw4ID0gY3lsID09IDgpICU+JQogIHNkZl9wYXJ0aXRpb24odHJhaW5pbmcgPSAwLjUsIHRlc3QgPSAwLjUsIHNlZWQgPSAxMDk5KQoKIyBhZGF0dGEgdW4gbW9kZWxsbyBsaW5lYXJlIGFsIHRyYWluaW5nIGRhdGFzZXQKZml0IDwtIHBhcnRpdGlvbnMkdHJhaW5pbmcgJT4lCiAgbWxfbGluZWFyX3JlZ3Jlc3Npb24ocmVzcG9uc2UgPSAibXBnIiwgZmVhdHVyZXMgPSBjKCJ3dCIsICJjeWwiKSkKZml0CmBgYAoKd2UgY2FuIHVzZSBzdW1tYXJ5KCkgdG8gbGVhcm4gYSBiaXQgbW9yZSBhYm91dCB0aGUgcXVhbGl0eSBvZiBvdXIgZml0LCBhbmQgdGhlIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSBvZiBlYWNoIG9mIG91ciBwcmVkaWN0b3JzLgpgYGB7cn0Kc3VtbWFyeShmaXQpCmBgYAoKKioqIAogCiMjIFVzYXJlIHBhY2hldHRpIG5vbi1zcHBhcmtseSAgCgoqKiogCgpJbiBxdWVzdG8gY2FzbyB1c2FuZG8gaWwgcGFjY2hldHRvICoqZHBseXIqKgpMZSBxdWVyeSBmYXR0ZSBzb3ByYSwgcXVhbmRvIHNpIGxhdm9yYSBjb24gaWwgZGF0YXNldCBkaSAqbnljZmxpZ2h0czEzKiBkYW5ubyBsbyBzdGVzc28gcmlzdWx0YXRvIGRpIHF1YW5kbyBzaSBsYXZvcmEgc2VuemEgaWwgcGFjY2hldHRvICoqc3BhcmtseXIqKiBhZ2dpdW50byBhZCBlc3NvLiBNYSBjaSBzb25vIGFsY3VuZSBkaWZmZXJlbnplLCBwZXIgZXNlbXBpbyBndWFyZGFuZG8gbGEgcXVlcnkgc290dG8KCi0gUXVhbmRvIHNpIGxhdm9yYSBjb24gKipzcGFya2x5ciAvIGRwbHlyKio6CmBgYHtyfQpkZWxheSA8LSBmbGlnaHRzX3RibCAlPiUgICAKZ3JvdXBfYnkodGFpbG51bSkgJT4lICAKc3VtbWFyaXNlKGNvdW50ID0gbigpLCBkaXN0ID0gbWVhbihkaXN0YW5jZSksIGRlbGF5ID0gCm1lYW4oYXJyX2RlbGF5KSkgJT4lIAogZmlsdGVyKGNvdW50ID4gMjAsIGRpc3QgPCAyMDAwLCAhaXMubmEoZGVsYXkpKSAlPiUgIGNvbGxlY3QKCgpgYGAKYGBge3J9Cm5yb3coZGVsYXkpICAKCnN1bW1hcnkoZGVsYXkpCmBgYAoKLSBRdWFuZG8gc2kgbGF2b3JhIGNvbiAqKmRwbHlyKio6CgpgYGB7cn0KZGVsYXlfMSA8LSBmbGlnaHRzX3RibF8xICU+JSAgIGdyb3VwX2J5KHRhaWxudW0pICU+JSAgCnN1bW1hcmlzZShjb3VudCA9IG4oKSwgZGlzdCA9IG1lYW4oZGlzdGFuY2UpLCAKZGVsYXkgPSBtZWFuKGFycl9kZWxheSkpICU+JSAgCmZpbHRlcihjb3VudCA+IDIwLCBkaXN0IDwgMjAwMCwgIWlzLm5hKGRlbGF5KSkKCmBgYApgYGB7cn0KbnJvdyhkZWxheV8xKSAKCnN1bW1hcnkoZGVsYXlfMSkKCmBgYAoKIyMjIERpc2VnbmEgaSBncmFmaWNpIHBlciBlbnRyYW1iaSBpIGNhc2k6IAoKYGBge3J9CmdncGxvdChkZWxheSwgYWVzKGRpc3QsIGRlbGF5KSkgKyAKIGdlb21fcG9pbnQoYWVzKHNpemUgPSBjb3VudCksIGFscGhhID0gMS8yKSArICAKZ2VvbV9zbW9vdGgoKSArICBzY2FsZV9zaXplX2FyZWEobWF4X3NpemUgPSAyKSArCmdndGl0bGUoInNwYXJrbHlyL2RwbHlyIikKCmdncGxvdChkZWxheV8xLCBhZXMoZGlzdCwgZGVsYXkpKSArICAKZ2VvbV9wb2ludChhZXMoc2l6ZSA9IGNvdW50KSwgYWxwaGEgPSAxLzIpICsgIApnZW9tX3Ntb290aCgpICsgIHNjYWxlX3NpemVfYXJlYShtYXhfc2l6ZSA9IDIpICsKZ2d0aXRsZSgiZHBseXIiKQoKCmBgYAoKIyMjIExlIGRpZmZlcmVuemUKLSBTcGFyayBoYSB1biBpbXBvc3RhemlvbmUgbmEucm0gPSBUUlVFICBieSBkZWZhdWx0IHBlciB0dXR0aSBvcGVyYXppb25pIGFyaW1ldGljaGU7IGkgdmFsb3JpIE5BIHNvbm8gZ2nDoCBzdGF0aSByaW1vc3NpCklsIHJpc3VsdGF0byBzYXJhIHVndWFsZSBzZSBtZXR0aWFtbyBuYS5ybSA9IFRSVUUgIHN1bCBjb2RpY2UgZXF1aXZhbGVudGUgaW4gUgoKLSBzcGFrbHlyIMOoIHVuJ2ltcGxlbWVudGF6aW9uZSBSIGRlbGwnaW50ZXJmYWNjaWEgZGkgU3BhcmsuIFNwYXJrIGluIHPDqSDDqCB1bmEgc29sdXppb25lIGNoZSB0aSBwZXJtZXR0ZSBkaSBsYXZvcmFyZSBjb24gaSBiaWcgZGF0YSAocGVuc2EgYSB0ZXJhYnl0ZSBvIHBlcnNpbm8gYSBwZXRhYnl0ZSkgY2hlIMOoIHNlbXBsaWNlbWVudGUgaW1wb3NzaWJpbGUgc3UgdW5hIHNpbmdvbGEgbWFjY2hpbmEuIFF1aW5kaSB1c2VyZXN0aSBzcGFya2x5ciBxdWFuZG8gZSBzZSBub24gdnVvaSBsYXZvcmFyZSBkaXJldHRhbWVudGUgY29uIFNwYXJrIChhdHRyYXZlcnNvIFNjYWxhLCBhZCBlc2VtcGlvKSwgbWEgdnVvaSByaW1hbmVyZSBuZWxsJ2Vjb3Npc3RlbWEgUi4KCi0gSW5vbHRyZSwgZHBseXIgZSBzcGFya2x5ciBub24gc29ubyBvcHBvc3RpIGwndW5vIGFsbCdhbHRyby4gUHVvaSAoZSBwcm9iYWJpbG1lbnRlIGxvIGZhcmFubm8pIHVzYXJsaSBpbnNpZW1lLgpQZXIgcXVhbnRvIHJpZ3VhcmRhIFNwYXJrLCBsJ2F1bWVudG8gZGVsbGEgdmVsb2NpdMOgIGRpIGNhbGNvbG8gLSBpbiByZWFsdMOgIG5vbiDDqCBpbCBjYXNvIGluIHNlbnNvIHN0cmV0dG8uIEFkIGVzZW1waW8sIHNlIHZ1b2kgY29zdHJ1aXJlIHVuIG1vZGVsbG8gZSBpIGRhdGkgc2kgYWRhdHRhbm8gYWxsYSBtZW1vcmlhIGRlbGxhIHR1YSBtYWNjaGluYSAocGVyIGVzZW1waW8sIHNvbm8gc29sbyAxMDBrIHJpZ2hlKSwgw6ggcGnDuSB2ZWxvY2UgZmFybG8gbG9jYWxtZW50ZSBlIG5vbiBwcmVvY2N1cGFydGkgZGkgU3Bhcms=