Calcola la probabilità di ottenere la metà delle teste dei lanci di monete.
Voce poliziotti (pubblicato da Conor O'Brien): /codegolf//a/100521/8927
Domanda originale: calcola la probabilità di ottenere la metà delle teste dei lanci di monete.
Alla soluzione pubblicata sono state applicate un paio di tecniche di offuscamento, seguite da più strati della stessa tecnica di offuscamento. Una volta passati i primi trucchi, è diventato un compito semplice (se noioso!) Estrarre la funzione effettiva:
nCr(a,b) = a! / ((a-b)! * b!)
result = nCr(x, x/2) / 2^x
Ci sono voluti un po 'per capire cosa stavo guardando (per un po' sospettavo che qualcosa avesse a che fare con l'entropia), ma una volta risolto, sono riuscito a trovare facilmente la domanda cercando "probabilità di lancio della moneta".
Poiché Conor O'Brien ha sfidato una spiegazione approfondita del suo codice, ecco una carrellata dei bit più interessanti:
Si inizia offuscando alcune chiamate di funzione integrate. Ciò si ottiene con la base-32 codificando i nomi delle funzioni, quindi assegnandoli a nuovi nomi di spazi dei nomi globali di un singolo carattere. Viene effettivamente utilizzato solo "atob"; gli altri 2 sono solo aringhe rosse (eval prende la stessa scorciatoia di atob, solo per essere scavalcato, e btoa semplicemente non viene usato).
_=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)]
}
);
Successivamente ci sono un paio di banali confusioni di stringhe per nascondere il codice. Questi sono facilmente invertiti:
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))')"
La maggior parte dell'offuscamento è l'uso della g
funzione, che definisce semplicemente nuove funzioni. Questo viene applicato in modo ricorsivo, con funzioni che restituiscono nuove funzioni o che richiedono funzioni come parametri, ma alla fine si semplifica verso il basso. La funzione più interessante da scoprire è:
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)
}
C'è anche un trucco finale con questa linea:
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"
Sebbene dal momento che il prossimo bit sia ".pow (T, a)", è sempre stato abbastanza probabile che dovesse essere "Math"!
I passi che ho intrapreso lungo il percorso delle funzioni in espansione sono stati:
// 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))
}
La struttura della funzione di annidamento si basa sull'utilità; la funzione "D" / "j" più esterna calcola un rapporto, quindi le funzioni "C" / "h" ed "E" (in linea) interne calcolano i conteggi di lancio della moneta necessari. La funzione "F", rimossa nel terzo passaggio, è responsabile del collegamento di questi in un insieme utilizzabile. Allo stesso modo la funzione "k" è responsabile della scelta del numero di teste che devono essere osservate; un'attività che delega alla funzione di rapporto "D" / "j" tramite la funzione di associazione dei parametri "L"; qui utilizzato il parametro correzione b
a T
(qui sempre 2, essendo il numero di stati della moneta può prendere).
Alla fine, otteniamo:
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)
}