Необходимо ещё немного доработать предыдущую задачу.
Разработайте методы:
turn()
— поворачивает прямоугольник на 90° по часовой стрелке вокруг его центра;scale(factor)
— изменяет размер в указанное количество раз, тоже относительно центра.
Все вычисления производить с округлением до сотых.
Примечание
Ваше решение должно содержать только классы и функции.
В решении не должно быть вызовов инициализации требуемых классов.
Все результаты вычислений нужно округлить до сотых.
Пример
Ввод
rect = Rectangle((3.14, 2.71), (-3.14, -2.71))
print(rect.get_pos(), rect.get_size(), sep='\n')
rect.turn()
print(rect.get_pos(), rect.get_size(), sep='\n')
Вывод
(-3.14, 2.71)
(6.28, 5.42)
(-2.71, 3.14)
(5.42, 6.28)
Ввод
rect = Rectangle((3.14, 2.71), (-3.14, -2.71))
print(rect.get_pos(), rect.get_size(), sep='\n')
rect.scale(2.0)
print(rect.get_pos(), rect.get_size(), sep='\n')
Вывод
(-3.14, 2.71)
(6.28, 5.42)
(-6.28, 5.42)
(12.56, 10.84)
Решение
Продолжение задач про прямоугольник. Приведу лишь решение для класса, описанного через точку и ширину/высоту. Если интересно, почему и как работают методы изменения размера и поворота, приглашаю попробовать нарисовать их на бумаге и на картинке прописать где какие данные используются. Это будет прекрасная тренировка на то, как можно решать задачи альтернативными от классических формул методами. Зачастую такой путь бывает более простым и коротким, но очень важно следить, чтобы он оставался математически и логически безупречным, иначе можно породить алгоритм, который будет будет содержать ошибку и который будет очень сложно отладить в силу его кажущейся очевидности.
Обратите внимание, что мы теперь не просто используем класс Point, а наследуем от него наш прямоугольник.
Посмотреть код
Решение
class Point:
def __init__(self, x, y):
self.x = round(x, 2)
self.y = round(y, 2)
self.round()
def round(self):
self.x = round(self.x, 2)
self.y = round(self.y, 2)
return self
class Rectangle(Point):
def __init__(self, corner1, corner2):
self.x = min(corner1[0], corner2[0])
self.y = max(corner1[1], corner2[1])
self.width = round(max(corner1[0], corner2[0]) - self.x, 2)
self.height = round(self.y - min(corner1[1], corner2[1]), 2)
def perimeter(self):
return round((self.width + self.height) * 2, 2)
def area(self):
return round(self.width * self.height, 2)
def get_pos(self):
return self.x, self.y
def get_size(self):
return self.width, self.height
def move(self, dx, dy):
self.x += dx
self.y += dy
self.round()
def resize(self, width, height):
self.width = round(width, 2)
self.height = round(height, 2)
def turn(self):
delta = round((self.width - self.height) / 2, 2)
self.move(delta, delta)
self.height, self.width = self.width, self.height
def scale(self, ratio):
dx = round((self.width * (ratio - 1)), 2)
dy = round((self.height * (ratio - 1)), 2)
self.move(-dx / 2, dy / 2)
self.resize(self.width * ratio, self.height * ratio)
Просто ушлепская задача, с округлением. Это просто чудо что она прошла тесты!
С одной стороны, требования к округлениям в промежуточных вычислениях действительно странные. Так как один и те же операции с прямоугольником могут быть выполнены разными способами, то округления промежуточных значений могут давать разные результаты в зависимости от “дорожной карты решения” и приводить в результате к накоплению ошибки.
С другой стороны, Яндекс в этом задании заказчик. и бывает, что заказчик хочет странного и приходится этому странному следовать. Или отказываться от выполнения работы.
абсолютно с тобой с тобой согласен. Способов может быть много и зачем каждое действие округлять непонятно. Гораздо точнее производить вычисления и хранить их с точностью который позволяет тип float в python а результат выводить округленным.
Без класса Point
class Rectangle:
def __init__(self, corner1, corner2):
self.x, self.y = round(min(corner1[0], corner2[0]), 2), round(min(corner1[1], corner2[1]), 2)
self.width, self.height = round(abs(corner1[0] – corner2[0]), 2), round(abs(corner1[1] – corner2[1]), 2)
def perimeter(self):
return round((self.width + self.height) * 2, 2)
def area(self):
return round(self.width * self.height, 2)
def get_pos(self):
return self.x, self.y + self.height
def get_size(self):
return self.width, self.height
def move(self, dx, dy):
self.x, self.y = round(self.x + dx, 2), round(self.y + dy, 2)
def resize(self, width, height):
self.width, self.height = round(width, 2), round(height, 2)
def turn(self):
cx, cy = self.x + self.width / 2, self.y + self.height / 2
self.width, self.height = self.height, self.width
self.x, self.y = round(cx – self.width / 2, 2), round(cy – self.height / 2, 2)
def scale(self, ratio):
cx, cy = self.x + self.width / 2, self.y + self.height / 2
self.width, self.height = round(self.width * ratio, 2), round(self.height * ratio, 2)
self.x, self.y = round(cx – self.width / 2, 2), round(cy – self.height / 2, 2)
Спасибо за решение. Описал класс Rectangle как у вас во втором решении, но без использования класса Point.
class Rectangle:
def __init__(self, point1, point2):
self.position = [min(point1[0], point2[0]), max(point1[1], point2[1])]
self.width= abs(point1[0] – point2[0])
self.height= abs(point1[1] – point2[1])
И думаю, что округлять лучше в методах класса при выводе ответа, иначе все операции будут производиться уже с округлёнными данными (длиной и высотой прямоугольника), что будет давать лишнюю погрешность.
Когда-то давно у яндекса был форум посвященный обсуждению задач. Собственно его удаление и послужило причиной к созданию этого ресурса.
Так вот, при обсуждении этой задачи, яндексы активно настаивали, что округлять надо после каждой операции. При написании кода для решебника это автоматом привело к округлению по накатанной.
А так, я с вам абсолютно согласен. Сейчас посмотрел на код и судя по всему они действительно дают на вход только две точки после запятой, иначе одно из решений часть тестов просто бы не проходило.
Класс Point во втором решении используется просто для того, чтобы дополнительно пояснить как все устроено в ООП и показать почему иногда бывает необходимо “перегрузить” метод родителя.
Установка ТЗ “Округлять в каждом действии” не является корректной, пока в ТЗ действия не будут описаны.
Я могу сделать периметр:
P = (a + b) * 2,
P = 2*a + 2*b
P = a + a + b + b
Все эти варианты являются математически однозначными, и даже, я бы сказал, от ситуации и языка зависит, какой лучше, ибо, например, 3 сложения быстрее, чем 2 умножения и сложение.
И это самый простой пример, конечно, здесь сложение и умножение не меняют точности, однако суть проблемы обозначена прекрасно.
Если ТЗ не говорит о том, что величину надо считать по конкретной формуле без права на математические преобразования, утверждение “округлять при каждом действии” просто бред.
Может ли кто-то дать подсказку? Первые два теста проходит, в третьем выдает ошибку.
class Rectangle:
def __init__(self, corner_1: tuple, corner_2: tuple):
self.corner_1 = corner_1
self.corner_2 = corner_2
self.left_x = min(self.corner_1[0], self.corner_2[0])
self.left_y = max(self.corner_1[1], self.corner_2[1])
self.width = abs(self.corner_2[0] – self.corner_1[0])
self.length = abs(self.corner_2[1] – self.corner_1[1])
self.x_center = round(self.left_x + self.width / 2.0, 2)
self.y_center = round(self.left_y – self.length / 2.0, 2)
def get_size(self):
return tuple((round(self.width, 2), round(self.length, 2)))
def move(self, dx, dy):
self.left_x += dx
self.left_y += dy
def get_pos(self):
return tuple((round(self.left_x, 2), round(self.left_y, 2)))
def resize(self, width, height):
self.width = width
self.length = height
def perimeter(self):
return round((self.width + self.length) * 2, 2)
def area(self):
return round(self.width * self.length, 2)
def turn(self):
width = self.length
length = self.width
self.left_x = self.x_center – width / 2.0
self.left_y = self.y_center + length / 2.0
self.length = length
self.width = width
def scale(self, factor):
self.width *= factor
self.length *= factor
self.left_x = self.x_center – self.width / 2.0
self.left_y = self.y_center + self.length / 2.0
у вас иногда теряется точность
bench – тестовый расчет
sample – ваш результат
например при таких данных в операции поворот
benchmark: (-4.86, 9.98) (0.75, 14.05)
sample : (-4.86, 9.98) (0.75, 14.05)
turn bench : (-11.51, 3.33) (14.05, 0.75)
turn sample: (-11.52, 3.33) (14.05, 0.75)
benchmark: (-6.36, 6.73) (13.67, 13.28)
sample : (-6.36, 6.73) (13.67, 13.28)
turn bench : (-6.16, 6.93) (13.28, 13.67)
turn sample: (-6.17, 6.92) (13.28, 13.67)
или при таких при изменении размера
benchmark: (-8.63, -2.22) (9.28, 4.81)
sample : (-8.63, -2.22) (9.28, 4.81)
scale to: 8.04
scale bench : (-41.3, 14.71) (74.61, 38.67)
scale sample: (-41.3, 14.72) (74.61, 38.67)
benchmark: (-8.57, 7.18) (4.69, 10.81)
sample : (-8.57, 7.18) (4.69, 10.81)
scale to: 6.57
scale bench : (-21.63, 37.28) (30.81, 71.02)
scale sample: (-21.63, 37.29) (30.81, 71.02)
Вы правы, проблема оказалось в том, что я не округлял вот здесь:
self.width *= factor
self.length *= factor
Спасибо!