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:

  • Low = 27
  • High = 18

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:

  • Middle = 37
  • High = 16

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:

  • Low = 40
  • very_low = 21

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:

  • High = 27
  • Middle = 26

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=