简体   繁体   中英

What's the most pythonic way to check to what particular range (out of many) a number belongs?

I know I can check if a number X belongs to a particular range (out of Y consecutive ranges) by using concatenated if a<X<b elif b<X<C else... but is there a more concise, pythonic way to do so?

There is several way, this one seems not so bad:

def in_range(number : int, arange : range) -> bool:
    return number in arange

ranges = [range(6), range(4, 14), range(10, 20), range(12, 34)]
results = list(filter(lambda arange : in_range(5, arange), ranges))
print(results)

[range(0, 6), range(4, 14)]

You can simplify it like this.

def in_range(number : int, ranges : List[range]) -> List[range]:
    return [arange for arange in ranges if number in arange]

ranges = [range(6), range(4, 14), range(10, 20), range(12, 34)]
print(in_range(6, ranges))

[range(0, 6), range(4, 14)]

You may want to know it on the fly.

def in_range(number : int, start : int, stop : int) -> bool:
    return number in range(start,stop)

Edit 1

You can also used any or want to test it for float, in this case look at @chepner comment.

Use any :

x = 57
ranges = [range(10), range(15,27), range(38,42), range(49, 63), range(70, 95)]
if any(x in range for range in ranges):
    ...

If you can't use range (because you are testing against an interval with non-integer end points), store the interval as a tuple.

ranges = [(1.5, 3.7), ...]
if any(t1 < x < t2 for t1, t2 in ranges):

If your consecutive ranges are dense (meaning there are no gaps in between), I would do

def get_range(boundaries, x):
    for a, b in zip(boundaries, boundaries[1:]):
        if a < x < b:
            return a, b

# Test example
boundaries = [0, 5, 10, 15, 20]
x = 13
a, b = get_range(boundaries, x)
print(a, b)  # 10 15

Note that the exact boundaries are not included in any of the ranges. Ie get_range(boundaries, 10) does not find any range. To fix this, replace a < x < b with eg a <= x < b .

The above is great if you have a lot of ranges, where a hard-coded if - elif chain would be impractical. That said, if you have millions of ranges, you might want to be a bit more clever, like implementing a binary search.

If you are looking to implement the equivalent of a switch statement (there isn't one in Python), you could define a utility function to make the code more concise and readable (ie avoiding repetitions):

def switch(value):
    def inRange(a,b): 
        return a <= value and value < b
    return inRange

number = 25

case = switch(number)
if   case(10,20): print("small")
elif case(20,50): print("medium")
elif case(50,90): print("large")
else:             print("huge")  

you could also use this with ternary operators:

inRange = switch(number)
size    = "small"  if inRange(10,20) else \
          "medium" if inRange(20,50) else \
          "large"  if inRange(50,90) else \
          "huge"

print(size)

If your ranges are consecutive, you could avoid repeating the upper boundaries using a bisecting search (which would give better performance for large lists of ranges):

ranges = [10,20,50,90]
sizes  = ["small","medium","large","huge"]
from bisect import bisect_right
size = sizes[bisect_right(ranges,number)-1]
print(size)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM