SpiralString (Шамаева Лена)
Для хранения букв будем использовать collections.Counter.
Дополнительно он умеет выводить содержимое в спиральку, а не в строчку. Но в задаче у спиральки особый вид, поэтому эта идея не подходит.
Counter поддерживает сложение (причем новые символы дописываются в конец) и вычитание.
Спираль состоит из отрезков, каждый из которых на 1 длиннее предыдущего. Необходимо следить, когда меняется направление. На картинке показано, что каждая сторона на 1 больше предыдущей.
Создаем направления (dx, dy):
- шаг вниз, координаты шага — (0,1),
- шаг вправо — (1, 0),
- вверх — (0, -1),
- влево — (-1, 0).
Можно было бы использовать ByteArray, но его не было в лекциях. Поэтому будет словарь, в котором пара координат — это ключ.
1 from collections import Counter
2
3 class Spiral:
4
5 def __init__(self, string = ""):
6 self.cells = Counter(string)
7
8 def __iter__(self): # после этого можно делать list
9 return self.cells.elements()
10
11 def __add__(self, other):
12 return type(self)(self.cells + other.cells)# См. Зам.1 про type(self)
13
14 def __sub__(self, other):
15 return type(self)(self.cells - other.cells)
16
17 def __mul__(self, num):
18 return type(self)(list(self) * num)
19
20 __rmul__ = __mul__
21
22 def __len__(self):
23 return sum(self.cells.values())
24
25 def _square(self):
26 dx, dy = (0, 1, 0, -1), (1, 0, -1, 0)
27 F = {} # поле для заполнения буквами, ключ – tuple координат, (x,y); значение - буква
28 x = y = n = k = j = mx = Mx = my = My = 0
29 # mx/my, Mx/My – минимальный и максимальный x/y
30 # k — количество шагов, которые надо сделать в соответствующем направлении.
31 # n — через сколько шагов (от начала работы) надо поворачивать.
32 for i, c in enumerate(self): # Прошли по итератору, нумеруя символы
33 F[x,y] = c
34 mx, my, Mx, My = min(mx, x), min(my, y), max(Mx, x), max(My, y)
35 # перевычисляем минимальные/максимальные координаты.
36 # Можно это было сделать в конце, пройдясь по всему полю
37 if i>= n:
38 k += 1
39 n += k
40 j = (j+1)%4 # индекс в массиве направлений
41 x, y = x+dx[j], y+dy[j]
42 return F, (mx, Mx), (my, My)
43
44 def __str__(self):
45 F, (mx, Mx), (my, My) = self._square()
46 return "\n".join("".join(F.get((x,y), " ") for x in range(mx, Mx+1)) for y in range(my, My+1))
В последней строке формируем печать поля, сначала построчно, а потом соединяем эти строки поля "\n".join(…). При формировании строки, если в клетке стоит буква, печатаем ее, иначе печатаем пробел.
Замечание
Зачем в __add__() делать type(self)?
type(self) — не просто строка, которая рассказывает о типе объекта. type(self) — это и есть тип: объект, который является классом для данного экземпляра.
Если мы создаем класс A, потом описываем в нем __add__, потом делаем дочерний класс B, то при таком описании
1 >>> class A:
2 ... def __init__(self, val):
3 ... self.val = val
4 ... def __add__(self, other):
5 ... return A(self.val + other.val)
6 ...
7 >>> a3 = A(5) + A(3)
8 >>> a3
9 <__main__.A object at 0x7f57de123940>
10 >>> a3.val
11 8
12 >>> class B(A):
13 ... pass
14 >>> b1 = B(5)
15 >>> b1
16 <__main__.B object at 0x7f57de123780>
17 >>> b1.val
18 5
19 >>> b3 = B(5) + B(3)
20 >>> b3.val
21 8
22 >>> b3
23 '''<__main__.A object at 0x7f57de123908> '''
24
В результате суммы B(5) + B(3) получился объект класса A !
Почему? Потому что было return A(self.val + other.val), то есть мы своими руками сделали объект класса A.
Чтобы этого не было, надо делать в __add__ так: return type(self)(self.val + other.val) (или self.__class__(self.val + other.val))
Тогда
создаётся self.val + other.val
происходит type(self): в данном случае будет <class '__main__.B'>
конструируется объект класса B (по смыслу происходит B(self.val + other.val))
1 >>> class A:
2 ... def __init__(self, val):
3 ... self.val = val
4 ... def __add__(self, other):
5 ... print("type(self) = ", type(self))
6 ... return type(self)(self.val + other.val)
7 ...
8 >>> class B(A):
9 ... pass
10 ...
11 >>> b = B(5) + B(3)
12 type(self) = <class '__main__.B'> # Напечатался объект type(self)
13 >>> b
14 '''<__main__.B object at 0x7f57de123ba8> # '''Сумма объектов B – объект B
15
То есть при работе __add__() с экземплярами дочернего класса результат тоже будет принадлежать дочернему классу.