Pull to refresh

Как рисовать с помощью SQL?

Reading time4 min
Views28K

Видимо я сделала какое-то очень плохое зло, поэтому живу во время перемен. Справиться с эмоциями и повысить свою конкурентоспособность на рынке Data Enigneer’ов мне помогает сайт Hackerrank. На пути к решению вообще всех задач по SQL с этого сайта мне попалась задачка на нетривиальные запросы.

В задачке требовалось звёздочками нарисовать прямоугольный треугольник.

Понятно, что можно было сделать как-то так:

SELECT '*' 
UNION ALL
SELECT '* *' 
UNION ALL
...

Но это дико скучно и некрасиво.

Давайте разберемся, как рисовать с помощью SQL, и при этом ощущать себя настоящим творцом!

Оригинальный текст задачи:

P(R) represents a pattern drawn by Julia in R rows. The following pattern represents P(5):

* * * * *
* * * *
* * *
* *
*

Write a query to print the pattern P(20).

Моё решение здесь и далее на MySQL 8.0:

SET @n = 20;
WITH RECURSIVE seq AS (
    SELECT 1 AS val 
    UNION ALL 
    SELECT val + 1 
    FROM seq 
    WHERE val+1 <= @n
) 
SELECT REPEAT('* ', val) 
FROM seq 
ORDER BY val DESC;

В следующей задачке треугольник надо было перевернуть, что решается удалением ORDER BY.

После этих задач я задумалась: а как не менее изящно нарисовать равнобедренный треугольник?

SET @n = 4;
SET @fill = '8';
SET @sp = '.';
WITH RECURSIVE seq AS (
    SELECT 1 AS val 
    UNION ALL 
    SELECT val + 1 
    FROM seq 
    WHERE val+1 <= @n
) 
SELECT CONCAT(REPEAT(@sp, @n - val), 
              REPEAT(CONCAT(@fill, @sp), val), 
              REPEAT(@sp, @n - val - 1))
FROM seq;

Здесь я решила использовать как переменные кирпичики нашей картины, чтобы получать более творческие картинки.

Вот результат:

...8...
..8.8..
.8.8.8.
8.8.8.8.

А что насчёт кружочка?

SET @n = 20;
SET @fill = '8';
SET @sp = '.';
SET @r_x = 8;
SET @r_y = 5;

/* Procedure which draws lines by points */
DELIMITER $$ 
CREATE FUNCTION draw_circle(x_points VARCHAR(255)) 
RETURNS varchar(255) 
NO SQL
BEGIN 
  DECLARE c INT;
  DECLARE str VARCHAR(255);
  SET c = 0;
  SET str = '';

  line_loop: LOOP
    IF c > @n THEN LEAVE line_loop; END IF;
    IF FIND_IN_SET(CAST(c AS CHAR), x_points)
        THEN SET str = CONCAT(str, @fill);
        ELSE SET str = CONCAT(str, @sp);
    END IF;
    SET c = c + 1;
    ITERATE line_loop;
  END LOOP;
  RETURN str;
END $$
DELIMITER ;

/* Here we get points for circle */
WITH RECURSIVE seq AS (
    SELECT 0 AS x, 0 AS y, 0 AS val
    UNION ALL 
    SELECT 
      round(@r_x + @r_x * COS(2 * PI() * val / @n)) as x, 
      round(@r_y + @r_y * SIN(2 * PI() * val / @n)) as y,
      val + 1
    FROM seq 
    WHERE val < @n
)
, points AS (
    SELECT GROUP_CONCAT(x) AS xs, y
    FROM seq
    WHERE val > 0
    GROUP BY y
    ORDER BY y ASC
)
SELECT
    draw_circle(xs)
FROM
    points
;

Здесь пришлось попотеть: разобраться с функциями и циклами в MySQL, дискретизировать формулу окружности.

Зато каков результат:

.......8..8..8....... 
....8...........8.... 
..8...............8.. 
8...................8 
8...................8 
8...................8 
..8...............8.. 
....8...........8.... 
.......8..8..8....... 

Теперь нарисуем шахматную доску.

SET @n = 8;
SET @fill = '#';
SET @sp = '.';

/* Procedure which draws lines by points */
DROP FUNCTION IF EXISTS draw_chessboard;
DELIMITER $$ 
CREATE FUNCTION draw_chessboard(i INTEGER) 
RETURNS varchar(255) 
NO SQL
BEGIN 
  DECLARE c INT;
  DECLARE str VARCHAR(255);
  SET c = 0;
  SET str = '';

  line_loop: LOOP
    IF c > @n THEN LEAVE line_loop; END IF;
    IF i mod 2 = 0 THEN
        IF c mod 2 <> 0 
        THEN SET str = CONCAT(str, @fill); 
        ELSE SET str = CONCAT(str, @sp);
        END IF;
    ELSEIF i mod 2 <> 0 THEN
        IF c mod 2 = 0 
        THEN SET str = CONCAT(str, @fill); 
        ELSE SET str = CONCAT(str, @sp);
        END IF;
    END IF;
    SET c = c + 1;
    ITERATE line_loop;
  END LOOP;
  RETURN str;
END $$
DELIMITER ;

WITH RECURSIVE seq AS (
    SELECT 1 AS val
    UNION ALL 
    SELECT 
      val + 1 AS val
    FROM seq 
    WHERE val < @n
)
SELECT
    draw_chessboard(val)
FROM
    seq
;

Результат:

#_#_#_#_#        
_#_#_#_#_        
#_#_#_#_#        
_#_#_#_#_        
#_#_#_#_#        
_#_#_#_#_        
#_#_#_#_#        
_#_#_#_#_   

Под конец перейдем к чему-то посерьезнее! К сердечку!

SET @n = 40;
SET @fill = '8';
SET @sp = '.';
SET @r_x = 16;
SET @r_y = 16;

/* Procedure which draws lines by points */
DROP FUNCTION IF EXISTS draw_heart;
DELIMITER $$ 
CREATE FUNCTION draw_heart(x_points VARCHAR(255)) 
RETURNS varchar(255) 
NO SQL
BEGIN 
  DECLARE c INT;
  DECLARE str VARCHAR(255);
  SET c = 0;
  SET str = '';

  line_loop: LOOP
    IF c > @r_x * 2 THEN LEAVE line_loop; END IF;
    IF FIND_IN_SET(CAST(c AS CHAR), x_points)
        THEN SET str = CONCAT(str, @fill);
        ELSE SET str = CONCAT(str, @sp);
    END IF;
    SET c = c + 1;
    ITERATE line_loop;
  END LOOP;
  RETURN str;
END $$
DELIMITER ;

/* Here we get points for heart */
WITH RECURSIVE seq AS (
    SELECT 0 AS x, 0 AS y, 0 AS val
    UNION ALL 
    SELECT 
      round(@r_x + 16 * POWER(SIN(2 * PI() * val / @n), 3)) as x, 
      round(2 * @r_y - ( 13 * COS(2 * PI() * val / @n) - 5 * COS(4 * PI() * val / @n) - 2 * COS(6 * PI() * val / @n) - COS(8 * PI() * val / @n) ) ) as y,
      val + 1
    FROM seq 
    WHERE val < @n
)
, points AS (
    SELECT GROUP_CONCAT(x) AS xs, y
    FROM seq
    WHERE val > 0
    GROUP BY y
    ORDER BY y ASC
)
SELECT
    draw_heart(xs)
FROM
    points
;

Формулу для сердца я добыла здесь: http://www.wolframalpha-ru.com/2012/03/blog-post.html, и это было самой сложной частью рисунка.

Результат:

........8.8...........8.8........ 
.....8.....................8..... 
.............8.....8............. 
..8............8.8............8.. 
.8..............8..............8. 
................8................ 
8...............................8 
.8.............................8. 
..8...........................8.. 
.....8.....................8..... 
........8...............8........ 
..........8...........8.......... 
.............8.....8............. 
...............8.8............... 
................8................ 
................8................ 

Вот таким образом можно почувствовать себя творцом, будучи простым дата-аналитиком. Тем не менее, уверена, что мои решения далеки от оптимальности, поэтому жду ваших комментариев и замечаний.

Tags:
Hubs:
Total votes 54: ↑50 and ↓4+58
Comments30

Articles