27. Программирование: #299486
Фрагмент звёздного неба спроецирован на плоскость с декартовой системой координат. Учёный решил провести кластеризацию полученных точек, являющихся изображениями звёзд, то есть разбить их множество на $N$ непересекающихся непустых подмножеств (кластеров) так, что они будут лежать внутри сектора окружности радиуса $R = 50$ с центральным углом $20°$.
В ответе запишите четыре числа через запятую: сначала целую часть произведения $|P_x| \times 10,000$, затем целую часть произведения $|P_y| \times 10,000$ для файла $А$, а далее — аналогичные данные для файла $Б$.
Возможные данные одного из файлов иллюстрированы графиком. Внимание! График приведён в иллюстративных целях для произвольных значений, не имеющий отношения к заданию. Для выполнения задания используйте данные из прилагаемых файлов.

Центр определяется через сумму квадратов расстояний, а не через сумму расстояний. Это другая точка — она тянется к «центру масс» кластера сильнее, чем медианная. Технически меняется одна строчка (math.dist(a, b) ** 2вместо math.dist(a, b)), но результат отличается: например, в файле $А$ по сумме расстояний центром первого кластера была бы точка $(22{,}92;\ -8{,}64)$, а по сумме квадратов — $(24{,}52;\ -8{,}49)$.
Кластеры — не круглые сгустки, а вытянутые лучи, расходящиеся из общей точки $C$ (график с «веером» это и показывает). Привычная кластеризация по связности здесь не работает: возле точки $C$ лучи сходятся почти вплотную, а на дальнем конце соседние точки одного луча могут отстоять друг от друга дальше, чем точки разных лучей у основания.
Ключевая идея. Сектор — это диапазон углов. Значит, надо перейти к полярным координатам с полюсом в $C$: для каждой звезды считаем угол $\varphi = \operatorname{atan2}(y — y_C,\ x — x_C)$, переводим в градусы и приводим к $[0; 360)$. Точки одного кластера дают близкие углы, кластеры разделены заметными пустыми промежутками.
Сортируем точки по углу и разрезаем список там, где разрыв больше порога (я взял $5°$: реальные промежутки между кластерами составляют $11°$–$14°$, а внутри кластера соседние углы отличаются на доли градуса). Отдельно обрабатываем «склейку через ноль»: если первая и последняя группы примыкают друг к другу через $360°$, их надо объединить — в обоих файлах такой кластер есть.
Проверка допустимости: угловая ширина каждой группы должна укладываться в $20°$, а расстояние от $C$ до самой дальней точки — в $R = 50$. Здесь максимальные удаления составляют $29$–$36$, и число кластеров совпадает с условием.
import zipfile, math
from xml.etree import ElementTree as ET
T = '{urn:oasis:names:tc:opendocument:xmlns:table:1.0}'
O = '{urn:oasis:names:tc:opendocument:xmlns:office:1.0}'
def load(name):
x = ET.fromstring(zipfile.ZipFile(name).read('content.xml'))
P = []
for r in x.iter(T + 'table-row'):
parts = []
for c in r.iter(T + 'table-cell'):
v = c.get(O + 'value') or ''.join(c.itertext()).strip()
if v: parts.append(v)
tok = ' '.join(parts).replace(',', '.').split()
if len(tok) < 2: continue
try: P.append((float(tok[0]), float(tok[1])))
except ValueError: pass
return P
def center(c): # минимум СУММЫ КВАДРАТОВ расстояний
return min(c, key=lambda a: sum(math.dist(a, b) ** 2 for b in c))
def sectors(P, C, gap=5):
ang = lambda p: math.degrees(math.atan2(p[1] - C[1], p[0] - C[0])) % 360
pts = sorted(P, key=ang)
cl = [[pts[0]]]
for i in range(1, len(pts)):
if ang(pts[i]) - ang(pts[i - 1]) > gap:
cl.append([])
cl[-1].append(pts[i])
if len(cl) > 1 and ang(pts[0]) + 360 - ang(pts[-1]) <= gap:
cl[0] = cl.pop() + cl[0] # склейка кластера через 0°
return cl
for name, C in [('27-23_А.ods', (5, -9)), ('27-23_Б.ods', (-10, -7))]:
cs = [center(c) for c in sectors(load(name), C)]
Px = sum(p[0] for p in cs) / len(cs)
Py = sum(p[1] for p in cs) / len(cs)
print(int(abs(Px) * 10000), int(abs(Py) * 10000))
Результаты для файла $А$. Всего $99$ звёзд, ровно три кластера по $33$ точки. Их центры: $(24{,}52414172;\ -8{,}492290407)$, $(20{,}80248901;\ 2{,}526363421)$ и $(10{,}64430338;\ 7{,}349926526)$. Отсюда $P_x = 18{,}656978037$ и $P_y = 0{,}461333180$. Умножаем модули на $10,000$ и берём целые части: $186569$ и $4613$.
Результаты для файла $Б$. Всего $9908$ звёзд, ровно шесть кластеров: $1639$, $1644$, $1636$, $1662$, $1664$ и $1663$ точки. Их центры: $(8{,}661643205;\ -6{,}999126546)$, $(6{,}888135249;\ 0{,}865131725)$, $(2{,}18959174;\ 7{,}439353909)$, $(-5{,}101623919;\ 11{,}19238178)$, $(-13{,}16640753;\ 11{,}54924551)$ и $(-20{,}65207842;\ 8{,}28058072)$. Отсюда $P_x = -3{,}530123279$ и $P_y = 5{,}387927850$, что даёт $35301$ и $53879$.