В этом листке мы полностью реализуем класс комплексных чисел.
Обращайтесь к статье в википедии для подробного текста.
Чуть ниже будет базовое напоминание.
Вообще в питоне уже есть встроенная поддержка комплексных чисел.
Но она поддерживает только коэффициенты типа float.
Мы же сделаем реализацию, поддерживающую коэффициенты типа int, float, Decimal, Fraction
.
Это позволит выполнять вычисления с большим числов знаков или точные вычисления.
Основные сложности как раз будут при взаимодействиях с классами Decimal и Fraction
и между ними.
Ликбез по комплексным числам.
Определение
Комплексное число $z$ — это выражение вида $z=a+bi$, где $a,\ b\in\R$, а $i$ — мнимая единица.
По определению $i^2=-1$.
Число $a$ называют вещественной частью
$z$ (пишут $a={\rm Re}\,(z)$), а число $b$ —
мнимой частью $z$ (пишут $b={\rm Im}\,(z)$).
Комплексные числа складывают и умножают,
«раскрывая скобки и приводя подобные».
Множество комплексных чисел обозначают буквой $\mathbb{C}$.
Комплексные числа и вектора на плоскости
Каждому комплексному числу $z=a+bi$ сопоставим точку $(a,b)$
и вектор с координатами
$(a,b)$. Длина этого вектора называется модулем комплексного
числа $z$ и обозначается $|z|$.
Пусть $z\ne0$.
Угол (в радианах), отсчитанный против часовой стрелки
от вектора
$(1,0)$ до вектора
$(a,b)$,
называется аргументом комплексного
числа $z$ и обозначается
${\rm Arg}\,(z)$.
Аргумент
определен с точностью до прибавления числа вида $2\pi n$, где $n\in\Z$.
Тригонометрическая форма записи
Для любого ненулевого комплексного числа $z$ имеет место
равенство $z=r (\cos \varphi +i\sin \varphi )$, где $r=|z|$,
$\varphi={\rm Arg}\,(z)$.
Формула Муавра
Пусть $z=r(\cos\varphi+i\sin\varphi)$, $n\in\N$.
Тогда для любого целого $n$ выполнено равенство
$z^n=r^n(\cos n\varphi+i\sin n\varphi)$.
Формула Эйлера
Оказывается, $e^{i\varphi}=\cos\varphi+i\sin\varphi$.
Можно использовать выражение $e^{i\varphi}$ как короткое и удобное обозначение для $\cos\varphi+i\sin\varphi$.
Однако удивительная часть этой формулы состоит в том, что вместо $\varphi$ можно подставлять не только вещественные числа, но и
комплексные.
Правда тождество от этого перестанет быть в полной мере честным из-за возможной многозначности.
После этого путём нехитрых арифметических преобразований можно научиться возводить сначала вещественное число в любую степень, а потом
любое комплексное число в любую комплексную степень.
А также вычислять синус и косинус комплексных чисел.
И да, на комплексной плоскости синус вполне себе достигает значения 5.
Ввод-вывод при тестировании
Во всех задачах на доработку методов класса тестирование производится следующим образом:
вашей программе на вход передаётся тест, состоящий из последовательность питоновских команд, который нужно исполнить при помощи функции exec.
Сделать это удобнее всего одним из двух способов:
Создайте новый класс Complex с двумя атрибутами: real — вещественной частью, и imag — мнимой частью комплексного числа.
Создайте конструктор, принимающий два необязательных параметра — вещественную и мнимую часть комплексного числа (по умолчанию параметры равны нулю).
Реализуйте метод __repr__, возвращающий строку, из которой можно создать в точности такое же комплексное число.
Если метод __str__ не реализован, то автоматически будет вызываться метод __repr__, поэтому наше комплексное число уже будет можно вывести на экран.
one = Complex(1)
zero = Complex()
i = Complex(0, 1)
other = Complex(2.73, -12.14)
print(one)
print(zero)
print(i)
print(other)
print(Complex(*(map(int, '4 2'.split()))))
Реализуйте метод __str__, который будет выводить комплексное число в удобной для человека форме:
для комплексного число с нулевой мнимой частью выводится только вещественная часть,
для комплексного число с нулевой вещественной частью выводится только мнимая часть (не забудьте про символ 'i'),
в остальных случаях выводится число вида (a+bi), (a-bi), (-a+bi), (-a-bi).
Если мнимая часть равна плюс-минус единице, то единица не выводится.
one = Complex(1)
zero = Complex()
i = Complex(0, 1)
other = Complex(2.73, -12.14)
print(one)
print(zero)
print(i)
print(other)
print(Complex(0.0))
1
0
i
(2.73-12.14i)
0.0
Подсказка
Да, мне кажется, что я уже достаточно думал над этой задачей
Чес-слово! :)
Вряд ли удастся придумать что-то удачнее, чем
def __str__(self):
if self.real != 0 and self.imag != 0:
...
elif self.imag != 0:
...
else:
...
return res
IDE
C: __add__
Реализуйте метод __add__, принимающий на вход два комплексных числа и возвращающий их новое комплексное число — их сумму.
one = Complex(1)
zero = Complex()
i = Complex(0, 1)
other = Complex(2.73, -12.14)
print(one + zero)
print(one + i)
print(other + other)
print(other + zero)
print(i + Complex(0, -1))
print(i + i + i + i)
1
(1+i)
(5.46-24.28i)
(2.73-12.14i)
0
4i
IDE
D: __repr__ — 2 и __slots__
Сравните реализации методов __str__ и __repr__ у наших классов Decimal и Fraction:
from fractions import *
from decimal import *
a = Fraction(3, 17)
print(str(a))
print(repr(a))
b = Decimal('1.23')
print(str(b))
print(repr(b))
В отличие от __str__ метод __repr__ выдаёт такое представление, из текста которого можно создать исходных объект.
Обновите метод __repr__ так, чтобы возвращалась представление комплексной и мнимой части, соответствующее repr.
Если специальному атрибуту класса с именем __slots__ присвоить значение в виде списка из нескольких текстовых строк, то только перечисленные в данном списке имена могут использоваться в качестве имен полей (атрибутов) классов. Например:
Это также приведет к уменьшению времени обращения к атрибутам класса за счет
другого способа хранения атрибутов в экземпляре класса.
Так как у комплексного числа не будет других атрибутов, то заполните эту переменную вещественной и мнимой частью.
from fractions import Fraction
from decimal import Decimal
print(repr(Complex(Fraction(1,3))))
print(repr(Complex(1.31, Decimal('123.321'))))
print(repr(Complex(Fraction(1,3), Fraction('0.333'))))
print(repr(Complex(Decimal('1.1'), Decimal('0.333'))))
a = Complex(3)
a.foo = 'fail'
Complex(Fraction(1, 3), 0)
Complex(1.31, Decimal('123.321'))
Complex(Fraction(1, 3), Fraction(333, 1000))
Complex(Decimal('1.1'), Decimal('0.333'))
Traceback (most recent call last):
File "<string>", line 420, in run_nodebug
File "<module3>", line 35, in <module>
AttributeError: 'Complex' object has no attribute 'foo'
IDE
E: __add__ — 2
Обновите метод __add__ так, чтобы к комплексному числу можно было корректно прибавить комплексное число, а также число типа int, float, Decimal, Fraction.
Коэффициенты типа Decimal не поддерживают операции с числами типа float или Fraction, соответствующие ошики
обрабатывать не нужно.
При попытке прибавить элемент другого класса возвращайте константу NotImplemented.
one = Complex(1)
zero = Complex()
i = Complex(0, 1)
other = Complex(2.73, -12.14)
print(one + 0)
print(i + 1)
print(other + Fraction(1,3))
print(i + Decimal('3.14'))
print(Complex(Fraction(1,3), Fraction('0.33')) + 1)
print(i + 'a')
1
(1+i)
(3.0633333333333335-12.14i)
(3.14+i)
(4/3+33/100i)
Traceback (most recent call last):
File "<string>", line 420, in run_nodebug
File "<module3>", line 45, in <module>
TypeError: unsupported operand type(s) for +: 'Complex' and 'str'
IDE
F: __init__ — 2
Обновите метод __init__ так, чтобы на вход можно было передать строку, содержащую комплексное число, у которого вещественная и мнимая часть могут быть только целыми.
Да, мне кажется, что я уже достаточно думал над этой задачей
Чес-слово! :)
Сначала нужно избавиться от пробелов при помощи .replace(' ', '').
Затем нужно найти самый правый знак + или - (если он есть).
Для этого можно, например, поместить в отдельную переменную нашу строку, в которой заменили знак + на -.
После этого при помощи метода rfind('-') найти этот знак.
И разрезать строку на две части.
В мнимой части будет символ i (можно писать if 'i' in part:), в вещественной его не будет.
Дальше избавляемся от символа i. Пустая строчка — это 0, + и - превращаются в +1 и -1,
а в противном случае нужно применить int.
Для тех, кто уже месяц не может решить эту задачу
Просто игнорируйте её и решайте дальше, начиная задачи H. В качестве конструктора используйте следующий код:
def __init__(self, a=0, b=0, c=lambda a: float(a) if '.' in a else int(a), d={'': 0, 'i': 1, '+i': 1, '-i': -1}):
self.real, self.imag = (a, b) if type(a) != str else (lambda a, b: (c(a) if a else 0, (lambda a: d[a] if a in d else c(a[:-1]))(b)))(*(lambda a, b: (b, a) if 'i' in a else (a, b))(*(lambda a, b: (a[:b], a[b:]))(*(lambda a: (a, (lambda x: x if x > 0 else 100)(a.replace('+', '-').rfind('-'))))(a.replace(' ', '')))))
IDE
G: __init__ — 3
Обновите метод __init__ так, чтобы на вход можно было передать строку, содержащую комплексное число, у которого вещественная и мнимая часть могут быть типов int или float.
0
1
-1.0
i
1.12i
-2i
(2.13-i)
(4.54-3.12i)
(7.65-3.12i)
Для тех, кто уже месяц не может решить эту задачу
Просто игнорируйте её и решайте дальше, начиная задачи H. В качестве конструктора используйте следующий код:
def __init__(self, a=0, b=0, c=lambda a: float(a) if '.' in a else int(a), d={'': 0, 'i': 1, '+i': 1, '-i': -1}):
self.real, self.imag = (a, b) if type(a) != str else (lambda a, b: (c(a) if a else 0, (lambda a: d[a] if a in d else c(a[:-1]))(b)))(*(lambda a, b: (b, a) if 'i' in a else (a, b))(*(lambda a, b: (a[:b], a[b:]))(*(lambda a: (a, (lambda x: x if x > 0 else 100)(a.replace('+', '-').rfind('-'))))(a.replace(' ', '')))))
IDE
H: __neg__, __pos__, __bool__ и conjugate
Реализуйте методы __neg__ и __pos__.
Метод __neg__ должен возвращать новое комплексное число с противоположным знаком, метод __pos__ — само число (т.е. self).
Реализуйте метод __bool__, возвращающий False тогда и только тогда, когда вещественная и мнимая часть равны нулю, и True иначе.
Реализуете метод conjugate, возвращающий сопряжённое число.
Реализуйте метод __radd__ так, чтобы к числу типа int, float, Decimal, Fraction можно было прибавить комплексное число.
При попытке прибавить комплексное число к другому типу возвращайте константу NotImplemented.
one = Complex(1)
zero = Complex()
other = Complex(2.73, -12.14)
print(0 + one)
i = Complex(0, 1)
print(1 + i)
print(Fraction(1,3) + other)
print(Decimal('3.14') + i)
print(1 + Complex(Fraction(1,3), Fraction('0.33')))
try:
print('a' + i)
except TypeError as e:
print(e)
Реализуйте методы __sub__ и __rsub__ так, чтобы из комплексного числа можно было вычесть комплексное число и число типа int, float, Decimal, Fraction, а также наоборот: из обычных чисел вычитать комплексные.
При попытке выполнить операцию с другим типом возвращайте константу NotImplemented.
one = Complex(1)
zero = Complex()
other = Complex(2.73, -12.14)
strong = Complex(Fraction(1,3), Decimal('0.33'))
i = Complex(0, 1)
print(one - zero)
print(zero - i)
print(strong - 1)
print(3.13 - other)
try:
print('a' - i)
except TypeError as e:
print(e)
try:
print(i - 'a')
except TypeError as e:
print(e)
1
-i
(-2/3+0.33i)
(0.3999999999999999+12.14i)
unsupported operand type(s) for -: 'str' and 'Complex'
unsupported operand type(s) for -: 'Complex' and 'str'
IDE
K: __mul__ и __rmul__
Реализуйте методы __mul__ и __rmul__ так, чтобы из комплексное число можно было умножить на комплексное число и число типа int, float, Decimal, Fraction, а также наоборот:
обычное число умножить на комплексное.
При попытке выполнить операцию с другим типом возвращайте константу NotImplemented.
После определения класса добавьте определение константы I = Complex(0, 1).
Тогда для создания комплексного числа можно будет пользоваться как конструкцией Complex(a, b), так и конструкцией a + b * I (хотя первая, конечно, будет работать в 4 раза быстрее).
one = Complex(1)
zero = Complex()
other = 2.73 - 12.14 * I
fr = Complex(Fraction(1,3), Fraction(-4, 13))
dc = Complex(Decimal('-12.12'), Decimal('2.33'))
print(one * zero)
print(other * fr)
print(dc * I)
print(one * 3)
print(3.12 * I)
print(fr * 5)
print(9 * dc)
print(dc * dc)
try:
print('a' * I)
except TypeError as e:
print(e)
try:
print(I * 'a')
except TypeError as e:
print(e)
0
(-2.825384615384616-4.886666666666667i)
(-2.33-12.12i)
3
3.12i
(5/3-20/13i)
(-109.08+20.97i)
(141.4655-56.4792i)
can't multiply sequence by non-int of type 'Complex'
can't multiply sequence by non-int of type 'Complex'
IDE
L: __abs__
Реализуйте метод __abs__, возвращающий модуль комплексного числа.
Используйте функцию hypot() из модуля math.
Помните, что импортировать модули лучше всего в самом начале программы.
Если либо мнимая, либо вещественная часть комплексного числа имеет тип Decimal,
то для того, чтобы не потерять точность, необходимо вычислять модуль вручную.
Положите вещественную и мнимую часть в отдельные переменные, преобразовав к типу Decimal.
Для извлечения корня используйте метод .sqrt() (не путайте с функцией sqrt() из модуля math, он работает только со float'ами).
Реализуйте функцию phase(z), возвращающую аргумент числа (в диапазоне $(-\pi,\pi]$),
функцию polar(z), возвращающую пару (модуль, аргумент),
и функцию rect(r, phi), возвращающую комплексное число по его тригонометрической записи.
numbers = (Complex(1),
Complex(1,1),
Complex(1,-1),
Complex(Fraction(-2,3),
Fraction(-4,5)),
Complex(Decimal('3.25'),
Decimal('-123.12')))
for number in numbers:
print(number,
phase(number),
polar(number),
rect(abs(number), phase(number)),
sep='\n', end='\n' * 2)
Реализуйте методы __truediv__ и __rtruediv__ так, чтобы комплексное число можно было поделить на комплексное число и на число типа int, float, Decimal, Fraction, а также наоборот: обычное число поделить на комплексное.
При попытке выполнить операцию с другим типом возвращайте константу NotImplemented.
0.9999999999999999i
(0.6153846153846154-0.9230769230769231i)
i
Подсказка
Да, мне кажется, что я уже достаточно думал над этой задачей
Чес-слово! :)
Чтобы не отстрелить себе что-нибудь при операциях с числами вида 1e-300,
использовать следующую формулу для деления чисел:
$$
\dfrac{z}{w} = \left(\dfrac{z}{|w|}\right) \cdot \left(\dfrac{\overline{w}}{|w|}\right)
$$
(как обычно, \(|w|\) — это модуль комплексного числа, а \(\overline{w}\) — это комплексно сопряжённое)
IDE
P: __iter__
Реализуйте метод __iter__, который является генератором, возвращающим при помощи yield сначала вещественную часть, а затем мнимую. Это позволит совсем просто получать из комплексного числа пару чисел.
print(tuple(1+I))
print(list(-I))
print(*(1.12 * I - 34.29))
def foo(x, y):
return x + y
cmx = Complex(170, 9)
print(foo(*cmx))
print(' '.join(map(str, Complex(2, -3))))
(1, 1)
[0, -1]
-34.29 1.12
179
2 -3
Подсказка
Да, мне кажется, что я уже достаточно думал над этой задачей
Чес-слово! :)
def __iter__(self):
yield ???
yield ???
IDE
Используем комплексные числа в геометрии
Q: Самая дальняя точка
Программа получает на вход число $N$, далее координаты $N$ точек с целыми координатами.
Выведите координаты точки, наиболее удаленной от начала координат.
2
1 2
2 3
2 3
★
Сделайте это за две строчки, не считая определения класса Complex.
IDE
R: Центр масс
Выведите координаты центра масс данного множества точек
(учтите, что это —два действительных числа).
2
1 2
2 3
1.5 2.5
★
Сделайте это за три строчки, не считая определения класса Complex.
★★
Сделайте это за одну строчку, не считая определения класса Complex.
IDE
S: Диаметр множества
Выведите диаметр данного множества – максимальное расстояние между двумя данными точками.
3
1 1
1 0
0 0
1.4142135623731
★
Сделайте это за три строчки, не считая определения класса Complex.
★★★
Сделайте это за одну строчку, не считая определения класса Complex.
IDE
T: Сортировка
Отсортируйте данные точки в порядке возрастания расстояния от начала координат.
Используете встроенную сортировку с указанием подходящего параметра key.
3
1 0
-1 -1
0 0
0 0
1 0
-1 -1
★
Сделайте это за три строчки, не считая определения класса Complex.
★★★
Сделайте это за одну строчку, не считая определения класса Complex.
IDE
U: Максимальный периметр
Среди данных точек найдите три точки, образующие треугольник с наибольшим
периметром. Выведите данный периметр.
Для нахождения периметра треугольника напишите отдельную функцию Perimeter(A, B, C).
4
0 0
0 1
1 0
1 1
3.41421356237309
IDE
V: Максимальная площадь
Среди данных точек найдите три точки, образующие треугольник с наибольшей площадью.
Выведите данную площадь.
Для нахождения площади треугольника напишите отдельную функцию Area(A, B, C).
4
0 0
0 1
1 0
1 1
0.5
IDE
W: Поворот
Дан угол ϕ и центр поворота — точка A.
После этого идёт число n и координаты n точек на плоскости.
Для каждой из этих точек выведите её образ при повороте на угол ϕ вокруг точки A.
1.5707963267948966
1+i
3
1+i
2+i
1+2i
1.0+i
1.0+2.0i
i
IDE
X: Система линейных уравнений
Даны шесть комплексных чисел — \(a_1, b_1, c_1, a_2, b_2, c_2\).
Выведите решение системы уравнений
$$
\left\{
\begin{array}{c}
a_1z+b_1w=c_1 \\
a_2z+b_2w=c_2
\end{array}
\right.
$$
Гарантируется, что у этой системы ровно одно решение.
2+i
1
4-i
3-i
1-2i
2+i
(4-2i)
(-6-i)
IDE
Возведение в степень, синус, и т.д.
Y: __pow__
Реализуйте метод __pow__, возводящий данное комплексное число в целую степень n за время порядка log(n).
Реализуйте метод __pow__, точно возводящий данное комплексное число в целую степень n за время порядка log(n),
и возводящий комплексное число в действительную степень (типа float) при помощи формулы Муавра.
Добавьте методу __pow__ возможности возводить комплексное число в комплексную степень.
Также реализуйте метод __rpow__ для возведения числа типа int или float в комплексную степень.
(0.7071067811865476+0.7071067811865475i)
-i
0.20787957635076193
(-0.9999999955096336+9.476672816471575e-05i)
unsupported operand type(s) for ** or pow(): 'str' and 'Complex'
unsupported operand type(s) for ** or pow(): 'Complex' and 'str'
Подсказка
Да, мне кажется, что я уже достаточно думал над этой задачей
Чес-слово! :)
$$
z = e^{\ln|z| + i\cdot\text{Arg}(z)}
$$
IDE
ZB: Косинус
Реализуйте функцию ccos, вычисляющую косинус комплексного числа.
Формула Эйлера — ключ к этой задаче.