library(readxl)
library(dplyr)
library(cluster)
library(mclust)
library(factoextra)
library(ggplot2)
library(reshape2)
Pendahuluan
Clustering merupakan salah satu metode unsupervised learning yang
digunakan untuk mengelompokkan data berdasarkan kemiripan karakteristik.
Pada analisis ini digunakan metode K-Means Clustering pada dataset User
Knowledge Modeling dari UCI Machine Learning Repository.
Tujuan analisis ini adalah untuk mengevaluasi kualitas cluster
menggunakan beberapa metode cluster validation, yaitu internal
validation, external validation, dan relative validation.
Dataset User Knowledge Modeling
Dataset yang digunakan ini dari UCI Machine Learning Repository.
Dataset digunakan untuk menganalisis tingkat pengetahuan pengguna
berdasarkan aktivitas belajar.
Variabel dalam dataset ini dapat dijelaskan sebagai berikut:
1. STG (Study Time Goal) = Waktu belajar
2. SCG (Study Count Goal) = Jumlah pengulangan materi
3. STR (Study Time Real) = Waktu belajar aktual
4. LPR (Learning Process Rate) = Tingkat pemahaman selama
proses belajar
5. PEG (Performance Evaluation Grade) = Hasil evaluasi
belajar
6. UNS (User Knowledge Level) = Tingkat pengetahuan pengguna
(Target)
ukm <- read_excel("User Knowledge Modeling.xlsx")
head(ukm)
str(ukm)
## tibble [258 × 6] (S3: tbl_df/tbl/data.frame)
## $ STG: num [1:258] 0 0.08 0.06 0.1 0.08 0.09 0.1 0.15 0.2 0 ...
## $ SCG: num [1:258] 0 0.08 0.06 0.1 0.08 0.15 0.1 0.02 0.14 0 ...
## $ STR: num [1:258] 0 0.1 0.05 0.15 0.08 0.4 0.43 0.34 0.35 0.5 ...
## $ LPR: num [1:258] 0 0.24 0.25 0.65 0.98 0.1 0.29 0.4 0.72 0.2 ...
## $ PEG: num [1:258] 0 0.9 0.33 0.3 0.24 0.66 0.56 0.01 0.25 0.85 ...
## $ UNS: chr [1:258] "very_low" "High" "Low" "Middle" ...
Preprocessing Data
Pada tahap preprocessing dilakukan normalisasi pada seluruh variabel
numerik yaitu STG, SCG, STR, LPR, dan PEG menggunakan fungsi scale().
Normalisasi diperlukan karena metode K-Means sensitif terhadap perbedaan
skala data. Dengan normalisasi, seluruh variabel memiliki kontribusi
yang seimbang dalam proses pembentukan cluster.
# Normalisasi
numeric_var <- ukm %>%
select("STG", "SCG", "STR", "LPR", "PEG")
numeric_scale <- scale(numeric_var)
Selain itu, variabel UNS diubah menjadi tipe faktor agar dapat
digunakan dalam proses validasi eksternal menggunakan Adjusted Rand
Index (ARI).
# Type Casting
ukm$UNS <- factor(ukm$UNS, levels = c("very_low", "Low", "Middle", "High"))
Hasil pengecekan missing value menunjukkan bahwa dataset tidak
memiliki data yang hilang, sehingga data dapat langsung digunakan untuk
proses clustering.
# Cek Missing Value
sum(is.na(ukm))
## [1] 0
Clustering K - Means
Metode K-Means dilakukan dengan jumlah cluster sebanyak:
\[k = 4\]
set.seed(123)
k <- 4
kmeans_result <- kmeans(numeric_scale, centers = k)
ukm$Cluster <- kmeans_result$cluster
table(ukm$Cluster)
##
## 1 2 3 4
## 60 67 75 56
Hasil clustering menunjukkan bahwa data berhasil dibagi menjadi empat
kelompok dengan jumlah anggota sebagai berikut:
- Cluster 1 = 60 data
- Cluster 2 = 67 data
- Cluster 3 = 75 data
- Cluster 4 = 56 data
Distribusi jumlah anggota cluster terlihat cukup seimbang sehingga
tidak terdapat cluster yang terlalu kecil maupun terlalu dominan.
Cluster Validation
Internal Validation
sil <- silhouette(ukm$Cluster, dist(numeric_scale))
sil_score <- mean(sil[,3])
cat("Avarage Silhouette Score : ", sil_score)
## Avarage Silhouette Score : 0.1813992
Hasil internal validation menggunakan silhouette score menghasilkan
nilai sebesar: \(0.1813992\).
Nilai silhouette score berada pada rentang −1 hingga 1. Semakin
mendekati nilai 1, maka kualitas cluster semakin baik karena anggota
dalam cluster memiliki kemiripan tinggi dan berbeda jauh dengan cluster
lain.
Nilai silhouette sebesar 0.181 menunjukkan bahwa kualitas pemisahan
cluster masih tergolong rendah. Hal ini mengindikasikan bahwa beberapa
data masih memiliki kemiripan dengan cluster lain sehingga batas antar
cluster belum terbentuk secara jelas.
Eksternal Validation
ari_score <- adjustedRandIndex(ukm$Cluster, ukm$UNS)
cat("Adjusted Rand Index :", ari_score)
## Adjusted Rand Index : 0.125298
Hasil external validation menggunakan Adjusted Rand Index
menghasilkan nilai sebesar: \(0.125298\).
Nilai ARI digunakan untuk mengukur kesesuaian hasil clustering dengan
label asli UNS. Nilai yang mendekati 1 menunjukkan kesesuaian yang
sangat baik.
Nilai ARI sebesar 0.125 menunjukkan bahwa hasil cluster belum
memiliki kesesuaian yang tinggi terhadap kategori pengetahuan asli
pengguna. Hal ini menunjukkan bahwa metode K-Means masih belum mampu
memisahkan tingkat pengetahuan pengguna secara optimal berdasarkan label
asli dataset.
Relative Validation
set.seed(123)
k_range <- 1:10
wss <- numeric(length(k_range))
for (i in k_range) {
km <- kmeans(numeric_scale, centers = i, nstart = 25)
wss[i] <- km$tot.withinss
}
wss_result <- data.frame(
k = k_range,
WSS = wss
)
wss_result
# Visualisasi Elbow
fviz_nbclust(numeric_scale, kmeans, method = "wss") +
labs(title = "Optimal Number of Clusters - Elbow Method") +
theme_minimal()

Relative validation dilakukan menggunakan metode elbow berdasarkan
nilai Within Sum of Squares (WSS). Hasil menunjukkan bahwa nilai WSS
terus menurun seiring bertambahnya jumlah cluster.
Namun, penurunan mulai melambat setelah: \(k = 4\).
Hal ini menunjukkan bahwa penggunaan lebih dari empat cluster tidak
memberikan penurunan variasi within-cluster yang terlalu signifikan.
Oleh karena itu, jumlah cluster sebanyak empat dianggap cukup optimal
berdasarkan metode elbow.
Perbandingan Hasil
# Perbandingan nilai k
k_values <- 2:10
silhouette_values <- numeric(length(k_values))
ari_values <- numeric(length(k_values))
wss_values <- numeric(length(k_values))
set.seed(123)
for (i in seq_along(k_values)) {
km <- kmeans(
numeric_scale,
centers = k_values[i],
nstart = 25
)
# Internal Validation
sil_temp <- silhouette(km$cluster, dist(numeric_scale))
silhouette_values[i] <- mean(sil_temp[, 3])
# External Validation
ari_values[i] <- adjustedRandIndex(km$cluster, ukm$UNS)
# Relative Validation
wss_values[i] <- km$tot.withinss
}
validation_result <- data.frame(
k = k_values,
Silhouette = round(silhouette_values, 3),
ARI = round(ari_values, 3),
WSS = round(wss_values, 2)
)
validation_result
Berdasarkan hasil perbandingan beberapa nilai cluster, diperoleh
bahwa nilai silhouette score tertinggi berada pada: \(k=8\), dengan nilai silhouette sebesar:
\(0.202\).
Sementara itu, nilai Adjusted Rand Index tertinggi berada pada: \(k=2\), dengan nilai: \(0.276\).
Hal ini menunjukkan bahwa jumlah cluster terbaik dapat berbeda
tergantung metode validasi yang digunakan. Internal validation lebih
mendukung penggunaan cluster yang lebih banyak, sedangkan external
validation menunjukkan bahwa penggunaan dua cluster lebih sesuai
terhadap label asli UNS.
Di sisi lain, nilai WSS terus mengalami penurunan ketika jumlah
cluster bertambah. Namun, berdasarkan metode elbow, titik penurunan
mulai melandai pada sekitar k=4. Oleh karena itu, jumlah cluster empat
tetap dipilih karena dianggap mampu memberikan keseimbangan antara
kompleksitas model dan kualitas cluster.
Secara umum, hasil validation menunjukkan bahwa struktur data masih
memiliki overlap antar kelompok sehingga pemisahan cluster belum
sepenuhnya optimal.
# Visual
validation_long <- melt(
validation_result[, c("k", "Silhouette", "ARI")],
id.vars = "k"
)
ggplot(validation_long,
aes(x = k, y = value, color = variable, group = variable)) +
geom_line(size = 1.2) +
geom_point(size = 3) +
labs(
title = "Perbandingan Silhouette Score dan ARI",
x = "Jumlah Cluster (k)",
y = "Score",
color = "Validation"
) +
theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Perbandingan Cluster
Table
(tab <- table(ukm$Cluster, ukm$UNS))
##
## very_low Low Middle High
## 1 2 27 13 18
## 2 1 13 37 16
## 3 21 40 12 2
## 4 0 3 26 27
Cluster 1
Cluster 1 didominasi oleh kategori:
Cluster ini menggambarkan pengguna dengan tingkat pengetahuan yang
cenderung rendah hingga menengah. Pengguna pada cluster ini kemungkinan
memiliki performa belajar yang cukup baik namun belum konsisten.
Cluster 2
Cluster 2 didominasi oleh:
Cluster ini merepresentasikan kelompok pengguna dengan kemampuan
belajar menengah. Pengguna dalam kelompok ini memiliki pola belajar dan
pemahaman yang relatif stabil.
Cluster 3
Cluster 3 didominasi oleh:
Cluster ini menunjukkan kelompok pengguna dengan tingkat pengetahuan
paling rendah. Kelompok ini kemungkinan memiliki waktu belajar dan hasil
evaluasi yang lebih rendah dibanding cluster lain.
Cluster 4
Cluster 4 didominasi oleh:
Cluster ini menggambarkan pengguna dengan performa belajar terbaik.
Pengguna pada cluster ini memiliki tingkat pemahaman dan hasil evaluasi
yang lebih tinggi dibanding kelompok lainnya.
Heatmap
library(ggplot2)
library(reshape2)
ggplot(melt(tab), aes(x=Var2, y=Var1, fill=value)) +
geom_tile() +
geom_text(aes(label=value), color="white") +
labs(x="UNS", y="Cluster", fill="Jumlah") +
theme_minimal()

Heatmap menunjukkan distribusi jumlah anggota pada setiap cluster
terhadap kategori UNS. Terlihat bahwa Cluster 3 lebih banyak diisi oleh
kategori Low dan very_low, sedangkan Cluster 4 lebih dominan pada
kategori Middle dan High.
Visualisasi ini menunjukkan bahwa metode K-Means mampu menangkap pola
umum tingkat pengetahuan pengguna meskipun masih terdapat beberapa
overlap antar cluster.
Scatter plot visual
df_plot <- as.data.frame(numeric_scale)
df_plot$Cluster <- factor(ukm$Cluster)
df_plot$UNS <- ukm$UNS
# Convex Hull
hull_data <- df_plot %>%
group_by(Cluster) %>%
slice(chull(STG, SCG)) %>%
ungroup()
ggplot(df_plot, aes(x = STG, y = SCG, color = Cluster)) +
geom_point(size = 3) +
geom_polygon(
data = hull_data,
aes(fill = Cluster),
alpha = 0.2,
color = NA) +
labs(title = "Visualisasi Cluster dengan Convex Hull") +
theme_minimal()

Scatter plot dengan convex hull menunjukkan persebaran data
berdasarkan variabel STG dan SCG. Cluster masih terlihat saling
bertumpang tindih, yang menunjukkan bahwa batas antar cluster belum
sepenuhnya jelas.
Namun demikian, terdapat kecenderungan bahwa pengguna dengan nilai
belajar lebih tinggi membentuk kelompok tersendiri dibanding pengguna
dengan performa rendah. Hal ini mendukung hasil clustering yang mampu
membedakan kelompok pengguna berdasarkan aktivitas belajar dan performa
akademik.
Penutup
Analisis clustering menggunakan metode K-Means menunjukkan bahwa data
pengguna dapat dikelompokkan berdasarkan aktivitas belajar dan performa
akademik. Meskipun kualitas cluster masih belum optimal berdasarkan
silhouette score dan ARI, metode elbow menunjukkan bahwa penggunaan
empat cluster sudah cukup baik untuk merepresentasikan struktur
data.
LS0tDQp0aXRsZTogIkNsdXN0ZXIgVmFsaWRhdGlvbiB1c2luZyBLLU1lYW5zIG9uIFVzZXIgS25vd2xlZGdlIE1vZGVsaW5nIERhdGFzZXQiDQphdXRob3I6ICJSYWZseSBQcml5YW50YW1hIFJhbWFkaGFuIEJhZ2Fza2FyYSINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBkZl9wcmludDogInBhZ2VkIg0KICAgIGNvZGVfZm9sZGluZzogImhpZGUiDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgdGhlbWU6IHlldGkNCi0tLQ0KDQo8c3R5bGU+DQpib2R5IHsNCiAgYmFja2dyb3VuZC1jb2xvcjogI2VlZjVmZjsNCn0NCg0KcCB7DQogIHRleHQtYWxpZ246IGp1c3RpZnk7DQp9DQo8L3N0eWxlPg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoY2x1c3RlcikNCmxpYnJhcnkobWNsdXN0KQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShyZXNoYXBlMikNCmBgYA0KDQojIFBlbmRhaHVsdWFuDQoNCkNsdXN0ZXJpbmcgbWVydXBha2FuIHNhbGFoIHNhdHUgbWV0b2RlIHVuc3VwZXJ2aXNlZCBsZWFybmluZyB5YW5nIGRpZ3VuYWthbiB1bnR1ayBtZW5nZWxvbXBva2thbiBkYXRhIGJlcmRhc2Fya2FuIGtlbWlyaXBhbiBrYXJha3RlcmlzdGlrLiBQYWRhIGFuYWxpc2lzIGluaSBkaWd1bmFrYW4gbWV0b2RlIEstTWVhbnMgQ2x1c3RlcmluZyBwYWRhIGRhdGFzZXQgVXNlciBLbm93bGVkZ2UgTW9kZWxpbmcgZGFyaSBVQ0kgTWFjaGluZSBMZWFybmluZyBSZXBvc2l0b3J5Lg0KDQpUdWp1YW4gYW5hbGlzaXMgaW5pIGFkYWxhaCB1bnR1ayBtZW5nZXZhbHVhc2kga3VhbGl0YXMgY2x1c3RlciBtZW5nZ3VuYWthbiBiZWJlcmFwYSBtZXRvZGUgY2x1c3RlciB2YWxpZGF0aW9uLCB5YWl0dSBpbnRlcm5hbCB2YWxpZGF0aW9uLCBleHRlcm5hbCB2YWxpZGF0aW9uLCBkYW4gcmVsYXRpdmUgdmFsaWRhdGlvbi4NCg0KIyBEYXRhc2V0IGBVc2VyIEtub3dsZWRnZSBNb2RlbGluZ2ANCg0KRGF0YXNldCB5YW5nIGRpZ3VuYWthbiBpbmkgZGFyaSBVQ0kgTWFjaGluZSBMZWFybmluZyBSZXBvc2l0b3J5LiBEYXRhc2V0IGRpZ3VuYWthbiB1bnR1ayBtZW5nYW5hbGlzaXMgdGluZ2thdCBwZW5nZXRhaHVhbiBwZW5nZ3VuYSBiZXJkYXNhcmthbiBha3Rpdml0YXMgYmVsYWphci4NCg0KVmFyaWFiZWwgZGFsYW0gZGF0YXNldCBpbmkgZGFwYXQgZGlqZWxhc2thbiBzZWJhZ2FpIGJlcmlrdXQ6XA0KMS4gKlNURyAoU3R1ZHkgVGltZSBHb2FsKSogPSBXYWt0dSBiZWxhamFyXA0KMi4gKlNDRyAoU3R1ZHkgQ291bnQgR29hbCkqID0gSnVtbGFoIHBlbmd1bGFuZ2FuIG1hdGVyaVwNCjMuICpTVFIgKFN0dWR5IFRpbWUgUmVhbCkqID0gV2FrdHUgYmVsYWphciBha3R1YWxcDQo0LiAqTFBSIChMZWFybmluZyBQcm9jZXNzIFJhdGUpKiA9IFRpbmdrYXQgcGVtYWhhbWFuIHNlbGFtYSBwcm9zZXMgYmVsYWphclwNCjUuICpQRUcgKFBlcmZvcm1hbmNlIEV2YWx1YXRpb24gR3JhZGUpKiA9IEhhc2lsIGV2YWx1YXNpIGJlbGFqYXJcDQo2LiAqVU5TIChVc2VyIEtub3dsZWRnZSBMZXZlbCkqID0gVGluZ2thdCBwZW5nZXRhaHVhbiBwZW5nZ3VuYSAoVGFyZ2V0KQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnVrbSA8LSByZWFkX2V4Y2VsKCJVc2VyIEtub3dsZWRnZSBNb2RlbGluZy54bHN4IikNCmhlYWQodWttKQ0Kc3RyKHVrbSkNCmBgYA0KDQojIFByZXByb2Nlc3NpbmcgRGF0YQ0KDQpQYWRhIHRhaGFwIHByZXByb2Nlc3NpbmcgZGlsYWt1a2FuIG5vcm1hbGlzYXNpIHBhZGEgc2VsdXJ1aCB2YXJpYWJlbCBudW1lcmlrIHlhaXR1IFNURywgU0NHLCBTVFIsIExQUiwgZGFuIFBFRyBtZW5nZ3VuYWthbiBmdW5nc2kgc2NhbGUoKS4gTm9ybWFsaXNhc2kgZGlwZXJsdWthbiBrYXJlbmEgbWV0b2RlIEstTWVhbnMgc2Vuc2l0aWYgdGVyaGFkYXAgcGVyYmVkYWFuIHNrYWxhIGRhdGEuIERlbmdhbiBub3JtYWxpc2FzaSwgc2VsdXJ1aCB2YXJpYWJlbCBtZW1pbGlraSBrb250cmlidXNpIHlhbmcgc2VpbWJhbmcgZGFsYW0gcHJvc2VzIHBlbWJlbnR1a2FuIGNsdXN0ZXIuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KIyBOb3JtYWxpc2FzaQ0KbnVtZXJpY192YXIgPC0gdWttICU+JSANCiAgc2VsZWN0KCJTVEciLCAiU0NHIiwgIlNUUiIsICJMUFIiLCAiUEVHIikNCm51bWVyaWNfc2NhbGUgPC0gc2NhbGUobnVtZXJpY192YXIpDQpgYGANCg0KU2VsYWluIGl0dSwgdmFyaWFiZWwgVU5TIGRpdWJhaCBtZW5qYWRpIHRpcGUgZmFrdG9yIGFnYXIgZGFwYXQgZGlndW5ha2FuIGRhbGFtIHByb3NlcyB2YWxpZGFzaSBla3N0ZXJuYWwgbWVuZ2d1bmFrYW4gQWRqdXN0ZWQgUmFuZCBJbmRleCAoQVJJKS4NCg0KYGBge3J9DQojIFR5cGUgQ2FzdGluZw0KdWttJFVOUyA8LSBmYWN0b3IodWttJFVOUywgbGV2ZWxzID0gYygidmVyeV9sb3ciLCAiTG93IiwgIk1pZGRsZSIsICJIaWdoIikpDQpgYGANCg0KSGFzaWwgcGVuZ2VjZWthbiBtaXNzaW5nIHZhbHVlIG1lbnVuanVra2FuIGJhaHdhIGRhdGFzZXQgdGlkYWsgbWVtaWxpa2kgZGF0YSB5YW5nIGhpbGFuZywgc2VoaW5nZ2EgZGF0YSBkYXBhdCBsYW5nc3VuZyBkaWd1bmFrYW4gdW50dWsgcHJvc2VzIGNsdXN0ZXJpbmcuDQoNCmBgYHtyfQ0KIyBDZWsgTWlzc2luZyBWYWx1ZQ0Kc3VtKGlzLm5hKHVrbSkpDQpgYGANCg0KIyBDbHVzdGVyaW5nIEsgLSBNZWFucw0KDQpNZXRvZGUgSy1NZWFucyBkaWxha3VrYW4gZGVuZ2FuIGp1bWxhaCBjbHVzdGVyIHNlYmFueWFrOg0KDQokJGsgPSA0JCQNCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQprIDwtIDQNCmttZWFuc19yZXN1bHQgPC0ga21lYW5zKG51bWVyaWNfc2NhbGUsIGNlbnRlcnMgPSBrKQ0KdWttJENsdXN0ZXIgPC0ga21lYW5zX3Jlc3VsdCRjbHVzdGVyDQp0YWJsZSh1a20kQ2x1c3RlcikNCmBgYA0KDQpIYXNpbCBjbHVzdGVyaW5nIG1lbnVuanVra2FuIGJhaHdhIGRhdGEgYmVyaGFzaWwgZGliYWdpIG1lbmphZGkgZW1wYXQga2Vsb21wb2sgZGVuZ2FuIGp1bWxhaCBhbmdnb3RhIHNlYmFnYWkgYmVyaWt1dDoNCg0KLSBDbHVzdGVyIDEgPSA2MCBkYXRhDQotIENsdXN0ZXIgMiA9IDY3IGRhdGENCi0gQ2x1c3RlciAzID0gNzUgZGF0YQ0KLSBDbHVzdGVyIDQgPSA1NiBkYXRhDQoNCkRpc3RyaWJ1c2kganVtbGFoIGFuZ2dvdGEgY2x1c3RlciB0ZXJsaWhhdCBjdWt1cCBzZWltYmFuZyBzZWhpbmdnYSB0aWRhayB0ZXJkYXBhdCBjbHVzdGVyIHlhbmcgdGVybGFsdSBrZWNpbCBtYXVwdW4gdGVybGFsdSBkb21pbmFuLg0KDQojIENsdXN0ZXIgVmFsaWRhdGlvbg0KDQojIyBJbnRlcm5hbCBWYWxpZGF0aW9uDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0Kc2lsIDwtIHNpbGhvdWV0dGUodWttJENsdXN0ZXIsIGRpc3QobnVtZXJpY19zY2FsZSkpDQpzaWxfc2NvcmUgPC0gbWVhbihzaWxbLDNdKQ0KY2F0KCJBdmFyYWdlIFNpbGhvdWV0dGUgU2NvcmUgOiAiLCBzaWxfc2NvcmUpDQpgYGANCg0KSGFzaWwgaW50ZXJuYWwgdmFsaWRhdGlvbiBtZW5nZ3VuYWthbiBzaWxob3VldHRlIHNjb3JlIG1lbmdoYXNpbGthbiBuaWxhaSBzZWJlc2FyOiAkMC4xODEzOTkyJC4NCg0KTmlsYWkgc2lsaG91ZXR0ZSBzY29yZSBiZXJhZGEgcGFkYSByZW50YW5nIOKIkjEgaGluZ2dhIDEuIFNlbWFraW4gbWVuZGVrYXRpIG5pbGFpIDEsIG1ha2Ega3VhbGl0YXMgY2x1c3RlciBzZW1ha2luIGJhaWsga2FyZW5hIGFuZ2dvdGEgZGFsYW0gY2x1c3RlciBtZW1pbGlraSBrZW1pcmlwYW4gdGluZ2dpIGRhbiBiZXJiZWRhIGphdWggZGVuZ2FuIGNsdXN0ZXIgbGFpbi4NCg0KTmlsYWkgc2lsaG91ZXR0ZSBzZWJlc2FyIDAuMTgxIG1lbnVuanVra2FuIGJhaHdhIGt1YWxpdGFzIHBlbWlzYWhhbiBjbHVzdGVyIG1hc2loIHRlcmdvbG9uZyByZW5kYWguIEhhbCBpbmkgbWVuZ2luZGlrYXNpa2FuIGJhaHdhIGJlYmVyYXBhIGRhdGEgbWFzaWggbWVtaWxpa2kga2VtaXJpcGFuIGRlbmdhbiBjbHVzdGVyIGxhaW4gc2VoaW5nZ2EgYmF0YXMgYW50YXIgY2x1c3RlciBiZWx1bSB0ZXJiZW50dWsgc2VjYXJhIGplbGFzLg0KDQojIyBFa3N0ZXJuYWwgVmFsaWRhdGlvbg0KDQpgYGB7cn0NCmFyaV9zY29yZSA8LSBhZGp1c3RlZFJhbmRJbmRleCh1a20kQ2x1c3RlciwgdWttJFVOUykNCmNhdCgiQWRqdXN0ZWQgUmFuZCBJbmRleCA6IiwgYXJpX3Njb3JlKQ0KYGBgDQoNCkhhc2lsIGV4dGVybmFsIHZhbGlkYXRpb24gbWVuZ2d1bmFrYW4gQWRqdXN0ZWQgUmFuZCBJbmRleCBtZW5naGFzaWxrYW4gbmlsYWkgc2ViZXNhcjogJDAuMTI1Mjk4JC4NCg0KTmlsYWkgQVJJIGRpZ3VuYWthbiB1bnR1ayBtZW5ndWt1ciBrZXNlc3VhaWFuIGhhc2lsIGNsdXN0ZXJpbmcgZGVuZ2FuIGxhYmVsIGFzbGkgVU5TLiBOaWxhaSB5YW5nIG1lbmRla2F0aSAxIG1lbnVuanVra2FuIGtlc2VzdWFpYW4geWFuZyBzYW5nYXQgYmFpay4NCg0KTmlsYWkgQVJJIHNlYmVzYXIgMC4xMjUgbWVudW5qdWtrYW4gYmFod2EgaGFzaWwgY2x1c3RlciBiZWx1bSBtZW1pbGlraSBrZXNlc3VhaWFuIHlhbmcgdGluZ2dpIHRlcmhhZGFwIGthdGVnb3JpIHBlbmdldGFodWFuIGFzbGkgcGVuZ2d1bmEuIEhhbCBpbmkgbWVudW5qdWtrYW4gYmFod2EgbWV0b2RlIEstTWVhbnMgbWFzaWggYmVsdW0gbWFtcHUgbWVtaXNhaGthbiB0aW5na2F0IHBlbmdldGFodWFuIHBlbmdndW5hIHNlY2FyYSBvcHRpbWFsIGJlcmRhc2Fya2FuIGxhYmVsIGFzbGkgZGF0YXNldC4NCg0KIyMgUmVsYXRpdmUgVmFsaWRhdGlvbg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnNldC5zZWVkKDEyMykNCmtfcmFuZ2UgPC0gMToxMA0Kd3NzIDwtIG51bWVyaWMobGVuZ3RoKGtfcmFuZ2UpKQ0KDQpmb3IgKGkgaW4ga19yYW5nZSkgew0KICBrbSA8LSBrbWVhbnMobnVtZXJpY19zY2FsZSwgY2VudGVycyA9IGksIG5zdGFydCA9IDI1KQ0KICB3c3NbaV0gPC0ga20kdG90LndpdGhpbnNzDQp9DQoNCndzc19yZXN1bHQgPC0gZGF0YS5mcmFtZSgNCiAgayA9IGtfcmFuZ2UsDQogIFdTUyA9IHdzcw0KKQ0KDQp3c3NfcmVzdWx0DQoNCiMgVmlzdWFsaXNhc2kgRWxib3cNCmZ2aXpfbmJjbHVzdChudW1lcmljX3NjYWxlLCBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKSArDQogIGxhYnModGl0bGUgPSAiT3B0aW1hbCBOdW1iZXIgb2YgQ2x1c3RlcnMgLSBFbGJvdyBNZXRob2QiKSArDQogIHRoZW1lX21pbmltYWwoKSANCmBgYA0KDQpSZWxhdGl2ZSB2YWxpZGF0aW9uIGRpbGFrdWthbiBtZW5nZ3VuYWthbiBtZXRvZGUgZWxib3cgYmVyZGFzYXJrYW4gbmlsYWkgV2l0aGluIFN1bSBvZiBTcXVhcmVzIChXU1MpLiBIYXNpbCBtZW51bmp1a2thbiBiYWh3YSBuaWxhaSBXU1MgdGVydXMgbWVudXJ1biBzZWlyaW5nIGJlcnRhbWJhaG55YSBqdW1sYWggY2x1c3Rlci4NCg0KTmFtdW4sIHBlbnVydW5hbiBtdWxhaSBtZWxhbWJhdCBzZXRlbGFoOiAkayA9IDQkLg0KDQpIYWwgaW5pIG1lbnVuanVra2FuIGJhaHdhIHBlbmdndW5hYW4gbGViaWggZGFyaSBlbXBhdCBjbHVzdGVyIHRpZGFrIG1lbWJlcmlrYW4gcGVudXJ1bmFuIHZhcmlhc2kgd2l0aGluLWNsdXN0ZXIgeWFuZyB0ZXJsYWx1IHNpZ25pZmlrYW4uIE9sZWgga2FyZW5hIGl0dSwganVtbGFoIGNsdXN0ZXIgc2ViYW55YWsgZW1wYXQgZGlhbmdnYXAgY3VrdXAgb3B0aW1hbCBiZXJkYXNhcmthbiBtZXRvZGUgZWxib3cuDQoNCiMgUGVyYmFuZGluZ2FuIEhhc2lsDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KIyBQZXJiYW5kaW5nYW4gbmlsYWkgaw0Ka192YWx1ZXMgPC0gMjoxMA0KDQpzaWxob3VldHRlX3ZhbHVlcyA8LSBudW1lcmljKGxlbmd0aChrX3ZhbHVlcykpDQphcmlfdmFsdWVzIDwtIG51bWVyaWMobGVuZ3RoKGtfdmFsdWVzKSkNCndzc192YWx1ZXMgPC0gbnVtZXJpYyhsZW5ndGgoa192YWx1ZXMpKQ0KDQpzZXQuc2VlZCgxMjMpDQoNCmZvciAoaSBpbiBzZXFfYWxvbmcoa192YWx1ZXMpKSB7DQogIA0KICBrbSA8LSBrbWVhbnMoDQogICAgbnVtZXJpY19zY2FsZSwNCiAgICBjZW50ZXJzID0ga192YWx1ZXNbaV0sDQogICAgbnN0YXJ0ID0gMjUNCiAgKQ0KICANCiAgIyBJbnRlcm5hbCBWYWxpZGF0aW9uDQogIHNpbF90ZW1wIDwtIHNpbGhvdWV0dGUoa20kY2x1c3RlciwgZGlzdChudW1lcmljX3NjYWxlKSkNCiAgc2lsaG91ZXR0ZV92YWx1ZXNbaV0gPC0gbWVhbihzaWxfdGVtcFssIDNdKQ0KICANCiAgIyBFeHRlcm5hbCBWYWxpZGF0aW9uDQogIGFyaV92YWx1ZXNbaV0gPC0gYWRqdXN0ZWRSYW5kSW5kZXgoa20kY2x1c3RlciwgdWttJFVOUykNCiAgDQogICMgUmVsYXRpdmUgVmFsaWRhdGlvbg0KICB3c3NfdmFsdWVzW2ldIDwtIGttJHRvdC53aXRoaW5zcw0KfQ0KDQp2YWxpZGF0aW9uX3Jlc3VsdCA8LSBkYXRhLmZyYW1lKA0KICBrID0ga192YWx1ZXMsDQogIFNpbGhvdWV0dGUgPSByb3VuZChzaWxob3VldHRlX3ZhbHVlcywgMyksDQogIEFSSSA9IHJvdW5kKGFyaV92YWx1ZXMsIDMpLA0KICBXU1MgPSByb3VuZCh3c3NfdmFsdWVzLCAyKQ0KKQ0KDQp2YWxpZGF0aW9uX3Jlc3VsdA0KYGBgDQoNCkJlcmRhc2Fya2FuIGhhc2lsIHBlcmJhbmRpbmdhbiBiZWJlcmFwYSBuaWxhaSBjbHVzdGVyLCBkaXBlcm9sZWggYmFod2EgbmlsYWkgc2lsaG91ZXR0ZSBzY29yZSB0ZXJ0aW5nZ2kgYmVyYWRhIHBhZGE6ICRrPTgkLCBkZW5nYW4gbmlsYWkgc2lsaG91ZXR0ZSBzZWJlc2FyOiAkMC4yMDIkLg0KDQpTZW1lbnRhcmEgaXR1LCBuaWxhaSBBZGp1c3RlZCBSYW5kIEluZGV4IHRlcnRpbmdnaSBiZXJhZGEgcGFkYTogJGs9MiQsIGRlbmdhbiBuaWxhaTogJDAuMjc2JC4NCg0KSGFsIGluaSBtZW51bmp1a2thbiBiYWh3YSBqdW1sYWggY2x1c3RlciB0ZXJiYWlrIGRhcGF0IGJlcmJlZGEgdGVyZ2FudHVuZyBtZXRvZGUgdmFsaWRhc2kgeWFuZyBkaWd1bmFrYW4uIEludGVybmFsIHZhbGlkYXRpb24gbGViaWggbWVuZHVrdW5nIHBlbmdndW5hYW4gY2x1c3RlciB5YW5nIGxlYmloIGJhbnlhaywgc2VkYW5na2FuIGV4dGVybmFsIHZhbGlkYXRpb24gbWVudW5qdWtrYW4gYmFod2EgcGVuZ2d1bmFhbiBkdWEgY2x1c3RlciBsZWJpaCBzZXN1YWkgdGVyaGFkYXAgbGFiZWwgYXNsaSBVTlMuDQoNCkRpIHNpc2kgbGFpbiwgbmlsYWkgV1NTIHRlcnVzIG1lbmdhbGFtaSBwZW51cnVuYW4ga2V0aWthIGp1bWxhaCBjbHVzdGVyIGJlcnRhbWJhaC4gTmFtdW4sIGJlcmRhc2Fya2FuIG1ldG9kZSBlbGJvdywgdGl0aWsgcGVudXJ1bmFuIG11bGFpIG1lbGFuZGFpIHBhZGEgc2VraXRhciBrPTQuIE9sZWgga2FyZW5hIGl0dSwganVtbGFoIGNsdXN0ZXIgZW1wYXQgdGV0YXAgZGlwaWxpaCBrYXJlbmEgZGlhbmdnYXAgbWFtcHUgbWVtYmVyaWthbiBrZXNlaW1iYW5nYW4gYW50YXJhIGtvbXBsZWtzaXRhcyBtb2RlbCBkYW4ga3VhbGl0YXMgY2x1c3Rlci4NCg0KU2VjYXJhIHVtdW0sIGhhc2lsIHZhbGlkYXRpb24gbWVudW5qdWtrYW4gYmFod2Egc3RydWt0dXIgZGF0YSBtYXNpaCBtZW1pbGlraSBvdmVybGFwIGFudGFyIGtlbG9tcG9rIHNlaGluZ2dhIHBlbWlzYWhhbiBjbHVzdGVyIGJlbHVtIHNlcGVudWhueWEgb3B0aW1hbC4NCg0KYGBge3J9DQojIFZpc3VhbA0KdmFsaWRhdGlvbl9sb25nIDwtIG1lbHQoDQogIHZhbGlkYXRpb25fcmVzdWx0WywgYygiayIsICJTaWxob3VldHRlIiwgIkFSSSIpXSwNCiAgaWQudmFycyA9ICJrIg0KKQ0KDQpnZ3Bsb3QodmFsaWRhdGlvbl9sb25nLA0KICAgICAgIGFlcyh4ID0gaywgeSA9IHZhbHVlLCBjb2xvciA9IHZhcmlhYmxlLCBncm91cCA9IHZhcmlhYmxlKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX3BvaW50KHNpemUgPSAzKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUGVyYmFuZGluZ2FuIFNpbGhvdWV0dGUgU2NvcmUgZGFuIEFSSSIsDQogICAgeCA9ICJKdW1sYWggQ2x1c3RlciAoaykiLA0KICAgIHkgPSAiU2NvcmUiLA0KICAgIGNvbG9yID0gIlZhbGlkYXRpb24iDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQojIFBlcmJhbmRpbmdhbiBDbHVzdGVyDQoNCiMjIFRhYmxlDQoNCmBgYHtyfQ0KKHRhYiA8LSB0YWJsZSh1a20kQ2x1c3RlciwgdWttJFVOUykpDQpgYGANCg0KKipDbHVzdGVyIDEqKg0KDQpDbHVzdGVyIDEgZGlkb21pbmFzaSBvbGVoIGthdGVnb3JpOg0KDQotIExvdyA9IDI3DQotIEhpZ2ggPSAxOA0KDQpDbHVzdGVyIGluaSBtZW5nZ2FtYmFya2FuIHBlbmdndW5hIGRlbmdhbiB0aW5na2F0IHBlbmdldGFodWFuIHlhbmcgY2VuZGVydW5nIHJlbmRhaCBoaW5nZ2EgbWVuZW5nYWguIFBlbmdndW5hIHBhZGEgY2x1c3RlciBpbmkga2VtdW5na2luYW4gbWVtaWxpa2kgcGVyZm9ybWEgYmVsYWphciB5YW5nIGN1a3VwIGJhaWsgbmFtdW4gYmVsdW0ga29uc2lzdGVuLg0KDQoqKkNsdXN0ZXIgMioqDQoNCkNsdXN0ZXIgMiBkaWRvbWluYXNpIG9sZWg6DQoNCi0gTWlkZGxlID0gMzcNCi0gSGlnaCA9IDE2DQoNCkNsdXN0ZXIgaW5pIG1lcmVwcmVzZW50YXNpa2FuIGtlbG9tcG9rIHBlbmdndW5hIGRlbmdhbiBrZW1hbXB1YW4gYmVsYWphciBtZW5lbmdhaC4gUGVuZ2d1bmEgZGFsYW0ga2Vsb21wb2sgaW5pIG1lbWlsaWtpIHBvbGEgYmVsYWphciBkYW4gcGVtYWhhbWFuIHlhbmcgcmVsYXRpZiBzdGFiaWwuDQoNCioqQ2x1c3RlciAzKioNCg0KQ2x1c3RlciAzIGRpZG9taW5hc2kgb2xlaDoNCg0KLSBMb3cgPSA0MA0KLSB2ZXJ5X2xvdyA9IDIxDQoNCkNsdXN0ZXIgaW5pIG1lbnVuanVra2FuIGtlbG9tcG9rIHBlbmdndW5hIGRlbmdhbiB0aW5na2F0IHBlbmdldGFodWFuIHBhbGluZyByZW5kYWguIEtlbG9tcG9rIGluaSBrZW11bmdraW5hbiBtZW1pbGlraSB3YWt0dSBiZWxhamFyIGRhbiBoYXNpbCBldmFsdWFzaSB5YW5nIGxlYmloIHJlbmRhaCBkaWJhbmRpbmcgY2x1c3RlciBsYWluLg0KDQoqKkNsdXN0ZXIgNCoqDQoNCkNsdXN0ZXIgNCBkaWRvbWluYXNpIG9sZWg6DQoNCi0gSGlnaCA9IDI3DQotIE1pZGRsZSA9IDI2DQoNCkNsdXN0ZXIgaW5pIG1lbmdnYW1iYXJrYW4gcGVuZ2d1bmEgZGVuZ2FuIHBlcmZvcm1hIGJlbGFqYXIgdGVyYmFpay4gUGVuZ2d1bmEgcGFkYSBjbHVzdGVyIGluaSBtZW1pbGlraSB0aW5na2F0IHBlbWFoYW1hbiBkYW4gaGFzaWwgZXZhbHVhc2kgeWFuZyBsZWJpaCB0aW5nZ2kgZGliYW5kaW5nIGtlbG9tcG9rIGxhaW5ueWEuDQoNCiMjIEhlYXRtYXANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KZ2dwbG90KG1lbHQodGFiKSwgYWVzKHg9VmFyMiwgeT1WYXIxLCBmaWxsPXZhbHVlKSkgKw0KICBnZW9tX3RpbGUoKSArDQogIGdlb21fdGV4dChhZXMobGFiZWw9dmFsdWUpLCBjb2xvcj0id2hpdGUiKSArDQogIGxhYnMoeD0iVU5TIiwgeT0iQ2x1c3RlciIsIGZpbGw9Ikp1bWxhaCIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KSGVhdG1hcCBtZW51bmp1a2thbiBkaXN0cmlidXNpIGp1bWxhaCBhbmdnb3RhIHBhZGEgc2V0aWFwIGNsdXN0ZXIgdGVyaGFkYXAga2F0ZWdvcmkgVU5TLiBUZXJsaWhhdCBiYWh3YSBDbHVzdGVyIDMgbGViaWggYmFueWFrIGRpaXNpIG9sZWgga2F0ZWdvcmkgTG93IGRhbiB2ZXJ5X2xvdywgc2VkYW5na2FuIENsdXN0ZXIgNCBsZWJpaCBkb21pbmFuIHBhZGEga2F0ZWdvcmkgTWlkZGxlIGRhbiBIaWdoLg0KDQpWaXN1YWxpc2FzaSBpbmkgbWVudW5qdWtrYW4gYmFod2EgbWV0b2RlIEstTWVhbnMgbWFtcHUgbWVuYW5na2FwIHBvbGEgdW11bSB0aW5na2F0IHBlbmdldGFodWFuIHBlbmdndW5hIG1lc2tpcHVuIG1hc2loIHRlcmRhcGF0IGJlYmVyYXBhIG92ZXJsYXAgYW50YXIgY2x1c3Rlci4NCg0KIyMgU2NhdHRlciBwbG90IHZpc3VhbA0KDQpgYGB7cn0NCmRmX3Bsb3QgPC0gYXMuZGF0YS5mcmFtZShudW1lcmljX3NjYWxlKQ0KZGZfcGxvdCRDbHVzdGVyIDwtIGZhY3Rvcih1a20kQ2x1c3RlcikNCmRmX3Bsb3QkVU5TIDwtIHVrbSRVTlMNCg0KIyBDb252ZXggSHVsbA0KaHVsbF9kYXRhIDwtIGRmX3Bsb3QgJT4lDQogIGdyb3VwX2J5KENsdXN0ZXIpICU+JQ0KICBzbGljZShjaHVsbChTVEcsIFNDRykpICU+JQ0KICB1bmdyb3VwKCkNCg0KZ2dwbG90KGRmX3Bsb3QsIGFlcyh4ID0gU1RHLCB5ID0gU0NHLCBjb2xvciA9IENsdXN0ZXIpKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDMpICsNCiAgZ2VvbV9wb2x5Z29uKA0KICAgIGRhdGEgPSBodWxsX2RhdGEsDQogICAgYWVzKGZpbGwgPSBDbHVzdGVyKSwNCiAgICBhbHBoYSA9IDAuMiwNCiAgICBjb2xvciA9IE5BKSArDQogIGxhYnModGl0bGUgPSAiVmlzdWFsaXNhc2kgQ2x1c3RlciBkZW5nYW4gQ29udmV4IEh1bGwiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNClNjYXR0ZXIgcGxvdCBkZW5nYW4gY29udmV4IGh1bGwgbWVudW5qdWtrYW4gcGVyc2ViYXJhbiBkYXRhIGJlcmRhc2Fya2FuIHZhcmlhYmVsIFNURyBkYW4gU0NHLiBDbHVzdGVyIG1hc2loIHRlcmxpaGF0IHNhbGluZyBiZXJ0dW1wYW5nIHRpbmRpaCwgeWFuZyBtZW51bmp1a2thbiBiYWh3YSBiYXRhcyBhbnRhciBjbHVzdGVyIGJlbHVtIHNlcGVudWhueWEgamVsYXMuDQoNCk5hbXVuIGRlbWlraWFuLCB0ZXJkYXBhdCBrZWNlbmRlcnVuZ2FuIGJhaHdhIHBlbmdndW5hIGRlbmdhbiBuaWxhaSBiZWxhamFyIGxlYmloIHRpbmdnaSBtZW1iZW50dWsga2Vsb21wb2sgdGVyc2VuZGlyaSBkaWJhbmRpbmcgcGVuZ2d1bmEgZGVuZ2FuIHBlcmZvcm1hIHJlbmRhaC4gSGFsIGluaSBtZW5kdWt1bmcgaGFzaWwgY2x1c3RlcmluZyB5YW5nIG1hbXB1IG1lbWJlZGFrYW4ga2Vsb21wb2sgcGVuZ2d1bmEgYmVyZGFzYXJrYW4gYWt0aXZpdGFzIGJlbGFqYXIgZGFuIHBlcmZvcm1hIGFrYWRlbWlrLg0KDQojIFBlbnV0dXANCg0KQW5hbGlzaXMgY2x1c3RlcmluZyBtZW5nZ3VuYWthbiBtZXRvZGUgSy1NZWFucyBtZW51bmp1a2thbiBiYWh3YSBkYXRhIHBlbmdndW5hIGRhcGF0IGRpa2Vsb21wb2trYW4gYmVyZGFzYXJrYW4gYWt0aXZpdGFzIGJlbGFqYXIgZGFuIHBlcmZvcm1hIGFrYWRlbWlrLiBNZXNraXB1biBrdWFsaXRhcyBjbHVzdGVyIG1hc2loIGJlbHVtIG9wdGltYWwgYmVyZGFzYXJrYW4gc2lsaG91ZXR0ZSBzY29yZSBkYW4gQVJJLCBtZXRvZGUgZWxib3cgbWVudW5qdWtrYW4gYmFod2EgcGVuZ2d1bmFhbiBlbXBhdCBjbHVzdGVyIHN1ZGFoIGN1a3VwIGJhaWsgdW50dWsgbWVyZXByZXNlbnRhc2lrYW4gc3RydWt0dXIgZGF0YS4=