Calcule la probabilidad de obtener la mitad de caras que arrojar monedas.
Entrada de policías (publicada por Conor O'Brien): /codegolf//a/100521/8927
Pregunta original: Calcule la probabilidad de obtener la mitad de caras que arrojar monedas.
La solución publicada tuvo un par de técnicas de ofuscación aplicadas, seguidas de múltiples capas de la misma técnica de ofuscación. Una vez pasados los primeros trucos, se convirtió en una tarea simple (¡aunque tediosa!) Extraer la función real:
nCr(a,b) = a! / ((a-b)! * b!)
result = nCr(x, x/2) / 2^x
Me tomó un tiempo darme cuenta de lo que estaba mirando (durante un tiempo sospeché algo que tenía que ver con la entropía), pero una vez que se movió, logré encontrar la pregunta fácilmente buscando "probabilidad de lanzamiento de monedas".
Dado que Conor O'Brien desafió una explicación en profundidad de su código, aquí hay un resumen de los bits más interesantes:
Comienza por ofuscar algunas llamadas a funciones incorporadas. Esto se logra codificando los nombres de las funciones en base-32 y luego asignándolos a nuevos nombres de espacios de nombres globales de un solo carácter. Solo se usa 'atob'; los otros 2 son solo pistas falsas (eval toma la misma taquigrafía que atob, solo para anularse, y btoa simplemente no se usa).
_=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)]
}
);
A continuación, hay un par de mezclas triviales de cadenas para ocultar el código. Estos se revierten fácilmente:
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 mayor parte de la ofuscación es el uso de la g
función, que simplemente define nuevas funciones. Esto se aplica de forma recursiva, con funciones que devuelven nuevas funciones, o que requieren funciones como parámetros, pero eventualmente se simplifica de inmediato. La función más interesante que resulta de esto es:
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)
}
También hay un truco final con esta línea:
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"
Aunque como el siguiente bit es ".pow (T, a)", ¡siempre fue bastante probable que tuviera que ser "Math"!
Los pasos que tomé a lo largo de la ruta de las funciones de expansión fueron:
// 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 estructura de la función de anidamiento se basa en la utilidad; la función más externa "D" / "j" calcula una relación, luego las funciones internas "C" / "h" y "E" (en línea) calculan los recuentos de monedas necesarios. La función "F", eliminada en el tercer paso, es responsable de conectarlos en un todo utilizable. Del mismo modo, la función "k" es responsable de elegir el número de cabezas que deben observarse; una tarea que delega a la función de relación "D" / "j" a través de la función de enlace de parámetros "L"; se usa aquí para fijar el parámetro b
a T
(aquí siempre 2, es el número de estados que puede tomar la moneda)
Al final, obtenemos:
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)
}