views:

306

answers:

6

I've recently learned that python doesn't have the switch/case statement. I've been reading about using dictionaries in its stead, like this for example:

values = { 
     value1: do_some_stuff1, 
     value2: do_some_stuff2, 
     valueN: do_some_stuffN,
}
values.get(var, do_default_stuff)()

What I can't figure out is how to apply this to do a range test. So instead of doing some stuff if value1=4 say, doing some stuff if value1<4. So something like this (which I know doesn't work):

values = { 
     if value1 <val: do_some_stuff1, 
     if value2 >val: do_some_stuff2, 
}
values.get(var, do_default_stuff)()

I've tried doing this with if/elif/else statements. It works fine but it seems to go considerably slower compared to the situation where I don't need the if statements at all (which is maybe something obvious an inevitable). So here's my code with the if/elif/else statement:

if sep_ang(val1,val2,X,Y)>=ROI :
    main.removeChild(source)
elif sep_ang(val1,val2,X,Y)<=5.0:
    integral=float(spectrum[0].getElementsByTagName("parameter")[0].getAttribute("free"))
    index=float(spectrum[0].getElementsByTagName("parameter")[0].getAttribute("free"))              
    print name,val1,val2,sep_ang(val1,val2,X,Y),integral,index
    print >> reg,'fk5;point(',val1,val2,')# point=cross text={',name,'}'
else:
    spectrum[0].getElementsByTagName("parameter")[0].setAttribute("free","0") #Integral
    spectrum[0].getElementsByTagName("parameter")[1].setAttribute("free","0") #Index
    integral=float(spectrum[0].getElementsByTagName("parameter")[0].getAttribute("free"))
    index=float(spectrum[0].getElementsByTagName("parameter")[0].getAttribute("free"))
    print name,val1,val2,sep_ang(val1,val2,X,Y),integral,index
    print >> reg,'fk5;point(',val1,val2,')# point=cross text={',name,'}'

Which takes close to 5 min for checking about 1500 values of the var sep_ang. Where as if I don't want to use setAttribute() to change values in my xml file based on the value of sep_ang, I use this simple if else:

if sep_ang(val1,val2,X,Y)>=ROI :
    main.removeChild(source)
else:
    print name,val1,val2,ang_sep(val1,val2,X,Y);print >> reg,'fk5;point(',val1,val2,')# point

Which only takes ~30sec. Again I know it's likely that adding that elif statement and changing values of that attribute inevitably increases the execution time of my code by a great deal, I was just curious if there's a way around it.

Edit: Is the benefit of using bisect as opposed to an if/elif statement in my situation that it can check values over some range quicker than using a bunch of elif statements?

It seems like I'll still need to use elif statements. Like this for example:

range=[10,100]
options='abc' 
def func(val)
     return options[bisect(range, val)]
if func(val)=a:
     do stuff
elif func(val)=b:
     do other stuff
else:
     do other other stuff

So then my elif statement are only checking against a single value.

Thanks much for the help, it's greatly appreciated.

+3  A: 

In that case you would use an if/then/else. You cannot do this with a switch, either.

The idea of a switch statement is that you have a value V that you test for identity against N possible outcomes. You can do this with an if-construct - however that would take O(N) runtime on average. The switch gives you constant O(1) every time.

This is obviously not possible for ranges (since they are not easily hashable) and thus you use if-constructs for these cases.

Example

if value1 <val: do_some_stuff1()
elif value2 >val: do_some_stuff2()

Note that this is actually smaller than trying to use a dictionary.

bayer
The problem isn't with ranges being difficult to hash, it's with not knowing which range to look up in your dict when you only have one value within it.
Mike Graham
You might want to look up locality sensitive hashing. That's why I wrote "difficult" and not "impossible".
bayer
LSH doesn't solve this problem.
Mike Graham
So you're saying there is no way to hash values from the same range to the same hash key?
bayer
+4  A: 

Whilst the dictionary approach works well for single values, if you want ranges, if ... else if ... else if is probably the simplest approach.

If you're looking for a single value this a good match to a dictionary - since this is what dictionaries are for - but if you're looking for a range it doesn't work. You could do it with a dict using something like:

values = {
    lambda x: x < 4: foo,
    lambda x: x > 4: bar
}

and then loop through all the key-value pairs in the dictionary, passing your value key and running the value as a function if the key function returns true.

However, this wouldn't give you any benefit over a number of if statements and would be harder to maintain and debug. So don't do it, and just use if instead.

Dave Webb
I agree that, while this is possible, it isn't the right solution. Just use a series of if/else statements. The code will be much easier to understand and maintain.
Bryan Oakley
+8  A: 

A dictionary is the wrong structure for this. The bisect examples show an example of this sort of range test.

Ignacio Vazquez-Abrams
A: 

I don't know of any practicable solution. If you want to go with the guess what it does approach though you could do something like this:

obsure_switch = {
     lambda x: 1<x<6 : some_function,
     ...
}

[action() for condition,action in obscure_switch.iteritems() if condition(var)]
Till Backhaus
That's totally against the idea of gaining O(1) by use of a dictionary.
bayer
+1  A: 

dict is not for doing this (nor is switch!).

A couple posters have suggested a dict with containment functions, but this is not the solution you want at all. It is O(n) (like an if statement), it doesn't really work (because you could have overlapping conditions), is unpredictable (because you do not know what order you will do the loop), and is much less clear than the equivalent if statement. The if statement is probably the way you want to go if you have a short, static-length list of conditions to apply.

If you have tons of conditions or if they could change as a result of your program, you want a different data structure. You could implement a binary tree or keep a sorted list and use the bisect module to find a value associated with the given range.

Mike Graham
A: 

Finally figured out what to do!

So instead of using a bunch of elif statements I did this:

range=[10,100]
options='abc' 
def func(val)
     choose=str(options[bisect(range,val)])
     exec choose+"()"
def a():
      do_stuff
def b():
      do_other_stuff
def c():
      do_other_other stuff

Not only does it work but it goes almost as fast as my original 4 line code where I'm not changing any values of things!

Jamie
Use a list of functions, not `exec`.
Ignacio Vazquez-Abrams