1

I've already checked the following answers: Replacements for switch statement in Python? and How to refactor Python "switch statement"

but I think both refer to simpler switch statements with single cases.

I've got a problem where a switch statement would look something like this:

switch(view) {
  case "negatives":
    label = 0;
    break;
  case "cars\\00inclination_000azimuth":
  case "buses\\00inclination_000azimuth":
  case "trucks\\00inclination_000azimuth":
    label = 1;
    break;
  case "cars\\00inclination_045azimuth":
  case "buses\\00inclination_045azimuth":
  case "trucks\\00inclination_045azimuth":
  case "cars\\00inclination_090azimuth":
  case "buses\\00inclination_090azimuth":
  case "trucks\\00inclination_090zimuth":
  case "cars\\00inclination_135azimuth":
  case "buses\\00inclination_135azimuth":
  case "trucks\\00inclination_135azimuth":
    label = 2;
    break;
  # and so on

So there are many cases that result in the same label. Is there a quick way to do this using lists? Where I could use something like this

a = ["cars\\00inclination_045azimuth","buses\\00inclination_045azimuth","trucks\\00inclination_045azimuth","cars\\00inclination_090azimuth","buses\\00inclination_090azimuth", "trucks\\00inclination_090zimuth","cars\\00inclination_135azimuth","buses\\00inclination_135azimuth","trucks\\00inclination_135azimuth"]

if view in a:
    label = 2

But then I'd have to make a list for every set of cases that map to the same label and then go through each of them.

Is there a way to do the following, and if not, then what is the easiest way to do this?

 if view in _any_of_the_lists_i've_made:
     label = the_index_of_that_list

Update

The values I showed here in the question were just a few, in order to get a general idea of the problem. But I realized from some of the comments that it would be better to give the full range of values I have as cases.

  • There are 3 prefixes: "cars", "trucks" and "buses".
  • There are 4 angles of inclination (the first two digits after the slashes). So I can have cars\00inclination_000azimuth, or cars\30inclination_000azimuth or cars\60inclination_000azimuth or cars\90inclination_000azimuth
  • There are a total of 25 different azimuths. With differences of 45 degrees, so I can have cars\00inclination_000azimuth and cars\00inclination_045azimuth all the way to cars\00inclination_315azimuth

So in total I have 25 views for each vehicle, and with 3 vehicles, that's 75 different possible views, i.e. 75 cases.

Community
  • 1
  • 1
user961627
  • 12,379
  • 42
  • 136
  • 210
  • 3
    The accepted answer to the question you referenced is to use a dict. That will work here also. – tdelaney Aug 02 '14 at 14:34
  • So in place of this: `def f(x): return { 'a': 1, 'b': 2, }[x]`, would I use: `def f(x): return { 'a': 'b': 'c': 1, 'd': 'e': 2 }[x]` ? – user961627 Aug 02 '14 at 14:37
  • It seems easy enough to process the strings to extract the azimuth and use that as the dictionary key. – jonrsharpe Aug 02 '14 at 14:38
  • If you reduce the string to *just* the `000azimuth` part, you already reduce it to just 5 options. I bet there are no more than 9 in total (8 azimuth values and 1 'negative'). – Martijn Pieters Aug 02 '14 at 14:38
  • Every part of the string is important - I've just updated the questions. – user961627 Aug 02 '14 at 14:51
  • @user961627: You may have 75 different strings, but the resulting label only varies by the azimuth value, at least in your example. It *may* be that the inclination also plays a role, in which case you parse that out too and use it as a key together. – Martijn Pieters Aug 02 '14 at 14:53

4 Answers4

3

Your view strings follow a pattern; they consist of <transport>\<inclination>_<azimuth>, and your labels only really vary on the azimuth value.

Reduce the string to just the azimuth value, then use a dict to find your label:

labels = {'000': 1, '045': 2, '090': 2, '135': 2}

label = 0 if view == 'negatives' else labels[view.rpartition('_')[-1][:3]]

This takes out just the 3 digits from the azimuth string.

To illustrate how this works, a short demo:

>>> "cars\\00inclination_000azimuth".rpartition('_')
('cars\\00inclination', '_', '000azimuth')
>>> "cars\\00inclination_000azimuth".rpartition('_')[-1]
'000azimuth'
>>> "cars\\00inclination_000azimuth".rpartition('_')[-1][:3]
'000'

str.rpartition() splits on the last _ character, from which we select the last element, and from that last element we slice the first 3 characters.

If you need to vary by inclination too, parse that out separely:

labels = {
    None: 0,
    ('00', '000'): 1,
    ('00', '045'): 2,
    ('00', '090'): 2,
    ('00', '135'): 2,
    # etc.
}

if view == 'negatives':
    key = None
else:
    key = view.partition('\\')[-1][:2], view.rpartition('_')[-1][:3]
label = labels[key]

By reducing the view to just those parts that matter you can greatly reduce the size of the dictionary, or at least that part you need to type out.

Of course, just looking up the view string directly would be faster. You can always generate the full strings from the above dictionary:

for key, label in labels.items():
    if key is None:
        labels['negatives'] = label
        continue
    for vehicle in ('cars', 'trucks', 'buses'):
        labels['{}\\{}inclination_{}azimuth'.format(vehicle, *key)] = label

then look up your label directly:

label = labels[view]
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • That was fast! My labels actually vary on a combination of both inclination and azimuth. But yes, transport vehicle doesn't matter. – user961627 Aug 02 '14 at 14:54
0

As some commenters have pointed out, your best solution is to parse the strings more fully, and get at the key information inside them, which will reduce your cases. If for some reason you cannot, you can do this:

CASES = [
    {
        "negatives",
    },
    {
        "cars\\00inclination_000azimuth",
        "buses\\00inclination_000azimuth",
        "trucks\\00inclination_000azimuth",
    },
    {
        "cars\\00inclination_045azimuth",
        "buses\\00inclination_045azimuth",
        "trucks\\00inclination_045azimuth",
        "cars\\00inclination_090azimuth",
        "buses\\00inclination_090azimuth",
        "trucks\\00inclination_090zimuth",
        "cars\\00inclination_135azimuth",
        "buses\\00inclination_135azimuth",
        "trucks\\00inclination_135azimuth",
    },
]

def find_label(view):
    for label, views in enumerate(CASES):
        if view in views:
            return label

Note that I've used sets to speed the view in views check.

Even better, you can pre-process your list of sets into a single dictionary:

CASE_DICT = { view:label for label, views in enumerate(CASES) for view in views }

def find_label(view):
    return CASE_DICT.get(view, None)
Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
0

Other answers here are good, but there's nothing wrong with a dict based lookup table - if you plan to access it often, a bigger dict is faster than preprocessing the string to extract values on each lookup.

def get_label(val):
    label_map = { 
        "negatives": 0,
        "cars\\00inclination_000azimuth": 1,
        "buses\\00inclination_000azimuth": 1,
        "trucks\\00inclination_000azimuth": 1,
        "cars\\00inclination_045azimuth": 2,
        (...etc...)
    }
    return label_map[val]
tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • so how will I use this? simply as follows, is that right: `mylabel = get_label("negatives");` – user961627 Aug 02 '14 at 15:50
  • yes. or you could pull the array out of the function and do `label_map["negatives"]`. - except loose the semicolon on the end. its not needed in python. – tdelaney Aug 02 '14 at 18:16
0

Since there is tremendous overlap in the mapping of vehicle:inclination:azimuth values, you can use intertools product to describe tuples of the overlapping strings to a mapping.

Suppose that you can describe label as '2' for all cases where vehicle is any of ['cars', 'buses', 'trucks'], azimuth is any of ['0'] and inclination is any of ['45', '90', '135'] -- what your example says.

You can do:

from itertools import product
vehicles=['cars', 'buses', 'trucks']

switch={}
switch.update({t:2 for t in product(vehicles, ['0'], ['45', '90', '135'])})
print switch
# {('buses', '0', '90'): 2, ('buses', '0', '45'): 2, ('cars', '0', '135'): 2, ('trucks', '0', '135'): 2, ('trucks', '0', '90'): 2, ('cars', '0', '45'): 2, ('trucks', '0', '45'): 2, ('cars', '0', '90'): 2, ('buses', '0', '135'): 2}

Then it is easy to add addition 'case' values (to borrow the C sense of that) with .update, product and a dict comprehension:

>>> switch.update({t:1 for t in product(vehicles, ['0'], ['0'])})
>>> switch
{('buses', '0', '90'): 2, ('buses', '0', '45'): 2, ('cars', '0', '135'): 2, ('trucks', '0', '135'): 2, ('trucks', '0', '90'): 2, ('cars', '0', '45'): 2, ('trucks', '0', '45'): 2, ('cars', '0', '90'): 2, ('buses', '0', '135'): 2, ('cars', '0', '0'): 1, ('buses', '0', '0'): 1, ('trucks', '0', '0'): 1}

Repeat with each describe-able bucket of combinations of vehicle:inclination:azimuth values.

Then you can assign label directly from the key value of a tuple of the values of interest:

>>> label=switch[('cars', '0', '0')]
>>> label
1

Without having to type them all out.

You can also use .get() to have a default value:

>>> switch.get(('bicycle','0','0'), 'default')
'default'
dawg
  • 98,345
  • 23
  • 131
  • 206