为了接触更广泛的读者群,我通过精心设计(并且有点乏味)的现实生活中的例子重新阐述了我原来的问题。原始问题显示在下面(远)。
汤姆刚刚被聘用(取决于他在前两个工作日的表现)给Acme Inc.担任初级软件工程师。他的工作是在编程语言Acme ++中实现由高级软件开发人员设计的算法。该公司遵循严格的要求goto
"政策由首席执行官的独家订单。如果汤姆可以在他的试用时间内表现异常,他将获得该公司的全职工作。
在第1天,Tom收到以下要实施的算法。
Step 1. START
Step 2. Input x
Step 3. In case x<0 goto Step 4 otherwise goto Step 5
Step 4. Set x=-x
Step 5. Output x
Step 6. END
汤姆觉得这个任务非常复杂,他认为通过将其表示为流程图,他将从研究程序的抽象结构中受益。在绘制下面的图Flow chart of day one之后,他很快意识到,他被要求计算x的绝对值,并且他可以使用简单的if-then-else语句来实现它。汤姆非常高兴,他在一天结束时完成了他的任务。
在第2天,Tom收到要实施的以下算法。
Step 1. START
Step 2. Input x
Step 3. In case x<0 goto Step 2 otherwise goto Step 4
Step 4. Output x
Step 5. END
汤姆,作为一个新手,感觉再一次以抽象的方式理解算法会更好,所以他绘制了以下流程图Flow chart of day two。
检查流程图显示Tom被要求实现一个等待第一个非负输入的while循环。汤姆非常高兴,并在一天结束时完成任务。
基于他的出色表现,汤姆已被聘用到公司。
然而,在第3天,汤姆被深陷其中,因为他获得了由公司前雇员设计的1996年goto
跳跃的1000线算法,并且没有其他任何人留在那里谁知道算法做了什么,它是如何做到的,以及为什么它首先以这种方式设计。然而,这根本不涉及汤姆,因为他唯一的任务是实现算法而不管它是什么。凭借前两天的专业知识,他绘制了1000个节点上的流量图,其中包含1997个有向边。汤姆,非常绝望,问堆栈流如何处理如此混乱,经验丰富的程序员反复建议他
goto
;和goto
是一个好的还是坏的编程实践(参见GOTO still considered harmful?),他关心的是他的公司有一些编程指南他在任何时候都需要遵循什么。 你能否帮助汤姆弄清楚如何开始实施一些不是&#34;双线死亡的东西&#34;?特别是,很明显应该以什么顺序实现流程图节点描述的任务?是否应该以一个接一个的顺序出现某些嵌套循环?
什么是最小的&#34;原子&#34;无法进一步细分成流程图的流程图?也就是说,汤姆什么时候可以自信地回应堆栈溢出&#34;我已经将我的算法分解成更小的部分&#34;?是真的,一切都基本上是一个while循环和/或一个二进制分支点(第一天和第二天的任务)?
如何实现这样的&#34;原子&#34;自动,或多或少与汤姆在第一天和第二天所做的一样?
汤姆可以与首席执行官争辩说,在某些情况下使用goto
是至关重要的,也就是说,他们要么使用它来实施某种算法,要么根据公司&#39绝对没有其他办法来实施它。 ; s指南(即不使用goto
)?
流程图的哪些部分存在问题,需要重组,为什么?例如,一个三向分支可以用嵌套的双向if-then-else语句替换,也就是说,Tom可以安全地假设他的流程图上的每个节点最多只有两个。但是,应该采取哪些其他重组来处理由goto
语句引起的所有嵌套循环?什么图表属性使重组成为必要?也许,高度?
最初提出的算法(由软件开发团队)的流程图背后的数学(图)理论是什么,以及算法的重构和分解流程图(说汤姆)实际上或多或少自动实现?
假设我有一些使用二元决策和goto
语句的算法。该算法以下面的高级方式在N> = 2(有限)步骤中描述,并且应该顺序执行(一步一步):
算法
Step 1. START
Step 2. Do something. If condition in Step 2 holds goto Step X else goto Step Y.
Step 3. Do something. If condition in Step 3 holds goto Step U else goto Step V.
Step 4. Do something.
Step 5. Do something. If condition in Step 5 holds goto...
Step 6. ...
...
Step N. END
你明白了。例如,Knuth在他的书中以这种独立于编程语言的高级方式描述了这些算法。
现在的问题是如何使用goto
语句将这种高级描述转换为使用while循环和if / else语句的实际实现?是否可以完全消除所有goto
语句,并用while循环替换它们?如果是这样,一般如何做 ?
基于算法的描述,可以构建相应的流程图,从而构建(定向的)流程图。换句话说,问题是&#34;如何在没有goto
语句的情况下基于其流程图实现代码?&#34;。
有两种方法可以回答这个问题。最好,而且非常希望,我正在寻找一种算法方法来实现ALGORITHM WHATEVER。如果ALGORITHM WHATEVER 非常简单,那么直观地清楚应该做什么,但在我看来,一旦经常访问一个步骤(有许多goto语句跳到那里),事情变得相当复杂,或者换句话说,当流程图的一个节点具有大的度数时。然后,我不太清楚应该嵌套while循环的特定顺序。另一方面,很可能一个人根本无法做我想要的一般事情,这样的答案应该得到高级别的ALGORITHM IMPOSSIBLE描述的支持,这清楚地表明无论如何,一个人根本无法做到避免在实际实现中使用goto
跳转。
在我看来,多次询问将实施转换为流程图:Automatic flowchart tool和Algorithm to create flow chart [A little guidance??]。程序code2flow似乎是可视化代码的良好起点。
然而,在这里,我对另一个方向感兴趣。一个简单的搜索显示,DRAKON(另请参阅https://en.wikipedia.org/wiki/DRAKON和http://drakon-editor.sourceforge.net/)可能正在做我正在询问的内容。从这个角度来看,问题是,这样一个自动流程图到代码程序如何在不使用goto
语句的额外假设下工作?
答案 0 :(得分:4)
引用OP:最好,并且非常希望,我正在寻找一种算法方法来实现ALGORITHM WHATEVER
嗯,显而易见的答案是为算法的每个步骤定义目标语言中的标签,为该标签的每个步骤编写代码,并完全按照ALGORITHM WHATEVER描述的方式插入GOTO语句。这将为您提供一个大多数人称之为“意大利面条代码”的工作程序。
OP有一个冗长的介绍,表明他(或者也许汤姆)更愿意以令人信服的方式与ALGORITHM WHATEVER匹配来编写一个无goto版本。好的,所以好消息是Bohm and Jocopini很久以前就表明你可以用序列的三个基本控制流操作实现任何计算, if-then- else 和 while循环,具有单入口,单出口的良好属性。所以我们知道有一个“结构化程序”实现。
不太好的消息是,他们的建设性证明(从gotoful /流程图版本构建此类程序的过程)引入了一组布尔变量和控制循环的其他测试。这些变量用于跳过循环迭代的剩余部分,强制退出循环,并告诉循环调用程序循环退出。这个额外的 代码,对我来说,即使你不反对管理这些变量所需的存储和执行时间,也会使得结果程序的读取更糟糕。 (有关为goto-ful COBOL程序执行此操作的算法,请参阅维基百科链接。)
更好的消息是S. Rao Kosaraju证明,如果你可以突破任意嵌套深度的循环,你就不需要额外的控制变量来构建这样的程序。许多编程语言都提供这样的功能; C提供了一个脆弱的版本,其“破N”;突破N个嵌套循环的声明[当有人在现有代码中插入一个额外的循环并且没有注意到break语句时,使用这个着名的AT&amp; T的东海岸电话系统崩溃]。 Ada和其他语言允许标记循环,并且基本上“离开”以留下具有指定标签名称的循环。对我来说,这是一个非常好的解决方案。 [我更喜欢一种变体,其中一个具有标记的开始 - 结束块,可以“离开”d。 如果您的语言没有这样的带标签的假结构,但您愿意以风格批准(而不是临时方式)使用GOTO语句,则可以通过在之后放置标签来模拟离开语句每个循环并写“goto”而不是离开。
因此,我们知道可以使用干净的任意流程图/ gotoful程序构建结构化程序。
执行此操作的算法通过将原始流程图缩减为结构化子元素来实现,这与Sue Graham 1975年的论文一致, A fast and usually linear algorithm for global flow analysis
该算法的本质是在可能的情况下逐步将原始流程图缩减为Bohm / Jacopini原语(sequence / if-then-else / loop)结构,并减少“不可简化”的结构(想想一个小结)在流程图中)以特殊方式看起来不像这些。在每个步骤中,流程图的一部分简化为该部分的单个摘要节点。最终,此过程将任意复杂性的原始流程图缩减为单个节点。此时,还原过程可以反向运行,每个摘要节点都会扩展回原始节点。
为了OP的目的,扩展摘要节点是以结构化方式编写简化子图的代码的机会。重复生成原始流程图然后导致整个程序以结构化方式写入。
[这就是今天的一切。让我们看看我是否可以生成算法。看这个空间]。
答案 1 :(得分:1)
如果我们对待每一个&#34;做某事&#34; ALGORITHM WHATEVER中的语句作为返回TRUE或FALSE的函数,您可以执行以下操作(在伪代码中):
Let N be an int
curr_state be an int
T[1..N] be an array of ints
F[1..N] be an array of ints
Func[1..N] be an array of functions returning booleans
1. curr_state := 1
2. while curr_state != N+1 do /* end state */
3. if (Func[curr_state] == TRUE) then
4. curr_state := T[curr_state]
5. else
6. curr_state := F[curr_state]
7. fi
8. od
有人可能会问,&#34;但是,如果函数采用参数或需要修改某些共享状态会怎么样?&#34;原则上,函数参数和共享状态都可以存储在全局变量中(在算法范围内)。在实践中,你可能会做一些稍微不同的事情。
答案 2 :(得分:1)
嵌入式软件社区已经在很长一段时间内使用这些工具取得了不同程度的成功。我不再开发那种软件了,但是10年前还有Rational Rose RT(我认为IBM买了它们),MATLAB Simulink,MATLAB Real-time Workshop等等。他们将采用各种类型的图形输入(无论是类图,状态图还是流程图)并生成C ++代码。
您描述示例的方式并不完全确定您是否正在描述状态转换原子仅依赖于观察状态(流程图如何工作)的有限状态机,或者您是否描述了更多的专家系统,其中过渡项目不仅关注观察到的状态,还关注到达该状态之前发生的转变。
如果您正在描述前者,那么您需要做的就是注意您的goto标签本身就是状态。流程图中的每个决策块都成为一个状态转换函数,它作用于某些信息世界,并在完成后设置一个新状态。新状态可能是“结束”或其他。通常在所有状态转换函数之间共享信息世界(即,共享存储器)。软件实现通常是命令设计模式(GoF)的一些变体。
如果您正在描述后者,请查看Rete算法。 Rete算法是一种优化的通用算法,用于实现任何专家系统的规则手册。