1. Packages
Package adalah satu set fungsi yang bisa kita install dan panggil di dalam R untuk melakukan hal-hal spesifik. Saat kita baru menginstall R, sudah ada beberapa package yang otomatis terinstall, dan package-package yang sudah terinstall dapat dilihat di panel berjudul packages.
Selain package-package bawaan ini, banyak package lain yang tersebar di internet yang dapat kita install dan gunakan di R. Seiring berjalannya waktu, kita akan terus menambah dan memperbarui package yang kita punya di R kita.
1.1. Menginstall package
Sebagai contoh kita sekarang akan menginstall tidyverse, sebuah package omnibus yang berisi banyak package lain.tidyverse adalah package yang sangat penting untuk berbagai macam penggunaan, dan kebanyakan data wrangling yang dilakukan sudah bisa dilakukan dengan package ini (https://www.tidyverse.org/).
Untuk menginstall sebuah package, kita menggunakan fungsi install.packages(). Hal ini cukup dilakukan sekali saja, karena untuk seterusnya package tersebut sudah tersimpan di dalam memori R kita.
install.packages("tidyverse")
WARNING: Rtools is required to build R packages but is not currently installed. Please download and install the appropriate version of Rtools before proceeding:
https://cran.rstudio.com/bin/windows/Rtools/
Installing package into ‘C:/Users/annas/OneDrive/Documents/R/win-library/4.1’
(as ‘lib’ is unspecified)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.1/tidyverse_1.3.1.zip'
Content type 'application/zip' length 430187 bytes (420 KB)
downloaded 420 KB
package ‘tidyverse’ successfully unpacked and MD5 sums checked
The downloaded binary packages are in
C:\Users\annas\AppData\Local\Temp\RtmpAVgPck\downloaded_packages
Sekarang tidyverse akan sudah terinstall di dalam R kita, dalam versi terbarunya. Hal ini bisa dilihat di panel packages di sebelah kanan bawah.
1.2.Menyalakan package
Package yang sudah terinstall tidak akan otomatis ‘nyala’ dan bisa digunakan. Untuk melakukan hal ini, di setiap sesi R baru kita harus menyalakan package tersebut. Gunakan fungsi library().
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
-- Attaching packages ------------------------------------------------------------ tidyverse 1.3.1 --
v ggplot2 3.3.5.9000 v purrr 0.3.4
v tibble 3.1.6 v dplyr 1.0.7
v tidyr 1.1.4 v stringr 1.4.0
v readr 2.1.0 v forcats 0.5.1
-- Conflicts --------------------------------------------------------------- tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag() masks stats::lag()
Sekarang tidyverse sudah bisa digunakan. Seperti yang sudah disebut tadi, hal ini perlu dilakukan di setiap sesi. Oleh karena itu setiap kita membuat script baru, kita harus menulis lagi perintah untuk menyalakan package (bisa di awal script atau sebelum script tersebut mengeksekusi fungsi dari package yang relevan).
2. Membaca tabel
2.1. Set working directory
Sebelum kita membaca data eksternal, ada baiknya kita melakukan penyetingan working directory terlebih dahulu. Working directory adalah folder atau alamat di dalam komputer kita tempat R melakukan penyimpanan secara default. Hal ini akan berguna jika kita ingin membaca maupun menulis file baru.
Pertama, kita bisa periksa terlebih dahulu, apa working directory kita dengan getwd()
getwd()
[1] "D:/Temp/R Tutoring/Jan 21"
Kita bisa mengganti working directory kita dengan menggunakan setwd. Silahkan ganti alamatnya dengan alamat folder yang Anda inginkan!
setwd("D:/Temp/R Tutoring/Jan 21")
Kalau kita coba cek lagi di getwd(), kita akan melihat bahwa perubahan alamat working directory telah berhasil.(Belum dikasih contoh di sini karena Rnotebook, yang digunakan untuk menulis modul ini, tidak bisa menyimpan setting dari setwd() secara konsisten).
2.2. Data dari CSV dan Excel
Untuk mengambil data dari file .csv, kita bisa menggunakan fungsi read.csv. Perintah yang kita masukkan adalah seperti berikut:
jajan_csv <- read.csv("./Session 2/Jajan Q1 - Sheet1.csv")
jajan_csv
Bagian dari sintaks yang ditulis di dalam kurung menunjukkan bahwa kita sedang mengambil data dari directory tertentu. Singkatnya, "./Session 2/" menunjukkan kita sedang merujuk sesuatu di folder “Session 2”, dan Jajan Q1 - Sheet1.csv menunjukkan nama dari filenya.
Ingat kembali ke pembahasan tentang setwd. Perintah di dalam read.csv akan merujuk ke sub-folder yang dijadikan working directory untuk menentukan path ke file yang dituju, sehingga kita tidak perlu menuliskan "D:/Temp/R Tutoring/Jan 21/Session 2/Jajan Q1 - Sheet1.csv". Bagian awal hingga ‘Jan 21’ sudah cukup di setting working directory, dan tidak perlu dituliskan lagi.
Untuk file-file excel seperti ‘.xlsx’, kita perlu menggunakan fungsi yang berbeda, yaitu readxl. Package ini sudah termasuk di dalam tidyverse jadi kita cukup menginstall tidyverse saja, dan seterusnya sudah bisa menggunakan package ini. Tetapi, kita tetap harus memanggil package readxl di tiap sesi jika ingin menggunakannya.
library(readxl)
Perintah untuk memanggil file excel tidak jauh berbeda dengan file, csv, yang bisa dilihat di contoh di bawah ini dengan dataset yang sama.
jajan_xlsx <- read_xlsx("./Session 2/Jajan Q1.xlsx", sheet = "Sheet1")
jajan_xlsx
Perbedaan yang paling terlihat di sini adalah kita bisa memberikan spesifikasi sheet mana yang harus diambil. Hal ini dilakukan menggunakan sheet =. Jika tidak dispesifikasi, maka secara default read_xlsx. Akan mengambil sheet pertama.
Perlu diperhatikan juga ketika kita membaca data dari tabel, format tabel tersebut harus sederhana; satu sheet = satu tabel yang konsisten. R juga cenderung otomatis mengambil row pertama dari tabel apapun sebagai header. Namun, jika format data tidak sesuai, bisa dilakukan data wrangling. Hal ini bisa dilakukan dengan cara misalnya, kita gunakan opsi header = FALSE, yang akan membuat R otomatis membuat header baru di atas tabel kita.
Setelahnya, kita bisa menggunakan list lain, yang kita gunakan untuk me-replace nama kolom yang otomatis dibuat oleh R. Replace dengan menggunakan colnames
# Membuat list baru untuk nama kolom
nama_kolom <- c("kol_1", "kol_2", "kol_3", "kol_4", "kol_5", "kol_6")
nama_kolom
[1] "kol_1" "kol_2" "kol_3" "kol_4" "kol_5" "kol_6"
# Mengganti nama kolom dengan 'colnames'
colnames(jajan_csv_noheader) <- nama_kolom
jajan_csv_noheader
Tentunya setelahnya, rows yang berisi nama kolom sebelumnya harus di-delete. Mendelete kolom dan rows akan dibahas di bagian selanjutnya.
3. Data screening dan cleaning dasar
Umumnya, hal pertama yang kita lakukan setelah meng-ekspor tabel adalah untuk melihat apakah ada permasalahan di data. Oleh karena itu, sekarang mari kita bahas secara singkat mengenai data screening, utamanya mengecek accuracy dari data. Accuracy di sini artinya
Untungnya cukup mudah di R untuk secara cepat mendapatkan gambaran keseluruhan tentang data kita. Fungsi summary memberikan gambaran tersebut, seperti yang bisa dilihat di bawah.
summary(jajan_csv)
nama bulan jajan jumlah harga
Length:25 Length:25 Length:25 Min. :1.000 Min. : 30000
Class :character Class :character Class :character 1st Qu.:1.000 1st Qu.: 30000
Mode :character Mode :character Mode :character Median :1.000 Median : 57500
Mean :2.083 Mean : 206667
3rd Qu.:2.250 3rd Qu.: 88750
Max. :8.000 Max. :1200000
NA's :1 NA's :1
pengeluaran
Min. : 30000
1st Qu.: 75000
Median : 112500
Mean : 243958
3rd Qu.: 195000
Max. :1200000
NA's :1
Dari summary kita bisa melihat beberapa hal:
- Class dari
nama, bulan, dan jajan adalah character, sedangkan jumlah, harga, dan pengeluaran adalah numeric, karena yang langsung muncul adalah data-data summary statistics dari kolom tersebut.
- Untuk variabel yang
numeric, kita langsung bisa melihat mean, median, quantile, serta nilai min da maks-nya.
- Terdapat mising data (
NA's) di beberapa kolom
Dengan informasi ini, kita bisa tahu apa saja yang harus diubah (dibersihkan). Lebih lanjut, kita akan bahas cara-cara membersihkannya di bagian selanjutnya.
4. Manipulasi tabel (lanjut)
4.1. Menghapus row
Terdapat banyak fungsi untuk bisa menghapus row tertentu. Cara yang paling sederhana adalah dengan menggunakan operator - untuk membuat slice dari dataset yang tidak memiliki row di urutan tersebut. Ingatlah bahwa di dataset jajan_csv row kedua adalah catatan bahwa Alvin jajan burger di bulan Januari. Mari kita coba hilangkan row tersebut dengan menggunakan metode ini.
jajan_baru <- jajan_csv[-c(2), ]
jajan_baru
Beberapa fungsi lain memiliki kegunaan yang sama. Misalnya dengan fungsi slice yang merupakan bagian dari tidyverse. Sintaksnya tidak jauh beda dengan menggunakan operator -.
slice(jajan_csv, -c(2))
Kita juga bisa menghapus row berdasarkan nilai di dalam row tersebut. Misalnya, kita ingin menghapus semua catatan tentang Alvin dengan melakukan subsetting biasa, dengan operator !=.
jajan_csv[(jajan_csv$nama == "Alvin"), ]
atau menggunakan operator ! sebelum kita menspesifikasi value yang ingin dihilangkan. (perhatikan tanda != diubah menjadi ==). Kali ini kita coba untuk menghilangkan semua yang bernama Alvin dan Bene.
jajan_csv[!(jajan_csv$nama == "Alvin" | jajan_csv$nama == "Bene"), ]
Kita bisa melakukan omission menggunakan dua kondisi, dengan operator boolean &. Untuk contoh kali ini, kita coba hilangkan semua catatan tentang Alvin membeli jajan kopi, tapi kita ingin semua catatan tentang Alvin tetap ada, di luar dari jajan kopinya. Artinya, kita ingin menghapus row dengan nama = Alvin DAN jajan = kopi.
jajan_csv[!(jajan_csv$nama == "Alvin" & jajan_csv$jajan == "kopi"), ]
4.2. Menghapus kolom
Sama seperti menghapus row, kolom dapat dihapus dengan merefer ke indeksnya. Sintaksnya pun tidak begitu berbeda dengan menghapus row. Contoh, mari kita hapus kolom nama.
jajan_csv[, -c(1)]
Karena kolom memiliki nama variabel yang jelas, kita bisa menggunakan nama tersebut untuk memanggil atau menghapus kolom itu. Untuk melakukan hal ini, kita bisa menggunakan fungsi subset, fungsi yang tersedia di base R. Sintaksnya cukup simpel, cukup spesifikasikan data dan kolom yang ingin dihilangkan di dalam variabel select, di-wrap dengan -().
Selain menghapus kolom, kita juga bisa melakukan sebaliknya, yaitu membuat dataset baru yang isinya hanya kolom yang ingin kita gunakan. Tentu ini kurang lebih sama saja dengan menghapus kolom biasa, maka dari itu kita bisa menggunakan pilihan menghapus atau menyeleksi kolom, tergantung dengan kondisi. Di metode di bawah ini, kita membuat list dari nama kolom yang ingin kita simpan, lalu menggunakan list tersebut di dalam [] untuk memanggil kolom tersebut dari nama-namanya.
keep
[1] "nama" "bulan" "jajan"
Package tidyverse menyediakan opsi yang sangat fleksibel dengan fungsi select. Di fungsi ini kita bisa menyeleksi variabel berdasarkan matching parsial (mis. hanya kolom yang memiliki string tertentu di namanya), menggunakan regex, atau menggunakan fungsi. Lihatlah dokumentasinya untuk penjelasan lebih lengkap (?select)
Salah satu contoh, kita bisa mengambil hanya variabel yang bersifat numeric. Untuk melakukan ini, kita gunakan fungsi di dalam select where() untuk memberi perintah bahwa kita mencari kolom dengan kondisi tertentu.
4.3. Missing data
Missing data biasanya kita tangani dengan dua cara, yaitu menghapus atau menggantinya. Kita akan bahas cara pertama terlebih dahulu.
Menghapus missing data di sini bisa dengan dua cara, yaitu menghapus row atau kolom. Sebuah rule of thumb, jika jumlah missing data tersebar atau tidak terlalu banyak, kita bisa menggunakan opsi pertama, menghapus row. Cara yang mudah untuk melakukan ini adalah dengan na.omit, yang akan menghapus semua row yang memiliki missing data tanpa terkecuali.
na.omit(jajan_csv)
Namun, jika missing data terkonsentrasi di satu variabel, kita bisa menghapus kolom yang relevan. Untuk contoh di bawah ini, kita coba gunakan dataset yang merfleksikan kondisi tersebut. Ingat bahwa kita bisa mendapatkan jumlah missing data dengan menggunakan summary terlebih dahulu.
jajan_missing <- read.csv("./Session 2/Jajan Q1 - Sheet1 (missing).csv")
summary(jajan_missing)
nama bulan jajan jumlah harga
Length:25 Length:25 Length:25 Min. :1.000 Min. : 30000
Class :character Class :character Class :character 1st Qu.:1.000 1st Qu.: 30000
Mode :character Mode :character Mode :character Median :1.000 Median : 57500
Mean :2.083 Mean : 206667
3rd Qu.:2.250 3rd Qu.: 88750
Max. :8.000 Max. :1200000
NA's :1 NA's :1
pengeluaran
Min. : 30000
1st Qu.: 80000
Median : 120000
Mean : 245714
3rd Qu.: 225000
Max. :1200000
NA's :11
Kita lihat bahwa kita memiliki jumlah missing data yang banyak di pengeluaran. Jumlah itu mencakup sampel yang signifikan, hampir setengah dari sampel kita. Sehingga, lebih bijak untuk kita drop saja kolomnya, daripada harus menghabisi sampel.
subset(jajan_missing, select= -(pengeluaran))
Lalu, kita bisa mengkombinasikan cara tersebut untuk membuat dataset kita benar-benar bersih dari missing data:
na.omit(subset(jajan_missing, select= -(pengeluaran)))
Jika kita lebih ingin mengganti missing data dengan nilai lain, kita bisa melakukan assignment biasa, dengan cara seperti contoh di bawah ini.
jajan_csv[is.na(jajan_csv)] <- 0
jajan_csv
4.4. Membuat kolom baru berdasarkan value di kolom lain
Kadang kita butuh membuat kategori atau nilai baru di dalam data, di luar dari hal yang kita dapat di dataset mentah.Umumnya, bisa digunakan fungsi ifelse().
Katakanlah kita ingin mengkategorisasi pengeluaran menjadi tinggi atau rendah, berdasarkan dia di atas 500 ribu rupiah atau tidak.Lalu, jika di antara 500 ribu dan 250 ribu, kita anggap ‘sedang’, dan di bawah 250 ribu kita anggap ‘rendah’.
jajan_csv$tinggi_rendah <- ifelse(jajan_csv$pengeluaran >= 500000, "Tinggi",
ifelse(jajan_csv$pengeluaran >= 250000, "Sedang",
"Rendah"))
jajan_csv
Fungsi ifelse seringkali cukup intuitif, tetapi bisa membingungkan jika kondisinya banyak. Strukturnya yang harus bersifat nested membuat mudah untuk fungsi ini menjadi terlalu banyak fungsi-di-dalam-fungsi yang membingungkan. Alternatif dari ifelse yang sering digunakan adalah mutate, bagian dari tidyverse.
Mari kita coba mutate untuk tujuan yang sama dengan contoh sebelumnya, kali ini di dataset yang berbeda.
Perhatikan bahwa di sini kita menggunakan operator %>%, yang disebut sebagai pipe. Operator ini merupakan bagian dari tidyverse, di mana artinya, setelah operator pipe ditulis, perintah selanjutnya akan merefer ke dataset yang dituju (dalam hal ini jajan_xlsx).
Selain itu, terdapat juga baris bertulisan TRUE ~ "Lainnya". Baris ini artinya “Jika nilainya tidak masuk di dalam kondisi yang disebutkan di atas, maka tulislah”Lainnya”.
jajan_xlsx %>% mutate(tinggi_rendah = case_when(
pengeluaran >= 500000 ~ "Tinggi",
pengeluaran < 500000 & pengeluaran >= 250000 ~ "Sedang",
pengeluaran < 250000 ~ "Rendah",
TRUE ~ "Lainnya"
))
4.5. Mengganti jenis variabel
Data yang kita ambil dari sumber eksternal akan secara otomatis di-assign oleh R ke jenis variabel yang paling masuk akal untuk data tersebut. Tetapi, kadang jenis data tersebut tidak sesuai dengan yang kita inginkan. Sebagai contoh, mari kita lihat lagi struktur data yang kita miliki.
summary(jajan_csv)
nama bulan jajan jumlah harga
Length:25 Length:25 Length:25 Min. :0 Min. : 0
Class :character Class :character Class :character 1st Qu.:1 1st Qu.: 30000
Mode :character Mode :character Mode :character Median :1 Median : 55000
Mean :2 Mean : 198400
3rd Qu.:2 3rd Qu.: 80000
Max. :8 Max. :1200000
pengeluaran tinggi_rendah
Min. : 0 Length:25
1st Qu.: 60000 Class :character
Median : 110000 Mode :character
Mean : 234200
3rd Qu.: 180000
Max. :1200000
Kita lihat di situ nama, bulan, dan jajan adalah character. Ini tidak ideal, karena kita mengetahui bahwa setiap Alvin misalnya, merupakan orang yang sama. Variabel jenis character tidak memperhatikan hal tersebut. Oleh karena itu, di dalam kasus ini kita lebih baik mengubah variabel-variabel tersebut menjadi factor, menggunakan perintah as.factor.
summary(jajan_csv)
nama bulan jajan jumlah harga pengeluaran
1:11 Length:25 Length:25 Min. :0 Min. : 0 Min. : 0
2: 8 Class :character Class :character 1st Qu.:1 1st Qu.: 30000 1st Qu.: 60000
3: 6 Mode :character Mode :character Median :1 Median : 55000 Median : 110000
Mean :2 Mean : 198400 Mean : 234200
3rd Qu.:2 3rd Qu.: 80000 3rd Qu.: 180000
Max. :8 Max. :1200000 Max. :1200000
tinggi_rendah
Length:25
Class :character
Mode :character
Fungsi as.factor adalah turunan dari fungsi dalam base r, yaitu as. dan factor. Sama dengan fungsi is., kita bisa menggunakan fungsi ini untuk berbagai jenis variabel, yang sering digunakan adalah sebagai berikut:
as.factor |
Mengubah variabel menjadi faktor |
as.numeric |
Mengubah variabel menjadi numerik |
as.character |
Mengubah variabel menjadi karakter |
LS0tDQp0aXRsZTogIjIuIERhdGEgRWtzdGVybmFsLCBTY3JlZW5pbmcsIGRhbiBEYXRhIENsZWFuaW5nIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIyMgQXBhIHlhbmcgYWthbiBkaXBlbGFqYXJpIGhhcmkgaW5pPw0KRGkgc2VzaSBpbmkga2l0YSBha2FuIG1lbXByYWt0ZWtrYW4gY2FyYSB1bnR1ayBtZW5nZ3VuYWthbiBkYXRhIGVrc3Rlcm5hbCBkaSBSLCBkYW4gYmViZXJhcGEgb3BlcmFzaSBkYXNhciB1bnR1ayBhbmFsaXNpcyBkYXRhLiBVbnR1ayBpdHUsIHNlc2kga2FsaSBpbmkgZGliYWdpIGRhbGFtIGJlYmVyYXBhIHRvcGlrLCBkaW11bGFpIGRlbmdhbiBtZW5nZW5hbCBmdW5nc2kgZGFuIHBhY2thZ2VzLiBEaWxhbmp1dGthbiBkZW5nYW4gY2FyYSBtZW1iYWNhIGJlcmJhZ2FpIGplbmlzIHRhYmVsLCBkYW4gYmViZXJhcGEgbWV0b2RlIG1hbmlwdWxhc2kgdGFiZWwgbGFuanV0YW4uIFRlcmFraGlyLCBraXRhIGFrYW4gbWVtcHJha3Rla2thbiBjYXJhIHVudHVrIG1lbnlpbXBhbiB0YWJlbCBkaSBSIGRhbGFtIGJlbnR1ayBla3N0ZXJuYWwuDQoNCg0KIyMgMS4gUGFja2FnZXMNClBhY2thZ2UgYWRhbGFoIHNhdHUgc2V0IGZ1bmdzaSB5YW5nIGJpc2Ega2l0YSBpbnN0YWxsIGRhbiBwYW5nZ2lsIGRpIGRhbGFtIFIgdW50dWsgbWVsYWt1a2FuIGhhbC1oYWwgc3Blc2lmaWsuIFNhYXQga2l0YSBiYXJ1IG1lbmdpbnN0YWxsIFIsIHN1ZGFoIGFkYSBiZWJlcmFwYSBwYWNrYWdlIHlhbmcgb3RvbWF0aXMgdGVyaW5zdGFsbCwgZGFuIHBhY2thZ2UtcGFja2FnZSB5YW5nIHN1ZGFoIHRlcmluc3RhbGwgZGFwYXQgZGlsaWhhdCBkaSBwYW5lbCBiZXJqdWR1bCBgcGFja2FnZXNgLiANCg0KU2VsYWluIHBhY2thZ2UtcGFja2FnZSBiYXdhYW4gaW5pLCBiYW55YWsgcGFja2FnZSBsYWluIHlhbmcgdGVyc2ViYXIgZGkgaW50ZXJuZXQgeWFuZyBkYXBhdCBraXRhIGluc3RhbGwgZGFuIGd1bmFrYW4gZGkgUi4gU2VpcmluZyBiZXJqYWxhbm55YSB3YWt0dSwga2l0YSBha2FuIHRlcnVzIG1lbmFtYmFoIGRhbiBtZW1wZXJiYXJ1aSBwYWNrYWdlIHlhbmcga2l0YSBwdW55YSBkaSBSIGtpdGEuIA0KDQojIyMgMS4xLiBNZW5naW5zdGFsbCBwYWNrYWdlDQpTZWJhZ2FpIGNvbnRvaCBraXRhIHNla2FyYW5nIGFrYW4gbWVuZ2luc3RhbGwgYHRpZHl2ZXJzZWAsIHNlYnVhaCBwYWNrYWdlIG9tbmlidXMgeWFuZyBiZXJpc2kgYmFueWFrIHBhY2thZ2UgbGFpbi5gdGlkeXZlcnNlYCBhZGFsYWggcGFja2FnZSB5YW5nIHNhbmdhdCBwZW50aW5nIHVudHVrIGJlcmJhZ2FpIG1hY2FtIHBlbmdndW5hYW4sIGRhbiBrZWJhbnlha2FuIGRhdGEgd3JhbmdsaW5nIHlhbmcgZGlsYWt1a2FuIHN1ZGFoIGJpc2EgZGlsYWt1a2FuIGRlbmdhbiBwYWNrYWdlIGluaSAoaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pLg0KDQpVbnR1ayBtZW5naW5zdGFsbCBzZWJ1YWggcGFja2FnZSwga2l0YSBtZW5nZ3VuYWthbiBmdW5nc2kgYGluc3RhbGwucGFja2FnZXMoKWAuIEhhbCBpbmkgY3VrdXAgZGlsYWt1a2FuIHNla2FsaSBzYWphLCBrYXJlbmEgdW50dWsgc2V0ZXJ1c255YSBwYWNrYWdlIHRlcnNlYnV0IHN1ZGFoIHRlcnNpbXBhbiBkaSBkYWxhbSBtZW1vcmkgUiBraXRhLiANCg0KYGBge3IgZWNobz1UUlVFfQ0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikNCmBgYA0KDQpTZWthcmFuZyBgdGlkeXZlcnNlYCBha2FuIHN1ZGFoIHRlcmluc3RhbGwgZGkgZGFsYW0gUiBraXRhLCBkYWxhbSB2ZXJzaSB0ZXJiYXJ1bnlhLiBIYWwgaW5pIGJpc2EgZGlsaWhhdCBkaSBwYW5lbCBgcGFja2FnZXNgIGRpIHNlYmVsYWgga2FuYW4gYmF3YWguIA0KDQojIyMgMS4yLk1lbnlhbGFrYW4gcGFja2FnZQ0KUGFja2FnZSB5YW5nIHN1ZGFoIHRlcmluc3RhbGwgdGlkYWsgYWthbiBvdG9tYXRpcyAnbnlhbGEnIGRhbiBiaXNhIGRpZ3VuYWthbi4gVW50dWsgbWVsYWt1a2FuIGhhbCBpbmksIGRpIHNldGlhcCBzZXNpIFIgYmFydSBraXRhIGhhcnVzIG1lbnlhbGFrYW4gcGFja2FnZSB0ZXJzZWJ1dC4gR3VuYWthbiBmdW5nc2kgYGxpYnJhcnkoKWAuDQpgYGB7ciBlY2hvPVRSVUV9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQpTZWthcmFuZyBgdGlkeXZlcnNlYCBzdWRhaCBiaXNhIGRpZ3VuYWthbi4gU2VwZXJ0aSB5YW5nIHN1ZGFoIGRpc2VidXQgdGFkaSwgaGFsIGluaSBwZXJsdSBkaWxha3VrYW4gZGkgc2V0aWFwIHNlc2kuIE9sZWgga2FyZW5hIGl0dSBzZXRpYXAga2l0YSBtZW1idWF0IHNjcmlwdCBiYXJ1LCBraXRhIGhhcnVzIG1lbnVsaXMgbGFnaSBwZXJpbnRhaCB1bnR1ayBtZW55YWxha2FuIHBhY2thZ2UgKGJpc2EgZGkgYXdhbCBzY3JpcHQgYXRhdSBzZWJlbHVtIHNjcmlwdCB0ZXJzZWJ1dCBtZW5nZWtzZWt1c2kgZnVuZ3NpIGRhcmkgcGFja2FnZSB5YW5nIHJlbGV2YW4pLg0KDQoNCiMjIDIuIE1lbWJhY2EgdGFiZWwNCiMjIyAyLjEuIFNldCB3b3JraW5nIGRpcmVjdG9yeQ0KU2ViZWx1bSBraXRhIG1lbWJhY2EgZGF0YSBla3N0ZXJuYWwsIGFkYSBiYWlrbnlhIGtpdGEgbWVsYWt1a2FuIHBlbnlldGluZ2FuIHdvcmtpbmcgZGlyZWN0b3J5IHRlcmxlYmloIGRhaHVsdS4gKldvcmtpbmcgZGlyZWN0b3J5KiBhZGFsYWggZm9sZGVyIGF0YXUgYWxhbWF0IGRpIGRhbGFtIGtvbXB1dGVyIGtpdGEgdGVtcGF0IFIgbWVsYWt1a2FuIHBlbnlpbXBhbmFuIHNlY2FyYSBkZWZhdWx0LiBIYWwgaW5pIGFrYW4gYmVyZ3VuYSBqaWthIGtpdGEgaW5naW4gbWVtYmFjYSBtYXVwdW4gbWVudWxpcyBmaWxlIGJhcnUuDQoNClBlcnRhbWEsIGtpdGEgYmlzYSBwZXJpa3NhIHRlcmxlYmloIGRhaHVsdSwgYXBhIHdvcmtpbmcgZGlyZWN0b3J5IGtpdGEgZGVuZ2FuIGBnZXR3ZCgpYA0KYGBge3IgZWNobz1UUlVFfQ0KZ2V0d2QoKQ0KYGBgDQpLaXRhIGJpc2EgbWVuZ2dhbnRpIHdvcmtpbmcgZGlyZWN0b3J5IGtpdGEgZGVuZ2FuIG1lbmdndW5ha2FuIGBzZXR3ZGAuIFNpbGFoa2FuIGdhbnRpIGFsYW1hdG55YSBkZW5nYW4gYWxhbWF0IGZvbGRlciB5YW5nIEFuZGEgaW5naW5rYW4hDQpgYGB7ciBlY2hvPVRSVUV9DQpzZXR3ZCgiRDovVGVtcC9SIFR1dG9yaW5nL0phbiAyMSIpDQpgYGANCg0KS2FsYXUga2l0YSBjb2JhIGNlayBsYWdpIGRpIGBnZXR3ZCgpYCwga2l0YSBha2FuIG1lbGloYXQgYmFod2EgcGVydWJhaGFuIGFsYW1hdCB3b3JraW5nIGRpcmVjdG9yeSB0ZWxhaCBiZXJoYXNpbC4oQmVsdW0gZGlrYXNpaCBjb250b2ggZGkgc2luaSBrYXJlbmEgUm5vdGVib29rLCB5YW5nIGRpZ3VuYWthbiB1bnR1ayBtZW51bGlzIG1vZHVsIGluaSwgdGlkYWsgYmlzYSBtZW55aW1wYW4gc2V0dGluZyBkYXJpIGBzZXR3ZCgpYCBzZWNhcmEga29uc2lzdGVuKS4NCg0KIyMjIDIuMi4gRGF0YSBkYXJpIENTViBkYW4gRXhjZWwNClVudHVrIG1lbmdhbWJpbCBkYXRhIGRhcmkgZmlsZSAqKi5jc3YqKiwga2l0YSBiaXNhIG1lbmdndW5ha2FuIGZ1bmdzaSBgcmVhZC5jc3ZgLiBQZXJpbnRhaCB5YW5nIGtpdGEgbWFzdWtrYW4gYWRhbGFoIHNlcGVydGkgYmVyaWt1dDoNCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX2NzdiA8LSByZWFkLmNzdigiLi9TZXNzaW9uIDIvSmFqYW4gUTEgLSBTaGVldDEuY3N2IikNCmphamFuX2Nzdg0KYGBgDQpCYWdpYW4gZGFyaSBzaW50YWtzIHlhbmcgZGl0dWxpcyBkaSBkYWxhbSBrdXJ1bmcgbWVudW5qdWtrYW4gYmFod2Ega2l0YSBzZWRhbmcgbWVuZ2FtYmlsIGRhdGEgZGFyaSBkaXJlY3RvcnkgdGVydGVudHUuIFNpbmdrYXRueWEsIGAiLi9TZXNzaW9uIDIvImAgbWVudW5qdWtrYW4ga2l0YSBzZWRhbmcgbWVydWp1ayBzZXN1YXR1IGRpIGZvbGRlciAiU2Vzc2lvbiAyIiwgZGFuIGBKYWphbiBRMSAtIFNoZWV0MS5jc3ZgIG1lbnVuanVra2FuIG5hbWEgZGFyaSBmaWxlbnlhLiANCg0KSW5nYXQga2VtYmFsaSBrZSBwZW1iYWhhc2FuIHRlbnRhbmcgYHNldHdkYC4gUGVyaW50YWggZGkgZGFsYW0gYHJlYWQuY3N2YCBha2FuIG1lcnVqdWsga2Ugc3ViLWZvbGRlciB5YW5nIGRpamFkaWthbiB3b3JraW5nIGRpcmVjdG9yeSB1bnR1ayBtZW5lbnR1a2FuIHBhdGgga2UgZmlsZSB5YW5nIGRpdHVqdSwgc2VoaW5nZ2Ega2l0YSB0aWRhayBwZXJsdSBtZW51bGlza2FuIGAiRDovVGVtcC9SIFR1dG9yaW5nL0phbiAyMS9TZXNzaW9uIDIvSmFqYW4gUTEgLSBTaGVldDEuY3N2ImAuIEJhZ2lhbiBhd2FsIGhpbmdnYSAnSmFuIDIxJyBzdWRhaCBjdWt1cCBkaSBzZXR0aW5nIHdvcmtpbmcgZGlyZWN0b3J5LCBkYW4gdGlkYWsgcGVybHUgZGl0dWxpc2thbiBsYWdpLiANCg0KVW50dWsgZmlsZS1maWxlIGV4Y2VsIHNlcGVydGkgJy54bHN4Jywga2l0YSBwZXJsdSBtZW5nZ3VuYWthbiBmdW5nc2kgeWFuZyBiZXJiZWRhLCB5YWl0dSBgcmVhZHhsYC4gUGFja2FnZSBpbmkgc3VkYWggdGVybWFzdWsgZGkgZGFsYW0gYHRpZHl2ZXJzZWAgamFkaSBraXRhIGN1a3VwIG1lbmdpbnN0YWxsIGB0aWR5dmVyc2VgIHNhamEsIGRhbiBzZXRlcnVzbnlhIHN1ZGFoIGJpc2EgbWVuZ2d1bmFrYW4gcGFja2FnZSBpbmkuIFRldGFwaSwga2l0YSB0ZXRhcCBoYXJ1cyBtZW1hbmdnaWwgcGFja2FnZSBgcmVhZHhsYCBkaSB0aWFwIHNlc2kgamlrYSBpbmdpbiBtZW5nZ3VuYWthbm55YS4NCmBgYHtyIGVjaG89VFJVRX0NCmxpYnJhcnkocmVhZHhsKQ0KYGBgDQoNClBlcmludGFoIHVudHVrIG1lbWFuZ2dpbCBmaWxlIGV4Y2VsIHRpZGFrIGphdWggYmVyYmVkYSBkZW5nYW4gZmlsZSwgY3N2LCB5YW5nIGJpc2EgZGlsaWhhdCBkaSBjb250b2ggZGkgYmF3YWggaW5pIGRlbmdhbiBkYXRhc2V0IHlhbmcgc2FtYS4gDQpgYGB7ciBlY2hvPVRSVUV9DQpqYWphbl94bHN4IDwtIHJlYWRfeGxzeCgiLi9TZXNzaW9uIDIvSmFqYW4gUTEueGxzeCIsIHNoZWV0ID0gIlNoZWV0MSIpDQpqYWphbl94bHN4DQpgYGANClBlcmJlZGFhbiB5YW5nIHBhbGluZyB0ZXJsaWhhdCBkaSBzaW5pIGFkYWxhaCBraXRhIGJpc2EgbWVtYmVyaWthbiBzcGVzaWZpa2FzaSBzaGVldCBtYW5hIHlhbmcgaGFydXMgZGlhbWJpbC4gSGFsIGluaSBkaWxha3VrYW4gbWVuZ2d1bmFrYW4gYHNoZWV0ID0gYC4gSmlrYSB0aWRhayBkaXNwZXNpZmlrYXNpLCBtYWthIHNlY2FyYSBkZWZhdWx0IGByZWFkX3hsc3hgLiBBa2FuIG1lbmdhbWJpbCBzaGVldCBwZXJ0YW1hLg0KDQpQZXJsdSBkaXBlcmhhdGlrYW4ganVnYSBrZXRpa2Ega2l0YSBtZW1iYWNhIGRhdGEgZGFyaSB0YWJlbCwgZm9ybWF0IHRhYmVsIHRlcnNlYnV0IGhhcnVzIHNlZGVyaGFuYTsgc2F0dSBzaGVldCA9IHNhdHUgdGFiZWwgeWFuZyBrb25zaXN0ZW4uIFIganVnYSBjZW5kZXJ1bmcgb3RvbWF0aXMgbWVuZ2FtYmlsIHJvdyBwZXJ0YW1hIGRhcmkgdGFiZWwgYXBhcHVuIHNlYmFnYWkgYGhlYWRlcmAuIE5hbXVuLCBqaWthIGZvcm1hdCBkYXRhIHRpZGFrIHNlc3VhaSwgYmlzYSBkaWxha3VrYW4gZGF0YSB3cmFuZ2xpbmcuIEhhbCBpbmkgYmlzYSBkaWxha3VrYW4gZGVuZ2FuIGNhcmEgbWlzYWxueWEsIGtpdGEgZ3VuYWthbiBvcHNpIGBoZWFkZXIgPSBGQUxTRWAsIHlhbmcgYWthbiBtZW1idWF0IFIgb3RvbWF0aXMgbWVtYnVhdCBoZWFkZXIgYmFydSBkaSBhdGFzIHRhYmVsIGtpdGEuDQpgYGB7ciBlY2hvPVRSVUV9DQpqYWphbl9jc3Zfbm9oZWFkZXIgPC0gcmVhZC5jc3YoIi4vU2Vzc2lvbiAyL0phamFuIFExIC0gU2hlZXQxLmNzdiIsIGhlYWRlciA9IEZBTFNFKQ0KamFqYW5fY3N2X25vaGVhZGVyDQpgYGANClNldGVsYWhueWEsIGtpdGEgYmlzYSBtZW5nZ3VuYWthbiBgbGlzdGAgbGFpbiwgeWFuZyBraXRhIGd1bmFrYW4gdW50dWsgbWUtcmVwbGFjZSBuYW1hIGtvbG9tIHlhbmcgb3RvbWF0aXMgZGlidWF0IG9sZWggUi4gUmVwbGFjZSBkZW5nYW4gbWVuZ2d1bmFrYW4gYGNvbG5hbWVzYA0KYGBge3IgZWNobz1UUlVFfQ0KIyBNZW1idWF0IGxpc3QgYmFydSB1bnR1ayBuYW1hIGtvbG9tIA0KbmFtYV9rb2xvbSA8LSBjKCJrb2xfMSIsICJrb2xfMiIsICJrb2xfMyIsICJrb2xfNCIsICJrb2xfNSIsICJrb2xfNiIpDQpuYW1hX2tvbG9tDQpgYGANCmBgYHtyIGVjaG89VFJVRX0NCiMgTWVuZ2dhbnRpIG5hbWEga29sb20gZGVuZ2FuICdjb2xuYW1lcycNCmNvbG5hbWVzKGphamFuX2Nzdl9ub2hlYWRlcikgPC0gbmFtYV9rb2xvbQ0KamFqYW5fY3N2X25vaGVhZGVyDQpgYGANClRlbnR1bnlhIHNldGVsYWhueWEsIHJvd3MgeWFuZyBiZXJpc2kgbmFtYSBrb2xvbSBzZWJlbHVtbnlhIGhhcnVzIGRpLWRlbGV0ZS4gTWVuZGVsZXRlIGtvbG9tIGRhbiByb3dzIGFrYW4gZGliYWhhcyBkaSBiYWdpYW4gc2VsYW5qdXRueWEuIA0KDQojIyAzLiBEYXRhIHNjcmVlbmluZyBkYW4gY2xlYW5pbmcgZGFzYXINClVtdW1ueWEsIGhhbCBwZXJ0YW1hIHlhbmcga2l0YSBsYWt1a2FuIHNldGVsYWggbWVuZy1la3Nwb3IgdGFiZWwgYWRhbGFoIHVudHVrIG1lbGloYXQgYXBha2FoIGFkYSBwZXJtYXNhbGFoYW4gZGkgZGF0YS4gT2xlaCBrYXJlbmEgaXR1LCBzZWthcmFuZyBtYXJpIGtpdGEgYmFoYXMgc2VjYXJhIHNpbmdrYXQgbWVuZ2VuYWkgZGF0YSBzY3JlZW5pbmcsIHV0YW1hbnlhIG1lbmdlY2VrICoqYWNjdXJhY3kqKiBkYXJpIGRhdGEuICoqQWNjdXJhY3kqKiBkaSBzaW5pIGFydGlueWEgDQoNClVudHVuZ255YSBjdWt1cCBtdWRhaCBkaSBSIHVudHVrIHNlY2FyYSBjZXBhdCBtZW5kYXBhdGthbiBnYW1iYXJhbiBrZXNlbHVydWhhbiB0ZW50YW5nIGRhdGEga2l0YS4gRnVuZ3NpIGBzdW1tYXJ5YCBtZW1iZXJpa2FuIGdhbWJhcmFuIHRlcnNlYnV0LCBzZXBlcnRpIHlhbmcgYmlzYSBkaWxpaGF0IGRpIGJhd2FoLg0KYGBge3IgZWNobz1UUlVFfQ0Kc3VtbWFyeShqYWphbl9jc3YpDQpgYGANCkRhcmkgYHN1bW1hcnlgIGtpdGEgYmlzYSBtZWxpaGF0IGJlYmVyYXBhIGhhbDogDQoNCi0gQ2xhc3MgZGFyaSBgbmFtYWAsIGBidWxhbmAsIGRhbiBgamFqYW5gIGFkYWxhaCBgY2hhcmFjdGVyYCwgc2VkYW5na2FuIGBqdW1sYWhgLCBgaGFyZ2FgLCBkYW4gYHBlbmdlbHVhcmFuYCBhZGFsYWggYG51bWVyaWNgLCBrYXJlbmEgeWFuZyBsYW5nc3VuZyBtdW5jdWwgYWRhbGFoIGRhdGEtZGF0YSBzdW1tYXJ5IHN0YXRpc3RpY3MgZGFyaSBrb2xvbSB0ZXJzZWJ1dC4gDQotIFVudHVrIHZhcmlhYmVsIHlhbmcgYG51bWVyaWNgLCBraXRhIGxhbmdzdW5nIGJpc2EgbWVsaWhhdCBtZWFuLCBtZWRpYW4sIHF1YW50aWxlLCBzZXJ0YSBuaWxhaSBtaW4gZGEgbWFrcy1ueWEuDQotIFRlcmRhcGF0IG1pc2luZyBkYXRhIChgTkEnc2ApIGRpIGJlYmVyYXBhIGtvbG9tDQoNCkRlbmdhbiBpbmZvcm1hc2kgaW5pLCBraXRhIGJpc2EgdGFodSBhcGEgc2FqYSB5YW5nIGhhcnVzIGRpdWJhaCAoZGliZXJzaWhrYW4pLiBMZWJpaCBsYW5qdXQsIGtpdGEgYWthbiBiYWhhcyBjYXJhLWNhcmEgbWVtYmVyc2loa2FubnlhIGRpIGJhZ2lhbiBzZWxhbmp1dG55YS4NCg0KIyMgNC4gTWFuaXB1bGFzaSB0YWJlbCAobGFuanV0KQ0KIyMjIDQuMS4gTWVuZ2hhcHVzIHJvdw0KVGVyZGFwYXQgYmFueWFrIGZ1bmdzaSB1bnR1ayBiaXNhIG1lbmdoYXB1cyByb3cgdGVydGVudHUuIENhcmEgeWFuZyBwYWxpbmcgc2VkZXJoYW5hIGFkYWxhaCBkZW5nYW4gbWVuZ2d1bmFrYW4gb3BlcmF0b3IgYC1gIHVudHVrIG1lbWJ1YXQgc2xpY2UgZGFyaSBkYXRhc2V0IHlhbmcgdGlkYWsgbWVtaWxpa2kgcm93IGRpIHVydXRhbiB0ZXJzZWJ1dC4gSW5nYXRsYWggYmFod2EgZGkgZGF0YXNldCBgamFqYW5fY3N2YCByb3cga2VkdWEgYWRhbGFoIGNhdGF0YW4gYmFod2EgQWx2aW4gamFqYW4gYnVyZ2VyIGRpIGJ1bGFuIEphbnVhcmkuIE1hcmkga2l0YSBjb2JhIGhpbGFuZ2thbiByb3cgdGVyc2VidXQgZGVuZ2FuIG1lbmdndW5ha2FuIG1ldG9kZSBpbmkuDQoNCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX2NzdlstYygyKSwgXQ0KYGBgDQpCZWJlcmFwYSBmdW5nc2kgbGFpbiBtZW1pbGlraSBrZWd1bmFhbiB5YW5nIHNhbWEuIE1pc2FsbnlhIGRlbmdhbiBmdW5nc2kgYHNsaWNlYCB5YW5nIG1lcnVwYWthbiBiYWdpYW4gZGFyaSBgdGlkeXZlcnNlYC4gU2ludGFrc255YSB0aWRhayBqYXVoIGJlZGEgZGVuZ2FuIG1lbmdndW5ha2FuIG9wZXJhdG9yIGAtYC4NCmBgYHtyIGVjaG89VFJVRX0NCnNsaWNlKGphamFuX2NzdiwgLWMoMikpDQpgYGANCktpdGEganVnYSBiaXNhIG1lbmdoYXB1cyByb3cgYmVyZGFzYXJrYW4gbmlsYWkgZGkgZGFsYW0gcm93IHRlcnNlYnV0LiBNaXNhbG55YSwga2l0YSBpbmdpbiBtZW5naGFwdXMgc2VtdWEgY2F0YXRhbiB0ZW50YW5nIEFsdmluIGRlbmdhbiBtZWxha3VrYW4gc3Vic2V0dGluZyBiaWFzYSwgZGVuZ2FuIG9wZXJhdG9yIGAhPWAuIA0KYGBge3IgZWNobz1UUlVFfQ0KamFqYW5fY3N2WyhqYWphbl9jc3YkbmFtYSAhPSAiQWx2aW4iKSwgXQ0KYGBgDQphdGF1IG1lbmdndW5ha2FuIG9wZXJhdG9yIGAhYCBzZWJlbHVtIGtpdGEgbWVuc3Blc2lmaWthc2kgdmFsdWUgeWFuZyBpbmdpbiBkaWhpbGFuZ2thbi4gKHBlcmhhdGlrYW4gdGFuZGEgYCE9YCBkaXViYWggbWVuamFkaSBgPT1gKS4gS2FsaSBpbmkga2l0YSBjb2JhIHVudHVrIG1lbmdoaWxhbmdrYW4gc2VtdWEgeWFuZyBiZXJuYW1hIEFsdmluIGRhbiBCZW5lLg0KYGBge3IgZWNobz1UUlVFfQ0KamFqYW5fY3N2WyEoamFqYW5fY3N2JG5hbWEgPT0gIkFsdmluIiB8IGphamFuX2NzdiRuYW1hID09ICJCZW5lIiksIF0NCmBgYA0KDQpLaXRhIGJpc2EgbWVsYWt1a2FuIG9taXNzaW9uIG1lbmdndW5ha2FuIGR1YSBrb25kaXNpLCBkZW5nYW4gb3BlcmF0b3IgYm9vbGVhbiBgJmAuIFVudHVrIGNvbnRvaCBrYWxpIGluaSwga2l0YSBjb2JhIGhpbGFuZ2thbiBzZW11YSBjYXRhdGFuIHRlbnRhbmcgQWx2aW4gbWVtYmVsaSBqYWphbiBrb3BpLCB0YXBpIGtpdGEgaW5naW4gc2VtdWEgY2F0YXRhbiB0ZW50YW5nIEFsdmluIHRldGFwIGFkYSwgZGkgbHVhciBkYXJpIGphamFuIGtvcGlueWEuIEFydGlueWEsIGtpdGEgaW5naW4gbWVuZ2hhcHVzIHJvdyBkZW5nYW4gbmFtYSA9IEFsdmluIERBTiBqYWphbiA9IGtvcGkuDQoNCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX2Nzdl8yIDwtIGphamFuX2NzdlshKGphamFuX2NzdiRuYW1hID09ICJBbHZpbiIgJiBqYWphbl9jc3YkamFqYW4gPT0gImtvcGkiKSwgXQ0KYGBgDQojIyMgNC4yLiBNZW5naGFwdXMga29sb20NClNhbWEgc2VwZXJ0aSBtZW5naGFwdXMgcm93LCBrb2xvbSBkYXBhdCBkaWhhcHVzIGRlbmdhbiBtZXJlZmVyIGtlIGluZGVrc255YS4gU2ludGFrc255YSBwdW4gdGlkYWsgYmVnaXR1IGJlcmJlZGEgZGVuZ2FuIG1lbmdoYXB1cyByb3cuIENvbnRvaCwgbWFyaSBraXRhIGhhcHVzIGtvbG9tIGBuYW1hYC4NCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX2NzdlssIC1jKDEpXQ0KYGBgDQpLYXJlbmEga29sb20gbWVtaWxpa2kgbmFtYSB2YXJpYWJlbCB5YW5nIGplbGFzLCBraXRhIGJpc2EgbWVuZ2d1bmFrYW4gbmFtYSB0ZXJzZWJ1dCB1bnR1ayBtZW1hbmdnaWwgYXRhdSBtZW5naGFwdXMga29sb20gaXR1LiBVbnR1ayBtZWxha3VrYW4gaGFsIGluaSwga2l0YSBiaXNhIG1lbmdndW5ha2FuIGZ1bmdzaSBgc3Vic2V0YCwgZnVuZ3NpIHlhbmcgdGVyc2VkaWEgZGkgYmFzZSBSLiBTaW50YWtzbnlhIGN1a3VwIHNpbXBlbCwgY3VrdXAgc3Blc2lmaWthc2lrYW4gZGF0YSBkYW4ga29sb20geWFuZyBpbmdpbiBkaWhpbGFuZ2thbiBkaSBkYWxhbSB2YXJpYWJlbCBgc2VsZWN0YCwgZGktd3JhcCBkZW5nYW4gYC0oKWAuDQpgYGB7ciBlY2hvPVRSVUV9DQpzdWJzZXQoamFqYW5fY3N2LCBzZWxlY3QgPSAtKG5hbWEpKQ0KYGBgDQoNClNlbGFpbiBtZW5naGFwdXMga29sb20sIGtpdGEganVnYSBiaXNhIG1lbGFrdWthbiBzZWJhbGlrbnlhLCB5YWl0dSBtZW1idWF0IGRhdGFzZXQgYmFydSB5YW5nIGlzaW55YSBoYW55YSBrb2xvbSB5YW5nIGluZ2luIGtpdGEgZ3VuYWthbi4gVGVudHUgaW5pIGt1cmFuZyBsZWJpaCBzYW1hIHNhamEgZGVuZ2FuIG1lbmdoYXB1cyBrb2xvbSBiaWFzYSwgbWFrYSBkYXJpIGl0dSBraXRhIGJpc2EgbWVuZ2d1bmFrYW4gcGlsaWhhbiBtZW5naGFwdXMgYXRhdSBtZW55ZWxla3NpIGtvbG9tLCB0ZXJnYW50dW5nIGRlbmdhbiBrb25kaXNpLiBEaSBtZXRvZGUgZGkgYmF3YWggaW5pLCBraXRhIG1lbWJ1YXQgbGlzdCBkYXJpIG5hbWEga29sb20geWFuZyBpbmdpbiBraXRhIHNpbXBhbiwgbGFsdSBtZW5nZ3VuYWthbiBsaXN0IHRlcnNlYnV0IGRpIGRhbGFtIGBbXWAgdW50dWsgbWVtYW5nZ2lsIGtvbG9tIHRlcnNlYnV0IGRhcmkgbmFtYS1uYW1hbnlhLiANCg0KYGBge3IgZWNobz1UUlVFfQ0Ka2VlcCA8LSBjKCJuYW1hIiwgImJ1bGFuIiwgImphamFuIikNCmtlZXANCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUV9DQpqYWphbl9jc3Zba2VlcF0NCmBgYA0KUGFja2FnZSBgdGlkeXZlcnNlYCBtZW55ZWRpYWthbiBvcHNpIHlhbmcgc2FuZ2F0IGZsZWtzaWJlbCBkZW5nYW4gZnVuZ3NpIGBzZWxlY3RgLiBEaSBmdW5nc2kgaW5pIGtpdGEgYmlzYSBtZW55ZWxla3NpIHZhcmlhYmVsIGJlcmRhc2Fya2FuIG1hdGNoaW5nIHBhcnNpYWwgKG1pcy4gaGFueWEga29sb20geWFuZyBtZW1pbGlraSBzdHJpbmcgdGVydGVudHUgZGkgbmFtYW55YSksIG1lbmdndW5ha2FuIHJlZ2V4LCBhdGF1IG1lbmdndW5ha2FuIGZ1bmdzaS4gTGloYXRsYWggZG9rdW1lbnRhc2lueWEgdW50dWsgcGVuamVsYXNhbiBsZWJpaCBsZW5na2FwIChgP3NlbGVjdGApDQoNClNhbGFoIHNhdHUgY29udG9oLCBraXRhIGJpc2EgbWVuZ2FtYmlsIGhhbnlhIHZhcmlhYmVsIHlhbmcgYmVyc2lmYXQgYG51bWVyaWNgLiBVbnR1ayBtZWxha3VrYW4gaW5pLCBraXRhIGd1bmFrYW4gZnVuZ3NpIGRpIGRhbGFtIHNlbGVjdCBgd2hlcmUoKWAgdW50dWsgbWVtYmVyaSBwZXJpbnRhaCBiYWh3YSBraXRhIG1lbmNhcmkga29sb20gZGVuZ2FuIGtvbmRpc2kgdGVydGVudHUuDQpgYGB7ciBlY2hvPVRSVUV9DQpzZWxlY3QoamFqYW5fY3N2LCB3aGVyZShpcy5udW1lcmljKSkNCmBgYA0KDQoNCiMjIyA0LjMuIE1pc3NpbmcgZGF0YQ0KTWlzc2luZyBkYXRhIGJpYXNhbnlhIGtpdGEgdGFuZ2FuaSBkZW5nYW4gZHVhIGNhcmEsIHlhaXR1IG1lbmdoYXB1cyBhdGF1IG1lbmdnYW50aW55YS4gS2l0YSBha2FuIGJhaGFzIGNhcmEgcGVydGFtYSB0ZXJsZWJpaCBkYWh1bHUuDQoNCk1lbmdoYXB1cyBtaXNzaW5nIGRhdGEgZGkgc2luaSBiaXNhIGRlbmdhbiBkdWEgY2FyYSwgeWFpdHUgbWVuZ2hhcHVzIHJvdyBhdGF1IGtvbG9tLiBTZWJ1YWggcnVsZSBvZiB0aHVtYiwgamlrYSBqdW1sYWggbWlzc2luZyBkYXRhIHRlcnNlYmFyIGF0YXUgdGlkYWsgdGVybGFsdSBiYW55YWssIGtpdGEgYmlzYSBtZW5nZ3VuYWthbiBvcHNpIHBlcnRhbWEsIG1lbmdoYXB1cyByb3cuIENhcmEgeWFuZyBtdWRhaCB1bnR1ayBtZWxha3VrYW4gaW5pIGFkYWxhaCBkZW5nYW4gYG5hLm9taXRgLCB5YW5nIGFrYW4gbWVuZ2hhcHVzIHNlbXVhIHJvdyB5YW5nIG1lbWlsaWtpIG1pc3NpbmcgZGF0YSB0YW5wYSB0ZXJrZWN1YWxpLg0KDQpgYGB7ciBlY2hvPVRSVUV9DQpuYS5vbWl0KGphamFuX2NzdikNCmBgYA0KTmFtdW4sIGppa2EgbWlzc2luZyBkYXRhIHRlcmtvbnNlbnRyYXNpIGRpIHNhdHUgdmFyaWFiZWwsIGtpdGEgYmlzYSBtZW5naGFwdXMga29sb20geWFuZyByZWxldmFuLiBVbnR1ayBjb250b2ggZGkgYmF3YWggaW5pLCBraXRhIGNvYmEgZ3VuYWthbiBkYXRhc2V0IHlhbmcgbWVyZmxla3Npa2FuIGtvbmRpc2kgdGVyc2VidXQuIEluZ2F0IGJhaHdhIGtpdGEgYmlzYSBtZW5kYXBhdGthbiBqdW1sYWggbWlzc2luZyBkYXRhIGRlbmdhbiBtZW5nZ3VuYWthbiBgc3VtbWFyeWAgdGVybGViaWggZGFodWx1LiANCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX21pc3NpbmcgPC0gcmVhZC5jc3YoIi4vU2Vzc2lvbiAyL0phamFuIFExIC0gU2hlZXQxIChtaXNzaW5nKS5jc3YiKQ0Kc3VtbWFyeShqYWphbl9taXNzaW5nKQ0KYGBgDQpLaXRhIGxpaGF0IGJhaHdhIGtpdGEgbWVtaWxpa2kganVtbGFoIG1pc3NpbmcgZGF0YSB5YW5nIGJhbnlhayBkaSBgcGVuZ2VsdWFyYW5gLiBKdW1sYWggaXR1IG1lbmNha3VwIHNhbXBlbCB5YW5nIHNpZ25pZmlrYW4sIGhhbXBpciBzZXRlbmdhaCBkYXJpIHNhbXBlbCBraXRhLiBTZWhpbmdnYSwgbGViaWggYmlqYWsgdW50dWsga2l0YSBkcm9wIHNhamEga29sb21ueWEsIGRhcmlwYWRhIGhhcnVzIG1lbmdoYWJpc2kgc2FtcGVsLg0KYGBge3IgZWNobz1UUlVFfQ0Kc3Vic2V0KGphamFuX21pc3NpbmcsIHNlbGVjdD0gLShwZW5nZWx1YXJhbikpDQpgYGANCkxhbHUsIGtpdGEgYmlzYSBtZW5na29tYmluYXNpa2FuIGNhcmEgdGVyc2VidXQgdW50dWsgbWVtYnVhdCBkYXRhc2V0IGtpdGEgYmVuYXItYmVuYXIgYmVyc2loIGRhcmkgbWlzc2luZyBkYXRhOg0KYGBge3IgZWNobz1UUlVFfQ0KbmEub21pdChzdWJzZXQoamFqYW5fbWlzc2luZywgc2VsZWN0PSAtKHBlbmdlbHVhcmFuKSkpDQpgYGANCkppa2Ega2l0YSBsZWJpaCBpbmdpbiBtZW5nZ2FudGkgbWlzc2luZyBkYXRhIGRlbmdhbiBuaWxhaSBsYWluLCBraXRhIGJpc2EgbWVsYWt1a2FuIGFzc2lnbm1lbnQgYmlhc2EsIGRlbmdhbiBjYXJhIHNlcGVydGkgY29udG9oIGRpIGJhd2FoIGluaS4NCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX2Nzdltpcy5uYShqYWphbl9jc3YpXSA8LSAwDQpqYWphbl9jc3YNCmBgYA0KIyMjIDQuNC4gTWVtYnVhdCBrb2xvbSBiYXJ1IGJlcmRhc2Fya2FuIHZhbHVlIGRpIGtvbG9tIGxhaW4gDQpLYWRhbmcga2l0YSBidXR1aCBtZW1idWF0IGthdGVnb3JpIGF0YXUgbmlsYWkgYmFydSBkaSBkYWxhbSBkYXRhLCBkaSBsdWFyIGRhcmkgaGFsIHlhbmcga2l0YSBkYXBhdCBkaSBkYXRhc2V0IG1lbnRhaC5VbXVtbnlhLCBiaXNhIGRpZ3VuYWthbiBmdW5nc2kgYGlmZWxzZSgpYC4gDQoNCkthdGFrYW5sYWgga2l0YSBpbmdpbiBtZW5na2F0ZWdvcmlzYXNpIHBlbmdlbHVhcmFuIG1lbmphZGkgdGluZ2dpIGF0YXUgcmVuZGFoLCBiZXJkYXNhcmthbiBkaWEgZGkgYXRhcyA1MDAgcmlidSBydXBpYWggYXRhdSB0aWRhay5MYWx1LCBqaWthIGRpIGFudGFyYSA1MDAgcmlidSBkYW4gMjUwIHJpYnUsIGtpdGEgYW5nZ2FwICdzZWRhbmcnLCBkYW4gZGkgYmF3YWggMjUwIHJpYnUga2l0YSBhbmdnYXAgJ3JlbmRhaCcuDQpgYGB7ciBlY2hvPVRSVUV9DQpqYWphbl9jc3YkdGluZ2dpX3JlbmRhaCA8LSBpZmVsc2UoamFqYW5fY3N2JHBlbmdlbHVhcmFuID49IDUwMDAwMCwgIlRpbmdnaSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShqYWphbl9jc3YkcGVuZ2VsdWFyYW4gPj0gMjUwMDAwLCAiU2VkYW5nIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJlbmRhaCIpKQ0KamFqYW5fY3N2DQpgYGANCkZ1bmdzaSBgaWZlbHNlYCBzZXJpbmdrYWxpIGN1a3VwIGludHVpdGlmLCB0ZXRhcGkgYmlzYSBtZW1iaW5ndW5na2FuIGppa2Ega29uZGlzaW55YSBiYW55YWsuIFN0cnVrdHVybnlhIHlhbmcgaGFydXMgYmVyc2lmYXQgbmVzdGVkIG1lbWJ1YXQgbXVkYWggdW50dWsgZnVuZ3NpIGluaSBtZW5qYWRpIHRlcmxhbHUgYmFueWFrIGZ1bmdzaS1kaS1kYWxhbS1mdW5nc2kgeWFuZyBtZW1iaW5ndW5na2FuLiBBbHRlcm5hdGlmIGRhcmkgYGlmZWxzZWAgeWFuZyBzZXJpbmcgZGlndW5ha2FuIGFkYWxhaCBgbXV0YXRlYCwgYmFnaWFuIGRhcmkgYHRpZHl2ZXJzZWAuIA0KDQpNYXJpIGtpdGEgY29iYSBgbXV0YXRlYCB1bnR1ayB0dWp1YW4geWFuZyBzYW1hIGRlbmdhbiBjb250b2ggc2ViZWx1bW55YSwga2FsaSBpbmkgZGkgZGF0YXNldCB5YW5nIGJlcmJlZGEuIA0KDQpQZXJoYXRpa2FuIGJhaHdhIGRpIHNpbmkga2l0YSBtZW5nZ3VuYWthbiBvcGVyYXRvciBgICU+JSBgLCB5YW5nIGRpc2VidXQgc2ViYWdhaSBwaXBlLiBPcGVyYXRvciBpbmkgbWVydXBha2FuIGJhZ2lhbiBkYXJpIGB0aWR5dmVyc2VgLCBkaSBtYW5hIGFydGlueWEsIHNldGVsYWggb3BlcmF0b3IgcGlwZSBkaXR1bGlzLCBwZXJpbnRhaCBzZWxhbmp1dG55YSBha2FuIG1lcmVmZXIga2UgZGF0YXNldCB5YW5nIGRpdHVqdSAoZGFsYW0gaGFsIGluaSBgamFqYW5feGxzeGApLg0KDQpTZWxhaW4gaXR1LCB0ZXJkYXBhdCBqdWdhIGJhcmlzIGJlcnR1bGlzYW4gYFRSVUUgfiAiTGFpbm55YSJgLiBCYXJpcyBpbmkgYXJ0aW55YSAiSmlrYSBuaWxhaW55YSB0aWRhayBtYXN1ayBkaSBkYWxhbSBrb25kaXNpIHlhbmcgZGlzZWJ1dGthbiBkaSBhdGFzLCBtYWthIHR1bGlzbGFoICJMYWlubnlhIi4NCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX3hsc3ggJT4lICBtdXRhdGUodGluZ2dpX3JlbmRhaCA9IGNhc2Vfd2hlbigNCiAgcGVuZ2VsdWFyYW4gPj0gNTAwMDAwIH4gIlRpbmdnaSIsIA0KICBwZW5nZWx1YXJhbiA8IDUwMDAwMCAmIHBlbmdlbHVhcmFuID49IDI1MDAwMCB+ICJTZWRhbmciLCANCiAgcGVuZ2VsdWFyYW4gPCAyNTAwMDAgfiAiUmVuZGFoIiwgDQogIFRSVUUgfiAiTGFpbm55YSINCikpDQpgYGANCg0KDQoNCg0KIyMjIDQuNS4gTWVuZ2dhbnRpIGplbmlzIHZhcmlhYmVsDQpEYXRhIHlhbmcga2l0YSBhbWJpbCBkYXJpIHN1bWJlciBla3N0ZXJuYWwgYWthbiBzZWNhcmEgb3RvbWF0aXMgZGktYXNzaWduIG9sZWggUiBrZSBqZW5pcyB2YXJpYWJlbCB5YW5nIHBhbGluZyBtYXN1ayBha2FsIHVudHVrIGRhdGEgdGVyc2VidXQuIFRldGFwaSwga2FkYW5nIGplbmlzIGRhdGEgdGVyc2VidXQgdGlkYWsgc2VzdWFpIGRlbmdhbiB5YW5nIGtpdGEgaW5naW5rYW4uIFNlYmFnYWkgY29udG9oLCBtYXJpIGtpdGEgbGloYXQgbGFnaSBzdHJ1a3R1ciBkYXRhIHlhbmcga2l0YSBtaWxpa2kuDQpgYGB7cn0NCnN1bW1hcnkoamFqYW5fY3N2KQ0KYGBgDQoNCktpdGEgbGloYXQgZGkgc2l0dSBgbmFtYWAsIGBidWxhbmAsIGRhbiBgamFqYW5gIGFkYWxhaCBgY2hhcmFjdGVyYC4gSW5pIHRpZGFrIGlkZWFsLCBrYXJlbmEga2l0YSBtZW5nZXRhaHVpIGJhaHdhIHNldGlhcCBgQWx2aW5gIG1pc2FsbnlhLCBtZXJ1cGFrYW4gb3JhbmcgeWFuZyBzYW1hLiBWYXJpYWJlbCBqZW5pcyBgY2hhcmFjdGVyYCB0aWRhayBtZW1wZXJoYXRpa2FuIGhhbCB0ZXJzZWJ1dC4gT2xlaCBrYXJlbmEgaXR1LCBkaSBkYWxhbSBrYXN1cyBpbmkga2l0YSBsZWJpaCBiYWlrIG1lbmd1YmFoIHZhcmlhYmVsLXZhcmlhYmVsIHRlcnNlYnV0IG1lbmphZGkgYGZhY3RvcmAsIG1lbmdndW5ha2FuIHBlcmludGFoIGBhcy5mYWN0b3JgLiANCg0KYGBge3J9DQpqYWphbl9jc3YkbmFtYSA8LSBhcy5mYWN0b3IoamFqYW5fY3N2JG5hbWEpDQpzdW1tYXJ5KGphamFuX2NzdikNCmBgYA0KRnVuZ3NpIGBhcy5mYWN0b3JgIGFkYWxhaCB0dXJ1bmFuIGRhcmkgZnVuZ3NpIGRhbGFtIGJhc2UgciwgeWFpdHUgYGFzLmAgZGFuIGBmYWN0b3JgLiBTYW1hIGRlbmdhbiBmdW5nc2kgYGlzLmAsIGtpdGEgYmlzYSBtZW5nZ3VuYWthbiBmdW5nc2kgaW5pIHVudHVrIGJlcmJhZ2FpIGplbmlzIHZhcmlhYmVsLCB5YW5nIHNlcmluZyBkaWd1bmFrYW4gYWRhbGFoIHNlYmFnYWkgYmVyaWt1dDoNCg0KfCBGdW5nc2kgfCBEZXNrcmlwc2kgfA0KfCAtLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0tIHwNCnwgYGFzLmZhY3RvcmAgfCBNZW5ndWJhaCB2YXJpYWJlbCBtZW5qYWRpIGZha3RvciB8DQp8IGBhcy5udW1lcmljYCB8IE1lbmd1YmFoIHZhcmlhYmVsIG1lbmphZGkgbnVtZXJpayB8DQp8IGBhcy5jaGFyYWN0ZXJgIHwgTWVuZ3ViYWggdmFyaWFiZWwgbWVuamFkaSBrYXJha3RlciB8DQoNCiMjIDUuIE1lbnVsaXMgZGFuIG1lbnlpbXBhbiB0YWJlbA0KVGVyYWtoaXIsIGtpdGEgYmlzYSBtZW55aW1wYW4gdWxhbmcgZGF0YSBkYXJpIHRhYmVsIGFwYXB1biBkaSBkYWxhbSBSIGtlIGZvcm1hdCBsYWluLCBzZXBlcnRpIC5jc3YuIFVudHVrIG1lbnVsaXMgZGkgLmNzdiwga2l0YSBiaXNhIG1lbmdndW5ha2FuIGB3cml0ZS5jc3ZgLCBzZWJ1YWggZnVuZ3NpIGRhcmkgYmFzZSBSLiBQZXJoYXRpa2FuIHBlcmludGFoIGByb3cubmFtZXMgPSBGQUxTRWAuIFBlcmludGFoIGluaSBwZW50aW5nIGFnYXIga2l0YSB0aWRhayBtZW1pbGlraSBzYXR1IGtvbG9tIHRlcnNlbmRpcmkgeWFuZyBpc2lueWEgYWRhbGFoIGluZGVrcyBkYXJpIHRhYmVsLiAgDQpgYGB7cn0NCndyaXRlLmNzdihqYWphbl9jc3YsICIuL1Nlc3Npb24gMi9qYWphbl9jc3ZfY29weS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQpgYGB7cn0NCiMgY29udG9oIGppa2Egcm93Lm5hbWVzID0gVFJVRQ0Kd3JpdGUuY3N2KGphamFuX2NzdiwgImphamFuX2Nzdl9jb3B5X3Jvd25hbWVzLmNzdiIsIHJvdy5uYW1lcyA9IFRSVUUpDQpgYGANCg0K