Dom*_*nic 12 c# math computer-science dice
所以我希望能够在C#中解析和评估"骰子表达式".骰子表达式定义如下:
<expr> := <expr> + <expr>
| <expr> - <expr>
| [<number>]d(<number>|%)
| <number>
<number> := positive integer
Run Code Online (Sandbox Code Playgroud)
所以例如d6+20-2d3是允许的,并且应该评估为
rand.Next(1, 7) + 20 - (rand.Next(1, 4) + rand.Next(1, 4))
Run Code Online (Sandbox Code Playgroud)
也d%应该相当于d100.
我知道我可以解决一些解决方案,但我也知道这似乎是一个非常典型的计算机科学类型的问题,所以必须有一些我应该研究的超优雅解决方案.
我希望我的解析结果具有以下功能:
1d6-2d3+20.任何实例d%也会变成d100标准化形式.1*6+20+2*3 = 32.我知道这正是Haskell的类型,可能还有其他功能类型的语言,但是如果可能的话,我想留在C#中.
我最初的想法倾向于递归,列表,也许还有一些LINQ,但是,如果我尝试没有知道事物的人的一些指示,我肯定它最终会成为一个不优雅的混乱.
另一种可能有效的策略是一些基于正则表达式的初始字符串替换,将骰子表达式转换为rand.Next调用,然后进行即时评估或编译......这实际上有用吗?我怎么能避免rand每次都创建一个新对象?
这是我最终提出的:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
public enum DiceExpressionOptions
{
None,
SimplifyStringValue
}
public class DiceExpression
{
/* <expr> := <expr> + <expr>
* | <expr> - <expr>
* | [<number>]d(<number>|%)
* | <number>
* <number> := positive integer
* */
private static readonly Regex numberToken = new Regex("^[0-9]+$");
private static readonly Regex diceRollToken = new Regex("^([0-9]*)d([0-9]+|%)$");
public static readonly DiceExpression Zero = new DiceExpression("0");
private List<KeyValuePair<int, IDiceExpressionNode>> nodes = new List<KeyValuePair<int, IDiceExpressionNode>>();
public DiceExpression(string expression)
: this(expression, DiceExpressionOptions.None)
{ }
public DiceExpression(string expression, DiceExpressionOptions options)
{
// A well-formed dice expression's tokens will be either +, -, an integer, or XdY.
var tokens = expression.Replace("+", " + ").Replace("-", " - ").Split(' ', StringSplitOptions.RemoveEmptyEntries);
// Blank dice expressions end up being DiceExpression.Zero.
if (!tokens.Any())
{
tokens = new[] { "0" };
}
// Since we parse tokens in operator-then-operand pairs, make sure the first token is an operand.
if (tokens[0] != "+" && tokens[0] != "-")
{
tokens = (new[] { "+" }).Concat(tokens).ToArray();
}
// This is a precondition for the below parsing loop to make any sense.
if (tokens.Length % 2 != 0)
{
throw new ArgumentException("The given dice expression was not in an expected format: even after normalization, it contained an odd number of tokens.");
}
// Parse operator-then-operand pairs into this.nodes.
for (int tokenIndex = 0; tokenIndex < tokens.Length; tokenIndex += 2)
{
var token = tokens[tokenIndex];
var nextToken = tokens[tokenIndex + 1];
if (token != "+" && token != "-")
{
throw new ArgumentException("The given dice expression was not in an expected format.");
}
int multiplier = token == "+" ? +1 : -1;
if (DiceExpression.numberToken.IsMatch(nextToken))
{
this.nodes.Add(new KeyValuePair<int, IDiceExpressionNode>(multiplier, new NumberNode(int.Parse(nextToken))));
}
else if (DiceExpression.diceRollToken.IsMatch(nextToken))
{
var match = DiceExpression.diceRollToken.Match(nextToken);
int numberOfDice = match.Groups[1].Value == string.Empty ? 1 : int.Parse(match.Groups[1].Value);
int diceType = match.Groups[2].Value == "%" ? 100 : int.Parse(match.Groups[2].Value);
this.nodes.Add(new KeyValuePair<int, IDiceExpressionNode>(multiplier, new DiceRollNode(numberOfDice, diceType)));
}
else
{
throw new ArgumentException("The given dice expression was not in an expected format: the non-operand token was neither a number nor a dice-roll expression.");
}
}
// Sort the nodes in an aesthetically-pleasing fashion.
var diceRollNodes = this.nodes.Where(pair => pair.Value.GetType() == typeof(DiceRollNode))
.OrderByDescending(node => node.Key)
.ThenByDescending(node => ((DiceRollNode)node.Value).DiceType)
.ThenByDescending(node => ((DiceRollNode)node.Value).NumberOfDice);
var numberNodes = this.nodes.Where(pair => pair.Value.GetType() == typeof(NumberNode))
.OrderByDescending(node => node.Key)
.ThenByDescending(node => node.Value.Evaluate());
// If desired, merge all number nodes together, and merge dice nodes of the same type together.
if (options == DiceExpressionOptions.SimplifyStringValue)
{
int number = numberNodes.Sum(pair => pair.Key * pair.Value.Evaluate());
var diceTypes = diceRollNodes.Select(node => ((DiceRollNode)node.Value).DiceType).Distinct();
var normalizedDiceRollNodes = from type in diceTypes
let numDiceOfThisType = diceRollNodes.Where(node => ((DiceRollNode)node.Value).DiceType == type).Sum(node => node.Key * ((DiceRollNode)node.Value).NumberOfDice)
where numDiceOfThisType != 0
let multiplicand = numDiceOfThisType > 0 ? +1 : -1
let absNumDice = Math.Abs(numDiceOfThisType)
orderby multiplicand descending
orderby type descending
select new KeyValuePair<int, IDiceExpressionNode>(multiplicand, new DiceRollNode(absNumDice, type));
this.nodes = (number == 0 ? normalizedDiceRollNodes
: normalizedDiceRollNodes.Concat(new[] { new KeyValuePair<int, IDiceExpressionNode>(number > 0 ? +1 : -1, new NumberNode(number)) })).ToList();
}
// Otherwise, just put the dice-roll nodes first, then the number nodes.
else
{
this.nodes = diceRollNodes.Concat(numberNodes).ToList();
}
}
public override string ToString()
{
string result = (this.nodes[0].Key == -1 ? "-" : string.Empty) + this.nodes[0].Value.ToString();
foreach (var pair in this.nodes.Skip(1))
{
result += pair.Key == +1 ? " + " : " ? "; // NOTE: unicode minus sign, not hyphen-minus '-'.
result += pair.Value.ToString();
}
return result;
}
public int Evaluate()
{
int result = 0;
foreach (var pair in this.nodes)
{
result += pair.Key * pair.Value.Evaluate();
}
return result;
}
public decimal GetCalculatedAverage()
{
decimal result = 0;
foreach (var pair in this.nodes)
{
result += pair.Key * pair.Value.GetCalculatedAverage();
}
return result;
}
private interface IDiceExpressionNode
{
int Evaluate();
decimal GetCalculatedAverage();
}
private class NumberNode : IDiceExpressionNode
{
private int theNumber;
public NumberNode(int theNumber)
{
this.theNumber = theNumber;
}
public int Evaluate()
{
return this.theNumber;
}
public decimal GetCalculatedAverage()
{
return this.theNumber;
}
public override string ToString()
{
return this.theNumber.ToString();
}
}
private class DiceRollNode : IDiceExpressionNode
{
private static readonly Random roller = new Random();
private int numberOfDice;
private int diceType;
public DiceRollNode(int numberOfDice, int diceType)
{
this.numberOfDice = numberOfDice;
this.diceType = diceType;
}
public int Evaluate()
{
int total = 0;
for (int i = 0; i < this.numberOfDice; ++i)
{
total += DiceRollNode.roller.Next(1, this.diceType + 1);
}
return total;
}
public decimal GetCalculatedAverage()
{
return this.numberOfDice * ((this.diceType + 1.0m) / 2.0m);
}
public override string ToString()
{
return string.Format("{0}d{1}", this.numberOfDice, this.diceType);
}
public int NumberOfDice
{
get { return this.numberOfDice; }
}
public int DiceType
{
get { return this.diceType; }
}
}
}
Run Code Online (Sandbox Code Playgroud)
你可以在C#的编译器编译器(比如Yacc)中使用你的语法(比如antlr),或者只是开始写你的递归下降解析器.
然后,您构建一个可访问的内存数据结构(如果您想要除+之外的任意数学运算的树),那么您需要编写几个访问者:
RollVisitor:初始化rand种子,然后访问每个节点,累积结果GetMaxVisitor:求和每个骰子的上限 PrettyPrintVisitor,RollTwiceVisitor等等)我认为可访问树在这里是一个值得解决的问题.