Apa yang akan dipelajari hari ini?

Di sesi ini kita akan mempraktekkan cara untuk menggunakan data eksternal di R, dan beberapa operasi dasar untuk analisis data. Untuk itu, sesi kali ini dibagi dalam beberapa topik, dimulai dengan mengenal fungsi dan packages. Dilanjutkan dengan cara membaca berbagai jenis tabel, dan beberapa metode manipulasi tabel lanjutan. Terakhir, kita akan mempraktekkan cara untuk menyimpan tabel di R dalam bentuk eksternal.

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:

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:

Fungsi Deskripsi
as.factor Mengubah variabel menjadi faktor
as.numeric Mengubah variabel menjadi numerik
as.character Mengubah variabel menjadi karakter
LS0tDQp0aXRsZTogIjIuIERhdGEgRWtzdGVybmFsLCBTY3JlZW5pbmcsIGRhbiBEYXRhIENsZWFuaW5nIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIyMgQXBhIHlhbmcgYWthbiBkaXBlbGFqYXJpIGhhcmkgaW5pPw0KRGkgc2VzaSBpbmkga2l0YSBha2FuIG1lbXByYWt0ZWtrYW4gY2FyYSB1bnR1ayBtZW5nZ3VuYWthbiBkYXRhIGVrc3Rlcm5hbCBkaSBSLCBkYW4gYmViZXJhcGEgb3BlcmFzaSBkYXNhciB1bnR1ayBhbmFsaXNpcyBkYXRhLiBVbnR1ayBpdHUsIHNlc2kga2FsaSBpbmkgZGliYWdpIGRhbGFtIGJlYmVyYXBhIHRvcGlrLCBkaW11bGFpIGRlbmdhbiBtZW5nZW5hbCBmdW5nc2kgZGFuIHBhY2thZ2VzLiBEaWxhbmp1dGthbiBkZW5nYW4gY2FyYSBtZW1iYWNhIGJlcmJhZ2FpIGplbmlzIHRhYmVsLCBkYW4gYmViZXJhcGEgbWV0b2RlIG1hbmlwdWxhc2kgdGFiZWwgbGFuanV0YW4uIFRlcmFraGlyLCBraXRhIGFrYW4gbWVtcHJha3Rla2thbiBjYXJhIHVudHVrIG1lbnlpbXBhbiB0YWJlbCBkaSBSIGRhbGFtIGJlbnR1ayBla3N0ZXJuYWwuDQoNCg0KIyMgMS4gUGFja2FnZXMNClBhY2thZ2UgYWRhbGFoIHNhdHUgc2V0IGZ1bmdzaSB5YW5nIGJpc2Ega2l0YSBpbnN0YWxsIGRhbiBwYW5nZ2lsIGRpIGRhbGFtIFIgdW50dWsgbWVsYWt1a2FuIGhhbC1oYWwgc3Blc2lmaWsuIFNhYXQga2l0YSBiYXJ1IG1lbmdpbnN0YWxsIFIsIHN1ZGFoIGFkYSBiZWJlcmFwYSBwYWNrYWdlIHlhbmcgb3RvbWF0aXMgdGVyaW5zdGFsbCwgZGFuIHBhY2thZ2UtcGFja2FnZSB5YW5nIHN1ZGFoIHRlcmluc3RhbGwgZGFwYXQgZGlsaWhhdCBkaSBwYW5lbCBiZXJqdWR1bCBgcGFja2FnZXNgLiANCg0KU2VsYWluIHBhY2thZ2UtcGFja2FnZSBiYXdhYW4gaW5pLCBiYW55YWsgcGFja2FnZSBsYWluIHlhbmcgdGVyc2ViYXIgZGkgaW50ZXJuZXQgeWFuZyBkYXBhdCBraXRhIGluc3RhbGwgZGFuIGd1bmFrYW4gZGkgUi4gU2VpcmluZyBiZXJqYWxhbm55YSB3YWt0dSwga2l0YSBha2FuIHRlcnVzIG1lbmFtYmFoIGRhbiBtZW1wZXJiYXJ1aSBwYWNrYWdlIHlhbmcga2l0YSBwdW55YSBkaSBSIGtpdGEuIA0KDQojIyMgMS4xLiBNZW5naW5zdGFsbCBwYWNrYWdlDQpTZWJhZ2FpIGNvbnRvaCBraXRhIHNla2FyYW5nIGFrYW4gbWVuZ2luc3RhbGwgYHRpZHl2ZXJzZWAsIHNlYnVhaCBwYWNrYWdlIG9tbmlidXMgeWFuZyBiZXJpc2kgYmFueWFrIHBhY2thZ2UgbGFpbi5gdGlkeXZlcnNlYCBhZGFsYWggcGFja2FnZSB5YW5nIHNhbmdhdCBwZW50aW5nIHVudHVrIGJlcmJhZ2FpIG1hY2FtIHBlbmdndW5hYW4sIGRhbiBrZWJhbnlha2FuIGRhdGEgd3JhbmdsaW5nIHlhbmcgZGlsYWt1a2FuIHN1ZGFoIGJpc2EgZGlsYWt1a2FuIGRlbmdhbiBwYWNrYWdlIGluaSAoaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pLg0KDQpVbnR1ayBtZW5naW5zdGFsbCBzZWJ1YWggcGFja2FnZSwga2l0YSBtZW5nZ3VuYWthbiBmdW5nc2kgYGluc3RhbGwucGFja2FnZXMoKWAuIEhhbCBpbmkgY3VrdXAgZGlsYWt1a2FuIHNla2FsaSBzYWphLCBrYXJlbmEgdW50dWsgc2V0ZXJ1c255YSBwYWNrYWdlIHRlcnNlYnV0IHN1ZGFoIHRlcnNpbXBhbiBkaSBkYWxhbSBtZW1vcmkgUiBraXRhLiANCg0KYGBge3IgZWNobz1UUlVFfQ0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikNCmBgYA0KDQpTZWthcmFuZyBgdGlkeXZlcnNlYCBha2FuIHN1ZGFoIHRlcmluc3RhbGwgZGkgZGFsYW0gUiBraXRhLCBkYWxhbSB2ZXJzaSB0ZXJiYXJ1bnlhLiBIYWwgaW5pIGJpc2EgZGlsaWhhdCBkaSBwYW5lbCBgcGFja2FnZXNgIGRpIHNlYmVsYWgga2FuYW4gYmF3YWguIA0KDQojIyMgMS4yLk1lbnlhbGFrYW4gcGFja2FnZQ0KUGFja2FnZSB5YW5nIHN1ZGFoIHRlcmluc3RhbGwgdGlkYWsgYWthbiBvdG9tYXRpcyAnbnlhbGEnIGRhbiBiaXNhIGRpZ3VuYWthbi4gVW50dWsgbWVsYWt1a2FuIGhhbCBpbmksIGRpIHNldGlhcCBzZXNpIFIgYmFydSBraXRhIGhhcnVzIG1lbnlhbGFrYW4gcGFja2FnZSB0ZXJzZWJ1dC4gR3VuYWthbiBmdW5nc2kgYGxpYnJhcnkoKWAuDQpgYGB7ciBlY2hvPVRSVUV9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQpTZWthcmFuZyBgdGlkeXZlcnNlYCBzdWRhaCBiaXNhIGRpZ3VuYWthbi4gU2VwZXJ0aSB5YW5nIHN1ZGFoIGRpc2VidXQgdGFkaSwgaGFsIGluaSBwZXJsdSBkaWxha3VrYW4gZGkgc2V0aWFwIHNlc2kuIE9sZWgga2FyZW5hIGl0dSBzZXRpYXAga2l0YSBtZW1idWF0IHNjcmlwdCBiYXJ1LCBraXRhIGhhcnVzIG1lbnVsaXMgbGFnaSBwZXJpbnRhaCB1bnR1ayBtZW55YWxha2FuIHBhY2thZ2UgKGJpc2EgZGkgYXdhbCBzY3JpcHQgYXRhdSBzZWJlbHVtIHNjcmlwdCB0ZXJzZWJ1dCBtZW5nZWtzZWt1c2kgZnVuZ3NpIGRhcmkgcGFja2FnZSB5YW5nIHJlbGV2YW4pLg0KDQoNCiMjIDIuIE1lbWJhY2EgdGFiZWwNCiMjIyAyLjEuIFNldCB3b3JraW5nIGRpcmVjdG9yeQ0KU2ViZWx1bSBraXRhIG1lbWJhY2EgZGF0YSBla3N0ZXJuYWwsIGFkYSBiYWlrbnlhIGtpdGEgbWVsYWt1a2FuIHBlbnlldGluZ2FuIHdvcmtpbmcgZGlyZWN0b3J5IHRlcmxlYmloIGRhaHVsdS4gKldvcmtpbmcgZGlyZWN0b3J5KiBhZGFsYWggZm9sZGVyIGF0YXUgYWxhbWF0IGRpIGRhbGFtIGtvbXB1dGVyIGtpdGEgdGVtcGF0IFIgbWVsYWt1a2FuIHBlbnlpbXBhbmFuIHNlY2FyYSBkZWZhdWx0LiBIYWwgaW5pIGFrYW4gYmVyZ3VuYSBqaWthIGtpdGEgaW5naW4gbWVtYmFjYSBtYXVwdW4gbWVudWxpcyBmaWxlIGJhcnUuDQoNClBlcnRhbWEsIGtpdGEgYmlzYSBwZXJpa3NhIHRlcmxlYmloIGRhaHVsdSwgYXBhIHdvcmtpbmcgZGlyZWN0b3J5IGtpdGEgZGVuZ2FuIGBnZXR3ZCgpYA0KYGBge3IgZWNobz1UUlVFfQ0KZ2V0d2QoKQ0KYGBgDQpLaXRhIGJpc2EgbWVuZ2dhbnRpIHdvcmtpbmcgZGlyZWN0b3J5IGtpdGEgZGVuZ2FuIG1lbmdndW5ha2FuIGBzZXR3ZGAuIFNpbGFoa2FuIGdhbnRpIGFsYW1hdG55YSBkZW5nYW4gYWxhbWF0IGZvbGRlciB5YW5nIEFuZGEgaW5naW5rYW4hDQpgYGB7ciBlY2hvPVRSVUV9DQpzZXR3ZCgiRDovVGVtcC9SIFR1dG9yaW5nL0phbiAyMSIpDQpgYGANCg0KS2FsYXUga2l0YSBjb2JhIGNlayBsYWdpIGRpIGBnZXR3ZCgpYCwga2l0YSBha2FuIG1lbGloYXQgYmFod2EgcGVydWJhaGFuIGFsYW1hdCB3b3JraW5nIGRpcmVjdG9yeSB0ZWxhaCBiZXJoYXNpbC4oQmVsdW0gZGlrYXNpaCBjb250b2ggZGkgc2luaSBrYXJlbmEgUm5vdGVib29rLCB5YW5nIGRpZ3VuYWthbiB1bnR1ayBtZW51bGlzIG1vZHVsIGluaSwgdGlkYWsgYmlzYSBtZW55aW1wYW4gc2V0dGluZyBkYXJpIGBzZXR3ZCgpYCBzZWNhcmEga29uc2lzdGVuKS4NCg0KIyMjIDIuMi4gRGF0YSBkYXJpIENTViBkYW4gRXhjZWwNClVudHVrIG1lbmdhbWJpbCBkYXRhIGRhcmkgZmlsZSAqKi5jc3YqKiwga2l0YSBiaXNhIG1lbmdndW5ha2FuIGZ1bmdzaSBgcmVhZC5jc3ZgLiBQZXJpbnRhaCB5YW5nIGtpdGEgbWFzdWtrYW4gYWRhbGFoIHNlcGVydGkgYmVyaWt1dDoNCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX2NzdiA8LSByZWFkLmNzdigiLi9TZXNzaW9uIDIvSmFqYW4gUTEgLSBTaGVldDEuY3N2IikNCmphamFuX2Nzdg0KYGBgDQpCYWdpYW4gZGFyaSBzaW50YWtzIHlhbmcgZGl0dWxpcyBkaSBkYWxhbSBrdXJ1bmcgbWVudW5qdWtrYW4gYmFod2Ega2l0YSBzZWRhbmcgbWVuZ2FtYmlsIGRhdGEgZGFyaSBkaXJlY3RvcnkgdGVydGVudHUuIFNpbmdrYXRueWEsIGAiLi9TZXNzaW9uIDIvImAgbWVudW5qdWtrYW4ga2l0YSBzZWRhbmcgbWVydWp1ayBzZXN1YXR1IGRpIGZvbGRlciAiU2Vzc2lvbiAyIiwgZGFuIGBKYWphbiBRMSAtIFNoZWV0MS5jc3ZgIG1lbnVuanVra2FuIG5hbWEgZGFyaSBmaWxlbnlhLiANCg0KSW5nYXQga2VtYmFsaSBrZSBwZW1iYWhhc2FuIHRlbnRhbmcgYHNldHdkYC4gUGVyaW50YWggZGkgZGFsYW0gYHJlYWQuY3N2YCBha2FuIG1lcnVqdWsga2Ugc3ViLWZvbGRlciB5YW5nIGRpamFkaWthbiB3b3JraW5nIGRpcmVjdG9yeSB1bnR1ayBtZW5lbnR1a2FuIHBhdGgga2UgZmlsZSB5YW5nIGRpdHVqdSwgc2VoaW5nZ2Ega2l0YSB0aWRhayBwZXJsdSBtZW51bGlza2FuIGAiRDovVGVtcC9SIFR1dG9yaW5nL0phbiAyMS9TZXNzaW9uIDIvSmFqYW4gUTEgLSBTaGVldDEuY3N2ImAuIEJhZ2lhbiBhd2FsIGhpbmdnYSAnSmFuIDIxJyBzdWRhaCBjdWt1cCBkaSBzZXR0aW5nIHdvcmtpbmcgZGlyZWN0b3J5LCBkYW4gdGlkYWsgcGVybHUgZGl0dWxpc2thbiBsYWdpLiANCg0KVW50dWsgZmlsZS1maWxlIGV4Y2VsIHNlcGVydGkgJy54bHN4Jywga2l0YSBwZXJsdSBtZW5nZ3VuYWthbiBmdW5nc2kgeWFuZyBiZXJiZWRhLCB5YWl0dSBgcmVhZHhsYC4gUGFja2FnZSBpbmkgc3VkYWggdGVybWFzdWsgZGkgZGFsYW0gYHRpZHl2ZXJzZWAgamFkaSBraXRhIGN1a3VwIG1lbmdpbnN0YWxsIGB0aWR5dmVyc2VgIHNhamEsIGRhbiBzZXRlcnVzbnlhIHN1ZGFoIGJpc2EgbWVuZ2d1bmFrYW4gcGFja2FnZSBpbmkuIFRldGFwaSwga2l0YSB0ZXRhcCBoYXJ1cyBtZW1hbmdnaWwgcGFja2FnZSBgcmVhZHhsYCBkaSB0aWFwIHNlc2kgamlrYSBpbmdpbiBtZW5nZ3VuYWthbm55YS4NCmBgYHtyIGVjaG89VFJVRX0NCmxpYnJhcnkocmVhZHhsKQ0KYGBgDQoNClBlcmludGFoIHVudHVrIG1lbWFuZ2dpbCBmaWxlIGV4Y2VsIHRpZGFrIGphdWggYmVyYmVkYSBkZW5nYW4gZmlsZSwgY3N2LCB5YW5nIGJpc2EgZGlsaWhhdCBkaSBjb250b2ggZGkgYmF3YWggaW5pIGRlbmdhbiBkYXRhc2V0IHlhbmcgc2FtYS4gDQpgYGB7ciBlY2hvPVRSVUV9DQpqYWphbl94bHN4IDwtIHJlYWRfeGxzeCgiLi9TZXNzaW9uIDIvSmFqYW4gUTEueGxzeCIsIHNoZWV0ID0gIlNoZWV0MSIpDQpqYWphbl94bHN4DQpgYGANClBlcmJlZGFhbiB5YW5nIHBhbGluZyB0ZXJsaWhhdCBkaSBzaW5pIGFkYWxhaCBraXRhIGJpc2EgbWVtYmVyaWthbiBzcGVzaWZpa2FzaSBzaGVldCBtYW5hIHlhbmcgaGFydXMgZGlhbWJpbC4gSGFsIGluaSBkaWxha3VrYW4gbWVuZ2d1bmFrYW4gYHNoZWV0ID0gYC4gSmlrYSB0aWRhayBkaXNwZXNpZmlrYXNpLCBtYWthIHNlY2FyYSBkZWZhdWx0IGByZWFkX3hsc3hgLiBBa2FuIG1lbmdhbWJpbCBzaGVldCBwZXJ0YW1hLg0KDQpQZXJsdSBkaXBlcmhhdGlrYW4ganVnYSBrZXRpa2Ega2l0YSBtZW1iYWNhIGRhdGEgZGFyaSB0YWJlbCwgZm9ybWF0IHRhYmVsIHRlcnNlYnV0IGhhcnVzIHNlZGVyaGFuYTsgc2F0dSBzaGVldCA9IHNhdHUgdGFiZWwgeWFuZyBrb25zaXN0ZW4uIFIganVnYSBjZW5kZXJ1bmcgb3RvbWF0aXMgbWVuZ2FtYmlsIHJvdyBwZXJ0YW1hIGRhcmkgdGFiZWwgYXBhcHVuIHNlYmFnYWkgYGhlYWRlcmAuIE5hbXVuLCBqaWthIGZvcm1hdCBkYXRhIHRpZGFrIHNlc3VhaSwgYmlzYSBkaWxha3VrYW4gZGF0YSB3cmFuZ2xpbmcuIEhhbCBpbmkgYmlzYSBkaWxha3VrYW4gZGVuZ2FuIGNhcmEgbWlzYWxueWEsIGtpdGEgZ3VuYWthbiBvcHNpIGBoZWFkZXIgPSBGQUxTRWAsIHlhbmcgYWthbiBtZW1idWF0IFIgb3RvbWF0aXMgbWVtYnVhdCBoZWFkZXIgYmFydSBkaSBhdGFzIHRhYmVsIGtpdGEuDQpgYGB7ciBlY2hvPVRSVUV9DQpqYWphbl9jc3Zfbm9oZWFkZXIgPC0gcmVhZC5jc3YoIi4vU2Vzc2lvbiAyL0phamFuIFExIC0gU2hlZXQxLmNzdiIsIGhlYWRlciA9IEZBTFNFKQ0KamFqYW5fY3N2X25vaGVhZGVyDQpgYGANClNldGVsYWhueWEsIGtpdGEgYmlzYSBtZW5nZ3VuYWthbiBgbGlzdGAgbGFpbiwgeWFuZyBraXRhIGd1bmFrYW4gdW50dWsgbWUtcmVwbGFjZSBuYW1hIGtvbG9tIHlhbmcgb3RvbWF0aXMgZGlidWF0IG9sZWggUi4gUmVwbGFjZSBkZW5nYW4gbWVuZ2d1bmFrYW4gYGNvbG5hbWVzYA0KYGBge3IgZWNobz1UUlVFfQ0KIyBNZW1idWF0IGxpc3QgYmFydSB1bnR1ayBuYW1hIGtvbG9tIA0KbmFtYV9rb2xvbSA8LSBjKCJrb2xfMSIsICJrb2xfMiIsICJrb2xfMyIsICJrb2xfNCIsICJrb2xfNSIsICJrb2xfNiIpDQpuYW1hX2tvbG9tDQpgYGANCmBgYHtyIGVjaG89VFJVRX0NCiMgTWVuZ2dhbnRpIG5hbWEga29sb20gZGVuZ2FuICdjb2xuYW1lcycNCmNvbG5hbWVzKGphamFuX2Nzdl9ub2hlYWRlcikgPC0gbmFtYV9rb2xvbQ0KamFqYW5fY3N2X25vaGVhZGVyDQpgYGANClRlbnR1bnlhIHNldGVsYWhueWEsIHJvd3MgeWFuZyBiZXJpc2kgbmFtYSBrb2xvbSBzZWJlbHVtbnlhIGhhcnVzIGRpLWRlbGV0ZS4gTWVuZGVsZXRlIGtvbG9tIGRhbiByb3dzIGFrYW4gZGliYWhhcyBkaSBiYWdpYW4gc2VsYW5qdXRueWEuIA0KDQojIyAzLiBEYXRhIHNjcmVlbmluZyBkYW4gY2xlYW5pbmcgZGFzYXINClVtdW1ueWEsIGhhbCBwZXJ0YW1hIHlhbmcga2l0YSBsYWt1a2FuIHNldGVsYWggbWVuZy1la3Nwb3IgdGFiZWwgYWRhbGFoIHVudHVrIG1lbGloYXQgYXBha2FoIGFkYSBwZXJtYXNhbGFoYW4gZGkgZGF0YS4gT2xlaCBrYXJlbmEgaXR1LCBzZWthcmFuZyBtYXJpIGtpdGEgYmFoYXMgc2VjYXJhIHNpbmdrYXQgbWVuZ2VuYWkgZGF0YSBzY3JlZW5pbmcsIHV0YW1hbnlhIG1lbmdlY2VrICoqYWNjdXJhY3kqKiBkYXJpIGRhdGEuICoqQWNjdXJhY3kqKiBkaSBzaW5pIGFydGlueWEgDQoNClVudHVuZ255YSBjdWt1cCBtdWRhaCBkaSBSIHVudHVrIHNlY2FyYSBjZXBhdCBtZW5kYXBhdGthbiBnYW1iYXJhbiBrZXNlbHVydWhhbiB0ZW50YW5nIGRhdGEga2l0YS4gRnVuZ3NpIGBzdW1tYXJ5YCBtZW1iZXJpa2FuIGdhbWJhcmFuIHRlcnNlYnV0LCBzZXBlcnRpIHlhbmcgYmlzYSBkaWxpaGF0IGRpIGJhd2FoLg0KYGBge3IgZWNobz1UUlVFfQ0Kc3VtbWFyeShqYWphbl9jc3YpDQpgYGANCkRhcmkgYHN1bW1hcnlgIGtpdGEgYmlzYSBtZWxpaGF0IGJlYmVyYXBhIGhhbDogDQoNCi0gQ2xhc3MgZGFyaSBgbmFtYWAsIGBidWxhbmAsIGRhbiBgamFqYW5gIGFkYWxhaCBgY2hhcmFjdGVyYCwgc2VkYW5na2FuIGBqdW1sYWhgLCBgaGFyZ2FgLCBkYW4gYHBlbmdlbHVhcmFuYCBhZGFsYWggYG51bWVyaWNgLCBrYXJlbmEgeWFuZyBsYW5nc3VuZyBtdW5jdWwgYWRhbGFoIGRhdGEtZGF0YSBzdW1tYXJ5IHN0YXRpc3RpY3MgZGFyaSBrb2xvbSB0ZXJzZWJ1dC4gDQotIFVudHVrIHZhcmlhYmVsIHlhbmcgYG51bWVyaWNgLCBraXRhIGxhbmdzdW5nIGJpc2EgbWVsaWhhdCBtZWFuLCBtZWRpYW4sIHF1YW50aWxlLCBzZXJ0YSBuaWxhaSBtaW4gZGEgbWFrcy1ueWEuDQotIFRlcmRhcGF0IG1pc2luZyBkYXRhIChgTkEnc2ApIGRpIGJlYmVyYXBhIGtvbG9tDQoNCkRlbmdhbiBpbmZvcm1hc2kgaW5pLCBraXRhIGJpc2EgdGFodSBhcGEgc2FqYSB5YW5nIGhhcnVzIGRpdWJhaCAoZGliZXJzaWhrYW4pLiBMZWJpaCBsYW5qdXQsIGtpdGEgYWthbiBiYWhhcyBjYXJhLWNhcmEgbWVtYmVyc2loa2FubnlhIGRpIGJhZ2lhbiBzZWxhbmp1dG55YS4NCg0KIyMgNC4gTWFuaXB1bGFzaSB0YWJlbCAobGFuanV0KQ0KIyMjIDQuMS4gTWVuZ2hhcHVzIHJvdw0KVGVyZGFwYXQgYmFueWFrIGZ1bmdzaSB1bnR1ayBiaXNhIG1lbmdoYXB1cyByb3cgdGVydGVudHUuIENhcmEgeWFuZyBwYWxpbmcgc2VkZXJoYW5hIGFkYWxhaCBkZW5nYW4gbWVuZ2d1bmFrYW4gb3BlcmF0b3IgYC1gIHVudHVrIG1lbWJ1YXQgc2xpY2UgZGFyaSBkYXRhc2V0IHlhbmcgdGlkYWsgbWVtaWxpa2kgcm93IGRpIHVydXRhbiB0ZXJzZWJ1dC4gSW5nYXRsYWggYmFod2EgZGkgZGF0YXNldCBgamFqYW5fY3N2YCByb3cga2VkdWEgYWRhbGFoIGNhdGF0YW4gYmFod2EgQWx2aW4gamFqYW4gYnVyZ2VyIGRpIGJ1bGFuIEphbnVhcmkuIE1hcmkga2l0YSBjb2JhIGhpbGFuZ2thbiByb3cgdGVyc2VidXQgZGVuZ2FuIG1lbmdndW5ha2FuIG1ldG9kZSBpbmkuDQoNCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX2NzdlstYygyKSwgXQ0KYGBgDQpCZWJlcmFwYSBmdW5nc2kgbGFpbiBtZW1pbGlraSBrZWd1bmFhbiB5YW5nIHNhbWEuIE1pc2FsbnlhIGRlbmdhbiBmdW5nc2kgYHNsaWNlYCB5YW5nIG1lcnVwYWthbiBiYWdpYW4gZGFyaSBgdGlkeXZlcnNlYC4gU2ludGFrc255YSB0aWRhayBqYXVoIGJlZGEgZGVuZ2FuIG1lbmdndW5ha2FuIG9wZXJhdG9yIGAtYC4NCmBgYHtyIGVjaG89VFJVRX0NCnNsaWNlKGphamFuX2NzdiwgLWMoMikpDQpgYGANCktpdGEganVnYSBiaXNhIG1lbmdoYXB1cyByb3cgYmVyZGFzYXJrYW4gbmlsYWkgZGkgZGFsYW0gcm93IHRlcnNlYnV0LiBNaXNhbG55YSwga2l0YSBpbmdpbiBtZW5naGFwdXMgc2VtdWEgY2F0YXRhbiB0ZW50YW5nIEFsdmluIGRlbmdhbiBtZWxha3VrYW4gc3Vic2V0dGluZyBiaWFzYSwgZGVuZ2FuIG9wZXJhdG9yIGAhPWAuIA0KYGBge3IgZWNobz1UUlVFfQ0KamFqYW5fY3N2WyhqYWphbl9jc3YkbmFtYSAhPSAiQWx2aW4iKSwgXQ0KYGBgDQphdGF1IG1lbmdndW5ha2FuIG9wZXJhdG9yIGAhYCBzZWJlbHVtIGtpdGEgbWVuc3Blc2lmaWthc2kgdmFsdWUgeWFuZyBpbmdpbiBkaWhpbGFuZ2thbi4gKHBlcmhhdGlrYW4gdGFuZGEgYCE9YCBkaXViYWggbWVuamFkaSBgPT1gKS4gS2FsaSBpbmkga2l0YSBjb2JhIHVudHVrIG1lbmdoaWxhbmdrYW4gc2VtdWEgeWFuZyBiZXJuYW1hIEFsdmluIGRhbiBCZW5lLg0KYGBge3IgZWNobz1UUlVFfQ0KamFqYW5fY3N2WyEoamFqYW5fY3N2JG5hbWEgPT0gIkFsdmluIiB8IGphamFuX2NzdiRuYW1hID09ICJCZW5lIiksIF0NCmBgYA0KDQpLaXRhIGJpc2EgbWVsYWt1a2FuIG9taXNzaW9uIG1lbmdndW5ha2FuIGR1YSBrb25kaXNpLCBkZW5nYW4gb3BlcmF0b3IgYm9vbGVhbiBgJmAuIFVudHVrIGNvbnRvaCBrYWxpIGluaSwga2l0YSBjb2JhIGhpbGFuZ2thbiBzZW11YSBjYXRhdGFuIHRlbnRhbmcgQWx2aW4gbWVtYmVsaSBqYWphbiBrb3BpLCB0YXBpIGtpdGEgaW5naW4gc2VtdWEgY2F0YXRhbiB0ZW50YW5nIEFsdmluIHRldGFwIGFkYSwgZGkgbHVhciBkYXJpIGphamFuIGtvcGlueWEuIEFydGlueWEsIGtpdGEgaW5naW4gbWVuZ2hhcHVzIHJvdyBkZW5nYW4gbmFtYSA9IEFsdmluIERBTiBqYWphbiA9IGtvcGkuDQoNCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX2Nzdl8yIDwtIGphamFuX2NzdlshKGphamFuX2NzdiRuYW1hID09ICJBbHZpbiIgJiBqYWphbl9jc3YkamFqYW4gPT0gImtvcGkiKSwgXQ0KYGBgDQojIyMgNC4yLiBNZW5naGFwdXMga29sb20NClNhbWEgc2VwZXJ0aSBtZW5naGFwdXMgcm93LCBrb2xvbSBkYXBhdCBkaWhhcHVzIGRlbmdhbiBtZXJlZmVyIGtlIGluZGVrc255YS4gU2ludGFrc255YSBwdW4gdGlkYWsgYmVnaXR1IGJlcmJlZGEgZGVuZ2FuIG1lbmdoYXB1cyByb3cuIENvbnRvaCwgbWFyaSBraXRhIGhhcHVzIGtvbG9tIGBuYW1hYC4NCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX2NzdlssIC1jKDEpXQ0KYGBgDQpLYXJlbmEga29sb20gbWVtaWxpa2kgbmFtYSB2YXJpYWJlbCB5YW5nIGplbGFzLCBraXRhIGJpc2EgbWVuZ2d1bmFrYW4gbmFtYSB0ZXJzZWJ1dCB1bnR1ayBtZW1hbmdnaWwgYXRhdSBtZW5naGFwdXMga29sb20gaXR1LiBVbnR1ayBtZWxha3VrYW4gaGFsIGluaSwga2l0YSBiaXNhIG1lbmdndW5ha2FuIGZ1bmdzaSBgc3Vic2V0YCwgZnVuZ3NpIHlhbmcgdGVyc2VkaWEgZGkgYmFzZSBSLiBTaW50YWtzbnlhIGN1a3VwIHNpbXBlbCwgY3VrdXAgc3Blc2lmaWthc2lrYW4gZGF0YSBkYW4ga29sb20geWFuZyBpbmdpbiBkaWhpbGFuZ2thbiBkaSBkYWxhbSB2YXJpYWJlbCBgc2VsZWN0YCwgZGktd3JhcCBkZW5nYW4gYC0oKWAuDQpgYGB7ciBlY2hvPVRSVUV9DQpzdWJzZXQoamFqYW5fY3N2LCBzZWxlY3QgPSAtKG5hbWEpKQ0KYGBgDQoNClNlbGFpbiBtZW5naGFwdXMga29sb20sIGtpdGEganVnYSBiaXNhIG1lbGFrdWthbiBzZWJhbGlrbnlhLCB5YWl0dSBtZW1idWF0IGRhdGFzZXQgYmFydSB5YW5nIGlzaW55YSBoYW55YSBrb2xvbSB5YW5nIGluZ2luIGtpdGEgZ3VuYWthbi4gVGVudHUgaW5pIGt1cmFuZyBsZWJpaCBzYW1hIHNhamEgZGVuZ2FuIG1lbmdoYXB1cyBrb2xvbSBiaWFzYSwgbWFrYSBkYXJpIGl0dSBraXRhIGJpc2EgbWVuZ2d1bmFrYW4gcGlsaWhhbiBtZW5naGFwdXMgYXRhdSBtZW55ZWxla3NpIGtvbG9tLCB0ZXJnYW50dW5nIGRlbmdhbiBrb25kaXNpLiBEaSBtZXRvZGUgZGkgYmF3YWggaW5pLCBraXRhIG1lbWJ1YXQgbGlzdCBkYXJpIG5hbWEga29sb20geWFuZyBpbmdpbiBraXRhIHNpbXBhbiwgbGFsdSBtZW5nZ3VuYWthbiBsaXN0IHRlcnNlYnV0IGRpIGRhbGFtIGBbXWAgdW50dWsgbWVtYW5nZ2lsIGtvbG9tIHRlcnNlYnV0IGRhcmkgbmFtYS1uYW1hbnlhLiANCg0KYGBge3IgZWNobz1UUlVFfQ0Ka2VlcCA8LSBjKCJuYW1hIiwgImJ1bGFuIiwgImphamFuIikNCmtlZXANCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUV9DQpqYWphbl9jc3Zba2VlcF0NCmBgYA0KUGFja2FnZSBgdGlkeXZlcnNlYCBtZW55ZWRpYWthbiBvcHNpIHlhbmcgc2FuZ2F0IGZsZWtzaWJlbCBkZW5nYW4gZnVuZ3NpIGBzZWxlY3RgLiBEaSBmdW5nc2kgaW5pIGtpdGEgYmlzYSBtZW55ZWxla3NpIHZhcmlhYmVsIGJlcmRhc2Fya2FuIG1hdGNoaW5nIHBhcnNpYWwgKG1pcy4gaGFueWEga29sb20geWFuZyBtZW1pbGlraSBzdHJpbmcgdGVydGVudHUgZGkgbmFtYW55YSksIG1lbmdndW5ha2FuIHJlZ2V4LCBhdGF1IG1lbmdndW5ha2FuIGZ1bmdzaS4gTGloYXRsYWggZG9rdW1lbnRhc2lueWEgdW50dWsgcGVuamVsYXNhbiBsZWJpaCBsZW5na2FwIChgP3NlbGVjdGApDQoNClNhbGFoIHNhdHUgY29udG9oLCBraXRhIGJpc2EgbWVuZ2FtYmlsIGhhbnlhIHZhcmlhYmVsIHlhbmcgYmVyc2lmYXQgYG51bWVyaWNgLiBVbnR1ayBtZWxha3VrYW4gaW5pLCBraXRhIGd1bmFrYW4gZnVuZ3NpIGRpIGRhbGFtIHNlbGVjdCBgd2hlcmUoKWAgdW50dWsgbWVtYmVyaSBwZXJpbnRhaCBiYWh3YSBraXRhIG1lbmNhcmkga29sb20gZGVuZ2FuIGtvbmRpc2kgdGVydGVudHUuDQpgYGB7ciBlY2hvPVRSVUV9DQpzZWxlY3QoamFqYW5fY3N2LCB3aGVyZShpcy5udW1lcmljKSkNCmBgYA0KDQoNCiMjIyA0LjMuIE1pc3NpbmcgZGF0YQ0KTWlzc2luZyBkYXRhIGJpYXNhbnlhIGtpdGEgdGFuZ2FuaSBkZW5nYW4gZHVhIGNhcmEsIHlhaXR1IG1lbmdoYXB1cyBhdGF1IG1lbmdnYW50aW55YS4gS2l0YSBha2FuIGJhaGFzIGNhcmEgcGVydGFtYSB0ZXJsZWJpaCBkYWh1bHUuDQoNCk1lbmdoYXB1cyBtaXNzaW5nIGRhdGEgZGkgc2luaSBiaXNhIGRlbmdhbiBkdWEgY2FyYSwgeWFpdHUgbWVuZ2hhcHVzIHJvdyBhdGF1IGtvbG9tLiBTZWJ1YWggcnVsZSBvZiB0aHVtYiwgamlrYSBqdW1sYWggbWlzc2luZyBkYXRhIHRlcnNlYmFyIGF0YXUgdGlkYWsgdGVybGFsdSBiYW55YWssIGtpdGEgYmlzYSBtZW5nZ3VuYWthbiBvcHNpIHBlcnRhbWEsIG1lbmdoYXB1cyByb3cuIENhcmEgeWFuZyBtdWRhaCB1bnR1ayBtZWxha3VrYW4gaW5pIGFkYWxhaCBkZW5nYW4gYG5hLm9taXRgLCB5YW5nIGFrYW4gbWVuZ2hhcHVzIHNlbXVhIHJvdyB5YW5nIG1lbWlsaWtpIG1pc3NpbmcgZGF0YSB0YW5wYSB0ZXJrZWN1YWxpLg0KDQpgYGB7ciBlY2hvPVRSVUV9DQpuYS5vbWl0KGphamFuX2NzdikNCmBgYA0KTmFtdW4sIGppa2EgbWlzc2luZyBkYXRhIHRlcmtvbnNlbnRyYXNpIGRpIHNhdHUgdmFyaWFiZWwsIGtpdGEgYmlzYSBtZW5naGFwdXMga29sb20geWFuZyByZWxldmFuLiBVbnR1ayBjb250b2ggZGkgYmF3YWggaW5pLCBraXRhIGNvYmEgZ3VuYWthbiBkYXRhc2V0IHlhbmcgbWVyZmxla3Npa2FuIGtvbmRpc2kgdGVyc2VidXQuIEluZ2F0IGJhaHdhIGtpdGEgYmlzYSBtZW5kYXBhdGthbiBqdW1sYWggbWlzc2luZyBkYXRhIGRlbmdhbiBtZW5nZ3VuYWthbiBgc3VtbWFyeWAgdGVybGViaWggZGFodWx1LiANCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX21pc3NpbmcgPC0gcmVhZC5jc3YoIi4vU2Vzc2lvbiAyL0phamFuIFExIC0gU2hlZXQxIChtaXNzaW5nKS5jc3YiKQ0Kc3VtbWFyeShqYWphbl9taXNzaW5nKQ0KYGBgDQpLaXRhIGxpaGF0IGJhaHdhIGtpdGEgbWVtaWxpa2kganVtbGFoIG1pc3NpbmcgZGF0YSB5YW5nIGJhbnlhayBkaSBgcGVuZ2VsdWFyYW5gLiBKdW1sYWggaXR1IG1lbmNha3VwIHNhbXBlbCB5YW5nIHNpZ25pZmlrYW4sIGhhbXBpciBzZXRlbmdhaCBkYXJpIHNhbXBlbCBraXRhLiBTZWhpbmdnYSwgbGViaWggYmlqYWsgdW50dWsga2l0YSBkcm9wIHNhamEga29sb21ueWEsIGRhcmlwYWRhIGhhcnVzIG1lbmdoYWJpc2kgc2FtcGVsLg0KYGBge3IgZWNobz1UUlVFfQ0Kc3Vic2V0KGphamFuX21pc3NpbmcsIHNlbGVjdD0gLShwZW5nZWx1YXJhbikpDQpgYGANCkxhbHUsIGtpdGEgYmlzYSBtZW5na29tYmluYXNpa2FuIGNhcmEgdGVyc2VidXQgdW50dWsgbWVtYnVhdCBkYXRhc2V0IGtpdGEgYmVuYXItYmVuYXIgYmVyc2loIGRhcmkgbWlzc2luZyBkYXRhOg0KYGBge3IgZWNobz1UUlVFfQ0KbmEub21pdChzdWJzZXQoamFqYW5fbWlzc2luZywgc2VsZWN0PSAtKHBlbmdlbHVhcmFuKSkpDQpgYGANCkppa2Ega2l0YSBsZWJpaCBpbmdpbiBtZW5nZ2FudGkgbWlzc2luZyBkYXRhIGRlbmdhbiBuaWxhaSBsYWluLCBraXRhIGJpc2EgbWVsYWt1a2FuIGFzc2lnbm1lbnQgYmlhc2EsIGRlbmdhbiBjYXJhIHNlcGVydGkgY29udG9oIGRpIGJhd2FoIGluaS4NCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX2Nzdltpcy5uYShqYWphbl9jc3YpXSA8LSAwDQpqYWphbl9jc3YNCmBgYA0KIyMjIDQuNC4gTWVtYnVhdCBrb2xvbSBiYXJ1IGJlcmRhc2Fya2FuIHZhbHVlIGRpIGtvbG9tIGxhaW4gDQpLYWRhbmcga2l0YSBidXR1aCBtZW1idWF0IGthdGVnb3JpIGF0YXUgbmlsYWkgYmFydSBkaSBkYWxhbSBkYXRhLCBkaSBsdWFyIGRhcmkgaGFsIHlhbmcga2l0YSBkYXBhdCBkaSBkYXRhc2V0IG1lbnRhaC5VbXVtbnlhLCBiaXNhIGRpZ3VuYWthbiBmdW5nc2kgYGlmZWxzZSgpYC4gDQoNCkthdGFrYW5sYWgga2l0YSBpbmdpbiBtZW5na2F0ZWdvcmlzYXNpIHBlbmdlbHVhcmFuIG1lbmphZGkgdGluZ2dpIGF0YXUgcmVuZGFoLCBiZXJkYXNhcmthbiBkaWEgZGkgYXRhcyA1MDAgcmlidSBydXBpYWggYXRhdSB0aWRhay5MYWx1LCBqaWthIGRpIGFudGFyYSA1MDAgcmlidSBkYW4gMjUwIHJpYnUsIGtpdGEgYW5nZ2FwICdzZWRhbmcnLCBkYW4gZGkgYmF3YWggMjUwIHJpYnUga2l0YSBhbmdnYXAgJ3JlbmRhaCcuDQpgYGB7ciBlY2hvPVRSVUV9DQpqYWphbl9jc3YkdGluZ2dpX3JlbmRhaCA8LSBpZmVsc2UoamFqYW5fY3N2JHBlbmdlbHVhcmFuID49IDUwMDAwMCwgIlRpbmdnaSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShqYWphbl9jc3YkcGVuZ2VsdWFyYW4gPj0gMjUwMDAwLCAiU2VkYW5nIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJlbmRhaCIpKQ0KamFqYW5fY3N2DQpgYGANCkZ1bmdzaSBgaWZlbHNlYCBzZXJpbmdrYWxpIGN1a3VwIGludHVpdGlmLCB0ZXRhcGkgYmlzYSBtZW1iaW5ndW5na2FuIGppa2Ega29uZGlzaW55YSBiYW55YWsuIFN0cnVrdHVybnlhIHlhbmcgaGFydXMgYmVyc2lmYXQgbmVzdGVkIG1lbWJ1YXQgbXVkYWggdW50dWsgZnVuZ3NpIGluaSBtZW5qYWRpIHRlcmxhbHUgYmFueWFrIGZ1bmdzaS1kaS1kYWxhbS1mdW5nc2kgeWFuZyBtZW1iaW5ndW5na2FuLiBBbHRlcm5hdGlmIGRhcmkgYGlmZWxzZWAgeWFuZyBzZXJpbmcgZGlndW5ha2FuIGFkYWxhaCBgbXV0YXRlYCwgYmFnaWFuIGRhcmkgYHRpZHl2ZXJzZWAuIA0KDQpNYXJpIGtpdGEgY29iYSBgbXV0YXRlYCB1bnR1ayB0dWp1YW4geWFuZyBzYW1hIGRlbmdhbiBjb250b2ggc2ViZWx1bW55YSwga2FsaSBpbmkgZGkgZGF0YXNldCB5YW5nIGJlcmJlZGEuIA0KDQpQZXJoYXRpa2FuIGJhaHdhIGRpIHNpbmkga2l0YSBtZW5nZ3VuYWthbiBvcGVyYXRvciBgICU+JSBgLCB5YW5nIGRpc2VidXQgc2ViYWdhaSBwaXBlLiBPcGVyYXRvciBpbmkgbWVydXBha2FuIGJhZ2lhbiBkYXJpIGB0aWR5dmVyc2VgLCBkaSBtYW5hIGFydGlueWEsIHNldGVsYWggb3BlcmF0b3IgcGlwZSBkaXR1bGlzLCBwZXJpbnRhaCBzZWxhbmp1dG55YSBha2FuIG1lcmVmZXIga2UgZGF0YXNldCB5YW5nIGRpdHVqdSAoZGFsYW0gaGFsIGluaSBgamFqYW5feGxzeGApLg0KDQpTZWxhaW4gaXR1LCB0ZXJkYXBhdCBqdWdhIGJhcmlzIGJlcnR1bGlzYW4gYFRSVUUgfiAiTGFpbm55YSJgLiBCYXJpcyBpbmkgYXJ0aW55YSAiSmlrYSBuaWxhaW55YSB0aWRhayBtYXN1ayBkaSBkYWxhbSBrb25kaXNpIHlhbmcgZGlzZWJ1dGthbiBkaSBhdGFzLCBtYWthIHR1bGlzbGFoICJMYWlubnlhIi4NCmBgYHtyIGVjaG89VFJVRX0NCmphamFuX3hsc3ggJT4lICBtdXRhdGUodGluZ2dpX3JlbmRhaCA9IGNhc2Vfd2hlbigNCiAgcGVuZ2VsdWFyYW4gPj0gNTAwMDAwIH4gIlRpbmdnaSIsIA0KICBwZW5nZWx1YXJhbiA8IDUwMDAwMCAmIHBlbmdlbHVhcmFuID49IDI1MDAwMCB+ICJTZWRhbmciLCANCiAgcGVuZ2VsdWFyYW4gPCAyNTAwMDAgfiAiUmVuZGFoIiwgDQogIFRSVUUgfiAiTGFpbm55YSINCikpDQpgYGANCg0KDQoNCg0KIyMjIDQuNS4gTWVuZ2dhbnRpIGplbmlzIHZhcmlhYmVsDQpEYXRhIHlhbmcga2l0YSBhbWJpbCBkYXJpIHN1bWJlciBla3N0ZXJuYWwgYWthbiBzZWNhcmEgb3RvbWF0aXMgZGktYXNzaWduIG9sZWggUiBrZSBqZW5pcyB2YXJpYWJlbCB5YW5nIHBhbGluZyBtYXN1ayBha2FsIHVudHVrIGRhdGEgdGVyc2VidXQuIFRldGFwaSwga2FkYW5nIGplbmlzIGRhdGEgdGVyc2VidXQgdGlkYWsgc2VzdWFpIGRlbmdhbiB5YW5nIGtpdGEgaW5naW5rYW4uIFNlYmFnYWkgY29udG9oLCBtYXJpIGtpdGEgbGloYXQgbGFnaSBzdHJ1a3R1ciBkYXRhIHlhbmcga2l0YSBtaWxpa2kuDQpgYGB7cn0NCnN1bW1hcnkoamFqYW5fY3N2KQ0KYGBgDQoNCktpdGEgbGloYXQgZGkgc2l0dSBgbmFtYWAsIGBidWxhbmAsIGRhbiBgamFqYW5gIGFkYWxhaCBgY2hhcmFjdGVyYC4gSW5pIHRpZGFrIGlkZWFsLCBrYXJlbmEga2l0YSBtZW5nZXRhaHVpIGJhaHdhIHNldGlhcCBgQWx2aW5gIG1pc2FsbnlhLCBtZXJ1cGFrYW4gb3JhbmcgeWFuZyBzYW1hLiBWYXJpYWJlbCBqZW5pcyBgY2hhcmFjdGVyYCB0aWRhayBtZW1wZXJoYXRpa2FuIGhhbCB0ZXJzZWJ1dC4gT2xlaCBrYXJlbmEgaXR1LCBkaSBkYWxhbSBrYXN1cyBpbmkga2l0YSBsZWJpaCBiYWlrIG1lbmd1YmFoIHZhcmlhYmVsLXZhcmlhYmVsIHRlcnNlYnV0IG1lbmphZGkgYGZhY3RvcmAsIG1lbmdndW5ha2FuIHBlcmludGFoIGBhcy5mYWN0b3JgLiANCg0KYGBge3J9DQpqYWphbl9jc3YkbmFtYSA8LSBhcy5mYWN0b3IoamFqYW5fY3N2JG5hbWEpDQpzdW1tYXJ5KGphamFuX2NzdikNCmBgYA0KRnVuZ3NpIGBhcy5mYWN0b3JgIGFkYWxhaCB0dXJ1bmFuIGRhcmkgZnVuZ3NpIGRhbGFtIGJhc2UgciwgeWFpdHUgYGFzLmAgZGFuIGBmYWN0b3JgLiBTYW1hIGRlbmdhbiBmdW5nc2kgYGlzLmAsIGtpdGEgYmlzYSBtZW5nZ3VuYWthbiBmdW5nc2kgaW5pIHVudHVrIGJlcmJhZ2FpIGplbmlzIHZhcmlhYmVsLCB5YW5nIHNlcmluZyBkaWd1bmFrYW4gYWRhbGFoIHNlYmFnYWkgYmVyaWt1dDoNCg0KfCBGdW5nc2kgfCBEZXNrcmlwc2kgfA0KfCAtLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0tIHwNCnwgYGFzLmZhY3RvcmAgfCBNZW5ndWJhaCB2YXJpYWJlbCBtZW5qYWRpIGZha3RvciB8DQp8IGBhcy5udW1lcmljYCB8IE1lbmd1YmFoIHZhcmlhYmVsIG1lbmphZGkgbnVtZXJpayB8DQp8IGBhcy5jaGFyYWN0ZXJgIHwgTWVuZ3ViYWggdmFyaWFiZWwgbWVuamFkaSBrYXJha3RlciB8DQoNCiMjIDUuIE1lbnVsaXMgZGFuIG1lbnlpbXBhbiB0YWJlbA0KVGVyYWtoaXIsIGtpdGEgYmlzYSBtZW55aW1wYW4gdWxhbmcgZGF0YSBkYXJpIHRhYmVsIGFwYXB1biBkaSBkYWxhbSBSIGtlIGZvcm1hdCBsYWluLCBzZXBlcnRpIC5jc3YuIFVudHVrIG1lbnVsaXMgZGkgLmNzdiwga2l0YSBiaXNhIG1lbmdndW5ha2FuIGB3cml0ZS5jc3ZgLCBzZWJ1YWggZnVuZ3NpIGRhcmkgYmFzZSBSLiBQZXJoYXRpa2FuIHBlcmludGFoIGByb3cubmFtZXMgPSBGQUxTRWAuIFBlcmludGFoIGluaSBwZW50aW5nIGFnYXIga2l0YSB0aWRhayBtZW1pbGlraSBzYXR1IGtvbG9tIHRlcnNlbmRpcmkgeWFuZyBpc2lueWEgYWRhbGFoIGluZGVrcyBkYXJpIHRhYmVsLiAgDQpgYGB7cn0NCndyaXRlLmNzdihqYWphbl9jc3YsICIuL1Nlc3Npb24gMi9qYWphbl9jc3ZfY29weS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQpgYGB7cn0NCiMgY29udG9oIGppa2Egcm93Lm5hbWVzID0gVFJVRQ0Kd3JpdGUuY3N2KGphamFuX2NzdiwgImphamFuX2Nzdl9jb3B5X3Jvd25hbWVzLmNzdiIsIHJvdy5uYW1lcyA9IFRSVUUpDQpgYGANCg0K