Рисуем морозные узоры на SQL

от автора

Немного SQL-магии под катом: математика, рекурсия, псевдографика.

Вспоминаем под Новый год формулу угла между векторами:

WITH RECURSIVE T AS (   SELECT     0 x   , 0 y   , '{"{0,0}"}'::text[] c -- растим узор от центра   , 0 i UNION ALL   (     WITH Z AS (       SELECT         dn.x       , dn.y       , T.c       , T.i       FROM         T         -- вбрасываем случайную точку на плоскость       , LATERAL(           SELECT             ((random() * 2 - 1) * 100)::integer x           , ((random() * 2 - 1) * 100)::integer y         ) p         -- из всех существующих точек выбираем у ней ближайшую       , LATERAL(           SELECT             *           FROM             (               SELECT                 (unnest::text[])[1]::integer x               , (unnest::text[])[2]::integer y               FROM                 unnest(T.c::text[])             ) T           ORDER BY             sqrt((x - p.x) ^ 2 + (y - p.y) ^ 2)           LIMIT 1         ) n         -- из 8 ее соседей заполняем ближайшую по направлению к вброшенной       , LATERAL (         SELECT           n.x + dx x         , n.y + dy y         FROM           generate_series(-1, 1) dx         , generate_series(-1, 1) dy         WHERE           (dx, dy) <> (0, 0)         ORDER BY           CASE             WHEN (p.x, p.y) = (n.x, n.y) THEN 0             ELSE abs(acos(((p.x - n.x) * dx + (p.y - n.y) * dy) / sqrt((p.x - n.x) ^ 2 + (p.y - n.y) ^ 2) / sqrt(dx ^ 2 + dy ^ 2)))           END         LIMIT 1       ) dn     )     SELECT       Z.x     , Z.y     , Z.c || ARRAY[Z.x, Z.y]::text     , Z.i + 1     FROM       Z     WHERE       Z.i < (1 << 10)   ) ) -- для каждой точки рисунка вычисляем расстояние до узора , map AS (   SELECT     gx x   , gy y   , (       SELECT         sqrt((gx - T.x) ^ 2 + (gy - T.y) ^ 2) v       FROM         T       ORDER BY         v       LIMIT 1     ) v   FROM     generate_series(-40, 40) gx   , generate_series(-30, 30) gy ) -- формируем алфавит отрисовки , gr AS (   SELECT     regexp_split_to_array('#*+-. ', '') s ) -- рисуем картинку SELECT   string_agg(     coalesce(s[(v * (array_length(s, 1) - 1))::integer + 1], ' ')   , ' '   ORDER BY x) frozen FROM   (     SELECT       x     , y     , v::double precision / max(v) OVER() v -- нормируем значения расстояний по максимуму     FROM       map   ) T , gr GROUP BY   y ORDER BY   y; 


ссылка на оригинал статьи https://habr.com/ru/post/482460/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *