{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Програмування",
    "_rss_description": "Всім привіт! Я — Богдан, фулстек розробник з Києва. Пишу в основному про програмування та штуки які вивчаю. Час від часу публікую підбірки фоток, зроблених на плівку, розказую про подорожі та цікаві речі, що оточують мене.",
    "_rss_language": "uk",
    "_itunes_email": "",
    "_itunes_categories_xml": "",
    "_itunes_image": "",
    "_itunes_explicit": "",
    "home_page_url": "https:\/\/www.stefaniuk.website\/tags\/programming\/",
    "feed_url": "https:\/\/www.stefaniuk.website\/tags\/programming\/json\/",
    "icon": "https:\/\/www.stefaniuk.website\/pictures\/userpic\/userpic@2x.jpg?1565716580",
    "authors": [
        {
            "name": "Bohdan Stefaniuk",
            "url": "https:\/\/www.stefaniuk.website\/",
            "avatar": "https:\/\/www.stefaniuk.website\/pictures\/userpic\/userpic@2x.jpg?1565716580"
        }
    ],
    "items": [
        {
            "id": "277",
            "url": "https:\/\/www.stefaniuk.website\/all\/push-notification-via-asp-net-core\/",
            "title": "Push Notification via ASP.NET Core",
            "content_html": "<p>Недавно запустили на роботі новий функціонал інбоксів. Якщо коротко, то це універсальний месенджер з підтримкою різних платформ та каналів що дозволяє вести комунікацію з кінцевими клієнтами в одному місці.<\/p>\n<p>Для кращого UX треба було добавити пуш нотифікації. На цей момент у нас уже були підключені веб сокети по яким ганяли різні дані, в тому числі і про нові повідомлення. Але такі сповіщення можна відобразити тільки коли відкрита сторінка в браузері.<\/p>\n<p>Тому вирішили добавити підтримку браузерних пушів. Саме про те як їх підключити та використовувати в звʼязці з ASP.NET Core я хочу розказати.<\/p>\n<h2><b>Як це працює?<\/b><\/h2>\n<p>Процес роботи технології пуш нотифікацій у браузері досить простий. Спочатку ми запрошуємо права на отримання сповіщень у користувача. Браузер покаже йому модалку із запитанням «Do you want to receive push notifications?».<\/p>\n<p>Після того як він підтвердить вибір нам потрібно підписати користувача на отримання нотифікацій використовуючи Push API браузера. Під капотом браузер робить запит на спеціальний push service та повертає нам обʼєкт PushSubscription в якому є вся необхідна інформація для відправки пушів:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{\r\n  &quot;endpoint&quot;: &quot;https:\/\/fcm.googleapis.com\/fcm\/send\/c1KrmpTuRm…&quot;,\r\n  &quot;expirationTime&quot;: null,\r\n  &quot;keys&quot;: {\r\n    &quot;p256dh&quot;: &quot;BGyyVt9FFV…&quot;,\r\n    &quot;auth&quot;: &quot;R9sidzkcdf…&quot;\r\n  }\r\n}<\/code><\/pre><p>Далі ми берем цей обʼєкт і відправляємо на наш сервер, де кладемо його у базу даних або зберігаємо іншим способом щоб використовувати його в подальшому.<\/p>\n<p>Якщо придивитися до структури PushSubscription, можна помітити адрес ендпоінта. Суть в тому, що наш сервер не відправляє сповіщення напряму в браузер. Для цього використовується спеціальний push service, який у кожного розробника браузера свій.<\/p>\n<div style=\"max-width: 740px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/push-notifications-structure.png\" width=\"1761\" height=\"646\" alt=\"\" \/>\n<\/div>\n<\/div><p>Нам повезло, що браузери змогли домовитись і стандартизувати протокол push сервіса, тому нам можна не паритись за формат даних для кожного вендора. Усі вони приймають один формат даних, який містить:<\/p>\n<ul>\n<li>Контент повідомлення<\/li>\n<li>Кому його відправити<\/li>\n<li>Як саме його відправити, з яким пріорітетом, в який топік та з яким таймаутом.<\/li>\n<\/ul>\n<p>Кожне повідомлення шифрується за допомогою ключів, що були створені при генерації підписки. Крім цього, потрібно також підписувати повідомлення за допомогою приватного ключа, щоб підтвердити, що це саме наш сервер відправляє повідомлення. Виглядає це ось так:<\/p>\n<ol start=\"1\">\n<li>Генеруємо пару публічного та приватного ключа, які ще називають VAPID ключами. Цей крок треба зробити всього один раз. В інтернеті купа онлайн сервісів де можна згенерувати ключі.<\/li>\n<li>Публічний ключ використовуємо при створенні підписки на стороні браузера. Дальше цей ключ асоціюється з ендпоінтом для конкретної підписки.<\/li>\n<li>Приватний ключ використовуємо для підписки повідомлення на стороні нашого сервера.<\/li>\n<li>Дальше за допомогою публічного ключа ми можемо перевірити що повідомлення було відправлено саме нашим сервером а не кимось іншим.<\/li>\n<\/ol>\n<p>Структура VAPID ключа:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{\r\n    &quot;sub&quot;: &quot;mailto:bohdan@stefaniuk.io&quot;,\r\n    &quot;public_key&quot;: &quot;sdgdklsnvi2f0m-12dgsg...&quot;,\r\n    &quot;private_key&quot;: &quot;1fn0ASFfnaksf...&quot;\r\n }<\/code><\/pre><p>Але, як в тому жарті, є нюанс. Subject в VAPID ключі є опціональним і його не обовʼязково відправляти разом з запитом на push сервіс. Проте Сафарі так не вважає, ще й вимагає специфічний формат. Коли я генерував ключі, то мав ось такий sub <i>mailto: &lt;bohdan@stefaniuk.io&gt;<\/i>.<\/p>\n<p>В хромі все працювало чудово, а сафарі кидав помилку, що запит невалідний. Виявилось, що вони не підтримують пробіли та кутові лапки, замінив sub на <i>mailto:bohdan@stefaniuk.io<\/i> і все запрацювало.<\/p>\n<h2><b>Реалізація бекенду<\/b><\/h2>\n<p class=\"note-md\">Якщо у вас macOS, то ви не зможете локально протестувати сповіщення. Проблема в тому що .NET не підтримує шифрування AesGcm на маках, тому при відправці вилетить ексепшен. Поки я не знайшов як це обійти.<\/p>\n<p>На стороні сервера будемо використовувати бібліотеку <i>Lib.Net.Http.WebPush<\/i> яка під капотом буде шифрувати, підписувати та відправляти наші сповіщення. Для цього вона дає декілька базових модельок та один клас для відправки пушів.<\/p>\n<p>Почнемо з реалізації створення та видалення підписок та зберігання їх в БД. Для цього добавимо клас ApplicationPushSubscription, який описує структуру таблиці та підключимо його в Entity Framework контекст:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">public class ApplicationPushSubscription\r\n{\r\n    [Key]\r\n    public string P256Dh { get; set; }\r\n    public string Endpoint { get; set; }\r\n    public string Auth { get; set; }\r\n    public Guid UserId { get; set } \/\/ Id користувача в нашій системі\r\n}\r\n\r\npublic class DatabaseContext : DbContext\r\n{\r\n    public DbSet&lt;ApplicationPushSubscription&gt; PushSubscriptions { get; set; }\r\n}<\/code><\/pre><p>Тепер створимо клас PushNotificationsService в який добавимо методи для створення та видалення підписок. Метод створення буде приймати екземпляр класу PushSubscription з бібліотеки Lib.Net.Http.WebPush.<\/p>\n<p>Його можна було б змінити на свій клас, щоб логіка бібліотеки не просочувалась в інші модулі але задля простоти залишимо так як є.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">public class PushNotificationsService\r\n{\r\n    private readonly DatabaseContext _context;\r\n    private readonly PushServiceClient _pushClient;\r\n    \r\n    public PushNotificationsService (DatabaseContext context, PushServiceClient pushClient)\r\n    {\r\n        _context = context;\r\n        _pushClient = pushClient;\r\n    }\r\n    \r\n    public async Task CreateSubscription(Guid userId, PushSubscription pushSubscription)\r\n    {\r\n        var subscription = new ApplicationPushSubscription()\r\n        {\r\n            UserId = userId,\r\n            AccountId = currentUser.AccountId,\r\n            Endpoint = subscription.Endpoint,\r\n            P256Dh = subscription.GetKey(PushEncryptionKeyName.P256DH),\r\n            Auth = subscription.GetKey(PushEncryptionKeyName.Auth)\r\n        };\r\n        \r\n        if (await _context.PushSubscriptions.AnyAsync(x =&gt; x.P256Dh == subscription.P256Dh))\r\n        {\r\n            return;\r\n        }\r\n        \r\n        _context.PushSubscriptions.Add(subscription);\r\n    \r\n        try\r\n        {\r\n            await _context.SaveChangesAsync();\r\n        }\r\n        catch (DbUpdateException)\r\n        {\r\n            var alreadyExists = await _context.PushSubscriptions.AnyAsync(x =&gt; x.P256Dh == subscription.P256Dh);\r\n            if (!alreadyExists)\r\n            {\r\n                throw;\r\n            }\r\n        }\r\n    }\r\n    \r\n    public async Task DeleteSubscription(Guid userId, string endpoint)\r\n    {\r\n        var subscription = await _context.PushSubscriptions\r\n            .FirstOrDefaultAsync(x =&gt; x.UserId == userId &amp;&amp; x.Endpoint == endpoint);\r\n        \r\n        if (subscription == null)\r\n        {\r\n            return;\r\n        }\r\n        \r\n        _context.PushSubscriptions.Remove(subscription);\r\n        await _context.SaveChangesAsync();\r\n    }\r\n}<\/code><\/pre><p>Реалізовуємо метод для відправки сповіщень нашим користувачам. Він буде приймати Id користувача якому ми відправляємо повідомлення і модель з даними.<\/p>\n<p>Нам залишається отримати підписку з БД, серіалізувати повідомлення в JSON і скормити це все бібліотеці.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">public async Task SendNotificationAsync(Guid userId, PushNotificationModel model)\r\n{\r\n    try\r\n    {\r\n        var applicationPushSubscriptions = await _context.PushNotifications\r\n            .Where(x =&gt; x.UserId == userId)\r\n            .ToList();\r\n            \r\n        foreach(var applicationPushSubscription in applicationPushSubscriptions)\r\n        {\r\n            var pushSubscription = new PushSubscription();\r\n            pushSubscription.Endpoint = applicationPushSubscription.Endpoint;\r\n            pushSubscription.SetKey(PushEncryptionKeyName.P256DH, applicationPushSubscription.P256Dh);\r\n            pushSubscription.SetKey(PushEncryptionKeyName.Auth, applicationPushSubscription.Auth);\r\n            \r\n            var settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };\r\n            var payload = JsonConvert.SerializeObject(model, settings);\r\n    \r\n            var pushMessage = new PushMessage(payload);\r\n            await _pushClient.RequestPushMessageDeliveryAsync(\r\n                pushSubscription,\r\n                pushMessage,\r\n                _pushClient.DefaultAuthentication,\r\n                VapidAuthenticationScheme.WebPush);\r\n        }\r\n    }\r\n    catch (PushServiceClientException exception)\r\n    {\r\n        if (exception.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Gone)\r\n        {\r\n            _context.PushSubscriptions.Remove(subscription);\r\n            await _context.SaveChangesAsync();\r\n        }\r\n        else\r\n        {\r\n            \/\/ Re-Throw exception or log\r\n        }\r\n    }\r\n    catch (Exception exception)\r\n    {\r\n        \/\/ Re-Throw exception or log\r\n    }\r\n}<\/code><\/pre><p>Структура класу <i>PushNotificationModel<\/i> досить проста та універсальна. Я передаю заголовок сповіщення, текст та додаткові дані:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">public class PushNotificationModel\r\n{\r\n    public string Title { get; set; }\r\n    public string Body { get; set; }\r\n    public object AdditionalData { get; set; }\r\n}<\/code><\/pre><p>Підключаємо наш сервіс в ендопінт:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">[Route(&quot;api\/push-notifications&quot;)]\r\n[Authorize]\r\n[ApiController]\r\npublic class PushNotificationsController : ControllerBase\r\n{\r\n    private readonly PushNotificationService _pushNotificationService;\r\n\r\n    public PushNotificationsController(PushNotificationService pushNotificationService)\r\n    {\r\n        _pushNotificationService = pushNotificationService;\r\n    }\r\n    \r\n    [HttpPost(&quot;subscriptions&quot;)]\r\n    public async Task&lt;IActionResult&gt; CreateSubscription([FromBody] PushSubscription subscription)\r\n    {\r\n        var user = HttpContext.GetCurrentUser(); \/\/ Our custom method to get current logged-in user\r\n        await _pushNotificationService.CreateSubscription(user.Id, subscription);\r\n        return Ok();\r\n    }\r\n\r\n    [HttpDelete(&quot;subscriptions&quot;)]\r\n    public async Task&lt;IActionResult&gt; DeleteSubscription([FromQuery] string endpoint)\r\n    {\r\n        var user = HttpContext.GetCurrentUser();\r\n        await _pushNotificationService.DeleteSubscription(user.Id, endpoint);\r\n        return Ok();\r\n    }\r\n}<\/code><\/pre><p>Все що залишається — підключити наші класи в DI і все готово. Для простоти підключення можемо добавити ще один пакет <i>Lib.AspNetCore.WebPush<\/i>.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">services.AddTransient&lt;PushNotificationService&gt;();\r\nservices.AddMemoryVapidTokenCache();\r\nservices.AddPushServiceClient(options =&gt;\r\n{\r\n    var clientOptions = configuration.GetSection(nameof(PushServiceClient));\r\n    options.Subject = clientOptions.GetValue&lt;string&gt;(nameof(options.Subject));\r\n    options.PublicKey = clientOptions.GetValue&lt;string&gt;(nameof(options.PublicKey));\r\n    options.PrivateKey = clientOptions.GetValue&lt;string&gt;(nameof(options.PrivateKey));\r\n});<\/code><\/pre><h2><b>Реалізація фронтенда<\/b><\/h2>\n<p class=\"note-md\">Підтримка технології Web Push була додана в Сафарі тільки в версії MacOS 13 та вище.<\/p>\n<p>Перше, що потрібно реалізувати на стороні клієнта — це запит прав на отримання сповіщень. Для цього використовуємо обʼєкт <i>Notification<\/i>:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">\/\/ Запит у браузера прав на підключення пушей\r\nasync function askPermission() {\r\n    const permissionPromiseResult = await new Promise(function (resolve, reject) {\r\n        const permissionResult = Notification.requestPermission(function (result) {\r\n            resolve(result);\r\n        });\r\n\r\n        if (permissionResult) {\r\n            permissionResult.then(resolve, reject);\r\n        }\r\n    });\r\n    \r\n    if (permissionPromiseResult !== 'granted') {\r\n        alert(&quot;We weren't granted permission.&quot;);\r\n    } else {\r\n        await subscribeUserToPush();\r\n    }\r\n}<\/code><\/pre><p>Після того, як отримали права, нам потрібно зареєструвати service worker в якому буде логіка відображення сповіщень, створити підписку та відправити її на наш сервер.<\/p>\n<p>При створенні підписки необхідно передати наш публічний ключ. Але він повинен бути в форматі масиву байтів, тому треба спочатку його декодувати і вже тоді передавати.<\/p>\n<p>В результаті отримуємо обʼєкт <i>pushSubscription<\/i>, який ми відправляємо на створений раніше ендпоінт. Крім цього, ще відправляємо <i>Bearer<\/i> токен, щоб розуміти до якого користувача привʼязати підписку.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">const publicKey = &quot;vapid-public-key&quot;;\r\n\r\nasync function subscribeUserToPush() {\r\n    const registration = await navigator.serviceWorker.register('\/service-worker.js');\r\n    const subscribeOptions = {\r\n        userVisibleOnly: true,\r\n        applicationServerKey: urlBase64ToUint8Array(publicKey),\r\n    };\r\n\r\n    var pushSubscription = await registration.pushManager.subscribe(subscribeOptions);\r\n   \r\n    fetch('http:\/\/localhost:5000\/api\/push-notifications\/subscriptions', {\r\n        method: 'POST',\r\n        headers: {\r\n            'Content-Type': 'application\/json',\r\n            'Authorization': 'Bearer 1245'\r\n        },\r\n        body: JSON.stringify(pushSubscription)\r\n    }).then(function (response) {\r\n        if (response.ok) {\r\n            alert('Successfully subscribed for Push Notifications');\r\n        } else {\r\n            alert('Failed to store the Push Notifications subscription on server');\r\n        }\r\n    }).catch(function (error) {\r\n        console.log('Failed to store the Push Notifications subscription on server: ' + error);\r\n    });\r\n    \r\n    return pushSubscription;\r\n}\r\n\r\nfunction urlBase64ToUint8Array(base64String) {\r\n    var padding = '='.repeat((4 - (base64String.length % 4)) % 4);\r\n    var base64 = (base64String + padding).replace(\/\\-\/g, '+').replace(\/_\/g, '\/');\r\n\r\n    var rawData = window.atob(base64);\r\n    var outputArray = new Uint8Array(rawData.length);\r\n\r\n    for (var i = 0; i &lt; rawData.length; ++i) {\r\n        outputArray[i] = rawData.charCodeAt(i);\r\n    }\r\n    return outputArray;\r\n};<\/code><\/pre><p>Наш сервіс воркер буде максимально простим. Для початку додамо логіку відображення пушей та передачі в них додаткових даних з сервера.<\/p>\n<p>Також необхідно додати логіку обробки кліку по сповіщенню. Ми будемо перевіряти чи відкритий сайт в браузері. Якщо ні, то відкривати його в новій вкладці. А якщо у нас вже є вкладка з сайтом, то переключаємось на неї і робимо редірект на потрібну сторінку.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">self.addEventListener('push', function (event) {\r\n    var data = event.data.json();\r\n    \/\/ {\r\n    \/\/     &quot;title&quot;: &quot;New Notification&quot;,\r\n    \/\/     &quot;body&quot;: &quot;This is the body of the notification&quot;\r\n    \/\/     &quot;additionalData&quot;: {\r\n    \/\/         &quot;pageId&quot;: &quot;12512515&quot;\r\n    \/\/     }\r\n    \/\/ }\r\n\r\n    event.waitUntil(self.registration.showNotification(data.title, {\r\n        body: data.body,\r\n        icon: '\/logo.png',\r\n        data: {\r\n            pageId: data.additionalData?.pageId,\r\n        },\r\n    }));\r\n});\r\n\r\nself.addEventListener('notificationclick', function (event) {\r\n    var notification = event.notification;\r\n\r\n    event.waitUntil(\r\n        self.clients.matchAll().then(function (allClients) {\r\n            if (allClients.length === 0) {\r\n                self.clients.openWindow(\r\n                    `${self.location.origin}\/page\/${notification?.data?.pageId}`,\r\n                );\r\n                notification.close();\r\n                return;\r\n            }\r\n\r\n            allClients[0]?.navigate(`\/page\/${notification?.data?.pageId}`);\r\n            allClients[0]?.focus();\r\n            notification.close();\r\n        }),\r\n    );\r\n});<\/code><\/pre><p>В результаті всіх цих маніпуляцій у нас є сервер, який вміє відправляти сповіщення та клієнт, який вміє створювати підписки, відображати сповіщення та обробляти кліки.<\/p>\n<p>Окремо ще можна поговорити про топіки та пріоритети сповіщень, але це виходить за рамки цієї статті. Можливо, розкажу про це пізніше :)<\/p>\n<h2><b>Ресурси<\/b><\/h2>\n<ul>\n<li><a href=\"https:\/\/web.dev\/push-notifications-overview\/\">Оглад того як працюють пуші<\/a><\/li>\n<li><a href=\"https:\/\/www.tpeczek.com\/2017\/12\/push-notifications-and-aspnet-core-part.html\">Стаття з прикладом реалізації пушів за допомогою ASP.NET<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/tpeczek\/Demo.AspNetCore.PushNotifications\">Репозиторій з прикладами використання бібліотеки<\/a><\/li>\n<\/ul>\n",
            "date_published": "2023-06-08T20:43:17-04:00",
            "date_modified": "2023-06-08T20:42:47-04:00",
            "tags": [
                "asp.net core",
                "код",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/push-notifications-structure.png",
            "_date_published_rfc2822": "Thu, 08 Jun 2023 20:43:17 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "277",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/push-notifications-structure.png"
                ]
            }
        },
        {
            "id": "271",
            "url": "https:\/\/www.stefaniuk.website\/all\/ordered-queue-and-heap\/",
            "title": "Очередь с приоритетом",
            "content_html": "<p>Представим что нужно спроектировать список задач. У каждой задачи есть приоритет, это значит, что вначале должны быть самые приоритетные задачи. Сами задачи можно обрабатывать только по одной, мы не можем взять задачу с центра списка.<\/p>\n<div style=\"max-width: 450px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-pic-1.png\" width=\"628\" height=\"240\" alt=\"\" \/>\n<\/div>\n<\/div><p>Такой список достаточно просто реализовать с помощью обычного массива. Задачи будем хранить в случайном порядке, а в момент получения текущей задачи будем искать самую приоритетную. В таком случае добавление новой задачи будет иметь сложность O(1), а вот получение O(n), потому что каждый раз нужно искать элемент с самым большим приоритетом.<\/p>\n<p>Второй вариант тоже использует обычный массив, только элементы в нем хранятся уже в отсортированном виде. В таком случае сложность меняется зеркально. Добавление нового элемента будет занимать O(n), а получение O(1).<\/p>\n<p>Оба варианта не совсем эффективны, особенно когда очень много данных. Здесь на помощь приходят очереди с приоритетом.<\/p>\n<p>Очереди с приоритетом строятся на базе такой структуры данных как куча. Поэтому важно рассмотреть ее устройство. Куч есть большое множество, но нас сейчас интересует только двоичная куча.<\/p>\n<h2>Двоичная куча<\/h2>\n<p>Двоичная куча представляет из себя двоичное дерево где каждый элемент имеет два дочерних элемента и хранит значение не меньшее чем в дочерних. Они также делятся на два типа: максимальная и минимальная. В первой хранятся элементы в порядке убывания, а во второй в порядке возрастания.<\/p>\n<div style=\"max-width: 720px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-max-min-2.png\" width=\"1587\" height=\"712\" alt=\"\" \/>\n<\/div>\n<\/div><p>Для хранения данных кучи удобно использовать обычные массивы. Такой подход занимает мало места и сам по себе достаточно элегантный. На как определить какой элемент массива на какой ссылается? Для этого есть две простые формулы.<\/p>\n<ul>\n<li>2i + 1 — позволяет найти позицию левого дочернего элемента<\/li>\n<li>2i + 2 — позволяет найти позицию правого дочернего элемента<\/li>\n<\/ul>\n<div style=\"max-width: 720px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-array-structure-3.png\" width=\"1500\" height=\"552\" alt=\"\" \/>\n<\/div>\n<\/div><h2>Сортировка дерева<\/h2>\n<div style=\"max-width: 760px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-sorting-4.png\" width=\"2104\" height=\"695\" alt=\"\" \/>\n<\/div>\n<\/div><p>Когда добавляем или удаляем элемент из очереди, свойства кучи могут нарушиться. В таком случае нужно привести ее в порядок. Представим, что есть метод MakeHeap, который принимает на вход индекс элемента и он сортирует его поддерево.<\/p>\n<p>Берем элемент по текущему индексу и сравниваем его с дочерними элементами. Если какой-то дочерний элемент больше текущего, меняем его местами с текущим и спускаемся дальше, чтобы проверить поддерево. Таким образом, мы доходим до конца изначального поддерева сортируя все его поддеревья.<\/p>\n<p>Как это выглядит в коде:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">private void SortHeap(int i)\r\n{\r\n\tvar size = values.Count;\r\n\tvar left = 2 * i + 1;\r\n\tvar right = 2 * i + 2;\r\n\tvar largest = i;\r\n\t\r\n\tif (left &lt; size &amp;&amp; values[left] &gt; values[largest])\r\n\t\tlargest = left;\r\n\t\t\r\n\tif (right &lt; size &amp;&amp; values[right] &gt; values[largest])\r\n\t\tlargest = right;\r\n\t\t\r\n\tif (largest != i)\r\n\t{\r\n\t\tvar temp = values[i];\r\n\t\tvalues[i] = values[largest];\r\n\t\tvalues[largest] = temp;\r\n\t\tSortHeap(largest);\r\n\t}\r\n}<\/code><\/pre><h2>Делаем кучу из обычного массива<\/h2>\n<p>Мы умеем сортировать элементы в поддеревьях, теперь применим этот метод для того, чтобы с обычного массива сделать кучу. Так как потомки гарантированно есть у первых (n\/2 — 1) элементов, то достаточно только для них вызвать метод SortHeap.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">private void MakeHeap()\r\n{\r\n\tfor (int i = values.Count \/ 2 - 1; i &gt;= 0; i--)\r\n\t{\r\n\t\tSortHeap(i);\r\n\t}\r\n}<\/code><\/pre><h2>Добавление нового элемента<\/h2>\n<div style=\"max-width: 760px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-insert-5.png\" width=\"2225\" height=\"650\" alt=\"\" \/>\n<\/div>\n<\/div><p>Новый элемент сначала добавляется в конец массива, после чего запускаем сортировку дерева. Алгоритм получается достаточно простым:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">if (values.Count == 0)\r\n{\r\n\t\/\/ Просто добавляем в элемент в массив, если он пустой\r\n\tvalues.Add(value);\r\n}\r\nelse\r\n{\r\n\t\/\/ Добавляем элемент в конец массива и запускаем сортировку дерева\r\n\tvalues.Add(value);\r\n\tfor (int i = values.Count \/ 2 - 1; i &gt;= 0; i--)\r\n\t{\r\n\t\tSortHeap(i);\r\n\t}\t\t\r\n}<\/code><\/pre><h2>Удаление элемента<\/h2>\n<div style=\"max-width: 760px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/heap-remove-5.png\" width=\"2183\" height=\"652\" alt=\"\" \/>\n<\/div>\n<\/div><p>Чтобы удалить элемент нужно поменять его с последним элементов в куче, потом удалить и запустить сортировку кучи. Так как мы делаем очередь, то будем всегда удалять первый (корневой) элемент кучи.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var size = values.Count;\r\n\t\t\r\nvar last = values.Count - 1;\r\nvar temp = values[0];\r\nvalues[0] = values[last];\r\nvalues[last] = temp;\r\nvalues.RemoveAt(last);\r\n\r\nfor (int i = size \/ 2 - 1; i &gt;= 0; i--)\r\n\tSortHeap(i);\r\n\r\nreturn temp;<\/code><\/pre><h2>Итого<\/h2>\n<p>Куча обладает необходимыми свойствами для создания очереди с приоритетом. Она хранит данные в отсортированном виде, работает быстрее чем обычный массив. Достаточно реализовать такие же методы как в обычной очереди и у нас получиться очередь с приоритетом:<\/p>\n<ul>\n<li>Enqueue — обычная вставка в кучу, ничего даже менять не нужно.<\/li>\n<li>Dequeue — обычное удаление элемента с кучи, только удаляем мы всегда элемент с 0 индексов.<\/li>\n<\/ul>\n<p>Посмотреть полную реализацию можно на <a href=\"https:\/\/dotnetfiddle.net\/Smd2bH\">dotnetfiddle<\/a>.<br \/>\n <\/p>\n",
            "date_published": "2022-07-05T16:29:29-04:00",
            "date_modified": "2022-07-05T16:28:12-04:00",
            "tags": [
                "dotnet",
                "алгоритми та структури даних",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/heap-pic-1.png",
            "_date_published_rfc2822": "Tue, 05 Jul 2022 16:29:29 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "271",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-pic-1.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-max-min-2.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-array-structure-3.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-sorting-4.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-insert-5.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/heap-remove-5.png"
                ]
            }
        },
        {
            "id": "269",
            "url": "https:\/\/www.stefaniuk.website\/all\/binary-search-and-binary-search-tree\/",
            "title": "Бинарный поиск и бинарное дерево поиска",
            "content_html": "<p>Бинарный поиск один из самых популярных и простых алгоритмов поиска. Он активно используется во многих системах, например в базах данных для быстрого поиска и индексирования.<\/p>\n<p>Первое что нужно знать — алгоритм работает только на отсортированных данных. Поэтому перед поиском нужно отсортировать массив.<\/p>\n<p>Начинается алгоритм с того что берем элемент из середины массива и сравниваем с искомым значением. Если искомый элемент больше среднего, тогда искомое значение лежит в правой части массива. Идем в правую часть массива, опять берем элемент из середины и сравниваем его с искомым. В этот раз искомый элемент меньше среднего, тогда он скорее всего находится в левой части этого под массива.<\/p>\n<p>Таким образом мы постоянно сравниваем искомое значение со значением из середины массива. После этого откидываем половину значений. Это здорово ускоряет поиск, потому что не нужно проверять все элементы массива. На каждом этапе отбрасывается половина элементов.<\/p>\n<p>В итоге всех операций наступает момент когда искать приходиться в массиве из одного элемента. В таком случае просто сравниваем с искомым, если равны, тогда мы нашли элемент, если не, тогда не нашли.<\/p>\n<div style=\"max-width: 720px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/binary-search-1.png\" width=\"524\" height=\"215\" alt=\"\" \/>\n<\/div>\n<\/div><p>А теперь живой пример. Есть отсортированный массив и нужно найти число 9. Берем элемент из середины — 8, сравниваем, 9 больше 8, значит нужно продолжить поиск в правой части.<\/p>\n<div style=\"max-width: 720px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/binary-search-2.png\" width=\"497\" height=\"200\" alt=\"\" \/>\n<\/div>\n<\/div><p>Снова берем элемент из середины — 11, 9 меньше 11, значит нужно искать в левой части нашего подмассива.<\/p>\n<div style=\"max-width: 720px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/binary-search-3.png\" width=\"740\" height=\"200\" alt=\"\" \/>\n<\/div>\n<\/div><p>Берем элемент из середины — 9, сравниваем, элементы равны, число 9 есть в массиве и храниться по индексу 4. Мы нашли нужный элемент всего за 3 шага.<\/p>\n<p>Если пробовать оценить скорость работы алгоритма, то получается что она равна O(log n). Для сравнения, обычный поиск перебором имеет скорость O(n), это значит что нужно перебрать все элементы массива. Что это значит на практике?<\/p>\n<div style=\"max-width: 720px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/binary-search-speed.png\" width=\"968\" height=\"170\" alt=\"\" \/>\n<\/div>\n<\/div><p>Посмотреть реализацию на C# можно на <a href=\"https:\/\/dotnetfiddle.net\/LjCl90\">dotnetfiddle<\/a>.<\/p>\n<h2>Как оптимизировать сортировку?<\/h2>\n<p>Массив всегда должен быть отсортированным чтобы поиск работал, а это значит что при каждом добавлении нового элемента нужно запускать сортировку. Возникает вопрос, а что если сразу вставлять новый элемент в правильное место? Тогда у нас всегда будет отсортированный массив и не нужно каждый раз прогонять сортировку.<\/p>\n<p>Так же подумали умные ребята и придумали бинарное дерево поиска. Это структура данных, которая основывается на работе бинарного поиска. По сути это реализация бинарного поиска в виде структуры данных.<\/p>\n<p>Структура дерева достаточно простая: каждый элемент может иметь только два дочерних элемента. Их еще часто называют левым и правым. В левом дочернем элементе храниться значение, меньше чем в родительском, а в правом — больше.<\/p>\n<h2>Как работает поиск в дереве?<\/h2>\n<p>Сравниваем искомое значение со значением в корневом элементе. Если оно больше, тогда мы идем к правому дочернему элементу и повторяем процедуру. Таким образом мы начинаем с самого верха и плавно опускаемся пока не найдем элемент с таким же значением.<\/p>\n<div style=\"max-width: 720px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/binary-search-tree-1.png\" width=\"399\" height=\"324\" alt=\"\" \/>\n<\/div>\n<\/div><p>Вставка нового элемента работает так же как и поиск. Сравниваем новый элемент с текущим и опускаемся по дереву вниз пока не найдем подходящее место. В итоге при каждом добавлении у нас будет отсортированный набор данных.<\/p>\n<div style=\"max-width: 720px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/binary-search-tree-2.png\" width=\"468\" height=\"410\" alt=\"\" \/>\n<\/div>\n<\/div><p>Но что будет если всегда добавлять элементы с все большим и большим значением? Наше дерево будет выглядеть вот так:<\/p>\n<div style=\"max-width: 720px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/binary-search-tree-3.png\" width=\"562\" height=\"512\" alt=\"\" \/>\n<\/div>\n<\/div><p>По сути для поиска значения на нужно обойти все элементы дерева, а это значит что скорость работы упадет до O(n). Получается что бинарное дерево поиска в среднем работает со скоростью O(log n), а в худшем O(n).<\/p>\n<p>Такие деревья еще называют не сбалансированными. Чтобы исправить ситуацию существуют специальные алгоритмы балансировки деревьев, которые приводят их в нормальный вид. Но рассматривать здесь я их не буду.<\/p>\n<h2>Итог<\/h2>\n<p>Бинарный поиск один из самых простых и элегантных алгоритмов поиска, который привел к созданию новой структуры данных — бинарного дерева поиска. Данная структура является очень популярной и используется по многих системах. Особенно активно в базах данных для создания индексов и оптимизации выборки данных.<\/p>\n<p>Если тема интересная, то можно копнуть дальше и разобраться с:<\/p>\n<ul>\n<li>Красно черными деревьями<\/li>\n<li>Кучами<\/li>\n<li>B-деревьями<\/li>\n<li>и т. д.<\/li>\n<\/ul>\n<h2>Бонус<\/h2>\n<p>На сайте <a href=\"https:\/\/idea-instructions.com\"><a href=\"https:\/\/idea-instructions.com\">https:\/\/idea-instructions.com<\/a><\/a> есть объяснения разных алгоритмов в стиле инструкций с икеи. Например <a href=\"https:\/\/idea-instructions.com\/avl-tree\/\">AVL дерево<\/a><\/p>\n",
            "date_published": "2022-06-11T14:00:56-04:00",
            "date_modified": "2022-06-13T15:26:28-04:00",
            "tags": [
                "алгоритми та структури даних",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/binary-search-1.png",
            "_date_published_rfc2822": "Sat, 11 Jun 2022 14:00:56 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "269",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/binary-search-1.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/binary-search-2.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/binary-search-3.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/binary-search-speed.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/binary-search-tree-1.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/binary-search-tree-2.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/binary-search-tree-3.png"
                ]
            }
        },
        {
            "id": "265",
            "url": "https:\/\/www.stefaniuk.website\/all\/transactions-and-acid\/",
            "title": "Транзакции в базах данных и ACID",
            "content_html": "<p>Транзакции поддерживают все реляционные базы данных и некоторые NoSQL базы. Представляют они из себя простой набор команд, который должен быть выполнен как одно целое. В большинстве случает транзакция представляет некую бизнес операцию.<\/p>\n<p>Базы данных гарантируют что все запросы в рамках транзакции выполняться как одно целое или не выполняться вообще. Отталкиваясь от этой концепции был придуман набор требований к транзакциям и системам, которые их используют. Эти правила гарантируют надежную работу транзакций. Такой набор требований называется ACID.<\/p>\n<p>ACID гарантирует что данные в БД будут целостные независимо от любых сбоев.<\/p>\n<p>Всего есть четыре свойства у транзакций:<\/p>\n<ul>\n<li><b>Atomicity (атомарность)<\/b> — команды внутри транзакции буду выполнены все вместе или ни одной. То есть транзакция это атомарная команда. Достигается это за счет системы откатов и журнала транзакций. Если внутри транзакции какой-то запрос выполнился с ошибкой, то все изменения, сделанные в рамках этой транзакции откатываются.<\/li>\n<li><b>Сonsistency (консистентность)<\/b> — данные должны быть консистентными после выполнения транзакции. Это значит что в них нету логических и технических противоречий. Например: суммарный баланс счетов должен оставаться неизменным, это логическая целостность. Записи в таблицах не ссылаются на удаленные идентификаторы в другой таблице — техническая целостность.<\/li>\n<li><b>Isolation (изолированность)<\/b> — транзакции зачастую обрабатываются параллельно, это значит что они не должны влиять друг на друга. Так если два человека одновременно пересылают деньги третьему, то одна транзакция может переписать данные другой и деньги потеряются. На практике изоляция достигается за счет уровней изоляции.<\/li>\n<li><b>Durability (стойкость)<\/b> — если транзакция завершена успешно, то она не может быть отменена даже при авариях, внезапном отключении света в датацентре и проблем в сети. В этом случае база данных должна сама восстановить последние изменения.<\/li>\n<\/ul>\n<p>Отдельно стоит упомянуть уровни изоляции, потому что их понимание позволяет находить баги в коде, который работает с базой. Самих уровней существую большое множество, но рассмотрим четыре основных:<\/p>\n<ul>\n<li><b>read uncommited<\/b> — позволяет избежать потерянных обновлений, когда две транзакции изменяют одни и те же данные. Для этого одна транзакция блокирует данные, которые хочет изменить для других UPDATE  операций в других транзакциях.<\/li>\n<li><b>read committed<\/b> — решает проблему грязного чтения, когда вычитываем данные во время их обновления. Это может привести к тому что мы получим частично обновленные данные. В таком случае UPDATE операции в транзакции блокируют UPDATE и SELECT операции в других транзакциях. Именно этот уровень изоляции используется по умолчанию в большинстве БД.<\/li>\n<li><b>repeatable read<\/b> — внутри транзакции может быть несколько операций SELECT, которые читают одни и те же данные. Repeatable read гарантирует что это операции внутри одной транзакции будут возвращать одинаковые данные. Даже если другие транзакции хотят их удалить или изменить. В таком случает транзакция блокирует все строчки, которые затрагивают операции UPDATE и SELECT.<\/li>\n<li><b>serializable<\/b> — исключает проблему «фантомных чтений», которые возникают при вставке новой строки между двумя операциями SELECT внутри одной транзакции. Больше всего эта проблема влияет на агрегационные запросы. Такой уровень изоляции является самым строгим, все транзакции выполняются так, будто других параллельных транзакций не существует.<\/li>\n<\/ul>\n",
            "date_published": "2022-05-06T10:20:27-04:00",
            "date_modified": "2022-04-30T10:27:31-04:00",
            "tags": [
                "бази даних",
                "програмування"
            ],
            "_date_published_rfc2822": "Fri, 06 May 2022 10:20:27 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "265",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "266",
            "url": "https:\/\/www.stefaniuk.website\/all\/folder-by-type-and-folder-by-feature\/",
            "title": "Folder by Type и Folder by Feature",
            "content_html": "<p>ASP.NET, как и большинство других фреймворков, предлагают использовать свою структуру организации файлов в проекте. Почти всегда используется folder by type подход. Он предполагает разделение файлов по типу. Так все контроллеры лежать в одной папке, вьюхи в другой и т. д.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">com.example\r\n├── Domain\r\n│    ├── User.cs\r\n│    └── Pet.cs\r\n├── Controllers\r\n│    ├── UserController.cs\r\n│    └── PetController.cs\r\n├── Repositories\r\n│    ├── UserRepository.cs\r\n│    └── PetRepository.cs\r\n├── Services\r\n│    ├── UserService.cs\r\n│    └── PetService.cs\r\n│   \/\/ and everything else in the project\r\n└── Startup.cs<\/code><\/pre><p>Такой подход неплохо работает на небольших проектах, когда файлов немного и фичи состоят из ограниченного количества классов. Проблемы возникают при росте проекта. Когда разрабатываешь большую фичу нужно постоянно держать в голове что в какой папке лежит, постоянно переключаясь между ними. Частично эту проблему решают IDE, но только частично.<\/p>\n<p>Альтернативой является folder by feature подход, при котором все файлы одной фичи лежат в отдельной папке. В таком случае все необходимое для конкретной фичи лежит под рукой и нет ничего лишнего.<\/p>\n<p>Это позволяет:<\/p>\n<ul>\n<li>Упростить навигацию в проекте.<\/li>\n<li>Построить высокоуровневую абстракцию — открываешь проект и сразу понятно что он из себя представляет, из чего состоит.<\/li>\n<li>Выделить вертикальные слои.<\/li>\n<\/ul>\n<pre class=\"e2-text-code\"><code class=\"\">com.example\r\n├── Pet\r\n│    ├── Pet.cs\r\n│    ├── PetController.cs\r\n│    ├── PetRepository.cs\r\n│    └── PetService.cs\r\n├── User\r\n│    ├── User.cs\r\n│    ├── UserController.cs\r\n│    ├── UserRepository.cs\r\n│    └── UserService.cs\r\n│   \/\/ and everything else in the project\r\n└── Startup.cs<\/code><\/pre><p>Обычно проблемы возникают когда появляется общий код, который нужно использовать в нескольких частях приложения. Но, в принципе, это известная проблема и ее можно решить грамотной нарезкой на вертикальные слои. Вторая проблема достаточно банальная — привычки, большинство программистов привыкли к классической компоновки.<\/p>\n<p>Ссылки:<br \/>\n<a href=\"https:\/\/softwareengineering.stackexchange.com\/questions\/338597\/folder-by-type-or-folder-by-feature\">Folder-by-type or Folder-by-feature<\/a><\/p>\n",
            "date_published": "2022-04-30T09:59:29-04:00",
            "date_modified": "2022-05-01T11:15:12-04:00",
            "tags": [
                "код",
                "програмування"
            ],
            "_date_published_rfc2822": "Sat, 30 Apr 2022 09:59:29 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "266",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "267",
            "url": "https:\/\/www.stefaniuk.website\/all\/links-24-04-2022\/",
            "title": "Интересные статьи",
            "content_html": "<p>Решил вернуться к своему блогу после столь долгой паузы. Начну с небольшой подборки статей, которые я недавно прочитал и которые мне очень понравились.<\/p>\n<p><a href=\"https:\/\/boringtechnology.club\">Choose Boring Technology<\/a> — статья-презентация от одного из первых разработчиков Etsy. Рассказывает про выбор технологий для проекта. Основная идея в том чтобы выбирать «скучные», а если быть точнее, то хорошо изученные и устоявшиеся технологии. Горячо советую почитать.<\/p>\n<p><a href=\"https:\/\/lethain.com\/introduction-to-architecting-systems-for-scale\/\">Introduction to architecting systems for scale<\/a> — кратко о том из чего состоят приложения, которые рассчитаны для масштабирование.<\/p>\n<p><a href=\"https:\/\/jvns.ca\/blog\/2022\/03\/13\/celebrate-tiny-learning-milestones\/\">Celebrate tiny learning milestones<\/a> — не совсем техническая статья. Вместо того чтобы придумывать себе большие цели на год, лучше придумать набор небольших целей «майлстоунов» и идти по ним.<\/p>\n<p><a href=\"https:\/\/jvns.ca\/blog\/brag-documents\">Get your work recognized: write a brag document<\/a> — очень частая проблема это распознавание того что ты сделал на проекте. Когда приходит перформанс ревью, то бывает сложно вспомнить все что ты сделал за год. Автор советует завести документ в который нужно записывать результаты своей работы, а именно то, что было сделано и к чему это привело.<\/p>\n<p><a href=\"https:\/\/henrikwarne.com\/2020\/07\/23\/good-logging\/\">Good Logging<\/a> — коротко о том какими должны быть логи чтобы ими было удобно пользоваться. Ну и сразу еще две статьи от этого же автора: <a href=\"https:\/\/henrikwarne.com\/2013\/05\/05\/great-programmers-write-debuggable-code\/\">Great Programmers Write Debuggable Code<\/a> и <a href=\"https:\/\/henrikwarne.com\/2014\/01\/01\/finding-bugs-debugger-versus-logging\/\">Finding Bugs: Debugger versus Logging<\/a><\/p>\n",
            "date_published": "2022-04-24T05:39:07-04:00",
            "date_modified": "2023-06-02T20:18:58-04:00",
            "tags": [
                "system-design",
                "лінки",
                "програмування"
            ],
            "_date_published_rfc2822": "Sun, 24 Apr 2022 05:39:07 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "267",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "256",
            "url": "https:\/\/www.stefaniuk.website\/all\/repository-pattern\/",
            "title": "Паттерн «Репозиторий»",
            "content_html": "<p>Репозиторий один из самых популярных паттернов для доступа к данным. Он используется для абстрагирования от конкретной реализации и нюансов работы с источником данных, внешних сервисов, файловой системой и т. д.<\/p>\n<p>С одной стороны, это очень простой паттерн, который позволяет скрыть сложность работы с БД. Но с другой стороны, спросите 10 программистов описать этот шаблон и вы получите 10 разных реализаций.<\/p>\n<p>Самая большая проблема этого шаблона — множество вопросов, которые возникают при его реализации и дальнейшей поддержки.<\/p>\n<p>Изначально хотел сам расписать проблемы, но за меня это уже сделали, поэтому настоятельно советую прочитать статью: <a href=\"https:\/\/blog.byndyu.ru\/2011\/01\/domain-driven-design-repository.html\">«Проблемы паттерна Репозиторий».<\/a><\/p>\n<p>Если кратно, то:<\/p>\n<ul>\n<li>Что делать если репозиториям нужно использовать закрытые методы друг друга?<\/li>\n<li>Можно ли использовать один репозиторий на весь проект или делать репозиторий на каждую сущность?<\/li>\n<li>Нужно ли дублировать методы репозитория в сервис или мы можем напрямую использовать репозиторий в контроллерах?<\/li>\n<li>Нужно ли возвращать IQueryable и как это повлияет на дизайн системы в случае с .NET кодом? Если нет, то как правильно изменять сущности без использования Change Tracking?<\/li>\n<li>Как правильно объединить репозиторий и UoW?<\/li>\n<\/ul>\n<p>Отдельно также хочу отметить доклад: <a href=\"https:\/\/www.youtube.com\/watch?v=3yPpL1rEK9o&t=1208s\">Денис Цветцих «Repository и UnitOfWork в 2020 году, must have или антипаттерн?»<\/a>. В нем также поднимаются проблемы репозитория, способы их решения и варианты замены этот шаблона на другой.<\/p>\n<p>Репозиторий — хороший паттерн, который должен быть в арсенале любого программиста, но стоит использовать его с умом и учитывать проблемы, которые он может вызвать.<\/p>\n<p>Для себя я решил что репозиторий хорошо подходит, когда вся логика приложения вписывается в CRUD модель. Но если логика более сложная или приложение подразумевает Task Base UI, тогда лучше прибегнуть к подходу CQRS. Он позволяет разбить сложную бизнес логику включая репозитории на независимые объекты, каждый из которых выполняет только одну бизнес задачу или use case.<\/p>\n<div style=\"max-width: 750px\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/repository-pattern-orm-db.png\" width=\"1200\" height=\"579\" alt=\"\" \/>\n<\/div>\n<\/div><p>Я кроме все прочего не люблю репозитории за:<\/p>\n<ul>\n<li>Ограничение функциональности ORM, большенство специфических операций недоступны. В зависимости от реализации можем потерять Change Tracking.<\/li>\n<li>Дополнительный мапинг из доменных объектов в DTO.<\/li>\n<li>Дополнительный слой абстракции, который зачастую просто не нужен. Особенно если речь идет о небольших сервисах где логику нужно делать максимально простой и незамысловатой.<\/li>\n<li>Работает только в простых CRUD сценариях.<\/li>\n<\/ul>\n",
            "date_published": "2021-07-08T08:50:13-04:00",
            "date_modified": "2023-06-02T20:19:28-04:00",
            "tags": [
                "system-design",
                "код",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/repository-pattern-orm-db.png",
            "_date_published_rfc2822": "Thu, 08 Jul 2021 08:50:13 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "256",
            "_e2_data": {
                "is_favourite": true,
                "links_required": [],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/repository-pattern-orm-db.png"
                ]
            }
        },
        {
            "id": "253",
            "url": "https:\/\/www.stefaniuk.website\/all\/message-queues\/",
            "title": "Очереди сообщений",
            "content_html": "<p>При работе с большими системами, которые состоят из множества компонентов, возникает вопрос: «Как интегрировать несколько приложений для работы друг с другом?». Для этого нужен надёжный, асинхронный и быстрый способ передачи данных, который также позволит передавать информацию в разных форматах.<\/p>\n<div style=\"max-width: 650px\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/queue-general.png\" width=\"1936\" height=\"789\" alt=\"\" \/>\n<\/div>\n<\/div><p>Все эти свойства есть в системе обмена сообщениями. Они полезны когда:<\/p>\n<ul>\n<li>Нужно отправить данные из точки А в точку Б.<\/li>\n<li>Нужно интегрировать несколько систем.<\/li>\n<li>Нужно масштабирование.<\/li>\n<li>Нужна возможность мониторить потоки данных.<\/li>\n<li>Нужна асинхронная обработка.<\/li>\n<li>Нужна буферизация.<\/li>\n<\/ul>\n<h2>Концепт обмена сообщениями<\/h2>\n<p>Обмен сообщениями неновое изобретение. Он активно используется внутри операционной системы для обмена информацией между несколькими процессами (inter-process communication) и для обмена между несколькими потоками внутри процесса (inter-thread communication).<\/p>\n<p>Самый простой способ представить очередь сообщений как длинную трубу в которую можно помещать шарики. Мы можем написать сообщение на шаре и закинуть его в трубу и кто-то получит его на другой стороне. Из такой концепции можно понять несколько вещей.<\/p>\n<ul>\n<li>Отправитель не имеет представления о том кто именно примет сообщение.<\/li>\n<li>Отправитель не переживает о том когда его сообщение будет получено.<\/li>\n<li>Отправитель может засовывать сколько угодно сообщений с удобной ему скорость.<\/li>\n<li>Это не повлияет на получателя, ведь он сам решает сколько сообщений ему удобно доставать.<\/li>\n<li>Ни отправитель, ни получатель не знают об устройстве друг друга.<\/li>\n<li>Ни отправитель, ни получатель не переживают про нагрузку и вместительность друг друга.<\/li>\n<li>Также они не знаю о том где каждый из них находиться. Они могут быть в одной комнате, в разных комнатах или зданиях.<\/li>\n<\/ul>\n<p>Все эти свойства позволяют разделить две системы с точки зрения ответственности, времени, пропускной способности, внутреннего устройства, нагрузки и географии. Само по себе разделение является очень важной частью большой распределенной системы. Чем больше независимых компонентов, тем проще их разрабатывать, тестировать и запускать независимо от других.<\/p>\n<p>Также очереди работают по модели «издатель-подписчик». В ней издатель оправляет сообщение в очередь, а подписчик смотрит в очередь и извлекает интересные ему сообщения, после чего занимается их обработкой. Благодаря этой концепции, осуществляется асинхронная коммуникация.<\/p>\n<div style=\"max-width: 650px\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/queue-pub-sub.png\" width=\"1617\" height=\"597\" alt=\"\" \/>\n<\/div>\n<\/div><h2>Причины использовать очереди сообщений<\/h2>\n<p><b>Избыточность и поддержка транзакций<\/b><br \/>\nПеред тем как сообщение будет удалено может потребоваться подтверждение, что приложение, которое прочитало сообщение, успешно его обработало. В случае возникновения проблем, сообщение не потеряется, что позволит повторно его обработать.<\/p>\n<p><b>Масштабирование<\/b><br \/>\nОчереди распределяют процессы обработки информации. Это дает возможность гибко реагировать на нагрузку и добавлять или убирать дополнительные обработчики.<\/p>\n<p><b>Эластичность<\/b><br \/>\nОчереди сообщений позволяют выровнять нагрузку на приложение за счет буферизации данных. В случае большой нагрузки на систему очередь будет накапливать сообщения, а сами обработчики будут работать в нормальном режиме самым облегчая обработку данных и не допуская отказа системы.<\/p>\n<p><b>Отказоустойчивость<\/b><br \/>\nВ случае отказа обработчиков, очередь продолжит сохранять все сообщения, что позволит обработать их когда система поднимется, тем самым не потеряв никаких данных.<\/p>\n<p><b>Decoupling and coupling<\/b><br \/>\nС помощью очередей можно достичь двух противоположных целей.<\/p>\n<ul>\n<li>Decoupling. Если у нас большой монолит, то будет сложно интегрировать новые фичи. В таком случае очередь сообщений позволяет разъединить одно приложения на несколько независимых компонентов и настроить коммуникацию между ними.<\/li>\n<li>Coupling. Иногда нужно, чтобы несколько систем работали как одно целое, очередь позволяют создать промежуточный слой для коммуникации между разными элементами системы.<\/li>\n<\/ul>\n<p><b>Понимание потоков данных<\/b><br \/>\nПо сути, очереди накладывают ограничения на передачу данных между компонентами, что позволяет очень легко мониторить как обрабатываются данные. Благодаря этой информации можно понять как работает система и ее компоненты. Например, некоторые очереди могут простаивать, а некоторые всегда забиты, тем самым показывая где у нас есть проблемы с обработкой.<\/p>\n<h2>Гарантированная доставка<\/h2>\n<p>Гарантированная доставка является одной из ключевых характеристик системы обмена сообщений. Всего есть три типа доставки:<\/p>\n<ul>\n<li>at least once<\/li>\n<li>at most once<\/li>\n<li>exactly once<\/li>\n<\/ul>\n<p><b>At least once<\/b><br \/>\nСамый простой способ доставки при котором обработчик получает одно и то же сообщение до тех пор, пока не удалит его из очереди или не подтвердит получение. Это значит что возможны ситуации, когда приложение обрабатывает одно сообщение несколько раз.<\/p>\n<p>Такая особенность подразумевает, что обработчик будет корректно работать в случае дублирования сообщений. А такое может случаться часто. Например, упала сеть в момент, когда приложение подтверждало получение — оно получит его ещё раз.<\/p>\n<p>Если в сообщениях хранятся результаты каких-то измерений, у которых есть временная метка, то ничего страшного не произойдёт, если мы получим хоть миллион дубликатов. Но если в сообщениях храниться информация о финансовых транзакциях то будет совсем не круто обработать его дважды. Но такую ситуацию можно решить используя уникальные идентификаторы для сообщений и хранить список сообщений, которые мы уже обработали.<\/p>\n<p>С другой стороны, такой подход гарантирует 100% получение сообщения. Даже если получатель сообщения упадёт, до того как подтвердит обработку, то он просто ещё раз его обработает после перезапуска.<\/p>\n<p><b>At most once<\/b><br \/>\nОчень редкий сценарий, к нему прибегают когда двойная обработка сообщения может привести к серьезным проблемам. В таких очередях мы предпочтем потерять сообщение чем обработать его дважды.<\/p>\n<p>Если приложение упадет во время обработки сообщения, то мы его повторно не получим и оно будет считаться утерянным.<\/p>\n<p><b>Exactly Once<\/b><br \/>\nИдеальный сценарий работы очереди, но проблема в том что его нереально воплотить в жизнь из-за множества проблема, которые сами по себе решить сложно, не говоря уже об их совокупности.<\/p>\n<p>Все они происходят из двух утверждений:<\/p>\n<ul>\n<li>Отправители и получатели не идеальны<\/li>\n<li>Сеть не идеальна.<\/li>\n<\/ul>\n<p>Что порождает такие проблемы как:<\/p>\n<ul>\n<li>Отправитель может забыть отправить сообщение<\/li>\n<li>Сеть между отправителем и очередью может упасть<\/li>\n<li>Сеть между очередью и получателем может упасть<\/li>\n<li>База данных самой очереди может не сохранить сообщение<\/li>\n<li>Подтверждение что сообщение обработано может не дойти до очереди и отправителя<\/li>\n<\/ul>\n<p>Именно поэтому очень сложно гарантировать одноразовую доставку сообщения. Намного проще сделать систему устойчивой к дубликатам сообщений и использовать подход at-least-once.<\/p>\n<h2>Компоненты очереди сообщений<\/h2>\n<p>Система обмена сообщениями состоит из нескольких компонентов, которые позволяют ей нормально функционировать и выполнять свои задачи.<\/p>\n<div style=\"max-width: 650px\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/queue-components.png\" width=\"1456\" height=\"543\" alt=\"\" \/>\n<\/div>\n<\/div><p><b>Сообщения<\/b> — любые данные, которые нужно передать через очередь конвертируются в сообщение, которые состоят из двух частей:<\/p>\n<ul>\n<li>Заголовки (headers) — в них расположена служебная информация, которая используется самой очередью для правильной обработки сообщения.<\/li>\n<li>Тело (body) — информация, которую мы передаем с помощью очереди.<\/li>\n<\/ul>\n<p><b>Каналы (channels)<\/b> — логические соединения между приложениями и системой очередей, которые предоставляют изолированную коммуникацию. Каналы позволяют передавать сообщение в одном из двух режимов:<\/p>\n<ul>\n<li>point-to-point — протокол, который обеспечивает прямую коммуникацию между двумя приложениями.<\/li>\n<li>publish-subscribe — протокол, в котором отправитель сообщения не знает конкретного получателя, а просто отправляет сообщение в очередь, на которую могут быть подписаны потребители.<\/li>\n<\/ul>\n<p><b>Маршрутизатор (router)<\/b> — помещает сообщения из каналов по разным очередям используя ключ маршрутизации из заголовков сообщения.<\/p>\n<p><b>Очередь (queue)<\/b> — хранилище для наших сообщений, которое может находиться как в оперативной памяти так и на диске.<\/p>\n<h2>Виды очередей<\/h2>\n<p>Очереди сообщений можно разделить на несколько видов. Обычно несколько видом могут быть реализованы в одном продукте.<\/p>\n<p><b>broadcast (fanout) exchange<\/b><br \/>\nВ таком случае отправляется копия сообщения во все доступные очереди, ключ маршрутизации игнорируется.<\/p>\n<div style=\"max-width: 650px\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/queue-fanout-exchange.png\" width=\"1414\" height=\"617\" alt=\"\" \/>\n<\/div>\n<\/div><p><b>direct exchange<\/b><br \/>\nВсе сообщения имеют свой ключ маршрутизации, который определяет в какую очередь нужно положить сообщение. В дальнейшем сообщения будут переданы по принципу Round Robin подписанным обработчикам. Это значит что только один обработчик получит сообщение.<\/p>\n<div style=\"max-width: 650px\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/queue-direct-exchange.png\" width=\"1356\" height=\"574\" alt=\"\" \/>\n<\/div>\n<\/div><p><b>topic exchange (multicast)<\/b><br \/>\nТакие очереди подписаны на получение сообщений чей ключ подпадает под определенный паттерн. Если ключ маршрутизации подходит для нескольких очередей, то каждая получит по своей копии.<\/p>\n<p>Обычно ключи маршрутизации стараются делать в иерархическом виде. Это достигается за счет разделения логических частей (слов) точками. Например вот так:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">[region].[availability-zone].[service].[instance]\r\n\r\neu-east.az1.computer.homepc<\/code><\/pre><p>Сами же паттерны создаются с использованием специальных символов:<\/p>\n<ul>\n<li>* — заменитель только для одного слова.<\/li>\n<li># — заменитель для нескольких слов<\/li>\n<\/ul>\n<p>Что позволяет сделать такие шаблоны:<\/p>\n<ul>\n<li>*.*.computer.* — очередь, которая может обработать сообщения только от компьютеров без разницы где они находятся.<\/li>\n<li>eu-east.# — очередь, которая может обрабатывать сообщения только из зоны eu-east<\/li>\n<\/ul>\n<div style=\"max-width: 650px\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/queue-topic-exchange.png\" width=\"1307\" height=\"576\" alt=\"\" \/>\n<\/div>\n<\/div><h2>Протоколы<\/h2>\n<p>Исторически сложилось так, что почти все существующие протоколы для работы с очередями были проприетарными. Это накладывало массу ограничений, потому что не все языки поддерживали тот или иной протокол.<\/p>\n<p>Спустя какое-то время появились три открытых стандарта, которые сейчас повсеместно используются:<\/p>\n<ul>\n<li><b>Advanced Message Queuing Protocol (AMQP)<\/b> — бинарный протокол, который проектировался для  взаимодействия между различными вендорами и стал заменой существующих проприетарных протоколов. Основными особенностями AMQP является надежности и совместимость.<\/li>\n<li><b>Streaming Text Oriented Messaging Protocol (STOMP)<\/b> — простой текстовый протокол обмена сообщениями, который очень похож на HTTP и работает поверх TCP.<\/li>\n<li><b>MQTT (formerly MQ Telemetry Transport)<\/b> — очень простой и легковесный протокол, который разрабатывался для минимального использования трафика и работы в нестабильной сети. Все эти качества идеально подошли для использования протокола для общения между устройствами.<\/li>\n<\/ul>\n<h2>Материалы<\/h2>\n<ul>\n<li><a href=\"http:\/\/fulmanski.pl\/tutorials\/computer-science\/big-data\/message-queues\/\">Message queues<\/a> — отличная статья, которая описывает основные концепты работы очередей.<\/li>\n<li><a href=\"https:\/\/sudhir.io\/the-big-little-guide-to-message-queues\/\">The Big Little Guide to Message Queues<\/a> — большой гайд в котором описано про причины создания очередей их свойства и особенности работы, а также кратный разбор самых популярных реализаций.<\/li>\n<\/ul>\n",
            "date_published": "2021-05-18T02:17:40-04:00",
            "date_modified": "2021-05-18T02:17:55-04:00",
            "tags": [
                "system-design",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/queue-general.png",
            "_date_published_rfc2822": "Tue, 18 May 2021 02:17:40 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "253",
            "_e2_data": {
                "is_favourite": true,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/queue-general.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/queue-pub-sub.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/queue-components.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/queue-fanout-exchange.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/queue-direct-exchange.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/queue-topic-exchange.png"
                ]
            }
        },
        {
            "id": "252",
            "url": "https:\/\/www.stefaniuk.website\/all\/collection-in-csharp\/",
            "title": "Коллекции в C#",
            "content_html": "<p>В C# для хранения набора однотипных данных можно использовать массивы. Но с ними не всегда удобно работать потому, что они имеют фиксированный размер и часто бывает сложно угадать, какого размера нужен массив.<\/p>\n<p>Для решения этих задач в C# есть коллекции. Они позволяют динамически изменять свой размер. Также они удобны тем что некоторые из них представляют из себя готовые реализации стандартных структур данных, таких как список, хеш таблица, стек, очередь.<\/p>\n<p>Все коллекции лежат в нескольких пространствах имен:<\/p>\n<ul>\n<li>System.Collections — простые необобщенные коллекции.<\/li>\n<li>System.Collections.Generic — обобщенные коллекции.<\/li>\n<li>System.Collections.Specialized — специальные коллекции.<\/li>\n<li>System.Collections.Concurrent — коллекции для работы в многопоточной среде.<\/li>\n<\/ul>\n<h2>Устройство коллекций<\/h2>\n<p>Все коллекции, так или иначе, реализую интерфейс ICollection, некоторые реализуют интерфейсы IList и IDictionary (которые внутри наследуют ICollection). Этот интерфейс предоставляет минимальный набор методов, которые позволяют реализовать коллекцию.<\/p>\n<p>В свою очередь, ICollection расширяет интерфейс IEnumerable. Он предоставляет нумератор, который позволяет обходить коллекции элемент за элементом. Именно этот интерфейс позволяет использовать коллекции в цикле foreach.<\/p>\n<div style=\"max-width:440px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/collections-interfaces.png\" width=\"1496\" height=\"900\" alt=\"\" \/>\n<\/div>\n<\/div><h2>Вместительность коллекций<\/h2>\n<p>Одна из ключевых особенностей коллекций это изменяемый размер. Когда создается экземпляр коллекции она внутри себя инициализирует какую-то структуру данных, зачастую это массив. По умолчанию этот массив имеет определённую вместительность, которую можно просмотреть с помощью свойства Capacity.<\/p>\n<div style=\"max-width:440px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/collections-capacity.png\" width=\"900\" height=\"607\" alt=\"\" \/>\n<\/div>\n<\/div><p>После активного наполнения коллекции наступает момент, когда внутренний массив заполнен и мы не можем добавить новый элемент. В таком случае коллекция создаёт новый массив с большей вместительностью (обычно в два раза больше) и копирует туда данные со старого массива.<\/p>\n<div style=\"max-width:440px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/collections-capacity-copy.png\" width=\"900\" height=\"554\" alt=\"\" \/>\n<\/div>\n<\/div><p>Поэтому коллекции, которые основаны на массивах имеют сложность вставки:<\/p>\n<ul>\n<li>O(1) — когда вместительности достаточно.<\/li>\n<li>O(n) — когда вместительности недостаточно и нужно копировать данные в массив побольше.<\/li>\n<\/ul>\n<p>Чтобы избежать уменьшения производительности нужно при создании коллекции указать необходимую нам вместительности. Это позволит уменьшить количество копирований.<\/p>\n<h2>Сравнивание и сортировка элементов коллекций<\/h2>\n<p><b>Сравнение<\/b><br \/>\nТакие методы как Contains, IndexOf, LastIndexOf, and Remove используют сравнение элементов для свое работы. Если коллекция является обобщенной, то используются два механизма сравнения:<\/p>\n<ul>\n<li>Если тип реализует интерфейс IEquatable тогда механизм сравнения использует метод Equals этого интерфейса.<\/li>\n<li>Если тип не реализует интерфейс IEquatable тогда для сравнения используется Object.Equals<\/li>\n<\/ul>\n<p>Некоторые коллекции имеют конструктор, который принимает имплементацию IEqualityComparer<T> который используется для сравнения.<\/p>\n<p><b>Сортировка<\/b><br \/>\nСортировка работает похожим образом, как и сравнение, но делиться на два вида: явную сортировку и сортировка по умолчанию.<\/p>\n<p>Сортировка по умолчанию подразумевает, что типы хранящиеся в коллекции реализуют интерфейс IComparable, чьи методы под капотом используют коллекции для сравнения.<\/p>\n<p>Явная сортировка подразумевает, что наши элементы не реализуют интерфейс IComparable, поэтому в качестве параметра метода сортировки нужно передать объект, который реализует интерфейс IComparer.<\/p>\n<p>Если тип не реализует интерфейс IComparable и мы не передали явно тип, который реализует IComparer, то при вызове метода сортировки вылетит исключение.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">System.InvalidOperationException: Failed to compare two elements in the array.\r\n\tSystem.ArgumentException: At least one object must implement IComparable.<\/code><\/pre><h2>Алгоритмическая сложность коллекций<\/h2>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/collection-compexity-4.png\" width=\"1056\" height=\"414\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Источник: <a href=\"http:\/\/c-sharp-snippets.blogspot.com\/2010\/03\/runtime-complexity-of-net-generic.html\">Runtime Complexity of .NET Generic Collection<\/a><\/div>\n<\/div>\n<h2>Список List<T><\/h2>\n<p>Класс List<T> представляет из себя простейший список однотипных элементов, которые можно получить по индексу. Предоставляет методы для поиска, сортировки и изменения списка.<\/p>\n<!--\r\n<div style=\"width:400px;\">\r\ncollections-list.png\r\n<\/div>\r\n--><pre class=\"e2-text-code\"><code class=\"\">var linkedList = new List&lt;string&gt;();\r\nlinkedList.Add(&quot;A&quot;);\r\nlinkedList.Add(&quot;B&quot;);\r\nlinkedList.Add(&quot;C&quot;);<\/code><\/pre><p>Для своей работы списки используют обычные массивы. Это значит что могут быть проблемы с производительностью из-за частого создания нового массива. Также у списков есть еще два нюанса:<\/p>\n<ul>\n<li>Вставка элемента в середину списка приведет к созданию нового массива и копирование данных, что негативно влияет на производительность.<\/li>\n<li>Списки не могут хранить экстремально огромное количество элементов из-за фрагментации адресного пространства.<\/li>\n<\/ul>\n<p>Если эти проблемы существенны для вас, то стоит присмотреться к LinkedList<T> или ImmutableList<T><\/p>\n<h2>Связанный список LinkedList<T><\/h2>\n<p>Класс LinkedList<T> реализует простой двухсвязный список, каждый элемент которого имеет ссылка на предыдущий и следующий элемент.<\/p>\n<p>Каждый элемент списка оборачивается в специальный класс LinkedListNode<T>, который имеет ссылку на следующий элемент (Next), на предыдущий элемент (Previous) и само значение (Value).<\/p>\n<!--\r\n<div style=\"width:400px;\">\r\ncollections-linked-list.png\r\n<\/div>\r\n--><pre class=\"e2-text-code\"><code class=\"\">var linkedList = new LinkedList&lt;string&gt;();\r\nlinkedList.AddFirst(&quot;A&quot;);\r\nlinkedList.AddLast(&quot;B&quot;);\r\nlinkedList.AddLast(&quot;C&quot;);\r\n\t\t\r\nConsole.WriteLine(linkedList.First.Previous == null); \/\/ True\r\nConsole.WriteLine(linkedList.Last.Next == null);   \/\/ True<\/code><\/pre><p>Связанный список позволяет вставлять и удалять элементы со сложностью O (1). Также мы можем удалить элемент и заново вставить в тот же или другой список без дополнительного выделения памяти.<\/p>\n<h2>Словарь Dictionary<TKey, TValue><\/h2>\n<p>Словари хранят данные в виде ключ-значение. Каждый элемент словаря представляет из себя объект структуры KeyValuePair<TKey, TValue>.<\/p>\n<p>В качестве ключа можно использовать любой объект. Ключи должны быть уникальными в рамках коллекции.<\/p>\n<!--\r\n<div style=\"width:400px;\">\r\ncollectios-dict.png\r\n<\/div>\r\n--><pre class=\"e2-text-code\"><code class=\"\">var linkedList = new Dictionary&lt;string, string&gt;();\r\nlinkedList.Add(&quot;key1&quot;, &quot;A&quot;);\r\nlinkedList.Add(&quot;key2&quot;, &quot;B&quot;);\r\nlinkedList.Add(&quot;key3&quot;, &quot;C&quot;);<\/code><\/pre><p>Внутри словари построены на базе хеш-таблицы, что позволяет очень быстро вставлять элементы и получать по ключу (сложность O (1)). Сами же хеш-таблицы, в свою очередь, реализованы с помощью массивов.<\/p>\n<p>Для адресации значений внутри коллекции используются хеш коды ключей. Это значит что объект ключа не должен изменяться, потому что это приведет к изменению хеш кода, что в свою очередь приведет к потере данных.<\/p>\n<p><a href=\"https:\/\/github.com\/microsoft\/referencesource\/blob\/5697c29004a34d80acdaf5742d7e699022c64ecd\/mscorlib\/system\/collections\/generic\/dictionary.cs\">Исходники Dictionary<\/a><\/p>\n<h2>Стек Stack<T> и Очередь Queue<T><\/h2>\n<p>Очереди и стеки полезны, когда нужно временно хранить какие-то элементы, то есть удалять элемент после его извлечения. Также они позволяют определить строгую очередность записи и извлечения элементов.<\/p>\n<p>Стеки Stack<T> — реализуют подход LIFO (last in — first out).<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var stack = new Stack&lt;int&gt;();\r\nstack.Push(1); \/\/ stack = [1]\r\nstack.Push(2); \/\/ stack = [1,2]\r\nvar item = stack.Pop(); \/\/ stack = [1], item = 2<\/code><\/pre><p>Очереди Queue<T> — реализуют подход (first in — first out).<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var queue = new Queue&lt;int&gt;();\r\nqueue.Enqueue(1); \/\/ queue = [1]\r\nqueue.Enqueue(2); \/\/ queue = [1,2]\r\nitem = queue.Dequeue(); \/\/ queue = [2], item = 1<\/code><\/pre><p>Внутри они реализованы с помощью обычных массивов.<\/p>\n<h2>Множества HashSet<T> и SortedSet<T><\/h2>\n<p>Эти классы реализуют математические множества. По своей природе множество — набор уникальных элементов с общими характеристиками, в нашем случае одного типа.<\/p>\n<p>Также множества отличаются от обычных списков тем что они предоставляют набор методов, которые реализуют операции с <a href=\"https:\/\/ru.wikipedia.org\/wiki\/Теория_множеств\">теории множеств<\/a>.<\/p>\n<p>Внутренняя реализация этих классов отличается:<\/p>\n<ul>\n<li>HashSet<T> — множество, построенное на базе хеш-таблицы.<\/li>\n<li>SortedSet<T> — отсортированное множество, построенное на базе красно-черного дерева.<\/li>\n<\/ul>\n<p><br><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">ISet&lt;int&gt; set = new HashSet&lt;int&gt; { 1, 2, 3, 4, 5 };\r\n \r\nset.UnionWith(new[] { 5, 6 });              \/\/ set = { 1, 2, 3, 4, 5, 6 }\r\nset.IntersectWith(new[] { 3, 4, 5, 6, 7 }); \/\/ set = { 3, 4, 5, 6 }\r\nset.ExceptWith(new[] { 6, 7 });             \/\/ set = { 3, 4, 5 }\r\nset.SymmetricExceptWith(new[] { 4, 5, 6 }); \/\/ set = { 3, 6 }<\/code><\/pre><p>Можно заметить что LINQ предоставляет несколько похожих операций (Distinct, Union, Intersect, Except), которые можно выполнить с любой коллекцией. Но HastSet предоставляет намного больший набор операций с множествами.<\/p>\n<p>Основная разница в том что методы множеств изменяют текущую коллекцию, в то время как LINQ методы всегда создают новый экземпляр коллекции.<\/p>\n<h2>KeyedCollection<TKey,TItem><\/h2>\n<p>KeyedCollection это абстрактный класс, который позволяет построить собственную коллекцию.<\/p>\n<p>Эта коллекция является гибридом между списками (IList<T>) и словарями (IDictionary<T>). От списков ей досталась возможность получать элементы по индексу, а от словарей возможность получать элемент по ключу.<\/p>\n<p>В отличие от словарей, элемент KeyedCollection коллекции не является парой ключ-значение, вместо этого весь элемент является значением, а в качестве ключа используется его поле, свойство или любое другое значение. Для получения ключа используется абстрактный метод, который является обязательным для реализации. GetKeyForItem<\/p>\n<!--\r\n<div style=\"width:400px;\">\r\ncollections-keyed.png\r\n<\/div>\r\n--><pre class=\"e2-text-code\"><code class=\"\">var keyedCollection = new UserCollection();\r\nkeyedCollection.Add(new User {\r\n\tId = 1,\r\n\tName = &quot;A&quot;\r\n});\r\nkeyedCollection.Add(new User {\r\n\tId = 2,\r\n\tName = &quot;B&quot;\r\n});\r\n\t\t\r\nConsole.WriteLine(keyedCollection[2].Name); \/\/ B\r\n\t\r\npublic class UserCollection: KeyedCollection&lt;int, User&gt;\r\n{\r\n\tprotected override int GetKeyForItem(User user) =&gt; user.Id;\r\n}\r\n\t\r\npublic class User\r\n{\r\n\tpublic int Id {get;set;}\r\n\tpublic string Name {get;set;}\r\n}<\/code><\/pre><p>Внутри KeyedCollection построен на базе двух структур данных. Для быстрого получения данных по ключу используется словарь, а для получение элемента по индексу используется массив.<\/p>\n<h2>NameValueCollection<\/h2>\n<p>Представляет из себя коллекцию, которая похожа на словарь но в качестве ключей и значений используются строки. Элементы можно получить как по индексу так и по ключу.<\/p>\n<p>Особенной эту коллекцию делает то что однин ключ может содержать несколько эллементов.<\/p>\n<!--\r\n<div style=\"width:400px;\">\r\ncollections-named.png\r\n<\/div>\r\n--><pre class=\"e2-text-code\"><code class=\"\">var namedCollection = new NameValueCollection();\r\nnamedCollection.Add(&quot;key1&quot;, &quot;value1&quot;);\r\nnamedCollection.Add(&quot;key2&quot;, &quot;value2&quot;);\r\nnamedCollection.Add(&quot;key1&quot;, &quot;value3&quot;);\r\n\r\nConsole.WriteLine(namedCollection.Count);   \/\/ 2\r\nConsole.WriteLine(namedCollection[&quot;key1&quot;]); \/\/ value1,value3<\/code><\/pre><h2>Иммутабельные коллекции<\/h2>\n<p>Иммутабельные коллекции не входят в стандартную библиотеку классов (BCL). Для их использования нужно установить System.Collections.Immutable NuGet пакет.<\/p>\n<p>Они позволяют безопасно работать в многопоточной среде. Вместо того чтобы использовать блокировки синхронизации, как это делают многопоточные коллекции, неизменяемые коллекции не могут быть изменены после создания. Это автоматически делает их безопасными для использования в многопоточных сценариях, так как нет возможности другому потоку изменить коллекцию.<\/p>\n<p>Сами же коллекции можно поделить на несколько видов:<\/p>\n<ul>\n<li>Mutable — обычные коллекции которые поддерживают изменения.<\/li>\n<li>Immutable — коллекции, которые полностью запрещают изменения. Хотя на самом деле любое изменение иммутабельной коллекции приводит к созданию новой.<\/li>\n<li>ReadOnly — обертки над стандартными коллекциями, которые не дают поменять данные. Из-за того что это всего лишь обертка мы можем поменять данные в оригинальной коллекции и ead only коллекция подтянет изменения.<\/li>\n<\/ul>\n<p>Детальнее можно ознакомиться в статье: <a href=\"https:\/\/devblogs.microsoft.com\/premier-developer\/read-only-frozen-and-immutable-collections\/\">Read only, frozen, and immutable collections<\/a>.<\/p>\n<h2><b>Иммутабельные стеки ImmutableStack<T> и очереди ImmutableQueue<T><\/b><\/h2>\n<p>Ничем не отличаются от обычных стеков и очередей, кроме того, что являются не изменяемыми.<\/p>\n<p>Для реализации иммутабельных стеков\/очередей массивы не подойдут. Причина заключается в том, что на каждое изменение придётся делать полную копию массива что очень неэффективно.<\/p>\n<p>Поэтому для работы иммутабельных стеков\/очередей используется связанный список. При изменении коллекции нужно всего лишь создать новый элемент, который ссылается на предыдущее значение. В итоге не происходит никакого копирования и экономится память.<\/p>\n<h2><b>Иммутабельные списки ImmutableList<\/b><\/h2>\n<p>Под капотом используют сбалансированное бинарное дерево вместо массива или связанного списка.<\/p>\n<p>Массивы не подходят из-за накладных расходов на их использование. Связанный список тоже не подойдет, потому что ImmutableList поддерживает обращение по индексу из-за чего нужно долго перебирать связанные элементы. Поэтому для нормальной работы иммутабельных списков используют сбалансированное бинарное дерево.<\/p>\n<h2><b>Иммутабельные массивы ImmutableArray<\/b><\/h2>\n<p>По сути, это небольшая прослойка над обычными массивами и все. Любые мутабельные операции приводят к копированию массива. Из-за этого скорость добавления элементов равна O (n), но в то же время получение элемента по индексу занимает O (1).<\/p>\n<p>Итерация по массиву работает в несколько раз быстрее чем у других неизменяемых коллекциях.<\/p>\n<h2><b>Иммутабельные словари ImmutableDictionary<\/b><\/h2>\n<p>Неизменяемые словари внутри работают на базе сбалансированного дерева, но с одной особенностью. Каждый элемент внутри коллекции представлен в виде отдельного дерева (ImmutableList<KeyValuePair<TKey, TValue>>). Так что по своей сути иммутабельные словари — это деревья деревьев.<\/p>\n<p>Из-за своей особенности иммутабельные словари потребляют очень много памяти и долго работают. Поэтому стоит аккуратно их использовать.<\/p>\n<h2><b>Особенности использования<\/b><\/h2>\n<p>Как было выше сказано все операции изменения коллекций приводят к созданию новой коллекции. Нужно помнить, что необходимо использовать новый экземпляр вместо старого.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var immutableList = new[] { 1, 2, 3 }.ToImmutableList();\r\nimmutableList = immutableList.Add(4);<\/code><\/pre><p>По идее чтобы было проще, мы можем объединить все изменения в цепочку:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">immutableList = immutableList\r\n    .Add(5)\r\n    .Add(6)\r\n    .Add(7);<\/code><\/pre><p>Однако такого способа стоит избегать, потому что каждый вызов метода создает новый экземпляр коллекции. Хотя неизменяемые коллекции реализованы таким образом, чтобы повторно использовать как можно большую часть оригинальной коллекции при создании нового экземпляра, некоторые выделения памяти все же необходимы. Это означает больше работы для сборщика мусора.<\/p>\n<p>Чтобы минимизировать проблему стоит использовать методы, которые могут выполнить нужные изменения за один вызов. Например:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">immutableList = immutableList.AddRange(new[] { 5, 6, 7 });<\/code><\/pre><p>Но такие методы позволяют сделать только один вид изменения. Например у нас нет единого метода, который позволяет добавить и удалить одну запись:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">immutableList = immutableList\r\n    .Add(6)\r\n    .Remove(2);<\/code><\/pre><p>Для решения этой проблемы иммутабельные коллекции предоставляют билдеры (builders).<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var builder = immutableList.ToBuilder();\r\nbuilder.Add(6);\r\nbuilder.Remove(2);\r\nimmutableList = builder.ToImmutable();<\/code><\/pre><p>Внутри себя билдеры используют соответствующую мутабельную коллекцию, что позволяет выполнить все операции над одним экземпляром коллекции. Только после вызова метода ToImmutable экземпляр снова будет неизменяемым. Таким образом можно сократить объем работы уборщика мусора.<\/p>\n<h2>Источники и доп. материалы<\/h2>\n<ul>\n<li><a href=\"https:\/\/www.dotnetcurry.com\/csharp\/1466\/csharp-dotnet-collection-class\">How to Choose the Right .NET Collection Class?<\/a> Отличная статья про то какую коллекцию выбрать в .NET. И вообще хорошо хоть и поверхностно описаны конкурентные и неизменяемые коллекции.<\/li>\n<li><a href=\"http:\/\/staticvoidmain.cognitioab.se\/2013\/11\/28\/net-collections-comparing-performance-and-memory-usage\/\">.NET Collections: comparing performance and memory usage<\/a>. Сравнивались коллекции-словари и среди них лучше всего отработал Dictionary, SortedList в свою очередь в среднем потреблял в два раза меньше памяти чем обычный словарь. Хуже всего себя показал отсортированный словарь SortedDictionary.<\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/collections\/\">Collections and Data Structures<\/a>. Отличное описание коллекций на MSDN.<\/li>\n<\/ul>\n",
            "date_published": "2021-04-21T11:10:51-04:00",
            "date_modified": "2021-04-26T07:16:48-04:00",
            "tags": [
                "dotnet",
                "алгоритми та структури даних",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/collections-linked-list.png",
            "_date_published_rfc2822": "Wed, 21 Apr 2021 11:10:51 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "252",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-linked-list.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collectios-dict.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-keyed.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-named.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-list.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-interfaces.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-capacity.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collections-capacity-copy.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/collection-compexity-4.png"
                ]
            }
        },
        {
            "id": "247",
            "url": "https:\/\/www.stefaniuk.website\/all\/otlichnoe-video-pro-osnovy-docker\/",
            "title": "Отличное видео про основы Docker",
            "content_html": "<div class=\"e2-text-video\">\n<iframe src=\"https:\/\/www.youtube.com\/embed\/QF4ZF857m44?enablejsapi=1\" allow=\"autoplay\" frameborder=\"0\" allowfullscreen><\/iframe>\n<\/div>\n",
            "date_published": "2021-01-09T17:11:26-04:00",
            "date_modified": "2021-03-18T12:21:27-04:00",
            "tags": [
                "cloud",
                "видео",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/remote\/youtube-QF4ZF857m44-cover.jpg",
            "_date_published_rfc2822": "Sat, 09 Jan 2021 17:11:26 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "247",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/jquery\/jquery.js",
                    "system\/library\/media-seek\/media-seek.js"
                ],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/remote\/youtube-QF4ZF857m44-cover.jpg"
                ]
            }
        },
        {
            "id": "231",
            "url": "https:\/\/www.stefaniuk.website\/all\/kod-tayny-yazyk-informatiki\/",
            "title": "Код: тайный язык информатики",
            "content_html": "<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/code-hidden-language-of-computer-cover.jpg\" width=\"300\" height=\"410\" alt=\"\" \/>\n<\/div>\n<p>Составили вместе с другом план, по которому хотим изучить Computer Science. Первым пунктом плана стала книга — Код: тайный язык информатики. Прочитав её я был в шоке от подачи и крутости материала. Автор шаг за шагом рассказывает про развитие компьютерных технологий.<\/p>\n<p>Книга начинается с того, что вы изучаете как работает фонарик и как с его помощью можно общаться. Потом вы изобретаете телеграф, а вместе с ним и реле. Итак шаг за шагом вы плавно подходите к тому, как устроены компьютеры и большую часть книги вы будете изобретать свой.<\/p>\n<p>Также автор рассказывает, почему появилась и для чего нужна шестнадцатеричная и двоичная системы исчисления, как устроена оперативная память. Ближе к концу книги вы придумаете «свой» язык программирование и окажется, что вы придумали ассемблер. Ну а в самом конце автор расскажет, как мы пришли от командной строки к графическим интерфейсам.<\/p>\n<p>Каждая глава содержит множество иллюстраций, которые объясняют тот или иной аспект.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/code-hidden-language-of-computer.png\" width=\"2337\" height=\"653\" alt=\"\" \/>\n<\/div>\n<p>Книга сильно зацепила меня, прочитал всего за неделю. Советую всем кто хочет разобраться как устроен компьютер и его самые мелкие части.<\/p>\n",
            "date_published": "2020-08-09T08:18:12-04:00",
            "date_modified": "2023-06-02T20:26:05-04:00",
            "tags": [
                "книги",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/code-hidden-language-of-computer-cover.jpg",
            "_date_published_rfc2822": "Sun, 09 Aug 2020 08:18:12 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "231",
            "_e2_data": {
                "is_favourite": true,
                "links_required": [],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/code-hidden-language-of-computer-cover.jpg",
                    "https:\/\/www.stefaniuk.website\/pictures\/code-hidden-language-of-computer.png"
                ]
            }
        },
        {
            "id": "217",
            "url": "https:\/\/www.stefaniuk.website\/all\/semantica-tema-dlya-visual-studio-code\/",
            "title": "Semantica — тема для Visual Studio Code",
            "content_html": "<p>Красивая тема редактора сильно влияет на удобство работы с кодом. Я перепробовал множество разных тем, сначала были тёмные, но спустя какое-то время я перешёл на светлые. Большинство тем меня не устраивали. Для меня важно чтобы: интерфейс не обращал на себя внимания, подсветка кода похожая на то что есть в обычной Visual Studio и Rider.<\/p>\n<p>Однажды наткнулся на отличную тему <a href=\"https:\/\/github.com\/tonsky\/vscode-theme-alabaster\">Alabaster<\/a>. Общая цветовая палитра мне зашла, но вот подсветка синтаксиса слишком минималистичная. В итоге решил на основе этой темы запилить свою. Добавил подсветку синтаксиса похожую на Visual Studio Light и Rider Light. В итоге получилась новая тема — Semantica.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/icon.jpg\" width=\"225\" height=\"225\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Логотип темы.<\/div>\n<\/div>\n<h2>Ссылки<\/h2>\n<p>Исходный код: <a href=\"https:\/\/github.com\/teamkiller7112\/vscode-theme-semantica\">https:\/\/github.com\/teamkiller7112\/vscode-theme-semantica<\/a><br \/>\nМаркетплейс: <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=bogdanstefanjuk.theme-semantica\">https:\/\/marketplace.visualstudio.com\/items?itemName=bogdanstefanjuk.theme-semantica<\/a><\/p>\n<h2>Скриншоты<\/h2>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/csharp.jpg\" width=\"1000\" height=\"610\" alt=\"\" \/>\n<\/div>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/json.jpg\" width=\"1000\" height=\"610\" alt=\"\" \/>\n<\/div>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/xml.jpg\" width=\"1000\" height=\"610\" alt=\"\" \/>\n<\/div>\n<h2>PS<\/h2>\n<p>Замечания или пожелания оставляйте на <a href=\"https:\/\/github.com\/teamkiller7112\/vscode-theme-semantica\/issues\">гитхабе<\/a>. Если тема понравилась — поставьте звездочку в <a href=\"https:\/\/github.com\/teamkiller7112\/vscode-theme-semantica\">репозитории<\/a>. <\/p>\n<p>В качестве шрифта советую использовать <a href=\"https:\/\/www.jetbrains.com\/ru-ru\/lp\/mono\/\">JetBrains Mono<\/a>.<\/p>\n",
            "date_published": "2020-04-25T09:16:49-04:00",
            "date_modified": "2020-04-25T10:10:30-04:00",
            "tags": [
                "інструменти",
                "програмування",
                "проекти"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/icon.jpg",
            "_date_published_rfc2822": "Sat, 25 Apr 2020 09:16:49 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "217",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/icon.jpg",
                    "https:\/\/www.stefaniuk.website\/pictures\/csharp.jpg",
                    "https:\/\/www.stefaniuk.website\/pictures\/json.jpg",
                    "https:\/\/www.stefaniuk.website\/pictures\/xml.jpg"
                ]
            }
        },
        {
            "id": "189",
            "url": "https:\/\/www.stefaniuk.website\/all\/idei-dlya-programmistov\/",
            "title": "Идеи для программистов",
            "content_html": "<p>Много начинающих программистов — сам был таким — маются от вопроса: чего бы такого классного напрограммировать. Месяц за месяцем, год за годом проходят в ожидании достойной идеи™. «Вот бы мне проект, я уж я бы взялся за него, я б тогда выложился на все сто».<\/p>\n<p>Некоторым везёт, и они быстро попадают в классную команду на крутой проект. Кому повезло меньше, сидит в суровом энтерпрайзе и жаждет поделать какой-нибудь пет-проджект для души. Вот только идеи нет…<\/p>\n<p>Я придумал решение. Надо брать любую штуку, которая вам нравится, и просто попытаться повторить её. Нравится тетрис? Запрограммируйте тетрис. Нравится тот же Майнкрафт? Запилите свой Майнкрафт. Если при этом не хочется возиться с трёхмеркой (хотя, чего там возиться, с первого-то взгляда? :—) — делайте двумерный.<\/p>\n<p>Я однажды так делал игрушку с паравозиками. Правда, интерфейс мне было неинтересно делать. Зато крайне увлекала логика переключения стрелок, семафоров, решение коллизий и всё такое. Вот так я и играл в эти свои паровозики: в юниттестах.<\/p>\n<p>Смысл не в том, чтобы сделать продукт. И даже не в том, чтобы выключить радары, в поисках крутую идею. Смысл в том, чтобы, пока крутая идея не пришла, занять время чем-то полезным. А потом, когда придёт время клёвого проекта, скилл уже будет прокачан и будет шанс не запороть идею, а таки реализовать её.<\/p>\n<p>Ещё один бонус: в повторении чужих проектов можно выбирать, на чём концентрироваться. В готовых-то продуктах уже всё продумано: можно брать готовые интерфейсы, логику, арты и прочее-прочее. Плюс выбираем для воспроизведения то, что нравится. А не суровый энтерпрайз (хотя, кому-то и это нравится). И ещё не надо заморачиваться с прочими нюансами работы над реальными проектами: аналитика, требование, планирование, коммуникации и прочее-прочее. Делаем чисто для себя, как упражнение из учебника.<\/p>\n<p>На самом деле это решение придумал не я, конечно же. У древних мастеров это был главный способ прокачиваться: повторять за лучшими. Ну и чем мы хуже?<\/p>\n<p><b>Оригинал статьи: <a href=\"https:\/\/t.me\/anatoly_burov_channel\/116\">https:\/\/t.me\/anatoly_burov_channel\/116<\/a> <\/b><\/p>\n",
            "date_published": "2019-12-27T16:41:49-04:00",
            "date_modified": "2020-12-26T16:45:19-04:00",
            "tags": [
                "програмування",
                "проекти"
            ],
            "_date_published_rfc2822": "Fri, 27 Dec 2019 16:41:49 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "189",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "169",
            "url": "https:\/\/www.stefaniuk.website\/all\/stateless-i-stateful-services\/",
            "title": "stateless и stateful сервисы",
            "content_html": "<p><b>Stateful системы<\/b><br \/>\nТакие системы хранят внутри себя состояние, которое влияет на запросы от пользователя. Примером могут быть сессии пользователей, которые хранятся на сервера. Ответ на запрос пользователя зависит от состояния сессии.<\/p>\n<p>Одна из основных проблем таких систем — горизонтальное масштабирование. Чтобы развернуть несколько экземпляров сервиса нужно как переносить состояния но новые машины и синхронизировать их. Но проблема относительно легко решается за счет внешнего хранилища в котором можно хранить сессии. Система все равно является stateful, но сами веб сервисы стали stateless и теперь можно спокойно их масштабировать.<\/p>\n<p><b>Stateless системы<\/b><br \/>\nКак альтернативу сессиям можно использовать cookie файлы или JWT токены. В таком случае вместе с запросом мы всегда отправляем всю необходимую информацию для его выполнения и аутентификации. В таком случае серверу не нужно хранить информацию о пользователе, достаточно проверить на валидность куки или токен в запросе. Так что stateless система зависит только от данных которые ей были переданы, а не от внутреннего состояния.<\/p>\n<p>Еще один пример системы, которая хранит состояние: у нас есть интернет магазин. Пока корзина пользователя храниться в памяти сервера — это stateful. В таком случае мы опять упираемся в проблему с масштабированием. Но мы можем вынести состояние корзины в базу данных, тогда сервис станет stateless и можно легко его раскатывать на несколько машин.<\/p>\n<p>Более детально расписано тут: <a href=\"http:\/\/rsdn.org\/forum\/design\/2951941.1\">stateless\/full web service<\/a><\/p>\n",
            "date_published": "2019-12-18T13:03:03-04:00",
            "date_modified": "2022-05-08T05:53:36-04:00",
            "tags": [
                "system-design",
                "програмування"
            ],
            "_date_published_rfc2822": "Wed, 18 Dec 2019 13:03:03 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "169",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "171",
            "url": "https:\/\/www.stefaniuk.website\/all\/create-workplace-messenger-bot\/",
            "title": "Как я делал бота для Facebook workplace",
            "content_html": "<p>Дали мне задачу: написать бота для мессенджера workplace, с помощью которого можно получать уведомления из нашей CRM и управлять разными вещами. Расскажу о разных интересных вещах с которыми я столкнулся. Бота писал с помощью ASP.NET Core Web API.<\/p>\n<h2>Вебхуки<\/h2>\n<p>Для того чтобы бот мог обрабатывать запросы и сообщения от Facebook нам надо настроить вебхуки. Webhook — механизм оповещения системы о событиях. Для того чтобы Facebook принял наш хук, он должен обрабатывать как GET так и POST запросы.<\/p>\n<p class=\"note\"><a href=\"https:\/\/developers.facebook.com\/docs\/messenger-platform\/getting-started\/webhook-setup\">Подробнее о Webhook в официальной документации<\/a><\/p>\n<p>GET запрос служит для валидации работы нашего эндпоинта. POST принимает данные связанные с активностью пользователя, будь то нажатие на кнопки или другая активность.<\/p>\n<p class=\"cut-button\">Показать код метода-обработчика GET запроса<\/p>\n<div class=\"cut-content\"><pre class=\"e2-text-code\"><code class=\"\">public IActionResult Receive(\u2028[FromQuery(Name = &quot;hub.mode&quot;)] string mode,\u2028\r\n    [FromQuery(Name = &quot;hub.challenge&quot;)] string challenge,\u2028\r\n    [FromQuery(Name = &quot;hub.verify_token&quot;)] string verifyToken)\r\n{\u2028\r\n   if (string.IsNullOrEmpty(verifyToken)) {\u2028\r\n       return Unauthorized();\u2028\r\n    }\u2028\u2028\r\n    if (verifyToken.Equals(FacebookEnvironment.FacebookVToken)) {\u2028\r\n        return Ok(challenge);\u2028\r\n    }\u2028\r\n    return Unauthorized();\u2028\r\n}<\/code><\/pre><p>В качестве verify_token используется токен, который мы указали при регистрации нашего хука.<\/p>\n<\/div><p class=\"cut-button\">Показать код метода-обработчика POST запроса<\/p>\n<div class=\"cut-content\"><pre class=\"e2-text-code\"><code class=\"\">public async Task&lt;IActionResult&gt; Receive([FromBody]FbResponse response = null)\r\n{\r\n    if (response is null) {\r\n        return BadRequest();\r\n    }\r\n\r\n    if (response.Object != &quot;page&quot;) {\r\n        return Ok();\r\n    }\r\n\r\n    foreach (var entry in response.Entries) {\r\n        foreach (var message in entry.Messaging) {\r\n            await PrepareMessageAsync(message);\r\n        }\r\n    }\r\n    return Ok(&quot;EVENT_RECEIVED&quot;);\r\n}<\/code><\/pre><\/div><h2>Авторизация запросов Facebook<\/h2>\n<p>Для авторизации Facebook использует специальный http заголовок (X-Hub-Signature), в нем он передает некую сигнатуру с помощью которой мы можем авторизовать запрос. Для того чтобы добавить такую функциональность в наш контроллер, добавим фильтр.<\/p>\n<p class=\"cut-button\">Пример кода, для проверки подписи<\/p>\n<div class=\"cut-content\"><pre class=\"e2-text-code\"><code class=\"\">private const string Sha1Prefix = &quot;sha1=&quot;;\r\n\r\npublic static bool Validate(string signature, string contentString) {\r\n    if (!signature.StartsWith(Sha1Prefix, StringComparison.OrdinalIgnoreCase)) {\r\n        return false;\r\n    }\r\n    var secret = Encoding.ASCII.GetBytes(FacebookEnvironment.AppSecret);\r\n    var signatureWithoutPrefix = signature.Substring(Sha1Prefix.Length);\r\n    var content = Encoding.ASCII.GetBytes(contentString);\r\n    return GetIsHashValid(secret, signatureWithoutPrefix, content);\r\n}\r\n\r\nprivate static bool GetIsHashValid(byte[] secret, string signature, byte[] content) {\r\n    using var hmac = new HMACSHA1(secret);\r\n    var hash = hmac.ComputeHash(content);\r\n    var hashString = ToHexString(hash);\r\n    return hashString.Equals(signature);\r\n}\r\n\r\nprivate static string ToHexString(IReadOnlyCollection&lt;byte&gt; bytes)\r\n{\r\n    var builder = new StringBuilder(bytes.Count * 2);\r\n    foreach (var b in bytes)\r\n    {\r\n        builder.AppendFormat(&quot;{0:x2}&quot;, b);\r\n    }\r\n\r\n    return builder.ToString();\r\n}<\/code><\/pre><\/div><h2>Тестирование бота<\/h2>\n<p class=\"note-md\"><a href=\"http:\/\/winitpro.ru\/index.php\/2017\/11\/03\/ustanovka-besplatnogo-ssl-sertifikata-lets-encrypt-na-iis-v-windows-server-2012-r2\/\">Как установить letsencrypt сертификат для IIS<\/a>. Если же вы используете связку в виде ubuntu и nginx вам подойдет <a href=\"https:\/\/www.digitalocean.com\/community\/tutorials\/nginx-let-s-encrypt-ubuntu-18-04-ru\">эта инструкция<\/a>.<\/p>\n<p>Для тестирования нужно развернуть бот на сервере, который смотрит в мир. Также необходимо чтобы у сервера было доменное имя и валидный SSL сертификат. В моем случае, в качестве сервера выступала машина на винде, так как другой внутри нашей сети не было. Как мне показалось захостить приложение написанное на .NET Core намного проще под Ubuntu + nginx нежели под Windows + IIS. В качестве поставщика сертификатов выбрал letsencrypt, так как они предоставляют бесплатный сертификат на 3 месяца, с возможностью дальнейшего обновления.<\/p>\n",
            "date_published": "2019-11-26T10:22:26-04:00",
            "date_modified": "2023-06-02T20:26:31-04:00",
            "tags": [
                "asp.net core",
                "dotnet",
                "програмування",
                "проекти",
                "робота"
            ],
            "_date_published_rfc2822": "Tue, 26 Nov 2019 10:22:26 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "171",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "166",
            "url": "https:\/\/www.stefaniuk.website\/all\/links-17\/",
            "title": "Годнота, часть 17",
            "content_html": "<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/image-links-17.jpeg\" width=\"750\" height=\"498\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Случайное фото<\/div>\n<\/div>\n<p>Сегодня получилось так что большенство ссылок о программировании.<\/p>\n<p>— <a href=\"https:\/\/ru.hexlet.io\/blog\/posts\/how-to-test-code\">Начинаем писать тесты (правильно) <\/a><br \/>\n— <a href=\"https:\/\/www.thereformedprogrammer.net\/is-the-repository-pattern-useful-with-entity-framework-core\/\">Так ли полезен паттер репозиторий вместе с Entity Framework Core?<\/a><br \/>\n— <a href=\"https:\/\/blog.dmitrypodgorniy.com\/all\/kak-ulutshit-sebya-kak-programmista\/\"> Как улучшить себя как программиста<\/a><br \/>\n— <a href=\"http:\/\/dashvlas.com\/blog\/rest\"> Очень кратко о REST<\/a><br \/>\n— <a href=\"https:\/\/dotnetcoretutorials.com\/2019\/08\/03\/dapper-in-net-core-part-1-the-what-why-who\/\"> Мини гайд по использованию Dapper в .NET Core<\/a><br \/>\n— <a href=\"http:\/\/evtuhovich.ru\/blog\/2017\/03\/09\/service-discovery\/\">Service Discovery<\/a><\/p>\n<p>— <a href=\"http:\/\/mikeozornin.ru\/blog\/all\/weird-post-about-weird\/\"> Странное про слово «странное»<\/a><br \/>\n— <a href=\"https:\/\/www.youtube.com\/watch?v=JXpoALotxf0\">Видео экскурсия по заводу ILFORD<\/a><\/p>\n",
            "date_published": "2019-11-19T04:54:30-04:00",
            "date_modified": "2019-11-19T04:54:22-04:00",
            "tags": [
                "лінки",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/image-links-17.jpeg",
            "_date_published_rfc2822": "Tue, 19 Nov 2019 04:54:30 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "166",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/image-links-17.jpeg"
                ]
            }
        },
        {
            "id": "167",
            "url": "https:\/\/www.stefaniuk.website\/all\/delete-duplicates-in-table\/",
            "title": "Удаления дубликатов строк",
            "content_html": "<p>Недавно мне попалась интересная задачка по SQL.<br \/>\nУ вас есть таблица с 2 колонками: Id, Name. Но на колонках нет никаких ограничений. И получилось так что там если дубли и нам надо их удалить.<\/p>\n<p>Пример таблички<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/TableWithDuplicates.png\" width=\"168\" height=\"136\" alt=\"\" \/>\n<\/div>\n<p>В случае когда у нас нет ни одного уникального значения, мы можем использовать номер строки для идентификации записи для удаления.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">; WITH DuplicatesForDelete(Row) AS (\r\n\tSELECT ROW_NUMBER() OVER(PARTITION BY t1.Id, t1.Name ORDER BY Id, Name) AS Row \r\n\tFROM TableWithDuplicates t1\r\n)\r\nDELETE FROM DuplicatesForDelete WHERE Row &gt; 1<\/code><\/pre><p><a href=\"https:\/\/www.fastreport.ru\/ru\/blog\/251\/show\/\">Статья о том что такое оконные функции (OVER) <\/a><\/p>\n",
            "date_published": "2019-11-13T17:29:28-04:00",
            "date_modified": "2019-11-06T05:48:50-04:00",
            "tags": [
                "sql",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/TableWithDuplicates.png",
            "_date_published_rfc2822": "Wed, 13 Nov 2019 17:29:28 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "167",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/TableWithDuplicates.png"
                ]
            }
        },
        {
            "id": "161",
            "url": "https:\/\/www.stefaniuk.website\/all\/parallel-asynchronous-multithreading-programming\/",
            "title": "Parallel, Asynchronous, Multithreading programming",
            "content_html": "<h2>Многопоточное программирование<\/h2>\n<p>Код может выполнятся в разных потоках. Например основной UI поток и набор потоков для обработки данных. В данном случае нет гарантии что потоки будут работать параллельно. Обычно это зависит от процессора. Потоки «абстрагируют» от пользователя низкоуровневые детали и позволяют выполнять более чем одну работу «параллельно».<\/p>\n<h2>Параллельное программирование<\/h2>\n<p>Подразумевает что некоторая задача разбивается на несколько независимых подзадач, которые можно выполнить параллельно и потом объединить результаты.<br \/>\nПримером такой задачи может быть Parallel LINQ<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">IEnumerable&lt;Data&gt; yourData = GetYourData();\r\nvar result = yourData.AsParallel() \/\/ начинаем обрабатывать параллельно\r\n  .Select(d =&gt; d.CalcAmount()) \/\/ Вычисляем параллельно\r\n  .Where(amount =&gt; amount &gt; 0)\r\n  .ToArray(); \/\/ Возврвщаемся к синхронной модели<\/code><\/pre><h2>Асинхронное программирование<\/h2>\n<p>Мы запускаем какую-то задачу, но не ждем ответа, а продолжаем делать свою работу. А когда будет готов ответ — нас уведомят. Обычно такие операции бывают при работе с сетью, диском или любыми другими продолжительными задачами.<\/p>\n<p>Пример на C#.<br \/>\nУ нас есть продолжительная асинхронная задача, которая обращается к БД. С помощью конструкций async\/await мы организовываем асинхронную работу. Пока БД готовит для нас ответ, поток, который обслуживал этот метод возвращается в пулл потоков и может тем временем выполнять полезную работу. Как только БД отдаст ответ, нашему методу снова выделяется поток и продолжается работа.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">var asyncResult = await Database.GetAllUsers(); \/\/ длительная асинхронная операция\r\nvar activeUsers = asyncResult.Where(user =&gt; user.IsActive).ToList(); \/\/ работаем с результатом асинхронной операции<\/code><\/pre><h2>Еще один забавный но наглядный пример<\/h2>\n<p>Вам нужно выкопать во дворе бассейн.<\/p>\n<ol start=\"1\">\n<li>Вы взяли лопату и копаете. Это однопоточная работа<\/li>\n<li>Вы пригласили друга Васю и копаете вместе, периодически задевая друг-друга лопатами. Это многопоточная работа<\/li>\n<li>Пока вы копаете бассейн, Вася копает канаву под водопровод. Никто никому не мешает. Это распараллеливание<\/li>\n<li>Вы пригласили бригаду землекопов, а сами с Васей пошли пить пиво. Когда бригада все сделает, к вам придут за деньгами. Это асинхронная работа.<\/li>\n<\/ol>\n<p>Количество лопат в хозяйстве — это количество ядер в системе<\/p>\n<h2>Ссылки<\/h2>\n<p>— <a href=\"https:\/\/habr.com\/ru\/post\/337528\/\">Параллелизм против многопоточности против асинхронного программирования: разъяснение<\/a><br \/>\n— <a href=\"https:\/\/agladky.ru\/blog\/async-await-example\/\">Cтатья об async\/await в C#<\/a><br \/>\n— <a href=\"https:\/\/ru.stackoverflow.com\/questions\/445768\/%d0%9c%d0%bd%d0%be%d0%b3%d0%be%d0%bf%d0%be%d1%82%d0%be%d1%87%d0%bd%d0%be%d0%b5-vs-%d0%b0%d1%81%d0%b8%d0%bd%d1%85%d1%80%d0%be%d0%bd%d0%bd%d0%be%d0%b5-%d0%bf%d1%80%d0%be%d0%b3%d1%80%d0%b0%d0%bc%d0%bc%d0%b8%d1%80%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5\">Многопоточное vs асинхронное программирование (stackoverflow) <\/a><\/p>\n",
            "date_published": "2019-11-03T15:42:51-04:00",
            "date_modified": "2019-10-29T17:13:54-04:00",
            "tags": [
                "dotnet",
                "програмування"
            ],
            "_date_published_rfc2822": "Sun, 03 Nov 2019 15:42:51 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "161",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "134",
            "url": "https:\/\/www.stefaniuk.website\/all\/linq-cheetscheet\/",
            "title": "Шпаргалка по LINQ",
            "content_html": "<div style=\"max-width: 800px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/LINQ-result-methods-1.png\" width=\"1500\" height=\"1583\" alt=\"\" \/>\n<\/div>\n<\/div><div style=\"max-width: 800px;\"><div class=\"e2-text-picture\">\n<img src=\"https:\/\/www.stefaniuk.website\/pictures\/LINQ-query-methods.png\" width=\"1500\" height=\"1782\" alt=\"\" \/>\n<\/div>\n<\/div>",
            "date_published": "2019-11-02T19:35:18-04:00",
            "date_modified": "2023-06-02T20:01:41-04:00",
            "tags": [
                "dotnet",
                "програмування"
            ],
            "image": "https:\/\/www.stefaniuk.website\/pictures\/LINQ-result-methods-1.png",
            "_date_published_rfc2822": "Sat, 02 Nov 2019 19:35:18 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "134",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/www.stefaniuk.website\/pictures\/LINQ-result-methods-1.png",
                    "https:\/\/www.stefaniuk.website\/pictures\/LINQ-query-methods.png"
                ]
            }
        },
        {
            "id": "158",
            "url": "https:\/\/www.stefaniuk.website\/all\/luchshee-obyasneniya-raboty-semafora\/",
            "title": "Лучшее объяснения работы семафора",
            "content_html": "<p>A semaphore is like a nightclub: it has a certain capacity, enforced by a bouncer. Once it’s full, no more people can enter, and a queue builds up outside. Then, for each person that leaves, one person enters from the head of the queue. The constructor requires a minimum of two arguments: the number of places currently available in the nightclub and the club’s total capacity.<\/p>\n",
            "date_published": "2019-10-21T15:39:32-04:00",
            "date_modified": "2019-10-21T15:39:26-04:00",
            "tags": [
                "dotnet",
                "програмування"
            ],
            "_date_published_rfc2822": "Mon, 21 Oct 2019 15:39:32 -0400",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "158",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        }
    ],
    "_e2_version": 4116,
    "_e2_ua_string": "Aegea 11.2 (v4116)"
}