Оценить строковые нотации
Правила
Напишите функцию, которая принимает строку как параметр, возвращая
оценивается значение выражения в нотация кости,
включая сложение и умножение.
Чтобы прояснить ситуацию, здесь приводится определение юридических выражений EBNF:
roll ::= [positive integer], "d", positive integer
entity ::= roll | positive number
expression ::= entity { [, whitespace], "+"|"*"[, whitespace], entity }
Пример ввода:
- "3d6 + 12"
- "4 * d12 + 3"
- "d100"
Использование функций eval или подобных функций не запрещено, но я поощряю
к решению без их использования. Добро пожаловать в Re-entrancy.
Я не могу предоставить тестовые примеры, так как вывод должен быть случайным;).
Отформатируйте названия своих ответов: язык, n символов (важные заметки - нет eval и т.д.)
Мое рубиновое решение, 92 81 символ, используя eval:
def f s
eval s.gsub(/(\d+)?d(\d+)/i){eval"a+=rand $2.to_i;"*a=($1||1).to_i}
end
Еще одно рубиновое решение, но не короче (92 символа), но я считаю это интересным - он по-прежнему использует eval, но на этот раз совершенно творческим способом.
class Fixnum
def**b
eval"a+=rand b;"*a=self
end
end
def f s
eval s.gsub(/d/,'**')
end
Ответы
Ответ 1
Класс С#. Он рекурсивно рекурсивно оценивает добавление и умножение, слева направо для цепных бросков кубика
редактирует:
- Удалено
.Replace(" ","")
для каждого вызова
- Добавлен
.Trim()
на int.TryParse
вместо
- Вся работа теперь выполняется одним способом.
- Если счетчик граней лиц не указан, предполагается 6 (см. статью Wiki).
- Реализованный избыточный вызов для синтаксического анализа левой стороны "d"
- Реализован ненужный оператор
if
Minified: (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;}}
Расширенная форма:
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;
}
}
Тестовые случаи:
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));
}
Ответ 2
С помощью cobbal help сжимайте все на 93 символа.
$ jconsole
e=:"[email protected]([`('%'"_)@.(=&'/')"[email protected],)@:(3 :'":(1".r{.y)([:+/>:@[email protected]$) ::(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
Ответ 3
JavaScript, 340 символов при сжатии (без eval, поддерживает префиксный мультипликатор и добавление суффикса):
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) );
}
Тестовый код:
var test = ["3d6 + 12", "4*d12 + 3", "d100"];
for(var i in test)
alert( test[i] + ": " + parse(test[i]) );
Сжатая версия (довольно уверенно, что вы можете сделать короче):
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));}
Ответ 4
perl версия eval, 72 символа
sub e{$_=pop;s/(\d+)?d(\d+)/\$a/i;$a+=1+int rand$2-1for 0...$1-1;eval$_}
работает как
print e("4*d12+3"),"\n";
На основе решения ruby можно запускать только один раз (вы должны undef $a
между прогонами).
более короткая версия, 68 символов, фанки (на основе 0) кости
sub e{$_=pop;s/(\d+)?d(\d+)/\$a/i;$a+=int rand$2for 0...$1-1;eval$_}
ИЗМЕНИТЬ
perl 5.8.8 не понравилась предыдущая версия, здесь 73 char версия, которая работает
sub e{$_=pop;s/(\d+)?d(\d+)/\$a/i;$a+=1+int rand$2-1for 1...$1||1;eval$_}
70 char версия, поддерживающая несколько рулонов
sub e{$_=pop;s/(\d*)d(\d+)/$a=$1||1;$a+=int rand$a*$2-($a-1)/ieg;eval}
Ответ 5
Clojure, 854 символа как есть, 412 сокращено
Просто запустите "(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))))
Чтобы сжиматься, я делал переменные 1 буквой, перемещал (первые биты)/(второй бит) в переменные, делал dice-func анонимной функцией, делал обертку для Integer.parseInt с именем 'i' и удалял комментарии и дополнительные пробелы.
Это должно работать над любым действительным, с пробелом или без него. Просто не просите об этом "15dROBERT", он выдает исключение.
Они, как он работает, - это разбиение строки на кубики (3-я строка, let). Таким образом, "5d6 + 2 * d4-17" становится "5d6", "2 * d4", "- 17".
Затем каждый из них обрабатывается функцией dice-func, и результаты складываются (это карта/сокращение в последней строке)
Dice-func берет небольшую строку в кости (например, "5d6" ) и разбивает ее на "d". Если осталось только одна часть, это было простое число (6, -17 и т.д.).
Если первая часть содержит a *, мы умножаем это число на случайный интергер, от 1 до (число после d) включительно.
Если первая часть не содержит *, мы берем первые число случайных рулонов (как и предыдущую строку) и добавляем их (это карта/сокращение посередине).
Это была интересная задача.
Ответ 6
F # (без eval и без регулярного выражения)
233 символа
Это решение должно быть полностью общим, поскольку оно может обрабатывать практически любую строку, которую вы бросаете на нее, даже что-то сумасшедшее, например:
43d29d16d21 * 9 + d7d9 * 91 + 2 * d24 * 7
Необходимо определить это глобально:
let r = new System.Random()
Полностью запутанная версия:
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
Считываемая версия:
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
Мне сложно кого-то победить в этом решении (на любом языке) без использования eval
или регулярных выражений. Я думаю, что это возможно, но я заинтересован в том, чтобы подход все еще был.
Ответ 7
perl, нет оценок, 144 символа, работает несколько раз, поддерживает несколько бросков кубиков
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}
Расширенная версия с комментариями
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;
}
ИЗМЕНИТЬ, обновлено описание последней версии
Ответ 8
python, 197 символов в скрытой версии.
Считываемая версия: 369 символов. Отсутствие анализа, прямой анализ.
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))
сжатая версия: 258 символов, одиночные имена символов, условные выражения со злоупотреблением, ярлык в логическом выражении:
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)
скрытая версия: 216 символов, используя сокращение, сильно преобразуются, чтобы избежать "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('+')))
Последняя версия: 197 символов, сложенных в комментариях @Brain, добавлены тесты тестирования.
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('+')))
Тесты:
>>> 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]
Это решение не может обрабатывать пробелы без смежных цифр. поэтому "43d29d16d21 * 9 + d7d9 * 91 + 2 * d24 * 7" будет работать, но "43d29d16d21 * 9 + d7d9 * 91 + 2 * d24 * 7" не будет, из-за второго пространства (между "+" и "г" ). Его можно исправить, сначала удалив пробелы из s, но это сделает код длиннее 200 символов, поэтому я сохраню ошибку.
Ответ 9
Python 124 символа с eval, 154 без.
Просто, чтобы показать, что python не должен быть доступен для чтения, здесь используется 124-символьное решение с аналогичным подходом к оригиналу:
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))
[Edit] И здесь 154 символа без 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))
Примечание: оба будут работать на входы типа "2d6 + 1d3 + 5", но не поддерживают более продвинутые варианты, такие как "2d3d6" или отрицательные кости ( "1d6-4" в порядке, но "1d6-2d4" isn ' t) (Вы можете сбрить 2 символа, чтобы не поддерживать отрицательные числа вообще во втором)
Ответ 10
Ruby, 166 символов, eval
По-моему довольно элегантно;).
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
Deobfuscated version + комментарии:
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
Ответ 11
Python, 452 байта в сжатой версии
Я не уверен, что это круто, уродливо или просто глупо, но было весело писать его.
Мы делаем следующее: Мы используем регулярные выражения (которые обычно не являются подходящим инструментом для такого рода вещей), чтобы преобразовать строку нотации в кости в список команд на небольшом, основанном на стеках языке. Этот язык имеет четыре команды:
-
mul
умножает верхние два числа на стек и выталкивает результат
-
add
добавляет два верхних числа в стек и выталкивает результат
-
roll
выталкивает размер кубика из стека, затем подсчитывает, свертывает размерное число раз в кости и выталкивает результат
- число просто нажимается на стек
Затем этот список команд оценивается.
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")
Кстати, (это обсуждалось в комментариях к вопросу), эта реализация позволит использовать строки типа "2d3d6"
, понимая это как "рулон d3 два раза", а затем катит d6 столько раз, сколько результат двух рулонов. "
Кроме того, хотя есть некоторая проверка ошибок, он все еще ожидает действительный ввод. Например, передача "* 4" приведет к бесконечному циклу.
Вот сжатая версия (не очень):
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()
Ответ 12
Ruby, 87 символов, использует eval
Здесь мое решение Ruby, частично основанное на OP. Это пять символов короче и использует только eval
один раз.
def f s
eval s.gsub(/(\d+)?[dD](\d+)/){n=$1?$1.to_i: 1;n.times{n+=rand $2.to_i};n}
end
Читаемая версия кода:
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
Ответ 13
JAVASCRIPT, 1399 символов, нет eval
Старый пост, я знаю. но я стараюсь внести вклад
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;}
это один бросок кости, например "2d8 + 2" или "4-18" "3/4d6" (лучший 3 из 4 d6)
diceRoll("2d8+2");
diceRoll("4-18");
diceRoll("3/4d6");
чтобы проверить кумулятивные ролики, лучший цикл на согласованном результате, введя строку ввода, например
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)
Ответ 14
PHP, 147 символов, без 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
содержит строку ввода.
Изменить: oops, забыл о префиксной операции. BRB.