Neil Macneale
Neil Macneale

Reputation: 341

Check which range a number is within

I need to find which range a number, in this case value, is within. I cannot find any other way to set i to what I need other than this:

if value < -64:
  i = 0
elif value < -32:
  i = 1
elif value < -16:
  i = 2
elif value < -8:
  i = 3
elif value < -4:
  i = 4
elif value < -2:
  i = 5
elif value < -1:
  i = 6
elif value < -0.5:
  i = 7
elif value < 0:
  i = 8
elif value < 0.5:
  i = 9
elif value < 1:
  i = 10
elif value < 2:
  i = 11
elif value < 4:
  i = 12
elif value < 8:
  i = 13
elif value < 16:
  i = 14
elif value < 32:
  i = 15
elif value < 64:
  i = 16
else:
  i = 17

This is terrible code and I hate it. Is there any way I can do something like this?

ranges = [-64, -32, -16 ... 32, 64]
i = find_which_range(value, ranges)

Thanks!

Upvotes: 2

Views: 320

Answers (5)

Sheldore
Sheldore

Reputation: 39072

People already provided much shorter (and efficient) solutions. I am just posting here my attempt just to show one more way and logic to approach the problem using list comprehension.

def find_which_range(value, ranges):
    if value <= min(ranges):
        c = 0
    elif value >= max(ranges): 
        c = len(ranges)
    else:
        c = [i+1 for i in range(len(ranges)-1) if ranges[i] <= value <= ranges[i+1]][0]
    return c

value = -9
i = find_which_range(value, ranges) 

Upvotes: 1

modesitt
modesitt

Reputation: 7210

Mathew provided a looping solution that works fine. But also observe that you are checking basically, which powers of 2 your number is between. Thus, you can take the log.

import math
a_value = abs(value)
sign = 1 if value > 0 else -1
lg_value = math.log2(a_value)
range_ = (2**(sign*math.floor(lg_value)), 2**(sign*math.ceil(lg_value)))

note that slight modifications are required for ranges < 1.

Upvotes: 1

Thierry Lathuille
Thierry Lathuille

Reputation: 24288

Use bisect:

import bisect

ranges = [-64, -32, -16, -8, -4, -2, -1, -0.5, 0, 0.5, 1, 2, 4, 8, 16, 32, 64]

print(bisect.bisect(ranges, -65))
# 0

print(bisect.bisect(ranges, -64))
# 1

print(bisect.bisect(ranges, 63))
#16

print(bisect.bisect(ranges, 64))
# 17

bisect.bisect(l, value) returns the index at which value would have to be inserted in l, such that all values to the left are less than value. It will also be fast to search a large list, as it uses a bisection algorithm.

Upvotes: 4

Laurent LAPORTE
Laurent LAPORTE

Reputation: 22992

To find the right range, you can iterate over each range using zip function, like this:

ranges = [-64, -32, -16, -8, -4, -2, -1, -0.5, 0, 0.5, 1, 2, 4, 8, 16, 32, 64]

i = 9

for p, n in zip(ranges[:-1], ranges[1:]):
    if p <= i < n:
        r = p, n
        break
else:
    raise ValueError(i)

print(r)

You get: (8, 16)

Then you can use a mapping between each range and target values, something like this:

mapping = {
  (-64, -32): 1,
  (-32, -16): 2,
  (-16, -8): 3
}

print(mapping[(-32, -16)])
2

Upvotes: 1

Matthew H.
Matthew H.

Reputation: 153

Based off the way you described you could do something like this:

ranges = [-64, -32, -16, -8, -4, -2, -1, -0.5, 0, 0.5, 1, 2, 4, 8, 16, 32, 64]

def build_ranges(power):
    ranges = [-0.5, 0, 0.5]
    for i in range(power):
        ranges.append(2**i)
        ranges.append(-2**i)
    return sorted(ranges)

def find_which_range(value, ranges):
    for i, range in enumerate(sorted(ranges)):
        if value < range:
            return i
    return None

output = find_which_range(value, ranges)

Upvotes: 2

Related Questions