Видимо я сделала какое-то очень плохое зло, поэтому живу во время перемен. Справиться с эмоциями и повысить свою конкурентоспособность на рынке 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................
Вот таким образом можно почувствовать себя творцом, будучи простым дата-аналитиком. Тем не менее, уверена, что мои решения далеки от оптимальности, поэтому жду ваших комментариев и замечаний.
