1

For this list :

ranges = [[0,100],[100,200],[200,300]]

How to pythonically design the function getIndex() to do this:

getIndex(ranges, 23)  --> 0    23 is in the 1st range
getIndex(ranges, 100) --> 1   100 is in the 2nd range
getIndex(ranges, 188) --> 1   188 is in the 2nd range
getIndex(ranges, 223) --> 2   223 is in the 3rd range
getIndex(ranges, 999) --> -1  Not found

This question probably has been answered but I can't find it.

talonmies
  • 70,661
  • 34
  • 192
  • 269
H.C.Chen
  • 412
  • 4
  • 14

3 Answers3

1

One way using list comprehension:

import numpy as np
ranges = [[0,100],[100,200],[200,300]]

def getIndex(ranges, a):

    r = [i for i,r in enumerate(ranges) if np.logical_and(a>=r[0], a<r[1])]    
    return r[0] if r else -1

for a in [23, 100, 188, 223, 999]:
    print(getIndex(ranges, a))

0
1
1
2
-1
FBruzzesi
  • 6,385
  • 3
  • 15
  • 37
1

Try this.

list=[[0,100],[100,200],[200,300]]

def getIndex(list, num):
    for idx, val in enumerate(list):
        if(num in range(val[0],val[1])):
            return idx
    return -1
ABN
  • 1,024
  • 13
  • 26
  • 1
    range(val[0],val[1]) requires val[0] and val[1] to be integers right? then that's a little limitation. – H.C.Chen Apr 12 '20 at 14:06
  • Yes. Arguments to range constructor must be integers. – ABN Apr 12 '20 at 14:44
  • It also require the value youvpass to be an integer actually, it's a double limitation in this regard. – FBruzzesi Apr 12 '20 at 22:08
  • @H.C.Chen if you also require float range, please check https://stackoverflow.com/questions/477486/how-to-use-a-decimal-range-step-value. Replace checking of number in range with a custom range or third party float range. – ABN Apr 13 '20 at 07:57
  • @ABN That would be a very bad approach in my opinion.First you need to use a machine epsilon step to cover all the numbers, and then you are doing too many comparisons with an "in" statement. In my approach below you just do 2, and don't need to deal with floats differently. – FBruzzesi Apr 13 '20 at 08:50
  • @FBruzzesi you are right. Your implementation does not need to deal with floats differently. It may not matter, but there is an overhead of external libraries. We can make custom range for float by extending collections.abc.Sequence. For comparison we need not generate a list. Thus we can avoid many comparisons with an "in" operator – ABN Apr 13 '20 at 21:10
1

Here's a tricky way to do it without any loops.

def getIndex(ranges, val):
    r = np.where(((ranges-val).prod(axis=1)<0) + (ranges[:,0]==val))[0]
    return r[0] if len(r)!=0 else -1

I will now jokingly call this Mercury's theorem: For any range r defined by (r_start, r_end), if any number n falls within the range, then (r_start-n) and (r_end-n) will have opposing signs! Unless of course, n is equal to r_start, which leads to a non-negative 0. (n=r_end is outside of the range)

Subtract val from ranges and take product over column axis, basically doing (r_start-n)*(r_end-n). This should be negative, except for the boundary case, for which we do an OR (+) with if r_starts == n. Then we simply call np.where on the resulting boolean array. If the value is absent, it returns -1.

ranges = np.array([[0,100],[100,200],[200,300]])
values = [23, 100, 188, 223, 999]

for v in values:
    idx = getIndex(ranges,v)
    print('{} in ({},{})'.format(v,ranges[idx,0],ranges[idx,1]) if idx!=-1 else '{} not found'.format(v))

23 in (0,100)
100 in (100,200)
188 in (100,200)
223 in (200,300)
999 not found
Mercury
  • 3,417
  • 1
  • 10
  • 35