Present a valid state transition graph in Perl

Our application has something like checking for state changes.

Validation is currently being done using the ugly if statement

I want to replace it with a transition matrix:

my %allowed_status_changes = ( 1 => (2,5), 2 => (1,2,3,4,5), 3 => (4,2), 4 => (3,2), 5 => (), ); my $is_allowed_transition = $submitted_status ~~ $allowed_status_changes {$original_status}; if ($prerequestsites && !$is_allowed_transition) { return; } 

some transitions can only be allowed under the additional condition, so I will need something like

 2 => ( (target => 1) (target => 2, condition => $some_condition) (target => (3,4), condition => $other_condition), (target => 5) ), 

(I think it's too long)

What structure would you use in this situation if you should focus on readability and maintainability?

How do you analyze it to see if the transition is allowed?

+6
data-structures perl graph
source share
3 answers

If the conditions are very common (for example, almost every allowed transition has them), then your last structure is fine, except for your hashref syntax error using "()" instead of "{}".

If conditions are rare, I would suggest switching to C # 1, complemented by additional constructs like your # 2.

Please note that the readability of the checked code IMHO is very clear, although voluminous and not very idiomatic.

OTOH, the supporting matrix is ​​high - you have a concise but readable syntax from # 1 where no conditions are required and a clear, albeit longer syntax for conditions that are flexible enough for many conditions for many parameters like your # 2.

 my %allowed_status_changes = ( 1 => [2,5], 2 => [1,5,{targets=>[2], conditions=>[$some_condition]} ,{targets=>[3,4], conditions=>[$other_condition, $more_cond]}] 3 => [4,2], 4 => [3,2], 5 => [], ); sub is_allowed_transition { my ($submitted_status, $original_status ) = @_; foreach my $alowed_status (@$allowed_status_changes{$original_status}) { return 1 if !ref $alowed_status && $alowed_status == $submitted_status; if (ref $alowed_status) { foreach my $target (@$alowed_status{targets}) { foreach my $condition (@$alowed_status{conditions}) { return 1 if check_condition($submitted_status , $original_status, $condition); } } } } return 0; } if ($prerequestsites && !$is_allowed_transition($submitted_status, $original_status )) { return; } 
+2
source share

Although I agree with the DVK for the most part, I have to say that as soon as you begin to understand arrays of hash arrays, you will reach a level of code complexity that is difficult to maintain without a lot of goals and errors.

At this point, I will probably get to the object and class, for a little syntactic sugar.

 my $transitions = TransitionGraph->new(); $transition->add( 1, { targets => [ 2, 5 ] }); $transition->add( 2, { targets => [ 1, 5 ] }); $transition->add( 2, { targets => [ 2 ], conditions => [ $some_condition ] }); $transition->add( 2, { targets => [ 3, 4 ], conditions => [ $other_condition, $more_cond ]}); $transition->add( 3, { targets => [4,2] } ); $transition->add( 4, { targets => [3,2] } ); $transition->add( 5, { targets => [] } ); if( $transition->allowed( 1 , 3 )){ } 

The execution of the class is user-dependent, but I would use Moose.

The main advantages of this is that you encapsulate how the state graph works, so you can just use it and worry about how the graph works separately from where it was used.

pi in the above API, add () creates a new entry if it does not exist, and updates this entry if it exists. This turned out to be easier than the β€œupdate” or β€œget this item and then change it” methods.

Inside, he can do it or something like this:

 sub add { my ( $self , $input_state, $rules ) = @_; my $state; if ( $self->has_state( $input_state ) ) { $state = $self->get_state( $input_state ); } else { $state = TransitionGraphState->new( source_id => $input_state ); $self->add_state( $input_state, $state ); } my $targets = delete $rules{targets}; for my $target ( @$targets ) { $state->add_target( $target, $rules ); } return $self; } sub allowed { my ( $self, $from, $to ) = @_; if ( not $self->has_state( $from ) ){ croak "NO source state $from in transition graph"; } my $state = $self->get_state( $from ); return $state->allowed_to( $to ); } 

This also has a cool feature, because of which it is not required that one specific set of codes work on sub-nodes, you can create separate instances with their own behavior if you want one source state to be processed differently.

  $transition->add_state( 2, $some_other_class_wich_works_like_transitiongraphstate ); 

Hope this is helpful =).

+1
source share

There is nothing wrong with the second form. You cannot get around the fact that you must encode the state machine somewhere. In fact, I think that the whole machine encoded in one place is much easier to understand that there are too many layers of abstraction, where you need to look in different places to understand the flow of the machine.

+1
source share

All Articles