是否有更短,更清晰的代码样式来解决此问题? 我正在尝试将一些float值分类为区域间文件夹。
def classify(value):
if value < -0.85 and value >= -0.95:
ts_folder = r'\-0.9'
elif value < -0.75 and value >= -0.85:
ts_folder = r'\-0.8'
elif value < -0.65 and value >= -0.75:
ts_folder = r'\-0.7'
elif value < -0.55 and value >= -0.65:
ts_folder = r'\-0.6'
elif value < -0.45 and value >= -0.55:
ts_folder = r'\-0.5'
elif value < -0.35 and value >= -0.45:
ts_folder = r'\-0.4'
elif value < -0.25 and value >= -0.35:
ts_folder = r'\-0.3'
elif value < -0.15 and value >= -0.25:
ts_folder = r'\-0.2'
elif value < -0.05 and value >= -0.15:
ts_folder = r'\-0.1'
elif value < 0.05 and value >= -0.05:
ts_folder = r'\0.0'
elif value < 0.15 and value >= 0.05:
ts_folder = r'\0.1'
elif value < 0.25 and value >= 0.15:
ts_folder = r'\0.2'
elif value < 0.35 and value >= 0.25:
ts_folder = r'\0.3'
elif value < 0.45 and value >= 0.35:
ts_folder = r'\0.4'
elif value < 0.55 and value >= 0.45:
ts_folder = r'\0.5'
elif value < 0.65 and value >= 0.55:
ts_folder = r'\0.6'
elif value < 0.75 and value >= 0.65:
ts_folder = r'\0.7'
elif value < 0.85 and value >= 0.75:
ts_folder = r'\0.8'
elif value < 0.95 and value >= 0.85:
ts_folder = r'\0.9'
return ts_folder
答案 0 :(得分:44)
没有真正的通用解决方案,但是在您的情况下,您可以使用以下表达式。
ts_folder = r'\{:.1f}'.format(round(value, 1))
如果您实际上需要某种概括,请注意任何非线性模式都会引起麻烦。虽然,有一种方法可以缩短代码。
def classify(key, intervals):
for lo, hi, value in intervals:
if lo <= key < hi:
return value
else:
... # return a default value or None
# A list of tuples (lo, hi, key) which associates any value in the lo to hi interval to key
intervals = [
(value / 10 - 0.05, value / 10 + 0.05, r'\{:.1f}'.format(value / 10))
for value in range(-9, 10)
]
value = -0.73
ts_folder = classify(value, intervals) # r'\-0.7'
请注意,对于某些float rounding error,以上内容仍不完全安全。您可以通过手动键入intervals
列表而不是使用理解来提高精度。
如果您的数据中的间隔是连续的,那么它们之间就没有间隙,如您的示例所示,那么我们可以使用一些优化方法。即,我们只能在列表中存储每个间隔的上限。然后,通过对它们进行排序,我们可以使用bisect
进行有效的查找。
import bisect
def value_from_hi(hi):
return r'\{:.1f}'.format(hi - 0.05)
def classify(key, boundaries):
i = bisect.bisect_right(boundaries, key)
if i < len(boundaries):
return value_from_hi(boundaries[i])
else:
... # return some default value
# Sorted upper bounds
boundaries = [-0.85, -0.75, -0.65, -0.55, -0.45, -0.35, -0.25, -0.15, -0.05,
0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95]
ts_folder = classify(-0.32, boundaries) # r'\-0.3'
重要说明:之所以选择使用较高的界限和bisect_right
是因为在示例中排除了较高的界限。如果排除了下限,则我们必须将其与bisect_left
一起使用。
还请注意,您可能希望以某些特殊方式处理[-0.95、0.95 []范围以外的数字,并注意将其保留为bisect
。
答案 1 :(得分:25)
bisect模块将执行正确的查找,以便从断点列表中找到正确的bin。实际上,documentation中的示例就是这样的情况:
bisect()函数通常可用于对数字数据进行分类。本示例使用bisect()根据一组有序的数字断点来查找考试总计(例如)的字母等级:85及以上为'A',75..84为'B',依此类推。< / p>
>>> grades = "FEDCBA"
>>> breakpoints = [30, 44, 66, 75, 85]
>>> from bisect import bisect
>>> def grade(total):
... return grades[bisect(breakpoints, total)]
>>> grade(66)
'C'
>>> map(grade, [33, 99, 77, 44, 12, 88])
['E', 'A', 'B', 'D', 'F', 'A']
您想要一个字符串列表,而不是用于值查找的字符串,该字符串列表用于每个值范围的确切文件夹名称。例如:
breakpoints = [-0.85, -0.75, -0.65]
folders = [r'\-0.9', r'\-0.8', r'\-0.7']
foldername = folders[bisect(breakpoints, -0.72)]
当然,如果您可以自动执行此表生成的一部分(使用round()
或类似方法),
答案 2 :(得分:16)
带有这样的代码块的第一条规则之一是,始终使比较朝同一方向。因此,而不是
elif value < -0.75 and value >= -0.85:
写
elif -0.85 <= value and value < -0.75:
在这一点上,您可以观察到python允许链接比较,因此您可以编写:
elif -0.85 <= value < -0.75:
这本身就是一种进步。另外,您可以观察到这是比较的有序列表,因此,如果添加初始比较,则只需编写
if value < -0.95: ts_folder = ''
elif value < -0.85: ts_folder = r'\-0.9'
elif value < -0.75: ts_folder = r'\-0.8'
elif value < -0.65: ts_folder = r'\-0.7'
elif value < -0.55: ts_folder = r'\-0.6'
elif value < -0.45: ts_folder = r'\-0.5'
elif value < -0.35: ts_folder = r'\-0.4'
elif value < -0.25: ts_folder = r'\-0.3'
elif value < -0.15: ts_folder = r'\-0.2'
elif value < -0.05: ts_folder = r'\-0.1'
elif value < 0.05: ts_folder = r'\0.0'
elif value < 0.15: ts_folder = r'\0.1'
elif value < 0.25: ts_folder = r'\0.2'
elif value < 0.35: ts_folder = r'\0.3'
elif value < 0.45: ts_folder = r'\0.4'
elif value < 0.55: ts_folder = r'\0.5'
elif value < 0.65: ts_folder = r'\0.6'
elif value < 0.75: ts_folder = r'\0.7'
elif value < 0.85: ts_folder = r'\0.8'
elif value < 0.95: ts_folder = r'\0.9'
else: ts_folder = ''
这仍然很长,但是a)可读性更高; b)它具有处理value < -0.95 or 0.95 <= value
答案 3 :(得分:11)
您可以使用内置的round()
:
ts_folder = "\\" + str(round(value + 1e-16, 1)) # To round values like .05 to .1, not .0
if ts_folder == r"\-0.0": ts_folder = r"\0.0"
答案 4 :(得分:11)
所有答案都围绕四舍五入,这在这种情况下似乎很好,但仅出于论证的目的,我还要指出一种很酷的python字典用法,通常被描述为其他语言切换的替代方法(es),并允许任意值。
ranges = {
(-0.85, -0.95): r'\-0.9',
(-0.75, -0.85): r'\-0.8',
(-0.65, -0.75): r'\-0.7',
(-0.55, -0.65): r'\-0.6'
...
}
def classify (value):
for (ceiling, floor), rounded_value in ranges.items():
if floor <= value < ceiling:
return rounded_value
输出:
>>> classify(-0.78)
\-0.8
答案 5 :(得分:5)
实际上,在Python 3中,.85
将舍入到.8
。根据问题,.85
应该四舍五入到.9
。
您可以尝试以下方法吗?
round2 = lambda x, y=None: round(x+1e-15, y)
ts_folder = r'\{}'.format(str(round2(value, 1)))
输出:
>>> round2(.85, 1)
0.9
>>> round2(-.85, 1)
-0.8
答案 6 :(得分:3)
from decimal import Decimal
def classify(value):
number = Decimal(value)
result = "%.2f" % (number)
return Decimal(round(float(result), 2))
答案 7 :(得分:3)
如何将其变成循环?
def classify(value):
i = -5
while i < 95:
if value < (i + 10) / 100.0 and value >= i / 100.0:
return '\\' + repr((i + 5) / 100.0)
i += 10
这绝不是有效的方法,但是它等同于您拥有的,只是更短。
答案 8 :(得分:2)
您不需要and value >= -.85
中的elif value < -0.75 and value >= -0.85:
;如果该值不大于-.85,则不会到达省略号。您也可以通过让每个elif
立即返回来将所有if
变成def classify(value):
if value < -.05:
if value < -.45:
if value < -.65:
if value < -.85:
if value < -.95:
return None
return r'\-0.9'
if value < -.75:
return r'\-0.8'
return r'\-0.7'
...
。
在这种情况下,由于边界的间隔是固定的,因此您可以四舍五入(在常规间隔的一般情况下,您可能必须先分割然后四舍五入,例如,如果间隔是每三个单位,则您将数字除以三并四舍五入)。在一般情况下,将边界存储在树形结构中,然后对项目的去向进行二进制搜索会更快。
明确地执行二进制搜索将是这样的:
(sorted([(_-9.5)/10 for _ in range(20)]+[x]).index(x)-9)/10
尽管此代码比您的代码更难读,但相对于边界数量,它的运行时间是对数而不是线性。
如果项目数明显大于边界数,那么实际上创建项目树并插入边界可能会更快。
您还可以创建一个列表,对其进行排序,然后查看索引。例如,将{{1}}与您的函数进行比较。
答案 9 :(得分:2)
许多这些答案都提出了一种四舍五入的解决方案。不幸的是,为此目的使用舍入存在三个问题,在撰写本文时,它们都成为至少一个的牺牲品。
0.85
实际上是0.8499999999999999777955395...
。 -0.0
,而不是OP映射所需的0.0
。这些都可以使用Decimal模块来解决,尽管不像我想要的那样精致:
from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN
def classify(value):
number = Decimal('{:.2f}'.format(value))
if number < 0:
round_method = ROUND_HALF_DOWN
else:
round_method = ROUND_HALF_UP
rounded_number = number.quantize(Decimal('0.1'), rounding=round_method)
if rounded_number == 0.0:
rounded_number = Decimal('0.0')
return r'\{}'.format(rounded_number)
ROUND_HALF_DOWN和ROUND_HALF_UP都是必需的,因为ROUND_HALF_UP实际上将从零舍入,而不是向Infinity取整。 .quantize
将十进制值舍入到第一个参数给出的位置,并允许我们指定舍入方法。
对于bisect解决方案,这将生成OP使用的断点:
from decimal import Decimal
breakpoints = [Decimal('{}e-2'.format(e)) for e in range(-85, 96, 10)]
答案 10 :(得分:1)
看看python中的round()
函数。也许不用if就可以解决问题。
使用此功能,您可以指定需要保留的位数。 例如:
x = round(5.76543, 2)
print(x)
该代码将显示5.77
答案 11 :(得分:1)
如果您不喜欢循环,请尝试如下操作:
def classify(value):
endpts = [-0.95, -0.85, -0.75, -0.65, -0.55, -0.45, -0.35, -0.25, -0.15, -0.05, 0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95]
ts_folder = [ r'\-0.9', r'\-0.8', r'\-0.7', r'\-0.6', r'\-0.5', r'\-0.4', r'\-0.3', r'\-0.2', r'\-0.1', r'\0.0', r'\0.1', r'\0.2', r'\0.3', r'\0.4', r'\0.5', r'\0.6', r'\0.7', r'\0.8', r'\0.9']
idx = [value >= end for end in endpts].index(False)
if not idx:
raise ValueError('Value outside of range')
return ts_folder[idx-1]
当然,循环只是列表理解中的“隐藏”。
显然,在此示例中,最好以编程方式生成endpts
和ts_fol
而不是全部写出,但您指出,在实际情况下,端点和值并不是那么简单。 / p>
如果ValueError
≥0.95(因为在列表推导中找不到value
)或False
<-0.95(因为{{1 }}是0);在这种情况下,原始版本会引发value
。
您还可以保存三行,并跳过一些比较:
idx
此版本返回UnboundLocalError
,而不是引发超出范围的任何值的异常。