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:
- Selezionare, filtrare e aggregare i dati
- Usare le funzioni window functions (ad es. sampling)
- Eseguire join su DataFrames
- Raccogliere dati da Spark in R
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
- Quando si lavora con sparklyr / dplyr:
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
- Quando si lavora con dplyr:
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=