<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 19:50:25 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="bekZe2FLeaoSvHkPInzpxUxocEXpAIn3D2mlOUOSHK-CONJMkzXUyqT_XZcucxmyjGFd7-E3d1WyiT9tEZX7wQ";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Иерархические структуры | JS: Последовательности</title>
<meta name="description" content="Иерархические структуры / JS: Последовательности: Переходим к построению иерархических структур с помощью пар">
<link rel="canonical" href="https://ru.hexlet.io/courses/sequences/lessons/hierarchy/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Иерархические структуры">
<meta property="og:title" content="JS: Последовательности">
<meta property="og:description" content="Иерархические структуры / JS: Последовательности: Переходим к построению иерархических структур с помощью пар">
<meta property="og:url" content="https://ru.hexlet.io/courses/sequences/lessons/hierarchy/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="zOLtBai09AiJhuSjccD5Rjl2H1lQ4MQshbmX4Y1RZrUjMyYyWspZaD_FwDt9zwkx-X8y81jXOo44WQ2131aB2w" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MCwicHVyIjoiYmxvYl9pZCJ9fQ==--9348098e4053d798b6f34bee4ef66947540261e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T19:50:25.551Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"E74uvtzvHp_8YxvB4HSM9kqk5KI69dw4r4Sh6DmkAgH8b-WJLpGz_0ogP1nse3yBiq3JCDLCIpoSZDu8a6Plbw","topics":[{"id":3771,"title":"Трудное для меня задание. Я не могу понять как протащить состояние о текущем теге (состояние об узле), когда редьюсу нужно спускаться ниже. Очень прошу помочь. Вот мой код. Я знаю, мой код - полная чушь. Просто хочу показать, что я пытался что-то делать :)\n\nexport const select = (query, html) => {\n\n // const filtered = filter((element) => is(currentTag, element), html);\n const walker = (element, acc) => {\n const inDeep = (currentQuery, nextQuery) => {\n if (isEmpty(nextQuery) && is(name(currentQuery), element)) {\n return cons(element, acc);\n }\n if (is(name(currentQuery), element) && hasChildren(element)) {\n const currentQuery = head(ne);\n const nextQuery = tail(query);\n\n return append(reduce(walker, acc, children(element)), acc);\n }\n }\n return inDeep(head(query), tail(query));\n }\n reduce(walker, l(), html);\n}\n// END","plain_title":"Трудное для меня задание. Я не могу понять как протащить состояние о текущем теге (состояние об узле), когда редьюсу нужно спускаться ниже. Очень прошу помочь. Вот мой код. Я знаю, мой код - полная чушь. Просто хочу показать, что я пытался что-то делать :) export const select = (query, html) => { // const filtered = filter((element) => is(currentTag, element), html); const walker = (element, acc) => { const inDeep = (currentQuery, nextQuery) => { if (isEmpty(nextQuery) && is(name(currentQuery), element)) { return cons(element, acc); } if (is(name(currentQuery), element) && hasChildren(element)) { const currentQuery = head(ne); const nextQuery = tail(query); return append(reduce(walker, acc, children(element)), acc); } } return inDeep(head(query), tail(query)); } reduce(walker, l(), html); } // END ","creator":{"public_name":"Anton Shvab","id":46270,"is_tutor":false},"comments":[{"creator":{"public_name":"Anton Shvab","id":46270,"is_tutor":false},"id":6404,"body":"Решение учителя итеративное. Значит более эффективное. Хотя по мне читается сложнее. \nСвое отправил на код ревью. Код ревью вы делаете?","topic_id":3771},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6364,"body":">Но мое решение не такое эффективное как решение учителя\nПочему вы так решили?\n\nДавайте попробуем разобраться подробнее. Запостите сюда свое решение, я добавлю решение учителя и сравним:)","topic_id":3771},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6344,"body":"> Или ее нужно самостоятельно написать?\nНет, нет, все верно, она импортируется.\n\nДавайте начнем с того что распишем необходимый алгоритм решения, то есть у вас есть задача:\n\n>Реализуйте и экспортируйте функцию select, которая возвращает список тегов в соответствии с запросом. Запрос это список из имен тегов, в котором каждый следующий тег это тег, вложенный в предыдущий.\n\nРаспишите, как вы видите решение этой задачи, по пунктам, то есть:\n1. Принимаем такие-то аргументы.\n2. Проверяем/использует/etc.\n\nВот в таком виде, по пунктам, что должно произойти, чтобы был получен желаемый результат?\n\n ","topic_id":3771},{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":6283,"body":"Ваша функция `walker` и есть `reduce`.","topic_id":3771},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6365,"body":"Почему вы думаете что ваше решение не такое эффективно как решение учителя?","topic_id":3771},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6405,"body":"Да, я. Прямо сейчас смотрю на ваш код, вот-вот ревью будет. И про сравнение с решением учителя тоже напишу:)","topic_id":3771},{"creator":{"public_name":"Anton Shvab","id":46270,"is_tutor":false},"id":6284,"body":"Зачем вы так? я еще больше запутался.","topic_id":3771},{"creator":{"public_name":"Anton Shvab","id":46270,"is_tutor":false},"id":6355,"body":"Но мое решение не такое эффективное как решение учителя. Теперь я с трудом понимаю его. ","topic_id":3771},{"creator":{"public_name":"Anton Shvab","id":46270,"is_tutor":false},"id":6303,"body":"`reduce` импортируется из библиотеки. Или ее нужно самостоятельно написать? \nПро `ne` забудьте. Вот новая версия, которая тоже не работает :)\n```\nexport const select = (query, html) => {\n \n const walker = (element, acc) => {\n \n const currentQuery = head(query); \n const nextQuery = tail(query);\n \n if (isEmpty(nextQuery) && is(currentQuery, element)) {\n return append(element, acc);\n }\n \n if (is(currentQuery, element) && hasChildren(element)) {\n return append(select(tail(query), children(element)));\n }\n };\n return reduce(walker, l(), html);\n}\n```\nНа этот код выходит ошибка \n```\nTypeError: pair is not a function\n at Object.car (/usr/local/lib/node_modules/hexlet-pairs/dist/index.js:20:10)\n at head (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:31:16)\n at append (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:118:15)\n at append (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:118:28)\n at walker (solution.js:29:20)\n at iter (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:109:56)\n at Object.reduce (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:111:10)\n at reduce (/usr/local/lib/node_modules/hexlet-html-tags/dist/index.js:65:15)\n at select (solution.js:38:12)\n at walker (solution.js:34:27)\n at iter (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:109:56)\n at iter (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:109:38)\n at iter (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:109:38)\n at iter (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:109:38)\n at iter (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:109:38)\n at Object.reduce (/usr/local/lib/node_modules/hexlet-pairs-data/dist/index.js:111:10)\n at reduce (/usr/local/lib/node_modules/hexlet-html-tags/dist/index.js:65:15)\n at select (solution.js:38:12)\n at Context. (test.js:29:25)\n```\nДа, мой не верный, но я из любопытства пытаяся понять в чем ошибка, не понял )\nНаверное, когда применяется `append`, то внутри нее когда дело доходит до `head`: `car` пытается применится не к паре. Но почему? Ведь я оперирую либо списками тегов, либо тегами по отдельности, которые сами по себе пары. ","topic_id":3771},{"creator":{"public_name":"Александр Шакун","id":23001,"is_tutor":false},"id":6290,"body":"Давайте попробуем разобраться:)\n\nНе могли бы вы весь код своего решения скопировать еще раз или отредактировать существующее сообщение? Дело в том что, во-первых первая и последняя строки выбиваются из форматирования кода, а так же не понятно как у вас собственно выглядит функция `reduce` и что собой представляет переменная `ne` из `const currentQuery = head(ne);`\n\nСпасибо!","topic_id":3771},{"creator":{"public_name":"Anton Shvab","id":46270,"is_tutor":false},"id":6351,"body":"1. Получаем на вход `select` `query` и `tree`. \n2. Если `tree` - это пустой список, возвращаем пустой список. \n3. Разбиваем `query` на `queryItem = head(query)` и на `queryRest = tail(query)`.\n4. Если `queryRest` пустой, то возвращаем отфильтрованный `tree`. Предикат в фильтре: `(element) => is(queryItem, element)`\n5. Если `queryRest` не пустой ...\n\nВот здесь проблемы.\nНе понимаю как точно описать. \n\nЕсли `queryRest` не пустой: фильтруем `tree` по (`element` = `queryItem` && `hasChildren(queryItem)`). Назовем результат `filtered`.\n\nПройтись по `filtered`, применяя к каждому элементу `select(queryRest, children(element))`. А результаты применения за`append`ировать.","topic_id":3771},{"creator":{"public_name":"Anton Shvab","id":46270,"is_tutor":false},"id":6354,"body":"Я ее решил! ха!","topic_id":3771},{"creator":{"public_name":"Владимир Никитин","id":194689,"is_tutor":false},"id":50530,"body":"Добрый день!\nНаписал несколько вариантов и упираюсь в одну и ту же ошибку. Например:\n```\nconst select = (tag,dom) => {\n \n let new_dom = reduce((item,acc) => {\n\n if(is(tag,item)){\n return consList(item,acc);\n } \n \n if(hasChildren(item)){\n return select(tag,children(item));\n }\n\n },l(),dom);\n\n return new_dom;\n}\n```\nИ вот такое:\n```\nconst select = (tag,dom) => {\n \n let list = l();\n\n const iter = (dm) => {\n \n if(isEmpty(dm)){\n return list;\n }\n\n if(is(tag,head(dm))){\n list = consList(head(dm),list);\n }\n\n if(hasChildren(head(dm))){\n return select(tag,children(head(dm)));\n }\n\n return iter(tail(dm));\n };\n\n return iter(dom);\n}\n```\n\nОшибка:\n> expect(received).toBe(expected) // Object.is equality\n\n > quote hereExpected: 2\n > quote hereReceived: 1\n\nЯ понимаю, что первое решение не срабатывает, потому что не учитывается вариант, когда у подходящего (по условию) узла есть дети. Как это решить так и не понял. Второе решение тоже почему-то не работает. \n\nПодскажите, куда думать. Спасибо.","topic_id":3771}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Иерархические структуры","entity_url":null,"active":true}},{"id":25569,"title":"Не понимаю, что я делаю не так, но, не удаётся отфильтровать список на соответствие тегу.\n```\nconst select = (tag, elements) => {\n \n const func = (element, acc) => {\n if (hasChildren(element)) {\n return select(tag, children(element));\n }\n if (isList(element)) {\n const predicate = list => is(tag, list);\n const filtration = predicate(element) ? func(element, consList(element, acc)) : l();\n return filtration;\n }\n return acc;\n}\n const checkChild = reduce(func, l(), elements);\n return checkChild;\n};\nexport default select;\n```\nв результате чего, ответ постоянно 0.\n","plain_title":"Не понимаю, что я делаю не так, но, не удаётся отфильтровать список на соответствие тегу. ``` const select = (tag, elements) => { const func = (element, acc) => { if (hasChildren(element)) { return select(tag, children(element)); } if (isList(element)) { const predicate = list => is(tag, list); const filtration = predicate(element) ? func(element, consList(element, acc)) : l(); return filtration; } return acc; } const checkChild = reduce(func, l(), elements); return checkChild; }; export default select; ``` в результате чего, ответ постоянно 0. ","creator":{"public_name":"Vladislav Nikolaenko","id":192158,"is_tutor":false},"comments":[{"creator":{"public_name":"Vladislav Nikolaenko","id":192158,"is_tutor":false},"id":54694,"body":"Переосмыслил, начал пытаться собрать в `reduce` тот самый результирующий список, о котором говориться в readMe.\nПравда возникла проблема с бесконечным циклом, ибо с одной стороны нужно что-то возвращать в функцию, а с другой, если возвращать элемент без изменений возникает проблема.","topic_id":25569}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Иерархические структуры","entity_url":null,"active":true}},{"id":12301,"title":"Каким заклинанием вызывают ментора тут?","plain_title":"Каким заклинанием вызывают ментора тут? ","creator":{"public_name":"Pavel Perminov","id":106511,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":25641,"body":"Можно просто написать вопрос.","topic_id":12301}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Иерархические структуры","entity_url":null,"active":true}},{"id":14475,"title":"не проходят последние тесты, подскажите пожалуйста в чем причина. Какой случай я не учла. Решаю эту задачу уже 3-тий день. \n```\nconst select = (tagName, html) => {\n return reduce((element, acc) => {\n if(hasChildren(element)){\n return concat(acc, select(tagName, children(element)));\n } \n \n return is(tagName, element) ? concat(acc, l(element)) : acc;\n }, l(), html);\n}\n\nexport default select;\n```\n```\nmake: Entering directory '/usr/src/app'\nnpm test\nnpm info it worked if it ends with ok\nnpm info using npm@4.2.0\nnpm info using node@v7.10.0\nnpm info lifecycle @~pretest: @\nnpm info lifecycle @~test: @\n\n> @ test /usr/src/app\n> jest --colors\n\n FAIL __tests__/select.test.js\n ● dom › #select\n\n expect(received).toBe(expected)\n \n Expected value to be (using ===):\n 5\n Received:\n 4\n \n at Object.it (__tests__/select.test.js:31:75)\n at Promise.resolve.then.el (../../local/share/.config/yarn/global/node_modules/p-map/index.js:42:16)\n\n dom\n ✕ #select (16ms)\n\nTest Suites: 1 failed, 1 total\nTests: 1 failed, 1 total\nSnapshots: 0 total\nTime: 1.06s\nRan all test suites.\n console.log __tests__/select.test.js:24\n <h1><a><span>scheme</span></a></h1><p>is a lisp</p><ul><li>item 2</li><li>item 1</li></ul><ol><li>item 2</li><li>item 1</li></ol><p>is a functional language</p><ul><li>item</li></ul><div><p>another text</p></div><div><div><p><span>text</span></p></div></div><h1>prolog</h1><p>is about logic</p>\n\nnpm info lifecycle @~test: Failed to exec test script\nnpm ERR! Test failed. See above for more details.\nnpm WARN Local package.json exists, but node_modules missing, did you mean to install?\nMakefile:2: recipe for target 'test' failed\nmake: Leaving directory '/usr/src/app'\nmake: *** [test] Error 1\n```","plain_title":"не проходят последние тесты, подскажите пожалуйста в чем причина. Какой случай я не учла. Решаю эту задачу уже 3-тий день. ``` const select = (tagName, html) => { return reduce((element, acc) => { if(hasChildren(element)){ return concat(acc, select(tagName, children(element))); } return is(tagName, element) ? concat(acc, l(element)) : acc; }, l(), html); } export default select; make: Entering directory '/usr/src/app' npm test npm info it worked if it ends with ok npm info using npm@4.2.0 npm info using node@v7.10.0 npm info lifecycle @~pretest: @ npm info lifecycle @~test: @ @ test /usr/src/app jest --colors FAIL tests/select.test.js ● dom › #select expect(received).toBe(expected) Expected value to be (using ===): 5 Received: 4 at Object.it (__tests__/select.test.js:31:75) at Promise.resolve.then.el (../../local/share/.config/yarn/global/node_modules/p-map/index.js:42:16) dom ✕ #select (16ms) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 total Snapshots: 0 total Time: 1.06s Ran all test suites. console.log tests/select.test.js:24 schemeis a lispitem 2item 1item 2item 1is a functional languageitemanother texttextprologis about logic npm info lifecycle @~test: Failed to exec test script npm ERR! Test failed. See above for more details. npm WARN Local package.json exists, but node_modules missing, did you mean to install? Makefile:2: recipe for target 'test' failed make: Leaving directory '/usr/src/app' make: *** [test] Error 1 ``` ","creator":{"public_name":"Юлия Стрелкова","id":164543,"is_tutor":false},"comments":[{"creator":{"public_name":"Игорь Инковский","id":61651,"is_tutor":false},"id":30478,"body":"а что говорят тесты?","topic_id":14475},{"creator":{"public_name":"Николай Еловский","id":159932,"is_tutor":false},"id":30535,"body":"Ваше решение натолкнуло меня в нужное направление. Взгляните на тесты и исходную структура - возможно, как и я, упустили нюанс.","topic_id":14475},{"creator":{"public_name":"Алексей Королёв","id":188357,"is_tutor":false},"id":48203,"body":"Хм, код наверно лучше спрятать","topic_id":14475}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Иерархические структуры","entity_url":null,"active":true}},{"id":34967,"title":"Здравствуйте! Подскажите, пожалуйста, что я делаю не так? Вот ревью https://ru.hexlet.io/code_reviews/178016","plain_title":"Здравствуйте! Подскажите, пожалуйста, что я делаю не так? Вот ревью https://ru.hexlet.io/code_reviews/178016 ","creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":77089,"body":"**Мария Мардеева**,\n\nДа, тут путано у вас получилось. Вернитесь к предыдущему ревью и попробуйте ввести в реализацию переменные. Вместо того, что бы делать сразу возврат из функции, присваивайте результат переменной и для следующего условия вместо acc используйте список, который содержится в переменной.","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":76636,"body":"**Мария Мардеева**,\n\nС инициализацией аккумулятора порядок, а вот с использованием consList не совсем. Функция consList добавляет элемент к списку. В начале вы проверяете, соответствует ли элемент искомому, и если да, тогда его нужно добавить к аккумулятору (а это список). Соответственно нужно добавить element к acc.","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":76408,"body":"**Мария Мардеева**, приветствую!\n\nОх, у вас и длинная строка. Рекомендовал бы записать решение по другому, а то очень сложно читается. По самому решению, проанализируйте как отработает ваша функция, если элемент содержит детей и он соответствует искомому тегу. ","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":77144,"body":"**Мария**, использовать необходимо переменную, а не константы, и не так. Если выполняется первое условие (элемент соответствует искомому), вы добавляете его к аккумулятору и делаете возврат полученного значения. Так вот нужно не возвращать, а присвоить результат переменной. А потом во втором условии, если есть дети, нужно использовать не acc, а переменную которой вы присвоили новое значение аккумулятора. И далее вместо возврата снова присвоить переменной результат с уже добавленными детьми. И в самом конце уже возвращать итоговое значение аккумулятора. Прочитайте внимательно и разберитесь что нужно сделать.","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":77068,"body":"**Станислав Дзисяк**, не могу придумать что будет выполняться в условии, в котором элемент и является искомым и у него есть дети. Придумала что-то, но больше похоже на ерунду. https://ru.hexlet.io/code_reviews/178016","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":77097,"body":"**Станислав Дзисяк**, Поправила, как поняла. Вот ревью https://ru.hexlet.io/code_reviews/178016","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":76422,"body":"Все равно запуталась. Вот мое решение https://ru.hexlet.io/code_reviews/178016","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":76735,"body":"**Мария Мардеева**, \n\nТак, хорошо. Теперь подумайте, об условиях. Если элемент искомы, он добавляется к аккумулятору, если нет, тогда возвращается аккумулятор. Если есть дети будет возвращен результат прохождения по детям. А как быть с элементами которые уже есть в аккумуляторе? Подумайте что нужно сделать. Также проанализируйте, что произойдет если у элемента есть дети, но сам элемент соответствует искомому.","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":76972,"body":"**Мария Мардеева**, приветствую!\n\nВ предыдущем ревью у вас был правильно реализован рекурсивный вызов select. Но была проблема в том, что результат этого вызова затирал текущее значение аккумулятора. Необходимо было результат этого вызова присоединять к аккумулятору. Только подумайте с помощью какой функции это лучше выполнить. Одна из импортируемых функций хорошо подойдёт для этой задачи.","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":77310,"body":"**Мария Мардеева**,\n\nВы всё еще не избавились от возвратов. Функция func1 в итоге должна содержать один оператор возврата return в конце. То что происходит внутри условий, должно присваивается переменной, значение которой используется в следующем условии и так далее. Вот например:\n\n```\nconst func = (num) => {\n let result = num;\n if (result === 5) {\n result = result + 2;\n }\n if (result === 7) {\n result = result + 3;\n }\n return result;\n};\n\nfunc(5); // 10\n```\nПопробуйте выполнить данный код на [repl.it](https://repl.it/languages/babel) и разобраться как он работает. И примените такой способ для решения упражнения. Только вернитесь к ревью №9, оно ближе всего к решению.\n\nТакже порекомендую вам немного отдохнуть от данного упражнения и продолжить обучение. А через несколько дней вернутся к нему заново.","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":77041,"body":"**Станислав Дзисяк**, Исправила, но не работает. https://ru.hexlet.io/code_reviews/178016","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":76916,"body":"**Станислав Дзисяк**, Исправила вызов return в проверке на детей на этот acc + children(element); Я правильно сделала?","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":76507,"body":"**Мария Мардеева**, день добрый!\n\nОтлично, стало читаться гораздо лучше. Теперь давайте по порядку. Вначале нужно разобраться, что должна возвращать функция select. Посмотрите внимательно в описание задачи и внесите необходимые правки в решение.","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":76554,"body":"**Мария Мардеева**,\n\nВсё верно, должен возвращаться список, в вашем случае возвращается число. Посмотрите на второй аргумент функции редьюс. Это начальное значение аккумулятора, который в данном случае число 0. Инициализируйте аккумулятор списком и для добавления к аккумулятору нод используйте соответствующие функции из списка импортируемых. При необходимости информацию о каждой из функций можно просмотреть в файлах содержащихся в директории docs. Изучите, что каждая из них делает, рассмотрите примеры. \n\n","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":76609,"body":"**Станислав Дзисяк**, я не пойму, как вернуть список. Использовать consList? тогда будет вот так https://ru.hexlet.io/code_reviews/178016. И 0 поменяла на l(); Но что-то не так","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":76718,"body":"**Станислав Дзисяк**, Спасибо! А теперь не верно считает. Не пойму, что написать в ветку else. Наверное там не правильно написано. Вот мой ревью https://ru.hexlet.io/code_reviews/178016","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":76820,"body":"**Мария Мардеева**,\n\nСейчас у вас получается, что если есть дети, то аккумулятор получит результат рекурсивного вызова функции селект, а нужно этот результат добавить к аккумулятору. Посмотрите на функции, которые импортируются в модуль, и подберите подходящую для этой операции.\n\n> А если у элемента есть дети, но сам элемент соответствует искомому, то он должен записываться в список, а дети рекурсивно передаваться в функцию select. Так?\n\nДа, всё верно. Но сейчас получается, что если есть деть, то происходит возврат рекурсивного вызова селект, а проверка на соответствие искомому элементу не выполняется. Подумайте как можно реализовать нужное поведение в данном случае.","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":76514,"body":"По идее, должен возвращаться список. Но я не пойму, как это применить.","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":77188,"body":"**Станислав Дзисяк**, Сделала, как смогла. Дальше не знаю, что делать.https://ru.hexlet.io/code_reviews/178016","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":76647,"body":"Вернула на прежнее место. Не пойму, как добавить число в список, если не использовать функцию consList? Вот теперь мое ревью такое. Поменяла еще порядок аргументов в return https://ru.hexlet.io/code_reviews/178016 Подскажите, пожалуйста.","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":77646,"body":"**Мария Мардеева**, \n\nДа, верно. Вместо acc используйте result. Вначале вы ведь присвоили переменной result значение аккумулятора. \n\n> Ошибку не нашла.\n\nОбратите внимание, что передаётся в функцию select если у элемента есть дети. Нужно передавать детей, которые содержатся в элементе а не сам элемент.","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":77035,"body":"**Мария Мардеева**,\n\nВсё верно нужно использовать функцию concat. Обратите внимание, что она принимает на вход два списка, и результатом её вызова будет объединённый список. Соответственно у вас есть два списка: один содержится в аккумуляторе, а второй будет получен в результате рекурсивного вызова функции select. Проверьте правильно ли вы использовали функцию concat.","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":76711,"body":"**Мария**, приветствую!\n\nВариант с consList был хороший. Я имел в виду, что она неверно использовалась. Вам нужно было проверить, какие аргументы принимает данная функция и передать в неё верные значения. consList первым аргументом принимает элемент, который вы хотите добавить в список, а вторым - список к которому необходимо элемент добавить. Список, который формируется, это аккумулятор. Вот и присоединяйте к нему элемент с помощью consList.","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":77018,"body":"**Станислав Дзисяк**, Я бы сделала это с помощью функции concat. Но наверное я это неправильно записала. Вот мой ревью. https://ru.hexlet.io/code_reviews/178016","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":77563,"body":"**Мария Мардеева**, приветствую!\n\nУже ближе. До проверки haschildren нужно объявить переменную и присвоить ей значение аккумулятора - let result = acc; А далее не вместо acc используйте result. И инструкцию let в других местах уберите, должно быть просто result = ... Строка else result = acc; также не нужна. И предыдущий else if замените на просто if. После этого у вас останется одна ошибка которую мы разберем, но возможно вы и сами её заметите.","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":77664,"body":"**Мария Мардеева**, \n\nПожалуйста :)\n\nМария, обязательно разберите как работает данная функция. Пройдитесь по всем конструкциям используемых в ней. ","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":77658,"body":"**Станислав Дзисяк**, благодарю за терпение. Все получилось.","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":77441,"body":"**Станислав Дзисяк**, Здравствуйте! Отдохнула некоторое время. Взялась опять за решение данной задачи. https://ru.hexlet.io/code_reviews/178016 Применила тот подход, о котором Вы говорили.","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":77645,"body":"**Станислав Дзисяк**, Не понятно, вместо acc использовать result? Ошибку не нашла. Вот решение https://ru.hexlet.io/code_reviews/178016","topic_id":34967},{"creator":{"public_name":"Мария Мардеева","id":187347,"is_tutor":false},"id":76739,"body":"**Станислав Дзисяк**, Даже не представляю, что должно быть с элементами, которые уже есть в аккумуляторе. Наверное они должны накапливаться в списке. Только не пойму как это реализовать. А если у элемента есть дети, но сам элемент соответствует искомому, то он должен записываться в список, а дети рекурсивно передаваться в функцию select. Так?","topic_id":34967},{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":77058,"body":"**Мария Мардеева**, \n\nКак бы это странно не звучало, но и не должно было работать. С основной задачей вы справились. Теперь нужно подумать как сделать условия независимыми. В данный момент отрабатываются три сценария: искомый элемент найден, если нет, то работаем с детьми, если детей нет, то ничего не меняем. Это не совсем правильно, так как элемент может быть искомым или нет, и это никоим образом не влияет на то, могут быть у него дети или нет. То есть нужно проверить соответствует ли элемент искомому и потом независимо от результата проверить есть ли дети. Подумайте как можно выйти из данной ситуации.","topic_id":34967}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Иерархические структуры","entity_url":null,"active":true}},{"id":19155,"title":"Добрый день!\nПодскажите, пожалуйста, где ошибка в логике:\n\n`// removed`\n\nПолучается, что у меня строка return acc; должна выполняться в любом случае, если не срабатывает второй if, и в acc должны быть либо только то, что мы передали в функцию, либо список, в который добавлен текущий элемент в первом if (consList(element,acc)).\nНо по факту строка return acc; в первом же тесте возвращает пустой список.\nЕсли поставить return перед consList(element,acc), то возвращается то, что нужно, но некорректно обрабатывается случай, когда элемент является искомым и содержит потомков (валится последний тест).\nВ итоге задание я сделала, но, как я считаю, тупо и некрасиво. Хочется понять, где я тут косячу.","plain_title":"Добрый день! Подскажите, пожалуйста, где ошибка в логике: // removed Получается, что у меня строка return acc; должна выполняться в любом случае, если не срабатывает второй if, и в acc должны быть либо только то, что мы передали в функцию, либо список, в который добавлен текущий элемент в первом if (consList(element,acc)). Но по факту строка return acc; в первом же тесте возвращает пустой список. Если поставить return перед consList(element,acc), то возвращается то, что нужно, но некорректно обрабатывается случай, когда элемент является искомым и содержит потомков (валится последний тест). В итоге задание я сделала, но, как я считаю, тупо и некрасиво. Хочется понять, где я тут косячу. ","creator":{"public_name":"Ольга Косырькова","id":164663,"is_tutor":false},"comments":[{"creator":{"public_name":"Ольга Косырькова","id":164663,"is_tutor":false},"id":40527,"body":"Да, сохранила результат в acc и все прошло.\nЯ почему-то думала, что сама функция cons добавляет элемент в список, который указан вторым параметром, а получается, что нужно было сделать явное присвоение этому же списку.\nСпасибо за помощь!","topic_id":19155},{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":40505,"body":"Здравствуйте!\n\n> Подскажите, пожалуйста, где ошибка в логике:\n> и в acc должны быть либо только то, что мы передали в функцию, либо список, в который добавлен текущий элемент в первом if\n\nВ первой ветке `if` вы делаете вызов функции `consList`, но что происходит с **результатом** этого вызова? Результат нигде не сохраняется и никуда не возвращается. Как минимум, здесь явная ошибка.","topic_id":19155}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Иерархические структуры","entity_url":null,"active":true}},{"id":31829,"title":"День добрый!\nНе понимаю как проверить, что собирает моя свертка. Отладочную печать и так и сяк крутил, толку нет. Что печатать? И еще вопрос, как reduce обходит наш дом поэлементно? Список выпрямляется? Что является element при каждой итерации reduce?\n\n`// removed`\n","plain_title":"День добрый! Не понимаю как проверить, что собирает моя свертка. Отладочную печать и так и сяк крутил, толку нет. Что печатать? И еще вопрос, как reduce обходит наш дом поэлементно? Список выпрямляется? Что является element при каждой итерации reduce? ``` const select = (tag, list) => { const reduceList = reduce((element, acc) => { return (is(tag, element)) ? consList(element, acc) : acc; }, l(), list); console.log(htmlToString(reduceList)); return 22; }; export default select; ``` ","creator":{"public_name":"Денис Потехин","id":239601,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":69242,"body":"Денис, приветствую!\n\n> Не понимаю как проверить, что собирает моя свертка. Отладочную печать и так и сяк крутил, толку нет. Что печатать? \n\nВы правильно используете отладочную печать. И в сообщении тестов также можно увидеть, что `console.log()` срабатывает. Но в консоль не выводится никакого значения, потому что вызов `reduce` возвращает пустое значение. Проверьте, как наполняется аккумулятор внутри фукнции-обработчика, которую вы передаёте первым аргументом.\n\n> И еще вопрос, как reduce обходит наш дом поэлементно? \n\nВернитесь к уроку о [Свёртке](https://ru.hexlet.io/courses/sequences/lessons/reduce/theory_unit), в котором реализуется и разбирается внутреннее устройство нашего `reduce`. \n\n> Список выпрямляется?\n\nЭто зависит от того, как вы формируете аккумулятор. С помощью редьюс можно вернуть любую структуру.\n\n> Что является element при каждой итерации reduce?\n\nДля наглядности распечатайте `element` внутри функции-обработчика с помощью `console.log()`.","topic_id":31829}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Иерархические структуры","entity_url":null,"active":true}},{"id":9994,"title":"Александр здравствуйте.\nтест прошёл, однако решение сильно отличается от решения учителя, в связи с чем есть сомнения. Посмотрите пожалуйста\n```\n// removed\n```","plain_title":"Александр здравствуйте. тест прошёл, однако решение сильно отличается от решения учителя, в связи с чем есть сомнения. Посмотрите пожалуйста // removed ","creator":{"public_name":"Дмитрий Сибиряков","id":147425,"is_tutor":false},"comments":[{"creator":{"public_name":"Kirill Mokevnin","id":1,"is_tutor":false},"id":20520,"body":"1. Если хотите показать решение, то лучше отправить его в код ревью и здесь приложить ссылку.\n1. Прочитать ваш код не представляется возможным, вложенный тернарная операция, это за гранью добра и зла.","topic_id":9994}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Иерархические структуры","entity_url":null,"active":true}},{"id":18910,"title":"Здравствуйте! Нужна Ваша помощь.\n```\nconst select = (tagName, list) => {\n const result = reduce(((item, acc) => is(tagName, item) ? concat(acc, acc) : acc), l(), list);\n };\n```\nЯ в правильном направлении двигаюсь? Обхожу список и добавляю в асс. Но такое ощущение что не так надо. И как потом обходить следующие элементы не могу понять.","plain_title":"Здравствуйте! Нужна Ваша помощь. const select = (tagName, list) => { const result = reduce(((item, acc) => is(tagName, item) ? concat(acc, acc) : acc), l(), list); }; Я в правильном направлении двигаюсь? Обхожу список и добавляю в асс. Но такое ощущение что не так надо. И как потом обходить следующие элементы не могу понять. ","creator":{"public_name":"Андрей Захватошин","id":183403,"is_tutor":false},"comments":[{"creator":{"public_name":"Александр О.","id":61806,"is_tutor":false},"id":39996,"body":"Добрый день!\n\n> Обхожу список и добавляю в асс.\n\nДобавляете в acc что? Если acc это список, то по услоию заданя в acc надо добавлять ноды с подходящим именем. Вы же делаете, что-то совсем другое `concat(acc, acc)`.\n\n> И как потом обходить следующие элементы не могу понять.\n\nДля это надо организовать рекурсию. Ведь дети ноды - это тоже список, т.е. его можно рекрусивно обработать select-функцией.\n\nКроме того, вы вычисляете результат и сохраняете в констанет result, но забываете результат вернуть из функции.","topic_id":18910}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Иерархические структуры","entity_url":null,"active":true}},{"id":32425,"title":"Не могу понять с чего вообще начать. Что должна вернуть \"Проходимся по списку нод редьюсом, который собирает результирующий список.\"?","plain_title":"Не могу понять с чего вообще начать. Что должна вернуть \"Проходимся по списку нод редьюсом, который собирает результирующий список.\"? ","creator":{"public_name":"Alexander","id":233178,"is_tutor":false},"comments":[{"creator":{"public_name":"Stanislav Dzisiak","id":212236,"is_tutor":true},"id":70469,"body":"**Alexander**, приветствую.\n\n> Я правильно понимаю, что в редусе я должен смотреть, содержит ли текущая нода детей, то идем дальше. если нет, то проверяем текущую ноду is и добавляем в acc ?\n\nВы все правильно поняли, только дополню один момент - в случае если нода имеет детей, вам необходимо рекурсивно вызвать вашу функцию select и ее результат добавить к аккумулятору.","topic_id":32425},{"creator":{"public_name":"Alexander","id":233178,"is_tutor":false},"id":70471,"body":"Зря я прочитал подсказку. Она очень смутила, изначально я правильно думал","topic_id":32425},{"creator":{"public_name":"Alexander","id":233178,"is_tutor":false},"id":70405,"body":"Я правильно понимаю, что в редусе я должен смотреть, содержит ли текущая нода детей, то идем дальше. если нет, то проверяем текущую ноду **is** и добавляем в acc ? ","topic_id":32425},{"creator":{"public_name":"Alexander","id":233178,"is_tutor":false},"id":70410,"body":"Остановился на этом https://ru.hexlet.io/code_reviews/153595","topic_id":32425},{"creator":{"public_name":"Alexander","id":233178,"is_tutor":false},"id":70408,"body":"https://ru.hexlet.io/code_reviews/153595 пока такое понимание, но не работает ","topic_id":32425},{"creator":{"public_name":"Alexander","id":233178,"is_tutor":false},"id":70406,"body":"то есть надо правильно сделать reduce и только ? Подсказка из 3 пунктов вводит в заблуждение (","topic_id":32425},{"creator":{"public_name":"Alexander","id":233178,"is_tutor":false},"id":70417,"body":"Решил, 2 час ночи...","topic_id":32425}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Иерархические структуры","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":455,"slug":"js_sequences_hierarchy_exercise","name":null,"state":"active","kind":"exercise","language":"javascript","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"Работа с древовидными структурами в промышленном программировании достаточно частая ситуация.\nНапример, вывод файловой структуры в нашем редакторе — типичный пример работы с деревьями.\n\n## select.js\n\nРеализуйте и экспортируйте по умолчанию функцию, которая принимает на вход имя тега и HTML список, а возвращает список всех нод, соответствующих имени. Ноды возвращаются в том виде, в котором они представлены в дереве. Порядок, в котором ноды возвращаются — не важен.\n\n### Примеры\n\nПредположим, что у нас есть такой HTML:\n\n```html\n<h1>scheme</h1>\n<p>is a lisp</p>\n<ul>\n <li>item 1</li>\n <li>item 2</li>\n</ul>\n<ol>\n <li>item 1</li>\n <li>item 2</li>\n</ol>\n<p>\n is a functional lang\n</p>\n<ul>\n <li>item</li>\n</ul>\n<div>\n <p>text</p>\n</div>\n<div>\n <div>\n <p>text</p>\n </div>\n</div>\n<h1>prolog</h1>\n<p>is about logic</p>\n```\n\nТогда:\n\n```javascript\nconst dom1 = make(); // Список нод, то есть это лес, а не дерево\nconst dom2 = append(dom1, node('h1', 'scheme'));\nconst dom3 = append(dom2, node('p', 'is a lisp'));\n\nconst children1 = l(node('li', 'item 1'), node('li', 'item 2'));\nconst dom4 = append(dom3, node('ul', children1));\nconst children2 = l(node('li', 'item 1'), node('li', 'item 2'));\nconst dom5 = append(dom4, node('ol', children2));\nconst dom6 = append(dom5, node('p', 'is a functional language'));\nconst children3 = l(node('li', 'item'));\nconst dom7 = append(dom6, node('ul', children3));\nconst dom8 = append(dom7, node('div', l(node('p', 'text'))));\nconst dom9 = append(dom8, node('div', l(node('div', l(node('p', 'text'))))));\n\nconst dom10 = append(dom9, node('h1', 'prolog'));\nconst dom = append(dom10, node('p', 'is about logic'));\n\nselect('li', dom);\n// [('li', 'item 1'), ('li', 'item 2'), ('li', 'item 1'), ('li', 'item 2'), ('li', 'item')]\n\nselect('p', dom);\n// [('p', 'is a lisp'), ('p', 'text'), ('p', 'text'), ('p', 'is about logic'), ('p', 'is a functional language')]\n```\n\n### Подсказки\n\nПосмотрите в документации примеры использования функций, описанных ниже.\n\n* `hasChildren()` — функция, которая проверяет, есть ли потомки у элемента\n* `children()` — функция, которая возвращает список потомков\n* `is()` - функция, которая проверяет соответствие ноды переданному имени\n* Проанализируйте тесты\n\nЭту задачу можно решить разными способами, алгоритм самого простого выглядит так:\n\n1. Проходимся по списку нод редьюсом, который собирает результирующий список.\n2. Если текущая нода содержит детей, то запускаем `select()` рекурсивно для детей, а результат вызова\n добавляем в аккумулятор.\n3. Если текущая нода соответствует тому, что мы ищем, добавляем её в аккумулятор.\n","prepared_readme":"Работа с древовидными структурами в промышленном программировании достаточно частая ситуация.\nНапример, вывод файловой структуры в нашем редакторе — типичный пример работы с деревьями.\n\n## select.js\n\nРеализуйте и экспортируйте по умолчанию функцию, которая принимает на вход имя тега и HTML список, а возвращает список всех нод, соответствующих имени. Ноды возвращаются в том виде, в котором они представлены в дереве. Порядок, в котором ноды возвращаются — не важен.\n\n### Примеры\n\nПредположим, что у нас есть такой HTML:\n\n```html\n<h1>scheme</h1>\n<p>is a lisp</p>\n<ul>\n <li>item 1</li>\n <li>item 2</li>\n</ul>\n<ol>\n <li>item 1</li>\n <li>item 2</li>\n</ol>\n<p>\n is a functional lang\n</p>\n<ul>\n <li>item</li>\n</ul>\n<div>\n <p>text</p>\n</div>\n<div>\n <div>\n <p>text</p>\n </div>\n</div>\n<h1>prolog</h1>\n<p>is about logic</p>\n```\n\nТогда:\n\n```javascript\nconst dom1 = make(); // Список нод, то есть это лес, а не дерево\nconst dom2 = append(dom1, node('h1', 'scheme'));\nconst dom3 = append(dom2, node('p', 'is a lisp'));\n\nconst children1 = l(node('li', 'item 1'), node('li', 'item 2'));\nconst dom4 = append(dom3, node('ul', children1));\nconst children2 = l(node('li', 'item 1'), node('li', 'item 2'));\nconst dom5 = append(dom4, node('ol', children2));\nconst dom6 = append(dom5, node('p', 'is a functional language'));\nconst children3 = l(node('li', 'item'));\nconst dom7 = append(dom6, node('ul', children3));\nconst dom8 = append(dom7, node('div', l(node('p', 'text'))));\nconst dom9 = append(dom8, node('div', l(node('div', l(node('p', 'text'))))));\n\nconst dom10 = append(dom9, node('h1', 'prolog'));\nconst dom = append(dom10, node('p', 'is about logic'));\n\nselect('li', dom);\n// [('li', 'item 1'), ('li', 'item 2'), ('li', 'item 1'), ('li', 'item 2'), ('li', 'item')]\n\nselect('p', dom);\n// [('p', 'is a lisp'), ('p', 'text'), ('p', 'text'), ('p', 'is about logic'), ('p', 'is a functional language')]\n```\n\n### Подсказки\n\nПосмотрите в документации примеры использования функций, описанных ниже.\n\n* `hasChildren()` — функция, которая проверяет, есть ли потомки у элемента\n* `children()` — функция, которая возвращает список потомков\n* `is()` - функция, которая проверяет соответствие ноды переданному имени\n* Проанализируйте тесты\n\nЭту задачу можно решить разными способами, алгоритм самого простого выглядит так:\n\n1. Проходимся по списку нод редьюсом, который собирает результирующий список.\n2. Если текущая нода содержит детей, то запускаем `select()` рекурсивно для детей, а результат вызова\n добавляем в аккумулятор.\n3. Если текущая нода соответствует тому, что мы ищем, добавляем её в аккумулятор.\n","has_solution":true,"entity_name":"Иерархические структуры"},"units":[{"id":1368,"name":"theory","url":"/courses/sequences/lessons/hierarchy/theory_unit"},{"id":1370,"name":"quiz","url":"/courses/sequences/lessons/hierarchy/quiz_unit"},{"id":1369,"name":"exercise","url":"/courses/sequences/lessons/hierarchy/exercise_unit"}],"links":[],"ordered_units":[{"id":1368,"name":"theory","url":"/courses/sequences/lessons/hierarchy/theory_unit"},{"id":1370,"name":"quiz","url":"/courses/sequences/lessons/hierarchy/quiz_unit"},{"id":1369,"name":"exercise","url":"/courses/sequences/lessons/hierarchy/exercise_unit"}],"id":693,"slug":"hierarchy","state":"approved","name":"Иерархические структуры","course_order":800,"goal":"Переходим к построению иерархических структур с помощью пар","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"170597960","theory":"До текущего момента мы работали только со списками. Но как мы помним свойство замыкания множества относительно операции позволяет строить из пар иерархические структуры, т.е. те структуры, части которых сами являются составными структурами. Самый яркий представитель иерархических структур - это деревья.\n\n\n\nДеревья бывают очень разные. Вообще это название произошло от обычных деревьев, потому что на него можно смотреть, как на иерархическую структуру. На стволе расположены ветки, на ветках могут быть тоже ветки, на которых, в свою очередь, есть ещё ветки, на которых есть листья. Деревьев существует очень много видов как в реальной жизни, так и в программировании, в Computer Science их много десятков, если не сотен.\n\nПри этом все они обладают общими свойствами и имеют соответствующий словарь терминов. Давайте с ними познакомимся. Деревья в программировании так или иначе встречаются всем, любым программистам, на любом языке, в любой области.\n\n\n\n* Узел — это любой элемент дерева. Иногда говорят вершина, нода.\n* Корневой узел — самый верхний узел дерева (на картинке это узел под номером 8).\n* Лист — это узел, не имеющий дочерних элементов. Листья расположены в самой глубине дерева.\n\n## Особенности\n\n* Рекурсивное определение\n* Естественный способ обработки - рекурсия\n\nКакие особенности присущи таким структурам?\n\nОпределение дерева является рекурсивным. Потому что постепенно идёт рекурсивный спуск в глубину, и любой элемент может оказаться тоже деревом или поддеревом, которые в свою очередь тоже могут содержать деревья или поддеревья. Как можно догадаться, естественный способ обработки таких элементов — это рекурсия.\n\nВот пример с использованием языка Haskell, в котором дерево можно определить как тип данных, используя по сути математическое определение.\n\n```haskell\ndata Tree a = EmptyTree | Node a (Tree a) (Tree a)\n```\n\nДерево - это либо пустое дерево (EmptyTree), либо нода, которая содержит в себе элемент (Node a), который содержит два поддерева (Tree a).\n\nЭто определение бинарного дерева, но общий принцип понятен. Фактически одна такая строчка определяет бинарное дерево любой сложности, т.е. все они будут подходить под это определение.\n\n```html\n<html>\n <body>\n <h1>Сообщество</h1>\n <p>Общение между пользователями Хекслета</p>\n <div class='hexlet-community'>\n <div class='text-xs-center'>\n <div class='fa fa-spinner'></div>\n </div>\n </div>\n </body>\n</html>\n```\n\nДавайте вернемся к нашему HTML. В одном из первых уроков мы сказали, что на самом деле это древовидная структура, хотя мы работали с ним как со списком. Корнем является тег `html`. При этом вкладывать можно не всё во всё, есть определённые правила, по которым это делается. Но в любом случае это древовидная структура, которая позволяет нам строить иерархию любой глубины, что обычно и происходит. Поэтому в общем случае с HTML и работают как с деревом, а не просто списком.\n\nВспомним, что с помощью пар можно представлять деревья совершенно по-разному. В общем-то, благодаря замыканию мы можем построить любые типы деревьев.\n\nДавайте посмотрим конкретную задачу, решаемую в контексте работы с деревьями. Фактически мы расширим ту функциональность, которую использовали для работы со списками. Для того, чтобы построить дерево, нам достаточно тех же самых списков, а в общем случае — пар.\n\n## Количество листьев\n\n```javascript\nimport { head, tail, l } from 'hexlet-pairs-data'\n\nconst tree = l(l(1, 2), l(3, l(4, 5)), 6)\n\nconst countElements = (tree) => {\n if (!isList(tree)) {\n return 1\n }\n if (isEmpty(tree)) {\n return 0\n }\n\n return countElements(head(tree)) + countElements(tail(tree))\n}\n\ncountElements(tree) // 6\n```\n\nВ примере выше мы создаём дерево, по факту просто используя списки внутри списков, что достаточно очевидно. Можно представить, как дерево визуально будет выглядеть. У нас есть список, в котором есть вложенный список, дальше ещё один список, вложенный в другой список и какой-то элемент, т.е. листья есть на разных уровнях нашего дерева. Давайте попробуем подсчитать количество элементов. Здесь используется обычный рекурсивный процесс, в котором в конечном итоге происходит следующее.\n\nМы получаем `head(tree)` и `tail(tree)`, \"голову\" и \"хвост\" списка. При этом в отличие от работы со списками в предыдущих уроках, \"голова\" тоже может оказаться поддеревом, поэтому функцию `countElements` мы рекурсивно применяем и к \"голове\" и к \"хвосту\". Нам нет разницы, работаем мы с левой или с правой частью, ведь расшириться вглубь может любая из них.\n\nДанный вид рекурсии называется уже не линейной, а древовидной, так как каждый `countElements` внутри вызывает две функции `countElements`, что её раздваивает. Если посмотреть все вызовы этой функции, можно увидеть, что они \"расползаются\" по дереву.\n\nВ примере выше есть две проверки. Если это не список, т.е. мы дошли до листового узла, мы возвращаем единицу. Если список пустой, т.е. мы обработали весь текущий уровень, то возвращаем `0`. И в конце данная функция возвращает количество листьев в дереве.\n\n## Обход в глубину\n\nРассмотрим еще один пример, в котором не требуется накапливать результат. Предположим, что у нас есть список списков любой вложенности, который содержит числа. Все что нужно сделать — узнать, есть ли среди этих чисел хотя бы один ноль. Пример входных данных для подобной функции выглядит так: `l(1, l(5, 0), 10, l(l(8), 3))`.\n\nПрежде чем разбирать код, опишем алгоритм, по которому работает функция `hasZero`:\n\n* Рекурсивно обходим список:\n * Если список закончился, а `0` не найден, то возвращаем `false` (guard expression).\n * Если текущий элемент — не список, то проверяем, равен ли он нулю.\n * Если равен нулю, то возвращаем `true`.\n * Если текущий элемент — список, то запускаем `hasZero` рекурсивно, передав туда текущий элемент.\n * Если результат этого вызова `true`, то возвращаем `true`.\n * Если не сработали предыдущие терминальные условия, проверяем следующий элемент списка.\n\nТакой обход дерева называется обход в глубину. Сначала мы опускаемся до самого дна самой левой ветки, затем ветки чуть правее и так далее пока не дойдем до конца.\n\n```javascript\nimport { l, cons, head, tail, isEmpty, isList, toString } from '@hexlet/pairs-data'\n\nconst hasZero = (list) => {\n if (isEmpty(list)) {\n return false\n }\n\n const current = head(list)\n const rest = tail(list)\n if (!isList(current)) {\n if (current === 0) {\n return true\n }\n }\n else if (hasZero(current)) {\n return true\n }\n\n return hasZero(rest)\n}\n\nconsole.log(hasZero(l(1, 3, l(5, l(9), 3), 10))) // => false\nconsole.log(hasZero(l(1, l(l(5, 100), 0), 10))) // => true\n```\n\n## Агрегация\n\nБолее интересный и сложный пример связан с агрегацией: посчитать количество чего-либо в дереве или собрать список узлов, соответствующий какому-либо критерию. Общий механизм обхода в этих случаях останется абсолютно тем же, но к нему добавится аккумулятор, который нужно прокидывать до самой глубины. По сути, вся задача сводится к реализации рекурсивного `reduce`. Ниже код функции `searchZeros`, которая, в отличие от предыдущей реализации, возвращает число нулей в дереве:\n\n```javascript\nimport { l, cons, head, tail, isEmpty, isList, toString } from '@hexlet/pairs-data'\n\nconst searchZeros = (tree) => {\n const iter = (list, acc) => {\n if (isEmpty(list)) {\n return acc\n }\n\n const current = head(list)\n const rest = tail(list)\n if (!isList(current)) {\n const newAcc = current === 0 ? acc + 1 : acc\n return iter(rest, newAcc)\n }\n else {\n return iter(rest, iter(current, acc))\n }\n }\n\n return iter(tree, 0)\n}\n\nconsole.log(searchZeros(l(1, 3, l(5, l(9), 3), 10))) // => 0\nconsole.log(searchZeros(l(0, l(l(0, 100), 0), 10))) // => 3\n```\n\nГлавное в этом коде находится тут: `return iter(rest, iter(current, acc));`. Если `current` список, то прежде чем продолжать проверять список, по которому идет алгоритм, нужно выполнить поиск в найденном поддереве и так далее до самого дна. Соответственно, сначала отработает код `iter(current, acc)`, который вернет `acc` для поддерева, сложенный с текущим значением аккумулятора. В итоге, на выходе получится новый аккумулятор, который передается дальше.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":1351,"name":"theory","url":"/courses/sequences/lessons/intro/theory_unit"}],"links":[{"id":425614,"name":"Документация по функциям для работы с парами js-pairs","url":"https://github.com/hexlet-components/js-pairs/tree/main/docs"}],"ordered_units":[{"id":1351,"name":"theory","url":"/courses/sequences/lessons/intro/theory_unit"}],"id":686,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с курсом и проектом «Генератор HTML», который будет постепенно разрабатываться в течение всего курса","self_study":null,"theory_video_provider":"vimeo","theory_video_uid":"170597336","theory":"Этот курс — логическое продолжение предыдущего курса про [Составные данные](https://ru.hexlet.io/courses/compound_data). Теперь мы будем говорить о составных данных в чуть более сложном и продвинутом их виде. Как это часто бывает на Хекслете, данный курс будет не про JavaScript и не про изучение его возможностей, а про некоторые фундаментальные вещи, которые обязан знать программист, не зависимо от того, на каком языке он пишет код. JavaScript в данном случае всего лишь способ выражения той идеи, которую мы хотим донести.\n\n## Что такое последовательности\n\n\n\nПоследовательность — это упорядоченная совокупность объектов данных.\n\nДавайте разберём это определение. Оно звучит немного страшновато, но в реальности всё очень просто. Под совокупностью подразумевается некая единая сущность, а под объектами данных подразумевается всё, что угодно. В нашем случае, в программировании, это могут быть: числа, строки, составные объекты (например, пары). В математике последовательности представлены очень широко. Этому посвящены целые её разделы, например, матанализ, который изучает различные числовые последовательности: натуральные числа или числа Фибоначчи, с которыми мы так или иначе имеем дело когда учимся программированию. В реальной жизни с ними никто не сталкивается, но их любят использовать при обучении.\n\n- Натуральные числа\n\n```\n1, 2, 3, 4, 5, 6, 7, 8, ...\n```\n\n- Числа Фибоначчи\n\n```\n0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...\n```\n\n## Списки\n\n- список файлов\n- список дел\n- список сотрудников\n- список сайтов\n- список списков :)\n\nВ программировании гораздо чаще, чем с числовыми последовательностями, мы будем иметь дело со списками. Списки являются практически центральной частью в любой системе. Например, в функциональных языках манипуляции со списками являются ключевым моментом при написании кода.\n\nКакие бывают списки? Если на компьютере вы открываете папку, то видите список файлов. У вас где-то записан список дел, более того, вы можете их вести в онлайновых системах. На предприятии есть список сотрудников. А если вы ищете в Яндекс, Google или другом поисковике, то видите список сайтов с пейджингом (переключатель, который позволяет вам ходить по страницам одного большого списка). И даже такой мета-список, который вы видите сейчас перед собой в данный момент — список списков. Это тоже список. Так что списки можно комбинировать, делать из них новые списки и это очень напоминает то, что мы делали в курсе Составные данные.\n\nВ этом курсе мы будем разрабатывать проект, который называется **Генератор HTML**. Это библиотека, генерирующая части HTML-кода. Если на текущий момент вы не знакомы с HTML, то не стоит переживать, потому что в будущих уроках мы будем обязательно разбирать, что это такое, как он работает, для чего нужен и что такое языки разметки вообще. Это крайне простая тема, на освоение которой не уйдет много времени.\n\n## Принцип работы\n\nПринцип работы нашего проекта показан в примере ниже:\n\n```javascript\nimport {\n make, append, addChild, toString, node,\n} from '@hexlet/html-tags'\n\nconst ul1 = node('ul')\nconst ul2 = addChild(ul1, node('li', 'hello'))\nconst ul3 = addChild(ul2, node('li', 'world'))\n\nconst html1 = make()\nconst html2 = append(html1, ul3)\n```\n\nСейчас не обязательно пытаться полностью понять данный код. Он иллюстрирует, что в конечном итоге получится библиотека, в которой есть обычные функции, позволяющие нам строить древовидную структуру. Которая с одной стороны список,а с другой может быть сложней, чем список. Например, она может быть деревом, о чём будет рассказано позже. В конечном итоге после того, как мы создали эту структуру, мы можем её распечатать и увидеть, что генерируется такое представление:\n\n```javascript\ntoString(html2)\n// <ul>\n// <li>hello</li>\n// <li>world</li>\n// </ul>\n```\n\nЭтот кусочек и есть часть HTML-кода.\n\n## Применение\n\n- программная (динамическая) генерация\n- манипуляции\n- анализ (например, проверка корректности)\n- внутреннее представление в браузере\n\n## Зачем такие библиотеки вообще нужны?\n\n1. Программная (динамическая) генерация HTML очень востребованная операция. Существует большое количество сайтов, которые используют, например, технологии [Single Page Application](https://en.wikipedia.org/wiki/Single-page_application), когда не происходит перезагрузки страницы, а всё меняется прямо на самом сайте. HTML в таком случае действительно генерируется динамически и в итоге представление перерисовывается непосредственно на клиенте.\n\n2. Кроме этого такие библиотеки позволяют манипулировать текущим HTML, который уже загружен и используется. Собственно сайты это постоянно и делают, когда вы закрываете какое-то окно, что-то открываете или меняете на странице.\n\n3. Помимо самого использования есть и анализ, например, валидация или проверка корректности. Верификация — когда проверяется правильно ли создана структура, так как HTML обладает определёнными правилами построения и их можно нарушить. То есть когда в браузере идёт работа с HTML, можно создать HTML, который будет невалиден — не пройдет верификацию. Библиотеки позволяют анализировать и давать рекомендации о том, как лучше делать, а как лучше не делать.\n\n4. И самое интересное, что в реальности именно так внутри браузера и представлен HTML. Если мы загрузим какую-то страницу и посмотрим её исходный код, мы увидим, что там есть HTML как текст. Но текст — это всего лишь текстовое представление HTML, а в реальности он загружается в виде некоторой программной сущности. У неё есть специальное название — [DOM](https://en.wikipedia.org/wiki/Document_Object_Model). Модель построения элементов на веб-странице. Она хранится где-то внутри и ею можно манипулировать\n\n## Document Object Model\n\n```javascript\nconst impl = document.implementation\nlet doc = impl.createDocument('', '', null)\nlet peopleElem = doc.createDocument('people')\n\nlet personElem1 = doc.createDocument('personal')\npersonElem1.setAttribute('first-name', 'eric')\n\nlet addressElem = doc.createDocument('address')\naddressElem.setAttribute('street', '321 south st')\npersonElem1.appendChild(addressElem)\n\npeopleElem.apendChild(personElem1)\ndoc.appendChild(peopleElem)\n```\n\nЗдесь как раз показан пример того, как в браузере происходит манипуляция DOM, т.е. реальным представлением HTML, как он представлен в программном коде. Опять же, не нужно рассчитывать на то, что вы сейчас поймёте этот код, тем более, что он содержит некоторые новые аспекты. Важно, что та библиотека, которую мы разрабатываем, является по сути локальным представлением того, что происходит в браузере. Зная принципы её работы, умея самостоятельно её написать, вы будете легко ориентироваться в том, как это происходит в браузере. По сути мы делаем прототип настоящего DOM. Конечно, сложность очень сильно отличается, потому что DOM в браузере очень навороченная вещь. Наша библиотека гораздо проще, но главное это понять общий принцип.\n\n## Темы\n\n- списки, множества, деревья\n- отображение, фильтрация, агрегация\n- стандартные интерфейсы\n- уровневое проектирования\n\n### Какие темы мы рассмотрим в этом курсе?\n\nИх много, и они достаточно серьёзные.\n\n1. Первое — это структуры данных. Мы рассмотрим списки, познакомимся с понятием множеств и с деревьями.\n\n2. Мы научимся с ними работать посредством тройки функций: отображение (`map`), фильтрация (`filter`) и агрегация (`reduce`). Это тройка методов, которые используются для обработки различных списков, множеств, деревьев и других структур данных, которые существуют в JavaScript. Когда в будущем мы будем работать с внутренними структурами языка, вы увидите, что это основные способы манипуляции структурами в JS и, кстати, во всех функциональных языках и даже не в функциональных. Во всех достаточно продвинутых языках программирования, которые поддерживают функции высшего порядка, реализована эта тройка. А код с использованием данных методов является чаще всего каноническим, т.е. так принято и правильно писать, а не использовать, например, циклы, о чём мы позже обязательно поговорим.\n\n3. Мы познакомимся с таким подходом, как стандартные интерфейсы. Ярким примером данного подхода является конструктор для детей Lego.\n\n4. Также мы познакомимся с уровневым проектированием, которое касается не только программирования, а всей инженерной технической области. Именно оттуда оно берет начало и именно оттуда уровневому проектированию можно учиться.\n\n## Превосходство Хекслета\n\n- функциональный стиль и неизменяемость\n- СИКП\n- фокус на программировании, а не синтаксисе языка\n- двигаемся вперед не с помощью изучения новых фич языка, а путем комбинирования изученных инструментов и развития абстрактного мышления\n- нет нового синтаксиса\n\nНапоследок, несколько аспектов, почему курс сделан именно так. Почему и зачем мы освещаем эту тему на Хекслете.\n\nЭтот курс продолжает традицию предыдущего курса. Всё, что мы здесь делаем, будет неизменяемым. Мы используем функциональный стиль. Потому что введение состояния привносит множество сложностей и проблем, и на данном этапе для понимания темы это совершенно не нужно. Поэтому мы оставляем за скобками изменение. Библиотека может показаться вам немного странной, но чуть позже вы поймёте, почему она реализована именно так.\n\nПомимо этого курс продолжает традицию и также основан на СИКПе ([Структура и интерпретация компьютерных программ](https://web.mit.edu/6.001/6.037/sicp.pdf)). В нём отсутствует новый синтаксис. Мы в очередной раз подчёркиваем, что этот курс не про изучение JavaScript, он про изучение программирования. Пройдя его, вы действительно поймёте и, наконец, осознаете разницу между тем, что такое программировать и что такое знать синтаксис языка.\n\nВсё, что происходит в этом курсе — мы просто берём то, что уже изучили, начинаем это комбинировать и получаем какие-то новые возможности нашей системы, более сложное поведение. Данный подход развивает абстрактное мышление. Многие вещи, которые мы делаем в этом курсе, направлены не только на то, чтобы вы понимали, что такое программирование, но и постепенно начинали развивать свой мозг и тренировать его для того, чтобы он мог переваривать всё более и более сложные сущности и концепции, потому что изучение нового синтаксиса не привносит нового развития в части того, как вы размышляете. Оно не позволит вам автоматически строить действительно сложные программы. А вот абстрактное мышление — это как раз та вещь, которая влияет на это больше всего. Некоторые уроки и практические задания могут показаться весьма сложными, потому что в этом курсе будет много кода и он будет заставлять ваш мозг кипеть. В каком-то смысле это лакмусовая бумажка. Если вы пройдёте курс, действительно в нём разберётесь и поймёте его, то скорее всего дальнейшее обучение пройдет для вас достаточно легко и, в целом, в программировании у вас всё будет хорошо получаться.\n\nЖелаю удачи! Вперёд!\n"},"id":120,"slug":"sequences","challenges_count":10,"name":"JS: Последовательности","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы продолжите изучать составные данные на более продвинутом уровне и рассмотрите тип данных «список». Вы узнаете больше о функциях высшего порядка filter, map и reduce и иерархических структурах. В итоге вы научитесь строить сложные структуры данных на базе более простых и проектировать функции так, чтобы их можно было легко соединять друг с другом. Составные данные пригодятся, если вы решите работать над проектами, которые требуют обработку сложных структур данных. Знания из этого курса помогают программистам обрабатывать коллекции, представленные списками с помощью функций высшего порядка.","kind":"additional","updated_at":"2026-01-20T11:55:07.138Z","language":"javascript","duration_cache":36000,"skills":["Строить сложные структуры данных на базе более простых","Проектировать функции так чтобы их можно было легко соединять друг с другом","Обрабатывать коллекции представленные списками с помощью функций высшего порядка (map/filter/reduce)","Разделять код на уровни, выстраивая правильное взаимодействие между слоями"],"keywords":["функции высшего порядка","стандартные интерфейсы","уровневое проектирование"],"lessons_count":9,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTE0OCwicHVyIjoiYmxvYl9pZCJ9fQ==--d07901b5af81babe7daa5cc3f1853beb9c936160/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJqcGciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--39ba06fa99226096df9fc6bb31f84e1d29ea98e9/image.png"},"recommendedLandings":[{"stack":{"id":20,"slug":"js-sicp","title":"СИКП на JS","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":4050,"duration_in_months":1},"id":28,"slug":"js-sicp","title":"СИКП на JS","subtitle":"Навык понимать программы на фундаментальном уровне, уверенно проходить собеседования и решать сложные задачи","subtitle_for_lists":"Навык фундаментального программирования","locale":"ru","current":true,"duration_in_months_text":"1 месяц","stack_slug":"js-sicp","price_text":"от 3 900 ₽","duration_text":"1 месяц","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MCwicHVyIjoiYmxvYl9pZCJ9fQ==--9348098e4053d798b6f34bee4ef66947540261e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/sequences/lessons/hierarchy/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">JS: Последовательности</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Иерархические структуры</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Иерархические структуры","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"JS: Последовательности"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>До текущего момента мы работали только со списками. Но как мы помним свойство замыкания множества относительно операции позволяет строить из пар иерархические структуры, т.е. те структуры, части которых сами являются составными структурами. Самый яркий представитель иерархических структур - это деревья.</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTIwNiwicHVyIjoiYmxvYl9pZCJ9fQ==--e7cd18cc8db1f86e6e618576ad0fda7421a9d105/tree.png" alt="Деревья" loading="lazy"/></p>
<p>Деревья бывают очень разные. Вообще это название произошло от обычных деревьев, потому что на него можно смотреть, как на иерархическую структуру. На стволе расположены ветки, на ветках могут быть тоже ветки, на которых, в свою очередь, есть ещё ветки, на которых есть листья. Деревьев существует очень много видов как в реальной жизни, так и в программировании, в Computer Science их много десятков, если не сотен.</p>
<p>При этом все они обладают общими свойствами и имеют соответствующий словарь терминов. Давайте с ними познакомимся. Деревья в программировании так или иначе встречаются всем, любым программистам, на любом языке, в любой области.</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTIwOCwicHVyIjoiYmxvYl9pZCJ9fQ==--2e7ece6c9edacbbd070ade61dd214686262a3fec/tree2.png" alt="Деревья" loading="lazy"/></p>
<ul>
<li>Узел — это любой элемент дерева. Иногда говорят вершина, нода.</li>
<li>Корневой узел — самый верхний узел дерева (на картинке это узел под номером 8).</li>
<li>Лист — это узел, не имеющий дочерних элементов. Листья расположены в самой глубине дерева.</li>
</ul>
<h2 id="heading-2-1">Особенности</h2>
<ul>
<li>Рекурсивное определение</li>
<li>Естественный способ обработки - рекурсия</li>
</ul>
<p>Какие особенности присущи таким структурам?</p>
<p>Определение дерева является рекурсивным. Потому что постепенно идёт рекурсивный спуск в глубину, и любой элемент может оказаться тоже деревом или поддеревом, которые в свою очередь тоже могут содержать деревья или поддеревья. Как можно догадаться, естественный способ обработки таких элементов — это рекурсия.</p>
<p>Вот пример с использованием языка Haskell, в котором дерево можно определить как тип данных, используя по сути математическое определение.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">data Tree a = EmptyTree | Node a (Tree a) (Tree a)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Дерево - это либо пустое дерево (EmptyTree), либо нода, которая содержит в себе элемент (Node a), который содержит два поддерева (Tree a).</p>
<p>Это определение бинарного дерева, но общий принцип понятен. Фактически одна такая строчка определяет бинарное дерево любой сложности, т.е. все они будут подходить под это определение.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><html>
<body>
<h1>Сообщество</h1>
<p>Общение между пользователями Хекслета</p>
<div class='hexlet-community'>
<div class='text-xs-center'>
<div class='fa fa-spinner'></div>
</div>
</div>
</body>
</html></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Давайте вернемся к нашему HTML. В одном из первых уроков мы сказали, что на самом деле это древовидная структура, хотя мы работали с ним как со списком. Корнем является тег <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">html</code>. При этом вкладывать можно не всё во всё, есть определённые правила, по которым это делается. Но в любом случае это древовидная структура, которая позволяет нам строить иерархию любой глубины, что обычно и происходит. Поэтому в общем случае с HTML и работают как с деревом, а не просто списком.</p>
<p>Вспомним, что с помощью пар можно представлять деревья совершенно по-разному. В общем-то, благодаря замыканию мы можем построить любые типы деревьев.</p>
<p>Давайте посмотрим конкретную задачу, решаемую в контексте работы с деревьями. Фактически мы расширим ту функциональность, которую использовали для работы со списками. Для того, чтобы построить дерево, нам достаточно тех же самых списков, а в общем случае — пар.</p>
<h2 id="heading-2-2">Количество листьев</h2>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">import { head, tail, l } from 'hexlet-pairs-data'
const tree = l(l(1, 2), l(3, l(4, 5)), 6)
const countElements = (tree) => {
if (!isList(tree)) {
return 1
}
if (isEmpty(tree)) {
return 0
}
return countElements(head(tree)) + countElements(tail(tree))
}
countElements(tree) // 6</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В примере выше мы создаём дерево, по факту просто используя списки внутри списков, что достаточно очевидно. Можно представить, как дерево визуально будет выглядеть. У нас есть список, в котором есть вложенный список, дальше ещё один список, вложенный в другой список и какой-то элемент, т.е. листья есть на разных уровнях нашего дерева. Давайте попробуем подсчитать количество элементов. Здесь используется обычный рекурсивный процесс, в котором в конечном итоге происходит следующее.</p>
<p>Мы получаем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">head(tree)</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">tail(tree)</code>, "голову" и "хвост" списка. При этом в отличие от работы со списками в предыдущих уроках, "голова" тоже может оказаться поддеревом, поэтому функцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">countElements</code> мы рекурсивно применяем и к "голове" и к "хвосту". Нам нет разницы, работаем мы с левой или с правой частью, ведь расшириться вглубь может любая из них.</p>
<p>Данный вид рекурсии называется уже не линейной, а древовидной, так как каждый <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">countElements</code> внутри вызывает две функции <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">countElements</code>, что её раздваивает. Если посмотреть все вызовы этой функции, можно увидеть, что они "расползаются" по дереву.</p>
<p>В примере выше есть две проверки. Если это не список, т.е. мы дошли до листового узла, мы возвращаем единицу. Если список пустой, т.е. мы обработали весь текущий уровень, то возвращаем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">0</code>. И в конце данная функция возвращает количество листьев в дереве.</p>
<h2 id="heading-2-3">Обход в глубину</h2>
<p>Рассмотрим еще один пример, в котором не требуется накапливать результат. Предположим, что у нас есть список списков любой вложенности, который содержит числа. Все что нужно сделать — узнать, есть ли среди этих чисел хотя бы один ноль. Пример входных данных для подобной функции выглядит так: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">l(1, l(5, 0), 10, l(l(8), 3))</code>.</p>
<p>Прежде чем разбирать код, опишем алгоритм, по которому работает функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hasZero</code>:</p>
<ul>
<li>Рекурсивно обходим список:
<ul>
<li>Если список закончился, а <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">0</code> не найден, то возвращаем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">false</code> (guard expression).</li>
<li>Если текущий элемент — не список, то проверяем, равен ли он нулю.
<ul>
<li>Если равен нулю, то возвращаем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">true</code>.</li>
</ul>
</li>
<li>Если текущий элемент — список, то запускаем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hasZero</code> рекурсивно, передав туда текущий элемент.
<ul>
<li>Если результат этого вызова <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">true</code>, то возвращаем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">true</code>.</li>
</ul>
</li>
<li>Если не сработали предыдущие терминальные условия, проверяем следующий элемент списка.</li>
</ul>
</li>
</ul>
<p>Такой обход дерева называется обход в глубину. Сначала мы опускаемся до самого дна самой левой ветки, затем ветки чуть правее и так далее пока не дойдем до конца.</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">import { l, cons, head, tail, isEmpty, isList, toString } from '@hexlet/pairs-data'
const hasZero = (list) => {
if (isEmpty(list)) {
return false
}
const current = head(list)
const rest = tail(list)
if (!isList(current)) {
if (current === 0) {
return true
}
}
else if (hasZero(current)) {
return true
}
return hasZero(rest)
}
console.log(hasZero(l(1, 3, l(5, l(9), 3), 10))) // => false
console.log(hasZero(l(1, l(l(5, 100), 0), 10))) // => true</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h2 id="heading-2-4">Агрегация</h2>
<p>Более интересный и сложный пример связан с агрегацией: посчитать количество чего-либо в дереве или собрать список узлов, соответствующий какому-либо критерию. Общий механизм обхода в этих случаях останется абсолютно тем же, но к нему добавится аккумулятор, который нужно прокидывать до самой глубины. По сути, вся задача сводится к реализации рекурсивного <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">reduce</code>. Ниже код функции <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">searchZeros</code>, которая, в отличие от предыдущей реализации, возвращает число нулей в дереве:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">import { l, cons, head, tail, isEmpty, isList, toString } from '@hexlet/pairs-data'
const searchZeros = (tree) => {
const iter = (list, acc) => {
if (isEmpty(list)) {
return acc
}
const current = head(list)
const rest = tail(list)
if (!isList(current)) {
const newAcc = current === 0 ? acc + 1 : acc
return iter(rest, newAcc)
}
else {
return iter(rest, iter(current, acc))
}
}
return iter(tree, 0)
}
console.log(searchZeros(l(1, 3, l(5, l(9), 3), 10))) // => 0
console.log(searchZeros(l(0, l(l(0, 100), 0), 10))) // => 3</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Главное в этом коде находится тут: <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">return iter(rest, iter(current, acc));</code>. Если <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">current</code> список, то прежде чем продолжать проверять список, по которому идет алгоритм, нужно выполнить поиск в найденном поддереве и так далее до самого дна. Соответственно, сначала отработает код <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">iter(current, acc)</code>, который вернет <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">acc</code> для поддерева, сложенный с текущим значением аккумулятора. В итоге, на выходе получится новый аккумулятор, который передается дальше.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/js-sicp?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">1 месяц</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">СИКП на JS</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Навык фундаментального программирования</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc2MCwicHVyIjoiYmxvYl9pZCJ9fQ==--9348098e4053d798b6f34bee4ef66947540261e4/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Low%20code%20development-rafiki.png" alt="СИКП на JS" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/sequences/lessons/hierarchy/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 9</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/sequences/lessons/hierarchy/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>