-1

I want to return a string according to a set of ints. such as:
A: 0-9
B: 10-19
C: 20-49
D: 50-100

If I give an int 13, it's between 10 to 19 so it should return "B".

If given an int 31, it should return "C" because 31 is between 20 to 49.

Is there a better than below if statements?

def func(x):
    if 0<=x<=9:
        return "A"
    elif 10<=x<=19:
        return "B"
    ...
yudhiesh
  • 6,383
  • 3
  • 16
  • 49
  • 3
    For something that short, an `elif` chain is a fine way to go. You could do something with the `bisect` module for more complex cases. – user2357112 Oct 22 '20 at 06:14
  • 1
    There is a simple explanation, is there a pattern? If a pattern is present, then it's likely you can avoid a thread of `elif` statements, If these numbers are **not** based on any patterns, then yes `elif` is your choice. –  Oct 22 '20 at 06:18
  • Is just an example or are these the actual values? –  Oct 22 '20 at 06:24
  • @user2357112supportsMonica, bisect is the good one! thank you! need to start learning all the standard modules. – SentimentalK Oct 22 '20 at 07:09

3 Answers3

2

Solution

The following solution uses a list-comprehension + dict to produce what you need. However, if you have many values, you may also want to check that each value is getting correctly classified. For that purpose, I would suggest you to use a dict instead.

Code: Example

# define rules as a dict
rules = {
    'A': [0, 9],
    'B': [10, 19],
    'C': [20, 49],
    'D': [50, 100], 
}

# use a list comprehension: one-line solution
[k for k, v in rules.items() if (min(v) <= x <= max(v))]

Code: output a list of dicts

This is a quick sanity-check.

values = [0, 8, 9, 12, 15, 19, 20, 30, 45, 49, 50, 60, 80, 100]
[dict((x, k) for k, v in rules.items() if (min(v) <= x <= max(v))) for x in values]

Output:

[{0: 'A'},
 {8: 'A'},
 {9: 'A'},
 {12: 'B'},
 {15: 'B'},
 {19: 'B'},
 {20: 'C'},
 {30: 'C'},
 {45: 'C'},
 {49: 'C'},
 {50: 'D'},
 {60: 'D'},
 {80: 'D'},
 {100: 'D'}]

An alternative: using pandas library

# Dummy data
values = [0, 8, 9, 12, 15, 19, 20, 30, 45, 49, 50, 60, 80, 100]
df = pd.DataFrame({'data': values})

# Method-1: "df.assign" + "pd.cut"
df.assign(Rule = pd.cut(df.data, bins = [0, 10, 20, 50, 100], labels = list('ABCD'), include_lowest=True))

# Method-2: just "pd.cut"
df['Rule'] = pd.cut(df.data, bins = [0, 10, 20, 50, 100], labels = list('ABCD'), include_lowest=True)

# Method-3:
pd.cut(values, bins = [0, 10, 20, 50, 100], labels = list('ABCD'), include_lowest=True)
CypherX
  • 7,019
  • 3
  • 25
  • 37
  • 1
    well it isn't a clear victory, since you have basically just moved code, and used more memory –  Oct 22 '20 at 06:31
  • Thank you, this is good idea, it can create a new dictionary actually, not a list but we can build a dictionary-like {0:"A",8:"A",9:"A",12:"B" ...} so when given an int we can just call the dict with the int to get the string. but there is a better way. i just learned with “bisect” module. so we can having an array with the rule, like Import bisect; x = [10,20,50,101]; bisect.bisect(x,12) It will return 1, which means it’s B. It inserting 12 into index 1. – SentimentalK Oct 22 '20 at 07:17
  • That's nice. I haven't yet used the `bisect` module. There is another alternative: **`pandas.cut()`**. – CypherX Oct 22 '20 at 07:54
1

Using a block of if/elif statements is intuitive and easy. Using elif allows you to simplify the statements somewhat:

def fun(x):
    if x < 0 or x > 100:
        raise Exception("What???")
    elif x < 10:
         return "A"
    elif x < 20:
         return "B"
    elif x < 50:
         return "C"
    else:
         return "D"
    
Michael
  • 2,344
  • 6
  • 12
  • 1
    `else/elif` after `raise/return` is not required. Explained here: https://stackoverflow.com/questions/63755912/why-does-pylint-complain-about-unnecessary-elif-after-return-no-else-return – VPfB Oct 22 '20 at 07:30
0

You can use a dict with some math hacks to achieve a pretty clean solution with if/else-

import math

d = {
    range(0, 10): 'A',
    range(10, 20): 'B',
    range(20, 30): 'C',
    range(30, 40): 'C',
    range(40, 50): 'C',
    range(50, 60): 'D',
    range(60, 70): 'D',
    range(70, 80): 'D',
    range(80, 90): 'D',
    range(90, 100): 'D'
}

def func(x):
    minrange = (x // 10) * 10
    maxrange = math.ceil(x / 10) * 10
    return d[range(minrange, maxrange)]

This relies on the fact that all the numbers will be between 0 and 100. Notice if you divide 17 by 10 and floor it (aka integer division) you get 1, multiply it back by 10 and you get 10, which is the minimum range. Now ceil the result of 17 / 10 and you get 2, multiply by 10 and you get 20 - which is the maximum range. So you get range(10, 20) which is the key for 'A' - boom!

This would be better suited with equally distributed ranges of course.

Chase
  • 5,315
  • 2
  • 15
  • 41