2

If I have multiple conditions (nested and/or otherwise) with boolean (either False or True) outputs; how could I further simplify the code and make it more efficient, comprehensive, and elegant?

For example, under such circumstances as follows:

if condition_1:
    if condition_2:
        # do one thing
        pass
    elif condition_3:
        # do another thing
        pass
    else:
        # do a third thing
        pass

elif condition_2:
    if condition_3:
        # do a fourth thing
        pass

and so on.

It's a exam project of mine, so not get too much help, I'll try and explain what my code should do. I basically want to go through a dataset, looking for different things. Lets say its a dictionary, like this:

myDict = {'a': ['b', 'c'], 'b': ['c', 'd']}

If I go through the dict:

for item, element in myDict.items():
    for letter in element:
        if letter == 'd':
            dflag = True
        if letter == 'c':
            cflag = True

    if cflag:
        if dflag:
            print('cd!')
        else:
            print('only c')
Gliz
  • 73
  • 1
  • 2
  • 13
  • 1
    Can you give us a sample `flag`? – Bahrom May 03 '16 at 14:45
  • Do you need the nested flags or could you just set 4 different flags like here? http://stackoverflow.com/questions/60208/replacements-for-switch-statement-in-python#answer-103081 – Murphy4 May 03 '16 at 14:51
  • Do a truth table for your flags and see which values lead to the same result. Maybe you'll find that there are less paths than you think. – ChatterOne May 03 '16 at 14:59
  • @Bahrom I've added a simple example :) – Gliz May 03 '16 at 18:13
  • Having a sample flag and/or a sample thing might help. – Diane M May 04 '16 at 06:32
  • @Gliz , it would be great if you check various answers kindly provided by different people and see whether any of them address your question. If one of them does, mark it as such so that others can benefit from it and that the thread is closed. Otherwise, please elaborate by commenting on them so that we can further and more effectively help you. – Pouria May 04 '16 at 11:19
  • Ok, I have now added your example to my answer, and tailored a workflow specifically based on it. – Pouria May 04 '16 at 14:51

5 Answers5

4

Using 'if', 'elif', and 'else' is not bad if it is done efficiently. But in general, the answer to your question would really depend upon individual circumstances.

Having said that, however, one way to do it would be to put your conditions inside a dict (as highlighted by yourself in the tags).

Here are a few examples:

As a dict:

conditions = {
    1: 'One',
    2: 'Two',
    3: 'Three',
    4: 'Four',
    5: lambda x: x**2  # Can be substituted with actual functions defined elsewhere.
}

x = 3

if x in conditions.keys():
    print(conditions[x])

returns:

Three

or in the case of a function:

x = 5

if x in conditions.keys():
    func = conditions[x]
    print(func(x))

returns:

25

Using a function to resemble switch...case:

To make it even clearer, and have something like a switch...case statement, you can do this:

def switch(case):
    conditions = {
        1: 'One',
        2: 'Two',
        3: 'Three',
        4: 'Four',
        5: lambda x: x**2  
    }

    return condition[case] if case in conditions else False

It is ran like so:

>>> print(switch(2))
2

or for a non-existent items:

>>> print(switch(6))
False

Implementation on your example:

switch...case function decorator (wrapper)

So to address the example you have added, we can do as follows:

First we need a general switch/case decorator:

def switch_case(switch):
    def switcher(func):
        def case(case):
            return switch[case](case) if case in switch else None
        return case 
    return switcher

Then we need a dictionary of our conditions, which are the one given in your example:

# Define the conditions in a dict.
conditions = {
    'd': lambda x: True if 'd' else False,  # You can say: True if 'a' or 'b' else False
    'c': lambda x: True if 'c' else False  
}

Now we Create a decorated switch-case function based on your conditions:

@switch_case(conditions)
def my_conditions(case):
    return case

Then we specify the elements, or read them from a file, database or anything:

# Elements to be tested as string.
# It can be any flattened (not nested) iterable (str, tuple, list, numpy.array, etc.)
myDict = {'a': ['b', 'c'], 'b': ['c', 'd']}
elements = sum(myDict.values(), [])  # Returns a flattened lists of values. 

Evaluate the elements based on the conditions (generator object).

verdicts = map(my_conditions, elements)

Match the elements to the corresponding evaluation results (generator object).

status = zip(elements, verdicts)

Now we can positively regulate the outputs (discard None vlues) and create a dict in which the keys are the elements, and the values are the status of their conditions.

passed = {key+'flag': val for key, val in status if val is not None}

print(passed)
# output: {'cflag': True, 'dflag': True}

Add as variables to the namespace

At this point, you can use the dict as is; however, if you insist on adding it to the namespace, here is how:

# Rename values and add them to the name space.
locals().update(passed)

Test

Finally, let's test and ensure the values exist in the local namespace (notice that we haven't implemented any of these names before). So, if the condition returned a True value for the particular character in the sequence, a variable would have been created:

>>> print(dflag)  # We had 'd' in `myDict` values.
True

On the other had, if the condition returned None, there will be no value in the namespace.

>>> print(aflag)  # We didn't have 'a' in `myDict` values.
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-25-26f7e9594747> in <module>()
     24
---> 25 print(aflag)

NameError: name 'aflag' is not defined

Note: Under the existing structure, if the condition returns False, a variable will be created in the namespace and assigned a value of False.

Hope this helps.

Pouria
  • 1,081
  • 9
  • 24
0

Your code here about as simple as you can get.

The one way you can condense is by changing the last branch from:

elif flag2:
    if flag3:
        do a fourth thing

to

elif flag2 and flag3:
    do a fourth thing
user2096065
  • 93
  • 1
  • 4
0

You can use an iterator over flags. A dictionary could work too, depending on what your flags are, but it wouldn't really help if your flags are something like x==1, y==10, z=='a' that could all evaluate to a True or False only (because the keys can only be unique). If your flags are in the form of a == b you'd probably have to go with some sort of iterator.

def f(): print('f')
def g(): print('g')
def h(): print('h')

y = 3
x = 2

flags = [
    # Outer condition
    (y==1, (
        # Inner conditions and actions
        (x==1, f), (x==2, g), (x==3, h)
     )),
    (y==3, (
        (x==1, g), (x==2, f), (x==3, h)
    ))
]
# Using next ensures that the rest of the flags/actions aren't even evaluated,
# the same way an if/elif would work
_, inner_flags_and_actions = next(
   (flag, inner) for (flag, inner) in flags if flag
)
_, action = next(
    (flag, action) for (flag, action) in inner_flags_and_actions if flag
)

# By now you have reached the action you want to take.
action()

This prints: f

Bahrom
  • 4,752
  • 32
  • 41
  • Why not a `dict`? Gets rid of the need for generators and makes it loop more organised. Don't you agree? – Pouria May 03 '16 at 15:01
  • @PouriaHadjibagheri I agree with a `dict`, I was just thinking along the lines of - suppose your flags are `x==1, x==2, x==3, y==5`. Now these can evaluate to a `True` or `False`, but dictionary keys have to be unique. I guess you could get around that by using something like: `{True: [first_action, second_action, etc.], False: [fifth, etc.]}`. I think this depends on what kind of flags you're supplied at this point. – Bahrom May 03 '16 at 15:08
  • I see your point, and it is a valid one. But I tend to avoid iterations as much as I can, even if it is in a generator. In such scenarios as the ones your mentioned, one can also swap the key and the item, or alternatively put them in as string values and use `exec` to implement them. Have a look at my example. Since it's a function, the use of `exec` would become quite easy and efficient. – Pouria May 03 '16 at 15:14
  • Yes, I would have actually liked to use a `dict` here as well, it works out nicely in something like here http://codereview.stackexchange.com/questions/126746/making-my-code-look-neater/126748#126748, but we really need an example of flags. It's just that if you go with `(True, True, False, True)` or something as your key, you kind of lose track of what that flag actually means (`True` gives me less info compared to `a==b`). – Bahrom May 03 '16 at 15:26
  • @PouriaHadjibagheri Also, I actually tend to avoid `exec` and `eval` - executing a string of code makes me nervous lol. I think at this point, the main issue is that the asker needs to rethink what they're trying to accomplish. – Bahrom May 03 '16 at 15:26
  • Sure. Assign `lambda` then instead of `exec`. Makes different objects with different assignment codes. I agree, the point is to make them rethink, which is exactly what I mentioned. It really depends on individual circumstances, as it is matter of taste, best practice, remaining concise, and not writing things like these when we can get it done with a couple of `if...elif..else`. – Pouria May 03 '16 at 15:41
0

Have you considered refactoring? A well written function should do one thing, the fact you have three flags indicates this block of code is going to be doing AT LEAST 3 things, which brings in a lot of headaches for testing, code readability etc.

Are you sure this couldn't be refactored into three or more methods, check for the flags at the beginning and launch the corresponding methods.

dahui
  • 2,128
  • 2
  • 21
  • 40
  • I'm going through a dataset, where I look for certain conditions. If I find them, I set my 'condition'-flag to True. If not, then the flag is false. – Gliz May 03 '16 at 18:02
0

You can use a dict :

d = { (False, False, False) : f1,
(False, False, True) : f2,
(False, True, False) : f3,
(False, True, True) : f4
...

Then call d[(flag1,flag2,flag3)]() This is if you need an industrial amount of if/elses, otherwise just try to make the right simplifications.

Of course, if you are testing agains the same entry variable, or applying the same function with different parameters as output, then you can simplify even more by replacing booleans and functions with actual data.

Diane M
  • 1,503
  • 1
  • 12
  • 23