4

I have seperated out a test to determine if two schedule items overlap because of the unreadability of it.

Is there any application to assist in simplifying a logic statement?

Example: (originally a faulty example, but exposes reasons I request this)

if (x < y && y < z && x < z)  

could be reduced to

if (x < y && y < z)

My code is:

return (shift.Start <= shift2.Start && shift.End >= shift2.End) || (shift2.Start <= shift.Start && shift2.End >= shift.Start)

I would love to make that simpler, and I believe it's possible, just unsure how.

Seeing as this is really language agnostic, even converting to a different script to find possibilities would be nice, no need for it to be in C# for instance.

Brett Allen
  • 5,297
  • 5
  • 32
  • 62
  • 2
    It is not true that (x < y && y < z) can be simplified to if (x < z), e.g. with x = 1, y = 0 and z = 2 then the first condition is false and the second one is true – Simon Nickerson Jan 15 '10 at 20:35
  • Apologies everyone for initial incorrect example, it has been fixed. – Brett Allen Jan 15 '10 at 20:42
  • One thing I'm curious about is what the difference between `shift.StartTime` and `shift.Start` is. I was trying to come up with commenting or names for boolean vars that might help document the intent of the expressions, but got stymied by that detail. – Michael Burr Jan 15 '10 at 21:05
  • Ah, there was two different classes in the original code, one used .StartTime and the other used .Start Since I was doing this for examples sake, I tried to make them both consistent with each other. – Brett Allen Jan 15 '10 at 23:25

11 Answers11

10

Kill the duplicate logic and you'll kill two birds with one stone. You'll get DRY, and you'll get a function name (the rich man's comment):

class Shift:
  def encompasses(other_shift)
    self.start <= other_shift.start && self.end >= other_shift.end

...

return shift1.encompasses(shift2) || shift2.encompasses(shift1)
Wayne Conrad
  • 103,207
  • 26
  • 155
  • 191
  • Beat me to it. Another thing to consider is avoiding variables named `shift` and `shift2`, as it makes long code even longer and more repetitive. Give me `a.covers(b) || b.covers(a)` any day. – Jason Orendorff Jan 15 '10 at 21:14
  • Unfortunately this would not cover the situation where a shift starts before the other shift and ends after the other starts but before it ends. It's not encompassing I'm looking for, but overlapping. – Brett Allen Aug 30 '17 at 14:58
7

Be very, very careful with these kinds of changes. They might seem straightforward at inital glance, and boolean logic (and DeMorgan's laws) are not too hard to grasp, but there are often potential gotchas when you look at individual cases:

For example: if (x < y && y < z) could be simplified to if (x < z)

This is not correct, if (x < z), y might still be greater than z. That state would not pass your original tests.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
4

Although x < y && y < z implies x < z (< is transitive), the reverse is not true so the expressions are not equivalent. Indeed if y were defined as, say, Integer y = null, then the former may even cause an NPE in Java, or UNKNOWN in SQL.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
3

You need to be really careful when doing this... the example you gave, for example, simply isn't true...

if x = 1, y = 2, and z = 2, then x < y = true, and x < z = true, but y < z = false.

For this type of reasoning, you really want to go for code readability in these circumstances and not worry about the most efficient code that you can get.

Michael Bray
  • 14,998
  • 7
  • 42
  • 68
  • Code readability is what I'm trying to obtain, as the complex if statement in my code is a nightmare, had a bug fix I needed to make in it, and wished it could have been written simpler. – Brett Allen Jan 15 '10 at 20:43
3

When it comes to complex logic statements, you're usually best off with formatting your code in a readable manner than attempting some premature optimization (root of all evil, etc.)

For example:

return (shift.Start <= shift2.Start && shift.End >= shift2.End) || (shift2.Start <= shift.StartTime && shift2.End >= shift.Start)

Can, for readability and maintainability, be refactored to:

bool bRetVal = false;
bRetVal = (    (   (shift.Start <= shift2.Start)
                && (shift.End >= shift2.End))
            || (   (shift2.Start <= shift.StartTime)
                && (shift2.End >= shift.Start)))
return bRetVal;

Most places maintain a coding standard that defines something like the above for large logic blocks. I'd much rather maintain a few extra lines of code that can be read and understood than a one line monstrosity.

Matt Jordan
  • 2,899
  • 1
  • 27
  • 29
  • Readability was the original goal for simplification rather than optimization :) Very good idea, short of some mathmagical application to simplify boolean logic for easier reading, I suppose this will do. – Brett Allen Jan 15 '10 at 20:56
  • If for readability use this: http://stackoverflow.com/questions/2074572/simplifying-if-statement-logic/2074816#2074816 Remember that someone eventually will have to maintain your code, and in the original way or this, it makes it hard to understand what's going one here. – OscarRyz Jan 15 '10 at 22:23
3

Is there any application to assist in simplifying a logic statement?

I didn't see anyone addressing this part of the question, So I'll take a stab and see what discussion occurs.

There are techniques for working with boolean logic. Back in my college days (BSEE) we used Karnaugh maps. Basically, you can take a very complex arbitrary truth table and determine a correct and optimized logical expression. We used this to reduce the number of logic gates in a circuit, which is analogous to simplifying a complex if statement.

Pros:

  • You can implement/optimize a very complex and arbitrary truth table relatively easily.

Cons:

  • The resulting logic usually bore little resemblance to the intent of the truth table. As others have suggested, this is "unreadable".
  • A change to a single cell of the truth table would often result in a completely different expression. A simple design tweak becomes a re-write, so it's unmaintainable.
  • Non-optimized logic is a lot cheaper than it used to be, while the design costs are the same.

Ultimately, the most critical thing is the correctness of the truth table/logic expression. An error means your program won't work right. No application or design technique will help if you don't properly understand the definition of the logic that needs to be implemented.

In my opinion, few real-world problems are sufficiently complex to truly benefit from this type of technique, but it does exist.

mbmcavoy
  • 2,628
  • 5
  • 23
  • 34
1

Sometimes you can wrap statements such as:

shift.Start <= shift2.Start && shift.End >= shift2.End

Into a boolean function to make it more readable such as:

function ShiftWithinValidRange //(terrible name here, but you get the idea)
{
    return (shift.Start <= shift2.Start && shift.End >= shift2.End);    
}
monO
  • 67
  • 1
  • 2
  • 5
1

Assuming Start and StartTime are actually supposed to be the same field, your condition boils down to

(a <= b && c >= d) || (b <= a && d >= c)

We can turn this into

(a <= b && d <= c) || (b <= a && c <= d)

but this still doesn't look like it simplifies much.

Simon Nickerson
  • 42,159
  • 20
  • 102
  • 127
0

Not only is this dangerous, but it often leads to harder to maintain code. Boolean logic is easier to understand when broken down into specific steps. Condensing the logic will often lead to harder to understand logic.

i.e. in your example, why are we checking if x < z , when what we really want to know is x < y && y < z?

The simplest solution is often the best one. Condensing your logic into 'cooler' but less readable code is not good in the long run.

Chris Kannon
  • 5,931
  • 4
  • 25
  • 35
0

I don't have a general solution for you, but if I use Lisp syntax it looks a lot simpler to me:

(and (< x y)
     (< y z)
     (< x z))

Then notice that the first two clauses:

(and (< x y)
     (< y z))

can be combined into:

(and (< x y z))

So the full expression now looks like:

(and (< x y z)
     (< x z))

Now it's obvious that the second one is redundant, so it's down to:

(and (< x y z))

or simply:

(< x y z)

which in C-syntax is:

(x < y && y < z)
Ken
  • 1,066
  • 1
  • 10
  • 17
0

I think Wayne Conrad's answer is the right one, but for entertainment purposes only, here's another way to say it (I think):

(long) shift.Start.CompareTo(shift2.Start) * (long) shift.End.CompareTo(shift2.End) <= 0

Is this actually faster? I don't know. It's certainly harder to read.

Jason Orendorff
  • 42,793
  • 6
  • 62
  • 96