Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
280 views
in Technique[技术] by (71.8m points)

c# - Expressing 2x2 Logic Grid in Code Efficiently

In an event handler I'm responding to the change of a value. I have access to the old value and the new value and want to do certain things depending on what the change is.

Each different outcome will do some combination of actions/functions X, Y, or Z. Z accepts a parameter between -1 and 1. Order of performing these is not important.

Look at the following logic grid. The old value is the leftmost column of labels, and the new value is the top row of labels:

          New:
          0          !=0
          --------   -------
Old:  0 | nothing    Y, Z(1)
    !=0 | X, Z(-1)   X, Y    -- Z(0) is okay but not required for this quadrant

What would be a good way to represent this?

I'm working in C# but will accept answers in any language since it's not really a language question—I can translate whatever.

Example:

if (oldvalue == 0 && newvalue == 0) return;
if (oldvalue != 0) X();
if (newvalue != 0) Y();
Z(oldvalue != 0 ? -1 : 0 + newvalue != 0 ? 1 : 0);

I suppose that looks pretty good, but there are other ways it could be done.

int which = (oldvalue == 0 ? 0 : 1) + (newvalue == 0 ? 0 : 2)

switch (which) {
   case 1:
      X(); Z(-1);
      break;
   case 2:
      Y(); Z(1);
      break;
   case 3:
      X(); Y();
      break;
}

This is actually a slightly simpler case than what I'm dealing with. In the case that oldvalue and newvalue are nonzero and equal to each other, treat newvalue as if it was 0.

Feel free to answer as given or with this additional constraint. There's still a little bit more but I think it's too much to start off with. If things seem interesting afterward, I'll present the rest either here or in a new question.

I guess I'm asking the question because I end up with these logic grid things often, and they aren't always 2x2, sometimes they're a bit bigger. It's nice to notice that I can handle some responses with entire "stripes" like noticing that X is done every time the oldvalue != 0, but it seems like I'm starting to run into a pattern that begs for some expressive logic to handle it more generally instead of laboriously turning it into if then else statements. I mean, it would be really cool if I could provide a sort of grid of logic and let the compiler figure out the best way to handle it.

Just doing some wild brainstorming:

Conditions:
oldvalue == 0 ? 0 : 1
newvalue == 0 ? 0 : 2

Actions:
X = {false, true, false, true}
Y = {false, false, true, true}
Z(-1) = true where condition = 1
Z(1) = true where condition = 2

What are your ideas? I'll reward any material involvement at all.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Let's look at your problem from another point of view. When designing a body of code, I try to apply the following principle:

  1. Make it correct.
  2. Make it clear.
  3. Make it concise.
  4. Make it fast. ... in that order.

All of these, to one extent or another, are subjective. However, reasonable people tend to find common ground - and there's often more agreement about their opposites. But that aside...

The first priority here is making sure that your code will work correctly. Clearly, there are multiple implementation that achieve this - but I would also add that it's important that it be easy to demonstrate that the implementation is correct. One way of achieving this is to make the code read more like the spec (more on this later).

The second priority is to make sure that in the future, when a developer (including the original author!) looks at this code they'll be able to understand what it's doing right away. The more sophisticated (read: fancy) the implementation, the harder it is for a developer to immediately understand what the code is doing.

The third priority - short, concise code, is one that partially stands in opposition to the first two. A desire to make the code more concise, may lead you to use more complex constructs than are actually necessary to solve the problem. While it's important to keep the code short, we shouldn't do this by making it unintelligibly dense.

The last priority - performance - only matters when it matters. By this, I mean that you shouldn't complicate the implementation from the point of view of performance unless you've performed profiling and identified it as bottleneck in your system.

So now that we've looked at the principles that should drive our decisions, let's apply them to the problem at hand. You've provided a very clear specification of how the code is supposed to behave. Let's try to adhere to them:

void YourMethod( int oldValue, int newValue )
{
    bool oldValueNonZero = oldValue != 0;
    bool newValueNonZero = newValue != 0;

    if( oldValueNonZero ) { X(); }
    if( newValueNonZero ) { Y(); }
    if( oldValueNonZero && newValueNonZero ) { Z(); }
}

So why do I like this particular implementation. Let's break it down.

First, note that I've chosen to create temporary boolean to capture the result of testing the old/new value for whether they are nonzero. By capturing these values, I avoid performing the calculation more than once, and I also make the code more readable (see below).

Second, by choosing the descriptive names oldValueNonZero and newValueNonZero I'm making the implementation clearly indicate my expectations. This both improves the readability of the code and clearly conveys my intent to future developers who have to read it.

Third, note that the body of the if() test is wrapped in { and } brackets - this helps reduce that chance that future changes to the implementation will break the behavior - by accidentally including a new case, for instance. Using single-line ifs is a recipe for future problems.

Finally, I don't try to short-circuit the comparison and exit the function early. If performance were extremely important, an early exit may be useful. But otherwise, it makes it easier to understand the behavior of a method if there's only one exit point(1).

Does this code do what the spec says? I believe so.

Is it easy to read and understand. At least to my eyes, I would say yes.

Is this code the most compact or sophisticated way of short-circuiting the logic? Almost certainly not ... but it's other qualities more than make up for that, in my opinion.

Whether you like this particular structure of code or not is, to some extent, a matter of taste and style. But I hope that the principles I've laid out about how I chose to organize it may help you make such decisions in the future.

You've indicated that you sometimes run into similar "logic-grid" problems, but ones where the number of cases are more numerous. These kinds of problems can become complicated for two separate reasons:

  1. The number of values that the parameters can take on increase - they can take on the general form MxN.
  2. The number of dimensions increase - in other words, there are more variables to include in the rules: MxNxOxP...xZ.

One generalized solution to the problem (as another response indicates), is to encode the solution as multidimensional matrix - and define a set of actions for each case. However, it's entirely possible for the rules to overlap - and it's probably desirable to collapse equivalent cases together, for simplicity.

My response to dealing with the general case is ... that it depends. If the conditions can be reduced to some very small number of cases, than imperative if/else logic may still be the best way to solve the problem. If the number of conditions are very large, than it may make sense to use a declarative approach, in which you use some kind of lookup table or matrix to encode the cases.

1> - One common exception to the principle of only having a single exit point from a method is for preconditions. It's cleaner to avoid nesting and inverted logic by first checking all/any preconditions, and failing (exiting) the method if they are violated.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...