0

Is there a smart way to shorten very long if-elif-elif-elif... statements?

Let's say I have a function like this:

def very_long_func():
  something = 'Audi'
  
  car = ['VW', 'Audi', 'BMW']
  drinks = ['Cola', 'Fanta', 'Pepsi']
  countries = ['France', 'Germany', 'Italy']
  
  if something in car:
    return {'type':'car brand'}
  elif something in drinks:
    return  {'type':'lemonade brand'}
  elif something in countries:
    return {'type':'country'}
  else:
    return {'type':'nothing found'}
  

very_long_func()
>>>> {'type': 'car brand'}

The actual function is much longer than the example. What would be the best way to write this function (not in terms of speed but in readability)

I was reading this, but I have trouble to apply it to my problem.

dimay
  • 2,768
  • 1
  • 13
  • 22
PParker
  • 1,419
  • 2
  • 10
  • 25
  • 2
    "I was reading this, but I have trouble to apply it to my problem." What happened when you tried? What code did you write, and where did you get stuck? – Karl Knechtel Feb 05 '21 at 15:15
  • 1
    in this case you could have a dictionary where "VW", "Audi", and "BMW" were keyed to "car brand", and "Cola", "Fanta", and "Pepsi" were keyed to "lemonade brand" and so forth. – Andrew Allaire Feb 05 '21 at 15:17
  • I was trying to apply option 4 from the master solution. One problem is, that it doens't work to put ``return ....`` into a python dictionary – PParker Feb 05 '21 at 15:18

4 Answers4

3

You can't hash lists as dictionary values. So go other way round. Create a mapping of type -> list. And initialize your output with the default type. This allows you to keep on adding new types to your mapping without changing any code.

def very_long_func():
  something = 'Audi'
  
  car = ['VW', 'Audi', 'BMW']
  drinks = ['Cola', 'Fanta', 'Pepsi']
  countries = ['France', 'Germany', 'Italy']
  
  out = {'type': 'nothing found'}  # If nothing matches
  mapping = {
      'car brand': car,
      'lemonade brand': drinks,
      'country': countries
    }
  for k,v in mapping.items() :
    if something in v:
      out['type'] = k    # update if match found
      break
  return out             # returns matched or default value
AnkurSaxena
  • 825
  • 7
  • 11
  • 2
    This answer solves the OP's question as asked and has the cleanest coding style out of the numerous similar answers so this one should be the accepted answer IMO. – Biggsy Feb 05 '21 at 15:35
  • Thank you very much to ALL OF YOU. I think this is my prefered answer, because it is the most readable (at least for a Python beginner like me) – PParker Feb 05 '21 at 15:40
2

you can create dictionary like this and then use map_dict.

from functools import reduce
car = ['VW', 'Audi', 'BMW']
drinks = ['Cola', 'Fanta', 'Pepsi']
countries = ['France', 'Germany', 'Italy']

li = [car, drinks, countries]
types = ['car brand', 'lemonade brand', 'country', 'nothing found']
dl = [dict(zip(l, [types[idx]]*len(l))) for idx, l in enumerate(li)]

map_dict = reduce(lambda a, b: dict(a, **b), dl)
Pygirl
  • 12,969
  • 5
  • 30
  • 43
1

Try this:

def create_dct(lst, flag):
    return {k:flag for k in lst}

car = ['VW', 'Audi', 'BMW']
drinks = ['Cola', 'Fanta', 'Pepsi']
countries = ['France', 'Germany', 'Italy']

merge_dcts = {}
merge_dcts.update(create_dct(car, 'car brand'))
merge_dcts.update(create_dct(drinks, 'lemonade brand'))
merge_dcts.update(create_dct(countries, 'country'))

something = 'Audi'
try:
    print("type: ", merge_dcts[something])
except:
    print("type: nothing found")
dimay
  • 2,768
  • 1
  • 13
  • 22
0

You can simulate a switch statement with a helper function like this:

def switch(v): yield lambda *c: v in c

The your code could be written like this:

something = 'Audi'

for case in switch(something):
    if case('VW', 'Audi', 'BMW'):          name = 'car brand'      ; break
    if case('Cola', 'Fanta', 'Pepsi'):     name = 'lemonade brand' ; break
    if case('France', 'Germany', 'Italy'): name = 'country'        ; break
else:                                      name = 'nothing found'
return {'type':name}

If you don't have specific code to do for each value, then a simple mapping dictionary would probably suffice. For ease of maintenance, you can start with a category-list:type-name mapping and expand it before use:

mapping = { ('VW', 'Audi', 'BMW'):'car brand',
            ('Cola', 'Fanta', 'Pepsi'):'lemonade brand',
            ('France', 'Germany', 'Italy'):'country' }
mapping = { categ:name for categs,name in mapping.items() for categ in categs }

Then your code will look like this:

something = 'Audi'
return {'type':mapping.get(something,'nothing found')}

using a defaultdict would make this even simpler to use by providing the 'nothing found' value automatically so you could write: return {'type':mapping[something]}

Alain T.
  • 40,517
  • 4
  • 31
  • 51