Стиль кода if-else Python для сокращенного кода для округления чисел с плавающей точкой
Есть ли более короткий, более разборчивый стиль кода для решения этой проблемы?
Я пытаюсь классифицировать некоторые значения с плавающей точкой в межрегиональные папки.
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
Ответы
Ответ 1
Конкретное решение
Реального общего решения не существует, но в вашем случае вы можете использовать следующее выражение.
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
.
Ответ 2
Модуль 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()
или что-то подобное), конечно, вам следует это сделать.
Ответ 3
Одно из первых правил с таким блоком кода - всегда делать сравнения в одном направлении. Так что вместо
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 = ''
Это все еще довольно долго, но а) это намного более читабельно; б) он имеет явный код для обработки value < -0.95 or 0.95 <= value
Ответ 4
Вы можете использовать встроенный 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"
Подробнее о round()
Ответ 5
Все ответы вращаются вокруг округления, что в данном случае кажется вполне приемлемым, но просто в качестве аргумента я хотел бы также отметить классное использование словарей на 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
Ответ 6
На самом деле в 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
Ответ 7
from decimal import Decimal
def classify(value):
number = Decimal(value)
result = "%.2f" % (number)
return Decimal(round(float(result), 2))
Ответ 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
это не эффективно ни в коем случае, но это эквивалентно тому, что у вас есть, только короче.
Ответ 9
Вам не нужен 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
с вашей функцией.
Ответ 10
Многие из этих ответов предлагают какое-то округление в качестве решения. К сожалению, существует три проблемы с использованием округления для этой цели, и на момент написания все они стали жертвами как минимум одной.
- Представление десятичных значений с плавающей точкой неточно. Например, поплавок
0.85
фактически является 0.8499999999999999777955395...
.
- round() использует циклическое округление к четному, также известное как научное или банковское округление, а не арифметическое округление, которое многие из нас изучали в школе. Это означает, например, 0,85 раунда до 0,8 вместо 0,9 и 0,25 раунда до 0,2 вместо 0,3.
- очень маленькие отрицательные числа с плавающей запятой (и десятичные дроби) округляются до
-0.0
, а не до 0.0
, как требуется для сопоставления OP.
Все они могут быть решены с помощью модуля 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 фактически округляется от нуля, а не к бесконечности. .quantize
округляет десятичное значение до мест, заданных первым аргументом, и позволяет нам указать метод округления.
Бонус: разделить пополам точки прерывания с помощью range()
Для биссектрисовых решений это создаст точки останова, используемые OP:
from decimal import Decimal
breakpoints = [Decimal('{}e-2'.format(e)) for e in range(-85, 96, 10)]
Ответ 11
Посмотрите на функцию round()
в python. Может быть, вы можете обойтись без if.
С помощью этой функции вы можете указать количество цифр, которое вам нужно сохранить.
Например:
x = round(5.76543, 2)
print(x)
Этот код напечатает 5,77
Ответ 12
Попробуйте что-то вроде этого, если вам не нравятся циклы:
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
программно, а не записывать их все, но вы указали, что в реальной ситуации конечные точки и значения не так просты.
Это поднимает ValueError
, если value
≥ 0,95 (потому что False
не найдено в понимании списка) или если value
& lt; -0.95 (потому что тогда idx
равен 0); в этих случаях оригинальная версия вызывает 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.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']
return next((ts for ts, end in zip(ts_fol, endpts) if value < end), None)
Эта версия возвращает None
, а не вызывает исключения для любого значения за пределами.