Лекция №8. Геометрические задачи

Хорошевич Павел Александрович

Классы в Python

Объекты и классы

Все значения в Python являются объектами определённых классов. Чтобы узнать экземпляром какого класса является значение, используем функцию type().

print(type(42))
print(type(3.14))
print(type("Python"))
print(type([1, 1, 2, 3, 5]))

Даже функции являются объектами класса function:

def hello():
    print("Привет, мир!")
    
print(type(hello))

Описание класса

Любой объект хранит в себе набор членов класса. К ним относятся поля - состояние объекта и методы - функции, которые меняют состояние объект.

Опишем класс Point для представление точки на плоскости.

  • Какими полями будет обладать этот класс?
  • Какие методы будут в этом классе?

Описание простейшего класса:

class Point:
    pass # пустой класс

Создание объекта (экземпляра класса):

a = Point()

Для обращения к полям и методам класса используется оператор . (точка):

a.x =  4.3  # добавляем поле x
a.y = -2.1  # добавляем поле y

# получаем значения из полей x и y
print(a.x, a.y)

Визуализация

Конструктор класса

Объявим функцию init(), которая в качестве аргументов будет принимать объект типа Point и значения полей x и y.

class Point:
    pass

def init(p, x, y):
    # задаём начальные значения полей
    p.x = x
    p.y = y
    
b = Point()     # создаём новый объект типа Point
init(b, 3, 5)   # инициализируем начальные значения полей
print(b.x, b.y)

Инициализация объекта может быть размещена в теле класса. Для этого используется метод с именем __init__().

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

Первый параметр метода __init()__ будет ссылкой на инициализируемый объект.

Этот параметр может иметь произвольное имя, но принято называть его self.

Методы класса

Опишем ещё одну функцию dist(p1, p2) с помощью которой рассчитаем расстояние между точками p1 и p2.

def dist(p1, p2):
    return ((p1.x - p2.x) ** 2 +
            (p1.y - p2.y) ** 2) ** 0.5

t1 = Point(0, 4)
t2 = Point(3, 0)
d = dist(t1, t2) # вызов функции, сохранение результата в переменную
print(d)

Функцию dist(p1, p2) так же можно оформить как метод класса Point.

Первый параметр любого метода класса - это ссылка на объект, который вызвал метод.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def dist(self, p2):
        return ((self.x - p2.x) ** 2 +
                (self.y - p2.y) ** 2) ** 0.5

t1 = Point(0, 4)
t2 = Point(3, 0)
d = t1.dist(t2)
print(d)
t1 = Point(0, 4)
t2 = Point(3, 0)

d = Point.dist(t1, t2)
print(dist)

Специальные методы класса

Специальные методы добавляют классу поведение, аналогичное встроенным в Python классам.

  • __str__(self) - вызывается при преобразовании объекта в строковый тип.
  • __add__(self, other) - вызывается при использовании оператора +.
  • __eq__(self, other) - вызывается при использовании оператора ==.
  • . . .1

Преобразование точки в строку:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __str__(self):
        return f"x:{self.x}, y:{self.y}"

t1 = Point(0, 4)
print(t1)

Реализация оператора == для двух точек:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __eq__(self, p2):
        return self.x == p2.x and self.y == p2.y

t1 = Point(0, 4)
t2 = Point(1 - 1, 2 + 2)
print(t1 == t2)

Примеры задач

Векторное произведение

Векторное произведение \(\vec{a} \times \vec{b}\) двух векторов \(\vec{a}=(x_1, y_1)\) и \(\vec{b}=(x_2, y_2)\) применятся в ряде алгоритмов.

Зачастую для решения задач будет достаточно вычислить значение координаты \(z\) результирующего вектора.

В случае векторного умножения векторов лежащих в плоскости \(XY\), значение координаты \(z\) вычисляется следующим образом1: \[ x_1y_2-x_2y_1 \]

С помощью векторного произведения можем определить направление поворота вектора \(\vec{b}\) относительно вектора \(\vec{a}\):

Направление

Направление

Класс Vector2

Создадим класс Vector2 на основе класса Point с помощью наследования.

Разместим в классе метод для вычисления векторного произведения:

class Vector2(Point):
    def cross_prod(self, v2):
        return self.x * v2.y - self.y * v2.x

Родительский класс указывается в круглых скобках после имени класса.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
class Vector2(Point):
    def cross_prod(self, v2):
        return self.x * v2.y - self.y * v2.x
    
a = Vector2(4, 3)
b = Vector2(1, 3)
print(a.cross_prod(b))

Сложение и вычитание векторов

Добавим в класс Vector2 методы для сложения и вычитания векторов.

class Vector2(Point):
    ...
    def __add__(self, v2):
        return Vector2(self.x + v2.x, self.y + v2.y)
    
    def __sub__(self, v2):
        return Vector2(self.x - v2.x, self.y - v2.y)

Методы __add__() и __sub__() возвращают новый экземпляр класса Vector2.

Примеры задач с векторами

Положение точки относительно прямой

Даны точки \(s_1\) и \(s_2\). Определить с какой стороны от вектора \(\vec{s_1 s_2}\) находится точка \(p\).

Положение точки относительно прямой

Положение точки относительно прямой

Положение определим, вычислив векторное произведение \((p - s_1) \times (p - s_2)\). Если оно положительно, то \(p\) слева от прямой, если отрицательно - справа, равно нулю - все точки лежат на одной прямой.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __str__(self):
        return f"x:{self.x}, y:{self.y}"
        
class Vector2(Point):
    def cross_prod(self, v2):
        return self.x * v2.y - self.y * v2.x
    
    def __add__(self, v2):
        return Vector2(self.x + v2.x, self.y + v2.y)
    
    def __sub__(self, v2):
        return Vector2(self.x - v2.x, self.y - v2.y)
    
s1 = Vector2(2, 1)
s2 = Vector2(6, 3)
p = Vector2(2, 3)
print((p - s1).cross_prod(p - s2))

Расстояние от точки до прямой

Площадь треугольника можно вычислить с помощью векторного произведения: \[ S=\frac{\left | \vec{AC} \times \vec{AB} \right |}{2} \] где \(A\), \(B\) и \(C\) - координаты вершин.

Добавим в класс Vector2 метод length() для вычисления длины вектора:

class Vector2(Point):
    ...
    def length(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5

Расстояние до прямой

Расстояние до прямой

Кратчайшее расстояние \(d\) от точки \(p\) до прямой, проходящей через точки \(s_1\) и \(s_2\) можно вычислить по следующей формуле: \[ d=\frac{(s_1-p)\times (s_2-p)}{\left | s_2-s_1\right |} \]

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
class Vector2(Point):
    def length(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    
    def cross_prod(self, v2):
        return self.x * v2.y - self.y * v2.x
    
    def __add__(self, v2):
        return Vector2(self.x + v2.x, self.y + v2.y)
    
    def __sub__(self, v2):
        return Vector2(self.x - v2.x, self.y - v2.y)
    
s1 = Vector2(2, 1)
s2 = Vector2(6, 3)
p = Vector2(2, 3)

d = (s1 - p).cross_prod(s2 - p) / (s2-s1).length()
print(d)

Площадь многоугольника

Для вычисления площади многоугольника можно использовать следующую формулу: \[ S=\frac{1}{2}\left | \sum_{i=1}^{n-1}(p_i\times p_{i+1})\right | \] Где \(p_1, p_2, ... , p_n\) - последовательно расположенные вершины многоугольника, при этом \(p_1=p_n\).

p = [Vector2(0, 0), Vector2(3, 0), Vector2(0, 4), Vector2(0, 0)]
S = 0
for i in range(len(p) - 1):
    S += p[i].cross_prod(p[i+1])
print(S * 0.5)

Проверка выпуклости многоугольника

Многоугольник является выпуклым, если для каждой прямой, проходящей через любую его вершину, все остальные вершины лежат в одной полуплоскости относительно прямой.

Многоугольники

Многоугольники

Определить выпуклость многоугольника можно с помощью векторного произведения векторов, образованных тремя последовательными вершинами.

Выпуклость многоугольника

Выпуклость многоугольника

Для выпуклого многоугольника знак векторного произведения должен быть одинаковым при обходе всех троек вершин.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Vector2(Point):
    def cross_prod(self, v2):
        return self.x * v2.y - self.y * v2.x
    
    def __add__(self, v2):
        return Vector2(self.x + v2.x, self.y + v2.y)
    
    def __sub__(self, v2):
        return Vector2(self.x - v2.x, self.y - v2.y)
    
    
p = [Vector2(1, 3), Vector2(3, 5),
     Vector2(5, 4), Vector2(5, 2),
     Vector2(2, 1), Vector2(1, 3)]

for i in range(len(p) - 2):
    v = (p[i] - p[i+1]).cross_prod(p[i+1] - p[i+2])    
    print(v)