Как рисовать с помощью SQL?
Видимо я сделала какое-то очень плохое зло, поэтому живу во время перемен. Справиться с эмоциями и повысить свою конкурентоспособность на рынке Data Enigneer«ов мне помогает сайт Hackerrank. На пути к решению вообще всех задач по SQL с этого сайта мне попалась задачка на нетривиальные запросы.
В задачке требовалось звёздочками нарисовать прямоугольный треугольник.
Понятно, что можно было сделать как-то так:
SELECT '*'
UNION ALL
SELECT '* *'
UNION ALL
...
Но это дико скучно и некрасиво.
Давайте разберемся, как рисовать с помощью SQL, и при этом ощущать себя настоящим творцом!
Оригинальный текст задачи:
P® 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................
Вот таким образом можно почувствовать себя творцом, будучи простым дата-аналитиком. Тем не менее, уверена, что мои решения далеки от оптимальности, поэтому жду ваших комментариев и замечаний.