Python: Mapping from intervals to values
Solution 1
import bisect
bisect.bisect_left([100,300,500,800,1000], p)
here the docs: bisect
Solution 2
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.
Solution 3
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
Comments
-
Agos almost 2 years
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.
-
stefanw almost 15 yearsDoes not consider the case of p > 1000!
-
kjfletch almost 15 yearsThat is why I specified: "You could try a take on this"
-
sykora almost 15 yearsThat last sentence is ironic, considering the python philosophy of having preferably only one obvious way to do something.
-
John Machin almost 15 yearsBUG: It produces None if p is greater than the last endpoint.
-
JAB almost 15 yearsI like this one better than the one that has the most votes because of its more generalized/non-hardcoded form and because it is more in-depth.
-
John Machin almost 15 yearsLose the "previous" caper; it's quite redundant.
-
Steef almost 15 yearsYeah, you're right, I guess the original code "inspired" me to use it. BTW, your use of the imperative might sound a bit gruff to some.
-
Agos almost 15 yearsTruly impressive. Super clean, and I believe very fast too. It can also be easily extended in case one does need a non-natural ordering or something else in return, like a string: import bisect n = bisect.bisect_left([100,300,500,800,1000], p) a=["absent","low","average","high", "very high", "extreme"] a[n]
-
John Machin almost 15 years@Steef: You may wish to consider a humble suggestion that you might at your leisure revist your answer, note that your answer still includes a redundant line of code, and in the fullness of time, excise the same.
-
Charlie Parker about 3 yearshmmm but this doesn't return "arbitrary values" it returns the index. How do I have it return the arbitrary value? I tried
p = 10 x = bisect.bisect_left(OrderedDict({10: 'a', 11: 'b'}), p) print()
but it didn't work.