如何找到给定值出现的特定间隔(通过列表),以便从第二个列表中返回相应的值?

时间:2021-11-21 22:47:19

I am writing a python code to act as a gradebook. I have calculated the total number of points earned by each student and am now stuck on the last step of my code - assigning a letter grade to a student. The structure of my code is to define functions that evaluate a single student's performance, so that one "master function" can repeatedly call the functions by an index number corresponding to each student. As such, I was able to produce a working code that I am trying to condense and simplify. I've checked out similar questions in posts like these, but I haven't been able to adapt these approaches to my problem (I think because my for-loop screws up the indexing).

我正在编写一个python代码来充当成绩册。我已经计算了每个学生获得的总分数,现在我已经停留在我的代码的最后一步 - 为学生分配一个字母等级。我的代码结构是定义评估单个学生表现的函数,这样一个“主函数”就可以通过对应每个学生的索引号重复调用函数。因此,我能够生成一个正在尝试压缩和简化的工作代码。我在这些帖子中检查了类似的问题,但是我无法使这些方法适应我的问题(我认为因为我的for-loop搞砸了索引)。

Working code:

import numpy as np

failing_score = 100
perfect_score = 200
letter_grades = ['A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F']
grade_borders = np.linspace(perfect_score, failing_score, len(letter_grades))

print(grade_borders)
>> [ 200.          190.90909091  181.81818182  172.72727273  163.63636364
  154.54545455  145.45454545  136.36363636  127.27272727  118.18181818
  109.09090909  100.        ]

def get_letter_grade(student_grade, grade_borders=grade_borders):
    """
    perfect score = 200, failing score ≤ 100

    190.9 - 200:    A
    181.8 - 190.9:  A-
    172.7 - 181.8:  B+
    etc
    """
    if student_grade >= grade_borders[1]:
        letter = 'A'
    elif grade_borders[2] <= student_grade < grade_borders[1]:
        letter = 'A-'
    elif grade_borders[3] <= student_grade < grade_borders[2]:
        letter = 'B+'
    elif grade_borders[4] <= student_grade < grade_borders[3]:
        letter = 'B'
    elif grade_borders[5] <= student_grade < grade_borders[4]:
        letter = 'B-'
    elif grade_borders[6] <= student_grade < grade_borders[5]:
        letter = 'C+'
    elif grade_borders[7] <= student_grade < grade_borders[6]:
        letter = 'C'
    elif grade_borders[8] <= student_grade < grade_borders[7]:
        letter = 'C-'
    elif grade_borders[9] <= student_grade < grade_borders[8]:
        letter = 'D+'
    elif grade_borders[10] <= student_grade < grade_borders[9]:
        letter = 'D'
    elif grade_borders[11] <= student_grade < grade_borders[10]:
        letter = 'D-'
    elif student_grade < grade_borders[11]:
        letter = 'F'
    return letter

print(get_letter_grade(182)) 
>> A-

I don't like having to specify each index and each letter grade using so many if/elif loops. So I tried to modify my approach by using a for-loop; the values at the boundaries (A and F) are handled separately while the grades in-between can be handled by the same code as functions of index.

我不喜欢使用如此多的if / elif循环来指定每个索引和每个字母等级。所以我试图通过使用for循环来修改我的方法;边界(A和F)处的值是分开处理的,而中间的等级可以用与索引函数相同的代码处理。

Unsuccessful attempt:

def assign_letter_grade(student_score, grade_borders=grade_borders, letter_grades=letter_grades):
    """
    This is my unsuccessful attempt at replicating the function above.
    """
    res = 0 # not including this line ==> UnboundLocalError: local variable 'res' referenced before assignment
    for idx in range(len(grade_borders)):
        if idx == 0:
            if student_score >= grade_borders[1]:
                res = letter_grades[idx]
                print("--", res)
        elif idx > 0 and idx < len(letter_grades)-1:
            if (student_score >= grade_borders[idx+1]) and (student_score < grade_borders[idx]) is True:
                res = letter_grades[idx]
                print("--", res)
        elif idx == len(letter_grades)-1:
            if student_score < grade_borders[len(grade_borders)-1]:
                res = letter_grades[idx]
                print("--", res)
    return res

print(assign_letter_grade(182)) 

Question: What is my mistake in the code just above? Is this a dumb approach (and why /why not?) I've thought about another alternate method that uses a histogram to find the non-zero interval corresponding to a student's grade, but I'm not sure about the efficiency and implementation.

问题:上面的代码中我的错误是什么?这是一种愚蠢的方法(以及为什么/为什么不呢?)我想到了另一种使用直方图找到对应于学生成绩的非零区间的替代方法,但我不确定效率和实施方法。

2 个解决方案

#1


1  

For interval search, you can use numpy's searchsorted. Things will be more smooth if you sort the values from smallest the largest though.

对于区间搜索,您可以使用numpy的searchsorted。如果您将值从最小的值排序,那么情况会更顺利。

letter_grades = np.array(letter_grades[::-1])
grade_borders = grade_borders[::-1]

def assign_letter_grade(student_score, grade_borders=grade_borders, letter_grades=letter_grades):
    return letter_grades[np.searchsorted(grade_borders, student_score)]

A few example on this function:

关于这个功能的一些例子:

assign_letter_grade(95)
Out: 'F'

assign_letter_grade(105)
Out: 'D-'

assign_letter_grade(182)
Out: 'A-'

The same can be done with Python's bisect module.

使用Python的bisect模块也可以做到这一点。

import bisect

bisect.bisect(grade_borders, 182)
Out: 10

letter_grades[bisect.bisect(grade_borders, 182)]
Out: 'A-'

The advantage of the numpy function is that it is vectorized so you can pass an array of scores:

numpy函数的优点是它是矢量化的,因此你可以传递一个分数数组:

assign_letter_grade([95, 110, 135, 157, 189, 198])
Out: 
array(['F', 'D', 'C-', 'B-', 'A-', 'A'], 
      dtype='<U2')

Of course the same can be done with bisect in a loop but numpy's version will probably be faster.

当然,在循环中可以使用bisect完成相同的操作,但是numpy的版本可能会更快。

#2


0  

I was able to adapt the histogram approach, but with a deprecation warning.

我能够调整直方图方法,但有一个弃用警告。

def hlet(student_score, grade_borders=grade_borders):
    bins = grade_borders[::-1]
    interval, _, _ = plt.hist(student_score, bins=bins)
    interval = interval[::-1]
    letter_idx = np.where(interval>0)[0]
    return letter_grades[letter_idx]

print(hlet(182))

>> VisibleDeprecationWarning: converting an array with ndim > 0 to an index will result in an error in the future
  return letter_grades[letter_idx]
A-

While this approach works, I am still curious to see how other approaches attack this problem.

虽然这种方法有效,但我仍然很想知道其他方法如何解决这个问题。

#1


1  

For interval search, you can use numpy's searchsorted. Things will be more smooth if you sort the values from smallest the largest though.

对于区间搜索,您可以使用numpy的searchsorted。如果您将值从最小的值排序,那么情况会更顺利。

letter_grades = np.array(letter_grades[::-1])
grade_borders = grade_borders[::-1]

def assign_letter_grade(student_score, grade_borders=grade_borders, letter_grades=letter_grades):
    return letter_grades[np.searchsorted(grade_borders, student_score)]

A few example on this function:

关于这个功能的一些例子:

assign_letter_grade(95)
Out: 'F'

assign_letter_grade(105)
Out: 'D-'

assign_letter_grade(182)
Out: 'A-'

The same can be done with Python's bisect module.

使用Python的bisect模块也可以做到这一点。

import bisect

bisect.bisect(grade_borders, 182)
Out: 10

letter_grades[bisect.bisect(grade_borders, 182)]
Out: 'A-'

The advantage of the numpy function is that it is vectorized so you can pass an array of scores:

numpy函数的优点是它是矢量化的,因此你可以传递一个分数数组:

assign_letter_grade([95, 110, 135, 157, 189, 198])
Out: 
array(['F', 'D', 'C-', 'B-', 'A-', 'A'], 
      dtype='<U2')

Of course the same can be done with bisect in a loop but numpy's version will probably be faster.

当然,在循环中可以使用bisect完成相同的操作,但是numpy的版本可能会更快。

#2


0  

I was able to adapt the histogram approach, but with a deprecation warning.

我能够调整直方图方法,但有一个弃用警告。

def hlet(student_score, grade_borders=grade_borders):
    bins = grade_borders[::-1]
    interval, _, _ = plt.hist(student_score, bins=bins)
    interval = interval[::-1]
    letter_idx = np.where(interval>0)[0]
    return letter_grades[letter_idx]

print(hlet(182))

>> VisibleDeprecationWarning: converting an array with ndim > 0 to an index will result in an error in the future
  return letter_grades[letter_idx]
A-

While this approach works, I am still curious to see how other approaches attack this problem.

虽然这种方法有效,但我仍然很想知道其他方法如何解决这个问题。