是一种状态图/有限状态机,适合于对问卷进行建模

时间:2018-08-16 15:56:56

标签: graph-theory state-machine statechart

enter image description here

我想对上述问卷进行建模,我相信这是一个有向无环图。

我一直在看的两个库是:

我遇到的几个问题是:

  1. 调查表依赖于以前的状态,例如答案 先前的问题用于过渡到 另一个状态(问题)。我认为“外部状态”正确吗? 可以解决这个问题吗?

  2. 如果我在第六季度就想过渡到上一个问题, 然后根据先前的答案,可能是Q1,Q4或Q5。一世 我认为我可以使用一个堆栈在调查表进行时将每个状态推入,然后弹出以返回到先前的状态。

这听起来是否可行,或者有更好的方法来模拟此问题?

5 个答案:

答案 0 :(得分:1)

可以通过扩展状态机对这个问题的解决方案进行建模。您不一定需要层次结构。您显示的状态和转换可以使用常规状态机来解决,但是“内存”部分可以通过扩展状态机来解决。

简而言之,您的图形保持不变,并且在每次转换时都会对扩展状态进行更新。例如,您的扩展状态可以为{A1, A2. ..., A6, H},其中Ax代表对Qx的回答,而H是计算机经历的状态的历史。从第一季度过渡到第二季度时,您还将A1更新为答案,并将H更新为[A1]。您对所有Q执行此操作。到达Q6时,您将拥有编写警戒所需的所有信息,该警戒根据先前状态的历史和答案来决定从何处过渡。

因此,总结一下您的问题的答案:是的,可以以有利的方式使用外部状态,在这种情况下,如您所知,拥有状态历史记录的堆栈将解决您的问题。当然,您也可以使用状态图库,因为状态图可以概括扩展状态机。

作为附加组件,我还将我自己的扩展状态机库包括到您已经提到的出色的状态机库中:https://github.com/brucou/state-transducer

您将find there a demo与您刚刚描述的问题非常相似。它也是关于多步调查表的问题,其复杂之处在于它包含错误路径。

答案 1 :(得分:0)

在这里回答问题的后半部分,关于弄清楚如何到达Q4和Q5(并暂时忽略Q1至Q6)

将其表示为状态机的典型第一种幼稚方式(也是我的初始方式)使每个问题进入其自己的状态,每个状态在状态机中仅表示一次。使用状态图,您可以将问题4和5提取到compound state中,使得当Q4AND5处于活动状态时,恰好是Q4或Q5之一处于活动状态:

<scxml>
  <state id="Q1"/>
  <state id="Q2"/>
  <state id="Q3"/>
  <state id="Q4AND5">
    <state id="Q4"/>
    <state id="Q5"/>
  </state>
  <state id="Q6"/>
</scxml>

然后,由于受保护的过渡,从Q3到Q4AND5的过渡将导致Q4或Q5变为活动状态。

<scxml>
  <state id="Q1">
    <transition event="answer" target="Q2"/>
  </state>
  <state id="Q2">
    <transition event="answer" target="Q3"/>
  </state>
  <state id="Q3">
    <transition event="answer" target="Q4AND5"/>
  </state>
  <state id="Q4AND5">
    <transition cond="if q1 == 'FOO' and q3 == 'BAR'" target="Q4"/>
    <transition cond="if q1 == 'BAR' and q3 == 'FOO'" target="Q5"/>
    <state id="Q4"/>
    <state id="Q5"/>
    <transition event="answer" target="Q6"/>
  </state>
  <state id="Q6"/>
</scxml>

从Q6返回返回将转到Q4AND5,这将导致机器输入Q4或Q5:

  <state id="Q6">
    <transition event="back" target="Q4AND5"/>
  </state>

现在,在对问题进行修改以包括从Q1到Q6的过渡之后,很明显,将每个问题建模为不同的状态并不能解决问题。这也不是很正确。如果考虑一下,有两个Q6状态,一个是从Q1到达Q6之后,另一个是从Q4AND5到达Q6之后。如果我们改为将这两个Q6分为两个不同的状态,那么很容易容纳这个新过渡:

<scxml>
  <state id="Q1">
    <transition event="answer" target="Q6B" cond="q1 == 'BAZ'"/>
    ...
  </state>
  ...
  <state id="Q6A">
    <transition event="back" target="Q4AND5"/>
  </state>
  <state id="Q6B">
    <transition event="back" target="Q1"/>
  </state>
</scxml>

现在的问题是Q6由两个状态(Q6A和Q6B)表示。解决方案是从状态名称中分离 ,并在每个状态中声明要显示的问题,通常是通过操作或更改某些变量来显示。在下面,我定义了一个 data 元素,状态图将更新为要显示的正确问题的名称。

<scxml>
  <datamodel>
    <data id="question"> <!-- The name of the question to show -->
  </datamodel>
  <state id="Q1">
    <assign location="question" expr="'Q1'"/>
    <transition event="answer" target="Q6B" cond="q1 == 'BAZ'"/>
    <transition event="answer" target="Q2"/>
  </state>
  <state id="Q2">
    <assign location="question" expr="'Q2'"/>
    <transition event="answer" target="Q3"/>
    <transition event="back" target="Q1"/>
  </state>
  <state id="Q3">
    <assign location="question" expr="'Q3'"/>
    <transition event="answer" target="Q4AND5"/>
    <transition event="back" target="Q2"/>
  </state>
  <state id="Q4AND5">
    <transition event="answer" cond="if q1 == 'FOO' and q3 == 'BAR'" target="Q4"/>
    <transition event="answer" cond="if q1 == 'BAR' and q3 == 'FOO'" target="Q5"/>
    <transition event="back" target="Q3"/>
    <state id="Q4">
      <assign location="question" expr="'Q4'"/>
    </state>
    <state id="Q5">
      <assign location="question" expr="'Q5'"/>
    </state>
    <transition event="answer" target="Q6A"/>
  </state>
  <state id="Q6A">
    <assign location="question" expr="'Q6'"/>
    <transition event="back" target="Q4AND5"/>
  </state>
  <state id="Q6B">
    <assign location="question" expr="'Q6'"/>
    <transition event="back" target="Q1"/>
  </state>
</scxml>

这种去耦使更改状态图,移动事物变得更容易,而无需更改状态图的每个用户。相反:根据状态图/状态机外部的状态名称,状态名称本身将成为状态机的API,这意味着它不容易更改。

答案 2 :(得分:0)

我将使用FSM并记住Map或js对象中的状态来实现这一点。然后,您可以防止触发检查存储在内存中的状态的转换。

这是我要采取的实用解决方案,您可以使用FSM库,但我的建议也使用对象来保留答案。

是否有FSM的实现,其中转换可以采用外部状态?如果存在这种情况,您可以在机器内部编写逻辑,但是您必须手动记住答案(以使其成为通用解决方案)

答案 3 :(得分:0)

现实世界中的调查表需要将答案保存在某处,而外部状态(例如一堆键值对)才是有用的。任何试图将给定答案存储在图中(每个可能的值都是一个单独的节点)的尝试都将由于称为状态爆炸的现象而立即失败。

但是,获取先前给定的答案,然后将其与条件表达式一起使用以实现转换是一种逃生舱口。作为基于自动机编程的倡导者,我认为在大多数情况下IF的使用应仅限于输入数据,并且不应以这种方式检查外部状态,除非期望的行为无法用合理的图表示。

为了证明它确实可以工作,我在Rosmaro中实现了它,并将其推送到此存储库-https://github.com/lukaszmakuch/so-questionnaire中。您可以在此处的编辑器-https://i.stack.imgur.com/uDkh5.png中看到图形。

这是一个完整的工作示例,它支持:

  • 阅读当前问题
  • 回答当前问题
  • 阅读给出的答案
  • 回到上一个问题

这是它的工作方式:

git clone https://github.com/lukaszmakuch/so-questionnaire.git
cd so-questionnaire/
npm i
npm start
$ question()
'Q1'
$ answer('baz')
undefined
$ question();
'Q6'
$ answer('anything')
undefined
$ answers();
{ Q1: 'baz',
  Q2: null,
  Q3: null,
  Q4: null,
  Q5: null,
  Q6: 'anything' }
$ back()
undefined
$ question()
'Q1'
$ answer('foo')
undefined
$ question()
'Q2'
$ answer('test')
undefined
$ answers()
{ Q1: 'foo', Q2: 'test', Q3: null, Q4: null, Q5: null, Q6: null }
$ question()
'Q3'
$ answer('bar')
undefined
$ question()
'Q4'
$ answer('fuzz')
undefined
$ question()
'Q6'
$ back()
undefined
$ question()
'Q4'
$ 

答案 4 :(得分:0)

与对S4和S5使用复合状态不同的方法是对S6使用复合状态:

<scxml>
  <state id="Q1">
    <transition event="answer" target="Q6_from_1" cond="q1 == baz" />
    <transition event="answer" target="Q2"/>
  </state>
  <state id="Q2">
    <transition event="back" target="Q1"/>
    <transition event="answer" target="Q3"/>
  </state>
  <state id="Q3">
    <transition event="back" target="Q2"/>
    <transition event="answer" cond="if q1 == 'FOO' and q3 == 'BAR'" target="Q4"/>
    <transition event="answer" cond="if q1 == 'BAR' and q3 == 'FOO'" target="Q5"/>
  </state>
  <state id="Q4"/>
    <transition event="back" target="Q3"/>
    <transition event="answer" target="Q6_from_4"/>
  </state>
  <state id="Q5"/>
    <transition event="back" target="Q3"/>
    <transition event="answer" target="Q6_from_5"/>
  </state>
  <state id="Q6">
    <state id="Q6_from_1"/>
      <transition event="back" target="Q1"/>
    </state>
    <state id="Q6_from_4"/>
      <transition event="back" target="Q4"/>
    </state>
    <state id="Q6_from_5"/>
      <transition event="back" target="Q5"/>
    </state>
  </state>
</scxml>

唯一的特殊状态是Q6,因为它会记住通过过渡到针对特定子状态的状态来记住“来源”。

这可以与Q4和Q5的复合状态的概念结合在一起。