Было бы круто от тебя видеть цикл видео про ООП, принципы правильного проектирования кода и подобное. Нравится как ты все визуализируешь, сразу все понятно становится
(...принципы правильного проектирования кода...) видео на 3 секунды. Их не существует... правильных. Они есть рзные, каждые служат своей цели. Проектирование кода... структурирование кода ещё туда-сюда. Проектируют, обычно, системы, компоненты, модули методом декомпозиции функциональной, структурной, логической, физической, организационной. 2023 год, забудьте уже про ООП, в век поведенческих моделей -- дитя рожённое сумрачным гением Гарди Буча и Ива Якобса под конец 80-х. Имеет очень ограниченое применение, для прикладных систем. ООП - как парадигма моделирования и описания реальных систем ещё более менее, но с оговорками. Но как принцип структурирования кода уже давно не выдерживает критики.
Алекс, спасибо огромное, для меня твой канал просто как глоток свежего воздуха и огромное вдохновение. Моя жизнь и профессия давно устоялись, и работа моя не связана с программирование и IT. Сейчас изучаю Си просто для души, в школе увлекался бэйсиком и паскалем, благодаря этому поступил в институт без экзаменов после олимпиады, потом забросил, некогда было. Отсутствие необходимости изучать то что модно и в тренде, дает возможность изучать программирование так, как мне интересно а не требуется для обретения или смены профессии. Твой канал для меня просто находка, именно та информация которую я ищу. И пусть в современном мире низкоуровневое программирование не особо нужно, а все алгоритмы разложены по полочкам и изучены, пусть. Мне нравится именно корни и основы, и я буду программировать так, как раньше, экономя байты памяти, оптимизируя какие то задачи, что бы это могло запускаться на любом примитивном железе. Это самый кайф! Спасибо!
Спасибо за труд, но всё же как-то недостаточно. Я уже настроился и ожидал увидеть информацию о других стурктурах, как вдруг видео закончилось. Жду продолжения.
Как же круто подаётся материал! Прямо интересно смотреть и прямо сразу хочется ещё больше видео! Низкий поклон автору за такие шедевры! Всегда с нетерпением жду новые видео!
Alek - Спасибо тебе за стольчудесный контент. Ты просто огромнейшый молодец, за столь краткий период времени ты уже сделал огромный вклад в жизни других людей, жаль что я больше не смогу видеть твои видео. 😁😗😉
Я делал такую базу, не подозревая, что именно о такой будут когда-то рассказывать. Нигде не учился. Пришел логично. Первый прототип базы был статический, но очень быстрый. И в момент, когда я не знал какой именно длинны будут некоторые текстовые поля, пришлось изобретать велосипед. Добавил ссылки. При удалении, информация просто не учитывалась. По факту она не удалялась. Ровно также вижу и работает Microsoft Access. Динамическая база данных при GOTO на нужный нам столбец работает медленней чем статическая, поскольку в статической можно просто умножить наш указатель на длину и мы точно попадем в начало нужной нам информации. А здесь нам уже нужно высчитывать из ссылки к ссылке пока не получим желаемую. Вначале я использовал только указатель "вперед". Но позже столкнулся с проблемой вставки информацию в середину и решил не раздвигать ячейки, не перезаписывать жёсткий диск за каждый раз, а это даже очень актуально при использовании SSD накопителя. По этому добавил указатель "назад" и это позволило мне добавлять значения в конец, но подавать пользователю это как будто данные находятся в середине. Чесно говоря не знаю что лучше прыжки или перезапись. Возможно найдутся люди умнее и заявят мол диск всегда перезаписывается. Не знаю что конкретно на физическом уровне там делается. Исходил из логики. Access будет удобней использовать, но там есть ограничения по объему памяти. Да и по скорости он проигрывает, но удобен в конструкторе, особенно на этапе создания, когда в процессе может понадобиться добавить новое поле в базу данных. А в своем движке приходится допилить функционал, чтоб поле безопасно добавить или удалить, чтоб при необходимости сжать базу (очистить от удаленных записей). Кстате, потом сделал еще один гибрид интересный где использовались два файла: один статический а другой динамический. Динамический только для текста, а в статическом были уже ссылки на точку входа информации в динамическом файле. Через 5 лет посмотрел на весь этот чудо код и офигел сколько времени было потрачено
Как всегда афигенный контент! Всё раскладываешь по полочкам и показываешь наглядный пример! Если бы ты записал серию уроков по какому-либо ЯП или технологии, я бы в запой просмотрел всё!
Спасибо за качественный контент! Подача материала отличная, сразу видно, что Автор знает о чём говорит. Могу сказать пару слов о Связанных списках: Преимущество Однонаправленного списка перед Двунаправленным в чуть меньшем потреблении памяти на каждый элемент списка и чуть более быстром выполнении некоторых функций. На этом его преимущества заканчиваются, и проявляется множество НЕпреимуществ. К примеру: Поиск/Удаление/Вставка в Двунаправленном списке можно начать как с начала, так и с конца. То-есть, если Index < Length / 2 = начинаем идти с Head-a к нужному элементу, а если Index > Length / 2 с Tail-a назад к нужному элементу. А в Однонаправленном списке тебе придётся идти всегда сначала. По-этому, зачастую, Двунаправленные списки выигрывают у Однонаправленных. Я промолчу о некоторых функциях, типа Реверса данных внутри списка или их Смещения (Не Nod-ов), там Однонаправленные списки сразу проигрывают...
У списков есть ещё один весомый минус помимо дополнительного поля с указателем, жрущего память. Это malloc, который к тому же враппер к системному вызову alloc, который работает на VAD таблицах, которые также жрут память. Особенно когда элементы списка 10-50 байт в большинстве своём случаев, а alloc работает со страницами памяти по 4 килобайта. Это лютейший стресс для операционки. А некоторые операционки не умеют в принципе выделять память меньше страницы и получается что на элемент, в смысле на каждый элемент, размером в несколько байт улетает ровно 4 килобайта. Короче списки - величайшее зло и ошибка. Можно без них если чуть повнимательней отнестись к организации алгоритмов и структур данных.
@@MrRastler Зачем писать велосипеды? Есть уже готовый список называется ArrayList. Если нужна скорость добавления данных то установите capacity правильное значение и arrayResize ни когда не произойдет. Если что элементы списка это 32 битные ссылки, поэтому смело можно задавать капасити в 8 мегабайтов, если точно знаете что у вас в списках может быть миллион элементов
Разве malloc может выделить 4кб памяти на КАЖДЫЙ элемент? Да, минимальный размер памяти, который можно "попросить" у операционной системы - это размер страницы виртуальной памяти. Далее уже задача libc при вызове malloc постараться задействовать куски этой страницы, и только если не получилось - запрашивать у операционной системы. Это уже не говоря о всяких jemalloc с кучей эвристик - thead-local буферы для обьектов фиксированной маленькой величины и тд В крайней случае, можно выделять единым куском память память под 100, 200, 400 узлов списка и потом использовать их - реализовать pool allocator Большим недостатком при этом будет частые промахи по кэшу - но это неизбежно И все таки списки бывают довольно полезны - как без них реализовывать персистентные(ну даже персистентный стек) или lock-free (например Michael-Scott Queue) структуры?
Тонкость на которой любят валить на собеседованиях по C# и Java: массивы всегда являются ссылочным типом и следовательно память всегда будет выделяться в управляемой куче (за исключением unsafe кода), в стеке будет находиться указатель на выделенную область памяти. В C/C++ по умолчанию массив определяется в стеке, либо с помощью malloc определяется в куче.
Очень круто. За 13 минут благодаря крутому визуалу и грамотным объясниям вспомнил все, что в универе проходили чуть ли не целый семестр. Было бы очень круто так же разобрать более сложные структуры, вроде тех же хеш-таблиц. А ещё было бы полезно делать референсы на популярные языки, например, "массив в С - это чистый массив, а вот в плюсах - это двунаправленный связный список" Ps не надо пожалуйста тригериться, про с/с++ я написал для примера и знаю что это не так
Здравствуйте Алек. Я так понимаю, что когда то будет продолжение.. Дисциплина "Алгоритмы и структуры данных" интересная и большая. По структурам: хэш-таблицы (с открытым, закрытым хэшированием), целая роща всяких деревьев с их самобалансировкой. Кстати, есть ещё "списки с пропусками". Это когда при движении прыгают через несколько узлов. При приближении к цели - постепенно через меньшее количество узлов. Т.е., там присутствуют узлы с разным количеством "next", как бы разной "высоты". По алгоритмам: семейство сортировок . Начиная с трёх базовых O(n2). Потом сортировка Шелла, прочесыванием. Потом O(n*ln(n)) - Хоара, слиянием, пирамидальная. Ну, и мало знакомые - поразрядные (распределением) - для целых чисел . Для 4-хбайтных целых чисел - у поразрядной сортировки O(4n). Сравните: 4 и ln(n). Круче же, согласитесь! А почему то не не знают )) Ее на списках применять надо конечно. На массивах памяти много надо. Для строк есть запатентованная поразрядная: abc-sort. Поиск подстроки в строке. Другими словами: слова или предложения в тексте. Или какой-либо последовательности (сигнатуры) в бинарном содержимом. Алгоритмы Кнута-Морриса-Пратта, Боуэра-Мура-Хорспула, Рабина-Карпа. Это основные вещи, которые просто интересно знать!
@@404Negative Если быть принципиальным, то да. Вы правы. Однако в своем комментарии я делал упор на практическую составляющую. Программистов интересует именно это, они не математики. Даже в литературе встречается что-нибудь подобное как я написал, O(4n). В первом приближении это допустимо. После округления, по правилам асимптотической точности, O(4n) будет O(n). Это я знаю. Думаю и Вам было понятно, что я имею ввиду. Я ведь акцентировал внимание на разнице между: 4 и ln(n). Т.е., буквально это выглядит так: T1=k*4*n и T2=k*ln(n)*n. Это конкретно время работы сортировки! Где k-некий коэффициент, одинаковый для разных сортировок при определенных условиях. Это один и тот же набор данных, одно и то же ЭВМ, с примерно одинаковой загруженностью операционной системы другими "посторонними" заданиями. Для примера, у математиков все 3 базовые сортировки, конечно O(n2). Однако программист должен представлять, у "пузырька" и "извлечения(выбора)" количество сравнений пропорционально n2/2, а у "вставки (включения)" n2/4. Это конкретные цифры, которые определяют время сортировки в вашем приложении! На основе уже этого программист будет делать выбор.
Я бы все таки рекомендую именовать поиск в массиве - доступом к элементу. Это не поиск в чистом виде. К примеру если взять код который на экране когда идет речь про поиск в списке - которое О(n) , где сверяется некая мифическая data, то такой код и на массиве будет за O(n) работать. Но это не отменяет крутости видоса!
Видео класс. Есть код для более глубокого ознакомления, тут же есть принципиальная картинка что происходит. Очень удобно. А звуковое оформление вообще топ.
5:36 Поиск в массиве имеет сложность О(1) ?? Не Поиск, а Доступ! Поиск как раз в неотсортированном массиве O(n), а в отсортированном зависит от метода, но все равно НЕ О(1)
Кстати, если тебе понравится такая тема, то предлагаю записать видео о двойной буферизации (когда используется два буфера для байтов данных). Это очень связано с тематикой канала, потому что такие алгоритмы помогают избежать например мерцания экрана. Но вообщем ты показываешь правильные вещи, респект и удачи в дальнейшем желаю.
Шок. Вся правда о массивах. Материал, запрещённый в официальной литературе по компьютерным технологиям. По существу, материал качественный, по-моему, подход автора серьёзен и строг :-)
Можно довольно несложным путём сделать, чтобы операция добавления и удаления в массив работала за O(1). Для этого нужно при добавлении нового элемента в массив в ситуации, когда свободных "ячеек" нет - создавать новый массив не с размером N+1, а с размером N*2. А при удалении - если количество оставшихся после удаления элементов в массиве меньше, чем половина длины - то нужно создать новый массив, размером в 2 раза меньше, и перенести туда все оставшиеся элементы. Допустим, что у нас изначально массив размера 10, и мы добавляем туда 10 элементов. Тогда при добавлении первого элемента - у нас произойдёт создание нового массива, размером 20 + копирование 10 исходных элементов + добавление 1 элемента - т.е. 11 операций. При этом добавление остальных 9 элементов - займёт 9 операций. В сумме получится, что добавление 10 элементов заняло 20 операций. А значит, добавление 1 элемента заняло, в среднем, 2 операции. Если добавляем 100 элементов при изначальном количество 10 - то количество операций будет такое: 11 + 9 = 20 - чтобы добавить 10 элементов 21 + 19 = 40 - чтобы добавить ещё 20 элементов 41 + 39 = 80 - чтобы добавить ещё 40 элементов 81 + 29 = 110 - чтобы добавить оставшиеся 30 элементов. Получается, суммарное количество операций - 250, что в среднем представляет из себя 2.5 операции на добавление 1 элемента. При 1000 элементов - это будет так: 20 + 40 + 80 + 160 + 320 + 641 + 379 = 1640. Получится, в среднем, 1.64 операции для добавления 1 элемента. При 10000 элементов - это будет: 20 + 40 + 80 + 160 + 320 + 640 + 1280 + 2560 + 5121 + 4899 = 15120 - уже 1.512 операций для добавления одного элемента. Если так продолжать - то в пределе количество операций для добавления одного элемента будет 1 - что и является асимптотикой O(1). Чтобы было в среднем ровно 1 операция на добавление - нужно, чтобы на добавление M элементов нужно было M операций. Худший вариант, который может быть - это когда у нас изначально 1 элемент в массиве. В такой ситуации - добавление M элементов потребует 2+4+8+16+... операций - из которых половина уйдёт на дублирование массив, а половина - на добавление M элементов. Количество шагов (n) можно рассчитать как логарифм по основание 2 от M, округлённый в нижнюю сторону + разница между M и этой суммой оставшихся элементов - но это значение не больше M - поэтому общее количество шагов можно считать как логарифм от M, округлённый в верхнюю сторону. Сумма такой геометрической прогрессии - это 2*(2^n-1) =2^(n+1)-2. Учитывая, что n - количество шагов - это ⌈log_2_M⌉, получится, что сложность добавления M элементов - это O(2^(⌈log_2_M⌉+1)-2) = O(2*(M+1)-2) = O(2*M) = O(M) (я предполагаю, что, в худшем случае, округление вверх даст M+1). Ну и отсюда вывод, что добавление одного элемента - O(1). Немного кривоватое доказательство - но какое есть. Примерно аналогичные рассуждения и для удаления.
Это конечно всё простенько, просто ужас сколько всего есть сложного в информатике, и поэтому становиться интересно. А самое главное, что математика тут играет ключевую роль, она помогает анализировать алгоритмы, описывать наш окружающий мир и тд. Поэтому самым главным инструментом программиста является именно математика.
6:20 в java приходиться вручную создавать новый массив перезаписывать в него данные, а потом добавлять что хотел, по этому я массивы уже давно забыл...
Вообще, при удалении в обычном массиве можно менять удаляемый элемент с последним местами, и уменьшать размер на 1. Теряем строгий порядок (который, на самом деле, нужен не всегда), зато получаем удаление за О(1). Оптимизаций куча. По опыту, при всех плюсах, связный список нужен только в очень небольшом пуле задач.
@@Dmytro-Tsymbaliuk а кто говорит что удаление в массиве это = освобождению памяти? В java и шарпе вообще отсутствует такое понятие как освобождение памяти. Там невозможно ни какими способами освободить память. Даже если прописать GC.Collect() это не дает гарантию что ваш обьект будет удален. Это легко проверить прописав в деструкторе лог. Вызов деструктора очень рандомная штука, сами майкософты запрещают в деструкторе класса что-то писать и за такой код бьют по рукам.
@@Dmytro-Tsymbaliuk Так разберись в фундаментальных вещах- Удаление из массива НЕ равно освобождение памяти. Это противоречит всем принципам программирования. Даже принципам в С++. Уже молчу про языки высокого уровня где освободить память невозможно
@@Dmytro-Tsymbaliuk Например возьми сишные функции работы со строками. Заметь ни одна сишная функция по операция со строками не удаляет память под строку. Хотя ты вроде обрезал строку слева или справо. И название функции как раз обрезка строки. Но по факту память та же самая диль указатель передвинулся вперед если удаление из начала строки. Или символ нуля перенесли если удаление из конца
У тебя хорошо поставлен голос, хорошо рассказываешь, но иногда убаюкивает. Какой-то интересный тон есть :). Такое ощущение, что сейчас в транс войду. Наверно музыка еще создает трассовое состояние.
Контент шикарный! Спасибо за труд! И пожелание: Интересно будет видео о процессах и потоках ОС. Как они создаются, хранятся, исполняются. Их параллельный запуск, приостановка, возобновление, взаимодействие. Что такое процесс? Есть ли предел количества потоков? Как чередуется исполнение потоков в CPU?
@@diknik1148 чувак, что за наезд? Ты еще погуглить посоветуешь? Это лишь было пожелание к новому контенту. Было бы здорово увидеть видео от автора с разбором этой темы, т.к. считаю что автор умеет хорошо разъяснять. Наверное, окружающие люди считают тебя токсичным, как думаешь?
@@Mikhail-s5v, кстати, погуглить тоже неплохой совет. А наезд мой в том, что твои вопросы не алгоритмические и не требуют визуализации для ответа. Потом собеседуешь таких любителей просмотров видосов, а у них знания нет. Поверхностно глянут что-то в видео, а доки читать не удосуживаются. Навык развития чтения документации нужно развивать, а не на ютуб идти по каждому чиху.
строго говоря же ведь динамический массив не может тоже свой размер менять. Можно его удалить и аллоцировать новый, а вот resize насколько я помню невозможен на низком уровне. При этом мы присваиваем старому указателю адрес на новый динамический массив. При этом указатель сам по себе можно не связывать в своём мышлении с динамическим массивом. Так как он может быть перезаписан другим адресом. Мы можем вообще создать ссылку на динамический массив, а старый указатель удалить, удалить ссылку, сделать новый указатель или ссылку и присвоить в него адрес массива. В общем не так важно каким способом мы помним по какому адресу хранится динамический массив, как много указателей для этого используем. Динамический массив ничего не почувствует, потому что это отдельная сущность. resize же на верхнем уровне работает на основе высвобождения памяти и аллокации нового участка памяти и копирования указателя на участок этой памяти в объект-обёртку, который обслуживает динамический массив.
по поводу поиска в массиве хочу сказать что O(1) это доступ на адрес так как в Си например *arr == arr[0] , получается что первый элемент это так же указатель на массив, и это облегчает найти нужный нам адрес но никак элемент который лежит в адресе, а поиск элемента уже O(n).
Работа с массивами может быть проще! ведь можно, 1.массив создать изначально больше, и расчета количества возможных элементов. 2. Можно задать определенное число являющееся показателем того что эта ячейка массива пуста. например в 2 байтовом массиве нам нужны только числа в дипаазоне от 0 до 1000, значит можно сказать что 65535 - это значение пустое! 3. Можно сделать битовую карту массива хранящую флаг пустой ячейки. Искать в битовой карте пустые ячейки в большом массиве можно быстрее... если число не равно FFFFFFFF значит тут в одной из 32 ячеек есть пустой элемент.
1 - Всё зависит от задачи, но в любом случае часть памяти не занята. Но на современном железе это обычно не проблема. 2 - Поиск условно пустых ячеек тоже занимает время и в этом случае отличие от связанных списков не велико. 3 - Несколько быстрее, но те же проблемы, как во 2-м случае. Плюс карта будет занимать дополнительно памяти в 1/8 от размера основного массива и в случае больших данных будет занимать прилично места. Плюс (не уверен) убивается профит от процессорного кэширования. Задачи по поиску элемента (и многие другие) отлично решают деревья и контейнеры. Возможно автор рассказывал, если нет, может расскажет. ЗЫ Во втором случае, если сортировка не важна, можно удалять ячейку просто записав на место "удаляемой" значение последней ячейки массива и уменьшив значение счётчика элементов массива.
Забыл, это всё не плохо при удалении одной или нескольких ячеек, а если нужно "вырезать" в середине массива чуть ли не половину ячеек? В этом случае будет гораздо дешевле перебросить "хвост" на место вырезаемых ячеек.
@@DenisShaver естественно, все зависит от задач это не замена списков и деревьев. Это расширение возможностей конкретно массивов. 3ий вариант при оч больших массивов можно и на сектора разбить когда 1 бит в карте значит блок из 16 элементов и нужный пустой найти перебором 16 элементов. Можно также на базе массива рализовать очередь как Fifo, так и аналог стека. Вместо реализации через списки. Сама очередь в этом случае будет занимать меньше памяти, и работать в теории быстрее, но она будет иметь ограниченную глубину.
@@DenisShaver Вы все описали ArrayList. И менеджер памяти alloc не умеет создавать блоки памяти меньше 4кб. Поэтому даже если вы создадите массив на 1 элемент, все равно будет задействоваться 4кб. Это можно проверить в с++. Создать массив и через цикл обойти его за пределами его размера. Массив нужно создавать не через new потому что там программное прерывание вылетит. А через malloc. Тогда будет уже аппаратное прерывание на 4097 байте
возможно я не совсем понял, но связанный список где хранится? в массиве? в чем смысл тогда что то выдумывать если по факту это массив пользовательских типов данных? ИЛИ ЭТО И ЕСТЬ СУТЬ ВИДЕО что есть массивы и есть массивы с пользовательскими типами данных, только одни так и называются массивами данных а другие - связанные списки (ну что б сложней было разобраться)
Было бы круто от тебя видеть цикл видео про ООП, принципы правильного проектирования кода и подобное. Нравится как ты все визуализируешь, сразу все понятно становится
Рано или поздно все сводится к процедурному программированию.
@Пиво и приколы Перегорание
@@DocNight процессор сгорает?
@@DocNight оаоаоаоао. Как круто.
(...принципы правильного проектирования кода...) видео на 3 секунды. Их не существует... правильных. Они есть рзные, каждые служат своей цели. Проектирование кода... структурирование кода ещё туда-сюда.
Проектируют, обычно, системы, компоненты, модули методом декомпозиции функциональной, структурной, логической, физической, организационной.
2023 год, забудьте уже про ООП, в век поведенческих моделей -- дитя рожённое сумрачным гением Гарди Буча и Ива Якобса под конец 80-х. Имеет очень ограниченое применение, для прикладных систем.
ООП - как парадигма моделирования и описания реальных систем ещё более менее, но с оговорками. Но как принцип структурирования кода уже давно не выдерживает критики.
Поиск в массиве имеет сложность O(n), это доступ к конкретному элементу по известному индексу O(1).
оговорочка
Очень хорошая подача, сам до конца не знаю как устроены все структуры, поэтому жду продолжения!
Алекс, спасибо огромное, для меня твой канал просто как глоток свежего воздуха и огромное вдохновение. Моя жизнь и профессия давно устоялись, и работа моя не связана с программирование и IT. Сейчас изучаю Си просто для души, в школе увлекался бэйсиком и паскалем, благодаря этому поступил в институт без экзаменов после олимпиады, потом забросил, некогда было. Отсутствие необходимости изучать то что модно и в тренде, дает возможность изучать программирование так, как мне интересно а не требуется для обретения или смены профессии. Твой канал для меня просто находка, именно та информация которую я ищу. И пусть в современном мире низкоуровневое программирование не особо нужно, а все алгоритмы разложены по полочкам и изучены, пусть. Мне нравится именно корни и основы, и я буду программировать так, как раньше, экономя байты памяти, оптимизируя какие то задачи, что бы это могло запускаться на любом примитивном железе. Это самый кайф! Спасибо!
Спасибо за труд, но всё же как-то недостаточно. Я уже настроился и ожидал увидеть информацию о других стурктурах, как вдруг видео закончилось. Жду продолжения.
видео называется "вся правда о массивах"
Как же круто подаётся материал! Прямо интересно смотреть и прямо сразу хочется ещё больше видео!
Низкий поклон автору за такие шедевры! Всегда с нетерпением жду новые видео!
Самый великолепный канал по програмированию
Красавчик, Алекс. Контент пушка, пили дальше. Будь уверен, благое дело делаешь, сил тебе, хорошей работы и здоровья, брат!
Непревзойденно! Браво! Супер важное и сложное простым языком, понятной картинкой, стильно даже! Ты лучший!
Однозначно эти видео надо включать на уроках информатики... Всё доступно и понятно, а не нудятина которую преподают.
Великолепно изложено! Огромная благодарность за титанический труд!
Спасибо Алек за то, что делишься с нами своими знаниями
Жду объяснение следующих структур СПС за видео.
Alek - Спасибо тебе за стольчудесный контент. Ты просто огромнейшый молодец, за столь краткий период времени ты уже сделал огромный вклад в жизни других людей, жаль что я больше не смогу видеть твои видео. 😁😗😉
Видос невероятный, монтаж на высоте как и объяснение, продолжай в том же духе!
Я делал такую базу, не подозревая, что именно о такой будут когда-то рассказывать. Нигде не учился. Пришел логично. Первый прототип базы был статический, но очень быстрый. И в момент, когда я не знал какой именно длинны будут некоторые текстовые поля, пришлось изобретать велосипед. Добавил ссылки. При удалении, информация просто не учитывалась. По факту она не удалялась. Ровно также вижу и работает Microsoft Access. Динамическая база данных при GOTO на нужный нам столбец работает медленней чем статическая, поскольку в статической можно просто умножить наш указатель на длину и мы точно попадем в начало нужной нам информации. А здесь нам уже нужно высчитывать из ссылки к ссылке пока не получим желаемую. Вначале я использовал только указатель "вперед". Но позже столкнулся с проблемой вставки информацию в середину и решил не раздвигать ячейки, не перезаписывать жёсткий диск за каждый раз, а это даже очень актуально при использовании SSD накопителя. По этому добавил указатель "назад" и это позволило мне добавлять значения в конец, но подавать пользователю это как будто данные находятся в середине. Чесно говоря не знаю что лучше прыжки или перезапись. Возможно найдутся люди умнее и заявят мол диск всегда перезаписывается. Не знаю что конкретно на физическом уровне там делается. Исходил из логики. Access будет удобней использовать, но там есть ограничения по объему памяти. Да и по скорости он проигрывает, но удобен в конструкторе, особенно на этапе создания, когда в процессе может понадобиться добавить новое поле в базу данных. А в своем движке приходится допилить функционал, чтоб поле безопасно добавить или удалить, чтоб при необходимости сжать базу (очистить от удаленных записей). Кстате, потом сделал еще один гибрид интересный где использовались два файла: один статический а другой динамический. Динамический только для текста, а в статическом были уже ссылки на точку входа информации в динамическом файле. Через 5 лет посмотрел на весь этот чудо код и офигел сколько времени было потрачено
Благодарю Вас за ваш труд! Очень интересная подача материала. Не жалею, что нашёл этот канал.
Возвращаюсь к каждому видео по 3 раза, и как в хорошей книге нахожу что-то новое. Благодарю!
Как всегда афигенный контент! Всё раскладываешь по полочкам и показываешь наглядный пример! Если бы ты записал серию уроков по какому-либо ЯП или технологии, я бы в запой просмотрел всё!
Я сейчас как раз изучаю массивы в ассемблере. Так как ассемблер самый низкоуровневый, то это видео для меня стает очень актуальным. Спасибо
Спасибо за качественный контент! Подача материала отличная, сразу видно, что Автор знает о чём говорит.
Могу сказать пару слов о Связанных списках:
Преимущество Однонаправленного списка перед Двунаправленным в чуть меньшем потреблении памяти на каждый элемент списка и чуть более быстром выполнении некоторых функций.
На этом его преимущества заканчиваются, и проявляется множество НЕпреимуществ.
К примеру:
Поиск/Удаление/Вставка в Двунаправленном списке можно начать как с начала, так и с конца.
То-есть, если Index < Length / 2 = начинаем идти с Head-a к нужному элементу,
а если Index > Length / 2 с Tail-a назад к нужному элементу.
А в Однонаправленном списке тебе придётся идти всегда сначала.
По-этому, зачастую, Двунаправленные списки выигрывают у Однонаправленных.
Я промолчу о некоторых функциях, типа Реверса данных внутри списка или их Смещения (Не Nod-ов), там Однонаправленные списки сразу проигрывают...
Алек молодец ! всё очень понятно и без воды так держать!
Массивы и связанные списки это так сказать база, в книге «Грокаем алгоритмы» очень доходчиво объяснены.
Круто.
Я наконец-то стал что-то понимать!
Спасибо, друг!
Нужно продолжение.
Листы, Мапы, и т.д. и т.п. что и как устроено и как работает)
Ну о списках здесь было рассказано) Если не брать во внимание, что существуют списки реализованные поверх массивов
Просто мурашки от этих видосов, такой уже умничка 😍
Подача и полезность в одном видео - это редкость! красава
Спасибо, продолжай в том же духе!
Автору спасибо, за столь огромный труд!
Респект, брачо! всего тебе хорошего и побольше!
Очень круто. Добавляй в конце таблицу со сравнению сложностей пожалуйста
Большое спасибо!
У списков есть ещё один весомый минус помимо дополнительного поля с указателем, жрущего память. Это malloc, который к тому же враппер к системному вызову alloc, который работает на VAD таблицах, которые также жрут память. Особенно когда элементы списка 10-50 байт в большинстве своём случаев, а alloc работает со страницами памяти по 4 килобайта. Это лютейший стресс для операционки. А некоторые операционки не умеют в принципе выделять память меньше страницы и получается что на элемент, в смысле на каждый элемент, размером в несколько байт улетает ровно 4 килобайта. Короче списки - величайшее зло и ошибка. Можно без них если чуть повнимательней отнестись к организации алгоритмов и структур данных.
Отличное замечание. И вообще, было бы неплохо автору указать как выделяться память ОС, из этого можно пересмотреть вообще подход к данным.
@@MrRastler Зачем писать велосипеды? Есть уже готовый список называется ArrayList. Если нужна скорость добавления данных то установите capacity правильное значение и arrayResize ни когда не произойдет. Если что элементы списка это 32 битные ссылки, поэтому смело можно задавать капасити в 8 мегабайтов, если точно знаете что у вас в списках может быть миллион элементов
Разве malloc может выделить 4кб памяти на КАЖДЫЙ элемент? Да, минимальный размер памяти, который можно "попросить" у операционной системы - это размер страницы виртуальной памяти. Далее уже задача libc при вызове malloc постараться задействовать куски этой страницы, и только если не получилось - запрашивать у операционной системы. Это уже не говоря о всяких jemalloc с кучей эвристик - thead-local буферы для обьектов фиксированной маленькой величины и тд
В крайней случае, можно выделять единым куском память память под 100, 200, 400 узлов списка и потом использовать их - реализовать pool allocator
Большим недостатком при этом будет частые промахи по кэшу - но это неизбежно
И все таки списки бывают довольно полезны - как без них реализовывать персистентные(ну даже персистентный стек) или lock-free (например Michael-Scott Queue) структуры?
Тонкость на которой любят валить на собеседованиях по C# и Java: массивы всегда являются ссылочным типом и следовательно память всегда будет выделяться в управляемой куче (за исключением unsafe кода), в стеке будет находиться указатель на выделенную область памяти. В C/C++ по умолчанию массив определяется в стеке, либо с помощью malloc определяется в куче.
Очень круто. За 13 минут благодаря крутому визуалу и грамотным объясниям вспомнил все, что в универе проходили чуть ли не целый семестр.
Было бы очень круто так же разобрать более сложные структуры, вроде тех же хеш-таблиц.
А ещё было бы полезно делать референсы на популярные языки, например, "массив в С - это чистый массив, а вот в плюсах - это двунаправленный связный список"
Ps не надо пожалуйста тригериться, про с/с++ я написал для примера и знаю что это не так
Ха) моя курсовая работа с первого курса. Жаль , что видео вышли так поздно. Уж очень я страдал на с++ .
Спасибо за контент.
Я кнш это всё знаю, но я не могу пропустить ни один твой видос. Они прекрасны😍
Здравствуйте Алек. Я так понимаю, что когда то будет продолжение.. Дисциплина "Алгоритмы и структуры данных" интересная и большая.
По структурам: хэш-таблицы (с открытым, закрытым хэшированием), целая роща всяких деревьев с их самобалансировкой. Кстати, есть ещё "списки с пропусками". Это когда при движении прыгают через несколько узлов. При приближении к цели - постепенно через меньшее количество узлов. Т.е., там присутствуют узлы с разным количеством "next", как бы разной "высоты".
По алгоритмам: семейство сортировок . Начиная с трёх базовых O(n2). Потом сортировка Шелла, прочесыванием. Потом O(n*ln(n)) - Хоара, слиянием, пирамидальная. Ну, и мало знакомые - поразрядные (распределением) - для целых чисел . Для 4-хбайтных целых чисел - у поразрядной сортировки O(4n). Сравните: 4 и ln(n). Круче же, согласитесь! А почему то не не знают )) Ее на списках применять надо конечно. На массивах памяти много надо. Для строк есть запатентованная поразрядная: abc-sort.
Поиск подстроки в строке. Другими словами: слова или предложения в тексте. Или какой-либо последовательности (сигнатуры) в бинарном содержимом. Алгоритмы Кнута-Морриса-Пратта, Боуэра-Мура-Хорспула, Рабина-Карпа.
Это основные вещи, которые просто интересно знать!
@@404Negative Если быть принципиальным, то да. Вы правы. Однако в своем комментарии я делал упор на практическую составляющую. Программистов интересует именно это, они не математики. Даже в литературе встречается что-нибудь подобное как я написал, O(4n). В первом приближении это допустимо. После округления, по правилам асимптотической точности, O(4n) будет O(n). Это я знаю. Думаю и Вам было понятно, что я имею ввиду.
Я ведь акцентировал внимание на разнице между: 4 и ln(n). Т.е., буквально это выглядит так: T1=k*4*n и T2=k*ln(n)*n. Это конкретно время работы сортировки! Где k-некий коэффициент, одинаковый для разных сортировок при определенных условиях. Это один и тот же набор данных, одно и то же ЭВМ, с примерно одинаковой загруженностью операционной системы другими "посторонними" заданиями.
Для примера, у математиков все 3 базовые сортировки, конечно O(n2). Однако программист должен представлять, у "пузырька" и "извлечения(выбора)" количество сравнений пропорционально n2/2, а у "вставки (включения)" n2/4. Это конкретные цифры, которые определяют время сортировки в вашем приложении! На основе уже этого программист будет делать выбор.
Это просто щииииииииикарноооооооо! Топовый котент! Все очень наглядно) огромное спасибо за Ваш труд
Кольцевой однонаправленный список -> план эвакуации при пожаре.
Я бы все таки рекомендую именовать поиск в массиве - доступом к элементу. Это не поиск в чистом виде. К примеру если взять код который на экране когда идет речь про поиск в списке - которое О(n) , где сверяется некая мифическая data, то такой код и на массиве будет за O(n) работать. Но это не отменяет крутости видоса!
Случайно попал на видос! Подписываюсь) Автор крут!
спасибо, Брат!
пока что, структуры данных воспринимаю только в твоём изложении
Видео класс. Есть код для более глубокого ознакомления, тут же есть принципиальная картинка что происходит. Очень удобно.
А звуковое оформление вообще топ.
Подписался, спасибо большое! Как раз не хватает нормального материала на эту тему.
Спасибо за выпуск!👍
Извините, я не понял, что на моменте 5:37, обозначает ? А так же на 6:38 ?
5:36 Поиск в массиве имеет сложность О(1) ?? Не Поиск, а Доступ! Поиск как раз в неотсортированном массиве O(n), а в отсортированном зависит от метода, но все равно НЕ О(1)
gold in front of my eyes, very well done content. Thank you
Кстати, если тебе понравится такая тема, то предлагаю записать видео о двойной буферизации (когда используется два буфера для байтов данных). Это очень связано с тематикой канала, потому что такие алгоритмы помогают избежать например мерцания экрана. Но вообщем ты показываешь правильные вещи, респект и удачи в дальнейшем желаю.
Отлично!
Шок. Вся правда о массивах. Материал, запрещённый в официальной литературе по компьютерным технологиям. По существу, материал качественный, по-моему, подход автора серьёзен и строг :-)
Реально так контент из которого, начинающим и даже программистам с опытом, нужно впитывать каждое слово.
Графика кайф! Братан хорош, давай давай вперёд! Контент в кайф, можно ещё, вот этого и вон того? Вообще красавчик! Можно вот этого вот почаще?
Супер объяснение, кратко и четко
Сделай видео по алгоритмам! Понятно, что по хорошему оно займет не один час, но мне бы очень хотелось увидеть такое видео на твоём канале
Можно довольно несложным путём сделать, чтобы операция добавления и удаления в массив работала за O(1).
Для этого нужно при добавлении нового элемента в массив в ситуации, когда свободных "ячеек" нет - создавать новый массив не с размером N+1, а с размером N*2.
А при удалении - если количество оставшихся после удаления элементов в массиве меньше, чем половина длины - то нужно создать новый массив, размером в 2 раза меньше, и перенести туда все оставшиеся элементы.
Допустим, что у нас изначально массив размера 10, и мы добавляем туда 10 элементов. Тогда при добавлении первого элемента - у нас произойдёт создание нового массива, размером 20 + копирование 10 исходных элементов + добавление 1 элемента - т.е. 11 операций. При этом добавление остальных 9 элементов - займёт 9 операций.
В сумме получится, что добавление 10 элементов заняло 20 операций. А значит, добавление 1 элемента заняло, в среднем, 2 операции.
Если добавляем 100 элементов при изначальном количество 10 - то количество операций будет такое:
11 + 9 = 20 - чтобы добавить 10 элементов
21 + 19 = 40 - чтобы добавить ещё 20 элементов
41 + 39 = 80 - чтобы добавить ещё 40 элементов
81 + 29 = 110 - чтобы добавить оставшиеся 30 элементов.
Получается, суммарное количество операций - 250, что в среднем представляет из себя 2.5 операции на добавление 1 элемента.
При 1000 элементов - это будет так:
20 + 40 + 80 + 160 + 320 + 641 + 379 = 1640.
Получится, в среднем, 1.64 операции для добавления 1 элемента.
При 10000 элементов - это будет:
20 + 40 + 80 + 160 + 320 + 640 + 1280 + 2560 + 5121 + 4899 = 15120 - уже 1.512 операций для добавления одного элемента.
Если так продолжать - то в пределе количество операций для добавления одного элемента будет 1 - что и является асимптотикой O(1).
Чтобы было в среднем ровно 1 операция на добавление - нужно, чтобы на добавление M элементов нужно было M операций.
Худший вариант, который может быть - это когда у нас изначально 1 элемент в массиве.
В такой ситуации - добавление M элементов потребует 2+4+8+16+... операций - из которых половина уйдёт на дублирование массив, а половина - на добавление M элементов.
Количество шагов (n) можно рассчитать как логарифм по основание 2 от M, округлённый в нижнюю сторону + разница между M и этой суммой оставшихся элементов - но это значение не больше M - поэтому общее количество шагов можно считать как логарифм от M, округлённый в верхнюю сторону.
Сумма такой геометрической прогрессии - это 2*(2^n-1) =2^(n+1)-2. Учитывая, что n - количество шагов - это ⌈log_2_M⌉, получится, что сложность добавления M элементов - это O(2^(⌈log_2_M⌉+1)-2) = O(2*(M+1)-2) = O(2*M) = O(M) (я предполагаю, что, в худшем случае, округление вверх даст M+1).
Ну и отсюда вывод, что добавление одного элемента - O(1).
Немного кривоватое доказательство - но какое есть.
Примерно аналогичные рассуждения и для удаления.
Блин, ну ты и тему выбрал. Это всё я и так знал! :(
Повторение - мать учения
@@IDragonThunderI Можно было хотя бы про бинарные и лгбт деревья :)
Это конечно всё простенько, просто ужас сколько всего есть сложного в информатике, и поэтому становиться интересно. А самое главное, что математика тут играет ключевую роль, она помогает анализировать алгоритмы, описывать наш окружающий мир и тд. Поэтому самым главным инструментом программиста является именно математика.
Спасибо, очень крутые видео!
Отличная подача... Спасибо.
Спасибо за видео!
Афигенный ролик только бы вот были бы примеры с питоном, с++
Контент просто огонь !!!
полезное дело делаешь, спасибо, продолжай!
Спасибо за труды 👍👍👍
6:20 в java приходиться вручную создавать новый массив перезаписывать в него данные, а потом добавлять что хотел, по этому я массивы уже давно забыл...
6:50 сдвигать в java тоже самому надо все...
Вот это реально, больше чем просто лайк
Приятель, наверно только СВЯЗНЫЕ списки, а не связанные. Или они в узлы связаны?
Огромное спасибо автору! Очень полезная информация
Сделай видео, как монтируешь видео.
Хорошо рассказал! И мультик наглядный! Пили есчо!
Круто, спасибо за контент.
Помню в древних проектах создавали классы Array.
Я думал это из-за отсутствия std::vector.
Сейчас понимаю, что так работать с массивами проще.
Новый водосток подъехал, так ждал, так ждал
Вообще, при удалении в обычном массиве можно менять удаляемый элемент с последним местами, и уменьшать размер на 1. Теряем строгий порядок (который, на самом деле, нужен не всегда), зато получаем удаление за О(1). Оптимизаций куча. По опыту, при всех плюсах, связный список нужен только в очень небольшом пуле задач.
Но это не будет удалением, а просто неиспользованием выделенной программе памяти
@@Dmytro-Tsymbaliuk а кто говорит что удаление в массиве это = освобождению памяти? В java и шарпе вообще отсутствует такое понятие как освобождение памяти. Там невозможно ни какими способами освободить память. Даже если прописать GC.Collect() это не дает гарантию что ваш обьект будет удален. Это легко проверить прописав в деструкторе лог. Вызов деструктора очень рандомная штука, сами майкософты запрещают в деструкторе класса что-то писать и за такой код бьют по рукам.
@@serhiis_ а причем тут вообще джава и шарп, когда речь о фундаментальных вещах? Совершенно не в тему
@@Dmytro-Tsymbaliuk Так разберись в фундаментальных вещах- Удаление из массива НЕ равно освобождение памяти. Это противоречит всем принципам программирования. Даже принципам в С++. Уже молчу про языки высокого уровня где освободить память невозможно
@@Dmytro-Tsymbaliuk Например возьми сишные функции работы со строками. Заметь ни одна сишная функция по операция со строками не удаляет память под строку. Хотя ты вроде обрезал строку слева или справо. И название функции как раз обрезка строки. Но по факту память та же самая диль указатель передвинулся вперед если удаление из начала строки. Или символ нуля перенесли если удаление из конца
11:55 айя-яй две стрелк из 3 ведут в 4)))
У тебя хорошо поставлен голос, хорошо рассказываешь, но иногда убаюкивает. Какой-то интересный тон есть :). Такое ощущение, что сейчас в транс войду. Наверно музыка еще создает трассовое состояние.
Продолжай в том же духе, очень круто!!
Контент шикарный! Спасибо за труд! И пожелание:
Интересно будет видео о процессах и потоках ОС. Как они создаются, хранятся, исполняются. Их параллельный запуск, приостановка, возобновление, взаимодействие. Что такое процесс? Есть ли предел количества потоков? Как чередуется исполнение потоков в CPU?
Открыть и почитать доку вообще не вариант?
@@diknik1148 чувак, что за наезд? Ты еще погуглить посоветуешь?
Это лишь было пожелание к новому контенту. Было бы здорово увидеть видео от автора с разбором этой темы, т.к. считаю что автор умеет хорошо разъяснять.
Наверное, окружающие люди считают тебя токсичным, как думаешь?
@@Mikhail-s5v, кстати, погуглить тоже неплохой совет.
А наезд мой в том, что твои вопросы не алгоритмические и не требуют визуализации для ответа.
Потом собеседуешь таких любителей просмотров видосов, а у них знания нет. Поверхностно глянут что-то в видео, а доки читать не удосуживаются.
Навык развития чтения документации нужно развивать, а не на ютуб идти по каждому чиху.
Массивы: чёрт нас раскрыли, сваливаем
Единственный канал где не проматываю рекламу, чтоб поддержать автора.
Интеграцию?😂
@@Долой_Уныние да
Не смотрел, но уже понял, что топ
Если я это видео смотрел 20 лет назад...то я бы стал программистом
круто, спасибо!
строго говоря же ведь динамический массив не может тоже свой размер менять. Можно его удалить и аллоцировать новый, а вот resize насколько я помню невозможен на низком уровне. При этом мы присваиваем старому указателю адрес на новый динамический массив. При этом указатель сам по себе можно не связывать в своём мышлении с динамическим массивом. Так как он может быть перезаписан другим адресом. Мы можем вообще создать ссылку на динамический массив, а старый указатель удалить, удалить ссылку, сделать новый указатель или ссылку и присвоить в него адрес массива. В общем не так важно каким способом мы помним по какому адресу хранится динамический массив, как много указателей для этого используем. Динамический массив ничего не почувствует, потому что это отдельная сущность. resize же на верхнем уровне работает на основе высвобождения памяти и аллокации нового участка памяти и копирования указателя на участок этой памяти в объект-обёртку, который обслуживает динамический массив.
по поводу поиска в массиве хочу сказать что O(1) это доступ на адрес так как в Си например *arr == arr[0] , получается что первый элемент это так же указатель на массив, и это облегчает найти нужный нам адрес но никак элемент который лежит в адресе, а поиск элемента уже O(n).
Ничего не понял, но очень интересно. Жаль что мой уровень программирования пока еще слишком маленький что бы понимать такие вещи.
Скажите пожалуйста что за язык программирование в видео? Ради интереса.
когда видео про ассемблер?
Плюсую, тоже жду вторую серию. Такое чувство, будто автор сам посмотрел-посмотрел и испугался.
Вторая часть давно была написана, и уже несколько месяцев ожидает когда я её запишу и смонтирую.
Всему своё время.
@@AlekOS Фух, думал ты уже забросил
@@AlekOS Ура! Ждём, обязательно будем смотреть)
@@AlekOS, привет, ты забыл в телеграмм уведомить о видео)
Автору огромное спасибо за работу! А можешь подсказать что за фоновая музыка играет? Мне кажется она идеально подошла бы во время работы
Работа с массивами может быть проще! ведь можно,
1.массив создать изначально больше, и расчета количества возможных элементов.
2. Можно задать определенное число являющееся показателем того что эта ячейка массива пуста. например в 2 байтовом массиве нам нужны только числа в дипаазоне от 0 до 1000, значит можно сказать что 65535 - это значение пустое!
3. Можно сделать битовую карту массива хранящую флаг пустой ячейки. Искать в битовой карте пустые ячейки в большом массиве можно быстрее... если число не равно FFFFFFFF значит тут в одной из 32 ячеек есть пустой элемент.
1 - Всё зависит от задачи, но в любом случае часть памяти не занята. Но на современном железе это обычно не проблема.
2 - Поиск условно пустых ячеек тоже занимает время и в этом случае отличие от связанных списков не велико.
3 - Несколько быстрее, но те же проблемы, как во 2-м случае. Плюс карта будет занимать дополнительно памяти в 1/8 от размера основного массива и в случае больших данных будет занимать прилично места. Плюс (не уверен) убивается профит от процессорного кэширования.
Задачи по поиску элемента (и многие другие) отлично решают деревья и контейнеры. Возможно автор рассказывал, если нет, может расскажет.
ЗЫ Во втором случае, если сортировка не важна, можно удалять ячейку просто записав на место "удаляемой" значение последней ячейки массива и уменьшив значение счётчика элементов массива.
Забыл, это всё не плохо при удалении одной или нескольких ячеек, а если нужно "вырезать" в середине массива чуть ли не половину ячеек? В этом случае будет гораздо дешевле перебросить "хвост" на место вырезаемых ячеек.
@@DenisShaver естественно, все зависит от задач это не замена списков и деревьев. Это расширение возможностей конкретно массивов. 3ий вариант при оч больших массивов можно и на сектора разбить когда 1 бит в карте значит блок из 16 элементов и нужный пустой найти перебором 16 элементов.
Можно также на базе массива рализовать очередь как Fifo, так и аналог стека. Вместо реализации через списки. Сама очередь в этом случае будет занимать меньше памяти, и работать в теории быстрее, но она будет иметь ограниченную глубину.
@@iDDMZ А ну тогда зачем глупости писать? Всё уже придумано и незачем изобретать велосипед )))
@@DenisShaver Вы все описали ArrayList. И менеджер памяти alloc не умеет создавать блоки памяти меньше 4кб. Поэтому даже если вы создадите массив на 1 элемент, все равно будет задействоваться 4кб. Это можно проверить в с++. Создать массив и через цикл обойти его за пределами его размера. Массив нужно создавать не через new потому что там программное прерывание вылетит. А через malloc. Тогда будет уже аппаратное прерывание на 4097 байте
Только начал писать курсовую,на тему алгоритмов сортировки
Спасибо за видос! Расскажите пожалуйста про хеши.
Спасиба бальшой ты очен памог
Спасибо за инфу! Стал не много умнее)
Я бы хотел попросить о видео по расчёту сложности алгоритмов 🙏
Спасибо!
Понравилось, круто
возможно я не совсем понял, но связанный список где хранится? в массиве? в чем смысл тогда что то выдумывать если по факту это массив пользовательских типов данных? ИЛИ ЭТО И ЕСТЬ СУТЬ ВИДЕО что есть массивы и есть массивы с пользовательскими типами данных, только одни так и называются массивами данных а другие - связанные списки (ну что б сложней было разобраться)
интересно,всегда забываю про удаление, потому что это по сути обычная запись