1. Membangun Dataset Praktikum

Pada praktikum ini digunakan contoh data pelanggan dan data transaksi. Dataset dibuat secara sengaja dengan beberapa masalah kualitas data agar proses preprocessing dapat didemonstrasikan.

Masalah yang akan diatasi yaitu:

  • missing value;
  • format kategori yang tidak konsisten;
  • duplikasi identifier;
  • nilai usia yang tidak masuk akal;
  • nilai pendapatan ekstrem;
  • identifier yang tidak memiliki pasangan ketika dilakukan integrasi.

Data Pelanggan Mentah

pelanggan_raw <- data.frame(
  customer_id = c(
    "M101","M102","M103","M104","M105",
    "M106","M107","M108","M109","M110",
    "M110","M112","M113","M114"
  ),

  nama = c(
    "Rani","Dimas","Salsa","Fajar","Nadia",
    "Rizky","Tio","Maya","Arif","Putri",
    "Putri","Kevin","Lala","Bagas"
  ),

  usia = c(
    22, 27, NA, 31, 145,
    26, 29, 24, 38, 33,
    33, NA, 21, 30
  ),

  pendapatan = c(
    4200000, NA, 5100000, 5600000, 4900000,
    4800000, 75000000, 4600000, NA, 5300000,
    5300000, 4700000, 4400000, 5200000
  ),

  kota = c(
    "Pekanbaru"," PKU","PEKANBARU","Dumai",
    "dumai","DUMAI","Pekanbaru ","PEKANBARU",
    "Siak","pku","pku","Pekanbaru",
    NA,"DUMAI"
  ),

  status = c(
    "Aktif","ACTIVE","aktif","A",
    "Tidak aktif","nonaktif","AKTIF","Aktif",
    "Tidak Aktif","A","A","active",
    "nonaktif","Aktif"
  ),

  stringsAsFactors = FALSE
)

pelanggan_raw

Melihat Struktur Data

dim(pelanggan_raw)
## [1] 14  6
names(pelanggan_raw)
## [1] "customer_id" "nama"        "usia"        "pendapatan"  "kota"       
## [6] "status"
str(pelanggan_raw)
## 'data.frame':    14 obs. of  6 variables:
##  $ customer_id: chr  "M101" "M102" "M103" "M104" ...
##  $ nama       : chr  "Rani" "Dimas" "Salsa" "Fajar" ...
##  $ usia       : num  22 27 NA 31 145 26 29 24 38 33 ...
##  $ pendapatan : num  4.2e+06 NA 5.1e+06 5.6e+06 4.9e+06 4.8e+06 7.5e+07 4.6e+06 NA 5.3e+06 ...
##  $ kota       : chr  "Pekanbaru" " PKU" "PEKANBARU" "Dumai" ...
##  $ status     : chr  "Aktif" "ACTIVE" "aktif" "A" ...
head(pelanggan_raw)
summary(pelanggan_raw)
##  customer_id            nama                usia          pendapatan      
##  Length:14          Length:14          Min.   : 21.00   Min.   : 4200000  
##  Class :character   Class :character   1st Qu.: 25.50   1st Qu.: 4675000  
##  Mode  :character   Mode  :character   Median : 29.50   Median : 5000000  
##                                        Mean   : 38.25   Mean   :10758333  
##                                        3rd Qu.: 33.00   3rd Qu.: 5300000  
##                                        Max.   :145.00   Max.   :75000000  
##                                        NA's   :2        NA's   :2         
##      kota              status         
##  Length:14          Length:14         
##  Class :character   Class :character  
##  Mode  :character   Mode  :character  
##                                       
##                                       
##                                       
## 

Sebelum melakukan perubahan, struktur, dimensi, tipe atribut, dan beberapa observasi awal perlu diperiksa terlebih dahulu.

2. Konsep dan Kualitas Data

Dimensi Kualitas Data

Enam dimensi kualitas data yang digunakan dalam pembahasan adalah:

  1. Accuracy — apakah nilai sesuai dengan kondisi sebenarnya.
  2. Completeness — apakah informasi yang dibutuhkan tersedia.
  3. Consistency — apakah nilai menggunakan format dan aturan yang konsisten.
  4. Timeliness — apakah data cukup mutakhir.
  5. Believability — apakah data dapat dipercaya.
  6. Interpretability — apakah arti dan format data mudah dipahami.

Mengukur Missing Value

colSums(is.na(pelanggan_raw))
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           2           2           1           0
round(
  colMeans(is.na(pelanggan_raw)) * 100,
  2
)
## customer_id        nama        usia  pendapatan        kota      status 
##        0.00        0.00       14.29       14.29        7.14        0.00

Memeriksa Duplikasi

sum(duplicated(pelanggan_raw))
## [1] 1
sum(duplicated(pelanggan_raw$customer_id))
## [1] 1

Memeriksa Kategori

sort(unique(pelanggan_raw$kota))
## [1] " PKU"       "dumai"      "Dumai"      "DUMAI"      "Pekanbaru" 
## [6] "PEKANBARU"  "Pekanbaru " "pku"        "Siak"
sort(unique(pelanggan_raw$status))
## [1] "A"           "active"      "ACTIVE"      "aktif"       "Aktif"      
## [6] "AKTIF"       "nonaktif"    "Tidak aktif" "Tidak Aktif"

Memeriksa Rentang Numerik

range(
  pelanggan_raw$usia,
  na.rm = TRUE
)
## [1]  21 145
range(
  pelanggan_raw$pendapatan,
  na.rm = TRUE
)
## [1] 4.2e+06 7.5e+07

Fungsi Audit Data

audit_data <- function(data) {

  data.frame(
    atribut = names(data),

    tipe = sapply(
      data,
      function(x) class(x)[1]
    ),

    jumlah_missing = sapply(
      data,
      function(x) sum(is.na(x))
    ),

    persen_missing = round(
      sapply(
        data,
        function(x)
          mean(is.na(x)) * 100
      ),
      2
    ),

    jumlah_unik = sapply(
      data,
      function(x)
        length(unique(x))
    ),

    row.names = NULL
  )
}

audit_awal <- audit_data(
  pelanggan_raw
)

audit_awal

Interpretasi: Pemeriksaan awal menunjukkan kondisi setiap variabel dalam dataset, meliputi tipe data, jumlah dan persentase nilai yang hilang, serta jumlah nilai unik. Informasi tersebut dapat digunakan untuk mengetahui variabel yang perlu diperiksa lebih lanjut sebelum dilakukan proses pembersihan dan pengolahan data.

3. Implementasi Data Cleaning

Membuat Salinan Kerja

pelanggan <- pelanggan_raw

Membersihkan Spasi dan Kapitalisasi

pelanggan$kota <- trimws(
  pelanggan$kota
)

pelanggan$status <- trimws(
  pelanggan$status
)

pelanggan$kota <- tolower(
  pelanggan$kota
)

pelanggan$status <- tolower(
  pelanggan$status
)

sort(unique(pelanggan$kota))
## [1] "dumai"     "pekanbaru" "pku"       "siak"
sort(unique(pelanggan$status))
## [1] "a"           "active"      "aktif"       "nonaktif"    "tidak aktif"

Menyeragamkan Kategori

pelanggan$kota[
  pelanggan$kota %in%
    c("pku", "pekanbaru")
] <- "Pekanbaru"

pelanggan$kota[
  pelanggan$kota == "dumai"
] <- "Dumai"

pelanggan$kota[
  pelanggan$kota == "siak"
] <- "Siak"
pelanggan$status[
  pelanggan$status %in%
    c("aktif","active","a")
] <- "Aktif"

pelanggan$status[
  pelanggan$status %in%
    c("tidak aktif","nonaktif")
] <- "Tidak Aktif"
sort(unique(pelanggan$kota))
## [1] "Dumai"     "Pekanbaru" "Siak"
sort(unique(pelanggan$status))
## [1] "Aktif"       "Tidak Aktif"

Mendeteksi Duplikasi

pelanggan[
  duplicated(pelanggan$customer_id) |
  duplicated(
    pelanggan$customer_id,
    fromLast = TRUE
  ),
]

Pada dataset ini M110 muncul dua kali dengan informasi yang identik.

Karena customer_id dirancang sebagai identifier unik pelanggan, salah satu kemunculan dapat dihapus.

pelanggan <- pelanggan[
  !duplicated(pelanggan$customer_id),
]

rownames(pelanggan) <- NULL

dim(pelanggan)
## [1] 13  6

Memeriksa Aturan Domain

Untuk contoh ini digunakan aturan:

  • usia yang masuk akal: 15–100 tahun;
  • pendapatan tidak boleh negatif.
pelanggan[
  pelanggan$usia < 15 |
  pelanggan$usia > 100,
]

Nilai usia 145 merupakan salah satu kesalahan input.

Misalkan setelah diperiksa pada formulir sumber, usia yang benar adalah 45.

pelanggan$usia[
  pelanggan$customer_id == "M105"
] <- 45

Pemeriksaan Pendapatan

pelanggan[
  pelanggan$pendapatan < 0,
]

Tidak ditemukan pendapatan negatif.

Nilai yang melanggar aturan awal tidak boleh langsung diubah tanpa memahami sumber data. Koreksi sebaiknya dilakukan jika nilai yang benar dapat diverifikasi.

Membuat Log Perubahan

log_perubahan <- data.frame(

  tahap = c(
    "Standardisasi",
    "Standardisasi",
    "Deduplikasi",
    "Koreksi domain"
  ),

  atribut = c(
    "kota",
    "status",
    "customer_id",
    "usia"
  ),

  tindakan = c(
    "Menyamakan PKU dan variasi kapitalisasi menjadi Pekanbaru",
    "Menyamakan ACTIVE/A/aktif menjadi Aktif dan nonaktif menjadi Tidak Aktif",
    "Menghapus kemunculan kedua M110",
    "Usia M105 dikoreksi dari 145 menjadi 45 berdasarkan sumber"
  ),

  stringsAsFactors = FALSE
)

log_perubahan

4. Penanganan Missing Values

Mengidentifikasi Lokasi Missing

colSums(
  is.na(pelanggan)
)
## customer_id        nama        usia  pendapatan        kota      status 
##           0           0           2           2           1           0
pelanggan[
  !complete.cases(pelanggan),
]

Strategi 1 — Menghapus Baris

pelanggan_complete <-
  pelanggan[
    complete.cases(pelanggan),
  ]

nrow(pelanggan)
## [1] 13
nrow(pelanggan_complete)
## [1] 8

Persentase Baris yang Hilang

round(
  (
    1 -
    nrow(pelanggan_complete) /
    nrow(pelanggan)
  ) * 100,
  2
)
## [1] 38.46

Menghapus seluruh baris yang memiliki missing value dapat membuang banyak informasi. Keputusan ini harus mempertimbangkan mekanisme missingness dan tujuan analisis.

Strategi 2 — Imputasi Mean dan Median

Pendapatan memiliki nilai ekstrem sehingga mean dan median perlu dibandingkan.

mean_pendapatan <- mean(
  pelanggan$pendapatan,
  na.rm = TRUE
)

median_pendapatan <- median(
  pelanggan$pendapatan,
  na.rm = TRUE
)

mean_pendapatan
## [1] 11254545
median_pendapatan
## [1] 4900000

Karena terdapat nilai pendapatan yang sangat besar, median lebih robust terhadap nilai ekstrem.

Imputasi Pendapatan

pelanggan$pendapatan_imputasi <-
  pelanggan$pendapatan

pelanggan$pendapatan_imputasi[
  is.na(
    pelanggan$pendapatan_imputasi
  )
] <- median_pendapatan

Imputasi Usia

median_usia <- median(
  pelanggan$usia,
  na.rm = TRUE
)

pelanggan$usia_imputasi <-
  pelanggan$usia

pelanggan$usia_imputasi[
  is.na(
    pelanggan$usia_imputasi
  )
] <- median_usia
pelanggan[
  ,
  c(
    "customer_id",
    "usia",
    "usia_imputasi",
    "pendapatan",
    "pendapatan_imputasi"
  )
]

Strategi 3 — Imputasi Kategori

Untuk kota yang tidak diketahui, digunakan kategori eksplisit "Tidak diketahui".

pelanggan$kota_imputasi <-
  pelanggan$kota

pelanggan$kota_imputasi[
  is.na(
    pelanggan$kota_imputasi
  )
] <- "Tidak diketahui"

table(
  pelanggan$kota_imputasi,
  useNA = "ifany"
)
## 
##           Dumai       Pekanbaru            Siak Tidak diketahui 
##               4               7               1               1

Menambahkan Indikator Missing

pelanggan$pendapatan_missing <-
  as.integer(
    is.na(
      pelanggan$pendapatan
    )
  )

table(
  pelanggan$pendapatan_missing
)
## 
##  0  1 
## 11  2

Indikator missing berguna karena setelah imputasi, informasi bahwa nilai aslinya hilang tidak sepenuhnya hilang dari dataset.

Membandingkan Distribusi Sebelum dan Sesudah Imputasi

par(
  mfrow = c(1, 2)
)

hist(
  pelanggan$pendapatan,
  main = "Sebelum Imputasi",
  xlab = "Pendapatan",
  col = "#f8c9db",
  breaks = 8
)

hist(
  pelanggan$pendapatan_imputasi,
  main = "Sesudah Imputasi Median",
  xlab = "Pendapatan",
  col = "#f48fb1",
  breaks = 8
)

par(
  mfrow = c(1, 1)
)

Setelah imputasi, evaluasi tidak berhenti hanya karena jumlah NA menjadi nol. Distribusi sebelum dan sesudah imputasi harus dibandingkan.

5. Penanganan Outlier

Visualisasi dengan Boxplot

boxplot(
  pelanggan$pendapatan_imputasi,
  horizontal = TRUE,
  col = "#f6a9c3",
  main = "Boxplot Pendapatan",
  xlab = "Pendapatan"
)

Menghitung Batas IQR

q1 <- quantile(
  pelanggan$pendapatan_imputasi,
  0.25
)

q3 <- quantile(
  pelanggan$pendapatan_imputasi,
  0.75
)

iqr <- IQR(
  pelanggan$pendapatan_imputasi
)

batas_bawah <-
  q1 - 1.5 * iqr

batas_atas <-
  q3 + 1.5 * iqr

c(
  Q1 = q1,
  Q3 = q3,
  IQR = iqr,
  batas_bawah = batas_bawah,
  batas_atas = batas_atas
)
##          Q1.25%          Q3.75%             IQR batas_bawah.25%  batas_atas.75% 
##         4700000         5200000          500000         3950000         5950000

Menandai Kandidat Outlier

pelanggan$outlier_pendapatan <-

  pelanggan$pendapatan_imputasi <
  batas_bawah |

  pelanggan$pendapatan_imputasi >
  batas_atas

pelanggan[
  pelanggan$outlier_pendapatan,
  c(
    "customer_id",
    "nama",
    "pendapatan_imputasi"
  )
]

Mengevaluasi Tindakan

Outlier tidak otomatis dihapus.

Nilai ekstrem dapat merupakan:

  1. kesalahan input;
  2. observasi valid tetapi ekstrem;
  3. anggota populasi yang berbeda.

Dalam contoh ini pendapatan ekstrem dipertahankan.

Sebagai demonstrasi, dibuat versi winsorized.

pelanggan$pendapatan_winsor <-

  pmin(
    pmax(
      pelanggan$pendapatan_imputasi,
      batas_bawah
    ),
    batas_atas
  )

pelanggan[
  pelanggan$outlier_pendapatan,
  c(
    "customer_id",
    "pendapatan_imputasi",
    "pendapatan_winsor"
  )
]

Winsorizing hanya digunakan sebagai demonstrasi. Tindakan terhadap outlier harus disesuaikan dengan tujuan analisis dan didokumentasikan.

6. Transformasi Data

Normalisasi Min–Max

Rumus:

\[ x' = \frac{x-\min(x)} {\max(x)-\min(x)} \]

minmax <- function(x) {

  if (
    all(is.na(x))
  ) {
    return(
      rep(
        NA_real_,
        length(x)
      )
    )
  }

  rentang <-
    max(
      x,
      na.rm = TRUE
    ) -
    min(
      x,
      na.rm = TRUE
    )

  if (
    rentang == 0
  ) {
    return(
      rep(
        0,
        length(x)
      )
    )
  }

  (
    x -
    min(
      x,
      na.rm = TRUE
    )
  ) /
    rentang
}
pelanggan$usia_minmax <-
  minmax(
    pelanggan$usia_imputasi
  )

pelanggan$pendapatan_minmax <-
  minmax(
    pelanggan$pendapatan_imputasi
  )

pelanggan[
  ,
  c(
    "customer_id",
    "usia_imputasi",
    "usia_minmax",
    "pendapatan_imputasi",
    "pendapatan_minmax"
  )
]

Normalisasi Z-Score

Rumus:

\[ z = \frac{x-\bar{x}}{s} \]

pelanggan$usia_z <-
  as.numeric(
    scale(
      pelanggan$usia_imputasi
    )
  )

pelanggan$pendapatan_z <-
  as.numeric(
    scale(
      pelanggan$pendapatan_imputasi
    )
  )

round(
  pelanggan[
    ,
    c(
      "usia_z",
      "pendapatan_z"
    )
  ],
  3
)

Decimal Scaling

decimal_scale <- function(x) {

  maks <-
    max(
      abs(x),
      na.rm = TRUE
    )

  if (
    maks == 0
  ) {
    return(x)
  }

  j <-
    ceiling(
      log10(
        maks + 1
      )
    )

  x /
    (
      10 ^ j
    )
}
pelanggan$pendapatan_decimal <-

  decimal_scale(
    pelanggan$pendapatan_imputasi
  )

range(
  pelanggan$pendapatan_decimal,
  na.rm = TRUE
)
## [1] 0.042 0.750

Membandingkan Metode Transformasi

transformasi <-
  pelanggan[
    ,
    c(
      "customer_id",
      "pendapatan_imputasi",
      "pendapatan_minmax",
      "pendapatan_z",
      "pendapatan_decimal"
    )
  ]

transformasi

Dampak Outlier terhadap Normalisasi

pelanggan$pendapatan_winsor_minmax <-

  minmax(
    pelanggan$pendapatan_winsor
  )

plot(
  pelanggan$pendapatan_minmax,
  pelanggan$pendapatan_winsor_minmax,
  pch = 19,
  col = "#c2185b",
  xlab = "Min-Max Data Asli",
  ylab = "Min-Max Data Winsorized",
  main =
    "Dampak Outlier terhadap Normalisasi"
)

abline(
  0,
  1,
  col = "#f6a9c3",
  lty = 2
)

Outlier dapat membuat sebagian besar nilai lain menjadi sangat dekat dengan nol pada normalisasi min–max. Karena itu, deteksi outlier penting sebelum memilih transformasi.

7. Integrasi Data

Selain data pelanggan, dibuat sumber data transaksi terpisah.

Data Transaksi

transaksi_raw <- data.frame(

  cust_code = c(
    "M101","M102","M103","M104",
    "M105","M106","M107","M108",
    "M109","M110","M115"
  ),

  jumlah_transaksi = c(
    4,6,3,8,2,5,15,4,7,9,3
  ),

  total_purchase = c(
    1200000,
    1800000,
    950000,
    2600000,
    700000,
    1500000,
    8200000,
    1400000,
    3100000,
    4200000,
    900000
  ),

  stringsAsFactors = FALSE
)

transaksi_raw

Memeriksa Kunci pada Kedua Sumber

sum(
  duplicated(
    pelanggan$customer_id
  )
)
## [1] 0
sum(
  duplicated(
    transaksi_raw$cust_code
  )
)
## [1] 0

Mencari Identifier yang Tidak Memiliki Pasangan

setdiff(
  pelanggan$customer_id,
  transaksi_raw$cust_code
)
## [1] "M112" "M113" "M114"
setdiff(
  transaksi_raw$cust_code,
  pelanggan$customer_id
)
## [1] "M115"

setdiff() digunakan untuk menemukan identifier yang hanya muncul pada salah satu sumber data.

Menyelaraskan Nama Identifier

transaksi <- transaksi_raw

names(transaksi)[
  names(transaksi) == "cust_code"
] <- "customer_id"

Melakukan Left Join

data_terintegrasi <-

  merge(
    pelanggan,
    transaksi,
    by = "customer_id",
    all.x = TRUE
  )

data_terintegrasi[
  ,
  c(
    "customer_id",
    "nama",
    "jumlah_transaksi",
    "total_purchase"
  )
]

all.x = TRUE mempertahankan seluruh pelanggan walaupun pelanggan tersebut tidak memiliki pasangan transaksi.

Memvalidasi Hasil Integrasi

Jumlah Baris

c(
  sebelum =
    nrow(pelanggan),

  sesudah =
    nrow(data_terintegrasi)
)
## sebelum sesudah 
##      13      13

Apakah Identifier Tetap Unik?

sum(
  duplicated(
    data_terintegrasi$customer_id
  )
)
## [1] 0

Missing Value Akibat Integrasi

colSums(
  is.na(
    data_terintegrasi[
      ,
      c(
        "jumlah_transaksi",
        "total_purchase"
      )
    ]
  )
)
## jumlah_transaksi   total_purchase 
##                3                3

Pelanggan Tanpa Transaksi

data_terintegrasi[
  is.na(
    data_terintegrasi$jumlah_transaksi
  ),
  c(
    "customer_id",
    "nama"
  )
]

Mengisi Nol atau Mempertahankan NA?

NA setelah integrasi dapat memiliki arti yang berbeda.

  • pelanggan memang belum pernah bertransaksi;
  • data transaksi tidak tersedia;
  • identifier gagal dipadankan.

Karena itu NA tidak boleh otomatis diubah menjadi 0.

Sebagai contoh, apabila definisi data telah dikonfirmasi bahwa NA berarti pelanggan belum pernah bertransaksi:

data_terintegrasi$jumlah_transaksi_final <-
  data_terintegrasi$jumlah_transaksi

data_terintegrasi$total_purchase_final <-
  data_terintegrasi$total_purchase

data_terintegrasi$jumlah_transaksi_final[
  is.na(
    data_terintegrasi$jumlah_transaksi_final
  )
] <- 0

data_terintegrasi$total_purchase_final[
  is.na(
    data_terintegrasi$total_purchase_final
  )
] <- 0

8. Dataset Akhir dan Evaluasi

Memilih Atribut Akhir

data_final <-

  data_terintegrasi[
    ,
    c(
      "customer_id",
      "nama",
      "usia_imputasi",
      "kota_imputasi",
      "status",
      "pendapatan_imputasi",
      "pendapatan_missing",
      "outlier_pendapatan",
      "usia_minmax",
      "pendapatan_minmax",
      "jumlah_transaksi_final",
      "total_purchase_final"
    )
  ]

names(data_final)[
  names(data_final) == "usia_imputasi"
] <- "usia"

names(data_final)[
  names(data_final) == "kota_imputasi"
] <- "kota"

data_final

Pemeriksaan Akhir

audit_akhir <-
  audit_data(
    data_final
  )

audit_akhir

Duplikasi Identifier

sum(
  duplicated(
    data_final$customer_id
  )
)
## [1] 0

Missing Value

colSums(
  is.na(
    data_final
  )
)
##            customer_id                   nama                   usia 
##                      0                      0                      0 
##                   kota                 status    pendapatan_imputasi 
##                      0                      0                      0 
##     pendapatan_missing     outlier_pendapatan            usia_minmax 
##                      0                      0                      0 
##      pendapatan_minmax jumlah_transaksi_final   total_purchase_final 
##                      0                      0                      0

Parameter Transformasi dan Data Training

Dalam machine learning, parameter preprocessing seperti median, mean, standar deviasi, nilai minimum, dan maksimum sebaiknya dihitung dari data training saja.

Hal ini mencegah data leakage, yaitu informasi dari data testing secara tidak sengaja masuk ke proses pembentukan model.

set.seed(2026)

n_train <-
  floor(
    0.8 *
    nrow(data_final)
  )

id_train <-
  sample(
    seq_len(
      nrow(data_final)
    ),
    n_train
  )

train_data <-
  data_final[
    id_train,
  ]

test_data <-
  data_final[
    -id_train,
  ]

Parameter Median dari Training

median_train_income <-

  median(
    train_data$pendapatan_imputasi,
    na.rm = TRUE
  )

median_train_income
## [1] 4900000

Parameter Min-Max dari Training

min_train_income <-
  min(
    train_data$pendapatan_imputasi,
    na.rm = TRUE
  )

max_train_income <-
  max(
    train_data$pendapatan_imputasi,
    na.rm = TRUE
  )

c(
  min_train_income,
  max_train_income
)
## [1] 4.2e+06 7.5e+07

Transformasi Training

train_data$income_minmax <-

  (
    train_data$pendapatan_imputasi -
    min_train_income
  ) /
  (
    max_train_income -
    min_train_income
  )

head(
  train_data[
    ,
    c(
      "customer_id",
      "pendapatan_imputasi",
      "income_minmax"
    )
  ]
)

Jika minimum, maksimum, mean, median, atau standar deviasi dihitung dari gabungan training dan testing, informasi testing ikut memengaruhi proses preprocessing.

Membandingkan Kondisi Sebelum dan Sesudah

perbandingan <-

  data.frame(

    indikator = c(
      "Jumlah baris",
      "Duplikasi customer_id",
      "Total missing value",
      "Kategori kota unik",
      "Kategori status unik"
    ),

    sebelum = c(

      nrow(
        pelanggan_raw
      ),

      sum(
        duplicated(
          pelanggan_raw$customer_id
        )
      ),

      sum(
        is.na(
          pelanggan_raw
        )
      ),

      length(
        unique(
          pelanggan_raw$kota
        )
      ),

      length(
        unique(
          pelanggan_raw$status
        )
      )
    ),

    sesudah = c(

      nrow(
        data_final
      ),

      sum(
        duplicated(
          data_final$customer_id
        )
      ),

      sum(
        is.na(
          data_final
        )
      ),

      length(
        unique(
          data_final$kota
        )
      ),

      length(
        unique(
          data_final$status
        )
      )
    )
  )

perbandingan

9. Pertanyaan Refleksi

1. Apakah dataset tanpa missing value selalu lebih berkualitas?

Tidak.

Dataset tanpa missing value belum tentu lebih berkualitas karena nilai yang hilang mungkin telah dihapus atau diimputasi dengan cara yang tidak tepat. Mengubah seluruh missing value menjadi nilai tertentu dapat menghilangkan informasi mengenai ketidakpastian data.

Kualitas data harus dilihat secara lebih luas, termasuk accuracy, completeness, consistency, timeliness, believability, dan interpretability.

Jadi, dataset dengan sedikit missing value tetapi banyak nilai salah dapat lebih buruk daripada dataset yang memiliki beberapa missing value namun nilai lainnya akurat.

2. Mengapa outlier tidak boleh otomatis dihapus?

Outlier tidak selalu merupakan kesalahan.

Nilai ekstrem dapat merupakan:

  • kesalahan input;
  • observasi yang benar tetapi jarang terjadi;
  • anggota populasi yang berbeda.

Misalnya pendapatan seseorang yang jauh lebih tinggi daripada mayoritas pelanggan belum tentu salah. Jika nilai tersebut benar, menghapusnya justru menghilangkan informasi penting.

Karena itu outlier perlu dideteksi, diperiksa, kemudian dievaluasi. Materi juga menekankan tiga kemungkinan tindakan: koreksi jika terbukti salah, mempertahankan jika valid, atau memisahkan segmen jika berasal dari populasi berbeda.

3. Bagaimana preprocessing dapat menimbulkan bias?

Preprocessing dapat menimbulkan bias apabila keputusan cleaning membuat sebagian kelompok lebih banyak kehilangan atau diubah datanya daripada kelompok lain.

Contohnya:

  • menghapus semua baris dengan missing value;
  • menggunakan satu metode imputasi untuk semua kelompok;
  • menghapus outlier yang sebenarnya merupakan kelompok minoritas;
  • melakukan standardisasi kategori berdasarkan asumsi yang salah.

Jika kelompok tertentu lebih sering memiliki missing value lalu barisnya lebih banyak dihapus, distribusi sampel akhir dapat berbeda dari populasi aslinya.

Karena itu preprocessing bukan sekadar proses teknis, tetapi merupakan serangkaian keputusan yang dapat memengaruhi hasil analisis.

4. Mengapa parameter imputasi dan transformasi seharusnya dihitung dari data pelatihan?

Karena penggunaan seluruh dataset dapat menyebabkan data leakage.

Misalnya kita menghitung median, mean, standar deviasi, minimum, atau maksimum menggunakan data training dan testing sekaligus. Informasi dari testing kemudian ikut menentukan bagaimana data training diproses.

Akibatnya evaluasi model menjadi terlalu optimistis karena model secara tidak langsung telah memperoleh informasi dari data testing.

Prosedur yang benar adalah:

Training data → hitung parameter → gunakan parameter pada training dan testing.

Dengan demikian testing tetap menjadi data yang benar-benar belum digunakan dalam proses pembentukan model.

5. Apa risiko integrasi data jika identifier tidak unik?

Identifier yang tidak unik dapat menyebabkan many-to-many matching atau penggandaan baris saat dua dataset digabungkan.

Contohnya, jika satu customer_id muncul dua kali pada tabel pelanggan dan dua kali pada tabel transaksi, proses join dapat menghasilkan empat kombinasi baris.

Akibatnya:

  • jumlah observasi menjadi terlalu besar;
  • total transaksi dapat terhitung lebih dari sekali;
  • nilai agregat menjadi salah;
  • analisis statistik dapat bias;
  • hubungan antar-record menjadi ambigu.

Karena itu sebelum integrasi harus diperiksa apakah identifier memang unik pada tabel yang seharusnya memiliki satu record per entitas. Materi juga secara eksplisit memeriksa duplikasi key sebelum melakukan integrasi.

10. Ringkasan

Alur preprocessing yang dilakukan adalah:

  1. memahami struktur dan kualitas awal data;
  2. melakukan audit data;
  3. membuat salinan data kerja;
  4. membersihkan spasi dan kapitalisasi;
  5. menyeragamkan kategori;
  6. mendeteksi dan menangani duplikasi;
  7. memeriksa aturan domain;
  8. membuat log perubahan;
  9. mengidentifikasi missing value;
  10. membandingkan strategi penanganan missing;
  11. melakukan imputasi;
  12. membuat indikator missing;
  13. mendeteksi outlier menggunakan IQR;
  14. mengevaluasi tindakan terhadap outlier;
  15. melakukan winsorizing sebagai demonstrasi;
  16. melakukan min-max normalization;
  17. melakukan z-score;
  18. melakukan decimal scaling;
  19. membandingkan metode transformasi;
  20. mengevaluasi dampak outlier terhadap normalisasi;
  21. memeriksa identifier pada dua sumber;
  22. melakukan integrasi data;
  23. memvalidasi hasil integrasi;
  24. membangun dataset akhir;
  25. melakukan audit akhir.

Kesimpulan

Data preprocessing bukan sekadar membuat dataset menjadi “bersih”.

Preprocessing merupakan proses pengambilan keputusan. Setiap perubahan terhadap data harus memiliki alasan, mempertimbangkan konteks data, dapat ditelusuri melalui dokumentasi, dan dievaluasi kembali setelah dilakukan.

Dataset yang baik bukan hanya dataset yang tidak memiliki NA, tetapi dataset yang akurat, konsisten, dapat dipercaya, dapat dipahami, dan sesuai dengan tujuan analisis.

11. Menyimpan Hasil

write.csv(
  data_final,
  "data_final_preprocessing_baru.csv",
  row.names = FALSE
)

write.csv(
  log_perubahan,
  "log_perubahan_preprocessing_baru.csv",
  row.names = FALSE
)

Daftar Pustaka

Han, J., Kamber, M., & Pei, J. (2012). Data Mining: Concepts and Techniques (3rd ed.). Morgan Kaufmann. Chapter 3: Data Preprocessing.

LS0tDQp0aXRsZTogIkRhdGEgUHJlcHJvY2Vzc2luZyBNZW5nZ3VuYWthbiBSIg0KYXV0aG9yOiAiSW5kYWggQXN0aXphIg0KZGF0ZTogIjI0MDMxMTQwMTAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgdGhlbWU6IGZsYXRseQ0KICAgIGhpZ2hsaWdodDogYnJlZXplZGFyaw0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQo8c3R5bGU+DQoNCkBpbXBvcnQgdXJsKCdodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2NzczI/ZmFtaWx5PVF1aWNrc2FuZDp3Z2h0QDQwMDs1MDA7NjAwOzcwMCZmYW1pbHk9UG9wcGluczp3Z2h0QDMwMDs0MDA7NTAwOzYwMDs3MDAmZGlzcGxheT1zd2FwJyk7DQoNCmJvZHkgew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjRjhGN0YzOw0KICBmb250LWZhbWlseTogJ1F1aWNrc2FuZCcsICdQb3BwaW5zJywgc2Fucy1zZXJpZjsNCiAgY29sb3I6ICM1YTNjNDg7DQp9DQoNCmgxLCBoMiwgaDMsIGg0IHsNCiAgZm9udC1mYW1pbHk6ICdQb3BwaW5zJywgJ1F1aWNrc2FuZCcsIHNhbnMtc2VyaWY7DQogIGZvbnQtd2VpZ2h0OiA2MDA7DQp9DQoNCmgxIHsNCiAgY29sb3I6ICNjMjE4NWI7DQogIGJvcmRlci1ib3R0b206IDNweCBzb2xpZCAjZjRiN2NkOw0KICBwYWRkaW5nLWJvdHRvbTogOHB4Ow0KfQ0KDQpoMiB7DQogIGNvbG9yOiAjYWQxNDU3Ow0KICBtYXJnaW4tdG9wOiAzMHB4Ow0KICBib3JkZXItbGVmdDogNXB4IHNvbGlkICNmNmM2ZGE7DQogIHBhZGRpbmctbGVmdDogMTBweDsNCn0NCg0KaDMgew0KICBjb2xvcjogI2Q2MzM2YzsNCn0NCg0KcCwgbGkgew0KICBmb250LWZhbWlseTogJ1F1aWNrc2FuZCcsIHNhbnMtc2VyaWY7DQogIGZvbnQtc2l6ZTogMTZweDsNCiAgbGluZS1oZWlnaHQ6IDEuNjU7DQp9DQoNCmNvZGUgew0KICBmb250LWZhbWlseTogJ0ZpcmEgQ29kZScsICdDb25zb2xhcycsIG1vbm9zcGFjZTsNCiAgYmFja2dyb3VuZC1jb2xvcjogI2ZkZWFmMTsNCiAgY29sb3I6ICNhZDE0NTc7DQogIGJvcmRlci1yYWRpdXM6IDZweDsNCn0NCg0KcHJlIHsNCiAgYm9yZGVyLXJhZGl1czogMTJweCAhaW1wb3J0YW50Ow0KICBib3JkZXI6IDFweCBzb2xpZCAjZjRjNmRhICFpbXBvcnRhbnQ7DQp9DQoNCi5ib3ggew0KICBwYWRkaW5nOiAyMHB4Ow0KICBib3JkZXItcmFkaXVzOiAxNnB4Ow0KICBtYXJnaW46IDE1cHggMDsNCiAgYmFja2dyb3VuZDogI2ZmZmFmYzsNCiAgYm9yZGVyLWxlZnQ6IDZweCBzb2xpZCAjZjQ4ZmIxOw0KICBib3gtc2hhZG93OiAwIDNweCAxMnB4IHJnYmEoMjE0LCA1MSwgMTA4LCAwLjEwKTsNCn0NCg0KLndhcm5pbmcgew0KICBib3JkZXItbGVmdC1jb2xvcjogI2Y2YTViMDsNCiAgYmFja2dyb3VuZDogI2ZmZjVmMjsNCn0NCg0KLnN1Y2Nlc3Mgew0KICBib3JkZXItbGVmdC1jb2xvcjogI2U3NTQ4MDsNCiAgYmFja2dyb3VuZDogI2ZkZjBmNjsNCn0NCg0KLnF1ZXN0aW9uIHsNCiAgcGFkZGluZzogMThweDsNCiAgYmFja2dyb3VuZDogI2ZkZWFmMTsNCiAgYm9yZGVyLXJhZGl1czogMTRweDsNCiAgbWFyZ2luLXRvcDogMTVweDsNCiAgYm9yZGVyOiAxcHggZGFzaGVkICNmNGI3Y2Q7DQp9DQoNCi8qIE5hdmJhciAvIFRPQyAqLw0KI1RPQyB7DQogIGJhY2tncm91bmQtY29sb3I6ICNmZmY2ZmE7DQogIGJvcmRlci1yYWRpdXM6IDE0cHg7DQogIGJvcmRlcjogMXB4IHNvbGlkICNmNmM2ZGE7DQp9DQoNCi5saXN0LWdyb3VwLWl0ZW0uYWN0aXZlLCAubGlzdC1ncm91cC1pdGVtLmFjdGl2ZTpob3ZlciB7DQogIGJhY2tncm91bmQtY29sb3I6ICNmNDhmYjEgIWltcG9ydGFudDsNCiAgYm9yZGVyLWNvbG9yOiAjZjQ4ZmIxICFpbXBvcnRhbnQ7DQp9DQoNCmEgew0KICBjb2xvcjogI2Q2MzM2YzsNCn0NCg0KYTpob3ZlciB7DQogIGNvbG9yOiAjYWQxNDU3Ow0KfQ0KDQovKiBUb21ib2wgY29kZSBmb2xkaW5nICovDQouYnRuLWRlZmF1bHQgew0KICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmJlM2VjICFpbXBvcnRhbnQ7DQogIGJvcmRlci1jb2xvcjogI2Y0YjdjZCAhaW1wb3J0YW50Ow0KICBjb2xvcjogI2FkMTQ1NyAhaW1wb3J0YW50Ow0KICBib3JkZXItcmFkaXVzOiA4cHggIWltcG9ydGFudDsNCn0NCg0KLmJ0bi1kZWZhdWx0OmhvdmVyIHsNCiAgYmFja2dyb3VuZC1jb2xvcjogI2Y2YTljMyAhaW1wb3J0YW50Ow0KICBjb2xvcjogI2ZmZmZmZiAhaW1wb3J0YW50Ow0KfQ0KDQp0YWJsZSB7DQogIGJvcmRlci1yYWRpdXM6IDEwcHg7DQogIG92ZXJmbG93OiBoaWRkZW47DQp9DQoNCnRoZWFkIHsNCiAgYmFja2dyb3VuZC1jb2xvcjogI2Y4YzlkYiAhaW1wb3J0YW50Ow0KICBjb2xvcjogIzZkMWEzYjsNCn0NCg0KPC9zdHlsZT4NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldCgNCiAgZWNobyA9IFRSVUUsDQogIHdhcm5pbmcgPSBGQUxTRSwNCiAgbWVzc2FnZSA9IEZBTFNFDQopDQoNCnNldC5zZWVkKDIwMjYpDQoNCmBgYA0KDQoNCiMgMS4gTWVtYmFuZ3VuIERhdGFzZXQgUHJha3Rpa3VtDQoNCjxkaXYgY2xhc3M9ImJveCI+DQoNClBhZGEgcHJha3Rpa3VtIGluaSBkaWd1bmFrYW4gY29udG9oIGRhdGEgcGVsYW5nZ2FuIGRhbiBkYXRhIHRyYW5zYWtzaS4NCkRhdGFzZXQgZGlidWF0IHNlY2FyYSBzZW5nYWphIGRlbmdhbiBiZWJlcmFwYSBtYXNhbGFoIGt1YWxpdGFzIGRhdGEgYWdhcg0KcHJvc2VzIHByZXByb2Nlc3NpbmcgZGFwYXQgZGlkZW1vbnN0cmFzaWthbi4NCg0KTWFzYWxhaCB5YW5nIGFrYW4gZGlhdGFzaSB5YWl0dToNCg0KLSBtaXNzaW5nIHZhbHVlOw0KLSBmb3JtYXQga2F0ZWdvcmkgeWFuZyB0aWRhayBrb25zaXN0ZW47DQotIGR1cGxpa2FzaSBpZGVudGlmaWVyOw0KLSBuaWxhaSB1c2lhIHlhbmcgdGlkYWsgbWFzdWsgYWthbDsNCi0gbmlsYWkgcGVuZGFwYXRhbiBla3N0cmVtOw0KLSBpZGVudGlmaWVyIHlhbmcgdGlkYWsgbWVtaWxpa2kgcGFzYW5nYW4ga2V0aWthIGRpbGFrdWthbiBpbnRlZ3Jhc2kuDQoNCjwvZGl2Pg0KDQojIyBEYXRhIFBlbGFuZ2dhbiBNZW50YWgNCg0KYGBge3J9DQoNCnBlbGFuZ2dhbl9yYXcgPC0gZGF0YS5mcmFtZSgNCiAgY3VzdG9tZXJfaWQgPSBjKA0KICAgICJNMTAxIiwiTTEwMiIsIk0xMDMiLCJNMTA0IiwiTTEwNSIsDQogICAgIk0xMDYiLCJNMTA3IiwiTTEwOCIsIk0xMDkiLCJNMTEwIiwNCiAgICAiTTExMCIsIk0xMTIiLCJNMTEzIiwiTTExNCINCiAgKSwNCg0KICBuYW1hID0gYygNCiAgICAiUmFuaSIsIkRpbWFzIiwiU2Fsc2EiLCJGYWphciIsIk5hZGlhIiwNCiAgICAiUml6a3kiLCJUaW8iLCJNYXlhIiwiQXJpZiIsIlB1dHJpIiwNCiAgICAiUHV0cmkiLCJLZXZpbiIsIkxhbGEiLCJCYWdhcyINCiAgKSwNCg0KICB1c2lhID0gYygNCiAgICAyMiwgMjcsIE5BLCAzMSwgMTQ1LA0KICAgIDI2LCAyOSwgMjQsIDM4LCAzMywNCiAgICAzMywgTkEsIDIxLCAzMA0KICApLA0KDQogIHBlbmRhcGF0YW4gPSBjKA0KICAgIDQyMDAwMDAsIE5BLCA1MTAwMDAwLCA1NjAwMDAwLCA0OTAwMDAwLA0KICAgIDQ4MDAwMDAsIDc1MDAwMDAwLCA0NjAwMDAwLCBOQSwgNTMwMDAwMCwNCiAgICA1MzAwMDAwLCA0NzAwMDAwLCA0NDAwMDAwLCA1MjAwMDAwDQogICksDQoNCiAga290YSA9IGMoDQogICAgIlBla2FuYmFydSIsIiBQS1UiLCJQRUtBTkJBUlUiLCJEdW1haSIsDQogICAgImR1bWFpIiwiRFVNQUkiLCJQZWthbmJhcnUgIiwiUEVLQU5CQVJVIiwNCiAgICAiU2lhayIsInBrdSIsInBrdSIsIlBla2FuYmFydSIsDQogICAgTkEsIkRVTUFJIg0KICApLA0KDQogIHN0YXR1cyA9IGMoDQogICAgIkFrdGlmIiwiQUNUSVZFIiwiYWt0aWYiLCJBIiwNCiAgICAiVGlkYWsgYWt0aWYiLCJub25ha3RpZiIsIkFLVElGIiwiQWt0aWYiLA0KICAgICJUaWRhayBBa3RpZiIsIkEiLCJBIiwiYWN0aXZlIiwNCiAgICAibm9uYWt0aWYiLCJBa3RpZiINCiAgKSwNCg0KICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCikNCg0KcGVsYW5nZ2FuX3Jhdw0KDQpgYGANCg0KIyMgTWVsaWhhdCBTdHJ1a3R1ciBEYXRhDQoNCmBgYHtyfQ0KDQpkaW0ocGVsYW5nZ2FuX3JhdykNCg0KYGBgDQoNCmBgYHtyfQ0KDQpuYW1lcyhwZWxhbmdnYW5fcmF3KQ0KDQpgYGANCg0KYGBge3J9DQoNCnN0cihwZWxhbmdnYW5fcmF3KQ0KDQpgYGANCg0KYGBge3J9DQoNCmhlYWQocGVsYW5nZ2FuX3JhdykNCg0KYGBgDQoNCmBgYHtyfQ0KDQpzdW1tYXJ5KHBlbGFuZ2dhbl9yYXcpDQoNCmBgYA0KDQo8ZGl2IGNsYXNzPSJib3giPg0KDQpTZWJlbHVtIG1lbGFrdWthbiBwZXJ1YmFoYW4sIHN0cnVrdHVyLCBkaW1lbnNpLCB0aXBlIGF0cmlidXQsIGRhbiBiZWJlcmFwYQ0Kb2JzZXJ2YXNpIGF3YWwgcGVybHUgZGlwZXJpa3NhIHRlcmxlYmloIGRhaHVsdS4NCg0KPC9kaXY+DQoNCg0KIyAyLiBLb25zZXAgZGFuIEt1YWxpdGFzIERhdGENCg0KIyMgRGltZW5zaSBLdWFsaXRhcyBEYXRhDQoNCkVuYW0gZGltZW5zaSBrdWFsaXRhcyBkYXRhIHlhbmcgZGlndW5ha2FuIGRhbGFtIHBlbWJhaGFzYW4gYWRhbGFoOg0KDQoxLiAqKkFjY3VyYWN5Kiog4oCUIGFwYWthaCBuaWxhaSBzZXN1YWkgZGVuZ2FuIGtvbmRpc2kgc2ViZW5hcm55YS4NCjIuICoqQ29tcGxldGVuZXNzKiog4oCUIGFwYWthaCBpbmZvcm1hc2kgeWFuZyBkaWJ1dHVoa2FuIHRlcnNlZGlhLg0KMy4gKipDb25zaXN0ZW5jeSoqIOKAlCBhcGFrYWggbmlsYWkgbWVuZ2d1bmFrYW4gZm9ybWF0IGRhbiBhdHVyYW4geWFuZyBrb25zaXN0ZW4uDQo0LiAqKlRpbWVsaW5lc3MqKiDigJQgYXBha2FoIGRhdGEgY3VrdXAgbXV0YWtoaXIuDQo1LiAqKkJlbGlldmFiaWxpdHkqKiDigJQgYXBha2FoIGRhdGEgZGFwYXQgZGlwZXJjYXlhLg0KNi4gKipJbnRlcnByZXRhYmlsaXR5Kiog4oCUIGFwYWthaCBhcnRpIGRhbiBmb3JtYXQgZGF0YSBtdWRhaCBkaXBhaGFtaS4NCg0KDQojIyBNZW5ndWt1ciBNaXNzaW5nIFZhbHVlDQoNCmBgYHtyfQ0KDQpjb2xTdW1zKGlzLm5hKHBlbGFuZ2dhbl9yYXcpKQ0KDQpgYGANCg0KYGBge3J9DQoNCnJvdW5kKA0KICBjb2xNZWFucyhpcy5uYShwZWxhbmdnYW5fcmF3KSkgKiAxMDAsDQogIDINCikNCg0KYGBgDQoNCiMjIE1lbWVyaWtzYSBEdXBsaWthc2kNCg0KYGBge3J9DQoNCnN1bShkdXBsaWNhdGVkKHBlbGFuZ2dhbl9yYXcpKQ0KDQpgYGANCg0KYGBge3J9DQoNCnN1bShkdXBsaWNhdGVkKHBlbGFuZ2dhbl9yYXckY3VzdG9tZXJfaWQpKQ0KDQpgYGANCg0KIyMgTWVtZXJpa3NhIEthdGVnb3JpDQoNCmBgYHtyfQ0KDQpzb3J0KHVuaXF1ZShwZWxhbmdnYW5fcmF3JGtvdGEpKQ0KDQpgYGANCg0KYGBge3J9DQoNCnNvcnQodW5pcXVlKHBlbGFuZ2dhbl9yYXckc3RhdHVzKSkNCg0KYGBgDQoNCiMjIE1lbWVyaWtzYSBSZW50YW5nIE51bWVyaWsNCg0KYGBge3J9DQoNCnJhbmdlKA0KICBwZWxhbmdnYW5fcmF3JHVzaWEsDQogIG5hLnJtID0gVFJVRQ0KKQ0KDQpgYGANCg0KYGBge3J9DQoNCnJhbmdlKA0KICBwZWxhbmdnYW5fcmF3JHBlbmRhcGF0YW4sDQogIG5hLnJtID0gVFJVRQ0KKQ0KDQpgYGANCg0KIyMgRnVuZ3NpIEF1ZGl0IERhdGENCg0KYGBge3J9DQoNCmF1ZGl0X2RhdGEgPC0gZnVuY3Rpb24oZGF0YSkgew0KDQogIGRhdGEuZnJhbWUoDQogICAgYXRyaWJ1dCA9IG5hbWVzKGRhdGEpLA0KDQogICAgdGlwZSA9IHNhcHBseSgNCiAgICAgIGRhdGEsDQogICAgICBmdW5jdGlvbih4KSBjbGFzcyh4KVsxXQ0KICAgICksDQoNCiAgICBqdW1sYWhfbWlzc2luZyA9IHNhcHBseSgNCiAgICAgIGRhdGEsDQogICAgICBmdW5jdGlvbih4KSBzdW0oaXMubmEoeCkpDQogICAgKSwNCg0KICAgIHBlcnNlbl9taXNzaW5nID0gcm91bmQoDQogICAgICBzYXBwbHkoDQogICAgICAgIGRhdGEsDQogICAgICAgIGZ1bmN0aW9uKHgpDQogICAgICAgICAgbWVhbihpcy5uYSh4KSkgKiAxMDANCiAgICAgICksDQogICAgICAyDQogICAgKSwNCg0KICAgIGp1bWxhaF91bmlrID0gc2FwcGx5KA0KICAgICAgZGF0YSwNCiAgICAgIGZ1bmN0aW9uKHgpDQogICAgICAgIGxlbmd0aCh1bmlxdWUoeCkpDQogICAgKSwNCg0KICAgIHJvdy5uYW1lcyA9IE5VTEwNCiAgKQ0KfQ0KDQphdWRpdF9hd2FsIDwtIGF1ZGl0X2RhdGEoDQogIHBlbGFuZ2dhbl9yYXcNCikNCg0KYXVkaXRfYXdhbA0KDQpgYGANCg0KPGRpdiBjbGFzcz0iYm94IHdhcm5pbmciPg0KDQoqKkludGVycHJldGFzaToqKiBQZW1lcmlrc2FhbiBhd2FsIG1lbnVuanVra2FuIGtvbmRpc2kgc2V0aWFwIHZhcmlhYmVsIGRhbGFtIGRhdGFzZXQsIG1lbGlwdXRpIHRpcGUgZGF0YSwganVtbGFoIGRhbiBwZXJzZW50YXNlIG5pbGFpIHlhbmcgaGlsYW5nLCBzZXJ0YSBqdW1sYWggbmlsYWkgdW5pay4gSW5mb3JtYXNpIHRlcnNlYnV0IGRhcGF0IGRpZ3VuYWthbiB1bnR1ayBtZW5nZXRhaHVpIHZhcmlhYmVsIHlhbmcgcGVybHUgZGlwZXJpa3NhIGxlYmloIGxhbmp1dCBzZWJlbHVtIGRpbGFrdWthbiBwcm9zZXMgcGVtYmVyc2loYW4gZGFuIHBlbmdvbGFoYW4gZGF0YS4NCg0KPC9kaXY+DQoNCg0KIyAzLiBJbXBsZW1lbnRhc2kgRGF0YSBDbGVhbmluZw0KDQojIyBNZW1idWF0IFNhbGluYW4gS2VyamENCg0KYGBge3J9DQoNCnBlbGFuZ2dhbiA8LSBwZWxhbmdnYW5fcmF3DQoNCmBgYA0KDQojIyBNZW1iZXJzaWhrYW4gU3Bhc2kgZGFuIEthcGl0YWxpc2FzaQ0KDQpgYGB7cn0NCg0KcGVsYW5nZ2FuJGtvdGEgPC0gdHJpbXdzKA0KICBwZWxhbmdnYW4ka290YQ0KKQ0KDQpwZWxhbmdnYW4kc3RhdHVzIDwtIHRyaW13cygNCiAgcGVsYW5nZ2FuJHN0YXR1cw0KKQ0KDQpwZWxhbmdnYW4ka290YSA8LSB0b2xvd2VyKA0KICBwZWxhbmdnYW4ka290YQ0KKQ0KDQpwZWxhbmdnYW4kc3RhdHVzIDwtIHRvbG93ZXIoDQogIHBlbGFuZ2dhbiRzdGF0dXMNCikNCg0Kc29ydCh1bmlxdWUocGVsYW5nZ2FuJGtvdGEpKQ0KDQpgYGANCg0KYGBge3J9DQoNCnNvcnQodW5pcXVlKHBlbGFuZ2dhbiRzdGF0dXMpKQ0KDQpgYGANCg0KDQojIyBNZW55ZXJhZ2Fta2FuIEthdGVnb3JpDQoNCmBgYHtyfQ0KDQpwZWxhbmdnYW4ka290YVsNCiAgcGVsYW5nZ2FuJGtvdGEgJWluJQ0KICAgIGMoInBrdSIsICJwZWthbmJhcnUiKQ0KXSA8LSAiUGVrYW5iYXJ1Ig0KDQpwZWxhbmdnYW4ka290YVsNCiAgcGVsYW5nZ2FuJGtvdGEgPT0gImR1bWFpIg0KXSA8LSAiRHVtYWkiDQoNCnBlbGFuZ2dhbiRrb3RhWw0KICBwZWxhbmdnYW4ka290YSA9PSAic2lhayINCl0gPC0gIlNpYWsiDQoNCmBgYA0KDQpgYGB7cn0NCg0KcGVsYW5nZ2FuJHN0YXR1c1sNCiAgcGVsYW5nZ2FuJHN0YXR1cyAlaW4lDQogICAgYygiYWt0aWYiLCJhY3RpdmUiLCJhIikNCl0gPC0gIkFrdGlmIg0KDQpwZWxhbmdnYW4kc3RhdHVzWw0KICBwZWxhbmdnYW4kc3RhdHVzICVpbiUNCiAgICBjKCJ0aWRhayBha3RpZiIsIm5vbmFrdGlmIikNCl0gPC0gIlRpZGFrIEFrdGlmIg0KDQpgYGANCg0KYGBge3J9DQoNCnNvcnQodW5pcXVlKHBlbGFuZ2dhbiRrb3RhKSkNCg0KYGBgDQoNCmBgYHtyfQ0KDQpzb3J0KHVuaXF1ZShwZWxhbmdnYW4kc3RhdHVzKSkNCg0KYGBgDQoNCg0KIyMgTWVuZGV0ZWtzaSBEdXBsaWthc2kNCg0KYGBge3J9DQoNCnBlbGFuZ2dhblsNCiAgZHVwbGljYXRlZChwZWxhbmdnYW4kY3VzdG9tZXJfaWQpIHwNCiAgZHVwbGljYXRlZCgNCiAgICBwZWxhbmdnYW4kY3VzdG9tZXJfaWQsDQogICAgZnJvbUxhc3QgPSBUUlVFDQogICksDQpdDQoNCmBgYA0KDQpQYWRhIGRhdGFzZXQgaW5pIGBNMTEwYCBtdW5jdWwgZHVhIGthbGkgZGVuZ2FuIGluZm9ybWFzaSB5YW5nIGlkZW50aWsuDQoNCkthcmVuYSBgY3VzdG9tZXJfaWRgIGRpcmFuY2FuZyBzZWJhZ2FpIGlkZW50aWZpZXIgdW5payBwZWxhbmdnYW4sIHNhbGFoIHNhdHUNCmtlbXVuY3VsYW4gZGFwYXQgZGloYXB1cy4NCg0KYGBge3J9DQoNCnBlbGFuZ2dhbiA8LSBwZWxhbmdnYW5bDQogICFkdXBsaWNhdGVkKHBlbGFuZ2dhbiRjdXN0b21lcl9pZCksDQpdDQoNCnJvd25hbWVzKHBlbGFuZ2dhbikgPC0gTlVMTA0KDQpkaW0ocGVsYW5nZ2FuKQ0KDQpgYGANCg0KDQojIyBNZW1lcmlrc2EgQXR1cmFuIERvbWFpbg0KDQpVbnR1ayBjb250b2ggaW5pIGRpZ3VuYWthbiBhdHVyYW46DQoNCi0gdXNpYSB5YW5nIG1hc3VrIGFrYWw6IDE14oCTMTAwIHRhaHVuOw0KLSBwZW5kYXBhdGFuIHRpZGFrIGJvbGVoIG5lZ2F0aWYuDQoNCmBgYHtyfQ0KDQpwZWxhbmdnYW5bDQogIHBlbGFuZ2dhbiR1c2lhIDwgMTUgfA0KICBwZWxhbmdnYW4kdXNpYSA+IDEwMCwNCl0NCg0KYGBgDQoNCk5pbGFpIHVzaWEgYDE0NWAgbWVydXBha2FuIHNhbGFoIHNhdHUga2VzYWxhaGFuIGlucHV0Lg0KDQpNaXNhbGthbiBzZXRlbGFoIGRpcGVyaWtzYSBwYWRhIGZvcm11bGlyIHN1bWJlciwgdXNpYSB5YW5nIGJlbmFyIGFkYWxhaA0KYDQ1YC4NCg0KYGBge3J9DQoNCnBlbGFuZ2dhbiR1c2lhWw0KICBwZWxhbmdnYW4kY3VzdG9tZXJfaWQgPT0gIk0xMDUiDQpdIDwtIDQ1DQoNCmBgYA0KDQojIyMgUGVtZXJpa3NhYW4gUGVuZGFwYXRhbg0KDQpgYGB7cn0NCg0KcGVsYW5nZ2FuWw0KICBwZWxhbmdnYW4kcGVuZGFwYXRhbiA8IDAsDQpdDQoNCmBgYA0KDQpUaWRhayBkaXRlbXVrYW4gcGVuZGFwYXRhbiBuZWdhdGlmLg0KDQo8ZGl2IGNsYXNzPSJib3ggd2FybmluZyI+DQoNCk5pbGFpIHlhbmcgbWVsYW5nZ2FyIGF0dXJhbiBhd2FsIHRpZGFrIGJvbGVoIGxhbmdzdW5nIGRpdWJhaCB0YW5wYQ0KbWVtYWhhbWkgc3VtYmVyIGRhdGEuIEtvcmVrc2kgc2ViYWlrbnlhIGRpbGFrdWthbiBqaWthIG5pbGFpIHlhbmcgYmVuYXINCmRhcGF0IGRpdmVyaWZpa2FzaS4NCg0KPC9kaXY+DQoNCg0KIyMgTWVtYnVhdCBMb2cgUGVydWJhaGFuDQoNCmBgYHtyfQ0KDQpsb2dfcGVydWJhaGFuIDwtIGRhdGEuZnJhbWUoDQoNCiAgdGFoYXAgPSBjKA0KICAgICJTdGFuZGFyZGlzYXNpIiwNCiAgICAiU3RhbmRhcmRpc2FzaSIsDQogICAgIkRlZHVwbGlrYXNpIiwNCiAgICAiS29yZWtzaSBkb21haW4iDQogICksDQoNCiAgYXRyaWJ1dCA9IGMoDQogICAgImtvdGEiLA0KICAgICJzdGF0dXMiLA0KICAgICJjdXN0b21lcl9pZCIsDQogICAgInVzaWEiDQogICksDQoNCiAgdGluZGFrYW4gPSBjKA0KICAgICJNZW55YW1ha2FuIFBLVSBkYW4gdmFyaWFzaSBrYXBpdGFsaXNhc2kgbWVuamFkaSBQZWthbmJhcnUiLA0KICAgICJNZW55YW1ha2FuIEFDVElWRS9BL2FrdGlmIG1lbmphZGkgQWt0aWYgZGFuIG5vbmFrdGlmIG1lbmphZGkgVGlkYWsgQWt0aWYiLA0KICAgICJNZW5naGFwdXMga2VtdW5jdWxhbiBrZWR1YSBNMTEwIiwNCiAgICAiVXNpYSBNMTA1IGRpa29yZWtzaSBkYXJpIDE0NSBtZW5qYWRpIDQ1IGJlcmRhc2Fya2FuIHN1bWJlciINCiAgKSwNCg0KICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCikNCg0KbG9nX3BlcnViYWhhbg0KDQpgYGANCg0KDQojIDQuIFBlbmFuZ2FuYW4gTWlzc2luZyBWYWx1ZXMNCg0KIyMgTWVuZ2lkZW50aWZpa2FzaSBMb2thc2kgTWlzc2luZw0KDQpgYGB7cn0NCg0KY29sU3VtcygNCiAgaXMubmEocGVsYW5nZ2FuKQ0KKQ0KDQpgYGANCg0KYGBge3J9DQoNCnBlbGFuZ2dhblsNCiAgIWNvbXBsZXRlLmNhc2VzKHBlbGFuZ2dhbiksDQpdDQoNCmBgYA0KDQotLS0NCg0KIyMgU3RyYXRlZ2kgMSDigJQgTWVuZ2hhcHVzIEJhcmlzDQoNCmBgYHtyfQ0KDQpwZWxhbmdnYW5fY29tcGxldGUgPC0NCiAgcGVsYW5nZ2FuWw0KICAgIGNvbXBsZXRlLmNhc2VzKHBlbGFuZ2dhbiksDQogIF0NCg0KbnJvdyhwZWxhbmdnYW4pDQoNCmBgYA0KDQpgYGB7cn0NCg0KbnJvdyhwZWxhbmdnYW5fY29tcGxldGUpDQoNCmBgYA0KDQojIyMgUGVyc2VudGFzZSBCYXJpcyB5YW5nIEhpbGFuZw0KDQpgYGB7cn0NCg0Kcm91bmQoDQogICgNCiAgICAxIC0NCiAgICBucm93KHBlbGFuZ2dhbl9jb21wbGV0ZSkgLw0KICAgIG5yb3cocGVsYW5nZ2FuKQ0KICApICogMTAwLA0KICAyDQopDQoNCmBgYA0KDQo8ZGl2IGNsYXNzPSJib3ggd2FybmluZyI+DQoNCk1lbmdoYXB1cyBzZWx1cnVoIGJhcmlzIHlhbmcgbWVtaWxpa2kgbWlzc2luZyB2YWx1ZSBkYXBhdCBtZW1idWFuZyBiYW55YWsNCmluZm9ybWFzaS4gS2VwdXR1c2FuIGluaSBoYXJ1cyBtZW1wZXJ0aW1iYW5na2FuIG1la2FuaXNtZSBtaXNzaW5nbmVzcyBkYW4NCnR1anVhbiBhbmFsaXNpcy4NCg0KPC9kaXY+DQoNCg0KIyMgU3RyYXRlZ2kgMiDigJQgSW1wdXRhc2kgTWVhbiBkYW4gTWVkaWFuDQoNClBlbmRhcGF0YW4gbWVtaWxpa2kgbmlsYWkgZWtzdHJlbSBzZWhpbmdnYSBtZWFuIGRhbiBtZWRpYW4gcGVybHUNCmRpYmFuZGluZ2thbi4NCg0KYGBge3J9DQoNCm1lYW5fcGVuZGFwYXRhbiA8LSBtZWFuKA0KICBwZWxhbmdnYW4kcGVuZGFwYXRhbiwNCiAgbmEucm0gPSBUUlVFDQopDQoNCm1lZGlhbl9wZW5kYXBhdGFuIDwtIG1lZGlhbigNCiAgcGVsYW5nZ2FuJHBlbmRhcGF0YW4sDQogIG5hLnJtID0gVFJVRQ0KKQ0KDQptZWFuX3BlbmRhcGF0YW4NCg0KYGBgDQoNCmBgYHtyfQ0KDQptZWRpYW5fcGVuZGFwYXRhbg0KDQpgYGANCg0KS2FyZW5hIHRlcmRhcGF0IG5pbGFpIHBlbmRhcGF0YW4geWFuZyBzYW5nYXQgYmVzYXIsIG1lZGlhbiBsZWJpaCByb2J1c3QNCnRlcmhhZGFwIG5pbGFpIGVrc3RyZW0uDQoNCiMjIyBJbXB1dGFzaSBQZW5kYXBhdGFuDQoNCmBgYHtyfQ0KDQpwZWxhbmdnYW4kcGVuZGFwYXRhbl9pbXB1dGFzaSA8LQ0KICBwZWxhbmdnYW4kcGVuZGFwYXRhbg0KDQpwZWxhbmdnYW4kcGVuZGFwYXRhbl9pbXB1dGFzaVsNCiAgaXMubmEoDQogICAgcGVsYW5nZ2FuJHBlbmRhcGF0YW5faW1wdXRhc2kNCiAgKQ0KXSA8LSBtZWRpYW5fcGVuZGFwYXRhbg0KDQpgYGANCg0KIyMjIEltcHV0YXNpIFVzaWENCg0KYGBge3J9DQoNCm1lZGlhbl91c2lhIDwtIG1lZGlhbigNCiAgcGVsYW5nZ2FuJHVzaWEsDQogIG5hLnJtID0gVFJVRQ0KKQ0KDQpwZWxhbmdnYW4kdXNpYV9pbXB1dGFzaSA8LQ0KICBwZWxhbmdnYW4kdXNpYQ0KDQpwZWxhbmdnYW4kdXNpYV9pbXB1dGFzaVsNCiAgaXMubmEoDQogICAgcGVsYW5nZ2FuJHVzaWFfaW1wdXRhc2kNCiAgKQ0KXSA8LSBtZWRpYW5fdXNpYQ0KDQpgYGANCg0KYGBge3J9DQoNCnBlbGFuZ2dhblsNCiAgLA0KICBjKA0KICAgICJjdXN0b21lcl9pZCIsDQogICAgInVzaWEiLA0KICAgICJ1c2lhX2ltcHV0YXNpIiwNCiAgICAicGVuZGFwYXRhbiIsDQogICAgInBlbmRhcGF0YW5faW1wdXRhc2kiDQogICkNCl0NCg0KYGBgDQoNCg0KIyMgU3RyYXRlZ2kgMyDigJQgSW1wdXRhc2kgS2F0ZWdvcmkNCg0KVW50dWsga290YSB5YW5nIHRpZGFrIGRpa2V0YWh1aSwgZGlndW5ha2FuIGthdGVnb3JpIGVrc3BsaXNpdA0KYCJUaWRhayBkaWtldGFodWkiYC4NCg0KYGBge3J9DQoNCnBlbGFuZ2dhbiRrb3RhX2ltcHV0YXNpIDwtDQogIHBlbGFuZ2dhbiRrb3RhDQoNCnBlbGFuZ2dhbiRrb3RhX2ltcHV0YXNpWw0KICBpcy5uYSgNCiAgICBwZWxhbmdnYW4ka290YV9pbXB1dGFzaQ0KICApDQpdIDwtICJUaWRhayBkaWtldGFodWkiDQoNCnRhYmxlKA0KICBwZWxhbmdnYW4ka290YV9pbXB1dGFzaSwNCiAgdXNlTkEgPSAiaWZhbnkiDQopDQoNCmBgYA0KDQoNCiMjIE1lbmFtYmFoa2FuIEluZGlrYXRvciBNaXNzaW5nDQoNCmBgYHtyfQ0KDQpwZWxhbmdnYW4kcGVuZGFwYXRhbl9taXNzaW5nIDwtDQogIGFzLmludGVnZXIoDQogICAgaXMubmEoDQogICAgICBwZWxhbmdnYW4kcGVuZGFwYXRhbg0KICAgICkNCiAgKQ0KDQp0YWJsZSgNCiAgcGVsYW5nZ2FuJHBlbmRhcGF0YW5fbWlzc2luZw0KKQ0KDQpgYGANCg0KPGRpdiBjbGFzcz0iYm94Ij4NCg0KSW5kaWthdG9yIG1pc3NpbmcgYmVyZ3VuYSBrYXJlbmEgc2V0ZWxhaCBpbXB1dGFzaSwgaW5mb3JtYXNpIGJhaHdhIG5pbGFpDQphc2xpbnlhIGhpbGFuZyB0aWRhayBzZXBlbnVobnlhIGhpbGFuZyBkYXJpIGRhdGFzZXQuDQoNCjwvZGl2Pg0KDQoNCiMjIE1lbWJhbmRpbmdrYW4gRGlzdHJpYnVzaSBTZWJlbHVtIGRhbiBTZXN1ZGFoIEltcHV0YXNpDQoNCmBgYHtyfQ0KDQpwYXIoDQogIG1mcm93ID0gYygxLCAyKQ0KKQ0KDQpoaXN0KA0KICBwZWxhbmdnYW4kcGVuZGFwYXRhbiwNCiAgbWFpbiA9ICJTZWJlbHVtIEltcHV0YXNpIiwNCiAgeGxhYiA9ICJQZW5kYXBhdGFuIiwNCiAgY29sID0gIiNmOGM5ZGIiLA0KICBicmVha3MgPSA4DQopDQoNCmhpc3QoDQogIHBlbGFuZ2dhbiRwZW5kYXBhdGFuX2ltcHV0YXNpLA0KICBtYWluID0gIlNlc3VkYWggSW1wdXRhc2kgTWVkaWFuIiwNCiAgeGxhYiA9ICJQZW5kYXBhdGFuIiwNCiAgY29sID0gIiNmNDhmYjEiLA0KICBicmVha3MgPSA4DQopDQoNCnBhcigNCiAgbWZyb3cgPSBjKDEsIDEpDQopDQoNCmBgYA0KDQo8ZGl2IGNsYXNzPSJib3ggd2FybmluZyI+DQoNClNldGVsYWggaW1wdXRhc2ksIGV2YWx1YXNpIHRpZGFrIGJlcmhlbnRpIGhhbnlhIGthcmVuYSBqdW1sYWggYE5BYCBtZW5qYWRpDQpub2wuIERpc3RyaWJ1c2kgc2ViZWx1bSBkYW4gc2VzdWRhaCBpbXB1dGFzaSBoYXJ1cyBkaWJhbmRpbmdrYW4uDQoNCjwvZGl2Pg0KDQoNCiMgNS4gUGVuYW5nYW5hbiBPdXRsaWVyDQoNCiMjIFZpc3VhbGlzYXNpIGRlbmdhbiBCb3hwbG90DQoNCmBgYHtyfQ0KDQpib3hwbG90KA0KICBwZWxhbmdnYW4kcGVuZGFwYXRhbl9pbXB1dGFzaSwNCiAgaG9yaXpvbnRhbCA9IFRSVUUsDQogIGNvbCA9ICIjZjZhOWMzIiwNCiAgbWFpbiA9ICJCb3hwbG90IFBlbmRhcGF0YW4iLA0KICB4bGFiID0gIlBlbmRhcGF0YW4iDQopDQoNCmBgYA0KDQojIyBNZW5naGl0dW5nIEJhdGFzIElRUg0KDQpgYGB7cn0NCg0KcTEgPC0gcXVhbnRpbGUoDQogIHBlbGFuZ2dhbiRwZW5kYXBhdGFuX2ltcHV0YXNpLA0KICAwLjI1DQopDQoNCnEzIDwtIHF1YW50aWxlKA0KICBwZWxhbmdnYW4kcGVuZGFwYXRhbl9pbXB1dGFzaSwNCiAgMC43NQ0KKQ0KDQppcXIgPC0gSVFSKA0KICBwZWxhbmdnYW4kcGVuZGFwYXRhbl9pbXB1dGFzaQ0KKQ0KDQpiYXRhc19iYXdhaCA8LQ0KICBxMSAtIDEuNSAqIGlxcg0KDQpiYXRhc19hdGFzIDwtDQogIHEzICsgMS41ICogaXFyDQoNCmMoDQogIFExID0gcTEsDQogIFEzID0gcTMsDQogIElRUiA9IGlxciwNCiAgYmF0YXNfYmF3YWggPSBiYXRhc19iYXdhaCwNCiAgYmF0YXNfYXRhcyA9IGJhdGFzX2F0YXMNCikNCg0KYGBgDQoNCiMjIE1lbmFuZGFpIEthbmRpZGF0IE91dGxpZXINCg0KYGBge3J9DQoNCnBlbGFuZ2dhbiRvdXRsaWVyX3BlbmRhcGF0YW4gPC0NCg0KICBwZWxhbmdnYW4kcGVuZGFwYXRhbl9pbXB1dGFzaSA8DQogIGJhdGFzX2Jhd2FoIHwNCg0KICBwZWxhbmdnYW4kcGVuZGFwYXRhbl9pbXB1dGFzaSA+DQogIGJhdGFzX2F0YXMNCg0KcGVsYW5nZ2FuWw0KICBwZWxhbmdnYW4kb3V0bGllcl9wZW5kYXBhdGFuLA0KICBjKA0KICAgICJjdXN0b21lcl9pZCIsDQogICAgIm5hbWEiLA0KICAgICJwZW5kYXBhdGFuX2ltcHV0YXNpIg0KICApDQpdDQoNCmBgYA0KDQojIyBNZW5nZXZhbHVhc2kgVGluZGFrYW4NCg0KPGRpdiBjbGFzcz0iYm94IHdhcm5pbmciPg0KDQpPdXRsaWVyIHRpZGFrIG90b21hdGlzIGRpaGFwdXMuDQoNCk5pbGFpIGVrc3RyZW0gZGFwYXQgbWVydXBha2FuOg0KDQoxLiBrZXNhbGFoYW4gaW5wdXQ7DQoyLiBvYnNlcnZhc2kgdmFsaWQgdGV0YXBpIGVrc3RyZW07DQozLiBhbmdnb3RhIHBvcHVsYXNpIHlhbmcgYmVyYmVkYS4NCg0KPC9kaXY+DQoNCkRhbGFtIGNvbnRvaCBpbmkgcGVuZGFwYXRhbiBla3N0cmVtIGRpcGVydGFoYW5rYW4uDQoNClNlYmFnYWkgZGVtb25zdHJhc2ksIGRpYnVhdCB2ZXJzaSB3aW5zb3JpemVkLg0KDQpgYGB7cn0NCg0KcGVsYW5nZ2FuJHBlbmRhcGF0YW5fd2luc29yIDwtDQoNCiAgcG1pbigNCiAgICBwbWF4KA0KICAgICAgcGVsYW5nZ2FuJHBlbmRhcGF0YW5faW1wdXRhc2ksDQogICAgICBiYXRhc19iYXdhaA0KICAgICksDQogICAgYmF0YXNfYXRhcw0KICApDQoNCnBlbGFuZ2dhblsNCiAgcGVsYW5nZ2FuJG91dGxpZXJfcGVuZGFwYXRhbiwNCiAgYygNCiAgICAiY3VzdG9tZXJfaWQiLA0KICAgICJwZW5kYXBhdGFuX2ltcHV0YXNpIiwNCiAgICAicGVuZGFwYXRhbl93aW5zb3IiDQogICkNCl0NCg0KYGBgDQoNCjxkaXYgY2xhc3M9ImJveCI+DQoNCldpbnNvcml6aW5nIGhhbnlhIGRpZ3VuYWthbiBzZWJhZ2FpIGRlbW9uc3RyYXNpLiBUaW5kYWthbiB0ZXJoYWRhcCBvdXRsaWVyDQpoYXJ1cyBkaXNlc3VhaWthbiBkZW5nYW4gdHVqdWFuIGFuYWxpc2lzIGRhbiBkaWRva3VtZW50YXNpa2FuLg0KDQo8L2Rpdj4NCg0KDQojIDYuIFRyYW5zZm9ybWFzaSBEYXRhDQoNCiMjIE5vcm1hbGlzYXNpIE1pbuKAk01heA0KDQpSdW11czoNCg0KJCQNCngnID0NClxmcmFje3gtXG1pbih4KX0NCntcbWF4KHgpLVxtaW4oeCl9DQokJA0KDQpgYGB7cn0NCg0KbWlubWF4IDwtIGZ1bmN0aW9uKHgpIHsNCg0KICBpZiAoDQogICAgYWxsKGlzLm5hKHgpKQ0KICApIHsNCiAgICByZXR1cm4oDQogICAgICByZXAoDQogICAgICAgIE5BX3JlYWxfLA0KICAgICAgICBsZW5ndGgoeCkNCiAgICAgICkNCiAgICApDQogIH0NCg0KICByZW50YW5nIDwtDQogICAgbWF4KA0KICAgICAgeCwNCiAgICAgIG5hLnJtID0gVFJVRQ0KICAgICkgLQ0KICAgIG1pbigNCiAgICAgIHgsDQogICAgICBuYS5ybSA9IFRSVUUNCiAgICApDQoNCiAgaWYgKA0KICAgIHJlbnRhbmcgPT0gMA0KICApIHsNCiAgICByZXR1cm4oDQogICAgICByZXAoDQogICAgICAgIDAsDQogICAgICAgIGxlbmd0aCh4KQ0KICAgICAgKQ0KICAgICkNCiAgfQ0KDQogICgNCiAgICB4IC0NCiAgICBtaW4oDQogICAgICB4LA0KICAgICAgbmEucm0gPSBUUlVFDQogICAgKQ0KICApIC8NCiAgICByZW50YW5nDQp9DQoNCmBgYA0KDQpgYGB7cn0NCg0KcGVsYW5nZ2FuJHVzaWFfbWlubWF4IDwtDQogIG1pbm1heCgNCiAgICBwZWxhbmdnYW4kdXNpYV9pbXB1dGFzaQ0KICApDQoNCnBlbGFuZ2dhbiRwZW5kYXBhdGFuX21pbm1heCA8LQ0KICBtaW5tYXgoDQogICAgcGVsYW5nZ2FuJHBlbmRhcGF0YW5faW1wdXRhc2kNCiAgKQ0KDQpwZWxhbmdnYW5bDQogICwNCiAgYygNCiAgICAiY3VzdG9tZXJfaWQiLA0KICAgICJ1c2lhX2ltcHV0YXNpIiwNCiAgICAidXNpYV9taW5tYXgiLA0KICAgICJwZW5kYXBhdGFuX2ltcHV0YXNpIiwNCiAgICAicGVuZGFwYXRhbl9taW5tYXgiDQogICkNCl0NCg0KYGBgDQoNCg0KIyMgTm9ybWFsaXNhc2kgWi1TY29yZQ0KDQpSdW11czoNCg0KJCQNCnogPQ0KXGZyYWN7eC1cYmFye3h9fXtzfQ0KJCQNCg0KYGBge3J9DQoNCnBlbGFuZ2dhbiR1c2lhX3ogPC0NCiAgYXMubnVtZXJpYygNCiAgICBzY2FsZSgNCiAgICAgIHBlbGFuZ2dhbiR1c2lhX2ltcHV0YXNpDQogICAgKQ0KICApDQoNCnBlbGFuZ2dhbiRwZW5kYXBhdGFuX3ogPC0NCiAgYXMubnVtZXJpYygNCiAgICBzY2FsZSgNCiAgICAgIHBlbGFuZ2dhbiRwZW5kYXBhdGFuX2ltcHV0YXNpDQogICAgKQ0KICApDQoNCnJvdW5kKA0KICBwZWxhbmdnYW5bDQogICAgLA0KICAgIGMoDQogICAgICAidXNpYV96IiwNCiAgICAgICJwZW5kYXBhdGFuX3oiDQogICAgKQ0KICBdLA0KICAzDQopDQoNCmBgYA0KDQoNCiMjIERlY2ltYWwgU2NhbGluZw0KDQpgYGB7cn0NCg0KZGVjaW1hbF9zY2FsZSA8LSBmdW5jdGlvbih4KSB7DQoNCiAgbWFrcyA8LQ0KICAgIG1heCgNCiAgICAgIGFicyh4KSwNCiAgICAgIG5hLnJtID0gVFJVRQ0KICAgICkNCg0KICBpZiAoDQogICAgbWFrcyA9PSAwDQogICkgew0KICAgIHJldHVybih4KQ0KICB9DQoNCiAgaiA8LQ0KICAgIGNlaWxpbmcoDQogICAgICBsb2cxMCgNCiAgICAgICAgbWFrcyArIDENCiAgICAgICkNCiAgICApDQoNCiAgeCAvDQogICAgKA0KICAgICAgMTAgXiBqDQogICAgKQ0KfQ0KDQpgYGANCg0KYGBge3J9DQoNCnBlbGFuZ2dhbiRwZW5kYXBhdGFuX2RlY2ltYWwgPC0NCg0KICBkZWNpbWFsX3NjYWxlKA0KICAgIHBlbGFuZ2dhbiRwZW5kYXBhdGFuX2ltcHV0YXNpDQogICkNCg0KcmFuZ2UoDQogIHBlbGFuZ2dhbiRwZW5kYXBhdGFuX2RlY2ltYWwsDQogIG5hLnJtID0gVFJVRQ0KKQ0KDQpgYGANCg0KDQojIyBNZW1iYW5kaW5na2FuIE1ldG9kZSBUcmFuc2Zvcm1hc2kNCg0KYGBge3J9DQoNCnRyYW5zZm9ybWFzaSA8LQ0KICBwZWxhbmdnYW5bDQogICAgLA0KICAgIGMoDQogICAgICAiY3VzdG9tZXJfaWQiLA0KICAgICAgInBlbmRhcGF0YW5faW1wdXRhc2kiLA0KICAgICAgInBlbmRhcGF0YW5fbWlubWF4IiwNCiAgICAgICJwZW5kYXBhdGFuX3oiLA0KICAgICAgInBlbmRhcGF0YW5fZGVjaW1hbCINCiAgICApDQogIF0NCg0KdHJhbnNmb3JtYXNpDQoNCmBgYA0KDQoNCiMjIERhbXBhayBPdXRsaWVyIHRlcmhhZGFwIE5vcm1hbGlzYXNpDQoNCmBgYHtyfQ0KDQpwZWxhbmdnYW4kcGVuZGFwYXRhbl93aW5zb3JfbWlubWF4IDwtDQoNCiAgbWlubWF4KA0KICAgIHBlbGFuZ2dhbiRwZW5kYXBhdGFuX3dpbnNvcg0KICApDQoNCnBsb3QoDQogIHBlbGFuZ2dhbiRwZW5kYXBhdGFuX21pbm1heCwNCiAgcGVsYW5nZ2FuJHBlbmRhcGF0YW5fd2luc29yX21pbm1heCwNCiAgcGNoID0gMTksDQogIGNvbCA9ICIjYzIxODViIiwNCiAgeGxhYiA9ICJNaW4tTWF4IERhdGEgQXNsaSIsDQogIHlsYWIgPSAiTWluLU1heCBEYXRhIFdpbnNvcml6ZWQiLA0KICBtYWluID0NCiAgICAiRGFtcGFrIE91dGxpZXIgdGVyaGFkYXAgTm9ybWFsaXNhc2kiDQopDQoNCmFibGluZSgNCiAgMCwNCiAgMSwNCiAgY29sID0gIiNmNmE5YzMiLA0KICBsdHkgPSAyDQopDQoNCmBgYA0KDQo8ZGl2IGNsYXNzPSJib3giPg0KDQpPdXRsaWVyIGRhcGF0IG1lbWJ1YXQgc2ViYWdpYW4gYmVzYXIgbmlsYWkgbGFpbiBtZW5qYWRpIHNhbmdhdCBkZWthdA0KZGVuZ2FuIG5vbCBwYWRhIG5vcm1hbGlzYXNpIG1pbuKAk21heC4gS2FyZW5hIGl0dSwgZGV0ZWtzaSBvdXRsaWVyIHBlbnRpbmcNCnNlYmVsdW0gbWVtaWxpaCB0cmFuc2Zvcm1hc2kuDQoNCjwvZGl2Pg0KDQoNCiMgNy4gSW50ZWdyYXNpIERhdGENCg0KU2VsYWluIGRhdGEgcGVsYW5nZ2FuLCBkaWJ1YXQgc3VtYmVyIGRhdGEgdHJhbnNha3NpIHRlcnBpc2FoLg0KDQojIyBEYXRhIFRyYW5zYWtzaQ0KDQpgYGB7cn0NCg0KdHJhbnNha3NpX3JhdyA8LSBkYXRhLmZyYW1lKA0KDQogIGN1c3RfY29kZSA9IGMoDQogICAgIk0xMDEiLCJNMTAyIiwiTTEwMyIsIk0xMDQiLA0KICAgICJNMTA1IiwiTTEwNiIsIk0xMDciLCJNMTA4IiwNCiAgICAiTTEwOSIsIk0xMTAiLCJNMTE1Ig0KICApLA0KDQogIGp1bWxhaF90cmFuc2Frc2kgPSBjKA0KICAgIDQsNiwzLDgsMiw1LDE1LDQsNyw5LDMNCiAgKSwNCg0KICB0b3RhbF9wdXJjaGFzZSA9IGMoDQogICAgMTIwMDAwMCwNCiAgICAxODAwMDAwLA0KICAgIDk1MDAwMCwNCiAgICAyNjAwMDAwLA0KICAgIDcwMDAwMCwNCiAgICAxNTAwMDAwLA0KICAgIDgyMDAwMDAsDQogICAgMTQwMDAwMCwNCiAgICAzMTAwMDAwLA0KICAgIDQyMDAwMDAsDQogICAgOTAwMDAwDQogICksDQoNCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQopDQoNCnRyYW5zYWtzaV9yYXcNCg0KYGBgDQoNCg0KIyMgTWVtZXJpa3NhIEt1bmNpIHBhZGEgS2VkdWEgU3VtYmVyDQoNCmBgYHtyfQ0KDQpzdW0oDQogIGR1cGxpY2F0ZWQoDQogICAgcGVsYW5nZ2FuJGN1c3RvbWVyX2lkDQogICkNCikNCg0KYGBgDQoNCmBgYHtyfQ0KDQpzdW0oDQogIGR1cGxpY2F0ZWQoDQogICAgdHJhbnNha3NpX3JhdyRjdXN0X2NvZGUNCiAgKQ0KKQ0KDQpgYGANCg0KIyMgTWVuY2FyaSBJZGVudGlmaWVyIHlhbmcgVGlkYWsgTWVtaWxpa2kgUGFzYW5nYW4NCg0KYGBge3J9DQoNCnNldGRpZmYoDQogIHBlbGFuZ2dhbiRjdXN0b21lcl9pZCwNCiAgdHJhbnNha3NpX3JhdyRjdXN0X2NvZGUNCikNCg0KYGBgDQoNCmBgYHtyfQ0KDQpzZXRkaWZmKA0KICB0cmFuc2Frc2lfcmF3JGN1c3RfY29kZSwNCiAgcGVsYW5nZ2FuJGN1c3RvbWVyX2lkDQopDQoNCmBgYA0KDQo8ZGl2IGNsYXNzPSJib3giPg0KDQpgc2V0ZGlmZigpYCBkaWd1bmFrYW4gdW50dWsgbWVuZW11a2FuIGlkZW50aWZpZXIgeWFuZyBoYW55YSBtdW5jdWwgcGFkYQ0Kc2FsYWggc2F0dSBzdW1iZXIgZGF0YS4NCg0KPC9kaXY+DQoNCg0KIyMgTWVueWVsYXJhc2thbiBOYW1hIElkZW50aWZpZXINCg0KYGBge3J9DQoNCnRyYW5zYWtzaSA8LSB0cmFuc2Frc2lfcmF3DQoNCm5hbWVzKHRyYW5zYWtzaSlbDQogIG5hbWVzKHRyYW5zYWtzaSkgPT0gImN1c3RfY29kZSINCl0gPC0gImN1c3RvbWVyX2lkIg0KDQpgYGANCg0KIyMgTWVsYWt1a2FuIExlZnQgSm9pbg0KDQpgYGB7cn0NCg0KZGF0YV90ZXJpbnRlZ3Jhc2kgPC0NCg0KICBtZXJnZSgNCiAgICBwZWxhbmdnYW4sDQogICAgdHJhbnNha3NpLA0KICAgIGJ5ID0gImN1c3RvbWVyX2lkIiwNCiAgICBhbGwueCA9IFRSVUUNCiAgKQ0KDQpkYXRhX3RlcmludGVncmFzaVsNCiAgLA0KICBjKA0KICAgICJjdXN0b21lcl9pZCIsDQogICAgIm5hbWEiLA0KICAgICJqdW1sYWhfdHJhbnNha3NpIiwNCiAgICAidG90YWxfcHVyY2hhc2UiDQogICkNCl0NCg0KYGBgDQoNCjxkaXYgY2xhc3M9ImJveCI+DQoNCmBhbGwueCA9IFRSVUVgIG1lbXBlcnRhaGFua2FuIHNlbHVydWggcGVsYW5nZ2FuIHdhbGF1cHVuIHBlbGFuZ2dhbiB0ZXJzZWJ1dA0KdGlkYWsgbWVtaWxpa2kgcGFzYW5nYW4gdHJhbnNha3NpLg0KDQo8L2Rpdj4NCg0KDQojIyBNZW12YWxpZGFzaSBIYXNpbCBJbnRlZ3Jhc2kNCg0KIyMjIEp1bWxhaCBCYXJpcw0KDQpgYGB7cn0NCg0KYygNCiAgc2ViZWx1bSA9DQogICAgbnJvdyhwZWxhbmdnYW4pLA0KDQogIHNlc3VkYWggPQ0KICAgIG5yb3coZGF0YV90ZXJpbnRlZ3Jhc2kpDQopDQoNCmBgYA0KDQojIyMgQXBha2FoIElkZW50aWZpZXIgVGV0YXAgVW5paz8NCg0KYGBge3J9DQoNCnN1bSgNCiAgZHVwbGljYXRlZCgNCiAgICBkYXRhX3RlcmludGVncmFzaSRjdXN0b21lcl9pZA0KICApDQopDQoNCmBgYA0KDQojIyMgTWlzc2luZyBWYWx1ZSBBa2liYXQgSW50ZWdyYXNpDQoNCmBgYHtyfQ0KDQpjb2xTdW1zKA0KICBpcy5uYSgNCiAgICBkYXRhX3RlcmludGVncmFzaVsNCiAgICAgICwNCiAgICAgIGMoDQogICAgICAgICJqdW1sYWhfdHJhbnNha3NpIiwNCiAgICAgICAgInRvdGFsX3B1cmNoYXNlIg0KICAgICAgKQ0KICAgIF0NCiAgKQ0KKQ0KDQpgYGANCg0KIyMjIFBlbGFuZ2dhbiBUYW5wYSBUcmFuc2Frc2kNCg0KYGBge3J9DQoNCmRhdGFfdGVyaW50ZWdyYXNpWw0KICBpcy5uYSgNCiAgICBkYXRhX3RlcmludGVncmFzaSRqdW1sYWhfdHJhbnNha3NpDQogICksDQogIGMoDQogICAgImN1c3RvbWVyX2lkIiwNCiAgICAibmFtYSINCiAgKQ0KXQ0KDQpgYGANCg0KDQojIyBNZW5naXNpIE5vbCBhdGF1IE1lbXBlcnRhaGFua2FuIE5BPw0KDQpgTkFgIHNldGVsYWggaW50ZWdyYXNpIGRhcGF0IG1lbWlsaWtpIGFydGkgeWFuZyBiZXJiZWRhLg0KDQotIHBlbGFuZ2dhbiBtZW1hbmcgYmVsdW0gcGVybmFoIGJlcnRyYW5zYWtzaTsNCi0gZGF0YSB0cmFuc2Frc2kgdGlkYWsgdGVyc2VkaWE7DQotIGlkZW50aWZpZXIgZ2FnYWwgZGlwYWRhbmthbi4NCg0KS2FyZW5hIGl0dSBgTkFgICoqdGlkYWsgYm9sZWggb3RvbWF0aXMgZGl1YmFoIG1lbmphZGkgMCoqLg0KDQpTZWJhZ2FpIGNvbnRvaCwgYXBhYmlsYSBkZWZpbmlzaSBkYXRhIHRlbGFoIGRpa29uZmlybWFzaSBiYWh3YSBgTkFgIGJlcmFydGkNCnBlbGFuZ2dhbiBiZWx1bSBwZXJuYWggYmVydHJhbnNha3NpOg0KDQpgYGB7cn0NCg0KZGF0YV90ZXJpbnRlZ3Jhc2kkanVtbGFoX3RyYW5zYWtzaV9maW5hbCA8LQ0KICBkYXRhX3RlcmludGVncmFzaSRqdW1sYWhfdHJhbnNha3NpDQoNCmRhdGFfdGVyaW50ZWdyYXNpJHRvdGFsX3B1cmNoYXNlX2ZpbmFsIDwtDQogIGRhdGFfdGVyaW50ZWdyYXNpJHRvdGFsX3B1cmNoYXNlDQoNCmRhdGFfdGVyaW50ZWdyYXNpJGp1bWxhaF90cmFuc2Frc2lfZmluYWxbDQogIGlzLm5hKA0KICAgIGRhdGFfdGVyaW50ZWdyYXNpJGp1bWxhaF90cmFuc2Frc2lfZmluYWwNCiAgKQ0KXSA8LSAwDQoNCmRhdGFfdGVyaW50ZWdyYXNpJHRvdGFsX3B1cmNoYXNlX2ZpbmFsWw0KICBpcy5uYSgNCiAgICBkYXRhX3RlcmludGVncmFzaSR0b3RhbF9wdXJjaGFzZV9maW5hbA0KICApDQpdIDwtIDANCg0KYGBgDQoNCiMgOC4gRGF0YXNldCBBa2hpciBkYW4gRXZhbHVhc2kNCg0KIyMgTWVtaWxpaCBBdHJpYnV0IEFraGlyDQoNCmBgYHtyfQ0KDQpkYXRhX2ZpbmFsIDwtDQoNCiAgZGF0YV90ZXJpbnRlZ3Jhc2lbDQogICAgLA0KICAgIGMoDQogICAgICAiY3VzdG9tZXJfaWQiLA0KICAgICAgIm5hbWEiLA0KICAgICAgInVzaWFfaW1wdXRhc2kiLA0KICAgICAgImtvdGFfaW1wdXRhc2kiLA0KICAgICAgInN0YXR1cyIsDQogICAgICAicGVuZGFwYXRhbl9pbXB1dGFzaSIsDQogICAgICAicGVuZGFwYXRhbl9taXNzaW5nIiwNCiAgICAgICJvdXRsaWVyX3BlbmRhcGF0YW4iLA0KICAgICAgInVzaWFfbWlubWF4IiwNCiAgICAgICJwZW5kYXBhdGFuX21pbm1heCIsDQogICAgICAianVtbGFoX3RyYW5zYWtzaV9maW5hbCIsDQogICAgICAidG90YWxfcHVyY2hhc2VfZmluYWwiDQogICAgKQ0KICBdDQoNCm5hbWVzKGRhdGFfZmluYWwpWw0KICBuYW1lcyhkYXRhX2ZpbmFsKSA9PSAidXNpYV9pbXB1dGFzaSINCl0gPC0gInVzaWEiDQoNCm5hbWVzKGRhdGFfZmluYWwpWw0KICBuYW1lcyhkYXRhX2ZpbmFsKSA9PSAia290YV9pbXB1dGFzaSINCl0gPC0gImtvdGEiDQoNCmRhdGFfZmluYWwNCg0KYGBgDQoNCg0KIyMgUGVtZXJpa3NhYW4gQWtoaXINCg0KYGBge3J9DQoNCmF1ZGl0X2FraGlyIDwtDQogIGF1ZGl0X2RhdGEoDQogICAgZGF0YV9maW5hbA0KICApDQoNCmF1ZGl0X2FraGlyDQoNCmBgYA0KDQojIyMgRHVwbGlrYXNpIElkZW50aWZpZXINCg0KYGBge3J9DQoNCnN1bSgNCiAgZHVwbGljYXRlZCgNCiAgICBkYXRhX2ZpbmFsJGN1c3RvbWVyX2lkDQogICkNCikNCg0KYGBgDQoNCiMjIyBNaXNzaW5nIFZhbHVlDQoNCmBgYHtyfQ0KDQpjb2xTdW1zKA0KICBpcy5uYSgNCiAgICBkYXRhX2ZpbmFsDQogICkNCikNCg0KYGBgDQoNCg0KIyMgUGFyYW1ldGVyIFRyYW5zZm9ybWFzaSBkYW4gRGF0YSBUcmFpbmluZw0KDQo8ZGl2IGNsYXNzPSJib3giPg0KDQpEYWxhbSBtYWNoaW5lIGxlYXJuaW5nLCBwYXJhbWV0ZXIgcHJlcHJvY2Vzc2luZyBzZXBlcnRpIG1lZGlhbiwgbWVhbiwNCnN0YW5kYXIgZGV2aWFzaSwgbmlsYWkgbWluaW11bSwgZGFuIG1ha3NpbXVtIHNlYmFpa255YSBkaWhpdHVuZyBkYXJpDQpkYXRhIHRyYWluaW5nIHNhamEuDQoNCkhhbCBpbmkgbWVuY2VnYWggKipkYXRhIGxlYWthZ2UqKiwgeWFpdHUgaW5mb3JtYXNpIGRhcmkgZGF0YSB0ZXN0aW5nDQpzZWNhcmEgdGlkYWsgc2VuZ2FqYSBtYXN1ayBrZSBwcm9zZXMgcGVtYmVudHVrYW4gbW9kZWwuDQoNCjwvZGl2Pg0KDQpgYGB7cn0NCg0Kc2V0LnNlZWQoMjAyNikNCg0Kbl90cmFpbiA8LQ0KICBmbG9vcigNCiAgICAwLjggKg0KICAgIG5yb3coZGF0YV9maW5hbCkNCiAgKQ0KDQppZF90cmFpbiA8LQ0KICBzYW1wbGUoDQogICAgc2VxX2xlbigNCiAgICAgIG5yb3coZGF0YV9maW5hbCkNCiAgICApLA0KICAgIG5fdHJhaW4NCiAgKQ0KDQp0cmFpbl9kYXRhIDwtDQogIGRhdGFfZmluYWxbDQogICAgaWRfdHJhaW4sDQogIF0NCg0KdGVzdF9kYXRhIDwtDQogIGRhdGFfZmluYWxbDQogICAgLWlkX3RyYWluLA0KICBdDQoNCmBgYA0KDQojIyMgUGFyYW1ldGVyIE1lZGlhbiBkYXJpIFRyYWluaW5nDQoNCmBgYHtyfQ0KDQptZWRpYW5fdHJhaW5faW5jb21lIDwtDQoNCiAgbWVkaWFuKA0KICAgIHRyYWluX2RhdGEkcGVuZGFwYXRhbl9pbXB1dGFzaSwNCiAgICBuYS5ybSA9IFRSVUUNCiAgKQ0KDQptZWRpYW5fdHJhaW5faW5jb21lDQoNCmBgYA0KDQojIyMgUGFyYW1ldGVyIE1pbi1NYXggZGFyaSBUcmFpbmluZw0KDQpgYGB7cn0NCg0KbWluX3RyYWluX2luY29tZSA8LQ0KICBtaW4oDQogICAgdHJhaW5fZGF0YSRwZW5kYXBhdGFuX2ltcHV0YXNpLA0KICAgIG5hLnJtID0gVFJVRQ0KICApDQoNCm1heF90cmFpbl9pbmNvbWUgPC0NCiAgbWF4KA0KICAgIHRyYWluX2RhdGEkcGVuZGFwYXRhbl9pbXB1dGFzaSwNCiAgICBuYS5ybSA9IFRSVUUNCiAgKQ0KDQpjKA0KICBtaW5fdHJhaW5faW5jb21lLA0KICBtYXhfdHJhaW5faW5jb21lDQopDQoNCmBgYA0KDQojIyMgVHJhbnNmb3JtYXNpIFRyYWluaW5nDQoNCmBgYHtyfQ0KDQp0cmFpbl9kYXRhJGluY29tZV9taW5tYXggPC0NCg0KICAoDQogICAgdHJhaW5fZGF0YSRwZW5kYXBhdGFuX2ltcHV0YXNpIC0NCiAgICBtaW5fdHJhaW5faW5jb21lDQogICkgLw0KICAoDQogICAgbWF4X3RyYWluX2luY29tZSAtDQogICAgbWluX3RyYWluX2luY29tZQ0KICApDQoNCmhlYWQoDQogIHRyYWluX2RhdGFbDQogICAgLA0KICAgIGMoDQogICAgICAiY3VzdG9tZXJfaWQiLA0KICAgICAgInBlbmRhcGF0YW5faW1wdXRhc2kiLA0KICAgICAgImluY29tZV9taW5tYXgiDQogICAgKQ0KICBdDQopDQoNCmBgYA0KDQo8ZGl2IGNsYXNzPSJib3ggd2FybmluZyI+DQoNCkppa2EgbWluaW11bSwgbWFrc2ltdW0sIG1lYW4sIG1lZGlhbiwgYXRhdSBzdGFuZGFyIGRldmlhc2kgZGloaXR1bmcgZGFyaQ0KZ2FidW5nYW4gdHJhaW5pbmcgZGFuIHRlc3RpbmcsIGluZm9ybWFzaSB0ZXN0aW5nIGlrdXQgbWVtZW5nYXJ1aGkgcHJvc2VzDQpwcmVwcm9jZXNzaW5nLg0KDQo8L2Rpdj4NCg0KDQojIyBNZW1iYW5kaW5na2FuIEtvbmRpc2kgU2ViZWx1bSBkYW4gU2VzdWRhaA0KDQpgYGB7cn0NCg0KcGVyYmFuZGluZ2FuIDwtDQoNCiAgZGF0YS5mcmFtZSgNCg0KICAgIGluZGlrYXRvciA9IGMoDQogICAgICAiSnVtbGFoIGJhcmlzIiwNCiAgICAgICJEdXBsaWthc2kgY3VzdG9tZXJfaWQiLA0KICAgICAgIlRvdGFsIG1pc3NpbmcgdmFsdWUiLA0KICAgICAgIkthdGVnb3JpIGtvdGEgdW5payIsDQogICAgICAiS2F0ZWdvcmkgc3RhdHVzIHVuaWsiDQogICAgKSwNCg0KICAgIHNlYmVsdW0gPSBjKA0KDQogICAgICBucm93KA0KICAgICAgICBwZWxhbmdnYW5fcmF3DQogICAgICApLA0KDQogICAgICBzdW0oDQogICAgICAgIGR1cGxpY2F0ZWQoDQogICAgICAgICAgcGVsYW5nZ2FuX3JhdyRjdXN0b21lcl9pZA0KICAgICAgICApDQogICAgICApLA0KDQogICAgICBzdW0oDQogICAgICAgIGlzLm5hKA0KICAgICAgICAgIHBlbGFuZ2dhbl9yYXcNCiAgICAgICAgKQ0KICAgICAgKSwNCg0KICAgICAgbGVuZ3RoKA0KICAgICAgICB1bmlxdWUoDQogICAgICAgICAgcGVsYW5nZ2FuX3JhdyRrb3RhDQogICAgICAgICkNCiAgICAgICksDQoNCiAgICAgIGxlbmd0aCgNCiAgICAgICAgdW5pcXVlKA0KICAgICAgICAgIHBlbGFuZ2dhbl9yYXckc3RhdHVzDQogICAgICAgICkNCiAgICAgICkNCiAgICApLA0KDQogICAgc2VzdWRhaCA9IGMoDQoNCiAgICAgIG5yb3coDQogICAgICAgIGRhdGFfZmluYWwNCiAgICAgICksDQoNCiAgICAgIHN1bSgNCiAgICAgICAgZHVwbGljYXRlZCgNCiAgICAgICAgICBkYXRhX2ZpbmFsJGN1c3RvbWVyX2lkDQogICAgICAgICkNCiAgICAgICksDQoNCiAgICAgIHN1bSgNCiAgICAgICAgaXMubmEoDQogICAgICAgICAgZGF0YV9maW5hbA0KICAgICAgICApDQogICAgICApLA0KDQogICAgICBsZW5ndGgoDQogICAgICAgIHVuaXF1ZSgNCiAgICAgICAgICBkYXRhX2ZpbmFsJGtvdGENCiAgICAgICAgKQ0KICAgICAgKSwNCg0KICAgICAgbGVuZ3RoKA0KICAgICAgICB1bmlxdWUoDQogICAgICAgICAgZGF0YV9maW5hbCRzdGF0dXMNCiAgICAgICAgKQ0KICAgICAgKQ0KICAgICkNCiAgKQ0KDQpwZXJiYW5kaW5nYW4NCg0KYGBgDQoNCg0KIyA5LiBQZXJ0YW55YWFuIFJlZmxla3NpDQoNCjxkaXYgY2xhc3M9InF1ZXN0aW9uIj4NCg0KIyMgMS4gQXBha2FoIGRhdGFzZXQgdGFucGEgbWlzc2luZyB2YWx1ZSBzZWxhbHUgbGViaWggYmVya3VhbGl0YXM/DQoNCioqVGlkYWsuKioNCg0KRGF0YXNldCB0YW5wYSBtaXNzaW5nIHZhbHVlIGJlbHVtIHRlbnR1IGxlYmloIGJlcmt1YWxpdGFzIGthcmVuYSBuaWxhaQ0KeWFuZyBoaWxhbmcgbXVuZ2tpbiB0ZWxhaCBkaWhhcHVzIGF0YXUgZGlpbXB1dGFzaSBkZW5nYW4gY2FyYSB5YW5nIHRpZGFrDQp0ZXBhdC4gTWVuZ3ViYWggc2VsdXJ1aCBtaXNzaW5nIHZhbHVlIG1lbmphZGkgbmlsYWkgdGVydGVudHUgZGFwYXQNCm1lbmdoaWxhbmdrYW4gaW5mb3JtYXNpIG1lbmdlbmFpIGtldGlkYWtwYXN0aWFuIGRhdGEuDQoNCkt1YWxpdGFzIGRhdGEgaGFydXMgZGlsaWhhdCBzZWNhcmEgbGViaWggbHVhcywgdGVybWFzdWsgKiphY2N1cmFjeSwNCmNvbXBsZXRlbmVzcywgY29uc2lzdGVuY3ksIHRpbWVsaW5lc3MsIGJlbGlldmFiaWxpdHksIGRhbg0KaW50ZXJwcmV0YWJpbGl0eSoqLg0KDQpKYWRpLCBkYXRhc2V0IGRlbmdhbiBzZWRpa2l0IG1pc3NpbmcgdmFsdWUgdGV0YXBpIGJhbnlhayBuaWxhaSBzYWxhaA0KZGFwYXQgbGViaWggYnVydWsgZGFyaXBhZGEgZGF0YXNldCB5YW5nIG1lbWlsaWtpIGJlYmVyYXBhIG1pc3NpbmcgdmFsdWUNCm5hbXVuIG5pbGFpIGxhaW5ueWEgYWt1cmF0Lg0KDQo8L2Rpdj4NCg0KPGRpdiBjbGFzcz0icXVlc3Rpb24iPg0KDQojIyAyLiBNZW5nYXBhIG91dGxpZXIgdGlkYWsgYm9sZWggb3RvbWF0aXMgZGloYXB1cz8NCg0KT3V0bGllciB0aWRhayBzZWxhbHUgbWVydXBha2FuIGtlc2FsYWhhbi4NCg0KTmlsYWkgZWtzdHJlbSBkYXBhdCBtZXJ1cGFrYW46DQoNCi0ga2VzYWxhaGFuIGlucHV0Ow0KLSBvYnNlcnZhc2kgeWFuZyBiZW5hciB0ZXRhcGkgamFyYW5nIHRlcmphZGk7DQotIGFuZ2dvdGEgcG9wdWxhc2kgeWFuZyBiZXJiZWRhLg0KDQpNaXNhbG55YSBwZW5kYXBhdGFuIHNlc2VvcmFuZyB5YW5nIGphdWggbGViaWggdGluZ2dpIGRhcmlwYWRhIG1heW9yaXRhcw0KcGVsYW5nZ2FuIGJlbHVtIHRlbnR1IHNhbGFoLiBKaWthIG5pbGFpIHRlcnNlYnV0IGJlbmFyLCBtZW5naGFwdXNueWEganVzdHJ1DQptZW5naGlsYW5na2FuIGluZm9ybWFzaSBwZW50aW5nLg0KDQpLYXJlbmEgaXR1IG91dGxpZXIgcGVybHUgKipkaWRldGVrc2ksIGRpcGVyaWtzYSwga2VtdWRpYW4gZGlldmFsdWFzaSoqLg0KTWF0ZXJpIGp1Z2EgbWVuZWthbmthbiB0aWdhIGtlbXVuZ2tpbmFuIHRpbmRha2FuOiBrb3Jla3NpIGppa2EgdGVyYnVrdGkNCnNhbGFoLCBtZW1wZXJ0YWhhbmthbiBqaWthIHZhbGlkLCBhdGF1IG1lbWlzYWhrYW4gc2VnbWVuIGppa2EgYmVyYXNhbCBkYXJpDQpwb3B1bGFzaSBiZXJiZWRhLg0KDQo8L2Rpdj4NCg0KPGRpdiBjbGFzcz0icXVlc3Rpb24iPg0KDQojIyAzLiBCYWdhaW1hbmEgcHJlcHJvY2Vzc2luZyBkYXBhdCBtZW5pbWJ1bGthbiBiaWFzPw0KDQpQcmVwcm9jZXNzaW5nIGRhcGF0IG1lbmltYnVsa2FuIGJpYXMgYXBhYmlsYSBrZXB1dHVzYW4gY2xlYW5pbmcgbWVtYnVhdA0Kc2ViYWdpYW4ga2Vsb21wb2sgbGViaWggYmFueWFrIGtlaGlsYW5nYW4gYXRhdSBkaXViYWggZGF0YW55YSBkYXJpcGFkYQ0Ka2Vsb21wb2sgbGFpbi4NCg0KQ29udG9obnlhOg0KDQotIG1lbmdoYXB1cyBzZW11YSBiYXJpcyBkZW5nYW4gbWlzc2luZyB2YWx1ZTsNCi0gbWVuZ2d1bmFrYW4gc2F0dSBtZXRvZGUgaW1wdXRhc2kgdW50dWsgc2VtdWEga2Vsb21wb2s7DQotIG1lbmdoYXB1cyBvdXRsaWVyIHlhbmcgc2ViZW5hcm55YSBtZXJ1cGFrYW4ga2Vsb21wb2sgbWlub3JpdGFzOw0KLSBtZWxha3VrYW4gc3RhbmRhcmRpc2FzaSBrYXRlZ29yaSBiZXJkYXNhcmthbiBhc3Vtc2kgeWFuZyBzYWxhaC4NCg0KSmlrYSBrZWxvbXBvayB0ZXJ0ZW50dSBsZWJpaCBzZXJpbmcgbWVtaWxpa2kgbWlzc2luZyB2YWx1ZSBsYWx1IGJhcmlzbnlhDQpsZWJpaCBiYW55YWsgZGloYXB1cywgZGlzdHJpYnVzaSBzYW1wZWwgYWtoaXIgZGFwYXQgYmVyYmVkYSBkYXJpIHBvcHVsYXNpDQphc2xpbnlhLg0KDQpLYXJlbmEgaXR1IHByZXByb2Nlc3NpbmcgYnVrYW4gc2VrYWRhciBwcm9zZXMgdGVrbmlzLCB0ZXRhcGkgbWVydXBha2FuDQoqKnNlcmFuZ2thaWFuIGtlcHV0dXNhbiB5YW5nIGRhcGF0IG1lbWVuZ2FydWhpIGhhc2lsIGFuYWxpc2lzKiouDQoNCjwvZGl2Pg0KDQo8ZGl2IGNsYXNzPSJxdWVzdGlvbiI+DQoNCiMjIDQuIE1lbmdhcGEgcGFyYW1ldGVyIGltcHV0YXNpIGRhbiB0cmFuc2Zvcm1hc2kgc2VoYXJ1c255YSBkaWhpdHVuZyBkYXJpIGRhdGEgcGVsYXRpaGFuPw0KDQpLYXJlbmEgcGVuZ2d1bmFhbiBzZWx1cnVoIGRhdGFzZXQgZGFwYXQgbWVueWViYWJrYW4gKipkYXRhIGxlYWthZ2UqKi4NCg0KTWlzYWxueWEga2l0YSBtZW5naGl0dW5nIG1lZGlhbiwgbWVhbiwgc3RhbmRhciBkZXZpYXNpLCBtaW5pbXVtLCBhdGF1DQptYWtzaW11bSBtZW5nZ3VuYWthbiBkYXRhIHRyYWluaW5nIGRhbiB0ZXN0aW5nIHNla2FsaWd1cy4gSW5mb3JtYXNpIGRhcmkNCnRlc3Rpbmcga2VtdWRpYW4gaWt1dCBtZW5lbnR1a2FuIGJhZ2FpbWFuYSBkYXRhIHRyYWluaW5nIGRpcHJvc2VzLg0KDQpBa2liYXRueWEgZXZhbHVhc2kgbW9kZWwgbWVuamFkaSB0ZXJsYWx1IG9wdGltaXN0aXMga2FyZW5hIG1vZGVsIHNlY2FyYQ0KdGlkYWsgbGFuZ3N1bmcgdGVsYWggbWVtcGVyb2xlaCBpbmZvcm1hc2kgZGFyaSBkYXRhIHRlc3RpbmcuDQoNClByb3NlZHVyIHlhbmcgYmVuYXIgYWRhbGFoOg0KDQoqKlRyYWluaW5nIGRhdGEg4oaSIGhpdHVuZyBwYXJhbWV0ZXIg4oaSIGd1bmFrYW4gcGFyYW1ldGVyIHBhZGEgdHJhaW5pbmcgZGFuIHRlc3RpbmcuKioNCg0KRGVuZ2FuIGRlbWlraWFuIHRlc3RpbmcgdGV0YXAgbWVuamFkaSBkYXRhIHlhbmcgYmVuYXItYmVuYXIgYmVsdW0NCmRpZ3VuYWthbiBkYWxhbSBwcm9zZXMgcGVtYmVudHVrYW4gbW9kZWwuDQoNCjwvZGl2Pg0KDQo8ZGl2IGNsYXNzPSJxdWVzdGlvbiI+DQoNCiMjIDUuIEFwYSByaXNpa28gaW50ZWdyYXNpIGRhdGEgamlrYSBpZGVudGlmaWVyIHRpZGFrIHVuaWs/DQoNCklkZW50aWZpZXIgeWFuZyB0aWRhayB1bmlrIGRhcGF0IG1lbnllYmFia2FuICoqbWFueS10by1tYW55IG1hdGNoaW5nKioNCmF0YXUgcGVuZ2dhbmRhYW4gYmFyaXMgc2FhdCBkdWEgZGF0YXNldCBkaWdhYnVuZ2thbi4NCg0KQ29udG9obnlhLCBqaWthIHNhdHUgYGN1c3RvbWVyX2lkYCBtdW5jdWwgZHVhIGthbGkgcGFkYSB0YWJlbCBwZWxhbmdnYW4gZGFuDQpkdWEga2FsaSBwYWRhIHRhYmVsIHRyYW5zYWtzaSwgcHJvc2VzIGpvaW4gZGFwYXQgbWVuZ2hhc2lsa2FuIGVtcGF0DQprb21iaW5hc2kgYmFyaXMuDQoNCkFraWJhdG55YToNCg0KLSBqdW1sYWggb2JzZXJ2YXNpIG1lbmphZGkgdGVybGFsdSBiZXNhcjsNCi0gdG90YWwgdHJhbnNha3NpIGRhcGF0IHRlcmhpdHVuZyBsZWJpaCBkYXJpIHNla2FsaTsNCi0gbmlsYWkgYWdyZWdhdCBtZW5qYWRpIHNhbGFoOw0KLSBhbmFsaXNpcyBzdGF0aXN0aWsgZGFwYXQgYmlhczsNCi0gaHVidW5nYW4gYW50YXItcmVjb3JkIG1lbmphZGkgYW1iaWd1Lg0KDQpLYXJlbmEgaXR1IHNlYmVsdW0gaW50ZWdyYXNpIGhhcnVzIGRpcGVyaWtzYSBhcGFrYWggaWRlbnRpZmllciBtZW1hbmcNCnVuaWsgcGFkYSB0YWJlbCB5YW5nIHNlaGFydXNueWEgbWVtaWxpa2kgc2F0dSByZWNvcmQgcGVyIGVudGl0YXMuIE1hdGVyaQ0KanVnYSBzZWNhcmEgZWtzcGxpc2l0IG1lbWVyaWtzYSBkdXBsaWthc2kga2V5IHNlYmVsdW0gbWVsYWt1a2FuIGludGVncmFzaS4NCg0KPC9kaXY+DQoNCg0KIyAxMC4gUmluZ2thc2FuDQoNCkFsdXIgcHJlcHJvY2Vzc2luZyB5YW5nIGRpbGFrdWthbiBhZGFsYWg6DQoNCjEuIG1lbWFoYW1pIHN0cnVrdHVyIGRhbiBrdWFsaXRhcyBhd2FsIGRhdGE7DQoyLiBtZWxha3VrYW4gYXVkaXQgZGF0YTsNCjMuIG1lbWJ1YXQgc2FsaW5hbiBkYXRhIGtlcmphOw0KNC4gbWVtYmVyc2loa2FuIHNwYXNpIGRhbiBrYXBpdGFsaXNhc2k7DQo1LiBtZW55ZXJhZ2Fta2FuIGthdGVnb3JpOw0KNi4gbWVuZGV0ZWtzaSBkYW4gbWVuYW5nYW5pIGR1cGxpa2FzaTsNCjcuIG1lbWVyaWtzYSBhdHVyYW4gZG9tYWluOw0KOC4gbWVtYnVhdCBsb2cgcGVydWJhaGFuOw0KOS4gbWVuZ2lkZW50aWZpa2FzaSBtaXNzaW5nIHZhbHVlOw0KMTAuIG1lbWJhbmRpbmdrYW4gc3RyYXRlZ2kgcGVuYW5nYW5hbiBtaXNzaW5nOw0KMTEuIG1lbGFrdWthbiBpbXB1dGFzaTsNCjEyLiBtZW1idWF0IGluZGlrYXRvciBtaXNzaW5nOw0KMTMuIG1lbmRldGVrc2kgb3V0bGllciBtZW5nZ3VuYWthbiBJUVI7DQoxNC4gbWVuZ2V2YWx1YXNpIHRpbmRha2FuIHRlcmhhZGFwIG91dGxpZXI7DQoxNS4gbWVsYWt1a2FuIHdpbnNvcml6aW5nIHNlYmFnYWkgZGVtb25zdHJhc2k7DQoxNi4gbWVsYWt1a2FuIG1pbi1tYXggbm9ybWFsaXphdGlvbjsNCjE3LiBtZWxha3VrYW4gei1zY29yZTsNCjE4LiBtZWxha3VrYW4gZGVjaW1hbCBzY2FsaW5nOw0KMTkuIG1lbWJhbmRpbmdrYW4gbWV0b2RlIHRyYW5zZm9ybWFzaTsNCjIwLiBtZW5nZXZhbHVhc2kgZGFtcGFrIG91dGxpZXIgdGVyaGFkYXAgbm9ybWFsaXNhc2k7DQoyMS4gbWVtZXJpa3NhIGlkZW50aWZpZXIgcGFkYSBkdWEgc3VtYmVyOw0KMjIuIG1lbGFrdWthbiBpbnRlZ3Jhc2kgZGF0YTsNCjIzLiBtZW12YWxpZGFzaSBoYXNpbCBpbnRlZ3Jhc2k7DQoyNC4gbWVtYmFuZ3VuIGRhdGFzZXQgYWtoaXI7DQoyNS4gbWVsYWt1a2FuIGF1ZGl0IGFraGlyLg0KDQo8ZGl2IGNsYXNzPSJib3ggc3VjY2VzcyI+DQoNCiMjIyBLZXNpbXB1bGFuDQoNCkRhdGEgcHJlcHJvY2Vzc2luZyBidWthbiBzZWthZGFyIG1lbWJ1YXQgZGF0YXNldCBtZW5qYWRpICJiZXJzaWgiLg0KDQpQcmVwcm9jZXNzaW5nIG1lcnVwYWthbiBwcm9zZXMgcGVuZ2FtYmlsYW4ga2VwdXR1c2FuLiBTZXRpYXAgcGVydWJhaGFuDQp0ZXJoYWRhcCBkYXRhIGhhcnVzIG1lbWlsaWtpIGFsYXNhbiwgbWVtcGVydGltYmFuZ2thbiBrb250ZWtzIGRhdGEsIGRhcGF0DQpkaXRlbHVzdXJpIG1lbGFsdWkgZG9rdW1lbnRhc2ksIGRhbiBkaWV2YWx1YXNpIGtlbWJhbGkgc2V0ZWxhaCBkaWxha3VrYW4uDQoNCkRhdGFzZXQgeWFuZyBiYWlrIGJ1a2FuIGhhbnlhIGRhdGFzZXQgeWFuZyB0aWRhayBtZW1pbGlraSBgTkFgLCB0ZXRhcGkNCmRhdGFzZXQgeWFuZyAqKmFrdXJhdCwga29uc2lzdGVuLCBkYXBhdCBkaXBlcmNheWEsIGRhcGF0IGRpcGFoYW1pLCBkYW4NCnNlc3VhaSBkZW5nYW4gdHVqdWFuIGFuYWxpc2lzKiouDQoNCjwvZGl2Pg0KDQoNCiMgMTEuIE1lbnlpbXBhbiBIYXNpbA0KDQpgYGB7cn0NCg0Kd3JpdGUuY3N2KA0KICBkYXRhX2ZpbmFsLA0KICAiZGF0YV9maW5hbF9wcmVwcm9jZXNzaW5nX2JhcnUuY3N2IiwNCiAgcm93Lm5hbWVzID0gRkFMU0UNCikNCg0Kd3JpdGUuY3N2KA0KICBsb2dfcGVydWJhaGFuLA0KICAibG9nX3BlcnViYWhhbl9wcmVwcm9jZXNzaW5nX2JhcnUuY3N2IiwNCiAgcm93Lm5hbWVzID0gRkFMU0UNCikNCg0KYGBgDQoNCiMgRGFmdGFyIFB1c3Rha2ENCg0KSGFuLCBKLiwgS2FtYmVyLCBNLiwgJiBQZWksIEouICgyMDEyKS4gKkRhdGEgTWluaW5nOiBDb25jZXB0cyBhbmQNClRlY2huaXF1ZXMqICgzcmQgZWQuKS4gTW9yZ2FuIEthdWZtYW5uLiBDaGFwdGVyIDM6IERhdGEgUHJlcHJvY2Vzc2luZy4NCg==