I'm refactoring a function that, given a series of endpoints that implicitly define intervals, checks if a number is included in the interval, and then return a corresponding (not related in any computable way). The code that is now handling the work is:
if p <= 100:
return 0
elif p > 100 and p <= 300:
return 1
elif p > 300 and p <= 500:
return 2
elif p > 500 and p <= 800:
return 3
elif p > 800 and p <= 1000:
return 4
elif p > 1000:
return 5
Which is IMO quite horrible, and lacks in that both the intervals and the return values are hardcoded. Any use of any data structure is of course possible.
import bisect
bisect.bisect_left([100,300,500,800,1000], p)
here the docs: bisect
You could try a take on this:
def check_mapping(p):
mapping = [(100, 0), (300, 1), (500, 2)] # Add all your values and returns here
for check, value in mapping:
if p <= check:
return value
print check_mapping(12)
print check_mapping(101)
print check_mapping(303)
produces:
0
1
2
As always in Python, there will be any better ways to do it.
It is indeed quite horrible. Without a requirement to have no hardcoding, it should have been written like this:
if p <= 100:
return 0
elif p <= 300:
return 1
elif p <= 500:
return 2
elif p <= 800:
return 3
elif p <= 1000:
return 4
else:
return 5
Here are examples of creating a lookup function, both linear and using binary search, with the no-hardcodings requirement fulfilled, and a couple of sanity checks on the two tables:
def make_linear_lookup(keys, values):
assert sorted(keys) == keys
assert len(values) == len(keys) + 1
def f(query):
return values[sum(1 for key in keys if query > key)]
return f
import bisect
def make_bisect_lookup(keys, values):
assert sorted(keys) == keys
assert len(values) == len(keys) + 1
def f(query):
return values[bisect.bisect_left(keys, query)]
return f
Try something along the lines of:
d = {(None,100): 0,
(100,200): 1,
...
(1000, None): 5}
value = 300 # example value
for k,v in d.items():
if (k[0] is None or value > k[0]) and (k[1] is None or value <= k[1]):
return v
Another way ...
def which(lst, p):
return len([1 for el in lst if p > el])
lst = [100, 300, 500, 800, 1000]
which(lst, 2)
which(lst, 101)
which(lst, 1001)
def which_interval(endpoints, number):
for n, endpoint in enumerate(endpoints):
if number <= endpoint:
return n
previous = endpoint
return n + 1
Pass your endpoints as a list in endpoints
, like this:
which_interval([100, 300, 500, 800, 1000], 5)
Edit:
The above is a linear search. Glenn Maynard's answer will have better performance, since it uses a bisection algorithm.
来源:https://stackoverflow.com/questions/1199053/python-mapping-from-intervals-to-values