if (a || (b and (c or d)) and (not e or (f and g and h))) then ???September 25, 2007 7:24 PMSubscribe

Comp Sci: What's the best Design Pattern or structure for dealing with a complex relationship between many booleans?

I'm working on an app that has a zillion configuration options. I need to deal with situations in which the user choses Option A and (Option B or Option C) but not (Option D and (Option E or F)). That sort of thing.

Using tons of if/then/else's is clearly wrong. It's totally confusing. The State Pattern seems like the right direction, but unless I misunderstand it, it's too simple. It assumes that an app is in State A, State B or State C. What about apps that are in States A and C? In other words, there are 50 options and any combination of them can be on/off.

Consider, say, three bools. Since a bool can have only two states, I can treat it as a bit. A binary number is just a vector of bits too.

So:

enum mybittype { option1 = 1, option2 = 2, option3 = 4 };

enum mybittyype mybits = option1 | option2 ; // bitwise |, not ||

if( mybits & option1) // bitwise &

Alternately, make a class type that wraps an enum (or bitmap or bitvector) that encapsulates frequent and complex operations as named methods.posted by orthogonality at 7:40 PM on September 25, 2007

I think the "states" are supposed to represent your options in their entirity - perhaps thinking of it that way may help.posted by puddpunk at 7:41 PM on September 25, 2007

Yes, but in the state pattern examples I've seen, you can only have one "live" state at once. My app would need to be in state a and b at the same time.posted by grumblebee at 7:46 PM on September 25, 2007

Yes, and that one state is the entirety of all the boolean states. Again, if you have N booleans, you have N2 possible states. Each can be given a number (they can be enumerated) , and that number is the number of your your "live" state. In the case of boolean, the mapping of total state to number (in ten most obvious way, that is by treating the bits as a binary number) happens to be a number that can be masked off to reveal the value of each boolean.posted by orthogonality at 7:52 PM on September 25, 2007

I think you mean 2N possible states there, orthogonality.posted by breath at 8:31 PM on September 25, 2007 [1 favorite]

Deciding things about boolean expressions (e.g. whether there exists a valid configuration meeting a set of constraints) is in general NP-complete. This means that it is very hard and expensive. BDDs allow you to solve a single NP-complete problem once, "offline" as it were, and use the resulting structure to give you quick "online" answers about boolean formulas.posted by goingonit at 8:49 PM on September 25, 2007

I'm not hearing real solutions either, except for the enum / mapping ones maybe. You may want to wikipedia vectormap and look at what enum and bitmaps are. But even still - I'm having trouble dealing the the many options (not just a, b, and c but instead many many of them yes?). Because, like how are you supposed to map behaviors to all these weird config possibilities? Why would you have to? Can you explain what kind of output behaviors you have to define, or at least how many of them exist? By behaviors I mean whatever decision is made based on one of these long complex booleans being true or not.

If there's say 10 possible resulting output behaviors, then you could go right ahead and use if then statements (and boolean logic).

example (PERL) : if ($a) {return $some_behavior;}
- ie if a is true do something based on this fact
- just use the boolean logic tools in your programming language?

Tell more about what happens when a certain long boolean is true?posted by albatross5000 at 8:54 PM on September 25, 2007

My suggestion is banking on you having a set of possibilities that you're looking for specifically.posted by albatross5000 at 9:01 PM on September 25, 2007

I think you might get more helpful answers if you were clearer about the setup.

Is the issue here more about deciding what behavior to run given the particular states of the configuration options, or is it about figuring out what legal transitions there are between configuration states?

Do you usually have totally separate code to run for each possible arrangement, or do different configurations share some code and have some unique code? Are there many cases where having A set means B, C, and D are irrelevant; or is it usually the case that every option's value is important every time?posted by inkyz at 9:04 PM on September 25, 2007

In addition to the previously mentioned BDD's, you could try to write some abstraction of a relation mapping. For example, you could have a "requires", "mutually exclusive", and so on as different relation types. You could then define some sort of evaluator, and set off events or something similar when a condition is met. These can control the state of meta-configuration settings, which adds another layer of insulation to keep the relationship between actions and particular requirements more flexible.

Disclaimer: I'm not familiar with ActionScript, so these kinds of suggestions from me may not be as applicable as they would be to languages I typically deal with (Java, Python, Scala). I understand that it is highly event-oriented, so some kind of dependency system is probably not out of reach.posted by mezamashii at 9:16 PM on September 25, 2007

If at all possible, you should back up and rethink this. If the combinations of options are so complicated that you're having trouble conceptualizing them in code, they're almost certainly more complicated than any person will be able to use effectively.posted by scottreynen at 9:17 PM on September 25, 2007 [1 favorite]

This sounds like a situation where you might want to rethink the idea of having a zillion configuration options in the first place. Will anyone really understand this? Most of the apps people use on a day-to-day basis have very few configuration options, and most people leave them as the defaults, because they tend to confuse people.

It's the software designers' job to design the app so that it woirks well without extensive configuration, and to provide configuration options only when there really is a legitimate variation in how users with truly different use-cases might want to use the app.posted by lsemel at 9:34 PM on September 25, 2007

YOUR DOIN IT WRONG.

Look, you should never have a situation where you've got tons of if-statements for tons of different user settings. If you really did that, you would actually have 2n states. And you could never test it.

What you need to do is split your program up into lots of smaller objects (or functions), each with it's own config, and only a few options (like, no more then three or so, so they can all be tested) And the key is that the configuration of one object cannot effect any of the other objects.

Think about it: If your program's options are all totally independent, you only need to test each choice for your app. If your options affect each other, you're basically fucked.

You do come across complex boolean propositions in coding, but it shouldn't be happening with UI stuff.posted by delmoi at 10:02 PM on September 25, 2007 [1 favorite]

Oh, and if you're doing actionscript 2.0, you should just shoot yourself in the head now. I hear AS 3 is more "java like" but AS 2 is impossible to debug. IMPOSSIBLE! I tell you.posted by delmoi at 10:04 PM on September 25, 2007

Seconding the people who say you likely need to rethink your design.

You can sometimes use binary algebra to simplify the conditions of your if statements, or at least make them more readable. Default/fallback options also really help. You can set them before you enter your if statement, and only change them if the condition is true, removing the need for an else statement. In your case maybe you could set defaults for all your options in one place, then do your logic right after.

delmoi is right that you should split this task into smaller pieces. One way to do it is to identify options that affect eachother, and deal with them in separate (descriptively named!) functions. Then instead of having a giant chunk of if/else statements, you call these functions. This would probably go a long way toward making your code more readable and make it easier to refactor later.posted by benign at 11:03 PM on September 25, 2007

Think about what scottreynen and delmoi (and, on preview, benign) said, and do that if at all possible.

If you're too far down the coding road to break things up nicely into modules at this point, and you just need to push something out by Thursday, take advantage of any separation you can find between groups of options, and process each group in its own slab of code.

In most cases, you should be able to find a reasonably clear if/then/else way to express what the options do. The key to this is finding those options which, if turned on, make as many other options as possible irrelevant; then test for those in the outermost if/then/else.

If you're half way through coding up an if/then/else tree and you're looking at it and just going "wtf, I don't understand how to test this properly, and there's way too much repeated code on the leaves of this tree" then it's time to do the bit-vector dance.

To get this right, you really need to understand the fundamental difference between a state and an option. The thing about a state is that you can't be in more than one state at once; if you think you can, you simply haven't identified all your states yet.

Let's look at a simple example of that. If I have four options, which for the sake of brevity I'll call option A, option B, option C and option D, then I can represent each possible combination of those options as a single state:

state = 0;
if (option_A) state |= A;
if (option_B) state |= B;
if (option_C) state |= C;
if (option_D) state |= D;

So, if options A, B and D are on, then I'm in state 13. If A, B and C were on, I'd be in state 14. Each possible combination of options generates a single state.

I can now set up a Switch statement:

switch (state) {
case 0|0|0|0:
// code that handles the case where all options are off
break;
case 0|0|0|D:
case 0|0|C|0:
case 0|0|C|D:
// code that handles the cases where options A and B are both off, but C or D or both are on
break;
case 0|B|0|0:
case A|0|0|0:
case A|B|0|0:
case 0|B|0|D:
// code than handles the cases where options C and D are both off, but A or B or both are on; or when A is off, B is on, C is off and D is on:
break;
case A|B|C|0:
case A|B|0|D:
case A|0|C|D:
case 0|B|C|D:
// code that handles the cases where exactly three of A, B, C or D are on
break;
default:
// code that handles combinations of options not explicitly handled above - should throw an error if you believe you've covered everything
}

This approach will probably start adding clarity once you get over about five options, and will probably work OK for up to maybe seven; but if you're trying to build a single massive switch statement to handle fifty, it's not going to work well at all - the number of possible states just gets way out of control.

The only way to deal with that is, as I said at the start, to break the options up into groups that don't interact with each other, and state-ize each group independently.posted by flabdablet at 11:30 PM on September 25, 2007 [3 favorites]

Effectively, the state approach allows you to replace a bunch of Boolean expressions with something more akin to a truth table. If you've got a relatively restricted number of final behaviours and a large number of ways to get those, then a truth table will usually be clearer. But there really is no general coding substitute for careful initial design.posted by flabdablet at 11:46 PM on September 25, 2007

The only way to deal with that is, as I said at the start, to break the options up into groups that don't interact with each other, and state-ize each group independently.

Good. I was thinking along the same lines, and I mocked something up last night to test.

Thank you for all your answers. Let me clarify my situation a bit:

The app simulates a complex contraption -- let's say a TV. Not too complex to start off: a bunch of channels, each one showing a different show at a different time.

Then my boss asks me to add a DVD player. So now there's a DVD/TV switch that changes what's showing on channel 3. Then he asks me to add a TV, so now you can be watching live tv, a dvd or Tivo.

This doesn't quite capture what I'm up against, because these are all mutually-exclusive states. So lets say someone invents a device called a FLIV. With a fliv, you can enhance the colors on regular TV and Tivo -- but not on DVDs. So now the TV can be in these states:

TV -- FLIV
TV -- no FLIV
DVD
Tivo -- FLIV
Tivo -- no FLIV

And, of course, changing channels works if you're in TV mode and Tivo mode, but not in DVD mode.

Now add several more devices: VHS, Xbox, Wii all hooked up to the TV and various events that can -- and can/can not -- happen when various combos of these states are on at once.

The situation is very fluid. At any time, my boss can say, "add WebTV" ... "add Playstation."

The good news is that the general public will never be manipulating the choices. Generally, if my boss says "Put it on channel ten and start the Tivo," I'll be the one doing it.

The TV metaphor is apt, because I get into the same pickle with my TV at home. It has so many devices hooked up to it, that I get confused as to how they all affect each other.

One solution would be to get several TVs and hook only a few devices up to each one. I can't do that at home, because I can't afford to do it. I can't do it at work, because that's not what I was asked to build. Like the Gestapo, I'm just following orders.posted by grumblebee at 6:54 AM on September 26, 2007

state = 0;
if (option_A) state |= A;
if (option_B) state |= B;
if (option_C) state |= C;
if (option_D) state |= D;

(I'm assuming that != means "not equal" as it does in most of the languages I know).

If you start state as zero and then say that state is not equal to a bunch of values, doesn't state pop out at the other end still as zero? Where are you actually turning options on?posted by grumblebee at 6:58 AM on September 26, 2007

Sorry -- too early in the morning for me. You're doing bit operations. I confused your pipe symbol with an exclamation mark. I get it now.posted by grumblebee at 7:00 AM on September 26, 2007

-- article on bitwise operators in Actionscriptposted by grumblebee at 7:07 AM on September 26, 2007

If you're simulating a complex contraption, the Right Thing is to simulate the pieces, design some more pieces that connect the original pieces together, and design a clean interface convention so that any input or output works the same way as any other input or output.

This might involve breaking up the pieces into smaller pieces before you start. So, to simulate a TV, you'd probably break it up into a tuner and a monitor.

The monitor is easy to simulate - it's just a window with an input interface that controls what gets seen in the window. But wait - what about brightness, contrast, saturation, hue, size and geometry controls? Well, the Right Thing would be to make each of these an independent signal-processing module, each with one control, and connect them in a chain. I can't see ActionScript having the grunt to do heavy-duty signal processing, so you'd probably just leave them out.

The tuner is easy too - it's just a selector with a control that sets a channel number, and it connects the input defined by the current channel number to its output. You'd design your input/output connections in such a way that only an output that ultimately ended up feeding the monitor actually executes code, but conceptually, all the sources are alive all the time.

Connect the monitor's input to the tuner's output, and channel feeds to the tuner's inputs, and you have a TV.

So, now you add a DVD. First thing you need to think about: is the DVD going to look like an extra channel available via the tuner, or is it a separate thing that can feed the monitor instead of the tuner? If it's a separate thing, you'd need a separate selector for it; but it sounds like you've already decided to make it occupy channel 3. But in either case you don't need any extra controls at all in the monitor to deal with this - the monitor can remain blissfully ignorant about the source of what it's showing, if you get the interface right. Adding the DVD does not contribute to the monitor's complexity. If you just make the DVD = channel 3, it doesn't even contribute to the tuner's complexity.

On the other hand, you've said that changing channels "works if you're in TV mode and Tivo mode but not if you're in DVD mode". Which says to me that you're not doing the kind of analysis that you really need to do. You need that sheet of butcher's paper with blocks representing your data sources and consumers, and lines representing your signal flow paths, before you even think about writing any code.

If operating the channel selector has no effect if you're watching a DVD, that says to me that the DVD does not occupy a channel: there must be some other selector-type device between the tuner, which owns the channel control, and the monitor. So you need to simulate that video source selector explicitly, and connect your tuner's output to one of its inputs.

You now have a channel control and a video source control that operate completely independently. If your video source control is set to DVD, it doesn't matter what your channel control gets set to, because it will be changing settings inside a simulated device that's not even in use. You don't end up with a massive mode-selecting if/then/else in one spot, because all the signal processing decisions are distributed.

So now you add a simulated Tivo. Presumably, this has its own tuner - quite separate from the one in the TV - that selects what's going onto its hard disk, and you'd probably want to set up multiple threads to deal with that; or you might want to dispense with the record functionality completely, and only simulate playback.
I get into the same pickle with my TV at home. It has so many devices hooked up to it, that I get confused as to how they all affect each other.

It is absolutely essential that you have a clear conceptual understanding of simulated sources, selectors and signal paths, and a clear understanding of which controls affect which subdevices, before you write any code. As an exercise, map your home TV setup. Sit down with a sheet of butcher's paper and draw a big sprawling diagram that has a box for each signal-transforming unit (source, selector, or consumer), an arrow for each connection from an output to an input, and some clear representation of the controls (or, preferably, control, singular) available on each box.

Then do the same thing for the simulator your boss wants.

If your mysterious FLIV can't fix DVD colour, you need a simulated signal path that goes straight from the DVD output to the monitor input, bypassing the FLIV. You shouldn't be building a monolith that tests explicitly whether the signal source is DVD before deciding whether to apply FLIVness; you should be building a simulated FLIV that just Does Stuff in between its input and its output, and arranging your signal switching so the DVD never gets "wired" through it.

Comp Sci 101 says that before you even worry about design patterns, you need to break your design into simple pieces that you can understand, each one with a well-defined and preferably regular way of passing data from inputs to outputs; then working out how to connect the appropriate outputs to the appropriate inputs; then assigning controls to the pieces as necessary to get the job done. The controls within a piece should have absolutely no effect on the state of any other piece.

One reasonable approach to implementing this stuff is to think about the monitor as "pulling" data, instead of as a passive receiver for data that gets pushed to it. If the monitor is conceptually in charge, it can call a function (or invoke a method, if you insist on thinking OO) in the simulated device connected to its input, to provide it with the next chunk of data; if that device is a switch, it could call a similar function inside one of N simulated input devices and pass the resulting data chunk back to its caller, and so on back through the chain until you get to something that can actually generate the data required.

The controls attached to the various devices will just set booleans, numbers or selector variables local to those devices. The only time these settings have any actual effect is when the data-pump function inside the device actually gets called upon to move some data (or, if no actual data transformation is needed, a reference to some data) from input to output. You should be able to build what you want with no global controls at all; closest you might get to global controls is if you come up with some fancy schmancy scheme for re-wiring inputs and outputs at runtime instead of building the wiring into the design. The only true global you need for a realtime sim is the realtime clock.

It's too late at night for me, and I'm just raving now. Hope at least some of this is useful.posted by flabdablet at 9:34 AM on September 26, 2007

Thank you for all your input, flabdablet. I agree with everything you say. My problem is implementing it in the less-than-ideal workflow that I'm forced to work in (and that, I suspect, many others are forced to work in). For the sake of this discussion, let's assume I have no control over what my boss wants. I simply have to deliver.

So...

History:

=== DAY ONE ===

boss asks me to simulate a simple TV. I make a channel object which has a number and a program (we'll simplify and say that each channel shows the same program all day).

There's a tuner object that, given a channel number, knows which show to play (it can switch to the appropriate channel class).

Boss swears that the project is done and will never need to do anything else.

=== DAY TWO ===

Boss changes his mind. He now wants DVD capability. IF the channel is turned to 3 and the DVD is playing, he wants to see the DVD program, not the what's normally on channel 3.

If you turn to another channel, though, he wants to see what's normally on that channel (DVD or no DVD).

If the DVD player is turned off, he wants to see what's normall on channel 3.

He wants the possibility of using channel 4 as the DVD channel -- or channel 3.

But he swears he will NEVER ask for any more changes!

== DAY THREE ===

Boss breaks his vow. He now wants a Tivo that can record anything playing on the regular channels (including 3 and 4), but not the DVD.

If the DVD is playing, you can STILL record 3 and 4, but it will record what's on the TV-version of 3 and 4 -- not what's playing on the DVD. (You'll still see the DVD on the monitor, though).

Tivo will also play back over 3 or 4. It will override whatever the TV wants to play on those channels. It will not override the DVD.

== DAY FOUR ===

Boss wants a FLIV. If turned on, it enhances the color of the TV and TiVo, but not the DVD.

Okay, now don't worry about figuring out the specifics of that project. It's just an example. But it's the SORT of thing I have to do.

Assume that there's NO WAY I can go to my boss and say, "look, before we start this project, can we make a pretty definitive list of what it's supposed to do?"

The basic problem is this: given a device called X that has features A, B, C and D (and may have new features added to it at any time), these features need to be able to affect each other is arbitrary ways. For instance, if A and B are on, D should be locked in the off position.posted by grumblebee at 10:09 AM on September 26, 2007

Once your boss crosses some line where the complexity of implementing the latest "never bug you again" feature outweighs the time it takes to refactor into something a bit more elegant that can handle n cases of adding new features, you have to refactor. Whether your boss has faith in your assessment or not goes a long way in determining whether this is an environment in which you should be working. All application development is ultimately iterative and explaining how this additional feature request tips the balance to refactoring is usually a pretty easy sell.

The few times in which it hasn't8212;as in "why didn't you code this way from the start?"8212;afford you the opportunity to pitch up-front planning as something that is actually useful.

As for moving forward, flabadablet's methodology is key. Even when, in the future, your boss hands you a one-off assignment, take some time to think about the pieces involved and how to model them. This opens the door to all the benefits of OO programing like re-usable code and pieces that are easier to refactor.posted by Fezboy! at 11:24 AM on September 26, 2007

Damn! I always forget that 8212; gets borked. &emdash;!!!posted by Fezboy! at 11:26 AM on September 26, 2007

I feel like I'm not using the right words to ask this question.

I agree with everything flabadeblet wrote, and for the most part, my app IS structured that way. It's a "machine" with over 100 different parts. Each part is its own class -- or family of classes. Each is self-contained and "just works." So, you could -- say -- take my DVD player and slap it onto your app and it would work just-as-well-as it does in my app.

I have an API in place for making new parts and plugging them into the machine. That works well.

The only thing that's difficult is the relationship -- relationshipS -- between all the parts. Which is a totally different thing that the parts themselves. THAT'S where 99% of my debugging time gets spent and 99% of my hair-pulling gets done.

In a way, it's really simple. Part A should only do its thing if Parts Q, R, W and (N or Z) are doing their things. But there are a zillion of these "rules" and I'm looking for a way to manage them.

I want something like this:

A.state=on (if and only if) B.state=on AND C.state=off AND E.state="green"

B.state=on (if and only if) R.state=true AND (E.state="green" OR E.state = "red").

I'm looking for my options for this sort of thing. I remember tackling the same thing, years ago, when I was trying to write an adventure game. The magic word works if you're in the blue room and you have the scroll but not if you have the wand...posted by grumblebee at 12:09 PM on September 26, 2007

Karnaugh maps are a tool to reduce complicated boolean expressions to their shortest forms. But trying to apply them here, with this many options, would probably quickly result in unreadable code.

My only other advice may not apply depending on the details of the situtation and your language (I know nothing about ActionScript.) Wrap things up in as much meaning as possible.

(E.state="green" OR E.state = "red") means something -- I don't know what -- let's say it means E is "beautiful". Can you add a "beautiful" method to E such that your code becomes:

B.state = on if R.state = true and E.beautiful

And maybe the combination of R.state and E.modifable means something sensibly nameable that can become a function --

B.state = ready_for_my_closeup(R,E)

Of course, it'd be easy for this to be hard to maintain given ongoing upheavals in the rules.posted by Zed_Lopez at 2:21 PM on September 26, 2007

s/modifiable/beautiful/ -- I changed my example midstream but didn't get the last one.posted by Zed_Lopez at 2:24 PM on September 26, 2007

Well, if your boss is just pulling the UI design out of his arse, there is no way the edge cases are ever going to get tested. So to some extent it doesn't actually matter if all the rules work properly.

But I still think the key to this is regularizing the interface design so that you can connect any source to any sink with the same kind of code, then drawing up a diagram that shows where the signal connections go, then modifying the code.

If all this talk of sources and sinks and signal paths is too specific to your analogous TV example, and not really applicable to your actual task at hand, that's just an illustration that in programming, possibly to a greater extent than in any other field of endeavour, the devil is always in the details.

In a way, it's really simple. Part A should only do its thing if Parts Q, R, W and (N or Z) are doing their things. But there are a zillion of these "rules" and I'm looking for a way to manage them.

I want something like this:

A.state=on (if and only if) B.state=on AND C.state=off AND E.state="green"

B.state=on (if and only if) R.state=true AND (E.state="green" OR E.state = "red").

If all your pieces have a .enable() method that takes a boolean parameter, you could just have some setup code that does

This is the same kind of regularization as logic designers do when moving a random-logic design into a PAL.posted by flabdablet at 6:25 PM on September 27, 2007

Whether a table actually ends up easier to maintain than a forest of Boolean expressions will depend entirely on the typical pattern of those expressions. For example, here's the expression from your title rendered as a table:

a b c d e f g h enabletrue any any any any any any any ???any true true any false any any any ???any true any true false any any any ???any true true any any true true true ???any true any true any true true true ???

The table is very explicit and easy to walk through when testing cases, but may be a better or worse description of exactly what the intentional behaviour is, depending how the reader's mind works.posted by flabdablet at 8:55 PM on September 27, 2007

Another late thought, that may be no good depending on how close to your actual task the TV analogy is: if the FLIV is not supposed to affect the output of the DVD, then perhaps you could add some metadata to your simulated "video signals" - make unmodifiability a property of the signal itself, rather than its source. That way, your FLIV doesn't need to know anything about the rest of the architecture; it only needs to know whether its input is modifiable.

Then, when your boss decides at the last minute that you need a Blu-Ray player as well, and that its output won't be affected by the FLIV either, you just need to add another source of unmodifiable video.posted by flabdablet at 6:29 PM on September 29, 2007

Tags

Share

About Ask MetaFilter

Ask MetaFilter is a question and answer site that covers nearly any question on earth, where members help each other solve problems. Ask MetaFilter is where thousands of life's little questions are answered.