24点计算

在家和太太拿扑克算24点,屡战屡败。为了争口气,于是偷偷写了个小程序来作弊,哈哈。

首先对问题进行分析,24点,就是给定4个操作数,用四则运算符将它们连成合法的算式,可以加括号,以求得24.

碰到的第一个问题就是如何表示算式,常见的算式都是中缀表达式,即运算符在两个操作数之间。中缀表达式的好处是符合人的习惯,容易理解,缺陷则是需要借助额外的括号才能表示清楚

如下面的算式

10-6/2-1

按照运算符优先次序是先计算6/2,再用10减去其结果3,再用刚才的结果7-16

那我们要表示这种优先次序呢?

(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应是一个合法的操作数序列。

相关代码如下


ExpandedBlockStart.gif 代码
     public   static   class  Permutation
    {
        
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指令,操作符替换为操作指令,再逐条执行指令。

ExpandedBlockStart.gif 代码
    public   abstract   class  Instruction
    {
        
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();
        }
    }


 

 

最后还需要解决一个问题,如何把得到的指令集以便于阅读的方式输出

ExpandedBlockStart.gif 代码
     class  PostOrderToInOrderConvertor
    {
        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个数四则运算求某个值

 

源码下载

 

转载于:https://www.cnblogs.com/MichaelPeng/archive/2010/09/05/24Points.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值