ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

State Machine with Transitions and Guards

Ruby

Goal -- WPM

Ready
Exercise Algorithm Area
1require 'set'
2
3# Represents a state in the state machine.
4class State
5attr_reader :name
6
7def initialize(name)
8@name = name
9end
10
11def to_s
12@name.to_s
13end
14end
15
16# Represents a transition between two states.
17class Transition
18attr_reader :from_state, :to_state, :event, :guard
19
20def initialize(from_state, to_state, event, guard = nil)
21@from_state = from_state
22@to_state = to_state
23@event = event
24@guard = guard # A lambda or proc that returns true/false
25end
26
27# Checks if this transition is valid given the current context.
28def can_transition?(context)
29@guard.nil? || @guard.call(context)
30end
31end
32
33# The state machine itself.
34class StateMachine
35attr_reader :current_state
36attr_reader :states
37attr_reader :transitions
38
39def initialize(initial_state_name, states_data)
40@states = states_data.each_with_object({}) { |(name, _), obj| obj[name] = State.new(name) }
41@initial_state = @states[initial_state_name]
42@current_state = @initial_state
43@transitions = []
44@context = {}
45end
46
47# Adds a transition to the state machine.
48def add_transition(from_state_name, to_state_name, event, guard = nil)
49from = @states[from_state_name]
50to = @states[to_state_name]
51raise ArgumentError, "Invalid state name" unless from && to
52
53@transitions << Transition.new(from, to, event, guard)
54end
55
56# Sets the context for guard evaluation.
57def set_context(context_hash)
58@context.merge!(context_hash)
59end
60
61# Triggers an event, attempting to transition the state machine.
62# Args:
63# event: The event that occurred.
64# Returns:
65# True if a transition occurred, false otherwise.
66def trigger(event)
67possible_transitions = @transitions.select do |t|
68t.from_state == @current_state && t.event == event
69end
70
71valid_transition = possible_transitions.find do |t|
72t.can_transition?(@context)
73end
74
75if valid_transition
76old_state = @current_state
77@current_state = valid_transition.to_state
78puts "Transitioned from #{old_state} to #{@current_state} on event '#{event}'."
79true
80else
81puts "Could not transition from #{@current_state} on event '#{event}'."
82false
83end
84end
85
86# Checks if a transition is possible for a given event.
87def can_transition?(event)
88@transitions.any? do |t|
89t.from_state == @current_state && t.event == event && t.can_transition?(@context)
90end
91end
92
93def to_s
94"#{@current_state}"
95end
96end
97
98# --- Example Usage ---
99
100# Define states for a simple order processing system
101states = {
102pending: nil,
103processing: nil,
104shipped: nil,
105delivered: nil,
106cancelled: nil
107}
108
109sm = StateMachine.new(:pending, states)
110
111# Define transitions
112sm.add_transition(:pending, :processing, :pay)
113sm.add_transition(:pending, :cancelled, :cancel)
114sm.add_transition(:processing, :shipped, :ship)
115sm.add_transition(:processing, :cancelled, :cancel)
116sm.add_transition(:shipped, :delivered, :receive)
117sm.add_transition(:shipped, :cancelled, :return)
118sm.add_transition(:delivered, :cancelled, :return)
119
120# Add a guard: only allow shipping if payment is confirmed
121sm.add_transition(:processing, :shipped, :ship, ->(ctx) { ctx[:payment_confirmed] })
122
123puts "Initial state: #{sm}"
124
125# Example 1: Successful transitions
126sm.trigger(:pay)
127puts "Current state: #{sm}"
128sm.trigger(:ship) # This will fail without payment confirmation
129
130# Set context for guards
131sm.set_context({ payment_confirmed: true })
132sm.trigger(:ship) # Now this should succeed
133puts "Current state: #{sm}"
134sm.trigger(:receive)
135puts "Current state: #{sm}"
136
137# Example 2: Invalid transitions and cancellation
138sm.set_context({ payment_confirmed: false }) # Reset context
139sm.trigger(:cancel)
140puts "Current state: #{sm}"
141
142# Try to trigger an event from an invalid state
143sm.trigger(:pay)
144puts "Current state: #{sm}"
Algorithm description viewbox

State Machine with Transitions and Guards

Algorithm description:

This Ruby code implements a state machine pattern. It defines states, transitions between them triggered by events, and guard conditions that must be met for a transition to occur. The `StateMachine` class manages the current state and allows adding transitions with optional guards. This is fundamental for modeling complex workflows, protocols, or user interfaces where behavior depends on the current status.

Algorithm explanation:

The `StateMachine` class holds the `current_state`, a collection of `states`, and a list of `transitions`. Each `Transition` object links a `from_state`, `to_state`, `event`, and an optional `guard` (a callable that takes a `context` hash). When `trigger(event)` is called, it finds all transitions matching the current state and the event. It then selects the first transition whose guard evaluates to true using the provided `@context`. If a valid transition is found, the `current_state` is updated. If no valid transition exists, the state remains unchanged. Time complexity for `trigger` is O(T), where T is the number of transitions, as it iterates to find a match. Space complexity is O(S + Tr), where S is the number of states and Tr is the number of transitions.

Pseudocode:

Class State:
  Initialize with name.

Class Transition:
  Initialize with from_state, to_state, event, guard.
  Function can_transition?(context):
    If guard exists:
      Return guard.call(context).
    Else:
      Return true.

Class StateMachine:
  Initialize with initial_state_name and states_data.
  Attributes: current_state, states (hash map), transitions (array), context (hash map).
  Set current_state to initial_state.

  Function add_transition(from_name, to_name, event, guard = nil):
    Get from_state and to_state objects from states map.
    Create a new Transition object and add it to transitions array.

  Function set_context(context_hash):
    Merge context_hash into @context.

  Function trigger(event):
    Find all transitions where transition.from_state == current_state and transition.event == event.
    Find the first valid_transition among these where transition.can_transition?(context) is true.
    If valid_transition exists:
      Update current_state to valid_transition.to_state.
      Return true.
    Else:
      Return false.