SpiralString (Шамаева Лена)

Для хранения букв будем использовать collections.Counter.

   1 >>> from collections import Counter
   2 >>> c = Counter("ababcdcddbbbbc")
   3 >>> c
   4 Counter({'b': 6, 'c': 3, 'd': 3, 'a': 2})
   5 >>> list(c.elements())
   6 ['a', 'a', 'b', 'b', 'b', 'b', 'b', 'b', 'c', 'c', 'c', 'd', 'd', 'd']
   7 

Дополнительно он умеет выводить содержимое в спиральку, а не в строчку. Но в задаче у спиральки особый вид, поэтому эта идея не подходит.

Counter поддерживает сложение (причем новые символы дописываются в конец) и вычитание.

   1 >>> a = Counter("abc")
   2 >>> a
   3 Counter({'a': 1, 'b': 1, 'c': 1})
   4 >>> b = Counter("wsba")
   5 >>> b
   6 Counter({'w': 1, 's': 1, 'b': 1, 'a': 1})
   7 >>> a+b
   8 Counter({'a': 2, 'b': 2, 'c': 1, 'w': 1, 's': 1})
   9 >>> a-b
  10 Counter({'c': 1})
  11 

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

diag.png

Создаем направления (dx, dy):

Можно было бы использовать 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) — это и есть тип: объект, который является классом для данного экземпляра.

   1 >>> class D:
   2 ...     a = 5
   3 ...
   4 >>> d = D()
   5 >>> d.a
   6 5
   7 >>> D.a
   8 5
   9 >>> d.a = 7
  10 >>> d.a
  11 7
  12 >>> D.a
  13 5
  14 >>> type(d)
  15 <class '__main__.D'>
  16 >>> type(d).a # вернулся класс D, у которого можно обратиться к атрибуту d
  17 5
  18 

Если мы создаем класс 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))

Тогда

  1. создаётся self.val + other.val

  2. происходит type(self): в данном случае будет <class '__main__.B'>

  3. конструируется объект класса 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__() с экземплярами дочернего класса результат тоже будет принадлежать дочернему классу.

LecturesCMC/PythonIntro2020/Homework_SpiralString/Review (последним исправлял пользователь FrBrGeorge 2020-12-05 16:44:29)