I have a dictionary:
BATTERY_LEVEL_TRANSFORMS = {(75, 101): "High", (30, 75): "Medium", (0, 30): "Low"}
and am trying to set a text indicator based on a battery value. Whichever key it is in the range of, the text will be assigned accordingly. Here's what I have:
for level_range, level_text in BATTERY_LEVEL_TRANSFORMS.items():
if msg in range(*level_range):
batt_level_str = level_text
break
else:
batt_level_str = "Error"
That's adjusted code to make the problem understandable. Is this the proper way to do this? It doesn't seem to be the correct solution, but I can't think of what the correct solution would be (other than the equivalent interval conditionals).
-
If it's possible I would reverse that dictionary. Those keys should be the values.kylieCatt– kylieCatt2016年08月20日 05:40:33 +00:00Commented Aug 20, 2016 at 5:40
-
Why do you think it's incorrect? What is your expected output?zarak– zarak2016年08月20日 05:54:17 +00:00Commented Aug 20, 2016 at 5:54
-
1It works, it just doesn't seem idiomatic. I feel like there is a built in function I'm not using that I should be. Maybe something with sets, I don't know.linus72982– linus729822016年08月20日 05:55:50 +00:00Commented Aug 20, 2016 at 5:55
2 Answers 2
One option would be store the breakpoints between levels and level names to two sorted lists and use bisect.bisect_right
to do binary search on the breakpoints. Benefit of this approach would be that level retrieval would be O(log n) time complexity although it doesn't really matter when you have only couple levels:
from bisect import bisect_right
LEVELS = [0, 30, 75, 101]
TEXTS = ['Low', 'Medium', 'High']
def get_level(num):
index = bisect_right(LEVELS, num) - 1
return TEXTS[index] if 0 <= index < len(TEXTS) else 'Error'
for x in [-1, 0, 29, 30, 74, 75, 100, 101]:
print('{}: {}'.format(x, get_level(x)))
Output:
-1: Error
0: Low
29: Low
30: Medium
74: Medium
75: High
100: High
101: Error
If you need fast retrieval and are willing to utilize more space you could create a dict containing all the valid values so that retrieval would be O(1) time complexity:
BATTERY_LEVEL_TRANSFORMS = {(75, 101): "High", (30, 75): "Medium", (0, 30): "Low"}
LEVEL_MAP = {i: level
for (lo, hi), level in sorted(BATTERY_LEVEL_TRANSFORMS.items())
for i in range(lo, hi)}
def get_level(num):
return LEVEL_MAP.get(num, 'Error')
for x in [-1, 0, 29, 30, 74, 75, 100, 101]:
print('{}: {}'.format(x, get_level(x)))
Output:
-1: Error
0: Low
29: Low
30: Medium
74: Medium
75: High
100: High
101: Error
Feasibility of above approach obviously depends on how many valid values you have.
I actually think your solution is not bad. Here's my variation on it to make it a bit more readable:
BATTERY_LEVELS = (
(0, 30, 'Low'),
(30, 75, 'Medium'),
(75, 101, 'High'),
)
def get_level_text(level):
for low, high, level_text in BATTERY_LEVELS:
if low <= level <= high:
return level_text
return 'Error'
print(get_level_text(20)) # Low
print(get_level_text(40)) # Medium
print(get_level_text(80)) # High
print(get_level_text(120)) # Error
Here are the changes and reasoning behind them:
- Change
BATTERY_LEVEL_TRANSFORMS
, which is a dictionary of tuples to text, to just a tuple of tuples calledBATTERY_LEVELS
. Since this structure doesn't change and tuples are immutable, it seems like a simplification. There's also no need to have a dictionary since we just have bunch of associated data with no clear key. - Change
msg
tolevel
since it seems to be a numeric value. - Now that we have the tuple of tuples, change the loop to have intuitive variable names by using
low, high, level_text
to store the tuple values. - Change the condition to
if low <= level <= high
to check the range in a more readable way.
-
I like that. The level variable was msg because in the actual code, it was a message sent with a pubsub framework. I like the idea of the tuple of tuples rather than dictionary, but I think I'll stick with "in range" because, to my eye, that's more readable than interval conditions. Most people know what you mean when you ask if something is "in range". Thanks!linus72982– linus729822016年08月21日 03:46:29 +00:00Commented Aug 21, 2016 at 3:46
-
Sure :) One caveat is that range(low, high) will generate every integer between low and high and linearly step through them until it finds the target, which is less efficient than a number comparison. So in a way, though
in range(...)
does what you want it to, it does it in a sort of unexpected way. Another caveat is that this also won't work for floating point numbers ;)Karin– Karin2016年08月21日 04:50:38 +00:00Commented Aug 21, 2016 at 4:50 -
1That's a good point. The floats aren't a concern, but if interval comparison is that much more efficient, I might change it. I was under the impression that python3 ranges were more efficient at that sort of thing (Python 2's xrange) in that they didn't generate an entire list, just an iterator. Ahh, I think I just answered my own question. An iterator can't really compare its values without iterating.linus72982– linus729822016年08月21日 04:55:27 +00:00Commented Aug 21, 2016 at 4:55
Explore related questions
See similar questions with these tags.