12

I'd like to create a switch/case where the cases can have intervals as condition, like:

switch = {
    1..<21: do one stuff,
    21...31: do another
}

How can I achieve this result?

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
gabrielrf97
  • 227
  • 1
  • 2
  • 7
  • What about `if 1 <= x < 21: ...; elif x < 31: ...` – olinox14 Sep 11 '19 at 07:38
  • 1
    Possible duplicate of [Replacements for switch statement in Python?](https://stackoverflow.com/questions/60208/replacements-for-switch-statement-in-python) – milanbalazs Sep 11 '19 at 07:44
  • 1
    @olinox14 works. But if the code evolves to support multiple ranged conditions, than I think this switch case I'm looking for will look better. – gabrielrf97 Sep 11 '19 at 08:09

3 Answers3

26

In Python 3.10 an explicit switch statement was introduced - match. Although it doesn't support direct containment checking, so we'll have to exploit the guard feature:

number = int(input("num: "))

match number:
    case num if 1 <= num <  21:
        # do stuff
    case num if 21 <= num < 31:
        # do other stuff
    case _:
        # do default

But at this point it begs the question why not just use an if/elif/else structure... Up to personal taste.

For earlier versions, as it looks like you already tried, the obvious way of implementing a switch structure in Python is using a dictionary.

In order to support intervals, you could implement your own dict class:

class Switch(dict):
    def __getitem__(self, item):
        for key in self.keys():                 # iterate over the intervals
            if item in key:                     # if the argument is in that interval
                return super().__getitem__(key) # return its associated value
        raise KeyError(item)                    # if not in any interval, raise KeyError

And now you can use ranges as keys:

switch = Switch({
    range(1, 21): 'a',
    range(21, 31): 'b'
})

And a few examples:

>>> print(switch[4])
a

>>> print(switch[21])
b

>>> print(switch[0])
KeyError: 0

Another option is to unpack the ranges and save each number of the range individually. Something like:

cases = {range(1, 21): 'a',
         range(21, 31): 'b'
        }

switch = {num: value for rng, value in cases.items() for num in rng}

The rest works the same.


The difference between the two options is that the first saves memory, but loses the time efficiency of dicts (as you check all the keys), while the second one will maintain the dict's O(1) look-up at the cost of taking more memory (the contents of all ranges together).

According to your application you can choose between them, as a general rule:

  • Few long ranges - the first option
  • Many short ranges - the second option
  • Anything in-between - find the optimal solution for your case
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
1

If you really have to use switch/case, then dictionary in Python can help you with that. Is there a way for a dictionary key be a range?

This is my take on it:

def switch_demo(argument):
    switcher = {
        range(1, 41): "Class 1 (1-40)",
        range(41, 50): "Class 2 (41-49)",
        range(50, 99): "Class 3 (50-98)",
        99: "Class 4 (99)",
        100: "Class 5 (100)"
    }
    
    for key in switcher:
        if type(key) is range and argument in key:
            print(switcher[key])
            return
        elif type(key) is not range and argument == key:
            print(switcher[argument])
            return
            
    print("Number class not captured")
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Zack
  • 101
  • 5
1

In Python 3.10 with the match feature, you can also make check numbers in a specific range. Here example of how I did it:

number: int = 10

match number:
    case num if num in range(10, 49):
    # do stuff
    case num if num in range(50, 100):
    # do other stuff
    case _:
    # do default
Tehdrew
  • 856
  • 11
  • 7