在家和太太拿扑克算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 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();
}
}
}
}
{
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();
}
}
}
}