Pemrograman Berorientasi Objek

Pemrograman berorientasi objek merupakan sebuah paradigma dalam pembuatan sebuah program. PBO menitikberatkan pada identifikasi objek-objek yang terlibat dalam sebuah program dan bagaimana objek-objek tersebut berinterakasi. Pada PBO, program yang dibangun akan dibagi-bagi menjadi objek-objek. Prinsip dasar dari PBO adalah abstraksi, enkapsulasi, inheritance (pewarisan), dan polymorphism. PBO menyediakan class dan object sebagai alat dasar untuk meminimalisir dan mengatur kompleksitas dari program.

Class (kelas)

Merupakan definisi statik (kerangka dasar) dari objek yang akan diciptakan. Suatu class dibagi menjadi:

  • Property : data atau state yang dimiliki oleh class. Contoh pada class Mobil, memiliki property: warna, Model, Produsen.
  • Method : behavior (perilaku) sebuah class. Bisa dikatakan sebagai aksi atau tindakan yang bisa dilakukan oleh suatu class. Contoh pada class Mobil, memiliki method: Start, Stop, Change Gear, Turn.

Object

Objek adalah komponen yang diciptakan dari class (instance of class). Satu class bisa menghasilkan banyak objek. Proses untuk membuat sebuah objek disebut instantiation. Setiap objek memiliki karakteristik dan fitur masing masing. Objek memiliki siklus creation, manipulation, dan destruction.

Pengecekan objek data pada R bisa menggunakan fungsi is.object() Vektor

A<-c(1:10)
is.vector(A)
[1] TRUE
typeof(A)
[1] "integer"
is.object(A)
[1] FALSE

Matriks

matriks = matrix(1:20,ncol=4,byrow = T)
is.matrix(matriks)
[1] TRUE
typeof(matriks)
[1] "integer"
is.object(matriks)
[1] FALSE

Array

arai<-array(c(1:10),c(2,5)) 
is.array(arai) 
[1] TRUE
typeof(arai)
[1] "integer"
is.object(arai)
[1] FALSE

Data frame

a<-1:12
b<-letters[1:12]
data1<-data.frame(a,b)
is.data.frame(data1)
[1] TRUE
typeof(data1)
[1] "list"
is.object(data1)
[1] TRUE

Metode

set.seed(55)
y<-10+a+rnorm(12)
f.y<-lm(y~a)
typeof(f.y)
[1] "list"
is.object(f.y)
[1] TRUE

Hasil suatu metode dapat dikenali sebagai objek di R.

Objek yang dianggap oleh R setidaknya memiliki tipe data/ typeof(nama.objek) = “list”. Adapun tingkatan tipe data dalam R (dari rendah ke tinggi) adalah sebagai berikut: vector < matrix < array < list < data frame

Pemrograman Berorientasi Objek di R

R telah mengimplementasikan pemrograman berorientasi objek. Semua dalam R adalah objek. Pengembangan awal objek di R menggunakan Class System S3 yang tidak terlalu ketat. Pendefinisian yang ketat secara formal, R menggunakan Class System S4.

Objek: Class System S3

Suatu class dalam S3 tidak didefinisikan dengan ketat. Fungsi class digunakan untuk menjadikan sebuah objek menjadi class yang diinginkan.

pts <- list (x=round(rnorm(5),2), y=round(rnorm(5),2))
class(pts)
[1] "list"
pts
$x
[1]  1.13 -0.41 -2.00 -2.27 -0.11

$y
[1]  0.78 -0.67 -0.69  0.91 -0.27

Menjadikan pts sebagai class baru secara langsung:

class(pts) <- "coords"
class(pts)
[1] "coords"
pts
$x
[1]  1.13 -0.41 -2.00 -2.27 -0.11

$y
[1]  0.78 -0.67 -0.69  0.91 -0.27

attr(,"class")
[1] "coords"

Langkah sederhana membuat objek di atas sangat tidak dianjurkan karena nilai-nilai instan-nya mungkin tidak tepat.

Konstruktor dan Aksesor

Fungsi konstruktor dibutuhkan untuk mengecek instan sesuai dengan objek, sementara Fungsi aksesor dibutuhkan untuk mengakses data pada suatu class.

Konstruktor

Misalnya berikut adalah ketentuan class coords

  • x dan y harus berupa numerik
  • vektor tidak boleh NA, NaN, Inf
  • vektor harus memiliki panjang yang sama

Fungsi konstruktor untuk membuat class coords:

coords <- function(x, y) {
  if (!is.numeric(x) || !is.numeric(y) || 
      !all(is.finite(x)) || !all(is.finite(y)))
    stop("Titik koordinat tidak tepat!")
  if (length(x) != length(y))
    stop("Panjang koordinat berbeda")
  pts <- list(x=x, y=y)
  class(pts) = "coords"
  pts
}
pts <- coords(x = round(rnorm(5),2), y = round(rnorm(5),2))
pts
$x
[1]  0.31 -0.59  1.08 -0.96 -0.78

$y
[1] -1.16  1.50 -0.50 -0.31  1.68

attr(,"class")
[1] "coords"

Aksesor

Mengakses data dalam class coords dapat menggunakan akses objek awalnya(list)

pts[1]
$x
[1]  0.31 -0.59  1.08 -0.96 -0.78
pts[2]
$y
[1] -1.16  1.50 -0.50 -0.31  1.68

Tetapi secara formal tidak dianjurkan mengakses data secara langsung (seperti contoh di atas).

Sehingga diperlukan fungsi aksesor untuk mengakses data pada class coords seperti berikut.

xcoords <- function(obj) obj$x
ycoords <- function(obj) obj$y
xcoords(pts)
[1]  0.31 -0.59  1.08 -0.96 -0.78
ycoords(pts)
[1] -1.16  1.50 -0.50 -0.31  1.68

Fungsi Generik

Fungsi generik merupakan suatu method dari suatu class objek dalam R. Syntax untuk mendefinisikan suatu fungsi generik: method.class <- function() ekspresibaru . Terdapat beberapa fungsi generik yang sudah ada seperti berikut.

Method Print

Method print merupakan cara menampilkan data pada suatu objek Class System S3.

print.coords <- function(obj) {
  print(paste("(",
              format(xcoords(obj)),
              ", ",
              format(ycoords(obj)),
              ")", sep=""),
        quote=FALSE)
}
pts
[1] ( 0.31, -1.16) (-0.59,  1.50) ( 1.08, -0.50) (-0.96, -0.31)
[5] (-0.78,  1.68)

Method Length

Fungsi length menghitung banyaknya anggota dari objek.

length(pts)
[1] 2

Penggunaan length di atas kurang tepat karena length akan menghitung banyaknya anggota list. Sehingga perlu mendefinisikan ulang fungsi length untuk class coords:

length.coords <- function(obj) length(xcoords(obj))
length(pts)
[1] 5

Membuat Fungsi Generik Baru

bbox <- function(obj)
  UseMethod("bbox") #menjadikan bbox sebagai fungsi generik


bbox.coords <- function(obj) {
  matrix(c(mean(xcoords(obj)),
           mean(ycoords(obj))),
         nc = 2, dimnames = list(
           c("mean"),
           c("x:", "y:")))
}
bbox(pts)
         x:    y:
mean -0.188 0.242

Method Plot

Berikut adalah fungsi untuk membuat plot khusus untuk class coords:

plot.coords <- function(obj, bbox=FALSE, ...) {
  if (bbox) {
    plot(xcoords(obj),ycoords(obj), ...);
    x <- c(bbox(obj)[1],bbox(obj)[2],
           bbox(obj)[2],bbox(obj)[1]);
    y <- c(bbox(obj)[3],bbox(obj)[3],
           bbox(obj)[4],bbox(obj)[4]);
    polygon(x,y)
  } else {
    plot(xcoords(obj),ycoords(obj), ...)
  }
}
plot(pts)


plot(pts, bbox=T,pch=19,col="blue")

Pewarisan Class

Diinginkan sebuah objek yang berisi lokasi (coords) dan terdapat nilai pada lokasi tersebut. Diperlukan menciptakan class baru vcoords sebagai turunan dari coords. Fungsi konstruktor dari class vcoords:

vcoords <- function(x, y, v) {
  if (!is.numeric(x) || !is.numeric(y) || !is.numeric(v) || 
      !all(is.finite(x)) || !all(is.finite(y)))
    stop("Titik koordinat tidak tepat!")
  if (length(x) != length(y) || length(x) != length(v))
    stop("Panjang koordinat berbeda")
  pts <- list(x=x, y=y, v=v)
  class(pts) = c("vcoords", "coords")
  pts
}
nilai <- function(obj) obj$v
vpts <- vcoords(x = round(rnorm(5), 2),
                y = round(rnorm(5), 2),
                v = round(runif(5,0,100)))
vpts
[1] ( 0.03, -0.55) ( 0.65,  1.39) (-0.34,  2.20) ( 1.25, -1.53)
[5] ( 0.29,  1.28)

Fungsi xcoords, ycoords, dan method bbox dari kelas coords masih sama sehingga tidak perlu didefinisi ulang (bisa langsung digunakan pada class vcoords)

xcoords(vpts)
[1]  0.03  0.65 -0.34  1.25  0.29
ycoords(vpts)
[1] -0.55  1.39  2.20 -1.53  1.28
bbox(vpts)
        x:    y:
mean 0.376 0.558

Method print juga diwariskan dari class coords, namun karena ada tambahan nilai sehingga perlu didefinisi ulang sebagai berikut.

print.vcoords <- function(obj) {
  print(paste("(",
              format(xcoords(obj)),
              ", ",
              format(ycoords(obj)),
              ": ", format(nilai(obj)),
              ")", sep=""),
           quote=FALSE)
   }

vpts
[1] ( 0.03, -0.55: 96) ( 0.65,  1.39: 68) (-0.34,  2.20: 52)
[4] ( 1.25, -1.53: 96) ( 0.29,  1.28: 66)

Begitu pula dengan method plot perlu didefinisi ulang karena ada tambahan nilai seperti berikut.

plot.vcoords <- function(obj, txt=FALSE, bbox=FALSE,...) {
  if (bbox) {
    if (!txt) {
      plot(xcoords(obj),ycoords(obj),...);
    } else {
      plot(xcoords(obj),ycoords(obj),type="n",...);
      text(xcoords(obj),ycoords(obj),nilai(obj),...);
    }
    x <- c(bbox(pts)[1],bbox(pts)[2],bbox(pts)[2],bbox(pts)[1]);
    y <- c(bbox(pts)[3],bbox(pts)[3],bbox(pts)[4],bbox(pts)[4]);
    polygon(x,y)
  } else {
    if (!txt) {
      plot(xcoords(obj),ycoords(obj),...);
    } else {
      plot(xcoords(obj),ycoords(obj),type="n",...);
      text(xcoords(obj),ycoords(obj),nilai(obj),...);
    }
  }
}
plot(vpts)

plot(vpts,txt=T,bbox=T,col="blue")

Transformasi dengan Matematik

Objek vcoords berisi slot numerik (nilai, bukan titik koordinat) yang dapat diubah dengan transformasi matematika.

Fungsi Matematik

Fungsi Cosinus

cos.vcoords <- function(obj){
  vcoords(xcoords(obj),
          ycoords(obj),
          cos(nilai(obj)))
}
cos.vcoords(vpts)
[1] ( 0.03, -0.55: -0.1804304) ( 0.65,  1.39:  0.4401430)
[3] (-0.34,  2.20: -0.1629908) ( 1.25, -1.53: -0.1804304)
[5] ( 0.29,  1.28: -0.9996475)

Fungsi Sinus

sin.vcoords <- function(obj){
  vcoords(xcoords(obj),
          ycoords(obj),
          sin(nilai(obj)))
}
sin.vcoords(vpts)
[1] ( 0.03, -0.55:  0.98358775) ( 0.65,  1.39: -0.89792768)
[3] (-0.34,  2.20:  0.98662759) ( 1.25, -1.53:  0.98358775)
[5] ( 0.29,  1.28: -0.02655115)

Method Group: Math

Class vcoords memungkinkan untuk dilakukan operasi aritmatika terhadap nilainya. Menggunakan fungsi get(.Generic) untuk mengambil fungsi dengan nama Math.vcoords yang dijalankan seperti berikut.

Math.vcoords <- function(obj) {
  vcoords(xcoords(obj),
          ycoords(obj),
          get(.Generic)(nilai(obj)))
}
sqrt(vpts)
[1] ( 0.03, -0.55: 9.797959) ( 0.65,  1.39: 8.246211)
[3] (-0.34,  2.20: 7.211103) ( 1.25, -1.53: 9.797959)
[5] ( 0.29,  1.28: 8.124038)
log(vpts)
[1] ( 0.03, -0.55: 4.564348) ( 0.65,  1.39: 4.219508)
[3] (-0.34,  2.20: 3.951244) ( 1.25, -1.53: 4.564348)
[5] ( 0.29,  1.28: 4.189655)
exp(vpts)
[1] ( 0.03, -0.55: 4.923458e+41) ( 0.65,  1.39: 3.404276e+29)
[3] (-0.34,  2.20: 3.831008e+22) ( 1.25, -1.53: 4.923458e+41)
[5] ( 0.29,  1.28: 4.607187e+28)

Operasi Aritmetika Method Groups Ops

Method Group Ops merupakan fungsi generik yang digunakan untuk mendefinisikan semua operasi biner (operasi antar 2 operand) dengan operator sebagai berikut:

  • "+", "-", "*", "/", "^", "%%", "%/%"
  • "&", "|", "!"
  • "==", "!=", "<", "<=", ">=", ">"

Supaya berjalan dengan tepat diperlukan lokasi dari vcoords yang dioperasikan adalah identik

sameloc <- function(e1,e2) {
  (length(nilai(e1))==length(nilai(e2))
   || all(xcoords(e1)==xcoords(e2))
   || all(ycoords(e1)==ycoords(e2))
   )
}
Ops.vcoords <- function(e1,e2) {
  if (!sameloc(e1,e2))
    stop ("Lokasi berbeda")
  else vcoords(xcoords(e1),
               ycoords(e1),
               get(.Generic) (nilai(e1),nilai(e2)))
}
vpts
[1] ( 0.03, -0.55: 96) ( 0.65,  1.39: 68) (-0.34,  2.20: 52)
[4] ( 1.25, -1.53: 96) ( 0.29,  1.28: 66)
vpts+vpts
[1] ( 0.03, -0.55: 192) ( 0.65,  1.39: 136) (-0.34,  2.20: 104)
[4] ( 1.25, -1.53: 192) ( 0.29,  1.28: 132)
vpts*vpts
[1] ( 0.03, -0.55: 9216) ( 0.65,  1.39: 4624) (-0.34,  2.20: 2704)
[4] ( 1.25, -1.53: 9216) ( 0.29,  1.28: 4356)

Method Subset

Method subset dapat diakses sesuai dengan kebutuhan, misal kondisi yang diinginkan: vpts[xcoords(vpts) < 0 & ycoords(vpts) < 0] Dapat ditangani dengan mendefinisikan method berikut.

`[.vcoords` <- function(x, i) {
  vcoords(xcoords(x)[i], ycoords(x)[i],
          nilai(x)[i])
}
vpts
[1] ( 0.03, -0.55: 96) ( 0.65,  1.39: 68) (-0.34,  2.20: 52)
[4] ( 1.25, -1.53: 96) ( 0.29,  1.28: 66)
vpts[1:3]
[1] ( 0.03, -0.55: 96) ( 0.65,  1.39: 68) (-0.34,  2.20: 52)
vpts[xcoords(vpts) < 0 & ycoords(vpts) < 0]
[1] (, : )

Pemeriksaan Suatu Class Objek

untuk mengecek apakah suatu objek merupakan suatu class digunakan fungsi inherits seperti berikut.

inherits(pts,"coords")
[1] TRUE
inherits(pts,"vcoords")
[1] FALSE
inherits(vpts,"coords")
[1] TRUE
inherits(vpts,"vcoords")
[1] TRUE

terlihat bahwa objek pts tidak termasuk ke dalam class vcoords. Sedangkan objek vpts termasuk ke dalam class coords, karena vpts merupakan turunan dari pts sehingga mendapat warisan (inheritance) class dari objek pts.

Penutup Class System S3

Class system S3 memberikan fasilitas object-oriented yang terlalu longgar sehingga banyak technical issue dalam sistem class ini. Sebagai contoh ekspresi berikut diperbolehkan dalam R

model <- 1:10
class(model) <- "lm"
class(model)
[1] "lm"

padahal class “lm” merupakan class untuk pemodelan linear.


  1. Mahasiswa Statistika dan Sains Data IPB, ↩︎

LS0tDQp0aXRsZTogIlBlbXJvZ3JhbWFuIEJlcm9yaWVudGFzaSBPYmplayBkYWxhbSBSIENsYXNzIFN5c3RlbSBTMyINCmF1dGhvcjogIkFubmlzc2EgTnVyIEZpdHJpYSBGYXRoaW5hXltNYWhhc2lzd2EgU3RhdGlzdGlrYSBkYW4gU2FpbnMgRGF0YSBJUEIsIGFubmlzc2FfbmZmQGFwcHMuaXBiLmFjLmlkXSINCmRhdGU6ICIyLzQvMjAyMiINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDMNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICB0aGVtZTogY29zbW8NCiAgICBoaWdobGlnaHQ6IGthdGUNCmVkaXRvcl9vcHRpb25zOiANCiAgbWFya2Rvd246IA0KICAgIHdyYXA6IHNlbnRlbmNlDQotLS0NCg0KIyBQZW1yb2dyYW1hbiBCZXJvcmllbnRhc2kgT2JqZWsNCg0KUGVtcm9ncmFtYW4gYmVyb3JpZW50YXNpIG9iamVrIG1lcnVwYWthbiBzZWJ1YWggcGFyYWRpZ21hIGRhbGFtIHBlbWJ1YXRhbiBzZWJ1YWggcHJvZ3JhbS4NClBCTyBtZW5pdGlrYmVyYXRrYW4gcGFkYSBpZGVudGlmaWthc2kgb2JqZWstb2JqZWsgeWFuZyB0ZXJsaWJhdCBkYWxhbSBzZWJ1YWggcHJvZ3JhbSBkYW4gYmFnYWltYW5hIG9iamVrLW9iamVrIHRlcnNlYnV0IGJlcmludGVyYWthc2kuDQpQYWRhIFBCTywgcHJvZ3JhbSB5YW5nIGRpYmFuZ3VuIGFrYW4gZGliYWdpLWJhZ2kgbWVuamFkaSBvYmplay1vYmplay4NClByaW5zaXAgZGFzYXIgZGFyaSBQQk8gYWRhbGFoIGFic3RyYWtzaSwgZW5rYXBzdWxhc2ksIGluaGVyaXRhbmNlIChwZXdhcmlzYW4pLCBkYW4gcG9seW1vcnBoaXNtLg0KUEJPIG1lbnllZGlha2FuICpjbGFzcyogZGFuICpvYmplY3QqIHNlYmFnYWkgYWxhdCBkYXNhciB1bnR1ayBtZW1pbmltYWxpc2lyIGRhbiBtZW5nYXR1ciBrb21wbGVrc2l0YXMgZGFyaSBwcm9ncmFtLg0KDQojIyBDbGFzcyAoa2VsYXMpDQoNCk1lcnVwYWthbiBkZWZpbmlzaSBzdGF0aWsgKGtlcmFuZ2thIGRhc2FyKSBkYXJpIG9iamVrIHlhbmcgYWthbiBkaWNpcHRha2FuLg0KU3VhdHUgY2xhc3MgZGliYWdpIG1lbmphZGk6DQoNCi0gICAqUHJvcGVydHkqIDogZGF0YSBhdGF1ICpzdGF0ZSogeWFuZyBkaW1pbGlraSBvbGVoICpjbGFzcyouIENvbnRvaCBwYWRhIGNsYXNzIE1vYmlsLCBtZW1pbGlraSBwcm9wZXJ0eTogd2FybmEsIE1vZGVsLCBQcm9kdXNlbi4NCi0gICAqTWV0aG9kKiA6ICpiZWhhdmlvciogKHBlcmlsYWt1KSBzZWJ1YWggKmNsYXNzKi4gQmlzYSBkaWthdGFrYW4gc2ViYWdhaSBha3NpIGF0YXUgdGluZGFrYW4geWFuZyBiaXNhIGRpbGFrdWthbiBvbGVoIHN1YXR1IGNsYXNzLiBDb250b2ggcGFkYSBjbGFzcyBNb2JpbCwgbWVtaWxpa2kgbWV0aG9kOiBTdGFydCwgU3RvcCwgQ2hhbmdlIEdlYXIsIFR1cm4uDQoNCiMjIE9iamVjdA0KDQpPYmplayBhZGFsYWgga29tcG9uZW4geWFuZyBkaWNpcHRha2FuIGRhcmkgY2xhc3MgKihpbnN0YW5jZSBvZiBjbGFzcykqLg0KU2F0dSBjbGFzcyBiaXNhIG1lbmdoYXNpbGthbiBiYW55YWsgb2JqZWsuDQpQcm9zZXMgdW50dWsgbWVtYnVhdCBzZWJ1YWggb2JqZWsgZGlzZWJ1dCBpbnN0YW50aWF0aW9uLg0KU2V0aWFwIG9iamVrIG1lbWlsaWtpIGthcmFrdGVyaXN0aWsgZGFuIGZpdHVyIG1hc2luZyBtYXNpbmcuDQpPYmplayBtZW1pbGlraSBzaWtsdXMgY3JlYXRpb24sIG1hbmlwdWxhdGlvbiwgZGFuIGRlc3RydWN0aW9uLg0KDQpQZW5nZWNla2FuIG9iamVrIGRhdGEgcGFkYSBSIGJpc2EgbWVuZ2d1bmFrYW4gZnVuZ3NpIGBpcy5vYmplY3QoKWAgDQoqVmVrdG9yKg0KYGBge3J9DQpBPC1jKDE6MTApDQppcy52ZWN0b3IoQSkNCnR5cGVvZihBKQ0KaXMub2JqZWN0KEEpDQpgYGANCg0KKk1hdHJpa3MqDQpgYGB7cn0NCm1hdHJpa3MgPSBtYXRyaXgoMToyMCxuY29sPTQsYnlyb3cgPSBUKQ0KaXMubWF0cml4KG1hdHJpa3MpDQp0eXBlb2YobWF0cmlrcykNCmlzLm9iamVjdChtYXRyaWtzKQ0KYGBgDQoNCipBcnJheSoNCmBgYHtyfQ0KYXJhaTwtYXJyYXkoYygxOjEwKSxjKDIsNSkpIA0KaXMuYXJyYXkoYXJhaSkgDQp0eXBlb2YoYXJhaSkNCmlzLm9iamVjdChhcmFpKQ0KYGBgDQoNCipEYXRhIGZyYW1lKg0KYGBge3J9DQphPC0xOjEyDQpiPC1sZXR0ZXJzWzE6MTJdDQpkYXRhMTwtZGF0YS5mcmFtZShhLGIpDQppcy5kYXRhLmZyYW1lKGRhdGExKQ0KdHlwZW9mKGRhdGExKQ0KaXMub2JqZWN0KGRhdGExKQ0KYGBgDQoNCipNZXRvZGUqDQpgYGB7cn0NCnNldC5zZWVkKDU1KQ0KeTwtMTArYStybm9ybSgxMikNCmYueTwtbG0oeX5hKQ0KdHlwZW9mKGYueSkNCmlzLm9iamVjdChmLnkpDQpgYGANCkhhc2lsIHN1YXR1IG1ldG9kZSBkYXBhdCBkaWtlbmFsaSBzZWJhZ2FpIG9iamVrIGRpIFIuDQoNCk9iamVrIHlhbmcgZGlhbmdnYXAgb2xlaCBSIHNldGlkYWtueWEgbWVtaWxpa2kgdGlwZSBkYXRhLyB0eXBlb2YobmFtYS5vYmplaykgPSAibGlzdCIuIA0KQWRhcHVuIHRpbmdrYXRhbiB0aXBlIGRhdGEgZGFsYW0gUiAoZGFyaSByZW5kYWgga2UgdGluZ2dpKSBhZGFsYWggc2ViYWdhaSBiZXJpa3V0Og0KdmVjdG9yIDwgbWF0cml4IDwgYXJyYXkgPCBsaXN0IDwgZGF0YSBmcmFtZQ0KDQojIyBQZW1yb2dyYW1hbiBCZXJvcmllbnRhc2kgT2JqZWsgZGkgUg0KUiB0ZWxhaCBtZW5naW1wbGVtZW50YXNpa2FuIHBlbXJvZ3JhbWFuIGJlcm9yaWVudGFzaSBvYmplay4gU2VtdWEgZGFsYW0gUiBhZGFsYWggb2JqZWsuIFBlbmdlbWJhbmdhbiBhd2FsIG9iamVrIGRpIFIgbWVuZ2d1bmFrYW4gKkNsYXNzIFN5c3RlbSBTMyogeWFuZyB0aWRhayB0ZXJsYWx1IGtldGF0LiBQZW5kZWZpbmlzaWFuIHlhbmcga2V0YXQgc2VjYXJhIGZvcm1hbCwgUiBtZW5nZ3VuYWthbiAqQ2xhc3MgU3lzdGVtIFM0Ki4NCg0KIyBPYmplazogQ2xhc3MgU3lzdGVtIFMzIA0KU3VhdHUgY2xhc3MgZGFsYW0gUzMgdGlkYWsgZGlkZWZpbmlzaWthbiBkZW5nYW4ga2V0YXQuDQpGdW5nc2kgYGNsYXNzYCBkaWd1bmFrYW4gdW50dWsgbWVuamFkaWthbiBzZWJ1YWggb2JqZWsgbWVuamFkaSBjbGFzcyB5YW5nIGRpaW5naW5rYW4uDQpgYGB7cn0NCnB0cyA8LSBsaXN0ICh4PXJvdW5kKHJub3JtKDUpLDIpLCB5PXJvdW5kKHJub3JtKDUpLDIpKQ0KY2xhc3MocHRzKQ0KcHRzDQpgYGANCg0KTWVuamFkaWthbiBgcHRzYCBzZWJhZ2FpIGNsYXNzIGJhcnUgc2VjYXJhIGxhbmdzdW5nOg0KYGBge3J9DQpjbGFzcyhwdHMpIDwtICJjb29yZHMiDQpjbGFzcyhwdHMpDQpwdHMNCmBgYA0KDQpMYW5na2FoIHNlZGVyaGFuYSBtZW1idWF0IG9iamVrIGRpIGF0YXMgc2FuZ2F0IHRpZGFrIGRpYW5qdXJrYW4ga2FyZW5hIG5pbGFpLW5pbGFpIGluc3Rhbi1ueWEgbXVuZ2tpbiB0aWRhayB0ZXBhdC4NCg0KIyBLb25zdHJ1a3RvciBkYW4gQWtzZXNvcg0KRnVuZ3NpIGtvbnN0cnVrdG9yIGRpYnV0dWhrYW4gdW50dWsgbWVuZ2VjZWsgaW5zdGFuIHNlc3VhaSBkZW5nYW4gb2JqZWssIHNlbWVudGFyYSBGdW5nc2kgYWtzZXNvciBkaWJ1dHVoa2FuIHVudHVrIG1lbmdha3NlcyBkYXRhIHBhZGEgc3VhdHUgY2xhc3MuDQoNCiMjIEtvbnN0cnVrdG9yDQpNaXNhbG55YSBiZXJpa3V0IGFkYWxhaCBrZXRlbnR1YW4gY2xhc3MgY29vcmRzDQoNCi0gICB4IGRhbiB5IGhhcnVzIGJlcnVwYSBudW1lcmlrDQotICAgdmVrdG9yIHRpZGFrIGJvbGVoIE5BLCBOYU4sIEluZg0KLSAgIHZla3RvciBoYXJ1cyBtZW1pbGlraSBwYW5qYW5nIHlhbmcgc2FtYQ0KDQpGdW5nc2kga29uc3RydWt0b3IgdW50dWsgbWVtYnVhdCBjbGFzcyBjb29yZHM6DQpgYGB7cn0NCmNvb3JkcyA8LSBmdW5jdGlvbih4LCB5KSB7DQogIGlmICghaXMubnVtZXJpYyh4KSB8fCAhaXMubnVtZXJpYyh5KSB8fCANCiAgICAgICFhbGwoaXMuZmluaXRlKHgpKSB8fCAhYWxsKGlzLmZpbml0ZSh5KSkpDQogICAgc3RvcCgiVGl0aWsga29vcmRpbmF0IHRpZGFrIHRlcGF0ISIpDQogIGlmIChsZW5ndGgoeCkgIT0gbGVuZ3RoKHkpKQ0KICAgIHN0b3AoIlBhbmphbmcga29vcmRpbmF0IGJlcmJlZGEiKQ0KICBwdHMgPC0gbGlzdCh4PXgsIHk9eSkNCiAgY2xhc3MocHRzKSA9ICJjb29yZHMiDQogIHB0cw0KfQ0KcHRzIDwtIGNvb3Jkcyh4ID0gcm91bmQocm5vcm0oNSksMiksIHkgPSByb3VuZChybm9ybSg1KSwyKSkNCnB0cw0KDQpgYGANCg0KIyMgQWtzZXNvcg0KTWVuZ2Frc2VzIGRhdGEgZGFsYW0gY2xhc3MgYGNvb3Jkc2AgZGFwYXQgbWVuZ2d1bmFrYW4gYWtzZXMgb2JqZWsgYXdhbG55YShsaXN0KQ0KYGBge3J9DQpwdHNbMV0NCnB0c1syXQ0KYGBgDQoNClRldGFwaSBzZWNhcmEgZm9ybWFsIHRpZGFrIGRpYW5qdXJrYW4gbWVuZ2Frc2VzIGRhdGEgc2VjYXJhIGxhbmdzdW5nIChzZXBlcnRpIGNvbnRvaCBkaSBhdGFzKS4NCg0KU2VoaW5nZ2EgZGlwZXJsdWthbiBmdW5nc2kgYWtzZXNvciB1bnR1ayBtZW5nYWtzZXMgZGF0YSBwYWRhIGNsYXNzIGBjb29yZHNgIHNlcGVydGkgYmVyaWt1dC4NCmBgYHtyfQ0KeGNvb3JkcyA8LSBmdW5jdGlvbihvYmopIG9iaiR4DQp5Y29vcmRzIDwtIGZ1bmN0aW9uKG9iaikgb2JqJHkNCnhjb29yZHMocHRzKQ0KeWNvb3JkcyhwdHMpDQpgYGANCg0KIyBGdW5nc2kgR2VuZXJpaw0KRnVuZ3NpIGdlbmVyaWsgbWVydXBha2FuIHN1YXR1IG1ldGhvZCBkYXJpIHN1YXR1IGNsYXNzIG9iamVrIGRhbGFtIFIuIFN5bnRheCB1bnR1ayBtZW5kZWZpbmlzaWthbiBzdWF0dSBmdW5nc2kgZ2VuZXJpazoNCmBtZXRob2QuY2xhc3MgPC0gZnVuY3Rpb24oKSBla3NwcmVzaWJhcnVgIC4gDQpUZXJkYXBhdCBiZWJlcmFwYSBmdW5nc2kgZ2VuZXJpayB5YW5nIHN1ZGFoIGFkYSBzZXBlcnRpIGJlcmlrdXQuDQoNCiMjIE1ldGhvZCBQcmludA0KTWV0aG9kIHByaW50IG1lcnVwYWthbiBjYXJhIG1lbmFtcGlsa2FuIGRhdGEgcGFkYSBzdWF0dSBvYmplayBDbGFzcyBTeXN0ZW0gUzMuDQpgYGB7cn0NCnByaW50LmNvb3JkcyA8LSBmdW5jdGlvbihvYmopIHsNCiAgcHJpbnQocGFzdGUoIigiLA0KICAgICAgICAgICAgICBmb3JtYXQoeGNvb3JkcyhvYmopKSwNCiAgICAgICAgICAgICAgIiwgIiwNCiAgICAgICAgICAgICAgZm9ybWF0KHljb29yZHMob2JqKSksDQogICAgICAgICAgICAgICIpIiwgc2VwPSIiKSwNCiAgICAgICAgcXVvdGU9RkFMU0UpDQp9DQpwdHMNCmBgYA0KDQojIyBNZXRob2QgTGVuZ3RoDQpGdW5nc2kgbGVuZ3RoIG1lbmdoaXR1bmcgYmFueWFrbnlhIGFuZ2dvdGEgZGFyaSBvYmplay4NCmBgYHtyfQ0KbGVuZ3RoKHB0cykNCmBgYA0KUGVuZ2d1bmFhbiBsZW5ndGggZGkgYXRhcyBrdXJhbmcgdGVwYXQga2FyZW5hIGxlbmd0aCBha2FuIG1lbmdoaXR1bmcgYmFueWFrbnlhIGFuZ2dvdGEgbGlzdC4NClNlaGluZ2dhIHBlcmx1IG1lbmRlZmluaXNpa2FuIHVsYW5nIGZ1bmdzaSBsZW5ndGggdW50dWsgY2xhc3MgY29vcmRzOg0KYGBge3J9DQpsZW5ndGguY29vcmRzIDwtIGZ1bmN0aW9uKG9iaikgbGVuZ3RoKHhjb29yZHMob2JqKSkNCmxlbmd0aChwdHMpDQpgYGANCg0KIyMgTWVtYnVhdCBGdW5nc2kgR2VuZXJpayBCYXJ1DQpgYGB7cn0NCmJib3ggPC0gZnVuY3Rpb24ob2JqKQ0KICBVc2VNZXRob2QoImJib3giKSAjbWVuamFkaWthbiBiYm94IHNlYmFnYWkgZnVuZ3NpIGdlbmVyaWsNCg0KDQpiYm94LmNvb3JkcyA8LSBmdW5jdGlvbihvYmopIHsNCiAgbWF0cml4KGMobWVhbih4Y29vcmRzKG9iaikpLA0KICAgICAgICAgICBtZWFuKHljb29yZHMob2JqKSkpLA0KICAgICAgICAgbmMgPSAyLCBkaW1uYW1lcyA9IGxpc3QoDQogICAgICAgICAgIGMoIm1lYW4iKSwNCiAgICAgICAgICAgYygieDoiLCAieToiKSkpDQp9DQpiYm94KHB0cykNCmBgYA0KDQojIyBNZXRob2QgUGxvdA0KQmVyaWt1dCBhZGFsYWggZnVuZ3NpIHVudHVrIG1lbWJ1YXQgcGxvdCBraHVzdXMgdW50dWsgY2xhc3MgY29vcmRzOg0KYGBge3J9DQpwbG90LmNvb3JkcyA8LSBmdW5jdGlvbihvYmosIGJib3g9RkFMU0UsIC4uLikgew0KICBpZiAoYmJveCkgew0KICAgIHBsb3QoeGNvb3JkcyhvYmopLHljb29yZHMob2JqKSwgLi4uKTsNCiAgICB4IDwtIGMoYmJveChvYmopWzFdLGJib3gob2JqKVsyXSwNCiAgICAgICAgICAgYmJveChvYmopWzJdLGJib3gob2JqKVsxXSk7DQogICAgeSA8LSBjKGJib3gob2JqKVszXSxiYm94KG9iailbM10sDQogICAgICAgICAgIGJib3gob2JqKVs0XSxiYm94KG9iailbNF0pOw0KICAgIHBvbHlnb24oeCx5KQ0KICB9IGVsc2Ugew0KICAgIHBsb3QoeGNvb3JkcyhvYmopLHljb29yZHMob2JqKSwgLi4uKQ0KICB9DQp9DQpwbG90KHB0cykNCmBgYA0KDQpgYGB7cn0NCg0KcGxvdChwdHMsIGJib3g9VCxwY2g9MTksY29sPSJibHVlIikNCmBgYA0KDQojIFBld2FyaXNhbiBDbGFzcw0KRGlpbmdpbmthbiBzZWJ1YWggb2JqZWsgeWFuZyBiZXJpc2kgbG9rYXNpIChjb29yZHMpIGRhbiB0ZXJkYXBhdCBuaWxhaSBwYWRhIGxva2FzaSB0ZXJzZWJ1dC4NCkRpcGVybHVrYW4gbWVuY2lwdGFrYW4gY2xhc3MgYmFydSB2Y29vcmRzIHNlYmFnYWkgdHVydW5hbiBkYXJpIGNvb3Jkcy4NCkZ1bmdzaSBrb25zdHJ1a3RvciBkYXJpIGNsYXNzIHZjb29yZHM6DQpgYGB7cn0NCnZjb29yZHMgPC0gZnVuY3Rpb24oeCwgeSwgdikgew0KICBpZiAoIWlzLm51bWVyaWMoeCkgfHwgIWlzLm51bWVyaWMoeSkgfHwgIWlzLm51bWVyaWModikgfHwgDQogICAgICAhYWxsKGlzLmZpbml0ZSh4KSkgfHwgIWFsbChpcy5maW5pdGUoeSkpKQ0KICAgIHN0b3AoIlRpdGlrIGtvb3JkaW5hdCB0aWRhayB0ZXBhdCEiKQ0KICBpZiAobGVuZ3RoKHgpICE9IGxlbmd0aCh5KSB8fCBsZW5ndGgoeCkgIT0gbGVuZ3RoKHYpKQ0KICAgIHN0b3AoIlBhbmphbmcga29vcmRpbmF0IGJlcmJlZGEiKQ0KICBwdHMgPC0gbGlzdCh4PXgsIHk9eSwgdj12KQ0KICBjbGFzcyhwdHMpID0gYygidmNvb3JkcyIsICJjb29yZHMiKQ0KICBwdHMNCn0NCm5pbGFpIDwtIGZ1bmN0aW9uKG9iaikgb2JqJHYNCnZwdHMgPC0gdmNvb3Jkcyh4ID0gcm91bmQocm5vcm0oNSksIDIpLA0KICAgICAgICAgICAgICAgIHkgPSByb3VuZChybm9ybSg1KSwgMiksDQogICAgICAgICAgICAgICAgdiA9IHJvdW5kKHJ1bmlmKDUsMCwxMDApKSkNCnZwdHMNCmBgYA0KDQpGdW5nc2kgeGNvb3JkcywgeWNvb3JkcywgZGFuIG1ldGhvZCBiYm94IGRhcmkga2VsYXMgY29vcmRzIG1hc2loIHNhbWEgc2VoaW5nZ2EgdGlkYWsgcGVybHUgZGlkZWZpbmlzaSB1bGFuZyAoYmlzYSBsYW5nc3VuZyBkaWd1bmFrYW4gcGFkYSBjbGFzcyB2Y29vcmRzKQ0KYGBge3J9DQp4Y29vcmRzKHZwdHMpDQp5Y29vcmRzKHZwdHMpDQpiYm94KHZwdHMpDQpgYGANCg0KTWV0aG9kIHByaW50IGp1Z2EgZGl3YXJpc2thbiBkYXJpIGNsYXNzIGNvb3JkcywgbmFtdW4ga2FyZW5hIGFkYSB0YW1iYWhhbiBuaWxhaSBzZWhpbmdnYSBwZXJsdSBkaWRlZmluaXNpIHVsYW5nIHNlYmFnYWkgYmVyaWt1dC4NCmBgYHtyfQ0KcHJpbnQudmNvb3JkcyA8LSBmdW5jdGlvbihvYmopIHsNCiAgcHJpbnQocGFzdGUoIigiLA0KICAgICAgICAgICAgICBmb3JtYXQoeGNvb3JkcyhvYmopKSwNCiAgICAgICAgICAgICAgIiwgIiwNCiAgICAgICAgICAgICAgZm9ybWF0KHljb29yZHMob2JqKSksDQogICAgICAgICAgICAgICI6ICIsIGZvcm1hdChuaWxhaShvYmopKSwNCiAgICAgICAgICAgICAgIikiLCBzZXA9IiIpLA0KICAgICAgICAgICBxdW90ZT1GQUxTRSkNCiAgIH0NCg0KdnB0cw0KYGBgDQoNCkJlZ2l0dSBwdWxhIGRlbmdhbiBtZXRob2QgcGxvdCBwZXJsdSBkaWRlZmluaXNpIHVsYW5nIGthcmVuYSBhZGEgdGFtYmFoYW4gbmlsYWkgc2VwZXJ0aSBiZXJpa3V0Lg0KYGBge3J9DQpwbG90LnZjb29yZHMgPC0gZnVuY3Rpb24ob2JqLCB0eHQ9RkFMU0UsIGJib3g9RkFMU0UsLi4uKSB7DQogIGlmIChiYm94KSB7DQogICAgaWYgKCF0eHQpIHsNCiAgICAgIHBsb3QoeGNvb3JkcyhvYmopLHljb29yZHMob2JqKSwuLi4pOw0KICAgIH0gZWxzZSB7DQogICAgICBwbG90KHhjb29yZHMob2JqKSx5Y29vcmRzKG9iaiksdHlwZT0ibiIsLi4uKTsNCiAgICAgIHRleHQoeGNvb3JkcyhvYmopLHljb29yZHMob2JqKSxuaWxhaShvYmopLC4uLik7DQogICAgfQ0KICAgIHggPC0gYyhiYm94KHB0cylbMV0sYmJveChwdHMpWzJdLGJib3gocHRzKVsyXSxiYm94KHB0cylbMV0pOw0KICAgIHkgPC0gYyhiYm94KHB0cylbM10sYmJveChwdHMpWzNdLGJib3gocHRzKVs0XSxiYm94KHB0cylbNF0pOw0KICAgIHBvbHlnb24oeCx5KQ0KICB9IGVsc2Ugew0KICAgIGlmICghdHh0KSB7DQogICAgICBwbG90KHhjb29yZHMob2JqKSx5Y29vcmRzKG9iaiksLi4uKTsNCiAgICB9IGVsc2Ugew0KICAgICAgcGxvdCh4Y29vcmRzKG9iaikseWNvb3JkcyhvYmopLHR5cGU9Im4iLC4uLik7DQogICAgICB0ZXh0KHhjb29yZHMob2JqKSx5Y29vcmRzKG9iaiksbmlsYWkob2JqKSwuLi4pOw0KICAgIH0NCiAgfQ0KfQ0KcGxvdCh2cHRzKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdCh2cHRzLHR4dD1ULGJib3g9VCxjb2w9ImJsdWUiKQ0KYGBgDQoNCiMgVHJhbnNmb3JtYXNpIGRlbmdhbiBNYXRlbWF0aWsNCk9iamVrIHZjb29yZHMgYmVyaXNpIHNsb3QgbnVtZXJpayAobmlsYWksIGJ1a2FuIHRpdGlrIGtvb3JkaW5hdCkgeWFuZyBkYXBhdCBkaXViYWggZGVuZ2FuIHRyYW5zZm9ybWFzaSBtYXRlbWF0aWthLg0KDQojIyBGdW5nc2kgTWF0ZW1hdGlrDQoqRnVuZ3NpIENvc2ludXMqDQpgYGB7cn0NCmNvcy52Y29vcmRzIDwtIGZ1bmN0aW9uKG9iail7DQogIHZjb29yZHMoeGNvb3JkcyhvYmopLA0KICAgICAgICAgIHljb29yZHMob2JqKSwNCiAgICAgICAgICBjb3MobmlsYWkob2JqKSkpDQp9DQpjb3MudmNvb3Jkcyh2cHRzKQ0KYGBgDQoNCipGdW5nc2kgU2ludXMqDQpgYGB7cn0NCnNpbi52Y29vcmRzIDwtIGZ1bmN0aW9uKG9iail7DQogIHZjb29yZHMoeGNvb3JkcyhvYmopLA0KICAgICAgICAgIHljb29yZHMob2JqKSwNCiAgICAgICAgICBzaW4obmlsYWkob2JqKSkpDQp9DQpzaW4udmNvb3Jkcyh2cHRzKQ0KYGBgDQoNCiMjIE1ldGhvZCBHcm91cDogTWF0aA0KQ2xhc3MgdmNvb3JkcyBtZW11bmdraW5rYW4gdW50dWsgZGlsYWt1a2FuIG9wZXJhc2kgYXJpdG1hdGlrYSB0ZXJoYWRhcCBuaWxhaW55YS4gTWVuZ2d1bmFrYW4gZnVuZ3NpIGdldCguR2VuZXJpYykgdW50dWsgbWVuZ2FtYmlsIGZ1bmdzaSBkZW5nYW4gbmFtYSBNYXRoLnZjb29yZHMgeWFuZyBkaWphbGFua2FuIHNlcGVydGkgYmVyaWt1dC4NCmBgYHtyfQ0KTWF0aC52Y29vcmRzIDwtIGZ1bmN0aW9uKG9iaikgew0KICB2Y29vcmRzKHhjb29yZHMob2JqKSwNCiAgICAgICAgICB5Y29vcmRzKG9iaiksDQogICAgICAgICAgZ2V0KC5HZW5lcmljKShuaWxhaShvYmopKSkNCn0NCnNxcnQodnB0cykNCmxvZyh2cHRzKQ0KZXhwKHZwdHMpDQpgYGANCg0KIyBPcGVyYXNpIEFyaXRtZXRpa2EgTWV0aG9kIEdyb3VwcyBPcHMNCk1ldGhvZCBHcm91cCBPcHMgbWVydXBha2FuIGZ1bmdzaSBnZW5lcmlrIHlhbmcgZGlndW5ha2FuIHVudHVrIG1lbmRlZmluaXNpa2FuIHNlbXVhIG9wZXJhc2kgYmluZXIgKG9wZXJhc2kgYW50YXIgMiBvcGVyYW5kKSBkZW5nYW4gb3BlcmF0b3Igc2ViYWdhaSBiZXJpa3V0Og0KDQotICAgYCIrIiwgIi0iLCAiKiIsICIvIiwgIl4iLCAiJSUiLCAiJS8lImANCi0gICBgIiYiLCAifCIsICIhImANCi0gICBgIj09IiwgIiE9IiwgIjwiLCAiPD0iLCAiPj0iLCAiPiJgDQoNClN1cGF5YSBiZXJqYWxhbiBkZW5nYW4gdGVwYXQgZGlwZXJsdWthbiBsb2thc2kgZGFyaSB2Y29vcmRzIHlhbmcgZGlvcGVyYXNpa2FuIGFkYWxhaCBpZGVudGlrDQpgYGB7cn0NCnNhbWVsb2MgPC0gZnVuY3Rpb24oZTEsZTIpIHsNCiAgKGxlbmd0aChuaWxhaShlMSkpPT1sZW5ndGgobmlsYWkoZTIpKQ0KICAgfHwgYWxsKHhjb29yZHMoZTEpPT14Y29vcmRzKGUyKSkNCiAgIHx8IGFsbCh5Y29vcmRzKGUxKT09eWNvb3JkcyhlMikpDQogICApDQp9DQpPcHMudmNvb3JkcyA8LSBmdW5jdGlvbihlMSxlMikgew0KICBpZiAoIXNhbWVsb2MoZTEsZTIpKQ0KICAgIHN0b3AgKCJMb2thc2kgYmVyYmVkYSIpDQogIGVsc2UgdmNvb3Jkcyh4Y29vcmRzKGUxKSwNCiAgICAgICAgICAgICAgIHljb29yZHMoZTEpLA0KICAgICAgICAgICAgICAgZ2V0KC5HZW5lcmljKSAobmlsYWkoZTEpLG5pbGFpKGUyKSkpDQp9DQp2cHRzDQp2cHRzK3ZwdHMNCnZwdHMqdnB0cw0KYGBgDQoNCiMgTWV0aG9kIFN1YnNldA0KTWV0aG9kIHN1YnNldCBkYXBhdCBkaWFrc2VzIHNlc3VhaSBkZW5nYW4ga2VidXR1aGFuLCBtaXNhbCBrb25kaXNpIHlhbmcgZGlpbmdpbmthbjoNCmB2cHRzW3hjb29yZHModnB0cykgPCAwICYgeWNvb3Jkcyh2cHRzKSA8IDBdYA0KRGFwYXQgZGl0YW5nYW5pIGRlbmdhbiBtZW5kZWZpbmlzaWthbiBtZXRob2QgYmVyaWt1dC4NCmBgYHtyfQ0KYFsudmNvb3Jkc2AgPC0gZnVuY3Rpb24oeCwgaSkgew0KICB2Y29vcmRzKHhjb29yZHMoeClbaV0sIHljb29yZHMoeClbaV0sDQogICAgICAgICAgbmlsYWkoeClbaV0pDQp9DQp2cHRzDQp2cHRzWzE6M10NCnZwdHNbeGNvb3Jkcyh2cHRzKSA8IDAgJiB5Y29vcmRzKHZwdHMpIDwgMF0NCmBgYA0KDQojIFBlbWVyaWtzYWFuIFN1YXR1IENsYXNzIE9iamVrDQp1bnR1ayBtZW5nZWNlayBhcGFrYWggc3VhdHUgb2JqZWsgbWVydXBha2FuIHN1YXR1IGNsYXNzIGRpZ3VuYWthbiBmdW5nc2kgaW5oZXJpdHMgc2VwZXJ0aSBiZXJpa3V0Lg0KYGBge3J9DQppbmhlcml0cyhwdHMsImNvb3JkcyIpDQppbmhlcml0cyhwdHMsInZjb29yZHMiKQ0KaW5oZXJpdHModnB0cywiY29vcmRzIikNCmluaGVyaXRzKHZwdHMsInZjb29yZHMiKQ0KYGBgDQp0ZXJsaWhhdCBiYWh3YSBvYmplayBwdHMgdGlkYWsgdGVybWFzdWsga2UgZGFsYW0gY2xhc3MgdmNvb3Jkcy4gU2VkYW5na2FuIG9iamVrIHZwdHMgdGVybWFzdWsga2UgZGFsYW0gY2xhc3MgY29vcmRzLCBrYXJlbmEgdnB0cyBtZXJ1cGFrYW4gdHVydW5hbiBkYXJpIHB0cyBzZWhpbmdnYSBtZW5kYXBhdCB3YXJpc2FuIChpbmhlcml0YW5jZSkgY2xhc3MgZGFyaSBvYmplayBwdHMuDQoNCiMgUGVudXR1cCBDbGFzcyBTeXN0ZW0gUzMNCkNsYXNzIHN5c3RlbSBTMyBtZW1iZXJpa2FuIGZhc2lsaXRhcyBvYmplY3Qtb3JpZW50ZWQgeWFuZyB0ZXJsYWx1IGxvbmdnYXIgc2VoaW5nZ2EgYmFueWFrIHRlY2huaWNhbCBpc3N1ZSBkYWxhbSBzaXN0ZW0gY2xhc3MgaW5pLiBTZWJhZ2FpIGNvbnRvaCBla3NwcmVzaSBiZXJpa3V0IGRpcGVyYm9sZWhrYW4gZGFsYW0gUg0KYGBge3J9DQptb2RlbCA8LSAxOjEwDQpjbGFzcyhtb2RlbCkgPC0gImxtIg0KY2xhc3MobW9kZWwpDQpgYGANCg0KcGFkYWhhbCBjbGFzcyAibG0iIG1lcnVwYWthbiBjbGFzcyB1bnR1ayBwZW1vZGVsYW4gbGluZWFyLg0K