我正在用C#为项目创建一个布尔代数简化程序。为了简化布尔代数表达式,我采用以下方法:
1)简化每个变量的NOT,并在适用的情况下应用De Morgan定律
2)简化表达式中的括号
3)扩展表达式中可以扩展的方括号
4)简化表达式中的每个术语,例如对于表达式A + B•A,B•A将是一项。项被拆分,因此每个项仅包含一个门-AND,OR,XOR。 Not应用于这些变量,并在与数组中每个变量的索引相对应的列表中表示。例如。 Nots [0]包含表达式中第一个变量的Nots数。在我的程序中,此时没有变量通过NOT门连接。
5)尽可能在工厂摆设
6)如果无法分解表达式,则将其简化。如果已将其分解,则重复执行第2步,直到在执行这些步骤时表达式不更改为止。
我无法创建适用于所有/大多数情况的分解子程序。我创建了一个子程序进行分解,并将其放在下面。我已经尝试过使它最多只能扩展两个括号,并且括号中没有括号,这使得我的子例程更易于创建。但是,事实证明,创建这样的算法对我来说很难。
如果有人可以提供一些伪代码,或对如何创建这种算法的解释,指出我的代码中的错误,甚至提供一些我可以分析和理解的代码,我将不胜感激。代码如下所示:(警告:由于我的经验不足,这是可怕的编程。)
private bool Factorise(ref List<string> Expression, ref List<int> NOTsNew)
{
string PreviousExpression = convertexpressionlisttostring(Expression);
// loop and get each indiviual variable - no duplicates
// loop through expression for every variable and see if it occurs more than once
List<List<string>> itemsthatappearwithinexpression = new List<List<string>>();
List<string> charactersthatappearwithinexpression = new List<string>();
List<string> Notsofcharactersthathappearwithinexpression = new List<string>();
List<string> numberoftimescharacterappears = new List<string>();
List<string> positionofitemswithinexpression = new List<string>();
itemsthatappearwithinexpression.Add(charactersthatappearwithinexpression);
itemsthatappearwithinexpression.Add(Notsofcharactersthathappearwithinexpression);
itemsthatappearwithinexpression.Add(positionofitemswithinexpression);
itemsthatappearwithinexpression.Add(numberoftimescharacterappears);
for (int i = 0; i < Expression.Count; i++)
{
if (Expression[i] != "•" && Expression[i] != "+" && Expression[i] != "⊕")
{
if (itemsthatappearwithinexpression[0].Count == 0)
{
itemsthatappearwithinexpression[0].Add(Expression[i]);
itemsthatappearwithinexpression[1].Add(NOTsNew[i].ToString());
itemsthatappearwithinexpression[2].Add(i.ToString());
}
bool matched = false;
for (int y = 0; y < itemsthatappearwithinexpression[0].Count; y++)
{
if (itemsthatappearwithinexpression[0][y] == Expression[i] && itemsthatappearwithinexpression[1][y] == NOTsNew[i].ToString())
{
matched = true;
break;
}
}
if (!matched)
{
itemsthatappearwithinexpression[0].Add(Expression[i]);
itemsthatappearwithinexpression[1].Add(NOTsNew[i].ToString());
itemsthatappearwithinexpression[2].Add(i.ToString());
}
}
}
for (int x = 0; x < itemsthatappearwithinexpression[0].Count; x++)
{
int occurances = 1;
for (int c = 0; c < Expression.Count; c++)
{
int position = int.Parse(itemsthatappearwithinexpression[2][x]);
if (NOTsNew[c] == NOTsNew[position] && c != position && itemsthatappearwithinexpression[0][x] == Expression[c])
{
occurances++;
}
}
itemsthatappearwithinexpression[3].Add(occurances.ToString());
}
for (int i = 0; i < itemsthatappearwithinexpression[0].Count; i++)
{
if (i < itemsthatappearwithinexpression[0].Count - 1)
{
if (itemsthatappearwithinexpression[3][i] == itemsthatappearwithinexpression[3][i + 1] && int.Parse(itemsthatappearwithinexpression[2][i]) == (int.Parse(itemsthatappearwithinexpression[2][i + 1]) - 2))
{
itemsthatappearwithinexpression[0][i] = itemsthatappearwithinexpression[0][i].ToString() + itemsthatappearwithinexpression[0][i + 1].ToString(); // chars, nots, position, occurances
itemsthatappearwithinexpression[1][i] = itemsthatappearwithinexpression[1][i].ToString() + itemsthatappearwithinexpression[1][i + 1].ToString(); // Nots[0]
itemsthatappearwithinexpression[0].RemoveAt(i + 1);
itemsthatappearwithinexpression[1].RemoveAt(i + 1);
itemsthatappearwithinexpression[2].RemoveAt(i + 1);
itemsthatappearwithinexpression[3].RemoveAt(i + 1);
}
}
}
List<int> positionsoffirstcharinmatches = new List<int>();
string factorisedexpression = "";
bool donextthing = false;
List<int> NOTsinfactorisation = new List<int>();
for (int d = 0; d < itemsthatappearwithinexpression[0].Count; d++)
{
int counter = 0;
bool singularexpansion = false;
if (itemsthatappearwithinexpression[0][d].Length == 1)
{
singularexpansion = true;
}
if (int.Parse(itemsthatappearwithinexpression[3][d]) > 1)
{
for (int i = 0; i < Expression.Count; i++)
{
bool Continue = false;
if (singularexpansion && Expression[i] == itemsthatappearwithinexpression[0][d] && NOTsNew[i] == NOTsNew[int.Parse(itemsthatappearwithinexpression[2][d])])
{
Continue = true;
}
if (i+2 <= Expression.Count-1 && !singularexpansion && Expression[i] == itemsthatappearwithinexpression[0][d][0].ToString() && Expression[i+2] == itemsthatappearwithinexpression[0][d][1].ToString() && NOTsNew[i] == int.Parse(itemsthatappearwithinexpression[1][d][0].ToString()) && NOTsNew[i+2] == int.Parse(itemsthatappearwithinexpression[1][d][1].ToString()))
{
Continue = true;
}
donextthing = false;
if (Continue)
{
if (i != 0)
{
if (Expression[i - 1] == "•")
{
positionsoffirstcharinmatches.Add(i - 2);
if (counter == 0)
{
if (singularexpansion)
{
factorisedexpression += itemsthatappearwithinexpression[0][d] + "•(" + Expression[i - 2] + Expression[i - 3];
NOTsinfactorisation.Add(int.Parse(itemsthatappearwithinexpression[1][d]));
NOTsinfactorisation.Add(0);
NOTsinfactorisation.Add(0);
NOTsinfactorisation.Add(NOTsNew[i - 2]);
NOTsinfactorisation.Add(0);
counter++;
}
else
{
positionsoffirstcharinmatches.Add(i);
factorisedexpression += itemsthatappearwithinexpression[0][d][0] + "•" + itemsthatappearwithinexpression[0][d][1] + "•(" + Expression[i - 2] + Expression[i - 3];
//string NOTsOfAdjacentVariables = itemsthatappearwithinexpression[1][d];
NOTsinfactorisation.Add(int.Parse(itemsthatappearwithinexpression[1][d][0].ToString()));
NOTsinfactorisation.Add(0);
NOTsinfactorisation.Add(int.Parse(itemsthatappearwithinexpression[1][d][1].ToString()));
NOTsinfactorisation.Add(0);
NOTsinfactorisation.Add(0);
NOTsinfactorisation.Add(NOTsNew[i - 2]);
NOTsinfactorisation.Add(0);
counter++;
}
}
else
{
if (i >= Expression.Count - 3)
{
factorisedexpression += Expression[i - 2] + ")";
NOTsinfactorisation.Add(NOTsNew[i - 2]);
NOTsinfactorisation.Add(0);
}
else
{
factorisedexpression += Expression[i + 3] + Expression[i + 2];
NOTsinfactorisation.Add(0);
NOTsinfactorisation.Add(NOTsNew[i + 2]);
}
}
}
else
{
donextthing = true;
}
}
else
{
donextthing = true;
}
if (donextthing)
{
positionsoffirstcharinmatches.Add(i);
if (counter == 0)
{
if (singularexpansion)
{
positionsoffirstcharinmatches.Add(i + 2);
factorisedexpression += itemsthatappearwithinexpression[0][d] + "•(" + Expression[i + 2] + Expression[i + 3];
NOTsinfactorisation.Add(int.Parse(itemsthatappearwithinexpression[1][d]));
NOTsinfactorisation.Add(0);
NOTsinfactorisation.Add(0);
NOTsinfactorisation.Add(NOTsNew[i + 2]);
NOTsinfactorisation.Add(0);
counter++;
}
else
{
bool useone = false;
if (Expression[i]+Expression[i+2] == itemsthatappearwithinexpression[0][d] || Expression[i + 2] + Expression[i] == itemsthatappearwithinexpression[0][d])
{
useone = true;
}
positionsoffirstcharinmatches.Add(i+2);
if (useone)
{
factorisedexpression += itemsthatappearwithinexpression[0][d][0] + "•" + itemsthatappearwithinexpression[0][d][1] + "•(" + "1" + Expression[i + 3];
}
else
{
factorisedexpression += itemsthatappearwithinexpression[0][d][0] + "•" + itemsthatappearwithinexpression[0][d][1] + "•(" + Expression[i + 2] + Expression[i + 3];
}
//string NOTsOfAdjacentVariables = itemsthatappearwithinexpression[1][d];
NOTsinfactorisation.Add(int.Parse(itemsthatappearwithinexpression[1][d][0].ToString()));
NOTsinfactorisation.Add(0);
NOTsinfactorisation.Add(int.Parse(itemsthatappearwithinexpression[1][d][1].ToString()));
NOTsinfactorisation.Add(0);
NOTsinfactorisation.Add(0);
if (useone)
{
NOTsinfactorisation.Add(0);
}
else
{
NOTsinfactorisation.Add(NOTsNew[i + 2]);
}
NOTsinfactorisation.Add(0);
counter++;
}
}
else
{
if (i == Expression.Count - 3)
{
if (Expression[i]+Expression[i+2] == itemsthatappearwithinexpression[0][d] || Expression[i + 2] + Expression[i] == itemsthatappearwithinexpression[0][d])
{
factorisedexpression += "1" + ")";
NOTsinfactorisation.Add(0);
}
else
{
factorisedexpression += Expression[i + 2] + ")";
NOTsinfactorisation.Add(NOTsNew[i + 2]);
}
NOTsinfactorisation.Add(0);
}
else
{
factorisedexpression += Expression[i + 3] + Expression[i + 2];
NOTsinfactorisation.Add(0);
NOTsinfactorisation.Add(NOTsNew[i + 2]);
}
}
}
}
}
}
else
{
}
}
// character • () --> A•B + A•C Xor A•D = A•(B+C XOR D) - find every instance of the object - get the operator before the object and place the o
//int n = 5; //Expression
positionsoffirstcharinmatches = intbubblesorthightolow(positionsoffirstcharinmatches);
List<int> PositionstoremovefromExpression = new List<int>();
for (int i = 0; i < positionsoffirstcharinmatches.Count; i++)
{
if (positionsoffirstcharinmatches[i] < Expression.Count - 3)
{
PositionstoremovefromExpression.Add(positionsoffirstcharinmatches[i] + 3);
PositionstoremovefromExpression.Add(positionsoffirstcharinmatches[i] + 2);
PositionstoremovefromExpression.Add(positionsoffirstcharinmatches[i] + 1);
PositionstoremovefromExpression.Add(positionsoffirstcharinmatches[i]);
}
else
{
PositionstoremovefromExpression.Add(positionsoffirstcharinmatches[i] + 2);
PositionstoremovefromExpression.Add(positionsoffirstcharinmatches[i] + 1);
PositionstoremovefromExpression.Add(positionsoffirstcharinmatches[i]);
}
}
PositionstoremovefromExpression = intbubblesorthightolow(PositionstoremovefromExpression);
PositionstoremovefromExpression = PositionstoremovefromExpression.Distinct().ToList();
for (int i = 0; i < PositionstoremovefromExpression.Count; i++)
{
NOTsNew.RemoveAt(PositionstoremovefromExpression[i]);
Expression.RemoveAt(PositionstoremovefromExpression[i]); // A • B + C • A
}
for (int i = 0; i < factorisedexpression.Length; i++)
{
try
{
Expression[PositionstoremovefromExpression[PositionstoremovefromExpression.Count - 1] + i] = factorisedexpression[i].ToString();
NOTsNew[PositionstoremovefromExpression[PositionstoremovefromExpression.Count - 1] + i] = NOTsinfactorisation[i];
}
catch (Exception)
{
Expression.Add(factorisedexpression[i].ToString());
NOTsNew.Add(NOTsinfactorisation[i]);
}
}
if (PreviousExpression == convertexpressionlisttostring(Expression))
{
return false;
}
else
{
return true;
}
}
List表达式是一个字符串列表,其中包含我的表达式中的每个字符。例如,对于A + B,列表将为[“ A”,“ +”,“ B”]。 NOTsNew列表是上述列表,其中包含每个变量的NOT。只要我花时间去理解它,就可以在其他人的项目中使用代码,可以在需要的地方加以修改并提及,因此我不会作弊。 附言上面的某些代码可以放在子例程中,但是我目前正在尝试获取一些有效的代码,然后再将其简化为单个子例程。
答案 0 :(得分:15)
您说:
警告:由于我缺乏经验,这太可怕了。
这是正确的。然后您说:
上面的某些代码可以放在子例程中,但是我目前正在尝试获取一些有效的代码,然后再将其简化为单个子例程。
这就是为什么您的代码糟糕透了的原因。将代码分成 first 子例程。然后为这些子例程编写 test case ,直到您 100%置信度确信该子例程正确无误。然后,您将拥有可用于制作更复杂例程的工具。
但这只是代码布局。 您的基本问题是,您正在编写一个可在词法分析器的输出上运行的分析器,但是却忘记了编写解析器。
这是事情发生的顺序:
"A+B•A"
说。什么是代币?他们是:
abstract class Token { ... }
sealed class IdentifierToken : Token { ... }
sealed class NotToken : Token { ... }
sealed class OrToken : Token { ... }
sealed class AndToken : Token { ... }
sealed class LeftParenToken : Token { ... }
sealed class RightParenToken : Token { ... }
sealed class TrueToken : Token { ... }
sealed class FalseToken : Token { ... }
因此,任务一是编写此方法:
public static List<Token> Lexer(string s) { ... }
编写方法后,为其编写大量测试用例。您需要确保您的词法分析器完全可靠。
树中的节点是什么?
abstract class ParseNode { ... }
sealed class OrNode : ParseNode
{
public ParseNode Left { get; }
public ParseNode Right { get; }
...
// Or maybe IEnumerable<ParseNode> Children { get; }
// is easier; both techniques have their strengths.
}
sealed class AndNode : ParseNode { ... }
sealed class NotNode : ParseNode { ... }
sealed class IdentifierNode : ParseNode { ... }
sealed class TrueNode : ParseNode { ... }
sealed class FalseNode : ParseNode { ... }
请注意,没有括号。 括号以树形结构表示。
例如,如果我们有"(A+~B)*C"
,则词法分析器说LPAREN, IDENTIFIER(A), OR, NOT, IDENTIFIER(B), RPAREN, AND, IDENTIFIER(C)
。然后解析器从词法分析器中获取列表并产生
And
/ \
Or Id(C)
/ \
Id(A) Not
|
Id(B)
因此,您的下一个任务是编写此方法:
public static ParseNode Parser(List<Token> tokens) { ... }
再次,编写大量测试用例。解析器必须是 perfect 。
对于初学者而言,解析器最难的部分是使操作符优先级正确。您需要确保A+B*C
解析为A+(B*C)
,而不是(A+B)*C
。 这将有助于为您要解析的语言编写一个正式的,无上下文关联的语法。例如,我想不起的是,我对此毫无疑问地进行了解析:
EXPR : OREX
OREX : ANDEX ORTAIL
ORTAIL : NIL
ORTAIL : + ANDEX ORTAIL
ANDEX : NOTEX ANDTAIL
ANDTAIL : NIL
ANDTAIL : * NOTEX ANDTAIL
NOTEX : CONST
NOTEX : ( EXPR )
NOTEX : ~ NOTEX
NOTEX : IDENT
IDENT : <any single letter>
CONST : 1
CONST : 0
但是不要相信我的话;编写自己的语法,然后编写递归下降语法分析器对其进行解析。
一些例子:
Not -> True
可以替换为False
,反之亦然Not -> Not -> anything
,可以替换为anything
Or
左侧或右侧带有True
的地方可以替换为True
Or
的False
可以替换为右侧的任何内容。And
两侧为False
的是False
,依此类推。 您必须将每个优化表示为对解析树的操作。用自己的方法编写每个优化。
锻炼:解析树上的分解优化是什么? 这可能是一个非常棘手的难题,因此请在实现完整算法之前尝试提供一个简化版本。
高级练习:解析树优化器是 double dispatch 的一个示例,因为有两个因素驱动该方法的动作: (1)节点的类型是什么,(2)优化器的作用是什么。 C#本机不支持双重调度。 您可以使用“访客模式”来优雅地实现此模式而无需重复大量代码吗?
因此,编写大量这种形式的方法:
public static ParseNode FooOptimization(ParseNode p) { ... }
public static ParseNode BarOptimization(ParseNode p) { ... }
并全部运行,直到树完全优化为止。
再次,使用自己的方法编写每个优化,并为每个方法编写测试用例,直到您确定每个优化都是正确的。
现在养成良好的习惯。 广泛的测试可以节省时间。当我还是学生时,我整夜看到同学们在解析器中寻找错误。如果他们早编写了测试用例,那么他们就不会整夜试图找出错误。
让我们看一个例子:
public static ParseNode NotFalseOptimization(ParseNode p)
{
if (p is NotNode n)
{
// The child might itself have a Not(False) somewhere in it.
ParseNode child = NotFalseOptimization(n.Child);
if (child is FalseNode)
return new TrueNode();
else
return new NotNode(child);
}
else if (p is OrNode o)
return new OrNode(NotFalseOptimization(o.Left), NotFalseOptimization(o.Right);
else if (p is AndNode a)
return new AndNode(NotFalseOptimization(a.Left), NotFalseOptimization(a.Right);
else
return p;
}
研究该实现。确保您了解其工作原理。
锻炼:您能否优化我的糟糕实现,以便在优化没有发现任何更改的情况下不分配内存?
ToString
上实现ParseNode
。现在您的程序是:
static string DoItAll(string s)
{
var tokens = Lex(s);
var tree = Parse(tokens);
var optimized = Optimize(tree);
return optimized.ToString();
}
您已完成。 忙碌,您需要做很多工作,但是每一步都是可行的。
一些其他建议:
使解析树不可变。优化器不会重写树。优化器仅保留旧树,并生成新树作为其输出。
在优化布尔代数时,您必须小心,每次优化都不会撤消任何先前优化的进度。您可以进入一个循环,在该循环中,一个优化扩展一个术语,然后下一个优化使它再次退出,然后再次扩展,依此类推,直到永远。