Customer Segmentation/Freepik/vectorjuice

Customer Segmentation adalah praktik membagi pelanggan perusahaan ke dalam kelompok-kelompok yang mencerminkan kesamaan di antara pelanggan di setiap kelompok.

Pengelompokkan Customer Segmentation ini umumnya terbagi ke dalam 4 kategori:

  • demografis

  • psikografis

  • perilaku

  • geografis

Pentingnya melakukan customer Segmentation ini bertujuan untuk memutuskan bagaimana berhubungan dengan pelanggan di setiap segmen untuk memaksimalkan nilai setiap pelanggan dalam berbisnis.

Berikut adalah langkah-langkah dalam melakukan customer segmentation.

Membaca data dengan fungsi read.csv

pelanggan <- read.csv("https://storage.googleapis.com/dqlab-dataset/customer_segments.txt", sep="\t")   
pelanggan
pelanggan[c("Jenis.Kelamin", "Umur", "Profesi","Tipe.Residen")]

Vector untuk Menyimpan Nama Field

a <- c("Jenis.Kelamin", "Umur", "Profesi")
#Tampilan data pelanggan dengan nama kolom sesuai isi vector field_yang_digunakan
pelanggan[a]

Konversi Data dengan data.matrix

pelanggan_matrix <- data.matrix(pelanggan[c("Jenis.Kelamin", "Profesi", "Tipe.Residen")])
pelanggan_matrix 
      Jenis.Kelamin Profesi Tipe.Residen
 [1,]             1       5            2
 [2,]             2       3            1
 [3,]             1       4            1
 [4,]             1       4            1
 [5,]             2       5            1
 [6,]             2       4            1
 [7,]             1       5            2
 [8,]             1       4            1
 [9,]             2       4            2
[10,]             1       4            1
[11,]             2       4            2
[12,]             2       4            2
[13,]             2       5            1
[14,]             1       5            1
[15,]             2       5            1
[16,]             1       4            1
[17,]             2       1            1
[18,]             2       1            1
[19,]             2       5            1
[20,]             2       3            2
[21,]             2       5            1
[22,]             2       4            1
[23,]             1       4            1
[24,]             2       5            1
[25,]             2       5            2
[26,]             2       4            1
[27,]             2       5            1
[28,]             2       1            1
[29,]             2       4            1
[30,]             2       1            2
[31,]             2       2            1
[32,]             2       5            2
[33,]             2       2            1
[34,]             2       5            2
[35,]             2       4            2
[36,]             2       5            1
[37,]             2       4            2
[38,]             2       5            2
[39,]             2       4            1
[40,]             2       3            2
[41,]             2       1            1
[42,]             2       5            1
[43,]             2       4            1
[44,]             2       5            1
[45,]             2       4            1
[46,]             2       5            2
[47,]             2       1            1
[48,]             2       5            2
[49,]             2       1            2
[50,]             2       5            2

Menggabungkan Hasil Konversi

pelanggan <- data.frame(pelanggan, pelanggan_matrix)
#Tampilkan kembali data hasil penggabungan
pelanggan

Menormalisasikan Nilai Belanja

pelanggan$NilaiBelanjaSetahun <- pelanggan$NilaiBelanjaSetahun/1000000
pelanggan$NilaiBelanjaSetahun
 [1]  9.497927  2.722700  5.286429  5.204498 10.615206  5.215541  9.837260  5.223569
 [9]  5.993218  5.257448  5.987367  5.941914  9.333168  9.471615 10.365668  5.262521
[17]  5.677762  5.340690 10.884508  2.896845  9.222070  5.298157  5.239290 10.259572
[25] 10.721998  5.269392  9.114159  6.631680  5.271845  5.020976  3.042773 10.663179
[33]  3.047926  9.759822  5.962575  9.678994  5.972787 10.477127  5.257775  2.861855
[41]  6.820976  9.880607  5.268410  9.339737  5.211041 10.099807  6.130724 10.390732
[49]  4.992585 10.569316

Membuat Data Master

untuk melihat nilai kategori dari variabel yang telah dimodifikasi

Profesi

Profesi <- unique(pelanggan[c("Profesi","Profesi.1")])
Profesi

Jenis Kelamin

Jenis.Kelamin <- unique(pelanggan[c("Jenis.Kelamin","Jenis.Kelamin.1")])
Jenis.Kelamin

Tipe Residen

Tipe.Residen <- unique(pelanggan[c("Tipe.Residen","Tipe.Residen.1")])
Tipe.Residen

Dalam melakukan segmentasi customer ini digunakanlah penggunaan analisis clustering, dimana metode yang akan digunakan yaitu K-Means.

Kmeans adalah suatu metode yang mencoba untuk mempartisi dataset menjadi K subkelompok (cluster) non-overlapping yang berbeda yang telah ditentukan sebelumnya di mana setiap titik data hanya dimiliki oleh satu grup.

Adapun algoritma K-Means itu sendiri sebagai berikut:

  1. Tentukan jumlah cluster K
  2. Inisialisasi centroid dengan terlebih dahulu mengacak dataset dan kemudian secara acak memilih K titik data untuk centroid tanpa penggantian.
  3. Lakukan iterasi sampai tidak ada perubahan pada centroid atau penugasan titik data ke cluster tidak berubah.
  4. Hitung jumlah kuadrat jarak antara titik data dan semua centroid.
  5. Tetapkan setiap titik data ke cluster terdekat (centroid).
  6. Hitung centroid untuk cluster dengan mengambil rata-rata dari semua titik data yang dimiliki setiap cluster.

Fungsi kmeans

#Bagian K-Means
set.seed(100)
#fungsi kmeans untuk membentuk 5 cluster dengan 25 skenario random dan simpan ke dalam variable segmentasi
segmentasi <-kmeans(x=pelanggan[c("Jenis.Kelamin.1","Umur","Profesi.1","Tipe.Residen.1","NilaiBelanjaSetahun")], centers=5, nstart=25)
#tampilkan hasil k-means
segmentasi
K-means clustering with 5 clusters of sizes 5, 12, 14, 9, 10

Cluster means:
  Jenis.Kelamin.1     Umur Profesi.1 Tipe.Residen.1 NilaiBelanjaSetahun
1            1.40 61.80000  4.200000       1.400000            8.696132
2            1.75 31.58333  3.916667       1.250000            7.330958
3            2.00 20.07143  3.571429       1.357143            5.901089
4            2.00 42.33333  4.000000       1.555556            8.804791
5            1.70 52.50000  3.800000       1.300000            6.018321

Clustering vector:
 [1] 1 3 5 5 4 3 1 5 2 2 5 5 1 1 3 2 2 1 2 3 4 5 2 4 2 5 2 4 5 4 3 4 3 3 4 2 3 4 3 3 3 2 2
[44] 3 3 3 5 4 2 5

Within cluster sum of squares by cluster:
[1]  58.21123 174.85164 316.73367 171.67372 108.49735
 (between_SS / total_SS =  92.4 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"      

Disini dapat terlihat dari hasil yang dikeluarkan bahwa data pelanggan telah terbagi ke dalam 5 cluster.

Analisa Hasil Clustering Vector

#Penggabungan hasil cluster

segmentasi$cluster
 [1] 1 3 5 5 4 3 1 5 2 2 5 5 1 1 3 2 2 1 2 3 4 5 2 4 2 5 2 4 5 4 3 4 3 3 4 2 3 4 3 3 3 2 2
[44] 3 3 3 5 4 2 5
pelanggan$cluster <- segmentasi$cluster

str(pelanggan)
'data.frame':   50 obs. of  11 variables:
 $ Customer_ID        : chr  "CUST-001" "CUST-002" "CUST-003" "CUST-004" ...
 $ Nama.Pelanggan     : chr  "Budi Anggara" "Shirley Ratuwati" "Agus Cahyono" "Antonius Winarta" ...
 $ Jenis.Kelamin      : chr  "Pria" "Wanita" "Pria" "Pria" ...
 $ Umur               : int  58 14 48 53 41 24 64 52 29 33 ...
 $ Profesi            : chr  "Wiraswasta" "Pelajar" "Professional" "Professional" ...
 $ Tipe.Residen       : chr  "Sector" "Cluster" "Cluster" "Cluster" ...
 $ NilaiBelanjaSetahun: num  9.5 2.72 5.29 5.2 10.62 ...
 $ Jenis.Kelamin.1    : int  1 2 1 1 2 2 1 1 2 1 ...
 $ Profesi.1          : int  5 3 4 4 5 4 5 4 4 4 ...
 $ Tipe.Residen.1     : int  2 1 1 1 1 1 2 1 2 1 ...
 $ cluster            : int  1 3 5 5 4 3 1 5 2 2 ...
pelanggan

Analisa Hasil Cluster Size

Cluster 1

pelanggan[which(pelanggan$cluster == 1),]

Banyaknya cluster yang terbentuk

length(which(pelanggan$cluster == 1))
[1] 5

Cluster 2

pelanggan[which(pelanggan$cluster == 2),] 

Banyaknya cluster yang terbentuk

length(which(pelanggan$cluster == 2))
[1] 12

Cluster 3

pelanggan[which(pelanggan$cluster == 3),]

Banyaknya cluster yang terbentuk

length(which(pelanggan$cluster == 3))
[1] 14

Cluster 4

pelanggan[which(pelanggan$cluster == 4),]

Banyaknya cluster yang terbentuk

length(which(pelanggan$cluster == 4))
[1] 9

Cluster 5

pelanggan[which(pelanggan$cluster == 5),]

Banyaknya cluster yang terbentuk

length(which(pelanggan$cluster == 5))
[1] 10

Analisa Hasil Cluster Means

#Analisa hasil
#Melihat cluster means dari objek 
segmentasi$centers
  Jenis.Kelamin.1     Umur Profesi.1 Tipe.Residen.1 NilaiBelanjaSetahun
1            1.40 61.80000  4.200000       1.400000            8.696132
2            1.75 31.58333  3.916667       1.250000            7.330958
3            2.00 20.07143  3.571429       1.357143            5.901089
4            2.00 42.33333  4.000000       1.555556            8.804791
5            1.70 52.50000  3.800000       1.300000            6.018321

Pada analisa hasil cluster means ini dapat terlihat bahwa hasilnya adalah sebaran kontinu. Padahal di awal sebelumnya data berdistribusi diskrit.

Meski demikian kita bisa menyimpulkan hasil di atas dengan pendekatan pembulatan, sehingga hasil yang diperoleh dapat dijelaskan.

Pada analisis ini diperoleh:

  • cluster 1, pada jenis kelamin memiliki kecenderungan yaitu laki-laki dengan rataan umur usia 62 tahun yang memiliki kategori profesi yaitu profesional dengan tipe residen cluster dan rataan belanja adalah 8.7 juta.

  • Cluster 2, pada jenis kelamin memiliki kecenderungan yaitu laki-laki dengan rataan umur usia 32 tahun yang memiliki kecenderungan kategori profesi yaitu profesional dengan tipe residen cluster dan rataan belanja adalah 7.3 juta.

  • Cluster 3, pada jenis kelamin yaitu Perempuan dengan rataan umur usia 20 tahun yang memiliki kecenderungan kategori profesi yaitu profesional dengan tipe residen cluster dan rataan belanja adalah 5.9 juta.

  • Cluster 4, pada jenis kelamin semuanya yaitu Perempuan dengan rataan umur usia 42 tahun yang berprofesi sebagai profesional dengan tipe residen sectorc dan rataan belanja adalah 8.8 juta.

  • Cluster 5, pada jenis kelamin memiliki kecenderungan yaitu perempuan dengan rataan umur usia 53 tahun yang memiliki kecenderungan kategori profesi yaitu profesional dengan tipe residen cluster dan rataan belanja adalah 6 juta.

Analisa Hasil Sum of Squares

Info: Sum of Square digunakan untuk melihat perbedaan tiap titik data dengan mean atau centroidnya.

Rumusannya sebagai berikut:

Semakin besar nilai SS menyatakan semakin lebarnya perbedaan antar tiap titik data di dalam cluster tersebut. Hal ini tentu tidak diinginkan dalam analisa cluster. Sehingga, kita harus memilih nilai SSE yang paling kecil.

Misalnya, disini akan dibandingkan antara cluster yang nantinya akan terbentuk 2 cluster atau 5 cluster

Kmeans 2 Cluster

set.seed(100)
kmeans(x=pelanggan[c("Jenis.Kelamin.1","Umur","Profesi.1","Tipe.Residen.1","NilaiBelanjaSetahun")], centers=2, nstart=25)
K-means clustering with 2 clusters of sizes 23, 27

Cluster means:
  Jenis.Kelamin.1     Umur Profesi.1 Tipe.Residen.1 NilaiBelanjaSetahun
1        1.739130 51.17391  3.913043       1.434783            7.551518
2        1.888889 25.85185  3.777778       1.296296            6.659586

Clustering vector:
 [1] 1 2 1 1 1 2 1 1 2 2 1 1 1 1 2 2 2 1 2 2 2 1 2 1 2 1 2 1 1 1 2 1 2 2 1 2 2 1 2 2 2 2 2
[44] 2 2 2 1 1 2 1

Within cluster sum of squares by cluster:
[1] 1492.481 1524.081
 (between_SS / total_SS =  72.6 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"      

Kmeans 5 Cluster

set.seed(100)
kmeans(x=pelanggan[c("Jenis.Kelamin.1","Umur","Profesi.1","Tipe.Residen.1","NilaiBelanjaSetahun")], centers=5, nstart=25)
K-means clustering with 5 clusters of sizes 5, 12, 14, 9, 10

Cluster means:
  Jenis.Kelamin.1     Umur Profesi.1 Tipe.Residen.1 NilaiBelanjaSetahun
1            1.40 61.80000  4.200000       1.400000            8.696132
2            1.75 31.58333  3.916667       1.250000            7.330958
3            2.00 20.07143  3.571429       1.357143            5.901089
4            2.00 42.33333  4.000000       1.555556            8.804791
5            1.70 52.50000  3.800000       1.300000            6.018321

Clustering vector:
 [1] 1 3 5 5 4 3 1 5 2 2 5 5 1 1 3 2 2 1 2 3 4 5 2 4 2 5 2 4 5 4 3 4 3 3 4 2 3 4 3 3 3 2 2
[44] 3 3 3 5 4 2 5

Within cluster sum of squares by cluster:
[1]  58.21123 174.85164 316.73367 171.67372 108.49735
 (between_SS / total_SS =  92.4 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"      

Hasilnya bisa kita simpulkan sebagai berikut:

Cluster Persentase
2 cluster 72.6%
5 cluster 92.4%

Persentase adalah pembagian antara between dengan tots dengan definisi masing-masing yaitu:

  • Betweens didefinisikan sebagai totss-tot.withinss size: Size (jumlah anggota) dari masing-masing K cluster.

  • tot.withinss: Jumlah/Sum of K withinss

\(totss=\frac{1}{N} \sum_{i,i'\epsilon \{1,2,\dots N\}}\sum_{j=1}^{p} (x_{ij}-x_{i'j})^2\)

Sehingga, hasil dengan pembentukkan 5 cluster lebih baik daripada pembentukkan 2 cluster.

Available Components

segmentasi$withinss
[1]  58.21123 174.85164 316.73367 171.67372 108.49735
segmentasi$cluster
 [1] 1 3 5 5 4 3 1 5 2 2 5 5 1 1 3 2 2 1 2 3 4 5 2 4 2 5 2 4 5 4 3 4 3 3 4 2 3 4 3 3 3 2 2
[44] 3 3 3 5 4 2 5
segmentasi$tot.withinss
[1] 829.9676

Simulasi Jumlah Cluster dan SS

Menjalankan fungsi Kmeans dengan beragam n-cluster (n=10)

#Bagian K-Means
field_yang_digunakan = c("Jenis.Kelamin.1","Umur","Profesi.1","Tipe.Residen.1","NilaiBelanjaSetahun")
set.seed(100)
sse <- sapply(1:10,
function(param_k)
{kmeans(pelanggan[field_yang_digunakan], param_k, nstart=25)$tot.withinss})
sse
 [1] 10990.9740  3016.5612  1550.8725  1064.4187   829.9676   625.1462   508.1568
 [8]   431.6977   374.1095   317.9424

Grafik Elbow Effect

library(ggplot2)
#jumlah_cluster_max <- 10
ssdata = data.frame(cluster=c(1:10),sse)
k <- ggplot(ssdata, aes(x=cluster,y=sse)) +
                geom_line(color="red") + geom_point() +
                ylab("Within Cluster Sum of Squares") + xlab("Jumlah Cluster") +
                scale_x_discrete(limits=factor(1:10)) + ggtitle("Grafik Elbow", subtitle = "Menunjukkan perubahan nilai SSE") #geom_text(aes(label=format(round(sse, 2), nsmall = 2)),hjust=-0.2, vjust=-0.5)
ggplotly(k)

Melakukan Segmentasi Customers

  • Cluster 1 Diamond Senior Member karena karakteristik pada kategori ini adalah orang tua yang berusia 61 tahun yang memiliki pembelanjaan sekitar 8.7 juta.

  • Cluster 2 Gold Young Professional karena karakteristik pada kategori ini adalah orang yang berusia 32 tahun yang memiliki pembelanjaan sekitar 7.3 juta.

  • Cluster 3 Silver Youth Gals karena karakteristik pada kategori ini adalah anak muda beusia 20tahun dan keseluruhan adalah wanita dengan profesi kecenderungan adalah pelajar, mahasiswa dan beberapa profesional serta memiliki pembelanjaan sekitar 5,9 juta.

  • Cluster 4 Diamond Profesional karena karakteristik pada kategori ini adalah orang tua yang berusia 42 tahun dan keseluruhan adalah wanita yang memiliki pembelanjaan sekitar 8.8 juta.

  • Cluster 5 Silver Mid Professional karena karakteristik pada kategori ini adalah orang tua yang berusia 53 tahun yang memiliki pembelanjaan sekitar 6 juta.
Segmen.Pelanggan <- data.frame(cluster = c(1:5),Nama.Segmen = c("Diamond Senior Member","Gold Young Professional", "Silver Youth Gals" , "Diamond Professional","Silver Mid Professional"))
Segmen.Pelanggan

Menggabungkan Referensi

#Menggabungkan seluruh aset ke dalam variable Identitas.Cluster
Identitas.Cluster <- list(Profesi=Profesi, Jenis.Kelamin=Jenis.Kelamin, Tipe.Residen=Tipe.Residen, Segmentasi=segmentasi, Segmen.Pelanggan=Segmen.Pelanggan, field_yang_digunakan=field_yang_digunakan)
Identitas.Cluster
$Profesi

$Jenis.Kelamin

$Tipe.Residen

$Segmentasi
K-means clustering with 5 clusters of sizes 5, 12, 14, 9, 10

Cluster means:
  Jenis.Kelamin.1     Umur Profesi.1 Tipe.Residen.1 NilaiBelanjaSetahun
1            1.40 61.80000  4.200000       1.400000            8.696132
2            1.75 31.58333  3.916667       1.250000            7.330958
3            2.00 20.07143  3.571429       1.357143            5.901089
4            2.00 42.33333  4.000000       1.555556            8.804791
5            1.70 52.50000  3.800000       1.300000            6.018321

Clustering vector:
 [1] 1 3 5 5 4 3 1 5 2 2 5 5 1 1 3 2 2 1 2 3 4 5 2 4 2 5 2 4 5 4 3 4 3 3 4 2
[37] 3 4 3 3 3 2 2 3 3 3 5 4 2 5

Within cluster sum of squares by cluster:
[1]  58.21123 174.85164 316.73367 171.67372 108.49735
 (between_SS / total_SS =  92.4 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"    
[5] "tot.withinss" "betweenss"    "size"         "iter"        
[9] "ifault"      

$Segmen.Pelanggan

$field_yang_digunakan
[1] "Jenis.Kelamin.1"     "Umur"                "Profesi.1"          
[4] "Tipe.Residen.1"      "NilaiBelanjaSetahun"

Menyimpan Objek dalam Bentuk File

saveRDS(Identitas.Cluster,"cluster.rds")

Data Baru

databaru <- data.frame(Customer_ID="CUST-100", Nama.Pelanggan="Rudi Wilamar",Umur=20,Jenis.Kelamin="Wanita",Profesi="Pelajar",Tipe.Residen="Cluster",NilaiBelanjaSetahun=3.5)
databaru

Memuat Objek Clustering dari File

Identitas.Cluster <- readRDS(file="cluster.rds")
Identitas.Cluster
$Profesi

$Jenis.Kelamin

$Tipe.Residen

$Segmentasi
K-means clustering with 5 clusters of sizes 5, 12, 14, 9, 10

Cluster means:
  Jenis.Kelamin.1     Umur Profesi.1 Tipe.Residen.1 NilaiBelanjaSetahun
1            1.40 61.80000  4.200000       1.400000            8.696132
2            1.75 31.58333  3.916667       1.250000            7.330958
3            2.00 20.07143  3.571429       1.357143            5.901089
4            2.00 42.33333  4.000000       1.555556            8.804791
5            1.70 52.50000  3.800000       1.300000            6.018321

Clustering vector:
 [1] 1 3 5 5 4 3 1 5 2 2 5 5 1 1 3 2 2 1 2 3 4 5 2 4 2 5 2 4 5 4 3 4 3 3 4 2
[37] 3 4 3 3 3 2 2 3 3 3 5 4 2 5

Within cluster sum of squares by cluster:
[1]  58.21123 174.85164 316.73367 171.67372 108.49735
 (between_SS / total_SS =  92.4 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"    
[5] "tot.withinss" "betweenss"    "size"         "iter"        
[9] "ifault"      

$Segmen.Pelanggan

$field_yang_digunakan
[1] "Jenis.Kelamin.1"     "Umur"                "Profesi.1"          
[4] "Tipe.Residen.1"      "NilaiBelanjaSetahun"

Merge dengan Data Referensi

#Masukkan perintah untuk penggabungan data
databaru <- merge(databaru, Identitas.Cluster$Profesi)
databaru <- merge(databaru, Identitas.Cluster$Jenis.Kelamin)
databaru <- merge(databaru,  Identitas.Cluster$Tipe.Residen)
databaru

Menentukan Cluster

#menentukan data baru di cluster mana
Identitas.Cluster$Segmen.Pelanggan[which.min(sapply( 1:5, function( x ) sum( ( databaru[Identitas.Cluster$field_yang_digunakan] - Identitas.Cluster$Segmentasi$centers[x,])^2 ) )),]

Visualize

set.seed(100)
xe = kmeans(x=pelanggan[c("Jenis.Kelamin.1","Umur","Profesi.1","Tipe.Residen.1","NilaiBelanjaSetahun")], centers=5, nstart=25)

plot(x=pelanggan[c("Jenis.Kelamin.1","Umur","Profesi.1","Tipe.Residen.1","NilaiBelanjaSetahun")], col = xe$cluster)

library(factoextra)
fviz_cluster(xe, data =pelanggan[c("Jenis.Kelamin.1","Umur","Profesi.1","Tipe.Residen.1","NilaiBelanjaSetahun")], label=NA)+theme_bw()

Source:

github

Connect with me:)

LS0tCnRpdGxlOiAiQ3VzdG9tZXIgU2VnbWVudGF0aW9uIgpzdWJ0aXRsZTogJ1N1bWJlciBBY3VhbjogRFFMQUInCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDIKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBubwogICAgICBzbW9vdGhfc2Nyb2xsOiBubwogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAnMicKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KIVtDdXN0b21lciBTZWdtZW50YXRpb24vRnJlZXBpay92ZWN0b3JqdWljZV0oaHR0cHM6Ly9pbWFnZS5mcmVlcGlrLmNvbS9mcmVlLXZlY3Rvci9lY29tbWVyY2UtaW50ZXJuZXQtc2hvcHBpbmctcHJvbW90aW9uLWNhbXBhaWduXzMzNTY1Ny0yOTc3LmpwZykKCgpDdXN0b21lciBTZWdtZW50YXRpb24gYWRhbGFoIHByYWt0aWsgbWVtYmFnaSBwZWxhbmdnYW4gcGVydXNhaGFhbiBrZSBkYWxhbSBrZWxvbXBvay1rZWxvbXBvayB5YW5nIG1lbmNlcm1pbmthbiBrZXNhbWFhbiBkaSBhbnRhcmEgcGVsYW5nZ2FuIGRpIHNldGlhcCBrZWxvbXBvay4KClBlbmdlbG9tcG9ra2FuIEN1c3RvbWVyIFNlZ21lbnRhdGlvbiBpbmkgdW11bW55YSB0ZXJiYWdpIGtlIGRhbGFtIDQga2F0ZWdvcmk6CgoqIGRlbW9ncmFmaXMKCiogcHNpa29ncmFmaXMKCiogcGVyaWxha3UKCiogZ2VvZ3JhZmlzCgpQZW50aW5nbnlhIG1lbGFrdWthbiBjdXN0b21lciBTZWdtZW50YXRpb24gaW5pIGJlcnR1anVhbiB1bnR1ayBtZW11dHVza2FuIGJhZ2FpbWFuYSBiZXJodWJ1bmdhbiBkZW5nYW4gcGVsYW5nZ2FuIGRpIHNldGlhcCBzZWdtZW4gdW50dWsgbWVtYWtzaW1hbGthbiBuaWxhaSBzZXRpYXAgcGVsYW5nZ2FuIGRhbGFtIGJlcmJpc25pcy4KCkJlcmlrdXQgYWRhbGFoIGxhbmdrYWgtbGFuZ2thaCBkYWxhbSBtZWxha3VrYW4gY3VzdG9tZXIgc2VnbWVudGF0aW9uLgoKIyBNZW1iYWNhIGRhdGEgZGVuZ2FuIGZ1bmdzaSByZWFkLmNzdgoKYGBge3J9CnBlbGFuZ2dhbiA8LSByZWFkLmNzdigiaHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL2RxbGFiLWRhdGFzZXQvY3VzdG9tZXJfc2VnbWVudHMudHh0Iiwgc2VwPSJcdCIpCQpwZWxhbmdnYW4KcGVsYW5nZ2FuW2MoIkplbmlzLktlbGFtaW4iLCAiVW11ciIsICJQcm9mZXNpIiwiVGlwZS5SZXNpZGVuIildCmBgYAojIFZlY3RvciB1bnR1ayBNZW55aW1wYW4gTmFtYSBGaWVsZApgYGB7cn0KYSA8LSBjKCJKZW5pcy5LZWxhbWluIiwgIlVtdXIiLCAiUHJvZmVzaSIpCiNUYW1waWxhbiBkYXRhIHBlbGFuZ2dhbiBkZW5nYW4gbmFtYSBrb2xvbSBzZXN1YWkgaXNpIHZlY3RvciBmaWVsZF95YW5nX2RpZ3VuYWthbgpwZWxhbmdnYW5bYV0KYGBgCiMgS29udmVyc2kgRGF0YSBkZW5nYW4gZGF0YS5tYXRyaXgKYGBge3J9CnBlbGFuZ2dhbl9tYXRyaXggPC0gZGF0YS5tYXRyaXgocGVsYW5nZ2FuW2MoIkplbmlzLktlbGFtaW4iLCAiUHJvZmVzaSIsICJUaXBlLlJlc2lkZW4iKV0pCnBlbGFuZ2dhbl9tYXRyaXggCmBgYAojIE1lbmdnYWJ1bmdrYW4gSGFzaWwgS29udmVyc2kKYGBge3J9CnBlbGFuZ2dhbiA8LSBkYXRhLmZyYW1lKHBlbGFuZ2dhbiwgcGVsYW5nZ2FuX21hdHJpeCkKI1RhbXBpbGthbiBrZW1iYWxpIGRhdGEgaGFzaWwgcGVuZ2dhYnVuZ2FuCnBlbGFuZ2dhbgpgYGAKIyBNZW5vcm1hbGlzYXNpa2FuIE5pbGFpIEJlbGFuamEKYGBge3J9CnBlbGFuZ2dhbiROaWxhaUJlbGFuamFTZXRhaHVuIDwtIHBlbGFuZ2dhbiROaWxhaUJlbGFuamFTZXRhaHVuLzEwMDAwMDAKcGVsYW5nZ2FuJE5pbGFpQmVsYW5qYVNldGFodW4KYGBgCiMgTWVtYnVhdCBEYXRhIE1hc3RlciB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30KdW50dWsgbWVsaWhhdCBuaWxhaSBrYXRlZ29yaSBkYXJpIHZhcmlhYmVsIHlhbmcgdGVsYWggZGltb2RpZmlrYXNpCgojIyBQcm9mZXNpIApgYGB7cn0KUHJvZmVzaSA8LSB1bmlxdWUocGVsYW5nZ2FuW2MoIlByb2Zlc2kiLCJQcm9mZXNpLjEiKV0pClByb2Zlc2kKYGBgCgojIyBKZW5pcyBLZWxhbWluCmBgYHtyfQpKZW5pcy5LZWxhbWluIDwtIHVuaXF1ZShwZWxhbmdnYW5bYygiSmVuaXMuS2VsYW1pbiIsIkplbmlzLktlbGFtaW4uMSIpXSkKSmVuaXMuS2VsYW1pbgpgYGAKCiMjIFRpcGUgUmVzaWRlbgpgYGB7cn0KVGlwZS5SZXNpZGVuIDwtIHVuaXF1ZShwZWxhbmdnYW5bYygiVGlwZS5SZXNpZGVuIiwiVGlwZS5SZXNpZGVuLjEiKV0pClRpcGUuUmVzaWRlbgpgYGAKCkRhbGFtIG1lbGFrdWthbiBzZWdtZW50YXNpIGN1c3RvbWVyIGluaSBkaWd1bmFrYW5sYWggcGVuZ2d1bmFhbiBhbmFsaXNpcyBjbHVzdGVyaW5nLCBkaW1hbmEgbWV0b2RlIHlhbmcgYWthbiBkaWd1bmFrYW4geWFpdHUgSy1NZWFucy4gCgpLbWVhbnMgYWRhbGFoIHN1YXR1IG1ldG9kZSB5YW5nIG1lbmNvYmEgdW50dWsgbWVtcGFydGlzaSBkYXRhc2V0IG1lbmphZGkgSyBzdWJrZWxvbXBvayAoY2x1c3Rlcikgbm9uLW92ZXJsYXBwaW5nIHlhbmcgYmVyYmVkYSB5YW5nIHRlbGFoIGRpdGVudHVrYW4gc2ViZWx1bW55YSBkaSBtYW5hIHNldGlhcCB0aXRpayBkYXRhIGhhbnlhIGRpbWlsaWtpIG9sZWggc2F0dSBncnVwLgoKQWRhcHVuIGFsZ29yaXRtYSBLLU1lYW5zIGl0dSBzZW5kaXJpIHNlYmFnYWkgYmVyaWt1dDoKCjEuIFRlbnR1a2FuIGp1bWxhaCBjbHVzdGVyIEsKMi4gSW5pc2lhbGlzYXNpIGNlbnRyb2lkIGRlbmdhbiB0ZXJsZWJpaCBkYWh1bHUgbWVuZ2FjYWsgZGF0YXNldCBkYW4ga2VtdWRpYW4gc2VjYXJhIGFjYWsgbWVtaWxpaCBLIHRpdGlrIGRhdGEgdW50dWsgY2VudHJvaWQgdGFucGEgcGVuZ2dhbnRpYW4uCjMuIExha3VrYW4gaXRlcmFzaSBzYW1wYWkgdGlkYWsgYWRhIHBlcnViYWhhbiBwYWRhIGNlbnRyb2lkIGF0YXUgcGVudWdhc2FuIHRpdGlrIGRhdGEga2UgY2x1c3RlciB0aWRhayBiZXJ1YmFoLgo0LiBIaXR1bmcganVtbGFoIGt1YWRyYXQgamFyYWsgYW50YXJhIHRpdGlrIGRhdGEgZGFuIHNlbXVhIGNlbnRyb2lkLgo1LiBUZXRhcGthbiBzZXRpYXAgdGl0aWsgZGF0YSBrZSBjbHVzdGVyIHRlcmRla2F0IChjZW50cm9pZCkuCjYuIEhpdHVuZyBjZW50cm9pZCB1bnR1ayBjbHVzdGVyIGRlbmdhbiBtZW5nYW1iaWwgcmF0YS1yYXRhIGRhcmkgc2VtdWEgdGl0aWsgZGF0YSB5YW5nIGRpbWlsaWtpIHNldGlhcCBjbHVzdGVyLgoKIyBGdW5nc2kga21lYW5zCmBgYHtyfQojQmFnaWFuIEstTWVhbnMKc2V0LnNlZWQoMTAwKQojZnVuZ3NpIGttZWFucyB1bnR1ayBtZW1iZW50dWsgNSBjbHVzdGVyIGRlbmdhbiAyNSBza2VuYXJpbyByYW5kb20gZGFuIHNpbXBhbiBrZSBkYWxhbSB2YXJpYWJsZSBzZWdtZW50YXNpCnNlZ21lbnRhc2kgPC1rbWVhbnMoeD1wZWxhbmdnYW5bYygiSmVuaXMuS2VsYW1pbi4xIiwiVW11ciIsIlByb2Zlc2kuMSIsIlRpcGUuUmVzaWRlbi4xIiwiTmlsYWlCZWxhbmphU2V0YWh1biIpXSwgY2VudGVycz01LCBuc3RhcnQ9MjUpCiN0YW1waWxrYW4gaGFzaWwgay1tZWFucwpzZWdtZW50YXNpCmBgYApEaXNpbmkgZGFwYXQgdGVybGloYXQgZGFyaSBoYXNpbCB5YW5nIGRpa2VsdWFya2FuIGJhaHdhIGRhdGEgcGVsYW5nZ2FuIHRlbGFoIHRlcmJhZ2kga2UgZGFsYW0gNSBjbHVzdGVyLgoKIyBBbmFsaXNhIEhhc2lsIENsdXN0ZXJpbmcgVmVjdG9yCmBgYHtyfQojUGVuZ2dhYnVuZ2FuIGhhc2lsIGNsdXN0ZXIKCnNlZ21lbnRhc2kkY2x1c3RlcgoKcGVsYW5nZ2FuJGNsdXN0ZXIgPC0gc2VnbWVudGFzaSRjbHVzdGVyCgpzdHIocGVsYW5nZ2FuKQpwZWxhbmdnYW4KYGBgCiMjIEFuYWxpc2EgSGFzaWwgQ2x1c3RlciBTaXplIHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQojIyMgQ2x1c3RlciAxCmBgYHtyfQpwZWxhbmdnYW5bd2hpY2gocGVsYW5nZ2FuJGNsdXN0ZXIgPT0gMSksXQpgYGAKQmFueWFrbnlhIGNsdXN0ZXIgeWFuZyB0ZXJiZW50dWsgCmBgYHtyfQpsZW5ndGgod2hpY2gocGVsYW5nZ2FuJGNsdXN0ZXIgPT0gMSkpCmBgYAoKCiMjIyBDbHVzdGVyIDIKYGBge3J9CnBlbGFuZ2dhblt3aGljaChwZWxhbmdnYW4kY2x1c3RlciA9PSAyKSxdIApgYGAKQmFueWFrbnlhIGNsdXN0ZXIgeWFuZyB0ZXJiZW50dWsgCmBgYHtyfQpsZW5ndGgod2hpY2gocGVsYW5nZ2FuJGNsdXN0ZXIgPT0gMikpCmBgYAoKCiMjIyBDbHVzdGVyIDMKYGBge3J9CnBlbGFuZ2dhblt3aGljaChwZWxhbmdnYW4kY2x1c3RlciA9PSAzKSxdCmBgYApCYW55YWtueWEgY2x1c3RlciB5YW5nIHRlcmJlbnR1ayAKYGBge3J9Cmxlbmd0aCh3aGljaChwZWxhbmdnYW4kY2x1c3RlciA9PSAzKSkKYGBgCgoKIyMjIENsdXN0ZXIgNApgYGB7cn0KcGVsYW5nZ2FuW3doaWNoKHBlbGFuZ2dhbiRjbHVzdGVyID09IDQpLF0KYGBgCkJhbnlha255YSBjbHVzdGVyIHlhbmcgdGVyYmVudHVrIApgYGB7cn0KbGVuZ3RoKHdoaWNoKHBlbGFuZ2dhbiRjbHVzdGVyID09IDQpKQpgYGAKCgojIyMgQ2x1c3RlciA1CmBgYHtyfQpwZWxhbmdnYW5bd2hpY2gocGVsYW5nZ2FuJGNsdXN0ZXIgPT0gNSksXQpgYGAKQmFueWFrbnlhIGNsdXN0ZXIgeWFuZyB0ZXJiZW50dWsgCmBgYHtyfQpsZW5ndGgod2hpY2gocGVsYW5nZ2FuJGNsdXN0ZXIgPT0gNSkpCmBgYAoKCgoKIyBBbmFsaXNhIEhhc2lsIENsdXN0ZXIgTWVhbnMKYGBge3J9CiNBbmFsaXNhIGhhc2lsCiNNZWxpaGF0IGNsdXN0ZXIgbWVhbnMgZGFyaSBvYmplayAKc2VnbWVudGFzaSRjZW50ZXJzCmBgYApQYWRhIGFuYWxpc2EgaGFzaWwgY2x1c3RlciBtZWFucyBpbmkgZGFwYXQgdGVybGloYXQgYmFod2EgaGFzaWxueWEgYWRhbGFoIHNlYmFyYW4ga29udGludS4gUGFkYWhhbCBkaSBhd2FsIHNlYmVsdW1ueWEgZGF0YSBiZXJkaXN0cmlidXNpIGRpc2tyaXQuIAoKTWVza2kgZGVtaWtpYW4ga2l0YSBiaXNhIG1lbnlpbXB1bGthbiBoYXNpbCBkaSBhdGFzIGRlbmdhbiBwZW5kZWthdGFuIHBlbWJ1bGF0YW4sIHNlaGluZ2dhIGhhc2lsIHlhbmcgZGlwZXJvbGVoIGRhcGF0IGRpamVsYXNrYW4uIAoKUGFkYSBhbmFsaXNpcyBpbmkgZGlwZXJvbGVoOgoKKiBjbHVzdGVyIDEsIHBhZGEgamVuaXMga2VsYW1pbiBtZW1pbGlraSBrZWNlbmRlcnVuZ2FuIHlhaXR1ICpsYWtpLWxha2kqIGRlbmdhbiByYXRhYW4gdW11ciB1c2lhICo2MiB0YWh1biogeWFuZyBtZW1pbGlraSBrYXRlZ29yaSBwcm9mZXNpIHlhaXR1ICpwcm9mZXNpb25hbCogZGVuZ2FuIHRpcGUgcmVzaWRlbiAqY2x1c3RlciogZGFuIHJhdGFhbiBiZWxhbmphIGFkYWxhaCAqOC43IGp1dGEqLgoKKiBDbHVzdGVyIDIsIHBhZGEgamVuaXMga2VsYW1pbiBtZW1pbGlraSBrZWNlbmRlcnVuZ2FuIHlhaXR1ICpsYWtpLWxha2kqIGRlbmdhbiByYXRhYW4gdW11ciB1c2lhICozMiB0YWh1biogeWFuZyBtZW1pbGlraSBrZWNlbmRlcnVuZ2FuIGthdGVnb3JpIHByb2Zlc2kgeWFpdHUgKnByb2Zlc2lvbmFsKiBkZW5nYW4gdGlwZSByZXNpZGVuICpjbHVzdGVyKiBkYW4gcmF0YWFuIGJlbGFuamEgYWRhbGFoICo3LjMganV0YSouCgoqIENsdXN0ZXIgMywgcGFkYSBqZW5pcyBrZWxhbWluIHlhaXR1ICpQZXJlbXB1YW4qIGRlbmdhbiByYXRhYW4gdW11ciB1c2lhICoyMCB0YWh1biogeWFuZyBtZW1pbGlraSBrZWNlbmRlcnVuZ2FuIGthdGVnb3JpIHByb2Zlc2kgeWFpdHUgKnByb2Zlc2lvbmFsKiBkZW5nYW4gdGlwZSByZXNpZGVuICpjbHVzdGVyKiBkYW4gcmF0YWFuIGJlbGFuamEgYWRhbGFoICo1LjkganV0YSouCgoqIENsdXN0ZXIgNCwgcGFkYSBqZW5pcyBrZWxhbWluIHNlbXVhbnlhIHlhaXR1ICpQZXJlbXB1YW4qIGRlbmdhbiByYXRhYW4gdW11ciB1c2lhICo0MiB0YWh1biogeWFuZyBiZXJwcm9mZXNpIHNlYmFnYWkgKnByb2Zlc2lvbmFsKiBkZW5nYW4gdGlwZSByZXNpZGVuICpzZWN0b3JjKiBkYW4gcmF0YWFuIGJlbGFuamEgYWRhbGFoICo4LjgganV0YSouCgoqIENsdXN0ZXIgNSwgcGFkYSBqZW5pcyBrZWxhbWluIG1lbWlsaWtpIGtlY2VuZGVydW5nYW4geWFpdHUgKnBlcmVtcHVhbiogZGVuZ2FuIHJhdGFhbiB1bXVyIHVzaWEgKjUzIHRhaHVuKiB5YW5nIG1lbWlsaWtpIGtlY2VuZGVydW5nYW4ga2F0ZWdvcmkgcHJvZmVzaSB5YWl0dSAqcHJvZmVzaW9uYWwqIGRlbmdhbiB0aXBlIHJlc2lkZW4gKmNsdXN0ZXIqIGRhbiByYXRhYW4gYmVsYW5qYSBhZGFsYWggKjYganV0YSouCgoKIyBBbmFsaXNhIEhhc2lsIFN1bSBvZiBTcXVhcmVzIHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQpJbmZvOiBTdW0gb2YgU3F1YXJlIGRpZ3VuYWthbiB1bnR1ayBtZWxpaGF0IHBlcmJlZGFhbiB0aWFwIHRpdGlrIGRhdGEgZGVuZ2FuIG1lYW4gYXRhdSBjZW50cm9pZG55YS4gCgpSdW11c2FubnlhIHNlYmFnYWkgYmVyaWt1dDoKCiFbXShodHRwczovL2Jsb2ctYzdmZi5reGNkbi5jb20vYmxvZy93cC1jb250ZW50L3VwbG9hZHMvMjAxNy8wMS9TZWxlY3Rpb25fMDAzLTEucG5nKQoKU2VtYWtpbiBiZXNhciBuaWxhaSBTUyBtZW55YXRha2FuIHNlbWFraW4gbGViYXJueWEgcGVyYmVkYWFuIGFudGFyIHRpYXAgdGl0aWsgZGF0YSBkaSBkYWxhbSBjbHVzdGVyIHRlcnNlYnV0LiBIYWwgaW5pIHRlbnR1IHRpZGFrIGRpaW5naW5rYW4gZGFsYW0gYW5hbGlzYSBjbHVzdGVyLiBTZWhpbmdnYSwga2l0YSBoYXJ1cyBtZW1pbGloIG5pbGFpIFNTRSB5YW5nIHBhbGluZyBrZWNpbC4gCgoKTWlzYWxueWEsIGRpc2luaSBha2FuIGRpYmFuZGluZ2thbiBhbnRhcmEgY2x1c3RlciB5YW5nIG5hbnRpbnlhIGFrYW4gdGVyYmVudHVrIDIgY2x1c3RlciBhdGF1IDUgY2x1c3RlcgoKIyMgS21lYW5zIDIgQ2x1c3RlciAKYGBge3J9CnNldC5zZWVkKDEwMCkKa21lYW5zKHg9cGVsYW5nZ2FuW2MoIkplbmlzLktlbGFtaW4uMSIsIlVtdXIiLCJQcm9mZXNpLjEiLCJUaXBlLlJlc2lkZW4uMSIsIk5pbGFpQmVsYW5qYVNldGFodW4iKV0sIGNlbnRlcnM9MiwgbnN0YXJ0PTI1KQpgYGAKCiMjIEttZWFucyA1IENsdXN0ZXIgCmBgYHtyfQpzZXQuc2VlZCgxMDApCmttZWFucyh4PXBlbGFuZ2dhbltjKCJKZW5pcy5LZWxhbWluLjEiLCJVbXVyIiwiUHJvZmVzaS4xIiwiVGlwZS5SZXNpZGVuLjEiLCJOaWxhaUJlbGFuamFTZXRhaHVuIildLCBjZW50ZXJzPTUsIG5zdGFydD0yNSkKCmBgYAojIApIYXNpbG55YSBiaXNhIGtpdGEgc2ltcHVsa2FuIHNlYmFnYWkgYmVyaWt1dDoKCnwgQ2x1c3RlciAgfCBQZXJzZW50YXNlIHwKfC0tLS0tLS0tLS18LS0tLS0tLS0tLS0tfAp8MiBjbHVzdGVyIHw3Mi42JSAgICAgICB8Cnw1IGNsdXN0ZXIgfDkyLjQlICAgICAgIHwKClBlcnNlbnRhc2UgYWRhbGFoIHBlbWJhZ2lhbiBhbnRhcmEgYmV0d2VlbiBkZW5nYW4gdG90cyBkZW5nYW4gZGVmaW5pc2kgbWFzaW5nLW1hc2luZyB5YWl0dToKCiogQmV0d2VlbnMgZGlkZWZpbmlzaWthbiBzZWJhZ2FpIHRvdHNzLXRvdC53aXRoaW5zcyBzaXplOiBTaXplIChqdW1sYWggYW5nZ290YSkgZGFyaSBtYXNpbmctbWFzaW5nIEsgY2x1c3Rlci4KCiogdG90LndpdGhpbnNzOiBKdW1sYWgvU3VtIG9mIEsgd2l0aGluc3MKCiR0b3Rzcz1cZnJhY3sxfXtOfSBcc3VtX3tpLGknXGVwc2lsb24gXHsxLDIsXGRvdHMgTlx9fVxzdW1fe2o9MX1ee3B9ICh4X3tpan0teF97aSdqfSleMiQKClNlaGluZ2dhLCBoYXNpbCBkZW5nYW4gcGVtYmVudHVra2FuIDUgY2x1c3RlciBsZWJpaCBiYWlrIGRhcmlwYWRhIHBlbWJlbnR1a2thbiAyIGNsdXN0ZXIuIAoKIyBBdmFpbGFibGUgQ29tcG9uZW50cwpgYGB7cn0Kc2VnbWVudGFzaSR3aXRoaW5zcwpzZWdtZW50YXNpJGNsdXN0ZXIKc2VnbWVudGFzaSR0b3Qud2l0aGluc3MKYGBgCiMgU2ltdWxhc2kgSnVtbGFoIENsdXN0ZXIgZGFuIFNTCk1lbmphbGFua2FuIGZ1bmdzaSBLbWVhbnMgZGVuZ2FuIGJlcmFnYW0gbi1jbHVzdGVyIChuPTEwKQoKYGBge3J9CiNCYWdpYW4gSy1NZWFucwpmaWVsZF95YW5nX2RpZ3VuYWthbiA9IGMoIkplbmlzLktlbGFtaW4uMSIsIlVtdXIiLCJQcm9mZXNpLjEiLCJUaXBlLlJlc2lkZW4uMSIsIk5pbGFpQmVsYW5qYVNldGFodW4iKQpzZXQuc2VlZCgxMDApCnNzZSA8LSBzYXBwbHkoMToxMCwKZnVuY3Rpb24ocGFyYW1faykKe2ttZWFucyhwZWxhbmdnYW5bZmllbGRfeWFuZ19kaWd1bmFrYW5dLCBwYXJhbV9rLCBuc3RhcnQ9MjUpJHRvdC53aXRoaW5zc30pCnNzZQpgYGAKIyBHcmFmaWsgRWxib3cgRWZmZWN0CgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpgYGAKCmBgYHtyfQojanVtbGFoX2NsdXN0ZXJfbWF4IDwtIDEwCnNzZGF0YSA9IGRhdGEuZnJhbWUoY2x1c3Rlcj1jKDE6MTApLHNzZSkKayA8LSBnZ3Bsb3Qoc3NkYXRhLCBhZXMoeD1jbHVzdGVyLHk9c3NlKSkgKwogICAgICAgICAgICAgICAgZ2VvbV9saW5lKGNvbG9yPSJyZWQiKSArIGdlb21fcG9pbnQoKSArCiAgICAgICAgICAgICAgICB5bGFiKCJXaXRoaW4gQ2x1c3RlciBTdW0gb2YgU3F1YXJlcyIpICsgeGxhYigiSnVtbGFoIENsdXN0ZXIiKSArCiAgICAgICAgICAgICAgICBzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cz1mYWN0b3IoMToxMCkpICsgZ2d0aXRsZSgiR3JhZmlrIEVsYm93Iiwgc3VidGl0bGUgPSAiTWVudW5qdWtrYW4gcGVydWJhaGFuIG5pbGFpIFNTRSIpICNnZW9tX3RleHQoYWVzKGxhYmVsPWZvcm1hdChyb3VuZChzc2UsIDIpLCBuc21hbGwgPSAyKSksaGp1c3Q9LTAuMiwgdmp1c3Q9LTAuNSkKZ2dwbG90bHkoaykKYGBgCiMgTWVsYWt1a2FuIFNlZ21lbnRhc2kgQ3VzdG9tZXJzCgohW10oaHR0cHM6Ly9pbWFnZS5mcmVlcGlrLmNvbS9mcmVlLXZlY3Rvci9jYXJ0b29uLWRpYS1kb3MtYXZvcy1pbGx1c3RyYXRpb25fNTI2ODMtNjQ2NTcuanBnKQoKKiBDbHVzdGVyIDEgKkRpYW1vbmQgU2VuaW9yIE1lbWJlcioga2FyZW5hIGthcmFrdGVyaXN0aWsgcGFkYSBrYXRlZ29yaSBpbmkgYWRhbGFoIG9yYW5nIHR1YSB5YW5nIGJlcnVzaWEgNjEgdGFodW4geWFuZyBtZW1pbGlraSBwZW1iZWxhbmphYW4gc2VraXRhciA4LjcganV0YS4KCgohW10oaHR0cHM6Ly9pbWFnZS5mcmVlcGlrLmNvbS9mcmVlLXZlY3Rvci9wZW9wbGUtd2VhcmluZy1hdXR1bW4tY2xvdGhlc18yMy0yMTQ4MzIyOTA4LmpwZykKCiogQ2x1c3RlciAyICpHb2xkIFlvdW5nIFByb2Zlc3Npb25hbCoga2FyZW5hIGthcmFrdGVyaXN0aWsgcGFkYSBrYXRlZ29yaSBpbmkgYWRhbGFoIG9yYW5nIHlhbmcgYmVydXNpYSAzMiB0YWh1biB5YW5nIG1lbWlsaWtpIHBlbWJlbGFuamFhbiBzZWtpdGFyIDcuMyBqdXRhLiAKCiFbXShodHRwczovL2ltYWdlLmZyZWVwaWsuY29tL2ZyZWUtdmVjdG9yL2JhY2hlbG9yZXR0ZS1wYXJ0eS1jb25jZXB0LWlsbHVzdHJhdGlvbl8xMTQzNjAtNjg1NS5qcGcpCgoqIENsdXN0ZXIgMyAqU2lsdmVyIFlvdXRoIEdhbHMqIGthcmVuYSBrYXJha3RlcmlzdGlrIHBhZGEga2F0ZWdvcmkgaW5pIGFkYWxhaCBhbmFrIG11ZGEgYmV1c2lhIDIwdGFodW4gZGFuIGtlc2VsdXJ1aGFuIGFkYWxhaCB3YW5pdGEgZGVuZ2FuIHByb2Zlc2kga2VjZW5kZXJ1bmdhbiBhZGFsYWggcGVsYWphciwgbWFoYXNpc3dhIGRhbiBiZWJlcmFwYSBwcm9mZXNpb25hbCBzZXJ0YSBtZW1pbGlraSBwZW1iZWxhbmphYW4gc2VraXRhciA1LDkganV0YS4gCgohW10oaHR0cHM6Ly9pbWFnZS5mcmVlcGlrLmNvbS9mcmVlLXZlY3Rvci9lbGRlcmx5LXBlb3BsZS12aWRlby1jb21tdW5pY2F0aW9uLWNhcnRvb24tY29tcG9zaXRpb24td2l0aC1vbGQtd29tYW4tY2hhdHRpbmctd2l0aC1jaGlsZHJlbi11c2luZy1sYXB0b3BfMTI4NC01NDU2MS5qcGcpCgoqIENsdXN0ZXIgNCAqRGlhbW9uZCBQcm9mZXNpb25hbCoga2FyZW5hIGthcmFrdGVyaXN0aWsgcGFkYSBrYXRlZ29yaSBpbmkgYWRhbGFoIG9yYW5nIHR1YSB5YW5nIGJlcnVzaWEgNDIgdGFodW4gZGFuIGtlc2VsdXJ1aGFuIGFkYWxhaCB3YW5pdGEgeWFuZyBtZW1pbGlraSBwZW1iZWxhbmphYW4gc2VraXRhciA4LjgganV0YS4gCgoKIVtdKGh0dHBzOi8vaW1hZ2UuZnJlZXBpay5jb20vZnJlZS12ZWN0b3IvbWFuLXNvY2lhbC13b3JrZXItaGVscGluZy1lbGRlci1ncmV5LWhhaXJlZC13b21hbl8zNDQ2LTM3MS5qcGcpCgoqIENsdXN0ZXIgNSAqU2lsdmVyIE1pZCBQcm9mZXNzaW9uYWwqIGthcmVuYSBrYXJha3RlcmlzdGlrIHBhZGEga2F0ZWdvcmkgaW5pIGFkYWxhaCBvcmFuZyB0dWEgeWFuZyBiZXJ1c2lhIDUzIHRhaHVuIHlhbmcgbWVtaWxpa2kgcGVtYmVsYW5qYWFuIHNla2l0YXIgNiBqdXRhLiAKCmBgYHtyfQpTZWdtZW4uUGVsYW5nZ2FuIDwtIGRhdGEuZnJhbWUoY2x1c3RlciA9IGMoMTo1KSxOYW1hLlNlZ21lbiA9IGMoIkRpYW1vbmQgU2VuaW9yIE1lbWJlciIsIkdvbGQgWW91bmcgUHJvZmVzc2lvbmFsIiwgIlNpbHZlciBZb3V0aCBHYWxzIiAsICJEaWFtb25kIFByb2Zlc3Npb25hbCIsIlNpbHZlciBNaWQgUHJvZmVzc2lvbmFsIikpClNlZ21lbi5QZWxhbmdnYW4KYGBgCiMgTWVuZ2dhYnVuZ2thbiBSZWZlcmVuc2kKYGBge3J9CiNNZW5nZ2FidW5na2FuIHNlbHVydWggYXNldCBrZSBkYWxhbSB2YXJpYWJsZSBJZGVudGl0YXMuQ2x1c3RlcgpJZGVudGl0YXMuQ2x1c3RlciA8LSBsaXN0KFByb2Zlc2k9UHJvZmVzaSwgSmVuaXMuS2VsYW1pbj1KZW5pcy5LZWxhbWluLCBUaXBlLlJlc2lkZW49VGlwZS5SZXNpZGVuLCBTZWdtZW50YXNpPXNlZ21lbnRhc2ksIFNlZ21lbi5QZWxhbmdnYW49U2VnbWVuLlBlbGFuZ2dhbiwgZmllbGRfeWFuZ19kaWd1bmFrYW49ZmllbGRfeWFuZ19kaWd1bmFrYW4pCklkZW50aXRhcy5DbHVzdGVyCmBgYAojIE1lbnlpbXBhbiBPYmplayBkYWxhbSBCZW50dWsgRmlsZQpgYGB7cn0Kc2F2ZVJEUyhJZGVudGl0YXMuQ2x1c3RlciwiY2x1c3Rlci5yZHMiKQpgYGAKCiMgRGF0YSBCYXJ1CmBgYHtyfQpkYXRhYmFydSA8LSBkYXRhLmZyYW1lKEN1c3RvbWVyX0lEPSJDVVNULTEwMCIsIE5hbWEuUGVsYW5nZ2FuPSJSdWRpIFdpbGFtYXIiLFVtdXI9MjAsSmVuaXMuS2VsYW1pbj0iV2FuaXRhIixQcm9mZXNpPSJQZWxhamFyIixUaXBlLlJlc2lkZW49IkNsdXN0ZXIiLE5pbGFpQmVsYW5qYVNldGFodW49My41KQpkYXRhYmFydQpgYGAKIyBNZW11YXQgT2JqZWsgQ2x1c3RlcmluZyBkYXJpIEZpbGUKYGBge3J9CklkZW50aXRhcy5DbHVzdGVyIDwtIHJlYWRSRFMoZmlsZT0iY2x1c3Rlci5yZHMiKQpJZGVudGl0YXMuQ2x1c3RlcgpgYGAKIyBNZXJnZSBkZW5nYW4gRGF0YSBSZWZlcmVuc2kKYGBge3J9CiNNYXN1a2thbiBwZXJpbnRhaCB1bnR1ayBwZW5nZ2FidW5nYW4gZGF0YQpkYXRhYmFydSA8LSBtZXJnZShkYXRhYmFydSwgSWRlbnRpdGFzLkNsdXN0ZXIkUHJvZmVzaSkKZGF0YWJhcnUgPC0gbWVyZ2UoZGF0YWJhcnUsIElkZW50aXRhcy5DbHVzdGVyJEplbmlzLktlbGFtaW4pCmRhdGFiYXJ1IDwtIG1lcmdlKGRhdGFiYXJ1LCAgSWRlbnRpdGFzLkNsdXN0ZXIkVGlwZS5SZXNpZGVuKQpkYXRhYmFydQpgYGAKIyBNZW5lbnR1a2FuIENsdXN0ZXIKYGBge3J9CiNtZW5lbnR1a2FuIGRhdGEgYmFydSBkaSBjbHVzdGVyIG1hbmEKSWRlbnRpdGFzLkNsdXN0ZXIkU2VnbWVuLlBlbGFuZ2dhblt3aGljaC5taW4oc2FwcGx5KCAxOjUsIGZ1bmN0aW9uKCB4ICkgc3VtKCAoIGRhdGFiYXJ1W0lkZW50aXRhcy5DbHVzdGVyJGZpZWxkX3lhbmdfZGlndW5ha2FuXSAtIElkZW50aXRhcy5DbHVzdGVyJFNlZ21lbnRhc2kkY2VudGVyc1t4LF0pXjIgKSApKSxdCmBgYAojIFZpc3VhbGl6ZQoKYGBge3J9CnNldC5zZWVkKDEwMCkKeGUgPSBrbWVhbnMoeD1wZWxhbmdnYW5bYygiSmVuaXMuS2VsYW1pbi4xIiwiVW11ciIsIlByb2Zlc2kuMSIsIlRpcGUuUmVzaWRlbi4xIiwiTmlsYWlCZWxhbmphU2V0YWh1biIpXSwgY2VudGVycz01LCBuc3RhcnQ9MjUpCgpwbG90KHg9cGVsYW5nZ2FuW2MoIkplbmlzLktlbGFtaW4uMSIsIlVtdXIiLCJQcm9mZXNpLjEiLCJUaXBlLlJlc2lkZW4uMSIsIk5pbGFpQmVsYW5qYVNldGFodW4iKV0sIGNvbCA9IHhlJGNsdXN0ZXIpCgpgYGAKCmBgYHtyfQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmZ2aXpfY2x1c3Rlcih4ZSwgZGF0YSA9cGVsYW5nZ2FuW2MoIkplbmlzLktlbGFtaW4uMSIsIlVtdXIiLCJQcm9mZXNpLjEiLCJUaXBlLlJlc2lkZW4uMSIsIk5pbGFpQmVsYW5qYVNldGFodW4iKV0sIGxhYmVsPU5BKSt0aGVtZV9idygpCmBgYAoKU291cmNlOgoKKiBodHRwczovL3d3dy5vcHRpbW92ZS5jb20vcmVzb3VyY2VzL2xlYXJuaW5nLWNlbnRlci9jdXN0b21lci1zZWdtZW50YXRpb24jOn46dGV4dD1DdXN0b21lciUyMHNlZ21lbnRhdGlvbiUyMGlzJTIwdGhlJTIwcHJhY3RpY2UsZWFjaCUyMGN1c3RvbWVyJTIwdG8lMjB0aGUlMjBidXNpbmVzcy4KCiogaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2stbWVhbnMtY2x1c3RlcmluZy1hbGdvcml0aG0tYXBwbGljYXRpb25zLWV2YWx1YXRpb24tbWV0aG9kcy1hbmQtZHJhd2JhY2tzLWFhMDNlNjQ0YjQ4YQoKClsyXTogaHR0cHM6Ly93d3cubGlua2VkaW4uY29tL2luL2t1cm5pYXJhaG1pLwpbIVtnaXRodWJdKGh0dHBzOi8vY2xvdWQuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Fzc2V0cy8xNzAxNjI5Ny8xODgzOTg0OC8wZmM3ZTc0ZS04M2QyLTExZTYtOGM2YS0yNzdmYzlkNmUwNjcucG5nKV1bMl0KCkNvbm5lY3Qgd2l0aCBtZTopCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg==