package ramenj;
import java.math.BigInteger;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.LongStream;
/**
* RamenJ (ラーメン素数)制限時間内に算出した最大の素数をラーメンと呼称する (制限時間)ラーメンの計算は3分以内(180秒以内)とし超えてはならない
* (All or Nothingの原則)制限時間内に計算が完了しなかった場合、ラーメンは2とする
* (既知の素数)計算なしに使用してよい既知の素数は2と3とする、それ以外の素数は未知とする
*
* @author noramen
*/
public class Program {
public final static int ramen2 = 2;
public final static int ramen3 = 3;
public final static int noramen = ramen2;
public static void main
(String args
[]) { long start
= System.
currentTimeMillis();
// int ramen = Ramen1(); // NG
// long ramen = Ramen2(); // OK 9223372036854775783
// long ramen = Ramen3(); // OK 9223372036854775783
// long ramen = Ramen3_1(); // OK 9223372036854775783
// long ramen = Ramen3_2(); // OK 9223372036854775783
// BigInteger ramen = Ramen4(); // OK 18446744073709551629
// BigInteger ramen = Ramen5(); // OK 18446744073709551629
// BigInteger ramen = Ramen6(2l, 1l, 1l, 1l); // Ramen5()と同値
// BigInteger ramen = Ramen6(5l, 1l, 2l, 1l); // OK 46116860184273879079
// BigInteger ramen = Ramen6(6l, 1l, 5l, 2l); // OK 55340232221128654843
// BigInteger ramen = Ramen6(13l, 2l, 5l, 2l); // OK 59951918239556042783
// BigInteger ramen = Ramen6(68l, 10l, 5l, 2l); // OK 62718929850612475567
// BigInteger ramen = Ramen7(69l, 10l, 5l, 2l); // OK 63641267054297953129
// BigInteger ramen = Ramen7(9l, 1l, 3l, 1l); // OK 83010348331692982271
// BigInteger ramen = Ramen7(11l, 1l, 3l, 1l); // OK 101457092405402533879
// BigInteger ramen = Ramen7(112l, 10l, 3l, 1l); //OK 103301766812773489067
// BigInteger ramen = Ramen7(114l, 10l, 346l, 100l); // OK 105146441220144444251
// BigInteger ramen = Ramen7(116l, 10l, 346l, 100l); // NG
// BigInteger ramen = Ramen7(118l, 10l, 346l, 100l); // NG
// BigInteger ramen = Ramen7(12l, 1l, 346l, 100l); // NG
// BigInteger ramen = Ramen8(114l, 10l, 346l, 100l); // OK 105146441220144444251
// BigInteger ramen = Ramen8(12l, 1l, 346l, 100l); // OK 110680464442257309779
// BigInteger ramen = Ramen8(13l, 1l, 346l, 100l); // OK 119903836479112085531
// BigInteger ramen = Ramen8(14l, 1l, 346l, 100l); // OK 129127208515966861349
// BigInteger ramen = Ramen8(16l, 1l, 4l, 1l); // OK 147573952589676412931
// BigInteger ramen = Ramen8(18l, 1l, 4l, 1l); // OK 166020696663385964539
//BigInteger ramen = Ramen8(19l, 1l, 4l, 1l); // OK 175244068700240740367
BigInteger ramen
= Ramen8
(20l, 1l, 4l, 1l
); // OK? 184467440737095516163
long end
= System.
currentTimeMillis(); System.
out.
println((end
- start
) / 1000); }
/**
* i,j,kを使った逐次法 (NG)5分経っても終らない
*
* @return noramen
*/
static int Ramen1() {
int ramen = 2;
for (int i
= 3; i
<= Integer.
MAX_VALUE; i
+= 2) { boolean k = true;
for (int j
= 3; j
<= Math.
sqrt(i
); j
+= 2) { if (i % j == 0) {
k = false;
break;
}
}
if (k) {
ramen = i;
}
}
return ramen;
}
/**
* Long.MAX_VALUEから逆順でAll or Nothing (OK)約14秒で出る
*
* @return 9223372036854775783
*/
static long Ramen2() {
for (long i
= Long.
MAX_VALUE; i
>= 3; i
-= 2) { boolean flag = true;
for (long j
= 3; j
<= Math.
sqrt(i
); j
+= 2) { if (i % j == 0) {
flag = false;
break;
}
}
if (flag) {
return i;
}
}
return noramen;
}
/**
* Ramen2()のうちflagをFunctionで書き直したもの (OK)約14秒で出る
*
* @return 9223372036854775783
*/
static long Ramen3() {
Function
<Long, Boolean
> isramen
= t
-> { for (long i
= 3; i
<= Math.
sqrt(t
); i
+= 2) { if (t % i == 0) {
return false;
}
}
return true;
};
for (long i
= Long.
MAX_VALUE; i
>= 3; i
-= 2) { if (isramen.apply(i)) {
return i;
}
}
return noramen;
}
/**
* Ramen3()の素数判定(試し割り法)を+=2ではなく++になるよう書き直したもの (OK)約12秒かかる
*
* @return 9223372036854775783
*/
static long Ramen3_1() {
Function
<Long, Boolean
> isramen
= t
-> { long halfSqrt
= (long) (Math.
sqrt(t
) / 2); for (long i = 1; i <= halfSqrt; i++) {
if (t % (i * 2 + 1) == 0) {
return false;
}
}
return true;
};
for (long i
= Long.
MAX_VALUE; i
>= 3; i
-= 2) { if (isramen.apply(i)) {
return i;
}
}
return noramen;
}
/**
* Ramen3_1()をLongStreamで書き直しparallelしたもの (OK)約7~9秒かかる
*
* @return 9223372036854775783
*/
static long Ramen3_2() {
Function
<Long, Boolean
> isramen
= t
-> { long halfSqrt
= (long) (Math.
sqrt(t
) / 2); OptionalLong findAny = LongStream.rangeClosed(1, halfSqrt).parallel().filter(x -> t % (x * 2 + 1) == 0)
.findAny();
return !findAny.isPresent();
};
for (long i
= Long.
MAX_VALUE; i
>= 3; i
-= 2) { if (isramen.apply(i)) {
return i;
}
}
return noramen;
}
/**
* Ramen3()をBigIntegerに書き直したもの。また (1)sqrtを使わずpowを使用
* (2)ループ処理でをstart3,step2→start5,step6に書き換え
* (3)BigInteger.nextProbablePrimeを使用(OK)約128~140秒かかる
*
* @return 18446744073709551629
*/
while (i.pow(2).compareTo(t) <= 0) {
return false;
}
}
return true;
};
}
while (!isramen.apply(ramen)) {
System.
out.
println(ramen
+ " is not ramen"); ramen = ramen.nextProbablePrime();
}
return ramen;
}
/**
* Ramen4()をStreamで書き直しparallelしたもの ループ処理でをstart3,step2に戻し√x/2に近似で求める
* (OK)約80秒かかる
*
* @return 18446744073709551629
*/
while (xnn.
subtract(xn
).
abs().
compareTo(BigInteger.
ONE) > 0) { xn = xnn;
xnn
= x.
divide(xnn
).
add(xnn
).
divide(BigInteger.
valueOf(2l
)); }
return xnn.
divide(BigInteger.
valueOf(2l
)).
longValueExact(); };
LongStream stream = LongStream.rangeClosed(1, halfsqrt.apply(t));
Optional<BigInteger> findAny = stream.parallel()
.
filter(i
-> t.
remainder(i
) == BigInteger.
ZERO).
findAny(); return !findAny.isPresent();
};
ramen = ramen.nextProbablePrime();
while (!isramen.apply(ramen)) {
System.
out.
println(ramen
+ " is not ramen"); ramen = ramen.nextProbablePrime();
}
return ramen;
}
/**
* Ramen5()にパラメーターを付与し変更しやすいよう変形したもの
*
* @param mul1
* 開始数の乗数
* @param div1
* 開始数の除数
* @param mul2
* ニュートン近似の乗数
* @param div2
* ニュートン近似の除数
* @return Ramen6(13l, 2l, 5l, 2l)=59951918239556042783
*/
static BigInteger Ramen6
(long mul1,
long div1,
long mul2,
long div2
) { while (xnn.
subtract(xn
).
abs().
compareTo(BigInteger.
ONE) > 0) { xn = xnn;
xnn
= x.
divide(xnn
).
add(xnn
).
divide(BigInteger.
valueOf(2l
)); }
return xnn.
divide(BigInteger.
valueOf(2l
)).
longValueExact(); };
LongStream stream = LongStream.rangeClosed(1, halfsqrt.apply(t));
Optional<BigInteger> findAny = stream.parallel()
.
filter(i
-> t.
remainder(i
) == BigInteger.
ZERO).
findAny(); return !findAny.isPresent();
};
ramen = ramen.nextProbablePrime();
while (!isramen.apply(ramen)) {
System.
out.
println(ramen
+ " is not ramen"); ramen = ramen.nextProbablePrime();
}
return ramen;
}
/**
* Ramen6の (1)mapToObj内をBigIntegerからLong (2)findAny.isPresentからnoneMatch
* (3)remainder判定を==からcompareTo==
* (4)isramenをFunction<BigInteger,Boolean>からPredicate<BigInteger> 、で書き直したもの
*
* @param mul1
* 開始数の乗数
* @param div1
* 開始数の除数
* @param mul2
* ニュートン近似の乗数
* @param div2
* ニュートン近似の除数
* @return Ramen7(114l, 10l, 346l, 100l)=105146441220144444251
*/
static BigInteger Ramen7
(long mul1,
long div1,
long mul2,
long div2
) { while (xnn.
subtract(xn
).
abs().
compareTo(BigInteger.
ONE) > 0) { xn = xnn;
xnn
= x.
divide(xnn
).
add(xnn
).
divide(BigInteger.
valueOf(2l
)); }
return xnn.
divide(BigInteger.
valueOf(2l
)).
longValueExact(); };
Predicate<BigInteger> isramen = t -> LongStream.rangeClosed(1, halfsqrt.apply(t)).parallel()
.
noneMatch(i
-> t.
remainder(i
).
compareTo(BigInteger.
ZERO) == 0);
ramen = ramen.nextProbablePrime();
while (!isramen.test(ramen)) {
System.
out.
println(ramen
+ " is not ramen"); ramen = ramen.nextProbablePrime();
}
return ramen;
}
/**
* Ramen7のhalfsqrtからsextantsqrtに書き換えたもの
*
* @param mul1
* 開始数の乗数
* @param div1
* 開始数の除数
* @param mul2
* ニュートン近似の乗数
* @param div2
* ニュートン近似の除数
* @return
*/
static BigInteger Ramen8
(long mul1,
long div1,
long mul2,
long div2
) {
do {
xn = xnn;
xnn
= x.
divide(xnn
).
add(xnn
).
divide(BigInteger.
valueOf(2l
)); } while (xnn.
subtract(xn
).
abs().
compareTo(BigInteger.
ONE) > 0);
return xnn.
max(xn
).
divide(BigInteger.
valueOf(6l
)).
longValueExact(); };
return false;
}
return LongStream.
range(0, sextantsqrt.
apply(t
)).
parallel().
mapToObj(i
-> BigInteger.
valueOf(i
* 6l
+ 5l
)) .
noneMatch(i
-> t.
remainder(i
).
compareTo(BigInteger.
ZERO) == 0 };
ramen = ramen.nextProbablePrime();
while (!isramen.apply(ramen)) {
System.
out.
println(ramen
+ " is not ramen"); ramen = ramen.nextProbablePrime();
}
return ramen;
}
}
cGFja2FnZSByYW1lbmo7CgppbXBvcnQgamF2YS5tYXRoLkJpZ0ludGVnZXI7CmltcG9ydCBqYXZhLnV0aWwuT3B0aW9uYWw7CmltcG9ydCBqYXZhLnV0aWwuT3B0aW9uYWxMb25nOwppbXBvcnQgamF2YS51dGlsLmZ1bmN0aW9uLkZ1bmN0aW9uOwppbXBvcnQgamF2YS51dGlsLmZ1bmN0aW9uLlByZWRpY2F0ZTsKaW1wb3J0IGphdmEudXRpbC5zdHJlYW0uTG9uZ1N0cmVhbTsKCi8qKgogKiBSYW1lbkogKOODqeODvOODoeODs+e0oOaVsCnliLbpmZDmmYLplpPlhoXjgavnrpflh7rjgZfjgZ/mnIDlpKfjga7ntKDmlbDjgpLjg6njg7zjg6Hjg7Pjgajlkbznp7DjgZnjgosgKOWItumZkOaZgumWkynjg6njg7zjg6Hjg7Pjga7oqIjnrpfjga8z5YiG5Lul5YaFKDE4MOenkuS7peWGhSnjgajjgZfotoXjgYjjgabjga/jgarjgonjgarjgYQKICogKEFsbCBvciBOb3RoaW5n44Gu5Y6f5YmHKeWItumZkOaZgumWk+WGheOBq+ioiOeul+OBjOWujOS6huOBl+OBquOBi+OBo+OBn+WgtOWQiOOAgeODqeODvOODoeODs+OBrzLjgajjgZnjgosKICogKOaXouefpeOBrue0oOaVsCnoqIjnrpfjgarjgZfjgavkvb/nlKjjgZfjgabjgojjgYTml6Lnn6Xjga7ntKDmlbDjga8y44GoM+OBqOOBmeOCi+OAgeOBneOCjOS7peWkluOBrue0oOaVsOOBr+acquefpeOBqOOBmeOCiwogKiAKICogQGF1dGhvciBub3JhbWVuCiAqLwpwdWJsaWMgY2xhc3MgUHJvZ3JhbSB7CglwdWJsaWMgZmluYWwgc3RhdGljIGludCByYW1lbjIgPSAyOwoJcHVibGljIGZpbmFsIHN0YXRpYyBpbnQgcmFtZW4zID0gMzsKCXB1YmxpYyBmaW5hbCBzdGF0aWMgaW50IG5vcmFtZW4gPSByYW1lbjI7CgoJcHVibGljIHN0YXRpYyB2b2lkIG1haW4oU3RyaW5nIGFyZ3NbXSkgewoJCWxvbmcgc3RhcnQgPSBTeXN0ZW0uY3VycmVudFRpbWVNaWxsaXMoKTsKCgkJLy8gaW50IHJhbWVuID0gUmFtZW4xKCk7IC8vIE5HCgkJLy8gbG9uZyByYW1lbiA9IFJhbWVuMigpOyAvLyBPSyA5MjIzMzcyMDM2ODU0Nzc1NzgzCgkJLy8gbG9uZyByYW1lbiA9IFJhbWVuMygpOyAvLyBPSyA5MjIzMzcyMDM2ODU0Nzc1NzgzCgkJLy8gbG9uZyByYW1lbiA9IFJhbWVuM18xKCk7IC8vIE9LIDkyMjMzNzIwMzY4NTQ3NzU3ODMKCQkvLyBsb25nIHJhbWVuID0gUmFtZW4zXzIoKTsgLy8gT0sgOTIyMzM3MjAzNjg1NDc3NTc4MwoKCQkvLyBCaWdJbnRlZ2VyIHJhbWVuID0gUmFtZW40KCk7IC8vIE9LIDE4NDQ2NzQ0MDczNzA5NTUxNjI5CgkJLy8gQmlnSW50ZWdlciByYW1lbiA9IFJhbWVuNSgpOyAvLyBPSyAxODQ0Njc0NDA3MzcwOTU1MTYyOQoKCQkvLyBCaWdJbnRlZ2VyIHJhbWVuID0gUmFtZW42KDJsLCAxbCwgMWwsIDFsKTsgLy8gUmFtZW41KCnjgajlkIzlgKQKCQkvLyBCaWdJbnRlZ2VyIHJhbWVuID0gUmFtZW42KDVsLCAxbCwgMmwsIDFsKTsgLy8gT0sgNDYxMTY4NjAxODQyNzM4NzkwNzkKCQkvLyBCaWdJbnRlZ2VyIHJhbWVuID0gUmFtZW42KDZsLCAxbCwgNWwsIDJsKTsgLy8gT0sgNTUzNDAyMzIyMjExMjg2NTQ4NDMKCQkvLyBCaWdJbnRlZ2VyIHJhbWVuID0gUmFtZW42KDEzbCwgMmwsIDVsLCAybCk7IC8vIE9LIDU5OTUxOTE4MjM5NTU2MDQyNzgzCgkJLy8gQmlnSW50ZWdlciByYW1lbiA9IFJhbWVuNig2OGwsIDEwbCwgNWwsIDJsKTsgLy8gT0sgNjI3MTg5Mjk4NTA2MTI0NzU1NjcKCgkJLy8gQmlnSW50ZWdlciByYW1lbiA9IFJhbWVuNyg2OWwsIDEwbCwgNWwsIDJsKTsgLy8gT0sgNjM2NDEyNjcwNTQyOTc5NTMxMjkKCQkvLyBCaWdJbnRlZ2VyIHJhbWVuID0gUmFtZW43KDlsLCAxbCwgM2wsIDFsKTsgLy8gT0sgODMwMTAzNDgzMzE2OTI5ODIyNzEKCQkvLyBCaWdJbnRlZ2VyIHJhbWVuID0gUmFtZW43KDExbCwgMWwsIDNsLCAxbCk7IC8vIE9LIDEwMTQ1NzA5MjQwNTQwMjUzMzg3OQoJCS8vIEJpZ0ludGVnZXIgcmFtZW4gPSBSYW1lbjcoMTEybCwgMTBsLCAzbCwgMWwpOyAvL09LIDEwMzMwMTc2NjgxMjc3MzQ4OTA2NwoJCS8vIEJpZ0ludGVnZXIgcmFtZW4gPSBSYW1lbjcoMTE0bCwgMTBsLCAzNDZsLCAxMDBsKTsgLy8gT0sgMTA1MTQ2NDQxMjIwMTQ0NDQ0MjUxCgkJLy8gQmlnSW50ZWdlciByYW1lbiA9IFJhbWVuNygxMTZsLCAxMGwsIDM0NmwsIDEwMGwpOyAvLyBORwoJCS8vIEJpZ0ludGVnZXIgcmFtZW4gPSBSYW1lbjcoMTE4bCwgMTBsLCAzNDZsLCAxMDBsKTsgLy8gTkcKCQkvLyBCaWdJbnRlZ2VyIHJhbWVuID0gUmFtZW43KDEybCwgMWwsIDM0NmwsIDEwMGwpOyAvLyBORwoKCQkvLyBCaWdJbnRlZ2VyIHJhbWVuID0gUmFtZW44KDExNGwsIDEwbCwgMzQ2bCwgMTAwbCk7IC8vIE9LIDEwNTE0NjQ0MTIyMDE0NDQ0NDI1MQoJCS8vIEJpZ0ludGVnZXIgcmFtZW4gPSBSYW1lbjgoMTJsLCAxbCwgMzQ2bCwgMTAwbCk7IC8vIE9LIDExMDY4MDQ2NDQ0MjI1NzMwOTc3OQoJCS8vIEJpZ0ludGVnZXIgcmFtZW4gPSBSYW1lbjgoMTNsLCAxbCwgMzQ2bCwgMTAwbCk7IC8vIE9LIDExOTkwMzgzNjQ3OTExMjA4NTUzMQoJCS8vIEJpZ0ludGVnZXIgcmFtZW4gPSBSYW1lbjgoMTRsLCAxbCwgMzQ2bCwgMTAwbCk7IC8vIE9LIDEyOTEyNzIwODUxNTk2Njg2MTM0OQoJCS8vIEJpZ0ludGVnZXIgcmFtZW4gPSBSYW1lbjgoMTZsLCAxbCwgNGwsIDFsKTsgLy8gT0sgMTQ3NTczOTUyNTg5Njc2NDEyOTMxCgkJLy8gQmlnSW50ZWdlciByYW1lbiA9IFJhbWVuOCgxOGwsIDFsLCA0bCwgMWwpOyAvLyBPSyAxNjYwMjA2OTY2NjMzODU5NjQ1MzkKCQkvL0JpZ0ludGVnZXIgcmFtZW4gPSBSYW1lbjgoMTlsLCAxbCwgNGwsIDFsKTsgLy8gT0sgMTc1MjQ0MDY4NzAwMjQwNzQwMzY3CgkJQmlnSW50ZWdlciByYW1lbiA9IFJhbWVuOCgyMGwsIDFsLCA0bCwgMWwpOyAvLyBPSz8gMTg0NDY3NDQwNzM3MDk1NTE2MTYzCgoJCVN5c3RlbS5vdXQucHJpbnRsbigpOwoJCVN5c3RlbS5vdXQucHJpbnRsbihyYW1lbik7CgoJCWxvbmcgZW5kID0gU3lzdGVtLmN1cnJlbnRUaW1lTWlsbGlzKCk7CgkJU3lzdGVtLm91dC5wcmludGxuKChlbmQgLSBzdGFydCkgLyAxMDAwKTsKCX0KCgkvKioKCSAqIGksaixr44KS5L2/44Gj44Gf6YCQ5qyh5rOVIChORyk15YiG57WM44Gj44Gm44KC57WC44KJ44Gq44GECgkgKiAKCSAqIEByZXR1cm4gbm9yYW1lbgoJICovCglzdGF0aWMgaW50IFJhbWVuMSgpIHsKCQlpbnQgcmFtZW4gPSAyOwoKCQlmb3IgKGludCBpID0gMzsgaSA8PSBJbnRlZ2VyLk1BWF9WQUxVRTsgaSArPSAyKSB7CgkJCWJvb2xlYW4gayA9IHRydWU7CgkJCWZvciAoaW50IGogPSAzOyBqIDw9IE1hdGguc3FydChpKTsgaiArPSAyKSB7CgkJCQlpZiAoaSAlIGogPT0gMCkgewoJCQkJCWsgPSBmYWxzZTsKCQkJCQlicmVhazsKCQkJCX0KCQkJfQoKCQkJaWYgKGspIHsKCQkJCXJhbWVuID0gaTsKCQkJCVN5c3RlbS5vdXQucHJpbnRsbihpKTsKCQkJfQoJCX0KCgkJcmV0dXJuIHJhbWVuOwoJfQoKCS8qKgoJICogTG9uZy5NQVhfVkFMVUXjgYvjgonpgIbpoIbjgadBbGwgb3IgTm90aGluZyAoT0sp57SEMTTnp5Ljgaflh7rjgosKCSAqIAoJICogQHJldHVybiA5MjIzMzcyMDM2ODU0Nzc1NzgzCgkgKi8KCXN0YXRpYyBsb25nIFJhbWVuMigpIHsKCQlmb3IgKGxvbmcgaSA9IExvbmcuTUFYX1ZBTFVFOyBpID49IDM7IGkgLT0gMikgewoJCQlib29sZWFuIGZsYWcgPSB0cnVlOwoJCQlmb3IgKGxvbmcgaiA9IDM7IGogPD0gTWF0aC5zcXJ0KGkpOyBqICs9IDIpIHsKCQkJCWlmIChpICUgaiA9PSAwKSB7CgkJCQkJZmxhZyA9IGZhbHNlOwoJCQkJCWJyZWFrOwoJCQkJfQoJCQl9CgoJCQlpZiAoZmxhZykgewoJCQkJcmV0dXJuIGk7CgkJCX0KCQl9CgkJcmV0dXJuIG5vcmFtZW47Cgl9CgoJLyoqCgkgKiBSYW1lbjIoKeOBruOBhuOBoWZsYWfjgpJGdW5jdGlvbuOBp+abuOOBjeebtOOBl+OBn+OCguOBriAoT0sp57SEMTTnp5Ljgaflh7rjgosKCSAqIAoJICogQHJldHVybiA5MjIzMzcyMDM2ODU0Nzc1NzgzCgkgKi8KCXN0YXRpYyBsb25nIFJhbWVuMygpIHsKCQlGdW5jdGlvbjxMb25nLCBCb29sZWFuPiBpc3JhbWVuID0gdCAtPiB7CgkJCWZvciAobG9uZyBpID0gMzsgaSA8PSBNYXRoLnNxcnQodCk7IGkgKz0gMikgewoJCQkJaWYgKHQgJSBpID09IDApIHsKCQkJCQlyZXR1cm4gZmFsc2U7CgkJCQl9CgkJCX0KCQkJcmV0dXJuIHRydWU7CgkJfTsKCgkJZm9yIChsb25nIGkgPSBMb25nLk1BWF9WQUxVRTsgaSA+PSAzOyBpIC09IDIpIHsKCQkJaWYgKGlzcmFtZW4uYXBwbHkoaSkpIHsKCQkJCXJldHVybiBpOwoJCQl9CgkJfQoKCQlyZXR1cm4gbm9yYW1lbjsKCX0KCgkvKioKCSAqIFJhbWVuMygp44Gu57Sg5pWw5Yik5a6aKOippuOBl+WJsuOCiuazlSnjgpIrPTLjgafjga/jgarjgY8rK+OBq+OBquOCi+OCiOOBhuabuOOBjeebtOOBl+OBn+OCguOBriAoT0sp57SEMTLnp5LjgYvjgYvjgosKCSAqIAoJICogQHJldHVybiA5MjIzMzcyMDM2ODU0Nzc1NzgzCgkgKi8KCXN0YXRpYyBsb25nIFJhbWVuM18xKCkgewoJCUZ1bmN0aW9uPExvbmcsIEJvb2xlYW4+IGlzcmFtZW4gPSB0IC0+IHsKCQkJbG9uZyBoYWxmU3FydCA9IChsb25nKSAoTWF0aC5zcXJ0KHQpIC8gMik7CgkJCWZvciAobG9uZyBpID0gMTsgaSA8PSBoYWxmU3FydDsgaSsrKSB7CgkJCQlpZiAodCAlIChpICogMiArIDEpID09IDApIHsKCQkJCQlyZXR1cm4gZmFsc2U7CgkJCQl9CgkJCX0KCQkJcmV0dXJuIHRydWU7CgkJfTsKCgkJZm9yIChsb25nIGkgPSBMb25nLk1BWF9WQUxVRTsgaSA+PSAzOyBpIC09IDIpIHsKCQkJaWYgKGlzcmFtZW4uYXBwbHkoaSkpIHsKCQkJCXJldHVybiBpOwoJCQl9CgkJfQoKCQlyZXR1cm4gbm9yYW1lbjsKCX0KCgkvKioKCSAqIFJhbWVuM18xKCnjgpJMb25nU3RyZWFt44Gn5pu444GN55u044GXcGFyYWxsZWzjgZfjgZ/jgoLjga4gKE9LKee0hDfvvZ4556eS44GL44GL44KLCgkgKiAKCSAqIEByZXR1cm4gOTIyMzM3MjAzNjg1NDc3NTc4MwoJICovCglzdGF0aWMgbG9uZyBSYW1lbjNfMigpIHsKCQlGdW5jdGlvbjxMb25nLCBCb29sZWFuPiBpc3JhbWVuID0gdCAtPiB7CgkJCWxvbmcgaGFsZlNxcnQgPSAobG9uZykgKE1hdGguc3FydCh0KSAvIDIpOwoJCQlPcHRpb25hbExvbmcgZmluZEFueSA9IExvbmdTdHJlYW0ucmFuZ2VDbG9zZWQoMSwgaGFsZlNxcnQpLnBhcmFsbGVsKCkuZmlsdGVyKHggLT4gdCAlICh4ICogMiArIDEpID09IDApCgkJCQkJLmZpbmRBbnkoKTsKCQkJcmV0dXJuICFmaW5kQW55LmlzUHJlc2VudCgpOwoJCX07CgoJCWZvciAobG9uZyBpID0gTG9uZy5NQVhfVkFMVUU7IGkgPj0gMzsgaSAtPSAyKSB7CgkJCWlmIChpc3JhbWVuLmFwcGx5KGkpKSB7CgkJCQlyZXR1cm4gaTsKCQkJfQoJCX0KCgkJcmV0dXJuIG5vcmFtZW47Cgl9CgoJLyoqCgkgKiBSYW1lbjMoKeOCkkJpZ0ludGVnZXLjgavmm7jjgY3nm7TjgZfjgZ/jgoLjga7jgILjgb7jgZ8gKDEpc3FydOOCkuS9v+OCj+OBmnBvd+OCkuS9v+eUqAoJICogKDIp44Or44O844OX5Yem55CG44Gn44KSc3RhcnQzLHN0ZXAy4oaSc3RhcnQ1LHN0ZXA244Gr5pu444GN5o+b44GICgkgKiAoMylCaWdJbnRlZ2VyLm5leHRQcm9iYWJsZVByaW1l44KS5L2/55SoKE9LKee0hDEyOO+9njE0MOenkuOBi+OBi+OCiwoJICogCgkgKiBAcmV0dXJuIDE4NDQ2NzQ0MDczNzA5NTUxNjI5CgkgKi8KCXN0YXRpYyBCaWdJbnRlZ2VyIFJhbWVuNCgpIHsKCQlGdW5jdGlvbjxCaWdJbnRlZ2VyLCBCb29sZWFuPiBpc3JhbWVuID0gdCAtPiB7CgkJCUJpZ0ludGVnZXIgaSA9IEJpZ0ludGVnZXIudmFsdWVPZig1bCk7CgkJCXdoaWxlIChpLnBvdygyKS5jb21wYXJlVG8odCkgPD0gMCkgewoJCQkJaWYgKHQucmVtYWluZGVyKGkpID09IEJpZ0ludGVnZXIuWkVSTwoJCQkJCQl8fCB0LnJlbWFpbmRlcihpLmFkZChCaWdJbnRlZ2VyLnZhbHVlT2YoMmwpKSkgPT0gQmlnSW50ZWdlci5aRVJPKSB7CgkJCQkJcmV0dXJuIGZhbHNlOwoJCQkJfQoJCQkJaSA9IGkuYWRkKEJpZ0ludGVnZXIudmFsdWVPZig2bCkpOwoJCQl9CgkJCXJldHVybiB0cnVlOwoJCX07CgoJCUJpZ0ludGVnZXIgcmFtZW4gPSBCaWdJbnRlZ2VyLnZhbHVlT2YoTG9uZy5NQVhfVkFMVUUpLm11bHRpcGx5KEJpZ0ludGVnZXIudmFsdWVPZigybCkpOwoJCWlmIChyYW1lbi5yZW1haW5kZXIoQmlnSW50ZWdlci52YWx1ZU9mKDJsKSkgPT0gQmlnSW50ZWdlci5aRVJPKSB7CgkJCXJhbWVuID0gcmFtZW4uYWRkKEJpZ0ludGVnZXIuT05FKTsKCQl9CgkJd2hpbGUgKCFpc3JhbWVuLmFwcGx5KHJhbWVuKSkgewoJCQlTeXN0ZW0ub3V0LnByaW50bG4ocmFtZW4gKyAiIGlzIG5vdCByYW1lbiIpOwoJCQlyYW1lbiA9IHJhbWVuLm5leHRQcm9iYWJsZVByaW1lKCk7CgkJfQoJCXJldHVybiByYW1lbjsKCX0KCgkvKioKCSAqIFJhbWVuNCgp44KSU3RyZWFt44Gn5pu444GN55u044GXcGFyYWxsZWzjgZfjgZ/jgoLjga4g44Or44O844OX5Yem55CG44Gn44KSc3RhcnQzLHN0ZXAy44Gr5oi744GX4oiaeC8y44Gr6L+R5Ly844Gn5rGC44KB44KLCgkgKiAoT0sp57SEODDnp5LjgYvjgYvjgosKCSAqIAoJICogQHJldHVybiAxODQ0Njc0NDA3MzcwOTU1MTYyOQoJICovCglzdGF0aWMgQmlnSW50ZWdlciBSYW1lbjUoKSB7CgkJRnVuY3Rpb248QmlnSW50ZWdlciwgTG9uZz4gaGFsZnNxcnQgPSB4IC0+IHsKCQkJQmlnSW50ZWdlciB4bm4gPSBCaWdJbnRlZ2VyLnZhbHVlT2YoKGxvbmcpIE1hdGguc3FydChMb25nLk1BWF9WQUxVRSkpOwoJCQlCaWdJbnRlZ2VyIHhuID0gQmlnSW50ZWdlci5aRVJPOwoJCQl3aGlsZSAoeG5uLnN1YnRyYWN0KHhuKS5hYnMoKS5jb21wYXJlVG8oQmlnSW50ZWdlci5PTkUpID4gMCkgewoJCQkJeG4gPSB4bm47CgkJCQl4bm4gPSB4LmRpdmlkZSh4bm4pLmFkZCh4bm4pLmRpdmlkZShCaWdJbnRlZ2VyLnZhbHVlT2YoMmwpKTsKCQkJfQoJCQlyZXR1cm4geG5uLmRpdmlkZShCaWdJbnRlZ2VyLnZhbHVlT2YoMmwpKS5sb25nVmFsdWVFeGFjdCgpOwoJCX07CgoJCUZ1bmN0aW9uPEJpZ0ludGVnZXIsIEJvb2xlYW4+IGlzcmFtZW4gPSB0IC0+IHsKCQkJTG9uZ1N0cmVhbSBzdHJlYW0gPSBMb25nU3RyZWFtLnJhbmdlQ2xvc2VkKDEsIGhhbGZzcXJ0LmFwcGx5KHQpKTsKCQkJT3B0aW9uYWw8QmlnSW50ZWdlcj4gZmluZEFueSA9IHN0cmVhbS5wYXJhbGxlbCgpCgkJCQkJLm1hcFRvT2JqKGkgLT4gQmlnSW50ZWdlci52YWx1ZU9mKGkpLm11bHRpcGx5KEJpZ0ludGVnZXIudmFsdWVPZigybCkuYWRkKEJpZ0ludGVnZXIuT05FKSkpCgkJCQkJLmZpbHRlcihpIC0+IHQucmVtYWluZGVyKGkpID09IEJpZ0ludGVnZXIuWkVSTykuZmluZEFueSgpOwoJCQlyZXR1cm4gIWZpbmRBbnkuaXNQcmVzZW50KCk7CgkJfTsKCgkJQmlnSW50ZWdlciByYW1lbiA9IEJpZ0ludGVnZXIudmFsdWVPZihMb25nLk1BWF9WQUxVRSkubXVsdGlwbHkoQmlnSW50ZWdlci52YWx1ZU9mKDJsKSk7CgkJcmFtZW4gPSByYW1lbi5uZXh0UHJvYmFibGVQcmltZSgpOwoJCXdoaWxlICghaXNyYW1lbi5hcHBseShyYW1lbikpIHsKCQkJU3lzdGVtLm91dC5wcmludGxuKHJhbWVuICsgIiBpcyBub3QgcmFtZW4iKTsKCQkJcmFtZW4gPSByYW1lbi5uZXh0UHJvYmFibGVQcmltZSgpOwoJCX0KCQlyZXR1cm4gcmFtZW47Cgl9CgoJLyoqCgkgKiBSYW1lbjUoKeOBq+ODkeODqeODoeODvOOCv+ODvOOCkuS7mOS4juOBl+WkieabtOOBl+OChOOBmeOBhOOCiOOBhuWkieW9ouOBl+OBn+OCguOBrgoJICogCgkgKiBAcGFyYW0gbXVsMQoJICogICAgICAgICAgICDplovlp4vmlbDjga7kuZfmlbAKCSAqIEBwYXJhbSBkaXYxCgkgKiAgICAgICAgICAgIOmWi+Wni+aVsOOBrumZpOaVsAoJICogQHBhcmFtIG11bDIKCSAqICAgICAgICAgICAg44OL44Ol44O844OI44Oz6L+R5Ly844Gu5LmX5pWwCgkgKiBAcGFyYW0gZGl2MgoJICogICAgICAgICAgICDjg4vjg6Xjg7zjg4jjg7Pov5HkvLzjga7pmaTmlbAKCSAqIEByZXR1cm4gUmFtZW42KDEzbCwgMmwsIDVsLCAybCk9NTk5NTE5MTgyMzk1NTYwNDI3ODMKCSAqLwoJc3RhdGljIEJpZ0ludGVnZXIgUmFtZW42KGxvbmcgbXVsMSwgbG9uZyBkaXYxLCBsb25nIG11bDIsIGxvbmcgZGl2MikgewoJCUZ1bmN0aW9uPEJpZ0ludGVnZXIsIExvbmc+IGhhbGZzcXJ0ID0geCAtPiB7CgkJCUJpZ0ludGVnZXIgeG5uID0gQmlnSW50ZWdlci52YWx1ZU9mKChsb25nKSBNYXRoLnNxcnQoTG9uZy5NQVhfVkFMVUUpKS5tdWx0aXBseShCaWdJbnRlZ2VyLnZhbHVlT2YobXVsMikpCgkJCQkJLmRpdmlkZShCaWdJbnRlZ2VyLnZhbHVlT2YoZGl2MikpOwoJCQlCaWdJbnRlZ2VyIHhuID0gQmlnSW50ZWdlci5aRVJPOwoJCQl3aGlsZSAoeG5uLnN1YnRyYWN0KHhuKS5hYnMoKS5jb21wYXJlVG8oQmlnSW50ZWdlci5PTkUpID4gMCkgewoJCQkJeG4gPSB4bm47CgkJCQl4bm4gPSB4LmRpdmlkZSh4bm4pLmFkZCh4bm4pLmRpdmlkZShCaWdJbnRlZ2VyLnZhbHVlT2YoMmwpKTsKCQkJfQoJCQlyZXR1cm4geG5uLmRpdmlkZShCaWdJbnRlZ2VyLnZhbHVlT2YoMmwpKS5sb25nVmFsdWVFeGFjdCgpOwoJCX07CgoJCUZ1bmN0aW9uPEJpZ0ludGVnZXIsIEJvb2xlYW4+IGlzcmFtZW4gPSB0IC0+IHsKCQkJTG9uZ1N0cmVhbSBzdHJlYW0gPSBMb25nU3RyZWFtLnJhbmdlQ2xvc2VkKDEsIGhhbGZzcXJ0LmFwcGx5KHQpKTsKCQkJT3B0aW9uYWw8QmlnSW50ZWdlcj4gZmluZEFueSA9IHN0cmVhbS5wYXJhbGxlbCgpCgkJCQkJLm1hcFRvT2JqKGkgLT4gQmlnSW50ZWdlci52YWx1ZU9mKGkpLm11bHRpcGx5KEJpZ0ludGVnZXIudmFsdWVPZigybCkuYWRkKEJpZ0ludGVnZXIuT05FKSkpCgkJCQkJLmZpbHRlcihpIC0+IHQucmVtYWluZGVyKGkpID09IEJpZ0ludGVnZXIuWkVSTykuZmluZEFueSgpOwoJCQlyZXR1cm4gIWZpbmRBbnkuaXNQcmVzZW50KCk7CgkJfTsKCgkJQmlnSW50ZWdlciByYW1lbiA9IEJpZ0ludGVnZXIudmFsdWVPZihMb25nLk1BWF9WQUxVRSkubXVsdGlwbHkoQmlnSW50ZWdlci52YWx1ZU9mKG11bDEpKQoJCQkJLmRpdmlkZShCaWdJbnRlZ2VyLnZhbHVlT2YoZGl2MSkpOwoJCXJhbWVuID0gcmFtZW4ubmV4dFByb2JhYmxlUHJpbWUoKTsKCQl3aGlsZSAoIWlzcmFtZW4uYXBwbHkocmFtZW4pKSB7CgkJCVN5c3RlbS5vdXQucHJpbnRsbihyYW1lbiArICIgaXMgbm90IHJhbWVuIik7CgkJCXJhbWVuID0gcmFtZW4ubmV4dFByb2JhYmxlUHJpbWUoKTsKCQl9CgkJcmV0dXJuIHJhbWVuOwoJfQoKCS8qKgoJICogUmFtZW4244GuICgxKW1hcFRvT2Jq5YaF44KSQmlnSW50ZWdlcuOBi+OCiUxvbmcgKDIpZmluZEFueS5pc1ByZXNlbnTjgYvjgolub25lTWF0Y2gKCSAqICgzKXJlbWFpbmRlcuWIpOWumuOCkj0944GL44KJY29tcGFyZVRvPT0KCSAqICg0KWlzcmFtZW7jgpJGdW5jdGlvbjxCaWdJbnRlZ2VyLEJvb2xlYW4+44GL44KJUHJlZGljYXRlPEJpZ0ludGVnZXI+IOOAgeOBp+abuOOBjeebtOOBl+OBn+OCguOBrgoJICogCgkgKiBAcGFyYW0gbXVsMQoJICogICAgICAgICAgICDplovlp4vmlbDjga7kuZfmlbAKCSAqIEBwYXJhbSBkaXYxCgkgKiAgICAgICAgICAgIOmWi+Wni+aVsOOBrumZpOaVsAoJICogQHBhcmFtIG11bDIKCSAqICAgICAgICAgICAg44OL44Ol44O844OI44Oz6L+R5Ly844Gu5LmX5pWwCgkgKiBAcGFyYW0gZGl2MgoJICogICAgICAgICAgICDjg4vjg6Xjg7zjg4jjg7Pov5HkvLzjga7pmaTmlbAKCSAqIEByZXR1cm4gUmFtZW43KDExNGwsIDEwbCwgMzQ2bCwgMTAwbCk9MTA1MTQ2NDQxMjIwMTQ0NDQ0MjUxCgkgKi8KCXN0YXRpYyBCaWdJbnRlZ2VyIFJhbWVuNyhsb25nIG11bDEsIGxvbmcgZGl2MSwgbG9uZyBtdWwyLCBsb25nIGRpdjIpIHsKCQlGdW5jdGlvbjxCaWdJbnRlZ2VyLCBMb25nPiBoYWxmc3FydCA9IHggLT4gewoJCQlCaWdJbnRlZ2VyIHhubiA9IEJpZ0ludGVnZXIudmFsdWVPZigobG9uZykgTWF0aC5zcXJ0KExvbmcuTUFYX1ZBTFVFKSkubXVsdGlwbHkoQmlnSW50ZWdlci52YWx1ZU9mKG11bDIpKQoJCQkJCS5kaXZpZGUoQmlnSW50ZWdlci52YWx1ZU9mKGRpdjIpKTsKCQkJQmlnSW50ZWdlciB4biA9IEJpZ0ludGVnZXIuWkVSTzsKCQkJd2hpbGUgKHhubi5zdWJ0cmFjdCh4bikuYWJzKCkuY29tcGFyZVRvKEJpZ0ludGVnZXIuT05FKSA+IDApIHsKCQkJCXhuID0geG5uOwoJCQkJeG5uID0geC5kaXZpZGUoeG5uKS5hZGQoeG5uKS5kaXZpZGUoQmlnSW50ZWdlci52YWx1ZU9mKDJsKSk7CgkJCX0KCQkJcmV0dXJuIHhubi5kaXZpZGUoQmlnSW50ZWdlci52YWx1ZU9mKDJsKSkubG9uZ1ZhbHVlRXhhY3QoKTsKCQl9OwoKCQlQcmVkaWNhdGU8QmlnSW50ZWdlcj4gaXNyYW1lbiA9IHQgLT4gTG9uZ1N0cmVhbS5yYW5nZUNsb3NlZCgxLCBoYWxmc3FydC5hcHBseSh0KSkucGFyYWxsZWwoKQoJCQkJLm1hcFRvT2JqKGkgLT4gQmlnSW50ZWdlci52YWx1ZU9mKGkgKiAybCArIDFsKSkKCQkJCS5ub25lTWF0Y2goaSAtPiB0LnJlbWFpbmRlcihpKS5jb21wYXJlVG8oQmlnSW50ZWdlci5aRVJPKSA9PSAwKTsKCgkJQmlnSW50ZWdlciByYW1lbiA9IEJpZ0ludGVnZXIudmFsdWVPZihMb25nLk1BWF9WQUxVRSkubXVsdGlwbHkoQmlnSW50ZWdlci52YWx1ZU9mKG11bDEpKQoJCQkJLmRpdmlkZShCaWdJbnRlZ2VyLnZhbHVlT2YoZGl2MSkpOwoJCXJhbWVuID0gcmFtZW4ubmV4dFByb2JhYmxlUHJpbWUoKTsKCQl3aGlsZSAoIWlzcmFtZW4udGVzdChyYW1lbikpIHsKCQkJU3lzdGVtLm91dC5wcmludGxuKHJhbWVuICsgIiBpcyBub3QgcmFtZW4iKTsKCQkJcmFtZW4gPSByYW1lbi5uZXh0UHJvYmFibGVQcmltZSgpOwoJCX0KCQlyZXR1cm4gcmFtZW47Cgl9CgoJLyoqCgkgKiBSYW1lbjfjga5oYWxmc3FydOOBi+OCiXNleHRhbnRzcXJ044Gr5pu444GN5o+b44GI44Gf44KC44GuCgkgKiAKCSAqIEBwYXJhbSBtdWwxCgkgKiAgICAgICAgICAgIOmWi+Wni+aVsOOBruS5l+aVsAoJICogQHBhcmFtIGRpdjEKCSAqICAgICAgICAgICAg6ZaL5aeL5pWw44Gu6Zmk5pWwCgkgKiBAcGFyYW0gbXVsMgoJICogICAgICAgICAgICDjg4vjg6Xjg7zjg4jjg7Pov5HkvLzjga7kuZfmlbAKCSAqIEBwYXJhbSBkaXYyCgkgKiAgICAgICAgICAgIOODi+ODpeODvOODiOODs+i/keS8vOOBrumZpOaVsAoJICogQHJldHVybgoJICovCglzdGF0aWMgQmlnSW50ZWdlciBSYW1lbjgobG9uZyBtdWwxLCBsb25nIGRpdjEsIGxvbmcgbXVsMiwgbG9uZyBkaXYyKSB7CgkJRnVuY3Rpb248QmlnSW50ZWdlciwgTG9uZz4gc2V4dGFudHNxcnQgPSB4IC0+IHsKCQkJQmlnSW50ZWdlciB4bm4gPSBCaWdJbnRlZ2VyLnZhbHVlT2YoKGxvbmcpIE1hdGguc3FydChMb25nLk1BWF9WQUxVRSkpLm11bHRpcGx5KEJpZ0ludGVnZXIudmFsdWVPZihtdWwyKSkKCQkJCQkuZGl2aWRlKEJpZ0ludGVnZXIudmFsdWVPZihkaXYyKSk7CgkJCUJpZ0ludGVnZXIgeG4gPSBCaWdJbnRlZ2VyLlpFUk87CgoJCQlkbyB7CgkJCQl4biA9IHhubjsKCQkJCXhubiA9IHguZGl2aWRlKHhubikuYWRkKHhubikuZGl2aWRlKEJpZ0ludGVnZXIudmFsdWVPZigybCkpOwoJCQl9IHdoaWxlICh4bm4uc3VidHJhY3QoeG4pLmFicygpLmNvbXBhcmVUbyhCaWdJbnRlZ2VyLk9ORSkgPiAwKTsKCgkJCXJldHVybiB4bm4ubWF4KHhuKS5kaXZpZGUoQmlnSW50ZWdlci52YWx1ZU9mKDZsKSkubG9uZ1ZhbHVlRXhhY3QoKTsKCQl9OwoKCQlGdW5jdGlvbjxCaWdJbnRlZ2VyLCBCb29sZWFuPiBpc3JhbWVuID0gdCAtPiB7CgkJCWlmICh0LnJlbWFpbmRlcihCaWdJbnRlZ2VyLnZhbHVlT2YoMmwpKS5jb21wYXJlVG8oQmlnSW50ZWdlci5aRVJPKSA9PSAwCgkJCQkJfHwgdC5yZW1haW5kZXIoQmlnSW50ZWdlci52YWx1ZU9mKDNsKSkuY29tcGFyZVRvKEJpZ0ludGVnZXIuWkVSTykgPT0gMCkgewoJCQkJcmV0dXJuIGZhbHNlOwoJCQl9CgoJCQlyZXR1cm4gTG9uZ1N0cmVhbS5yYW5nZSgwLCBzZXh0YW50c3FydC5hcHBseSh0KSkucGFyYWxsZWwoKS5tYXBUb09iaihpIC0+IEJpZ0ludGVnZXIudmFsdWVPZihpICogNmwgKyA1bCkpCgkJCQkJLm5vbmVNYXRjaChpIC0+IHQucmVtYWluZGVyKGkpLmNvbXBhcmVUbyhCaWdJbnRlZ2VyLlpFUk8pID09IDAKCQkJCQkJCXx8IHQucmVtYWluZGVyKGkuYWRkKEJpZ0ludGVnZXIudmFsdWVPZigybCkpKS5jb21wYXJlVG8oQmlnSW50ZWdlci5aRVJPKSA9PSAwKTsKCQl9OwoKCQlCaWdJbnRlZ2VyIHJhbWVuID0gQmlnSW50ZWdlci52YWx1ZU9mKExvbmcuTUFYX1ZBTFVFKS5tdWx0aXBseShCaWdJbnRlZ2VyLnZhbHVlT2YobXVsMSkpCgkJCQkuZGl2aWRlKEJpZ0ludGVnZXIudmFsdWVPZihkaXYxKSk7CgkJcmFtZW4gPSByYW1lbi5uZXh0UHJvYmFibGVQcmltZSgpOwoJCXdoaWxlICghaXNyYW1lbi5hcHBseShyYW1lbikpIHsKCQkJU3lzdGVtLm91dC5wcmludGxuKHJhbWVuICsgIiBpcyBub3QgcmFtZW4iKTsKCQkJcmFtZW4gPSByYW1lbi5uZXh0UHJvYmFibGVQcmltZSgpOwoJCX0KCQlyZXR1cm4gcmFtZW47Cgl9Cn0K