24点计算
在家和太太拿扑克算24点,屡战屡败。为了争口气,于是偷偷写了个小程序来作弊,哈哈。
首先对问题进行分析,24点,就是给定4个操作数,用四则运算符将它们连成合法的算式,可以加括号,以求得24.
碰到的第一个问题就是如何表示算式,常见的算式都是中缀表达式,即运算符在两个操作数之间。中缀表达式的好处是符合人的习惯,容易理解,缺陷则是需要借助额外的括号才能表示清楚
如下面的算式
10-6/2-1
按照运算符优先次序是先计算6/2,再用10减去其结果3,再用刚才的结果7-1得6
那我们要表示这种优先次序呢?
(10-6)/(2-1)
不加括号时无法用中缀准确表达,一加括号,我们的问题就会引入额外的复杂度。
但后缀表达式则不借助括号就能轻松表达以上的两种优先关系。
10 6 2 / - 1 - 表示10 - 6/2 - 1
10 6 - 2 1 - /表示 (10 - 6) / (2 - 1)
解释:后缀表达式可以看成一个栈操作序列,从左到右扫描表达式,看见一个操作数则压栈,看见一个操作符则从栈上弹出其所需个数的操作数,运算后再将结果压入栈中。
如果用后缀表达式来表示给定4个数所有合法的四则运算式,则其应为4个数全排列中的每一种,和从四种运算符中可重复抽取3个组成的每一种序列,组成的任一合法后缀表达式。
何为合法的后缀表达式?在本文场景下即任一运算符出现时栈中至少有两个操作数。如果去掉第一个操作数,则余下序列中任意一个从头开始的子序列中操作数个数不小于操作符个数。若将其替换为栈操作,操作数看成一个入栈操作,操作符看成一个出栈操作,用X表示入栈,S表示出栈,则对应的序列如XSXSXXSS应是一个合法的操作数序列。
相关代码如下

{
public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
{
foreach(T t in enumerable)
{
action(t);
}
}
/// <summary>
/// delete an object from an object array (use reference equal to compare two objects)
/// </summary>
/// <param name="objs"></param>
/// <param name="v"></param>
/// <returns></returns>
private static Object[] SkipByReference(Object[] objs, Object v)
{
return objs.Where(x => !Object.ReferenceEquals(x, v)).ToArray();
}
/// <summary>
/// generate all permutations of objects in objs
/// </summary>
/// <param name="objs"></param>
/// <returns></returns>
public static IEnumerable<Object[]> GetPernumtation(Object[] objs)
{
Int32 n = objs.Length;
Debug.Assert(n > 0);
if (n == 1)
{
yield return objs;
}
else
{
Object[] current = new Object[n];
for (int i = 0; i < n; ++i)
{
current[0] = objs[i];
foreach (Object[] next in GetPernumtation(SkipByReference(objs, objs[i])))
{
Array.Copy(next, 0, current, 1, n - 1);
yield return current.Clone() as Object[];
}
}
}
}
/// <summary>
/// Generate repeatable
/// </summary>
/// <param name="objs"></param>
/// <param name="resultCount"></param>
/// <returns></returns>
public static IEnumerable<Object[]> GetRepeatablePermutation(Object[] objs, int resultCount)
{
if (resultCount == 1)
{
foreach (Object obj in objs)
{
yield return new Object[] { obj };
}
}
else
{
Object[] result = new Object[resultCount];
for (int i = 0; i < objs.Length; ++i)
{
result[0] = objs[i];
foreach (Object[] next in GetRepeatablePermutation(objs, resultCount - 1))
{
Array.Copy(next, 0, result, 1, resultCount - 1);
yield return result.Clone() as Object[];
}
}
}
}
/// <summary>
/// Generate all sequences of possible push ans pop in form of String like "XXXYYY"
/// X means push and Y means pop
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
public static IEnumerable<String> GenerateStackOperateSequences(int n)
{
Char[] results = new Char[2 * n];
return GenerateStackOperateSequencesPrivate(results, 0, n, n).Select(x=>new String(x));
}
private static IEnumerable<Char[]> GenerateStackOperateSequencesPrivate(char[] results, int moreX, int leftX, int leftY)
{
int startIndex = results.Length - leftX - leftY;
if (startIndex == results.Length)
{
yield return results.Clone() as char[];
}
if (moreX > 0)
{
results[startIndex] = 'Y';
foreach(var v in GenerateStackOperateSequencesPrivate(results, moreX - 1, leftX, leftY - 1))
{
yield return v;
}
}
if (leftX > 0)
{
results[startIndex] = 'X';
foreach (var v in GenerateStackOperateSequencesPrivate(results, moreX + 1, leftX - 1, leftY))
{
yield return v;
}
}
yield break;
}
}
private static IEnumerable<Instruction[]> GenerateInsructions(Object[] operands,
Instruction[] instructionCandidates)
{
string[] patterns = Permutation.GenerateStackOperateSequences(operands.Length - 1).Select(x => 'X' + x).ToArray();
int instructionCountExpected = operands.Count() - 1;
List<Object[]> operantsCandidate = new List<object[]>(
Permutation.GetPernumtation(operands));
List<Object[]> instructionCandiatesList = new List<Object[]>(
Permutation.GetRepeatablePermutation(instructionCandidates, instructionCountExpected));
List<Instruction> result = new List<Instruction>();
foreach (Object[] operants in operantsCandidate)
{
Queue<Object> operantsQueue = new Queue<object>(operants);
foreach (Object[] instructions in instructionCandiatesList)
{
foreach (String pattern in patterns)
{
int iA = 0;
int iB = 0;
result.Clear();
for (int i = 0; i < pattern.Length; ++i)
{
if (pattern[i] == 'X')
{
result.Add(new LoadInstruction(operants[iA++]));
}
else
{
result.Add(instructions[iB++] as Instruction);
}
}
yield return result.ToArray();
}
}
}
}
至此我们解决了生成所有可能的表达式问题,但还没有考虑如何计算这些表达式。最简单的办法是直接写一个解释器,对表达式中每个元素依次解析。但如果我们增加一种运算,如乘方pow,解释器就需要相应修改。一个更通用的方案是构造一个基于栈的虚拟机,将后缀表达式每个元素替换为一个指令(instruction),操作数替换为load指令,操作符替换为操作指令,再逐条执行指令。

{
public abstract String InstructionName { get; }
public abstract void Execute(RunTime runtime);
/// <summary>
/// Simple form of an instruction
/// For a load instruction just the number to load
/// for arighmetic, just +-*/
/// </summary>
/// <returns></returns>
public abstract String GetSimpleForm();
}
public abstract class BinaryInstruction:Instruction
{
public override string InstructionName
{
get { return "Binary"; }
}
public override void Execute(RunTime runtime)
{
// op1 is the one push into stack first
Object operand2 = runtime.Pop();
Object operand1 = runtime.Pop();
runtime.Push(DoExecute(operand1, operand2));
}
protected abstract Object DoExecute(Object operant1, Object operand2);
}
public enum ArithmeticOperator
{
Add,
Sub,
Mul,
Div,
Log,
Pow
}
public class ArithmeticInstruction:BinaryInstruction
{
private ArithmeticOperator _op;
public ArithmeticInstruction(ArithmeticOperator op)
{
_op = op;
}
protected override Object DoExecute(object operant1, object operand2)
{
double op1 = (double)operant1;
double op2 = (double)operand2;
switch (_op)
{
case ArithmeticOperator.Add:
return op1 + op2;
case ArithmeticOperator.Sub:
return op1 - op2;
case ArithmeticOperator.Mul:
return op1 * op2;
case ArithmeticOperator.Div:
return op1 / op2;
}
throw new NotImplementedException();
}
public override string ToString()
{
return _op.ToString();
}
public override String GetSimpleForm()
{
switch (_op)
{
case ArithmeticOperator.Add:
return "+";
case ArithmeticOperator.Sub:
return "-";
case ArithmeticOperator.Mul:
return "*";
case ArithmeticOperator.Div:
return "/";
}
throw new NotImplementedException();
}
}
public class LoadInstruction:Instruction
{
private Object _value;
public LoadInstruction(Object value)
{
_value = value;
}
public override string InstructionName
{
get { return "Load"; }
}
public override void Execute(RunTime runtime)
{
runtime.Push(_value);
}
public override string ToString()
{
return String.Format("{0}:{1}", InstructionName, _value == null ? String.Empty : _value.ToString());
}
public override string GetSimpleForm()
{
return _value.ToString();
}
}
public class RunTime
{
Stack<Object> _stack = new Stack<object>();
public void Push(Object v)
{
_stack.Push(v);
}
public Object Pop()
{
return _stack.Pop();
}
public Object Execute(Instruction[] instructions)
{
instructions.ForEach(x=>x.Execute(this));
return Pop();
}
}
最后还需要解决一个问题,如何把得到的指令集以便于阅读的方式输出

{
Stack<String> _stack = new Stack<String>();
public String Convert(Instruction[] instructions)
{
foreach (var v in instructions)
{
if (v is LoadInstruction)
{
_stack.Push(v.GetSimpleForm());
}
else if (v is BinaryInstruction)
{
String s2 = PopNormalized();
String s1 = PopNormalized();
_stack.Push(s1 + " " + v.GetSimpleForm() + " " + s2);
}
}
return _stack.Pop();
}
private String PopNormalized()
{
return AddBrackIfNeeded(_stack.Pop());
}
private static String AddBrackIfNeeded(String s)
{
if (s.IndexOf(' ') >= 0)
{
return "(" + s + ")";
}
else
{
return s;
}
}
}
稍微对程序做了下扩展,可以支持任意n个数四则运算求某个值
源码下载
posted on 2010-09-05 21:31 Michael Peng 阅读(3645) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?