language agnostic - Evaluar dados rodando notación cadenas
language-agnostic dice (14)
Reglas
Escriba una función que acepte la cadena como parámetro, devolviendo el valor de expresión evaluado en notación de dados , incluida la suma y la multiplicación.
Para aclarar las cosas, aquí viene la definición EBNF de expresiones legales:
roll ::= [positive integer], "d", positive integer
entity ::= roll | positive number
expression ::= entity { [, whitespace], "+"|"*"[, whitespace], entity }
Entradas de ejemplo:
- "3d6 + 12"
- "4 * d12 + 3"
- "d100"
El uso de funciones eval , o similares, no está prohibido, pero lo aliento a resolver sin usarlas. La re-entrada es bienvenida.
No puedo proporcionar casos de prueba, ya que la salida debe ser aleatoria;).
Formatee los títulos de sus respuestas: idioma, n caracteres (notas importantes, no eval, etc.)
Mi solución de rubí , 92 81 caracteres, utilizando eval:
def f s
eval s.gsub(/(/d+)?d(/d+)/i){eval"a+=rand $2.to_i;"*a=($1||1).to_i}
end
Otra solución de rubí , no más corta (92 caracteres), pero me parece interesante: todavía usa eval, pero esta vez de una manera bastante creativa.
class Fixnum
def**b
eval"a+=rand b;"*a=self
end
end
def f s
eval s.gsub(/d/,''**'')
end
J
Con la ayuda de Cobbal, exprime todo en 93 caracteres.
$ jconsole e=:".@([`(''%''"_)@.(=&''/'')"0@,)@:(3 :''":(1".r{.y)([:+/>:@?@$) ::(y&[)0".}.y}.~r=.y i.''''d''''''@>)@;: e ''3d6 + 12'' 20 e 10$,:''3d6 + 12'' 19 23 20 26 24 20 20 20 24 27 e 10$,:''4*d12 + 3'' 28 52 56 16 52 52 52 36 44 56 e 10$,:''d100'' 51 51 79 58 22 47 95 6 5 64
F # ( no eval y no regex)
233 caracteres
Esta solución debe ser totalmente genérica, ya que puede manejar casi cualquier cadena que le lances, incluso algo loco como:
43d29d16d21 * 9 + d7d9 * 91 + 2 * d24 * 7
Necesitamos definir esto globalmente:
let r = new System.Random()
Versión completamente ofuscada:
let f(s:string)=let g d o i p (t:string)=t.Split([|d|])|>Array.fold(fun s x->o s (p x))i in g ''+''(+)0(g ''*'' (*) 1 (fun s->let b=ref true in g ''d''(+)1(fun t->if !b then b:=false;(if t.Trim()=""then 1 else int t)else r.Next(int t))s))s
Versión legible:
let f (s:string) =
let g d o i p (t:string) =
t.Split([|d|]) |> Array.fold (fun s x -> o s (p x)) i
g ''+'' (+) 0 (g ''*'' (*) 1 (fun s ->
let b = ref true
g ''d'' (+) 1 (fun t ->
if !b then b := false; (if t.Trim() = "" then 1 else int t)
else r.Next(int t)) s)) s
Estoy desafiando a alguien a superar esta solución (en cualquier idioma) sin usar eval
o expresiones regulares. Creo que es probable que sea posible, pero todavía estoy interesado en ver el enfoque.
Clase c # Se evalúa de forma recursiva para sumas y multiplicaciones, de izquierda a derecha para tiradas de dados encadenados
Ediciones:
- Eliminado
.Replace(" ","")
en cada llamada - Se agregó
.Trim()
enint.TryParse
enint.TryParse
lugar - Todo el trabajo se realiza ahora en un solo método.
- Si no se especifica el recuento de caras, asume 6 (vea el artículo de Wiki)
- Llamada redundante refactorizada para analizar el lado izquierdo de "d"
- Refactored innecesario
if
declaración
Minificado: (411 bytes)
class D{Random r=new Random();public int R(string s){int t=0;var a=s.Split(''+'');if(a.Count()>1)foreach(var b in a)t+=R(b);else{var m=a[0].Split(''*'');if(m.Count()>1){t=1;foreach(var n in m)t*=R(n);}else{var d=m[0].Split(''d'');if(!int.TryParse(d[0].Trim(),out t))t=0;int f;for(int i=1;i<d.Count();i++){if(!int.TryParse(d[i].Trim(),out f))f=6;int u=0;for(int j=0;j<(t== 0?1:t);j++)u+=r.Next(1,f);t+=u;}}}return t;}}
Forma expandida:
class D
{
/// <summary>Our Random object. Make it a first-class citizen so that it produces truly *random* results</summary>
Random r = new Random();
/// <summary>Roll</summary>
/// <param name="s">string to be evaluated</param>
/// <returns>result of evaluated string</returns>
public int R(string s)
{
int t = 0;
// Addition is lowest order of precedence
var a = s.Split(''+'');
// Add results of each group
if (a.Count() > 1)
foreach (var b in a)
t += R(b);
else
{
// Multiplication is next order of precedence
var m = a[0].Split(''*'');
// Multiply results of each group
if (m.Count() > 1)
{
t = 1; // So that we don''t zero-out our results...
foreach (var n in m)
t *= R(n);
}
else
{
// Die definition is our highest order of precedence
var d = m[0].Split(''d'');
// This operand will be our die count, static digits, or else something we don''t understand
if (!int.TryParse(d[0].Trim(), out t))
t = 0;
int f;
// Multiple definitions ("2d6d8") iterate through left-to-right: (2d6)d8
for (int i = 1; i < d.Count(); i++)
{
// If we don''t have a right side (face count), assume 6
if (!int.TryParse(d[i].Trim(), out f))
f = 6;
int u = 0;
// If we don''t have a die count, use 1
for (int j = 0; j < (t == 0 ? 1 : t); j++)
u += r.Next(1, f);
t += u;
}
}
}
return t;
}
}
Casos de prueba:
static void Main(string[] args)
{
var t = new List<string>();
t.Add("2d6");
t.Add("2d6d6");
t.Add("2d8d6 + 4d12*3d20");
t.Add("4d12");
t.Add("4*d12");
t.Add("4d"); // Rolls 4 d6
D d = new D();
foreach (var s in t)
Console.WriteLine(string.Format("{0}/t{1}", d.R(s), s));
}
Python, 197 caracteres en versión oscurecida.
Versión legible: 369 caracteres. No eval, análisis directo.
import random
def dice(s):
return sum(term(x) for x in s.split(''+''))
def term(t):
p = t.split(''*'')
return factor(p[0]) if len(p)==1 else factor(p[0])*factor(p[1])
def factor(f):
p = f.split(''d'')
if len(p)==1:
return int(f)
return sum(random.randint(1, int(g[1]) if g[1] else 6) for /
i in range(int(g[0]) if g[0] else 1))
versión comprimida: 258 caracteres, nombres de un solo carácter, expresiones condicionales abusadas, atajo en expresión lógica:
import random
def d(s):
return sum(t(x.split(''*'')) for x in s.split(''+''))
def t(p):
return f(p[0])*f(p[1]) if p[1:] else f(p[0])
def f(s):
g = s.split(''d'')
return sum(random.randint(1, int(g[1] or 6)) for i in range(int(g[0] or 1))) if g[1:] else int(s)
versión oscurecida: 216 caracteres, usando reducir, mapea en gran medida para evitar "def", "return".
import random
def d(s):
return sum(map(lambda t:reduce(lambda x,y:x*y,map(lambda f:reduce(lambda x,y:sum(random.randint(1,int(y or 6)) for i in range(int(x or 1))), f.split(''d'')+[1]),t.split(''*'')),1),s.split(''+'')))
Última versión: 197 caracteres, doblados en los comentarios de @ Brain, añadieron ejecuciones de prueba.
import random
R=reduce;D=lambda s:sum(map(lambda t:R(int.__mul__,map(lambda f:R(lambda x,y:sum(random.randint(1,int(y or 6))for i in[0]*int(x or 1)),f.split(''d'')+[1]),t.split(''*''))),s.split(''+'')))
Pruebas:
>>> for dice_expr in ["3d6 + 12", "4*d12 + 3","3d+12", "43d29d16d21*9+d7d9*91+2*d24*7"]: print dice_expr, ": ", list(D(dice_expr) for i in range(10))
...
3d6 + 12 : [22, 21, 22, 27, 21, 22, 25, 19, 22, 25]
4*d12 + 3 : [7, 39, 23, 35, 23, 23, 35, 27, 23, 7]
3d+12 : [16, 25, 21, 25, 20, 18, 27, 18, 27, 25]
43d29d16d21*9+d7d9*91+2*d24*7 : [571338, 550124, 539370, 578099, 496948, 525259, 527563, 546459, 615556, 588495]
Esta solución no puede manejar espacios en blanco sin dígitos adyacentes. así que "43d29d16d21 * 9 + d7d9 * 91 + 2 * d24 * 7" funcionará, pero "43d29d16d21 * 9 + d7d9 * 91 + 2 * d24 * 7" no funcionará, debido al segundo espacio (entre "+" y " re"). Se puede corregir eliminando primero los espacios en blanco de s, pero esto hará que el código tenga más de 200 caracteres, así que mantendré el error.
Solución de JavaScript , 340 caracteres cuando está comprimido (no eval, admite multiplicador con prefijo y adición con sufijo):
function comp (s, m, n, f, a) {
m = parseInt( m );
if( isNaN( m ) ) m = 1;
n = parseInt( n );
if( isNaN( n ) ) n = 1;
f = parseInt( f );
a = typeof(a) == ''string'' ? parseInt( a.replace(//s/g, '''') ) : 0;
if( isNaN( a ) ) a = 0;
var r = 0;
for( var i=0; i<n; i++ )
r += Math.floor( Math.random() * f );
return r * m + a;
};
function parse( de ) {
return comp.apply( this, de.match(/(?:(/d+)/s*/*/s*)?(/d*)d(/d+)(?:/s*([/+/-]/s*/d+))?/i) );
}
Código de prueba:
var test = ["3d6 + 12", "4*d12 + 3", "d100"];
for(var i in test)
alert( test[i] + ": " + parse(test[i]) );
Versión comprimida (bastante seguro de que puedes hacer más corto):
function c(s,m,n,f,a){m=parseInt(m);if(isNaN(m))m=1;n=parseInt(n);if(isNaN(n))n=1;f=parseInt(f);a=typeof(a)==''string''?parseInt(a.replace(//s/g,'''')):0;if(isNaN(a))a=0;var r=0;for(var i=0;i<n;i++)r+=Math.floor(Math.random()*f);return r*m+a;};function p(d){return c.apply(this,d.match(/(?:(/d+)/s*/*/s*)?(/d*)d(/d+)(?:/s*([/+/-]/s*/d+))?/i));}
Clojure, 854 caracteres como está, 412 encogidos
Simplemente ejecuta "(roll-dice" input-string ")".
(defn roll-dice
[string]
(let [parts (. (. (. string replace "-" "+-") replaceAll "//s" "") split "//+")
dice-func (fn [d-notation]
(let [bits (. d-notation split "d")]
(if (= 1 (count bits))
(Integer/parseInt (first bits)) ; Just a number, like 12
(if (zero? (count (first bits)))
(inc (rand-int (Integer/parseInt (second bits)))) ; Just d100 or some such
(if (. (first bits) contains "*")
(* (Integer/parseInt (. (first bits) replace "*" ""))
(inc (rand-int (Integer/parseInt (second bits)))))
(reduce +
(map #(+ 1 %)
(map rand-int
(repeat
(Integer/parseInt (first bits))
(Integer/parseInt (second bits)))))))))))]
(reduce + (map dice-func parts))))
Para reducir el tamaño, hice las variables 1 letra, moví los (primeros bits) / (segundos bits) a las variables, convertí a dado-func en una función anónima, hice un envoltorio para Integer.parseInt llamado ''i'', y eliminé los comentarios y los espacios en blanco adicionales .
Esto debería funcionar en cualquier cosa válida, con o sin espacios en blanco. Simplemente no vayas a pedir "15dROBERT", lanzará una excepción.
La forma en que funciona es dividiendo la cadena en dados (esa es la tercera línea, la let). Entonces "5d6 + 2 * d4-17" se convierte en "5d6", "2 * d4", "- 17".
Cada uno de ellos es procesado por la función dice-func, y los resultados se suman (esto es el mapa / reducir en la última línea)
Dice-func toma una pequeña cadena de dados (como "5d6") y la divide en la "d". Si solo queda una parte, es un número simple (6, -17, etc.).
Si la primera parte contiene un *, multiplicamos ese número por un intergeridor aleatorio, 1 a (número después de d), inclusive.
Si la primera parte no contiene un *, tomamos los primeros rollos aleatorios (como en la línea anterior) y los sumamos (este es el mapa / reducción en el medio).
Este fue un pequeño reto divertido.
JAVASCRIPT , 1399 caracteres, no eval
viejo post, lo sé. pero trato de contribuir
Roll = window.Roll || {};
Roll.range = function (str) {
var rng_min, rng_max, str_split,
delta, value;
str = str.replace(//s+/g, "");
str_split = str.split("-");
rng_min = str_split[0];
rng_max = str_split[1];
rng_min = parseInt(rng_min) || 0;
rng_max = Math.max(parseInt(rng_max), rng_min) || rng_min;
delta = (rng_max - rng_min + 1);
value = Math.random() * delta;
value = parseInt(value);
return value + rng_min;
};
Roll.rollStr = function (str) {
var check,
qta, max, dice, mod_opts, mod,
rng_min, rng_max,
rolls = [], value = 0;
str = str.replace(//s+/g, "");
check = str.match(/(?:^[-+]?(/d+)?(?://(/d+))?[dD](/d+)(?:([-+])(/d+)/b)?$|^(/d+)/-(/d+)$)/);
if (check == null) {return "ERROR"}
qta = check[1];
max = check[2];
dice = check[3];
mod_opts = check[4];
mod = check[5];
rng_min = check[6];
rng_max = check[7];
check = check[0];
if (rng_min && rng_max) {return Roll.range(str)}
dice = parseInt(dice);
mod_opts = mod_opts || "";
mod = parseInt(mod) || 0;
qta = parseInt(qta) || 1;
max = Math.max(parseInt(max), qta) || qta;
for (var val; max--;) {
val = Math.random() * dice;
val = Math.floor(val) + 1;
rolls.push(val);
}
if (max != qta) {
rolls.sort(function (a, b) {return a < b});
rolls.unshift(rolls.splice(0, qta));
}
while (rolls[0][0]) {value += rolls[0].shift();}
if (mod_opts == "-") {value -= mod;}
else {value += mod;}
return value
};
if (!window.diceRoll) {window.diceRoll= Roll.rollStr;}
es una tirada de dados simple, como "2d8 + 2" o "4-18" "3 / 4d6" (el mejor 3 de 4 d6)
diceRoll("2d8+2");
diceRoll("4-18");
diceRoll("3/4d6");
para verificar los rollos acumulados, mejor bucle en el resultado coincidente sobre la cadena de entrada como
r = "2d8+2+3/4d6"
r.match(/([-+])?(/d+)?(?://(/d+))?[dD](/d+)(?:([-+])(/d+)/b)?/g);
// => ["2d8+2", "+3/4d6"]
// a program can manage the "+" or "-" on the second one (usually is always an addiction)
PHP , 147 símbolos, no eval:
preg_match(''/(/d+)?d(/d+)[/s+]?([/+/*])?[/s+]?(/d+)?/'',$i,$a);$d=rand(1,$a[2])*((!$a[1])?1:$a[1]);$e[''+'']=$d+$a[4];$e[''*'']=$d*$a[4];print$e[$a[3]];
$i
contiene una cadena de entrada.
Edición: oops, se olvidó de la operación prefijada. brb
Perl , no evals, 144 caracteres, funciona varias veces, soporta múltiples tiradas de dados
sub e{($c=pop)=~y/+* /PT/d;$b=''(/d+)'';map{$a=0while$c=~s!$b?$_$b!$d=$1||1;$a+=1+int rand$2for 1..$d;$e=$2;/d/?$a:/P/?$d+$e:$d*$e!e}qw(d T P);$c}
Versión ampliada, con comentarios.
sub f {
($c = pop); #assign first function argument to $c
$c =~ tr/+* /PT/d; #replace + and * so we won''t have to escape them later.
#also remove spaces
#for each of ''d'',''T'' and ''P'', assign to $_ and run the following
map {
#repeatedly replace in $c the first instance of <number> <operator> <number> with
#the result of the code in second part of regex, capturing both numbers and
#setting $a to zero after every iteration
$a=0 while $c =~ s[(/d+)?$_(/d+)][
$d = $1 || 1; #save first parameter (or 1 if not defined) as later regex
#will overwrite it
#roll $d dice, sum in $a
for (1..$d)
{
$a += 1 + int rand $2;
}
$e = $2; #save second parameter, following regexes will overwrite
#Code blocks return the value of their last statement
if (/d/)
{
$a; #calculated dice throw
}
elsif (/P/)
{
$d + $e;
}
else
{
$d * $e;
}
]e;
} qw(d T P);
return $c;
}
EDIT limpiado, explicación actualizada a la última versión
Python , 452 bytes en la versión comprimida.
No estoy seguro de si esto es genial, feo o simplemente estúpido, pero fue divertido escribirlo.
Lo que hacemos es lo siguiente: usamos expresiones regulares (que generalmente no es la herramienta adecuada para este tipo de cosas) para convertir la cadena de notación de dados en una lista de comandos en un lenguaje pequeño, basado en la pila. Este lenguaje tiene cuatro comandos:
-
mul
multiplica los dos primeros números de la pila y empuja el resultado -
add
agrega los dos primeros números en la pila y empuja el resultado -
roll
el tamaño del dado de la pila, luego el conteo, tira un conteo de dados del lado del tamaño veces y empuja el resultado - un número simplemente se empuja sobre la pila
Esta lista de comandos es luego evaluada.
import re, random
def dice_eval(s):
s = s.replace(" ","")
s = re.sub(r"(/d+|[d+*])",r"/1 ",s) #seperate tokens by spaces
s = re.sub(r"(^|[+*] )d",r"/g<1>1 d",s) #e.g. change d 6 to 1 d 6
while "*" in s:
s = re.sub(r"([^+]+) /* ([^+]+)",r"/1 /2mul ",s,1)
while "+" in s:
s = re.sub(r"(.+) /+ (.+)",r"/1 /2add ",s,1)
s = re.sub(r"d (/d+) ",r"/1 roll ",s)
stack = []
for token in s.split():
if token == "mul":
stack.append(stack.pop() * stack.pop())
elif token == "add":
stack.append(stack.pop() + stack.pop())
elif token == "roll":
v = 0
dice = stack.pop()
for i in xrange(stack.pop()):
v += random.randint(1,dice)
stack.append(v)
elif token.isdigit():
stack.append(int(token))
else:
raise ValueError
assert len(stack) == 1
return stack.pop()
print dice_eval("2*d12+3d20*3+d6")
Por cierto (esto fue discutido en los comentarios de la pregunta), esta implementación permitirá cadenas como "2d3d6"
, entendiendo esto como "rodar un d3 dos veces, luego rodar un d6 tantas veces como el resultado de los dos rollos".
Además, aunque hay algunas comprobaciones de errores, todavía se espera una entrada válida. Al pasar "* 4", por ejemplo, se obtendrá un bucle infinito.
Aquí está la versión comprimida (no bonita):
import re, random
r=re.sub
def e(s):
s=r(" ","",s)
s=r(r"(/d+|[d+*])",r"/1 ",s)
s=r(r"(^|[+*] )d",r"/g<1>1 d",s)
while"*"in s:s=r(r"([^+]+) /* ([^+]+)",r"/1 /2M ",s)
while"+"in s:s=r(r"(.+) /+ (.+)",r"/1 /2A ",s)
s=r(r"d (/d+)",r"/1 R",s)
t=[]
a=t.append
p=t.pop
for k in s.split():
if k=="M":a(p()*p())
elif k=="A":a(p()+p())
elif k=="R":
v=0
d=p()
for i in [[]]*p():v+=random.randint(1,d)
a(v)
else:a(int(k))
return p()
Python 124 caracteres con eval, 154 sin.
Solo para mostrar que Python no tiene que ser legible, aquí hay una solución de 124 caracteres, con un enfoque similar basado en evaluación del original:
import random,re
f=lambda s:eval(re.sub(r''(/d*)d(/d+)'',lambda m:int(m.group(1)or 1)*(''+random.randint(1,%s)''%m.group(2)),s))
[Editar] Y aquí hay un personaje de 154 sin eval:
import random,re
f=lambda s:sum(int(c or 0)+sum(random.randint(1,int(b))for i in[0]*int(a or 1))for a,b,c in re.findall(r''(/d*)d(/d+)(/s*[+-]/s*/d+)?'',s))
Nota: ambos funcionarán para entradas como "2d6 + 1d3 + 5" pero no admiten variantes más avanzadas como "2d3d6" o dados negativos ("1d6-4" está bien, pero "1d6-2d4" no lo está) ( Puede eliminar 2 caracteres para no admitir números negativos en el segundo en su lugar)
Ruby, 87 caracteres, utiliza eval
Aquí está mi solución Ruby, parcialmente basada en los OP''s. Es cinco caracteres más corto y solo usa eval
una vez.
def f s
eval s.gsub(/(/d+)?[dD](/d+)/){n=$1?$1.to_i: 1;n.times{n+=rand $2.to_i};n}
end
Una versión legible del código:
def f s
eval (s.gsub /(/d+)?[dD](/d+)/ do
n = $1 ? $1.to_i : 1
n.times { n += rand $2.to_i }
n
end)
end
Ruby , 166 caracteres, no eval
En mi opinión bastante elegante;).
def g s,o=%w{/+ /* d}
o[a=0]?s[/#{p=o.pop}/]?g(s.sub(/(/d+)?/s*(#{p})/s*(/d+)/i){c=$3.to_i
o[1]?($1||1).to_i.times{a+=rand c}+a:$1.to_i.send($2,c)},o<<p):g(s,o):s
end
Versión despoblada + comentarios:
def evaluate(string, opers = ["//+","//*","d"])
if opers.empty?
string
else
if string.scan(opers.last[/.$/]).empty? # check if string contains last element of opers array
# Proceed to next operator from opers array.
opers.pop
evaluate(string, opers)
else # string contains that character...
# This is hard to deobfuscate. It substitutes subexpression with highest priority with
# its value (e.g. chooses random value for XdY, or counts value of N+M or N*M), and
# calls recursively evaluate with substituted string.
evaluate(string.sub(/(/d+)?/s*(#{opers.last})/s*(/d+)/i) { a,c=0,$3.to_i; ($2 == ''d'') ? ($1||1).to_i.times{a+=rand c}+a : $1.to_i.send($2,c) }, opers)
end
end
end
perl eval version, 72 caracteres
sub e{$_=pop;s/(/d+)?d(/d+)//$a/i;$a+=1+int rand$2-1for 0...$1-1;eval$_}
corre como
print e("4*d12+3"),"/n";
De acuerdo con la solución de Ruby, solo se puede ejecutar una vez (debe undef $a
entre ejecuciones).
Versión más corta, 68 caracteres, funky (basado en 0) dados
sub e{$_=pop;s/(/d+)?d(/d+)//$a/i;$a+=int rand$2for 0...$1-1;eval$_}
EDITAR
Perl 5.8.8 no le gustó la versión anterior, aquí hay una versión de 73 caracteres que funciona
sub e{$_=pop;s/(/d+)?d(/d+)//$a/i;$a+=1+int rand$2-1for 1...$1||1;eval$_}
Versión de 70 caracteres que admite múltiples rollos.
sub e{$_=pop;s/(/d*)d(/d+)/$a=$1||1;$a+=int rand$a*$2-($a-1)/ieg;eval}