Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Checking lots of conditions...ORed and ANDed

by TVSET (Chaplain)
on Jun 08, 2004 at 01:44 UTC ( [id://362180]=perlmeditation: print w/replies, xml ) Need Help??

I have been working on a rather complex system recently, which lead me to talking to a lot of my collegues. Here is a piece that I got from those talks. :)

Problem: generate and display a complex interface/menu, which varies from user to user and depends on a number of options. There are plenty of conditions to check and there are multiple combinations of conditions that can produce the same result. The number of conditions and their combinations can grow and change over time. Heh, even describing this is complex. :)

Example:

if ($not_in_shift) { if ($current || !$past) { print "Register for shift\n"; } } else { if ($current) { print "Confirm in shift\n"; print "Remove from shift\n"; } if ($past) { print "Confirm in shift\n"; } }

Even as it is, it already looks ugly. Adding few more conditions and combinations brings code to a real mess and makes it unmaintainable.

Solution: first the code, then explanation.

#!/usr/bin/perl -w use strict; # These can be filled by some function my %conditions = ( Current => 1, Not_in_shift => 1, Past => 0, ); # Menu item to print if any set of conditions satisfies my %menu = ( 'Add Self' => [ "Current,Not_in_shift", "!Past,Not_in_shift", ], ); # No need to touch this stuff ever for my $item ( keys %menu ) { for my $set ( @{$menu{$item}} ) { my $result = 1; for my $condition ( split(/,/,$set) ) { # Negating condition is a convenient thing to have if (substr($condition,0,1) eq '!') { substr($condition,0,1,''); ($result &&= !$conditions{$condition}) || last; } else { ($result &&= $conditions{$condition}) || last; } } if ($result) { add_menu_item($item); last; } } } # Whatever we want to do with the menu item sub add_menu_item { my $item = shift; print "$item\n"; }

Basically, here we have a %conditions hash that contains the current status. It can be updated by a separate function. %menu contains all menu items that can be displayed. For each item, there is a list of combinations (strings). Each combination lists conditions that must true (or false, with negation) to satisfy. Any combination that satisfies all conditions causes the menu item to appear.

The good side of this solution is that it is obvious under which conditions which menu item will get displayed. It is easy to add more items, edit or delete existing ones, or temporarily hide menu items. Chances of making a mistake are lower and it is also possible to store the whole thing outside the code (like in the database or config file) and provide GUI menu editing functionlity. The possibilities are endless. :)

Update: One picture is a thousand words. Here is a screenshot from the running system.

Replies are listed 'Best First'.
Re: Checking lots of conditions...ORed and ANDed
by dragonchild (Archbishop) on Jun 08, 2004 at 01:53 UTC
    I've put together the design for something similar and am waiting for project go-ahead at work to implement it. The design takes a different tack.
    • There is a set of authorities which define every action a given user can take.
    • Each user has a given set of authorities assigned to them.
    • There is a given set of menu items which are all the items that can be put into a menu.
    • Each menu item knows what authority(s) are necessary to have it displayed.
    • A controller will have every menu item query the user object for its specific authority(s). The menu item determines if it should be displayed.

    So, when you create a new menu item, you have it (and only it) know which authority combination is required for it to be displayable. If you don't want a menu item to be shown, you simply have the controller not put it in the list for querying.

    Simple.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

    I shouldn't have to say this, but any code, unless otherwise stated, is untested

      In my case, menus are very dynamic, depend on many factors and can change several times during the day. Consider managing a schedule for few people for few shifts (24x7 network operations center). Checking who is allowed to do what, has been already done. It is one level above this code. ;)
        Ok. So, have the menu item know what factors affect its ability to display itself. *shrugs* Just because you've handled the problem I describe solving doesn't mean the algorithm wouldn't be helpful in solving what you're looking to do.

        ------
        We are the carpenters and bricklayers of the Information Age.

        Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

        I shouldn't have to say this, but any code, unless otherwise stated, is untested

Re: Checking lots of conditions...ORed and ANDed
by baruch (Beadle) on Jun 08, 2004 at 03:06 UTC

    I hesitate to mention it, but this is an ideal situation for using Boolean algebra to reduce the number of conditions. It sounds terrible, but the rules aren't all that difficult, and it's well worth the effort of learning (assuming you'll be doing much of this sort of complicated work with conditionals).


    רוּךבּ

      Boolean algebra would simplify the conditions. Polymorphisim could obliviate them all together. I'm not entirely certain how it would apply to this specific information (as I suspect I do not fully understand the OP's problem), but this is generally the case.

      Still, you get a ++ from me for bringing up a concept which is significantly undervalued. Besides, Boolean algebra is fun :)

      ----
      send money to your kernel via the boot loader.. This and more wisdom available from Markov Hardburn.

        as I suspect I do not fully understand the OP's problem

        One picture is a thousand words. Here is the screenshot of the actual system. I have opened one of the menus also. Menus differ with each cell. This should help you understand better what I mean. :)

      I'll back Baruch on this. It's compact, quick, and fairly flexible.

      Remember, there are times when code has to be complex. The world is complex and sometimes you just have to deal with it.

      --
      tbone1, YAPS (Yet Another Perl Schlub)
      And remember, if he succeeds, so what.
      - Chick McGee

Re: Checking lots of conditions...ORed and ANDed
by NetWallah (Canon) on Jun 08, 2004 at 17:12 UTC
    You might want to take a look at Deterministic Finite Automata.

    I have not used this in perl, but the concept is simple and logical, and avoids a lot of programming errors - config errors are discovered and corrected easier than hard-coded condition errors.

    Offense, like beauty, is in the eye of the beholder, and a fantasy.
    By guaranteeing freedom of expression, the First Amendment also guarantees offense.

Re: Checking lots of conditions...ORed and ANDed
by stvn (Monsignor) on Jun 08, 2004 at 17:38 UTC

    I have to agree with NetWallah, this sounds like a you need a state-machine. Which is really what you have started doing already, although in a highly specific way. Your approach and code can only benefit from reading some of the theory about it, since it is a rather highly developed concept. There are a number of resources on the web for such things, and surely there is a CPAN module out there which would suit your needs.

    -stvn
Re: Checking lots of conditions...ORed and ANDed
by Solo (Deacon) on Jun 09, 2004 at 16:21 UTC
    If you're conditions are simple enough, turning your structure inside-out will help I think.

    my $rules = { 'not in shift' => { allows => [ 'Register for shift', ], }, 'in shift' => { allows => [ 'Confirm in shift', 'Remove from shift', ], }, # 'current' => { # nothing seems to depend on current # }, 'past' => { disallows => [ 'Remove from shift', 'Register for shift', ], }, }; sub get_menu_items { my ($rules,@conditions) = @_; my %results; for my $cond ( @conditions ) { next unless exists $rules->{$cond}; # irrelevant condition for $item ( @{$rules->{$cond}->{allows}} ) { $results{$item}++; } for $item ( @{$rules->{$cond}->{disallows}} ) { delete $results{$item}; } } return keys %results; } my @conditions = ( ['in shift', 'past' ], ['in shift', 'current' ], ['not in shift', 'past' ], ['not in shift', 'current' ], ); for (@conditions) { my @result = get_menu_items($rules,@$_); print "Conditions: " . join(',',@$_) . "\n" . "Menu items: " . join(',',@result) . "\n\n"; }

    And the results:

    Conditions: in shift,past Menu items: Confirm in shift Conditions: in shift,current Menu items: Remove from shift,Confirm in shift Conditions: not in shift,past Menu items: Conditions: not in shift,current Menu items: Register for shift

    This may just be a cheap application of others' suggestions to use boolean algebra and a state machine (or maybe not). I'm just a fan of code examples.

    I DO think this is a very natural way for people to express rules in external config files.

    --Solo
    --
    You said you wanted to be around when I made a mistake; well, this could be it, sweetheart.
      It's a neat piece of code. ;) I still prefer my though due to two reasons:
      1. It is more obvious which conditions need to be satisfied for the menu to get displayed.
      2. Each menu caption is written only once. It is easier to maintain interface consistancy.

      Thank you for the idea anyway.

      My above code has a nasty habit of allowing things it shouldn't depending on the order of the conditions. Assuming that 'disallow' rules trump 'allow' rules, here is a quick fix.

      sub get_menu_items { my ($rules,@conditions) = @_; my %results; for my $cond ( @conditions ) { next unless exists $rules->{$cond}; for $item ( @{$rules->{$cond}->{allows}} ) { $results{$item}++; } } for my $cond ( @conditions ) { next unless exists $rules->{$cond}; for $item ( @{$rules->{$cond}->{disallows}} ) { delete $results{$item}; } } return keys %results; }
      --Solo
      --
      You said you wanted to be around when I made a mistake; well, this could be it, sweetheart.
Re: Checking lots of conditions...ORed and ANDed
by andyf (Pilgrim) on Jun 10, 2004 at 08:56 UTC
    If you follow Baruch and Netwalla towards a state machine with bool xformation matrix and your form code for the 'edge' functions, you might find these worth looking at to help build it.

    The best advantage to going down this road is _not_ to simplify your code. It is removing the state transitions from the code altogether, no hardwired logic code, just a conf file of transitions.
    Math::MatrixBool
    StateMachine::Gestinanna

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://362180]
Approved by BrowserUk
Front-paged by dbwiz
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (8)
As of 2024-04-18 14:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found