コイン投げの半分の頭を獲得する確率を計算します。
警官のエントリ(コナーオブライエン投稿):https : //codegolf.stackexchange.com/a/100521/8927
元の質問:コイン投げの半分の頭を獲得する確率を計算します。
投稿されたソリューションには、いくつかの難読化手法が適用され、その後に同じ難読化手法の複数の層が続きました。最初のいくつかのトリックを過ぎると、実際の機能を抽出するのは単純な(退屈な場合!)タスクになりました。
nCr(a,b) = a! / ((a-b)! * b!)
result = nCr(x, x/2) / 2^x
私が見ているものを理解するのにしばらく時間がかかりました(しばらくエントロピーに関係しているのではないかと疑っていました)が、ひとたび小枝ができたら、「コイントスの確率」を検索して問題を簡単に見つけることができました。
Conor O'Brienが彼のコードの詳細な説明に挑戦したので、ここにもっと興味深いビットの要約があります:
いくつかの組み込み関数呼び出しを難読化することから始めます。これは、関数名をbase-32エンコードしてから、それらを単一文字の新しいグローバル名前空間名に割り当てることによって実現されます。実際には「atob」のみが使用されます。他の2つは単なる赤いニシンです(evalはatobと同じ速記を取り、オーバーライドされるだけで、btoaは単に使用されません)。
_=this;
[
490837, // eval -> U="undefined" -> u(x) = eval(x) (but overwritten below), y = eval
358155, // atob -> U="function (M,..." -> u(x) = atob(x)
390922 // btoa -> U="function (M,..." -> n(x) = btoa(x), U[10] = 'M'
].map(
y=function(M,i){
return _[(U=y+"")[i]] = _[M.toString(2<<2<<2)]
}
);
次に、コードを隠すためのいくつかの些細な文字列の混同があります。これらは簡単に元に戻すことができます。
u(["","GQ9ZygiYTwyPzE6YSpk","C0tYSki","SkoYSkvZChhLWIpL2QoYikg"].join("K"))
// becomes
'(d=g("a<2?1:a*d(--a)"))(a)/d(a-b)/d(b) '
u("KScpKWIsYShFLCliLGEoQyhEJyhnLGM9RSxiPUQsYT1D").split("").reverse().join("")
// becomes
"C=a,D=b,E=c,g('D(C(a,b),E(a,b))')"
難読化の大部分は、g
新しい関数を単純に定義する関数の使用です。これは再帰的に適用され、関数は新しい関数を返すか、関数をパラメーターとして必要としますが、最終的には単純化されます。これから出てくる最も興味深い関数は次のとおりです。
function e(a,b){ // a! / ((a-b)! * b!) = nCr
d=function(a){return a<2?1:a*d(--a)} // Factorial
return d(a)/d(a-b)/d(b)
}
この行には最後のトリックもあります。
U[10]+[![]+[]][+[]][++[+[]][+[]]]+[!+[]+[]][+[]][+[]]+17..toString(2<<2<<2)
// U = "function (M,i"..., so U[10] = 'M'. The rest just evaluates to "ath", so this just reads "Math"
次のビットは「.pow(T、a)」なので、「Math」でなければならない可能性が常に高いです!
機能拡張のルートに沿って行った手順は次のとおりです。
// Minimal substitutions:
function g(s){return Function("a","b","c","return "+s)};
function e(a,b,c){return (d=g("a<2?1:a*d(--a)"))(a)/d(a-b)/d(b)}
function h(a,b,c){return A=a,B=b,g('A(a,B(a))')}
function j(a,b,c){return a/b}
function L(a,b,c){return Z=a,Y=b,g('Z(a,Y)')}
k=L(j,T=2);
function F(a,b,c){return C=a,D=b,E=c,g('D(C(a,b),E(a,b))')}
RESULT=F(
h(e,k),
j,
function(a,b,c){return _['Math'].pow(T,a)}
);
// First pass
function e(a,b){
d=function(a){return a<2?1:a*d(--a)}
return d(a)/d(a-b)/d(b)
}
function h(a,b){
A=a
B=b
return function(a){
return A(a,B(a))
}
}
function j(a,b){ // ratio function
return a/b
}
function L(a,b){ // binding function (binds param b)
Z=a
Y=b
return function(a){
return Z(a,Y)
}
}
T=2; // Number of states the coin can take
k=L(j,T); // function to calculate number of heads required for fairness
function F(a,b,c){
C=a
D=b
E=c
return function(a,b,c){return D(C(a,b),E(a,b))}
}
RESULT=F(
h(e,k),
j,
function(a){return Math.pow(T,a)}
);
// Second pass
function e(a,b){...}
function k(a){
return a/2
}
function F(a,b,c){
C=a
D=b
E=c
return function(a,b,c){return D(C(a,b),E(a,b))}
}
RESULT=F(
function(a){
return e(a,k(a))
},
function(a,b){
return a/b
},
function(a){return Math.pow(2,a)}
);
// Third pass
function e(a,b) {...}
C=function(a){ // nCr(x,x/2) function
return e(a,a/2)
}
D=function(a,b){ // ratio function
return a/b
}
E=function(a){return Math.pow(2,a)} // 2^x function
RESULT=function(a,b,c){
return D(C(a,b),E(a,b))
}
関数のネストの構造は、ユーティリティに基づいています。最も外側の「D」/「j」関数が比率を計算し、次に内側の「C」/「h」および「E」(インライン)関数が必要なコインフリップカウントを計算します。3番目のパスで削除された「F」関数は、これらを使用可能な全体に接続する役割を果たします。同様に、「k」関数は、観察する必要があるヘッドの数を選択する役割を果たします。パラメータバインディング関数「L」を介して比率関数「D」/「j」に委任するタスク。修正パラメータに、ここで使用b
するT
(コインを取ることができます状態の数であること、ここでは常に2)。
最終的に、次のようになります。
function e(a,b){ // a! / ((a-b)! * b!)
d=function(a){return a<2?1:a*d(--a)} // Factorial
return d(a)/d(a-b)/d(b)
}
RESULT=function(a){
return e(a, a/2) / Math.pow(2,a)
}