Is there any advantage to using De Morgan's laws in python?
Asked Answered
R

6

8

I use pycharm and a few times when using if statements I have seen the suggestion to change the statement using De Morgan laws, such as with the following if statement:

if new_odds > 10 and new_odds <= 30:

if not (not (new_odds > 10) or not (new_odds <= 20)):

For me it makes it less readable, so is there any advantage to using De Morgan's laws or is it strictly a personal choice?

Rumpus answered 23/1, 2014 at 22:49 Comment(3)
why wouldnt it suggest 10 < new_odds <= 30?Twelvemonth
It did as I said in a comment below, I ended up using that I just wanted to post my original code to show what I wrote to get the suggestion. I was just curious as to what advantage if any using DeMorgan's Law had.Rumpus
I think PyCharm is not suggesting that you should change your code, but rather if you wish to change your code, you can do so simply by clicking the suggestion.Inadvertency
P
17

De Morgan's laws state that:

"not (A and B)" is the same as "(not A) or (not B)"

and also,

"not (A or B)" is the same as "(not A) and (not B)"

Which are used to transform logic between alternate forms. So while the transformation you've done conforms to De Morgan's laws, it has become more difficult to read. As others have suggested the much simpler 10 < new_odds <= 30 would be much more readable, however it is very important to understand that this is short for 10 < new_odds and new_odds <= 30 because, following from this you can do logic like:

10 < new_odds <= 30 != max_odds | default_condition

Which expands to:

10 < new_odds and new_odds <= 30 and 30 != max_odds and max_odds | default_condition

So with that syntactic sugar in mind, lets look at another example:

We will consider a contrived example in simple role-playing game where we look at a skill we will call "dutch courage". The premise of this attack is that you can a bonus if you are at max health, and either your armor or your attack rating is insufficient to attack an enemy. I need to know when this rule won't apply.

Writing this out we have 4 conditions:

A = health == max_health
B = armor > enemy.attack
C = attack > enemy.defense
no_bonus = not(A and not(B and C))

Using De Morgan's laws I can decompose this thus:

not(A and not(B and C))
not(A) or not(B and C)
not(A) or not(B) or not(C)
not(health == max_health) or not(armor > enemy.attack) or (attack > enemy.defense)

Well, now I can decompose this further...

health < max_meath or armor < enemy.attack < attack > enemy.defense

Here we assume the opposite of == max_health is < max_health otherwise its not the maximum.

Although contrived, this shows us that De Morgan's laws are a tool to enable us to rewrite logic. Whether this logic is improved or not is up to the programmer, but the intent is to be able produce simpler constructs, that are first more readable and secondly and hopefully require less instructions and are thus faster.

Portsalut answered 23/1, 2014 at 23:32 Comment(2)
Stormtrooper, thanks, it makes sense finally. Pycharm's editor calls it Demorgan Law so I will blame Jetbrain's for the incorrect term.Rumpus
There are some errors with this; Firstly and mainly, the operator chaining in Python only works for comparison operators, ie. 30 != max_odds | default_condition does not expand to 30 != max_odds and max_odds | default_condition like you indicated. Secondly, you have errors in the example: Decomposing not(A and not(B and C)) yields not(A) or (B and C) instead of not(A) or not(B and C). Later, the last not also mysteriously disappears and the last or changes into an unrelated comparison...Enteron
P
7

In Python it is almost always better to be clear than slightly faster (if that even would have been, which I doubt).

I prefer this even simpler statement:

if 10 < new_odds <= 30:
Pharisaic answered 23/1, 2014 at 22:51 Comment(3)
I actually used that in the end, it was suggested by pycharm also, I just wondered about why anyone would use DeMorgan Law as it makes a simple statement much harder to read, I thought there must have been some advantage.Rumpus
While a better soultion, this doesn't explain what DeMorgans Laws are and how they are useful.Portsalut
@LegoStormtroopr, I do agree that my answer doesn't speak as broadly as some others, but to OP's case of comparing against a precomputed variable (new_odds) there is zero possible benefit to applying DeMorgans Laws.Pharisaic
C
5

In some cases, it makes things more verbose and readable. However, in cases where you already have a bunch of nots sprinkled around, or where you're comparing things in a less-than-natural order, demorganing can reduce the number of nots or reverse the order of the unequal comparisons. For example:

if not foo() and not bar():
if not(foo() or bar()):

if new_score <= high_score and new_level <= high_level:
if not (new_score > high_score or new_level > high_level)

(The second one is debatable… but that's exactly what you'd expect from a question of readability and style.)

So, if it makes your code more readable, do it; otherwise, don't.


There are a handful of languages (logic, constraint-satisfaction, relational, etc.) where this is not true, because applying a not to a value isn't just flipping True and False but generating a converse, possibly much slower, or possibly even indeterminate, query.

But that isn't the case with Python, or most other "general-purpose" languages.

Corcovado answered 23/1, 2014 at 22:51 Comment(3)
so using DeMorgan Law, if the first condition is false it does not continue as opposed to using not foo() and not bar() where both functions will be checked regardless,am I understanding you correctly?Rumpus
@PadraicCunningham: No, you'll short-circuit in the exact same place: if the demorganed version can short-circuit the or because foo() is True, then the original version can short-circuit the and because not foo() is true. There is no significant performance or correctness benefit either way, so readability is the only concern.Corcovado
@PadraicCunningham: (Well, there is a small performance benefit from, in effect, whichever one has fewer total operators, but (a) that will hardly ever matter, and (b) it will almost always go the same way as readability anyway…)Corcovado
E
2

Given the results below it would seem that the more complex/less readable expression is also the slowest. In any case readability is most of the time more valuable in python.

In [1]: new_odds = 0  
In [2]: %timeit if new_odds > 10 and new_odds <= 30: pass  
10000000 loops, best of 3: 24.3 ns per loop

In [3]: %timeit if not (not (new_odds > 10) or not (new_odds <= 20)): pass  
10000000 loops, best of 3: 48.6 ns per loop  

In [4]: %timeit if 10 < new_odds <= 30:pass  
10000000 loops, best of 3: 43.4 ns per loop  

In [5]: new_odds = 20  
In [6]: %timeit if new_odds > 10 and new_odds <= 30: pass  
10000000 loops, best of 3: 57.7 ns per loop  

In [7]: %timeit if not (not (new_odds > 10) or not (new_odds <= 20)): pass  
10000000 loops, best of 3: 102 ns per loop  

In [8]: %timeit if 10 < new_odds <= 30:pass  
10000000 loops, best of 3: 52.7 ns per loop
Erupt answered 23/1, 2014 at 23:0 Comment(0)
P
1

It can be useful sometimes for readability but it's the same thing at execution. For example with:

not(A AND B) === not(A) OR not(B)

if not a() and not b():
if not(a() or b()):

The execution will be the same with a() True or False.

For your example, the best solution remains to use the power of the Python syntax and write:

if 10 < new_odds <= 30:

This syntax is extremely useful for checking that a numeric value is in a particular range.

Pori answered 23/1, 2014 at 22:55 Comment(1)
No, b() will not be called every time in the second case; if a() is True, the or will short-circuit—just like in the first case, if not a() is False, the and will short-circuit. There is no difference here.Corcovado
G
0

There is no use of applying the De Morgan's laws alone, because the execution is precisely the same, including any side effects. Any observed differences arise not from De Morgan's laws per se, but from something else (e.g. from replacing not not A with A). Credit to abarnert, who already commented about this.

Consider two expressions, which we assign to expr1 and expr2:

expr1 = not (A or B)
expr2 = (not A) and (not B)

They are executed in exactly the same way:

  • First, bool(A) is called. In expr1 it is called because this is how or works. In expr2 it is called because this is how not works. See docs on this topic.
  • If bool(A) is True, then the True value is assigned and evaluation is complete.
  • If bool(A) is False, then not bool(B) is assigned as the result. Note that bool(B) will be called in both expr1 and expr2 because B ends up inside not.

"I-dentical" (a quote from My Cousin Vinny, a 1992 film).

Grocer answered 23/12, 2023 at 19:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.