Python if-else 代码样式,用于减少舍入浮点数的代码



有没有更短、更清晰的代码风格来解决这个问题? 我正在尝试将一些浮点值分类到区域间文件夹中。

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'
elif value < 0.15 and value >= 0.05:
ts_folder = r'.1'
elif value < 0.25 and value >= 0.15:
ts_folder = r'.2'
elif value < 0.35 and value >= 0.25:
ts_folder = r'.3'
elif value < 0.45 and value >= 0.35:
ts_folder = r'.4'
elif value < 0.55 and value >= 0.45:
ts_folder = r'.5'
elif value < 0.65 and value >= 0.55:
ts_folder = r'.6'
elif value < 0.75 and value >= 0.65:
ts_folder = r'.7'  
elif value < 0.85 and value >= 0.75:
ts_folder = r'.8'
elif value < 0.95 and value >= 0.85:
ts_folder = r'.9'
return ts_folder

具体解决方案

没有包罗万象的解决方案,但在您的情况下,您可以使用以下表达式。

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'

请注意,上述内容仍然不能完全安全,不会产生一些浮点舍入误差。您可以通过手动键入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

bisect 模块将执行正确的查找,以便从断点列表中查找正确的箱。 实际上,文档中的示例正是这样的情况:

bisect() 函数通常用于对数值数据进行分类。此示例使用 bisect() 根据一组有序数字断点查找考试总成绩(例如):85 及以上是"A",75..84 是"B",依此类推。

>>> 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()或类似的东西),您当然应该这样做。

像这样的代码块的第一个规则是始终使比较方向相同。 所以而不是

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'
elif value < 0.15:       ts_folder = r'.1'
elif value < 0.25:       ts_folder = r'.2'
elif value < 0.35:       ts_folder = r'.3'
elif value < 0.45:       ts_folder = r'.4'
elif value < 0.55:       ts_folder = r'.5'
elif value < 0.65:       ts_folder = r'.6'
elif value < 0.75:       ts_folder = r'.7'  
elif value < 0.85:       ts_folder = r'.8'
elif value < 0.95:       ts_folder = r'.9'
else:                    ts_folder = ''

这仍然很长,但是a)它的可读性要高得多;b) 它有明确的代码来处理value < -0.95 or 0.95 <= value

所有答案都围绕着四舍五入,在这种情况下似乎很好,但为了论证,我还想指出字典的一个很酷的python使用,它通常被描述为其他语言开关的替代品,反过来又允许任意值。

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

您可以使用内置的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" 

有关round()的更多信息

实际上在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

把它变成一个循环怎么样?

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

它无论如何都不是有效的,但它相当于你所拥有的,只是更短。

from decimal import Decimal
def classify(value):
number = Decimal(value)
result = "%.2f" % (number)
return Decimal(round(float(result), 2))

你不需要elif value < -0.75 and value >= -0.85:中的and value >= -.85;如果该值不大于或等于-.85,那么你将无法达到ELIF。您也可以通过立即返回每个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与您的函数进行比较。

其中许多答案都建议将某种舍入作为解决方案。不幸的是,为此目的使用舍入存在三个问题,在撰写本文时,所有这些问题都至少是一个问题。

  • 十进制值的浮点表示形式不精确。例如,浮0.85实际上是0.8499999999999999777955395...
  • round() 使用平局四舍五入到偶数,也称为科学或银行家舍入,而不是我们许多人在学校学到的算术四舍五入。这意味着例如 0.85 舍入到 0.8 而不是 0.9,0.25 舍入到 0.2 而不是 0.3。
  • 非常小的负浮点数(和小数)四舍五入为-0.0,而不是按照 OP 映射的要求0.0

这些都可以使用十进制模块解决,尽管没有我想要的那么漂亮:

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实际上从零而不是向无穷大舍入。.quantize将 Decimal 值舍入到第一个参数给出的位置,并允许我们指定舍入方法。

奖励:使用 range() 平分断点

对于平分解决方案,这将生成 OP 使用的断点:

from decimal import Decimal
breakpoints = [Decimal('{}e-2'.format(e)) for e in range(-85, 96, 10)]

看看 python 中的round()函数。也许你可以在没有if的情况下解决它。

使用此功能,您可以指定需要保留的位数。 例如:

x = round(5.76543, 2)
print(x)

该代码将打印 5.77

如果您不喜欢循环,请尝试以下操作:

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', r'.1', r'.2', r'.3', r'.4', r'.5', r'.6', r'.7', r'.8', r'.9'] 
idx = [value >= end for end in endpts].index(False) 
if not idx:
raise ValueError('Value outside of range')
return ts_folder[idx-1] 

当然,循环只是"隐藏"在列表理解中。 显然,在此示例中,最好以编程方式生成endptsts_fol,而不是将它们全部写出来,但你指出在实际情况下,终结点和值并不那么简单。

如果value≥ 0.95(因为在列表理解中找不到False),或者如果value<-0.95(因为idx为 0),这将引发ValueError;在这些情况下,原始版本会引发UnboundLocalError

您还可以保存三行并通过这样做跳过一些比较:

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_fol = [ None, 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', r'.1', r'.2', r'.3', r'.4', r'.5', r'.6', r'.7', r'.8', r'.9']
return next((ts for ts, end in zip(ts_fol, endpts) if value < end), None)

此版本返回None,而不是为边界外的任何值引发异常。

最新更新