C++ 解开Knuth';s结:如何重新构造意大利面代码?
这个问题的灵感来源于它询问如何从代码中通过算法消除C++ 解开Knuth';s结:如何重新构造意大利面代码?,c++,algorithm,loops,refactoring,software-design,C++,Algorithm,Loops,Refactoring,Software Design,这个问题的灵感来源于它询问如何从代码中通过算法消除goto语句。科学论文中描述了一般问题的解决方法 我根据Knuth的《计算机编程的艺术》(the art of computer programming)中的算法X的高级草图实现了一些代码,描述了使用受限前缀生成词典排列(参见本文第16页) 这是上述算法的对应部分 这可能是一个非常聪明和高效的算法,但代码的结构似乎很难遵循。我最终使用了良好的老式goto风格的实现: //Algorithm X; 1: initialize(); 2: enter
goto
语句。科学论文中描述了一般问题的解决方法
我根据Knuth的《计算机编程的艺术》(the art of computer programming)中的算法X的高级草图实现了一些代码,描述了使用受限前缀生成词典排列(参见本文第16页)
这是上述算法的对应部分
这可能是一个非常聪明和高效的算法,但代码的结构似乎很难遵循。我最终使用了良好的老式goto
风格的实现:
//Algorithm X;
1:
initialize();
2:
enter_level(k);
3:
set(a[k],q);
if(test() == ok) {
if (k == n) {
visit();
goto 6;
}
goto 4;
}
goto 5;
4:
increase(k);
goto 2;
5:
increasev2(a[k]);
if (q != 0) {
goto 3;
}
6:
decrease(k);
if (k==0) {
goto 7;
}
set(p,u_k);
goto 5;
7:
return;
问题是:如何重构此代码以消除所有的goto
调用
一个(伪造的)答案是建议“查阅引用的科学论文,逐行跟踪”——事实上,这当然是一种可能性。但这个问题是关于有经验的程序员一旦看一眼就会立即看到什么
我感兴趣的是如何一步一步地重构,而不仅仅是代码
注:
goto
跳转,实际实现算法X非常简单。实现黑盒函数initialize()
等只需要一些额外的指令,但这些指令与代码的结构无关。函数调用期间发生的事情并不重要,因为现在的重点是程序的流程相关:您可以使用许多变量来模拟GoTo的流程,以使用
if's
和while's
initialize();
enterLevel = true;
executeWhile = true;
do
{
if (enterLevel)
{
enter_level(k);
}
enterLevel = false;
goto4 = false;
goto5 = false;
goto6 = false;
set(a[k],q);
if(test() == ok)
{
if (k == n)
{
visit();
goto6 = true;
}
else
{
goto4 = true;
}
}
else
{
goto5 = true;
}
if (goto4)
{
increase(k);
enterLevel = true;
}
else
{
do
{
if(goto5)
{
increasev2(a[k]);
goto6 = goto5 = !(q != 0); // if (q != 0) { goto6 = goto5 = false; } else { goto6 = goto5 = true; }
}
if(goto6)
{
decrease(k);
executeWhile = !(k==0); // if (k == 0) { executeWhile = false; } else { executeWhile = true; }
set(p,u_k);
goto5 = true;
}
} while (goto5 && executeWhile);
}
} while (executeWhile);
不过我不能说这个版本是否比带有goto的版本好
首先,我将所有标签完全分开
然后我发现这里有两个循环:
1 -
* label 4 -> goto 2
* label 5 -> goto 3.
两者都进入代码顶部,但其中一个执行enter_level(k)
,而另一个不执行。
这就是为什么enterLevel变量
2 -
* label 6 -> goto 5. This goes up a little in the code, and then executes again.
在这个循环中,有两种情况下它会失效:
* label 5 -> goto 3. The same as before, but now inside a nested loop
* label 6 -> goto 7. The way out of the outer loop.
其他变量和if只是为了维持控制流
是的,我可以使用一些中断(代码可以变得更短),
但问题是关于goto's,我个人不喜欢使用它们。不需要太多努力(也不需要太多风险),您可以快速减少goto和标签的数量
1) 删除未在任何位置引用的标签(这将是标签1:)
2) 查找除了goto之外无法输入的代码块,这些代码块在很少的地方被调用。这些通常可以简单地分解出来。4:可以通过将代码移动到调用它的地方来处理,并且可以安全地完成,因为它唯一的出口是goto。这也允许我们删除它上面的goto 5,因为该代码只会下降到5:。7:可以通过修改if语句来处理。在这一点上,我们有
initialize();
2:
enter_level(k);
3:
set(a[k],q);
if(test() == ok) {
if (k == n) {
visit();
goto 6;
}
increase(k);
goto 2;
}
5:
increasev2(a[k]);
if (q != 0) {
goto 3;
}
6:
decrease(k);
if (k!=0) {
set(p,u_k);
goto 5;
}
return;
我倾向于到此为止。但如果您继续,就变成了识别循环并用循环构造替换GOTO的问题。然而,由于代码的结构方式,进行这些更改的风险似乎要大得多。此外,你可能会以休息和继续结束,这是一种无论如何都要做的事情。我的结论是(如果没有一些非常严格的测试,我不会保证它的正确性):
我做了3:一个循环,6:一个内循环。我通过复制5:代码来代替goto,并用中断替换GoTo3,从而摆脱了GoTo5。这使得制作更干净的循环更容易一些。goto 6通过使用else进行修复。goto 3的功能正在继续
在此之后(如果您还有剩余能量),您可以尝试将循环从while(true)with continues更改为whiles with实际条件
首先开发测试,然后进行一两次更改和测试,这是一个好主意。再做一次更改,然后再次测试。如果你不这样做,很容易在早期就犯一个结构错误,然后使后续步骤无效,并迫使你从头再来。 < P> C++中,该算法可以写成:
void initialize() {}
void enter_level(int k) {}
void set(int x,int y) {}
bool test() { return true; }
void visit() {}
void increase(int k) {}
void increasev2(int k) {}
void decrease(int k) {}
void algorithm_x()
{
int k{0};
int a[] ={1,2,3,4,5};
int q{0};
bool ok{true};
int n{0};
int p{0};
int u_k{0};
//Algorithm X;
lbl1:
initialize();
lbl2:
enter_level(k);
lbl3:
set(a[k],q);
if (test() == ok) {
if (k == n) {
visit();
goto lbl6;
}
goto lbl4;
}
goto lbl5;
lbl4:
increase(k);
goto lbl2;
lbl5:
increasev2(a[k]);
if (q != 0) {
goto lbl3;
}
lbl6:
decrease(k);
if (k==0) {
goto lbl7;
}
set(p,u_k);
goto lbl5;
lbl7:
return;
}
int main()
{
algorithm_x();
return 0;
}
假设我们不使用break语句,那么程序可能是:
void initialize() {}
void enter_level(int k) {}
void set(int x,int y) {}
bool test() { return true; }
void visit() {}
void increase(int k) {}
void increasev2(int k) {}
void decrease(int k) {}
void algorithm_x()
{
int k{0};
int a[] ={1,2,3,4,5};
int q{0};
bool ok{true};
int n{0};
int p{0};
int u_k{0};
bool skiptail{false};
//Algorithm X;
initialize();
enter_level(k);
while (true) {
skiptail = false;
set(a[k],q);
if (test() == ok) {
if (k == n) {
visit();
decrease(k);
if (k==0) {
return;
}
set(p,u_k);
while (true) {
increasev2(a[k]);
if (q != 0) {
//goto lbl3;
skiptail = true;
}
if (!skiptail) decrease(k);
if (!skiptail) if (k==0) {
return;
}
if (!skiptail) set(p,u_k);
}
}
if (!skiptail) increase(k);
if (!skiptail) enter_level(k);
//goto lbl3;
skiptail = true;
}
if (!skiptail) while (true) {
increasev2(a[k]);
if (q != 0) {
//goto lbl3;
skiptail = true;
}
if (!skiptail) decrease(k);
if (!skiptail) if (k==0) {
return;
}
if (!skiptail) set(p,u_k);
}
if (!skiptail) increase(k);
if (!skiptail) enter_level(k);
//goto lbl3;
skiptail = true;
if (!skiptail) while (true) {
increasev2(a[k]);
if (q != 0) {
//goto lbl3;
skiptail = true;
}
if (!skiptail) decrease(k);
if (!skiptail) if (k==0) {
return;
}
if (!skiptail) set(p,u_k);
}
}
}
int main()
{
algorithm_x();
return 0;
}
更改使用了以下算法:
扔掉未使用的标签。删除lbl1
如果标签以goto结尾,则在使用该块的任何位置替换该块。
删除lbl4
,lbl6
,lbl7
如果标签返回自身,则在while中放置块(true)。
拆下底部的lbl5
(lbl5
现在是独立的,可以在使用的地方更换)
如果一个块是独立的,则在使用它的地方进行更换。
删除lbl5
如果一个标签跟在另一个标签后面,则在块的末尾放置一个转到下一个标签,以便按照规则2进行替换。
删除lbl2
(可以转到lbl3
)
现在我们只剩下代码中最后一个标签的goto
。用skiptail=true
替换goto lbl3
,将剩余的块放在while(true)
块中,并设置剩余语句以检查skiptail=false
。
移除lbl3
并替换为skiptail=false
我从未使用过goto
,但这似乎是一个有趣的挑战,所以我尝试了自己的重构
首先,检查代码,看看每个标签上有多少语句goto
ing;记住这一点很重要,以避免犯错误。在您的示例中,没有任何结果是1,因此我们可以忽略它
有时候,我发现了它
void initialize() {}
void enter_level(int k) {}
void set(int x,int y) {}
bool test() { return true; }
void visit() {}
void increase(int k) {}
void increasev2(int k) {}
void decrease(int k) {}
void algorithm_x()
{
int k{0};
int a[] ={1,2,3,4,5};
int q{0};
bool ok{true};
int n{0};
int p{0};
int u_k{0};
bool skiptail{false};
//Algorithm X;
initialize();
enter_level(k);
while (true) {
skiptail = false;
set(a[k],q);
if (test() == ok) {
if (k == n) {
visit();
decrease(k);
if (k==0) {
return;
}
set(p,u_k);
while (true) {
increasev2(a[k]);
if (q != 0) {
//goto lbl3;
skiptail = true;
}
if (!skiptail) decrease(k);
if (!skiptail) if (k==0) {
return;
}
if (!skiptail) set(p,u_k);
}
}
if (!skiptail) increase(k);
if (!skiptail) enter_level(k);
//goto lbl3;
skiptail = true;
}
if (!skiptail) while (true) {
increasev2(a[k]);
if (q != 0) {
//goto lbl3;
skiptail = true;
}
if (!skiptail) decrease(k);
if (!skiptail) if (k==0) {
return;
}
if (!skiptail) set(p,u_k);
}
if (!skiptail) increase(k);
if (!skiptail) enter_level(k);
//goto lbl3;
skiptail = true;
if (!skiptail) while (true) {
increasev2(a[k]);
if (q != 0) {
//goto lbl3;
skiptail = true;
}
if (!skiptail) decrease(k);
if (!skiptail) if (k==0) {
return;
}
if (!skiptail) set(p,u_k);
}
}
}
int main()
{
algorithm_x();
return 0;
}
1: initialize();
reached4=false;
do5 = false;
while(true){
if (reached4){
4: increase(k);
}
2: enter_level(k);
while(true){
if(do5){
5:
increasev2(a[k]);
if (q != 0) {
do5 = false;//goto 3
}
}
if(!do5){
3:
set(a[k],q);
if(test() == ok) {
if (k == n) {
visit();//goto 6;
}else{
reached4 = true;
break;//goto 4
}
}
}
6:
decrease(k);
if (k==0) {
7: return;
}
set(p,u_k);
do5 = true;
}
}
//Algorithm X;
1:
initialize();
2:
while (true) {
enter_level(k);
3:
while (true) {
set(a[k],q);
if (test() == ok) {
if (k != n) exit_while@3;
visit();
decrease(k); // replicate logic at 6 to avoid jumping into middle of 5 loop
if (k==0) return;
set(p,u_k);
}
5:
while (true) {
increasev2(a[k]);
if (q != 0) continue_while@3;
6:
decrease(k);
if (k==0) return;
set(p,u_k);
} // while(true)@5
} // while(true)@3
4:
increase(k);
} // while(true)@2