6

The variable a can take any number of values. The value of a is the amount of extra pre-defined conditions to have for the while loop.

This can be done with multiple elif statements but is there a cleaner way of doing this?

if a == 0:
    while condition_1:
        ...
elif a == 1:
    while condition_1 or condition_2:
        ...
elif a == 2:
    while condition_1 or condition_2 or condition_3:
        ...
Lobstw
  • 489
  • 3
  • 18

3 Answers3

10

A general way of doing what other languages do with a switch statement is to create a dictionary containing a function for each of your cases:

conds = {
    0: lambda: condition_1,
    1: lambda: condition_1 or condition_2,
    2: lambda: condition_1 or condition_2 or condition_3
}

Then:

while conds[a]():
    # do stuff

By using lambdas (or named functions if your conditions are particularly complex) the appropriate condition can be evaluated each time through the loop, instead of once when the dictionary is defined.

In this simple case where your a has sequential integer values starting at 0, you could use a list and save a bit of typing. To further simplify, you could define each of your conditions in terms of the previous one, since you're just adding a condition each time:

conds = [
     lambda: condition_1,
     lambda: conds[0]() or condition_2,
     lambda: conds[1]() or condition_3
]

Or, as suggested by Julien in a comment:

conds = [
     lambda: condition_1,
     lambda: condition_2,
     lambda: condition_3
]

while any(cond() for cond in conds[:a+1]):
    # do stuff
kindall
  • 178,883
  • 35
  • 278
  • 309
  • 2
    +1 on a clever and simplistic solution. However, You may want to specify on why lambdas - if I interpret correctly, it's because not using them would cause the boolean values to be fixed at dictionary initialization, correct? – MutantOctopus Mar 16 '16 at 01:22
  • 1
    Yes. You could also use named functions for particularly complex conditions. In any case it needs to be a function to defer evaluation until later (and to allow repeated evaluation). – kindall Mar 16 '16 at 01:24
  • 1
    I personally would recommend putting that into the answer directly, so if someone who doesn't understand comes in they would have an immediate explanation – MutantOctopus Mar 16 '16 at 01:27
  • That list idea is incredibly clever. I might have to go see if you have any other gems in your answers on the site, heh. – MutantOctopus Mar 16 '16 at 01:41
  • 1
    small improvement (maybe?): define `conds = [lambda: cond1, lambda: cond2, ...]` and then `while any([cond() for cond in conds[:a]])` – Julien Mar 16 '16 at 01:54
  • argh, @Julien got there before me, I wanted to suggest that, but wasn't on a computer. One minor change though: you don't need a list inside the any, it can just be a generator (this way the entire list doesn't need to get created). – Bahrom Mar 16 '16 at 02:08
  • That's a great one, @JulienBernu. – kindall Mar 16 '16 at 04:30
  • @kindall Brilliant answer, thank you. Could you possibly explain why the parentheses are needed here `while conds[a]()` because I missed that originally and it led to a lot of head scratching. – Lobstw Mar 16 '16 at 12:05
  • 1
    The parentheses are needed because `conds[a]` is a function and it must be called in order to evaluate the condition(s) defined in it. Somewhat confusingly, function objects are "truthy" by themselves, so if you don't call the function, your `while` loop runs forever. – kindall Mar 16 '16 at 16:35
  • @kindall Hello, you mentioned using a `switch` in other languages and so I tried it in Java using `switch` and `case` but the only way I could make it work is by putting a `while` loop in each `case`. Could you perhaps tell me what functions I should be using to achieve this in Java? Thank you. – Lobstw Mar 26 '16 at 16:52
3

Have you tried something like this:

while (a >= 0 and condition_1) or (a >= 1 and condition_2) or (a >= 2 and condition_3) ...
Blundering Philosopher
  • 6,245
  • 2
  • 43
  • 59
  • 2
    While technically correct I'd like to advise readers that this is a somewhat wordy, excessive and non-Pythonic solution, especially if the conditionals get increasingly complex. – MutantOctopus Mar 16 '16 at 01:36
  • While a little less elegant, your answer worked right off the bat for me. Could you explain for me, how it's working? is the `(a >= 0 and condition_1)` a mini-if statement? – Lobstw Mar 16 '16 at 12:02
  • The `and` keyword is logically equivalent to nested `if` statements, so, sorta... – kindall Mar 20 '16 at 13:53
2

You could define a function to be evaluated for while:

def test(a):
    if a == 1:
        return condition1(...)
    elif a == 2:
        return condition2(...) or condition1(...)
    elif a == 3:
        return condition2(...) or condition1(...) or condition3(...)
    else:
        return False

# test(a) will check the conditions ... define additional arguments if you need them
while test(a):
    do_stuff

It does have still the elifs but you don't need to write the while-loop multiple times.

MSeifert
  • 145,886
  • 38
  • 333
  • 352